Skip to content

Commit

Permalink
Add function that returns an iterator with subgraph isomorphisms
Browse files Browse the repository at this point in the history
  • Loading branch information
vlvrd committed Jun 23, 2022
1 parent 8a47271 commit 0c1515d
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 5 deletions.
162 changes: 158 additions & 4 deletions src/algo/isomorphism.rs
Expand Up @@ -557,6 +557,35 @@ mod matching {
edge_match: &mut EM,
match_subgraph: bool,
) -> Option<bool>
where
G0: NodeCompactIndexable
+ EdgeCount
+ GetAdjacencyMatrix
+ GraphProp
+ IntoNeighborsDirected,
G1: NodeCompactIndexable
+ EdgeCount
+ GetAdjacencyMatrix
+ GraphProp
+ IntoNeighborsDirected,
NM: NodeMatcher<G0, G1>,
EM: EdgeMatcher<G0, G1>,
{
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<G0, G1, NM, EM>(
mut st: &mut (Vf2State<'_, G0>, Vf2State<'_, G1>),
node_match: &mut NM,
edge_match: &mut EM,
match_subgraph: bool,
stack: &mut Vec<Frame<G0, G1>>,
) -> Option<Vec<usize>>
where
G0: NodeCompactIndexable
+ EdgeCount
Expand All @@ -572,13 +601,13 @@ mod matching {
EM: EdgeMatcher<G0, G1>,
{
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<Frame<G0, G1>> = vec![Frame::Outer];
let mut result = None;
while let Some(frame) = stack.pop() {
match frame {
Frame::Unwind { nodes, open_list } => {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<G0, G1>,
EM: EdgeMatcher<G0, G1>,
{
st: (Vf2State<'a, G0>, Vf2State<'b, G1>),
node_match: &'c mut NM,
edge_match: &'c mut EM,
match_subgraph: bool,
stack: Vec<Frame<G0, G1>>,
}

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<G0, G1>,
EM: EdgeMatcher<G0, G1>,
{
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<G0, G1>,
EM: EdgeMatcher<G0, G1>,
{
type Item = Vec<usize>;

fn next(&mut self) -> Option<Self::Item> {
isomorphisms(
&mut self.st,
self.node_match,
self.edge_match,
self.match_subgraph,
&mut self.stack,
)
}
None
}
}

Expand Down Expand Up @@ -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<impl Iterator<Item = Vec<usize>> + 'a>
where
G0: 'a
+ NodeCompactIndexable
+ EdgeCount
+ DataMap
+ GetAdjacencyMatrix
+ GraphProp
+ IntoEdgesDirected,
G1: 'a
+ NodeCompactIndexable
+ EdgeCount
+ DataMap
+ GetAdjacencyMatrix
+ GraphProp<EdgeType = G0::EdgeType>
+ 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,
))
}
1 change: 1 addition & 0 deletions src/algo/mod.rs
Expand Up @@ -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};
Expand Down
50 changes: 49 additions & 1 deletion tests/iso.rs
@@ -1,13 +1,16 @@
extern crate petgraph;

use std::collections::HashSet;
use std::fs::File;
use std::io::prelude::*;

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
///
Expand Down Expand Up @@ -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<usize>> = 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
Expand Down

0 comments on commit 0c1515d

Please sign in to comment.