|
1 | 1 | """Planning (Chapters 10-11)
|
2 | 2 | """
|
3 | 3 |
|
4 |
| -# flake8: noqa |
| 4 | +from utils import Expr, expr, first |
| 5 | +from logic import FolKB |
5 | 6 |
|
6 |
| -import agents |
| 7 | +class PDLL: |
| 8 | + """ |
| 9 | + PDLL used to deine a search problem |
| 10 | + It stores states in a knowledge base consisting of first order logic statements |
| 11 | + The conjunction of these logical statements completely define a state |
| 12 | + """ |
7 | 13 |
|
8 |
| -import math |
9 |
| -import random |
10 |
| -import sys |
11 |
| -import time |
12 |
| -import bisect |
13 |
| -import string |
| 14 | + def __init__(self, initial_state, actions, goal_test): |
| 15 | + self.kb = FolKB(initial_state) |
| 16 | + self.actions = actions |
| 17 | + self.goal_test_func = goal_test |
| 18 | + |
| 19 | + def goal_test(self): |
| 20 | + return self.goal_test_func(self.kb) |
| 21 | + |
| 22 | + def act(self, action): |
| 23 | + """ |
| 24 | + Performs the action given as argument |
| 25 | + Note that action is an Expr like expr('Remove(Glass, Table)') or expr('Eat(Sandwich)') |
| 26 | + """ |
| 27 | + action_name = action.op |
| 28 | + args = action.args |
| 29 | + list_action = first(a for a in self.actions if a.name == action_name) |
| 30 | + if list_action is None: |
| 31 | + raise Exception("Action '{}' not found".format(action_name)) |
| 32 | + if not list_action.check_precond(self.kb, args): |
| 33 | + raise Exception("Action '{}' pre-conditions not satisfied".format(action)) |
| 34 | + list_action(self.kb, args) |
| 35 | + |
| 36 | +class Action: |
| 37 | + """ |
| 38 | + Defines an action schema using preconditions and effects |
| 39 | + Use this to describe actions in PDDL |
| 40 | + action is an Expr where variables are given as arguments(args) |
| 41 | + Precondition and effect are both lists with positive and negated literals |
| 42 | + Example: |
| 43 | + precond_pos = [expr("Human(person)"), expr("Hungry(Person)")] |
| 44 | + precond_neg = [expr("Eaten(food)")] |
| 45 | + effect_add = [expr("Eaten(food)")] |
| 46 | + effect_rem = [expr("Hungry(person)")] |
| 47 | + eat = Action(expr("Eat(person, food)"), [precond_pos, precond_neg], [effect_add, effect_rem]) |
| 48 | + """ |
| 49 | + |
| 50 | + def __init__(self,action , precond, effect): |
| 51 | + self.name = action.op |
| 52 | + self.args = action.args |
| 53 | + self.precond_pos = precond[0] |
| 54 | + self.precond_neg = precond[1] |
| 55 | + self.effect_add = effect[0] |
| 56 | + self.effect_rem = effect[1] |
| 57 | + |
| 58 | + def __call__(self, kb, args): |
| 59 | + return self.act(kb, args) |
| 60 | + |
| 61 | + def substitute(self, e, args): |
| 62 | + """Replaces variables in expression with their respective Propostional symbol""" |
| 63 | + new_args = [args[i] for x in e.args for i in range(len(self.args)) if self.args[i]==x] |
| 64 | + return Expr(e.op, *new_args) |
| 65 | + |
| 66 | + def check_precond(self, kb, args): |
| 67 | + """Checks if the precondition is satisfied in the current state""" |
| 68 | + #check for positive clauses |
| 69 | + for clause in self.precond_pos: |
| 70 | + if self.substitute(clause, args) not in kb.clauses: |
| 71 | + return False |
| 72 | + #check for negative clauses |
| 73 | + for clause in self.precond_neg: |
| 74 | + if self.substitute(clause, args) in kb.clauses: |
| 75 | + return False |
| 76 | + return True |
| 77 | + |
| 78 | + def act(self, kb, args): |
| 79 | + """Executes the action on the state's kb""" |
| 80 | + #check if the preconditions are satisfied |
| 81 | + if not self.check_precond(kb, args): |
| 82 | + raise Exception("Action pre-conditions not satisfied") |
| 83 | + #remove negative literals |
| 84 | + for clause in self.effect_rem: |
| 85 | + kb.retract(self.substitute(clause, args)) |
| 86 | + #add positive literals |
| 87 | + for clause in self.effect_add: |
| 88 | + kb.tell(self.substitute(clause, args)) |
| 89 | + |
| 90 | + |
| 91 | +def air_cargo(): |
| 92 | + init = [expr('At(C1, SFO)'), |
| 93 | + expr('At(C2, JFK)'), |
| 94 | + expr('At(P1, SFO)'), |
| 95 | + expr('At(P2, JFK)'), |
| 96 | + expr('Cargo(C1)'), |
| 97 | + expr('Cargo(C2)'), |
| 98 | + expr('Plane(P1)'), |
| 99 | + expr('Plane(P2)'), |
| 100 | + expr('Airport(JFK)'), |
| 101 | + expr('Airport(SFO)')] |
| 102 | + |
| 103 | + def goal_test(kb): |
| 104 | + required = [expr('At(C1 , JFK)'), expr('At(C2 ,SFO)')] |
| 105 | + for q in required: |
| 106 | + if kb.ask(q) is False: |
| 107 | + return False |
| 108 | + return True |
| 109 | + |
| 110 | + ## Actions |
| 111 | + # Load |
| 112 | + precond_pos = [expr("At(c, a)"), expr("At(p, a)"), expr("Cargo(c)"), expr("Plane(p)"), expr("Airport(a)")] |
| 113 | + precond_neg = [] |
| 114 | + effect_add = [expr("In(c, p)")] |
| 115 | + effect_rem = [expr("At(c, a)")] |
| 116 | + load = Action(expr("Load(c, p, a)"), [precond_pos, precond_neg], [effect_add, effect_rem]) |
| 117 | + |
| 118 | + # Unload |
| 119 | + precond_pos = [expr("In(c, p)"), expr("At(p, a)"), expr("Cargo(c)"), expr("Plane(p)"), expr("Airport(a)")] |
| 120 | + precond_neg = [] |
| 121 | + effect_add = [expr("At(c, a)")] |
| 122 | + effect_rem = [expr("In(c, p)")] |
| 123 | + unload = Action(expr("Unload(c, p, a)"), [precond_pos, precond_neg], [effect_add, effect_rem]) |
| 124 | + |
| 125 | + # Load |
| 126 | + # Used used 'f' instead of 'from' because 'from' is a python keyword and expr uses eval() function |
| 127 | + precond_pos = [expr("At(p, f)"), expr("Plane(p)"), expr("Airport(f)"), expr("Airport(to)")] |
| 128 | + precond_neg = [] |
| 129 | + effect_add = [expr("At(p, to)")] |
| 130 | + effect_rem = [expr("At(p, f)")] |
| 131 | + fly = Action(expr("Fly(p, f, to)"), [precond_pos, precond_neg], [effect_add, effect_rem]) |
| 132 | + |
| 133 | + return PDLL(init, [load, unload, fly], goal_test) |
| 134 | + |
0 commit comments