Skip to content

Commit 866c236

Browse files
committed
dinic + capacity scaling + test environment
1 parent 7dbe91c commit 866c236

File tree

6 files changed

+261
-75
lines changed

6 files changed

+261
-75
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Visualization of max flow algorithms

graph.py

+14-5
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,22 @@ def __init__(self, n: int):
4242
self.edges = [[] for _ in range(n)]
4343
self.n = n
4444

45-
def add_edge(self, source: int, target: int, capacity: int):
46-
edge = Edge(source, target, capacity)
47-
rev_edge = Edge(target, source, reverse=True)
45+
def add_edge(self, start: int, end: int, capacity: int):
46+
edge = Edge(start, end, capacity)
47+
rev_edge = Edge(end, start, reverse=True)
4848

4949
edge.reverse_edge = rev_edge
5050
rev_edge.reverse_edge = edge
5151

52-
self.edges[source].append(edge)
53-
self.edges[target].append(rev_edge)
52+
self.edges[start].append(edge)
53+
self.edges[end].append(rev_edge)
5454

5555
def get_edges_by_node(self, node: int):
5656
return self.edges[node]
5757

58+
def get_degree(self, node: int):
59+
return len(self.edges[node])
60+
5861
def get_edges(self):
5962
for i in range(self.n):
6063
for edge in self.get_edges_by_node(i):
@@ -71,3 +74,9 @@ def get_nodes(self):
7174

7275
def number_of_nodes(self):
7376
return self.n
77+
78+
def copy(self):
79+
new_graph = Graph(self.number_of_nodes())
80+
for edge in self.get_edges():
81+
new_graph.add_edge(edge.start, edge.end, edge.capacity)
82+
return new_graph

gui.py

+144-34
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,38 @@
1-
import random
21
import tkinter as tk
32
from tkinter import messagebox
43
from tkinter import ttk
54

5+
from tabulate import tabulate
6+
67
import max_flow
78
import random_graph
89
import utils
910

11+
ALGORITHMS_MAP = {"Ford-Fulkerson": max_flow.FordFulkerson,
12+
"Edmonds-Karp": max_flow.EdmondsKarp,
13+
"Capacity Scaling": max_flow.CapacityScaling,
14+
"Dinic": max_flow.Dinic,
15+
# TODO
16+
# "Goldberg-Tarjan": max_flow.GoldbergTarjan
17+
}
18+
ALGORITHMS = list(ALGORITHMS_MAP.keys())
19+
1020

1121
class Visualization(tk.Frame):
12-
ALGORITHMS_MAP = {"Ford-Fulkerson": max_flow.FordFulkerson,
13-
"Edmonds-Karp": max_flow.EdmondsKarp,
14-
"Dinic": max_flow.Dinic,
15-
"Goldberg-Tarjan": max_flow.GoldbergTarjan}
16-
ALGORITHMS = list(ALGORITHMS_MAP.keys())
1722
DEFAULT_NODES = 6
1823
DEFAULT_MAX_CAPACITY = 5
19-
NODE_RADIUS = 5
24+
NODE_RADIUS = 8
2025

2126
def __init__(self, parent):
2227
super().__init__(parent)
2328

2429
config_bar = tk.Frame(self)
2530

2631
self.algo_variable = tk.StringVar(config_bar)
27-
self.algo_variable.set(self.ALGORITHMS[0])
32+
self.algo_variable.set(ALGORITHMS[0])
2833

29-
self.opt_algorithm = tk.OptionMenu(config_bar, self.algo_variable, *self.ALGORITHMS)
34+
self.opt_algorithm = tk.OptionMenu(config_bar, self.algo_variable, *ALGORITHMS)
35+
self.opt_algorithm.config(width=20)
3036
self.opt_algorithm.grid(row=0, column=0, padx=10)
3137
self.max_flow_algo = None
3238

@@ -59,6 +65,9 @@ def __init__(self, parent):
5965
self.btn_stop = tk.Button(text="stop", master=config_bar, command=self.stop)
6066
self.btn_stop.grid(row=0, column=9, padx=10)
6167

68+
self.btn_help = tk.Button(text="help", master=config_bar, command=self.help)
69+
self.btn_help.grid(row=0, column=9, padx=11)
70+
6271
config_bar.pack(anchor=tk.N)
6372

6473
self.canvas = tk.Canvas(self, bg="white")
@@ -78,8 +87,8 @@ def render(self):
7887

7988
self.canvas.create_rectangle(0, 0, width, height, fill="white")
8089

81-
self.render_nodes()
8290
self.render_edges()
91+
self.render_nodes()
8392
self.render_text()
8493

