def test_bellman_ford_path_reconstruction(self) -> None:
        g = DiGraph(
            [
                ("s", "t", 10),
                ("s", "y", 5),
                ("t", "y", -6),
                ("t", "x", 1),
                ("x", "z", 4),
                ("y", "t", 8),
                ("y", "x", 4),
                ("y", "z", -3),
                ("z", "s", 7),
                ("z", "x", 6),
            ]
        )

        path_dict: VertexDict[ShortestPath[DiVertex]] = bellman_ford(g, "s", save_paths=True)

        assert path_dict["t"].path() == ["s", "t"], "Path s ~> t should be [s, t]."
        assert path_dict["x"].path() == [
            "s",
            "t",
            "y",
            "z",
            "x",
        ], "Path s ~> x should be [s, t, y, z, x]."
        assert path_dict["z"].path() == ["s", "t", "y", "z"], "Path s ~> z should be [s, t, y, z]."

        path_s_t = reconstruct_path("s", "t", path_dict)
        assert path_s_t == path_dict["t"].path(), "Algorithm path should match reconstructed path."
        path_s_x = reconstruct_path("s", "x", path_dict)
        assert path_s_x == path_dict["x"].path(), "Algorithm path should match reconstructed path."
        path_s_z = reconstruct_path("s", "z", path_dict)
        assert path_s_z == path_dict["z"].path(), "Algorithm path should match reconstructed path."
    def test_bellman_ford_undirected_negative_weight_cycle(self) -> None:
        g = MultiGraph(
            [
                ("s", "t", 10),
                ("s", "y", 5),
                ("t", "y", -6),
                ("t", "x", 1),
                ("x", "z", 4),
                ("y", "t", 8),
                ("y", "x", 4),
                ("y", "z", -3),
                ("z", "s", 7),
                ("z", "x", 6),
            ]
        )

        with pytest.raises(NegativeWeightCycle):
            bellman_ford(g, "s")
    def test_bellman_ford_negative_edge_weights(self) -> None:
        g = DiGraph(
            [
                ("s", "t", 10),
                ("s", "y", 5),
                ("t", "y", -6),
                ("t", "x", 1),
                ("x", "z", 4),
                ("y", "t", 8),
                ("y", "x", 4),
                ("y", "z", -3),
                ("z", "s", 7),
                ("z", "x", 6),
            ]
        )

        path_dict: VertexDict[ShortestPath[DiVertex]] = bellman_ford(g, "s")

        assert len(path_dict) == 5, "Shortest path_dict dictionary should have length equal to |V|."
        assert path_dict["s"].length == 0, "Length of s path should be 0."
        assert path_dict["t"].length == 10, "Length of path s ~> t should be 10."
        assert path_dict["x"].length == 7, "Length of path s ~> x should be 7."
        assert path_dict["y"].length == 4, "Length of path s ~> y should be 4."
        assert path_dict["z"].length == 1, "Length of path s ~> z should be 1."
    def test_bellman_ford_default_edge_weight(self) -> None:
        g = DiGraph(
            [
                ("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[DiVertex]] = bellman_ford(g, "s")

        assert len(path_dict) == 5, "Shortest path_dict dictionary should have length equal to |V|."
        assert path_dict["s"].length == 0, "Length of s path should be 0."
        assert path_dict["t"].length == 8, "Length of path s ~> t should be 8."
        assert path_dict["x"].length == 9, "Length of path s ~> x should be 9."
        assert path_dict["y"].length == 5, "Length of path s ~> y should be 5."
        assert path_dict["z"].length == 7, "Length of path s ~> z should be 7."
    def test_bellman_ford_undirected(self) -> None:
        g = MultiGraph(
            [
                ("s", "t", 10),
                ("s", "y", 5),
                ("t", "y", 6),
                ("t", "x", 1),
                ("x", "z", 4),
                ("y", "t", 8),
                ("y", "x", 4),
                ("y", "z", 3),
                ("z", "s", 7),
                ("z", "x", 6),
            ]
        )

        path_dict: VertexDict[ShortestPath[MultiVertex]] = bellman_ford(g, "s")

        assert len(path_dict) == 5, "Shortest path_dict dictionary should have length equal to |V|."
        assert path_dict["s"].length == 0, "Length of s path should be 0."
        assert path_dict["t"].length == 10, "Length of path s ~> t should be 10."
        assert path_dict["x"].length == 9, "Length of path s ~> x should be 9."
        assert path_dict["y"].length == 5, "Length of path s ~> y should be 5."
        assert path_dict["z"].length == 7, "Length of path s ~> z should be 7."
예제 #6
0
def johnson_fibonacci(
    graph: "GraphBase[V_co, E_co]",
    save_paths: bool = False,
    weight: str = "Edge__weight"
) -> "VertexDict[VertexDict[ShortestPath[V_co]]]":
    r"""Finds the shortest paths between all pairs of vertices in a graph using Donald Johnson's
    algorithm implemented with a Fibonacci heap version of Dijkstra's algorithm.

    Running time: :math:`O(n^2 (\log{n}) + mn)` where :math:`m = |E|` and :math:`n = |V|`

    Pairs of vertices for which there is no connecting path will have path length infinity. In
    additional, :meth:`ShortestPath.is_destination_reachable()
    <vertizee.algorithms.algo_utils.path_utils.ShortestPath.is_destination_reachable>` will return
    False.

    Note:
        This implementation is based on JOHNSON. :cite:`2009:clrs`

    Args:
        graph: The graph to search.
        save_paths: Optional; If True, saves the actual vertex sequences comprising each
            path. To reconstruct specific shortest paths, see
            :func:`vertizee.algorithms.algo_utils.path_utils.reconstruct_path`. Defaults to False.
        weight: Optional; The key to use to retrieve the weight from the edge ``attr``
            dictionary. The default value ("Edge__weight") uses the edge property ``weight``.

    Returns:
        VertexDict[VertexDict[ShortestPath]]: A dictionary mapping source vertices to dictionaries
        mapping destination vertices to :class:`ShortestPath
        <vertizee.algorithms.algo_utils.path_utils.ShortestPath>` objects.

    Raises:
        NegativeWeightCycle: If the graph contains a negative weight cycle.

    See Also:
        * :func:`reconstruct_path <vertizee.algorithms.algo_utils.path_utils.reconstruct_path>`
        * :class:`ShortestPath <vertizee.algorithms.algo_utils.path_utils.ShortestPath>`
        * :class:`VertexDict <vertizee.classes.data_structures.vertex_dict.VertexDict>`
    """
    weight_function = get_weight_function(weight)

    g_prime = graph.deepcopy()
    G_PRIME_SOURCE = "__g_prime_src"
    for v in graph.vertices():
        g_prime.add_edge(G_PRIME_SOURCE, v, weight=0)

    bellman_paths: VertexDict[ShortestPath[V_co]] = single_source.bellman_ford(
        g_prime, G_PRIME_SOURCE)

    # pylint: disable=unused-argument
    def new_weight(v1: VertexType,
                   v2: VertexType,
                   reverse_graph: bool = False) -> float:
        edge = graph.get_edge(v1, v2)
        return weight_function(
            edge) + bellman_paths[v1].length - bellman_paths[v2].length

    source_and_destination_to_path: VertexDict[VertexDict[
        ShortestPath[V_co]]] = VertexDict()

    for i in graph:
        source_and_destination_to_path[i] = VertexDict()
        dijkstra_paths: VertexDict[
            ShortestPath[V_co]] = single_source.dijkstra_fibonacci(
                graph, source=i, weight=new_weight, save_paths=save_paths)
        for j in graph:
            source_and_destination_to_path[i][j] = dijkstra_paths[j]
            source_and_destination_to_path[i][j]._length += (
                bellman_paths[j].length - bellman_paths[i].length)

    return source_and_destination_to_path