1#![allow(dead_code)]
4
5use faer::{Mat, MatRef};
6use rayon::prelude::*;
7use rustc_hash::FxHashMap;
8use std::clone::Clone;
9use std::collections::BinaryHeap;
10
11use crate::prelude::*;
12use bixverse_core::core::math::sparse::coo_to_csr;
13
14const KNN_ROW_BLOCK: usize = 32;
20
21const KNN_SYM_TILE: usize = 64;
23
24pub struct UnionFind {
30 parent: Vec<u32>,
32 size: Vec<u32>,
34}
35
36impl UnionFind {
37 pub fn new(n: usize) -> Self {
47 Self {
48 parent: (0..n as u32).collect(),
49 size: vec![1; n],
50 }
51 }
52
53 pub fn find(&mut self, mut x: u32) -> u32 {
63 while self.parent[x as usize] != x {
64 let grandparent = self.parent[self.parent[x as usize] as usize];
65 self.parent[x as usize] = grandparent;
66 x = grandparent;
67 }
68 x
69 }
70
71 pub fn union(&mut self, a: u32, b: u32) -> bool {
84 let (mut ra, mut rb) = (self.find(a), self.find(b));
85 if ra == rb {
86 return false;
87 }
88 if self.size[ra as usize] < self.size[rb as usize] {
89 std::mem::swap(&mut ra, &mut rb);
90 }
91 self.parent[rb as usize] = ra;
92 self.size[ra as usize] += self.size[rb as usize];
93 true
94 }
95}
96
97#[derive(Clone, Debug)]
110pub struct SparseGraph<T: Clone> {
111 adjacency: CompressedSparseData2<T>,
113 num_nodes: usize,
114 directed: bool,
115}
116
117impl<T> SparseGraph<T>
118where
119 T: Clone + BixverseFloat + std::iter::Sum,
120{
121 pub fn new(num_nodes: usize, adjacency: CompressedSparseData2<T>, directed: bool) -> Self {
134 Self {
135 adjacency,
136 num_nodes,
137 directed,
138 }
139 }
140
141 #[inline]
151 pub fn get_neighbours(&self, node: usize) -> (Vec<usize>, &[T]) {
152 let start = self.adjacency.indptr[node] as usize;
153 let end = self.adjacency.indptr[node + 1] as usize;
154 (
155 self.adjacency.indices[start..end].index_cast(),
156 &self.adjacency.data[start..end],
157 )
158 }
159
160 #[inline]
170 pub fn get_neighbours_raw(&self, node: usize) -> (&[u32], &[T]) {
171 let start = self.adjacency.indptr[node] as usize;
172 let end = self.adjacency.indptr[node + 1] as usize;
173 (
174 &self.adjacency.indices[start..end],
175 &self.adjacency.data[start..end],
176 )
177 }
178
179 #[inline]
189 pub fn get_node_degree(&self, node: usize) -> usize {
190 (self.adjacency.indptr[node + 1] - self.adjacency.indptr[node]) as usize
191 }
192
193 pub fn total_weight(&self) -> T {
203 let mut total = T::zero();
204 for i in 0..self.num_nodes {
205 let (_, weights) = self.get_neighbours(i);
206 total += weights.iter().copied().sum::<T>();
207 }
208 let half = T::from_f64(0.5).unwrap();
209 if !self.directed { total * half } else { total }
210 }
211
212 pub fn get_node_number(&self) -> usize {
218 self.num_nodes
219 }
220
221 pub fn is_directed(&self) -> bool {
227 self.directed
228 }
229}
230
231#[allow(dead_code)]
243pub struct CoarseLevel<T>
244where
245 T: Clone,
246{
247 graph: SparseGraph<T>,
248 node_map: Vec<usize>,
249 coarse_weights: Vec<usize>,
250}
251
252pub struct GraphHierarchy<T>
259where
260 T: Clone,
261{
262 finest: SparseGraph<T>,
263 levels: Vec<CoarseLevel<T>>,
264}
265
266impl<T> GraphHierarchy<T>
267where
268 T: Clone + BixverseFloat + std::iter::Sum + std::default::Default,
269{
270 pub fn build(graph: SparseGraph<T>, reduction_ratio: T, max_levels: usize) -> Self {
285 assert!(
286 !graph.is_directed(),
287 "This is implemented for undirected graphs!"
288 );
289 let mut levels = Vec::new();
290 let mut current_graph = graph.clone();
291
292 let min_nodes = T::from_usize(100).unwrap();
293
294 for _ in 0..max_levels {
295 let coarse_level = Self::coarsen_level(¤t_graph);
296
297 let current_nodes = T::from_usize(current_graph.num_nodes).unwrap();
298 let coarse_nodes = T::from_usize(coarse_level.graph.num_nodes).unwrap();
299 let reduction = coarse_nodes / current_nodes;
300
301 if reduction > reduction_ratio || coarse_nodes < min_nodes {
302 break;
303 }
304
305 current_graph = coarse_level.graph.clone();
306 levels.push(coarse_level);
307 }
308
309 Self {
310 finest: graph,
311 levels,
312 }
313 }
314
315 fn coarsen_level(graph: &SparseGraph<T>) -> CoarseLevel<T> {
327 let matching = Self::heavy_edge_matching(graph);
328 let (coarse_graph, node_map, coarse_weights) = Self::contract_graph(graph, &matching);
329
330 CoarseLevel {
331 graph: coarse_graph,
332 node_map,
333 coarse_weights,
334 }
335 }
336
337 fn heavy_edge_matching(graph: &SparseGraph<T>) -> Vec<Option<usize>> {
351 let n = graph.num_nodes;
352 let mut matching = vec![None; n];
353 let mut matched = vec![false; n];
354
355 for i in 0..n {
356 if matched[i] {
357 continue;
358 }
359
360 let (neighbour, weights) = graph.get_neighbours(i);
361
362 let mut best_neighbour = None;
364 let mut best_weight = T::zero();
365
366 for (&j, &w) in neighbour.iter().zip(weights.iter()) {
367 if !matched[j] && j != i && w > best_weight {
368 best_weight = w;
369 best_neighbour = Some(j)
370 }
371 }
372
373 if let Some(j) = best_neighbour {
374 matching[i] = Some(j);
375 matching[j] = Some(i);
376 matched[i] = true;
377 matched[j] = true;
378 }
379 }
380
381 matching
382 }
383
384 fn contract_graph(
400 graph: &SparseGraph<T>,
401 matching: &[Option<usize>],
402 ) -> (SparseGraph<T>, Vec<usize>, Vec<usize>) {
403 let n = graph.num_nodes;
404 let mut node_map = vec![0; n];
405 let mut coarse_id = 0;
406
407 for i in 0..n {
409 if node_map[i] == 0 || i == 0 {
410 match matching[i] {
411 Some(j) if j > i => {
412 node_map[i] = coarse_id;
413 node_map[j] = coarse_id;
414 coarse_id += 1;
415 }
416 None => {
417 node_map[i] = coarse_id;
419 coarse_id += 1;
420 }
421 _ => {} }
423 }
424 }
425
426 let num_coarse = coarse_id;
427 let mut coarse_weights = vec![0; num_coarse];
428
429 for i in 0..n {
430 coarse_weights[node_map[i]] += 1;
431 }
432
433 let mut rows = Vec::new();
435 let mut cols = Vec::new();
436 let mut vals = Vec::new();
437
438 for i in 0..n {
439 let ci = node_map[i];
440 let (neighbours, weights) = graph.get_neighbours(i);
441
442 for (&j, &w) in neighbours.iter().zip(weights.iter()) {
443 let cj = node_map[j];
444 if ci != cj {
445 rows.push(ci);
446 cols.push(cj);
447 vals.push(w);
448 }
449 }
450 }
451
452 let mut edges: Vec<(usize, usize, T)> = rows
454 .into_iter()
455 .zip(cols)
456 .zip(vals)
457 .map(|((r, c), v)| (r, c, v))
458 .collect();
459 edges.sort_unstable_by_key(|(r, c, _)| (*r, *c));
460
461 let mut deduped_rows = Vec::new();
463 let mut deduped_cols = Vec::new();
464 let mut deduped_vals = Vec::new();
465
466 if !edges.is_empty() {
467 let (mut curr_r, mut curr_c, mut curr_v) = edges[0];
468
469 for &(r, c, v) in &edges[1..] {
470 if r == curr_r && c == curr_c {
471 curr_v += v;
472 } else {
473 deduped_rows.push(curr_r);
474 deduped_cols.push(curr_c);
475 deduped_vals.push(curr_v);
476 (curr_r, curr_c, curr_v) = (r, c, v);
477 }
478 }
479 deduped_rows.push(curr_r);
480 deduped_cols.push(curr_c);
481 deduped_vals.push(curr_v);
482 }
483
484 let coarse_csr = coo_to_csr(
485 &deduped_rows.index_cast(),
486 &deduped_cols.index_cast(),
487 &deduped_vals,
488 (num_coarse, num_coarse),
489 );
490
491 let coarse_graph = SparseGraph::new(num_coarse, coarse_csr, false);
492
493 (coarse_graph, node_map, coarse_weights)
494 }
495
496 pub fn prolong(&self, level: usize, coarse_labels: &[usize]) -> Vec<usize> {
507 if level >= self.levels.len() {
508 return coarse_labels.to_vec();
509 }
510
511 let node_map = &self.levels[level].node_map;
512 node_map.iter().map(|&ci| coarse_labels[ci]).collect()
513 }
514
515 pub fn coarsest(&self) -> &SparseGraph<T> {
521 self.levels.last().map(|l| &l.graph).unwrap_or(&self.finest)
522 }
523}
524
525#[derive(Debug, Clone)]
532pub struct AttributedGraph<'a, T> {
533 pub node_types: Vec<&'a str>,
535 pub from: Vec<usize>,
537 pub to: Vec<usize>,
539 pub edge_types: Vec<&'a str>,
541 pub weights: Vec<T>,
543}
544
545pub fn graph_from_strings_with_attributes<'a, T>(
567 nodes: &'a [String],
568 node_types: &'a [String],
569 from: &'a [String],
570 to: &'a [String],
571 edge_types: &'a [String],
572 edge_weights: &'a [T],
573) -> AttributedGraph<'a, T>
574where
575 T: BixverseFloat,
576{
577 assert_same_len!(nodes, node_types);
578 assert_same_len!(from, to, edge_types, edge_weights);
579
580 let name_to_idx: FxHashMap<&String, usize> = nodes
581 .iter()
582 .enumerate()
583 .map(|(idx, name)| (name, idx))
584 .collect();
585 let lookup = |name: &String| {
586 *name_to_idx
587 .get(name)
588 .unwrap_or_else(|| panic!("Node '{name}' not found in nodes list"))
589 };
590
591 AttributedGraph {
592 node_types: node_types.iter().map(String::as_str).collect(),
593 from: from.iter().map(lookup).collect(),
594 to: to.iter().map(lookup).collect(),
595 edge_types: edge_types.iter().map(String::as_str).collect(),
596 weights: edge_weights.to_vec(),
597 }
598}
599
600pub(crate) fn edges_to_csr<T>(
619 n: usize,
620 rows: &[u32],
621 cols: &[u32],
622 vals: &[T],
623) -> (Vec<usize>, Vec<u32>, Vec<T>)
624where
625 T: BixverseFloat,
626{
627 let mut indptr = vec![0usize; n + 1];
628 for &r in rows {
629 indptr[r as usize + 1] += 1;
630 }
631 for i in 0..n {
632 indptr[i + 1] += indptr[i];
633 }
634
635 let mut next = indptr[..n].to_vec();
636 let mut indices = vec![0u32; rows.len()];
637 let mut values = vec![T::zero(); rows.len()];
638 for ((&r, &c), &v) in rows.iter().zip(cols).zip(vals) {
639 let pos = next[r as usize];
640 indices[pos] = c;
641 values[pos] = v;
642 next[r as usize] += 1;
643 }
644
645 (indptr, indices, values)
646}
647
648pub fn get_knn_graph_adj<T>(similarities: &MatRef<T>, k: usize) -> Mat<T>
659where
660 T: BixverseFloat,
661{
662 assert_symmetric_mat!(similarities);
663 let n = similarities.nrows();
664 let mut adjacency: Mat<T> = Mat::zeros(n, n);
665
666 let blocks: Vec<usize> = (0..n).step_by(KNN_ROW_BLOCK).collect();
669 let rows: Vec<Vec<(usize, T)>> = blocks
670 .par_iter()
671 .flat_map_iter(|&start| {
672 let end = (start + KNN_ROW_BLOCK).min(n);
673 let mut heaps: Vec<BinaryHeap<(RevOrderedFloat<T>, usize)>> = (start..end)
674 .map(|_| BinaryHeap::with_capacity(k + 1))
675 .collect();
676
677 for j in 0..n {
678 let col = similarities.col(j);
679 for (heap, i) in heaps.iter_mut().zip(start..end) {
680 if i == j {
681 continue;
682 }
683 let entry = (RevOrderedFloat(col[i]), j);
684 if heap.len() == k && heap.peek().is_some_and(|top| entry >= *top) {
686 continue;
687 }
688 heap.push(entry);
689 if heap.len() > k {
690 heap.pop();
691 }
692 }
693 }
694
695 heaps.into_iter().map(|heap| {
696 heap.into_iter()
697 .map(|(ordered_sim, index)| (index, ordered_sim.0))
698 .collect::<Vec<_>>()
699 })
700 })
701 .collect();
702
703 for (i, neighbors) in rows.iter().enumerate() {
705 for &(j, sim) in neighbors {
706 adjacency[(i, j)] = sim;
707 }
708 }
709
710 let two = T::from_f64(2.0).unwrap();
712 for ib in (0..n).step_by(KNN_SYM_TILE) {
713 for jb in (ib..n).step_by(KNN_SYM_TILE) {
714 for j in jb..(jb + KNN_SYM_TILE).min(n) {
715 for i in ib..(ib + KNN_SYM_TILE).min(n).min(j) {
716 let val = (adjacency[(i, j)] + adjacency[(j, i)]) / two;
717 adjacency[(i, j)] = val;
718 adjacency[(j, i)] = val;
719 }
720 }
721 }
722 }
723
724 adjacency
725}
726
727pub fn adjacency_to_laplacian<T>(adjacency: &MatRef<T>, normalise: bool) -> Mat<T>
737where
738 T: BixverseFloat,
739{
740 assert_symmetric_mat!(adjacency);
741 let n = adjacency.nrows();
742
743 let mut degrees = vec![T::zero(); n];
745 for j in 0..n {
746 for (d, &x) in degrees.iter_mut().zip(adjacency.col(j).iter()) {
747 *d += x;
748 }
749 }
750
751 if !normalise {
752 let mut laplacian = adjacency.cloned();
753 for j in 0..n {
754 for i in 0..n {
755 if i != j {
756 laplacian[(i, j)] = -adjacency[(i, j)];
757 }
758 }
759 laplacian[(j, j)] = degrees[j] - adjacency[(j, j)];
760 }
761 laplacian
762 } else {
763 let inv_sqrt_d: Vec<T> = degrees
764 .iter()
765 .map(|&d| {
766 if d > T::from_f64(1e-10).unwrap() {
767 T::one() / d.sqrt()
768 } else {
769 T::zero()
770 }
771 })
772 .collect();
773 let mut laplacian = Mat::zeros(n, n);
774 for j in 0..n {
775 laplacian[(j, j)] = T::one();
776 for i in 0..n {
777 laplacian[(i, j)] -= inv_sqrt_d[i] * adjacency[(i, j)] * inv_sqrt_d[j];
778 }
779 }
780 laplacian
781 }
782}
783
784pub fn edge_pairs_to_sparse_graph<T>(
800 edges: &[usize],
801 weights: &[T],
802 n_nodes: usize,
803) -> SparseGraph<T>
804where
805 T: BixverseFloat + std::iter::Sum + Send + Sync,
806{
807 let n_edges = weights.len();
808
809 let mut indptr = vec![0usize; n_nodes + 1];
810 for e in 0..n_edges {
811 indptr[edges[2 * e] + 1] += 1;
812 indptr[edges[2 * e + 1] + 1] += 1;
813 }
814 for i in 1..=n_nodes {
815 indptr[i] += indptr[i - 1];
816 }
817
818 let nnz = indptr[n_nodes];
819 let mut indices = vec![0usize; nnz];
820 let mut data = vec![T::zero(); nnz];
821 let mut cursor = indptr.clone();
822
823 for e in 0..n_edges {
824 let i = edges[2 * e];
825 let j = edges[2 * e + 1];
826 let w = weights[e];
827
828 indices[cursor[i]] = j;
829 data[cursor[i]] = w;
830 cursor[i] += 1;
831
832 indices[cursor[j]] = i;
833 data[cursor[j]] = w;
834 cursor[j] += 1;
835 }
836
837 let mut rows: Vec<(&mut [usize], &mut [T])> = Vec::with_capacity(n_nodes);
839 let (mut idx_rest, mut data_rest) = (indices.as_mut_slice(), data.as_mut_slice());
840 for node in 0..n_nodes {
841 let len = indptr[node + 1] - indptr[node];
842 let (idx_row, idx_tail) = idx_rest.split_at_mut(len);
843 let (data_row, data_tail) = data_rest.split_at_mut(len);
844 rows.push((idx_row, data_row));
845 idx_rest = idx_tail;
846 data_rest = data_tail;
847 }
848
849 rows.par_iter_mut()
850 .for_each_init(Vec::<(usize, T)>::new, |scratch, (idx_row, data_row)| {
851 if idx_row.len() < 2 {
852 return;
853 }
854 scratch.clear();
855 scratch.extend(idx_row.iter().copied().zip(data_row.iter().copied()));
856 scratch.sort_unstable_by_key(|&(node, _)| node);
857 for ((i, d), &(node, w)) in idx_row.iter_mut().zip(data_row.iter_mut()).zip(&*scratch) {
858 *i = node;
859 *d = w;
860 }
861 });
862
863 let adjacency = CompressedSparseData2 {
864 data,
865 indices: indices.index_cast(),
866 indptr: indptr.index_cast(),
867 cs_type: CompressedSparseFormat::Csr,
868 data_2: None,
869 shape: (n_nodes, n_nodes),
870 };
871
872 SparseGraph::new(n_nodes, adjacency, false)
873}
874
875#[cfg(test)]
880mod tests {
881 use super::*;
882 use bixverse_core::core::math::sparse::CompressedSparseData2;
883 use faer::Mat;
884
885 #[test]
887 fn test_sparse_graph_properties() {
888 let data = vec![1.0, 1.0, 1.0, 1.0];
890 let indices = vec![1, 0, 2, 1]; let indptr = vec![0, 1, 3, 4]; let csr =
893 CompressedSparseData2::<f64, f64>::new_csr(&data, &indices, &indptr, None, (3, 3));
894
895 let graph = SparseGraph::new(3, csr, false);
896
897 assert_eq!(graph.get_node_degree(0), 1);
898 assert_eq!(graph.get_node_degree(1), 2);
899 assert_eq!(graph.total_weight(), 2.0); let (n0_neighbors, n0_weights) = graph.get_neighbours(0);
902 assert_eq!(n0_neighbors, &[1]);
903 assert_eq!(n0_weights, &[1.0]);
904 }
905
906 #[test]
908 fn test_adjacency_to_laplacian() {
909 let mut adj: Mat<f64> = Mat::zeros(2, 2);
911 adj[(0, 1)] = 1.0;
912 adj[(1, 0)] = 1.0;
913
914 let lap = adjacency_to_laplacian(&adj.as_ref(), false);
917
918 assert!((lap[(0, 0)] - 1.0).abs() < 1e-6);
919 assert!((lap[(1, 1)] - 1.0).abs() < 1e-6);
920 assert!((lap[(0, 1)] - (-1.0)).abs() < 1e-6);
921 assert!((lap[(1, 0)] - (-1.0)).abs() < 1e-6);
922 }
923
924 #[test]
926 fn test_graph_from_strings_with_attributes() {
927 let nodes = vec!["A".to_string(), "B".to_string(), "C".to_string()];
928 let node_types = vec!["x".to_string(), "y".to_string(), "x".to_string()];
929 let from = vec!["A".to_string(), "B".to_string(), "C".to_string()];
930 let to = vec!["B".to_string(), "C".to_string(), "B".to_string()];
931 let edge_types = vec!["e".to_string(), "f".to_string(), "e".to_string()];
932 let weights = vec![1.5, 2.5, 0.5];
933
934 let graph = graph_from_strings_with_attributes(
935 &nodes,
936 &node_types,
937 &from,
938 &to,
939 &edge_types,
940 &weights,
941 );
942
943 assert_eq!(graph.node_types, vec!["x", "y", "x"]);
944 assert_eq!(graph.from, vec![0, 1, 2]);
945 assert_eq!(graph.to, vec![1, 2, 1]);
946 assert_eq!(graph.edge_types, vec!["e", "f", "e"]);
947 assert_eq!(graph.weights, weights);
948 }
949}