8594
def render_nodes(self):
@@ -123,9 +132,45 @@ def render_edges(self):
123132

124133
for edge in self.graph.get_edges():
125134
if not edge.reverse:
135+
# TODO move arrow start and end to the outline of the node circles
126136
p1 = utils.absolute_position(self.graph.get_nodes()[edge.start], width, height)
127137
p2 = utils.absolute_position(self.graph.get_nodes()[edge.end], width, height)
128-
self.canvas.create_line(p1, p2, width=3, fill="black", arrow=tk.LAST, arrowshape=(10, 15, 5))
138+
self.canvas.create_line(p1, p2, width=3, fill="black", arrow=tk.LAST, arrowshape=(10, 10, 5))
139+
140+
def render_step(self, result):
141+
self.render()
142+
143+
width = self.canvas.winfo_width()
144+
height = self.canvas.winfo_height()
145+
146+
if self.algo_variable.get() == "Dinic":
147+
edges, level = result
148+
for node in self.graph.get_nodes():
149+
position = utils.absolute_position(node, width, height)
150+
self.canvas.create_text(*position,
151+
text=f"{level[node.node_id]}",
152+
fill="white",
153+
font=('Helvetica', '10', 'bold'))
154+
else:
155+
edges = result
156+
157+
for edge in edges:
158+
p1 = utils.absolute_position(self.graph.get_nodes()[edge.start], width, height)
159+
p2 = utils.absolute_position(self.graph.get_nodes()[edge.end], width, height)
160+
self.canvas.create_line(p1, p2, width=3, fill="red", arrow=tk.LAST, arrowshape=(10, 15, 5))
161+
self.render_text()
162+
163+
def algorithm_terminated(self):
164+
self.btn_stop["state"] = tk.DISABLED
165+
self.btn_step["state"] = tk.DISABLED
166+
self.btn_start["state"] = tk.DISABLED
167+
self.ent_time["state"] = tk.DISABLED
168+
self.render()
169+
if self._jop is not None:
170+
window.after_cancel(self._jop)
171+
self._jop = None
172+
flow_value = sum(e.flow for e in self.graph.get_edges_by_node(self.source))
173+
messagebox.showinfo("Info", f"algorithm terminated!\nmax-flow value: {flow_value}")
129174

130175
def reset(self):
131176
try:
@@ -136,41 +181,37 @@ def reset(self):
136181
self.render()
137182

138183
self.opt_algorithm["state"] = tk.NORMAL
184+
self.btn_step["state"] = tk.NORMAL
185+
self.btn_start["state"] = tk.NORMAL
186+
self.btn_stop["state"] = tk.NORMAL
187+
self.ent_time["state"] = tk.NORMAL
188+
139189
self.max_flow_algo = None
140190
except ValueError:
141191
messagebox.showerror("Error", "nodes and capacity must be an integer")
142192

143193
def step(self):
144194
if self.max_flow_algo is None:
145-
self.source, self.target = random.sample(range(self.graph.number_of_nodes()), 2) # TODO
146-
self.max_flow_algo = self.ALGORITHMS_MAP[self.algo_variable.get()](self.graph, self.source, self.target)
147-
148-
self.opt_algorithm["state"] = tk.DISABLED
149-
150-
if path := self.max_flow_algo.step():
151-
self.render()
152-
153-
for edge in path:
154-
width = self.canvas.winfo_width()
155-
height = self.canvas.winfo_height()
195+
self.source, self.target = utils.get_source_and_target(self.graph)
196+
self.max_flow_algo = ALGORITHMS_MAP[self.algo_variable.get()](self.graph, self.source, self.target)
197+
self.opt_algorithm["state"] = tk.DISABLED
156198

157-
p1 = utils.absolute_position(self.graph.get_nodes()[edge.start], width, height)
158-
p2 = utils.absolute_position(self.graph.get_nodes()[edge.end], width, height)
159-
self.canvas.create_line(p1, p2, width=3, fill="red", arrow=tk.LAST, arrowshape=(10, 15, 5))
160-
self.render_text()
199+
if result := self.max_flow_algo.step():
200+
self.render_step(result)
161201
else:
162-
messagebox.showinfo("Info", "algorithm terminated!")
202+
self.algorithm_terminated()
163203

164204
def start(self):
165205
try:
166206
intervall = int(self.ent_time.get())
167207

168208
self.opt_algorithm["state"] = tk.DISABLED
169209
self.btn_step["state"] = tk.DISABLED
210+
self.btn_start["state"] = tk.DISABLED
170211
self.ent_time["state"] = tk.DISABLED
171212

172-
self.step()
173213
self._jop = window.after(intervall, self.start)
214+
self.step()
174215
except ValueError:
175216
messagebox.showerror("Error", "intervall must be an integer")
176217

