Skip to content

Commit cf23e5c

Browse files
ad71norvig
authored andcommitted
Adding algorithm selection menu for TSP (#706)
* Added dropdown option to solve using genetic algorithm * Added option to solve using Hill Climbing * Added messagebox to confirm exit
1 parent a690882 commit cf23e5c

File tree

1 file changed

+135
-8
lines changed

1 file changed

+135
-8
lines changed

gui/tsp.py

Lines changed: 135 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from tkinter import *
2+
from tkinter import messagebox
23
import sys
34
import os.path
45
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
56
from search import *
7+
import utils
68
import numpy as np
79

810
distances = {}
@@ -56,6 +58,7 @@ def __init__(self, root, all_cities):
5658
self.calculate_canvas_size()
5759
self.button_text = StringVar()
5860
self.button_text.set("Start")
61+
self.algo_var = StringVar()
5962
self.all_cities = all_cities
6063
self.frame_select_cities = Frame(self.root)
6164
self.frame_select_cities.grid(row=1)
@@ -85,9 +88,18 @@ def create_buttons(self):
8588
""" Create start and quit button """
8689

8790
Button(self.frame_select_cities, textvariable=self.button_text,
88-
command=self.run_traveling_salesman).grid(row=3, column=4, sticky=E + W)
89-
Button(self.frame_select_cities, text='Quit', command=self.root.destroy).grid(
90-
row=3, column=5, sticky=E + W)
91+
command=self.run_traveling_salesman).grid(row=5, column=4, sticky=E + W)
92+
Button(self.frame_select_cities, text='Quit', command=self.on_closing).grid(
93+
row=5, column=5, sticky=E + W)
94+
95+
def create_dropdown_menu(self):
96+
""" Create dropdown menu for algorithm selection """
97+
98+
choices = {'Simulated Annealing', 'Genetic Algorithm', 'Hill Climbing'}
99+
self.algo_var.set('Simulated Annealing')
100+
dropdown_menu = OptionMenu(self.frame_select_cities, self.algo_var, *choices)
101+
dropdown_menu.grid(row=4, column=4, columnspan=2, sticky=E + W)
102+
dropdown_menu.config(width=19)
91103

