# Jordi Torrents # Test for k-cutsets import itertools import pytest import networkx as nx from networkx.algorithms import flow from networkx.algorithms.connectivity.kcutsets import _is_separating_set MAX_CUTSETS_TO_TEST = 4 # originally 100. cut to decrease testing time flow_funcs = [ flow.boykov_kolmogorov, flow.dinitz, flow.edmonds_karp, flow.preflow_push, flow.shortest_augmenting_path, ] ## # 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') 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)])]: new_node = G.order() + 1 # Petersen graph is triconnected P = nx.petersen_graph() G = nx.disjoint_union(G, P) # Add two edges between the grid and P G.add_edge(new_node + 1, nodes[0]) G.add_edge(new_node, nodes[1]) # K5 is 4-connected K = nx.complete_graph(5) G = nx.disjoint_union(G, K) # Add three edges between P and K5 G.add_edge(new_node + 2, new_node + 11) G.add_edge(new_node + 3, new_node + 12) G.add_edge(new_node + 4, new_node + 13) # Add another K5 sharing a node G = nx.disjoint_union(G, K) nbrs = G[new_node + 10] G.remove_node(new_node + 10) for nbr in nbrs: G.add_edge(new_node + 17, nbr) G.add_edge(new_node + 16, new_node + 5) return G 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') labels = {v: k for k, v in rlabels.items()} 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() G = nx.disjoint_union(G, P) # Add two edges between the grid and P G.add_edge(new_node + 1, nodes[0]) G.add_edge(new_node, nodes[1]) # K5 is 4-connected K = nx.complete_graph(5) G = nx.disjoint_union(G, K) # Add three edges between P and K5 G.add_edge(new_node + 2, new_node + 11) G.add_edge(new_node + 3, new_node + 12) G.add_edge(new_node + 4, new_node + 13) # Add another K5 sharing a node G = nx.disjoint_union(G, K) nbrs = G[new_node + 10] G.remove_node(new_node + 10) for nbr in nbrs: G.add_edge(new_node + 17, nbr) # Commenting this makes the graph not biconnected !! # 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)])]: new_node = G.order() + 1 # Petersen graph is triconnected P = nx.petersen_graph() G = nx.disjoint_union(G, P) # Add two edges between the grid and P G.add_edge(new_node + 1, nodes[0]) G.add_edge(new_node, nodes[1]) # K5 is 4-connected K = nx.complete_graph(5) G = nx.disjoint_union(G, K) # Add three edges between P and K5 G.add_edge(new_node + 2, new_node + 11) G.add_edge(new_node + 3, new_node + 12) G.add_edge(new_node + 4, new_node + 13) # Add another K5 sharing two nodes G = nx.disjoint_union(G, K) nbrs = G[new_node + 10] G.remove_node(new_node + 10) for nbr in nbrs: G.add_edge(new_node + 17, nbr) nbrs2 = G[new_node + 9] G.remove_node(new_node + 9) for nbr in nbrs2: G.add_edge(new_node + 18, nbr) return G # Helper function def _check_separating_sets(G): for cc in nx.connected_components(G): if len(cc) < 3: continue Gc = G.subgraph(cc) node_conn = nx.node_connectivity(Gc) all_cuts = nx.all_node_cuts(Gc) # Only test a limited number of cut sets to reduce test time. for cut in itertools.islice(all_cuts, MAX_CUTSETS_TO_TEST): assert node_conn == len(cut) assert not nx.is_connected(nx.restricted_view(G, cut, [])) def test_torrents_and_ferraro_graph(): G = torrents_and_ferraro_graph() _check_separating_sets(G) def test_example_1(): G = graph_example_1() _check_separating_sets(G) def test_random_gnp(): G = nx.gnp_random_graph(100, 0.1, seed=42) _check_separating_sets(G) def test_shell(): constructor = [(20, 80, 0.8), (80, 180, 0.6)] G = nx.random_shell_graph(constructor, seed=42) _check_separating_sets(G) def test_configuration(): deg_seq = nx.random_powerlaw_tree_sequence(100, tries=5, seed=72) G = nx.Graph(nx.configuration_model(deg_seq)) G.remove_edges_from(nx.selfloop_edges(G)) _check_separating_sets(G) def test_karate(): G = nx.karate_club_graph() _check_separating_sets(G) def _generate_no_biconnected(max_attempts=50): attempts = 0 while True: G = nx.fast_gnp_random_graph(100, 0.0575, seed=42) if nx.is_connected(G) and not nx.is_biconnected(G): attempts = 0 yield G else: if attempts >= max_attempts: msg = "Tried %d times: no suitable Graph." % attempts raise Exception(msg % max_attempts) else: attempts += 1 def test_articulation_points(): Ggen = _generate_no_biconnected() for i in range(1): # change 1 to 3 or more for more realizations. G = next(Ggen) articulation_points = list({a} for a in nx.articulation_points(G)) for cut in nx.all_node_cuts(G): assert cut in articulation_points def test_grid_2d_graph(): # All minimum node cuts of a 2d grid # are the four pairs of nodes that are # neighbors of the four corner nodes. G = nx.grid_2d_graph(5, 5) solution = [ set([(0, 1), (1, 0)]), set([(3, 0), (4, 1)]), set([(3, 4), (4, 3)]), set([(0, 3), (1, 4)]), ] for cut in nx.all_node_cuts(G): assert cut in solution def test_disconnected_graph(): G = nx.fast_gnp_random_graph(100, 0.01, seed=42) cuts = nx.all_node_cuts(G) pytest.raises(nx.NetworkXError, next, cuts) def test_alternative_flow_functions(): 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: all_cuts = nx.all_node_cuts(G, flow_func=flow_func) # Only test a limited number of cut sets to reduce test time. for cut in itertools.islice(all_cuts, MAX_CUTSETS_TO_TEST): assert node_conn == len(cut) assert not nx.is_connected(nx.restricted_view(G, cut, [])) def test_is_separating_set_complete_graph(): G = nx.complete_graph(5) assert _is_separating_set(G, {0, 1, 2, 3}) def test_is_separating_set(): for i in [5, 10, 15]: G = nx.star_graph(i) max_degree_node = max(G, key=G.degree) assert _is_separating_set(G, {max_degree_node}) def test_non_repeated_cuts(): # The algorithm was repeating the cut {0, 1} for the giant biconnected # component of the Karate club graph. K = nx.karate_club_graph() bcc = max(list(nx.biconnected_components(K)), key=len) G = K.subgraph(bcc) solution = [{32, 33}, {2, 33}, {0, 3}, {0, 1}, {29, 33}] cuts = list(nx.all_node_cuts(G)) if len(solution) != len(cuts): print(nx.info(G)) print("Solution: {}".format(solution)) print("Result: {}".format(cuts)) assert len(solution) == len(cuts) for cut in cuts: assert cut in solution def test_cycle_graph(): G = nx.cycle_graph(5) solution = [{0, 2}, {0, 3}, {1, 3}, {1, 4}, {2, 4}] cuts = list(nx.all_node_cuts(G)) assert len(solution) == len(cuts) for cut in cuts: assert cut in solution 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}, ] cuts = list(nx.all_node_cuts(G)) assert len(solution) == len(cuts) for cut in cuts: assert cut in solution