def test_dijkstra_edge_attr_weights(self) -> None: WEIGHT = "weight_key" g = DiGraph( [ ("s", "t"), ("s", "y"), ("t", "y"), ("t", "x"), ("x", "z"), ("y", "t"), ("y", "x"), ("y", "z"), ("z", "s"), ("z", "x"), ] ) g.get_edge("s", "t")[WEIGHT] = 10 g.get_edge("s", "y")[WEIGHT] = 5 g.get_edge("t", "y")[WEIGHT] = 2 g.get_edge("t", "x")[WEIGHT] = 1 g.get_edge("x", "z")[WEIGHT] = 4 g.get_edge("y", "t")[WEIGHT] = 3 g.get_edge("y", "x")[WEIGHT] = 9 g.get_edge("y", "z")[WEIGHT] = 2 g.get_edge("z", "s")[WEIGHT] = 7 g.get_edge("z", "x")[WEIGHT] = 6 path_dict: VertexDict[ShortestPath[DiVertex]] = dijkstra(g, "s", weight=WEIGHT) 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_dijkstra_path_reconstruction(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]] = dijkstra(g, "s", save_paths=True) assert path_dict["t"].path() == ["s", "y", "t"], "Path s ~> t should be [s, y, t]." assert path_dict["x"].path() == ["s", "y", "t", "x"], "Path s ~> x should be [s, y, t, x]." assert path_dict["z"].path() == ["s", "y", "z"], "Path s ~> z should be [s, 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_dijkstra_edge_weight_filter_function(self) -> None: COLOR = "color_key" 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), ] ) g.get_edge("s", "t")[COLOR] = "RED" g.get_edge("s", "y")[COLOR] = "BLUE" g.get_edge("t", "y")[COLOR] = "RED" g.get_edge("t", "x")[COLOR] = "RED" g.get_edge("x", "z")[COLOR] = "RED" g.get_edge("y", "t")[COLOR] = "BLUE" g.get_edge("y", "x")[COLOR] = "RED" g.get_edge("y", "z")[COLOR] = "BLUE" g.get_edge("z", "s")[COLOR] = "BLUE" g.get_edge("z", "x")[COLOR] = "BLUE" # Exclude blue edges. def get_weight(v1: V, v2: V, reverse_graph: bool) -> Optional[float]: graph = v1._parent_graph if reverse_graph: edge = graph.get_edge(v2, v1) edge_str = f"({v2.label}, {v1.label})" else: edge = graph.get_edge(v1, v2) edge_str = f"({v1.label}, {v2.label})" if edge is None: raise ValueError(f"graph does not have edge {edge_str}") if graph.is_multigraph(): assert isinstance(edge, MultiEdgeBase) has_color = any(c.attr.get(COLOR, "no color") == "BLUE" for c in edge.connections()) if has_color: return None return min(c.weight for c in edge.connections()) if cast(Attributes, edge).attr.get(COLOR, "no color attribute") == "BLUE": return None return edge.weight path_dict: VertexDict[ShortestPath[DiVertex]] = dijkstra(g, "s", weight=get_weight) 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 == 11, "Length of path s ~> x should be 11." assert path_dict["y"].length == 12, "Length of path s ~> y should be 12." assert path_dict["z"].length == 15, "Length of path s ~> z should be 15."
def test_dijkstra_undirected_graph(self) -> None: g = MultiGraph( [ ("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[MultiVertex]] = dijkstra(g, "s") assert path_dict["s"].length == 0, "Length of s path should be 0." assert path_dict["t"].length == 7, "Length of path s ~> t should be 7." assert path_dict["x"].length == 8, "Length of path s ~> x should be 8." 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_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_dijkstra_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]] = dijkstra(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 johnson( 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. Running time: :math:`O(mn(\log{n}))` where :math:`m = |E|` and :math:`n = |V|` For a theoretically faster implementation with running time :math:`O((n^2)\log{n} + mn)`, see :func:`johnson_fibonacci`. When :math:`m > (n^2)/\log{n}`, then the graph is sufficiently dense that the Floyd-Warshall algorithm will provide better asymptotic running time. See :func:`floyd_warshall`. 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(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