1use anyhow::{anyhow, Result};
17use std::collections::HashMap;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
21pub struct NodeId(pub u32);
22
23#[derive(Debug, Clone)]
44pub struct CsrGraph {
45 row_offsets: Vec<u32>,
49
50 col_indices: Vec<u32>,
53
54 edge_weights: Vec<f32>,
57
58 rev_row_offsets: Vec<u32>,
62
63 rev_col_indices: Vec<u32>,
66
67 rev_edge_weights: Vec<f32>,
70
71 node_names: HashMap<NodeId, String>,
73
74 num_nodes: usize,
76}
77
78impl CsrGraph {
79 #[must_use]
81 pub fn new() -> Self {
82 Self {
83 row_offsets: vec![0], col_indices: Vec::new(),
85 edge_weights: Vec::new(),
86 rev_row_offsets: vec![0], rev_col_indices: Vec::new(),
88 rev_edge_weights: Vec::new(),
89 node_names: HashMap::new(),
90 num_nodes: 0,
91 }
92 }
93
94 pub fn from_edge_list(edges: &[(NodeId, NodeId, f32)]) -> Result<Self> {
104 if edges.is_empty() {
105 return Ok(Self::new());
106 }
107
108 let max_node = edges
110 .iter()
111 .flat_map(|(src, dst, _)| [src.0, dst.0])
112 .max()
113 .ok_or_else(|| anyhow!("Empty edge list"))?;
114
115 let num_nodes = (max_node + 1) as usize;
116
117 let mut adj_list: Vec<Vec<(u32, f32)>> = vec![Vec::new(); num_nodes];
119 let mut rev_adj_list: Vec<Vec<(u32, f32)>> = vec![Vec::new(); num_nodes];
120
121 for (src, dst, weight) in edges {
122 adj_list[src.0 as usize].push((dst.0, *weight));
123 rev_adj_list[dst.0 as usize].push((src.0, *weight)); }
125
126 let mut row_offsets = Vec::with_capacity(num_nodes + 1);
128 let mut col_indices = Vec::new();
129 let mut edge_weights_vec = Vec::new();
130
131 let mut offset = 0_u32;
132 row_offsets.push(offset);
133
134 for neighbors in &adj_list {
135 #[allow(clippy::cast_possible_truncation)] let len_u32 = neighbors.len() as u32;
137 offset += len_u32;
138 row_offsets.push(offset);
139
140 for (target, weight) in neighbors {
141 col_indices.push(*target);
142 edge_weights_vec.push(*weight);
143 }
144 }
145
146 let mut rev_row_offsets = Vec::with_capacity(num_nodes + 1);
148 let mut rev_col_indices = Vec::new();
149 let mut rev_edge_weights_vec = Vec::new();
150
151 let mut rev_offset = 0_u32;
152 rev_row_offsets.push(rev_offset);
153
154 for rev_neighbors in &rev_adj_list {
155 #[allow(clippy::cast_possible_truncation)]
156 let len_u32 = rev_neighbors.len() as u32;
157 rev_offset += len_u32;
158 rev_row_offsets.push(rev_offset);
159
160 for (source, weight) in rev_neighbors {
161 rev_col_indices.push(*source);
162 rev_edge_weights_vec.push(*weight);
163 }
164 }
165
166 Ok(Self {
167 row_offsets,
168 col_indices,
169 edge_weights: edge_weights_vec,
170 rev_row_offsets,
171 rev_col_indices,
172 rev_edge_weights: rev_edge_weights_vec,
173 node_names: HashMap::new(),
174 num_nodes,
175 })
176 }
177
178 pub fn add_edge(&mut self, src: NodeId, dst: NodeId, weight: f32) -> Result<()> {
186 let max_node = src.0.max(dst.0) as usize;
188 if max_node >= self.num_nodes {
189 self.expand_to(max_node + 1);
190 }
191
192 let src_idx = src.0 as usize;
194 let end = self.row_offsets[src_idx + 1] as usize;
195
196 self.col_indices.insert(end, dst.0);
197 self.edge_weights.insert(end, weight);
198
199 for offset in &mut self.row_offsets[src_idx + 1..] {
201 *offset += 1;
202 }
203
204 let dst_idx = dst.0 as usize;
206 let rev_end = self.rev_row_offsets[dst_idx + 1] as usize;
207
208 self.rev_col_indices.insert(rev_end, src.0);
209 self.rev_edge_weights.insert(rev_end, weight);
210
211 for offset in &mut self.rev_row_offsets[dst_idx + 1..] {
213 *offset += 1;
214 }
215
216 Ok(())
217 }
218
219 pub fn outgoing_neighbors(&self, node: NodeId) -> Result<&[u32]> {
225 if (node.0 as usize) >= self.num_nodes {
226 return Err(anyhow!("Node ID {} out of bounds", node.0));
227 }
228
229 let idx = node.0 as usize;
230 let start = self.row_offsets[idx] as usize;
231 let end = self.row_offsets[idx + 1] as usize;
232
233 Ok(&self.col_indices[start..end])
234 }
235
236 pub fn incoming_neighbors(&self, target: NodeId) -> Result<&[u32]> {
244 if (target.0 as usize) >= self.num_nodes {
245 return Err(anyhow!("Node ID {} out of bounds", target.0));
246 }
247
248 let idx = target.0 as usize;
249 let start = self.rev_row_offsets[idx] as usize;
250 let end = self.rev_row_offsets[idx + 1] as usize;
251
252 Ok(&self.rev_col_indices[start..end])
253 }
254
255 pub fn set_node_name(&mut self, node: NodeId, name: String) {
257 self.node_names.insert(node, name);
258 }
259
260 #[must_use]
262 pub fn get_node_name(&self, node: NodeId) -> Option<&str> {
263 self.node_names.get(&node).map(String::as_str)
264 }
265
266 #[must_use]
268 pub const fn num_nodes(&self) -> usize {
269 self.num_nodes
270 }
271
272 #[must_use]
274 pub fn num_edges(&self) -> usize {
275 self.col_indices.len()
276 }
277
278 #[must_use]
280 pub fn row_offsets_slice(&self) -> &[u32] {
281 &self.row_offsets
282 }
283
284 #[must_use]
286 pub fn col_indices_slice(&self) -> &[u32] {
287 &self.col_indices
288 }
289
290 #[must_use]
292 pub fn edge_weights_slice(&self) -> &[f32] {
293 &self.edge_weights
294 }
295
296 #[must_use]
300 pub fn adjacency(&self, node_id: NodeId) -> (&[u32], &[f32]) {
301 let idx = node_id.0 as usize;
302 if idx >= self.num_nodes {
303 return (&[], &[]);
304 }
305
306 let start = self.row_offsets[idx] as usize;
307 let end = self.row_offsets[idx + 1] as usize;
308
309 (&self.col_indices[start..end], &self.edge_weights[start..end])
310 }
311
312 pub fn iter_adjacency(&self) -> impl Iterator<Item = (NodeId, &[u32], &[f32])> + '_ {
316 (0..self.num_nodes).map(move |node_id| {
317 let start = self.row_offsets[node_id] as usize;
318 let end = self.row_offsets[node_id + 1] as usize;
319
320 #[allow(clippy::cast_possible_truncation)]
321 (NodeId(node_id as u32), &self.col_indices[start..end], &self.edge_weights[start..end])
322 })
323 }
324
325 fn expand_to(&mut self, new_size: usize) {
327 if new_size <= self.num_nodes {
328 return;
329 }
330
331 let last_offset = *self.row_offsets.last().unwrap_or(&0);
333 for _ in self.num_nodes..new_size {
334 self.row_offsets.push(last_offset);
335 }
336
337 let rev_last_offset = *self.rev_row_offsets.last().unwrap_or(&0);
339 for _ in self.num_nodes..new_size {
340 self.rev_row_offsets.push(rev_last_offset);
341 }
342
343 self.num_nodes = new_size;
344 }
345
346 #[must_use]
348 pub fn csr_components(&self) -> (&[u32], &[u32], &[f32]) {
349 (&self.row_offsets, &self.col_indices, &self.edge_weights)
350 }
351}
352
353impl Default for CsrGraph {
354 fn default() -> Self {
355 Self::new()
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn test_empty_graph() {
365 let graph = CsrGraph::new();
366 assert_eq!(graph.num_nodes(), 0);
367 assert_eq!(graph.num_edges(), 0);
368 }
369
370 #[test]
371 fn test_from_edge_list_simple() {
372 let edges = vec![
373 (NodeId(0), NodeId(1), 1.0),
374 (NodeId(0), NodeId(2), 1.0),
375 (NodeId(1), NodeId(2), 1.0),
376 ];
377
378 let graph = CsrGraph::from_edge_list(&edges).unwrap();
379
380 assert_eq!(graph.num_nodes(), 3);
381 assert_eq!(graph.num_edges(), 3);
382
383 assert_eq!(graph.row_offsets, vec![0, 2, 3, 3]);
385 assert_eq!(graph.col_indices, vec![1, 2, 2]);
386 assert_eq!(graph.edge_weights, vec![1.0, 1.0, 1.0]);
387 }
388
389 #[test]
390 fn test_outgoing_neighbors() {
391 let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(0), NodeId(2), 2.0)];
392
393 let graph = CsrGraph::from_edge_list(&edges).unwrap();
394
395 let neighbors = graph.outgoing_neighbors(NodeId(0)).unwrap();
396 assert_eq!(neighbors, &[1, 2]);
397
398 let neighbors = graph.outgoing_neighbors(NodeId(1)).unwrap();
399 let empty: &[u32] = &[];
400 assert_eq!(neighbors, empty);
401 }
402
403 #[test]
404 fn test_incoming_neighbors() {
405 let edges = vec![(NodeId(0), NodeId(2), 1.0), (NodeId(1), NodeId(2), 1.0)];
406
407 let graph = CsrGraph::from_edge_list(&edges).unwrap();
408
409 let callers = graph.incoming_neighbors(NodeId(2)).unwrap();
410 assert_eq!(callers.len(), 2);
411 assert!(callers.contains(&0));
412 assert!(callers.contains(&1));
413 }
414
415 #[test]
416 fn test_reverse_csr_structure() {
417 let edges = vec![
419 (NodeId(0), NodeId(1), 1.0), (NodeId(0), NodeId(2), 2.0), (NodeId(1), NodeId(2), 3.0), ];
423
424 let graph = CsrGraph::from_edge_list(&edges).unwrap();
425
426 let empty: &[u32] = &[];
428 assert_eq!(graph.incoming_neighbors(NodeId(0)).unwrap(), empty);
429
430 assert_eq!(graph.incoming_neighbors(NodeId(1)).unwrap(), &[0]);
432
433 let node2_incoming = graph.incoming_neighbors(NodeId(2)).unwrap();
435 assert_eq!(node2_incoming.len(), 2);
436 assert!(node2_incoming.contains(&0));
437 assert!(node2_incoming.contains(&1));
438 }
439
440 #[test]
441 fn test_reverse_csr_multi_edges() {
442 let edges = vec![
444 (NodeId(0), NodeId(1), 1.0),
445 (NodeId(0), NodeId(1), 2.0), (NodeId(2), NodeId(1), 3.0),
447 ];
448
449 let graph = CsrGraph::from_edge_list(&edges).unwrap();
450
451 let incoming = graph.incoming_neighbors(NodeId(1)).unwrap();
453 assert_eq!(incoming.len(), 3);
454
455 let count_0 = incoming.iter().filter(|&&x| x == 0).count();
457 let count_2 = incoming.iter().filter(|&&x| x == 2).count();
458
459 assert_eq!(count_0, 2, "Should have 2 edges from node 0");
460 assert_eq!(count_2, 1, "Should have 1 edge from node 2");
461 }
462
463 #[test]
464 fn test_reverse_csr_with_add_edge() {
465 let mut graph = CsrGraph::new();
467
468 graph.add_edge(NodeId(0), NodeId(1), 1.0).unwrap();
469 graph.add_edge(NodeId(2), NodeId(1), 2.0).unwrap();
470
471 let incoming = graph.incoming_neighbors(NodeId(1)).unwrap();
473 assert_eq!(incoming.len(), 2);
474 assert!(incoming.contains(&0));
475 assert!(incoming.contains(&2));
476
477 graph.add_edge(NodeId(3), NodeId(1), 3.0).unwrap();
479
480 let incoming = graph.incoming_neighbors(NodeId(1)).unwrap();
482 assert_eq!(incoming.len(), 3);
483 assert!(incoming.contains(&0));
484 assert!(incoming.contains(&2));
485 assert!(incoming.contains(&3));
486 }
487
488 #[test]
489 fn test_add_edge_dynamic() {
490 let mut graph = CsrGraph::new();
491
492 graph.add_edge(NodeId(0), NodeId(1), 1.0).unwrap();
493 graph.add_edge(NodeId(0), NodeId(2), 1.0).unwrap();
494
495 assert_eq!(graph.num_nodes(), 3);
496 assert_eq!(graph.num_edges(), 2);
497
498 let neighbors = graph.outgoing_neighbors(NodeId(0)).unwrap();
499 assert_eq!(neighbors, &[1, 2]);
500 }
501
502 #[test]
503 fn test_node_names() {
504 let mut graph = CsrGraph::new();
505 graph.add_edge(NodeId(0), NodeId(1), 1.0).unwrap();
506
507 graph.set_node_name(NodeId(0), "main".to_string());
508 graph.set_node_name(NodeId(1), "parse_args".to_string());
509
510 assert_eq!(graph.get_node_name(NodeId(0)), Some("main"));
511 assert_eq!(graph.get_node_name(NodeId(1)), Some("parse_args"));
512 }
513
514 #[test]
515 fn test_csr_components() {
516 let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(0), NodeId(2), 2.0)];
517
518 let graph = CsrGraph::from_edge_list(&edges).unwrap();
519 let (row_offsets, col_indices, weights) = graph.csr_components();
520
521 assert_eq!(row_offsets, &[0, 2, 2, 2]);
522 assert_eq!(col_indices, &[1, 2]);
523 assert_eq!(weights, &[1.0, 2.0]);
524 }
525
526 #[test]
527 fn test_adjacency() {
528 let edges = vec![
529 (NodeId(0), NodeId(1), 1.5),
530 (NodeId(0), NodeId(2), 2.5),
531 (NodeId(1), NodeId(2), 3.5),
532 ];
533
534 let graph = CsrGraph::from_edge_list(&edges).unwrap();
535
536 let (targets, weights) = graph.adjacency(NodeId(0));
538 assert_eq!(targets, &[1, 2]);
539 assert_eq!(weights, &[1.5, 2.5]);
540
541 let (targets, weights) = graph.adjacency(NodeId(1));
543 assert_eq!(targets, &[2]);
544 assert_eq!(weights, &[3.5]);
545
546 let (targets, weights) = graph.adjacency(NodeId(2));
548 let empty_u32: &[u32] = &[];
549 let empty_f32: &[f32] = &[];
550 assert_eq!(targets, empty_u32);
551 assert_eq!(weights, empty_f32);
552 }
553
554 #[test]
555 fn test_adjacency_out_of_bounds() {
556 let edges = vec![(NodeId(0), NodeId(1), 1.0)];
557 let graph = CsrGraph::from_edge_list(&edges).unwrap();
558
559 let (targets, weights) = graph.adjacency(NodeId(999));
561 let empty_u32: &[u32] = &[];
562 let empty_f32: &[f32] = &[];
563 assert_eq!(targets, empty_u32);
564 assert_eq!(weights, empty_f32);
565 }
566
567 #[test]
568 fn test_iter_adjacency() {
569 let edges = vec![
570 (NodeId(0), NodeId(1), 1.0),
571 (NodeId(0), NodeId(2), 2.0),
572 (NodeId(1), NodeId(2), 3.0),
573 ];
574
575 let graph = CsrGraph::from_edge_list(&edges).unwrap();
576
577 let adjacencies: Vec<_> = graph.iter_adjacency().collect();
578
579 assert_eq!(adjacencies.len(), 3);
580
581 assert_eq!(adjacencies[0].0, NodeId(0));
583 assert_eq!(adjacencies[0].1, &[1, 2]);
584 assert_eq!(adjacencies[0].2, &[1.0, 2.0]);
585
586 assert_eq!(adjacencies[1].0, NodeId(1));
588 assert_eq!(adjacencies[1].1, &[2]);
589 assert_eq!(adjacencies[1].2, &[3.0]);
590
591 assert_eq!(adjacencies[2].0, NodeId(2));
593 let empty_u32: &[u32] = &[];
594 let empty_f32: &[f32] = &[];
595 assert_eq!(adjacencies[2].1, empty_u32);
596 assert_eq!(adjacencies[2].2, empty_f32);
597 }
598
599 #[test]
600 fn test_slice_methods() {
601 let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(0), NodeId(2), 2.0)];
602 let graph = CsrGraph::from_edge_list(&edges).unwrap();
603
604 assert_eq!(graph.row_offsets_slice(), &[0, 2, 2, 2]);
606 assert_eq!(graph.col_indices_slice(), &[1, 2]);
607 assert_eq!(graph.edge_weights_slice(), &[1.0, 2.0]);
608 }
609
610 #[test]
611 fn test_get_node_name_nonexistent() {
612 let graph = CsrGraph::new();
613 assert_eq!(graph.get_node_name(NodeId(0)), None);
614 }
615
616 #[test]
617 fn test_empty_adjacency_iterator() {
618 let graph = CsrGraph::new();
619 let adjacencies: Vec<_> = graph.iter_adjacency().collect();
620 assert_eq!(adjacencies.len(), 0);
621 }
622}