"""Maximum weight clique test suite. """ import networkx as nx import pytest class TestMaximumWeightClique: def test_basic_cases(self): def check_basic_case(graph_func, expected_weight, weight_accessor): graph = graph_func() clique, weight = nx.algorithms.max_weight_clique(graph, weight_accessor) assert verify_clique( graph, clique, weight, expected_weight, weight_accessor ) for graph_func, (expected_weight, expected_size) in TEST_CASES.items(): check_basic_case(graph_func, expected_weight, "weight") check_basic_case(graph_func, expected_size, None) def test_key_error(self): graph = two_node_graph() with pytest.raises(KeyError): nx.algorithms.max_weight_clique(graph, "non-existent-key") def test_error_on_non_integer_weight(self): graph = two_node_graph() graph.nodes[2]["weight"] = 1.5 with pytest.raises(ValueError): nx.algorithms.max_weight_clique(graph) def test_unaffected_by_self_loops(self): graph = two_node_graph() graph.add_edge(1, 1) graph.add_edge(2, 2) clique, weight = nx.algorithms.max_weight_clique(graph, "weight") assert verify_clique(graph, clique, weight, 30, "weight") graph = three_node_independent_set() graph.add_edge(1, 1) clique, weight = nx.algorithms.max_weight_clique(graph, "weight") assert verify_clique(graph, clique, weight, 20, "weight") def test_30_node_prob(self): G = nx.Graph() G.add_nodes_from(range(1, 31)) for i in range(1, 31): G.nodes[i]["weight"] = i + 1 # fmt: off G.add_edges_from( [ (1, 12), (1, 13), (1, 15), (1, 16), (1, 18), (1, 19), (1, 20), (1, 23), (1, 26), (1, 28), (1, 29), (1, 30), (2, 3), (2, 4), (2, 5), (2, 8), (2, 9), (2, 10), (2, 14), (2, 17), (2, 18), (2, 21), (2, 22), (2, 23), (2, 27), (3, 9), (3, 15), (3, 21), (3, 22), (3, 23), (3, 24), (3, 27), (3, 28), (3, 29), (4, 5), (4, 6), (4, 8), (4, 21), (4, 22), (4, 23), (4, 26), (4, 28), (4, 30), (5, 6), (5, 8), (5, 9), (5, 13), (5, 14), (5, 15), (5, 16), (5, 20), (5, 21), (5, 22), (5, 25), (5, 28), (5, 29), (6, 7), (6, 8), (6, 13), (6, 17), (6, 18), (6, 19), (6, 24), (6, 26), (6, 27), (6, 28), (6, 29), (7, 12), (7, 14), (7, 15), (7, 16), (7, 17), (7, 20), (7, 25), (7, 27), (7, 29), (7, 30), (8, 10), (8, 15), (8, 16), (8, 18), (8, 20), (8, 22), (8, 24), (8, 26), (8, 27), (8, 28), (8, 30), (9, 11), (9, 12), (9, 13), (9, 14), (9, 15), (9, 16), (9, 19), (9, 20), (9, 21), (9, 24), (9, 30), (10, 12), (10, 15), (10, 18), (10, 19), (10, 20), (10, 22), (10, 23), (10, 24), (10, 26), (10, 27), (10, 29), (10, 30), (11, 13), (11, 15), (11, 16), (11, 17), (11, 18), (11, 19), (11, 20), (11, 22), (11, 29), (11, 30), (12, 14), (12, 17), (12, 18), (12, 19), (12, 20), (12, 21), (12, 23), (12, 25), (12, 26), (12, 30), (13, 20), (13, 22), (13, 23), (13, 24), (13, 30), (14, 16), (14, 20), (14, 21), (14, 22), (14, 23), (14, 25), (14, 26), (14, 27), (14, 29), (14, 30), (15, 17), (15, 18), (15, 20), (15, 21), (15, 26), (15, 27), (15, 28), (16, 17), (16, 18), (16, 19), (16, 20), (16, 21), (16, 29), (16, 30), (17, 18), (17, 21), (17, 22), (17, 25), (17, 27), (17, 28), (17, 30), (18, 19), (18, 20), (18, 21), (18, 22), (18, 23), (18, 24), (19, 20), (19, 22), (19, 23), (19, 24), (19, 25), (19, 27), (19, 30), (20, 21), (20, 23), (20, 24), (20, 26), (20, 28), (20, 29), (21, 23), (21, 26), (21, 27), (21, 29), (22, 24), (22, 25), (22, 26), (22, 29), (23, 25), (23, 30), (24, 25), (24, 26), (25, 27), (25, 29), (26, 27), (26, 28), (26, 30), (28, 29), (29, 30), ] ) # fmt: on clique, weight = nx.algorithms.max_weight_clique(G) assert verify_clique(G, clique, weight, 111, "weight") # ############################ Utility functions ############################ def verify_clique( graph, clique, reported_clique_weight, expected_clique_weight, weight_accessor ): for node1 in clique: for node2 in clique: if node1 == node2: continue if not graph.has_edge(node1, node2): return False if weight_accessor is None: clique_weight = len(clique) else: clique_weight = sum(graph.nodes[v]["weight"] for v in clique) if clique_weight != expected_clique_weight: return False if clique_weight != reported_clique_weight: return False return True # ############################ Graph Generation ############################ def empty_graph(): return nx.Graph() def one_node_graph(): graph = nx.Graph() graph.add_nodes_from([1]) graph.nodes[1]["weight"] = 10 return graph def two_node_graph(): graph = nx.Graph() graph.add_nodes_from([1, 2]) graph.add_edges_from([(1, 2)]) graph.nodes[1]["weight"] = 10 graph.nodes[2]["weight"] = 20 return graph def three_node_clique(): graph = nx.Graph() graph.add_nodes_from([1, 2, 3]) graph.add_edges_from([(1, 2), (1, 3), (2, 3)]) graph.nodes[1]["weight"] = 10 graph.nodes[2]["weight"] = 20 graph.nodes[3]["weight"] = 5 return graph def three_node_independent_set(): graph = nx.Graph() graph.add_nodes_from([1, 2, 3]) graph.nodes[1]["weight"] = 10 graph.nodes[2]["weight"] = 20 graph.nodes[3]["weight"] = 5 return graph def disconnected(): graph = nx.Graph() graph.add_edges_from([(1, 2), (2, 3), (4, 5), (5, 6)]) graph.nodes[1]["weight"] = 10 graph.nodes[2]["weight"] = 20 graph.nodes[3]["weight"] = 5 graph.nodes[4]["weight"] = 100 graph.nodes[5]["weight"] = 200 graph.nodes[6]["weight"] = 50 return graph # -------------------------------------------------------------------------- # Basic tests for all strategies # For each basic graph function, specify expected weight of max weight clique # and expected size of maximum clique TEST_CASES = { empty_graph: (0, 0), one_node_graph: (10, 1), two_node_graph: (30, 2), three_node_clique: (35, 3), three_node_independent_set: (20, 1), disconnected: (300, 2), }