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, thread_chunks, validate_square_csr};
13use bixverse_sparse::serialise::{decode_with_header, elem_width, encode_with_header};
14use serde::{Deserialize, Serialize, de::DeserializeOwned};
15
16const KNN_ROW_BLOCK: usize = 32;
22
23const KNN_SYM_TILE: usize = 64;
25
26const GRAPH_MAGIC: &[u8; 8] = b"BXSPGRPH";
28
29pub struct UnionFind {
35 parent: Vec<u32>,
37 size: Vec<u32>,
39}
40
41impl UnionFind {
42 pub fn new(n: usize) -> Self {
52 Self {
53 parent: (0..n as u32).collect(),
54 size: vec![1; n],
55 }
56 }
57
58 pub fn find(&mut self, mut x: u32) -> u32 {
68 while self.parent[x as usize] != x {
69 let grandparent = self.parent[self.parent[x as usize] as usize];
70 self.parent[x as usize] = grandparent;
71 x = grandparent;
72 }
73 x
74 }
75
76 pub fn union(&mut self, a: u32, b: u32) -> bool {
89 let (mut ra, mut rb) = (self.find(a), self.find(b));
90 if ra == rb {
91 return false;
92 }
93 if self.size[ra as usize] < self.size[rb as usize] {
94 std::mem::swap(&mut ra, &mut rb);
95 }
96 self.parent[rb as usize] = ra;
97 self.size[ra as usize] += self.size[rb as usize];
98 true
99 }
100}
101
102#[derive(Clone, Debug, Serialize, Deserialize)]
115pub struct SparseGraph<T: Clone> {
116 adjacency: CompressedSparseData2<T>,
118 num_nodes: usize,
120 directed: bool,
122}
123
124impl<T> SparseGraph<T>
125where
126 T: Clone + BixverseFloat + std::iter::Sum,
127{
128 pub fn new(num_nodes: usize, adjacency: CompressedSparseData2<T>, directed: bool) -> Self {
141 Self {
142 adjacency,
143 num_nodes,
144 directed,
145 }
146 }
147
148 #[inline]
158 pub fn get_neighbours(&self, node: usize) -> (Vec<usize>, &[T]) {
159 let start = self.adjacency.indptr[node] as usize;
160 let end = self.adjacency.indptr[node + 1] as usize;
161 (
162 self.adjacency.indices[start..end].index_cast(),
163 &self.adjacency.data[start..end],
164 )
165 }
166
167 #[inline]
177 pub fn get_neighbours_raw(&self, node: usize) -> (&[u32], &[T]) {
178 let start = self.adjacency.indptr[node] as usize;
179 let end = self.adjacency.indptr[node + 1] as usize;
180 (
181 &self.adjacency.indices[start..end],
182 &self.adjacency.data[start..end],
183 )
184 }
185
186 #[inline]
196 pub fn get_node_degree(&self, node: usize) -> usize {
197 (self.adjacency.indptr[node + 1] - self.adjacency.indptr[node]) as usize
198 }
199
200 pub fn total_weight(&self) -> T {
210 let mut total = T::zero();
211 for i in 0..self.num_nodes {
212 let (_, weights) = self.get_neighbours(i);
213 total += weights.iter().copied().sum::<T>();
214 }
215 let half = T::from_f64(0.5).unwrap();
216 if !self.directed { total * half } else { total }
217 }
218
219 pub fn get_node_number(&self) -> usize {
225 self.num_nodes
226 }
227
228 pub fn is_directed(&self) -> bool {
234 self.directed
235 }
236
237 pub fn get_edge_number(&self) -> usize {
247 let nnz = self.adjacency.indices.len();
248 if self.directed { nnz } else { nnz / 2 }
249 }
250
251 pub fn to_edge_pairs(&self) -> (Vec<u32>, Vec<T>)
264 where
265 T: Send + Sync,
266 {
267 let directed = self.directed;
268 let parts: Vec<(Vec<u32>, Vec<T>)> = thread_chunks(self.num_nodes)
269 .into_par_iter()
270 .map(|(start, end)| {
271 let mut edges = Vec::new();
272 let mut weights = Vec::new();
273 for i in start..end {
274 let (nbrs, ws) = self.get_neighbours_raw(i);
275 let mut odd_loop = false;
277 for (&j, &w) in nbrs.iter().zip(ws) {
278 let j_us = j as usize;
279 if j_us == i {
280 odd_loop = !odd_loop;
281 }
282 if directed || j_us > i || (j_us == i && odd_loop) {
283 edges.push(i as u32);
284 edges.push(j);
285 weights.push(w);
286 }
287 }
288 }
289 (edges, weights)
290 })
291 .collect();
292
293 let n_pairs = parts.iter().map(|(_, w)| w.len()).sum();
294 let mut edges = Vec::with_capacity(2 * n_pairs);
295 let mut weights = Vec::with_capacity(n_pairs);
296 for (e, w) in parts {
297 edges.extend_from_slice(&e);
298 weights.extend_from_slice(&w);
299 }
300 (edges, weights)
301 }
302}
303
304impl<T> SparseGraph<T>
305where
306 T: Clone + BixverseFloat + std::iter::Sum + Serialize + DeserializeOwned,
307{
308 pub fn to_bytes(&self) -> Result<Vec<u8>, MethodsErrors> {
315 let w = elem_width::<T>();
316 encode_with_header(GRAPH_MAGIC, [w, w], self)
317 .map_err(|e| MethodsErrors::Core(CoreErrors::from(e)))
318 }
319
320 pub fn from_bytes(bytes: &[u8]) -> Result<Self, MethodsErrors> {
333 let w = elem_width::<T>();
334 let graph: Self = decode_with_header(GRAPH_MAGIC, [w, w], "SparseGraph", bytes)
335 .map_err(CoreErrors::from)?;
336 let n = validate_square_csr(&graph.adjacency).map_err(CoreErrors::from)?;
337 if n != graph.num_nodes {
338 return Err(CoreErrors::from(SparseErrors::ShapeMismatch {
339 expected: (graph.num_nodes, graph.num_nodes),
340 got: graph.adjacency.shape,
341 })
342 .into());
343 }
344 Ok(graph)
345 }
346}
347
348#[allow(dead_code)]
360pub struct CoarseLevel<T>
361where
362 T: Clone,
363{
364 graph: SparseGraph<T>,
365 node_map: Vec<usize>,
366 coarse_weights: Vec<usize>,
367}
368
369pub struct GraphHierarchy<T>
376where
377 T: Clone,
378{
379 finest: SparseGraph<T>,
380 levels: Vec<CoarseLevel<T>>,
381}
382
383impl<T> GraphHierarchy<T>
384where
385 T: Clone + BixverseFloat + std::iter::Sum + std::default::Default,
386{
387 pub fn build(graph: SparseGraph<T>, reduction_ratio: T, max_levels: usize) -> Self {
402 assert!(
403 !graph.is_directed(),
404 "This is implemented for undirected graphs!"
405 );
406 let mut levels = Vec::new();
407 let mut current_graph = graph.clone();
408
409 let min_nodes = T::from_usize(100).unwrap();
410
411 for _ in 0..max_levels {
412 let coarse_level = Self::coarsen_level(¤t_graph);
413
414 let current_nodes = T::from_usize(current_graph.num_nodes).unwrap();
415 let coarse_nodes = T::from_usize(coarse_level.graph.num_nodes).unwrap();
416 let reduction = coarse_nodes / current_nodes;
417
418 if reduction > reduction_ratio || coarse_nodes < min_nodes {
419 break;
420 }
421
422 current_graph = coarse_level.graph.clone();
423 levels.push(coarse_level);
424 }
425
426 Self {
427 finest: graph,
428 levels,
429 }
430 }
431
432 fn coarsen_level(graph: &SparseGraph<T>) -> CoarseLevel<T> {
444 let matching = Self::heavy_edge_matching(graph);
445 let (coarse_graph, node_map, coarse_weights) = Self::contract_graph(graph, &matching);
446
447 CoarseLevel {
448 graph: coarse_graph,
449 node_map,
450 coarse_weights,
451 }
452 }
453
454 fn heavy_edge_matching(graph: &SparseGraph<T>) -> Vec<Option<usize>> {
468 let n = graph.num_nodes;
469 let mut matching = vec![None; n];
470 let mut matched = vec![false; n];
471
472 for i in 0..n {
473 if matched[i] {
474 continue;
475 }
476
477 let (neighbour, weights) = graph.get_neighbours(i);
478
479 let mut best_neighbour = None;
481 let mut best_weight = T::zero();
482
483 for (&j, &w) in neighbour.iter().zip(weights.iter()) {
484 if !matched[j] && j != i && w > best_weight {
485 best_weight = w;
486 best_neighbour = Some(j)
487 }
488 }
489
490 if let Some(j) = best_neighbour {
491 matching[i] = Some(j);
492 matching[j] = Some(i);
493 matched[i] = true;
494 matched[j] = true;
495 }
496 }
497
498 matching
499 }
500
501 fn contract_graph(
517 graph: &SparseGraph<T>,
518 matching: &[Option<usize>],
519 ) -> (SparseGraph<T>, Vec<usize>, Vec<usize>) {
520 let n = graph.num_nodes;
521 let mut node_map = vec![0; n];
522 let mut coarse_id = 0;
523
524 for i in 0..n {
526 if node_map[i] == 0 || i == 0 {
527 match matching[i] {
528 Some(j) if j > i => {
529 node_map[i] = coarse_id;
530 node_map[j] = coarse_id;
531 coarse_id += 1;
532 }
533 None => {
534 node_map[i] = coarse_id;
536 coarse_id += 1;
537 }
538 _ => {} }
540 }
541 }
542
543 let num_coarse = coarse_id;
544 let mut coarse_weights = vec![0; num_coarse];
545
546 for i in 0..n {
547 coarse_weights[node_map[i]] += 1;
548 }
549
550 let mut rows = Vec::new();
552 let mut cols = Vec::new();
553 let mut vals = Vec::new();
554
555 for i in 0..n {
556 let ci = node_map[i];
557 let (neighbours, weights) = graph.get_neighbours(i);
558
559 for (&j, &w) in neighbours.iter().zip(weights.iter()) {
560 let cj = node_map[j];
561 if ci != cj {
562 rows.push(ci);
563 cols.push(cj);
564 vals.push(w);
565 }
566 }
567 }
568
569 let mut edges: Vec<(usize, usize, T)> = rows
571 .into_iter()
572 .zip(cols)
573 .zip(vals)
574 .map(|((r, c), v)| (r, c, v))
575 .collect();
576 edges.sort_unstable_by_key(|(r, c, _)| (*r, *c));
577
578 let mut deduped_rows = Vec::new();
580 let mut deduped_cols = Vec::new();
581 let mut deduped_vals = Vec::new();
582
583 if !edges.is_empty() {
584 let (mut curr_r, mut curr_c, mut curr_v) = edges[0];
585
586 for &(r, c, v) in &edges[1..] {
587 if r == curr_r && c == curr_c {
588 curr_v += v;
589 } else {
590 deduped_rows.push(curr_r);
591 deduped_cols.push(curr_c);
592 deduped_vals.push(curr_v);
593 (curr_r, curr_c, curr_v) = (r, c, v);
594 }
595 }
596 deduped_rows.push(curr_r);
597 deduped_cols.push(curr_c);
598 deduped_vals.push(curr_v);
599 }
600
601 let coarse_csr = coo_to_csr(
602 &deduped_rows.index_cast(),
603 &deduped_cols.index_cast(),
604 &deduped_vals,
605 (num_coarse, num_coarse),
606 );
607
608 let coarse_graph = SparseGraph::new(num_coarse, coarse_csr, false);
609
610 (coarse_graph, node_map, coarse_weights)
611 }
612
613 pub fn prolong(&self, level: usize, coarse_labels: &[usize]) -> Vec<usize> {
624 if level >= self.levels.len() {
625 return coarse_labels.to_vec();
626 }
627
628 let node_map = &self.levels[level].node_map;
629 node_map.iter().map(|&ci| coarse_labels[ci]).collect()
630 }
631
632 pub fn coarsest(&self) -> &SparseGraph<T> {
638 self.levels.last().map(|l| &l.graph).unwrap_or(&self.finest)
639 }
640}
641
642#[derive(Debug, Clone)]
649pub struct AttributedGraph<'a, T> {
650 pub node_types: Vec<&'a str>,
652 pub from: Vec<usize>,
654 pub to: Vec<usize>,
656 pub edge_types: Vec<&'a str>,
658 pub weights: Vec<T>,
660}
661
662pub fn graph_from_strings_with_attributes<'a, T>(
684 nodes: &'a [String],
685 node_types: &'a [String],
686 from: &'a [String],
687 to: &'a [String],
688 edge_types: &'a [String],
689 edge_weights: &'a [T],
690) -> AttributedGraph<'a, T>
691where
692 T: BixverseFloat,
693{
694 assert_same_len!(nodes, node_types);
695 assert_same_len!(from, to, edge_types, edge_weights);
696
697 let name_to_idx: FxHashMap<&String, usize> = nodes
698 .iter()
699 .enumerate()
700 .map(|(idx, name)| (name, idx))
701 .collect();
702 let lookup = |name: &String| {
703 *name_to_idx
704 .get(name)
705 .unwrap_or_else(|| panic!("Node '{name}' not found in nodes list"))
706 };
707
708 AttributedGraph {
709 node_types: node_types.iter().map(String::as_str).collect(),
710 from: from.iter().map(lookup).collect(),
711 to: to.iter().map(lookup).collect(),
712 edge_types: edge_types.iter().map(String::as_str).collect(),
713 weights: edge_weights.to_vec(),
714 }
715}
716
717pub(crate) fn edges_to_csr<T>(
736 n: usize,
737 rows: &[u32],
738 cols: &[u32],
739 vals: &[T],
740) -> (Vec<usize>, Vec<u32>, Vec<T>)
741where
742 T: BixverseFloat,
743{
744 let mut indptr = vec![0usize; n + 1];
745 for &r in rows {
746 indptr[r as usize + 1] += 1;
747 }
748 for i in 0..n {
749 indptr[i + 1] += indptr[i];
750 }
751
752 let mut next = indptr[..n].to_vec();
753 let mut indices = vec![0u32; rows.len()];
754 let mut values = vec![T::zero(); rows.len()];
755 for ((&r, &c), &v) in rows.iter().zip(cols).zip(vals) {
756 let pos = next[r as usize];
757 indices[pos] = c;
758 values[pos] = v;
759 next[r as usize] += 1;
760 }
761
762 (indptr, indices, values)
763}
764
765pub fn get_knn_graph_adj<T>(similarities: &MatRef<T>, k: usize) -> Mat<T>
776where
777 T: BixverseFloat,
778{
779 assert_symmetric_mat!(similarities);
780 let n = similarities.nrows();
781 let mut adjacency: Mat<T> = Mat::zeros(n, n);
782
783 let blocks: Vec<usize> = (0..n).step_by(KNN_ROW_BLOCK).collect();
786 let rows: Vec<Vec<(usize, T)>> = blocks
787 .par_iter()
788 .flat_map_iter(|&start| {
789 let end = (start + KNN_ROW_BLOCK).min(n);
790 let mut heaps: Vec<BinaryHeap<(RevOrderedFloat<T>, usize)>> = (start..end)
791 .map(|_| BinaryHeap::with_capacity(k + 1))
792 .collect();
793
794 for j in 0..n {
795 let col = similarities.col(j);
796 for (heap, i) in heaps.iter_mut().zip(start..end) {
797 if i == j {
798 continue;
799 }
800 let entry = (RevOrderedFloat(col[i]), j);
801 if heap.len() == k && heap.peek().is_some_and(|top| entry >= *top) {
803 continue;
804 }
805 heap.push(entry);
806 if heap.len() > k {
807 heap.pop();
808 }
809 }
810 }
811
812 heaps.into_iter().map(|heap| {
813 heap.into_iter()
814 .map(|(ordered_sim, index)| (index, ordered_sim.0))
815 .collect::<Vec<_>>()
816 })
817 })
818 .collect();
819
820 for (i, neighbors) in rows.iter().enumerate() {
822 for &(j, sim) in neighbors {
823 adjacency[(i, j)] = sim;
824 }
825 }
826
827 let two = T::from_f64(2.0).unwrap();
829 for ib in (0..n).step_by(KNN_SYM_TILE) {
830 for jb in (ib..n).step_by(KNN_SYM_TILE) {
831 for j in jb..(jb + KNN_SYM_TILE).min(n) {
832 for i in ib..(ib + KNN_SYM_TILE).min(n).min(j) {
833 let val = (adjacency[(i, j)] + adjacency[(j, i)]) / two;
834 adjacency[(i, j)] = val;
835 adjacency[(j, i)] = val;
836 }
837 }
838 }
839 }
840
841 adjacency
842}
843
844pub fn adjacency_to_laplacian<T>(adjacency: &MatRef<T>, normalise: bool) -> Mat<T>
854where
855 T: BixverseFloat,
856{
857 assert_symmetric_mat!(adjacency);
858 let n = adjacency.nrows();
859
860 let mut degrees = vec![T::zero(); n];
862 for j in 0..n {
863 for (d, &x) in degrees.iter_mut().zip(adjacency.col(j).iter()) {
864 *d += x;
865 }
866 }
867
868 if !normalise {
869 let mut laplacian = adjacency.cloned();
870 for j in 0..n {
871 for i in 0..n {
872 if i != j {
873 laplacian[(i, j)] = -adjacency[(i, j)];
874 }
875 }
876 laplacian[(j, j)] = degrees[j] - adjacency[(j, j)];
877 }
878 laplacian
879 } else {
880 let inv_sqrt_d: Vec<T> = degrees
881 .iter()
882 .map(|&d| {
883 if d > T::from_f64(1e-10).unwrap() {
884 T::one() / d.sqrt()
885 } else {
886 T::zero()
887 }
888 })
889 .collect();
890 let mut laplacian = Mat::zeros(n, n);
891 for j in 0..n {
892 laplacian[(j, j)] = T::one();
893 for i in 0..n {
894 laplacian[(i, j)] -= inv_sqrt_d[i] * adjacency[(i, j)] * inv_sqrt_d[j];
895 }
896 }
897 laplacian
898 }
899}
900
901pub fn edge_pairs_to_sparse_graph<T>(
917 edges: &[usize],
918 weights: &[T],
919 n_nodes: usize,
920) -> SparseGraph<T>
921where
922 T: BixverseFloat + std::iter::Sum + Send + Sync,
923{
924 let n_edges = weights.len();
925
926 let mut indptr = vec![0usize; n_nodes + 1];
927 for e in 0..n_edges {
928 indptr[edges[2 * e] + 1] += 1;
929 indptr[edges[2 * e + 1] + 1] += 1;
930 }
931 for i in 1..=n_nodes {
932 indptr[i] += indptr[i - 1];
933 }
934
935 let nnz = indptr[n_nodes];
936 let mut indices = vec![0usize; nnz];
937 let mut data = vec![T::zero(); nnz];
938 let mut cursor = indptr.clone();
939
940 for e in 0..n_edges {
941 let i = edges[2 * e];
942 let j = edges[2 * e + 1];
943 let w = weights[e];
944
945 indices[cursor[i]] = j;
946 data[cursor[i]] = w;
947 cursor[i] += 1;
948
949 indices[cursor[j]] = i;
950 data[cursor[j]] = w;
951 cursor[j] += 1;
952 }
953
954 let mut rows: Vec<(&mut [usize], &mut [T])> = Vec::with_capacity(n_nodes);
956 let (mut idx_rest, mut data_rest) = (indices.as_mut_slice(), data.as_mut_slice());
957 for node in 0..n_nodes {
958 let len = indptr[node + 1] - indptr[node];
959 let (idx_row, idx_tail) = idx_rest.split_at_mut(len);
960 let (data_row, data_tail) = data_rest.split_at_mut(len);
961 rows.push((idx_row, data_row));
962 idx_rest = idx_tail;
963 data_rest = data_tail;
964 }
965
966 rows.par_iter_mut()
967 .for_each_init(Vec::<(usize, T)>::new, |scratch, (idx_row, data_row)| {
968 if idx_row.len() < 2 {
969 return;
970 }
971 scratch.clear();
972 scratch.extend(idx_row.iter().copied().zip(data_row.iter().copied()));
973 scratch.sort_unstable_by_key(|&(node, _)| node);
974 for ((i, d), &(node, w)) in idx_row.iter_mut().zip(data_row.iter_mut()).zip(&*scratch) {
975 *i = node;
976 *d = w;
977 }
978 });
979
980 let adjacency = CompressedSparseData2 {
981 data,
982 indices: indices.index_cast(),
983 indptr: indptr.index_cast(),
984 cs_type: CompressedSparseFormat::Csr,
985 data_2: None,
986 shape: (n_nodes, n_nodes),
987 };
988
989 SparseGraph::new(n_nodes, adjacency, false)
990}
991
992#[cfg(test)]
997mod tests {
998 use super::*;
999 use bixverse_core::core::math::sparse::CompressedSparseData2;
1000 use faer::Mat;
1001
1002 #[test]
1004 fn test_sparse_graph_properties() {
1005 let data = vec![1.0, 1.0, 1.0, 1.0];
1007 let indices = vec![1, 0, 2, 1]; let indptr = vec![0, 1, 3, 4]; let csr =
1010 CompressedSparseData2::<f64, f64>::new_csr(&data, &indices, &indptr, None, (3, 3));
1011
1012 let graph = SparseGraph::new(3, csr, false);
1013
1014 assert_eq!(graph.get_node_degree(0), 1);
1015 assert_eq!(graph.get_node_degree(1), 2);
1016 assert_eq!(graph.total_weight(), 2.0); let (n0_neighbors, n0_weights) = graph.get_neighbours(0);
1019 assert_eq!(n0_neighbors, &[1]);
1020 assert_eq!(n0_weights, &[1.0]);
1021 }
1022
1023 #[test]
1025 fn test_adjacency_to_laplacian() {
1026 let mut adj: Mat<f64> = Mat::zeros(2, 2);
1028 adj[(0, 1)] = 1.0;
1029 adj[(1, 0)] = 1.0;
1030
1031 let lap = adjacency_to_laplacian(&adj.as_ref(), false);
1034
1035 assert!((lap[(0, 0)] - 1.0).abs() < 1e-6);
1036 assert!((lap[(1, 1)] - 1.0).abs() < 1e-6);
1037 assert!((lap[(0, 1)] - (-1.0)).abs() < 1e-6);
1038 assert!((lap[(1, 0)] - (-1.0)).abs() < 1e-6);
1039 }
1040
1041 #[test]
1043 fn test_graph_from_strings_with_attributes() {
1044 let nodes = vec!["A".to_string(), "B".to_string(), "C".to_string()];
1045 let node_types = vec!["x".to_string(), "y".to_string(), "x".to_string()];
1046 let from = vec!["A".to_string(), "B".to_string(), "C".to_string()];
1047 let to = vec!["B".to_string(), "C".to_string(), "B".to_string()];
1048 let edge_types = vec!["e".to_string(), "f".to_string(), "e".to_string()];
1049 let weights = vec![1.5, 2.5, 0.5];
1050
1051 let graph = graph_from_strings_with_attributes(
1052 &nodes,
1053 &node_types,
1054 &from,
1055 &to,
1056 &edge_types,
1057 &weights,
1058 );
1059
1060 assert_eq!(graph.node_types, vec!["x", "y", "x"]);
1061 assert_eq!(graph.from, vec![0, 1, 2]);
1062 assert_eq!(graph.to, vec![1, 2, 1]);
1063 assert_eq!(graph.edge_types, vec!["e", "f", "e"]);
1064 assert_eq!(graph.weights, weights);
1065 }
1066
1067 fn small_graph() -> SparseGraph<f32> {
1069 let edges = [0usize, 1, 1, 2, 2, 0, 3, 3, 1, 2];
1070 let weights = [0.5f32, 1.0, 1.5, 2.0, 0.25];
1071 edge_pairs_to_sparse_graph(&edges, &weights, 5)
1072 }
1073
1074 fn edge_set(edges: &[u32], weights: &[f32]) -> Vec<(u32, u32, u32)> {
1076 let mut out: Vec<_> = edges
1077 .chunks_exact(2)
1078 .zip(weights)
1079 .map(|(e, w)| (e[0].min(e[1]), e[0].max(e[1]), w.to_bits()))
1080 .collect();
1081 out.sort_unstable();
1082 out
1083 }
1084
1085 #[test]
1087 fn test_sparse_graph_edge_pairs_round_trip() {
1088 let graph = small_graph();
1089 let (edges, weights) = graph.to_edge_pairs();
1090 assert_eq!(weights.len(), 5);
1091 assert_eq!(graph.get_edge_number(), 5);
1092 assert_eq!(
1093 edge_set(&edges, &weights),
1094 edge_set(&[0, 1, 1, 2, 2, 0, 3, 3, 1, 2], &[0.5, 1.0, 1.5, 2.0, 0.25])
1095 );
1096 }
1097
1098 #[test]
1100 fn test_sparse_graph_bytes_round_trip() {
1101 let graph = small_graph();
1102 let back = SparseGraph::<f32>::from_bytes(&graph.to_bytes().unwrap()).unwrap();
1103 assert_eq!(back.get_node_number(), 5);
1104 assert!(!back.is_directed());
1105 assert_eq!(back.adjacency.indptr, graph.adjacency.indptr);
1106 assert_eq!(back.adjacency.indices, graph.adjacency.indices);
1107 assert_eq!(back.adjacency.data, graph.adjacency.data);
1108 }
1109
1110 #[test]
1112 fn test_sparse_graph_bytes_bad_magic() {
1113 let csr_bytes = small_graph().adjacency.to_bytes().unwrap();
1114 assert!(matches!(
1115 SparseGraph::<f32>::from_bytes(&csr_bytes),
1116 Err(MethodsErrors::Core(CoreErrors::NotSerialised(_)))
1117 ));
1118 }
1119
1120 #[test]
1122 fn test_sparse_graph_bytes_version_mismatch() {
1123 let mut bytes = small_graph().to_bytes().unwrap();
1124 bytes[8..12].copy_from_slice(&999u32.to_le_bytes());
1125 assert!(matches!(
1126 SparseGraph::<f32>::from_bytes(&bytes),
1127 Err(MethodsErrors::Core(CoreErrors::SerialisedVersionMismatch {
1128 found: 999,
1129 ..
1130 }))
1131 ));
1132 }
1133
1134 #[test]
1136 fn test_sparse_graph_bytes_width_mismatch() {
1137 let bytes = small_graph().to_bytes().unwrap();
1138 assert!(matches!(
1139 SparseGraph::<f64>::from_bytes(&bytes),
1140 Err(MethodsErrors::Core(
1141 CoreErrors::SerialisedWidthMismatch { .. }
1142 ))
1143 ));
1144 }
1145}