123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- import pytest
- import networkx as nx
- from networkx.algorithms.planarity import (
- check_planarity_recursive,
- get_counterexample,
- get_counterexample_recursive,
- )
- class TestLRPlanarity:
- """Nose Unit tests for the :mod:`networkx.algorithms.planarity` module.
- Tests three things:
- 1. Check that the result is correct
- (returns planar if and only if the graph is actually planar)
- 2. In case a counter example is returned: Check if it is correct
- 3. In case an embedding is returned: Check if its actually an embedding
- """
- @staticmethod
- def check_graph(G, is_planar=None):
- """Raises an exception if the lr_planarity check returns a wrong result
- Parameters
- ----------
- G : NetworkX graph
- is_planar : bool
- The expected result of the planarity check.
- If set to None only counter example or embedding are verified.
- """
- # obtain results of planarity check
- is_planar_lr, result = nx.check_planarity(G, True)
- is_planar_lr_rec, result_rec = check_planarity_recursive(G, True)
- if is_planar is not None:
- # set a message for the assert
- if is_planar:
- msg = "Wrong planarity check result. Should be planar."
- else:
- msg = "Wrong planarity check result. Should be non-planar."
- # check if the result is as expected
- assert is_planar == is_planar_lr, msg
- assert is_planar == is_planar_lr_rec, msg
- if is_planar_lr:
- # check embedding
- check_embedding(G, result)
- check_embedding(G, result_rec)
- else:
- # check counter example
- check_counterexample(G, result)
- check_counterexample(G, result_rec)
- def test_simple_planar_graph(self):
- e = [
- (1, 2),
- (2, 3),
- (3, 4),
- (4, 6),
- (6, 7),
- (7, 1),
- (1, 5),
- (5, 2),
- (2, 4),
- (4, 5),
- (5, 7),
- ]
- self.check_graph(nx.Graph(e), is_planar=True)
- def test_planar_with_selfloop(self):
- e = [
- (1, 1),
- (2, 2),
- (3, 3),
- (4, 4),
- (5, 5),
- (1, 2),
- (1, 3),
- (1, 5),
- (2, 5),
- (2, 4),
- (3, 4),
- (3, 5),
- (4, 5),
- ]
- self.check_graph(nx.Graph(e), is_planar=True)
- def test_k3_3(self):
- self.check_graph(nx.complete_bipartite_graph(3, 3), is_planar=False)
- def test_k5(self):
- self.check_graph(nx.complete_graph(5), is_planar=False)
- def test_multiple_components_planar(self):
- e = [(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)]
- self.check_graph(nx.Graph(e), is_planar=True)
- def test_multiple_components_non_planar(self):
- G = nx.complete_graph(5)
- # add another planar component to the non planar component
- # G stays non planar
- G.add_edges_from([(6, 7), (7, 8), (8, 6)])
- self.check_graph(G, is_planar=False)
- def test_non_planar_with_selfloop(self):
- G = nx.complete_graph(5)
- # add self loops
- for i in range(5):
- G.add_edge(i, i)
- self.check_graph(G, is_planar=False)
- def test_non_planar1(self):
- # tests a graph that has no subgraph directly isomorph to K5 or K3_3
- e = [
- (1, 5),
- (1, 6),
- (1, 7),
- (2, 6),
- (2, 3),
- (3, 5),
- (3, 7),
- (4, 5),
- (4, 6),
- (4, 7),
- ]
- self.check_graph(nx.Graph(e), is_planar=False)
- def test_loop(self):
- # test a graph with a selfloop
- e = [(1, 2), (2, 2)]
- G = nx.Graph(e)
- self.check_graph(G, is_planar=True)
- def test_comp(self):
- # test multiple component graph
- e = [(1, 2), (3, 4)]
- G = nx.Graph(e)
- G.remove_edge(1, 2)
- self.check_graph(G, is_planar=True)
- def test_goldner_harary(self):
- # test goldner-harary graph (a maximal planar graph)
- e = [
- (1, 2),
- (1, 3),
- (1, 4),
- (1, 5),
- (1, 7),
- (1, 8),
- (1, 10),
- (1, 11),
- (2, 3),
- (2, 4),
- (2, 6),
- (2, 7),
- (2, 9),
- (2, 10),
- (2, 11),
- (3, 4),
- (4, 5),
- (4, 6),
- (4, 7),
- (5, 7),
- (6, 7),
- (7, 8),
- (7, 9),
- (7, 10),
- (8, 10),
- (9, 10),
- (10, 11),
- ]
- G = nx.Graph(e)
- self.check_graph(G, is_planar=True)
- def test_planar_multigraph(self):
- G = nx.MultiGraph([(1, 2), (1, 2), (1, 2), (1, 2), (2, 3), (3, 1)])
- self.check_graph(G, is_planar=True)
- def test_non_planar_multigraph(self):
- G = nx.MultiGraph(nx.complete_graph(5))
- G.add_edges_from([(1, 2)] * 5)
- self.check_graph(G, is_planar=False)
- def test_planar_digraph(self):
- G = nx.DiGraph([(1, 2), (2, 3), (2, 4), (4, 1), (4, 2), (1, 4), (3, 2)])
- self.check_graph(G, is_planar=True)
- def test_non_planar_digraph(self):
- G = nx.DiGraph(nx.complete_graph(5))
- G.remove_edge(1, 2)
- G.remove_edge(4, 1)
- self.check_graph(G, is_planar=False)
- def test_single_component(self):
- # Test a graph with only a single node
- G = nx.Graph()
- G.add_node(1)
- self.check_graph(G, is_planar=True)
- def test_graph1(self):
- G = nx.Graph(
- [
- (3, 10),
- (2, 13),
- (1, 13),
- (7, 11),
- (0, 8),
- (8, 13),
- (0, 2),
- (0, 7),
- (0, 10),
- (1, 7),
- ]
- )
- self.check_graph(G, is_planar=True)
- def test_graph2(self):
- G = nx.Graph(
- [
- (1, 2),
- (4, 13),
- (0, 13),
- (4, 5),
- (7, 10),
- (1, 7),
- (0, 3),
- (2, 6),
- (5, 6),
- (7, 13),
- (4, 8),
- (0, 8),
- (0, 9),
- (2, 13),
- (6, 7),
- (3, 6),
- (2, 8),
- ]
- )
- self.check_graph(G, is_planar=False)
- def test_graph3(self):
- G = nx.Graph(
- [
- (0, 7),
- (3, 11),
- (3, 4),
- (8, 9),
- (4, 11),
- (1, 7),
- (1, 13),
- (1, 11),
- (3, 5),
- (5, 7),
- (1, 3),
- (0, 4),
- (5, 11),
- (5, 13),
- ]
- )
- self.check_graph(G, is_planar=False)
- def test_counterexample_planar(self):
- with pytest.raises(nx.NetworkXException):
- # Try to get a counterexample of a planar graph
- G = nx.Graph()
- G.add_node(1)
- get_counterexample(G)
- def test_counterexample_planar_recursive(self):
- with pytest.raises(nx.NetworkXException):
- # Try to get a counterexample of a planar graph
- G = nx.Graph()
- G.add_node(1)
- get_counterexample_recursive(G)
- def check_embedding(G, embedding):
- """Raises an exception if the combinatorial embedding is not correct
- Parameters
- ----------
- G : NetworkX graph
- embedding : a dict mapping nodes to a list of edges
- This specifies the ordering of the outgoing edges from a node for
- a combinatorial embedding
- Notes
- -----
- Checks the following things:
- - The type of the embedding is correct
- - The nodes and edges match the original graph
- - Every half edge has its matching opposite half edge
- - No intersections of edges (checked by Euler's formula)
- """
- if not isinstance(embedding, nx.PlanarEmbedding):
- raise nx.NetworkXException("Bad embedding. Not of type nx.PlanarEmbedding")
- # Check structure
- embedding.check_structure()
- # Check that graphs are equivalent
- assert set(G.nodes) == set(
- embedding.nodes
- ), "Bad embedding. Nodes don't match the original graph."
- # Check that the edges are equal
- g_edges = set()
- for edge in G.edges:
- if edge[0] != edge[1]:
- g_edges.add((edge[0], edge[1]))
- g_edges.add((edge[1], edge[0]))
- assert g_edges == set(
- embedding.edges
- ), "Bad embedding. Edges don't match the original graph."
- def check_counterexample(G, sub_graph):
- """Raises an exception if the counterexample is wrong.
- Parameters
- ----------
- G : NetworkX graph
- subdivision_nodes : set
- A set of nodes inducing a subgraph as a counterexample
- """
- # 1. Create the sub graph
- sub_graph = nx.Graph(sub_graph)
- # 2. Remove self loops
- for u in sub_graph:
- if sub_graph.has_edge(u, u):
- sub_graph.remove_edge(u, u)
- # keep track of nodes we might need to contract
- contract = list(sub_graph)
- # 3. Contract Edges
- while len(contract) > 0:
- contract_node = contract.pop()
- if contract_node not in sub_graph:
- # Node was already contracted
- continue
- degree = sub_graph.degree[contract_node]
- # Check if we can remove the node
- if degree == 2:
- # Get the two neighbors
- neighbors = iter(sub_graph[contract_node])
- u = next(neighbors)
- v = next(neighbors)
- # Save nodes for later
- contract.append(u)
- contract.append(v)
- # Contract edge
- sub_graph.remove_node(contract_node)
- sub_graph.add_edge(u, v)
- # 4. Check for isomorphism with K5 or K3_3 graphs
- if len(sub_graph) == 5:
- if not nx.is_isomorphic(nx.complete_graph(5), sub_graph):
- raise nx.NetworkXException("Bad counter example.")
- elif len(sub_graph) == 6:
- if not nx.is_isomorphic(nx.complete_bipartite_graph(3, 3), sub_graph):
- raise nx.NetworkXException("Bad counter example.")
- else:
- raise nx.NetworkXException("Bad counter example.")
- class TestPlanarEmbeddingClass:
- def test_get_data(self):
- embedding = self.get_star_embedding(3)
- data = embedding.get_data()
- data_cmp = {0: [2, 1], 1: [0], 2: [0]}
- assert data == data_cmp
- def test_missing_edge_orientation(self):
- with pytest.raises(nx.NetworkXException):
- embedding = nx.PlanarEmbedding()
- embedding.add_edge(1, 2)
- embedding.add_edge(2, 1)
- # Invalid structure because the orientation of the edge was not set
- embedding.check_structure()
- def test_invalid_edge_orientation(self):
- with pytest.raises(nx.NetworkXException):
- embedding = nx.PlanarEmbedding()
- embedding.add_half_edge_first(1, 2)
- embedding.add_half_edge_first(2, 1)
- embedding.add_edge(1, 3)
- embedding.check_structure()
- def test_missing_half_edge(self):
- with pytest.raises(nx.NetworkXException):
- embedding = nx.PlanarEmbedding()
- embedding.add_half_edge_first(1, 2)
- # Invalid structure because other half edge is missing
- embedding.check_structure()
- def test_not_fulfilling_euler_formula(self):
- with pytest.raises(nx.NetworkXException):
- embedding = nx.PlanarEmbedding()
- for i in range(5):
- for j in range(5):
- if i != j:
- embedding.add_half_edge_first(i, j)
- embedding.check_structure()
- def test_missing_reference(self):
- with pytest.raises(nx.NetworkXException):
- embedding = nx.PlanarEmbedding()
- embedding.add_half_edge_cw(1, 2, 3)
- def test_connect_components(self):
- embedding = nx.PlanarEmbedding()
- embedding.connect_components(1, 2)
- def test_successful_face_traversal(self):
- embedding = nx.PlanarEmbedding()
- embedding.add_half_edge_first(1, 2)
- embedding.add_half_edge_first(2, 1)
- face = embedding.traverse_face(1, 2)
- assert face == [1, 2]
- def test_unsuccessful_face_traversal(self):
- with pytest.raises(nx.NetworkXException):
- embedding = nx.PlanarEmbedding()
- embedding.add_edge(1, 2, ccw=2, cw=3)
- embedding.add_edge(2, 1, ccw=1, cw=3)
- embedding.traverse_face(1, 2)
- @staticmethod
- def get_star_embedding(n):
- embedding = nx.PlanarEmbedding()
- for i in range(1, n):
- embedding.add_half_edge_first(0, i)
- embedding.add_half_edge_first(i, 0)
- return embedding
|