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 | |
---|
77 | This paper documents the implementation of the \code{isomorphism()} |
---|
78 | function of the Boost Graph Library. The implementation was by Jeremy |
---|
79 | Siek with algorithmic improvements and test code from Douglas Gregor |
---|
80 | and Brian Osman. The \code{isomorphism()} function answers the |
---|
81 | question, ``are these two graphs equal?'' By \emph{equal} we mean |
---|
82 | the two graphs have the same structure---the vertices and edges are |
---|
83 | connected in the same way. The mathematical name for this kind of |
---|
84 | equality is \emph{isomorphism}. |
---|
85 | |
---|
86 | More precisely, an \emph{isomorphism} is a one-to-one mapping of the |
---|
87 | vertices in one graph to the vertices of another graph such that |
---|
88 | adjacency is preserved. Another words, given graphs $G_{1} = |
---|
89 | (V_{1},E_{1})$ and $G_{2} = (V_{2},E_{2})$, an isomorphism is a |
---|
90 | function $f$ such that for all pairs of vertices $a,b$ in $V_{1}$, |
---|
91 | edge $(a,b)$ is in $E_{1}$ if and only if edge $(f(a),f(b))$ is in |
---|
92 | $E_{2}$. |
---|
93 | |
---|
94 | The graph $G_1$ is \emph{isomorphic} to $G_2$ if an isomorphism exists |
---|
95 | between the two graphs, which we denote by $G_1 \isomorphic G_2$. |
---|
96 | Both graphs must be the same size, so let $N = |V_1| = |V_2|$. |
---|
97 | |
---|
98 | In the following discussion we will need to use several more notions |
---|
99 | from graph theory. The graph $G_s=(V_s,E_s)$ is a \emph{subgraph} of |
---|
100 | graph $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)$ |
---|
102 | consists of the vertices in $V_s$, which is a subset of $V$, and every |
---|
103 | edge $(u,v)$ in $E$ such that both $u$ and $v$ are in $V_s$. We use |
---|
104 | the notation $E[V_s]$ to mean the edges in $G[V_s]$. |
---|
105 | |
---|
106 | \section{Backtracking Search} |
---|
107 | \label{sec:backtracking} |
---|
108 | |
---|
109 | The algorithm used by the \code{isomorphism()} function is, at first |
---|
110 | approximation, an exhaustive search implemented via backtracking. The |
---|
111 | backtracking algorithm is a recursive function. At each stage we will |
---|
112 | try to extend the match that we have found so far. So suppose that we |
---|
113 | have already determined that some subgraph of $G_1$ is isomorphic to a |
---|
114 | subgraph of $G_2$. We then try to add a vertex to each subgraph such |
---|
115 | that the new subgraphs are still isomorphic to one another. At some |
---|
116 | point we may hit a dead end---there are no vertices that can be added |
---|
117 | to extend the isomorphic subgraphs. We then backtrack to previous |
---|
118 | smaller matching subgraphs, and try extending with a different vertex |
---|
119 | choice. The process ends by either finding a complete mapping between |
---|
120 | $G_1$ and $G_2$ and returning true, or by exhausting all possibilities |
---|
121 | and returning false. |
---|
122 | |
---|
123 | The problem with the exhaustive backtracking algorithm is that there |
---|
124 | are $N!$ possible vertex mappings, and $N!$ gets very large as $N$ |
---|
125 | increases, so we need to prune the search space. We use the pruning |
---|
126 | techniques described in |
---|
127 | \cite{deo77:_new_algo_digraph_isomorph,fortin96:_isomorph,reingold77:_combin_algo}, |
---|
128 | some of which originated in |
---|
129 | \cite{sussenguth65:_isomorphism,unger64:_isomorphism}. Also, the |
---|
130 | specific backtracking method we use is the one from |
---|
131 | \cite{deo77:_new_algo_digraph_isomorph}. |
---|
132 | |
---|
133 | We consider the vertices of $G_1$ for addition to the matched subgraph |
---|
134 | in 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 |
---|
136 | ordering of the vertices is by DFS discover time. Let $G_1[k]$ denote |
---|
137 | the subgraph of $G_1$ induced by the first $k$ vertices, with $G_1[0]$ |
---|
138 | being an empty graph. We also consider the edges of $G_1$ in a |
---|
139 | specific 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), |
---|
143 | u, v \rangle$. Figure~\ref{fig:iso-eg} shows an example of a graph |
---|
144 | with the vertices labelled by DFS discover time. The edge ordering for |
---|
145 | this graph is as follows: |
---|
146 | |
---|
147 | \begin{tabular}{lccccccccc} |
---|
148 | source: &0&1&0&1&3&0&5&6&6\\ |
---|
149 | target: &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 |
---|
153 | edges are the solid lines. Nodes $0$ and $5$ are DFS tree root nodes.} |
---|
154 | |
---|
155 | Each step of the backtracking search moves from left to right though |
---|
156 | the ordered edges. At each step it examines an edge $(i,j)$ of $G_1$ |
---|
157 | and decides whether to continue to the left or to go back. There are |
---|
158 | three 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 |
---|
168 | happens at the very beginning of the search, or when $i$ is not |
---|
169 | reachable from any of the vertices in $G_1[k]$. This means that we |
---|
170 | are finished with $G_1[k]$. We increment $k$ and find a match for it |
---|
171 | amongst any of the eligible vertices in $V_2 - S$. We then proceed to |
---|
172 | Case 2. It is usually the case that $i$ is equal to the new $k$, but |
---|
173 | when there is another DFS root $r$ with no in-edges or out-edges |
---|
174 | and 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 |
---|
178 | to 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 |
---|
182 | extension of the isomorphism to $k$. At this point we are guaranteed |
---|
183 | to have seen all the edges to and from vertex $k$ (because the edges |
---|
184 | are sorted), and in previous steps we have checked that for each edge |
---|
185 | incident 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 |
---|
188 | on $f(k)$ there is $(f^{-1}(u),f^{-1}(v)) \in E_1[k]$. A quick way to |
---|
189 | verify this is to make sure that the number of edges incident on $k$ |
---|
190 | in $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 |
---|
192 | see 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 | |
---|
195 | Once we have verified that $G_1[k] \isomorphic G_2[S]$ we add $f(k)$ |
---|
196 | to $S$, increment $k$, and then try assigning $j$ to |
---|
197 | any 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$.} |
---|
201 | Both $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 | |
---|
208 | One way to reduce the search space is through the use of \emph{vertex |
---|
209 | invariants}. The idea is to compute a number for each vertex $i(v)$ |
---|
210 | such 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 |
---|
212 | those vertices that have the same vertex invariant number are |
---|
213 | ``eligible''. The number of vertices in a graph with the same vertex |
---|
214 | invariant 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 |
---|
217 | supply there own invariant function. The ability of the invariant |
---|
218 | function to prune the search space varies widely with the type of |
---|
219 | graph. |
---|
220 | |
---|
221 | The following is the definition of the functor that implements the |
---|
222 | default vertex invariant. The functor models the |
---|
223 | \stlconcept{AdaptableUnaryFunction} concept. |
---|
224 | |
---|
225 | @d Degree vertex invariant functor |
---|
226 | @{ |
---|
227 | template <typename InDegreeMap, typename Graph> |
---|
228 | class 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; |
---|
232 | public: |
---|
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 | } |
---|
247 | private: |
---|
248 | InDegreeMap m_in_degree_map; |
---|
249 | const Graph& m_g; |
---|
250 | }; |
---|
251 | @} |
---|
252 | |
---|
253 | |
---|
254 | \subsection{Vertex Order} |
---|
255 | |
---|
256 | A good choice of the labeling for the vertices (which determines the |
---|
257 | order in which the subgraph $G_1[k]$ is grown) can also reduce the |
---|
258 | search space. In the following we discuss two labeling heuristics. |
---|
259 | |
---|
260 | \subsubsection{Most Constrained First} |
---|
261 | |
---|
262 | Consider the most constrained vertices first. That is, examine |
---|
263 | lower-degree vertices before higher-degree vertices. This reduces the |
---|
264 | search space because it chops off a trunk before the trunk has a |
---|
265 | chance to blossom out. We can generalize this to use vertex |
---|
266 | invariants. We examine vertices with low invariant multiplicity |
---|
267 | before examining vertices with high invariant multiplicity. |
---|
268 | |
---|
269 | \subsubsection{Adjacent First} |
---|
270 | |
---|
271 | It only makes sense to examine an edge if one or more of its vertices |
---|
272 | has been assigned a mapping. This means that we should visit vertices |
---|
273 | adjacent to those in the current matched subgraph before proceeding. |
---|
274 | |
---|
275 | \subsubsection{DFS Order, Starting with Lowest Multiplicity} |
---|
276 | |
---|
277 | For this implementation, we combine the above two heuristics in the |
---|
278 | following way. To implement the ``adjacent first'' heuristic we apply |
---|
279 | DFS to the graph, and use the DFS discovery order as our vertex |
---|
280 | order. To comply with the ``most constrained first'' heuristic we |
---|
281 | order the roots of our DFS trees by invariant multiplicity. |
---|
282 | |
---|
283 | |
---|
284 | \subsection{Implementation of the \code{match} function} |
---|
285 | |
---|
286 | |
---|
287 | The \code{match} function implements the recursive backtracking, |
---|
288 | handling the four cases described in \S\ref{sec:backtracking}. |
---|
289 | |
---|
290 | @d Match function |
---|
291 | @{ |
---|
292 | bool 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]$.} |
---|
314 | We 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 |
---|
317 | not yet found a match for edge, or for target $j$. We reset the edge |
---|
318 | counter to zero. |
---|
319 | |
---|
320 | @d Find a match for the DFS tree root $k+1$ |
---|
321 | @{ |
---|
322 | vertex1_t kp1 = dfs_vertices[dfs_num_k + 1]; |
---|
323 | BGL_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]$.} |
---|
337 | Before we extend the subgraph by incrementing $k$, we need to finish |
---|
338 | verifying that $G_1[k]$ and $G_2[S]$ are isomorphic. We decrement the |
---|
339 | edge counter for every edge incident to $f(k)$ in $G_2[S]$, which |
---|
340 | should 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 | @{ |
---|
344 | vertex1_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]$@> |
---|
347 | if (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 |
---|
354 | counting, using \code{boost::bind} to create the predicate functor. |
---|
355 | |
---|
356 | @d Count out-edges of $f(k)$ in $G_2[S]$ |
---|
357 | @{ |
---|
358 | num_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 |
---|
363 | we 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 | @{ |
---|
369 | for (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 | |
---|
375 | Now that we have finished verifying that $G_1[k] \isomorphic G_2[S]$, |
---|
376 | we can now consider extending the isomorphism. We need to find a match |
---|
377 | for $j$ in $V_2 - S$. Since $j$ is adjacent to $i$, we can further |
---|
378 | narrow down the search by only considering vertices adjacent to |
---|
379 | $f(i)$. Also, the vertex must have the same vertex invariant. Once we |
---|
380 | have a matching vertex $v$ we extend the matching subgraphs by |
---|
381 | incrementing $k$ and adding $v$ to $S$, we set $f(j) = v$, and we set |
---|
382 | the edge counter to $1$ (since $(i,j)$ is the first edge incident on |
---|
383 | our new $k$). We continue to the next edge by calling \code{match}. If |
---|
384 | that fails we undo the assignment $f(j) = v$. |
---|
385 | |
---|
386 | @d Find a match for $j$ and continue |
---|
387 | @{ |
---|
388 | BGL_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]$.} |
---|
401 | Our goal is to check whether $(f(i),f(j)) \in E_2[S]$. If $f(j)$ is |
---|
402 | in $Adj[f(i)]$ then we have a match for the edge $(i,j)$, and can |
---|
403 | increment 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 | @{ |
---|
408 | edge2_t e2; |
---|
409 | bool fi_fj_exists = false; |
---|
410 | typename graph_traits<Graph2>::out_edge_iterator io, io_end; |
---|
411 | for (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 | |
---|
417 | if (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 | |
---|
426 | The following is the public interface for the \code{isomorphism} |
---|
427 | function. The input to the function is the two graphs $G_1$ and $G_2$, |
---|
428 | mappings from the vertices in the graphs to integers (in the range |
---|
429 | $[0,|V|)$), and a vertex invariant function object. The output of the |
---|
430 | function is an isomorphism $f$ if there is one. The \code{isomorphism} |
---|
431 | function returns true if the graphs are isomorphic and false |
---|
432 | otherwise. The invariant parameters are function objects that compute |
---|
433 | the vertex invariants for vertices of the two graphs. The |
---|
434 | \code{max\_invariant} parameter is to specify one past the largest |
---|
435 | integer that a vertex invariant number could be (the invariants |
---|
436 | numbers are assumed to span from zero to \code{max\_invariant-1}). |
---|
437 | The requirements on the template parameters are described below in the |
---|
438 | ``Concept checking'' code part. |
---|
439 | |
---|
440 | |
---|
441 | @d Isomorphism function interface |
---|
442 | @{ |
---|
443 | template <typename Graph1, typename Graph2, typename IsoMapping, |
---|
444 | typename Invariant1, typename Invariant2, typename EdgeCompare, |
---|
445 | typename IndexMap1, typename IndexMap2> |
---|
446 | bool 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 | |
---|
453 | The function body consists of the concept checks followed by a quick |
---|
454 | check for empty graphs or graphs of different size and then constructs |
---|
455 | an algorithm object. We then call the \code{test\_isomorphism} member |
---|
456 | function, which runs the algorithm. The reason that we implement the |
---|
457 | algorithm using a class is that there are a fair number of internal |
---|
458 | data structures required, and it is easier to make these data members |
---|
459 | of a class and make each section of the algorithm a member |
---|
460 | function. This relieves us from the burden of passing lots of |
---|
461 | arguments to each function, while at the same time avoiding the evils |
---|
462 | of 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 |
---|
481 | trivially isomorphic. If the graphs have different numbers of vertices |
---|
482 | then they are not isomorphic. We could also check the number of edges |
---|
483 | here, but that would introduce the \bglconcept{EdgeListGraph} |
---|
484 | requirement, which we otherwise do not need. |
---|
485 | |
---|
486 | @d Quick return based on size |
---|
487 | @{ |
---|
488 | if (num_vertices(G1) != num_vertices(G2)) |
---|
489 | return false; |
---|
490 | if (num_vertices(G1) == 0 && num_vertices(G2) == 0) |
---|
491 | return true; |
---|
492 | @} |
---|
493 | |
---|
494 | We use the Boost Concept Checking Library to make sure that the |
---|
495 | template arguments fulfill certain requirements. The graph types must |
---|
496 | model the \bglconcept{VertexListGraph} and \bglconcept{AdjacencyGraph} |
---|
497 | concepts. The vertex invariants must model the |
---|
498 | \stlconcept{AdaptableUnaryFunction} concept, with a vertex as their |
---|
499 | argument and an integer return type. The \code{IsoMapping} type |
---|
500 | representing the isomorphism $f$ must be a |
---|
501 | \pmconcept{ReadWritePropertyMap} that maps from vertices in $G_1$ to |
---|
502 | vertices in $G_2$. The two other index maps are |
---|
503 | \pmconcept{ReadablePropertyMap}s from vertices in $G_1$ and $G_2$ to |
---|
504 | unsigned integers. |
---|
505 | |
---|
506 | |
---|
507 | @d Concept checking |
---|
508 | @{ |
---|
509 | // Graph requirements |
---|
510 | function_requires< VertexListGraphConcept<Graph1> >(); |
---|
511 | function_requires< EdgeListGraphConcept<Graph1> >(); |
---|
512 | function_requires< VertexListGraphConcept<Graph2> >(); |
---|
513 | function_requires< BidirectionalGraphConcept<Graph2> >(); |
---|
514 | |
---|
515 | typedef typename graph_traits<Graph1>::vertex_descriptor vertex1_t; |
---|
516 | typedef typename graph_traits<Graph2>::vertex_descriptor vertex2_t; |
---|
517 | typedef typename graph_traits<Graph1>::vertices_size_type size_type; |
---|
518 | |
---|
519 | // Vertex invariant requirement |
---|
520 | function_requires< AdaptableUnaryFunctionConcept<Invariant1, |
---|
521 | size_type, vertex1_t> >(); |
---|
522 | function_requires< AdaptableUnaryFunctionConcept<Invariant2, |
---|
523 | size_type, vertex2_t> >(); |
---|
524 | |
---|
525 | // Property map requirements |
---|
526 | function_requires< ReadWritePropertyMapConcept<IsoMapping, vertex1_t> >(); |
---|
527 | typedef typename property_traits<IsoMapping>::value_type IsoMappingValue; |
---|
528 | BOOST_STATIC_ASSERT((is_same<IsoMappingValue, vertex2_t>::value)); |
---|
529 | |
---|
530 | function_requires< ReadablePropertyMapConcept<IndexMap1, vertex1_t> >(); |
---|
531 | typedef typename property_traits<IndexMap1>::value_type IndexMap1Value; |
---|
532 | BOOST_STATIC_ASSERT((is_convertible<IndexMap1Value, size_type>::value)); |
---|
533 | |
---|
534 | function_requires< ReadablePropertyMapConcept<IndexMap2, vertex2_t> >(); |
---|
535 | typedef typename property_traits<IndexMap2>::value_type IndexMap2Value; |
---|
536 | BOOST_STATIC_ASSERT((is_convertible<IndexMap2Value, size_type>::value)); |
---|
537 | @} |
---|
538 | |
---|
539 | |
---|
540 | \section{Data Structure Setup} |
---|
541 | |
---|
542 | The following is the outline of the isomorphism algorithm class. The |
---|
543 | class is templated on all of the same parameters as the |
---|
544 | \code{isomorphism} function, and all of the parameter values are |
---|
545 | stored in the class as data members, in addition to the internal data |
---|
546 | structures. |
---|
547 | |
---|
548 | @d Isomorphism algorithm class |
---|
549 | @{ |
---|
550 | template <typename Graph1, typename Graph2, typename IsoMapping, |
---|
551 | typename Invariant1, typename Invariant2, typename EdgeCompare, |
---|
552 | typename IndexMap1, typename IndexMap2> |
---|
553 | class 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@> |
---|
562 | public: |
---|
563 | @<Isomorphism algorithm constructor@> |
---|
564 | @<Test isomorphism member function@> |
---|
565 | private: |
---|
566 | @<Match function@> |
---|
567 | }; |
---|
568 | @} |
---|
569 | |
---|
570 | The interesting parts of this class are the \code{test\_isomorphism} |
---|
571 | function and the \code{match} function. We focus on those in the |
---|
572 | following sections, and leave the other parts of the class to the |
---|
573 | Appendix. |
---|
574 | |
---|
575 | The \code{test\_isomorphism} function does all of the setup required |
---|
576 | of the algorithm. This consists of sorting the vertices according to |
---|
577 | invariant multiplicity, and then by DFS order. The edges are then |
---|
578 | sorted as previously described. The last step of this function is to |
---|
579 | begin the backtracking search. |
---|
580 | |
---|
581 | @d Test isomorphism member function |
---|
582 | @{ |
---|
583 | bool 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 | |
---|
595 | As a first check to rule out graphs that have no possibility of |
---|
596 | matching, one can create a list of computed vertex invariant numbers |
---|
597 | for the vertices in each graph, sort the two lists, and then compare |
---|
598 | them. If the two lists are different then the two graphs are not |
---|
599 | isomorphic. If the two lists are the same then the two graphs may be |
---|
600 | isomorphic. |
---|
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 | |
---|
619 | Next we compute the invariant multiplicity, the number of vertices |
---|
620 | with the same invariant number. The \code{invar\_mult} vector is |
---|
621 | indexed by invariant number. We loop through all the vertices in the |
---|
622 | graph to record the multiplicity. We then order the vertices by their |
---|
623 | invariant multiplicity. This will allow us to search the more |
---|
624 | constrained vertices first. |
---|
625 | |
---|
626 | @d Sort vertices according to invariant multiplicity |
---|
627 | @{ |
---|
628 | std::vector<vertex1_t> V_mult; |
---|
629 | BGL_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 |
---|
640 | is 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 | @{ |
---|
645 | struct 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 | |
---|
659 | Next we order the vertices and edges by DFS discover time. We would |
---|
660 | normally call the BGL \code{depth\_first\_search} function to do this, |
---|
661 | but we want the roots of the DFS tree's to be ordered by invariant |
---|
662 | multiplicity. Therefore we implement the outer-loop of the DFS here |
---|
663 | and then call \code{depth\_\-first\_\-visit} to handle the recursive |
---|
664 | portion of the DFS. The \code{record\_dfs\_order} adapts the DFS to |
---|
665 | record the ordering, storing the results in in the |
---|
666 | \code{dfs\_vertices} and \code{ordered\_edges} arrays. We then create |
---|
667 | the \code{dfs\_num} array which provides a mapping from vertex to DFS |
---|
668 | number. |
---|
669 | |
---|
670 | @d Order vertices and edges by DFS |
---|
671 | @{ |
---|
672 | std::vector<default_color_type> color_vec(num_vertices(G1)); |
---|
673 | safe_iterator_property_map<std::vector<default_color_type>::iterator, IndexMap1> |
---|
674 | color_map(color_vec.begin(), color_vec.size(), index_map1); |
---|
675 | record_dfs_order dfs_visitor(dfs_vertices, ordered_edges); |
---|
676 | typedef color_traits<default_color_type> Color; |
---|
677 | for (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 |
---|
684 | dfs_num_vec.resize(num_vertices(G1)); |
---|
685 | dfs_num = make_safe_iterator_property_map(dfs_num_vec.begin(), |
---|
686 | dfs_num_vec.size(), index_map1); |
---|
687 | size_type n = 0; |
---|
688 | for (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 |
---|
693 | class is as follows. |
---|
694 | |
---|
695 | @d DFS visitor to record vertex and edge order |
---|
696 | @{ |
---|
697 | struct 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 | |
---|
713 | The final stage of the setup is to reorder the edges so that all edges |
---|
714 | belonging 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 | @{ |
---|
719 | sort(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 | @{ |
---|
726 | struct 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 | @{ |
---|
750 | typedef typename graph_traits<Graph1>::vertex_descriptor vertex1_t; |
---|
751 | typedef typename graph_traits<Graph2>::vertex_descriptor vertex2_t; |
---|
752 | typedef typename graph_traits<Graph1>::edge_descriptor edge1_t; |
---|
753 | typedef typename graph_traits<Graph2>::edge_descriptor edge2_t; |
---|
754 | typedef typename graph_traits<Graph1>::vertices_size_type size_type; |
---|
755 | typedef typename Invariant1::result_type invar1_value; |
---|
756 | typedef typename Invariant2::result_type invar2_value; |
---|
757 | @} |
---|
758 | |
---|
759 | @d Data members for the parameters |
---|
760 | @{ |
---|
761 | const Graph1& G1; |
---|
762 | const Graph2& G2; |
---|
763 | IsoMapping f; |
---|
764 | Invariant1 invariant1; |
---|
765 | Invariant2 invariant2; |
---|
766 | std::size_t max_invariant; |
---|
767 | EdgeCompare edge_compare; |
---|
768 | IndexMap1 index_map1; |
---|
769 | IndexMap2 index_map2; |
---|
770 | @} |
---|
771 | |
---|
772 | @d Internal data structures |
---|
773 | @{ |
---|
774 | std::vector<vertex1_t> dfs_vertices; |
---|
775 | typedef typename std::vector<vertex1_t>::iterator vertex_iter; |
---|
776 | std::vector<int> dfs_num_vec; |
---|
777 | typedef safe_iterator_property_map<typename std::vector<int>::iterator, |
---|
778 | IndexMap1> DFSNumMap; |
---|
779 | DFSNumMap dfs_num; |
---|
780 | std::vector<edge1_t> ordered_edges; |
---|
781 | typedef typename std::vector<edge1_t>::iterator edge_iter; |
---|
782 | |
---|
783 | std::vector<char> in_S_vec; |
---|
784 | typedef safe_iterator_property_map<typename std::vector<char>::iterator, |
---|
785 | IndexMap2> InSMap; |
---|
786 | InSMap in_S; |
---|
787 | |
---|
788 | int num_edges_on_k; |
---|
789 | @} |
---|
790 | |
---|
791 | @d Isomorphism algorithm constructor |
---|
792 | @{ |
---|
793 | isomorphism_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 | |
---|
834 | namespace boost { |
---|
835 | |
---|
836 | namespace detail { |
---|
837 | |
---|
838 | @<Isomorphism algorithm class@> |
---|
839 | |
---|
840 | template <typename Graph, typename InDegreeMap> |
---|
841 | void 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 | |
---|
859 | namespace detail { |
---|
860 | |
---|
861 | struct default_edge_compare { |
---|
862 | template <typename Edge1, typename Edge2> |
---|
863 | bool operator()(Edge1 e1, Edge2 e2) const { return true; } |
---|
864 | }; |
---|
865 | |
---|
866 | template <typename Graph1, typename Graph2, |
---|
867 | typename IsoMapping, |
---|
868 | typename IndexMap1, typename IndexMap2, |
---|
869 | typename P, typename T, typename R> |
---|
870 | bool 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 |
---|
904 | template <typename Graph1, typename Graph2, class P, class T, class R> |
---|
905 | bool 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 |
---|
925 | template <typename Graph1, typename Graph2> |
---|
926 | bool 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. |
---|
938 | template<typename Graph1, typename Graph2, typename IsoMap> |
---|
939 | inline 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 |
---|