@@ -182,29 +223,98 @@ def stop(self):
182223
window.after_cancel(self._jop)
183224
self._jop = None
184225

226+
def help(self):
227+
messagebox.showinfo("Help", """
228+
Max-Flow Visualization
229+
230+
notation:
231+
n: number of nodes\tm: number of edges
232+
F: max-flow value\tC: max capacity
185233
186-
class TestEnvoirement(tk.Frame):
234+
implemented algorithms:
235+
Ford-Fulkerson: O(m F)
236+
Edmonds-Karp: O(n m^2)
237+
Capacity Scaling: O(n m logC)
238+
Dinic: O(m n^2)
239+
Goldberg-Tarjan: O()
240+
""")
241+
242+
243+
class TestEnviroment(tk.Frame):
187244

188245
def __init__(self, parent):
189246
super().__init__(parent)
190247

191248
self.btn_start = tk.Button(text="start", master=self, command=self.start_test)
192249
self.btn_start.pack(fill="x")
193250

251+
lbl_info = tk.Label(text="Please enter one comma separated triple per line: instances, nodes, capacity",
252+
master=self)
253+
lbl_info.pack(fill="x")
254+
194255
self.txt_triples = tk.Text(master=self)
195256
self.txt_triples.insert(tk.END, "10, 5, 5")
196257
self.txt_triples.pack(fill="x")
197258

198-
self.lbl_output = tk.Label(text="Hello World", master=self)
199-
self.lbl_output.pack(fill="both")
259+
scroll_output = tk.Scrollbar(orient='vertical', master=self)
260+
scroll_output.pack(side=tk.RIGHT, fill='both')
261+
262+
self.txt_output = tk.Text(yscrollcommand=scroll_output.set, master=self)
263+
scroll_output.config(command=self.txt_output.yview)
264+
self.txt_output.pack(fill="both")
200265

201266
def start_test(self):
267+
self.txt_output.delete(1.0, "end-1c")
268+
202269
triples = self.txt_triples.get(1.0, "end-1c")
203270
for line in triples.split("\n"):
204-
instances, nodes, capacity = map(int, line.split(","))
271+
line = line.strip()
272+
if not line:
273+
continue
274+
try:
275+
instances, nodes, capacity = map(int, line.split(","))
276+
except ValueError:
277+
messagebox.showerror("Error", "invalid input")
278+
return
279+
205280
for _ in range(instances):
206281
graph = random_graph.generate(nodes, capacity)
207-
# TODO Flusserhaltung, Kapazitätsbedingung, Flusswerte unterschiedlich
282+
source, target = utils.get_source_and_target(graph)
283+
284+
result = [["Algorithm", "flow preservation", "capacity bound", "max flow"]]
285+
flow_values = []
286+
287+
for name, algo_class in ALGORITHMS_MAP.items():
288+
result_graph = graph.copy()
289+
algo = algo_class(result_graph, source, target)
290+
291+
while algo_result := algo.step():
292+
pass
293+
294+
# capacity bound check
295+
capacity_bound = True
296+
flow_in = [0 for _ in range(result_graph.number_of_nodes())]
297+
flow_out = [0 for _ in range(result_graph.number_of_nodes())]
298+
299+
for edge in result_graph.get_edges():
300+
if not edge.reverse:
301+
flow_out[edge.start] += edge.flow
302+
flow_in[edge.end] += edge.flow
303+
if edge.flow > edge.capacity:
304+
capacity_bound = False
305+
306+
# flow preservation check
307+
flow_preservation = all(flow_in[i] == flow_out[i] for i in range(result_graph.number_of_nodes())
308+
if i not in (source, target))
309+
310+
flow = sum(edge.flow for edge in result_graph.get_edges() if not edge.reverse)
311+
flow_values.append(flow)
312+
313+
result.append([name, flow_preservation, capacity_bound, flow])
314+
315+
self.txt_output.insert("end-1c", "\n" +
316+
tabulate(result, headers="firstrow", tablefmt="fancy_grid") +
317+
f"\nidetical max flow: {len(set(flow_values)) == 1}\n")
208318

209319

210320
window = tk.Tk()
@@ -222,7 +332,7 @@ def start_test(self):
222332
frame_visualization.pack()
223333
tabs.add(frame_visualization, text="Visualization")
224334

225-
frame_test_envoirement = TestEnvoirement(tabs)
335+
frame_test_envoirement = TestEnviroment(tabs)
226336
frame_test_envoirement.pack()
227337
tabs.add(frame_test_envoirement, text="Test enviroment")
228338

0 commit comments

Comments
 (0)