Skip to content

Implementation of Fork Fulkerson Algorithm #668

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down Expand Up @@ -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)"
Expand Down Expand Up @@ -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()"
Expand Down Expand Up @@ -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()"
Expand Down
132 changes: 132 additions & 0 deletions pydatastructs/graphs/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
55 changes: 52 additions & 3 deletions pydatastructs/graphs/tests/test_algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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():
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
codecov
pytest-cov
pytest
Loading