• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1\documentclass[11pt]{report}
2
3%\input{defs}
4\usepackage{math}
5\usepackage{jweb}
6\usepackage{lgrind}
7\usepackage{times}
8\usepackage{fullpage}
9\usepackage{graphicx}
10
11\newif\ifpdf
12\ifx\pdfoutput\undefined
13   \pdffalse
14\else
15   \pdfoutput=1
16   \pdftrue
17\fi
18
19\ifpdf
20  \usepackage[
21              pdftex,
22              colorlinks=true, %change to true for the electronic version
23              linkcolor=blue,filecolor=blue,pagecolor=blue,urlcolor=blue
24              ]{hyperref}
25\fi
26
27\ifpdf
28  \newcommand{\stlconcept}[1]{\href{https://boost.org/sgi/stl/#1.html}{{\small \textsf{#1}}}}
29  \newcommand{\bglconcept}[1]{\href{http://www.boost.org/libs/graph/doc/#1.html}{{\small \textsf{#1}}}}
30  \newcommand{\pmconcept}[1]{\href{http://www.boost.org/libs/property_map/#1.html}{{\small \textsf{#1}}}}
31  \newcommand{\myhyperref}[2]{\hyperref[#1]{#2}}
32  \newcommand{\vizfig}[2]{\begin{figure}[htbp]\centerline{\includegraphics*{#1.pdf}}\caption{#2}\label{fig:#1}\end{figure}}
33\else
34  \newcommand{\myhyperref}[2]{#2}
35  \newcommand{\bglconcept}[1]{{\small \textsf{#1}}}
36  \newcommand{\pmconcept}[1]{{\small \textsf{#1}}}
37  \newcommand{\stlconcept}[1]{{\small \textsf{#1}}}
38  \newcommand{\vizfig}[2]{\begin{figure}[htbp]\centerline{\includegraphics*{#1.eps}}\caption{#2}\label{fig:#1}\end{figure}}
39\fi
40
41\newcommand{\code}[1]{{\small{\em \textbf{#1}}}}
42
43
44\newcommand{\isomorphic}{\cong}
45
46\begin{document}
47
48\title{An Implementation of Isomorphism Testing}
49\author{Jeremy G. Siek}
50
51\maketitle
52
53\section{Introduction}
54
55This paper documents the implementation of the \code{isomorphism()}
56function of the Boost Graph Library.  The implementation was by Jeremy
57Siek with algorithmic improvements and test code from Douglas Gregor
58and Brian Osman.  The \code{isomorphism()} function answers the
59question, ``are these two graphs equal?''  By \emph{equal} we mean
60the two graphs have the same structure---the vertices and edges are
61connected in the same way. The mathematical name for this kind of
62equality is \emph{isomorphism}.
63
64More precisely, an \emph{isomorphism} is a one-to-one mapping of the
65vertices in one graph to the vertices of another graph such that
66adjacency is preserved. Another words, given graphs $G_{1} =
67(V_{1},E_{1})$ and $G_{2} = (V_{2},E_{2})$, an isomorphism is a
68function $f$ such that for all pairs of vertices $a,b$ in $V_{1}$,
69edge $(a,b)$ is in $E_{1}$ if and only if edge $(f(a),f(b))$ is in
70$E_{2}$.
71
72Both graphs must be the same size, so let $N = |V_1| = |V_2|$. The
73graph $G_1$ is \emph{isomorphic} to $G_2$ if an isomorphism exists
74between the two graphs, which we denote by $G_1 \isomorphic G_2$.
75
76In the following discussion we will need to use several notions from
77graph theory. The graph $G_s=(V_s,E_s)$ is a \emph{subgraph} of graph
78$G=(V,E)$ if $V_s \subseteq V$ and $E_s \subseteq E$.  An
79\emph{induced subgraph}, denoted by $G[V_s]$, of a graph $G=(V,E)$
80consists of the vertices in $V_s$, which is a subset of $V$, and every
81edge $(u,v)$ in $E$ such that both $u$ and $v$ are in $V_s$.  We use
82the notation $E[V_s]$ to mean the edges in $G[V_s]$.
83
84In some places we express a function as a set of pairs, so the set $f
85= \{ \pair{a_1}{b_1}, \ldots, \pair{a_n}{b_n} \}$
86means $f(a_i) = b_i$ for $i=1,\ldots,n$.
87
88\section{Exhaustive Backtracking Search}
89\label{sec:backtracking}
90
91The algorithm used by the \code{isomorphism()} function is, at
92first approximation, an exhaustive search implemented via
93backtracking.  The backtracking algorithm is a recursive function. At
94each stage we will try to extend the match that we have found so far.
95So suppose that we have already determined that some subgraph of $G_1$
96is isomorphic to a subgraph of $G_2$.  We then try to add a vertex to
97each subgraph such that the new subgraphs are still isomorphic to one
98another. At some point we may hit a dead end---there are no vertices
99that can be added to extend the isomorphic subgraphs. We then
100backtrack to previous smaller matching subgraphs, and try extending
101with a different vertex choice. The process ends by either finding a
102complete mapping between $G_1$ and $G_2$ and return true, or by
103exhausting all possibilities and returning false.
104
105We consider the vertices of $G_1$ for addition to the matched subgraph
106in a specific order, so assume that the vertices of $G_1$ are labeled
107$1,\ldots,N$ according to that order. As we will see later, a good
108ordering of the vertices is by DFS discover time.  Let $G_1[k]$ denote
109the subgraph of $G_1$ induced by the first $k$ vertices, with $G_1[0]$
110being an empty graph. We also consider the edges of $G_1$ in a
111specific order. We always examine edges in the current subgraph
112$G_1[k]$ first, that is, edges $(u,v)$ where both $u \leq k$ and $v
113\leq k$. This ordering of edges can be acheived by sorting the edges
114according to number of the larger of the source and target vertex.
115
116Each step of the backtracking search examines an edge $(u,v)$ of $G_1$
117and decides whether to continue or go back. There are three cases to
118consider:
119
120\begin{enumerate}
121
122\item $i \leq k \Land j \leq k$. Both $i$ and $j$ are in $G_1[k]$.  We
123check to make sure the $(f(i),f(j)) \in E_2[S]$.
124
125\item $i \leq k \Land j > k$. $i$ is in the matched subgraph $G_1[k]$,
126but $j$ is not. We are about to increment $k$ try to grow the matched
127subgraph to include $j$. However, first we need to finalize our check
128of the isomorphism between subgraphs $G_1[k]$ and $G_2[S]$.  At this
129point we are guaranteed to have seen all the edges to and from vertex $k$
130(because the edges are sorted), and in previous steps we have checked
131that for each edge incident on $k$ in $G_1[k]$ there is a matching
132edge in $G_2[S]$.  However we have not checked that for each edge
133incident on $f(k)$ in $E_2[S]$, there is a matching edge in $E_1[k]$
134(we need to check the ``only if'' part of the ``if and only if'').
135Therefore we scan through all the edges $(u,v)$ incident on $f(k)$ and
136make sure that $(f^{-1}(u),f^{-1}(v)) \in E_1[k]$. Once this check has
137been performed, we add $f(k)$ to $S$, we increment $k$ (so now $k=j$),
138and then try assigning the new $k$ to any of the eligible vertices in
139$V_2 - S$. More about what ``eligible'' means later.
140
141\item $i > k \Land j \leq k$. This case will not occur due to the DFS
142numbering of the vertices. There is an edge $(i,j)$ so $i$ must be
143less than $j$.
144
145\item $i > k \Land j > k$. Neither $i$ or $j$ is in the matched
146subgraph $G_1[k]$. This situation only happens at the very beginning
147of the search, or when $i$ and $j$ are not reachable from any of the
148vertices in $G_1[k]$. This means the smaller of $i$ and $j$ must be
149the root of a DFS tree. We assign $r$ to any of the eligible vertices
150in $V_2 - S$, and then proceed as if we were in Case 2.
151
152\end{enumerate}
153
154
155
156@d Match function
157@{
158bool match(edge_iter iter)
159{
160if (iter != ordered_edges.end()) {
161    ordered_edge edge = *iter;
162    size_type k_num = edge.k_num;
163    vertex1_t k = dfs_vertices[k_num];
164    vertex1_t u;
165    if (edge.source != -1) // might be a ficticious edge
166        u = dfs_vertices[edge.source];
167    vertex1_t v = dfs_vertices[edge.target];
168    if (edge.source == -1) { // root node
169        @<$v$ is a DFS tree root@>
170    } else if (f_assigned[v] == false) {
171        @<$v$ is an unmatched vertex, $(u,v)$ is a tree edge@>
172    } else {
173        @<Check to see if there is an edge in $G_2$ to match $(u,v)$@>
174    }
175} else
176    return true;
177return false;
178} // match()
179@}
180
181
182
183
184
185
186The basic idea will be to examine $G_1$ one edge at a time, trying to
187create a vertex mapping such that each edge matches one in $G_2$.  We
188are going to consider the edges of $G_1$ in a specific order, so we
189will label the edges $0,\ldots,|E_1|-1$.
190
191At each stage of the recursion we
192start with an isomorphism $f_{k-1}$ between $G_1[k-1]$ and a subgraph
193of $G_2$, which we denote by $G_2[S]$, so $G_1[k-1] \isomorphic
194G_2[S]$. The vertex set $S$ is the subset of $V_2$ that corresponds
195via $f_{k-1}$ to the first $k-1$ vertices in $G_1$.
196
197We also order the edges of $G_1$
198
199
200
201We try to extend the isomorphism by finding a vertex $v \in V_2 - S$
202that matches with vertex $k$. If a matching vertex is found, we have a
203new isomorphism $f_k$ with $G_1[k] \isomorphic G_2[S \union \{ v \}]$.
204
205
206
207
208\begin{tabbing}
209IS\=O\=M\=O\=RPH($k$, $S$, $f_{k-1}$) $\equiv$ \\
210\>\textbf{if} ($k = |V_1|+1$) \\
211\>\>\textbf{return} true \\
212\>\textbf{for} each vertex $v \in V_2 - S$ \\
213\>\>\textbf{if} (MATCH($k$, $v$)) \\
214\>\>\>$f_k = f_{k-1} \union \pair{k}{v}$ \\
215\>\>\>ISOMORPH($k+1$, $S \union \{ v \}$, $f_k$)\\
216\>\>\textbf{else}\\
217\>\>\>\textbf{return} false \\
218\\
219ISOMORPH($0$, $G_1$, $\emptyset$, $G_2$)
220\end{tabbing}
221
222The basic idea of the match operation is to check whether $G_1[k]$ is
223isomorphic to $G_2[S \union \{ v \}]$. We already know that $G_1[k-1]
224\isomorphic G_2[S]$ with the mapping $f_{k-1}$, so all we need to do
225is verify that the edges in $E_1[k] - E_1[k-1]$ connect vertices that
226correspond to the vertices connected by the edges in $E_2[S \union \{
227v \}] - E_2[S]$. The edges in $E_1[k] - E_1[k-1]$ are all the
228out-edges $(k,j)$ and in-edges $(j,k)$ of $k$ where $j$ is less than
229or equal to $k$ according to the ordering.  The edges in $E_2[S \union
230\{ v \}] - E_2[S]$ consists of all the out-edges $(v,u)$ and
231in-edges $(u,v)$ of $v$ where $u \in S$.
232
233\begin{tabbing}
234M\=ATCH($k$, $v$) $\equiv$ \\
235\>$out_k \leftarrow \forall (k,j) \in E_1[k] - E_1[k-1] \Big( (v,f(j)) \in E_2[S \union \{ v \}] - E_2[S] \Big)$ \\
236\>$in_k \leftarrow \forall (j,k) \in E_1[k] - E_1[k-1] \Big( (f(j),v) \in E_2[S \union \{ v \}] - E_2[S] \Big)$ \\
237\>$out_v \leftarrow \forall (v,u) \in E_2[S \union \{ v \}] - E_2[S] \Big( (k,f^{-1}(u)) \in E_1[k] - E_1[k-1] \Big)$ \\
238\>$in_v \leftarrow \forall (u,v) \in E_2[S \union \{ v \}] - E_2[S] \Big( (f^{-1}(u),k) \in E_1[k] - E_1[k-1] \Big)$ \\
239\>\textbf{return} $out_k \Land in_k \Land out_v \Land in_v$
240\end{tabbing}
241
242The problem with the exhaustive backtracking algorithm is that there
243are $N!$ possible vertex mappings, and $N!$ gets very large as $N$
244increases, so we need to prune the search space. We use the pruning
245techniques described in
246\cite{deo77:_new_algo_digraph_isomorph,fortin96:_isomorph,reingold77:_combin_algo}
247that originated in
248\cite{sussenguth65:_isomorphism,unger64:_isomorphism}.
249
250\section{Vertex Invariants}
251\label{sec:vertex-invariants}
252
253One way to reduce the search space is through the use of \emph{vertex
254invariants}. The idea is to compute a number for each vertex $i(v)$
255such that $i(v) = i(v')$ if there exists some isomorphism $f$ where
256$f(v) = v'$. Then when we look for a match to some vertex $v$, we only
257need to consider those vertices that have the same vertex invariant
258number. The number of vertices in a graph with the same vertex
259invariant number $i$ is called the \emph{invariant multiplicity} for
260$i$.  In this implementation, by default we use the out-degree of the
261vertex as the vertex invariant, though the user can also supply there
262own invariant function. The ability of the invariant function to prune
263the search space varies widely with the type of graph.
264
265As a first check to rule out graphs that have no possibility of
266matching, one can create a list of computed vertex invariant numbers
267for the vertices in each graph, sort the two lists, and then compare
268them.  If the two lists are different then the two graphs are not
269isomorphic.  If the two lists are the same then the two graphs may be
270isomorphic.
271
272Also, we extend the MATCH operation to use the vertex invariants to
273help rule out vertices.
274
275\section{Vertex Order}
276
277A good choice of the labeling for the vertices (which determines the
278order in which the subgraph $G_1[k]$ is grown) can also reduce the
279search space. In the following we discuss two labeling heuristics.
280
281\subsection{Most Constrained First}
282
283Consider the most constrained vertices first.  That is, examine
284lower-degree vertices before higher-degree vertices. This reduces the
285search space because it chops off a trunk before the trunk has a
286chance to blossom out. We can generalize this to use vertex
287invariants. We examine vertices with low invariant multiplicity
288before examining vertices with high invariant multiplicity.
289
290\subsection{Adjacent First}
291
292The MATCH operation only considers edges when the other vertex already
293has a mapping defined. This means that the MATCH operation can only
294weed out vertices that are adjacent to vertices that have already been
295matched. Therefore, when choosing the next vertex to examine, it is
296desirable to choose one that is adjacent a vertex already in $S_1$.
297
298\subsection{DFS Order, Starting with Lowest Multiplicity}
299
300For this implementation, we combine the above two heuristics in the
301following way. To implement the ``adjacent first'' heuristic we apply
302DFS to the graph, and use the DFS discovery order as our vertex
303order. To comply with the ``most constrained first'' heuristic we
304order the roots of our DFS trees by invariant multiplicity.
305
306
307
308\section{Implementation}
309
310The following is the public interface for the \code{isomorphism}
311function. The input to the function is the two graphs $G_1$ and $G_2$,
312mappings from the vertices in the graphs to integers (in the range
313$[0,|V|)$), and a vertex invariant function object. The output of the
314function is an isomorphism $f$ if there is one. The \code{isomorphism}
315function returns true if the graphs are isomorphic and false
316otherwise. The invariant parameters are function objects that compute
317the vertex invariants for vertices of the two graphs.  The
318\code{max\_invariant} parameter is to specify one past the largest
319integer that a vertex invariant number could be (the invariants
320numbers are assumed to span from zero to the number).  The
321requirements on type template parameters are described below in the
322``Concept checking'' part.
323
324
325@d Isomorphism function interface
326@{
327template <typename Graph1, typename Graph2, typename IsoMapping,
328          typename Invariant1, typename Invariant2,
329          typename IndexMap1, typename IndexMap2>
330bool isomorphism(const Graph1& G1, const Graph2& G2, IsoMapping f,
331                 Invariant1 invariant1, Invariant2 invariant2,
332                 std::size_t max_invariant,
333                 IndexMap1 index_map1, IndexMap2 index_map2)
334@}
335
336
337The function body consists of the concept checks followed by a quick
338check for empty graphs or graphs of different size and then construct
339an algorithm object. We then call the \code{test\_isomorphism} member
340function, which runs the algorithm.  The reason that we implement the
341algorithm using a class is that there are a fair number of internal
342data structures required, and it is easier to make these data members
343of a class and make each section of the algorithm a member
344function. This relieves us from the burden of passing lots of
345arguments to each function, while at the same time avoiding the evils
346of global variables (non-reentrant, etc.).
347
348
349@d Isomorphism function body
350@{
351{
352    @<Concept checking@>
353    @<Quick return based on size@>
354    detail::isomorphism_algo<Graph1, Graph2, IsoMapping, Invariant1, Invariant2,
355        IndexMap1, IndexMap2>
356        algo(G1, G2, f, invariant1, invariant2, max_invariant,
357             index_map1, index_map2);
358    return algo.test_isomorphism();
359}
360@}
361
362
363\noindent If there are no vertices in either graph, then they are
364trivially isomorphic. If the graphs have different numbers of vertices
365then they are not isomorphic.
366
367@d Quick return based on size
368@{
369if (num_vertices(G1) != num_vertices(G2))
370    return false;
371if (num_vertices(G1) == 0 && num_vertices(G2) == 0)
372    return true;
373@}
374
375We use the Boost Concept Checking Library to make sure that the type
376arguments to the function fulfill there requirements. The graph types
377must model the \bglconcept{VertexListGraph} and
378\bglconcept{AdjacencyGraph} concepts. The vertex invariants must model
379the \stlconcept{AdaptableUnaryFunction} concept, with a vertex as
380their argument and an integer return type.  The \code{IsoMapping} type
381that represents the isomorphism $f$ must be a
382\pmconcept{ReadWritePropertyMap} that maps from vertices in $G_1$ to
383vertices in $G_2$. The two other index maps are
384\pmconcept{ReadablePropertyMap}s from vertices in $G_1$ and $G_2$ to
385unsigned integers.
386
387
388@d Concept checking
389@{
390// Graph requirements
391BOOST_CONCEPT_ASSERT(( VertexListGraphConcept<Graph1> ));
392BOOST_CONCEPT_ASSERT(( EdgeListGraphConcept<Graph1> ));
393BOOST_CONCEPT_ASSERT(( VertexListGraphConcept<Graph2> ));
394BOOST_CONCEPT_ASSERT(( BidirectionalGraphConcept<Graph2> ));
395
396typedef typename graph_traits<Graph1>::vertex_descriptor vertex1_t;
397typedef typename graph_traits<Graph2>::vertex_descriptor vertex2_t;
398typedef typename graph_traits<Graph1>::vertices_size_type size_type;
399
400// Vertex invariant requirement
401BOOST_CONCEPT_ASSERT(( AdaptableUnaryFunctionConcept<Invariant1,
402  size_type, vertex1_t> ));
403BOOST_CONCEPT_ASSERT(( AdaptableUnaryFunctionConcept<Invariant2,
404  size_type, vertex2_t> ));
405
406// Property map requirements
407BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept<IsoMapping, vertex1_t> ));
408typedef typename property_traits<IsoMapping>::value_type IsoMappingValue;
409BOOST_STATIC_ASSERT((is_same<IsoMappingValue, vertex2_t>::value));
410
411BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept<IndexMap1, vertex1_t> ));
412typedef typename property_traits<IndexMap1>::value_type IndexMap1Value;
413BOOST_STATIC_ASSERT((is_convertible<IndexMap1Value, size_type>::value));
414
415BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept<IndexMap2, vertex2_t> ));
416typedef typename property_traits<IndexMap2>::value_type IndexMap2Value;
417BOOST_STATIC_ASSERT((is_convertible<IndexMap2Value, size_type>::value));
418@}
419
420The following is the outline of the isomorphism algorithm class.  The
421class is templated on all of the same parameters of the
422\code{isomorphism} function, and all of the parameter values are
423stored in the class as data members, in addition to the internal data
424structures.
425
426@d Isomorphism algorithm class
427@{
428template <typename Graph1, typename Graph2, typename IsoMapping,
429  typename Invariant1, typename Invariant2,
430  typename IndexMap1, typename IndexMap2>
431class isomorphism_algo
432{
433    @<Typedefs for commonly used types@>
434    @<Data members for the parameters@>
435    @<Ordered edge class@>
436    @<Internal data structures@>
437    friend struct compare_multiplicity;
438    @<Invariant multiplicity comparison functor@>
439    @<DFS visitor to record vertex and edge order@>
440public:
441    @<Isomorphism algorithm constructor@>
442    @<Test isomorphism member function@>
443private:
444    @<Match function@>
445};
446@}
447
448The interesting parts of this class are the \code{test\_isomorphism}
449function, and the \code{match} function. We focus on those in in the
450following sections, and mention the other parts of the class when
451needed (and a few are left to the appendix).
452
453The \code{test\_isomorphism} function does all of the setup required
454of the algorithm. This consists of sorting the vertices according to
455invariant multiplicity, and then by DFS order.  The edges are then
456sorted by the DFS order of vertices incident on the edges. More
457details about this to come. The last step of this function is to
458invoke the recursive \code{match} function which performs the
459backtracking search.
460
461
462@d Test isomorphism member function
463@{
464bool test_isomorphism()
465{
466    @<Quick return if the vertex invariants do not match up@>
467    @<Sort vertices according to invariant multiplicity@>
468    @<Order vertices and edges by DFS@>
469    @<Sort edges according to vertex DFS order@>
470
471    return this->match(ordered_edges.begin());
472}
473@}
474
475As discussed in \S\ref{sec:vertex-invariants}, we can quickly rule out
476the possibility of any isomorphism between two graphs by checking to
477see if the vertex invariants can match up. We sort both vectors of vertex
478invariants, and then check to see if they are equal.
479
480@d Quick return if the vertex invariants do not match up
481@{
482{
483    std::vector<invar1_value> invar1_array;
484    BGL_FORALL_VERTICES_T(v, G1, Graph1)
485        invar1_array.push_back(invariant1(v));
486    std::sort(invar1_array.begin(), invar1_array.end());
487
488    std::vector<invar2_value> invar2_array;
489    BGL_FORALL_VERTICES_T(v, G2, Graph2)
490        invar2_array.push_back(invariant2(v));
491    std::sort(invar2_array.begin(), invar2_array.end());
492
493    if (!std::equal(invar1_array.begin(), invar1_array.end(), invar2_array.begin()))
494        return false;
495}
496@}
497
498Next we compute the invariant multiplicity, the number of vertices
499with the same invariant number. The \code{invar\_mult} vector is
500indexed by invariant number. We loop through all the vertices in the
501graph to record the multiplicity. We then order the vertices by their
502invariant multiplicity.  This will allow us to search the more
503constrained vertices first.
504
505@d Sort vertices according to invariant multiplicity
506@{
507std::vector<vertex1_t> V_mult;
508BGL_FORALL_VERTICES_T(v, G1, Graph1)
509    V_mult.push_back(v);
510{
511    std::vector<size_type> multiplicity(max_invariant, 0);
512    BGL_FORALL_VERTICES_T(v, G1, Graph1)
513        ++multiplicity[invariant1(v)];
514
515    std::sort(V_mult.begin(), V_mult.end(), compare_multiplicity(*this, &multiplicity[0]));
516}
517@}
518
519\noindent The definition of the \code{compare\_multiplicity} predicate
520is shown below. This predicate provides the glue that binds
521\code{std::sort} to our current purpose.
522
523@d Invariant multiplicity comparison functor
524@{
525struct compare_multiplicity
526{
527    compare_multiplicity(isomorphism_algo& algo, size_type* multiplicity)
528        : algo(algo), multiplicity(multiplicity) { }
529    bool operator()(const vertex1_t& x, const vertex1_t& y) const {
530        return multiplicity[algo.invariant1(x)] < multiplicity[algo.invariant1(y)];
531    }
532    isomorphism_algo& algo;
533    size_type* multiplicity;
534};
535@}
536
537\subsection{Backtracking Search and Matching}
538
539
540
541
542
543
544\subsection{Ordering by DFS Discover Time}
545
546To implement the ``visit adjacent vertices first'' heuristic, we order
547the vertices according to DFS discover time. This will give us the
548order that the subgraph $G_1[k]$ will be expanded. As described in
549\S\ref{sec:backtracking}, when trying to match $k$ with some vertex
550$v$ in $V_2 - S$, we need to examine the edges in $E_1[k] -
551E_1[k-1]$. It would be nice if we had the edges of $G_1$ arranged so
552that when we are interested in vertex $k$, the edges in $E_1[k] -
553E_1[k-1]$ are easy to find. This can be achieved by creating an array
554of edges sorted by the DFS number of the larger of the source and
555target vertex. The following array of ordered edges corresponds
556to the graph in Figure~\ref{fig:edge-order}.
557
558\begin{tabular}{cccccccccc}
559      &0&1&2&3&4&5&6&7&8\\ \hline
560source&0&1&1&3&3&4&4&5&6\\
561target&1&2&3&1&2&3&5&6&4
562\end{tabular}
563
564The backtracking algorithm will scan through the edge array from left
565to right to extend isomorphic subgraphs, and move back to the right
566when a match fails. We will want to
567
568
569
570
571
572
573
574
575
576
577For example, suppose we have already matched the vertices
578\{0,1,2\}, and
579
580
581
582\vizfig{edge-order}{Vertices with DFS numbering. The DFS trees are the solid edges.}
583
584@c edge-order.dot
585@{
586digraph G {
587size="3,2"
588ratio=fill
589node[shape=circle]
5900 -> 1[style=bold]
5911 -> 2[style=bold]
5921 -> 3[style=bold]
5933 -> 1[style=dashed]
5943 -> 2[style=dashed]
5954 -> 3[style=dashed]
5964 -> 5[style=bold]
5975 -> 6[style=bold]
5986 -> 4[style=dashed]
599}
600@}
601
602
603
604
605We implement the outer-loop of the DFS here, instead of calling the
606\code{depth\_first\_search} function, because we want the roots of the
607DFS tree's to be ordered by invariant multiplicity. We call
608\code{depth\_\-first\_\-visit} to implement the recursive portion of
609the DFS. The \code{record\_dfs\_order} adapts the DFS to record the
610order in which DFS discovers the vertices, storing the results in in
611the \code{dfs\_vertices} and \code{ordered\_edges} arrays. We then
612create the \code{dfs\_number} array which provides a mapping from
613vertex to DFS number, and renumber the edges with the DFS numbers.
614
615@d Order vertices and edges by DFS
616@{
617std::vector<default_color_type> color_vec(num_vertices(G1));
618safe_iterator_property_map<std::vector<default_color_type>::iterator, IndexMap1>
619     color_map(color_vec.begin(), color_vec.size(), index_map1);
620record_dfs_order dfs_visitor(dfs_vertices, ordered_edges);
621typedef color_traits<default_color_type> Color;
622for (vertex_iter u = V_mult.begin(); u != V_mult.end(); ++u) {
623    if (color_map[*u] == Color::white()) {
624	dfs_visitor.start_vertex(*u, G1);
625	depth_first_visit(G1, *u, dfs_visitor, color_map);
626    }
627}
628// Create the dfs_number array and dfs_number_map
629dfs_number_vec.resize(num_vertices(G1));
630dfs_number = make_safe_iterator_property_map(dfs_number_vec.begin(),
631	                  dfs_number_vec.size(), index_map1);
632size_type n = 0;
633for (vertex_iter v = dfs_vertices.begin(); v != dfs_vertices.end(); ++v)
634    dfs_number[*v] = n++;
635
636// Renumber ordered_edges array according to DFS number
637for (edge_iter e = ordered_edges.begin(); e != ordered_edges.end(); ++e) {
638    if (e->source >= 0)
639      e->source = dfs_number_vec[e->source];
640    e->target = dfs_number_vec[e->target];
641}
642@}
643
644\noindent The definition of the \code{record\_dfs\_order} visitor
645class is as follows. EXPLAIN ficticious edges
646
647@d DFS visitor to record vertex and edge order
648@{
649struct record_dfs_order : default_dfs_visitor
650{
651    record_dfs_order(std::vector<vertex1_t>& v, std::vector<ordered_edge>& e)
652	: vertices(v), edges(e) { }
653
654    void start_vertex(vertex1_t v, const Graph1&) const {
655        edges.push_back(ordered_edge(-1, v));
656    }
657    void discover_vertex(vertex1_t v, const Graph1&) const {
658        vertices.push_back(v);
659    }
660    void examine_edge(edge1_t e, const Graph1& G1) const {
661        edges.push_back(ordered_edge(source(e, G1), target(e, G1)));
662    }
663    std::vector<vertex1_t>& vertices;
664    std::vector<ordered_edge>& edges;
665};
666@}
667
668
669Reorder the edges so that all edges belonging to $G_1[k]$
670appear before any edges not in $G_1[k]$, for $k=1,...,n$.
671
672The order field needs a better name. How about k?
673
674@d Sort edges according to vertex DFS order
675@{
676std::stable_sort(ordered_edges.begin(), ordered_edges.end());
677// Fill in i->k_num field
678if (!ordered_edges.empty()) {
679  ordered_edges[0].k_num = 0;
680  for (edge_iter i = next(ordered_edges.begin()); i != ordered_edges.end(); ++i)
681      i->k_num = std::max(prior(i)->source, prior(i)->target);
682}
683@}
684
685
686
687
688
689
690@d Typedefs for commonly used types
691@{
692typedef typename graph_traits<Graph1>::vertex_descriptor vertex1_t;
693typedef typename graph_traits<Graph2>::vertex_descriptor vertex2_t;
694typedef typename graph_traits<Graph1>::edge_descriptor edge1_t;
695typedef typename graph_traits<Graph1>::vertices_size_type size_type;
696typedef typename Invariant1::result_type invar1_value;
697typedef typename Invariant2::result_type invar2_value;
698@}
699
700@d Data members for the parameters
701@{
702const Graph1& G1;
703const Graph2& G2;
704IsoMapping f;
705Invariant1 invariant1;
706Invariant2 invariant2;
707std::size_t max_invariant;
708IndexMap1 index_map1;
709IndexMap2 index_map2;
710@}
711
712@d Internal data structures
713@{
714std::vector<vertex1_t> dfs_vertices;
715typedef std::vector<vertex1_t>::iterator vertex_iter;
716std::vector<size_type> dfs_number_vec;
717safe_iterator_property_map<typename std::vector<size_type>::iterator, IndexMap1>
718   dfs_number;
719std::vector<ordered_edge> ordered_edges;
720typedef std::vector<ordered_edge>::iterator edge_iter;
721
722std::vector<vertex1_t> f_inv_vec;
723safe_iterator_property_map<typename std::vector<vertex1_t>::iterator,
724    IndexMap2> f_inv;
725
726std::vector<char> f_assigned_vec;
727safe_iterator_property_map<typename std::vector<char>::iterator,
728    IndexMap1> f_assigned;
729
730std::vector<char> f_inv_assigned_vec;
731safe_iterator_property_map<typename std::vector<char>::iterator,
732    IndexMap2> f_inv_assigned;
733
734int num_edges_incident_on_k;
735@}
736
737@d Isomorphism algorithm constructor
738@{
739isomorphism_algo(const Graph1& G1, const Graph2& G2, IsoMapping f,
740		 Invariant1 invariant1, Invariant2 invariant2, std::size_t max_invariant,
741		 IndexMap1 index_map1, IndexMap2 index_map2)
742    : G1(G1), G2(G2), f(f), invariant1(invariant1), invariant2(invariant2),
743      max_invariant(max_invariant),
744      index_map1(index_map1), index_map2(index_map2)
745{
746    f_assigned_vec.resize(num_vertices(G1));
747    f_assigned = make_safe_iterator_property_map
748	(f_assigned_vec.begin(), f_assigned_vec.size(), index_map1);
749    f_inv_vec.resize(num_vertices(G1));
750    f_inv = make_safe_iterator_property_map
751	(f_inv_vec.begin(), f_inv_vec.size(), index_map2);
752
753    f_inv_assigned_vec.resize(num_vertices(G1));
754    f_inv_assigned = make_safe_iterator_property_map
755	(f_inv_assigned_vec.begin(), f_inv_assigned_vec.size(), index_map2);
756}
757@}
758
759
760
761
762@d Degree vertex invariant functor
763@{
764template <typename InDegreeMap, typename Graph>
765class degree_vertex_invariant
766{
767    typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
768    typedef typename graph_traits<Graph>::degree_size_type size_type;
769public:
770    typedef vertex_t argument_type;
771    typedef size_type result_type;
772
773    degree_vertex_invariant(const InDegreeMap& in_degree_map, const Graph& g)
774        : m_in_degree_map(in_degree_map), m_g(g) { }
775
776    size_type operator()(vertex_t v) const {
777        return (num_vertices(m_g) + 1) * out_degree(v, m_g)
778            + get(m_in_degree_map, v);
779    }
780    // The largest possible vertex invariant number
781    size_type max() const {
782        return num_vertices(m_g) * num_vertices(m_g) + num_vertices(m_g);
783    }
784private:
785    InDegreeMap m_in_degree_map;
786    const Graph& m_g;
787};
788@}
789
790
791
792ficticiuos edges for the DFS tree roots
793Use \code{ordered\_edge} instead of \code{edge1\_t} so that we can create ficticious
794edges for the DFS tree roots.
795
796@d Ordered edge class
797@{
798struct ordered_edge {
799    ordered_edge(int s, int t) : source(s), target(t) { }
800
801    bool operator<(const ordered_edge& e) const {
802        using namespace std;
803        int m1 = max(source, target);
804        int m2 = max(e.source, e.target);
805        // lexicographical comparison of (m1,source,target) and (m2,e.source,e.target)
806        return make_pair(m1, make_pair(source, target)) < make_pair(m2, make_pair(e.source, e.target));
807    }
808    int source;
809    int target;
810    int k_num;
811};
812@}
813
814
815
816
817
818
819\subsection{Recursive Match Function}
820
821
822
823
824
825@d $v$ is a DFS tree root
826@{
827// Try all possible mappings
828BGL_FORALL_VERTICES_T(y, G2, Graph2) {
829    if (invariant1(v) == invariant2(y) && f_inv_assigned[y] == false) {
830        f[v] = y; f_assigned[v] = true;
831        f_inv[y] = v; f_inv_assigned[y] = true;
832        num_edges_incident_on_k = 0;
833        if (match(next(iter)))
834            return true;
835        f_assigned[v] = false;
836        f_inv_assigned[y] = false;
837    }
838}
839@}
840
841Growing the subgraph.
842
843@d $v$ is an unmatched vertex, $(u,v)$ is a tree edge
844@{
845@<Count out-edges of $f(k)$ in $G_2[S]$@>
846@<Count in-edges of $f(k)$ in $G_2[S]$@>
847if (num_edges_incident_on_k != 0)
848    return false;
849@<Assign $v$ to some vertex in $V_2 - S$@>
850@}
851@d Count out-edges of $f(k)$ in $G_2[S]$
852@{
853BGL_FORALL_ADJACENT_T(f[k], w, G2, Graph2)
854    if (f_inv_assigned[w] == true)
855        --num_edges_incident_on_k;
856@}
857
858@d Count in-edges of $f(k)$ in $G_2[S]$
859@{
860for (std::size_t jj = 0; jj < k_num; ++jj) {
861    vertex1_t j = dfs_vertices[jj];
862    BGL_FORALL_ADJACENT_T(f[j], w, G2, Graph2)
863        if (w == f[k])
864            --num_edges_incident_on_k;
865}
866@}
867
868@d Assign $v$ to some vertex in $V_2 - S$
869@{
870BGL_FORALL_ADJACENT_T(f[u], y, G2, Graph2)
871    if (invariant1(v) == invariant2(y) && f_inv_assigned[y] == false) {
872        f[v] = y; f_assigned[v] = true;
873        f_inv[y] = v; f_inv_assigned[y] = true;
874        num_edges_incident_on_k = 1;
875        if (match(next(iter)))
876            return true;
877        f_assigned[v] = false;
878        f_inv_assigned[y] = false;
879    }
880@}
881
882
883
884@d Check to see if there is an edge in $G_2$ to match $(u,v)$
885@{
886bool verify = false;
887assert(f_assigned[u] == true);
888BGL_FORALL_ADJACENT_T(f[u], y, G2, Graph2) {
889    if (y == f[v]) {
890        verify = true;
891        break;
892    }
893}
894if (verify == true) {
895    ++num_edges_incident_on_k;
896    if (match(next(iter)))
897         return true;
898}
899@}
900
901
902
903@o isomorphism-v2.hpp
904@{
905// Copyright (C) 2001 Jeremy Siek, Douglas Gregor, Brian Osman
906//
907// Permission to copy, use, sell and distribute this software is granted
908// provided this copyright notice appears in all copies.
909// Permission to modify the code and to distribute modified code is granted
910// provided this copyright notice appears in all copies, and a notice
911// that the code was modified is included with the copyright notice.
912//
913// This software is provided "as is" without express or implied warranty,
914// and with no claim as to its suitability for any purpose.
915#ifndef BOOST_GRAPH_ISOMORPHISM_HPP
916#define BOOST_GRAPH_ISOMORPHISM_HPP
917
918#include <utility>
919#include <vector>
920#include <iterator>
921#include <algorithm>
922#include <boost/graph/iteration_macros.hpp>
923#include <boost/graph/depth_first_search.hpp>
924#include <boost/utility.hpp>
925#include <boost/tuple/tuple.hpp>
926
927namespace boost {
928
929namespace detail {
930
931@<Isomorphism algorithm class@>
932
933template <typename Graph, typename InDegreeMap>
934void compute_in_degree(const Graph& g, InDegreeMap in_degree_map)
935{
936    BGL_FORALL_VERTICES_T(v, g, Graph)
937        put(in_degree_map, v, 0);
938
939    BGL_FORALL_VERTICES_T(u, g, Graph)
940      BGL_FORALL_ADJACENT_T(u, v, g, Graph)
941        put(in_degree_map, v, get(in_degree_map, v) + 1);
942}
943
944} // namespace detail
945
946
947@<Degree vertex invariant functor@>
948
949@<Isomorphism function interface@>
950@<Isomorphism function body@>
951
952namespace detail {
953
954template <typename Graph1, typename Graph2,
955          typename IsoMapping,
956          typename IndexMap1, typename IndexMap2,
957          typename P, typename T, typename R>
958bool isomorphism_impl(const Graph1& G1, const Graph2& G2,
959                      IsoMapping f, IndexMap1 index_map1, IndexMap2 index_map2,
960		      const bgl_named_params<P,T,R>& params)
961{
962  std::vector<std::size_t> in_degree1_vec(num_vertices(G1));
963  typedef safe_iterator_property_map<std::vector<std::size_t>::iterator, IndexMap1> InDeg1;
964  InDeg1 in_degree1(in_degree1_vec.begin(), in_degree1_vec.size(), index_map1);
965  compute_in_degree(G1, in_degree1);
966
967  std::vector<std::size_t> in_degree2_vec(num_vertices(G2));
968  typedef safe_iterator_property_map<std::vector<std::size_t>::iterator, IndexMap2> InDeg2;
969  InDeg2 in_degree2(in_degree2_vec.begin(), in_degree2_vec.size(), index_map2);
970  compute_in_degree(G2, in_degree2);
971
972  degree_vertex_invariant<InDeg1, Graph1> invariant1(in_degree1, G1);
973  degree_vertex_invariant<InDeg2, Graph2> invariant2(in_degree2, G2);
974
975  return isomorphism(G1, G2, f,
976        choose_param(get_param(params, vertex_invariant1_t()), invariant1),
977        choose_param(get_param(params, vertex_invariant2_t()), invariant2),
978        choose_param(get_param(params, vertex_max_invariant_t()), invariant2.max()),
979	index_map1, index_map2
980	);
981}
982
983} // namespace detail
984
985
986// Named parameter interface
987template <typename Graph1, typename Graph2, class P, class T, class R>
988bool isomorphism(const Graph1& g1,
989		 const Graph2& g2,
990		 const bgl_named_params<P,T,R>& params)
991{
992  typedef typename graph_traits<Graph2>::vertex_descriptor vertex2_t;
993  typename std::vector<vertex2_t>::size_type n = num_vertices(g1);
994  std::vector<vertex2_t> f(n);
995  return detail::isomorphism_impl
996    (g1, g2,
997     choose_param(get_param(params, vertex_isomorphism_t()),
998          make_safe_iterator_property_map(f.begin(), f.size(),
999                  choose_const_pmap(get_param(params, vertex_index1),
1000		                    g1, vertex_index), vertex2_t())),
1001     choose_const_pmap(get_param(params, vertex_index1), g1, vertex_index),
1002     choose_const_pmap(get_param(params, vertex_index2), g2, vertex_index),
1003     params
1004     );
1005}
1006
1007// All defaults interface
1008template <typename Graph1, typename Graph2>
1009bool isomorphism(const Graph1& g1, const Graph2& g2)
1010{
1011  return isomorphism(g1, g2,
1012    bgl_named_params<int, buffer_param_t>(0));// bogus named param
1013}
1014
1015
1016// Verify that the given mapping iso_map from the vertices of g1 to the
1017// vertices of g2 describes an isomorphism.
1018// Note: this could be made much faster by specializing based on the graph
1019// concepts modeled, but since we're verifying an O(n^(lg n)) algorithm,
1020// O(n^4) won't hurt us.
1021template<typename Graph1, typename Graph2, typename IsoMap>
1022inline bool verify_isomorphism(const Graph1& g1, const Graph2& g2, IsoMap iso_map)
1023{
1024  if (num_vertices(g1) != num_vertices(g2) || num_edges(g1) != num_edges(g2))
1025    return false;
1026
1027  for (typename graph_traits<Graph1>::edge_iterator e1 = edges(g1).first;
1028       e1 != edges(g1).second; ++e1) {
1029    bool found_edge = false;
1030    for (typename graph_traits<Graph2>::edge_iterator e2 = edges(g2).first;
1031         e2 != edges(g2).second && !found_edge; ++e2) {
1032      if (source(*e2, g2) == get(iso_map, source(*e1, g1)) &&
1033          target(*e2, g2) == get(iso_map, target(*e1, g1))) {
1034        found_edge = true;
1035      }
1036    }
1037
1038    if (!found_edge)
1039      return false;
1040  }
1041
1042  return true;
1043}
1044
1045} // namespace boost
1046
1047#include <boost/graph/iteration_macros_undef.hpp>
1048
1049#endif // BOOST_GRAPH_ISOMORPHISM_HPP
1050@}
1051
1052\bibliographystyle{abbrv}
1053\bibliography{ggcl}
1054
1055\end{document}
1056% LocalWords:  Isomorphism Siek isomorphism adjacency subgraph subgraphs OM DFS
1057% LocalWords:  ISOMORPH Invariants invariants typename IsoMapping bool const
1058% LocalWords:  VertexInvariant VertexIndexMap iterator typedef VertexG Idx num
1059% LocalWords:  InvarValue struct invar vec iter tmp_matches mult inserter permute ui
1060% LocalWords:  dfs cmp isomorph VertexIter edge_iter_t IndexMap desc RPH ATCH pre
1061
1062% LocalWords:  iterators VertexListGraph EdgeListGraph BidirectionalGraph tmp
1063% LocalWords:  ReadWritePropertyMap VertexListGraphConcept EdgeListGraphConcept
1064% LocalWords:  BidirectionalGraphConcept ReadWritePropertyMapConcept indices ei
1065% LocalWords:  IsoMappingValue ReadablePropertyMapConcept namespace InvarFun
1066% LocalWords:  MultMap vip inline bitset typedefs fj hpp ifndef adaptor params
1067% LocalWords:  bgl param pmap endif
1068