1use crate::algorithms::properties::degree::DegreeMode;
21use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
22
23fn incident_with_mode(graph: &Graph, v: VertexId, mode: DegreeMode) -> IgraphResult<Vec<u32>> {
25 if !graph.is_directed() {
26 return graph.incident(v);
27 }
28 match mode {
29 DegreeMode::Out => graph.incident(v),
30 DegreeMode::In => graph.incident_in(v),
31 DegreeMode::All => {
32 let mut out = graph.incident(v)?;
33 let in_edges = graph.incident_in(v)?;
34 out.extend(in_edges);
35 Ok(out)
36 }
37 }
38}
39
40fn neighbors_with_mode(graph: &Graph, v: VertexId, mode: DegreeMode) -> IgraphResult<Vec<u32>> {
42 if !graph.is_directed() {
43 return graph.neighbors(v);
44 }
45 match mode {
46 DegreeMode::Out => {
47 let edges = graph.incident(v)?;
48 edges.iter().map(|&e| graph.edge_other(e, v)).collect()
49 }
50 DegreeMode::In => {
51 let edges = graph.incident_in(v)?;
52 edges.iter().map(|&e| graph.edge_other(e, v)).collect()
53 }
54 DegreeMode::All => {
55 let out_edges = graph.incident(v)?;
56 let in_edges = graph.incident_in(v)?;
57 let mut neis = Vec::with_capacity(out_edges.len() + in_edges.len());
58 for &e in &out_edges {
59 neis.push(graph.edge_other(e, v)?);
60 }
61 for &e in &in_edges {
62 neis.push(graph.edge_other(e, v)?);
63 }
64 Ok(neis)
65 }
66 }
67}
68
69pub fn local_scan_1(graph: &Graph, weights: Option<&[f64]>) -> IgraphResult<Vec<f64>> {
101 local_scan_1_ecount(graph, weights, DegreeMode::All)
102}
103
104pub fn local_scan_0(
125 graph: &Graph,
126 weights: Option<&[f64]>,
127 mode: DegreeMode,
128) -> IgraphResult<Vec<f64>> {
129 if let Some(w) = weights {
130 if w.len() != graph.ecount() {
131 return Err(IgraphError::InvalidArgument(format!(
132 "weight vector length {} != edge count {}",
133 w.len(),
134 graph.ecount()
135 )));
136 }
137 }
138
139 let n = graph.vcount() as usize;
140 let mut res = vec![0.0_f64; n];
141 let directed = graph.is_directed();
142 let ecount = graph.ecount();
143
144 for eid in 0..ecount {
145 let eid_u32 =
146 u32::try_from(eid).map_err(|_| IgraphError::Internal("ecount exceeds u32::MAX"))?;
147 let from = graph.edge_source(eid_u32)? as usize;
148 let to = graph.edge_target(eid_u32)? as usize;
149 let w = weights.map_or(1.0, |ws| ws[eid]);
150
151 if directed {
152 match mode {
153 DegreeMode::Out => res[from] += w,
154 DegreeMode::In => res[to] += w,
155 DegreeMode::All => {
156 res[from] += w;
157 res[to] += w;
158 }
159 }
160 } else {
161 res[from] += w;
162 res[to] += w;
163 }
164 }
165
166 Ok(res)
167}
168
169fn local_scan_1_directed(
173 graph: &Graph,
174 weights: Option<&[f64]>,
175 mode: DegreeMode,
176) -> IgraphResult<Vec<f64>> {
177 let n = graph.vcount();
178 let n_usize = n as usize;
179 let mut res = vec![0.0_f64; n_usize];
180 let mut marker: Vec<i64> = vec![-1; n_usize];
181
182 for node in 0..n {
183 let node_i = i64::from(node);
184 let edges1 = incident_with_mode(graph, node, mode)?;
185
186 marker[node as usize] = node_i;
187 for &e in &edges1 {
188 let nei = graph.edge_other(e, node)?;
189 let w = weights.map_or(1.0, |ws| ws[e as usize]);
190 marker[nei as usize] = node_i;
191 res[node as usize] += w;
192 }
193
194 for &e in &edges1 {
195 let nei = graph.edge_other(e, node)?;
196 if nei == node {
197 break;
198 }
199 let edges2 = incident_with_mode(graph, nei, mode)?;
200 for &e2 in &edges2 {
201 let nei2 = graph.edge_other(e2, nei)?;
202 if marker[nei2 as usize] == node_i {
203 let w2 = weights.map_or(1.0, |ws| ws[e2 as usize]);
204 res[node as usize] += w2;
205 }
206 }
207 }
208 }
209
210 Ok(res)
211}
212
213fn local_scan_1_directed_all(graph: &Graph, weights: Option<&[f64]>) -> IgraphResult<Vec<f64>> {
215 let n = graph.vcount();
216 let n_usize = n as usize;
217 let mut res = vec![0.0_f64; n_usize];
218 let mut marker: Vec<i64> = vec![-1; n_usize];
219
220 for node in 0..n {
221 let node_i = i64::from(node);
222 let edges1 = incident_with_mode(graph, node, DegreeMode::All)?;
223
224 for &e in &edges1 {
225 let nei = graph.edge_other(e, node)?;
226 let w = weights.map_or(1.0, |ws| ws[e as usize]);
227 marker[nei as usize] = node_i;
228 res[node as usize] += w;
229 }
230
231 marker[node as usize] = -1;
232
233 for &e in &edges1 {
234 let nei = graph.edge_other(e, node)?;
235 if marker[nei as usize] != node_i {
236 continue;
237 }
238 let edges2 = incident_with_mode(graph, nei, DegreeMode::All)?;
239 for &e2 in &edges2 {
240 let nei2 = graph.edge_other(e2, nei)?;
241 if marker[nei2 as usize] == node_i {
242 let w2 = weights.map_or(1.0, |ws| ws[e2 as usize]);
243 res[node as usize] += w2;
244 }
245 }
246 marker[nei as usize] = -1;
247 }
248 }
249
250 Ok(res)
251}
252
253pub fn local_scan_1_ecount(
272 graph: &Graph,
273 weights: Option<&[f64]>,
274 mode: DegreeMode,
275) -> IgraphResult<Vec<f64>> {
276 if let Some(w) = weights {
277 if w.len() != graph.ecount() {
278 return Err(IgraphError::InvalidArgument(format!(
279 "weight vector length {} != edge count {}",
280 w.len(),
281 graph.ecount()
282 )));
283 }
284 }
285
286 if graph.is_directed() {
287 if mode == DegreeMode::All {
288 local_scan_1_directed_all(graph, weights)
289 } else {
290 local_scan_1_directed(graph, weights, mode)
291 }
292 } else {
293 crate::algorithms::properties::local_scan_k::local_scan_k_ecount(graph, 1, weights, mode)
295 }
296}
297
298pub fn local_scan_0_them(
322 us: &Graph,
323 them: &Graph,
324 weights_them: Option<&[f64]>,
325 mode: DegreeMode,
326) -> IgraphResult<Vec<f64>> {
327 check_them_compat(us, them, weights_them, "local_scan_0_them")?;
328
329 let is_graph = crate::algorithms::operators::intersection::intersection(us, them)?;
330
331 if weights_them.is_none() {
333 return local_scan_0(&is_graph, None, mode);
334 }
335
336 let ws = weights_them.expect("checked above");
345 let n = us.vcount() as usize;
346 let mut res = vec![0.0_f64; n];
347 let directed = us.is_directed();
348 let us_ecount = us.ecount();
349
350 for eid in 0..us_ecount {
351 let eid_u32 =
352 u32::try_from(eid).map_err(|_| IgraphError::Internal("ecount exceeds u32::MAX"))?;
353 let from = us.edge_source(eid_u32)?;
354 let to = us.edge_target(eid_u32)?;
355
356 if let Some(them_eid) = them.find_eid(from, to)? {
358 let w = ws[them_eid as usize];
359 if directed {
360 match mode {
361 DegreeMode::Out => res[from as usize] += w,
362 DegreeMode::In => res[to as usize] += w,
363 DegreeMode::All => {
364 res[from as usize] += w;
365 res[to as usize] += w;
366 }
367 }
368 } else {
369 res[from as usize] += w;
370 res[to as usize] += w;
371 }
372 }
373 }
374
375 Ok(res)
376}
377
378pub fn local_scan_1_ecount_them(
401 us: &Graph,
402 them: &Graph,
403 weights_them: Option<&[f64]>,
404 mode: DegreeMode,
405) -> IgraphResult<Vec<f64>> {
406 check_them_compat(us, them, weights_them, "local_scan_1_ecount_them")?;
407
408 let n = us.vcount();
409 let n_usize = n as usize;
410 let mut res = vec![0.0_f64; n_usize];
411 let mut marker: Vec<i64> = vec![-1; n_usize];
412 let undirected_or_all = mode == DegreeMode::All || !us.is_directed();
413
414 for node in 0..n {
415 let node_i = i64::from(node);
416
417 marker[node as usize] = node_i;
418 let us_neis = neighbors_with_mode(us, node, mode)?;
419 for &nei in &us_neis {
420 marker[nei as usize] = node_i;
421 }
422
423 let them_edges_ego = incident_with_mode(them, node, mode)?;
424 for &e in &them_edges_ego {
425 let nei = them.edge_other(e, node)?;
426 if marker[nei as usize] == node_i {
427 let w = weights_them.map_or(1.0, |ws| ws[e as usize]);
428 res[node as usize] += w;
429 }
430 }
431
432 for &us_nei in &us_neis {
433 let them_edges = incident_with_mode(them, us_nei, mode)?;
434 for &e2 in &them_edges {
435 let nei2 = them.edge_other(e2, us_nei)?;
436 if marker[nei2 as usize] == node_i {
437 let w = weights_them.map_or(1.0, |ws| ws[e2 as usize]);
438 res[node as usize] += w;
439 }
440 }
441 }
442
443 if undirected_or_all {
444 res[node as usize] /= 2.0;
445 }
446 }
447
448 Ok(res)
449}
450
451pub fn local_scan_subset_ecount(
472 graph: &Graph,
473 subsets: &[Vec<VertexId>],
474 weights: Option<&[f64]>,
475) -> IgraphResult<Vec<f64>> {
476 if let Some(w) = weights {
477 if w.len() != graph.ecount() {
478 return Err(IgraphError::InvalidArgument(format!(
479 "weight vector length {} != edge count {}",
480 w.len(),
481 graph.ecount()
482 )));
483 }
484 }
485
486 let n = graph.vcount();
487 let n_usize = n as usize;
488 let directed = graph.is_directed();
489 let num_subsets = subsets.len();
490 let mut res = vec![0.0_f64; num_subsets];
491 let mut marker: Vec<i64> = vec![-1; n_usize];
492
493 for (subset_idx, subset) in subsets.iter().enumerate() {
494 let subset_i = i64::try_from(subset_idx)
495 .map_err(|_| IgraphError::Internal("subset index overflow"))?;
496
497 for &v in subset {
498 if v >= n {
499 return Err(IgraphError::InvalidArgument(format!(
500 "vertex {v} out of range for graph with {n} vertices"
501 )));
502 }
503 marker[v as usize] = subset_i;
504 }
505
506 for &v in subset {
509 let edges = graph.incident(v)?;
510 for &e in &edges {
511 let nei = graph.edge_other(e, v)?;
512 if marker[nei as usize] == subset_i {
513 let w = weights.map_or(1.0, |ws| ws[e as usize]);
514 res[subset_idx] += w;
515 }
516 }
517
518 if directed {
519 let in_edges = graph.incident_in(v)?;
520 for &e in &in_edges {
521 let nei = graph.edge_other(e, v)?;
522 if marker[nei as usize] == subset_i {
523 let w = weights.map_or(1.0, |ws| ws[e as usize]);
524 res[subset_idx] += w;
525 }
526 }
527 }
528 }
529
530 res[subset_idx] /= 2.0;
531 }
532
533 Ok(res)
534}
535
536fn check_them_compat(
538 us: &Graph,
539 them: &Graph,
540 weights_them: Option<&[f64]>,
541 name: &str,
542) -> IgraphResult<()> {
543 if us.vcount() != them.vcount() {
544 return Err(IgraphError::InvalidArgument(format!(
545 "{name}: vertex count mismatch ({} vs {})",
546 us.vcount(),
547 them.vcount()
548 )));
549 }
550 if us.is_directed() != them.is_directed() {
551 return Err(IgraphError::InvalidArgument(format!(
552 "{name}: directedness mismatch"
553 )));
554 }
555 if let Some(w) = weights_them {
556 if w.len() != them.ecount() {
557 return Err(IgraphError::InvalidArgument(format!(
558 "{name}: weight vector length {} != them edge count {}",
559 w.len(),
560 them.ecount()
561 )));
562 }
563 }
564 Ok(())
565}
566
567#[cfg(test)]
568mod tests {
569 use super::*;
570
571 fn close(a: f64, b: f64) -> bool {
572 (a - b).abs() < 1e-10
573 }
574
575 #[test]
578 fn empty_graph() {
579 let g = Graph::with_vertices(0);
580 let s = local_scan_1(&g, None).unwrap();
581 assert!(s.is_empty());
582 }
583
584 #[test]
585 fn no_edges() {
586 let g = Graph::with_vertices(5);
587 let s = local_scan_1(&g, None).unwrap();
588 assert!(s.iter().all(|&v| close(v, 0.0)));
589 }
590
591 #[test]
592 fn triangle() {
593 let mut g = Graph::with_vertices(3);
594 g.add_edge(0, 1).unwrap();
595 g.add_edge(1, 2).unwrap();
596 g.add_edge(2, 0).unwrap();
597 let s = local_scan_1(&g, None).unwrap();
598 assert!(close(s[0], 3.0));
599 assert!(close(s[1], 3.0));
600 assert!(close(s[2], 3.0));
601 }
602
603 #[test]
604 fn path_5() {
605 let mut g = Graph::with_vertices(5);
606 for i in 0..4u32 {
607 g.add_edge(i, i + 1).unwrap();
608 }
609 let s = local_scan_1(&g, None).unwrap();
610 assert!(close(s[0], 1.0));
611 assert!(close(s[1], 2.0));
612 assert!(close(s[2], 2.0));
613 assert!(close(s[3], 2.0));
614 assert!(close(s[4], 1.0));
615 }
616
617 #[test]
618 fn star() {
619 let mut g = Graph::with_vertices(4);
620 g.add_edge(0, 1).unwrap();
621 g.add_edge(0, 2).unwrap();
622 g.add_edge(0, 3).unwrap();
623 let s = local_scan_1(&g, None).unwrap();
624 assert!(close(s[0], 3.0));
625 assert!(close(s[1], 1.0));
626 assert!(close(s[2], 1.0));
627 assert!(close(s[3], 1.0));
628 }
629
630 #[test]
631 fn k4() {
632 let mut g = Graph::with_vertices(4);
633 for u in 0..4u32 {
634 for v in (u + 1)..4 {
635 g.add_edge(u, v).unwrap();
636 }
637 }
638 let s = local_scan_1(&g, None).unwrap();
639 for &val in &s {
640 assert!(close(val, 6.0));
641 }
642 }
643
644 #[test]
645 fn two_triangles_with_bridge() {
646 let mut g = Graph::with_vertices(6);
647 g.add_edge(0, 1).unwrap();
648 g.add_edge(0, 2).unwrap();
649 g.add_edge(1, 2).unwrap();
650 g.add_edge(3, 4).unwrap();
651 g.add_edge(3, 5).unwrap();
652 g.add_edge(4, 5).unwrap();
653 g.add_edge(2, 3).unwrap();
654 let s = local_scan_1(&g, None).unwrap();
655 assert!(close(s[0], 3.0));
656 assert!(close(s[1], 3.0));
657 assert!(close(s[2], 4.0));
658 assert!(close(s[3], 4.0));
659 assert!(close(s[4], 3.0));
660 assert!(close(s[5], 3.0));
661 }
662
663 #[test]
664 fn weighted() {
665 let mut g = Graph::with_vertices(3);
666 g.add_edge(0, 1).unwrap();
667 g.add_edge(1, 2).unwrap();
668 g.add_edge(2, 0).unwrap();
669 let w = vec![2.0, 3.0, 5.0];
670 let s = local_scan_1(&g, Some(&w)).unwrap();
671 assert!(close(s[0], 10.0));
672 assert!(close(s[1], 10.0));
673 assert!(close(s[2], 10.0));
674 }
675
676 #[test]
677 fn self_loop() {
678 let mut g = Graph::with_vertices(2);
679 g.add_edge(0, 0).unwrap();
680 g.add_edge(0, 1).unwrap();
681 let s = local_scan_1(&g, None).unwrap();
682 assert!(close(s[0], 2.0));
683 assert!(close(s[1], 2.0));
684 }
685
686 #[test]
687 fn weights_mismatch_errors() {
688 let mut g = Graph::with_vertices(2);
689 g.add_edge(0, 1).unwrap();
690 assert!(local_scan_1(&g, Some(&[1.0, 2.0])).is_err());
691 }
692
693 #[test]
696 fn scan0_empty() {
697 let g = Graph::with_vertices(0);
698 let res = local_scan_0(&g, None, DegreeMode::All).unwrap();
699 assert!(res.is_empty());
700 }
701
702 #[test]
703 fn scan0_no_edges() {
704 let g = Graph::with_vertices(5);
705 let res = local_scan_0(&g, None, DegreeMode::All).unwrap();
706 assert_eq!(res, vec![0.0; 5]);
707 }
708
709 #[test]
710 fn scan0_triangle() {
711 let mut g = Graph::with_vertices(3);
712 g.add_edge(0, 1).unwrap();
713 g.add_edge(1, 2).unwrap();
714 g.add_edge(2, 0).unwrap();
715 let res = local_scan_0(&g, None, DegreeMode::All).unwrap();
716 for &d in &res {
717 assert!(close(d, 2.0));
718 }
719 }
720
721 #[test]
722 fn scan0_weighted() {
723 let mut g = Graph::with_vertices(3);
724 g.add_edge(0, 1).unwrap();
725 g.add_edge(1, 2).unwrap();
726 let w = vec![3.0, 5.0];
727 let res = local_scan_0(&g, Some(&w), DegreeMode::All).unwrap();
728 assert!(close(res[0], 3.0));
729 assert!(close(res[1], 8.0));
730 assert!(close(res[2], 5.0));
731 }
732
733 #[test]
734 fn scan0_directed_out() {
735 let mut g = Graph::new(3, true).unwrap();
736 g.add_edge(0, 1).unwrap();
737 g.add_edge(0, 2).unwrap();
738 g.add_edge(1, 2).unwrap();
739 let res = local_scan_0(&g, None, DegreeMode::Out).unwrap();
740 assert!(close(res[0], 2.0));
741 assert!(close(res[1], 1.0));
742 assert!(close(res[2], 0.0));
743 }
744
745 #[test]
746 fn scan0_directed_in() {
747 let mut g = Graph::new(3, true).unwrap();
748 g.add_edge(0, 1).unwrap();
749 g.add_edge(0, 2).unwrap();
750 g.add_edge(1, 2).unwrap();
751 let res = local_scan_0(&g, None, DegreeMode::In).unwrap();
752 assert!(close(res[0], 0.0));
753 assert!(close(res[1], 1.0));
754 assert!(close(res[2], 2.0));
755 }
756
757 #[test]
760 fn scan1e_triangle() {
761 let mut g = Graph::with_vertices(3);
762 g.add_edge(0, 1).unwrap();
763 g.add_edge(1, 2).unwrap();
764 g.add_edge(2, 0).unwrap();
765 let res = local_scan_1_ecount(&g, None, DegreeMode::All).unwrap();
766 for &v in &res {
767 assert!(close(v, 3.0));
768 }
769 }
770
771 #[test]
772 fn scan1e_path4() {
773 let mut g = Graph::with_vertices(4);
774 g.add_edge(0, 1).unwrap();
775 g.add_edge(1, 2).unwrap();
776 g.add_edge(2, 3).unwrap();
777 let res = local_scan_1_ecount(&g, None, DegreeMode::All).unwrap();
778 assert!(close(res[0], 1.0));
779 assert!(close(res[1], 2.0));
780 assert!(close(res[2], 2.0));
781 assert!(close(res[3], 1.0));
782 }
783
784 #[test]
785 fn scan1e_directed_out() {
786 let mut g = Graph::new(3, true).unwrap();
787 g.add_edge(0, 1).unwrap();
788 g.add_edge(1, 2).unwrap();
789 let res = local_scan_1_ecount(&g, None, DegreeMode::Out).unwrap();
790 assert!(close(res[0], 1.0));
791 assert!(close(res[1], 1.0));
792 assert!(close(res[2], 0.0));
793 }
794
795 #[test]
796 fn scan1e_directed_all_cycle() {
797 let mut g = Graph::new(3, true).unwrap();
798 g.add_edge(0, 1).unwrap();
799 g.add_edge(1, 2).unwrap();
800 g.add_edge(2, 0).unwrap();
801 let res = local_scan_1_ecount(&g, None, DegreeMode::All).unwrap();
802 for &v in &res {
803 assert!(close(v, 3.0));
804 }
805 }
806
807 #[test]
810 fn scan0t_basic() {
811 let mut us = Graph::with_vertices(3);
812 us.add_edge(0, 1).unwrap();
813 us.add_edge(0, 2).unwrap();
814 let mut them = Graph::with_vertices(3);
815 them.add_edge(0, 1).unwrap();
816 them.add_edge(1, 2).unwrap();
817 let res = local_scan_0_them(&us, &them, None, DegreeMode::All).unwrap();
818 assert!(close(res[0], 1.0));
819 assert!(close(res[1], 1.0));
820 assert!(close(res[2], 0.0));
821 }
822
823 #[test]
824 fn scan0t_identical() {
825 let mut g = Graph::with_vertices(3);
826 g.add_edge(0, 1).unwrap();
827 g.add_edge(1, 2).unwrap();
828 g.add_edge(2, 0).unwrap();
829 let res = local_scan_0_them(&g, &g, None, DegreeMode::All).unwrap();
830 let self_scan = local_scan_0(&g, None, DegreeMode::All).unwrap();
831 for (a, b) in res.iter().zip(self_scan.iter()) {
832 assert!(close(*a, *b));
833 }
834 }
835
836 #[test]
837 fn scan0t_vcount_mismatch() {
838 let us = Graph::with_vertices(3);
839 let them = Graph::with_vertices(4);
840 assert!(local_scan_0_them(&us, &them, None, DegreeMode::All).is_err());
841 }
842
843 #[test]
846 fn scan1t_basic() {
847 let mut us = Graph::with_vertices(3);
848 us.add_edge(0, 1).unwrap();
849 us.add_edge(1, 2).unwrap();
850 let mut them = Graph::with_vertices(3);
851 them.add_edge(0, 1).unwrap();
852 them.add_edge(0, 2).unwrap();
853 them.add_edge(1, 2).unwrap();
854 let res = local_scan_1_ecount_them(&us, &them, None, DegreeMode::All).unwrap();
855 assert!(close(res[1], 3.0));
857 }
858
859 #[test]
860 fn scan1t_vcount_mismatch() {
861 let us = Graph::with_vertices(3);
862 let them = Graph::with_vertices(4);
863 assert!(local_scan_1_ecount_them(&us, &them, None, DegreeMode::All).is_err());
864 }
865
866 #[test]
869 fn subset_triangle() {
870 let mut g = Graph::with_vertices(4);
871 g.add_edge(0, 1).unwrap();
872 g.add_edge(1, 2).unwrap();
873 g.add_edge(2, 0).unwrap();
874 g.add_edge(2, 3).unwrap();
875 let subsets = vec![vec![0, 1, 2], vec![2, 3], vec![0, 3]];
876 let res = local_scan_subset_ecount(&g, &subsets, None).unwrap();
877 assert!(close(res[0], 3.0));
878 assert!(close(res[1], 1.0));
879 assert!(close(res[2], 0.0));
880 }
881
882 #[test]
883 fn subset_weighted() {
884 let mut g = Graph::with_vertices(3);
885 g.add_edge(0, 1).unwrap();
886 g.add_edge(1, 2).unwrap();
887 let w = vec![3.0, 7.0];
888 let subsets = vec![vec![0, 1, 2]];
889 let res = local_scan_subset_ecount(&g, &subsets, Some(&w)).unwrap();
890 assert!(close(res[0], 10.0));
891 }
892
893 #[test]
894 fn subset_empty_subset() {
895 let mut g = Graph::with_vertices(3);
896 g.add_edge(0, 1).unwrap();
897 let subsets: Vec<Vec<VertexId>> = vec![vec![]];
898 let res = local_scan_subset_ecount(&g, &subsets, None).unwrap();
899 assert!(close(res[0], 0.0));
900 }
901
902 #[test]
903 fn subset_invalid_vertex() {
904 let g = Graph::with_vertices(3);
905 let subsets = vec![vec![0, 5]];
906 assert!(local_scan_subset_ecount(&g, &subsets, None).is_err());
907 }
908
909 #[test]
910 fn subset_directed() {
911 let mut g = Graph::new(3, true).unwrap();
912 g.add_edge(0, 1).unwrap();
913 g.add_edge(1, 2).unwrap();
914 g.add_edge(2, 0).unwrap();
915 let subsets = vec![vec![0, 1, 2]];
916 let res = local_scan_subset_ecount(&g, &subsets, None).unwrap();
917 assert!(close(res[0], 3.0));
918 }
919}