1
- import random
2
1
import tkinter as tk
3
2
from tkinter import messagebox
4
3
from tkinter import ttk
5
4
5
+ from tabulate import tabulate
6
+
6
7
import max_flow
7
8
import random_graph
8
9
import utils
9
10
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
+
10
20
11
21
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 ())
17
22
DEFAULT_NODES = 6
18
23
DEFAULT_MAX_CAPACITY = 5
19
- NODE_RADIUS = 5
24
+ NODE_RADIUS = 8
20
25
21
26
def __init__ (self , parent ):
22
27
super ().__init__ (parent )
23
28
24
29
config_bar = tk .Frame (self )
25
30
26
31
self .algo_variable = tk .StringVar (config_bar )
27
- self .algo_variable .set (self . ALGORITHMS [0 ])
32
+ self .algo_variable .set (ALGORITHMS [0 ])
28
33
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 )
30
36
self .opt_algorithm .grid (row = 0 , column = 0 , padx = 10 )
31
37
self .max_flow_algo = None
32
38
@@ -59,6 +65,9 @@ def __init__(self, parent):
59
65
self .btn_stop = tk .Button (text = "stop" , master = config_bar , command = self .stop )
60
66
self .btn_stop .grid (row = 0 , column = 9 , padx = 10 )
61
67
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
+
62
71
config_bar .pack (anchor = tk .N )
63
72
64
73
self .canvas = tk .Canvas (self , bg = "white" )
@@ -78,8 +87,8 @@ def render(self):
78
87
79
88
self .canvas .create_rectangle (0 , 0 , width , height , fill = "white" )
80
89
81
- self .render_nodes ()
82
90
self .render_edges ()
91
+ self .render_nodes ()
83
92
self .render_text ()
84
93
85
94
def render_nodes (self ):
@@ -123,9 +132,45 @@ def render_edges(self):
123
132
124
133
for edge in self .graph .get_edges ():
125
134
if not edge .reverse :
135
+ # TODO move arrow start and end to the outline of the node circles
126
136
p1 = utils .absolute_position (self .graph .get_nodes ()[edge .start ], width , height )
127
137
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!\n max-flow value: { flow_value } " )
129
174
130
175
def reset (self ):
131
176
try :
@@ -136,41 +181,37 @@ def reset(self):
136
181
self .render ()
137
182
138
183
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
+
139
189
self .max_flow_algo = None
140
190
except ValueError :
141
191
messagebox .showerror ("Error" , "nodes and capacity must be an integer" )
142
192
143
193
def step (self ):
144
194
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
156
198
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 )
161
201
else :
162
- messagebox . showinfo ( "Info" , "algorithm terminated!" )
202
+ self . algorithm_terminated ( )
163
203
164
204
def start (self ):
165
205
try :
166
206
intervall = int (self .ent_time .get ())
167
207
168
208
self .opt_algorithm ["state" ] = tk .DISABLED
169
209
self .btn_step ["state" ] = tk .DISABLED
210
+ self .btn_start ["state" ] = tk .DISABLED
170
211
self .ent_time ["state" ] = tk .DISABLED
171
212
172
- self .step ()
173
213
self ._jop = window .after (intervall , self .start )
214
+ self .step ()
174
215
except ValueError :
175
216
messagebox .showerror ("Error" , "intervall must be an integer" )
176
217
@@ -182,29 +223,98 @@ def stop(self):
182
223
window .after_cancel (self ._jop )
183
224
self ._jop = None
184
225
226
+ def help (self ):
227
+ messagebox .showinfo ("Help" , """
228
+ Max-Flow Visualization
229
+
230
+ notation:
231
+ n: number of nodes\t m: number of edges
232
+ F: max-flow value\t C: max capacity
185
233
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 ):
187
244
188
245
def __init__ (self , parent ):
189
246
super ().__init__ (parent )
190
247
191
248
self .btn_start = tk .Button (text = "start" , master = self , command = self .start_test )
192
249
self .btn_start .pack (fill = "x" )
193
250
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
+
194
255
self .txt_triples = tk .Text (master = self )
195
256
self .txt_triples .insert (tk .END , "10, 5, 5" )
196
257
self .txt_triples .pack (fill = "x" )
197
258
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" )
200
265
201
266
def start_test (self ):
267
+ self .txt_output .delete (1.0 , "end-1c" )
268
+
202
269
triples = self .txt_triples .get (1.0 , "end-1c" )
203
270
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
+
205
280
for _ in range (instances ):
206
281
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"\n idetical max flow: { len (set (flow_values )) == 1 } \n " )
208
318
209
319
210
320
window = tk .Tk ()
@@ -222,7 +332,7 @@ def start_test(self):
222
332
frame_visualization .pack ()
223
333
tabs .add (frame_visualization , text = "Visualization" )
224
334
225
- frame_test_envoirement = TestEnvoirement (tabs )
335
+ frame_test_envoirement = TestEnviroment (tabs )
226
336
frame_test_envoirement .pack ()
227
337
tabs .add (frame_test_envoirement , text = "Test enviroment" )
228
338
0 commit comments