Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aimacode
GitHub Repository: aimacode/aima-python
Path: blob/master/gui/romania_problem.py
621 views
1
from copy import deepcopy
2
from tkinter import *
3
4
from search import *
5
from utils import PriorityQueue
6
7
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
8
9
root = None
10
city_coord = {}
11
romania_problem = None
12
algo = None
13
start = None
14
goal = None
15
counter = -1
16
city_map = None
17
frontier = None
18
front = None
19
node = None
20
next_button = None
21
explored = None
22
23
24
def create_map(root):
25
"""This function draws out the required map."""
26
global city_map, start, goal
27
romania_locations = romania_map.locations
28
width = 750
29
height = 670
30
margin = 5
31
city_map = Canvas(root, width=width, height=height)
32
city_map.pack()
33
34
# Since lines have to be drawn between particular points, we need to list
35
# them separately
36
make_line(
37
city_map,
38
romania_locations['Arad'][0],
39
height -
40
romania_locations['Arad'][1],
41
romania_locations['Sibiu'][0],
42
height -
43
romania_locations['Sibiu'][1],
44
romania_map.get('Arad', 'Sibiu'))
45
make_line(
46
city_map,
47
romania_locations['Arad'][0],
48
height -
49
romania_locations['Arad'][1],
50
romania_locations['Zerind'][0],
51
height -
52
romania_locations['Zerind'][1],
53
romania_map.get('Arad', 'Zerind'))
54
make_line(
55
city_map,
56
romania_locations['Arad'][0],
57
height -
58
romania_locations['Arad'][1],
59
romania_locations['Timisoara'][0],
60
height -
61
romania_locations['Timisoara'][1],
62
romania_map.get('Arad', 'Timisoara'))
63
make_line(
64
city_map,
65
romania_locations['Oradea'][0],
66
height -
67
romania_locations['Oradea'][1],
68
romania_locations['Zerind'][0],
69
height -
70
romania_locations['Zerind'][1],
71
romania_map.get('Oradea', 'Zerind'))
72
make_line(
73
city_map,
74
romania_locations['Oradea'][0],
75
height -
76
romania_locations['Oradea'][1],
77
romania_locations['Sibiu'][0],
78
height -
79
romania_locations['Sibiu'][1],
80
romania_map.get('Oradea', 'Sibiu'))
81
make_line(
82
city_map,
83
romania_locations['Lugoj'][0],
84
height -
85
romania_locations['Lugoj'][1],
86
romania_locations['Timisoara'][0],
87
height -
88
romania_locations['Timisoara'][1],
89
romania_map.get('Lugoj', 'Timisoara'))
90
make_line(
91
city_map,
92
romania_locations['Lugoj'][0],
93
height -
94
romania_locations['Lugoj'][1],
95
romania_locations['Mehadia'][0],
96
height -
97
romania_locations['Mehadia'][1],
98
romania_map.get('Lugoj', 'Mehadia'))
99
make_line(
100
city_map,
101
romania_locations['Drobeta'][0],
102
height -
103
romania_locations['Drobeta'][1],
104
romania_locations['Mehadia'][0],
105
height -
106
romania_locations['Mehadia'][1],
107
romania_map.get('Drobeta', 'Mehadia'))
108
make_line(
109
city_map,
110
romania_locations['Drobeta'][0],
111
height -
112
romania_locations['Drobeta'][1],
113
romania_locations['Craiova'][0],
114
height -
115
romania_locations['Craiova'][1],
116
romania_map.get('Drobeta', 'Craiova'))
117
make_line(
118
city_map,
119
romania_locations['Pitesti'][0],
120
height -
121
romania_locations['Pitesti'][1],
122
romania_locations['Craiova'][0],
123
height -
124
romania_locations['Craiova'][1],
125
romania_map.get('Pitesti', 'Craiova'))
126
make_line(
127
city_map,
128
romania_locations['Rimnicu'][0],
129
height -
130
romania_locations['Rimnicu'][1],
131
romania_locations['Craiova'][0],
132
height -
133
romania_locations['Craiova'][1],
134
romania_map.get('Rimnicu', 'Craiova'))
135
make_line(
136
city_map,
137
romania_locations['Rimnicu'][0],
138
height -
139
romania_locations['Rimnicu'][1],
140
romania_locations['Sibiu'][0],
141
height -
142
romania_locations['Sibiu'][1],
143
romania_map.get('Rimnicu', 'Sibiu'))
144
make_line(
145
city_map,
146
romania_locations['Rimnicu'][0],
147
height -
148
romania_locations['Rimnicu'][1],
149
romania_locations['Pitesti'][0],
150
height -
151
romania_locations['Pitesti'][1],
152
romania_map.get('Rimnicu', 'Pitesti'))
153
make_line(
154
city_map,
155
romania_locations['Bucharest'][0],
156
height -
157
romania_locations['Bucharest'][1],
158
romania_locations['Pitesti'][0],
159
height -
160
romania_locations['Pitesti'][1],
161
romania_map.get('Bucharest', 'Pitesti'))
162
make_line(
163
city_map,
164
romania_locations['Fagaras'][0],
165
height -
166
romania_locations['Fagaras'][1],
167
romania_locations['Sibiu'][0],
168
height -
169
romania_locations['Sibiu'][1],
170
romania_map.get('Fagaras', 'Sibiu'))
171
make_line(
172
city_map,
173
romania_locations['Fagaras'][0],
174
height -
175
romania_locations['Fagaras'][1],
176
romania_locations['Bucharest'][0],
177
height -
178
romania_locations['Bucharest'][1],
179
romania_map.get('Fagaras', 'Bucharest'))
180
make_line(
181
city_map,
182
romania_locations['Giurgiu'][0],
183
height -
184
romania_locations['Giurgiu'][1],
185
romania_locations['Bucharest'][0],
186
height -
187
romania_locations['Bucharest'][1],
188
romania_map.get('Giurgiu', 'Bucharest'))
189
make_line(
190
city_map,
191
romania_locations['Urziceni'][0],
192
height -
193
romania_locations['Urziceni'][1],
194
romania_locations['Bucharest'][0],
195
height -
196
romania_locations['Bucharest'][1],
197
romania_map.get('Urziceni', 'Bucharest'))
198
make_line(
199
city_map,
200
romania_locations['Urziceni'][0],
201
height -
202
romania_locations['Urziceni'][1],
203
romania_locations['Hirsova'][0],
204
height -
205
romania_locations['Hirsova'][1],
206
romania_map.get('Urziceni', 'Hirsova'))
207
make_line(
208
city_map,
209
romania_locations['Eforie'][0],
210
height -
211
romania_locations['Eforie'][1],
212
romania_locations['Hirsova'][0],
213
height -
214
romania_locations['Hirsova'][1],
215
romania_map.get('Eforie', 'Hirsova'))
216
make_line(
217
city_map,
218
romania_locations['Urziceni'][0],
219
height -
220
romania_locations['Urziceni'][1],
221
romania_locations['Vaslui'][0],
222
height -
223
romania_locations['Vaslui'][1],
224
romania_map.get('Urziceni', 'Vaslui'))
225
make_line(
226
city_map,
227
romania_locations['Iasi'][0],
228
height -
229
romania_locations['Iasi'][1],
230
romania_locations['Vaslui'][0],
231
height -
232
romania_locations['Vaslui'][1],
233
romania_map.get('Iasi', 'Vaslui'))
234
make_line(
235
city_map,
236
romania_locations['Iasi'][0],
237
height -
238
romania_locations['Iasi'][1],
239
romania_locations['Neamt'][0],
240
height -
241
romania_locations['Neamt'][1],
242
romania_map.get('Iasi', 'Neamt'))
243
244
for city in romania_locations.keys():
245
make_rectangle(
246
city_map,
247
romania_locations[city][0],
248
height -
249
romania_locations[city][1],
250
margin,
251
city)
252
253
make_legend(city_map)
254
255
256
def make_line(map, x0, y0, x1, y1, distance):
257
"""This function draws out the lines joining various points."""
258
map.create_line(x0, y0, x1, y1)
259
map.create_text((x0 + x1) / 2, (y0 + y1) / 2, text=distance)
260
261
262
def make_rectangle(map, x0, y0, margin, city_name):
263
"""This function draws out rectangles for various points."""
264
global city_coord
265
rect = map.create_rectangle(
266
x0 - margin,
267
y0 - margin,
268
x0 + margin,
269
y0 + margin,
270
fill="white")
271
if "Bucharest" in city_name or "Pitesti" in city_name or "Lugoj" in city_name \
272
or "Mehadia" in city_name or "Drobeta" in city_name:
273
map.create_text(
274
x0 - 2 * margin,
275
y0 - 2 * margin,
276
text=city_name,
277
anchor=E)
278
else:
279
map.create_text(
280
x0 - 2 * margin,
281
y0 - 2 * margin,
282
text=city_name,
283
anchor=SE)
284
city_coord.update({city_name: rect})
285
286
287
def make_legend(map):
288
rect1 = map.create_rectangle(600, 100, 610, 110, fill="white")
289
text1 = map.create_text(615, 105, anchor=W, text="Un-explored")
290
291
rect2 = map.create_rectangle(600, 115, 610, 125, fill="orange")
292
text2 = map.create_text(615, 120, anchor=W, text="Frontier")
293
294
rect3 = map.create_rectangle(600, 130, 610, 140, fill="red")
295
text3 = map.create_text(615, 135, anchor=W, text="Currently Exploring")
296
297
rect4 = map.create_rectangle(600, 145, 610, 155, fill="grey")
298
text4 = map.create_text(615, 150, anchor=W, text="Explored")
299
300
rect5 = map.create_rectangle(600, 160, 610, 170, fill="dark green")
301
text5 = map.create_text(615, 165, anchor=W, text="Final Solution")
302
303
304
def tree_search(problem):
305
"""
306
Search through the successors of a problem to find a goal.
307
The argument frontier should be an empty queue.
308
Don't worry about repeated paths to a state. [Figure 3.7]
309
This function has been changed to make it suitable for the Tkinter GUI.
310
"""
311
global counter, frontier, node
312
313
if counter == -1:
314
frontier.append(Node(problem.initial))
315
316
display_frontier(frontier)
317
if counter % 3 == 0 and counter >= 0:
318
node = frontier.pop()
319
320
display_current(node)
321
if counter % 3 == 1 and counter >= 0:
322
if problem.goal_test(node.state):
323
return node
324
frontier.extend(node.expand(problem))
325
326
display_frontier(frontier)
327
if counter % 3 == 2 and counter >= 0:
328
display_explored(node)
329
return None
330
331
332
def graph_search(problem):
333
"""
334
Search through the successors of a problem to find a goal.
335
The argument frontier should be an empty queue.
336
If two paths reach a state, only use the first one. [Figure 3.7]
337
This function has been changed to make it suitable for the Tkinter GUI.
338
"""
339
global counter, frontier, node, explored
340
if counter == -1:
341
frontier.append(Node(problem.initial))
342
explored = set()
343
344
display_frontier(frontier)
345
if counter % 3 == 0 and counter >= 0:
346
node = frontier.pop()
347
348
display_current(node)
349
if counter % 3 == 1 and counter >= 0:
350
if problem.goal_test(node.state):
351
return node
352
explored.add(node.state)
353
frontier.extend(child for child in node.expand(problem)
354
if child.state not in explored and
355
child not in frontier)
356
357
display_frontier(frontier)
358
if counter % 3 == 2 and counter >= 0:
359
display_explored(node)
360
return None
361
362
363
def display_frontier(queue):
364
"""This function marks the frontier nodes (orange) on the map."""
365
global city_map, city_coord
366
qu = deepcopy(queue)
367
while qu:
368
node = qu.pop()
369
for city in city_coord.keys():
370
if node.state == city:
371
city_map.itemconfig(city_coord[city], fill="orange")
372
373
374
def display_current(node):
375
"""This function marks the currently exploring node (red) on the map."""
376
global city_map, city_coord
377
city = node.state
378
city_map.itemconfig(city_coord[city], fill="red")
379
380
381
def display_explored(node):
382
"""This function marks the already explored node (gray) on the map."""
383
global city_map, city_coord
384
city = node.state
385
city_map.itemconfig(city_coord[city], fill="gray")
386
387
388
def display_final(cities):
389
"""This function marks the final solution nodes (green) on the map."""
390
global city_map, city_coord
391
for city in cities:
392
city_map.itemconfig(city_coord[city], fill="green")
393
394
395
def breadth_first_tree_search(problem):
396
"""Search the shallowest nodes in the search tree first."""
397
global frontier, counter, node
398
if counter == -1:
399
frontier = deque()
400
401
if counter == -1:
402
frontier.append(Node(problem.initial))
403
404
display_frontier(frontier)
405
if counter % 3 == 0 and counter >= 0:
406
node = frontier.popleft()
407
408
display_current(node)
409
if counter % 3 == 1 and counter >= 0:
410
if problem.goal_test(node.state):
411
return node
412
frontier.extend(node.expand(problem))
413
414
display_frontier(frontier)
415
if counter % 3 == 2 and counter >= 0:
416
display_explored(node)
417
return None
418
419
420
def depth_first_tree_search(problem):
421
"""Search the deepest nodes in the search tree first."""
422
# This search algorithm might not work in case of repeated paths.
423
global frontier, counter, node
424
if counter == -1:
425
frontier = [] # stack
426
427
if counter == -1:
428
frontier.append(Node(problem.initial))
429
430
display_frontier(frontier)
431
if counter % 3 == 0 and counter >= 0:
432
node = frontier.pop()
433
434
display_current(node)
435
if counter % 3 == 1 and counter >= 0:
436
if problem.goal_test(node.state):
437
return node
438
frontier.extend(node.expand(problem))
439
440
display_frontier(frontier)
441
if counter % 3 == 2 and counter >= 0:
442
display_explored(node)
443
return None
444
445
446
def breadth_first_graph_search(problem):
447
"""[Figure 3.11]"""
448
global frontier, node, explored, counter
449
if counter == -1:
450
node = Node(problem.initial)
451
display_current(node)
452
if problem.goal_test(node.state):
453
return node
454
455
frontier = deque([node]) # FIFO queue
456
457
display_frontier(frontier)
458
explored = set()
459
if counter % 3 == 0 and counter >= 0:
460
node = frontier.popleft()
461
display_current(node)
462
explored.add(node.state)
463
if counter % 3 == 1 and counter >= 0:
464
for child in node.expand(problem):
465
if child.state not in explored and child not in frontier:
466
if problem.goal_test(child.state):
467
return child
468
frontier.append(child)
469
display_frontier(frontier)
470
if counter % 3 == 2 and counter >= 0:
471
display_explored(node)
472
return None
473
474
475
def depth_first_graph_search(problem):
476
"""Search the deepest nodes in the search tree first."""
477
global counter, frontier, node, explored
478
if counter == -1:
479
frontier = [] # stack
480
if counter == -1:
481
frontier.append(Node(problem.initial))
482
explored = set()
483
484
display_frontier(frontier)
485
if counter % 3 == 0 and counter >= 0:
486
node = frontier.pop()
487
488
display_current(node)
489
if counter % 3 == 1 and counter >= 0:
490
if problem.goal_test(node.state):
491
return node
492
explored.add(node.state)
493
frontier.extend(child for child in node.expand(problem)
494
if child.state not in explored and
495
child not in frontier)
496
497
display_frontier(frontier)
498
if counter % 3 == 2 and counter >= 0:
499
display_explored(node)
500
return None
501
502
503
def best_first_graph_search(problem, f):
504
"""Search the nodes with the lowest f scores first.
505
You specify the function f(node) that you want to minimize; for example,
506
if f is a heuristic estimate to the goal, then we have greedy best
507
first search; if f is node.depth then we have breadth-first search.
508
There is a subtlety: the line "f = memoize(f, 'f')" means that the f
509
values will be cached on the nodes as they are computed. So after doing
510
a best first search you can examine the f values of the path returned."""
511
global frontier, node, explored, counter
512
513
if counter == -1:
514
f = memoize(f, 'f')
515
node = Node(problem.initial)
516
display_current(node)
517
if problem.goal_test(node.state):
518
return node
519
frontier = PriorityQueue('min', f)
520
frontier.append(node)
521
display_frontier(frontier)
522
explored = set()
523
if counter % 3 == 0 and counter >= 0:
524
node = frontier.pop()
525
display_current(node)
526
if problem.goal_test(node.state):
527
return node
528
explored.add(node.state)
529
if counter % 3 == 1 and counter >= 0:
530
for child in node.expand(problem):
531
if child.state not in explored and child not in frontier:
532
frontier.append(child)
533
elif child in frontier:
534
if f(child) < frontier[child]:
535
del frontier[child]
536
frontier.append(child)
537
display_frontier(frontier)
538
if counter % 3 == 2 and counter >= 0:
539
display_explored(node)
540
return None
541
542
543
def uniform_cost_search(problem):
544
"""[Figure 3.14]"""
545
return best_first_graph_search(problem, lambda node: node.path_cost)
546
547
548
def astar_search(problem, h=None):
549
"""A* search is best-first graph search with f(n) = g(n)+h(n).
550
You need to specify the h function when you call astar_search, or
551
else in your Problem subclass."""
552
h = memoize(h or problem.h, 'h')
553
return best_first_graph_search(problem, lambda n: n.path_cost + h(n))
554
555
556
# TODO:
557
# Remove redundant code.
558
# Make the interchangeability work between various algorithms at each step.
559
def on_click():
560
"""
561
This function defines the action of the 'Next' button.
562
"""
563
global algo, counter, next_button, romania_problem, start, goal
564
romania_problem = GraphProblem(start.get(), goal.get(), romania_map)
565
if "Breadth-First Tree Search" == algo.get():
566
node = breadth_first_tree_search(romania_problem)
567
if node is not None:
568
final_path = breadth_first_tree_search(romania_problem).solution()
569
final_path.append(start.get())
570
display_final(final_path)
571
next_button.config(state="disabled")
572
counter += 1
573
elif "Depth-First Tree Search" == algo.get():
574
node = depth_first_tree_search(romania_problem)
575
if node is not None:
576
final_path = depth_first_tree_search(romania_problem).solution()
577
final_path.append(start.get())
578
display_final(final_path)
579
next_button.config(state="disabled")
580
counter += 1
581
elif "Breadth-First Graph Search" == algo.get():
582
node = breadth_first_graph_search(romania_problem)
583
if node is not None:
584
final_path = breadth_first_graph_search(romania_problem).solution()
585
final_path.append(start.get())
586
display_final(final_path)
587
next_button.config(state="disabled")
588
counter += 1
589
elif "Depth-First Graph Search" == algo.get():
590
node = depth_first_graph_search(romania_problem)
591
if node is not None:
592
final_path = depth_first_graph_search(romania_problem).solution()
593
final_path.append(start.get())
594
display_final(final_path)
595
next_button.config(state="disabled")
596
counter += 1
597
elif "Uniform Cost Search" == algo.get():
598
node = uniform_cost_search(romania_problem)
599
if node is not None:
600
final_path = uniform_cost_search(romania_problem).solution()
601
final_path.append(start.get())
602
display_final(final_path)
603
next_button.config(state="disabled")
604
counter += 1
605
elif "A* - Search" == algo.get():
606
node = astar_search(romania_problem)
607
if node is not None:
608
final_path = astar_search(romania_problem).solution()
609
final_path.append(start.get())
610
display_final(final_path)
611
next_button.config(state="disabled")
612
counter += 1
613
614
615
def reset_map():
616
global counter, city_coord, city_map, next_button
617
counter = -1
618
for city in city_coord.keys():
619
city_map.itemconfig(city_coord[city], fill="white")
620
next_button.config(state="normal")
621
622
623
# TODO: Add more search algorithms in the OptionMenu
624
if __name__ == "__main__":
625
global algo, start, goal, next_button
626
root = Tk()
627
root.title("Road Map of Romania")
628
root.geometry("950x1150")
629
algo = StringVar(root)
630
start = StringVar(root)
631
goal = StringVar(root)
632
algo.set("Breadth-First Tree Search")
633
start.set('Arad')
634
goal.set('Bucharest')
635
cities = sorted(romania_map.locations.keys())
636
algorithm_menu = OptionMenu(
637
root,
638
algo, "Breadth-First Tree Search", "Depth-First Tree Search",
639
"Breadth-First Graph Search", "Depth-First Graph Search",
640
"Uniform Cost Search", "A* - Search")
641
Label(root, text="\n Search Algorithm").pack()
642
algorithm_menu.pack()
643
Label(root, text="\n Start City").pack()
644
start_menu = OptionMenu(root, start, *cities)
645
start_menu.pack()
646
Label(root, text="\n Goal City").pack()
647
goal_menu = OptionMenu(root, goal, *cities)
648
goal_menu.pack()
649
frame1 = Frame(root)
650
next_button = Button(
651
frame1,
652
width=6,
653
height=2,
654
text="Next",
655
command=on_click,
656
padx=2,
657
pady=2,
658
relief=GROOVE)
659
next_button.pack(side=RIGHT)
660
reset_button = Button(
661
frame1,
662
width=6,
663
height=2,
664
text="Reset",
665
command=reset_map,
666
padx=2,
667
pady=2,
668
relief=GROOVE)
669
reset_button.pack(side=RIGHT)
670
frame1.pack(side=BOTTOM)
671
create_map(root)
672
root.mainloop()
673
674