1#![allow(
35 clippy::cast_possible_truncation,
36 clippy::cast_possible_wrap,
37 clippy::cast_sign_loss
38)]
39
40use super::vf2::{Flow, adjacency, contains_sorted, in_degree, out_degree, perform_pre_checks};
41use crate::core::{Graph, IgraphError, IgraphResult};
42
43#[derive(Debug, Clone)]
45pub struct Vf2Subisomorphism {
46 pub iso: bool,
48 pub map21: Vec<u32>,
52 pub map12: Vec<Option<u32>>,
56}
57
58#[allow(clippy::too_many_arguments)]
67#[allow(clippy::too_many_lines)]
68fn subiso_engine(
69 graph1: &Graph,
70 graph2: &Graph,
71 mut vertex_color1: Option<&[u32]>,
72 mut vertex_color2: Option<&[u32]>,
73 mut edge_color1: Option<&[u32]>,
74 mut edge_color2: Option<&[u32]>,
75 mut isohandler: impl FnMut(&[i64], &[i64]) -> Flow,
76) -> IgraphResult<()> {
77 perform_pre_checks(graph1, graph2)?;
78
79 let no_of_nodes1 = i64::from(graph1.vcount());
80 let no_of_nodes2 = i64::from(graph2.vcount());
81 let no_of_edges1 = graph1.ecount() as i64;
82 let no_of_edges2 = graph2.ecount() as i64;
83
84 if no_of_nodes1 < no_of_nodes2 || no_of_edges1 < no_of_edges2 {
86 return Ok(());
87 }
88
89 if vertex_color1.is_some() != vertex_color2.is_some() {
91 vertex_color1 = None;
92 vertex_color2 = None;
93 }
94 if edge_color1.is_some() != edge_color2.is_some() {
95 edge_color1 = None;
96 edge_color2 = None;
97 }
98
99 if let (Some(c1), Some(c2)) = (vertex_color1, vertex_color2) {
100 if c1.len() as i64 != no_of_nodes1 || c2.len() as i64 != no_of_nodes2 {
101 return Err(IgraphError::InvalidArgument(
102 "invalid vertex color vector length".into(),
103 ));
104 }
105 }
106 if let (Some(c1), Some(c2)) = (edge_color1, edge_color2) {
107 if c1.len() as i64 != no_of_edges1 || c2.len() as i64 != no_of_edges2 {
108 return Err(IgraphError::InvalidArgument(
109 "invalid edge color vector length".into(),
110 ));
111 }
112 }
113
114 let n1 = no_of_nodes1 as usize;
115 let n2 = no_of_nodes2 as usize;
116
117 let inneis_1 = adjacency(graph1, true)?;
118 let outneis_1 = adjacency(graph1, false)?;
119 let inneis_2 = adjacency(graph2, true)?;
120 let outneis_2 = adjacency(graph2, false)?;
121
122 let mut indeg1 = vec![0i64; n1];
123 let mut outdeg1 = vec![0i64; n1];
124 for v in 0..no_of_nodes1 {
125 let vu = v as usize;
126 indeg1[vu] = in_degree(graph1, v as u32)?;
127 outdeg1[vu] = out_degree(graph1, v as u32)?;
128 }
129 let mut indeg2 = vec![0i64; n2];
130 let mut outdeg2 = vec![0i64; n2];
131 for v in 0..no_of_nodes2 {
132 let vu = v as usize;
133 indeg2[vu] = in_degree(graph2, v as u32)?;
134 outdeg2[vu] = out_degree(graph2, v as u32)?;
135 }
136
137 let mut core_1 = vec![-1i64; n1];
139 let mut core_2 = vec![-1i64; n2];
141 let mut in_1 = vec![0i64; n1];
143 let mut in_2 = vec![0i64; n2];
144 let mut out_1 = vec![0i64; n1];
145 let mut out_2 = vec![0i64; n2];
146 let mut in_1_size = 0i64;
147 let mut in_2_size = 0i64;
148 let mut out_1_size = 0i64;
149 let mut out_2_size = 0i64;
150
151 let mut path: Vec<i64> = Vec::with_capacity(2 * n2);
153 let mut matched_nodes = 0i64;
154 let mut depth = 0i64;
155 let mut last1 = -1i64;
156 let mut last2 = -1i64;
157
158 while depth >= 0 {
159 let mut cand1 = -1i64;
160 let mut cand2 = -1i64;
161
162 if in_1_size < in_2_size || out_1_size < out_2_size {
165 } else if out_1_size > 0 && out_2_size > 0 {
167 if last2 >= 0 {
168 cand2 = last2;
169 } else {
170 let mut i = 0i64;
171 while cand2 < 0 && i < no_of_nodes2 {
172 if out_2[i as usize] > 0 && core_2[i as usize] < 0 {
173 cand2 = i;
174 }
175 i += 1;
176 }
177 }
178 let mut i = last1 + 1;
179 while cand1 < 0 && i < no_of_nodes1 {
180 if out_1[i as usize] > 0 && core_1[i as usize] < 0 {
181 cand1 = i;
182 }
183 i += 1;
184 }
185 } else if in_1_size > 0 && in_2_size > 0 {
186 if last2 >= 0 {
187 cand2 = last2;
188 } else {
189 let mut i = 0i64;
190 while cand2 < 0 && i < no_of_nodes2 {
191 if in_2[i as usize] > 0 && core_2[i as usize] < 0 {
192 cand2 = i;
193 }
194 i += 1;
195 }
196 }
197 let mut i = last1 + 1;
198 while cand1 < 0 && i < no_of_nodes1 {
199 if in_1[i as usize] > 0 && core_1[i as usize] < 0 {
200 cand1 = i;
201 }
202 i += 1;
203 }
204 } else {
205 if last2 >= 0 {
206 cand2 = last2;
207 } else {
208 let mut i = 0i64;
209 while cand2 < 0 && i < no_of_nodes2 {
210 if core_2[i as usize] < 0 {
211 cand2 = i;
212 }
213 i += 1;
214 }
215 }
216 let mut i = last1 + 1;
217 while cand1 < 0 && i < no_of_nodes1 {
218 if core_1[i as usize] < 0 {
219 cand1 = i;
220 }
221 i += 1;
222 }
223 }
224
225 if cand1 < 0 || cand2 < 0 {
226 if depth >= 1 {
228 last2 = path
229 .pop()
230 .ok_or(IgraphError::Internal("subiso: empty path"))?;
231 last1 = path
232 .pop()
233 .ok_or(IgraphError::Internal("subiso: empty path"))?;
234 let l1 = last1 as usize;
235 let l2 = last2 as usize;
236 matched_nodes -= 1;
237 core_1[l1] = -1;
238 core_2[l2] = -1;
239
240 if in_1[l1] != 0 {
241 in_1_size += 1;
242 }
243 if out_1[l1] != 0 {
244 out_1_size += 1;
245 }
246 if in_2[l2] != 0 {
247 in_2_size += 1;
248 }
249 if out_2[l2] != 0 {
250 out_2_size += 1;
251 }
252
253 for &node in &inneis_1[l1] {
254 if in_1[node as usize] == depth {
255 in_1[node as usize] = 0;
256 in_1_size -= 1;
257 }
258 }
259 for &node in &outneis_1[l1] {
260 if out_1[node as usize] == depth {
261 out_1[node as usize] = 0;
262 out_1_size -= 1;
263 }
264 }
265 for &node in &inneis_2[l2] {
266 if in_2[node as usize] == depth {
267 in_2[node as usize] = 0;
268 in_2_size -= 1;
269 }
270 }
271 for &node in &outneis_2[l2] {
272 if out_2[node as usize] == depth {
273 out_2[node as usize] = 0;
274 out_2_size -= 1;
275 }
276 }
277 }
278 depth -= 1;
279 } else {
280 let c1 = cand1 as usize;
282 let c2 = cand2 as usize;
283 let mut xin1 = 0i64;
284 let mut xin2 = 0i64;
285 let mut xout1 = 0i64;
286 let mut xout2 = 0i64;
287 let mut end = false;
288
289 if indeg1[c1] < indeg2[c2] || outdeg1[c1] < outdeg2[c2] {
291 end = true;
292 }
293 if let (Some(vc1), Some(vc2)) = (vertex_color1, vertex_color2) {
294 if vc1[c1] != vc2[c2] {
295 end = true;
296 }
297 }
298
299 for &node in &inneis_1[c1] {
302 if end {
303 break;
304 }
305 let nu = node as usize;
306 if core_1[nu] < 0 {
307 if in_1[nu] != 0 {
308 xin1 += 1;
309 }
310 if out_1[nu] != 0 {
311 xout1 += 1;
312 }
313 }
314 }
315 for &node in &outneis_1[c1] {
316 if end {
317 break;
318 }
319 let nu = node as usize;
320 if core_1[nu] < 0 {
321 if in_1[nu] != 0 {
322 xin1 += 1;
323 }
324 if out_1[nu] != 0 {
325 xout1 += 1;
326 }
327 }
328 }
329 for &node in &inneis_2[c2] {
332 if end {
333 break;
334 }
335 let nu = node as usize;
336 if core_2[nu] >= 0 {
337 let node2 = core_2[nu];
338 if !contains_sorted(&inneis_1[c1], node2 as u32) {
339 end = true;
340 } else if edge_color1.is_some() {
341 let eid1 = graph1.get_eid(node2 as u32, cand1 as u32)? as usize;
342 let eid2 = graph2.get_eid(node, cand2 as u32)? as usize;
343 if let (Some(ec1), Some(ec2)) = (edge_color1, edge_color2) {
344 if ec1[eid1] != ec2[eid2] {
345 end = true;
346 }
347 }
348 }
349 } else {
350 if in_2[nu] != 0 {
351 xin2 += 1;
352 }
353 if out_2[nu] != 0 {
354 xout2 += 1;
355 }
356 }
357 }
358 for &node in &outneis_2[c2] {
359 if end {
360 break;
361 }
362 let nu = node as usize;
363 if core_2[nu] >= 0 {
364 let node2 = core_2[nu];
365 if !contains_sorted(&outneis_1[c1], node2 as u32) {
366 end = true;
367 } else if edge_color1.is_some() {
368 let eid1 = graph1.get_eid(cand1 as u32, node2 as u32)? as usize;
369 let eid2 = graph2.get_eid(cand2 as u32, node)? as usize;
370 if let (Some(ec1), Some(ec2)) = (edge_color1, edge_color2) {
371 if ec1[eid1] != ec2[eid2] {
372 end = true;
373 }
374 }
375 }
376 } else {
377 if in_2[nu] != 0 {
378 xin2 += 1;
379 }
380 if out_2[nu] != 0 {
381 xout2 += 1;
382 }
383 }
384 }
385
386 if !end && xin1 >= xin2 && xout1 >= xout2 {
387 depth += 1;
389 path.push(cand1);
390 path.push(cand2);
391 matched_nodes += 1;
392 core_1[c1] = cand2;
393 core_2[c2] = cand1;
394
395 if in_1[c1] != 0 {
396 in_1_size -= 1;
397 }
398 if out_1[c1] != 0 {
399 out_1_size -= 1;
400 }
401 if in_2[c2] != 0 {
402 in_2_size -= 1;
403 }
404 if out_2[c2] != 0 {
405 out_2_size -= 1;
406 }
407
408 for &node in &inneis_1[c1] {
409 let nu = node as usize;
410 if in_1[nu] == 0 && core_1[nu] < 0 {
411 in_1[nu] = depth;
412 in_1_size += 1;
413 }
414 }
415 for &node in &outneis_1[c1] {
416 let nu = node as usize;
417 if out_1[nu] == 0 && core_1[nu] < 0 {
418 out_1[nu] = depth;
419 out_1_size += 1;
420 }
421 }
422 for &node in &inneis_2[c2] {
423 let nu = node as usize;
424 if in_2[nu] == 0 && core_2[nu] < 0 {
425 in_2[nu] = depth;
426 in_2_size += 1;
427 }
428 }
429 for &node in &outneis_2[c2] {
430 let nu = node as usize;
431 if out_2[nu] == 0 && core_2[nu] < 0 {
432 out_2[nu] = depth;
433 out_2_size += 1;
434 }
435 }
436
437 last1 = -1;
438 last2 = -1;
439 } else {
440 last1 = cand1;
441 last2 = cand2;
442 }
443 }
444
445 if matched_nodes == no_of_nodes2 {
446 if let Flow::Stop = isohandler(&core_1, &core_2) {
447 break;
448 }
449 }
450 }
451
452 Ok(())
453}
454
455#[allow(clippy::too_many_arguments)]
494pub fn subisomorphic_vf2(
495 graph1: &Graph,
496 graph2: &Graph,
497 vertex_color1: Option<&[u32]>,
498 vertex_color2: Option<&[u32]>,
499 edge_color1: Option<&[u32]>,
500 edge_color2: Option<&[u32]>,
501) -> IgraphResult<Vf2Subisomorphism> {
502 let mut map21: Vec<u32> = Vec::new();
503 let mut map12: Vec<Option<u32>> = Vec::new();
504 let mut iso = false;
505
506 subiso_engine(
507 graph1,
508 graph2,
509 vertex_color1,
510 vertex_color2,
511 edge_color1,
512 edge_color2,
513 |core_1, core_2| {
514 iso = true;
515 map21 = core_2.iter().map(|&x| x as u32).collect();
516 map12 = core_1
517 .iter()
518 .map(|&x| if x < 0 { None } else { Some(x as u32) })
519 .collect();
520 Flow::Stop
521 },
522 )?;
523
524 if !iso {
525 map21.clear();
526 map12.clear();
527 }
528 Ok(Vf2Subisomorphism { iso, map21, map12 })
529}
530
531#[allow(clippy::too_many_arguments)]
556pub fn count_subisomorphisms_vf2(
557 graph1: &Graph,
558 graph2: &Graph,
559 vertex_color1: Option<&[u32]>,
560 vertex_color2: Option<&[u32]>,
561 edge_color1: Option<&[u32]>,
562 edge_color2: Option<&[u32]>,
563) -> IgraphResult<u64> {
564 let mut count = 0u64;
565 subiso_engine(
566 graph1,
567 graph2,
568 vertex_color1,
569 vertex_color2,
570 edge_color1,
571 edge_color2,
572 |_core_1, _core_2| {
573 count += 1;
574 Flow::Continue
575 },
576 )?;
577 Ok(count)
578}
579
580#[allow(clippy::too_many_arguments)]
609pub fn get_subisomorphisms_vf2(
610 graph1: &Graph,
611 graph2: &Graph,
612 vertex_color1: Option<&[u32]>,
613 vertex_color2: Option<&[u32]>,
614 edge_color1: Option<&[u32]>,
615 edge_color2: Option<&[u32]>,
616) -> IgraphResult<Vec<Vec<u32>>> {
617 let mut maps: Vec<Vec<u32>> = Vec::new();
618 subiso_engine(
619 graph1,
620 graph2,
621 vertex_color1,
622 vertex_color2,
623 edge_color1,
624 edge_color2,
625 |_core_1, core_2| {
626 maps.push(core_2.iter().map(|&x| x as u32).collect());
627 Flow::Continue
628 },
629 )?;
630 Ok(maps)
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 fn ring(n: u32, directed: bool) -> Graph {
639 let mut g = Graph::new(n, directed).expect("graph");
640 for i in 0..n {
641 g.add_edge(i, (i + 1) % n).expect("edge");
642 }
643 g
644 }
645
646 fn graph_from(n: u32, directed: bool, edges: &[(u32, u32)]) -> Graph {
648 let mut g = Graph::new(n, directed).expect("graph");
649 for &(u, v) in edges {
650 g.add_edge(u, v).expect("edge");
651 }
652 g
653 }
654
655 fn complete(n: u32) -> Graph {
657 let mut g = Graph::new(n, false).expect("graph");
658 for i in 0..n {
659 for j in (i + 1)..n {
660 g.add_edge(i, j).expect("edge");
661 }
662 }
663 g
664 }
665
666 fn triangle() -> Graph {
667 graph_from(3, false, &[(0, 1), (1, 2), (2, 0)])
668 }
669
670 #[test]
671 fn triangle_embeds_into_k4() {
672 let k4 = complete(4);
673 let tri = triangle();
674 let r = subisomorphic_vf2(&k4, &tri, None, None, None, None).expect("ok");
675 assert!(r.iso);
676 assert_eq!(r.map21.len(), 3);
677 for e in 0..tri.ecount() {
680 let eid = u32::try_from(e).expect("fits");
681 let (u, v) = tri.edge(eid).expect("edge");
682 let mu = r.map21[u as usize];
683 let mv = r.map21[v as usize];
684 assert!(k4.find_eid(mu, mv).expect("lookup").is_some());
685 }
686 for (j, &t) in r.map21.iter().enumerate() {
688 assert_eq!(r.map12[t as usize], Some(j as u32));
689 }
690 }
691
692 #[test]
693 fn triangle_count_into_k4_and_k5() {
694 assert_eq!(
696 count_subisomorphisms_vf2(&complete(4), &triangle(), None, None, None, None)
697 .expect("ok"),
698 24
699 );
700 assert_eq!(
702 count_subisomorphisms_vf2(&complete(5), &triangle(), None, None, None, None)
703 .expect("ok"),
704 60
705 );
706 }
707
708 #[test]
709 fn edge_count_into_triangle_and_path() {
710 let edge = graph_from(2, false, &[(0, 1)]);
711 assert_eq!(
713 count_subisomorphisms_vf2(&triangle(), &edge, None, None, None, None).expect("ok"),
714 6
715 );
716 let p4 = graph_from(4, false, &[(0, 1), (1, 2), (2, 3)]);
718 assert_eq!(
719 count_subisomorphisms_vf2(&p4, &edge, None, None, None, None).expect("ok"),
720 6
721 );
722 }
723
724 #[test]
725 fn path_embeds_into_cycle() {
726 let c4 = ring(4, false);
728 let p3 = graph_from(3, false, &[(0, 1), (1, 2)]);
729 let maps = get_subisomorphisms_vf2(&c4, &p3, None, None, None, None).expect("ok");
730 assert_eq!(maps.len(), 8);
731 let c = count_subisomorphisms_vf2(&c4, &p3, None, None, None, None).expect("ok");
732 assert_eq!(c, 8);
733 }
734
735 #[test]
736 fn triangle_not_in_cycle() {
737 let c4 = ring(4, false);
739 let r = subisomorphic_vf2(&c4, &triangle(), None, None, None, None).expect("ok");
740 assert!(!r.iso);
741 assert!(r.map21.is_empty());
742 assert!(r.map12.is_empty());
743 assert_eq!(
744 count_subisomorphisms_vf2(&c4, &triangle(), None, None, None, None).expect("ok"),
745 0
746 );
747 }
748
749 #[test]
750 fn pattern_larger_than_target_has_no_embedding() {
751 let tri = triangle();
752 let edge = graph_from(2, false, &[(0, 1)]);
753 let r = subisomorphic_vf2(&edge, &tri, None, None, None, None).expect("ok");
755 assert!(!r.iso);
756 assert_eq!(
757 count_subisomorphisms_vf2(&edge, &tri, None, None, None, None).expect("ok"),
758 0
759 );
760 let path = graph_from(3, false, &[(0, 1), (1, 2)]);
762 assert_eq!(
763 count_subisomorphisms_vf2(&path, &triangle(), None, None, None, None).expect("ok"),
764 0
765 );
766 }
767
768 #[test]
769 fn self_subiso_equals_automorphism_count() {
770 assert_eq!(
773 count_subisomorphisms_vf2(&ring(6, false), &ring(6, false), None, None, None, None)
774 .expect("ok"),
775 12
776 );
777 assert_eq!(
778 count_subisomorphisms_vf2(&ring(4, false), &ring(4, false), None, None, None, None)
779 .expect("ok"),
780 8
781 );
782 assert_eq!(
783 count_subisomorphisms_vf2(&ring(4, true), &ring(4, true), None, None, None, None)
784 .expect("ok"),
785 4
786 );
787 }
788
789 #[test]
790 fn empty_pattern_embeds_once() {
791 let empty = Graph::new(0, false).expect("graph");
793 let tri = triangle();
794 let r = subisomorphic_vf2(&tri, &empty, None, None, None, None).expect("ok");
795 assert!(r.iso);
796 assert!(r.map21.is_empty());
797 assert_eq!(
798 count_subisomorphisms_vf2(&tri, &empty, None, None, None, None).expect("ok"),
799 1
800 );
801 }
802
803 #[test]
804 fn directed_pattern_respects_orientation() {
805 let dtri = ring(3, true);
808 let dpath = graph_from(3, true, &[(0, 1), (1, 2)]);
809 assert_eq!(
810 count_subisomorphisms_vf2(&dtri, &dpath, None, None, None, None).expect("ok"),
811 3
812 );
813 }
814
815 #[test]
816 fn vertex_colors_constrain_embedding() {
817 let tri = triangle();
820 let tcolors = [0u32, 1, 2];
821 let edge = graph_from(2, false, &[(0, 1)]);
822 let pcolors = [0u32, 1];
823 let c = count_subisomorphisms_vf2(&tri, &edge, Some(&tcolors), Some(&pcolors), None, None)
825 .expect("ok");
826 assert_eq!(c, 1);
827 }
828
829 #[test]
830 fn only_one_side_colored_ignores_colors() {
831 let tri = triangle();
832 let tcolors = [0u32, 1, 2];
833 let edge = graph_from(2, false, &[(0, 1)]);
834 let c =
836 count_subisomorphisms_vf2(&tri, &edge, Some(&tcolors), None, None, None).expect("ok");
837 assert_eq!(c, 6);
838 }
839
840 #[test]
841 fn edge_colors_constrain_embedding() {
842 let tri = triangle();
845 let mut tec = vec![0u32; tri.ecount()];
846 for (e, slot) in tec.iter_mut().enumerate() {
847 let eid = u32::try_from(e).expect("fits");
848 let (a, b) = tri.edge(eid).expect("edge");
849 if (a, b) == (1, 2) || (a, b) == (2, 1) {
850 *slot = 1;
851 }
852 }
853 let edge = graph_from(2, false, &[(0, 1)]);
854 let pec = [1u32];
855 let c =
857 count_subisomorphisms_vf2(&tri, &edge, None, None, Some(&tec), Some(&pec)).expect("ok");
858 assert_eq!(c, 2);
859 }
860
861 #[test]
862 fn get_and_count_agree() {
863 let k4 = complete(4);
864 let tri = triangle();
865 let maps = get_subisomorphisms_vf2(&k4, &tri, None, None, None, None).expect("ok");
866 let c = count_subisomorphisms_vf2(&k4, &tri, None, None, None, None).expect("ok");
867 assert_eq!(maps.len() as u64, c);
868 for m in &maps {
870 for e in 0..tri.ecount() {
871 let eid = u32::try_from(e).expect("fits");
872 let (u, v) = tri.edge(eid).expect("edge");
873 assert!(
874 k4.find_eid(m[u as usize], m[v as usize])
875 .expect("lookup")
876 .is_some()
877 );
878 }
879 }
880 }
881
882 #[test]
883 fn self_loops_are_rejected() {
884 let g = graph_from(2, false, &[(0, 0), (0, 1)]);
885 let h = graph_from(2, false, &[(0, 1)]);
886 assert!(subisomorphic_vf2(&g, &h, None, None, None, None).is_err());
887 assert!(subisomorphic_vf2(&h, &g, None, None, None, None).is_err());
888 }
889
890 #[test]
891 fn directedness_mismatch_is_rejected() {
892 let u = ring(3, false);
893 let d = ring(3, true);
894 assert!(subisomorphic_vf2(&u, &d, None, None, None, None).is_err());
895 }
896
897 #[test]
898 fn wrong_color_vector_length_errors() {
899 let tri = triangle();
900 let edge = graph_from(2, false, &[(0, 1)]);
901 let short = [0u32];
902 assert!(
904 subisomorphic_vf2(&tri, &edge, Some(&short), Some(&[0u32, 0]), None, None).is_err()
905 );
906 }
907}
908
909#[cfg(all(test, feature = "proptest-harness"))]
910mod proptests {
911 use super::*;
912 use proptest::prelude::*;
913 use std::collections::HashSet;
914
915 fn arb_simple_graph(max_v: u32) -> impl Strategy<Value = (u32, Vec<(u32, u32)>, bool)> {
918 (1..=max_v, any::<bool>()).prop_flat_map(|(n, directed)| {
919 proptest::collection::vec((0..n, 0..n), 0..=12).prop_map(move |raw| {
920 let mut seen: HashSet<(u32, u32)> = HashSet::new();
921 let mut edges = Vec::new();
922 for (u, v) in raw {
923 if u == v {
924 continue;
925 }
926 let key = if directed {
927 (u, v)
928 } else {
929 (u.min(v), u.max(v))
930 };
931 if seen.insert(key) {
932 edges.push((u, v));
933 }
934 }
935 (n, edges, directed)
936 })
937 })
938 }
939
940 fn relabel(edges: &[(u32, u32)], perm: &[u32]) -> Vec<(u32, u32)> {
941 edges
942 .iter()
943 .map(|&(u, v)| (perm[u as usize], perm[v as usize]))
944 .collect()
945 }
946
947 fn build(n: u32, directed: bool, edges: &[(u32, u32)]) -> Graph {
948 let mut g = Graph::new(n, directed).expect("graph");
949 for &(u, v) in edges {
950 g.add_edge(u, v).expect("edge");
951 }
952 g
953 }
954
955 proptest! {
956 #[test]
960 fn self_embeds((n, edges, directed) in arb_simple_graph(6)) {
961 let g = build(n, directed, &edges);
962 let r = subisomorphic_vf2(&g, &g, None, None, None, None).expect("ok");
963 prop_assert!(r.iso);
964 prop_assert_eq!(r.map21.len(), n as usize);
965 let c = count_subisomorphisms_vf2(&g, &g, None, None, None, None).expect("ok");
966 prop_assert!(c >= 1, "identity embedding always exists");
967 }
968
969 #[test]
973 fn get_count_consistency_and_adjacency(
974 (n, edges, directed) in arb_simple_graph(6),
975 ) {
976 let g = build(n, directed, &edges);
977 let maps = get_subisomorphisms_vf2(&g, &g, None, None, None, None).expect("ok");
978 let c = count_subisomorphisms_vf2(&g, &g, None, None, None, None).expect("ok");
979 prop_assert_eq!(maps.len() as u64, c);
980 for m in &maps {
981 prop_assert_eq!(m.len(), n as usize);
982 for e in 0..g.ecount() {
983 let eid = u32::try_from(e).expect("fits");
984 let (u, v) = g.edge(eid).expect("edge");
985 prop_assert!(
986 g.find_eid(m[u as usize], m[v as usize]).expect("lookup").is_some()
987 );
988 }
989 }
990 }
991
992 #[test]
995 fn count_invariant_under_target_relabelling(
996 (n, edges, directed) in arb_simple_graph(6),
997 seed in any::<u64>(),
998 ) {
999 let mut perm: Vec<u32> = (0..n).collect();
1000 let mut state = seed;
1001 for i in (1..n as usize).rev() {
1002 state = state
1003 .wrapping_mul(6_364_136_223_846_793_005)
1004 .wrapping_add(1_442_695_040_888_963_407);
1005 let j = (state >> 33) as usize % (i + 1);
1006 perm.swap(i, j);
1007 }
1008 let g = build(n, directed, &edges);
1009 let h = build(n, directed, &relabel(&edges, &perm));
1010 let pattern = build(2, directed, &[(0, 1)]);
1013 let cg = count_subisomorphisms_vf2(&g, &pattern, None, None, None, None).expect("ok");
1014 let ch = count_subisomorphisms_vf2(&h, &pattern, None, None, None, None).expect("ok");
1015 prop_assert_eq!(cg, ch);
1016 }
1017
1018 #[test]
1020 fn oversized_pattern_never_embeds(
1021 (n, edges, directed) in arb_simple_graph(5),
1022 ) {
1023 let target = build(n, directed, &edges);
1024 let bigger = build(n + 1, directed, &edges);
1027 let c = count_subisomorphisms_vf2(&target, &bigger, None, None, None, None)
1028 .expect("ok");
1029 prop_assert_eq!(c, 0);
1030 }
1031 }
1032}