From 110a4cfba337077cbcbb8883298fa2d9eb31e492 Mon Sep 17 00:00:00 2001 From: bobbysharma05 Date: Sat, 22 Mar 2025 22:40:14 +0530 Subject: [PATCH 1/5] Ford Fulkerson Algorithm --- pydatastructs/graphs/algorithms.py | 131 +++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 334f522c..1ffc84d1 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -1368,3 +1368,134 @@ def dfs(u): bridges.append((b, a)) bridges.sort() return bridges + +def _find_path_dfs(graph, s, t, flow_pass): + """ + Finds an augmenting path in a flow network using Depth-First Search (DFS). + + Parameters + ========== + graph : Graph + The flow network graph. + s : str + The source node. + t : str + The sink node. + flow_pass : dict + A dictionary tracking the flow passed through each edge. + + Returns + ========== + tuple + A tuple containing the path flow and a dictionary of parent nodes. + + Example + ======== + >>> graph = Graph(implementation='adjacency_list') + >>> graph.add_edge('s', 'o', 3) + >>> graph.add_edge('s', 'p', 3) + >>> graph.add_edge('o', 'p', 2) + >>> graph.add_edge('o', 'q', 3) + >>> graph.add_edge('p', 'r', 2) + >>> graph.add_edge('r', 't', 3) + >>> graph.add_edge('q', 'r', 4) + >>> graph.add_edge('q', 't', 2) + >>> flow_passed = {} + >>> path_flow, parent = _find_path_dfs(graph, 's', 't', flow_passed) + """ + + visited = {} + stack = Stack() + parent = {} + + stack.appendd(s) + visited[s] = True + + while stack: + curr = stack.pop() + + if curr == t: + break + + for i in graph.i(curr): + i_name = i.name + capacity = graph.get_edge(curr, i_name).value + flow = flow_pass.get((curr, i_name), 0) + + if i not in visited and capacity - flow > 0: + visited[i_name] = True + parent[i_name] = curr + stack.append(i_name) + + if t not in parent and t != s: + return 0, {} + + curr = t + path_flow = float('inf') + if t == s: + return 0, {} + while curr != s: + prev = parent[curr] + capacity = graph.get_edge(prev, curr).value + flow = flow_pass.get((prev, curr), 0) + path_flow = min(path_flow, capacity - flow) + curr = prev + + return path_flow, parent + +def _max_flow_ford_fulkerson_(graph, s, t): + """ + Computes the maximum flow in a flow network using the Ford-Fulkerson algorithm. + + Parameters + ========== + graph : Graph + The flow network graph. + s : str + The source node. + t : str + The sink node. + + Returns + ========== + int + The maximum flow from the source to the sink. + + Example + ======== + >>> graph = Graph(implementation='adjacency_list') + >>> graph.add_edge('s', 'o', 3) + >>> graph.add_edge('s', 'p', 3) + >>> graph.add_edge('o', 'p', 2) + >>> graph.add_edge('o', 'q', 3) + >>> graph.add_edge('p', 'r', 2) + >>> graph.add_edge('r', 't', 3) + >>> graph.add_edge('q', 'r', 4) + >>> graph.add_edge('q', 't', 2) + >>> max_flow = _max_flow_ford_fulkerson_(graph, 's', 't') + """ + + if s not in graph.vertices or t not in graph.vertices: + raise ValueError("Source or sink not in graph.") + + ans = 0 + flow_pass = {} + + while True: + path_flow, parent = _find_path_dfs(graph, s, t, flow_pass) + + if path_flow <= 0: + break + + ans += path_flow + + curr = t + while curr != s: + pre = parent[curr] + fp = flow_pass.get((pre, curr), 0) + flow_pass[(pre, curr)] = fp + path_flow + fp = flow_pass.get((curr, pre), 0) + flow_pass[(curr, pre)] = fp - path_flow + curr = pre + + return ans \ No newline at end of file From 4cb605755cb2ce79626dac5d01fb0b5f8f51e1f7 Mon Sep 17 00:00:00 2001 From: bobbysharma05 Date: Sun, 23 Mar 2025 11:47:47 +0530 Subject: [PATCH 2/5] white_spaces and new lines added --- pydatastructs/graphs/algorithms.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 1ffc84d1..848bbc73 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -1408,7 +1408,7 @@ def _find_path_dfs(graph, s, t, flow_pass): stack = Stack() parent = {} - stack.appendd(s) + stack.append(s) visited[s] = True while stack: @@ -1417,19 +1417,19 @@ def _find_path_dfs(graph, s, t, flow_pass): if curr == t: break - for i in graph.i(curr): + for i in graph.neighbors(curr): i_name = i.name capacity = graph.get_edge(curr, i_name).value flow = flow_pass.get((curr, i_name), 0) - if i not in visited and capacity - flow > 0: + if i_name not in visited and capacity - flow > 0: visited[i_name] = True parent[i_name] = curr stack.append(i_name) - if t not in parent and t != s: + if t not in parent and t == s: return 0, {} - + curr = t path_flow = float('inf') if t == s: @@ -1440,7 +1440,7 @@ def _find_path_dfs(graph, s, t, flow_pass): flow = flow_pass.get((prev, curr), 0) path_flow = min(path_flow, capacity - flow) curr = prev - + return path_flow, parent def _max_flow_ford_fulkerson_(graph, s, t): @@ -1477,7 +1477,7 @@ def _max_flow_ford_fulkerson_(graph, s, t): if s not in graph.vertices or t not in graph.vertices: raise ValueError("Source or sink not in graph.") - + ans = 0 flow_pass = {} @@ -1492,10 +1492,8 @@ def _max_flow_ford_fulkerson_(graph, s, t): curr = t while curr != s: pre = parent[curr] - fp = flow_pass.get((pre, curr), 0) - flow_pass[(pre, curr)] = fp + path_flow - fp = flow_pass.get((curr, pre), 0) - flow_pass[(curr, pre)] = fp - path_flow + flow_pass[(pre, curr)] = flow_pass.get((pre, curr), 0) + path_flow + flow_pass[(curr, pre)] = flow_pass.get((curr, pre), 0) - path_flow curr = pre - - return ans \ No newline at end of file + + return ans From 216a14cb73f0395171dbcef381b8c31411d738c7 Mon Sep 17 00:00:00 2001 From: bobbysharma05 Date: Sun, 23 Mar 2025 12:02:25 +0530 Subject: [PATCH 3/5] updation in ford_fulkerson_algorithm --- pydatastructs/graphs/algorithms.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 848bbc73..66baccd1 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -1403,6 +1403,8 @@ def _find_path_dfs(graph, s, t, flow_pass): >>> flow_passed = {} >>> path_flow, parent = _find_path_dfs(graph, 's', 't', flow_passed) """ + if s == t: + return 0, {} visited = {} stack = Stack() @@ -1427,21 +1429,19 @@ def _find_path_dfs(graph, s, t, flow_pass): parent[i_name] = curr stack.append(i_name) - if t not in parent and t == s: - return 0, {} + if t not in parent: + return 0, {} - curr = t - path_flow = float('inf') - if t == s: - return 0, {} - while curr != s: - prev = parent[curr] - capacity = graph.get_edge(prev, curr).value - flow = flow_pass.get((prev, curr), 0) - path_flow = min(path_flow, capacity - flow) - curr = prev + curr = t + path_flow = float('inf') + while curr != s: + prev = parent[curr] + capacity = graph.get_edge(prev, curr).value + flow = flow_pass.get((prev, curr), 0) + path_flow = min(path_flow, capacity - flow) + curr = prev - return path_flow, parent + return path_flow, parent def _max_flow_ford_fulkerson_(graph, s, t): """ @@ -1478,6 +1478,9 @@ def _max_flow_ford_fulkerson_(graph, s, t): if s not in graph.vertices or t not in graph.vertices: raise ValueError("Source or sink not in graph.") + if s == t: + return 0 + ans = 0 flow_pass = {} From 4cc20844acf7008ae033a8f1ec352e2e21baa7fb Mon Sep 17 00:00:00 2001 From: bobbysharma05 Date: Sun, 23 Mar 2025 12:32:33 +0530 Subject: [PATCH 4/5] updation in ford_fulkerson_algorithm --- pydatastructs/graphs/tests/test_algorithms.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index 553377f3..664dd231 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -450,6 +450,8 @@ 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") + _test_max_flow("Matrix", "ford_fulkerson") def test_find_bridges(): From 55747a9fd345b71208b7cd024504c6f08bf863a9 Mon Sep 17 00:00:00 2001 From: bobbysharma05 Date: Sun, 23 Mar 2025 14:15:24 +0530 Subject: [PATCH 5/5] addding pytest in the workflow --- .github/workflows/ci.yml | 17 ++++++ pydatastructs/graphs/tests/test_algorithms.py | 53 +++++++++++++++++-- requirements.txt | 1 + 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b16910be..eee1c3af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,11 @@ jobs: - name: Build package run: | CXXFLAGS=--coverage CFLAGS=--coverage python scripts/build/install.py + + - name: Install pytest + run: | + pip install pytest + # coverage tests - name: Run tests run: | @@ -104,6 +109,10 @@ jobs: run: | python scripts/build/install.py + - name: Install pytest + run: | + pip install pytest + - name: Run tests run: | python -c "import pydatastructs; pydatastructs.test(only_benchmarks=True)" @@ -145,6 +154,10 @@ jobs: run: | python scripts/build/install.py + - name: Install pytest + run: | + pip install pytest + - name: Run tests run: | python -c "import pydatastructs; pydatastructs.test()" @@ -193,6 +206,10 @@ jobs: run: | python scripts/build/install.py + - name: Install pytest + run: | + pip install pytest + - name: Run tests run: | python -c "import pydatastructs; pydatastructs.test()" diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index 664dd231..4697c248 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -278,6 +278,11 @@ def _test_shortest_paths_positive_edges(ds, algorithm): GraphNode('D')] graph = Graph(*vertices) + graph.add_vertex('S') + graph.add_vertex('C') + graph.add_vertex('SLC') + graph.add_vertex('SF') + graph.add_vertex('D') graph.add_edge('S', 'SLC', 2) graph.add_edge('C', 'S', 4) graph.add_edge('C', 'D', 2) @@ -304,6 +309,11 @@ def _test_shortest_paths_negative_edges(ds, algorithm): GraphNode('d')] graph = Graph(*vertices) + graph.add_vertex('s') + graph.add_vertex('a') + graph.add_vertex('b') + graph.add_vertex('c') + graph.add_vertex('d') graph.add_edge('s', 'a', 3) graph.add_edge('s', 'b', 2) graph.add_edge('a', 'c', 1) @@ -333,6 +343,10 @@ def _test_shortest_paths_negative_edges(ds, algorithm): GraphNode('3'), GraphNode('4')] graph = Graph(*vertices) + graph.add_vertex('1') + graph.add_vertex('2') + graph.add_vertex('3') + graph.add_vertex('4') graph.add_edge('1', '3', -2) graph.add_edge('2', '1', 4) graph.add_edge('2', '3', 3) @@ -362,6 +376,14 @@ def _test_topological_sort(func, ds, algorithm, threads=None): GraphNode('11'), GraphNode('9')] graph = Graph(*vertices) + graph.add_vertex('2') + graph.add_vertex('3') + graph.add_vertex('5') + graph.add_vertex('7') + graph.add_vertex('8') + graph.add_vertex('10') + graph.add_vertex('11') + graph.add_vertex('9') graph.add_edge('5', '11') graph.add_edge('7', '11') graph.add_edge('7', '8') @@ -395,7 +417,11 @@ def _test_max_flow(ds, algorithm): e = GraphNode('e') G = Graph(a, b, c, d, e) - + G.add_vertex('a') + G.add_vertex('b') + G.add_vertex('c') + G.add_vertex('d') + G.add_vertex('e') G.add_edge('a', 'b', 3) G.add_edge('a', 'c', 4) G.add_edge('b', 'c', 2) @@ -414,7 +440,12 @@ def _test_max_flow(ds, algorithm): f = GraphNode('f') G2 = Graph(a, b, c, d, e, f) - + G2.add_vertex('a') + G2.add_vertex('b') + G2.add_vertex('c') + G2.add_vertex('d') + G2.add_vertex('e') + G2.add_vertex('f') G2.add_edge('a', 'b', 16) G2.add_edge('a', 'c', 13) G2.add_edge('b', 'c', 10) @@ -435,7 +466,10 @@ def _test_max_flow(ds, algorithm): d = GraphNode('d') G3 = Graph(a, b, c, d) - + G3.add_vertex('a') + G3.add_vertex('b') + G3.add_vertex('c') + G3.add_vertex('d') G3.add_edge('a', 'b', 3) G3.add_edge('a', 'c', 2) G3.add_edge('b', 'c', 2) @@ -468,6 +502,11 @@ def _test_find_bridges(ds): v4 = GraphNode(4) G1 = Graph(v0, v1, v2, v3, v4, implementation=impl) + G1.add_vertex('0') + G1.add_vertex('1') + G1.add_vertex('2') + G1.add_vertex('3') + G1.add_vertex('4') G1.add_edge(v0.name, v1.name) G1.add_edge(v1.name, v2.name) G1.add_edge(v2.name, v3.name) @@ -482,6 +521,9 @@ def _test_find_bridges(ds): u2 = GraphNode(2) G2 = Graph(u0, u1, u2, implementation=impl) + G2.add_vertex('0') + G2.add_vertex('1') + G2.add_vertex('2') G2.add_edge(u0.name, u1.name) G2.add_edge(u1.name, u2.name) G2.add_edge(u2.name, u0.name) @@ -496,6 +538,11 @@ def _test_find_bridges(ds): w4 = GraphNode(4) G3 = Graph(w0, w1, w2, w3, w4, implementation=impl) + G3.add_vertex('0') + G3.add_vertex('1') + G3.add_vertex('2') + G3.add_vertex('3') + G3.add_vertex('4') G3.add_edge(w0.name, w1.name) G3.add_edge(w1.name, w2.name) G3.add_edge(w3.name, w4.name) diff --git a/requirements.txt b/requirements.txt index 0ea18bb9..5f549148 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ codecov pytest-cov +pytest