Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b009d1f

Browse files
kaivalyarnorvig
authored andcommitted
Planning implementations - 11.1 and 11.5 (aimacode#505)
* define HLA, Problem and implement 11.1 * add demonstration of job_shop_problem * implementing 11.5 * adding test for refinement
1 parent 5ea1fb6 commit b009d1f

File tree

2 files changed

+359
-1
lines changed

2 files changed

+359
-1
lines changed

planning.py

Lines changed: 311 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"""
33

44
import itertools
5-
from utils import Expr, expr, first
5+
from search import Node
6+
from utils import Expr, expr, first, FIFOQueue
67
from logic import FolKB
78

89

@@ -574,3 +575,312 @@ def goal_test(kb):
574575
go = Action(expr("Go(actor, to)"), [precond_pos, precond_neg], [effect_add, effect_rem])
575576

576577
return PDDL(init, [hit, go], goal_test)
578+
579+
580+
class HLA(Action):
581+
"""
582+
Define Actions for the real-world (that may be refined further), and satisfy resource
583+
constraints.
584+
"""
585+
unique_group = 1
586+
587+
def __init__(self, action, precond=[None, None], effect=[None, None], duration=0,
588+
consume={}, use={}):
589+
"""
590+
As opposed to actions, to define HLA, we have added constraints.
591+
duration holds the amount of time required to execute the task
592+
consumes holds a dictionary representing the resources the task consumes
593+
uses holds a dictionary representing the resources the task uses
594+
"""
595+
super().__init__(action, precond, effect)
596+
self.duration = duration
597+
self.consumes = consume
598+
self.uses = use
599+
self.completed = False
600+
# self.priority = -1 # must be assigned in relation to other HLAs
601+
# self.job_group = -1 # must be assigned in relation to other HLAs
602+
603+
def do_action(self, job_order, available_resources, kb, args):
604+
"""
605+
An HLA based version of act - along with knowledge base updation, it handles
606+
resource checks, and ensures the actions are executed in the correct order.
607+
"""
608+
# print(self.name)
609+
if not self.has_usable_resource(available_resources):
610+
raise Exception('Not enough usable resources to execute {}'.format(self.name))
611+
if not self.has_consumable_resource(available_resources):
612+
raise Exception('Not enough consumable resources to execute {}'.format(self.name))
613+
if not self.inorder(job_order):
614+
raise Exception("Can't execute {} - execute prerequisite actions first".
615+
format(self.name))
616+
super().act(kb, args) # update knowledge base
617+
for resource in self.consumes: # remove consumed resources
618+
available_resources[resource] -= self.consumes[resource]
619+
self.completed = True # set the task status to complete
620+
621+
def has_consumable_resource(self, available_resources):
622+
"""
623+
Ensure there are enough consumable resources for this action to execute.
624+
"""
625+
for resource in self.consumes:
626+
if available_resources.get(resource) is None:
627+
return False
628+
if available_resources[resource] < self.consumes[resource]:
629+
return False
630+
return True
631+
632+
def has_usable_resource(self, available_resources):
633+
"""
634+
Ensure there are enough usable resources for this action to execute.
635+
"""
636+
for resource in self.uses:
637+
if available_resources.get(resource) is None:
638+
return False
639+
if available_resources[resource] < self.uses[resource]:
640+
return False
641+
return True
642+
643+
def inorder(self, job_order):
644+
"""
645+
Ensure that all the jobs that had to be executed before the current one have been
646+
successfully executed.
647+
"""
648+
for jobs in job_order:
649+
if self in jobs:
650+
for job in jobs:
651+
if job is self:
652+
return True
653+
if not job.completed:
654+
return False
655+
return True
656+
657+
658+
class Problem(PDDL):
659+
"""
660+
Define real-world problems by aggregating resources as numerical quantities instead of
661+
named entities.
662+
663+
This class is identical to PDLL, except that it overloads the act function to handle
664+
resource and ordering conditions imposed by HLA as opposed to Action.
665+
"""
666+
def __init__(self, initial_state, actions, goal_test, jobs=None, resources={}):
667+
super().__init__(initial_state, actions, goal_test)
668+
self.jobs = jobs
669+
self.resources = resources
670+
671+
def act(self, action):
672+
"""
673+
Performs the HLA given as argument.
674+
675+
Note that this is different from the superclass action - where the parameter was an
676+
Expression. For real world problems, an Expr object isn't enough to capture all the
677+
detail required for executing the action - resources, preconditions, etc need to be
678+
checked for too.
679+
"""
680+
args = action.args
681+
list_action = first(a for a in self.actions if a.name == action.name)
682+
if list_action is None:
683+
raise Exception("Action '{}' not found".format(action.name))
684+
list_action.do_action(self.jobs, self.resources, self.kb, args)
685+
# print(self.resources)
686+
687+
def refinements(hla, state, library): # TODO - refinements may be (multiple) HLA themselves ...
688+
"""
689+
state is a Problem, containing the current state kb
690+
library is a dictionary containing details for every possible refinement. eg:
691+
{
692+
"HLA": [
693+
"Go(Home,SFO)",
694+
"Go(Home,SFO)",
695+
"Drive(Home, SFOLongTermParking)",
696+
"Shuttle(SFOLongTermParking, SFO)",
697+
"Taxi(Home, SFO)"
698+
],
699+
"steps": [
700+
["Drive(Home, SFOLongTermParking)", "Shuttle(SFOLongTermParking, SFO)"],
701+
["Taxi(Home, SFO)"],
702+
[], # empty refinements ie primitive action
703+
[],
704+
[]
705+
],
706+
"precond_pos": [
707+
["At(Home), Have(Car)"],
708+
["At(Home)"],
709+
["At(Home)", "Have(Car)"]
710+
["At(SFOLongTermParking)"]
711+
["At(Home)"]
712+
],
713+
"precond_neg": [[],[],[],[],[]],
714+
"effect_pos": [
715+
["At(SFO)"],
716+
["At(SFO)"],
717+
["At(SFOLongTermParking)"],
718+
["At(SFO)"],
719+
["At(SFO)"]
720+
],
721+
"effect_neg": [
722+
["At(Home)"],
723+
["At(Home)"],
724+
["At(Home)"],
725+
["At(SFOLongTermParking)"],
726+
["At(Home)"]
727+
]
728+
}
729+
"""
730+
e = Expr(hla.name, hla.args)
731+
indices = [i for i,x in enumerate(library["HLA"]) if expr(x).op == hla.name]
732+
for i in indices:
733+
action = HLA(expr(library["steps"][i][0]), [ # TODO multiple refinements
734+
[expr(x) for x in library["precond_pos"][i]],
735+
[expr(x) for x in library["precond_neg"][i]]
736+
],
737+
[
738+
[expr(x) for x in library["effect_pos"][i]],
739+
[expr(x) for x in library["effect_neg"][i]]
740+
])
741+
if action.check_precond(state.kb, action.args):
742+
yield action
743+
744+
def hierarchical_search(problem, hierarchy):
745+
"""
746+
[Figure 11.5] 'Hierarchical Search, a Breadth First Search implementation of Hierarchical
747+
Forward Planning Search'
748+
749+
The problem is a real-world prodlem defined by the problem class, and the hierarchy is
750+
a dictionary of HLA - refinements (see refinements generator for details)
751+
"""
752+
act = Node(problem.actions[0])
753+
frontier = FIFOQueue()
754+
frontier.append(act)
755+
while(True):
756+
if not frontier: #(len(frontier)==0):
757+
return None
758+
plan = frontier.pop()
759+
print(plan.state.name)
760+
hla = plan.state #first_or_null(plan)
761+
prefix = None
762+
if plan.parent:
763+
prefix = plan.parent.state.action #prefix, suffix = subseq(plan.state, hla)
764+
outcome = Problem.result(problem, prefix)
765+
if hla is None:
766+
if outcome.goal_test():
767+
return plan.path()
768+
else:
769+
print("else")
770+
for sequence in Problem.refinements(hla, outcome, hierarchy):
771+
print("...")
772+
frontier.append(Node(plan.state, plan.parent, sequence))
773+
774+
def result(problem, action):
775+
"""The outcome of applying an action to the current problem"""
776+
if action is not None:
777+
problem.act(action)
778+
return problem
779+
else:
780+
return problem
781+
782+
783+
def job_shop_problem():
784+
"""
785+
[figure 11.1] JOB-SHOP-PROBLEM
786+
787+
A job-shop scheduling problem for assembling two cars,
788+
with resource and ordering constraints.
789+
790+
Example:
791+
>>> from planning import *
792+
>>> p = job_shop_problem()
793+
>>> p.goal_test()
794+
False
795+
>>> p.act(p.jobs[1][0])
796+
>>> p.act(p.jobs[1][1])
797+
>>> p.act(p.jobs[1][2])
798+
>>> p.act(p.jobs[0][0])
799+
>>> p.act(p.jobs[0][1])
800+
>>> p.goal_test()
801+
False
802+
>>> p.act(p.jobs[0][2])
803+
>>> p.goal_test()
804+
True
805+
>>>
806+
"""
807+
init = [expr('Car(C1)'),
808+
expr('Car(C2)'),
809+
expr('Wheels(W1)'),
810+
expr('Wheels(W2)'),
811+
expr('Engine(E2)'),
812+
expr('Engine(E2)')]
813+
814+
def goal_test(kb):
815+
# print(kb.clauses)
816+
required = [expr('Has(C1, W1)'), expr('Has(C1, E1)'), expr('Inspected(C1)'),
817+
expr('Has(C2, W2)'), expr('Has(C2, E2)'), expr('Inspected(C2)')]
818+
for q in required:
819+
# print(q)
820+
# print(kb.ask(q))
821+
if kb.ask(q) is False:
822+
return False
823+
return True
824+
825+
resources = {'EngineHoists': 1, 'WheelStations': 2, 'Inspectors': 2, 'LugNuts': 500}
826+
827+
# AddEngine1
828+
precond_pos = []
829+
precond_neg = [expr("Has(C1,E1)")]
830+
effect_add = [expr("Has(C1,E1)")]
831+
effect_rem = []
832+
add_engine1 = HLA(expr("AddEngine1"),
833+
[precond_pos, precond_neg], [effect_add, effect_rem],
834+
duration=30, use={'EngineHoists': 1})
835+
836+
# AddEngine2
837+
precond_pos = []
838+
precond_neg = [expr("Has(C2,E2)")]
839+
effect_add = [expr("Has(C2,E2)")]
840+
effect_rem = []
841+
add_engine2 = HLA(expr("AddEngine2"),
842+
[precond_pos, precond_neg], [effect_add, effect_rem],
843+
duration=60, use={'EngineHoists': 1})
844+
845+
# AddWheels1
846+
precond_pos = []
847+
precond_neg = [expr("Has(C1,W1)")]
848+
effect_add = [expr("Has(C1,W1)")]
849+
effect_rem = []
850+
add_wheels1 = HLA(expr("AddWheels1"),
851+
[precond_pos, precond_neg], [effect_add, effect_rem],
852+
duration=30, consume={'LugNuts': 20}, use={'WheelStations': 1})
853+
854+
# AddWheels2
855+
precond_pos = []
856+
precond_neg = [expr("Has(C2,W2)")]
857+
effect_add = [expr("Has(C2,W2)")]
858+
effect_rem = []
859+
add_wheels2 = HLA(expr("AddWheels2"),
860+
[precond_pos, precond_neg], [effect_add, effect_rem],
861+
duration=15, consume={'LugNuts': 20}, use={'WheelStations': 1})
862+
863+
# Inspect1
864+
precond_pos = []
865+
precond_neg = [expr("Inspected(C1)")]
866+
effect_add = [expr("Inspected(C1)")]
867+
effect_rem = []
868+
inspect1 = HLA(expr("Inspect1"),
869+
[precond_pos, precond_neg], [effect_add, effect_rem],
870+
duration=10, use={'Inspectors': 1})
871+
872+
# Inspect2
873+
precond_pos = []
874+
precond_neg = [expr("Inspected(C2)")]
875+
effect_add = [expr("Inspected(C2)")]
876+
effect_rem = []
877+
inspect2 = HLA(expr("Inspect2"),
878+
[precond_pos, precond_neg], [effect_add, effect_rem],
879+
duration=10, use={'Inspectors': 1})
880+
881+
job_group1 = [add_engine1, add_wheels1, inspect1]
882+
job_group2 = [add_engine2, add_wheels2, inspect2]
883+
884+
return Problem(init, [add_engine1, add_engine2, add_wheels1, add_wheels2, inspect1, inspect2],
885+
goal_test, [job_group1, job_group2], resources)
886+

tests/test_planning.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,51 @@ def test_graph_call():
8181
graph()
8282

8383
assert levels_size == len(graph.levels) - 1
84+
85+
86+
def test_job_shop_problem():
87+
p = job_shop_problem()
88+
assert p.goal_test() is False
89+
90+
solution = [p.jobs[1][0],
91+
p.jobs[0][0],
92+
p.jobs[0][1],
93+
p.jobs[0][2],
94+
p.jobs[1][1],
95+
p.jobs[1][2]]
96+
97+
for action in solution:
98+
p.act(action)
99+
100+
assert p.goal_test()
101+
102+
def test_refinements() :
103+
init = [expr('At(Home)')]
104+
def goal_test(kb):
105+
return kb.ask(expr('At(SFO)'))
106+
107+
library = {"HLA": ["Go(Home,SFO)","Taxi(Home, SFO)"],
108+
"steps": [["Taxi(Home, SFO)"],[]],
109+
"precond_pos": [["At(Home)"],["At(Home)"]],
110+
"precond_neg": [[],[]],
111+
"effect_pos": [["At(SFO)"],["At(SFO)"]],
112+
"effect_neg": [["At(Home)"],["At(Home)"],]}
113+
# Go SFO
114+
precond_pos = [expr("At(Home)")]
115+
precond_neg = []
116+
effect_add = [expr("At(SFO)")]
117+
effect_rem = [expr("At(Home)")]
118+
go_SFO = HLA(expr("Go(Home,SFO)"),
119+
[precond_pos, precond_neg], [effect_add, effect_rem])
120+
# Taxi SFO
121+
precond_pos = [expr("At(Home)")]
122+
precond_neg = []
123+
effect_add = [expr("At(SFO)")]
124+
effect_rem = [expr("At(Home)")]
125+
taxi_SFO = HLA(expr("Go(Home,SFO)"),
126+
[precond_pos, precond_neg], [effect_add, effect_rem])
127+
prob = Problem(init, [go_SFO, taxi_SFO], goal_test)
128+
result = [i for i in Problem.refinements(go_SFO, prob, library)]
129+
assert(len(result) == 1)
130+
assert(result[0].name == "Taxi")
131+
assert(result[0].args == (expr("Home"), expr("SFO")))

0 commit comments

Comments
 (0)