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.

Open In GitHub Open In Colab

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!