diff --git a/README.md b/README.md index 900ef3324..08d59b481 100644 --- a/README.md +++ b/README.md @@ -72,10 +72,10 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 3.2 | Romania | `romania` | [`search.py`][search] | Done | Included | | 3.7 | Tree-Search | `tree_search` | [`search.py`][search] | Done | | | 3.7 | Graph-Search | `graph_search` | [`search.py`][search] | Done | | -| 3.11 | Breadth-First-Search | `breadth_first_graph_search` | [`search.py`][search] | Done | Included | +| 3.11 | Breadth-First-Search | `breadth_first_graph_search` | [`search.py`][search] | Done | Included | | 3.14 | Uniform-Cost-Search | `uniform_cost_search` | [`search.py`][search] | Done | Included | -| 3.17 | Depth-Limited-Search | `depth_limited_search` | [`search.py`][search] | Done | Included | -| 3.18 | Iterative-Deepening-Search | `iterative_deepening_search` | [`search.py`][search] | Done | Included | +| 3.17 | Depth-Limited-Search | `depth_limited_search` | [`search.py`][search] | Done | Included | +| 3.18 | Iterative-Deepening-Search | `iterative_deepening_search` | [`search.py`][search] | Done | Included | | 3.22 | Best-First-Search | `best_first_graph_search` | [`search.py`][search] | Done | Included | | 3.24 | A\*-Search | `astar_search` | [`search.py`][search] | Done | Included | | 3.26 | Recursive-Best-First-Search | `recursive_best_first_search` | [`search.py`][search] | Done | | @@ -102,7 +102,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 7.17 | DPLL-Satisfiable? | `dpll_satisfiable` | [`logic.py`][logic] | Done | Included | | 7.18 | WalkSAT | `WalkSAT` | [`logic.py`][logic] | Done | Included | | 7.20 | Hybrid-Wumpus-Agent | `HybridWumpusAgent` | | | | -| 7.22 | SATPlan | `SAT_plan` | [`logic.py`][logic] | Done | Included | +| 7.22 | SATPlan | `SAT_plan` | [`logic.py`][logic] | Done | Included | | 9 | Subst | `subst` | [`logic.py`][logic] | Done | | | 9.1 | Unify | `unify` | [`logic.py`][logic] | Done | Included | | 9.3 | FOL-FC-Ask | `fol_fc_ask` | [`logic.py`][logic] | Done | Included | @@ -111,8 +111,8 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and | 10.1 | Air-Cargo-problem | `air_cargo` | [`planning.py`][planning] | Done | Included | | 10.2 | Spare-Tire-Problem | `spare_tire` | [`planning.py`][planning] | Done | Included | | 10.3 | Three-Block-Tower | `three_block_tower` | [`planning.py`][planning] | Done | Included | -| 10.7 | Cake-Problem | `have_cake_and_eat_cake_too` | [`planning.py`][planning] | Done | Included | -| 10.9 | Graphplan | `GraphPlan` | [`planning.py`][planning] | | | +| 10.7 | Cake-Problem | `have_cake_and_eat_cake_too` | [`planning.py`][planning] | Done | Included | +| 10.9 | Graphplan | `GraphPlan` | [`planning.py`][planning] | Done | Included | | 10.13 | Partial-Order-Planner | | | | | | 11.1 | Job-Shop-Problem-With-Resources | `job_shop_problem` | [`planning.py`][planning] | Done | | | 11.5 | Hierarchical-Search | `hierarchical_search` | [`planning.py`][planning] | | | diff --git a/images/cake_graph.jpg b/images/cake_graph.jpg new file mode 100644 index 000000000..160a413ca Binary files /dev/null and b/images/cake_graph.jpg differ diff --git a/planning.ipynb b/planning.ipynb index 6a79a3100..fd21a6e88 100644 --- a/planning.ipynb +++ b/planning.ipynb @@ -6,309 +6,1784 @@ "collapsed": true }, "source": [ - "# Planning: planning.py; chapters 10-11" + "# Planning\n", + "#### Chapters 10-11\n", + "----" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "This notebook describes the [planning.py](https://github.com/aimacode/aima-python/blob/master/planning.py) module, which covers Chapters 10 (Classical Planning) and 11 (Planning and Acting in the Real World) of *[Artificial Intelligence: A Modern Approach](http://aima.cs.berkeley.edu)*. See the [intro notebook](https://github.com/aimacode/aima-python/blob/master/intro.ipynb) for instructions.\n", + "This notebook serves as supporting material for topics covered in **Chapter 10 - Classical Planning** and **Chapter 11 - Planning and Acting in the Real World** from the book *[Artificial Intelligence: A Modern Approach](http://aima.cs.berkeley.edu)*. \n", + "This notebook uses implementations from the [planning.py](https://github.com/aimacode/aima-python/blob/master/planning.py) module. \n", + "See the [intro notebook](https://github.com/aimacode/aima-python/blob/master/intro.ipynb) for instructions.\n", "\n", - "We'll start by looking at `PDDL` and `Action` data types for defining problems and actions. Then, we will see how to use them by trying to plan a trip from *Sibiu* to *Bucharest* across the familiar map of Romania, from [search.ipynb](https://github.com/aimacode/aima-python/blob/master/search.ipynb). Finally, we will look at the implementation of the GraphPlan algorithm.\n", + "We'll start by looking at `PDDL` and `Action` data types for defining problems and actions. \n", + "Then, we will see how to use them by trying to plan a trip from *Sibiu* to *Bucharest* across the familiar map of Romania, from [search.ipynb](https://github.com/aimacode/aima-python/blob/master/search.ipynb) \n", + "followed by some common planning problems and methods of solving them.\n", "\n", - "The first step is to load the code:" + "Let's start by importing everything from the planning module." ] }, { "cell_type": "code", "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from planning import *\n", + "from notebook import psource" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## CONTENTS\n", + "\n", + "- PDDL\n", + "- Action\n", + "- Planning Problems\n", + " * Air cargo problem\n", + " * Spare tire problem\n", + " * Three block tower problem\n", + " * Shopping Problem\n", + " * Cake problem\n", + "- Solving Planning Problems\n", + " * GraphPlan" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## PDDL\n", + "\n", + "PDDL stands for Planning Domain Definition Language.\n", + "The `PDDL` class is used to represent planning problems in this module. The following attributes are essential to be able to define a problem:\n", + "* an initial state\n", + "* a set of goals\n", + "* a set of viable actions that can be executed in the search space of the problem\n", + "\n", + "View the source to see how the Python code tries to realise these." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "
\n", + "class PDDL:\n",
+ " """\n",
+ " Planning Domain Definition Language (PDDL) used to define a search problem.\n",
+ " It stores states in a knowledge base consisting of first order logic statements.\n",
+ " The conjunction of these logical statements completely defines a state.\n",
+ " """\n",
+ "\n",
+ " def __init__(self, init, goals, actions):\n",
+ " self.init = self.convert(init)\n",
+ " self.goals = expr(goals)\n",
+ " self.actions = actions\n",
+ "\n",
+ " def convert(self, init):\n",
+ " """Converts strings into exprs"""\n",
+ " try:\n",
+ " init = conjuncts(expr(init))\n",
+ " except AttributeError:\n",
+ " init = expr(init)\n",
+ " return init\n",
+ "\n",
+ " def goal_test(self):\n",
+ " """Checks if the goals have been reached"""\n",
+ " return all(goal in self.init for goal in conjuncts(self.goals))\n",
+ "\n",
+ " def act(self, action):\n",
+ " """\n",
+ " Performs the action given as argument.\n",
+ " Note that action is an Expr like expr('Remove(Glass, Table)') or expr('Eat(Sandwich)')\n",
+ " """ \n",
+ " action_name = action.op\n",
+ " args = action.args\n",
+ " list_action = first(a for a in self.actions if a.name == action_name)\n",
+ " if list_action is None:\n",
+ " raise Exception("Action '{}' not found".format(action_name))\n",
+ " if not list_action.check_precond(self.init, args):\n",
+ " raise Exception("Action '{}' pre-conditions not satisfied".format(action))\n",
+ " self.init = list_action(self.init, args).clauses\n",
+ "class Action:\n",
+ " """\n",
+ " Defines an action schema using preconditions and effects.\n",
+ " Use this to describe actions in PDDL.\n",
+ " action is an Expr where variables are given as arguments(args).\n",
+ " Precondition and effect are both lists with positive and negative literals.\n",
+ " Negative preconditions and effects are defined by adding a 'Not' before the name of the clause\n",
+ " Example:\n",
+ " precond = [expr("Human(person)"), expr("Hungry(Person)"), expr("NotEaten(food)")]\n",
+ " effect = [expr("Eaten(food)"), expr("Hungry(person)")]\n",
+ " eat = Action(expr("Eat(person, food)"), precond, effect)\n",
+ " """\n",
+ "\n",
+ " def __init__(self, action, precond, effect):\n",
+ " action = expr(action)\n",
+ " self.name = action.op\n",
+ " self.args = action.args\n",
+ " self.precond, self.effect = self.convert(precond, effect)\n",
+ "\n",
+ " def __call__(self, kb, args):\n",
+ " return self.act(kb, args)\n",
+ "\n",
+ " def convert(self, precond, effect):\n",
+ " """Converts strings into Exprs"""\n",
+ "\n",
+ " precond = precond.replace('~', 'Not')\n",
+ " if len(precond) > 0:\n",
+ " precond = expr(precond)\n",
+ " effect = effect.replace('~', 'Not')\n",
+ " if len(effect) > 0:\n",
+ " effect = expr(effect)\n",
+ "\n",
+ " try:\n",
+ " precond = conjuncts(precond)\n",
+ " except AttributeError:\n",
+ " pass\n",
+ " try:\n",
+ " effect = conjuncts(effect)\n",
+ " except AttributeError:\n",
+ " pass\n",
+ "\n",
+ " return precond, effect\n",
+ "\n",
+ " def substitute(self, e, args):\n",
+ " """Replaces variables in expression with their respective Propositional symbol"""\n",
+ "\n",
+ " new_args = list(e.args)\n",
+ " for num, x in enumerate(e.args):\n",
+ " for i, _ in enumerate(self.args):\n",
+ " if self.args[i] == x:\n",
+ " new_args[num] = args[i]\n",
+ " return Expr(e.op, *new_args)\n",
+ "\n",
+ " def check_precond(self, kb, args):\n",
+ " """Checks if the precondition is satisfied in the current state"""\n",
+ "\n",
+ " if isinstance(kb, list):\n",
+ " kb = FolKB(kb)\n",
+ "\n",
+ " for clause in self.precond:\n",
+ " if self.substitute(clause, args) not in kb.clauses:\n",
+ " return False\n",
+ " return True\n",
+ "\n",
+ " def act(self, kb, args):\n",
+ " """Executes the action on the state's knowledge base"""\n",
+ "\n",
+ " if isinstance(kb, list):\n",
+ " kb = FolKB(kb)\n",
+ "\n",
+ " if not self.check_precond(kb, args):\n",
+ " raise Exception('Action pre-conditions not satisfied')\n",
+ " for clause in self.effect:\n",
+ " kb.tell(self.substitute(clause, args))\n",
+ " if clause.op[:3] == 'Not':\n",
+ " new_clause = Expr(clause.op[3:], *clause.args)\n",
+ "\n",
+ " if kb.ask(self.substitute(new_clause, args)) is not False:\n",
+ " kb.retract(self.substitute(new_clause, args))\n",
+ " else:\n",
+ " new_clause = Expr('Not' + clause.op, *clause.args)\n",
+ "\n",
+ " if kb.ask(self.substitute(new_clause, args)) is not False: \n",
+ " kb.retract(self.substitute(new_clause, args))\n",
+ "\n",
+ " return kb\n",
+ "def air_cargo():\n",
+ " """Air cargo problem"""\n",
+ "\n",
+ " return PDDL(init='At(C1, SFO) & At(C2, JFK) & At(P1, SFO) & At(P2, JFK) & Cargo(C1) & Cargo(C2) & Plane(P1) & Plane(P2) & Airport(SFO) & Airport(JFK)',\n",
+ " goals='At(C1, JFK) & At(C2, SFO)', \n",
+ " actions=[Action('Load(c, p, a)', \n",
+ " precond='At(c, a) & At(p, a) & Cargo(c) & Plane(p) & Airport(a)', \n",
+ " effect='In(c, p) & ~At(c, a)'),\n",
+ " Action('Unload(c, p, a)',\n",
+ " precond='In(c, p) & At(p, a) & Cargo(c) & Plane(p) & Airport(a)',\n",
+ " effect='At(c, a) & ~In(c, p)'),\n",
+ " Action('Fly(p, f, to)',\n",
+ " precond='At(p, f) & Plane(p) & Airport(f) & Airport(to)',\n",
+ " effect='At(p, to) & ~At(p, f)')])\n",
+ "def spare_tire():\n",
+ " """Spare tire problem"""\n",
+ "\n",
+ " return PDDL(init='Tire(Flat) & Tire(Spare) & At(Flat, Axle) & At(Spare, Trunk)',\n",
+ " goals='At(Spare, Axle) & At(Flat, Ground)',\n",
+ " actions=[Action('Remove(obj, loc)',\n",
+ " precond='At(obj, loc)',\n",
+ " effect='At(obj, Ground) & ~At(obj, loc)'),\n",
+ " Action('PutOn(t, Axle)',\n",
+ " precond='Tire(t) & At(t, Ground) & ~At(Flat, Axle)',\n",
+ " effect='At(t, Axle) & ~At(t, Ground)'),\n",
+ " Action('LeaveOvernight',\n",
+ " precond='',\n",
+ " effect='~At(Spare, Ground) & ~At(Spare, Axle) & ~At(Spare, Trunk) & \\\n",
+ " ~At(Flat, Ground) & ~At(Flat, Axle) & ~At(Flat, Trunk)')])\n",
+ "def three_block_tower():\n",
+ " """Sussman Anomaly problem"""\n",
+ "\n",
+ " return PDDL(init='On(A, Table) & On(B, Table) & On(C, A) & Block(A) & Block(B) & Block(C) & Clear(B) & Clear(C)',\n",
+ " goals='On(A, B) & On(B, C)',\n",
+ " actions=[Action('Move(b, x, y)',\n",
+ " precond='On(b, x) & Clear(b) & Clear(y) & Block(b) & Block(y)',\n",
+ " effect='On(b, y) & Clear(x) & ~On(b, x) & ~Clear(y)'),\n",
+ " Action('MoveToTable(b, x)',\n",
+ " precond='On(b, x) & Clear(b) & Block(b)',\n",
+ " effect='On(b, Table) & Clear(x) & ~On(b, x)')])\n",
+ "def shopping_problem():\n",
+ " """Shopping problem"""\n",
+ "\n",
+ " return PDDL(init='At(Home) & Sells(SM, Milk) & Sells(SM, Banana) & Sells(HW, Drill)',\n",
+ " goals='Have(Milk) & Have(Banana) & Have(Drill)', \n",
+ " actions=[Action('Buy(x, store)',\n",
+ " precond='At(store) & Sells(store, x)',\n",
+ " effect='Have(x)'),\n",
+ " Action('Go(x, y)',\n",
+ " precond='At(x)',\n",
+ " effect='At(y) & ~At(x)')])\n",
+ "def air_cargo():\n",
- " init = [expr('At(C1, SFO)'),\n",
- " expr('At(C2, JFK)'),\n",
- " expr('At(P1, SFO)'),\n",
- " expr('At(P2, JFK)'),\n",
- " expr('Cargo(C1)'),\n",
- " expr('Cargo(C2)'),\n",
- " expr('Plane(P1)'),\n",
- " expr('Plane(P2)'),\n",
- " expr('Airport(JFK)'),\n",
- " expr('Airport(SFO)')]\n",
- "\n",
- " def goal_test(kb):\n",
- " required = [expr('At(C1 , JFK)'), expr('At(C2 ,SFO)')]\n",
- " return all([kb.ask(q) is not False for q in required])\n",
- "\n",
- " # Actions\n",
- "\n",
- " # Load\n",
- " precond_pos = [expr("At(c, a)"), expr("At(p, a)"), expr("Cargo(c)"), expr("Plane(p)"),\n",
- " expr("Airport(a)")]\n",
- " precond_neg = []\n",
- " effect_add = [expr("In(c, p)")]\n",
- " effect_rem = [expr("At(c, a)")]\n",
- " load = Action(expr("Load(c, p, a)"), [precond_pos, precond_neg], [effect_add, effect_rem])\n",
- "\n",
- " # Unload\n",
- " precond_pos = [expr("In(c, p)"), expr("At(p, a)"), expr("Cargo(c)"), expr("Plane(p)"),\n",
- " expr("Airport(a)")]\n",
- " precond_neg = []\n",
- " effect_add = [expr("At(c, a)")]\n",
- " effect_rem = [expr("In(c, p)")]\n",
- " unload = Action(expr("Unload(c, p, a)"), [precond_pos, precond_neg], [effect_add, effect_rem])\n",
- "\n",
- " # Fly\n",
- " # Used 'f' instead of 'from' because 'from' is a python keyword and expr uses eval() function\n",
- " precond_pos = [expr("At(p, f)"), expr("Plane(p)"), expr("Airport(f)"), expr("Airport(to)")]\n",
- " precond_neg = []\n",
- " effect_add = [expr("At(p, to)")]\n",
- " effect_rem = [expr("At(p, f)")]\n",
- " fly = Action(expr("Fly(p, f, to)"), [precond_pos, precond_neg], [effect_add, effect_rem])\n",
- "\n",
- " return PDDL(init, [load, unload, fly], goal_test)\n",
+ "def have_cake_and_eat_cake_too():\n",
+ " """Cake problem"""\n",
+ "\n",
+ " return PDDL(init='Have(Cake)',\n",
+ " goals='Have(Cake) & Eaten(Cake)',\n",
+ " actions=[Action('Eat(Cake)',\n",
+ " precond='Have(Cake)',\n",
+ " effect='Eaten(Cake) & ~Have(Cake)'),\n",
+ " Action('Bake(Cake)',\n",
+ " precond='~Have(Cake)',\n",
+ " effect='Have(Cake)')])\n",
"
\n",
"\n",
"