13. Plan Parsing and Conversion
This python notebook shows how to use the unified planning library to parse a plan and how to convert plans from one type to another.
13.1. Setup
For this example there are no planners needed, so the library can be installed
[1]:
%pip install unified-planning
We are now ready to use the Unified-Planning library and to modify plans!
13.2. Demo
We start importing the shortcuts.
[2]:
from unified_planning.shortcuts import *
Now we create a problem that models a world where there are a number of locations to be fixed. To fix a location, both a worker and a supervisor are needed.
The workers and the supervisors can move between locations freely and independently.
The idea of the deordering is that the order in which the supervisors and the workers move in-between locations does not matter, they just have to arrive both in a location before it can be fixed.
13.2.1. Creating the fluents
First, we define the UserTypes
, and the Fluents
.
[3]:
Supervisor = UserType('Supervisor')
Worker = UserType('Worker')
Location = UserType('Location')
is_supervisor_at = Fluent('is_supervisor_at', s=Supervisor, pos=Location)
is_worker_at = Fluent('is_worker_at', w=Worker, pos=Location)
is_fixed = Fluent('is_fixed', l=Location)
13.2.2. Creating the actions
One to move a Supervisor, one to move a Worker and one to fix a Location.
[4]:
move_supervisor = InstantaneousAction('move_supervisor', supervisor=Supervisor, l_from=Location, l_to=Location)
supervisor = move_supervisor.supervisor
l_from = move_supervisor.l_from
l_to = move_supervisor.l_to
move_supervisor.add_precondition(Not(Equals(l_from, l_to)))
move_supervisor.add_precondition(is_supervisor_at(supervisor, l_from))
move_supervisor.add_effect(is_supervisor_at(supervisor, l_from), False)
move_supervisor.add_effect(is_supervisor_at(supervisor, l_to), True)
move_worker = InstantaneousAction('move_worker', worker=Worker, l_from=Location, l_to=Location)
worker = move_worker.worker
l_from = move_worker.l_from
l_to = move_worker.l_to
move_worker.add_precondition(Not(Equals(l_from, l_to)))
move_worker.add_precondition(is_worker_at(worker, l_from))
move_worker.add_effect(is_worker_at(worker, l_from), False)
move_worker.add_effect(is_worker_at(worker, l_to), True)
fix_location = InstantaneousAction('fix_location', loc=Location, supervisor=Supervisor, worker=Worker)
loc = fix_location.loc
supervisor = fix_location.supervisor
worker = fix_location.worker
fix_location.add_precondition(is_supervisor_at(supervisor, loc))
fix_location.add_precondition(is_worker_at(worker, loc))
fix_location.add_effect(is_fixed(loc), True)
Finally, we can create a Problem
that encompasses the fluents and the actions, and puts them together with concrete objects, an initial state and a goal.
The goal is that every location is fixed at the end.
Both the supervisor and the worker start at location ‘l0’.
[5]:
problem = Problem('problem')
problem.add_fluent(is_supervisor_at, default_initial_value=False)
problem.add_fluent(is_worker_at, default_initial_value=False)
problem.add_fluent(is_fixed, default_initial_value=False)
s0 = Object('s0', Supervisor)
w0 = Object('w0', Worker)
l0 = Object('l0', Location)
l1 = Object('l1', Location)
l2 = Object('l2', Location)
problem.add_objects((s0, w0, l0, l1, l2))
problem.add_action(move_supervisor)
problem.add_action(move_worker)
problem.add_action(fix_location)
problem.set_initial_value(is_supervisor_at(s0, l0), True)
problem.set_initial_value(is_worker_at(w0, l0), True)
var_loc = Variable('l', Location)
problem.add_goal(Forall(is_fixed(var_loc), var_loc))
Now we specify a valid plan in a format parsable by the PDDLReader; the accepted formats are in the PDDLReader.parse_plan (or parse_plan_string) methods
[6]:
from unified_planning.io import PDDLReader
plan_str = """(fix_location l0 s0 w0)
(move_supervisor s0 l0 l1)
(move_worker w0 l0 l1)
(fix_location l1 s0 w0)
(move_supervisor s0 l1 l2)
(move_worker w0 l1 l2)
(fix_location l2 s0 w0)
"""
reader = PDDLReader()
sequential_plan = reader.parse_plan_string(problem, plan_str)
print("Parsed plan: ", sequential_plan)
Parsed plan: SequentialPlan:
fix_location(l0, s0, w0)
move_supervisor(s0, l0, l1)
move_worker(w0, l0, l1)
fix_location(l1, s0, w0)
move_supervisor(s0, l1, l2)
move_worker(w0, l1, l2)
fix_location(l2, s0, w0)
13.2.3. Converting the SequentialPlan to a PartialOrderPlan
Now we use the Plan.convert_to
method, that allows conversion from a PlanKind
to another PlanKind
.
Few conversions are implemented at the moment, also because some conversions do not make sense at all!
The most interesting currently implemented is the conversion between Sequential
an PartialOrder
plans; it’s important to specify that the conversion is not the most general possible.
[7]:
from unified_planning.plans import PlanKind
partial_order_plan = sequential_plan.convert_to(PlanKind.PARTIAL_ORDER_PLAN, problem)
print(partial_order_plan)
PartialOrderPlan:
actions:
0) fix_location(l0, s0, w0)
1) move_supervisor(s0, l0, l1)
2) move_worker(w0, l0, l1)
3) fix_location(l1, s0, w0)
4) move_worker(w0, l1, l2)
5) move_supervisor(s0, l1, l2)
6) fix_location(l2, s0, w0)
constraints:
0 < 1 ,2
1 < 3
2 < 3
3 < 4 ,5
5 < 6
4 < 6
As we can see from the print above, location l0
must be fixed before the supervisor and the worker both move to location l1
, (constraint 0 < 1, 2
) but both the move actions must be performed before fixing the location l1
(constraints 1 < 3
and 2 < 3
). Same for l1
and l2
.
The following code shows a special feature of the PartialOrderPlan
, where we can get an Iterator over all the SequentialPlans
that follow the ordering enforced by the PartialOrderPlan
constraints.
[8]:
for i, sorted_plan in enumerate(partial_order_plan.all_sequential_plans()):
print(i)
print(sorted_plan)
0
SequentialPlan:
fix_location(l0, s0, w0)
move_worker(w0, l0, l1)
move_supervisor(s0, l0, l1)
fix_location(l1, s0, w0)
move_supervisor(s0, l1, l2)
move_worker(w0, l1, l2)
fix_location(l2, s0, w0)
1
SequentialPlan:
fix_location(l0, s0, w0)
move_worker(w0, l0, l1)
move_supervisor(s0, l0, l1)
fix_location(l1, s0, w0)
move_worker(w0, l1, l2)
move_supervisor(s0, l1, l2)
fix_location(l2, s0, w0)
2
SequentialPlan:
fix_location(l0, s0, w0)
move_supervisor(s0, l0, l1)
move_worker(w0, l0, l1)
fix_location(l1, s0, w0)
move_supervisor(s0, l1, l2)
move_worker(w0, l1, l2)
fix_location(l2, s0, w0)
3
SequentialPlan:
fix_location(l0, s0, w0)
move_supervisor(s0, l0, l1)
move_worker(w0, l0, l1)
fix_location(l1, s0, w0)
move_worker(w0, l1, l2)
move_supervisor(s0, l1, l2)
fix_location(l2, s0, w0)
In the 4 plans above the order of the move_supervisor and move_worker between the 3 locations are mixed up!