Yc@sdZddlZddlZddgZdefdYZdefdYZdefdYZd efd YZ dS( s ************* VF2 Algorithm ************* An implementation of VF2 algorithm for graph ismorphism testing. The simplest interface to use this module is to call networkx.is_isomorphic(). Introduction ------------ The GraphMatcher and DiGraphMatcher are responsible for matching graphs or directed graphs in a predetermined manner. This usually means a check for an isomorphism, though other checks are also possible. For example, a subgraph of one graph can be checked for isomorphism to a second graph. Matching is done via syntactic feasibility. It is also possible to check for semantic feasibility. Feasibility, then, is defined as the logical AND of the two functions. To include a semantic check, the (Di)GraphMatcher class should be subclassed, and the semantic_feasibility() function should be redefined. By default, the semantic feasibility function always returns True. The effect of this is that semantics are not considered in the matching of G1 and G2. Examples -------- Suppose G1 and G2 are isomorphic graphs. Verification is as follows: >>> from networkx.algorithms import isomorphism >>> G1 = nx.path_graph(4) >>> G2 = nx.path_graph(4) >>> GM = isomorphism.GraphMatcher(G1,G2) >>> GM.is_isomorphic() True GM.mapping stores the isomorphism mapping from G1 to G2. >>> GM.mapping {0: 0, 1: 1, 2: 2, 3: 3} Suppose G1 and G2 are isomorphic directed graphs graphs. Verification is as follows: >>> G1 = nx.path_graph(4, create_using=nx.DiGraph()) >>> G2 = nx.path_graph(4, create_using=nx.DiGraph()) >>> DiGM = isomorphism.DiGraphMatcher(G1,G2) >>> DiGM.is_isomorphic() True DiGM.mapping stores the isomorphism mapping from G1 to G2. >>> DiGM.mapping {0: 0, 1: 1, 2: 2, 3: 3} Subgraph Isomorphism -------------------- Graph theory literature can be ambiguious about the meaning of the above statement, and we seek to clarify it now. In the VF2 literature, a mapping M is said to be a graph-subgraph isomorphism iff M is an isomorphism between G2 and a subgraph of G1. Thus, to say that G1 and G2 are graph-subgraph isomorphic is to say that a subgraph of G1 is isomorphic to G2. Other literature uses the phrase 'subgraph isomorphic' as in 'G1 does not have a subgraph isomorphic to G2'. Another use is as an in adverb for isomorphic. Thus, to say that G1 and G2 are subgraph isomorphic is to say that a subgraph of G1 is isomorphic to G2. Finally, the term 'subgraph' can have multiple meanings. In this context, 'subgraph' always means a 'node-induced subgraph'. Edge-induced subgraph isomorphisms are not directly supported, but one should be able to perform the check by making use of nx.line_graph(). For subgraphs which are not induced, the term 'monomorphism' is preferred over 'isomorphism'. Currently, it is not possible to check for monomorphisms. Let G=(N,E) be a graph with a set of nodes N and set of edges E. If G'=(N',E') is a subgraph, then: N' is a subset of N E' is a subset of E If G'=(N',E') is a node-induced subgraph, then: N' is a subset of N E' is the subset of edges in E relating nodes in N' If G'=(N',E') is an edge-induced subgraph, then: N' is the subset of nodes in N related by edges in E' E' is a subset of E References ---------- [1] Luigi P. Cordella, Pasquale Foggia, Carlo Sansone, Mario Vento, "A (Sub)Graph Isomorphism Algorithm for Matching Large Graphs", IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 26, no. 10, pp. 1367-1372, Oct., 2004. http://ieeexplore.ieee.org/iel5/34/29305/01323804.pdf [2] L. P. Cordella, P. Foggia, C. Sansone, M. Vento, "An Improved Algorithm for Matching Large Graphs", 3rd IAPR-TC15 Workshop on Graph-based Representations in Pattern Recognition, Cuen, pp. 149-159, 2001. http://amalfi.dis.unina.it/graph/db/papers/vf-algorithm.pdf See Also -------- syntactic_feasibliity(), semantic_feasibility() Notes ----- The implementation handles both directed and undirected graphs as well as multigraphs. However, it does require that nodes in the graph are orderable (in addition to the general NetworkX requirement that nodes are hashable). If the nodes in your graph are not orderable, you can convert them to an orderable type (`int`, for example) by using the :func:`networkx.relabel` function. You can store the dictionary of old-to-new node labels to retrieve the original node labels after running the isomorphism algorithm:: >>> G = nx.Graph() >>> node1, node2 = object(), object() >>> G.add_nodes_from([node1, node2]) >>> mapping = {k: v for v, k in enumerate(G)} >>> G = nx.relabel_nodes(G, mapping) In general, the subgraph isomorphism problem is NP-complete whereas the graph isomorphism problem is most likely not NP-complete (although no polynomial-time algorithm is known to exist). iNt GraphMatchertDiGraphMatchercBsqeZdZdZdZdZdZdZdZdZ dZ d Z d Z d Z RS( svImplementation of VF2 algorithm for matching undirected graphs. Suitable for Graph and MultiGraph instances. cCs||_||_t|j|_t|j|_tj|_t |j}|jd|krtj t d|nd|_ |j dS(sInitialize GraphMatcher. Parameters ---------- G1,G2: NetworkX Graph or MultiGraph instances. The two graphs to check for isomorphism. Examples -------- To create a GraphMatcher which checks for syntactic feasibility: >>> from networkx.algorithms import isomorphism >>> G1 = nx.path_graph(4) >>> G2 = nx.path_graph(4) >>> GM = isomorphism.GraphMatcher(G1,G2) g?tgraphN(tG1tG2tsettnodestG1_nodestG2_nodestsystgetrecursionlimittold_recursion_limittlentsetrecursionlimittintttestt initialize(tselfRRtexpected_max_recursion_level((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyt__init__s   cCstj|jdS(sRestores the recursion limit.N(R R R (R((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pytreset_recursion_limits ccs|j}|j}g|D]*}||jkr||jkr|^q}g|D]*}||jkrP||jkrP|^qP}|r|rxl|D]}|t|fVqWnJt|t|j}x.|jD]#}||jkr||fVqqWdS(s4Iterator over candidate pairs of nodes in G1 and G2.N( RRtinout_1tcore_1tinout_2tcore_2tminRR(RRRtnodetT1_inouttT2_inoutt other_node((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pytcandidate_pairs_iters  77  cCsIi|_i|_i|_i|_t||_|jj|_dS(sReinitializes the state of the algorithm. This method should be redefined if using something other than GMState. If only subclassing GraphMatcher, a redefinition is not necessary. N(RRRRtGMStatetstatetcopytmapping(R((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyRs   cCs|jj|jjkr"tStd|jjD}td|jjD}||krptSyt|j}tSWnt k rtSXdS(s0Returns True if G1 and G2 are isomorphic graphs.css|]\}}|VqdS(N((t.0tntd((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pys scss|]\}}|VqdS(N((R#R$R%((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pys sN( RtorderRtFalsetsortedtdegreetnexttisomorphisms_itertTruet StopIteration(Rtd1td2tx((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyt is_isomorphics  ccs3d|_|jx|jD] }|Vq WdS(s.Generator over isomorphisms between G1 and G2.RN(RRtmatch(RR"((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyR+&s  ccst|jt|jkr;|jj|_|jVnx|jD]t\}}|j||rH|j||r|jj |||}x|j D] }|VqW|j qqHqHWdS(s%Extends the isomorphism mapping. This function is called recursively to determine if a complete isomorphism can be found between G1 and G2. It cleans up the class variables after each recursive call. If an isomorphism is found, we yield the mapping. N( R RRR!R"Rtsyntactic_feasibilitytsemantic_feasibilityR t __class__R2trestore(RtG1_nodetG2_nodetnewstateR"((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyR2.s   cCstS(s(Returns True if adding (G1_node, G2_node) is symantically feasible. The semantic feasibility function should return True if it is acceptable to add the candidate pair (G1_node, G2_node) to the current partial isomorphism mapping. The logic should focus on semantic information contained in the edge data or a formalized node class. By acceptable, we mean that the subsequent mapping can still become a complete isomorphism mapping. Thus, if adding the candidate pair definitely makes it so that the subsequent mapping cannot become a complete isomorphism mapping, then this function must return False. The default semantic feasibility function always returns True. The effect is that semantics are not considered in the matching of G1 and G2. The semantic checks might differ based on the what type of test is being performed. A keyword description of the test is stored in self.test. Here is a quick description of the currently implemented tests:: test='graph' Indicates that the graph matcher is looking for a graph-graph isomorphism. test='subgraph' Indicates that the graph matcher is looking for a subgraph-graph isomorphism such that a subgraph of G1 is isomorphic to G2. Any subclass which redefines semantic_feasibility() must maintain the above form to keep the match() method functional. Implementations should consider multigraphs. (R,(RR7R8((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyR4Hs"cCs3yt|j}tSWntk r.tSXdS(s5Returns True if a subgraph of G1 is isomorphic to G2.N(R*tsubgraph_isomorphisms_iterR,R-R'(RR0((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pytsubgraph_is_isomorphicls  ccs3d|_|jx|jD] }|Vq WdS(s<Generator over isomorphisms between a subgraph of G1 and G2.tsubgraphN(RRR2(RR"((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyR:vs  cCs|jj|||jj||kr.tSxz|j|D]k}||jkr<|j||j|krotS|jj|||jj|j||krtSq<q<Wxz|j|D]k}||jkr|j||j|krtS|jj|j|||jj||kr$tSqqWd}x@|j|D]1}||jkr<||jkr<|d7}q<q<Wd}x@|j|D]1}||jkr||jkr|d7}qqW|jdkr||kstSn||kstSd}x1|j|D]"}||jkr|d7}qqWd}x1|j|D]"}||jkr:|d7}q:q:W|jdkr||kstSn||kstSt S(scReturns True if adding (G1_node, G2_node) is syntactically feasible. This function returns True if it is adding the candidate pair to the current partial isomorphism mapping is allowable. The addition is allowable if the inclusion of the candidate pair does not make it impossible for an isomorphism to be found. iiR( Rtnumber_of_edgesRR'RRRRRR,(RR7R8tneighbortnum1tnum2((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyR3sR$*1 1      (t__name__t __module__t__doc__RRRRR1R+R2R4R;R:R3(((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyRs #      $ cBs2eZdZdZdZdZdZRS(sxImplementation of VF2 algorithm for matching directed graphs. Suitable for DiGraph and MultiDiGraph instances. cCstt|j||dS(sInitialize DiGraphMatcher. G1 and G2 should be nx.Graph or nx.MultiGraph instances. Examples -------- To create a GraphMatcher which checks for syntactic feasibility: >>> from networkx.algorithms import isomorphism >>> G1 = nx.DiGraph(nx.path_graph(4, create_using=nx.DiGraph())) >>> G2 = nx.DiGraph(nx.path_graph(4, create_using=nx.DiGraph())) >>> DiGM = isomorphism.DiGraphMatcher(G1,G2) N(tsuperRR(RRR((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyRsc cs|j}|j}g|D]*}||jkr||jkr|^q}g|D]*}||jkrP||jkrP|^qP}|r|rt|}x|D]}||fVqWng|D]*}||jkr||jkr|^q}g|D]*}||jkr||jkr|^q} |r\| r\t| }xc|D]}||fVqDWnGt|t |j}x+|D]#}||jkr|||fVq|q|WdS(s4Iterator over candidate pairs of nodes in G1 and G2.N( RRtout_1Rtout_2RRtin_1tin_2R( RRRRtT1_outtT2_outtnode_2tnode_1tT1_intT2_in((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyRs$  77   77    cCs[i|_i|_i|_i|_i|_i|_t||_|jj|_ dS(sReinitializes the state of the algorithm. This method should be redefined if using something other than DiGMState. If only subclassing GraphMatcher, a redefinition is not necessary. N( RRRGRHRERFt DiGMStateR R!R"(R((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyR.s     cCs|jj|||jj||kr.tSx|jj|D]n}||jkr?|j||jj|krutS|jj|||jj|j||krtSq?q?Wx|jj|D]n}||jkr|j||jj|krtS|jj|j|||jj||kr0tSqqWxz|j|D]k}||jkrB|j||j|krutS|jj|||jj||j|krtSqBqBWxz|j|D]k}||jkr|j||j|krtS|jj||j||jj||kr*tSqqWd}xC|jj|D]1}||jkrE||jkrE|d7}qEqEWd}xC|jj|D]1}||jkr||jkr|d7}qqW|j dkr||kstSn||kstSd}x@|j|D]1}||jkr ||jkr |d7}q q Wd}x@|j|D]1}||jkrU||jkrU|d7}qUqUW|j dkr||kstSn||kstSd}xC|jj|D]1}||j kr||jkr|d7}qqWd}xC|jj|D]1}||j kr||jkr|d7}qqW|j dkrv||kstSn||kstSd}x@|j|D]1}||j kr||jkr|d7}qqWd}x@|j|D]1}||j kr||jkr|d7}qqW|j dkr:||ksJtSn||ksJtSd}xC|jj|D]1}||jkra||j kra|d7}qaqaWd}xC|jj|D]1}||jkr||j kr|d7}qqW|j dkr||kstSn||kstSd}x@|j|D]1}||jkr(||j kr(|d7}q(q(Wd}x@|j|D]1}||jkrq||j krq|d7}qqqqW|j dkr||kstSn||kstSt S(scReturns True if adding (G1_node, G2_node) is syntactically feasible. This function returns True if it is adding the candidate pair to the current partial isomorphism mapping is allowable. The addition is allowable if the inclusion of the candidate pair does not make it impossible for an isomorphism to be found. iiR( RR=RR'tpredRRRGRHRRERFR,(RR7R8t predecessort successorR?R@((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyR3Ps&*1 1 1 1              (RARBRCRRRR3(((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyRs   . "RcBs&eZdZdddZdZRS(sGInternal representation of state for the GraphMatcher class. This class is used internally by the GraphMatcher class. It is used only to store state specific data. There will be at most G2.order() of these objects in memory at a time, due to the depth-first search strategy employed by the VF2 algorithm. cCs||_d|_d|_t|j|_|dksE|dkrli|_i|_i|_i|_ n|dk r|dk r||j|<||j|<||_||_t|j|_||jkr|j|j|((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyRs>            :  : cCs|jdk rA|jdk rA|jj|j=|jj|j=nxZ|jj|jjfD]@}x7t|j D]#}|||j krs||=qsqsWqZWdS(s<Deletes the GMState object and restores the class variables.N( R7RTR8RSRRRRtlisttkeysRU(RtvectorR((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyR6ZsN(RARBRCRTRR6(((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyRs;ROcBs&eZdZdddZdZRS(sLInternal representation of state for the DiGraphMatcher class. This class is used internally by the DiGraphMatcher class. It is used only to store state specific data. There will be at most G2.order() of these objects in memory at a time, due to the depth-first search strategy employed by the VF2 algorithm. c Cs||_d|_d|_t|j|_|dksE|dkr~i|_i|_i|_i|_ i|_ i|_ n|dk r|dk r||j|<||j|<||_||_t|j|_x6|j|j fD]"}||kr|j||Deletes the DiGMState object and restores the class variables.N( R7RTR8RSRRRGRHRERFRXRYRU(RRZR((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyR6s1N(RARBRCRTRR6(((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyROjsO( RCR tnetworkxtnxt__all__tobjectRRRRO(((s/private/var/folders/w6/vb91730s7bb1k90y_rnhql1dhvdd44/T/pip-build-w4MwvS/networkx/networkx/algorithms/isomorphism/isomorphvf2.pyts   L/S