Compare commits
2 commits
881fe54ef0
...
0bf014766a
Author | SHA1 | Date | |
---|---|---|---|
Théophile Bastian | 0bf014766a | ||
Théophile Bastian | 34458bcc44 |
|
@ -420,7 +420,114 @@ VF2 algorithm~\cite{cordella2004sub}, published in 2004. This algorithm is
|
|||
mostly Ullmann's algorithm, transcribed in a recursive writing, with the
|
||||
addition of five heuristics. \qtodo{Why not use it then?}
|
||||
|
||||
\todo{Describe Ullmann}
|
||||
Ullmann is a widely used and fast algorithm for this problem. It makes an
|
||||
extensive use of adjacency matrix description of the graph, and the initial
|
||||
article takes advantage of the representation of those matrices as bitsets to
|
||||
make extensive use of bitwise operations.
|
||||
|
||||
The to-be-built permutation matrix is a $\card{needle} \times \card{haystack}$
|
||||
matrix. Each $1$ in a cell $(i, j)$ indicates that the $i$-th needle part is a
|
||||
possible match with the $j$-th haystack part. This matrix is called $perm$
|
||||
thereafter.
|
||||
|
||||
The algorithm, left apart the \textsc{refine} function (detailed just after),
|
||||
is described in Figure~\ref{alg:ullmann}.
|
||||
|
||||
\begin{figure}[h]
|
||||
\begin{algorithmic}
|
||||
\Function{find\_at\_depth}{depth, perm, freeVert}
|
||||
\If{no 1s on \lstc{perm[depth]}}
|
||||
\State{} \Return{}
|
||||
\EndIf{}
|
||||
|
||||
\State{} Save perm
|
||||
|
||||
\For{$0 \leq$ chosen $< \card{\text{haystack}}$ such that
|
||||
\lstc{perm[depth][chosen]} $ = 1$ and \lstc{freeVert[chosen]}}
|
||||
\State{} Put $0$s everywhere on \lstc{perm[depth]}, but on
|
||||
\lstc{chosen}
|
||||
|
||||
\State{} Refine perm
|
||||
\If{a row of perm has only $0$s}
|
||||
\State{} \Return{}
|
||||
\EndIf{}
|
||||
|
||||
\If{depth $=$ $\card{\text{needle}} - 1$}
|
||||
\State{} Store perm as a result
|
||||
\Else{}
|
||||
\State{} \Call{find\_at\_depth}{depth$+1$, perm, freeVert with
|
||||
freeVert[chosen] $= 0$}
|
||||
\EndIf{}
|
||||
|
||||
\State{} Restore perm
|
||||
\EndFor{}
|
||||
\EndFunction{}
|
||||
|
||||
\vspace{1em}
|
||||
|
||||
\Function{find}{perm}
|
||||
\State{} \Return{} \Call{find\_at\_depth}{0, perm, [$1, \ldots, 1$]}
|
||||
\EndFunction{}
|
||||
\end{algorithmic}
|
||||
\caption{Ullmann's algorithm (without refining)}\label{alg:ullmann}
|
||||
\end{figure}
|
||||
|
||||
The refining process is the actual keystone of the algorithm. It is the
|
||||
mechanism allowing the algorithm to cut down many exploration branches, by
|
||||
removing ones from the matrix.
|
||||
|
||||
The idea is that a match between a needle's vertex $i$ and a haystack's vertex
|
||||
$j$ is only possible if, for each neighbour $k$ of $i$, $j$ has a neighbour
|
||||
$k'$ such that the permutation matrix has a one in position $(k, k')$. In other
|
||||
words, a match between $i$ and $j$ is only possible if every neighbour $k$ of
|
||||
$i$ (in needle) has a possibly matching (\wrt{} $perm$) vertex $k'$ (in
|
||||
haystack) which is a neighbour of $j$.
|
||||
|
||||
This condition is checked on every $1$ in the permutation matrix. If it is not
|
||||
met, the cell is nulled. This, though, potentially creates new ones not
|
||||
matching the condition: the process must be run again, until no new zeroes
|
||||
appear.
|
||||
|
||||
In the initial article~\cite{ullmann1976algorithm}, Ullmann advocates for
|
||||
bitwise tricks to complete this expensive step: indeed, checking the existence
|
||||
of such a $k'$ can be done by checking the nullity of the bitwise \textsc{and}
|
||||
of the adjacency of $j$ and the permutation matrix row of $k$.
|
||||
|
||||
The refining function is detailed in Figure~\ref{alg:ullmann_refine}.
|
||||
|
||||
\todo{Insert explaining figure}
|
||||
|
||||
\begin{figure}[h]
|
||||
\begin{algorithmic}
|
||||
\Function{refine}{perm}
|
||||
\While{changes during last run}
|
||||
\For{each needle vertex $i$}
|
||||
\For{each haystack vertex $j$}
|
||||
\For{each neighbour $k$ of $i$ in needle}
|
||||
\If{\lstc{perm[k] & haystack\_adjacency[j]}
|
||||
$= 0$}
|
||||
\State{} \lstc{perm[i][j]} $\gets 0$
|
||||
\EndIf{}
|
||||
\EndFor{}
|
||||
\EndFor{}
|
||||
\EndFor{}
|
||||
\EndWhile{}
|
||||
\EndFunction{}
|
||||
\end{algorithmic}
|
||||
\caption{Ullmann's refining function}\label{alg:ullmann_refine}
|
||||
\end{figure}
|
||||
|
||||
\subsection{Implementation optimisations}
|
||||
|
||||
|
||||
The matrix is first filled according to the signatures matches. It is then
|
||||
refined a bit more, by making sure that for every match, every potentially
|
||||
matching gate has the same ``wire kinds''. Indeed, a gate needle's wire must
|
||||
have at least the same inbound adjacent signatures as its matching haystack
|
||||
wire, and same goes for outbound adjacent signatures. Thus, two circuits cannot
|
||||
be matched if this condition is not respected for each pair of corresponding
|
||||
wires of those circuits, and their corresponding cell in the permutation matrix
|
||||
can be nulled.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\section{Performance}
|
||||
|
@ -432,6 +539,8 @@ addition of five heuristics. \qtodo{Why not use it then?}
|
|||
There were a few observed cases where the algorithm tends to be slower on
|
||||
certain configurations.
|
||||
|
||||
\todo{More corner cases}
|
||||
|
||||
\paragraph{Split/merge trees.} A common pattern that tends to slow down the
|
||||
algorithm is split/merge trees. Those patterns occur when one wants to merge
|
||||
$n$ one bit wires into a single $n$ bits wire, or the other way around.
|
||||
|
@ -444,9 +553,16 @@ tree of depth \eg{} 8, a node just below the root will need a signature of
|
|||
order 7 to have a different signature than another one at the same depth. With
|
||||
a signature of order up to 6, only other gates from the tree will be included
|
||||
in the signature when going down in the tree; the exact same gates will be
|
||||
included above the tree's root. Thus, nothing will differentiate one gate from another while
|
||||
the boundary of the tree is not reached (assuming the gates below the tree's
|
||||
leaves are not all the same; if so, more levels will be needed).
|
||||
included above the tree's root. Thus, nothing will differentiate one gate from
|
||||
another while the boundary of the tree is not reached (assuming the gates below
|
||||
the tree's leaves are not all the same; if so, more levels will be needed).
|
||||
|
||||
As the notion of ``left child'' and ``right child'' cannot be used (since it
|
||||
would rely on the order or description of the graph), there seems to be no good
|
||||
way to discriminate those two nodes. Furthermore, the nodes are not totally
|
||||
interchangeable: indeed, when checking for an equality between two such trees,
|
||||
it does not matter which node is the left one; but once this is fixed, the
|
||||
nodes on the layer below cannot be freely exchanged.
|
||||
|
||||
\todo{Figure describing the problem}
|
||||
|
||||
|
|
Loading…
Reference in a new issue