diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b16910bea..eee1c3af4 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/algorithms.py b/pydatastructs/graphs/algorithms.py index 334f522c5..66baccd16 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -1368,3 +1368,135 @@ 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) + """ + if s == t: + return 0, {} + + visited = {} + stack = Stack() + parent = {} + + stack.append(s) + visited[s] = True + + while stack: + curr = stack.pop() + + if curr == t: + break + + 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_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: + return 0, {} + + 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 + +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.") + + if s == t: + return 0 + + 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] + 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 diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index 553377f34..4697c248a 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) @@ -450,6 +484,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(): @@ -466,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) @@ -480,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) @@ -494,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 0ea18bb95..5f549148e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ codecov pytest-cov +pytest