From ba8d6a352eb635f8561c276cd9a91e4ffddc2f0d Mon Sep 17 00:00:00 2001 From: Kaivalya Rawal Date: Sun, 16 Apr 2017 16:56:50 +0530 Subject: [PATCH 1/4] define HLA, Problem and implement 11.1 --- planning.py | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) diff --git a/planning.py b/planning.py index 30b8a79f6..e69c9280e 100644 --- a/planning.py +++ b/planning.py @@ -574,3 +574,227 @@ def goal_test(kb): go = Action(expr("Go(actor, to)"), [precond_pos, precond_neg], [effect_add, effect_rem]) return PDDL(init, [hit, go], goal_test) + + +class HLA(Action): + """ + Define Actions for the real-world (that may be refined further), and satisfy resource + constraints. + """ + unique_group = 1 + + def __init__(self, action, precond=[None, None], effect=[None, None], duration=0, + consume={}, use={}): + """ + 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 + """ + super().__init__(action, precond, effect) + self.duration = duration + self.consumes = consume + self.uses = use + 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 + + def do_action(self, job_order, available_resources, kb, args): + """ + An HLA based version of act - along with knowledge base updation, it handles + resource checks, and ensures the actions are executed in the correct order. + """ + # print(self.name) + if not self.has_usable_resource(available_resources): + raise Exception('Not enough usable resources to execute {}'.format(self.name)) + if not self.has_consumable_resource(available_resources): + raise Exception('Not enough consumable resources to execute {}'.format(self.name)) + if not self.inorder(job_order): + raise Exception("Can't execute {} - execute prerequisite actions first". + format(self.name)) + super().act(kb, args) # update knowledge base + for resource in self.consumes: # remove consumed resources + available_resources[resource] -= self.consumes[resource] + self.completed = True # set the task status to complete + + def has_consumable_resource(self, available_resources): + """ + Ensure there are enough consumable resources for this action to execute. + """ + for resource in self.consumes: + if available_resources.get(resource) is None: + return False + if available_resources[resource] < self.consumes[resource]: + return False + return True + + def has_usable_resource(self, available_resources): + """ + Ensure there are enough usable resources for this action to execute. + """ + for resource in self.uses: + if available_resources.get(resource) is None: + return False + if available_resources[resource] < self.uses[resource]: + return False + return True + + def inorder(self, job_order): + """ + Ensure that all the jobs that had to be executed before the current one have been + successfully executed. + """ + for jobs in job_order: + if self in jobs: + for job in jobs: + if job is self: + return True + if not job.completed: + return False + return True + + def refine(self, precond, library): # TODO + raise NotImplementedError + + +def refinements(hla, outcome, hierarchy): + return hla.refine(outcome, hierarchy) + +def result(problem, action): + return problem.act(action) + +class Problem(PDDL): + """ + Define real-world problems by aggregating resources as numerical quantities instead of + named entities. + + 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={}): + super().__init__(initial_state, actions, goal_test) + self.jobs = jobs + self.resources = resources + + def act(self, action): + """ + Performs the HLA given as argument. + + Note that this is different from the superclass action - where the parameter was an + Expression. For real world problems, an Expr object isn't enough to capture all the + detail required for executing the action - resources, preconditions, etc need to be + checked for too. + """ + args = action.args + list_action = first(a for a in self.actions if a.name == action.name) + if list_action is None: + raise Exception("Action '{}' not found".format(action.name)) + list_action.do_action(self.jobs, self.resources, self.kb, args) + # print(self.resources) + + def subseq(hla): + return (None,None) + +def job_shop_problem(): + """ + [figure 11.1] JOB-SHOP-PROBLEM + + A job-shop scheduling problem for assembling two cars, + with resource and ordering constraints. + + Example: + >>> from planning import * + >>> p = job_shop_problem() + >>> p.goal_test() + False + >>> p.act(p.jobs[1][0]) + >>> p.act(p.jobs[1][1]) + >>> p.act(p.jobs[1][2]) + >>> p.act(p.jobs[0][0]) + >>> p.act(p.jobs[0][1]) + >>> p.goal_test() + False + >>> p.act(p.jobs[0][2]) + >>> p.goal_test() + True + >>> + """ + init = [expr('Car(C1)'), + expr('Car(C2)'), + expr('Wheels(W1)'), + expr('Wheels(W2)'), + expr('Engine(E2)'), + expr('Engine(E2)')] + + def goal_test(kb): + # print(kb.clauses) + required = [expr('Has(C1, W1)'), expr('Has(C1, E1)'), expr('Inspected(C1)'), + expr('Has(C2, W2)'), expr('Has(C2, E2)'), expr('Inspected(C2)')] + for q in required: + # print(q) + # print(kb.ask(q)) + if kb.ask(q) is False: + return False + return True + + resources = {'EngineHoists': 1, 'WheelStations': 2, 'Inspectors': 2, 'LugNuts': 500} + + # AddEngine1 + precond_pos = [] + precond_neg = [expr("Has(C1,E1)")] + effect_add = [expr("Has(C1,E1)")] + effect_rem = [] + add_engine1 = HLA(expr("AddEngine1"), + [precond_pos, precond_neg], [effect_add, effect_rem], + duration=30, use={'EngineHoists': 1}) + + # AddEngine2 + precond_pos = [] + precond_neg = [expr("Has(C2,E2)")] + effect_add = [expr("Has(C2,E2)")] + effect_rem = [] + add_engine2 = HLA(expr("AddEngine2"), + [precond_pos, precond_neg], [effect_add, effect_rem], + duration=60, use={'EngineHoists': 1}) + + # AddWheels1 + precond_pos = [] + precond_neg = [expr("Has(C1,W1)")] + effect_add = [expr("Has(C1,W1)")] + effect_rem = [] + add_wheels1 = HLA(expr("AddWheels1"), + [precond_pos, precond_neg], [effect_add, effect_rem], + duration=30, consume={'LugNuts': 20}, use={'WheelStations': 1}) + + # AddWheels2 + precond_pos = [] + precond_neg = [expr("Has(C2,W2)")] + effect_add = [expr("Has(C2,W2)")] + effect_rem = [] + add_wheels2 = HLA(expr("AddWheels2"), + [precond_pos, precond_neg], [effect_add, effect_rem], + duration=15, consume={'LugNuts': 20}, use={'WheelStations': 1}) + + # Inspect1 + precond_pos = [] + precond_neg = [expr("Inspected(C1)")] + effect_add = [expr("Inspected(C1)")] + effect_rem = [] + inspect1 = HLA(expr("Inspect1"), + [precond_pos, precond_neg], [effect_add, effect_rem], + duration=10, use={'Inspectors': 1}) + + # Inspect2 + precond_pos = [] + precond_neg = [expr("Inspected(C2)")] + effect_add = [expr("Inspected(C2)")] + effect_rem = [] + inspect2 = HLA(expr("Inspect2"), + [precond_pos, precond_neg], [effect_add, effect_rem], + duration=10, use={'Inspectors': 1}) + + job_group1 = [add_engine1, add_wheels1, inspect1] + job_group2 = [add_engine2, add_wheels2, inspect2] + + return Problem(init, [add_engine1, add_engine2, add_wheels1, add_wheels2, inspect1, inspect2], + goal_test, [job_group1, job_group2], resources) From e17fd43875d2505403b88e6f4bdbec5e735063ec Mon Sep 17 00:00:00 2001 From: Kaivalya Rawal Date: Sun, 16 Apr 2017 16:57:15 +0530 Subject: [PATCH 2/4] add demonstration of job_shop_problem --- tests/test_planning.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_planning.py b/tests/test_planning.py index e13bcfd92..2388de336 100644 --- a/tests/test_planning.py +++ b/tests/test_planning.py @@ -81,3 +81,20 @@ def test_graph_call(): graph() assert levels_size == len(graph.levels) - 1 + + +def test_job_shop_problem(): + p = job_shop_problem() + assert p.goal_test() is False + + solution = [p.jobs[1][0], + p.jobs[0][0], + p.jobs[0][1], + p.jobs[0][2], + p.jobs[1][1], + p.jobs[1][2]] + + for action in solution: + p.act(action) + + assert p.goal_test() From 9f9936f77d14e13264a11466024626fa437b6df3 Mon Sep 17 00:00:00 2001 From: Kaivalya Rawal Date: Mon, 17 Apr 2017 02:36:25 +0530 Subject: [PATCH 3/4] implementing 11.5 --- planning.py | 105 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 11 deletions(-) diff --git a/planning.py b/planning.py index e69c9280e..e665cc06f 100644 --- a/planning.py +++ b/planning.py @@ -2,6 +2,7 @@ """ import itertools +from search import Node from utils import Expr, expr, first from logic import FolKB @@ -653,15 +654,56 @@ def inorder(self, job_order): return False return True - def refine(self, precond, library): # TODO - raise NotImplementedError - - -def refinements(hla, outcome, hierarchy): - return hla.refine(outcome, hierarchy) - -def result(problem, action): - return problem.act(action) + def refine(self, state, library): # TODO - refinements may be (multiple) HLA themselves ... + """ + state is a Problem, containing the current state kb + library is a dictionary containing details for every possible refinement. eg: + { + "HLA": [ + "Go(Home,SFO)", + "Go(Home,SFO)", + "Drive(Home, SFOLongTermParking)", + "Shuttle(SFOLongTermParking, SFO)", + "Taxi(Home, SFO)" + ], + "steps": [ + ["Drive(Home, SFOLongTermParking)", "Shuttle(SFOLongTermParking, SFO)"], + ["Taxi(Home, SFO)"], + [], # empty refinements ie primitive action + [], + [] + ], + "precond_pos": [ + ["At(Home), Have(Car)"], + ["At(Home)"], + ["At(Home)", "Have(Car)"] + ["At(SFOLongTermParking)"] + ["At(Home)"] + ], + "precond_neg": [[],[],[],[],[]], + "effect_pos": [ + ["At(SFO)"], + ["At(SFO)"], + ["At(SFOLongTermParking)"], + ["At(SFO)"], + ["At(SFO)"] + ], + "effect_neg": [ + ["At(Home)"], + ["At(Home)"], + ["At(Home)"], + ["At(SFOLongTermParking)"], + ["At(Home)"] + ] + } + """ + e = Expr(self.name, self.args) + indices = [i for i,x in enumerate(library["HLA"]) if expr(x) == e] + for i in indices: + action = HLA(expr(library["steps"][i]), [s["precond_pos"][i],s["precond_neg"][i]], + [s["effect_pos"][i],s["effect_neg"][i]]) + if action.check_precond(state.kb, action.args): + yield action class Problem(PDDL): """ @@ -692,8 +734,49 @@ def act(self, action): list_action.do_action(self.jobs, self.resources, self.kb, args) # print(self.resources) - def subseq(hla): - return (None,None) + def hierarchical_search(problem, hierarchy): + act = Node(problem.initial_state) + frontier = FIFOQueue() + frontier.append(act) + while(True): + if not frontier: #(len(frontier)==0): + return None + plan = frontier.pop() + hla = plan.state #first_or_null(plan) + prefix = plan.parent.state #prefix, suffix = subseq(plan.state, hla) + outcome = result(problem, prefix) + if hla is None: + if outcome.goal_test(): + return plan.path() + else: + for sequence in refinements(hla, outcome, hierarchy): + frontier.append(Node(plan.state, plan.parent, sequence)) + + def refinements(hla, outcome, hierarchy): + return hla.refine(outcome, hierarchy) + + def result(problem, action): + if action is not None: + problem.act(action) + return problem + else: + return problem + + def first_or_null(plan): + l = [x for x in plan.state if x is not None] + if len(l) > 0: + return l[0] + else: + return None + + def subseq(plan, hla): + index = plan.index(hla) + result = (plan[index-1], plan[index+1]) + if index == 0: + result = (None, result[1]) + if index == len(plan)-1: + result = (result[0], None) + return result def job_shop_problem(): """ From 1d3b0f690612625bd0bfad0d73c2b59543ebe7da Mon Sep 17 00:00:00 2001 From: Kaivalya Rawal Date: Mon, 17 Apr 2017 04:39:22 +0530 Subject: [PATCH 4/4] adding test for refinement --- planning.py | 117 +++++++++++++++++++++-------------------- tests/test_planning.py | 31 +++++++++++ 2 files changed, 91 insertions(+), 57 deletions(-) diff --git a/planning.py b/planning.py index e665cc06f..edfb39f19 100644 --- a/planning.py +++ b/planning.py @@ -3,7 +3,7 @@ import itertools from search import Node -from utils import Expr, expr, first +from utils import Expr, expr, first, FIFOQueue from logic import FolKB @@ -653,8 +653,38 @@ def inorder(self, job_order): if not job.completed: return False return True + + +class Problem(PDDL): + """ + Define real-world problems by aggregating resources as numerical quantities instead of + named entities. + + 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={}): + super().__init__(initial_state, actions, goal_test) + self.jobs = jobs + self.resources = resources + + def act(self, action): + """ + Performs the HLA given as argument. + + Note that this is different from the superclass action - where the parameter was an + Expression. For real world problems, an Expr object isn't enough to capture all the + detail required for executing the action - resources, preconditions, etc need to be + checked for too. + """ + args = action.args + list_action = first(a for a in self.actions if a.name == action.name) + if list_action is None: + raise Exception("Action '{}' not found".format(action.name)) + list_action.do_action(self.jobs, self.resources, self.kb, args) + # print(self.resources) - def refine(self, state, library): # TODO - refinements may be (multiple) HLA themselves ... + def refinements(hla, state, library): # TODO - refinements may be (multiple) HLA themselves ... """ state is a Problem, containing the current state kb library is a dictionary containing details for every possible refinement. eg: @@ -697,86 +727,58 @@ def refine(self, state, library): # TODO - refinements may be (multiple) HLA the ] } """ - e = Expr(self.name, self.args) - indices = [i for i,x in enumerate(library["HLA"]) if expr(x) == e] + e = Expr(hla.name, hla.args) + indices = [i for i,x in enumerate(library["HLA"]) if expr(x).op == hla.name] for i in indices: - action = HLA(expr(library["steps"][i]), [s["precond_pos"][i],s["precond_neg"][i]], - [s["effect_pos"][i],s["effect_neg"][i]]) + action = HLA(expr(library["steps"][i][0]), [ # TODO multiple refinements + [expr(x) for x in library["precond_pos"][i]], + [expr(x) for x in library["precond_neg"][i]] + ], + [ + [expr(x) for x in library["effect_pos"][i]], + [expr(x) for x in library["effect_neg"][i]] + ]) if action.check_precond(state.kb, action.args): yield action - -class Problem(PDDL): - """ - Define real-world problems by aggregating resources as numerical quantities instead of - named entities. - - 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={}): - super().__init__(initial_state, actions, goal_test) - self.jobs = jobs - self.resources = resources - - def act(self, action): + + def hierarchical_search(problem, hierarchy): """ - Performs the HLA given as argument. - - Note that this is different from the superclass action - where the parameter was an - Expression. For real world problems, an Expr object isn't enough to capture all the - detail required for executing the action - resources, preconditions, etc need to be - checked for too. + [Figure 11.5] 'Hierarchical Search, a Breadth First Search implementation of Hierarchical + Forward Planning Search' + + The problem is a real-world prodlem defined by the problem class, and the hierarchy is + a dictionary of HLA - refinements (see refinements generator for details) """ - args = action.args - list_action = first(a for a in self.actions if a.name == action.name) - if list_action is None: - raise Exception("Action '{}' not found".format(action.name)) - list_action.do_action(self.jobs, self.resources, self.kb, args) - # print(self.resources) - - def hierarchical_search(problem, hierarchy): - act = Node(problem.initial_state) + act = Node(problem.actions[0]) frontier = FIFOQueue() frontier.append(act) while(True): if not frontier: #(len(frontier)==0): return None plan = frontier.pop() + print(plan.state.name) hla = plan.state #first_or_null(plan) - prefix = plan.parent.state #prefix, suffix = subseq(plan.state, hla) - outcome = result(problem, prefix) + prefix = None + if plan.parent: + prefix = plan.parent.state.action #prefix, suffix = subseq(plan.state, hla) + outcome = Problem.result(problem, prefix) if hla is None: if outcome.goal_test(): return plan.path() else: - for sequence in refinements(hla, outcome, hierarchy): + print("else") + for sequence in Problem.refinements(hla, outcome, hierarchy): + print("...") frontier.append(Node(plan.state, plan.parent, sequence)) - def refinements(hla, outcome, hierarchy): - return hla.refine(outcome, hierarchy) - def result(problem, action): + """The outcome of applying an action to the current problem""" if action is not None: problem.act(action) return problem else: return problem - - def first_or_null(plan): - l = [x for x in plan.state if x is not None] - if len(l) > 0: - return l[0] - else: - return None - def subseq(plan, hla): - index = plan.index(hla) - result = (plan[index-1], plan[index+1]) - if index == 0: - result = (None, result[1]) - if index == len(plan)-1: - result = (result[0], None) - return result def job_shop_problem(): """ @@ -881,3 +883,4 @@ def goal_test(kb): return Problem(init, [add_engine1, add_engine2, add_wheels1, add_wheels2, inspect1, inspect2], goal_test, [job_group1, job_group2], resources) + diff --git a/tests/test_planning.py b/tests/test_planning.py index 2388de336..0e57ffca6 100644 --- a/tests/test_planning.py +++ b/tests/test_planning.py @@ -98,3 +98,34 @@ def test_job_shop_problem(): p.act(action) assert p.goal_test() + +def test_refinements() : + init = [expr('At(Home)')] + def goal_test(kb): + return kb.ask(expr('At(SFO)')) + + library = {"HLA": ["Go(Home,SFO)","Taxi(Home, SFO)"], + "steps": [["Taxi(Home, SFO)"],[]], + "precond_pos": [["At(Home)"],["At(Home)"]], + "precond_neg": [[],[]], + "effect_pos": [["At(SFO)"],["At(SFO)"]], + "effect_neg": [["At(Home)"],["At(Home)"],]} + # Go SFO + precond_pos = [expr("At(Home)")] + precond_neg = [] + effect_add = [expr("At(SFO)")] + effect_rem = [expr("At(Home)")] + go_SFO = HLA(expr("Go(Home,SFO)"), + [precond_pos, precond_neg], [effect_add, effect_rem]) + # Taxi SFO + precond_pos = [expr("At(Home)")] + precond_neg = [] + effect_add = [expr("At(SFO)")] + effect_rem = [expr("At(Home)")] + taxi_SFO = HLA(expr("Go(Home,SFO)"), + [precond_pos, precond_neg], [effect_add, effect_rem]) + prob = Problem(init, [go_SFO, taxi_SFO], goal_test) + result = [i for i in Problem.refinements(go_SFO, prob, library)] + assert(len(result) == 1) + assert(result[0].name == "Taxi") + assert(result[0].args == (expr("Home"), expr("SFO")))