def test_digraph_read_adj_list(self) -> None: g = MultiDiGraph() read_adj_list(os.path.join(os.getcwd(), TEST_DIR, DIGRAPH_FILE01), g) assert g.vertex_count == 5, "graph should have 5 vertices" assert g[1].loop_edge is not None, "v1 should have a loop" assert (len(g[1].incident_edges_incoming()) == 2 ), "v1 should have 2 incoming edges (including its loop)" assert len(g[1].incident_edges_outgoing() ) == 3, "v1 edges_outgoing should have length 3" assert g[1].degree == 6, "deg(v1) should be 6" assert g[2].degree == 4, "deg(v2) should be 4" assert len(g[2].incident_edges_outgoing() ) == 2, "v2 should have 2 outgoing edges" assert len(g[3].incident_edges_incoming() ) == 2, "v3 should have 2 incoming edges" assert len(g[3].incident_edges_outgoing() ) == 1, "v3 should have 1 outgoing edge" assert len(g[4].incident_edges_outgoing() ) == 0, "v4 should have 0 outgoing edges" v5_loop_edge = g[5].loop_edge assert v5_loop_edge is not None assert v5_loop_edge.multiplicity == 2, "v5 should have 2 loops" assert (len(g[5].incident_edges_incoming()) == 1 ), "v5 should have 1 incoming edge (self-loop)" with pytest.raises(KeyError): assert g.get_edge(4, 3) is None, "graph should not have edge (4, 3)" assert g.get_edge(3, 4) is not None, "graph should have edge (3, 4)"
def test_add_edges_from(self) -> None: g = MultiDiGraph() g.add_edges_from([ (1, 2, 4.5, { "color": "blue", "mass": 42 }), (4, 3, 9.5), (5, 6, { "color": "red", "mass": 99 }), (8, 7), (8, 7), (7, 8), ]) connection12 = g.get_edge(1, 2).connections()[0] connection43 = g.get_edge(4, 3).connections()[0] assert g.edge_count == 6, "graph should have 6 edges" assert g.get_edge( 1, 2).weight == 4.5, "edge (1, 2) should have weight 4.5" assert connection12[ "color"] == "blue", "edge (1, 2) should have 'color' set to 'blue'" assert connection12[ "mass"] == 42, "edge (1, 2) should have 'mass' set to 42" assert (not connection43.has_attributes_dict() ), "edge connection (4, 3) 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 not g.get_edge( 8, 7), "order of vertices define different directed edges" assert (g.get_edge(8, 7).multiplicity == 2 ), "edge (8, 7) should have two parallel connections"
def test_dfs_directed_cyclic_graph(self) -> None: g = MultiDiGraph() # This graph is from "Introduction to Algorithms: Third Edition", page 607. g.add_edges_from( [ ("y", "x"), ("z", "y"), ("z", "w"), ("s", "z"), ("s", "w"), ("t", "v"), ("t", "u"), ("x", "z"), ("w", "x"), ("v", "w"), ("v", "s"), ("u", "v"), ("u", "t"), ] ) # Test DiGraph DFS by specifying source vertex s. results: SearchResults[MultiDiVertex, MultiDiEdge] = dfs(g, "s") assert ( len(results.graph_search_trees()) == 1 ), "DFS search should find 1 DFS tree, since source vertex 's' was specified" tree: Tree[MultiDiVertex, MultiDiEdge] = next(iter(results.graph_search_trees())) assert len(tree.edges()) == 4, "DFS tree rooted at vertex 's' should have 4 edges" assert len(tree) == 5, "DFS tree rooted at vertex 's' should have 5 vertices" assert results.vertices_preorder()[0] == "s", "first vertex should be s" assert not results.is_acyclic(), "graph should not be acyclic, since it contains cycles" # Test DiGraph DFS without specifying a source vertex. results: SearchResults[MultiDiVertex, MultiDiEdge] = dfs(g) assert len(results.vertices_preorder()) == len( results.vertices_postorder() ), "graph should equal number of vertices in pre and post order" assert ( len(results.vertices_postorder()) == 8 ), "all vertices should be accounted for when a source vertex is not specified" classified_edge_count = ( len(results.back_edges()) + len(results.cross_edges()) + len(results.forward_edges()) + len(results.tree_edges()) ) assert classified_edge_count == g.edge_count, "classified edges should equal total edges"
def test_edmonds_min_arborescence_multigraph(self) -> None: g = MultiDiGraph([ ("a", "b", 1), ("a", "b", 2), ("b", "c", 3), ("b", "c", 6), ("c", "d", 5), ("c", "d", 10), ("d", "a", 2), ("d", "a", 4), ]) spanning_edges_min_arborescence = {("d", "a"), ("a", "b"), ("b", "c")} arborescence = next( directed.edmonds(g, minimum=True, find_spanning_arborescence=True)) for edge_label in spanning_edges_min_arborescence: assert edge_label in arborescence assert len(arborescence.edges() ) == 3, "min spanning arborescence should have 3 edges" weight = 0.0 for edge in spanning_edges_min_arborescence: weight += min( c.weight for c in arborescence.get_edge(edge[0], edge[1]).connections()) assert weight == 6
def test_weakly_connected_components(self) -> None: g: MultiDiGraph = get_example_multidigraph() scc_list = list(components.weakly_connected_components(g)) assert len( scc_list) == 1, "graph should have one weakly-connected component" g2 = MultiDiGraph([(1, 2), (3, 2), (4, 5)]) assert len(list(components.weakly_connected_components(g2))) == 2 assert len(list(components.strongly_connected_components(g2))) == 5
def get_example_multidigraph() -> MultiDiGraph: """This graph is from "Introduction to Algorithms: Third Edition", page 616. It contains four strongly-connected components.""" g = MultiDiGraph() g.add_edges_from([ ("a", "b"), ("b", "e"), ("e", "a"), ("e", "f"), ("b", "f"), ("b", "c"), ("c", "d"), ("d", "c"), ("c", "g"), ("d", "h"), ("h", "h"), ("f", "g"), ("g", "f"), ]) return g
def test_edges_and_edge_count(self) -> None: g = MultiDiGraph([(1, 2), (2, 1), (2, 3), (3, 4)]) assert set(g.edges()) == { g.get_edge(1, 2), g.get_edge(2, 1), g.get_edge(2, 3), g.get_edge(3, 4), }, "multiedges should be (1, 2), (2, 1), (2, 3), (3, 4)" assert g.edge_count == 4, "graph should have 4 edge connections" assert (g.get_edge(1, 2).multiplicity == 1 ), "multiedge (1, 2) should have one edge connection"
def test_dijkstra_reverse_graph(self) -> None: g = MultiDiGraph( [ ("s", "t", 10), ("s", "y", 5), ("t", "y", 2), ("t", "x", 1), ("x", "z", 4), ("y", "t", 3), ("y", "x", 9), ("y", "z", 2), ("z", "s", 7), ("z", "x", 6), ] ) path_dict: VertexDict[ShortestPath[MultiDiVertex]] = dijkstra(g, "s", reverse_graph=True) assert path_dict["s"].length == 0, "Length of s path should be 0." assert path_dict["t"].length == 11, "Length of path s ~> t should be 11." assert path_dict["x"].length == 11, "Length of path s ~> x should be 11." assert path_dict["y"].length == 9, "Length of path s ~> y should be 9." assert path_dict["z"].length == 7, "Length of path s ~> z should be 7."
def test_get_random_edge(self) -> None: g = MultiDiGraph([(1, 2), (1, 2), (3, 4)]) cnt1: Counter[MultiDiEdge] = collections.Counter() for _ in range(1000): rand_edge = g.get_random_edge_weighted_by_multiplicity() if rand_edge is None: raise Exception("rand_edge returned None") cnt1[rand_edge] += 1 assert cnt1[g.get_edge( 1, 2)] > 600, r"~66% of random samples should be edge (1, 2)" assert cnt1[g.get_edge( 3, 4)] < 400, r"~33% of random samples should be edge (3, 4)" cnt2: Counter[MultiDiEdge] = collections.Counter() for _ in range(1000): rand_edge = g.get_random_edge() if rand_edge is None: raise Exception("rand_edge returned None") cnt2[rand_edge] += 1 assert cnt2[g.get_edge( 1, 2)] > 400, r"~50% of random samples should be edge (1, 2)" assert cnt2[g.get_edge( 3, 4)] > 400, r"~50% of random samples should be edge (3, 4)"
def test__init__from_graph(self) -> None: g0 = MultiGraph([(2, 1, 5.0, { "color": "red" }), (2, 1, 2.5, { "color": "blue" })]) g = MultiDiGraph(g0) assert g.has_edge( 2, 1), "multigraph should have edge (2, 1) copied from graph" connection21 = g.get_edge(2, 1).connections()[0] assert ( connection21["color"] == "red" ), "first connection of edge (2, 1) should have 'color' attribute set to red" assert g.get_edge( 2, 1).weight == 7.5, "multiedge (2, 1) should have weight 7.5" assert g.edge_count == 2, "graph should have two edges" assert g.weight == 7.5, "graph should have weight 6.0" assert g.get_edge(2, 1) is not g0.get_edge( # type: ignore 2, 1), "multigraph should have deep copies of edges and vertices"
def test_add_edge(self) -> None: g = MultiDiGraph() edge = g.add_edge(1, 2, weight=4.5, color="blue", mass=42) assert isinstance(edge, MultiDiEdge), "new edge should a MultiDiEdge object" assert g.get_edge( 1, 2).weight == 4.5, "edge (1, 2) should have weight 4.5" connection12 = g.get_edge(1, 2).connections()[0] assert connection12[ "color"] == "blue", "edge should have 'color' attribute set to 'blue'" assert connection12[ "mass"] == 42, "edge should have 'mass' attribute set to 42" edge_dup = g.add_edge(1, 2, weight=1.5, color="red", mass=57) assert ( edge is edge_dup ), "adding an edge with same vertices as existing edge should return existing edge" assert ( g.get_edge(1, 2).multiplicity == 2 ), "after adding parallel connection, edge multiplicity should be 2" g.add_edge(3, 4) assert (g.get_edge(3, 4).weight == edge_module.DEFAULT_WEIGHT ), "edge should have default weight" connection34 = g.get_edge(3, 4).connections()[0] assert not connection34.has_attributes_dict( ), "edge should not have attributes dictionary"
def test_bfs_directed_cyclic_graph(self) -> None: g = MultiDiGraph() g.add_edges_from( [ (1, 2), (2, 2), (2, 1), (1, 3), (4, 3), (4, 5), (4, 7), (5, 7), (5, 7), (7, 5), (7, 6), (6, 5), (7, 8), (5, 8), ] ) # from pprint import pprint # pprint(list(bfs_labeled_edge_traversal(g, 1))) results1: SearchResults[MultiDiVertex, MultiDiEdge] = bfs(g, 1) search_trees = results1.graph_search_trees() assert ( len(search_trees) == 1 ), "BFS search should find 1 BFS tree, since source vertex was specified" tree: Tree[MultiDiVertex, MultiDiEdge] = search_trees[0] assert len(tree.edges()) == 2, "BFS tree rooted at vertex 1 should have 2 tree edges" assert len(tree) == 3, "BFS tree rooted at vertex 1 should have 3 vertices" assert results1.vertices_preorder()[0] == 1, "first vertex should be 1" assert len(results1.tree_edges()) == 2 assert len(results1.back_edges()) == 2 assert not results1.forward_edges() assert not results1.cross_edges() results4: SearchResults[MultiDiVertex, MultiDiEdge] = bfs(g, 4) assert len(results4.tree_edges()) == 5 assert len(results4.back_edges()) == 1 assert len(results4.cross_edges()) == 2 assert len(results4.forward_edges()) == 1 # Test DiGraph BFS without specifying a source vertex. results: SearchResults[MultiDiVertex, MultiDiEdge] = bfs(g) assert len(results.graph_search_trees()) > 1, "BFS search should find at least 2 BFS trees" assert ( results.vertices_preorder() == results.vertices_postorder() ), "vertices should be the same in preorder and postorder" assert ( len(results.vertices_postorder()) == 8 ), "all vertices should be accounted for when a source vertex is not specified" classified_edge_count = ( len(results.back_edges()) + len(results.cross_edges()) + len(results.forward_edges()) + len(results.tree_edges()) ) assert classified_edge_count == len(g.edges()), "classified edges should equal total edges"
def test_digraph_write_adj_list(self) -> None: g = MultiDiGraph() read_adj_list(os.path.join(os.getcwd(), TEST_DIR, DIGRAPH_FILE01), g) write_adj_list_to_file( os.path.join(os.getcwd(), TEST_DIR, DIGRAPH_OUTPUT_FILE), g)