92104
def run_traveling_salesman(self):
93105
""" Choose selected citites """
@@ -151,13 +163,30 @@ def create_canvas(self, problem):
151163
variable=self.speed, label="Speed ----> ", showvalue=0, font="Times 11",
152164
relief="sunken", cursor="gumby")
153165
speed_scale.grid(row=1, columnspan=5, sticky=N + S + E + W)
154-
self.temperature = IntVar()
155-
temperature_scale = Scale(self.frame_canvas, from_=100, to=0, orient=HORIZONTAL,
166+
167+
if self.algo_var.get() == 'Simulated Annealing':
168+
self.temperature = IntVar()
169+
temperature_scale = Scale(self.frame_canvas, from_=100, to=0, orient=HORIZONTAL,
156170
length=200, variable=self.temperature, label="Temperature ---->",
157171
font="Times 11", relief="sunken", showvalue=0, cursor="gumby")
158-
159-
temperature_scale.grid(row=1, column=5, columnspan=5, sticky=N + S + E + W)
160-
self.simulated_annealing_with_tunable_T(problem, map_canvas)
172+
temperature_scale.grid(row=1, column=5, columnspan=5, sticky=N + S + E + W)
173+
self.simulated_annealing_with_tunable_T(problem, map_canvas)
174+
elif self.algo_var.get() == 'Genetic Algorithm':
175+
self.mutation_rate = DoubleVar()
176+
self.mutation_rate.set(0.05)
177+
mutation_rate_scale = Scale(self.frame_canvas, from_=0, to=1, orient=HORIZONTAL,
178+
length=200, variable=self.mutation_rate, label='Mutation Rate ---->',
179+
font='Times 11', relief='sunken', showvalue=0, cursor='gumby', resolution=0.001)
180+
mutation_rate_scale.grid(row=1, column=5, columnspan=5, sticky='nsew')
181+
self.genetic_algorithm(problem, map_canvas)
182+
elif self.algo_var.get() == 'Hill Climbing':
183+
self.no_of_neighbors = IntVar()
184+
self.no_of_neighbors.set(100)
185+
no_of_neighbors_scale = Scale(self.frame_canvas, from_=10, to=1000, orient=HORIZONTAL,
186+
length=200, variable=self.no_of_neighbors, label='Number of neighbors ---->',
187+
font='Times 11',relief='sunken', showvalue=0, cursor='gumby')
188+
no_of_neighbors_scale.grid(row=1, column=5, columnspan=5, sticky='nsew')
189+
self.hill_climbing(problem, map_canvas)
161190

162191
def exp_schedule(k=100, lam=0.03, limit=1000):
163192
""" One possible schedule function for simulated annealing """
@@ -191,6 +220,102 @@ def simulated_annealing_with_tunable_T(self, problem, map_canvas, schedule=exp_s
191220
map_canvas.update()
192221
map_canvas.after(self.speed.get())
193222

223+
def genetic_algorithm(self, problem, map_canvas):
224+
""" Genetic Algorithm modified for the given problem """
225+
226+
def init_population(pop_number, gene_pool, state_length):
227+
""" initialize population """
228+
229+
population = []
230+
for i in range(pop_number):
231+
population.append(utils.shuffled(gene_pool))
232+
return population
233+
234+
def recombine(state_a, state_b):
235+
""" recombine two problem states """
236+
237+
start = random.randint(0, len(state_a) - 1)
238+
end = random.randint(start + 1, len(state_a))
239+
new_state = state_a[start:end]
240+
for city in state_b:
241+
if city not in new_state:
242+
new_state.append(city)
243+
return new_state
244+
245+
def mutate(state, mutation_rate):
246+
""" mutate problem states """
247+
248+
if random.uniform(0, 1) < mutation_rate:
249+
sample = random.sample(range(len(state)), 2)
250+
state[sample[0]], state[sample[1]] = state[sample[1]], state[sample[0]]
251+
return state
252+
253+
def fitness_fn(state):
254+
""" calculate fitness of a particular state """
255+
256+
fitness = problem.value(state)
257+
return int((5600 + fitness) ** 2)
258+
259+
current = Node(problem.initial)
260+
population = init_population(100, current.state, len(current.state))
261+
all_time_best = current.state
262+
while(1):
263+
population = [mutate(recombine(*select(2, population, fitness_fn)), self.mutation_rate.get()) for i in range(len(population))]
264+
current_best = utils.argmax(population, key=fitness_fn)
265+
if fitness_fn(current_best) > fitness_fn(all_time_best):
266+
all_time_best = current_best
267+
self.cost.set("Cost = " + str('%0.3f' % (-1 * problem.value(all_time_best))))
268+
map_canvas.delete('poly')
269+
points = []
270+
for city in current_best:
271+
points.append(self.frame_locations[city][0])
272+
points.append(self.frame_locations[city][1])
273+
map_canvas.create_polygon(points, outline='red', width=1, fill='', tag='poly')
274+
best_points = []
275+
for city in all_time_best:
276+
best_points.append(self.frame_locations[city][0])
277+
best_points.append(self.frame_locations[city][1])
278+
map_canvas.create_polygon(best_points, outline='red', width=3, fill='', tag='poly')
279+
map_canvas.update()
280+
map_canvas.after(self.speed.get())
281+
282+
def hill_climbing(self, problem, map_canvas):
283+
""" hill climbing where number of neighbors is taken as user input """
284+
285+
def find_neighbors(state, number_of_neighbors=100):
286+
""" finds neighbors using two_opt method """
287+
288+
neighbors = []
289+
for i in range(number_of_neighbors):
290+
new_state = problem.two_opt(state)
291+
neighbors.append(Node(new_state))
292+
state = new_state
293+
return neighbors
294+
295+
current = Node(problem.initial)
296+
while(1):
297+
neighbors = find_neighbors(current.state, self.no_of_neighbors.get())
298+
neighbor = utils.argmax_random_tie(neighbors, key=lambda node: problem.value(node.state))
299+
map_canvas.delete('poly')
300+
points = []
301+
for city in current.state:
302+
points.append(self.frame_locations[city][0])
303+
points.append(self.frame_locations[city][1])
304+
map_canvas.create_polygon(points, outline='red', width=3, fill='', tag='poly')
305+
neighbor_points = []
306+
for city in neighbor.state:
307+
neighbor_points.append(self.frame_locations[city][0])
308+
neighbor_points.append(self.frame_locations[city][1])
309+
map_canvas.create_polygon(neighbor_points, outline='red', width=1, fill='', tag='poly')
310+
map_canvas.update()
311+
map_canvas.after(self.speed.get())
312+
if problem.value(neighbor.state) > problem.value(current.state):
313+
current.state = neighbor.state
314+
self.cost.set("Cost = " + str('%0.3f' % (-1 * problem.value(current.state))))
315+
316+
def on_closing(self):
317+
if messagebox.askokcancel('Quit', 'Do you want to quit?'):
318+
self.root.destroy()
194319

195320
def main():
196321
all_cities = []
@@ -212,6 +337,8 @@ def main():
212337
cities_selection_panel = TSP_Gui(root, all_cities)
213338
cities_selection_panel.create_checkboxes()
214339
cities_selection_panel.create_buttons()
340+
cities_selection_panel.create_dropdown_menu()
341+
root.protocol('WM_DELETE_WINDOW', cities_selection_panel.on_closing)
215342
root.mainloop()
216343

217344

0 commit comments

Comments
 (0)