4. Temporal Planning
This python notebook shows how to use the unified planning library to model temporal problems.
4.1. Setup
We install the unified planning library.
[1]:
!apt install graphviz graphviz-dev
%pip install unified-planning[tamer,plot]
We are now ready to use the Unified-Planning library!
4.2. Demo
In this demo we show how to model a temporal planning problem using the Unified Planning library.
Temporal planning extends classical planning introducing durative actions, timed effects and timed goals.
We start importing the shortcuts.
[2]:
from unified_planning.shortcuts import *
Now we start to model the MatchCellar problem.
4.2.1. Creating the problem
4.2.1.1. Classical part
First, we define the UserTypes
and the Fluents
.
[3]:
Match = UserType('Match')
Fuse = UserType('Fuse')
handfree = Fluent('handfree')
light = Fluent('light')
match_used = Fluent('match_used', BoolType(), m=Match)
fuse_mended = Fluent('fuse_mended', BoolType(), f=Fuse)
We create a Problem
, we add the fluents to it and we set their initial values.
[4]:
problem = Problem('MatchCellar')
problem.add_fluent(handfree)
problem.add_fluent(light)
problem.add_fluent(match_used, default_initial_value=False)
problem.add_fluent(fuse_mended, default_initial_value=False)
problem.set_initial_value(light, False)
problem.set_initial_value(handfree, True)
We create the objects and we add them to the problem.
[5]:
fuses = [Object(f'f{i}', Fuse) for i in range(3)]
matches = [Object(f'm{i}', Match) for i in range(3)]
problem.add_objects(fuses)
problem.add_objects(matches)
4.2.1.2. Temporal part
Now we start with the temporal aspects creating two durative actions.
A durative action has a duration, a set of conditions associated to an interval/timing and a set of effects associated to a timing.
We define the light_match
action setting a fixed duration and defining a condition at its start and three effects at its end.
[6]:
light_match = DurativeAction('light_match', m=Match)
m = light_match.parameter('m')
light_match.set_fixed_duration(6)
light_match.add_condition(StartTiming(), Not(match_used(m)))
light_match.add_effect(StartTiming(), match_used(m), True)
light_match.add_effect(StartTiming(), light, True)
light_match.add_effect(EndTiming(), light, False)
problem.add_action(light_match)
print(light_match)
durative action light_match(Match m) {
duration = [6, 6]
conditions = [
[start]:
(not match_used(m))
]
effects = [
start:
match_used(m) := true:
light := true:
end:
light := false:
]
simulated effects = [
]
}
Defining the mend_fuse
action we defined also a condition over an interval.
[7]:
mend_fuse = DurativeAction('mend_fuse', f=Fuse)
f = mend_fuse.parameter('f')
mend_fuse.set_fixed_duration(5)
mend_fuse.add_condition(StartTiming(), handfree)
mend_fuse.add_condition(ClosedTimeInterval(StartTiming(), EndTiming()), light)
mend_fuse.add_effect(StartTiming(), handfree, False)
mend_fuse.add_effect(EndTiming(), fuse_mended(f), True)
mend_fuse.add_effect(EndTiming(), handfree, True)
problem.add_action(mend_fuse)
print(mend_fuse)
durative action mend_fuse(Fuse f) {
duration = [5, 5]
conditions = [
[start]:
handfree
[start, end]:
light
]
effects = [
start:
handfree := false:
end:
fuse_mended(f) := true:
handfree := true:
]
simulated effects = [
]
}
We conclude the modeling defining three goals at the end of the execution.
[8]:
for f in fuses:
problem.add_timed_goal(EndTiming(), fuse_mended(f))
print(problem)
problem name = MatchCellar
types = [Match, Fuse]
fluents = [
bool handfree
bool light
bool match_used[m=Match]
bool fuse_mended[f=Fuse]
]
actions = [
durative action light_match(Match m) {
duration = [6, 6]
conditions = [
[start]:
(not match_used(m))
]
effects = [
start:
match_used(m) := true:
light := true:
end:
light := false:
]
simulated effects = [
]
}
durative action mend_fuse(Fuse f) {
duration = [5, 5]
conditions = [
[start]:
handfree
[start, end]:
light
]
effects = [
start:
handfree := false:
end:
fuse_mended(f) := true:
handfree := true:
]
simulated effects = [
]
}
]
objects = [
Match: [m0, m1, m2]
Fuse: [f0, f1, f2]
]
initial fluents default = [
bool match_used[m=Match] := false
bool fuse_mended[f=Fuse] := false
]
initial values = [
light := false
handfree := true
]
timed goals = [
[end] :
fuse_mended(f0)
fuse_mended(f1)
fuse_mended(f2)
]
goals = [
]
4.2.2. Solving the problem
The unified_planning can automatically select, among the available planners installed on the system, one that is able to handle the temporal features of the problem.
[9]:
with OneshotPlanner(problem_kind=problem.kind) as planner:
result = planner.solve(problem)
plan = result.plan
if plan is not None:
print(f"{planner.name} returned:")
for start, action, duration in plan.timed_actions:
print(f"{float(start)}: {action} [{float(duration)}]")
else:
print("No plan found.")
NOTE: To disable printing of planning engine credits, add this line to your code: `up.shortcuts.get_environment().credits_stream = None`
*** Credits ***
* In operation mode `OneshotPlanner` at line 1 of `/tmp/ipykernel_153164/3087807287.py`, you are using the following planning engine:
* Engine name: Tamer
* Developers: FBK Tamer Development Team
* Description: Tamer offers the capability to generate a plan for classical, numerical and temporal problems.
* For those kind of problems tamer also offers the possibility of validating a submitted plan.
Tamer returned:
0.0: light_match(m1) [6.0]
0.01: mend_fuse(f1) [5.0]
6.01: light_match(m2) [6.0]
6.02: mend_fuse(f2) [5.0]
12.02: light_match(m0) [6.0]
12.03: mend_fuse(f0) [5.0]
[ ]:
from unified_planning.plot import plot_plan
Ignore the code below, it’s used to make this notebook also runnable in the Countinuous Intergation.
[ ]:
# Redefine the plot package methods imported above to print the plot to a temp file
# if the exception "could not locate runnable browser" is raised. This usually happens
# in the Continuous Integration.
from inspect import getmembers, isfunction
from unified_planning import plot
from functools import partial
import os, uuid, tempfile as tf
# Define the function that will be executed instead
def _function(original_function, *args, **kwargs):
try:
original_function(*args, **kwargs)
except Exception as e:
if "could not locate runnable browser" in str(e):
original_function(*args, **kwargs,
filename=f"{os.path.join(tf.gettempdir(), str(uuid.uuid1()))}.png"
)
else:
raise e
# Iterate over all the functions of the plot package
for function_name, function in getmembers(plot, isfunction):
# Override the original function with the new one
globals()[function_name] = partial(_function, function)
[ ]:
if plan is not None:
plot_plan(plan)