use crate::base::{EdgeWeight, Graph, IndexType, Node};
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
#[allow(dead_code)]
pub fn find_subgraph_matches<N1, N2, E, Ix>(
pattern: &Graph<N1, E, Ix>,
target: &Graph<N2, E, Ix>,
) -> Vec<HashMap<N1, N2>>
where
N1: Node + Clone + Hash + Eq + std::fmt::Debug,
N2: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight,
Ix: IndexType,
{
let pattern_nodes: Vec<N1> = pattern.nodes().into_iter().cloned().collect();
let target_nodes: Vec<N2> = target.nodes().into_iter().cloned().collect();
if pattern_nodes.is_empty() || pattern_nodes.len() > target_nodes.len() {
return vec![];
}
let mut matches = Vec::new();
let mut current_mapping = HashMap::new();
for start_node in &target_nodes {
find_matches_recursive(
&pattern_nodes,
pattern,
target,
&mut current_mapping,
0,
start_node,
&mut matches,
);
}
matches
}
#[allow(dead_code)]
fn find_matches_recursive<N1, N2, E, Ix>(
pattern_nodes: &[N1],
pattern: &Graph<N1, E, Ix>,
target: &Graph<N2, E, Ix>,
current_mapping: &mut HashMap<N1, N2>,
pattern_idx: usize,
target_node: &N2,
matches: &mut Vec<HashMap<N1, N2>>,
) where
N1: Node + Clone + Hash + Eq + std::fmt::Debug,
N2: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight,
Ix: IndexType,
{
if pattern_idx >= pattern_nodes.len() {
matches.push(current_mapping.clone());
return;
}
let pattern_node = &pattern_nodes[pattern_idx];
if current_mapping.values().any(|n| n == target_node) {
return;
}
current_mapping.insert(pattern_node.clone(), target_node.clone());
if is_mapping_consistent(pattern, target, current_mapping) {
if pattern_idx + 1 < pattern_nodes.len() {
if let Ok(target_neighbors) = target.neighbors(target_node) {
for next_target in target_neighbors {
find_matches_recursive(
pattern_nodes,
pattern,
target,
current_mapping,
pattern_idx + 1,
&next_target,
matches,
);
}
}
for next_target in &target.nodes().into_iter().cloned().collect::<Vec<_>>() {
if !current_mapping.values().any(|n| n == next_target) {
find_matches_recursive(
pattern_nodes,
pattern,
target,
current_mapping,
pattern_idx + 1,
next_target,
matches,
);
}
}
} else {
find_matches_recursive(
pattern_nodes,
pattern,
target,
current_mapping,
pattern_idx + 1,
target_node,
matches,
);
}
}
current_mapping.remove(pattern_node);
}
#[allow(dead_code)]
fn is_mapping_consistent<N1, N2, E, Ix>(
pattern: &Graph<N1, E, Ix>,
target: &Graph<N2, E, Ix>,
mapping: &HashMap<N1, N2>,
) -> bool
where
N1: Node + Hash + Eq + std::fmt::Debug,
N2: Node + Hash + Eq + std::fmt::Debug,
E: EdgeWeight,
Ix: IndexType,
{
for (n1, n2) in mapping {
for (m1, m2) in mapping {
if n1 != m1 {
let pattern_has_edge = pattern.has_edge(n1, m1);
let target_has_edge = target.has_edge(n2, m2);
if pattern_has_edge && !target_has_edge {
return false;
}
}
}
}
true
}
#[allow(dead_code)]
pub fn are_graphs_isomorphic<N1, N2, E, Ix>(
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
) -> bool
where
N1: Node + Clone + Hash + Eq + std::fmt::Debug,
N2: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight,
Ix: IndexType,
{
if graph1.node_count() != graph2.node_count() || graph1.edge_count() != graph2.edge_count() {
return false;
}
if !have_same_degree_sequence(graph1, graph2) {
return false;
}
if graph1.node_count() == 0 {
return true;
}
find_isomorphism(graph1, graph2).is_some()
}
#[allow(dead_code)]
pub fn find_isomorphism<N1, N2, E, Ix>(
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
) -> Option<HashMap<N1, N2>>
where
N1: Node + Clone + Hash + Eq + std::fmt::Debug,
N2: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight,
Ix: IndexType,
{
let nodes1: Vec<N1> = graph1.nodes().into_iter().cloned().collect();
let nodes2: Vec<N2> = graph2.nodes().into_iter().cloned().collect();
if nodes1.len() != nodes2.len() {
return None;
}
let mut mapping = HashMap::new();
if backtrack_isomorphism(&nodes1, &nodes2, graph1, graph2, &mut mapping, 0) {
Some(mapping)
} else {
None
}
}
#[allow(dead_code)]
fn have_same_degree_sequence<N1, N2, E, Ix>(
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
) -> bool
where
N1: Node + std::fmt::Debug,
N2: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: IndexType,
{
let mut degrees1: Vec<usize> = graph1
.nodes()
.iter()
.map(|node| {
graph1
.neighbors(node)
.map_or(0, |neighbors| neighbors.len())
})
.collect();
let mut degrees2: Vec<usize> = graph2
.nodes()
.iter()
.map(|node| {
graph2
.neighbors(node)
.map_or(0, |neighbors| neighbors.len())
})
.collect();
degrees1.sort_unstable();
degrees2.sort_unstable();
degrees1 == degrees2
}
#[allow(dead_code)]
fn backtrack_isomorphism<N1, N2, E, Ix>(
nodes1: &[N1],
nodes2: &[N2],
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
mapping: &mut HashMap<N1, N2>,
depth: usize,
) -> bool
where
N1: Node + Clone + Hash + Eq + std::fmt::Debug,
N2: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight,
Ix: IndexType,
{
if depth == nodes1.len() {
return is_valid_isomorphism(graph1, graph2, mapping);
}
let node1 = &nodes1[depth];
for node2 in nodes2 {
if mapping.values().any(|mapped| mapped == node2) {
continue;
}
let degree1 = graph1
.neighbors(node1)
.map_or(0, |neighbors| neighbors.len());
let degree2 = graph2
.neighbors(node2)
.map_or(0, |neighbors| neighbors.len());
if degree1 != degree2 {
continue;
}
mapping.insert(node1.clone(), node2.clone());
if is_partial_mapping_valid(graph1, graph2, mapping, depth + 1)
&& backtrack_isomorphism(nodes1, nodes2, graph1, graph2, mapping, depth + 1)
{
return true;
}
mapping.remove(node1);
}
false
}
#[allow(dead_code)]
fn is_partial_mapping_valid<N1, N2, E, Ix>(
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
mapping: &HashMap<N1, N2>,
_mapped_count: usize,
) -> bool
where
N1: Node + Hash + Eq + std::fmt::Debug,
N2: Node + Hash + Eq + std::fmt::Debug,
E: EdgeWeight,
Ix: IndexType,
{
for (n1, n2) in mapping {
for (m1, m2) in mapping {
if n1 != m1 {
let edge1_exists = graph1.has_edge(n1, m1);
let edge2_exists = graph2.has_edge(n2, m2);
if edge1_exists != edge2_exists {
return false;
}
}
}
}
true
}
#[allow(dead_code)]
fn is_valid_isomorphism<N1, N2, E, Ix>(
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
mapping: &HashMap<N1, N2>,
) -> bool
where
N1: Node + Hash + Eq + std::fmt::Debug,
N2: Node + Hash + Eq + std::fmt::Debug,
E: EdgeWeight,
Ix: IndexType,
{
for (n1, n2) in mapping {
for (m1, m2) in mapping {
if n1 != m1 {
let edge1_exists = graph1.has_edge(n1, m1);
let edge2_exists = graph2.has_edge(n2, m2);
if edge1_exists != edge2_exists {
return false;
}
}
}
}
true
}
#[derive(Debug, Clone)]
struct VF2State<N1, N2>
where
N1: Node + Clone + Hash + Eq + std::fmt::Debug,
N2: Node + Clone + Hash + Eq + std::fmt::Debug,
{
mapping: HashMap<N1, N2>,
reverse_mapping: HashMap<N2, N1>,
terminal_1: HashSet<N1>,
terminal_2: HashSet<N2>,
mapped_1: HashSet<N1>,
mapped_2: HashSet<N2>,
depth: usize,
}
impl<N1, N2> VF2State<N1, N2>
where
N1: Node + Clone + Hash + Eq + std::fmt::Debug,
N2: Node + Clone + Hash + Eq + std::fmt::Debug,
{
fn new() -> Self {
VF2State {
mapping: HashMap::new(),
reverse_mapping: HashMap::new(),
terminal_1: HashSet::new(),
terminal_2: HashSet::new(),
mapped_1: HashSet::new(),
mapped_2: HashSet::new(),
depth: 0,
}
}
fn add_pair<E, Ix>(
&mut self,
n1: N1,
n2: N2,
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
) where
E: EdgeWeight,
Ix: IndexType,
{
self.mapping.insert(n1.clone(), n2.clone());
self.reverse_mapping.insert(n2.clone(), n1.clone());
self.mapped_1.insert(n1.clone());
self.mapped_2.insert(n2.clone());
self.depth += 1;
if let Ok(neighbors1) = graph1.neighbors(&n1) {
for neighbor in neighbors1 {
if !self.mapped_1.contains(&neighbor) {
self.terminal_1.insert(neighbor);
}
}
}
if let Ok(neighbors2) = graph2.neighbors(&n2) {
for neighbor in neighbors2 {
if !self.mapped_2.contains(&neighbor) {
self.terminal_2.insert(neighbor);
}
}
}
self.terminal_1.remove(&n1);
self.terminal_2.remove(&n2);
}
fn remove_pair<E, Ix>(
&mut self,
n1: &N1,
n2: &N2,
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
) where
E: EdgeWeight,
Ix: IndexType,
{
self.mapping.remove(n1);
self.reverse_mapping.remove(n2);
self.mapped_1.remove(n1);
self.mapped_2.remove(n2);
self.depth -= 1;
self.update_terminal_sets_after_removal(n1, n2, graph1, graph2);
}
fn update_terminal_sets_after_removal<E, Ix>(
&mut self,
_n1: &N1,
_n2: &N2,
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
) where
E: EdgeWeight,
Ix: IndexType,
{
self.terminal_1.clear();
self.terminal_2.clear();
for mapped_node in &self.mapped_1 {
if let Ok(neighbors) = graph1.neighbors(mapped_node) {
for neighbor in neighbors {
if !self.mapped_1.contains(&neighbor) {
self.terminal_1.insert(neighbor);
}
}
}
}
for mapped_node in &self.mapped_2 {
if let Ok(neighbors) = graph2.neighbors(mapped_node) {
for neighbor in neighbors {
if !self.mapped_2.contains(&neighbor) {
self.terminal_2.insert(neighbor);
}
}
}
}
}
fn get_candidate_pairs<E, Ix>(
&self,
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
) -> Vec<(N1, N2)>
where
E: EdgeWeight,
Ix: IndexType,
{
let mut candidates = Vec::new();
if !self.terminal_1.is_empty() && !self.terminal_2.is_empty() {
for n1 in &self.terminal_1 {
for n2 in &self.terminal_2 {
candidates.push((n1.clone(), n2.clone()));
}
}
return candidates;
}
if !self.terminal_1.is_empty() {
let all_nodes2: Vec<N2> = graph2.nodes().into_iter().cloned().collect();
for n1 in &self.terminal_1 {
for n2 in all_nodes2.iter() {
if !self.mapped_2.contains(n2) {
candidates.push((n1.clone(), n2.clone()));
}
}
}
return candidates;
}
if !self.terminal_2.is_empty() {
let all_nodes1: Vec<N1> = graph1.nodes().into_iter().cloned().collect();
for n1 in all_nodes1.iter() {
if !self.mapped_1.contains(n1) {
for n2 in &self.terminal_2 {
candidates.push((n1.clone(), n2.clone()));
}
}
}
return candidates;
}
let all_nodes1: Vec<N1> = graph1.nodes().into_iter().cloned().collect();
let all_nodes2: Vec<N2> = graph2.nodes().into_iter().cloned().collect();
for n1 in all_nodes1.iter() {
if !self.mapped_1.contains(n1) {
for n2 in all_nodes2.iter() {
if !self.mapped_2.contains(n2) {
candidates.push((n1.clone(), n2.clone()));
return candidates;
}
}
}
}
candidates
}
fn is_feasible<E, Ix>(
&self,
n1: &N1,
n2: &N2,
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
) -> bool
where
E: EdgeWeight,
Ix: IndexType,
{
let degree1 = graph1.neighbors(n1).map_or(0, |neighbors| neighbors.len());
let degree2 = graph2.neighbors(n2).map_or(0, |neighbors| neighbors.len());
if degree1 != degree2 {
return false;
}
for (mapped_n1, mapped_n2) in &self.mapping {
let edge1_exists = graph1.has_edge(n1, mapped_n1) || graph1.has_edge(mapped_n1, n1);
let edge2_exists = graph2.has_edge(n2, mapped_n2) || graph2.has_edge(mapped_n2, n2);
if edge1_exists != edge2_exists {
return false;
}
}
let n1_terminal_neighbors = self.count_terminal_neighbors_1(n1, graph1);
let n2_terminal_neighbors = self.count_terminal_neighbors_2(n2, graph2);
if n1_terminal_neighbors != n2_terminal_neighbors {
return false;
}
let n1_unmapped_neighbors = self.count_unmapped_neighbors_1(n1, graph1);
let n2_unmapped_neighbors = self.count_unmapped_neighbors_2(n2, graph2);
if n1_unmapped_neighbors != n2_unmapped_neighbors {
return false;
}
true
}
fn count_terminal_neighbors_1<E, Ix>(&self, node: &N1, graph: &Graph<N1, E, Ix>) -> usize
where
E: EdgeWeight,
Ix: IndexType,
{
if let Ok(neighbors) = graph.neighbors(node) {
neighbors
.into_iter()
.filter(|n| self.terminal_1.contains(n) && !self.mapped_1.contains(n))
.count()
} else {
0
}
}
fn count_terminal_neighbors_2<E, Ix>(&self, node: &N2, graph: &Graph<N2, E, Ix>) -> usize
where
E: EdgeWeight,
Ix: IndexType,
{
if let Ok(neighbors) = graph.neighbors(node) {
neighbors
.into_iter()
.filter(|n| self.terminal_2.contains(n) && !self.mapped_2.contains(n))
.count()
} else {
0
}
}
fn count_unmapped_neighbors_1<E, Ix>(&self, node: &N1, graph: &Graph<N1, E, Ix>) -> usize
where
E: EdgeWeight,
Ix: IndexType,
{
if let Ok(neighbors) = graph.neighbors(node) {
neighbors
.into_iter()
.filter(|n| !self.mapped_1.contains(n) && !self.terminal_1.contains(n))
.count()
} else {
0
}
}
fn count_unmapped_neighbors_2<E, Ix>(&self, node: &N2, graph: &Graph<N2, E, Ix>) -> usize
where
E: EdgeWeight,
Ix: IndexType,
{
if let Ok(neighbors) = graph.neighbors(node) {
neighbors
.into_iter()
.filter(|n| !self.mapped_2.contains(n) && !self.terminal_2.contains(n))
.count()
} else {
0
}
}
}
#[allow(dead_code)]
pub fn find_isomorphism_vf2<N1, N2, E, Ix>(
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
) -> Option<HashMap<N1, N2>>
where
N1: Node + Clone + Hash + Eq + std::fmt::Debug,
N2: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight,
Ix: IndexType,
{
if graph1.node_count() != graph2.node_count() || graph1.edge_count() != graph2.edge_count() {
return None;
}
if !have_same_degree_sequence(graph1, graph2) {
return None;
}
if graph1.node_count() == 0 {
return Some(HashMap::new());
}
let mut state = VF2State::new();
if vf2_match(&mut state, graph1, graph2) {
Some(state.mapping)
} else {
None
}
}
#[allow(dead_code)]
fn vf2_match<N1, N2, E, Ix>(
state: &mut VF2State<N1, N2>,
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
) -> bool
where
N1: Node + Clone + Hash + Eq + std::fmt::Debug,
N2: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight,
Ix: IndexType,
{
if state.depth == graph1.node_count() {
return true;
}
let candidates = state.get_candidate_pairs(graph1, graph2);
for (n1, n2) in candidates {
if state.is_feasible(&n1, &n2, graph1, graph2) {
state.add_pair(n1.clone(), n2.clone(), graph1, graph2);
if vf2_match(state, graph1, graph2) {
return true;
}
state.remove_pair(&n1, &n2, graph1, graph2);
}
}
false
}
#[allow(dead_code)]
pub fn are_graphs_isomorphic_enhanced<N1, N2, E, Ix>(
graph1: &Graph<N1, E, Ix>,
graph2: &Graph<N2, E, Ix>,
) -> bool
where
N1: Node + Clone + Hash + Eq + std::fmt::Debug,
N2: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight,
Ix: IndexType,
{
if graph1.node_count() <= 4 {
return are_graphs_isomorphic(graph1, graph2);
}
find_isomorphism_vf2(graph1, graph2).is_some()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Result as GraphResult;
use crate::generators::create_graph;
#[test]
fn test_find_subgraph_matches() -> GraphResult<()> {
let mut pattern = create_graph::<&str, ()>();
pattern.add_edge("A", "B", ())?;
pattern.add_edge("B", "C", ())?;
pattern.add_edge("C", "A", ())?;
let mut target = create_graph::<&str, ()>();
target.add_edge("1", "2", ())?;
target.add_edge("2", "3", ())?;
target.add_edge("3", "1", ())?;
target.add_edge("4", "5", ())?;
target.add_edge("5", "6", ())?;
target.add_edge("6", "4", ())?;
target.add_edge("3", "4", ())?;
let matches = find_subgraph_matches(&pattern, &target);
assert!(matches.len() >= 2);
for match_map in &matches {
assert_eq!(match_map.len(), 3);
}
Ok(())
}
#[test]
fn test_no_subgraph_match() -> GraphResult<()> {
let mut pattern = create_graph::<&str, ()>();
pattern.add_edge("A", "B", ())?;
pattern.add_edge("B", "C", ())?;
pattern.add_edge("C", "A", ())?;
let mut target = create_graph::<&str, ()>();
target.add_edge("1", "2", ())?;
target.add_edge("2", "3", ())?;
target.add_edge("3", "4", ())?;
let matches = find_subgraph_matches(&pattern, &target);
assert_eq!(matches.len(), 0);
Ok(())
}
#[test]
fn test_isomorphic_graphs() -> GraphResult<()> {
let mut graph1 = create_graph::<&str, ()>();
graph1.add_edge("A", "B", ())?;
graph1.add_edge("B", "C", ())?;
graph1.add_edge("C", "A", ())?;
let mut graph2 = create_graph::<i32, ()>();
graph2.add_edge(1, 2, ())?;
graph2.add_edge(2, 3, ())?;
graph2.add_edge(3, 1, ())?;
assert!(are_graphs_isomorphic(&graph1, &graph2));
let isomorphism = find_isomorphism(&graph1, &graph2);
assert!(isomorphism.is_some());
Ok(())
}
#[test]
fn test_non_isomorphic_graphs() -> GraphResult<()> {
let mut triangle = create_graph::<i32, ()>();
triangle.add_edge(1, 2, ())?;
triangle.add_edge(2, 3, ())?;
triangle.add_edge(3, 1, ())?;
let mut path = create_graph::<i32, ()>();
path.add_edge(1, 2, ())?;
path.add_edge(2, 3, ())?;
assert!(!are_graphs_isomorphic(&triangle, &path));
assert!(find_isomorphism(&triangle, &path).is_none());
Ok(())
}
#[test]
fn test_different_size_graphs() -> GraphResult<()> {
let mut small = create_graph::<i32, ()>();
small.add_edge(1, 2, ())?;
let mut large = create_graph::<i32, ()>();
large.add_edge(1, 2, ())?;
large.add_edge(2, 3, ())?;
assert!(!are_graphs_isomorphic(&small, &large));
assert!(find_isomorphism(&small, &large).is_none());
Ok(())
}
#[test]
fn test_empty_graphs() {
let graph1 = create_graph::<i32, ()>();
let graph2 = create_graph::<&str, ()>();
assert!(are_graphs_isomorphic(&graph1, &graph2));
assert!(find_isomorphism(&graph1, &graph2).is_some());
}
#[test]
fn test_vf2_algorithm_triangles() -> GraphResult<()> {
let mut graph1 = create_graph::<&str, ()>();
graph1.add_edge("A", "B", ())?;
graph1.add_edge("B", "C", ())?;
graph1.add_edge("C", "A", ())?;
let mut graph2 = create_graph::<i32, ()>();
graph2.add_edge(1, 2, ())?;
graph2.add_edge(2, 3, ())?;
graph2.add_edge(3, 1, ())?;
let vf2_mapping = find_isomorphism_vf2(&graph1, &graph2);
assert!(vf2_mapping.is_some());
assert!(are_graphs_isomorphic_enhanced(&graph1, &graph2));
assert!(are_graphs_isomorphic(&graph1, &graph2));
Ok(())
}
#[test]
fn test_vf2_algorithm_larger_graph() -> GraphResult<()> {
let mut graph1 = create_graph::<i32, ()>();
graph1.add_edge(1, 2, ())?;
graph1.add_edge(2, 3, ())?;
graph1.add_edge(3, 4, ())?;
graph1.add_edge(4, 5, ())?;
graph1.add_edge(5, 1, ())?;
graph1.add_edge(1, 3, ())?;
graph1.add_edge(2, 4, ())?;
let mut graph2 = create_graph::<char, ()>();
graph2.add_edge('A', 'B', ())?;
graph2.add_edge('B', 'C', ())?;
graph2.add_edge('C', 'D', ())?;
graph2.add_edge('D', 'E', ())?;
graph2.add_edge('E', 'A', ())?;
graph2.add_edge('A', 'C', ())?;
graph2.add_edge('B', 'D', ())?;
assert!(find_isomorphism_vf2(&graph1, &graph2).is_some());
assert!(are_graphs_isomorphic_enhanced(&graph1, &graph2));
Ok(())
}
#[test]
fn test_vf2_non_isomorphic_graphs() -> GraphResult<()> {
let mut graph1 = create_graph::<i32, ()>(); graph1.add_edge(1, 2, ())?;
graph1.add_edge(2, 3, ())?;
graph1.add_edge(3, 4, ())?;
let mut graph2 = create_graph::<i32, ()>(); graph2.add_edge(1, 2, ())?;
graph2.add_edge(1, 3, ())?;
graph2.add_edge(1, 4, ())?;
assert!(!are_graphs_isomorphic(&graph1, &graph2));
assert!(!are_graphs_isomorphic_enhanced(&graph1, &graph2));
assert!(find_isomorphism_vf2(&graph1, &graph2).is_none());
Ok(())
}
#[test]
fn test_vf2_single_node_graphs() -> GraphResult<()> {
let mut graph1 = create_graph::<&str, ()>();
graph1.add_node("A");
let mut graph2 = create_graph::<i32, ()>();
graph2.add_node(1);
assert!(find_isomorphism_vf2(&graph1, &graph2).is_some());
assert!(are_graphs_isomorphic_enhanced(&graph1, &graph2));
Ok(())
}
#[test]
fn test_vf2_empty_graphs() {
let graph1 = create_graph::<i32, ()>();
let graph2 = create_graph::<&str, ()>();
assert!(find_isomorphism_vf2(&graph1, &graph2).is_some());
assert!(are_graphs_isomorphic_enhanced(&graph1, &graph2));
}
#[test]
fn test_vf2_algorithm_performance_comparison() -> GraphResult<()> {
let mut graph1 = create_graph::<i32, ()>();
let mut graph2 = create_graph::<i32, ()>();
for i in 1..=4 {
for j in (i + 1)..=4 {
graph1.add_edge(i, j, ())?;
graph2.add_edge(i, j, ())?; }
}
let naive_result = are_graphs_isomorphic(&graph1, &graph2);
let vf2_result = are_graphs_isomorphic_enhanced(&graph1, &graph2);
assert_eq!(naive_result, vf2_result);
assert!(naive_result);
Ok(())
}
#[test]
fn test_vf2_different_degree_sequences() -> GraphResult<()> {
let mut graph1 = create_graph::<i32, ()>(); graph1.add_edge(1, 2, ())?;
graph1.add_edge(1, 3, ())?;
let mut graph2 = create_graph::<i32, ()>(); graph2.add_edge(1, 2, ())?;
graph2.add_edge(2, 3, ())?;
let mut graph3 = create_graph::<i32, ()>(); graph3.add_edge(1, 2, ())?;
graph3.add_edge(2, 3, ())?;
graph3.add_edge(3, 1, ())?;
assert!(!are_graphs_isomorphic(&graph1, &graph3));
assert!(!are_graphs_isomorphic_enhanced(&graph1, &graph3));
assert!(find_isomorphism_vf2(&graph1, &graph3).is_none());
Ok(())
}
}