diff options
Diffstat (limited to 'lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/isomorphvf2.py')
-rw-r--r-- | lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/isomorphvf2.py | 965 |
1 files changed, 0 insertions, 965 deletions
diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/isomorphvf2.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/isomorphvf2.py deleted file mode 100644 index 1efe74d..0000000 --- a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/isomorphvf2.py +++ /dev/null @@ -1,965 +0,0 @@ -# -*- coding: utf-8 -*- -""" -************* -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 subgrpah, 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 ------ -Modified to handle undirected graphs. -Modified to handle multiple edges. - - -In general, this problem is NP-Complete. - - - -""" - -# Copyright (C) 2007-2009 by the NetworkX maintainers -# All rights reserved. -# BSD license. - -# This work was originally coded by Christopher Ellison -# as part of the Computational Mechanics Python (CMPy) project. -# James P. Crutchfield, principal investigator. -# Complexity Sciences Center and Physics Department, UC Davis. - -import sys -import networkx as nx - -__all__ = ['GraphMatcher', - 'DiGraphMatcher'] - -class GraphMatcher(object): - """Implementation of VF2 algorithm for matching undirected graphs. - - Suitable for Graph and MultiGraph instances. - """ - def __init__(self, G1, G2): - """Initialize 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) - """ - self.G1 = G1 - self.G2 = G2 - self.G1_nodes = set(G1.nodes()) - self.G2_nodes = set(G2.nodes()) - - # Set recursion limit. - self.old_recursion_limit = sys.getrecursionlimit() - expected_max_recursion_level = len(self.G2) - if self.old_recursion_limit < 1.5 * expected_max_recursion_level: - # Give some breathing room. - sys.setrecursionlimit(int(1.5 * expected_max_recursion_level)) - - # Declare that we will be searching for a graph-graph isomorphism. - self.test = 'graph' - - # Initialize state - self.initialize() - - def reset_recursion_limit(self): - """Restores the recursion limit.""" - ### TODO: - ### Currently, we use recursion and set the recursion level higher. - ### It would be nice to restore the level, but because the - ### (Di)GraphMatcher classes make use of cyclic references, garbage - ### collection will never happen when we define __del__() to - ### restore the recursion level. The result is a memory leak. - ### So for now, we do not automatically restore the recursion level, - ### and instead provide a method to do this manually. Eventually, - ### we should turn this into a non-recursive implementation. - sys.setrecursionlimit(self.old_recursion_limit) - - def candidate_pairs_iter(self): - """Iterator over candidate pairs of nodes in G1 and G2.""" - - # All computations are done using the current state! - - G1_nodes = self.G1_nodes - G2_nodes = self.G2_nodes - - # First we compute the inout-terminal sets. - T1_inout = [node for node in G1_nodes if (node in self.inout_1) and (node not in self.core_1)] - T2_inout = [node for node in G2_nodes if (node in self.inout_2) and (node not in self.core_2)] - - # If T1_inout and T2_inout are both nonempty. - # P(s) = T1_inout x {min T2_inout} - if T1_inout and T2_inout: - for node in T1_inout: - yield node, min(T2_inout) - - else: - # If T1_inout and T2_inout were both empty.... - # P(s) = (N_1 - M_1) x {min (N_2 - M_2)} - ##if not (T1_inout or T2_inout): # as suggested by [2], incorrect - if 1: # as inferred from [1], correct - # First we determine the candidate node for G2 - other_node = min(G2_nodes - set(self.core_2)) - for node in self.G1: - if node not in self.core_1: - yield node, other_node - - # For all other cases, we don't have any candidate pairs. - - def initialize(self): - """Reinitializes 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. - - """ - - # core_1[n] contains the index of the node paired with n, which is m, - # provided n is in the mapping. - # core_2[m] contains the index of the node paired with m, which is n, - # provided m is in the mapping. - self.core_1 = {} - self.core_2 = {} - - # See the paper for definitions of M_x and T_x^{y} - - # inout_1[n] is non-zero if n is in M_1 or in T_1^{inout} - # inout_2[m] is non-zero if m is in M_2 or in T_2^{inout} - # - # The value stored is the depth of the SSR tree when the node became - # part of the corresponding set. - self.inout_1 = {} - self.inout_2 = {} - # Practically, these sets simply store the nodes in the subgraph. - - self.state = GMState(self) - - # Provide a convienient way to access the isomorphism mapping. - self.mapping = self.core_1.copy() - - def is_isomorphic(self): - """Returns True if G1 and G2 are isomorphic graphs.""" - - # Let's do two very quick checks! - # QUESTION: Should we call faster_graph_could_be_isomorphic(G1,G2)? - # For now, I just copy the code. - - # Check global properties - if self.G1.order() != self.G2.order(): return False - - # Check local properties - d1=sorted(self.G1.degree().values()) - d2=sorted(self.G2.degree().values()) - if d1 != d2: return False - - try: - x = next(self.isomorphisms_iter()) - return True - except StopIteration: - return False - - def isomorphisms_iter(self): - """Generator over isomorphisms between G1 and G2.""" - # Declare that we are looking for a graph-graph isomorphism. - self.test = 'graph' - self.initialize() - for mapping in self.match(): - yield mapping - - def match(self): - """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. - - """ - if len(self.core_1) == len(self.G2): - # Save the final mapping, otherwise garbage collection deletes it. - self.mapping = self.core_1.copy() - # The mapping is complete. - yield self.mapping - else: - for G1_node, G2_node in self.candidate_pairs_iter(): - if self.syntactic_feasibility(G1_node, G2_node): - if self.semantic_feasibility(G1_node, G2_node): - # Recursive call, adding the feasible state. - newstate = self.state.__class__(self, G1_node, G2_node) - for mapping in self.match(): - yield mapping - - # restore data structures - newstate.restore() - - def semantic_feasibility(self, G1_node, G2_node): - """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. - """ - return True - - def subgraph_is_isomorphic(self): - """Returns True if a subgraph of G1 is isomorphic to G2.""" - try: - x = next(self.subgraph_isomorphisms_iter()) - return True - except StopIteration: - return False - -# subgraph_is_isomorphic.__doc__ += "\n" + subgraph.replace('\n','\n'+indent) - - def subgraph_isomorphisms_iter(self): - """Generator over isomorphisms between a subgraph of G1 and G2.""" - # Declare that we are looking for graph-subgraph isomorphism. - self.test = 'subgraph' - self.initialize() - for mapping in self.match(): - yield mapping - -# subgraph_isomorphisms_iter.__doc__ += "\n" + subgraph.replace('\n','\n'+indent) - - def syntactic_feasibility(self, G1_node, G2_node): - """Returns 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. - """ - - # The VF2 algorithm was designed to work with graphs having, at most, - # one edge connecting any two nodes. This is not the case when - # dealing with an MultiGraphs. - # - # Basically, when we test the look-ahead rules R_neighbor, we will - # make sure that the number of edges are checked. We also add - # a R_self check to verify that the number of selfloops is acceptable. - # - # Users might be comparing Graph instances with MultiGraph instances. - # So the generic GraphMatcher class must work with MultiGraphs. - # Care must be taken since the value in the innermost dictionary is a - # singlet for Graph instances. For MultiGraphs, the value in the - # innermost dictionary is a list. - - - ### - ### Test at each step to get a return value as soon as possible. - ### - - ### Look ahead 0 - - # R_self - - # The number of selfloops for G1_node must equal the number of - # self-loops for G2_node. Without this check, we would fail on - # R_neighbor at the next recursion level. But it is good to prune the - # search tree now. - if self.G1.number_of_edges(G1_node,G1_node) != self.G2.number_of_edges(G2_node,G2_node): - return False - - - # R_neighbor - - # For each neighbor n' of n in the partial mapping, the corresponding - # node m' is a neighbor of m, and vice versa. Also, the number of - # edges must be equal. - for neighbor in self.G1[G1_node]: - if neighbor in self.core_1: - if not (self.core_1[neighbor] in self.G2[G2_node]): - return False - elif self.G1.number_of_edges(neighbor, G1_node) != self.G2.number_of_edges(self.core_1[neighbor], G2_node): - return False - for neighbor in self.G2[G2_node]: - if neighbor in self.core_2: - if not (self.core_2[neighbor] in self.G1[G1_node]): - return False - elif self.G1.number_of_edges(self.core_2[neighbor], G1_node) != self.G2.number_of_edges(neighbor, G2_node): - return False - - ### Look ahead 1 - - # R_terminout - # The number of neighbors of n that are in T_1^{inout} is equal to the - # number of neighbors of m that are in T_2^{inout}, and vice versa. - num1 = 0 - for neighbor in self.G1[G1_node]: - if (neighbor in self.inout_1) and (neighbor not in self.core_1): - num1 += 1 - num2 = 0 - for neighbor in self.G2[G2_node]: - if (neighbor in self.inout_2) and (neighbor not in self.core_2): - num2 += 1 - if self.test == 'graph': - if not (num1 == num2): - return False - else: # self.test == 'subgraph' - if not (num1 >= num2): - return False - - - ### Look ahead 2 - - # R_new - - # The number of neighbors of n that are neither in the core_1 nor - # T_1^{inout} is equal to the number of neighbors of m - # that are neither in core_2 nor T_2^{inout}. - num1 = 0 - for neighbor in self.G1[G1_node]: - if neighbor not in self.inout_1: - num1 += 1 - num2 = 0 - for neighbor in self.G2[G2_node]: - if neighbor not in self.inout_2: - num2 += 1 - if self.test == 'graph': - if not (num1 == num2): - return False - else: # self.test == 'subgraph' - if not (num1 >= num2): - return False - - # Otherwise, this node pair is syntactically feasible! - return True - - -class DiGraphMatcher(GraphMatcher): - """Implementation of VF2 algorithm for matching directed graphs. - - Suitable for DiGraph and MultiDiGraph instances. - """ -# __doc__ += "Notes\n%s-----" % (indent,) + sources.replace('\n','\n'+indent) - - def __init__(self, G1, G2): - """Initialize 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) - """ - super(DiGraphMatcher, self).__init__(G1, G2) - - def candidate_pairs_iter(self): - """Iterator over candidate pairs of nodes in G1 and G2.""" - - # All computations are done using the current state! - - G1_nodes = self.G1_nodes - G2_nodes = self.G2_nodes - - # First we compute the out-terminal sets. - T1_out = [node for node in G1_nodes if (node in self.out_1) and (node not in self.core_1)] - T2_out = [node for node in G2_nodes if (node in self.out_2) and (node not in self.core_2)] - - # If T1_out and T2_out are both nonempty. - # P(s) = T1_out x {min T2_out} - if T1_out and T2_out: - node_2 = min(T2_out) - for node_1 in T1_out: - yield node_1, node_2 - - # If T1_out and T2_out were both empty.... - # We compute the in-terminal sets. - - ##elif not (T1_out or T2_out): # as suggested by [2], incorrect - else: # as suggested by [1], correct - T1_in = [node for node in G1_nodes if (node in self.in_1) and (node not in self.core_1)] - T2_in = [node for node in G2_nodes if (node in self.in_2) and (node not in self.core_2)] - - # If T1_in and T2_in are both nonempty. - # P(s) = T1_out x {min T2_out} - if T1_in and T2_in: - node_2 = min(T2_in) - for node_1 in T1_in: - yield node_1, node_2 - - # If all terminal sets are empty... - # P(s) = (N_1 - M_1) x {min (N_2 - M_2)} - - ##elif not (T1_in or T2_in): # as suggested by [2], incorrect - else: # as inferred from [1], correct - node_2 = min(G2_nodes - set(self.core_2)) - for node_1 in G1_nodes: - if node_1 not in self.core_1: - yield node_1, node_2 - - # For all other cases, we don't have any candidate pairs. - - def initialize(self): - """Reinitializes 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. - """ - - # core_1[n] contains the index of the node paired with n, which is m, - # provided n is in the mapping. - # core_2[m] contains the index of the node paired with m, which is n, - # provided m is in the mapping. - self.core_1 = {} - self.core_2 = {} - - # See the paper for definitions of M_x and T_x^{y} - - # in_1[n] is non-zero if n is in M_1 or in T_1^{in} - # out_1[n] is non-zero if n is in M_1 or in T_1^{out} - # - # in_2[m] is non-zero if m is in M_2 or in T_2^{in} - # out_2[m] is non-zero if m is in M_2 or in T_2^{out} - # - # The value stored is the depth of the search tree when the node became - # part of the corresponding set. - self.in_1 = {} - self.in_2 = {} - self.out_1 = {} - self.out_2 = {} - - self.state = DiGMState(self) - - # Provide a convienient way to access the isomorphism mapping. - self.mapping = self.core_1.copy() - - def syntactic_feasibility(self, G1_node, G2_node): - """Returns 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. - """ - - # The VF2 algorithm was designed to work with graphs having, at most, - # one edge connecting any two nodes. This is not the case when - # dealing with an MultiGraphs. - # - # Basically, when we test the look-ahead rules R_pred and R_succ, we - # will make sure that the number of edges are checked. We also add - # a R_self check to verify that the number of selfloops is acceptable. - - # Users might be comparing DiGraph instances with MultiDiGraph - # instances. So the generic DiGraphMatcher class must work with - # MultiDiGraphs. Care must be taken since the value in the innermost - # dictionary is a singlet for DiGraph instances. For MultiDiGraphs, - # the value in the innermost dictionary is a list. - - - ### - ### Test at each step to get a return value as soon as possible. - ### - - - - ### Look ahead 0 - - # R_self - - # The number of selfloops for G1_node must equal the number of - # self-loops for G2_node. Without this check, we would fail on R_pred - # at the next recursion level. This should prune the tree even further. - - if self.G1.number_of_edges(G1_node,G1_node) != self.G2.number_of_edges(G2_node,G2_node): - return False - - - # R_pred - - # For each predecessor n' of n in the partial mapping, the - # corresponding node m' is a predecessor of m, and vice versa. Also, - # the number of edges must be equal - for predecessor in self.G1.pred[G1_node]: - if predecessor in self.core_1: - if not (self.core_1[predecessor] in self.G2.pred[G2_node]): - return False - elif self.G1.number_of_edges(predecessor, G1_node) != self.G2.number_of_edges(self.core_1[predecessor], G2_node): - return False - - for predecessor in self.G2.pred[G2_node]: - if predecessor in self.core_2: - if not (self.core_2[predecessor] in self.G1.pred[G1_node]): - return False - elif self.G1.number_of_edges(self.core_2[predecessor], G1_node) != self.G2.number_of_edges(predecessor, G2_node): - return False - - - # R_succ - - # For each successor n' of n in the partial mapping, the corresponding - # node m' is a successor of m, and vice versa. Also, the number of - # edges must be equal. - for successor in self.G1[G1_node]: - if successor in self.core_1: - if not (self.core_1[successor] in self.G2[G2_node]): - return False - elif self.G1.number_of_edges(G1_node, successor) != self.G2.number_of_edges(G2_node, self.core_1[successor]): - return False - - for successor in self.G2[G2_node]: - if successor in self.core_2: - if not (self.core_2[successor] in self.G1[G1_node]): - return False - elif self.G1.number_of_edges(G1_node, self.core_2[successor]) != self.G2.number_of_edges(G2_node, successor): - return False - - - ### Look ahead 1 - - # R_termin - # The number of predecessors of n that are in T_1^{in} is equal to the - # number of predecessors of m that are in T_2^{in}. - num1 = 0 - for predecessor in self.G1.pred[G1_node]: - if (predecessor in self.in_1) and (predecessor not in self.core_1): - num1 += 1 - num2 = 0 - for predecessor in self.G2.pred[G2_node]: - if (predecessor in self.in_2) and (predecessor not in self.core_2): - num2 += 1 - if self.test == 'graph': - if not (num1 == num2): - return False - else: # self.test == 'subgraph' - if not (num1 >= num2): - return False - - # The number of successors of n that are in T_1^{in} is equal to the - # number of successors of m that are in T_2^{in}. - num1 = 0 - for successor in self.G1[G1_node]: - if (successor in self.in_1) and (successor not in self.core_1): - num1 += 1 - num2 = 0 - for successor in self.G2[G2_node]: - if (successor in self.in_2) and (successor not in self.core_2): - num2 += 1 - if self.test == 'graph': - if not (num1 == num2): - return False - else: # self.test == 'subgraph' - if not (num1 >= num2): - return False - - # R_termout - - # The number of predecessors of n that are in T_1^{out} is equal to the - # number of predecessors of m that are in T_2^{out}. - num1 = 0 - for predecessor in self.G1.pred[G1_node]: - if (predecessor in self.out_1) and (predecessor not in self.core_1): - num1 += 1 - num2 = 0 - for predecessor in self.G2.pred[G2_node]: - if (predecessor in self.out_2) and (predecessor not in self.core_2): - num2 += 1 - if self.test == 'graph': - if not (num1 == num2): - return False - else: # self.test == 'subgraph' - if not (num1 >= num2): - return False - - # The number of successors of n that are in T_1^{out} is equal to the - # number of successors of m that are in T_2^{out}. - num1 = 0 - for successor in self.G1[G1_node]: - if (successor in self.out_1) and (successor not in self.core_1): - num1 += 1 - num2 = 0 - for successor in self.G2[G2_node]: - if (successor in self.out_2) and (successor not in self.core_2): - num2 += 1 - if self.test == 'graph': - if not (num1 == num2): - return False - else: # self.test == 'subgraph' - if not (num1 >= num2): - return False - - ### Look ahead 2 - - # R_new - - # The number of predecessors of n that are neither in the core_1 nor - # T_1^{in} nor T_1^{out} is equal to the number of predecessors of m - # that are neither in core_2 nor T_2^{in} nor T_2^{out}. - num1 = 0 - for predecessor in self.G1.pred[G1_node]: - if (predecessor not in self.in_1) and (predecessor not in self.out_1): - num1 += 1 - num2 = 0 - for predecessor in self.G2.pred[G2_node]: - if (predecessor not in self.in_2) and (predecessor not in self.out_2): - num2 += 1 - if self.test == 'graph': - if not (num1 == num2): - return False - else: # self.test == 'subgraph' - if not (num1 >= num2): - return False - - # The number of successors of n that are neither in the core_1 nor - # T_1^{in} nor T_1^{out} is equal to the number of successors of m - # that are neither in core_2 nor T_2^{in} nor T_2^{out}. - num1 = 0 - for successor in self.G1[G1_node]: - if (successor not in self.in_1) and (successor not in self.out_1): - num1 += 1 - num2 = 0 - for successor in self.G2[G2_node]: - if (successor not in self.in_2) and (successor not in self.out_2): - num2 += 1 - if self.test == 'graph': - if not (num1 == num2): - return False - else: # self.test == 'subgraph' - if not (num1 >= num2): - return False - - # Otherwise, this node pair is syntactically feasible! - return True - - -class GMState(object): - """Internal 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. - """ - def __init__(self, GM, G1_node=None, G2_node=None): - """Initializes GMState object. - - Pass in the GraphMatcher to which this GMState belongs and the - new node pair that will be added to the GraphMatcher's current - isomorphism mapping. - """ - self.GM = GM - - # Initialize the last stored node pair. - self.G1_node = None - self.G2_node = None - self.depth = len(GM.core_1) - - if G1_node is None or G2_node is None: - # Then we reset the class variables - GM.core_1 = {} - GM.core_2 = {} - GM.inout_1 = {} - GM.inout_2 = {} - - # Watch out! G1_node == 0 should evaluate to True. - if G1_node is not None and G2_node is not None: - # Add the node pair to the isomorphism mapping. - GM.core_1[G1_node] = G2_node - GM.core_2[G2_node] = G1_node - - # Store the node that was added last. - self.G1_node = G1_node - self.G2_node = G2_node - - # Now we must update the other two vectors. - # We will add only if it is not in there already! - self.depth = len(GM.core_1) - - # First we add the new nodes... - if G1_node not in GM.inout_1: - GM.inout_1[G1_node] = self.depth - if G2_node not in GM.inout_2: - GM.inout_2[G2_node] = self.depth - - # Now we add every other node... - - # Updates for T_1^{inout} - new_nodes = set([]) - for node in GM.core_1: - new_nodes.update([neighbor for neighbor in GM.G1[node] if neighbor not in GM.core_1]) - for node in new_nodes: - if node not in GM.inout_1: - GM.inout_1[node] = self.depth - - # Updates for T_2^{inout} - new_nodes = set([]) - for node in GM.core_2: - new_nodes.update([neighbor for neighbor in GM.G2[node] if neighbor not in GM.core_2]) - for node in new_nodes: - if node not in GM.inout_2: - GM.inout_2[node] = self.depth - - def restore(self): - """Deletes the GMState object and restores the class variables.""" - # First we remove the node that was added from the core vectors. - # Watch out! G1_node == 0 should evaluate to True. - if self.G1_node is not None and self.G2_node is not None: - del self.GM.core_1[self.G1_node] - del self.GM.core_2[self.G2_node] - - # Now we revert the other two vectors. - # Thus, we delete all entries which have this depth level. - for vector in (self.GM.inout_1, self.GM.inout_2): - for node in list(vector.keys()): - if vector[node] == self.depth: - del vector[node] - - -class DiGMState(object): - """Internal 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. - - """ - def __init__(self, GM, G1_node=None, G2_node=None): - """Initializes DiGMState object. - - Pass in the DiGraphMatcher to which this DiGMState belongs and the - new node pair that will be added to the GraphMatcher's current - isomorphism mapping. - """ - self.GM = GM - - # Initialize the last stored node pair. - self.G1_node = None - self.G2_node = None - self.depth = len(GM.core_1) - - if G1_node is None or G2_node is None: - # Then we reset the class variables - GM.core_1 = {} - GM.core_2 = {} - GM.in_1 = {} - GM.in_2 = {} - GM.out_1 = {} - GM.out_2 = {} - - # Watch out! G1_node == 0 should evaluate to True. - if G1_node is not None and G2_node is not None: - # Add the node pair to the isomorphism mapping. - GM.core_1[G1_node] = G2_node - GM.core_2[G2_node] = G1_node - - # Store the node that was added last. - self.G1_node = G1_node - self.G2_node = G2_node - - # Now we must update the other four vectors. - # We will add only if it is not in there already! - self.depth = len(GM.core_1) - - # First we add the new nodes... - for vector in (GM.in_1, GM.out_1): - if G1_node not in vector: - vector[G1_node] = self.depth - for vector in (GM.in_2, GM.out_2): - if G2_node not in vector: - vector[G2_node] = self.depth - - # Now we add every other node... - - # Updates for T_1^{in} - new_nodes = set([]) - for node in GM.core_1: - new_nodes.update([predecessor for predecessor in GM.G1.predecessors(node) if predecessor not in GM.core_1]) - for node in new_nodes: - if node not in GM.in_1: - GM.in_1[node] = self.depth - - # Updates for T_2^{in} - new_nodes = set([]) - for node in GM.core_2: - new_nodes.update([predecessor for predecessor in GM.G2.predecessors(node) if predecessor not in GM.core_2]) - for node in new_nodes: - if node not in GM.in_2: - GM.in_2[node] = self.depth - - # Updates for T_1^{out} - new_nodes = set([]) - for node in GM.core_1: - new_nodes.update([successor for successor in GM.G1.successors(node) if successor not in GM.core_1]) - for node in new_nodes: - if node not in GM.out_1: - GM.out_1[node] = self.depth - - # Updates for T_2^{out} - new_nodes = set([]) - for node in GM.core_2: - new_nodes.update([successor for successor in GM.G2.successors(node) if successor not in GM.core_2]) - for node in new_nodes: - if node not in GM.out_2: - GM.out_2[node] = self.depth - - def restore(self): - """Deletes the DiGMState object and restores the class variables.""" - - # First we remove the node that was added from the core vectors. - # Watch out! G1_node == 0 should evaluate to True. - if self.G1_node is not None and self.G2_node is not None: - del self.GM.core_1[self.G1_node] - del self.GM.core_2[self.G2_node] - - # Now we revert the other four vectors. - # Thus, we delete all entries which have this depth level. - for vector in (self.GM.in_1, self.GM.in_2, self.GM.out_1, self.GM.out_2): - for node in list(vector.keys()): - if vector[node] == self.depth: - del vector[node] - |