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{https://boost.org/sgi/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 510BOOST_CONCEPT_ASSERT(( VertexListGraphConcept<Graph1> )); 511BOOST_CONCEPT_ASSERT(( EdgeListGraphConcept<Graph1> )); 512BOOST_CONCEPT_ASSERT(( VertexListGraphConcept<Graph2> )); 513BOOST_CONCEPT_ASSERT(( 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 520BOOST_CONCEPT_ASSERT(( AdaptableUnaryFunctionConcept<Invariant1, 521 size_type, vertex1_t> )); 522BOOST_CONCEPT_ASSERT(( AdaptableUnaryFunctionConcept<Invariant2, 523 size_type, vertex2_t> )); 524 525// Property map requirements 526BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept<IsoMapping, vertex1_t> )); 527typedef typename property_traits<IsoMapping>::value_type IsoMappingValue; 528BOOST_STATIC_ASSERT((is_same<IsoMappingValue, vertex2_t>::value)); 529 530BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept<IndexMap1, vertex1_t> )); 531typedef typename property_traits<IndexMap1>::value_type IndexMap1Value; 532BOOST_STATIC_ASSERT((is_convertible<IndexMap1Value, size_type>::value)); 533 534BOOST_CONCEPT_ASSERT(( 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 988