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