1use crate::base::{EdgeWeight, Graph, IndexType, Node};
7use std::collections::{HashMap, HashSet};
8use std::hash::Hash;
9
10#[allow(dead_code)]
14pub fn find_subgraph_matches<N1, N2, E, Ix>(
15 pattern: &Graph<N1, E, Ix>,
16 target: &Graph<N2, E, Ix>,
17) -> Vec<HashMap<N1, N2>>
18where
19 N1: Node + Clone + Hash + Eq + std::fmt::Debug,
20 N2: Node + Clone + Hash + Eq + std::fmt::Debug,
21 E: EdgeWeight,
22 Ix: IndexType,
23{
24 let pattern_nodes: Vec<N1> = pattern.nodes().into_iter().cloned().collect();
25 let target_nodes: Vec<N2> = target.nodes().into_iter().cloned().collect();
26
27 if pattern_nodes.is_empty() || pattern_nodes.len() > target_nodes.len() {
28 return vec![];
29 }
30
31 let mut matches = Vec::new();
32 let mut current_mapping = HashMap::new();
33
34 for start_node in &target_nodes {
36 find_matches_recursive(
37 &pattern_nodes,
38 pattern,
39 target,
40 &mut current_mapping,
41 0,
42 start_node,
43 &mut matches,
44 );
45 }
46
47 matches
48}
49
50#[allow(dead_code)]
51fn find_matches_recursive<N1, N2, E, Ix>(
52 pattern_nodes: &[N1],
53 pattern: &Graph<N1, E, Ix>,
54 target: &Graph<N2, E, Ix>,
55 current_mapping: &mut HashMap<N1, N2>,
56 pattern_idx: usize,
57 target_node: &N2,
58 matches: &mut Vec<HashMap<N1, N2>>,
59) where
60 N1: Node + Clone + Hash + Eq + std::fmt::Debug,
61 N2: Node + Clone + Hash + Eq + std::fmt::Debug,
62 E: EdgeWeight,
63 Ix: IndexType,
64{
65 if pattern_idx >= pattern_nodes.len() {
66 matches.push(current_mapping.clone());
68 return;
69 }
70
71 let pattern_node = &pattern_nodes[pattern_idx];
72
73 if current_mapping.values().any(|n| n == target_node) {
75 return;
76 }
77
78 current_mapping.insert(pattern_node.clone(), target_node.clone());
80
81 if is_mapping_consistent(pattern, target, current_mapping) {
83 if pattern_idx + 1 < pattern_nodes.len() {
84 if let Ok(target_neighbors) = target.neighbors(target_node) {
86 for next_target in target_neighbors {
87 find_matches_recursive(
88 pattern_nodes,
89 pattern,
90 target,
91 current_mapping,
92 pattern_idx + 1,
93 &next_target,
94 matches,
95 );
96 }
97 }
98
99 for next_target in &target.nodes().into_iter().cloned().collect::<Vec<_>>() {
101 if !current_mapping.values().any(|n| n == next_target) {
102 find_matches_recursive(
103 pattern_nodes,
104 pattern,
105 target,
106 current_mapping,
107 pattern_idx + 1,
108 next_target,
109 matches,
110 );
111 }
112 }
113 } else {
114 find_matches_recursive(
116 pattern_nodes,
117 pattern,
118 target,
119 current_mapping,
120 pattern_idx + 1,
121 target_node,
122 matches,
123 );
124 }
125 }
126
127 current_mapping.remove(pattern_node);
129}
130
131#[allow(dead_code)]
132fn is_mapping_consistent<N1, N2, E, Ix>(
133 pattern: &Graph<N1, E, Ix>,
134 target: &Graph<N2, E, Ix>,
135 mapping: &HashMap<N1, N2>,
136) -> bool
137where
138 N1: Node + Hash + Eq + std::fmt::Debug,
139 N2: Node + Hash + Eq + std::fmt::Debug,
140 E: EdgeWeight,
141 Ix: IndexType,
142{
143 for (n1, n2) in mapping {
145 for (m1, m2) in mapping {
146 if n1 != m1 {
147 let pattern_has_edge = pattern.has_edge(n1, m1);
148 let target_has_edge = target.has_edge(n2, m2);
149
150 if pattern_has_edge && !target_has_edge {
151 return false;
152 }
153 }
154 }
155 }
156
157 true
158}
159
160#[allow(dead_code)]
172pub fn are_graphs_isomorphic<N1, N2, E, Ix>(
173 graph1: &Graph<N1, E, Ix>,
174 graph2: &Graph<N2, E, Ix>,
175) -> bool
176where
177 N1: Node + Clone + Hash + Eq + std::fmt::Debug,
178 N2: Node + Clone + Hash + Eq + std::fmt::Debug,
179 E: EdgeWeight,
180 Ix: IndexType,
181{
182 if graph1.node_count() != graph2.node_count() || graph1.edge_count() != graph2.edge_count() {
184 return false;
185 }
186
187 if !have_same_degree_sequence(graph1, graph2) {
189 return false;
190 }
191
192 if graph1.node_count() == 0 {
194 return true;
195 }
196
197 find_isomorphism(graph1, graph2).is_some()
199}
200
201#[allow(dead_code)]
210pub fn find_isomorphism<N1, N2, E, Ix>(
211 graph1: &Graph<N1, E, Ix>,
212 graph2: &Graph<N2, E, Ix>,
213) -> Option<HashMap<N1, N2>>
214where
215 N1: Node + Clone + Hash + Eq + std::fmt::Debug,
216 N2: Node + Clone + Hash + Eq + std::fmt::Debug,
217 E: EdgeWeight,
218 Ix: IndexType,
219{
220 let nodes1: Vec<N1> = graph1.nodes().into_iter().cloned().collect();
221 let nodes2: Vec<N2> = graph2.nodes().into_iter().cloned().collect();
222
223 if nodes1.len() != nodes2.len() {
224 return None;
225 }
226
227 let mut mapping = HashMap::new();
228 if backtrack_isomorphism(&nodes1, &nodes2, graph1, graph2, &mut mapping, 0) {
229 Some(mapping)
230 } else {
231 None
232 }
233}
234
235#[allow(dead_code)]
237fn have_same_degree_sequence<N1, N2, E, Ix>(
238 graph1: &Graph<N1, E, Ix>,
239 graph2: &Graph<N2, E, Ix>,
240) -> bool
241where
242 N1: Node + std::fmt::Debug,
243 N2: Node + std::fmt::Debug,
244 E: EdgeWeight,
245 Ix: IndexType,
246{
247 let mut degrees1: Vec<usize> = graph1
248 .nodes()
249 .iter()
250 .map(|node| {
251 graph1
252 .neighbors(node)
253 .map_or(0, |neighbors| neighbors.len())
254 })
255 .collect();
256
257 let mut degrees2: Vec<usize> = graph2
258 .nodes()
259 .iter()
260 .map(|node| {
261 graph2
262 .neighbors(node)
263 .map_or(0, |neighbors| neighbors.len())
264 })
265 .collect();
266
267 degrees1.sort_unstable();
268 degrees2.sort_unstable();
269
270 degrees1 == degrees2
271}
272
273#[allow(dead_code)]
275fn backtrack_isomorphism<N1, N2, E, Ix>(
276 nodes1: &[N1],
277 nodes2: &[N2],
278 graph1: &Graph<N1, E, Ix>,
279 graph2: &Graph<N2, E, Ix>,
280 mapping: &mut HashMap<N1, N2>,
281 depth: usize,
282) -> bool
283where
284 N1: Node + Clone + Hash + Eq + std::fmt::Debug,
285 N2: Node + Clone + Hash + Eq + std::fmt::Debug,
286 E: EdgeWeight,
287 Ix: IndexType,
288{
289 if depth == nodes1.len() {
291 return is_valid_isomorphism(graph1, graph2, mapping);
292 }
293
294 let node1 = &nodes1[depth];
295
296 for node2 in nodes2 {
297 if mapping.values().any(|mapped| mapped == node2) {
299 continue;
300 }
301
302 let degree1 = graph1
304 .neighbors(node1)
305 .map_or(0, |neighbors| neighbors.len());
306 let degree2 = graph2
307 .neighbors(node2)
308 .map_or(0, |neighbors| neighbors.len());
309
310 if degree1 != degree2 {
311 continue;
312 }
313
314 mapping.insert(node1.clone(), node2.clone());
316
317 if is_partial_mapping_valid(graph1, graph2, mapping, depth + 1)
319 && backtrack_isomorphism(nodes1, nodes2, graph1, graph2, mapping, depth + 1)
320 {
321 return true;
322 }
323
324 mapping.remove(node1);
326 }
327
328 false
329}
330
331#[allow(dead_code)]
333fn is_partial_mapping_valid<N1, N2, E, Ix>(
334 graph1: &Graph<N1, E, Ix>,
335 graph2: &Graph<N2, E, Ix>,
336 mapping: &HashMap<N1, N2>,
337 _mapped_count: usize,
338) -> bool
339where
340 N1: Node + Hash + Eq + std::fmt::Debug,
341 N2: Node + Hash + Eq + std::fmt::Debug,
342 E: EdgeWeight,
343 Ix: IndexType,
344{
345 for (n1, n2) in mapping {
346 for (m1, m2) in mapping {
347 if n1 != m1 {
348 let edge1_exists = graph1.has_edge(n1, m1);
349 let edge2_exists = graph2.has_edge(n2, m2);
350
351 if edge1_exists != edge2_exists {
352 return false;
353 }
354 }
355 }
356 }
357 true
358}
359
360#[allow(dead_code)]
362fn is_valid_isomorphism<N1, N2, E, Ix>(
363 graph1: &Graph<N1, E, Ix>,
364 graph2: &Graph<N2, E, Ix>,
365 mapping: &HashMap<N1, N2>,
366) -> bool
367where
368 N1: Node + Hash + Eq + std::fmt::Debug,
369 N2: Node + Hash + Eq + std::fmt::Debug,
370 E: EdgeWeight,
371 Ix: IndexType,
372{
373 for (n1, n2) in mapping {
375 for (m1, m2) in mapping {
376 if n1 != m1 {
377 let edge1_exists = graph1.has_edge(n1, m1);
378 let edge2_exists = graph2.has_edge(n2, m2);
379
380 if edge1_exists != edge2_exists {
381 return false;
382 }
383 }
384 }
385 }
386 true
387}
388
389#[derive(Debug, Clone)]
394struct VF2State<N1, N2>
395where
396 N1: Node + Clone + Hash + Eq + std::fmt::Debug,
397 N2: Node + Clone + Hash + Eq + std::fmt::Debug,
398{
399 mapping: HashMap<N1, N2>,
401 reverse_mapping: HashMap<N2, N1>,
403 terminal_1: HashSet<N1>,
405 terminal_2: HashSet<N2>,
407 mapped_1: HashSet<N1>,
409 mapped_2: HashSet<N2>,
411 depth: usize,
413}
414
415impl<N1, N2> VF2State<N1, N2>
416where
417 N1: Node + Clone + Hash + Eq + std::fmt::Debug,
418 N2: Node + Clone + Hash + Eq + std::fmt::Debug,
419{
420 fn new() -> Self {
422 VF2State {
423 mapping: HashMap::new(),
424 reverse_mapping: HashMap::new(),
425 terminal_1: HashSet::new(),
426 terminal_2: HashSet::new(),
427 mapped_1: HashSet::new(),
428 mapped_2: HashSet::new(),
429 depth: 0,
430 }
431 }
432
433 fn add_pair<E, Ix>(
435 &mut self,
436 n1: N1,
437 n2: N2,
438 graph1: &Graph<N1, E, Ix>,
439 graph2: &Graph<N2, E, Ix>,
440 ) where
441 E: EdgeWeight,
442 Ix: IndexType,
443 {
444 self.mapping.insert(n1.clone(), n2.clone());
445 self.reverse_mapping.insert(n2.clone(), n1.clone());
446 self.mapped_1.insert(n1.clone());
447 self.mapped_2.insert(n2.clone());
448 self.depth += 1;
449
450 if let Ok(neighbors1) = graph1.neighbors(&n1) {
452 for neighbor in neighbors1 {
453 if !self.mapped_1.contains(&neighbor) {
454 self.terminal_1.insert(neighbor);
455 }
456 }
457 }
458
459 if let Ok(neighbors2) = graph2.neighbors(&n2) {
460 for neighbor in neighbors2 {
461 if !self.mapped_2.contains(&neighbor) {
462 self.terminal_2.insert(neighbor);
463 }
464 }
465 }
466
467 self.terminal_1.remove(&n1);
469 self.terminal_2.remove(&n2);
470 }
471
472 fn remove_pair<E, Ix>(
474 &mut self,
475 n1: &N1,
476 n2: &N2,
477 graph1: &Graph<N1, E, Ix>,
478 graph2: &Graph<N2, E, Ix>,
479 ) where
480 E: EdgeWeight,
481 Ix: IndexType,
482 {
483 self.mapping.remove(n1);
484 self.reverse_mapping.remove(n2);
485 self.mapped_1.remove(n1);
486 self.mapped_2.remove(n2);
487 self.depth -= 1;
488
489 self.update_terminal_sets_after_removal(n1, n2, graph1, graph2);
491 }
492
493 fn update_terminal_sets_after_removal<E, Ix>(
495 &mut self,
496 _n1: &N1,
497 _n2: &N2,
498 graph1: &Graph<N1, E, Ix>,
499 graph2: &Graph<N2, E, Ix>,
500 ) where
501 E: EdgeWeight,
502 Ix: IndexType,
503 {
504 self.terminal_1.clear();
506 self.terminal_2.clear();
507
508 for mapped_node in &self.mapped_1 {
509 if let Ok(neighbors) = graph1.neighbors(mapped_node) {
510 for neighbor in neighbors {
511 if !self.mapped_1.contains(&neighbor) {
512 self.terminal_1.insert(neighbor);
513 }
514 }
515 }
516 }
517
518 for mapped_node in &self.mapped_2 {
519 if let Ok(neighbors) = graph2.neighbors(mapped_node) {
520 for neighbor in neighbors {
521 if !self.mapped_2.contains(&neighbor) {
522 self.terminal_2.insert(neighbor);
523 }
524 }
525 }
526 }
527 }
528
529 fn get_candidate_pairs<E, Ix>(
531 &self,
532 graph1: &Graph<N1, E, Ix>,
533 graph2: &Graph<N2, E, Ix>,
534 ) -> Vec<(N1, N2)>
535 where
536 E: EdgeWeight,
537 Ix: IndexType,
538 {
539 let mut candidates = Vec::new();
540
541 if !self.terminal_1.is_empty() && !self.terminal_2.is_empty() {
543 for n1 in &self.terminal_1 {
544 for n2 in &self.terminal_2 {
545 candidates.push((n1.clone(), n2.clone()));
546 }
547 }
548 return candidates;
549 }
550
551 if !self.terminal_1.is_empty() {
553 let all_nodes2: Vec<N2> = graph2.nodes().into_iter().cloned().collect();
554 for n1 in &self.terminal_1 {
555 for n2 in all_nodes2.iter() {
556 if !self.mapped_2.contains(n2) {
557 candidates.push((n1.clone(), n2.clone()));
558 }
559 }
560 }
561 return candidates;
562 }
563
564 if !self.terminal_2.is_empty() {
565 let all_nodes1: Vec<N1> = graph1.nodes().into_iter().cloned().collect();
566 for n1 in all_nodes1.iter() {
567 if !self.mapped_1.contains(n1) {
568 for n2 in &self.terminal_2 {
569 candidates.push((n1.clone(), n2.clone()));
570 }
571 }
572 }
573 return candidates;
574 }
575
576 let all_nodes1: Vec<N1> = graph1.nodes().into_iter().cloned().collect();
578 let all_nodes2: Vec<N2> = graph2.nodes().into_iter().cloned().collect();
579
580 for n1 in all_nodes1.iter() {
581 if !self.mapped_1.contains(n1) {
582 for n2 in all_nodes2.iter() {
583 if !self.mapped_2.contains(n2) {
584 candidates.push((n1.clone(), n2.clone()));
585 return candidates;
587 }
588 }
589 }
590 }
591
592 candidates
593 }
594
595 fn is_feasible<E, Ix>(
597 &self,
598 n1: &N1,
599 n2: &N2,
600 graph1: &Graph<N1, E, Ix>,
601 graph2: &Graph<N2, E, Ix>,
602 ) -> bool
603 where
604 E: EdgeWeight,
605 Ix: IndexType,
606 {
607 let degree1 = graph1.neighbors(n1).map_or(0, |neighbors| neighbors.len());
609 let degree2 = graph2.neighbors(n2).map_or(0, |neighbors| neighbors.len());
610 if degree1 != degree2 {
611 return false;
612 }
613
614 for (mapped_n1, mapped_n2) in &self.mapping {
616 let edge1_exists = graph1.has_edge(n1, mapped_n1) || graph1.has_edge(mapped_n1, n1);
617 let edge2_exists = graph2.has_edge(n2, mapped_n2) || graph2.has_edge(mapped_n2, n2);
618
619 if edge1_exists != edge2_exists {
620 return false;
621 }
622 }
623
624 let n1_terminal_neighbors = self.count_terminal_neighbors_1(n1, graph1);
626 let n2_terminal_neighbors = self.count_terminal_neighbors_2(n2, graph2);
627
628 if n1_terminal_neighbors != n2_terminal_neighbors {
629 return false;
630 }
631
632 let n1_unmapped_neighbors = self.count_unmapped_neighbors_1(n1, graph1);
634 let n2_unmapped_neighbors = self.count_unmapped_neighbors_2(n2, graph2);
635
636 if n1_unmapped_neighbors != n2_unmapped_neighbors {
637 return false;
638 }
639
640 true
641 }
642
643 fn count_terminal_neighbors_1<E, Ix>(&self, node: &N1, graph: &Graph<N1, E, Ix>) -> usize
645 where
646 E: EdgeWeight,
647 Ix: IndexType,
648 {
649 if let Ok(neighbors) = graph.neighbors(node) {
650 neighbors
651 .into_iter()
652 .filter(|n| self.terminal_1.contains(n) && !self.mapped_1.contains(n))
653 .count()
654 } else {
655 0
656 }
657 }
658
659 fn count_terminal_neighbors_2<E, Ix>(&self, node: &N2, graph: &Graph<N2, E, Ix>) -> usize
661 where
662 E: EdgeWeight,
663 Ix: IndexType,
664 {
665 if let Ok(neighbors) = graph.neighbors(node) {
666 neighbors
667 .into_iter()
668 .filter(|n| self.terminal_2.contains(n) && !self.mapped_2.contains(n))
669 .count()
670 } else {
671 0
672 }
673 }
674
675 fn count_unmapped_neighbors_1<E, Ix>(&self, node: &N1, graph: &Graph<N1, E, Ix>) -> usize
677 where
678 E: EdgeWeight,
679 Ix: IndexType,
680 {
681 if let Ok(neighbors) = graph.neighbors(node) {
682 neighbors
683 .into_iter()
684 .filter(|n| !self.mapped_1.contains(n) && !self.terminal_1.contains(n))
685 .count()
686 } else {
687 0
688 }
689 }
690
691 fn count_unmapped_neighbors_2<E, Ix>(&self, node: &N2, graph: &Graph<N2, E, Ix>) -> usize
693 where
694 E: EdgeWeight,
695 Ix: IndexType,
696 {
697 if let Ok(neighbors) = graph.neighbors(node) {
698 neighbors
699 .into_iter()
700 .filter(|n| !self.mapped_2.contains(n) && !self.terminal_2.contains(n))
701 .count()
702 } else {
703 0
704 }
705 }
706}
707
708#[allow(dead_code)]
714pub fn find_isomorphism_vf2<N1, N2, E, Ix>(
715 graph1: &Graph<N1, E, Ix>,
716 graph2: &Graph<N2, E, Ix>,
717) -> Option<HashMap<N1, N2>>
718where
719 N1: Node + Clone + Hash + Eq + std::fmt::Debug,
720 N2: Node + Clone + Hash + Eq + std::fmt::Debug,
721 E: EdgeWeight,
722 Ix: IndexType,
723{
724 if graph1.node_count() != graph2.node_count() || graph1.edge_count() != graph2.edge_count() {
726 return None;
727 }
728
729 if !have_same_degree_sequence(graph1, graph2) {
731 return None;
732 }
733
734 if graph1.node_count() == 0 {
736 return Some(HashMap::new());
737 }
738
739 let mut state = VF2State::new();
740 if vf2_match(&mut state, graph1, graph2) {
741 Some(state.mapping)
742 } else {
743 None
744 }
745}
746
747#[allow(dead_code)]
749fn vf2_match<N1, N2, E, Ix>(
750 state: &mut VF2State<N1, N2>,
751 graph1: &Graph<N1, E, Ix>,
752 graph2: &Graph<N2, E, Ix>,
753) -> bool
754where
755 N1: Node + Clone + Hash + Eq + std::fmt::Debug,
756 N2: Node + Clone + Hash + Eq + std::fmt::Debug,
757 E: EdgeWeight,
758 Ix: IndexType,
759{
760 if state.depth == graph1.node_count() {
762 return true;
763 }
764
765 let candidates = state.get_candidate_pairs(graph1, graph2);
767
768 for (n1, n2) in candidates {
769 if state.is_feasible(&n1, &n2, graph1, graph2) {
771 state.add_pair(n1.clone(), n2.clone(), graph1, graph2);
773
774 if vf2_match(state, graph1, graph2) {
776 return true;
777 }
778
779 state.remove_pair(&n1, &n2, graph1, graph2);
781 }
782 }
783
784 false
785}
786
787#[allow(dead_code)]
792pub fn are_graphs_isomorphic_enhanced<N1, N2, E, Ix>(
793 graph1: &Graph<N1, E, Ix>,
794 graph2: &Graph<N2, E, Ix>,
795) -> bool
796where
797 N1: Node + Clone + Hash + Eq + std::fmt::Debug,
798 N2: Node + Clone + Hash + Eq + std::fmt::Debug,
799 E: EdgeWeight,
800 Ix: IndexType,
801{
802 if graph1.node_count() <= 4 {
804 return are_graphs_isomorphic(graph1, graph2);
805 }
806
807 find_isomorphism_vf2(graph1, graph2).is_some()
809}
810
811#[cfg(test)]
812mod tests {
813 use super::*;
814 use crate::error::Result as GraphResult;
815 use crate::generators::create_graph;
816
817 #[test]
818 fn test_find_subgraph_matches() -> GraphResult<()> {
819 let mut pattern = create_graph::<&str, ()>();
821 pattern.add_edge("A", "B", ())?;
822 pattern.add_edge("B", "C", ())?;
823 pattern.add_edge("C", "A", ())?;
824
825 let mut target = create_graph::<&str, ()>();
827 target.add_edge("1", "2", ())?;
829 target.add_edge("2", "3", ())?;
830 target.add_edge("3", "1", ())?;
831 target.add_edge("4", "5", ())?;
833 target.add_edge("5", "6", ())?;
834 target.add_edge("6", "4", ())?;
835 target.add_edge("3", "4", ())?;
837
838 let matches = find_subgraph_matches(&pattern, &target);
839
840 assert!(matches.len() >= 2);
842
843 for match_map in &matches {
845 assert_eq!(match_map.len(), 3);
846 }
847
848 Ok(())
849 }
850
851 #[test]
852 fn test_no_subgraph_match() -> GraphResult<()> {
853 let mut pattern = create_graph::<&str, ()>();
855 pattern.add_edge("A", "B", ())?;
856 pattern.add_edge("B", "C", ())?;
857 pattern.add_edge("C", "A", ())?;
858
859 let mut target = create_graph::<&str, ()>();
861 target.add_edge("1", "2", ())?;
862 target.add_edge("2", "3", ())?;
863 target.add_edge("3", "4", ())?;
864
865 let matches = find_subgraph_matches(&pattern, &target);
866
867 assert_eq!(matches.len(), 0);
869
870 Ok(())
871 }
872
873 #[test]
874 fn test_isomorphic_graphs() -> GraphResult<()> {
875 let mut graph1 = create_graph::<&str, ()>();
877 graph1.add_edge("A", "B", ())?;
878 graph1.add_edge("B", "C", ())?;
879 graph1.add_edge("C", "A", ())?;
880
881 let mut graph2 = create_graph::<i32, ()>();
882 graph2.add_edge(1, 2, ())?;
883 graph2.add_edge(2, 3, ())?;
884 graph2.add_edge(3, 1, ())?;
885
886 assert!(are_graphs_isomorphic(&graph1, &graph2));
887
888 let isomorphism = find_isomorphism(&graph1, &graph2);
889 assert!(isomorphism.is_some());
890
891 Ok(())
892 }
893
894 #[test]
895 fn test_non_isomorphic_graphs() -> GraphResult<()> {
896 let mut triangle = create_graph::<i32, ()>();
898 triangle.add_edge(1, 2, ())?;
899 triangle.add_edge(2, 3, ())?;
900 triangle.add_edge(3, 1, ())?;
901
902 let mut path = create_graph::<i32, ()>();
903 path.add_edge(1, 2, ())?;
904 path.add_edge(2, 3, ())?;
905
906 assert!(!are_graphs_isomorphic(&triangle, &path));
907 assert!(find_isomorphism(&triangle, &path).is_none());
908
909 Ok(())
910 }
911
912 #[test]
913 fn test_different_size_graphs() -> GraphResult<()> {
914 let mut small = create_graph::<i32, ()>();
915 small.add_edge(1, 2, ())?;
916
917 let mut large = create_graph::<i32, ()>();
918 large.add_edge(1, 2, ())?;
919 large.add_edge(2, 3, ())?;
920
921 assert!(!are_graphs_isomorphic(&small, &large));
922 assert!(find_isomorphism(&small, &large).is_none());
923
924 Ok(())
925 }
926
927 #[test]
928 fn test_empty_graphs() {
929 let graph1 = create_graph::<i32, ()>();
930 let graph2 = create_graph::<&str, ()>();
931
932 assert!(are_graphs_isomorphic(&graph1, &graph2));
933 assert!(find_isomorphism(&graph1, &graph2).is_some());
934 }
935
936 #[test]
937 fn test_vf2_algorithm_triangles() -> GraphResult<()> {
938 let mut graph1 = create_graph::<&str, ()>();
940 graph1.add_edge("A", "B", ())?;
941 graph1.add_edge("B", "C", ())?;
942 graph1.add_edge("C", "A", ())?;
943
944 let mut graph2 = create_graph::<i32, ()>();
945 graph2.add_edge(1, 2, ())?;
946 graph2.add_edge(2, 3, ())?;
947 graph2.add_edge(3, 1, ())?;
948
949 let vf2_mapping = find_isomorphism_vf2(&graph1, &graph2);
951 assert!(vf2_mapping.is_some());
952
953 assert!(are_graphs_isomorphic_enhanced(&graph1, &graph2));
955
956 assert!(are_graphs_isomorphic(&graph1, &graph2));
958
959 Ok(())
960 }
961
962 #[test]
963 fn test_vf2_algorithm_larger_graph() -> GraphResult<()> {
964 let mut graph1 = create_graph::<i32, ()>();
966 graph1.add_edge(1, 2, ())?;
968 graph1.add_edge(2, 3, ())?;
969 graph1.add_edge(3, 4, ())?;
970 graph1.add_edge(4, 5, ())?;
971 graph1.add_edge(5, 1, ())?;
972 graph1.add_edge(1, 3, ())?;
974 graph1.add_edge(2, 4, ())?;
975
976 let mut graph2 = create_graph::<char, ()>();
978 graph2.add_edge('A', 'B', ())?;
979 graph2.add_edge('B', 'C', ())?;
980 graph2.add_edge('C', 'D', ())?;
981 graph2.add_edge('D', 'E', ())?;
982 graph2.add_edge('E', 'A', ())?;
983 graph2.add_edge('A', 'C', ())?;
984 graph2.add_edge('B', 'D', ())?;
985
986 assert!(find_isomorphism_vf2(&graph1, &graph2).is_some());
988 assert!(are_graphs_isomorphic_enhanced(&graph1, &graph2));
989
990 Ok(())
991 }
992
993 #[test]
994 fn test_vf2_non_isomorphic_graphs() -> GraphResult<()> {
995 let mut graph1 = create_graph::<i32, ()>(); graph1.add_edge(1, 2, ())?;
998 graph1.add_edge(2, 3, ())?;
999 graph1.add_edge(3, 4, ())?;
1000
1001 let mut graph2 = create_graph::<i32, ()>(); graph2.add_edge(1, 2, ())?;
1003 graph2.add_edge(1, 3, ())?;
1004 graph2.add_edge(1, 4, ())?;
1005
1006 assert!(!are_graphs_isomorphic(&graph1, &graph2));
1008 assert!(!are_graphs_isomorphic_enhanced(&graph1, &graph2));
1009 assert!(find_isomorphism_vf2(&graph1, &graph2).is_none());
1010
1011 Ok(())
1012 }
1013
1014 #[test]
1015 fn test_vf2_single_node_graphs() -> GraphResult<()> {
1016 let mut graph1 = create_graph::<&str, ()>();
1017 graph1.add_node("A");
1018
1019 let mut graph2 = create_graph::<i32, ()>();
1020 graph2.add_node(1);
1021
1022 assert!(find_isomorphism_vf2(&graph1, &graph2).is_some());
1023 assert!(are_graphs_isomorphic_enhanced(&graph1, &graph2));
1024
1025 Ok(())
1026 }
1027
1028 #[test]
1029 fn test_vf2_empty_graphs() {
1030 let graph1 = create_graph::<i32, ()>();
1031 let graph2 = create_graph::<&str, ()>();
1032
1033 assert!(find_isomorphism_vf2(&graph1, &graph2).is_some());
1034 assert!(are_graphs_isomorphic_enhanced(&graph1, &graph2));
1035 }
1036
1037 #[test]
1038 fn test_vf2_algorithm_performance_comparison() -> GraphResult<()> {
1039 let mut graph1 = create_graph::<i32, ()>();
1041 let mut graph2 = create_graph::<i32, ()>();
1042
1043 for i in 1..=4 {
1045 for j in (i + 1)..=4 {
1046 graph1.add_edge(i, j, ())?;
1047 graph2.add_edge(i, j, ())?; }
1049 }
1050
1051 let naive_result = are_graphs_isomorphic(&graph1, &graph2);
1053 let vf2_result = are_graphs_isomorphic_enhanced(&graph1, &graph2);
1054
1055 assert_eq!(naive_result, vf2_result);
1056 assert!(naive_result); Ok(())
1059 }
1060
1061 #[test]
1062 fn test_vf2_different_degree_sequences() -> GraphResult<()> {
1063 let mut graph1 = create_graph::<i32, ()>(); graph1.add_edge(1, 2, ())?;
1066 graph1.add_edge(1, 3, ())?;
1067 let mut graph2 = create_graph::<i32, ()>(); graph2.add_edge(1, 2, ())?;
1071 graph2.add_edge(2, 3, ())?;
1072 let mut graph3 = create_graph::<i32, ()>(); graph3.add_edge(1, 2, ())?;
1078 graph3.add_edge(2, 3, ())?;
1079 graph3.add_edge(3, 1, ())?;
1080 assert!(!are_graphs_isomorphic(&graph1, &graph3));
1084 assert!(!are_graphs_isomorphic_enhanced(&graph1, &graph3));
1085 assert!(find_isomorphism_vf2(&graph1, &graph3).is_none());
1086
1087 Ok(())
1088 }
1089}