diff --git a/notebook.py b/notebook.py index d60ced855..29501e9d2 100644 --- a/notebook.py +++ b/notebook.py @@ -1113,3 +1113,42 @@ def plot_pomdp_utility(utility): plt.text((right + left)/2 - 0.02, 10, 'Ask') plt.text((right + 1)/2 - 0.07, 10, 'Delete') plt.show() + + +# Function to Visualize Water Pouring Problem +def visual_pour(searcher, problem): + "Show what happens when searcvher solves problem." + problem = Instrumented(problem) + print('\n{}:'.format(searcher.__name__)) + result = searcher(problem) + if result: + node = result + "The sequence of actions to get to this node." + action = [] + while node.parent: + action.append(node.action) + node = node.parent + actions = action[::-1] + state = problem.initial + path_cost = 0 + for steps, action in enumerate(actions, 1): + path_cost += problem.step_cost(state, action, 0) + result = problem.result(state, action) + print(' {} =={}==> {}; cost {} after {} steps' + .format(state, action, result, path_cost, steps, + '; GOAL!' if problem.goal_test(result) else '')) + state = result + msg = 'GOAL FOUND' if result else 'no solution' + print('{} after {} results and {} goal checks' + .format(msg, problem._counter['result'], problem._counter['goal_test'])) + + +class Instrumented: + "Instrument an object to count all the attribute accesses in _counter." + def __init__(self, obj): + self._object = obj + self._counter = Counter() + + def __getattr__(self, attr): + self._counter[attr] += 1 + return getattr(self._object, attr) diff --git a/search.ipynb b/search.ipynb index aeb035902..182476fc5 100644 --- a/search.ipynb +++ b/search.ipynb @@ -20,7 +20,7 @@ "outputs": [], "source": [ "from search import *\n", - "from notebook import psource, heatmap, gaussian_kernel, show_map, final_path_colors, display_visual, plot_NQueens\n", + "from notebook import psource, heatmap, gaussian_kernel, show_map, final_path_colors, display_visual, plot_NQueens , visual_pour\n", "\n", "# Needed to hide warnings in the matplotlib sections\n", "import warnings\n", @@ -47,6 +47,7 @@ "* Hill Climbing\n", "* Simulated Annealing\n", "* Genetic Algorithm\n", + "* Water Pouring Problem\n", "* AND-OR Graph Search\n", "* Online DFS Agent\n", "* LRTA* Agent" @@ -1459,7 +1460,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -1544,7 +1545,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -4951,6 +4952,428 @@ "*NOTE: Because the algorithm is non-deterministic, there is a chance a different solution is given. It might even be wrong, if we are very unlucky!*" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Water Pouring Problem\n", + "\n", + "Here is another problem domain, to show you how to define one. The idea is that we have a number of water jugs and a water tap and the goal is to measure out a specific amount of water (in, say, ounces or liters). You can completely fill or empty a jug, but because the jugs don't have markings on them, you can't partially fill them with a specific amount. You can, however, pour one jug into another, stopping when the second is full or the first is empty." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
class PourProblem(Problem):\n",
+       "    """Problem about pouring water between jugs to achieve some water level.\n",
+       "    Each state is a tuples of levels. In the initialization, provide a tuple of \n",
+       "    capacities, e.g. PourProblem(capacities=(8, 16, 32), initial=(2, 4, 3), \n",
+       "    goals={7}), which means three jugs of capacity 8, 16, 32, currently filled \n",
+       "    with 2, 4, 3 units of water, respectively, and the goal is to get a level \n",
+       "    of 7 in any one of the jugs."""\n",
+       "    def __init__(self, initial = None, goals = (), capacities = None):\n",
+       "        self.initial = initial\n",
+       "        self.goals = goals\n",
+       "        self.capacities = capacities\n",
+       "\n",
+       "    def actions(self, state):\n",
+       "        """The actions executable in this state."""\n",
+       "        jugs = range(len(state))\n",
+       "        return ([('Fill', i)    for i in jugs if state[i] != self.capacities[i]] +\n",
+       "                [('Dump', i)    for i in jugs if state[i] != 0] +\n",
+       "                [('Pour', i, j) for i in jugs for j in jugs if i != j])\n",
+       "\n",
+       "    def result(self, state, action):\n",
+       "        """The state that results from executing this action in this state."""\n",
+       "        result = list(state)\n",
+       "        act, i, j = action[0], action[1], action[-1]\n",
+       "        if act == 'Fill': # Fill i to capacity\n",
+       "            result[i] = self.capacities[i]\n",
+       "        elif act == 'Dump': # Empty i\n",
+       "            result[i] = 0\n",
+       "        elif act == 'Pour':\n",
+       "            a, b = state[i], state[j]\n",
+       "            result[i], result[j] = ((0, a + b) \n",
+       "                                    if (a + b <= self.capacities[j]) else\n",
+       "                                    (a + b - self.capacities[j], self.capacities[j]))\n",
+       "        else:\n",
+       "            raise ValueError('unknown action', action)\n",
+       "        return tuple(result)\n",
+       "\n",
+       "    def goal_test(self, state):\n",
+       "        """True if any of the jugs has a level equal to one of the goal levels."""\n",
+       "        return any(level in self.goals for level in state)\n",
+       "    \n",
+       "    \n",
+       "    def step_cost(self, state, action, result=None):\n",
+       "        "The cost of taking this action from this state."\n",
+       "        return 1 # Override this if actions have different costs\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(PourProblem)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let, us solve a problem using this algorithm. We have a goal of getting 7 in any one of jug. Capacities of jugs will be (5,13) and initial state is (2,0)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(2, 13)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p7 = PourProblem(initial=(2, 0), capacities=(5, 13), goals={7})\n", + "p7.result((2, 0), ('Fill', 1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We need a searcher algorithm to find the action sequence of PourProblem. So, here we are using uniform_cost_search algorithm for that. Now we will see what steps did this algorithm took to complete this task " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('Pour', 0, 1), ('Fill', 0), ('Pour', 0, 1)]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = uniform_cost_search(p7)\n", + "def action_sequence(node):\n", + " \"The sequence of actions to get to this node.\"\n", + " actions = []\n", + " while node.parent:\n", + " actions.append(node.action)\n", + " node = node.parent\n", + " return actions[::-1]\n", + "action_sequence(result) \n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Visualization Output\n", + "Getting the visualized output of our solution using visual_pour fuction" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def visual_pour(searcher, problem):\n",
+       "    "Show what happens when searcvher solves problem."\n",
+       "    problem = Instrumented(problem)\n",
+       "    print('\\n{}:'.format(searcher.__name__))\n",
+       "    result = searcher(problem)\n",
+       "    if result:\n",
+       "        node = result\n",
+       "        "The sequence of actions to get to this node."\n",
+       "        action = []\n",
+       "        while node.parent:\n",
+       "              action.append(node.action)\n",
+       "              node = node.parent\n",
+       "        actions = action[::-1]\n",
+       "        state = problem.initial\n",
+       "        path_cost = 0\n",
+       "        for steps, action in enumerate(actions, 1):\n",
+       "            path_cost += problem.step_cost(state, action, 0)\n",
+       "            result = problem.result(state, action)\n",
+       "            print('  {} =={}==> {}; cost {} after {} steps'\n",
+       "                  .format(state, action, result, path_cost, steps,\n",
+       "                          '; GOAL!' if problem.goal_test(result) else ''))\n",
+       "            state = result\n",
+       "    msg = 'GOAL FOUND' if result else 'no solution'\n",
+       "    print('{} after {} results and {} goal checks'\n",
+       "          .format(msg, problem._counter['result'], problem._counter['goal_test']))\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(visual_pour)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "uniform_cost_search:\n", + " (2, 0) ==('Pour', 0, 1)==> (0, 2); cost 1 after 1 steps\n", + " (0, 2) ==('Fill', 0)==> (5, 2); cost 2 after 2 steps\n", + " (5, 2) ==('Pour', 0, 1)==> (0, 7); cost 3 after 3 steps\n", + "GOAL FOUND after 53 results and 15 goal checks\n" + ] + } + ], + "source": [ + "visual_pour(uniform_cost_search,p7)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "uniform_cost_search:\n", + " (0, 0) ==('Fill', 0)==> (7, 0); cost 1 after 1 steps\n", + " (7, 0) ==('Pour', 0, 1)==> (0, 7); cost 2 after 2 steps\n", + " (0, 7) ==('Fill', 0)==> (7, 7); cost 3 after 3 steps\n", + " (7, 7) ==('Pour', 0, 1)==> (1, 13); cost 4 after 4 steps\n", + " (1, 13) ==('Dump', 1)==> (1, 0); cost 5 after 5 steps\n", + " (1, 0) ==('Pour', 0, 1)==> (0, 1); cost 6 after 6 steps\n", + " (0, 1) ==('Fill', 0)==> (7, 1); cost 7 after 7 steps\n", + " (7, 1) ==('Pour', 0, 1)==> (0, 8); cost 8 after 8 steps\n", + " (0, 8) ==('Fill', 0)==> (7, 8); cost 9 after 9 steps\n", + " (7, 8) ==('Pour', 0, 1)==> (2, 13); cost 10 after 10 steps\n", + "GOAL FOUND after 110 results and 32 goal checks\n" + ] + } + ], + "source": [ + "p = PourProblem(initial=(0, 0), capacities=(7, 13), goals={2})\n", + "visual_pour(uniform_cost_search, p)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -6526,7 +6949,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.7.0" }, "widgets": { "state": { diff --git a/search.py b/search.py index 5b9eb2822..1ca52b6ca 100644 --- a/search.py +++ b/search.py @@ -984,6 +984,56 @@ def mutate(x, gene_pool, pmut): # _____________________________________________________________________________ # The remainder of this file implements examples for the search algorithms. +# _____________________________________________________________________________ +# Implementation of Water Pouring Problem + + +class PourProblem(Problem): + """Problem about pouring water between jugs to achieve some water level. + Each state is a tuples of levels. In the initialization, provide a tuple of + capacities, e.g. PourProblem(capacities=(8, 16, 32), initial=(2, 4, 3), + goals={7}), which means three jugs of capacity 8, 16, 32, currently filled + with 2, 4, 3 units of water, respectively, and the goal is to get a level + of 7 in any one of the jugs.""" + def __init__(self, initial=None, goals=(), capacities=None): + self.initial = initial + self.goals = goals + self.capacities = capacities + + def actions(self, state): + """The actions executable in this state.""" + jugs = range(len(state)) + return ([('Fill', i) for i in jugs if state[i] != self.capacities[i]] + + [('Dump', i) for i in jugs if state[i] != 0] + + [('Pour', i, j) for i in jugs for j in jugs if i != j]) + + def result(self, state, action): + """The state that results from executing this action in this state.""" + result = list(state) + act, i, j = action[0], action[1], action[-1] + if act == 'Fill': # Fill i to capacity + result[i] = self.capacities[i] + elif act == 'Dump': # Empty i + result[i] = 0 + elif act == 'Pour': + a, b = state[i], state[j] + result[i], result[j] = ((0, a + b) + if (a + b <= self.capacities[j]) else + (a + b - self.capacities[j], self.capacities[j])) + else: + raise ValueError('unknown action', action) + return tuple(result) + + def goal_test(self, state): + """True if any of the jugs has a level equal to + one of the goal levels.""" + return any(level in self.goals for level in state) + + def step_cost(self, state, action, result=None): + "The cost of taking this action from this state." + return 1 + + # ______________________________________________________________________________ # Graphs and Graph Problems