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 001/200] 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 002/200] 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": "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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"
- }
- ],
+ "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 003/200] 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 004/200] 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 005/200] 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 006/200] 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 007/200] 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 @@
"