Skip to main content

scirs2_graph/signed_directed/
types.rs

1//! Core types for signed and directed graph learning.
2//!
3//! Provides data structures representing signed graphs (with +/-1 edge signs),
4//! directed weighted graphs, and configuration/result types for their embeddings.
5
6/// A signed edge: carries a sign of +1 (positive) or -1 (negative).
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct SignedEdge {
9    /// Source node index.
10    pub src: usize,
11    /// Destination node index.
12    pub dst: usize,
13    /// Edge sign: +1 for positive, -1 for negative.
14    pub sign: i8,
15}
16
17impl SignedEdge {
18    /// Create a new signed edge.
19    pub fn new(src: usize, dst: usize, sign: i8) -> Self {
20        Self { src, dst, sign }
21    }
22
23    /// Return `true` if this edge is positive.
24    pub fn is_positive(&self) -> bool {
25        self.sign > 0
26    }
27}
28
29/// A directed weighted edge.
30#[derive(Debug, Clone, Copy, PartialEq)]
31pub struct DirectedEdge {
32    /// Source node index.
33    pub src: usize,
34    /// Destination node index.
35    pub dst: usize,
36    /// Edge weight.
37    pub weight: f64,
38}
39
40impl DirectedEdge {
41    /// Create a new directed edge.
42    pub fn new(src: usize, dst: usize, weight: f64) -> Self {
43        Self { src, dst, weight }
44    }
45}
46
47/// A signed (undirected) graph with separate positive and negative adjacency lists.
48#[derive(Debug, Clone)]
49pub struct SignedGraph {
50    /// Number of nodes.
51    pub n_nodes: usize,
52    /// All edges.
53    pub edges: Vec<SignedEdge>,
54    /// Positive adjacency list: `pos_adj[i]` = list of (neighbor, weight=+1.0).
55    pub pos_adj: Vec<Vec<usize>>,
56    /// Negative adjacency list: `neg_adj[i]` = list of (neighbor, weight=+1.0).
57    pub neg_adj: Vec<Vec<usize>>,
58}
59
60impl SignedGraph {
61    /// Create an empty signed graph with `n_nodes` nodes.
62    pub fn new(n_nodes: usize) -> Self {
63        Self {
64            n_nodes,
65            edges: Vec::new(),
66            pos_adj: vec![Vec::new(); n_nodes],
67            neg_adj: vec![Vec::new(); n_nodes],
68        }
69    }
70
71    /// Add a signed edge (undirected: both directions are recorded).
72    pub fn add_edge(&mut self, src: usize, dst: usize, sign: i8) {
73        assert!(
74            src < self.n_nodes && dst < self.n_nodes,
75            "node index out of range"
76        );
77        self.edges.push(SignedEdge { src, dst, sign });
78        if sign > 0 {
79            self.pos_adj[src].push(dst);
80            self.pos_adj[dst].push(src);
81        } else {
82            self.neg_adj[src].push(dst);
83            self.neg_adj[dst].push(src);
84        }
85    }
86
87    /// Return the number of positive edges.
88    pub fn positive_edge_count(&self) -> usize {
89        self.edges.iter().filter(|e| e.sign > 0).count()
90    }
91
92    /// Return the number of negative edges.
93    pub fn negative_edge_count(&self) -> usize {
94        self.edges.iter().filter(|e| e.sign < 0).count()
95    }
96
97    /// Degree of node `v` in the absolute-value adjacency (all edges regardless of sign).
98    pub fn abs_degree(&self, v: usize) -> usize {
99        self.pos_adj[v].len() + self.neg_adj[v].len()
100    }
101}
102
103/// A directed weighted graph with per-node in-edge and out-edge lists.
104#[derive(Debug, Clone)]
105pub struct DirectedGraph {
106    /// Number of nodes.
107    pub n_nodes: usize,
108    /// All edges.
109    pub edges: Vec<DirectedEdge>,
110    /// Out-adjacency list: `out_adj[i]` = list of (dst, weight).
111    pub out_adj: Vec<Vec<(usize, f64)>>,
112    /// In-adjacency list: `in_adj[i]` = list of (src, weight).
113    pub in_adj: Vec<Vec<(usize, f64)>>,
114}
115
116impl DirectedGraph {
117    /// Create an empty directed graph with `n_nodes` nodes.
118    pub fn new(n_nodes: usize) -> Self {
119        Self {
120            n_nodes,
121            edges: Vec::new(),
122            out_adj: vec![Vec::new(); n_nodes],
123            in_adj: vec![Vec::new(); n_nodes],
124        }
125    }
126
127    /// Add a directed weighted edge from `src` to `dst`.
128    pub fn add_edge(&mut self, src: usize, dst: usize, weight: f64) {
129        assert!(
130            src < self.n_nodes && dst < self.n_nodes,
131            "node index out of range"
132        );
133        self.edges.push(DirectedEdge { src, dst, weight });
134        self.out_adj[src].push((dst, weight));
135        self.in_adj[dst].push((src, weight));
136    }
137
138    /// Out-degree of node `v`.
139    pub fn out_degree(&self, v: usize) -> usize {
140        self.out_adj[v].len()
141    }
142
143    /// In-degree of node `v`.
144    pub fn in_degree(&self, v: usize) -> usize {
145        self.in_adj[v].len()
146    }
147}
148
149/// Configuration for signed graph spectral embedding (SPONGE / ratio-cut).
150#[derive(Debug, Clone)]
151pub struct SignedEmbedConfig {
152    /// Embedding dimension.
153    pub dim: usize,
154    /// Number of power-iteration steps.
155    pub n_iter: usize,
156    /// Learning rate (used in gradient-based optimisation stages).
157    pub lr: f64,
158    /// Number of negative samples (used in sampling-based objectives).
159    pub neg_sample: usize,
160}
161
162impl Default for SignedEmbedConfig {
163    fn default() -> Self {
164        Self {
165            dim: 16,
166            n_iter: 100,
167            lr: 0.01,
168            neg_sample: 5,
169        }
170    }
171}
172
173/// Configuration for directed graph embedding (HOPE / APP).
174#[derive(Debug, Clone)]
175pub struct DirectedEmbedConfig {
176    /// Embedding dimension.
177    pub dim: usize,
178    /// Number of power-iteration / random-walk steps.
179    pub n_iter: usize,
180}
181
182impl Default for DirectedEmbedConfig {
183    fn default() -> Self {
184        Self {
185            dim: 16,
186            n_iter: 10,
187        }
188    }
189}
190
191/// Result of an embedding computation: n_nodes × dim matrix stored row-major.
192#[derive(Debug, Clone)]
193pub struct EmbeddingResult {
194    /// Row-major embedding matrix; `embeddings[i]` is the d-dimensional vector for node i.
195    pub embeddings: Vec<Vec<f64>>,
196    /// Embedding dimension.
197    pub dim: usize,
198    /// Number of nodes.
199    pub n_nodes: usize,
200}
201
202impl EmbeddingResult {
203    /// Construct a new zero-initialised result.
204    pub fn zeros(n_nodes: usize, dim: usize) -> Self {
205        Self {
206            embeddings: vec![vec![0.0_f64; dim]; n_nodes],
207            dim,
208            n_nodes,
209        }
210    }
211
212    /// Return the embedding vector for node `v`.
213    pub fn get(&self, v: usize) -> &[f64] {
214        &self.embeddings[v]
215    }
216}