Skip to content

Commit a248e8f

Browse files
authored
Merge pull request petgraph#213 from jrraymond/optimize-is_isomorphic
Optimize is isomorphic
2 parents 476331e + 93e7c12 commit a248e8f

File tree

6 files changed

+2351
-89
lines changed

6 files changed

+2351
-89
lines changed

src/isomorphism.rs

Lines changed: 125 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ impl<T, F> SemanticMatcher<T> for F where F: FnMut(&T, &T) -> bool {
221221
}
222222

223223
/// Return Some(bool) if isomorphism is decided, else None.
224-
fn try_match<N, E, Ty, Ix, F, G>(st: &mut [Vf2State<Ty, Ix>; 2],
224+
fn try_match<N, E, Ty, Ix, F, G>(mut st: &mut [Vf2State<Ty, Ix>; 2],
225225
g0: &Graph<N, E, Ty, Ix>,
226226
g1: &Graph<N, E, Ty, Ix>,
227227
node_match: &mut F,
@@ -232,15 +232,13 @@ fn try_match<N, E, Ty, Ix, F, G>(st: &mut [Vf2State<Ty, Ix>; 2],
232232
F: SemanticMatcher<N>,
233233
G: SemanticMatcher<E>,
234234
{
235+
if st[0].is_complete() {
236+
return Some(true);
237+
}
235238
let g = [g0, g1];
236239
let graph_indices = 0..2;
237240
let end = NodeIndex::end();
238241

239-
// if all are mapped -- we are done and have an iso
240-
if st[0].is_complete() {
241-
return Some(true)
242-
}
243-
244242
// A "depth first" search of a valid mapping from graph 1 to graph 2
245243

246244
// F(s, n, m) -- evaluate state s and add mapping n <-> m
@@ -252,67 +250,81 @@ fn try_match<N, E, Ty, Ix, F, G>(st: &mut [Vf2State<Ty, Ix>; 2],
252250
In,
253251
Other,
254252
}
255-
let mut open_list = OpenList::Out;
256-
257-
let mut to_index;
258-
let mut from_index = None;
259-
// Try the out list
260-
to_index = st[1].next_out_index(0);
261253

262-
if to_index.is_some() {
263-
from_index = st[0].next_out_index(0);
264-
open_list = OpenList::Out;
254+
#[derive(Clone, PartialEq, Debug)]
255+
enum Frame<N: marker::Copy> {
256+
Outer,
257+
Inner{ nodes: [N; 2], open_list: OpenList },
258+
Unwind{ nodes: [N; 2], open_list: OpenList },
265259
}
266260

267-
// Try the in list
268-
if to_index.is_none() || from_index.is_none() {
269-
to_index = st[1].next_in_index(0);
261+
let next_candidate = |st: &mut[Vf2State<Ty, Ix>; 2]|
262+
-> Option<(NodeIndex<Ix>, NodeIndex<Ix>, OpenList)> {
263+
let mut to_index;
264+
let mut from_index = None;
265+
let mut open_list = OpenList::Out;
266+
// Try the out list
267+
to_index = st[1].next_out_index(0);
270268

271269
if to_index.is_some() {
272-
from_index = st[0].next_in_index(0);
273-
open_list = OpenList::In;
270+
from_index = st[0].next_out_index(0);
271+
open_list = OpenList::Out;
274272
}
275-
}
273+
// Try the in list
274+
if to_index.is_none() || from_index.is_none() {
275+
to_index = st[1].next_in_index(0);
276276

277-
// Try the other list -- disconnected graph
278-
if to_index.is_none() || from_index.is_none() {
279-
to_index = st[1].next_rest_index(0);
280-
if to_index.is_some() {
281-
from_index = st[0].next_rest_index(0);
282-
open_list = OpenList::Other;
277+
if to_index.is_some() {
278+
from_index = st[0].next_in_index(0);
279+
open_list = OpenList::In;
280+
}
281+
}
282+
// Try the other list -- disconnected graph
283+
if to_index.is_none() || from_index.is_none() {
284+
to_index = st[1].next_rest_index(0);
285+
if to_index.is_some() {
286+
from_index = st[0].next_rest_index(0);
287+
open_list = OpenList::Other;
288+
}
289+
}
290+
match (from_index, to_index) {
291+
(Some(n), Some(m)) => Some((NodeIndex::new(n), NodeIndex::new(m), open_list)),
292+
// No more candidates
293+
_ => None,
283294
}
284-
}
285-
286-
let (cand0, cand1) = match (from_index, to_index) {
287-
(Some(n), Some(m)) => (n, m),
288-
// No more candidates
289-
_ => return None,
290295
};
291-
292-
let mut nx = NodeIndex::new(cand0);
293-
let mx = NodeIndex::new(cand1);
294-
295-
let mut first = true;
296-
297-
'candidates: loop {
298-
if !first {
299-
// Find the next node index to try on the `from` side of the mapping
300-
let start = nx.index() + 1;
301-
let cand0 = match open_list {
302-
OpenList::Out => st[0].next_out_index(start),
303-
OpenList::In => st[0].next_in_index(start),
304-
OpenList::Other => st[0].next_rest_index(start),
305-
}.map(|c| c + start); // compensate for start offset.
306-
nx = match cand0 {
307-
None => break, // no more candidates
308-
Some(ix) => NodeIndex::new(ix),
309-
};
310-
debug_assert!(nx.index() >= start);
296+
let next_from_ix = |st: &mut[Vf2State<Ty, Ix>; 2], nx: NodeIndex<Ix>, open_list: OpenList| -> Option<NodeIndex<Ix>> {
297+
// Find the next node index to try on the `from` side of the mapping
298+
let start = nx.index() + 1;
299+
let cand0 = match open_list {
300+
OpenList::Out => st[0].next_out_index(start),
301+
OpenList::In => st[0].next_in_index(start),
302+
OpenList::Other => st[0].next_rest_index(start),
303+
}.map(|c| c + start); // compensate for start offset.
304+
match cand0 {
305+
None => None, // no more candidates
306+
Some(ix) => {
307+
debug_assert!(ix >= start);
308+
Some(NodeIndex::new(ix))
309+
},
311310
}
312-
first = false;
313-
314-
let nodes = [nx, mx];
315-
311+
};
312+
//fn pop_state(nodes: [NodeIndex<Ix>; 2]) {
313+
let pop_state = |st: &mut[Vf2State<Ty, Ix>; 2], nodes: [NodeIndex<Ix>; 2]| {
314+
// Restore state.
315+
for j in graph_indices.clone() {
316+
st[j].pop_mapping(nodes[j], g[j]);
317+
}
318+
};
319+
//fn push_state(nodes: [NodeIndex<Ix>; 2]) {
320+
let push_state = |st: &mut[Vf2State<Ty, Ix>; 2],nodes: [NodeIndex<Ix>; 2]| {
321+
// Add mapping nx <-> mx to the state
322+
for j in graph_indices.clone() {
323+
st[j].push_mapping(nodes[j], nodes[1-j], g[j]);
324+
}
325+
};
326+
//fn is_feasible(nodes: [NodeIndex<Ix>; 2]) -> bool {
327+
let mut is_feasible = |st: &mut[Vf2State<Ty, Ix>; 2], nodes: [NodeIndex<Ix>; 2]| -> bool {
316328
// Check syntactic feasibility of mapping by ensuring adjacencies
317329
// of nx map to adjacencies of mx.
318330
//
@@ -345,14 +357,13 @@ fn try_match<N, E, Ty, Ix, F, G>(st: &mut [Vf2State<Ty, Ix>; 2],
345357
}
346358
let has_edge = g[1-j].is_adjacent(&st[1-j].adjacency_matrix, nodes[1-j], m_neigh);
347359
if !has_edge {
348-
continue 'candidates;
360+
return false;
349361
}
350362
}
351363
}
352364
if succ_count[0] != succ_count[1] {
353-
continue 'candidates;
365+
return false;
354366
}
355-
356367
// R_pred
357368
if g[0].is_directed() {
358369
let mut pred_count = [0, 0];
@@ -366,20 +377,18 @@ fn try_match<N, E, Ty, Ix, F, G>(st: &mut [Vf2State<Ty, Ix>; 2],
366377
}
367378
let has_edge = g[1-j].is_adjacent(&st[1-j].adjacency_matrix, m_neigh, nodes[1-j]);
368379
if !has_edge {
369-
continue 'candidates;
380+
return false;
370381
}
371382
}
372383
}
373384
if pred_count[0] != pred_count[1] {
374-
continue 'candidates;
385+
return false;
375386
}
376387
}
377-
378388
// semantic feasibility: compare associated data for nodes
379389
if F::enabled() && !node_match.eq(&g[0][nodes[0]], &g[1][nodes[1]]) {
380-
continue 'candidates;
390+
return false;
381391
}
382-
383392
// semantic feasibility: compare associated data for edges
384393
if G::enabled() {
385394
// outgoing edges
@@ -398,14 +407,13 @@ fn try_match<N, E, Ty, Ix, F, G>(st: &mut [Vf2State<Ty, Ix>; 2],
398407
match g[1-j].find_edge(nodes[1 - j], m_neigh) {
399408
Some(m_edge) => {
400409
if !edge_match.eq(&g[j][n_edge], &g[1-j][m_edge]) {
401-
continue 'candidates;
410+
return false;
402411
}
403412
}
404413
None => unreachable!() // covered by syntactic check
405414
}
406415
}
407416
}
408-
409417
// incoming edges
410418
if g[0].is_directed() {
411419
for j in graph_indices.clone() {
@@ -419,7 +427,7 @@ fn try_match<N, E, Ty, Ix, F, G>(st: &mut [Vf2State<Ty, Ix>; 2],
419427
match g[1-j].find_edge(m_neigh, nodes[1-j]) {
420428
Some(m_edge) => {
421429
if !edge_match.eq(&g[j][n_edge], &g[1-j][m_edge]) {
422-
continue 'candidates;
430+
return false;
423431
}
424432
}
425433
None => unreachable!() // covered by syntactic check
@@ -428,29 +436,57 @@ fn try_match<N, E, Ty, Ix, F, G>(st: &mut [Vf2State<Ty, Ix>; 2],
428436
}
429437
}
430438
}
431-
432-
// Add mapping nx <-> mx to the state
433-
for j in graph_indices.clone() {
434-
st[j].push_mapping(nodes[j], nodes[1-j], g[j]);
435-
}
436-
437-
// Check cardinalities of Tin, Tout sets
438-
if st[0].out_size == st[1].out_size &&
439-
st[0].ins_size == st[1].ins_size
440-
{
441-
442-
// Recurse
443-
match try_match(st, g0, g1, node_match, edge_match) {
444-
None => {}
445-
result => return result,
439+
true
440+
};
441+
let mut stack: Vec<Frame<NodeIndex<Ix>>> = vec![Frame::Outer];
442+
443+
while let Some(frame) = stack.pop() {
444+
match frame {
445+
Frame::Unwind{nodes, open_list: ol} => {
446+
pop_state(&mut st, nodes);
447+
448+
match next_from_ix(&mut st, nodes[0], ol) {
449+
None => continue,
450+
Some(nx) => {
451+
let f = Frame::Inner{nodes: [nx, nodes[1]], open_list: ol};
452+
stack.push(f);
453+
}
454+
}
455+
}
456+
Frame::Outer => {
457+
match next_candidate(&mut st) {
458+
None => continue,
459+
Some((nx, mx, ol)) => {
460+
let f = Frame::Inner{nodes: [nx, mx], open_list: ol};
461+
stack.push(f);
462+
}
463+
}
464+
}
465+
Frame::Inner{nodes, open_list: ol} => {
466+
if is_feasible(&mut st, nodes) {
467+
push_state(&mut st, nodes);
468+
if st[0].is_complete() {
469+
return Some(true);
470+
}
471+
// Check cardinalities of Tin, Tout sets
472+
if st[0].out_size == st[1].out_size &&
473+
st[0].ins_size == st[1].ins_size {
474+
let f0 = Frame::Unwind{nodes: nodes, open_list: ol};
475+
stack.push(f0);
476+
stack.push(Frame::Outer);
477+
continue;
478+
}
479+
pop_state(&mut st, nodes);
480+
}
481+
match next_from_ix(&mut st, nodes[0], ol) {
482+
None => continue,
483+
Some(nx) => {
484+
let f = Frame::Inner{nodes: [nx, nodes[1]], open_list: ol};
485+
stack.push(f);
486+
}
487+
}
446488
}
447-
}
448-
449-
// Restore state.
450-
for j in graph_indices.clone() {
451-
st[j].pop_mapping(nodes[j], g[j]);
452489
}
453490
}
454491
None
455492
}
456-

tests/iso.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
extern crate petgraph;
22

3+
use std::fs::File;
4+
use std::io::prelude::*;
5+
36
use petgraph::prelude::*;
47
use petgraph::{
58
EdgeType,
@@ -237,6 +240,15 @@ fn str_to_digraph(s: &str) -> Graph<(), (), Directed> {
237240
parse_graph(s)
238241
}
239242

243+
/// Parse a file in adjacency matrix format into a directed graph
244+
fn graph_from_file(path: &str) -> Graph<(), (), Directed>
245+
{
246+
let mut f = File::open(path).expect("file not found");
247+
let mut contents = String::new();
248+
f.read_to_string(&mut contents).expect("failed to read from file");
249+
parse_graph(&contents)
250+
}
251+
240252
/*
241253
fn graph_to_ad_matrix<N, E, Ty: EdgeType>(g: &Graph<N,E,Ty>)
242254
{
@@ -470,6 +482,20 @@ fn iso_matching() {
470482
assert!(!is_isomorphic_matching(&g0, &g2, |x, y| x == y, |x, y| x == y));
471483
}
472484

485+
#[test]
486+
fn iso_100n_100e() {
487+
let g0 = graph_from_file("tests/res/graph_100n_100e.txt");
488+
let g1 = graph_from_file("tests/res/graph_100n_100e_iso.txt");
489+
assert!(petgraph::algo::is_isomorphic(&g0, &g1));
490+
}
491+
492+
#[test]
493+
fn iso_large() {
494+
let g0 = graph_from_file("tests/res/graph_1000n_1000e.txt");
495+
let g1 = graph_from_file("tests/res/graph_1000n_1000e_iso.txt");
496+
assert!(petgraph::algo::is_isomorphic(&g0, &g1));
497+
}
498+
473499
// isomorphism isn't correct for multigraphs.
474500
// Keep this testcase to document how
475501
#[should_panic]

0 commit comments

Comments
 (0)