1use std::collections::{HashMap, HashSet, VecDeque};
17
18use crate::error::{GraphError, Result};
19
20type InducedSubgraphResult = (Vec<Vec<(usize, f64)>>, Vec<usize>);
22
23struct Lcg {
31 state: u64,
32}
33
34impl Lcg {
35 fn new(seed: u64) -> Self {
36 Self {
38 state: seed.wrapping_add(6364136223846793005),
39 }
40 }
41
42 fn next_u64(&mut self) -> u64 {
44 self.state = self
45 .state
46 .wrapping_mul(6364136223846793005)
47 .wrapping_add(1442695040888963407);
48 self.state
49 }
50
51 fn next_f64(&mut self) -> f64 {
53 (self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
55 }
56
57 fn next_usize(&mut self, n: usize) -> usize {
59 debug_assert!(n > 0, "n must be > 0");
60 (self.next_u64() as usize) % n
61 }
62}
63
64pub fn random_walk(
87 adjacency: &[Vec<usize>],
88 start_node: usize,
89 walk_length: usize,
90 rng_seed: u64,
91) -> Result<Vec<usize>> {
92 let n = adjacency.len();
93 if start_node >= n {
94 return Err(GraphError::invalid_parameter(
95 "start_node",
96 start_node,
97 format!("must be < n_nodes ({})", n),
98 ));
99 }
100 if walk_length == 0 {
101 return Ok(Vec::new());
102 }
103
104 let mut rng = Lcg::new(rng_seed);
105 let mut walk = Vec::with_capacity(walk_length);
106 walk.push(start_node);
107
108 let mut current = start_node;
109 for _ in 1..walk_length {
110 let neighbours = &adjacency[current];
111 if neighbours.is_empty() {
112 break;
113 }
114 current = neighbours[rng.next_usize(neighbours.len())];
115 walk.push(current);
116 }
117
118 Ok(walk)
119}
120
121pub fn node2vec_walk(
151 adjacency: &[Vec<(usize, f64)>],
152 start_node: usize,
153 walk_length: usize,
154 p: f64,
155 q: f64,
156 rng_seed: u64,
157) -> Result<Vec<usize>> {
158 let n = adjacency.len();
159 if start_node >= n {
160 return Err(GraphError::invalid_parameter(
161 "start_node",
162 start_node,
163 format!("must be < n_nodes ({})", n),
164 ));
165 }
166 if p <= 0.0 {
167 return Err(GraphError::invalid_parameter(
168 "p",
169 p,
170 "must be strictly positive",
171 ));
172 }
173 if q <= 0.0 {
174 return Err(GraphError::invalid_parameter(
175 "q",
176 q,
177 "must be strictly positive",
178 ));
179 }
180 if walk_length == 0 {
181 return Ok(Vec::new());
182 }
183
184 let neighbour_sets: Vec<HashSet<usize>> = adjacency
187 .iter()
188 .map(|nbrs| nbrs.iter().map(|&(v, _)| v).collect())
189 .collect();
190
191 let mut rng = Lcg::new(rng_seed);
192 let mut walk: Vec<usize> = Vec::with_capacity(walk_length);
193 walk.push(start_node);
194
195 if walk_length == 1 || adjacency[start_node].is_empty() {
197 return Ok(walk);
198 }
199 let first_idx = rng.next_usize(adjacency[start_node].len());
200 let first_next = adjacency[start_node][first_idx].0;
201 walk.push(first_next);
202
203 for _ in 2..walk_length {
205 let prev = walk[walk.len() - 2];
206 let curr = walk[walk.len() - 1];
207
208 let nbrs = &adjacency[curr];
209 if nbrs.is_empty() {
210 break;
211 }
212
213 let prev_set = &neighbour_sets[prev];
215 let weights: Vec<f64> = nbrs
216 .iter()
217 .map(|&(x, edge_w)| {
218 let bias = if x == prev {
219 1.0 / p
220 } else if prev_set.contains(&x) {
221 1.0
222 } else {
223 1.0 / q
224 };
225 (edge_w.max(0.0)) * bias
226 })
227 .collect();
228
229 let total: f64 = weights.iter().sum();
230 let next_node = if total <= 0.0 {
231 nbrs[rng.next_usize(nbrs.len())].0
233 } else {
234 let threshold = rng.next_f64() * total;
235 let mut cumulative = 0.0;
236 let mut chosen = nbrs.last().map(|&(v, _)| v).unwrap_or(curr);
237 for (idx, &w) in weights.iter().enumerate() {
238 cumulative += w;
239 if cumulative >= threshold {
240 chosen = nbrs[idx].0;
241 break;
242 }
243 }
244 chosen
245 };
246
247 walk.push(next_node);
248 }
249
250 Ok(walk)
251}
252
253pub fn frontier_sampling(
282 adjacency: &[Vec<usize>],
283 n_nodes: usize,
284 sample_size: usize,
285 rng_seed: u64,
286) -> Result<Vec<usize>> {
287 if n_nodes == 0 {
288 return Err(GraphError::invalid_parameter(
289 "n_nodes",
290 0usize,
291 "must be > 0",
292 ));
293 }
294 if sample_size > n_nodes {
295 return Err(GraphError::invalid_parameter(
296 "sample_size",
297 sample_size,
298 format!("must be ≤ n_nodes ({})", n_nodes),
299 ));
300 }
301 if sample_size == 0 {
302 return Ok(Vec::new());
303 }
304
305 let mut rng = Lcg::new(rng_seed);
306 let mut sampled: HashSet<usize> = HashSet::with_capacity(sample_size);
307 let mut frontier: Vec<usize> = Vec::new();
308
309 let seed = rng.next_usize(n_nodes);
311 sampled.insert(seed);
312 frontier.push(seed);
313
314 let mut iters = 0usize;
315 let max_iters = sample_size * n_nodes.max(100) * 10;
316
317 while sampled.len() < sample_size && !frontier.is_empty() && iters < max_iters {
318 iters += 1;
319 let fi = rng.next_usize(frontier.len());
321 let u = frontier[fi];
322
323 let nbrs = &adjacency[u];
324 if nbrs.is_empty() {
325 frontier.swap_remove(fi);
327 continue;
328 }
329
330 let v = nbrs[rng.next_usize(nbrs.len())];
331 if sampled.insert(v) {
332 frontier.push(v);
334 }
335 }
337
338 if sampled.len() < sample_size {
340 for candidate in 0..n_nodes {
341 if sampled.len() >= sample_size {
342 break;
343 }
344 sampled.insert(candidate);
345 }
346 }
347
348 let mut result: Vec<usize> = sampled.into_iter().collect();
349 result.sort_unstable();
350 Ok(result)
351}
352
353pub fn forest_fire_sampling(
377 adjacency: &[Vec<usize>],
378 n_nodes: usize,
379 sample_size: usize,
380 forward_prob: f64,
381 rng_seed: u64,
382) -> Result<Vec<usize>> {
383 if n_nodes == 0 {
384 return Err(GraphError::invalid_parameter(
385 "n_nodes",
386 0usize,
387 "must be > 0",
388 ));
389 }
390 if sample_size > n_nodes {
391 return Err(GraphError::invalid_parameter(
392 "sample_size",
393 sample_size,
394 format!("must be ≤ n_nodes ({})", n_nodes),
395 ));
396 }
397 if forward_prob <= 0.0 || forward_prob >= 1.0 {
398 return Err(GraphError::invalid_parameter(
399 "forward_prob",
400 forward_prob,
401 "must be in (0, 1)",
402 ));
403 }
404 if sample_size == 0 {
405 return Ok(Vec::new());
406 }
407
408 let mut rng = Lcg::new(rng_seed);
409 let mut sampled: HashSet<usize> = HashSet::with_capacity(sample_size);
410 let mut burning: VecDeque<usize> = VecDeque::new();
412
413 let geometric_draw = |rng: &mut Lcg| -> usize {
416 let mut count = 0usize;
417 while rng.next_f64() < forward_prob {
418 count += 1;
419 }
420 count
421 };
422
423 while sampled.len() < sample_size {
424 if burning.is_empty() {
426 let start = rng.next_usize(n_nodes);
428 let mut found = false;
429 for offset in 0..n_nodes {
430 let candidate = (start + offset) % n_nodes;
431 if sampled.insert(candidate) {
432 burning.push_back(candidate);
433 found = true;
434 break;
435 }
436 }
437 if !found {
438 break; }
440 }
441
442 while let Some(u) = burning.pop_front() {
444 if sampled.len() >= sample_size {
445 break;
446 }
447 let nbrs = &adjacency[u];
448 if nbrs.is_empty() {
449 continue;
450 }
451
452 let n_burn = geometric_draw(&mut rng).min(nbrs.len());
454 if n_burn == 0 {
455 continue;
456 }
457
458 let mut candidates: Vec<usize> = nbrs.clone();
461 for i in 0..n_burn {
462 let j = i + rng.next_usize(candidates.len() - i);
463 candidates.swap(i, j);
464 }
465 for &v in candidates.iter().take(n_burn) {
466 if sampled.len() >= sample_size {
467 break;
468 }
469 if sampled.insert(v) {
470 burning.push_back(v);
471 }
472 }
473 }
474 }
475
476 let mut result: Vec<usize> = sampled.into_iter().collect();
477 result.sort_unstable();
478 Ok(result)
479}
480
481pub fn snowball_sampling(
502 adjacency: &[Vec<usize>],
503 seed_nodes: &[usize],
504 n_hops: usize,
505) -> Result<Vec<usize>> {
506 let n = adjacency.len();
507 if n == 0 {
508 return Err(GraphError::invalid_parameter(
509 "adjacency",
510 "empty",
511 "graph must have at least one node",
512 ));
513 }
514 for &s in seed_nodes {
515 if s >= n {
516 return Err(GraphError::invalid_parameter(
517 "seed_node",
518 s,
519 format!("must be < n_nodes ({})", n),
520 ));
521 }
522 }
523
524 let mut visited: HashSet<usize> = seed_nodes.iter().cloned().collect();
525 let mut frontier: Vec<usize> = seed_nodes.to_vec();
526
527 for _ in 0..n_hops {
528 let mut next_frontier: Vec<usize> = Vec::new();
529 for &u in &frontier {
530 for &v in &adjacency[u] {
531 if visited.insert(v) {
532 next_frontier.push(v);
533 }
534 }
535 }
536 if next_frontier.is_empty() {
537 break;
538 }
539 frontier = next_frontier;
540 }
541
542 let mut result: Vec<usize> = visited.into_iter().collect();
543 result.sort_unstable();
544 Ok(result)
545}
546
547pub fn induced_subgraph(
575 adjacency: &[Vec<(usize, f64)>],
576 node_set: &[usize],
577) -> Result<InducedSubgraphResult> {
578 let n = adjacency.len();
579 for &v in node_set {
580 if v >= n {
581 return Err(GraphError::invalid_parameter(
582 "node_set entry",
583 v,
584 format!("must be < n_nodes ({})", n),
585 ));
586 }
587 }
588
589 let mut original_indices: Vec<usize> = {
591 let mut s: Vec<usize> = node_set.to_vec();
592 s.sort_unstable();
593 s.dedup();
594 s
595 };
596 original_indices.sort_unstable();
597
598 let sub_n = original_indices.len();
599
600 let mut rev_map: HashMap<usize, usize> = HashMap::with_capacity(sub_n);
602 for (sub_i, &orig_i) in original_indices.iter().enumerate() {
603 rev_map.insert(orig_i, sub_i);
604 }
605
606 let mut sub_adj: Vec<Vec<(usize, f64)>> = vec![Vec::new(); sub_n];
608 for (sub_i, &orig_i) in original_indices.iter().enumerate() {
609 for &(orig_j, w) in &adjacency[orig_i] {
610 if let Some(&sub_j) = rev_map.get(&orig_j) {
611 sub_adj[sub_i].push((sub_j, w));
612 }
613 }
614 }
615
616 Ok((sub_adj, original_indices))
617}
618
619#[cfg(test)]
624mod tests {
625 use super::*;
626
627 fn path_adj(n: usize) -> Vec<Vec<usize>> {
631 let mut adj = vec![vec![]; n];
632 for i in 0..n.saturating_sub(1) {
633 adj[i].push(i + 1);
634 adj[i + 1].push(i);
635 }
636 adj
637 }
638
639 fn two_clique_adj(k: usize) -> Vec<Vec<usize>> {
641 let n = 2 * k;
642 let mut adj = vec![vec![]; n];
643 for i in 0..k {
644 for j in (i + 1)..k {
645 adj[i].push(j);
646 adj[j].push(i);
647 }
648 }
649 for i in 0..k {
650 for j in (i + 1)..k {
651 adj[k + i].push(k + j);
652 adj[k + j].push(k + i);
653 }
654 }
655 adj[0].push(k);
657 adj[k].push(0);
658 adj
659 }
660
661 fn weighted_cycle(n: usize) -> Vec<Vec<(usize, f64)>> {
663 let mut adj = vec![vec![]; n];
664 for i in 0..n {
665 let j = (i + 1) % n;
666 adj[i].push((j, 1.0));
667 adj[j].push((i, 1.0));
668 }
669 adj
670 }
671
672 #[test]
675 fn test_random_walk_length() {
676 let adj = path_adj(10);
677 let walk = random_walk(&adj, 0, 8, 42).expect("random_walk");
678 assert!(walk.len() <= 8, "walk too long: {}", walk.len());
679 assert_eq!(walk[0], 0, "must start at start_node");
680 }
681
682 #[test]
683 fn test_random_walk_all_valid_nodes() {
684 let adj = two_clique_adj(5);
685 let walk = random_walk(&adj, 0, 20, 7).expect("random_walk");
686 let n = adj.len();
687 for &node in &walk {
688 assert!(node < n, "node {} out of range", node);
689 }
690 }
691
692 #[test]
693 fn test_random_walk_isolated_node_stops_early() {
694 let adj: Vec<Vec<usize>> = vec![vec![], vec![0]];
696 let walk = random_walk(&adj, 0, 5, 0).expect("random_walk");
697 assert_eq!(walk, vec![0]);
699 }
700
701 #[test]
702 fn test_random_walk_zero_length() {
703 let adj = path_adj(5);
704 let walk = random_walk(&adj, 0, 0, 0).expect("random_walk");
705 assert!(walk.is_empty());
706 }
707
708 #[test]
709 fn test_random_walk_invalid_start() {
710 let adj = path_adj(5);
711 assert!(random_walk(&adj, 99, 5, 0).is_err());
712 }
713
714 #[test]
715 fn test_random_walk_consecutive_valid_edges() {
716 let adj = two_clique_adj(4);
718 let walk = random_walk(&adj, 0, 30, 123).expect("random_walk");
719 for window in walk.windows(2) {
720 let u = window[0];
721 let v = window[1];
722 assert!(
723 adj[u].contains(&v),
724 "edge ({}, {}) does not exist in adjacency list",
725 u,
726 v
727 );
728 }
729 }
730
731 #[test]
734 fn test_node2vec_walk_length() {
735 let adj = weighted_cycle(8);
736 let walk = node2vec_walk(&adj, 0, 10, 1.0, 1.0, 42).expect("node2vec_walk");
737 assert!(walk.len() <= 10);
738 assert_eq!(walk[0], 0);
739 }
740
741 #[test]
742 fn test_node2vec_walk_all_valid_nodes() {
743 let adj = weighted_cycle(6);
744 let n = adj.len();
745 let walk = node2vec_walk(&adj, 2, 20, 2.0, 0.5, 77).expect("node2vec_walk");
746 for &v in &walk {
747 assert!(v < n, "invalid node index {}", v);
748 }
749 }
750
751 #[test]
752 fn test_node2vec_walk_consecutive_edges() {
753 let adj = weighted_cycle(6);
754 let walk = node2vec_walk(&adj, 0, 15, 1.0, 1.0, 0).expect("node2vec_walk");
755 let unweighted: Vec<Vec<usize>> = adj
756 .iter()
757 .map(|nbrs| nbrs.iter().map(|&(v, _)| v).collect())
758 .collect();
759 for w in walk.windows(2) {
760 let u = w[0];
761 let v = w[1];
762 assert!(unweighted[u].contains(&v), "({}, {}) not an edge", u, v);
763 }
764 }
765
766 #[test]
767 fn test_node2vec_walk_invalid_p() {
768 let adj = weighted_cycle(4);
769 assert!(node2vec_walk(&adj, 0, 5, 0.0, 1.0, 0).is_err());
770 assert!(node2vec_walk(&adj, 0, 5, -1.0, 1.0, 0).is_err());
771 }
772
773 #[test]
774 fn test_node2vec_walk_invalid_q() {
775 let adj = weighted_cycle(4);
776 assert!(node2vec_walk(&adj, 0, 5, 1.0, 0.0, 0).is_err());
777 }
778
779 #[test]
780 fn test_node2vec_walk_zero_length() {
781 let adj = weighted_cycle(4);
782 let walk = node2vec_walk(&adj, 0, 0, 1.0, 1.0, 0).expect("node2vec_walk");
783 assert!(walk.is_empty());
784 }
785
786 #[test]
787 fn test_node2vec_walk_length_one() {
788 let adj = weighted_cycle(4);
789 let walk = node2vec_walk(&adj, 1, 1, 1.0, 1.0, 0).expect("node2vec_walk");
790 assert_eq!(walk, vec![1]);
791 }
792
793 #[test]
796 fn test_frontier_sampling_basic() {
797 let adj = two_clique_adj(5);
798 let n = adj.len();
799 let sample = frontier_sampling(&adj, n, 6, 42).expect("frontier_sampling");
800 assert_eq!(sample.len(), 6);
801 for &v in &sample {
803 assert!(v < n);
804 }
805 let set: HashSet<usize> = sample.iter().cloned().collect();
807 assert_eq!(set.len(), sample.len());
808 }
809
810 #[test]
811 fn test_frontier_sampling_full_graph() {
812 let adj = path_adj(5);
813 let sample = frontier_sampling(&adj, 5, 5, 0).expect("frontier_sampling");
814 assert_eq!(sample.len(), 5);
815 }
816
817 #[test]
818 fn test_frontier_sampling_zero_size() {
819 let adj = path_adj(5);
820 let sample = frontier_sampling(&adj, 5, 0, 0).expect("frontier_sampling");
821 assert!(sample.is_empty());
822 }
823
824 #[test]
825 fn test_frontier_sampling_invalid_n_nodes() {
826 let adj: Vec<Vec<usize>> = vec![];
827 assert!(frontier_sampling(&adj, 0, 1, 0).is_err());
828 }
829
830 #[test]
831 fn test_frontier_sampling_sample_exceeds_n() {
832 let adj = path_adj(3);
833 assert!(frontier_sampling(&adj, 3, 5, 0).is_err());
834 }
835
836 #[test]
837 fn test_frontier_sampling_sorted_output() {
838 let adj = two_clique_adj(4);
839 let n = adj.len();
840 let sample = frontier_sampling(&adj, n, 5, 99).expect("frontier_sampling");
841 let mut sorted = sample.clone();
842 sorted.sort_unstable();
843 assert_eq!(sample, sorted, "output must be sorted");
844 }
845
846 #[test]
849 fn test_forest_fire_basic() {
850 let adj = two_clique_adj(5);
851 let n = adj.len();
852 let sample = forest_fire_sampling(&adj, n, 6, 0.7, 42).expect("forest_fire");
853 assert_eq!(sample.len(), 6);
854 for &v in &sample {
855 assert!(v < n);
856 }
857 let set: HashSet<usize> = sample.iter().cloned().collect();
858 assert_eq!(set.len(), sample.len());
859 }
860
861 #[test]
862 fn test_forest_fire_full_graph() {
863 let adj = path_adj(4);
864 let sample = forest_fire_sampling(&adj, 4, 4, 0.5, 0).expect("forest_fire");
865 assert_eq!(sample.len(), 4);
866 }
867
868 #[test]
869 fn test_forest_fire_zero_size() {
870 let adj = path_adj(5);
871 let sample = forest_fire_sampling(&adj, 5, 0, 0.5, 0).expect("forest_fire");
872 assert!(sample.is_empty());
873 }
874
875 #[test]
876 fn test_forest_fire_invalid_prob() {
877 let adj = path_adj(5);
878 assert!(forest_fire_sampling(&adj, 5, 3, 0.0, 0).is_err());
879 assert!(forest_fire_sampling(&adj, 5, 3, 1.0, 0).is_err());
880 assert!(forest_fire_sampling(&adj, 5, 3, -0.5, 0).is_err());
881 }
882
883 #[test]
884 fn test_forest_fire_sorted_output() {
885 let adj = two_clique_adj(4);
886 let n = adj.len();
887 let sample = forest_fire_sampling(&adj, n, 5, 0.6, 13).expect("forest_fire");
888 let mut sorted = sample.clone();
889 sorted.sort_unstable();
890 assert_eq!(sample, sorted);
891 }
892
893 #[test]
896 fn test_snowball_sampling_zero_hops() {
897 let adj = path_adj(8);
898 let sample = snowball_sampling(&adj, &[3], 0).expect("snowball");
899 assert_eq!(sample, vec![3]);
900 }
901
902 #[test]
903 fn test_snowball_sampling_one_hop_path() {
904 let adj = path_adj(6);
905 let sample = snowball_sampling(&adj, &[3], 1).expect("snowball");
907 let set: HashSet<usize> = sample.iter().cloned().collect();
908 assert!(set.contains(&2));
909 assert!(set.contains(&3));
910 assert!(set.contains(&4));
911 assert_eq!(sample.len(), 3);
912 }
913
914 #[test]
915 fn test_snowball_sampling_two_hops_path() {
916 let adj = path_adj(7);
917 let sample = snowball_sampling(&adj, &[3], 2).expect("snowball");
919 let set: HashSet<usize> = sample.iter().cloned().collect();
920 for v in [1, 2, 3, 4, 5] {
921 assert!(set.contains(&v), "node {} missing", v);
922 }
923 }
924
925 #[test]
926 fn test_snowball_sampling_multiple_seeds() {
927 let adj = path_adj(10);
928 let sample = snowball_sampling(&adj, &[0, 9], 1).expect("snowball");
930 let set: HashSet<usize> = sample.iter().cloned().collect();
931 assert!(set.contains(&0) && set.contains(&1));
933 assert!(set.contains(&8) && set.contains(&9));
934 }
935
936 #[test]
937 fn test_snowball_sampling_empty_adj() {
938 let adj: Vec<Vec<usize>> = vec![];
939 assert!(snowball_sampling(&adj, &[0], 1).is_err());
940 }
941
942 #[test]
943 fn test_snowball_sampling_out_of_range_seed() {
944 let adj = path_adj(4);
945 assert!(snowball_sampling(&adj, &[99], 1).is_err());
946 }
947
948 #[test]
949 fn test_snowball_sampling_sorted_no_duplicates() {
950 let adj = two_clique_adj(4);
951 let sample = snowball_sampling(&adj, &[0, 1], 2).expect("snowball");
952 let mut sorted = sample.clone();
953 sorted.sort_unstable();
954 sorted.dedup();
955 assert_eq!(sample, sorted, "output must be sorted with no duplicates");
956 }
957
958 #[test]
961 fn test_induced_subgraph_basic() {
962 let adj = vec![
964 vec![(1, 1.0)],
965 vec![(0, 1.0), (2, 1.0)],
966 vec![(1, 1.0), (3, 1.0)],
967 vec![(2, 1.0)],
968 ];
969 let (sub, orig) = induced_subgraph(&adj, &[1, 2]).expect("induced_subgraph");
971 assert_eq!(orig, vec![1, 2]);
972 assert_eq!(sub.len(), 2);
973 assert_eq!(sub[0].len(), 1);
975 assert_eq!(sub[0][0], (1, 1.0));
976 assert_eq!(sub[1].len(), 1);
978 assert_eq!(sub[1][0], (0, 1.0));
979 }
980
981 #[test]
982 fn test_induced_subgraph_no_internal_edges() {
983 let adj = vec![
985 vec![(1, 1.0), (2, 1.0), (3, 1.0)],
986 vec![(0, 1.0)],
987 vec![(0, 1.0)],
988 vec![(0, 1.0)],
989 ];
990 let (sub, orig) = induced_subgraph(&adj, &[1, 2, 3]).expect("induced_subgraph");
992 assert_eq!(orig, vec![1, 2, 3]);
993 for nbrs in &sub {
994 assert!(
995 nbrs.is_empty(),
996 "leaves should have no edges among themselves"
997 );
998 }
999 }
1000
1001 #[test]
1002 fn test_induced_subgraph_full_graph() {
1003 let adj = vec![vec![(1, 2.0)], vec![(0, 2.0), (2, 3.0)], vec![(1, 3.0)]];
1004 let (sub, orig) = induced_subgraph(&adj, &[0, 1, 2]).expect("induced_subgraph");
1005 assert_eq!(orig, vec![0, 1, 2]);
1006 assert_eq!(sub, adj);
1008 }
1009
1010 #[test]
1011 fn test_induced_subgraph_duplicates_in_node_set() {
1012 let adj = vec![vec![(1, 1.0)], vec![(0, 1.0), (2, 1.0)], vec![(1, 1.0)]];
1013 let (sub, orig) = induced_subgraph(&adj, &[0, 0, 1]).expect("induced_subgraph");
1015 assert_eq!(orig, vec![0, 1]);
1016 assert_eq!(sub.len(), 2);
1017 }
1018
1019 #[test]
1020 fn test_induced_subgraph_out_of_range() {
1021 let adj = vec![vec![(1, 1.0)], vec![(0, 1.0)]];
1022 assert!(induced_subgraph(&adj, &[0, 99]).is_err());
1023 }
1024
1025 #[test]
1026 fn test_induced_subgraph_empty_node_set() {
1027 let adj = vec![vec![(1, 1.0)], vec![(0, 1.0)]];
1028 let (sub, orig) = induced_subgraph(&adj, &[]).expect("induced_subgraph");
1029 assert!(sub.is_empty());
1030 assert!(orig.is_empty());
1031 }
1032
1033 #[test]
1034 fn test_induced_subgraph_preserves_weights() {
1035 let adj = vec![vec![(1, 5.0)], vec![(0, 5.0), (2, 3.0)], vec![(1, 3.0)]];
1037 let (sub, _) = induced_subgraph(&adj, &[0, 1]).expect("induced_subgraph");
1038 assert_eq!(sub[0], vec![(1, 5.0)]);
1040 assert_eq!(sub[1], vec![(0, 5.0)]);
1041 }
1042}