4. Temporal Planning

This python notebook shows how to use the unified planning library to model temporal problems.

Open In GitHub Open In Colab

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)