diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index ea3322c02..aefbf4ca0 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -3,6 +3,7 @@ data structure. """ from collections import deque +from typing import Tuple, Dict, Set from concurrent.futures import ThreadPoolExecutor from pydatastructs.utils.misc_util import ( _comp, raise_if_backend_is_not_python, Backend) @@ -826,6 +827,7 @@ def all_pair_shortest_paths(graph: Graph, algorithm: str, are implemented, 'floyd_warshall' -> Floyd Warshall algorithm as given in [1]. + 'floyd_warshall_parallel' -> Parallel Floyd Warshall algorithm as given in [2]. backend: pydatastructs.Backend The backend to be used. Optional, by default, the best available @@ -853,11 +855,17 @@ def all_pair_shortest_paths(graph: Graph, algorithm: str, 21 >>> dist['V3']['V1'] 5 + >>> dist, _ = all_pair_shortest_paths(G, 'floyd_warshall_parallel') + >>> dist['V1']['V3'] + 21 + >>> dist['V3']['V1'] + 5 References ========== .. [1] https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm + .. [2] https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm#Parallel_execution """ raise_if_backend_is_not_python( all_pair_shortest_paths, kwargs.get('backend', Backend.PYTHON)) @@ -900,6 +908,44 @@ def _floyd_warshall_adjacency_list(graph: Graph): _floyd_warshall_adjacency_matrix = _floyd_warshall_adjacency_list +def _floyd_warshall_parallel_adjacency_list(graph: Graph) -> Tuple[Dict, Dict]: + """ + Parallel Floyd-Warshall using ThreadPoolExecutor. + Ensures correct updates of both distance and next_vertex matrices. + + :param graph: Graph object with vertices and edge_weights. + :return: Tuple (dist, next_vertex) matrices. + """ + dist, next_vertex = {}, {} + V, E = graph.vertices, graph.edge_weights + + # Initialize distance and next vertex matrices + for v in V: + dist[v] = {u: float('inf') for u in V} + next_vertex[v] = {u: None for u in V} + dist[v][v] = 0 # Distance to itself is 0 + + # Populate initial distances from edges + for name, edge in E.items(): + dist[edge.source.name][edge.target.name] = edge.value + next_vertex[edge.source.name][edge.target.name] = edge.target.name + + def _update_row(i, k): + """Update a single row in parallel.""" + for j in V: + if dist[i][j] > dist[i][k] + dist[k][j]: + dist[i][j] = dist[i][k] + dist[k][j] + next_vertex[i][j] = next_vertex[i][k] if next_vertex[i][k] else k + + # Parallel Floyd-Warshall execution + for k in V: + with ThreadPoolExecutor() as executor: + executor.map(lambda i: _update_row(i, k), V) + + return dist, next_vertex + +_floyd_warshall_parallel_adjacency_matrix = _floyd_warshall_parallel_adjacency_list + def topological_sort(graph: Graph, algorithm: str, **kwargs) -> list: """ diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index fde3571da..02f03afac 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -1,7 +1,7 @@ from pydatastructs import (breadth_first_search, Graph, breadth_first_search_parallel, minimum_spanning_tree, minimum_spanning_tree_parallel, strongly_connected_components, -depth_first_search, shortest_paths, topological_sort, +depth_first_search, shortest_paths, all_pair_shortest_paths, topological_sort, topological_sort_parallel, max_flow) from pydatastructs.utils.raises_util import raises @@ -336,7 +336,7 @@ def _test_shortest_paths_negative_edges(ds, algorithm): graph.add_edge('2', '3', 3) graph.add_edge('3', '4', 2) graph.add_edge('4', '2', -1) - dist, next_v = shortest_paths(graph, algorithm, 's') + dist, next_v = all_pair_shortest_paths(graph, algorithm) assert dist == {'1': {'3': -2, '1': 0, '4': 0, '2': -1}, '2': {'1': 4, '3': 2, '2': 0, '4': 4}, '3': {'4': 2, '3': 0, '1': 5, '2': 1}, @@ -346,6 +346,9 @@ def _test_shortest_paths_negative_edges(ds, algorithm): '3': {'4': '3', '3': '3', '1': None, '2': None}, '4': {'2': '4', '4': '4', '1': None, '3': None}} + _test_shortest_paths_negative_edges("List", 'floyd_warshall') + _test_shortest_paths_negative_edges("Matrix", 'floyd_warshall') + def test_topological_sort(): def _test_topological_sort(func, ds, algorithm, threads=None):