From 0c1515d88c073c4757d18078e61178fa54cd805c Mon Sep 17 00:00:00 2001 From: Sergio Valverde Date: Thu, 23 Jun 2022 17:34:19 -0600 Subject: [PATCH] Add function that returns an iterator with subgraph isomorphisms --- src/algo/isomorphism.rs | 162 +++++++++++++++++++++++++++++++++++++++- src/algo/mod.rs | 1 + tests/iso.rs | 50 ++++++++++++- 3 files changed, 208 insertions(+), 5 deletions(-) diff --git a/src/algo/isomorphism.rs b/src/algo/isomorphism.rs index febabcca1..e9bc9d881 100644 --- a/src/algo/isomorphism.rs +++ b/src/algo/isomorphism.rs @@ -557,6 +557,35 @@ mod matching { edge_match: &mut EM, match_subgraph: bool, ) -> Option + where + G0: NodeCompactIndexable + + EdgeCount + + GetAdjacencyMatrix + + GraphProp + + IntoNeighborsDirected, + G1: NodeCompactIndexable + + EdgeCount + + GetAdjacencyMatrix + + GraphProp + + IntoNeighborsDirected, + NM: NodeMatcher, + EM: EdgeMatcher, + { + let mut stack = vec![Frame::Outer]; + if isomorphisms(st, node_match, edge_match, match_subgraph, &mut stack).is_some() { + Some(true) + } else { + None + } + } + + fn isomorphisms( + mut st: &mut (Vf2State<'_, G0>, Vf2State<'_, G1>), + node_match: &mut NM, + edge_match: &mut EM, + match_subgraph: bool, + stack: &mut Vec>, + ) -> Option> where G0: NodeCompactIndexable + EdgeCount @@ -572,13 +601,13 @@ mod matching { EM: EdgeMatcher, { if st.0.is_complete() { - return Some(true); + return Some(st.0.mapping.clone()); } // A "depth first" search of a valid mapping from graph 1 to graph 2 // F(s, n, m) -- evaluate state s and add mapping n <-> m // Find least T1out node (in st.out[1] but not in M[1]) - let mut stack: Vec> = vec![Frame::Outer]; + let mut result = None; while let Some(frame) = stack.pop() { match frame { Frame::Unwind { nodes, open_list } => { @@ -609,7 +638,7 @@ mod matching { if is_feasible(&mut st, nodes, node_match, edge_match) { push_state(&mut st, nodes); if st.0.is_complete() { - return Some(true); + result = Some(st.0.mapping.clone()); } // Check cardinalities of Tin, Tout sets if (!match_subgraph @@ -638,8 +667,94 @@ mod matching { } } } + if result.is_some() { + return result; + } + } + result + } + + pub struct GraphMatcher<'a, 'b, 'c, G0, G1, NM, EM> + where + G0: NodeCompactIndexable + + EdgeCount + + GetAdjacencyMatrix + + GraphProp + + IntoNeighborsDirected, + G1: NodeCompactIndexable + + EdgeCount + + GetAdjacencyMatrix + + GraphProp + + IntoNeighborsDirected, + NM: NodeMatcher, + EM: EdgeMatcher, + { + st: (Vf2State<'a, G0>, Vf2State<'b, G1>), + node_match: &'c mut NM, + edge_match: &'c mut EM, + match_subgraph: bool, + stack: Vec>, + } + + impl<'a, 'b, 'c, G0, G1, NM, EM> GraphMatcher<'a, 'b, 'c, G0, G1, NM, EM> + where + G0: NodeCompactIndexable + + EdgeCount + + GetAdjacencyMatrix + + GraphProp + + IntoNeighborsDirected, + G1: NodeCompactIndexable + + EdgeCount + + GetAdjacencyMatrix + + GraphProp + + IntoNeighborsDirected, + NM: NodeMatcher, + EM: EdgeMatcher, + { + pub fn new( + g0: &'a G0, + g1: &'b G1, + node_match: &'c mut NM, + edge_match: &'c mut EM, + match_subgraph: bool, + ) -> Self { + let stack = vec![Frame::Outer]; + Self { + st: (Vf2State::new(g0), Vf2State::new(g1)), + node_match, + edge_match, + match_subgraph, + stack, + } + } + } + + impl<'a, 'b, 'c, G0, G1, NM, EM> Iterator for GraphMatcher<'a, 'b, 'c, G0, G1, NM, EM> + where + G0: NodeCompactIndexable + + EdgeCount + + GetAdjacencyMatrix + + GraphProp + + IntoNeighborsDirected, + G1: NodeCompactIndexable + + EdgeCount + + GetAdjacencyMatrix + + GraphProp + + IntoNeighborsDirected, + NM: NodeMatcher, + EM: EdgeMatcher, + { + type Item = Vec; + + fn next(&mut self) -> Option { + isomorphisms( + &mut self.st, + self.node_match, + self.edge_match, + self.match_subgraph, + &mut self.stack, + ) } - None } } @@ -794,3 +909,42 @@ where let mut st = (Vf2State::new(&g0), Vf2State::new(&g1)); self::matching::try_match(&mut st, &mut node_match, &mut edge_match, true).unwrap_or(false) } + +/// Using the VF2 algorithm, examine both syntactic and semantic graph +/// isomorphism (graph structure and matching node and edge weights) and, +/// if `g0` is isomorphic to a subgraph of `g1`, return the mappings between +/// them. +/// +/// The graphs should not be multigraphs. +pub fn subgraph_isomorphisms_iter<'a, G0, G1, NM, EM>( + g0: &'a G0, + g1: &'a G1, + node_match: &'a mut NM, + edge_match: &'a mut EM, +) -> Option> + 'a> +where + G0: 'a + + NodeCompactIndexable + + EdgeCount + + DataMap + + GetAdjacencyMatrix + + GraphProp + + IntoEdgesDirected, + G1: 'a + + NodeCompactIndexable + + EdgeCount + + DataMap + + GetAdjacencyMatrix + + GraphProp + + IntoEdgesDirected, + NM: 'a + FnMut(&G0::NodeWeight, &G1::NodeWeight) -> bool, + EM: 'a + FnMut(&G0::EdgeWeight, &G1::EdgeWeight) -> bool, +{ + if g0.node_count() > g1.node_count() || g0.edge_count() > g1.edge_count() { + return None; + } + + Some(self::matching::GraphMatcher::new( + g0, g1, node_match, edge_match, true, + )) +} diff --git a/src/algo/mod.rs b/src/algo/mod.rs index cb836c5dc..ef5911d80 100644 --- a/src/algo/mod.rs +++ b/src/algo/mod.rs @@ -40,6 +40,7 @@ pub use feedback_arc_set::greedy_feedback_arc_set; pub use floyd_warshall::floyd_warshall; pub use isomorphism::{ is_isomorphic, is_isomorphic_matching, is_isomorphic_subgraph, is_isomorphic_subgraph_matching, + subgraph_isomorphisms_iter, }; pub use k_shortest_path::k_shortest_path; pub use matching::{greedy_matching, maximum_matching, Matching}; diff --git a/tests/iso.rs b/tests/iso.rs index 5880e5f20..f808830e4 100644 --- a/tests/iso.rs +++ b/tests/iso.rs @@ -1,5 +1,6 @@ extern crate petgraph; +use std::collections::HashSet; use std::fs::File; use std::io::prelude::*; @@ -7,7 +8,9 @@ use petgraph::graph::{edge_index, node_index}; use petgraph::prelude::*; use petgraph::EdgeType; -use petgraph::algo::{is_isomorphic, is_isomorphic_matching, is_isomorphic_subgraph}; +use petgraph::algo::{ + is_isomorphic, is_isomorphic_matching, is_isomorphic_subgraph, subgraph_isomorphisms_iter, +}; /// Petersen A and B are isomorphic /// @@ -493,6 +496,51 @@ fn iso_subgraph() { assert!(is_isomorphic_subgraph(&g0, &g1)); } +#[test] +fn iter_subgraph() { + let a = Graph::<(), ()>::from_edges(&[(0, 1), (1, 2), (2, 0)]); + let b = Graph::<(), ()>::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3), (0, 4)]); + let a_ref = &a; + let b_ref = &b; + let mut node_match = { |x: &(), y: &()| x == y }; + let mut edge_match = { |x: &(), y: &()| x == y }; + + let mappings = + subgraph_isomorphisms_iter(&a_ref, &b_ref, &mut node_match, &mut edge_match).unwrap(); + + // Verify the iterator returns the expected mappings + let expected_mappings: Vec> = vec![vec![0, 1, 2], vec![1, 2, 0], vec![2, 0, 1]]; + for mapping in mappings { + assert!(expected_mappings.contains(&mapping)) + } + + // Verify all the mappings from the iterator are different + let a = str_to_digraph(COXETER_A); + let b = str_to_digraph(COXETER_B); + let a_ref = &a; + let b_ref = &b; + + let mut unique = HashSet::new(); + assert!( + subgraph_isomorphisms_iter(&a_ref, &b_ref, &mut node_match, &mut edge_match) + .unwrap() + .all(|x| unique.insert(x)) + ); + + // The iterator should return None for graphs that are not isomorphic + let a = str_to_digraph(G8_1); + let b = str_to_digraph(G8_2); + let a_ref = &a; + let b_ref = &b; + + assert!( + subgraph_isomorphisms_iter(&a_ref, &b_ref, &mut node_match, &mut edge_match) + .unwrap() + .next() + .is_none() + ); +} + /// Isomorphic pair const COXETER_A: &str = " 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1