diff options
| author | Jarrod Millman <jarrod.millman@gmail.com> | 2019-11-19 23:12:09 -0800 |
|---|---|---|
| committer | Jarrod Millman <jarrod.millman@gmail.com> | 2019-11-19 23:36:38 -0800 |
| commit | a9daddcd6dbd3a9323ed892d8d0c8236deb37e2f (patch) | |
| tree | ed079a14afbf8c4f1b34510b26cbfee1e3576b6b /networkx/algorithms | |
| parent | f34040c8e5c72294c4d9b99dad1ff515a0a06eaf (diff) | |
| download | networkx-a9daddcd6dbd3a9323ed892d8d0c8236deb37e2f.tar.gz | |
Update style
Diffstat (limited to 'networkx/algorithms')
| -rw-r--r-- | networkx/algorithms/connectivity/tests/test_kcomponents.py | 204 | ||||
| -rw-r--r-- | networkx/algorithms/connectivity/tests/test_kcutsets.py | 41 | ||||
| -rw-r--r-- | networkx/algorithms/flow/tests/test_gomory_hu.py | 55 | ||||
| -rw-r--r-- | networkx/algorithms/flow/tests/test_maxflow_large_graph.py | 76 | ||||
| -rw-r--r-- | networkx/algorithms/similarity.py | 431 |
5 files changed, 500 insertions, 307 deletions
diff --git a/networkx/algorithms/connectivity/tests/test_kcomponents.py b/networkx/algorithms/connectivity/tests/test_kcomponents.py index f1160261..fbe111ad 100644 --- a/networkx/algorithms/connectivity/tests/test_kcomponents.py +++ b/networkx/algorithms/connectivity/tests/test_kcomponents.py @@ -14,14 +14,12 @@ from networkx.algorithms.connectivity.kcomponents import ( def torrents_and_ferraro_graph(): # Graph from https://arxiv.org/pdf/1503.04476v1 p.26 G = nx.convert_node_labels_to_integers( - nx.grid_graph([5, 5]), - label_attribute='labels', + nx.grid_graph([5, 5]), label_attribute="labels" ) - rlabels = nx.get_node_attributes(G, 'labels') + rlabels = nx.get_node_attributes(G, "labels") labels = {v: k for k, v in rlabels.items()} - for nodes in [(labels[(0, 4)], labels[(1, 4)]), - (labels[(3, 4)], labels[(4, 4)])]: + for nodes in [(labels[(0, 4)], labels[(1, 4)]), (labels[(3, 4)], labels[(4, 4)])]: new_node = G.order() + 1 # Petersen graph is triconnected P = nx.petersen_graph() @@ -46,8 +44,7 @@ def torrents_and_ferraro_graph(): # needed because K5s share only one node. G.add_edge(new_node + 16, new_node + 8) - for nodes in [(labels[(0, 0)], labels[(1, 0)]), - (labels[(3, 0)], labels[(4, 0)])]: + for nodes in [(labels[(0, 0)], labels[(1, 0)]), (labels[(3, 0)], labels[(4, 0)])]: new_node = G.order() + 1 # Petersen graph is triconnected P = nx.petersen_graph() @@ -137,10 +134,40 @@ def test_karate(): def test_karate_component_number(): karate_k_num = { - 0: 4, 1: 4, 2: 4, 3: 4, 4: 3, 5: 3, 6: 3, 7: 4, 8: 4, 9: 2, - 10: 3, 11: 1, 12: 2, 13: 4, 14: 2, 15: 2, 16: 2, 17: 2, - 18: 2, 19: 3, 20: 2, 21: 2, 22: 2, 23: 3, 24: 3, 25: 3, - 26: 2, 27: 3, 28: 3, 29: 3, 30: 4, 31: 3, 32: 4, 33: 4 + 0: 4, + 1: 4, + 2: 4, + 3: 4, + 4: 3, + 5: 3, + 6: 3, + 7: 4, + 8: 4, + 9: 2, + 10: 3, + 11: 1, + 12: 2, + 13: 4, + 14: 2, + 15: 2, + 16: 2, + 17: 2, + 18: 2, + 19: 3, + 20: 2, + 21: 2, + 22: 2, + 23: 3, + 24: 3, + 25: 3, + 26: 2, + 27: 3, + 28: 3, + 29: 3, + 30: 4, + 31: 3, + 32: 4, + 33: 4, } G = nx.karate_club_graph() k_components = nx.k_components(G) @@ -156,62 +183,64 @@ def test_davis_southern_women(): def test_davis_southern_women_detail_3_and_4(): solution = { - 3: [{ - 'Nora Fayette', - 'E10', - 'Myra Liddel', - 'E12', - 'E14', - 'Frances Anderson', - 'Evelyn Jefferson', - 'Ruth DeSand', - 'Helen Lloyd', - 'Eleanor Nye', - 'E9', - 'E8', - 'E5', - 'E4', - 'E7', - 'E6', - 'E1', - 'Verne Sanderson', - 'E3', - 'E2', - 'Theresa Anderson', - 'Pearl Oglethorpe', - 'Katherina Rogers', - 'Brenda Rogers', - 'E13', - 'Charlotte McDowd', - 'Sylvia Avondale', - 'Laura Mandeville', - }, + 3: [ + { + "Nora Fayette", + "E10", + "Myra Liddel", + "E12", + "E14", + "Frances Anderson", + "Evelyn Jefferson", + "Ruth DeSand", + "Helen Lloyd", + "Eleanor Nye", + "E9", + "E8", + "E5", + "E4", + "E7", + "E6", + "E1", + "Verne Sanderson", + "E3", + "E2", + "Theresa Anderson", + "Pearl Oglethorpe", + "Katherina Rogers", + "Brenda Rogers", + "E13", + "Charlotte McDowd", + "Sylvia Avondale", + "Laura Mandeville", + } ], - 4: [{ - 'Nora Fayette', - 'E10', - 'Verne Sanderson', - 'E12', - 'Frances Anderson', - 'Evelyn Jefferson', - 'Ruth DeSand', - 'Helen Lloyd', - 'Eleanor Nye', - 'E9', - 'E8', - 'E5', - 'E4', - 'E7', - 'E6', - 'Myra Liddel', - 'E3', - 'Theresa Anderson', - 'Katherina Rogers', - 'Brenda Rogers', - 'Charlotte McDowd', - 'Sylvia Avondale', - 'Laura Mandeville', - }, + 4: [ + { + "Nora Fayette", + "E10", + "Verne Sanderson", + "E12", + "Frances Anderson", + "Evelyn Jefferson", + "Ruth DeSand", + "Helen Lloyd", + "Eleanor Nye", + "E9", + "E8", + "E5", + "E4", + "E7", + "E6", + "Myra Liddel", + "E3", + "Theresa Anderson", + "Katherina Rogers", + "Brenda Rogers", + "Charlotte McDowd", + "Sylvia Avondale", + "Laura Mandeville", + } ], } G = nx.davis_southern_women_graph() @@ -227,24 +256,37 @@ def test_davis_southern_women_detail_3_and_4(): def test_set_consolidation_rosettacode(): # Tests from http://rosettacode.org/wiki/Set_consolidation def list_of_sets_equal(result, solution): - assert ( - {frozenset(s) for s in result} == - {frozenset(s) for s in solution}) - question = [{'A', 'B'}, {'C', 'D'}] - solution = [{'A', 'B'}, {'C', 'D'}] + assert {frozenset(s) for s in result} == {frozenset(s) for s in solution} + + question = [{"A", "B"}, {"C", "D"}] + solution = [{"A", "B"}, {"C", "D"}] list_of_sets_equal(_consolidate(question, 1), solution) - question = [{'A', 'B'}, {'B', 'C'}] - solution = [{'A', 'B', 'C'}] + question = [{"A", "B"}, {"B", "C"}] + solution = [{"A", "B", "C"}] list_of_sets_equal(_consolidate(question, 1), solution) - question = [{'A', 'B'}, {'C', 'D'}, {'D', 'B'}] - solution = [{'A', 'C', 'B', 'D'}] + question = [{"A", "B"}, {"C", "D"}, {"D", "B"}] + solution = [{"A", "C", "B", "D"}] list_of_sets_equal(_consolidate(question, 1), solution) - question = [{'H', 'I', 'K'}, {'A', 'B'}, {'C', 'D'}, {'D', 'B'}, {'F', 'G', 'H'}] - solution = [{'A', 'C', 'B', 'D'}, {'G', 'F', 'I', 'H', 'K'}] + question = [{"H", "I", "K"}, {"A", "B"}, {"C", "D"}, {"D", "B"}, {"F", "G", "H"}] + solution = [{"A", "C", "B", "D"}, {"G", "F", "I", "H", "K"}] list_of_sets_equal(_consolidate(question, 1), solution) - question = [{'A', 'H'}, {'H', 'I', 'K'}, {'A', 'B'}, {'C', 'D'}, {'D', 'B'}, {'F', 'G', 'H'}] - solution = [{'A', 'C', 'B', 'D', 'G', 'F', 'I', 'H', 'K'}] + question = [ + {"A", "H"}, + {"H", "I", "K"}, + {"A", "B"}, + {"C", "D"}, + {"D", "B"}, + {"F", "G", "H"}, + ] + solution = [{"A", "C", "B", "D", "G", "F", "I", "H", "K"}] list_of_sets_equal(_consolidate(question, 1), solution) - question = [{'H', 'I', 'K'}, {'A', 'B'}, {'C', 'D'}, {'D', 'B'}, {'F', 'G', 'H'}, {'A', 'H'}] - solution = [{'A', 'C', 'B', 'D', 'G', 'F', 'I', 'H', 'K'}] + question = [ + {"H", "I", "K"}, + {"A", "B"}, + {"C", "D"}, + {"D", "B"}, + {"F", "G", "H"}, + {"A", "H"}, + ] + solution = [{"A", "C", "B", "D", "G", "F", "I", "H", "K"}] list_of_sets_equal(_consolidate(question, 1), solution) diff --git a/networkx/algorithms/connectivity/tests/test_kcutsets.py b/networkx/algorithms/connectivity/tests/test_kcutsets.py index 492b309d..4bb07094 100644 --- a/networkx/algorithms/connectivity/tests/test_kcutsets.py +++ b/networkx/algorithms/connectivity/tests/test_kcutsets.py @@ -22,15 +22,18 @@ flow_funcs = [ # Some nice synthetic graphs ## def graph_example_1(): - G = nx.convert_node_labels_to_integers(nx.grid_graph([5, 5]), - label_attribute='labels') - rlabels = nx.get_node_attributes(G, 'labels') + G = nx.convert_node_labels_to_integers( + nx.grid_graph([5, 5]), label_attribute="labels" + ) + rlabels = nx.get_node_attributes(G, "labels") labels = {v: k for k, v in rlabels.items()} - for nodes in [(labels[(0, 0)], labels[(1, 0)]), - (labels[(0, 4)], labels[(1, 4)]), - (labels[(3, 0)], labels[(4, 0)]), - (labels[(3, 4)], labels[(4, 4)])]: + for nodes in [ + (labels[(0, 0)], labels[(1, 0)]), + (labels[(0, 4)], labels[(1, 4)]), + (labels[(3, 0)], labels[(4, 0)]), + (labels[(3, 4)], labels[(4, 4)]), + ]: new_node = G.order() + 1 # Petersen graph is triconnected P = nx.petersen_graph() @@ -56,13 +59,13 @@ def graph_example_1(): def torrents_and_ferraro_graph(): - G = nx.convert_node_labels_to_integers(nx.grid_graph([5, 5]), - label_attribute='labels') - rlabels = nx.get_node_attributes(G, 'labels') + G = nx.convert_node_labels_to_integers( + nx.grid_graph([5, 5]), label_attribute="labels" + ) + rlabels = nx.get_node_attributes(G, "labels") labels = {v: k for k, v in rlabels.items()} - for nodes in [(labels[(0, 4)], labels[(1, 4)]), - (labels[(3, 4)], labels[(4, 4)])]: + for nodes in [(labels[(0, 4)], labels[(1, 4)]), (labels[(3, 4)], labels[(4, 4)])]: new_node = G.order() + 1 # Petersen graph is triconnected P = nx.petersen_graph() @@ -87,8 +90,7 @@ def torrents_and_ferraro_graph(): # This stupid mistake make one reviewer very angry :P G.add_edge(new_node + 16, new_node + 8) - for nodes in [(labels[(0, 0)], labels[(1, 0)]), - (labels[(3, 0)], labels[(4, 0)])]: + for nodes in [(labels[(0, 0)], labels[(1, 0)]), (labels[(3, 0)], labels[(4, 0)])]: new_node = G.order() + 1 # Petersen graph is triconnected P = nx.petersen_graph() @@ -209,8 +211,7 @@ def test_disconnected_graph(): def test_alternative_flow_functions(): - graphs = [nx.grid_2d_graph(4, 4), - nx.cycle_graph(5)] + graphs = [nx.grid_2d_graph(4, 4), nx.cycle_graph(5)] for G in graphs: node_conn = nx.node_connectivity(G) for flow_func in flow_funcs: @@ -261,13 +262,7 @@ def test_cycle_graph(): def test_complete_graph(): G = nx.complete_graph(5) - solution = [ - {0, 1, 2, 3}, - {0, 1, 2, 4}, - {0, 1, 3, 4}, - {0, 2, 3, 4}, - {1, 2, 3, 4}, - ] + solution = [{0, 1, 2, 3}, {0, 1, 2, 4}, {0, 1, 3, 4}, {0, 2, 3, 4}, {1, 2, 3, 4}] cuts = list(nx.all_node_cuts(G)) assert len(solution) == len(cuts) for cut in cuts: diff --git a/networkx/algorithms/flow/tests/test_gomory_hu.py b/networkx/algorithms/flow/tests/test_gomory_hu.py index b82a32f7..0d9ded84 100644 --- a/networkx/algorithms/flow/tests/test_gomory_hu.py +++ b/networkx/algorithms/flow/tests/test_gomory_hu.py @@ -18,10 +18,9 @@ flow_funcs = [ class TestGomoryHuTree: - def minimum_edge_weight(self, T, u, v): - path = nx.shortest_path(T, u, v, weight='weight') - return min((T[u][v]['weight'], (u, v)) for (u, v) in zip(path, path[1:])) + path = nx.shortest_path(T, u, v, weight="weight") + return min((T[u][v]["weight"], (u, v)) for (u, v) in zip(path, path[1:])) def compute_cutset(self, G, T_orig, edge): T = T_orig.copy() @@ -34,61 +33,56 @@ class TestGomoryHuTree: def test_default_flow_function_karate_club_graph(self): G = nx.karate_club_graph() - nx.set_edge_attributes(G, 1, 'capacity') + nx.set_edge_attributes(G, 1, "capacity") T = nx.gomory_hu_tree(G) assert nx.is_tree(T) for u, v in combinations(G, 2): cut_value, edge = self.minimum_edge_weight(T, u, v) - assert (nx.minimum_cut_value(G, u, v) == - cut_value) + assert nx.minimum_cut_value(G, u, v) == cut_value def test_karate_club_graph(self): G = nx.karate_club_graph() - nx.set_edge_attributes(G, 1, 'capacity') + nx.set_edge_attributes(G, 1, "capacity") for flow_func in flow_funcs: T = nx.gomory_hu_tree(G, flow_func=flow_func) assert nx.is_tree(T) for u, v in combinations(G, 2): cut_value, edge = self.minimum_edge_weight(T, u, v) - assert (nx.minimum_cut_value(G, u, v) == - cut_value) + assert nx.minimum_cut_value(G, u, v) == cut_value def test_davis_southern_women_graph(self): G = nx.davis_southern_women_graph() - nx.set_edge_attributes(G, 1, 'capacity') + nx.set_edge_attributes(G, 1, "capacity") for flow_func in flow_funcs: T = nx.gomory_hu_tree(G, flow_func=flow_func) assert nx.is_tree(T) for u, v in combinations(G, 2): cut_value, edge = self.minimum_edge_weight(T, u, v) - assert (nx.minimum_cut_value(G, u, v) == - cut_value) + assert nx.minimum_cut_value(G, u, v) == cut_value def test_florentine_families_graph(self): G = nx.florentine_families_graph() - nx.set_edge_attributes(G, 1, 'capacity') + nx.set_edge_attributes(G, 1, "capacity") for flow_func in flow_funcs: T = nx.gomory_hu_tree(G, flow_func=flow_func) assert nx.is_tree(T) for u, v in combinations(G, 2): cut_value, edge = self.minimum_edge_weight(T, u, v) - assert (nx.minimum_cut_value(G, u, v) == - cut_value) + assert nx.minimum_cut_value(G, u, v) == cut_value def test_les_miserables_graph_cutset(self): G = nx.les_miserables_graph() - nx.set_edge_attributes(G, 1, 'capacity') + nx.set_edge_attributes(G, 1, "capacity") for flow_func in flow_funcs: T = nx.gomory_hu_tree(G, flow_func=flow_func) assert nx.is_tree(T) for u, v in combinations(G, 2): cut_value, edge = self.minimum_edge_weight(T, u, v) - assert (nx.minimum_cut_value(G, u, v) == - cut_value) + assert nx.minimum_cut_value(G, u, v) == cut_value def test_karate_club_graph_cutset(self): G = nx.karate_club_graph() - nx.set_edge_attributes(G, 1, 'capacity') + nx.set_edge_attributes(G, 1, "capacity") T = nx.gomory_hu_tree(G) assert nx.is_tree(T) u, v = 0, 33 @@ -99,18 +93,25 @@ class TestGomoryHuTree: def test_wikipedia_example(self): # Example from https://en.wikipedia.org/wiki/Gomory%E2%80%93Hu_tree G = nx.Graph() - G.add_weighted_edges_from(( - (0, 1, 1), (0, 2, 7), (1, 2, 1), - (1, 3, 3), (1, 4, 2), (2, 4, 4), - (3, 4, 1), (3, 5, 6), (4, 5, 2), - )) + G.add_weighted_edges_from( + ( + (0, 1, 1), + (0, 2, 7), + (1, 2, 1), + (1, 3, 3), + (1, 4, 2), + (2, 4, 4), + (3, 4, 1), + (3, 5, 6), + (4, 5, 2), + ) + ) for flow_func in flow_funcs: - T = nx.gomory_hu_tree(G, capacity='weight', flow_func=flow_func) + T = nx.gomory_hu_tree(G, capacity="weight", flow_func=flow_func) assert nx.is_tree(T) for u, v in combinations(G, 2): cut_value, edge = self.minimum_edge_weight(T, u, v) - assert (nx.minimum_cut_value(G, u, v, capacity='weight') == - cut_value) + assert nx.minimum_cut_value(G, u, v, capacity="weight") == cut_value def test_directed_raises(self): with pytest.raises(nx.NetworkXNotImplemented): diff --git a/networkx/algorithms/flow/tests/test_maxflow_large_graph.py b/networkx/algorithms/flow/tests/test_maxflow_large_graph.py index e152cb50..dceba17b 100644 --- a/networkx/algorithms/flow/tests/test_maxflow_large_graph.py +++ b/networkx/algorithms/flow/tests/test_maxflow_large_graph.py @@ -30,29 +30,27 @@ def gen_pyramid(N): G = nx.DiGraph() for i in range(N - 1): - cap = 1. / (i + 2) + cap = 1.0 / (i + 2) for j in range(i + 1): - G.add_edge((i, j), (i + 1, j), - capacity=cap) - cap = 1. / (i + 1) - cap - G.add_edge((i, j), (i + 1, j + 1), - capacity=cap) - cap = 1. / (i + 2) - cap + G.add_edge((i, j), (i + 1, j), capacity=cap) + cap = 1.0 / (i + 1) - cap + G.add_edge((i, j), (i + 1, j + 1), capacity=cap) + cap = 1.0 / (i + 2) - cap for j in range(N): - G.add_edge((N - 1, j), 't') + G.add_edge((N - 1, j), "t") return G def read_graph(name): dirname = os.path.dirname(__file__) - path = os.path.join(dirname, name + '.gpickle.bz2') + path = os.path.join(dirname, name + ".gpickle.bz2") return nx.read_gpickle(path) def validate_flows(G, s, t, soln_value, R, flow_func): - flow_value = R.graph['flow_value'] + flow_value = R.graph["flow_value"] flow_dict = build_flow_dict(G, R) assert soln_value == flow_value, msg.format(flow_func.__name__) assert set(G) == set(flow_dict), msg.format(flow_func.__name__) @@ -61,7 +59,9 @@ def validate_flows(G, s, t, soln_value, R, flow_func): excess = {u: 0 for u in flow_dict} for u in flow_dict: for v, flow in flow_dict[u].items(): - assert flow <= G[u][v].get('capacity', float('inf')), msg.format(flow_func.__name__) + assert flow <= G[u][v].get("capacity", float("inf")), msg.format( + flow_func.__name__ + ) assert flow >= 0, msg.format(flow_func.__name__) excess[u] -= flow excess[v] += flow @@ -75,16 +75,15 @@ def validate_flows(G, s, t, soln_value, R, flow_func): class TestMaxflowLargeGraph: - def test_complete_graph(self): N = 50 G = nx.complete_graph(N) - nx.set_edge_attributes(G, 5, 'capacity') - R = build_residual_network(G, 'capacity') + nx.set_edge_attributes(G, 5, "capacity") + R = build_residual_network(G, "capacity") kwargs = dict(residual=R) for flow_func in flow_funcs: - kwargs['flow_func'] = flow_func + kwargs["flow_func"] = flow_func flow_value = nx.maximum_flow_value(G, 1, 2, **kwargs) assert flow_value == 5 * (N - 1), msg.format(flow_func.__name__) @@ -92,56 +91,55 @@ class TestMaxflowLargeGraph: N = 10 # N = 100 # this gives a graph with 5051 nodes G = gen_pyramid(N) - R = build_residual_network(G, 'capacity') + R = build_residual_network(G, "capacity") kwargs = dict(residual=R) for flow_func in flow_funcs: - kwargs['flow_func'] = flow_func - flow_value = nx.maximum_flow_value(G, (0, 0), 't', **kwargs) - assert almost_equal(flow_value, 1.), msg.format(flow_func.__name__) + kwargs["flow_func"] = flow_func + flow_value = nx.maximum_flow_value(G, (0, 0), "t", **kwargs) + assert almost_equal(flow_value, 1.0), msg.format(flow_func.__name__) def test_gl1(self): - G = read_graph('gl1') + G = read_graph("gl1") s = 1 t = len(G) - R = build_residual_network(G, 'capacity') + R = build_residual_network(G, "capacity") kwargs = dict(residual=R) # do one flow_func to save time flow_func = flow_funcs[0] - validate_flows(G, s, t, 156545, flow_func(G, s, t, **kwargs), - flow_func) -# for flow_func in flow_funcs: -# validate_flows(G, s, t, 156545, flow_func(G, s, t, **kwargs), -# flow_func) + validate_flows(G, s, t, 156545, flow_func(G, s, t, **kwargs), flow_func) + + # for flow_func in flow_funcs: + # validate_flows(G, s, t, 156545, flow_func(G, s, t, **kwargs), + # flow_func) def test_gw1(self): - G = read_graph('gw1') + G = read_graph("gw1") s = 1 t = len(G) - R = build_residual_network(G, 'capacity') + R = build_residual_network(G, "capacity") kwargs = dict(residual=R) for flow_func in flow_funcs: - validate_flows(G, s, t, 1202018, flow_func(G, s, t, **kwargs), - flow_func) + validate_flows(G, s, t, 1202018, flow_func(G, s, t, **kwargs), flow_func) def test_wlm3(self): - G = read_graph('wlm3') + G = read_graph("wlm3") s = 1 t = len(G) - R = build_residual_network(G, 'capacity') + R = build_residual_network(G, "capacity") kwargs = dict(residual=R) # do one flow_func to save time flow_func = flow_funcs[0] - validate_flows(G, s, t, 11875108, flow_func(G, s, t, **kwargs), - flow_func) -# for flow_func in flow_funcs: -# validate_flows(G, s, t, 11875108, flow_func(G, s, t, **kwargs), -# flow_func) + validate_flows(G, s, t, 11875108, flow_func(G, s, t, **kwargs), flow_func) + + # for flow_func in flow_funcs: + # validate_flows(G, s, t, 11875108, flow_func(G, s, t, **kwargs), + # flow_func) def test_preflow_push_global_relabel(self): - G = read_graph('gw1') + G = read_graph("gw1") R = preflow_push(G, 1, len(G), global_relabel_freq=50) - assert R.graph['flow_value'] == 1202018 + assert R.graph["flow_value"] == 1202018 diff --git a/networkx/algorithms/similarity.py b/networkx/algorithms/similarity.py index 985e9500..dfd95efc 100644 --- a/networkx/algorithms/similarity.py +++ b/networkx/algorithms/similarity.py @@ -16,12 +16,12 @@ from itertools import product import networkx as nx __all__ = [ - 'graph_edit_distance', - 'optimal_edit_paths', - 'optimize_graph_edit_distance', - 'optimize_edit_paths', - 'simrank_similarity', - 'simrank_similarity_numpy', + "graph_edit_distance", + "optimal_edit_paths", + "optimize_graph_edit_distance", + "optimize_edit_paths", + "simrank_similarity", + "simrank_similarity_numpy", ] @@ -29,12 +29,19 @@ def debug_print(*args, **kwargs): print(*args, **kwargs) -def graph_edit_distance(G1, G2, node_match=None, edge_match=None, - node_subst_cost=None, node_del_cost=None, - node_ins_cost=None, - edge_subst_cost=None, edge_del_cost=None, - edge_ins_cost=None, - upper_bound=None): +def graph_edit_distance( + G1, + G2, + node_match=None, + edge_match=None, + node_subst_cost=None, + node_del_cost=None, + node_ins_cost=None, + edge_subst_cost=None, + edge_del_cost=None, + edge_ins_cost=None, + upper_bound=None, +): """Returns GED (graph edit distance) between graphs G1 and G2. Graph edit distance is a graph similarity measure analogous to @@ -153,22 +160,38 @@ def graph_edit_distance(G1, G2, node_match=None, edge_match=None, """ bestcost = None - for vertex_path, edge_path, cost in \ - optimize_edit_paths(G1, G2, node_match, edge_match, - node_subst_cost, node_del_cost, node_ins_cost, - edge_subst_cost, edge_del_cost, edge_ins_cost, - upper_bound, True): + for vertex_path, edge_path, cost in optimize_edit_paths( + G1, + G2, + node_match, + edge_match, + node_subst_cost, + node_del_cost, + node_ins_cost, + edge_subst_cost, + edge_del_cost, + edge_ins_cost, + upper_bound, + True, + ): # assert bestcost is None or cost < bestcost bestcost = cost return bestcost -def optimal_edit_paths(G1, G2, node_match=None, edge_match=None, - node_subst_cost=None, node_del_cost=None, - node_ins_cost=None, - edge_subst_cost=None, edge_del_cost=None, - edge_ins_cost=None, - upper_bound=None): +def optimal_edit_paths( + G1, + G2, + node_match=None, + edge_match=None, + node_subst_cost=None, + node_del_cost=None, + node_ins_cost=None, + edge_subst_cost=None, + edge_del_cost=None, + edge_ins_cost=None, + upper_bound=None, +): """Returns all minimum-cost edit paths transforming G1 to G2. Graph edit path is a sequence of node and edge edit operations @@ -296,11 +319,20 @@ def optimal_edit_paths(G1, G2, node_match=None, edge_match=None, """ paths = list() bestcost = None - for vertex_path, edge_path, cost in \ - optimize_edit_paths(G1, G2, node_match, edge_match, - node_subst_cost, node_del_cost, node_ins_cost, - edge_subst_cost, edge_del_cost, edge_ins_cost, - upper_bound, False): + for vertex_path, edge_path, cost in optimize_edit_paths( + G1, + G2, + node_match, + edge_match, + node_subst_cost, + node_del_cost, + node_ins_cost, + edge_subst_cost, + edge_del_cost, + edge_ins_cost, + upper_bound, + False, + ): # assert bestcost is None or cost <= bestcost if bestcost is not None and cost < bestcost: paths = list() @@ -309,12 +341,19 @@ def optimal_edit_paths(G1, G2, node_match=None, edge_match=None, return paths, bestcost -def optimize_graph_edit_distance(G1, G2, node_match=None, edge_match=None, - node_subst_cost=None, node_del_cost=None, - node_ins_cost=None, - edge_subst_cost=None, edge_del_cost=None, - edge_ins_cost=None, - upper_bound=None): +def optimize_graph_edit_distance( + G1, + G2, + node_match=None, + edge_match=None, + node_subst_cost=None, + node_del_cost=None, + node_ins_cost=None, + edge_subst_cost=None, + edge_del_cost=None, + edge_ins_cost=None, + upper_bound=None, +): """Returns consecutive approximations of GED (graph edit distance) between graphs G1 and G2. @@ -435,20 +474,37 @@ def optimize_graph_edit_distance(G1, G2, node_match=None, edge_match=None, <10.5220/0005209202710278>. <hal-01168816> https://hal.archives-ouvertes.fr/hal-01168816 """ - for vertex_path, edge_path, cost in \ - optimize_edit_paths(G1, G2, node_match, edge_match, - node_subst_cost, node_del_cost, node_ins_cost, - edge_subst_cost, edge_del_cost, edge_ins_cost, - upper_bound, True): + for vertex_path, edge_path, cost in optimize_edit_paths( + G1, + G2, + node_match, + edge_match, + node_subst_cost, + node_del_cost, + node_ins_cost, + edge_subst_cost, + edge_del_cost, + edge_ins_cost, + upper_bound, + True, + ): yield cost -def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, - node_subst_cost=None, node_del_cost=None, - node_ins_cost=None, - edge_subst_cost=None, edge_del_cost=None, - edge_ins_cost=None, - upper_bound=None, strictly_decreasing=True): +def optimize_edit_paths( + G1, + G2, + node_match=None, + edge_match=None, + node_subst_cost=None, + node_del_cost=None, + node_ins_cost=None, + edge_subst_cost=None, + edge_del_cost=None, + edge_ins_cost=None, + upper_bound=None, + strictly_decreasing=True, +): """GED (graph edit distance) calculation: advanced interface. Graph edit path is a sequence of node and edge edit operations @@ -603,8 +659,9 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, lsa_row_ind[dummy_ind] = lsa_col_ind[subst_ind] + m lsa_col_ind[dummy_ind] = lsa_row_ind[subst_ind] + n - return CostMatrix(C, lsa_row_ind, lsa_col_ind, - C[lsa_row_ind, lsa_col_ind].sum()) + return CostMatrix( + C, lsa_row_ind, lsa_col_ind, C[lsa_row_ind, lsa_col_ind].sum() + ) def extract_C(C, i, j, m, n): # assert(C.shape == (m + n, m + n)) @@ -646,12 +703,18 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, N = len(pending_h) # assert Ce.C.shape == (M + N, M + N) - g_ind = [i for i in range(M) if pending_g[i][:2] == (u, u) or - any(pending_g[i][:2] in ((p, u), (u, p)) - for p, q in matched_uv)] - h_ind = [j for j in range(N) if pending_h[j][:2] == (v, v) or - any(pending_h[j][:2] in ((q, v), (v, q)) - for p, q in matched_uv)] + g_ind = [ + i + for i in range(M) + if pending_g[i][:2] == (u, u) + or any(pending_g[i][:2] in ((p, u), (u, p)) for p, q in matched_uv) + ] + h_ind = [ + j + for j in range(N) + if pending_h[j][:2] == (v, v) + or any(pending_h[j][:2] in ((q, v), (v, q)) for p, q in matched_uv) + ] m = len(g_ind) n = len(h_ind) @@ -666,13 +729,16 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, for l, j in zip(range(n), h_ind): h = pending_h[j][:2] if nx.is_directed(G1) or nx.is_directed(G2): - if any(g == (p, u) and h == (q, v) or - g == (u, p) and h == (v, q) - for p, q in matched_uv): + if any( + g == (p, u) and h == (q, v) or g == (u, p) and h == (v, q) + for p, q in matched_uv + ): continue else: - if any(g in ((p, u), (u, p)) and h in ((q, v), (v, q)) - for p, q in matched_uv): + if any( + g in ((p, u), (u, p)) and h in ((q, v), (v, q)) + for p, q in matched_uv + ): continue if g == (u, u): continue @@ -681,10 +747,14 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, C[k, l] = inf localCe = make_CostMatrix(C, m, n) - ij = list((g_ind[k] if k < m else M + h_ind[l], - h_ind[l] if l < n else N + g_ind[k]) - for k, l in zip(localCe.lsa_row_ind, localCe.lsa_col_ind) - if k < m or l < n) + ij = list( + ( + g_ind[k] if k < m else M + h_ind[l], + h_ind[l] if l < n else N + g_ind[k], + ) + for k, l in zip(localCe.lsa_row_ind, localCe.lsa_col_ind) + if k < m or l < n + ) else: ij = [] @@ -701,8 +771,9 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, else: return Ce - def get_edit_ops(matched_uv, pending_u, pending_v, Cv, - pending_g, pending_h, Ce, matched_cost): + def get_edit_ops( + matched_uv, pending_u, pending_v, Cv, pending_g, pending_h, Ce, matched_cost + ): """ Parameters: matched_uv: partial vertex edit path @@ -730,57 +801,86 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, # assert Cv.C.shape == (m + n, m + n) # 1) a vertex mapping from optimal linear sum assignment - i, j = min((k, l) for k, l in zip(Cv.lsa_row_ind, Cv.lsa_col_ind) - if k < m or l < n) - xy, localCe = match_edges(pending_u[i] if i < m else None, - pending_v[j] if j < n else None, - pending_g, pending_h, Ce, matched_uv) + i, j = min( + (k, l) for k, l in zip(Cv.lsa_row_ind, Cv.lsa_col_ind) if k < m or l < n + ) + xy, localCe = match_edges( + pending_u[i] if i < m else None, + pending_v[j] if j < n else None, + pending_g, + pending_h, + Ce, + matched_uv, + ) Ce_xy = reduce_Ce(Ce, xy, len(pending_g), len(pending_h)) # assert Ce.ls <= localCe.ls + Ce_xy.ls if prune(matched_cost + Cv.ls + localCe.ls + Ce_xy.ls): pass else: # get reduced Cv efficiently - Cv_ij = CostMatrix(reduce_C(Cv.C, (i,), (j,), m, n), - reduce_ind(Cv.lsa_row_ind, (i, m + j)), - reduce_ind(Cv.lsa_col_ind, (j, n + i)), - Cv.ls - Cv.C[i, j]) + Cv_ij = CostMatrix( + reduce_C(Cv.C, (i,), (j,), m, n), + reduce_ind(Cv.lsa_row_ind, (i, m + j)), + reduce_ind(Cv.lsa_col_ind, (j, n + i)), + Cv.ls - Cv.C[i, j], + ) yield (i, j), Cv_ij, xy, Ce_xy, Cv.C[i, j] + localCe.ls # 2) other candidates, sorted by lower-bound cost estimate other = list() fixed_i, fixed_j = i, j if m <= n: - candidates = ((t, fixed_j) for t in range(m + n) - if t != fixed_i and (t < m or t == m + fixed_j)) + candidates = ( + (t, fixed_j) + for t in range(m + n) + if t != fixed_i and (t < m or t == m + fixed_j) + ) else: - candidates = ((fixed_i, t) for t in range(m + n) - if t != fixed_j and (t < n or t == n + fixed_i)) + candidates = ( + (fixed_i, t) + for t in range(m + n) + if t != fixed_j and (t < n or t == n + fixed_i) + ) for i, j in candidates: if prune(matched_cost + Cv.C[i, j] + Ce.ls): continue - Cv_ij = make_CostMatrix(reduce_C(Cv.C, (i,), (j,), m, n), - m - 1 if i < m else m, - n - 1 if j < n else n) + Cv_ij = make_CostMatrix( + reduce_C(Cv.C, (i,), (j,), m, n), + m - 1 if i < m else m, + n - 1 if j < n else n, + ) # assert Cv.ls <= Cv.C[i, j] + Cv_ij.ls if prune(matched_cost + Cv.C[i, j] + Cv_ij.ls + Ce.ls): continue - xy, localCe = match_edges(pending_u[i] if i < m else None, - pending_v[j] if j < n else None, - pending_g, pending_h, Ce, matched_uv) + xy, localCe = match_edges( + pending_u[i] if i < m else None, + pending_v[j] if j < n else None, + pending_g, + pending_h, + Ce, + matched_uv, + ) if prune(matched_cost + Cv.C[i, j] + Cv_ij.ls + localCe.ls): continue Ce_xy = reduce_Ce(Ce, xy, len(pending_g), len(pending_h)) # assert Ce.ls <= localCe.ls + Ce_xy.ls - if prune(matched_cost + Cv.C[i, j] + Cv_ij.ls + localCe.ls + - Ce_xy.ls): + if prune(matched_cost + Cv.C[i, j] + Cv_ij.ls + localCe.ls + Ce_xy.ls): continue other.append(((i, j), Cv_ij, xy, Ce_xy, Cv.C[i, j] + localCe.ls)) yield from sorted(other, key=lambda t: t[4] + t[1].ls + t[3].ls) - def get_edit_paths(matched_uv, pending_u, pending_v, Cv, - matched_gh, pending_g, pending_h, Ce, matched_cost): + def get_edit_paths( + matched_uv, + pending_u, + pending_v, + Cv, + matched_gh, + pending_g, + pending_h, + Ce, + matched_cost, + ): """ Parameters: matched_uv: partial vertex edit path @@ -833,8 +933,16 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, yield matched_uv, matched_gh, matched_cost else: - edit_ops = get_edit_ops(matched_uv, pending_u, pending_v, Cv, - pending_g, pending_h, Ce, matched_cost) + edit_ops = get_edit_ops( + matched_uv, + pending_u, + pending_v, + Cv, + pending_g, + pending_h, + Ce, + matched_cost, + ) for ij, Cv_ij, xy, Ce_xy, edit_cost in edit_ops: i, j = ij # assert Cv.C[i, j] + sum(Ce.C[t] for t in xy) == edit_cost @@ -848,20 +956,34 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, for x, y in xy: len_g = len(pending_g) len_h = len(pending_h) - matched_gh.append((pending_g[x] if x < len_g else None, - pending_h[y] if y < len_h else None)) + matched_gh.append( + ( + pending_g[x] if x < len_g else None, + pending_h[y] if y < len_h else None, + ) + ) sortedx = list(sorted(x for x, y in xy)) sortedy = list(sorted(y for x, y in xy)) - G = list((pending_g.pop(x) if x < len(pending_g) else None) - for x in reversed(sortedx)) - H = list((pending_h.pop(y) if y < len(pending_h) else None) - for y in reversed(sortedy)) - - yield from get_edit_paths(matched_uv, pending_u, pending_v, - Cv_ij, - matched_gh, pending_g, pending_h, - Ce_xy, - matched_cost + edit_cost) + G = list( + (pending_g.pop(x) if x < len(pending_g) else None) + for x in reversed(sortedx) + ) + H = list( + (pending_h.pop(y) if y < len(pending_h) else None) + for y in reversed(sortedy) + ) + + yield from get_edit_paths( + matched_uv, + pending_u, + pending_v, + Cv_ij, + matched_gh, + pending_g, + pending_h, + Ce_xy, + matched_cost + edit_cost, + ) # backtrack if u is not None: @@ -888,13 +1010,21 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, n = len(pending_v) C = np.zeros((m + n, m + n)) if node_subst_cost: - C[0:m, 0:n] = np.array([node_subst_cost(G1.nodes[u], G2.nodes[v]) - for u in pending_u for v in pending_v] - ).reshape(m, n) + C[0:m, 0:n] = np.array( + [ + node_subst_cost(G1.nodes[u], G2.nodes[v]) + for u in pending_u + for v in pending_v + ] + ).reshape(m, n) elif node_match: - C[0:m, 0:n] = np.array([1 - int(node_match(G1.nodes[u], G2.nodes[v])) - for u in pending_u for v in pending_v] - ).reshape(m, n) + C[0:m, 0:n] = np.array( + [ + 1 - int(node_match(G1.nodes[u], G2.nodes[v])) + for u in pending_u + for v in pending_v + ] + ).reshape(m, n) else: # all zeroes pass @@ -910,12 +1040,12 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, ins_costs = [1] * len(pending_v) # assert not n or min(ins_costs) >= 0 inf = C[0:m, 0:n].sum() + sum(del_costs) + sum(ins_costs) + 1 - C[0:m, n:n + m] = np.array([del_costs[i] if i == j else inf - for i in range(m) for j in range(m)] - ).reshape(m, m) - C[m:m + n, 0:n] = np.array([ins_costs[i] if i == j else inf - for i in range(n) for j in range(n)] - ).reshape(n, n) + C[0:m, n : n + m] = np.array( + [del_costs[i] if i == j else inf for i in range(m) for j in range(m)] + ).reshape(m, m) + C[m : m + n, 0:n] = np.array( + [ins_costs[i] if i == j else inf for i in range(n) for j in range(n)] + ).reshape(n, n) Cv = make_CostMatrix(C, m, n) # debug_print('Cv: {} x {}'.format(m, n)) # debug_print(Cv.C) @@ -928,13 +1058,21 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, n = len(pending_h) C = np.zeros((m + n, m + n)) if edge_subst_cost: - C[0:m, 0:n] = np.array([edge_subst_cost(G1.edges[g], G2.edges[h]) - for g in pending_g for h in pending_h] - ).reshape(m, n) + C[0:m, 0:n] = np.array( + [ + edge_subst_cost(G1.edges[g], G2.edges[h]) + for g in pending_g + for h in pending_h + ] + ).reshape(m, n) elif edge_match: - C[0:m, 0:n] = np.array([1 - int(edge_match(G1.edges[g], G2.edges[h])) - for g in pending_g for h in pending_h] - ).reshape(m, n) + C[0:m, 0:n] = np.array( + [ + 1 - int(edge_match(G1.edges[g], G2.edges[h])) + for g in pending_g + for h in pending_h + ] + ).reshape(m, n) else: # all zeroes pass @@ -950,12 +1088,12 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, ins_costs = [1] * len(pending_h) # assert not n or min(ins_costs) >= 0 inf = C[0:m, 0:n].sum() + sum(del_costs) + sum(ins_costs) + 1 - C[0:m, n:n + m] = np.array([del_costs[i] if i == j else inf - for i in range(m) for j in range(m)] - ).reshape(m, m) - C[m:m + n, 0:n] = np.array([ins_costs[i] if i == j else inf - for i in range(n) for j in range(n)] - ).reshape(n, n) + C[0:m, n : n + m] = np.array( + [del_costs[i] if i == j else inf for i in range(m) for j in range(m)] + ).reshape(m, m) + C[m : m + n, 0:n] = np.array( + [ins_costs[i] if i == j else inf for i in range(n) for j in range(n)] + ).reshape(n, n) Ce = make_CostMatrix(C, m, n) # debug_print('Ce: {} x {}'.format(m, n)) # debug_print(Ce.C) @@ -966,6 +1104,7 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, # initial upper-bound estimate # NOTE: should work for empty graph self.value = Cv.C.sum() + Ce.C.sum() + 1 + maxcost = MaxCost() def prune(cost): @@ -979,9 +1118,9 @@ def optimize_edit_paths(G1, G2, node_match=None, edge_match=None, # Now go! - for vertex_path, edge_path, cost in \ - get_edit_paths([], pending_u, pending_v, Cv, - [], pending_g, pending_h, Ce, 0): + for vertex_path, edge_path, cost in get_edit_paths( + [], pending_u, pending_v, Cv, [], pending_g, pending_h, Ce, 0 + ): # assert sorted(G1.nodes) == sorted(u for u, v in vertex_path if u is not None) # assert sorted(G2.nodes) == sorted(v for u, v in vertex_path if v is not None) # assert sorted(G1.edges) == sorted(g for g, h in edge_path if g is not None) @@ -1026,8 +1165,14 @@ def _is_close(d1, d2, atolerance=0, rtolerance=0): return all(all(_is_close(d1[u][v], d2[u][v]) for v in d1[u]) for u in d1) -def simrank_similarity(G, source=None, target=None, importance_factor=0.9, - max_iterations=100, tolerance=1e-4): +def simrank_similarity( + G, + source=None, + target=None, + importance_factor=0.9, + max_iterations=100, + tolerance=1e-4, +): """Returns the SimRank similarity of nodes in the graph ``G``. SimRank is a similarity metric that says "two objects are considered @@ -1121,15 +1266,19 @@ def simrank_similarity(G, source=None, target=None, importance_factor=0.9, # These functions compute the update to the similarity value of the nodes # `u` and `v` with respect to the previous similarity values. - def avg_sim(s): return sum(newsim[w][x] for (w, x) in s) / len(s) if s else 0.0 - def sim(u, v): return importance_factor * avg_sim(list(product(G[u], G[v]))) + def avg_sim(s): + return sum(newsim[w][x] for (w, x) in s) / len(s) if s else 0.0 + + def sim(u, v): + return importance_factor * avg_sim(list(product(G[u], G[v]))) for _ in range(max_iterations): if prevsim and _is_close(prevsim, newsim, tolerance): break prevsim = newsim - newsim = {u: {v: sim(u, v) if u is not v else 1 - for v in newsim[u]} for u in newsim} + newsim = { + u: {v: sim(u, v) if u is not v else 1 for v in newsim[u]} for u in newsim + } if source is not None and target is not None: return newsim[source][target] @@ -1138,8 +1287,14 @@ def simrank_similarity(G, source=None, target=None, importance_factor=0.9, return newsim -def simrank_similarity_numpy(G, source=None, target=None, importance_factor=0.9, - max_iterations=100, tolerance=1e-4): +def simrank_similarity_numpy( + G, + source=None, + target=None, + importance_factor=0.9, + max_iterations=100, + tolerance=1e-4, +): """Calculate SimRank of nodes in ``G`` using matrices with ``numpy``. The SimRank algorithm for determining node similarity is defined in @@ -1209,6 +1364,7 @@ def simrank_similarity_numpy(G, source=None, target=None, importance_factor=0.9, # where C is the importance factor, A is the column normalized # adjacency matrix, and I is the identity matrix. import numpy as np + adjacency_matrix = nx.to_numpy_array(G) # column-normalize the ``adjacency_matrix`` @@ -1218,7 +1374,8 @@ def simrank_similarity_numpy(G, source=None, target=None, importance_factor=0.9, for _ in range(max_iterations): prevsim = np.copy(newsim) newsim = importance_factor * np.matmul( - np.matmul(adjacency_matrix.T, prevsim), adjacency_matrix) + np.matmul(adjacency_matrix.T, prevsim), adjacency_matrix + ) np.fill_diagonal(newsim, 1.0) if np.allclose(prevsim, newsim, atol=tolerance): |
