From ddac0dcf391dd3c8d42ad1487139ada88aad3bbf Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Fri, 26 Jan 2018 11:25:22 +0530 Subject: [PATCH 1/9] Update PeakFindingProblem code to allow diagonal motion (#684) * Update PeakFindingProblem code to allow diagonal motion * Fix unit test issues * update PeakFindingProblem to take actions as input param * Refactor code in search.py --- search.py | 31 +++++++++++++++---------------- tests/test_search.py | 4 ++-- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/search.py b/search.py index 873c03752..8bf742489 100644 --- a/search.py +++ b/search.py @@ -7,7 +7,7 @@ from utils import ( is_in, argmin, argmax, argmax_random_tie, probability, weighted_sampler, memoize, print_table, open_data, Stack, FIFOQueue, PriorityQueue, name, - distance + distance, vector_add ) from collections import defaultdict @@ -526,39 +526,37 @@ def and_search(states, problem, path): # body of and or search return or_search(problem.initial, problem, []) +# Pre-defined actions for PeakFindingProblem +directions4 = { 'W':(-1, 0), 'N':(0, 1), 'E':(1, 0), 'S':(0, -1) } +directions8 = dict(directions4) +directions8.update({'NW':(-1, 1), 'NE':(1, 1), 'SE':(1, -1), 'SW':(-1, -1) }) class PeakFindingProblem(Problem): """Problem of finding the highest peak in a limited grid""" - def __init__(self, initial, grid): + def __init__(self, initial, grid, defined_actions=directions4): """The grid is a 2 dimensional array/list whose state is specified by tuple of indices""" Problem.__init__(self, initial) self.grid = grid + self.defined_actions = defined_actions self.n = len(grid) assert self.n > 0 self.m = len(grid[0]) assert self.m > 0 def actions(self, state): - """Allows movement in only 4 directions""" - # TODO: Add flag to allow diagonal motion + """Returns the list of actions which are allowed to be taken from the given state""" allowed_actions = [] - if state[0] > 0: - allowed_actions.append('N') - if state[0] < self.n - 1: - allowed_actions.append('S') - if state[1] > 0: - allowed_actions.append('W') - if state[1] < self.m - 1: - allowed_actions.append('E') + for action in self.defined_actions: + next_state = vector_add(state, self.defined_actions[action]) + if next_state[0] >= 0 and next_state[1] >= 0 and next_state[0] <= self.n - 1 and next_state[1] <= self.m - 1: + allowed_actions.append(action) + return allowed_actions def result(self, state, action): """Moves in the direction specified by action""" - x, y = state - x = x + (1 if action == 'S' else (-1 if action == 'N' else 0)) - y = y + (1 if action == 'E' else (-1 if action == 'W' else 0)) - return (x, y) + return vector_add(state, self.defined_actions[action]) def value(self, state): """Value of a state is the value it is the index to""" @@ -1347,3 +1345,4 @@ def compare_graph_searchers(): GraphProblem('Q', 'WA', australia_map)], header=['Searcher', 'romania_map(Arad, Bucharest)', 'romania_map(Oradea, Neamt)', 'australia_map']) + diff --git a/tests/test_search.py b/tests/test_search.py index f22ca6f89..04cb2db35 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -88,12 +88,12 @@ def test_hill_climbing(): def test_simulated_annealing(): random.seed("aima-python") prob = PeakFindingProblem((0, 0), [[0, 5, 10, 20], - [-3, 7, 11, 5]]) + [-3, 7, 11, 5]], directions4) sols = {prob.value(simulated_annealing(prob)) for i in range(100)} assert max(sols) == 20 prob = PeakFindingProblem((0, 0), [[0, 5, 10, 8], [-3, 7, 9, 999], - [1, 2, 5, 11]]) + [1, 2, 5, 11]], directions8) sols = {prob.value(simulated_annealing(prob)) for i in range(100)} assert max(sols) == 999 From 1bdbb1e1ff9bf86519db90ee642c24de8801312a Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Fri, 26 Jan 2018 11:29:33 +0530 Subject: [PATCH 2/9] Visualisation of TSP. (#699) Add features like selecting cities to be part of tsp, controlling temperature and speed of animation. --- gui/tsp.py | 219 +++++++++++++++++++++++++++++++++++++++++ images/romania_map.png | Bin 0 -> 15206 bytes 2 files changed, 219 insertions(+) create mode 100644 gui/tsp.py create mode 100644 images/romania_map.png diff --git a/gui/tsp.py b/gui/tsp.py new file mode 100644 index 000000000..6a460261e --- /dev/null +++ b/gui/tsp.py @@ -0,0 +1,219 @@ +from tkinter import * +import sys +import os.path +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +from search import * +import numpy as np + +distances = {} + + +class TSP_problem(Problem): + + """ subclass of Problem to define various functions """ + + def two_opt(self, state): + """ Neighbour generating function for Traveling Salesman Problem """ + neighbour_state = state[:] + left = random.randint(0, len(neighbour_state) - 1) + right = random.randint(0, len(neighbour_state) - 1) + if left > right: + left, right = right, left + neighbour_state[left: right + 1] = reversed(neighbour_state[left: right + 1]) + return neighbour_state + + def actions(self, state): + """ action that can be excuted in given state """ + return [self.two_opt] + + def result(self, state, action): + """ result after applying the given action on the given state """ + return action(state) + + def path_cost(self, c, state1, action, state2): + """ total distance for the Traveling Salesman to be covered if in state2 """ + cost = 0 + for i in range(len(state2) - 1): + cost += distances[state2[i]][state2[i + 1]] + cost += distances[state2[0]][state2[-1]] + return cost + + def value(self, state): + """ value of path cost given negative for the given state """ + return -1 * self.path_cost(None, None, None, state) + + +class TSP_Gui(): + """ Class to create gui of Traveling Salesman using simulated annealing where one can + select cities, change speed and temperature. Distances between cities are euclidean + distances between them. + """ + + def __init__(self, root, all_cities): + self.root = root + self.vars = [] + self.frame_locations = {} + self.calculate_canvas_size() + self.button_text = StringVar() + self.button_text.set("Start") + self.all_cities = all_cities + self.frame_select_cities = Frame(self.root) + self.frame_select_cities.grid(row=1) + self.frame_canvas = Frame(self.root) + self.frame_canvas.grid(row=2) + Label(self.root, text="Map of Romania", font="Times 13 bold").grid(row=0, columnspan=10) + + def create_checkboxes(self, side=LEFT, anchor=W): + """ To select cities which are to be a part of Traveling Salesman Problem """ + + row_number = 0 + column_number = 0 + + for city in self.all_cities: + var = IntVar() + var.set(1) + Checkbutton(self.frame_select_cities, text=city, variable=var).grid( + row=row_number, column=column_number, sticky=W) + + self.vars.append(var) + column_number += 1 + if column_number == 10: + column_number = 0 + row_number += 1 + + def create_buttons(self): + """ Create start and quit button """ + + Button(self.frame_select_cities, textvariable=self.button_text, + command=self.run_traveling_salesman).grid(row=3, column=4, sticky=E + W) + Button(self.frame_select_cities, text='Quit', command=self.root.destroy).grid( + row=3, column=5, sticky=E + W) + + def run_traveling_salesman(self): + """ Choose selected citites """ + + cities = [] + for i in range(len(self.vars)): + if self.vars[i].get() == 1: + cities.append(self.all_cities[i]) + + tsp_problem = TSP_problem(cities) + self.button_text.set("Reset") + self.create_canvas(tsp_problem) + + def calculate_canvas_size(self): + """ Width and height for canvas """ + + minx, maxx = sys.maxsize, -1 * sys.maxsize + miny, maxy = sys.maxsize, -1 * sys.maxsize + + for value in romania_map.locations.values(): + minx = min(minx, value[0]) + maxx = max(maxx, value[0]) + miny = min(miny, value[1]) + maxy = max(maxy, value[1]) + + # New locations squeezed to fit inside the map of romania + for name, coordinates in romania_map.locations.items(): + self.frame_locations[name] = (coordinates[0] / 1.2 - minx + + 150, coordinates[1] / 1.2 - miny + 165) + + canvas_width = maxx - minx + 200 + canvas_height = maxy - miny + 200 + + self.canvas_width = canvas_width + self.canvas_height = canvas_height + + def create_canvas(self, problem): + """ creating map with cities """ + + map_canvas = Canvas(self.frame_canvas, width=self.canvas_width, height=self.canvas_height) + map_canvas.grid(row=3, columnspan=10) + current = Node(problem.initial) + map_canvas.delete("all") + self.romania_image = PhotoImage(file="../images/romania_map.png") + map_canvas.create_image(self.canvas_width / 2, self.canvas_height / 2, + image=self.romania_image) + cities = current.state + for city in cities: + x = self.frame_locations[city][0] + y = self.frame_locations[city][1] + map_canvas.create_oval(x - 3, y - 3, x + 3, y + 3, + fill="red", outline="red") + map_canvas.create_text(x - 15, y - 10, text=city) + + self.cost = StringVar() + Label(self.frame_canvas, textvariable=self.cost, relief="sunken").grid( + row=2, columnspan=10) + + self.speed = IntVar() + speed_scale = Scale(self.frame_canvas, from_=500, to=1, orient=HORIZONTAL, + variable=self.speed, label="Speed ----> ", showvalue=0, font="Times 11", + relief="sunken", cursor="gumby") + speed_scale.grid(row=1, columnspan=5, sticky=N + S + E + W) + self.temperature = IntVar() + temperature_scale = Scale(self.frame_canvas, from_=100, to=0, orient=HORIZONTAL, + length=200, variable=self.temperature, label="Temperature ---->", + font="Times 11", relief="sunken", showvalue=0, cursor="gumby") + + temperature_scale.grid(row=1, column=5, columnspan=5, sticky=N + S + E + W) + self.simulated_annealing_with_tunable_T(problem, map_canvas) + + def exp_schedule(k=100, lam=0.03, limit=1000): + """ One possible schedule function for simulated annealing """ + + return lambda t: (k * math.exp(-lam * t) if t < limit else 0) + + def simulated_annealing_with_tunable_T(self, problem, map_canvas, schedule=exp_schedule()): + """ Simulated annealing where temperature is taken as user input """ + + current = Node(problem.initial) + + while(1): + T = schedule(self.temperature.get()) + if T == 0: + return current.state + neighbors = current.expand(problem) + if not neighbors: + return current.state + next = random.choice(neighbors) + delta_e = problem.value(next.state) - problem.value(current.state) + if delta_e > 0 or probability(math.exp(delta_e / T)): + map_canvas.delete("poly") + + current = next + self.cost.set("Cost = " + str('%0.3f' % (-1 * problem.value(current.state)))) + points = [] + for city in current.state: + points.append(self.frame_locations[city][0]) + points.append(self.frame_locations[city][1]) + map_canvas.create_polygon(points, outline='red', width=3, fill='', tag="poly") + map_canvas.update() + map_canvas.after(self.speed.get()) + + +def main(): + all_cities = [] + for city in romania_map.locations.keys(): + distances[city] = {} + all_cities.append(city) + all_cities.sort() + + # distances['city1']['city2'] contains euclidean distance between their coordinates + for name_1, coordinates_1 in romania_map.locations.items(): + for name_2, coordinates_2 in romania_map.locations.items(): + distances[name_1][name_2] = np.linalg.norm( + [coordinates_1[0] - coordinates_2[0], coordinates_1[1] - coordinates_2[1]]) + distances[name_2][name_1] = np.linalg.norm( + [coordinates_1[0] - coordinates_2[0], coordinates_1[1] - coordinates_2[1]]) + + root = Tk() + root.title("Traveling Salesman Problem") + cities_selection_panel = TSP_Gui(root, all_cities) + cities_selection_panel.create_checkboxes() + cities_selection_panel.create_buttons() + root.mainloop() + + +if __name__ == '__main__': + main() diff --git a/images/romania_map.png b/images/romania_map.png new file mode 100644 index 0000000000000000000000000000000000000000..426c76f1e94b9ee691ab416f9da910944193f549 GIT binary patch literal 15206 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 literal 0 HcmV?d00001 From 130ad4be5c03a34a770889143db86e1496152e9f Mon Sep 17 00:00:00 2001 From: Apurv Bajaj Date: Fri, 26 Jan 2018 11:30:35 +0530 Subject: [PATCH 3/9] Add reset button for XYEnv (#698) --- gui/xy_vacuum_environment.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/gui/xy_vacuum_environment.py b/gui/xy_vacuum_environment.py index 72d2f2434..14c3abc1a 100644 --- a/gui/xy_vacuum_environment.py +++ b/gui/xy_vacuum_environment.py @@ -11,6 +11,7 @@ class Gui(VacuumEnvironment): dirty, clean or can have a wall. The user can change these at each step. """ xi, yi = (0, 0) + perceptible_distance = 1 def __init__(self, root, width=7, height=7, elements=['D', 'W']): super().__init__(width, height) @@ -122,6 +123,20 @@ def update_env(self): self.step() xf, yf = agt.location + def reset_env(self, agt): + """Resets the GUI environment to the intial state.""" + self.read_env() + for i, btn_row in enumerate(self.buttons): + for j, btn in enumerate(btn_row): + if (i != 0 and i != len(self.buttons) - 1) and (j != 0 and j != len(btn_row) - 1): + if self.some_things_at((i, j)): + for thing in self.list_things_at((i, j)): + self.delete_thing(thing) + btn.config(text='', state='normal') + self.add_thing(agt, location=(3, 3)) + self.buttons[3][3].config( + text='A', state='disabled', disabledforeground='black') + def XYReflexAgentProgram(percept): """The modified SimpleReflexAgentProgram for the GUI environment.""" @@ -151,7 +166,9 @@ def __init__(self, program=None): self.direction = Direction("up") -# TODO: Check the coordinate system. +# TODO: +# Check the coordinate system. +# Give manual choice for agent's location. def main(): """The main function.""" root = Tk() @@ -159,10 +176,9 @@ def main(): root.geometry("420x440") root.resizable(0, 0) frame = Frame(root, bg='black') - # create a reset button - # reset_button = Button(frame, text='Reset', height=2, - # width=6, padx=2, pady=2, command=None) - # reset_button.pack(side='left') + reset_button = Button(frame, text='Reset', height=2, + width=6, padx=2, pady=2) + reset_button.pack(side='left') next_button = Button(frame, text='Next', height=2, width=6, padx=2, pady=2) next_button.pack(side='left') @@ -171,6 +187,7 @@ def main(): agt = XYReflexAgent(program=XYReflexAgentProgram) env.add_thing(agt, location=(3, 3)) next_button.config(command=env.update_env) + reset_button.config(command=lambda: env.reset_env(agt)) root.mainloop() From b068a56d0a018a2047d4f9453a4df97bf8f7f76b Mon Sep 17 00:00:00 2001 From: surya saini Date: Fri, 26 Jan 2018 06:13:54 +0000 Subject: [PATCH 4/9] Solve Issue of Loading search.ipynb (#689) * rebase with master * solve error * solve error * solve error * Update search.ipynb --- search.ipynb | 370 +++++++++++++++++++-------------------------------- search.py | 90 +++++++++++++ 2 files changed, 230 insertions(+), 230 deletions(-) diff --git a/search.ipynb b/search.ipynb index 019ea8eb4..d537bd6c0 100644 --- a/search.ipynb +++ b/search.ipynb @@ -80,7 +80,9 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "%psource Problem" @@ -120,7 +122,9 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "%psource GraphProblem" @@ -136,7 +140,9 @@ { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "romania_map = UndirectedGraph(dict(\n", @@ -181,7 +187,9 @@ { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)" @@ -212,11 +220,7 @@ "name": "stdout", "output_type": "stream", "text": [ -<<<<<<< HEAD - "{'Rimnicu': (233, 410), 'Timisoara': (94, 410), 'Iasi': (473, 506), 'Neamt': (406, 537), 'Fagaras': (305, 449), 'Giurgiu': (375, 270), 'Urziceni': (456, 350), 'Mehadia': (168, 339), 'Lugoj': (165, 379), 'Sibiu': (207, 457), 'Oradea': (131, 571), 'Zerind': (108, 531), 'Craiova': (253, 288), 'Hirsova': (534, 350), 'Arad': (91, 492), 'Vaslui': (509, 444), 'Drobeta': (165, 299), 'Bucharest': (400, 327), 'Eforie': (562, 293), 'Pitesti': (320, 368)}\n" -======= "{'Oradea': (131, 571), 'Eforie': (562, 293), 'Timisoara': (94, 410), 'Hirsova': (534, 350), 'Bucharest': (400, 327), 'Rimnicu': (233, 410), 'Fagaras': (305, 449), 'Lugoj': (165, 379), 'Giurgiu': (375, 270), 'Mehadia': (168, 339), 'Pitesti': (320, 368), 'Drobeta': (165, 299), 'Craiova': (253, 288), 'Sibiu': (207, 457), 'Iasi': (473, 506), 'Urziceni': (456, 350), 'Vaslui': (509, 444), 'Neamt': (406, 537), 'Zerind': (108, 531), 'Arad': (91, 492)}\n" ->>>>>>> 8561c52d63fcaef4c0f99d997073aeb93e926e56 ] } ], @@ -235,26 +239,10 @@ { "cell_type": "code", "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "ename": "ImportError", - "evalue": "No module named 'matplotlib'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mget_ipython\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun_line_magic\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'matplotlib'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'inline'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mnetworkx\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mnx\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mmatplotlib\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpyplot\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mmatplotlib\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mlines\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/.local/lib/python3.5/site-packages/IPython/core/interactiveshell.py\u001b[0m in \u001b[0;36mrun_line_magic\u001b[0;34m(self, magic_name, line, _stack_depth)\u001b[0m\n\u001b[1;32m 2093\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'local_ns'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_getframe\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstack_depth\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mf_locals\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2094\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbuiltin_trap\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2095\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2096\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2097\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m\u001b[0m in \u001b[0;36mmatplotlib\u001b[0;34m(self, line)\u001b[0m\n", - "\u001b[0;32m~/.local/lib/python3.5/site-packages/IPython/core/magic.py\u001b[0m in \u001b[0;36m\u001b[0;34m(f, *a, **k)\u001b[0m\n\u001b[1;32m 185\u001b[0m \u001b[0;31m# but it's overkill for just that one bit of state.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 186\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmagic_deco\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 187\u001b[0;31m \u001b[0mcall\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mlambda\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mk\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mk\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 188\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 189\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcallable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/.local/lib/python3.5/site-packages/IPython/core/magics/pylab.py\u001b[0m in \u001b[0;36mmatplotlib\u001b[0;34m(self, line)\u001b[0m\n\u001b[1;32m 97\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Available matplotlib backends: %s\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mbackends_list\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 98\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 99\u001b[0;31m \u001b[0mgui\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbackend\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshell\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menable_matplotlib\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgui\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 100\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_show_matplotlib_backend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgui\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbackend\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 101\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/.local/lib/python3.5/site-packages/IPython/core/interactiveshell.py\u001b[0m in \u001b[0;36menable_matplotlib\u001b[0;34m(self, gui)\u001b[0m\n\u001b[1;32m 2964\u001b[0m \"\"\"\n\u001b[1;32m 2965\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mIPython\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcore\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpylabtools\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mpt\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2966\u001b[0;31m \u001b[0mgui\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbackend\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfind_gui_and_backend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgui\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpylab_gui_select\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2967\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2968\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mgui\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;34m'inline'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/.local/lib/python3.5/site-packages/IPython/core/pylabtools.py\u001b[0m in \u001b[0;36mfind_gui_and_backend\u001b[0;34m(gui, gui_select)\u001b[0m\n\u001b[1;32m 268\u001b[0m \"\"\"\n\u001b[1;32m 269\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 270\u001b[0;31m \u001b[0;32mimport\u001b[0m \u001b[0mmatplotlib\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 271\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 272\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mgui\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mgui\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;34m'auto'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mImportError\u001b[0m: No module named 'matplotlib'" - ] - } - ], + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "%matplotlib inline\n", "import networkx as nx\n", @@ -277,20 +265,10 @@ { "cell_type": "code", "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'nx' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# initialise a graph\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mG\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGraph\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;31m# use this while labeling nodes in the map\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mnode_labels\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mNameError\u001b[0m: name 'nx' is not defined" - ] - } - ], + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "# initialise a graph\n", "G = nx.Graph()\n", @@ -429,7 +407,9 @@ { "cell_type": "code", "execution_count": 11, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { @@ -1275,51 +1255,32 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## A* Search Heuristics Comparison\n", + "## A* Heuristics\n", "\n", - "Different Heuristics have different efficiency in solving a particular problem via A* search which is generally defined by the node of explored nodes as well as the branching factor. With the help of the Classic 8* Puzzle we can effectively visualize the difference in performance of these heuristics. \n", + "Different heuristics provide different efficiency in solving A* problems which are generally defined by the number of explored nodes as well as the branching factor. With the classic 8 puzzle we can show the efficiency of different heuristics through the number of explored nodes.\n", "\n", - "### 8-Puzzle Problem\n", + "### 8 Puzzle Problem\n", "\n", - "*8-Puzzle Problem* is another problem that is classified as NP hard for which genetic algorithms provide a better solution than any pre-existing ones.\n", + "The *8 Puzzle Problem* consists of a 3x3 tray in which the goal is to get the initial configuration to the goal state by shifting the numbered tiles into the blank space.\n", "\n", - "The *8-Puzzle Problem* consists of a *3x3 tray* in which 8 tiles numbered 1-8 are placed and the 9th tile is uncovered. The aim of the game is that given a initial placement of the tiles, we have to reach the goal state on the constraint that a tile adjacent to be the blank space can be slid into that space.\n", + "example:- \n", "\n", - "*example:*\n", - " Initial State Goal State\n", + " Initial State Goal State\n", + " | 7 | 2 | 4 | | 0 | 1 | 2 |\n", + " | 5 | 0 | 6 | | 3 | 4 | 5 |\n", + " | 8 | 3 | 1 | | 6 | 7 | 8 |\n", + " \n", + "We have a total of 9 blank tiles giving us a total of 9! initial configuration but not all of these are solvable, the solvability of a configuration can be checked by calculating the Inversion Permutation. If the total Inversion Permutation is even then the initial configuration is solvable else the initial configuration is not solvable which means that only 9!/2 initial states lead to a solution.\n", "\n", - " | 7 | 2 | 4 | | | 1 | 2 |\n", - " | 5 | | 6 | ----> | 3 | 4 | 5 |\n", - " | 8 | 3 | 1 | | 6 | 7 | 8 |\n", + "#### Heuristics :-\n", "\n", - "We have a total of 8+1(blank) tiles giving us total of 9! initial configurations but of all these configurations only 9!/2 can lead to a solution.The solvability can be checked by calculating the *Permutation Inversion* of each tile and then summing it up.\n", - "Inversion is defined as when a tile preceeds another tile with lower number.\n", - "Let's calculate the Permutation Inversion of the example shown above -\n", - " \n", - " Tile 7 -> 6 Inversions (for tile 2, 4, 5, 6, 3, 1)\n", - " Tile 2 -> 1 Inversions\n", - " Tile 4 -> 2 Inversions\n", - " Tile 5 -> 2 Inversions\n", - " Tile 6 -> 2 Inversions\n", - " Tile 8 -> 2 Inversions\n", - " Tile 3 -> 1 Inversions\n", - " Tile 1 -> 0 Inversions\n", - "Total Inversions = 16 Inversions, \n", - "Is total Inversions are even then the initial configuration is solvable else the configuration is impossible to solve.\n", - "\n", - "For example we can have a state \"724506831\" where 0 represents the empty tile.\n", + "1.) Manhattan Distance:- For the 8 puzzle problem Manhattan distance is defined as the distance of a tile from its goal state( for the tile numbered '1' in the initial configuration Manhattan distance is 4 \"2 for left and 2 for upward displacement\").\n", "\n", - "#### Heuristics:-\n", - "1.) Manhattan Distance:- For the 8 Puzzle problem \"Manhattan distance is defined as the distance of a tile from its \n", - " goal. In the example shown above the manhattan distance for the 'numbered tile 1' is 4\n", - " (2 unit left and 2 unit up).\n", + "2.) No. of Misplaced Tiles:- The heuristic calculates the number of misplaced tiles between the current state and goal state.\n", "\n", - "2.) No. of Misplaced Tiles:- This heuristics calculates the number of misplaced tile in the state from the goal \n", - " state.\n", + "3.) Sqrt of Manhattan Distance:- It calculates the square root of Manhattan distance.\n", "\n", - "3.) Sqrt of Manhattan Distance:- Uses the sqaure root of the Manhattan distance\n", - "\n", - "4.) Max Heuristic :- Score on the basis of max of Manhattan Distance and No. of Misplced tiles." + "4.) Max Heuristic:- It assign the score as max of Manhattan Distance and No. of misplaced tiles. " ] }, { @@ -1328,128 +1289,43 @@ "metadata": {}, "outputs": [], "source": [ - "# define heuristics\n", + "# heuristics for 8 Puzzle Problem\n", + "\n", "def linear(state,goal):\n", " return sum([1 if state[i] != goal[i] else 0 for i in range(8)])\n", "\n", "def manhanttan(state,goal):\n", - " index_goal = {0:[2,2], 1:[0,0], 2:[0,1], 3:[0,2], 4:[1,0], 5:[1,1], 6:[1,2], 7:[2,0], 8:[2,1]}\n", - " index_state = {}\n", - " index = [[0,0], [0,1], [0,2], [1,0], [1,1], [1,2], [2,0], [2,1], [2,2]]\n", - " x=0\n", - " y=0\n", - " for i in range(len(state)):\n", - " index_state[state[i]] = index[i]\n", - " mhd = 0\n", - " for i in range(8):\n", - " for j in range(2):\n", - " mhd = abs(index_goal[i][j] - index_state[i][j]) + mhd\n", - " return mhd\n", + "\tindex_goal = {0:[2,2], 1:[0,0], 2:[0,1], 3:[0,2], 4:[1,0], 5:[1,1], 6:[1,2], 7:[2,0], 8:[2,1]}\n", + "\tindex_state = {}\n", + "\tindex = [[0,0], [0,1], [0,2], [1,0], [1,1], [1,2], [2,0], [2,1], [2,2]]\n", + "\tx=0\n", + "\ty=0\n", + "\tfor i in range(len(state)):\n", + "\t\tindex_state[state[i]] = index[i]\n", + "\tmhd = 0\n", + "\tfor i in range(8):\n", + "\t\tfor j in range(2):\n", + "\t\t\tmhd = abs(index_goal[i][j] - index_state[i][j]) + mhd\n", + "\treturn mhd\n", "\n", "def sqrt_manhanttan(state,goal):\n", - " index_goal = {0:[2,2], 1:[0,0], 2:[0,1], 3:[0,2], 4:[1,0], 5:[1,1], 6:[1,2], 7:[2,0], 8:[2,1]}\n", - " index_state = {}\n", - " index = [[0,0], [0,1], [0,2], [1,0], [1,1], [1,2], [2,0], [2,1], [2,2]]\n", - " x=0\n", - " y=0\n", - " for i in range(len(state)):\n", - " index_state[state[i]] = index[i]\n", - " mhd = 0\n", - " for i in range(8):\n", - " for j in range(2):\n", - " mhd = (index_goal[i][j] - index_state[i][j])**2 + mhd\n", - " return math.sqrt(mhd)\n", + "\tindex_goal = {0:[2,2], 1:[0,0], 2:[0,1], 3:[0,2], 4:[1,0], 5:[1,1], 6:[1,2], 7:[2,0], 8:[2,1]}\n", + "\tindex_state = {}\n", + "\tindex = [[0,0], [0,1], [0,2], [1,0], [1,1], [1,2], [2,0], [2,1], [2,2]]\n", + "\tx=0\n", + "\ty=0\n", + "\tfor i in range(len(state)):\n", + "\t\tindex_state[state[i]] = index[i]\n", + "\tmhd = 0\n", + "\tfor i in range(8):\n", + "\t\tfor j in range(2):\n", + "\t\t\tmhd = (index_goal[i][j] - index_state[i][j])**2 + mhd\n", + "\treturn math.sqrt(mhd)\n", "\n", "def max_heuristic(state,goal):\n", - " score1 = manhanttan(state, goal)\n", - " score2 = linear(state, goal)\n", - " return max(score1, score2)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# Algorithm for 8 Puzzle problem\n", - "\n", - "def checkSolvability(state):\n", - " inversion = 0\n", - " for i in range(len(state)):\n", - " for j in range(i,len(state)):\n", - " if (state[i]>state[j] and state[j]!=0):\n", - " inversion += 1\n", - " check = True\n", - " if inversion%2 != 0:\n", - " check = False\n", - " print(check)\n", - " return check\n", - "\n", - "def getPossibleMoves(state,heuristic,goal,moves):\n", - " move = {0:[1,3], 1:[0,2,4], 2:[1,5], 3:[0,6,4], 4:[1,3,5,7], 5:[2,4,8], 6:[3,7], 7:[6,8], 8:[7,5]} # create a dictionary of moves\n", - " index = state[0].index(0)\n", - " possible_moves = []\n", - " for i in range(len(move[index])):\n", - " conf = list(state[0][:])\n", - " a = conf[index]\n", - " b = conf[move[index][i]]\n", - " conf[move[index][i]] = a\n", - " conf[index] = b\n", - " possible_moves.append(conf)\n", - " scores = []\n", - " for i in possible_moves:\n", - " scores.append(heuristic(i,goal))\n", - " scores = [x+moves for x in scores]\n", - " allowed_state = []\n", - " for i in range(len(possible_moves)):\n", - " node = []\n", - " node.append(possible_moves[i])\n", - " node.append(scores[i])\n", - " node.append(state[0])\n", - " allowed_state.append(node) \n", - " return allowed_state\n", - "\n", - "path = []\n", - "final = []\n", - "def create_path(goal,initial):\n", - " node = goal[0]\n", - " final.append(goal[0])\n", - " if goal[2] == initial:\n", - " return reversed(final)\n", - " else:\n", - " parent = goal[2]\n", - " for i in path:\n", - " if i[0] == parent:\n", - " parent = i\n", - " create_path(parent,initial)\t\n", - "\n", - "def show_path(initial):\n", - " move = []\n", - " for i in range(0,len(path)):\n", - " move.append(''.join(str(x) for x in path[i][0]))\n", - " print(\"Number of explored nodes by the following heuristic are: \", len(set(move)))\t\n", - " print(initial)\n", - " for i in reversed(final):\n", - " print(i)\n", - " return\n", - "\n", - "def solve(initial,goal,heuristic):\n", - " root = [initial,heuristic(initial,goal),'']\n", - " nodes = [] # nodes is a priority Queue based on the state score \n", - " nodes.append(root)\n", - " moves = 0\n", - " while len(nodes) != 0:\n", - " node = nodes[0]\n", - " del nodes[0]\n", - " path.append(node)\n", - " if node[0] == goal:\n", - " soln = create_path(path[-1],initial )\n", - " show_path(initial)\n", - " return \n", - " moves +=1\n", - " opened_nodes = getPossibleMoves(node,heuristic,goal,moves)\n", - " nodes = sorted(opened_nodes+nodes, key=itemgetter(1))\n" + "\tscore1 = manhanttan(state, goal)\n", + "\tscore2 = linear(state, goal)\n", + "\treturn max(score1, score2)\t\t\n" ] }, { @@ -1461,7 +1337,6 @@ "name": "stdout", "output_type": "stream", "text": [ - "Heuristics is max_heuristic\n", "True\n", "Number of explored nodes by the following heuristic are: 126\n", "[2, 4, 3, 1, 5, 6, 7, 8, 0]\n", @@ -1472,16 +1347,48 @@ "[1, 2, 3, 0, 4, 5, 7, 8, 6]\n", "[1, 2, 3, 4, 0, 5, 7, 8, 6]\n", "[1, 2, 3, 4, 5, 0, 7, 8, 6]\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 0]\n", + "Number of explored nodes by the following heuristic are: 129\n", + "[2, 4, 3, 1, 5, 6, 7, 8, 0]\n", + "[2, 4, 3, 1, 5, 0, 7, 8, 6]\n", + "[2, 4, 3, 1, 0, 5, 7, 8, 6]\n", + "[2, 0, 3, 1, 4, 5, 7, 8, 6]\n", + "[0, 2, 3, 1, 4, 5, 7, 8, 6]\n", + "[1, 2, 3, 0, 4, 5, 7, 8, 6]\n", + "[1, 2, 3, 4, 0, 5, 7, 8, 6]\n", + "[1, 2, 3, 4, 5, 0, 7, 8, 6]\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 0]\n", + "Number of explored nodes by the following heuristic are: 126\n", + "[2, 4, 3, 1, 5, 6, 7, 8, 0]\n", + "[2, 4, 3, 1, 5, 0, 7, 8, 6]\n", + "[2, 4, 3, 1, 0, 5, 7, 8, 6]\n", + "[2, 0, 3, 1, 4, 5, 7, 8, 6]\n", + "[0, 2, 3, 1, 4, 5, 7, 8, 6]\n", + "[1, 2, 3, 0, 4, 5, 7, 8, 6]\n", + "[1, 2, 3, 4, 0, 5, 7, 8, 6]\n", + "[1, 2, 3, 4, 5, 0, 7, 8, 6]\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 0]\n", + "Number of explored nodes by the following heuristic are: 139\n", + "[2, 4, 3, 1, 5, 6, 7, 8, 0]\n", + "[2, 4, 3, 1, 5, 0, 7, 8, 6]\n", + "[2, 4, 3, 1, 0, 5, 7, 8, 6]\n", + "[2, 0, 3, 1, 4, 5, 7, 8, 6]\n", + "[0, 2, 3, 1, 4, 5, 7, 8, 6]\n", + "[1, 2, 3, 0, 4, 5, 7, 8, 6]\n", + "[1, 2, 3, 4, 0, 5, 7, 8, 6]\n", + "[1, 2, 3, 4, 5, 0, 7, 8, 6]\n", "[1, 2, 3, 4, 5, 6, 7, 8, 0]\n" ] } ], "source": [ - "goal_state = [1,2,3,4,5,6,7,8,0] # define the goal state\n", - "initial_state = [2,4,3,1,5,6,7,8,0] # define the initial state\n", - "print(\"Heuristics is max_heuristic\")\n", - "checkSolvability(initial_state)\n", - "solve(initial_state,goal_state,max_heuristic) # to check the different heuristics change the function name in solve" + "# Solving the puzzle \n", + "puzzle = EightPuzzle()\n", + "puzzle.checkSolvability([2,4,3,1,5,6,7,8,0]) # checks whether the initialized configuration is solvable or not\n", + "puzzle.solve([2,4,3,1,5,6,7,8,0],[1,2,3,4,5,6,7,8,0],max_heuristic) # Max_heuristic\n", + "puzzle.solve([2,4,3,1,5,6,7,8,0],[1,2,3,4,5,6,7,8,0],linear) # Linear\n", + "puzzle.solve([2,4,3,1,5,6,7,8,0],[1,2,3,4,5,6,7,8,0],manhanttan) # Manhattan\n", + "puzzle.solve([2,4,3,1,5,6,7,8,0],[1,2,3,4,5,6,7,8,0],sqrt_manhanttan) # Sqrt_manhattan" ] }, { @@ -1594,7 +1501,9 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "%psource genetic_algorithm" @@ -1633,7 +1542,9 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "%psource reproduce" @@ -1651,7 +1562,9 @@ { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "%psource mutate" @@ -1669,7 +1582,9 @@ { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "%psource init_population" @@ -1710,8 +1625,10 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, + "execution_count": 6, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "edges = {\n", @@ -1733,14 +1650,14 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[['R', 'G', 'G', 'R'], ['G', 'R', 'G', 'G'], ['G', 'G', 'G', 'G'], ['R', 'G', 'G', 'G'], ['R', 'G', 'G', 'R'], ['G', 'R', 'G', 'R'], ['G', 'G', 'G', 'R'], ['G', 'R', 'G', 'R']]\n" + "[['R', 'G', 'G', 'R'], ['R', 'G', 'R', 'R'], ['G', 'R', 'G', 'R'], ['R', 'G', 'R', 'G'], ['G', 'R', 'R', 'G'], ['G', 'R', 'G', 'R'], ['G', 'R', 'R', 'R'], ['R', 'G', 'G', 'G']]\n" ] } ], @@ -1760,8 +1677,10 @@ }, { "cell_type": "code", - "execution_count": 14, - "metadata": {}, + "execution_count": 8, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def fitness(c):\n", @@ -1777,14 +1696,14 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "['G', 'R', 'G', 'R']\n" + "['R', 'G', 'R', 'G']\n" ] } ], @@ -1802,7 +1721,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -1847,14 +1766,14 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[[6, 7, 3, 6, 3, 0, 1, 4], [7, 1, 4, 1, 5, 2, 0, 0], [1, 4, 7, 0, 0, 2, 5, 2], [2, 0, 3, 7, 5, 7, 0, 0], [6, 3, 1, 7, 5, 6, 3, 0]]\n" + "[[0, 2, 7, 1, 7, 3, 2, 4], [2, 7, 5, 4, 4, 5, 2, 0], [7, 1, 6, 0, 1, 3, 0, 2], [0, 3, 6, 1, 3, 0, 5, 4], [0, 4, 6, 4, 7, 4, 1, 6]]\n" ] } ], @@ -1878,8 +1797,10 @@ }, { "cell_type": "code", - "execution_count": 7, - "metadata": {}, + "execution_count": 12, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def fitness(q):\n", @@ -1908,20 +1829,20 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[3, 5, 7, 2, 0, 6, 4, 1]\n", - "28\n" + "[5, 0, 6, 3, 7, 4, 1, 3]\n", + "26\n" ] } ], "source": [ - "solution = genetic_algorithm(population, fitness, f_thres=28, gene_pool=range(8))\n", + "solution = genetic_algorithm(population, fitness, f_thres=25, gene_pool=range(8))\n", "print(solution)\n", "print(fitness(solution))" ] @@ -1939,13 +1860,6 @@ "source": [ "With that this tutorial on the genetic algorithm comes to an end. Hope you found this guide helpful!" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -1964,11 +1878,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", -<<<<<<< HEAD "version": "3.5.4rc1" -======= - "version": "3.5.2" ->>>>>>> 8561c52d63fcaef4c0f99d997073aeb93e926e56 }, "widgets": { "state": { diff --git a/search.py b/search.py index 8bf742489..726001dd1 100644 --- a/search.py +++ b/search.py @@ -17,6 +17,7 @@ import bisect from operator import itemgetter + infinity = float('inf') # ______________________________________________________________________________ @@ -400,6 +401,95 @@ def astar_search(problem, h=None): h = memoize(h or problem.h, 'h') return best_first_graph_search(problem, lambda n: n.path_cost + h(n)) +# ______________________________________________________________________________ +# A* heuristics + +class EightPuzzle(): + + def __init__(self): + self.path = [] + self.final = [] + + def checkSolvability(self, state): + inversion = 0 + for i in range(len(state)): + for j in range(i,len(state)): + if (state[i]>state[j] and state[j]!=0): + inversion += 1 + check = True + if inversion%2 != 0: + check = False + print(check) + + def getPossibleMoves(self,state,heuristic,goal,moves): + move = {0:[1,3], 1:[0,2,4], 2:[1,5], 3:[0,6,4], 4:[1,3,5,7], 5:[2,4,8], 6:[3,7], 7:[6,8], 8:[7,5]} # create a dictionary of moves + index = state[0].index(0) + possible_moves = [] + for i in range(len(move[index])): + conf = list(state[0][:]) + a = conf[index] + b = conf[move[index][i]] + conf[move[index][i]] = a + conf[index] = b + possible_moves.append(conf) + scores = [] + for i in possible_moves: + scores.append(heuristic(i,goal)) + scores = [x+moves for x in scores] + allowed_state = [] + for i in range(len(possible_moves)): + node = [] + node.append(possible_moves[i]) + node.append(scores[i]) + node.append(state[0]) + allowed_state.append(node) + return allowed_state + + + def create_path(self,goal,initial): + node = goal[0] + self.final.append(goal[0]) + if goal[2] == initial: + return reversed(self.final) + else: + parent = goal[2] + for i in self.path: + if i[0] == parent: + parent = i + self.create_path(parent,initial) + + def show_path(self,initial): + move = [] + for i in range(0,len(self.path)): + move.append(''.join(str(x) for x in self.path[i][0])) + + print("Number of explored nodes by the following heuristic are: ", len(set(move))) + print(initial) + for i in reversed(self.final): + print(i) + + del self.path[:] + del self.final[:] + return + + def solve(self,initial,goal,heuristic): + root = [initial,heuristic(initial,goal),''] + nodes = [] # nodes is a priority Queue based on the state score + nodes.append(root) + moves = 0 + while len(nodes) != 0: + node = nodes[0] + del nodes[0] + self.path.append(node) + if node[0] == goal: + soln = self.create_path(self.path[-1],initial ) + self.show_path(initial) + return + moves +=1 + opened_nodes = self.getPossibleMoves(node,heuristic,goal,moves) + nodes = sorted(opened_nodes+nodes, key=itemgetter(1)) + + # ______________________________________________________________________________ # Other search algorithms From 7c5bcdda2563248c4dc454d02026fc280fc9b066 Mon Sep 17 00:00:00 2001 From: Rishav1 Date: Mon, 29 Jan 2018 08:33:43 +0530 Subject: [PATCH 5/9] Fixed issue #700 (#701) --- search-4e.ipynb | 1 + 1 file changed, 1 insertion(+) diff --git a/search-4e.ipynb b/search-4e.ipynb index c7286c88b..73da69119 100644 --- a/search-4e.ipynb +++ b/search-4e.ipynb @@ -825,6 +825,7 @@ " def __init__(self, initial, LIFO=False):\n", " \"\"\"Initialize Frontier with an initial Node.\n", " If LIFO is True, pop from the end first; otherwise from front first.\"\"\"\n", + " super(FrontierQ, self).__init__()\n", " self.LIFO = LIFO\n", " self.add(initial)\n", " \n", From 0a0d64601738851e298bb9b6d958269e0b0c4526 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Wed, 7 Feb 2018 02:12:01 +0530 Subject: [PATCH 6/9] Explanation of genetic algorithm functions with an example. Fixed #696 (#702) * Added explanation of Genetic Algorithm functions using an example * Added GUI version of genetic algorithm example (phrase generation problem) --- gui/genetic_algorithm_example.py | 172 ++++ search.ipynb | 1403 +++++++++++++++++++++--------- 2 files changed, 1144 insertions(+), 431 deletions(-) create mode 100644 gui/genetic_algorithm_example.py diff --git a/gui/genetic_algorithm_example.py b/gui/genetic_algorithm_example.py new file mode 100644 index 000000000..418da02e9 --- /dev/null +++ b/gui/genetic_algorithm_example.py @@ -0,0 +1,172 @@ +# author: ad71 +# A simple program that implements the solution to the phrase generation problem using +# genetic algorithms as given in the search.ipynb notebook. +# +# Type on the home screen to change the target phrase +# Click on the slider to change genetic algorithm parameters +# Click 'GO' to run the algorithm with the specified variables +# Displays best individual of the current generation +# Displays a progress bar that indicates the amount of completion of the algorithm +# Displays the first few individuals of the current generation + +import sys +import time +import random +import os.path +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) + +from tkinter import * +from tkinter import ttk + +import search +from utils import argmax + +LARGE_FONT = ('Verdana', 12) +EXTRA_LARGE_FONT = ('Consolas', 36, 'bold') + +canvas_width = 800 +canvas_height = 600 + +black = '#000000' +white = '#ffffff' +p_blue = '#042533' +lp_blue = '#0c394c' + +# genetic algorithm variables +# feel free to play around with these +target = 'Genetic Algorithm' # the phrase to be generated +max_population = 100 # number of samples in each population +mutation_rate = 0.1 # probability of mutation +f_thres = len(target) # fitness threshold +ngen = 1200 # max number of generations to run the genetic algorithm + +generation = 0 # counter to keep track of generation number + +u_case = [chr(x) for x in range(65, 91)] # list containing all uppercase characters +l_case = [chr(x) for x in range(97, 123)] # list containing all lowercase characters +punctuations1 = [chr(x) for x in range(33, 48)] # lists containing punctuation symbols +punctuations2 = [chr(x) for x in range(58, 65)] +punctuations3 = [chr(x) for x in range(91, 97)] +numerals = [chr(x) for x in range(48, 58)] # list containing numbers + +# extend the gene pool with the required lists and append the space character +gene_pool = [] +gene_pool.extend(u_case) +gene_pool.extend(l_case) +gene_pool.append(' ') + +# callbacks to update global variables from the slider values +def update_max_population(slider_value): + global max_population + max_population = slider_value + +def update_mutation_rate(slider_value): + global mutation_rate + mutation_rate = slider_value + +def update_f_thres(slider_value): + global f_thres + f_thres = slider_value + +def update_ngen(slider_value): + global ngen + ngen = slider_value + +# fitness function +def fitness_fn(_list): + fitness = 0 + # create string from list of characters + phrase = ''.join(_list) + # add 1 to fitness value for every matching character + for i in range(len(phrase)): + if target[i] == phrase[i]: + fitness += 1 + return fitness + +# function to bring a new frame on top +def raise_frame(frame, init=False, update_target=False, target_entry=None, f_thres_slider=None): + frame.tkraise() + global target + if update_target and target_entry is not None: + target = target_entry.get() + f_thres_slider.config(to=len(target)) + if init: + population = search.init_population(max_population, gene_pool, len(target)) + genetic_algorithm_stepwise(population) + +# defining root and child frames +root = Tk() +f1 = Frame(root) +f2 = Frame(root) + +# pack frames on top of one another +for frame in (f1, f2): + frame.grid(row=0, column=0, sticky='news') + +# Home Screen (f1) widgets +target_entry = Entry(f1, font=('Consolas 46 bold'), exportselection=0, foreground=p_blue, justify=CENTER) +target_entry.insert(0, target) +target_entry.pack(expand=YES, side=TOP, fill=X, padx=50) +target_entry.focus_force() + +max_population_slider = Scale(f1, from_=3, to=1000, orient=HORIZONTAL, label='Max population', command=lambda value: update_max_population(int(value))) +max_population_slider.set(max_population) +max_population_slider.pack(expand=YES, side=TOP, fill=X, padx=40) + +mutation_rate_slider = Scale(f1, from_=0, to=1, orient=HORIZONTAL, label='Mutation rate', resolution=0.0001, command=lambda value: update_mutation_rate(float(value))) +mutation_rate_slider.set(mutation_rate) +mutation_rate_slider.pack(expand=YES, side=TOP, fill=X, padx=40) + +f_thres_slider = Scale(f1, from_=0, to=len(target), orient=HORIZONTAL, label='Fitness threshold', command=lambda value: update_f_thres(int(value))) +f_thres_slider.set(f_thres) +f_thres_slider.pack(expand=YES, side=TOP, fill=X, padx=40) + +ngen_slider = Scale(f1, from_=1, to=5000, orient=HORIZONTAL, label='Max number of generations', command=lambda value: update_ngen(int(value))) +ngen_slider.set(ngen) +ngen_slider.pack(expand=YES, side=TOP, fill=X, padx=40) + +button = ttk.Button(f1, text='RUN', command=lambda: raise_frame(f2, init=True, update_target=True, target_entry=target_entry, f_thres_slider=f_thres_slider)).pack(side=BOTTOM, pady=50) + +# f2 widgets +canvas = Canvas(f2, width=canvas_width, height=canvas_height) +canvas.pack(expand=YES, fill=BOTH, padx=20, pady=15) +button = ttk.Button(f2, text='EXIT', command=lambda: raise_frame(f1)).pack(side=BOTTOM, pady=15) + +# function to run the genetic algorithm and update text on the canvas +def genetic_algorithm_stepwise(population): + root.title('Genetic Algorithm') + for generation in range(ngen): + # generating new population after selecting, recombining and mutating the existing population + population = [search.mutate(search.recombine(*search.select(2, population, fitness_fn)), gene_pool, mutation_rate) for i in range(len(population))] + # genome with the highest fitness in the current generation + current_best = ''.join(argmax(population, key=fitness_fn)) + # collecting first few examples from the current population + members = [''.join(x) for x in population][:48] + + # clear the canvas + canvas.delete('all') + # displays current best on top of the screen + canvas.create_text(canvas_width / 2, 40, fill=p_blue, font='Consolas 46 bold', text=current_best) + + # displaying a part of the population on the screen + for i in range(len(members) // 3): + canvas.create_text((canvas_width * .175), (canvas_height * .25 + (25 * i)), fill=lp_blue, font='Consolas 16', text=members[3 * i]) + canvas.create_text((canvas_width * .500), (canvas_height * .25 + (25 * i)), fill=lp_blue, font='Consolas 16', text=members[3 * i + 1]) + canvas.create_text((canvas_width * .825), (canvas_height * .25 + (25 * i)), fill=lp_blue, font='Consolas 16', text=members[3 * i + 2]) + + # displays current generation number + canvas.create_text((canvas_width * .5), (canvas_height * 0.95), fill=p_blue, font='Consolas 18 bold', text=f'Generation {generation}') + + # displays blue bar that indicates current maximum fitness compared to maximum possible fitness + scaling_factor = fitness_fn(current_best) / len(target) + canvas.create_rectangle(canvas_width * 0.1, 90, canvas_width * 0.9, 100, outline=p_blue) + canvas.create_rectangle(canvas_width * 0.1, 90, canvas_width * 0.1 + scaling_factor * canvas_width * 0.8, 100, fill=lp_blue) + canvas.update() + + # checks for completion + fittest_individual = search.fitness_threshold(fitness_fn, f_thres, population) + if fittest_individual: + break + +raise_frame(f1) +root.mainloop() \ No newline at end of file diff --git a/search.ipynb b/search.ipynb index d537bd6c0..96ac09aa7 100644 --- a/search.ipynb +++ b/search.ipynb @@ -15,11 +15,13 @@ "cell_type": "code", "execution_count": 1, "metadata": { + "collapsed": true, "scrolled": true }, "outputs": [], "source": [ "from search import *\n", + "from notebook import psource\n", "\n", "# Needed to hide warnings in the matplotlib sections\n", "import warnings\n", @@ -1286,7 +1288,9 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# heuristics for 8 Puzzle Problem\n", @@ -1501,12 +1505,123 @@ { "cell_type": "code", "execution_count": 2, - "metadata": { - "collapsed": true - }, - "outputs": [], + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def genetic_algorithm(population, fitness_fn, gene_pool=[0, 1], f_thres=None, ngen=1000, pmut=0.1):\n",
+       "    """[Figure 4.8]"""\n",
+       "    for i in range(ngen):\n",
+       "        population = [mutate(recombine(*select(2, population, fitness_fn)), gene_pool, pmut)\n",
+       "                      for i in range(len(population))]\n",
+       "\n",
+       "        fittest_individual = fitness_threshold(fitness_fn, f_thres, population)\n",
+       "        if fittest_individual:\n",
+       "            return fittest_individual\n",
+       "\n",
+       "\n",
+       "    return argmax(population, key=fitness_fn)\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "%psource genetic_algorithm" + "psource(genetic_algorithm)" ] }, { @@ -1536,65 +1651,904 @@ "source": [ "For each generation, the algorithm updates the population. First it calculates the fitnesses of the individuals, then it selects the most fit ones and finally crosses them over to produce offsprings. There is a chance that the offspring will be mutated, given by `pmut`. If at the end of the generation an individual meets the fitness threshold, the algorithm halts and returns that individual.\n", "\n", - "The function of mating is accomplished by the method `reproduce`:" + "The function of mating is accomplished by the method `recombine`:" ] }, { "cell_type": "code", "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def recombine(x, y):\n",
+       "    n = len(x)\n",
+       "    c = random.randrange(0, n)\n",
+       "    return x[:c] + y[c:]\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(recombine)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The method picks at random a point and merges the parents (`x` and `y`) around it.\n", + "\n", + "The mutation is done in the method `mutate`:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def mutate(x, gene_pool, pmut):\n",
+       "    if random.uniform(0, 1) >= pmut:\n",
+       "        return x\n",
+       "\n",
+       "    n = len(x)\n",
+       "    g = len(gene_pool)\n",
+       "    c = random.randrange(0, n)\n",
+       "    r = random.randrange(0, g)\n",
+       "\n",
+       "    new_gene = gene_pool[r]\n",
+       "    return x[:c] + [new_gene] + x[c+1:]\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(mutate)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We pick a gene in `x` to mutate and a gene from the gene pool to replace it with.\n", + "\n", + "To help initializing the population we have the helper function `init_population`\":" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def init_population(pop_number, gene_pool, state_length):\n",
+       "    """Initializes population for genetic algorithm\n",
+       "    pop_number  :  Number of individuals in population\n",
+       "    gene_pool   :  List of possible values for individuals\n",
+       "    state_length:  The length of each individual"""\n",
+       "    g = len(gene_pool)\n",
+       "    population = []\n",
+       "    for i in range(pop_number):\n",
+       "        new_individual = [gene_pool[random.randrange(0, g)] for j in range(state_length)]\n",
+       "        population.append(new_individual)\n",
+       "\n",
+       "    return population\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(init_population)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function takes as input the number of individuals in the population, the gene pool and the length of each individual/state. It creates individuals with random genes and returns the population when done." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Explanation\n", + "\n", + "Before we solve problems using the genetic algorithm, we will explain how to intuitively understand the algorithm using a trivial exmaple.\n", + "\n", + "#### Generating Phrases\n", + "\n", + "In this problem, we use a genetic algorithm to generate a particular target phrase from a population of random strings. This is a classic example that helps build intuition about how to use this algorithm in other problems as well. Before we break the problem down, let us try to brute force the solution. Let us say that we want to generate the phrase \"genetic algorithm\". The phrase is 17 characters long. We can use any character from the 26 lowercase characters and the space character. To generate a random phrase of length 17, each space can be filled in 27 ways. So the total number of possible phrases is\n", + "\n", + "$$ 27^{17} = 2153693963075557766310747 $$\n", + "\n", + "which is a massive number. If we wanted to generate the phrase \"Genetic Algorithm\", we would also have to include all the 26 uppercase characters into consideration thereby increasing the sample space from 27 characters to 53 characters and the total number of possible phrases then would be\n", + "\n", + "$$ 53^{17} = 205442259656281392806087233013 $$\n", + "\n", + "If we wanted to include punctuations and numerals into the sample space, we would have further complicated an already impossible problem. Hence, brute forcing is not an option. Now we'll apply the genetic algorithm and see how it significantly reduces the search space. We essentially want to *evolve* our population of random strings so that they better approximate the target phrase as the number of generations increase. Genetic algorithms work on the principle of Darwinian Natural Selection according to which, there are three key concepts that need to be in place for evolution to happen. They are:\n", + "\n", + "1. Heredity : There must be a process in place by which children receive the properties of their parents.
\n", + "For this particular problem, two strings from the population will be chosen as parents and will be split at a random index and recombined as described in the `recombine` function to create a child. This child string will then be added to the new generation.\n", + "
\n",
+    "
\n", + "2. Variation : There must be a variety of traits present in the population or a means with which to introduce variation.
If there is no variation in the sample space, we might never reach the global optimum. To ensure that there is enough variation, we can initialize a large population, but this gets computationally expensive as the population gets larger. Hence, we often use another method called mutation. In this method, we randomly change one or more characters of some strings in the population based on a predefined probability value called the mutation rate or mutation probability as described in the `mutate` function. The mutation rate is usually kept quite low. A mutation rate of zero fails to introduce variation in the population and a high mutation rate (say 50%) is as good as a coin flip and the population fails to benefit from the previous recombinations. An optimum balance has to be maintained between population size and mutation rate so as to reduce the computational cost as well as have sufficient variation in the population.\n", + "
\n",
+    "
\n", + "3. Selection : There must be some mechanism by which some members of the population have the opportunity to be parents and pass down their genetic information and some do not. This is typically referred to as \"survival of the fittest\".
\n", + "There has to be some way of determining which phrases in our population have a better chance of eventually evolving into the target phrase. This is done by introducing a fitness function that calculates how close the generated phrase is to the target phrase. The function will simply return a scalar value corresponding to the number of matching characters between the generated phrase and the target phrase." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before solving the problem, we first need to define our target phrase." + ] + }, + { + "cell_type": "code", + "execution_count": 33, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "%psource reproduce" + "target = 'Genetic Algorithm'" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "We then need to define our gene pool, i.e the elements which an individual from the population might comprise of. Here, the gene pool contains all uppercase and lowercase letters of the English alphabet and the space character." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# The ASCII values of uppercase characters ranges from 65 to 91\n", + "u_case = [chr(x) for x in range(65, 91)]\n", + "# The ASCII values of lowercase characters ranges from 97 to 123\n", + "l_case = [chr(x) for x in range(97, 123)]\n", + "\n", + "gene_pool = []\n", + "gene_pool.extend(u_case) # adds the uppercase list to the gene pool\n", + "gene_pool.extend(l_case) # adds the lowercase list to the gene pool\n", + "gene_pool.append(' ') # adds the space character to the gene pool" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The method picks at random a point and merges the parents (`x` and `y`) around it.\n", + "We now need to define the maximum size of each population. Larger populations have more variation but are computationally more expensive to run algorithms on." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "max_population = 100" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As our population is not very large, we can afford to keep a relatively large mutation rate." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "mutation_rate = 0.07 # 7%" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Great! Now, we need to define the most important metric for the genetic algorithm, i.e the fitness function. This will simply return the number of matching characters between the generated sample and the target phrase." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def fitness_fn(sample):\n", + " # initialize fitness to 0\n", + " fitness = 0\n", + " for i in range(len(sample)):\n", + " # increment fitness by 1 for every matching character\n", + " if sample[i] == target[i]:\n", + " fitness += 1\n", + " return fitness" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before we run our genetic algorithm, we need to initialize a random population. We will use the `init_population` function to do this. We need to pass in the maximum population size, the gene pool and the length of each individual, which in this case will be the same as the length of the target phrase." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "population = init_population(max_population, gene_pool, len(target))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now define how the individuals in the population should change as the number of generations increases. First, the `select` function will be run on the population to select *two* individuals with high fitness values. These will be the parents which will then be recombined using the `recombine` function to generate the child." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "parents = select(2, population, fitness_fn) " + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# The recombine function takes two parents as arguments, so we need to unpack the previous variable\n", + "child = recombine(*parents)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to apply a mutation according to the mutation rate. We call the `mutate` function on the child with the gene pool and mutation rate as the additional arguments." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "child = mutate(child, gene_pool, mutation_rate)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The above lines can be condensed into\n", "\n", - "The mutation is done in the method `mutate`:" + "`child = mutate(recombine(*select(2, population, fitness_fn)), gene_pool, mutation_rate)`\n", + "\n", + "And, we need to do this `for` every individual in the current population to generate the new population." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 42, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "%psource mutate" + "population = [mutate(recombine(*select(2, population, fitness_fn)), gene_pool, mutation_rate) for i in range(len(population))]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We pick a gene in `x` to mutate and a gene from the gene pool to replace it with.\n", + "The individual with the highest fitness can then be found using the `max` function." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "current_best = max(population, key=fitness_fn)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's print this out" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['j', 'F', 'm', 'F', 'N', 'i', 'c', 'v', 'm', 'j', 'V', 'o', 'd', 'r', 't', 'V', 'H']\n" + ] + } + ], + "source": [ + "print(current_best)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that this is a list of characters. This can be converted to a string using the join function" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "jFmFNicvmjVodrtVH\n" + ] + } + ], + "source": [ + "current_best_string = ''.join(current_best)\n", + "print(current_best_string)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now need to define the conditions to terminate the algorithm. This can happen in two ways\n", + "1. Termination after a predefined number of generations\n", + "2. Termination when the fitness of the best individual of the current generation reaches a predefined threshold value.\n", "\n", - "To help initializing the population we have the helper function `init_population`\":" + "We define these variables below" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 46, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "ngen = 1200 # maximum number of generations\n", + "# we set the threshold fitness equal to the length of the target phrase\n", + "# i.e the algorithm only terminates whne it has got all the characters correct \n", + "# or it has completed 'ngen' number of generations\n", + "f_thres = len(target)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "To generate `ngen` number of generations, we run a `for` loop `ngen` number of times. After each generation, we calculate the fitness of the best individual of the generation and compare it to the value of `f_thres` using the `fitness_threshold` function. After every generation, we print out the best individual of the generation and the corresponding fitness value. Lets now write a function to do this." + ] + }, + { + "cell_type": "code", + "execution_count": 47, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "%psource init_population" + "def genetic_algorithm_stepwise(population, fitness_fn, gene_pool=[0, 1], f_thres=None, ngen=1200, pmut=0.1):\n", + " for generation in range(ngen):\n", + " population = [mutate(recombine(*select(2, population, fitness_fn)), gene_pool, pmut) for i in range(len(population))]\n", + " # stores the individual genome with the highest fitness in the current population\n", + " current_best = ''.join(max(population, key=fitness_fn))\n", + " print(f'Current best: {current_best}\\t\\tGeneration: {str(generation)}\\t\\tFitness: {fitness_fn(current_best)}\\r', end='')\n", + " \n", + " # compare the fitness of the current best individual to f_thres\n", + " fittest_individual = fitness_threshold(fitness_fn, f_thres, population)\n", + " \n", + " # if fitness is greater than or equal to f_thres, we terminate the algorithm\n", + " if fittest_individual:\n", + " return fittest_individual, generation\n", + " return max(population, key=fitness_fn) , generation " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The function takes as input the number of individuals in the population, the gene pool and the length of each individual/state. It creates individuals with random genes and returns the population when done." + "The function defined above is essentially the same as the one defined in `search.py` with the added functionality of printing out the data of each generation." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def genetic_algorithm(population, fitness_fn, gene_pool=[0, 1], f_thres=None, ngen=1000, pmut=0.1):\n",
+       "    """[Figure 4.8]"""\n",
+       "    for i in range(ngen):\n",
+       "        population = [mutate(recombine(*select(2, population, fitness_fn)), gene_pool, pmut)\n",
+       "                      for i in range(len(population))]\n",
+       "\n",
+       "        fittest_individual = fitness_threshold(fitness_fn, f_thres, population)\n",
+       "        if fittest_individual:\n",
+       "            return fittest_individual\n",
+       "\n",
+       "\n",
+       "    return argmax(population, key=fitness_fn)\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(genetic_algorithm)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have defined all the required functions and variables. Let's now create a new population and test the function we wrote above." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current best: Genetic Algorithm\t\tGeneration: 472\t\tFitness: 17\r" + ] + } + ], + "source": [ + "population = init_population(max_population, gene_pool, len(target))\n", + "solution, generations = genetic_algorithm_stepwise(population, fitness_fn, gene_pool, f_thres, ngen, mutation_rate)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The genetic algorithm was able to converge!\n", + "We implore you to rerun the above cell and play around with `target, max_population, f_thres, ngen` etc parameters to get a better intuition of how the algorithm works. To summarize, if we can define the problem states in simple array format and if we can create a fitness function to gauge how good or bad our approximate solutions are, there is a high chance that we can get a satisfactory solution using a genetic algorithm. \n", + "- There is also a better GUI version of this program `genetic_algorithm_example.py` in the GUI folder for you to play around with." ] }, { @@ -1878,420 +2832,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.4rc1" - }, - "widgets": { - "state": { - "013d8df0a2ab4899b09f83aa70ce5d50": { - "views": [] - }, - "01ee7dc2239c4b0095710436453b362d": { - "views": [] - }, - "04d594ae6a704fc4b16895e6a7b85270": { - "views": [] - }, - "052ea3e7259346a4b022ec4fef1fda28": { - "views": [ - { - "cell_index": 32 - } - ] - }, - "0ade4328785545c2b66d77e599a3e9da": { - "views": [ - { - "cell_index": 29 - } - ] - }, - "0b94d8de6b4e47f89b0382b60b775cbd": { - "views": [] - }, - "0c63dcc0d11a451ead31a4c0c34d7b43": { - "views": [] - }, - "0d91be53b6474cdeac3239fdffeab908": { - "views": [ - { - "cell_index": 39 - } - ] - }, - "0fe9c3b9b1264d4abd22aef40a9c1ab9": { - "views": [] - }, - "10fd06131b05455d9f0a98072d7cebc6": { - "views": [] - }, - "1193eaa60bb64cb790236d95bf11f358": { - "views": [ - { - "cell_index": 38 - } - ] - }, - "11b596cbf81a47aabccae723684ac3a5": { - "views": [] - }, - "127ae5faa86f41f986c39afb320f2298": { - "views": [] - }, - "16a9167ec7b4479e864b2a32e40825a1": { - "views": [ - { - "cell_index": 39 - } - ] - }, - "170e2e101180413f953a192a41ecbfcc": { - "views": [] - }, - "181efcbccf89478792f0e38a25500e51": { - "views": [] - }, - "1894a28092604d69b0d7d465a3b165b1": { - "views": [] - }, - "1a56cc2ab5ae49ea8bf2a3f6ca2b1c36": { - "views": [] - }, - "1cfd8f392548467696d8cd4fc534a6b4": { - "views": [] - }, - "1e395e67fdec406f8698aa5922764510": { - "views": [] - }, - "23509c6536404e96985220736d286183": { - "views": [] - }, - "23bffaca1206421fb9ea589126e35438": { - "views": [] - }, - "25330d0b799e4f02af5e510bc70494cf": { - "views": [] - }, - "2ab8bf4795ac4240b70e1a94e14d1dd6": { - "views": [ - { - "cell_index": 30 - } - ] - }, - "2bd48f1234e4422aaedecc5815064181": { - "views": [] - }, - "2d3a082066304c8ebf2d5003012596b4": { - "views": [] - }, - "2dc962f16fd143c1851aaed0909f3963": { - "views": [ - { - "cell_index": 35 - } - ] - }, - "2f659054242a453da5ea0884de996008": { - "views": [] - }, - "30a214881db545729c1b883878227e95": { - "views": [] - }, - "3275b81616424947be98bf8fd3cd7b82": { - "views": [] - }, - "330b52bc309d4b6a9b188fd9df621180": { - "views": [] - }, - "3320648123f44125bcfda3b7c68febcf": { - "views": [] - }, - "338e3b1562e747f197ab3ceae91e371f": { - "views": [] - }, - "34658e2de2894f01b16cf89905760f14": { - "views": [ - { - "cell_index": 39 - } - ] - }, - "352f5fd9f698460ea372c6af57c5b478": { - "views": [] - }, - "35dc16b828a74356b56cd01ff9ddfc09": { - "views": [] - }, - "3805ce2994364bd1b259373d8798cc7a": { - "views": [] - }, - "3d1f1f899cfe49aaba203288c61686ac": { - "views": [] - }, - "3d7e943e19794e29b7058eb6bbe23c66": { - "views": [] - }, - "3f6652b3f85740949b7711fbcaa509ba": { - "views": [] - }, - "43e48664a76342c991caeeb2d5b17a49": { - "views": [ - { - "cell_index": 35 - } - ] - }, - "4662dec8595f45fb9ae061b2bdf44427": { - "views": [] - }, - "47ae3d2269d94a95a567be21064eb98a": { - "views": [] - }, - "49c49d665ba44746a1e1e9dc598bc411": { - "views": [ - { - "cell_index": 39 - } - ] - }, - "4a1c43b035f644699fd905d5155ad61f": { - "views": [ - { - "cell_index": 39 - } - ] - }, - "4eb88b6f6b4241f7b755f69b9e851872": { - "views": [] - }, - "4fbb3861e50f41c688e9883da40334d4": { - "views": [] - }, - "52d76de4ee8f4487b335a4a11726fbce": { - "views": [] - }, - "53eccc8fc0ad461cb8277596b666f32a": { - "views": [ - { - "cell_index": 29 - } - ] - }, - "54d3a6067b594ad08907ce059d9f4a41": { - "views": [] - }, - "612530d3edf8443786b3093ab612f88b": { - "views": [] - }, - "613a133b6d1f45e0ac9c5c270bc408e0": { - "views": [] - }, - "636caa7780614389a7f52ad89ea1c6e8": { - "views": [ - { - "cell_index": 39 - } - ] - }, - "63aa621196294629b884c896b6a034d8": { - "views": [] - }, - "66d1d894cc7942c6a91f0630fc4321f9": { - "views": [] - }, - "6775928a174b43ecbe12608772f1cb05": { - "views": [] - }, - "6bce621c90d543bca50afbe0c489a191": { - "views": [] - }, - "6ebbb8c7ec174c15a6ee79a3c5b36312": { - "views": [] - }, - "743219b9d37e4f47a5f777bb41ad0a96": { - "views": [ - { - "cell_index": 29 - } - ] - }, - "774f464794cc409ca6d1106bcaac0cf1": { - "views": [] - }, - "7ba3da40fb26490697fc64b3248c5952": { - "views": [] - }, - "7e79fea4654f4bedb5969db265736c25": { - "views": [] - }, - "85c82ed0844f4ae08a14fd750e55fc15": { - "views": [] - }, - "86e8f92c1d584cdeb13b36af1b6ad695": { - "views": [ - { - "cell_index": 35 - } - ] - }, - "88485e72d2ec447ba7e238b0a6de2839": { - "views": [] - }, - "892d7b895d3840f99504101062ba0f65": { - "views": [] - }, - "89be4167713e488696a20b9b5ddac9bd": { - "views": [] - }, - "8a24a07d166b45498b7d8b3f97c131eb": { - "views": [] - }, - "8e7c7f3284ee45b38d95fe9070d5772f": { - "views": [] - }, - "98985eefab414365991ed6844898677f": { - "views": [] - }, - "98df98e5af87474d8b139cb5bcbc9792": { - "views": [] - }, - "99f11243d387409bbad286dd5ecb1725": { - "views": [] - }, - "9ab2d641b0be4cf8950be5ba72e5039f": { - "views": [] - }, - "9b1ffbd1e7404cb4881380a99c7d11bc": { - "views": [] - }, - "9c07ec6555cb4d0ba8b59007085d5692": { - "views": [] - }, - "9cc80f47249b4609b98223ce71594a3d": { - "views": [] - }, - "9d79bfd34d3640a3b7156a370d2aabae": { - "views": [] - }, - "a015f138cbbe4a0cad4d72184762ed75": { - "views": [] - }, - "a27d2f1eb3834c38baf1181b0de93176": { - "views": [] - }, - "a29b90d050f3442a89895fc7615ccfee": { - "views": [ - { - "cell_index": 29 - } - ] - }, - "a725622cfc5b43b4ae14c74bc2ad7ad0": { - "views": [] - }, - "ac2e05d7d7e945bf99862a2d9d1fa685": { - "views": [] - }, - "b0bb2ca65caa47579a4d3adddd94504b": { - "views": [] - }, - "b8995c40625d465489e1b7ec8014b678": { - "views": [] - }, - "ba83da1373fe45d19b3c96a875f2f4fb": { - "views": [] - }, - "baa0040d35c64604858c529418c22797": { - "views": [] - }, - "badc9fd7b56346d6b6aea68bfa6d2699": { - "views": [ - { - "cell_index": 38 - } - ] - }, - "bdb41c7654e54c83a91452abc59141bd": { - "views": [] - }, - "c2399056ef4a4aa7aa4e23a0f381d64a": { - "views": [ - { - "cell_index": 38 - } - ] - }, - "c73b47b242b4485fb1462abcd92dc7c9": { - "views": [] - }, - "ce3f28a8aeee4be28362d068426a71f6": { - "views": [ - { - "cell_index": 32 - } - ] - }, - "d3067a6bb84544bba5f1abd241a72e55": { - "views": [] - }, - "db13a2b94de34ce9bea721aaf971c049": { - "views": [] - }, - "db468d80cb6e43b6b88455670b036618": { - "views": [] - }, - "e2cb458522b4438ea3f9873b6e411acb": { - "views": [] - }, - "e77dca31f1d94d4dadd3f95d2cdbf10e": { - "views": [] - }, - "e7bffb1fed664dea90f749ea79dcc4f1": { - "views": [ - { - "cell_index": 39 - } - ] - }, - "e80abb145fce4e888072b969ba8f455a": { - "views": [] - }, - "e839d0cf348c4c1b832fc1fc3b0bd3c9": { - "views": [] - }, - "e948c6baadde46f69f105649555b84eb": { - "views": [] - }, - "eb16e9da25bf4bef91a34b1d0565c774": { - "views": [] - }, - "ec82b64048834eafa3e53733bb54a713": { - "views": [] - }, - "edbb3a621c87445e9df4773cc60ec8d2": { - "views": [] - }, - "ef6c99705936425a975e49b9e18ac267": { - "views": [] - }, - "f1b494f025dd48d1ae58ae8e3e2ebf46": { - "views": [] - }, - "f435b108c59c42989bf209a625a3a5b5": { - "views": [ - { - "cell_index": 32 - } - ] - }, - "f71ed7e15a314c28973943046c4529d6": { - "views": [] - }, - "f81f726f001c4fb999851df532ed39f2": { - "views": [] - } - }, - "version": "1.1.1" + "version": "3.6.1" } }, "nbformat": 4, From a690882878420253fac9b7b1ac3abbe20a81bcfc Mon Sep 17 00:00:00 2001 From: Vinay Varma Date: Wed, 7 Feb 2018 02:22:37 +0530 Subject: [PATCH 7/9] added Best First search in search.ipynb (#708) * added Best First search * fixed minor conflicts * minor changes --- search.ipynb | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 2 deletions(-) diff --git a/search.ipynb b/search.ipynb index 96ac09aa7..ac621b622 100644 --- a/search.ipynb +++ b/search.ipynb @@ -41,6 +41,7 @@ "* Breadth-First Search\n", "* Uniform Cost Search\n", "* A\\* Search\n", + "* Best First Search\n", "* Genetic Algorithm" ] }, @@ -447,7 +448,7 @@ "2. Depth First Tree Search - Implemented\n", "3. Depth First Graph Search - Implemented\n", "4. Breadth First Search - Implemented\n", - "5. Best First Graph Search\n", + "5. Best First Graph Search - Implemented\n", "6. Uniform Cost Search - Implemented\n", "7. Depth Limited Search\n", "8. Iterative Deepening Search\n", @@ -1190,7 +1191,7 @@ ], "source": [ "all_node_colors = []\n", - "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)\n", + "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)\n", "display_visual(user_input = False, algorithm = astar_search, problem = romania_problem)" ] }, @@ -1253,6 +1254,128 @@ "display_visual(user_input = True)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## BEST FIRST SEARCH\n", + "Let's change all the node_colors to starting position and define a different problem statement." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "def best_first_graph_search(problem, f):\n", + " \"\"\"Search the nodes with the lowest f scores first.\n", + " You specify the function f(node) that you want to minimize; for example,\n", + " if f is a heuristic estimate to the goal, then we have greedy best\n", + " first search; if f is node.depth then we have breadth-first search.\n", + " There is a subtlety: the line \"f = memoize(f, 'f')\" means that the f\n", + " values will be cached on the nodes as they are computed. So after doing\n", + " a best first search you can examine the f values of the path returned.\"\"\"\n", + " \n", + " # we use these two variables at the time of visualisations\n", + " iterations = 0\n", + " all_node_colors = []\n", + " node_colors = dict(initial_node_colors)\n", + " \n", + " f = memoize(f, 'f')\n", + " node = Node(problem.initial)\n", + " \n", + " node_colors[node.state] = \"red\"\n", + " iterations += 1\n", + " all_node_colors.append(dict(node_colors))\n", + " \n", + " if problem.goal_test(node.state):\n", + " node_colors[node.state] = \"green\"\n", + " iterations += 1\n", + " all_node_colors.append(dict(node_colors))\n", + " return(iterations, all_node_colors, node)\n", + " \n", + " frontier = PriorityQueue(min, f)\n", + " frontier.append(node)\n", + " \n", + " node_colors[node.state] = \"orange\"\n", + " iterations += 1\n", + " all_node_colors.append(dict(node_colors))\n", + " \n", + " explored = set()\n", + " while frontier:\n", + " node = frontier.pop()\n", + " \n", + " node_colors[node.state] = \"red\"\n", + " iterations += 1\n", + " all_node_colors.append(dict(node_colors))\n", + " \n", + " if problem.goal_test(node.state):\n", + " node_colors[node.state] = \"green\"\n", + " iterations += 1\n", + " all_node_colors.append(dict(node_colors))\n", + " return(iterations, all_node_colors, node)\n", + " \n", + " explored.add(node.state)\n", + " for child in node.expand(problem):\n", + " if child.state not in explored and child not in frontier:\n", + " frontier.append(child)\n", + " node_colors[child.state] = \"orange\"\n", + " iterations += 1\n", + " all_node_colors.append(dict(node_colors))\n", + " elif child in frontier:\n", + " incumbent = frontier[child]\n", + " if f(child) < f(incumbent):\n", + " del frontier[incumbent]\n", + " frontier.append(child)\n", + " node_colors[child.state] = \"orange\"\n", + " iterations += 1\n", + " all_node_colors.append(dict(node_colors))\n", + "\n", + " node_colors[node.state] = \"gray\"\n", + " iterations += 1\n", + " all_node_colors.append(dict(node_colors))\n", + " return None\n", + "\n", + "def best_first_search(problem, h=None):\n", + " \"\"\"Best-first graph search is an informative searching algorithm with f(n) = h(n).\n", + " You need to specify the h function when you call best_first_search, or\n", + " else in your Problem subclass.\"\"\"\n", + " h = memoize(h or problem.h, 'h')\n", + " iterations, all_node_colors, node = best_first_graph_search(problem, lambda n: h(n))\n", + " return(iterations, all_node_colors, node)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5ae2d521b74743afa988c462a851c269" + } + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "559c20b044a4469db7f0ab8c3fae1022" + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "all_node_colors = []\n", + "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)\n", + "display_visual(user_input = False, algorithm = best_first_search, problem = romania_problem)" + ] + }, { "cell_type": "markdown", "metadata": {}, From cf23e5c9b20a8bdc50835dfa30f3fc8e153f7d5f Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Wed, 7 Feb 2018 03:37:17 +0530 Subject: [PATCH 8/9] 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 --- gui/tsp.py | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 135 insertions(+), 8 deletions(-) diff --git a/gui/tsp.py b/gui/tsp.py index 6a460261e..1830cba23 100644 --- a/gui/tsp.py +++ b/gui/tsp.py @@ -1,8 +1,10 @@ from tkinter import * +from tkinter import messagebox import sys import os.path sys.path.append(os.path.join(os.path.dirname(__file__), '..')) from search import * +import utils import numpy as np distances = {} @@ -56,6 +58,7 @@ def __init__(self, root, all_cities): self.calculate_canvas_size() self.button_text = StringVar() self.button_text.set("Start") + self.algo_var = StringVar() self.all_cities = all_cities self.frame_select_cities = Frame(self.root) self.frame_select_cities.grid(row=1) @@ -85,9 +88,18 @@ def create_buttons(self): """ Create start and quit button """ Button(self.frame_select_cities, textvariable=self.button_text, - command=self.run_traveling_salesman).grid(row=3, column=4, sticky=E + W) - Button(self.frame_select_cities, text='Quit', command=self.root.destroy).grid( - row=3, column=5, sticky=E + W) + command=self.run_traveling_salesman).grid(row=5, column=4, sticky=E + W) + Button(self.frame_select_cities, text='Quit', command=self.on_closing).grid( + row=5, column=5, sticky=E + W) + + def create_dropdown_menu(self): + """ Create dropdown menu for algorithm selection """ + + choices = {'Simulated Annealing', 'Genetic Algorithm', 'Hill Climbing'} + self.algo_var.set('Simulated Annealing') + dropdown_menu = OptionMenu(self.frame_select_cities, self.algo_var, *choices) + dropdown_menu.grid(row=4, column=4, columnspan=2, sticky=E + W) + dropdown_menu.config(width=19) def run_traveling_salesman(self): """ Choose selected citites """ @@ -151,13 +163,30 @@ def create_canvas(self, problem): variable=self.speed, label="Speed ----> ", showvalue=0, font="Times 11", relief="sunken", cursor="gumby") speed_scale.grid(row=1, columnspan=5, sticky=N + S + E + W) - self.temperature = IntVar() - temperature_scale = Scale(self.frame_canvas, from_=100, to=0, orient=HORIZONTAL, + + if self.algo_var.get() == 'Simulated Annealing': + self.temperature = IntVar() + temperature_scale = Scale(self.frame_canvas, from_=100, to=0, orient=HORIZONTAL, length=200, variable=self.temperature, label="Temperature ---->", font="Times 11", relief="sunken", showvalue=0, cursor="gumby") - - temperature_scale.grid(row=1, column=5, columnspan=5, sticky=N + S + E + W) - self.simulated_annealing_with_tunable_T(problem, map_canvas) + temperature_scale.grid(row=1, column=5, columnspan=5, sticky=N + S + E + W) + self.simulated_annealing_with_tunable_T(problem, map_canvas) + elif self.algo_var.get() == 'Genetic Algorithm': + self.mutation_rate = DoubleVar() + self.mutation_rate.set(0.05) + mutation_rate_scale = Scale(self.frame_canvas, from_=0, to=1, orient=HORIZONTAL, + length=200, variable=self.mutation_rate, label='Mutation Rate ---->', + font='Times 11', relief='sunken', showvalue=0, cursor='gumby', resolution=0.001) + mutation_rate_scale.grid(row=1, column=5, columnspan=5, sticky='nsew') + self.genetic_algorithm(problem, map_canvas) + elif self.algo_var.get() == 'Hill Climbing': + self.no_of_neighbors = IntVar() + self.no_of_neighbors.set(100) + no_of_neighbors_scale = Scale(self.frame_canvas, from_=10, to=1000, orient=HORIZONTAL, + length=200, variable=self.no_of_neighbors, label='Number of neighbors ---->', + font='Times 11',relief='sunken', showvalue=0, cursor='gumby') + no_of_neighbors_scale.grid(row=1, column=5, columnspan=5, sticky='nsew') + self.hill_climbing(problem, map_canvas) def exp_schedule(k=100, lam=0.03, limit=1000): """ One possible schedule function for simulated annealing """ @@ -191,6 +220,102 @@ def simulated_annealing_with_tunable_T(self, problem, map_canvas, schedule=exp_s map_canvas.update() map_canvas.after(self.speed.get()) + def genetic_algorithm(self, problem, map_canvas): + """ Genetic Algorithm modified for the given problem """ + + def init_population(pop_number, gene_pool, state_length): + """ initialize population """ + + population = [] + for i in range(pop_number): + population.append(utils.shuffled(gene_pool)) + return population + + def recombine(state_a, state_b): + """ recombine two problem states """ + + start = random.randint(0, len(state_a) - 1) + end = random.randint(start + 1, len(state_a)) + new_state = state_a[start:end] + for city in state_b: + if city not in new_state: + new_state.append(city) + return new_state + + def mutate(state, mutation_rate): + """ mutate problem states """ + + if random.uniform(0, 1) < mutation_rate: + sample = random.sample(range(len(state)), 2) + state[sample[0]], state[sample[1]] = state[sample[1]], state[sample[0]] + return state + + def fitness_fn(state): + """ calculate fitness of a particular state """ + + fitness = problem.value(state) + return int((5600 + fitness) ** 2) + + current = Node(problem.initial) + population = init_population(100, current.state, len(current.state)) + all_time_best = current.state + while(1): + population = [mutate(recombine(*select(2, population, fitness_fn)), self.mutation_rate.get()) for i in range(len(population))] + current_best = utils.argmax(population, key=fitness_fn) + if fitness_fn(current_best) > fitness_fn(all_time_best): + all_time_best = current_best + self.cost.set("Cost = " + str('%0.3f' % (-1 * problem.value(all_time_best)))) + map_canvas.delete('poly') + points = [] + for city in current_best: + points.append(self.frame_locations[city][0]) + points.append(self.frame_locations[city][1]) + map_canvas.create_polygon(points, outline='red', width=1, fill='', tag='poly') + best_points = [] + for city in all_time_best: + best_points.append(self.frame_locations[city][0]) + best_points.append(self.frame_locations[city][1]) + map_canvas.create_polygon(best_points, outline='red', width=3, fill='', tag='poly') + map_canvas.update() + map_canvas.after(self.speed.get()) + + def hill_climbing(self, problem, map_canvas): + """ hill climbing where number of neighbors is taken as user input """ + + def find_neighbors(state, number_of_neighbors=100): + """ finds neighbors using two_opt method """ + + neighbors = [] + for i in range(number_of_neighbors): + new_state = problem.two_opt(state) + neighbors.append(Node(new_state)) + state = new_state + return neighbors + + current = Node(problem.initial) + while(1): + neighbors = find_neighbors(current.state, self.no_of_neighbors.get()) + neighbor = utils.argmax_random_tie(neighbors, key=lambda node: problem.value(node.state)) + map_canvas.delete('poly') + points = [] + for city in current.state: + points.append(self.frame_locations[city][0]) + points.append(self.frame_locations[city][1]) + map_canvas.create_polygon(points, outline='red', width=3, fill='', tag='poly') + neighbor_points = [] + for city in neighbor.state: + neighbor_points.append(self.frame_locations[city][0]) + neighbor_points.append(self.frame_locations[city][1]) + map_canvas.create_polygon(neighbor_points, outline='red', width=1, fill='', tag='poly') + map_canvas.update() + map_canvas.after(self.speed.get()) + if problem.value(neighbor.state) > problem.value(current.state): + current.state = neighbor.state + self.cost.set("Cost = " + str('%0.3f' % (-1 * problem.value(current.state)))) + + def on_closing(self): + if messagebox.askokcancel('Quit', 'Do you want to quit?'): + self.root.destroy() def main(): all_cities = [] @@ -212,6 +337,8 @@ def main(): cities_selection_panel = TSP_Gui(root, all_cities) cities_selection_panel.create_checkboxes() cities_selection_panel.create_buttons() + cities_selection_panel.create_dropdown_menu() + root.protocol('WM_DELETE_WINDOW', cities_selection_panel.on_closing) root.mainloop() From 685e8d85103a1f8ce5c9f392b25091cac71c08df Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Wed, 7 Feb 2018 03:37:39 +0530 Subject: [PATCH 9/9] added function to implement uniform crossover (#704) --- search.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/search.py b/search.py index 726001dd1..1e32d5b8c 100644 --- a/search.py +++ b/search.py @@ -860,6 +860,19 @@ def recombine(x, y): return x[:c] + y[c:] +def recombine_uniform(x, y): + n = len(x) + result = [0] * n; + indexes = random.sample(range(n), n) + for i in range(n): + ix = indexes[i] + result[ix] = x[ix] if i < n / 2 else y[ix] + try: + return ''.join(result) + except: + return result + + def mutate(x, gene_pool, pmut): if random.uniform(0, 1) >= pmut: return x