rustkernel_graph/
types.rs

1//! Common graph types and data structures.
2
3use serde::{Deserialize, Serialize};
4
5/// Graph representation in Compressed Sparse Row (CSR) format.
6///
7/// Efficient for GPU processing with coalesced memory access.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct CsrGraph {
10    /// Number of nodes.
11    pub num_nodes: usize,
12    /// Number of edges.
13    pub num_edges: usize,
14    /// Row offsets (length: num_nodes + 1).
15    pub row_offsets: Vec<u64>,
16    /// Column indices (length: num_edges).
17    pub col_indices: Vec<u64>,
18    /// Edge weights (optional, length: num_edges).
19    pub weights: Option<Vec<f32>>,
20}
21
22impl CsrGraph {
23    /// Create an empty graph.
24    #[must_use]
25    pub fn empty() -> Self {
26        Self {
27            num_nodes: 0,
28            num_edges: 0,
29            row_offsets: vec![0],
30            col_indices: Vec::new(),
31            weights: None,
32        }
33    }
34
35    /// Create a graph from edge list.
36    #[must_use]
37    pub fn from_edges(num_nodes: usize, edges: &[(u64, u64)]) -> Self {
38        let mut row_counts = vec![0u64; num_nodes];
39        for (src, _) in edges {
40            row_counts[*src as usize] += 1;
41        }
42
43        let mut row_offsets = vec![0u64; num_nodes + 1];
44        for i in 0..num_nodes {
45            row_offsets[i + 1] = row_offsets[i] + row_counts[i];
46        }
47
48        let mut col_indices = vec![0u64; edges.len()];
49        let mut current_pos = row_offsets.clone();
50
51        for (src, dst) in edges {
52            let pos = current_pos[*src as usize] as usize;
53            col_indices[pos] = *dst;
54            current_pos[*src as usize] += 1;
55        }
56
57        Self {
58            num_nodes,
59            num_edges: edges.len(),
60            row_offsets,
61            col_indices,
62            weights: None,
63        }
64    }
65
66    /// Get the out-degree of a node.
67    #[must_use]
68    pub fn out_degree(&self, node: u64) -> u64 {
69        let n = node as usize;
70        if n >= self.num_nodes {
71            return 0;
72        }
73        self.row_offsets[n + 1] - self.row_offsets[n]
74    }
75
76    /// Get the neighbors of a node.
77    #[must_use]
78    pub fn neighbors(&self, node: u64) -> &[u64] {
79        let n = node as usize;
80        if n >= self.num_nodes {
81            return &[];
82        }
83        let start = self.row_offsets[n] as usize;
84        let end = self.row_offsets[n + 1] as usize;
85        &self.col_indices[start..end]
86    }
87
88    /// Check if the graph has weights.
89    #[must_use]
90    pub fn is_weighted(&self) -> bool {
91        self.weights.is_some()
92    }
93
94    /// Calculate graph density.
95    #[must_use]
96    pub fn density(&self) -> f64 {
97        if self.num_nodes <= 1 {
98            return 0.0;
99        }
100        let max_edges = self.num_nodes * (self.num_nodes - 1);
101        self.num_edges as f64 / max_edges as f64
102    }
103}
104
105/// Node with centrality score.
106#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
107pub struct NodeScore {
108    /// Node ID.
109    pub node_id: u64,
110    /// Centrality score.
111    pub score: f64,
112}
113
114/// Centrality result for a graph.
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct CentralityResult {
117    /// Scores per node.
118    pub scores: Vec<NodeScore>,
119    /// Number of iterations (for iterative algorithms).
120    pub iterations: Option<u32>,
121    /// Whether the algorithm converged.
122    pub converged: bool,
123}
124
125impl CentralityResult {
126    /// Get the top-k nodes by score.
127    #[must_use]
128    pub fn top_k(&self, k: usize) -> Vec<NodeScore> {
129        let mut sorted = self.scores.clone();
130        sorted.sort_by(|a, b| {
131            b.score
132                .partial_cmp(&a.score)
133                .unwrap_or(std::cmp::Ordering::Equal)
134        });
135        sorted.truncate(k);
136        sorted
137    }
138}
139
140/// Community detection result.
141#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct CommunityResult {
143    /// Community assignment per node.
144    pub assignments: Vec<u64>,
145    /// Number of communities found.
146    pub num_communities: usize,
147    /// Modularity score.
148    pub modularity: f64,
149}
150
151/// Similarity result between two nodes or sets.
152#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
153pub struct SimilarityScore {
154    /// First node/set ID.
155    pub id_a: u64,
156    /// Second node/set ID.
157    pub id_b: u64,
158    /// Similarity score (0.0 to 1.0).
159    pub similarity: f64,
160}
161
162/// Result of similarity computation.
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct SimilarityResult {
165    /// Computed similarity scores.
166    pub scores: Vec<SimilarityScore>,
167    /// Method used for computation.
168    pub method: String,
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn test_csr_from_edges() {
177        let edges = vec![(0, 1), (0, 2), (1, 2), (2, 0)];
178        let graph = CsrGraph::from_edges(3, &edges);
179
180        assert_eq!(graph.num_nodes, 3);
181        assert_eq!(graph.num_edges, 4);
182        assert_eq!(graph.out_degree(0), 2);
183        assert_eq!(graph.out_degree(1), 1);
184        assert_eq!(graph.out_degree(2), 1);
185    }
186
187    #[test]
188    fn test_graph_density() {
189        let edges = vec![(0, 1), (1, 0), (0, 2), (2, 0), (1, 2), (2, 1)];
190        let graph = CsrGraph::from_edges(3, &edges);
191
192        // Complete graph of 3 nodes has 6 edges, density = 1.0
193        assert!((graph.density() - 1.0).abs() < 0.001);
194    }
195}