def test_add_edge_attrs(self): g = self.graph.to_undirected() new_attrs = {"b": FiniteSet({1})} add_edge_attrs(g, '1', '2', new_attrs) normalize_attrs(new_attrs) assert (valid_attributes(new_attrs, g.edge['1']['2'])) assert (valid_attributes(new_attrs, g.edge['2']['1']))
def test_merge_nodes(self): g = self.graph.to_undirected() old_attrs1 = self.graph.node['8'] old_attrs2 = self.graph.node['9'] old_edge_attrs1 = self.graph.edge['10']['8'] old_edge_attrs2 = self.graph.edge['10']['9'] new_name = merge_nodes(self.graph, ["8", "9"]) assert(new_name in self.graph.nodes()) assert("8" not in self.graph.nodes()) assert("9" not in self.graph.nodes()) assert(valid_attributes(old_attrs1, self.graph.node[new_name])) assert(valid_attributes(old_attrs2, self.graph.node[new_name])) assert((new_name, new_name) in self.graph.edges()) assert(valid_attributes(old_edge_attrs1, self.graph.edge['10'][new_name])) assert(valid_attributes(old_edge_attrs2, self.graph.edge['10'][new_name])) # test undirected case old_attrs1 = g.node['8'] old_attrs2 = g.node['9'] old_edge_attrs1 = g.edge['10']['8'] old_edge_attrs2 = g.edge['10']['9'] new_name = merge_nodes(g, ["8", "9"]) assert(new_name in g.nodes()) assert("8" not in g.nodes()) assert("9" not in g.nodes()) assert(valid_attributes(old_attrs1, g.node[new_name])) assert(valid_attributes(old_attrs2, g.node[new_name])) assert((new_name, new_name) in g.edges()) assert(valid_attributes(old_edge_attrs1, g.edge['10'][new_name])) assert(valid_attributes(old_edge_attrs1, g.edge[new_name]['10'])) assert(valid_attributes(old_edge_attrs2, g.edge['10'][new_name])) assert(valid_attributes(old_edge_attrs2, g.edge[new_name]['10'])) assert(g.edge['10'][new_name] == g.edge[new_name]['10']) assert(id(g.edge['10'][new_name]) == id(g.edge[new_name]['10']))
def test_add_edge_attrs(self): g = self.graph.to_undirected() new_attrs = {"b": FiniteSet({1})} add_edge_attrs(g, '1', '2', new_attrs) normalize_attrs(new_attrs) assert(valid_attributes(new_attrs, g.edge['1']['2'])) assert(valid_attributes(new_attrs, g.edge['2']['1']))
def check_homomorphism(source, target, dictionary, total=True): """Check if the homomorphism is valid. Valid homomorphism preserves edges, and attributes if requires. """ # check if there is mapping for all the nodes of source graph if total: check_totality(source.nodes(), dictionary) if not set(dictionary.values()).issubset(target.nodes()): raise InvalidHomomorphism( "Some of the image nodes in mapping %s do not " "exist in target graph (target graph nodes %s) " "namely %s" % (dictionary.values(), target.nodes(), set(dictionary.values()) - set(target.nodes()))) # check connectivity for s, t in source.edges(): try: if (s in dictionary.keys() and t in dictionary.keys() and not (dictionary[s], dictionary[t]) in target.edges()): if not target.is_directed(): if not (dictionary[t], dictionary[s]) in target.edges(): raise InvalidHomomorphism( "Connectivity is not preserved!" " Was expecting an edge '%s' and '%s'" % (dictionary[t], dictionary[s])) else: raise InvalidHomomorphism( "Connectivity is not preserved!" " Was expecting an edge between '%s' and '%s'" % (dictionary[s], dictionary[t])) except KeyError: pass for s, t in dictionary.items(): # check sets of attributes of nodes (here homomorphism = set # inclusion) if not valid_attributes(source.node[s], target.node[t]): raise InvalidHomomorphism("Attributes of nodes source:'%s' %s and " "target:'%s' %s do not match!" % (s, source.node[s], t, target.node[t])) # check sets of attributes of edges (homomorphism = set inclusion) for s1, s2 in source.edges(): try: if (s1 in dictionary.keys() and s2 in dictionary.keys() and not valid_attributes( source.edge[s1][s2], target.edge[dictionary[s1]][dictionary[s2]])): raise InvalidHomomorphism( "Attributes of edges (%s)-(%s) (%s) and " "(%s)-(%s) (%s) do not match!" % (s1, s2, source.edge[s1][s2], dictionary[s1], dictionary[s2], target.edge[dictionary[s1]][dictionary[s2]])) except KeyError: pass return True
def _compare_dicts(d1, d2): types1 = d1[typing_key] types2 = d2[typing_key] d1_without_types = copy.copy(d1) d2_without_types = copy.copy(d1) del d1_without_types[typing_key] del d2_without_types[typing_key] return (valid_attributes(types2, types1) and valid_attributes(d1_without_types, d2_without_types))
def _compare_dicts(d1, d2): types1 = d1[typing_key] types2 = d2[typing_key] d1_without_types = copy.copy(d1) d2_without_types = copy.copy(d1) del d1_without_types[typing_key] del d2_without_types[typing_key] return (valid_attributes(types2, types1) and valid_attributes(d1_without_types, d2_without_types))
def check_homomorphism(source, target, dictionary, total=True): """Check if the homomorphism is valid. Valid homomorphism preserves edges, and attributes if requires. """ # check if there is mapping for all the nodes of source graph if total: check_totality(source.nodes(), dictionary) if not set(dictionary.values()).issubset(target.nodes()): raise InvalidHomomorphism("The image nodes {} do not exist ".format( set(dictionary.values()) - set(target.nodes())) + "in the target graph (existing nodes '{}') ". format(target.nodes()) + "in dictionary '{}'".format(dictionary)) # check connectivity for s, t in source.edges(): try: if (s in dictionary.keys() and t in dictionary.keys() and not (dictionary[s], dictionary[t]) in target.edges()): raise InvalidHomomorphism( "Connectivity is not preserved!" " Was expecting an edge between '{}' and '{}'".format( dictionary[s], dictionary[t])) except KeyError: pass for s, t in dictionary.items(): # check sets of attributes of nodes (here homomorphism = set # inclusion) if not valid_attributes(source.get_node(s), target.get_node(t)): raise InvalidHomomorphism( "Attributes of nodes source: '{}' {} and ".format( s, source.get_node(s)) + "target: '{}' {} do not match!".format(t, target.get_node(t))) # check sets of attributes of edges (homomorphism = set inclusion) for s1, s2 in source.edges(): try: if (s1 in dictionary.keys() and s2 in dictionary.keys() and not valid_attributes( source.get_edge(s1, s2), target.get_edge(dictionary[s1], dictionary[s2]))): raise InvalidHomomorphism( "Attributes of edges ({})-({}) ({}) and ".format( s1, s2, source.get_edge(s1, s2)) + "({})-({}) ({}) do not match!".format( dictionary[s1], dictionary[s2], target.get_edge(dictionary[s1], dictionary[s2]))) except KeyError: pass return True
def test_merge_nodes(self): g = self.graph old_attrs1 = self.graph.get_node('8') old_attrs2 = self.graph.get_node('9') old_edge_attrs1 = self.graph.get_edge('10', '8') old_edge_attrs2 = self.graph.get_edge('10', '9') new_name = merge_nodes(self.graph, ["8", "9"]) assert (new_name in self.graph.nodes()) assert ("8" not in self.graph.nodes()) assert ("9" not in self.graph.nodes()) assert (valid_attributes(old_attrs1, self.graph.get_node(new_name))) assert (valid_attributes(old_attrs2, self.graph.get_node(new_name))) assert ((new_name, new_name) in self.graph.edges()) assert (valid_attributes(old_edge_attrs1, self.graph.get_edge('10', new_name))) assert (valid_attributes(old_edge_attrs2, self.graph.get_edge('10', new_name)))
def test_merge_nodes(self): g = self.graph.to_undirected() old_attrs1 = self.graph.node['8'] old_attrs2 = self.graph.node['9'] old_edge_attrs1 = self.graph.edge['10']['8'] old_edge_attrs2 = self.graph.edge['10']['9'] new_name = merge_nodes(self.graph, ["8", "9"]) assert (new_name in self.graph.nodes()) assert ("8" not in self.graph.nodes()) assert ("9" not in self.graph.nodes()) assert (valid_attributes(old_attrs1, self.graph.node[new_name])) assert (valid_attributes(old_attrs2, self.graph.node[new_name])) assert ((new_name, new_name) in self.graph.edges()) assert (valid_attributes(old_edge_attrs1, self.graph.edge['10'][new_name])) assert (valid_attributes(old_edge_attrs2, self.graph.edge['10'][new_name])) # test undirected case old_attrs1 = g.node['8'] old_attrs2 = g.node['9'] old_edge_attrs1 = g.edge['10']['8'] old_edge_attrs2 = g.edge['10']['9'] new_name = merge_nodes(g, ["8", "9"]) assert (new_name in g.nodes()) assert ("8" not in g.nodes()) assert ("9" not in g.nodes()) assert (valid_attributes(old_attrs1, g.node[new_name])) assert (valid_attributes(old_attrs2, g.node[new_name])) assert ((new_name, new_name) in g.edges()) assert (valid_attributes(old_edge_attrs1, g.edge['10'][new_name])) assert (valid_attributes(old_edge_attrs1, g.edge[new_name]['10'])) assert (valid_attributes(old_edge_attrs2, g.edge['10'][new_name])) assert (valid_attributes(old_edge_attrs2, g.edge[new_name]['10'])) assert (g.edge['10'][new_name] == g.edge[new_name]['10']) assert (id(g.edge['10'][new_name]) == id(g.edge[new_name]['10']))
def _same_graphs(hie1, hie2, rel): for id1, id2s in rel.items(): for id2 in id2s: new_rel = copy.deepcopy(rel) for ch2 in all_children(hie2, id2): for ch1 in all_children(hie1, id1): if hie1.node[ch1].attrs["name"] == hie2.node[ch2].attrs["name"]\ and hie1.node[ch1] == hie2.node[ch2]: if ch1 in new_rel: new_rel[ch1].add(ch2) else: new_rel[ch1] = {ch2} if valid_attributes(new_rel, rel): return rel else: return _same_graphs(hie1, hie2, new_rel)
def _same_graphs(hie1, hie2, rel): for id1, id2s in rel.items(): for id2 in id2s: new_rel = copy.deepcopy(rel) for ch2 in all_children(hie2, id2): for ch1 in all_children(hie1, id1): if hie1.node[ch1].attrs["name"] == hie2.node[ch2].attrs["name"]\ and hie1.node[ch1] == hie2.node[ch2]: if ch1 in new_rel: new_rel[ch1].add(ch2) else: new_rel[ch1] = {ch2} if valid_attributes(new_rel, rel): return rel else: return _same_graphs(hie1, hie2, new_rel)
def add_attributes(hie, g_id, parent, node, json_attrs): """add the attributes from the json_attrs dict to a node""" attrs = prim.json_dict_to_attrs(json_attrs) if isinstance(hie.node[g_id], GraphNode): for typing in hie.successors(g_id): mapping = hie.edge[g_id][typing].mapping if node in mapping: parent_attrs = hie.node[typing].graph.node[mapping[node]] if not valid_attributes(attrs, parent_attrs): raise ValueError("Attributes not in node {} of {}" .format(mapping[node], typing)) prim.add_node_attrs(hie.node[g_id].graph, node, attrs) elif isinstance(hie.node[g_id], RuleNode): hie.node[g_id].rule.add_node_attrs_rhs(node, attrs) else: raise ValueError("node is neither a rule nor a graph")
def add_attributes(hie, g_id, parent, node, json_attrs): """add the attributes from the json_attrs dict to a node""" attrs = prim.json_dict_to_attrs(json_attrs) if isinstance(hie.node[g_id], GraphNode): for typing in hie.successors(g_id): mapping = hie.edge[g_id][typing].mapping if node in mapping: parent_attrs = hie.node[typing].graph.node[mapping[node]] if not valid_attributes(attrs, parent_attrs): raise ValueError("Attributes not in node {} of {}".format( mapping[node], typing)) prim.add_node_attrs(hie.node[g_id].graph, node, attrs) elif isinstance(hie.node[g_id], RuleNode): hie.node[g_id].rule.add_node_attrs_rhs(node, attrs) else: raise ValueError("node is neither a rule nor a graph")
def find_matching(self, pattern, nodes=None, graph_typing=None, pattern_typing=None): """Find matching of a pattern in a graph. This function takes as an input a graph and a pattern, optionally, it also takes a collection of nodes specifying the subgraph of the original graph, where the matching should be searched in, then it searches for a matching of the pattern inside of the graph (or induced subragh), which corresponds to solving subgraph matching problem. The matching is defined by a map from the nodes of the pattern to the nodes of the graph such that: * edges are preserved, i.e. if there is an edge between nodes `n1` and `n2` in the pattern, there is an edge between the nodes of the graph that correspond to the image of `n1` and `n2`, moreover, the attribute dictionary of the edge between `n1` and `n2` is the subdictiotary of the edge it corresponds to in the graph; * the attribute dictionary of a pattern node is a subdictionary of its image in the graph; Uses `networkx.isomorphism.(Di)GraphMatcher` class, which implements subgraph matching algorithm. In addition, two parameters `graph_typing` and `pattern_typing` can be specified. They restrict the space of admisible solutions by checking if an isomorphic subgraph found in the input graph respects the provided pattern typings according to the specified graph typings. Parameters ---------- graph : nx.(Di)Graph pattern : nx.(Di)Graph Pattern graph to search for nodes : iterable, optional Subset of nodes to search for matching graph_typing : dict of dict, optional Dictionary defining typing of graph nodes pattern_typing : dict of dict, optional Dictionary definiting typing of pattern nodes Returns ------- instances : list of dict's List of instances of matching found in the graph, every instance is represented with a dictionary where keys are nodes of the pattern, and values are corresponding nodes of the graph. """ new_pattern_typing = dict() if pattern_typing: for graph, pattern_mapping in pattern_typing.items(): new_pattern_typing[graph] = normalize_relation(pattern_mapping) if graph_typing is None: graph_typing = {} # check graph/pattern typing is consistent for g, mapping in new_pattern_typing.items(): if g not in graph_typing: raise ReGraphError( "Graph is not typed by '{}' from the specified ".format( g) + "pattern typing") if nodes is not None: g = self._graph.subgraph(nodes) else: g = self._graph labels_mapping = dict([(n, i + 1) for i, n in enumerate(g.nodes())]) g = self.get_relabeled_graph(labels_mapping, raw=True) inverse_mapping = dict([(value, key) for key, value in labels_mapping.items()]) matching_nodes = set() # find all the nodes matching the nodes in pattern for pattern_node in pattern.nodes(): for node in g.nodes(): if new_pattern_typing: # check types match match = False for graph, pattern_mapping in new_pattern_typing.items(): if node in graph_typing[graph].keys() and\ pattern_node in pattern_mapping.keys(): if graph_typing[graph][node] in pattern_mapping[ pattern_node]: if valid_attributes( pattern.get_node(pattern_node), g.nodes[node]): match = True else: if valid_attributes(pattern.get_node(pattern_node), g.nodes[node]): match = True if match: matching_nodes.add(node) else: if valid_attributes(pattern.get_node(pattern_node), g.nodes[node]): matching_nodes.add(node) # find all the isomorphic subgraphs reduced_graph = g.subgraph(matching_nodes) instances = [] isomorphic_subgraphs = [] for sub_nodes in itertools.combinations(reduced_graph.nodes(), len(pattern.nodes())): subg = reduced_graph.subgraph(sub_nodes) for edgeset in itertools.combinations(subg.edges(), len(pattern.edges())): edge_induced_graph = nx.DiGraph(list(edgeset)) edge_induced_graph.add_nodes_from([ n for n in subg.nodes() if n not in edge_induced_graph.nodes() ]) if isinstance(pattern, Graph): matching_obj = isomorphism.DiGraphMatcher( pattern._graph, edge_induced_graph) else: matching_obj = isomorphism.DiGraphMatcher( pattern, edge_induced_graph) for isom in matching_obj.isomorphisms_iter(): isomorphic_subgraphs.append((subg, isom)) for subgraph, mapping in isomorphic_subgraphs: # print(subgraph.nodes(), mapping) # check node matches # exclude subgraphs which nodes information does not # correspond to pattern for (pattern_node, node) in mapping.items(): if new_pattern_typing: for g, pattern_mapping in new_pattern_typing.items(): if inverse_mapping[node] in graph_typing[g].keys() and\ pattern_node in pattern_mapping.keys(): if graph_typing[g][inverse_mapping[ node]] not in pattern_mapping[ pattern_node]: break if not valid_attributes(pattern.get_node(pattern_node), subgraph.nodes[node]): break else: continue break else: if not valid_attributes(pattern.get_node(pattern_node), subgraph.nodes[node]): break else: # check edge attribute matched for edge in pattern.edges(): pattern_attrs = pattern.get_edge(edge[0], edge[1]) target_attrs = subgraph.adj[mapping[edge[0]]][mapping[ edge[1]]] if not valid_attributes(pattern_attrs, target_attrs): break else: instances.append(mapping) # bring back original labeling for instance in instances: for key, value in instance.items(): instance[key] = inverse_mapping[value] return instances
def _compare_dicts(d1, d2): return valid_attributes(d2, d1)
def find_matching(graph, pattern): """Find matching of a pattern in a graph. This function takes as an input a graph and a pattern graph, it searches for a matching of the pattern inside of the graph (corresponds to subgraph matching problem). The matching is defined by a map from the nodes of the pattern to the nodes of the graph such that: * edges are preserved, i.e. if there is an edge between nodes `n1` and `n2` in the pattern, there is an edge between the nodes of the graph that correspond to the image of `n1` and `n2`, moreover, the attribute dictionary of the edge between `n1` and `n2` is the subdictiotary of the edge it corresponds to in the graph; * the attribute dictionary of a pattern node is a subdictionary of its image in the graph; Uses `networkx.isomorphism.(Di)GraphMatcher` class, which implements subgraph matching algorithm. Parameters ---------- graph : nx.(Di)Graph pattern : nx.(Di)Graph Pattern graph to search for Returns ------- instances : list of dict's List of instances of matching found in the graph, every instance is represented with a dictionary where keys are nodes of the pattern, and values are corresponding nodes of the graph. Examples -------- Suppose you are given the following graph: >>> g = networkx.DiGraph() >>> add_nodes_from(g, [(1, {"color": {"red"}}), 2, (3, {"color": {"blue"}})]) >>> add_edges_from(g, [(1, 1), (1, 2), (3, 3), (3, 2)]) And you would like to match the following pattern: >>> pattern = networkx.DiGraph() >>> add_nodes_from(pattern, [("x", {"color": "blue"}), "y"]) >>> add_edges_from(pattern, [("x", "x"), ("x", "y")]) Matching instances can be found as follows: >>> instances = find_matching(g, pattern) >>> instances [{"x": 3, "y": 2}] """ labels_mapping = dict([(n, i + 1) for i, n in enumerate(graph.nodes())]) g = get_relabeled_graph(graph, labels_mapping) matching_nodes = set() # find all the nodes matching the nodes in pattern for pattern_node in pattern.nodes(): for node in g.nodes(): if valid_attributes(pattern.node[pattern_node], g.node[node]): matching_nodes.add(node) reduced_graph = g.subgraph(matching_nodes) instances = [] isomorphic_subgraphs = [] for sub_nodes in itertools.combinations(reduced_graph.nodes(), len(pattern.nodes())): subg = reduced_graph.subgraph(sub_nodes) for edgeset in itertools.combinations(subg.edges(), len(pattern.edges())): if g.is_directed(): edge_induced_graph = nx.DiGraph(list(edgeset)) edge_induced_graph.add_nodes_from([ n for n in subg.nodes() if n not in edge_induced_graph.nodes() ]) matching_obj = isomorphism.DiGraphMatcher( pattern, edge_induced_graph) for isom in matching_obj.isomorphisms_iter(): isomorphic_subgraphs.append((subg, isom)) else: edge_induced_graph = nx.Graph(edgeset) edge_induced_graph.add_nodes_from([ n for n in subg.nodes() if n not in edge_induced_graph.nodes() ]) matching_obj = isomorphism.GraphMatcher( pattern, edge_induced_graph) for isom in matching_obj.isomorphisms_iter(): isomorphic_subgraphs.append((subg, isom)) for subgraph, mapping in isomorphic_subgraphs: # check node matches # exclude subgraphs which nodes information does not # correspond to pattern for (pattern_node, node) in mapping.items(): if not valid_attributes(pattern.node[pattern_node], subgraph.node[node]): break else: # check edge attribute matched for edge in pattern.edges(): pattern_attrs = get_edge(pattern, edge[0], edge[1]) target_attrs = get_edge(subgraph, mapping[edge[0]], mapping[edge[1]]) if not valid_attributes(pattern_attrs, target_attrs): break else: instances.append(mapping) # bring back original labeling inverse_mapping = dict([(value, key) for key, value in labels_mapping.items()]) for instance in instances: for key, value in instance.items(): instance[key] = inverse_mapping[value] return instances
def test_remove_edge_attrs(self): g = self.graph.to_undirected() attrs = {"s": FiniteSet({"p"})} remove_edge_attrs(g, '1', '2', attrs) assert (not valid_attributes(attrs, g.edge['1']['2'])) assert (not valid_attributes(attrs, g.edge['2']['1']))
def test_remove_edge_attrs(self): g = self.graph.to_undirected() attrs = {"s": FiniteSet({"p"})} remove_edge_attrs(g, '1', '2', attrs) assert(not valid_attributes(attrs, g.edge['1']['2'])) assert(not valid_attributes(attrs, g.edge['2']['1']))
def check_homomorphism(source, target, dictionary, total=True): """Check if the homomorphism is valid. Valid homomorphism preserves edges, and attributes if requires. """ # check if there is mapping for all the nodes of source graph if total: check_totality(source.nodes(), dictionary) if not set(dictionary.values()).issubset(target.nodes()): raise InvalidHomomorphism( "Some of the image nodes in mapping %s do not " "exist in target graph (target graph nodes %s) " "namely %s" % (dictionary.values(), target.nodes(), set(dictionary.values()) - set(target.nodes())) ) # check connectivity for s, t in source.edges(): try: if (s in dictionary.keys() and t in dictionary.keys() and not (dictionary[s], dictionary[t]) in target.edges()): if not target.is_directed(): if not (dictionary[t], dictionary[s]) in target.edges(): raise InvalidHomomorphism( "Connectivity is not preserved!" " Was expecting an edge '%s' and '%s'" % (dictionary[t], dictionary[s])) else: raise InvalidHomomorphism( "Connectivity is not preserved!" " Was expecting an edge between '%s' and '%s'" % (dictionary[s], dictionary[t])) except KeyError: pass for s, t in dictionary.items(): # check sets of attributes of nodes (here homomorphism = set # inclusion) if not valid_attributes(source.node[s], target.node[t]): raise InvalidHomomorphism( "Attributes of nodes source:'%s' %s and " "target:'%s' %s do not match!" % (s, source.node[s], t, target.node[t]) ) # check sets of attributes of edges (homomorphism = set inclusion) for s1, s2 in source.edges(): try: if (s1 in dictionary.keys() and s2 in dictionary.keys() and not valid_attributes( source.edge[s1][s2], target.edge[dictionary[s1]][dictionary[s2]])): raise InvalidHomomorphism( "Attributes of edges (%s)-(%s) (%s) and " "(%s)-(%s) (%s) do not match!" % (s1, s2, source.edge[s1][s2], dictionary[s1], dictionary[s2], target.edge[dictionary[s1]][dictionary[s2]])) except KeyError: pass return True
def _compare_dicts(d1, d2): return valid_attributes(d2, d1)
def find_matching(graph, pattern): """Find matching of a pattern in a graph. This function takes as an input a graph and a pattern graph, it searches for a matching of the pattern inside of the graph (corresponds to subgraph matching problem). The matching is defined by a map from the nodes of the pattern to the nodes of the graph such that: * edges are preserved, i.e. if there is an edge between nodes `n1` and `n2` in the pattern, there is an edge between the nodes of the graph that correspond to the image of `n1` and `n2`, moreover, the attribute dictionary of the edge between `n1` and `n2` is the subdictiotary of the edge it corresponds to in the graph; * the attribute dictionary of a pattern node is a subdictionary of its image in the graph; Uses `networkx.isomorphism.(Di)GraphMatcher` class, which implements subgraph matching algorithm. Parameters ---------- graph : nx.(Di)Graph pattern : nx.(Di)Graph Pattern graph to search for Returns ------- instances : list of dict's List of instances of matching found in the graph, every instance is represented with a dictionary where keys are nodes of the pattern, and values are corresponding nodes of the graph. Examples -------- Suppose you are given the following graph: >>> g = networkx.DiGraph() >>> add_nodes_from(g, [(1, {"color": {"red"}}), 2, (3, {"color": {"blue"}})]) >>> add_edges_from(g, [(1, 1), (1, 2), (3, 3), (3, 2)]) And you would like to match the following pattern: >>> pattern = networkx.DiGraph() >>> add_nodes_from(pattern, [("x", {"color": "blue"}), "y"]) >>> add_edges_from(pattern, [("x", "x"), ("x", "y")]) Matching instances can be found as follows: >>> instances = find_matching(g, pattern) >>> instances [{"x": 3, "y": 2}] """ labels_mapping = dict([(n, i + 1) for i, n in enumerate(graph.nodes())]) g = get_relabeled_graph(graph, labels_mapping) matching_nodes = set() # find all the nodes matching the nodes in pattern for pattern_node in pattern.nodes(): for node in g.nodes(): if valid_attributes(pattern.node[pattern_node], g.node[node]): matching_nodes.add(node) reduced_graph = g.subgraph(matching_nodes) instances = [] isomorphic_subgraphs = [] for sub_nodes in itertools.combinations(reduced_graph.nodes(), len(pattern.nodes())): subg = reduced_graph.subgraph(sub_nodes) for edgeset in itertools.combinations(subg.edges(), len(pattern.edges())): if g.is_directed(): edge_induced_graph = nx.DiGraph(list(edgeset)) edge_induced_graph.add_nodes_from( [n for n in subg.nodes() if n not in edge_induced_graph.nodes()]) matching_obj = isomorphism.DiGraphMatcher( pattern, edge_induced_graph) for isom in matching_obj.isomorphisms_iter(): isomorphic_subgraphs.append((subg, isom)) else: edge_induced_graph = nx.Graph(edgeset) edge_induced_graph.add_nodes_from( [n for n in subg.nodes() if n not in edge_induced_graph.nodes()]) matching_obj = isomorphism.GraphMatcher( pattern, edge_induced_graph) for isom in matching_obj.isomorphisms_iter(): isomorphic_subgraphs.append((subg, isom)) for subgraph, mapping in isomorphic_subgraphs: # check node matches # exclude subgraphs which nodes information does not # correspond to pattern for (pattern_node, node) in mapping.items(): if not valid_attributes(pattern.node[pattern_node], subgraph.node[node]): break else: # check edge attribute matched for edge in pattern.edges(): pattern_attrs = get_edge(pattern, edge[0], edge[1]) target_attrs = get_edge( subgraph, mapping[edge[0]], mapping[edge[1]]) if not valid_attributes(pattern_attrs, target_attrs): break else: instances.append(mapping) # bring back original labeling inverse_mapping = dict( [(value, key) for key, value in labels_mapping.items()] ) for instance in instances: for key, value in instance.items(): instance[key] = inverse_mapping[value] return instances