From ddac0dcf391dd3c8d42ad1487139ada88aad3bbf Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Fri, 26 Jan 2018 11:25:22 +0530 Subject: [PATCH 001/269] 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 002/269] 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 003/269] 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 004/269] 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 005/269] 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 006/269] 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 007/269] 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 008/269] 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 009/269] 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 From 0390d06999183fd712a19fedb7cc97e565b3d6b3 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Fri, 9 Feb 2018 09:47:46 +0530 Subject: [PATCH 010/269] Updated move dictionary (#715) --- search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.py b/search.py index 1e32d5b8c..9caee609a 100644 --- a/search.py +++ b/search.py @@ -422,7 +422,7 @@ def checkSolvability(self, state): 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 + 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:[4,6,8], 8:[7,5]} # create a dictionary of moves index = state[0].index(0) possible_moves = [] for i in range(len(move[index])): From a643323fcf68c02f3a7be4420dffc85e654c41b5 Mon Sep 17 00:00:00 2001 From: Vinay Varma Date: Fri, 9 Feb 2018 09:49:53 +0530 Subject: [PATCH 011/269] improved search.ipynb (#716) * added submodule * removed duplicates * minor changes * Update README.md * removed an unwanted commit --- README.md | 2 +- search.ipynb | 303 +++++++++++++++------------------------------------ 2 files changed, 90 insertions(+), 215 deletions(-) diff --git a/README.md b/README.md index f66a5cb8d..dd8c0b38a 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 3.14 | Uniform-Cost-Search | `uniform_cost_search` | [`search.py`][search] | Done | Included | | 3.17 | Depth-Limited-Search | `depth_limited_search` | [`search.py`][search] | Done | | | 3.18 | Iterative-Deepening-Search | `iterative_deepening_search` | [`search.py`][search] | Done | | -| 3.22 | Best-First-Search | `best_first_graph_search` | [`search.py`][search] | Done | | +| 3.22 | Best-First-Search | `best_first_graph_search` | [`search.py`][search] | Done | Included | | 3.24 | A\*-Search | `astar_search` | [`search.py`][search] | Done | Included | | 3.26 | Recursive-Best-First-Search | `recursive_best_first_search` | [`search.py`][search] | Done | | | 4.2 | Hill-Climbing | `hill_climbing` | [`search.py`][search] | Done | | diff --git a/search.ipynb b/search.ipynb index ac621b622..5415dd89a 100644 --- a/search.ipynb +++ b/search.ipynb @@ -39,9 +39,10 @@ "* Search Algorithms Visualization\n", "* Breadth-First Tree Search\n", "* Breadth-First Search\n", + "* Best First Search\n", "* Uniform Cost Search\n", + "* Greedy Best First Search\n", "* A\\* Search\n", - "* Best First Search\n", "* Genetic Algorithm" ] }, @@ -948,21 +949,18 @@ "display_visual(user_input = False, algorithm = depth_first_graph_search, problem = romania_problem)" ] }, - { + { "cell_type": "markdown", "metadata": {}, "source": [ - "## UNIFORM COST SEARCH\n", - "\n", + "## 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": 22, - "metadata": { - "collapsed": true - }, + "execution_count": 21, + "metadata": {}, "outputs": [], "source": [ "def best_first_graph_search(problem, f):\n", @@ -1032,10 +1030,27 @@ " node_colors[node.state] = \"gray\"\n", " iterations += 1\n", " all_node_colors.append(dict(node_colors))\n", - " return None\n", + " return None" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## UNIFORM COST SEARCH\n", "\n", + "Let's change all the node_colors to starting position and define a different problem statement." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ "def uniform_cost_search(problem):\n", " \"[Figure 3.14]\"\n", + " #Uniform Cost Search uses Best First Search algorithm with f(n) = g(n)\n", " iterations, all_node_colors, node = best_first_graph_search(problem, lambda node: node.path_cost)\n", " return(iterations, all_node_colors, node)" ] @@ -1048,7 +1063,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "a667c668001e4e598478ba4a870c6aec" + "model_id": "46b8200b4a8f47e7b18145234a8469da" } }, "metadata": {}, @@ -1057,8 +1072,8 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "135c6bd739de4aab8fc7b2fcb6b90954" - } + "model_id": "ca9b2d01bbd5458bb037585c719d73fc" + } }, "metadata": {}, "output_type": "display_data" @@ -1074,106 +1089,34 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## A\\* SEARCH\n", - "\n", + "## GREEDY 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": 24, - "metadata": { - "collapsed": true - }, + "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 astar_search(problem, h=None):\n", - " \"\"\"A* search is best-first graph search with f(n) = g(n)+h(n).\n", - " You need to specify the h function when you call astar_search, or\n", + "def greedy_best_first_search(problem, h=None):\n", + " \"\"\"Greedy 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: n.path_cost + h(n))\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": 43, + "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "3e62c492a82044e4813ad5d84e698874" + "model_id": "e3ddd0260d7d4a8aa62d610976b9568a" } }, "metadata": {}, @@ -1182,7 +1125,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "b661fd0c0c8d495db2672aedc25b9a44" + "model_id": "dae485b1f4224c34a88de42d252da76c" } }, "metadata": {}, @@ -1191,21 +1134,43 @@ ], "source": [ "all_node_colors = []\n", - "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)\n", - "display_visual(user_input = False, algorithm = astar_search, problem = romania_problem)" + "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)\n", + "display_visual(user_input = False, algorithm = greedy_best_first_search, problem = romania_problem)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## A\\* SEARCH\n", + "\n", + "Let's change all the node_colors to starting position and define a different problem statement." ] }, { "cell_type": "code", - "execution_count": 44, - "metadata": { - "scrolled": false - }, + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "def astar_search(problem, h=None):\n", + " \"\"\"A* search is best-first graph search with f(n) = g(n)+h(n).\n", + " You need to specify the h function when you call astar_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: n.path_cost + h(n))\n", + " return(iterations, all_node_colors, node)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "7f1ffa858c92429bb28f74c23c0c939c" + "model_id": "15a78d815f0c4ea589cdd5ad40bc8794" } }, "metadata": {}, @@ -1214,16 +1179,30 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "7a98e98ffec14520b93ce542f5169bcc" + "model_id": "10450687dd574be2a380e9e40403fa83" } }, "metadata": {}, "output_type": "display_data" - }, + } + ], + "source": [ + "all_node_colors = []\n", + "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)\n", + "display_visual(user_input = False, algorithm = astar_search, problem = romania_problem)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "scrolled": false + }, + "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "094beb8cf34c4a5b87f8368539d24091" + "model_id": "9019790cf8324d73966373bb3f5373a8" } }, "metadata": {}, @@ -1232,7 +1211,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "a8f89c87de964ee69004902763e68a54" + "model_id": "b8a3195598da472d996e4e8b81595cb7" } }, "metadata": {}, @@ -1241,120 +1220,16 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "2ccdb4aba3ee4371a78306755e5642ad" + "model_id": "aabe167a0d6440f0a020df8a85a9206c" } }, "metadata": {}, "output_type": "display_data" - } - ], - "source": [ - "all_node_colors = []\n", - "# display_visual(user_input = True, algorithm = breadth_first_tree_search)\n", - "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" + "model_id": "25d146d187004f4f9db6a7dccdbc7e93" } }, "metadata": {}, @@ -1363,7 +1238,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "559c20b044a4469db7f0ab8c3fae1022" + "model_id": "68d532810a9e46309415fd353c474a4d" } }, "metadata": {}, @@ -1372,8 +1247,8 @@ ], "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)" + "# display_visual(user_input = True, algorithm = breadth_first_tree_search)\n", + "display_visual(user_input = True)" ] }, { From 485c94fbfd8487dab9fe9bbccb1818cfc531769b Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Mon, 12 Feb 2018 15:20:49 +0530 Subject: [PATCH 012/269] Added GridMDP editor to create and solve grid-world problems (Closes #713) (#719) * Added GridMDP editor to create and solve grid-world problems * Added reference to grid_mdp.py in mdp.ipynb * Replacing %psource with psource function * Print matrix to console as well --- gui/grid_mdp.py | 629 ++++++++++++++++++++++++++++++++++++++++++++++++ mdp.ipynb | 310 +++++++++++++++++++++++- 2 files changed, 927 insertions(+), 12 deletions(-) create mode 100644 gui/grid_mdp.py diff --git a/gui/grid_mdp.py b/gui/grid_mdp.py new file mode 100644 index 000000000..fd5aeb8ae --- /dev/null +++ b/gui/grid_mdp.py @@ -0,0 +1,629 @@ +# author: ad71 +import tkinter as tk +import tkinter.messagebox +from tkinter import ttk + +from functools import partial + +import sys +import os.path +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) + +from mdp import * +import utils +import numpy as np +import time + +import matplotlib +import matplotlib.animation as animation +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +from matplotlib.ticker import MaxNLocator +from matplotlib.figure import Figure +from matplotlib import style +from matplotlib import pyplot as plt +matplotlib.use('TkAgg') +style.use('ggplot') + +fig = Figure(figsize=(20, 15)) +sub = fig.add_subplot(111) +plt.rcParams['axes.grid'] = False + +WALL_VALUE = -99999.0 +TERM_VALUE = -999999.0 + +black = '#000' +white = '#fff' +gray2 = '#222' +gray9 = '#999' +grayd = '#ddd' +grayef = '#efefef' +pblue = '#000040' +green8 = '#008080' +green4 = '#004040' + + +def extents(f): + ''' adjusts axis markers for heatmap ''' + + delta = f[1] - f[0] + return [f[0] - delta/2, f[-1] + delta/2] + +def display(gridmdp, _height, _width): + ''' displays matrix ''' + + dialog = tk.Toplevel() + dialog.wm_title('Values') + + container = tk.Frame(dialog) + container.pack(side=tk.TOP, fill=tk.BOTH, expand=True) + + for i in range(max(1, _height)): + for j in range(max(1, _width)): + label = ttk.Label(container, text=f'{gridmdp[_height - i - 1][j]:.3f}', font=('Helvetica', 12)) + label.grid(row=i + 1, column=j + 1, padx=3, pady=3) + + dialog.mainloop() + +def initialize_dialogbox(_width, _height, gridmdp, terminals, buttons): + ''' creates dialogbox for initialization ''' + + dialog = tk.Toplevel() + dialog.wm_title('Initialize') + + container = tk.Frame(dialog) + container.pack(side=tk.TOP, fill=tk.BOTH, expand=True) + container.grid_rowconfigure(0, weight=1) + container.grid_columnconfigure(0, weight=1) + + wall = tk.IntVar() + wall.set(0) + term = tk.IntVar() + term.set(0) + reward = tk.DoubleVar() + reward.set(0.0) + + label = ttk.Label(container, text='Initialize', font=('Helvetica', 12), anchor=tk.N) + label.grid(row=0, column=0, columnspan=3, sticky='new', pady=15, padx=5) + label_reward = ttk.Label(container, text='Reward', font=('Helvetica', 10), anchor=tk.N) + label_reward.grid(row=1, column=0, columnspan=3, sticky='new', pady=1, padx=5) + entry_reward = ttk.Entry(container, font=('Helvetica', 10), justify=tk.CENTER, exportselection=0, textvariable=reward) + entry_reward.grid(row=2, column=0, columnspan=3, sticky='new', pady=5, padx=50) + + rbtn_term = ttk.Radiobutton(container, text='Terminal', variable=term, value=TERM_VALUE) + rbtn_term.grid(row=3, column=0, columnspan=3, sticky='nsew', padx=160, pady=5) + rbtn_wall = ttk.Radiobutton(container, text='Wall', variable=wall, value=WALL_VALUE) + rbtn_wall.grid(row=4, column=0, columnspan=3, sticky='nsew', padx=172, pady=5) + + initialize_widget_disability_checks(_width, _height, gridmdp, terminals, label_reward, entry_reward, rbtn_wall, rbtn_term) + + btn_apply = ttk.Button(container, text='Apply', command=partial(initialize_update_table, _width, _height, gridmdp, terminals, buttons, reward, term, wall, label_reward, entry_reward, rbtn_term, rbtn_wall)) + btn_apply.grid(row=5, column=0, sticky='nsew', pady=5, padx=5) + btn_reset = ttk.Button(container, text='Reset', command=partial(initialize_reset_all, _width, _height, gridmdp, terminals, buttons, label_reward, entry_reward, rbtn_wall, rbtn_term)) + btn_reset.grid(row=5, column=1, sticky='nsew', pady=5, padx=5) + btn_ok = ttk.Button(container, text='Ok', command=dialog.destroy) + btn_ok.grid(row=5, column=2, sticky='nsew', pady=5, padx=5) + + dialog.geometry('400x200') + dialog.mainloop() + +def update_table(i, j, gridmdp, terminals, buttons, reward, term, wall, label_reward, entry_reward, rbtn_term, rbtn_wall): + ''' functionality for 'apply' button ''' + + if wall.get() == WALL_VALUE: + buttons[i][j].configure(style='wall.TButton') + buttons[i][j].config(text='Wall') + label_reward.config(foreground='#999') + entry_reward.config(state=tk.DISABLED) + rbtn_term.state(['!focus', '!selected']) + rbtn_term.config(state=tk.DISABLED) + gridmdp[i][j] = WALL_VALUE + + elif wall.get() != WALL_VALUE: + if reward.get() != 0.0: + gridmdp[i][j] = reward.get() + buttons[i][j].configure(style='reward.TButton') + buttons[i][j].config(text=f'R = {reward.get()}') + + if term.get() == TERM_VALUE: + if (i, j) not in terminals: + terminals.append((i, j)) + rbtn_wall.state(['!focus', '!selected']) + rbtn_wall.config(state=tk.DISABLED) + + if gridmdp[i][j] < 0: + buttons[i][j].configure(style='-term.TButton') + + elif gridmdp[i][j] > 0: + buttons[i][j].configure(style='+term.TButton') + + elif gridmdp[i][j] == 0.0: + buttons[i][j].configure(style='=term.TButton') + +def initialize_update_table(_width, _height, gridmdp, terminals, buttons, reward, term, wall, label_reward, entry_reward, rbtn_term, rbtn_wall): + ''' runs update_table for all cells ''' + + for i in range(max(1, _height)): + for j in range(max(1, _width)): + update_table(i, j, gridmdp, terminals, buttons, reward, term, wall, label_reward, entry_reward, rbtn_term, rbtn_wall) + +def reset_all(_height, i, j, gridmdp, terminals, buttons, label_reward, entry_reward, rbtn_wall, rbtn_term): + ''' functionality for reset button ''' + + gridmdp[i][j] = 0.0 + buttons[i][j].configure(style='TButton') + buttons[i][j].config(text=f'({_height - i - 1}, {j})') + + if (i, j) in terminals: + terminals.remove((i, j)) + + label_reward.config(foreground='#000') + entry_reward.config(state=tk.NORMAL) + rbtn_term.config(state=tk.NORMAL) + rbtn_wall.config(state=tk.NORMAL) + rbtn_wall.state(['!focus', '!selected']) + rbtn_term.state(['!focus', '!selected']) + +def initialize_reset_all(_width, _height, gridmdp, terminals, buttons, label_reward, entry_reward, rbtn_wall, rbtn_term): + ''' runs reset_all for all cells ''' + + for i in range(max(1, _height)): + for j in range(max(1, _width)): + reset_all(_height, i, j, gridmdp, terminals, buttons, label_reward, entry_reward, rbtn_wall, rbtn_term) + +def external_reset(_width, _height, gridmdp, terminals, buttons): + ''' reset from edit menu ''' + + terminals = [] + for i in range(max(1, _height)): + for j in range(max(1, _width)): + gridmdp[i][j] = 0.0 + buttons[i][j].configure(style='TButton') + buttons[i][j].config(text=f'({_height - i - 1}, {j})') + +def widget_disability_checks(i, j, gridmdp, terminals, label_reward, entry_reward, rbtn_wall, rbtn_term): + ''' checks for required state of widgets in dialogboxes ''' + + if gridmdp[i][j] == WALL_VALUE: + label_reward.config(foreground='#999') + entry_reward.config(state=tk.DISABLED) + rbtn_term.config(state=tk.DISABLED) + rbtn_wall.state(['!focus', 'selected']) + rbtn_term.state(['!focus', '!selected']) + + if (i, j) in terminals: + rbtn_wall.config(state=tk.DISABLED) + rbtn_wall.state(['!focus', '!selected']) + +def flatten_list(_list): + ''' returns a flattened list ''' + + return sum(_list, []) + +def initialize_widget_disability_checks(_width, _height, gridmdp, terminals, label_reward, entry_reward, rbtn_wall, rbtn_term): + ''' checks for required state of widgets when cells are initialized ''' + + bool_walls = [['False']*max(1, _width) for _ in range(max(1, _height))] + bool_terms = [['False']*max(1, _width) for _ in range(max(1, _height))] + + for i in range(max(1, _height)): + for j in range(max(1, _width)): + if gridmdp[i][j] == WALL_VALUE: + bool_walls[i][j] = 'True' + + if (i, j) in terminals: + bool_terms[i][j] = 'True' + + bool_walls_fl = flatten_list(bool_walls) + bool_terms_fl = flatten_list(bool_terms) + + if bool_walls_fl.count('True') == len(bool_walls_fl): + print('`') + label_reward.config(foreground='#999') + entry_reward.config(state=tk.DISABLED) + rbtn_term.config(state=tk.DISABLED) + rbtn_wall.state(['!focus', 'selected']) + rbtn_term.state(['!focus', '!selected']) + + if bool_terms_fl.count('True') == len(bool_terms_fl): + rbtn_wall.config(state=tk.DISABLED) + rbtn_wall.state(['!focus', '!selected']) + rbtn_term.state(['!focus', 'selected']) + +def dialogbox(i, j, gridmdp, terminals, buttons, _height): + ''' creates dialogbox for each cell ''' + + dialog = tk.Toplevel() + dialog.wm_title(f'{_height - i - 1}, {j}') + + container = tk.Frame(dialog) + container.pack(side=tk.TOP, fill=tk.BOTH, expand=True) + container.grid_rowconfigure(0, weight=1) + container.grid_columnconfigure(0, weight=1) + + wall = tk.IntVar() + wall.set(gridmdp[i][j]) + term = tk.IntVar() + term.set(TERM_VALUE if (i, j) in terminals else 0.0) + reward = tk.DoubleVar() + reward.set(gridmdp[i][j] if gridmdp[i][j] != WALL_VALUE else 0.0) + + label = ttk.Label(container, text=f'Configure cell {_height - i - 1}, {j}', font=('Helvetica', 12), anchor=tk.N) + label.grid(row=0, column=0, columnspan=3, sticky='new', pady=15, padx=5) + label_reward = ttk.Label(container, text='Reward', font=('Helvetica', 10), anchor=tk.N) + label_reward.grid(row=1, column=0, columnspan=3, sticky='new', pady=1, padx=5) + entry_reward = ttk.Entry(container, font=('Helvetica', 10), justify=tk.CENTER, exportselection=0, textvariable=reward) + entry_reward.grid(row=2, column=0, columnspan=3, sticky='new', pady=5, padx=50) + + rbtn_term = ttk.Radiobutton(container, text='Terminal', variable=term, value=TERM_VALUE) + rbtn_term.grid(row=3, column=0, columnspan=3, sticky='nsew', padx=160, pady=5) + rbtn_wall = ttk.Radiobutton(container, text='Wall', variable=wall, value=WALL_VALUE) + rbtn_wall.grid(row=4, column=0, columnspan=3, sticky='nsew', padx=172, pady=5) + + widget_disability_checks(i, j, gridmdp, terminals, label_reward, entry_reward, rbtn_wall, rbtn_term) + + btn_apply = ttk.Button(container, text='Apply', command=partial(update_table, i, j, gridmdp, terminals, buttons, reward, term, wall, label_reward, entry_reward, rbtn_term, rbtn_wall)) + btn_apply.grid(row=5, column=0, sticky='nsew', pady=5, padx=5) + btn_reset = ttk.Button(container, text='Reset', command=partial(reset_all, _height, i, j, gridmdp, terminals, buttons, label_reward, entry_reward, rbtn_wall, rbtn_term)) + btn_reset.grid(row=5, column=1, sticky='nsew', pady=5, padx=5) + btn_ok = ttk.Button(container, text='Ok', command=dialog.destroy) + btn_ok.grid(row=5, column=2, sticky='nsew', pady=5, padx=5) + + dialog.geometry('400x200') + dialog.mainloop() + + +class MDPapp(tk.Tk): + + def __init__(self, *args, **kwargs): + + tk.Tk.__init__(self, *args, **kwargs) + tk.Tk.wm_title(self, 'Grid MDP') + self.shared_data = { + 'height': tk.IntVar(), + 'width': tk.IntVar() + } + self.shared_data['height'].set(1) + self.shared_data['width'].set(1) + self.container = tk.Frame(self) + self.container.pack(side='top', fill='both', expand=True) + self.container.grid_rowconfigure(0, weight=1) + self.container.grid_columnconfigure(0, weight=1) + + self.frames = {} + + self.menu_bar = tk.Menu(self.container) + self.file_menu = tk.Menu(self.menu_bar, tearoff=0) + self.file_menu.add_command(label='Exit', command=self.exit) + self.menu_bar.add_cascade(label='File', menu=self.file_menu) + + self.edit_menu = tk.Menu(self.menu_bar, tearoff=1) + self.edit_menu.add_command(label='Reset', command=self.master_reset) + self.edit_menu.add_command(label='Initialize', command=self.initialize) + self.edit_menu.add_separator() + self.edit_menu.add_command(label='View matrix', command=self.view_matrix) + self.edit_menu.add_command(label='View terminals', command=self.view_terminals) + self.menu_bar.add_cascade(label='Edit', menu=self.edit_menu) + self.menu_bar.entryconfig('Edit', state=tk.DISABLED) + + self.build_menu = tk.Menu(self.menu_bar, tearoff=1) + self.build_menu.add_command(label='Build and Run', command=self.build) + self.menu_bar.add_cascade(label='Build', menu=self.build_menu) + self.menu_bar.entryconfig('Build', state=tk.DISABLED) + tk.Tk.config(self, menu=self.menu_bar) + + for F in (HomePage, BuildMDP, SolveMDP): + frame = F(self.container, self) + self.frames[F] = frame + frame.grid(row=0, column=0, sticky='nsew') + + self.show_frame(HomePage) + + def placeholder_function(self): + ''' placeholder function ''' + + print('Not supported yet!') + + def exit(self): + ''' function to exit ''' + + if tkinter.messagebox.askokcancel('Exit?', 'All changes will be lost'): + quit() + + def new(self): + ''' function to create new GridMDP ''' + + self.master_reset() + build_page = self.get_page(BuildMDP) + build_page.gridmdp = None + build_page.terminals = None + build_page.buttons = None + self.show_frame(HomePage) + + def get_page(self, page_class): + ''' returns pages from stored frames ''' + + return self.frames[page_class] + + def view_matrix(self): + ''' prints current matrix to console ''' + + build_page = self.get_page(BuildMDP) + _height = self.shared_data['height'].get() + _width = self.shared_data['width'].get() + print(build_page.gridmdp) + display(build_page.gridmdp, _height, _width) + + def view_terminals(self): + ''' prints current terminals to console ''' + + build_page = self.get_page(BuildMDP) + print('Terminals', build_page.terminals) + + def initialize(self): + ''' calls initialize from BuildMDP ''' + + build_page = self.get_page(BuildMDP) + build_page.initialize() + + def master_reset(self): + ''' calls master_reset from BuildMDP ''' + + build_page = self.get_page(BuildMDP) + build_page.master_reset() + + def build(self): + ''' runs specified mdp solving algorithm ''' + + frame = SolveMDP(self.container, self) + self.frames[SolveMDP] = frame + frame.grid(row=0, column=0, sticky='nsew') + self.show_frame(SolveMDP) + build_page = self.get_page(BuildMDP) + gridmdp = build_page.gridmdp + terminals = build_page.terminals + solve_page = self.get_page(SolveMDP) + _height = self.shared_data['height'].get() + _width = self.shared_data['width'].get() + solve_page.create_graph(gridmdp, terminals, _height, _width) + + def show_frame(self, controller, cb=False): + ''' shows specified frame and optionally runs create_buttons ''' + + if cb: + build_page = self.get_page(BuildMDP) + build_page.create_buttons() + frame = self.frames[controller] + frame.tkraise() + + +class HomePage(tk.Frame): + + def __init__(self, parent, controller): + ''' HomePage constructor ''' + + tk.Frame.__init__(self, parent) + self.controller = controller + frame1 = tk.Frame(self) + frame1.pack(side=tk.TOP) + frame3 = tk.Frame(self) + frame3.pack(side=tk.TOP) + frame4 = tk.Frame(self) + frame4.pack(side=tk.TOP) + frame2 = tk.Frame(self) + frame2.pack(side=tk.TOP) + + s = ttk.Style() + s.theme_use('clam') + s.configure('TButton', background=grayd, padding=0) + s.configure('wall.TButton', background=gray2, foreground=white) + s.configure('reward.TButton', background=gray9) + s.configure('+term.TButton', background=green8) + s.configure('-term.TButton', background=pblue, foreground=white) + s.configure('=term.TButton', background=green4) + + label = ttk.Label(frame1, text='GridMDP builder', font=('Helvetica', 18, 'bold'), background=grayef) + label.pack(pady=75, padx=50, side=tk.TOP) + + ec_btn = ttk.Button(frame3, text='Empty cells', width=20) + ec_btn.pack(pady=0, padx=0, side=tk.LEFT, ipady=10) + ec_btn.configure(style='TButton') + + w_btn = ttk.Button(frame3, text='Walls', width=20) + w_btn.pack(pady=0, padx=0, side=tk.LEFT, ipady=10) + w_btn.configure(style='wall.TButton') + + r_btn = ttk.Button(frame3, text='Rewards', width=20) + r_btn.pack(pady=0, padx=0, side=tk.LEFT, ipady=10) + r_btn.configure(style='reward.TButton') + + term_p = ttk.Button(frame3, text='Positive terminals', width=20) + term_p.pack(pady=0, padx=0, side=tk.LEFT, ipady=10) + term_p.configure(style='+term.TButton') + + term_z = ttk.Button(frame3, text='Neutral terminals', width=20) + term_z.pack(pady=0, padx=0, side=tk.LEFT, ipady=10) + term_z.configure(style='=term.TButton') + + term_n = ttk.Button(frame3, text='Negative terminals', width=20) + term_n.pack(pady=0, padx=0, side=tk.LEFT, ipady=10) + term_n.configure(style='-term.TButton') + + label = ttk.Label(frame4, text='Dimensions', font=('Verdana', 14), background=grayef) + label.pack(pady=15, padx=10, side=tk.TOP) + entry_h = tk.Entry(frame2, textvariable=self.controller.shared_data['height'], font=('Verdana', 10), width=3, justify=tk.CENTER) + entry_h.pack(pady=10, padx=10, side=tk.LEFT) + label_x = ttk.Label(frame2, text='X', font=('Verdana', 10), background=grayef) + label_x.pack(pady=10, padx=4, side=tk.LEFT) + entry_w = tk.Entry(frame2, textvariable=self.controller.shared_data['width'], font=('Verdana', 10), width=3, justify=tk.CENTER) + entry_w.pack(pady=10, padx=10, side=tk.LEFT) + button = ttk.Button(self, text='Build a GridMDP', command=lambda: controller.show_frame(BuildMDP, cb=True)) + button.pack(pady=10, padx=10, side=tk.TOP, ipadx=20, ipady=10) + button.configure(style='reward.TButton') + + +class BuildMDP(tk.Frame): + + def __init__(self, parent, controller): + + tk.Frame.__init__(self, parent) + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(0, weight=1) + self.frame = tk.Frame(self) + self.frame.pack() + self.controller = controller + + def create_buttons(self): + ''' creates interactive cells to build MDP ''' + + _height = self.controller.shared_data['height'].get() + _width = self.controller.shared_data['width'].get() + self.controller.menu_bar.entryconfig('Edit', state=tk.NORMAL) + self.controller.menu_bar.entryconfig('Build', state=tk.NORMAL) + self.gridmdp = [[0.0]*max(1, _width) for _ in range(max(1, _height))] + self.buttons = [[None]*max(1, _width) for _ in range(max(1, _height))] + self.terminals = [] + + s = ttk.Style() + s.theme_use('clam') + s.configure('TButton', background=grayd, padding=0) + s.configure('wall.TButton', background=gray2, foreground=white) + s.configure('reward.TButton', background=gray9) + s.configure('+term.TButton', background=green8) + s.configure('-term.TButton', background=pblue, foreground=white) + s.configure('=term.TButton', background=green4) + + for i in range(max(1, _height)): + for j in range(max(1, _width)): + self.buttons[i][j] = ttk.Button(self.frame, text=f'({_height - i - 1}, {j})', width=int(196/max(1, _width)), command=partial(dialogbox, i, j, self.gridmdp, self.terminals, self.buttons, _height)) + self.buttons[i][j].grid(row=i, column=j, ipady=int(336/max(1, _height)) - 12) + + def initialize(self): + ''' runs initialize_dialogbox ''' + + _height = self.controller.shared_data['height'].get() + _width = self.controller.shared_data['width'].get() + initialize_dialogbox(_width, _height, self.gridmdp, self.terminals, self.buttons) + + def master_reset(self): + ''' runs external reset ''' + + _height = self.controller.shared_data['height'].get() + _width = self.controller.shared_data['width'].get() + if tkinter.messagebox.askokcancel('Reset', 'Are you sure you want to reset all cells?'): + external_reset(_width, _height, self.gridmdp, self.terminals, self.buttons) + + +class SolveMDP(tk.Frame): + + def __init__(self, parent, controller): + + tk.Frame.__init__(self, parent) + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(0, weight=1) + self.frame = tk.Frame(self) + self.frame.pack() + self.controller = controller + self.terminated = False + self.iterations = 0 + self.epsilon = 0.001 + self.delta = 0 + + def process_data(self, terminals, _height, _width, gridmdp): + ''' preprocess variables ''' + + flipped_terminals = [] + + for terminal in terminals: + flipped_terminals.append((terminal[1], _height - terminal[0] - 1)) + + grid_to_solve = [[0.0]*max(1, _width) for _ in range(max(1, _height))] + grid_to_show = [[0.0]*max(1, _width) for _ in range(max(1, _height))] + + for i in range(max(1, _height)): + for j in range(max(1, _width)): + if gridmdp[i][j] == WALL_VALUE: + grid_to_show[i][j] = 0.0 + grid_to_solve[i][j] = None + + else: + grid_to_show[i][j] = grid_to_solve[i][j] = gridmdp[i][j] + + return flipped_terminals, grid_to_solve, np.flipud(grid_to_show) + + def create_graph(self, gridmdp, terminals, _height, _width): + ''' creates canvas and initializes value_iteration_paramteres ''' + + self._height = _height + self._width = _width + self.controller.menu_bar.entryconfig('Edit', state=tk.DISABLED) + self.controller.menu_bar.entryconfig('Build', state=tk.DISABLED) + + self.terminals, self.gridmdp, self.grid_to_show = self.process_data(terminals, _height, _width, gridmdp) + self.sequential_decision_environment = GridMDP(self.gridmdp, terminals=self.terminals) + + self.initialize_value_iteration_parameters(self.sequential_decision_environment) + + self.canvas = FigureCanvasTkAgg(fig, self.frame) + self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True) + self.anim = animation.FuncAnimation(fig, self.animate_graph, interval=50) + self.canvas.show() + + def animate_graph(self, i): + ''' performs value iteration and animates graph ''' + + # cmaps to use: bone_r, Oranges, inferno, BrBG, copper + self.iterations += 1 + x_interval = max(2, len(self.gridmdp[0])) + y_interval = max(2, len(self.gridmdp)) + x = np.linspace(0, len(self.gridmdp[0]) - 1, x_interval) + y = np.linspace(0, len(self.gridmdp) - 1, y_interval) + + sub.clear() + sub.imshow(self.grid_to_show, cmap='BrBG', aspect='auto', interpolation='none', extent=extents(x) + extents(y), origin='lower') + fig.tight_layout() + + U = self.U1.copy() + + for s in self.sequential_decision_environment.states: + self.U1[s] = self.R(s) + self.gamma * max([sum([p * U[s1] for (p, s1) in self.T(s, a)]) for a in self.sequential_decision_environment.actions(s)]) + self.delta = max(self.delta, abs(self.U1[s] - U[s])) + + self.grid_to_show = grid_to_show = [[0.0]*max(1, self._width) for _ in range(max(1, self._height))] + for k, v in U.items(): + self.grid_to_show[k[1]][k[0]] = v + + if (self.delta < self.epsilon * (1 - self.gamma) / self.gamma) or (self.iterations > 60) and self.terminated == False: + self.terminated = True + display(self.grid_to_show, self._height, self._width) + + ax = fig.gca() + ax.xaxis.set_major_locator(MaxNLocator(integer=True)) + ax.yaxis.set_major_locator(MaxNLocator(integer=True)) + + def initialize_value_iteration_parameters(self, mdp): + ''' initializes value_iteration parameters ''' + + self.U1 = {s: 0 for s in mdp.states} + self.R, self.T, self.gamma = mdp.R, mdp.T, mdp.gamma + + def value_iteration_metastep(self, mdp, iterations=20): + ''' runs value_iteration ''' + + U_over_time = [] + U1 = {s: 0 for s in mdp.states} + R, T, gamma = mdp.R, mdp.T, mdp.gamma + + for _ in range(iterations): + U = U1.copy() + + for s in mdp.states: + U1[s] = R(s) + gamma * max([sum([p * U[s1] for (p, s1) in T(s, a)]) for a in mdp.actions(s)]) + + U_over_time.append(U) + return U_over_time + + +if __name__ == '__main__': + app = MDPapp() + app.geometry('1280x720') + app.mainloop() \ No newline at end of file diff --git a/mdp.ipynb b/mdp.ipynb index e288d1b49..af46f948c 100644 --- a/mdp.ipynb +++ b/mdp.ipynb @@ -65,12 +65,156 @@ { "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", + "
class MDP:\n",
+       "\n",
+       "    """A Markov Decision Process, defined by an initial state, transition model,\n",
+       "    and reward function. We also keep track of a gamma value, for use by\n",
+       "    algorithms. The transition model is represented somewhat differently from\n",
+       "    the text. Instead of P(s' | s, a) being a probability number for each\n",
+       "    state/state/action triplet, we instead have T(s, a) return a\n",
+       "    list of (p, s') pairs. We also keep track of the possible states,\n",
+       "    terminal states, and actions for each state. [page 646]"""\n",
+       "\n",
+       "    def __init__(self, init, actlist, terminals, transitions={}, states=None, gamma=.9):\n",
+       "        if not (0 < gamma <= 1):\n",
+       "            raise ValueError("An MDP must have 0 < gamma <= 1")\n",
+       "\n",
+       "        if states:\n",
+       "            self.states = states\n",
+       "        else:\n",
+       "            self.states = set()\n",
+       "        self.init = init\n",
+       "        self.actlist = actlist\n",
+       "        self.terminals = terminals\n",
+       "        self.transitions = transitions\n",
+       "        self.gamma = gamma\n",
+       "        self.reward = {}\n",
+       "\n",
+       "    def R(self, state):\n",
+       "        """Return a numeric reward for this state."""\n",
+       "        return self.reward[state]\n",
+       "\n",
+       "    def T(self, state, action):\n",
+       "        """Transition model. From a state and an action, return a list\n",
+       "        of (probability, result-state) pairs."""\n",
+       "        if(self.transitions == {}):\n",
+       "            raise ValueError("Transition model is missing")\n",
+       "        else:\n",
+       "            return self.transitions[state][action]\n",
+       "\n",
+       "    def actions(self, state):\n",
+       "        """Set of actions that can be performed in this state. By default, a\n",
+       "        fixed list of actions, except for terminal states. Override this\n",
+       "        method if you need to specialize by state."""\n",
+       "        if state in self.terminals:\n",
+       "            return [None]\n",
+       "        else:\n",
+       "            return self.actlist\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "%psource MDP" + "psource(MDP)" ] }, { @@ -198,12 +342,154 @@ { "cell_type": "code", "execution_count": 6, - "metadata": { - "collapsed": true - }, - "outputs": [], + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
class GridMDP(MDP):\n",
+       "\n",
+       "    """A two-dimensional grid MDP, as in [Figure 17.1]. All you have to do is\n",
+       "    specify the grid as a list of lists of rewards; use None for an obstacle\n",
+       "    (unreachable state). Also, you should specify the terminal states.\n",
+       "    An action is an (x, y) unit vector; e.g. (1, 0) means move east."""\n",
+       "\n",
+       "    def __init__(self, grid, terminals, init=(0, 0), gamma=.9):\n",
+       "        grid.reverse()  # because we want row 0 on bottom, not on top\n",
+       "        MDP.__init__(self, init, actlist=orientations,\n",
+       "                     terminals=terminals, gamma=gamma)\n",
+       "        self.grid = grid\n",
+       "        self.rows = len(grid)\n",
+       "        self.cols = len(grid[0])\n",
+       "        for x in range(self.cols):\n",
+       "            for y in range(self.rows):\n",
+       "                self.reward[x, y] = grid[y][x]\n",
+       "                if grid[y][x] is not None:\n",
+       "                    self.states.add((x, y))\n",
+       "\n",
+       "    def T(self, state, action):\n",
+       "        if action is None:\n",
+       "            return [(0.0, state)]\n",
+       "        else:\n",
+       "            return [(0.8, self.go(state, action)),\n",
+       "                    (0.1, self.go(state, turn_right(action))),\n",
+       "                    (0.1, self.go(state, turn_left(action)))]\n",
+       "\n",
+       "    def go(self, state, direction):\n",
+       "        """Return the state that results from going in this direction."""\n",
+       "        state1 = vector_add(state, direction)\n",
+       "        return state1 if state1 in self.states else state\n",
+       "\n",
+       "    def to_grid(self, mapping):\n",
+       "        """Convert a mapping from (x, y) to v into a [[..., v, ...]] grid."""\n",
+       "        return list(reversed([[mapping.get((x, y), None)\n",
+       "                               for x in range(self.cols)]\n",
+       "                              for y in range(self.rows)]))\n",
+       "\n",
+       "    def to_arrows(self, policy):\n",
+       "        chars = {\n",
+       "            (1, 0): '>', (0, 1): '^', (-1, 0): '<', (0, -1): 'v', None: '.'}\n",
+       "        return self.to_grid({s: chars[a] for (s, a) in policy.items()})\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "%psource GridMDP" + "psource(GridMDP)" ] }, { @@ -478,7 +764,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Move the slider above to observe how the utility changes across iterations. It is also possible to move the slider using arrow keys or to jump to the value by directly editing the number with a double click. The **Visualize Button** will automatically animate the slider for you. The **Extra Delay Box** allows you to set time delay in seconds upto one second for each time step." + "Move the slider above to observe how the utility changes across iterations. It is also possible to move the slider using arrow keys or to jump to the value by directly editing the number with a double click. The **Visualize Button** will automatically animate the slider for you. The **Extra Delay Box** allows you to set time delay in seconds upto one second for each time step. There is also an interactive editor for grid-world problems `grid_mdp.py` in the gui folder for you to play around with." ] } ], @@ -2990,4 +3276,4 @@ }, "nbformat": 4, "nbformat_minor": 1 -} +} \ No newline at end of file From beaea67b6736cd339a9d78dee5d98441fb01a76f Mon Sep 17 00:00:00 2001 From: Vinay Varma Date: Mon, 12 Feb 2018 15:21:39 +0530 Subject: [PATCH 013/269] modify AC3 algorithm (#717) * added submodule * fixed ac3 in csp.py * added a test to verify the modified ac3 algorithm in csp.py * Update .gitmodules --- csp.py | 2 +- tests/test_csp.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/csp.py b/csp.py index 9e933c266..62772c322 100644 --- a/csp.py +++ b/csp.py @@ -168,7 +168,7 @@ def AC3(csp, queue=None, removals=None): if not csp.curr_domains[Xi]: return False for Xk in csp.neighbors[Xi]: - if Xk != Xi: + if Xk != Xj: queue.append((Xk, Xi)) return True diff --git a/tests/test_csp.py b/tests/test_csp.py index 4e2c4f119..f63e657aa 100644 --- a/tests/test_csp.py +++ b/tests/test_csp.py @@ -210,6 +210,13 @@ def test_AC3(): assert AC3(csp, removals=removals) is True assert (removals == [('A', 1), ('A', 3), ('B', 1), ('B', 3)] or removals == [('B', 1), ('B', 3), ('A', 1), ('A', 3)]) + + domains = {'A': [ 2, 4], 'B': [ 3, 5]} + constraints = lambda X, x, Y, y: int(x) > int (y) + removals=[] + csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) + + assert AC3(csp, removals=removals) def test_first_unassigned_variable(): From 504c34ee94819a6274178d7befd7460019ecf4f0 Mon Sep 17 00:00:00 2001 From: Apurv Bajaj Date: Mon, 12 Feb 2018 15:22:11 +0530 Subject: [PATCH 014/269] Add vacuum_world.ipynb (#721) * Added vacuum_world.ipynb * Add psource for environment --- README.md | 14 +- images/model_based_reflex_agent.jpg | Bin 0 -> 57354 bytes images/model_goal_based_agent.jpg | Bin 0 -> 61937 bytes images/model_utility_based_agent.jpg | Bin 0 -> 69438 bytes images/simple_reflex_agent.jpg | Bin 0 -> 40659 bytes vacuum_world.ipynb | 563 +++++++++++++++++++++++++++ 6 files changed, 570 insertions(+), 7 deletions(-) create mode 100644 images/model_based_reflex_agent.jpg create mode 100644 images/model_goal_based_agent.jpg create mode 100644 images/model_utility_based_agent.jpg create mode 100644 images/simple_reflex_agent.jpg create mode 100644 vacuum_world.ipynb diff --git a/README.md b/README.md index dd8c0b38a..99b19c773 100644 --- a/README.md +++ b/README.md @@ -30,15 +30,15 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | **Figure** | **Name (in 3rd edition)** | **Name (in repository)** | **File** | **Tests** | **Notebook** |:-------|:----------------------------------|:------------------------------|:--------------------------------|:-----|:---------| -| 2 | Random-Vacuum-Agent | `RandomVacuumAgent` | [`agents.py`][agents] | Done | | -| 2 | Model-Based-Vacuum-Agent | `ModelBasedVacuumAgent` | [`agents.py`][agents] | Done | | +| 2 | Random-Vacuum-Agent | `RandomVacuumAgent` | [`agents.py`][agents] | Done | Included | +| 2 | Model-Based-Vacuum-Agent | `ModelBasedVacuumAgent` | [`agents.py`][agents] | Done | Included | | 2.1 | Environment | `Environment` | [`agents.py`][agents] | Done | Included | | 2.1 | Agent | `Agent` | [`agents.py`][agents] | Done | Included | -| 2.3 | Table-Driven-Vacuum-Agent | `TableDrivenVacuumAgent` | [`agents.py`][agents] | | | -| 2.7 | Table-Driven-Agent | `TableDrivenAgent` | [`agents.py`][agents] | | | -| 2.8 | Reflex-Vacuum-Agent | `ReflexVacuumAgent` | [`agents.py`][agents] | Done | | -| 2.10 | Simple-Reflex-Agent | `SimpleReflexAgent` | [`agents.py`][agents] | | | -| 2.12 | Model-Based-Reflex-Agent | `ReflexAgentWithState` | [`agents.py`][agents] | | | +| 2.3 | Table-Driven-Vacuum-Agent | `TableDrivenVacuumAgent` | [`agents.py`][agents] | | Included | +| 2.7 | Table-Driven-Agent | `TableDrivenAgent` | [`agents.py`][agents] | | Included | +| 2.8 | Reflex-Vacuum-Agent | `ReflexVacuumAgent` | [`agents.py`][agents] | Done | Included | +| 2.10 | Simple-Reflex-Agent | `SimpleReflexAgent` | [`agents.py`][agents] | | Included | +| 2.12 | Model-Based-Reflex-Agent | `ReflexAgentWithState` | [`agents.py`][agents] | | Included | | 3 | Problem | `Problem` | [`search.py`][search] | Done | | | 3 | Node | `Node` | [`search.py`][search] | Done | | | 3 | Queue | `Queue` | [`utils.py`][utils] | Done | | diff --git a/images/model_based_reflex_agent.jpg b/images/model_based_reflex_agent.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b6c12ed099011b9cca7411c10d71bbd3bb10fb0a GIT binary patch literal 57354 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 literal 0 HcmV?d00001 diff --git a/images/model_goal_based_agent.jpg b/images/model_goal_based_agent.jpg new file mode 100644 index 0000000000000000000000000000000000000000..93d6182b4b884fc446709229801225fb7ca0d846 GIT binary patch literal 61937 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 R!fx{;e`_oxI|`f9xz0!Wn}Tzn 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.ipynb](https://github.com/aimacode/aima-python/blob/master/agents.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Agent Programs\n", + "\n", + "An agent program takes the current percept as input from the sensors and return an action to the actuators. There is a difference between an agent program and an agent function: an agent program takes the current percept as input whereas an agent function takes the entire percept history. \n", + "The agent program takes just the current percept as input because nothing more is available from the environment; if the agent's actions need to depend on the entire percept sequence, the agent will have to remember the percept. \n", + "\n", + "We'll discuss the following agent programs here with the help of the vacuum world example:\n", + "\n", + "* Random Agent Program\n", + "* Table Driven Agent Program\n", + "* Simple Reflex Agent Program\n", + "* Model-Based Reflex Agent Program\n", + "* Goal-Based Agent Program\n", + "* Utility-Based Agent Program" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Random Agent Program\n", + "\n", + "A random agent program, as the name suggests, choses an action at random, without taking into account the percepts. \n", + "Here, we will demonstrate a random vacuum agent for a trivial vacuum environment, that is, the two-state environment." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's begin by importing all the functions from the agents module:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "ename": "FileNotFoundError", + "evalue": "[Errno 2] No such file or directory: '/home/apurv/aima-python/aima-data/orings.csv'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mFileNotFoundError\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;32mfrom\u001b[0m \u001b[0magents\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mnotebook\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpsource\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/aima-python/notebook.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mgames\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mTicTacToe\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malphabeta_player\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrandom_player\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mFig52Extended\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minfinity\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mlogic\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mparse_definite_clause\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstandardize_variables\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0munify\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubst\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mlearning\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mDataSet\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mIPython\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdisplay\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mHTML\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdisplay\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mcollections\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mCounter\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdefaultdict\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/aima-python/learning.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1105\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1106\u001b[0m orings = DataSet(name='orings', target='Distressed',\n\u001b[0;32m-> 1107\u001b[0;31m attrnames=\"Rings Distressed Temp Pressure Flightnum\")\n\u001b[0m\u001b[1;32m 1108\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1109\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/aima-python/learning.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, examples, attrs, attrnames, target, inputs, values, distance, name, source, exclude)\u001b[0m\n\u001b[1;32m 96\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mparse_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mexamples\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 97\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mexamples\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 98\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mparse_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mopen_data\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m'.csv'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\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 99\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 100\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mexamples\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/aima-python/utils.py\u001b[0m in \u001b[0;36mopen_data\u001b[0;34m(name, mode)\u001b[0m\n\u001b[1;32m 414\u001b[0m \u001b[0maima_file\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maima_root\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'aima-data'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 415\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 416\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maima_file\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 417\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 418\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: '/home/apurv/aima-python/aima-data/orings.csv'" + ] + } + ], + "source": [ + "from agents import *\n", + "from notebook import psource" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us first see how we define the TrivialVacuumEnvironment. Run the next cell to see how abstract class TrivialVacuumEnvironment is defined in agents module:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "%psource TrivialVacuumEnvironment" + ] + }, + { + "cell_type": "code", + "execution_count": 119, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "State of the Environment: {(1, 0): 'Clean', (0, 0): 'Dirty'}.\n" + ] + } + ], + "source": [ + "# These are the two locations for the two-state environment.\n", + "loc_A, loc_B = (0, 0), (1, 0)\n", + "\n", + "# Initialise the two-state environment.\n", + "trivial_vacuum_env = TrivialVacuumEnvironment()\n", + "\n", + "# Check the intial state of the environment.\n", + "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's create our agent now. This agent will chose any of the actions from 'Right', 'Left', 'Suck' and 'NoOp' (No Operation) randomly. " + ] + }, + { + "cell_type": "code", + "execution_count": 120, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Create the random agent.\n", + "random_agent = Agent(program=RandomAgentProgram(['Right', 'Left', 'Suck', 'NoOp']))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now add our agent to the environment." + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RandomVacuumAgent is located at (0, 0).\n" + ] + } + ], + "source": [ + "# Add agent to the environment.\n", + "trivial_vacuum_env.add_thing(random_agent)\n", + "\n", + "print(\"RandomVacuumAgent is located at {}.\".format(random_agent.location))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's run our environment now." + ] + }, + { + "cell_type": "code", + "execution_count": 122, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "State of the Environment: {(1, 0): 'Clean', (0, 0): 'Dirty'}.\n", + "RandomVacuumAgent is located at (0, 0).\n" + ] + } + ], + "source": [ + "# Running the environment.\n", + "trivial_vacuum_env.step()\n", + "\n", + "# Check the current state of the environment.\n", + "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))\n", + "\n", + "print(\"RandomVacuumAgent is located at {}.\".format(random_agent.location))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Table Driven Agent Program\n", + "\n", + "A table driven agent program keeps track of the percept sequence and then uses it to index into a table of actions to decide what to do. The table represents eplicitly the agent function that the agent program embodies. \n", + "In the two-state vacuum world, the table would consist of all the possible states of the agent." + ] + }, + { + "cell_type": "code", + "execution_count": 123, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "table = {((loc_A, 'Clean'),): 'Right',\n", + " ((loc_A, 'Dirty'),): 'Suck',\n", + " ((loc_B, 'Clean'),): 'Left',\n", + " ((loc_B, 'Dirty'),): 'Suck',\n", + " ((loc_A, 'Clean'), (loc_A, 'Clean')): 'Right',\n", + " ((loc_A, 'Clean'), (loc_A, 'Dirty')): 'Suck',\n", + " ((loc_A, 'Clean'), (loc_A, 'Clean'), (loc_A, 'Clean')): 'Right',\n", + " ((loc_A, 'Clean'), (loc_A, 'Clean'), (loc_A, 'Dirty')): 'Suck',\n", + " }" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now create a table driven agent program for our two-state environment." + ] + }, + { + "cell_type": "code", + "execution_count": 124, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Create a table driven agent.\n", + "table_driven_agent = Agent(program=TableDrivenAgentProgram(table=table))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since we are using the same environment, let us remove the previously added random agent from the environment to avoid confusion." + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "trivial_vacuum_env.delete_thing(random_agent)" + ] + }, + { + "cell_type": "code", + "execution_count": 126, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TableDrivenVacuumAgent is located at (0, 0).\n" + ] + } + ], + "source": [ + "# Add the table driven agent to the environment\n", + "trivial_vacuum_env.add_thing(table_driven_agent)\n", + "\n", + "print(\"TableDrivenVacuumAgent is located at {}.\".format(table_driven_agent.location))" + ] + }, + { + "cell_type": "code", + "execution_count": 127, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "State of the Environment: {(1, 0): 'Clean', (0, 0): 'Clean'}.\n", + "TableDrivenVacuumAgent is located at (0, 0).\n" + ] + } + ], + "source": [ + "# Run the environment.\n", + "trivial_vacuum_env.step()\n", + "\n", + "# Check the current state of the environment.\n", + "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))\n", + "\n", + "print(\"TableDrivenVacuumAgent is located at {}.\".format(table_driven_agent.location))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple Reflex Agent Program\n", + "\n", + "A simple reflex agent program selects actions on the basis of the current percept, ignoring the rest of the percept history. These agents work on a **condition-action rule** (also called **situation-action rule**, **production** or **if-then rule**), which tell the agent the action to trigger when a particular situtation is encountered. \n", + "\n", + "The schematic diagram shown in **Figure 2.9** of the book will make this more clear:\n", + "\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now create a simple reflex agent for the environment." + ] + }, + { + "cell_type": "code", + "execution_count": 131, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Delete the previously added table driven agent.\n", + "trivial_vacuum_env.delete_thing(table_driven_agent)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To create our agent, we need two functions: INTERPRET-INPUT function, which generates an abstracted description of the current state from the percerpt and the RULE-MATCH function, which returns the first rule in the set of rules that matches the given state description." + ] + }, + { + "cell_type": "code", + "execution_count": 134, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# TODO: Implement these functions for two-dimensional environment.\n", + "# Interpret-input function for the two-state environment.\n", + "def interpret_input(percept):\n", + " pass\n", + "\n", + "rules = None\n", + "\n", + "# Rule-match function for the two-state environment.\n", + "def rule_match(state, rule):\n", + " for rule in rules:\n", + " if rule.matches(state):\n", + " return rule \n", + " \n", + "# Create a simple reflex agent the two-state environment.\n", + "simple_reflex_agent = ReflexVacuumAgent()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now add the agent to the environment:" + ] + }, + { + "cell_type": "code", + "execution_count": 135, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SimpleReflexVacuumAgent is located at (0, 0).\n" + ] + } + ], + "source": [ + "trivial_vacuum_env.add_thing(simple_reflex_agent)\n", + "\n", + "print(\"SimpleReflexVacuumAgent is located at {}.\".format(simple_reflex_agent.location))" + ] + }, + { + "cell_type": "code", + "execution_count": 137, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "State of the Environment: {(1, 0): 'Clean', (0, 0): 'Clean'}.\n", + "SimpleReflexVacuumAgent is located at (0, 0).\n" + ] + } + ], + "source": [ + "# Run the environment.\n", + "trivial_vacuum_env.step()\n", + "\n", + "# Check the current state of the environment.\n", + "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))\n", + "\n", + "print(\"SimpleReflexVacuumAgent is located at {}.\".format(simple_reflex_agent.location))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model-Based Reflex Agent Program\n", + "\n", + "A model-based reflex agent maintains some sort of internal state that depends on the percept history and thereby reflects at least some of the unobserved aspects of the current state. In additon to this, it also requires a model of the world, that is, knowledge about \"how the world works\". \n", + "\n", + "The schematic diagram shown in figure 2.11 of the book will make this more clear:\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now create a model-based reflex agent for the environment:" + ] + }, + { + "cell_type": "code", + "execution_count": 139, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "list.remove(x): x not in list\n", + " in Environment delete_thing\n", + " Thing to be removed: at (0, 0)\n", + " from list: []\n" + ] + } + ], + "source": [ + "# Delete the previously added simple reflex agent.\n", + "trivial_vacuum_env.delete_thing(simple_reflex_agent)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We need a another function UPDATE-STATE which will be reponsible for creating a new state description." + ] + }, + { + "cell_type": "code", + "execution_count": 140, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ModelBasedVacuumAgent is located at (0, 0).\n" + ] + } + ], + "source": [ + "# TODO: Implement this function for the two-dimensional environment.\n", + "def update_state(state, action, percept, model):\n", + " pass\n", + "\n", + "# Create a model-based reflex agent.\n", + "model_based_reflex_agent = ModelBasedVacuumAgent()\n", + "\n", + "# Add the agent to the environment.\n", + "trivial_vacuum_env.add_thing(model_based_reflex_agent)\n", + "\n", + "print(\"ModelBasedVacuumAgent is located at {}.\".format(model_based_reflex_agent.location))" + ] + }, + { + "cell_type": "code", + "execution_count": 143, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "State of the Environment: {(1, 0): 'Clean', (0, 0): 'Clean'}.\n", + "ModelBasedVacuumAgent is located at (1, 0).\n" + ] + } + ], + "source": [ + "# Run the environment.\n", + "trivial_vacuum_env.step()\n", + "\n", + "# Check the current state of the environment.\n", + "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))\n", + "\n", + "print(\"ModelBasedVacuumAgent is located at {}.\".format(model_based_reflex_agent.location))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Goal-Based Agent Program \n", + "\n", + "A goal-based agent needs some sort of goal information that describes situations that are desirable, apart from the current state description. \n", + "Figure 2.13 of the book shows a model-based, goal-based agent: \n", + "\n", + "\n", + "Search (Chapters 3 to 5) and Planning (Chapters 10 to 11) are the subfields of AI devoted to finding action sequences that achieve the agent's goals.\n", + "\n", + "## Utility-Based Agent Program\n", + "\n", + "A utility-based agent maximizes its utility using the agent's utility function, which is essentially an internalization of the agent's performance measure. \n", + "Figure 2.14 of the book shows a model-based, utility-based agent:\n", + "\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 74dff567e460af7ef5eb75f6091172b7812652e9 Mon Sep 17 00:00:00 2001 From: Apurv Bajaj Date: Mon, 12 Feb 2018 15:23:00 +0530 Subject: [PATCH 015/269] Add explanation for Simple Problem Solving Agent (#724) * Add SimpleProblemSolvingAgent * Fix typo in search.py --- README.md | 2 +- search.ipynb | 133 +++++++++++++++++++++++++++++++++++---------------- search.py | 2 +- 3 files changed, 94 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 99b19c773..91ce5b37e 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 3 | Problem | `Problem` | [`search.py`][search] | Done | | | 3 | Node | `Node` | [`search.py`][search] | Done | | | 3 | Queue | `Queue` | [`utils.py`][utils] | Done | | -| 3.1 | Simple-Problem-Solving-Agent | `SimpleProblemSolvingAgent` | [`search.py`][search] | | | +| 3.1 | Simple-Problem-Solving-Agent | `SimpleProblemSolvingAgent` | [`search.py`][search] | | Included | | 3.2 | Romania | `romania` | [`search.py`][search] | Done | Included | | 3.7 | Tree-Search | `tree_search` | [`search.py`][search] | Done | | | 3.7 | Graph-Search | `graph_search` | [`search.py`][search] | Done | | diff --git a/search.ipynb b/search.ipynb index 5415dd89a..6da1d0ef5 100644 --- a/search.ipynb +++ b/search.ipynb @@ -15,10 +15,25 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": true, "scrolled": true }, - "outputs": [], + "outputs": [ + { + "ename": "FileNotFoundError", + "evalue": "[Errno 2] No such file or directory: '/home/apurv/aima-python/aima-data/orings.csv'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mFileNotFoundError\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;32mfrom\u001b[0m \u001b[0msearch\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mnotebook\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpsource\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# Needed to hide warnings in the matplotlib sections\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mwarnings\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/aima-python/notebook.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mgames\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mTicTacToe\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malphabeta_player\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrandom_player\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mFig52Extended\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minfinity\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mlogic\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mparse_definite_clause\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstandardize_variables\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0munify\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubst\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mlearning\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mDataSet\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mIPython\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdisplay\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mHTML\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdisplay\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mcollections\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mCounter\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdefaultdict\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/aima-python/learning.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1105\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1106\u001b[0m orings = DataSet(name='orings', target='Distressed',\n\u001b[0;32m-> 1107\u001b[0;31m attrnames=\"Rings Distressed Temp Pressure Flightnum\")\n\u001b[0m\u001b[1;32m 1108\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1109\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/aima-python/learning.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, examples, attrs, attrnames, target, inputs, values, distance, name, source, exclude)\u001b[0m\n\u001b[1;32m 96\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mparse_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mexamples\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 97\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mexamples\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 98\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mparse_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mopen_data\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m'.csv'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\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 99\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 100\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mexamples\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/aima-python/utils.py\u001b[0m in \u001b[0;36mopen_data\u001b[0;34m(name, mode)\u001b[0m\n\u001b[1;32m 414\u001b[0m \u001b[0maima_file\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maima_root\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'aima-data'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 415\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 416\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maima_file\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 417\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 418\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: '/home/apurv/aima-python/aima-data/orings.csv'" + ] + } + ], "source": [ "from search import *\n", "from notebook import psource\n", @@ -83,7 +98,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { "collapsed": true }, @@ -125,7 +140,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "collapsed": true }, @@ -143,7 +158,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "collapsed": true }, @@ -190,7 +205,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": { "collapsed": true }, @@ -217,17 +232,11 @@ }, { "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'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" - ] - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "romania_locations = romania_map.locations\n", "print(romania_locations)" @@ -242,7 +251,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": { "collapsed": true }, @@ -268,7 +277,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { "collapsed": true }, @@ -314,7 +323,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "collapsed": true }, @@ -367,7 +376,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": { "collapsed": true }, @@ -410,22 +419,12 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": { + "collapsed": true, "scrolled": true }, - "outputs": [ - { - "data": { - "image/png": "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\nTpyoCxcuWDsOAAAAAFtjGJnX6Lx6NXf7Xb2auZ9h5EusmTNnqmTJknrllVdyfL1q1ary9fWVJEVF\nReVYVoaGhqpSpUrm57/++qtMJpP+85//aPTo0SpbtqycnJx08eJFxcbGymQyKT4+Xt27d5e7u7sa\nN25s3vfTTz9Vq1at5ObmJhcXFwUGBurbb7+1OF9AQICaNWumuLg4NWzYUM7Ozqpbt642bNhgkWn5\n8uU6deqUeUn5PzP+U3h4uEqXLq3U1FSL7UlJSXJzc9PYsWNv+RneiWXLlmVb1p6enq4WLVqoatWq\nunz5sqT/fcZHjhxRy5Yt5ezsLG9vb02aNEkZGRm3PIdhGJo7d65q1qwpR0dHeXt7a/DgweZjZzGZ\nTBo/frxmzJihypUry9HRUUeOHMlxGfudfNZZ3nnnHdWqVUtFixZVvXr19MEHHyggIEABAQF3/8HZ\nAMpOFDqXL1/W7NmzFRkZae0ohV6DBg307LPPatKkSdaOAgAAYDUP6rX5gAIvISHzZkR348yZzP3z\nWHp6unbt2qW2bduqaNGieX78qVOn6scff9SSJUu0YcMGi3OEhISocuXKWrdunWbMmCFJ2rp1q1q1\naiVXV1etWrVKq1evVlJSkpo3b64TJ05YHPv48eMaOnSoIiIitH79enl7e6t79+766aefJEkTJ05U\n+/btVapUKSUkJCghISHHgk6SBg4cqLNnz2Z7ffXq1UpOTs5xefqNDMNQWlpatkeWsLAwde/eXX37\n9tWpU6ckSZMnT9bnn3+u1atXq3jx4hbHe/rpp9W6dWtt3LhRwcHBmjx5sl5++eVbZhg/frwiIiLU\npk0bbd68WaNHj1ZsbKw6dOiQrSiNjY3V1q1bNWvWLG3dulVly5a96XFv91lL0o4dOxQSEqJatWpp\n/fr1GjlypIYNG6Yff/zxtp+drWMZOwqd+fPnq23btvLx8bF2FCjzD5vatWurX79+ql+/vrXjAAAA\n3HdpaWnq06ePIiIi1LBhQ2vHAR4Mw4ZJX3996zEnT+Z+VmeWq1el556Type/+ZgGDaSYmFwd9ty5\nc7p27ZoqVqx4d7luo3Tp0tqwYUOOs0G7deuWbTbp0KFD1aJFC23atMm8rWXLlqpSpYpmz56tmH+8\nv3Pnzik+Pl7Vq1eXlHm9SW9vb7333nsaN26cqlatqlKlSsnR0fG2S7Nr166tFi1aaPHixQoKCjJv\nX7x4sdq2bavKlSvf9r1OmzZN06ZNy7b9zz//lKenpyRpyZIlql+/vnr37q3IyEhNmTJFkydPtpjZ\nmqVfv37mGaVt27Y1T5QaNmyY3N3ds42/cOGCZs+erT59+mjhwoWSpMDAQJUqVUq9e/fWli1b1Llz\nZ/N4wzC0fft2FStWzLwtMTExx/d2u89akiIjI1W7dm2LX++6devKz89PNWrUuO3nZ8uY2YlC5eLF\ni5o3bx6zOgsQDw8PRUdHKzw8XEY+LRMBAAAoyOzt7dW0aVN17NhR3bt3v+lffgHkUnr63S9FN4zM\n/R8wTz/9dI5FpyR17drV4vmxY8d0/PhxhYSEWMyMdHZ2VtOmTRUfH28xvnr16ubyTZK8vLzk5eWl\n33///a6yvvjii9q1a5eOHTsmSdq/f7+++uqrO5rVKUkvvPCC9u/fn+3xz2LS3d1dq1evVnx8vAID\nA/XEE09ozJgxOR7vn6WrJPXo0UNXrlzJtqQ/y759+5SSkqJevXpl28/e3l6ffvqpxfannnrKoui8\nldt91unp6Tpw4ICeffZZi1/vRx999I6KYlvHzE4UKjExMerYsaPFbxqwvn79+mnJkiVas2aNevbs\nae04AAAA91WRIkU0aNAgPf/881q4cKFatGihDh06KDIy8qbXuwMKvTuZURkTI40ZI6Wk5P74Tk6Z\ns0eHDs39vrfg4eGhYsWK6bfffsvT42bx9va+49fO/v8S/7CwMIWFhWUbX6FCBYvnJUuWzDbGyclJ\nf//9991EVdeuXVWmTBktXrxYs2bN0uuvv66yZcuqU6dOd7S/t7e3/Pz8bjuuSZMmqlmzpo4ePaoh\nQ4bIzi7neX+lS5fO8XnWEvgbZd174sbP1d7eXh4eHtnuTXGrX5sb3e6zPnfunFJTU+Xl5ZVt3I3v\nozBiZicKjZSUFB06dEgTJ060dhTcoEiRIlqwYIFGjRqlK1euWDsOAACAVTg7O2v06NE6duyYHn74\nYT366KMaPHiwTp8+be1owIPJ319ycLi7fe3tpUaN8jaPMouwgIAA7dixQ9fv4A7xWdfcTLmhsD1/\n/nyO4282qzOn1zw8PCRJ06dPz3GG5ObNm2+b7144ODiob9++io2N1dmzZ7VmzRqFhYXJ3j5v5+VF\nR0fr2LFj8vX11fDhw3Xp0qUcx505cybH5+XKlctxfFYh+ccff1hsT0tL0/nz57MVlrf6tcktT09P\nOTg4mAvrf7rxfRRGlJ0oNOzt7fXee++pSpUq1o6CHDz++ONq2bKlpk6dau0oAAAAVlWiRAm9/PLL\nSkxMlKOjo+rWrauxY8dmmyUE4DaaNpVymPl2R0qXztw/H4wdO1bnz5/X6NGjc3z9l19+0TfffCNJ\n5mt7/nMp9cWLF/X555/fc46aNWuqUqVK+u677+Tn55ftkXVH+NxwcnLStWvX7nj8gAEDdPHiRXXv\n3l3Xr19Xv379cn3OW9mzZ4+mTp2qqVOnavPmzbp48aIGDhyY49j33nvP4vmaNWvk6uqqevXq5Ti+\nSZMmcnR01Jo1ayy2v/vuu0pLS8vXO6IXKVJEfn5+ev/99y0uB3fw4EH98ssv+XbeBwXL2FFo2NnZ\n5cvd7pB3XnnlFdWrV08vvPAClxoAAACFnpeXl+bMmaOIiAhNnjxZNWrU0LBhwzR06FC5ublZOx5Q\n8JlM0ujR0ogRubtRkbNz5n55OBPvn5544gnzd/vo0aMKDQ1VhQoV9Ndff2nnzp1aunSpVq9eLV9f\nX7Vr104lSpRQv379FB0drevXr+uVV16Rq6vrPecwmUx67bXX1KVLF6WkpCgoKEienp46c+aMPv/8\nc1WoUEERERG5Ombt2rV14cIFLVq0SH5+fipatOhNy0Ipc9Zk586dtWHDBnXq1EkPP/zwHZ/r1KlT\n2rdvX7btFStWlLe3t/766y+FhISoVatWGjlypEwmk5YsWaKgoCAFBgaqT58+Fvu98cYbysjIUKNG\njfTxxx9r6dKlioqKUokSJXI8f8mSJTVixAhNnz5dLi4uat++vRITEzVhwgQ1a9ZMHTp0uOP3cjei\no6PVtm1bde3aVf3799e5c+cUFRWlMmXK3HSpfmFRuN89gALF29tbY8aM0bBhw6wdBQAAoMAoX768\nFi9erISEBCUmJqp69eqKiYm56+vkAYVKWJjUsGHmNTjvhJOT9Oij0gsv5GusYcOG6bPPPpO7u7tG\njhypJ598UqGhoUpMTNTixYvN1610d3fXli1bZGdnp6CgIL300ksKDw9Xy5Yt8yRH+/btFR8fr+Tk\nZPXt21eBgYEaPXq0/vjjDzW9i5mtffv2VY8ePTRu3Dj5+/vf0fU3u3fvLkl3fGOiLLGxsWratGm2\nx9tvvy1J6t+/v65du6bly5ebl5B3795dYWFhGjx4sH766SeL423atEk7duxQ586dtWrVKk2YMOG2\n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\npKdgwkRKegrJqcmcunGKN2LeoPni5iSlJOVonoULF2YuvvXXxzfffAPAN998g4WFBXFxcTmWo3v3\n7nh6ev7rfhcvXiQ8PBwvLy8cHBxwcXGhVq1aDBo0iNTU1Ee65smTJ7GwsODzzz9/5LxbtmwhMjIy\nW88pBZOFSX8VRES4e/cudnZ2RscQERERkf9ZunQpbm5uNGzYEOCBHZ0mk4n33nuP0qVL06VLF43q\nKYCOHz+Oj4/PYx2bmp5K4OJA9ibu5W763X/d387Kjjrl6hDbIzbHOjwXLlxIr169WL58Oa6urlle\nq1q1KoULF+b333/n2LFj+Pr6Zlm4Nzt1796d7777jpMnTz5wnxs3buDv74+trS0RERFUqVKFa9eu\nER8fz5IlSzhy5AhOTk4Pfc2TJ09SuXJlPvvsM7p37/5IeceMGcOUKVPu+3Lj7t27xMfH4+npiYuL\nyyOd05w9ye9VXqdh7CJi1jIyMti6dSsHDhygR48elCpVyuhIIiIiIgJ06dIly/MHDV23sLCgdu3a\nvPnmm0ydOpXJkyfTvn17je4RAObFz+PAhQMPVegEuJt+l/0X9jM/fj79a/fP0Ww1atR4YGdl4cKF\nqVevXo5e/2EsW7aMs2fPcvToUXx9fTO3v/jii0yaNClP/J7Z2dnlifdK8g4NYxcRs2ZpacmtW7fY\ntm0bgwYNMjqOiIiIiDyGpk2bEhcXxzvvvENkZCR169Zl8+bNGt5u5kwmE+9++y63Um890nG3Um/x\n7rfvGvrz83fD2Bs1akTTpk2JiYkhICAAR0dH/Pz8WLNmTZZjExIS6N69OxUqVMDBwYFKlSrx2muv\ncePGjUfOce3aNQBKly5932t/LXSmpKQwevRo3N3dsbW1pUKFCowbN+5fh7o3atSIZ5999r7trq6u\nvPzyy8D/7+q8d10LCwusrf/o33vQMPZFixbh7++PnZ0dTz31FD179uTSpUv3XSMsLIwlS5bg7e1N\noUKFePrpp9m1a9c/Zpa8TcVOETFbKSkpAAQFBfHiiy+ybNkyNm/ebHAqEREREXkcFhYWtG3blgMH\nDhAREcHAgQMJDAxU0cKM7T63m8vJlx/r2EvJl9h9bnc2J8oqPT2dtLS0zEd6evq/HpOQkMDQoUOJ\niIhgxYoVlCpVihdffJFTp05l7pOYmIi7uzsffvghmzZt4s0332TTpk08//zzj5yxTp06AHTu3JmY\n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\nmzdn5syZVKhQwei4IiIiIiIiuSI1NZVFixYxYcIEatasyaRJk/D39zc6lohIvqMxwpKrChcuzJUr\nV/Dw8ADAysoK+GOOxAEDBrB48WJ++OEHBg0axK1bt4yMKpKFyWQiPT3d6BgiIiIiUkDZ2Njw8ssv\n89NPP9GsWTNatmxJt27dOHnypNHRRETyFRU7JdcVL178ga916tSJ9957jytXruDo6JiLqUT+WXJy\nMuXLl+f8+fNGRxERERGRAsze3p7Bgwdz8uRJqlatSr169di2bZvmkxcReUgaxi550vXr1ylWrJjR\nMUSyGD16NGfOnOHzzz83OoqIiIiImIlr167h5OSEra2t0VFERPIFFTvFMCaTCQsLC6NjiDy0pKQk\nfHx8WLp0KY0aNTI6joiIiIiIiIj8hYaxi2FOnz5NWlqa0TFEHpqTkxPTpk0jPDxc83eKiIiIiIiI\n5EEqdophunTpwsaNG42OIfJIQkJCKFKkCJ9++qnRUURERERERETkLzSMXQzxww8/0LJlS3755Res\nra2NjiPySA4fPsyzzz7L8ePHKVGihNFxREREREREROR/1Nkphpg/fz49e/ZUoVPyJX9/f0JCQhgz\nZozRUURERERERETkT9TZKbkuJSUFV1dXdu3ahaenp9FxRB7L9evX8fHxYcOGDQQEBBgdR0RERERE\nRERQZ6cYYO3atfj4+KjQKflasWLFmDRpEuHh4eg7IxEREREREZG8QcVOyXXz58+nT58+RscQeWK9\ne/fmzp07LFmyxOgoIiIiIiIiIoKGsUsuS0xMpFq1apw7dw5HR0ej44g8se+++44XX3yREydO4Ozs\nbHQcEREREREREbOmzk7JVQsXLiQ4OFiFTikw6tWrR4sWLZg0aZLRUURERERERETMnjo7JddkZGRQ\nuXJlli5dSp06dYyOI5JtLl68iJ+fH99++y1VqlQxOo6IiIiImLH09HTS0tKws7MzOoqIiCHU2Sm5\nZseOHTg6OvL0008bHUUkW5UuXZrRo0czaNAgLVYkIiIiIoZr06YNO3bsMDqGiIghVOyUXDNv3jz6\n9OmDhYWF0VFEsl14eDhnzpxhzZo1RkcRERERETNmZWVFjx49GDNmjL6IFxGzpGHskitu3LhBhQoV\nOHnyJC4uLkbHEckR33zzDf369eOHH37AwcHB6DgiIiIiYqbS0tLw9fVl1qxZtGjRwug4IiK5Sp2d\n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\ndWw2m2w2m9zd3U1HAYC7UOzM4Gw2m5YuXfpY55g9e7a8vb3tlCjlXL16Vf7+/nrvvfd08eJF03EA\nGFSgQAGNHz/e4ddx9P3y1VdfZeV2wE5atWql0qVLa8iQIaajAECqxUgSAKkRxc506s4nbfd7dOjQ\nQZIUGRmphg0bPta1WrZsqWPHjtkhdcr67LPPtHv3bkVFRalYsWJ69913df78edOxANhZhw4dku59\nLi4uevLJJ/XWW2/pjz/+SNpn27Zt6tGjh8OzpMT90tXVVd27d9fhw4fl7++vgIAAvfvuu7py5YpD\nrwukNzabTZ988omWLFmidevWmY4DAACAB0SxM52KjIxMekybNu2ubR999JEkKXfu3I899MDT01M5\nc+Z87MyPIy4u7pGOy58/v6ZMmaK9e/fq9u3bKlGihPr27atz587ZOSEAk2rWrKnIyEidOHFC06dP\nV3h4eLLipp+fnzJlyuTwHCl5v/T29tbw4cO1f/9+RUdHq1ixYnr//fcZkgs8hOzZs2vatGnq0KGD\n/vzzT9NxAAAA8AAodqZTuXPnTnpkyZLlrm2ZM2eWlHwY+4kTJ2Sz2bRw4UJVrVpVnp6eKlu2rPbs\n2aN9+/apSpUq8vLy0gsvvJBsWOT/Dss8ffq0GjdurGzZsilTpkwqVqyYFi5cmPT63r17VbNmTXl6\neipbtmx3/QGxbds21a5dWzly5JCvr69eeOEF/frrr8nen81m05QpU9S0aVN5eXlpyJAhSkhIUOfO\nnVWwYEF5enqqSJEiev/995WYmPivP687c3Pt379fTk5OKlmypHr27KkzZ848wk8fQGrj7u6u3Llz\nK1++fKpdu7ZatmypH374Ien1/x3GbrPZ9Omnn6px48bKlCmT/P39tW7dOp05c0Z16tSRl5eXypQp\nk2xezDv3wrVr16pkyZLy8vJStWrV/vF+KUkrVqxQxYoV5enpqezZs6thw4aKiYm5Zy5Jevnll9Wz\nZ88Hfu+5c+fWp59+qg0bNmjz5s0qWrSo5syZw8rtwAOqV6+e6tevrz59+piOAgBGsKYxgLSGYifu\nMnz4cA0cOFA7d+5UlixZ9Prrr6tXr14KCwvT1q1bFRMTo969e9/3+B49eig6Olrr1q3T/v379eGH\nHyYVXKOiolSnTh15e3tr69atWr58uTZt2qROnTolHX/jxg21bdtWv/zyi7Zu3aoyZcqofv36dw3B\n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\nxyEqFwEBAfDw8OBUJxGRAuGZnUSkUh4+fIi4uDj069dPdBSiD1JYWIjS0lLo6OiIjkJKJDg4GBER\nETh16hQ0NDREx6myTpw4AQ8PD9StWxehoaGwsbERHUmh+Pr6IjU1Ffv37+dRMqTUys7qvH79OmrW\nrCk6DhER/T9OdhKRSrl37x63ZFKVsGXLFoSEhKCkpER0FFISsbGxWLFiBXbv3s2is4J1794dly9f\nxtChQ9GjRw9MmTIFjx49Eh1LYSxevBg3b97Etm3bREch+iB79uyBh4cHi04iIgXDspOIVErt2rWR\nnZ0tOgbRP9q8eTPS0tJQWlqK4uLiv5WaDRs2xJ49e3Djxg1BCUmZPH78GKNGjcKmTZvQqFEj0XFU\ngrq6OqZMmYJr165BKpXCysoKa9asQVFRkehowmlpaSE8PBw+Pj7IysoSHYfovchkMnh6emL27Nmi\noxAR0V+w7CQilcKyk5SFr68voqKiIJVKoa6uDjU1NQDA06dPkZKSgtu3byM5ORkJCQmCk5Kik8lk\nGD9+PAYNGoQBAwaIjqNyPvroI6xZswYnT57EgQMH0KpVKxw/flx0LOFsbGzg7e0NFxcXlJaWio5D\n9K9JJBJUr15d/vuZiIgUB8/sJCKVIpPJoKWlhby8PGhqaoqOQ/RGAwcORF5eHrp27YqrV68iIyMD\n9+7dQ15eHqRSKQwMDKCjo4OvvvoKn3/+uei4pMDWrFmD7du3IyYmBlpaWqLjqDSZTIaIiAh4enrC\nxsYGK1asgKmpqehYwpSUlKBz584YMmQIPD09RcchIiKiKoKTnUSkUiQSCWrVqsXpTlJ4n376KaKi\nohAREYFnz56hY8eO8PHxwdatW3Ho0CFEREQgIiICnTp1Eh2VFNivv/6KgIAA/PDDDyw6FYBEIsGg\nQYOQkpKC9u3bw87ODr6+vnj69Ok7Pb64uLiCE1YuNTU1bN++HUuXLkVycrLoOERUSZ4+fQoPDw+Y\nmJhAW1sbn376KS5cuCC/PS8vD9OmTUODBg2gra0NCwsLrFq1SmBiIlI26qIDEBFVtrJL2evVqyc6\nCtEbNWrUCLVr18Z3330HfX19aGlpQVtbm5fL0TvLzc2Fo6Mj1qxZo9LTg4qoWrVq8PPzw5gxY+Dn\n5wdLS0ssXboUzs7Ob9xOLpPJcPToURw+fBidOnXCiBEjKjl1xTA1NcWyZcvg5OSEuLg4XnVBpAJc\n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\nZcv0zTff6MMPP7R1OQAAoIog7AQAyGKxKD8CGnabAAAeCUlEQVQ//4qzQbOysmSxWHTixInrdjIs\nKCiQp6en4uLi9NRTT131ujNnzsjT01MpKSkKDw+XJLVv316FhYVatGiRvL29NWzYMJWUlGj16tXs\nCVnJvL299f3331v3u7tRP//8szp06KDk5GTrrCoAsKW8vDxZLBZ5eXnZuhQAAFBFsLENAEAmk0ke\nHh7y8PDQww8/XO786dOn5eDgcNXXX95v8+DBgzKZTNa9On9//vI4krRy5Urdc889CgoKkiRt2bJF\nKSkp2rVrlzVEmzt3rpo3b66DBw+qWbNmlfKc+M3ljuw3E3ZevHhRAwYM0PTp0wk6AVQZ9evXt3UJ\nAACgirn59WsAgGrnesvYzWazJGnv3r1ydXWVm5tbmfO/bz6UmJioyZMn6+WXX1bdunV16dIlrVu3\nTt7e3goJCdGvv/4qSapTp468vLyUnp5+m56q+rocdt6McePGKTg4WM8999xtqgoArq2kpEQsSgMA\nANdD2AkAqDQZGRny9PS0NjuyWCwqLS2VnZ2dCgoKNH78eE2aNEmjR4/WjBkzJEmXLl3S3r171aRJ\nE0n/C07z8vLk4eGhX375xXovVI6bDTuXL1+udevW6d1336WjJQCbefzxx5WcnGzrMgAAQBXHMnYA\nQIVYLBadPXtW7u7u2rdvn3x9fVWnTh1JvwWXNWrUUFpaml588UWdPXtWCxcuVERERJnZnnl5edal\n6pdDzZycHNWoUaNCXeJxZUFBQdq0adMNXXvgwAGNGTNGa9assf67AsCddvDgQaWlpalDhw62LgUA\nAFRxhJ0AgAo5duyYunfvrqKiIh06dEh+fn5655131KlTJ7Vr104JCQmaPXu22rdvr9dff12urq6S\nftu/02KxyNXVVYWFhdbO3jVq1JAkpaWlydHRUX5+ftbrLyspKVGfPn3KdY739fXVPffcc4ffgbtP\nkyZNbmhmZ3FxsQYOHKgJEyZYG0kBgC3ExcVp8ODB122UBwAAQDd2AECFWCwWpaena+fOncrNzdX2\n7du1fft2tWnTRvPnz1erVq105swZRUREKCwsTMHBwQoKClLLli3l4OAgOzs7DR06VIcPH9ayZcvU\nsGFDSVJoaKjatGmj2bNnWwPSy0pKSrR27dpyneOPHTumRo0alQtBAwMD5efnd80mS9VJUVGR6tat\n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\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "show_map(node_colors)" ] @@ -437,6 +436,50 @@ "Voila! You see, the romania map as shown in the Figure[3.2] in the book. Now, see how different searching algorithms perform with our problem statements." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## SIMPLE PROBLEM SOLVING AGENT PROGRAM\n", + "\n", + "Let us now define a Simple Problem Solving Agent Program. Run the next cell to see how the abstract class SimpleProblemSolvingAgentProgram is defined in the search module." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%psource SimpleProblemSolvingAgentProgram" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The SimpleProblemSolvingAgentProgram class has six methods: \n", + "\n", + "* `__init__(self, intial_state=None)`: This is the `contructor` of the class and is the first method to be called when the class is instantiated. It takes in a keyword argument, `initial_state` which is initially `None`. The argument `intial_state` represents the state from which the agent starts. \n", + "\n", + "\n", + "* `__call__(self, percept)`: This method updates the `state` of the agent based on its `percept` using the `update_state` method. It then formulates a `goal` with the help of `formulate_goal` method and a `problem` using the `formulate_problem` method and returns a sequence of actions to solve it (using the `search` method).\n", + "\n", + "\n", + "* `update_state(self, percept)`: This method updates the `state` of the agent based on its `percept`. \n", + "\n", + "\n", + "* `formulate_goal(self, state)`: Given a `state` of the agent, this method formulates the `goal` for it.\n", + "\n", + "\n", + "* `formulate_problem(self, state, goal)`: It is used in problem formulation given a `state` and a `goal` for the `agent`.\n", + "\n", + "\n", + "* `search(self, problem)`: This method is used to search a sequence of `actions` to solve a `problem`." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -949,7 +992,7 @@ "display_visual(user_input = False, algorithm = depth_first_graph_search, problem = romania_problem)" ] }, - { + { "cell_type": "markdown", "metadata": {}, "source": [ @@ -960,7 +1003,9 @@ { "cell_type": "code", "execution_count": 21, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def best_first_graph_search(problem, f):\n", @@ -1045,7 +1090,9 @@ { "cell_type": "code", "execution_count": 22, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def uniform_cost_search(problem):\n", @@ -1073,7 +1120,7 @@ "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "ca9b2d01bbd5458bb037585c719d73fc" - } + } }, "metadata": {}, "output_type": "display_data" @@ -1096,7 +1143,9 @@ { "cell_type": "code", "execution_count": 24, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def greedy_best_first_search(problem, h=None):\n", @@ -1150,7 +1199,9 @@ { "cell_type": "code", "execution_count": 25, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def astar_search(problem, h=None):\n", @@ -2830,7 +2881,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.1" + "version": "3.5.2" } }, "nbformat": 4, diff --git a/search.py b/search.py index 9caee609a..b705d6f28 100644 --- a/search.py +++ b/search.py @@ -145,7 +145,7 @@ class SimpleProblemSolvingAgentProgram: """Abstract framework for a problem-solving agent. [Figure 3.1]""" def __init__(self, initial_state=None): - """State is an sbstract representation of the state + """State is an abstract representation of the state of the world, and seq is the list of actions required to get to a particular state from the initial state(root).""" self.state = initial_state From d4520ca7400bb320c432b2db182ea6d8d43f4967 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Mon, 12 Feb 2018 15:23:23 +0530 Subject: [PATCH 016/269] Added more tests for mdp.py (#722) --- tests/test_mdp.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tests/test_mdp.py b/tests/test_mdp.py index b27c1af71..9117a32d9 100644 --- a/tests/test_mdp.py +++ b/tests/test_mdp.py @@ -1,5 +1,21 @@ from mdp import * +sequential_decision_environment_1 = GridMDP([[-0.1, -0.1, -0.1, +1], + [-0.1, None, -0.1, -1], + [-0.1, -0.1, -0.1, -0.1]], + terminals=[(3, 2), (3, 1)]) + +sequential_decision_environment_2 = GridMDP([[-2, -2, -2, +1], + [-2, None, -2, -1], + [-2, -2, -2, -2]], + terminals=[(3, 2), (3, 1)]) + +sequential_decision_environment_3 = GridMDP([[-1.0, -0.1, -0.1, -0.1, -0.1, 0.5], + [-0.1, None, None, -0.5, -0.1, -0.1], + [-0.1, None, 1.0, 3.0, None, -0.1], + [-0.1, -0.1, -0.1, None, None, -0.1], + [0.5, -0.1, -0.1, -0.1, -0.1, -1.0]], + terminals=[(2, 2), (3, 2), (0, 4), (5, 0)]) def test_value_iteration(): assert value_iteration(sequential_decision_environment, .01) == { @@ -10,6 +26,30 @@ def test_value_iteration(): (2, 0): 0.34461306281476806, (2, 1): 0.48643676237737926, (2, 2): 0.79536093684710951} + assert value_iteration(sequential_decision_environment_1, .01) == { + (3, 2): 1.0, (3, 1): -1.0, + (3, 0): -0.0897388258468311, (0, 1): 0.146419707398967840, + (0, 2): 0.30596200514385086, (1, 0): 0.010092796415625799, + (0, 0): 0.00633408092008296, (1, 2): 0.507390193380827400, + (2, 0): 0.15072242145212010, (2, 1): 0.358309043654212570, + (2, 2): 0.71675493618997840} + + assert value_iteration(sequential_decision_environment_2, .01) == { + (3, 2): 1.0, (3, 1): -1.0, + (3, 0): -3.5141584808407855, (0, 1): -7.8000009574737180, + (0, 2): -6.1064293596058830, (1, 0): -7.1012549580376760, + (0, 0): -8.5872244532783200, (1, 2): -3.9653547121245810, + (2, 0): -5.3099468802901630, (2, 1): -3.3543366255753995, + (2, 2): -1.7383376462930498} + + assert value_iteration(sequential_decision_environment_3, .01) == { + (0, 0): 4.350592130345558, (0, 1): 3.640700980321895, (0, 2): 3.0734806370346943, (0, 3): 2.5754335063434937, (0, 4): -1.0, + (1, 0): 3.640700980321895, (1, 1): 3.129579352304856, (1, 4): 2.0787517066719916, + (2, 0): 3.0259220379893352, (2, 1): 2.5926103577982897, (2, 2): 1.0, (2, 4): 2.507774181360808, + (3, 0): 2.5336747364500076, (3, 2): 3.0, (3, 3): 2.292172805400873, (3, 4): 2.996383110867515, + (4, 0): 2.1014575936349886, (4, 3): 3.1297590518608907, (4, 4): 3.6408806798779287, + (5, 0): -1.0, (5, 1): 2.5756132058995282, (5, 2): 3.0736603365907276, (5, 3): 3.6408806798779287, (5, 4): 4.350771829901593} + def test_policy_iteration(): assert policy_iteration(sequential_decision_environment) == { @@ -18,6 +58,26 @@ def test_policy_iteration(): (2, 1): (0, 1), (2, 2): (1, 0), (3, 0): (-1, 0), (3, 1): None, (3, 2): None} + assert policy_iteration(sequential_decision_environment_1) == { + (0, 0): (0, 1), (0, 1): (0, 1), (0, 2): (1, 0), + (1, 0): (1, 0), (1, 2): (1, 0), (2, 0): (0, 1), + (2, 1): (0, 1), (2, 2): (1, 0), (3, 0): (-1, 0), + (3, 1): None, (3, 2): None} + + assert policy_iteration(sequential_decision_environment_2) == { + (0, 0): (1, 0), (0, 1): (0, 1), (0, 2): (1, 0), + (1, 0): (1, 0), (1, 2): (1, 0), (2, 0): (1, 0), + (2, 1): (1, 0), (2, 2): (1, 0), (3, 0): (0, 1), + (3, 1): None, (3, 2): None} + + assert policy_iteration(sequential_decision_environment_3) == { + (0, 0): (-1, 0), (0, 1): (0, -1), (0, 2): (0, -1), (0, 3): (0, -1), (0, 4): None, + (1, 0): (-1, 0), (1, 1): (-1, 0), (1, 4): (1, 0), + (2, 0): (-1, 0), (2, 1): (0, -1), (2, 2): None, (2, 4): (1, 0), + (3, 0): (-1, 0), (3, 2): None, (3, 3): (1, 0), (3, 4): (1, 0), + (4, 0): (-1, 0), (4, 3): (1, 0), (4, 4): (1, 0), + (5, 0): None, (5, 1): (0, 1), (5, 2): (0, 1), (5, 3): (0, 1), (5, 4): (1, 0)} + def test_best_policy(): pi = best_policy(sequential_decision_environment, @@ -26,6 +86,26 @@ def test_best_policy(): ['^', None, '^', '.'], ['^', '>', '^', '<']] + pi_1 = best_policy(sequential_decision_environment_1, + value_iteration(sequential_decision_environment_1, .01)) + assert sequential_decision_environment_1.to_arrows(pi_1) == [['>', '>', '>', '.'], + ['^', None, '^', '.'], + ['^', '>', '^', '<']] + + pi_2 = best_policy(sequential_decision_environment_2, + value_iteration(sequential_decision_environment_2, .01)) + assert sequential_decision_environment_2.to_arrows(pi_2) == [['>', '>', '>', '.'], + ['^', None, '>', '.'], + ['>', '>', '>', '^']] + + pi_3 = best_policy(sequential_decision_environment_3, + value_iteration(sequential_decision_environment_3, .01)) + assert sequential_decision_environment_3.to_arrows(pi_3) == [['.', '>', '>', '>', '>', '>'], + ['v', None, None, '>', '>', '^'], + ['v', None, '.', '.', None, '^'], + ['v', '<', 'v', None, None, '^'], + ['<', '<', '<', '<', '<', '.']] + def test_transition_model(): transition_model = { From 9ccc092b70db3d1b9c1bb36c51123092f79e3a93 Mon Sep 17 00:00:00 2001 From: Anthony Marakis Date: Mon, 12 Feb 2018 21:30:28 +0200 Subject: [PATCH 017/269] Update vacuum_world.ipynb (#725) --- vacuum_world.ipynb | 225 ++++++++++++++++++++------------------------- 1 file changed, 102 insertions(+), 123 deletions(-) diff --git a/vacuum_world.ipynb b/vacuum_world.ipynb index 92f5b90d9..34bcd2d5b 100644 --- a/vacuum_world.ipynb +++ b/vacuum_world.ipynb @@ -4,11 +4,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# The Vacuum World \n", + "# THE VACUUM WORLD \n", "\n", - "In this notebook, we will be discussing about **the structure of agents** through an example of the **vacuum agent**. The job of AI is to design an **agent program** that implements the agent function: the mapping from percepts to actions. We assume this program will run on some sort of computing device with physical sensors and actuators: we call this the **architecture**: \n", + "In this notebook, we will be discussing **the structure of agents** through an example of the **vacuum agent**. The job of AI is to design an **agent program** that implements the agent function: the mapping from percepts to actions. We assume this program will run on some sort of computing device with physical sensors and actuators: we call this the **architecture**:\n", "\n", - " agent = architecture + program " + "

agent = architecture + program

" ] }, { @@ -22,15 +22,31 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Agent Programs\n", + "## CONTENTS\n", "\n", - "An agent program takes the current percept as input from the sensors and return an action to the actuators. There is a difference between an agent program and an agent function: an agent program takes the current percept as input whereas an agent function takes the entire percept history. \n", - "The agent program takes just the current percept as input because nothing more is available from the environment; if the agent's actions need to depend on the entire percept sequence, the agent will have to remember the percept. \n", + "* Agent\n", + "* Random Agent Program\n", + "* Table-Driven Agent Program\n", + "* Simple Reflex Agent Program\n", + "* Model-Based Reflex Agent Program\n", + "* Goal-Based Agent Program\n", + "* Utility-Based Agent Program" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## AGENT PROGRAMS\n", + "\n", + "An agent program takes the current percept as input from the sensors and returns an action to the actuators. There is a difference between an agent program and an agent function: an agent program takes the current percept as input whereas an agent function takes the entire percept history.\n", + "\n", + "The agent program takes just the current percept as input because nothing more is available from the environment; if the agent's actions depend on the entire percept sequence, the agent will have to remember the percept.\n", "\n", "We'll discuss the following agent programs here with the help of the vacuum world example:\n", "\n", "* Random Agent Program\n", - "* Table Driven Agent Program\n", + "* Table-Driven Agent Program\n", "* Simple Reflex Agent Program\n", "* Model-Based Reflex Agent Program\n", "* Goal-Based Agent Program\n", @@ -43,7 +59,7 @@ "source": [ "## Random Agent Program\n", "\n", - "A random agent program, as the name suggests, choses an action at random, without taking into account the percepts. \n", + "A random agent program, as the name suggests, chooses an action at random, without taking into account the percepts. \n", "Here, we will demonstrate a random vacuum agent for a trivial vacuum environment, that is, the two-state environment." ] }, @@ -56,25 +72,9 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, - "outputs": [ - { - "ename": "FileNotFoundError", - "evalue": "[Errno 2] No such file or directory: '/home/apurv/aima-python/aima-data/orings.csv'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mFileNotFoundError\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;32mfrom\u001b[0m \u001b[0magents\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mnotebook\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpsource\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m~/aima-python/notebook.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mgames\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mTicTacToe\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malphabeta_player\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrandom_player\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mFig52Extended\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minfinity\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mlogic\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mparse_definite_clause\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstandardize_variables\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0munify\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubst\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mlearning\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mDataSet\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mIPython\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdisplay\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mHTML\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdisplay\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mcollections\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mCounter\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdefaultdict\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/aima-python/learning.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1105\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1106\u001b[0m orings = DataSet(name='orings', target='Distressed',\n\u001b[0;32m-> 1107\u001b[0;31m attrnames=\"Rings Distressed Temp Pressure Flightnum\")\n\u001b[0m\u001b[1;32m 1108\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1109\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/aima-python/learning.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, examples, attrs, attrnames, target, inputs, values, distance, name, source, exclude)\u001b[0m\n\u001b[1;32m 96\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mparse_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mexamples\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 97\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mexamples\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 98\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mparse_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mopen_data\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m'.csv'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\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 99\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 100\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mexamples\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/aima-python/utils.py\u001b[0m in \u001b[0;36mopen_data\u001b[0;34m(name, mode)\u001b[0m\n\u001b[1;32m 414\u001b[0m \u001b[0maima_file\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maima_root\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'aima-data'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 415\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 416\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maima_file\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 417\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 418\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: '/home/apurv/aima-python/aima-data/orings.csv'" - ] - } - ], + "outputs": [], "source": [ "from agents import *\n", "from notebook import psource" @@ -89,34 +89,34 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "%psource TrivialVacuumEnvironment" + "psource(TrivialVacuumEnvironment)" ] }, { "cell_type": "code", - "execution_count": 119, + "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "State of the Environment: {(1, 0): 'Clean', (0, 0): 'Dirty'}.\n" + "State of the Environment: {(0, 0): 'Dirty', (1, 0): 'Clean'}.\n" ] } ], "source": [ - "# These are the two locations for the two-state environment.\n", + "# These are the two locations for the two-state environment\n", "loc_A, loc_B = (0, 0), (1, 0)\n", "\n", - "# Initialise the two-state environment.\n", + "# Initialize the two-state environment\n", "trivial_vacuum_env = TrivialVacuumEnvironment()\n", "\n", - "# Check the intial state of the environment.\n", + "# Check the intial state of the environment\n", "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))" ] }, @@ -124,18 +124,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's create our agent now. This agent will chose any of the actions from 'Right', 'Left', 'Suck' and 'NoOp' (No Operation) randomly. " + "Let's create our agent now. This agent will choose any of the actions from 'Right', 'Left', 'Suck' and 'NoOp' (No Operation) randomly." ] }, { "cell_type": "code", - "execution_count": 120, - "metadata": { - "collapsed": true - }, + "execution_count": 4, + "metadata": {}, "outputs": [], "source": [ - "# Create the random agent.\n", + "# Create the random agent\n", "random_agent = Agent(program=RandomAgentProgram(['Right', 'Left', 'Suck', 'NoOp']))" ] }, @@ -148,7 +146,7 @@ }, { "cell_type": "code", - "execution_count": 121, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -160,7 +158,7 @@ } ], "source": [ - "# Add agent to the environment.\n", + "# Add agent to the environment\n", "trivial_vacuum_env.add_thing(random_agent)\n", "\n", "print(\"RandomVacuumAgent is located at {}.\".format(random_agent.location))" @@ -175,23 +173,23 @@ }, { "cell_type": "code", - "execution_count": 122, + "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "State of the Environment: {(1, 0): 'Clean', (0, 0): 'Dirty'}.\n", + "State of the Environment: {(0, 0): 'Dirty', (1, 0): 'Clean'}.\n", "RandomVacuumAgent is located at (0, 0).\n" ] } ], "source": [ - "# Running the environment.\n", + "# Running the environment\n", "trivial_vacuum_env.step()\n", "\n", - "# Check the current state of the environment.\n", + "# Check the current state of the environment\n", "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))\n", "\n", "print(\"RandomVacuumAgent is located at {}.\".format(random_agent.location))" @@ -201,18 +199,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Table Driven Agent Program\n", + "## TABLE-DRIVEN AGENT PROGRAM\n", "\n", - "A table driven agent program keeps track of the percept sequence and then uses it to index into a table of actions to decide what to do. The table represents eplicitly the agent function that the agent program embodies. \n", + "A table-driven agent program keeps track of the percept sequence and then uses it to index into a table of actions to decide what to do. The table represents explicitly the agent function that the agent program embodies. \n", "In the two-state vacuum world, the table would consist of all the possible states of the agent." ] }, { "cell_type": "code", - "execution_count": 123, - "metadata": { - "collapsed": true - }, + "execution_count": 7, + "metadata": {}, "outputs": [], "source": [ "table = {((loc_A, 'Clean'),): 'Right',\n", @@ -230,18 +226,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We will now create a table driven agent program for our two-state environment." + "We will now create a table-driven agent program for our two-state environment." ] }, { "cell_type": "code", - "execution_count": 124, - "metadata": { - "collapsed": true - }, + "execution_count": 8, + "metadata": {}, "outputs": [], "source": [ - "# Create a table driven agent.\n", + "# Create a table-driven agent\n", "table_driven_agent = Agent(program=TableDrivenAgentProgram(table=table))" ] }, @@ -249,15 +243,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Since we are using the same environment, let us remove the previously added random agent from the environment to avoid confusion." + "Since we are using the same environment, let's remove the previously added random agent from the environment to avoid confusion." ] }, { "cell_type": "code", - "execution_count": 125, - "metadata": { - "collapsed": true - }, + "execution_count": 9, + "metadata": {}, "outputs": [], "source": [ "trivial_vacuum_env.delete_thing(random_agent)" @@ -265,7 +257,7 @@ }, { "cell_type": "code", - "execution_count": 126, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -277,7 +269,7 @@ } ], "source": [ - "# Add the table driven agent to the environment\n", + "# Add the table-driven agent to the environment\n", "trivial_vacuum_env.add_thing(table_driven_agent)\n", "\n", "print(\"TableDrivenVacuumAgent is located at {}.\".format(table_driven_agent.location))" @@ -285,23 +277,23 @@ }, { "cell_type": "code", - "execution_count": 127, + "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "State of the Environment: {(1, 0): 'Clean', (0, 0): 'Clean'}.\n", + "State of the Environment: {(0, 0): 'Clean', (1, 0): 'Clean'}.\n", "TableDrivenVacuumAgent is located at (0, 0).\n" ] } ], "source": [ - "# Run the environment.\n", + "# Run the environment\n", "trivial_vacuum_env.step()\n", "\n", - "# Check the current state of the environment.\n", + "# Check the current state of the environment\n", "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))\n", "\n", "print(\"TableDrivenVacuumAgent is located at {}.\".format(table_driven_agent.location))" @@ -311,9 +303,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Simple Reflex Agent Program\n", + "## SIMPLE REFLEX AGENT PROGRAM\n", "\n", - "A simple reflex agent program selects actions on the basis of the current percept, ignoring the rest of the percept history. These agents work on a **condition-action rule** (also called **situation-action rule**, **production** or **if-then rule**), which tell the agent the action to trigger when a particular situtation is encountered. \n", + "A simple reflex agent program selects actions on the basis of the *current* percept, ignoring the rest of the percept history. These agents work on a **condition-action rule** (also called **situation-action rule**, **production** or **if-then rule**), which tells the agent the action to trigger when a particular situtation is encountered. \n", "\n", "The schematic diagram shown in **Figure 2.9** of the book will make this more clear:\n", "\n", @@ -329,13 +321,11 @@ }, { "cell_type": "code", - "execution_count": 131, - "metadata": { - "collapsed": true - }, + "execution_count": 12, + "metadata": {}, "outputs": [], "source": [ - "# Delete the previously added table driven agent.\n", + "# Delete the previously added table-driven agent\n", "trivial_vacuum_env.delete_thing(table_driven_agent)" ] }, @@ -348,26 +338,24 @@ }, { "cell_type": "code", - "execution_count": 134, - "metadata": { - "collapsed": true - }, + "execution_count": 13, + "metadata": {}, "outputs": [], "source": [ - "# TODO: Implement these functions for two-dimensional environment.\n", - "# Interpret-input function for the two-state environment.\n", + "# TODO: Implement these functions for two-dimensional environment\n", + "# Interpret-input function for the two-state environment\n", "def interpret_input(percept):\n", " pass\n", "\n", "rules = None\n", "\n", - "# Rule-match function for the two-state environment.\n", + "# Rule-match function for the two-state environment\n", "def rule_match(state, rule):\n", " for rule in rules:\n", " if rule.matches(state):\n", " return rule \n", " \n", - "# Create a simple reflex agent the two-state environment.\n", + "# Create a simple reflex agent the two-state environment\n", "simple_reflex_agent = ReflexVacuumAgent()" ] }, @@ -380,14 +368,14 @@ }, { "cell_type": "code", - "execution_count": 135, + "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "SimpleReflexVacuumAgent is located at (0, 0).\n" + "SimpleReflexVacuumAgent is located at (1, 0).\n" ] } ], @@ -399,23 +387,23 @@ }, { "cell_type": "code", - "execution_count": 137, + "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "State of the Environment: {(1, 0): 'Clean', (0, 0): 'Clean'}.\n", + "State of the Environment: {(0, 0): 'Clean', (1, 0): 'Clean'}.\n", "SimpleReflexVacuumAgent is located at (0, 0).\n" ] } ], "source": [ - "# Run the environment.\n", + "# Run the environment\n", "trivial_vacuum_env.step()\n", "\n", - "# Check the current state of the environment.\n", + "# Check the current state of the environment\n", "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))\n", "\n", "print(\"SimpleReflexVacuumAgent is located at {}.\".format(simple_reflex_agent.location))" @@ -425,11 +413,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Model-Based Reflex Agent Program\n", + "## MODEL-BASED REFLEX AGENT PROGRAM\n", "\n", - "A model-based reflex agent maintains some sort of internal state that depends on the percept history and thereby reflects at least some of the unobserved aspects of the current state. In additon to this, it also requires a model of the world, that is, knowledge about \"how the world works\". \n", + "A model-based reflex agent maintains some sort of **internal state** that depends on the percept history and thereby reflects at least some of the unobserved aspects of the current state. In additon to this, it also requires a **model** of the world, that is, knowledge about \"how the world works\".\n", "\n", - "The schematic diagram shown in figure 2.11 of the book will make this more clear:\n", + "The schematic diagram shown in **Figure 2.11** of the book will make this more clear:\n", "" ] }, @@ -442,22 +430,11 @@ }, { "cell_type": "code", - "execution_count": 139, + "execution_count": 16, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "list.remove(x): x not in list\n", - " in Environment delete_thing\n", - " Thing to be removed: at (0, 0)\n", - " from list: []\n" - ] - } - ], + "outputs": [], "source": [ - "# Delete the previously added simple reflex agent.\n", + "# Delete the previously added simple reflex agent\n", "trivial_vacuum_env.delete_thing(simple_reflex_agent)" ] }, @@ -470,7 +447,7 @@ }, { "cell_type": "code", - "execution_count": 140, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -482,14 +459,14 @@ } ], "source": [ - "# TODO: Implement this function for the two-dimensional environment.\n", + "# TODO: Implement this function for the two-dimensional environment\n", "def update_state(state, action, percept, model):\n", " pass\n", "\n", - "# Create a model-based reflex agent.\n", + "# Create a model-based reflex agent\n", "model_based_reflex_agent = ModelBasedVacuumAgent()\n", "\n", - "# Add the agent to the environment.\n", + "# Add the agent to the environment\n", "trivial_vacuum_env.add_thing(model_based_reflex_agent)\n", "\n", "print(\"ModelBasedVacuumAgent is located at {}.\".format(model_based_reflex_agent.location))" @@ -497,23 +474,23 @@ }, { "cell_type": "code", - "execution_count": 143, + "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "State of the Environment: {(1, 0): 'Clean', (0, 0): 'Clean'}.\n", + "State of the Environment: {(0, 0): 'Clean', (1, 0): 'Clean'}.\n", "ModelBasedVacuumAgent is located at (1, 0).\n" ] } ], "source": [ - "# Run the environment.\n", + "# Run the environment\n", "trivial_vacuum_env.step()\n", "\n", - "# Check the current state of the environment.\n", + "# Check the current state of the environment\n", "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))\n", "\n", "print(\"ModelBasedVacuumAgent is located at {}.\".format(model_based_reflex_agent.location))" @@ -523,19 +500,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Goal-Based Agent Program \n", + "## GOAL-BASED AGENT PROGRAM\n", "\n", - "A goal-based agent needs some sort of goal information that describes situations that are desirable, apart from the current state description. \n", - "Figure 2.13 of the book shows a model-based, goal-based agent: \n", + "A goal-based agent needs some sort of **goal** information that describes situations that are desirable, apart from the current state description.\n", + "\n", + "**Figure 2.13** of the book shows a model-based, goal-based agent:\n", "\n", "\n", - "Search (Chapters 3 to 5) and Planning (Chapters 10 to 11) are the subfields of AI devoted to finding action sequences that achieve the agent's goals.\n", + "**Search** (Chapters 3 to 5) and **Planning** (Chapters 10 to 11) are the subfields of AI devoted to finding action sequences that achieve the agent's goals.\n", + "\n", + "## UTILITY-BASED AGENT PROGRAM\n", "\n", - "## Utility-Based Agent Program\n", + "A utility-based agent maximizes its **utility** using the agent's **utility function**, which is essentially an internalization of the agent's performance measure.\n", "\n", - "A utility-based agent maximizes its utility using the agent's utility function, which is essentially an internalization of the agent's performance measure. \n", - "Figure 2.14 of the book shows a model-based, utility-based agent:\n", - "\n" + "**Figure 2.14** of the book shows a model-based, utility-based agent:\n", + "" ] } ], @@ -555,7 +534,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.6.3" } }, "nbformat": 4, From af50f309ec700d1dbd71b64fa7fcbd2065ea08a7 Mon Sep 17 00:00:00 2001 From: Pranjal Aswani Date: Fri, 23 Feb 2018 07:20:27 +0530 Subject: [PATCH 018/269] Adaboost example (#739) * added overview for AdaBoost * added implementation for AdaBoost * added example for AdaBoost * added tests for AdaBoost * rephrased sentences * final changes to AdaBoost * changed adaboost tests to use grade_learner * grammar check --- learning.ipynb | 271 ++++++++++++++++++++++++++++++++++++++++- tests/test_learning.py | 16 +++ 2 files changed, 286 insertions(+), 1 deletion(-) diff --git a/learning.ipynb b/learning.ipynb index 16bb4bd6b..0e4d97934 100644 --- a/learning.ipynb +++ b/learning.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": { "collapsed": true }, @@ -1778,6 +1778,275 @@ "source": [ "The Perceptron didn't fare very well mainly because the dataset is not linearly separated. On simpler datasets the algorithm performs much better, but unfortunately such datasets are rare in real life scenarios." ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## AdaBoost\n", + "\n", + "### Overview\n", + "\n", + "**AdaBoost** is an algorithm which uses **ensemble learning**. In ensemble learning the hypotheses in the collection, or ensemble, vote for what the output should be and the output with the majority votes is selected as the final answer.\n", + "\n", + "AdaBoost algorithm, as mentioned in the book, works with a **weighted training set** and **weak learners** (classifiers that have about 50%+epsilon accuracy i.e slightly better than random guessing). It manipulates the weights attached to the the examples that are showed to it. Importance is given to the examples with higher weights.\n", + "\n", + "All the examples start with equal weights and a hypothesis is generated using these examples. Examples which are incorrectly classified, their weights are increased so that they can be classified correctly by the next hypothesis. The examples that are correctly classified, their weights are reduced. This process is repeated *K* times (here *K* is an input to the algorithm) and hence, *K* hypotheses are generated.\n", + "\n", + "These *K* hypotheses are also assigned weights according to their performance on the weighted training set. The final ensemble hypothesis is the weighted-majority combination of these *K* hypotheses.\n", + "\n", + "The speciality of AdaBoost is that by using weak learners and a sufficiently large *K*, a highly accurate classifier can be learned irrespective of the complexity of the function being learned or the dullness of the hypothesis space." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Implementation\n", + "\n", + "As seen in the previous section, the `PerceptronLearner` does not perform that well on the iris dataset. We'll use perceptron as the learner for the AdaBoost algorithm and try to increase the accuracy. \n", + "\n", + "Let's first see what AdaBoost is exactly:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def AdaBoost(L, K):\n",
+       "    """[Figure 18.34]"""\n",
+       "    def train(dataset):\n",
+       "        examples, target = dataset.examples, dataset.target\n",
+       "        N = len(examples)\n",
+       "        epsilon = 1. / (2 * N)\n",
+       "        w = [1. / N] * N\n",
+       "        h, z = [], []\n",
+       "        for k in range(K):\n",
+       "            h_k = L(dataset, w)\n",
+       "            h.append(h_k)\n",
+       "            error = sum(weight for example, weight in zip(examples, w)\n",
+       "                        if example[target] != h_k(example))\n",
+       "            # Avoid divide-by-0 from either 0% or 100% error rates:\n",
+       "            error = clip(error, epsilon, 1 - epsilon)\n",
+       "            for j, example in enumerate(examples):\n",
+       "                if example[target] == h_k(example):\n",
+       "                    w[j] *= error / (1. - error)\n",
+       "            w = normalize(w)\n",
+       "            z.append(math.log((1. - error) / error))\n",
+       "        return WeightedMajority(h, z)\n",
+       "    return train\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(AdaBoost)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "AdaBoost takes as inputs: **L** and *K* where **L** is the learner and *K* is the number of hypotheses to be generated. The learner **L** takes in as inputs: a dataset and the weights associated with the examples in the dataset. But the `PerceptronLearner` doesnot handle weights and only takes a dataset as its input. \n", + "To remedy that we will give as input to the PerceptronLearner a modified dataset in which the examples will be repeated according to the weights associated to them. Intuitively, what this will do is force the learner to repeatedly learn the same example again and again until it can classify it correctly. \n", + "\n", + "To convert `PerceptronLearner` so that it can take weights as input too, we will have to pass it through the **`WeightedLearner`** function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "psource(WeightedLearner)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `WeightedLearner` function will then call the `PerceptronLearner`, during each iteration, with the modified dataset which contains the examples according to the weights associated with them." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example\n", + "\n", + "We will pass the `PerceptronLearner` through `WeightedLearner` function. Then we will create an `AdaboostLearner` classifier with number of hypotheses or *K* equal to 5." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "WeightedPerceptron = WeightedLearner(PerceptronLearner)\n", + "AdaboostLearner = AdaBoost(WeightedPerceptron, 5)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "iris2 = DataSet(name=\"iris\")\n", + "iris2.classes_to_numbers()\n", + "\n", + "adaboost = AdaboostLearner(iris2)\n", + "\n", + "adaboost([5, 3, 1, 0.1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That is the correct answer. Let's check the error rate of adaboost with perceptron." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Error ratio for adaboost: 0.046666666666666634\n" + ] + } + ], + "source": [ + "print(\"Error ratio for adaboost: \", err_ratio(adaboost, iris2))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "It reduced the error rate considerably. Unlike the `PerceptronLearner`, `AdaBoost` was able to learn the complexity in the iris dataset." + ] } ], "metadata": { diff --git a/tests/test_learning.py b/tests/test_learning.py index 8a21d6462..3c6d02d28 100644 --- a/tests/test_learning.py +++ b/tests/test_learning.py @@ -218,3 +218,19 @@ def test_random_weights(): assert len(test_weights) == num_weights for weight in test_weights: assert weight >= min_value and weight <= max_value + + +def test_adaboost(): + iris = DataSet(name="iris") + iris.classes_to_numbers() + WeightedPerceptron = WeightedLearner(PerceptronLearner) + AdaboostLearner = AdaBoost(WeightedPerceptron, 5) + adaboost = AdaboostLearner(iris) + tests = [([5, 3, 1, 0.1], 0), + ([5, 3.5, 1, 0], 0), + ([6, 3, 4, 1.1], 1), + ([6, 2, 3.5, 1], 1), + ([7.5, 4, 6, 2], 2), + ([7, 3, 6, 2.5], 2)] + assert grade_learner(adaboost, tests) > 5/6 + assert err_ratio(adaboost, iris) < 0.1 From ce8a0989176873df6498ec4ccf61d2787b6dc085 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Fri, 23 Feb 2018 01:52:12 +0000 Subject: [PATCH 019/269] Enhanced mdp notebook (#743) * Added Policy Iteration section * Removed ambiguous test * Capitalized header * Added images * Added section for sequential decision problems --- images/-0.04.jpg | Bin 0 -> 16933 bytes images/-0.4.jpg | Bin 0 -> 20027 bytes images/-4.jpg | Bin 0 -> 19579 bytes images/4.jpg | Bin 0 -> 21550 bytes images/ge0.jpg | Bin 0 -> 20226 bytes images/ge1.jpg | Bin 0 -> 21080 bytes images/ge2.jpg | Bin 0 -> 26216 bytes images/ge4.jpg | Bin 0 -> 23605 bytes images/grid_mdp.jpg | Bin 0 -> 13536 bytes images/grid_mdp_agent.jpg | Bin 0 -> 6643 bytes mdp.ipynb | 1222 ++++++++++++++++++++++++++++++++++++- tests/test_mdp.py | 8 - 12 files changed, 1220 insertions(+), 10 deletions(-) create mode 100644 images/-0.04.jpg create mode 100644 images/-0.4.jpg create mode 100644 images/-4.jpg create mode 100644 images/4.jpg create mode 100644 images/ge0.jpg create mode 100644 images/ge1.jpg create mode 100644 images/ge2.jpg create mode 100644 images/ge4.jpg create mode 100644 images/grid_mdp.jpg create mode 100644 images/grid_mdp_agent.jpg diff --git a/images/-0.04.jpg b/images/-0.04.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3cf27642178a9417922ca4c5c36426ed64f9f3ea GIT binary patch literal 16933 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

+Wxbu#xm8>9rF1HnSUCh7t6irRP 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

G%fsIpkYcM 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 literal 0 HcmV?d00001 diff --git a/images/4.jpg b/images/4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..55e75001d92ab790c824e4647f035b200519d4ac GIT binary patch literal 21550 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 literal 0 HcmV?d00001 diff --git a/images/ge0.jpg b/images/ge0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a70b1870321307b9e1ff7ffbbd2d6fc2504eb3a9 GIT binary patch literal 20226 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 literal 0 HcmV?d00001 diff --git a/images/ge1.jpg b/images/ge1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..624f16e25b914c6ef9b1622dcb2b0c2a44b8f6fa GIT binary patch literal 21080 zcmeIZ2|Sd2+c$m|QX$!sm}Je`LXu@FA-NFQx2a^EkbN7b2qA=UMG```CfTyhT=tM; zXDnmQ&Wv?1%X_-t=Y8%jT}$`>|Np%2|9PK#I6s`{oag!5j^leA$M-mX)PCwHaP)?n zh8nPM-#*|r_ybS}0Tp2XzJ0sD|M8)rquKo&prxguqd!1Tzk3`!#Bk`~!NUjX=?^m< zKFq)fe&`P!VP;}Hvitw;OLiaMeG2?#JV<|V_l|$en@H+|oBNG%_|ZwYq0*V{2#c;O6e(>E-R?8}v9hB=kvGcx>GB_=Ln?UnHexyvfYU zew&l~p{Tf|wCrPfMQvSuLu1pY=9bQ`?w;Ph{sHXR_{8MYx9OSLIl{{7+WN*Oacg^b zT>Ai;|2eGx9NC_6v4G>+53T_%{qDH-?e_u?8WvhQ{<8;Iujta>abY`k?$JT^t1)SX zHHQSw>)|=>x^^7q6qFegBJ2+BA0zv@fd&3Q8rgpi?4RSp0*o~Kz{R6s0ieLP=k#dx zaA_W0^oCr$z4W`gvE$<22!4qVRW8qs9^~;W=(ipdIY!slt&hXMUXR}92#`dM(R)#W z2BR!wjRbOX4ux6JuciW>t%u1>IaB_|OVKUP#GAfNSk8$gg<> zI2AakfT4(CRH1h5*q~Y}P{bU<->rDrgCQ?@xVBZR%aPLI%~4N5hmNFQ@exyM=5B)E z@Hs3c9DY@ksp-uOelt%fH*z2xzf zXMpr8|CJN)@-Q2IeOC>U9KpUP2!603S0c}7`{E9PXQDACWA*~JDH@@aJ?p0@vYNjX zyF{6|`BCq)E*&oQo`tTFw8N5XXE{`*&r_UNtlM6+mx%IoQCD z?hhs@C9Ak?!th;V_xW&~+=wjKGN*uUr&wvXts< z%qg%SXKj2FcV=-oqgst;cZ`y``8M}SE(eE!fqHc>eO+qEB}lE~k$EjVIk8^?<)N^6 z*N!wRg!Az8kR1N}!t<_Ny9}zBH?Q*W{)Y$1`ct2meko{|<9+$2FCz|)KP;O5W`DTo z>woDe2hEw~x$Xc6k#)zY6CpCre8PeA6)Zm8F3z|P=~tnuGF7tJQ1ZS%-+U`hq$RS< z&Czg1^fhxT6&U-Ve2NNmWFwH^h%tfCtu)kY(e#I4Hy$3|qG`Z<@TvuEHsx?KL50Ez ztJ47x*C=&FgVE>(98rU$WS>U`Y_}>f5hR?*A`2!F(-Q4D@S@VBZ*cFdXszn9D43LHYnGvh4WHlB ziEv7+HWyu&~G3brZOq+!!Pf+jTuE!fnv3Q6l%VSa)bgt$4sF|+=^(dP$6CJQPgtIw6IIqd?i8*8fRuB$A0++ zw|{pkMCr!H_{FMbiA9qa9L8G*&qs;(ivsE4q5vuo3Qr9iksyblFTy>cMdUbQ>3rkY zws9Dbx2l9qcL5qAu~L}WP^fa~NTUgDn{YfgtHZfBv4acoQX{bH`2?aZ-lz^j|4vZ_ z8d_XZSV3??DOctXD|VWj&l$zBRA^YozvvgsRX^@Dz^>cG?hw=XfJO0}pi39)A?8x&!fZf5_b;Uw$1t*96=0ZqXV@#i!J%5GhT zNFZy>pp%b@I+Vtbn6688UgWu1`zbEH^i{yCAkvME&p$0_$ zvFj5i`}j_Wp>IQ{Yja$Cg@cKjxN1+wNM6_}iQhgmSG z0%N4S<2w*@!t{nzj%3*YqFovym;7{+ri*W#&_rU3rf8eaQyP-n$zp}5oGMUR1ho1{ zG*sZSX?SGj>y82{z@S2TiCmEbn@}f7GH-*oe8Q0@s5P%A#1;aCh zSAtRw8{{LdccYKbplOIU*s?GZ0&&IO&#Hj<$o-?Dw14{06}m~eTdH);-kZ%QP53n! zSUB14KkeAkF5meDafMt6b$~qDRz*K@8FOYl#0D!Z`0;WHkVa@B$llj4`iG98PaB}L`gk{O@L%D%IcYrf7IT`Fs>6wXlc zu7o>N8l%7KJ27I6Yq1qMjts=m$}ERcXcJ8d(G5)d{aw3m^pWnk_F^xj+!tUQvYh*H z#l`J3s}AjTL>TXgZb~lqW8`7zXqIW;Q^ajWg>h&IeJyqo({PN!4Fg@u+RM_c+}h8! z!_bip6+SV%$yRzSDZGNpCc^vyJE=uxBe<<|q3t8F5v5_TJv zhlpkfv_8laxX*MdIBwR7E8Lp6pnMK4dbiuU)GVv3 zxCzd=b3cb6gY%_Hmh}@(Jy>fyR_=)3?6%1bO2TuGS@$w~_SxcY)1EhdZN(V&HJsXp zdLI?|v(ARtQ-Knrpc3f;t$_1Qeo)fMraIdOwb8nr$s*2go}c{@zr7Aj@!Bumj*#22 zB2IuV7qcy4vmO<^jSQslh=jbW(%vqKj$kuDcfJl0O5sNr!R-{w--IRa?={P0<-htH zhwJp|TD_;mF1S`CMRl;cSePM2waL=4YJ&>MZl>r zc)V{&AN9_tH(fA-(juUFza)iZ%67K;NM+{=CQ9%|*|u%XOj+o zF%JYc`b=n^}f=NDSTHZ zPz+`cjcwU+76b`#@ALE?Rbb zkixwFE+v0JFnzodho1s95R>JJ`7@CbQPugcY5;!5be)hRu}@R3PX~igX%h{`c~3N#jN z{Nkm!5xyo(yt0PbS_iFlGzCN+=1eNkj-p&c5;6wPlus8MrTy+2<*2_j#UHsMKm{WC znuFVw(yJN1zXrhv%_3LlK`Q|v*=_tH<+I2!1N;6ONP9s*?>QAldIHiF>L2d7wcDYt z?zu`jxeX(2gMtH2&2gnThF5;7zFfb%2>sp27JfBf#eN}p1>Z+_jA`jy>p5vwaNf8$ zp>c^5BJs5)m{+IAIJ&V~=g)fTV5{;<>fAs)_s0^^rRb0v zK7+M*>B|>3F|4Uq9bwWXvwkg7Fi|6_<8^su$Qk`J=n#1emX>RWqVmfYQYYFULf8=s z6VMPZU!7==>s1i=DCTJ01de3{JM7S5+AP?+Nd>TnoIR<4-Ky}f*P{=y9=gH|Neta_ z`_m2%*-U-GRpatHnpU2pcb4LGm)@=jEp5zJ!KRN@-Y>Y9k3}A$wBz2ZhXpW^jEJ`5 zjwmlBYn%#f3-pRKUvR!K)`&KXXC@VeiW#0(b&C zOt~UZC6FVUS+hyFXxY$zM7C4^dme6`LALI1Je%Tn}rG9)V% zNtU;UTyOQDSi}T$l-S~urXIq^aV+3P3U(k--ypwVcW2^HJ$_h@J1w$j0T_z7l zXF-e%ki`;r>K25`%ka1`$CDkSU76t{t1YoMWdlN=T`UXAO|S#j#*bYeV)BrGE{CQ~ zDH?6dU9GrCEiPh)zh9w#?5mW`)lF+=vasRuy-LU{%VS_%Ee6N%@id_&s_`7vJh1e&k{~~YPt7_j71U+93NyZV}B0T(w?a7+U1L_Jh$3S58(CbVA(KlFfUC zS+;bWow@oKLz3DPysTV&qOA6n`D8e693qD*n(Rn=r`BzvPj)LR_;AMI>#pPV>vUe( zbE71sQSSs}yy{cSPkS8Gdr%+J@^By2^G}h?BD?L&CfQMR@k6H$T(VfmGgGO`q+?ZohuW zaFju`nOB^oT?NA)fF59y!fMjvSLb!!X3m~BYmqYK2p(O*bJTchD0XUuTzT&}Ns+l0lKs;PP&J~WwIxFI|1>t9~nH5&Hb^kUB;-g5`UJAV(NzhA^C z4b$I-7XuPWj>u-dzlPt=|GhZ>*JdsHUhVX&4olG7{;5d%33L6wH+-}plDz~eqZ4=x z`6*~xXPFie_AqOZoYY26MtQ?1jdoOEEo1{NQ_!r`^@H6W=D=h2*m@L6eZsFAVv;m+ zaj`|cjPCh#Jbm9toV^qMEmao?{sRg#9iL5J!I2DpccN4LWZ}z5_&9XT8is5a*V&oE z>?RSvJ5hRb_4DG)I{^x(!6s>?804(LXeAtU0DpIiy(aPR*Zg~K{yWIc3XFKVofOl~ z08%h*5X!TWzQ*_s*dO@mg?zK1^#(bQxP%HYX0lnO0ypyEbIvD_pSnOF9H0VUIngE{ z61e=HJYM&D>K;*vVl`LvEMw^8t|pW-E$boblUq=QV^Z5*;a9!K7C#%Jst*r6c6 ze8X}pGWd>-iC%aaIS5S8DH|a-r!ckA3p~qPE0t6LBS~R5dGz1y+lcY#Elv-}_Bt4~ zT$bJP*byPp*LXphkk*kRDntgZTAe4`L%w{4Rs^XDosRngeHlgJ-0R0ODhod*ESW4j zDt}EMnG&$KinV>9m`$_pG+(>vRobZ{6gj9a_S|La%qM{ZAn|EPj#T6?K(OIA>zYZD z2t|=G>G#OHgAE>DOpOV>LQHve?om>#*GVOHVR9_bG#GyyreBzoN8^#A<#JnjlCN?G zAH3blxfGyB(u09s!J}29K#cP;7~OE79-1tFDJFldX$}zu-Z}11IPJOj~zw^t@ zIXTl_wPX)x$+k4Lve1@TXmKMNuIO~c3C!rxsS?zm-U-*@QU3}M=tI{kEsmTnkP?hh z)NzsewTv^4vc4}wsAo~TeYlwt?W%bv0QPn*x*t`0oU4(G&{Gz&9^9Ao&5StVe}kfR zu6N1!v+HnOc5&7ho);`H6$!N;Fv(Kil&zNX-cV}LK!>!9$zm19InA+U)?p&L=IN&M zq(h<^X+DtQ*}y6fOR3e>qq#ZPu)4)Q@;z%t>jQ&58kLyhjBKe#hMletR?EJjX6>3t zQaaAzgn^QH%buZ2O{ur0KkQp@wX=o|)vijhW9* zHYLUOZPkUpwx4~ba$G!7=+VS+)8z%OQP@!;Pt7ad#9C}a=?naJoBJYdze7c2WKaC- zuv73`ZnVuZ?=Ptzi#=`836qq|Hl@kOJN(KR|D+aiw< zXVZpe19+XhxJcO#Ie!Gpw^JRG&d=?q=VZA8!fXXg8Gz^T51D#@{( zMeX&^p1vMciMKxLHCU-iG||NUrf7J|d%`lFM!K&3___C=_={H#Qi0!cqbD$=XH@?Q7ZunNs}2vB*a-=Z0$ES^IP@RmaUX?{v~sh)cfW+jgTYke#Ojwe+3JR;6JL(xozvhsn9iT@((3eu zxIxMhoZ#R>LSw=ypUi=hu-_eN?IbuU&~ z<>|o33vS%K-eGk~JHyA8@%BZi-HQd@C>1ePmZ5z~!l-n+DbH`1wP0~I^JqVj6|b_% zpIn$5|5euT|2LXZRI4nd!>QuS6u*@0l{G|;UFuvANp=>lf41MsR^@pN&y>~X`v+HO zEXz~rke|R3K`u(;St^jVBQ~4Uq|dCzuB&>Mj$QXML=O2wX!-M*-E=gFaeLiVu*Ly= z`#%oXR1g>@Ly4`F-;8kE+UxTb5YnUjRA4?3IdkeGgvIW{$Z0%zC5l!{5Uv3B@XruqJ&F>kdXpHMvNV(#Nr@S=^{X=LV%t) zDMA%@`|F07IH<&jL1<)$?Ly-vBuH;}fbqT&2#x8)KFZcEG;&6uYtk0RY7Q6-GxR;F8?5v{7pDbyy4nzk91#D zZ{*`dG-|KtVozM6O0#1g3l)$jqoz=6^(0i{X9|jGqdCJ zJi`xJ=UfI~RoFyV(@Xm*qPB@%TeZ@y@n!`iw{d3@8(TwC*`oYtG%wL@BDDUZ%&@|^ z5H8B2K#Zbv9(T{J{ zJ{XDf^UJZAGlIe^D=SZb8`3BgpncMF^p1e)mcIYc%UF%RY~G_vwc(;!C&6kkns3WL z8IJ@1qHDJQFA5Y7n|>?jd}LSj%ZGDCkM{v07e&hX;uFDw46!DPj|GS;;^004U4l5} zvM#dEg$lebxkFMUO1>rO;P3OD^bhdolT;IM%QQ1HRXrthtt?)$#=q^<^+5Rx>XVla zaWgpaB#NANGhs5z0gE1g|IR(2pIVVq4jx%#Eqxk_6az6^xYx$LCRwETd#kOg$GZDV_1%=yQrs@6 zf1rNsqqX%}^fkY-cMtgWz3gYLk@1}*><-z{fr)gfXrY~GVhCMrS*C2kC}PpypP((` zD2G|_rGvE_+Xamottm#^{?=dPzkMk2fIdYUb)7?cZA3eN%8|oGaV!uhuav(v@bn_`(LwXLhFKY4P$g zR>Av^&pO@GZr>gx^^H`9V`14O=oYkmLpcvjR>xaW0Y&ho%klrQP2+bL@}_d2PEcTI z7ONh9n^5qS@5o0yrShHm6_>H$%dlco{b!jAk5dZnJDQ{jl_t=fuLLT8aVjVnbslsc z!_^mX)t6OpY(6PDJEq|0HwXrwr}KDbCEv-BUwy;&EsXdNv2MBgxDE=&d{h&wr%`#? z>9~e#H7wc6-g5fYbzx_5x)kTD4CZtHwwFl8fc^v|yYeN{#SW5?AlC>7Au1ZI$Z1B& z%;!m}eOcD5cwc%(=KPb%NAXJ{CIU$(N7Kneydx7@E%w6OIJ{$keQ?Z-FC?O%WT!NQ zJ;mf^xzuaMdq?WGdkb73Ovp(EY4QW;Hfx`=>6OxVpYEu#8vweh?0gjt$k?){n; z;Rm_gmoMk;4?a+lqIo08_@VlPS9cE#9T=CN^xW=&e4lPieu{EGAJpw}sKo!q_|jNe zy9a-Q%9nF~jIj?60NfXWBR%7m{NzaY<>Pi3-r@q7aYdpg@tfyxW-$XJR^L{mtjk!S z&?Bah{?}u#fscCm2XuO%f1W5bsgTS{HL#t*B9qjf<&Ft~>EhskgUA}W=P_gDzZg78#K;4ud6U~M&r4F>IMien; zISNZ^m@lSYXN%r(M`sW$w>S?!8h|BLi3nJ#6M((Oa|L&N!od!cTLU9cOIbl;yz16_xlP z;z?v%LFdph>oOeQtp`#yEqz$XpHH1`v$*J*83F}HjnJx2gG_N;p7ZqS)6mA#q+(@B z@(bu_s&2p+OMbi->C#3Nfv7Ca`nqc^^@woHZQP(-X_>X;m_~}@_`|{b`dgdgPv47q z`Sbr}o@a`?S#TSf0`62$$RuK_ho;e>yZFfywmphm6M8rDb{2i2sAKw^S>05*#hYcT zCi?P?yq*V!hWFm5hl@QFO%DhD>e#CMqZ$r>(1Os(gYmBX7;=X@11ziL!4f+k{#*vA z;(r!$gC_AG1>Jw~m*!UtF>-@vHf@J*Sy)YL#OZuc^intMpEWkJ-yp@(^_7rdJ|049 zn5@05ox3!hH`0`lP`a)Ep!w~E)U4lZ5)=%QH?VV}8_qnjL(_zrDQ(eoiG3WB-ScWy(y%^5G?R&<>r^NED zy<|{RV?HIW#mXOgK|;)#VJ6Aw>AT?4vK-JJ?8{Mk15d!JMI&=Z%q+B=?cHOV937nn zyC~z^y;r|x6;DKp(G1L>D=)l#(l3i=7vi z*PAOB(b_{Am3-#O0e6YPJ2&SbMO0vsj&KAl=B}-)8(|Ia(szGRkP%8@W72CVivC@9EZ{E{P; zBID`V8gfdMsX0Z;2;K-pFdAsEXXuvr&D$*YwkA;l*GSy+jGq2%9$yRTWX+`!>{3@r zd%imgTc^<*Anp*r6sktJ;jpdsVYuA|8oDkjlYRBnN!7R29|k^Cfok(6_RkZn`^zIY zI*7bGaBtW~SZ7Hxig6H*BxujU29BqcqB3wZi3xHr+rSxhp_0X_^Nclaw2w+1#l@a! zAyURtiKnGfcQl6(7b=N99xhp?=A;|otZCL8cAC$c=C*TfaI6!rl^1@jEU}g-U98~V z%v1X`7+|gBMPl8V;4C8yZn*FX5=26%z=smDYVp82liPv|7Mw)5Oa*7+`8n^XV_z4~ zk)y?{m>0-tAvj5y%`Nj(P2^cGU>e5Lp3<_(HHI3QXRvV8%@R z`MT<0gp9gr2N0g5Gk&c1Wr};c%S=o}P3n=|G0JOdi~iSNV{nhj_B4T+|3R zijP!Y{!F~)0n(TAf^o{*fG-e6^eK`DNU6`>AwHAEMI5Iqi4L<)RPVmsai=t@|1xG^ zbH$8-?Yv}5Vw8NQ1fg`2JoM1Nnj8eWUCM^3n^VZ${1aqrH)D(ur?6MYX=H+09f{hK z{`k(zZ#&?~{E}~KD)I=L3jr;~UrS|O_E?TaizL>e&jrls>K~*~lJ^u)2*t5OJ#ru4Satk$Amp zV#PV#&s$J4&O5R5!>8o#U&1{(Rx-9RI|rI5hyv(x3+R49`Y%9Nr$8Pvz=$MPZf%3A z#?V=B^24rGiX-0Pz72dcn602EJve-~?s67qfb^qNY+k&o?1q8= z*irPqb{74~Uu7o{B7ZLX|C$-(IQ9}R>r&+tsG}CdJd=_xr!{}&zU!4{66Uk9mKx`I z>_nu?olYHw25>ns4~Zy_B1--s1v#s;m4X1*=?=IyU5>%ORPd+wk%W|`!7v2}Dl$GL z8O&MklAzfQU=;!n9!Fs-LaumR2$#P)ff+kZK#~uTK=c220Y^$v|KY)$V%-)lkPSJc z69H~6lpDj4L~KCrV-C9KNv-s#n;(~nHxMu?a0SBz64nafOGR{leA1EKHMj{`dke&g zZ6=~Kc*6i@>r(U&kKVG&crK+tVNK-%9U$jABvHE!bQijRcrqC#16Eqx2vD-a96>cill z6ET#a03HydJ)J4q8GA?4vjVGyVml|Uk+YOfNfpRWwFk;BA2XiqH4WZKZ>0~ZYCk-? ziEo-ZbNa;ji>ZQ__Q^as(bELI(I(I&%8C=#qic+S00r@E&OfcT5762=_4SRxgJe7lfLXk^WnMv&PX5DLK zo-w+}U{t-5At7}nOg7nF< zdvB2w*t-pvRJd8?HzQD4TPa zqvAvjbour4INh(ee~pLx@TrBL5KwW75R6a^k*+vhp(T#qAhD5k6=$)-24HqieKq0_ zP*esP#&_~PVd+uUoY5uaH26C$S+ryEfW&0ky+Dm1>vuwo%n3%vAl@ekf!I$U!BC=n zRC?#&FmjfzO~tS~qkq1onE44>f&dK#D-V@4>v~-r5}u7TIbj96iW$Tt9xsv-I&}BDxiaMBw^CZH5{g)>b?c$;S|c9Ycg_H5izq zo}eLJ2*-)Dqg3DsGp%>(;-wIbf1ZOWtA+P7(~;ktg%ix0Z@*%2y{6q>lVUXa;E}k6 zn}YBbQh0D7pX@%UKfKahoQ_IIBFee)zL731MOXj5X5>8~r&5IHQn>@TO}sO7zb?8n zSh$hpt_LbzhiP6&J<04SGi9Y8ZmY)FA#fw-87=CIf@%3J7ti>v^H00uCvqv6y!Ag= z+t1)mB56)#9wwQnnlzkkF!?ra(fNJMOWw$66;s(3(gSd5q2sU} zdM(guCxwZNKYx`4Znpo)%>pJOow&jyIhk{?;xdw^vqvH;VT=DhRmQ35<|!H9Ib8@cZrnu2vh`1-D7!?eu2{^)O_87@Tr`)Bh+C_ zWy6#>cxMNSu#AKC$SfM^7A32H?AoxOXSne}vA6T|DGdQ07Y291=@UjBQ2c5tJ@PAR zB6WuVN>;OhVui_(Z#a@PGd*a$TQiWOdb|5(s}vz1d`Lxtl`$F=*)Y&MmdAUM4^zM( z8N&(P%|0tm*WlK~iPIF4@ou7;4{rw|T`eAuBr{gXQRqa79Ux;`R|7gS=Vow3_S6aE z)ICk>BkR5(z~#7#i9#+nfUKO5?(%_<|I545%Z?T#+C%FNad{?B7@)^A(1AFRNw#=NYd%Xqd-&z zcaYZZmNXS}1+NpURID?-9a@-=4m%KL1Ejm%Bh@T4Z^dajUmAgHWO&B}zZ`LRQ<%pc zFyRyB069u&PCZx!-?^-l>rnA37*aQ{Lu1d!(j}uMU7uKSd%)HWDVWfAW>DjV7pE3S zJ;UTLooYMB6up5|KPnLVlBhrlMxFra_z1{uQ3Idb=<+rP$qn@PC6HTUFXdln99!hY z@c6fANwr?PmC~gH@=JnmC>!r~UR~MIKuF-F8`^k`@ZaK~kM4FT3&9Od6QevBhF$Nt zv$i?vows)q@CFIw&Wmo}E*CP7+%;?|s~jZR;Vm*NRJoqGUrZ0XdP=B;PW1#pe+QJ# zzm+>Z^JY^j&=U)DHD$|sWy*v6z5L4{znAM?k-f4H+1r3$Oas?yyeP~v~ zXSoEn>SDr{A=A{Srv@Ra1N5|fR~n8lKKKeUm%(Eom~V5tJChFU5HcuSp+R6EVcmhj zgLGdZbZlDr21`? za$|4~d!tqSmpiH|c$Pv^O>eHl4OEE`Lq z{|(C&yiuMtfHSuDEG@gR`dJ`h#CY_YyR~A7q258IgSkHeMYrrnX8u1=B1^oT5wRc- zAx#_{&5LICPB!T9;5|1sk?b%h=`35-@iF%FSdxgz?Id{)3!$)t>%#`|@egG`*}>>3 zw03+4yJcw0i>ga{))gdebt}57G<@F9`Nj{w?9hP=HLTY!d&oV2gm73Y~MvB~%@Akgx1W%2*p*t7~n(a=@AL$-`bG5UZhW zaXU(b+j0EM^^lV>ITBX`o0LTUb@{=Epj4-h5k;+Mk%PRyG+B^TJ2VT0FS zJ~K1Ba;d85Q_I!#w^P5q0#3w&#ME9${{gRWb|<~JI1a_%oX<0p4t>QOyJ3H_6vigC z#o8#|3nM1t_U*jj)-7)`JT@l}7=f%YJ`QB(gFuRes4|;Mim?b?^xCnQNi8-Cl+hBn z-l3?G-IhJi@K#eQjrT*tR0RCVd&TnMGN*VyEADSqQlaeYw^IVcMRSk-O`7n3r4aX( zjp=OdCq`3hz|b_)v&U~Dk(7WrhU|CRqyp;I?1u0OYyY@}^lB(vxOP!3^Ql`5B2t1dhgrq-uwIA-~ZFp`Iz&ZXP)J|Z_i=WGn%1=yLV~tf|!_? zpu^xF#At-nA!a6~$^YLzENm>3pLwjTENtxa*x4s94o)sk4vzU8?CkTo=g;R_06y%T zJiOcscqYG34l?=k$zOs03pm(0CVTvIA4UPRh=b`lixmr#1jM|EiDeNJqXa@h5EC0% z?c{>LeVCY8SlQ-*CC%po9nuzp<+HFbgT=D3vVz`T;5o#)h;6a>`t9?U=v%T&IPh(_ z5ERKFxg({Je}5%OYU2?{Z_fDwf=ibPtyn27vud@%rp;TnDk`b()Yzq|rM=tWfT5A` zL6bvPM~@x1K4Ejx>8!JhtDC#W#Y;YyeXm^g3l6z)Gc@eh?K@G?F%M$n;u9V|d77G* zo{^cAT~u81?0IQf`HQORns>Ez_3s;6THD$?K6iF?_mBsNhDS!nDAe)EeKA2SU$^yr zXS4RT2<(d);DD8Va$ii$uHa%>#L6bVe%|8k`s|htOC&a2;NaU46q!=UDY7b!pXBSETap ze9}&ObS$_^ea!;$>2+B~x-Ybw`&`TdUuo{JMPW#z{;f&tzohALQ*8G?4c6*7u{O2- z^VNtg^1}RwcNxCFt#_m(P}lHX@DPTie_Fqz($?0q|04sEtTfD!*(xa+*@xlLVL)sw zWa5Pp`+z#Zm-Y|Cd7nADSr^qd-0TmEu@T5Oa}7?#IflG;;|OeI?=!08T;aA~u4h2P z%T2}7js0T=1IpK<)U9`I)?3gcn4R|uSxCB8r$oKd6I>p%qatDH-Lu$pi*9e;BAcVE zC&@2yE%;E#(mF5J|srfUEP3}3F!L((*U*XMUi3nt9>zi845 zoiMvwoDu5kk@QMne}m0fEmq8Z(P0+5j`v}PH=c-J)mJoHBN>2%QS#bQ+_ZV5;}<@klLS!ZdiXbo!nJy>0;|3vSDfc^?8eSIR)F6C!_5d5X;BAs4azJi$f;XZr>D2 zi9;WCSb9wX?!t(3JiKk@`H+3P;ryVzgT{^HNInu}w9iKF9R8#}MhY&j9&Q?EvuUF6 zyECA&v$O7So9NcW!$$-Qw=QNxZP|JwQ*J41vaoxn>`PD?y z>LCVny=zWGHW$jY@^5E8YtDvC>LLWATe`>X>CCAEwE4LVs9kCH{aWwR+F|2z6ls{g z1-Wp@6=Ac02SsPy3U0UR^^k4HQ9Q7B8PK*QWd_7dKvQ|)VJN;x~bO*S2ui?mMn$88ZiuutpDfkU3>y&@s@kc(zG+RjC! zUc^%m*$WS@I9pw0oFL}5J*}&PSFq%^(c-7Ytquz>)pnP!%80XT!dch^zQ9Q)X z5+G!OfS1r`K#N#Cl^76y2?Gkj)2(DCR9+DmEMI<;V zz%WviWS7$_ieyV2je@7$1B2~Wi@XX(vG2D;GTSl3f_gyqVm@QND=~Nsd7)-8 zfl5YG6@l)#F?$8yCgdF{l>o9h8>TE~KpTez{AlA0=!&{^*$$OUKsiVkeRBxaL1KNHT7zry7>n=Hd>d0&mYR;T~AAog5#@IRr zWacP8&J_oAjr2EdewQA9ArLVT&d2MCOY;c zW?U!M_(j@?F^x|T(XJ>aj?-$0!(n>}H*E(pVfu3vn!XBOX0aMfs|UC*13mmjQzq|H} z-AMI)0J*!)MO6=hB7>)e;C>Os+~wQQZn=h3ea+pBZg7|`J>=wUX(0fOwDmKVoz(0L@d zz&DEK?I#m+_D47nlbk6qZTklr>kzFQ_yHY2@d2Ka+NVq30!SX2DedA<4K&vrUffv0 zR}HxkI2HV8eW=R;L)NG33-&1m>p=!~$*s8= zCYGVAqOKp43Mh`o9oeb?W@pMIzwxlj)8HN!6u_SdbUT`{`rz(h*@Dk!*)q%bft+~vHZgPHRRX~f>_i$sh@0f;E2?EX4vi=NKu3&yKqGCu zc;rS!yF5SK9tW(MM*R3`aScA>RIJ+!dARR&>QNHAR76eLTFI;{+9E;a!qz<3mhI)? z;b@DGQFTRWh@XiLR=PlmxDny#p0g#Ho|E1$oKn5D7w557C=kf}E7R4>f1MiP*rX-Y ztCM{dl3%lU*Zq6UCLGJq1{R7W`DVSV*!iB^xunrMcXlcbnRb8XO3lUTd7DV4d#}>Z zP1LU45MdgB-8Ab4zcg#8*gFj_oyH_0(}&JfRoN1&B9EQM9*+c+Ua@i0NNxHi+oT<1 z0=VHy_5XSB|F-i zS}N{Taq6!-Y9P5VVnAbMh^{L_Ob#zpR+xY>bu^%f=TS`8D z;ppSM&lxcv>I>vphQZx7K&}(^Xqn2%g(&a;qSSzPjvAYN3~5P+!YlQQb%g$rgL`6`?9K6rsb}{ zR*IH9*|rZ$(G-5@tGHp!?vk_Ht8$-~!b~d1+R*3K){z2nu@p>^Oa2Pm#&^{#CHmPG zACls<@^`%|!K(n<*cgVh>^>0fSwd&g5Z(;b3a|`lTkY)Sdx1l8-uJ_vv5mRGPgIhh zg$K=-UEh8^CU~CHd2Pv>kyw2V=G`v}fx)_#9h)kHzd$IH~hFmY1>~C=6bT^@LdQzRLDH!l8E76KbD2 zRLYm?ihWcg$z0F4-Olk=gLwGJLgR;(nmRn4D6((iozeiBwEMBRj}6p~hi-)Jjo{~u z-+JtDtksKE^A7}MDrXhEc5k|(#^F|7g65&2BkR>$Fue6$9>Ypwqh3XuzSxH!Zd#GO z^r(-e>DDXTaqEw*W~N$hSL@bLOBi*X@L@m;WUrgGZLL@~924cYZHA!u4+)v9c!JBu zv?8-dRjY3;7PkwJys`k;J2{t8&w|VY_>{DBXfNV!9k{fD+UYS0t0*K0e6xhEFE<#BFYNZ~@bi_0Rx!gy8#=Eh4G_*^rH{_tv0fl;m1 zHPbl$iUSqRS?+Az$EFMdEoc?`Qy6eXgebd$KjQ8KIF`&1sI#rSY4@{Y5yrD)6?;4a ze%g}(YVB9l8S@Owdzuw~efU9)stn7^A7aW7F5C&&{T>aZaR{%0W1)Ta1=T2j0z?!( zSPlVS={C)$l|Z{e!B03bAl(vRI*#Hh{&KGAYxowIs7`qLHBcd*RM90o3c`;edr5+H z@8uxXBapX0moTS^xGMAuGFAv;n_~Wh0DnPme}o1%d|%6w3X(~cg1bm+=7IesNY^lP zz93QtQd@Z0CK@|TB4op-Gj!lf`#z}V3d&ilgp`DwmJ!)eu$ceGy`y6Ue`3o~Gq8${ zfX;{;VRA#j=`Tj1HKo&sh@OxbLOcmD2@rTuI(pU*X5VASMu0{ZTFQXl3*v`5&H+~RLvmO7{5TvHuw-3coO^yj+uXN+iyB0X6PcP#_1Z_{lerEk`)*l#ERp!}CWA@YGu zhGfg-1l0Rv&*IZxmXzC-c;)DIE`1{#Hl5Cs*+)m{_eYw}6Q5uc$XvxnpxEAo;Cl(R<-1^61VN zV+@F?i6%3Bp@diV6cDs2wWp4gwjfLV!B>z)@U5V^0JbNvz%5&eG)MoJ86#u!e7h@V zkp2u-qJjWBFP_rJfY2860B<|Mln&JF4e3uVr}-^iarz2ohy>z#Vha$HqQuVLf+osd z5F8VUMo?&SBZAH41_5^v9V5SxU)xmoLFiusj7CS}_{2r^;m+yu2=~Rmf;yN7axx%r zHlOx<4jgcEK$V*VW6qqw-ur9j#@uR}1BSVv5)8BN?dRsL#?M#+dK%VbhlJ+<0zDg6 zQG#f@^r;x)&H&kOK+%7k2<&xDhyzFcpyZI^`qGWrzCo)-cB&AH% zGYR`|_w4l&Lik^b&CEK3;?W8V^=8yGPHweK-O@|B)IEOSVBg0V$Lsby-zIWa)l0Ucf}*|&KhAQJ0a4h1>|J63oG3Od zJ%eUDv4C=}<#P%NQJw!{tTf|J27Iav;1|V|!1Nl)!>(rPq=c)xja0y=#@NjHFH*kfhfi&~1K^5SlWsg7?VPmkgRWRia z6$s18F{h{rGcI@p)C!>gpj}x4pk1u_iD{_#6>1Ws(J0d2h5|;*IPw+)tVldf`bg(U zbp7$Bq0eXviUFNOk=fP}hlK#y9|BHlMHJk!pcd}pP6WOYACabun#mUVE>k4K1xznK zxZm6#UyGx_gg#buNADErR`lxpIkqjZJN}^4C;tFE?`g{(Q2{|Lw!`$}sg6^;9PuYS zCTW$ZmWrat$MwzI&VY84jQ3b0izeI7EoekMOF=m)1M1V9oC?^3f3O6CM;~#!gLPNr$(aLREI3rTJ;L3Hv?bXiX}Ee^OGMmqedpvZqlVY-cR2<)xe9%FRVI4t z;j$Hu@jQGB??Vvb1mTrwe%ydU!i17T+P-2#a0#IJzMIa z=!*5m?E%;fxDX_XJ4D39K@R%?4!QS5c5qkJRP-wGr!w)A(W?mnDOQjfzG3NRiWHIH zu0D^vL%DGXX9xq5DI#UieL#e#vSGyISK?SC3{Auft7j4{e44VLm zptNaF7t0EKcwC-qHv!ahvTec}lxW1S`%zeFP8&DhewK+X z08n=KkODlrVg*gV!FcO)_-zZpj>~x4!-q2r5PXXuEUT z#JhF0Jod2{!X$?-2IOx`l11`IKEZvBy9h5{&~r$@sk(=KHcWI7W+?=cyS-42#~DLAUC4my_XBCO*M*-diAicA8NyUe_ABt0RX{7pDbJ?`#ec1^`RSM@ zwiqtl0Cz045CE*4iyBw9AQ%x=0jfO;*nVBE9AJAl^sq~wMba-++IR|r`Zh5fC7s9| zi0&9LUk2)*?1YH2vLkv3j~a?>hh-fGY?$QQJBtm+0UL@*e`7<%qUiz0zy7Xp9(fIP z#$7AaW9h;-(TGf+?8O>*lH0ywaVh{?OGJ=uNFgW|i(;|?`qiceH&ZhF?2_hE;rTqKWx&BfL3J88j-b8d0BpRTjN zZ*RXbFX|~xntSgpEVR~kPC(JyR8P0T+C(gAOZh*BpdYbH59WhQqU z-}hvuho|W^$QlQiYSo^4b+I_e?4&`xx@sv?Zu+1n=>>0p`@S>p z`AcYy6ge-F;ImO7RXRO1dUIrqsV%46L!~H9#p|mV7AyOPIyD9voRexYUu2e)9;S;} z3v2Kh%;x2PRYf`B1+WsuBz(b^hGCN2E2CX2*5|!w6yKng;$8JvoKJu?g&h*(KJXT+ za3X`qGYl{4$q+z9GHUmw>NkDdHE+A+pcc>jg?%3abCou%D>$Y9ncZCDh+A7}s0HiW z)ADPVt&G^3TXw4Is7e2u9p}$S+`Ya1l;!sFN3!^U(Q(%csa_VHyNNnUrU!L7Q@f4^ z+Sr}OHjQ!)Hus%qxuLjLZ7{yzC6nfL(Pw;%F3zgP`-RXb=U zwTibpMVt@ZaPDw>5Ams{Kl?Qm%LiQ^p80a=B;Af@$&m?NDS59$>rD!8V_3#cNgijt zP|zjId1Tdd?3=xB>Zle?x2o(7yWHvgjWm7=>KyrEshC)`b-r)WIQojSLX|;@(9N6e z*=Ge8a2~90R9|zg?>zHEv4a8)S(c5So9S26&{rZj$@z)&yx4j(H;d6;-Reg%mo>ex zpQ{>db7FR3Ld8W>*G%9qTL)4I+C&qiu0%Aify?R;Fh4wFvD3k%+WN z4ZcRZkKWZgf$6(^x&ihtS~lkL#VG$z;*>4tlFi&4si%&S#e7{`8VBD><=cf<#b1{> z>{2#AI_4s`aMY9Q8cqlHv1fLH1S#HMNgyGyNjP@D`z!eqMpQ<*7$WO7^rOv&AN~a`~UT+bE|dx>n%w`{jlim$CwO$3sDR`KRO5 z-o2?*9}909sUGB*cvv}ogMWNSTN0aZqX)0abHVi%ymV25aiZ}Jz@D>J_m9e%-;niu z9?_0P-xVHqm%DQ?c6IW;&_InKMO2?m8*z7O7G+DMImM!G*U=hd-IP!hyC`3Stc*dH zr&~gkgcAnq95`+__%WY{_-iV+EmM;vZNAy&wb|5e_nTF;`aR5NB2QZyczb4PzVTfy za~!j&PKk!BQu!OsI5;shFPqr~-Rf1L*N5-(f_P1HTN?ussB6}vd)el~jp+fHn>WYffMtO~QPT0u zaPNTm*6)TzAn@XXj(Q!VKE*8|(+fdqU5!Y~v`jR+J}ItE`U;lQR_JnMOX5&$)5$|; z8_Y5|&X-+(8Gx9I=_C8plb0?kOsb>D)AoC6wD##rkc!OQ8cd{XYA7y6T4QA*iar)A z2RG*7`qP{51#TlKMLh|h?-ZtR^JCKBrzW>*TdPP?*faZul$Ca}Uxd0l z7kqn7YtA+Ar|xU^9Ls#ZdvJr2vHJsTV}$Zs8fOy4<24O!cdV5od~JunM!F7R!>;TN zZ$elJ>4j}2vqi0LkJ!` z46`f2W#JFbPWm}#hW~I#M390x4+?kD*OW%(p_(^pYp@9(P=65=|KIKUT-E+woV}0gn|enc`O;^f4=Iycj)QObadNS zSH%i=KkX*GCfHN0C7$pZfT@vrOw(xbAAA?u27|OMNjDl_&)eLo$bkHmcXuwW7#G>p zm)43Q=|08RFPYs=VIQH}Q=h&&_ZyGQ({VTb`w#K>9br?xC9(YuZ8GEu>>Bjw*Qh}X zaBTWAbIs?=EC41#x-*dy``hiN# zzu){WSqB%zWr9$18F3&7gJ?w~{HmoiM1ui&H2_l8 za8%m?JHV?|E+MVR0h2Lqo}5`7P^5%@bmyS_M1LJ9U6&0STr%NL)t?$asv`4*bJL9H zAouR0=KzxEL2#1`krrfzyN>^Wd4Us0XA*A;conOI!rmDjG-<2P;ixo!WFifSK)-g+ zsfD<6(x1`dpY{uhy3Tx#p1E(ysdYq`>GjKZXyDJh?Iq554gfwsq78v1z&jNBV5&qG zYHA%~Q(Im$aoN{=RtFsAz!++XDo7u*1*fq1%cxW9__}554+=^HGoJI!1Z}eB)H*b$ zcI>a#^4|t})My#~hBK}KGkT^5D{NzI6Zf%+>Jc~k;Kh#GeQT6_{J%IprGI`tkJlpe zML&gzh;RdIYFtJQL|=)3tjhVvr624fk{)i-Pqiw?u4h1c;M`Bd6fAGWB}cocff$Z2 ziyH$%{CF?I167mcg(5ZEgK%l(c9K1P9FQL&`zxhQA^72=34iq#m~v(d4jd!9PuYUd zGB1jQrRFhHAt_GVA-RXK}5 zD403h;)9DXZ|JYZM z|H;7rv-nD!SdP4E$SB}_f2gh{_Q4r>dj+nSI$4_zE@pj_IV5c4yi*QsrWcrd>u4Lx zrVlguF}}aefaLtV64%EqdkwBuJ(-1>V*fn`{adX52{|-HrvF6$-*Rh)O8DPa8N5aA z6}0@>3U2`??wdv`MK{NxNxDso;KJ=H=;nYen(z8N74o6nn#HC`d$;C>Ykf4eiE&?H z-D!Q@)q0{gklPc8tA+0<1BjpIB_-Iw0|==ex+eM3Zmj4D^lGxZQ=k$YRc|akC3^@4 zY~gK>YiKfo`OE01sf^w=q>GSKIb(e9JW9 zWe)dlwgGw-WMj6TEW>$j$GoA`b$YB}S+uPE&a3+EdjC7U7~@d~fgt z>j;-@!YWtLZs0hakya2{BGyc`0Ff5d!bALEfgc+u?xIao#|+5isUCHLB0tD`OfQ96 zhHh>kiUQfLMe?=4POi9ym$I2q>Ipw9Bl!}`z@Gm0_Z8oMKh+yYxlAa9hxe<9S5NrY zAq>d4t7)3`fcg%A?Ozf`(TeC+p+BYv>K`*f-wt4EtMV15VogBjlY8jG(?oeR{rGEi z=Rh;DO!cN(=?oNn>&{CPHg3y@Tie~#8{5IVZZ=^9rdt5W)8qJ|F%qU->;>LxCz^3Nesl{JR}2pVc4e%%FG_$qo|;)M<>HdfXV$$hdzDMfS-p z6USGHE$pU0>t#T2!W0l*Iglj_sikKvh=2+-ziOKI&3Y>kjfuFlS(w;HJZ#Tf(L7M! z+49-PG5A5L?^(CACVTu6J5{5&tXBIR7ZxcocXUb%+p1zrsEfe9$n|6;jnN%p`hvR1 zI`4FaNjLO|=l*{+sqz=DIdJd(j~qOA#sc;Yb_L}ebsZH*aJ{jtxv@X6gTlMeBUC4F ztRkKNMNP5g&!3Pl|9WTwzvt^IiW4luPNN_(AI#YTOuWzFT1HSOsL*f1?cQ!Ml0`%= z+rI7AJgRsaxF(8G3>WyEh0WfZ5KvZcD zUOE;W;pmq0aZG#XHVapKHvjw~r*C{>GyIKl&Z9Na$1v385x|*Awg;6NcZQXQ=HTjv zi+U&sAf{yI}!k|pcSR)Qdfr4_%7V%@CT z@hQVT@gyRZ?p@TfW|L`eZ}=zY)CKM4X*nM@ciJqA9@GnNi7wbDWaI1hu%&as2+BGE08{<+7I5z zxVUFac1977%lxFam)HY`Xp?fcjD#nh73-vL9TUBmt}WuI$MWDT|9&GoPUS?P1hg!t z#XNuI1uqLK@72kY-;5bl=X1HGw`tg>)rwcGwJ2$rH&jxz@#*omrY;Tv_wn0w9W9oT zUHv@SNx>r>)yZieU5ZU3Ue)&qj@6;8ecfdi4Cws{ABye6m$~S5;E{c^2~@^L ztVyNVw!Ac7T>8A>2H7@!;9NYHv~7A7F@3Y?n%KjlpUy3hu(fhbZk^CM9-gN2rf7&7 zLDrBBNY)e)anRP95zqY=!%ZZ^w2(Y@gt!(S4lT+$)RT(=VTJ)ND`Xa#Y?)>oU)5n5xVTPs`IVHRMQVm04)il;R!~eV zHs*%3+>G(S`0Y;WdxVwF4=h_Y|3*ZuL~(_fJT^U>%xAjp0{MLJ=mcV=4syFC7tGqIYT z)JhGA#gDVR2cBH_^n}6W_kW4tq6vgfuvV5-@^e)gX|>cpS^EzbXZva<#8v-cH$Zfe znKoN*#T+%I|DMMb$cf(u_7vv7z1iuvf&Jfks{FTs9s1kA{zU`(CC!kcALogAD_QNV znPqz5dd?at-gW$|4WIBXMc*fY)c9p)5-*LM2eLt7G}*@Z2Zupb&{D1f^w9G$WE?|^ zo_<3(9p%6L_w(r>`33k`pz93^9ZXf`k4H6>FAHd1EFIchE^=G)v{SZ}Vg;!NO)9Wp zVn7jFh-1J~_BrRwX966hnT@EKx4--RPr?V}h>9Y(HtU_l9I(|E5}Z&xJNPbY3G3m_ zoy*O1o5GQz$T@5kee*X8r_uEPI{6MF0E1bn2UmFQw{>gZ@N7vozmK1UC{cdd!D@sW z54W7pWnn-n_VB<1kPaX1k?GjxhNG<$JP&sU4A6&(Et=DBEIw)o{C@sV!AA=|jDi*+ zQBz8WU=Q@l`dpUgyBJ@KC25sk?pUWi{UEvFy6J*vDFrOon$OLDALV!WM`a=ZKeoY) G_x}$8gkLNG literal 0 HcmV?d00001 diff --git a/images/ge4.jpg b/images/ge4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b3a4b4acdd107d05d25958003b6366f65c29a922 GIT binary patch literal 23605 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 CQl0+* literal 0 HcmV?d00001 diff --git a/images/grid_mdp.jpg b/images/grid_mdp.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fa77fa2768ec47d9f1ffa609650e862c38b24a88 GIT binary patch literal 13536 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≪%ac>58Zc1p=1NXs&le0v|8# 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 literal 0 HcmV?d00001 diff --git a/mdp.ipynb b/mdp.ipynb index af46f948c..4a3f1f757 100644 --- a/mdp.ipynb +++ b/mdp.ipynb @@ -30,7 +30,9 @@ "* Overview\n", "* MDP\n", "* Grid MDP\n", - "* Value Iteration Visualization" + "* Value Iteration\n", + " * Value Iteration Visualization\n", + "* Policy Iteration" ] }, { @@ -547,7 +549,7 @@ "collapsed": true }, "source": [ - "# Value Iteration\n", + "# VALUE ITERATION\n", "\n", "Now that we have looked how to represent MDPs. Let's aim at solving them. Our ultimate goal is to obtain an optimal policy. We start with looking at Value Iteration and a visualisation that should help us understanding it better.\n", "\n", @@ -649,6 +651,30 @@ "pseudocode(\"Value-Iteration\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### AIMA3e\n", + "__function__ VALUE-ITERATION(_mdp_, _ε_) __returns__ a utility function \n", + " __inputs__: _mdp_, an MDP with states _S_, actions _A_(_s_), transition model _P_(_s′_ | _s_, _a_), \n", + "      rewards _R_(_s_), discount _γ_ \n", + "   _ε_, the maximum error allowed in the utility of any state \n", + " __local variables__: _U_, _U′_, vectors of utilities for states in _S_, initially zero \n", + "        _δ_, the maximum change in the utility of any state in an iteration \n", + "\n", + " __repeat__ \n", + "   _U_ ← _U′_; _δ_ ← 0 \n", + "   __for each__ state _s_ in _S_ __do__ \n", + "     _U′_\\[_s_\\] ← _R_(_s_) + _γ_ max_a_ ∈ _A_(_s_) Σ _P_(_s′_ | _s_, _a_) _U_\\[_s′_\\] \n", + "     __if__ | _U′_\\[_s_\\] − _U_\\[_s_\\] | > _δ_ __then__ _δ_ ← | _U′_\\[_s_\\] − _U_\\[_s_\\] | \n", + " __until__ _δ_ < _ε_(1 − _γ_)/_γ_ \n", + " __return__ _U_ \n", + "\n", + "---\n", + "__Figure ??__ The value iteration algorithm for calculating utilities of states. The termination condition is from Equation (__??__)." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -766,6 +792,1198 @@ "source": [ "Move the slider above to observe how the utility changes across iterations. It is also possible to move the slider using arrow keys or to jump to the value by directly editing the number with a double click. The **Visualize Button** will automatically animate the slider for you. The **Extra Delay Box** allows you to set time delay in seconds upto one second for each time step. There is also an interactive editor for grid-world problems `grid_mdp.py` in the gui folder for you to play around with." ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "# POLICY ITERATION\n", + "\n", + "We have already seen that value iteration converges to the optimal policy long before it accurately estimates the utility function. \n", + "If one action is clearly better than all the others, then the exact magnitude of the utilities in the states involved need not be precise. \n", + "The policy iteration algorithm works on this insight. \n", + "The algorithm executes two fundamental steps:\n", + "* **Policy evaluation**: Given a policy _πᵢ_, calculate _Uᵢ = U(πᵢ)_, the utility of each state if _πᵢ_ were to be executed.\n", + "* **Policy improvement**: Calculate a new policy _πᵢ₊₁_ using one-step look-ahead based on the utility values calculated.\n", + "\n", + "The algorithm terminates when the policy improvement step yields no change in the utilities. \n", + "Refer to **Figure 17.6** in the book to see how this is an improvement over value iteration.\n", + "We now have a simplified version of the Bellman equation\n", + "\n", + "$$U_i(s) = R(s) + \\gamma \\sum_{s'}P(s'\\ |\\ s, \\pi_i(s))U_i(s')$$\n", + "\n", + "An important observation in this equation is that this equation doesn't have the `max` operator, which makes it linear.\n", + "For _n_ states, we have _n_ linear equations with _n_ unknowns, which can be solved exactly in time _**O(n³)**_.\n", + "For more implementational details, have a look at **Section 17.3**.\n", + "Let us now look at how the expected utility is found and how `policy_iteration` is implemented." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def expected_utility(a, s, U, mdp):\n",
+       "    """The expected utility of doing a in state s, according to the MDP and U."""\n",
+       "    return sum([p * U[s1] for (p, s1) in mdp.T(s, a)])\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(expected_utility)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def policy_iteration(mdp):\n",
+       "    """Solve an MDP by policy iteration [Figure 17.7]"""\n",
+       "    U = {s: 0 for s in mdp.states}\n",
+       "    pi = {s: random.choice(mdp.actions(s)) for s in mdp.states}\n",
+       "    while True:\n",
+       "        U = policy_evaluation(pi, U, mdp)\n",
+       "        unchanged = True\n",
+       "        for s in mdp.states:\n",
+       "            a = argmax(mdp.actions(s), key=lambda a: expected_utility(a, s, U, mdp))\n",
+       "            if a != pi[s]:\n",
+       "                pi[s] = a\n",
+       "                unchanged = False\n",
+       "        if unchanged:\n",
+       "            return pi\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(policy_iteration)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
Fortunately, it is not necessary to do _exact_ policy evaluation. \n", + "The utilities can instead be reasonably approximated by performing some number of simplified value iteration steps.\n", + "The simplified Bellman update equation for the process is\n", + "\n", + "$$U_{i+1}(s) \\leftarrow R(s) + \\gamma\\sum_{s'}P(s'\\ |\\ s,\\pi_i(s))U_{i}(s')$$\n", + "\n", + "and this is repeated _k_ times to produce the next utility estimate. This is called _modified policy iteration_." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def policy_evaluation(pi, U, mdp, k=20):\n",
+       "    """Return an updated utility mapping U from each state in the MDP to its\n",
+       "    utility, using an approximation (modified policy iteration)."""\n",
+       "    R, T, gamma = mdp.R, mdp.T, mdp.gamma\n",
+       "    for i in range(k):\n",
+       "        for s in mdp.states:\n",
+       "            U[s] = R(s) + gamma * sum([p * U[s1] for (p, s1) in T(s, pi[s])])\n",
+       "    return U\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(policy_evaluation)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now solve **`sequential_decision_environment`** using `policy_iteration`." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{(0, 0): (0, 1),\n", + " (0, 1): (0, 1),\n", + " (0, 2): (1, 0),\n", + " (1, 0): (1, 0),\n", + " (1, 2): (1, 0),\n", + " (2, 0): (0, 1),\n", + " (2, 1): (0, 1),\n", + " (2, 2): (1, 0),\n", + " (3, 0): (-1, 0),\n", + " (3, 1): None,\n", + " (3, 2): None}" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "policy_iteration(sequential_decision_environment)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "pseudocode('Policy-Iteration')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### AIMA3e\n", + "__function__ POLICY-ITERATION(_mdp_) __returns__ a policy \n", + " __inputs__: _mdp_, an MDP with states _S_, actions _A_(_s_), transition model _P_(_s′_ | _s_, _a_) \n", + " __local variables__: _U_, a vector of utilities for states in _S_, initially zero \n", + "        _π_, a policy vector indexed by state, initially random \n", + "\n", + " __repeat__ \n", + "   _U_ ← POLICY\\-EVALUATION(_π_, _U_, _mdp_) \n", + "   _unchanged?_ ← true \n", + "   __for each__ state _s_ __in__ _S_ __do__ \n", + "     __if__ max_a_ ∈ _A_(_s_) Σ_s′_ _P_(_s′_ | _s_, _a_) _U_\\[_s′_\\] > Σ_s′_ _P_(_s′_ | _s_, _π_\\[_s_\\]) _U_\\[_s′_\\] __then do__ \n", + "       _π_\\[_s_\\] ← argmax_a_ ∈ _A_(_s_) Σ_s′_ _P_(_s′_ | _s_, _a_) _U_\\[_s′_\\] \n", + "       _unchanged?_ ← false \n", + " __until__ _unchanged?_ \n", + " __return__ _π_ \n", + "\n", + "---\n", + "__Figure ??__ The policy iteration algorithm for calculating an optimal policy." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "## Sequential Decision Problems\n", + "\n", + "Now that we have the tools required to solve MDPs, let us see how Sequential Decision Problems can be solved step by step and how a few built-in tools in the GridMDP class help us better analyse the problem at hand. \n", + "As always, we will work with the grid world from **Figure 17.1** from the book.\n", + "![title](images/grid_mdp.jpg)\n", + "
This is the environment for our agent.\n", + "We assume for now that the environment is _fully observable_, so that the agent always knows where it is.\n", + "We also assume that the transitions are **Markovian**, that is, the probability of reaching state _s'_ from state _s_ only on _s_ and not on the history of earlier states.\n", + "Almost all stochastic decision problems can be reframed as a Markov Decision Process just by tweaking the definition of a _state_ for that particular problem.\n", + "
\n", + "However, the actions of our agent in this environment are unreliable.\n", + "In other words, the motion of our agent is stochastic. \n", + "More specifically, the agent does the intended action with a probability of _0.8_, but with probability _0.1_, it moves to the right and with probability _0.1_ it moves to the left of the intended direction.\n", + "The agent stays put if it bumps into a wall.\n", + "![title](images/grid_mdp_agent.jpg)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These properties of the agent are called the transition properties and are hardcoded into the GridMDP class as you can see below." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
    def T(self, state, action):\n",
+       "        if action is None:\n",
+       "            return [(0.0, state)]\n",
+       "        else:\n",
+       "            return [(0.8, self.go(state, action)),\n",
+       "                    (0.1, self.go(state, turn_right(action))),\n",
+       "                    (0.1, self.go(state, turn_left(action)))]\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(GridMDP.T)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To completely define our task environment, we need to specify the utility function for the agent. \n", + "This is the function that gives the agent a rough estimate of how good being in a particular state is, or how much _reward_ an agent receives by being in that state.\n", + "The agent then tries to maximize the reward it gets.\n", + "As the decision problem is sequential, the utility function will depend on a sequence of states rather than on a single state.\n", + "For now, we simply stipulate that in each state s, the agent receives a finite reward _R(s)_.\n", + "\n", + "For any given state, the actions the agent can take are encoded as given below:\n", + "- Move Up: (0, 1)\n", + "- Move Down: (0, -1)\n", + "- Move Left: (-1, 0)\n", + "- Move Right: (1, 0)\n", + "- Do nothing: `None`\n", + "\n", + "We now wonder what a valid solution to the problem might look like. \n", + "We cannot have fixed action sequences as the environment is stochastic and we can eventually end up in an undesirable state.\n", + "Therefore, a solution must specify what the agent shoulddo for _any_ state the agent might reach.\n", + "
\n", + "Such a solution is known as a **policy** and is usually denoted by **π**.\n", + "
\n", + "The **optimal policy** is the policy that yields the highest expected utility an is usually denoted by **π* **.\n", + "
\n", + "The `GridMDP` class has a useful method `to_arrows` that outputs a grid showing the direction the agent should move, given a policy.\n", + "We will use this later to better understand the properties of the environment." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
    def to_arrows(self, policy):\n",
+       "        chars = {\n",
+       "            (1, 0): '>', (0, 1): '^', (-1, 0): '<', (0, -1): 'v', None: '.'}\n",
+       "        return self.to_grid({s: chars[a] for (s, a) in policy.items()})\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(GridMDP.to_arrows)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This method directly encodes the actions that the agent can take (described above) to characters representing arrows and shows it in a grid format for human visalization purposes. \n", + "It converts the received policy from a `dictionary` to a grid using the `to_grid` method." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
    def to_grid(self, mapping):\n",
+       "        """Convert a mapping from (x, y) to v into a [[..., v, ...]] grid."""\n",
+       "        return list(reversed([[mapping.get((x, y), None)\n",
+       "                               for x in range(self.cols)]\n",
+       "                              for y in range(self.rows)]))\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(GridMDP.to_grid)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we have all the tools required and a good understanding of the agent and the environment, we consider some cases and see how the agent should behave for each case." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Case 1\n", + "---\n", + "R(s) = -0.04 in all states except terminal states" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Note that this environment is also initialized in mdp.py by default\n", + "sequential_decision_environment = GridMDP([[-0.04, -0.04, -0.04, +1],\n", + " [-0.04, None, -0.04, -1],\n", + " [-0.04, -0.04, -0.04, -0.04]],\n", + " terminals=[(3, 2), (3, 1)])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will use the `best_policy` function to find the best policy for this environment.\n", + "But, as you can see, `best_policy` requires a utility function as well.\n", + "We already know that the utility function can be found by `value_iteration`.\n", + "Hence, our best policy is:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "pi = best_policy(sequential_decision_environment, value_iteration(sequential_decision_environment, .001))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now use the `to_arrows` method to see how our agent should pick its actions in the environment." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> > > .\n", + "^ None ^ .\n", + "^ > ^ <\n" + ] + } + ], + "source": [ + "from utils import print_table\n", + "print_table(sequential_decision_environment.to_arrows(pi))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is exactly the output we expected\n", + "
\n", + "![title](images/-0.04.jpg)\n", + "
\n", + "Notice that, because the cost of taking a step is fairly small compared with the penalty for ending up in `(4, 2)` by accident, the optimal policy is conservative. \n", + "In state `(3, 1)` it recommends taking the long way round, rather than taking the shorter way and risking getting a large negative reward of -1 in `(4, 2)`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Case 2\n", + "---\n", + "R(s) = -0.4 in all states except in terminal states" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "sequential_decision_environment = GridMDP([[-0.4, -0.4, -0.4, +1],\n", + " [-0.4, None, -0.4, -1],\n", + " [-0.4, -0.4, -0.4, -0.4]],\n", + " terminals=[(3, 2), (3, 1)])" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> > > .\n", + "^ None ^ .\n", + "^ > ^ <\n" + ] + } + ], + "source": [ + "pi = best_policy(sequential_decision_environment, value_iteration(sequential_decision_environment, .001))\n", + "from utils import print_table\n", + "print_table(sequential_decision_environment.to_arrows(pi))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is exactly the output we expected\n", + "![title](images/-0.4.jpg)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As the reward for each state is now more negative, life is certainly more unpleasant.\n", + "The agent takes the shortest route to the +1 state and is willing to risk falling into the -1 state by accident." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Case 3\n", + "---\n", + "R(s) = -4 in all states except terminal states" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "sequential_decision_environment = GridMDP([[-4, -4, -4, +1],\n", + " [-4, None, -4, -1],\n", + " [-4, -4, -4, -4]],\n", + " terminals=[(3, 2), (3, 1)])" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> > > .\n", + "^ None > .\n", + "> > > ^\n" + ] + } + ], + "source": [ + "pi = best_policy(sequential_decision_environment, value_iteration(sequential_decision_environment, .001))\n", + "from utils import print_table\n", + "print_table(sequential_decision_environment.to_arrows(pi))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is exactly the output we expected\n", + "![title](images/-4.jpg)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The living reward for each state is now more negative than the most negative terminal. Life is so painful that the agent heads for the nearest exit as even the worst exit is less painful than the current state." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Case 4\n", + "---\n", + "R(s) = 4 in all states except terminal states" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "sequential_decision_environment = GridMDP([[4, 4, 4, +1],\n", + " [4, None, 4, -1],\n", + " [4, 4, 4, 4]],\n", + " terminals=[(3, 2), (3, 1)])" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> > < .\n", + "> None < .\n", + "> > > v\n" + ] + } + ], + "source": [ + "pi = best_policy(sequential_decision_environment, value_iteration(sequential_decision_environment, .001))\n", + "from utils import print_table\n", + "print_table(sequential_decision_environment.to_arrows(pi))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case, the output we expect is\n", + "![title](images/4.jpg)\n", + "
\n", + "As life is positively enjoyable and the agent avoids _both_ exits.\n", + "Even though the output we get is not exactly what we want, it is definitely not wrong.\n", + "The scenario here requires the agent to anything but reach a terminal state, as this is the only way the agent can maximize its reward (total reward tends to infinity), and the program does just that.\n", + "
\n", + "Currently, the GridMDP class doesn't support an explicit marker for a \"do whatever you like\" action or a \"don't care\" condition.\n", + "You can however, extend the class to do so.\n", + "
\n", + "For in-depth knowledge about sequential decision problems, refer **Section 17.1** in the AIMA book." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Appendix\n", + "\n", + "Surprisingly, it turns out that there are six other optimal policies for various ranges of R(s). \n", + "You can try to find them out for yourself.\n", + "See **Exercise 17.5**.\n", + "To help you with this, we have a GridMDP editor in `grid_mdp.py` in the GUI folder. \n", + "
\n", + "Here's a brief tutorial about how to use it\n", + "
\n", + "Let us use it to solve `Case 2` above\n", + "1. Run `python gui/grid_mdp.py` from the master directory.\n", + "2. Enter the dimensions of the grid (3 x 4 in this case), and click on `'Build a GridMDP'`\n", + "3. Click on `Initialize` in the `Edit` menu.\n", + "4. Set the reward as -0.4 and click `Apply`. Exit the dialog. \n", + "![title](images/ge0.jpg)\n", + "
\n", + "5. Select cell (1, 1) and check the `Wall` radio button. `Apply` and exit the dialog.\n", + "![title](images/ge1.jpg)\n", + "
\n", + "6. Select cells (4, 1) and (4, 2) and check the `Terminal` radio button for both. Set the rewards appropriately and click on `Apply`. Exit the dialog. Your window should look something like this.\n", + "![title](images/ge2.jpg)\n", + "
\n", + "7. You are all set up now. Click on `Build and Run` in the `Build` menu and watch the heatmap calculate the utility function.\n", + "![title](images/ge4.jpg)\n", + "
\n", + "Green shades indicate positive utilities and brown shades indicate negative utilities. \n", + "The values of the utility function and arrow diagram will pop up in separate dialogs after the algorithm converges." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/tests/test_mdp.py b/tests/test_mdp.py index 9117a32d9..1aed4b58f 100644 --- a/tests/test_mdp.py +++ b/tests/test_mdp.py @@ -70,14 +70,6 @@ def test_policy_iteration(): (2, 1): (1, 0), (2, 2): (1, 0), (3, 0): (0, 1), (3, 1): None, (3, 2): None} - assert policy_iteration(sequential_decision_environment_3) == { - (0, 0): (-1, 0), (0, 1): (0, -1), (0, 2): (0, -1), (0, 3): (0, -1), (0, 4): None, - (1, 0): (-1, 0), (1, 1): (-1, 0), (1, 4): (1, 0), - (2, 0): (-1, 0), (2, 1): (0, -1), (2, 2): None, (2, 4): (1, 0), - (3, 0): (-1, 0), (3, 2): None, (3, 3): (1, 0), (3, 4): (1, 0), - (4, 0): (-1, 0), (4, 3): (1, 0), (4, 4): (1, 0), - (5, 0): None, (5, 1): (0, 1), (5, 2): (0, 1), (5, 3): (0, 1), (5, 4): (1, 0)} - def test_best_policy(): pi = best_policy(sequential_decision_environment, From 2c902441af84b452abe3f685fa88b65a64baa694 Mon Sep 17 00:00:00 2001 From: Anthony Marakis Date: Fri, 23 Feb 2018 04:02:13 +0200 Subject: [PATCH 020/269] Update search.ipynb (#726) --- search.ipynb | 255 +++++++++++++++++++++++---------------------------- 1 file changed, 117 insertions(+), 138 deletions(-) diff --git a/search.ipynb b/search.ipynb index 6da1d0ef5..52eb39c0e 100644 --- a/search.ipynb +++ b/search.ipynb @@ -17,23 +17,7 @@ "metadata": { "scrolled": true }, - "outputs": [ - { - "ename": "FileNotFoundError", - "evalue": "[Errno 2] No such file or directory: '/home/apurv/aima-python/aima-data/orings.csv'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mFileNotFoundError\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;32mfrom\u001b[0m \u001b[0msearch\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mnotebook\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpsource\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# Needed to hide warnings in the matplotlib sections\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mwarnings\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/aima-python/notebook.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mgames\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mTicTacToe\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malphabeta_player\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrandom_player\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mFig52Extended\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minfinity\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mlogic\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mparse_definite_clause\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstandardize_variables\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0munify\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubst\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mlearning\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mDataSet\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mIPython\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdisplay\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mHTML\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdisplay\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mcollections\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mCounter\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdefaultdict\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/aima-python/learning.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1105\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1106\u001b[0m orings = DataSet(name='orings', target='Distressed',\n\u001b[0;32m-> 1107\u001b[0;31m attrnames=\"Rings Distressed Temp Pressure Flightnum\")\n\u001b[0m\u001b[1;32m 1108\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1109\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/aima-python/learning.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, examples, attrs, attrnames, target, inputs, values, distance, name, source, exclude)\u001b[0m\n\u001b[1;32m 96\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mparse_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mexamples\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 97\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mexamples\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 98\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mparse_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mopen_data\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m'.csv'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\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 99\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 100\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mexamples\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/aima-python/utils.py\u001b[0m in \u001b[0;36mopen_data\u001b[0;34m(name, mode)\u001b[0m\n\u001b[1;32m 414\u001b[0m \u001b[0maima_file\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maima_root\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'aima-data'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 415\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 416\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maima_file\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 417\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 418\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: '/home/apurv/aima-python/aima-data/orings.csv'" - ] - } - ], + "outputs": [], "source": [ "from search import *\n", "from notebook import psource\n", @@ -158,10 +142,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 2, + "metadata": {}, "outputs": [], "source": [ "romania_map = UndirectedGraph(dict(\n", @@ -205,10 +187,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 3, + "metadata": {}, "outputs": [], "source": [ "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)" @@ -232,11 +212,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'Arad': (91, 492), 'Bucharest': (400, 327), 'Craiova': (253, 288), 'Drobeta': (165, 299), 'Eforie': (562, 293), 'Fagaras': (305, 449), 'Giurgiu': (375, 270), 'Hirsova': (534, 350), 'Iasi': (473, 506), 'Lugoj': (165, 379), 'Mehadia': (168, 339), 'Neamt': (406, 537), 'Oradea': (131, 571), 'Pitesti': (320, 368), 'Rimnicu': (233, 410), 'Sibiu': (207, 457), 'Timisoara': (94, 410), 'Urziceni': (456, 350), 'Vaslui': (509, 444), 'Zerind': (108, 531)}\n" + ] + } + ], "source": [ "romania_locations = romania_map.locations\n", "print(romania_locations)" @@ -251,10 +237,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 5, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", @@ -277,10 +261,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 6, + "metadata": {}, "outputs": [], "source": [ "# initialise a graph\n", @@ -323,10 +305,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 7, + "metadata": {}, "outputs": [], "source": [ "# initialise a graph\n", @@ -442,7 +422,7 @@ "source": [ "## SIMPLE PROBLEM SOLVING AGENT PROGRAM\n", "\n", - "Let us now define a Simple Problem Solving Agent Program. Run the next cell to see how the abstract class SimpleProblemSolvingAgentProgram is defined in the search module." + "Let us now define a Simple Problem Solving Agent Program. Run the next cell to see how the abstract class `SimpleProblemSolvingAgentProgram` is defined in the search module." ] }, { @@ -462,21 +442,16 @@ "source": [ "The SimpleProblemSolvingAgentProgram class has six methods: \n", "\n", - "* `__init__(self, intial_state=None)`: This is the `contructor` of the class and is the first method to be called when the class is instantiated. It takes in a keyword argument, `initial_state` which is initially `None`. The argument `intial_state` represents the state from which the agent starts. \n", - "\n", + "* `__init__(self, intial_state=None)`: This is the `contructor` of the class and is the first method to be called when the class is instantiated. It takes in a keyword argument, `initial_state` which is initially `None`. The argument `intial_state` represents the state from which the agent starts.\n", "\n", "* `__call__(self, percept)`: This method updates the `state` of the agent based on its `percept` using the `update_state` method. It then formulates a `goal` with the help of `formulate_goal` method and a `problem` using the `formulate_problem` method and returns a sequence of actions to solve it (using the `search` method).\n", "\n", - "\n", - "* `update_state(self, percept)`: This method updates the `state` of the agent based on its `percept`. \n", - "\n", + "* `update_state(self, percept)`: This method updates the `state` of the agent based on its `percept`.\n", "\n", "* `formulate_goal(self, state)`: Given a `state` of the agent, this method formulates the `goal` for it.\n", "\n", - "\n", "* `formulate_problem(self, state, goal)`: It is used in problem formulation given a `state` and a `goal` for the `agent`.\n", "\n", - "\n", "* `search(self, problem)`: This method is used to search a sequence of `actions` to solve a `problem`." ] }, @@ -695,8 +670,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now, we use ipywidgets to display a slider, a button and our romania map. By sliding the slider we can have a look at all the intermediate steps of a particular search algorithm. By pressing the button **Visualize**, you can see all the steps without interacting with the slider. These two helper functions are the callback functions which are called when we interact with the slider and the button.\n", - "\n" + "Now, we use `ipywidgets` to display a slider, a button and our romania map. By sliding the slider we can have a look at all the intermediate steps of a particular search algorithm. By pressing the button **Visualize**, you can see all the steps without interacting with the slider. These two helper functions are the callback functions which are called when we interact with the slider and the button." ] }, { @@ -791,7 +765,7 @@ "source": [ "## BREADTH-FIRST SEARCH\n", "\n", - "Let's change all the node_colors to starting position and define a different problem statement." + "Let's change all the `node_colors` to starting position and define a different problem statement." ] }, { @@ -997,7 +971,8 @@ "metadata": {}, "source": [ "## BEST FIRST SEARCH\n", - "Let's change all the node_colors to starting position and define a different problem statement." + "\n", + "Let's change all the `node_colors` to starting position and define a different problem statement." ] }, { @@ -1084,7 +1059,7 @@ "source": [ "## UNIFORM COST SEARCH\n", "\n", - "Let's change all the node_colors to starting position and define a different problem statement." + "Let's change all the `node_colors` to starting position and define a different problem statement." ] }, { @@ -1193,7 +1168,7 @@ "source": [ "## A\\* SEARCH\n", "\n", - "Let's change all the node_colors to starting position and define a different problem statement." + "Let's change all the `node_colors` to starting position and define a different problem statement." ] }, { @@ -1321,69 +1296,73 @@ " | 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", + "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", "#### Heuristics :-\n", "\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", + "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", - "2.) No. of Misplaced Tiles:- The heuristic calculates the number of misplaced tiles between the current state and goal state.\n", + "2) No. of Misplaced Tiles:- The heuristic calculates the number of misplaced tiles between the current state and goal state.\n", "\n", - "3.) Sqrt of Manhattan Distance:- It calculates the square root of Manhattan distance.\n", + "3) Sqrt of Manhattan Distance:- It calculates the square root of Manhattan distance.\n", "\n", - "4.) Max Heuristic:- It assign the score as max of Manhattan Distance and No. of misplaced tiles. " + "4) Max Heuristic:- It assign the score as the maximum between \"Manhattan Distance\" and \"No. of Misplaced Tiles\". " ] }, { "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": true - }, + "execution_count": 11, + "metadata": {}, "outputs": [], "source": [ - "# heuristics for 8 Puzzle Problem\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", - "\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", + " 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, y = 0, 0\n", + " \n", + " for i in range(len(state)):\n", + " index_state[state[i]] = index[i]\n", + " \n", + " mhd = 0\n", + " \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", + " \n", + " return mhd\n", "\n", "def sqrt_manhanttan(state,goal):\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", + " 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, y = 0, 0\n", + " \n", + " for i in range(len(state)):\n", + " index_state[state[i]] = index[i]\n", + " \n", + " mhd = 0\n", + " \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", + " \n", + " return math.sqrt(mhd)\n", "\n", "def max_heuristic(state,goal):\n", - "\tscore1 = manhanttan(state, goal)\n", - "\tscore2 = linear(state, goal)\n", - "\treturn max(score1, score2)\t\t\n" + " score1 = manhanttan(state, goal)\n", + " score2 = linear(state, goal)\n", + " return max(score1, score2)" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -1391,45 +1370,45 @@ "output_type": "stream", "text": [ "True\n", - "Number of explored nodes by the following heuristic are: 126\n", + "Number of explored nodes by the following heuristic are: 145\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", + "[2, 4, 3, 1, 5, 6, 7, 0, 8]\n", + "[2, 4, 3, 1, 0, 6, 7, 5, 8]\n", + "[2, 0, 3, 1, 4, 6, 7, 5, 8]\n", + "[0, 2, 3, 1, 4, 6, 7, 5, 8]\n", + "[1, 2, 3, 0, 4, 6, 7, 5, 8]\n", + "[1, 2, 3, 4, 0, 6, 7, 5, 8]\n", + "[1, 2, 3, 4, 5, 6, 7, 0, 8]\n", "[1, 2, 3, 4, 5, 6, 7, 8, 0]\n", - "Number of explored nodes by the following heuristic are: 129\n", + "Number of explored nodes by the following heuristic are: 153\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", + "[2, 4, 3, 1, 5, 6, 7, 0, 8]\n", + "[2, 4, 3, 1, 0, 6, 7, 5, 8]\n", + "[2, 0, 3, 1, 4, 6, 7, 5, 8]\n", + "[0, 2, 3, 1, 4, 6, 7, 5, 8]\n", + "[1, 2, 3, 0, 4, 6, 7, 5, 8]\n", + "[1, 2, 3, 4, 0, 6, 7, 5, 8]\n", + "[1, 2, 3, 4, 5, 6, 7, 0, 8]\n", "[1, 2, 3, 4, 5, 6, 7, 8, 0]\n", - "Number of explored nodes by the following heuristic are: 126\n", + "Number of explored nodes by the following heuristic are: 145\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", + "[2, 4, 3, 1, 5, 6, 7, 0, 8]\n", + "[2, 4, 3, 1, 0, 6, 7, 5, 8]\n", + "[2, 0, 3, 1, 4, 6, 7, 5, 8]\n", + "[0, 2, 3, 1, 4, 6, 7, 5, 8]\n", + "[1, 2, 3, 0, 4, 6, 7, 5, 8]\n", + "[1, 2, 3, 4, 0, 6, 7, 5, 8]\n", + "[1, 2, 3, 4, 5, 6, 7, 0, 8]\n", "[1, 2, 3, 4, 5, 6, 7, 8, 0]\n", - "Number of explored nodes by the following heuristic are: 139\n", + "Number of explored nodes by the following heuristic are: 169\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", + "[2, 4, 3, 1, 5, 6, 7, 0, 8]\n", + "[2, 4, 3, 1, 0, 6, 7, 5, 8]\n", + "[2, 0, 3, 1, 4, 6, 7, 5, 8]\n", + "[0, 2, 3, 1, 4, 6, 7, 5, 8]\n", + "[1, 2, 3, 0, 4, 6, 7, 5, 8]\n", + "[1, 2, 3, 4, 0, 6, 7, 5, 8]\n", + "[1, 2, 3, 4, 5, 6, 7, 0, 8]\n", "[1, 2, 3, 4, 5, 6, 7, 8, 0]\n" ] } @@ -1438,10 +1417,10 @@ "# 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" + "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" ] }, { @@ -2105,14 +2084,14 @@ "\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", + "* **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", + "\n", + "\n", + "* **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", + "* **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." ] }, @@ -2881,7 +2860,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.6.3" } }, "nbformat": 4, From 3c56362bf9174100cab22cd4aff77f5b4dac4521 Mon Sep 17 00:00:00 2001 From: Sai Sasank Date: Fri, 23 Feb 2018 07:34:02 +0530 Subject: [PATCH 021/269] Fix EightPuzzle class implementation in search.py (#710) (#733) * Fix EightPuzzle class implementation * Fix EightPuzzle class implementation (#710) * Address style issues (#710) --- search.py | 163 +++++++++++++++++++++++++++++------------------------- 1 file changed, 88 insertions(+), 75 deletions(-) diff --git a/search.py b/search.py index b705d6f28..14388c684 100644 --- a/search.py +++ b/search.py @@ -404,91 +404,104 @@ def astar_search(problem, h=None): # ______________________________________________________________________________ # A* heuristics -class EightPuzzle(): +class EightPuzzle(Problem): - def __init__(self): - self.path = [] - self.final = [] + """The problem of sliding tiles numbered from 1 to 8 on a 3x3 board, + where one of the squares is a blank. A state is represented as a 3x3 list, + where element at index i,j represents the tile number (0 if it's an empty square).""" + + def __init__(self, initial, goal=None): + if goal: + self.goal = goal + else: + self.goal = [ [0,1,2], + [3,4,5], + [6,7,8] ] + Problem.__init__(self, initial, goal) + def find_blank_square(self, state): + """Return the index of the blank square in a given state""" + for row in len(state): + for column in len(row): + if state[row][column] == 0: + index_blank_square = (row, column) + return index_blank_square + + def actions(self, state): + """Return the actions that can be executed in the given state. + The result would be a list, since there are only four possible actions + in any given state of the environment.""" + + possible_actions = list() + index_blank_square = self.find_blank_square(state) + + if index_blank_square(0) == 0: + possible_actions += ['DOWN'] + elif index_blank_square(0) == 1: + possible_actions += ['UP', 'DOWN'] + elif index_blank_square(0) == 2: + possible_actions += ['UP'] + + if index_blank_square(1) == 0: + possible_actions += ['RIGHT'] + elif index_blank_square(1) == 1: + possible_actions += ['LEFT', 'RIGHT'] + elif index_blank_square(1) == 2: + possible_actions += ['LEFT'] + + return possible_actions + + def result(self, state, action): + """Given state and action, return a new state that is the result of the action. + Action is assumed to be a valid action in the state.""" + + blank_square = self.find_blank_square(state) + new_state = [row[:] for row in state] + + if action=='UP': + new_state[blank_square(0)][blank_square(1)] = new_state[blank_square(0)-1][blank_square(1)] + new_state[blank_square(0)-1][blank_square(1)] = 0 + elif action=='LEFT': + new_state[blank_square(0)][blank_square(1)] = new_state[blank_square(0)][blank_square(1)-1] + new_state[blank_square(0)][blank_square(1)-1] = 0 + elif action=='DOWN': + new_state[blank_square(0)][blank_square(1)] = new_state[blank_square(0)+1][blank_square(1)] + new_state[blank_square(0)+1][blank_square(1)] = 0 + elif action=='RIGHT': + new_state[blank_square(0)][blank_square(1)] = new_state[blank_square(0)][blank_square(1)+1] + new_state[blank_square(0)][blank_square(1)+1] = 0 + else: + print("Invalid Action!") + return new_state + + def goal_test(self, state): + """Given a state, return True if state is a goal state or False, otherwise""" + for row in len(state): + for column in len(row): + if state[row][col] != self.goal[row][column]: + return False + return True + 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): + 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:[4,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)) - + + def h(self, state): + """Return the heuristic value for a given state. Heuristic function used is + h(n) = number of misplaced tiles.""" + num_misplaced_tiles = 0 + for row in len(state): + for column in len(row): + if state[row][col] != self.goal[row][column]: + num_misplaced_tiles += 1 + return num_misplaced_tiles # ______________________________________________________________________________ # Other search algorithms From 25e4193e6cb8fbe6486cf1e6530da8ddf7f26bb2 Mon Sep 17 00:00:00 2001 From: Apurv Bajaj Date: Fri, 23 Feb 2018 07:40:35 +0530 Subject: [PATCH 022/269] Modified table for TableDrivenVacuumAgent (#738) --- vacuum_world.ipynb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/vacuum_world.ipynb b/vacuum_world.ipynb index 34bcd2d5b..8557bed3f 100644 --- a/vacuum_world.ipynb +++ b/vacuum_world.ipynb @@ -212,13 +212,15 @@ "outputs": [], "source": [ "table = {((loc_A, 'Clean'),): 'Right',\n", - " ((loc_A, 'Dirty'),): 'Suck',\n", - " ((loc_B, 'Clean'),): 'Left',\n", - " ((loc_B, 'Dirty'),): 'Suck',\n", - " ((loc_A, 'Clean'), (loc_A, 'Clean')): 'Right',\n", - " ((loc_A, 'Clean'), (loc_A, 'Dirty')): 'Suck',\n", - " ((loc_A, 'Clean'), (loc_A, 'Clean'), (loc_A, 'Clean')): 'Right',\n", - " ((loc_A, 'Clean'), (loc_A, 'Clean'), (loc_A, 'Dirty')): 'Suck',\n", + " ((loc_A, 'Dirty'),): 'Suck',\n", + " ((loc_B, 'Clean'),): 'Left',\n", + " ((loc_B, 'Dirty'),): 'Suck',\n", + " ((loc_A, 'Dirty'), (loc_A, 'Clean')): 'Right',\n", + " ((loc_A, 'Clean'), (loc_B, 'Dirty')): 'Suck',\n", + " ((loc_B, 'Clean'), (loc_A, 'Dirty')): 'Suck',\n", + " ((loc_B, 'Dirty'), (loc_B, 'Clean')): 'Left',\n", + " ((loc_A, 'Dirty'), (loc_A, 'Clean'), (loc_B, 'Dirty')): 'Suck',\n", + " ((loc_B, 'Dirty'), (loc_B, 'Clean'), (loc_A, 'Dirty')): 'Suck'\n", " }" ] }, From 516eff08987e7ca5bf26424374decb75d772dca0 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Fri, 23 Feb 2018 03:04:05 +0000 Subject: [PATCH 023/269] Enhanced explanation of value iteration (#736) * Enhanced explanation of value iteration * Fixed minor typo --- mdp.ipynb | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 149 insertions(+), 10 deletions(-) diff --git a/mdp.ipynb b/mdp.ipynb index 4a3f1f757..50a936dd5 100644 --- a/mdp.ipynb +++ b/mdp.ipynb @@ -61,7 +61,7 @@ "source": [ "## MDP\n", "\n", - "To begin with let us look at the implementation of MDP class defined in mdp.py The docstring tells us what all is required to define a MDP namely - set of states,actions, initial state, transition model, and a reward function. Each of these are implemented as methods. Do not close the popup so that you can follow along the description of code below." + "To begin with let us look at the implementation of MDP class defined in mdp.py The docstring tells us what all is required to define a MDP namely - set of states, actions, initial state, transition model, and a reward function. Each of these are implemented as methods. Do not close the popup so that you can follow along the description of code below." ] }, { @@ -338,7 +338,7 @@ "source": [ "## GRID MDP\n", "\n", - "Now we look at a concrete implementation that makes use of the MDP as base class. The GridMDP class in the mdp module is used to represent a grid world MDP like the one shown in in **Fig 17.1** of the AIMA Book. The code should be easy to understand if you have gone through the CustomMDP example." + "Now we look at a concrete implementation that makes use of the MDP as base class. The GridMDP class in the mdp module is used to represent a grid world MDP like the one shown in in **Fig 17.1** of the AIMA Book. We assume for now that the environment is _fully observable_, so that the agent always knows where it is. The code should be easy to understand if you have gone through the CustomMDP example." ] }, { @@ -553,25 +553,164 @@ "\n", "Now that we have looked how to represent MDPs. Let's aim at solving them. Our ultimate goal is to obtain an optimal policy. We start with looking at Value Iteration and a visualisation that should help us understanding it better.\n", "\n", - "We start by calculating Value/Utility for each of the states. The Value of each state is the expected sum of discounted future rewards given we start in that state and follow a particular policy pi.The algorithm Value Iteration (**Fig. 17.4** in the book) relies on finding solutions of the Bellman's Equation. The intuition Value Iteration works is because values propagate. This point will we more clear after we encounter the visualisation. For more information you can refer to **Section 17.2** of the book. \n" + "We start by calculating Value/Utility for each of the states. The Value of each state is the expected sum of discounted future rewards given we start in that state and follow a particular policy _pi_. The value or the utility of a state is given by\n", + "\n", + "$$U(s)=R(s)+\\gamma\\max_{a\\epsilon A(s)}\\sum_{s'} P(s'\\ |\\ s,a)U(s')$$\n", + "\n", + "This is called the Bellman equation. The algorithm Value Iteration (**Fig. 17.4** in the book) relies on finding solutions of this Equation. The intuition Value Iteration works is because values propagate through the state space by means of local updates. This point will we more clear after we encounter the visualisation. For more information you can refer to **Section 17.2** of the book. \n" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def value_iteration(mdp, epsilon=0.001):\n",
+       "    """Solving an MDP by value iteration. [Figure 17.4]"""\n",
+       "    U1 = {s: 0 for s in mdp.states}\n",
+       "    R, T, gamma = mdp.R, mdp.T, mdp.gamma\n",
+       "    while True:\n",
+       "        U = U1.copy()\n",
+       "        delta = 0\n",
+       "        for s in mdp.states:\n",
+       "            U1[s] = R(s) + gamma * max([sum([p * U[s1] for (p, s1) in T(s, a)])\n",
+       "                                        for a in mdp.actions(s)])\n",
+       "            delta = max(delta, abs(U1[s] - U[s]))\n",
+       "        if delta < epsilon * (1 - gamma) / gamma:\n",
+       "            return U\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "psource(value_iteration)" ] - }, + }, { "cell_type": "markdown", "metadata": {}, "source": [ - "It takes as inputs two parameters, an MDP to solve and epsilon the maximum error allowed in the utility of any state. It returns a dictionary containing utilities where the keys are the states and values represent utilities. Let us solve the **sequencial_decision_enviornment** GridMDP." + "It takes as inputs two parameters, an MDP to solve and epsilon the maximum error allowed in the utility of any state. It returns a dictionary containing utilities where the keys are the states and values represent utilities.
Value Iteration starts with arbitrary initial values for the utilities, calculates the right side of the Bellman equation and plugs it into the left hand side, thereby updating the utility of each state from the utilities of its neighbors. \n", + "This is repeated until equilibrium is reached. \n", + "It works on the principle of _Dynamic Programming_. \n", + "If U_i(s) is the utility value for state _s_ at the _i_ th iteration, the iteration step, called Bellman update, looks like this:\n", + "\n", + "$$ U_{i+1}(s) \\leftarrow R(s) + \\gamma \\max_{a \\epsilon A(s)} \\sum_{s'} P(s'\\ |\\ s,a)U_{i}(s') $$\n", + "\n", + "As you might have noticed, `value_iteration` has an infinite loop. How do we decide when to stop iterating? \n", + "The concept of _contraction_ successfully explains the convergence of value iteration. \n", + "Refer to **Section 17.2.3** of the book for a detailed explanation. \n", + "In the algorithm, we calculate a value _delta_ that measures the difference in the utilities of the current time step and the previous time step. \n", + "\n", + "$$\\delta = \\max{(\\delta, \\begin{vmatrix}U_{i + 1}(s) - U_i(s)\\end{vmatrix})}$$\n", + "\n", + "This value of delta decreases over time.\n", + "We terminate the algorithm if the delta value is less than a threshold value determined by the hyperparameter _epsilon_.\n", + "\n", + "$$\\delta \\lt \\epsilon \\frac{(1 - \\gamma)}{\\gamma}$$\n", + "\n", + "To summarize, the Bellman update is a _contraction_ by a factor of `gamma` on the space of utility vectors. \n", + "Hence, from the properties of contractions in general, it follows that `value_iteration` always converges to a unique solution of the Bellman equations whenever gamma is less than 1.\n", + "We then terminate the algorithm when a reasonable approximation is achieved.\n", + "In practice, it often occurs that the policy _pi_ becomes optimal long before the utility function converges. For the given 4 x 3 environment with _gamma = 0.9_, the policy _pi_ is optimal when _i = 4_, even though the maximum error in the utility function is stil 0.46.This can be clarified from **figure 17.6** in the book. Hence, to increase computational efficiency, we often use another method to solve MDPs called Policy Iteration which we will see in the later part of this notebook. \n", + "
For now, let us solve the **sequential_decision_environment** GridMDP using `value_iteration`." ] }, { From a6edaa107977c3ef3348d93afd7cd65ffb935a6d Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Fri, 23 Feb 2018 03:04:26 +0000 Subject: [PATCH 024/269] Minor enhancement to grid_mdp editor (#734) * Fixed reset function to reset placeholder variables as well * Added functionality to display best policy --- gui/grid_mdp.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/gui/grid_mdp.py b/gui/grid_mdp.py index fd5aeb8ae..d975ba5df 100644 --- a/gui/grid_mdp.py +++ b/gui/grid_mdp.py @@ -64,6 +64,22 @@ def display(gridmdp, _height, _width): dialog.mainloop() +def display_best_policy(_best_policy, _height, _width): + ''' displays best policy ''' + + dialog = tk.Toplevel() + dialog.wm_title('Best Policy') + + container = tk.Frame(dialog) + container.pack(side=tk.TOP, fill=tk.BOTH, expand=True) + + for i in range(max(1, _height)): + for j in range(max(1, _width)): + label = ttk.Label(container, text=_best_policy[i][j], font=('Helvetica', 12, 'bold')) + label.grid(row=i + 1, column=j + 1, padx=3, pady=3) + + dialog.mainloop() + def initialize_dialogbox(_width, _height, gridmdp, terminals, buttons): ''' creates dialogbox for initialization ''' @@ -98,7 +114,7 @@ def initialize_dialogbox(_width, _height, gridmdp, terminals, buttons): btn_apply = ttk.Button(container, text='Apply', command=partial(initialize_update_table, _width, _height, gridmdp, terminals, buttons, reward, term, wall, label_reward, entry_reward, rbtn_term, rbtn_wall)) btn_apply.grid(row=5, column=0, sticky='nsew', pady=5, padx=5) - btn_reset = ttk.Button(container, text='Reset', command=partial(initialize_reset_all, _width, _height, gridmdp, terminals, buttons, label_reward, entry_reward, rbtn_wall, rbtn_term)) + btn_reset = ttk.Button(container, text='Reset', command=partial(initialize_reset_all, _width, _height, gridmdp, terminals, buttons, reward, term, wall, label_reward, entry_reward, rbtn_wall, rbtn_term)) btn_reset.grid(row=5, column=1, sticky='nsew', pady=5, padx=5) btn_ok = ttk.Button(container, text='Ok', command=dialog.destroy) btn_ok.grid(row=5, column=2, sticky='nsew', pady=5, padx=5) @@ -146,9 +162,12 @@ def initialize_update_table(_width, _height, gridmdp, terminals, buttons, reward for j in range(max(1, _width)): update_table(i, j, gridmdp, terminals, buttons, reward, term, wall, label_reward, entry_reward, rbtn_term, rbtn_wall) -def reset_all(_height, i, j, gridmdp, terminals, buttons, label_reward, entry_reward, rbtn_wall, rbtn_term): +def reset_all(_height, i, j, gridmdp, terminals, buttons, reward, term, wall, label_reward, entry_reward, rbtn_wall, rbtn_term): ''' functionality for reset button ''' + reward.set(0.0) + term.set(0) + wall.set(0) gridmdp[i][j] = 0.0 buttons[i][j].configure(style='TButton') buttons[i][j].config(text=f'({_height - i - 1}, {j})') @@ -163,12 +182,12 @@ def reset_all(_height, i, j, gridmdp, terminals, buttons, label_reward, entry_re rbtn_wall.state(['!focus', '!selected']) rbtn_term.state(['!focus', '!selected']) -def initialize_reset_all(_width, _height, gridmdp, terminals, buttons, label_reward, entry_reward, rbtn_wall, rbtn_term): +def initialize_reset_all(_width, _height, gridmdp, terminals, buttons, reward, term, wall, label_reward, entry_reward, rbtn_wall, rbtn_term): ''' runs reset_all for all cells ''' for i in range(max(1, _height)): for j in range(max(1, _width)): - reset_all(_height, i, j, gridmdp, terminals, buttons, label_reward, entry_reward, rbtn_wall, rbtn_term) + reset_all(_height, i, j, gridmdp, terminals, buttons, reward, term, wall, label_reward, entry_reward, rbtn_wall, rbtn_term) def external_reset(_width, _height, gridmdp, terminals, buttons): ''' reset from edit menu ''' @@ -263,7 +282,7 @@ def dialogbox(i, j, gridmdp, terminals, buttons, _height): btn_apply = ttk.Button(container, text='Apply', command=partial(update_table, i, j, gridmdp, terminals, buttons, reward, term, wall, label_reward, entry_reward, rbtn_term, rbtn_wall)) btn_apply.grid(row=5, column=0, sticky='nsew', pady=5, padx=5) - btn_reset = ttk.Button(container, text='Reset', command=partial(reset_all, _height, i, j, gridmdp, terminals, buttons, label_reward, entry_reward, rbtn_wall, rbtn_term)) + btn_reset = ttk.Button(container, text='Reset', command=partial(reset_all, _height, i, j, gridmdp, terminals, buttons, reward, term, wall, label_reward, entry_reward, rbtn_wall, rbtn_term)) btn_reset.grid(row=5, column=1, sticky='nsew', pady=5, padx=5) btn_ok = ttk.Button(container, text='Ok', command=dialog.destroy) btn_ok.grid(row=5, column=2, sticky='nsew', pady=5, padx=5) @@ -595,6 +614,9 @@ def animate_graph(self, i): if (self.delta < self.epsilon * (1 - self.gamma) / self.gamma) or (self.iterations > 60) and self.terminated == False: self.terminated = True display(self.grid_to_show, self._height, self._width) + + pi = best_policy(self.sequential_decision_environment, value_iteration(self.sequential_decision_environment, .01)) + display_best_policy(self.sequential_decision_environment.to_arrows(pi), self._height, self._width) ax = fig.gca() ax.xaxis.set_major_locator(MaxNLocator(integer=True)) From 195708d959fd91e0df24179e4d6d97f38ed7ad70 Mon Sep 17 00:00:00 2001 From: Apurv Bajaj Date: Fri, 23 Feb 2018 08:35:22 +0530 Subject: [PATCH 025/269] Fix #731: Add table and tests for TableDrivenVacuumAgent (#732) * Add test for TableDrivenVacuumAgent * Debug Travis * Minor fix * Fixed table for TableDrivenAgent * Update README --- README.md | 2 +- agents.py | 12 ++++++------ tests/test_agents.py | 15 ++++++++++++++- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 91ce5b37e..3b811453b 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 2 | Model-Based-Vacuum-Agent | `ModelBasedVacuumAgent` | [`agents.py`][agents] | Done | Included | | 2.1 | Environment | `Environment` | [`agents.py`][agents] | Done | Included | | 2.1 | Agent | `Agent` | [`agents.py`][agents] | Done | Included | -| 2.3 | Table-Driven-Vacuum-Agent | `TableDrivenVacuumAgent` | [`agents.py`][agents] | | Included | +| 2.3 | Table-Driven-Vacuum-Agent | `TableDrivenVacuumAgent` | [`agents.py`][agents] | Done | Included | | 2.7 | Table-Driven-Agent | `TableDrivenAgent` | [`agents.py`][agents] | | Included | | 2.8 | Reflex-Vacuum-Agent | `ReflexVacuumAgent` | [`agents.py`][agents] | Done | Included | | 2.10 | Simple-Reflex-Agent | `SimpleReflexAgent` | [`agents.py`][agents] | | Included | diff --git a/agents.py b/agents.py index 9308225f2..9b1ff0d33 100644 --- a/agents.py +++ b/agents.py @@ -181,12 +181,12 @@ def TableDrivenVacuumAgent(): ((loc_A, 'Dirty'),): 'Suck', ((loc_B, 'Clean'),): 'Left', ((loc_B, 'Dirty'),): 'Suck', - ((loc_A, 'Clean'), (loc_A, 'Clean')): 'Right', - ((loc_A, 'Clean'), (loc_A, 'Dirty')): 'Suck', - # ... - ((loc_A, 'Clean'), (loc_A, 'Clean'), (loc_A, 'Clean')): 'Right', - ((loc_A, 'Clean'), (loc_A, 'Clean'), (loc_A, 'Dirty')): 'Suck', - # ... + ((loc_A, 'Dirty'), (loc_A, 'Clean')): 'Right', + ((loc_A, 'Clean'), (loc_B, 'Dirty')): 'Suck', + ((loc_B, 'Clean'), (loc_A, 'Dirty')): 'Suck', + ((loc_B, 'Dirty'), (loc_B, 'Clean')): 'Left', + ((loc_A, 'Dirty'), (loc_A, 'Clean'), (loc_B, 'Dirty')): 'Suck', + ((loc_B, 'Dirty'), (loc_B, 'Clean'), (loc_A, 'Dirty')): 'Suck' } return Agent(TableDrivenAgentProgram(table)) diff --git a/tests/test_agents.py b/tests/test_agents.py index 59ab6bce9..eedaf0d76 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -2,7 +2,7 @@ from agents import Direction from agents import Agent from agents import ReflexVacuumAgent, ModelBasedVacuumAgent, TrivialVacuumEnvironment, compare_agents,\ - RandomVacuumAgent + RandomVacuumAgent, TableDrivenVacuumAgent random.seed("aima-python") @@ -94,6 +94,19 @@ def test_ModelBasedVacuumAgent() : assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} +def test_TableDrivenVacuumAgent() : + # create an object of the TableDrivenVacuumAgent + agent = TableDrivenVacuumAgent() + # create an object of the TrivialVacuumEnvironment + environment = TrivialVacuumEnvironment() + # add agent to the environment + environment.add_thing(agent) + # run the environment + environment.run() + # check final status of the environment + assert environment.status == {(1, 0):'Clean', (0, 0):'Clean'} + + def test_compare_agents() : environment = TrivialVacuumEnvironment agents = [ModelBasedVacuumAgent, ReflexVacuumAgent] From 8dbf924efd2fa4b16f2579a82e8388573bb9f36e Mon Sep 17 00:00:00 2001 From: Anthony Marakis Date: Fri, 23 Feb 2018 05:06:33 +0200 Subject: [PATCH 026/269] Update text.py (#740) --- text.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/text.py b/text.py index c62c1627a..8dc0ab855 100644 --- a/text.py +++ b/text.py @@ -21,6 +21,11 @@ class UnigramWordModel(CountingProbDist): can add, sample, or get P[word], just like with CountingProbDist. You can also generate a random text, n words long, with P.samples(n).""" + def __init__(self, observations, default=0): + # Call CountingProbDist constructor, + # passing the observations and default parameters. + super(UnigramWordModel, self).__init__(observations, default) + def samples(self, n): """Return a string of n words, random according to the model.""" return ' '.join(self.sample() for i in range(n)) @@ -203,7 +208,7 @@ class UnixConsultant(IRSystem): def __init__(self): IRSystem.__init__(self, stopwords="how do i the a of") - + import os aima_root = os.path.dirname(__file__) mandir = os.path.join(aima_root, 'aima-data/MAN/') From 3092089f0efd13acd02c68f11c26f47bf6d3dcf1 Mon Sep 17 00:00:00 2001 From: Apurv Bajaj Date: Fri, 23 Feb 2018 08:37:22 +0530 Subject: [PATCH 027/269] Fix #741: Add learning agent to vacuum_world.ipynb (#742) * Modified table for TableDrivenVacuumAgent * Add learing agent * Add image for learning agent --- images/general_learning_agent.jpg | Bin 0 -> 32599 bytes vacuum_world.ipynb | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 images/general_learning_agent.jpg diff --git a/images/general_learning_agent.jpg b/images/general_learning_agent.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a8153bef8b4e10ff4cb0a908a62153ea910ac351 GIT binary patch literal 32599 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 literal 0 HcmV?d00001 diff --git a/vacuum_world.ipynb b/vacuum_world.ipynb index 8557bed3f..59950566b 100644 --- a/vacuum_world.ipynb +++ b/vacuum_world.ipynb @@ -30,7 +30,8 @@ "* Simple Reflex Agent Program\n", "* Model-Based Reflex Agent Program\n", "* Goal-Based Agent Program\n", - "* Utility-Based Agent Program" + "* Utility-Based Agent Program\n", + "* Learning Agent" ] }, { @@ -518,6 +519,20 @@ "**Figure 2.14** of the book shows a model-based, utility-based agent:\n", "" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## LEARNING AGENT\n", + "\n", + "Learning allows the agent to operate in initially unknown environments and to become more competent than its initial knowledge alone might allow. Here, we will breifly introduce the main ideas of learning agents. \n", + "\n", + "A learning agent can be divided into four conceptual components. The **learning element** is responsible for making improvements. It uses the feedback from the **critic** on how the agent is doing and determines how the performance element should be modified to do better in the future. The **performance element** is responsible for selecting external actions for the agent: it takes in percepts and decides on actions. The critic tells the learning element how well the agent is doing with respect to a fixed performance standard. It is necesaary because the percepts themselves provide no indication of the agent's success. The last component of the learning agent is the **problem generator**. It is responsible for suggesting actions that will lead to new and informative experiences. \n", + "\n", + "**Figure 2.15** of the book sums up the components and their working: \n", + "" + ] } ], "metadata": { From eae217bef528a05e04d35b9d4d9abcbb25b0dde4 Mon Sep 17 00:00:00 2001 From: Anthony Marakis Date: Fri, 23 Feb 2018 05:08:19 +0200 Subject: [PATCH 028/269] Learning: Neural Net Test + Minor Styling Fix (#746) * Update learning.py * Update test_learning.py --- learning.py | 2 +- tests/test_learning.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/learning.py b/learning.py index 0d3d3b110..a231e8a78 100644 --- a/learning.py +++ b/learning.py @@ -309,7 +309,7 @@ def predict(example): def NaiveBayesLearner(dataset, continuous=True, simple=False): if simple: return NaiveBayesSimple(dataset) - if(continuous): + if continuous: return NaiveBayesContinuous(dataset) else: return NaiveBayesDiscrete(dataset) diff --git a/tests/test_learning.py b/tests/test_learning.py index 3c6d02d28..cb43fe1b6 100644 --- a/tests/test_learning.py +++ b/tests/test_learning.py @@ -192,7 +192,7 @@ def test_neural_network_learner(): ([7.3, 4.0, 6.1, 2.4], 2), ([7.0, 3.3, 6.1, 2.5], 2)] assert grade_learner(nNL, tests) >= 1/3 - assert err_ratio(nNL, iris) < 0.2 + assert err_ratio(nNL, iris) < 0.21 def test_perceptron(): From 06af67e6ef905369e2ad3df8465e6b80fb8a7673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20H=C3=B6nig?= Date: Thu, 22 Feb 2018 22:09:21 -0500 Subject: [PATCH 029/269] Fix various typos. (#750) --- CONTRIBUTING.md | 4 ++-- agents.ipynb | 16 ++++++++-------- csp.ipynb | 8 ++++---- games.ipynb | 2 +- gui/xy_vacuum_environment.py | 2 +- learning.ipynb | 6 +++--- logic.ipynb | 2 +- mdp.ipynb | 4 ++-- nlp.ipynb | 26 +++++++++++++------------- nlp.py | 4 ++-- nlp_apps.ipynb | 2 +- notebook.py | 6 +++--- planning.ipynb | 2 +- probability.py | 2 +- rl.ipynb | 4 ++-- rl.py | 2 +- search-4e.ipynb | 6 +++--- search.ipynb | 2 +- search.py | 2 +- tests/test_utils.py | 2 +- text.ipynb | 10 +++++----- utils.py | 6 +++--- vacuum_world.ipynb | 8 ++++---- 23 files changed, 64 insertions(+), 64 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c8a165a25..ed17ed4da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ In more detail: ## Port to Python 3; Pythonic Idioms; py.test -- Check for common problems in [porting to Python 3](http://python3porting.com/problems.html), such as: `print` is now a function; `range` and `map` and other functions no longer produce `list`s; objects of different types can no longer be compared with `<`; strings are now Unicode; it would be nice to move `%` string formating to `.format`; there is a new `next` function for generators; integer division now returns a float; we can now use set literals. +- Check for common problems in [porting to Python 3](http://python3porting.com/problems.html), such as: `print` is now a function; `range` and `map` and other functions no longer produce `list`s; objects of different types can no longer be compared with `<`; strings are now Unicode; it would be nice to move `%` string formatting to `.format`; there is a new `next` function for generators; integer division now returns a float; we can now use set literals. - Replace old Lisp-based idioms with proper Python idioms. For example, we have many functions that were taken directly from Common Lisp, such as the `every` function: `every(callable, items)` returns true if every element of `items` is callable. This is good Lisp style, but good Python style would be to use `all` and a generator expression: `all(callable(f) for f in items)`. Eventually, fix all calls to these legacy Lisp functions and then remove the functions. - Add more tests in `test_*.py` files. Strive for terseness; it is ok to group multiple asserts into one `def test_something():` function. Move most tests to `test_*.py`, but it is fine to have a single `doctest` example in the docstring of a function in the `.py` file, if the purpose of the doctest is to explain how to use the function, rather than test the implementation. @@ -83,7 +83,7 @@ Reporting Issues - Under which versions of Python does this happen? -- Provide an example of the issue occuring. +- Provide an example of the issue occurring. - Is anybody working on this? diff --git a/agents.ipynb b/agents.ipynb index 6c547ee6c..ed6920bd0 100644 --- a/agents.ipynb +++ b/agents.ipynb @@ -566,7 +566,7 @@ " print('{} decided to move {}wards at location: {}'.format(str(agent)[1:-1], agent.direction.direction, agent.location))\n", " agent.moveforward()\n", " else:\n", - " print('{} decided to move {}wards at location: {}, but couldnt'.format(str(agent)[1:-1], agent.direction.direction, agent.location))\n", + " print('{} decided to move {}wards at location: {}, but couldn\\'t'.format(str(agent)[1:-1], agent.direction.direction, agent.location))\n", " agent.moveforward(False)\n", " elif action == \"eat\":\n", " items = self.list_things_at(agent.location, tclass=Food)\n", @@ -605,17 +605,17 @@ "EnergeticBlindDog decided to move downwards at location: [0, 1]\n", "EnergeticBlindDog drank Water at location: [0, 2]\n", "EnergeticBlindDog decided to turnright at location: [0, 2]\n", - "EnergeticBlindDog decided to move leftwards at location: [0, 2], but couldnt\n", + "EnergeticBlindDog decided to move leftwards at location: [0, 2], but couldn't\n", "EnergeticBlindDog decided to turnright at location: [0, 2]\n", "EnergeticBlindDog decided to turnright at location: [0, 2]\n", "EnergeticBlindDog decided to turnleft at location: [0, 2]\n", "EnergeticBlindDog decided to turnleft at location: [0, 2]\n", - "EnergeticBlindDog decided to move leftwards at location: [0, 2], but couldnt\n", + "EnergeticBlindDog decided to move leftwards at location: [0, 2], but couldn't\n", "EnergeticBlindDog decided to turnleft at location: [0, 2]\n", "EnergeticBlindDog decided to turnright at location: [0, 2]\n", - "EnergeticBlindDog decided to move leftwards at location: [0, 2], but couldnt\n", + "EnergeticBlindDog decided to move leftwards at location: [0, 2], but couldn't\n", "EnergeticBlindDog decided to turnleft at location: [0, 2]\n", - "EnergeticBlindDog decided to move downwards at location: [0, 2], but couldnt\n", + "EnergeticBlindDog decided to move downwards at location: [0, 2], but couldn't\n", "EnergeticBlindDog decided to turnright at location: [0, 2]\n", "EnergeticBlindDog decided to turnleft at location: [0, 2]\n", "EnergeticBlindDog decided to turnleft at location: [0, 2]\n", @@ -684,7 +684,7 @@ " print('{} decided to move {}wards at location: {}'.format(str(agent)[1:-1], agent.direction.direction, agent.location))\n", " agent.moveforward()\n", " else:\n", - " print('{} decided to move {}wards at location: {}, but couldnt'.format(str(agent)[1:-1], agent.direction.direction, agent.location))\n", + " print('{} decided to move {}wards at location: {}, but couldn\\'t'.format(str(agent)[1:-1], agent.direction.direction, agent.location))\n", " agent.moveforward(False)\n", " elif action == \"eat\":\n", " items = self.list_things_at(agent.location, tclass=Food)\n", @@ -1012,7 +1012,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "EnergeticBlindDog decided to move leftwards at location: [0, 3], but couldnt\n" + "EnergeticBlindDog decided to move leftwards at location: [0, 3], but couldn't\n" ] }, { @@ -1069,7 +1069,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "EnergeticBlindDog decided to move leftwards at location: [0, 3], but couldnt\n" + "EnergeticBlindDog decided to move leftwards at location: [0, 3], but couldn't\n" ] }, { diff --git a/csp.ipynb b/csp.ipynb index 2192352cf..f6414f701 100644 --- a/csp.ipynb +++ b/csp.ipynb @@ -647,7 +647,7 @@ "source": [ "## TREE CSP SOLVER\n", "\n", - "The `tree_csp_solver` function (**Figure 6.11** in the book) can be used to solve problems whose constraint graph is a tree. Given a CSP, with `neighbors` forming a tree, it returns an assignement that satisfies the given constraints. The algorithm works as follows:\n", + "The `tree_csp_solver` function (**Figure 6.11** in the book) can be used to solve problems whose constraint graph is a tree. Given a CSP, with `neighbors` forming a tree, it returns an assignment that satisfies the given constraints. The algorithm works as follows:\n", "\n", "First it finds the *topological sort* of the tree. This is an ordering of the tree where each variable/node comes after its parent in the tree. The function that accomplishes this is `topological_sort`, which builds the topological sort using the recursive function `build_topological`. That function is an augmented DFS, where each newly visited node of the tree is pushed on a stack. The stack in the end holds the variables topologically sorted.\n", "\n", @@ -896,7 +896,7 @@ "\n", "visualize_callback = make_visualize(iteration_slider)\n", "\n", - "visualize_button = widgets.ToggleButton(desctiption = \"Visualize\", value = False)\n", + "visualize_button = widgets.ToggleButton(description = \"Visualize\", value = False)\n", "time_select = widgets.ToggleButtons(description='Extra Delay:',options=['0', '0.1', '0.2', '0.5', '0.7', '1.0'])\n", "\n", "a = widgets.interactive(visualize_callback, Visualize = visualize_button, time_step=time_select)\n", @@ -1055,7 +1055,7 @@ "\n", "visualize_callback = make_visualize(iteration_slider)\n", "\n", - "visualize_button = widgets.ToggleButton(desctiption = \"Visualize\", value = False)\n", + "visualize_button = widgets.ToggleButton(description = \"Visualize\", value = False)\n", "time_select = widgets.ToggleButtons(description='Extra Delay:',options=['0', '0.1', '0.2', '0.5', '0.7', '1.0'])\n", "\n", "a = widgets.interactive(visualize_callback, Visualize = visualize_button, time_step=time_select)\n", @@ -1138,7 +1138,7 @@ "\n", "visualize_callback = make_visualize(iteration_slider)\n", "\n", - "visualize_button = widgets.ToggleButton(desctiption = \"Visualize\", value = False)\n", + "visualize_button = widgets.ToggleButton(description = \"Visualize\", value = False)\n", "time_select = widgets.ToggleButtons(description='Extra Delay:',options=['0', '0.1', '0.2', '0.5', '0.7', '1.0'])\n", "\n", "a = widgets.interactive(visualize_callback, Visualize = visualize_button, time_step=time_select)\n", diff --git a/games.ipynb b/games.ipynb index 042116969..51a2015b4 100644 --- a/games.ipynb +++ b/games.ipynb @@ -210,7 +210,7 @@ "\n", "\n", "\n", - "The states are represented wih capital letters inside the triangles (eg. \"A\") while moves are the labels on the edges between states (eg. \"a1\"). Terminal nodes carry utility values. Note that the terminal nodes are named in this example 'B1', 'B2' and 'B2' for the nodes below 'B', and so forth.\n", + "The states are represented with capital letters inside the triangles (eg. \"A\") while moves are the labels on the edges between states (eg. \"a1\"). Terminal nodes carry utility values. Note that the terminal nodes are named in this example 'B1', 'B2' and 'B2' for the nodes below 'B', and so forth.\n", "\n", "We will model the moves, utilities and initial state like this:" ] diff --git a/gui/xy_vacuum_environment.py b/gui/xy_vacuum_environment.py index 14c3abc1a..4ba4497ea 100644 --- a/gui/xy_vacuum_environment.py +++ b/gui/xy_vacuum_environment.py @@ -124,7 +124,7 @@ def update_env(self): xf, yf = agt.location def reset_env(self, agt): - """Resets the GUI environment to the intial state.""" + """Resets the GUI environment to the initial state.""" self.read_env() for i, btn_row in enumerate(self.buttons): for j, btn in enumerate(btn_row): diff --git a/learning.ipynb b/learning.ipynb index 0e4d97934..dc3a78697 100644 --- a/learning.ipynb +++ b/learning.ipynb @@ -1065,7 +1065,7 @@ "source": [ "The implementation of `DecisionTreeLearner` provided in [learning.py](https://github.com/aimacode/aima-python/blob/master/learning.py) uses information gain as the metric for selecting which attribute to test for splitting. The function builds the tree top-down in a recursive manner. Based on the input it makes one of the four choices:\n", "

    \n", - "
  1. If the input at the current step has no training data we return the mode of classes of input data recieved in the parent step (previous level of recursion).
  2. \n", + "
  3. If the input at the current step has no training data we return the mode of classes of input data received in the parent step (previous level of recursion).
  4. \n", "
  5. If all values in training data belong to the same class it returns a `DecisionLeaf` whose class label is the class which all the data belongs to.
  6. \n", "
  7. If the data has no attributes that can be tested we return the class with highest plurality value in the training data.
  8. \n", "
  9. We choose the attribute which gives the highest amount of entropy gain and return a `DecisionFork` which splits based on this attribute. Each branch recursively calls `decision_tree_learning` to construct the sub-tree.
  10. \n", @@ -1155,7 +1155,7 @@ "\n", "*a)* The probability of **Class** in the dataset.\n", "\n", - "*b)* The conditional probability of each feature occuring in an item classified in **Class**.\n", + "*b)* The conditional probability of each feature occurring in an item classified in **Class**.\n", "\n", "*c)* The probabilities of each individual feature.\n", "\n", @@ -1339,7 +1339,7 @@ "source": [ "You can see the means of the features for the \"Setosa\" class and the deviations for \"Versicolor\".\n", "\n", - "The prediction function will work similarly to the Discrete algorithm. It will multiply the probability of the class occuring with the conditional probabilities of the feature values for the class.\n", + "The prediction function will work similarly to the Discrete algorithm. It will multiply the probability of the class occurring with the conditional probabilities of the feature values for the class.\n", "\n", "Since we are using the Gaussian distribution, we will input the value for each feature into the Gaussian function, together with the mean and deviation of the feature. This will return the probability of the particular feature value for the given class. We will repeat for each class and pick the max value." ] diff --git a/logic.ipynb b/logic.ipynb index fb42df7aa..4ac164861 100644 --- a/logic.ipynb +++ b/logic.ipynb @@ -766,7 +766,7 @@ "metadata": {}, "source": [ "\"Nono ... has some missiles\"
    \n", - "This states the existance of some missile which is owned by Nono. $\\exists x \\text{Owns}(\\text{Nono}, x) \\land \\text{Missile}(x)$. We invoke existential instantiation to introduce a new constant `M1` which is the missile owned by Nono.\n", + "This states the existence of some missile which is owned by Nono. $\\exists x \\text{Owns}(\\text{Nono}, x) \\land \\text{Missile}(x)$. We invoke existential instantiation to introduce a new constant `M1` which is the missile owned by Nono.\n", "\n", "$\\text{Owns}(\\text{Nono}, \\text{M1}), \\text{Missile}(\\text{M1})$" ] diff --git a/mdp.ipynb b/mdp.ipynb index 50a936dd5..59d8b8e3a 100644 --- a/mdp.ipynb +++ b/mdp.ipynb @@ -329,7 +329,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "With this we have sucessfully represented our MDP. Later we will look at ways to solve this MDP." + "With this we have successfully represented our MDP. Later we will look at ways to solve this MDP." ] }, { @@ -919,7 +919,7 @@ "\n", "visualize_callback = make_visualize(iteration_slider)\n", "\n", - "visualize_button = widgets.ToggleButton(desctiption = \"Visualize\", value = False)\n", + "visualize_button = widgets.ToggleButton(description = \"Visualize\", value = False)\n", "time_select = widgets.ToggleButtons(description='Extra Delay:',options=['0', '0.1', '0.2', '0.5', '0.7', '1.0'])\n", "a = widgets.interactive(visualize_callback, Visualize = visualize_button, time_step=time_select)\n", "display(a)" diff --git a/nlp.ipynb b/nlp.ipynb index f95d8283c..7d4f3c87a 100644 --- a/nlp.ipynb +++ b/nlp.ipynb @@ -79,7 +79,7 @@ "source": [ "### Probabilistic Context-Free Grammar\n", "\n", - "While a simple CFG can be very useful, we might want to know the chance of each rule occuring. Above, we do not know if `S` is more likely to be replaced by `aSb` or `ε`. **Probabilistic Context-Free Grammars (PCFG)** are built to fill exactly that need. Each rule has a probability, given in brackets, and the probabilities of a rule sum up to 1:\n", + "While a simple CFG can be very useful, we might want to know the chance of each rule occurring. Above, we do not know if `S` is more likely to be replaced by `aSb` or `ε`. **Probabilistic Context-Free Grammars (PCFG)** are built to fill exactly that need. Each rule has a probability, given in brackets, and the probabilities of a rule sum up to 1:\n", "\n", "```\n", "S -> aSb [0.7] | ε [0.3]\n", @@ -89,7 +89,7 @@ "\n", "An issue with *PCFGs* is how we will assign the various probabilities to the rules. We could use our knowledge as humans to assign the probabilities, but that is a laborious and prone to error task. Instead, we can *learn* the probabilities from data. Data is categorized as labeled (with correctly parsed sentences, usually called a **treebank**) or unlabeled (given only lexical and syntactic category names).\n", "\n", - "With labeled data, we can simply count the occurences. For the above grammar, if we have 100 `S` rules and 30 of them are of the form `S -> ε`, we assign a probability of 0.3 to the transformation.\n", + "With labeled data, we can simply count the occurrences. For the above grammar, if we have 100 `S` rules and 30 of them are of the form `S -> ε`, we assign a probability of 0.3 to the transformation.\n", "\n", "With unlabeled data we have to learn both the grammar rules and the probability of each rule. We can go with many approaches, one of them the **inside-outside** algorithm. It uses a dynamic programming approach, that first finds the probability of a substring being generated by each rule, and then estimates the probability of each rule." ] @@ -119,7 +119,7 @@ "source": [ "### Lexicon\n", "\n", - "The lexicon of a language is defined as a list of allowable words. These words are grouped into the usual classes: `verbs`, `nouns`, `adjectives`, `adverbs`, `pronouns`, `names`, `articles`, `prepositions` and `conjuctions`. For the first five classes it is impossible to list all words, since words are continuously being added in the classes. Recently \"google\" was added to the list of verbs, and words like that will continue to pop up and get added to the lists. For that reason, these first five categories are called **open classes**. The rest of the categories have much fewer words and much less development. While words like \"thou\" were commonly used in the past but have declined almost completely in usage, most changes take many decades or centuries to manifest, so we can safely assume the categories will remain static for the foreseeable future. Thus, these categories are called **closed classes**.\n", + "The lexicon of a language is defined as a list of allowable words. These words are grouped into the usual classes: `verbs`, `nouns`, `adjectives`, `adverbs`, `pronouns`, `names`, `articles`, `prepositions` and `conjunctions`. For the first five classes it is impossible to list all words, since words are continuously being added in the classes. Recently \"google\" was added to the list of verbs, and words like that will continue to pop up and get added to the lists. For that reason, these first five categories are called **open classes**. The rest of the categories have much fewer words and much less development. While words like \"thou\" were commonly used in the past but have declined almost completely in usage, most changes take many decades or centuries to manifest, so we can safely assume the categories will remain static for the foreseeable future. Thus, these categories are called **closed classes**.\n", "\n", "An example lexicon for a PCFG (note that other classes can also be used according to the language, like `digits`, or `RelPro` for relative pronoun):\n", "\n", @@ -133,7 +133,7 @@ "Name -> john [0.05] | mary [0.05] | peter [0.01] | ...\n", "Article -> the [0.35] | a [0.25] | an [0.025] | ...\n", "Preposition -> to [0.25] | in [0.2] | at [0.1] | ...\n", - "Conjuction -> and [0.5] | or [0.2] | but [0.2] | ...\n", + "Conjunction -> and [0.5] | or [0.2] | but [0.2] | ...\n", "Digit -> 1 [0.3] | 2 [0.2] | 0 [0.2] | ...\n", "```" ] @@ -147,7 +147,7 @@ "With grammars we combine words from the lexicon into valid phrases. A grammar is comprised of **grammar rules**. Each rule transforms the left-hand side of the rule into the right-hand side. For example, `A -> B` means that `A` transforms into `B`. Let's build a grammar for the language we started building with the lexicon. We will use a PCFG.\n", "\n", "```\n", - "S -> NP VP [0.9] | S Conjuction S [0.1]\n", + "S -> NP VP [0.9] | S Conjunction S [0.1]\n", "\n", "NP -> Pronoun [0.3] | Name [0.1] | Noun [0.1] | Article Noun [0.25] |\n", " Article Adjs Noun [0.05] | Digit [0.05] | NP PP [0.1] |\n", @@ -216,9 +216,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Lexicon {'Adverb': ['here', 'lightly', 'now'], 'Verb': ['is', 'say', 'are'], 'Digit': ['1', '2', '0'], 'RelPro': ['that', 'who', 'which'], 'Conjuction': ['and', 'or', 'but'], 'Name': ['john', 'mary', 'peter'], 'Pronoun': ['me', 'you', 'he'], 'Article': ['the', 'a', 'an'], 'Noun': ['robot', 'sheep', 'fence'], 'Adjective': ['good', 'new', 'sad'], 'Preposition': ['to', 'in', 'at']}\n", + "Lexicon {'Adverb': ['here', 'lightly', 'now'], 'Verb': ['is', 'say', 'are'], 'Digit': ['1', '2', '0'], 'RelPro': ['that', 'who', 'which'], 'Conjunction': ['and', 'or', 'but'], 'Name': ['john', 'mary', 'peter'], 'Pronoun': ['me', 'you', 'he'], 'Article': ['the', 'a', 'an'], 'Noun': ['robot', 'sheep', 'fence'], 'Adjective': ['good', 'new', 'sad'], 'Preposition': ['to', 'in', 'at']}\n", "\n", - "Rules: {'RelClause': [['RelPro', 'VP']], 'Adjs': [['Adjective'], ['Adjective', 'Adjs']], 'NP': [['Pronoun'], ['Name'], ['Noun'], ['Article', 'Noun'], ['Article', 'Adjs', 'Noun'], ['Digit'], ['NP', 'PP'], ['NP', 'RelClause']], 'S': [['NP', 'VP'], ['S', 'Conjuction', 'S']], 'VP': [['Verb'], ['VP', 'NP'], ['VP', 'Adjective'], ['VP', 'PP'], ['VP', 'Adverb']], 'PP': [['Preposition', 'NP']]}\n" + "Rules: {'RelClause': [['RelPro', 'VP']], 'Adjs': [['Adjective'], ['Adjective', 'Adjs']], 'NP': [['Pronoun'], ['Name'], ['Noun'], ['Article', 'Noun'], ['Article', 'Adjs', 'Noun'], ['Digit'], ['NP', 'PP'], ['NP', 'RelClause']], 'S': [['NP', 'VP'], ['S', 'Conjunction', 'S']], 'VP': [['Verb'], ['VP', 'NP'], ['VP', 'Adjective'], ['VP', 'PP'], ['VP', 'Adverb']], 'PP': [['Preposition', 'NP']]}\n" ] } ], @@ -233,14 +233,14 @@ " Name = \"john | mary | peter\",\n", " Article = \"the | a | an\",\n", " Preposition = \"to | in | at\",\n", - " Conjuction = \"and | or | but\",\n", + " Conjunction = \"and | or | but\",\n", " Digit = \"1 | 2 | 0\"\n", ")\n", "\n", "print(\"Lexicon\", lexicon)\n", "\n", "rules = Rules(\n", - " S = \"NP VP | S Conjuction S\",\n", + " S = \"NP VP | S Conjunction S\",\n", " NP = \"Pronoun | Name | Noun | Article Noun \\\n", " | Article Adjs Noun | Digit | NP PP | NP RelClause\",\n", " VP = \"Verb | VP NP | VP Adjective | VP PP | VP Adverb\",\n", @@ -393,9 +393,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Lexicon {'Noun': [('robot', 0.4), ('sheep', 0.4), ('fence', 0.2)], 'Name': [('john', 0.4), ('mary', 0.4), ('peter', 0.2)], 'Adverb': [('here', 0.6), ('lightly', 0.1), ('now', 0.3)], 'Digit': [('0', 0.35), ('1', 0.35), ('2', 0.3)], 'Adjective': [('good', 0.5), ('new', 0.2), ('sad', 0.3)], 'Pronoun': [('me', 0.3), ('you', 0.4), ('he', 0.3)], 'Article': [('the', 0.5), ('a', 0.25), ('an', 0.25)], 'Preposition': [('to', 0.4), ('in', 0.3), ('at', 0.3)], 'Verb': [('is', 0.5), ('say', 0.3), ('are', 0.2)], 'Conjuction': [('and', 0.5), ('or', 0.2), ('but', 0.3)], 'RelPro': [('that', 0.5), ('who', 0.3), ('which', 0.2)]}\n", + "Lexicon {'Noun': [('robot', 0.4), ('sheep', 0.4), ('fence', 0.2)], 'Name': [('john', 0.4), ('mary', 0.4), ('peter', 0.2)], 'Adverb': [('here', 0.6), ('lightly', 0.1), ('now', 0.3)], 'Digit': [('0', 0.35), ('1', 0.35), ('2', 0.3)], 'Adjective': [('good', 0.5), ('new', 0.2), ('sad', 0.3)], 'Pronoun': [('me', 0.3), ('you', 0.4), ('he', 0.3)], 'Article': [('the', 0.5), ('a', 0.25), ('an', 0.25)], 'Preposition': [('to', 0.4), ('in', 0.3), ('at', 0.3)], 'Verb': [('is', 0.5), ('say', 0.3), ('are', 0.2)], 'Conjunction': [('and', 0.5), ('or', 0.2), ('but', 0.3)], 'RelPro': [('that', 0.5), ('who', 0.3), ('which', 0.2)]}\n", "\n", - "Rules: {'S': [(['NP', 'VP'], 0.6), (['S', 'Conjuction', 'S'], 0.4)], 'RelClause': [(['RelPro', 'VP'], 1.0)], 'VP': [(['Verb'], 0.3), (['VP', 'NP'], 0.2), (['VP', 'Adjective'], 0.25), (['VP', 'PP'], 0.15), (['VP', 'Adverb'], 0.1)], 'Adjs': [(['Adjective'], 0.5), (['Adjective', 'Adjs'], 0.5)], 'PP': [(['Preposition', 'NP'], 1.0)], 'NP': [(['Pronoun'], 0.2), (['Name'], 0.05), (['Noun'], 0.2), (['Article', 'Noun'], 0.15), (['Article', 'Adjs', 'Noun'], 0.1), (['Digit'], 0.05), (['NP', 'PP'], 0.15), (['NP', 'RelClause'], 0.1)]}\n" + "Rules: {'S': [(['NP', 'VP'], 0.6), (['S', 'Conjunction', 'S'], 0.4)], 'RelClause': [(['RelPro', 'VP'], 1.0)], 'VP': [(['Verb'], 0.3), (['VP', 'NP'], 0.2), (['VP', 'Adjective'], 0.25), (['VP', 'PP'], 0.15), (['VP', 'Adverb'], 0.1)], 'Adjs': [(['Adjective'], 0.5), (['Adjective', 'Adjs'], 0.5)], 'PP': [(['Preposition', 'NP'], 1.0)], 'NP': [(['Pronoun'], 0.2), (['Name'], 0.05), (['Noun'], 0.2), (['Article', 'Noun'], 0.15), (['Article', 'Adjs', 'Noun'], 0.1), (['Digit'], 0.05), (['NP', 'PP'], 0.15), (['NP', 'RelClause'], 0.1)]}\n" ] } ], @@ -410,14 +410,14 @@ " Name = \"john [0.4] | mary [0.4] | peter [0.2]\",\n", " Article = \"the [0.5] | a [0.25] | an [0.25]\",\n", " Preposition = \"to [0.4] | in [0.3] | at [0.3]\",\n", - " Conjuction = \"and [0.5] | or [0.2] | but [0.3]\",\n", + " Conjunction = \"and [0.5] | or [0.2] | but [0.3]\",\n", " Digit = \"0 [0.35] | 1 [0.35] | 2 [0.3]\"\n", ")\n", "\n", "print(\"Lexicon\", lexicon)\n", "\n", "rules = ProbRules(\n", - " S = \"NP VP [0.6] | S Conjuction S [0.4]\",\n", + " S = \"NP VP [0.6] | S Conjunction S [0.4]\",\n", " NP = \"Pronoun [0.2] | Name [0.05] | Noun [0.2] | Article Noun [0.15] \\\n", " | Article Adjs Noun [0.1] | Digit [0.05] | NP PP [0.15] | NP RelClause [0.1]\",\n", " VP = \"Verb [0.3] | VP NP [0.2] | VP Adjective [0.25] | VP PP [0.15] | VP Adverb [0.1]\",\n", diff --git a/nlp.py b/nlp.py index f34d088b5..ace6de90d 100644 --- a/nlp.py +++ b/nlp.py @@ -214,7 +214,7 @@ def __repr__(self): E_Prob = ProbGrammar('E_Prob', # The Probabilistic Grammar from the notebook ProbRules( - S="NP VP [0.6] | S Conjuction S [0.4]", + S="NP VP [0.6] | S Conjunction S [0.4]", NP="Pronoun [0.2] | Name [0.05] | Noun [0.2] | Article Noun [0.15] \ | Article Adjs Noun [0.1] | Digit [0.05] | NP PP [0.15] | NP RelClause [0.1]", VP="Verb [0.3] | VP NP [0.2] | VP Adjective [0.25] | VP PP [0.15] | VP Adverb [0.1]", @@ -232,7 +232,7 @@ def __repr__(self): Name="john [0.4] | mary [0.4] | peter [0.2]", Article="the [0.5] | a [0.25] | an [0.25]", Preposition="to [0.4] | in [0.3] | at [0.3]", - Conjuction="and [0.5] | or [0.2] | but [0.3]", + Conjunction="and [0.5] | or [0.2] | but [0.3]", Digit="0 [0.35] | 1 [0.35] | 2 [0.3]" )) diff --git a/nlp_apps.ipynb b/nlp_apps.ipynb index d50588cb7..2da3b9283 100644 --- a/nlp_apps.ipynb +++ b/nlp_apps.ipynb @@ -30,7 +30,7 @@ "\n", "First we need to build our dataset. We will take as input text in English and in German and we will extract n-gram character models (in this case, *bigrams* for n=2). For English, we will use *Flatland* by Edwin Abbott and for German *Faust* by Goethe.\n", "\n", - "Let's build our text models for each language, which will hold the probability of each bigram occuring in the text." + "Let's build our text models for each language, which will hold the probability of each bigram occurring in the text." ] }, { diff --git a/notebook.py b/notebook.py index 3fe64de2d..6e1a0fbfc 100644 --- a/notebook.py +++ b/notebook.py @@ -260,7 +260,7 @@ class Canvas: """Inherit from this class to manage the HTML canvas element in jupyter notebooks. To create an object of this class any_name_xyz = Canvas("any_name_xyz") The first argument given must be the name of the object being created. - IPython must be able to refernce the variable name that is being passed.""" + IPython must be able to reference the variable name that is being passed.""" def __init__(self, varname, width=800, height=600, cid=None): self.name = varname @@ -279,10 +279,10 @@ def mouse_move(self, x, y): raise NotImplementedError def execute(self, exec_str): - """Stores the command to be exectued to a list which is used later during update()""" + """Stores the command to be executed to a list which is used later during update()""" if not isinstance(exec_str, str): print("Invalid execution argument:", exec_str) - self.alert("Recieved invalid execution command format") + self.alert("Received invalid execution command format") prefix = "{0}_canvas_object.".format(self.cid) self.exec_list.append(prefix + exec_str + ';') diff --git a/planning.ipynb b/planning.ipynb index 37461ee9b..1054f1ee8 100644 --- a/planning.ipynb +++ b/planning.ipynb @@ -63,7 +63,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "It is interesting to see the way preconditions and effects are represented here. Instead of just being a list of expressions each, they consist of two lists - `precond_pos` and `precond_neg`. This is to work around the fact that PDDL doesn't allow for negations. Thus, for each precondition, we maintain a seperate list of those preconditions that must hold true, and those whose negations must hold true. Similarly, instead of having a single list of expressions that are the result of executing an action, we have two. The first (`effect_add`) contains all the expressions that will evaluate to true if the action is executed, and the the second (`effect_neg`) contains all those expressions that would be false if the action is executed (ie. their negations would be true).\n", + "It is interesting to see the way preconditions and effects are represented here. Instead of just being a list of expressions each, they consist of two lists - `precond_pos` and `precond_neg`. This is to work around the fact that PDDL doesn't allow for negations. Thus, for each precondition, we maintain a separate list of those preconditions that must hold true, and those whose negations must hold true. Similarly, instead of having a single list of expressions that are the result of executing an action, we have two. The first (`effect_add`) contains all the expressions that will evaluate to true if the action is executed, and the the second (`effect_neg`) contains all those expressions that would be false if the action is executed (ie. their negations would be true).\n", "\n", "The constructor parameters, however combine the two precondition lists into a single `precond` parameter, and the effect lists into a single `effect` parameter." ] diff --git a/probability.py b/probability.py index 5c9e28245..a9f65fbb0 100644 --- a/probability.py +++ b/probability.py @@ -651,7 +651,7 @@ def particle_filtering(e, N, HMM): return s # _________________________________________________________________________ -## TODO: Implement continous map for MonteCarlo similar to Fig25.10 from the book +## TODO: Implement continuous map for MonteCarlo similar to Fig25.10 from the book class MCLmap: """Map which provides probability distributions and sensor readings. diff --git a/rl.ipynb b/rl.ipynb index b0920b8ed..019bef3b7 100644 --- a/rl.ipynb +++ b/rl.ipynb @@ -336,7 +336,7 @@ "source": [ "The Agent Program can be obtained by creating the instance of the class by passing the appropriate parameters. Because of the __ call __ method the object that is created behaves like a callable and returns an appropriate action as most Agent Programs do. To instantiate the object we need a mdp similar to the PassiveTDAgent.\n", "\n", - " Let us use the same GridMDP object we used above. **Figure 17.1 (sequential_decision_environment)** is similar to **Figure 21.1** but has some discounting as **gamma = 0.9**. The class also implements an exploration function **f** which returns fixed **Rplus** untill agent has visited state, action **Ne** number of times. This is the same as the one defined on page **842** of the book. The method **actions_in_state** returns actions possible in given state. It is useful when applying max and argmax operations." + " Let us use the same GridMDP object we used above. **Figure 17.1 (sequential_decision_environment)** is similar to **Figure 21.1** but has some discounting as **gamma = 0.9**. The class also implements an exploration function **f** which returns fixed **Rplus** until agent has visited state, action **Ne** number of times. This is the same as the one defined on page **842** of the book. The method **actions_in_state** returns actions possible in given state. It is useful when applying max and argmax operations." ] }, { @@ -381,7 +381,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now let us see the Q Values. The keys are state-action pairs. Where differnt actions correspond according to:\n", + "Now let us see the Q Values. The keys are state-action pairs. Where different actions correspond according to:\n", "\n", "north = (0, 1)\n", "south = (0,-1)\n", diff --git a/rl.py b/rl.py index 868784e9f..3258bfffe 100644 --- a/rl.py +++ b/rl.py @@ -13,7 +13,7 @@ class PassiveADPAgent: on a given MDP and policy. [Figure 21.2]""" class ModelMDP(MDP): - """ Class for implementing modifed Version of input MDP with + """ Class for implementing modified Version of input MDP with an editable transition model P and a custom function T. """ def __init__(self, init, actlist, terminals, gamma, states): super().__init__(init, actlist, terminals, gamma) diff --git a/search-4e.ipynb b/search-4e.ipynb index 73da69119..c2d0dae61 100644 --- a/search-4e.ipynb +++ b/search-4e.ipynb @@ -929,7 +929,7 @@ " \"\"\"Provide an initial state and optional goal states.\n", " A subclass can have additional keyword arguments.\"\"\"\n", " self.initial = initial # The initial state of the problem.\n", - " self.goals = goals # A collection of possibe goal states.\n", + " self.goals = goals # A collection of possible goal states.\n", " self.__dict__.update(**additional_keywords)\n", "\n", " def actions(self, state):\n", @@ -2706,7 +2706,7 @@ " // Register the callback with on_msg.\n", " comm.on_msg(function(msg) {\n", " //console.log('receiving', msg['content']['data'], msg);\n", - " // Pass the mpl event to the overriden (by mpl) onmessage function.\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", " ws.onmessage(msg['content']['data'])\n", " });\n", " return ws;\n", @@ -3559,7 +3559,7 @@ " // Register the callback with on_msg.\n", " comm.on_msg(function(msg) {\n", " //console.log('receiving', msg['content']['data'], msg);\n", - " // Pass the mpl event to the overriden (by mpl) onmessage function.\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", " ws.onmessage(msg['content']['data'])\n", " });\n", " return ws;\n", diff --git a/search.ipynb b/search.ipynb index 52eb39c0e..7bc81040a 100644 --- a/search.ipynb +++ b/search.ipynb @@ -2070,7 +2070,7 @@ "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", + "Before we solve problems using the genetic algorithm, we will explain how to intuitively understand the algorithm using a trivial example.\n", "\n", "#### Generating Phrases\n", "\n", diff --git a/search.py b/search.py index 14388c684..ac834d80c 100644 --- a/search.py +++ b/search.py @@ -907,7 +907,7 @@ def mutate(x, gene_pool, pmut): class Graph: - """A graph connects nodes (verticies) by edges (links). Each edge can also + """A graph connects nodes (vertices) by edges (links). Each edge can also have a length associated with it. The constructor call is something like: g = Graph({'A': {'B': 1, 'C': 2}) this makes a graph with 3 nodes, A, B, and C, with an edge of length 1 from diff --git a/tests/test_utils.py b/tests/test_utils.py index a07bc76ef..dbc1bc01a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -281,7 +281,7 @@ def test_FIFOQueue() : front_head += 1 # check for __len__ method assert len(queue) == front_head - back_head - # chek for __contains__ method + # check for __contains__ method if front_head - back_head > 0 : assert random.choice(test_data[back_head:front_head]) in queue diff --git a/text.ipynb b/text.ipynb index aeebf8ecd..f8c3aea13 100644 --- a/text.ipynb +++ b/text.ipynb @@ -115,7 +115,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We see that the most used word in *Flatland* is 'the', with 2081 occurences, while the most used sequence is 'of the' with 368 occurences. Also, the probability of 'an' is approximately 0.003, while for 'i was' it is close to 0.001. Note that the strings used as keys are all lowercase. For the unigram model, the keys are single strings, while for n-gram models we have n-tuples of strings.\n", + "We see that the most used word in *Flatland* is 'the', with 2081 occurrences, while the most used sequence is 'of the' with 368 occurrences. Also, the probability of 'an' is approximately 0.003, while for 'i was' it is close to 0.001. Note that the strings used as keys are all lowercase. For the unigram model, the keys are single strings, while for n-gram models we have n-tuples of strings.\n", "\n", "Below we take a look at how we can get information from the conditional probabilities of the model, and how we can generate the next word in a sequence." ] @@ -297,7 +297,7 @@ "\n", "We are given a string containing words of a sentence, but all the spaces are gone! It is very hard to read and we would like to separate the words in the string. We can accomplish this by employing the `Viterbi Segmentation` algorithm. It takes as input the string to segment and a text model, and it returns a list of the separate words.\n", "\n", - "The algorithm operates in a dynamic programming approach. It starts from the beginning of the string and iteratively builds the best solution using previous solutions. It accomplishes that by segmentating the string into \"windows\", each window representing a word (real or gibberish). It then calculates the probability of the sequence up that window/word occuring and updates its solution. When it is done, it traces back from the final word and finds the complete sequence of words." + "The algorithm operates in a dynamic programming approach. It starts from the beginning of the string and iteratively builds the best solution using previous solutions. It accomplishes that by segmentating the string into \"windows\", each window representing a word (real or gibberish). It then calculates the probability of the sequence up that window/word occurring and updates its solution. When it is done, it traces back from the final word and finds the complete sequence of words." ] }, { @@ -386,7 +386,7 @@ "\n", "How does an IR system determine which documents are relevant though? We can sign a document as relevant if all the words in the query appear in it, and sign it as irrelevant otherwise. We can even extend the query language to support boolean operations (for example, \"paint AND brush\") and then sign as relevant the outcome of the query for the document. This technique though does not give a level of relevancy. All the documents are either relevant or irrelevant, but in reality some documents are more relevant than others.\n", "\n", - "So, instead of a boolean relevancy system, we use a *scoring function*. There are many scoring functions around for many different situations. One of the most used takes into account the frequency of the words appearing in a document, the frequency of a word appearing across documents (for example, the word \"a\" appears a lot, so it is not very important) and the length of a document (since large documents will have higher occurences for the query terms, but a short document with a lot of occurences seems very relevant). We combine these properties in a formula and we get a numeric score for each document, so we can then quantify relevancy and pick the best documents.\n", + "So, instead of a boolean relevancy system, we use a *scoring function*. There are many scoring functions around for many different situations. One of the most used takes into account the frequency of the words appearing in a document, the frequency of a word appearing across documents (for example, the word \"a\" appears a lot, so it is not very important) and the length of a document (since large documents will have higher occurrences for the query terms, but a short document with a lot of occurrences seems very relevant). We combine these properties in a formula and we get a numeric score for each document, so we can then quantify relevancy and pick the best documents.\n", "\n", "These scoring functions are not perfect though and there is room for improvement. For instance, for the above scoring function we assume each word is independent. That is not the case though, since words can share meaning. For example, the words \"painter\" and \"painters\" are closely related. If in a query we have the word \"painter\" and in a document the word \"painters\" appears a lot, this might be an indication that the document is relevant but we are missing out since we are only looking for \"painter\". There are a lot of ways to combat this. One of them is to reduce the query and document words into their stems. For example, both \"painter\" and \"painters\" have \"paint\" as their stem form. This can improve slightly the performance of algorithms.\n", "\n", @@ -527,7 +527,7 @@ "source": [ "## INFORMATION EXTRACTION\n", "\n", - "**Information Extraction (IE)** is a method for finding occurences of object classes and relationships in text. Unlike IR systems, an IE system includes (limited) notions of syntax and semantics. While it is difficult to extract object information in a general setting, for more specific domains the system is very useful. One model of an IE system makes use of templates that match with strings in a text.\n", + "**Information Extraction (IE)** is a method for finding occurrences of object classes and relationships in text. Unlike IR systems, an IE system includes (limited) notions of syntax and semantics. While it is difficult to extract object information in a general setting, for more specific domains the system is very useful. One model of an IE system makes use of templates that match with strings in a text.\n", "\n", "A typical example of such a model is reading prices from web pages. Prices usually appear after a dollar and consist of numbers, maybe followed by two decimal points. Before the price, usually there will appear a string like \"price:\". Let's build a sample template.\n", "\n", @@ -535,7 +535,7 @@ "\n", "`[$][0-9]+([.][0-9][0-9])?`\n", "\n", - "Where `+` means 1 or more occurences and `?` means at most 1 occurence. Usually a template consists of a prefix, a target and a postfix regex. In this template, the prefix regex can be \"price:\", the target regex can be the above regex and the postfix regex can be empty.\n", + "Where `+` means 1 or more occurrences and `?` means at most 1 occurrence. Usually a template consists of a prefix, a target and a postfix regex. In this template, the prefix regex can be \"price:\", the target regex can be the above regex and the postfix regex can be empty.\n", "\n", "A template can match with multiple strings. If this is the case, we need a way to resolve the multiple matches. Instead of having just one template, we can use multiple templates (ordered by priority) and pick the match from the highest-priority template. We can also use other ways to pick. For the dollar example, we can pick the match closer to the numerical half of the highest match. For the text \"Price $90, special offer $70, shipping $5\" we would pick \"$70\" since it is closer to the half of the highest match (\"$90\")." ] diff --git a/utils.py b/utils.py index e5dbfd5cd..709c5621f 100644 --- a/utils.py +++ b/utils.py @@ -22,7 +22,7 @@ def sequence(iterable): def removeall(item, seq): - """Return a copy of seq (or string) with all occurences of item removed.""" + """Return a copy of seq (or string) with all occurrences of item removed.""" if isinstance(seq, str): return seq.replace(item, '') else: @@ -135,7 +135,7 @@ def element_wise_product(X, Y): def matrix_multiplication(X_M, *Y_M): - """Return a matrix as a matrix-multiplication of X_M and arbitary number of matrices *Y_M""" + """Return a matrix as a matrix-multiplication of X_M and arbitrary number of matrices *Y_M""" def _mat_mult(X_M, Y_M): """Return a matrix as a matrix-multiplication of two matrices X_M and Y_M @@ -418,7 +418,7 @@ def open_data(name, mode='r'): def failure_test(algorithm, tests): """Grades the given algorithm based on how many tests it passes. - Most algorithms have arbitary output on correct execution, which is difficult + Most algorithms have arbitrary output on correct execution, which is difficult to check for correctness. On the other hand, a lot of algorithms output something particular on fail (for example, False, or None). tests is a list with each element in the form: (values, failure_output).""" diff --git a/vacuum_world.ipynb b/vacuum_world.ipynb index 59950566b..2c18e4185 100644 --- a/vacuum_world.ipynb +++ b/vacuum_world.ipynb @@ -117,7 +117,7 @@ "# Initialize the two-state environment\n", "trivial_vacuum_env = TrivialVacuumEnvironment()\n", "\n", - "# Check the intial state of the environment\n", + "# Check the initial state of the environment\n", "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))" ] }, @@ -308,7 +308,7 @@ "source": [ "## SIMPLE REFLEX AGENT PROGRAM\n", "\n", - "A simple reflex agent program selects actions on the basis of the *current* percept, ignoring the rest of the percept history. These agents work on a **condition-action rule** (also called **situation-action rule**, **production** or **if-then rule**), which tells the agent the action to trigger when a particular situtation is encountered. \n", + "A simple reflex agent program selects actions on the basis of the *current* percept, ignoring the rest of the percept history. These agents work on a **condition-action rule** (also called **situation-action rule**, **production** or **if-then rule**), which tells the agent the action to trigger when a particular situation is encountered. \n", "\n", "The schematic diagram shown in **Figure 2.9** of the book will make this more clear:\n", "\n", @@ -418,7 +418,7 @@ "source": [ "## MODEL-BASED REFLEX AGENT PROGRAM\n", "\n", - "A model-based reflex agent maintains some sort of **internal state** that depends on the percept history and thereby reflects at least some of the unobserved aspects of the current state. In additon to this, it also requires a **model** of the world, that is, knowledge about \"how the world works\".\n", + "A model-based reflex agent maintains some sort of **internal state** that depends on the percept history and thereby reflects at least some of the unobserved aspects of the current state. In addition to this, it also requires a **model** of the world, that is, knowledge about \"how the world works\".\n", "\n", "The schematic diagram shown in **Figure 2.11** of the book will make this more clear:\n", "" @@ -445,7 +445,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We need a another function UPDATE-STATE which will be reponsible for creating a new state description." + "We need a another function UPDATE-STATE which will be responsible for creating a new state description." ] }, { From ae4f1cfc62431be53a6aa7139c9f4c33b7987b77 Mon Sep 17 00:00:00 2001 From: Ayush Jain Date: Fri, 23 Feb 2018 08:52:18 +0530 Subject: [PATCH 030/269] Added tests for information_content (#753) * Added tests for information_content Added some tests for information_content function from learning.py * Added test for information_content --- tests/test_learning.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_learning.py b/tests/test_learning.py index cb43fe1b6..ff7b9b3e2 100644 --- a/tests/test_learning.py +++ b/tests/test_learning.py @@ -165,6 +165,15 @@ def test_decision_tree_learner(): assert dTL([7.5, 4, 6, 2]) == "virginica" +def test_information_content(): + assert information_content([]) == 0 + assert information_content([4]) == 0 + assert information_content([5, 4, 0, 2, 5, 0]) > 1.9 + assert information_content([5, 4, 0, 2, 5, 0]) < 2 + assert information_content([1.5, 2.5]) > 0.9 + assert information_content([1.5, 2.5]) < 1.0 + + def test_random_forest(): iris = DataSet(name="iris") rF = RandomForest(iris) From 1e96cd1cee11b20e3dc5c6872113f25ebe08f321 Mon Sep 17 00:00:00 2001 From: Sheikh Adilina <31650090+SkAdilina@users.noreply.github.com> Date: Fri, 23 Feb 2018 09:25:23 +0600 Subject: [PATCH 031/269] Added Node in search.ipynb (#761) --- search.ipynb | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/search.ipynb b/search.ipynb index 7bc81040a..238fd8228 100644 --- a/search.ipynb +++ b/search.ipynb @@ -15,6 +15,7 @@ "cell_type": "code", "execution_count": 1, "metadata": { + "collapsed": true, "scrolled": true }, "outputs": [], @@ -82,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": { "collapsed": true }, @@ -115,6 +116,51 @@ "* `value(self, state)` : This acts as a bit of extra information in problems where we try to optimise a value when we cannot do a goal test." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## NODE\n", + "\n", + "Let's see how we define a Node. Run the next cell to see how abstract class `Node` is defined in the search module." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%psource Node" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `Node` class has nine methods.\n", + "\n", + "* `__init__(self, state, parent, action, path_cost)` : This method creates a node. `parent` represents the the node that this is a successor of and `action` is the action required to get from the parent node to this node. `path_cost` is the cost to reach current node from parent node.\n", + "\n", + "* `__repr__(self)` : This returns the state of this node.\n", + "\n", + "* `__lt__(self, node)` : Given a `node`, this method returns `True` if the state of current node is less than the state of the `node`. Otherwise it returns `False`.\n", + "\n", + "* `expand(self, problem)` : This methods lists all the neighbouring(reachable in one step) nodes of current node. \n", + "\n", + "* `child_node(self, problem, action)` : Given an `action`, this methods returns the immediate neighbour that can be reached with that `action`.\n", + "\n", + "* `solution(self)` : This returns the sequence of actions required to reach this node from the root node. \n", + "\n", + "* `path(self)` : This returns a list of all the nodes that lies in the path from the root to this node.\n", + "\n", + "* `__eq__(self, other)` : This method returns `True` if the state of current node is equal to the other node. Else it returns `False`.\n", + "\n", + "* `__hash__(self)` : This returns the hash of the state of current node." + ] + }, { "cell_type": "markdown", "metadata": {}, From 35c9673fcacad2df28f5208d492285e8d106b4b6 Mon Sep 17 00:00:00 2001 From: Anthony Marakis Date: Fri, 23 Feb 2018 19:39:03 +0200 Subject: [PATCH 032/269] fixing build --- tests/test_learning.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_learning.py b/tests/test_learning.py index ff7b9b3e2..6afadc282 100644 --- a/tests/test_learning.py +++ b/tests/test_learning.py @@ -241,5 +241,5 @@ def test_adaboost(): ([6, 2, 3.5, 1], 1), ([7.5, 4, 6, 2], 2), ([7, 3, 6, 2.5], 2)] - assert grade_learner(adaboost, tests) > 5/6 - assert err_ratio(adaboost, iris) < 0.1 + assert grade_learner(adaboost, tests) > 4/6 + assert err_ratio(adaboost, iris) < 0.25 From c67fb654588e986f0ad914ddb4581d3eef075fa2 Mon Sep 17 00:00:00 2001 From: Anthony Marakis Date: Sun, 25 Feb 2018 01:34:45 +0200 Subject: [PATCH 033/269] Update README.md (#767) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b811453b..847d43657 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 2.12 | Model-Based-Reflex-Agent | `ReflexAgentWithState` | [`agents.py`][agents] | | Included | | 3 | Problem | `Problem` | [`search.py`][search] | Done | | | 3 | Node | `Node` | [`search.py`][search] | Done | | -| 3 | Queue | `Queue` | [`utils.py`][utils] | Done | | +| 3 | Queue | `Queue` | [`utils.py`][utils] | Done | No Need | | 3.1 | Simple-Problem-Solving-Agent | `SimpleProblemSolvingAgent` | [`search.py`][search] | | Included | | 3.2 | Romania | `romania` | [`search.py`][search] | Done | Included | | 3.7 | Tree-Search | `tree_search` | [`search.py`][search] | Done | | From 1b65fec1eb193a2fda21654ad16301390754ccfc Mon Sep 17 00:00:00 2001 From: Sheikh Adilina <31650090+SkAdilina@users.noreply.github.com> Date: Sun, 25 Feb 2018 05:35:28 +0600 Subject: [PATCH 034/269] Minor update in search.ipynb (#763) --- search.ipynb | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/search.ipynb b/search.ipynb index 238fd8228..cf3b4306e 100644 --- a/search.ipynb +++ b/search.ipynb @@ -36,6 +36,7 @@ "\n", "* Overview\n", "* Problem\n", + "* Node\n", "* Search Algorithms Visualization\n", "* Breadth-First Tree Search\n", "* Breadth-First Search\n", @@ -189,7 +190,9 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "romania_map = UndirectedGraph(dict(\n", @@ -234,7 +237,9 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)" @@ -284,7 +289,9 @@ { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "%matplotlib inline\n", @@ -308,7 +315,9 @@ { "cell_type": "code", "execution_count": 6, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# initialise a graph\n", @@ -352,7 +361,9 @@ { "cell_type": "code", "execution_count": 7, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# initialise a graph\n", @@ -1358,7 +1369,9 @@ { "cell_type": "code", "execution_count": 11, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Heuristics for 8 Puzzle Problem\n", From 4f6c7167872d833714625cf3d25cc1f6f7cf15fe Mon Sep 17 00:00:00 2001 From: Anthony Marakis Date: Sun, 25 Feb 2018 01:40:39 +0200 Subject: [PATCH 035/269] Update nlp_apps.ipynb (#764) --- nlp_apps.ipynb | 193 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 181 insertions(+), 12 deletions(-) diff --git a/nlp_apps.ipynb b/nlp_apps.ipynb index 2da3b9283..94a91bb36 100644 --- a/nlp_apps.ipynb +++ b/nlp_apps.ipynb @@ -15,7 +15,8 @@ "source": [ "## CONTENTS\n", "\n", - "* Language Recognition" + "* Language Recognition\n", + "* Author Recognition" ] }, { @@ -30,15 +31,13 @@ "\n", "First we need to build our dataset. We will take as input text in English and in German and we will extract n-gram character models (in this case, *bigrams* for n=2). For English, we will use *Flatland* by Edwin Abbott and for German *Faust* by Goethe.\n", "\n", - "Let's build our text models for each language, which will hold the probability of each bigram occurring in the text." + "Let's build our text models for each language, which will hold the probability of each bigram occuring in the text." ] }, { "cell_type": "code", "execution_count": 1, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from utils import open_data\n", @@ -67,9 +66,7 @@ { "cell_type": "code", "execution_count": 2, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from learning import NaiveBayesLearner\n", @@ -91,9 +88,7 @@ { "cell_type": "code", "execution_count": 3, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "def recognize(sentence, nBS, n):\n", @@ -106,6 +101,8 @@ " for b, p in P_sentence.dictionary.items():\n", " ngrams += [b]*p\n", " \n", + " print(ngrams)\n", + " \n", " return nBS(ngrams)" ] }, @@ -121,6 +118,13 @@ "execution_count": 4, "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[(' ', 'i'), ('i', 'c'), ('c', 'h'), (' ', 'b'), ('b', 'i'), ('i', 'n'), ('i', 'n'), (' ', 'e'), ('e', 'i'), (' ', 'p'), ('p', 'l'), ('l', 'a'), ('a', 't'), ('t', 'z')]\n" + ] + }, { "data": { "text/plain": [ @@ -141,6 +145,13 @@ "execution_count": 5, "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[(' ', 't'), ('t', 'u'), ('u', 'r'), ('r', 't'), ('t', 'l'), ('l', 'e'), ('e', 's'), (' ', 'f'), ('f', 'l'), ('l', 'y'), (' ', 'h'), ('h', 'i'), ('i', 'g'), ('g', 'h')]\n" + ] + }, { "data": { "text/plain": [ @@ -161,6 +172,13 @@ "execution_count": 6, "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[(' ', 'd'), ('d', 'e'), ('e', 'r'), ('e', 'r'), (' ', 'p'), ('p', 'e'), ('e', 'l'), ('l', 'i'), ('i', 'k'), ('k', 'a'), ('a', 'n'), (' ', 'i'), ('i', 's'), ('s', 't'), (' ', 'h'), ('h', 'i'), ('i', 'e')]\n" + ] + }, { "data": { "text/plain": [ @@ -181,6 +199,13 @@ "execution_count": 7, "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[(' ', 'a'), ('a', 'n'), ('n', 'd'), (' ', 't'), (' ', 't'), ('t', 'h'), ('t', 'h'), ('h', 'u'), ('u', 's'), ('h', 'e'), (' ', 'w'), ('w', 'i'), ('i', 'z'), ('z', 'a'), ('a', 'r'), ('r', 'd'), (' ', 's'), ('s', 'p'), ('p', 'o'), ('o', 'k'), ('k', 'e')]\n" + ] + }, { "data": { "text/plain": [ @@ -202,6 +227,150 @@ "source": [ "You can add more languages if you want, the algorithm works for as many as you like! Also, you can play around with *n*. Here we used 2, but other numbers work too (even though 2 suffices). The algorithm is not perfect, but it has high accuracy even for small samples like the ones we used. That is because English and German are very different languages. The closer together languages are (for example, Norwegian and Swedish share a lot of common ground) the lower the accuracy of the classifier." ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## AUTHOR RECOGNITION\n", + "\n", + "Another similar application to language recognition is recognizing who is more likely to have written a sentence, given text written by them. Here we will try and predict text from Edwin Abbott and Jane Austen. They wrote *Flatland* and *Pride and Prejudice* respectively.\n", + "\n", + "We are optimistic we can determine who wrote what based on the fact that Abbott wrote his novella on much later date than Austen, which means there will be linguistic differences between the two works. Indeed, *Flatland* uses more modern and direct language while *Pride and Prejudice* is written in a more archaic tone containing more sophisticated wording.\n", + "\n", + "Similarly with Language Recognition, we will first import the two datasets. This time though we are not looking for connections between characters, since that wouldn't give that great results. Why? Because both authors use English and English follows a set of patterns, as we show earlier. Trying to determine authorship based on this patterns would not be very efficient.\n", + "\n", + "Instead, we will abstract our querying to a higher level. We will use words instead of characters. That way we can more accurately pick at the differences between their writing style and thus have a better chance at guessing the correct author.\n", + "\n", + "Let's go right ahead and import our data:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "from utils import open_data\n", + "from text import *\n", + "\n", + "flatland = open_data(\"EN-text/flatland.txt\").read()\n", + "wordseq = words(flatland)\n", + "\n", + "P_Abbott = UnigramWordModel(wordseq, 5)\n", + "\n", + "pride = open_data(\"EN-text/pride.txt\").read()\n", + "wordseq = words(pride)\n", + "\n", + "P_Austen = UnigramWordModel(wordseq, 5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This time we set the `default` parameter of the model to 5, instead of 0. If we leave it at 0, then when we get a sentence containing a word we have not seen from that particular author, the chance of that sentence coming from that author is exactly 0 (since to get the probability, we multiply all the separate probabilities; if one is 0 then the result is also 0). To avoid that, we tell the model to add 5 to the count of all the words that appear.\n", + "\n", + "Next we will build the Naive Bayes Classifier:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "from learning import NaiveBayesLearner\n", + "\n", + "dist = {('Abbott', 1): P_Abbott, ('Austen', 1): P_Austen}\n", + "\n", + "nBS = NaiveBayesLearner(dist, simple=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we have build our classifier, we will start classifying. First, we need to convert the given sentence to the format the classifier needs. That is, a list of words." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "def recognize(sentence, nBS):\n", + " sentence = sentence.lower()\n", + " sentence_words = words(sentence)\n", + " \n", + " return nBS(sentence_words)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First we will input a sentence that is something Abbott would write. Note the use of square and the simpler language." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Abbott'" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "recognize(\"the square is mad\", nBS)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The classifier correctly guessed Abbott.\n", + "\n", + "Next we will input a more sophisticated sentence, similar to the style of Austen." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Austen'" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "recognize(\"a most peculiar acquaintance\", nBS)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The classifier guessed correctly again.\n", + "\n", + "You can try more sentences on your own. Unfortunately though, since the datasets are pretty small, chances are the guesses will not always be correct." + ] } ], "metadata": { @@ -220,7 +389,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.3" + "version": "3.6.3" } }, "nbformat": 4, From 7d3c37bab03b883b700d8b82257ab7d98606e6fa Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Sun, 25 Feb 2018 20:50:26 +0000 Subject: [PATCH 036/269] Updated README.md (#771) * Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 847d43657..34e03ae45 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 2.10 | Simple-Reflex-Agent | `SimpleReflexAgent` | [`agents.py`][agents] | | Included | | 2.12 | Model-Based-Reflex-Agent | `ReflexAgentWithState` | [`agents.py`][agents] | | Included | | 3 | Problem | `Problem` | [`search.py`][search] | Done | | -| 3 | Node | `Node` | [`search.py`][search] | Done | | +| 3 | Node | `Node` | [`search.py`][search] | Done | Included | | 3 | Queue | `Queue` | [`utils.py`][utils] | Done | No Need | | 3.1 | Simple-Problem-Solving-Agent | `SimpleProblemSolvingAgent` | [`search.py`][search] | | Included | | 3.2 | Romania | `romania` | [`search.py`][search] | Done | Included | @@ -105,7 +105,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 15.17 | Particle-Filtering | `particle_filtering` | [`probability.py`][probability] | Done | | | 16.9 | Information-Gathering-Agent | | | | | | 17.4 | Value-Iteration | `value_iteration` | [`mdp.py`][mdp] | Done | Included | -| 17.7 | Policy-Iteration | `policy_iteration` | [`mdp.py`][mdp] | Done | | +| 17.7 | Policy-Iteration | `policy_iteration` | [`mdp.py`][mdp] | Done | Included | | 17.9 | POMDP-Value-Iteration | | | | | | 18.5 | Decision-Tree-Learning | `DecisionTreeLearner` | [`learning.py`][learning] | Done | Included | | 18.8 | Cross-Validation | `cross_validation` | [`learning.py`][learning] | | | From ac04a200fd64838279927fcb929d8a6fe740ac35 Mon Sep 17 00:00:00 2001 From: Anthony Marakis Date: Mon, 26 Feb 2018 01:44:42 +0200 Subject: [PATCH 037/269] Update SUBMODULE.md --- SUBMODULE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SUBMODULE.md b/SUBMODULE.md index b9048ea4c..2c080bb91 100644 --- a/SUBMODULE.md +++ b/SUBMODULE.md @@ -1,4 +1,4 @@ -This is a guide on how to update the `aima-data` submodule. This needs to be done every time something changes in the [aima-data](https://github.com/aimacode/aima-data) repository. All the below commands should be executed from the local directory of the `aima-python` repository, using `git`. +This is a guide on how to update the `aima-data` submodule to the latest version. This needs to be done every time something changes in the [aima-data](https://github.com/aimacode/aima-data) repository. All the below commands should be executed from the local directory of the `aima-python` repository, using `git`. ``` git submodule deinit aima-data From 6cac3655646d60be807499903a7a12b0af529938 Mon Sep 17 00:00:00 2001 From: Pranjal Aswani Date: Mon, 26 Feb 2018 16:38:09 +0530 Subject: [PATCH 038/269] added Done tag for adaboost (#774) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34e03ae45..5a3ff1ba3 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 18.8 | Cross-Validation | `cross_validation` | [`learning.py`][learning] | | | | 18.11 | Decision-List-Learning | `DecisionListLearner` | [`learning.py`][learning]\* | | | | 18.24 | Back-Prop-Learning | `BackPropagationLearner` | [`learning.py`][learning] | Done | Included | -| 18.34 | AdaBoost | `AdaBoost` | [`learning.py`][learning] | | | +| 18.34 | AdaBoost | `AdaBoost` | [`learning.py`][learning] | Done | Included | | 19.2 | Current-Best-Learning | `current_best_learning` | [`knowledge.py`](knowledge.py) | Done | Included | | 19.3 | Version-Space-Learning | `version_space_learning` | [`knowledge.py`](knowledge.py) | Done | Included | | 19.8 | Minimal-Consistent-Det | `minimal_consistent_det` | [`knowledge.py`](knowledge.py) | Done | | From f5d4793e464db8a989188fcef422174e527c44cb Mon Sep 17 00:00:00 2001 From: Saloni Gupta Date: Mon, 26 Feb 2018 21:36:47 +0530 Subject: [PATCH 039/269] csp.ipynb: removed some typos (#769) --- csp.ipynb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/csp.ipynb b/csp.ipynb index f6414f701..aa8b37c7d 100644 --- a/csp.ipynb +++ b/csp.ipynb @@ -6,7 +6,7 @@ "source": [ "# CONSTRAINT SATISFACTION PROBLEMS\n", "\n", - "This IPy notebook acts as supporting material for topics covered in **Chapter 6 Constraint Satisfaction Problems** of the book* Artificial Intelligence: A Modern Approach*. We make use of the implementations in **csp.py** module. Even though this notebook includes a brief summary of the main topics familiarity with the material present in the book is expected. We will look at some visualizations and solve some of the CSP problems described in the book. Let us import everything from the csp module to get started." + "This IPy notebook acts as supporting material for topics covered in **Chapter 6 Constraint Satisfaction Problems** of the book* Artificial Intelligence: A Modern Approach*. We make use of the implementations in **csp.py** module. Even though this notebook includes a brief summary of the main topics, familiarity with the material present in the book is expected. We will look at some visualizations and solve some of the CSP problems described in the book. Let us import everything from the csp module to get started." ] }, { @@ -20,7 +20,7 @@ "from csp import *\n", "from notebook import psource, pseudocode\n", "\n", - "# Needed to hide warnings in the matplotlib sections\n", + "# Hide warnings in the matplotlib sections\n", "import warnings\n", "warnings.filterwarnings(\"ignore\")" ] @@ -115,7 +115,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The CSP class takes neighbors in the form of a Dict. The module specifies a simple helper function named **parse_neighbors** which allows to take input in the form of strings and return a Dict of the form compatible with the **CSP Class**." + "The CSP class takes neighbors in the form of a Dict. The module specifies a simple helper function named **parse_neighbors** which allows us to take input in the form of strings and return a Dict of a form compatible with the **CSP Class**." ] }, { @@ -133,7 +133,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The **MapColoringCSP** function creates and returns a CSP with the above constraint function and states. The variables our the keys of the neighbors dict and the constraint is the one specified by the **different_values_constratint** function. **australia**, **usa** and **france** are three CSPs that have been created using **MapColoringCSP**. **australia** corresponds to ** Figure 6.1 ** in the book." + "The **MapColoringCSP** function creates and returns a CSP with the above constraint function and states. The variables are the keys of the neighbors dict and the constraint is the one specified by the **different_values_constratint** function. **australia**, **usa** and **france** are three CSPs that have been created using **MapColoringCSP**. **australia** corresponds to ** Figure 6.1 ** in the book." ] }, { @@ -173,7 +173,7 @@ "source": [ "## N-QUEENS\n", "\n", - "The N-queens puzzle is the problem of placing N chess queens on a N×N chessboard so that no two queens threaten each other. Here N is a natural number. Like the graph coloring, problem NQueens is also implemented in the csp module. The **NQueensCSP** class inherits from the **CSP** class. It makes some modifications in the methods to suit the particular problem. The queens are assumed to be placed one per column, from left to right. That means position (x, y) represents (var, val) in the CSP. The constraint that needs to be passed on the CSP is defined in the **queen_constraint** function. The constraint is satisfied (true) if A, B are really the same variable, or if they are not in the same row, down diagonal, or up diagonal. " + "The N-queens puzzle is the problem of placing N chess queens on an N×N chessboard so that no two queens threaten each other. Here N is a natural number. Like the graph coloring problem, NQueens is also implemented in the csp module. The **NQueensCSP** class inherits from the **CSP** class. It makes some modifications in the methods to suit the particular problem. The queens are assumed to be placed one per column, from left to right. That means position (x, y) represents (var, val) in the CSP. The constraint that needs to be passed on the CSP is defined in the **queen_constraint** function. The constraint is satisfied (true) if A, B are really the same variable, or if they are not in the same row, down diagonal, or up diagonal. " ] }, { @@ -189,7 +189,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The **NQueensCSP** method implements methods that support solving the problem via **min_conflicts** which is one of the techniques for solving CSPs. Because **min_conflicts** hill climbs the number of conflicts to solve the CSP **assign** and **unassign** are modified to record conflicts. More details about the structures **rows**, **downs**, **ups** which help in recording conflicts are explained in the docstring." + "The **NQueensCSP** method implements methods that support solving the problem via **min_conflicts** which is one of the techniques for solving CSPs. Because **min_conflicts** hill climbs the number of conflicts to solve, the CSP **assign** and **unassign** are modified to record conflicts. More details about the structures **rows**, **downs**, **ups** which help in recording conflicts are explained in the docstring." ] }, { From 84586ceb8ea176b8f7d2efc5c913e7acb6004901 Mon Sep 17 00:00:00 2001 From: Anthony Marakis Date: Mon, 26 Feb 2018 19:27:07 +0200 Subject: [PATCH 040/269] Update README.md --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5a3ff1ba3..7355d2561 100644 --- a/README.md +++ b/README.md @@ -30,19 +30,19 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | **Figure** | **Name (in 3rd edition)** | **Name (in repository)** | **File** | **Tests** | **Notebook** |:-------|:----------------------------------|:------------------------------|:--------------------------------|:-----|:---------| -| 2 | Random-Vacuum-Agent | `RandomVacuumAgent` | [`agents.py`][agents] | Done | Included | -| 2 | Model-Based-Vacuum-Agent | `ModelBasedVacuumAgent` | [`agents.py`][agents] | Done | Included | +| 2 | Random-Vacuum-Agent | `RandomVacuumAgent` | [`agents.py`][agents] | Done | Included | +| 2 | Model-Based-Vacuum-Agent | `ModelBasedVacuumAgent` | [`agents.py`][agents] | Done | Included | | 2.1 | Environment | `Environment` | [`agents.py`][agents] | Done | Included | | 2.1 | Agent | `Agent` | [`agents.py`][agents] | Done | Included | -| 2.3 | Table-Driven-Vacuum-Agent | `TableDrivenVacuumAgent` | [`agents.py`][agents] | Done | Included | -| 2.7 | Table-Driven-Agent | `TableDrivenAgent` | [`agents.py`][agents] | | Included | -| 2.8 | Reflex-Vacuum-Agent | `ReflexVacuumAgent` | [`agents.py`][agents] | Done | Included | -| 2.10 | Simple-Reflex-Agent | `SimpleReflexAgent` | [`agents.py`][agents] | | Included | -| 2.12 | Model-Based-Reflex-Agent | `ReflexAgentWithState` | [`agents.py`][agents] | | Included | -| 3 | Problem | `Problem` | [`search.py`][search] | Done | | +| 2.3 | Table-Driven-Vacuum-Agent | `TableDrivenVacuumAgent` | [`agents.py`][agents] | Done | Included | +| 2.7 | Table-Driven-Agent | `TableDrivenAgent` | [`agents.py`][agents] | | Included | +| 2.8 | Reflex-Vacuum-Agent | `ReflexVacuumAgent` | [`agents.py`][agents] | Done | Included | +| 2.10 | Simple-Reflex-Agent | `SimpleReflexAgent` | [`agents.py`][agents] | | Included | +| 2.12 | Model-Based-Reflex-Agent | `ReflexAgentWithState` | [`agents.py`][agents] | | Included | +| 3 | Problem | `Problem` | [`search.py`][search] | Done | Included | | 3 | Node | `Node` | [`search.py`][search] | Done | Included | -| 3 | Queue | `Queue` | [`utils.py`][utils] | Done | No Need | -| 3.1 | Simple-Problem-Solving-Agent | `SimpleProblemSolvingAgent` | [`search.py`][search] | | Included | +| 3 | Queue | `Queue` | [`utils.py`][utils] | Done | No Need | +| 3.1 | Simple-Problem-Solving-Agent | `SimpleProblemSolvingAgent` | [`search.py`][search] | | Included | | 3.2 | Romania | `romania` | [`search.py`][search] | Done | Included | | 3.7 | Tree-Search | `tree_search` | [`search.py`][search] | Done | | | 3.7 | Graph-Search | `graph_search` | [`search.py`][search] | Done | | @@ -50,7 +50,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 3.14 | Uniform-Cost-Search | `uniform_cost_search` | [`search.py`][search] | Done | Included | | 3.17 | Depth-Limited-Search | `depth_limited_search` | [`search.py`][search] | Done | | | 3.18 | Iterative-Deepening-Search | `iterative_deepening_search` | [`search.py`][search] | Done | | -| 3.22 | Best-First-Search | `best_first_graph_search` | [`search.py`][search] | Done | Included | +| 3.22 | Best-First-Search | `best_first_graph_search` | [`search.py`][search] | Done | Included | | 3.24 | A\*-Search | `astar_search` | [`search.py`][search] | Done | Included | | 3.26 | Recursive-Best-First-Search | `recursive_best_first_search` | [`search.py`][search] | Done | | | 4.2 | Hill-Climbing | `hill_climbing` | [`search.py`][search] | Done | | From c7fff61d1d2ba69947760f74eed89e77b730d08a Mon Sep 17 00:00:00 2001 From: Nouman Ahmed <35970677+Noumanmufc1@users.noreply.github.com> Date: Wed, 28 Feb 2018 00:18:07 +0500 Subject: [PATCH 041/269] Add test for table_driven_agent_program and Random_agent_program (#770) * Add test for table driven agent * Some style fixes * Added done to tabledrivenagent test in readme * Added randomAgentProgram test to test_agents.py * Added Import randomAgentProgram * Style fixes * Added the done tag tp tabledrivenagent test --- README.md | 4 ++-- tests/test_agents.py | 45 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7355d2561..21a63448f 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 2.1 | Environment | `Environment` | [`agents.py`][agents] | Done | Included | | 2.1 | Agent | `Agent` | [`agents.py`][agents] | Done | Included | | 2.3 | Table-Driven-Vacuum-Agent | `TableDrivenVacuumAgent` | [`agents.py`][agents] | Done | Included | -| 2.7 | Table-Driven-Agent | `TableDrivenAgent` | [`agents.py`][agents] | | Included | +| 2.7 | Table-Driven-Agent | `TableDrivenAgent` | [`agents.py`][agents] | Done | Included | | 2.8 | Reflex-Vacuum-Agent | `ReflexVacuumAgent` | [`agents.py`][agents] | Done | Included | | 2.10 | Simple-Reflex-Agent | `SimpleReflexAgent` | [`agents.py`][agents] | | Included | | 2.12 | Model-Based-Reflex-Agent | `ReflexAgentWithState` | [`agents.py`][agents] | | Included | @@ -160,4 +160,4 @@ Many thanks for contributions over the years. I got bug reports, corrected code, [rl]:../master/rl.py [search]:../master/search.py [utils]:../master/utils.py -[text]:../master/text.py +[text]:../master/text.py \ No newline at end of file diff --git a/tests/test_agents.py b/tests/test_agents.py index eedaf0d76..73b149f99 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -2,7 +2,7 @@ from agents import Direction from agents import Agent from agents import ReflexVacuumAgent, ModelBasedVacuumAgent, TrivialVacuumEnvironment, compare_agents,\ - RandomVacuumAgent, TableDrivenVacuumAgent + RandomVacuumAgent, TableDrivenVacuumAgent, TableDrivenAgentProgram, RandomAgentProgram random.seed("aima-python") @@ -54,6 +54,21 @@ def test_add(): assert l1.direction == Direction.U assert l2.direction == Direction.D +def test_RandomAgentProgram() : + #create a list of all the actions a vacuum cleaner can perform + list = ['Right', 'Left', 'Suck', 'NoOp'] + # create a program and then an object of the RandomAgentProgram + program = RandomAgentProgram(list) + + agent = Agent(program) + # create an object of TrivialVacuumEnvironment + environment = TrivialVacuumEnvironment() + # add agent to the environment + environment.add_thing(agent) + # run the environment + environment.run() + # check final status of the environment + assert environment.status == {(1, 0): 'Clean' , (0, 0): 'Clean'} def test_RandomVacuumAgent() : # create an object of the RandomVacuumAgent @@ -68,6 +83,34 @@ def test_RandomVacuumAgent() : assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} +def test_TableDrivenAgent() : + #create a table that would consist of all the possible states of the agent + loc_A, loc_B = (0, 0), (1, 0) + + table = {((loc_A, 'Clean'),): 'Right', + ((loc_A, 'Dirty'),): 'Suck', + ((loc_B, 'Clean'),): 'Left', + ((loc_B, 'Dirty'),): 'Suck', + ((loc_A, 'Dirty'), (loc_A, 'Clean')): 'Right', + ((loc_A, 'Clean'), (loc_B, 'Dirty')): 'Suck', + ((loc_B, 'Clean'), (loc_A, 'Dirty')): 'Suck', + ((loc_B, 'Dirty'), (loc_B, 'Clean')): 'Left', + ((loc_A, 'Dirty'), (loc_A, 'Clean'), (loc_B, 'Dirty')): 'Suck', + ((loc_B, 'Dirty'), (loc_B, 'Clean'), (loc_A, 'Dirty')): 'Suck' + } + # create an program and then an object of the TableDrivenAgent + program = TableDrivenAgentProgram(table) + agent = Agent(program) + # create an object of the TrivialVacuumEnvironment + environment = TrivialVacuumEnvironment() + # add agent to the environment + environment.add_thing(agent) + # run the environment + environment.run() + # check final status of the environment + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + + def test_ReflexVacuumAgent() : # create an object of the ReflexVacuumAgent agent = ReflexVacuumAgent() From 5f7278350cce24776dc030fa256e3e14fd855945 Mon Sep 17 00:00:00 2001 From: Anthony Marakis Date: Tue, 27 Feb 2018 21:18:39 +0200 Subject: [PATCH 042/269] Update README.md (#773) --- README.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 21a63448f..2dcf7d368 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,33 @@ When complete, this project will have Python implementations for all the pseudoc ## Python 3.4 and up This code requires Python 3.4 or later, and does not run in Python 2. You can [install Python](https://www.python.org/downloads) or use a browser-based Python interpreter such as [repl.it](https://repl.it/languages/python3). -You can run the code in an IDE, or from the command line with `python -i filename.py` where the `-i` option puts you in an interactive loop where you can run Python functions. See [jupyter.org](http://jupyter.org/) for instructions on setting up your own Jupyter notebook environment, or run the notebooks online with [try.jupiter.org](https://try.jupyter.org/). +You can run the code in an IDE, or from the command line with `python -i filename.py` where the `-i` option puts you in an interactive loop where you can run Python functions. See [jupyter.org](http://jupyter.org/) for instructions on setting up your own Jupyter notebook environment, or run the notebooks online with [try.jupiter.org](https://try.jupyter.org/). + + +## Installation Guide + +To download the repository: + +`git clone https://github.com/aimacode/aima-python.git` + +You also need to fetch the datasets from the [`aima-data`](https://github.com/aimacode/aima-data) repository: + +``` +cd aima-python +git submodule init +git submodule update +``` + +Wait for the datasets to download, it may take a while. Once they are downloaded, you need to install `pytest`, so that you can run the test suite: + +`pip install pytest` + +Then to run the tests: + +`py.test` + +And you are good to go! + # Index of Algorithms From 657a51152f611c7d970e65a9539735eff2f62e72 Mon Sep 17 00:00:00 2001 From: Aabir Abubaker Kar <16526730+bakerwho@users.noreply.github.com> Date: Tue, 27 Feb 2018 14:20:18 -0500 Subject: [PATCH 043/269] Fixed typos and added inline LaTeX to mdp.ipynb (#776) * Fixed typos and added inline LaTeX * Fixed more backslashes --- mdp.ipynb | 313 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 174 insertions(+), 139 deletions(-) diff --git a/mdp.ipynb b/mdp.ipynb index 59d8b8e3a..910b49040 100644 --- a/mdp.ipynb +++ b/mdp.ipynb @@ -247,7 +247,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": { "collapsed": true }, @@ -279,7 +279,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -316,7 +316,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": { "collapsed": true }, @@ -525,16 +525,16 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 5, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -553,7 +553,7 @@ "\n", "Now that we have looked how to represent MDPs. Let's aim at solving them. Our ultimate goal is to obtain an optimal policy. We start with looking at Value Iteration and a visualisation that should help us understanding it better.\n", "\n", - "We start by calculating Value/Utility for each of the states. The Value of each state is the expected sum of discounted future rewards given we start in that state and follow a particular policy _pi_. The value or the utility of a state is given by\n", + "We start by calculating Value/Utility for each of the states. The Value of each state is the expected sum of discounted future rewards given we start in that state and follow a particular policy $pi$. The value or the utility of a state is given by\n", "\n", "$$U(s)=R(s)+\\gamma\\max_{a\\epsilon A(s)}\\sum_{s'} P(s'\\ |\\ s,a)U(s')$$\n", "\n", @@ -682,40 +682,40 @@ "source": [ "psource(value_iteration)" ] - }, + }, { "cell_type": "markdown", "metadata": {}, "source": [ - "It takes as inputs two parameters, an MDP to solve and epsilon the maximum error allowed in the utility of any state. It returns a dictionary containing utilities where the keys are the states and values represent utilities.
    Value Iteration starts with arbitrary initial values for the utilities, calculates the right side of the Bellman equation and plugs it into the left hand side, thereby updating the utility of each state from the utilities of its neighbors. \n", + "It takes as inputs two parameters, an MDP to solve and epsilon, the maximum error allowed in the utility of any state. It returns a dictionary containing utilities where the keys are the states and values represent utilities.
    Value Iteration starts with arbitrary initial values for the utilities, calculates the right side of the Bellman equation and plugs it into the left hand side, thereby updating the utility of each state from the utilities of its neighbors. \n", "This is repeated until equilibrium is reached. \n", - "It works on the principle of _Dynamic Programming_. \n", - "If U_i(s) is the utility value for state _s_ at the _i_ th iteration, the iteration step, called Bellman update, looks like this:\n", + "It works on the principle of _Dynamic Programming_ - using precomputed information to simplify the subsequent computation. \n", + "If $U_i(s)$ is the utility value for state $s$ at the $i$ th iteration, the iteration step, called Bellman update, looks like this:\n", "\n", "$$ U_{i+1}(s) \\leftarrow R(s) + \\gamma \\max_{a \\epsilon A(s)} \\sum_{s'} P(s'\\ |\\ s,a)U_{i}(s') $$\n", "\n", "As you might have noticed, `value_iteration` has an infinite loop. How do we decide when to stop iterating? \n", "The concept of _contraction_ successfully explains the convergence of value iteration. \n", "Refer to **Section 17.2.3** of the book for a detailed explanation. \n", - "In the algorithm, we calculate a value _delta_ that measures the difference in the utilities of the current time step and the previous time step. \n", + "In the algorithm, we calculate a value $\\delta$ that measures the difference in the utilities of the current time step and the previous time step. \n", "\n", "$$\\delta = \\max{(\\delta, \\begin{vmatrix}U_{i + 1}(s) - U_i(s)\\end{vmatrix})}$$\n", "\n", - "This value of delta decreases over time.\n", - "We terminate the algorithm if the delta value is less than a threshold value determined by the hyperparameter _epsilon_.\n", + "This value of delta decreases as the values of $U_i$ converge.\n", + "We terminate the algorithm if the $\\delta$ value is less than a threshold value determined by the hyperparameter _epsilon_.\n", "\n", "$$\\delta \\lt \\epsilon \\frac{(1 - \\gamma)}{\\gamma}$$\n", "\n", - "To summarize, the Bellman update is a _contraction_ by a factor of `gamma` on the space of utility vectors. \n", - "Hence, from the properties of contractions in general, it follows that `value_iteration` always converges to a unique solution of the Bellman equations whenever gamma is less than 1.\n", + "To summarize, the Bellman update is a _contraction_ by a factor of $gamma$ on the space of utility vectors. \n", + "Hence, from the properties of contractions in general, it follows that `value_iteration` always converges to a unique solution of the Bellman equations whenever $gamma$ is less than 1.\n", "We then terminate the algorithm when a reasonable approximation is achieved.\n", - "In practice, it often occurs that the policy _pi_ becomes optimal long before the utility function converges. For the given 4 x 3 environment with _gamma = 0.9_, the policy _pi_ is optimal when _i = 4_, even though the maximum error in the utility function is stil 0.46.This can be clarified from **figure 17.6** in the book. Hence, to increase computational efficiency, we often use another method to solve MDPs called Policy Iteration which we will see in the later part of this notebook. \n", + "In practice, it often occurs that the policy $pi$ becomes optimal long before the utility function converges. For the given 4 x 3 environment with $gamma = 0.9$, the policy $pi$ is optimal when $i = 4$ (at the 4th iteration), even though the maximum error in the utility function is stil 0.46. This can be clarified from **figure 17.6** in the book. Hence, to increase computational efficiency, we often use another method to solve MDPs called Policy Iteration which we will see in the later part of this notebook. \n", "
    For now, let us solve the **sequential_decision_environment** GridMDP using `value_iteration`." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -734,7 +734,7 @@ " (3, 2): 1.0}" ] }, - "execution_count": 6, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -752,7 +752,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -781,7 +781,7 @@ "" ] }, - "execution_count": 2, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -795,23 +795,23 @@ "metadata": {}, "source": [ "### AIMA3e\n", - "__function__ VALUE-ITERATION(_mdp_, _ε_) __returns__ a utility function \n", - " __inputs__: _mdp_, an MDP with states _S_, actions _A_(_s_), transition model _P_(_s′_ | _s_, _a_), \n", - "      rewards _R_(_s_), discount _γ_ \n", - "   _ε_, the maximum error allowed in the utility of any state \n", - " __local variables__: _U_, _U′_, vectors of utilities for states in _S_, initially zero \n", - "        _δ_, the maximum change in the utility of any state in an iteration \n", - "\n", - " __repeat__ \n", - "   _U_ ← _U′_; _δ_ ← 0 \n", - "   __for each__ state _s_ in _S_ __do__ \n", - "     _U′_\\[_s_\\] ← _R_(_s_) + _γ_ max_a_ ∈ _A_(_s_) Σ _P_(_s′_ | _s_, _a_) _U_\\[_s′_\\] \n", - "     __if__ | _U′_\\[_s_\\] − _U_\\[_s_\\] | > _δ_ __then__ _δ_ ← | _U′_\\[_s_\\] − _U_\\[_s_\\] | \n", - " __until__ _δ_ < _ε_(1 − _γ_)/_γ_ \n", - " __return__ _U_ \n", - "\n", - "---\n", - "__Figure ??__ The value iteration algorithm for calculating utilities of states. The termination condition is from Equation (__??__)." + "__function__ VALUE-ITERATION(_mdp_, _ε_) __returns__ a utility function \n", + " __inputs__: _mdp_, an MDP with states _S_, actions _A_(_s_), transition model _P_(_s′_ | _s_, _a_), \n", + "      rewards _R_(_s_), discount _γ_ \n", + "   _ε_, the maximum error allowed in the utility of any state \n", + " __local variables__: _U_, _U′_, vectors of utilities for states in _S_, initially zero \n", + "        _δ_, the maximum change in the utility of any state in an iteration \n", + "\n", + " __repeat__ \n", + "   _U_ ← _U′_; _δ_ ← 0 \n", + "   __for each__ state _s_ in _S_ __do__ \n", + "     _U′_\\[_s_\\] ← _R_(_s_) + _γ_ max_a_ ∈ _A_(_s_) Σ _P_(_s′_ | _s_, _a_) _U_\\[_s′_\\] \n", + "     __if__ | _U′_\\[_s_\\] − _U_\\[_s_\\] | > _δ_ __then__ _δ_ ← | _U′_\\[_s_\\] − _U_\\[_s_\\] | \n", + " __until__ _δ_ < _ε_(1 − _γ_)/_γ_ \n", + " __return__ _U_ \n", + "\n", + "---\n", + "__Figure ??__ The value iteration algorithm for calculating utilities of states. The termination condition is from Equation (__??__)." ] }, { @@ -1366,18 +1366,13 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 11, "metadata": {}, - "outputs": [], - "source": [ - "pseudocode('Policy-Iteration')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### AIMA3e\n", + "outputs": [ + { + "data": { + "text/markdown": [ + "### AIMA3e\n", "__function__ POLICY-ITERATION(_mdp_) __returns__ a policy \n", " __inputs__: _mdp_, an MDP with states _S_, actions _A_(_s_), transition model _P_(_s′_ | _s_, _a_) \n", " __local variables__: _U_, a vector of utilities for states in _S_, initially zero \n", @@ -1395,6 +1390,42 @@ "\n", "---\n", "__Figure ??__ The policy iteration algorithm for calculating an optimal policy." + ], + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pseudocode('Policy-Iteration')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### AIMA3e\n", + "__function__ POLICY-ITERATION(_mdp_) __returns__ a policy \n", + " __inputs__: _mdp_, an MDP with states _S_, actions _A_(_s_), transition model _P_(_s′_ | _s_, _a_) \n", + " __local variables__: _U_, a vector of utilities for states in _S_, initially zero \n", + "        _π_, a policy vector indexed by state, initially random \n", + "\n", + " __repeat__ \n", + "   _U_ ← POLICY\\-EVALUATION(_π_, _U_, _mdp_) \n", + "   _unchanged?_ ← true \n", + "   __for each__ state _s_ __in__ _S_ __do__ \n", + "     __if__ max_a_ ∈ _A_(_s_) Σ_s′_ _P_(_s′_ | _s_, _a_) _U_\\[_s′_\\] > Σ_s′_ _P_(_s′_ | _s_, _π_\\[_s_\\]) _U_\\[_s′_\\] __then do__ \n", + "       _π_\\[_s_\\] ← argmax_a_ ∈ _A_(_s_) Σ_s′_ _P_(_s′_ | _s_, _a_) _U_\\[_s′_\\] \n", + "       _unchanged?_ ← false \n", + " __until__ _unchanged?_ \n", + " __return__ _π_ \n", + "\n", + "---\n", + "__Figure ??__ The policy iteration algorithm for calculating an optimal policy." ] }, { @@ -1410,12 +1441,16 @@ "![title](images/grid_mdp.jpg)\n", "
    This is the environment for our agent.\n", "We assume for now that the environment is _fully observable_, so that the agent always knows where it is.\n", - "We also assume that the transitions are **Markovian**, that is, the probability of reaching state _s'_ from state _s_ only on _s_ and not on the history of earlier states.\n", + "We also assume that the transitions are **Markovian**, that is, the probability of reaching state $s'$ from state $s$ depends only on $s$ and not on the history of earlier states.\n", "Almost all stochastic decision problems can be reframed as a Markov Decision Process just by tweaking the definition of a _state_ for that particular problem.\n", "
    \n", - "However, the actions of our agent in this environment are unreliable.\n", - "In other words, the motion of our agent is stochastic. \n", - "More specifically, the agent does the intended action with a probability of _0.8_, but with probability _0.1_, it moves to the right and with probability _0.1_ it moves to the left of the intended direction.\n", + "However, the actions of our agent in this environment are unreliable. In other words, the motion of our agent is stochastic. \n", + "

    \n", + "More specifically, the agent may - \n", + "* move correctly in the intended direction with a probability of _0.8_, \n", + "* move $90^\\circ$ to the right of the intended direction with a probability 0.1\n", + "* move $90^\\circ$ to the left of the intended direction with a probability 0.1\n", + "

    \n", "The agent stays put if it bumps into a wall.\n", "![title](images/grid_mdp_agent.jpg)" ] @@ -1429,7 +1464,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -1552,7 +1587,7 @@ "This is the function that gives the agent a rough estimate of how good being in a particular state is, or how much _reward_ an agent receives by being in that state.\n", "The agent then tries to maximize the reward it gets.\n", "As the decision problem is sequential, the utility function will depend on a sequence of states rather than on a single state.\n", - "For now, we simply stipulate that in each state s, the agent receives a finite reward _R(s)_.\n", + "For now, we simply stipulate that in each state $s$, the agent receives a finite reward $R(s)$.\n", "\n", "For any given state, the actions the agent can take are encoded as given below:\n", "- Move Up: (0, 1)\n", @@ -1565,9 +1600,9 @@ "We cannot have fixed action sequences as the environment is stochastic and we can eventually end up in an undesirable state.\n", "Therefore, a solution must specify what the agent shoulddo for _any_ state the agent might reach.\n", "
    \n", - "Such a solution is known as a **policy** and is usually denoted by **π**.\n", + "Such a solution is known as a **policy** and is usually denoted by $\\pi$.\n", "
    \n", - "The **optimal policy** is the policy that yields the highest expected utility an is usually denoted by **π* **.\n", + "The **optimal policy** is the policy that yields the highest expected utility an is usually denoted by $\\pi^*$.\n", "
    \n", "The `GridMDP` class has a useful method `to_arrows` that outputs a grid showing the direction the agent should move, given a policy.\n", "We will use this later to better understand the properties of the environment." @@ -1575,7 +1610,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -1697,7 +1732,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -1828,7 +1863,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -1853,7 +1888,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 16, "metadata": { "collapsed": true }, @@ -1871,7 +1906,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -1898,7 +1933,7 @@ "![title](images/-0.04.jpg)\n", "
    \n", "Notice that, because the cost of taking a step is fairly small compared with the penalty for ending up in `(4, 2)` by accident, the optimal policy is conservative. \n", - "In state `(3, 1)` it recommends taking the long way round, rather than taking the shorter way and risking getting a large negative reward of -1 in `(4, 2)`" + "In state `(3, 1)` it recommends taking the long way round, rather than taking the shorter way and risking getting a large negative reward of -1 in `(4, 2)`." ] }, { @@ -1912,7 +1947,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 18, "metadata": { "collapsed": true }, @@ -1926,7 +1961,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -1972,7 +2007,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 20, "metadata": { "collapsed": true }, @@ -1986,7 +2021,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -2017,7 +2052,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The living reward for each state is now more negative than the most negative terminal. Life is so painful that the agent heads for the nearest exit as even the worst exit is less painful than the current state." + "The living reward for each state is now lower than the least rewarding terminal. Life is so _painful_ that the agent heads for the nearest exit as even the worst exit is less painful than any living state." ] }, { @@ -2031,7 +2066,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 22, "metadata": { "collapsed": true }, @@ -2045,7 +2080,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 23, "metadata": {}, "outputs": [ { @@ -2141,7 +2176,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.3" + "version": "3.6.1" }, "widgets": { "state": { @@ -2166,7 +2201,7 @@ "022a5fdfc8e44fb09b21c4bd5b67a0db": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2197,7 +2232,7 @@ "0675230fb92f4539bc257b768fb4cd10": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2213,7 +2248,7 @@ "0783e74a8c2b40cc9b0f5706271192f4": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2241,7 +2276,7 @@ "098f12158d844cdf89b29a4cd568fda0": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2266,7 +2301,7 @@ "0b65fb781274495ab498ad518bc274d4": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2375,7 +2410,7 @@ "1af711fe8e4f43f084cef6c89eec40ae": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2391,7 +2426,7 @@ "1c5c913acbde4e87a163abb2e24e6e38": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2416,7 +2451,7 @@ "200e3ebead3d4858a47e2f6d345ca395": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2534,7 +2569,7 @@ "2d3acd8872c342eab3484302cac2cb05": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2544,7 +2579,7 @@ "2e1351ad05384d058c90e594bc6143c1": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2557,7 +2592,7 @@ "2f5438f1b34046a597a467effd43df11": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2594,7 +2629,7 @@ "319425ba805346f5ba366c42e220f9c6": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2613,7 +2648,7 @@ "332a89c03bfb49c2bb291051d172b735": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2662,7 +2697,7 @@ "388571e8e0314dfab8e935b7578ba7f9": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2684,7 +2719,7 @@ "3a21291c8e7249e3b04417d31b0447cf": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2697,7 +2732,7 @@ "3b22d68709b046e09fe70f381a3944cd": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2707,7 +2742,7 @@ "3c1b2ec10a9041be8a3fad9da78ff9f6": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2732,7 +2767,7 @@ "3e5b9fd779574270bf58101002c152ce": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2742,7 +2777,7 @@ "3e8bb05434cb4a0291383144e4523840": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2791,7 +2826,7 @@ "428e42f04a1e4347a1f548379c68f91b": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2807,7 +2842,7 @@ "4379175239b34553bf45c8ef9443ac55": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2820,7 +2855,7 @@ "4421c121414d464bb3bf1b5f0e86c37b": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2851,7 +2886,7 @@ "4731208453424514b471f862804d9bb8": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2900,7 +2935,7 @@ "4d281cda33fa489d86228370e627a5b0": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2919,7 +2954,7 @@ "4ec035cba73647358d416615cf4096ee": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2944,7 +2979,7 @@ "5141ae07149b46909426208a30e2861e": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -2981,7 +3016,7 @@ "55a1b0b794f44ac796bc75616f65a2a1": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3042,7 +3077,7 @@ "595c537ed2514006ac823b4090cf3b4b": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3103,7 +3138,7 @@ "5f823979d2ce4c34ba18b4ca674724e4": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3143,14 +3178,14 @@ "644dcff39d7c47b7b8b729d01f59bee5": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, "6455faf9dbc6477f8692528e6eb90c9a": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3163,7 +3198,7 @@ "665ed2b201144d78a5a1f57894c2267c": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3206,7 +3241,7 @@ "6a28f605a5d14589907dba7440ede2fc": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3231,7 +3266,7 @@ "6d7effd6bc4c40a4b17bf9e136c5814c": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3280,7 +3315,7 @@ "72dfe79a3e52429da1cf4382e78b2144": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3311,7 +3346,7 @@ "75e344508b0b45d1a9ae440549d95b1a": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3369,7 +3404,7 @@ "7f2f98bbffc0412dbb31c387407a9fed": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3400,7 +3435,7 @@ "82e2820c147a4dff85a01bcddbad8645": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3503,21 +3538,21 @@ "8cffde5bdb3d4f7597131b048a013929": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, "8db2abcad8bc44df812d6ccf2d2d713c": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, "8dd5216b361c44359ba1233ee93683a4": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3563,7 +3598,7 @@ "933904217b6045c1b654b7e5749203f5": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3591,7 +3626,7 @@ "94f2b877a79142839622a61a3a081c03": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3613,7 +3648,7 @@ "97207358fc65430aa196a7ed78b252f0": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3626,7 +3661,7 @@ "986c6c4e92964759903d6eb7f153df8a": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3669,14 +3704,14 @@ "9d5e9658af264ad795f6a5f3d8c3c30f": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, "9d7aa65511b6482d9587609ad7898f54": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3695,7 +3730,7 @@ "9efb46d2bb0648f6b109189986f4f102": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3711,7 +3746,7 @@ "9f43f85a0fb9464e9b7a25a85f6dba9c": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3724,7 +3759,7 @@ "9faa50b44e1842e0acac301f93a129c4": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3749,7 +3784,7 @@ "a1840ca22d834df2b145151baf6d8241": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3786,7 +3821,7 @@ "a39cfb47679c4d2895cda12c6d9d2975": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3817,7 +3852,7 @@ "a87c651448f14ce4958d73c2f1e413e1": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3926,7 +3961,7 @@ "b7e4c497ff5c4173961ffdc3bd3821a9": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3951,7 +3986,7 @@ "b9c138598fce460692cc12650375ee52": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -3970,7 +4005,7 @@ "bbe5dea9d57d466ba4e964fce9af13cf": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -4004,7 +4039,7 @@ "beb0c9b29d8d4d69b3147af666fa298b": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -4071,7 +4106,7 @@ "c74bbd55a8644defa3fcef473002a626": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -4138,7 +4173,7 @@ "ce3a0e82e80d48b9b2658e0c52196644": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -4148,7 +4183,7 @@ "ce8d3cd3535b459c823da2f49f3cc526": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -4218,7 +4253,7 @@ "d83329fe36014f85bb5d0247d3ae4472": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -4252,7 +4287,7 @@ "dc7376a2272e44179f237e5a1c7f6a49": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -4349,7 +4384,7 @@ "e4e5dd3dc28d4aa3ab8f8f7c4a475115": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -4365,7 +4400,7 @@ "e64ab85e80184b70b69d01a9c6851943": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -4462,7 +4497,7 @@ "f262055f3f1b48029f9e2089f752b0b8": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -4493,7 +4528,7 @@ "f3df35ce53e0466e81a48234b36a1430": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -4572,7 +4607,7 @@ "f9458080ed534d25856c67ce8f93d5a1": { "views": [ { - "cell_index": 27.0 + "cell_index": 27 } ] }, @@ -4633,4 +4668,4 @@ }, "nbformat": 4, "nbformat_minor": 1 -} \ No newline at end of file +} From 5285177b72fd479e61fd39d0408e79b1922decf9 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Tue, 27 Feb 2018 19:21:32 +0000 Subject: [PATCH 044/269] Added mdp_apps notebook (#778) * Added mdp_apps notebook * Added images * LaTeX formatting errors fixed --- images/mdp-b.png | Bin 0 -> 17560 bytes images/mdp-c.png | Bin 0 -> 18293 bytes mdp_apps.ipynb | 1316 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1316 insertions(+) create mode 100644 images/mdp-b.png create mode 100644 images/mdp-c.png create mode 100644 mdp_apps.ipynb diff --git a/images/mdp-b.png b/images/mdp-b.png new file mode 100644 index 0000000000000000000000000000000000000000..f21a3760c7644f91a79ac72c81ef8f8648682a80 GIT binary patch literal 17560 zcmaI7WmFu^+66i|FOZO+0fJj_0>Rx~g3aI-Ja~ZM7Th7Y1qiMaAUFgvxXa)c+y);U z=1$&o*80x9Kkg3})7{n8UDYM~*?T`xn(7MAaL9200KhXPMOiHX096D4K)J_8MV={c z7-U5LLvhzqkOtI@z1~M2pxa2PNdW-h1l$L64CL`YE{gi@0KoI#fB#VWoy$G|0J0oP zvQj#qO<*hjiELi}yZ8A&uM)BY%hay=bpF`rkvos(sB}3J|(fPtflxERL@>$OSGHvd6cw4i!zIzJOVEt;TkJZdX>nAV7CXFb)f-X zd}JU2(7aY;2S^Ggf&f7-G++SWkE8_(peGoD3iyH+g#p0EBESW_c>aF{U5vZU#gd!* z)Zw0ihkPO!fDC>DhdT2gLvhQfLHab{J2Jk?fe<2LOT-#dp#g!GjCvBfN%W*#&;1@DBxW z)7rB;Jg4-!e1`gq!6FxgTjlYl9$=<30~%VcpdMleyfXA7boitD+=>Jc`-R+L)dT5dfWM$!`*W?bg#Q&Zx2(*CWL~&+zl=y$L810Dl>60EEOBfa%p9lnfW2N|8 zo7Nh{-b4tj&B>;sB*F*ovon6mHc`)90B7!!J>nahRm)^z0E!N>K}`S1b}qWH*vQrY z1WrJcl3AMVvv(HI#M0VGn$XV7WF*pDp#}#6L_AvNwL3LAe(Bk};BEnn4&;Y;mMs%N zUff_nZ)`u05w8C+ZBbXXM2Po1$$quX2oaRo@X~g6(*1b%lb*eV+CBbWO17{KqU7wB=fX+(m8YZmC!2iv_&pABGS1hZj8OEEr4m zG`>gMA8#qwU$CbgdfnzOYaL!2tUh62iR?H}4g&y5)6C$H1kDa4WRms|W~{)fs;ZNG zs4kEE$DL11(I3*i^u+tK2d~g5HyO8L*ERx%m&Ubg=PtZ;KSDJq?8S{)WthrF8P` z^Pe~qL+0a?=jz<(AIhlcF;UDucMuLUN!h%gE@@l+=(C_X>{NcC1P`T$D>^oTNN*kW zm7|Wb;$F5Gan;n8eFF!V<(E^2+PC=U5fY17x2X1u55q1#2c#RI10uiXMQxi}L~ZQ^ zu;i1#Em)Hlh!0z}CzPG21S*PCL*rDfH(gp5C;TI|elC~oOs5*LCJp3nsX)VxYYyld z1C(=@m3MQu@4RPwrmxY4u7=LER4HpkMi#vflBw6znLMV*w!M(Y(Oy;F{O8>Lx+Mm@Y<%?nX`vZ>ppcM{biy2oDLC;so%n*msD~-V) z{ST2GDWAU0!>{cic|Lx!d6l()xgcx}_VvF$zq%XCw`xShe(KbLi_YA(`AWF&+8^ul+RdWUg^}YX zncZV9HLAKh%jnYF!qkNFjf~SHPKOHaza$7ZP&+d@jvLsiqpB`Q(EG(KOi7MR*Q^*Q>@F(RYmCdcnd#Qur&EHCZgj%QLoIxcD(B zj*%Bxq4Pnjz1eFDb%pf>WPd$?8DBtBw!@IzX5>hfWJwPnQnt;+4u z4_Y(Nu~I?jC+~^1t;aplnR=6-YCOIeo!A_Ms{f}dj<%Aam--C@s%0>e!@|zOp^C9h zKLu_N(Jf`BHlx}tlP>y>ckUZQA7YUoOxVE9!`QzvTN_kCs&RdlS#LKRbjoggU>!>u{<2cqjmCza7YbQj3J3($V8}gbYIA zpSJ6)cfT&07hinUHFT@@W9J14b2>*Ch5GmiSy7Xx?JuWDS#=&wfdw@z@}@a40I`fj zmKZ77OSj53n!J{8vKpmplGlzaYIQi$>}tPDbmf-!X0>@eU_Z6r(J1 zV~Ebxkye83X;^1seNbj3AmjZa*3#0oVxm;NEffmvKl(8=nrkR?1)pTZ2E>0hE*^Br zm$_@gM`S*Dl}K$5=WN{dpdp4v?&xYtLM}j(AHIC=%VPB+7-_9J=zTCrN$-qg1Dr-$ zT3{&DPSDi=z8R_#R%*EN+TfO-0%&44GCm$hZrcyP<9_a?7XifW17+WUU`_zbN`)Yfb%S z>g-4OR-T?ahH;VAmHiHrH_w)6V_F_%IY_3g*I`Qzq-jk{et^~HTOq*R;=OE7Nu6G= zBviaP9Ih|qa58US!F=7oJ{6tn)46C1!+MS z07o_vPstY!@S;YQv%spTa2YP*{yA-78Tg^&YDAlPyxrw$p^O6eTEf)-KF-rgdX*WA zP64U~)iUW%(*f>+RZntvzv_5J6=x2Pxf}Zw|3y3EEmKvaYtwQyqJZm_?m;uSeO@|K zq3HIZA+8`}0_WhH^Uj&u)HCn=f$ftlx6}ZFcvO$|9AdQDDF0N&t z{!!1XKCjOGeA9>WCIV&SqBgABmLeC#MPLKZx5`uIbV%22katf(UOSoEBcJhCVgT?*$E}Xstq7T3WcM%Sg2|ds3}(HDMK1D_n{RX0PRV!F}v? zv4KygzsD=pC3X+dyyg{%_|P{SBM_HsRYSSOSDjd^LK}ddCyn^TRv)M)%q+Pvk^_-?bdJD5cC<5kf++s+S=k`>;3T( z!t8pfJF*%JQGORqC03S8)%}?cEWDD`Vca~ZNl2^rbZVI1d7fHp)OuMhVf2!D?S8Y( zE5Ff!U6rAJ$mo>LqfX2IGb<-VcA4;SDk7&|*&1-SByn0wS`(nyH>J>ZR-Pd4c70zH zu<>@Tzs||zcEsJ80C9{`|0IIQy@9xw7&>qBBAz_Yf(&X%Olo*x5D+)!x*R zO4wT4EPKn^R>t#f5fu7gSg?zI@(<+qg33wiyHU zH+<7~OswP@8&DS&+0EF2rjw~ZKrQ<|k1MW^YFC@m7mf4HJjO{WfhhiR)j$^<bH2Zk{`tIp=XyI)rzySb9lqHd4U+1I;E|#g6KqZH5X$GIB$uOAl$f; z$o-S*xKI3g{KyBRv2ty7<#zL={HuY!zL3y+OZ32d{H7*ram9)AfDq96YmEq@*F{UG zUkW$`jy1pG=j`W^>jb671NFZGhWk-X9T#BWl#Nj zsv&y@$bbgWw=D~-y`*a@iH4vC+3gV;e>KvUws_`w&N3&~a5*P1c044=jMoN`wlw=> zZuDM--5ZpSP2{ovO>cZ7+K8X0dgDL2T+i9~-HZ`fA}C{P zHv^KAH9|S1YuHExnM-720Q8Pz*hu*Fnx&C9N@|=vuWr7?31@6Tq7gKRV1vacDz=*f zIU|%;%0@D5CiGk(uMHr?K`$XZ4M?=1#Vvyb9^5}}!t{;^RB#MzUjhMrMD-KVmKI9g z|8Q*J{E}TRAoh5x#?FHMw~~yKvUL43HTJxu1j7P3exsb=fr>C1 z@C(x81J=R<;spq~c%kSjoUMxs!BLAS$;`$1VIQo>4pE#Lt}0`kRg{8{jRy>?dng@= z+A%RjaT4kf=r>Bbi+uCiEsP};;ZtEC5tbEhC`<$)A!okJ!FUwsZ%Q-7W*VYb_2J+G z0aaC0S5`vTtupHLTTpMzl?Yr_M{y_I@9cJA)irRc&y=W%RNkRDWID37CW0UxUH#!USa z&lWln%}apWO)$yB_AZJzr;10BUC^f&iQmjo=nSmG=)tX<_V1;vKK+Gsy@XL=G|?8B z#Fr2p13jgVjUk$bjgmkkvM(egL6EFcfOYDm1oAD|^c30Afm_mS-+(=ENa{#M9fa7z zDx*0W8ShI52CJ}V>aMt2m6A9PG}A!==rO)D@);OxU{pf`lok1koHzA#fJyR2PK-By zPYE46cUU7M_4Hsi0HBZLD+DLN2!)D1uGH)lgM^)cGBo%V+g^O7<_FzcUR@oaiw`35 z4Z7SHc|6X}J{)5s6aa}ucNB6|0KyvvAV|n-wv#jV>okAJ0Azt2M?p#KOkm_F{8dJh z2znXGHXczk!j6W4Vpc-pO%-#11K45RD*dsO3pz$3VQze*Hh;XFPEm7xv*po*yhfB)F>o>+wC5oKd0zKnm!bMM<- zl?$DvLhB(}hccLxRfu;b;054?Kayv*VlEvOljTmfBm)-!kpH{r{Fi*@Rbp>NM*(On{NJf* z4fboS2oyj@8rdu;2T4||zC_;tzYMndP*iUnF5p+}NJXE;JO;q5b&nW5=oL0%6wL7d zp|t-EuQS{&_#Ax0RC|Vq?A6-Zn|AU{x-_+U8+#Vs0ccEiGC@3kS}hjEPBwB#8q4Ym z^NXI_MKfb+y<4mIw(MWFcV8i72x4Cvj2QjnA$x;9a-n*Lj}}ChZEC@H%^%HLHC1dc zW2IS?mF^DTs1d(xO~_xFb}M0G*1G%WpjRjHke#rS>c8}Hc>pnUyyJza_QP+0S>H|? zXtW3|1$dvW@1Lk@-K1=H+u>H$)9?Rql$XZ4FuECTB4Ml*Tn!!6mS`)5#g2xU9p1!@58&_utFUu_@^~MnS`59;+%jj? zt(v*0IIY_07>47&Q|{3}lD)YCtDpK<^>4)*NC1f(?)&?f3O~=TEIQmSETMX%>-IWy z`m`%`@VTG&j_Dq#%XnVLW~*&D3jKN~NKO5`#hX#@Uh?qxiyN%KreDKGcl6PcA6Gb! z?(>mpRU79se@ULwO;sj_biEl?X5iNOV$H$6YWh!|Kbgkbl*JW$4aQ}+j&QU=sE58$ zmcdLX_AQ^jW!7#v^=rOd_}t9&_q6SUlD5VjE+*&gqp-DE3uz@cpe`v!rVeLNnj?@yyR;eQ2ml^eb zt%WRh&5u%re5(Dn|2+And3u`(8l$0J|JzJT{%cmD##XVwQo^xEy5@lNHHV_vlg9Q( z+~=MnYlZ3NCdjz7HB_%FK}ekQFyK61ZKSG;D&YTLLWRp77T*_S$1N-$3pWUxZ+x zm-9rFv;^l}7pc_PO((y9T2p?hN$u`7in@9Z|BR^h$k*X>cny(z1@?GOZoivjr3vv> z(9jb5c#RP9(L(MN-jh@|=Dx!^z^_A<{*5X62XUP+{L0Cp0cO$#nt(!NOB} z2%S!YRaMU!UH<;1EfHI2(ex!dFa}znroJfoZY1`A=Ex}U^!)1LyGop!ciRea)Iy4` zZjr&%3BlJ^;RLG#hI*w>!(hYGN@VkwMJM2$Sdn=xjA;wNTw~p1vmYV0`G*3odR$IK z@oAUYn)TNUVl!W{2S*({k{3sTS;b*oZli^(Jf|fjN zZaP<}r)5ACBV)Za5Nea-aoJGcAE18I+@9(2>^*m>N6uT69op;UZ(s%7leU$uolYOa zYI(~Kp1rN&s`@)(*F)X%O!cnKMve^#hlL-}UbmQ{|KPaTUcde^2-A&rkVS?;m2vm>zm>5R-P8@yx-rNB9(?C-!ZG39NTV~C{eJYGg} z*A1lWovCMMsb?efHvFwoCr!YL--7oyXzPsiuJH&KPRHdhsr$qg%8^X<`>2TE;U$gA zG%s=dZ~k{A_rwA2)nKDVXqvV#;Jpowz1MhiMY2brw2jqFWzUtVQvkoIfygpc;&pv`@aBDM=>zdK*0YDz_XJ_> zf_+>=hN3tD^&gbk@V1H1n5{L&d=1#h^(JOS8c zZ}K3?$WQnl+ccp(z0#t1S_(;-! z0RXBNs~sVj1sDKr5;&)nT}_nS8V$hgV@x@i0Vx#zQv8c2cw#0{gZ3kjq0TELnwFCS z*noP1l@!pFg%1W9^57M;XUtLv8!4eG;>rI*j;m6Ioj>tEc!O(HtkKQddBO+q!Yif< zTu5es6j+#lQg9T5Io!&nN5wZvTbf+!k~NluWEZ)et?V({!b1gkgDkr{LXvQ+cUILI zO*w2*d^j^XfETH`c@eL3flG$No+R)2FAX#ObZe(Q+wI5+CuBahq8In(_ebm4@+CY! zxNWS;)&Ajdd)J-h{9&-2wdP~`iUWN`cpzH}5>l`r;eiG_i*Nx{uzHC_!CuH%!f~Fg zY0=45j;iPAHJ#&p6VE6c^Tww+sJCJ|&0S1?f8A>&a%Sm4d;0*zX-SzP@g^puZ%-}H zk8Jdxrk$TB!77(sBP<9a4&{_$c@xDbN87OVen4Gl3tZ8budoXhXMVGugw&ZRo9>_M z?*0j2F&1L@0ic(RVhj^+NUl80LsNy-C7YPSVwpVDWoBYnmf$I@v!kzzdgvUGFJp{k zpf|Q~V5prIvy5+$7?eT7HjJYw`CsN%>{APiL%DLmlA%huz!Ybkmce(GXYyfk^DfR- zTfUj9CG~`wHB5SGwxoMpRK_7%>jWxp5ots}CXj-HF4*#*^e;ME3}w4i2xBR-urF)k zVV#F87+5#HD-{X;`Nc26nHjA_q)b%lr%M7u&0E6jCKF!5U)#g!qmbK+-O(>Hm=p3f zg%M`oFQAWC`>eNmOi0=|oC9p^;8BJ7DS8^&cin32zf%l@aX1KYSMC@{xYdy1EXL1y zI`BjsB)T0kmf~5_G<#+2Y4536MT2YRlmyz7w~$K^D155rT8I)4IHdQK&JGCb<*E5T z!H~8kf^HeyB4<%R5k*X9BgLmG3!i&lS1v~yMNBe1%5m{=^YOE$FjgH+=d<-GLj*Ig z>pea~f>(+Ho3hP{8Nb3wxRuF~K;1}*LGR7YPTD)ID?tsO2aOhdEo#5(b?a-WgoI%} zJrEu5g1;DlQS#R)cjYqENOdy_NDD+byq9V?_3Op?!$$->cXNiD1saoL^7^N=O%IZd zr@+n)zPQaIsb{BRk;tPZR>AQZv9X39m7| z^}ju-szJAn-AfDW zk4hDdNt=I(PcIZzdk7YK(#fV7F_wY7=%7xKK?z?Rs=~20Qo3`dm;i`k=w0l6%QJNc z>MS(+J+2R2i@zN0h}#(QJh@|Aw@3#aqXfGd1auq%1J)p^4|9H*3Z!8ZS@mX}iw(j0 zb*{vIXfngquJ)YplVk(+ISmTWzUrTbM&;rBwH6tmyzQuZqzPzIa=E47=#$F7(F&hP z>CAJseK>T?7HI>}k?pW`O2||E9__(PT6166?^tDA zsI1chXgRn;QG+`CZ6qsAllsmMtq8~8NGQB3R{MKzWgm{6ky6e?R&-rgcmoQ=D3*Y& z-PP)a0bz7sr2@sPJfG^q_hw4;{?cr%8_6Iy%Lw5bJZW6XoVVww;1|W;ca4X+XDZ)) z2!^(@7cgT#s>_|v;_Gxrk ziqNLM_j7t&g~-%epDbi4X_8fED~nuLcYaE2fr&-AJ4E4PDG021wcqRAzX^Zw2uZ&8 z{D8wA%HzOL)UGE%8b=+|Hx4tZ8!dbG&LK=yKCnsw87$dQZ03 zUR7k9-0$-o98B+x7?bK$Di8rD+YZ1r*ZN%O)Ix&5b5*5J^TIcuJB*f0w&&f>-&`(B@}P!!;x_xBWi!SlE0U>=xL;2bquvA9o0OM1Wyax zlJ5~$Ovoedq|DS{foSb9{zi`5Ld%$=K+CHJ-K2I)3kI8SccVkVIzh(NI|`5?EpDz# zm*HnDXUpc$F&6&!CJpsdQ)2h|BnnHd46W78*Bg#j#smu+M9;LkLwm$)Z5PR;n!{(Z{a#^T2(D?2QvA68w{>Ya+P-4Wnj# z9qLS7O|o+V6F&o^i-5D$`(8e-7zKevGNBN=uDMxYUW#Gc43Z?!G8l`Tme)2GlroM` z|M|>=QTX;r&Q!U(lg(A5vLz(oBxRv8v(0RvZ_^vU$VS13@EsvytF@csa%fI_SKPF{ zkWg-C0YA?@Rkk)yw6+uKV4oYU-ydks?2)WGXHI%z=zljE$JJq$ZizhuavC)?sp;@( zxm6LNAbcyLetQEh%ZqLzhUI)UAf3yP-W8$${+=M!D-!DxSp0!hxk8W^S0Kz?fo?6# zX3Lpqqf_iGO(4e9H}td0dm|7r6zzV!5Q^`4t&^II+>9IJ6NVSQ=S+?E@F%;?t|kw* zBik@6AknKVX6o2*mHymo(b;&Uw-+Xc>EWv~evn7X+FN>0R#ogdZTQuon2! z`}YZz#PxPxcYo#eYUXU?*Ar>#s8aVI7TUXDDzVm!&P`|2SKhG}u_BmC?1#|Y5uo!* z^X2c~>8un9Ygs~1B|;;!@y??;orTOco9SyKejdy(6wWlMOPS7NfMFnFO5H6A*{Ayr z4(B@eDXva>PMovVlbDF@xz5jzNjJv{P&e(ImD2Psn7S+YJ1g&680Bi!L5pcU?~p`i zw>o=rD`xYZNiC840f7e@5K*_V?07u2#OJv7ur>EXiB_OXQ=*WnPNVANZJARBTPr7f zCpH2Gy$Y;pD)uEO4FvjCm!ZlBh+XCw*VxxKj>urOY2eao?{1_+KR;Uw^EdKtz41+$ zbrNg}h%pWdF3bU)I}Riohw$*{HUBst%crV!O6Mg_khm#M-SrZ%CfDiX;v%J5^Pt9- z=kAE_9-t}>_xFFwPlw4;cU_5W{IL+RT%}0^_O1=)@H*8EYae^~kzRT3vu&vPu*KT8 zo-{&zzuL2`J;@wY1kUU9=GCkoH*scVzI*mvt@f*t!TV`uU{<5Ce&cRas7vqZE*E8o z-;%-D7z=Xoz>0Xr`^#9RYt!g_8_}%YNa?jNU}D1*>jT>#vdkZ^t|iVBqNx4THlYK>@X=g|rxk|<3sMBMb-)+^~yfxu#3OH%r{%0(v>Fqt~jmoBjT3n1>m*N=SdTw{0 zmbD)BQD9f5cRSsfW1M?VR?TP1(mtkP_EO;KMw0R8;oObX+OkQq)a0AV1V@hTwmN*> zlosO{Ht`{0%{tzG`(fWtz8&o=J+H%Xoy9dDjoW1}{i zcL7WfB%XOi6|)oq564f(hA`pk?DMv3E7O20$^1KVHKV_Slh&l=91;Bxw+qWjGwPf- z@b5Z2y&GCBPKeBwSh`!+*amJcYi<9tl4=uEF9950%Qg-CLmvh-Ju}_&@V>4!nGZOT z1FCZBlqG1Vke4W^_d$zHUpg<%b%d+|OLOsbeFqn=V*;5VYkpdy6<8#9oR`<`9({+- z$3)(9`VGCd$NQ6oWGj5^jQ_f7oGjatMvZJJgr;xlVLokCiBwXRSGPU9bE*0FZksXG zwN#?Bcb6WG5uPwXT$$QB@3XdQ8~p3FtAlVDv0uRtT~UI={_*nd>X^fg!(JnpTvu2w zJr{w-u9JK9#qw<0=XV#rq-1P@?E_3{erP;LZ@Saf5o1WF=;O|T#_;f5cIMW(;l;*K z>J=bvdv1TbR-|^eWvphiZDQNc+e74DScJF4?h~H;l^|aa+l9j}1g?;~Ngq%%#B#mf zv71jahX__|9RK*ZmV1(`?W|YCe7;8N>=rk7YP@$9Lme{|{^Er6b`7l3$5VX0=w?{% z>@m&9UhHNq8Gjhpr_>+kWCdG3e#uX`3KVvQ%M%(U zvcvvpT0VoNc32`uCcO_=YVfE2yF*0l%}sS?5pLL~^K3x51It>N6!G+?PN_g-Q+37J z%lN+F&sO@&8|2<+5^L-x-V15UamnO{Wdk_-l%Ck(41{~kZfbv;Cg3`K3CbI1t*nb7 zlVoqag(J50RCg<{s2Y*<2Onms$>9E_!{Fp` z>dLuOC9-Qk?8!;2ZodsjgDDb3LZ93t4*x7SL*kncN0XlkKD(tDe z7WN#j!}T%H#dieFXZ=KNd3kss)PGQZov5Y$pu4M)kkb;L`e^jdgm|O1ezhwRyL-jX zLk3jtL;vx$^|Y7cr$*#1g><*Xti?qY>08DT0JYqN@&ljfZGIyl+bcE*VgCzCoBsK^ zm1er9yBWN1H{07ij3NvjEYo|orPdm@9a_Hou4f5OIxnG(E_%-Fr2>8a{FmRZGOw{c zi48Y9nVdD5^Lt2uTHls}ZfU#^%!+3+42)WsUocTz@-wlXG>SX2O9oNjz%O{nCM5;Q~7Rj8>~w zBtOT6XiXHO)6&I)o-*~5&HHZ-@e7e=6t4o>L@07iy330C+*C}w#C?s+HgHVZ_u~s% zoIm-GZ>$2J8?`ElS9GBUnr_w=^x=rG`&{sAZl^W>ro<`ujK{`V$j=50(EYX7#s``-{0|^ z<=Z^r3^Xrx?ee?Jk7a|@jXEBvVX5qwVV|~##25I9FL6*`Ww!dr-CxliI9wbNLNbbG zyH0O&e$TM4=nD&qc51)&e-uK{Y90Lea=rxZfL(>9*ZBEglfgSS55h74e0qCt&wCEL zyK)Z)aNP%`_SN!rN$V2#;Wx}-cQm&LqO~N7aaJQWBSUNVXN?rSe-LLa(ar0ILfc)R zL7l&K&BzuKvfJ!OzmF6Gxyco)6v_4_MNhyX62LjkueTpIhU&&x4hzShfStzsOk8DL1# z)W(T#TeI?XYVt}V_O;%{+9g%fvKePD)Nh~hapi0(mG-zRoHhwWs7*Rhmx3}`l_@f> zQj^W=>9DqMGDx&2(1cIr&3*86u+!!;s}7I*QPbsqDn{{E1b5-GsC6GxzEvY0lBEf2 z_NTo5R`yras&;Z;v*-IMcfS*Bav z*0^zZst*~`TJaS-7dv-6c?@fI*hIv)wH=$@DBq7YwxneDQvwg3i|?*)`x0f>13L?A zdh_n9I+Hf32ZmH0wo(euDew0}@U-o-9qd}HLJS?e_EXJqQ`^tvMXXku-$5v4-ZI!% z{bR%_de;1qCVst8ZOW%YqHgTX-?h^jZ|ccmDj|4I6=olidU;nNYPMi$kSK!Td*J`e z;Fs_Ca_8q5@zm5iO4ocm4ob^oU+J;rafH`^mSN;y=8PX6bEJ`3-y52y8&!%Am-KW# zF};7#gJDU`ti63n;PCOM#3|e`cW%U(-Q8jR`Cr{`X4<^qn5f;YwavBpXDz<=d7_?b z*#3&gL{)TCGp2r7Hu+)H+}JS|x9RV+#q&qKImhp4wbvr4y%i6VYubo#_rs<=Z;r=! zcOQCDFA(%1122lN#52%r|B6bW6Q_M>@W(V;8NMBSgHwi5;R5qdAn(N0d6%K4#WO?u zN-xgRwwf`S)dtnVtJ@IP`kIyH7PDa_9KSkg(GxqL*kxT_|4fdwWz~zNTHT}da(JJp zQSJ`zrhsqH<4|W!OQti2RW8aOW_V$t1>I};#unfuv_#zE9rO;2X#dSJz|Yasu+yx!tHqsB#W>StjuCyvQOhsQ(D z*JjAT^DhF>V;8;LJDz)vgHKGjdz=fkafVVNGIy7s}n14R!{|7T1>S{Yqp}b9Bb>|=R8)2&>x81Z>SC1=k%hTOaMujH8E!4 zl^dP6#)X3pU$xp~-0X@ka`F0-K7+M<4FZ#NwMj-R-wEw_w3O`ZV^A8LR0p{D_D~!@ zuN?2q{Gjf*#oB86Cbw3YxpIcNF1m2WMLu+}On7lcNbseFmA|h8ud3xz1kYS0?$A`l zq__h-VNG-#pXiyZ9U)X1(x@ zp_5Lpx^e=FDs1nF2Y=wJn>cgMvZ%P>a23f_vvh-8dtdU7uJtG}#%$t-p=Gb(*Ws#H zq#y!5l0x>ydw$oj=BNOR!498r>JbBU9QJP#jiKzP70va8C#WigebNv)5ByzYT#Gj( zQHMcyolx9GHLKZEg`@_x^$(w-bHIYt~I1c4n3P%8# zxQLwX=&v!PNUKd1eTW^W>gQ)TPPjlrWaXgTCdPNNSmh3jg6A9?LyVrg;H{td#z=?B zG{IGRk~KFKyfy3{vY>iwyJY(pFU0Nu2IMVxE04MmX@PM#=(+Kk!eshx@@pa$`Bugj z29|@KFQSpo+911s^pUj$6b3Rbi;q}og?i^25thAMSomjB&dm-}n+g%cU;@2^w)f$LLt6MIG8X+X#RH~A&#KYwmgKb@=V zshn-@)FO{|wb=RbT3oTp+(V$q+-+EiCF&baZMgyvVVM>hI>-Lm1Zu){z9643+PXNm z=t7Nt_tEI5FBikCrVk$t^lXKch4?L@JkZN`FYx2W0G~%*`lk%weA{HiKXqfnQs9-+ zFbH{-#$kbZ??U1}^e^=Wi{R&%%OiDfpYzOsts<&fm;666%$oUUJ+H>Gr06`G7-8D= zenxte{9}c=`w@(}w`OarC3)(7rR}+Nid3z76Z_SEk9mRd-9P8X?qY1g@%Vf&A~u$W z?Cvt6O`b(z^3#sv&LiL-XK^T6l#SI&kYv6ae>tg_-7ztLw6$1^g;{ITV? zKGYXd^u4M7?y)P0&g+KNeLn|^aro{PBX&%fJSI$Pp&$vN`8P9JS5hM=Q5ZD#@-b{= zvLdN|jmCP6_1pHHKkDalWgAxt6Lodl9+0_5)YRAPecZSVBtd(Lf40V$$E&*jn(@2R z8r_xuv&YHn{VR~9!a+<0RygA2;hA(`fb19M988R7zl$dOjwCijvxbe{-TTSuUkmlbXxs zr8_kMwMcY4hiHduF5QO4nycAcGz@Pa7_6fq&lw@#REM_4Fo+l>XGMhwYDN!DV2~Z*R??Y&{;mP13O2 zl|P=*BGpIil)YZ!a0LtaZwhL5^k=oCFMSO+9>MVN zg6_OckWivXmai$Y$lohrXMf8)A=AQQq>RjU81%4g^38WqtcN;c2JNl{@j@Y}+`v3P zx=LiaCho6?&*xFiJ&MTE?$So)(hRd?HVAVC)Do3r?cz;+vwS7=dILiYqB#N0yT0T8FKa-_zqs(;dq6iISmU37zpS>$BIVwJrbP zZ4G_l`nUSH|5We$<7~Q3p2rf?i`9P7*rJ7+rtaUyqiP5~OVu^VCs6s&G$VNJ<67j> z&`=}ao?o5HsjI?<#lojdkIYRPLf5NeG^kF?jeToVi$qjt*0k$tlNUg6m+Jdp0{)Vk zmME`OspMA_w8q)FsgWZCMf8uXo?7lj^7-PGf(44y%aPw{y9s|1qcM<6!lkScttN69 zh<^}lxftZdWk*Mj+mIA2DQwK~HAy{M8ss&~NH%nmsA9o;1`*~B)t`aaoa>prv4UJY zwO5i1Bz2#q1g*UT_!KnIUqoR9HA^G&jU}rFUEFq4Y)G()2yj8%%(Oey#CdCqdZTNo zO#!cxKqd_o5OmkD30g3_Cy*UdNKWM%#*JKdC;w^>Jp+GTS`Fztc?LrD0=6BuR^bnF z$7|b}y`n<3qS|>LVfY7*eK`s|bE#ei@sck!oO#BHL z^h#b<^ziNzOw@6-=Hhm!tBLra2YT0F=Gs#|8;Rnpgl*ohlT_$XH8*(#kTW2EM~4g6s1$oawR zC|r9bFuG79m-K9YC0wjt@^AkfLHAf%TJvgNw_;(<{#O#y`pqlbk-7IY>HMe6Mc*7g8L-n_+CB3?N%@f!=omWQgiQGBl=Fwvtw1|Q4sWy(!_=1EvvQWW;@A8ZzG|e%c)B_j;%;;!WIIwuQKIe7T8F&NRq}B!cRpm6X$+ zyCwQ;AGcUN_%g?U*GQ!XIf$xYFe<8?9BlB9&W^?`wYyq=RvVe?tkVYcarTfBWUmeG zL_xArOh&VI#0`;Ipv)D|a<%`JwsprAu_%$|*NQjijAXjyeUr!PU^?Zv%~2(p;f3bo z>Sdc4jrmvMBi1U2{KI{_2Z_xB-$T{r4sJ{WhZ<|Ih7f8G9q|=UxvzM2x!X0CpXRhu z+{GvP$!)SdH9loGpx~vCI-aL42pYdkP4OUbpjlJn-M{+#VSD%1mKVrMirDIW`DOz_%#g>@e?x1TVV8Z-=p1+*!C&2H}-Wa!#|I-AFRrcC5vY} zPjQG0A#`edn@iFT5iC(2C3AWipAOBC6ZRLG+qD@N`N;0Km9}%u%lR;{I_z@o_4#D* zlEXUABVInJhBOGmbJu=O=twjIh$6P7hjAYn8@I+U`lAkihYtOE>nY9@xXtVzZeHJF zX`Vw^iNGDJAwrT~8L_SO;LU{%%hSE9p1xqv+UnwKq3XWCq|#Q8@FD9)t@jB5CqyZ< z$7rT5SLsM@x5qxYgH6xZIzfYFzo_YUCKVAZDzP;Y^WJnszvJe&RlVVB`zmu27zp;< zT){%Fe@ld_?YK2Y<|3>?HDo#WMuX^y2SGNc0afSjy2s{96+olMWVgc<0CdCr%G zbAB9rGa=%@4>wz@ViR_?kNj`S85o*?b^kH*ptCNGtiU58nAD6?_yofbJOLKULClvs zb}%h#nkatYN{PB~B&&}g_~42vRz`xSS%8=RB9@)uKK6p<3LQHB%e&~Bq+B_*CkA+$ O2!p4qpUXO@geCxoIew=A literal 0 HcmV?d00001 diff --git a/images/mdp-c.png b/images/mdp-c.png new file mode 100644 index 0000000000000000000000000000000000000000..1034079a2e355aa54c96d8abe8dbe7679a7f1c90 GIT binary patch literal 18293 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�e%Vf zmXH51haS1Zry_B7yQe-gS6WJEBHJ z`k#x+-3~2n+RpIztAK<;X)D+`Ku+490^b>95k90tB33j-ns*!f>`Z~U;rgZ)X>f*MKp^MZYJi*=T+^8TQ*#2ECJ zvz?mlqdsfx)jqpFOzCM@`$Fb?X&~ii9Z2|f`*(APg4Mflb;aL71@fz0VCbS6mhgPW zUoOP!?Iot6$CM04@^`M)A#ry}EAjBznt$amA(VyRQl08iA}TxHlYHH_^TUS_4iN_d z93}T*#zOWUp-q?ku}UR(1~_VXS*7S$`wqtafJn{#p}?jKq1!4`xLB{4_HeueI=Ht1 zhyf5|cZ|I7!xVYAH#>VRszi4x8tTw@lu?|cpWz>Fl+q>+1EL2&!YCm9O%ON;PY5ni z!6ZY|8+x83JbC`}G<`$EgaRKWMm(lqrDs0SM2rb*tw+welEJk%=#1g+B&Rti^vv~7 zWK4W|W}^4#fOT9t@k8vNw*Fd;}2tQK1D!bw$APi2h8>@u4f<#PPO zJHalPkl%(w@}d(Ijyyd_S#r;W<>pz5Iw7yffVc-L^#RcRRT&In@va}!jFoEBuV~c2U?gni5{&w-tP*L7x{~5C z&jb=BB7pilr3=&Rz9Gb)*(sAzH9h8Ij!%oV(7gM&@%2*y7VYpcZ`n**y~SHGZt^9YK{L6lu9G$Y%59SQ)8DCQe$THx{%BeGUj`;XW8!C$G^h7 zj`|P$p8GOA1%eCNu+P^Uo-(QHQmHW`3(Jx7#3GD=X_v0~6rK|HX&&MOR4pW)ZFV1) z8H-~g`q%bNmEG3wqTP2s_6vD3?%3Y^d?LSMZ4*#O4KEV2rO*GPBjv>8#rGh1|H1h5 z=1<0b4vb@dzfM0!nVG5nSADF-1XXfie9KgkiA76drSQ&c*@>28*wN=^#J-vnqk`;7 zr)}K*&V9DybgOm21tRONx^`ggLgdSrp{Ii)%$eM|^BEkbBFw7l)!hX|9}|R_ijOS@ zOZq%jUr6z1|6P>ha@3pkPuPTK+kS64AS{5q7Ke$gmrY{!+fGRe-xu|5DQ-GXtwa>= ztvI}kz!r78PX59roB3eeT2s<+`>o-A7#02X^cS4Ck|)#J5MH=!_Ns;c28R9bhi8wR z4Vcf#b9-uc*L~Szq5JP{B6?BoLfi*Gzx^mpBI{WW+8SY!7)j2VKb8=m*Y|uUkTcI! zOeAE@$?170XMY)-7X4@0c&#E1F2pRQ(4yMI*(``7`W>xfB*bwbr+{1Ecp40SA!mL1 z_BIup$h)M4#=C(1`2O3qTj9NMagIC7DV%oo&FOx_O?02Q2<@@2>C{STu*Wrwq=S!i ziGQcON}*w<+R&2zTCgOhUs_92NZe#_T%+e8dJ`MBJn5{A2CX`?C_l?)SbbJ5$Id;s zxVL2P#6G*`R4U=zXt$-g5Xl?497!qzW% zeC_gd6s@K2hO_(JRr>5$DS|zMk{${a%AQwCG^}tQ+d1qNnDZCPZnAxvmHN_BV>U-} z?UXM<^{IUy;T6$Wd+&6n-rSRmKYh!6!-&N#2#Kpv{Wc`$nLdyBOr2t{cYNmSCg&uc zT74j|+05H8kVEma>mcQD7JFhBFMIyeMSUgHg*^Ow$-gY99{Xv`K>hUB;pR&!ntm^ z6ekn(`C}k|Nn1Vi71>iNO<60U{@v3o^$F1Uab z`OoO3&dAjI`uLh#T(s_u_wrBAevLqEfy_n``s4aHUMZJ~yyv7e54H)8MLODjAsR z2WE@R{_g*c99-G7+I*K`!s>FRAKKJe<*%js_B)GDws@Qd#)&T0XJ zc4zJs#bn%{59Eh`GI@=9yuzB`VAv!ne$Rx+G|6ZI&%T{ywOrG6#V_vzta{DYlX#vy zQ*%OOZ-dgvyAj^Go2};insgAQqQAnXA-dRNZLrh>52`9!faRbOez1DC0Ne(ul?QMrrX-)j6P>j$4e7Ur=3pQYNpNC`S%&v(uWRffYst($zE<2nU*pf*52ku&*hIDrxT%f zmR}KSY{vdPJsBZ$WvL&{&3$Of>S%%nEYk6%B9RaJ)+3b7206LM72eLYK_csON?y(E zoZc$MAtL-yRCggQVVf!Lx~}bxuRj(_w3>51h@MI7-Q;15>+3fS&E9>w>YPnF_H9h1 zjwb-7On~8%4Gq{)xEOiR{SckLf(W<)>(Kr@HOvHJ2q2% zr=2+G;Y9gny>nvR$lU9QDckw{*9YOFvQrUP0rKzgSGC0}ces{KBPkb6G`*8HMYCD% zg)}?%^#1Dbt(ZnB+Sd6<*@r@$_^T(1Ge%CSe;4oL0K$bZd$#x2gv@e7V7(8JpC$(f zo?U0(*4H;gBk~5<)CDRYz+be2GMo#SF8wh|#D;H;2Za`mjGmr}8) z_pYgvd(3s5tJLT*6c0TQRb&-z zf08J&%}}pJeDIHO(&pC7{Y%^mgrj`yrT&!SQ8y@WYNdX>FM1D*3B z7hX$>#GFG1sTENwCyj!Bf6vKxxg@g1)KI00m&H6`IqGW}g}sw`k>crX0+GAO09q`^|vq>n~?<>a2&;-RHm zn&;X;#N!Oi(?UmQO@vM{`5*fW7bw;KI@XuhJBM!7oG4_D110C#N%?z7WNTiTRD1Xr1Zd+COr#xsMHogKYuE^kHlMtcw*&77qo zoX6@^)&4&oDwaIra2^Ow!LNdn)sqR)!6FU?g6+*KEUUXmJY?7cT7TvX9cYI-5-FbZ#Mh@&at3=qI{Zyf(xeHQwT0o z+okTdX4R9I$sEP~q5?{Iue4`ue8IoTEqxDIAT~y>8!f_9+0?d5YxH56>FbOsx|%d| zUv@q>;3=yo!jR_oDUxdxoENglEg_CZ);|X1>5q^9{-|g$rrV`{=Mnl|XPl3a4j4Vm zl8P81HIp50uc;3-R`snz{5#f1!>-}%;-S-g`EaYCfsq+Z2o3?1PYM=H>1zs^840kE zcxnF-c;UnvkWxJU0}X^Zy@?nWF+nCkV}al(&dpJv#Lkvk^RAtzy%K#3Qh|0zI+@}C ze$acErcYVrTfmX5nQOP_QTuFG>hS7|=>?EV&Lqc?E_iENgl6c^7xI%D4ri^|ZsczgdIt2+HUJz+F1%P=djt zG87se8>WGaQrL^cmEwtnsMBzcEc-<@b|JwHs0?--chp_ZV!eY@(oKTB@=NLb=B>(x z1qK?7*Y9Csv{@DyNFTPcb!=swTo`wL^A@)J;*-##yUR>B>EAhXfEte#4wTsy#M_t$ zzQTh8+PLSEM+xq_r206dGZ;`M6xkEnM8LjF1T`2ayf23a_*&_%?@iWYA5 za{rNG@mYI@F90BDq+k*N@q%f9EFtqun3WtME~uF%7Y`6xnTP;7>)!jM?8iqrMc!Yk z=3FX;)7T!P9L7Ln-Wxk*4ve6JfEbLMQBvTesLjFo9Vz#Zc>9$D%1%J+C<_RNjQ`*p zeyoq$DiKQ%HKp%JJRQlwE8sP|$n!s_Z`6hy`#T~6C%(k6PK!K~9h<`f_JExZB%QX2 z^D^B{Ja;pz+>0ClS};cjY#NKE02m%~Q8{hm$sZHU7e2V{zSec98eIZ*ps@u5mBa{{ zV+vNyomR}7Dx=^}|I(m_OSH{JbqSrm5@uQe-Ug&4#I~ITw_ZT6HodPx&eR({Z)(m+ z8zo`EiaO%>G#EBASVC!+X&1$<@DCN8cbfa6g#7YtM;Sj3DFG7cI*}XZTMhb=-@RA@ zcu>Z@ox3oRs|4Gwk_mBGFhJPFt9NERTs;9l)HWM>+#xVp{oWu+(QSTCe1k>_{(6cx zLheWbSDY9`R8F7d95~GW?tLPfI?Km9zhgrPHSEwdUt*P0GzjwXw>}U!Wt$JKD;JkZ zaHZ+BH2vM{5IbFQE_JgA?vM`~(k7h26*~NyN%)l@>!m-y>>}iczT8{Ac*}8PRa!M% z<`;qWCdqlBL3Sb2^AO#Di7~zyr1o$cg313~_IbOh-i@vkQ~L=0)va)+lZg6CO}!1s z9*rF1fIATr^14iR!xT-7$co5I4Qpq=#(O4V^Y{ktcP%(;omS{&m*v?*Wx#~4W`0>j z(O2+sL3sZ|#igg7%;gjFw0>ZgAYcW!sU#?(T36O9ux%P?fKX7Lhkxd z=<=3;r=i@?aC~4m1RoY(j8nwgzjmPs1m!Y4dq@Emf@s#nb4LPO;6+hmOaT}gz!so( z6c}?6|FI%S{p9g>-YqgN4TcgepzH6%$HXXrU5y{=)V=sn+bo*)vG?VhEt2#hrrhfAVJok+8;jL8*RIJdpV2(4FtGgu%G(rG@H~mm=MV3 zGIg09h_APyirfpqix64=4m{Tg#p794+~CLmj?*y`UZwMs4@Kx>5zX?_8a8a zEkJ_jhZh}EhC)pO+xr%p?oUq#hx@prwD%y3{@SySb%t#5O^5M3 zxSMPNjYR_K09{W4;vL(|8f9xB0)Dr~%_Y98VU*|qpz5+qv={I2CVYTX>fOg}0)+Oq zZ(!d+-%*;`k8L7uw~=HXiFRSfI6MIqS%iReuJ7soY&39*u4Y05f8@^zO$*dQ!%!^q zf5rad@CHyA1DqrfR~zRPfny{I@ExSz`r$op^F+FMABg+JCvW%QhjTK}TWJXJ$+9B< zjZGoIA&lfg1+TJn;Q6S0MGRFo7|lM|AX=AfDIl`hAk*p9IAhcK&Qt6T0kU=@xpfOD47Wejv18by@tzXmKnPI zpB#Lc-OBm39f6pQ zXk9Xv>AWLqGN7?Uqs=jCjhzZn)qdezxGXpYT$yqZJTQE#xb-AwE1lWDU0REU8Ml$C z(Agge0_^McLDl5nHJeQ5vXX9p;hf14_-!v&c2coct_*nXT|)#{?m& zfV&0%RpoyP5Nbq`cSSdFdA&|blF>*?4XYV4#dIYTqF}DOqVRX&+y`HCN`t5#xmGmx zwgK_Bk)XD;m2;yr>jAbp756Vq;q^}h1a@DJfWG7=TMDGl1yCWXLMMmOx`0hRq8ZtD za@|ldsYG;=T$YVavNn%0(;zBDl=>vn2p*HDYWJUMWW#VkHC|_-{NSIu9M-mYp;Dr5 zQc~|iZFHu8lVz!p{FfU-H2BtjU!q7`#THu=Lq-(n>dzw_9?KvKqd2i z!X+t#%Tb<+ZsWSxEHzoP0+E&Ph4y&?$O2Zp6B{0dN9`Qv&KapBP=V1`R)Rl%ii^&pV< z)Ea=!!3h2E2yg-YlK22K~I2M{U2W?=8kP2Gpu; z`*f}6F$)cUD)li+Rt$}@(&Kk|RmA9KdeS1D%PsUv`BMAV{zMU8w%vg1*GQp&6wJue zD0!BmUyj-H`_gk)GijW1x$1F;{l~Jyb)Iad7$57aZNIP)^g8t+oVq;kY6>7rF`|uG zMZ{Q11F24_DK;hox&s<3qzf09fGWU-fLP%-)p||7$?)UhtsQ%^+O0Y}>f!Ki~_K|gE7e^+IDA)#M5nye>yJik8sS=`QbeamV*R} zJeqPY>LY;oD^lh1U*7K~iD$klM;7me4(CzktSaLA4?n)B2O*TUtA{W@S|m2pQ2o#q z|8Q?5=I~eLY4`%uMBHy4?2+Gpz9{Yk!SOVt;zm8w+tjqP=X!^HVmIHJ05nmqOg(q3 zY^#>7o!@^tC19jmzb9M0r(b_WJ|r+9LaAxhZ`SWKlQ&D+x?_}r79Cth{F*ObI?i6| zy-*Z!u5zaLbpwC&oR}KGmz%ofRd`Wni3% z5P2O@Ydxm69y$OlO@5$8sj+rGGuET`usK3RNP7tKkVLcar4ZFV{j=YmfEhLo($MtT z>d;X*HFBp~v87zyp8W!>*(qR$QhH!_u;c9v|BP5sC|}GIV5uEv#bd><7+SjT)FQp5 zOgO_UOi!UF22cd-FY@@Sel&h`TgXj4x2!B2LAf2WWUcz`NC(dvtB8w_ z6S(WSOQKM3;Ht_AQ9f9Tl7DOaahUtCcI$LGQAZ~)*q_dPSs>Ql>&UZa%O-TN)jE(b7TAFot6f7|nUNcbEl?jKZ9Ao8Ao(%kMwxY&jF`j)p6v2tfq|JvqM$fu zQGl&)_f|#zg>KZ0H2T?Brvvtwd}ci)OjoNx*`AC9UaW&;2#%;KpMG|7+S-(k(2TR&h_iv_~x98Oto! zXLi@V-N`+D``omxy9;hTRZ}c<3qRJUKf5UXzYAA-%iP$TzPBBL^O#0?bEnSdb!9rU z_Sl)5Gxaa5f4W4;cIKL@cGDfVFYS3<=ak)iyT#>tE%z?#2^)lWy1rG~V%F@U_$DFi zZZDJcLMBN;BLh0TJx(P zH+s%dzA3HCJCHsGAxbwIxM&`?tzFoy3j_I)rZQi*le*6X8q_`pM@0y#( z5`b&*&9=Q|YvT>^n4@(xs(s$|CqI0itf=&UwOTG~ufv4-xh8K@{!YkXo!wSDVb}bm zj`p|9uUgGFz0JR-c|-F7mc1Vnleg_W`n-E%`5Rut=gk{eN%0m(uV3YV{an%Qx-#pZ zZ_GJ5<3h@=ZGQLsj2Yu>r8f#CiMb1Id^mb}+x*jd_Sd&MEt>h-?Z(Xf%jc(ll>zRD zOh_-=D42X$a-YJzWV_{0ZhhPQc(c0%aQ(yDB{^R55$bz@8#`M%8BZ}dnd_esC}MDF1U3^Gf_n3S>sTga i0o4qBGD+z_H`|Wlbrx##4+2+#1NXoBxvX\n", + "Attending the first lecture gives you 4 points of reward.\n", + "After the first lecture, you have a 0.6 probability to continue into the second one, yielding 6 more points of reward.\n", + "
    \n", + "But, with a probability of 0.4, you get distracted and start using Facebook instead and get a reward of -1.\n", + "From then onwards, you really can't let go of Facebook and there's just a 0.1 probability that you will concentrate back on the lecture.\n", + "
    \n", + "After the second lecture, you have an equal chance of attending the next lecture or just falling asleep.\n", + "Falling asleep is the terminal state and yields you no reward, but continuing on to the final lecture gives you a big reward of 10 points.\n", + "
    \n", + "From there on, you have a 40% chance of going to study and reach the terminal state, \n", + "but a 60% chance of going to the pub with your friends instead. \n", + "You end up drunk and don't know which lecture to attend, so you go to one of the lectures according to the probabilities given above.\n", + "
    \n", + "We now have an outline of our stochastic environment and we need to maximize our reward by solving this MDP.\n", + "
    \n", + "
    \n", + "We first have to define our Transition Matrix as a nested dictionary to fit the requirements of the MDP class." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "t = {\n", + " 'leisure': {\n", + " 'facebook': {'leisure':0.9, 'class1':0.1},\n", + " 'quit': {'leisure':0.1, 'class1':0.9},\n", + " 'study': {},\n", + " 'sleep': {},\n", + " 'pub': {}\n", + " },\n", + " 'class1': {\n", + " 'study': {'class2':0.6, 'leisure':0.4},\n", + " 'facebook': {'class2':0.4, 'leisure':0.6},\n", + " 'quit': {},\n", + " 'sleep': {},\n", + " 'pub': {}\n", + " },\n", + " 'class2': {\n", + " 'study': {'class3':0.5, 'end':0.5},\n", + " 'sleep': {'end':0.5, 'class3':0.5},\n", + " 'facebook': {},\n", + " 'quit': {},\n", + " 'pub': {},\n", + " },\n", + " 'class3': {\n", + " 'study': {'end':0.6, 'class1':0.08, 'class2':0.16, 'class3':0.16},\n", + " 'pub': {'end':0.4, 'class1':0.12, 'class2':0.24, 'class3':0.24},\n", + " 'facebook': {},\n", + " 'quit': {},\n", + " 'sleep': {}\n", + " },\n", + " 'end': {}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now need to define the reward for each state." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "rewards = {\n", + " 'class1': 4,\n", + " 'class2': 6,\n", + " 'class3': 10,\n", + " 'leisure': -1,\n", + " 'end': 0\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This MDP has only one terminal state." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "terminals = ['end']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now set the initial state to Class 1." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "init = 'class1'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will write a CustomMDP class to extend the MDP class for the problem at hand. \n", + "This class will implement the `T` method to implement the transition model. This is the exact same class as given in [`mdp.ipynb`](https://github.com/aimacode/aima-python/blob/master/mdp.ipynb#MDP)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class CustomMDP(MDP):\n", + "\n", + " def __init__(self, transition_matrix, rewards, terminals, init, gamma=.9):\n", + " # All possible actions.\n", + " actlist = []\n", + " for state in transition_matrix.keys():\n", + " actlist.extend(transition_matrix[state])\n", + " actlist = list(set(actlist))\n", + " print(actlist)\n", + "\n", + " MDP.__init__(self, init, actlist, terminals=terminals, gamma=gamma)\n", + " self.t = transition_matrix\n", + " self.reward = rewards\n", + " for state in self.t:\n", + " self.states.add(state)\n", + "\n", + " def T(self, state, action):\n", + " if action is None:\n", + " return [(0.0, state)]\n", + " else: \n", + " return [(prob, new_state) for new_state, prob in self.t[state][action].items()]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now need an instance of this class." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['study', 'pub', 'sleep', 'facebook', 'quit']\n" + ] + } + ], + "source": [ + "mdp = CustomMDP(t, rewards, terminals, init, gamma=.9)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The utility of each state can be found by `value_iteration`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'class1': 16.90340650279542,\n", + " 'class2': 14.597383430869879,\n", + " 'class3': 19.10533144728953,\n", + " 'end': 0.0,\n", + " 'leisure': 13.946891353066082}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "value_iteration(mdp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we can compute the utility values, we can find the best policy." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "pi = best_policy(mdp, value_iteration(mdp, .01))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`pi` stores the best action for each state." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'class3': 'pub', 'leisure': 'quit', 'class2': 'study', 'class1': 'study', 'end': None}\n" + ] + } + ], + "source": [ + "print(pi)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can confirm that this is the best policy by verifying this result against `policy_iteration`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'class1': 'study',\n", + " 'class2': 'study',\n", + " 'class3': 'pub',\n", + " 'end': None,\n", + " 'leisure': 'quit'}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "policy_iteration(mdp)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Everything looks perfect, but let us look at another possibility for an MDP.\n", + "
    \n", + "Till now we have only dealt with rewards that the agent gets while it is **on** a particular state.\n", + "What if we want to have different rewards for a state depending on the action that the agent takes next. \n", + "The agent gets the reward _during its transition_ to the next state.\n", + "
    \n", + "For the sake of clarity, we will call this the _transition reward_ and we will call this kind of MDP a _dynamic_ MDP. \n", + "This is not a conventional term, we just use it to minimize confusion between the two.\n", + "
    \n", + "This next section deals with how to create and solve a dynamic MDP." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### State and action dependent reward function\n", + "Let us consider a very similar problem, but this time, we do not have rewards _on_ states, \n", + "instead, we have rewards on the transitions between states. \n", + "This state diagram will make it clearer.\n", + "![title](images/mdp-c.png)\n", + "\n", + "A very similar scenario as the previous problem, but we have different rewards for the same state depending on the action taken.\n", + "
    \n", + "To deal with this, we just need to change the `R` method of the `MDP` class, but to prevent confusion, we will write a new similar class `DMDP`." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class DMDP:\n", + "\n", + " \"\"\"A Markov Decision Process, defined by an initial state, transition model,\n", + " and reward model. We also keep track of a gamma value, for use by\n", + " algorithms. The transition model is represented somewhat differently from\n", + " the text. Instead of P(s' | s, a) being a probability number for each\n", + " state/state/action triplet, we instead have T(s, a) return a\n", + " list of (p, s') pairs. The reward function is very similar.\n", + " We also keep track of the possible states,\n", + " terminal states, and actions for each state.\"\"\"\n", + "\n", + " def __init__(self, init, actlist, terminals, transitions={}, rewards={}, states=None, gamma=.9):\n", + " if not (0 < gamma <= 1):\n", + " raise ValueError(\"An MDP must have 0 < gamma <= 1\")\n", + "\n", + " if states:\n", + " self.states = states\n", + " else:\n", + " self.states = set()\n", + " self.init = init\n", + " self.actlist = actlist\n", + " self.terminals = terminals\n", + " self.transitions = transitions\n", + " self.rewards = rewards\n", + " self.gamma = gamma\n", + "\n", + " def R(self, state, action):\n", + " \"\"\"Return a numeric reward for this state and this action.\"\"\"\n", + " if (self.rewards == {}):\n", + " raise ValueError('Reward model is missing')\n", + " else:\n", + " return self.rewards[state][action]\n", + "\n", + " def T(self, state, action):\n", + " \"\"\"Transition model. From a state and an action, return a list\n", + " of (probability, result-state) pairs.\"\"\"\n", + " if(self.transitions == {}):\n", + " raise ValueError(\"Transition model is missing\")\n", + " else:\n", + " return self.transitions[state][action]\n", + "\n", + " def actions(self, state):\n", + " \"\"\"Set of actions that can be performed in this state. By default, a\n", + " fixed list of actions, except for terminal states. Override this\n", + " method if you need to specialize by state.\"\"\"\n", + " if state in self.terminals:\n", + " return [None]\n", + " else:\n", + " return self.actlist" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The transition model will be the same" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "t = {\n", + " 'leisure': {\n", + " 'facebook': {'leisure':0.9, 'class1':0.1},\n", + " 'quit': {'leisure':0.1, 'class1':0.9},\n", + " 'study': {},\n", + " 'sleep': {},\n", + " 'pub': {}\n", + " },\n", + " 'class1': {\n", + " 'study': {'class2':0.6, 'leisure':0.4},\n", + " 'facebook': {'class2':0.4, 'leisure':0.6},\n", + " 'quit': {},\n", + " 'sleep': {},\n", + " 'pub': {}\n", + " },\n", + " 'class2': {\n", + " 'study': {'class3':0.5, 'end':0.5},\n", + " 'sleep': {'end':0.5, 'class3':0.5},\n", + " 'facebook': {},\n", + " 'quit': {},\n", + " 'pub': {},\n", + " },\n", + " 'class3': {\n", + " 'study': {'end':0.6, 'class1':0.08, 'class2':0.16, 'class3':0.16},\n", + " 'pub': {'end':0.4, 'class1':0.12, 'class2':0.24, 'class3':0.24},\n", + " 'facebook': {},\n", + " 'quit': {},\n", + " 'sleep': {}\n", + " },\n", + " 'end': {}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The reward model will be a dictionary very similar to the transition dictionary with a reward for every action for every state." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "r = {\n", + " 'leisure': {\n", + " 'facebook':-1,\n", + " 'quit':0,\n", + " 'study':0,\n", + " 'sleep':0,\n", + " 'pub':0\n", + " },\n", + " 'class1': {\n", + " 'study':-2,\n", + " 'facebook':-1,\n", + " 'quit':0,\n", + " 'sleep':0,\n", + " 'pub':0\n", + " },\n", + " 'class2': {\n", + " 'study':-2,\n", + " 'sleep':0,\n", + " 'facebook':0,\n", + " 'quit':0,\n", + " 'pub':0\n", + " },\n", + " 'class3': {\n", + " 'study':10,\n", + " 'pub':1,\n", + " 'facebook':0,\n", + " 'quit':0,\n", + " 'sleep':0\n", + " },\n", + " 'end': {\n", + " 'study':0,\n", + " 'pub':0,\n", + " 'facebook':0,\n", + " 'quit':0,\n", + " 'sleep':0\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The MDP has only one terminal state" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "terminals = ['end']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now set the initial state to Class 1." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "init = 'class1'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will write a CustomDMDP class to extend the DMDP class for the problem at hand.\n", + "This class will implement everything that the previous CustomMDP class implements along with a new reward model." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class CustomDMDP(DMDP):\n", + " \n", + " def __init__(self, transition_matrix, rewards, terminals, init, gamma=.9):\n", + " actlist = []\n", + " for state in transition_matrix.keys():\n", + " actlist.extend(transition_matrix[state])\n", + " actlist = list(set(actlist))\n", + " print(actlist)\n", + " \n", + " DMDP.__init__(self, init, actlist, terminals=terminals, gamma=gamma)\n", + " self.t = transition_matrix\n", + " self.rewards = rewards\n", + " for state in self.t:\n", + " self.states.add(state)\n", + " \n", + " \n", + " def T(self, state, action):\n", + " if action is None:\n", + " return [(0.0, state)]\n", + " else:\n", + " return [(prob, new_state) for new_state, prob in self.t[state][action].items()]\n", + " \n", + " def R(self, state, action):\n", + " if action is None:\n", + " return 0\n", + " else:\n", + " return self.rewards[state][action]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One thing we haven't thought about yet is that the `value_iteration` algorithm won't work now that the reward model is changed.\n", + "It will be quite similar to the one we currently have nonetheless." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Bellman update equation now is defined as follows\n", + "\n", + "$$U(s)=\\max_{a\\epsilon A(s)}\\bigg[R(s, a) + \\gamma\\sum_{s'}P(s'\\ |\\ s,a)U(s')\\bigg]$$\n", + "\n", + "It is not difficult to see that the update equation we have been using till now is just a special case of this more generalized equation. \n", + "We also need to max over the reward function now as the reward function is action dependent as well.\n", + "
    \n", + "We will use this to write a function to carry out value iteration, very similar to the one we are familiar with." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def value_iteration_dmdp(dmdp, epsilon=0.001):\n", + " U1 = {s: 0 for s in dmdp.states}\n", + " R, T, gamma = dmdp.R, dmdp.T, dmdp.gamma\n", + " while True:\n", + " U = U1.copy()\n", + " delta = 0\n", + " for s in dmdp.states:\n", + " U1[s] = max([(R(s, a) + gamma*sum([(p*U[s1]) for (p, s1) in T(s, a)])) for a in dmdp.actions(s)])\n", + " delta = max(delta, abs(U1[s] - U[s]))\n", + " if delta < epsilon * (1 - gamma) / gamma:\n", + " return U" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We're all set.\n", + "Let's instantiate our class." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['study', 'pub', 'sleep', 'facebook', 'quit']\n" + ] + } + ], + "source": [ + "dmdp = CustomDMDP(t, r, terminals, init, gamma=.9)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Calculate utility values by calling `value_iteration_dmdp`." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'class1': 2.0756895004431364,\n", + " 'class2': 5.772550326127298,\n", + " 'class3': 12.827904448229472,\n", + " 'end': 0.0,\n", + " 'leisure': 1.8474896554396596}" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "value_iteration_dmdp(dmdp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These are the expected utility values for our new MDP.\n", + "
    \n", + "As you might have guessed, we cannot use the old `best_policy` function to find the best policy.\n", + "So we will write our own.\n", + "But, before that we need a helper function to calculate the expected utility value given a state and an action." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def expected_utility_dmdp(a, s, U, dmdp):\n", + " return dmdp.R(s, a) + dmdp.gamma*sum([(p*U[s1]) for (p, s1) in dmdp.T(s, a)])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we write our modified `best_policy` function." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from utils import argmax\n", + "def best_policy_dmdp(dmdp, U):\n", + " pi = {}\n", + " for s in dmdp.states:\n", + " pi[s] = argmax(dmdp.actions(s), key=lambda a: expected_utility_dmdp(a, s, U, dmdp))\n", + " return pi" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Find the best policy." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'class3': 'study', 'leisure': 'quit', 'class2': 'sleep', 'class1': 'facebook', 'end': None}\n" + ] + } + ], + "source": [ + "pi = best_policy_dmdp(dmdp, value_iteration_dmdp(dmdp, .01))\n", + "print(pi)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From this, we can infer that `value_iteration_dmdp` tries to minimize the negative reward. \n", + "Since we don't have rewards for states now, the algorithm takes the action that would try to avoid getting negative rewards and take the lesser of two evils if all rewards are negative.\n", + "You might also want to have state rewards alongside transition rewards. \n", + "Perhaps you can do that yourself now that the difficult part has been done.\n", + "
    " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### State, action and next-state dependent reward function\n", + "\n", + "For truly stochastic environments, \n", + "we have noticed that taking an action from a particular state doesn't always do what we want it to. \n", + "Instead, for every action taken from a particular state, \n", + "it might be possible to reach a different state each time depending on the transition probabilities. \n", + "What if we want different rewards for each state, action and next-state triplet? \n", + "Mathematically, we now want a reward function of the form R(s, a, s') for our MDP. \n", + "This section shows how we can tweak the MDP class to achieve this.\n", + "
    \n", + "\n", + "Let's now take a different problem statement. \n", + "The one we are working with is a bit too simple.\n", + "Consider a taxi that serves three adjacent towns A, B, and C.\n", + "Each time the taxi discharges a passenger, the driver must choose from three possible actions:\n", + "1. Cruise the streets looking for a passenger.\n", + "2. Go to the nearest taxi stand.\n", + "3. Wait for a radio call from the dispatcher with instructions.\n", + "
    \n", + "Subject to the constraint that the taxi driver cannot do the third action in town B because of distance and poor reception.\n", + "\n", + "Let's model our MDP.\n", + "
    \n", + "The MDP has three states, namely A, B and C.\n", + "
    \n", + "It has three actions, namely 1, 2 and 3.\n", + "
    \n", + "Action sets:\n", + "
    \n", + "$K_{a}$ = {1, 2, 3}\n", + "
    \n", + "$K_{b}$ = {1, 2}\n", + "
    \n", + "$K_{c}$ = {1, 2, 3}\n", + "
    \n", + "\n", + "We have the following transition probability matrices:\n", + "
    \n", + "
    \n", + "Action 1: Cruising streets\n", + "
    \n", + "
    \n", + "$$\\\\\n", + " P^{1} = \n", + " \\left[ {\\begin{array}{ccc}\n", + " \\frac{1}{2} & \\frac{1}{4} & \\frac{1}{4} \\\\\n", + " \\frac{1}{2} & 0 & \\frac{1}{2} \\\\\n", + " \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{2} \\\\\n", + " \\end{array}}\\right] \\\\\n", + " \\\\\n", + "$$\n", + "
    \n", + "
    \n", + "Action 2: Waiting at the taxi stand \n", + "
    \n", + "
    \n", + "$$\\\\\n", + " P^{2} = \n", + " \\left[ {\\begin{array}{ccc}\n", + " \\frac{1}{16} & \\frac{3}{4} & \\frac{3}{16} \\\\\n", + " \\frac{1}{16} & \\frac{7}{8} & \\frac{1}{16} \\\\\n", + " \\frac{1}{8} & \\frac{3}{4} & \\frac{1}{8} \\\\\n", + " \\end{array}}\\right] \\\\\n", + " \\\\\n", + "$$\n", + "
    \n", + "
    \n", + "Action 3: Waiting for dispatch \n", + "
    \n", + "
    \n", + "$$\\\\\n", + " P^{3} =\n", + " \\left[ {\\begin{array}{ccc}\n", + " \\frac{1}{4} & \\frac{1}{8} & \\frac{5}{8} \\\\\n", + " 0 & 1 & 0 \\\\\n", + " \\frac{3}{4} & \\frac{1}{16} & \\frac{3}{16} \\\\\n", + " \\end{array}}\\right] \\\\\n", + " \\\\\n", + "$$\n", + "
    \n", + "
    \n", + "For the sake of readability, we will call the states A, B and C and the actions 'cruise', 'stand' and 'dispatch'.\n", + "We will now build the transition model as a dictionary using these matrices." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "t = {\n", + " 'A': {\n", + " 'cruise': {'A':0.5, 'B':0.25, 'C':0.25},\n", + " 'stand': {'A':0.0625, 'B':0.75, 'C':0.1875},\n", + " 'dispatch': {'A':0.25, 'B':0.125, 'C':0.625}\n", + " },\n", + " 'B': {\n", + " 'cruise': {'A':0.5, 'B':0, 'C':0.5},\n", + " 'stand': {'A':0.0625, 'B':0.875, 'C':0.0625},\n", + " 'dispatch': {'A':0, 'B':1, 'C':0}\n", + " },\n", + " 'C': {\n", + " 'cruise': {'A':0.25, 'B':0.25, 'C':0.5},\n", + " 'stand': {'A':0.125, 'B':0.75, 'C':0.125},\n", + " 'dispatch': {'A':0.75, 'B':0.0625, 'C':0.1875}\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The reward matrices for the problem are as follows:\n", + "
    \n", + "
    \n", + "Action 1: Cruising streets \n", + "
    \n", + "
    \n", + "$$\\\\\n", + " R^{1} = \n", + " \\left[ {\\begin{array}{ccc}\n", + " 10 & 4 & 8 \\\\\n", + " 14 & 0 & 18 \\\\\n", + " 10 & 2 & 8 \\\\\n", + " \\end{array}}\\right] \\\\\n", + " \\\\\n", + "$$\n", + "
    \n", + "
    \n", + "Action 2: Waiting at the taxi stand \n", + "
    \n", + "
    \n", + "$$\\\\\n", + " R^{2} = \n", + " \\left[ {\\begin{array}{ccc}\n", + " 8 & 2 & 4 \\\\\n", + " 8 & 16 & 8 \\\\\n", + " 6 & 4 & 2\\\\\n", + " \\end{array}}\\right] \\\\\n", + " \\\\\n", + "$$\n", + "
    \n", + "
    \n", + "Action 3: Waiting for dispatch \n", + "
    \n", + "
    \n", + "$$\\\\\n", + " R^{3} = \n", + " \\left[ {\\begin{array}{ccc}\n", + " 4 & 6 & 4 \\\\\n", + " 0 & 0 & 0 \\\\\n", + " 4 & 0 & 8\\\\\n", + " \\end{array}}\\right] \\\\\n", + " \\\\\n", + "$$\n", + "
    \n", + "
    \n", + "We now build the reward model as a dictionary using these matrices." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "r = {\n", + " 'A': {\n", + " 'cruise': {'A':10, 'B':4, 'C':8},\n", + " 'stand': {'A':8, 'B':2, 'C':4},\n", + " 'dispatch': {'A':4, 'B':6, 'C':4}\n", + " },\n", + " 'B': {\n", + " 'cruise': {'A':14, 'B':0, 'C':18},\n", + " 'stand': {'A':8, 'B':16, 'C':8},\n", + " 'dispatch': {'A':0, 'B':0, 'C':0}\n", + " },\n", + " 'C': {\n", + " 'cruise': {'A':10, 'B':2, 'C':18},\n", + " 'stand': {'A':6, 'B':4, 'C':2},\n", + " 'dispatch': {'A':4, 'B':0, 'C':8}\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "The Bellman update equation now is defined as follows\n", + "\n", + "$$U(s)=\\max_{a\\epsilon A(s)}\\sum_{s'}P(s'\\ |\\ s,a)(R(s'\\ |\\ s,a) + \\gamma U(s'))$$\n", + "\n", + "It is not difficult to see that all the update equations we have used till now is just a special case of this more generalized equation. \n", + "If we did not have next-state-dependent rewards, the first term inside the summation exactly sums up to R(s, a) or the state-reward for a particular action and we would get the update equation used in the previous problem.\n", + "If we did not have action dependent rewards, the first term inside the summation sums up to R(s) or the state-reward and we would get the first update equation used in `mdp.ipynb`.\n", + "
    \n", + "For example, as we have the same reward regardless of the action, let's consider a reward of **r** units for a particular state and let's assume the transition probabilities to be 0.1, 0.2, 0.3 and 0.4 for 4 possible actions for that state.\n", + "We will further assume that a particular action in a state leads to the same state every time we take that action.\n", + "The first term inside the summation for this case will evaluate to (0.1 + 0.2 + 0.3 + 0.4)r = r which is equal to R(s) in the first update equation.\n", + "
    \n", + "There are many ways to write value iteration for this situation, but we will go with the most intuitive method.\n", + "One that can be implemented with minor alterations to the existing `value_iteration` algorithm.\n", + "
    \n", + "Our `DMDP` class will be slightly different.\n", + "More specifically, the `R` method will have one more index to go through now that we have three levels of nesting in the reward model.\n", + "We will call the new class `DMDP2` as I have run out of creative names." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class DMDP2:\n", + "\n", + " \"\"\"A Markov Decision Process, defined by an initial state, transition model,\n", + " and reward model. We also keep track of a gamma value, for use by\n", + " algorithms. The transition model is represented somewhat differently from\n", + " the text. Instead of P(s' | s, a) being a probability number for each\n", + " state/state/action triplet, we instead have T(s, a) return a\n", + " list of (p, s') pairs. The reward function is very similar.\n", + " We also keep track of the possible states,\n", + " terminal states, and actions for each state.\"\"\"\n", + "\n", + " def __init__(self, init, actlist, terminals, transitions={}, rewards={}, states=None, gamma=.9):\n", + " if not (0 < gamma <= 1):\n", + " raise ValueError(\"An MDP must have 0 < gamma <= 1\")\n", + "\n", + " if states:\n", + " self.states = states\n", + " else:\n", + " self.states = set()\n", + " self.init = init\n", + " self.actlist = actlist\n", + " self.terminals = terminals\n", + " self.transitions = transitions\n", + " self.rewards = rewards\n", + " self.gamma = gamma\n", + "\n", + " def R(self, state, action, state_):\n", + " \"\"\"Return a numeric reward for this state, this action and the next state_\"\"\"\n", + " if (self.rewards == {}):\n", + " raise ValueError('Reward model is missing')\n", + " else:\n", + " return self.rewards[state][action][state_]\n", + "\n", + " def T(self, state, action):\n", + " \"\"\"Transition model. From a state and an action, return a list\n", + " of (probability, result-state) pairs.\"\"\"\n", + " if(self.transitions == {}):\n", + " raise ValueError(\"Transition model is missing\")\n", + " else:\n", + " return self.transitions[state][action]\n", + "\n", + " def actions(self, state):\n", + " \"\"\"Set of actions that can be performed in this state. By default, a\n", + " fixed list of actions, except for terminal states. Override this\n", + " method if you need to specialize by state.\"\"\"\n", + " if state in self.terminals:\n", + " return [None]\n", + " else:\n", + " return self.actlist\n", + " \n", + " def actions(self, state):\n", + " \"\"\"Set of actions that can be performed in this state. By default, a\n", + " fixed list of actions, except for terminal states. Override this\n", + " method if you need to specialize by state.\"\"\"\n", + " if state in self.terminals:\n", + " return [None]\n", + " else:\n", + " return self.actlist" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Only the `R` method is different from the previous `DMDP` class.\n", + "
    \n", + "Our traditional custom class will be required to implement the transition model and the reward model.\n", + "
    \n", + "We call this class `CustomDMDP2`." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class CustomDMDP2(DMDP2):\n", + " \n", + " def __init__(self, transition_matrix, rewards, terminals, init, gamma=.9):\n", + " actlist = []\n", + " for state in transition_matrix.keys():\n", + " actlist.extend(transition_matrix[state])\n", + " actlist = list(set(actlist))\n", + " print(actlist)\n", + " \n", + " DMDP2.__init__(self, init, actlist, terminals=terminals, gamma=gamma)\n", + " self.t = transition_matrix\n", + " self.rewards = rewards\n", + " for state in self.t:\n", + " self.states.add(state)\n", + " \n", + " def T(self, state, action):\n", + " if action is None:\n", + " return [(0.0, state)]\n", + " else:\n", + " return [(prob, new_state) for new_state, prob in self.t[state][action].items()]\n", + " \n", + " def R(self, state, action, state_):\n", + " if action is None:\n", + " return 0\n", + " else:\n", + " return self.rewards[state][action][state_]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can finally write value iteration for this problem.\n", + "The latest update equation will be used." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def value_iteration_taxi_mdp(dmdp2, epsilon=0.001):\n", + " U1 = {s: 0 for s in dmdp2.states}\n", + " R, T, gamma = dmdp2.R, dmdp2.T, dmdp2.gamma\n", + " while True:\n", + " U = U1.copy()\n", + " delta = 0\n", + " for s in dmdp2.states:\n", + " U1[s] = max([sum([(p*(R(s, a, s1) + gamma*U[s1])) for (p, s1) in T(s, a)]) for a in dmdp2.actions(s)])\n", + " delta = max(delta, abs(U1[s] - U[s]))\n", + " if delta < epsilon * (1 - gamma) / gamma:\n", + " return U" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These algorithms can be made more pythonic by using cleverer list comprehensions.\n", + "We can also write the variants of value iteration in such a way that all problems are solved using the same base class, regardless of the reward function and the number of arguments it takes.\n", + "Quite a few things can be done to refactor the code and reduce repetition, but we have done it this way for the sake of clarity.\n", + "Perhaps you can try this as an exercise.\n", + "
    \n", + "We now need to define terminals and initial state." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "terminals = ['end']\n", + "init = 'A'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's instantiate our class." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['cruise', 'dispatch', 'stand']\n" + ] + } + ], + "source": [ + "dmdp2 = CustomDMDP2(t, r, terminals, init, gamma=.9)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'A': 124.4881543573768, 'B': 137.70885410461636, 'C': 129.08041190693115}" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "value_iteration_taxi_mdp(dmdp2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These are the expected utility values for the states of our MDP.\n", + "Let's proceed to write a helper function to find the expected utility and another to find the best policy." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def expected_utility_dmdp2(a, s, U, dmdp2):\n", + " return sum([(p*(dmdp2.R(s, a, s1) + dmdp2.gamma*U[s1])) for (p, s1) in dmdp2.T(s, a)])" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from utils import argmax\n", + "def best_policy_dmdp2(dmdp2, U):\n", + " pi = {}\n", + " for s in dmdp2.states:\n", + " pi[s] = argmax(dmdp2.actions(s), key=lambda a: expected_utility_dmdp2(a, s, U, dmdp2))\n", + " return pi" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Find the best policy." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'C': 'cruise', 'A': 'stand', 'B': 'stand'}\n" + ] + } + ], + "source": [ + "pi = best_policy_dmdp2(dmdp2, value_iteration_taxi_mdp(dmdp2, .01))\n", + "print(pi)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have successfully adapted the existing code to a different scenario yet again.\n", + "The takeaway from this section is that you can convert the vast majority of reinforcement learning problems into MDPs and solve for the best policy using simple yet efficient tools." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 751a28689e1bf9604013874cd99abebf4377cf8a Mon Sep 17 00:00:00 2001 From: Alan Oliveira Date: Tue, 27 Feb 2018 16:21:52 -0300 Subject: [PATCH 045/269] Minor fix in typo (#779) --- search.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/search.ipynb b/search.ipynb index cf3b4306e..332ba11b9 100644 --- a/search.ipynb +++ b/search.ipynb @@ -143,15 +143,15 @@ "source": [ "The `Node` class has nine methods.\n", "\n", - "* `__init__(self, state, parent, action, path_cost)` : This method creates a node. `parent` represents the the node that this is a successor of and `action` is the action required to get from the parent node to this node. `path_cost` is the cost to reach current node from parent node.\n", + "* `__init__(self, state, parent, action, path_cost)` : This method creates a node. `parent` represents the node that this is a successor of and `action` is the action required to get from the parent node to this node. `path_cost` is the cost to reach current node from parent node.\n", "\n", "* `__repr__(self)` : This returns the state of this node.\n", "\n", "* `__lt__(self, node)` : Given a `node`, this method returns `True` if the state of current node is less than the state of the `node`. Otherwise it returns `False`.\n", "\n", - "* `expand(self, problem)` : This methods lists all the neighbouring(reachable in one step) nodes of current node. \n", + "* `expand(self, problem)` : This method lists all the neighbouring(reachable in one step) nodes of current node. \n", "\n", - "* `child_node(self, problem, action)` : Given an `action`, this methods returns the immediate neighbour that can be reached with that `action`.\n", + "* `child_node(self, problem, action)` : Given an `action`, this method returns the immediate neighbour that can be reached with that `action`.\n", "\n", "* `solution(self)` : This returns the sequence of actions required to reach this node from the root node. \n", "\n", From cc2d40566a592d7ca63c802e9762ade26c3c385a Mon Sep 17 00:00:00 2001 From: Nouman Ahmed <35970677+Noumanmufc1@users.noreply.github.com> Date: Wed, 28 Feb 2018 04:41:36 +0500 Subject: [PATCH 046/269] Minor typos (#780) --- csp.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/csp.ipynb b/csp.ipynb index aa8b37c7d..1de9e1312 100644 --- a/csp.ipynb +++ b/csp.ipynb @@ -275,7 +275,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We will now use a graph defined as a dictonary for plotting purposes in our Graph Coloring Problem. The keys are the nodes and their corresponding values are the nodes they are connected to." + "We will now use a graph defined as a dictionary for plotting purposes in our Graph Coloring Problem. The keys are the nodes and their corresponding values are the nodes they are connected to." ] }, { @@ -431,7 +431,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now let us check the total number of assignments and unassignments which is the length ofour assignment history." + "Now let us check the total number of assignments and unassignments which is the length of our assignment history." ] }, { From 7e763e6bd7c550c9ff9dda2f06d084c9c209fbe6 Mon Sep 17 00:00:00 2001 From: Ayush Jain Date: Wed, 28 Feb 2018 06:38:06 +0530 Subject: [PATCH 047/269] Added TableDrivenAgentProgram tests (#777) * Add tests for TableDrivenAgentProgram * Add tests for TableDrivenAgentProgram * Check environment status at every step * Check environment status at every step of TableDrivenAgentProgram --- tests/test_agents.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/test_agents.py b/tests/test_agents.py index 73b149f99..caefe61d4 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -83,10 +83,9 @@ def test_RandomVacuumAgent() : assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} -def test_TableDrivenAgent() : - #create a table that would consist of all the possible states of the agent +def test_TableDrivenAgent(): loc_A, loc_B = (0, 0), (1, 0) - + # table defining all the possible states of the agent table = {((loc_A, 'Clean'),): 'Right', ((loc_A, 'Dirty'),): 'Suck', ((loc_B, 'Clean'),): 'Left', @@ -98,17 +97,26 @@ def test_TableDrivenAgent() : ((loc_A, 'Dirty'), (loc_A, 'Clean'), (loc_B, 'Dirty')): 'Suck', ((loc_B, 'Dirty'), (loc_B, 'Clean'), (loc_A, 'Dirty')): 'Suck' } + # create an program and then an object of the TableDrivenAgent program = TableDrivenAgentProgram(table) agent = Agent(program) - # create an object of the TrivialVacuumEnvironment + # create an object of TrivialVacuumEnvironment environment = TrivialVacuumEnvironment() + # initializing some environment status + environment.status = {loc_A:'Dirty', loc_B:'Dirty'} # add agent to the environment environment.add_thing(agent) - # run the environment - environment.run() - # check final status of the environment - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + + # run the environment by single step everytime to check how environment evolves using TableDrivenAgentProgram + environment.run(steps = 1) + assert environment.status == {(1,0): 'Clean', (0,0): 'Dirty'} + + environment.run(steps = 1) + assert environment.status == {(1,0): 'Clean', (0,0): 'Dirty'} + + environment.run(steps = 1) + assert environment.status == {(1,0): 'Clean', (0,0): 'Clean'} def test_ReflexVacuumAgent() : From d1f162beeed35ff99683c3620c5350eed32089ed Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Thu, 1 Mar 2018 23:39:29 +0000 Subject: [PATCH 048/269] Enhanced mdp_apps notebook (#782) * Added pathfinding example * Added images --- images/maze.png | Bin 0 -> 4576 bytes images/mdp-d.png | Bin 0 -> 21321 bytes mdp_apps.ipynb | 193 ++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 166 insertions(+), 27 deletions(-) create mode 100644 images/maze.png create mode 100644 images/mdp-d.png diff --git a/images/maze.png b/images/maze.png new file mode 100644 index 0000000000000000000000000000000000000000..f3fcd19904cfe1ae6e57f38897ee1b924745e6fe GIT binary patch literal 4576 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 literal 0 HcmV?d00001 diff --git a/images/mdp-d.png b/images/mdp-d.png new file mode 100644 index 0000000000000000000000000000000000000000..8ba7cf073988e5487d1a2e2d4b2fb37af08b9f40 GIT binary patch literal 21321 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\n", "
    \n", - "Action 1: Cruising streets\n", - "
    \n", + "Action 1: Cruising streets \n", "
    \n", - "$$\\\\\n", + "$\\\\\n", " P^{1} = \n", " \\left[ {\\begin{array}{ccc}\n", " \\frac{1}{2} & \\frac{1}{4} & \\frac{1}{4} \\\\\n", @@ -843,13 +843,12 @@ " \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{2} \\\\\n", " \\end{array}}\\right] \\\\\n", " \\\\\n", - "$$\n", + " $\n", "
    \n", "
    \n", - "Action 2: Waiting at the taxi stand \n", + "Action 2: Waiting at the taxi stand \n", "
    \n", - "
    \n", - "$$\\\\\n", + "$\\\\\n", " P^{2} = \n", " \\left[ {\\begin{array}{ccc}\n", " \\frac{1}{16} & \\frac{3}{4} & \\frac{3}{16} \\\\\n", @@ -857,13 +856,12 @@ " \\frac{1}{8} & \\frac{3}{4} & \\frac{1}{8} \\\\\n", " \\end{array}}\\right] \\\\\n", " \\\\\n", - "$$\n", + " $\n", "
    \n", "
    \n", "Action 3: Waiting for dispatch \n", "
    \n", - "
    \n", - "$$\\\\\n", + "$\\\\\n", " P^{3} =\n", " \\left[ {\\begin{array}{ccc}\n", " \\frac{1}{4} & \\frac{1}{8} & \\frac{5}{8} \\\\\n", @@ -871,7 +869,7 @@ " \\frac{3}{4} & \\frac{1}{16} & \\frac{3}{16} \\\\\n", " \\end{array}}\\right] \\\\\n", " \\\\\n", - "$$\n", + " $\n", "
    \n", "
    \n", "For the sake of readability, we will call the states A, B and C and the actions 'cruise', 'stand' and 'dispatch'.\n", @@ -914,8 +912,7 @@ "
    \n", "Action 1: Cruising streets \n", "
    \n", - "
    \n", - "$$\\\\\n", + "$\\\\\n", " R^{1} = \n", " \\left[ {\\begin{array}{ccc}\n", " 10 & 4 & 8 \\\\\n", @@ -923,13 +920,12 @@ " 10 & 2 & 8 \\\\\n", " \\end{array}}\\right] \\\\\n", " \\\\\n", - "$$\n", + " $\n", "
    \n", "
    \n", "Action 2: Waiting at the taxi stand \n", "
    \n", - "
    \n", - "$$\\\\\n", + "$\\\\\n", " R^{2} = \n", " \\left[ {\\begin{array}{ccc}\n", " 8 & 2 & 4 \\\\\n", @@ -937,13 +933,12 @@ " 6 & 4 & 2\\\\\n", " \\end{array}}\\right] \\\\\n", " \\\\\n", - "$$\n", + " $\n", "
    \n", "
    \n", "Action 3: Waiting for dispatch \n", "
    \n", - "
    \n", - "$$\\\\\n", + "$\\\\\n", " R^{3} = \n", " \\left[ {\\begin{array}{ccc}\n", " 4 & 6 & 4 \\\\\n", @@ -951,7 +946,7 @@ " 4 & 0 & 8\\\\\n", " \\end{array}}\\right] \\\\\n", " \\\\\n", - "$$\n", + " $\n", "
    \n", "
    \n", "We now build the reward model as a dictionary using these matrices." @@ -1194,7 +1189,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "['cruise', 'dispatch', 'stand']\n" + "['stand', 'dispatch', 'cruise']\n" ] } ], @@ -1290,6 +1285,150 @@ "We have successfully adapted the existing code to a different scenario yet again.\n", "The takeaway from this section is that you can convert the vast majority of reinforcement learning problems into MDPs and solve for the best policy using simple yet efficient tools." ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## GRID MDP\n", + "---\n", + "### Pathfinding Problem\n", + "Markov Decision Processes can be used to find the best path through a maze. Let us consider this simple maze.\n", + "![title](images/maze.png)\n", + "\n", + "This environment can be formulated as a GridMDP.\n", + "
    \n", + "To make the grid matrix, we will consider the state-reward to be -0.1 for every state.\n", + "
    \n", + "State (1, 1) will have a reward of -5 to signify that this state is to be prohibited.\n", + "
    \n", + "State (9, 9) will have a reward of +5.\n", + "This will be the terminal state.\n", + "
    \n", + "The matrix can be generated using the GridMDP editor or we can write it ourselves." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "grid = [\n", + " [None, None, None, None, None, None, None, None, None, None, None], \n", + " [None, -0.1, -0.1, -0.1, -0.1, -0.1, -0.1, -0.1, None, +5.0, None], \n", + " [None, -0.1, None, None, None, None, None, None, None, -0.1, None], \n", + " [None, -0.1, -0.1, -0.1, -0.1, -0.1, -0.1, -0.1, -0.1, -0.1, None], \n", + " [None, -0.1, None, None, None, None, None, None, None, None, None], \n", + " [None, -0.1, None, -0.1, -0.1, -0.1, -0.1, -0.1, -0.1, -0.1, None], \n", + " [None, -0.1, None, None, None, None, None, -0.1, None, -0.1, None], \n", + " [None, -0.1, -0.1, -0.1, -0.1, -0.1, -0.1, -0.1, None, -0.1, None], \n", + " [None, None, None, None, None, -0.1, None, -0.1, None, -0.1, None], \n", + " [None, -5.0, -0.1, -0.1, -0.1, -0.1, None, -0.1, None, -0.1, None], \n", + " [None, None, None, None, None, None, None, None, None, None, None]\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have only one terminal state, (9, 9)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "terminals = [(9, 9)]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We define our maze environment below" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [], + "source": [ + "maze = GridMDP(grid, terminals)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To solve the maze, we can use the `best_policy` function along with `value_iteration`." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "pi = best_policy(maze, value_iteration(maze))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is the heatmap generated by the GridMDP editor using `value_iteration` on this environment\n", + "
    \n", + "![title](images/mdp-d.png)\n", + "
    \n", + "Let's print out the best policy" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "None None None None None None None None None None None\n", + "None v < < < < < < None . None\n", + "None v None None None None None None None ^ None\n", + "None > > > > > > > > ^ None\n", + "None ^ None None None None None None None None None\n", + "None ^ None > > > > v < < None\n", + "None ^ None None None None None v None ^ None\n", + "None ^ < < < < < < None ^ None\n", + "None None None None None ^ None ^ None ^ None\n", + "None > > > > ^ None ^ None ^ None\n", + "None None None None None None None None None None None\n" + ] + } + ], + "source": [ + "from utils import print_table\n", + "print_table(maze.to_arrows(pi))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can infer, we can find the path to the terminal state starting from any given state using this policy.\n", + "All maze problems can be solved by formulating it as a MDP." + ] } ], "metadata": { From d6a175c4644d73712590c14b8a351a7788f9d2d2 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Fri, 2 Mar 2018 06:18:53 +0530 Subject: [PATCH 049/269] Backgammon implementation (#783) * Create model classes for backgammon * Add game functions to model * Implement expectiminimax function * Correct logic in some functions * Correct expectiminimax logic * Refactor code and add docstrings * Remove print statements --- games.py | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 2 deletions(-) diff --git a/games.py b/games.py index 00a2c33d3..be9620bd4 100644 --- a/games.py +++ b/games.py @@ -2,8 +2,9 @@ from collections import namedtuple import random - -from utils import argmax +import itertools +import copy +from utils import argmax, vector_add infinity = float('inf') GameState = namedtuple('GameState', 'to_move, utility, board, moves') @@ -40,6 +41,47 @@ def min_value(state): # ______________________________________________________________________________ +def expectiminimax(state, game): + """Returns the best move for a player after dice are thrown. The game tree + includes chance nodes along with min and max nodes. [Figure 5.11]""" + player = game.to_move(state) + + def max_value(state): + if game.terminal_test(state): + return game.utility(state, player) + v = -infinity + for a in game.actions(state): + v = max(v, chance_node(state, a)) + return v + + def min_value(state): + if game.terminal_test(state): + return game.utility(state, player) + v = infinity + for a in game.actions(state): + v = min(v, chance_node(state, a)) + return v + + def chance_node(state, action): + res_state = game.result(state, action) + sum_chances = 0 + num_chances = 21 + dice_rolls = list(itertools.combinations_with_replacement([1, 2, 3, 4, 5, 6], 2)) + if res_state.to_move == 'W': + for val in dice_rolls: + game.dice_roll = (-val[0], -val[1]) + sum_chances += max_value(res_state) * (1/36 if val[0] == val[1] else 1/18) + elif res_state.to_move == 'B': + for val in dice_rolls: + game.dice_roll = val + sum_chances += min_value(res_state) * (1/36 if val[0] == val[1] else 1/18) + + return sum_chances / num_chances + + # Body of expectiminimax: + return argmax(game.actions(state), + key=lambda a: chance_node(state, a)) + def alphabeta_search(state, game): """Search game to determine best action; use alpha-beta pruning. @@ -155,6 +197,9 @@ def random_player(game, state): def alphabeta_player(game, state): return alphabeta_search(state, game) +def expectiminimax_player(game, state): + return expectiminimax(state, game) + # ______________________________________________________________________________ # Some Sample Games @@ -342,3 +387,162 @@ def __init__(self, h=7, v=6, k=4): def actions(self, state): return [(x, y) for (x, y) in state.moves if y == 1 or (x, y - 1) in state.board] + + +class Backgammon(Game): + """A two player game where the goal of each player is to move all the + checkers off the board. The moves for each state are determined by + rolling a pair of dice.""" + + def __init__(self): + self.dice_roll = (-random.randint(1, 6), -random.randint(1, 6)) + board = Board() + self.initial = GameState(to_move='W', + utility=0, board=board, moves=self.get_all_moves(board, 'W')) + + def actions(self, state): + """Returns a list of legal moves for a state.""" + player = state.to_move + moves = state.moves + legal_moves = [] + for move in moves: + board = copy.deepcopy(state.board) + if board.is_legal_move(move, self.dice_roll, player): + legal_moves.append(move) + return legal_moves + + def result(self, state, move): + board = copy.deepcopy(state.board) + player = state.to_move + board.move_checker(move[0], self.dice_roll[0], player) + board.move_checker(move[1], self.dice_roll[1], player) + to_move = ('W' if player == 'B' else 'B') + return GameState(to_move=to_move, + utility=self.compute_utility(board, move, to_move), + board=board, + moves=self.get_all_moves(board, to_move)) + + + def utility(self, state, player): + """Return the value to player; 1 for win, -1 for loss, 0 otherwise.""" + return state.utility if player == 'W' else -state.utility + + def terminal_test(self, state): + """A state is terminal if one player wins.""" + return state.utility != 0 + + def get_all_moves(self, board, player): + """All possible moves for a player i.e. all possible ways of + choosing two checkers of a player from the board for a move + at a given state.""" + all_points = board.points + taken_points = [index for index, point in enumerate(all_points) + if point.checkers[player] > 0] + moves = list(itertools.permutations(taken_points, 2)) + moves = moves + [(index, index) for index, point in enumerate(all_points) + if point.checkers[player] >= 2] + return moves + + def display(self, state): + """Display state of the game.""" + board = state.board + player = state.to_move + for index, point in enumerate(board.points): + if point.checkers['W'] != 0 or point.checkers['B'] != 0: + print("Point : ", index, " W : ", point.checkers['W'], " B : ", point.checkers['B']) + print("player : ", player) + + + def compute_utility(self, board, move, player): + """If 'W' wins with this move, return 1; if 'B' wins return -1; else return 0.""" + count = 0 + for idx in range(0, 24): + count = count + board.points[idx].checkers[player] + if player == 'W' and count == 0: + return 1 + if player == 'B' and count == 0: + return -1 + return 0 + + +class Board: + """The board consists of 24 points. Each player('W' and 'B') initially + has 15 checkers on board. Player 'W' moves from point 23 to point 0 + and player 'B' moves from point 0 to 23. Points 0-7 are + home for player W and points 17-24 are home for B.""" + + def __init__(self): + """Initial state of the game""" + # TODO : Add bar to Board class where a blot is placed when it is hit. + self.points = [Point() for index in range(24)] + self.points[0].checkers['B'] = self.points[23].checkers['W'] = 2 + self.points[5].checkers['W'] = self.points[18].checkers['B'] = 5 + self.points[7].checkers['W'] = self.points[16].checkers['B'] = 3 + self.points[11].checkers['B'] = self.points[12].checkers['W'] = 5 + self.allow_bear_off = {'W': False, 'B': False} + + def checkers_at_home(self, player): + """Returns the no. of checkers at home for a player.""" + sum_range = range(0, 7) if player == 'W' else range(17, 24) + count = 0 + for idx in sum_range: + count = count + self.points[idx].checkers[player] + return count + + def is_legal_move(self, start, steps, player): + """Move is a tuple which contains starting points of checkers to be + moved during a player's turn. An on-board move is legal if both the destinations + are open. A bear-off move is the one where a checker is moved off-board. + It is legal only after a player has moved all his checkers to his home.""" + dest1, dest2 = vector_add(start, steps) + dest_range = range(0, 24) + move1_legal = move2_legal = False + if dest1 in dest_range: + if self.points[dest1].is_open_for(player): + self.move_checker(start[0], steps[0], player) + move1_legal = True + else: + if self.allow_bear_off[player]: + self.move_checker(start[0], steps[0], player) + move1_legal = True + if not move1_legal: + return False + if dest2 in dest_range: + if self.points[dest2].is_open_for(player): + move2_legal = True + else: + if self.allow_bear_off[player]: + move2_legal = True + return move1_legal and move2_legal + + def move_checker(self, start, steps, player): + """Moves a checker from starting point by a given number of steps""" + dest = start + steps + dest_range = range(0, 24) + self.points[start].remove_checker(player) + if dest in dest_range: + self.points[dest].add_checker(player) + if self.checkers_at_home(player) == 15: + self.allow_bear_off[player] = True + +class Point: + """A point is one of the 24 triangles on the board where + the players' checkers are placed.""" + + def __init__(self): + self.checkers = {'W':0, 'B':0} + + def is_open_for(self, player): + """A point is open for a player if the no. of opponent's + checkers already present on it is 0 or 1. A player can + move a checker to a point only if it is open.""" + opponent = 'B' if player == 'W' else 'W' + return self.checkers[opponent] <= 1 + + def add_checker(self, player): + """Place a player's checker on a point.""" + self.checkers[player] += 1 + + def remove_checker(self, player): + """Remove a player's checker from a point.""" + self.checkers[player] -= 1 From 2e2cd77e70bb424615ed75a4dd91f0fd80608b97 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Fri, 2 Mar 2018 00:50:46 +0000 Subject: [PATCH 050/269] Added section on Hill Climbing (#787) * Added section on Hill Climbing * Added images * Updated README.md --- README.md | 2 +- images/hillclimb-tsp.png | Bin 0 -> 32028 bytes search.ipynb | 1006 +++++++++++++++++++++++++++++++++++++- 3 files changed, 984 insertions(+), 24 deletions(-) create mode 100644 images/hillclimb-tsp.png diff --git a/README.md b/README.md index 2dcf7d368..fc5f38bb5 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 3.22 | Best-First-Search | `best_first_graph_search` | [`search.py`][search] | Done | Included | | 3.24 | A\*-Search | `astar_search` | [`search.py`][search] | Done | Included | | 3.26 | Recursive-Best-First-Search | `recursive_best_first_search` | [`search.py`][search] | Done | | -| 4.2 | Hill-Climbing | `hill_climbing` | [`search.py`][search] | Done | | +| 4.2 | Hill-Climbing | `hill_climbing` | [`search.py`][search] | Done | Included | | 4.5 | Simulated-Annealing | `simulated_annealing` | [`search.py`][search] | Done | | | 4.8 | Genetic-Algorithm | `genetic_algorithm` | [`search.py`][search] | Done | Included | | 4.11 | And-Or-Graph-Search | `and_or_graph_search` | [`search.py`][search] | Done | | diff --git a/images/hillclimb-tsp.png b/images/hillclimb-tsp.png new file mode 100644 index 0000000000000000000000000000000000000000..8446bbafc45203f5a29feb1218a793c9583e8866 GIT binary patch literal 32028 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
    O# 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\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    class Problem(object):\n",
    +       "\n",
    +       "    """The abstract class for a formal problem. You should subclass\n",
    +       "    this and implement the methods actions and result, and possibly\n",
    +       "    __init__, goal_test, and path_cost. Then you will create instances\n",
    +       "    of your subclass and solve them with the various search functions."""\n",
    +       "\n",
    +       "    def __init__(self, initial, goal=None):\n",
    +       "        """The constructor specifies the initial state, and possibly a goal\n",
    +       "        state, if there is a unique goal. Your subclass's constructor can add\n",
    +       "        other arguments."""\n",
    +       "        self.initial = initial\n",
    +       "        self.goal = goal\n",
    +       "\n",
    +       "    def actions(self, state):\n",
    +       "        """Return the actions that can be executed in the given\n",
    +       "        state. The result would typically be a list, but if there are\n",
    +       "        many actions, consider yielding them one at a time in an\n",
    +       "        iterator, rather than building them all at once."""\n",
    +       "        raise NotImplementedError\n",
    +       "\n",
    +       "    def result(self, state, action):\n",
    +       "        """Return the state that results from executing the given\n",
    +       "        action in the given state. The action must be one of\n",
    +       "        self.actions(state)."""\n",
    +       "        raise NotImplementedError\n",
    +       "\n",
    +       "    def goal_test(self, state):\n",
    +       "        """Return True if the state is a goal. The default method compares the\n",
    +       "        state to self.goal or checks for state in self.goal if it is a\n",
    +       "        list, as specified in the constructor. Override this method if\n",
    +       "        checking against a single self.goal is not enough."""\n",
    +       "        if isinstance(self.goal, list):\n",
    +       "            return is_in(state, self.goal)\n",
    +       "        else:\n",
    +       "            return state == self.goal\n",
    +       "\n",
    +       "    def path_cost(self, c, state1, action, state2):\n",
    +       "        """Return the cost of a solution path that arrives at state2 from\n",
    +       "        state1 via action, assuming cost c to get up to state1. If the problem\n",
    +       "        is such that the path doesn't matter, this function will only look at\n",
    +       "        state2.  If the path does matter, it will consider c and maybe state1\n",
    +       "        and action. The default method costs 1 for every step in the path."""\n",
    +       "        return c + 1\n",
    +       "\n",
    +       "    def value(self, state):\n",
    +       "        """For optimization problems, each state has a value.  Hill-climbing\n",
    +       "        and related algorithms try to maximize this value."""\n",
    +       "        raise NotImplementedError\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "%psource Problem" + "psource(Problem)" ] }, { @@ -128,13 +276,173 @@ }, { "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": true - }, - "outputs": [], + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    class Node:\n",
    +       "\n",
    +       "    """A node in a search tree. Contains a pointer to the parent (the node\n",
    +       "    that this is a successor of) and to the actual state for this node. Note\n",
    +       "    that if a state is arrived at by two paths, then there are two nodes with\n",
    +       "    the same state.  Also includes the action that got us to this state, and\n",
    +       "    the total path_cost (also known as g) to reach the node.  Other functions\n",
    +       "    may add an f and h value; see best_first_graph_search and astar_search for\n",
    +       "    an explanation of how the f and h values are handled. You will not need to\n",
    +       "    subclass this class."""\n",
    +       "\n",
    +       "    def __init__(self, state, parent=None, action=None, path_cost=0):\n",
    +       "        """Create a search tree Node, derived from a parent by an action."""\n",
    +       "        self.state = state\n",
    +       "        self.parent = parent\n",
    +       "        self.action = action\n",
    +       "        self.path_cost = path_cost\n",
    +       "        self.depth = 0\n",
    +       "        if parent:\n",
    +       "            self.depth = parent.depth + 1\n",
    +       "\n",
    +       "    def __repr__(self):\n",
    +       "        return "<Node {}>".format(self.state)\n",
    +       "\n",
    +       "    def __lt__(self, node):\n",
    +       "        return self.state < node.state\n",
    +       "\n",
    +       "    def expand(self, problem):\n",
    +       "        """List the nodes reachable in one step from this node."""\n",
    +       "        return [self.child_node(problem, action)\n",
    +       "                for action in problem.actions(self.state)]\n",
    +       "\n",
    +       "    def child_node(self, problem, action):\n",
    +       "        """[Figure 3.10]"""\n",
    +       "        next = problem.result(self.state, action)\n",
    +       "        return Node(next, self, action,\n",
    +       "                    problem.path_cost(self.path_cost, self.state,\n",
    +       "                                      action, next))\n",
    +       "\n",
    +       "    def solution(self):\n",
    +       "        """Return the sequence of actions to go from the root to this node."""\n",
    +       "        return [node.action for node in self.path()[1:]]\n",
    +       "\n",
    +       "    def path(self):\n",
    +       "        """Return a list of nodes forming the path from the root to this node."""\n",
    +       "        node, path_back = self, []\n",
    +       "        while node:\n",
    +       "            path_back.append(node)\n",
    +       "            node = node.parent\n",
    +       "        return list(reversed(path_back))\n",
    +       "\n",
    +       "    # We want for a queue of nodes in breadth_first_search or\n",
    +       "    # astar_search to have no duplicated states, so we treat nodes\n",
    +       "    # with the same state as equal. [Problem: this may not be what you\n",
    +       "    # want in other contexts.]\n",
    +       "\n",
    +       "    def __eq__(self, other):\n",
    +       "        return isinstance(other, Node) and self.state == other.state\n",
    +       "\n",
    +       "    def __hash__(self):\n",
    +       "        return hash(self.state)\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "%psource Node" + "psource(Node)" ] }, { @@ -171,13 +479,150 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    class GraphProblem(Problem):\n",
    +       "\n",
    +       "    """The problem of searching a graph from one node to another."""\n",
    +       "\n",
    +       "    def __init__(self, initial, goal, graph):\n",
    +       "        Problem.__init__(self, initial, goal)\n",
    +       "        self.graph = graph\n",
    +       "\n",
    +       "    def actions(self, A):\n",
    +       "        """The actions at a graph node are just its neighbors."""\n",
    +       "        return list(self.graph.get(A).keys())\n",
    +       "\n",
    +       "    def result(self, state, action):\n",
    +       "        """The result of going to a neighbor is just that neighbor."""\n",
    +       "        return action\n",
    +       "\n",
    +       "    def path_cost(self, cost_so_far, A, action, B):\n",
    +       "        return cost_so_far + (self.graph.get(A, B) or infinity)\n",
    +       "\n",
    +       "    def find_min_edge(self):\n",
    +       "        """Find minimum value of edges."""\n",
    +       "        m = infinity\n",
    +       "        for d in self.graph.dict.values():\n",
    +       "            local_min = min(d.values())\n",
    +       "            m = min(m, local_min)\n",
    +       "\n",
    +       "        return m\n",
    +       "\n",
    +       "    def h(self, node):\n",
    +       "        """h function is straight-line distance from a node's state to goal."""\n",
    +       "        locs = getattr(self.graph, 'locations', None)\n",
    +       "        if locs:\n",
    +       "            if type(node) is str:\n",
    +       "                return int(distance(locs[node], locs[self.goal]))\n",
    +       "\n",
    +       "            return int(distance(locs[node.state], locs[self.goal]))\n",
    +       "        else:\n",
    +       "            return infinity\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "%psource GraphProblem" + "psource(GraphProblem)" ] }, { @@ -484,13 +929,146 @@ }, { "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": true - }, - "outputs": [], + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    class SimpleProblemSolvingAgentProgram:\n",
    +       "\n",
    +       "    """Abstract framework for a problem-solving agent. [Figure 3.1]"""\n",
    +       "\n",
    +       "    def __init__(self, initial_state=None):\n",
    +       "        """State is an abstract representation of the state\n",
    +       "        of the world, and seq is the list of actions required\n",
    +       "        to get to a particular state from the initial state(root)."""\n",
    +       "        self.state = initial_state\n",
    +       "        self.seq = []\n",
    +       "\n",
    +       "    def __call__(self, percept):\n",
    +       "        """[Figure 3.1] Formulate a goal and problem, then\n",
    +       "        search for a sequence of actions to solve it."""\n",
    +       "        self.state = self.update_state(self.state, percept)\n",
    +       "        if not self.seq:\n",
    +       "            goal = self.formulate_goal(self.state)\n",
    +       "            problem = self.formulate_problem(self.state, goal)\n",
    +       "            self.seq = self.search(problem)\n",
    +       "            if not self.seq:\n",
    +       "                return None\n",
    +       "        return self.seq.pop(0)\n",
    +       "\n",
    +       "    def update_state(self, percept):\n",
    +       "        raise NotImplementedError\n",
    +       "\n",
    +       "    def formulate_goal(self, state):\n",
    +       "        raise NotImplementedError\n",
    +       "\n",
    +       "    def formulate_problem(self, state, goal):\n",
    +       "        raise NotImplementedError\n",
    +       "\n",
    +       "    def search(self, problem):\n",
    +       "        raise NotImplementedError\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "%psource SimpleProblemSolvingAgentProgram" + "psource(SimpleProblemSolvingAgentProgram)" ] }, { @@ -1482,6 +2060,388 @@ "puzzle.solve([2,4,3,1,5,6,7,8,0], [1,2,3,4,5,6,7,8,0],sqrt_manhanttan) # Sqrt_manhattan" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## HILL CLIMBING\n", + "\n", + "Hill Climbing is a heuristic search used for optimization problems.\n", + "Given a large set of inputs and a good heuristic function, it tries to find a sufficiently good solution to the problem. \n", + "This solution may or may not be the global optimum.\n", + "The algorithm is a variant of generate and test algorithm. \n", + "
    \n", + "As a whole, the algorithm works as follows:\n", + "- Evaluate the initial state.\n", + "- If it is equal to the goal state, return.\n", + "- Find a neighboring state (one which is heuristically similar to the current state)\n", + "- Evaluate this state. If it is closer to the goal state than before, replace the initial state with this state and repeat these steps.\n", + "
    " + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def hill_climbing(problem):\n",
    +       "    """From the initial node, keep choosing the neighbor with highest value,\n",
    +       "    stopping when no neighbor is better. [Figure 4.2]"""\n",
    +       "    current = Node(problem.initial)\n",
    +       "    while True:\n",
    +       "        neighbors = current.expand(problem)\n",
    +       "        if not neighbors:\n",
    +       "            break\n",
    +       "        neighbor = argmax_random_tie(neighbors,\n",
    +       "                                     key=lambda node: problem.value(node.state))\n",
    +       "        if problem.value(neighbor.state) <= problem.value(current.state):\n",
    +       "            break\n",
    +       "        current = neighbor\n",
    +       "    return current.state\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(hill_climbing)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will find an approximate solution to the traveling salespersons problem using this algorithm.\n", + "
    \n", + "We need to define a class for this problem.\n", + "
    \n", + "`Problem` will be used as a base class." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class TSP_problem(Problem):\n", + "\n", + " \"\"\" subclass of Problem to define various functions \"\"\"\n", + "\n", + " def two_opt(self, state):\n", + " \"\"\" Neighbour generating function for Traveling Salesman Problem \"\"\"\n", + " neighbour_state = state[:]\n", + " left = random.randint(0, len(neighbour_state) - 1)\n", + " right = random.randint(0, len(neighbour_state) - 1)\n", + " if left > right:\n", + " left, right = right, left\n", + " neighbour_state[left: right + 1] = reversed(neighbour_state[left: right + 1])\n", + " return neighbour_state\n", + "\n", + " def actions(self, state):\n", + " \"\"\" action that can be excuted in given state \"\"\"\n", + " return [self.two_opt]\n", + "\n", + " def result(self, state, action):\n", + " \"\"\" result after applying the given action on the given state \"\"\"\n", + " return action(state)\n", + "\n", + " def path_cost(self, c, state1, action, state2):\n", + " \"\"\" total distance for the Traveling Salesman to be covered if in state2 \"\"\"\n", + " cost = 0\n", + " for i in range(len(state2) - 1):\n", + " cost += distances[state2[i]][state2[i + 1]]\n", + " cost += distances[state2[0]][state2[-1]]\n", + " return cost\n", + "\n", + " def value(self, state):\n", + " \"\"\" value of path cost given negative for the given state \"\"\"\n", + " return -1 * self.path_cost(None, None, None, state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will use cities from the Romania map as our cities for this problem.\n", + "
    \n", + "A list of all cities and a dictionary storing distances between them will be populated." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['Arad', 'Bucharest', 'Craiova', 'Drobeta', 'Eforie', 'Fagaras', 'Giurgiu', 'Hirsova', 'Iasi', 'Lugoj', 'Mehadia', 'Neamt', 'Oradea', 'Pitesti', 'Rimnicu', 'Sibiu', 'Timisoara', 'Urziceni', 'Vaslui', 'Zerind']\n" + ] + } + ], + "source": [ + "distances = {}\n", + "all_cities = []\n", + "\n", + "for city in romania_map.locations.keys():\n", + " distances[city] = {}\n", + " all_cities.append(city)\n", + " \n", + "all_cities.sort()\n", + "print(all_cities)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to populate the individual lists inside the dictionary with the manhattan distance between the cities." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "for name_1, coordinates_1 in romania_map.locations.items():\n", + " for name_2, coordinates_2 in romania_map.locations.items():\n", + " distances[name_1][name_2] = np.linalg.norm(\n", + " [coordinates_1[0] - coordinates_2[0], coordinates_1[1] - coordinates_2[1]])\n", + " distances[name_2][name_1] = np.linalg.norm(\n", + " [coordinates_1[0] - coordinates_2[0], coordinates_1[1] - coordinates_2[1]])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The way neighbours are chosen currently isn't suitable for the travelling salespersons problem.\n", + "We need a neighboring state that is similar in total path distance to the current state.\n", + "
    \n", + "We need to change the function that finds neighbors." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def hill_climbing(problem):\n", + " \n", + " \"\"\"From the initial node, keep choosing the neighbor with highest value,\n", + " stopping when no neighbor is better. [Figure 4.2]\"\"\"\n", + " \n", + " def find_neighbors(state, number_of_neighbors=100):\n", + " \"\"\" finds neighbors using two_opt method \"\"\"\n", + " \n", + " neighbors = []\n", + " \n", + " for i in range(number_of_neighbors):\n", + " new_state = problem.two_opt(state)\n", + " neighbors.append(Node(new_state))\n", + " state = new_state\n", + " \n", + " return neighbors\n", + "\n", + " # as this is a stochastic algorithm, we will set a cap on the number of iterations\n", + " iterations = 10000\n", + " \n", + " current = Node(problem.initial)\n", + " while iterations:\n", + " neighbors = find_neighbors(current.state)\n", + " if not neighbors:\n", + " break\n", + " neighbor = argmax_random_tie(neighbors,\n", + " key=lambda node: problem.value(node.state))\n", + " if problem.value(neighbor.state) <= problem.value(current.state):\n", + " current.state = neighbor.state\n", + " iterations -= 1\n", + " \n", + " return current.state" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An instance of the TSP_problem class will be created." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "tsp = TSP_problem(all_cities)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now generate an approximate solution to the problem by calling `hill_climbing`.\n", + "The results will vary a bit each time you run it." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Fagaras',\n", + " 'Neamt',\n", + " 'Iasi',\n", + " 'Vaslui',\n", + " 'Hirsova',\n", + " 'Eforie',\n", + " 'Urziceni',\n", + " 'Bucharest',\n", + " 'Giurgiu',\n", + " 'Pitesti',\n", + " 'Craiova',\n", + " 'Drobeta',\n", + " 'Mehadia',\n", + " 'Lugoj',\n", + " 'Timisoara',\n", + " 'Arad',\n", + " 'Zerind',\n", + " 'Oradea',\n", + " 'Sibiu',\n", + " 'Rimnicu']" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hill_climbing(tsp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The solution looks like this.\n", + "It is not difficult to see why this might be a good solution.\n", + "
    \n", + "![title](images/hillclimb-tsp.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, From 3f888808bea2e6f27f8e6ab16bfe0100f7605d71 Mon Sep 17 00:00:00 2001 From: Nouman Ahmed <35970677+Noumanmufc1@users.noreply.github.com> Date: Fri, 2 Mar 2018 05:53:52 +0500 Subject: [PATCH 051/269] Added test for simpleProblemSolvingAgentProgram (#784) * Added test for simpleProblemSolvingAgent * Some Style fixes * Fixed update_state in test_search.py --- tests/test_search.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/test_search.py b/tests/test_search.py index 04cb2db35..23f8b0f43 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -201,6 +201,50 @@ def GA_GraphColoringInts(edges, fitness): return genetic_algorithm(population, fitness) +def test_simpleProblemSolvingAgent(): + class vacuumAgent(SimpleProblemSolvingAgentProgram): + def update_state(self, state, percept): + return percept + + def formulate_goal(self, state): + goal = [state7, state8] + return goal + + def formulate_problem(self, state, goal): + problem = state + return problem + + def search(self, problem): + if problem == state1: + seq = ["Suck", "Right", "Suck"] + elif problem == state2: + seq = ["Suck", "Left", "Suck"] + elif problem == state3: + seq = ["Right", "Suck"] + elif problem == state4: + seq = ["Suck"] + elif problem == state5: + seq = ["Suck"] + elif problem == state6: + seq = ["Left", "Suck"] + return seq + + state1 = [(0, 0), [(0, 0), "Dirty"], [(1, 0), ["Dirty"]]] + state2 = [(1, 0), [(0, 0), "Dirty"], [(1, 0), ["Dirty"]]] + state3 = [(0, 0), [(0, 0), "Clean"], [(1, 0), ["Dirty"]]] + state4 = [(1, 0), [(0, 0), "Clean"], [(1, 0), ["Dirty"]]] + state5 = [(0, 0), [(0, 0), "Dirty"], [(1, 0), ["Clean"]]] + state6 = [(1, 0), [(0, 0), "Dirty"], [(1, 0), ["Clean"]]] + state7 = [(0, 0), [(0, 0), "Clean"], [(1, 0), ["Clean"]]] + state8 = [(1, 0), [(0, 0), "Clean"], [(1, 0), ["Clean"]]] + + a = vacuumAgent(state1) + + assert a(state6) == "Left" + assert a(state1) == "Suck" + assert a(state3) == "Right" + + # TODO: for .ipynb: """ From f44631dc1415fd33ee56790903c5742fc70bae0a Mon Sep 17 00:00:00 2001 From: Aabir Abubaker Kar <16526730+bakerwho@users.noreply.github.com> Date: Thu, 1 Mar 2018 21:52:29 -0500 Subject: [PATCH 052/269] Fix MDP class and add POMDP subclass and notebook (#781) * Fixed typos and added inline LaTeX * Fixed backslash for inline LaTeX * Fixed more backslashes * generalised MDP class and created POMDP notebook * Fixed consistency issues with base MDP class * Small fix on CustomMDP * Set default args to pass tests * Added TableDrivenAgentProgram tests (#777) * Add tests for TableDrivenAgentProgram * Add tests for TableDrivenAgentProgram * Check environment status at every step * Check environment status at every step of TableDrivenAgentProgram * Fixing tests * fixed test_rl * removed redundant code, fixed a comment --- mdp.ipynb | 1573 ++++++++++----------------------------------- mdp.py | 100 ++- pomdp.ipynb | 240 +++++++ rl.ipynb | 127 ++-- rl.py | 17 +- tests/test_mdp.py | 30 +- tests/test_rl.py | 3 +- 7 files changed, 761 insertions(+), 1329 deletions(-) create mode 100644 pomdp.ipynb diff --git a/mdp.ipynb b/mdp.ipynb index 910b49040..4c44ff9d8 100644 --- a/mdp.ipynb +++ b/mdp.ipynb @@ -1,7 +1,7 @@ { "cells": [ { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "# Markov decision processes (MDPs)\n", @@ -10,19 +10,24 @@ ] }, { - "cell_type": "code", - "execution_count": 1, +<<<<<<< HEAD + "cell_type": "raw", "metadata": { "collapsed": true }, +======= + "cell_type": "code", + "execution_count": null, + "metadata": {}, "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "from mdp import *\n", "from notebook import psource, pseudocode" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "## CONTENTS\n", @@ -36,7 +41,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "## OVERVIEW\n", @@ -56,7 +61,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "## MDP\n", @@ -65,162 +70,21 @@ ] }, { +<<<<<<< HEAD + "cell_type": "raw", + "metadata": {}, +======= "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
    class MDP:\n",
    -       "\n",
    -       "    """A Markov Decision Process, defined by an initial state, transition model,\n",
    -       "    and reward function. We also keep track of a gamma value, for use by\n",
    -       "    algorithms. The transition model is represented somewhat differently from\n",
    -       "    the text. Instead of P(s' | s, a) being a probability number for each\n",
    -       "    state/state/action triplet, we instead have T(s, a) return a\n",
    -       "    list of (p, s') pairs. We also keep track of the possible states,\n",
    -       "    terminal states, and actions for each state. [page 646]"""\n",
    -       "\n",
    -       "    def __init__(self, init, actlist, terminals, transitions={}, states=None, gamma=.9):\n",
    -       "        if not (0 < gamma <= 1):\n",
    -       "            raise ValueError("An MDP must have 0 < gamma <= 1")\n",
    -       "\n",
    -       "        if states:\n",
    -       "            self.states = states\n",
    -       "        else:\n",
    -       "            self.states = set()\n",
    -       "        self.init = init\n",
    -       "        self.actlist = actlist\n",
    -       "        self.terminals = terminals\n",
    -       "        self.transitions = transitions\n",
    -       "        self.gamma = gamma\n",
    -       "        self.reward = {}\n",
    -       "\n",
    -       "    def R(self, state):\n",
    -       "        """Return a numeric reward for this state."""\n",
    -       "        return self.reward[state]\n",
    -       "\n",
    -       "    def T(self, state, action):\n",
    -       "        """Transition model. From a state and an action, return a list\n",
    -       "        of (probability, result-state) pairs."""\n",
    -       "        if(self.transitions == {}):\n",
    -       "            raise ValueError("Transition model is missing")\n",
    -       "        else:\n",
    -       "            return self.transitions[state][action]\n",
    -       "\n",
    -       "    def actions(self, state):\n",
    -       "        """Set of actions that can be performed in this state. By default, a\n",
    -       "        fixed list of actions, except for terminal states. Override this\n",
    -       "        method if you need to specialize by state."""\n",
    -       "        if state in self.terminals:\n",
    -       "            return [None]\n",
    -       "        else:\n",
    -       "            return self.actlist\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "psource(MDP)" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "The **_ _init_ _** method takes in the following parameters:\n", @@ -238,7 +102,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "Now let us implement the simple MDP in the image below. States A, B have actions X, Y available in them. Their probabilities are shown just above the arrows. We start with using MDP as base class for our CustomMDP. Obviously we need to make a few changes to suit our case. We make use of a transition matrix as our transitions are not very simple.\n", @@ -246,22 +110,29 @@ ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 3, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 +======= + "execution_count": null, +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "metadata": { "collapsed": true }, - "outputs": [], "source": [ - "# Transition Matrix as nested dict. State -> Actions in state -> States by each action -> Probabilty\n", + "# Transition Matrix as nested dict. State -> Actions in state -> List of (Probability, State) tuples\n", "t = {\n", " \"A\": {\n", - " \"X\": {\"A\":0.3, \"B\":0.7},\n", - " \"Y\": {\"A\":1.0}\n", + " \"X\": [(0.3, \"A\"), (0.7, \"B\")],\n", + " \"Y\": [(1.0, \"A\")]\n", " },\n", " \"B\": {\n", - " \"X\": {\"End\":0.8, \"B\":0.2},\n", - " \"Y\": {\"A\":1.0}\n", + " \"X\": {(0.8, \"End\"), (0.2, \"B\")},\n", + " \"Y\": {(1.0, \"A\")}\n", " },\n", " \"End\": {}\n", "}\n", @@ -278,62 +149,72 @@ ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 4, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 +======= + "execution_count": null, +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "metadata": { "collapsed": true }, - "outputs": [], "source": [ "class CustomMDP(MDP):\n", - "\n", - " def __init__(self, transition_matrix, rewards, terminals, init, gamma=.9):\n", + " def __init__(self, init, terminals, transition_matrix, reward = None, gamma=.9):\n", " # All possible actions.\n", " actlist = []\n", " for state in transition_matrix.keys():\n", " actlist.extend(transition_matrix[state])\n", " actlist = list(set(actlist))\n", - "\n", - " MDP.__init__(self, init, actlist, terminals=terminals, gamma=gamma)\n", - " self.t = transition_matrix\n", - " self.reward = rewards\n", - " for state in self.t:\n", - " self.states.add(state)\n", + " MDP.__init__(self, init, actlist, terminals, transition_matrix, reward, gamma=gamma)\n", "\n", " def T(self, state, action):\n", " if action is None:\n", " return [(0.0, state)]\n", " else: \n", - " return [(prob, new_state) for new_state, prob in self.t[state][action].items()]" + " return self.t[state][action]" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "Finally we instantize the class with the parameters for our MDP in the picture." ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 5, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "metadata": { "collapsed": true }, +======= + "execution_count": null, + "metadata": {}, "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ - "our_mdp = CustomMDP(t, rewards, terminals, init, gamma=.9)" + "our_mdp = CustomMDP(init, terminals, t, rewards, gamma=.9)" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "With this we have successfully represented our MDP. Later we will look at ways to solve this MDP." ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "## GRID MDP\n", @@ -342,160 +223,21 @@ ] }, { +<<<<<<< HEAD + "cell_type": "raw", + "metadata": {}, +======= "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
    class GridMDP(MDP):\n",
    -       "\n",
    -       "    """A two-dimensional grid MDP, as in [Figure 17.1]. All you have to do is\n",
    -       "    specify the grid as a list of lists of rewards; use None for an obstacle\n",
    -       "    (unreachable state). Also, you should specify the terminal states.\n",
    -       "    An action is an (x, y) unit vector; e.g. (1, 0) means move east."""\n",
    -       "\n",
    -       "    def __init__(self, grid, terminals, init=(0, 0), gamma=.9):\n",
    -       "        grid.reverse()  # because we want row 0 on bottom, not on top\n",
    -       "        MDP.__init__(self, init, actlist=orientations,\n",
    -       "                     terminals=terminals, gamma=gamma)\n",
    -       "        self.grid = grid\n",
    -       "        self.rows = len(grid)\n",
    -       "        self.cols = len(grid[0])\n",
    -       "        for x in range(self.cols):\n",
    -       "            for y in range(self.rows):\n",
    -       "                self.reward[x, y] = grid[y][x]\n",
    -       "                if grid[y][x] is not None:\n",
    -       "                    self.states.add((x, y))\n",
    -       "\n",
    -       "    def T(self, state, action):\n",
    -       "        if action is None:\n",
    -       "            return [(0.0, state)]\n",
    -       "        else:\n",
    -       "            return [(0.8, self.go(state, action)),\n",
    -       "                    (0.1, self.go(state, turn_right(action))),\n",
    -       "                    (0.1, self.go(state, turn_left(action)))]\n",
    -       "\n",
    -       "    def go(self, state, direction):\n",
    -       "        """Return the state that results from going in this direction."""\n",
    -       "        state1 = vector_add(state, direction)\n",
    -       "        return state1 if state1 in self.states else state\n",
    -       "\n",
    -       "    def to_grid(self, mapping):\n",
    -       "        """Convert a mapping from (x, y) to v into a [[..., v, ...]] grid."""\n",
    -       "        return list(reversed([[mapping.get((x, y), None)\n",
    -       "                               for x in range(self.cols)]\n",
    -       "                              for y in range(self.rows)]))\n",
    -       "\n",
    -       "    def to_arrows(self, policy):\n",
    -       "        chars = {\n",
    -       "            (1, 0): '>', (0, 1): '^', (-1, 0): '<', (0, -1): 'v', None: '.'}\n",
    -       "        return self.to_grid({s: chars[a] for (s, a) in policy.items()})\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "psource(GridMDP)" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "The **_ _init_ _** method takes **grid** as an extra parameter compared to the MDP class. The grid is a nested list of rewards in states.\n", @@ -510,7 +252,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "We can create a GridMDP like the one in **Fig 17.1** as follows: \n", @@ -524,9 +266,11 @@ ] }, { +<<<<<<< HEAD "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, +<<<<<<< HEAD "outputs": [ { "data": { @@ -539,12 +283,19 @@ "output_type": "execute_result" } ], +======= + "cell_type": "raw", + "metadata": {}, +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 +======= + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "sequential_decision_environment" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": { "collapsed": true }, @@ -553,7 +304,11 @@ "\n", "Now that we have looked how to represent MDPs. Let's aim at solving them. Our ultimate goal is to obtain an optimal policy. We start with looking at Value Iteration and a visualisation that should help us understanding it better.\n", "\n", +<<<<<<< HEAD "We start by calculating Value/Utility for each of the states. The Value of each state is the expected sum of discounted future rewards given we start in that state and follow a particular policy $pi$. The value or the utility of a state is given by\n", +======= + "We start by calculating Value/Utility for each of the states. The Value of each state is the expected sum of discounted future rewards given we start in that state and follow a particular policy $\\pi$. The value or the utility of a state is given by\n", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "\n", "$$U(s)=R(s)+\\gamma\\max_{a\\epsilon A(s)}\\sum_{s'} P(s'\\ |\\ s,a)U(s')$$\n", "\n", @@ -561,130 +316,21 @@ ] }, { +<<<<<<< HEAD + "cell_type": "raw", + "metadata": {}, +======= "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
    def value_iteration(mdp, epsilon=0.001):\n",
    -       "    """Solving an MDP by value iteration. [Figure 17.4]"""\n",
    -       "    U1 = {s: 0 for s in mdp.states}\n",
    -       "    R, T, gamma = mdp.R, mdp.T, mdp.gamma\n",
    -       "    while True:\n",
    -       "        U = U1.copy()\n",
    -       "        delta = 0\n",
    -       "        for s in mdp.states:\n",
    -       "            U1[s] = R(s) + gamma * max([sum([p * U[s1] for (p, s1) in T(s, a)])\n",
    -       "                                        for a in mdp.actions(s)])\n",
    -       "            delta = max(delta, abs(U1[s] - U[s]))\n",
    -       "        if delta < epsilon * (1 - gamma) / gamma:\n",
    -       "            return U\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "psource(value_iteration)" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "It takes as inputs two parameters, an MDP to solve and epsilon, the maximum error allowed in the utility of any state. It returns a dictionary containing utilities where the keys are the states and values represent utilities.
    Value Iteration starts with arbitrary initial values for the utilities, calculates the right side of the Bellman equation and plugs it into the left hand side, thereby updating the utility of each state from the utilities of its neighbors. \n", @@ -697,11 +343,23 @@ "As you might have noticed, `value_iteration` has an infinite loop. How do we decide when to stop iterating? \n", "The concept of _contraction_ successfully explains the convergence of value iteration. \n", "Refer to **Section 17.2.3** of the book for a detailed explanation. \n", +<<<<<<< HEAD +<<<<<<< HEAD "In the algorithm, we calculate a value $\\delta$ that measures the difference in the utilities of the current time step and the previous time step. \n", +======= + "In the algorithm, we calculate a value $delta$ that measures the difference in the utilities of the current time step and the previous time step. \n", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 +======= + "In the algorithm, we calculate a value $\\delta$ that measures the difference in the utilities of the current time step and the previous time step. \n", +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "\n", "$$\\delta = \\max{(\\delta, \\begin{vmatrix}U_{i + 1}(s) - U_i(s)\\end{vmatrix})}$$\n", "\n", "This value of delta decreases as the values of $U_i$ converge.\n", +<<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "We terminate the algorithm if the $\\delta$ value is less than a threshold value determined by the hyperparameter _epsilon_.\n", "\n", "$$\\delta \\lt \\epsilon \\frac{(1 - \\gamma)}{\\gamma}$$\n", @@ -710,13 +368,25 @@ "Hence, from the properties of contractions in general, it follows that `value_iteration` always converges to a unique solution of the Bellman equations whenever $gamma$ is less than 1.\n", "We then terminate the algorithm when a reasonable approximation is achieved.\n", "In practice, it often occurs that the policy $pi$ becomes optimal long before the utility function converges. For the given 4 x 3 environment with $gamma = 0.9$, the policy $pi$ is optimal when $i = 4$ (at the 4th iteration), even though the maximum error in the utility function is stil 0.46. This can be clarified from **figure 17.6** in the book. Hence, to increase computational efficiency, we often use another method to solve MDPs called Policy Iteration which we will see in the later part of this notebook. \n", +======= + "We terminate the algorithm if the $delta$ value is less than a threshold value determined by the hyperparameter _epsilon_.\n", + "\n", + "$$\\delta \\lt \\epsilon \\frac{(1 - \\gamma)}{\\gamma}$$\n", + "\n", + "To summarize, the Bellman update is a _contraction_ by a factor of $\\gamma$ on the space of utility vectors. \n", + "Hence, from the properties of contractions in general, it follows that `value_iteration` always converges to a unique solution of the Bellman equations whenever $\\gamma$ is less than 1.\n", + "We then terminate the algorithm when a reasonable approximation is achieved.\n", + "In practice, it often occurs that the policy $\\pi$ becomes optimal long before the utility function converges. For the given 4 x 3 environment with $\gamma = 0.9$, the policy $\\pi$ is optimal when $i = 4$ (at the 4th iteration), even though the maximum error in the utility function is stil 0.46. This can be clarified from **figure 17.6** in the book. Hence, to increase computational efficiency, we often use another method to solve MDPs called Policy Iteration which we will see in the later part of this notebook. \n", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "
    For now, let us solve the **sequential_decision_environment** GridMDP using `value_iteration`." ] }, { +<<<<<<< HEAD "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, +<<<<<<< HEAD "outputs": [ { "data": { @@ -739,21 +409,30 @@ "output_type": "execute_result" } ], +======= + "cell_type": "raw", + "metadata": {}, +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 +======= + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "value_iteration(sequential_decision_environment)" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "The pseudocode for the algorithm:" ] }, { +<<<<<<< HEAD "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, +<<<<<<< HEAD "outputs": [ { "data": { @@ -786,12 +465,19 @@ "output_type": "execute_result" } ], +======= + "cell_type": "raw", + "metadata": {}, +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 +======= + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "pseudocode(\"Value-Iteration\")" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "### AIMA3e\n", @@ -815,7 +501,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "## VALUE ITERATION VISUALIZATION\n", @@ -824,12 +510,15 @@ ] }, { +<<<<<<< HEAD + "cell_type": "raw", +======= "cell_type": "code", - "execution_count": 7, + "execution_count": null, +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "metadata": { "collapsed": true }, - "outputs": [], "source": [ "def value_iteration_instru(mdp, iterations=20):\n", " U_over_time = []\n", @@ -845,19 +534,22 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "Next, we define a function to create the visualisation from the utilities returned by **value_iteration_instru**. The reader need not concern himself with the code that immediately follows as it is the usage of Matplotib with IPython Widgets. If you are interested in reading more about these visit [ipywidgets.readthedocs.io](http://ipywidgets.readthedocs.io)" ] }, { +<<<<<<< HEAD + "cell_type": "raw", +======= "cell_type": "code", - "execution_count": 8, + "execution_count": null, +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "metadata": { "collapsed": true }, - "outputs": [], "source": [ "columns = 4\n", "rows = 3\n", @@ -865,12 +557,15 @@ ] }, { +<<<<<<< HEAD + "cell_type": "raw", +======= "cell_type": "code", - "execution_count": 9, + "execution_count": null, +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "metadata": { "collapsed": true }, - "outputs": [], "source": [ "%matplotlib inline\n", "from notebook import make_plot_grid_step_function\n", @@ -879,35 +574,19 @@ ] }, { +<<<<<<< HEAD + "cell_type": "raw", + "metadata": { + "scrolled": true + }, +======= "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAATcAAADuCAYAAABcZEBhAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAADVdJREFUeJzt239o2/edx/HX9+prSRfbbQqLrK9d2iKzcporX2kcnyAH\nV0i8/JjbP7pL/MfcboGQXEaYYab5Y1cYgbZXzuFwmgbcCyX5xwn0D3s4P6rQMAiInKCJ/pjDgWpk\nsL6KU9zN9Vw36WK++8OKUjeO5XWW9M17zwcY/NXnY/h834hnpUh1fN8XAFjzD9U+AACUA3EDYBJx\nA2AScQNgEnEDYBJxA2AScQNgEnEDYBJxA2BSzV+zeXZW/O8MQBmtrXWqfYTg8/0VDYlXbgBMIm4A\nTCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBM\nIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwi\nbgBMCmzcfN9Xb+8BxWIRtbc/p3T6ypL7rl79RBs3tigWi6i394B831+03t/fp9paR1NTU5U4dsUw\nn9KY0f39XNL3Jf3wPuu+pAOSIpKek/TNyZ2Q1Fz4OVHGM/6tAhu3ROKcxsYySqcz6u8fUE/PviX3\n9fTs05Ej7yudzmhsLKMLF84X13K5CV28mFBT05OVOnbFMJ/SmNH9vSbp/DLr5yRlCj8Dku5M7g+S\nfiPp/ySlCr//sWyn/NsENm5nzgyrq6tbjuOora1d09PTmpy8vmjP5OR1zczMqK2tXY7jqKurWyMj\nQ8X1gwd7dOjQO3Icp9LHLzvmUxozur9/lbRumfVhSd2SHEntkqYlXZf0kaTNhb99vPD7cpGspsDG\nLZ/35LpNxWvXbVQ+7y2xp7F4HQ7f3TMyMqxw2FVLS6wyB64w5lMaM/ruPElN37huLDx2v8eDqKba\nByiHubk59fW9qaGhRLWPEkjMpzRm9OAL1Cu3gYGjisdbFY+3KhRqkOdNFNc8L6dw2F20Pxx25Xm5\n4nU+v7Anmx3T+HhW8XhM0ehT8rycNm16XjduTFbsXsqB+ZTGjFaHK2niG9e5wmP3ezyIAhW3PXv2\nK5lMK5lMa8eOlzU4eFK+7yuVuqz6+nqFQg2L9odCDaqrq1MqdVm+72tw8KS2b39J0WiLstnPNDo6\nrtHRcbluoy5duqL160NVurPVwXxKY0aro1PSSS18anpZUr2kBkkdkhJa+BDhj4XfO6p0xlIC+7a0\no2ObEomzisUiWrPmUR079kFxLR5vVTKZliQdPvye9u59TTdvfqXNm7dqy5at1TpyRTGf0pjR/XVJ\n+p2kKS38u9lvJP25sLZX0jZJZ7XwVZBHJd2Z3DpJ/ylpQ+H6DS3/wUQ1Od/+Ts9yZme18s0A/mpr\na219KlsWvr+iIQXqbSkArBbiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk\n4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTi\nBsAk4gbAJOIGwCTiBsAk4gbAJOIGwKSaah/AkrXf86t9hMCb/dKp9hECzRHPoVJWOiFeuQEwibgB\nMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEw\nibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJ\nuAEwKbBx831fvb0HFItF1N7+nNLpK0vuu3r1E23c2KJYLKLe3gPyfX/Ren9/n2prHU1NTVXi2BVz\n/vx5/eDZZxVpbtbbb799z/qtW7e0c9cuRZqbtbG9XePj48W1t956S5HmZv3g2Wf10UcfVfDUlcVz\nqJT/l/Qvkh6R9N/L7MtK2igpImmnpK8Lj98qXEcK6+PlOuh3Eti4JRLnNDaWUTqdUX//gHp69i25\nr6dnn44ceV/pdEZjYxlduHC+uJbLTejixYSamp6s1LErYn5+Xvt/8QudO3tW10ZHNXjqlK5du7Zo\nz/Hjx/X4Y4/p00xGPb/8pV4/eFCSdO3aNZ06fVqjv/+9zp87p//Yv1/z8/PVuI2y4zlUyjpJ/ZJ+\nVWLf65J6JH0q6XFJxwuPHy9cf1pYf708x/yOAhu3M2eG1dXVLcdx1NbWrunpaU1OXl+0Z3LyumZm\nZtTW1i7HcdTV1a2RkaHi+sGDPTp06B05jlPp45dVKpVSJBLRM888o4cffli7du7U8PDwoj3Dv/2t\nXn31VUnSK6+8oo8//li+72t4eFi7du7UI488oqefflqRSESpVKoat1F2PIdK+b6kDZL+cZk9vqSL\nkl4pXL8q6c58hgvXKqx/XNgfDIGNWz7vyXWbiteu26h83ltiT2PxOhy+u2dkZFjhsKuWllhlDlxB\nnuepqfHufTc2NsrzvHv3NC3Mr6amRvX19fr8888XPS5Jja57z99awXNoNXwu6TFJNYXrRkl3ZuhJ\nujPfGkn1hf3BUFN6y4Nnbm5OfX1vamgoUe2j4AHFc+jBF6hXbgMDRxWPtyoeb1Uo1CDPmyiueV5O\n4bC7aH847MrzcsXrfH5hTzY7pvHxrOLxmKLRp+R5OW3a9Lxu3Jis2L2Uk+u6msjdve9cLifXde/d\nM7Ewv9u3b+uLL77QE088sehxScp53j1/+yDjOVTKUUmthZ/8CvY/IWla0u3CdU7SnRm6ku7M97ak\nLwr7gyFQcduzZ7+SybSSybR27HhZg4Mn5fu+UqnLqq+vVyjUsGh/KNSguro6pVKX5fu+BgdPavv2\nlxSNtiib/Uyjo+MaHR2X6zbq0qUrWr8+VKU7W10bNmxQJpNRNpvV119/rVOnT6uzs3PRns4f/1gn\nTpyQJH344Yd68cUX5TiOOjs7der0ad26dUvZbFaZTEZtbW3VuI2y4DlUyn5J6cJPeAX7HUn/JunD\nwvUJSS8Vfu8sXKuw/mJhfzAE9m1pR8c2JRJnFYtFtGbNozp27IPiWjzeqmQyLUk6fPg97d37mm7e\n/EqbN2/Vli1bq3XkiqmpqdG7R46o40c/0vz8vH7+s58pGo3qjTfe0AsvvKDOzk7t3r1bP+3uVqS5\nWevWrdOpwUFJUjQa1b//5Cf6p2hUNTU1Ovruu3rooYeqfEflwXOolElJL0ia0cLrnP+RdE1SnaRt\nkv5XCwH8L0m7JP1a0j9L2l34+92SfqqFr4Ksk3Sqgmcvzfn2d3qWMzsboI9CAmjt9xhPKbNfBue/\n7EFUW1vtEwSf76/s5WGg3pYCwGohbgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwi\nbgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJu\nAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEyqqfYBLJn90qn2EfCA+9Ofqn0CO3jlBsAk4gbAJOIG\nwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbA\nJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk\n4gbApMDGzfd99fYeUCwWUXv7c0qnryy57+rVT7RxY4tisYh6ew/I9/1F6/39faqtdTQ1NVWJY1cM\n8ymNGS3P+nwCG7dE4pzGxjJKpzPq7x9QT8++Jff19OzTkSPvK53OaGwsowsXzhfXcrkJXbyYUFPT\nk5U6dsUwn9KY0fKszyewcTtzZlhdXd1yHEdtbe2anp7W5OT1RXsmJ69rZmZGbW3tchxHXV3dGhkZ\nKq4fPNijQ4fekeM4lT5+2TGf0pjR8qzPJ7Bxy+c9uW5T8dp1G5XPe0vsaSxeh8N394yMDCscdtXS\nEqvMgSuM+ZTGjJZnfT411T5AOczNzamv700NDSWqfZRAYj6lMaPlPQjzCdQrt4GBo4rHWxWPtyoU\napDnTRTXPC+ncNhdtD8cduV5ueJ1Pr+wJ5sd0/h4VvF4TNHoU/K8nDZtel43bkxW7F7KgfmUxoyW\n9/c0n0DFbc+e/Uom00om09qx42UNDp6U7/tKpS6rvr5eoVDDov2hUIPq6uqUSl2W7/saHDyp7dtf\nUjTaomz2M42Ojmt0dFyu26hLl65o/fpQle5sdTCf0pjR8v6e5hPYt6UdHduUSJxVLBbRmjWP6tix\nD4pr8Xirksm0JOnw4fe0d+9runnzK23evFVbtmyt1pErivmUxoyWZ30+zre/s7Kc2VmtfDMAlMHa\ntVrRR7OBelsKAKuFuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4\nATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgB\nMIm4ATCJuAEwibgBMIm4ATDJ8X2/2mcAgFXHKzcAJhE3ACYRNwAmETcAJhE3ACYRNwAmETcAJhE3\nACYRNwAmETcAJv0F9s8EDYqi1wAAAAAASUVORK5CYII=\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Widget Javascript not detected. It may not be installed or enabled properly.\n" - ] - }, - { - "data": {}, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "import ipywidgets as widgets\n", "from IPython.display import display\n", @@ -926,14 +605,14 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "Move the slider above to observe how the utility changes across iterations. It is also possible to move the slider using arrow keys or to jump to the value by directly editing the number with a double click. The **Visualize Button** will automatically animate the slider for you. The **Extra Delay Box** allows you to set time delay in seconds upto one second for each time step. There is also an interactive editor for grid-world problems `grid_mdp.py` in the gui folder for you to play around with." ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": { "collapsed": true }, @@ -960,244 +639,35 @@ ] }, { +<<<<<<< HEAD + "cell_type": "raw", + "metadata": {}, +======= "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
    def expected_utility(a, s, U, mdp):\n",
    -       "    """The expected utility of doing a in state s, according to the MDP and U."""\n",
    -       "    return sum([p * U[s1] for (p, s1) in mdp.T(s, a)])\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "psource(expected_utility)" ] }, { +<<<<<<< HEAD + "cell_type": "raw", + "metadata": {}, +======= "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
    def policy_iteration(mdp):\n",
    -       "    """Solve an MDP by policy iteration [Figure 17.7]"""\n",
    -       "    U = {s: 0 for s in mdp.states}\n",
    -       "    pi = {s: random.choice(mdp.actions(s)) for s in mdp.states}\n",
    -       "    while True:\n",
    -       "        U = policy_evaluation(pi, U, mdp)\n",
    -       "        unchanged = True\n",
    -       "        for s in mdp.states:\n",
    -       "            a = argmax(mdp.actions(s), key=lambda a: expected_utility(a, s, U, mdp))\n",
    -       "            if a != pi[s]:\n",
    -       "                pi[s] = a\n",
    -       "                unchanged = False\n",
    -       "        if unchanged:\n",
    -       "            return pi\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "psource(policy_iteration)" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "
    Fortunately, it is not necessary to do _exact_ policy evaluation. \n", @@ -1210,164 +680,46 @@ ] }, { +<<<<<<< HEAD + "cell_type": "raw", + "metadata": {}, +======= "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
    def policy_evaluation(pi, U, mdp, k=20):\n",
    -       "    """Return an updated utility mapping U from each state in the MDP to its\n",
    -       "    utility, using an approximation (modified policy iteration)."""\n",
    -       "    R, T, gamma = mdp.R, mdp.T, mdp.gamma\n",
    -       "    for i in range(k):\n",
    -       "        for s in mdp.states:\n",
    -       "            U[s] = R(s) + gamma * sum([p * U[s1] for (p, s1) in T(s, pi[s])])\n",
    -       "    return U\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "psource(policy_evaluation)" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "Let us now solve **`sequential_decision_environment`** using `policy_iteration`." ] }, { +<<<<<<< HEAD + "cell_type": "raw", + "metadata": {}, +======= "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{(0, 0): (0, 1),\n", - " (0, 1): (0, 1),\n", - " (0, 2): (1, 0),\n", - " (1, 0): (1, 0),\n", - " (1, 2): (1, 0),\n", - " (2, 0): (0, 1),\n", - " (2, 1): (0, 1),\n", - " (2, 2): (1, 0),\n", - " (3, 0): (-1, 0),\n", - " (3, 1): None,\n", - " (3, 2): None}" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "policy_iteration(sequential_decision_environment)" ] }, { +<<<<<<< HEAD "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, +<<<<<<< HEAD "outputs": [ { "data": { @@ -1400,12 +752,23 @@ "output_type": "execute_result" } ], +======= + "cell_type": "raw", + "metadata": {}, +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 +======= + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "pseudocode('Policy-Iteration')" ] }, { +<<<<<<< HEAD "cell_type": "markdown", +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "metadata": {}, "source": [ "### AIMA3e\n", @@ -1429,7 +792,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": { "collapsed": true }, @@ -1456,131 +819,32 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "These properties of the agent are called the transition properties and are hardcoded into the GridMDP class as you can see below." ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 12, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
        def T(self, state, action):\n",
    -       "        if action is None:\n",
    -       "            return [(0.0, state)]\n",
    -       "        else:\n",
    -       "            return [(0.8, self.go(state, action)),\n",
    -       "                    (0.1, self.go(state, turn_right(action))),\n",
    -       "                    (0.1, self.go(state, turn_left(action)))]\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], +======= + "execution_count": null, + "metadata": {}, + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "psource(GridMDP.T)" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "To completely define our task environment, we need to specify the utility function for the agent. \n", @@ -1609,121 +873,25 @@ ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 13, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
        def to_arrows(self, policy):\n",
    -       "        chars = {\n",
    -       "            (1, 0): '>', (0, 1): '^', (-1, 0): '<', (0, -1): 'v', None: '.'}\n",
    -       "        return self.to_grid({s: chars[a] for (s, a) in policy.items()})\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], +======= + "execution_count": null, + "metadata": {}, + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "psource(GridMDP.to_arrows)" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "This method directly encodes the actions that the agent can take (described above) to characters representing arrows and shows it in a grid format for human visalization purposes. \n", @@ -1731,129 +899,32 @@ ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 14, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
        def to_grid(self, mapping):\n",
    -       "        """Convert a mapping from (x, y) to v into a [[..., v, ...]] grid."""\n",
    -       "        return list(reversed([[mapping.get((x, y), None)\n",
    -       "                               for x in range(self.cols)]\n",
    -       "                              for y in range(self.rows)]))\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], +======= + "execution_count": null, + "metadata": {}, + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "psource(GridMDP.to_grid)" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "Now that we have all the tools required and a good understanding of the agent and the environment, we consider some cases and see how the agent should behave for each case." ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "### Case 1\n", @@ -1862,12 +933,19 @@ ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 15, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 +======= + "execution_count": null, +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "metadata": { "collapsed": true }, - "outputs": [], "source": [ "# Note that this environment is also initialized in mdp.py by default\n", "sequential_decision_environment = GridMDP([[-0.04, -0.04, -0.04, +1],\n", @@ -1877,7 +955,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "We will use the `best_policy` function to find the best policy for this environment.\n", @@ -1887,45 +965,51 @@ ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 16, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 +======= + "execution_count": null, +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "metadata": { "collapsed": true }, - "outputs": [], "source": [ "pi = best_policy(sequential_decision_environment, value_iteration(sequential_decision_environment, .001))" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "We can now use the `to_arrows` method to see how our agent should pick its actions in the environment." ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 17, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "> > > .\n", - "^ None ^ .\n", - "^ > ^ <\n" - ] - } - ], +======= + "execution_count": null, + "metadata": {}, + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "from utils import print_table\n", "print_table(sequential_decision_environment.to_arrows(pi))" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "This is exactly the output we expected\n", @@ -1937,7 +1021,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "### Case 2\n", @@ -1946,12 +1030,19 @@ ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 18, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 +======= + "execution_count": null, +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "metadata": { "collapsed": true }, - "outputs": [], "source": [ "sequential_decision_environment = GridMDP([[-0.4, -0.4, -0.4, +1],\n", " [-0.4, None, -0.4, -1],\n", @@ -1960,20 +1051,19 @@ ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 19, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "> > > .\n", - "^ None ^ .\n", - "^ > ^ <\n" - ] - } - ], +======= + "execution_count": null, + "metadata": {}, + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "pi = best_policy(sequential_decision_environment, value_iteration(sequential_decision_environment, .001))\n", "from utils import print_table\n", @@ -1981,7 +1071,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "This is exactly the output we expected\n", @@ -1989,7 +1079,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "As the reward for each state is now more negative, life is certainly more unpleasant.\n", @@ -1997,7 +1087,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "### Case 3\n", @@ -2006,12 +1096,19 @@ ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 20, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 +======= + "execution_count": null, +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "metadata": { "collapsed": true }, - "outputs": [], "source": [ "sequential_decision_environment = GridMDP([[-4, -4, -4, +1],\n", " [-4, None, -4, -1],\n", @@ -2020,20 +1117,19 @@ ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 21, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "> > > .\n", - "^ None > .\n", - "> > > ^\n" - ] - } - ], +======= + "execution_count": null, + "metadata": {}, + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "pi = best_policy(sequential_decision_environment, value_iteration(sequential_decision_environment, .001))\n", "from utils import print_table\n", @@ -2041,7 +1137,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "This is exactly the output we expected\n", @@ -2049,14 +1145,14 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "The living reward for each state is now lower than the least rewarding terminal. Life is so _painful_ that the agent heads for the nearest exit as even the worst exit is less painful than any living state." ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "### Case 4\n", @@ -2065,12 +1161,19 @@ ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 22, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 +======= + "execution_count": null, +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "metadata": { "collapsed": true }, - "outputs": [], "source": [ "sequential_decision_environment = GridMDP([[4, 4, 4, +1],\n", " [4, None, 4, -1],\n", @@ -2079,20 +1182,19 @@ ] }, { +<<<<<<< HEAD "cell_type": "code", +<<<<<<< HEAD "execution_count": 23, +======= + "cell_type": "raw", +>>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "> > < .\n", - "> None < .\n", - "> > > v\n" - ] - } - ], +======= + "execution_count": null, + "metadata": {}, + "outputs": [], +>>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "pi = best_policy(sequential_decision_environment, value_iteration(sequential_decision_environment, .001))\n", "from utils import print_table\n", @@ -2100,7 +1202,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "In this case, the output we expect is\n", @@ -2117,7 +1219,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "---\n", @@ -2149,15 +1251,6 @@ "Green shades indicate positive utilities and brown shades indicate negative utilities. \n", "The values of the utility function and arrow diagram will pop up in separate dialogs after the algorithm converges." ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/mdp.py b/mdp.py index 6637108e5..9dcbd781a 100644 --- a/mdp.py +++ b/mdp.py @@ -21,20 +21,36 @@ class MDP: list of (p, s') pairs. We also keep track of the possible states, terminal states, and actions for each state. [page 646]""" - def __init__(self, init, actlist, terminals, transitions={}, states=None, gamma=.9): + def __init__(self, init, actlist, terminals, transitions = {}, reward = None, states=None, gamma=.9): if not (0 < gamma <= 1): raise ValueError("An MDP must have 0 < gamma <= 1") if states: self.states = states else: - self.states = set() + ## collect states from transitions table + self.states = self.get_states_from_transitions(transitions) + + self.init = init - self.actlist = actlist + + if isinstance(actlist, list): + ## if actlist is a list, all states have the same actions + self.actlist = actlist + elif isinstance(actlist, dict): + ## if actlist is a dict, different actions for each state + self.actlist = actlist + self.terminals = terminals self.transitions = transitions + if self.transitions == {}: + print("Warning: Transition table is empty.") self.gamma = gamma - self.reward = {} + if reward: + self.reward = reward + else: + self.reward = {s : 0 for s in self.states} + #self.check_consistency() def R(self, state): """Return a numeric reward for this state.""" @@ -57,6 +73,34 @@ def actions(self, state): else: return self.actlist + def get_states_from_transitions(self, transitions): + if isinstance(transitions, dict): + s1 = set(transitions.keys()) + s2 = set([tr[1] for actions in transitions.values() + for effects in actions.values() for tr in effects]) + return s1.union(s2) + else: + print('Could not retrieve states from transitions') + return None + + def check_consistency(self): + # check that all states in transitions are valid + assert set(self.states) == self.get_states_from_transitions(self.transitions) + # check that init is a valid state + assert self.init in self.states + # check reward for each state + #assert set(self.reward.keys()) == set(self.states) + assert set(self.reward.keys()) == set(self.states) + # check that all terminals are valid states + assert all([t in self.states for t in self.terminals]) + # check that probability distributions for all actions sum to 1 + for s1, actions in self.transitions.items(): + for a in actions.keys(): + s = 0 + for o in actions[a]: + s += o[0] + assert abs(s - 1) < 0.001 + class GridMDP(MDP): @@ -67,25 +111,41 @@ class GridMDP(MDP): def __init__(self, grid, terminals, init=(0, 0), gamma=.9): grid.reverse() # because we want row 0 on bottom, not on top - MDP.__init__(self, init, actlist=orientations, - terminals=terminals, gamma=gamma) - self.grid = grid + reward = {} + states = set() self.rows = len(grid) self.cols = len(grid[0]) + self.grid = grid for x in range(self.cols): for y in range(self.rows): - self.reward[x, y] = grid[y][x] if grid[y][x] is not None: - self.states.add((x, y)) - - def T(self, state, action): + states.add((x, y)) + reward[(x, y)] = grid[y][x] + self.states = states + actlist = orientations + transitions = {} + for s in states: + transitions[s] = {} + for a in actlist: + transitions[s][a] = self.calculate_T(s, a) + MDP.__init__(self, init, actlist=actlist, + terminals=terminals, transitions = transitions, + reward = reward, states = states, gamma=gamma) + + def calculate_T(self, state, action): if action is None: return [(0.0, state)] else: return [(0.8, self.go(state, action)), (0.1, self.go(state, turn_right(action))), (0.1, self.go(state, turn_left(action)))] - + + def T(self, state, action): + if action is None: + return [(0.0, state)] + else: + return self.transitions[state][action] + def go(self, state, direction): """Return the state that results from going in this direction.""" state1 = vector_add(state, direction) @@ -192,3 +252,19 @@ def policy_evaluation(pi, U, mdp, k=20): ^ None ^ . ^ > ^ < """ # noqa + +""" +s = { 'a' : { 'plan1' : [(0.2, 'a'), (0.3, 'b'), (0.3, 'c'), (0.2, 'd')], + 'plan2' : [(0.4, 'a'), (0.15, 'b'), (0.45, 'c')], + 'plan3' : [(0.2, 'a'), (0.5, 'b'), (0.3, 'c')], + }, + 'b' : { 'plan1' : [(0.2, 'a'), (0.6, 'b'), (0.2, 'c'), (0.1, 'd')], + 'plan2' : [(0.6, 'a'), (0.2, 'b'), (0.1, 'c'), (0.1, 'd')], + 'plan3' : [(0.3, 'a'), (0.3, 'b'), (0.4, 'c')], + }, + 'c' : { 'plan1' : [(0.3, 'a'), (0.5, 'b'), (0.1, 'c'), (0.1, 'd')], + 'plan2' : [(0.5, 'a'), (0.3, 'b'), (0.1, 'c'), (0.1, 'd')], + 'plan3' : [(0.1, 'a'), (0.3, 'b'), (0.1, 'c'), (0.5, 'd')], + }, + } +""" \ No newline at end of file diff --git a/pomdp.ipynb b/pomdp.ipynb new file mode 100644 index 000000000..1c8391818 --- /dev/null +++ b/pomdp.ipynb @@ -0,0 +1,240 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Partially Observable Markov decision processes (POMDPs)\n", + "\n", + "This Jupyter notebook acts as supporting material for POMDPs, covered in **Chapter 17 Making Complex Decisions** of the book* Artificial Intelligence: A Modern Approach*. We make use of the implementations of POMPDPs in mdp.py module. This notebook has been separated from the notebook `mdp.py` as the topics are considerably more advanced.\n", + "\n", + "**Note that it is essential to work through and understand the mdp.ipynb notebook before diving into this one.**\n", + "\n", + "Let us import everything from the mdp module to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from mdp import *\n", + "from notebook import psource, pseudocode" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## CONTENTS\n", + "\n", + "1. Overview of MDPs\n", + "2. POMDPs - a conceptual outline\n", + "3. POMDPs - a rigorous outline\n", + "4. Value Iteration\n", + " - Value Iteration Visualization" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. OVERVIEW\n", + "\n", + "We first review Markov property and MDPs as in [Section 17.1] of the book.\n", + "\n", + "- A stochastic process is said to have the **Markov property**, or to have a **Markovian transition model** if the conditional probability distribution of future states of the process (conditional on both past and present states) depends only on the present state, not on the sequence of events that preceded it.\n", + "\n", + " -- (Source: [Wikipedia](https://en.wikipedia.org/wiki/Markov_property))\n", + "\n", + "A Markov decision process or MDP is defined as:\n", + "- a sequential decision problem for a fully observable, stochastic environment with a Markovian transition model and additive rewards.\n", + "\n", + "An MDP consists of a set of states (with an initial state $s_0$); a set $A(s)$ of actions\n", + "in each state; a transition model $P(s' | s, a)$; and a reward function $R(s)$.\n", + "\n", + "The MDP seeks to make sequential decisions to occupy states so as to maximise some combination of the reward function $R(s)$.\n", + "\n", + "The characteristic problem of the MDP is hence to identify the optimal policy function $\\pi^*(s)$ that provides the _utility-maximising_ action $a$ to be taken when the current state is $s$.\n", + "\n", + "### Belief vector\n", + "\n", + "**Note**: The book refers to the _belief vector_ as the _belief state_. We use the latter terminology here to retain our ability to refer to the belief vector as a _probability distribution over states_.\n", + "\n", + "The solution of an MDP is subject to certain properties of the problem which are assumed and justified in [Section 17.1]. One critical assumption is that the agent is **fully aware of its current state at all times**.\n", + "\n", + "A tedious (but rewarding, as we will see) way of expressing this is in terms of the **belief vector** $b$ of the agent. The belief vector is a function mapping states to probabilities or certainties of being in those states.\n", + "\n", + "Consider an agent that is fully aware that it is in state $s_i$ in the statespace $(s_1, s_2, ... s_n)$ at the current time.\n", + "\n", + "Its belief vector is the vector $(b(s_1), b(s_2), ... b(s_n))$ given by the function $b(s)$:\n", + "\\begin{align*}\n", + "b(s) &= 0 \\quad \\text{if }s \\neq s_i \\\\ &= 1 \\quad \\text{if } s = s_i\n", + "\\end{align*}\n", + "\n", + "Note that $b(s)$ is a probability distribution that necessarily sums to $1$ over all $s$.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "## 2. POMDPs - a conceptual outline\n", + "\n", + "The POMDP really has only two modifications to the **problem formulation** compared to the MDP.\n", + "\n", + "- **Belief state** - In the real world, the current state of an agent is often not known with complete certainty. This makes the concept of a belief vector extremely relevant. It allows the agent to represent different degrees of certainty with which it _believes_ it is in each state.\n", + "\n", + "- **Evidence percepts** - In the real world, agents often have certain kinds of evidence, collected from sensors. They can use the probability distribution of observed evidence, conditional on state, to consolidate their information. This is a known distribution $P(e\\ |\\ s)$ - $e$ being an evidence, and $s$ being the state it is conditional on.\n", + "\n", + "Consider the world we used for the MDP. \n", + "\n", + "![title](images/grid_mdp.jpg)\n", + "\n", + "#### Using the belief vector\n", + "An agent beginning at $(1, 1)$ may not be certain that it is indeed in $(1, 1)$. Consider a belief vector $b$ such that:\n", + "\\begin{align*}\n", + " b((1,1)) &= 0.8 \\\\\n", + " b((2,1)) &= 0.1 \\\\\n", + " b((1,2)) &= 0.1 \\\\\n", + " b(s) &= 0 \\quad \\quad \\forall \\text{ other } s\n", + "\\end{align*}\n", + "\n", + "By horizontally catenating each row, we can represent this as an 11-dimensional vector (omitting $(2, 2)$).\n", + "\n", + "Thus, taking $s_1 = (1, 1)$, $s_2 = (1, 2)$, ... $s_{11} = (4,3)$, we have $b$:\n", + "\n", + "$b = (0.8, 0.1, 0, 0, 0.1, 0, 0, 0, 0, 0, 0)$ \n", + "\n", + "This fully represents the certainty to which the agent is aware of its state.\n", + "\n", + "#### Using evidence\n", + "The evidence observed here could be the number of adjacent 'walls' or 'dead ends' observed by the agent. We assume that the agent cannot 'orient' the walls - only count them.\n", + "\n", + "In this case, $e$ can take only two values, 1 and 2. This gives $P(e\\ |\\ s)$ as:\n", + "\\begin{align*}\n", + " P(e=2\\ |\\ s) &= \\frac{1}{7} \\quad \\forall \\quad s \\in \\{s_1, s_2, s_4, s_5, s_8, s_9, s_{11}\\}\\\\\n", + " P(e=1\\ |\\ s) &= \\frac{1}{4} \\quad \\forall \\quad s \\in \\{s_3, s_6, s_7, s_{10}\\} \\\\\n", + " P(e\\ |\\ s) &= 0 \\quad \\forall \\quad \\text{ other } s, e\n", + "\\end{align*}\n", + "\n", + "Note that the implications of the evidence on the state must be known **a priori** to the agent. Ways of reliably learning this distribution from percepts are beyond the scope of this notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. POMDPs - a rigorous outline\n", + "\n", + "A POMDP is thus a sequential decision problem for for a *partially* observable, stochastic environment with a Markovian transition model, a known 'sensor model' for inferring state from observation, and additive rewards. \n", + "\n", + "Practically, a POMDP has the following, which an MDP also has:\n", + "- a set of states, each denoted by $s$\n", + "- a set of actions available in each state, $A(s)$\n", + "- a reward accrued on attaining some state, $R(s)$\n", + "- a transition probability $P(s'\\ |\\ s, a)$ of action $a$ changing the state from $s$ to $s'$\n", + "\n", + "And the following, which an MDP does not:\n", + "- a sensor model $P(e\\ |\\ s)$ on evidence conditional on states\n", + "\n", + "Additionally, the POMDP is now uncertain of its current state hence has:\n", + "- a belief vector $b$ representing the certainty of being in each state (as a probability distribution)\n", + "\n", + "\n", + "#### New uncertainties\n", + "\n", + "It is useful to intuitively appreciate the new uncertainties that have arisen in the agent's awareness of its own state.\n", + "\n", + "- At any point, the agent has belief vector $b$, the distribution of its believed likelihood of being in each state $s$.\n", + "- For each of these states $s$ that the agent may **actually** be in, it has some set of actions given by $A(s)$.\n", + "- Each of these actions may transport it to some other state $s'$, assuming an initial state $s$, with probability $P(s'\\ |\\ s, a)$\n", + "- Once the action is performed, the agent receives a percept $e$. $P(e\\ |\\ s)$ now tells it the chances of having perceived $e$ for each state $s$. The agent must use this information to update its new belief state appropriately.\n", + "\n", + "#### Evolution of the belief vector - the `FORWARD` function\n", + "\n", + "The new belief vector $b'(s')$ after an action $a$ on the belief vector $b(s)$ and the noting of evidence $e$ is:\n", + "$$ b'(s') = \\alpha P(e\\ |\\ s') \\sum_s P(s'\\ | s, a) b(s)$$ \n", + "\n", + "where $\\alpha$ is a normalising constant (to retain the interpretation of $b$ as a probability distribution.\n", + "\n", + "This equation is just counts the sum of likelihoods of going to a state $s'$ from every possible state $s$, times the initial likelihood of being in each $s$. This is multiplied by the likelihood that the known evidence actually implies the new state $s'$. \n", + "\n", + "This function is represented as `b' = FORWARD(b, a, e)`\n", + "\n", + "#### Probability distribution of the evolving belief vector\n", + "\n", + "The goal here is to find $P(b'\\ |\\ b, a)$ - the probability that action $a$ transforms belief vector $b$ into belief vector $b'$. The following steps illustrate this -\n", + "\n", + "The probability of observing evidence $e$ when action $a$ is enacted on belief vector $b$ can be distributed over each possible new state $s'$ resulting from it:\n", + "\\begin{align*}\n", + " P(e\\ |\\ b, a) &= \\sum_{s'} P(e\\ |\\ b, a, s') P(s'\\ |\\ b, a) \\\\\n", + " &= \\sum_{s'} P(e\\ |\\ s') P(s'\\ |\\ b, a) \\\\\n", + " &= \\sum_{s'} P(e\\ |\\ s') \\sum_s P(s'\\ |\\ s, a) b(s)\n", + "\\end{align*}\n", + "\n", + "The probability of getting belief vector $b'$ from $b$ by application of action $a$ can thus be summed over all possible evidences $e$:\n", + "\\begin{align*}\n", + " P(b'\\ |\\ b, a) &= \\sum_{e} P(b'\\ |\\ b, a, e) P(e\\ |\\ b, a) \\\\\n", + " &= \\sum_{e} P(b'\\ |\\ b, a, e) \\sum_{s'} P(e\\ |\\ s') \\sum_s P(s'\\ |\\ s, a) b(s)\n", + "\\end{align*}\n", + "\n", + "where $P(b'\\ |\\ b, a, e) = 1$ if $b' = $ `FORWARD(b, a, e)` and $= 0$ otherwise.\n", + "\n", + "Given initial and final belief states $b$ and $b'$, the transition probabilities still depend on the action $a$ and observed evidence $e$. Some belief states may be achievable by certain actions, but have non-zero probabilities for states prohibited by the evidence $e$. Thus, the above condition thus ensures that only valid combinations of $(b', b, a, e)$ are considered.\n", + "\n", + "#### A modified rewardspace\n", + "\n", + "For MDPs, the reward space was simple - one reward per available state. However, for a belief vector $b(s)$, the expected reward is now:\n", + "$$\\rho(b) = \\sum_s b(s) R(s)$$\n", + "\n", + "Thus, as the belief vector can take infinite values of the distribution over states, so can the reward for each belief vector vary over a hyperplane in the belief space, or space of states (planes in an $N$-dimensional space are formed by a linear combination of the axes)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/rl.ipynb b/rl.ipynb index 019bef3b7..f05613ddd 100644 --- a/rl.ipynb +++ b/rl.ipynb @@ -6,7 +6,7 @@ "source": [ "# Reinforcement Learning\n", "\n", - "This IPy notebook acts as supporting material for **Chapter 21 Reinforcement Learning** of the book* Artificial Intelligence: A Modern Approach*. This notebook makes use of the implementations in rl.py module. We also make use of implementation of MDPs in the mdp.py module to test our agents. It might be helpful if you have already gone through the IPy notebook dealing with Markov decision process. Let us import everything from the rl module. It might be helpful to view the source of some of our implementations. Please refer to the Introductory IPy file for more details." + "This Jupyter notebook acts as supporting material for **Chapter 21 Reinforcement Learning** of the book* Artificial Intelligence: A Modern Approach*. This notebook makes use of the implementations in `rl.py` module. We also make use of implementation of MDPs in the `mdp.py` module to test our agents. It might be helpful if you have already gone through the Jupyter notebook dealing with Markov decision process. Let us import everything from the `rl` module. It might be helpful to view the source of some of our implementations. Please refer to the Introductory Jupyter notebook for more details." ] }, { @@ -47,7 +47,7 @@ "\n", "-- Source: [Wikipedia](https://en.wikipedia.org/wiki/Reinforcement_learning)\n", "\n", - "In summary we have a sequence of state action transitions with rewards associated with some states. Our goal is to find the optimal policy (pi) which tells us what action to take in each state." + "In summary we have a sequence of state action transitions with rewards associated with some states. Our goal is to find the optimal policy $\\pi$ which tells us what action to take in each state." ] }, { @@ -56,7 +56,7 @@ "source": [ "## PASSIVE REINFORCEMENT LEARNING\n", "\n", - "In passive Reinforcement Learning the agent follows a fixed policy and tries to learn the Reward function and the Transition model (if it is not aware of that)." + "In passive Reinforcement Learning the agent follows a fixed policy and tries to learn the Reward function and the Transition model (if it is not aware of these)." ] }, { @@ -83,7 +83,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The Agent Program can be obtained by creating the instance of the class by passing the appropriate parameters. Because of the __ call __ method the object that is created behaves like a callable and returns an appropriate action as most Agent Programs do. To instantiate the object we need a policy(pi) and a mdp whose utility of states will be estimated. Let us import a GridMDP object from the mdp module. **Figure 17.1 (sequential_decision_environment)** is similar to **Figure 21.1** but has some discounting as **gamma = 0.9**." + "The Agent Program can be obtained by creating the instance of the class by passing the appropriate parameters. Because of the __ call __ method the object that is created behaves like a callable and returns an appropriate action as most Agent Programs do. To instantiate the object we need a policy ($\\pi$) and a mdp whose utility of states will be estimated. Let us import a `GridMDP` object from the `MDP` module. **Figure 17.1 (sequential_decision_environment)** is similar to **Figure 21.1** but has some discounting as **gamma = 0.9**." ] }, { @@ -201,7 +201,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{(0, 1): 0.3892840731173828, (1, 2): 0.6211579621949068, (3, 2): 1, (0, 0): 0.3022330060485855, (2, 0): 0.0, (3, 0): 0.0, (1, 0): 0.18020445259687815, (3, 1): -1, (2, 2): 0.822969605478094, (2, 1): -0.8456690895152308, (0, 2): 0.49454878907979766}\n" + "{(0, 1): 0.4431282384930237, (1, 2): 0.6719826603921873, (3, 2): 1, (0, 0): 0.32008510559157544, (3, 0): 0.0, (3, 1): -1, (2, 1): 0.6258841793121656, (2, 0): 0.0, (2, 2): 0.7626863051408717, (1, 0): 0.19543350078456248, (0, 2): 0.550838599140139}\n" ] } ], @@ -258,9 +258,9 @@ "outputs": [ { "data": { - "image/png": "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\nBaWUUq7/D2ktlL9G6rguAAAAAElFTkSuQmCC\n", + "image/png": "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\n6vG4hJOPymRc/4bnz2k+qvXy+3fW8fZKf/b3Te5BvD7D2j2l/PmSCU5NPHtIb+b+YFqbmqkjKZLh\naAkwUkSGikgs/o7keUHn7AROBRCRMUA80HHtQ62wm4eC171pUlOwg0JM22Oo2yXU+wzFlf4+Bbv6\n19wIGGjIzO2aQkaIVUPbwikR1fuorPU6JeC8In9NYUAbgoJ9jT4925eGSApsP7czsZW7ihmanuRk\nhuFwhipav6fymnruf39DyA7Tkso6Nu0vZ8pgf+d7QVkNNzy7lFqvr9kVOHtbmfJRfZLJbGeTYLjs\nNX7OCNGcFYrLJQzoldBi89EH6/xrfl05bXDI0XQA8R43NSF+b2+t2E18jKvZmpL9dztueHqLw58P\nhf35am64NsDry/1L0/zw5OH8aFboYcHBkuI8zP3BNO6/+OhGx+2C1e7iKt5dvddpVt6wr4x/f5pL\n/5R4Lpg0gJF9ejDrqAx+dc7YLg8IEMGagjGmXkRuBuYDbuBJY8xaEfk9kGOMmQf8DPiPiPwUfx78\nPdOJ8/NNQEdzoIZ5Co37FMKp0rpdgtfns5qPYoj1uPjgpyeGXNbCZneE7XOaj9pZUwhoPgqUV1RF\nSkJMmxZ/65sSz5CAJSEOJ/0CMg070K3fV8qEVkZJNcf+vXutPoUH3t/A01/vYFSfHk06+9fs8ddI\nTh3Th2U7i9lbUs0Oq1nux6eEHi5oFwhCrRsVKXbgbG7mfSgDeiWQ10JN4cN1+5k5Io37Ljy62XPi\nY1xNmo/qvD7eXb2X08f2bXao7Xar4/q8Sc1PkjxUMe7WO5rfXL6bcf17csfs8EY+hSoQ2PnFU19t\np7rOx/u3nsCjn2zlTWtE2O3fOspp6v3v96eG9fMiKaINV8aYd40xo4wxw40x91nH7rYCAsaYdcaY\nmcaYicaYScaYDyKZnhDpA0IEBed9///h9CnY3C6hssZLdZ3PyRRG9UlucT8AT0BNISnW3e52Vfvh\nD1wpdXdxFW8u393mZR3iY9x8evssp9PscJIU52HZr0+nR5yH3UVVVNbWk1dUxah2LoUdPCR1ldUU\nFapzz26mmmX9Xm6cuxSvz/DPyyc3W8Idmp7EiaMyuOSY9o0ma49fnT2Gd245vtEiiq1JTYqltJnF\nBrcV+Jc/P72VPpGEWHeTGtbyncUUVdZx1vjmay12k2aojvqO4gkY8h1Kbn45K/NKuHBy8HiY9km0\n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\nYII=\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -286,9 +286,9 @@ "outputs": [ { "data": { - "image/png": "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\ngohICjRr1qzY6wW6du3KwQcfXKaS/sSJE3nxxReZNGlSgeGm27Zto1mzZpWW3wQFBRGRFGjbti25\nublxYMjOzmbr1q0AbNy4kZkzZ9K3b18ALrzwQt57773dtjF16lTuuOMOMjIyaN68eYF1H3/8MQcf\nfHCl51tBQUQkRYYPH87MmTMBWLRoEUcccQQDBw5k6NChXH/99fTv3x+ArKysIvsXrrrqKr766iuG\nDRvGoEGDuOKKK+J1r7/+Oqeeemql51l9CiIiKXLllVdy9913c/LJJzNs2DCysrJ2S7N582Z69+5N\nt27ddlu3ZMmSIre7fft2MjMzueeeeyo9z6opiIikyODBgznhhBPIzc0tNk3r1q155plnyrXdlStX\nctttt5GWVvnletUURERS6Ac/+EGlb7N379707t270rcLqimIiEgSBQUREYkpKIiISExBQUREYgoK\nIiIpkjx19ty5cznyyCM56KCDGDBgAE899VSp77/rrrvo168fAwYM4KSTTmLFihUA5OTkMGLEiJTk\nWUFBRCRFkqfObt68OY8++igLFixg6tSpXHvttfH9EYpzyCGHkJmZSVZWFqNHj45nRO3YsSNdunRh\n1qxZlZ5nDUkVkbrv5Rvg83mVu829+8PI20pMkjx1dp8+feLlXbt2pVOnTuTk5Ow2aV6yE044IX4+\nZMgQHn/88fj1mWeeyaRJkzj66KMrugdFUk1BRCQFipo6O+G9995jx44d7L///mXe3kMPPaSps0VE\nKkUpJfpUKGrqbIDPPvuMCy64gEceeYQGDcpWLn/88cfJzMzkjTfeiJelaupsBQURkRQoaurszZs3\nc+qpp3LrrbcyZMiQMm1n+vTp3Hrrrbzxxhs0adIkXq6ps0VEapHCU2fv2LGDs846iwsvvJDRo0cX\nSHvjjTfy3HPP7baNOXPmcPnll5ORkUGnTp0KrNPU2SIitUzy1NlPP/00M2bMYOLEiQwaNIhBgwYx\nd+5cAObNm8fee++92/vHjRvHli1bOPfccxk0aBCjRo2K12nqbBGRWiZ56uyxY8cyduzYItPt3LmT\nI488crfl06dPL3bbGRkZPP/885WW1wTVFEREUqQsU2cDTJs2rVzbzcnJ4brrrqNt27Z7kr0iqaYg\nIpJCqZg6u2PHjpx55pmVvl1QTUFE6rAQQnVnocrt6T4rKIhIndS0aVPWr19frwJDCIH169fTtGnT\n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\nAW4Hvin6vj2yAAAAAElFTkSuQmCC\n", + "image/png": "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\nRDwKBRER8fw/mBIlJRttB04AAAAASUVORK5CYII=\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -398,44 +398,44 @@ "data": { "text/plain": [ "defaultdict(float,\n", - " {((0, 0), (-1, 0)): -0.12953971401732597,\n", - " ((0, 0), (0, -1)): -0.12753699595470713,\n", - " ((0, 0), (0, 1)): -0.01158029172666495,\n", - " ((0, 0), (1, 0)): -0.13035841083471436,\n", - " ((0, 1), (-1, 0)): -0.04,\n", - " ((0, 1), (0, -1)): -0.1057916516323444,\n", - " ((0, 1), (0, 1)): 0.13072636267769677,\n", - " ((0, 1), (1, 0)): -0.07323076923076924,\n", - " ((0, 2), (-1, 0)): 0.12165200587479848,\n", - " ((0, 2), (0, -1)): 0.09431411803674361,\n", - " ((0, 2), (0, 1)): 0.14047883620608154,\n", - " ((0, 2), (1, 0)): 0.19224095989491635,\n", - " ((1, 0), (-1, 0)): -0.09696833851887868,\n", - " ((1, 0), (0, -1)): -0.15641263417341367,\n", - " ((1, 0), (0, 1)): -0.15340385689815017,\n", - " ((1, 0), (1, 0)): -0.15224266498911238,\n", - " ((1, 2), (-1, 0)): 0.18537063683043895,\n", - " ((1, 2), (0, -1)): 0.17757702529142774,\n", - " ((1, 2), (0, 1)): 0.17562120416256435,\n", - " ((1, 2), (1, 0)): 0.27484289408254886,\n", - " ((2, 0), (-1, 0)): -0.16785234970594098,\n", - " ((2, 0), (0, -1)): -0.1448679824723624,\n", - " ((2, 0), (0, 1)): -0.028114098214323924,\n", - " ((2, 0), (1, 0)): -0.16267477943781278,\n", - " ((2, 1), (-1, 0)): -0.2301056003129034,\n", - " ((2, 1), (0, -1)): -0.4332722098873507,\n", - " ((2, 1), (0, 1)): 0.2965645851500498,\n", - " ((2, 1), (1, 0)): -0.90815406879654,\n", - " ((2, 2), (-1, 0)): 0.1905755278897695,\n", - " ((2, 2), (0, -1)): 0.07306332481110034,\n", - " ((2, 2), (0, 1)): 0.1793881607466996,\n", - " ((2, 2), (1, 0)): 0.34260576652777697,\n", - " ((3, 0), (-1, 0)): -0.16576962655130892,\n", - " ((3, 0), (0, -1)): -0.16840120349372995,\n", - " ((3, 0), (0, 1)): -0.5090288592720464,\n", - " ((3, 0), (1, 0)): -0.88375,\n", - " ((3, 1), None): -0.6897322258069369,\n", - " ((3, 2), None): 0.388990723935834})" + " {((0, 0), (-1, 0)): -0.10293706293706295,\n", + " ((0, 0), (0, -1)): -0.10590764087842354,\n", + " ((0, 0), (0, 1)): 0.05460040868097919,\n", + " ((0, 0), (1, 0)): -0.09867203219315898,\n", + " ((0, 1), (-1, 0)): 0.07177237857105365,\n", + " ((0, 1), (0, -1)): 0.060286786739471215,\n", + " ((0, 1), (0, 1)): 0.10374209705939107,\n", + " ((0, 1), (1, 0)): -0.04,\n", + " ((0, 2), (-1, 0)): 0.09308553784444584,\n", + " ((0, 2), (0, -1)): 0.09710376713758972,\n", + " ((0, 2), (0, 1)): 0.12895703412485182,\n", + " ((0, 2), (1, 0)): 0.1325347830202934,\n", + " ((1, 0), (-1, 0)): -0.07589625670469141,\n", + " ((1, 0), (0, -1)): -0.0759999433406361,\n", + " ((1, 0), (0, 1)): -0.07323076923076924,\n", + " ((1, 0), (1, 0)): 0.07539875443960498,\n", + " ((1, 2), (-1, 0)): 0.09841555812424703,\n", + " ((1, 2), (0, -1)): 0.1713989451054505,\n", + " ((1, 2), (0, 1)): 0.16142640572251182,\n", + " ((1, 2), (1, 0)): 0.19259892322613212,\n", + " ((2, 0), (-1, 0)): -0.0759999433406361,\n", + " ((2, 0), (0, -1)): -0.0759999433406361,\n", + " ((2, 0), (0, 1)): -0.08367037404281108,\n", + " ((2, 0), (1, 0)): -0.0437928007023705,\n", + " ((2, 1), (-1, 0)): -0.009680447057460156,\n", + " ((2, 1), (0, -1)): -0.6618548845169473,\n", + " ((2, 1), (0, 1)): -0.4333323454834963,\n", + " ((2, 1), (1, 0)): -0.8872940082892214,\n", + " ((2, 2), (-1, 0)): 0.1483330033351123,\n", + " ((2, 2), (0, -1)): 0.04473676319907405,\n", + " ((2, 2), (0, 1)): 0.13217540013336543,\n", + " ((2, 2), (1, 0)): 0.30829164610044535,\n", + " ((3, 0), (-1, 0)): -0.6432395354845424,\n", + " ((3, 0), (0, -1)): 0.0,\n", + " ((3, 0), (0, 1)): -0.787040488208054,\n", + " ((3, 0), (1, 0)): -0.04,\n", + " ((3, 1), None): -0.7641890167582844,\n", + " ((3, 2), None): 0.4106787728880888})" ] }, "execution_count": 15, @@ -483,17 +483,17 @@ "data": { "text/plain": [ "defaultdict(>,\n", - " {(0, 0): -0.01158029172666495,\n", - " (0, 1): 0.13072636267769677,\n", - " (0, 2): 0.19224095989491635,\n", - " (1, 0): -0.09696833851887868,\n", - " (1, 2): 0.27484289408254886,\n", - " (2, 0): -0.028114098214323924,\n", - " (2, 1): 0.2965645851500498,\n", - " (2, 2): 0.34260576652777697,\n", - " (3, 0): -0.16576962655130892,\n", - " (3, 1): -0.6897322258069369,\n", - " (3, 2): 0.388990723935834})" + " {(0, 0): 0.05460040868097919,\n", + " (0, 1): 0.10374209705939107,\n", + " (0, 2): 0.1325347830202934,\n", + " (1, 0): 0.07539875443960498,\n", + " (1, 2): 0.19259892322613212,\n", + " (2, 0): -0.0437928007023705,\n", + " (2, 1): -0.009680447057460156,\n", + " (2, 2): 0.30829164610044535,\n", + " (3, 0): 0.0,\n", + " (3, 1): -0.7641890167582844,\n", + " (3, 2): 0.4106787728880888})" ] }, "execution_count": 17, @@ -529,6 +529,15 @@ "print(value_iteration(sequential_decision_environment))" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": null, @@ -555,7 +564,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2+" + "version": "3.6.1" } }, "nbformat": 4, diff --git a/rl.py b/rl.py index 3258bfffe..94664b130 100644 --- a/rl.py +++ b/rl.py @@ -16,7 +16,7 @@ class ModelMDP(MDP): """ Class for implementing modified Version of input MDP with an editable transition model P and a custom function T. """ def __init__(self, init, actlist, terminals, gamma, states): - super().__init__(init, actlist, terminals, gamma) + super().__init__(init, actlist, terminals, states = states, gamma = gamma) nested_dict = lambda: defaultdict(nested_dict) # StackOverflow:whats-the-best-way-to-initialize-a-dict-of-dicts-in-python self.P = nested_dict() @@ -35,15 +35,17 @@ def __init__(self, pi, mdp): self.Ns1_sa = defaultdict(int) self.s = None self.a = None + self.visited = set() # keeping track of visited states def __call__(self, percept): s1, r1 = percept - self.mdp.states.add(s1) # Model keeps track of visited states. - R, P, mdp, pi = self.mdp.reward, self.mdp.P, self.mdp, self.pi + mdp = self.mdp + R, P, terminals, pi = mdp.reward, mdp.P, mdp.terminals, self.pi s, a, Nsa, Ns1_sa, U = self.s, self.a, self.Nsa, self.Ns1_sa, self.U - if s1 not in R: # Reward is only available for visted state. + if s1 not in self.visited: # Reward is only known for visited state. U[s1] = R[s1] = r1 + self.visited.add(s1) if s is not None: Nsa[(s, a)] += 1 Ns1_sa[(s1, s, a)] += 1 @@ -52,8 +54,11 @@ def __call__(self, percept): if (state, act) == (s, a) and freq != 0]: P[(s, a)][t] = Ns1_sa[(t, s, a)] / Nsa[(s, a)] - U = policy_evaluation(pi, U, mdp) - if s1 in mdp.terminals: + self.U = policy_evaluation(pi, U, mdp) + ## + ## + self.Nsa, self.Ns1_sa = Nsa, Ns1_sa + if s1 in terminals: self.s = self.a = None else: self.s, self.a = s1, self.pi[s1] diff --git a/tests/test_mdp.py b/tests/test_mdp.py index 1aed4b58f..00710bc9f 100644 --- a/tests/test_mdp.py +++ b/tests/test_mdp.py @@ -100,14 +100,22 @@ def test_best_policy(): def test_transition_model(): - transition_model = { - "A": {"a1": (0.3, "B"), "a2": (0.7, "C")}, - "B": {"a1": (0.5, "B"), "a2": (0.5, "A")}, - "C": {"a1": (0.9, "A"), "a2": (0.1, "B")}, - } - - mdp = MDP(init="A", actlist={"a1","a2"}, terminals={"C"}, states={"A","B","C"}, transitions=transition_model) - - assert mdp.T("A","a1") == (0.3, "B") - assert mdp.T("B","a2") == (0.5, "A") - assert mdp.T("C","a1") == (0.9, "A") + transition_model = { 'a' : { 'plan1' : [(0.2, 'a'), (0.3, 'b'), (0.3, 'c'), (0.2, 'd')], + 'plan2' : [(0.4, 'a'), (0.15, 'b'), (0.45, 'c')], + 'plan3' : [(0.2, 'a'), (0.5, 'b'), (0.3, 'c')], + }, + 'b' : { 'plan1' : [(0.2, 'a'), (0.6, 'b'), (0.2, 'c'), (0.1, 'd')], + 'plan2' : [(0.6, 'a'), (0.2, 'b'), (0.1, 'c'), (0.1, 'd')], + 'plan3' : [(0.3, 'a'), (0.3, 'b'), (0.4, 'c')], + }, + 'c' : { 'plan1' : [(0.3, 'a'), (0.5, 'b'), (0.1, 'c'), (0.1, 'd')], + 'plan2' : [(0.5, 'a'), (0.3, 'b'), (0.1, 'c'), (0.1, 'd')], + 'plan3' : [(0.1, 'a'), (0.3, 'b'), (0.1, 'c'), (0.5, 'd')], + }, + } + + mdp = MDP(init="a", actlist={"plan1","plan2", "plan3"}, terminals={"d"}, states={"a","b","c", "d"}, transitions=transition_model) + + assert mdp.T("a","plan3") == [(0.2, 'a'), (0.5, 'b'), (0.3, 'c')] + assert mdp.T("b","plan2") == [(0.6, 'a'), (0.2, 'b'), (0.1, 'c'), (0.1, 'd')] + assert mdp.T("c","plan1") == [(0.3, 'a'), (0.5, 'b'), (0.1, 'c'), (0.1, 'd')] diff --git a/tests/test_rl.py b/tests/test_rl.py index 05f071266..932b34ae5 100644 --- a/tests/test_rl.py +++ b/tests/test_rl.py @@ -19,11 +19,12 @@ def test_PassiveADPAgent(): agent = PassiveADPAgent(policy, sequential_decision_environment) - for i in range(75): + for i in range(100): run_single_trial(agent,sequential_decision_environment) # Agent does not always produce same results. # Check if results are good enough. + #print(agent.U[(0, 0)], agent.U[(0,1)], agent.U[(1,0)]) assert agent.U[(0, 0)] > 0.15 # In reality around 0.3 assert agent.U[(0, 1)] > 0.15 # In reality around 0.4 assert agent.U[(1, 0)] > 0 # In reality around 0.2 From 18f39373ff47b775e1c05777a2f35ec3a9977c43 Mon Sep 17 00:00:00 2001 From: Aabir Abubaker Kar <16526730+bakerwho@users.noreply.github.com> Date: Thu, 1 Mar 2018 22:44:55 -0500 Subject: [PATCH 053/269] Ignoring .DS_Store for macOS (#788) --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index af3dab103..84d9a0eea 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,7 @@ target/ # dotenv .env .idea + +# for macOS +.DS_Store +._.DS_Store From 49dee462b932c6bf95ac3608c966c9899ffd12cb Mon Sep 17 00:00:00 2001 From: Vinay Varma Date: Sat, 3 Mar 2018 01:24:09 +0530 Subject: [PATCH 054/269] Removed a repeating cell (#789) --- search.ipynb | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/search.ipynb b/search.ipynb index 2ac393ea0..a45a30ea6 100644 --- a/search.ipynb +++ b/search.ipynb @@ -803,52 +803,6 @@ " edge_labels[(node, connection)] = distance" ] }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# initialise a graph\n", - "G = nx.Graph()\n", - "\n", - "# use this while labeling nodes in the map\n", - "node_labels = dict()\n", - "# use this to modify colors of nodes while exploring the graph.\n", - "# This is the only dict we send to `show_map(node_colors)` while drawing the map\n", - "node_colors = dict()\n", - "\n", - "for n, p in romania_locations.items():\n", - " # add nodes from romania_locations\n", - " G.add_node(n)\n", - " # add nodes to node_labels\n", - " node_labels[n] = n\n", - " # node_colors to color nodes while exploring romania map\n", - " node_colors[n] = \"white\"\n", - "\n", - "# we'll save the initial node colors to a dict to use later\n", - "initial_node_colors = dict(node_colors)\n", - " \n", - "# positions for node labels\n", - "node_label_pos = { k:[v[0],v[1]-10] for k,v in romania_locations.items() }\n", - "\n", - "# use this while labeling edges\n", - "edge_labels = dict()\n", - "\n", - "# add edges between cities in romania map - UndirectedGraph defined in search.py\n", - "for node in romania_map.nodes():\n", - " connections = romania_map.get(node)\n", - " for connection in connections.keys():\n", - " distance = connections[connection]\n", - "\n", - " # add edges to the graph\n", - " G.add_edge(node, connection)\n", - " # add distances to edge_labels\n", - " edge_labels[(node, connection)] = distance" - ] - }, { "cell_type": "markdown", "metadata": {}, From efeeaf56861f9e3a97fb5b9252c62221cdc37cb4 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Sat, 3 Mar 2018 04:35:41 +0530 Subject: [PATCH 055/269] Updated index (#790) --- search.ipynb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/search.ipynb b/search.ipynb index a45a30ea6..072a20fff 100644 --- a/search.ipynb +++ b/search.ipynb @@ -37,6 +37,7 @@ "* Overview\n", "* Problem\n", "* Node\n", + "* Simple Problem Solving Agent Program\n", "* Search Algorithms Visualization\n", "* Breadth-First Tree Search\n", "* Breadth-First Search\n", @@ -44,6 +45,7 @@ "* Uniform Cost Search\n", "* Greedy Best First Search\n", "* A\\* Search\n", + "* Hill Climbing\n", "* Genetic Algorithm" ] }, From 086d4a449ac0df0b04c3bf64dbbb4f135fc8196f Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Sat, 3 Mar 2018 18:24:26 +0530 Subject: [PATCH 056/269] Updated README.md (#794) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc5f38bb5..d23cc6851 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 6.11 | Tree-CSP-Solver | `tree_csp_solver` | [`csp.py`][csp] | Done | Included | | 7 | KB | `KB` | [`logic.py`][logic] | Done | Included | | 7.1 | KB-Agent | `KB_Agent` | [`logic.py`][logic] | Done | | -| 7.7 | Propositional Logic Sentence | `Expr` | [`logic.py`][logic] | Done | | +| 7.7 | Propositional Logic Sentence | `Expr` | [`utils.py`][utils] | Done | Included | | 7.10 | TT-Entails | `tt_entails` | [`logic.py`][logic] | Done | | | 7.12 | PL-Resolution | `pl_resolution` | [`logic.py`][logic] | Done | Included | | 7.14 | Convert to CNF | `to_cnf` | [`logic.py`][logic] | Done | | From 5b9fb0c45db3df3e688b77457287c72a080d4a51 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Sun, 4 Mar 2018 00:19:58 +0530 Subject: [PATCH 057/269] Replace Point class with dict (#798) --- games.py | 55 ++++++++++++++++++++----------------------------------- 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/games.py b/games.py index be9620bd4..4868367f8 100644 --- a/games.py +++ b/games.py @@ -75,7 +75,6 @@ def chance_node(state, action): for val in dice_rolls: game.dice_roll = val sum_chances += min_value(res_state) * (1/36 if val[0] == val[1] else 1/18) - return sum_chances / num_chances # Body of expectiminimax: @@ -396,7 +395,7 @@ class Backgammon(Game): def __init__(self): self.dice_roll = (-random.randint(1, 6), -random.randint(1, 6)) - board = Board() + board = BackgammonBoard() self.initial = GameState(to_move='W', utility=0, board=board, moves=self.get_all_moves(board, 'W')) @@ -437,10 +436,10 @@ def get_all_moves(self, board, player): at a given state.""" all_points = board.points taken_points = [index for index, point in enumerate(all_points) - if point.checkers[player] > 0] + if point[player] > 0] moves = list(itertools.permutations(taken_points, 2)) moves = moves + [(index, index) for index, point in enumerate(all_points) - if point.checkers[player] >= 2] + if point[player] >= 2] return moves def display(self, state): @@ -448,8 +447,8 @@ def display(self, state): board = state.board player = state.to_move for index, point in enumerate(board.points): - if point.checkers['W'] != 0 or point.checkers['B'] != 0: - print("Point : ", index, " W : ", point.checkers['W'], " B : ", point.checkers['B']) + if point['W'] != 0 or point['B'] != 0: + print("Point : ", index, " W : ", point['W'], " B : ", point['B']) print("player : ", player) @@ -457,7 +456,7 @@ def compute_utility(self, board, move, player): """If 'W' wins with this move, return 1; if 'B' wins return -1; else return 0.""" count = 0 for idx in range(0, 24): - count = count + board.points[idx].checkers[player] + count = count + board.points[idx][player] if player == 'W' and count == 0: return 1 if player == 'B' and count == 0: @@ -465,7 +464,7 @@ def compute_utility(self, board, move, player): return 0 -class Board: +class BackgammonBoard: """The board consists of 24 points. Each player('W' and 'B') initially has 15 checkers on board. Player 'W' moves from point 23 to point 0 and player 'B' moves from point 0 to 23. Points 0-7 are @@ -474,11 +473,12 @@ class Board: def __init__(self): """Initial state of the game""" # TODO : Add bar to Board class where a blot is placed when it is hit. - self.points = [Point() for index in range(24)] - self.points[0].checkers['B'] = self.points[23].checkers['W'] = 2 - self.points[5].checkers['W'] = self.points[18].checkers['B'] = 5 - self.points[7].checkers['W'] = self.points[16].checkers['B'] = 3 - self.points[11].checkers['B'] = self.points[12].checkers['W'] = 5 + point = {'W':0, 'B':0} + self.points = [point.copy() for index in range(24)] + self.points[0]['B'] = self.points[23]['W'] = 2 + self.points[5]['W'] = self.points[18]['B'] = 5 + self.points[7]['W'] = self.points[16]['B'] = 3 + self.points[11]['B'] = self.points[12]['W'] = 5 self.allow_bear_off = {'W': False, 'B': False} def checkers_at_home(self, player): @@ -486,7 +486,7 @@ def checkers_at_home(self, player): sum_range = range(0, 7) if player == 'W' else range(17, 24) count = 0 for idx in sum_range: - count = count + self.points[idx].checkers[player] + count = count + self.points[idx][player] return count def is_legal_move(self, start, steps, player): @@ -498,7 +498,7 @@ def is_legal_move(self, start, steps, player): dest_range = range(0, 24) move1_legal = move2_legal = False if dest1 in dest_range: - if self.points[dest1].is_open_for(player): + if self.is_point_open(player, self.points[dest1]): self.move_checker(start[0], steps[0], player) move1_legal = True else: @@ -508,7 +508,7 @@ def is_legal_move(self, start, steps, player): if not move1_legal: return False if dest2 in dest_range: - if self.points[dest2].is_open_for(player): + if self.is_point_open(player, self.points[dest2]): move2_legal = True else: if self.allow_bear_off[player]: @@ -519,30 +519,15 @@ def move_checker(self, start, steps, player): """Moves a checker from starting point by a given number of steps""" dest = start + steps dest_range = range(0, 24) - self.points[start].remove_checker(player) + self.points[start][player] -= 1 if dest in dest_range: - self.points[dest].add_checker(player) + self.points[dest][player] += 1 if self.checkers_at_home(player) == 15: self.allow_bear_off[player] = True -class Point: - """A point is one of the 24 triangles on the board where - the players' checkers are placed.""" - - def __init__(self): - self.checkers = {'W':0, 'B':0} - - def is_open_for(self, player): + def is_point_open(self, player, point): """A point is open for a player if the no. of opponent's checkers already present on it is 0 or 1. A player can move a checker to a point only if it is open.""" opponent = 'B' if player == 'W' else 'W' - return self.checkers[opponent] <= 1 - - def add_checker(self, player): - """Place a player's checker on a point.""" - self.checkers[player] += 1 - - def remove_checker(self, player): - """Remove a player's checker from a point.""" - self.checkers[player] -= 1 + return point[opponent] <= 1 From cae3d019c24c50485dab216276ff364fadec9d33 Mon Sep 17 00:00:00 2001 From: Aabir Abubaker Kar <16526730+bakerwho@users.noreply.github.com> Date: Sat, 3 Mar 2018 19:29:51 -0500 Subject: [PATCH 058/269] Add to rl module (#799) * Ignoring .DS_Store for macOS * Added Direct Utility Estimation code and fixed notebook * Added implementation to README.md --- README.md | 2 +- rl.ipynb | 425 +++++++++++++++++++++++++++-------------------- rl.py | 55 ++++++ tests/test_rl.py | 12 +- 4 files changed, 311 insertions(+), 183 deletions(-) diff --git a/README.md b/README.md index d23cc6851..f68ebdd06 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 19.3 | Version-Space-Learning | `version_space_learning` | [`knowledge.py`](knowledge.py) | Done | Included | | 19.8 | Minimal-Consistent-Det | `minimal_consistent_det` | [`knowledge.py`](knowledge.py) | Done | | | 19.12 | FOIL | `FOIL_container` | [`knowledge.py`](knowledge.py) | Done | | -| 21.2 | Passive-ADP-Agent | `PassiveADPAgent` | [`rl.py`][rl] | Done | | +| 21.2 | Passive-ADP-Agent | `PassiveADPAgent` | [`rl.py`][rl] | Done | Included | | 21.4 | Passive-TD-Agent | `PassiveTDAgent` | [`rl.py`][rl] | Done | Included | | 21.8 | Q-Learning-Agent | `QLearningAgent` | [`rl.py`][rl] | Done | Included | | 22.1 | HITS | `HITS` | [`nlp.py`][nlp] | Done | Included | diff --git a/rl.ipynb b/rl.ipynb index f05613ddd..a8f6adc2c 100644 --- a/rl.ipynb +++ b/rl.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "collapsed": true }, @@ -28,7 +28,11 @@ "\n", "* Overview\n", "* Passive Reinforcement Learning\n", - "* Active Reinforcement Learning" + " - Direct Utility Estimation\n", + " - Adaptive Dynamic Programming\n", + " - Temporal-Difference Agent\n", + "* Active Reinforcement Learning\n", + " - Q learning" ] }, { @@ -56,171 +60,331 @@ "source": [ "## PASSIVE REINFORCEMENT LEARNING\n", "\n", - "In passive Reinforcement Learning the agent follows a fixed policy and tries to learn the Reward function and the Transition model (if it is not aware of these)." + "In passive Reinforcement Learning the agent follows a fixed policy $\\pi$. Passive learning attempts to evaluate the given policy $pi$ - without any knowledge of the Reward function $R(s)$ and the Transition model $P(s'\\ |\\ s, a)$.\n", + "\n", + "This is usually done by some method of **utility estimation**. The agent attempts to directly learn the utility of each state that would result from following the policy. Note that at each step, it has to *perceive* the reward and the state - it has no global knowledge of these. Thus, if a certain the entire set of actions offers a very low probability of attaining some state $s_+$ - the agent may never perceive the reward $R(s_+)$.\n", + "\n", + "Consider a situation where an agent is given a policy to follow. Thus, at any point it knows only its current state and current reward, and the action it must take next. This action may lead it to more than one state, with different probabilities.\n", + "\n", + "For a series of actions given by $\\pi$, the estimated utility $U$:\n", + "$$U^{\\pi}(s) = E(\\sum_{t=0}^\\inf \\gamma^t R^t(s')$$)\n", + "Or the expected value of summed discounted rewards until termination.\n", + "\n", + "Based on this concept, we discuss three methods of estimating utility:\n", + "\n", + "1. **Direct Utility Estimation (DUE)**\n", + " \n", + " The first, most naive method of estimating utility comes from the simplest interpretation of the above definition. We construct an agent that follows the policy until it reaches the terminal state. At each step, it logs its current state, reward. Once it reaches the terminal state, it can estimate the utility for each state for *that* iteration, by simply summing the discounted rewards from that state to the terminal one.\n", + "\n", + " It can now run this 'simulation' $n$ times, and calculate the average utility of each state. If a state occurs more than once in a simulation, both its utility values are counted separately.\n", + " \n", + " Note that this method may be prohibitively slow for very large statespaces. Besides, **it pays no attention to the transition probability $P(s'\\ |\\ s, a)$.** It misses out on information that it is capable of collecting (say, by recording the number of times an action from one state led to another state). The next method addresses this issue.\n", + " \n", + "2. **Adaptive Dynamic Programming (ADP)**\n", + " \n", + " This method makes use of knowledge of the past state $s$, the action $a$, and the new perceived state $s'$ to estimate the transition probability $P(s'\\ |\\ s,a)$. It does this by the simple counting of new states resulting from previous states and actions.
    \n", + " The program runs through the policy a number of times, keeping track of:\n", + " - each occurrence of state $s$ and the policy-recommended action $a$ in $N_{sa}$\n", + " - each occurrence of $s'$ resulting from $a$ on $s$ in $N_{s'|sa}$.\n", + " \n", + " It can thus estimate $P(s'\\ |\\ s,a)$ as $N_{s'|sa}/N_{sa}$, which in the limit of infinite trials, will converge to the true value.
    \n", + " Using the transition probabilities thus estimated, it can apply `POLICY-EVALUATION` to estimate the utilities $U(s)$ using properties of convergence of the Bellman functions.\n", + "\n", + "3. **Temporal-difference learning (TD)**\n", + " \n", + " Instead of explicitly building the transition model $P$, the temporal-difference model makes use of the expected closeness between the utilities of two consecutive states $s$ and $s'$.\n", + " For the transition $s$ to $s'$, the update is written as:\n", + "$$U^{\\pi}(s) \\leftarrow U^{\\pi}(s) + \\alpha \\left( R(s) + \\gamma U^{\\pi}(s') - U^{\\pi}(s) \\right)$$\n", + " This model implicitly incorporates the transition probabilities by being weighed for each state by the number of times it is achieved from the current state. Thus, over a number of iterations, it converges similarly to the Bellman equations.\n", + " The advantage of the TD learning model is its relatively simple computation at each step, rather than having to keep track of various counts.\n", + " For $n_s$ states and $n_a$ actions the ADP model would have $n_s \\times n_a$ numbers $N_{sa}$ and $n_s^2 \\times n_a$ numbers $N_{s'|sa}$ to keep track of. The TD model must only keep track of a utility $U(s)$ for each state." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Passive Temporal Difference Agent\n", + "#### Demonstrating Passive agents\n", "\n", - "The PassiveTDAgent class in the rl module implements the Agent Program (notice the usage of word Program) described in **Fig 21.4** of the AIMA Book. PassiveTDAgent uses temporal differences to learn utility estimates. In simple terms we learn the difference between the states and backup the values to previous states while following a fixed policy. Let us look into the source before we see some usage examples." + "Passive agents are implemented in `rl.py` as various `Agent-Class`es.\n", + "\n", + "To demonstrate these agents, we make use of the `GridMDP` object from the `MDP` module. `sequential_decision_environment` is similar to that used for the `MDP` notebook but has discounting with $\\gamma = 0.9$.\n", + "\n", + "The `Agent-Program` can be obtained by creating an instance of the relevant `Agent-Class`. The `__call__` method allows the `Agent-Class` to be called as a function. The class needs to be instantiated with a policy ($\\pi$) and an `MDP` whose utility of states will be estimated." ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "%psource PassiveTDAgent" + "from mdp import sequential_decision_environment" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The Agent Program can be obtained by creating the instance of the class by passing the appropriate parameters. Because of the __ call __ method the object that is created behaves like a callable and returns an appropriate action as most Agent Programs do. To instantiate the object we need a policy ($\\pi$) and a mdp whose utility of states will be estimated. Let us import a `GridMDP` object from the `MDP` module. **Figure 17.1 (sequential_decision_environment)** is similar to **Figure 21.1** but has some discounting as **gamma = 0.9**." + "The `sequential_decision_environment` is a GridMDP object as shown below. The rewards are **+1** and **-1** in the terminal states, and **-0.04** in the rest. Now we define actions and a policy similar to **Fig 21.1** in the book." ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "from mdp import sequential_decision_environment" + "# Action Directions\n", + "north = (0, 1)\n", + "south = (0,-1)\n", + "west = (-1, 0)\n", + "east = (1, 0)\n", + "\n", + "policy = {\n", + " (0, 2): east, (1, 2): east, (2, 2): east, (3, 2): None,\n", + " (0, 1): north, (2, 1): north, (3, 1): None,\n", + " (0, 0): north, (1, 0): west, (2, 0): west, (3, 0): west, \n", + "}\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "**Figure 17.1 (sequential_decision_environment)** is a GridMDP object and is similar to the grid shown in **Figure 21.1**. The rewards in the terminal states are **+1** and **-1** and **-0.04** in rest of the states. Now we define a policy similar to **Fig 21.1** in the book." + "### Direction Utility Estimation Agent\n", + "\n", + "The `PassiveDEUAgent` class in the `rl` module implements the Agent Program described in **Fig 21.2** of the AIMA Book. `PassiveDEUAgent` sums over rewards to find the estimated utility for each state. It thus requires the running of a number of iterations." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "# Action Directions\n", - "north = (0, 1)\n", - "south = (0,-1)\n", - "west = (-1, 0)\n", - "east = (1, 0)\n", - "\n", - "policy = {\n", - " (0, 2): east, (1, 2): east, (2, 2): east, (3, 2): None,\n", - " (0, 1): north, (2, 1): north, (3, 1): None,\n", - " (0, 0): north, (1, 0): west, (2, 0): west, (3, 0): west, \n", - "}\n" + "%psource PassiveDUEAgent" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "DUEagent = PassiveDUEAgent(policy, sequential_decision_environment)\n", + "for i in range(200):\n", + " run_single_trial(DUEagent, sequential_decision_environment)\n", + " DUEagent.estimate_U()\n", + "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Let us create our object now. We also use the **same alpha** as given in the footnote of the book on **page 837**." + "The calculated utilities are:" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print('\\n'.join([str(k)+':'+str(v) for k, v in DUEagent.U.items()]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Adaptive Dynamic Programming Agent\n", + "\n", + "The `PassiveADPAgent` class in the `rl` module implements the Agent Program described in **Fig 21.2** of the AIMA Book. `PassiveADPAgent` uses state transition and occurrence counts to estimate $P$, and then $U$. Go through the source below to understand the agent." + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "our_agent = PassiveTDAgent(policy, sequential_decision_environment, alpha=lambda n: 60./(59+n))" + "%psource PassiveADPAgent" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We instantiate a `PassiveADPAgent` below with the `GridMDP` shown and train it over 200 iterations. The `rl` module has a simple implementation to simulate iterations. The function is called **run_single_trial**." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "ADPagent = PassiveADPAgent(policy, sequential_decision_environment)\n", + "for i in range(200):\n", + " run_single_trial(ADPagent, sequential_decision_environment)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The rl module also has a simple implementation to simulate iterations. The function is called **run_single_trial**. Now we can try our implementation. We can also compare the utility estimates learned by our agent to those obtained via **value iteration**.\n" + "The calculated utilities are:" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "print('\\n'.join([str(k)+':'+str(v) for k, v in ADPagent.U.items()]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Passive Temporal Difference Agent\n", + "\n", + "`PassiveTDAgent` uses temporal differences to learn utility estimates. We learn the difference between the states and backup the values to previous states. Let us look into the source before we see some usage examples." + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "from mdp import value_iteration" + "%psource PassiveTDAgent" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The values calculated by value iteration:" + "In creating the `TDAgent`, we use the **same learning rate** $\\alpha$ as given in the footnote of the book on **page 837**." ] }, { "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{(0, 1): 0.3984432178350045, (1, 2): 0.649585681261095, (3, 2): 1.0, (0, 0): 0.2962883154554812, (3, 0): 0.12987274656746342, (3, 1): -1.0, (2, 1): 0.48644001739269643, (2, 0): 0.3447542300124158, (2, 2): 0.7953620878466678, (1, 0): 0.25386699846479516, (0, 2): 0.5093943765842497}\n" - ] - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ - "print(value_iteration(sequential_decision_environment))" + "TDagent = PassiveTDAgent(policy, sequential_decision_environment, alpha = lambda n: 60./(59+n))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now the values estimated by our agent after **200 trials**." + "Now we run **200 trials** for the agent to estimate Utilities." ] }, { "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{(0, 1): 0.4431282384930237, (1, 2): 0.6719826603921873, (3, 2): 1, (0, 0): 0.32008510559157544, (3, 0): 0.0, (3, 1): -1, (2, 1): 0.6258841793121656, (2, 0): 0.0, (2, 2): 0.7626863051408717, (1, 0): 0.19543350078456248, (0, 2): 0.550838599140139}\n" - ] - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "for i in range(200):\n", - " run_single_trial(our_agent,sequential_decision_environment)\n", - "print(our_agent.U)" + " run_single_trial(TDagent,sequential_decision_environment)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The calculated utilities are:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print('\\n'.join([str(k)+':'+str(v) for k, v in TDagent.U.items()]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Comparison with value iteration method\n", + "\n", + "We can also compare the utility estimates learned by our agent to those obtained via **value iteration**.\n", + "\n", + "**Note that value iteration has a priori knowledge of the transition table $P$, the rewards $R$, and all the states $s$.**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from mdp import value_iteration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The values calculated by value iteration:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "U_values = value_iteration(sequential_decision_environment)\n", + "print('\\n'.join([str(k)+':'+str(v) for k, v in U_values.items()]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We can also explore how these estimates vary with time by using plots similar to **Fig 21.5a**. To do so we define a function to help us with the same. We will first enable matplotlib using the inline backend." + "## Evolution of utility estimates over iterations\n", + "\n", + "We can explore how these estimates vary with time by using plots similar to **Fig 21.5a**. We will first enable matplotlib using the inline backend. We also define a function to collect the values of utilities at each iteration." ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "collapsed": true }, @@ -248,25 +412,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Here is a plot of state (2,2)." + "Here is a plot of state $(2,2)$." ] }, { "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n6vG4hJOPymRc/4bnz2k+qvXy+3fW8fZKf/b3Te5BvD7D2j2l/PmSCU5NPHtIb+b+YFqbmqkjKZLh\naAkwUkSGikgs/o7keUHn7AROBRCRMUA80HHtQ62wm4eC171pUlOwg0JM22Oo2yXU+wzFlf4+Bbv6\n19wIGGjIzO2aQkaIVUPbwikR1fuorPU6JeC8In9NYUAbgoJ9jT4925eGSApsP7czsZW7ihmanuRk\nhuFwhipav6fymnruf39DyA7Tkso6Nu0vZ8pgf+d7QVkNNzy7lFqvr9kVOHtbmfJRfZLJbGeTYLjs\nNX7OCNGcFYrLJQzoldBi89EH6/xrfl05bXDI0XQA8R43NSF+b2+t2E18jKvZmpL9dztueHqLw58P\nhf35am64NsDry/1L0/zw5OH8aFboYcHBkuI8zP3BNO6/+OhGx+2C1e7iKt5dvddpVt6wr4x/f5pL\n/5R4Lpg0gJF9ejDrqAx+dc7YLg8IEMGagjGmXkRuBuYDbuBJY8xaEfk9kGOMmQf8DPiPiPwUfx78\nPdOJ8/NNQEdzoIZ5Co37FMKp0rpdgtfns5qPYoj1uPjgpyeGXNbCZneE7XOaj9pZUwhoPgqUV1RF\nSkJMmxZ/65sSz5CAJSEOJ/0CMg070K3fV8qEVkZJNcf+vXutPoUH3t/A01/vYFSfHk06+9fs8ddI\nTh3Th2U7i9lbUs0Oq1nux6eEHi5oFwhCrRsVKXbgbG7mfSgDeiWQ10JN4cN1+5k5Io37Ljy62XPi\nY1xNmo/qvD7eXb2X08f2bXao7Xar4/q8Sc1PkjxUMe7WO5rfXL6bcf17csfs8EY+hSoQ2PnFU19t\np7rOx/u3nsCjn2zlTWtE2O3fOspp6v3v96eG9fMiKaINV8aYd40xo4wxw40x91nH7rYCAsaYdcaY\nmcaYicaYScaYDyKZnhDpA0IEBed9///h9CnY3C6hssZLdZ3PyRRG9UlucT8AT0BNISnW3e52Vfvh\nD1wpdXdxFW8u393mZR3iY9x8evssp9PscJIU52HZr0+nR5yH3UVVVNbWk1dUxah2LoUdPCR1ldUU\nFapzz26mmmX9Xm6cuxSvz/DPyyc3W8Idmp7EiaMyuOSY9o0ma49fnT2Gd245vtEiiq1JTYqltJnF\nBrcV+Jc/P72VPpGEWHeTGtbyncUUVdZx1vjmay12k2aojvqO4gkY8h1Kbn45K/NKuHBy8HiY9km0\n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\nYII=\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "agent = PassiveTDAgent(policy, sequential_decision_environment, alpha=lambda n: 60./(59+n))\n", "graph_utility_estimates(agent, sequential_decision_environment, 500, [(2,2)])" @@ -276,25 +429,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "It is also possible to plot multiple states on the same plot." + "It is also possible to plot multiple states on the same plot. As expected, the utility of the finite state $(3,2)$ stays constant and is equal to $R((3,2)) = 1$." ] }, { "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\nRDwKBRER8fw/mBIlJRttB04AAAAASUVORK5CYII=\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "graph_utility_estimates(agent, sequential_decision_environment, 500, [(2,2), (3,2)])" ] @@ -321,7 +463,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": { "collapsed": true }, @@ -348,7 +490,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": { "collapsed": true }, @@ -367,7 +509,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": { "collapsed": true }, @@ -391,58 +533,9 @@ }, { "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "defaultdict(float,\n", - " {((0, 0), (-1, 0)): -0.10293706293706295,\n", - " ((0, 0), (0, -1)): -0.10590764087842354,\n", - " ((0, 0), (0, 1)): 0.05460040868097919,\n", - " ((0, 0), (1, 0)): -0.09867203219315898,\n", - " ((0, 1), (-1, 0)): 0.07177237857105365,\n", - " ((0, 1), (0, -1)): 0.060286786739471215,\n", - " ((0, 1), (0, 1)): 0.10374209705939107,\n", - " ((0, 1), (1, 0)): -0.04,\n", - " ((0, 2), (-1, 0)): 0.09308553784444584,\n", - " ((0, 2), (0, -1)): 0.09710376713758972,\n", - " ((0, 2), (0, 1)): 0.12895703412485182,\n", - " ((0, 2), (1, 0)): 0.1325347830202934,\n", - " ((1, 0), (-1, 0)): -0.07589625670469141,\n", - " ((1, 0), (0, -1)): -0.0759999433406361,\n", - " ((1, 0), (0, 1)): -0.07323076923076924,\n", - " ((1, 0), (1, 0)): 0.07539875443960498,\n", - " ((1, 2), (-1, 0)): 0.09841555812424703,\n", - " ((1, 2), (0, -1)): 0.1713989451054505,\n", - " ((1, 2), (0, 1)): 0.16142640572251182,\n", - " ((1, 2), (1, 0)): 0.19259892322613212,\n", - " ((2, 0), (-1, 0)): -0.0759999433406361,\n", - " ((2, 0), (0, -1)): -0.0759999433406361,\n", - " ((2, 0), (0, 1)): -0.08367037404281108,\n", - " ((2, 0), (1, 0)): -0.0437928007023705,\n", - " ((2, 1), (-1, 0)): -0.009680447057460156,\n", - " ((2, 1), (0, -1)): -0.6618548845169473,\n", - " ((2, 1), (0, 1)): -0.4333323454834963,\n", - " ((2, 1), (1, 0)): -0.8872940082892214,\n", - " ((2, 2), (-1, 0)): 0.1483330033351123,\n", - " ((2, 2), (0, -1)): 0.04473676319907405,\n", - " ((2, 2), (0, 1)): 0.13217540013336543,\n", - " ((2, 2), (1, 0)): 0.30829164610044535,\n", - " ((3, 0), (-1, 0)): -0.6432395354845424,\n", - " ((3, 0), (0, -1)): 0.0,\n", - " ((3, 0), (0, 1)): -0.787040488208054,\n", - " ((3, 0), (1, 0)): -0.04,\n", - " ((3, 1), None): -0.7641890167582844,\n", - " ((3, 2), None): 0.4106787728880888})" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "q_agent.Q" ] @@ -461,7 +554,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": { "collapsed": true }, @@ -476,31 +569,9 @@ }, { "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "defaultdict(>,\n", - " {(0, 0): 0.05460040868097919,\n", - " (0, 1): 0.10374209705939107,\n", - " (0, 2): 0.1325347830202934,\n", - " (1, 0): 0.07539875443960498,\n", - " (1, 2): 0.19259892322613212,\n", - " (2, 0): -0.0437928007023705,\n", - " (2, 1): -0.009680447057460156,\n", - " (2, 2): 0.30829164610044535,\n", - " (3, 0): 0.0,\n", - " (3, 1): -0.7641890167582844,\n", - " (3, 2): 0.4106787728880888})" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "U" ] @@ -514,17 +585,9 @@ }, { "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{(0, 1): 0.3984432178350045, (1, 2): 0.649585681261095, (3, 2): 1.0, (0, 0): 0.2962883154554812, (3, 0): 0.12987274656746342, (3, 1): -1.0, (2, 1): 0.48644001739269643, (2, 0): 0.3447542300124158, (2, 2): 0.7953620878466678, (1, 0): 0.25386699846479516, (0, 2): 0.5093943765842497}\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(value_iteration(sequential_decision_environment))" ] @@ -564,7 +627,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.1" + "version": "3.6.3" } }, "nbformat": 4, diff --git a/rl.py b/rl.py index 94664b130..1b7e20c33 100644 --- a/rl.py +++ b/rl.py @@ -7,6 +7,61 @@ import random +class PassiveDUEAgent: + + """Passive (non-learning) agent that uses direct utility estimation + on a given MDP and policy.""" + def __init__(self, pi, mdp): + self.pi = pi + self.mdp = mdp + self.U = {} + self.s = None + self.a = None + self.s_history = [] + self.r_history = [] + self.init = mdp.init + + def __call__(self, percept): + s1, r1 = percept + self.s_history.append(s1) + self.r_history.append(r1) + ## + ## + if s1 in self.mdp.terminals: + self.s = self.a = None + else: + self.s, self.a = s1, self.pi[s1] + return self.a + + def estimate_U(self): + # this function can be called only if the MDP has reached a terminal state + # it will also reset the mdp history + assert self.a is None, 'MDP is not in terminal state' + assert len(self.s_history) == len(self.r_history) + # calculating the utilities based on the current iteration + U2 = {s : [] for s in set(self.s_history)} + for i in range(len(self.s_history)): + s = self.s_history[i] + U2[s] += [sum(self.r_history[i:])] + U2 = {k : sum(v)/max(len(v), 1) for k, v in U2.items()} + # resetting history + self.s_history, self.r_history = [], [] + # setting the new utilities to the average of the previous + # iteration and this one + for k in U2.keys(): + if k in self.U.keys(): + self.U[k] = (self.U[k] + U2[k]) /2 + else: + self.U[k] = U2[k] + return self.U + + def update_state(self, percept): + '''To be overridden in most cases. The default case + assumes the percept to be of type (state, reward)''' + return percept + + + class PassiveADPAgent: """Passive (non-learning) agent that uses adaptive dynamic programming diff --git a/tests/test_rl.py b/tests/test_rl.py index 932b34ae5..95a0e2224 100644 --- a/tests/test_rl.py +++ b/tests/test_rl.py @@ -15,7 +15,17 @@ (0, 0): north, (1, 0): west, (2, 0): west, (3, 0): west, } - +def test_PassiveDUEAgent(): + agent = PassiveDUEAgent(policy, sequential_decision_environment) + for i in range(200): + run_single_trial(agent,sequential_decision_environment) + agent.estimate_U() + # Agent does not always produce same results. + # Check if results are good enough. + #print(agent.U[(0, 0)], agent.U[(0,1)], agent.U[(1,0)]) + assert agent.U[(0, 0)] > 0.15 # In reality around 0.3 + assert agent.U[(0, 1)] > 0.15 # In reality around 0.4 + assert agent.U[(1, 0)] > 0 # In reality around 0.2 def test_PassiveADPAgent(): agent = PassiveADPAgent(policy, sequential_decision_environment) From a6c7b577263fa706752081525ba1423e5a2c0cd8 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Sun, 4 Mar 2018 06:00:31 +0530 Subject: [PATCH 059/269] Added tt-entails explanation (#793) * added tt-entails explanation * Updated README.md --- README.md | 2 +- logic.ipynb | 390 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 384 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f68ebdd06..38c149cc5 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 7 | KB | `KB` | [`logic.py`][logic] | Done | Included | | 7.1 | KB-Agent | `KB_Agent` | [`logic.py`][logic] | Done | | | 7.7 | Propositional Logic Sentence | `Expr` | [`utils.py`][utils] | Done | Included | -| 7.10 | TT-Entails | `tt_entails` | [`logic.py`][logic] | Done | | +| 7.10 | TT-Entails | `tt_entails` | [`logic.py`][logic] | Done | Included | | 7.12 | PL-Resolution | `pl_resolution` | [`logic.py`][logic] | Done | Included | | 7.14 | Convert to CNF | `to_cnf` | [`logic.py`][logic] | Done | | | 7.15 | PL-FC-Entails? | `pl_fc_resolution` | [`logic.py`][logic] | Done | | diff --git a/logic.ipynb b/logic.ipynb index 4ac164861..6716e8515 100644 --- a/logic.ipynb +++ b/logic.ipynb @@ -29,7 +29,8 @@ "outputs": [], "source": [ "from utils import *\n", - "from logic import *" + "from logic import *\n", + "from notebook import psource" ] }, { @@ -553,19 +554,394 @@ { "cell_type": "code", "execution_count": 21, - "metadata": { - "collapsed": true - }, - "outputs": [], + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def tt_check_all(kb, alpha, symbols, model):\n",
    +       "    """Auxiliary routine to implement tt_entails."""\n",
    +       "    if not symbols:\n",
    +       "        if pl_true(kb, model):\n",
    +       "            result = pl_true(alpha, model)\n",
    +       "            assert result in (True, False)\n",
    +       "            return result\n",
    +       "        else:\n",
    +       "            return True\n",
    +       "    else:\n",
    +       "        P, rest = symbols[0], symbols[1:]\n",
    +       "        return (tt_check_all(kb, alpha, rest, extend(model, P, True)) and\n",
    +       "                tt_check_all(kb, alpha, rest, extend(model, P, False)))\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(tt_check_all)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The algorithm basically computes every line of the truth table $KB\\implies \\alpha$ and checks if it is true everywhere.\n", + "
    \n", + "If symbols are defined, the routine recursively constructs every combination of truth values for the symbols and then, \n", + "it checks whether `model` is consistent with `kb`.\n", + "The given models correspond to the lines in the truth table,\n", + "which have a `true` in the KB column, \n", + "and for these lines it checks whether the query evaluates to true\n", + "
    \n", + "`result = pl_true(alpha, model)`.\n", + "
    \n", + "
    \n", + "In short, `tt_check_all` evaluates this logical expression for each `model`\n", + "
    \n", + "`pl_true(kb, model) => pl_true(alpha, model)`\n", + "
    \n", + "which is logically equivalent to\n", + "
    \n", + "`pl_true(kb, model) & ~pl_true(alpha, model)` \n", + "
    \n", + "that is, the knowledge base and the negation of the query are logically inconsistent.\n", + "
    \n", + "
    \n", + "`tt_entails()` just extracts the symbols from the query and calls `tt_check_all()` with the proper parameters.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def tt_entails(kb, alpha):\n",
    +       "    """Does kb entail the sentence alpha? Use truth tables. For propositional\n",
    +       "    kb's and sentences. [Figure 7.10]. Note that the 'kb' should be an\n",
    +       "    Expr which is a conjunction of clauses.\n",
    +       "    >>> tt_entails(expr('P & Q'), expr('Q'))\n",
    +       "    True\n",
    +       "    """\n",
    +       "    assert not variables(alpha)\n",
    +       "    symbols = list(prop_symbols(kb & alpha))\n",
    +       "    return tt_check_all(kb, alpha, symbols, {})\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(tt_entails)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Keep in mind that for two symbols P and Q, P => Q is false only when P is `True` and Q is `False`.\n", + "Example usage of `tt_entails()`:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tt_entails(P & Q, Q)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "P & Q is True only when both P and Q are True. Hence, (P & Q) => Q is True" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tt_entails(P | Q, Q)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tt_entails(P | Q, P)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we know that P | Q is true, we cannot infer the truth values of P and Q. \n", + "Hence (P | Q) => Q is False and so is (P | Q) => P." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(A, B, C, D, E, F, G) = symbols('A, B, C, D, E, F, G')\n", + "tt_entails(A & (B | C) & D & E & ~(F | G), A & D & E & ~F & ~G)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, "source": [ - "%psource tt_check_all" + "We can see that for the KB to be true, A, D, E have to be True and F and G have to be False.\n", + "Nothing can be said about B or C." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Note that `tt_entails()` takes an `Expr` which is a conjunction of clauses as the input instead of the `KB` itself. You can use the `ask_if_true()` method of `PropKB` which does all the required conversions. Let's check what `wumpus_kb` tells us about $P_{1, 1}$." + "Coming back to our problem, note that `tt_entails()` takes an `Expr` which is a conjunction of clauses as the input instead of the `KB` itself. \n", + "You can use the `ask_if_true()` method of `PropKB` which does all the required conversions. \n", + "Let's check what `wumpus_kb` tells us about $P_{1, 1}$." ] }, { From 53edb7cf0650c43a305ea886133a919aa82ddacf Mon Sep 17 00:00:00 2001 From: Nouman Ahmed <35970677+Noumanmufc1@users.noreply.github.com> Date: Sun, 4 Mar 2018 05:35:34 +0500 Subject: [PATCH 060/269] Added simple problem solving agent in search.ipynb (#795) --- images/simple_problem_solving_agent.JPG | Bin 0 -> 40649 bytes search.ipynb | 146 ++++++++++++++++++------ 2 files changed, 113 insertions(+), 33 deletions(-) create mode 100644 images/simple_problem_solving_agent.JPG diff --git a/images/simple_problem_solving_agent.JPG b/images/simple_problem_solving_agent.JPG new file mode 100644 index 0000000000000000000000000000000000000000..80fb904b5a0e9eade01732cf4fcd46294fd4ed22 GIT binary patch literal 40649 zcmeFZ1yokuwm-Zd>Y=+k9t0_o?oa^{0YN}o5Rj4vNqGoCIt2s~kZz>ATck_r?v|4L zZ{Kt8`Cjn+@9Vj7zV933;@E@X-fM5xnrp5(=ladxT$f)jX8_y>(z4P31Ofr{z<U(10^Lh|4lZ| z+dMoxG)#h`0$d^-+&o-AKLSBRL&LxG6+OhQUV&Uk}~`6dewFCV{vpwQiW zQqnT_Wgk3NQB`}QuAymQX!P9J#MI2r-r=RAle3HWYaib?e*OUwk#D1-W8THaC8wmO zrDtSjWfv8fl$MoOR8}=Lx3spkcXWOp7#tcN8U6ZgY<6ybVR31BWp!)0EmAZ*55~VZCrTZxDb$#5Rsri#|1%f1TRE9BxJhVDEN{}P<`-_$b^vP zOJK*}?d~OzoOlVm#enaoT;SdEgYQ4S1ok|wE_hc6A3?znCTj=q< zI=V}sU-1(7j9h;_nxrH_4Zj3XV_L*dZe9XEB+4%V^6UN6^_2SGF8!~R{#Td&cg6j$ zUHV^B`9I`P%oBG@XmHP{W-&J57sO2CVqJ?D^QXj9qT{J5rxWuO#e=1;$e?FzoBjb4 z+CHzo1Xl0aLskft{%}s$TqxM?+8y9$!~Z{DM4ot*B@?Q0%>@oNly~bEG~hF zyZM*Ec>Tp&83}4nsh>XoSnlG%QOna)U-+u<6R_3Q|389&jsE$IC^pBet?^@NsmG)l z3CxeZ*@Bcxr*|i*qqA`hKni0vBV z^)Y`7UJF?4{%NcOg#(Rf)F9#w@zwYm%2P4BwOSKI8Rg-UN%BccUimu%Z79}(j6+@c z(TxJgeq;?y)NMtVz^w2N{2a@L<^ogy5}3>Bb{AhG9!qQ%6>~LagqQZ~sOzWb6Mj>J zhRJ-)t|LE2p4BmKs*=vK$uwsb-F(ZB&t`XzkUQxaiUAXuSh|Pk;|qd%=q1oM`tcH& zX`{Jd2|ZRgqqzj`>b|)Iyh-85+21dLV`G}1ZRE5sf%=_GU^iQQHr@?vB*|`faS1eo z%9x4RCw{eOnV{-zH(mn!yeshI+u+9n3dAn~UmLLZNTt1}mjDhJVrAyn8`mR#9qV*( z%riSY+zlR(;~hfv6P3L^gAVLJz222;eq`9803VjAu%O;y2v5e~4A^H&}31^@JQ3H~CSBpiufWSDY& zx@sgGO-#!yRGYkEl4t*`GFCN;MulZE%wKei^BMuG8?<g*g)Mjp^Rs8n=q-5&7hveD zm0yqyZk2XPnr3z4^5c32=k0HXdxv~$H6LN}q1(kO^T8_j*ZpjMtU^~&8WsN#IQ>FK zg0&uWa{Bm8W-gefNey?}+!1b67OjuaxmX;$fDai%qrAD~!E>u%W4}xA)~&6XlG0F$ zr36WGj{AsgPGm5gw!-Twk>h`yH}?vvdn}$$#iU+T1Jn8t_9eIZX!k&S!JWQh`wbja zvbk*p)2hYutsJW1(f75q(VX5d^1XpJMJcM^Q?g&zMas1~^$_`>D+Pc5FZ(DePuZybFt^QCr%FR zZ(kqse{EUS9!q78o5GPxN!qwkCDn;y3CvF91bY4_^LYs)RhEmgFrQoa32=@#ryC(YG? zBmhA4KnB( zuPmb=&cO~wW1Cc$0PMTsCGaU1e!`-;kAnd36xkU)_sWCsEB8SqrHmAF>kr>X6BAoG zv$2rBe1aNIaHJA>cF)eUPcPok$bC8o-bZl>+=%et`7j_bCQGJDi2tP>fJ}@s8s%yp z=d|WvGV3UE#T(FI$@U|^M6xYHIXU2u3k*;G{Oa19##$1^>SLwkUeUTA6un(m z`tgdi8)nx6W;gtw5#vYrc>Bd$SqUoRGln?DvlIA&%pbT9mD&+crzq+ae#MF^C>?3Mw`{*)KsO?sPP> z?CVCgGpaUhA-~x35qQ%TH>XQj(vg9lg#9|iZlo6a=K+cFT>_nSqwvjX(4#V>{P_{E zkItOz@yB&-tm}QOy%$$rOhWwgoBQbC3tmt6?%Urwbpqw;&ssEeAh7>6`FyYjHANLZc4J!sw~@#A&nHa4^G3RK^wY&{HosgVe$FwpJj z?xCtv9kc5sgfIn)NQbexu=_=5jenaf4xhEIH^Tpf97r>VE{9BJ$?YMSQ?=+*8XahM z7}gFwe9_m5(u&yiXof2^oA_y4A-1P!lBVg2Z|dPHXsqOl-PeX?J0b5QYOGHpHH=+lbW!@#X2ze56EX>}LGRO; zk~KT@XD_k4gBL3jW7tNe0ry72bv;`05He$J;ZdY;3IKy-F`D{8H~I6@If|@?>VA>; zFA>;i!ByZJ;L-DwGA(5U=V*R^e+!^(AQ+y_lkk^&*pi=XOVyGwF0n|Puy z3^u!VgAf3l{Zm(nIs+k4CV(_Ua8SU%ljvJxFl*k6s~yOWzOZ(*r|Tp1wsRpGws{GJ z+EtUSx4Os^LOiqb=8n}MnI-bBOXJQ$}J89USh7Saw;xsw{XPY6+%Zs051jnYVZ zX;8Tk-ESBn-Nr9m$kyGk5MgF@d`IsRNQh26hEN%sQHu^92(#7-HEn&D9}a+p`M-#r zOkomk%6C^ryvY;=Gc*R4S1-7$`Yn3LZbWKuOOMhKwLG8V5!R3myUC&8u8hJLA08ft z)hUAh+TWsJS8xEKAAiwR%a02E$y zJ6{y3AHG!YOJ&lWQgnwz?o!@p9O;prd}w9BLOR=~cK4>Rg7jbzoLI~=l>P%NygaixL@bVM z|GqjF$yy?Y>;7iHiXz>OytO)k(xH;^_f$elM-PV`>7=4NCl3wrtv`Os<*Kgoaxr}7 zAza1!Wq+HZJ4IsM&U&oRseATzg<^3N=|}AfkpC0fprVCP!>|NeCi7VZx9T=P|ZSD3$SS`hg zaza;h@gj4~r<3VL%*&1o<-}RZ2Bn4-6q_D%{y%$*$)OsHR8!5^;;Pg8Di#qFsf@|N zyA^Z{@nJF&&a@759<<d&@G zMay;AQd&tk;#XZq=%0Ng^MCXVvF~Tt!5Y~YvGo*cru4NPGv-$2nkjkj&0sYCVr+F* zy!OKUqQV+x1~IDg1gdo5jOa}bg}Z2mrrgKdz4H>(uKGKJE#z=td*b3m`I23}*Ng01 zDHY)#D?Q_j+iz_(A}$G#T7HmrI?z(9G`}CWCy-ppBGTe}XC-d}g(Wq#L1pNl$EN?)wf4bFIJ}Ja(oQFfvr^I+QCAk+)Kbk zrM>f(gqTsIz`V8oSeZHZ<3U(OobWoC8kRKV^~=+UTaPI`hZPGRIa4(PgQ+k;mKHJ; z*B02K)arL2CzpfaA*R>Q6Q1Z0E~zXUf|E()GuMT722bivR&RASHRpEUyRbf-wMii6 z_KjgGgxnWInt23$=jOb7=$XGtJViB-fAP)1%B7q@4)x0eBC3~LTekD*GSIk#Q0!IE z@l2!f$l-8zaj%SY(2xN?*~28~2_fNgRMg_w-3B6| zaIrjV7}wK=kF;O;LSep`Kqs<{wy$s{ZSyNjDD%{h!m-$g>S=*hEpd7+3xR6=go(4fWj87&BuW+PXd#;vZz5!&U2Tq;FwVBV+u^#UAl!Gx>Bf>n~Yf0wiY|v$cw|mc_e#~Ol=;X@U7K;s*RYK3Xq5azQ%psTzbbuX z7?N2o1(LzyBfCyme0=+kcWVO0pcwJWh~owAKpscR_oHUf4V$VZXVlM;;bD?z<`ut) zC<&WJxjOJcX^|JZP?J<)iM*^rrPY#7O%btOc^JKQgDnISg5ZH-xHIgVmTErsoG0+q z;?Cn@<*<926jANn44=i+gg<@OT3+Mil5!YyYHm2fmx4_(?Cgi)O;U*NBYiwTDktK< zE)Kw+@CgX{piO&&nQikvFnw(Rijzp@mv(knk?s+hrUD`pa%dA4!Ks#dQL!GAFk|f9 z#Ocm{m&68TH@)4bz6XjvRjp}n{CIMzGxkI(^lC$7UOap~E7{qAbV1bNL^9L(mP&}tKxtq*hZEbNbED^8`}rbRJ+?W#5>65)hq z@}_7zz80fiy&%~cu8R~&v~<$w9pmiZ!fbu>nRK44efDAh!7Y^avC_7fjCT8@vS#j9 z#2JB=nfkeeKzn=;JKkvGLg{xbc>}fPM-?SZmg+fzm z#!BBU0{T~rSUf2V6VE>$cP6hMCZh9Z&6Izm|3PZ8VG}z~)vb0cIoFG|nuuXbY?rVSZVsmTDsH$q1@l~LmnHMu{unb<5#qr~W zAFz-l0NLFCW1wuY_lBDMVL#*9<{YaM$I=}FbdStO2w7O)9>n9z=FeXTMnpXX@+8r|1Zzex&&6(<^ys)<}_SX;hyvg9Xmz`o@K5wez zVa>gM&Mj%k9tyzxLV_oLxA$5QZ*@*ZE#j(+=cs zrS9?FMc%L#e&*EKxkB=fbsX-DEjpZ)|M%Hp|035^M#9 zO`kfYAV3hyQ$rk81YE(~vzEceaBu|=tci=eaZj~V_nf=B|h8wDwE zWNU^Q%T4p&r6)w1-7hfnze#rb(a|s&h0<_?j3^;G&)xFI`#arfo~P`&C%idGz7qV! zckwL^VI3%oy`Uy*d+y!%VlpH0IFO4kZ$IM-!sIj~Ai7`x+hrLv+cR(X~9kIKXQpiM1yz2ie z3cM2u_StVWlUw;5M4Z_7|L~n!bEo^D`Q*jc1tor0FU>98;p)GXIcreiu{l#n9rXCw z@`sM$B0N5B z^0jEXf!g7R-;alP*-2?Oyeg0)xXj(H@b6s$)q`yL2N>Q#^mqD`dS8Y&s3K;Ov9I7f z0N{T4lNJQ>WzR{*{5kKa7`EO1-th_{Ur~gh#DTt)eI{gNjMYg6rbK&B>6=6Y(TB8E zJ4qXkkC_i6*B)m|8zRA&vG3V<_i%SIMT;bOU}^pxart-T<#&Ex2Pg_83a$lT9-;^V z%wR~i6LS`|?N@(<2?7-_e>S^zBk5f}5+_G-uqQKkYGNK79HZemv|7GK=Ox)7&El+UBHaP;=n3pvh8!Hw%Cc&`Wdq z{1O0@TES0pC#S|%4>xU`?XYTCM`YvaLt93)%m*j4$oXPyoUC7Vi&@O{o_^uS+sbb8 z5Lv&5aA!R|Lp_G4ZIDzQ7GQMPP+HibIb(DCC=sjAPy?c@L-obH=1WXU!$G$RTI&+( z9<@QS=(-Z@K5v|;j$_d%4r1_rNVU0t3Ak%N2cawH-Y?VsJdinq8ICuvJk#3?+Q}|~ znBY5e%+jLwp3yI_G>VmG7@zVxS+FZvl?H2?dx$xDw38ETq>+Yx87=OYpa%UC=KN1P zgl3+2bU|~$EAyX~%O?;Z{m*_)b(NZUXEO1%o1p2(ud*W1Wz~lw8yFvYdTb>SbEt<4 zr2wKOY6ON0;_Vh)_+nnch1bFAA0`y-XV-~%#Lr0XUIHzBD8r>gB%jbW(=?tjz7^Vs z;bV*Anwj~Ij7+tS2y4kXlE2!W!zvTC>Cwcz4?~&A=zpoM4dDgBz z{OrA`@$HUO7d`q5=QCFB3&JOFHk^j_cX1^nEZI2dD}yj+jPB7t4DUe*<8(eas$xcL zec=u{hAK42TePqP>NUbJ$$ZLLmkar*T?N7?L#HeK(qpMmn7*KZL92qK; z!4XyEeTxku^|SIO48l>8YR-`|xuDgoalrS)-;hW@e3qX0jb%Z`r0Lmw&-hYWN7 zW3vM?>sqnck+RD2nsV7q;{YdZBSZgYR-9)7JdlON>x=QP)#SfIbhwVC{i}%vkv4Op zd=%amn4!-06sBH1+}ttY-8TN*$+AIJ{3DavZv=nGIL%dq)zbiz^jorzs;JaHDOi| z#%+?)ML92KjvMa?@t;qlE#F1?cnQ$(zdSLNVdyHRwy$*(Gw?_WGJGMh;X2X>QDN}_ zNP(C61X8T+qFjHT;Za%ogu4(M#Hm_g$q?n>2`4Z@al9zubNIHu7ttd56p zv0b~s_tD%BxPv^pBT_sY8fI)X-~N-J=bu4RBW))~g}B-LXa1WU?-nyk><9`k0dp&y zeLX3&kz4*EQaEocEXYP~BpfbUut%G^Wsa#K1^0TF=qn0{dElEsh;QMV&LCBk4}SzO z6_5zd9htwjkDKARsO*XcDbNo^Sa3X3v8a>x1Eq|QU|^8Y=)d3+P?&B;67yKm>XjZl z2dxTw(Niw|$pocOXp{yp#Q5Fyj%m4%XLIh0S+JPy zXY7~gF{~#ZJVbXFw%fkXG`b-^KQ}V{IFM<%MC@Ubap+_vi_@B8sD2NCGD#F;I!DE| zwrKHiX=>M#eq{x!I zJC;~&jwL1l^8j{82r&z2xfrXhquISV@Z3R^skXZW9*zPUaJQM0J|6SaDF+>0HD$#U zQ9r)^c@ZN|E`kuT9cThn0%HDXw4|Fu5^H$uJSoee%#ZAbwNO;B;v^_^CvBV zRJ?5Sod~ZaD^GIo+sYJkk=GlftFMNWZdlNjs|i}M18lEkw_bH?C1ENa%ScTr6v7=c zjT0H)fA+8mayAWauA?k}nr5ywE@_I;Xn|?nl7c~LMkLdR9{o31n)M5B0je4KK~JpH z82VSIXta+}2i2zYcpFbBnt{8;guPv#@)Z!D)r$SC`R_zb|ELY?huv3E`B@pw>1IMa zaS88j7cK9ZN54jf-RG3-pv8$$15<&6!d+G z`3b}eCx#5Me&|T$Z>tSgSAEQkufW{Z9ILxJi0831mAb2U>vJj;t%qgdSrcQnijg<@ zahK0Wgx|U{Fz016BNCh{sujY_gq|eg{){OEYkF=ErwHixIO&|%W0aA%C1%BF$yF?b zi;qYh87dxy&Bp8^6sWSEGbj$76}uW^_Tqi9eex*ldwsK|*R{<@M5B(c{tg=P5N`d%ex!9s8eh=4Js zn5eCUl-O4r3G%^-yXF|(HaylX-fVlEs!Cgm4?3yoO~RBa8okxPfAU(5S)#ixq5Ay< znYLl5b%5D$h18WPm`{&o zE%M$!&$F;g=NA|K{G9rBZ(OD1XmAUSCDscWdU^6rf}9l0oSPjCUkCj<8gor+xb-U+ zKbe+v`SutUTHT7ANJqbYWW)n0mU_0ei{(w#WuD!bzz0w1NulDDl@aWnOOE2lP zvftLXT{dfWse8`s4s4E0lAiPK}>=Wzt$|D?$@4lA}*Y)Dl9Nsx=f!B0PQ1B}LhrGh#RC!|= zeVB4xo>6=!p{Lp$@A6v~AhP_kg!m>w<9Ed|l#_AgV}o=ZjW5M_PI8@$4)7kLioK>r z74f<)K^^`pRPhU#<^0sQ4&wAgV<$g6TicBMbFZrrR!bmdKQiVDspq@SHpQ$w#b4sa ze=U=4Zu{`n4zEvTMfHe-9~~0bo_13TlZW`Q15+tu;s!`S(78jvo?e>!C7_iwt%b=P zJLzKGd&2~A;2MUI`Ri=LVhC z(s?XbJ(QlHHB@d?6;5{N?aPj_0K2zT=jItZqYbyv*Tskic;$lcYG`b~_*fBc5J&s* z10pUrnLy_O59F(9J;4iyTCTAMafczGIO?ga&e~HmWkPb#OBy`hfkLWvcX}G^!%LToX9a zMVqZE515-$U-DAHp$z|ILiKA5$gVqdD_v>5~ZBPx4Lk(?j%9u=Ynz zYqR3!803XDS_9L%1T{+n`-qar6U5^&yPQ?W0j`5IOW}F zh#>6+)p}oz2yg2Ehz_tCoF{p(b{eq?$$5xb#S&&QiP(z%XaI(@U83{SS4P%tvdxI2 zIk{hJaHOlhEkkQyljwm69P(=E?A{2SeQ(0S`+j9(HbDu~_C7YoOuQks05r1IS-gGH z;NgfE#aH=sGrkxw3N~wxE{`=tJW@UZiE7h-0ej_qKI7fNXsYV9@zI?ikf zQ(y99HkN3?Y*XNRC2w{e`FI3Px(+@n{b~qw;eUlF04~2I46bJxRF{7PiTTa`pzRvZ z@_EmQqmQk{=}e{Z>`kRrvk&=wCQK=l9(o*~w7sq){#T|d2}k9ZT*7}BGxxeN`3Gw& z8>9Z)QNsxF(%iPj2<>ZJ{!TmepakF&P#sDbf~0TLZukF!R6z&3Q<4Sui;Cdc> zo{(WowdgZJ^0LU-uZ`*UUs z71K!Sym(pFlMO0Y7EiU^=Wk^87Jd5aibT*U%w zKj)4Kd6g)5u{X-FYo}Z`EQYZjInJ#|iq}7C-qz%_mzxnLlLk1z|8i=A+=P#B&eoGS z5y+3;+ICRi%CDJRMJjwvVPsE=!sWuets{O7lKU4gAh%1PIMLfc+Hsh-REq35KduL~ z;3PZEV25>fD}5tc^CC}}U9X6<{`tF#D--c@u)u%_=xsn2^ zrTAa|mH$RF@;_zPRKK|0`#Jxp#{|lwY_}XVIP%=`{6z}i3a8x@MBsF}y)8j~2^heQ zv%a-E>pLP(=Ei>G!AHaUbYDQfu@T9g=ov^9`Ez;)q)P67b5qJ}o);%sIe8O$V=!g# zHIu@VfI+uBdP~-l)>zI59wZ6UcDnvEUFXIsJ0XeAa~L#*W)W=<`z*#}U&~ zv8^`UJM&7zXOeT@N;^sxKaIY=aigrtF>ZKPIdg{8gDL)ibhZ1X7+%>fRkS} z%)A6u%wJCaJPq~*sA8>CNP`Ytr;}>JIR*SKl5F}Y-*iwQK?woEql(vE6xTHZtCluv%={j2;vewV@q}Q8g2wRepgcipf|jVrAtt8j);($dZ#)~%MBI|FqaDG z5^OFOaE-R<@7DcZFik(nTUUu82 zBh`qt(a&EayU6ZVsoV`?fTmFI|CLdapa%WaUuLvH-07O_qPdRgsz6q*8wZtN+*jgn z$od~m-LJb6pYn$3H;yr2naD127TYDjhsQC?Sa!bQz8&w_u5b|gUv-9|-lNfnG{`09 zIazAV1|-PuE`iaOc!Nq&fHH0^nY^vYZ8SojzU91QSY@I^R6XpT-X&m=&E`09waiQgGSuTc)`|7829wg0& zws|#iA2wVaFwW$dMV~3o__;ER-)RkX`Sx=^-2yiWW}&b`)0`eJmO2bpxGlmO93dXX z0b?izP&?L8d#rD74ug5S634U1Zt;sIFc4nJGC}OS7W(TT+g%`mlvwB4f>UHouuWQ) z@o3i?_czl|cO4kZh!+9o}4E!cTQ3TG=Z)C>T(C??xKnBw5)J_g`MNO?5LiQqzZ*|&a! z$Q(RIAsX}`hao?ulW_@rRTDoaVt0XhiZ3KMnO`)j)t}rnK}`C0Y^Z?dJf(gi0i*;T z5`}^_4syDUy5grYU{S;1C9uIta}f~jAvBG&Lhd$&u(OfRQC=7OqPuL*i0t+)2@1rW zyy;`<{b{-SGpzTQKwx&=)j7%c^5BBcQVhQ007F_ns|$|MKex^^`>DK+J7uG7LON+d zR6Al9!WSw&`{;Y*9zzpK7vO{!geG;69o6)YMh?z)uIzgfdkS6UrXkV-k5B}a zhT=|aYv9+$3Wif2ZDQ2_K3Male0W3Ro}vivC7_q{^qjD?@9z{mXy`@%^qwNmC2)$~ z4oWVqK;oaT_;{vR5HplfIH>2ly7`GiSI!e(dI5nzK=1|b{SDNElq(7bnD>hCmF+3o zut;H==`6g2s9>i?_B~?yc#v z?joH?HGrD=jd3iuND7@=*{JvA8yeJdam+`SHjLYSWWD)r8WNH{_~c~F=k0?fao`y>p&@TN!cp8S5@|g4^Ubf&Q-_L%_qTDK z)<)(Z)cS6*Vu`I`L=qTxMaWuw&e|8Rau?0Hb)`MzcYb;hntfb@6bjj1b$eI z_C&tyUsI43b;0(k75g==VZ~@cJ5Mm!MPe}jhN)w4p{ZrO+?zjLC-xeSmp5iu2-~Sq z=2&oGX&$1+0z_~nXcx#?!=qo_x#i=#L?x~D>dv4%hb8?z##Dd74hMZNmF4xuZEZFG zc`Wz6!KN7Ur+%S+ycEl!r%xV!vy>{i=e9?H&niG+_l+iQ5rEi;}0wH+~%J^_KqCL2=%!H ztY}V@7#-pgipuyOX-MKvJXOG8yCLEE$l>J+O3WF1L8f&NtOu+FSCr81WUC-&((Gjw zPZ@Zw&RRHbdDTR5MUS#9XF4y&cig1nJrqt`h*A(xs;bG1S! z$&b)gP-C2+*9*^F@k5!(7C4EJt;{@*kr~>4!SkN|4q3s}8ktX3V&bz;wqN&~5=;=U zQrpG;T&|jOMenXW0xMbnZrHBTRQ@PIy7DH_d(5A#p>uY#-}s%jbMW%A(NLy9f@Hrs zL}&Dj!Nn?V`U0gJByx?iUJ*8Uu8ko`_To=J0V`K#UQD0q(Ch@S*2Tb&DfmI2q~_2g zD8an=8@{@mI}=r^wG)R{Fuy7Lh%SLg6if@ojRmZ`?TtDZjrG_L^3OqPP`C8NJ-k$w z!r+1AcxJMd_AK6g)&JY{gZ1(47oq=5HPy=1I;_w?M-0?W*7dLR1}$Rq_lg?SQI)qIW4oQZ){KU<(2Xq>Tv3Mk*Z zCGvWe;W(mdl!Zu8grIPf_?4bNo3x;HRVgp8itKdG-5Oq)vMGeHK^i*r+}?*oXgEqr0)PR_bW>eBJMir9^z zY|_oNE(q0cPw`pbUuAri@JP4ajJmvb*Gawqjhk2i8D1l}CIGUPuLuE= ze8?lu zRYHe0Z*I8uq*g0?g|dNEY>Ocj$?(QhQqXe1ULI>ZHAD4|n~+aTfY#$(nQ=_Hk3aaj zmuVxdn8W=EEq(-AB8ENyjvibO59@-e?*-Fu{N%_Bh{fhZud4bVC1@g`+CJTTQb&IY zOmJTUvSZaS(lKhkl934r!~hWQho~L{TJ=<@Nr^GKh z^nxkl@2uF3EG7}q20;z4b95I-!ixJD>hAth9JE37YYbSha~N}@V5hwKW%ydm$;(Za zQTsBsKsxFNyU}XxLwct})@K4`UUcN74?QBat+@eF1>s&sp$8_?t)7((7|r%47`It1#k`+d|K4W1Vk~+r3aCvZEdzQ|zi$cw$~CAj;C?lcf7q=Ns=A7dIQz!}Jl7 z(#bmOmu@|%6k^Mh$d!Vd@|W!c@dk9CPMsL0NrfE! z3*7iS>R+j8b&@ld)F<30hDz3U$uhmOZ5%aYZiCQx?IqBF zC4LEnXZxnj=e*MF`7T?k^F(eqK!#0jfwkiq`-f(8y_;hlwvE0{%S15-bEk=zT2huA z;$uY5iCkg1To$qQAxWrqj?(Fw2Yc^h-Y*l&rK$! zt<1JOkwMb+1@O6YuNg5R@n(BLhV$%mP$^0cEEjNPx zX5({J7~o170E{HZg%nBWzhP;>m9bIyo$4=yTi#){?TxRfNwwQh;p67%{0;<*J)6Sw8kmzYAx~(VX@x z7g5qlr5n&7oXPf(bLc^PggsyvavuqP&G^Yj&JNcU7KxR+sQO~F+3%j5$5mPg>(=h9 zx>lCH4}P%tfnB%f#}uxi^@9f+ts}fGme6QUt&keE_&T;-u}p(RxuOV!U7F%?)B(dd zspo2wH)J{+nz3qVKwQ6%2K(vP8wn#Z@UguyDHzSJK$s=VqL{_X2MKN4O4^z5k>Zki z@MvO%|tu9BIWK7s0p^Gef!HG8|9Brscpk(==1)@cqCMvHwbZ zM)QAv^fy|_9~HXSLacwlpn_ca>K`>%SJeA&e3jqeqR?Dj1Wyv(h>R4y#~7-SRY{N{ z`*6e_BrLZdYWkE8N7ZPi1-G^!eVup}tNj^OzyVUx2KjNgBXh?Du`0PHMPXNJ)OT1# zS1gBpzADglJ(f=Ec@T`r!LGj?c+v`Rk1tjgl@!JX3xvhxF-Y?vA|L zE)iQq@~~7&n`*I1oMb2c1bGhA4}8w$qYqOAKRa&`Ua&yU%%6L(taiH};+AD#l{~39 zU(ag>9d+MyzC}>CheYV=tfHBoPY5#qWxGhR|$;som1oAnwc-`b}Fwz$A8eNi(D2=3sUAnJudU>;Cq^W}n2F|j`0N&4~T zD%I(6j=XKPIa7Ty|2rx9laoE#_8l|db;IXhaebd7_Pi08`YL~b*rNB z?33bOVoVSO(Gcu9HfM?8>;z2BKMqDIcFvV%9J`B}qP{fd5&E>oJ?SM_Z&7nSsIzv* z*v;^FXtqeGwtUc=n*9i4h4`i`Ki1)$V%fkB1OCD~Ly5JL~F~Z1C?k z1B2{eBZ_jZUSD$L;$jy06s@*a1ymFwO4reMXRmfSymOC<+ot74_QzL89|w;KYo$hc z{ZI}HON}@5OtjHy_c}A3)q09>>@qdqQpLmFnyv*4?}XzQar#Rl5qHq;1^~UgB)-#k z2J{k`3rex1WutF9zaT@-MyAy#V*KGOs_MxE^FU#3&FoEG;lp5K)n`ABo?Cilij?*N zRh6_wU-IdDn3STr7qmhAF-QIS5T%T+Vg*}KFxBuCsZ46M@D zT`cz3v)VSP@&+C>x@AeT?1P3+oD%}7D z#aSKcm~0JmO+)gK4+04THdQwf!bpwD8iB(Cyg_$}i7xGqdvMm+nqwJ%`Ab~I8+W>> z4};aiYN%7eqCOX6-x?g_*-HT9o9XCdZEZPoJhf=DC5f}huz(4huaBTQQZ|}Ip!&GU zIop}0DQ3&_X0Zyu%UbC5tW{%o5aGq~J5Dnr;Qr!Ih(aGqsnf4V$rSt<94%lzPbzsY zhXYaX0ur5v<{vL<%hI+--Fp5^_z^Vy_zK)xg+6meGOvg}c1+SdazxG8|1p7`>l-XY z?l>y^^#fGeGD&EV+Vb_B(Qj8V{%*A+=u)qp*|2dYn1Q7=%jcsvKw7g&Jp@G*R($&! zUpUA!eBw#G+kcDtckC(z@_A!@EGi$`IzX#wf3I9V*^Y5q8;IF}=;`q!h!VeLp=N}s z&w_39nb%ZDUc~ed&1C1nF5zglN+duMph}AW)Bu3V=j#j`bEoOX5S97>9%2^T)HU3A z8ml-DK^mNuxVFeFQ#Kyw*=kw7$%&75DR;{r1}zH;GQC8dtR9bCBLWj?*J}E}MB??O z_1{(s2p$2@B90?7D=*ws?)cPTDDR6WXo8}crWpjrCb_>GK#>w+3(N{)H|mHdoJME) zUzf6zJ=1x>A2;=C*{tNooZ`DgRi-sbF^?z3PTF!awb+AqN5m#MJqYC`wktv;I~P3P zc_5PN5n3aX0~6+0$?|N_|s7)%3N=hb0Nx{l3O=Tk}$=wx(`hl`_ke2AGlxWapU(wdZU;`;xor& zFJNX9x!q45My%bpdXcWqOY!1|GS{MxqWT2(g9NeH2tuk(ZgN({&xVuU%U4K#M|=A& zR9E>C->3O>$<;7!2?~d~X^J{JC+9P!%wx5Y4V^VdEK+?GGP-Z{l~a@4d*Pmu2^IPC zt<+XQ_QH8MGN<-WO&;ZCW|=VfS-XVCa*6v$Lc<=62I`)%R_quz6%env477{|itrtd zQ&_9}v^d*rO}|5&2mlTW{tP9t)aA`0?_78dCQqA{<3{-oc786+Hr_tV-soziq1LA4kmrX5;;Y*5Vo%qfgM)0dy&!@@&>Ad>7aEgB6rR4{my2^vr|J_ zcmFfWi{ua0Zw<4aEbQ5>+?o$X>F|N=N_(xjf$ zj*;Eiw`^&W4p3S?K_W)Gza}v~f{I@a*scW1YT~Vz0I`$R<72JdZ(p6fA5>RGc6`Gm zDeTzvpJzhHRsfiGQf8rnBK+xY?ESghXOd}}Ix@@KzOG;A9lq@0MKLkZdLQ>ebCt9~ z$B+wl=lX+%U z5?E4QicfkrgoMzXNrouuGU|0qGn4;j{?;2!-J+wTG8dnQx59K!f;OXu+)|Cf3h*bH z$27C0!=P?e>?DM%jb0%rgfXX}#eH|nTdo5tje)4XUP+g@>iiUi4-AAj7Iwq3- zAKHeN&l|K4JD1DMlR#HyCBF}zHPP^`{$1&dw zo<_}!VGZOWh{s1_LHE0n%{R#e**IAWnsC7Be|W5wqA`8!{h*{Ow09cwsIYB4c7cIK zL0S^J`)e7uhd|oU^8p@tQJAl&kk)?0){?x1$cQrmD#~rrr*Bc(h6NZFAIAmC4IWIa zo@DlIF$^UHoIO{VSk<3q1t@*6tqqU;yzW~AO1MCAWI}TtkIP++$l^UpFPdhfyWKaK+EK^z;KS$wXs=e&EGh2R3#I4q@)ivOg5oRi`v?SH~ z@_U*$R`a1A?v(Epg2l^T#KB>O=rHMuNLvDs=!f(1pg?vs{ye%`^J1|k@Sb=p9VNj=3frr5R697x4E9=RRS><<^g0W+=6&*40wAq_VbFvHd zaw~+Rd^wlP)9l|qf`XZtzhW)vHcA0txcOJa9n}q@z=Bwgu~8{s>7eQ8?~{}{sp!ym zDj|}LQMN)>yJg&1$#+>S^e`5H54MZKgUJa77lT3MKcO8hT#~ zt+EiCU&k7mx?UC;(j#ch8KaSo`5c4|AU|7(aKY-F+QmD=!MEAao} z*HpGIf#;j3KP)oV$pbb8?JEo|=KDA$%gY6<(p-%QAv1wH;!Ps~D4*(JB?1)bUQYS6 zz3^E531~G-z);Eg|7q{5^Kv&NdWH77>)I`x#*2&R(q@*j-K*(kw`rMMG+XsFE=fj!K!2r~3$lzvGGjs5%Y9GAxxs zk-IU(XqVm1TMY7&NyP_BhuT%q`Q?*PjNFUiR>O=h$48?EK&~8it7j9IP7_zt(&MU|WfQ&_D z7;#j~{fF3fA=$)lei+}}|V0JLPY8b^3S8Z z7+X}vU8!;I=0W|`^TNhjxymVB z&=(9Pup4m{I08mWgG^6UsAy%T#)flz(DQ)$9)0Q7eu`W8+1%ihlh6=gdVLXDtP{Dg z89X_;DqrgywfZPwtDlWScyuy9VogR;YbmTbPygpSEDJKthgr%}XhabEO(9I?s4<9l zd(tz9>WMySkdwh=#jiTF|4w`P3#97TG!m>Pk3|jTE~L zPM5^CkwTMR2%XAFB8kL|@N?5F1-Ib$D4>RXy^8R~MM_ySYYs-Wv|#Lx9a|Fd&TF~m zf@4WkAz8Ds9ou$mirYe1 z+(pRB)|$jJO_ij$P~Kq{$uqBWN>GH>mr+y%=S*$t5H^e`_Y8A@4hlZSTHmv*mwvfw zwk9<)fS%d8kU$doZg|#?ewb=mleLFfX%Ex;~fR4Ko2XN5YAi`v88dl~1Z zJ&Tc;f(0{zaL~*zV{XO_zS^mBpeJlVt+{Ht>(4g4DNgCA!qPC7FHJgWt|Rpl=fuO| z$TV>o-^hf|L>{2-D@ouM0MvaJ=ZaTkE8^}~!hDR@+`~tZG5~?ki`E>*vzwDvp>rlt zOFcDh_sg)1UI8mi;>!`9`gvZZIwMG@O#T{tk_@_PBc^48q-fOB=SC8_Lkrh%B=pCUkec<{6a^*f= zsn8D*pjS%|GYFF;QP4x$0=fp`I+eujQ;uj%VS{sedu}-W)z#Cb5XFTV4kAuIBFM1B zX@8kH{)(F+@&St&qo+v>_H0ESXRDvQ4UV~49xq2CY>26wwV5!6F5JUb4s@%fwMONw z;2j%bUv=J3jAQ$EjX;L)Z4~S6&SWDyRwFe=S2Qs9@B=+%(@ouD4ykDecl=W(AYI&c z)>nAEJ#D)y!>0G8%H6GgO1GUPcu|42vSgH8JzB1Yl!N&2ZUkjz)sx5s1oEb)BLT)& zwF->6hXV|SL*ianfeQxO92Rqv47I(`gDPB+gr;Nlh~~I2Pr#h~z?_0M+J$ zH@x|_9Xps$l%%O&A$8s146x=1sB__;ioZi_J*<7hzQV5JHYPUR(c~j8hoK5F0Mjh= zwi{on_RX_1Z2h`9SvmO+Z0TU*4T4PsPGfC|kTm1U%CN_!05UIVR<;Oe?RJq)Dweb0 zqoj9er>Ftt6$i0gt+{F^QRvqS{|ZI;8KoPNJC1TpHbdu*wR9d77(?@#S+BDNSu73l z%^Uyj2}GiF`mkhVk#J5!!Zz2wLI2X@6Tk7(kV>GiNkxI^PGVDyc}$3i@WkzL?HAC$ z=d%=1C%)u2kTRuO!GO3n`LH7+AEsU%u@90K+fBsRm`eA#KEB~Bm?*~{woJ;B|0MJg zr3JDto*;n-MY-C^SW+6XmDJ-auwEiU*b-+Xqs7Rg!X3r-BJ;Kg#jz;cn#)?|unF3v zyM$Pr@0jO{k1?6Ye1s^nz`5gQA%U>Ab{rb%7BIs|Mt6JjHja6H(X6(4t?=&jLz7mEX{%B2662@Sec05^ zVD668axwY022F(M#Zyysghu24DpeN0mzk|}PgP=6&5**080i5-YWRlrYHitY0sD?M z_o^HYRs{2D51RavswprSeLKAV-rpHs8D{aXf|+(wtn3&)Go$WdGm|aUgk!F&0wN%i z8@gEP+Wj^+k0ZSjjm@rT7SS_6ETr*c3pzgA<1>U?7$6Ai`D$a^29eUp9vR#g1^%;&#bb%PG+RUQafz8}9$_hD&I zUsu==o8&f&HS;R%K7G)XF4s6S6S_26^e4K@#V)0;DoQ`BS8buTVv$!)%U7RHo+Ptg z#o>(a+;p6Zt08H0|3>TeXHhauB+pCXL5iSu3!BUjO;nH@%!%&4&z)J<`(P(_-9dqt z?~J0A-OAOGaBw%##P4*_b6vWGZ(BX@Adc)B->0uE8{(yM2QV8jrx2}LQMzZaUMo5> z$IvSHOw_BlA`WxuFE2r8i4C^>iHqU!?e6!zqt_iOK;fT-i)9c5^REmLn-ml8 zIw@o+rRdo*aCnM47^W4FpP^jByOUmmAIM(Nw|#A8^?|jhtP1rbq(0$ysA%@~;^=d4 zY0M${VoNJsR1fIBfY@&&bVVc1tDDbg)>x3OLwZX0(B60mRA~C)4)&k$b=~BF+Dc1- zB6oQ34Ky78lt}$w-#?_g+LvXL_pv+6YFRl_8C!U#(p)=lGpU7!M#+-^tl=LfpubK; zzfWHO1_=DOk+Hw*co8gHf9dBt_=V1&OOc!%@7_)!2_cP6s(^La4pFa)ymGqXN~3iN z;5>hPAv?-Z!jX~JOBo4_n=m>h0NnYEKs%yAT;Mc4!)ff$Q}E$QZz^L))b+ProXjkb zQ9W&Mz09I%l}ACA{asa%-*6iP@R5|8R#FS$Z=B3nVo67paRizHDG=Iy3|e1&+R!Ir zxSp4wg{2GH43Dcu_+pY_U6jFBZl`0B4{#%MM~9{;i&N+PmFiIF!5tf?bO>wd%E8oTanq}v+K*WD)J7J=ZwUoF@Oq1WCPs_AUJ*Vg!~N#th0;_~3RJ&N9;!E8Ovw|r;7 zF6DrD&*g>A!N)zX%9g#XAm^lEwXQq?L(XmhV!-^+1Xm|XhuaQ%ps;tXezCTD8*fRj zrY@PSoHoQxY+_G(?`-Wdn}b)r-}G<=CTI znv}Sbw@1gJ*zHw<5!$siPI=V0@WBP}WwjN=J}$)xUr+Ju!|b4$tMgp2`BRsCn;-J- zE28wCfa{pQCuWXCENM-CoJxaAYqDAw)lmA?XJjH|$Eo-xe+=IO>`msr8lcrS#amZ{ zJ#yQfoK|+FnL=r&*ixIgqX(tj*MH*z|2b2{?^B}xLG!mf{We6ua%uSfhaK!%b>8=; zj6@82FKt`=d@;`0n*Qj{6#|!xdQ_1BRh6u%d-L>}@L8#QLNEwHM=40`^y8q0!9J?Pa)`OD7X~=_XLTha1?zBD6bOn zAf2e9&*KwsBA7 z7XVoL0-4(4doDEqMIzBaAf_I^kJ{hY1txbrUXo+ndGfp3c4h=NwT<)r^bt#2(ba~p zSZ;Yqwt1+%LW**rXLSgF{zl&mO}fJduUBZ&LNH<4tv_3Xr=7E#F~LZ?fI|=|N`-E} z(B6K67THB%aB$IHp&Yurww8CD!7!& zW4-r$nIWN)cT1Fz+P&@C-9KFvrvD?4t-mKUk-u~2$_M*XQxuN?9t8jt&^(9X^JfBc zh(c=XJ{}<>mZ0<1qfO1k=qG$qeo_Gs|J%c|ef2c^mabX2 z0)x%Tmqu-D$a9djsTa$@KzK6DKIn`CD2KnmL}P(lAX>AEz$gO4dlg5{yq;P|-nXIZ z;lCVJ(wQ?MZ-O;%7&Vl>ON;EURRSZYMOJOC*>b=3x+k>2i+^XEu33Md8><@mW!m%3 zH8vrI!L)(0$ebxTA)Sesc#Ahi`VcIP1LOl=tevK{EEJ@W4=F7WRegx+$h#^TUByyE zoR5YtF$Y$}1)j9s{W$1C!)zxWI|t`Hm&Zb;zfn*TgX38W|7Bm1-Bk6uJhZO$I+t=TNPx6d#lHB zmqhwSRtiiC?CO;Mfd=MpB)$KbJMo`$Eqjo`rPPWOK-LLcsjF;)B{M?%(iUGNG-7Nh zapdK6dRWs{moX^@(~26C35UNaQcm~b;=65~16fNHwUM*g*z?0_ql@TEN@~4 zr7(FwkS+=))L$gOl_4d#rNQHrN5*ze7JJ%}cglQcKP{^DaaT9o)vgwXqPdc<(!#ZgVweJxGTSjdB{hZj@^6nT?8>8^Kr-H4aW>~kBGPGHBxk>(|u zu9z=~o7tatn#OBvtDl2rmDi8YyzpFt^oQ4@)qUqu1-ihm{)LYK$c=pK?D)4;F#tQg z5RNZ=^QTDL-^bevHj1Jbm=PEL^`{&bQQ)j8aOD%~0c`yxP$K@({*%)g{MMRx8C1Pz zW7pWsguA8L3mKnFj<>A{VLC6*L($w#;xoP@1A*)tCq#kVN6Iq*0ZA?fA62P`jumCM zMrsRolypU3b7DnORU%g-q*1u}%RzY<(kHjPB6XZZ9)i zC^?VOtdCCxWt%K8j#?aIeOnPVDW#x6{vcLj?Lk!a$z|o=h$X)&TK}W%|HD}G)p(Jl zw@%f$RP$k4`Wp&<*b|Vy!{4mvr z)gDzxa_mbHiowo2oiF3KkM`%NZZqcT#V|te(E3;ms)<2go5BWx0cQ4-qnV$j>CrWU zu<<^U&hf7`I&aC6Gav#%#H$7!v0ro~TUw7W7zYwlHCq~sOS(QhjTEPLIKAWAA~E79 ztBW^2$JCdGy5G;!a&Y6R;)w4IzfllXu|vpF|F_1pkX~vQxS7*@V_=KY7{mQhYd)vS z`zq5!B;h*tVs+wT;th)paTk?O0D8b*Q>{Nyl=&Ir=Pw08^puOPe~%G_=9A9RP>Z_> zN{!{EFU}cOtKJyUY%cFe=cP~vdJ}9Wxt@xFPi&~%bp53=2fF9O_Zwz*$#PY`IW1`bM=X0*2K|ABEgdPX7 zmrb2ZD8?>cv+p;_)z)mgV=oCz-UJu~sLb0^EavMq)-=`|n5*lmXp2LpKJqJxqALd^ zCMp1)gWjUCFMJBOTGDJyZDWU3*onjHE7{&duRi;tFH23n#$;Qys5e@>Vdw+8^P%FA%{cu zZE+e|MJ;|m`dX;uGqe|rwjv?VOID%BtmCrj|W( zZb7jiwph#MLSJSRk{f2`&r%%~t{Bk~VXdjhcXLhnxyhssEwi`0G3V zd)uFQ|EZq=WmEK&i<$|l-+lhBBL01a{P#bj)9leSHA0JpMQi|ClIgi$XRT%mf5(A? zztdD$_xahXNFHF$weSYKVf_WvW%C7ORg?;tmH@=IZ`**(L>%JX?inE51)Ds%6axAT zkTSn%>MK7_ZkWmGwJ}=+^Nh{0@(m6{8%)LQ(BB*o_AR|cT8e%tva3^7MW8vZ-Z7}H z+r6BhH^$U#Vz)zl6bK^Bhjn4V<|{WGA;iwj^~fKyX;K`4Vyt!SV< zjYs_8PyAm%6_5eoru758>hDcJQ2b_RFQoe*PpLdtbz%M`p)mATQU{IuC<$^Sw`Q%E zh4cIY0*~HN$1&dqtg+NlHTLDqoux|yJ-+wY0pBy}j`=Q(6N;mLA~>?d-lIITT;0~i zlfG-=o%i0mP;1tg0tS1X|*=|ImhetBq2~h>ckGS#*?_j7X<& z^mIH38UZ)fJxOTX2NcDoOaZ*1(HBq-0GMKJJg;c*0hYPoi*4WTZn1{Ng1l%$;3|tC z6r+$!vxrlcu%TSmZn0S*4Lyfos5>Mtxu7#7s%8V(zS6f1Ia!)>ldi4x^{x8Suh;L( zDDmLoq=p;;yuUNi0bwH#BbPk!ZNB)L49QbjG%896`U-W>#+|W&!j(7YTn+VJ_1))0?5yp+<}8^`f#v=Yufoi`Xx-#_a{nrL%9 zoxvsmR8V|}_Qw!2);D(Os{*Rk>215fgvtX*_eoSvmEI525zT4exAvbWC&O(Yz*`+x zK#l!dt@qMw@d`w0~DP0SoZ7QSRNXG0jU`&MQh{l z1PHe!SGQ2Vrji$bUXq^9@>nrIfeUn)iu!9k^OXZ`F!*8Db0u7>`r|?ApBIMPQ$tvWs5iPb1~neFKAo29v0-l9|))_ zmX?j3QMmv%TBK0V$}3T_w~maJkBhg^q{O;>L~w=a-Z2E8O1>Z8I+=<&DUGtPdHtSHN4dmiY~5o)}FJg@%xj2uaG)4r!l{`I$8;D@}Hi{knyHYToaqq z;@?!W$$;yg%4|_;Hfny7VAB8$Oxbczwk{iTE`)4abU5*k3Nzu39K$M3cKtYZ8^){+ zXx3G%_YJv~M7;`C$(B7FQ}NC{sov%x!;;L39ReYoYBbqA^9Vlg;k!dyg`&f#p==Ie z4IUip-0O9p8Z`!fypul-f4NW{2afaoeDrGu@xpBLFS2fbsC4@G#O*%>1pMp^oG)-j zliG5Fohn#uL2qv=ICCG_uSzVY_NnJVUE$WjSKQD~T`mA=^_RFyB>ADkKV1H%__ddJ z4QFaS?$;WK?zj*ZqJh5xn|d{F>FytV zvYfCM8@F5qS3VG1c-Oe02+S^le64vZekx1R*V*MaZ5ap(3&dNRvm7ui7X-{MzfLay z-tynC|M%AY_qF+Nm-$-=`ER)RzY!|GAB~HEg>PUyuqc!I0=lkyo?9$zwKt5oOoO3o iN&^l5%W@Mjvtl(x-#D}1_I15EfIfT(Boo13CjJLNf;}z( literal 0 HcmV?d00001 diff --git a/search.ipynb b/search.ipynb index 072a20fff..edcdf592f 100644 --- a/search.ipynb +++ b/search.ipynb @@ -13,9 +13,8 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 134, "metadata": { - "collapsed": true, "scrolled": true }, "outputs": [], @@ -37,7 +36,7 @@ "* Overview\n", "* Problem\n", "* Node\n", - "* Simple Problem Solving Agent Program\n", + "* Simple Problem Solving Agent\n", "* Search Algorithms Visualization\n", "* Breadth-First Tree Search\n", "* Breadth-First Search\n", @@ -45,7 +44,6 @@ "* Uniform Cost Search\n", "* Greedy Best First Search\n", "* A\\* Search\n", - "* Hill Climbing\n", "* Genetic Algorithm" ] }, @@ -86,7 +84,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 135, "metadata": {}, "outputs": [ { @@ -278,7 +276,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 136, "metadata": {}, "outputs": [ { @@ -481,7 +479,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 137, "metadata": {}, "outputs": [ { @@ -636,10 +634,8 @@ }, { "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": true - }, + "execution_count": 138, + "metadata": {}, "outputs": [], "source": [ "romania_map = UndirectedGraph(dict(\n", @@ -683,10 +679,8 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, + "execution_count": 139, + "metadata": {}, "outputs": [], "source": [ "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)" @@ -710,7 +704,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 140, "metadata": {}, "outputs": [ { @@ -735,10 +729,8 @@ }, { "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": true - }, + "execution_count": 141, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", @@ -761,10 +753,8 @@ }, { "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": true - }, + "execution_count": 142, + "metadata": {}, "outputs": [], "source": [ "# initialise a graph\n", @@ -814,10 +804,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 143, + "metadata": {}, "outputs": [], "source": [ "def show_map(node_colors):\n", @@ -857,12 +845,22 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 144, "metadata": { - "collapsed": true, "scrolled": true }, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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Ll68qNmzZysoKChTeZmYmJixVP3mkvf4+Hi5urpmmSV685rExESVKVNGBQrwz0tOud+ZnQkJCerXr5+WL1+usmXL5mAyAMhb0tPTtXDhQn333XdmRwEAALmIT6MA4IROnDihdu3a6fr164qLi5Ovr6/mzJmj1q1bq0mTJoqIiNDUqVPVokULvf/++ypWrJikv/fvNAxDxYoV09WrV1W0aFFJkqurqyRp3759cnd3zzit/d+zOoOCgnT48GFVqVIly8nxVqtVPj4+KliwYO79IJyQv7+/4uLilJaWdtdSOT09Xb1799aQIUPUunXrXEoIAHnDhg0b5OXlpdq1a5sdBQAA5CJOYwcAJ2QYhvbv3689e/YoISFBu3bt0q5du9SgQQPNmDFDdevW1fnz5xUUFKRGjRqpWrVqCggIUO3ateXm5iYXFxf16dNHR48e1YoVK1SpUiVJUsOGDdWgQQNNnTo1oyD9t5SUFP31118ZJ8b/8/T4EydOyMvLK0sJarVa5evryzLDe1S1alVt3rxZ/v7+d7wuLCxMP/30k77//vuMwhoA8ovnnntO7du310svvWR2FAAAkIsoOwEgHzp8+LCioqK0detW7d+/X7GxsTp69KimT5+uQYMGycXFRXv27FGvXr3UsWNHdejQQZ9++qk2btyoH3/8UXXr1n2gcVNTU3X06NEsJWh0dLTi4+NVoUKFWxahfn5+cnd3t/NPwXE98cQTeuONNxQUFHTba3788Uf16tVLu3fvVsWKFXMxHQCYLzExUdWqVVN8fPxt/zgHAACcE2UnACCDzWbLdIDNV199pUmTJik2NlaNGzfW+PHj1ahRoxwZOy0tTfHx8VlK0OjoaP31118qW7ZslhLUarXK399fHh4eOZIprxoyZIiqV6+u4cOH3/L506dPq0GDBlqwYIHatWuXy+kAwHxTpkzRH3/8oYULF5odBQAA5DLKTgDZFhwcrLNnz2rt2rVmR0EOMvPk9fT0dB07dixLCRodHa3Y2FiVKFEiSwl688vT09OUzDkpOjpaRYoUydhe4J9sNps6duyoevXq6f333zchHQCYyzAM1ahRQ/PmzdMjjzxidhwAAJDLOKAIyAeCg4P12WefSZIKFCigkiVLqmbNmnr++ef10ksv5YkDY24eorNz584cmzmI7DGr6JT+PiDJx8dHPj4+atu2babnbDab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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "show_map(node_colors)" ] @@ -885,7 +883,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 145, "metadata": {}, "outputs": [ { @@ -1046,6 +1044,88 @@ "* `search(self, problem)`: This method is used to search a sequence of `actions` to solve a `problem`." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now define a Simple Problem Solving Agent Program. We will create a simple `vacuumAgent` class which will inherit from the abstract class `SimpleProblemSolvingAgentProgram` and overrides its methods. We will create a simple intelligent vacuum agent which can be in any one of the following states. It will move to any other state depending upon the current state as shown in the picture by arrows:\n", + "\n", + "![simple problem solving agent](images/simple_problem_solving_agent.jpg)" + ] + }, + { + "cell_type": "code", + "execution_count": 146, + "metadata": {}, + "outputs": [], + "source": [ + "class vacuumAgent(SimpleProblemSolvingAgentProgram):\n", + " def update_state(self, state, percept):\n", + " return percept\n", + "\n", + " def formulate_goal(self, state):\n", + " goal = [state7, state8]\n", + " return goal \n", + "\n", + " def formulate_problem(self, state, goal):\n", + " problem = state\n", + " return problem \n", + " \n", + " def search(self, problem):\n", + " if problem == state1:\n", + " seq = [\"Suck\", \"Right\", \"Suck\"]\n", + " elif problem == state2:\n", + " seq = [\"Suck\", \"Left\", \"Suck\"]\n", + " elif problem == state3:\n", + " seq = [\"Right\", \"Suck\"]\n", + " elif problem == state4:\n", + " seq = [\"Suck\"]\n", + " elif problem == state5:\n", + " seq = [\"Suck\"]\n", + " elif problem == state6:\n", + " seq = [\"Left\", \"Suck\"]\n", + " return seq" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we will define all the 8 states and create an object of the above class. Then, we will pass it different states and check the output:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Left\n", + "Suck\n", + "Right\n" + ] + } + ], + "source": [ + " state1 = [(0, 0), [(0, 0), \"Dirty\"], [(1, 0), [\"Dirty\"]]]\n", + " state2 = [(1, 0), [(0, 0), \"Dirty\"], [(1, 0), [\"Dirty\"]]]\n", + " state3 = [(0, 0), [(0, 0), \"Clean\"], [(1, 0), [\"Dirty\"]]]\n", + " state4 = [(1, 0), [(0, 0), \"Clean\"], [(1, 0), [\"Dirty\"]]]\n", + " state5 = [(0, 0), [(0, 0), \"Dirty\"], [(1, 0), [\"Clean\"]]]\n", + " state6 = [(1, 0), [(0, 0), \"Dirty\"], [(1, 0), [\"Clean\"]]]\n", + " state7 = [(0, 0), [(0, 0), \"Clean\"], [(1, 0), [\"Clean\"]]]\n", + " state8 = [(1, 0), [(0, 0), \"Clean\"], [(1, 0), [\"Clean\"]]]\n", + "\n", + " a = vacuumAgent(state1)\n", + "\n", + " print(a(state6)) \n", + " print(a(state1))\n", + " print(a(state3))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -3835,7 +3915,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.3" + "version": "3.6.4" } }, "nbformat": 4, From da7b85b866e380fbd5c48a79593f2f00654cf70c Mon Sep 17 00:00:00 2001 From: Nouman Ahmed <35970677+Noumanmufc1@users.noreply.github.com> Date: Sun, 4 Mar 2018 05:35:56 +0500 Subject: [PATCH 061/269] Update README.md (#796) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 38c149cc5..79c50c822 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 3 | Problem | `Problem` | [`search.py`][search] | Done | Included | | 3 | Node | `Node` | [`search.py`][search] | Done | Included | | 3 | Queue | `Queue` | [`utils.py`][utils] | Done | No Need | -| 3.1 | Simple-Problem-Solving-Agent | `SimpleProblemSolvingAgent` | [`search.py`][search] | | Included | +| 3.1 | Simple-Problem-Solving-Agent | `SimpleProblemSolvingAgent` | [`search.py`][search] | Done | Included | | 3.2 | Romania | `romania` | [`search.py`][search] | Done | Included | | 3.7 | Tree-Search | `tree_search` | [`search.py`][search] | Done | | | 3.7 | Graph-Search | `graph_search` | [`search.py`][search] | Done | | From fb71dc40ddefe5854addc6014a74f9e931f66bf5 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Sun, 4 Mar 2018 15:22:08 +0530 Subject: [PATCH 062/269] Resolved merge conflicts in mdp.ipynb (#801) * Resolved merge conflicts * Rerun * Metadata restored --- mdp.ipynb | 1631 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 1292 insertions(+), 339 deletions(-) diff --git a/mdp.ipynb b/mdp.ipynb index 4c44ff9d8..aa74514e0 100644 --- a/mdp.ipynb +++ b/mdp.ipynb @@ -1,7 +1,7 @@ { "cells": [ { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "# Markov decision processes (MDPs)\n", @@ -10,24 +10,17 @@ ] }, { -<<<<<<< HEAD - "cell_type": "raw", - "metadata": { - "collapsed": true - }, -======= "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "from mdp import *\n", "from notebook import psource, pseudocode" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "## CONTENTS\n", @@ -41,7 +34,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "## OVERVIEW\n", @@ -61,7 +54,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "## MDP\n", @@ -70,21 +63,206 @@ ] }, { -<<<<<<< HEAD - "cell_type": "raw", - "metadata": {}, -======= "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    class MDP:\n",
    +       "\n",
    +       "    """A Markov Decision Process, defined by an initial state, transition model,\n",
    +       "    and reward function. We also keep track of a gamma value, for use by\n",
    +       "    algorithms. The transition model is represented somewhat differently from\n",
    +       "    the text. Instead of P(s' | s, a) being a probability number for each\n",
    +       "    state/state/action triplet, we instead have T(s, a) return a\n",
    +       "    list of (p, s') pairs. We also keep track of the possible states,\n",
    +       "    terminal states, and actions for each state. [page 646]"""\n",
    +       "\n",
    +       "    def __init__(self, init, actlist, terminals, transitions = {}, reward = None, states=None, gamma=.9):\n",
    +       "        if not (0 < gamma <= 1):\n",
    +       "            raise ValueError("An MDP must have 0 < gamma <= 1")\n",
    +       "\n",
    +       "        if states:\n",
    +       "            self.states = states\n",
    +       "        else:\n",
    +       "            ## collect states from transitions table\n",
    +       "            self.states = self.get_states_from_transitions(transitions)\n",
    +       "            \n",
    +       "        \n",
    +       "        self.init = init\n",
    +       "        \n",
    +       "        if isinstance(actlist, list):\n",
    +       "            ## if actlist is a list, all states have the same actions\n",
    +       "            self.actlist = actlist\n",
    +       "        elif isinstance(actlist, dict):\n",
    +       "            ## if actlist is a dict, different actions for each state\n",
    +       "            self.actlist = actlist\n",
    +       "        \n",
    +       "        self.terminals = terminals\n",
    +       "        self.transitions = transitions\n",
    +       "        if self.transitions == {}:\n",
    +       "            print("Warning: Transition table is empty.")\n",
    +       "        self.gamma = gamma\n",
    +       "        if reward:\n",
    +       "            self.reward = reward\n",
    +       "        else:\n",
    +       "            self.reward = {s : 0 for s in self.states}\n",
    +       "        #self.check_consistency()\n",
    +       "\n",
    +       "    def R(self, state):\n",
    +       "        """Return a numeric reward for this state."""\n",
    +       "        return self.reward[state]\n",
    +       "\n",
    +       "    def T(self, state, action):\n",
    +       "        """Transition model. From a state and an action, return a list\n",
    +       "        of (probability, result-state) pairs."""\n",
    +       "        if(self.transitions == {}):\n",
    +       "            raise ValueError("Transition model is missing")\n",
    +       "        else:\n",
    +       "            return self.transitions[state][action]\n",
    +       "\n",
    +       "    def actions(self, state):\n",
    +       "        """Set of actions that can be performed in this state. By default, a\n",
    +       "        fixed list of actions, except for terminal states. Override this\n",
    +       "        method if you need to specialize by state."""\n",
    +       "        if state in self.terminals:\n",
    +       "            return [None]\n",
    +       "        else:\n",
    +       "            return self.actlist\n",
    +       "\n",
    +       "    def get_states_from_transitions(self, transitions):\n",
    +       "        if isinstance(transitions, dict):\n",
    +       "            s1 = set(transitions.keys())\n",
    +       "            s2 = set([tr[1] for actions in transitions.values() \n",
    +       "                              for effects in actions.values() for tr in effects])\n",
    +       "            return s1.union(s2)\n",
    +       "        else:\n",
    +       "            print('Could not retrieve states from transitions')\n",
    +       "            return None\n",
    +       "\n",
    +       "    def check_consistency(self):\n",
    +       "        # check that all states in transitions are valid\n",
    +       "        assert set(self.states) == self.get_states_from_transitions(self.transitions)\n",
    +       "        # check that init is a valid state\n",
    +       "        assert self.init in self.states\n",
    +       "        # check reward for each state\n",
    +       "        #assert set(self.reward.keys()) == set(self.states)\n",
    +       "        assert set(self.reward.keys()) == set(self.states)\n",
    +       "        # check that all terminals are valid states\n",
    +       "        assert all([t in self.states for t in self.terminals])\n",
    +       "        # check that probability distributions for all actions sum to 1\n",
    +       "        for s1, actions in self.transitions.items():\n",
    +       "            for a in actions.keys():\n",
    +       "                s = 0\n",
    +       "                for o in actions[a]:\n",
    +       "                    s += o[0]\n",
    +       "                assert abs(s - 1) < 0.001\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "psource(MDP)" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "The **_ _init_ _** method takes in the following parameters:\n", @@ -102,7 +280,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "Now let us implement the simple MDP in the image below. States A, B have actions X, Y available in them. Their probabilities are shown just above the arrows. We start with using MDP as base class for our CustomMDP. Obviously we need to make a few changes to suit our case. We make use of a transition matrix as our transitions are not very simple.\n", @@ -110,19 +288,12 @@ ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD "execution_count": 3, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 -======= - "execution_count": null, ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "metadata": { "collapsed": true }, + "outputs": [], "source": [ "# Transition Matrix as nested dict. State -> Actions in state -> List of (Probability, State) tuples\n", "t = {\n", @@ -149,19 +320,12 @@ ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD "execution_count": 4, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 -======= - "execution_count": null, ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "metadata": { "collapsed": true }, + "outputs": [], "source": [ "class CustomMDP(MDP):\n", " def __init__(self, init, terminals, transition_matrix, reward = None, gamma=.9):\n", @@ -180,41 +344,32 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "Finally we instantize the class with the parameters for our MDP in the picture." ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD "execution_count": 5, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "metadata": { "collapsed": true }, -======= - "execution_count": null, - "metadata": {}, "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "our_mdp = CustomMDP(init, terminals, t, rewards, gamma=.9)" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "With this we have successfully represented our MDP. Later we will look at ways to solve this MDP." ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "## GRID MDP\n", @@ -223,21 +378,176 @@ ] }, { -<<<<<<< HEAD - "cell_type": "raw", - "metadata": {}, -======= "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    class GridMDP(MDP):\n",
    +       "\n",
    +       "    """A two-dimensional grid MDP, as in [Figure 17.1]. All you have to do is\n",
    +       "    specify the grid as a list of lists of rewards; use None for an obstacle\n",
    +       "    (unreachable state). Also, you should specify the terminal states.\n",
    +       "    An action is an (x, y) unit vector; e.g. (1, 0) means move east."""\n",
    +       "\n",
    +       "    def __init__(self, grid, terminals, init=(0, 0), gamma=.9):\n",
    +       "        grid.reverse()  # because we want row 0 on bottom, not on top\n",
    +       "        reward = {}\n",
    +       "        states = set()\n",
    +       "        self.rows = len(grid)\n",
    +       "        self.cols = len(grid[0])\n",
    +       "        self.grid = grid\n",
    +       "        for x in range(self.cols):\n",
    +       "            for y in range(self.rows):\n",
    +       "                if grid[y][x] is not None:\n",
    +       "                    states.add((x, y))\n",
    +       "                    reward[(x, y)] = grid[y][x]\n",
    +       "        self.states = states\n",
    +       "        actlist = orientations\n",
    +       "        transitions = {}\n",
    +       "        for s in states:\n",
    +       "            transitions[s] = {}\n",
    +       "            for a in actlist:\n",
    +       "                transitions[s][a] = self.calculate_T(s, a)\n",
    +       "        MDP.__init__(self, init, actlist=actlist,\n",
    +       "                     terminals=terminals, transitions = transitions, \n",
    +       "                     reward = reward, states = states, gamma=gamma)\n",
    +       "\n",
    +       "    def calculate_T(self, state, action):\n",
    +       "        if action is None:\n",
    +       "            return [(0.0, state)]\n",
    +       "        else:\n",
    +       "            return [(0.8, self.go(state, action)),\n",
    +       "                    (0.1, self.go(state, turn_right(action))),\n",
    +       "                    (0.1, self.go(state, turn_left(action)))]\n",
    +       "    \n",
    +       "    def T(self, state, action):\n",
    +       "        if action is None:\n",
    +       "            return [(0.0, state)]\n",
    +       "        else:\n",
    +       "            return self.transitions[state][action]\n",
    +       " \n",
    +       "    def go(self, state, direction):\n",
    +       "        """Return the state that results from going in this direction."""\n",
    +       "        state1 = vector_add(state, direction)\n",
    +       "        return state1 if state1 in self.states else state\n",
    +       "\n",
    +       "    def to_grid(self, mapping):\n",
    +       "        """Convert a mapping from (x, y) to v into a [[..., v, ...]] grid."""\n",
    +       "        return list(reversed([[mapping.get((x, y), None)\n",
    +       "                               for x in range(self.cols)]\n",
    +       "                              for y in range(self.rows)]))\n",
    +       "\n",
    +       "    def to_arrows(self, policy):\n",
    +       "        chars = {\n",
    +       "            (1, 0): '>', (0, 1): '^', (-1, 0): '<', (0, -1): 'v', None: '.'}\n",
    +       "        return self.to_grid({s: chars[a] for (s, a) in policy.items()})\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "psource(GridMDP)" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "The **_ _init_ _** method takes **grid** as an extra parameter compared to the MDP class. The grid is a nested list of rewards in states.\n", @@ -252,7 +562,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "We can create a GridMDP like the one in **Fig 17.1** as follows: \n", @@ -266,16 +576,14 @@ ] }, { -<<<<<<< HEAD "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, -<<<<<<< HEAD "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 7, @@ -283,19 +591,12 @@ "output_type": "execute_result" } ], -======= - "cell_type": "raw", - "metadata": {}, ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 -======= - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "sequential_decision_environment" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": { "collapsed": true }, @@ -304,11 +605,7 @@ "\n", "Now that we have looked how to represent MDPs. Let's aim at solving them. Our ultimate goal is to obtain an optimal policy. We start with looking at Value Iteration and a visualisation that should help us understanding it better.\n", "\n", -<<<<<<< HEAD - "We start by calculating Value/Utility for each of the states. The Value of each state is the expected sum of discounted future rewards given we start in that state and follow a particular policy $pi$. The value or the utility of a state is given by\n", -======= "We start by calculating Value/Utility for each of the states. The Value of each state is the expected sum of discounted future rewards given we start in that state and follow a particular policy $\\pi$. The value or the utility of a state is given by\n", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "\n", "$$U(s)=R(s)+\\gamma\\max_{a\\epsilon A(s)}\\sum_{s'} P(s'\\ |\\ s,a)U(s')$$\n", "\n", @@ -316,21 +613,130 @@ ] }, { -<<<<<<< HEAD - "cell_type": "raw", - "metadata": {}, -======= "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def value_iteration(mdp, epsilon=0.001):\n",
    +       "    """Solving an MDP by value iteration. [Figure 17.4]"""\n",
    +       "    U1 = {s: 0 for s in mdp.states}\n",
    +       "    R, T, gamma = mdp.R, mdp.T, mdp.gamma\n",
    +       "    while True:\n",
    +       "        U = U1.copy()\n",
    +       "        delta = 0\n",
    +       "        for s in mdp.states:\n",
    +       "            U1[s] = R(s) + gamma * max([sum([p * U[s1] for (p, s1) in T(s, a)])\n",
    +       "                                        for a in mdp.actions(s)])\n",
    +       "            delta = max(delta, abs(U1[s] - U[s]))\n",
    +       "        if delta < epsilon * (1 - gamma) / gamma:\n",
    +       "            return U\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "psource(value_iteration)" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "It takes as inputs two parameters, an MDP to solve and epsilon, the maximum error allowed in the utility of any state. It returns a dictionary containing utilities where the keys are the states and values represent utilities.
    Value Iteration starts with arbitrary initial values for the utilities, calculates the right side of the Bellman equation and plugs it into the left hand side, thereby updating the utility of each state from the utilities of its neighbors. \n", @@ -343,23 +749,11 @@ "As you might have noticed, `value_iteration` has an infinite loop. How do we decide when to stop iterating? \n", "The concept of _contraction_ successfully explains the convergence of value iteration. \n", "Refer to **Section 17.2.3** of the book for a detailed explanation. \n", -<<<<<<< HEAD -<<<<<<< HEAD - "In the algorithm, we calculate a value $\\delta$ that measures the difference in the utilities of the current time step and the previous time step. \n", -======= "In the algorithm, we calculate a value $delta$ that measures the difference in the utilities of the current time step and the previous time step. \n", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 -======= - "In the algorithm, we calculate a value $\\delta$ that measures the difference in the utilities of the current time step and the previous time step. \n", ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "\n", "$$\\delta = \\max{(\\delta, \\begin{vmatrix}U_{i + 1}(s) - U_i(s)\\end{vmatrix})}$$\n", "\n", "This value of delta decreases as the values of $U_i$ converge.\n", -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "We terminate the algorithm if the $\\delta$ value is less than a threshold value determined by the hyperparameter _epsilon_.\n", "\n", "$$\\delta \\lt \\epsilon \\frac{(1 - \\gamma)}{\\gamma}$$\n", @@ -368,25 +762,13 @@ "Hence, from the properties of contractions in general, it follows that `value_iteration` always converges to a unique solution of the Bellman equations whenever $gamma$ is less than 1.\n", "We then terminate the algorithm when a reasonable approximation is achieved.\n", "In practice, it often occurs that the policy $pi$ becomes optimal long before the utility function converges. For the given 4 x 3 environment with $gamma = 0.9$, the policy $pi$ is optimal when $i = 4$ (at the 4th iteration), even though the maximum error in the utility function is stil 0.46. This can be clarified from **figure 17.6** in the book. Hence, to increase computational efficiency, we often use another method to solve MDPs called Policy Iteration which we will see in the later part of this notebook. \n", -======= - "We terminate the algorithm if the $delta$ value is less than a threshold value determined by the hyperparameter _epsilon_.\n", - "\n", - "$$\\delta \\lt \\epsilon \\frac{(1 - \\gamma)}{\\gamma}$$\n", - "\n", - "To summarize, the Bellman update is a _contraction_ by a factor of $\\gamma$ on the space of utility vectors. \n", - "Hence, from the properties of contractions in general, it follows that `value_iteration` always converges to a unique solution of the Bellman equations whenever $\\gamma$ is less than 1.\n", - "We then terminate the algorithm when a reasonable approximation is achieved.\n", - "In practice, it often occurs that the policy $\\pi$ becomes optimal long before the utility function converges. For the given 4 x 3 environment with $\gamma = 0.9$, the policy $\\pi$ is optimal when $i = 4$ (at the 4th iteration), even though the maximum error in the utility function is stil 0.46. This can be clarified from **figure 17.6** in the book. Hence, to increase computational efficiency, we often use another method to solve MDPs called Policy Iteration which we will see in the later part of this notebook. \n", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "
    For now, let us solve the **sequential_decision_environment** GridMDP using `value_iteration`." ] }, { -<<<<<<< HEAD "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, -<<<<<<< HEAD "outputs": [ { "data": { @@ -409,30 +791,21 @@ "output_type": "execute_result" } ], -======= - "cell_type": "raw", - "metadata": {}, ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 -======= - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "value_iteration(sequential_decision_environment)" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "The pseudocode for the algorithm:" ] }, { -<<<<<<< HEAD "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, -<<<<<<< HEAD "outputs": [ { "data": { @@ -465,19 +838,12 @@ "output_type": "execute_result" } ], -======= - "cell_type": "raw", - "metadata": {}, ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 -======= - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "pseudocode(\"Value-Iteration\")" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "### AIMA3e\n", @@ -501,7 +867,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "## VALUE ITERATION VISUALIZATION\n", @@ -510,15 +876,12 @@ ] }, { -<<<<<<< HEAD - "cell_type": "raw", -======= "cell_type": "code", - "execution_count": null, ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "execution_count": 11, "metadata": { "collapsed": true }, + "outputs": [], "source": [ "def value_iteration_instru(mdp, iterations=20):\n", " U_over_time = []\n", @@ -534,22 +897,19 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "Next, we define a function to create the visualisation from the utilities returned by **value_iteration_instru**. The reader need not concern himself with the code that immediately follows as it is the usage of Matplotib with IPython Widgets. If you are interested in reading more about these visit [ipywidgets.readthedocs.io](http://ipywidgets.readthedocs.io)" ] }, { -<<<<<<< HEAD - "cell_type": "raw", -======= "cell_type": "code", - "execution_count": null, ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "execution_count": 12, "metadata": { "collapsed": true }, + "outputs": [], "source": [ "columns = 4\n", "rows = 3\n", @@ -557,15 +917,12 @@ ] }, { -<<<<<<< HEAD - "cell_type": "raw", -======= "cell_type": "code", - "execution_count": null, ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "execution_count": 13, "metadata": { "collapsed": true }, + "outputs": [], "source": [ "%matplotlib inline\n", "from notebook import make_plot_grid_step_function\n", @@ -574,19 +931,39 @@ ] }, { -<<<<<<< HEAD - "cell_type": "raw", - "metadata": { - "scrolled": true - }, -======= "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": { "scrolled": true }, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAATcAAADuCAYAAABcZEBhAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4wLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvpW3flQAADYxJREFUeJzt211oW2eex/Hf2Xpb0onWrVkm1otL\nW2SmrNaVtzS2K8jCFhJPXsbtRWcTX4zbmUBINkMYw5jmYrYwhNJuMWTjaTCYDSW5cQK9iEOcpDad\nLAREVtBEF+OwoDEyWEdxirvjelw36cScubCi1PWLvK0lnfnP9wMGHz2P4dEf8fWRnDie5wkArPmb\nah8AAMqBuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMKnm/7N5bk78dwagjDYHnGofwf88\nb11D4s4NgEnEDYBJxA2AScQNgEnEDYBJxA2AScQNgEnEDYBJxA2AScQNgEnEDYBJxA2AScQNgEnE\nDYBJxA2AScQNgEnEDYBJxA2AScQNgEnEDYBJxA2AScQNgEnEDYBJxA2AScQNgEnEDYBJxA2AScQN\ngEnEDYBJxA2AScQNgEm+jZvneerpOaJ4PKq2tueVTt9Ycd/Nm5+otbVJ8XhUPT1H5HnekvUTJ3oV\nCDianp6uxLErhvmUxoxW9zNJ35f0j6use5KOSIpKel7S1yd3WlJj4et0Gc/4Xfk2biMjlzU+nlE6\nnVFf34C6uw+tuK+7+5D6+gaUTmc0Pp7R6OiV4louN6mrV0fV0PBUpY5dMcynNGa0ujckXVlj/bKk\nTOFrQNKDyf2fpF9L+h9JqcL3fyjbKb8b38ZteHhInZ1dchxHLS1tmpmZ0dTU7SV7pqZua3Z2Vq2t\nL8lxHHV2dunixfPF9aNHu3Xs2HtyHKfSxy875lMaM1rdP0uqW2N9SFKXJEdSm6QZSbclfSRpe+Fn\nnyx8v1Ykq8m3ccvnXYXDDcXrcDiifN5dYU+keB0KPdwzPHxBoVBYTU3xyhy4wphPaczo23MlNXzt\nOlJ4bLXH/aim2gdYzTc/95C07Lfnanvm5+fV2/u2zp8fKdv5qo35lMaMvr3lU1m8i1vtcT/y1Z3b\nwMBJJRLNSiSaFQyG5LqTxTXXzSkYDC3ZHw5H5Lq54nU+v7gnmx3XxERWiURcsdjTct2ctm17QXfu\nTFXsuZQD8ymNGW2MiKTJr13nJIXWeNyPfBW3AwcOK5lMK5lMa8+eVzU4eEae5ymVuq7a2lrV1weX\n7K+vDyoQCCiVui7P8zQ4eEa7d7+iWKxJ2eynGhub0NjYhMLhiK5du6EtW+qr9Mw2BvMpjRltjA5J\nZ7R4p3ZdUq2koKR2SSNa/CPCHwrft1fpjKX49m1pe/sujYxcUjwe1aZNj6u//4PiWiLRrGQyLUk6\nfrxfBw++obt3v9T27Tu1Y8fOah25ophPacxodZ2S/lvStBbvxn4t6U+FtYOSdkm6pMV/CvK4pAeT\nq5P075K2Fq7f0tp/mKgmZ6XPHFYzN7fiW24AG2RzwK+fYPmI561rSL56WwoAG4W4ATCJuAEwibgB\nMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEw\nibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMIm4ATCJuAEwibgBMKmm2gew\nZPP3vGofwffmvnCqfQRfc8RrqJT1Tog7NwAmETcAJhE3ACYRNwAmETcAJhE3ACYRNwAmETcAJhE3\nACYRNwAmETcAJhE3ACYRNwAmETcAJhE3ACYRNwAmETcAJhE3ACYRNwAmETcAJhE3ACYRNwAmETcA\nJhE3ACYRNwAmETcAJhE3ACYRNwAmETcAJhE3ACYRNwAm+TZunuepp+eI4vGo2tqeVzp9Y8V9N29+\notbWJsXjUfX0HJHneUvWT5zoVSDgaHp6uhLHrpgrV67oB889p2hjo959991l6/fu3dPeffsUbWxU\na1ubJiYmimvvvPOOoo2N+sFzz+mjjz6q4Kkri9dQKf8r6SVJj0nqXWNfVlKrpEZJeyV9VXj8XuE6\nWlifKNdBvxXfxm1k5LLGxzNKpzPq6xtQd/ehFfd1dx9SX9+A0umMxsczGh29UlzL5SZ19eqoGhqe\nqtSxK2JhYUGHf/5zXb50SbfGxjR49qxu3bq1ZM+pU6f05BNP6PeZjLp/8Qu9efSoJOnWrVs6e+6c\nxn73O125fFn/dviwFhYWqvE0yo7XUCl1kvok/bLEvjcldUvKSHpS0qnC46cK178vrL9ZnmN+S76N\n2/DwkDo7u+Q4jlpa2jQzM6OpqdtL9kxN3dbs7KxaW1+S4zjq7OzSxYvni+tHj3br2LH35DhOpY9f\nVqlUStFoVM8++6weffRR7du7V0NDQ0v2DF24oNdff12S9Nprr+njjz+W53kaGhrSvr179dhjj+mZ\nZ55RNBpVKpWqxtMoO15DpXxf0lZJf7vGHk/SbyW9Vrh+XdKD+QwVrlVY/7iw3x98G7d83lU43FC8\nDocjyufdFfZEiteh0MM9w8MXFAqF1dQUr8yBK8h1XTVEHj7vSCQi13WX72lYnF9NTY1qa2v12Wef\nLXlckiLh8LKftYLX0Eb4TNITkmoK1xFJD2boSnow3xpJtYX9/lBTekt1fPNzD0nLfnuutmd+fl69\nvW/r/PmRsp2vmr7LbNbzs1bwGtoIK92JOetYqz5f3bkNDJxUItGsRKJZwWBIrjtZXHPdnILB0JL9\n4XBErpsrXufzi3uy2XFNTGSVSMQViz0t181p27YXdOfOVMWeSzlFIhFN5h4+71wup1AotHzP5OL8\n7t+/r88//1x1dXVLHpeknOsu+9m/ZLyGSjkpqbnwlV/H/r+XNCPpfuE6J+nBDCOSHsz3vqTPtfg5\nnj/4Km4HDhxWMplWMpnWnj2vanDwjDzPUyp1XbW1taqvDy7ZX18fVCAQUCp1XZ7naXDwjHbvfkWx\nWJOy2U81NjahsbEJhcMRXbt2Q1u21FfpmW2srVu3KpPJKJvN6quvvtLZc+fU0dGxZE/Hj36k06dP\nS5I+/PBDvfzyy3IcRx0dHTp77pzu3bunbDarTCajlpaWajyNsuA1VMphSenC13p+qTmS/kXSh4Xr\n05JeKXzfUbhWYf1l+enOzbdvS9vbd2lk5JLi8ag2bXpc/f0fFNcSiWYlk2lJ0vHj/Tp48A3dvful\ntm/fqR07dlbryBVTU1Oj93/zG7X/8IdaWFjQz376U8ViMb311lt68cUX1dHRof379+snXV2KNjaq\nrq5OZwcHJUmxWEz/+uMf6x9iMdXU1Ojk++/rkUceqfIzKg9eQ6VMSXpR0qwW73P+U9ItSX8naZek\n/9JiAP9D0j5Jv5L0T5L2F35+v6SfaPGfgtRJOlvBs5fmrPSZw2rm5nz0pxAf2vw9xlPK3Bf++c3u\nR4FAtU/gf563vttDX70tBYCNQtwAmETcAJhE3ACYRNwAmETcAJhE3ACYRNwAmETcAJhE3ACYRNwA\nmETcAJhE3ACYRNwAmETcAJhE3ACYRNwAmETcAJhE3ACYRNwAmETcAJhE3ACYRNwAmETcAJhE3ACY\nRNwAmETcAJhE3ACYRNwAmETcAJhE3ACYVFPtA1gy94VT7SPgL9wf/1jtE9jBnRsAk4gbAJOIGwCT\niBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOI\nGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gb\nAJN8GzfP89TTc0TxeFRtbc8rnb6x4r6bNz9Ra2uT4vGoenqOyPO8JesnTvQqEHA0PT1diWNXDPMp\njRmtzfp8fBu3kZHLGh/PKJ3OqK9vQN3dh1bc1919SH19A0qnMxofz2h09EpxLZeb1NWro2poeKpS\nx64Y5lMaM1qb9fn4Nm7Dw0Pq7OyS4zhqaWnTzMyMpqZuL9kzNXVbs7Ozam19SY7jqLOzSxcvni+u\nHz3arWPH3pPjOJU+ftkxn9KY0dqsz8e3ccvnXYXDDcXrcDiifN5dYU+keB0KPdwzPHxBoVBYTU3x\nyhy4wphPacxobdbnU1PtA6zmm+/rJS377bDanvn5efX2vq3z50fKdr5qYz6lMaO1WZ+Pr+7cBgZO\nKpFoViLRrGAwJNedLK65bk7BYGjJ/nA4ItfNFa/z+cU92ey4JiaySiTiisWeluvmtG3bC7pzZ6pi\nz6UcmE9pzGhtf03z8VXcDhw4rGQyrWQyrT17XtXg4Bl5nqdU6rpqa2tVXx9csr++PqhAIKBU6ro8\nz9Pg4Bnt3v2KYrEmZbOfamxsQmNjEwqHI7p27Ya2bKmv0jPbGMynNGa0tr+m+fj2bWl7+y6NjFxS\nPB7Vpk2Pq7//g+JaItGsZDItSTp+vF8HD76hu3e/1PbtO7Vjx85qHbmimE9pzGht1ufjrPSeejVz\nc1r/ZgAog82bta4/zfrqbSkAbBTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTi\nBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIG\nwCTiBsAk4gbAJOIGwCTiBsAk4gbAJOIGwCTH87xqnwEANhx3bgBMIm4ATCJuAEwibgBMIm4ATCJu\nAEwibgBMIm4ATCJuAEwibgBM+jPdN0cNjYpeKAAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "The installed widget Javascript is the wrong version. It must satisfy the semver range ~2.1.4.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "77e9849e074841e49d8b0ebc8191507c" + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "import ipywidgets as widgets\n", "from IPython.display import display\n", @@ -605,14 +982,14 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "Move the slider above to observe how the utility changes across iterations. It is also possible to move the slider using arrow keys or to jump to the value by directly editing the number with a double click. The **Visualize Button** will automatically animate the slider for you. The **Extra Delay Box** allows you to set time delay in seconds upto one second for each time step. There is also an interactive editor for grid-world problems `grid_mdp.py` in the gui folder for you to play around with." ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": { "collapsed": true }, @@ -639,35 +1016,244 @@ ] }, { -<<<<<<< HEAD - "cell_type": "raw", - "metadata": {}, -======= "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def expected_utility(a, s, U, mdp):\n",
    +       "    """The expected utility of doing a in state s, according to the MDP and U."""\n",
    +       "    return sum([p * U[s1] for (p, s1) in mdp.T(s, a)])\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "psource(expected_utility)" ] }, { -<<<<<<< HEAD - "cell_type": "raw", - "metadata": {}, -======= "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def policy_iteration(mdp):\n",
    +       "    """Solve an MDP by policy iteration [Figure 17.7]"""\n",
    +       "    U = {s: 0 for s in mdp.states}\n",
    +       "    pi = {s: random.choice(mdp.actions(s)) for s in mdp.states}\n",
    +       "    while True:\n",
    +       "        U = policy_evaluation(pi, U, mdp)\n",
    +       "        unchanged = True\n",
    +       "        for s in mdp.states:\n",
    +       "            a = argmax(mdp.actions(s), key=lambda a: expected_utility(a, s, U, mdp))\n",
    +       "            if a != pi[s]:\n",
    +       "                pi[s] = a\n",
    +       "                unchanged = False\n",
    +       "        if unchanged:\n",
    +       "            return pi\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "psource(policy_iteration)" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "
    Fortunately, it is not necessary to do _exact_ policy evaluation. \n", @@ -680,46 +1266,164 @@ ] }, { -<<<<<<< HEAD - "cell_type": "raw", - "metadata": {}, -======= "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def policy_evaluation(pi, U, mdp, k=20):\n",
    +       "    """Return an updated utility mapping U from each state in the MDP to its\n",
    +       "    utility, using an approximation (modified policy iteration)."""\n",
    +       "    R, T, gamma = mdp.R, mdp.T, mdp.gamma\n",
    +       "    for i in range(k):\n",
    +       "        for s in mdp.states:\n",
    +       "            U[s] = R(s) + gamma * sum([p * U[s1] for (p, s1) in T(s, pi[s])])\n",
    +       "    return U\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "psource(policy_evaluation)" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "Let us now solve **`sequential_decision_environment`** using `policy_iteration`." ] }, { -<<<<<<< HEAD - "cell_type": "raw", - "metadata": {}, -======= "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "data": { + "text/plain": [ + "{(0, 0): (0, 1),\n", + " (0, 1): (0, 1),\n", + " (0, 2): (1, 0),\n", + " (1, 0): (1, 0),\n", + " (1, 2): (1, 0),\n", + " (2, 0): (0, 1),\n", + " (2, 1): (0, 1),\n", + " (2, 2): (1, 0),\n", + " (3, 0): (-1, 0),\n", + " (3, 1): None,\n", + " (3, 2): None}" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "policy_iteration(sequential_decision_environment)" ] }, { -<<<<<<< HEAD "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, -<<<<<<< HEAD "outputs": [ { "data": { @@ -747,28 +1451,17 @@ "" ] }, - "execution_count": 11, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], -======= - "cell_type": "raw", - "metadata": {}, ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 -======= - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb "source": [ "pseudocode('Policy-Iteration')" ] }, { -<<<<<<< HEAD "cell_type": "markdown", -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 "metadata": {}, "source": [ "### AIMA3e\n", @@ -792,7 +1485,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": { "collapsed": true }, @@ -819,32 +1512,129 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "These properties of the agent are called the transition properties and are hardcoded into the GridMDP class as you can see below." ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD - "execution_count": 12, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 - "metadata": {}, -======= - "execution_count": null, + "execution_count": 20, "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
        def T(self, state, action):\n",
    +       "        if action is None:\n",
    +       "            return [(0.0, state)]\n",
    +       "        else:\n",
    +       "            return self.transitions[state][action]\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "psource(GridMDP.T)" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "To completely define our task environment, we need to specify the utility function for the agent. \n", @@ -873,25 +1663,121 @@ ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD - "execution_count": 13, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 - "metadata": {}, -======= - "execution_count": null, + "execution_count": 21, "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
        def to_arrows(self, policy):\n",
    +       "        chars = {\n",
    +       "            (1, 0): '>', (0, 1): '^', (-1, 0): '<', (0, -1): 'v', None: '.'}\n",
    +       "        return self.to_grid({s: chars[a] for (s, a) in policy.items()})\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "psource(GridMDP.to_arrows)" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "This method directly encodes the actions that the agent can take (described above) to characters representing arrows and shows it in a grid format for human visalization purposes. \n", @@ -899,32 +1785,129 @@ ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD - "execution_count": 14, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 - "metadata": {}, -======= - "execution_count": null, + "execution_count": 22, "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
        def to_grid(self, mapping):\n",
    +       "        """Convert a mapping from (x, y) to v into a [[..., v, ...]] grid."""\n",
    +       "        return list(reversed([[mapping.get((x, y), None)\n",
    +       "                               for x in range(self.cols)]\n",
    +       "                              for y in range(self.rows)]))\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "psource(GridMDP.to_grid)" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have all the tools required and a good understanding of the agent and the environment, we consider some cases and see how the agent should behave for each case." ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "### Case 1\n", @@ -933,19 +1916,12 @@ ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD - "execution_count": 15, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 -======= - "execution_count": null, ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "execution_count": 23, "metadata": { "collapsed": true }, + "outputs": [], "source": [ "# Note that this environment is also initialized in mdp.py by default\n", "sequential_decision_environment = GridMDP([[-0.04, -0.04, -0.04, +1],\n", @@ -955,7 +1931,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "We will use the `best_policy` function to find the best policy for this environment.\n", @@ -965,51 +1941,45 @@ ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD - "execution_count": 16, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 -======= - "execution_count": null, ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "execution_count": 24, "metadata": { "collapsed": true }, + "outputs": [], "source": [ "pi = best_policy(sequential_decision_environment, value_iteration(sequential_decision_environment, .001))" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "We can now use the `to_arrows` method to see how our agent should pick its actions in the environment." ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD - "execution_count": 17, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 - "metadata": {}, -======= - "execution_count": null, + "execution_count": 25, "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> > > .\n", + "^ None ^ .\n", + "^ > ^ <\n" + ] + } + ], "source": [ "from utils import print_table\n", "print_table(sequential_decision_environment.to_arrows(pi))" ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "This is exactly the output we expected\n", @@ -1021,7 +1991,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "### Case 2\n", @@ -1030,19 +2000,12 @@ ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD - "execution_count": 18, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 -======= - "execution_count": null, ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "execution_count": 26, "metadata": { "collapsed": true }, + "outputs": [], "source": [ "sequential_decision_environment = GridMDP([[-0.4, -0.4, -0.4, +1],\n", " [-0.4, None, -0.4, -1],\n", @@ -1051,19 +2014,20 @@ ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD - "execution_count": 19, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 - "metadata": {}, -======= - "execution_count": null, + "execution_count": 27, "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> > > .\n", + "^ None ^ .\n", + "^ > ^ <\n" + ] + } + ], "source": [ "pi = best_policy(sequential_decision_environment, value_iteration(sequential_decision_environment, .001))\n", "from utils import print_table\n", @@ -1071,7 +2035,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "This is exactly the output we expected\n", @@ -1079,7 +2043,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "As the reward for each state is now more negative, life is certainly more unpleasant.\n", @@ -1087,7 +2051,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "### Case 3\n", @@ -1096,19 +2060,12 @@ ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD - "execution_count": 20, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 -======= - "execution_count": null, ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "execution_count": 28, "metadata": { "collapsed": true }, + "outputs": [], "source": [ "sequential_decision_environment = GridMDP([[-4, -4, -4, +1],\n", " [-4, None, -4, -1],\n", @@ -1117,19 +2074,20 @@ ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD - "execution_count": 21, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 + "execution_count": 29, "metadata": {}, -======= - "execution_count": null, - "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> > > .\n", + "^ None > .\n", + "> > > ^\n" + ] + } + ], "source": [ "pi = best_policy(sequential_decision_environment, value_iteration(sequential_decision_environment, .001))\n", "from utils import print_table\n", @@ -1137,7 +2095,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "This is exactly the output we expected\n", @@ -1145,14 +2103,14 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "The living reward for each state is now lower than the least rewarding terminal. Life is so _painful_ that the agent heads for the nearest exit as even the worst exit is less painful than any living state." ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "### Case 4\n", @@ -1161,19 +2119,12 @@ ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD - "execution_count": 22, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 -======= - "execution_count": null, ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "execution_count": 30, "metadata": { "collapsed": true }, + "outputs": [], "source": [ "sequential_decision_environment = GridMDP([[4, 4, 4, +1],\n", " [4, None, 4, -1],\n", @@ -1182,19 +2133,20 @@ ] }, { -<<<<<<< HEAD "cell_type": "code", -<<<<<<< HEAD - "execution_count": 23, -======= - "cell_type": "raw", ->>>>>>> 9d5ec3c0e1d0c03cd1333afcbd6bbc35daf30c21 + "execution_count": 31, "metadata": {}, -======= - "execution_count": null, - "metadata": {}, - "outputs": [], ->>>>>>> 3fed6614295b7270ca1226415beff7305e387eeb + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> > < .\n", + "> None < .\n", + "> > > v\n" + ] + } + ], "source": [ "pi = best_policy(sequential_decision_environment, value_iteration(sequential_decision_environment, .001))\n", "from utils import print_table\n", @@ -1202,7 +2154,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "In this case, the output we expect is\n", @@ -1219,7 +2171,7 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ "---\n", @@ -3762,3 +4714,4 @@ "nbformat": 4, "nbformat_minor": 1 } + From 007e2d7ec76bdb81f17608a1c23903ab5f45afe1 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Mon, 5 Mar 2018 10:56:53 +0530 Subject: [PATCH 063/269] Added to-cnf (#802) --- README.md | 2 +- logic.ipynb | 436 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 435 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 79c50c822..c97db60f1 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 7.7 | Propositional Logic Sentence | `Expr` | [`utils.py`][utils] | Done | Included | | 7.10 | TT-Entails | `tt_entails` | [`logic.py`][logic] | Done | Included | | 7.12 | PL-Resolution | `pl_resolution` | [`logic.py`][logic] | Done | Included | -| 7.14 | Convert to CNF | `to_cnf` | [`logic.py`][logic] | Done | | +| 7.14 | Convert to CNF | `to_cnf` | [`logic.py`][logic] | Done | Included | | 7.15 | PL-FC-Entails? | `pl_fc_resolution` | [`logic.py`][logic] | Done | | | 7.17 | DPLL-Satisfiable? | `dpll_satisfiable` | [`logic.py`][logic] | Done | | | 7.18 | WalkSAT | `WalkSAT` | [`logic.py`][logic] | Done | | diff --git a/logic.ipynb b/logic.ipynb index 6716e8515..726a8d69d 100644 --- a/logic.ipynb +++ b/logic.ipynb @@ -1006,15 +1006,447 @@ "unit clauses such as $P$ and $\\neg P$ which is a contradiction as both $P$ and $\\neg P$ can't be True at the same time." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is one catch however, the algorithm that implements proof by resolution cannot handle complex sentences. \n", + "Implications and bi-implications have to be simplified into simpler clauses. \n", + "We already know that *every sentence of a propositional logic is logically equivalent to a conjunction of clauses*.\n", + "We will use this fact to our advantage and simplify the input sentence into the **conjunctive normal form** (CNF) which is a conjunction of disjunctions of literals.\n", + "For eg:\n", + "
    \n", + "$$(A\\lor B)\\land (\\neg B\\lor C\\lor\\neg D)\\land (D\\lor\\neg E)$$\n", + "This is equivalent to the POS (Product of sums) form in digital electronics.\n", + "
    \n", + "Here's an outline of how the conversion is done:\n", + "1. Convert bi-implications to implications\n", + "
    \n", + "$\\alpha\\iff\\beta$ can be written as $(\\alpha\\implies\\beta)\\land(\\beta\\implies\\alpha)$\n", + "
    \n", + "This also applies to compound sentences\n", + "
    \n", + "$\\alpha\\iff(\\beta\\lor\\gamma)$ can be written as $(\\alpha\\implies(\\beta\\lor\\gamma))\\land((\\beta\\lor\\gamma)\\implies\\alpha)$\n", + "
    \n", + "2. Convert implications to their logical equivalents\n", + "
    \n", + "$\\alpha\\implies\\beta$ can be written as $\\neg\\alpha\\lor\\beta$\n", + "
    \n", + "3. Move negation inwards\n", + "
    \n", + "CNF requires atomic literals. Hence, negation cannot appear on a compound statement.\n", + "De Morgan's laws will be helpful here.\n", + "
    \n", + "$\\neg(\\alpha\\land\\beta)\\equiv(\\neg\\alpha\\lor\\neg\\beta)$\n", + "
    \n", + "$\\neg(\\alpha\\lor\\beta)\\equiv(\\neg\\alpha\\land\\neg\\beta)$\n", + "
    \n", + "4. Distribute disjunction over conjunction\n", + "
    \n", + "Disjunction and conjunction are distributive over each other.\n", + "Now that we only have conjunctions, disjunctions and negations in our expression, \n", + "we will distribute disjunctions over conjunctions wherever possible as this will give us a sentence which is a conjunction of simpler clauses, \n", + "which is what we wanted in the first place.\n", + "
    \n", + "We need a term of the form\n", + "
    \n", + "$(\\alpha_{1}\\lor\\alpha_{2}\\lor\\alpha_{3}...)\\land(\\beta_{1}\\lor\\beta_{2}\\lor\\beta_{3}...)\\land(\\gamma_{1}\\lor\\gamma_{2}\\lor\\gamma_{3}...)\\land...$\n", + "
    \n", + "
    \n", + "The `to_cnf` function executes this conversion using helper subroutines." + ] + }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def to_cnf(s):\n",
    +       "    """Convert a propositional logical sentence to conjunctive normal form.\n",
    +       "    That is, to the form ((A | ~B | ...) & (B | C | ...) & ...) [p. 253]\n",
    +       "    >>> to_cnf('~(B | C)')\n",
    +       "    (~B & ~C)\n",
    +       "    """\n",
    +       "    s = expr(s)\n",
    +       "    if isinstance(s, str):\n",
    +       "        s = expr(s)\n",
    +       "    s = eliminate_implications(s)  # Steps 1, 2 from p. 253\n",
    +       "    s = move_not_inwards(s)  # Step 3\n",
    +       "    return distribute_and_over_or(s)  # Step 4\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(to_cnf)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`to_cnf` calls three subroutines.\n", + "
    \n", + "`eliminate_implications` converts bi-implications and implications to their logical equivalents.\n", + "
    \n", + "`move_not_inwards` removes negations from compound statements and moves them inwards using De Morgan's laws.\n", + "
    \n", + "`distribute_and_over_or` distributes disjunctions over conjunctions.\n", + "
    \n", + "Run the cells below for implementation details.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%psource eliminate_implications" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%psource move_not_inwards" + ] + }, + { + "cell_type": "code", + "execution_count": 32, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "%psource pl_resolution" + "%psource distribute_and_over_or" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's convert some sentences to see how it works\n" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "((A | ~B) & (B | ~A))" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "A, B, C, D = expr('A, B, C, D')\n", + "to_cnf(A |'<=>'| B)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "((A | ~B | ~C) & (B | ~A) & (C | ~A))" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_cnf(A |'<=>'| (B & C))" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(A & (C | B) & (D | B))" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_cnf(A & (B | (C & D)))" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "((B | ~A | C | ~D) & (A | ~A | C | ~D) & (B | ~B | C | ~D) & (A | ~B | C | ~D))" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_cnf((A |'<=>'| ~B) |'==>'| (C | ~D))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Coming back to our resolution problem, we can see how the `to_cnf` function is utilized here" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def pl_resolution(KB, alpha):\n",
    +       "    """Propositional-logic resolution: say if alpha follows from KB. [Figure 7.12]"""\n",
    +       "    clauses = KB.clauses + conjuncts(to_cnf(~alpha))\n",
    +       "    new = set()\n",
    +       "    while True:\n",
    +       "        n = len(clauses)\n",
    +       "        pairs = [(clauses[i], clauses[j])\n",
    +       "                 for i in range(n) for j in range(i+1, n)]\n",
    +       "        for (ci, cj) in pairs:\n",
    +       "            resolvents = pl_resolve(ci, cj)\n",
    +       "            if False in resolvents:\n",
    +       "                return True\n",
    +       "            new = new.union(set(resolvents))\n",
    +       "        if new.issubset(set(clauses)):\n",
    +       "            return False\n",
    +       "        for c in new:\n",
    +       "            if c not in clauses:\n",
    +       "                clauses.append(c)\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(pl_resolution)" ] }, { From d4877cd6f6bf3adf806cb7731d5b30f38c4f1200 Mon Sep 17 00:00:00 2001 From: Anthony Marakis Date: Tue, 6 Mar 2018 00:08:27 +0200 Subject: [PATCH 064/269] Update CONTRIBUTING.md (#806) --- CONTRIBUTING.md | 43 ++++--------------------------------------- 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed17ed4da..df8b94881 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,14 +1,14 @@ How to Contribute to aima-python ========================== -Thanks for considering contributing to `aima-python`! Whether you are an aspiring [Google Summer of Code](https://summerofcode.withgoogle.com/organizations/5663121491361792/) student, or an independent contributor, here is a guide on how you can help. +Thanks for considering contributing to `aima-python`! Whether you are an aspiring [Google Summer of Code](https://summerofcode.withgoogle.com/organizations/5674023002832896/) student, or an independent contributor, here is a guide on how you can help. -First of all, you can read these write-ups from past GSoC students to get an idea on what you can do for the project. [Chipe1](https://github.com/aimacode/aima-python/issues/641) - [MrDupin](https://github.com/aimacode/aima-python/issues/632) +First of all, you can read these write-ups from past GSoC students to get an idea about what you can do for the project. [Chipe1](https://github.com/aimacode/aima-python/issues/641) - [MrDupin](https://github.com/aimacode/aima-python/issues/632) In general, the main ways you can contribute to the repository are the following: 1. Implement algorithms from the [list of algorithms](https://github.com/aimacode/aima-python/blob/master/README.md#index-of-algorithms). -1. Add tests for algorithms that are missing them (you can also add more tests to algorithms that already have some). +1. Add tests for algorithms. 1. Take care of [issues](https://github.com/aimacode/aima-python/issues). 1. Write on the notebooks (`.ipynb` files). 1. Add and edit documentation (the docstrings in `.py` files). @@ -21,20 +21,16 @@ In more detail: - Look at the [issues](https://github.com/aimacode/aima-python/issues) and pick one to work on. - One of the issues is that some algorithms are missing from the [list of algorithms](https://github.com/aimacode/aima-python/blob/master/README.md#index-of-algorithms) and that some don't have tests. -## Port to Python 3; Pythonic Idioms; py.test +## Port to Python 3; Pythonic Idioms - Check for common problems in [porting to Python 3](http://python3porting.com/problems.html), such as: `print` is now a function; `range` and `map` and other functions no longer produce `list`s; objects of different types can no longer be compared with `<`; strings are now Unicode; it would be nice to move `%` string formatting to `.format`; there is a new `next` function for generators; integer division now returns a float; we can now use set literals. - Replace old Lisp-based idioms with proper Python idioms. For example, we have many functions that were taken directly from Common Lisp, such as the `every` function: `every(callable, items)` returns true if every element of `items` is callable. This is good Lisp style, but good Python style would be to use `all` and a generator expression: `all(callable(f) for f in items)`. Eventually, fix all calls to these legacy Lisp functions and then remove the functions. -- Add more tests in `test_*.py` files. Strive for terseness; it is ok to group multiple asserts into one `def test_something():` function. Move most tests to `test_*.py`, but it is fine to have a single `doctest` example in the docstring of a function in the `.py` file, if the purpose of the doctest is to explain how to use the function, rather than test the implementation. ## New and Improved Algorithms - Implement functions that were in the third edition of the book but were not yet implemented in the code. Check the [list of pseudocode algorithms (pdf)](https://github.com/aimacode/pseudocode/blob/master/aima3e-algorithms.pdf) to see what's missing. - As we finish chapters for the new fourth edition, we will share the new pseudocode in the [`aima-pseudocode`](https://github.com/aimacode/aima-pseudocode) repository, and describe what changes are necessary. We hope to have an `algorithm-name.md` file for each algorithm, eventually; it would be great if contributors could add some for the existing algorithms. -- Give examples of how to use the code in the `.ipynb` files. - -We still support a legacy branch, `aima3python2` (for the third edition of the textbook and for Python 2 code). ## Jupyter Notebooks @@ -69,15 +65,6 @@ a one-line docstring suffices. It is rarely necessary to list what each argument - At some point I may add [Pep 484](https://www.python.org/dev/peps/pep-0484/) type annotations, but I think I'll hold off for now; I want to get more experience with them, and some people may still be in Python 3.4. - -Contributing a Patch -==================== - -1. Submit an issue describing your proposed change to the repo in question (or work on an existing issue). -1. The repo owner will respond to your issue promptly. -1. Fork the desired repo, develop and test your code changes. -1. Submit a pull request. - Reporting Issues ================ @@ -98,28 +85,6 @@ Patch Rules - Follow the style guidelines described above. -Running the Test-Suite -===================== - -The minimal requirement for running the testsuite is ``py.test``. You can -install it with: - - pip install pytest - -Clone this repository: - - git clone https://github.com/aimacode/aima-python.git - -Fetch the aima-data submodule: - - cd aima-python - git submodule init - git submodule update - -Then you can run the testsuite from the `aima-python` or `tests` directory with: - - py.test - # Choice of Programming Languages Are we right to concentrate on Java and Python versions of the code? I think so; both languages are popular; Java is From 1ba1aeddb822f3dddc8ff851036003fa2edf360d Mon Sep 17 00:00:00 2001 From: Seenivasan M Date: Tue, 6 Mar 2018 03:38:48 +0530 Subject: [PATCH 065/269] Remove commented codes in agents.ipynb (#805) --- agents.ipynb | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/agents.ipynb b/agents.ipynb index ed6920bd0..65878bbab 100644 --- a/agents.ipynb +++ b/agents.ipynb @@ -4,6 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "\n", "# AGENT #\n", "\n", "An agent, as defined in 2.1 is anything that can perceive its environment through sensors, and act upon that environment through actuators based on its agent program. This can be a dog, robot, or even you. As long as you can perceive the environment and act on it, you are an agent. This notebook will explain how to implement a simple agent, create an environment, and create a program that helps the agent act on the environment based on its percepts.\n", @@ -17,6 +18,7 @@ "cell_type": "code", "execution_count": 1, "metadata": { + "collapsed": true, "scrolled": true }, "outputs": [], @@ -80,7 +82,9 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "class Food(Thing):\n", @@ -151,7 +155,9 @@ { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "class BlindDog(Agent):\n", @@ -163,14 +169,12 @@ " def eat(self, thing):\n", " '''returns True upon success or False otherwise'''\n", " if isinstance(thing, Food):\n", - " #print(\"Dog: Ate food at {}.\".format(self.location))\n", " return True\n", " return False\n", " \n", " def drink(self, thing):\n", " ''' returns True upon success or False otherwise'''\n", " if isinstance(thing, Water):\n", - " #print(\"Dog: Drank water at {}.\".format(self.location))\n", " return True\n", " return False\n", " \n", @@ -456,7 +460,9 @@ { "cell_type": "code", "execution_count": 10, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "from random import choice\n", @@ -487,14 +493,12 @@ " def eat(self, thing):\n", " '''returns True upon success or False otherwise'''\n", " if isinstance(thing, Food):\n", - " #print(\"Dog: Ate food at {}.\".format(self.location))\n", " return True\n", " return False\n", " \n", " def drink(self, thing):\n", " ''' returns True upon success or False otherwise'''\n", " if isinstance(thing, Water):\n", - " #print(\"Dog: Drank water at {}.\".format(self.location))\n", " return True\n", " return False\n", " \n", @@ -546,11 +550,9 @@ " if action == 'turnright':\n", " print('{} decided to {} at location: {}'.format(str(agent)[1:-1], action, agent.location))\n", " agent.turn(Direction.R)\n", - " #print('now facing {}'.format(agent.direction.direction))\n", " elif action == 'turnleft':\n", " print('{} decided to {} at location: {}'.format(str(agent)[1:-1], action, agent.location))\n", " agent.turn(Direction.L)\n", - " #print('now facing {}'.format(agent.direction.direction))\n", " elif action == 'moveforward':\n", " loc = copy.deepcopy(agent.location) # find out the target location\n", " if agent.direction.direction == Direction.R:\n", @@ -561,7 +563,6 @@ " loc[1] += 1\n", " elif agent.direction.direction == Direction.U:\n", " loc[1] -= 1\n", - " #print('{} at {} facing {}'.format(agent, loc, agent.direction.direction))\n", " if self.is_inbounds(loc):# move only if the target is a valid location\n", " print('{} decided to move {}wards at location: {}'.format(str(agent)[1:-1], agent.direction.direction, agent.location))\n", " agent.moveforward()\n", @@ -664,11 +665,9 @@ " if action == 'turnright':\n", " print('{} decided to {} at location: {}'.format(str(agent)[1:-1], action, agent.location))\n", " agent.turn(Direction.R)\n", - " #print('now facing {}'.format(agent.direction.direction))\n", " elif action == 'turnleft':\n", " print('{} decided to {} at location: {}'.format(str(agent)[1:-1], action, agent.location))\n", " agent.turn(Direction.L)\n", - " #print('now facing {}'.format(agent.direction.direction))\n", " elif action == 'moveforward':\n", " loc = copy.deepcopy(agent.location) # find out the target location\n", " if agent.direction.direction == Direction.R:\n", @@ -679,7 +678,6 @@ " loc[1] += 1\n", " elif agent.direction.direction == Direction.U:\n", " loc[1] -= 1\n", - " #print('{} at {} facing {}'.format(agent, loc, agent.direction.direction))\n", " if self.is_inbounds(loc):# move only if the target is a valid location\n", " print('{} decided to move {}wards at location: {}'.format(str(agent)[1:-1], agent.direction.direction, agent.location))\n", " agent.moveforward()\n", @@ -1157,7 +1155,9 @@ { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "from ipythonblocks import BlockGrid\n", @@ -1252,7 +1252,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.4rc1" + "version": "3.6.4" } }, "nbformat": 4, From a8ccb309d11f25dcdf831c1726f738d34cf3a674 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Sat, 10 Mar 2018 00:20:30 +0530 Subject: [PATCH 066/269] Minor formatting issues (#832) --- planning.py | 2 +- probability.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/planning.py b/planning.py index 4c02c3d72..e31c8b3a3 100644 --- a/planning.py +++ b/planning.py @@ -524,7 +524,7 @@ def goal_test(kb, goals): if solution: return solution graphplan.graph.expand_graph() - if len(graphplan.graph.levels)>=2 and graphplan.check_leveloff(): + if len(graphplan.graph.levels) >=2 and graphplan.check_leveloff(): return None diff --git a/probability.py b/probability.py index a9f65fbb0..9b732edd7 100644 --- a/probability.py +++ b/probability.py @@ -653,6 +653,7 @@ def particle_filtering(e, N, HMM): # _________________________________________________________________________ ## TODO: Implement continuous map for MonteCarlo similar to Fig25.10 from the book + class MCLmap: """Map which provides probability distributions and sensor readings. Consists of discrete cells which are either an obstacle or empty""" @@ -679,7 +680,7 @@ def ray_cast(self, sensor_num, kin_state): # 0 # 3R1 # 2 - delta = ((sensor_num%2 == 0)*(sensor_num - 1), (sensor_num%2 == 1)*(2 - sensor_num)) + delta = ((sensor_num % 2 == 0)*(sensor_num - 1), (sensor_num % 2 == 1)*(2 - sensor_num)) # sensor direction changes based on orientation for _ in range(orient): delta = (delta[1], -delta[0]) From aa6664f4ecacbdb5d4a0c45104ab98956d196c08 Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Fri, 9 Mar 2018 14:26:42 -0800 Subject: [PATCH 067/269] Add injection A new function, `injection` for dependency injection of globals (for classes and functions that weren't designed for dependency injection). --- utils.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/utils.py b/utils.py index 709c5621f..b0e57e41f 100644 --- a/utils.py +++ b/utils.py @@ -348,6 +348,17 @@ def vector_clip(vector, lowest, highest): # ______________________________________________________________________________ # Misc Functions +class injection(): + """Dependency injection of temporary values for global functions/classes/etc. + E.g., `with injection(DataBase=MockDataBase): ...`""" + def __init__(self, **kwds): + self.new = kwds + def __enter__(self): + self.old = {v: globals()[v] for v in self.new} + globals().update(self.new) + def __exit__(self, type, value, traceback): + globals().update(self.old) + def memoize(fn, slot=None, maxsize=32): """Memoize fn: make it remember the computed value for any argument list. From 4cc35091faad57df2bc85e13ae2930f784f59007 Mon Sep 17 00:00:00 2001 From: Rahul Goswami Date: Sat, 10 Mar 2018 13:16:25 +0530 Subject: [PATCH 068/269] styling and several bug fixes in learning.py (#831) * styling changes and bug fixes in learning.py * Fix #833 and other pep corrections in mdp.py * minor change mdp.py * renamed train_and_test() to train_test_split() #55 #830 * typo fix --- learning.py | 136 ++++++++++++++++++++++++++++------------------------ mdp.py | 84 ++++++++++++++++---------------- 2 files changed, 115 insertions(+), 105 deletions(-) diff --git a/learning.py b/learning.py index a231e8a78..32cf73d81 100644 --- a/learning.py +++ b/learning.py @@ -19,7 +19,7 @@ def euclidean_distance(X, Y): - return math.sqrt(sum([(x - y)**2 for x, y in zip(X, Y)])) + return math.sqrt(sum((x - y)**2 for x, y in zip(X, Y))) def rms_error(X, Y): @@ -27,15 +27,15 @@ def rms_error(X, Y): def ms_error(X, Y): - return mean([(x - y)**2 for x, y in zip(X, Y)]) + return mean((x - y)**2 for x, y in zip(X, Y)) def mean_error(X, Y): - return mean([abs(x - y) for x, y in zip(X, Y)]) + return mean(abs(x - y) for x, y in zip(X, Y)) def manhattan_distance(X, Y): - return sum([abs(x - y) for x, y in zip(X, Y)]) + return sum(abs(x - y) for x, y in zip(X, Y)) def mean_boolean_error(X, Y): @@ -86,22 +86,20 @@ def __init__(self, examples=None, attrs=None, attrnames=None, target=-1, self.source = source self.values = values self.distance = distance - if values is None: - self.got_values_flag = False - else: - self.got_values_flag = True + self.got_values_flag = bool(values) # Initialize .examples from string or list or data directory if isinstance(examples, str): self.examples = parse_csv(examples) - elif examples is None: - self.examples = parse_csv(open_data(name + '.csv').read()) else: - self.examples = examples + self.examples = examples or parse_csv(open_data(name + '.csv').read()) + # Attrs are the indices of examples, unless otherwise stated. - if attrs is None and self.examples is not None: + if self.examples and not attrs: attrs = list(range(len(self.examples[0]))) + self.attrs = attrs + # Initialize .attrnames from string, list, or by default if isinstance(attrnames, str): self.attrnames = attrnames.split() @@ -201,14 +199,15 @@ def find_means_and_deviations(self): item_buckets = self.split_values_by_classes() - means = defaultdict(lambda: [0 for i in range(feature_numbers)]) - deviations = defaultdict(lambda: [0 for i in range(feature_numbers)]) + means = defaultdict(lambda: [0] * feature_numbers) + deviations = defaultdict(lambda: [0] * feature_numbers) for t in target_names: # Find all the item feature values for item in class t features = [[] for i in range(feature_numbers)] for item in item_buckets[t]: - features = [features[i] + [item[i]] for i in range(feature_numbers)] + for i in range(feature_numbers): + features[i].append(item[i]) # Calculate means and deviations fo the class for i in range(feature_numbers): @@ -245,12 +244,14 @@ class CountingProbDist: p.sample() returns a random element from the distribution. p[o] returns the probability for o (as in a regular ProbDist).""" - def __init__(self, observations=[], default=0): + def __init__(self, observations=None, default=0): """Create a distribution, and optionally add in some observations. By default this is an unsmoothed distribution, but saying default=1, for example, gives you add-one smoothing.""" + if observations is None: + observations = [] self.dictionary = {} - self.n_obs = 0.0 + self.n_obs = 0 self.default = default self.sampler = None @@ -400,10 +401,10 @@ def predict(example): def truncated_svd(X, num_val=2, max_iter=1000): - """Computes the first component of SVD""" + """Compute the first component of SVD.""" - def normalize_vec(X, n = 2): - """Normalizes two parts (:m and m:) of the vector""" + def normalize_vec(X, n=2): + """Normalize two parts (:m and m:) of the vector.""" X_m = X[:m] X_n = X[m:] norm_X_m = norm(X_m, n) @@ -413,7 +414,7 @@ def normalize_vec(X, n = 2): return Y_m + Y_n def remove_component(X): - """Removes components of already obtained eigen vectors from X""" + """Remove components of already obtained eigen vectors from X.""" X_m = X[:m] X_n = X[m:] for eivec in eivec_m: @@ -425,21 +426,21 @@ def remove_component(X): return X_m + X_n m, n = len(X), len(X[0]) - A = [[0 for _ in range(n + m)] for _ in range(n + m)] + A = [[0]*(n+m) for _ in range(n+m)] for i in range(m): for j in range(n): - A[i][m + j] = A[m + j][i] = X[i][j] + A[i][m+j] = A[m+j][i] = X[i][j] eivec_m = [] eivec_n = [] eivals = [] for _ in range(num_val): - X = [random.random() for _ in range(m + n)] + X = [random.random() for _ in range(m+n)] X = remove_component(X) X = normalize_vec(X) - for _ in range(max_iter): + for i in range(max_iter): old_X = X X = matrix_multiplication(A, [[x] for x in X]) X = [x[0] for x in X] @@ -489,6 +490,7 @@ def display(self, indent=0): for (val, subtree) in self.branches.items(): print(' ' * 4 * indent, name, '=', val, '==>', end=' ') subtree.display(indent + 1) + print() # newline def __repr__(self): return ('DecisionFork({0!r}, {1!r}, {2!r})' @@ -560,8 +562,8 @@ def information_gain(attr, examples): def I(examples): return information_content([count(target, v, examples) for v in values[target]]) - N = float(len(examples)) - remainder = sum((len(examples_i) / N) * I(examples_i) + N = len(examples) + remainder = sum((len(examples_i)/N) * I(examples_i) for (v, examples_i) in split_by(attr, examples)) return I(examples) - remainder @@ -643,7 +645,7 @@ def predict(example): # ______________________________________________________________________________ -def NeuralNetLearner(dataset, hidden_layer_sizes=[3], +def NeuralNetLearner(dataset, hidden_layer_sizes=None, learning_rate=0.01, epochs=100): """Layered feed-forward network. hidden_layer_sizes: List of number of hidden units per hidden layer @@ -651,6 +653,7 @@ def NeuralNetLearner(dataset, hidden_layer_sizes=[3], epochs: Number of passes over the dataset """ + hidden_layer_sizes = hidden_layer_sizes or [3] # default value i_units = len(dataset.inputs) o_units = len(dataset.values[dataset.target]) @@ -684,7 +687,7 @@ def predict(example): def random_weights(min_value, max_value, num_weights): - return [random.uniform(min_value, max_value) for i in range(num_weights)] + return [random.uniform(min_value, max_value) for _ in range(num_weights)] def BackPropagationLearner(dataset, net, learning_rate, epochs): @@ -699,7 +702,7 @@ def BackPropagationLearner(dataset, net, learning_rate, epochs): ''' As of now dataset.target gives an int instead of list, Changing dataset class will have effect on all the learners. - Will be taken care of later + Will be taken care of later. ''' o_nodes = net[-1] i_nodes = net[0] @@ -728,12 +731,13 @@ def BackPropagationLearner(dataset, net, learning_rate, epochs): node.value = node.activation(in_val) # Initialize delta - delta = [[] for i in range(n_layers)] + delta = [[] for _ in range(n_layers)] # Compute outer layer delta # Error for the MSE cost function err = [t_val[i] - o_nodes[i].value for i in range(o_units)] + # The activation function used is the sigmoid function delta[-1] = [sigmoid_derivative(o_nodes[i].value) * err[i] for i in range(o_units)] @@ -743,6 +747,7 @@ def BackPropagationLearner(dataset, net, learning_rate, epochs): layer = net[i] h_units = len(layer) nx_layer = net[i+1] + # weights from each ith layer node to each i + 1th layer node w = [[node.weights[k] for node in nx_layer] for k in range(h_units)] @@ -791,8 +796,8 @@ class NNUnit: """ def __init__(self, weights=None, inputs=None): - self.weights = [] - self.inputs = [] + self.weights = weights or [] + self.inputs = inputs or [] self.value = None self.activation = sigmoid @@ -827,6 +832,7 @@ def init_examples(examples, idx_i, idx_t, o_units): for i in range(len(examples)): e = examples[i] + # Input values of e inputs[i] = [e[i] for i in idx_i] @@ -902,24 +908,26 @@ def predict(example): def AdaBoost(L, K): """[Figure 18.34]""" + def train(dataset): examples, target = dataset.examples, dataset.target N = len(examples) - epsilon = 1. / (2 * N) - w = [1. / N] * N + epsilon = 1/(2*N) + w = [1/N]*N h, z = [], [] for k in range(K): h_k = L(dataset, w) h.append(h_k) error = sum(weight for example, weight in zip(examples, w) if example[target] != h_k(example)) + # Avoid divide-by-0 from either 0% or 100% error rates: error = clip(error, epsilon, 1 - epsilon) for j, example in enumerate(examples): if example[target] == h_k(example): - w[j] *= error / (1. - error) + w[j] *= error/(1 - error) w = normalize(w) - z.append(math.log((1. - error) / error)) + z.append(math.log((1 - error)/error)) return WeightedMajority(h, z) return train @@ -934,13 +942,13 @@ def predict(example): def weighted_mode(values, weights): """Return the value with the greatest total weight. - >>> weighted_mode('abbaa', [1,2,3,1,2]) + >>> weighted_mode('abbaa', [1, 2, 3, 1, 2]) 'b' """ totals = defaultdict(int) for v, w in zip(values, weights): totals[v] += w - return max(list(totals.keys()), key=totals.get) + return max(totals, key=totals.__getitem__) # _____________________________________________________________________________ # Adapting an unweighted learner for AdaBoost @@ -966,14 +974,14 @@ def weighted_replicate(seq, weights, n): """Return n selections from seq, with the count of each element of seq proportional to the corresponding weight (filling in fractions randomly). - >>> weighted_replicate('ABC', [1,2,1], 4) + >>> weighted_replicate('ABC', [1, 2, 1], 4) ['A', 'B', 'B', 'C'] """ assert len(seq) == len(weights) weights = normalize(weights) - wholes = [int(w * n) for w in weights] - fractions = [(w * n) % 1 for w in weights] - return (flatten([x] * nx for x, nx in zip(seq, wholes)) + + wholes = [int(w*n) for w in weights] + fractions = [(w*n) % 1 for w in weights] + return (flatten([x]*nx for x, nx in zip(seq, wholes)) + weighted_sample_with_replacement(n - sum(wholes), seq, fractions)) @@ -986,11 +994,10 @@ def flatten(seqs): return sum(seqs, []) def err_ratio(predict, dataset, examples=None, verbose=0): """Return the proportion of the examples that are NOT correctly predicted. verbose - 0: No output; 1: Output wrong; 2 (or greater): Output correct""" - if examples is None: - examples = dataset.examples + examples = examples or dataset.examples if len(examples) == 0: return 0.0 - right = 0.0 + right = 0 for example in examples: desired = example[dataset.target] output = predict(dataset.sanitize(example)) @@ -1001,7 +1008,7 @@ def err_ratio(predict, dataset, examples=None, verbose=0): elif verbose: print('WRONG: got {}, expected {} for {}'.format( output, desired, example)) - return 1 - (right / len(examples)) + return 1 - (right/len(examples)) def grade_learner(predict, tests): @@ -1010,7 +1017,7 @@ def grade_learner(predict, tests): return mean(int(predict(X) == y) for X, y in tests) -def train_and_test(dataset, start, end): +def train_test_split(dataset, start, end): """Reserve dataset.examples[start:end] for test; train on the remainder.""" start = int(start) end = int(end) @@ -1025,8 +1032,7 @@ def cross_validation(learner, size, dataset, k=10, trials=1): That is, keep out 1/k of the examples for testing on each of k runs. Shuffle the examples first; if trials>1, average over several shuffles. Returns Training error, Validataion error""" - if k is None: - k = len(dataset.examples) + k = k or len(dataset.examples) if trials > 1: trial_errT = 0 trial_errV = 0 @@ -1035,7 +1041,7 @@ def cross_validation(learner, size, dataset, k=10, trials=1): k=10, trials=1) trial_errT += errT trial_errV += errV - return trial_errT / trials, trial_errV / trials + return trial_errT/trials, trial_errV/trials else: fold_errT = 0 fold_errV = 0 @@ -1043,17 +1049,18 @@ def cross_validation(learner, size, dataset, k=10, trials=1): examples = dataset.examples for fold in range(k): random.shuffle(dataset.examples) - train_data, val_data = train_and_test(dataset, fold * (n / k), - (fold + 1) * (n / k)) + train_data, val_data = train_test_split(dataset, fold * (n / k), + (fold + 1) * (n / k)) dataset.examples = train_data h = learner(dataset, size) fold_errT += err_ratio(h, dataset, train_data) fold_errV += err_ratio(h, dataset, val_data) + # Reverting back to original once test is completed dataset.examples = examples - return fold_errT / k, fold_errV / k - + return fold_errT/k, fold_errV/k +# TODO: The function cross_validation_wrapper needs to be fixed. (The while loop runs forever!) def cross_validation_wrapper(learner, dataset, k=10, trials=1): """[Fig 18.8] Return the optimal value of size having minimum error @@ -1073,7 +1080,7 @@ def cross_validation_wrapper(learner, dataset, k=10, trials=1): min_val = math.inf i = 0 - while i', (0, 1): '^', (-1, 0): '<', (0, -1): 'v', None: '.'} + chars = {(1, 0): '>', (0, 1): '^', (-1, 0): '<', (0, -1): 'v', None: '.'} return self.to_grid({s: chars[a] for (s, a) in policy.items()}) # ______________________________________________________________________________ @@ -185,10 +183,10 @@ def value_iteration(mdp, epsilon=0.001): U = U1.copy() delta = 0 for s in mdp.states: - U1[s] = R(s) + gamma * max([sum([p * U[s1] for (p, s1) in T(s, a)]) - for a in mdp.actions(s)]) + U1[s] = R(s) + gamma * max(sum(p*U[s1] for (p, s1) in T(s, a)) + for a in mdp.actions(s)) delta = max(delta, abs(U1[s] - U[s])) - if delta < epsilon * (1 - gamma) / gamma: + if delta < epsilon*(1 - gamma)/gamma: return U @@ -203,7 +201,7 @@ def best_policy(mdp, U): def expected_utility(a, s, U, mdp): """The expected utility of doing a in state s, according to the MDP and U.""" - return sum([p * U[s1] for (p, s1) in mdp.T(s, a)]) + return sum(p*U[s1] for (p, s1) in mdp.T(s, a)) # ______________________________________________________________________________ @@ -230,7 +228,7 @@ def policy_evaluation(pi, U, mdp, k=20): R, T, gamma = mdp.R, mdp.T, mdp.gamma for i in range(k): for s in mdp.states: - U[s] = R(s) + gamma * sum([p * U[s1] for (p, s1) in T(s, pi[s])]) + U[s] = R(s) + gamma*sum(p*U[s1] for (p, s1) in T(s, pi[s])) return U @@ -267,4 +265,4 @@ def policy_evaluation(pi, U, mdp, k=20): 'plan3' : [(0.1, 'a'), (0.3, 'b'), (0.1, 'c'), (0.5, 'd')], }, } -""" \ No newline at end of file +""" From c908058e0dd6d504449bd65d0b281e5c330a3c4d Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Sat, 10 Mar 2018 13:19:55 +0530 Subject: [PATCH 069/269] Added DPLL and WalkSAT sections (#823) * Added dpll section * Updated README.md * Added WalkSAT section * Updated README.md --- README.md | 6 +- logic.ipynb | 847 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 850 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c97db60f1..a793deb30 100644 --- a/README.md +++ b/README.md @@ -98,9 +98,9 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 7.10 | TT-Entails | `tt_entails` | [`logic.py`][logic] | Done | Included | | 7.12 | PL-Resolution | `pl_resolution` | [`logic.py`][logic] | Done | Included | | 7.14 | Convert to CNF | `to_cnf` | [`logic.py`][logic] | Done | Included | -| 7.15 | PL-FC-Entails? | `pl_fc_resolution` | [`logic.py`][logic] | Done | | -| 7.17 | DPLL-Satisfiable? | `dpll_satisfiable` | [`logic.py`][logic] | Done | | -| 7.18 | WalkSAT | `WalkSAT` | [`logic.py`][logic] | Done | | +| 7.15 | PL-FC-Entails? | `pl_fc_resolution` | [`logic.py`][logic] | Done | Included | +| 7.17 | DPLL-Satisfiable? | `dpll_satisfiable` | [`logic.py`][logic] | Done | Included | +| 7.18 | WalkSAT | `WalkSAT` | [`logic.py`][logic] | Done | Included | | 7.20 | Hybrid-Wumpus-Agent | `HybridWumpusAgent` | | | | | 7.22 | SATPlan | `SAT_plan` | [`logic.py`][logic] | Done | | | 9 | Subst | `subst` | [`logic.py`][logic] | Done | | diff --git a/logic.ipynb b/logic.ipynb index 726a8d69d..0cd6cbc1f 100644 --- a/logic.ipynb +++ b/logic.ipynb @@ -1489,6 +1489,853 @@ "pl_resolution(wumpus_kb, ~P22), pl_resolution(wumpus_kb, P22)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Effective Propositional Model Checking\n", + "\n", + "The previous segments elucidate the algorithmic procedure for model checking. \n", + "In this segment, we look at ways of making them computationally efficient.\n", + "
    \n", + "The problem we are trying to solve is conventionally called the _propositional satisfiability problem_, abbreviated as the _SAT_ problem.\n", + "In layman terms, if there exists a model that satisfies a given Boolean formula, the formula is called satisfiable.\n", + "
    \n", + "The SAT problem was the first problem to be proven _NP-complete_.\n", + "The main characteristics of an NP-complete problem are:\n", + "- Given a solution to such a problem, it is easy to verify if the solution solves the problem.\n", + "- The time required to actually solve the problem using any known algorithm increases exponentially with respect to the size of the problem.\n", + "
    \n", + "
    \n", + "Due to these properties, heuristic and approximational methods are often applied to find solutions to these problems.\n", + "
    \n", + "It is extremely important to be able to solve large scale SAT problems efficiently because \n", + "many combinatorial problems in computer science can be conveniently reduced to checking the satisfiability of a propositional sentence under some constraints.\n", + "
    \n", + "We will introduce two new algorithms that perform propositional model checking in a computationally effective way.\n", + "
    \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1. DPLL (Davis-Putnam-Logeman-Loveland) algorithm\n", + "This algorithm is very similar to Backtracking-Search.\n", + "It recursively enumerates possible models in a depth-first fashion with the following improvements over algorithms like `tt_entails`:\n", + "1. Early termination:\n", + "
    \n", + "In certain cases, the algorithm can detect the truth value of a statement using just a partially completed model.\n", + "For example, $(P\\lor Q)\\land(P\\lor R)$ is true if P is true, regardless of other variables.\n", + "This reduces the search space significantly.\n", + "2. Pure symbol heuristic:\n", + "
    \n", + "A symbol that has the same sign (positive or negative) in all clauses is called a _pure symbol_.\n", + "It isn't difficult to see that any satisfiable model will have the pure symbols assigned such that its parent clause becomes _true_.\n", + "For example, $(P\\lor\\neg Q)\\land(\\neg Q\\lor\\neg R)\\land(R\\lor P)$ has P and Q as pure symbols\n", + "and for the sentence to be true, P _has_ to be true and Q _has_ to be false.\n", + "The pure symbol heuristic thus simplifies the problem a bit.\n", + "3. Unit clause heuristic:\n", + "
    \n", + "In the context of DPLL, clauses with just one literal and clauses with all but one _false_ literals are called unit clauses.\n", + "If a clause is a unit clause, it can only be satisfied by assigning the necessary value to make the last literal true.\n", + "We have no other choice.\n", + "
    \n", + "Assigning one unit clause can create another unit clause.\n", + "For example, when P is false, $(P\\lor Q)$ becomes a unit clause, causing _true_ to be assigned to Q.\n", + "A series of forced assignments derived from previous unit clauses is called _unit propagation_.\n", + "In this way, this heuristic simplifies the problem further.\n", + "
    \n", + "The algorithm often employs other tricks to scale up to large problems.\n", + "However, these tricks are currently out of the scope of this notebook. Refer to section 7.6 of the book for more details.\n", + "
    \n", + "
    \n", + "Let's have a look at the algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def dpll(clauses, symbols, model):\n",
    +       "    """See if the clauses are true in a partial model."""\n",
    +       "    unknown_clauses = []  # clauses with an unknown truth value\n",
    +       "    for c in clauses:\n",
    +       "        val = pl_true(c, model)\n",
    +       "        if val is False:\n",
    +       "            return False\n",
    +       "        if val is not True:\n",
    +       "            unknown_clauses.append(c)\n",
    +       "    if not unknown_clauses:\n",
    +       "        return model\n",
    +       "    P, value = find_pure_symbol(symbols, unknown_clauses)\n",
    +       "    if P:\n",
    +       "        return dpll(clauses, removeall(P, symbols), extend(model, P, value))\n",
    +       "    P, value = find_unit_clause(clauses, model)\n",
    +       "    if P:\n",
    +       "        return dpll(clauses, removeall(P, symbols), extend(model, P, value))\n",
    +       "    if not symbols:\n",
    +       "        raise TypeError("Argument should be of the type Expr.")\n",
    +       "    P, symbols = symbols[0], symbols[1:]\n",
    +       "    return (dpll(clauses, symbols, extend(model, P, True)) or\n",
    +       "            dpll(clauses, symbols, extend(model, P, False)))\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(dpll)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The algorithm uses the ideas described above to check satisfiability of a sentence in propositional logic.\n", + "It recursively calls itself, simplifying the problem at each step. It also uses helper functions `find_pure_symbol` and `find_unit_clause` to carry out steps 2 and 3 above.\n", + "
    \n", + "The `dpll_satisfiable` helper function converts the input clauses to _conjunctive normal form_ and calls the `dpll` function with the correct parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def dpll_satisfiable(s):\n",
    +       "    """Check satisfiability of a propositional sentence.\n",
    +       "    This differs from the book code in two ways: (1) it returns a model\n",
    +       "    rather than True when it succeeds; this is more useful. (2) The\n",
    +       "    function find_pure_symbol is passed a list of unknown clauses, rather\n",
    +       "    than a list of all clauses and the model; this is more efficient."""\n",
    +       "    clauses = conjuncts(to_cnf(s))\n",
    +       "    symbols = list(prop_symbols(s))\n",
    +       "    return dpll(clauses, symbols, {})\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(dpll_satisfiable)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's see a few examples of usage." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [], + "source": [ + "A, B, C, D = expr('A, B, C, D')" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{C: False, A: True, D: True, B: True}" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dpll_satisfiable(A & B & ~C & D)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is a simple case to highlight that the algorithm actually works." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{C: True, D: False, B: True}" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dpll_satisfiable((A & B) | (C & ~A) | (B & ~D))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If a particular symbol isn't present in the solution, \n", + "it means that the solution is independent of the value of that symbol.\n", + "In this case, the solution is independent of A." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{A: True, B: True}" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dpll_satisfiable(A |'<=>'| B)" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{C: True, A: True, B: False}" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dpll_satisfiable((A |'<=>'| B) |'==>'| (C & ~A))" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{C: True, A: True}" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dpll_satisfiable((A | (B & C)) |'<=>'| ((A | B) & (A | C)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2. WalkSAT algorithm\n", + "This algorithm is very similar to Hill climbing.\n", + "On every iteration, the algorithm picks an unsatisfied clause and flips a symbol in the clause.\n", + "This is similar to finding a neighboring state in the `hill_climbing` algorithm.\n", + "
    \n", + "The symbol to be flipped is decided by an evaluation function that counts the number of unsatisfied clauses.\n", + "Sometimes, symbols are also flipped randomly, to avoid local optima. A subtle balance between greediness and randomness is required. Alternatively, some versions of the algorithm restart with a completely new random assignment if no solution has been found for too long, as a way of getting out of local minima of numbers of unsatisfied clauses.\n", + "
    \n", + "
    \n", + "Let's have a look at the algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def WalkSAT(clauses, p=0.5, max_flips=10000):\n",
    +       "    """Checks for satisfiability of all clauses by randomly flipping values of variables\n",
    +       "    """\n",
    +       "    # Set of all symbols in all clauses\n",
    +       "    symbols = {sym for clause in clauses for sym in prop_symbols(clause)}\n",
    +       "    # model is a random assignment of true/false to the symbols in clauses\n",
    +       "    model = {s: random.choice([True, False]) for s in symbols}\n",
    +       "    for i in range(max_flips):\n",
    +       "        satisfied, unsatisfied = [], []\n",
    +       "        for clause in clauses:\n",
    +       "            (satisfied if pl_true(clause, model) else unsatisfied).append(clause)\n",
    +       "        if not unsatisfied:  # if model satisfies all the clauses\n",
    +       "            return model\n",
    +       "        clause = random.choice(unsatisfied)\n",
    +       "        if probability(p):\n",
    +       "            sym = random.choice(list(prop_symbols(clause)))\n",
    +       "        else:\n",
    +       "            # Flip the symbol in clause that maximizes number of sat. clauses\n",
    +       "            def sat_count(sym):\n",
    +       "                # Return the the number of clauses satisfied after flipping the symbol.\n",
    +       "                model[sym] = not model[sym]\n",
    +       "                count = len([clause for clause in clauses if pl_true(clause, model)])\n",
    +       "                model[sym] = not model[sym]\n",
    +       "                return count\n",
    +       "            sym = argmax(prop_symbols(clause), key=sat_count)\n",
    +       "        model[sym] = not model[sym]\n",
    +       "    # If no solution is found within the flip limit, we return failure\n",
    +       "    return None\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(WalkSAT)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function takes three arguments:\n", + "
    \n", + "1. The `clauses` we want to satisfy.\n", + "
    \n", + "2. The probability `p` of randomly changing a symbol.\n", + "
    \n", + "3. The maximum number of flips (`max_flips`) the algorithm will run for. If the clauses are still unsatisfied, the algorithm returns `None` to denote failure.\n", + "
    \n", + "The algorithm is identical in concept to Hill climbing and the code isn't difficult to understand.\n", + "
    \n", + "
    \n", + "Let's see a few examples of usage." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "A, B, C, D = expr('A, B, C, D')" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{C: False, A: True, D: True, B: True}" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "WalkSAT([A, B, ~C, D], 0.5, 100)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is a simple case to show that the algorithm converges." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{C: True, A: True, B: True}" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "WalkSAT([A & B, A & C], 0.5, 100)" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{C: True, A: True, D: True, B: True}" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "WalkSAT([A & B, C & D, C & B], 0.5, 100)" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "metadata": {}, + "outputs": [], + "source": [ + "WalkSAT([A & B, C | D, ~(D | B)], 0.5, 1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This one doesn't give any output because WalkSAT did not find any model where these clauses hold. We can solve these clauses to see that they together form a contradiction and hence, it isn't supposed to have a solution." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One point of difference between this algorithm and the `dpll_satisfiable` algorithms is that both these algorithms take inputs differently. \n", + "For WalkSAT to take complete sentences as input, \n", + "we can write a helper function that converts the input sentence into conjunctive normal form and then calls WalkSAT with the list of conjuncts of the CNF form of the sentence." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def WalkSAT_CNF(sentence, p=0.5, max_flips=10000):\n", + " return WalkSAT(conjuncts(to_cnf(sentence)), 0, max_flips)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can call `WalkSAT_CNF` and `DPLL_Satisfiable` with the same arguments." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{A: False, D: False, C: True, B: False}" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "WalkSAT_CNF((A & B) | (C & ~A) | (B & ~D), 0.5, 1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It works!\n", + "
    \n", + "Notice that the solution generated by WalkSAT doesn't omit variables that the sentence doesn't depend upon. \n", + "If the sentence is independent of a particular variable, the solution contains a random value for that variable because of the stochastic nature of the algorithm.\n", + "
    \n", + "
    \n", + "Let's compare the runtime of WalkSAT and DPLL for a few cases. We will use the `%%timeit` magic to do this." + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "sentence_1 = A |'<=>'| B\n", + "sentence_2 = (A & B) | (C & ~A) | (B & ~D)\n", + "sentence_3 = (A | (B & C)) |'<=>'| ((A | B) & (A | C))" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100 loops, best of 3: 2.46 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "dpll_satisfiable(sentence_1)\n", + "dpll_satisfiable(sentence_2)\n", + "dpll_satisfiable(sentence_3)" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100 loops, best of 3: 1.91 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "WalkSAT_CNF(sentence_1)\n", + "WalkSAT_CNF(sentence_2)\n", + "WalkSAT_CNF(sentence_3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On an average, for solvable cases, `WalkSAT` is quite faster than `dpll` because, for a small number of variables, \n", + "`WalkSAT` can reduce the search space significantly. \n", + "Results can be different for sentences with more symbols though.\n", + "Feel free to play around with this to understand the trade-offs of these algorithms better." + ] + }, { "cell_type": "markdown", "metadata": {}, From 0cd061206ede84cf6f6c808e4cd2064f752f7c54 Mon Sep 17 00:00:00 2001 From: Nouman Ahmed <35970677+Noumanmufc1@users.noreply.github.com> Date: Tue, 13 Mar 2018 16:09:40 +0500 Subject: [PATCH 070/269] Added test for SimpleReflexAgentProgram (#808) * Added test for simpleReflexAgent * Fixed a bug * Fixed another bug --- README.md | 2 +- tests/test_agents.py | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a793deb30..968632477 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 2.3 | Table-Driven-Vacuum-Agent | `TableDrivenVacuumAgent` | [`agents.py`][agents] | Done | Included | | 2.7 | Table-Driven-Agent | `TableDrivenAgent` | [`agents.py`][agents] | Done | Included | | 2.8 | Reflex-Vacuum-Agent | `ReflexVacuumAgent` | [`agents.py`][agents] | Done | Included | -| 2.10 | Simple-Reflex-Agent | `SimpleReflexAgent` | [`agents.py`][agents] | | Included | +| 2.10 | Simple-Reflex-Agent | `SimpleReflexAgent` | [`agents.py`][agents] | Done | Included | | 2.12 | Model-Based-Reflex-Agent | `ReflexAgentWithState` | [`agents.py`][agents] | | Included | | 3 | Problem | `Problem` | [`search.py`][search] | Done | Included | | 3 | Node | `Node` | [`search.py`][search] | Done | Included | diff --git a/tests/test_agents.py b/tests/test_agents.py index caefe61d4..d5f63bc48 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -2,7 +2,8 @@ from agents import Direction from agents import Agent from agents import ReflexVacuumAgent, ModelBasedVacuumAgent, TrivialVacuumEnvironment, compare_agents,\ - RandomVacuumAgent, TableDrivenVacuumAgent, TableDrivenAgentProgram, RandomAgentProgram + RandomVacuumAgent, TableDrivenVacuumAgent, TableDrivenAgentProgram, RandomAgentProgram, \ + SimpleReflexAgentProgram, rule_match random.seed("aima-python") @@ -131,6 +132,38 @@ def test_ReflexVacuumAgent() : # check final status of the environment assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} +def test_SimpleReflexAgentProgram(): + class Rule: + + def __init__(self, state, action): + self.__state = state + self.action = action + + def matches(self, state): + return self.__state == state + + loc_A = (0, 0) + loc_B = (1, 0) + + # create rules for a two state Vacuum Environment + rules = [Rule((loc_A, "Dirty"), "Suck"), Rule((loc_A, "Clean"), "Right"), + Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] + + def interpret_input(state): + return state + + # create a program and then an object of the SimpleReflexAgentProgram + program = SimpleReflexAgentProgram(rules, interpret_input) + agent = Agent(program) + # create an object of TrivialVacuumEnvironment + environment = TrivialVacuumEnvironment() + # add agent to the environment + environment.add_thing(agent) + # run the environment + environment.run() + # check final status of the environment + assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} + def test_ModelBasedVacuumAgent() : # create an object of the ModelBasedVacuumAgent From dc16a97cdc029be0f78cd49944bd6a06ab72c918 Mon Sep 17 00:00:00 2001 From: Aabir Abubaker Kar <16526730+bakerwho@users.noreply.github.com> Date: Tue, 13 Mar 2018 07:10:40 -0400 Subject: [PATCH 071/269] Move viz code + changes to search (#812) * Updating submodule * Moved viz code to notebook.py + changes * Changed use of 'next' * Added networkx to .travis.yml * Added others to .travis.yml * Remove time from .travis.yml * Added linebreaks and fixed case for no algo * Fixed spaces for args * Renamed *search as *search_for_vis --- .travis.yml | 2 + notebook.py | 156 ++++ search.ipynb | 2280 ++++++-------------------------------------------- search.py | 56 +- 4 files changed, 468 insertions(+), 2026 deletions(-) diff --git a/.travis.yml b/.travis.yml index e0932e6b2..600d6bd00 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,8 @@ install: - pip install flake8 - pip install ipython - pip install matplotlib + - pip install networkx + - pip install ipywidgets script: - py.test diff --git a/notebook.py b/notebook.py index 6e1a0fbfc..ae0976900 100644 --- a/notebook.py +++ b/notebook.py @@ -886,3 +886,159 @@ def draw_table(self): self.fill(0, 0, 0) self.text_n(self.table[self.context[0]][self.context[1]] if self.context else "Click for text", 0.025, 0.975) self.update() + +############################################################################################################ + +##################### Functions to assist plotting in search.ipynb #################### + +############################################################################################################ +import networkx as nx +import matplotlib.pyplot as plt +from matplotlib import lines + +from ipywidgets import interact +import ipywidgets as widgets +from IPython.display import display +import time +from search import GraphProblem, romania_map + +def show_map(graph_data, node_colors = None): + G = nx.Graph(graph_data['graph_dict']) + node_colors = node_colors or graph_data['node_colors'] + node_positions = graph_data['node_positions'] + node_label_pos = graph_data['node_label_positions'] + edge_weights= graph_data['edge_weights'] + + # set the size of the plot + plt.figure(figsize=(18,13)) + # draw the graph (both nodes and edges) with locations from romania_locations + nx.draw(G, pos = {k : node_positions[k] for k in G.nodes()}, + node_color = [node_colors[node] for node in G.nodes()], linewidths = 0.3, edgecolors = 'k') + + # draw labels for nodes + node_label_handles = nx.draw_networkx_labels(G, pos = node_label_pos, font_size = 14) + + # add a white bounding box behind the node labels + [label.set_bbox(dict(facecolor='white', edgecolor='none')) for label in node_label_handles.values()] + + # add edge lables to the graph + nx.draw_networkx_edge_labels(G, pos = node_positions, edge_labels = edge_weights, font_size = 14) + + # add a legend + white_circle = lines.Line2D([], [], color="white", marker='o', markersize=15, markerfacecolor="white") + orange_circle = lines.Line2D([], [], color="orange", marker='o', markersize=15, markerfacecolor="orange") + red_circle = lines.Line2D([], [], color="red", marker='o', markersize=15, markerfacecolor="red") + gray_circle = lines.Line2D([], [], color="gray", marker='o', markersize=15, markerfacecolor="gray") + green_circle = lines.Line2D([], [], color="green", marker='o', markersize=15, markerfacecolor="green") + plt.legend((white_circle, orange_circle, red_circle, gray_circle, green_circle), + ('Un-explored', 'Frontier', 'Currently Exploring', 'Explored', 'Final Solution'), + numpoints=1,prop={'size':16}, loc=(.8,.75)) + + # show the plot. No need to use in notebooks. nx.draw will show the graph itself. + plt.show() + +## helper functions for visualisations + +def final_path_colors(initial_node_colors, problem, solution): + "returns a node_colors dict of the final path provided the problem and solution" + + # get initial node colors + final_colors = dict(initial_node_colors) + # color all the nodes in solution and starting node to green + final_colors[problem.initial] = "green" + for node in solution: + final_colors[node] = "green" + return final_colors + +def display_visual(graph_data, user_input, algorithm=None, problem=None): + initial_node_colors = graph_data['node_colors'] + if user_input == False: + def slider_callback(iteration): + # don't show graph for the first time running the cell calling this function + try: + show_map(graph_data, node_colors = all_node_colors[iteration]) + except: + pass + def visualize_callback(Visualize): + if Visualize is True: + button.value = False + + global all_node_colors + + iterations, all_node_colors, node = algorithm(problem) + solution = node.solution() + all_node_colors.append(final_path_colors(all_node_colors[0], problem, solution)) + + slider.max = len(all_node_colors) - 1 + + for i in range(slider.max + 1): + slider.value = i + #time.sleep(.5) + + slider = widgets.IntSlider(min=0, max=1, step=1, value=0) + slider_visual = widgets.interactive(slider_callback, iteration = slider) + display(slider_visual) + + button = widgets.ToggleButton(value = False) + button_visual = widgets.interactive(visualize_callback, Visualize = button) + display(button_visual) + + if user_input == True: + node_colors = dict(initial_node_colors) + if isinstance(algorithm, dict): + assert set(algorithm.keys()).issubset(set(["Breadth First Tree Search", + "Depth First Tree Search", + "Breadth First Search", + "Depth First Graph Search", + "Uniform Cost Search", + "A-star Search"])) + + algo_dropdown = widgets.Dropdown(description = "Search algorithm: ", + options = sorted(list(algorithm.keys())), + value = "Breadth First Tree Search") + display(algo_dropdown) + elif algorithm is None: + print("No algorithm to run.") + return 0 + + def slider_callback(iteration): + # don't show graph for the first time running the cell calling this function + try: + show_map(graph_data, node_colors = all_node_colors[iteration]) + except: + pass + + def visualize_callback(Visualize): + if Visualize is True: + button.value = False + + problem = GraphProblem(start_dropdown.value, end_dropdown.value, romania_map) + global all_node_colors + + user_algorithm = algorithm[algo_dropdown.value] + + iterations, all_node_colors, node = user_algorithm(problem) + solution = node.solution() + all_node_colors.append(final_path_colors(all_node_colors[0], problem, solution)) + + slider.max = len(all_node_colors) - 1 + + for i in range(slider.max + 1): + slider.value = i + #time.sleep(.5) + + start_dropdown = widgets.Dropdown(description = "Start city: ", + options = sorted(list(node_colors.keys())), value = "Arad") + display(start_dropdown) + + end_dropdown = widgets.Dropdown(description = "Goal city: ", + options = sorted(list(node_colors.keys())), value = "Fagaras") + display(end_dropdown) + + button = widgets.ToggleButton(value = False) + button_visual = widgets.interactive(visualize_callback, Visualize = button) + display(button_visual) + + slider = widgets.IntSlider(min=0, max=1, step=1, value=0) + slider_visual = widgets.interactive(slider_callback, iteration = slider) + display(slider_visual) \ No newline at end of file diff --git a/search.ipynb b/search.ipynb index edcdf592f..1ac4b075a 100644 --- a/search.ipynb +++ b/search.ipynb @@ -13,14 +13,15 @@ }, { "cell_type": "code", - "execution_count": 134, + "execution_count": null, "metadata": { + "collapsed": true, "scrolled": true }, "outputs": [], "source": [ "from search import *\n", - "from notebook import psource\n", + "from notebook import psource, show_map, final_path_colors, display_visual\n", "\n", "# Needed to hide warnings in the matplotlib sections\n", "import warnings\n", @@ -73,6 +74,32 @@ "*Don't miss the visualisations of these algorithms solving the route-finding problem defined on Romania map at the end of this notebook.*" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For visualisations, we use networkx and matplotlib to show the map in the notebook and we use ipywidgets to interact with the map to see how the searching algorithm works. These are imported as required in `notebook.py`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import networkx as nx\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib import lines\n", + "\n", + "from ipywidgets import interact\n", + "import ipywidgets as widgets\n", + "from IPython.display import display\n", + "import time" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -84,159 +111,9 @@ }, { "cell_type": "code", - "execution_count": 135, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
    class Problem(object):\n",
    -       "\n",
    -       "    """The abstract class for a formal problem. You should subclass\n",
    -       "    this and implement the methods actions and result, and possibly\n",
    -       "    __init__, goal_test, and path_cost. Then you will create instances\n",
    -       "    of your subclass and solve them with the various search functions."""\n",
    -       "\n",
    -       "    def __init__(self, initial, goal=None):\n",
    -       "        """The constructor specifies the initial state, and possibly a goal\n",
    -       "        state, if there is a unique goal. Your subclass's constructor can add\n",
    -       "        other arguments."""\n",
    -       "        self.initial = initial\n",
    -       "        self.goal = goal\n",
    -       "\n",
    -       "    def actions(self, state):\n",
    -       "        """Return the actions that can be executed in the given\n",
    -       "        state. The result would typically be a list, but if there are\n",
    -       "        many actions, consider yielding them one at a time in an\n",
    -       "        iterator, rather than building them all at once."""\n",
    -       "        raise NotImplementedError\n",
    -       "\n",
    -       "    def result(self, state, action):\n",
    -       "        """Return the state that results from executing the given\n",
    -       "        action in the given state. The action must be one of\n",
    -       "        self.actions(state)."""\n",
    -       "        raise NotImplementedError\n",
    -       "\n",
    -       "    def goal_test(self, state):\n",
    -       "        """Return True if the state is a goal. The default method compares the\n",
    -       "        state to self.goal or checks for state in self.goal if it is a\n",
    -       "        list, as specified in the constructor. Override this method if\n",
    -       "        checking against a single self.goal is not enough."""\n",
    -       "        if isinstance(self.goal, list):\n",
    -       "            return is_in(state, self.goal)\n",
    -       "        else:\n",
    -       "            return state == self.goal\n",
    -       "\n",
    -       "    def path_cost(self, c, state1, action, state2):\n",
    -       "        """Return the cost of a solution path that arrives at state2 from\n",
    -       "        state1 via action, assuming cost c to get up to state1. If the problem\n",
    -       "        is such that the path doesn't matter, this function will only look at\n",
    -       "        state2.  If the path does matter, it will consider c and maybe state1\n",
    -       "        and action. The default method costs 1 for every step in the path."""\n",
    -       "        return c + 1\n",
    -       "\n",
    -       "    def value(self, state):\n",
    -       "        """For optimization problems, each state has a value.  Hill-climbing\n",
    -       "        and related algorithms try to maximize this value."""\n",
    -       "        raise NotImplementedError\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "psource(Problem)" ] @@ -276,171 +153,9 @@ }, { "cell_type": "code", - "execution_count": 136, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
    class Node:\n",
    -       "\n",
    -       "    """A node in a search tree. Contains a pointer to the parent (the node\n",
    -       "    that this is a successor of) and to the actual state for this node. Note\n",
    -       "    that if a state is arrived at by two paths, then there are two nodes with\n",
    -       "    the same state.  Also includes the action that got us to this state, and\n",
    -       "    the total path_cost (also known as g) to reach the node.  Other functions\n",
    -       "    may add an f and h value; see best_first_graph_search and astar_search for\n",
    -       "    an explanation of how the f and h values are handled. You will not need to\n",
    -       "    subclass this class."""\n",
    -       "\n",
    -       "    def __init__(self, state, parent=None, action=None, path_cost=0):\n",
    -       "        """Create a search tree Node, derived from a parent by an action."""\n",
    -       "        self.state = state\n",
    -       "        self.parent = parent\n",
    -       "        self.action = action\n",
    -       "        self.path_cost = path_cost\n",
    -       "        self.depth = 0\n",
    -       "        if parent:\n",
    -       "            self.depth = parent.depth + 1\n",
    -       "\n",
    -       "    def __repr__(self):\n",
    -       "        return "<Node {}>".format(self.state)\n",
    -       "\n",
    -       "    def __lt__(self, node):\n",
    -       "        return self.state < node.state\n",
    -       "\n",
    -       "    def expand(self, problem):\n",
    -       "        """List the nodes reachable in one step from this node."""\n",
    -       "        return [self.child_node(problem, action)\n",
    -       "                for action in problem.actions(self.state)]\n",
    -       "\n",
    -       "    def child_node(self, problem, action):\n",
    -       "        """[Figure 3.10]"""\n",
    -       "        next = problem.result(self.state, action)\n",
    -       "        return Node(next, self, action,\n",
    -       "                    problem.path_cost(self.path_cost, self.state,\n",
    -       "                                      action, next))\n",
    -       "\n",
    -       "    def solution(self):\n",
    -       "        """Return the sequence of actions to go from the root to this node."""\n",
    -       "        return [node.action for node in self.path()[1:]]\n",
    -       "\n",
    -       "    def path(self):\n",
    -       "        """Return a list of nodes forming the path from the root to this node."""\n",
    -       "        node, path_back = self, []\n",
    -       "        while node:\n",
    -       "            path_back.append(node)\n",
    -       "            node = node.parent\n",
    -       "        return list(reversed(path_back))\n",
    -       "\n",
    -       "    # We want for a queue of nodes in breadth_first_search or\n",
    -       "    # astar_search to have no duplicated states, so we treat nodes\n",
    -       "    # with the same state as equal. [Problem: this may not be what you\n",
    -       "    # want in other contexts.]\n",
    -       "\n",
    -       "    def __eq__(self, other):\n",
    -       "        return isinstance(other, Node) and self.state == other.state\n",
    -       "\n",
    -       "    def __hash__(self):\n",
    -       "        return hash(self.state)\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "psource(Node)" ] @@ -479,148 +194,9 @@ }, { "cell_type": "code", - "execution_count": 137, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
    class GraphProblem(Problem):\n",
    -       "\n",
    -       "    """The problem of searching a graph from one node to another."""\n",
    -       "\n",
    -       "    def __init__(self, initial, goal, graph):\n",
    -       "        Problem.__init__(self, initial, goal)\n",
    -       "        self.graph = graph\n",
    -       "\n",
    -       "    def actions(self, A):\n",
    -       "        """The actions at a graph node are just its neighbors."""\n",
    -       "        return list(self.graph.get(A).keys())\n",
    -       "\n",
    -       "    def result(self, state, action):\n",
    -       "        """The result of going to a neighbor is just that neighbor."""\n",
    -       "        return action\n",
    -       "\n",
    -       "    def path_cost(self, cost_so_far, A, action, B):\n",
    -       "        return cost_so_far + (self.graph.get(A, B) or infinity)\n",
    -       "\n",
    -       "    def find_min_edge(self):\n",
    -       "        """Find minimum value of edges."""\n",
    -       "        m = infinity\n",
    -       "        for d in self.graph.dict.values():\n",
    -       "            local_min = min(d.values())\n",
    -       "            m = min(m, local_min)\n",
    -       "\n",
    -       "        return m\n",
    -       "\n",
    -       "    def h(self, node):\n",
    -       "        """h function is straight-line distance from a node's state to goal."""\n",
    -       "        locs = getattr(self.graph, 'locations', None)\n",
    -       "        if locs:\n",
    -       "            if type(node) is str:\n",
    -       "                return int(distance(locs[node], locs[self.goal]))\n",
    -       "\n",
    -       "            return int(distance(locs[node.state], locs[self.goal]))\n",
    -       "        else:\n",
    -       "            return infinity\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "psource(GraphProblem)" ] @@ -634,8 +210,10 @@ }, { "cell_type": "code", - "execution_count": 138, - "metadata": {}, + "execution_count": null, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "romania_map = UndirectedGraph(dict(\n", @@ -679,8 +257,10 @@ }, { "cell_type": "code", - "execution_count": 139, - "metadata": {}, + "execution_count": null, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)" @@ -704,46 +284,14 @@ }, { "cell_type": "code", - "execution_count": 140, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'Arad': (91, 492), 'Bucharest': (400, 327), 'Craiova': (253, 288), 'Drobeta': (165, 299), 'Eforie': (562, 293), 'Fagaras': (305, 449), 'Giurgiu': (375, 270), 'Hirsova': (534, 350), 'Iasi': (473, 506), 'Lugoj': (165, 379), 'Mehadia': (168, 339), 'Neamt': (406, 537), 'Oradea': (131, 571), 'Pitesti': (320, 368), 'Rimnicu': (233, 410), 'Sibiu': (207, 457), 'Timisoara': (94, 410), 'Urziceni': (456, 350), 'Vaslui': (509, 444), 'Zerind': (108, 531)}\n" - ] - } - ], + "outputs": [], "source": [ "romania_locations = romania_map.locations\n", "print(romania_locations)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's start the visualisations by importing necessary modules. We use networkx and matplotlib to show the map in the notebook and we use ipywidgets to interact with the map to see how the searching algorithm works." - ] - }, - { - "cell_type": "code", - "execution_count": 141, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import networkx as nx\n", - "import matplotlib.pyplot as plt\n", - "from matplotlib import lines\n", - "\n", - "from ipywidgets import interact\n", - "import ipywidgets as widgets\n", - "from IPython.display import display\n", - "import time" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -753,46 +301,24 @@ }, { "cell_type": "code", - "execution_count": 142, - "metadata": {}, + "execution_count": null, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ - "# initialise a graph\n", - "G = nx.Graph()\n", - "\n", - "# use this while labeling nodes in the map\n", - "node_labels = dict()\n", - "# use this to modify colors of nodes while exploring the graph.\n", - "# This is the only dict we send to `show_map(node_colors)` while drawing the map\n", - "node_colors = dict()\n", - "\n", - "for n, p in romania_locations.items():\n", - " # add nodes from romania_locations\n", - " G.add_node(n)\n", - " # add nodes to node_labels\n", - " node_labels[n] = n\n", - " # node_colors to color nodes while exploring romania map\n", - " node_colors[n] = \"white\"\n", + "# node colors, node positions and node label positions\n", + "node_colors = {node: 'white' for node in romania_map.locations.keys()}\n", + "node_positions = romania_map.locations\n", + "node_label_pos = { k:[v[0],v[1]-10] for k,v in romania_map.locations.items() }\n", + "edge_weights = {(k, k2) : v2 for k, v in romania_map.graph_dict.items() for k2, v2 in v.items()}\n", "\n", - "# we'll save the initial node colors to a dict to use later\n", - "initial_node_colors = dict(node_colors)\n", - " \n", - "# positions for node labels\n", - "node_label_pos = { k:[v[0],v[1]-10] for k,v in romania_locations.items() }\n", - "\n", - "# use this while labeling edges\n", - "edge_labels = dict()\n", - "\n", - "# add edges between cities in romania map - UndirectedGraph defined in search.py\n", - "for node in romania_map.nodes():\n", - " connections = romania_map.get(node)\n", - " for connection in connections.keys():\n", - " distance = connections[connection]\n", - "\n", - " # add edges to the graph\n", - " G.add_edge(node, connection)\n", - " # add distances to edge_labels\n", - " edge_labels[(node, connection)] = distance" + "romania_graph_data = { 'graph_dict' : romania_map.graph_dict,\n", + " 'node_colors': node_colors,\n", + " 'node_positions': node_positions,\n", + " 'node_label_positions': node_label_pos,\n", + " 'edge_weights': edge_weights\n", + " }" ] }, { @@ -802,40 +328,6 @@ "We have completed building our graph based on romania_map and its locations. It's time to display it here in the notebook. This function `show_map(node_colors)` helps us do that. We will be calling this function later on to display the map at each and every interval step while searching, using variety of algorithms from the book." ] }, - { - "cell_type": "code", - "execution_count": 143, - "metadata": {}, - "outputs": [], - "source": [ - "def show_map(node_colors):\n", - " # set the size of the plot\n", - " plt.figure(figsize=(18,13))\n", - " # draw the graph (both nodes and edges) with locations from romania_locations\n", - " nx.draw(G, pos = romania_locations, node_color = [node_colors[node] for node in G.nodes()])\n", - "\n", - " # draw labels for nodes\n", - " node_label_handles = nx.draw_networkx_labels(G, pos = node_label_pos, labels = node_labels, font_size = 14)\n", - " # add a white bounding box behind the node labels\n", - " [label.set_bbox(dict(facecolor='white', edgecolor='none')) for label in node_label_handles.values()]\n", - "\n", - " # add edge lables to the graph\n", - " nx.draw_networkx_edge_labels(G, pos = romania_locations, edge_labels=edge_labels, font_size = 14)\n", - " \n", - " # add a legend\n", - " white_circle = lines.Line2D([], [], color=\"white\", marker='o', markersize=15, markerfacecolor=\"white\")\n", - " orange_circle = lines.Line2D([], [], color=\"orange\", marker='o', markersize=15, markerfacecolor=\"orange\")\n", - " red_circle = lines.Line2D([], [], color=\"red\", marker='o', markersize=15, markerfacecolor=\"red\")\n", - " gray_circle = lines.Line2D([], [], color=\"gray\", marker='o', markersize=15, markerfacecolor=\"gray\")\n", - " green_circle = lines.Line2D([], [], color=\"green\", marker='o', markersize=15, markerfacecolor=\"green\")\n", - " plt.legend((white_circle, orange_circle, red_circle, gray_circle, green_circle),\n", - " ('Un-explored', 'Frontier', 'Currently Exploring', 'Explored', 'Final Solution'),\n", - " numpoints=1,prop={'size':16}, loc=(.8,.75))\n", - " \n", - " # show the plot. No need to use in notebooks. nx.draw will show the graph itself.\n", - " plt.show()" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -845,24 +337,13 @@ }, { "cell_type": "code", - "execution_count": 144, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABTsAAAPKCAYAAABbVI7QAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAIABJREFUeJzs3Xl8DWf///H3iUiILRVCRC0NIsS+FyVU01qKu6itlZvgRlKk9jURsZXYW7WVFqVa1FZrrRWqWmqJWkrt1L6LJOf3R745v54mSEgyyfF6Ph7zSGfmmpn3TBbNJ9c1l8lsNpsFAAAAAAAAABmcndEBAAAAAAAAACAlUOwEAAAAAAAAYBModgIAAAAAAACwCRQ7AQAAAAAAANgEip0AAAAAAAAAbALFTgAAAAAAAAA2gWInAAAAAAAAAJtAsRMAAAAAAACATaDYCQAAAAAAAMAmUOwEAAAAAAAAYBModgIAAAAAAACwCRQ7AQAAAAAAANgEip0AAAAAAAAAbALFTgAAAAAAAAA2gWInAAAAAAAAAJtAsRMAAAAAAACATaDYCQAAAAAAAMAmUOwEAAAAAAAAYBModgIAAAAAAACwCRQ7AQAAAAAAANgEip0AAAAAAAAAbALFTgAAAAAAAAA2gWInAAAAAAAAAJtAsRMAAAAAAACATaDYCQAAAAAAAMAmUOwEAAAAAAAAYBModgIAAAAAAACwCRQ7AQAAAAAAANgEip0AAAAAAAAAbALFTgAAAAAAAAA2gWInAAAAAAAAAJtAsRMAAAAAAACATaDYCQAAAAAAAMAmUOwEAAAAAAAAYBModgIAAAAAAACwCRQ7AQAAAAAAANgEip0AAAAAAAAAbALFTgAAAAAAAAA2gWInAAAAAAAAAJtAsRMAAAAAAACATaDYCQAAAAAAAMAmUOwEAAAAAAAAYBModgIAAAAAAACwCRQ7AQAAAAAAANgEip0AAAAAAAAAbALFTgAAAAAAAAA2gWInAAAAAAAAAJtAsRMAAAAAAACATaDYCQAAAAAAAMAmUOwEAAAAAAAAYBModgIAAAAAAACwCRQ7AQAAAAAAANgEip0AAAAAAAAAbALFTgAAAAAAAAA2gWInAAAAAAAAAJtAsRMAAAAAAACATaDYCQAAAAAAAMAmUOwEAAAAAAAAYBModuKlYTabFRMT89Q29+7de2YbAAAAAAAApE8UO/HSmDlzpg4cOPDUNkuWLNHAgQPTKBEAAAAAAABSkslsNpuNDgGktqtXr8rT01O//vqrChcu/MR2V65cUenSpbV161aVLl06DRMCAAAAAADgRdGzEy+FTz75RO+///5TC52S5OrqqqFDh+qjjz4SfwcAAAAAAADIWOjZCZt3+fJllSpVSgcOHFDBggWf2T46OloVKlTQ8OHD1aJFizRICAAAAAAAgJRAsRM2LygoSDExMZo8eXKSj9m6das6dOigyMhIOTk5pWI6AAAAAAAApBSKnbBpFy5ckLe3tw4fPiw3N7dkHfv+++/L09NTI0aMSKV0AAAAAAAASEkUO2HTAgMD5eDgoAkTJiT72LNnz6p8+fLau3evXnvttVRIBwAAAAAAgJREsRM26+zZsypXrpyOHj0qV1fX5zpHWFiY9u7dqxUrVqRwOgAAAAAAAKQ0ip2wWd26dVOuXLk0ZsyY5z7Hw4cP5e3trenTp8vX1zcF0wEAAAAAACClUeyETTp9+rQqVaqkP/74Q3ny5Hmhc61atUp9+vTRwYMH5eDgkEIJAQAAAAAAkNIodgIAAAAAAACwCXZGBwAAAAAAAACAlECxEwAAAAAAAIBNoNgJAAAAAAAAwCZQ7AQAAAAAAABgEyh2Av/y7bffymQyGR0DAAAAAAAAyUSxE+ne+fPn1aVLFxUsWFAODg5yd3dX586dde7cOaOjAQAAAAAAIB2h2Il07dSpU6pcubIOHTqk+fPn68SJE1qwYIEOHz6sKlWq6PTp04keFxUVlbZBAQAAAAAAYDiKnUjXevToITs7O23atEn169dXoUKF5OPjo02bNsnOzk49evSQJNWtW1fdunVTnz59lDdvXtWsWVOSFB4errJlyypbtmxyd3eXv7+/bt68aXWNL7/8UoULF5aTk5MaN26sy5cvJ8ixatUqVapUSVmyZFHRokU1ePBgq4LqggULVKVKFeXIkUOurq5q2bKlzp8/n4pPBgAAAAAAAP9GsRPp1vXr17Vu3Tr16NFDTk5OVvucnJzUvXt3/fDDD7px44akuIKj2WzWjh079OWXX0qS7OzsNGnSJB0+fFiLFi3Szz//rMDAQMt59uzZIz8/P3Xp0kX79+9XkyZNNGzYMKtrrV+/Xu3atVNAQIAOHz6suXPn6ttvv9WgQYMsbaKiohQSEqIDBw5o9erVunr1qtq0aZNajwYAAAAAAACJMJnNZrPRIYDE7NmzR9WrV9eyZcvUvHnzBPuXL1+u//znP9qzZ4/69eun69ev6/fff3/qOdetW6emTZvqwYMHsrOzU9u2bfX3339r48aNljb+/v6aM2eO4r813njjDTVo0EBDhw61tFmxYoXat2+vO3fuJDqZ0dGjR+Xl5aWzZ8+qYMGCz/sIAAAAAAAAkAz07ES696SZ0eOLkfH7K1WqlKDNjz/+qAYNGqhgwYLKkSOH/vOf/ygqKkqXLl2SJEVGRqpGjRpWx/x7fd++fQoLC1P27NktS9u2bXXv3j3LeX799Vc1bdpUhQsXVo4cOVS5cmVJ0pkzZ17gzgEAAAAAAJAcFDuRbhUvXlwmk0mHDx9OdH9kZKRMJpM8PDwkSdmyZbPa/9dff6lRo0by8vLS0qVLtW/fPs2dO1fS/5/AKCkdm2NjYzV8+HDt37/fsvz+++86fvy48ubNq3v37snX11dOTk766quvtHfvXq1bt87qOgAAAAAAAEh99kYHAJ4kd+7c8vX11aeffqrevXtbvbfz/v37mj59ut555x3lzp070eN/+eUXRUVFaeLEicqUKZMkafXq1VZtSpUqpd27d1tt+/d6xYoVdfToURUrVizR6xw4cEBXr17VqFGjVLRoUUnSsmXLknezAAAAAAAAeGH07ES6Nm3aNEVHR+vNN9/Ujz/+qLNnz2rr1q1q0KCBzGazpk2b9sRjixcvrtjYWE2aNEmnTp3S119/rUmTJlm1+eijj7Rp0yaNHj1ax48f16xZs7R8+XKrNsOGDdOiRYs0bNgwHTp0SEePHtW3336rfv36SZIKFSokR0dHTZs2TX/++afWrFlj9X5PAAAAAAAApA2KnUjXPDw89Msvv6h06dL64IMP9Nprr6lt27by8vLS3r17LT0pE1O2bFlNnjxZ4eHhKlWqlGbPnq3x48dbtalevbrmzJmjzz77TGXLltWyZcsUHBxs1cbX11dr1qzRli1bVLVqVVWtWlVjxoxRoUKFJEl58+bV/PnztWLFCpUqVUohISEKDw9P8WcBAAAAAACAp2M2dgAAAAAAAAA2gZ6dAAAAAAAAAGwCxU4AAAAAAAAANoFiJwAAAAAAAACbQLETAAAAAAAAgE2g2AkAAAAAAADAJlDsRIZgNptVqVIlLVu2zOgoSWI2m9WgQQNNmjTJ6CgAAAAAAAAvDYqdyBBWrlyp2NhYNWvWzOgoSWIymTRlyhSNHDlSly9fNjoOAAAAAADAS8FkNpvNRocAniY2NlYVKlRQaGio3n33XaPjJMvHH3+sGzduaO7cuUZHAQAAAAAAsHn07ES6t2zZMjk4OKhJkyZGR0m24cOHa926ddqzZ4/RUQAAAAAAAGwexU6ka2azWX///bdGjBghk8lkdJxky5kzp0aPHq3AwEDFxsYaHQcAAAAAAMCmMYwd6V78l2hGLHZKccPwa9asKX9/f3Xq1MnoOAAAAAAAADaLYieQBvbt26dGjRrp6NGjcnZ2NjoOAAAAAACATaLYCaSRLl26KGvWrJo8ebLRUQAAAAAAAGwSxU4gjfz9998qVaqUtmzZIm9vb6PjAAAAAAAA2BwmKALSSN68eTV8+HAFBgaKvzEAAAAAAACkPIqdQBr63//+p2vXrmnp0qVGRwEAAAAAALA5DGMH0ti2bdv0wQcfKDIyUtmyZTM6DgAAAAAAgM2gZycMdf36daMjpLk6deqoZs2aGj16tNFRAAAAAAAAbAo9O2GY2bNna+fOnfLz81P58uXl7Oxs2Wc2m2UymZ64ntGdO3dO5cqV088//ywPDw+j4wAAAAAAANgEip0wRExMjHLnzq2oqCg5OzurefPmat26tcqVK6dcuXJZ2t27d0+ZM2eWg4ODgWlTx+jRoxUREaGVK1caHQUAAAAAAMAmMIwdhvj2229VunRp/fbbbwoJCdHatWvVsmVLDR06VDt27NCdO3ckSZMmTbLZ4d5BQUGKjIzUDz/8YHQUAAAAAAAAm0DPThhizZo12rx5s/r166f8+fNLkqZNm6axY8cqOjpabdq0UdWqVdW2bVtt3LhR9evXNzhx6lizZo169+6tgwcPytHR0eg4AAAAAAAAGRrFTqS5u3fvKnv27Przzz/12muvKTo6Wvb29pb9kydP1sSJE3XmzBnVrl1b27ZtMzBt6mvcuLFq166t/v37Gx0FAAAAAAAgQ6PYiTT18OFDNW7cWGPGjFHlypWtJh76Z9Hz6NGjKlWqlHbv3q2qVasaGTnVnThxQtWrV9eBAwfk7u5udBwAAAAAAIAMi3d2Ik0NGTJEP/74owYOHKjbt29bzbAeX+iMiYnRqFGjVLx4cZsvdEpSsWLF1KVLF/Xr18/oKAAAAAAAABkaxU6kmVu3bmny5MmaPXu2Ll68qLZt2+rixYuS4gqc8cxms2rXrq2lS5caFTXNDRo0SNu3b9eOHTuMjgIAAAAAAJBhMYwdacbf319//vmnfvzxRy1YsEC9evVSmzZtNHXq1ARtY2JilClTJgNSGmfx4sUaM2aM9u3b99LdOwAAAAAAQEqg2Ik0ce3aNeXPn1+7du1SlSpVJMUV9wIDA/XBBx8oLCxMWbNmVWxsrOzsXs4Ox2azWT4+PmrVqpW6d+9udBwAAAAAAIAMh2In0kS3bt30xx9/6Mcff1RMTIzs7OwUHR2tUaNGadKkSfrkk0/k7+9vdEzD/f7773rzzTd15MgR5cmTx+g4AAAAAAAAGQrFTqSJqKgo3blzRy4uLgn2DR48WFOnTtX48ePVpUsXA9KlL4GBgXr8+LFmzJhhdBQAAAAAAIAMhWInDBM/ZP3atWsKDAzU+vXrtXnzZpUvX97oaIa6ceOGvLy8tHbtWlWsWNHoOAAAAAAAABnGy/lyRKQL8e/mdHFx0Zw5c1S+fHk5OTkZnMp4r7zyikJDQxUYGCj+FgEAAAAAAJB09OyE4eJ7eN6+fVs5c+Y0Ok66EBMTo+rVq+ujjz7SBx98YHQcAAAAAACADIFiJ9JU/OREkmQymQxOk77t2bNH//nPfxQZGUkRGAAAAAAAIAkYxo401adPHy1YsIBCZxJUq1ZNb731lkJDQ42OAgAAAAAAkCHQsxNp5sKFC/L29taRI0eUP39+o+NkCJcvX5a3t7d27NihkiVLGh0HAAAAAAAgXaPYiTQTGBgoR0dHjR8/3ugoGcrEiRO1bt06rVu3jh6xAAAAAAAAT0GxE2ni7NmzKl++vCIjI+Xq6mp0nAzl8ePHKl++vMLCwtSsWTOj4wAAAABAmrt9+7auXLmix48fGx0FyNAyZ84sV1dXm54bhGIn0sT//vc/OTs7a8yYMUZHyZA2b96szp076/Dhw8qaNavRcQAAAAAgzdy+fVuXL1+Wu7u7smbNyog34DmZzWY9ePBA58+fV758+Wy24EmxE6nu9OnTqlSpko4dOyYXFxej42RYLVq0UNmyZTVs2DCjowAAAABAmjlx4oQKFCggJycno6MANuH+/fu6cOGCihUrZnSUVMFs7Eh1I0eOVPfu3Sl0vqAJEyZoypQp+uuvv4yOAgAAAABp5vHjx4xwA1JQ1qxZbfqVEBQ7kapOnjypFStWKCgoyOgoGV7hwoX10Ucf6eOPPzY6CgAAAACkKYauAynH1r+fKHYiVY0YMUKBgYF65ZVXjI5iE/r27atff/1VmzdvNjoKAAAAAABAumNvdADYrj/++ENr167ViRMnjI5iM7Jmzarw8HAFBgbqwIEDypw5s9GRAAAAAAAA0g16diLVjBgxQr1791auXLmMjmJTmjZtqldffVXTpk0zOgoAAAAA4Dn4+fmpYMGCie7bunWrTCaTNm3alMapUk78PWzdutXoKBZ+fn4qUqSI0TGQBih2IlUcOXJEmzZtUmBgoNFRbI7JZNLkyZM1atQoXb582eg4AAAAAAAA6QbFTqSK4OBgffzxx8qRI4fRUWxSyZIl5efnpwEDBhgdBQAAAACAVBMTE6Po6GijYyADodiJFPf7779rx44d6tGjh9FRbNrQoUO1YcMG7d692+goAAAAAIBUUqRIEbVv316LFy+Wl5eXsmXLpsqVK2vnzp1JPsesWbNUrlw5ZcmSRXny5FGnTp10/fp1y/7Zs2fLZDJpxYoVlm0xMTF644035OHhoTt37kiK69hkMpl08OBB+fj4yMnJSW5ubho2bJhiY2OfmsFsNmvixIny9PSUg4OD3NzcFBAQoNu3b1u1M5lMGjx4sMaMGaOiRYvKwcFBBw8elCRdvXpV3bp1k7u7uxwdHVWyZEnNnDkzwbU2b96sihUrKkuWLPLw8NDnn3+e5GeFjI8JipDigoOD1a9fP2XLls3oKDYtZ86cGjNmjAIDA7Vnzx7Z2fG3CwAAAACwRTt27NAff/yh0NBQZcmSRUOHDlXjxo11+vRpOTs7P/XYAQMGaMKECfroo4/0ySef6Pz58xoyZIgOHTqkXbt2KVOmTPL399eGDRvk7++vKlWqyN3dXaGhoYqIiNDOnTsTjNps1qyZOnbsqIEDB2r9+vUKDQ2VnZ2dgoODn5hj8ODBGj16tHr06KEmTZroyJEjGjp0qA4cOKBt27ZZ/U47b948vfbaaxo/fryyZcumAgUK6Pbt26pZs6YePHig4OBgFS1aVOvXr1e3bt306NEjy2v0IiMj1bBhQ1WuXFmLFy/Wo0ePFBwcrLt37ypTpkzP/0lAhkGxEynq119/1Z49e7Rw4UKjo7wU2rdvrxkzZmju3Lny9/c3Og4AAAAAIBXcvn1b+/fv1yuvvCJJyp8/v6pUqaK1a9eqbdu2Tzzu9OnT+uSTTzR8+HANGzbMsr1EiRKqVauWVq1apWbNmkmSZs6cqXLlyql9+/YKDg7WyJEjFRoaqmrVqiU4b+fOnS2vVXvrrbd0+/ZtTZgwQb169Uq0+Hr9+nWFh4erQ4cOlsl2fX19lTdvXn3wwQdavXq13n33XUt7s9msDRs2KGvWrJZtoaGh+uuvv3Tw4EEVL15ckvTmm2/q5s2bCgkJUbdu3WRvb6+RI0cqR44c2rBhg6UT1uuvvy4PDw8VKFAgaQ8cGRpdwZCihg8frgEDBlj9QELqMZlMmjp1qoYMGaIbN24YHQcAAAAAkApq1KhhKXRKUpkyZSRJZ86ckRRXHIyOjrYsMTExkqSNGzcqNjZW7dq1s9pfrVo15cyZU9u3b7ec09nZWYsWLdKOHTvk6+ur2rVrq3///onmadWqldV669atdffuXR06dCjR9rt379ajR4/Uvn37BMfZ29tr27ZtVtvffvvtBHWFdevWqVq1aipatKjVvfj6+uratWs6cuSIJCkiIkINGza0Gm366quvqmbNmolmg+2h2IkU8/PPP2v//v3q3Lmz0VFeKhUrVlSzZs00fPhwo6MAAAAAAJLA3t7eUpD8t/jt9vb/fzBu7ty5rdo4OjpKkh4+fChJmj9/vjJnzmxZPDw8JElXrlyRJBUrVsxqf+bMmXX79m1du3bN6rzVq1eXp6enHj16pJ49ez7xdWn58uVLdP38+fOJto9/P6ibm5vVdnt7e7m4uFi9PzSxdvH3sn379gT30bJlS0my3MvFixcT5EssM2wXw9iRYoYPH67BgwcrS5YsRkd56YSFhcnLy0v+/v4qW7as0XEAAAAAAE/h6uqqq1evKioqSg4ODlb7Lly4ICl5xbkmTZpo7969lvX4YqiLi4skacOGDVY9Q+PF748XEhKi48ePq2zZsurdu7d8fHyUK1euBMddvnxZr732mtW6JLm7uyeaL75Ye+nSJZUuXdqyPTo6WteuXUuQw2QyJZrV1dVVkydPTvQanp6ekuIKpfF5/p0ZLwd6diJF7Nq1S5GRkerYsaPRUV5KLi4uCg4OVmBgoMxms9FxAAAAAABP4ePjo+joaK1cuTLBvu+++05ubm6W4l1SuLi4qHLlypYlfph7gwYNZGdnpzNnzljtj1+KFi1qOceOHTs0atQohYWFadWqVbp586a6deuW6PW++eYbq/XFixcre/bs8vb2TrR99erV5ejoqMWLF1ttX7JkiaKjo1WnTp1n3uPbb7+to0ePqlChQoneS/wkSjVq1NDatWt17949y7Fnz57VTz/99MxrwDbQsxMpYtiwYRoyZEiCv0gh7XTt2lUzZ87UkiVL1Lp1a6PjAAAAAACe4M0331SDBg3k5+eno0ePqlq1arpz544WL16s77//Xl988cUTh5Anh4eHh/r376+AgAD98ccfqlOnjrJkyaKzZ89q48aN8vf3l4+Pj27cuKF27drJx8dHffr0kclk0syZM9WqVSv5+vqqQ4cOVuedNWuWYmNjVaVKFa1fv16zZ89WcHDwE2eGz507t4KCgjR69Ghly5ZNDRs2VGRkpIYMGaJatWqpUaNGz7yX3r17a8mSJapdu7Z69+4tT09P3bt3T0ePHtWOHTv0/fffS5KGDBmipUuX6q233lLfvn0VFRWl4cOHM4z9JUKxEy9s27ZtOnXqVIIffkhbmTJl0tSpU9W2bVs1btxY2bNnNzoSAAAAACARJpNJK1eu1MiRI/Xll18qNDRUDg4OKl++vFasWKGmTZum2LVGjRolLy8vTZ8+XdOnT5fJZNKrr76q+vXrW2Y179Klix48eKAvv/zSMoS8ZcuW6tSpkwICAlSzZk0VK1bMcs7vv/9egYGBCg0NVa5cuTRkyBANHTr0qTnCwsKUN29ezZgxQ59++qlcXFz04YcfavTo0Ukq7ObKlUu7du3SiBEjNHbsWJ0/f17Ozs7y9PTUe++9Z2nn5eWltWvXqm/fvnr//ffl7u6u/v37KyIiQlu3bn2OJ4iMxmRmzCtegNlsVt26ddWxY0eKnelEu3btVLhwYY0aNcroKAAAAADwwiIjI+Xl5WV0DEgKDg5WSEiIHj9+bDWBEjIeW/6+4p2deCFbtmzRxYsX1a5dO6Oj4P+MGzdOM2fO1IkTJ4yOAgAAAAAAkKYoduK5mc1mDR06VMOHD+cvOumIu7u7+vbtq169ehkdBQAAAAAAIE1R7MRz27Bhg27cuMFkOOlQr169dOzYMa1Zs8boKAAAAAAAGxEcHCyz2UyHJ6RrFDvxXMxms4YNG6bg4GBlypTJ6Dj4F0dHR02ePFm9evXSo0ePjI4DAAAAAACQJih24rmsXbtW9+/fV4sWLYyOgid455135OXlpfDwcKOjAAAAAAAApAmKnUi2+F6dISEhsrPjSyg9mzhxosaPH69z584ZHQUAAAAAACDVUalCsn3//fcym81q3ry50VHwDB4eHurWrZv69u1rdBQAAAAAAIBUR7ETyRIbG6vhw4crJCREJpPJ6DhIgoEDB+qnn37Stm3bjI4CAACAVBQbG2t0BAAADEexE8mybNkyOTg4qHHjxkZHQRJly5ZN48ePV2BgoKKjo42OAwAAgFTy8OFD9enTR5GRkUZHAdIvs1n6e5d0dJJ0MDTu49+74rYDsAkUO5FkMTExGj58uEaMGEGvzgymZcuWypMnj2bMmGF0FAAAAKQSR0dHubq6qk6dOvLz89OpU6eMjgSkH7GPpeMzpJUe0pa3pP39pYPD4z5ueStu+/EZce0AZGgUO5Fk33zzjXLlyqW3337b6ChIJpPJpClTpigkJER///230XEAAACQCjJlyqR+/frp+PHjKly4sCpXrqyAgABdvHjR6GiAsR7flTbXk379WLp3Soq+J8VGSTLHfYy+F7f914+lzfXj2qeyefPmyWQyJbps2rQp1a//T8uWLdOkSZMSbN+0aZNMJpN27tyZpnmAF0WxE0kSHR2t4OBgenVmYN7e3mrbtq0GDx5sdBQAAACkoly5cikkJERHjx6Vo6OjSpcurf79++vatWtGRwPSXuxjaes70rW9Usz9p7eNuS9d+1na2jDNenguXbpUERERVkvVqlXT5NrxnlTsrFq1qiIiIlSuXLk0zQO8KIqdSJKvv/5a+fLlU/369Y2OghcQEhKilStX6pdffjE6CgAAAFJZ3rx5NWHCBP3++++6efOmPD09FRoaqjt37hgdDUg7J+dI13+VYh8lrX3sI+n6Punk3NTN9X/Kly+v6tWrWy05c+ZMtO2jR0m8hxSSM2dOVa9eXTly5Hjhc5nNZkVFRaVAKuDZKHbimcxmsypXrqzPPvuMXp0ZnLOzs8LCwhQYGMhsnQAAAC+JggUL6vPPP9fu3bv1xx9/qFixYpo4caIePnxodDQgdZnN0pFxz+7R+W8x9+OOM3DSovgh5CtWrFDHjh2VJ08eubu7W/avXbtW1apVU9asWeXs7KzmzZvr+PHjVueoVauW6tatqw0bNqhChQpycnKSt7e3Vq5caWnTvn17LVy4UH/99ZdlGH2xYsWsMvx7GPu3336ratWqycnJSc7OzmrVqpXOnTtn1aZgwYLy8/PTrFmz5OnpKQcHB61fvz6lHxOQKIqdeCaTySQvLy+VLl3a6ChIAf/9738VExOjr776yugoAAAASEPFihXTggULtGnTJm3btk3FixfXrFmz9PgxE7LARl2NkB5deb5jH12OOz6VxcTEKDo62rLExMRY7e/Ro4fs7e21cOFCzZkzR5K0evVqNW7cWK+88oq++eYbTZ8+XQcOHFCtWrV06dIlq+OPHTumoKAg9enTR8uWLVO+fPn03nvvWSYwCwkJka+vr/Lnz28ZRv/tt98+Me8VFD6TAAAgAElEQVS0adPUqlUrlSlTRt99951mzJihAwcOqG7durp71/pdpxs3brTMHbFu3TpqCkgz9kYHAJC27OzsNHXqVDVv3lzNmjVTrly5jI4EAACANFSmTBmtWLFCe/bs0eDBgzV27FiNGDFCrVu3lp0d/WGQQezrJd3Y//Q2989J0cns1Rkv+r4U8aHkVPDJbV4pL1VK+K7L5ChZsqTVes2aNa16Ur7++uuaOXOmVZshQ4aoRIkSWrNmjTJlyiRJqlatmkqWLKnw8HCNGzfO0vbq1avauXOnXnvtNUlSuXLlVKBAAS1dulT9+vWTh4eH8uTJI0dHR1WvXv2pWW/fvq2BAwfK39/fKlOVKlVUsmRJzZs3TwEBAZbtt27d0m+//SZXV9dkPhXgxfAvGfASqlatmt5++22NGDHC6CgAAAAwSLVq1bRp0ybNnDlTU6ZMUfny5bVy5UqZDRy6C6Qoc4yk5/16Nv/f8alr+fLl2rt3r2WJ770Zr3nz5lbrt2/f1oEDB9S6dWtLoVOK67ldvXp1bdu2zap9yZIlLYVOSXJzc1OePHl05syZZGf96aefdPfuXbVr186qN2rhwoVVvHhxbd++3ar966+/TqEThqBnJ/CSGj16tLy9veXv7y8vLy+j4wAAAMAg9erVU0REhFavXq3Bgwdr1KhRGjVqlOrVq2d0NODJktKj8ugkaX9/KfY5Jsaxc5Q8e0kleyb/2GTw9va2vCMzMW5ublbr169fT3S7JOXPn18HDhyw2pY7d+4E7RwdHZ/rnb1XrsS9EqBu3bpJyppYRiAtUOwEXlL58uXT4MGD9dFHH2nDhg1MPgUAAPASM5lMatKkiRo1aqQlS5aoa9euKly4sMLCwlStWjWj4wHPx6WqZJf5OYud9pJLlZTPlEz//j0tvnj573dzxm9zcXFJtSzx5/7qq68SDL+XlGDWdn7HhFEYxg68xHr06KELFy5o+fLlRkcBAABAOmBnZ6c2bdroyJEjev/999WiRQs1bdpUBw8eNDoakHx5akiOzzmMOku+uOPTmZw5c6p8+fL65ptvFBsba9n+559/avfu3apTp06yz+no6KgHDx48s12tWrWULVs2nTx5UpUrV06weHp6JvvaQGqg2Am8xDJnzqypU6cqKChI9+8/54u7AQAAYHMyZ86szp076/jx4/Lx8VGDBg3Url07nThxwuhoQNKZTFKpflImp+Qdl8lJ8uoXd3w6FBoaqsjISDVp0kSrV6/WokWL9NZbb8nFxUW9e/dO9vlKlSqlK1euaObMmdq7d68OHTqUaDtnZ2eNHTtWI0eOVLdu3bRy5Upt3bpVCxculL+/v5YsWfKitwakCIqdwEuuXr16qlKlitWMfQAAAIAkZcmSRb169dLx48fl5eWl6tWrq2vXrjp37pzR0YCk8egk5a4Y9w7OpLBzlHJXkjw6pm6uF9C4cWOtWrVKV69eVYsWLdStWzeVKVNGO3fuVP78+ZN9vi5duqhVq1bq37+/qlatqmbNmj2xbY8ePbR8+XJFRkaqXbt2atiwoYKDg2U2m1WuXLkXuS0gxZjMTLUHvPTOnDmjChUqaN++fSpSpIjRcQAAAJBOXb9+XePGjdOsWbPUoUMHDRw4UHnz5jU6FmxcZGTki02q+viutLWhdH2fFPOUEW2ZnOIKnXXXSpmzP//1gAzghb+v0jF6dgJQoUKF1Lt3bwUFBRkdBQAAAOlY7ty5NWbMGB06dEhRUVEqWbKkhg0bplu3bhkdDXiyzNml+puliuFSttck+2z/19PTFPfRPpuU/bW4/fU3U+gEMjh6dgKQJD18+FClS5fWjBkz1KBBA6PjAAAAIAM4ffq0QkJCtGbNGvXp00cBAQFyckrm+xGBZ0jRHmhms3Q1Qrq2V4q+I9nniJu1PU/1dPuOTiA10LMTgM3LkiWLJk6cqI8++khRUVFGxwEAAEAGUKRIEX3xxRfatm2b9u7dq2LFimn69On8/yTSL5NJyvu6VLKn5D0k7mPeGhQ6ARtCsROARZMmTVSkSBFNnTrV6CgAAADIQLy8vLR06VKtWrVKq1evlqenp+bPn6+YmBijowEAXjIUOwFYmEwmTZ48WaNHj9bFixeNjgMAAIAMplKlSvrhhx80f/58zZ49W2XKlNF3330n3p4GAEgrFDsBWClRooQ6deqkAQMGGB0FAAAgw/Lz85PJZNLIkSOttm/dulUmk0lXr141KFmcefPmKXv21JuE5Y033tD27dsVHh6usLAwValSRevXr6foCQBIdRQ7IUmKiorS7du3FRsba3QUpANDhgzR5s2btWvXLqOjAAAAZFhZsmTRuHHj9PfffxsdxRAmk0lvv/22fvnlFw0YMEC9evVS3bp1tXPnTqOjAQBsGMVOSJIWLFig//73v7Kz40sCUo4cOTR27FgFBgbyniUAAIDn5OPjoyJFiig0NPSJbY4cOaJGjRopR44ccnV1VZs2bXTp0iXL/r179+qtt95Snjx5lDNnTtWqVUsRERFW5zCZTPrss8/UtGlTOTk5qUSJEtqyZYvOnTsnX19fZcuWTeXLl9evv/4qKa536X//+1/du3dPJpNJJpNJwcHBqfIMJMnOzk4tWrTQwYMH9d///lft27dXw4YNLXkAAEhJVLYgSZozZ446dOhgdAykI23btpWTk5PmzJljdBQAAIAMyc7OTmPGjNGMGTN08uTJBPsvXryoN954Q97e3vr555+1adMm3b17V++++65lxNWdO3f0wQcfaMeOHfr5559Vvnx5NWzYMMEw+JEjR6p169Y6cOCAKleurDZt2qhTp07q3r27fvvtNxUoUEB+fn6SpNdff12TJk2Sk5OTLl68qIsXL6pPnz6p/jzs7e3l5+enP/74Q40aNVLjxo3VqlUrHT16NNWvDViYzdKuXdKkSVJoaNzHXbvitgOwCSYzL0156UVGRqpevXo6c+aMMmfObHQcpCP79++Xr6+vIiMjlTt3bqPjAAAAZBh+fn66evWqVq9eLR8fH+XLl0+LFy/W1q1b5ePjo7///ltTpkzRTz/9pM2bN1uOu3HjhnLnzq09e/aoatWqCc5rNptVoEABffLJJ2rfvr2kuJ6dAwYM0OjRoyVJhw4dUpkyZTRhwgQFBQVJktV18+TJo3nz5ikgIEB3795Ng6eRuHv37mnatGkaP368mjRpouHDh6tw4cKG5UH6FRkZKS8vrxc7yePH0pw50rhx0pUrceuPH0uZM8ctrq5Sv35Sp05x64CNS5Hvq3SKnp3QF198oQ8//JBCJxIoX7683nvvPQ0bNszoKAAAABnWuHHjtHTpUv3yyy9W2/ft26ft27cre/bsluXVV1+VJEtP0CtXrqhr164qUaKEcuXKpRw5cujKlSs6c+aM1bnKli1r+e98+fJJksqUKZNg25UrV1L+Bp9TtmzZ1L9/fx0/flzu7u6qWLGiAgMDrYbxAyni7l2pXj3p44+lU6eke/ekqKi43pxRUXHrp07F7a9fP659GoiIiFCrVq1UoEABOTg4yMXFRQ0aNND8+fMz7OvEVqxYofDw8ATb4ydn27p1a4pcJ/4VHIktK1asSJFr/FtK30NqnRMUO196jx8/1pdffqmOHTsaHQXpVGhoqJYuXaoDBw4YHQUAACBDqlKlit577z3179/fantsbKwaNWqk/fv3Wy3Hjx9X48aNJUkdOnTQ3r17NXHiRO3atUv79+9XwYIFFRUVZXWuf3ZcMJlMT9yWHickdXZ2VmhoqCIjI5U5c2aVLl1aAwcO1PXr142OBlvw+LH0zjvS3r3S/ftPb3v/vvTzz1LDhnHHpaJJkyapZs2aun79usaOHatNmzZp7ty5KlGihLp166bVq1en6vVTy5OKnanBz89PERERCZY6deqkyfVTQsWKFRUREaGKFSsaHcWm2BsdAMZas2aNihcvLk9PT6OjIJ1ycXFRSEiIAgMDtW3bNsv/KAMAACDpRo0apVKlSmndunWWbRUrVtQ333yjwoULP3GU1c6dOzVlyhQ1atRIknT58mVdvHjxhfM4ODiku55jrq6uCg8PV+/evRUaGqoSJUqod+/e6tmzp7Jnz250PGRUc+ZIv/4qPXqUtPaPHkn79klz50pdu6ZKpO3btysoKEgBAQGaMmWK1b6mTZsqKChI9+7de+HrPH78WPb29on+Dvfo0SM5Ojq+8DWM5O7ururVqxsd47nExMTIbDYrZ86cGfYe0jN6dr7k5syZQ69OPFPnzp119+5dLV682OgoAAAAGVKxYsXUpUsXTZ482bKtR48eunXrlt5//33t2bNHf/75pzZt2qQuXbrozp07kqQSJUpowYIFOnLkiPbu3avWrVvLwcHhhfMUKVJEDx8+1MaNG3X16lXdf1aPtzT06quvaubMmYqIiNDhw4dVrFgxTZ48WQ8fPjQ6GjIasznuHZ3J/fq+fz/uuFSa4mTMmDHKnTu3xo0bl+h+Dw8Py6spgoODEy1W+vn5qUiRIpb106dPy2Qy6dNPP1W/fv1UoEABOTo66ubNm5o3b55MJpO2b9+uli1bytnZWdWqVbMcu23bNtWvX185cuRQtmzZ5Ovrq0OHDlldr27duqpVq5Y2bdqkihUrysnJSd7e3lZDxv38/DR//nydP3/eMqT8nxn/KSAgQPny5dPjf/WgvXv3rnLkyKGBAwc+9RkmxezZsxMMa4+JidEbb7whDw8Py8/Z+Gd88OBB+fj4yMnJSW5ubho2bNgze8ObzWZNnDhRnp6ecnBwkJubmwICAnT79m2rdiaTSYMHD9aYMWNUtGhROTg46ODBg4kOY0/Ks4739ddfq2TJksqSJYvKlCmjlStXqm7duqpbt+7zPzgbQLHzJXbhwgXt3LlTLVu2NDoK0rlMmTJp6tSp6tu3r6EvsQcAAMjIhg0bJnv7/z+4rkCBAvrpp59kZ2ent99+W6VLl1aPHj3k6Oho6XE1d+5c3b17V5UqVVLr1q3VsWPHJxYPkuP111/X//73P7Vp00Z58+Z9YtHFSMWLF9eiRYu0fv16bd68WSVKlNDs2bMVHR1tdDRkFBERcZMRPY/Ll+OOT2ExMTHaunWr3nrrLWXJkiXFzx8WFqZjx45p5syZWr58udU12rVrp6JFi+rbb7/VmDFjJMWN9qxfv76yZ8+uBQsWaNGiRbpz545q166ts2fPWp375MmT6tmzp4KCgrRs2TK5ubmpRYsWOnHihCRp6NChatiwofLmzWsZUr58+fJEc3bv3l1XrlxJsH/hwoW6d++eOnfu/Mx7NZvNio6OTrDE8/f3V8uWLeXv76/z589LintNW0REhBYtWqQcOXJYna9Zs2Z68803tWLFCrVt21ahoaEaMWLEUzMMHjxYQUFBatCggVatWqV+/fpp3rx5atSoUYJC6bx587RmzRqNHz9ea9asUYECBZ543mc9a0nauHGj2rVrp5IlS+q7775Tnz591KtXLx07duyZz87mmfHSGj16tNnf39/oGMhA2rdvbx4wYIDRMQAAAPASioiIMPv4+JiLFy9u/vrrr80xMTFGR0IaOXLkSMKNPXuazXXqPH3x8DCbTSazOa6PZvIWkynu+Kedv2fPZN/LpUuXzJKS/HvV8OHDzYmVbjp06GAuXLiwZf3UqVNmSeYKFSqYY2Njrdp+8cUXZknmXr16JTiPh4eHuV69elbbbt26ZXZxcTH3/Mf91alTx2xvb28+duyYZdvly5fNdnZ25rCwMKtc7u7uCa6zZcsWsyTzli1brM7572tXqFDB7Ovrm+D4f5P0xOXvv/+2tLtx44a5UKFC5rp165q3bt1qzpQpk3nUqFFW54p/xqNHj7ba7u/vb86ePbv5xo0bid7DtWvXzI6OjuYOHTpYHffVV1+ZJZm///57q7xubm7m+/fvJ+m5JOVZ16hRw1y6dGmrz/e+ffvMksx16tR55jNM9PvKRtCz8yU2YMAAzZo1y+gYyEDGjRunWbNm6fjx40ZHAQAAwEumevXq+vHHH/XZZ59p4sSJqlChglavXi1zKg01hg2IiXn+oehmc9zxGUyzZs2eOM9C8+bNrdaPHz+ukydPql27dlY9I52cnFSjRg1t377dqn3x4sVVvHhxy7qrq6tcXV115syZ58ravXt3bdmyxfL75d69e/Xbb7+paxLfldqxY0ft3bs3weLs7Gxp4+zsrEWLFmnHjh3y9fVV7dq1E0wWF69Vq1ZW661bt9bdu3cTDOmPt3v3bj169Ejt27dPcJy9vb22bdtmtf3tt99W1qxZk3Rvz3rWMTEx+uWXX/Tee+9Zfb4rVqyookWLJukatowJigAkmZubm/r3769evXppzZo1RscBAADAS6h+/fravXu3Vq5cqYEDByosLEyjRo2Sj49Pko6PjY2VnR39fjK8SZOS1qZ/fykqKvnnd3SUevWSevZM/rFP4eLioqxZs+qvv/5K0fPGc3NzS/K+K/83xL9Tp07q1KlTgvaFChWyWs+dO3eCNo6Ojs/9Pt3mzZsrf/78+vzzzzV+/HjNmDFDBQoUUJMmTZJ0vJubmypXrvzMdtWrV5enp6eOHDminj17PvH7P1++fImuxw+B/7fr169bcvyTvb29XFxcLPv/mTepnvWsr169qsePH8vV1TVBu3/fx8uIn/AAkqVnz546efKkVq9ebXQUAAAAvKRMJpOaNm2q/fv3KyAgQP7+/mrTps1Te3leunRJEydOlJ+fn4YNG5ZgYhTYoKpVpcyZn+9Ye3upSpWUzaO4QljdunW1ceNGPUrCDPHx79yM+lfB9tq1a4m2f1KvzsT2ubi4SJJGjx6daA/JVatWPTPfi8icObP8/f01b948XblyRYsXL1anTp2s3m2cEkJCQnT8+HGVLVtWvXv31q1btxJtd/ny5UTX3d3dE20fX5C8dOmS1fbo6Ghdu3bN8nzjPe1zk1x58uRR5syZLQXrf/r3fbyMKHYCSBYHBwdNnjxZvXr1YkZMAAAAGCpTpkxq166djh49qvDw8Ce2i42NVffu3TVp0iTlz59fP/74o9zd3bV06VJJYii8rapRQ0qk51uS5MsXd3wqGDBggK5du6a+ffsmuv/UqVP6/fffJUmFCxeWJKuh1Ddv3tSuXbteOIenp6eKFCmiw4cPq3LlygmW+Bnhk8PR0VEPHjxIcvuuXbvq1q1batmypR49epSkiYmSY8eOHRo1apTCwsK0atUq3bx5U926dUu07TfffGO1vnjxYmXPnl3e3t6Jtq9evbocHR21ePFiq+1LlixRdHS06tSpkzI3kYhMmTKpcuXK+u6776x+fu3bt0+nTp1KtetmFAxjB5Bsvr6+8vb2Vnh4uAYNGmR0HAAAALzkMmfO/NQhohcuXNCRI0c0ZMgQSzFl7NixmjZtmho1aiQnJ6e0ioq0ZDJJ/fpJH38s3b+f9OOcnOKOS8GeeP/0xhtvKDw8XEFBQYqMjJSfn58KFSqkGzduaPPmzZo9e7YWLVqksmXL6p133lGuXLnUuXNnhYSE6NGjRxo3bpyyZ8/+wjlMJpOmT5+upk2bKioqSq1atVKePHl0+fJl7dq1S4UKFVJQUFCyzlmqVCldv35dn332mSpXrqwsWbKoTJkyT2zv7u6uJk2aaPny5WrSpIleffXVJF/r/Pnz2r17d4LthQsXlpubm27cuKF27drJx8dHffr0kclk0syZM9WqVSv5+vqqQ4cOVsfNmjVLsbGxqlKlitavX6/Zs2crODjY6h2g/5Q7d24FBQVp9OjRypYtmxo2bKjIyEgNGTJEtWrVUqNGjZJ8L88jJCREb731lpo3b64uXbro6tWrCg4OVv78+V/6V3W83HePZ/Lz81Pjxo1f+Dze3t4KDg5+8UBIN8LDwxUeHq6zZ88aHQUAAAB4qvh3+/2zaFGoUCGdPHlSBw4ckBQ39HTOnDlGRURq6dRJqlgx7h2cSeHoKFWqJHXsmKqxevXqpZ07d8rZ2Vl9+vRRvXr15Ofnp8jISH3++eeW91Y6Oztr9erVsrOzU6tWrTRw4EAFBgYm+R21z9KwYUNt375d9+7dk7+/v3x9fdWvXz9dunRJNZ6jZ6u/v79at26tQYMGqWrVqkl6/2bLli0lKckTE8WbN2+eatSokWBZuHChJKlLly568OCBvvzyS8sQ8pYtW6pTp04KCAjQiRMnrM73/fffa+PGjXr33Xe1YMECDRkyREOHDn1qhrCwMIWHh+uHH35Q48aNNWbMGH344Ydas2ZNqhccGzRooIULFyoyMlLNmzfX2LFjNWHCBOXPn1+5cuVK1WundyYz/fUztK1btz71h1zdunW1ZcuW5z7/rVu3ZDabn/iXjKTy9vZWixYtKHjamGHDhunYsWMJuu0DAAAA6cWePXs0adIkHTt2TL/99psCAgLUqlUrDRgwQHZ2dpo1a5Y8PT3122+/qWrVqipQoIDCwsISzLAM40RGRsrLy+v5T3D3rtSwobRv39N7eDo5xRU6166VUqDnJJKmXbt2+umnn/Tnn38a0iMxODhYISEhevz4cYq/LzStnTt3TsWKFdPgwYOfWah94e+rdIyenRnc66+/rosXLyZYPv/8c5lMJnXv3v25zhsdHS2z2axcuXK9cKETtmvAgAGKiIjQ1q1bjY4CAAAAJPDgwQPVq1dPBQoU0KRJk7Ry5UqtX79effr00ZtvvqnRo0fL09NTklShQgU9fvxYffv2VVBQkDw8PLR27VqD7wApInt2afNmKTxceu01KVu2uB6cJlPcx2zZ4raHh8e1o9CZJnbv3q0ZM2ZoyZIlCgoKeumHXifXgwcP1K1bN3333Xfatm2bvvjiCzVo0EBOTk7y9/c3Op6h+ErK4BwcHJQ/f36r5caNG+rbt68GDRpk6Q5+/vx5tW7dWq+88opeeeUVNWrUSMePH7ecJzg4WN7e3po3b548PDzk6Oioe/fuJRjGXrduXXXv3l2DBg1Snjx55Orqqj59+ig2NtbS5sqVK2ratKmyZs2qwoULa+7cuWn3QJCmnJycNGHCBAUGBio6OtroOAAAAICVr7/+Wt7e3ho0aJBq166txo0ba/r06bpw4YK6du2qmjVrSoqboCh+CQgI0Llz59SkSRM1btxYvXv31v3kvO8R6VPmzFLXrtKJE9KGDdLYsdKIEXEfN26M29616/PP3o5kq1Gjhvr27asOHTo8d0etl1mmTJl06dIlBQQEqEGDBgoKClLx4sW1ffv2p77D+GVAsdPG3Lx5U82aNVOdOnUUGhoqSbp//758fHyUJUsWbdu2TREREXJzc9Obb75p9Y/2qVOntGjRIi1dulQHDhxQlixZEr3GwoULZW9vr127dmnatGmaNGmSlixZYtnv5+enEydOaNOmTVqxYoW+/PJLnT59OlXvG8Z577335Orqqk8//dToKAAAAICVx48f6+LFi7p9+7Zlm7u7u5ydnbVv3z7LNpPJJJPJZJnVePPmzTpx4oQ8PT3l4+PDBEa2xGSSXn9d6tlTGjIk7mONGqk2GRGezGw2686dO5ozZ46hw8eDg4NlNpsz3BB2BwcHLV++XBcvXlRUVJRu3LihlStXPnH2+JcJxU4bEhsbq7Zt2ypTpkxasGCB5QW8ixcvltls1hdffKGyZcuqZMmS+vzzz3X37l2tXr3acnxUVJS++uorVaxYUd7e3k/8Ri9VqpRGjBihEiVKqFWrVvLx8dHmzZslSceOHdMPP/ygmTNnqmbNmqpQoYLmz5+vBw8epP4DgCFMJpOmTJmi0NBQXblyxeg4AAAAgEWdOnWUP39+ffLJJzp//rwOHTqkr7/+WufOnVPx4sUlxRVc4keqxcTEaMeOHfrwww9169Ytfffdd3r33XeNvAUAQDJlrLI1nmrQoEGKiIjQzz//rJw5c1q279u3T6dOnVKOHDms2t+/f18nT560rBcsWFD58uV75nXKli1rtV6gQAFLkSsyMlJ2dnaqWrWqZX/hwoVVoECB57onZAylS5dW+/btNWjQIM2ePdvoOAAAAIAk6f+xd99hTV79G8DvEDa4cSAIDmQoKiooigscuDfD4kCsC4t7UgcOUFQc2OprFZwouK2iljrqQKxacaKivi4QcSugMkJ+f/iaX6mbAich9+e6crU8OXmeO7lk5JvvOcfa2hpr1qzB8OHDYW9vj3LlyuHt27eYOHEirKyskJubCw0NDUWjyOLFi7Fs2TK0aNECixcvhpmZGeRyueJ+IiJSfix2FhNRUVFYuHAhoqOjFZ9Qvpebmws7O7uP7phdtmxZxf8bGBh81bW0/rGGiUQiUXwS+n7aB6mfgIAAWFtb48yZM3BwcBAdh4iIiIgIwLsP5o8dO4bz58/j3r17aNiwISpUqADg3cas2traePbsGdasWYNZs2bB29sbCxYsgJ6eHgCw0ElEpGJY7CwGzp8/Dx8fH8ybNw+urq4f3N+gQQNs3rwZRkZGhb6zuo2NDXJzc3HmzBk0bdoUAHDv3j08ePCgUK9L4pUqVQpBQUH44YcfEBcXx530iIiIiEip2NnZwc7ODgAUzRra2toAgNGjRyM6OhpTp07FyJEjoaenp+j6JCIi1cKf3CruyZMn6N69O1q1aoW+ffvi4cOHH9y8vLxQsWJFdOvWDUePHsXt27dx7NgxjBs3Ls+O7AXBysoK7du3x9ChQxEXF4fz58/D29tb8akoFW8DBgyARCLBuXPnREchIiIiIvqk90XMu3fvokWLFti5cydmzZqFyZMnKzYj+mehk7PYiIhUAzs7VVx0dDTu3r2Lu3fvwtjY+KNj5HI5jh07hsmTJ8PNzQ0vX75E5cqV4ezsjDJlyhR4prVr12Lw4MFwcXGBkZERZsyYwY1r1ISGhgaOHz+ucrvYEREREZF6Mjc3x/Dhw2FmZgYnJycA+GxHp5+fH3744QdYWVkVZUwqQHK5HElJSUhOTkZmZiZ0dLnOcwsAACAASURBVHRgYmICU1NTLllAVExI5Px4ioiIiIiIiOizcnJysGDBAixatAhdu3bF9OnTYW5uLjqWWrh69SpsbGz+1TlkMhni4+MRGxuLjIwM5ObmQiaTQSqVQkNDAwYGBnByckL9+vUhlUoLKDmR8iqI7ytlxWnsRCRMZmam6AhERERERF9FU1MTU6ZMwY0bN2BsbIwGDRpg1KhRSE1NFR2NviArKwvr169HTEwMXrx4gezsbMhkMgDviqDZ2dl48eIFYmJisH79emRlZRV6prVr10IikXz0Vlh7bXh7e6Nq1aqFcu78kkgkCAgIEB2DihkWO4moyOXm5uLQoUMIDQ3Fw4cPRcchIiIiIvpqpUuXxpw5c5CQkACJRIJatWrhxx9/xPPnz0VHo4+QyWSIiIhAcnIysrOzPzs2OzsbycnJiIiIUBRDC9vWrVsRFxeX53bw4MEiuTZRccViJxEVOQ0NDbx+/Rp//PEHRo8eLToOEREREdE3q1ixIpYsWYL4+HikpqbC0tISc+fORUZGhuho9Dfx8fFISUn56uKlTCZDSkoK4uPjCznZO3Z2dnB0dMxzs7e3L5Jr/xucpUfKjMVOIipS76eEdOnSBb169cKWLVvw+++/C05FRERERJQ/ZmZmWL16NU6cOIELFy7AwsICoaGhLAYpAblcjtjY2C92dP5TdnY2YmNjIXKLk9zcXLRq1QpVq1bFy5cvFccvXboEPT09TJgwQXGsatWq6Nu3L1atWgULCwvo6uqiQYMGOHLkyBevk5KSgv79+8PIyAg6OjqoW7cuNm7cmGfM+yn3x44dg5ubG0qXLo3GjRsr7j969Chat26NEiVKwMDAAK6urrh8+XKec8hkMkydOhXGxsbQ19dHq1atcOXKlfy+PESfxWInERWJnJwcAIC2tjZycnIwbtw4jB07Fk5OTt/8xwcRERERkbKxsrJCZGQk9u/fj99//x2WlpYIDw9X/B1MRS8pKSnfnbYZGRlISkoq4EQfkslkyMnJyXPLzc2FhoYGNm7ciLS0NAwdOhQA8ObNG3h6eqJ27doIDAzMc56jR49i0aJFCAwMRGRkJHR0dNChQwdcv379k9fOyMhAy5YtsX//fgQFBWHXrl2oU6cO+vXrh19++eWD8V5eXqhWrRq2bduGefPmAQCio6PRunVrGBoaYuPGjdi0aRPS0tLQvHlz3L9/X/HYgIAABAUFwcvLC7t27UK7du3QtWvXgngJiT6gKToAFY6oqCisWrWKa32QULdu3UJubi5q1qwJTc13P27WrVsHf39/6OrqYtq0aejatStq1KghOCkRERERUcGws7PDnj17cPLkSfj7+yM4OBizZ89G7969oaHBfqOCcuDAgS+u///q1at8N1ZkZ2dj586dKFmy5CfHVKpUCe3bt8/X+d+ztrb+4FinTp2wd+9emJqaYvXq1ejZsydcXV0RFxeHu3fv4ty5c9DW1s7zmNTUVMTGxsLMzAwA0Lp1a5ibm2POnDnYsGHDR6+9Zs0a3LhxA0eOHEGrVq0AAB06dEBqaiqmTp2KQYMG5dmZvnfv3pg/f36ec4waNQotW7bE7t27FcecnZ1RvXp1hISEYMmSJXj+/DkWL16MIUOGYOHChQCAdu3aQSqVYvLkyd/+ohF9AYudxVRYWBgGDRokOgapuYiICGzevBlXr15FfHw8/Pz8cPnyZXz33XcYMGAA6tWrB11dXdExiYiIiIgKXNOmTXHkyBEcPHgQ/v7+CAoKQmBgIDp27AiJRCI6nlrIzc0V+vivsXPnTpiamuY59vfd2Hv06IGhQ4di+PDhyMzMRHh4OCwtLT84j6Ojo6LQCQAlSpRAp06dEBcX98lrHzt2DCYmJopC53t9+/bFwIEDkZCQgDp16uTJ8nc3btzArVu34O/vn6eDWV9fH02aNMGxY8cAvJt6n5GRAXd39zyP9/T0ZLGTCgWLncXQ69evkZWVhe7du4uOQmpuypQpCAkJQcOGDXHjxg00bdoU69evR7NmzVC2bNk8Y1+8eIELFy6gZcuWgtISERERERUsiUSCtm3bok2bNti1axcmTZqEoKAgBAUF8e/ef+lrOipPnTqFgwcP5mtndalUqtgwqDDZ2trCwsLis2MGDBiAlStXokKFCvjuu+8+OqZixYofPZacnPzJ8z579gzGxsYfHK9UqZLi/r/759hHjx4BAAYNGvTRZqv3xdeUlJSPZvxYZqKCwB76YkhPTw9HjhyBnp6e6Cik5rS0tLB8+XLEx8dj0qRJWLlyJbp27fpBofPAgQMYM2YMevbsiUOHDglKS0RERERUOCQSCXr06IELFy5g+PDhGDhwIFxdXXH27FnR0Yo1ExOTfC8doKGhARMTkwJO9O1ev34NHx8f2Nra4uXLl5/shExNTf3osc89h7Jly350KYD3x8qVK5fn+D87kt/fP3fuXJw5c+aD2549ewD8f5H0nxk/lpmoILDYWQxJJBJOiyCl4eXlhVq1aiExMRHm5uYAoNjV8OHDh5g1axZ+/PFHPH36FLa2tujfv7/IuEREREREhUYqlaJv3764du0aevTogW7duqFXr15ISEgQHa1YMjU1hYGBQb4ea2ho+MH0chFGjRqF5ORk7N69G/Pnz8fSpUtx4MCBD8adOnUqz4ZAaWlpiI6ORpMmTT557pYtWyIpKQmxsbF5jm/atAkVKlSAjY3NZ7NZWVmhatWquHLlCuzt7T+41a1bFwBQt25dGBgYYMuWLXkeHxkZ+cXnT5QfnMZORIUuPDwcQ4cORXJyMkxMTBTF+NzcXMhkMiQmJmLt2rWoU6cOrKysEBAQgICAALGhiYiIiIgKiba2NoYNG4YBAwbg559/hrOzM1xdXREQEIDq1auLjldsSCQSODk5ISYm5ps2KtLS0kLTpk2LpIno/PnzePLkyQfH7e3tsXv3bqxevRobNmxA9erVMXLkSMTExMDb2xsXL15EhQoVFOMrVqyIdu3aISAgADo6OggODkZGRgamTZv2yWt7e3tj6dKl6NmzJwIDA2FqaoqIiAj8/vvvWLlyZZ7NiT5GIpHg559/Rrdu3ZCVlQV3d3cYGRkhNTUVJ0+ehJmZGcaOHYvSpUtjzJgxCAwMRIkSJdCuXTucOXMGYWFh+X/hiD6DnZ1EVOgaNWqEbdu2oWTJkopFqgGgcuXK+OGHH+Dg4ICoqCgAwMKFCxEYGIjnz5+LiktEREREVCT09PQwfvx43LhxAzVq1ICDgwN8fX3x4MED0dGKjfr168PY2PiLhbv3pFIpjI2NUb9+/UJO9o6bmxuaNGnywS0lJQWDBw+Gl5cX+vbtqxi/Zs0aSCQSeHt7K2bMAe+6NMeNGwd/f394eHjg7du32L9//0c3M3rPwMAAR48eRbt27TB58mR069YNFy5cwIYNGzBkyJCvyt+xY0ccO3YMGRkZ+P777+Hq6oqJEyfi4cOHebpKAwIC4O/vjw0bNqBr166IiYlRTHMnKmgS+d+/O4iIColcLsf3338PmUyG1atXQyqVKj4pjYyMREhICPbt24fy5ctj7Nix6NixI9q0aSM4NRERERFR0Xny5AmCg4MRHh6OQYMGYdKkSR+sm6iOrl69+sUp1Z+TlZWFiIgIpKSkfLbDU0tLC8bGxvDy8oK2tna+r1fUqlatimbNmmHjxo2io5AK+bffV8qMnZ0qSi6Xg3VqUiUSiQT29vY4ffo0cnJyIJFIFLsiPnr0CHK5HIaGhgCAkJAQFjqJiIiISO0YGRlhwYIFuHjxItLS0mBlZYWZM2fi1atXoqOpNG1tbfTv3x/t2rVD6dKloaWlpej0lEql0NLSQpkyZdCuXTv0799fpQqdRPQhdnYWE3K5HBKJRPFfImVlYWGBfv36wc/PD2XLlkVycjK6dOmCsmXL4sCBA9DU5FLCREREREQAcOvWLQQEBCAmJgYTJ06Er68v9PT0RMcqcgXZgSaXy5GUlITk5GRkZWVBW1sbJiYmMDU1Vdn30uzspPwozp2dLHaqoLlz5+LFixcIDg4WHYXom8XGxmL48OEwMDBAlSpVcOrUKZiYmGDt2rWwsrJSjJPJZDh58iQqVqz42XVmiIiIiIiKu8uXL2P69Ok4ffo0pk2bBh8fH2hpaYmOVWSKc1GGSJTi/H3Faewq6KeffoKFhYXi6+joaKxYsQKLFy/GkSNHkJOTIzAd0ec5OTlh9erVaNKkCR4/fgwfHx8sWrQIlpaWeZZmuH37NiIiIjB58mRkZWUJTExEREREJJatrS127NiBnTt3Yvv27bCxscHGjRsVy0IREdH/Y2eniomLi0Pr1q3x7NkzaGpqYvz48Vi/fj309PRgZGQETU1NzJgxA127dhUdleir5ObmQkPj45+7/PHHHxg7dizs7e3xyy+/FHEyIiIiIiLldOTIEfz444949eoV5syZg27duqnsFOyvUZw70IhEKc7fV+zsVDELFiyAp6cndHV1sWXLFhw5cgQ///wzkpOTERERgZo1a8LLywsPHz4UHZXos3JzcwFAUej85+cuMpkMDx8+xO3bt7Fnzx4uyk5ERERE9D/Ozs6IjY1FcHAwAgIC4OjoiIMHD3ITWyIisNipck6ePIkLFy7g119/xbJly9C/f3/06dMHwLupDfPmzUO1atVw7tw5wUmJPu99kTM1NRUA8nwS/ddff6FLly7w8vKCh4cHzp49i5IlSwrJSURERESkjCQSCTp16oRz585h7Nix8PX1RevWrREXFyc6GhGRUCx2qpD09HSMHTsWVlZWmDhxIm7evAk7OzvF/TKZDJUqVYKGhgbX7SSVcOfOHfj6+uLGjRsAgOTkZIwbNw5OTk54+fIlTpw4gf/85z8wMTERnJSIiIiISDlpaGjAw8MDCQkJimaBrl274uLFi6KjEREJwTU7VUhCQgJq1aqF5ORknD59Gnfu3EHbtm1ha2urGHPs2DF07NgR6enpApMSfb1GjRrByMgIvXv3RkBAALKzszFnzhwMGjRIdDQiIiIiIpXz9u1b/PLLLwgKCoKzszNmzpwJS0tL0bH+lYJcW1AulyMuKQ6nk08jLTMNJXRKoJFJIzQxbVKs1z0l+qfivGYni50q4v79+3BwcMCyZcvg5uYGAMjOzgYAaGlpAQDOnz+PgIAAlC5dGmvXrhUVleib3Lp1S7ET+9ixYzF16lSULl1adCwiIiIiIpWWnp6O0NBQLF68GN27d8f06dNRpUoV0bHypSCKMtmybITFh2F+7Hw8yniE7NxsZMuyoSXVgpaGFioYVMBEp4kYVH8QtKRaBZScSHkV52Inp7GriAULFuDRo0fw9vbG7NmzkZaWBi0trTy7WF+7dg0SiQRTpkwRmJTo29SoUQNTpkyBmZkZgoKCWOgkIiIiIioAhoaG8Pf3R2JiIsqXLw87OzuMGTMGjx49Eh2tyKVnpcNlvQvGxYzD7Re3kZGdgSxZFuSQI0uWhYzsDNx+cRvjYsah9frWSM8q3JmSa9euhUQi+ejt4MGDAICDBw9CIpHgxIkThZajb9++sLCw+OK4hw8fws/PD5aWltDT04ORkREaNmyIUaNGKZqwvtbNmzchkUiwcePGb857+PBhBAQEFOg5qXhisVNFrFmzBocOHUJAQABWrVqF9evXAwCkUqlijKenJ7Zv3w4rKytRMYnyZc6cOUhKSlL8uyYiIiIiooJRpkwZBAUF4cqVK5DJZLCxscG0adPw4sUL0dGKRLYsGx0iOuBM8hm8zn792bGvs1/jdPJpdIzoiGzZtxXx8mPr1q2Ii4vLc2vUqBGAd8t9xcXFoV69eoWe43NevHiBRo0aYf/+/Rg7diz27duHlStXokOHDvj111+RmZlZZFkOHz6MmTNnfnC8SpUqiIuLQ/v27YssCyk3TdEB6Mt27NgBAwMDODs7o169ekhNTcXIkSNx8eJFzJ49GxUqVEBOTg4kEkme4ieRKvnjjz+QmZkJuVzOtXKIiIiIiApYpUqVEBoainHjxmHWrFmwtLTE2LFj4efnBwMDA9HxCk1YfBjOpZxDpuzrinKZskz8lfIXwuPDMdR+aKFms7Oz+2RnZcmSJeHo6Fio1/8aW7Zswf3793H58mXUrl1bcbxXr16YPXu2Urx309HRUYrXipQHOztVwKJFi+Dt7Q0AKFu2LBYuXIjly5fjt99+w4IFCwAAmpqaLHSSSmvWrBlat26tFL8siYiIiIiKK3Nzc4SFheHYsWOIj49HzZo18dNPPxVph15RkcvlmB87/4sdnf/0Ovs15sfOh8gtTj42jb1Zs2Zo1aoVYmJiUL9+fejr68PW1ha//vprnscmJiaib9++qFq1KvT09FCjRg2MGDEiX928z549A/CuWP5P/3zvlpWVBX9/f5ibm0NbWxtVq1bF9OnTvzjVvVmzZmjTps0Hx01NTfH9998DAKZOnYrAwEDFdSUSCTQ13/XvfWoa+7p161C3bl3o6OigfPnyGDBgAFJTUz+4hre3NyIiImBtbQ0DAwM4ODjg5MmTn81Myo3FTiX36tUrxMXFYciQIQAAmUwGABg0aBAmTpyIn3/+GV26dMGdO3cEpiQiIiIiIiJVYm1tjaioKERHR2P//v2wsrLC2rVrkZOTIzpagYlLisOjjPytUZqakYq4pLgCTpSXTCZDTk6O4vb+/f7nJCYmYuzYsRg/fjx27NiBihUrolevXrh9+7ZiTHJyMszNzbF06VL89ttv+PHHH/Hbb7+hc+fO35zx/bR6d3d3xMTEICMj45Nj+/btiwULFmDgwIHYu3cv+vfvj6CgIAwaNOibr/tPw4YNUzSBvZ/yHxsb+8nxy5cvh7e3N+rUqYNdu3YhMDAQ0dHRaNWqFV6/zlv8PnLkCEJDQxEYGIjIyEhkZWWhc+fOePXq1b/OTWJwGruSK1myJB4/foyyZcsC+P81OjU1NeHr64vy5ctj4sSJGDlyJCIjI6Gvry8yLlGBef8pKjs9iYiIiIgKT/369REdHY3Y2Fj4+/sjODgYs2bNQq9evfJsiKtsRh8YjfMPz392TNKrpG/u6nzvdfZr9N/ZH6YlTT85xq6SHZa0X5Kv8wPvCs5/5+Tk9MUNiZ48eYITJ06gevXqAIB69eqhcuXK2Lp1KyZOnAgAcHZ2hrOzs+IxTZs2RfXq1eHs7IxLly6hTp06X53RxcUF06dPR1BQEA4fPgypVIr69eujS5cuGD16NEqWLAkAuHDhArZu3YrZs2dj6tSpAIB27dpBQ0MDM2fOxOTJk1GrVq2vvu4/mZqawsTEBAC+OGU9JycHM2bMQOvWrREREaE4bmlpCWdnZ6xduxa+vr6K4+np6YiJiUGpUqUAAOXLl0eTJk1w4MABuLu75zsziaO8P7lI4X2h82Pc3NywaNEiPHnyhIVOKlZyc3Ph4OCAw4cPi45CRERERFTsOTk54Y8//sDSpUsRHBwMe3t77N+/X+hU7n9LliuDHPnLL4ccstwvd1r+Gzt37sSZM2cUt7CwsC8+xtraWlHoBABjY2MYGRnh3r17imOZmZmYM2cOrK2toaenBy0tLUXx8/r169+cc+bMmbh79y5WrVqFvn374vHjx5gxYwZsbW3x+PFjAMDRo0cBvOvu/Lv3X7+/vygkJCTgyZMnH2Rp1aoVTExMPsji5OSkKHQCUBSD//6akmphZ2cx0KNHD7Rq1Up0DKICJZVK4e/vj5EjRyI+Ph5aWlqiIxERERERFWsSiQTt2rVD27ZtsXPnTowbNw5BQUEICgpC8+bNRcfL42s6KpecWoJJBychS5b1zefXkepgtONojHIclZ94X8XW1vaTGxR9yseaoXR0dPD27VvF1xMnTsSKFSsQEBAAR0dHlChRAnfv3oWbm1uecd+icuXK+P777xVraC5duhSjR49GSEgI5s2bp1jb09jYOM/j3q/1+f7+ovCpLO/z/DPLP19THR0dAMj3a0XisbOzmChTpozoCEQFrkePHjA2Nsby5ctFRyEiIiIiUhsSiQQ9e/bEpUuXMHjwYPTv3x/jx4//qjUllUkjk0bQ0shf04SmhiYcTBwKOFHRiIyMhI+PD/z9/eHi4gIHB4c8nYsFYdSoUShZsiQSEhIA/H/B8OHDh3nGvf+6XLlynzyXrq4usrLyFqTlcjmeP3+er2yfyvL+2OeyUPHAYqeKUeUpBETfSiKRIDQ0FHPmzMGjR/lbWJyIiIiIiPJHKpWif//+uH79OkaMGKFy6+k3MW2CCgYV8vXYioYV0cS0SQEnKhpv3rz5YGbcmjVr8nWulJSUjxa5k5KSkJaWpuiebNmyJYB3hda/e79mZosWLT55DXNzc1y/fj3P5lhHjhz5YCOh9x2Xb968+WzmWrVqwcjI6IMsR48eRXJysiIrFV+cxq5Cbty4gd27d2PcuHEq90uGKL9sbGzQv39/TJky5avWsCEiIiIiooKlra2NatWqiY7xzSQSCSY6TcS4mHHftFGRvpY+JjadqLLvu11dXREeHo5atWqhRo0a2Lp1K06fPp2vc61btw4rVqyAj48PGjVqBD09PSQmJmLhwoXQ1dVVbPRTr149uLm5Ydq0acjKyoKjoyNiY2MRGBiIfv36fXZzIk9PT4SHh8PHxwf9+/fHrVu3sGTJEpQoUSLPuPfnWLhwIdq1awdNTU00bNjwg/Npampi5syZGDFiBAYMGIA+ffogKSkJ/v7+sLa2xoABA/L1WpDqYGenCgkPD0dKSorK/sAlyq8ZM2Zg//79+f4FTURERERE6mlQ/UFoYNwAOlKdrxqvI9VBQ+OG8KnvU8jJCs/y5cvRqVMnTJkyBR4eHnj79m2eXcm/RZcuXdCjRw/s3LkTXl5eaNu2LQICAmBnZ4eTJ0+iXr16irEbN27E+PHjsXr1anTs2BFr1679qqaVtm3b4ueff8bJkyfRpUsXbNiwAZs2bVLs9P5et27dMHToUISGhqJJkyZo3LjxJ8/p6+uLtWvXIj4+Ht26dcPkyZPRoUMH/PHHH9zcWQ1I5JwXrRJycnJgZmaGgwcPfvYTEaLiat26dfj5559x6tQpaGjwcxoiIiIiInVx9epV2NjY5Pvx6Vnp6BjREX+l/PXZDk99LX00NG6IfV77YKhtmO/rEamCf/t9pcxYMVARBw4cgLm5OQudpLb69esHqVSKtWvXio5CREREREQqxFDbEIf6H8KidotQvXR1GGgZQEeqAwkk0JHqwEDLANXLVMeidotwqP8hFjqJVBw7O1VEjx490KlTJ3z//feioxAJ89dff6Fz5864evUqSpcuLToOEREREREVgYLsQJPL5YhLisOZ5DNIy0pDCe0SaGTSCI6mjlwyjtRKce7sZLFTBaSmpsLKygr37t37YM0KInUzZMgQ6OvrY8mSJaKjEBERERFRESjORRkiUYrz9xWnsauADRs2oEePHix0EgEIDAzEpk2bcPnyZdFRiIiIiIiIiEjJsNip5ORyOcLCwjBo0CDRUYiUQvny5TF9+nSMHDkSbEwnIiIiIiIior9jsVPJxcXFITc3F05OTqKjECmNYcOG4cmTJ9i2bZvoKEREREREVATY6EBUcIr79xOLnUouLCwMPj4+XCiZ6G80NTWxbNkyjBs3DhkZGaLjEBERERFRIdLS0sKbN29ExyAqNt68eQMtLS3RMQoNNyhSYunp6ahSpQquXr2KSpUqiY5DpHT69OmDGjVqYM6cOaKjEBERERHR36SlpcHAwAAaGv++x+rVq1dITU2FiYkJ9PT02AxElE9yuRxv3rxBcnIyKlasWGz3hmGxU4mFh4dj9+7d2L17t+goREopKSkJ9erVw+nTp1GjRg3RcYiIiIiI6H+WL1+OBw8eFFhjwqtXr/Do0SNkZ2cXyPmI1JWWlhYqVKhQbAudAIudSs3JyQmTJk1C165dRUchUlpz585FXFwcfv31V9FRiIiIiIjof+7du4f69evj6tWrqFChgug4RKRGWOxUUlevXoWLiwvu3btXrNdRIPq3MjMzYWtri9DQUHTo0EF0HCIiIiIi+h8/Pz9oa2sjJCREdBQiUiMsdiqpiRMnQiKRIDg4WHQUIqUXHR2NMWPG4NKlS9DR0REdh4iIiIiIAKSkpKB27dq4fPkyKleuLDoOEakJFjuVUHZ2NqpUqYKjR4/CyspKdBwildC5c2c0b94ckyZNEh2FiIiIiIj+Z/z48Xj79i1++ukn0VGISE2w2KmEdu3ahZCQEBw/flx0FCKVcfPmTTg6OuLChQswMTERHYeIiIiIiAA8fvwY1tbWOHfuHMzNzUXHISI1oCE6AH0oLCwMPj4+omMQqRQLCwsMGTIEEydOFB2FiIiIiIj+p3z58hg2bFiB7cpORPQl7OxUMg8ePEDt2rVx//59GBoaio5DpFLS09NhY2ODTZs2oXnz5qLjEBERERERgGfPnsHS0hKnTp2ChYWF6DhEVMyxs1PJrF+/Hr1792ahkygfDA0NsWDBAvj5+UEmk4mOQ0REREREAMqWLYuRI0di1qxZoqMQkRpgZ6cSkcvlsLKywvr16+Ho6Cg6DpFKksvlcHZ2hru7O3x9fUXHISIiIiIiIqIixM5OJXL8+HFoamqicePGoqMQqSyJRILQ0FAEBATgyZMnouMQERERERERURFisVOJhIeHY9CgQZBIJKKjEKm0unXrwsPDA1OnThUdhYiIiIiIiIiKEKexK4lXr17BzMwMiYmJqFChgug4RCrv+fPnsLGxwb59+9CgQQPRcYiIiIiIiIioCLCzU0lERkaidevWLHQSFZAyZcpg9uzZ8PPzAz/TISIiIiIiIlIPLHYqifDwcPj4+IiOQVSs+Pj4IDMzExs3bhQdhYiIiIhI7QUEBMDW1lZ0DCIq5jiNXQlcuXIF7dq1w927d6GpqSk6DlGxcurUKfTq1QtXr15FyZIlRcchIiIiIlIp3t7eePLkCfbu3fuvz5WeQSqekgAAIABJREFUno7MzEyUK1euAJIREX0cOzuVQFhYGLy9vVnoJCoEjo6OaNu2LWbPni06ChERERGRWjM0NGShk4gKHYudgmVlZWHjxo0YOHCg6ChExda8efOwZs0aXLt2TXQUIiIiIiKVdebMGbRr1w5GRkYoWbIkmjVrhri4uDxjVq5cCUtLS+jq6qJ8+fJwdXVFTk4OAE5jJ6KiwWKnYHv27EGtWrVgYWEhOgpRsVWpUiX4+/tj1KhR3KyIiIiIiCif0tLS0K9fPxw/fhynT5+GnZ0dOnbsiCdPngAAzp49ixEjRmDGjBm4fv06Dh48iPbt2wtOTUTqhsVOwcLCwjBo0CDRMYiKPT8/P9y/fx+7d+8WHYWIiIiISCW5uLigX79+sLGxgbW1NZYtWwZdXV0cOHAAAHDv3j0YGBiga9euMDc3R7169TBmzBgu2UZERYrFToGSkpIUm6cQUeHS0tJCaGgoxo4dizdv3oiOQ0RERESkch49eoShQ4fC0tISpUqVQokSJfDo0SPcu3cPANC2bVuYm5ujWrVq8PLywrp165CWliY4NRGpGxY7BVq7di3c3d2hr68vOgqRWmjTpg0aNGiABQsWiI5CRERERKRyBgwYgDNnzmDx4sU4efIkzp8/D1NTU2RlZQEASpQogXPnzmHLli0wMzPD3LlzYW1tjQcPHghOTkTqhMVOQXJzc7FmzRpOYScqYiEhIQgNDcXdu3dFRyEiIiIiUiknTpyAn58fOnXqhNq1a6NEiRJISUnJM0ZTUxMuLi6YO3cuLl68iIyMDOzdu/erzp+bm1sYsYlIzbDYKYhcLsfWrVthb28vOgqRWjE3N8fIkSMxbtw40VGIiIiIiFSKpaUlNm7ciISEBJw5cwaenp7Q1tZW3L93714sXboU8fHxuHv3LjZt2oS0tDTY2Nh81fm3bt1aWNGJSI2w2CmIVCpFgwYNIJFIREchUjsTJkzAuXPncOjQIdFRiIiIiIhURnh4ONLT09GwYUN4enrCx8cHVatWVdxfunRp7Nq1C23atIG1tTUWLlyI1atXo3nz5l91/hkzZiAnJ6eQ0hORupDI5XK56BBEREVt165d8Pf3x4ULF6ClpSU6DhERERGR2mvRogW+//579O/fX3QUIlJhLHYSkVqSy+Vo37492rdvjzFjxoiOQ0RERESk9o4dOwZvb29cv36dDQlElG8sdhKR2rp27RqaN2+Oy5cvo2LFiqLjEBERERGpvbZt28LNzQ1DhgwRHYWIVBSLnUSk1iZMmIAnT55gzZo1oqMQEREREam9U6dOwd3dHYmJidDV1RUdh4hUEIudRKTWXr16BRsbG2zfvh2Ojo6i4xARERERqb3OnTvD1dUVfn5+oqMQkQpisZOI1N6GDRsQGhqKP//8ExoaGqLjEBERERGptXPnzqFz5864efMm9PX1RcchIhXDd/VEpPb69u0LbW1thIeHi45CRERERKT2GjRogCZNmmD58uWioxCRCmJnJxER3n163LFjR1y9ehVlypQRHYeIiIiISK1dvnwZrVu3xs2bN1GiRAnRcYhIhbCzU8mw9kwkRoMGDdC9e3fMmDFDdBQiIiIiIrVna2uL1q1bIzQ0VHQUIlIx7OxUMhcuXEBwcDA8PT3h6uoKHR0d0ZGI1MbTp09hY2ODQ4cOoU6dOqLjEBERERGptcTERDg5OeHGjRsoXbq06DhEpCLY2alkqlatiubNmyMkJATGxsbw9vbGgQMHkJ2dLToaUbFXrlw5BAQEwM/Pj13WRERERESCWVpaonPnzli0aJHoKESkQtjZqcSSk5Oxbds2REZG4ubNm+jRowc8PDzQqlUrSKVS0fGIiiWZTIaGDRtiypQp8PDwEB2HiIiIiEit3b59G/b29rh+/TqMjIxExyEiFcBip4q4e/cutmzZgqioKCQlJaF3797w8PCAk5MTNDTYoEtUkI4fPw4vLy9cvXoVBgYGouMQEREREam14cOHo2TJkggODhYdhYhUAIudKujmzZuIiopCVFQUnj17Bnd3d3h4eKBRo0aQSCSi4xEVC15eXqhatSoCAwNFRyEiIiIiUmtJSUmoV68erly5gkqVKomOQ0RKjsVOFZeQkKAofGZmZsLDwwMeHh6ws7Nj4ZPoX0hOTka9evVw6tQpWFhYiI5DRERERKTWRo8eDQBYsmSJ4CREpOxY7CxCOTk5SElJQZUqVQr83HK5HBcvXkRkZCSioqKgqakJT09PeHh4oHbt2gV+PSJ1EBwcjBMnTmDPnj2ioxARERERqbWHDx+idu3auHDhAkxNTUXHISIlxmJnEXr58iXMzMzw8uXLQr2OXC7H2bNnERkZiS1btqBUqVKKjk9LS8tCvTZRcZKZmYk6depgyZIl6Nixo+g4RERERERqbdKkSXj16hVWrFghOgoRKTEWO4tQZmYmSpYsiczMzCK7Zm5uLuLi4hAVFYWtW7fC2NhYUfisWrVqkeUgUlX79+/HyJEjcfnyZejo6IiOQ0RERESktp48eQIrKyucPXsW1apVEx2HiJQUi51FSC6XQyqVIjs7G1KptMivL5PJcOzYMURFRWH79u2oUaMGPDw84ObmxmkARJ/RtWtXNG3aFJMnTxYdhYiIiIhIrU2fPh1JSUkIDw8XHYWIlBSLnUVMT08PT58+hb6+vtAc2dnZOHz4MKKiorBr1y7Y2trCw8MDvXv3RsWKFYVmI1I2t27dQuPGjXHhwgWYmJiIjkNEREREpLZevHiBmjVrIjY2lsu0EdFHsdhZxMqWLYubN2+ibNmyoqMoZGZmIiYmBlFRUdi7dy/s7e3h4eGBnj17oly5cqLjESmFqVOn4r///S82bdokOgoRERERkVoLDAxEQkICIiIiREchIiXEYmcRq1y5Ms6cOaO03WFv3rzBvn37EBUVhd9++w1NmzaFp6cnunfvjlKlSomORyRMRkYGbGxssHHjRrRo0UJ0HCIiIiIitZWWlgYLCwscOnQItra2ouMQkZLREB1A3ejq6uLt27eiY3ySnp4eevXqhS1btiA5ORkDBgzAzp07YWZmhm7dumHz5s1IT08XHZOoyBkYGGDhwoXw8/NDTk6O6DhERERERGqrRIkSmDBhAgICAkRHISIlxGJnEdPT01PqYuffGRoawtPTE7t27cK9e/fQq1cvbNiwASYmJnBzc8O2bdvw5s0b0TGJioybmxvKlSuHlStXio5CRERERKTWfH19cfLkScTHx4uOQkRKhtPY6Zs9ffoUO3fuRGRkJM6ePYtOnTrBw8MDrq6u0NHRER2PqFBdvnwZLi4uSEhIgJGRkeg4RERERERqa9myZYiJicGePXtERyEiJcJiJ/0rqamp2L59O6KionDp0iV069YNHh4eaN26NbS0tETHIyoUo0aNwtu3b9nhSUREREQkUGZmJmrWrIktW7bA0dFRdBwiUhIsdlKBSU5OxtatWxEVFYWbN2+iZ8+e8PDwQMuWLSGVSkXHIyowL168gLW1Nfbu3Qt7e3vRcYiIiIiI1NYvv/yCbdu2ISYmRnQUIlISLHZSobhz5w62bNmCqKgoJCcnw83NDR4eHmjatCk0NLhULKm+sLAwrF69GrGxsfw3TUREREQkSHZ2NqytrbFmzRq0aNFCdBwiUgIsdlKhu3HjBqKiohAVFYUXL17Azc0Nnp6ecHBwgEQiER2PKF9yc3Ph6OiIESNGYMCAAaLjEBERERGprXXr1iEsLAxHjx7le0wiYrFTFXTu3BlGRkZYu3at6Cj/2pUrVxSFz+zsbLi7u8PDwwN2dnb8pUQq588//0SPHj1w9epVlCpVSnQcIiIiIiK1lJOTA1tbWyxbtgxt27YVHYeIBOPcy38hPj4eUqkUTk5OoqOojNq1a2PWrFm4du0aduzYAQDo2bMnrK2tMX36dCQkJAhOSPT1GjdujPbt22PWrFmioxARERERqS1NTU0EBARg2rRpYD8XEbHY+S+sWrUKvr6+uHz5Mq5evfrZsdnZ2UWUSjVIJBLY2dlh3rx5+O9//4sNGzYgIyMD7dq1Q506dTBnzhzcuHFDdEyiL5o7dy7Wr1//xZ8BRERERERUeNzd3ZGRkYHo6GjRUYhIMBY78+nNmzfYtGkTBg8ejN69eyMsLExx3507dyCRSLB582a4uLhAT08PK1euxNOnT9GnTx+YmppCT08PtWvXxpo1a/Kc9/Xr1/D29oahoSEqVqyIoKCgon5qRU4ikaBRo0YICQnBvXv3sGLFCqSmpqJ58+Zo2LAh5s+fjzt37oiOSfRRFStWxI8//oiRI0fyU2QiIiIiIkE0NDQwa9YsTJ8+Hbm5uaLjEJFALHbm07Zt22Bubo66deuiX79+WL9+/Qfdm1OmTIGvry8SEhLQvXt3vH37Fg0aNMDevXtx5coVjBo1CkOHDsWhQ4cUjxk/fjx+//13bN++HYcOHUJ8fDyOHTtW1E9PGA0NDTRr1gzLli1DcnIyFixYgFu3bsHBwQGOjo5YsmQJkpOTRcckymPEiBF48OABdu7cKToKEREREZHa6t69OyQSCf8uJ1Jz3KAon1q2bIkuXbpg/PjxkMvlqFatGkJCQtCrVy/cuXMH1apVw8KFCzFu3LjPnsfT0xOGhoZYvXo10tPTUa5cOYSHh8PLywsAkJ6eDlNTU3Tv3r1YbFCUX9nZ2Th8+DAiIyOxe/du2NrawsPDA71790bFihVFxyPC4cOH4ePjg4SEBOjr64uOQ0RERESklvbt24cJEybg4sWLkEqlouMQkQDs7MyHmzdvIjY2Ft999x2Ad9Owvby8sHr16jzj7O3t83wtk8kQGBiIunXroly5cjA0NMSOHTtw7949AMCtW7eQlZWFJk2aKB5jaGiIOnXqFPIzUn5aWlpwdXXFmjVrkJKSgvHjx+PkyZOwsrJCmzZtsHr1ajx79kx0TFJjLi4ucHBwwPz580VHISIiIiJSWx06dECpUqUQFRUlOgoRCaIpOoAqWr16NWQyGczMzBTH3jfI3r9/X3HMwMAgz+MWLlyIkJAQLF26FHXq1IGhoSH8/f3x6NGjPOegz9PR0UHXrl3RtWtXvHnzBvv27UNkZCTGjRsHJycneHh4oHv37ihVqpToqKRmQkJCUL9+fXh7e6Nq1aqi4xARERERqR2JRILZs2dj+PDhcHd3h6Ymyx5E6oadnd8oJycH69atw9y5c3H+/HnF7cKFC6hbt+4HGw793YkTJ9ClSxf069cPdnZ2qFGjBhITExX3W1hYQEtLC6dOnVIcy8jIwOXLlwv1OakyPT099OrVC1u3bkVycjL69euHnTt3wszMDN27d8fmzZuRnp4uOiapCTMzM4wePRpjx44VHYWIiIiISG25uLjAxMQEGzZsEB2FiARgsfMbRUdH48mTJxg8eDBsbW3z3Dw9PREeHv7Jnd8sLS1x6NAhnDhxAteuXcMPP/yA27dvK+43NDTEoEGDMGnSJPz++++4cuUKfHx8IJPJiurpqTRDQ0P06dMHu3btwt27d9GjRw9s2LABJiYmcHd3x/bt2/HmzRvRMamYmzBhAs6fP4/ff/9ddBQiIiIiIrX0vrtz1qxZyMrKEh2HiIoYi53fKCwsDM7OzihXrtwH97m5ueHu3bs4ePDgRx87depUNGrUCB06dECLFi1gYGCg2IjovYULF8LZ2Rk9evSAs7MzbG1t0aJFi0J5LsVZ6dKlMWDAAOzbtw///e9/0bZtW6xYsQLGxsbo27cv9uzZg8zMTNExqRjS1dXF4sWLMXLkSP5hRUREREQkSLNmzWBlZYXw8HDRUYioiHE3dlIrqamp2LZtG6KionD58mV069YNnp6ecHFxgZaWluh4VEzI5XJ06NABbdu2xbhx40THISIiIiJSS2fOnEGPHj1w8+ZN6Orqio5DREWExU5SW0lJSdi6dSuioqJw69Yt9OzZE56enmjRogWkUqnoeKTirl+/DicnJ1y6dAnGxsai4xARERERqaVu3brBxcUFo0aNEh2FiIoIi51EAO7cuYMtW7YgMjISKSkp6N27Nzw9PdGkSRNoaHC1B8qfiRMnIjU1FevWrRMdhYiIiIhILV24cAF//fUXBg4cCIlEIjoOERUBFjuJ/iExMVFR+Hz58iXc3d3h4eEBBwcH/nKkb5KWlgYbGxts2bIFTZs2FR2HiIiIiEgtyeVyvpcjUiMsdhJ9xpUrVxAVFYXIyEjk5OTAw8MDHh4eqFevHn9Z0leJiIjAokWLcPr0aS6PQERERERERFTIWOwk+gpyuRznz59HVFQUoqKioK2tDU9PT3h4eKBWrVqi45ESk8vlaNGiBfr164chQ4aIjkNERERERERUrLHYWcRSU1NRp04dPHr0SHQUyie5XI7Tp08jKioKW7ZsQZkyZRSFTwsLC9HxSAmdP38erq6uuHr1KsqWLSs6DhEREREREVGxxWJnEXv58iWqVKmCV69eiY5CBSA3NxexsbGIiorCtm3bYGJiAk9PT7i7u8Pc3Dxf58vOzoaOjk4hpCWRfH19oaGhgZ9++kl0FCIiIiIi+pu//voLurq6qF27tugoRFQAWOwsYllZWTA0NERWVpboKFTAZDIZjh49isjISOzYsQM1a9aEh4cH3NzcYGJi8lXnSExMxNKlS/Hw4UO4uLhg4MCB0NfXL+TkVBSePn2KWrVqISYmBvXq1RMdh4iIiIhI7Z08eRKDBg3CvXv3UKlSJbi4uGDevHkoV66c6GhE9C9oiA6gbrS0tJCTkwOZTCY6ChUwqVQKFxcX/PLLL0hJScGMGTNw/vx51KlTBy1btsTy5cuRmZn52XM8f/4cZcuWhYmJCfz8/LBkyRJkZ2cX0TOgwlSuXDnMnDkTfn5+4GdMRERERERivXz5EsOGDYOlpSX+/PNPzJ49G6mpqRg5cqToaET0L7GzUwB9fX08fvwYBgYGoqNQEcjMzMRvv/2GyMhIrF+/Hpqaml98THR0NHx8fLB582a4uLgUQUoqCjKZDA4ODpgwYQL69OkjOg4RERERkVp5/fo1tLW1oampicOHDyveczVp0gQAcOXKFTRp0gRXrlxBlSpVBKclovxiZ6cAenp6ePv2regYVER0dHTQtWtXbNq0CVKp9LNj3y9vsHnzZtSqVQtWVlYfHffixQssWrQIO3bsYJegCpFKpVi2bBkmTJiA9PR00XGIiIiIiNTGw4cPsWHDBiQmJgIAzM3NkZSUBDs7O8UYAwMD1K1bF8+fPxcVk4gKAIudAujq6rLYqaYkEsln79fW1gYAHDhwAK6urqhQoQKAdxsX5ebmAgAOHjyIGTNmYPz48fD19UVsbGzhhqYC5eTkBGdnZwQGBoqOQkRERESkNrS0tLBw4UI8ePAAAFCjRg00btwYfn5+yMzMRHp6OgIDA3Hv3j12dRKpOBY7BdDV1cWbN29ExyAl834d1+joaOTm5qJp06bQ0tICAGhoaEBDQwNLly7F4MGD0aFDBzg4OKB79+6oXr16nvM8evQIf/31V5Hnp683f/58rFq1Cjdu3BAdhYiIiIhILZQrVw4NGzbEihUrFM1Hu3fvxq1bt9C8eXM0bNgQZ8+eRVhYGMqUKSM4LRH9Gyx2CsDOTvqcNWvWwN7eHhYWFopj586dw+DBgxEREYHo6Gg0atQI9+/fR506dVC5cmXFuOXLl6NTp05wc3ODgYEBJkyYgIyMDBFPgz7D2NgYkyZNwujRo0VHISIiIiJSG4sXL8bFixfh5uaGnTt3Yvfu3bC2tsatW7cgl8sxdOhQtGjRAtHR0QgODkZqaqroyESUDyx2CsA1O+mf5HK5Yj3Pw4cPo3379jAyMgIAHD9+HP369UP9+vURGxuLWrVqITw8HKVLl0bdunUV54iJicGECRPQsGFDHDlyBFu3bsWvv/6Kw4cPC3lO9HmjRo3CrVu3sHfvXtFRiIiIiIjUgrGxMcLDw2FqaoqhQ4ciJCQECQkJ8PHxwfHjxzFs2DDo6Ojg3r17+O233zBx4kTRkYkoH768LTQVOE5jp7/Lzs5GcHAwDA0NoampCR0dHTg5OUFbWxs5OTm4ePEiEhMTsX79ekilUgwdOhQxMTFo3rw5ateuDQBISUnBzJkz0alTJ/znP/8B8G7B7YiICCxYsABdunQR+RTpI7S1tbF06VKMGDECbdq0ga6uruhIRERERETFXvPmzdG8eXOEhITgxYsX0NbWVjSa5OTkQFNTE8OGDYOTkxOaN2+OP//8E40bNxacmoi+BTs7BeA0dvo7DQ0NlChRAoGBgRg5ciRSU1Oxf/9+pKSkQCqVYvDgwTh16hSaN2+ORYsWQUtLC8eOHcPbt29RqlQpAO+muf/555+YPHkygHcFVODdboLa2tqK9UBJubi6usLW1haLFi0SHYWIiIiISK3o6+tDV1f3g0KnTCaDRCJB3bp10a9fP/z000+CkxLRt2KxUwBOY6e/k0qlGDVqFB4/foy7d+9i2rRpWLlyJQYOHIinT59CW1sbDRs2xIIFC3D9+nUMHToUpUqVwq+//go/Pz8AwLFjx1C5cmU0aNAAcrlcsbHRnTt3UL16dXYSK7FFixZh0aJFuH//vugoRERERERqQSaToXXr1rCzs8OECRNw6NAhxXum98uLAUBaWhr09fXZPEKkYljsFICdnfQpVapUwcyZM5GSkoL169crPmX8u4sXL6J79+64dOkSgoODAQAnTpyAq6srACArKwsAcOHCBTx79gxmZmYwNDQsuidB36R69erw9fXFhAkTREchIiIiIlILUqkU9vb2SEpKwtOnT9GnTx84ODhgyJAh2LZtG86cOYM9e/Zgx44dqFGjRp4CKBEpPxY7BeCanfQ1KlSo8MGx27dv4+zZs6hduzZMTU1RokQJAEBqaiqsrKwAAJqa75bi3b17NzQ1NdGkSRMA7zZBIuU0efJkxMXF4Y8//hAdhYiIiIhILcycOROampoYMWIEkpKSMHnyZGRnZ2Py5Mno0aMHevXqhf79+3OTIiIVJJGzAlLkBg8erPjUiOhryeVySCQS3LhxA7q6uqhSpQrkcjmys7Ph6+uLK1eu4MSJE5BKpcjIyEDNmjXx3XffYcaMGYqi6PvznD17FmXKlIGFhYXAZ0R/t23bNsyaNQvnzp1TFKyJiIiIiKjwjBkzBidOnMCZM2fyHD979ixq1qyp2CPh/XsxIlIN7OwUgGt2Un68/+Vas2ZNVKlSRXFMW1sbgwcPxosXLzB48GAEBQWhcePGKFmyJMaOHZun0Pne9u3b4eTkBHt7eyxYsAB3794t0udCH+rVqxfKly+PFStWiI5CRERERKQWFi5ciPj4eOzZswfAu02KAMDe3l5R6ATAQieRimGxUwBOY6eCJJfL0bhxY6xZswavXr3Cnj17MGDAAOzevRuVK1dGbm5unvESiQTz5s1DcnIygoODkZiYiIYNG6Jp06ZYunQpHjx4IOiZqDeJRILQ0FDMmjULjx8/Fh2HiIiIiKjYk0ql8Pf3x/79+wGAM6yIiglOYxdg+vTpkEqlmDFjhugoRACA7OxsHDx4EFFRUdi9ezfq1asHDw8P9OrV66Nrh1LhGTNmDNLT07Fq1SrRUYiIiIiI1MK1a9dgZWXFDk6iYoKdnQJwGjspGy0tLXTo0AFr165FSkoKxowZg+PHj8PS0hJt27ZFWFgYnj17JjqmWggICMDevXtx9uxZ0VGIiIiIiNSCtbX1B4VO9oURqS4WOwXQ1dVlsZOUlq6uLrp164ZNmzbhwYMHGDJkCPbv349q1aqhU6dO2LBhA169eiU6ZrFVqlQpBAUF4YcffvhgCQIiIiIiIipccrkccrkcz58/Fx3l/9i77+ioq7WL43vSAyF0CCUQpXciHUGkCwgiKE1KKNKLICI9AUIvKkW8IBDpgYhIEwQVBBEFqUKAgChVuvTUmfePe8krUgya5Exmvp+1skgmM/Pbk7vIxT3POQfAP0TZaQB7diKtSJcunV5//XVFRETo7NmzatOmjVauXCl/f3+9+uqrCg8P1507d0zHdDgdOnSQJC1cuNBwEgAAAMC5WCwWbdiwQfXr12e6E0ijKDsNYBk70qIMGTLojTfe0Jo1a/Trr7+qSZMmWrBggXLnzq2WLVtq1apVlPjJxMXFRTNmzNDQoUN148YN03EAAAAAp9KgQQPFxcVpzZo1pqMA+AcoOw1gGTvSusyZM6tjx47auHGjTp48qdq1a2vmzJnKnTu32rVrp3Xr1ik2NtZ0zDStQoUKatiwoUaNGmU6CgAAAOBUXFxcNHr0aI0cOZKtpYA0iLLTAJaxw5Fky5ZNXbt21ddff63IyEhVqlRJEyZMUK5cudS5c2d9+eWXio+PNx0zTRo3bpwWLVqkI0eOmI4CAAAAOJXGjRvL09NTERERpqMAeEqUnQYw2QlH5efnp969e2vHjh3av3+/SpQooeHDhyt37tzq0aOHtm7dqoSEBNMx04wcOXJoxIgR6tu3L/sFAQAAAKnIYrFozJgxCg4O5r9hgDSGstMA9uyEM/D399eAAQP0448/ateuXcqfP7/69+8vf39/9evXTzt37mRJSBL07NlTFy9e1KpVq0xHAQAAAJxKvXr1lC1bNi1dutR0FABPwWJjXCjV/fDDD+rbt69++OEH01GAVHfs2DGFh4dr+fLlun37tlq0aKFWrVqpXLlyslgspuPZpa1btyooKEhHjhxRunTpTMcBAAAAnMbWrVvVpUsXRUZGyt3d3XQcAEnAZKcB7NkJZ1akSBGNHDlShw8f1vr16+Xl5aXWrVurYMGCGjp0qA4cOMCS7b948cUXValSJU2cONF0FAAAAMCpvPjiiwoICNAnn3xiOgqAJGKy04Djx4/r5Zdf1vHjx01HAeyCzWbTvn37tHz5cq1YsULe3t5q2bKlWrZsqWLFipmOZxfOnDmjwMBA7d69W88884zpOAAAAIAF4UV5AAAgAElEQVTT+P7779WqVSsdP35cnp6epuMA+BtMdhrAAUXAgywWi5577jlNmjRJp06d0oIFC/THH3+oTp06KlOmjMaNG6eTJ0+ajmmUv7+/+vfvrwEDBpiOAgAAADiVKlWqqGTJkvr4449NRwGQBEx2GnDp0iWVKFFCly9fNh0FsGtWq1U7duzQ8uXL9emnnypfvnxq2bKlWrRooXz58pmOl+qio6NVsmRJzZo1S/Xr1zcdBwAAAHAaP/30k5o0aaITJ07I29vbdBwAT0DZacDNmzeVJ08e3bp1y3QUIM2Ij4/X1q1bFR4erlWrVqlIkSJq1aqVXn/9deXKlct0vFSzdu1aDRw4UIcOHZKHh4fpOAAAAIDTaNasmapVq8ZqK8DOUXYaEBcXp3Tp0ikuLs50FCBNio2N1ZYtWxQeHq41a9aoTJkyatWqlZo3b67s2bObjpeibDabGjVqpJo1a+qdd94xHQcAAABwGocOHVLdunV14sQJ+fj4mI4D4DEoOw2w2Wxyc3NTTEyM3NzcTMcB0rTo6Ght3LhR4eHh+uKLL1SxYkW1bNlSr776qrJkyWI6Xoo4fvy4qlatqoMHDyp37tym4wAAAABOo3Xr1ipdurSGDBliOgqAx6DsNCR9+vS6ePEi7wYByeju3btav369li9fri1btqh69epq2bKlXnnlFfn6+pqOl6wGDx6sc+fOadGiRaajAAAAAE7j2LFjqlatmk6cOKGMGTOajgPgESg7DcmWLZuOHj2qbNmymY4COKSbN29qzZo1Cg8P17fffqvatWurZcuWevnll5U+fXrT8f6127dvq2jRogoPD9fzzz9vOg4AAADgNIKCghQQEKCQkBDTUQA8AmWnIXnz5tWuXbuUN29e01EAh3f9+nWtXr1ay5cv165du9SgQQO1bNlSDRo0kJeXl+l4/9jSpUs1efJk7dmzR66urqbjAAAAAE7hl19+UcWKFXXs2DFlzZrVdBwAf+FiOoCz8vLy0r1790zHAJxC5syZ1bFjR23atEknTpxQzZo1NWPGDOXKlUvt27fX+vXrFRsbazrmU2vdurUyZMiguXPnmo4CAAAAOI1nn31WzZs315QpU0xHAfAITHYaUrJkSS1btkylSpUyHQVwWhcuXFBERITCw8MVGRmppk2bqlWrVqpZs2aaOTzswIEDqlu3riIjI3lXGQAAAEglZ86cUdmyZXXkyBHlzJnTdBwAf8JkpyHe3t6Kjo42HQNwarly5VKfPn20Y8cO7du3T8WLF9ewYcOUO3du9ejRQ1u3blVCQoLpmE9UpkwZvf766xoxYoTpKAAAAIDT8Pf31xtvvKGJEyeajgLgL5jsNKR69eoaO3asXnjhBdNRAPzFyZMntWLFCoWHh+vSpUt6/fXX1apVK1WuXFkWi8V0vIdcu3ZNxYoV06ZNm1S2bFnTcQAAAACncOHCBZUoUUKHDh1Snjx5TMcB8D9Mdhri5eXFZCdgpwoUKKAhQ4Zo//79+vrrr5UlSxZ17txZAQEBeuedd7Rnzx7Z0/tEWbJk0ejRo9WnTx+7ygUAAAA4sly5cqlz584aN26c6SgA/oSy0xCWsQNpQ9GiRRUcHKzDhw9r3bp18vT0VKtWrVSoUCENGzZMBw8etIuCsUuXLrp7966WLl1qOgoAAADgNAYNGqTly5frt99+Mx0FwP9QdhrCZCeQtlgsFpUqVUqhoaGKiopSeHi44uLi1LhxYxUvXlyjRo3S0aNHjeVzdXXVjBkzNGjQIN26dctYDgAAAMCZZM+eXT169NCYMWNMRwHwP5Sdhnh5eenevXumYwD4BywWi8qVK6dJkybp1KlTmj9/vq5fv65atWqpTJkyGjdunE6ePJnquapWraratWsrNDQ01a8NAAAAOKu3335bq1ev1okTJ0xHASDKTmOY7AQcg4uLi6pUqaL3339fZ86c0fTp03X27FlVqVJFFSpU0NSpU3XmzJlUyzNx4kTNmzdPx44dS7VrAgAAAM4sc+bMeuuttzRq1CjTUQCIstMY9uwEHI+rq6tq1KihDz/8UOfPn9e4ceMUGRmpsmXL6vnnn9f06dN14cKFFM2QK1cuDRkyRG+99ZZd7CUKAAAAOIN+/frpyy+/1JEjR0xHAZweZachLGMHHJubm5vq1q2rjz/+WBcuXNDQoUO1Z88eFS9eXDVr1tRHH32ky5cvp8i1+/Tpo19//VVr165NkecHAAAA8KAMGTJo4MCBCgkJMR0FcHqUnYawjB1wHh4eHmrUqJEWLlyoCxcuqF+/ftq6dasKFiyo+vXrJ+75mZzXmz59uvr378/vGQAAACCV9OrVSzt27ND+/ftNRwGcGmWnISxjB5yTl5eXmjZtquXLl+v8+fPq3Lmz1q1bp/z586tx48ZavHixbt68+a+vU7duXZUpU0ZTpkxJhtQAAAAA/k66dOk0ePBgjRw50nQUwKlRdhrCZCeA9OnTq0WLFlq1apXOnj2rli1bavny5fL391ezZs20YsUK3blz5x8//7Rp07Rly5Z/9RwAAAAAkq5r167at2+ffvzxR9NRAKdF2WkIe3YC+DNfX1+1bdtW69at06+//qqXX35Z8+bNU+7cudWqVSutXr36qd8gCQgI0ObNm+Xl5ZVCqQEAAAD8mZeXl4YPH64RI0aYjgI4LcpOQ5jsBPA4mTNnVqdOnbRp0yadOHFCL774oj744APlypVL7du314YNGxQbG5uk53J3d5erq2sKJwYAAABwX8eOHXX8+HFt377ddBTAKVF2GsKenQCSInv27Orevbu++eYbHT58WOXLl9fYsWOVK1cudenSRZs3b1Z8fLzpmAAAAAD+x8PDQ8HBwRoxYoRsNpvpOIDToew0hGXsAJ5W7ty51bdvX3333Xfat2+fihYtqqFDhypPnjzq2bOntm3bpoSEBNMxAQAAAKfXtm1bXbhwQV9//bXpKIDToew0hGXsAP6NfPnyaeDAgdq9e7d27typvHnzqm/fvnrllVcUExNjOh4AAADg1Nzc3BQSEqLhw4cz3QmkMspOQ1jGDiC5FChQQEOHDtWBAwe0dOlSubu7m44EAAAAOL2WLVvq1q1b+uKLL0xHAZyKxcZbDEZcuXJFBw4cUO3atU1HAQAAAAAAKWDVqlUaO3as9uzZI4vFYjoO4BSY7DQka9asqlWrlukYAPAQq9Wa5NPeAQAAADzeq6++KpvNptWrV5uOAjgNJjsBAA+4c+eO3n77bS1btkx+fn7y8/NTzpw5H/r8/p85cuSQh4eH6dgAAACAXVq/fr0GDx6sAwcOyMWFmTMgpVF2AgAeYrPZ9Mcff+j333/XxYsX9fvvvz/w+Z9vu3z5snx9fR9biv758+zZs8vV1dX0ywMAAABSjc1mU9WqVdWvXz+1atXKdBzA4VF2AgD+FavVqqtXrz6yFP1rQXrt2jVlyZLlkROif/08S5YsvPMNAAAAh7Blyxb16tVLhw8flpubm+k4gEOj7AQApJr4+Hhdvnz5kROif/385s2bypEjxxOX0N//PFOmTGz4DgAAALtls9lUs2ZNdezYUR06dDAdB3BolJ12Ki4uTi4uLiz3BOC0YmNjdenSpScuob//eUxMjHLmzPm306I5c+aUj48PxSgAAABS3fbt29WhQwcdPXqUPe+BFETZacimTZtUuXJlZcyYMfG2+/9TWCwWffzxx7JarerataupiACQZty7d++JZeifb5OUpGlRPz8/eXt7G35lSTd37lxt27ZN3t7eqlmzplq3bk2pCwAAYGfq16+vZs2aqVu3bqajAA6LstMQFxcXfffdd6pSpcojvz9nzhzNnTtXO3bskKenZyqnA+BMbDabU5Vit2/fTtK06MWLF+Xp6fnEMvTPf5p6d/7OnTvq16+fdu7cqSZNmuj3339XVFSUWrVqpT59+kiSIiMjNXr0aO3atUuurq5q3769Ro4caSQvAACAM/vxxx/VvHlzRUVFycvLy3QcwCGxK64h3t7eunz5si5fvqy7d+8qOjpa0dHRunfvnqKjo3X9+nX99NNPunfvHmUngBQTHx+v/fv3q3z58qajpBofHx8VLFhQBQsWfOL9bDabbty48cgydOfOnQ+dSO/j45OkadHs2bMn66b0Bw8eVHh4uBYsWKDXXntNkvTRRx9pxIgR6tChgy5evKh69eqpfPnyWrx4saKiojR37lzFxMRo7NixyZYDAAAAf69ixYoKDAzUnDlz1LdvX9NxAIfEZKchuXLl0sWLFxOXSFoslsQ9Ol1dXZU+fXrZbDYdOHBAmTNnNpwWgKOKjo5WgQIFtG7dOgUGBpqOk2ZZrVZdu3YtSSfSX716VZkzZ/7baVE/Pz9lzZr1b0+kX7Rokd59912dPHlSHh4ecnV11W+//abGjRurd+/ecnd314gRI3T06FH5+PhIkubPn69Ro0Zp3759ypIlS2r8iAAAAPA/+/fvV8OGDXXixAmlS5fOdBzA4TDZaUhCQoLefvtt1apVS25ubnJzc5O7u3vin66urrJarcqQIYPpqAAcmJeXlwYOHKjQ0FB9+umnpuOkWS4uLsqWLZuyZcumEiVKPPG+8fHxunLlykNL6M+fP699+/Y9UJDeuHFD2bNn16FDh5Q1a9ZHPl+GDBkUExOjNWvWqGXLlpKkL774QpGRkbp586bc3d2VOXNm+fj4KCYmRp6enipatKhiYmK0fft2vfLKK8n+8wAAAMDjlS1bVs8//7xmzZqld955x3QcwOFQdhri5uamcuXKqUGDBqajAHBy3bp108SJE3Xo0CGVKlXKdByH5+bmlji5WaZMmSfeNzY2VpcvX1amTJkee5+XXnpJnTp1Ut++fTV//nzlyJFDZ8+eVUJCgrJnz648efLo7NmzWrp0qdq0aaPbt29rxowZunz5su7cuZPcLw8AAABJEBISolq1aql79+4MOQHJzDUkJCTEdAhndO3aNVWqVEl58+Z96HvOdlgIALPc3d1ltVq1YsWKxD0fYR9cXV3l6+v7xKXsbm5uiXs/xcbGKleuXHr22Wd148YNVaxYUc2aNdOdO3c0ePBghYaGau3atYkTnvXr11fx4sUTn8tms+n8+fM6fPiw4uLi5OnpKXd399R4qQAAAE4lR44cOnDggE6ePKkXXnjBdBzAobBnp526fv264uLilC1btr/drw0A/q1bt26pQIEC+vbbb1W0aFHTcfAvjRkzRmvWrNGcOXMS92K9ceOGjhw5Ij8/P82fP19fffWVJk2apGrVqiU+zmazae3atRo3blziUnp3d/ckn0jPgXoAAABJFxUVpapVq+r48eOc1QEkI8pOQ1auXKkCBQroueeee+B2q9UqFxcXRUREaM+ePerdu/cjpz8BILmNHTtWx44d08KFC01HwVPYt2+fEhISFBgYKJvNps8++0w9evTQwIED9c477ySuFPjzG2c1atRQ3rx5NWPGjCceUGSz2XTz5s0nHrh0/7ZLly4pffr0ST6RnolRAAAAqXPnzsqdO7fGjBljOgrgMCg7DSlXrpwaN26sx+0i8P3336tPnz6aOnWqatSokbrhADilGzduqECBAtq1a5cKFixoOg6SaOPGjRoxYoRu3bqlHDly6Nq1a6pdu7bGjRun9OnT69NPP5Wrq6sqVqyou3fvasiQIdq+fbtWr16typUrJ1sOq9Wq69evJ+lE+itXrihTpkxJPpHe1dU12XICAADYk19//VXly5fX0aNHlS1bNtNxAIfAAUWGZMyYUefOndOxY8d0+/Zt3bt3T9HR0bp7965iYmJ0/vx57d+/X+fPnzcdFYCTyJgxo3r16qXx48dr3rx5puMgiWrWrKl58+bp+PHjunLligoWLKg6deokfj8+Pl7Dhg3TqVOnlD17dgUGBmrFihXJWnRK/50czZo1q7JmzfrAPqCPkpCQ8MgT6X///XcdOHDggYL0jz/+ULZs2R5Ziv61IM2SJQt7XgMAgDQlICBALVq00KRJkzRp0iTTcQCHwGSnIe3atdOSJUvk4eEhq9UqV1dXubm5yc3NTe7u7vLx8VFcXJzCwsJUu3Zt03EBOIlr166pUKFC+umnnxQQEGA6Dv6hRx10d/fuXV29elXp0qVT1qxZDSV7enFxcbp8+fITl9Df//zOnTvKmTPnE5fQ3//c19eXYhQAANiFc+fOqXTp0jp8+LD8/PxMxwHSPMpOQ1q0aKG7d+9q8uTJcnV1faDsdHNzk4uLixISEpQ5c2YOfAAAIAmio6N16dKlJO0xGh8fn6RpUT8/P6VPn970SwMAAA6uf//+slqt+uCDD0xHAdI8yk5D2rdvLxcXF4WFhZmOAgCA07lz585DJejj9ht1c3NL8on0Xl5epl8aAABIgy5evKjixYtr//798vf3Nx0HSNMoOw3ZuHGjYmNj1aRJE0n/v+TQZrMlfri4uLDEDgAAg2w2m27dupXkE+m9vb2TdCJ9jhw5OJEeAAA8YPDgwfrjjz/00UcfmY4CpGmUnQAAAMnAZrMl+UT6y5cvK2PGjH87LVq4cGF5eXnx5icAAE7g6tWrKlKkiHbv3q1nnnnGdBwgzaLsNCghIUGRkZE6ceKEAgICVLZsWUVHR2vv3r26d++eSpYsqZw5c5qOCQAAkllCQoKuXr36t0vo//Of/6hq1aqm4wIAgFQSHBys06dPa8GCBaajAGkWZadB48aN0/Dhw+Xh4aHs2bNrzJgxslgs6tevnywWi5o2baoJEyZQeAJ4ai+++KJKliypmTNnSpICAgLUu3dvDRw48LGPScp9AAAAAKScP/74Q4UKFdKOHTtUpEgR03GANMnFdABntW3bNi1ZskQTJkxQdHS03nvvPU2ZMkVz587Vhx9+qLCwMB0+fFhz5swxHRWAHbp8+bJ69uypgIAAeXp6KmfOnKpdu7Y2b94sSVq1apXGjx//VM+5e/du9ezZMyXiAgAAAEiCTJkyqX///ho1apTpKECa5WY6gLM6c+aMMmbMqLfffluS9Nprr+m7777TwYMH1aZNG0nS4cOHtXPnTpMxAdip5s2b6+7du5o3b54KFiyoS5cuadu2bbp69aokKUuWLE/9nNmzZ0/umAAAAACeUt++fVWwYEH9/PPPKlmypOk4QJrDZKch7u7uunv3rlxdXR+47c6dO4lfx8TEKD4+3kQ8AHbsjz/+0Pbt2zVhwgTVrl1b+fPnV4UKFTRw4EC1atVK0n+Xsffu3fuBx92+fVtt27aVj4+P/Pz8NGXKlAe+HxAQ8MBtFotFERERT7wPAAAAgOTl4+OjQYMGKTg42HQUIE2i7DTE399fNptNS5YskSTt2rVLP/zwgywWiz7++GNFRERo06ZNqlGjhuGkAOyNj4+PfHx8tGbNGkVHRyf5cdOmTVOxYsW0d+9ejRo1SkOHDtWqVatSMCkAAACAf6JHjx7atWuX9u7dazoKkOawjN2QsmXLqmHDhurYsaM++eQTnTp1SoGBgerSpYtat24tLy8vVaxYUW+++abpqADsjJubm8LCwvTmm29qzpw5CgwM1PPPP6/XX39dlSpVeuzjKlWqpGHDhkmSChcurN27d2vatGlq1qxZakUHAAAAkATe3t4aOnSoRo4cqXXr1pmOA6QplJ2GpEuXTqNHj1alSpX01Vdf6ZVXXlG3bt3k5uam/fv368SJE6pSpYq8vLxMRwVgh5o3b65GjRpp+/bt+v7777Vx40ZNnTpVY8eO1dChQx/5mCpVqjz0NZOdgGOyWq1ycWEBDwAAadmbb76pMmXK8P/rwFOi7DTI3d1dTZs2VdOmTR+43d/fX/7+/oZSAUgrvLy8VLduXdWtW1cjR45Uly5dFBISooEDBybL81ssFtlstgdui4uLS5bnBpByEhISdPHiRbm4uMjPz890HAAA8A95eHjo+eefl8ViMR0FSFN4a8AO2Gy2hwqFv34NAH+nePHiio+Pf+w+nrt27Xro62LFij32+bJnz64LFy4kfn3x4sUHvgZgn1xdXTVv3jwFBQXx7wkAANI4ik7g6VF22gGLxfLQLzB+oQF4nKtXr6pWrVpavHixDh48qFOnTmnlypWaNGmSateuLV9f30c+bteuXRo/fryioqI0d+5cLVy4UP3793/sdWrVqqVZs2Zpz5492rdvn4KCgthaA0gjBg8erKtXr+qjjz4yHQUAAABIVSxjB4A0xsfHR5UrV9YHH3ygEydOKCYmRnny5FGbNm00fPjwxz5uwIABOnjwoMaOHav06dNr9OjReu211x57/6lTp6pz58568cUXlTNnTk2aNEmRkZEp8ZIAJDN3d3ctWrRI1apVU506dVSoUCHTkQAAAIBUYbGxvgkAAMAhTZ8+XcuWLdP27dvl5sZ73AAAAHB8LGM3yGq1KioqynQMAADgoHr37q306dNr0qRJpqMAAAAAqYLJToPi4+Pl6emp+Ph49ugEAAAp4syZMypXrpw2bdqkwMBA03EAAACAFMVkp0Fubm5ycXFRfHy86SgAAMBB+fv7a+rUqWrXrp2io6NNxwEAAABSFGWnYV5eXrp3757pGAAAwIG1bdtWRYsW1YgRI0xHAQAAAFIUZadhXl5eTFkAAIAUZbFY9NFHH2nJkiXatm2b6TgAAABAiqHsNMzb25uyE0CaVaNGDS1atMh0DABJkC1bNp0/f141atQwHQUAAABIMZSdhjHZCSAtGzFihMaOHauEhATTUQAAAAAAoOw0jT07AaRltWvXVubMmRUREWE6CgAAAAAAlJ2msYwdQFpmsVg0cuRIjRkzRlar1XQcAAAAAICTo+w0jGXsANK6l156Sd7e3lq9erXpKAAAAIBDiYuLMx0BSHMoOw1jGTuAtM5isWj48OEKDQ2VzWYzHQcAAABwCDExMRozZoxiYmJMRwHSFMpOw5jsBOAImjRpIqvVqvXr15uOAtiNoKAgWSyWhz72799vOhoAAEgD5s2bp71798rT09N0FCBNoew0jD07ATiC+9Odo0ePZroT+JM6derowoULD3yULFnSWJ7Y2Fhj1wYAAEkXExOj8ePHKzg42HQUIM2h7DSMyU4AjqJZs2a6c+eOvvzyS9NRALvh6ekpPz+/Bz7c3Ny0YcMGVatWTZkyZVKWLFnUoEEDHTt27IHH7ty5U2XLlpWXl5eee+45rVu3ThaLRTt27JD03z28OnXqpGeeeUbe3t4qXLiwpkyZ8sAbDm3btlXTpk01btw45cmTR/nz55ckffLJJypfvrwyZMignDlzqmXLlrpw4ULi42JjY9W7d2/lypVLnp6e8vf317Bhw1LhJwYAAKT/TnWWLl1aFSpUMB0FSHPcTAdwduzZCcBRuLi4JE531qtXTxaLxXQkwG7duXNHAwYMUKlSpXT37l2NHj1ajRs31uHDh+Xu7q6bN2+qcePGatiwoZYuXaozZ87orbfeeuA5EhISlC9fPq1YsULZs2fXrl271LVrV2XPnl0dOnRIvN9XX30lX19fffnll4lFaFxcnMaMGaMiRYro8uXLGjRokNq0aaNvvvlGkvTee+9p7dq1WrFihfLly6ezZ88qKioq9X5AAAA4sZiYGE2YMEERERGmowBpksXGekOj+vfvr3z58ql///6mowDAv5aQkKDixYtr9uzZqlWrluk4gFFBQUFavHixvLy8Em+rXr26vvjii4fue/PmTWXKlEk7d+5U5cqVNWvWLAUHB+vs2bOJj1+4cKE6dOig7du3q1q1ao+85sCBA/Xzzz9r48aNkv472bllyxadPn1aHh4ej836888/q1SpUrpw4YL8/PzUs2dPnThxQps2beKNCwAAUtns2bO1bt069sMH/iGWsRvGMnYAjsTV1VVDhw7VmDFjTEcB7MILL7yg/fv3J358/PHHkqSoqCi1bt1azz77rHx9fZU7d27ZbDadPn1aknT06FGVLl36gaK0UqVKDz3/rFmzVL58eWXPnl0+Pj6aMWNG4nPcV6pUqYeKzj179qhJkybKnz+/MmTIkPjc9x/bsWNH7dmzR0WKFFGfPn30xRdfyGq1Jt8PBgAAPBJ7dQL/HmWnYSxjB+Bo2rRpo9OnT2v79u2mowDGpUuXTgULFkz8yJMnjySpUaNGunbtmubOnasffvhBP/30k1xcXBIPELLZbH87UblkyRINHDhQnTp10qZNm7R//35169btoUOI0qdP/8DXt27dUv369ZUhQwYtXrxYu3fv1oYNGyT9/wFGFSpU0K+//qrQ0FDFxcWpbdu2atCgAQeQAQCQwhYsWKCSJUuqYsWKpqMAaRZ7dhrm5eWlq1evmo4BAMnG3d1dQ4YM0ZgxYzisCHiEixcvKioqSvPmzVP16tUlST/++OMDk5PFihVTeHi4YmJi5OnpmXifP9uxY4eqVq2qnj17Jt524sSJv73+kSNHdO3aNU2YMEH+/v6SpIMHDz50P19fX7Vo0UItWrRQu3btVK1aNZ06dUrPPvvs079oAADwt2JiYjRu3DitXLnSdBQgTWOy0zBvb2+WsQNwOO3bt9e5c+d05coV01EAu5MtWzZlyZJFc+bM0YkTJ7R161b16tVLLi7//8+ydu3ayWq1qmvXroqMjNTmzZs1YcIESUqc+CxcuLD27NmjTZs2KSoqSiEhIfruu+/+9voBAQHy8PDQjBkzdOrUKa1bt+6hpXJTpkzR8uXLdfToUUVFRWnZsmXKmDGjcufOnYw/CQAA8Gf3pzoftXUNgKSj7DSMZewAHJGHh4d+/vlnZc2a1XQUwO64uroqPDxce/fuVcmSJdWnTx+NHz9e7u7uiffx9fXV2rVrtX//fpUtW1bvvvuuRo0aJUmJ+3j27NlTzZo1U8uWLVWxYkWdO3fuoRPbHyVnzpwKCwtTRESEihUrptDQUE2bNu2B+/j4+GjixIkqX768ypcvn3jo0Z/3EAUAAMmre/fuiVvLAPjnOI3dsIULF2rz5u4fDDwAACAASURBVM1atGiR6SgAAMCOffrpp2rRooWuXLmizJkzm44DAAAA2CX27DSMZewAAOBRFixYoEKFCilv3rw6dOiQBgwYoKZNm1J0AgAAAE9A2WmYl5cXZScAp2S1Wh/YoxDAg37//XeFhITo999/V65cudS4cePEfTsBAAAAPBrL2A3bvHmzJk6cqC1btpiOAgCpwmq1as2aNVq2bJkKFiyoJk2asAk7AAAAACBZMFJjGJOdAJxFXFycJGn//v16++23ZbVatX37dnXu3Fk3b940nA4AAABIm+Lj42WxWLR69eoUfQyQVlB2GsaenQAc3d27d/XOO++odOnSatKkiSIiIlS1alUtW7ZMW7dulZ+fn4YOHWo6JgAAAJDsGjdurDp16jzye5GRkbJYLNq8eXMqp5Lc3Nx04cIFNWjQINWvDaQ0yk7DvLy8dO/ePdMxACBF2Gw2tW7dWjt37lRoaKhKlSqltWvXKi4uTm5ubnJxcVG/fv20bds2xcbGmo4LAAAAJKsuXbro66+/1q+//vrQ9+bNm6f8+fOrdu3aqR9Mkp+fnzw9PY1cG0hJlJ2GsYwdgCM7duyYjh8/rnbt2ql58+YaO3aspk2bpoiICJ07d07R0dHasGGDsmXLpjt37piOC+BvTJs2TdWrV1dCQoLpKAAApAmNGjVSzpw5tWDBggduj4uL06JFi9SpUye5uLho4MCBKly4sLy9vfXMM89o8ODBiomJSbz/b7/9piZNmihLlixKly6dihUrppUrVz7ymidOnJDFYtH+/fsTb/vrsnWWscORUXYaxjJ2AI7Mx8dH9+7d0wsvvJB4W6VKlfTss88qKChIFStW1HfffacGDRooc+bMBpMCSIq33npLrq6umjZtmukoAACkCW5uburQoYPCwsJktVoTb1+7dq2uXLmijh07SpJ8fX0VFhamyMhIzZw5U4sXL9aECRMS79+9e3fFxsZq69atOnz4sKZNm6aMGTOm+usB0gLKTsOY7ATgyPLmzauiRYvq/fffT/zH3dq1a3Xnzh2Fhoaqa9eu6tChg4KCgiTpgX8AArA/Li4uCgsL06RJk3Tw4EHTcQAASBM6d+6s06dPa8uWLYm3zZs3T/Xq1ZO/v78kaeTIkapataoCAgLUqFEjDR48WMuWLUu8/2+//abq1aurdOnSeuaZZ9SgQQPVq1cv1V8LkBa4mQ7g7NizE4Cjmzx5slq0aKHatWsrMDBQ27dvV5MmTVSpUiVVqlQp8X6xsbHy8PAwmBRAUgQEBGjSpElq166dfvzxR/b6AgDgbxQqVEgvvPCC5s+fr3r16un8+fPatGmTwsPDE+8THh6u6dOn6+TJk7p9+7bi4+Pl4vL/82n9+vVT7969tX79etWuXVvNmjVTYGCgiZcD2D0mOw27P9lps9lMRwGAFFGqVCnNmDFDRYoU0d69e1WqVCmFhIRIkq5evaqNGzeqbdu26tatmz788ENFRUWZDQzgbwUFBSkgICDx7zIAAHiyLl26aPXq1bp27ZrCwsKUJUsWNWnSRJK0Y8cOvfHGG2rYsKHWrl2rffv2afTo0Q8c4NmtWzf98ssv6tChg44eParKlSsrNDT0kde6X5L+uWeIi4tLwVcH2BfKTsNcXV3l5ubGLx4ADq1OnTr66KOPtG7dOs2fP185c+ZUWFiYatSooZdfflnnzp3TtWvXNHPmTLVp08Z0XAB/w2KxaO7cuQoLC9N3331nOg4AAHbvtddek5eXlxYvXqz58+erffv2cnd3lyR99913yp8/v4YNG6YKFSqoUKFCjzy93d/fX926ddPKlSs1cuRIzZkz55HXypEjhyTpwoULibf9+bAiwNFRdtoBlrIDcAYJCQny8fHRuXPnVLduXb355puqUqWKIiMj9eWXX2rVqlX64YcfFBsbq4kTJ5qOC+Bv5MiRQ7Nnz1aHDh10+/Zt03EAALBr3t7eatOmjUJCQnTy5El17tw58XuFCxfW6dOntWzZMp08eVIzZ87UihUrHnh8nz59tGnTJv3yyy/at2+fNm3apOLFiz/yWj4+PipfvrwmTJigI0eOaMeOHRo0aFCKvj7AnlB22gEOKQLgDFxdXSVJ06ZN05UrV/TVV19p7ty5KlSokFxcXOTq6qoMGTKoQoUKOnTokOG0AJKiadOmql69ugYOHGg6CgAAdq9Lly66fv26qlatqmLFiiXe/uqrr6p///7q27evypYtq61bt2rUqFEPPDYhIUG9evVS8eLFVb9+feXJk0cLFix47LXCwsIUHx+v8uXLq2fPno9d8g44IouNzSKNy58/v7799lvlz5/fdBQASFFnz55VrVq11KFDBw0bNizx9PX7+wrdvn1bRYsW1fDhw9W9e3eTUQEk0Y0bN1SmTBnNnj1bDRo0MB0HAAAATo7JTjvAZCcAZ3H37l1FR0frjTfekPTfktPFxUXR0dH69NNPVbNmTWXLlk2vvvqq4aQAkipjxoxasGCBunTpoqtXr5qOAwAAACdH2WkH2LMTgLMoXLiwsmTJonHjxum3335TbGysli5dqr59+2ry5MnKkyePZs6cqZw5c5qOCuAp1KxZUy1btlSPHj3EoiEAAACYRNlpB5jsBOBMZs+ercjISAUGBipr1qyaMmWKjh8/rvr16+v9999XtWrVTEcE8A+MHTtWP//8s5YvX246CgAAAJyYm+kA+O+pbJSdAJxFlSpV9MUXX2jTpk3y9PSUJJUtW1Z58+Y1nAzAv+Ht7a1FixapQYMGql69On+nAQAAYARlpx1gGTsAZ+Pj46PmzZubjgEgmZUrV059+vRRp06dtGnTJlksFtORAAAA4GRYxm4HWMYOAAAcxZAhQ3Tjxg19+OGHpqMAAGBUXFycnn32WW3fvt10FMCpUHbaAZaxA4Bks9k42ARwAG5ublq4cKGCg4N1/Phx03EAADBm8eLFeuaZZ1S9enXTUQCnQtlpB5jsBABp1apVmjp1qukYAJJBkSJFFBISovbt2ys+Pt50HAAAUl1cXJxCQ0MVHBxsOgrgdCg77QB7dgKAVKhQIU2dOpXfh4CD6Nmzp3x9fTVhwgTTUQAASHWLFy9WQECAXnjhBdNRAKdD2WkHmOwEAKl06dKqXLmy5s6dazoKgGTg4uKi+fPna/r06dq7d6/pOAAApBqmOgGzKDvtAHt2AsB/DR8+XJMmTeJ3IuAg8ubNq/fee0/t2rXj7zUAwGksWbJE+fPnZ6oTMISy0w6wjB0A/qtcuXIqU6aMFixYYDoKgGTSpk0blShRQsOGDTMdBQCAFBcfH89UJ2AYZacdYBk7APy/ESNGaMKECYqNjTUdBUAysFgsmj17tpYvX66tW7eajgMAQIpavHix8uXLpxo1apiOAjgtyk47wDJ2APh/lStXVpEiRbRw4ULTUQAkk6xZs2ru3LkKCgrSzZs3TccBACBFMNUJ2AfKTjvAZCcAPGjEiBEaP3684uPjTUcBkEwaNmyo+vXr66233jIdBQCAFLFkyRL5+/sz1QkYRtlpB9izEwAeVL16deXLl09Lly41HQVAMpo6daq2bdumzz//3HQUAACSVXx8vMaMGcNUJ2AHKDvtAJOdAPCwESNGaOzYsUpISDAdBUAy8fHx0cKFC9W9e3ddunTJdBwAAJLNkiVLlDdvXr344oumowBOj7LTDrBnJwA8rGbNmsqWLZtWrFhhOgqAZPT888+rQ4cO6tq1q2w2m+k4AAD8a/f36gwJCTEdBYAoO+0Cy9gB4GEWi0UjR45UaGiorFar6TgAktGoUaN06tQpffLJJ6ajAADwry1dulR58uRhqhOwE5SddoBl7ADwaPXq1VP69Om1atUq01EAJCNPT08tWrRI77zzjn777TfTcQAA+Mfu79XJVCdgPyg77QDL2AHg0SwWi0aMGKHQ0FCWuwIOpnTp0ho4cKCCgoKY3gYApFlLly5V7ty5meoE7Ahlpx1gshMAHu/ll1+WxWLR2rVrTUcBkMwGDhyouLg4ffDBB6ajAADw1NirE7BPlJ12gD07AeDx7k93jhkzhulOwMG4urrqk08+0bhx43TkyBHTcQAAeCrLli1Trly5mOoE7Axlpx1gshMAnqxp06aKjo7Wxo0bTUcBkMwKFCigcePGqV27doqNjTUdBwCAJPnzXp0Wi8V0HAB/QtlpB9izEwCezMXFRcOGDWO6E3BQXbp0kZ+fn0JDQ01HAQAgSZYvXy4/Pz+mOgE7ZLHxX43G3b17V1mzZmUpOwA8QUJCgkqUKKFZs2apdu3apuMASGYXLlxQYGCgPv/8c1WqVMl0HAAAHis+Pl4lSpTQ7NmzVatWLdNxAPwFk512wMvLSzExMUwrAcATuLq6atiwYRo9erTpKABSQK5cuTRz5ky1a9dOd+/eNR0HAIDHWr58uXLmzKmaNWuajgLgEZjstBOenp66efOmPD09TUcBALsVHx+vokWLav78+XrhhRdMxwGQAtq2bavMmTNrxowZpqMAAPCQhIQEFS9eXB9++CGrjQA7xWSnneCQIgD4e25ubho6dKjGjBljOgqAFDJz5kx9/vnn2rx5s+koAAA8ZPny5cqRIwfL1wE7RtlpJ7y8vNizEwCSoF27doqKitL3339vOgqAFJApUybNmzdPnTp10vXr103HAQAgUUJCgkaPHs0J7ICdo+y0E0x2AkDSuLu7a/DgwUx3Ag6sbt26atq0qXr37m06CgAAiZjqBNIGyk474e3tTdkJAEnUsWNHHTp0SHv27DEdBUAKmThxovbs2aMVK1aYjgIAgBISEjRmzBgFBwcz1QnYOcpOO8EydgBIOk9PTw0aNIjpTsCBpUuXTosWLVKfPn104cIF03EAAE4uPDxc2bJl41AiIA2g7LQTLGMHgKfTpUsX7d69WwcOHDAdBUAKqVixorp3767OnTvLZrOZjgMAcFLs1QmkLZSddoJl7ADwdLy9vTVw4ECFhoaajgIgBQ0fPlwXL17U3LlzTUcBADgppjqBtIWy004w2QkAT69bt2769ttvdfjwYdNRAKQQd3d3LVq0SMOGDdPJkydNxwEAOBn26gTSHspOO8GenQDw9NKnT6/+/ftr7NixpqMASEHFixfXsGHD1L59eyUkJJiOAwBwIitWrFCWLFlUp04d01EAJBFlp51gshMA/plevXppy5YtOnbsmOkoAFJQ37595enpqSlTppiOAgBwEuzVCaRNlJ12gj07AeCfyZAhg/r06aNx48aZjgIgBbm4uCgsLExTpkzhYDIAQKpYsWKFMmfOzFQnkMZQdtoJlrEDwD/Xp08frV+/Xr/88ovpKABSUL58+TRlyhS1a9dOMTExpuMAABzY/b06meoE0h7KTjvBMnYA+OcyZcqknj17avz48aajAEhh7du3V4ECBTRy5EjTUQAADmzlypXKlCmT6tatazoKgKdE2WknWMYOAP/OW2+9pVWrVum3334zHQVACrJYLJozZ44WLlyoHTt2mI4DAHBA7NUJpG2UnXaCyU4A+HeyZMmiN998UxMnTjQdBUAKy549u/7zn/+oQ4cOunXrluk4AAAHs3LlSmXMmJGpTiCNouy0E+zZCQD/3oABA7R8+XKdO3fOdBQAKaxJkyZ68cUX9fbbb5uOAgBwIOzVCaR9lJ12gslOAPj3cuTIoY4dO2ry5MmmowBIBe+99542b96s9evXm44CAHAQERER8vX1Vb169UxHAfAPUXbaCfbsBIDkMXDgQC1cuFC///676SgAUpivr6/CwsLUtWtXXblyxXQcAEAaZ7Va2asTcACUnXaCZewAkDxy5cqlN954Q1OnTjUdBUAqqFGjhlq3bq3u3bvLZrOZjgMASMMiIiKUIUMGpjqBNI6y006wjB0Aks+7776refPm6fLly6ajAEgFoaGhioyM1NKlS01HAQCkUVarVaNGjWKqE3AAlJ12gmXsAJB88ubNqxYtWui9994zHQVAKvDy8tLixYvVv39/nTlzxnQcAEAadH+qs379+qajAPiXKDvtBJOdAJC8Bg8erP/85z+6du2a6SgAUkFgYKD69eunjh07ymq1mo4DAEhD7u/VGRwczFQn4AAoO+0Ee3YCQPIKCAhQ06ZNNX36dNNRAKSSd999V3fu3NGsWbNMRwEApCGffvqp0qdPr5deesl0FADJwGJjJ3e7sHfvXnXp0kV79+41HQUAHMaJEydUuXJlnTx5UhkzZjQdB0AqiIqKUpUqVbRjxw4VLVrUdBwAgJ2zWq0qXbq0Jk+erAYNGpiOAyAZMNlpJwoUKMD0EQAks4IFC6pBgwaaOXOm6SgAUkmhQoU0evRotW/fXvHx8abjAADsHFOdgONhshMA4NDOnj2rixcv6rnnnmMPJsBJ2Gw2vfTSS3r++ec1cuRI03EAAHbq/lTnpEmT1LBhQ9NxACQTyk4AAAA4nHPnzikwMFAbNmxQ+fLlTccBANihiIgITZo0ST/88ANvigMOhGXsAAAAcDh58uTRBx98oHbt2nEIJADgIVarVaNGjVJISAhFJ+BgKDsBAADgkFq3bq0yZcpo6NChpqMAAOzMqlWr5O3tzaFEgANiGTsAAAAc1tWrV1WmTBktWrRINWvWNB0HAGAHrFarypYtq/Hjx6tRo0am4wBIZkx2AgAAwGFlzZpVc+fOVVBQkG7cuGE6DgDADnz22Wfy9PTkUCLAQTHZCQAAAIfXo0cP3bt3T2FhYaajAAAMYqoTcHxMdgIAAMDhTZ48WTt27NBnn31mOgoAwCCmOgHHx2QnAAAAnMLOnTvVrFkzHThwQDlz5jQdBwCQyqxWqwIDAzV27Fi9/PLLpuMASCFMdgIAAMApVK1aVZ06ddKbb74p3u8HAOezevVqubu7s3wdcHCUnQAAAHAaISEhOn36tBYsWGA6CgAgFVmtVo0aNUohISGyWCym4wBIQZSdaUhCQoLpCAAAAGmah4eHFi1apHfffVenTp0yHQcAkEqY6gScB2VnGhIZGamXX35ZP/74o+koAOCw7t69q+XLl+v06dOmowBIIaVKldKgQYMUFBTEm8kA4ASsVqtGjx6t4OBgpjoBJ0DZmYYULlxYDRs21GuvvaYGDRro+++/Nx0JAByOt7e3Tp48qcDAQA0aNEjXr183HQlAChgwYIBsNpvef/9901EAACns888/l6urK4cSAU6CsjMN8fDwUM+ePRUVFaWmTZuqdevWqlu3rnbs2GE6GgA4DIvFomHDhungwYO6fv26ihQpomnTpikmJsZ0NADJyNXVVWFhYZowYYJ+/vln03EAACmEvToB50PZmQZ5enqqW7duOn78uFq1aqX27durVq1a2rp1q+loAOAw8uTJo7lz52rr1q3aunWrihYtqiVLlshqtZqOBiCZPPvssxo/frzatWun2NhY03EAAClgzZo1THUCTsZis9lspkPg34mLi9OSJUs0duxY5c6dWyNHjlStWrV41woA/obNZkvy78pt27bpnXfeUXx8vCZNmqQ6deqkcDoAqcFms6lJkyYqU6aMQkNDTccBACQjm82m5557TqNGjVKTJk1MxwGQSpjsdADu7u4KCgpSZGSk3nzzTfXq1UvVqlXTl19+KbpsAHi0xYsX6/Lly0m+f40aNfTDDz9oyJAh6t69u1566SUdOHAgBRMCSA0Wi0Vz587Vxx9/zH7oAOBgPv/8c1ksFjVu3Nh0FACpiLLTgbi5ualt27Y6fPiwevfurbfeektVqlTRhg0bKD0B4C/CwsK0d+/ep3qMxWLR66+/riNHjqhRo0aqX7++OnTowMntQBrn5+enWbNmqX379rpz547pOACAZGCz2TRq1ChOYAecEGWnA3J1dVXr1q116NAhDRgwQO+++64qVqyotWvXUnoCwP8ULlxYUVFR/+ixHh4e6tOnj44fPy5/f38FBgbq3Xff1R9//JHMKQGklubNm6tKlSoaNGiQ6SgAgGSwZs0aSWL5OuCEKDsdmKurq1q0aKEDBw5o8ODBGj58uMqVK6fPPvuMAzYAOL1ChQr947LzPl9fX4WGhurgwYO6du2aChcuzMntQBo2ffp0rV27Vps2bTIdBQDwL9hsNoWEhHACO+CkKDudgIuLi5o3b659+/YpODhYoaGhCgwMVEREBKUnAKeVHGXnffdPbv/mm2/0zTffcHI7kEZlypRJCxYsUOfOnXXt2jXTcQAA/xBTnYBz4zR2J2Sz2bR+/XqNHj1ad+/e1YgRI/Taa6/J1dXVdDQASDXHjh1To0aNdOLEiWR/7j+f3D558mTVrl072a8BIOX069dPly5d0rJly0xHAQA8JZvNpnLlymnkyJFq2rSp6TgADKDsdGI2m02bNm3SqFGjdOPGDQ0fPlwtW7ak9ATgFGJjY+Xr66tbt27J3d092Z/fZrMpIiJCQ4YMUaFChTRx4kSVLl062a8DIPndu3dPzz33nIKDg9WqVSvTcQAAT2HNmjUKDg7W3r17WcIOOCmWsTsxi8Wil156STt37tQHH3ygDz/8UMWLF9fChQsVHx9vOh4ApCgPDw/lyZNHp06dSpHn//PJ7Q0bNlTdunUVFBTEye1AGuDt7a2FCxeqX79+On/+vOk4AIAkur9XJyewA86NshOyWCyqW7eutm/frtmzZ2v+/PkqWrSoFixYoLi4ONPxACDFFCpUSMePH0/Ra9w/uT0qKkp58+bl5HYgjahQoYJ69OihTp06iYVQAJA2rF27VjabTa+88orpKAAMYhk7kiQ2NlYeHh6mYwCAw8iRI4cGDx6sXr16ydPT03QcAI8QFxenqlWrqnPnzurevbvpOACAJ7DZbCpfvryGDx+uV1991XQcAAYx2YkkKVSokD766CPFxMSYjgIADuHPJ7cvXbqUk9sBO+Tu7q5FixZpxIgRioqKMh0HAPAE69atU0JCAlOdACg7kTTh4eFas2aNChYsqJkzZyo6Otp0JABI00qUKKG1a9cqLCxM77//vipUqKCvvvrKdCwAf1G0aFGNGDFCHTp0YE9zALBTNptN48aNU3BwsFxcqDkAZ8cydjyV3bt3a8yYMfrpp580aNAgde3aVd7e3qZjAUCaZrPZtHLlSg0ZMkSFCxfm5HbAzlitVtWtW1d16tTRkCFDTMcBAPyFzWaT1WqVxWKh7ATAZCeeToUKFbRmzRqtXbtWW7duVYECBTRt2jTduXPHdDQASLMsFotatGihyMjIB05uP3PmjOloACS5uLhowYIFeu+997R//37TcQAAf2GxWOTq6krRCUASZedTsVgsioiI+FfPERYWJh8fn2RKZM5zzz2nzz77TBs2bNDOnTtVoEABTZo0Sbdv3zYdDYADCwgI0JQpU1L8OqZ+V//15PayZctycjtgJ/Lly6epU6eqXbt2bOcDAABgxyg79d8S80kfQUFBkqQLFy6ocePG/+paLVu21C+//JIMqe1D2bJlFRERoS1btmjv3r0qUKCAxo8fr5s3b5qOBiCNCQoKSvy96+bmpnz58qlHjx66fv164n12796tnj17pngW07+rfX19FRoaqoMHD+rq1asqXLiw3nvvPQ6JAwxr+3/s3Xlczfn3B/DXbdGubE2EKEWSsRvGVjGWYQxmRqRN0jAUyZa1iEEjMZasmezLGIOxJsaWbCWVSkRiEAZJ2u7vD7/uV2On2/t27+v5ePQY3fu59/O6Dbd7zz3v9xk0CFZWVpgyZYroKERERET0BtyzE8A///wj+/Pu3bvh6emJ27dvyy7T0dGBoaGhiGhykZeXhwoVKsjlvhMTExEUFIQDBw7Ax8cHI0eOVKqfHRHJj5ubGzIzMxEREYGCggIkJiZi8ODBaN++PTZu3Cg6nlAJCQmYMGECLl26hKCgIDg6OnKZFpEg9+7dw+eff45NmzahQ4cOouMQERER0X/wnRIAExMT2ZeRkdErlxUX615exp6eng6JRIJNmzahY8eO0NHRQdOmTXHx4kVcunQJbdu2hZ6eHtq1a4dr167JzvXfpZEZGRno3bs3KleuDF1dXTRo0ACbNm2SXR8fH4/OnTtDR0cHlStXhpubGx49eiS7/syZM/jqq69QtWpVVKxYEe3atcOpU6dKPD6JRILFixejb9++0NPTg7+/PwoLC+Hh4YG6detCR0cHlpaWmDt3LoqKij7pZ9mwYUOsX78ex48fR2pqKurVq4eAgIASnVlERG+ipaUFExMT1KxZE1999RX69++PAwcOyK7/7zJ2iUSCpUuXonfv3tDV1YWVlRWioqJw8+ZNdO3aFXp6emjSpAnOnz8vu03x83BkZCQaNWoEPT092NnZvfW5GgD27NmD1q1bQ0dHB1WqVEGvXr1kS1lft7y+U6dOGDFiRKn8XDi5nUhxVKtWDWFhYXBzc8OTJ09ExyEiUjns1yKid2Gx8xNNmzYN48ePx4ULF2BkZISBAwdi5MiRCAoKQkxMDHJzc+Ht7f3G2w8fPhw5OTmIiopCQkICFixYICu45uTkoFu3btDX10dMTAx27NiBkydPYvDgwbLbP3nyBM7Ozjh27BhiYmLQpEkT9OjRA1lZWSXOExAQgB49eiA+Ph4//fQTioqKYGpqii1btiApKQlBQUGYNWsW1qxZUyo/l/r162Pt2rU4deoUrl+/DktLS0yZMgX3798vlfsnIuV39epV7Nu3D5qamm89bubMmXB0dERcXBxatGiBAQMGwMPDA8OHD8eFCxdQo0YN2XYkxZ4/f47Zs2dj9erVOHXqFP7991/8+OOPbzzHvn370Lt3b3Tp0gXnzp1DVFQUOnbs+MkfEH2ojh074vTp0xg/fjyGDh2K7t274+LFi2WagYiAXr16wd7eHqNHjxYdhYhIJbxc4JRIJABQ5q/DiKgckVIJW7dulb7pxwJAunXrVqlUKpVeu3ZNCkC6bNky2fW7du2SApBu375ddtmaNWukenp6b/ze1tZWOn369Neeb/nyX1GpiAAAIABJREFU5dKKFStKHz9+LLssKipKCkCampr62tsUFRVJTUxMpBERESVyjxgx4m0PWyqVSqXjx4+XOjg4vPO4j5GWliYdMmSItHLlytKJEydK7927J5fzEFH55erqKlVXV5fq6elJtbW1pQCkAKTz58+XHWNmZiadN2+e7HsA0gkTJsi+j4+PlwKQ/vLLL7LLip83i5931qxZIwUgvXz5suyYdevWSTU1NaWFhYWyY15+rm7btq20f//+b8z+31xSqVTasWNH6U8//fShP4b39vz5c+nChQulxsbGUjc3N+mNGzfkdi4ietXjx4+ldevWlf7555+ioxARKb3c3Fzp8ePHpZ6entIpU6ZIc3JyREciIgXGzs5P1LhxY9mfP/vsMwCAra1ticuePn2KnJyc197ex8cHM2fORJs2bTB58mScO3dOdl1SUhIaN24MAwMD2WVt27aFmpoaEhMTAQB3796Fl5cXrKysYGhoCAMDA9y9exc3btwocZ4WLVq8cu5ly5ahRYsWqFatGvT19RESEvLK7UqLubk5VqxYgfPnz+PBgwewsrLCuHHjcPfuXbmcj4jKpw4dOiA2NhYxMTEYOXIkevTo8dbueOD9nocBlHi+0dLSQv369WXf16hRA/n5+W+cen7hwgU4ODh8+AOSo+LJ7SkpKahRowaaNGmCCRMmcHI7URkxMDDA2rVr4eXlhXv37omOQ0Sk1IKCgjBs2DBcvHgR69evR/369Uu8dyYiehmLnZ/o5eWVxe30r7vsTS32Hh4euHbtGtzd3ZGSkoK2bdti+vTpAF606hff/r+KL3d1dcWZM2cQEhKCkydPIjY2FjVr1kReXl6J4/X09Ep8v3nzZowaNQpubm7Yv38/YmNjMXz48FduV9rMzMywbNkyxMXFIScnBw0aNMCYMWNKDIkiItWlq6uLevXqwdbWFgsXLkROTg5mzJjx1tt8zPOwhoZGifv41OVQampqr+wflZ+f/1H39aEMDQ0RFBSEixcvIisri5PbicpQ+/btMWjQIHh5eXEPOSIiObl9+zbmz5+PkJAQ7N+/HydPnkStWrVkAywLCgoAcC9PIvofFjsVQM2aNTF06FBs2bIFgYGBWL58OYAXw37i4uJKbH5/8uRJFBUVwdraGgBw/PhxjBw5El9//TVsbGxgYGBQYpL8mxw/fhytW7fGiBEj0KxZM9SrVw9paWnyeYCvUatWLfz666+Ij49HQUEBGjZsiFGjRuHWrVtlloGIFN+0adMwZ84c4c8NTZs2fetAoGrVqpV47s3NzcXly5fLIpqMqakpVq5ciaioKBw+fBgNGjTAhg0buJ8VkZwFBgYiNTUV69atEx2FiEgphYSEwMHBAQ4ODjA0NMRnn32GsWPHYtu2bXjy5InsQ+ywsDDuZU5EAFjsFM7Hxwf79u3D1atXERsbi3379qFhw4YAACcnJ+jp6cHFxQXx8fH4+++/4eXlhb59+6JevXoAACsrK6xbtw6JiYk4c+YMHB0dUaFChXee18rKCufPn8fevXuRmpqKGTNm4OjRo3J9rK9jamqK0NBQJCQkQF1dHY0aNcKIESNw8+bNMs9CRIqnU6dOsLGxwcyZM4XmmDRpErZu3YrJkycjMTERCQkJCAkJkW1RYm9vj/Xr1+PIkSNISEjA4MGDy6yz87+KJ7evWbNGNrn98OHDQrIQqQJtbW1ERERgzJgxctsOiIhIVeXl5SEzMxOWlpYoLCwEABQWFsLe3h5aWlrYsWMHACA1NRXDhw8vsQUcEakuFjsFKyoqwsiRI9GwYUN06dIFn332GdauXQvgxXLO/fv34/Hjx2jVqhV69+6NNm3aYPXq1bLbr169GtnZ2WjevDkcHR0xePBg1KlT553n9fLywg8//ICBAweiZcuWSE9Px5gxY+T1MN+pevXq+OWXX3D58mXo6uqicePGGDZsGK5fvy4sExEpBl9fX6xatUro80GPHj2wY8cO7N27F02bNkXHjh0RFRUFNbUXv0YnTpwIe3t79O7dG1999RXatWuHZs2aCcsLvCgUF09u9/T05OR2Ijlq0qQJRo8eDXd3d3ZTExGVogoVKsDR0RH16tWDuro6AEBdXR0VK1bEl19+iV27dgEA/P398c0336Bu3boi4xKRgpBIubEFKaB79+5h/vz5WL58Ofr27Qt/f//3+sVVWFiIxMRE1K5dG4aGhmWQlIhI8eXl5SEsLAwzZ85Ejx49EBgYiFq1aomORaRUCgoK0KFDB/Tv3x8+Pj6i4xARKY3i1TKampol5lpERUXBy8sLW7duRfPmzZGcnAwLCwuRUYlIQbCzkxRStWrVMHv2bKSkpMDExAQtWrTA4MGD8fDhw7feLjExEfPmzUP79u3h6en5zuOJiFQBJ7cTyZ+GhgZ+++03zJgxA0lJSaLjEBGVe8WvUzQ1NV8pdObl5aFNmzaoXLkyWrVqhb59+7LQSUQyLHaSQqtSpQpmzJiBK1euoHbt2tDX13/r8TVr1oSjoyN++uknrFq1CiEhIcjNzS2jtEREio2T24nkq169epg5cyZcXFyE7dtLRKQMHjx4gGHDhuG3335Deno6AMgKncCLD3K1tbVhY2OD/Px8zJs3T1BSIlJELHZSuVCpUiVMnz5dNmnvbcf16NEDDx48gIWFBbp16wZtbW3Z9XzjQUT0v8nthw8fRmRkJKytrTm5naiUeHl5oWrVqggKChIdhYio3FqzZg02b96MBQsWYOzYsVi/fj0yMjIAvJi6XjysaPbs2fjzzz9hZmYmMi4RKRju2UlK4+VlDdWrV4ezszOmTp0q6wa9ceMGtm7dipycHDg7O7/XICciIlVw5MgRjBs3DoWFhZg3bx7s7e1FRyIq127duoWmTZti9+7daNmypeg4RETlzsmTJ+Hj4wMXFxfs3LkTly9fhoODA9TV1bF9+3bcvHmTk9eJ6I3Y2UlKo/jTvXnz5kFdXR19+vQpsez9wYMHuHv3Lk6dOgVzc3PMnz+fXUxERHh1cnuPHj0QHx8vOhZRuVWjRg0sXLgQzs7OyMnJER2HiKjcadu2Lb744gs8e/YMhw4dQmhoKG7cuIF169bB3Nwce/fuRVpamuiYRKSgWOwkpVG8xH3BggXo378/GjVqVOL6Jk2aICgoCNOnTwcAVKxYsawjEpECW716NVxcXETHEEYikeCHH35AUlISunXrhs6dO8Pd3V22ZIyIPkz//v3RrFkzTJw4UXQUIqJyydfXF/v27UNGRgb69esHNzc3GBgYQFdXF6NHj8aYMWP4gRIRvRaLnaQUijs0Q0JCIJVK0bdv31eWNRQWFkJDQwMrVqxA48aN0bt3b6iplfwn8OzZszLLTESKxcrKCqmpqaJjCFehQgV4e3tzcjtRKfj111+xfft2REZGio5CRFSuFBYWom7duqhevTqmTZsGAJg4cSJmzZqFEydOYP78+fjiiy+gq6srOCkRKSLu2UnlmlQqRWRkJPT09NCmTRuYmZmhT58+mDFjBgwMDErs4wm82LezXr16WLZsGQYPHiy7D4lEgmvXrmHVqlXIy8uDi4vLK52hRKTc7ty5AxsbG2RlZYmOolAyMzMxbdo0/Pnnn5g4cSKGDx8OLS0t0bGIyo39+/fD09MTFy9ehJGRkeg4REQK7+X3cMnJyfD19UWNGjWwe/duxMXFwdjYWHBCIlJ07Oykcq242Pnll1/CwsICjx8/Rr9+/WRdncW/JIs7P4OCgmBlZYWePXvK7qP4mAcPHkAikSApKQmNGzfmFFUiFWNsbIy8vDw8fPhQdBSF8rrJ7Rs3buSex0TvqWvXrujVqxe8vb1FRyEiUmjFq+xefg9Xv359fPHFFwgPD4e/v7+s0MnXIUT0Nix2UrmmpqaG2bNnIyUlBZ06dcKjR48wceJEXLhwocQvQDU1NWRmZiI8PBw+Pj6v/TSwefPmmDp1Knx8fAAANjY2ZfY4iEg8iUQCS0tLLmV/g0aNGmH37t1YvXo15s+fj1atWuHw4cOiYxGVC3PnzkV0dDS2b98uOgoRkUJ69OgRAgICcOTIETx69AgAZFuOeXh4YOXKlbK91aVS6SvbkRERvYzL2EmppKenY9y4cdDT08OKFSvw9OlT6OrqQlNTE8OHD0dUVBSioqJgYmJS4nYvL5UYNGgQkpOTcebMGREPgYgEcnR0RK9eveDk5CQ6ikIrKirC1q1b4e/vj/r162POnDmwtbUVHYtIoUVHR+Pbb79FbGzsK69DiIhU3bBhwxAWFobatWujV69e+OGHH9C4cWMYGhqWOO758+fcToeI3okfh5BSqVOnDrZs2YKlS5dCXV0dQUFBsLOzw+bNmxEREQFfX9/XvsEoLnSeO3cOW7Zsgb+/f1lHJyIFYGlpiZSUFNExFJ6amhr69+/Pye1EH+CLL77AkCFD4OnpCfYaEBH9z5MnTxAdHY1ly5ZhzJgx2LlzJ77//ntMnjwZR48elW0xdOnSJQwdOhRPnz4VnJiIFB2LnaSUtLW1IZFI4Ofnh2rVqmHQoEF4+vQpdHR0UFhY+NrbFBUVITQ0FDY2NujTp08ZJyYiRcBl7B/mdZPbJ06cyMntRG8wdepUZGVl4c6dO6KjEBEpjIyMDDRr1gwmJiYYOXIkbty4gSlTpuDPP//EDz/8gKlTp+Lvv/+Gj48PHj58CD09PdGRiUjBcRk7qYT79+9j0qRJWL58OUaMGIHAwMBXJqLGxsaidevWWL9+Pb777jtBSYlIpOjoaIwcOZLbWHykmzdvYtq0adi1axf8/f0xbNgwLjUj+o+ioiJIJBLZqhIiIlVXVFSE1NRUfPbZZ6+8R1u8eDGCg4Px77//4tGjR0hOToalpaWgpERUXrDYSSolKysLMTEx6Nq1K9TV1XHr1i0YGxtDQ0MD7u7uOHfuHOLi4vgGhEhF3b9/HxYWFnj48CGfBz7BpUuXMGHCBCQmJiIoKAj9+/fnIAEiIiJ6bwUFBdDQ0JB9XzyVfe3atQJTEVF5wWInqaxHjx5h7NixOHv2LJycnDB9+nSsWbOGXZ1EKq5y5cpITk5GtWrVREcp944cOYKxY8dCKpVi7ty5sLe3Fx2JSOHl5eUhNDQU5ubm6Nevn+g4RERCFRUV4cyZM2jTpg2SkpJQv3590ZGIqBxgmwWpLENDQ8yfPx/NmjXD1KlT8fTpU+Tn5+PZs2dvvI1UKkVRUVEZpiSissZ9O0tPp06dcPr0aYwdOxaenp7o0aMH4uPj3+u2/CyWVFVGRgZSU1MxZcoU7NmzR3QcIiKh1NTUkJ2djfHjx7PQSUTvjcVOUmn6+vpYuXIlsrKyMHbsWDg5OWHixInIzs5+5VipVIrTp0/D1tYWGzdufOOgIyIq31jsLF2vm9w+ePDgd05Szc/Px8OHDxETE1NGSYnEk0qlsLCwQGhoKNzc3ODp6Ynnz5+LjkVEJHdSqfSNH3Ta29sjKCiojBMRUXnGYicRAB0dHcyZMwc5OTlwcnKCjo7OK8dIJBK0bt0a8+fPx6JFi2BjY4N169ahoKBAQGIikhdLS0ukpKSIjqF0Xp7cbm5u/trn2ZcNHz4c7du3h5eXF+rUqYM1a9aUUVKisieVSku8ntDW1sbYsWNhbm6OpUuXCkxGRFQ2oqKi8Ndff7224CmRSLj3NxF9ED5jEL1EW1sbLVu2hLq6+muvl0gk6Nq1K06cOIHFixdj+fLlaNiwIdauXcuiJ5GSYGenfBkaGmLy5MlvHQC1ZMkSbNy4EcOHD8eWLVswdepUBAUFYe/evQC4xJ2UQ1FREW7duoXCwkJIJBJoaGjI/l0UT2vPycmBgYGB4KRERPIllUoxdepU/PvvvxwQSUSlQuPdhxDRf0kkEjg4OMDBwQFHjhxBYGAgAgMD4e/vDxcXF2hqaoqOSEQfycrKisXOMvC2NzPLli3DkCFDMHz4cAAvCtBnz57FihUr0K1bN0gkEiQnJ3PvLiq38vPzYWZmhjt37qB9+/bQ09NDixYt0LRpU5iamqJy5cqIiIhAbGwsTE1NRcclIpKrw4cP4969e3B0dBQdhYiUBDs7iT5Rp06dcPjwYYSHh2PTpk2wsrLC8uXLkZeXJzoaEX0ES0tLXLlyhd2DguTl5cHCwkK2p2fx/wepVCrrfIuPj4e1tTV69uyJjIwMkXGJPoqmpiZ8fX0hlUoxcuRINGrUCH///TdmzJiBnj17olWrVli5ciUWLVqEbt26iY5LRCQ3UqkU06dPx9SpU9+4uo6I6EOx2ElUStq3b4+DBw9i/fr12LFjB+rVq4clS5ZwsABROWNoaAgdHR38888/oqOopAoVKqBjx47Ytm0btm/fDolEgj179uDEiRMwNDREYWEhbG1tkZaWhooVK8LMzAweHh549uyZ6OhEH8TPzw+NGjVCZGQk5syZg8OHD+PcuXNITk7GoUOHkJaWBi8vL9nxmZmZyMzMFJiYiKj0HT58GHfv3mVXJxGVKhY7iUpZ27ZtsXfvXmzduhV//fUXLCwssGjRIuTm5oqORkTvift2ilHcxTlq1Cj8/PPP8PLyQuvWreHj44NLly7B3t4e6urqKCgoQN26dbFhwwacPXsWqampMDIyQkREhOBHQPRh/vzzT6xatQo7d+6ERCJBYWEhjIyM0LRpU2hpaUFD48WOU1lZWVi7di0mTJjAgicRKY3irs4pU6awq5OIShWLnURy0rp1a+zevRs7d+7EoUOHYGFhgQULFiAnJ0d0NCJ6BxY7y15BQQEiIyNx+/ZtAMCPP/6IrKwsDBs2DI0aNUKbNm0wYMAAAJAVPAGgevXqcHBwQH5+PuLj49lNT+VKnTp1MGvWLLi5uSE7O/uNb/arVq2Kli1bIicnB/379y/jlERE8hEVFcWuTiKSCxY7ieSsefPm2LlzJ3bv3o1jx47BwsICwcHBsv3oiEjxsNhZ9u7fv4+NGzciMDAQjx8/xqNHj1BYWIgdO3YgIyMD48ePB/BiT8/iydUPHjxA3759sXr1aqxevRpz586FlpaW4EdC9GHGjBmD0aNH4/Lly6+9vrCwEADQuXNn6Ovr4+TJk4iMjCzLiEREpe7lrs7iLnYiotLCYidRGWnatCm2b9+O/fv3IyYmBubm5pgzZw6ePHkiOhoR/YelpSVSUlJEx1Apn332GYYNG4YTJ06gYcOG+Pbbb1GjRg1cvXoVU6dOxTfffAMAsjdEO3fuRPfu3XH//n2EhYXBzc1NYHqiTzN58mS0aNGixGXF2zqoq6sjNjYWzZo1w/79+7Fs2TI0bdpUREwiolITFRWFO3fusKuTiORCIuW4WSIhEhISEBQUhEOHDmHUqFEYMWIEKlasKDoWEQG4cOECXFxcEB8fLzqKStqzZw/S0tJgbW2N5s2bo3LlyrLr8vLysH//fnh4eMDW1hZhYWGoV68egBfFIYlEIio20SdLTU2FoaEhjI2NZZfNmTMHU6ZMgYODA2bPno3GjRtDTY39CkRUfkmlUnTq1AlDhgyBs7Oz6DhEpIRY7CQS7PLlywgKCsK+ffvg7e2NkSNHwsjISHQsIpWWnZ0NY2NjZGdns6ggWFFRUYn/B5MnT0ZYWBh69uyJ6dOnw8zM7JVjiMqrhQsXYsuWLTh+/DjS09Ph4uKC8+fPY9q0afDw8ChR+OffeyIqr6KiouDl5YXExEQuYSciuWCxk0hBpKamIigoCLt378ZPP/0EHx+fEm9qiKhs1ahRA6dPn0atWrVERyEAGRkZGD16NPbv34+hQ4fil19+ER2JqNQVFBTAyMgIbdq0wZkzZ9CoUSPMnTsXrVu3fuPwomfPnkFHR6eMkxIRfRx2dRJRWeDHwUQKwtLSEuHh4Th9+jQyMzNhZWWFyZMn4/79+6KjEakkDilSLMbGxjAxMcHKlSvx888/A/jf4Jb/kkqlb7yOSJFpaGhg165diIyMRK9evfDHH3+gbdu2ry10ZmdnY+nSpQgNDRWQlIjo4xw5cgS3bt3CgAEDREchIiXGYieRgrGwsMDKlStx5swZ3Lt3D1ZWVpgwYQLu3bsnOhqRSmGxU7FoaWnh119/Rf/+/aGpqQkAb+x0A4BOnTohNDQUz58/L6uIRKXCzs4OQ4cOxbFjx966vFNfXx9aWlrYtWsXvL29yzAhEdHHCwgI4AR2IpI7FjuJFFTdunURFhaGCxcu4PHjx6hfvz7Gjh2LO3fuiI5GpBJY7Cy/JBIJlixZggMHDsDa2hqbNm1CUVGR6FhE723ZsmUwNTXFkSNH3nrcgAED0KtXL/z666/vPJaISLQjR44gMzMTAwcOFB2FiJQci51ECq527dpYsmQJLl68iOfPn8Pa2hqjR4/G7du3RUcjUmqWlpZISUkRHYM+kq2tLfbs2YNVq1YhODgYrVu3RlRUlOhYRO+teAn7mzx69AihoaEICgpCly5dYGFhUYbpiIg+3PTp09nVSURlgsVOonKiZs2aWLhwIRISEgAANjY28Pb2RmZmpuBkRMqJnZ3Kwc7ODjExMRgzZgw8PDzw9ddf49KlS6JjEb1TtWrVYGxsjJycHOTm5pa4Li4uDt9++y0CAwMxc+ZM7N+/n8PUiEihsauTiMoSi51E5Uz16tUREhKCxMREVKhQAba2tvjpp59w48YN0dGIlEq9evWQnp7OQTdKQE1NDY6OjkhKSsJXX30FBwcHDB48GDdv3hQdjeidIiIiMHPmTEilUuTm5uLXX39Fhw4d8Pz5c8TExMDHx0d0RCKidwoICMDkyZPZ1UlEZYLFTqJyysTEBMHBwbh8+TIMDAzQtGlTeHl5IT09XXQ0IqWgo6ODatWq8YMEJaKlpQUfHx+kpKTAxMQEn3/+Ofz9/fHo0SPR0YjeyM7ODrNmzUJwcDCcnJwwevRo+Pr64tixY2jUqJHoeERE73TkyBFkZGTAyclJdBQiUhEsdhKVc8bGxvj555+RnJyMqlWronnz5hgyZAiuXr0qOhpRucel7MrJ0NAQs2bNQlxcHP755x9YWVkhNDQUeXl5oqMRvcLKygrBwcEYP348EhMTcfz4cUybNg3q6uqioxERvRdOYCeissZiJ5GSqFq1KoKCgpCamgpTU1O0atUK7u7uLNQQfQIWO5VbzZo1sXr1ahw6dEg2uX3z5s2c3E4Kx9fXF507d0bt2rXRunVr0XGIiN7b0aNH2dVJRGWOxU4iJVO5cmUEBATgypUrqFu3Ltq2bQsXFxckJyeLjkZU7rDYqRqKJ7evXLkS8+bN4+R2Ukhr1qxBZGQk9uzZIzoKEdF7416dRCQCi51ESsrIyAhTp05FWloaGjRogPbt22PgwIFITEwUHY2o3LC0tERKSoroGFRGOLmdFJmpqSlOnToFMzMz0VGIiN7L0aNHcePGDQwaNEh0FCJSMSx2Eim5ihUrwt/fH2lpafj8889hZ2eH/v37Iz4+XnQ0IoXHzk7V8/Lk9i5dusDe3h4eHh6c3E4KoWXLlq8dSiSVSgWkISJ6u4CAAEyaNIldnURU5ljsJFIRBgYGGD9+PNLS0tCyZUt06dIF/fr1Q2xsrOhoRArL3NwcGRkZyM/PFx2FypiWlhZGjRqFlJQUGBsbc3I7KSypVIqjR4/i+vXroqMQEcn8/fffuH79Ors6iUgIFjuJVIy+vj78/Pxw9epVtGvXDj169MC3336Lc+fOiY5GpHC0tLRQo0YNpKeni45CghgZGWH27Nmc3E4KSyKR4PTp03Bzc+NwLSJSGMV7dWpqaoqOQkQqiMVOIhWlq6uL0aNHIy0tDQ4ODujduzd69eqFmJgY0dGIFAqXshPAye2k2Pz8/JCfn4+FCxeKjkJEhL///hvp6ens6iQiYVjsJFJxOjo6GDlyJK5cuYLu3bvju+++Q/fu3XHq1CnR0YgUAoud9LLiye0rVqyQTW4/cuSI6Fik4tTV1bF27VoEBQVxECERCVe8Vye7OolIFBY7iQgAoK2tjeHDhyM1NRV9+vTBgAED8NVXX+H48eOioxEJxWInvY69vT1iYmLg6+uLwYMHo2fPnpzcTkJZWFggKCgIzs7O3GeYiIQ5duwYrl27BmdnZ9FRiEiFsdhJRCVoaWlh6NChSElJQf/+/eHi4gJ7e3scPXpUdDQiIVjspDdRU1PDgAEDkJSUhM6dO8PBwYGT20koT09PmJiYYMaMGaKjEJGK4l6dRKQIWOwkoteqUKECPDw8kJycDBcXFwwZMgQdO3ZEVlYWpFKp6HhEZcbS0hIpKSmiY5ACK57cnpyczMntJJREIsHKlSsRFhaG06dPi45DRCrm+PHjuHr1Krs6iUg4FjuJ6K00NTXh5uaGpKQkeHt7w8DAABKJRHQsojJTp04d3Lp1C8+fPxcdhRRc8eT22NhY2eT2hQsXcnI7lanq1avj119/hYuLC3JyckTHISIVwr06iUhRSKRs0SKiDyCVSlnsJJVjZWWFnTt3wtraWnQUKkcuXryICRMmIDk5GbNmzcIPP/zA508qM4MGDUKlSpWwaNEi0VGISAUcP34czs7OSElJYbGTiIRjZycRfRC+USdVxH076WM0btwYf/31Fye3kxCLFi3CH3/8gYMHD4qOQkQqgHt1EpEiYbGTiIjoHVjspE9RPLl99OjRnNxOZaZSpUpYvXo1PDw88PDhQ9FxiEiJnThxAleuXIGLi4voKEREAFjsJCIieicWO+lT/Xdyu729PTw8PJCZmSk6GimxLl26oHfv3hg5cqToKESkxLhXJxEpGhY7iYiI3oHFTiotxZPbU1JSYGxsjMaNG2PSpEmc3E5yM2fOHJw5cwZbt24VHYWIlNCJEyeQmprKrk4iUigsdhIREb2DpaUlUlJSRMcgJfLy5Pbbt29zcjvJja6uLiIiIjBy5Ejcvn1bdBwiUjLFXZ0VKlQQHYWISIbT2ImIiN6hsLAQenp6ePDgAXR1dUXHISXEye0kb1OnTsW5c+ewe/du/t0iolIeaCjJAAAgAElEQVRx8uRJDBw4ECkpKSx2EpFCYWcnERHRO6irq8Pc3BxpaWmio5CSenly+9y5czm5nUrdlClT8M8//2DFihWioxCRkmBXJxEpKhY7iYiI3gP37aSyYG9vjzNnzmD06NFwd3dHz549kZCQIDoWKQFNTU1ERETA39+fH9wQ0Sc7efIkkpOT4erqKjoKEdErWOwkIiJ6Dyx2Ulkpntx++fJldO7cGXZ2dpzcTqWiYcOGmDRpElxdXVFYWCg6DhGVY+zqJCJFxmInERHRe2Cxk8ray5Pbq1WrxsntVCp8fHygqamJ4OBg0VGIqJw6deoUuzqJSKGx2ElEpSo7Oxv5+fmiYxCVOhY7SRQjIyP8/PPPiI2Nxa1btzi5nT6JmpoawsPDERwcjIsXL4qOQ0TlUEBAAPz9/dnVSUQKi8VOIipV8+fPx8GDB0XHICp1lpaWSElJER2DVFitWrWwZs0aHDx4EPv27YO1tTW2bNkCqVQqOhqVM2ZmZpg3bx4GDRqE58+fi45DROXIqVOnkJSUBDc3N9FRiIjeiMVOIipV//zzD65duyY6BlGpMzU1xaNHj/DkyRPRUUjFvTy5fc6cOZzcTh/F1dUVFhYWmDZtmugoRFSOsKuTiMoDFjuJqFRVqlQJDx8+FB2DqNSpqamhXr16uHLliugoRAA4uZ0+jUQiQVhYGNauXYvjx4+LjkNE5UB0dDSSkpLg7u4uOgoR0Vux2ElEpYrFTlJm3LeTFM3Lk9sdHBxgZ2eHIUOGcHI7vRdjY2MsW7YMrq6u7FonondiVycRlRcsdhJRqWKxk5QZi52kqLS0tDB69GikpKSgatWqnNxO7613797o2LEj/Pz8REchIgUWHR2NxMREdnUSUbnAYicRlSoWO0mZsdhJio6T2+ljLFiwAAcOHMCePXtERyEiBRUQEICJEyeyq5OIygUWO4moVLHYScqMxU4qLzi5nT5ExYoVER4ejqFDhyIrK0t0HCJSMKdPn0ZCQgK7Oomo3GCxk4hKFYudpMxY7KTypnhy+/Lly2WT248ePSo6Fimgjh07wtHREcOGDWNRnIhKKN6rU0tLS3QUIqL3IpHy1QwREdF7kUqlqFixIjIyMmBkZCQ6DtEHKSoqwubNm+Hv749GjRrh559/ho2NjehYpEByc3PRvHlz+Pv7w8nJSXQcIlIAMTEx+O6775CamspiJxGVG+zsJCIiek8SiYTdnVRuvTy53d7enpPb6RXa2tqIiIjA6NGjcfPmTdFxiEgBFO/VyUInEZUnLHYSERF9ABY7qbzj5HZ6m2bNmsHb2xvu7u4oKioSHYeIBIqJiUF8fDwGDx4sOgoR0QdhsZOIiOgDsNhJyuJ1k9sXLVrEye2ECRMm4MmTJ1iyZInoKEQkELs6iai8YrGTiIjoA7DYScrm5cnte/fuRcOGDTm5XcVpaGjgt99+w/Tp05GcnCw6DhEJEBMTg4sXL7Krk4jKJRY7iUihTJ8+HY0aNRIdg+iNWOwkZVU8uT0sLAxz5szBF198wcntKszKygoBAQFwcXFBQUGB6DhEVMYCAwPZ1UlE5RansRORjJubG7KysrB7925hGbKzs/H8+XNUqVJFWAait7l37x6srKzw4MEDSCQS0XGI5KKoqAibNm3CpEmTOLldhUmlUnTt2hXt27fHlClTRMchojJy5swZ9O3bF1euXGGxk4jKJXZ2EpFC0dfXZ6GTFFrVqlUhlUpx//590VGI5EZNTQ0DBw7k5HYVJ5FIsGbNGixatAjnzp0THYeIygj36iSi8o7FTiJ6LxKJBNu2bStxWZ06dRAcHCz7PiUlBR07doS2tjbq16+Pv/76C/r6+ggPD5cdEx8fj86dO0NHRweVK1eGm5tbiQnAXMZOik4ikXApO6mM101unzx5Mh4/fiw6GpURU1NThIaGwtnZGc+ePRMdh4jk7MyZM4iLi4OHh4foKEREH43FTiIqFUVFRejTpw80NDQQHR2N8PBwBAQE4Pnz57JjcnJy0K1bN+jr6yMmJgY7duzAyZMnufE5lTtWVlYsdpJKKZ7cfuHCBdy8eRPW1tYsfKkQR0dH2NraYtKkSaKjEJGcBQYGYsKECezqJKJyTUN0ACJSDgcPHkRycjIOHDgAU1NTAEBISAi+/PJL2THr169HdnY2IiIiYGBgAABYvnw57OzscOXKFdSrV09IdqIPxc5OUlW1a9dGeHg40tPTOa1dhUgkEixZsgSNGzdGr169YGdnJzoSEcnB2bNnceHCBWzdulV0FCKiT8LOTiIqFZcvX0aNGjVkhU4AaNmyJdTU/vc0k5SUhMaNG8sKnQDQtm1bqKmpITExsUzzEn0KFjtJ1dWpUwe6urqiY1AZqlKlClauXPnK9jNEpDyK9+rU1tYWHYWI6JOw2ElE70UikbzSxZOfny/7s1Qqfedk6rcdw6nWVJ6w2ElEqqh79+7o3r07fHx8REcholJ27tw5XLhwgXt1EpFSYLGTiN5LtWrVcPv2bdn3d+7cKfG9tbU1MjMzcevWLdllZ8+eRVFRkez7hg0bIi4uDk+ePJFddvLkSRQVFcHa2lrOj4Co9BQXO7mMl4hUTXBwMI4fP44dO3aIjkJEpSggIAATJkxgVycRKQUWO4mohMePHyM2NrbEV3p6Ouzt7bF48WLZXj5ubm4lXgx16dIF9evXh6urK+Li4hAdHQ1fX19oaGjIujadnJygp6cHFxcXxMfH4++//4aXlxf69u3L/TqpXKlUqRIqVKiAO3fuiI5CRFSm9PX1sXbtWgwfPhx3794VHYeISsG5c+dw/vx5DBkyRHQUIqJSwWInEZVw7NgxNG3atMSXn58ffvnlF5ibm6NTp0747rvvMGTIEBgbG8tup6amhh07duD58+do1aoVXF1dMWnSJEgkEllRVFdXF/v378fjx4/RqlUr9O7dG23atMHq1atFPVyij8al7ESkqr788ku4ubnB09OTHe5ESiAgIADjx49nVycRKQ2JlK9QiEhO4uLi0KRJE5w9exbNmzd/r9tMnDgRUVFRiI6OlnM6ok/j6uqKjh07YvDgwaKjEBGVuby8PLRq1Qo+Pj5wd3cXHYeIPtL58+fRq1cvpKWlsdhJREpDQ3QAIlIeO3bsgJ6eHiwtLZGeng5fX198/vnnaNas2TtvK5VKcfXqVURGRqJx48ZlkJbo07Czk+jjFRUVQU2NC4zKswoVKiAiIgL29vaws7NDnTp1REcioo/AvTqJSBnxVSYRlZonT55gxIgRaNiwIZycnGBtbY39+/e/16T1R48eoWHDhqhQoQKmTJlSBmmJPg2LnUQfLycnB8uWLUNBQYHoKPQJbG1tMW7cOLi6upYYSEhE5cP58+dx9uxZeHp6io5CRFSquIydiIjoI5w/fx7u7u6Ii4sTHYWo3Ll9+zacnJxw7949hIaGwt7eXnQk+kiFhYXo1KkT+vTpA19fX9FxiOgD9O7dGw4ODvD29hYdhYioVLHYSURE9BGePHkCExMTZGdnv1f3MpGyKigogIbGh++MJJVK8ccff2DMmDFo0qQJgoODYW5uLoeEJG9Xr15F69atceTIEdjY2IiOQ0Tv4cKFC+jZsyeuXLkCHR0d0XGIiEoVl7ETERF9BAMDAxgYGODWrVuioxAJk5qaii1btnzUEmaJRII+ffogMTERLVq0QKtWrTBp0iRkZ2fLISnJk7m5OWbPng1nZ2fk5eWJjkNE76F4AjsLnUSkjFjsJCK56N+/PzZt2iQ6BpFcWVpaIiUlRXQMIiFyc3Pxww8/4OHDh580bEhbWxv+/v6Ii4tDRkYGGjRogIiICO4BWc54eHjA1NQUgYGBoqMQ0TtcuHABZ86c4V6dRKS0WOwkIrmoVKkSHj58KDoGkVxZWVlxSBGprLFjx8Lc3BzDhw8vlfszNTXFb7/9hq1bt2LRokX48ssvERMTUyr3TfInkUiwYsUKrFy5EtHR0aLjENFbBAYGYty4cezqJCKlxWInEckFi52kCjiRnVTVjh07sHv3bqxatarU96xt06YNoqOj8eOPP+Lbb7+Fm5sbbt++XarnIPkwMTHB4sWL4eLigqdPn4qOQ0SvceHCBZw+fRpDhw4VHYWISG5Y7CQiuWCxk1QBi52kitLT0+Hl5YVNmzbByMhILudQU1ODq6srkpOTYWJiAltbW8yZMwfPnz+Xy/mo9PTr1w+tW7fGuHHjREchotcIDAzkXp1EpPQ4jZ2I5KL4qYVTqkmZXbx4EQMGDEBCQoLoKERlIj8/H+3bt8d3330HPz+/MjvvlStX4Ofnh0uXLuGXX37BN998w98vCuzff/9F48aNsWLFCnTt2lV0HCL6f7GxsejRowfS0tJY7CQipcZiJxER0UfKyclBlSpV8PTp008a0EJUXowbNw4JCQnYtWuXkL/zBw8exKhRo2BqaoqQkBDY2NiUeQZ6P5GRkXBzc0NcXBwqV64sOg4RAejbty86dOiAUaNGiY5CRCRXfGdGRET0kXR1dVGlShVkZGSIjkIkd3v37sXGjRuxdu1aYcX9Ll26IDY2Fr169YKdnR28vb3x4MEDIVno7RwcHNC3b1+MGDFCdBQiwouuzujoaHh5eYmOQkQkdyx2EhERfQJLS0ukpKSIjkEkV5mZmXB3d8f69etRtWpVoVk0NTUxcuRIJCYmoqCgANbW1li6dCkKCgqE5qJXzZ49G+fPn8fmzZtFRyFSeZzATkSqhMVOIiKiT8AhRaTsCgoKMHDgQPz000/o0KGD6DgyVatWxZIlS3Dw4EFs2bIFzZo1Q1RUlOhY9BJdXV1ERETA29sbt27dEh2HSGXFxcXh1KlT7OokIpXBPTuJiIg+QXBwMDIzMxESEiI6CpHKkkql2LFjB8aMGYNmzZohODgYdevWFR2L/t/06dNx+vRp/PXXXxwsRSRAv3790K5dO4wePVp0FCKiMsHOTiISIjc3FwsWLBAdg+iTsbOTSDyJRIK+ffsiMTERzZo1Q8uWLTF58mRkZ2eLjkYAJk2ahKysLISFhYmOQqRy4uLicPLkSXZ1EpFKYbGTiMrEf5vI8/Pz4evriydPnghKRFQ6WOwkUhw6OjqYNGkS4uLikJ6ejgYNGmDdunWv/A6isqWpqYnffvsNkydPxpUrV0THIVIpxXt16urqio5CRFRmuIydiOTi999/h42NDT777DMYGRnJLi8sLATwovhpYGCA1NRU1KxZU1RMok+Wm5sLIyMjZGdnQ0NDQ3QcInrJyZMn4ePjA01NTYSGhqJly5aiI6m00NBQbN68GceOHYO6urroOERK7+LFi+jatSvS0tJY7CQilcLOTiKSi0mTJqFp06ZwcXHB0qVLcfz4cTx8+BDq6upQV1eHhoYGtLS0cP/+fdFRiT6JtrY2TExMcP36ddFRiOg/2rZti9OnT2Po0KHo3bs33N3d8c8//4iOpbJGjhwJHR0dzJ07V3QUIpUQGBiIsWPHstBJRCqHxU4ikoujR49i0aJFyMnJwbRp0+Ds7AxHR0dMnjwZf/31FwCgcuXKuHv3ruCkRJ/O0tISKSkpomMQyU16ejokEgnOnj1b7s6tpqYGNzc3XL58GcbGxmjUqBHmzp2L58+fl3JSehc1NTWsWbMG8+fPR2xsrOg4RErt4sWLOHHiBH788UfRUYiIyhyLnUQkF8bGxvDw8MChQ4cQFxeHcePGwdDQEDt37oSnpyfatWuH9PR0PHv2THRUok/GfTtJGbi5uUEikUAikUBTUxPm5ubw8/PD06dPUatWLdy+fRtNmjQBABw5cgQSiQRZWVmlmqFTp04YMWJEicv+e+6PVbFiRcyZMwenTp3CiRMnYGNjgz///JP7eZax2rVr45dffoGzszNyc3NFxyFSWoGBgfDz82NXJxGpJBY7iUiuCgoKUL16dQwbNgxbtmzB9u3bERQUhObNm8PU1BQFBQWiIxJ9MisrKxY7SSl07twZt2/fxtWrVzFz5kwsWbIEfn5+UFdXh4mJiZB9aUv73JaWlti5cycWL16MCRMmoFu3bkhMTCyV+6b34+zsDCsrK0ydOlV0FCKlFB8fj+PHj7Ork4hUFoudRCRX/31zamVlBTc3N4SGhiIyMhKdOnUSE4yoFLGzk5SFlpYWTExMUKtWLQwcOBBOTk74448/SiwlT09Ph52dHQCgWrVqkEgkcHNzA/Bi+NzcuXNhYWEBHR0d2NraYt26dSXOERgYCDMzM9m5XFxcALzoLD169CgWL14s6zBNT0+X2xL6rl27Ii4uDl9//TU6duwIHx8fPHz4sFTPQa8nkUiwbNkyrFu3DseOHRMdh0jpFO/VqaenJzoKEZEQHBtLRHKVlZWF+Ph4JCQk4MaNG3jy5Ak0NTXRsWNH9OvXD8CLN8cSiURwUqKPx2InKSsdHR3k5+eXuKxWrVrYvn07+vXrh4SEBFSuXBk6OjoAgMmTJ2Pbtm1YvHgx6tevj1OnTsHT0xOVKlXC119/je3btyM4OBgbN26Era0t7t69i+joaAAvJnWnpKSgQYMGmDVrFoAXxdSMjAy5PT5NTU14e3tjwIABmDp1Kho0aICAgAB4enpyWricVatWDWFhYXB1dUVcXBwMDAxERyJSCvHx8Th27BjCw8NFRyEiEobFTiKSm/j4eEybNg2nTp2ClpYWjI2Noa2tjaKiIuzevRtbtmzBggULUL16ddFRiT5J3bp1kZmZiby8PFSoUEF0HKJSERMTgw0bNsDBwaHE5erq6qhcuTKAF/szV61aFQDw9OlTzJ8/HwcOHED79u0BvPi3ERMTg8WLF+Prr7/G9evXUb16dXz11VfQ1NRE7dq10aJFCwCAoaEhKlSoAF1dXZiYmJThI31ReFu6dCl+/PFH+Pj4YOnSpQgNDeXqAznr1asXdu7cCV9fX6xYsUJ0HCKlULxXJ7s6iUiVcRk7EclFZmYmxowZgytXrmDt2rWIjo7G0aNHsW/fPvz+++8ICgpCRkYGFixYIDoq0SfT1NREzZo1ce3aNdFRiD7Jvn37oK+vD21tbbRp0wYdOnTAokWL3uu2iYmJyM3NRbdu3aCvry/7Wrp0KdLS0gAA33//PXJzc1G3bl14eHhg69atCjUV/fPPP0dUVBSmTJkCNzc3fP/990hPTxcdS6nNnz8fkZGR2LVrl+goROXepUuXcOzYMQwbNkx0FCIioVjsJCK5SEpKQlpaGvbv34+vvvoKJiYm0NHRga6uLoyNjTFgwAAMGjQIBw4cEB2VqFRwKTspgw4dOiA2NhbJycnIzc3F77//DmNj4/e6bVFREQBg165diI2NlX0lJCTInutr1aqF5ORkhIWFoWLFihgzZgyaN2+Op0+fyu0xfSiJRILvvvsOSUlJ+Pzzz9GiRQtMmTJFoTIqk4oVKyI8PBxeXl64d++e6DhE5Rq7OomIXmCxk4jkQk9PD9nZ2dDV1X3jMVeuXOEeXaQ0LC0tkZKSIjoG0SfR1dVFvXr1YGZmBk1NzTceV7xdQ2Fhoeyyhg0bQktLC9evX0e9evVKfJmZmcmO09bWxtdff42QkBCcOXMGCQkJOHHihOx+X75PkXR0dDB58mTExsbi6tWraNCgATZs2ACpVCo6mtLp0KEDnJyc8OOPP/LnS/SRLl26hL///ptdnURE4J6dRCQndevWhZmZGXx8fDB+/Hioq6tDTU0NOTk5yMjIwLZt27Br1y5ERESIjkpUKqysrJCQkCA6BlGZMDMzg0QiwZ49e9CrVy/o6OjAwMAAfn5+8PPzg1QqRYcOHZCdnY3o6Gioqalh6NChCA8PR0FBAVq3bg19fX1s3rwZmpqasLS0BADUqVMHMTExSE9Ph76+vmxvUJFq1qyJ9evX48SJE/Dx8cHixYsRGhoq22uUSseMGTPQsmVLrFu3Ds7OzqLjEJU7M2bMwJgxY9jVSUQEFjuJSE5MTEwQEhICJycnHD16FBYWFigoKEBubi7y8vKgr6+PkJAQdO3aVXRUolJhaWmJP/74Q3QMojJhamqKgIAATJo0CUOGDIGLiwvCw8MxY8YMfPbZZwgODsawYcNQsWJFNGnSBOPGjQMAGBkZYc6cOfDz80N+fj4aNmyI33//HXXr1gUA+Pn5wdXVFQ0bNsSzZ88Uah/cL7/8EjExMQgPD0evXr3QvXt3zJo1q8yHKSkrbW1tREREoEuXLujUqRNq1aolOhJRuXHp0iUcPXoUq1evFh2FiEghSKRcK0JEcpSXl4etW7ciISEBBQUFMDIygrm5OZo1awYrKyvR8YhKzdWrV2FnZ4fr16+LjkJEcvb48WPMnDkTq1evxvjx4+Ht7Q0tLS3RsZTCrFmzEBkZiYMHD0JNjTtuEb2P/v37o0WLFhg7dqzoKERECoHFTiIiolJQUFAAfX19/Pvvv9DW1hYdh+i1kpOTUb9+fdExlEZqaip8fX1x+fJlzJ8/Hz179oREIhEdq1wrKChAhw4d4OjoCG9vb9FxiBReQkIC7O3tcfXqVS5hJyL6fyx2EpHcFT/NFP9XIpHwzSAppQYNGmDHjh2wtrYWHYXoFbm5ufjiiy8QGxsrOorS2bdvH0aPHg0zMzOEhITwOeATpaamok2bNjh+/DgaNGggOg6RQnN0dESzZs1k24UQERGnsRNRGSgubqqpqUFNTY2FTlJaiYmJfGNOCmvMmDHcPkROunXrhosXL6J79+7o0KEDRo0ahYcPH4qOVW5ZWlpixowZcHZ2Rn5+vug4RAorISEBUVFRGD58uOgoREQKhcVOIiKiUsJiPimqbdu2Ye/evVixYoXoKEpLU1MTPj4+SExMRG5uLqytrREWFobCwkLR0cqlH3/8EVWqVMGsWbNERyFSWMUT2PX19UVHISJSKFzGTkRy9fLSdSIiKnvXrl1D69atsWfPHrRs2VJ0HJURGxsLHx8fPHr0CKGhoejYsaPoSOXOrVu30LRpU+zevZt/d4n+IzExEXZ2dkhLS2Oxk4joP9jZSURytXbtWvz111+iYxARqaS8vDw4Ojpi4sSJLBaVsSZNmuDIkSOYNGkSXF1d8cMPP+D69euiY5UrNWrUwMKFC+Hs7Ixnz56JjkOkUGbMmAFfX18WOomIXoPFTiKSq8TERFy6dEl0DCIileTv7w9jY2OMGjVKdBSVJJFI8P333yMpKQm2trZo3rw5pk6diqdPn4qOVm70798fTZs2xcSJE0VHIVIYiYmJOHz4MH766SfRUYiIFBKLnUQkV5UqVeKQBqL/l5ubi5ycHNExSEXs3r0bW7ZsQXh4OLcSEUxHRwdTpkzBhQsXcOXKFVhbW2Pjxo3gblLvZ/Hixdi2bRsiIyNFRyFSCOzqJCJ6OxY7iUiuWOwk+p/Vq1cjODiYA0tI7m7evAkPDw9s2LABVapUER2H/l+tWrWwYcMGbNiwAcHBwWjfvj3OnTsnOpbCq1y5MlatWgV3d3f8+++/ouMQCXXz5k0cPXqUXZ1ERG/BYicRyRWLnaRKVq1aheTkZBQVFaGgoOCVomatWrWwdetWXL16VVBCUgUFBQUYOHAgfHx80K5dO9Fx6DXatWuHmJgYuLu7o2fPnhgyZAju3LkjOpZC69q1K3r27Alvb2/RUYiEqlKlCocSERG9A4udRCRXLHaSKpkwYQKioqKgpqYGDQ0NqKurAwCePHmCxMRE3LhxAwkJCYiLixOclJRZQEAAtLS0MGHCBNFR6C3U1dXh4eGBy5cvo1KlSrCxsUFwcDDy8vJER1NY8+bNw6lTp7B9+3bRUYiE0dHRgY6OjugYREQKTUN0ACJSbix2kipp27Yttm7diqysLFy8eBGpqam4desWsrOzoaamBmNjY9jY2PBNCsnNoUOHsGrVKpw/fx5qavxMuzwwNDTEvHnz4OnpCV9fXyxfvhwhISHo0aMH91r9Dz09Pfz222/o06cPvvzyS5iYmIiORERERAqIr4KJSK5Y7CRV0rZtW0RFRWHnzp149uwZ2rVrh3HjxmHNmjXYtWsXdu7ciZ07d6JDhw6io5ISunPnDlxdXfHbb7+xCFQOWVlZYffu3QgNDcWYMWPQo0cPXL58WXQshdOmTRt4ePwfe3ceV1P+/wH8dSvtWSrLUBJ1S8gSWQdjXzIZW6lQimiQkH2J0VCWCmNNi8YeM8yQZhhjyxayVIgUZlBMlIqWe35/+LnfaTBj6XZu9Xo+HufxcPbXDbd73vezuGPMmDGc4ImISl1OTg4mT54MExMTaGlpoUOHDjh//rx8//PnzzFx4kQYGRlBS0sLFhYWCAoKEjExEb0NW3YSkUKx2EmVSf369VGjRg1s27YN+vr60NDQgJaWlrw7O5GiyGQyuLi4YPTo0ejRo4fYcegT9O3bFz169MCaNWvw+eefw8XFBQsWLED16tX/89yioiKoqVX8j/cLFixA27ZtERYWBnd3d7HjEFEF4uHhgStXriAyMhJGRkb4/vvv0aNHDyQlJaFevXqYMmUKDh8+jKioKJiamuL48eMYM2YMDA0NMWLECLHjE9H/Y8tOIlKo6tWrIzs7GzKZTOwoRArXtGlTaGpqom7dujAwMICurq680CkIgnwhKm1Lly7Fy5cvsWDBArGjUCmoUqUKfHx8kJiYiLy8PFhaWiI2NvZf3z8EQcChQ4fg5eWFHTt2lGHasqeuro6oqCjMnDmTE74RUanJz8/Hnj17sHTpUnTt2hVmZmbw8/ODmZkZ1q1bBwCIi4vDiBEj8MUXX6BBgwYYOXIk2rVrh7Nnz4qcnoj+jsVOIlIoVVVV6OjoIDs7W+woRArXuHFjzJ49G8XFxXj+/Dmio6ORmJgIAJBIJPKFqDSdPHkSq1atwrZt2ypFq77KpFatWtiwYQNiYmL+c/iLoqIiZGdnQ1VVFZ6enujatSseP35cRknLXtOmTTFz5ky4urqiuLhY7DhEVAEUFRWhuLgYmpqaJbZraWnh5MmTAIBOnbVPJ3YAACAASURBVDrhp59+wr179wC8Kn4mJCSgT58+ZZ6XiN5NIrCJCREpWIMGDXD06FGYmpqKHYWozGRmZqJHjx64e/cuHB0d4eXlhWbNmgF41eWYk8dQaXjy5AlatmyJdevWoX///mLHIQUSBOG9vyxp3rw5ateujXXr1qFRo0YKTiae4uJidOvWDQMGDMC0adPEjkNEFUCHDh2gqqqKHTt2oE6dOti+fTtGjRoFMzMz3LhxAwUFBRg3bhzCw8PlXzCuXr0a48aNEzk5Ef0dn7SISOE4bidVJq+HbNDV1UVWVhYCAwMhlUoxaNAgzJgxA2fOnGGhk0qFIAhwdXXF0KFDWeisBP6r0FlQUAAA2Lp1K9LT0zFp0iR5obOiDiWjqqqKiIgIBAQE4OrVq2LHIRLN06dP0b179xK9SLi8e/m398SoqCioqKjAyMgIGhoaWLVqFYYPHy4flmj16tU4deoU9u/fjwsXLiAoKAjTpk3DoUOH3riWTCbD1KlTRX+95WV5+vSpwv6PUOXDlp1EpHDdunXDnDlz0L17d7GjECnc38fl/Pzzz2FnZ4dZs2YhIyMDgYGBePjwIaysrDBkyBBIpVKR01J5FhQUhO3bt+PkyZNQV1cXOw6J6O+tPo2NjWFnZ4fFixfDwMAAAPDixQvcu3cP586dg6GhIXr37i1m3FIXFhaGVatW4dy5c/y/QESlIjc3F9nZ2fjss8/g4OAgH56oWrVq2L17N+zt7eXHenh4IC0tDYcPHxYxMRH9HZuWEJHCsWUnVSYSiQQqKipQUVGBjY0Nrl27BuBVd0tPT0/UqlULc+fOxTfffCNyUirPzp8/jyVLlmDnzp0s7pB8zMqZM2dCVVUVI0eOlBc6AcDHxwfdunXDkiVLMGrUKHTs2FE+3lxF4Obmhvr162PhwoViRyH6KGx/pHx0dHTw2WefISsrC7GxsbC3t0dhYSEKCwvlrTxfU1VVrbAt6InKK45iT0QKx2InVSbZ2dnYs2cPHjx4gFOnTuHmzZto3LgxsrOzIQgCateujS+++AK1atUSOyqVU8+ePYODgwPWrl0LjoVMMpkMampquHv3Lr777jvMnj0bzZs3l+//9ttvERUVheDgYNjZ2aFKlSoYOHAgoqKiMHv2bBGTlx6JRIJNmzahefPm6N+/Pzp06CB2JKL38uzZMxw8eBADBgyArq6u2HEIQGxsLGQyGSwtLXHr1i34+vrCwsICbm5uqFKlCrp06YKZM2dCV1cXJiYmOHbsGLZs2YLAwECxoxPR37DYSUQKx2InVSZZWVmYOXMmpFIp1NXVIZPJMGbMGFStWhW1a9eGoaEhqlWrhpo1a4odlcohQRDg4eGBPn36YMiQIWLHIZFdvXoVGhoakEql8Pb2RpMmTTBw4EBoa2sDAM6ePYvFixdjyZIl8PDwkJ/XrVs3bNmyBb6+vqhSpYpY8UvV6wmZRo4ciYSEBBaOSKk9ePAAwcHBCA0NRd++fTF48GCxI9H/e/bsGWbNmoX79+9DX18fgwcPhr+/v/y9cseOHZg1axacnZ3x119/wcTEBN988w0mTJggcnIi+juO2UlECvftt98iJycHS5YsETsKUZk4deoUDAwM8ODBA/Tq1Qu5ubnsakylYv369Vi3bh3Onj0LTU1NseOQiGQyGWbOnInly5fDyckJ+/fvx4YNG+Dg4CCfBG3IkCFIT0/H+fPnAfxvbM/Ro0cjLS0Nv/32G4BXY9Pt2rUL1tbWsLGxEe01lYZRo0ZBW1sb69atEzsK0Rtu3LiBZcuWYe/evRgxYgR8fHzQoEEDsWMREVU4HLOTiBSOLTupsunYsSMsLS3RuXNnXLt27a2FTo7tRB/qypUrmDdvHnbt2sVCJ0FFRQWBgYHYvn07zp8/j+fPnyMjI0M+UVF6ejp+/PFHzJ8/H8CrcT0lEgmuX7+OtLQ0tGzZEkVFRQCAY8eO4eDBg3ByckLPnj3L9Xieq1atwsGDBxETEyN2FCK5s2fPYtCgQfj8889hbGyMmzdvIiQkhIVOIiIFYTd2IlI4FjupspHJZFBRUYGqqiosLCxw8+ZNpKWlIS8vDwUFBWjTpg3HWqQP8vz5cwwbNgxBQUGwsLAQOw4pEQcHBzg4OGDRokXw9fXFo0eP8O233yImJgZSqRStWrUCAPmEGtHR0Xj69Ck6d+4MNbVXjwL9+vVDw4YNERMTg6lTp+LQoUMYM2aMaK/pU1SrVg3h4eEYOXIkrly5An19fbEjUSUlCAJiYmIQGBiItLQ0TJ06FVFRUdDR0RE7GlGF8PLlS6irq8u/5CP6OxY7iUjhWOykykZFRQX5+flYu3Yt1q9fj3v37qGgoAAAIJVKUbt2bQwdOpTjO9F7+/rrr9G+fXu4uLiIHYWU1Pz58zFp0iRcvnwZAPDZZ5/hwYMHePHihfyYmJgY/Prrr2jZsiXs7e0BAEVFRVBTU4ORkRHOnDmDxo0bl9tC52vdunXD0KFD4eXlhR07dogdhyqZwsJC7Ny5E4GBgZBIJJg+fTqGDRtWYcbHJVIWv/76KzIyMjB69Gixo5ASYrGTiBSOxU6qjDZu3IiQkBD069cP5ubm+O2331BYWIjJkyfj9u3b2LZtG9TV1TF27Fixo5KSi4yMxLlz5xAfHy92FFJy1atXR5cuXQAAlpaWMDExQUxMDIYMGYLU1FRMnDgRTZs2xaRJkwD8r9Apk8kQGxuL3bt345dffimxr7z69ttv0apVK+zYsQOOjo5ix6FKIDc3F5s3b8bKlSthamqKwMBA9O7dm63OiBTE0NAQkyZNwqhRo+S9F4he4wRFRKRwKSkp6Nu3L27duiV2FKIykZKSguHDh2Pw4MHw8fGBpqYm8vLysHLlSsTFxeHgwYMICQlBaGgorl69KnZcUmLXr1/H559/jt9++w3NmjUTOw6VMzt37sTXX3+NatWqIS8vDzY2NggICECTJk0A/G/Cort372Lo0KHQ19dHTEyMfHt5Fx8fj379+uHSpUuoV6+e2HGognr8+DFWr16NdevW4fPPP8eMGTNga2srdiyiSqFt27aYPXu2vLcC0WucoIiIFI4tO6myUVFRQWpqKry9veUTyWhra6N169ZISkoCAHTv3h13794VMyYpufz8fAwbNgz+/v4sdNJHcXBwkBdiTp06hf3798sLnTKZDBKJBAUFBdizZw/i4+OxceNG+b6KoHXr1pgwYQJGjx4Ntu+g0paWloaJEydCKpXiwYMHOHHiBPbs2cNCJ1EZ8vb2RkhIiNgxSAmx2ElECle9enU8e/aswjw8Ef0XU1NTqKio4PTp0yW27927F+3bt0dxcTGeP3+OatWq4enTpyKlJGXn4+MDKyurcj9+Ionv9QREr+Xl5SEnJwcAcOPGDSxfvhze3t4wNjZGcXFxheoOOGvWLGRlZWH9+vViR6EK4vLly3B2doaNjQ10dHSQmJiIjRs3cvI4IhEMGTIEN27cwJUrV8SOQkqm/A7EQ0TlhpqaGrS1tZGTk4Nq1aqJHYdI4VRUVODt7Q13d3d06tQJ9evXx6VLl3D06FH89NNPUFVVRe3atbFlyxZoaWmJHZeU0K5du3D48GFcvHixQnQnJuWgovKqncO+ffuwfPlyuLi4IDU1FYWFhVi5ciUAVLh/b1WqVEFUVBQ6deqEHj16wNzcXOxIVA4JgoDff/8dAQEBuHLlCiZPnoy1a9fycy2RyNTV1eHl5YWQkBBs3rxZ7DikRDhmJxGVCRMTExw7dgwNGjQQOwpRmSgqKsK6detw7NgxZGZmonbt2vDx8UH79u3FjkZK7vbt22jfvj1iYmJgY2MjdhyqoJYtWwY/Pz/k5+dj6tSpWLZsWYVr1fl3q1evxrZt23DixIlyPfESla3i4mL8+OOPCAgIQHZ2Nnx9feHi4gINDQ2xoxHR/8vMzIRUKsXNmzdRs2ZNseOQkmCxk4jKRIsWLRAeHo6WLVuKHYWoTD19+hSFhYUwNDSscC2mqPQVFBSgY8eOcHFxgbe3t9hxqIJ7+fIlZs2aheDgYDg6OmLDhg3Q09N74zhBEFBYWAh1dXURUpYOmUyGXr164YsvvsCcOXPEjkNK7sWLF4iKisKyZcugr6+PGTNmwN7eXt46moiUi7u7Oxo2bMj3d5LjuzURlQlOUkSVVfXq1VGzZk0WOum9zJw5E3Xr1sWkSZPEjkKVgIaGBlauXImLFy9CKpWioKDgjWMEQcCePXtgbW2NmJgYEVKWDhUVFYSHhyMkJASXLl0SOw4pqadPn2Lp0qVo2LAhfvzxR4SGhuL06dP46quvWOgkUmLe3t5Yu3btW3+PUeXEPhxEVCZY7CQi+nf79+/Hnj17cOnSJRbHqUy1aNECLVq0eOs+iUSCIUOGQFtbG5MnT8aaNWsQFBQEqVRaxik/nbGxMVauXIkRI0YgPj4empqaYkciJfHnn38iODgYmzdvRr9+/RAbG4tmzZqJHYuI3pO1tTX++OMPsWOQEuHXU0RUJljsJCJ6t7t372LMmDHYvn079PX1xY5D9IZ+/frh6tWr6N69Ozp27Ihp06bh2bNnYsf6YM7OzmjcuDHmzp0rdhRSAtevX4e7uzuaNm2Kly9f4uLFi4iKimKhk4ionGOxk4jKBIudRERvV1RUBCcnJ/j4+KBDhw5ixyF6J3V1dUyZMgXXrl3Ds2fPYGlpidDQUBQXF4sd7b1JJBKsW7cO27Ztw7Fjx8SOQyI5c+YMvvrqK3Tp0gUmJiZISUlBSEgITExMxI5GRESlgMVOIioTLHZSZVVUVIT8/HyxY5ASW7BgAXR0dDB9+nSxoxC9l9q1a2PTpk04cOAAIiMjYWtri5MnT4od670ZGhpi06ZNcHV1RXZ2tthxqIwIgoADBw6gS5cuGD58OLp37447d+5g/vz5MDAwEDseERGVIhY7iahMsNhJlVVgYCD8/PzEjkFK6pdffkFERASioqI4+QWVO61atcLx48fh6+sLJycnDB8+HPfu3RM71nvp378/evbsCR8fH7GjkIIVFhYiKioK1tbWmDNnDjw9PZGSkoIJEyZAW1tb7HhERKQA/FRNRGXCw8MDq1evFjsGUZkzNzdHSkqK2DFICT148ACjRo1CVFQUatWqJXYcoo8ikUjg6OiI69evw8LCAi1btsSiRYuQl5cndrT/tGLFCvz+++/Yv3+/2FFIAZ4/f46QkBCYmZkhPDwcy5cvx6VLl+Dk5AQ1NeWdpzciIgK6urples/ff/8dEokEjx8/LtP7UuWTlpYGiUSC+Ph4saNQBcdiJxGVCXV1daX+YEmkKObm5rh586bYMUjJFBcXw8XFBWPHjkW3bt3EjkP0ybS1teHn54cLFy4gMTERjRs3xq5duyAIgtjR3klPTw+RkZEYN24cMjMzxY5DpSQzMxPz58+HqakpTp48iejoaPz222/o3bs3JBJJqd2na9eumDBhwhvbP7VY6eDggNTU1E+J9sE6dOiABw8esDs/fRJXV1fY2dm9sT0+Ph4SiQRpaWkwNjbGgwcP0KJFCxESUmXCYicREZECmZmZITU1FTKZTOwopESWLFmC4uJizJ8/X+woRKXKxMQEO3fuRFRUFJYsWYKuXbsiISFB7Fjv1KlTJ4wYMQKenp5KXZitrD7k7+TOnTuYMGECLCws8OjRI8TFxWH37t1o06aNAhN+mIKCgv88RktLq8xb+6urq6NOnTqlWgwmehtVVVXUqVPnXxvBFBYWlmEiqqhY7CQiIlIgXV1d1KhRA/fv3xc7CimJ48ePY82aNdi6dStUVVXFjkOkEJ07d0Z8fDycnZ3Rp08feHp6Km3ryUWLFuHWrVvYsmWL2FHob54+ffpexbeEhAQ4OTmhTZs20NPTQ1JSEjZs2ABzc/MySPnvXrd0CwgIgJGREYyMjBAREQGJRPLG4urqCuDtLUMPHDiAtm3bQktLCwYGBhgwYABevHgB4FUBdcaMGTAyMoKOjg7atGmD2NhY+bmvu6gfOXIEbdu2hba2Nlq3bo2LFy++cQy7sZOi/bMb++t/ewcPHoStrS3U1dURGxuLe/fuwd7eHvr6+tDW1oalpSV27Nghv87Vq1fRo0cPaGlpQV9fH66urnj27BkAIDY2Furq6njy5EmJe8+ePRvNmzcHADx58gTDhw+HkZERtLS00KRJE4SHh5fRT4HKAoudRERECsZxO+m1x48fw9nZGeHh4ahXr57YcYgUSlVVFWPHjsX169eho6MDKysrBAcHK12rHQ0NDURFRWHatGlIT08XO06ld+3aNfTv3x+NGzdGYmLiO48TBAEhISHo378/WrZsidTUVCxZsgR16tQpw7T/7dixY7hy5QoOHTqEI0eOwMHBAQ8ePJAvrwszXbp0eev5hw4dgr29PXr27IkLFy7g6NGj6NKli7zHiJubG44dO4Zt27bh6tWrGDVqFAYMGIDLly+XuM6sWbOwdOlSXLx4EQYGBnB2dmZrZlIaM2bMwOLFi3H9+nW0bdsWXl5eyMvLw9GjR5GYmIjg4GBUr14dAJCXl4c+ffpAV1cX586dww8//IC4uDiMHj0aANCjRw8YGBhg9+7d8usLgoDt27fDxcUFAPDixQu0atUKP//8MxITE+Ht7Q1PT08cOXKk7F88KYZARERECuXh4SGsW7dO7BgksuLiYqF///6Cr6+v2FGIRJGcnCz06dNHsLS0FGJiYsSO84YlS5YIX3zxhVBcXCx2lEopPj5e6NChg6ChoSEMHTpUuHHjxr8eL5PJhPz8fOHFixdllLCkLl26CF9//fUb28PDwwUdHR1BEARh1KhRgqGh4TszZmRkCCYmJoK3t/dbzxcEQejQoYPg4ODw1vNv3bolSCQSIT09vcR2e3t7Yfz48YIgCMLRo0cFAMKhQ4fk+0+ePCkAEO7du1fimMzMzPd56URvNWrUKEFVVVXQ0dEpsWhpaQkAhDt37gh37twRAAjnz58XBOF///aio6NLXKtZs2aCn5/fW++zceNGoWrVqkJ2drZ82+vrpKSkCIIgCJMnTxY6deok33/ixAlBRUVFuH///jvzOzg4CO7u7h/9+km5sGUnERGRgrFlJwFAUFAQnjx5An9/f7GjEInC0tISBw8exPLlyzFp0iTY2dkp1QRuvr6+ePnyJVatWiV2lEonNTUVbm5uSE9Px8OHD7Fr1y5IpdJ/PUcikUBTUxMaGhpllPLjNG3a9K0ZCwoK8NVXX6Fx48ZYsWLFO8+/dOkSunfv/tZ9Fy9ehCAIsLKygq6urnw5cOAAbt++XeJYa2tr+Z/r1q0LAMjIyPiYl0T0Tp07d0ZCQkKJZdu2bf95XuvWrUuse3t7Y/HixWjfvj3mzp2LCxcuyPclJyfD2toaenp68m0dOnSAiooKkpKSAAAuLi44deqUvLX+1q1b0bVrV3mvmuLiYvj7+8Pa2hoGBgbQ1dXF3r17cffu3U/+GZByYLGTiIhIwVjspLNnzyIgIADbt29HlSpVxI5DJBqJRIL+/fvj2rVr+OKLL9CxY0f4+vrKx1oTk6qqKrZs2YLFixfLH5hJcR49eiT/c8OGDeVd1x8+fIjDhw/Dzc0N8+bNKzFOnzKpWrXqW//dPn36FNWqVZOv6+jovPX8cePGISsrCzt37vzo8ZtlMhkkEgnOnz9foriUnJyMsLCwEsf+/XfP67FQOXkilTZtbW2YmZmVWIyMjP7zvH/+P3F3d8edO3fg5uaGmzdvokOHDvDz8wPwqkv6u8bzfb3dxsYGlpaW2LZtGwoLC7F79255F3YAWL58OVasWAFfX18cOXIECQkJGDhw4HtNIkblA4udRERECmZubq5UrZeobD19+hSOjo5Yv349GjRoIHYcIqWgrq6OqVOn4tq1a8jKyoKlpSU2b94sevGlUaNG8Pf3x8iRI5VubNGKQCaTYfHixWjSpAmGDh2KGTNmyMfl7NOnD54+fYp27drBy8sL2traOHbsGJycnPDNN98oRUH87ywsLOQtK//u4sWLsLCw+Ndzly9fjp9++gk///wzqlat+q/HtmzZ8p3jCLZs2RKCIODhw4dvFJg4LjSVd0ZGRhg7dix27dqFRYsWYePGjQAAKysrXL58GTk5OfJj4+LiIJPJ0LhxY/k2Z2dnbN26FYcOHUJubi4GDx4s33fy5EkMGDAAI0aMQIsWLdCoUSN+Vq9gWOwkIiJSsEaNGiEtLQ1FRUViR6EyJggCPDw8YGdnh0GDBokdh0jp1K5dG6Ghofj5558RHh4OW1tbnDp1StRMY8eORa1atbB48WJRc1Q0aWlp6NGjB/bt24e5c+eiT58+iImJwXfffQcA6NKlC3r16oUJEybgyJEj+O6773D8+HEEBQUhIiICx48fF/kVlDR+/HikpqZi4sSJuHz5Mm7cuIGgoCBs374d06ZNe+d5hw8fxuzZs7F27VpoaWnh4cOHePjw4TuLuXPmzMHu3bsxd+5cJCUlITExEUFBQcjLy4NUKoWzszNcXV0RHR2N1NRUxMfHY/ny5di7d6+iXjqRwnl7e+PQoUNITU1FQkICDh06BCsrKwCvipg6OjoYOXIkrl69iuPHj8PT0xODBg2CmZmZ/BouLi5ISkrCvHnz8OWXX5b4YkEqleLIkSM4efIkrl+/jgkTJuDOnTtl/jpJcVjsJCIiUjAtLS3Url2b4wBVQuvWrUNqaiqWLVsmdhQipWZjY4MTJ05g6tSpcHR0hJOTE+7fvy9KFolEgs2bN2P9+vU4d+6cKBkqohMnTiA9PR0HDhzA8OHDMXv2bDRs2BBFRUV4+fIlAMDDwwMTJkyAsbGx/Dxvb2/k5eXhxo0bYkV/q4YNG+L48eNISUlBr169YGtrix07dmD37t3o16/fO887efIkCgsLMWzYMHz22Wfyxdvb+63H9+vXDz/88ANiYmLQsmVLdOnSBUePHoWKyqtH+fDwcLi5uWH69OmwtLSEnZ0djh8/DhMTE4W8bqKyIJPJMHHiRFhZWaFnz56oXbs2IiMjAbzqKh8bG4vs7GzY2trC3t4e7du3f2PoBhMTE3Tq1AmXL18u0YUdAObOnQtbW1v07dsXnTt3ho6ODpydncvs9ZHiSYR/trsnIiKiUtejRw/4+vqid+/eYkehMpKQkICePXsiLi4O5ubmYschKjdyc3MRGBiI7777Dt7e3pg2bRq0tLTKPMfu3bsxb948XLx4Edra2mV+/4pm0aJFOHLkCCIjI9GgQQMIggB7e3u4ubnhq6++euN4QRAgCAJevnwJU1NTuLu7c4I3IiJ6L2zZSUREVAY4SVHlkpOTAwcHB4SEhLDQSfSBdHR0sHDhQsTHx+Pq1ato3Lgxdu/e/cbYiIo2dOhQ2NjYYObMmWV634pq2LBhePr0KTw8PODh4QE9PT2cO3cOU6dOxbhx4974HSmRSKCiooLw8HDUrVsXHh4eIiUnIqLyhi07iYiIysDKlSuRnp6OkJAQsaOQggmCgBEjRkBTUxOhoaFixyEq944dOwZvb29Ur14dISEhaN68eZndOysrC9bW1ggLC0PPnj3L7L4VVWpqKvbv34+1a9di8eLF6NWrFw4cOIDNmzdDV1cX+/fvR15eHqKioqCiooLIyEjcvn0b8+bNw7hx4yCRSN45CzMREdFrbNlJRERUBtiys/KIiIjApUuXsGrVKrGjEFUIXbp0wYULFzB8+HD07t0b48aNQ2ZmZpncu0aNGggLC8Po0aORlZVVJvesyBo2bIikpCR07NgRw4YNQ/Xq1eHs7Iy+ffsiPT0dmZmZ0NbWxr179xAcHIzPP/8cKSkp8PLygoqKCgudRET0XljsJCIiKgPm5ua4efOm2DFIwZKSkuDr64tdu3ZxjD+iUqSqqgpPT08kJydDS0sLTZo0QUhICAoLCxV+7549e8Le3h6TJk1S+L0qksLCwjeGHhAEARcvXkT79u1LbD937hzq168PPT09AMCMGTOQmJiIJUuWQFdXt8wyExFRxcBiJxERURlo2LAh7t27VyYP5iSOvLw8ODg4ICAgAE2aNBE7DlGFVKNGDQQFBeHYsWM4ePAgrK2tERsbq/D7BgYG4ty5c4iOjlb4vcq7S5cuYfjw4Rg+fPgb+yQSCVxdXbF+/XqsWrUKt2/fxty5c3H16lU4OztDU1MTAORFTyIioo/BMTuJqEwUFBSgsLAQOjo6YkchEk2jRo0QExMDqVQqdhRSgLFjxyI3Nxfff/89u1oSlQFBEHDgwAH4+PigcePGWLFihUInBDt79iy+/PJLJCQk4LPPPlPYfcojQRDw22+/ISAgAElJSfDx8cGYMWNQtWrVN44tLCzE8OHDce3aNRQUFMDAwAD+/v7o1auXCMmJqDK5cuUK+vbti7S0NFSpUkXsOKRAbNlJRGXi8OHDiIyMFDsGkag4bmfFtWPHDhw9ehTr169noZOojEgkEtjZ2eHatWv4/PPP0b59e0yfPh3Z2dkKuV/btm0xduxYeHh4lPnM8MqquLgYu3fvRps2bTBhwgQMHz4cqampmDp16lsLnQBQpUoVREdHY9++ffjll19w/vx5FjqJqExYW1tDKpWylX4lwGInEZWJxMREpKamih2DSFQsdlZMt27dwsSJE7Fz5052vSQSgYaGBnx9fXHt2jU8efIElpaWCA8Ph0wmK/V7zZs3Dw8fPkRoaGipX7s8yc/Px/r162FhYYGgoCDMmzcPiYmJcHNzg7q6+ntdw8LCAmZmZgpOSkRU0uTJkxEcHCx2DFIwFjuJqExkZWWhRo0aYscgEhWLnRXPy5cv4eDggAULFqBVq1ZixyGq1OrUqYPNmzdj//79CA0Nha2tLeLi4kr1Hurq6oiKisLs2bMr5Ze4WVlZ+Pbbb9GwYUMcOHAAERERiIuLg729PVRU+GhJRMrPzs4OmZmZOHPmjNhRSIH4G4mIygSLnUQsdlZE06dPh4mJCb7++muxoxDRW1VaxwAAIABJREFU/2vdujVOnjyJKVOmwMHBAc7Ozrh//36pXd/KygqzZ8/GyJEjUVxcXGrXVWb379/HtGnTYGZmhhs3buDXX3/FTz/9hE6dOokdjYjog6iqqmLixIkICQkROwopEIudRFQmWOwkYrGzovnxxx+xb98+bN68meN0EikZiUQCJycnXL9+HQ0bNkSLFi2wePFi5Ofnl8r1vb29oaamhhUrVpTK9ZRVcnIy3NzcYG1tjeLiYly6dAmRkZFo2rSp2NGIiD7a6NGjERsbW6pfhJFyYbGTiMoEi51EQIMGDfDgwQO8ePFC7Cj0idLT0+Hp6YkdO3bwvY1Iieno6OCbb75BfHw8Ll++DCsrK+zZs+eTJxhSUVFBZGQkli1bhitXrpRSWuXxumt6165d0ahRI9y6dQtBQUGoX7++2NGIiD5ZtWrV4OLigrVr14odhRSExU4iKhMsdhIBampqMDExqZTjvFUkhYWFGD58OKZNm4Z27dqJHYeI3kODBg2we/duhIeHY9GiRejWrdsnFylNTEywbNkyjBgxAi9fviylpOKRyWTyrukuLi7o3bs30tLSMHfuXOjr64sdj4ioVE2cOBGhoaGl1uKflAuLnURUJljsJHqFXdnLvzt37kBfXx9Tp04VOwoRfaCuXbviwoULcHBwQM+ePTF+/Hg8fvz4o683atQomJqaws/Pr/RClrGCggJERkbC2toaCxYswIQJE3Dz5k14eXlBS0tL7HhERAphbm4OW1tbbN26VewopAAsdhJRmUhJSYFUKhU7BpHoWOws/8zNzbF//37OPExUTqmpqWHcuHG4fv06NDQ0YGVlhVWrVqGwsPCDryWRSLBx40ZERETg1KlTCkirOM+fP0dQUBDMzMwQFRWFoKAgXLhwAY6OjlBTUxM7HhGRwnl7eyM4OPiThzYh5cNP6URERGWIxc7yTyKRsNBJVAHUqFEDwcHB+P333/Hzzz+jefPm+OWXXz74OrVq1cL69esxcuRIPH/+XAFJS1dGRgbmzp0LU1NTnD59Gj/88AMOHz6Mnj17crI1IqpUevToAUEQ8Ntvv4kdhUoZP6kTERGVIRY7iYiUi5WVFWJjYxEQEICvv/4a9vb2uHXr1gddw97eHp07d1bq4S1u374NLy8vWFpa4smTJzh9+jR27doFGxsbsaMREYlCIpHA29sbISEhYkehUsZiJxERURlisZOISPlIJBIMGDAA165dQ8eOHdGuXTvMmDEDOTk5732NkJAQxMbG4uDBgwpM+uEuXrwIBwcHtG3bFjVq1EBycjLWrVsHMzMzsaMREYnOxcUFp0+f/uAvuUi5sdhJRERUhoyNjfH48WPk5eWJHYXeIjk5GdHR0Th+/DgePHggdhwiKmMaGhqYPn06rl27hszMTFhYWCAiIgIymew/z61atSoiIiIwZswYPHnypAzSvpsgCPKu6fb29mjbti3u3LkDf39/1K5dW9RsRETKRFtbGx4eHli9erXYUagUsdhJRKVGIpEgOjq61K+7fPlyNGjQQL7u5+eHpk2blvp9iMqCqqoqTE1N+e2xEvrxxx8xbNgweHl5YejQoYiMjCyxn4PXE1UederUQVhYGPbt24cNGzagbdu2OH369H+e17VrVzg6OmL8+PGivGcUFxdj165daN26NSZNmgRnZ2fcvn0bU6ZMgZ6eXpnnISIqD7y8vBAVFYXs7Gyxo1ApYbGTqBJzdXWFRCKBh4fHG/umT58OiUQCOzs7EZL9u2nTpuHYsWNixyD6aFKplF3ZlUxGRgbc3Nzg4eGBlJQU+Pr6YuPGjcjOzoYgCHjx4gUn7iCqhNq0aYO4uDhMnjwZQ4cOxYgRI/DHH3/86zn+/v5ITEzE9u3byyglkJ+fj3Xr1kEqlSIkJAQLFizAtWvX4OrqCnV19TLLQURUHhkbG6Nnz54IDw8XOwqVEhY7iSo5Y2Nj7Ny5E7m5ufJtRUVFiIqKQv369UVM9m66urowMDAQOwbRR+O4nconMDAQXbt2hbe3N6pVqwZ3d3fUqlULo0ePRrt27TB+/HhcuHBB7JhEJAKJRAJnZ2dcv34dJiYmaN68Ofz9/fHixYu3Hq+pqYmoqChMnjwZ9+/fV2i2rKws+Pv7w9TUFDExMdiyZQtOnTqFL7/8EioqfNQjInpf3t7eWLVqFYqLi8WOQqWAvwGJKjlra2uYm5tj165d8m0HDhyApqYmunbtWuLY8PBwWFlZQVNTE1KpFEFBQW+MYfXXX39h6NCh0NHRQcOGDfH999+X2D9z5kxYWFhAS0sLDRo0wPTp0994WAgMDESdOnWgq6uLkSNH4vnz5yX2/7Mb+/nz59GrVy8YGhqiatWq6NSp03t1NSMSC4udykdLSwv5+fnIysoCAMydOxdpaWno3Lkz+vTpg1u3biE0NBQFBQUiJyUisejq6mLx4sU4f/48Ll26BCsrK+zdu/et3dVbtWqFSZMmwc3N7b3G+/xQ9+7dw9SpU9GoUSOkpKTgyJEj2L9/Pzp27Fjq9yIiqgzat28PAwMDHDhwQOwoVApY7CQiuLu7IywsTL4eFhYGNze3El02N23ahNmzZ2PRokVITk7GihUrEBAQgLVr15a41qJFi2Bvb4/Lly/DwcEBo0ePRnp6uny/jo4OwsLCkJycjLVr12LHjh3w9/eX79+1axfmzp2LhQsX4uLFi7CwsMDKlSv/NX9OTg5GjBiBEydO4Ny5c2jRogX69euHx48ff+qPhkghWOxUPrVq1UJcXBymTJkCd3d3bNiwAT///DMmTZqEhQsXYvDgwdi6dSsnLSIimJqaIjo6GqGhofDz80P37t1x5cqVN46bOXMmTExMPmhG9/+SlJQEV1dXNG/eHABw+fJlREREoEmTJqV2DyKiykgikcDb2xshISFiR6FSIBE42j5RpeXq6orHjx8jKioKdevWxZUrV6CnpwcTExOkpKRg/vz5ePz4MX7++WfUr18f/v7+GDFihPz84OBgbNy4EUlJSQBe/YKYOXMmlixZAuBVd/iqVati48aNcHFxeWuG9evXY/ny5fLJWjp06IAmTZpg06ZN8mN69OiBW7duIS0tDcCrlp3R0dG4du3aW68pCALq1q2LZcuWvfO+RGK6f/8+2rRpw8KZklm2bBni4+PRsmVL7N69GzExMTAwMICqqirOnTuH8ePHY+vWrbC0tBQ7KhEpiaKiImzatAl+fn4YMmQIFi1aVGKoHZlMVirdyU+dOoWAgACcO3cOEydOhJeXF2rUqPHJ1yUiov8pKChAgwYNEBsbi2bNmokdhz4BW3YSEWrUqIGvvvoKYWFhiIyMRNeuXUuM15mZmYl79+7B09MTurq68mXmzJm4fft2iWtZW1vL/6ympoaaNWsiIyNDvi06OhqdOnWSd1P38fHB3bt35fuTk5PRvn37Etf85/o/ZWRkwNPTE1KpFNWqVYOenh4yMjJKXJdImdStWxfZ2dmc8VFkhYWFePLkiXzd19cXO3bswLBhw1BYWIjCwkKoqqpCEASsWLEChoaGLHQSUQlqamoYP348kpOToaqqisaNG2P16tUoKioCgE8qdMpkMnnX9JEjR6Jv3764c+cO5syZw0InEZECqKurw8vLi607KwAWO4kIADB69Ghs2bIFYWFhGD16dIl9r8eaWr9+PRISEuTLtWvXkJiYWOLYKlWqlFiXSCTy88+cOQNHR0f07t0bP/30Ey5duoTFixejsLDwk7KPGjUK58+fR1BQEOLi4pCQkAAjIyOOrUdKS0VFBY0aNZK3aKayFxERAScnJ5iamsLT0xP5+fkAXr1n1a9fH1WrVoWNjQ3GjBkDOzs7nD9/Hjt37hQ5NREpK319faxatQpHjx7F/v37kZmZ+dHXEgQBW7ZsQbNmzbBw4UJ4e3vj5s2bGD9+PLS0tEoxNRER/ZOnpyf27NnDIdHKORY7iQgA0L17d6irq+Px48cYOHBgiX21a9dGvXr1cPv2bZiZmb2xvK9Tp06hXr16mDdvHtq0aQNzc/MS43kCQOPGjXHmzJkS2/65/k8nT57ExIkT0b9/fzRp0gR6enrsHkxKTyqVctxOkRw+fBhTp06FpaUlli1bhk2bNpUYt1hNTQ0HDx6Ek5MTLl68iBYtWmDv3r2oXr26iKmJqDxo0qQJfvnlF9SsWfOjr5Gbm4v79+8jJCQE8fHxGDZsGFRVVUsxJRERvUvNmjXx1VdfYePGjWJHoU+gJnYAIlIOEokEV65cgSAI0NDQeGO/n58fJk6ciOrVq6Nfv34oLCzExYsX8ccff2DWrFnvdQ+pVIo//vgDW7duRfv27REbG4vt27eXOMbb2xsjR45EmzZt0LVrV0RHR+Ps2bPQ19f/1+t+//33aNu2LXJzczF9+nSoq6t/2A+AqIxxkiJx5Ofnw93dHXPnzoWPjw8AIC0tDc+fP8eiRYtgaGgIc3Nz9OzZEytXrsSLFy+gqakpcmoiKk8kEgnU1D7+MUtXVxezZ88uxURERPQhvL290b9/f/j6+r7Rc5HKBxY7iUhOT0/vnfs8PDygo6ODZcuWYdasWdDS0kKTJk0wYcKE977+gAED4Ovri8mTJyM/Px+9evXCokWL4OXlJT/GwcEBqampmDNnDvLy8vDll19iypQpiIiIeOd1w8LCMHbsWNjY2KBu3brw8/P7pO5jRGXB3Nwcx44dEztGpbN+/Xq0atWqxHAdv/76K54+fQpjY2P88ccfMDQ0hJGRERo3bvzWL3+IiIiIqOJq3rw5zM3NER0djeHDh4sdhz4CZ2MnIiISwYkTJzBjxgzExcWJHaVSOXPmDNLT0zF48GCoqalh6dKlCAwMxPHjx9G0aVP89ddfaNSoEcaPH49vv/1W7LhEREREJIIff/wRS5cu/c8h1Ug5ccxOIiIiEbAbuzjatWuHQYMGQU1NDYWFhbCwsMCvv/6Kpk2bQiaTQV9fH7169YKurq7YUYmIiIhIJAMGDEBGRgaLneUUi51EREQiqF27Nl68eIGsrCyxo1QK2dnZ8j+/HkuvSpUqsLe3h42NDQBARUUFOTk5SE1NRY0aNUTJSURERETiU1VVxcSJExESEiJ2FPoILHYSERGJQCKRsHVnGfHx8UFAQADS09MBvPrZvx7FR0Xlfx+FZDIZpkyZgqKiIowfP16UrERERESkHEaPHo3Y2Fg8evRI7Cj0gThBERERkUikUilSUlJga2srdpQKa/PmzQgJCYG2tjZu3bqFKVOmwMbG5o2Zki9fvoygoCAcPXoUJ06cECktERERESmLatWqIS0t7V8n8iXlxJadREREImHLTsX666+/EB0djaVLl2Lfvn04d+4c3N3dsWfPHjx9+rTEsaamprC1tUV4eDjq168vUmIiIiIiUiZ6enqQSCRix6APxGInERGRSFjsVCwVFRX06tULTZo0Qffu3ZGcnAxzc3N4enpi5cqVSE1NBQDk5OQgOjoabm5u6Natm8ipiYiIiEhZsNBZPkmE14NWEREpwIoVK3D//n0EBQWJHYVI6Zw+fRre3t44d+6c2FEqrPz8fGhpaZXYFhQUhHnz5qFHjx6YOnUq1qxZg7S0NJw9e1aklEREREREVFo4ZicRKVRWVhZnNSZ6h9ctOwVB4LfGCvL3QmdxcTFUVVXh4+ODzp07Y8SIEbCzs0NeXh6uXr0qYkoiqugKCwtRpUoVsWMQEVEpys3NxenTp1GjRg1YWlpCR0dH7Ej0/9iNnYgUisVOonczMDAAADx58kTkJJWDqqoqBEGATCaDjY0NIiMjkZOTgy1btsDS0lLseERUga1fvx55eXlixyAiolLy5MkTDBw4ENOmTYOdnR28vb3FjkR/w27sRKRQr99i2GqN6O1sbW0RHByMDh06iB2lUvnrr7/Qrl07WFhY4KeffhI7DhFVYLdu3ULHjh1x7949qKurix2HiIg+gkwmw8GDB7Fx40bY2trCzMwMixYtQnBwMDQ1NTFmzBjMmjULrq6uYkclsGUnESmYRCJhoZPoX3CSIsV613e6giDAycmJhU4iUriwsDC4uLiw0ElEVI65urpi6tSpsLGxwfHjxzF//nz06tULvXr1QufOnTF27FisXr1a7Jj0/1jsJCIiEpFUKmWxU0EyMzNRUFDw1oKngYEBFixYIEIqIqpMioqKEBERAXd3d7GjEBHRR7px4wbOnj2LMWPGYMGCBYiNjYWXlxd27dolP+azzz6DhoYGMjMzRUxKr7HYSUREJCK27FSMoqIiDBkyBEFBQe9sXc5W50SkaDExMWjQoAGsrKzEjkJERB+poKAAMpkMjo6OAF59hnR0dMSTJ0/g7e0Nf39/BAYGokmTJqhZs+Y7exZR2WGxk4iISEQsdirGN998gypVqsDX11fsKERUiW3evJmtOomIyrlmzZpBEAT8/PPP8m3Hjx+Hubk5atWqhQMHDqBu3boYNWoUAH6hrgw4QREREZGInj59CmNjY2RnZ/ODUSn57bff4OLigosXL6JOnTpixyGiSurhw4do3Lgx7t69Cz09PbHjEBHRJ9i0aRPWrFmD7t27o3Xr1ti2bRvq1KmD0NBQ/PHHH6hatSrf65WImtgBiIiIKrPq1atDU1MTjx49YmGuFDx69AgjRoxAZGQkf55EJKrIyEgMHjyYD79ERBXAmDFjkJOTg++//x779u2DgYEB/Pz8AAD16tUD8Gq8+Jo1a4qYkl5jy04iIiKRdejQAUuXLkXnzp3FjlKuyWQy9O3bF61bt4a/v7/YcYioEhMEAZaWloiIiED79u3FjkNERKXk0aNHePbsGaRSKQDg2bNn2LdvH7777jtoaGigZs2aGDRoEL788kt+2SUijtlJRKWmuLi4xDq/SyF6Pxy3s3QEBgYiNzcXCxcuFDsKEVVyEokEN27cYKGTiKiCqVWrFqRSKQoKCrB48WKYm5vD1dUVmZmZGDx4MExNTREeHg4PDw+xo1Zq7MZORKVGVVW1xLpEIkFmZiZevHiB6tWr85stoneQSqUsdn6iU6dOISgoCPHx8VBT48cbIiIiIip9EokEMpkMixYtQnh4ODp16oTq1avjyZMnOHHiBKKjo3Hz5k106tQJhw4dQp8+fcSOXCmxZScRlYoXL15g7NixKCwsBAAUFBRg7dq1cHd3x5gxYzB58mQkJCSInJJIObFl56f566+/4OTkhNDQUBgbG4sdh4iIiIgqsPj4eKxYsQLTpk3Dhg0bEBYWhrVr1yI9PR3Lly+HVCqFo6MjVq5cKXbUSovFTiIqFY8ePUJoaCiqVKmCgoICrFmzBpMnT4aOjg7Mzc1x5swZ9OjRA+np6WJHJVI6LHZ+PEEQ4ObmhsGDB2PAgAFixyEiIiKiCu7s2bPo1q0bvL295RMS1atXD926dUNSUhIAoE+fPrCyssKLFy/EjFppsZ8XEZWKv/76C9WqVQMA3LlzB5s2bUJwcDC8vLwAvGr5aW9vj4CAAKxdu1bMqERKx8zMDLdv34ZMJoOKCr+H/BCrVq3Cn3/+id27d4sdhYiIiIgqAQMDAyQnJ6OoqAjq6uoAgJs3b2LLli2YNm0aAKBdu3bo0KEDNDU1xYxaafGJiohKRUZGBmrUqAEA8jf9kSNHQiaTobi4GJqamhg6dCguX74sclIi5aOnp4eqVavizz//FDtKuRIfH4/Fixdj586d8g+aRERi8/PzQ9OmTcWOQURECuLk5ARVVVXMnDkTYWFhCAsLw9y5c2Fubo5BgwYBAPT19VG9enWRk1ZeLHYSUal49uwZ0tLSEBISAn9/fwDAy5cvoaKiIp+4KCcn540Z24noFXZl/zDPnj2Do6MjvvvuOzRs2FDsOERUTri6ukIikcgXQ0ND2NnZ4fr162JHKxO///47JBIJHj9+LHYUIqJyLSIiAn/++ScWLlyI4OBgPH78GDNnzoSpqanY0Qjsxk5EpcTQ0BAtWrTATz/9hCdPnkAqleLBgwcwMDAA8KrQmZycDKlUKnJSIuVkbm6Omzdv4osvvhA7itITBAFjx45Fz549MWzYMLHjEFE506NHD0RFRQEA/vzzT/j6+uKrr75CcnKyyMn+XUFBAVuxExEpiY4dO6Jt27Z4+PAhsrKy0KxZM7Ej0d+wZScRlYquXbvi119/xdq1a7Fhwwb4+vqidu3a8v0pKSl4/vw5+vTpI2JKIuUllUrZsvM9bdq0CdevX+cMl0T0UTQ0NFCnTh3UqVMHrVq1go+PD65fv478/HykpaVBIpEgPj6+xDkSiQTR0dHy9T///BPOzs4wMDCAtrY2WrRogaNHj5Y4Z8eOHWjUqBH09PQwcODAEq0pz58/j169esHQ0BBVq1ZFp06dcPr06Tfu+d1332HQoEHQ0dHB7NmzAQBJSUno378/9PT0UKtWLQwfPhwPHz6Un3f16lV0794dVatWhZ6eHpo3b46jR48iLS1N/oVazZo1IZFI4OrqWio/UyKiykhNTQ1GRkYsdCohtuwkolJx5MgR5OTkyMcoeU0QBEgkErRq1Qrbtm0TKR2R8jM3N0dcXJzYMZTe1atXMWfOHJw4cQJaWlpixyGici4nJwc7d+5Es2bN3vs9JTc3F126dEGtWrXwww8/oF69em+MSZ6WloadO3fihx9+QG5uLhwdHTFnzhxs2LBBft8RI0YgJCQEEokEa9asQb9+/ZCSkgJDQ0P5dRYuXIhvv/0Wy5cvh0QiwYMHD9C5c2e4u7tj+fLlKCwsxJw5c/Dll1/izJkzUFFRgZOTE5o3b45z585BTU0NV69ehaamJoyNjbFnzx4MHjwYiYmJ0NfX5/soERFVSCx2ElGp2Lt3LzZs2IA+ffrAwcEBAwYMgL6+PiQSCYBXRU8A8nUiKoljdv633NxcDBs2DCtWrIClpaXYcYionDp06BB0dXUBvHpfMTY2xsGDB9/7/G3btuHhw4c4ffq0vDDZqFGjEscUFRUhIiIC1apVAwCMHTsW4eHh8v3dunUrcfzq1auxZ88eHDp0CC4uLvLtDg4O8PDwkK/Pnz8fzZs3R0BAgHzbli1boK+vj/j4eNja2iI9PR3Tpk2Tv0+amZnJj9XX1wcA1KpVq0RRlYiIPs3r512Az7zKgN3YiahUJCUloXfv3tDR0cHcuXMxatQobN26VT679OuJAIjo7Ro1aoQ7d+5wEq9/MWHCBLRt2xYjR44UOwoRlWOdO3dGQkICEhIScPbsWXTr1g29evXCvXv33uv8S5cuwdra+l+LhSYmJvJCJwDUrVsXGRkZ8vWMjAx4enpCKpWiWrVq0NPTQ0ZGBu7evVviOq1bty6xfuHCBRw/fhy6urryxdjYGABw+/ZtAMCUKVPg4eGBbt26wd/fv9JMvkREJCaJRAJ/f3+EhYWJHYXAYicRlZJHjx5h9OjRiIqKgr+/PwoKCjBjxgy4urpi165dJT7gE9GbtLW1YWho+N4P25VNVFQUTp8+jTVr1ogdhYjKOW1tbZiZmcHMzAy2trbYvHkzsrOzsXHjRqiovHo8+nsLncLCwhLn/33fu1SpUqXEukQigUwmk6+PGjUK58+fR1BQEOLi4pCQkAAjIyMUFBSUOE9HR6fEukwmQ//+/eXF2tdLSkoK7OzsAAB+fn5ISkrCwIEDERcXB2traz58ExGVAVtbW4SEhLzX7wlSLBY7iahU5OTkQFNTE5qamhg5ciQOHjyI4OBgSCQSuLm54csvv0RERMQbH+KJ6H/Ylf3tbty4gSlTpmDXrl3yrqdERKVFIpFARUUFeXl5qFmzJgDgwYMH8v0JCQkljm/VqhWuXLlSYsKhD3Xy5ElMnDgR/fv3R5MmTaCnp1finu/SqlUrJCYmwsTERF6wfb3o6enJjzM3N8ekSZNw4MABuLu7IzQ0FADks7mzFwERUenr2bMnioqK3piwjsoei51EVCpyc3PlDwhFRUVQVVXFkCFDEBsbi5iYGNStWxejR4+Wd2snojeZm5vj5s2bYsdQKvn5+Rg2bBgWL14Ma2trseMQUQXw8uVLPHz4EA8fPkRycjImTpyI58+fY8CAAdDS0kK7du0QEBCAxMRExMXFYdq0aSXOd3JyQq1atTBw4ECcOHECd+7cwf79+z/o4VYqleL7779HUlISzp8/D0dHR3kh8t98/fXXePbsGRwcHHD27Fmkpqbi8OHDGDt2LHJycpCfn4+vv/4av//+O9LS0nD27FmcPHkSVlZWAF51r5dIJDhw4AAyMzPx/PnzD/vhERHRO0kkEnh7eyMkJETsKJUei51EVCry8vLkY1Opqb2a+0wmk0EQBHTu3Bl79+7F5cuXYWRkJGZMIqXGlp1vmjp1KiwtLfF/7d15VJR14/7xa0BFRNx3UFkGzH3PLZfKlLQyLXctRE1ziRZT68mF7Gs9bqWp5YKaqGlKpWlpqWmZ9dXcfpqaLCEqivsCKghz//7oyDfCnYGbGd6vczgnZu65P9fwnPPIXHyWl156yewoAJzExo0bVbFiRVWsWFFNmjTRzp07tXLlSrVp00aSMpZ8N27cWIMGDdJ7772X6fUeHh7aunWrvLy89PTTT6tmzZoaN27cfe1NvmDBAiUlJalhw4bq0aOHQkJC5OPjc9fXVapUSb/88otcXFwUFBSkmjVraujQoXJzc5Obm5tcXV114cIFvfjii6pWrZo6d+6sZs2aadq0aZIkLy8vhYWF6T//+Y/Kly+vYcOG3XNmAMDd9e3bV9u3b8/YRxnmsBhsJgDADs6fP68SJUpk7HX1T4ZhyDCMWz4H4P+sWbNGc+bM0bp168yOkiesWrVKo0aN0u7duzMd9AEAAADkVaNGjVJKSoo++ugjs6PkW5SdAADkEYcOHVKnTp1Yyi4pNjZWTZs21bp169S4cWOz4wAAAAD3JD4+XvXq1VNcXJyKFStmdpx8iWlWAHLEzdmcAO6dn5+f4uPjlZaWZnYUU6WmpqpHjx56++23KTpVQdLOAAAgAElEQVQBAADgUKpUqaK2bdtq0aJFZkfJtyg7AeSIX3/9Vdu2bTM7BuBQ3NzcVLFiRcXFxZkdxVRvvfWWKlSooNDQULOjAAAAAPctNDRUM2bMkM1mMztKvkTZCSBHbNiwQZs2bTI7BuBw8vshRWvXrtXKlSu1cOHC+zrsAwAAAMgrmjdvrpIlS7IXv0koOwHkiAsXLqhkyZJmxwAcTkBAQL7ds/P48eMaMGCAli1bptKlS5sdBwAAAHggFotFoaGhmj59utlR8iXKTgA5grITeDD5dWZnWlqaevbsqdDQUD3yyCNmxwGAO2rWrJnWrl1rdgwAQB7WrVs3HTx4UAcOHDA7Sr5D2QkgR1B2Ag8mMDAwX5ad48ePl7u7u0aNGmV2FAC4oz/++EPx8fEKCgoyOwoAIA8rVKiQBg8ezOxOE1B2AsgRlJ3Ag8mPMzs3btyohQsXKiIiQi4u/GoCIG8LDw9XcHCwChQoYHYUAEAeN3jwYK1atUpnz541O0q+wicKADmCshN4MD4+PkpISFBqaqrZUXLFqVOn9MILL2jx4sUqX7682XEA4I5SUlK0ZMkShYSEmB0FAOAAypUrp2effVbz5s0zO0q+QtkJIEdQdgIPpmDBgqpcubJiY2PNjpLjbDab+vbtqwEDBujxxx83Ow4A3NWaNWtUq1Yt+fv7mx0FAOAgQkNDNWvWLN24ccPsKPkGZSeAHEHZCTy4/LKU/YMPPlBKSorGjh1rdhQAuCfh4eHq37+/2TEAAA6kXr16slqtioyMNDtKvkHZCcDurl27Jklyd3c3OQngmPJD2fnzzz9rxowZWrZsGfveAXAI8fHx2rlzp7p06WJ2FACAgwkNDeWgolxE2QnA7pjVCWRPQECAjhw5YnaMHHP27Fn17t1b4eHh8vb2NjsOANyThQsXqmfPnvwxFwBw35555hmdOnVKO3bsMDtKvkDZCcDuKDuB7AkMDHTamZ2GYahfv37q1q2bOnbsaHYcALgnNptNCxcuZAk7AOCBuLq6atiwYczuzCWUnQDsjrITyB5nXsb+0Ucf6fTp05o4caLZUQDgnm3atEmlSpVS/fr1zY4CAHBQ/fv313fffacTJ06YHcXpUXYCsDvKTiB7qlSpojNnzmTsf+ssduzYoffff1/Lly9XoUKFzI4DAPds/vz5GjBggNkxAAAOrESJEurVq5c++eQTs6M4PcpOAHZH2Qlkj6urq3x8fBQTE2N2FLu5dOmSevTooU8++US+vr5mxwGAe3b27Flt2LBBvXr1MjsKAMDBDR8+XHPnznW6SQ15DWUnALuj7ASyz5mWshuGoQEDBujJJ5/Uc889Z3YcALgvS5Ys0VNPPaUSJUqYHQUA4OCqVaumxo0ba9myZWZHcWqUnQDsjrITyD5nKjvnzJmjqKgoTZ061ewoAHBfDMNQeHg4S9gBAHYTGhqq6dOnyzAMs6M4LcpOAHZH2QlkX0BAgI4cOWJ2jGzbt2+fxowZoy+++EKFCxc2Ow4A3JedO3fq2rVrat26tdlRAABO4oknnlBaWpq2bNlidhSnRdkJwO4oO4Hsc4aZnUlJSerWrZs+/PBDBQYGmh0HAO7b/PnzFRISIovFYnYUAICTsFgseuWVVzR9+nSzozgtyk4AdkfZCWRfYGCgw5edQ4cOVYsWLdSnTx+zowDAfUtOTtaqVasUHBxsdhQAgJPp27evtm3b5lQHkuYllJ0A7I6yE8g+Ly8vXbx4UUlJSWZHeSCfffaZdu7cqY8//tjsKADwQFauXKkWLVqoUqVKZkcBADgZDw8P9e/fXzNnzjQ7ilOi7ARgd5SdQPa5uLjI399f0dHRZke5b4cOHdKIESP0xRdfyMPDw+w4APBA5s+fz8FEAIAcM3ToUC1evFiXL182O4rToewEYHeUnYB9OOK+ndeuXVP37t01ceJE1apVy+w4APBADh8+rJiYGHXo0MHsKAAAJ1WlShU99thjWrRokdlRnA5lJwC7o+wE7MMRy87XXntNNWvWZDYUAIe2YMECvfDCCypYsKDZUQAATuzVV1/Vxx9/LJvNZnYUp0LZCcCurl+/LpvNJnd3d7OjAA4vICBAR44cMTvGPVuxYoU2btyoOXPmcHIxAId148YNLV68WP379zc7CgDAyTVv3lzFixfXt99+a3YUp0LZCcCubs7qpOgAss+RZnbGxMRo+PDh+uKLL1SsWDGz4wDAA1u7dq0CAwMVGBhodhQAgJOzWCwKDQ3V9OnTzY7iVCg7AdgVS9gB+wkMDHSIsjMlJUXdu3fXO++8owYNGpgdBwCyJTw8nFmdAIBc061bNx04cEAHDhwwO4rToOwEYFeUnYD9VKhQQdeuXdOlS5fMjnJHo0ePlre3t4YPH252FADIlhMnTmj79u16/vnnzY4CAMgn3Nzc9PLLL2vGjBlmR3EalJ0A7IqyE7Afi8Uiq9Wap2d3rlmzRl999ZUWLFjA9hUAHN6iRYvUrVs3eXh4mB0FAJCPDBo0SCtXrtS5c+fMjuIUKDsB2BVlJ2BfeXnfzvj4eA0cOFDLli1TqVKlzI4DANlis9lYwg4AMEX58uXVqVMnzZ071+woToGyE4BdUXYC9pVXy84bN26oZ8+eev3119W8eXOz4wBAtm3ZskWenp5q1KiR2VEAAPlQaGioZs+erRs3bpgdxeFRdgKwK8pOwL7yatk5btw4eXp66s033zQ7CgDYRWRkpPr378+WHAAAU9SvX19+fn768ssvzY7i8Cg7AdgVZSdgXwEBATpy5IjZMTL5/vvvtXjxYi1evFguLvwqAcDxGYahmTNnaujQoWZHAQDkY6GhoZo+fbrZMRwen1AA2BVlJ2BfgYGBeWpm58mTJxUcHKyIiAiVK1fO7DgAYBcWi0UWi0Wurq5mRwEA5GOdOnXSyZMntWPHDrOjODSLYRiG2SEAOI+kpCS5uLioSJEiZkcBnIJhGCpZsqRiYmJUunRpU7Okp6erXbt2atmypcaPH29qFgAAAMAZTZ06Vbt379bSpUvNjuKwmNkJwK6KFi1K0QnYkcViyTP7dk6cOFE2m01jxowxOwoAAADglPr376/vvvtOCQkJZkdxWJSdAADkcXmh7Ny6datmzZqlpUuXsswTAAAAyCElSpRQz5499cknn5gdxWFRdgIAkMeZXXaeOXNGffr00cKFC1WpUiXTcgAAAAD5wSuvvKK5c+fq+vXrZkdxSAXMDgAAAO4sICBA69atM2Vsm82mF198Ub169dKTTz5pSgYAsIczZ85o9erVSktLk2EYqlOnjlq0aGF2LAAAsqhWrZoaNmyoZcuWKSQkxOw4DoeyEwCAPC4gIEBHjhwxZexp06bpwoULeu+990wZHwDsYfXq1Zo8ebL++OMPeXh4yMvLS2lpaapataq6du2qZ555Rh4eHmbHBAAgQ2hoqEaOHKl+/frJYrGYHcehsIwdAIA87uYydsMwcnXc//3f/9WkSZO0fPlyFSxYMFfHBgB7GjVqlJo0aaLY2FgdP35cU6ZMUbdu3ZSWlqZJkyYpPDzc7IgAAGTSrl073bhxQ1u2bDE7isOxGLn9yQkAANy3MmXK6I8//lD58uVzZbwLFy6oQYMGmjZtmjp37pwrYwJAToiNjVXz5s21a9cueXl5ZXru+PHjCg8PV1hYmJYuXaqePXualBIAgKw+/fRTrV+/Xl9//bXZURwKMzsBAHAAuXlIkWEYGjBggJ5++mmKTgAOz2KxqHTp0pozZ46kv/8/Lj09XYZhyNvbW+PGjVNwcLA2btyoGzdumJwWAID/07dvX23btk2xsbFmR3EolJ0AcpXNZsv1pbiAM8jNsnP27NmKi4vT5MmTc2U8AMhJvr6+6tq1q5YvX67ly5dLklxdXTPtf+bn56eDBw+yZQcAIE/x8PBQSEiIZs6caXYUh8IBRQByVVJSkgYOHKioqCgFBATIarVm+qpQoQKbLwO3kFtl5969ezV+/Hht375dbm5uOT4eAOQkwzBksVg0dOhQnTlzRn379tW7776rwYMHq3379rJYLNqzZ4+WLl2qIUOGmB0XAIAshg0bpvr16yssLEyenp5mx3EI7NkJINddvHhRUVFRio6OzvQVFRWlq1evZilAb35VqlRJLi5MSEf+tHz5ckVGRmrlypU5NsaVK1fUsGFDhYWFsW8dAKdx6dIlXblyRYZh6Ny5c1q1apWWLVumo0ePytfXV5cuXVKPHj300UcfydXV1ey4AABk0bVrV7Vq1UrDhw83O4pDoOwEkKdcunRJMTExtyxCL126JH9//1sWoZUrV6YIhVPbtWuXQkJCtG/fvhy5v2EY6tu3r9zd3TVv3rwcGQMActOlS5e0YMECvfvuu6pYsaLS09NVvnx5tW3bVs8++6wKFiyoPXv2qH79+qpevbrZcQEAuK1t27apX79++vPPP/ncew8oOwE4jKSkpCxF6M0ZoufOnZOvr2+WEjQgIECVK1dWgQLs2gHHdvnyZVWsWFFJSUk5stXDwoULNXXqVO3YsUNFihSx+/0BILeNHDlS27ZtU2hoqEqVKqWZM2fqm2++UcOGDeXh4aEpU6aoUaNGZscEAOCuDMNQo0aNFBYWpqeeesrsOHkeZScAp3D16lXFxsZmKUGjo6OVmJioqlWrZilBrVarqlatymEEcBgVKlTQrl275OXlZdf7Hjx4UK1bt9aWLVtUs2ZNu94bAMzi5eWluXPnqmPHjpKkM2fOqE+fPmrdurU2btyo48ePa926dQoICDA5KQAAdxcREaHFixfrhx9+MDtKnkfZCcDpXb9+XX/99VeWEjQ6OloJCQny9vbOUoJarVb5+vqqUKFCZscHMrRs2VITJkxQmzZt7HbPq1ev6uGHH9brr7+ukJAQu90XAMwUHR2trl27asaMGWrZsmXG4+XKldPOnTtVtWpVPfTQQxo8eLBeffXVjIOMAADIq1JSUuTj46ONGzcyQeEuKDsB5GupqamKi4u75YFJx44dU8WKFbOUoFarVX5+fipcuLDZ8ZHPhISEqFmzZho4cKDd7jlw4EBdu3ZNERERfNAH4BQMw1B6erq6dOmi4sWLa968ebp69aoiIiI0ceJEJSYmSpJGjBihuLg4LV++nO1uAAAOISwsTAkJCZozZ47ZUfI0/lUHkK8VKlRIgYGBCgwMzPLcjRs3FB8fn6kI3bx5s6KjoxUXF6dy5cplKUGtVqv8/f3Z8xA5IiAgQFFRUXa73+eff66tW7dq165dFJ0AnIbFYlGBAgX0/PPP6+WXX9b27dvl4eGhS5cuadKkSZmuTU1NpegEADiMwYMHq0mTJkpOTpaHh4fZcfIsZnYCwANIT09XfHx8ltmg0dHRio2NVenSpW95arzValXRokVzJeO1a9e0cuVK7du3T56enmrfvr0aN27MhzoHtmrVKi1dulRfffVVtu8VFRWl5s2b6/vvv1f9+vXtkA4A8p4zZ85owYIFOn36tF588UXVqVNHknT48GG1bt1a8+bN0zPPPGNySgAA7l1qaqokseXaHVB2AoCdpaen68SJE1lK0KioKMXExKh48eK3LUKLFy9utxzHjh3TBx98oKSkJEVERCgoKEiLFi1SuXLlJEk7d+7Uxo0bde3aNQUGBqpp06by9/fPNMOPPczyln379ql37946cOBAtu6TkpKi5s2bKyQkREOHDrVTOgBwDFeuXNGKFSu0efNmLVu2zOw4AADAzig7ASAX2Ww2nTx5MksJevO/ixQpkqUAvblUvmTJkvc1Vnp6uhISElS5cmU1bNhQrVu31nvvvZexxD44OFhnz55VoUKFdPz4cV2/fl3vvfdexgwXm80mFxcXXbx4UadOnVKFChVUokQJu/9McO+Sk5NVpkwZJScny8XF5YHvExoaqmPHjikyMpIyG0C+lJiYKMMwVKFCBbOjAAAAO6PsBIA8wjAMJSYm3rIEjYqKUsGCBbOUoO3atVPZsmXvWlhVqFBBb775pl577bWMkuzPP/+Uh4eHvL29ZbPZNGLECH322WfatWuXfHx8JP29zC8sLEzbt29XYmKiGjVqpEWLFslqteb0jwO34e3trV9++UVVq1Z9oNd//fXXeu2117R79+77LtABAAAAIK+j7AQAB2AYhs6ePZulBH3rrbdUq1atO5adycnJKleunBYsWKDu3bvf9rrz58+rXLly+vXXX9W4cWNJUosWLXT16lV9+umn8vb2Vv/+/XXjxg2tXbtW7u7udn+fuLtHH31U//nPf9S2bdv7fu3Ro0fVuHFjrVmzRk2bNs2BdACQ99z8uMNMdgAA8gdOqQAAB2CxWFS2bFmVLVtWzZo1u6fX3Nxv86+//pLFYsnYq/Ofz9+8tyStXr1aBQsWVEBAgCRp+/bt+vXXX7V3796MAx0+/PBD1axZU3/99Zdq1Khhr7eH+3DzRPb7LTtv3LihHj16aOTIkRSdAPKVV155RWPGjMny7yAAAHBOD77hFwAgT7PZbJKkQ4cOqVixYipVqlSm5/95+NCSJUs0btw4vfbaaypRooRSUlK0YcMGeXt7q06dOkpLS5MkFS9eXBUqVND+/ftz980gw82y836NGTNGJUuW1Ouvv54DqQAgb4qNjdXy5cvtegAgAADI25jZCQBO7uDBgypXrlzG/oyGYchms8nV1VXJyckaP368IiMjNWTIEI0ePVrS36d1Hzp0SIGBgZL+rzhNTExU2bJldenSpYx7sSwwdwUEBOinn366r9esX79eS5cu1e7du7N1sBEAOJqFCxeqd+/ecnNzMzsKAADIJZSdAOCEDMPQxYsXVbp0aR05ckQ+Pj4Zs1puFp379u1TaGioLl68qNmzZysoKChTeZmYmJixVP3mkvf4+Hi5urpmmSV685rExESVKVNGBQrwz0tOud+ZnQkJCerXr5+WL1+usmXL5mAyAMhb0tPTtXDhQn333XdmRwEAALmIT6MA4IROnDihdu3a6fr164qLi5Ovr6/mzJmj1q1bq0mTJoqIiNDUqVPVokULvf/++ypWrJikv/fvNAxDxYoV09WrV1W0aFFJkqurqyRp3759cnd3zzit/d+zOoOCgnT48GFVqVIly8nxVqtVPj4+KliwYO79IJyQv7+/4uLilJaWdtdSOT09Xb1799aQIUPUunXrXEoIAHnDhg0b5OXlpdq1a5sdBQAA5CJOYwcAJ2QYhvbv3689e/YoISFBu3bt0q5du9SgQQPNmDFDdevW1fnz5xUUFKRGjRqpWrVqCggIUO3ateXm5iYXFxf16dNHR48e1YoVK1SpUiVJUsOGDdWgQQNNnTo1oyD9t5SUFP31118ZJ8b/8/T4EydOyMvLK0sJarVa5evryzLDe1S1alVt3rxZ/v7+d7wuLCxMP/30k77//vuMwhoA8ovnnntO7du310svvWR2FAAAkIsoOwEgHzp8+LCioqK0detW7d+/X7GxsTp69KimT5+uQYMGycXFRXv27FGvXr3UsWNHdejQQZ9++qk2btyoH3/8UXXr1n2gcVNTU3X06NEsJWh0dLTi4+NVoUKFWxahfn5+cnd3t/NPwXE98cQTeuONNxQUFHTba3788Uf16tVLu3fvVsWKFXMxHQCYLzExUdWqVVN8fPxt/zgHAACcE2UnACCDzWbLdIDNV199pUmTJik2NlaNGzfW+PHj1ahRoxwZOy0tTfHx8VlK0OjoaP31118qW7ZslhLUarXK399fHh4eOZIprxoyZIiqV6+u4cOH3/L506dPq0GDBlqwYIHatWuXy+kAwHxTpkzRH3/8oYULF5odBQAA5DLKTgDZFhwcrLNnz2rt2rVmR0EOMvPk9fT0dB07dixLCRodHa3Y2FiVKFEiSwl688vT09OUzDkpOjpaRYoUydhe4J9sNps6duyoevXq6f333zchHQCYyzAM1ahRQ/PmzdMjjzxidhwAAJDLOKAIyAeCg4P12WefSZIKFCigkiVLqmbNmnr++ef10ksv5YkDY24eorNz584cmzmI7DGr6JT+PiDJx8dHPj4+atu2babnbDab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\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "show_map(node_colors)" + "show_map(romania_graph_data)" ] }, { @@ -883,144 +364,9 @@ }, { "cell_type": "code", - "execution_count": 145, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
    class SimpleProblemSolvingAgentProgram:\n",
    -       "\n",
    -       "    """Abstract framework for a problem-solving agent. [Figure 3.1]"""\n",
    -       "\n",
    -       "    def __init__(self, initial_state=None):\n",
    -       "        """State is an abstract representation of the state\n",
    -       "        of the world, and seq is the list of actions required\n",
    -       "        to get to a particular state from the initial state(root)."""\n",
    -       "        self.state = initial_state\n",
    -       "        self.seq = []\n",
    -       "\n",
    -       "    def __call__(self, percept):\n",
    -       "        """[Figure 3.1] Formulate a goal and problem, then\n",
    -       "        search for a sequence of actions to solve it."""\n",
    -       "        self.state = self.update_state(self.state, percept)\n",
    -       "        if not self.seq:\n",
    -       "            goal = self.formulate_goal(self.state)\n",
    -       "            problem = self.formulate_problem(self.state, goal)\n",
    -       "            self.seq = self.search(problem)\n",
    -       "            if not self.seq:\n",
    -       "                return None\n",
    -       "        return self.seq.pop(0)\n",
    -       "\n",
    -       "    def update_state(self, percept):\n",
    -       "        raise NotImplementedError\n",
    -       "\n",
    -       "    def formulate_goal(self, state):\n",
    -       "        raise NotImplementedError\n",
    -       "\n",
    -       "    def formulate_problem(self, state, goal):\n",
    -       "        raise NotImplementedError\n",
    -       "\n",
    -       "    def search(self, problem):\n",
    -       "        raise NotImplementedError\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "psource(SimpleProblemSolvingAgentProgram)" ] @@ -1055,8 +401,10 @@ }, { "cell_type": "code", - "execution_count": 146, - "metadata": {}, + "execution_count": null, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "class vacuumAgent(SimpleProblemSolvingAgentProgram):\n", @@ -1096,34 +444,24 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Left\n", - "Suck\n", - "Right\n" - ] - } - ], + "outputs": [], "source": [ - " state1 = [(0, 0), [(0, 0), \"Dirty\"], [(1, 0), [\"Dirty\"]]]\n", - " state2 = [(1, 0), [(0, 0), \"Dirty\"], [(1, 0), [\"Dirty\"]]]\n", - " state3 = [(0, 0), [(0, 0), \"Clean\"], [(1, 0), [\"Dirty\"]]]\n", - " state4 = [(1, 0), [(0, 0), \"Clean\"], [(1, 0), [\"Dirty\"]]]\n", - " state5 = [(0, 0), [(0, 0), \"Dirty\"], [(1, 0), [\"Clean\"]]]\n", - " state6 = [(1, 0), [(0, 0), \"Dirty\"], [(1, 0), [\"Clean\"]]]\n", - " state7 = [(0, 0), [(0, 0), \"Clean\"], [(1, 0), [\"Clean\"]]]\n", - " state8 = [(1, 0), [(0, 0), \"Clean\"], [(1, 0), [\"Clean\"]]]\n", + "state1 = [(0, 0), [(0, 0), \"Dirty\"], [(1, 0), [\"Dirty\"]]]\n", + "state2 = [(1, 0), [(0, 0), \"Dirty\"], [(1, 0), [\"Dirty\"]]]\n", + "state3 = [(0, 0), [(0, 0), \"Clean\"], [(1, 0), [\"Dirty\"]]]\n", + "state4 = [(1, 0), [(0, 0), \"Clean\"], [(1, 0), [\"Dirty\"]]]\n", + "state5 = [(0, 0), [(0, 0), \"Dirty\"], [(1, 0), [\"Clean\"]]]\n", + "state6 = [(1, 0), [(0, 0), \"Dirty\"], [(1, 0), [\"Clean\"]]]\n", + "state7 = [(0, 0), [(0, 0), \"Clean\"], [(1, 0), [\"Clean\"]]]\n", + "state8 = [(1, 0), [(0, 0), \"Clean\"], [(1, 0), [\"Clean\"]]]\n", "\n", - " a = vacuumAgent(state1)\n", + "a = vacuumAgent(state1)\n", "\n", - " print(a(state6)) \n", - " print(a(state1))\n", - " print(a(state3))" + "print(a(state6)) \n", + "print(a(state1))\n", + "print(a(state3))" ] }, { @@ -1134,157 +472,42 @@ "\n", "In this section, we have visualizations of the following searching algorithms:\n", "\n", - "1. Breadth First Tree Search - Implemented\n", - "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 - Implemented\n", - "6. Uniform Cost Search - Implemented\n", + "1. Breadth First Tree Search\n", + "2. Depth First Tree Search\n", + "3. Breadth First Search\n", + "4. Depth First Graph Search\n", + "5. Best First Graph Search\n", + "6. Uniform Cost Search\n", "7. Depth Limited Search\n", "8. Iterative Deepening Search\n", - "9. A\\*-Search - Implemented\n", + "9. A\\*-Search\n", "10. Recursive Best First Search\n", "\n", "We add the colors to the nodes to have a nice visualisation when displaying. So, these are the different colors we are using in these visuals:\n", "* Un-explored nodes - white\n", "* Frontier nodes - orange\n", "* Currently exploring node - red\n", - "* Already explored nodes - gray\n", - "\n", - "Now, we will define some helper methods to display interactive buttons and sliders when visualising search algorithms." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def final_path_colors(problem, solution):\n", - " \"returns a node_colors dict of the final path provided the problem and solution\"\n", - " \n", - " # get initial node colors\n", - " final_colors = dict(initial_node_colors)\n", - " # color all the nodes in solution and starting node to green\n", - " final_colors[problem.initial] = \"green\"\n", - " for node in solution:\n", - " final_colors[node] = \"green\" \n", - " return final_colors\n", - "\n", - "\n", - "def display_visual(user_input, algorithm=None, problem=None):\n", - " if user_input == False:\n", - " def slider_callback(iteration):\n", - " # don't show graph for the first time running the cell calling this function\n", - " try:\n", - " show_map(all_node_colors[iteration])\n", - " except:\n", - " pass\n", - " def visualize_callback(Visualize):\n", - " if Visualize is True:\n", - " button.value = False\n", - " \n", - " global all_node_colors\n", - " \n", - " iterations, all_node_colors, node = algorithm(problem)\n", - " solution = node.solution()\n", - " all_node_colors.append(final_path_colors(problem, solution))\n", - " \n", - " slider.max = len(all_node_colors) - 1\n", - " \n", - " for i in range(slider.max + 1):\n", - " slider.value = i\n", - " #time.sleep(.5)\n", - " \n", - " slider = widgets.IntSlider(min=0, max=1, step=1, value=0)\n", - " slider_visual = widgets.interactive(slider_callback, iteration = slider)\n", - " display(slider_visual)\n", - "\n", - " button = widgets.ToggleButton(value = False)\n", - " button_visual = widgets.interactive(visualize_callback, Visualize = button)\n", - " display(button_visual)\n", - " \n", - " if user_input == True:\n", - " node_colors = dict(initial_node_colors)\n", - " if algorithm == None:\n", - " algorithms = {\"Breadth First Tree Search\": breadth_first_tree_search,\n", - " \"Depth First Tree Search\": depth_first_tree_search,\n", - " \"Breadth First Search\": breadth_first_search,\n", - " \"Depth First Graph Search\": depth_first_graph_search,\n", - " \"Uniform Cost Search\": uniform_cost_search,\n", - " \"A-star Search\": astar_search}\n", - " algo_dropdown = widgets.Dropdown(description = \"Search algorithm: \",\n", - " options = sorted(list(algorithms.keys())),\n", - " value = \"Breadth First Tree Search\")\n", - " display(algo_dropdown)\n", - " \n", - " def slider_callback(iteration):\n", - " # don't show graph for the first time running the cell calling this function\n", - " try:\n", - " show_map(all_node_colors[iteration])\n", - " except:\n", - " pass\n", - " \n", - " def visualize_callback(Visualize):\n", - " if Visualize is True:\n", - " button.value = False\n", - " \n", - " problem = GraphProblem(start_dropdown.value, end_dropdown.value, romania_map)\n", - " global all_node_colors\n", - " \n", - " if algorithm == None:\n", - " user_algorithm = algorithms[algo_dropdown.value]\n", - " \n", - "# print(user_algorithm)\n", - "# print(problem)\n", - " \n", - " iterations, all_node_colors, node = user_algorithm(problem)\n", - " solution = node.solution()\n", - " all_node_colors.append(final_path_colors(problem, solution))\n", - "\n", - " slider.max = len(all_node_colors) - 1\n", - " \n", - " for i in range(slider.max + 1):\n", - " slider.value = i\n", - "# time.sleep(.5)\n", - " \n", - " start_dropdown = widgets.Dropdown(description = \"Start city: \",\n", - " options = sorted(list(node_colors.keys())), value = \"Arad\")\n", - " display(start_dropdown)\n", - "\n", - " end_dropdown = widgets.Dropdown(description = \"Goal city: \",\n", - " options = sorted(list(node_colors.keys())), value = \"Fagaras\")\n", - " display(end_dropdown)\n", - " \n", - " button = widgets.ToggleButton(value = False)\n", - " button_visual = widgets.interactive(visualize_callback, Visualize = button)\n", - " display(button_visual)\n", - " \n", - " slider = widgets.IntSlider(min=0, max=1, step=1, value=0)\n", - " slider_visual = widgets.interactive(slider_callback, iteration = slider)\n", - " display(slider_visual)" + "* Already explored nodes - gray" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## BREADTH-FIRST TREE SEARCH\n", + "## 1. BREADTH-FIRST TREE SEARCH\n", "\n", "We have a working implementation in search module. But as we want to interact with the graph while it is searching, we need to modify the implementation. Here's the modified breadth first tree search." ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "def tree_search(problem, frontier):\n", + "def tree_search_for_vis(problem, frontier):\n", " \"\"\"Search through the successors of a problem to find a goal.\n", " The argument frontier should be an empty queue.\n", " Don't worry about repeated paths to a state. [Figure 3.7]\"\"\"\n", @@ -1292,7 +515,7 @@ " # 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", + " node_colors = {k : 'white' for k in problem.graph.nodes()}\n", " \n", " #Adding first node to the queue\n", " frontier.append(Node(problem.initial))\n", @@ -1333,7 +556,7 @@ "\n", "def breadth_first_tree_search(problem):\n", " \"Search the shallowest nodes in the search tree first.\"\n", - " iterations, all_node_colors, node = tree_search(problem, FIFOQueue())\n", + " iterations, all_node_colors, node = tree_search_for_vis(problem, FIFOQueue())\n", " return(iterations, all_node_colors, node)" ] }, @@ -1346,45 +569,29 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d55324f7343a4c71a9a2d4da6d037037" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "b07a3813dd724c51a9b37f646cf2be25" - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "all_node_colors = []\n", "romania_problem = GraphProblem('Arad', 'Fagaras', romania_map)\n", - "display_visual(user_input = False, algorithm = breadth_first_tree_search, problem = romania_problem)" + "a, b, c = breadth_first_tree_search(romania_problem)\n", + "display_visual(romania_graph_data, user_input=False, \n", + " algorithm=breadth_first_tree_search, \n", + " problem=romania_problem)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Depth-First Tree Search:\n", + "## 2. Depth-First Tree Search:\n", "Now let's discuss another searching algorithm, Depth-First Tree Search." ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": { "collapsed": true }, @@ -1394,38 +601,21 @@ " \"Search the deepest nodes in the search tree first.\"\n", " # This algorithm might not work in case of repeated paths\n", " # and may run into an infinite while loop.\n", - " iterations, all_node_colors, node = tree_search(problem, Stack())\n", + " iterations, all_node_colors, node = tree_search_for_vis(problem, Stack())\n", " return(iterations, all_node_colors, node)" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "523b10cf84e54798a044ee714b864b52" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "aecea953f6a448c192ac8e173cf46e35" - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "all_node_colors = []\n", "romania_problem = GraphProblem('Arad', 'Oradea', romania_map)\n", - "display_visual(user_input = False, algorithm = depth_first_tree_search, problem = romania_problem)" + "display_visual(romania_graph_data, user_input=False, \n", + " algorithm=depth_first_tree_search, \n", + " problem=romania_problem)" ] }, { @@ -1434,14 +624,14 @@ "collapsed": true }, "source": [ - "## BREADTH-FIRST SEARCH\n", + "## 3. BREADTH-FIRST GRAPH SEARCH\n", "\n", "Let's change all the `node_colors` to starting position and define a different problem statement." ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": { "collapsed": true }, @@ -1453,7 +643,7 @@ " # 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", + " node_colors = {k : 'white' for k in problem.graph.nodes()}\n", " \n", " node = Node(problem.initial)\n", " \n", @@ -1505,58 +695,41 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "735a3dea191a42b6bd97fdfd337ea3e7" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "ef445770d70a4b7c9d1544b98a55ca4d" - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "all_node_colors = []\n", "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)\n", - "display_visual(user_input = False, algorithm = breadth_first_search, problem = romania_problem)" + "display_visual(romania_graph_data, user_input=False, \n", + " algorithm=breadth_first_search, \n", + " problem=romania_problem)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Depth-First Graph Search: \n", + "## 4. Depth-First Graph Search: \n", "Although we have a working implementation in search module, we have to make a few changes in the algorithm to make it suitable for visualization." ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "def graph_search(problem, frontier):\n", + "def graph_search_for_vis(problem, frontier):\n", " \"\"\"Search through the successors of a problem to find a goal.\n", " The argument frontier should be an empty queue.\n", " If two paths reach a state, only use the first one. [Figure 3.7]\"\"\"\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", + " node_colors = {k : 'white' for k in problem.graph.nodes()}\n", " \n", " frontier.append(Node(problem.initial))\n", " explored = set()\n", @@ -1603,58 +776,41 @@ "\n", "def depth_first_graph_search(problem):\n", " \"\"\"Search the deepest nodes in the search tree first.\"\"\"\n", - " iterations, all_node_colors, node = graph_search(problem, Stack())\n", + " iterations, all_node_colors, node = graph_search_for_vis(problem, Stack())\n", " return(iterations, all_node_colors, node)" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "61149ffbc02846af97170f8975d4f11d" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "90b1f8f77fdb4207a3570fbe88a0bdf6" - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "all_node_colors = []\n", "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)\n", - "display_visual(user_input = False, algorithm = depth_first_graph_search, problem = romania_problem)" + "display_visual(romania_graph_data, user_input=False, \n", + " algorithm=depth_first_graph_search, \n", + " problem=romania_problem)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## BEST FIRST SEARCH\n", + "## 5. BEST FIRST SEARCH\n", "\n", "Let's change all the `node_colors` to starting position and define a different problem statement." ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "def best_first_graph_search(problem, f):\n", + "def best_first_graph_search_for_vis(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", @@ -1666,7 +822,7 @@ " # 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", + " node_colors = {k : 'white' for k in problem.graph.nodes()}\n", " \n", " f = memoize(f, 'f')\n", " node = Node(problem.initial)\n", @@ -1728,14 +884,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## UNIFORM COST SEARCH\n", + "## 6. UNIFORM COST SEARCH\n", "\n", "Let's change all the `node_colors` to starting position and define a different problem statement." ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": { "collapsed": true }, @@ -1744,38 +900,21 @@ "def uniform_cost_search(problem):\n", " \"[Figure 3.14]\"\n", " #Uniform Cost Search uses Best First Search algorithm with f(n) = g(n)\n", - " iterations, all_node_colors, node = best_first_graph_search(problem, lambda node: node.path_cost)\n", - " return(iterations, all_node_colors, node)" + " iterations, all_node_colors, node = best_first_graph_search_for_vis(problem, lambda node: node.path_cost)\n", + " return(iterations, all_node_colors, node)\n" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "46b8200b4a8f47e7b18145234a8469da" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "ca9b2d01bbd5458bb037585c719d73fc" - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "all_node_colors = []\n", "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)\n", - "display_visual(user_input = False, algorithm = uniform_cost_search, problem = romania_problem)" + "display_visual(romania_graph_data, user_input=False, \n", + " algorithm=uniform_cost_search, \n", + " problem=romania_problem)" ] }, { @@ -1788,7 +927,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": { "collapsed": true }, @@ -1799,52 +938,35 @@ " 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)" + " iterations, all_node_colors, node = best_first_graph_search_for_vis(problem, lambda n: h(n))\n", + " return(iterations, all_node_colors, node)\n" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "e3ddd0260d7d4a8aa62d610976b9568a" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "dae485b1f4224c34a88de42d252da76c" - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "all_node_colors = []\n", "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)\n", - "display_visual(user_input = False, algorithm = greedy_best_first_search, problem = romania_problem)" + "display_visual(romania_graph_data, user_input=False, \n", + " algorithm=greedy_best_first_search, \n", + " problem=romania_problem)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## A\\* SEARCH\n", + "## 9. A\\* SEARCH\n", "\n", "Let's change all the `node_colors` to starting position and define a different problem statement." ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": { "collapsed": true }, @@ -1855,97 +977,41 @@ " You need to specify the h function when you call astar_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: n.path_cost + h(n))\n", - " return(iterations, all_node_colors, node)" + " iterations, all_node_colors, node = best_first_graph_search_for_vis(problem, \n", + " lambda n: n.path_cost + h(n))\n", + " return(iterations, all_node_colors, node)\n" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "15a78d815f0c4ea589cdd5ad40bc8794" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "10450687dd574be2a380e9e40403fa83" - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "all_node_colors = []\n", "romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)\n", - "display_visual(user_input = False, algorithm = astar_search, problem = romania_problem)" + "display_visual(romania_graph_data, user_input=False, \n", + " algorithm=astar_search, \n", + " problem=romania_problem)" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": { "scrolled": false }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "9019790cf8324d73966373bb3f5373a8" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "b8a3195598da472d996e4e8b81595cb7" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "aabe167a0d6440f0a020df8a85a9206c" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "25d146d187004f4f9db6a7dccdbc7e93" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "68d532810a9e46309415fd353c474a4d" - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "all_node_colors = []\n", - "# display_visual(user_input = True, algorithm = breadth_first_tree_search)\n", - "display_visual(user_input = True)" + "# display_visual(romania_graph_data, user_input=True, algorithm=breadth_first_tree_search)\n", + "algorithms = { \"Breadth First Tree Search\": breadth_first_tree_search,\n", + " \"Depth First Tree Search\": depth_first_tree_search,\n", + " \"Breadth First Search\": breadth_first_search,\n", + " \"Depth First Graph Search\": depth_first_graph_search,\n", + " \"Uniform Cost Search\": uniform_cost_search,\n", + " \"A-star Search\": astar_search}\n", + "display_visual(romania_graph_data, algorithm=algorithms, user_input=True)" ] }, { @@ -1982,7 +1048,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": { "collapsed": true }, @@ -2035,57 +1101,9 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "True\n", - "Number of explored nodes by the following heuristic are: 145\n", - "[2, 4, 3, 1, 5, 6, 7, 8, 0]\n", - "[2, 4, 3, 1, 5, 6, 7, 0, 8]\n", - "[2, 4, 3, 1, 0, 6, 7, 5, 8]\n", - "[2, 0, 3, 1, 4, 6, 7, 5, 8]\n", - "[0, 2, 3, 1, 4, 6, 7, 5, 8]\n", - "[1, 2, 3, 0, 4, 6, 7, 5, 8]\n", - "[1, 2, 3, 4, 0, 6, 7, 5, 8]\n", - "[1, 2, 3, 4, 5, 6, 7, 0, 8]\n", - "[1, 2, 3, 4, 5, 6, 7, 8, 0]\n", - "Number of explored nodes by the following heuristic are: 153\n", - "[2, 4, 3, 1, 5, 6, 7, 8, 0]\n", - "[2, 4, 3, 1, 5, 6, 7, 0, 8]\n", - "[2, 4, 3, 1, 0, 6, 7, 5, 8]\n", - "[2, 0, 3, 1, 4, 6, 7, 5, 8]\n", - "[0, 2, 3, 1, 4, 6, 7, 5, 8]\n", - "[1, 2, 3, 0, 4, 6, 7, 5, 8]\n", - "[1, 2, 3, 4, 0, 6, 7, 5, 8]\n", - "[1, 2, 3, 4, 5, 6, 7, 0, 8]\n", - "[1, 2, 3, 4, 5, 6, 7, 8, 0]\n", - "Number of explored nodes by the following heuristic are: 145\n", - "[2, 4, 3, 1, 5, 6, 7, 8, 0]\n", - "[2, 4, 3, 1, 5, 6, 7, 0, 8]\n", - "[2, 4, 3, 1, 0, 6, 7, 5, 8]\n", - "[2, 0, 3, 1, 4, 6, 7, 5, 8]\n", - "[0, 2, 3, 1, 4, 6, 7, 5, 8]\n", - "[1, 2, 3, 0, 4, 6, 7, 5, 8]\n", - "[1, 2, 3, 4, 0, 6, 7, 5, 8]\n", - "[1, 2, 3, 4, 5, 6, 7, 0, 8]\n", - "[1, 2, 3, 4, 5, 6, 7, 8, 0]\n", - "Number of explored nodes by the following heuristic are: 169\n", - "[2, 4, 3, 1, 5, 6, 7, 8, 0]\n", - "[2, 4, 3, 1, 5, 6, 7, 0, 8]\n", - "[2, 4, 3, 1, 0, 6, 7, 5, 8]\n", - "[2, 0, 3, 1, 4, 6, 7, 5, 8]\n", - "[0, 2, 3, 1, 4, 6, 7, 5, 8]\n", - "[1, 2, 3, 0, 4, 6, 7, 5, 8]\n", - "[1, 2, 3, 4, 0, 6, 7, 5, 8]\n", - "[1, 2, 3, 4, 5, 6, 7, 0, 8]\n", - "[1, 2, 3, 4, 5, 6, 7, 8, 0]\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "# Solving the puzzle \n", "puzzle = EightPuzzle()\n", @@ -2117,124 +1135,11 @@ }, { "cell_type": "code", - "execution_count": 33, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

    \n", - "\n", - "
    def hill_climbing(problem):\n",
    -       "    """From the initial node, keep choosing the neighbor with highest value,\n",
    -       "    stopping when no neighbor is better. [Figure 4.2]"""\n",
    -       "    current = Node(problem.initial)\n",
    -       "    while True:\n",
    -       "        neighbors = current.expand(problem)\n",
    -       "        if not neighbors:\n",
    -       "            break\n",
    -       "        neighbor = argmax_random_tie(neighbors,\n",
    -       "                                     key=lambda node: problem.value(node.state))\n",
    -       "        if problem.value(neighbor.state) <= problem.value(current.state):\n",
    -       "            break\n",
    -       "        current = neighbor\n",
    -       "    return current.state\n",
    -       "
    \n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "psource(hill_climbing)" ] @@ -2252,7 +1157,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "metadata": { "collapsed": true }, @@ -2304,17 +1209,11 @@ }, { "cell_type": "code", - "execution_count": 35, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['Arad', 'Bucharest', 'Craiova', 'Drobeta', 'Eforie', 'Fagaras', 'Giurgiu', 'Hirsova', 'Iasi', 'Lugoj', 'Mehadia', 'Neamt', 'Oradea', 'Pitesti', 'Rimnicu', 'Sibiu', 'Timisoara', 'Urziceni', 'Vaslui', 'Zerind']\n" - ] - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "distances = {}\n", "all_cities = []\n", @@ -2336,7 +1235,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": { "collapsed": true }, @@ -2363,7 +1262,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "metadata": { "collapsed": true }, @@ -2412,7 +1311,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "metadata": { "collapsed": true }, @@ -2431,39 +1330,11 @@ }, { "cell_type": "code", - "execution_count": 39, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['Fagaras',\n", - " 'Neamt',\n", - " 'Iasi',\n", - " 'Vaslui',\n", - " 'Hirsova',\n", - " 'Eforie',\n", - " 'Urziceni',\n", - " 'Bucharest',\n", - " 'Giurgiu',\n", - " 'Pitesti',\n", - " 'Craiova',\n", - " 'Drobeta',\n", - " 'Mehadia',\n", - " 'Lugoj',\n", - " 'Timisoara',\n", - " 'Arad',\n", - " 'Zerind',\n", - " 'Oradea',\n", - " 'Sibiu',\n", - " 'Rimnicu']" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "hill_climbing(tsp)" ] @@ -2587,122 +1458,11 @@ }, { "cell_type": "code", - "execution_count": 2, - "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" - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "psource(genetic_algorithm)" ] @@ -2739,114 +1499,11 @@ }, { "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" - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "psource(recombine)" ] @@ -2862,121 +1519,11 @@ }, { "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" - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "psource(mutate)" ] @@ -2992,122 +1539,11 @@ }, { "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" - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "psource(init_population)" ] @@ -3159,7 +1595,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3179,7 +1615,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3205,7 +1641,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3223,7 +1659,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3241,7 +1677,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3266,7 +1702,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3284,7 +1720,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3295,7 +1731,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3314,7 +1750,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3336,7 +1772,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3354,7 +1790,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3372,17 +1808,11 @@ }, { "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" - ] - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "print(current_best)" ] @@ -3396,17 +1826,11 @@ }, { "cell_type": "code", - "execution_count": 45, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "jFmFNicvmjVodrtVH\n" - ] - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "current_best_string = ''.join(current_best)\n", "print(current_best_string)" @@ -3425,7 +1849,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3449,7 +1873,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3480,122 +1904,11 @@ }, { "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" - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "psource(genetic_algorithm)" ] @@ -3609,17 +1922,11 @@ }, { "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" - ] - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "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)" @@ -3662,7 +1969,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3687,17 +1994,11 @@ }, { "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[['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" - ] - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "population = init_population(8, ['R', 'G'], 4)\n", "print(population)" @@ -3714,7 +2015,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3733,17 +2034,11 @@ }, { "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['R', 'G', 'R', 'G']\n" - ] - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "solution = genetic_algorithm(population, fitness, gene_pool=['R', 'G'])\n", "print(solution)" @@ -3758,17 +2053,11 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "4\n" - ] - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "print(fitness(solution))" ] @@ -3803,17 +2092,11 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[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" - ] - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "population = init_population(100, range(8), 8)\n", "print(population[:5])" @@ -3834,7 +2117,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": { "collapsed": true }, @@ -3866,18 +2149,11 @@ }, { "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[5, 0, 6, 3, 7, 4, 1, 3]\n", - "26\n" - ] - } - ], + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "solution = genetic_algorithm(population, fitness, f_thres=25, gene_pool=range(8))\n", "print(solution)\n", @@ -3915,7 +2191,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.6.3" } }, "nbformat": 4, diff --git a/search.py b/search.py index ac834d80c..a80a48c8c 100644 --- a/search.py +++ b/search.py @@ -109,10 +109,10 @@ def expand(self, problem): def child_node(self, problem, action): """[Figure 3.10]""" - next = problem.result(self.state, action) - return Node(next, self, action, + next_node = problem.result(self.state, action) + return Node(next_node, self, action, problem.path_cost(self.path_cost, self.state, - action, next)) + action, next_node)) def solution(self): """Return the sequence of actions to go from the root to this node.""" @@ -163,7 +163,7 @@ def __call__(self, percept): return None return self.seq.pop(0) - def update_state(self, percept): + def update_state(self, state, percept): raise NotImplementedError def formulate_goal(self, state): @@ -182,7 +182,7 @@ def search(self, problem): def tree_search(problem, frontier): """Search through the successors of a problem to find a goal. The argument frontier should be an empty queue. - Don't worry about repeated paths to a state. [Figure 3.7]""" + Repeats infinites in case of loops. [Figure 3.7]""" frontier.append(Node(problem.initial)) while frontier: node = frontier.pop() @@ -195,6 +195,7 @@ def tree_search(problem, frontier): def graph_search(problem, frontier): """Search through the successors of a problem to find a goal. The argument frontier should be an empty queue. + Does not get trapped by loops. If two paths reach a state, only use the first one. [Figure 3.7]""" frontier.append(Node(problem.initial)) explored = set() @@ -225,7 +226,11 @@ def depth_first_graph_search(problem): def breadth_first_search(problem): - """[Figure 3.11]""" + """[Figure 3.11] + Note that this function can be implemented in a + single line as below: + return graph_search(problem, FIFOQueue()) + """ node = Node(problem.initial) if problem.goal_test(node.state): return node @@ -571,10 +576,10 @@ def simulated_annealing(problem, schedule=exp_schedule()): neighbors = current.expand(problem) if not neighbors: return current.state - next = random.choice(neighbors) - delta_e = problem.value(next.state) - problem.value(current.state) + next_choice = random.choice(neighbors) + delta_e = problem.value(next_choice.state) - problem.value(current.state) if delta_e > 0 or probability(math.exp(delta_e / T)): - current = next + current = next_choice def simulated_annealing_full(problem, schedule=exp_schedule()): """ This version returns all the states encountered in reaching @@ -589,10 +594,10 @@ def simulated_annealing_full(problem, schedule=exp_schedule()): neighbors = current.expand(problem) if not neighbors: return current.state - next = random.choice(neighbors) - delta_e = problem.value(next.state) - problem.value(current.state) + next_choice = random.choice(neighbors) + delta_e = problem.value(next_choice.state) - problem.value(current.state) if delta_e > 0 or probability(math.exp(delta_e / T)): - current = next + current = next_choice def and_or_graph_search(problem): """[Figure 4.11]Used when the environment is nondeterministic and completely observable. @@ -730,10 +735,10 @@ def __init__(self, initial, goal, graph): self.graph = graph def actions(self, state): - return self.graph.dict[state].keys() + return self.graph.graph_dict[state].keys() def output(self, state, action): - return self.graph.dict[state][action] + return self.graph.graph_dict[state][action] def h(self, state): """Returns least possible cost to reach a goal for the given state.""" @@ -920,16 +925,16 @@ class Graph: length of the link from A to B. 'Lengths' can actually be any object at all, and nodes can be any hashable object.""" - def __init__(self, dict=None, directed=True): - self.dict = dict or {} + def __init__(self, graph_dict=None, directed=True): + self.graph_dict = graph_dict or {} self.directed = directed if not directed: self.make_undirected() def make_undirected(self): """Make a digraph into an undirected graph by adding symmetric edges.""" - for a in list(self.dict.keys()): - for (b, dist) in self.dict[a].items(): + for a in list(self.graph_dict.keys()): + for (b, dist) in self.graph_dict[a].items(): self.connect1(b, a, dist) def connect(self, A, B, distance=1): @@ -941,13 +946,13 @@ def connect(self, A, B, distance=1): def connect1(self, A, B, distance): """Add a link from A to B of given distance, in one direction only.""" - self.dict.setdefault(A, {})[B] = distance + self.graph_dict.setdefault(A, {})[B] = distance def get(self, a, b=None): """Return a link distance or a dict of {node: distance} entries. .get(a,b) returns the distance or None; .get(a) returns a dict of {node: distance} entries, possibly {}.""" - links = self.dict.setdefault(a, {}) + links = self.graph_dict.setdefault(a, {}) if b is None: return links else: @@ -955,12 +960,15 @@ def get(self, a, b=None): def nodes(self): """Return a list of nodes in the graph.""" - return list(self.dict.keys()) + s1 = set([k for k in self.graph_dict.keys()]) + s2 = set([k2 for v in self.graph_dict.values() for k2, v2 in v.items()]) + nodes = s1.union(s2) + return list(nodes) -def UndirectedGraph(dict=None): +def UndirectedGraph(graph_dict=None): """Build a Graph where every edge (including future ones) goes both ways.""" - return Graph(dict=dict, directed=False) + return Graph(graph_dict = graph_dict, directed=False) def RandomGraph(nodes=list(range(10)), min_links=2, width=400, height=300, @@ -1097,7 +1105,7 @@ def path_cost(self, cost_so_far, A, action, B): def find_min_edge(self): """Find minimum value of edges.""" m = infinity - for d in self.graph.dict.values(): + for d in self.graph.graph_dict.values(): local_min = min(d.values()) m = min(m, local_min) From 14a704b11d342233ea730d07716f57b73dd34e73 Mon Sep 17 00:00:00 2001 From: Nouman Ahmed <35970677+Noumanmufc1@users.noreply.github.com> Date: Thu, 15 Mar 2018 03:57:15 +0500 Subject: [PATCH 072/269] Added air_cargo to planning.ipynb (#835) * Added air_cargo to planning.ipynb * Some style issues --- README.md | 2 +- planning.ipynb | 152 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 112 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 968632477..3ab5777c1 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 9.3 | FOL-FC-Ask | `fol_fc_ask` | [`logic.py`][logic] | Done | | | 9.6 | FOL-BC-Ask | `fol_bc_ask` | [`logic.py`][logic] | Done | | | 9.8 | Append | | | | | -| 10.1 | Air-Cargo-problem | `air_cargo` | [`planning.py`][planning] | Done | | +| 10.1 | Air-Cargo-problem | `air_cargo` | [`planning.py`][planning] | Done | Included | | 10.2 | Spare-Tire-Problem | `spare_tire` | [`planning.py`][planning] | Done | | | 10.3 | Three-Block-Tower | `three_block_tower` | [`planning.py`][planning] | Done | | | 10.7 | Cake-Problem | `have_cake_and_eat_cake_too` | [`planning.py`][planning] | Done | | diff --git a/planning.ipynb b/planning.ipynb index 1054f1ee8..ca648a3a0 100644 --- a/planning.ipynb +++ b/planning.ipynb @@ -23,9 +23,7 @@ { "cell_type": "code", "execution_count": 1, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from planning import *" @@ -51,9 +49,7 @@ { "cell_type": "code", "execution_count": 2, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "%psource Action" @@ -83,9 +79,7 @@ { "cell_type": "code", "execution_count": 3, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "%psource PDDL" @@ -110,9 +104,7 @@ { "cell_type": "code", "execution_count": 4, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from utils import *\n", @@ -141,9 +133,7 @@ { "cell_type": "code", "execution_count": 5, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "knowledge_base.extend([\n", @@ -163,9 +153,7 @@ { "cell_type": "code", "execution_count": 6, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -203,9 +191,7 @@ { "cell_type": "code", "execution_count": 7, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "#Sibiu to Bucharest\n", @@ -261,9 +247,7 @@ { "cell_type": "code", "execution_count": 8, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "#Drive\n", @@ -284,9 +268,7 @@ { "cell_type": "code", "execution_count": 9, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "def goal_test(kb):\n", @@ -303,31 +285,119 @@ { "cell_type": "code", "execution_count": 10, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "prob = PDDL(knowledge_base, [fly_s_b, fly_b_s, fly_s_c, fly_c_s, fly_b_c, fly_c_b, drive], goal_test)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Air Cargo Problem:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Air Cargo problem involves loading and unloading of cargo and flying it from place to place. The problem can be with defined with three actions: Load, Unload and Fly. Let us now define an object of `air_cargo` problem:" + ] + }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 15, + "metadata": {}, "outputs": [], - "source": [] + "source": [ + "airCargo = air_cargo()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, before taking any actions, we will check the `airCargo` if it has completed the goal it is required to do:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False\n" + ] + } + ], + "source": [ + "print(airCargo.goal_test())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, it hasn't completed the goal. Now, we define the sequence of actions that it should take in order to achieve\n", + "the goal. Then the `airCargo` acts on each of them." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "solution = [expr(\"Load(C1 , P1, SFO)\"),\n", + " expr(\"Fly(P1, SFO, JFK)\"),\n", + " expr(\"Unload(C1, P1, JFK)\"),\n", + " expr(\"Load(C2, P2, JFK)\"),\n", + " expr(\"Fly(P2, JFK, SFO)\"),\n", + " expr(\"Unload (C2, P2, SFO)\")] \n", + "\n", + "for action in solution:\n", + " airCargo.act(action)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As the `airCargo` has taken all the steps it needed in order to achieve the goal, we can now check if it has acheived its goal:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "airCargo.goal_test()" + ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], - "source": [] + "source": [ + "It has now achieved its goal." + ] } ], "metadata": { @@ -346,9 +416,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.3" + "version": "3.6.4" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } From 80c48c838fd963093791745ce7aca7a00cc3e662 Mon Sep 17 00:00:00 2001 From: Rahul Goswami Date: Thu, 15 Mar 2018 04:38:03 +0530 Subject: [PATCH 073/269] fixed all instances of issue #833 (#843) * test commit * agents.ipynb * agents.ipynb * Fixed all the instances of issue #833 * minor fix and cleared change in agents.ipynb --- agents.py | 12 ++++++------ csp.py | 4 ++-- knowledge.py | 53 ++++++++++++++++++++++---------------------------- logic.py | 7 ++++--- nlp.py | 2 +- notebook.py | 46 +++++++++++++++++++++---------------------- planning.py | 32 ++++++++++++++++-------------- probability.py | 11 ++++++----- rl.py | 30 ++++++++++++++-------------- text.py | 24 ++++++++++++----------- 10 files changed, 110 insertions(+), 111 deletions(-) diff --git a/agents.py b/agents.py index 9b1ff0d33..eb085757a 100644 --- a/agents.py +++ b/agents.py @@ -96,7 +96,7 @@ def program(percept): self.program = program def can_grab(self, thing): - """Returns True if this agent can grab this thing. + """Return True if this agent can grab this thing. Override for appropriate subclasses of Agent and Thing.""" return False @@ -444,7 +444,7 @@ def move_to(self, thing, destination): return thing.bump def add_thing(self, thing, location=(1, 1), exclude_duplicate_class_items=False): - """Adds things to the world. If (exclude_duplicate_class_items) then the item won't be + """Add things to the world. If (exclude_duplicate_class_items) then the item won't be added if the location has at least one item of the same class.""" if (self.is_inbounds(location)): if (exclude_duplicate_class_items and @@ -809,7 +809,7 @@ def init_world(self, program): self.add_thing(Explorer(program), (1, 1), True) def get_world(self, show_walls=True): - """Returns the items in the world""" + """Return the items in the world""" result = [] x_start, y_start = (0, 0) if show_walls else (1, 1) @@ -826,7 +826,7 @@ def get_world(self, show_walls=True): return result def percepts_from(self, agent, location, tclass=Thing): - """Returns percepts from a given location, + """Return percepts from a given location, and replaces some items with percepts from chapter 7.""" thing_percepts = { Gold: Glitter(), @@ -846,7 +846,7 @@ def percepts_from(self, agent, location, tclass=Thing): return result if len(result) else [None] def percept(self, agent): - """Returns things in adjacent (not diagonal) cells of the agent. + """Return things in adjacent (not diagonal) cells of the agent. Result format: [Left, Right, Up, Down, Center / Current location]""" x, y = agent.location result = [] @@ -907,7 +907,7 @@ def execute_action(self, agent, action): agent.has_arrow = False def in_danger(self, agent): - """Checks if Explorer is in danger (Pit or Wumpus), if he is, kill him""" + """Check if Explorer is in danger (Pit or Wumpus), if he is, kill him""" for thing in self.list_things_at(agent.location): if isinstance(thing, Pit) or (isinstance(thing, Wumpus) and thing.alive): agent.alive = False diff --git a/csp.py b/csp.py index 62772c322..70223acf2 100644 --- a/csp.py +++ b/csp.py @@ -351,7 +351,7 @@ def topological_sort(X, root): def build_topological(node, parent, neighbors, visited, stack, parents): - """Builds the topological sort and the parents of each node in the graph""" + """Build the topological sort and the parents of each node in the graph.""" visited[node] = True for n in neighbors[node]: @@ -427,7 +427,7 @@ def MapColoringCSP(colors, neighbors): different_values_constraint) -def parse_neighbors(neighbors, variables=[]): +def parse_neighbors(neighbors, variables=None): """Convert a string of the form 'X: Y Z; Y: Z' into a dict mapping regions to neighbors. The syntax is a region name followed by a ':' followed by zero or more region names, followed by ';', repeated for diff --git a/knowledge.py b/knowledge.py index 6fe09acd2..2bb12f3b8 100644 --- a/knowledge.py +++ b/knowledge.py @@ -11,13 +11,14 @@ # ______________________________________________________________________________ -def current_best_learning(examples, h, examples_so_far=[]): +def current_best_learning(examples, h, examples_so_far=None): """ [Figure 19.2] The hypothesis is a list of dictionaries, with each dictionary representing a disjunction.""" if not examples: return h + examples_so_far = examples_so_far or [] e = examples[0] if is_consistent(e, h): return current_best_learning(examples[1:], h, examples_so_far + [e]) @@ -95,7 +96,7 @@ def generalizations(examples_so_far, h): def add_or(examples_so_far, h): - """Adds an OR operation to the hypothesis. The AND operations in the disjunction + """Add an OR operation to the hypothesis. The AND operations in the disjunction are generated by the last example (which is the problematic one).""" ors = [] e = examples_so_far[-1] @@ -135,7 +136,7 @@ def version_space_update(V, e): def all_hypotheses(examples): - """Builds a list of all the possible hypotheses""" + """Build a list of all the possible hypotheses""" values = values_table(examples) h_powerset = powerset(values.keys()) hypotheses = [] @@ -148,7 +149,7 @@ def all_hypotheses(examples): def values_table(examples): - """Builds a table with all the possible values for each attribute. + """Build a table with all the possible values for each attribute. Returns a dictionary with keys the attribute names and values a list with the possible values for the corresponding attribute.""" values = defaultdict(lambda: []) @@ -210,7 +211,7 @@ def build_h_combinations(hypotheses): def minimal_consistent_det(E, A): - """Returns a minimal set of attributes which give consistent determination""" + """Return a minimal set of attributes which give consistent determination""" n = len(A) for i in range(n + 1): @@ -220,7 +221,7 @@ def minimal_consistent_det(E, A): def consistent_det(A, E): - """Checks if the attributes(A) is consistent with the examples(E)""" + """Check if the attributes(A) is consistent with the examples(E)""" H = {} for e in E: @@ -235,9 +236,9 @@ def consistent_det(A, E): class FOIL_container(FolKB): - """Holds the kb and other necessary elements required by FOIL""" + """Hold the kb and other necessary elements required by FOIL.""" - def __init__(self, clauses=[]): + def __init__(self, clauses=None): self.const_syms = set() self.pred_syms = set() FolKB.__init__(self, clauses) @@ -251,7 +252,7 @@ def tell(self, sentence): raise Exception("Not a definite clause: {}".format(sentence)) def foil(self, examples, target): - """Learns a list of first-order horn clauses + """Learn a list of first-order horn clauses 'examples' is a tuple: (positive_examples, negative_examples). positive_examples and negative_examples are both lists which contain substitutions.""" clauses = [] @@ -268,10 +269,10 @@ def foil(self, examples, target): return clauses def new_clause(self, examples, target): - """Finds a horn clause which satisfies part of the positive + """Find a horn clause which satisfies part of the positive examples but none of the negative examples. The horn clause is specified as [consequent, list of antecedents] - Return value is the tuple (horn_clause, extended_positive_examples)""" + Return value is the tuple (horn_clause, extended_positive_examples).""" clause = [target, []] # [positive_examples, negative_examples] extended_examples = examples @@ -284,14 +285,14 @@ def new_clause(self, examples, target): return (clause, extended_examples[0]) def extend_example(self, example, literal): - """Generates extended examples which satisfy the literal""" + """Generate extended examples which satisfy the literal.""" # find all substitutions that satisfy literal for s in self.ask_generator(subst(example, literal)): s.update(example) yield s def new_literals(self, clause): - """Generates new literals based on known predicate symbols. + """Generate new literals based on known predicate symbols. Generated literal must share atleast one variable with clause""" share_vars = variables(clause[0]) for l in clause[1]: @@ -304,7 +305,7 @@ def new_literals(self, clause): yield Expr(pred, *[var for var in args]) def choose_literal(self, literals, examples): - """Chooses the best literal based on the information gain""" + """Choose the best literal based on the information gain.""" def gain(l): pre_pos = len(examples[0]) pre_neg = len(examples[1]) @@ -328,8 +329,8 @@ def represents(d): return max(literals, key=gain) def update_examples(self, target, examples, extended_examples): - """Adds to the kb those examples what are represented in extended_examples - List of omitted examples is returned""" + """Add to the kb those examples what are represented in extended_examples + List of omitted examples is returned.""" uncovered = [] for example in examples: def represents(d): @@ -346,7 +347,7 @@ def represents(d): def check_all_consistency(examples, h): - """Check for the consistency of all examples under h""" + """Check for the consistency of all examples under h.""" for e in examples: if not is_consistent(e, h): return False @@ -355,7 +356,7 @@ def check_all_consistency(examples, h): def check_negative_consistency(examples, h): - """Check if the negative examples are consistent under h""" + """Check if the negative examples are consistent under h.""" for e in examples: if e['GOAL']: continue @@ -367,7 +368,7 @@ def check_negative_consistency(examples, h): def disjunction_value(e, d): - """The value of example e under disjunction d""" + """The value of example e under disjunction d.""" for k, v in d.items(): if v[0] == '!': # v is a NOT expression @@ -381,7 +382,7 @@ def disjunction_value(e, d): def guess_value(e, h): - """Guess value of example e under hypothesis h""" + """Guess value of example e under hypothesis h.""" for d in h: if disjunction_value(e, d): return True @@ -394,16 +395,8 @@ def is_consistent(e, h): def false_positive(e, h): - if e["GOAL"] == False: - if guess_value(e, h): - return True - - return False + return guess_value(e, h) and not e["GOAL"] def false_negative(e, h): - if e["GOAL"] == True: - if not guess_value(e, h): - return True - - return False + return e["GOAL"] and not guess_value(e, h) diff --git a/logic.py b/logic.py index 5810e633f..129d281cf 100644 --- a/logic.py +++ b/logic.py @@ -901,10 +901,11 @@ class FolKB(KB): False """ - def __init__(self, initial_clauses=[]): + def __init__(self, initial_clauses=None): self.clauses = [] # inefficient: no indexing - for clause in initial_clauses: - self.tell(clause) + if initial_clauses: + for clause in initial_clauses: + self.tell(clause) def tell(self, sentence): if is_definite_clause(sentence): diff --git a/nlp.py b/nlp.py index ace6de90d..6ad92b6bb 100644 --- a/nlp.py +++ b/nlp.py @@ -272,7 +272,7 @@ def __repr__(self): class Chart: """Class for parsing sentences using a chart data structure. - >>> chart = Chart(E0); + >>> chart = Chart(E0) >>> len(chart.parses('the stench is in 2 2')) 1 """ diff --git a/notebook.py b/notebook.py index ae0976900..4bb53cf1c 100644 --- a/notebook.py +++ b/notebook.py @@ -912,17 +912,17 @@ def show_map(graph_data, node_colors = None): # set the size of the plot plt.figure(figsize=(18,13)) # draw the graph (both nodes and edges) with locations from romania_locations - nx.draw(G, pos = {k : node_positions[k] for k in G.nodes()}, - node_color = [node_colors[node] for node in G.nodes()], linewidths = 0.3, edgecolors = 'k') + nx.draw(G, pos={k: node_positions[k] for k in G.nodes()}, + node_color=[node_colors[node] for node in G.nodes()], linewidths=0.3, edgecolors='k') # draw labels for nodes - node_label_handles = nx.draw_networkx_labels(G, pos = node_label_pos, font_size = 14) + node_label_handles = nx.draw_networkx_labels(G, pos=node_label_pos, font_size=14) # add a white bounding box behind the node labels [label.set_bbox(dict(facecolor='white', edgecolor='none')) for label in node_label_handles.values()] # add edge lables to the graph - nx.draw_networkx_edge_labels(G, pos = node_positions, edge_labels = edge_weights, font_size = 14) + nx.draw_networkx_edge_labels(G, pos=node_positions, edge_labels=edge_weights, font_size=14) # add a legend white_circle = lines.Line2D([], [], color="white", marker='o', markersize=15, markerfacecolor="white") @@ -932,7 +932,7 @@ def show_map(graph_data, node_colors = None): green_circle = lines.Line2D([], [], color="green", marker='o', markersize=15, markerfacecolor="green") plt.legend((white_circle, orange_circle, red_circle, gray_circle, green_circle), ('Un-explored', 'Frontier', 'Currently Exploring', 'Explored', 'Final Solution'), - numpoints=1,prop={'size':16}, loc=(.8,.75)) + numpoints=1, prop={'size':16}, loc=(.8,.75)) # show the plot. No need to use in notebooks. nx.draw will show the graph itself. plt.show() @@ -940,7 +940,7 @@ def show_map(graph_data, node_colors = None): ## helper functions for visualisations def final_path_colors(initial_node_colors, problem, solution): - "returns a node_colors dict of the final path provided the problem and solution" + "Return a node_colors dict of the final path provided the problem and solution." # get initial node colors final_colors = dict(initial_node_colors) @@ -956,7 +956,7 @@ def display_visual(graph_data, user_input, algorithm=None, problem=None): def slider_callback(iteration): # don't show graph for the first time running the cell calling this function try: - show_map(graph_data, node_colors = all_node_colors[iteration]) + show_map(graph_data, node_colors=all_node_colors[iteration]) except: pass def visualize_callback(Visualize): @@ -976,26 +976,26 @@ def visualize_callback(Visualize): #time.sleep(.5) slider = widgets.IntSlider(min=0, max=1, step=1, value=0) - slider_visual = widgets.interactive(slider_callback, iteration = slider) + slider_visual = widgets.interactive(slider_callback, iteration=slider) display(slider_visual) - button = widgets.ToggleButton(value = False) - button_visual = widgets.interactive(visualize_callback, Visualize = button) + button = widgets.ToggleButton(value=False) + button_visual = widgets.interactive(visualize_callback, Visualize=button) display(button_visual) if user_input == True: node_colors = dict(initial_node_colors) if isinstance(algorithm, dict): - assert set(algorithm.keys()).issubset(set(["Breadth First Tree Search", + assert set(algorithm.keys()).issubset({"Breadth First Tree Search", "Depth First Tree Search", "Breadth First Search", "Depth First Graph Search", "Uniform Cost Search", - "A-star Search"])) + "A-star Search"}) - algo_dropdown = widgets.Dropdown(description = "Search algorithm: ", - options = sorted(list(algorithm.keys())), - value = "Breadth First Tree Search") + algo_dropdown = widgets.Dropdown(description="Search algorithm: ", + options=sorted(list(algorithm.keys())), + value="Breadth First Tree Search") display(algo_dropdown) elif algorithm is None: print("No algorithm to run.") @@ -1004,7 +1004,7 @@ def visualize_callback(Visualize): def slider_callback(iteration): # don't show graph for the first time running the cell calling this function try: - show_map(graph_data, node_colors = all_node_colors[iteration]) + show_map(graph_data, node_colors=all_node_colors[iteration]) except: pass @@ -1027,18 +1027,18 @@ def visualize_callback(Visualize): slider.value = i #time.sleep(.5) - start_dropdown = widgets.Dropdown(description = "Start city: ", - options = sorted(list(node_colors.keys())), value = "Arad") + start_dropdown = widgets.Dropdown(description="Start city: ", + options=sorted(list(node_colors.keys())), value="Arad") display(start_dropdown) - end_dropdown = widgets.Dropdown(description = "Goal city: ", - options = sorted(list(node_colors.keys())), value = "Fagaras") + end_dropdown = widgets.Dropdown(description="Goal city: ", + options=sorted(list(node_colors.keys())), value="Fagaras") display(end_dropdown) - button = widgets.ToggleButton(value = False) - button_visual = widgets.interactive(visualize_callback, Visualize = button) + button = widgets.ToggleButton(value=False) + button_visual = widgets.interactive(visualize_callback, Visualize=button) display(button_visual) slider = widgets.IntSlider(min=0, max=1, step=1, value=0) - slider_visual = widgets.interactive(slider_callback, iteration = slider) + slider_visual = widgets.interactive(slider_callback, iteration=slider) display(slider_visual) \ No newline at end of file diff --git a/planning.py b/planning.py index e31c8b3a3..95d7655d1 100644 --- a/planning.py +++ b/planning.py @@ -276,8 +276,8 @@ def find_mutex(self): if negeff in self.next_state_links_neg: for a in self.next_state_links_pos[poseff]: for b in self.next_state_links_neg[negeff]: - if set([a, b]) not in self.mutex: - self.mutex.append(set([a, b])) + if {a, b} not in self.mutex: + self.mutex.append({a, b}) # Interference for posprecond in self.current_state_links_pos: @@ -285,16 +285,16 @@ def find_mutex(self): if negeff in self.next_state_links_neg: for a in self.current_state_links_pos[posprecond]: for b in self.next_state_links_neg[negeff]: - if set([a, b]) not in self.mutex: - self.mutex.append(set([a, b])) + if {a, b} not in self.mutex: + self.mutex.append({a, b}) for negprecond in self.current_state_links_neg: poseff = negprecond if poseff in self.next_state_links_pos: for a in self.next_state_links_pos[poseff]: for b in self.current_state_links_neg[negprecond]: - if set([a, b]) not in self.mutex: - self.mutex.append(set([a, b])) + if {a, b} not in self.mutex: + self.mutex.append({a, b}) # Competing needs for posprecond in self.current_state_links_pos: @@ -302,8 +302,8 @@ def find_mutex(self): if negprecond in self.current_state_links_neg: for a in self.current_state_links_pos[posprecond]: for b in self.current_state_links_neg[negprecond]: - if set([a, b]) not in self.mutex: - self.mutex.append(set([a, b])) + if {a, b} not in self.mutex: + self.mutex.append({a, b}) # Inconsistent support state_mutex = [] @@ -314,7 +314,7 @@ def find_mutex(self): else: next_state_1 = self.next_action_links[list(pair)[0]] if (len(next_state_0) == 1) and (len(next_state_1) == 1): - state_mutex.append(set([next_state_0[0], next_state_1[0]])) + state_mutex.append({next_state_0[0], next_state_1[0]}) self.mutex = self.mutex+state_mutex @@ -565,18 +565,20 @@ class HLA(Action): """ unique_group = 1 - def __init__(self, action, precond=[None, None], effect=[None, None], duration=0, - consume={}, use={}): + def __init__(self, action, precond=None, effect=None, duration=0, + consume=None, use=None): """ As opposed to actions, to define HLA, we have added constraints. duration holds the amount of time required to execute the task consumes holds a dictionary representing the resources the task consumes uses holds a dictionary representing the resources the task uses """ + precond = precond or [None, None] + effect = effect or [None, None] super().__init__(action, precond, effect) self.duration = duration - self.consumes = consume - self.uses = use + self.consumes = consume or {} + self.uses = use or {} self.completed = False # self.priority = -1 # must be assigned in relation to other HLAs # self.job_group = -1 # must be assigned in relation to other HLAs @@ -644,10 +646,10 @@ class Problem(PDDL): This class is identical to PDLL, except that it overloads the act function to handle resource and ordering conditions imposed by HLA as opposed to Action. """ - def __init__(self, initial_state, actions, goal_test, jobs=None, resources={}): + def __init__(self, initial_state, actions, goal_test, jobs=None, resources=None): super().__init__(initial_state, actions, goal_test) self.jobs = jobs - self.resources = resources + self.resources = resources or {} def act(self, action): """ diff --git a/probability.py b/probability.py index 9b732edd7..205ae426e 100644 --- a/probability.py +++ b/probability.py @@ -165,10 +165,11 @@ def enumerate_joint(variables, e, P): class BayesNet: """Bayesian network containing only boolean-variable nodes.""" - def __init__(self, node_specs=[]): + def __init__(self, node_specs=None): """Nodes must be ordered with parents before children.""" self.nodes = [] self.variables = [] + node_specs = node_specs or [] for node_spec in node_specs: self.add(node_spec) @@ -526,10 +527,10 @@ def markov_blanket_sample(X, e, bn): class HiddenMarkovModel: """A Hidden markov model which takes Transition model and Sensor model as inputs""" - def __init__(self, transition_model, sensor_model, prior=[0.5, 0.5]): + def __init__(self, transition_model, sensor_model, prior=None): self.transition_model = transition_model self.sensor_model = sensor_model - self.prior = prior + self.prior = prior or [0.5, 0.5] def sensor_dist(self, ev): if ev is True: @@ -561,10 +562,10 @@ def forward_backward(HMM, ev, prior): t = len(ev) ev.insert(0, None) # to make the code look similar to pseudo code - fv = [[0.0, 0.0] for i in range(len(ev))] + fv = [[0.0, 0.0] for _ in range(len(ev))] b = [1.0, 1.0] bv = [b] # we don't need bv; but we will have a list of all backward messages here - sv = [[0, 0] for i in range(len(ev))] + sv = [[0, 0] for _ in range(len(ev))] fv[0] = prior diff --git a/rl.py b/rl.py index 1b7e20c33..9f9c90676 100644 --- a/rl.py +++ b/rl.py @@ -71,13 +71,13 @@ class ModelMDP(MDP): """ Class for implementing modified Version of input MDP with an editable transition model P and a custom function T. """ def __init__(self, init, actlist, terminals, gamma, states): - super().__init__(init, actlist, terminals, states = states, gamma = gamma) + super().__init__(init, actlist, terminals, states=states, gamma=gamma) nested_dict = lambda: defaultdict(nested_dict) # StackOverflow:whats-the-best-way-to-initialize-a-dict-of-dicts-in-python self.P = nested_dict() def T(self, s, a): - """Returns a list of tuples with probabilities for states + """Return a list of tuples with probabilities for states based on the learnt model P.""" return [(prob, res) for (res, prob) in self.P[(s, a)].items()] @@ -120,8 +120,8 @@ def __call__(self, percept): return self.a def update_state(self, percept): - '''To be overridden in most cases. The default case - assumes the percept to be of type (state, reward)''' + """To be overridden in most cases. The default case + assumes the percept to be of type (state, reward).""" return percept @@ -146,7 +146,7 @@ def __init__(self, pi, mdp, alpha=None): if alpha: self.alpha = alpha else: - self.alpha = lambda n: 1./(1+n) # udacity video + self.alpha = lambda n: 1/(1+n) # udacity video def __call__(self, percept): s1, r1 = self.update_state(percept) @@ -164,8 +164,8 @@ def __call__(self, percept): return self.a def update_state(self, percept): - ''' To be overridden in most cases. The default case - assumes the percept to be of type (state, reward)''' + """To be overridden in most cases. The default case + assumes the percept to be of type (state, reward).""" return percept @@ -202,7 +202,7 @@ def f(self, u, n): return u def actions_in_state(self, state): - """ Returns actions possible in given state. + """ Return actions possible in given state. Useful for max and argmax. """ if state in self.terminals: return [None] @@ -229,21 +229,21 @@ def __call__(self, percept): return self.a def update_state(self, percept): - ''' To be overridden in most cases. The default case - assumes the percept to be of type (state, reward)''' + """To be overridden in most cases. The default case + assumes the percept to be of type (state, reward).""" return percept def run_single_trial(agent_program, mdp): - ''' Execute trial for given agent_program + """Execute trial for given agent_program and mdp. mdp should be an instance of subclass - of mdp.MDP ''' + of mdp.MDP """ def take_single_action(mdp, s, a): - ''' - Selects outcome of taking action a + """ + Select outcome of taking action a in state s. Weighted Sampling. - ''' + """ x = random.uniform(0, 1) cumulative_probability = 0.0 for probability_state in mdp.T(s, a): diff --git a/text.py b/text.py index 8dc0ab855..b6beb28ca 100644 --- a/text.py +++ b/text.py @@ -37,19 +37,19 @@ class NgramWordModel(CountingProbDist): You can add, sample or get P[(word1, ..., wordn)]. The method P.samples(n) builds up an n-word sequence; P.add_cond_prob and P.add_sequence add data.""" - def __init__(self, n, observation_sequence=[], default=0): + def __init__(self, n, observation_sequence=None, default=0): # In addition to the dictionary of n-tuples, cond_prob is a # mapping from (w1, ..., wn-1) to P(wn | w1, ... wn-1) CountingProbDist.__init__(self, default=default) self.n = n self.cond_prob = defaultdict() - self.add_sequence(observation_sequence) + self.add_sequence(observation_sequence or []) # __getitem__, top, sample inherited from CountingProbDist # Note that they deal with tuples, not strings, as inputs def add_cond_prob(self, ngram): - """Builds the conditional probabilities P(wn | (w1, ..., wn-1)""" + """Build the conditional probabilities P(wn | (w1, ..., wn-1)""" if ngram[:-1] not in self.cond_prob: self.cond_prob[ngram[:-1]] = CountingProbDist() self.cond_prob[ngram[:-1]].add(ngram[-1]) @@ -88,14 +88,16 @@ def add_sequence(self, words): class UnigramCharModel(NgramCharModel): - def __init__(self, observation_sequence=[], default=0): + def __init__(self, observation_sequence=None, default=0): CountingProbDist.__init__(self, default=default) self.n = 1 self.cond_prob = defaultdict() - self.add_sequence(observation_sequence) + self.add_sequence(observation_sequence or []) def add_sequence(self, words): - [self.add(char) for word in words for char in list(word)] + for word in words: + for char in word: + self.add(char) # ______________________________________________________________________________ @@ -368,9 +370,9 @@ def decode(self, ciphertext): """Search for a decoding of the ciphertext.""" self.ciphertext = canonicalize(ciphertext) # reduce domain to speed up search - self.chardomain = {c for c in self.ciphertext if c is not ' '} + self.chardomain = {c for c in self.ciphertext if c != ' '} problem = PermutationDecoderProblem(decoder=self) - solution = search.best_first_graph_search( + solution = search.best_first_graph_search( problem, lambda node: self.score(node.state)) solution.state[' '] = ' ' @@ -388,9 +390,9 @@ def score(self, code): # add small positive value to prevent computing log(0) # TODO: Modify the values to make score more accurate - logP = (sum([log(self.Pwords[word] + 1e-20) for word in words(text)]) + - sum([log(self.P1[c] + 1e-5) for c in text]) + - sum([log(self.P2[b] + 1e-10) for b in bigrams(text)])) + logP = (sum(log(self.Pwords[word] + 1e-20) for word in words(text)) + + sum(log(self.P1[c] + 1e-5) for c in text) + + sum(log(self.P2[b] + 1e-10) for b in bigrams(text))) return -exp(logP) From e3270d0477a35c38e03c41ed6d8ab8e4794cfe07 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Thu, 15 Mar 2018 04:50:06 +0530 Subject: [PATCH 074/269] Added min-conflicts section (#841) * Added section on min-conflicts * Refactor one-liner for loop * Added tests for min_conflicts and NQueensCSP --- csp.ipynb | 604 ++++++++++++++++++++++++++++++++++++++++++++-- tests/test_csp.py | 55 +++++ 2 files changed, 641 insertions(+), 18 deletions(-) diff --git a/csp.ipynb b/csp.ipynb index 1de9e1312..be3882387 100644 --- a/csp.ipynb +++ b/csp.ipynb @@ -52,7 +52,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "psource(CSP)" @@ -105,7 +107,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "psource(different_values_constraint)" @@ -139,7 +143,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "psource(MapColoringCSP)" @@ -178,9 +184,114 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def queen_constraint(A, a, B, b):\n",
    +       "    """Constraint is satisfied (true) if A, B are really the same variable,\n",
    +       "    or if they are not in the same row, down diagonal, or up diagonal."""\n",
    +       "    return A == B or (a != b and A + a != B + b and A - a != B - b)\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "psource(queen_constraint)" ] @@ -194,9 +305,191 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    class NQueensCSP(CSP):\n",
    +       "    """Make a CSP for the nQueens problem for search with min_conflicts.\n",
    +       "    Suitable for large n, it uses only data structures of size O(n).\n",
    +       "    Think of placing queens one per column, from left to right.\n",
    +       "    That means position (x, y) represents (var, val) in the CSP.\n",
    +       "    The main structures are three arrays to count queens that could conflict:\n",
    +       "        rows[i]      Number of queens in the ith row (i.e val == i)\n",
    +       "        downs[i]     Number of queens in the \\ diagonal\n",
    +       "                     such that their (x, y) coordinates sum to i\n",
    +       "        ups[i]       Number of queens in the / diagonal\n",
    +       "                     such that their (x, y) coordinates have x-y+n-1 = i\n",
    +       "    We increment/decrement these counts each time a queen is placed/moved from\n",
    +       "    a row/diagonal. So moving is O(1), as is nconflicts.  But choosing\n",
    +       "    a variable, and a best value for the variable, are each O(n).\n",
    +       "    If you want, you can keep track of conflicted variables, then variable\n",
    +       "    selection will also be O(1).\n",
    +       "    >>> len(backtracking_search(NQueensCSP(8)))\n",
    +       "    8\n",
    +       "    """\n",
    +       "\n",
    +       "    def __init__(self, n):\n",
    +       "        """Initialize data structures for n Queens."""\n",
    +       "        CSP.__init__(self, list(range(n)), UniversalDict(list(range(n))),\n",
    +       "                     UniversalDict(list(range(n))), queen_constraint)\n",
    +       "\n",
    +       "        self.rows = [0]*n\n",
    +       "        self.ups = [0]*(2*n - 1)\n",
    +       "        self.downs = [0]*(2*n - 1)\n",
    +       "\n",
    +       "    def nconflicts(self, var, val, assignment):\n",
    +       "        """The number of conflicts, as recorded with each assignment.\n",
    +       "        Count conflicts in row and in up, down diagonals. If there\n",
    +       "        is a queen there, it can't conflict with itself, so subtract 3."""\n",
    +       "        n = len(self.variables)\n",
    +       "        c = self.rows[val] + self.downs[var+val] + self.ups[var-val+n-1]\n",
    +       "        if assignment.get(var, None) == val:\n",
    +       "            c -= 3\n",
    +       "        return c\n",
    +       "\n",
    +       "    def assign(self, var, val, assignment):\n",
    +       "        """Assign var, and keep track of conflicts."""\n",
    +       "        oldval = assignment.get(var, None)\n",
    +       "        if val != oldval:\n",
    +       "            if oldval is not None:  # Remove old val if there was one\n",
    +       "                self.record_conflict(assignment, var, oldval, -1)\n",
    +       "            self.record_conflict(assignment, var, val, +1)\n",
    +       "            CSP.assign(self, var, val, assignment)\n",
    +       "\n",
    +       "    def unassign(self, var, assignment):\n",
    +       "        """Remove var from assignment (if it is there) and track conflicts."""\n",
    +       "        if var in assignment:\n",
    +       "            self.record_conflict(assignment, var, assignment[var], -1)\n",
    +       "        CSP.unassign(self, var, assignment)\n",
    +       "\n",
    +       "    def record_conflict(self, assignment, var, val, delta):\n",
    +       "        """Record conflicts caused by addition or deletion of a Queen."""\n",
    +       "        n = len(self.variables)\n",
    +       "        self.rows[val] += delta\n",
    +       "        self.downs[var + val] += delta\n",
    +       "        self.ups[var - val + n - 1] += delta\n",
    +       "\n",
    +       "    def display(self, assignment):\n",
    +       "        """Print the queens and the nconflicts values (for debugging)."""\n",
    +       "        n = len(self.variables)\n",
    +       "        for val in range(n):\n",
    +       "            for var in range(n):\n",
    +       "                if assignment.get(var, '') == val:\n",
    +       "                    ch = 'Q'\n",
    +       "                elif (var + val) % 2 == 0:\n",
    +       "                    ch = '.'\n",
    +       "                else:\n",
    +       "                    ch = '-'\n",
    +       "                print(ch, end=' ')\n",
    +       "            print('    ', end=' ')\n",
    +       "            for var in range(n):\n",
    +       "                if assignment.get(var, '') == val:\n",
    +       "                    ch = '*'\n",
    +       "                else:\n",
    +       "                    ch = ' '\n",
    +       "                print(str(self.nconflicts(var, val, assignment)) + ch, end=' ')\n",
    +       "            print()\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "psource(NQueensCSP)" ] @@ -210,7 +503,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -219,6 +512,275 @@ "eight_queens = NQueensCSP(8)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have defined our CSP. \n", + "We now need to solve this.\n", + "\n", + "### Min-conflicts\n", + "As stated above, the `min_conflicts` algorithm is an efficient method to solve such a problem.\n", + "
    \n", + "To begin with, all the variables of the CSP are _randomly_ initialized. \n", + "
    \n", + "The algorithm then randomly selects a variable that has conflicts and violates some constraints of the CSP.\n", + "
    \n", + "The selected variable is then assigned a value that _minimizes_ the number of conflicts.\n", + "
    \n", + "This is a simple stochastic algorithm which works on a principle similar to **Hill-climbing**.\n", + "The conflicting state is repeatedly changed into a state with fewer conflicts in an attempt to reach an approximate solution.\n", + "
    \n", + "This algorithm sometimes benefits from having a good initial assignment.\n", + "Using greedy techniques to get a good initial assignment and then using `min_conflicts` to solve the CSP can speed up the procedure dramatically, especially for CSPs with a large state space." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def min_conflicts(csp, max_steps=100000):\n",
    +       "    """Solve a CSP by stochastic hillclimbing on the number of conflicts."""\n",
    +       "    # Generate a complete assignment for all variables (probably with conflicts)\n",
    +       "    csp.current = current = {}\n",
    +       "    for var in csp.variables:\n",
    +       "        val = min_conflicts_value(csp, var, current)\n",
    +       "        csp.assign(var, val, current)\n",
    +       "    # Now repeatedly choose a random conflicted variable and change it\n",
    +       "    for i in range(max_steps):\n",
    +       "        conflicted = csp.conflicted_vars(current)\n",
    +       "        if not conflicted:\n",
    +       "            return current\n",
    +       "        var = random.choice(conflicted)\n",
    +       "        val = min_conflicts_value(csp, var, current)\n",
    +       "        csp.assign(var, val, current)\n",
    +       "    return None\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(min_conflicts)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's use this algorithm to solve the `eight_queens` CSP." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "solution = min_conflicts(eight_queens)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is indeed a valid solution. \n", + "Let's write a helper function to visualize the solution space." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "%matplotlib inline\n", + "\n", + "def display_NQueensCSP(solution):\n", + " n = len(solution)\n", + " board = np.array([2 * int((i + j) % 2) for j in range(n) for i in range(n)]).reshape((n, n))\n", + " \n", + " for (k, v) in solution.items():\n", + " board[k][v] = 1\n", + " \n", + " fig = plt.figure(figsize=(7, 7))\n", + " ax = fig.add_subplot(111)\n", + " ax.set_title(f'{n} Queens')\n", + " plt.imshow(board, cmap='binary', interpolation='nearest')\n", + " ax.set_aspect('equal')\n", + " fig.tight_layout()\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAeAAAAHwCAYAAAB+ArwOAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4wLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvpW3flQAAFZFJREFUeJzt3HuspAd53/HfE6+52DFxG7bUFwpE\njSxR1AB7IEWuaIshsQMlVS+SaYNCVNVpGxLcRk1J/tmlSqU2f0SkokXZGAhJAItrRRGYECU0RW0M\nZ40pGEMFxhGLcbxu4hpwg7Hz9I8zbpdllzPbzOzjM+fzkY58Zuad9zzj18ff815mqrsDAJxb3zE9\nAADsRwIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AAAYZzoKqeWlXvr6o/qqq7q+p1VXXg2yx/\ncVW9frHsA1X1yar60XM5M7BeAgznxn9Ick+SS5I8M8lfS/JPT7dgVT0myW8leUqS5yX5riT/Iskv\nVNVPnZNpgbUTYDg3npbk7d39x919d5KbkvylMyz78iR/Icnf6+4vdPc3uvumJD+V5Oer6qIkqaqu\nqr/4yJOq6ler6udPuv2Sqrq1qu6rqv9aVX/5pMcurap3VdWJqvrCyWGvqiNV9faq+rWq+kpV3VZV\nWyc9/i+r6kuLxz5bVVet5l8R7C8CDOfGLyW5tqouqKrLklyTnQifzouSfKC7v3bK/e9KckGSv7Lb\nD6uqZyd5Y5IfT/LdSX45yXur6rFV9R1J/lOSTyS5LMlVSa6vqh88aRUvTXJjkouTvDfJ6xbrvSLJ\nK5M8p7svSvKDSe7cbR7gWwkwnBv/OTt7vPcnOZ5kO8l/PMOyT0zy5VPv7O6Hktyb5OASP+8fJfnl\n7r65ux/u7jcn+Xp24v2cJAe7+19194PdfUeSX0ly7UnP/0h3v7+7H07y60m+b3H/w0kem+TpVXV+\nd9/Z3Z9fYh7gFAIMa7bY4/xgkncnuTA7gf0zSf7tGZ5yb3bOFZ+6ngOL555Y4sc+JclPLw4/31dV\n9yV5cpJLF49despjP5fkSSc9/+6Tvn8gyeOq6kB3fy7J9UmOJLmnqm6sqkuXmAc4hQDD+v3Z7MTv\ndd399e7+n0nelOSHzrD8byW5pqouPOX+v5PkG0k+urj9QHYOST/iz5/0/ReT/Ovuvvikrwu6+22L\n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\nIMAAMECAAWCAAAPAAAEGgAECDAADBBgABggwAAwQYAAYIMAAMECAAWCAAAPAAAEGgAECDAADBBgA\nBggwAAwQYAAYIMAAMKC6e/UrrTqR5PdXvuIze2KSe8/hzzvXvL69zevb+zb9NXp9q/WU7j6420Jr\nCfC5VlXb3b01Pce6eH17m9e39236a/T6ZjgEDQADBBgABmxKgI9OD7BmXt/e5vXtfZv+Gr2+ARtx\nDhgA9ppN2QMGgD1FgAFgwJ4OcFVdXVWfrarPVdWrp+dZtap6Y1XdU1Wfmp5lHarqyVX1O1V1e1Xd\nVlWvmp5plarqcVX10ar6xOL1vWZ6pnWoqvOq6uNV9b7pWVatqu6sqk9W1a1VtT09z6pV1cVV9c6q\n+szi9/B50zOtUlVdsdh2j3zdX1XXT8/1iD17DriqzkvyP5K8KMnxJB9L8rLu/vToYCtUVc9P8tUk\nv9bdz5ieZ9Wq6pIkl3T3LVV1UZJjSf7WpmzDqqokF3b3V6vq/CQfSfKq7v694dFWqqr+eZKtJE/o\n7pdMz7NKVXVnkq3u3sgPqaiqNyf5L919Q1U9JskF3X3f9FzrsGjGl5J8f3efyw+KOqO9vAf83CSf\n6+47uvvBJDcm+eHhmVaqu383yR9Oz7Eu3f3l7r5l8f1Xktye5LLZqVand3x1cfP8xdfe/Iv3DKrq\n8iQvTnLD9Cycnap6QpLnJ3lDknT3g5sa34Wrknz+0RLfZG8H+LIkXzzp9vFs0P+895uqemqSZyW5\neXaS1Vocnr01yT1JPtTdG/X6krw2yc8k+ZPpQdakk/xmVR2rquumh1mx70lyIsmbFqcQbqiqC6eH\nWqNrk7xteoiT7eUA12nu26i9i/2iqr4zybuSXN/d90/Ps0rd/XB3PzPJ5UmeW1Ubcyqhql6S5J7u\nPjY9yxpd2d3PTnJNkp9YnBbaFAeSPDvJ67v7WUm+lmTjrqVJksXh9Zcmecf0LCfbywE+nuTJJ92+\nPMldQ7Pw/2lxbvRdSd7S3e+enmddFof2Ppzk6uFRVunKJC9dnCe9MckLquo3Zkdare6+a/HPe5K8\nJzunvjbF8STHTzoq887sBHkTXZPklu7+g+lBTraXA/yxJN9bVU9b/HVzbZL3Ds/EWVhcpPSGJLd3\n9y9Oz7NqVXWwqi5efP/4JC9M8pnZqVanu3+2uy/v7qdm5/fvt7v7R4bHWpmqunBxcWAWh2Z/IMnG\nvCOhu+9O8sWqumJx11VJNuICyNN4WR5lh5+TnUMQe1J3P1RVr0zywSTnJXljd982PNZKVdXbkvz1\nJE+squNJDnf3G2anWqkrk7w8yScX50mT5Oe6+/2DM63SJUnevLj68juSvL27N+6tOhvsSUnes/N3\nYg4keWt33zQ70sr9ZJK3LHZi7kjyY8PzrFxVXZCdd8v8+PQsp9qzb0MCgL1sLx+CBoA9S4ABYIAA\nA8AAAQaAAQIMAAMEGAAGCDAADPg/v2hxZuiP1asAAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display_NQueensCSP(solution)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The gray cells indicate the positions of the queens." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lets' see if we can find a different solution." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAeAAAAHwCAYAAAB+ArwOAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4wLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvpW3flQAAFaFJREFUeJzt3G2spAd53+H/Ha95sWPiNmwptikQ\nNbJEUQPsgRS5oi2GxA6UVH2RTBsUoqpO25DgNmpK8mWXKpXafIhIRYviGAhJAIvXilpgQpTQFLUx\nnDWmYAwVGEcsi+N1E9eAG4ydux/OuF2WXc5sM7O3z5zrko72zMwzz7nHj8a/87zMqe4OAHBufcf0\nAACwHwkwAAwQYAAYIMAAMECAAWCAAAPAAAEGgAECDAADBBjOgap6WlW9v6r+qKrurqrXV9WBb7P8\nxVX1hsWyD1TVJ6vqR8/lzMB6CTCcG/8hyT1JnpzkWUn+WpJ/eroFq+oxSX4ryVOTPD/JdyX5F0l+\noap+6pxMC6ydAMO58fQk7+juP+7uu5PcnOQvnWHZVyT5C0n+Xnd/obu/0d03J/mpJD9fVRclSVV1\nVf3FR55UVb9aVT9/0u2XVtVtVXVfVf3XqvrLJz12SVW9u6pOVNUXTg57VR2pqndU1a9V1Veq6vaq\n2jrp8X9ZVV9aPPbZqrpyNf+JYH8RYDg3finJNVV1QVVdmuTq7ET4dF6c5APd/bVT7n93kguS/JXd\nflhVPSfJm5L8eJLvTvLLSd5XVY+tqu9I8p+SfCLJpUmuTHJdVf3gSat4WZIbk1yc5H1JXr9Y7+VJ\nXpXkud19UZIfTHLXbvMA30qA4dz4z9nZ470/ybEk20n+4xmWfWKSL596Z3c/lOTeJAeX+Hn/KMkv\nd/ct3f1wd78lydezE+/nJjnY3f+qux/s7juT/EqSa056/ke6+/3d/XCSX0/yfYv7H07y2CTPqKrz\nu/uu7v78EvMApxBgWLPFHucHk7wnyYXZCeyfSfJvz/CUe7NzrvjU9RxYPPfEEj/2qUl+enH4+b6q\nui/JU5JcsnjsklMe+7kkTzrp+Xef9P0DSR5XVQe6+3NJrktyJMk9VXVjVV2yxDzAKQQY1u/PZid+\nr+/ur3f3/0zy5iQ/dIblfyvJ1VV14Sn3/50k30jy0cXtB7JzSPoRf/6k77+Y5F9398UnfV3Q3W9f\n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\nAgwAAwQYAAYIMAAMEGAAGCDAADBAgAFggAADwAABBoABAgwAAwQYAAYIMAAMEGAAGCDAADBAgAFg\ngAADwAABBoABAgwAAwQYAAYIMAAMEGAAGCDAADCgunv1K606keT3V77iM3tiknvP4c8717y+vc3r\n2/s2/TV6fav11O4+uNtCawnwuVZV2929NT3Hunh9e5vXt/dt+mv0+mY4BA0AAwQYAAZsSoCvnx5g\nzby+vc3r2/s2/TV6fQM24hwwAOw1m7IHDAB7igADwIA9HeCquqqqPltVn6uq10zPs2pV9aaquqeq\nPjU9yzpU1VOq6neq6o6qur2qXj090ypV1eOq6qNV9YnF63vt9EzrUFXnVdXHq+qm6VlWraruqqpP\nVtVtVbU9Pc+qVdXFVfWuqvrM4n34/OmZVqmqLl9su0e+7q+q66bnesSePQdcVecl+R9JXpzkWJKP\nJXl5d396dLAVqqoXJPlqkl/r7mdOz7NqVfXkJE/u7lur6qIkR5P8rU3ZhlVVSS7s7q9W1flJPpLk\n1d39e8OjrVRV/fMkW0me0N0vnZ5nlarqriRb3b2Rf6Siqt6S5L909w1V9ZgkF3T3fdNzrcOiGV9K\n8v3dfS7/UNQZ7eU94Ocl+Vx339ndDya5MckPD8+0Ut39u0n+cHqOdenuL3f3rYvvv5LkjiSXzk61\nOr3jq4ub5y++9uZvvGdQVZcleUmSG6Zn4exU1ROSvCDJG5Okux/c1PguXJnk84+W+CZ7O8CXJvni\nSbePZYP+573fVNXTkjw7yS2zk6zW4vDsbUnuSfKh7t6o15fkdUl+JsmfTA+yJp3kN6vqaFVdOz3M\nin1PkhNJ3rw4hXBDVV04PdQaXZPk7dNDnGwvB7hOc99G7V3sF1X1nUneneS67r5/ep5V6u6Hu/tZ\nSS5L8ryq2phTCVX10iT3dPfR6VnW6Irufk6Sq5P8xOK00KY4kOQ5Sd7Q3c9O8rUkG3ctTZIsDq+/\nLMk7p2c52V4O8LEkTznp9mVJjg/Nwv+nxbnRdyd5a3e/Z3qedVkc2vtwkquGR1mlK5K8bHGe9MYk\nL6yq35gdabW6+/ji33uSvDc7p742xbEkx046KvOu7AR5E12d5Nbu/oPpQU62lwP8sSTfW1VPX/x2\nc02S9w3PxFlYXKT0xiR3dPcvTs+zalV1sKouXnz/+CQvSvKZ2alWp7t/trsv6+6nZef999vd/SPD\nY61MVV24uDgwi0OzP5BkYz6R0N13J/liVV2+uOvKJBtxAeRpvDyPssPPyc4hiD2pux+qqlcl+WCS\n85K8qbtvHx5rparq7Un+epInVtWxJIe7+42zU63UFUlekeSTi/OkSfJz3f3+wZlW6clJ3rK4+vI7\nkryjuzfuozob7ElJ3rvze2IOJHlbd988O9LK/WSSty52Yu5M8mPD86xcVV2QnU/L/Pj0LKfasx9D\nAoC9bC8fggaAPUuAAWCAAAPAAAEGgAECDAADBBgABggwAAz4PyWycpsM6xLVAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "eight_queens = NQueensCSP(8)\n", + "solution = min_conflicts(eight_queens)\n", + "display_NQueensCSP(solution)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The solution is a bit different this time. \n", + "Running the above cell several times should give you various valid solutions.\n", + "
    \n", + "In the `search.ipynb` notebook, we will see how NQueensProblem can be solved using a heuristic search method such as `uniform_cost_search` and `astar_search`." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -466,7 +1028,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "psource(mrv)" @@ -475,7 +1039,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "psource(num_legal_values)" @@ -484,7 +1050,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "psource(CSP.nconflicts)" @@ -500,7 +1068,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "psource(lcv)" @@ -663,7 +1233,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "psource(tree_csp_solver)" @@ -1162,11 +1734,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.3" - }, - "widgets": { - "state": {}, - "version": "1.1.1" + "version": "3.6.1" } }, "nbformat": 4, diff --git a/tests/test_csp.py b/tests/test_csp.py index f63e657aa..0f282e3fe 100644 --- a/tests/test_csp.py +++ b/tests/test_csp.py @@ -351,6 +351,61 @@ def test_min_conflicts(): australia_impossible = MapColoringCSP(list('RG'), 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') assert min_conflicts(australia_impossible, 1000) is None + assert min_conflicts(NQueensCSP(2), 1000) is None + assert min_conflicts(NQueensCSP(3), 1000) is None + + +def test_nqueens_csp(): + csp = NQueensCSP(8) + + assignment = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4} + csp.assign(5, 5, assignment) + assert len(assignment) == 6 + csp.assign(6, 6, assignment) + assert len(assignment) == 7 + csp.assign(7, 7, assignment) + assert len(assignment) == 8 + assert assignment[5] == 5 + assert assignment[6] == 6 + assert assignment[7] == 7 + assert csp.nconflicts(3, 2, assignment) == 0 + assert csp.nconflicts(3, 3, assignment) == 0 + assert csp.nconflicts(1, 5, assignment) == 1 + assert csp.nconflicts(7, 5, assignment) == 2 + csp.unassign(1, assignment) + csp.unassign(2, assignment) + csp.unassign(3, assignment) + assert 1 not in assignment + assert 2 not in assignment + assert 3 not in assignment + + assignment = {} + assignment = {0: 0, 1: 1, 2: 4, 3: 1, 4: 6} + csp.assign(5, 7, assignment) + assert len(assignment) == 6 + csp.assign(6, 6, assignment) + assert len(assignment) == 7 + csp.assign(7, 2, assignment) + assert len(assignment) == 8 + assert assignment[5] == 7 + assert assignment[6] == 6 + assert assignment[7] == 2 + assignment = {0: 0, 1: 1, 2: 4, 3: 1, 4: 6, 5: 7, 6: 6, 7: 2} + assert csp.nconflicts(7, 7, assignment) == 4 + assert csp.nconflicts(3, 4, assignment) == 0 + assert csp.nconflicts(2, 6, assignment) == 2 + assert csp.nconflicts(5, 5, assignment) == 3 + csp.unassign(4, assignment) + csp.unassign(5, assignment) + csp.unassign(6, assignment) + assert 4 not in assignment + assert 5 not in assignment + assert 6 not in assignment + + for n in range(5, 9): + csp = NQueensCSP(n) + solution = min_conflicts(csp) + assert not solution or sorted(solution.values()) == list(range(n)) def test_universal_dict(): From fea29d195d6cab515d487973bba841c12d7e0ae2 Mon Sep 17 00:00:00 2001 From: Aabir Abubaker Kar <16526730+bakerwho@users.noreply.github.com> Date: Wed, 14 Mar 2018 19:38:05 -0400 Subject: [PATCH 075/269] Rewrote parts of search.ipynb (#809) * Rewrote parts of search.ipynb * Fixed typo and cleared cell output --- search-4e.ipynb | 3 ++- search.ipynb | 48 ++++++++++++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/search-4e.ipynb b/search-4e.ipynb index c2d0dae61..1912a7fa8 100644 --- a/search-4e.ipynb +++ b/search-4e.ipynb @@ -1929,6 +1929,7 @@ "execution_count": 52, "metadata": { "button": false, + "collapsed": true, "new_sheet": false, "run_control": { "read_only": false @@ -3822,7 +3823,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.3" + "version": "3.6.1" }, "widgets": { "state": {}, diff --git a/search.ipynb b/search.ipynb index 1ac4b075a..718161391 100644 --- a/search.ipynb +++ b/search.ipynb @@ -54,22 +54,24 @@ "source": [ "## OVERVIEW\n", "\n", - "Here, we learn about problem solving. Building goal-based agents that can plan ahead to solve problems, in particular, navigation problem/route finding problem. First, we will start the problem solving by precisely defining **problems** and their **solutions**. We will look at several general-purpose search algorithms. Broadly, search algorithms are classified into two types:\n", + "Here, we learn about a specific kind of problem solving - building goal-based agents that can plan ahead to solve problems. In particular, we examine navigation problem/route finding problem. We must begin by precisely defining **problems** and their **solutions**. We will look at several general-purpose search algorithms.\n", + "\n", + "Search algorithms can be classified into two types:\n", "\n", "* **Uninformed search algorithms**: Search algorithms which explore the search space without having any information about the problem other than its definition.\n", - "* Examples:\n", - " 1. Breadth First Search\n", - " 2. Depth First Search\n", - " 3. Depth Limited Search\n", - " 4. Iterative Deepening Search\n", + " * Examples:\n", + " 1. Breadth First Search\n", + " 2. Depth First Search\n", + " 3. Depth Limited Search\n", + " 4. Iterative Deepening Search\n", "\n", "\n", "* **Informed search algorithms**: These type of algorithms leverage any information (heuristics, path cost) on the problem to search through the search space to find the solution efficiently.\n", - "* Examples:\n", - " 1. Best First Search\n", - " 2. Uniform Cost Search\n", - " 3. A\\* Search\n", - " 4. Recursive Best First Search\n", + " * Examples:\n", + " 1. Best First Search\n", + " 2. Uniform Cost Search\n", + " 3. A\\* Search\n", + " 4. Recursive Best First Search\n", "\n", "*Don't miss the visualisations of these algorithms solving the route-finding problem defined on Romania map at the end of this notebook.*" ] @@ -124,7 +126,7 @@ "source": [ "The `Problem` class has six methods.\n", "\n", - "* `__init__(self, initial, goal)` : This is what is called a `constructor` and is the first method called when you create an instance of the class. `initial` specifies the initial state of our search problem. It represents the start state from where our agent begins its task of exploration to find the goal state(s) which is given in the `goal` parameter.\n", + "* `__init__(self, initial, goal)` : This is what is called a `constructor`. It is the first method called when you create an instance of the class as `Problem(initial, goal)`. The variable `initial` specifies the initial state $s_0$ of the search problem. It represents the beginning state. From here, our agent begins its task of exploration to find the goal state(s) which is given in the `goal` parameter.\n", "\n", "\n", "* `actions(self, state)` : This method returns all the possible actions agent can execute in the given state `state`.\n", @@ -133,7 +135,7 @@ "* `result(self, state, action)` : This returns the resulting state if action `action` is taken in the state `state`. This `Problem` class only deals with deterministic outcomes. So we know for sure what every action in a state would result to.\n", "\n", "\n", - "* `goal_test(self, state)` : Given a graph state, it checks if it is a terminal state. If the state is indeed a goal state, value of `True` is returned. Else, of course, `False` is returned.\n", + "* `goal_test(self, state)` : Return a boolean for a given state - `True` if it is a goal state, else `False`.\n", "\n", "\n", "* `path_cost(self, c, state1, action, state2)` : Return the cost of the path that arrives at `state2` as a result of taking `action` from `state1`, assuming total cost of `c` to get up to `state1`.\n", @@ -164,13 +166,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The `Node` class has nine methods.\n", + "The `Node` class has nine methods. The first is the `__init__` method.\n", "\n", "* `__init__(self, state, parent, action, path_cost)` : This method creates a node. `parent` represents the node that this is a successor of and `action` is the action required to get from the parent node to this node. `path_cost` is the cost to reach current node from parent node.\n", "\n", - "* `__repr__(self)` : This returns the state of this node.\n", - "\n", - "* `__lt__(self, node)` : Given a `node`, this method returns `True` if the state of current node is less than the state of the `node`. Otherwise it returns `False`.\n", + "The next 4 methods are specific `Node`-related functions.\n", "\n", "* `expand(self, problem)` : This method lists all the neighbouring(reachable in one step) nodes of current node. \n", "\n", @@ -180,6 +180,12 @@ "\n", "* `path(self)` : This returns a list of all the nodes that lies in the path from the root to this node.\n", "\n", + "The remaining 4 methods override standards Python functionality for representing an object as a string, the less-than ($<$) operator, the equal-to ($=$) operator, and the `hash` function.\n", + "\n", + "* `__repr__(self)` : This returns the state of this node.\n", + "\n", + "* `__lt__(self, node)` : Given a `node`, this method returns `True` if the state of current node is less than the state of the `node`. Otherwise it returns `False`.\n", + "\n", "* `__eq__(self, other)` : This method returns `True` if the state of current node is equal to the other node. Else it returns `False`.\n", "\n", "* `__hash__(self)` : This returns the hash of the state of current node." @@ -205,7 +211,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now it's time to define our problem. We will define it by passing `initial`, `goal`, `graph` to `GraphProblem`. So, our problem is to find the goal state starting from the given initial state on the provided graph. Have a look at our romania_map, which is an Undirected Graph containing a dict of nodes as keys and neighbours as values." + "Have a look at our romania_map, which is an Undirected Graph containing a dict of nodes as keys and neighbours as values." ] }, { @@ -252,7 +258,9 @@ "And `romania_map.locations` contains the positions of each of the nodes. We will use the straight line distance (which is different from the one provided in `romania_map`) between two cities in algorithms like A\\*-search and Recursive Best First Search.\n", "\n", "**Define a problem:**\n", - "Hmm... say we want to start exploring from **Arad** and try to find **Bucharest** in our romania_map. So, this is how we do it." + "Now it's time to define our problem. We will define it by passing `initial`, `goal`, `graph` to `GraphProblem`. So, our problem is to find the goal state starting from the given initial state on the provided graph. \n", + "\n", + "Say we want to start exploring from **Arad** and try to find **Bucharest** in our romania_map. So, this is how we do it." ] }, { @@ -377,7 +385,7 @@ "source": [ "The SimpleProblemSolvingAgentProgram class has six methods: \n", "\n", - "* `__init__(self, intial_state=None)`: This is the `contructor` of the class and is the first method to be called when the class is instantiated. It takes in a keyword argument, `initial_state` which is initially `None`. The argument `intial_state` represents the state from which the agent starts.\n", + "* `__init__(self, intial_state=None)`: This is the `contructor` of the class and is the first method to be called when the class is instantiated. It takes in a keyword argument, `initial_state` which is initially `None`. The argument `initial_state` represents the state from which the agent starts.\n", "\n", "* `__call__(self, percept)`: This method updates the `state` of the agent based on its `percept` using the `update_state` method. It then formulates a `goal` with the help of `formulate_goal` method and a `problem` using the `formulate_problem` method and returns a sequence of actions to solve it (using the `search` method).\n", "\n", From e245a64e51179d9b1c6883dcbaf58a7be094bd3a Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Thu, 15 Mar 2018 05:10:06 +0530 Subject: [PATCH 076/269] Added pl-fc-entails section (#818) * Added pl-fc-entails section * Updated README.md * Updated filename * Added tests for pl-fc-entails * Review fixes --- logic.ipynb | 849 ++++++++++++++++++++++++++++++++++++++++---- tests/test_logic.py | 8 + 2 files changed, 792 insertions(+), 65 deletions(-) diff --git a/logic.ipynb b/logic.ipynb index 0cd6cbc1f..92b8f51ed 100644 --- a/logic.ipynb +++ b/logic.ipynb @@ -946,7 +946,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 27, "metadata": {}, "outputs": [ { @@ -955,7 +955,7 @@ "(True, False)" ] }, - "execution_count": 22, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -973,7 +973,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -982,7 +982,7 @@ "(False, False)" ] }, - "execution_count": 23, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -1438,55 +1438,520 @@ "\n" ], "text/plain": [ - "" + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(pl_resolution)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(True, False)" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pl_resolution(wumpus_kb, ~P11), pl_resolution(wumpus_kb, P11)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(False, False)" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pl_resolution(wumpus_kb, ~P22), pl_resolution(wumpus_kb, P22)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Forward and backward chaining\n", + "Previously, we said we will look at two algorithms to check if a sentence is entailed by the `KB`, \n", + "but here's a third one. \n", + "The difference here is that our goal now is to determine if a knowledge base of definite clauses entails a single proposition symbol *q* - the query.\n", + "There is a catch however, the knowledge base can only contain **Horn clauses**.\n", + "
    \n", + "#### Horn Clauses\n", + "Horn clauses can be defined as a *disjunction* of *literals* with **at most** one positive literal. \n", + "
    \n", + "A Horn clause with exactly one positive literal is called a *definite clause*.\n", + "
    \n", + "A Horn clause might look like \n", + "
    \n", + "$\\neg a\\lor\\neg b\\lor\\neg c\\lor\\neg d... \\lor z$\n", + "
    \n", + "This, coincidentally, is also a definite clause.\n", + "
    \n", + "Using De Morgan's laws, the example above can be simplified to \n", + "
    \n", + "$a\\land b\\land c\\land d ... \\implies z$\n", + "
    \n", + "This seems like a logical representation of how humans process known data and facts. \n", + "Assuming percepts `a`, `b`, `c`, `d` ... to be true simultaneously, we can infer `z` to also be true at that point in time. \n", + "There are some interesting aspects of Horn clauses that make algorithmic inference or *resolution* easier.\n", + "- Definite clauses can be written as implications:\n", + "
    \n", + "The most important simplification a definite clause provides is that it can be written as an implication.\n", + "The premise (or the knowledge that leads to the implication) is a conjunction of positive literals.\n", + "The conclusion (the implied statement) is also a positive literal.\n", + "The sentence thus becomes easier to understand.\n", + "The premise and the conclusion are conventionally called the *body* and the *head* respectively.\n", + "A single positive literal is called a *fact*.\n", + "- Forward chaining and backward chaining can be used for inference from Horn clauses:\n", + "
    \n", + "Forward chaining is semantically identical to `AND-OR-Graph-Search` from the chapter on search algorithms.\n", + "Implementational details will be explained shortly.\n", + "- Deciding entailment with Horn clauses is linear in size of the knowledge base:\n", + "
    \n", + "Surprisingly, the forward and backward chaining algorithms traverse each element of the knowledge base at most once, greatly simplifying the problem.\n", + "
    \n", + "
    \n", + "The function `pl_fc_entails` implements forward chaining to see if a knowledge base `KB` entails a symbol `q`.\n", + "
    \n", + "Before we proceed further, note that `pl_fc_entails` doesn't use an ordinary `KB` instance. \n", + "The knowledge base here is an instance of the `PropDefiniteKB` class, derived from the `PropKB` class, \n", + "but modified to store definite clauses.\n", + "
    \n", + "The main point of difference arises in the inclusion of a helper method to `PropDefiniteKB` that returns a list of clauses in KB that have a given symbol `p` in their premise." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
        def clauses_with_premise(self, p):\n",
    +       "        """Return a list of the clauses in KB that have p in their premise.\n",
    +       "        This could be cached away for O(1) speed, but we'll recompute it."""\n",
    +       "        return [c for c in self.clauses\n",
    +       "                if c.op == '==>' and p in conjuncts(c.args[0])]\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(PropDefiniteKB.clauses_with_premise)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now have a look at the `pl_fc_entails` algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def pl_fc_entails(KB, q):\n",
    +       "    """Use forward chaining to see if a PropDefiniteKB entails symbol q.\n",
    +       "    [Figure 7.15]\n",
    +       "    >>> pl_fc_entails(horn_clauses_KB, expr('Q'))\n",
    +       "    True\n",
    +       "    """\n",
    +       "    count = {c: len(conjuncts(c.args[0]))\n",
    +       "             for c in KB.clauses\n",
    +       "             if c.op == '==>'}\n",
    +       "    inferred = defaultdict(bool)\n",
    +       "    agenda = [s for s in KB.clauses if is_prop_symbol(s.op)]\n",
    +       "    while agenda:\n",
    +       "        p = agenda.pop()\n",
    +       "        if p == q:\n",
    +       "            return True\n",
    +       "        if not inferred[p]:\n",
    +       "            inferred[p] = True\n",
    +       "            for c in KB.clauses_with_premise(p):\n",
    +       "                count[c] -= 1\n",
    +       "                if count[c] == 0:\n",
    +       "                    agenda.append(c.args[1])\n",
    +       "    return False\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(pl_fc_entails)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function accepts a knowledge base `KB` (an instance of `PropDefiniteKB`) and a query `q` as inputs.\n", + "
    \n", + "
    \n", + "`count` initially stores the number of symbols in the premise of each sentence in the knowledge base.\n", + "
    \n", + "The `conjuncts` helper function separates a given sentence at conjunctions.\n", + "
    \n", + "`inferred` is initialized as a *boolean* defaultdict. \n", + "This will be used later to check if we have inferred all premises of each clause of the agenda.\n", + "
    \n", + "`agenda` initially stores a list of clauses that the knowledge base knows to be true.\n", + "The `is_prop_symbol` helper function checks if the given symbol is a valid propositional logic symbol.\n", + "
    \n", + "
    \n", + "We now iterate through `agenda`, popping a symbol `p` on each iteration.\n", + "If the query `q` is the same as `p`, we know that entailment holds.\n", + "
    \n", + "The agenda is processed, reducing `count` by one for each implication with a premise `p`.\n", + "A conclusion is added to the agenda when `count` reaches zero. This means we know all the premises of that particular implication to be true.\n", + "
    \n", + "`clauses_with_premise` is a helpful method of the `PropKB` class.\n", + "It returns a list of clauses in the knowledge base that have `p` in their premise.\n", + "
    \n", + "
    \n", + "Now that we have an idea of how this function works, let's see a few examples of its usage, but we first need to define our knowledge base. We assume we know the following clauses to be true." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "clauses = ['(B & F)==>E', \n", + " '(A & E & F)==>G', \n", + " '(B & C)==>F', \n", + " '(A & B)==>D', \n", + " '(E & F)==>H', \n", + " '(H & I)==>J',\n", + " 'A', \n", + " 'B', \n", + " 'C']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now `tell` this information to our knowledge base." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "definite_clauses_KB = PropDefiniteKB()\n", + "for clause in clauses:\n", + " definite_clauses_KB.tell(expr(clause))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now check if our knowledge base entails the following queries." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" ] }, + "execution_count": 44, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "psource(pl_resolution)" + "pl_fc_entails(definite_clauses_KB, expr('G'))" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "(True, False)" + "True" ] }, - "execution_count": 25, + "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "pl_resolution(wumpus_kb, ~P11), pl_resolution(wumpus_kb, P11)" + "pl_fc_entails(definite_clauses_KB, expr('H'))" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "(False, False)" + "False" ] }, - "execution_count": 26, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "pl_resolution(wumpus_kb, ~P22), pl_resolution(wumpus_kb, P22)" + "pl_fc_entails(definite_clauses_KB, expr('I'))" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pl_fc_entails(definite_clauses_KB, expr('J'))" ] }, { @@ -2357,7 +2822,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 48, "metadata": { "collapsed": true }, @@ -2386,7 +2851,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 49, "metadata": { "collapsed": true }, @@ -2407,7 +2872,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 50, "metadata": { "collapsed": true }, @@ -2428,7 +2893,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 51, "metadata": { "collapsed": true }, @@ -2452,7 +2917,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 52, "metadata": { "collapsed": true }, @@ -2473,7 +2938,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 53, "metadata": { "collapsed": true }, @@ -2493,7 +2958,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 54, "metadata": { "collapsed": true }, @@ -2512,7 +2977,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 55, "metadata": { "collapsed": true }, @@ -2539,7 +3004,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 56, "metadata": {}, "outputs": [ { @@ -2548,7 +3013,7 @@ "{x: 3}" ] }, - "execution_count": 35, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -2559,7 +3024,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 57, "metadata": {}, "outputs": [ { @@ -2568,7 +3033,7 @@ "{x: B}" ] }, - "execution_count": 36, + "execution_count": 57, "metadata": {}, "output_type": "execute_result" } @@ -2579,7 +3044,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 58, "metadata": {}, "outputs": [ { @@ -2588,7 +3053,7 @@ "{x: Bella, y: Dobby}" ] }, - "execution_count": 37, + "execution_count": 58, "metadata": {}, "output_type": "execute_result" } @@ -2606,7 +3071,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 59, "metadata": {}, "outputs": [ { @@ -2630,7 +3095,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 60, "metadata": {}, "outputs": [ { @@ -2657,13 +3122,145 @@ }, { "cell_type": "code", - "execution_count": 40, - "metadata": { - "collapsed": true - }, - "outputs": [], + "execution_count": 61, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def fol_fc_ask(KB, alpha):\n",
    +       "    """A simple forward-chaining algorithm. [Figure 9.3]"""\n",
    +       "    # TODO: Improve efficiency\n",
    +       "    kb_consts = list({c for clause in KB.clauses for c in constant_symbols(clause)})\n",
    +       "    def enum_subst(p):\n",
    +       "        query_vars = list({v for clause in p for v in variables(clause)})\n",
    +       "        for assignment_list in itertools.product(kb_consts, repeat=len(query_vars)):\n",
    +       "            theta = {x: y for x, y in zip(query_vars, assignment_list)}\n",
    +       "            yield theta\n",
    +       "\n",
    +       "    # check if we can answer without new inferences\n",
    +       "    for q in KB.clauses:\n",
    +       "        phi = unify(q, alpha, {})\n",
    +       "        if phi is not None:\n",
    +       "            yield phi\n",
    +       "\n",
    +       "    while True:\n",
    +       "        new = []\n",
    +       "        for rule in KB.clauses:\n",
    +       "            p, q = parse_definite_clause(rule)\n",
    +       "            for theta in enum_subst(p):\n",
    +       "                if set(subst(theta, p)).issubset(set(KB.clauses)):\n",
    +       "                    q_ = subst(theta, q)\n",
    +       "                    if all([unify(x, q_, {}) is None for x in KB.clauses + new]):\n",
    +       "                        new.append(q_)\n",
    +       "                        phi = unify(q_, alpha, {})\n",
    +       "                        if phi is not None:\n",
    +       "                            yield phi\n",
    +       "        if not new:\n",
    +       "            break\n",
    +       "        for clause in new:\n",
    +       "            KB.tell(clause)\n",
    +       "    return None\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "%psource fol_fc_ask" + "psource(fol_fc_ask)" ] }, { @@ -2675,7 +3272,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 62, "metadata": {}, "outputs": [ { @@ -2700,7 +3297,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 63, "metadata": {}, "outputs": [ { @@ -2742,7 +3339,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 64, "metadata": { "collapsed": true }, @@ -2761,7 +3358,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 65, "metadata": { "collapsed": true }, @@ -2779,7 +3376,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 66, "metadata": { "collapsed": true }, @@ -2791,7 +3388,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 67, "metadata": {}, "outputs": [ { @@ -2800,7 +3397,7 @@ "{v_5: x, x: Nono}" ] }, - "execution_count": 46, + "execution_count": 67, "metadata": {}, "output_type": "execute_result" } @@ -2827,7 +3424,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 68, "metadata": {}, "outputs": [ { @@ -2836,7 +3433,7 @@ "(P ==> ~Q)" ] }, - "execution_count": 47, + "execution_count": 68, "metadata": {}, "output_type": "execute_result" } @@ -2854,7 +3451,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 69, "metadata": {}, "outputs": [ { @@ -2863,7 +3460,7 @@ "(P ==> ~Q)" ] }, - "execution_count": 48, + "execution_count": 69, "metadata": {}, "output_type": "execute_result" } @@ -2881,7 +3478,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 70, "metadata": {}, "outputs": [ { @@ -2890,7 +3487,7 @@ "PartialExpr('==>', P)" ] }, - "execution_count": 49, + "execution_count": 70, "metadata": {}, "output_type": "execute_result" } @@ -2910,7 +3507,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 71, "metadata": {}, "outputs": [ { @@ -2919,7 +3516,7 @@ "(P ==> ~Q)" ] }, - "execution_count": 50, + "execution_count": 71, "metadata": {}, "output_type": "execute_result" } @@ -2949,7 +3546,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 72, "metadata": {}, "outputs": [ { @@ -2958,7 +3555,7 @@ "(~(P & Q) ==> (~P | ~Q))" ] }, - "execution_count": 51, + "execution_count": 72, "metadata": {}, "output_type": "execute_result" } @@ -2976,7 +3573,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 73, "metadata": {}, "outputs": [ { @@ -2985,7 +3582,7 @@ "(~(P & Q) ==> (~P | ~Q))" ] }, - "execution_count": 52, + "execution_count": 73, "metadata": {}, "output_type": "execute_result" } @@ -3004,7 +3601,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 74, "metadata": {}, "outputs": [ { @@ -3013,7 +3610,7 @@ "(((P & Q) ==> P) | Q)" ] }, - "execution_count": 53, + "execution_count": 74, "metadata": {}, "output_type": "execute_result" } @@ -3031,7 +3628,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 75, "metadata": {}, "outputs": [ { @@ -3040,7 +3637,7 @@ "((P & Q) ==> (P | Q))" ] }, - "execution_count": 54, + "execution_count": 75, "metadata": {}, "output_type": "execute_result" } @@ -3058,11 +3655,133 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], + "execution_count": 76, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
    \n", + "\n", + "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from notebook import Canvas_fol_bc_ask\n", "canvas_bc_ask = Canvas_fol_bc_ask('canvas_bc_ask', crime_kb, expr('Criminal(x)'))" diff --git a/tests/test_logic.py b/tests/test_logic.py index 86bcc9ed6..6da2eb320 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -2,6 +2,10 @@ from logic import * from utils import expr_handle_infix_ops, count, Symbol +definite_clauses_KB = PropDefiniteKB() +for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', 'C']: + definite_clauses_KB.tell(expr(clause)) + def test_is_symbol(): assert is_symbol('x') @@ -154,6 +158,10 @@ def test_unify(): def test_pl_fc_entails(): assert pl_fc_entails(horn_clauses_KB, expr('Q')) + assert pl_fc_entails(definite_clauses_KB, expr('G')) + assert pl_fc_entails(definite_clauses_KB, expr('H')) + assert not pl_fc_entails(definite_clauses_KB, expr('I')) + assert not pl_fc_entails(definite_clauses_KB, expr('J')) assert not pl_fc_entails(horn_clauses_KB, expr('SomethingSilly')) From 49adcdb91636e0c5e126f8259fa01d2ffc67c0ef Mon Sep 17 00:00:00 2001 From: Kunwar Raj Singh Date: Thu, 15 Mar 2018 05:42:57 +0530 Subject: [PATCH 077/269] Implemented HybridWumpusAgent (#842) * Added WumpusKB for use in HybridWumpusAgent * Implemented HybridWumpusAgent added WumpusPosition helping class. --- logic.py | 307 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 306 insertions(+), 1 deletion(-) diff --git a/logic.py b/logic.py index 129d281cf..130718faa 100644 --- a/logic.py +++ b/logic.py @@ -690,16 +690,321 @@ def sat_count(sym): # ______________________________________________________________________________ +class WumpusKB(PropKB): + """ + Create a Knowledge Base that contains the atemporal "Wumpus physics" and temporal rules with time zero. + """ + def __init__(self,dimrow): + super().__init__() + self.dimrow = dimrow + self.tell('( NOT W1s1 )') + self.tell('( NOT P1s1 )') + for i in range(1, dimrow+1): + for j in range(1, dimrow+1): + bracket = 0 + sentence_b_str = "( B" + i + "s" + j + " <=> " + sentence_s_str = "( S" + i + "s" + j + " <=> " + if i > 1: + sentence_b_str += "( P" + (i-1) + "s" + j + " OR " + sentence_s_str += "( W" + (i-1) + "s" + j + " OR " + bracket += 1 + + if i < dimRow: + sentence_b_str += "( P" + (i+1) + "s" + j + " OR " + sentence_s_str += "( W" + (i+1) + "s" + j + " OR " + bracket += 1 + + if j > 1: + if j == dimRow: + sentence_b_str += "P" + i + "s" + (j-1) + " " + sentence_s_str += "W "+ i + "s" + (j-1) + " " + else: + sentence_b_str += "( P" + i + "s" + (j-1) + " OR " + sentence_s_str += "( W" + i + "s" + (j-1) + " OR " + bracket += 1 + + if j < dimRow: + sentence_b_str += "P" + i + "s" + (j+1) + " " + sentence_s_str += "W" + i + "s" + (j+1) + " " + + + for _ in range(bracket): + sentence_b_str += ") " + sentence_s_str += ") " + + sentence_b_str += ") " + sentence_s_str += ") " + + self.tell(sentence_b_str) + self.tell(sentence_s_str) + + + ## Rule that describes existence of at least one Wumpus + sentence_w_str = "" + for i in range(1, dimrow+1): + for j in range(1, dimrow+1): + if (i == dimrow) and (j == dimrow): + sentence_w_str += " W" + dimRow + "s" + dimrow + " " + else: + sentence_w_str += "( W" + i + "s" + j + " OR " + for _ in range(dimrow**2): + sentence_w_str += ") " + self.tell(sentence_w_str) + + + ## Rule that describes existence of at most one Wumpus + for i in range(1, dimrow+1): + for j in range(1, dimrow+1): + for u in range(1, dimrow+1): + for v in range(1, dimrow+1): + if i!=u or j!=v: + self.tell("( ( NOT W" + i + "s" + j + " ) OR ( NOT W" + u + "s" + v + " ) )") + + ## Temporal rules at time zero + self.tell("L1s1s0") + for i in range(1, dimrow+1): + for j in range(1, dimrow + 1): + self.tell("( L" + i + "s" + j + "s0 => ( Breeze0 <=> B" + i + "s" + j + " ) )") + self.tell("( L" + i + "s" + j + "s0 => ( Stench0 <=> S" + i + "s" + j + " ) )") + if i != 1 or j != 1: + self.tell("( NOT L" + i + "s" + j + "s" + "0 )") + self.tell("WumpusAlive0") + self.tell("HaveArrow0") + self.tell("FacingEast0") + self.tell("( NOT FacingWest0 )") + self.tell("( NOT FacingNorth0 )") + self.tell("( NOT FacingSouth0 )") + + + def make_action_sentence(self, action, time): + self.tell(action + time) + + + def make_percept_sentence(self, percept, time): + self.tell(percept + time) + + def add_temporal_sentences(self, time): + if time == 0: + return + t = time - 1 + + ## current location rules (L2s2s3 represent tile 2,2 at time 3) + ## ex.: ( L2s2s3 <=> ( ( L2s2s2 AND ( ( NOT Forward2 ) OR Bump3 ) ) + ## OR ( ( L1s2s2 AND ( FacingEast2 AND Forward2 ) ) OR ( L2s1s2 AND ( FacingNorth2 AND Forward2 ) ) ) + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): + self.tell("( L" + i + "s" + j + "s" + time + " => ( Breeze" + time + " <=> B" + i + "s" + j + " ) )") + self.tell("( L" + i + "s" + j + "s" + time + " => ( Stench" + time + " <=> S" + i + "s" + j + " ) )") + s = "( L" + i + "s" + j + "s" + time + " <=> ( ( L" + i + "s" + j + "s" + t + " AND ( ( NOT Forward"\ + + t + " ) OR Bump" + time + " ) )" + + count = 2 + if i != 1: + s += " OR ( ( L" + (i - 1) + "s" + j + "s" + t + " AND ( FacingEast" + t + " AND Forward" + t\ + + " ) )" + count += 1 + if i != self.dimrow: + s += " OR ( ( L" + (i + 1) + "s" + j + "s" + t + " AND ( FacingWest" + t + " AND Forward" + t\ + + " ) )" + count += 1 + if j != 1: + if j == self.dimrow: + s += " OR ( L" + i + "s" + (j - 1) + "s" + t + " AND ( FacingNorth" + t + " AND Forward" + t\ + + " ) )" + else: + s += " OR ( ( L" + i + "s" + (j - 1) + "s" + t + " AND ( FacingNorth" + t + " AND Forward" \ + + t + " ) )" + count += 1 + if j != self.dimrow: + s += " OR ( L" + i + "s" + (j + 1) + "s" + t + " AND ( FacingSouth" + t + " AND Forward" + t\ + + " ) )" + + for _ in range(count): + s += " )" + + ## add sentence about location i,j + self.tell(s) + + ## add sentence about safety of location i,j + self.tell("( OK" + i + "s" + j + "s" + time + " <=> ( ( NOT P" + i + "s" + j + " ) AND ( NOT ( W" + i\ + + "s" + j + " AND WumpusAlive" + time + " ) ) ) )") + + ## Rules about current orientation + ## ex.: ( FacingEast3 <=> ( ( FacingNorth2 AND TurnRight2 ) OR ( ( FacingSouth2 AND TurnLeft2 ) + ## OR ( FacingEast2 AND ( ( NOT TurnRight2 ) AND ( NOT TurnLeft2 ) ) ) ) ) ) + a = "( FacingNorth" + t + " AND TurnRight" + t + " )" + b = "( FacingSouth" + t + " AND TurnLeft" + t + " )" + c = "( FacingEast" + t + " AND ( ( NOT TurnRight" + t + " ) AND ( NOT TurnLeft" + t + " ) ) )" + s = "( FacingEast" + (t + 1) + " <=> ( " + a + " OR ( " + b + " OR " + c + " ) ) )" + this.tell(s) + + a = "( FacingNorth" + t + " AND TurnLeft" + t + " )" + b = "( FacingSouth" + t + " AND TurnRight" + t + " )" + c = "( FacingWest" + t + " AND ( ( NOT TurnRight" + t + " ) AND ( NOT TurnLeft" + t + " ) ) )" + s = "( FacingWest" + (t + 1) + " <=> ( " + a + " OR ( " + b + " OR " + c + " ) ) )" + this.tell(s) + + a = "( FacingEast" + t + " AND TurnLeft" + t + " )" + b = "( FacingWest" + t + " AND TurnRight" + t + " )" + c = "( FacingNorth" + t + " AND ( ( NOT TurnRight" + t + " ) AND ( NOT TurnLeft" + t + " ) ) )" + s = "( FacingNorth" + (t + 1) + " <=> ( " + a + " OR ( " + b + " OR " + c + " ) ) )" + this.tell(s) + + a = "( FacingWest" + t + " AND TurnLeft" + t + " )" + b = "( FacingEast" + t + " AND TurnRight" + t + " )" + c = "( FacingSouth" + t + " AND ( ( NOT TurnRight" + t + " ) AND ( NOT TurnLeft" + t + " ) ) )" + s = "( FacingSouth" + (t + 1) + " <=> ( " + a + " OR ( " + b + " OR " + c + " ) ) )" + this.tell(s) + + ## Rules about last action + self.tell("( Forward" + t + " <=> ( NOT TurnRight" + t + " ) )") + self.tell("( Forward" + t + " <=> ( NOT TurnLeft" + t + " ) )") + + ##Rule about the arrow + self.tell("( HaveArrow" + time + " <=> ( HaveArrow" + (time - 1) + " AND ( NOT Shot" + (time - 1) + " ) ) )") + + ##Rule about Wumpus (dead or alive) + self.tell("( WumpusAlive" + time + " <=> ( WumpusAlive" + (time - 1) + " AND ( NOT Scream" + time + " ) ) )") + + +# ______________________________________________________________________________ + + +class WumpusPosition(): + def __init__(self, X, Y, orientation): + self.X = X + self.Y = Y + self.orientation = orientation + + + def get_location(self): + return self.X, self.Y + + def get_orientation(self): + return self.orientation + + def equals(self, wumpus_position): + if wumpus_position.get_location() == self.get_location() and \ + wumpus_position.get_orientation()==self.get_orientation(): + return True + else: + return False + +# ______________________________________________________________________________ + + class HybridWumpusAgent(agents.Agent): """An agent for the wumpus world that does logical inference. [Figure 7.20]""" def __init__(self): - raise NotImplementedError + super().__init__() + self.dimrow = 3 + self.kb = WumpusKB(self.dimrow) + self.t = 0 + self.plan = list() + self.current_position = WumpusPosition(1, 1, 'UP') + + + def execute(self, percept): + self.kb.make_percept_sentence(percept, self.t) + self.kb.add_temporal_sentences(self.t) + + temp = list() + + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): + if self.kb.ask_with_dpll('L' + i + 's' + j + 's' + self.t): + temp.append(i) + temp.append(j) + + if self.kb.ask_with_dpll('FacingNorth' + self.t): + self.current_position = WumpusPosition(temp[0], temp[1], 'UP') + elif self.kb.ask_with_dpll('FacingSouth' + self.t): + self.current_position = WumpusPosition(temp[0], temp[1], 'DOWN') + elif self.kb.ask_with_dpll('FacingWest' + self.t): + self.current_position = WumpusPosition(temp[0], temp[1], 'LEFT') + elif self.kb.ask_with_dpll('FacingEast' + self.t): + self.current_position = WumpusPosition(temp[0], temp[1], 'RIGHT') + + safe_points = list() + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): + if self.kb.ask_with_dpll('OK' + i + 's' + j + 's' + self.t): + safe_points.append([i, j]) + + if self.kb.ask_with_dpll('Glitter' + self.t): + goals = list() + goals.append([1, 1]) + self.plan.append('Grab') + actions = plan_route(self.current_position,goals,safe_points) + for action in actions: + self.plan.append(action) + self.plan.append('Climb') + + if len(self.plan) == 0: + unvisited = list() + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): + for k in range(1, self.dimrow+1): + if self.kb.ask_with_dpll("L" + i + "s" + j + "s" + k): + unvisited.append([i, j]) + unvisited_and_safe = list() + for u in unvisited: + for s in safe_points: + if u not in unvisited_and_safe and s == u: + unvisited_and_safe.append(u) + + temp = plan_route(self.current_position,unvisited_and_safe,safe_points) + for t in temp: + self.plan.append(t) + + if len(self.plan) == 0 and self.kb.ask_with_dpll('HaveArrow' + self.t): + possible_wumpus = list() + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): + if not self.kb.ask_with_dpll('W' + i + 's' + j): + possible_wumpus.append([i, j]) + + temp = plan_shot(self.current_position, possible_wumpus, safe_points) + for t in temp: + self.plan.append(t) + + if len(self.plan) == 0: + not_unsafe = list() + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): + if not self.kb.ask_with_dpll('OK' + i + 's' + j + 's' + self.t): + not_unsafe.append([i, j]) + temp = plan_route(self.current_position, not_unsafe, safe_points) + for t in temp: + self.plan.append(t) + + if len(self.plan) == 0: + start = list() + start.append([1, 1]) + temp = plan_route(self.current_position, start, safe_points) + for t in temp: + self.plan.append(t) + self.plan.append('Climb') + + + + action = self.plan[1:] + + self.kb.make_action_sentence(action, self.t) + self.t += 1 + + return action def plan_route(current, goals, allowed): raise NotImplementedError + +def plan_shot(current, goals, allowed): + raise NotImplementedError + + # ______________________________________________________________________________ From c13408dbb36671172fe1c2d078bf73a907326cbd Mon Sep 17 00:00:00 2001 From: Dimkoim Date: Thu, 15 Mar 2018 01:19:25 +0100 Subject: [PATCH 078/269] Forward-Backward examples added to the probability.ipynb. Fixes issue #813 (#827) * Forward-Backward examples added to the ipynb. Fixes issue #813 * Forward-Backward examples added to the probability.ipynb. Fixes issue #813 * Convert Latex syntax to Markdown except from the equations with subscript characters --- probability.ipynb | 401 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 369 insertions(+), 32 deletions(-) diff --git a/probability.ipynb b/probability.ipynb index 2fd1c9dae..365039874 100644 --- a/probability.ipynb +++ b/probability.ipynb @@ -11,21 +11,19 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from probability import *\n", - "from notebook import psource" + "from notebook import *" ] }, { "cell_type": "markdown", - "metadata": { - "collapsed": true - }, + "metadata": {}, "source": [ "## Probability Distribution\n", "\n", @@ -34,7 +32,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 34, "metadata": { "collapsed": true }, @@ -45,7 +43,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -54,7 +52,7 @@ "0.75" ] }, - "execution_count": 2, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -255,9 +253,7 @@ }, { "cell_type": "markdown", - "metadata": { - "collapsed": true - }, + "metadata": {}, "source": [ "_A probability model is completely determined by the joint distribution for all of the random variables._ (**Section 13.3**) The probability module implements these as the class **JointProbDist** which inherits from the **ProbDist** class. This class specifies a discrete probability distribute over a set of variables. " ] @@ -512,9 +508,124 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

    \n", + "\n", + "
    def enumerate_joint_ask(X, e, P):\n",
    +       "    """Return a probability distribution over the values of the variable X,\n",
    +       "    given the {var:val} observations e, in the JointProbDist P. [Section 13.3]\n",
    +       "    >>> P = JointProbDist(['X', 'Y'])\n",
    +       "    >>> P[0,0] = 0.25; P[0,1] = 0.5; P[1,1] = P[2,1] = 0.125\n",
    +       "    >>> enumerate_joint_ask('X', dict(Y=1), P).show_approx()\n",
    +       "    '0: 0.667, 1: 0.167, 2: 0.167'\n",
    +       "    """\n",
    +       "    assert X not in e, "Query variable must be distinct from evidence"\n",
    +       "    Q = ProbDist(X)  # probability distribution for X, initially empty\n",
    +       "    Y = [v for v in P.variables if v != X and v not in e]  # hidden variables.\n",
    +       "    for xi in P.values(X):\n",
    +       "        Q[xi] = enumerate_joint(Y, extend(e, X, xi), P)\n",
    +       "    return Q.normalize()\n",
    +       "
    \n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "psource(enumerate_joint_ask)" ] @@ -792,7 +903,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ @@ -1178,7 +1289,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 32, "metadata": { "collapsed": true }, @@ -1418,21 +1529,8 @@ ] }, { - "cell_type": "code", - "execution_count": 45, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'False: 0.184, True: 0.816'" - ] - }, - "execution_count": 45, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ "likelihood_weighting('Cloudy', dict(Rain=True), sprinkler, 200).show_approx()" ] @@ -1450,7 +1548,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 27, "metadata": { "collapsed": true }, @@ -1485,6 +1583,245 @@ "source": [ "gibbs_ask('Cloudy', dict(Rain=True), sprinkler, 200).show_approx()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Inference in Temporal Models" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before we start, it will be helpful to understand the structure of a temporal model. We will use the example of the book with the guard and the umbrella. In this example, the state $\\textbf{X}$ is whether it is a rainy day (`X = True`) or not (`X = False`) at Day $\\textbf{t}$. In the sensor or observation model, the observation or evidence $\\textbf{U}$ is whether the professor holds an umbrella (`U = True`) or not (`U = False`) on **Day** $\\textbf{t}$. Based on that, the transition model is \n", + "\n", + "| $X_{t-1}$ | $X_{t}$ | **P**$(X_{t}| X_{t-1})$| \n", + "| ------------- |------------- | ----------------------------------|\n", + "| ***${False}$*** | ***${False}$*** | 0.7 |\n", + "| ***${False}$*** | ***${True}$*** | 0.3 |\n", + "| ***${True}$*** | ***${False}$*** | 0.3 |\n", + "| ***${True}$*** | ***${True}$*** | 0.7 |\n", + "\n", + "And the the sensor model will be,\n", + "\n", + "| $X_{t}$ | $U_{t}$ | **P**$(U_{t}|X_{t})$| \n", + "| :-------------: |:-------------: | :------------------------:|\n", + "| ***${False}$*** | ***${True}$*** | 0.2 |\n", + "| ***${False}$*** | ***${False}$*** | 0.8 |\n", + "| ***${True}$*** | ***${True}$*** | 0.9 |\n", + "| ***${True}$*** | ***${False}$*** | 0.1 |\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the filtering task we are given evidence **U** in each time **t** and we want to compute the belief $B_{t}(x)= P(X_{t}|U_{1:t})$. \n", + "We can think of it as a three step process:\n", + "1. In every step we start with the current belief $P(X_{t}|e_{1:t})$\n", + "2. We update it for time\n", + "3. We update it for evidence\n", + "\n", + "The forward algorithm performs the step 2 and 3 at once. It updates, or better say reweights, the initial belief using the transition and the sensor model. Let's see the umbrella example. On **Day 0** no observation is available, and for that reason we will assume that we have equal possibilities to rain or not. In the **`HiddenMarkovModel`** class, the prior probabilities for **Day 0** are by default [0.5, 0.5]. " + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "%psource HiddenMarkovModel" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We instantiate the object **`hmm`** of the class using a list of lists for both the transition and the sensor model." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "umbrella_transition_model = [[0.7, 0.3], [0.3, 0.7]]\n", + "umbrella_sensor_model = [[0.9, 0.2], [0.1, 0.8]]\n", + "hmm = HiddenMarkovModel(umbrella_transition_model, umbrella_sensor_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The **`sensor_dist()`** method returns a list with the conditional probabilities of the sensor model." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0.9, 0.2]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hmm.sensor_dist(ev=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The observation update is calculated with the **`forward()`** function. Basically, we update our belief using the observation model. The function returns a list with the probabilities of **raining or not** on **Day 1**." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "psource(forward)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The probability of raining on day 1 is 0.82\n" + ] + } + ], + "source": [ + "belief_day_1 = forward(hmm, umbrella_prior, ev=True)\n", + "print ('The probability of raining on day 1 is {:.2f}'.format(belief_day_1[0]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In **Day 2** our initial belief is the updated belief of **Day 1**. Again using the **`forward()`** function we can compute the probability of raining in **Day 2**" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The probability of raining in day 2 is 0.88\n" + ] + } + ], + "source": [ + "belief_day_2 = forward(hmm, belief_day_1, ev=True)\n", + "print ('The probability of raining in day 2 is {:.2f}'.format(belief_day_2[0]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the smoothing part we are interested in computing the distribution over past states given evidence up to the present. Assume that we want to compute the distribution for the time **k**, for $0\\leq k Date: Wed, 14 Mar 2018 20:45:34 -0400 Subject: [PATCH 079/269] Add test for TableDrivenAgentProgram. (#749) Fixes #748. --- tests/test_agents.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_agents.py b/tests/test_agents.py index d5f63bc48..ded9b7d95 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -208,6 +208,20 @@ def test_compare_agents() : assert performance_ReflexVacummAgent <= performance_ModelBasedVacummAgent +def test_TableDrivenAgentProgram(): + table = {(('foo', 1),): 'action1', + (('foo', 2),): 'action2', + (('bar', 1),): 'action3', + (('bar', 2),): 'action1', + (('foo', 1), ('foo', 1),): 'action2', + (('foo', 1), ('foo', 2),): 'action3', + } + agent_program = TableDrivenAgentProgram(table) + assert agent_program(('foo', 1)) == 'action1' + assert agent_program(('foo', 2)) == 'action3' + assert agent_program(('invalid percept',)) == None + + def test_Agent(): def constant_prog(percept): return percept From 11cc2ccee345dc8ce5787bc4dcd303b259d81350 Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Thu, 15 Mar 2018 06:28:10 +0530 Subject: [PATCH 080/269] Refactored EightPuzzle class (#807) * Refactor EightPuzzle class * return instead of print * Added tests for EightPuzzle * Review fixes * Review fixes * Fixed tests * Update inverted commas in docstrings --- search.py | 125 +++++++++++++++++-------------------------- tests/test_search.py | 59 ++++++++++++++++++++ 2 files changed, 108 insertions(+), 76 deletions(-) diff --git a/search.py b/search.py index a80a48c8c..7480d28ca 100644 --- a/search.py +++ b/search.py @@ -411,102 +411,75 @@ def astar_search(problem, h=None): class EightPuzzle(Problem): - """The problem of sliding tiles numbered from 1 to 8 on a 3x3 board, + """ The problem of sliding tiles numbered from 1 to 8 on a 3x3 board, where one of the squares is a blank. A state is represented as a 3x3 list, - where element at index i,j represents the tile number (0 if it's an empty square).""" + where element at index i,j represents the tile number (0 if it's an empty square) """ - def __init__(self, initial, goal=None): - if goal: - self.goal = goal - else: - self.goal = [ [0,1,2], - [3,4,5], - [6,7,8] ] + def __init__(self, initial, goal=(1, 2, 3, 4, 5, 6, 7, 8, 0)): + """ Define goal state and initialize a problem """ + + self.goal = goal Problem.__init__(self, initial, goal) def find_blank_square(self, state): """Return the index of the blank square in a given state""" - for row in len(state): - for column in len(row): - if state[row][column] == 0: - index_blank_square = (row, column) - return index_blank_square + + return state.index(0) def actions(self, state): - """Return the actions that can be executed in the given state. + """ Return the actions that can be executed in the given state. The result would be a list, since there are only four possible actions - in any given state of the environment.""" - - possible_actions = list() + in any given state of the environment """ + + possible_actions = ['UP', 'DOWN', 'LEFT', 'RIGHT'] index_blank_square = self.find_blank_square(state) - if index_blank_square(0) == 0: - possible_actions += ['DOWN'] - elif index_blank_square(0) == 1: - possible_actions += ['UP', 'DOWN'] - elif index_blank_square(0) == 2: - possible_actions += ['UP'] - - if index_blank_square(1) == 0: - possible_actions += ['RIGHT'] - elif index_blank_square(1) == 1: - possible_actions += ['LEFT', 'RIGHT'] - elif index_blank_square(1) == 2: - possible_actions += ['LEFT'] + if index_blank_square % 3 == 0: + possible_actions.remove('LEFT') + if index_blank_square < 3: + possible_actions.remove('UP') + if index_blank_square % 3 == 2: + possible_actions.remove('RIGHT') + if index_blank_square > 5: + possible_actions.remove('DOWN') return possible_actions def result(self, state, action): - """Given state and action, return a new state that is the result of the action. - Action is assumed to be a valid action in the state.""" - - blank_square = self.find_blank_square(state) - new_state = [row[:] for row in state] - - if action=='UP': - new_state[blank_square(0)][blank_square(1)] = new_state[blank_square(0)-1][blank_square(1)] - new_state[blank_square(0)-1][blank_square(1)] = 0 - elif action=='LEFT': - new_state[blank_square(0)][blank_square(1)] = new_state[blank_square(0)][blank_square(1)-1] - new_state[blank_square(0)][blank_square(1)-1] = 0 - elif action=='DOWN': - new_state[blank_square(0)][blank_square(1)] = new_state[blank_square(0)+1][blank_square(1)] - new_state[blank_square(0)+1][blank_square(1)] = 0 - elif action=='RIGHT': - new_state[blank_square(0)][blank_square(1)] = new_state[blank_square(0)][blank_square(1)+1] - new_state[blank_square(0)][blank_square(1)+1] = 0 - else: - print("Invalid Action!") - return new_state + """ Given state and action, return a new state that is the result of the action. + Action is assumed to be a valid action in the state """ + + # blank is the index of the blank square + blank = self.find_blank_square(state) + new_state = list(state) + + delta = {'UP':-3, 'DOWN':3, 'LEFT':-1, 'RIGHT':1} + neighbor = blank + delta[action] + new_state[blank], new_state[neighbor] = new_state[neighbor], new_state[blank] + + return tuple(new_state) def goal_test(self, state): - """Given a state, return True if state is a goal state or False, otherwise""" - for row in len(state): - for column in len(row): - if state[row][col] != self.goal[row][column]: - return False - return True - - def checkSolvability(self, state): + """ Given a state, return True if state is a goal state or False, otherwise """ + + return state == self.goal + + def check_solvability(self, state): + """ Checks if the given state is solvable """ + 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) + for j in range(i, len(state)): + if (state[i] > state[j] and state[j] != 0): + inversion += 1 + + return (inversion % 2 == 0) - def h(self, state): - """Return the heuristic value for a given state. Heuristic function used is - h(n) = number of misplaced tiles.""" - num_misplaced_tiles = 0 - for row in len(state): - for column in len(row): - if state[row][col] != self.goal[row][column]: - num_misplaced_tiles += 1 - return num_misplaced_tiles + def h(self, node): + """ Return the heuristic value for a given state. Default heuristic function used is + h(n) = number of misplaced tiles """ + + return sum(s != g for (s, g) in zip(node.state, self.goal)) # ______________________________________________________________________________ # Other search algorithms diff --git a/tests/test_search.py b/tests/test_search.py index 23f8b0f43..f35755315 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -5,6 +5,8 @@ romania_problem = GraphProblem('Arad', 'Bucharest', romania_map) vacumm_world = GraphProblemStochastic('State_1', ['State_7', 'State_8'], vacumm_world) LRTA_problem = OnlineSearchProblem('State_3', 'State_5', one_dim_state_space) +eight_puzzle = EightPuzzle((1, 2, 3, 4, 5, 7, 8, 6, 0)) +eight_puzzle2 = EightPuzzle((1, 0, 6, 8, 7, 5, 4, 2), (0, 1, 2, 3, 4, 5, 6, 7, 8)) def test_find_min_edge(): assert romania_problem.find_min_edge() == 70 @@ -64,6 +66,63 @@ def test_bidirectional_search(): def test_astar_search(): assert astar_search(romania_problem).solution() == ['Sibiu', 'Rimnicu', 'Pitesti', 'Bucharest'] + assert astar_search(eight_puzzle).solution() == ['LEFT', 'LEFT', 'UP', 'RIGHT', 'RIGHT', 'DOWN', 'LEFT', 'UP', 'LEFT', 'DOWN', 'RIGHT', 'RIGHT'] + assert astar_search(EightPuzzle((1, 2, 3, 4, 5, 6, 0, 7, 8))).solution() == ['RIGHT', 'RIGHT'] + + +def test_find_blank_square(): + assert eight_puzzle.find_blank_square((0, 1, 2, 3, 4, 5, 6, 7, 8)) == 0 + assert eight_puzzle.find_blank_square((6, 3, 5, 1, 8, 4, 2, 0, 7)) == 7 + assert eight_puzzle.find_blank_square((3, 4, 1, 7, 6, 0, 2, 8, 5)) == 5 + assert eight_puzzle.find_blank_square((1, 8, 4, 7, 2, 6, 3, 0, 5)) == 7 + assert eight_puzzle.find_blank_square((4, 8, 1, 6, 0, 2, 3, 5, 7)) == 4 + assert eight_puzzle.find_blank_square((1, 0, 6, 8, 7, 5, 4, 2, 3)) == 1 + assert eight_puzzle.find_blank_square((1, 2, 3, 4, 5, 6, 7, 8, 0)) == 8 + + +def test_actions(): + assert eight_puzzle.actions((0, 1, 2, 3, 4, 5, 6, 7, 8)) == ['DOWN', 'RIGHT'] + assert eight_puzzle.actions((6, 3, 5, 1, 8, 4, 2, 0, 7)) == ['UP', 'LEFT', 'RIGHT'] + assert eight_puzzle.actions((3, 4, 1, 7, 6, 0, 2, 8, 5)) == ['UP', 'DOWN', 'LEFT'] + assert eight_puzzle.actions((1, 8, 4, 7, 2, 6, 3, 0, 5)) == ['UP', 'LEFT', 'RIGHT'] + assert eight_puzzle.actions((4, 8, 1, 6, 0, 2, 3, 5, 7)) == ['UP', 'DOWN', 'LEFT', 'RIGHT'] + assert eight_puzzle.actions((1, 0, 6, 8, 7, 5, 4, 2, 3)) == ['DOWN', 'LEFT', 'RIGHT'] + assert eight_puzzle.actions((1, 2, 3, 4, 5, 6, 7, 8, 0)) == ['UP', 'LEFT'] + + +def test_result(): + assert eight_puzzle.result((0, 1, 2, 3, 4, 5, 6, 7, 8), 'DOWN') == (3, 1, 2, 0, 4, 5, 6, 7, 8) + assert eight_puzzle.result((6, 3, 5, 1, 8, 4, 2, 0, 7), 'LEFT') == (6, 3, 5, 1, 8, 4, 0, 2, 7) + assert eight_puzzle.result((3, 4, 1, 7, 6, 0, 2, 8, 5), 'UP') == (3, 4, 0, 7, 6, 1, 2, 8, 5) + assert eight_puzzle.result((1, 8, 4, 7, 2, 6, 3, 0, 5), 'RIGHT') == (1, 8, 4, 7, 2, 6, 3, 5, 0) + assert eight_puzzle.result((4, 8, 1, 6, 0, 2, 3, 5, 7), 'LEFT') == (4, 8, 1, 0, 6, 2, 3, 5, 7) + assert eight_puzzle.result((1, 0, 6, 8, 7, 5, 4, 2, 3), 'DOWN') == (1, 7, 6, 8, 0, 5, 4, 2, 3) + assert eight_puzzle.result((1, 2, 3, 4, 5, 6, 7, 8, 0), 'UP') == (1, 2, 3, 4, 5, 0, 7, 8, 6) + assert eight_puzzle.result((4, 8, 1, 6, 0, 2, 3, 5, 7), 'RIGHT') == (4, 8, 1, 6, 2, 0, 3, 5, 7) + + +def test_goal_test(): + assert eight_puzzle.goal_test((0, 1, 2, 3, 4, 5, 6, 7, 8)) == False + assert eight_puzzle.goal_test((6, 3, 5, 1, 8, 4, 2, 0, 7)) == False + assert eight_puzzle.goal_test((3, 4, 1, 7, 6, 0, 2, 8, 5)) == False + assert eight_puzzle.goal_test((1, 2, 3, 4, 5, 6, 7, 8, 0)) == True + assert eight_puzzle2.goal_test((4, 8, 1, 6, 0, 2, 3, 5, 7)) == False + assert eight_puzzle2.goal_test((3, 4, 1, 7, 6, 0, 2, 8, 5)) == False + assert eight_puzzle2.goal_test((1, 2, 3, 4, 5, 6, 7, 8, 0)) == False + assert eight_puzzle2.goal_test((0, 1, 2, 3, 4, 5, 6, 7, 8)) == True + + +def test_check_solvability(): + assert eight_puzzle.check_solvability((0, 1, 2, 3, 4, 5, 6, 7, 8)) == True + assert eight_puzzle.check_solvability((6, 3, 5, 1, 8, 4, 2, 0, 7)) == True + assert eight_puzzle.check_solvability((3, 4, 1, 7, 6, 0, 2, 8, 5)) == True + assert eight_puzzle.check_solvability((1, 8, 4, 7, 2, 6, 3, 0, 5)) == True + assert eight_puzzle.check_solvability((4, 8, 1, 6, 0, 2, 3, 5, 7)) == True + assert eight_puzzle.check_solvability((1, 0, 6, 8, 7, 5, 4, 2, 3)) == True + assert eight_puzzle.check_solvability((1, 2, 3, 4, 5, 6, 7, 8, 0)) == True + assert eight_puzzle.check_solvability((1, 2, 3, 4, 5, 6, 8, 7, 0)) == False + assert eight_puzzle.check_solvability((1, 0, 3, 2, 4, 5, 6, 7, 8)) == False + assert eight_puzzle.check_solvability((7, 0, 2, 8, 5, 3, 6, 4, 1)) == False def test_recursive_best_first_search(): From 651cf66bbb289a3dd1dbccf03e95e964af8aaaad Mon Sep 17 00:00:00 2001 From: Aman Deep Singh Date: Thu, 15 Mar 2018 13:11:28 +0530 Subject: [PATCH 081/269] Changed plotting function for NQueensCSP (#847) * Updated README.md * Added function to plot NQueensProblem * Added queen image * Changed plotting function for NQueensCSP * Replaced f'{}' with .format() notation * Added Pillow to travis.yml --- .travis.yml | 1 + README.md | 2 +- csp.ipynb | 61 +++++++++++---------------------------------- images/queen_s.png | Bin 0 -> 14407 bytes notebook.py | 30 +++++++++++++++++++++- 5 files changed, 45 insertions(+), 49 deletions(-) create mode 100644 images/queen_s.png diff --git a/.travis.yml b/.travis.yml index 600d6bd00..e374eff1f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ install: - pip install matplotlib - pip install networkx - pip install ipywidgets + - pip install Pillow script: - py.test diff --git a/README.md b/README.md index 3ab5777c1..4b8b4528f 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 6 | CSP | `CSP` | [`csp.py`][csp] | Done | Included | | 6.3 | AC-3 | `AC3` | [`csp.py`][csp] | Done | | | 6.5 | Backtracking-Search | `backtracking_search` | [`csp.py`][csp] | Done | Included | -| 6.8 | Min-Conflicts | `min_conflicts` | [`csp.py`][csp] | Done | | +| 6.8 | Min-Conflicts | `min_conflicts` | [`csp.py`][csp] | Done | Included | | 6.11 | Tree-CSP-Solver | `tree_csp_solver` | [`csp.py`][csp] | Done | Included | | 7 | KB | `KB` | [`logic.py`][logic] | Done | Included | | 7.1 | KB-Agent | `KB_Agent` | [`logic.py`][logic] | Done | | diff --git a/csp.ipynb b/csp.ipynb index be3882387..af85b81d6 100644 --- a/csp.ipynb +++ b/csp.ipynb @@ -18,7 +18,8 @@ "outputs": [], "source": [ "from csp import *\n", - "from notebook import psource, pseudocode\n", + "from notebook import psource, pseudocode, plot_NQueens\n", + "%matplotlib inline\n", "\n", "# Hide warnings in the matplotlib sections\n", "import warnings\n", @@ -159,9 +160,9 @@ { "data": { "text/plain": [ - "(,\n", - " ,\n", - " )" + "(,\n", + " ,\n", + " )" ] }, "execution_count": 3, @@ -684,47 +685,20 @@ "metadata": {}, "source": [ "This is indeed a valid solution. \n", - "Let's write a helper function to visualize the solution space." + "
    \n", + "`notebook.py` has a helper function to visualize the solution space." ] }, { "cell_type": "code", "execution_count": 9, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "%matplotlib inline\n", - "\n", - "def display_NQueensCSP(solution):\n", - " n = len(solution)\n", - " board = np.array([2 * int((i + j) % 2) for j in range(n) for i in range(n)]).reshape((n, n))\n", - " \n", - " for (k, v) in solution.items():\n", - " board[k][v] = 1\n", - " \n", - " fig = plt.figure(figsize=(7, 7))\n", - " ax = fig.add_subplot(111)\n", - " ax.set_title(f'{n} Queens')\n", - " plt.imshow(board, cmap='binary', interpolation='nearest')\n", - " ax.set_aspect('equal')\n", - " fig.tight_layout()\n", - " plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 10, "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAeAAAAHwCAYAAAB+ArwOAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4wLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvpW3flQAAFZFJREFUeJzt3HuspAd53/HfE6+52DFxG7bUFwpE\njSxR1AB7IEWuaIshsQMlVS+SaYNCVNVpGxLcRk1J/tmlSqU2f0SkokXZGAhJAItrRRGYECU0RW0M\nZ40pGEMFxhGLcbxu4hpwg7Hz9I8zbpdllzPbzOzjM+fzkY58Zuad9zzj18ff815mqrsDAJxb3zE9\nAADsRwIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AAAYZzoKqeWlXvr6o/qqq7q+p1VXXg2yx/\ncVW9frHsA1X1yar60XM5M7BeAgznxn9Ick+SS5I8M8lfS/JPT7dgVT0myW8leUqS5yX5riT/Iskv\nVNVPnZNpgbUTYDg3npbk7d39x919d5KbkvylMyz78iR/Icnf6+4vdPc3uvumJD+V5Oer6qIkqaqu\nqr/4yJOq6ler6udPuv2Sqrq1qu6rqv9aVX/5pMcurap3VdWJqvrCyWGvqiNV9faq+rWq+kpV3VZV\nWyc9/i+r6kuLxz5bVVet5l8R7C8CDOfGLyW5tqouqKrLklyTnQifzouSfKC7v3bK/e9KckGSv7Lb\nD6uqZyd5Y5IfT/LdSX45yXur6rFV9R1J/lOSTyS5LMlVSa6vqh88aRUvTXJjkouTvDfJ6xbrvSLJ\nK5M8p7svSvKDSe7cbR7gWwkwnBv/OTt7vPcnOZ5kO8l/PMOyT0zy5VPv7O6Hktyb5OASP+8fJfnl\n7r65ux/u7jcn+Xp24v2cJAe7+19194PdfUeSX0ly7UnP/0h3v7+7H07y60m+b3H/w0kem+TpVXV+\nd9/Z3Z9fYh7gFAIMa7bY4/xgkncnuTA7gf0zSf7tGZ5yb3bOFZ+6ngOL555Y4sc+JclPLw4/31dV\n9yV5cpJLF49despjP5fkSSc9/+6Tvn8gyeOq6kB3fy7J9UmOJLmnqm6sqkuXmAc4hQDD+v3Z7MTv\ndd399e7+n0nelOSHzrD8byW5pqouPOX+v5PkG0k+urj9QHYOST/iz5/0/ReT/Ovuvvikrwu6+22L\n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\nIMAAMECAAWCAAAPAAAEGgAECDAADBBgABggwAAwQYAAYIMAAMECAAWCAAAPAAAEGgAECDAADBBgA\nBggwAAwQYAAYIMAAMKC6e/UrrTqR5PdXvuIze2KSe8/hzzvXvL69zevb+zb9NXp9q/WU7j6420Jr\nCfC5VlXb3b01Pce6eH17m9e39236a/T6ZjgEDQADBBgABmxKgI9OD7BmXt/e5vXtfZv+Gr2+ARtx\nDhgA9ppN2QMGgD1FgAFgwJ4OcFVdXVWfrarPVdWrp+dZtap6Y1XdU1Wfmp5lHarqyVX1O1V1e1Xd\nVlWvmp5plarqcVX10ar6xOL1vWZ6pnWoqvOq6uNV9b7pWVatqu6sqk9W1a1VtT09z6pV1cVV9c6q\n+szi9/B50zOtUlVdsdh2j3zdX1XXT8/1iD17DriqzkvyP5K8KMnxJB9L8rLu/vToYCtUVc9P8tUk\nv9bdz5ieZ9Wq6pIkl3T3LVV1UZJjSf7WpmzDqqokF3b3V6vq/CQfSfKq7v694dFWqqr+eZKtJE/o\n7pdMz7NKVXVnkq3u3sgPqaiqNyf5L919Q1U9JskF3X3f9FzrsGjGl5J8f3efyw+KOqO9vAf83CSf\n6+47uvvBJDcm+eHhmVaqu383yR9Oz7Eu3f3l7r5l8f1Xktye5LLZqVand3x1cfP8xdfe/Iv3DKrq\n8iQvTnLD9Cycnap6QpLnJ3lDknT3g5sa34Wrknz+0RLfZG8H+LIkXzzp9vFs0P+895uqemqSZyW5\neXaS1Vocnr01yT1JPtTdG/X6krw2yc8k+ZPpQdakk/xmVR2rquumh1mx70lyIsmbFqcQbqiqC6eH\nWqNrk7xteoiT7eUA12nu26i9i/2iqr4zybuSXN/d90/Ps0rd/XB3PzPJ5UmeW1Ubcyqhql6S5J7u\nPjY9yxpd2d3PTnJNkp9YnBbaFAeSPDvJ67v7WUm+lmTjrqVJksXh9Zcmecf0LCfbywE+nuTJJ92+\nPMldQ7Pw/2lxbvRdSd7S3e+enmddFof2Ppzk6uFRVunKJC9dnCe9MckLquo3Zkdare6+a/HPe5K8\nJzunvjbF8STHTzoq887sBHkTXZPklu7+g+lBTraXA/yxJN9bVU9b/HVzbZL3Ds/EWVhcpPSGJLd3\n9y9Oz7NqVXWwqi5efP/4JC9M8pnZqVanu3+2uy/v7qdm5/fvt7v7R4bHWpmqunBxcWAWh2Z/IMnG\nvCOhu+9O8sWqumJx11VJNuICyNN4WR5lh5+TnUMQe1J3P1RVr0zywSTnJXljd982PNZKVdXbkvz1\nJE+squNJDnf3G2anWqkrk7w8yScX50mT5Oe6+/2DM63SJUnevLj68juSvL27N+6tOhvsSUnes/N3\nYg4keWt33zQ70sr9ZJK3LHZi7kjyY8PzrFxVXZCdd8v8+PQsp9qzb0MCgL1sLx+CBoA9S4ABYIAA\nA8AAAQaAAQIMAAMEGAAGCDAADPg/v2hxZuiP1asAAAAASUVORK5CYII=\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -732,14 +706,7 @@ } ], "source": [ - "display_NQueensCSP(solution)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The gray cells indicate the positions of the queens." + "plot_NQueens(solution)" ] }, { @@ -751,14 +718,14 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAeAAAAHwCAYAAAB+ArwOAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4wLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvpW3flQAAFaFJREFUeJzt3G2spAd53+H/Ha95sWPiNmwptikQ\nNbJEUQPsgRS5oi2GxA6UVH2RTBsUoqpO25DgNmpK8mWXKpXafIhIRYviGAhJAIvXilpgQpTQFLUx\nnDWmYAwVGEcsi+N1E9eAG4ydux/OuF2WXc5sM7O3z5zrko72zMwzz7nHj8a/87zMqe4OAHBufcf0\nAACwHwkwAAwQYAAYIMAAMECAAWCAAAPAAAEGgAECDAADBBjOgap6WlW9v6r+qKrurqrXV9WBb7P8\nxVX1hsWyD1TVJ6vqR8/lzMB6CTCcG/8hyT1JnpzkWUn+WpJ/eroFq+oxSX4ryVOTPD/JdyX5F0l+\noap+6pxMC6ydAMO58fQk7+juP+7uu5PcnOQvnWHZVyT5C0n+Xnd/obu/0d03J/mpJD9fVRclSVV1\nVf3FR55UVb9aVT9/0u2XVtVtVXVfVf3XqvrLJz12SVW9u6pOVNUXTg57VR2pqndU1a9V1Veq6vaq\n2jrp8X9ZVV9aPPbZqrpyNf+JYH8RYDg3finJNVV1QVVdmuTq7ET4dF6c5APd/bVT7n93kguS/JXd\nflhVPSfJm5L8eJLvTvLLSd5XVY+tqu9I8p+SfCLJpUmuTHJdVf3gSat4WZIbk1yc5H1JXr9Y7+VJ\nXpXkud19UZIfTHLXbvMA30qA4dz4z9nZ470/ybEk20n+4xmWfWKSL596Z3c/lOTeJAeX+Hn/KMkv\nd/ct3f1wd78lydezE+/nJjnY3f+qux/s7juT/EqSa056/ke6+/3d/XCSX0/yfYv7H07y2CTPqKrz\nu/uu7v78EvMApxBgWLPFHucHk7wnyYXZCeyfSfJvz/CUe7NzrvjU9RxYPPfEEj/2qUl+enH4+b6q\nui/JU5JcsnjsklMe+7kkTzrp+Xef9P0DSR5XVQe6+3NJrktyJMk9VXVjVV2yxDzAKQQY1u/PZid+\nr+/ur3f3/0zy5iQ/dIblfyvJ1VV14Sn3/50k30jy0cXtB7JzSPoRf/6k77+Y5F9398UnfV3Q3W9f\nPPaFUx67qLvPNM836e63dfdfzU7IO2f+RQL4NgQY1qy7703yhST/pKoOVNXFSX40O+dgT+fXs3OY\n+p2Ljy+dvzg/+++S/EJ3/6/Fcrcl+ftVdV5VXZWdK6sf8StJ/nFVfX/tuLCqXrK4gOujSe5fXEz1\n+MXzn1lVz93ttVTV5VX1wqp6bJI/TvK/s3NYGjhLAgznxt9OclV2Dh9/LslDSf7Z6Rbs7q8neVF2\n9lRvyU7kbk7yuiSvPWnRVyf5m0nuS/IPctI55e7ezs554Ncn+aPFz3zl4rGHF897VnZ+Mbg3yQ3Z\n+bjTbh6b5N8snnN3kj+XncPXwFmq7p6eAfg2qur8JB9I8qUkr2xvWtgI9oDhUa67v5Gd87+fT3L5\n8DjAitgDBoAB9oABYMAZ/xj8n0ZVbfRu9aFDh6ZHWKvjx49Pj7B2l1yy2R9dPXr06PQIa7Xp78FN\n337JZm/Du+66K/fee2/tttxaDkFveoA3/bD9kSNHpkdYu01/jVW7vvf3tE1/D2769ks2extubW1l\ne3t7143oEDQADBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAA\nA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAOWCnBVXVVVn62qz1XV\na9Y9FABsul0DXFXnJfn3Sa5O8owkL6+qZ6x7MADYZMvsAT8vyee6+87ufjDJjUl+eL1jAcBmWybA\nlyb54km3jy3u+yZVdW1VbVfV9qqGA4BNdWCJZeo09/W33NF9fZLrk6SqvuVxAOD/WWYP+FiSp5x0\n+7Ikx9czDgDsD8sE+GNJvreqnl5Vj0lyTZL3rXcsANhsux6C7u6HqupVST6Y5Lwkb+ru29c+GQBs\nsGXOAae735/k/WueBQD2DX8JCwAGCDAADBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAGCDAA\nDBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8CAA+tY6aFD\nh7K9vb2OVT8qVNX0CGvV3dMjrN2mb8PDhw9Pj7BWm779vAf3B3vAADBAgAFggAADwAABBoABAgwA\nAwQYAAYIMAAMEGAAGCDAADBAgAFggAADwAABBoABAgwAAwQYAAYIMAAMEGAAGCDAADBAgAFggAAD\nwAABBoABAgwAAwQYAAYIMAAMEGAAGLBrgKvqTVV1T1V96lwMBAD7wTJ7wL+a5Ko1zwEA+8quAe7u\n303yh+dgFgDYN5wDBoABKwtwVV1bVdtVtX3ixIlVrRYANtLKAtzd13f3VndvHTx4cFWrBYCN5BA0\nAAxY5mNIb0/y35JcXlXHquofrn8sANhsB3ZboLtffi4GAYD9xCFoABggwAAwQIABYIAAA8AAAQaA\nAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIAB\nYIAAA8AAAQaAAQIMAAMOrGOlR48eTVWtY9WPCocPH54eYa02eds9orunR1irTd+Gtt/et8nbcGtr\na6nl7AEDwAABBoABAgwAAwQYAAYIMAAMEGAAGCDAADBAgAFggAADwAABBoABAgwAAwQYAAYIMAAM\nEGAAGCDAADBAgAFggAADwAABBoABAgwAAwQYAAYIMAAMEGAAGCDAADBAgAFggAADwIBdA1xVT6mq\n36mqO6rq9qp69bkYDAA22YEllnkoyU93961VdVGSo1X1oe7+9JpnA4CNtesecHd/ubtvXXz/lSR3\nJLl03YMBwCZbZg/4/6qqpyV5dpJbTvPYtUmuXclUALDhlg5wVX1nkncnua677z/18e6+Psn1i2V7\nZRMCwAZa6iroqjo/O/F9a3e/Z70jAcDmW+Yq6EryxiR3dPcvrn8kANh8y+wBX5HkFUleWFW3Lb5+\naM1zAcBG2/UccHd/JEmdg1kAYN/wl7AAYIAAA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIAB\nYIAAA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAGCDAADDiw\njpUeOnQo29vb61j1o0JVTY+wVt09PcLa2YZ7m+239x05cmR6hLU5fvz4UsvZAwaAAQIMAAMEGAAG\nCDAADBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AAAQaA\nAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8CAXQNcVY+rqo9W1Seq6vaqeu25GAwANtmBJZb5\nepIXdvdXq+r8JB+pqg909++teTYA2Fi7Bri7O8lXFzfPX3z1OocCgE231Dngqjqvqm5Lck+SD3X3\nLadZ5tqq2q6q7RMnTqx6TgDYKEsFuLsf7u5nJbksyfOq6pmnWeb67t7q7q2DBw+uek4A2ChndRV0\nd9+X5MNJrlrLNACwTyxzFfTBqrp48f3jk7woyWfWPRgAbLJlroJ+cpK3VNV52Qn2O7r7pvWOBQCb\nbZmroP97kmefg1kAYN/wl7AAYIAAA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AA\nAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAGCDAADDiwjpUeP348\nR44cWceqHxW6e3qEtaqq6RHWzjbc22y/vW+Tt+FNN9201HL2gAFggAADwAABBoABAgwAAwQYAAYI\nMAAMEGAAGCDAADBAgAFggAADwAABBoABAgwAAwQYAAYIMAAMEGAAGCDAADBAgAFggAADwAABBoAB\nAgwAAwQYAAYIMAAMEGAAGCDAADBg6QBX1XlV9fGqummdAwHAfnA2e8CvTnLHugYBgP1kqQBX1WVJ\nXpLkhvWOAwD7w7J7wK9L8jNJ/uRMC1TVtVW1XVXbDzzwwEqGA4BNtWuAq+qlSe7p7qPfbrnuvr67\nt7p764ILLljZgACwiZbZA74iycuq6q4kNyZ5YVX9xlqnAoANt2uAu/tnu/uy7n5akmuS/HZ3/8ja\nJwOADeZzwAAw4MDZLNzdH07y4bVMAgD7iD1gABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAGCDAA\nDBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AAAQaAAQIM\nAAMOrGOll1xySY4cObKOVT8qVNX0CGvV3dMjrJ1tuLdt+vY7fPjw9Ahrt+nbcBn2gAFggAADwAAB\nBoABAgwAAwQYAAYIMAAMEGAAGCDAADBAgAFggAADwAABBoABAgwAAwQYAAYIMAAMEGAAGCDAADBA\ngAFggAADwAABBoABAgwAAwQYAAYIMAAMEGAAGCDAADDgwDILVdVdSb6S5OEkD3X31jqHAoBNt1SA\nF/5Gd9+7tkkAYB9xCBoABiwb4E7ym1V1tKquPd0CVXVtVW1X1faJEydWNyEAbKBlA3xFdz8nydVJ\nfqKqXnDqAt19fXdvdffWwYMHVzokAGyapQLc3ccX/96T5L1JnrfOoQBg0+0a4Kq6sKoueuT7JD+Q\n5FPrHgwANtkyV0E/Kcl7q+qR5d/W3TevdSoA2HC7Bri770zyfedgFgDYN3wMCQAGCDAADBBgABgg\nwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAG\nCDAADBBgABggwAAwQIABYIAAA8CAA+tY6dGjR1NV61j1o0J3T4+wVpu87R5x+PDh6RHWatO3offg\n3rfJ23Bra2up5ewBA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAME\nGAAGCDAADBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYMBSAa6q\ni6vqXVX1maq6o6qev+7BAGCTHVhyuV9KcnN3/92qekySC9Y4EwBsvF0DXFVPSPKCJK9Mku5+MMmD\n6x0LADbbMoegvyfJiSRvrqqPV9UNVXXhmucCgI22TIAPJHlOkjd097OTfC3Ja05dqKqurartqtpe\n8YwAsHGWCfCxJMe6+5bF7XdlJ8jfpLuv7+6t7t5a5YAAsIl2DXB3353ki1V1+eKuK5N8eq1TAcCG\nW/Yq6J9M8tbFFdB3Jvmx9Y0EAJtvqQB3921JHFoGgBXxl7AAYIAAA8AAAQaAAQIMAAMEGAAGCDAA\nDBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AAAQaAAQIM\nAAMEGAAGCDAADBBgABhwYB0rPXToULa3t9ex6keFqpoeYa26e3qEtbMN97YjR45Mj7BWm779ks1/\nDy7DHjAADBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AA\nAQaAAQIMAAMEGAAGCDAADBBgABggwAAwQIABYIAAA8AAAQaAAQIMAAMEGAAG7Brgqrq8qm476ev+\nqrruXAwHAJvqwG4LdPdnkzwrSarqvCRfSvLeNc8FABvtbA9BX5nk8939++sYBgD2i7MN8DVJ3n66\nB6rq2qrarqrtEydO/OknA4ANtnSAq+oxSV6W5J2ne7y7r+/ure7eOnjw4KrmA4CNdDZ7wFcnubW7\n/2BdwwDAfnE2AX55znD4GQA4O0sFuKouSPLiJO9Z7zgAsD/s+jGkJOnuB5J895pnAYB9w1/CAoAB\nAgwAAwQYAAYIMAAMEGAAGCDAADBAgAFggAADwAABBoABAgwAAwQYAAYIMAAMEGAAGCDAADBAgAFg\ngAADwAABBoABAgwAAwQYAAYIMAAMEGAAGCDAADCgunv1K606keT3V77iM3tiknvP4c8717y+vc3r\n2/s2/TV6fav11O4+uNtCawnwuVZV2929NT3Hunh9e5vXt/dt+mv0+mY4BA0AAwQYAAZsSoCvnx5g\nzby+vc3r2/s2/TV6fQM24hwwAOw1m7IHDAB7igADwIA9HeCquqqqPltVn6uq10zPs2pV9aaquqeq\nPjU9yzpU1VOq6neq6o6qur2qXj090ypV1eOq6qNV9YnF63vt9EzrUFXnVdXHq+qm6VlWraruqqpP\nVtVtVbU9Pc+qVdXFVfWuqvrM4n34/OmZVqmqLl9su0e+7q+q66bnesSePQdcVecl+R9JXpzkWJKP\nJXl5d396dLAVqqoXJPlqkl/r7mdOz7NqVfXkJE/u7lur6qIkR5P8rU3ZhlVVSS7s7q9W1flJPpLk\n1d39e8OjrVRV/fMkW0me0N0vnZ5nlarqriRb3b2Rf6Siqt6S5L909w1V9ZgkF3T3fdNzrcOiGV9K\n8v3dfS7/UNQZ7eU94Ocl+Vx339ndDya5MckPD8+0Ut39u0n+cHqOdenuL3f3rYvvv5LkjiSXzk61\nOr3jq4ub5y++9uZvvGdQVZcleUmSG6Zn4exU1ROSvCDJG5Okux/c1PguXJnk84+W+CZ7O8CXJvni\nSbePZYP+573fVNXTkjw7yS2zk6zW4vDsbUnuSfKh7t6o15fkdUl+JsmfTA+yJp3kN6vqaFVdOz3M\nin1PkhNJ3rw4hXBDVV04PdQaXZPk7dNDnGwvB7hOc99G7V3sF1X1nUneneS67r5/ep5V6u6Hu/tZ\nSS5L8ryq2phTCVX10iT3dPfR6VnW6Irufk6Sq5P8xOK00KY4kOQ5Sd7Q3c9O8rUkG3ctTZIsDq+/\nLMk7p2c52V4O8LEkTznp9mVJjg/Nwv+nxbnRdyd5a3e/Z3qedVkc2vtwkquGR1mlK5K8bHGe9MYk\nL6yq35gdabW6+/ji33uSvDc7p742xbEkx046KvOu7AR5E12d5Nbu/oPpQU62lwP8sSTfW1VPX/x2\nc02S9w3PxFlYXKT0xiR3dPcvTs+zalV1sKouXnz/+CQvSvKZ2alWp7t/trsv6+6nZef999vd/SPD\nY61MVV24uDgwi0OzP5BkYz6R0N13J/liVV2+uOvKJBtxAeRpvDyPssPPyc4hiD2pux+qqlcl+WCS\n85K8qbtvHx5rparq7Un+epInVtWxJIe7+42zU63UFUlekeSTi/OkSfJz3f3+wZlW6clJ3rK4+vI7\nkryjuzfuozob7ElJ3rvze2IOJHlbd988O9LK/WSSty52Yu5M8mPD86xcVV2QnU/L/Pj0LKfasx9D\nAoC9bC8fggaAPUuAAWCAAAPAAAEGgAECDAADBBgABggwAAz4PyWycpsM6xLVAAAAAElFTkSuQmCC\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -768,7 +735,7 @@ "source": [ "eight_queens = NQueensCSP(8)\n", "solution = min_conflicts(eight_queens)\n", - "display_NQueensCSP(solution)" + "plot_NQueens(solution)" ] }, { diff --git a/images/queen_s.png b/images/queen_s.png new file mode 100644 index 0000000000000000000000000000000000000000..cc693102aec1e78cf865bea5249941886d48cb89 GIT binary patch literal 14407 zcmV-NIJn1&P);M1&8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H1H^fOqK~#9!?Oh4Dm(%uMw?a};D)ZDNp-84sRGJVsQ<@M$SMy}J ziV#8*g(!ri%(F&AQfI1MxhPz-s4g;H)Bf-G_nq_aIGy1e_TJy#`?vP78ZnkJM-x<`S0rRPVnHtgTxZ5A`MbgX&g?MhGCdA z5Vy;JJ}dFI#19f835pr4q=3QuSD|cEB!)^^yGa~?Iq**M;NhEbdW&jFML$+zkTg(t z%YQDF_)&o|F=4RE!>>x2#VzG+noQJ@gEGk^14$^>C<&(pMhbbeK zOZDZ=phZ}}!DbczwfHzwRKA9)vqtc+ z(@?FZ9Wn*RdGr-4EaU%EBT$$vKVqyQJnS%3sOQV2A)J=Ls30w35Jwhyq-Us5G85GF zeZ~sO2#NKX8b&r;JPbS3;Y|OC6_61VAA&rQHHn8_LzPR1O1#Pn$54bNqJWySX7NyK zs8-OW&UHH}Boryp{uhD&_Qip9{*6oKFkGo|i z48tgZ>wz_pM>syM)D}{R@GV_l=oO+7?$7zaHO$j@+j%6 z+EE^inOvOAu(G&1pJs^VRFgH82Pj{TVy<+C1NaYK0IaduYN!HA;%nGf#c&9}7y4J3 z4I-3*avbSUgG#|N9?GrkeI)SfloRYN7{ zZyD|(jsh~WDT;{5x1dUF0bDXxD6h$<$M^sr)?AQ$4l_ zcKfTEWa_W@ure6HTKNHQ%$C8PkL{3`Qi3V#|?+u?mfqL<4W(!(|Uv%_cFlCyD*{A%X1)g9G*>F}yd4)sOJ{ zS%xWWVbnU)jZ|4U_K@J_#cOH}u4PN3##mj&hbeu${{)E>k4$c; zN}}V@B-TF3>uU)vXN!~U890CsQTlk`S`s^KuijV{Vf*a@Ul*^n6&T8vCw{pNXRxP; zKXOR)KU1HfDv5#JgWt((pL$08*=&KL7^|QI^DlgO(!=l4aGcZ8oUtlG&(3VLECA|q zPhd+F?imP*m`!|W(#6Hy%p0pBASHtLAa!hDYMP3TRh+C39nSFyj#;OQ&Z(@UC1@dVGTdE);*iLxTiVM-Lc{h{jatw(om7^)<-~L9T zbqf;rjUn;NvUF}&2Q{$IU^Q=S$1Mwc`_8=JBXsLv5@Y%WwUT2mAu(|%iRUJf_~2=; zjTUsYL$({MeSF|yheF|Yf?JddCEcT5;Q0RCV0eGwH2aKI5tiJY&c6OEKOB$VNaFOi z!L2xqth?7Gaqh`M+0pO*rDtr&+78p`n_jKdp;06`eNR@@Xb|Eo&IEf#ZdS$c<5FpFO6o&g<-@GSo34BsIl@$>?-64KF zcakcnPSmYs=2?h!xa&$1wG^+#-n#~8#V1OdY%^9Dy1r>sMv$mf$(+(&alSsKEqK^o zV^sv*lr-z->nZLoj$U0zY<|s~$YGysF;)#EzIS=ECJaqSX~89>V?Os@ZNI^qIa+V- z8T^JO=5b(s5}&@{h0)q9R#GG1{Z>^3T4+1GQE;v!qL-H9>R>e((Z_y+g@~Mv{mhxA z{s|c7iMZ}5FH#+H*2%~DnZ|KdUoZSwAByEv)2FB4 zOqVwVc{FJ~9m)b2K)+s~W`1X#XurYg-oei#Sv>JQY07brFSn}`TxSlo?RHg!(HE(i z-+;62H&}ztQI~6lNS#e{Lpp^QOr^Qh-gRSDO=6wvn{jYMJC%6PdTQ7?5qY%TU?F2% zRkE**^Lz6?FO1gvPG`Z(3EbxTCg8emvm&dgvwL;A{RT@_WVB5SJLh+H2QLg3aHFe* z%`JUkVYp?G-HLqqdCBL9h~swK+B-*(`TeYb!Q!K5(#@f(XpY~$;)TKbP2|uaE+&W; z`0&L*bjoIfb#ycT=h?NjnmOA3^!14QYn4+8rBc}8z7%@yx4YekyygyS<_Qu(Tjz-C z0u^&S?RdNQ@{-fNDv%!JhOt^r^`x-h`mpH#d(&)zb6k8iKlxli8faVU2C!S5U>{WM z=brWK-p9iZ@XBDJ^Hm)eZddpEvgoMbx7}C)V8T=}-8H#e|(s+ijkuiNXtDgw&B zJmavRW_yC@p}x=^1E9Qh!C;|)SH*ncd`DrfE%U;2{AWdU(WnD;VJ|&#ylAIJ54o|Io>0#eFOFVlr?JYXvwS1o-?Z3`L%x2# z+Ige%vM-tKMaX;5jlBfrwucQYPP5Bct)=axP#^i&EhKF^nTmj*fa+!kaB{a!F~Jh; z&wgF5c23B_a=~D!st>gc>#Rz1kk`J;d~I)p$c@bh926-!=KbF#Bo5s_9b=`VX#=Q< zZ8uiiRPlmtrYy3*ezqiuqWjIKswRdn3=-&2klX%rS|)E=*l(3QUnQ|)b-VWx&2@F2W!y4BHH|ZaeT6O+*Dd^h z6k-qT9`x7zW}(vG{|R1VHr%d;s36Bf|2K38OKA+ zOc-r^pj72sm4!~N?A}ZCKh!~uH0^Q{M;;Vi9_V7{R>1{AiQ7TG#L`X@-9ZZSRgwmz zv&IIuC>0xy#7A`HRq6jg{tmdOY&_uGZlfk?Gz`$$7++-7b3hseB9kWOa6|v>V5BJe z&>*i4Ez(rT)@bjb8X{qy*cYj?IY1dwF@TLJI z>g=TE<1&7r-X~y5;7r(Tuv9gscw;7sCj07<5o{L;Xdo;R}3h zd~6xli#1d|4d2zsI0D}`#B7NK{4Xj=9l zG!eCxJr)6^q<&x&Pn%4qiCs38OG8XQ1-Hp~{T-=QLsTSbjTTBf;4`wtclGbb?CK~4 zKBuzet-7uHT(P)7>5GadtxCW*m?YqUvwgv->BtVgxEL~jYBVoPTFBl}g+w$f&EnH* z6m*$^|Hg&`ex+rdR>IWjsVpB#is@I`Ww7APPBNdXFHBE%==8(a$GT;mWAcU-iJT9u zz^heFKO;P8Z5p1I$Wuvk9>`U)Ex9lZPdd_8Y>^lH2%nJ+#_EKY$!`CMu%=Ccgedx& zxshehGj+RaA4M=ISM$d9a4UWHYVu^trYSjY5&6OIw=Ii#>?IY;{?7~Sk{M6xv=d2@ ztoh0F$~mT;J%$#CRne=Z$w!eG4^MW_iw{c9KX`HT9(#%1Av{q$$`#|0WFR3yLZ z&dna=Jlzr+b^G_-mNu+ipr9O(1Qepzgt&3P~0$b8Vr|)WVltrC5 z)Xos{kRIx?iBWf@>%ik+59_6l#7Q>}i zRz6M8S>(JiiZBN?uyavy>r+{`_R4hi%~s*}Q^h2z6aRWN{G67}w09nOp25ed^W1?` ze);`s`1fnqv{ScmZX?cT18j>9g1(+n1X(rP?lOpHC#sJ;A$21uB@3$PpPry5+0_Id z85e$TcVB7uT=4Af(MeC}|J;++1x%j2)o$H-dv0Xsdv_fM>lL3Gkm5?M>)@|=fW*i? zB$_pf=F~-RK-YMVuId|pewUr)#+(-28B`LeN?TBItFqbIUJ?_AMmAV#X_UnZY8=Jc zZeuBnf~#xn(iIfl;wDm2;wyH_5g>gFyXNq_xHPw?ZVNXru#Q}M z28r882F)&Xo-6!pTS~;rqoRp1w{fbR?Nu@r))A9MD?8AD)MQ>0a<_bY79xZlHA3oA zSC}yQWf|g<`_0Mpp#$9X$T5pyw7(E@sj`aORfjmvOWPL}SUKC=K|?KFSPGU6i`-D? z+*#4r+!R@#N#e%&Q_accO1rX{0frM)7_7*hoKbGz!lIhwlZrwfXL~fQ2;jtuy5=Y# zcVTXLnunbeXtYhqg){4dZpAD)x>E&>Ok|QiS6aNAykVj6I;>pBALfQq5APj)&3kom zV{RuM8-7kRMw^q%Rx(La)2{4mxS*2M4R(p!^4x_*{m5X`O0cX(VV3yJ0bft&7)>3J z>S6o5;L?cV*aj&j`>_6s*wq=ibYhYl*8Oo8wq9+!7Zl-Y_$f>sw0p0PCZ9Jby z#jE91-6S<_zN|6*%*lpmbDF$zVE~5N1arJ6p0Qd(+sePZDef=U)nFp!&}(eK07a3n zWmEfaA#v9p(-HVD643mcpU*V`S zzW0Q^O1y9M9^u;N5FRJNC z92g>^%6RR?_9}LgW)4@JZ|}U~FpTJEfLb1Kc5T+J#AzP6G%=3?G1&MRO-c{iG|T1kymEUbwZ=W)>G z81ss7MmzgcFQVY6gOhc8wsB5q5E{p!eRvA~MpQ)Ds9OWhRyM~5QEpcQRUH7`O&#L^ z)%d0l7{0=BWCiPkZHf)*o9fk4$%NL5>n@Eqm&SXWmv!k3`}7LFzxUPoUHfa}Z7&bK zENk-)y5TgG;mCvH-$s)5RMc@3NxBcO-Qy%#GebA$fhB4-h=kMV0NlO;>(wn z&hH1YmbTMU?CeTZV?>J2t0eTh@CDkW+=yN#-3zo;w`op@Tqc>1O3H0lny6|oPs*pZ zVVFg)*RinOJEXgyhaHe^Gl#I1|D~aBExVeh#tk482RaVfG9Ns-_uD;{+BqdnKw33R zSGTm)hvw6mFgL8lI(|9o3)9Me2l;=RJWMb)N$2pQMYJp`GOcS=v&T30#?0jNfnP5z z`9Vjt|32yH7J3k9ya>z>L)N!h*TQaEoGct3*I1#;ud8J&Md)*qJ*0iA$Qex*pO*)O z;*-t={-iYL@WInT??;omb#ycB(caDFvNwnvI@|;?nyDNTa9`WNaH*og9)SzoQe7nN zD+|v|NPj+H#ze7h(zz_YEB(2AH9ubBYh2#;h)q-DMhg!u&wn-8l+OS}ZaO>HBE