petgraph/
isomorphism.rs

1use std::marker;
2use fixedbitset::FixedBitSet;
3
4use super::{
5    EdgeType,
6    Incoming,
7};
8use super::graph::{
9    Graph,
10    IndexType,
11    NodeIndex,
12};
13
14use super::visit::GetAdjacencyMatrix;
15
16#[derive(Debug)]
17struct Vf2State<Ty, Ix> {
18    /// The current mapping M(s) of nodes from G0 → G1 and G1 → G0,
19    /// NodeIndex::end() for no mapping.
20    mapping: Vec<NodeIndex<Ix>>,
21    /// out[i] is non-zero if i is in either M_0(s) or Tout_0(s)
22    /// These are all the next vertices that are not mapped yet, but
23    /// have an outgoing edge from the mapping.
24    out: Vec<usize>,
25    /// ins[i] is non-zero if i is in either M_0(s) or Tin_0(s)
26    /// These are all the incoming vertices, those not mapped yet, but
27    /// have an edge from them into the mapping.
28    /// Unused if graph is undirected -- it's identical with out in that case.
29    ins: Vec<usize>,
30    out_size: usize,
31    ins_size: usize,
32    adjacency_matrix: FixedBitSet,
33    generation: usize,
34    _etype: marker::PhantomData<Ty>,
35}
36
37impl<Ty, Ix> Vf2State<Ty, Ix>
38    where Ty: EdgeType,
39          Ix: IndexType,
40{
41    pub fn new<N, E>(g: &Graph<N, E, Ty, Ix>) -> Self {
42        let c0 = g.node_count();
43        let mut state = Vf2State {
44            mapping: Vec::with_capacity(c0),
45            out: Vec::with_capacity(c0),
46            ins: Vec::with_capacity(c0 * (g.is_directed() as usize)),
47            out_size: 0,
48            ins_size: 0,
49            adjacency_matrix: g.adjacency_matrix(),
50            generation: 0,
51            _etype: marker::PhantomData,
52        };
53        for _ in 0..c0 {
54            state.mapping.push(NodeIndex::end());
55            state.out.push(0);
56            if Ty::is_directed() {
57                state.ins.push(0);
58            }
59        }
60        state
61    }
62
63    /// Return **true** if we have a complete mapping
64    pub fn is_complete(&self) -> bool {
65        self.generation == self.mapping.len()
66    }
67
68    /// Add mapping **from** <-> **to** to the state.
69    pub fn push_mapping<N, E>(&mut self, from: NodeIndex<Ix>, to: NodeIndex<Ix>,
70                              g: &Graph<N, E, Ty, Ix>)
71    {
72        self.generation += 1;
73        let s = self.generation;
74        self.mapping[from.index()] = to;
75        // update T0 & T1 ins/outs
76        // T0out: Node in G0 not in M0 but successor of a node in M0.
77        // st.out[0]: Node either in M0 or successor of M0
78        for ix in g.neighbors(from) {
79            if self.out[ix.index()] == 0 {
80                self.out[ix.index()] = s;
81                self.out_size += 1;
82            }
83        }
84        if g.is_directed() {
85            for ix in g.neighbors_directed(from, Incoming) {
86                if self.ins[ix.index()] == 0 {
87                    self.ins[ix.index()] = s;
88                    self.ins_size += 1;
89                }
90            }
91        }
92    }
93
94    /// Restore the state to before the last added mapping
95    pub fn pop_mapping<N, E>(&mut self, from: NodeIndex<Ix>,
96                             g: &Graph<N, E, Ty, Ix>)
97    {
98        let s = self.generation;
99        self.generation -= 1;
100
101        // undo (n, m) mapping
102        self.mapping[from.index()] = NodeIndex::end();
103
104        // unmark in ins and outs
105        for ix in g.neighbors(from) {
106            if self.out[ix.index()] == s {
107                self.out[ix.index()] = 0;
108                self.out_size -= 1;
109            }
110        }
111        if g.is_directed() {
112            for ix in g.neighbors_directed(from, Incoming) {
113                if self.ins[ix.index()] == s {
114                    self.ins[ix.index()] = 0;
115                    self.ins_size -= 1;
116                }
117            }
118        }
119    }
120
121    /// Find the next (least) node in the Tout set.
122    pub fn next_out_index(&self, from_index: usize) -> Option<usize>
123    {
124        self.out[from_index..].iter()
125                    .enumerate()
126                    .find(move |&(index, elt)| *elt > 0 &&
127                          self.mapping[from_index + index] == NodeIndex::end())
128                    .map(|(index, _)| index)
129    }
130
131    /// Find the next (least) node in the Tin set.
132    pub fn next_in_index(&self, from_index: usize) -> Option<usize>
133    {
134        if !Ty::is_directed() {
135            return None
136        }
137        self.ins[from_index..].iter()
138                    .enumerate()
139                    .find(move |&(index, elt)| *elt > 0
140                          && self.mapping[from_index + index] == NodeIndex::end())
141                    .map(|(index, _)| index)
142    }
143
144    /// Find the next (least) node in the N - M set.
145    pub fn next_rest_index(&self, from_index: usize) -> Option<usize>
146    {
147        self.mapping[from_index..].iter()
148               .enumerate()
149               .find(|&(_, elt)| *elt == NodeIndex::end())
150               .map(|(index, _)| index)
151    }
152}
153
154
155/// [Graph] Return `true` if the graphs `g0` and `g1` are isomorphic.
156///
157/// Using the VF2 algorithm, only matching graph syntactically (graph
158/// structure).
159///
160/// The graphs should not be multigraphs.
161///
162/// **Reference**
163///
164/// * Luigi P. Cordella, Pasquale Foggia, Carlo Sansone, Mario Vento;
165///   *A (Sub)Graph Isomorphism Algorithm for Matching Large Graphs*
166pub fn is_isomorphic<N, E, Ty, Ix>(g0: &Graph<N, E, Ty, Ix>,
167                                   g1: &Graph<N, E, Ty, Ix>) -> bool
168    where Ty: EdgeType,
169          Ix: IndexType,
170{
171    if g0.node_count() != g1.node_count() || g0.edge_count() != g1.edge_count() {
172        return false
173    }
174
175    let mut st = [Vf2State::new(g0), Vf2State::new(g1)];
176    try_match(&mut st, g0, g1, &mut NoSemanticMatch, &mut NoSemanticMatch).unwrap_or(false)
177}
178
179/// [Graph] Return `true` if the graphs `g0` and `g1` are isomorphic.
180///
181/// Using the VF2 algorithm, examining both syntactic and semantic
182/// graph isomorphism (graph structure and matching node and edge weights).
183///
184/// The graphs should not be multigraphs.
185pub fn is_isomorphic_matching<N, E, Ty, Ix, F, G>(g0: &Graph<N, E, Ty, Ix>,
186                                                  g1: &Graph<N, E, Ty, Ix>,
187                                                  mut node_match: F,
188                                                  mut edge_match: G) -> bool
189    where Ty: EdgeType,
190          Ix: IndexType,
191          F: FnMut(&N, &N) -> bool,
192          G: FnMut(&E, &E) -> bool,
193{
194    if g0.node_count() != g1.node_count() || g0.edge_count() != g1.edge_count() {
195        return false
196    }
197
198    let mut st = [Vf2State::new(g0), Vf2State::new(g1)];
199    try_match(&mut st, g0, g1, &mut node_match, &mut edge_match).unwrap_or(false)
200}
201
202trait SemanticMatcher<T> {
203    fn enabled() -> bool;
204    fn eq(&mut self, &T, &T) -> bool;
205}
206
207struct NoSemanticMatch;
208
209impl<T> SemanticMatcher<T> for NoSemanticMatch {
210    #[inline]
211    fn enabled() -> bool { false }
212    #[inline]
213    fn eq(&mut self, _: &T, _: &T) -> bool { true }
214}
215
216impl<T, F> SemanticMatcher<T> for F where F: FnMut(&T, &T) -> bool {
217    #[inline]
218    fn enabled() -> bool { true }
219    #[inline]
220    fn eq(&mut self, a: &T, b: &T) -> bool { self(a, b) }
221}
222
223/// Return Some(bool) if isomorphism is decided, else None.
224fn try_match<N, E, Ty, Ix, F, G>(st: &mut [Vf2State<Ty, Ix>; 2],
225                                 g0: &Graph<N, E, Ty, Ix>,
226                                 g1: &Graph<N, E, Ty, Ix>,
227                                 node_match: &mut F,
228                                 edge_match: &mut G)
229    -> Option<bool>
230    where Ty: EdgeType,
231          Ix: IndexType,
232          F: SemanticMatcher<N>,
233          G: SemanticMatcher<E>,
234{
235    let g = [g0, g1];
236    let graph_indices = 0..2;
237    let end = NodeIndex::end();
238
239    // if all are mapped -- we are done and have an iso
240    if st[0].is_complete() {
241        return Some(true)
242    }
243
244    // A "depth first" search of a valid mapping from graph 1 to graph 2
245
246    // F(s, n, m) -- evaluate state s and add mapping n <-> m
247
248    // Find least T1out node (in st.out[1] but not in M[1])
249    #[derive(Copy, Clone, PartialEq, Debug)]
250    enum OpenList {
251        Out,
252        In,
253        Other,
254    }
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);
261
262    if to_index.is_some() {
263        from_index = st[0].next_out_index(0);
264        open_list = OpenList::Out;
265    }
266
267    // Try the in list
268    if to_index.is_none() || from_index.is_none() {
269        to_index = st[1].next_in_index(0);
270
271        if to_index.is_some() {
272            from_index = st[0].next_in_index(0);
273            open_list = OpenList::In;
274        }
275    }
276
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;
283        }
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,
290    };
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);
311        }
312        first = false;
313
314        let nodes = [nx, mx];
315
316        // Check syntactic feasibility of mapping by ensuring adjacencies
317        // of nx map to adjacencies of mx.
318        //
319        // nx == map to => mx
320        //
321        // R_succ
322        //
323        // Check that every neighbor of nx is mapped to a neighbor of mx,
324        // then check the reverse, from mx to nx. Check that they have the same
325        // count of edges.
326        //
327        // Note: We want to check the lookahead measures here if we can,
328        // R_out: Equal for G0, G1: Card(Succ(G, n) ^ Tout); for both Succ and Pred
329        // R_in: Same with Tin
330        // R_new: Equal for G0, G1: Ñ n Pred(G, n); both Succ and Pred,
331        //      Ñ is G0 - M - Tin - Tout
332        // last attempt to add these did not speed up any of the testcases
333        let mut succ_count = [0, 0];
334        for j in graph_indices.clone() {
335            for n_neigh in g[j].neighbors(nodes[j]) {
336                succ_count[j] += 1;
337                // handle the self loop case; it's not in the mapping (yet)
338                let m_neigh = if nodes[j] != n_neigh {
339                    st[j].mapping[n_neigh.index()]
340                } else {
341                    nodes[1 - j]
342                };
343                if m_neigh == end {
344                    continue;
345                }
346                let has_edge = g[1-j].is_adjacent(&st[1-j].adjacency_matrix, nodes[1-j], m_neigh);
347                if !has_edge {
348                    continue 'candidates;
349                }
350            }
351        }
352        if succ_count[0] != succ_count[1] {
353            continue 'candidates;
354        }
355
356        // R_pred
357        if g[0].is_directed() {
358            let mut pred_count = [0, 0];
359            for j in graph_indices.clone() {
360                for n_neigh in g[j].neighbors_directed(nodes[j], Incoming) {
361                    pred_count[j] += 1;
362                    // the self loop case is handled in outgoing
363                    let m_neigh = st[j].mapping[n_neigh.index()];
364                    if m_neigh == end {
365                        continue;
366                    }
367                    let has_edge = g[1-j].is_adjacent(&st[1-j].adjacency_matrix, m_neigh, nodes[1-j]);
368                    if !has_edge {
369                        continue 'candidates;
370                    }
371                }
372            }
373            if pred_count[0] != pred_count[1] {
374                continue 'candidates;
375            }
376        }
377
378        // semantic feasibility: compare associated data for nodes
379        if F::enabled() && !node_match.eq(&g[0][nodes[0]], &g[1][nodes[1]]) {
380            continue 'candidates;
381        }
382
383        // semantic feasibility: compare associated data for edges
384        if G::enabled() {
385            // outgoing edges
386            for j in graph_indices.clone() {
387                let mut edges = g[j].neighbors(nodes[j]).detach();
388                while let Some((n_edge, n_neigh)) = edges.next(g[j]) {
389                    // handle the self loop case; it's not in the mapping (yet)
390                    let m_neigh = if nodes[j] != n_neigh {
391                        st[j].mapping[n_neigh.index()]
392                    } else {
393                        nodes[1 - j]
394                    };
395                    if m_neigh == end {
396                        continue;
397                    }
398                    match g[1-j].find_edge(nodes[1 - j], m_neigh) {
399                        Some(m_edge) => {
400                            if !edge_match.eq(&g[j][n_edge], &g[1-j][m_edge]) {
401                                continue 'candidates;
402                            }
403                        }
404                        None => unreachable!() // covered by syntactic check
405                    }
406                }
407            }
408
409            // incoming edges
410            if g[0].is_directed() {
411                for j in graph_indices.clone() {
412                    let mut edges = g[j].neighbors_directed(nodes[j], Incoming).detach();
413                    while let Some((n_edge, n_neigh)) = edges.next(g[j]) {
414                        // the self loop case is handled in outgoing
415                        let m_neigh = st[j].mapping[n_neigh.index()];
416                        if m_neigh == end {
417                            continue;
418                        }
419                        match g[1-j].find_edge(m_neigh, nodes[1-j]) {
420                            Some(m_edge) => {
421                                if !edge_match.eq(&g[j][n_edge], &g[1-j][m_edge]) {
422                                    continue 'candidates;
423                                }
424                            }
425                            None => unreachable!() // covered by syntactic check
426                        }
427                    }
428                }
429            }
430        }
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,
446            }
447        }
448
449        // Restore state.
450        for j in graph_indices.clone() {
451            st[j].pop_mapping(nodes[j], g[j]);
452        }
453    }
454    None
455}
456