From c8a7506439944b1570da2237742550a9d819b98f Mon Sep 17 00:00:00 2001 From: tanishy7777 <23110328@iitgn.ac.in> Date: Thu, 13 Mar 2025 22:12:44 +0530 Subject: [PATCH 1/5] Implemented FordFulkerson Algorithm with tests --- pydatastructs/graphs/algorithms.py | 62 +++++++++++++++++++ pydatastructs/graphs/tests/test_algorithms.py | 1 + 2 files changed, 63 insertions(+) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 9de50e7c..70ebcce7 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -1143,6 +1143,68 @@ def _breadth_first_search_max_flow(graph: Graph, source_node, sink_node, flow_pa return (0, parent) +def _copy_graph_with_residual_edges(graph: Graph) -> Graph: + vertices = [graph.__getattribute__(v) for v in graph.vertices] + new_graph = type(graph)(*vertices) + + for key, edge in graph.edge_weights.items(): + new_graph.add_edge(edge.source.name, edge.target.name, edge.value) + + + for key, edge in list(new_graph.edge_weights.items()): + src = edge.source.name + tgt = edge.target.name + if new_graph.get_edge(tgt, src) is None: + new_graph.add_edge(tgt, src, 0) + + return new_graph + + +def _dfs_max_flow(graph: Graph, node, sink, flow_passed, visited, flow): + if node == sink: + return flow + + visited[node] = True + + for next_node in graph.neighbors(node): + capacity = graph.get_edge(node, next_node.name).value + fp = flow_passed.get((node, next_node.name), 0) + residual_capacity = capacity - fp + + if residual_capacity > 0 and next_node.name not in visited: + bottleneck_flow = _dfs_max_flow( + graph, next_node.name, sink, flow_passed, visited, min(flow, residual_capacity) + ) + if bottleneck_flow > 0: + flow_passed[(node, next_node.name)] = fp + bottleneck_flow + flow_passed[(next_node.name, node)] = flow_passed.get((next_node.name, node), 0) - bottleneck_flow + return bottleneck_flow + + return 0 + +def _max_flow_ford_fulkerson_(graph: Graph, source, sink): + graph_copy = _copy_graph_with_residual_edges(graph) + + m_flow = 0 + flow_passed = {} + + # Add residual edges to the graph. + print(graph.neighbors('a')) + + while True: + # Use a dictionary for visited nodes. Reset for each new DFS call. + visited = {} + new_flow = _dfs_max_flow(graph_copy, source, sink, flow_passed, visited, float('inf')) + + # If no augmenting path is found, exit the loop. + if new_flow == 0: + break + + m_flow += new_flow + + return m_flow + + def _max_flow_edmonds_karp_(graph: Graph, source, sink): m_flow = 0 flow_passed = {} diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index f1586f51..6634824d 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -448,3 +448,4 @@ def _test_max_flow(ds, algorithm): _test_max_flow("Matrix", "edmonds_karp") _test_max_flow("List", "dinic") _test_max_flow("Matrix", "dinic") + _test_max_flow("List", "ford_fulkerson") From 4b771c61f0c344e88980d8374d458851c6912ef4 Mon Sep 17 00:00:00 2001 From: tanishy7777 <23110328@iitgn.ac.in> Date: Thu, 13 Mar 2025 22:14:06 +0530 Subject: [PATCH 2/5] Code Cleanup --- pydatastructs/graphs/algorithms.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 70ebcce7..fe9527bf 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -1187,16 +1187,11 @@ def _max_flow_ford_fulkerson_(graph: Graph, source, sink): m_flow = 0 flow_passed = {} - - # Add residual edges to the graph. - print(graph.neighbors('a')) while True: - # Use a dictionary for visited nodes. Reset for each new DFS call. visited = {} new_flow = _dfs_max_flow(graph_copy, source, sink, flow_passed, visited, float('inf')) - # If no augmenting path is found, exit the loop. if new_flow == 0: break From eda1fbd48a94dd1623df2788b24a4953f6b05777 Mon Sep 17 00:00:00 2001 From: tanishy7777 <23110328@iitgn.ac.in> Date: Thu, 13 Mar 2025 22:17:30 +0530 Subject: [PATCH 3/5] Adds test for AdjMatrix --- pydatastructs/graphs/tests/test_algorithms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index 6634824d..e133246f 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -449,3 +449,4 @@ def _test_max_flow(ds, algorithm): _test_max_flow("List", "dinic") _test_max_flow("Matrix", "dinic") _test_max_flow("List", "ford_fulkerson") + _test_max_flow("Matrix", "ford_fulkerson") From 786885f9820608fa5e4c7f20f4fad670e2399936 Mon Sep 17 00:00:00 2001 From: tanishy7777 <23110328@iitgn.ac.in> Date: Thu, 13 Mar 2025 22:28:19 +0530 Subject: [PATCH 4/5] Remove Whitespaces --- pydatastructs/graphs/algorithms.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index fe9527bf..721bc34e 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -1146,31 +1146,24 @@ def _breadth_first_search_max_flow(graph: Graph, source_node, sink_node, flow_pa def _copy_graph_with_residual_edges(graph: Graph) -> Graph: vertices = [graph.__getattribute__(v) for v in graph.vertices] new_graph = type(graph)(*vertices) - for key, edge in graph.edge_weights.items(): new_graph.add_edge(edge.source.name, edge.target.name, edge.value) - - for key, edge in list(new_graph.edge_weights.items()): src = edge.source.name tgt = edge.target.name if new_graph.get_edge(tgt, src) is None: new_graph.add_edge(tgt, src, 0) - return new_graph def _dfs_max_flow(graph: Graph, node, sink, flow_passed, visited, flow): if node == sink: return flow - visited[node] = True - for next_node in graph.neighbors(node): capacity = graph.get_edge(node, next_node.name).value fp = flow_passed.get((node, next_node.name), 0) residual_capacity = capacity - fp - if residual_capacity > 0 and next_node.name not in visited: bottleneck_flow = _dfs_max_flow( graph, next_node.name, sink, flow_passed, visited, min(flow, residual_capacity) @@ -1179,24 +1172,18 @@ def _dfs_max_flow(graph: Graph, node, sink, flow_passed, visited, flow): flow_passed[(node, next_node.name)] = fp + bottleneck_flow flow_passed[(next_node.name, node)] = flow_passed.get((next_node.name, node), 0) - bottleneck_flow return bottleneck_flow - return 0 def _max_flow_ford_fulkerson_(graph: Graph, source, sink): graph_copy = _copy_graph_with_residual_edges(graph) - m_flow = 0 flow_passed = {} - while True: visited = {} new_flow = _dfs_max_flow(graph_copy, source, sink, flow_passed, visited, float('inf')) - if new_flow == 0: break - m_flow += new_flow - return m_flow From cb1496bae09546661d5d5c707a898336ee61ef11 Mon Sep 17 00:00:00 2001 From: tanishy7777 Date: Tue, 1 Apr 2025 15:16:22 +0530 Subject: [PATCH 5/5] Adds docstring for max_flow --- .../pydatastructs/graphs/algorithms.rst | 4 +- pydatastructs/graphs/algorithms.py | 55 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/docs/source/pydatastructs/graphs/algorithms.rst b/docs/source/pydatastructs/graphs/algorithms.rst index c508421a..160752f8 100644 --- a/docs/source/pydatastructs/graphs/algorithms.rst +++ b/docs/source/pydatastructs/graphs/algorithms.rst @@ -21,4 +21,6 @@ Algorithms .. autofunction:: pydatastructs.topological_sort_parallel -.. autofunction:: pydatastructs.find_bridges \ No newline at end of file +.. autofunction:: pydatastructs.find_bridges + +.. autofunction:: pydatastructs.max_flow \ No newline at end of file diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 0d9527b6..00637077 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -1299,6 +1299,61 @@ def _max_flow_dinic_(graph: Graph, source, sink): def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs): + """ + Computes the maximum flow in a flow network using the specified algorithm. + + Parameters + ========== + + graph: Graph + The flow network represented as a graph. + source: str + The source node in the flow network. + sink: str + The sink node in the flow network. + algorithm: str, optional + The algorithm to be used for computing maximum flow. + Currently, the following is supported: + + 'edmonds_karp' -> Edmonds-Karp algorithm as described in [1]. + 'ford_fulkerson' -> Ford-Fulkerson algorithm as described in [2]. + 'dinic' -> Dinic's algorithm as described in [3]. + + Default is 'edmonds_karp'. + **kwargs: + Additional keyword arguments specific to the chosen algorithm. + + Returns + ======= + + float + The maximum flow value from source to sink in the flow network. + + Examples + ======== + + >>> from pydatastructs import Graph, max_flow, AdjacencyListGraphNode + >>> a = AdjacencyListGraphNode("a") + >>> b = AdjacencyListGraphNode("b") + >>> c = AdjacencyListGraphNode("c") + >>> d = AdjacencyListGraphNode("d") + >>> e = AdjacencyListGraphNode("e") + >>> G = Graph(a, b, c, d, e) + >>> G.add_edge('a', 'b', 3) + >>> G.add_edge('a', 'c', 4) + >>> G.add_edge('b', 'c', 2) + >>> G.add_edge('b', 'd', 3) + >>> G.add_edge('c', 'd', 1) + >>> G.add_edge('d', 'e', 6) + 4 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm + .. [2] https://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm + .. [3] https://en.wikipedia.org/wiki/Dinic%27s_algorithm + """ raise_if_backend_is_not_python( max_flow, kwargs.get('backend', Backend.PYTHON))