123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- import itertools
- import networkx as nx
- from networkx.algorithms.approximation import (
- treewidth_min_degree,
- treewidth_min_fill_in,
- )
- from networkx.algorithms.approximation.treewidth import (
- MinDegreeHeuristic,
- min_fill_in_heuristic,
- )
- def is_tree_decomp(graph, decomp):
- """Check if the given tree decomposition is valid."""
- for x in graph.nodes():
- appear_once = False
- for bag in decomp.nodes():
- if x in bag:
- appear_once = True
- break
- assert appear_once
- # Check if each connected pair of nodes are at least once together in a bag
- for x, y in graph.edges():
- appear_together = False
- for bag in decomp.nodes():
- if x in bag and y in bag:
- appear_together = True
- break
- assert appear_together
- # Check if the nodes associated with vertex v form a connected subset of T
- for v in graph.nodes():
- subset = []
- for bag in decomp.nodes():
- if v in bag:
- subset.append(bag)
- sub_graph = decomp.subgraph(subset)
- assert nx.is_connected(sub_graph)
- class TestTreewidthMinDegree:
- """Unit tests for the min_degree function"""
- @classmethod
- def setup_class(cls):
- """Setup for different kinds of trees"""
- cls.complete = nx.Graph()
- cls.complete.add_edge(1, 2)
- cls.complete.add_edge(2, 3)
- cls.complete.add_edge(1, 3)
- cls.small_tree = nx.Graph()
- cls.small_tree.add_edge(1, 3)
- cls.small_tree.add_edge(4, 3)
- cls.small_tree.add_edge(2, 3)
- cls.small_tree.add_edge(3, 5)
- cls.small_tree.add_edge(5, 6)
- cls.small_tree.add_edge(5, 7)
- cls.small_tree.add_edge(6, 7)
- cls.deterministic_graph = nx.Graph()
- cls.deterministic_graph.add_edge(0, 1) # deg(0) = 1
- cls.deterministic_graph.add_edge(1, 2) # deg(1) = 2
- cls.deterministic_graph.add_edge(2, 3)
- cls.deterministic_graph.add_edge(2, 4) # deg(2) = 3
- cls.deterministic_graph.add_edge(3, 4)
- cls.deterministic_graph.add_edge(3, 5)
- cls.deterministic_graph.add_edge(3, 6) # deg(3) = 4
- cls.deterministic_graph.add_edge(4, 5)
- cls.deterministic_graph.add_edge(4, 6)
- cls.deterministic_graph.add_edge(4, 7) # deg(4) = 5
- cls.deterministic_graph.add_edge(5, 6)
- cls.deterministic_graph.add_edge(5, 7)
- cls.deterministic_graph.add_edge(5, 8)
- cls.deterministic_graph.add_edge(5, 9) # deg(5) = 6
- cls.deterministic_graph.add_edge(6, 7)
- cls.deterministic_graph.add_edge(6, 8)
- cls.deterministic_graph.add_edge(6, 9) # deg(6) = 6
- cls.deterministic_graph.add_edge(7, 8)
- cls.deterministic_graph.add_edge(7, 9) # deg(7) = 5
- cls.deterministic_graph.add_edge(8, 9) # deg(8) = 4
- def test_petersen_graph(self):
- """Test Petersen graph tree decomposition result"""
- G = nx.petersen_graph()
- _, decomp = treewidth_min_degree(G)
- is_tree_decomp(G, decomp)
- def test_small_tree_treewidth(self):
- """Test small tree
- Test if the computed treewidth of the known self.small_tree is 2.
- As we know which value we can expect from our heuristic, values other
- than two are regressions
- """
- G = self.small_tree
- # the order of removal should be [1,2,4]3[5,6,7]
- # (with [] denoting any order of the containing nodes)
- # resulting in treewidth 2 for the heuristic
- treewidth, _ = treewidth_min_fill_in(G)
- assert treewidth == 2
- def test_heuristic_abort(self):
- """Test heuristic abort condition for fully connected graph"""
- graph = {}
- for u in self.complete:
- graph[u] = set()
- for v in self.complete[u]:
- if u != v: # ignore self-loop
- graph[u].add(v)
- deg_heuristic = MinDegreeHeuristic(graph)
- node = deg_heuristic.best_node(graph)
- if node is None:
- pass
- else:
- assert False
- def test_empty_graph(self):
- """Test empty graph"""
- G = nx.Graph()
- _, _ = treewidth_min_degree(G)
- def test_two_component_graph(self):
- G = nx.Graph()
- G.add_node(1)
- G.add_node(2)
- treewidth, _ = treewidth_min_degree(G)
- assert treewidth == 0
- def test_not_sortable_nodes(self):
- G = nx.Graph([(0, "a")])
- treewidth_min_degree(G)
- def test_heuristic_first_steps(self):
- """Test first steps of min_degree heuristic"""
- graph = {
- n: set(self.deterministic_graph[n]) - {n} for n in self.deterministic_graph
- }
- deg_heuristic = MinDegreeHeuristic(graph)
- elim_node = deg_heuristic.best_node(graph)
- print(f"Graph {graph}:")
- steps = []
- while elim_node is not None:
- print(f"Removing {elim_node}:")
- steps.append(elim_node)
- nbrs = graph[elim_node]
- for u, v in itertools.permutations(nbrs, 2):
- if v not in graph[u]:
- graph[u].add(v)
- for u in graph:
- if elim_node in graph[u]:
- graph[u].remove(elim_node)
- del graph[elim_node]
- print(f"Graph {graph}:")
- elim_node = deg_heuristic.best_node(graph)
- # check only the first 5 elements for equality
- assert steps[:5] == [0, 1, 2, 3, 4]
- class TestTreewidthMinFillIn:
- """Unit tests for the treewidth_min_fill_in function."""
- @classmethod
- def setup_class(cls):
- """Setup for different kinds of trees"""
- cls.complete = nx.Graph()
- cls.complete.add_edge(1, 2)
- cls.complete.add_edge(2, 3)
- cls.complete.add_edge(1, 3)
- cls.small_tree = nx.Graph()
- cls.small_tree.add_edge(1, 2)
- cls.small_tree.add_edge(2, 3)
- cls.small_tree.add_edge(3, 4)
- cls.small_tree.add_edge(1, 4)
- cls.small_tree.add_edge(2, 4)
- cls.small_tree.add_edge(4, 5)
- cls.small_tree.add_edge(5, 6)
- cls.small_tree.add_edge(5, 7)
- cls.small_tree.add_edge(6, 7)
- cls.deterministic_graph = nx.Graph()
- cls.deterministic_graph.add_edge(1, 2)
- cls.deterministic_graph.add_edge(1, 3)
- cls.deterministic_graph.add_edge(3, 4)
- cls.deterministic_graph.add_edge(2, 4)
- cls.deterministic_graph.add_edge(3, 5)
- cls.deterministic_graph.add_edge(4, 5)
- cls.deterministic_graph.add_edge(3, 6)
- cls.deterministic_graph.add_edge(5, 6)
- def test_petersen_graph(self):
- """Test Petersen graph tree decomposition result"""
- G = nx.petersen_graph()
- _, decomp = treewidth_min_fill_in(G)
- is_tree_decomp(G, decomp)
- def test_small_tree_treewidth(self):
- """Test if the computed treewidth of the known self.small_tree is 2"""
- G = self.small_tree
- # the order of removal should be [1,2,4]3[5,6,7]
- # (with [] denoting any order of the containing nodes)
- # resulting in treewidth 2 for the heuristic
- treewidth, _ = treewidth_min_fill_in(G)
- assert treewidth == 2
- def test_heuristic_abort(self):
- """Test if min_fill_in returns None for fully connected graph"""
- graph = {}
- for u in self.complete:
- graph[u] = set()
- for v in self.complete[u]:
- if u != v: # ignore self-loop
- graph[u].add(v)
- next_node = min_fill_in_heuristic(graph)
- if next_node is None:
- pass
- else:
- assert False
- def test_empty_graph(self):
- """Test empty graph"""
- G = nx.Graph()
- _, _ = treewidth_min_fill_in(G)
- def test_two_component_graph(self):
- G = nx.Graph()
- G.add_node(1)
- G.add_node(2)
- treewidth, _ = treewidth_min_fill_in(G)
- assert treewidth == 0
- def test_not_sortable_nodes(self):
- G = nx.Graph([(0, "a")])
- treewidth_min_fill_in(G)
- def test_heuristic_first_steps(self):
- """Test first steps of min_fill_in heuristic"""
- graph = {
- n: set(self.deterministic_graph[n]) - {n} for n in self.deterministic_graph
- }
- print(f"Graph {graph}:")
- elim_node = min_fill_in_heuristic(graph)
- steps = []
- while elim_node is not None:
- print(f"Removing {elim_node}:")
- steps.append(elim_node)
- nbrs = graph[elim_node]
- for u, v in itertools.permutations(nbrs, 2):
- if v not in graph[u]:
- graph[u].add(v)
- for u in graph:
- if elim_node in graph[u]:
- graph[u].remove(elim_node)
- del graph[elim_node]
- print(f"Graph {graph}:")
- elim_node = min_fill_in_heuristic(graph)
- # check only the first 2 elements for equality
- assert steps[:2] == [6, 5]
|