Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aimacode
GitHub Repository: aimacode/aima-python
Path: blob/master/gui/tsp.py
621 views
1
from tkinter import *
2
from tkinter import messagebox
3
4
import utils
5
from search import *
6
7
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
8
9
distances = {}
10
11
12
class TSProblem(Problem):
13
"""subclass of Problem to define various functions"""
14
15
def two_opt(self, state):
16
"""Neighbour generating function for Traveling Salesman Problem"""
17
neighbour_state = state[:]
18
left = random.randint(0, len(neighbour_state) - 1)
19
right = random.randint(0, len(neighbour_state) - 1)
20
if left > right:
21
left, right = right, left
22
neighbour_state[left: right + 1] = reversed(neighbour_state[left: right + 1])
23
return neighbour_state
24
25
def actions(self, state):
26
"""action that can be executed in given state"""
27
return [self.two_opt]
28
29
def result(self, state, action):
30
"""result after applying the given action on the given state"""
31
return action(state)
32
33
def path_cost(self, c, state1, action, state2):
34
"""total distance for the Traveling Salesman to be covered if in state2"""
35
cost = 0
36
for i in range(len(state2) - 1):
37
cost += distances[state2[i]][state2[i + 1]]
38
cost += distances[state2[0]][state2[-1]]
39
return cost
40
41
def value(self, state):
42
"""value of path cost given negative for the given state"""
43
return -1 * self.path_cost(None, None, None, state)
44
45
46
class TSPGui():
47
"""Class to create gui of Traveling Salesman using simulated annealing where one can
48
select cities, change speed and temperature. Distances between cities are euclidean
49
distances between them.
50
"""
51
52
def __init__(self, root, all_cities):
53
self.root = root
54
self.vars = []
55
self.frame_locations = {}
56
self.calculate_canvas_size()
57
self.button_text = StringVar()
58
self.button_text.set("Start")
59
self.algo_var = StringVar()
60
self.all_cities = all_cities
61
self.frame_select_cities = Frame(self.root)
62
self.frame_select_cities.grid(row=1)
63
self.frame_canvas = Frame(self.root)
64
self.frame_canvas.grid(row=2)
65
Label(self.root, text="Map of Romania", font="Times 13 bold").grid(row=0, columnspan=10)
66
67
def create_checkboxes(self, side=LEFT, anchor=W):
68
"""To select cities which are to be a part of Traveling Salesman Problem"""
69
70
row_number = 0
71
column_number = 0
72
73
for city in self.all_cities:
74
var = IntVar()
75
var.set(1)
76
Checkbutton(self.frame_select_cities, text=city, variable=var).grid(
77
row=row_number, column=column_number, sticky=W)
78
79
self.vars.append(var)
80
column_number += 1
81
if column_number == 10:
82
column_number = 0
83
row_number += 1
84
85
def create_buttons(self):
86
"""Create start and quit button"""
87
88
Button(self.frame_select_cities, textvariable=self.button_text,
89
command=self.run_traveling_salesman).grid(row=5, column=4, sticky=E + W)
90
Button(self.frame_select_cities, text='Quit', command=self.on_closing).grid(
91
row=5, column=5, sticky=E + W)
92
93
def create_dropdown_menu(self):
94
"""Create dropdown menu for algorithm selection"""
95
96
choices = {'Simulated Annealing', 'Genetic Algorithm', 'Hill Climbing'}
97
self.algo_var.set('Simulated Annealing')
98
dropdown_menu = OptionMenu(self.frame_select_cities, self.algo_var, *choices)
99
dropdown_menu.grid(row=4, column=4, columnspan=2, sticky=E + W)
100
dropdown_menu.config(width=19)
101
102
def run_traveling_salesman(self):
103
"""Choose selected cities"""
104
105
cities = []
106
for i in range(len(self.vars)):
107
if self.vars[i].get() == 1:
108
cities.append(self.all_cities[i])
109
110
tsp_problem = TSProblem(cities)
111
self.button_text.set("Reset")
112
self.create_canvas(tsp_problem)
113
114
def calculate_canvas_size(self):
115
"""Width and height for canvas"""
116
117
minx, maxx = sys.maxsize, -1 * sys.maxsize
118
miny, maxy = sys.maxsize, -1 * sys.maxsize
119
120
for value in romania_map.locations.values():
121
minx = min(minx, value[0])
122
maxx = max(maxx, value[0])
123
miny = min(miny, value[1])
124
maxy = max(maxy, value[1])
125
126
# New locations squeezed to fit inside the map of romania
127
for name, coordinates in romania_map.locations.items():
128
self.frame_locations[name] = (coordinates[0] / 1.2 - minx +
129
150, coordinates[1] / 1.2 - miny + 165)
130
131
canvas_width = maxx - minx + 200
132
canvas_height = maxy - miny + 200
133
134
self.canvas_width = canvas_width
135
self.canvas_height = canvas_height
136
137
def create_canvas(self, problem):
138
"""creating map with cities"""
139
140
map_canvas = Canvas(self.frame_canvas, width=self.canvas_width, height=self.canvas_height)
141
map_canvas.grid(row=3, columnspan=10)
142
current = Node(problem.initial)
143
map_canvas.delete("all")
144
self.romania_image = PhotoImage(file="../images/romania_map.png")
145
map_canvas.create_image(self.canvas_width / 2, self.canvas_height / 2,
146
image=self.romania_image)
147
cities = current.state
148
for city in cities:
149
x = self.frame_locations[city][0]
150
y = self.frame_locations[city][1]
151
map_canvas.create_oval(x - 3, y - 3, x + 3, y + 3,
152
fill="red", outline="red")
153
map_canvas.create_text(x - 15, y - 10, text=city)
154
155
self.cost = StringVar()
156
Label(self.frame_canvas, textvariable=self.cost, relief="sunken").grid(
157
row=2, columnspan=10)
158
159
self.speed = IntVar()
160
speed_scale = Scale(self.frame_canvas, from_=500, to=1, orient=HORIZONTAL,
161
variable=self.speed, label="Speed ----> ", showvalue=0, font="Times 11",
162
relief="sunken", cursor="gumby")
163
speed_scale.grid(row=1, columnspan=5, sticky=N + S + E + W)
164
165
if self.algo_var.get() == 'Simulated Annealing':
166
self.temperature = IntVar()
167
temperature_scale = Scale(self.frame_canvas, from_=100, to=0, orient=HORIZONTAL,
168
length=200, variable=self.temperature, label="Temperature ---->",
169
font="Times 11", relief="sunken", showvalue=0, cursor="gumby")
170
temperature_scale.grid(row=1, column=5, columnspan=5, sticky=N + S + E + W)
171
self.simulated_annealing_with_tunable_T(problem, map_canvas)
172
elif self.algo_var.get() == 'Genetic Algorithm':
173
self.mutation_rate = DoubleVar()
174
self.mutation_rate.set(0.05)
175
mutation_rate_scale = Scale(self.frame_canvas, from_=0, to=1, orient=HORIZONTAL,
176
length=200, variable=self.mutation_rate, label='Mutation Rate ---->',
177
font='Times 11', relief='sunken', showvalue=0, cursor='gumby', resolution=0.001)
178
mutation_rate_scale.grid(row=1, column=5, columnspan=5, sticky='nsew')
179
self.genetic_algorithm(problem, map_canvas)
180
elif self.algo_var.get() == 'Hill Climbing':
181
self.no_of_neighbors = IntVar()
182
self.no_of_neighbors.set(100)
183
no_of_neighbors_scale = Scale(self.frame_canvas, from_=10, to=1000, orient=HORIZONTAL,
184
length=200, variable=self.no_of_neighbors, label='Number of neighbors ---->',
185
font='Times 11', relief='sunken', showvalue=0, cursor='gumby')
186
no_of_neighbors_scale.grid(row=1, column=5, columnspan=5, sticky='nsew')
187
self.hill_climbing(problem, map_canvas)
188
189
def exp_schedule(k=100, lam=0.03, limit=1000):
190
"""One possible schedule function for simulated annealing"""
191
192
return lambda t: (k * np.exp(-lam * t) if t < limit else 0)
193
194
def simulated_annealing_with_tunable_T(self, problem, map_canvas, schedule=exp_schedule()):
195
"""Simulated annealing where temperature is taken as user input"""
196
197
current = Node(problem.initial)
198
199
while True:
200
T = schedule(self.temperature.get())
201
if T == 0:
202
return current.state
203
neighbors = current.expand(problem)
204
if not neighbors:
205
return current.state
206
next = random.choice(neighbors)
207
delta_e = problem.value(next.state) - problem.value(current.state)
208
if delta_e > 0 or probability(np.exp(delta_e / T)):
209
map_canvas.delete("poly")
210
211
current = next
212
self.cost.set("Cost = " + str('%0.3f' % (-1 * problem.value(current.state))))
213
points = []
214
for city in current.state:
215
points.append(self.frame_locations[city][0])
216
points.append(self.frame_locations[city][1])
217
map_canvas.create_polygon(points, outline='red', width=3, fill='', tag="poly")
218
map_canvas.update()
219
map_canvas.after(self.speed.get())
220
221
def genetic_algorithm(self, problem, map_canvas):
222
"""Genetic Algorithm modified for the given problem"""
223
224
def init_population(pop_number, gene_pool, state_length):
225
"""initialize population"""
226
227
population = []
228
for i in range(pop_number):
229
population.append(utils.shuffled(gene_pool))
230
return population
231
232
def recombine(state_a, state_b):
233
"""recombine two problem states"""
234
235
start = random.randint(0, len(state_a) - 1)
236
end = random.randint(start + 1, len(state_a))
237
new_state = state_a[start:end]
238
for city in state_b:
239
if city not in new_state:
240
new_state.append(city)
241
return new_state
242
243
def mutate(state, mutation_rate):
244
"""mutate problem states"""
245
246
if random.uniform(0, 1) < mutation_rate:
247
sample = random.sample(range(len(state)), 2)
248
state[sample[0]], state[sample[1]] = state[sample[1]], state[sample[0]]
249
return state
250
251
def fitness_fn(state):
252
"""calculate fitness of a particular state"""
253
254
fitness = problem.value(state)
255
return int((5600 + fitness) ** 2)
256
257
current = Node(problem.initial)
258
population = init_population(100, current.state, len(current.state))
259
all_time_best = current.state
260
while True:
261
population = [mutate(recombine(*select(2, population, fitness_fn)), self.mutation_rate.get())
262
for _ in range(len(population))]
263
current_best = np.argmax(population, key=fitness_fn)
264
if fitness_fn(current_best) > fitness_fn(all_time_best):
265
all_time_best = current_best
266
self.cost.set("Cost = " + str('%0.3f' % (-1 * problem.value(all_time_best))))
267
map_canvas.delete('poly')
268
points = []
269
for city in current_best:
270
points.append(self.frame_locations[city][0])
271
points.append(self.frame_locations[city][1])
272
map_canvas.create_polygon(points, outline='red', width=1, fill='', tag='poly')
273
best_points = []
274
for city in all_time_best:
275
best_points.append(self.frame_locations[city][0])
276
best_points.append(self.frame_locations[city][1])
277
map_canvas.create_polygon(best_points, outline='red', width=3, fill='', tag='poly')
278
map_canvas.update()
279
map_canvas.after(self.speed.get())
280
281
def hill_climbing(self, problem, map_canvas):
282
"""hill climbing where number of neighbors is taken as user input"""
283
284
def find_neighbors(state, number_of_neighbors=100):
285
"""finds neighbors using two_opt method"""
286
287
neighbors = []
288
for i in range(number_of_neighbors):
289
new_state = problem.two_opt(state)
290
neighbors.append(Node(new_state))
291
state = new_state
292
return neighbors
293
294
current = Node(problem.initial)
295
while True:
296
neighbors = find_neighbors(current.state, self.no_of_neighbors.get())
297
neighbor = np.argmax_random_tie(neighbors, key=lambda node: problem.value(node.state))
298
map_canvas.delete('poly')
299
points = []
300
for city in current.state:
301
points.append(self.frame_locations[city][0])
302
points.append(self.frame_locations[city][1])
303
map_canvas.create_polygon(points, outline='red', width=3, fill='', tag='poly')
304
neighbor_points = []
305
for city in neighbor.state:
306
neighbor_points.append(self.frame_locations[city][0])
307
neighbor_points.append(self.frame_locations[city][1])
308
map_canvas.create_polygon(neighbor_points, outline='red', width=1, fill='', tag='poly')
309
map_canvas.update()
310
map_canvas.after(self.speed.get())
311
if problem.value(neighbor.state) > problem.value(current.state):
312
current.state = neighbor.state
313
self.cost.set("Cost = " + str('%0.3f' % (-1 * problem.value(current.state))))
314
315
def on_closing(self):
316
if messagebox.askokcancel('Quit', 'Do you want to quit?'):
317
self.root.destroy()
318
319
320
if __name__ == '__main__':
321
all_cities = []
322
for city in romania_map.locations.keys():
323
distances[city] = {}
324
all_cities.append(city)
325
all_cities.sort()
326
327
# distances['city1']['city2'] contains euclidean distance between their coordinates
328
for name_1, coordinates_1 in romania_map.locations.items():
329
for name_2, coordinates_2 in romania_map.locations.items():
330
distances[name_1][name_2] = np.linalg.norm(
331
[coordinates_1[0] - coordinates_2[0], coordinates_1[1] - coordinates_2[1]])
332
distances[name_2][name_1] = np.linalg.norm(
333
[coordinates_1[0] - coordinates_2[0], coordinates_1[1] - coordinates_2[1]])
334
335
root = Tk()
336
root.title("Traveling Salesman Problem")
337
cities_selection_panel = TSPGui(root, all_cities)
338
cities_selection_panel.create_checkboxes()
339
cities_selection_panel.create_buttons()
340
cities_selection_panel.create_dropdown_menu()
341
root.protocol('WM_DELETE_WINDOW', cities_selection_panel.on_closing)
342
root.mainloop()
343
344