Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/boost_1_34_1/libs/graph/doc/isomorphism-impl-v3.w @ 45

Last change on this file since 45 was 29, checked in by landauf, 16 years ago

updated boost from 1_33_1 to 1_34_1

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