예제 #1
0
    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)"
예제 #2
0
    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"
예제 #3
0
    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"
예제 #4
0
    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
예제 #5
0
    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
예제 #6
0
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
예제 #7
0
 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."
예제 #9
0
    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)"
예제 #10
0
    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"
예제 #11
0
    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"
예제 #12
0
    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"
예제 #13
0
 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)