diff --git a/docs/source/api/algorithm_functions/shortest_paths.rst b/docs/source/api/algorithm_functions/shortest_paths.rst index 9a7f98961..a2727eac5 100644 --- a/docs/source/api/algorithm_functions/shortest_paths.rst +++ b/docs/source/api/algorithm_functions/shortest_paths.rst @@ -19,6 +19,7 @@ Shortest Paths rustworkx.distance_matrix rustworkx.floyd_warshall rustworkx.floyd_warshall_numpy + rustworkx.floyd_warshall_successor_and_distance rustworkx.astar_shortest_path rustworkx.k_shortest_path_lengths rustworkx.num_shortest_paths_unweighted diff --git a/docs/source/api/pydigraph_api_functions.rst b/docs/source/api/pydigraph_api_functions.rst index 1c33e95f6..933c76b38 100644 --- a/docs/source/api/pydigraph_api_functions.rst +++ b/docs/source/api/pydigraph_api_functions.rst @@ -17,7 +17,7 @@ the functions from the explicitly typed based on the data type. rustworkx.digraph_distance_matrix rustworkx.digraph_floyd_warshall rustworkx.digraph_floyd_warshall_numpy - rustworkx.digraph_floyd_warshall_successor_and_distance_numpy + rustworkx.digraph_floyd_warshall_successor_and_distance rustworkx.digraph_adjacency_matrix rustworkx.digraph_all_simple_paths rustworkx.digraph_all_pairs_all_simple_paths diff --git a/docs/source/api/pygraph_api_functions.rst b/docs/source/api/pygraph_api_functions.rst index 7481fe12f..bc40f824e 100644 --- a/docs/source/api/pygraph_api_functions.rst +++ b/docs/source/api/pygraph_api_functions.rst @@ -17,7 +17,7 @@ typed API based on the data type. rustworkx.graph_distance_matrix rustworkx.graph_floyd_warshall rustworkx.graph_floyd_warshall_numpy - rustworkx.graph_floyd_warshall_successor_and_distance_numpy + rustworkx.graph_floyd_warshall_successor_and_distance rustworkx.graph_adjacency_matrix rustworkx.graph_all_simple_paths rustworkx.graph_all_pairs_all_simple_paths diff --git a/releasenotes/notes/add-fw-successor-80c09cb300c137f0.yaml b/releasenotes/notes/add-fw-successor-80c09cb300c137f0.yaml new file mode 100644 index 000000000..f7395b42d --- /dev/null +++ b/releasenotes/notes/add-fw-successor-80c09cb300c137f0.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Added a new algorithm function, + :func:`rustworkx.floyd_warshall_successor_and_distance`, that calculates + the shortest path distance and the successor nodes for all node pairs in + :class:`~rustworkx.PyGraph` and :class:`~rustworkx.PyDiGraph` graphs. \ No newline at end of file diff --git a/rustworkx/__init__.py b/rustworkx/__init__.py index 90c83ccce..ae158c6e3 100644 --- a/rustworkx/__init__.py +++ b/rustworkx/__init__.py @@ -2102,3 +2102,72 @@ def is_bipartite(graph): is_bipartite.register(PyDiGraph, digraph_is_bipartite) is_bipartite.register(PyGraph, graph_is_bipartite) + + +@functools.singledispatch +def floyd_warshall_successor_and_distance( + graph, + weight_fn=None, + default_weight=1.0, + parallel_threshold=300, +): + """ + Find all-pairs shortest path lengths using Floyd's algorithm. + + Floyd's algorithm is used for finding shortest paths in dense graphs + or graphs with negative weights (where Dijkstra's algorithm fails). + + This function is multithreaded and will launch a pool with threads equal + to the number of CPUs by default if the number of nodes in the graph is + above the value of ``parallel_threshold`` (it defaults to 300). + You can tune the number of threads with the ``RAYON_NUM_THREADS`` + environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would + limit the thread pool to 4 threads if parallelization was enabled. + + :param PyDiGraph graph: The directed graph to run Floyd's algorithm on + :param weight_fn: A callable object (function, lambda, etc) which + will be passed the edge object and expected to return a ``float``. This + tells rustworkx/rust how to extract a numerical weight as a ``float`` + for edge object. Some simple examples are:: + + floyd_warshall_successor_and_distance(graph, weight_fn=lambda _: 1) + + to return a weight of 1 for all edges. Also: + + floyd_warshall_successor_and_distance(graph, weight_fn=float) + + to cast the edge object as a float as the weight. + :param as_undirected: If set to true each directed edge will be treated as + bidirectional/undirected. + :param int parallel_threshold: The number of nodes to execute + the algorithm in parallel at. It defaults to 300, but this can + be tuned + + :returns: A tuple of two matrices. + First one is a matrix of shortest path distances between nodes. If there is no + path between two nodes then the corresponding matrix entry will be + ``np.inf``. + Second one is a matrix of **next** nodes for given source and target. If there is no + path between two nodes then the corresponding matrix entry will be the same as + a target node. To reconstruct the shortest path among nodes:: + + def reconstruct_path(source, target, successors): + path = [] + if source == target: + return path + curr = source + while curr != target: + path.append(curr) + curr = successors[curr, target] + path.append(target) + return path + + :rtype: (numpy.ndarray, numpy.ndarray) + """ + raise TypeError("Invalid Input Type %s for graph" % type(graph)) + + +floyd_warshall_successor_and_distance.register( + PyDiGraph, digraph_floyd_warshall_successor_and_distance +) +floyd_warshall_successor_and_distance.register(PyGraph, graph_floyd_warshall_successor_and_distance) diff --git a/rustworkx/shortest_path.pyi b/rustworkx/shortest_path.pyi index 2e0a2e4c9..c84b90b68 100644 --- a/rustworkx/shortest_path.pyi +++ b/rustworkx/shortest_path.pyi @@ -232,7 +232,7 @@ def graph_floyd_warshall_numpy( default_weight: float | None = ..., parallel_threshold: int | None = ..., ) -> np.ndarray: ... -def digraph_floyd_warshall_successor_and_distance_numpy( +def digraph_floyd_warshall_successor_and_distance( graph: PyDiGraph[_S, _T], /, weight_fn: Callable[[_T], float] | None = ..., @@ -240,7 +240,7 @@ def digraph_floyd_warshall_successor_and_distance_numpy( default_weight: float | None = ..., parallel_threshold: int | None = ..., ) -> tuple[np.ndarray, np.ndarray]: ... -def graph_floyd_warshall_successor_and_distance_numpy( +def graph_floyd_warshall_successor_and_distance( graph: PyGraph[_S, _T], /, weight_fn: Callable[[_T], float] | None = ..., diff --git a/src/lib.rs b/src/lib.rs index 92a570fc8..13d531317 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -390,10 +390,10 @@ fn rustworkx(py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(graph_floyd_warshall_numpy))?; m.add_wrapped(wrap_pyfunction!(digraph_floyd_warshall_numpy))?; m.add_wrapped(wrap_pyfunction!( - graph_floyd_warshall_successor_and_distance_numpy + graph_floyd_warshall_successor_and_distance ))?; m.add_wrapped(wrap_pyfunction!( - digraph_floyd_warshall_successor_and_distance_numpy + digraph_floyd_warshall_successor_and_distance ))?; m.add_wrapped(wrap_pyfunction!(collect_runs))?; m.add_wrapped(wrap_pyfunction!(collect_bicolor_runs))?; diff --git a/src/shortest_path/mod.rs b/src/shortest_path/mod.rs index 226d42084..959b1cbdd 100644 --- a/src/shortest_path/mod.rs +++ b/src/shortest_path/mod.rs @@ -1104,7 +1104,7 @@ pub fn graph_floyd_warshall_numpy( signature=(graph, weight_fn=None, default_weight=1.0, parallel_threshold=300), text_signature = "(graph, /, weight_fn=None, default_weight=1.0, parallel_threshold=300)" )] -pub fn graph_floyd_warshall_successor_and_distance_numpy( +pub fn graph_floyd_warshall_successor_and_distance( py: Python, graph: &graph::PyGraph, weight_fn: Option, @@ -1242,7 +1242,7 @@ pub fn digraph_floyd_warshall_numpy( signature=(graph, weight_fn=None, as_undirected=false, default_weight=1.0, parallel_threshold=300), text_signature = "(graph, /, weight_fn=None, as_undirected=False, default_weight=1.0, parallel_threshold=300)" )] -pub fn digraph_floyd_warshall_successor_and_distance_numpy( +pub fn digraph_floyd_warshall_successor_and_distance( py: Python, graph: &digraph::PyDiGraph, weight_fn: Option, diff --git a/tests/rustworkx_tests/digraph/test_floyd_warshall.py b/tests/rustworkx_tests/digraph/test_floyd_warshall.py index b9515e8a5..a081909c9 100644 --- a/tests/rustworkx_tests/digraph/test_floyd_warshall.py +++ b/tests/rustworkx_tests/digraph/test_floyd_warshall.py @@ -295,7 +295,7 @@ def test_floyd_warshall_successors_numpy(self): graph.add_edges_from_no_data( [(1, 2), (1, 7), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (0, 8)] ) - dist, succ = rustworkx.digraph_floyd_warshall_successor_and_distance_numpy( + dist, succ = rustworkx.floyd_warshall_successor_and_distance( graph, default_weight=2, parallel_threshold=self.parallel_threshold ) self.assertEqual(succ[1, 1], 1) diff --git a/tests/rustworkx_tests/graph/test_floyd_warshall.py b/tests/rustworkx_tests/graph/test_floyd_warshall.py index 466509e34..3fdc656a5 100644 --- a/tests/rustworkx_tests/graph/test_floyd_warshall.py +++ b/tests/rustworkx_tests/graph/test_floyd_warshall.py @@ -202,7 +202,7 @@ def test_floyd_warshall_successors_numpy(self): graph.add_edges_from_no_data( [(1, 2), (1, 7), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (0, 8)] ) - dist, succ = rustworkx.graph_floyd_warshall_successor_and_distance_numpy( + dist, succ = rustworkx.floyd_warshall_successor_and_distance( graph, default_weight=2, parallel_threshold=self.parallel_threshold ) self.assertEqual(succ[1, 1], 1)