def test_exceptions(self) -> None: empty_g = Graph() with pytest.raises(exception.Unfeasible): components.connected_components(empty_g) g = Graph([(1, 2)]) with pytest.raises(exception.GraphTypeNotSupported): components.strongly_connected_components(g)
def test_allows_self_loops(self) -> None: g = Graph() assert g.allows_self_loops( ), "graph should default to allowing self loops" g.add_edge(1, 1) # graph should allow adding self loop g2 = Graph(allow_self_loops=False) assert not g2.allows_self_loops( ), "graph 2 should not allow self loops" with pytest.raises(exception.SelfLoopsNotAllowed): g2.add_edge(1, 1)
def test_clear(self) -> None: g = Graph([(1, 2), (2, 3), (3, 4)]) assert g.edge_count > 0, "graph should have edges" assert g.vertex_count > 0, "graph should have vertices" g.clear() assert g.edge_count == 0, "graph should not have edges after clear()" assert g.vertex_count == 0, "graph should not have vertices after clear()"
def test_vertex_count(self) -> None: g = Graph([(1, 2), (2, 3), (3, 4)]) assert g.vertex_count == 4, "graph should have 4 vertices" g.add_vertex(5) assert g.vertex_count == 5, "graph should have 5 vertices" g.remove_vertex(5) assert g.vertex_count == 4, "graph should have 4 vertices"
def test_add_edges_from(self) -> None: g = Graph() g.add_edges_from([ (1, 2, 4.5, { "color": "blue", "mass": 42 }), (4, 3, 9.5), (5, 6, { "color": "red", "mass": 99 }), (8, 7), (7, 8), ]) assert g.edge_count == 4, "graph should have 4 edges" assert g.get_edge( 1, 2).weight == 4.5, "edge (1, 2) should have weight 4.5" assert g.get_edge( 1, 2 )["color"] == "blue", "edge (1, 2) should have 'color' set to 'blue'" assert g.get_edge( 1, 2)["mass"] == 42, "edge (1, 2) should have 'mass' set to 42" assert not g.get_edge(3, 4).has_attributes_dict( ), "edge (3, 4) should not have attributes dict" assert (g.get_edge(5, 6).weight == edge_module.DEFAULT_WEIGHT ), "edge should have default weight" assert g.get_edge(7, 8) is g.get_edge( 8, 7), "order of vertices should not matter"
def test__len__(self) -> None: g = Graph([(1, 2), (2, 3), (3, 4)]) tree: Tree[Vertex, Edge] = Tree(g[1]) tree.add_edge(g.get_edge(1, 2)) tree.add_edge(g.get_edge(2, 3)) tree.add_edge(g.get_edge(3, 4)) assert len(tree) == 4, "tree should contain 4 vertices"
def test_edmonds_undirected_graph(self) -> None: g = Graph([("s", "t", 10), ("s", "y", 5), ("t", "y", 2)]) # Edmonds' algorithm does not work on undirected graphs. with pytest.raises(exception.GraphTypeNotSupported): for _ in directed.edmonds(g): pass
def test_spanning_arborescence_undirected_graph(self) -> None: g = Graph([("s", "t", 10), ("s", "y", 5), ("t", "y", 2)]) # Spanning arborescence is undefined for an undirected graph. with pytest.raises(exception.GraphTypeNotSupported): for _ in directed.spanning_arborescence(g): pass
def test_component(self) -> None: g = Graph([(1, 2), (2, 3), (4, 5), (7, 7)]) component_list: List[Component[Vertex, Edge]] = list( components.connected_components(g)) c_123: Component[Vertex, Edge] = [c for c in component_list if 1 in c][0] assert not c_123._edge_set, "without accessing edges, `_edge_set` should not be initialized" c_123.edges() assert c_123._edge_set, "after accessing edges, `_edge_set` should be initialized" c_45 = None c_77 = None for component in component_list: if (4, 5) in component: c_45 = component elif (7, 7) in component: c_77 = component assert c_45 is not None assert c_77 is not None assert ( c_45._edge_set is not None ), "calling __contains__ should result in _edge_set initialization" assert ( c_77._edge_set is not None ), "calling __contains__ should result in _edge_set initialization" assert g[ 7] in c_77, "vertex 7 should be in component containing edge (7, 7)"
def test_vertices(self) -> None: g = Graph([(1, 2), (2, 3), (3, 4)]) assert set(g.vertices()) == { g[1], g[2], g[3], g[4], }, "graph should have vertices 1, 2, 3, 4"
def test_kruskal_optimum_forest_single_tree(self) -> None: g = Graph(test_edges) spanning_edges = set(undirected.kruskal_spanning_tree(g)) spanning_tree = next(undirected.kruskal_optimum_forest(g)) assert spanning_edges == set(spanning_tree.edges()) assert spanning_tree.weight == 28, "Min spanning tree weight should be 28."
def test_edges_and_edge_count(self) -> None: g = Graph([(1, 2), (2, 1), (2, 3), (3, 4)]) assert set(g.edges()) == { g.get_edge(1, 2), g.get_edge(2, 3), g.get_edge(3, 4), }, "edges should be (1, 2), (2, 3), (3, 4)" assert g.edge_count == 3, "graph should have 3 edges"
def test_attr(self) -> None: g = Graph([(1, 2), (3, 4)], name="undirected graph", secret=42) assert g.attr["name"] == "undirected graph" assert g.attr[ "secret"] == 42, "graph should have 'secret' attribute set to 42" with pytest.raises(KeyError): _ = g.attr["unknown_key"]
def test_has_vertex(self) -> None: g = Graph() g.add_edge(1, 2) assert g.has_vertex(1), "vertex specified as int should be in graph" assert g.has_vertex("1"), "vertex specified as str should be in graph" v1 = g[1] assert g.has_vertex( v1), "vertex specified as object should be in graph" assert not g.has_vertex(3), "vertex 3 should not be in the graph"
def test_is_weighted(self) -> None: g = Graph() g.add_edge(1, 2) assert ( not g.is_weighted() ), "graph without custom edge weights should not identify as weighted" g.add_edge(3, 4, weight=9.5) assert g.is_weighted( ), "graph with custom edge weights should identify as weighted"
def test_merge(self) -> None: g = Graph([(1, 2), (2, 3), (1, 4), (3, 4), (4, 5)]) tree1: Tree[Vertex, Edge] = Tree(g[1]) tree5: Tree[Vertex, Edge] = Tree(g[5]) tree1.add_edge(g.get_edge(1, 4)) tree1.add_edge(g.get_edge(4, 3)) tree5.add_edge(g.get_edge(5, 4)) tree1.merge(tree5) assert set(tree1.vertices()) == {tree1[1], tree1[3], tree1[4], tree1[5]}
def test_bfs_undirected_graph_with_self_loop_and_two_trees(self) -> None: g = Graph([(0, 0), (0, 1), (1, 2), (3, 4)]) results: SearchResults[Vertex, Edge] = bfs(g) assert len(results.graph_search_trees()) == 2, "BFS should have discovered two BFS trees" assert len(results.vertices_preorder()) == 5, "BFS tree should have 5 vertices" assert ( results.vertices_preorder() == results.vertices_postorder() ), "a BFS should yield vertices in same order for both preorder and postorder" assert len(results.back_edges()) == 1, "graph should have one self-loop back edge"
def test_add_vertices_from(self) -> None: g = Graph() g.add_vertices_from([1, "2", ("v3", {"color": "blue", "mass": 42})]) assert g.has_vertex(1), "graph should have vertex 1" assert g.has_vertex(2), "graph should have vertex 2" assert g.has_vertex("v3"), "graph should have vertex v3" assert g["v3"][ "color"] == "blue", "vertex should have 'color' attribute set to 'blue'" assert g["v3"][ "mass"] == 42, "vertex should have 'mass' attribute set to 42"
def test__iter__(self) -> None: g = Graph([(1, 2), (2, 3), (3, 4)]) tree: Tree[Vertex, Edge] = Tree(g[1]) tree.add_edge(g.get_edge(1, 2)) tree.add_edge(g.get_edge(2, 3)) tree.add_edge(g.get_edge(3, 4)) count = sum(1 for _ in tree) assert count == 4, "tree should iterate over its 4 vertices" assert set([tree[1], tree[2], tree[3], tree[4]]) == set( tree ), "tree should iterate over its 4 vertices"
def test_add_vertex(self) -> None: g = Graph() g.add_vertex(1) g.add_vertex("2") g.add_vertex("v3", color="blue", mass=42) assert g.has_vertex(1), "graph should have vertex 1" assert g.has_vertex(2), "graph should have vertex 2" assert g.has_vertex("v3"), "graph should have vertex v3" assert g["v3"][ "color"] == "blue", "vertex should have 'color' attribute set to 'blue'" assert g["v3"][ "mass"] == 42, "vertex should have 'mass' attribute set to 42"
def test_contains(self) -> None: d: VertexDict[str] = VertexDict(**{"1": "one", "2": "two"}) assert 1 in d assert "1" in d assert d.__contains__(2) g = Graph([(0, "six")]) d2: VertexDict[str] = VertexDict() d2[g[0]] = "zero" d2[g["six"]] = "six" assert 0 in d2 assert g[0] in d2 assert "six" in d2 assert g["six"] in d2
def test_kruskal_optimum_forest_multiple_trees(self) -> None: g = Graph(test_edges) g.add_edge("x", "y", weight=22) g.add_edge("y", "z", weight=20) g.add_vertex("isolated") count = 0 total_weight = 0.0 for tree in undirected.kruskal_optimum_forest(g): count += 1 total_weight += tree.weight assert count == 3, "there should be 3 trees in the spanning forest" assert total_weight == 70, "total weight of trees should be 70"
def test_add_edge(self) -> None: g = Graph([(1, 2), (2, 3), (1, 4), (3, 4), (4, 5)]) tree: Tree[Vertex, Edge] = Tree(g[1]) tree.add_edge(g.get_edge(1, 2)) assert (1, 2) in tree tree.add_edge(g.get_edge(2, 3)) with pytest.raises(exception.Unfeasible): # Raise exception due to (4, 5) not containing a vertex already in the tree. tree.add_edge(g.get_edge(4, 5)) tree.add_edge(g.get_edge(3, 4)) with pytest.raises(exception.Unfeasible): # Raises exception due to cycle. tree.add_edge(g.get_edge(1, 4))
def test_get_random_edge(self) -> None: g = Graph([(1, 2), (3, 4), (5, 6)]) cnt: Counter[Edge] = collections.Counter() for _ in range(1000): rand_edge = g.get_random_edge() if rand_edge is None: raise Exception("rand_edge returned None") cnt[rand_edge] += 1 assert cnt[g.get_edge( 1, 2)] > 270, r"~33% of random samples should be edge (1, 2)" assert cnt[g.get_edge( 3, 4)] > 270, r"~33% of random samples should be edge (3, 4)" assert cnt[g.get_edge( 5, 6)] > 270, r"~33% of random samples should be edge (5, 6)"
def test__getitem__setitem(self) -> None: g = Graph() v1: Vertex = g.add_vertex(1) pairs = [("1", "one"), (2, "two")] d1: VertexDict[str] = VertexDict(pairs) assert d1[ v1] == "one", "Dict d1 getitem should work with Vertex object key" assert d1[1] == "one", "Dict d1 getitem should work with int key" assert d1["1"] == "one", "Dict d1 getitem should work with int key" assert d1[ "2"] == "two", "Dict d1 should have item associated with key 2" d1[2] = "new value" assert d1[ "2"] == "new value", 'Dict d1 should have "new value" for key 2'
def test_remove_vertex(self) -> None: g = Graph([(1, 2), (3, 3)]) g.add_vertex(4) assert g.has_vertex(4), "graph should have vertex 4" g.remove_vertex(4) assert not g.has_vertex( 4), "graph should not have vertex 4 after removal" assert g.has_vertex(3), "graph should have semi-isolated vertex 3" g.remove_vertex(3) assert not g.has_vertex( 3), "graph should not have vertex 3 after removal" with pytest.raises(exception.VertizeeException): g.remove_vertex(1)
def test_remove_edge(self) -> None: g = Graph([(1, 2), (2, 3), (3, 4)]) assert g.edge_count == 3, "graph should have 3 edges" g.remove_edge(1, 2) assert g.edge_count == 2, "after edge removal, graph should have 2 edges" assert g.has_vertex( 1), "isolated vertex 1 should not have been removed" g.remove_edge(2, 3, remove_semi_isolated_vertices=True) assert g.edge_count == 1, "after edge removal, graph should have 1 edge" assert not g.has_vertex( 2 ), "with `remove_semi_isolated_vertices` set to True, vertex 2 should have been removed" with pytest.raises(exception.EdgeNotFound): g.remove_edge(8, 9)
def test_connected_components(self) -> None: g = Graph([(1, 2), (2, 3), (4, 5), (7, 7)]) g.add_vertex(8) component_list: List[Component[Vertex, Edge]] = list( components.connected_components(g)) assert len(component_list) == 4, "graph should have 4 components" edge_count = sum( len(list(component.edges())) for component in component_list) assert edge_count == 4, "components should contain a grand total of 4 edges" mg: MultiDiGraph = get_example_multidigraph() scc_list: List[Component[MultiDiVertex, MultiDiEdge]] = list( components.connected_components(mg)) assert len( scc_list ) == 4, "multidigraph should have 4 strongly-connected components"
def test_dfs_traversal_undirected_graph(self) -> None: g = Graph([(0, 1), (1, 2), (1, 3), (2, 3), (3, 4), (4, 5), (3, 5), (6, 7)]) edge_iter = dfs_labeled_edge_traversal(g) dfs_edge_tuples = list(edge_iter) tree_roots = set( child for parent, child, label, direction in dfs_edge_tuples if label == Label.TREE_ROOT ) assert len(tree_roots) == 2, "there should be two DFS trees" vertices = set(child for parent, child, label, direction in dfs_edge_tuples) assert len(vertices) == 8, "DFS traversal should include all vertices" vertices_preorder = list( child for parent, child, label, direction in dfs_edge_tuples if direction == Direction.PREORDER ) vertices_postorder = list( child for parent, child, label, direction in dfs_edge_tuples if direction == Direction.POSTORDER ) assert len(vertices_preorder) == len( vertices_postorder ), "the number of preorder vertices should match the number of postorder vertices" back_edges = set( (parent, child) for parent, child, label, direction in dfs_edge_tuples if label == Label.BACK_EDGE ) cross_edges = set( (parent, child) for parent, child, label, direction in dfs_edge_tuples if label == Label.CROSS_EDGE ) forward_edges = set( (parent, child) for parent, child, label, direction in dfs_edge_tuples if label == Label.FORWARD_EDGE ) assert len(back_edges) > 0, "graph should have back edges, since there are cycles" assert ( not cross_edges and not forward_edges ), "DFS on an undirected graph, every edge is either a tree edge or a back edge"
def test_dfs_undirected_cyclic_graph_with_self_loop(self) -> None: g = Graph([(0, 0), (0, 1), (1, 2), (3, 4)]) results: SearchResults[Vertex, Edge] = dfs(g) assert len(results.graph_search_trees()) == 2, "DFS should have discovered two DFS trees" assert len(results.vertices_preorder()) == 5, "DFS tree should have 5 vertices" assert len(results.vertices_preorder()) == len( results.vertices_postorder() ), "number of vertices should be the same in discovery as well post order" assert len(results.back_edges()) == 1, "graph should have one self-loop back edge" assert ( len(results.cross_edges()) == 0 ), "graph should have zero cross edges (true for all undirected graphs)" assert ( len(results.forward_edges()) == 0 ), "graph should have zero forward edges (true for all undirected graphs)" assert not results.is_acyclic(), "should not be acyclic, since it contains a self loop"