scirs2_graph/algorithms/
isomorphism.rs

1//! Graph isomorphism and subgraph matching algorithms
2//!
3//! This module contains algorithms for graph isomorphism testing and subgraph matching.
4//! Features both a naive backtracking algorithm and the efficient VF2 algorithm.
5
6use crate::base::{EdgeWeight, Graph, IndexType, Node};
7use std::collections::{HashMap, HashSet};
8use std::hash::Hash;
9
10/// Find all subgraph matches of a pattern graph in a target graph
11///
12/// Returns a vector of mappings from pattern nodes to target nodes for each match found.
13#[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    // Try to match starting from each target node
35    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        // Found a complete match
67        matches.push(current_mapping.clone());
68        return;
69    }
70
71    let pattern_node = &pattern_nodes[pattern_idx];
72
73    // Check if target_node is already mapped
74    if current_mapping.values().any(|n| n == target_node) {
75        return;
76    }
77
78    // Try to map pattern_node to target_node
79    current_mapping.insert(pattern_node.clone(), target_node.clone());
80
81    // Check if this _mapping is consistent with edges
82    if is_mapping_consistent(pattern, target, current_mapping) {
83        if pattern_idx + 1 < pattern_nodes.len() {
84            // Continue _mapping with remaining _nodes
85            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            // Also try non-neighbors
100            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            // Last _node - check if complete _mapping is valid
115            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    // Backtrack
128    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    // Check that all edges in the pattern exist in the target under the mapping
144    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/// Check if two graphs are isomorphic
161///
162/// Two graphs are isomorphic if there exists a bijection between their vertices
163/// that preserves the edge-adjacency relationship.
164///
165/// # Arguments
166/// * `graph1` - The first graph
167/// * `graph2` - The second graph
168///
169/// # Returns
170/// * `bool` - True if the graphs are isomorphic, false otherwise
171#[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    // Quick checks first
183    if graph1.node_count() != graph2.node_count() || graph1.edge_count() != graph2.edge_count() {
184        return false;
185    }
186
187    // Check degree sequence
188    if !have_same_degree_sequence(graph1, graph2) {
189        return false;
190    }
191
192    // If either graph is empty, they're isomorphic
193    if graph1.node_count() == 0 {
194        return true;
195    }
196
197    // Try to find an isomorphism
198    find_isomorphism(graph1, graph2).is_some()
199}
200
201/// Find an isomorphism between two graphs if one exists
202///
203/// # Arguments
204/// * `graph1` - The first graph
205/// * `graph2` - The second graph
206///
207/// # Returns
208/// * `Option<HashMap<N1, N2>>` - Mapping from graph1 nodes to graph2 nodes if isomorphic
209#[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/// Check if two graphs have the same degree sequence
236#[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/// Backtracking algorithm to find isomorphism
274#[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    // Base case: all nodes mapped
290    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        // Skip if this node2 is already mapped
298        if mapping.values().any(|mapped| mapped == node2) {
299            continue;
300        }
301
302        // Check degree compatibility
303        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        // Try this mapping
315        mapping.insert(node1.clone(), node2.clone());
316
317        // Check if current partial mapping is consistent
318        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        // Backtrack
325        mapping.remove(node1);
326    }
327
328    false
329}
330
331/// Check if a partial mapping is valid (preserves edges among mapped nodes)
332#[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/// Check if a complete mapping is a valid isomorphism
361#[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    // Check that the mapping preserves all edges
374    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/// VF2 Algorithm State for efficient graph isomorphism checking
390///
391/// The VF2 algorithm maintains state about the current mapping and feasible candidates
392/// to efficiently prune the search space and achieve much better performance than naive backtracking.
393#[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    /// Current mapping from graph1 to graph2
400    mapping: HashMap<N1, N2>,
401    /// Reverse mapping from graph2 to graph1  
402    reverse_mapping: HashMap<N2, N1>,
403    /// Terminal sets for graph1 (nodes adjacent to mapped nodes)
404    terminal_1: HashSet<N1>,
405    /// Terminal sets for graph2 (nodes adjacent to mapped nodes)
406    terminal_2: HashSet<N2>,
407    /// Mapped nodes in graph1
408    mapped_1: HashSet<N1>,
409    /// Mapped nodes in graph2
410    mapped_2: HashSet<N2>,
411    /// Depth of current state (number of mapped pairs)
412    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    /// Create a new empty VF2 state
421    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    /// Add a new mapping pair to the state
434    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        // Update terminal sets by adding neighbors of newly mapped nodes
451        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        // Remove newly mapped nodes from terminal sets
468        self.terminal_1.remove(&n1);
469        self.terminal_2.remove(&n2);
470    }
471
472    /// Remove the last mapping pair (backtrack)
473    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        // Restore terminal sets
490        self.update_terminal_sets_after_removal(n1, n2, graph1, graph2);
491    }
492
493    /// Update terminal sets after removing a mapping
494    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        // Rebuild terminal sets from scratch for simplicity and correctness
505        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    /// Get feasible candidate pairs for the next mapping step
530    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        // Priority 1: Terminal-Terminal pairs (both in terminal sets)
542        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        // Priority 2: Terminal-Non-terminal pairs
552        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        // Priority 3: Any unmapped pair
577        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                        // For efficiency, return first candidate in this case
586                        return candidates;
587                    }
588                }
589            }
590        }
591
592        candidates
593    }
594
595    /// Check if the current state is feasible (VF2 feasibility rules)
596    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        // Rule 1: Degree compatibility (basic check)
608        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        // Rule 2: Predecessor rule - check edges to already mapped nodes
615        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        // Rule 3: Successor rule - check potential future mappings
625        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        // Rule 4: Look-ahead rule for unmapped neighbors
633        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    /// Count neighbors in terminal set for graph1 nodes
644    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    /// Count neighbors in terminal set for graph2 nodes
660    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    /// Count unmapped neighbors for graph1 nodes (not in terminal or mapped sets)
676    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    /// Count unmapped neighbors for graph2 nodes (not in terminal or mapped sets)
692    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/// VF2 algorithm for graph isomorphism - enhanced performance version
709///
710/// This implementation uses the VF2 algorithm which provides significant performance
711/// improvements over naive backtracking through intelligent state space exploration
712/// and feasibility-based pruning.
713#[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    // Quick validation checks
725    if graph1.node_count() != graph2.node_count() || graph1.edge_count() != graph2.edge_count() {
726        return None;
727    }
728
729    // Check degree sequence
730    if !have_same_degree_sequence(graph1, graph2) {
731        return None;
732    }
733
734    // Handle empty graphs
735    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/// Core VF2 matching recursive function
748#[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    // Base case: all nodes mapped
761    if state.depth == graph1.node_count() {
762        return true;
763    }
764
765    // Get candidate pairs using VF2 ordering heuristics
766    let candidates = state.get_candidate_pairs(graph1, graph2);
767
768    for (n1, n2) in candidates {
769        // Check VF2 feasibility rules
770        if state.is_feasible(&n1, &n2, graph1, graph2) {
771            // Add the pair to current state
772            state.add_pair(n1.clone(), n2.clone(), graph1, graph2);
773
774            // Recursively try to complete the mapping
775            if vf2_match(state, graph1, graph2) {
776                return true;
777            }
778
779            // Backtrack
780            state.remove_pair(&n1, &n2, graph1, graph2);
781        }
782    }
783
784    false
785}
786
787/// Enhanced isomorphism checking using VF2 algorithm with fallback
788///
789/// This function first attempts to use the efficient VF2 algorithm for isomorphism checking.
790/// For very small graphs or edge cases, it may fall back to the simpler backtracking algorithm.
791#[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    // For very small graphs, use simple algorithm
803    if graph1.node_count() <= 4 {
804        return are_graphs_isomorphic(graph1, graph2);
805    }
806
807    // For larger graphs, use VF2 for better performance
808    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        // Create a pattern graph (triangle)
820        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        // Create a target graph with two triangles
826        let mut target = create_graph::<&str, ()>();
827        // First triangle
828        target.add_edge("1", "2", ())?;
829        target.add_edge("2", "3", ())?;
830        target.add_edge("3", "1", ())?;
831        // Second triangle
832        target.add_edge("4", "5", ())?;
833        target.add_edge("5", "6", ())?;
834        target.add_edge("6", "4", ())?;
835        // Connect them
836        target.add_edge("3", "4", ())?;
837
838        let matches = find_subgraph_matches(&pattern, &target);
839
840        // Should find at least 2 triangles
841        assert!(matches.len() >= 2);
842
843        // Each match should have 3 mappings
844        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        // Create a pattern graph (triangle)
854        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        // Create a target graph with no triangles (path)
860        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        // Should find no matches
868        assert_eq!(matches.len(), 0);
869
870        Ok(())
871    }
872
873    #[test]
874    fn test_isomorphic_graphs() -> GraphResult<()> {
875        // Create two isomorphic triangles with different node labels
876        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        // Triangle vs path
897        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        // Create two isomorphic triangles with different node labels
939        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        // Test VF2 algorithm
950        let vf2_mapping = find_isomorphism_vf2(&graph1, &graph2);
951        assert!(vf2_mapping.is_some());
952
953        // Test enhanced function
954        assert!(are_graphs_isomorphic_enhanced(&graph1, &graph2));
955
956        // Compare with original algorithm
957        assert!(are_graphs_isomorphic(&graph1, &graph2));
958
959        Ok(())
960    }
961
962    #[test]
963    fn test_vf2_algorithm_larger_graph() -> GraphResult<()> {
964        // Create a more complex graph (pentagon with internal connections)
965        let mut graph1 = create_graph::<i32, ()>();
966        // Pentagon
967        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        // Internal connections
973        graph1.add_edge(1, 3, ())?;
974        graph1.add_edge(2, 4, ())?;
975
976        // Create isomorphic graph with different labeling
977        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        // Test VF2 algorithm
987        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        // Create two non-isomorphic graphs with same number of nodes and edges
996        let mut graph1 = create_graph::<i32, ()>(); // Path graph
997        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, ()>(); // Star graph
1002        graph2.add_edge(1, 2, ())?;
1003        graph2.add_edge(1, 3, ())?;
1004        graph2.add_edge(1, 4, ())?;
1005
1006        // Both algorithms should return false
1007        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        // Create a larger graph to test performance characteristics
1040        let mut graph1 = create_graph::<i32, ()>();
1041        let mut graph2 = create_graph::<i32, ()>();
1042
1043        // Create two isomorphic complete graphs K4
1044        for i in 1..=4 {
1045            for j in (i + 1)..=4 {
1046                graph1.add_edge(i, j, ())?;
1047                graph2.add_edge(i, j, ())?; // Same structure for simplicity
1048            }
1049        }
1050
1051        // Both algorithms should find isomorphism
1052        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); // Should be isomorphic
1057
1058        Ok(())
1059    }
1060
1061    #[test]
1062    fn test_vf2_different_degree_sequences() -> GraphResult<()> {
1063        // Create graphs with different degree sequences (quick rejection test)
1064        let mut graph1 = create_graph::<i32, ()>(); // Star graph: 1 connects to 2,3
1065        graph1.add_edge(1, 2, ())?;
1066        graph1.add_edge(1, 3, ())?;
1067        // Degree sequence: [2, 1, 1] - node 1 has degree 2, nodes 2,3 have degree 1
1068
1069        let mut graph2 = create_graph::<i32, ()>(); // Path graph: 1-2-3
1070        graph2.add_edge(1, 2, ())?;
1071        graph2.add_edge(2, 3, ())?;
1072        // Degree sequence: [1, 2, 1] - node 2 has degree 2, nodes 1,3 have degree 1
1073
1074        // Both have same degree sequence [2,1,1] when sorted, so they might be isomorphic
1075        // Let's create truly different degree sequences instead
1076        let mut graph3 = create_graph::<i32, ()>(); // Triangle
1077        graph3.add_edge(1, 2, ())?;
1078        graph3.add_edge(2, 3, ())?;
1079        graph3.add_edge(3, 1, ())?;
1080        // Degree sequence: [2, 2, 2] - all nodes have degree 2
1081
1082        // Compare star vs triangle (different degree sequences)
1083        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}