Modelling and Solving Planning Tasks

This tutorial proposes to explore the various features of the unified-planning library. The focus is deliberately kept on the most common planning problems (classical and numeric) to ease the presentation.

Let’s first install the unified planning library.

[ ]:
%pip install unified-planning[engines]
[1]:
from unified_planning.shortcuts import *

Modelling States

Fluents

  • Fluents define the language for specifying states

  • a Fluent is specified by a

    • name – a string

    • type – a fluent type

    • signature – list of parameters (name + type with finite domain)

  • Available types relevant for classical and numerical problems

    • BoolType()

    • UserType(name, father=None)

    • IntType(lower_bound=None, upper_bound=None)

    • RealType(lower_bound=None, upper_bound=None)

[2]:
location = UserType("Location")
person = UserType("Person")
conference_attendee = UserType("Attendee", father=person)
laptop = UserType("Laptop")
[3]:
arrived = Fluent("arrived", BoolType(), person=conference_attendee)
destination = Fluent("destination", location, person=conference_attendee)
distance = Fluent("distance", IntType(0, 100), l_from=location, l_to=location)
battery_level = Fluent("battery_level", RealType(0.0, 1.0), l=laptop)

Objects

Declare an `Object <https://unified-planning.readthedocs.io/en/latest/api/model/Object.html>`__ with Object(name, typename, environment=None).

[4]:
basel = Object("Basel", location)
prague = Object("Prague", location)
gabi = Object("Gabi", conference_attendee)

We can use objects to instantiate fluents:

[5]:
dest_gabi = destination(gabi)
arrived_gabi = arrived(gabi)
[6]:
print(arrived_gabi.__class__)
<class 'unified_planning.model.fnode.FNode'>
[7]:
arrived_gabi.fluent()
[7]:
bool arrived[person=Attendee - Person]
[8]:
arrived_gabi.args
[8]:
(Gabi,)

Creating a Problem

For classical, numeric and temporal planning, we use the class `Problem <https://unified-planning.readthedocs.io/en/latest/api/model/Problem.html>`__.

[9]:
problem = Problem("Travel to ICAPS")
problem.add_fluent(destination, default_initial_value=prague)
problem.add_objects([gabi, basel, prague])

We don’t have to create fluents separately, but can also do so while adding them:

[10]:
fluent_at = problem.add_fluent("at", location, p=person)
[11]:
# You can also get hold of a fluent from its name:
problem.fluent("at")
[11]:
Location at[p=Person]

Analogously for objects:

[12]:
problem.add_object("Mum", person)
problem.add_objects(Object(name, conference_attendee) for name in ("Andrea", "Arthur", "Sebastian"))
problem.add_objects(Object(name, location) for name in ("Trento", "Toulouse", "Bremen"))
[13]:
list(problem.objects(conference_attendee))
[13]:
[Gabi, Andrea, Arthur, Sebastian]
[14]:
p = problem.object("Prague")
print(p, p.__class__)
Prague <class 'unified_planning.model.object.Object'>

How does the problem look like so far?

[15]:
print(problem)
problem name = Travel to ICAPS

types = [Location, Person, Attendee - Person]

fluents = [
  Location destination[person=Attendee - Person]
  Location at[p=Person]
]

actions = [
]

objects = [
  Location: [Basel, Prague, Trento, Toulouse, Bremen]
  Person: [Gabi, Mum, Andrea, Arthur, Sebastian]
  Attendee - Person: [Gabi, Andrea, Arthur, Sebastian]
]

initial fluents default = [
  Location destination[person=Attendee - Person] := Prague
]

initial values = [
]

goals = [
]


Let’s set the (rest of the) initial state:

[16]:
initially_at = [("Andrea", "Trento"), ("Arthur", "Toulouse"), ("Sebastian", "Bremen"),
                ("Gabi", "Basel"), ("Mum", "Basel")]
for name, loc in initially_at:
    problem.set_initial_value(fluent_at(problem.object(name)), problem.object(loc))

[17]:
print(problem)
problem name = Travel to ICAPS

types = [Location, Person, Attendee - Person]

fluents = [
  Location destination[person=Attendee - Person]
  Location at[p=Person]
]

actions = [
]

objects = [
  Location: [Basel, Prague, Trento, Toulouse, Bremen]
  Person: [Gabi, Mum, Andrea, Arthur, Sebastian]
  Attendee - Person: [Gabi, Andrea, Arthur, Sebastian]
]

initial fluents default = [
  Location destination[person=Attendee - Person] := Prague
]

initial values = [
  at(Andrea) := Trento
  at(Arthur) := Toulouse
  at(Sebastian) := Bremen
  at(Gabi) := Basel
  at(Mum) := Basel
]

goals = [
]


Goal

We need a goal. All attendees should be at their destination.

[18]:
a = Variable("a", conference_attendee)
problem.add_goal(Forall(fluent_at(a).Equals(destination(a)), a))

print(problem.goals)
[Forall (Attendee - Person a) (at(a) == destination(a))]

Actions

Let’s first add a very simple action to let persons travel between locations…

[19]:
travel = InstantaneousAction('travel', p=person, l_from=location, l_to=location)
p = travel.p
l_from = travel.l_from
l_to = travel.l_to
travel.add_precondition(fluent_at(p).Equals(l_from))
travel.add_effect(fluent_at(p), l_to)
problem.add_action(travel)
print(problem.actions)
[action travel(Person p, Location l_from, Location l_to) {
    preconditions = [
      (at(p) == l_from)
    ]
    effects = [
      at(p) := l_to
    ]
  }]

Action Costs & Metrics

We also want to consider the travel times between the different locations and optimize the accumulated travel time of everyone getting to Prague.

To represent the action costs, we use an integer fluent and set random travel times (for the sake of simplicity).

[20]:
import itertools
from random import randint

travel_cost = Fluent("travel_cost", IntType(), from_loc=location, to_loc=location)
problem.add_fluent(travel_cost, default_initial_value=Int(0))

for loc1, loc2 in itertools.combinations(["Prague", "Trento", "Toulouse", "Bremen", "Basel"], 2):
    dist = randint(1,100)
    l1 = problem.object(loc1)
    l2 = problem.object(loc2)
    problem.set_initial_value(travel_cost(l1, l2), Int(dist))
    problem.set_initial_value(travel_cost(l2, l1), Int(dist))

Now we can specify the metric as follows:

[21]:
m = MinimizeActionCosts({travel : travel_cost(travel.l_from, travel.l_to)}, default=Int(1))
problem.clear_quality_metrics()
problem.add_quality_metric(m)

Problem Kind

[22]:
print(problem.kind)
PROBLEM_CLASS: ['ACTION_BASED']
CONDITIONS_KIND: ['UNIVERSAL_CONDITIONS', 'EQUALITIES']
TYPING: ['FLAT_TYPING', 'HIERARCHICAL_TYPING']
FLUENTS_TYPE: ['OBJECT_FLUENTS']
QUALITY_METRICS: ['ACTIONS_COST']
ACTIONS_COST_KIND: ['INT_NUMBERS_IN_ACTIONS_COST', 'STATIC_FLUENTS_IN_ACTIONS_COST']

It’s time to solve this incredibly hard problem…

but of course, object fluents are not widely supported by planning engines… And indeed:

[23]:
with OneshotPlanner(problem_kind=problem.kind) as planner:
  res = planner.solve(problem)
  print(res)
---------------------------------------------------------------------------
UPNoSuitableEngineAvailableException      Traceback (most recent call last)
/tmp/ipykernel_173045/3821768881.py in <module>
----> 1 with OneshotPlanner(problem_kind=problem.kind) as planner:
      2   res = planner.solve(problem)
      3   print(res)

~/work/aries/planning/unified/deps/unified-planning/unified_planning/shortcuts.py in OneshotPlanner(name, names, params, problem_kind, optimality_guarantee)
    548         | e.g. ``OneshotPlanner(problem_kind=problem.kind, optimality_guarantee=SOLVED_OPTIMALLY)``
    549     """
--> 550     return get_environment().factory.OneshotPlanner(
    551         name=name,
    552         names=names,

~/work/aries/planning/unified/deps/unified-planning/unified_planning/engines/factory.py in OneshotPlanner(self, name, names, params, problem_kind, optimality_guarantee)
    752                     f"{optimality_guarantee} is not a valid OptimalityGuarantee."
    753                 )
--> 754         return self._get_engine(
    755             OperationMode.ONESHOT_PLANNER,
    756             name,

~/work/aries/planning/unified/deps/unified-planning/unified_planning/engines/factory.py in _get_engine(self, operation_mode, name, names, params, problem_kind, optimality_guarantee, compilation_kind, compilation_kinds, plan_kind, anytime_guarantee, problem)
    657                 params = {}
    658             assert isinstance(params, Dict)
--> 659             EngineClass = self._get_engine_class(
    660                 operation_mode,
    661                 name,

~/work/aries/planning/unified/deps/unified-planning/unified_planning/engines/factory.py in _get_engine_class(self, operation_mode, name, problem_kind, optimality_guarantee, compilation_kind, plan_kind, anytime_guarantee)
    544         else:
    545             msg = f"No available {operation_mode} engine"
--> 546         raise up.exceptions.UPNoSuitableEngineAvailableException(msg)
    547
    548     def _print_credits(self, all_credits: Sequence[Optional["up.engines.Credits"]]):

UPNoSuitableEngineAvailableException: No available engine supports all the problem features:
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Engine                              | FLAT_TYPING | UNIVERSAL_CONDITIONS | INT_NUMBERS_IN_ACTIONS_COST | STATIC_FLUENTS_IN_ACTIONS_COST | OBJECT_FLUENTS | HIERARCHICAL_TYPING | ACTION_BASED | ACTIONS_COST | EQUALITIES |
=============================================================================================================================================================================================================================
| fast-downward                       | True        | True                 | True                        | True                           | False          | True                | True         | True         | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| fast-downward-opt                   | True        | True                 | True                        | True                           | False          | True                | True         | True         | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| symk                                | True        | True                 | True                        | True                           | False          | True                | True         | True         | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| symk-opt                            | True        | True                 | True                        | True                           | False          | True                | True         | True         | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| pyperplan                           | True        | False                | True                        | False                          | False          | True                | True         | False        | False      |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| pyperplan-opt                       | True        | False                | True                        | False                          | False          | True                | True         | False        | False      |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| enhsp                               | True        | True                 | True                        | True                           | False          | True                | True         | True         | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| enhsp-opt                           | True        | True                 | True                        | True                           | False          | True                | True         | True         | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| enhsp-any                           | True        | True                 | True                        | True                           | False          | True                | True         | True         | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| tamer                               | True        | False                | True                        | False                          | True           | False               | True         | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| lpg                                 | True        | False                | True                        | True                           | False          | True                | True         | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| lpg-anytime                         | True        | False                | True                        | True                           | False          | True                | True         | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| lpg-repairer                        | True        | False                | True                        | True                           | False          | True                | True         | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| fmap                                | True        | True                 | True                        | False                          | True           | True                | False        | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| aries                               | True        | False                | True                        | True                           | True           | True                | True         | True         | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| oversubscription[fast-downward]     | True        | True                 | True                        | False                          | False          | True                | True         | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| oversubscription[fast-downward-opt] | True        | True                 | True                        | False                          | False          | True                | True         | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| oversubscription[symk]              | True        | True                 | True                        | False                          | False          | True                | True         | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| oversubscription[symk-opt]          | True        | True                 | True                        | False                          | False          | True                | True         | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| oversubscription[enhsp]             | True        | True                 | True                        | False                          | False          | True                | True         | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| oversubscription[enhsp-opt]         | True        | True                 | True                        | False                          | False          | True                | True         | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| oversubscription[enhsp-any]         | True        | True                 | True                        | False                          | False          | True                | True         | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| oversubscription[tamer]             | True        | False                | True                        | False                          | True           | False               | True         | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| oversubscription[aries]             | True        | False                | False                       | False                          | True           | True                | True         | False        | True       |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Using a Compiler

For Fast Downward, the only unsupported feature were the object fluents.

Let’s use a compiler to eliminate them:

[24]:
with Compiler(
        problem_kind = problem.kind,
        compilation_kind = CompilationKind.USERTYPE_FLUENTS_REMOVING
    ) as usertype_remover:
    utr_result = usertype_remover.compile(
        problem,
        CompilationKind.USERTYPE_FLUENTS_REMOVING
    )
[25]:
utr_problem = utr_result.problem
print(utr_problem)
problem name = utfr_Travel to ICAPS

types = [Person, Attendee - Person, Location]

fluents = [
  bool destination[person=Attendee - Person, location=Location]
  bool at[p=Person, location=Location]
  integer travel_cost[from_loc=Location, to_loc=Location]
]

actions = [
  action travel(Person p, Location l_from, Location l_to) {
    preconditions = [
      at(p, l_from)
    ]
    effects = [
      if (l_to == Basel) then at(p, Basel) := true
      if (not (l_to == Basel)) then at(p, Basel) := false
      if (l_to == Prague) then at(p, Prague) := true
      if (not (l_to == Prague)) then at(p, Prague) := false
      if (l_to == Trento) then at(p, Trento) := true
      if (not (l_to == Trento)) then at(p, Trento) := false
      if (l_to == Toulouse) then at(p, Toulouse) := true
      if (not (l_to == Toulouse)) then at(p, Toulouse) := false
      if (l_to == Bremen) then at(p, Bremen) := true
      if (not (l_to == Bremen)) then at(p, Bremen) := false
    ]
  }
]

objects = [
  Person: [Gabi, Mum, Andrea, Arthur, Sebastian]
  Attendee - Person: [Gabi, Andrea, Arthur, Sebastian]
  Location: [Basel, Prague, Trento, Toulouse, Bremen]
]

initial fluents default = [
]

initial values = [
  at(Andrea, Basel) := false
  at(Andrea, Prague) := false
  at(Andrea, Trento) := true
  at(Andrea, Toulouse) := false
  at(Andrea, Bremen) := false
  at(Arthur, Basel) := false
  at(Arthur, Prague) := false
  at(Arthur, Trento) := false
  at(Arthur, Toulouse) := true
  at(Arthur, Bremen) := false
  at(Sebastian, Basel) := false
  at(Sebastian, Prague) := false
  at(Sebastian, Trento) := false
  at(Sebastian, Toulouse) := false
  at(Sebastian, Bremen) := true
  at(Gabi, Basel) := true
  at(Gabi, Prague) := false
  at(Gabi, Trento) := false
  at(Gabi, Toulouse) := false
  at(Gabi, Bremen) := false
  at(Mum, Basel) := true
  at(Mum, Prague) := false
  at(Mum, Trento) := false
  at(Mum, Toulouse) := false
  at(Mum, Bremen) := false
  travel_cost(Prague, Trento) := 46
  travel_cost(Trento, Prague) := 46
  travel_cost(Prague, Toulouse) := 76
  travel_cost(Toulouse, Prague) := 76
  travel_cost(Prague, Bremen) := 81
  travel_cost(Bremen, Prague) := 81
  travel_cost(Prague, Basel) := 24
  travel_cost(Basel, Prague) := 24
  travel_cost(Trento, Toulouse) := 67
  travel_cost(Toulouse, Trento) := 67
  travel_cost(Trento, Bremen) := 12
  travel_cost(Bremen, Trento) := 12
  travel_cost(Trento, Basel) := 32
  travel_cost(Basel, Trento) := 32
  travel_cost(Toulouse, Bremen) := 59
  travel_cost(Bremen, Toulouse) := 59
  travel_cost(Toulouse, Basel) := 10
  travel_cost(Basel, Toulouse) := 10
  travel_cost(Bremen, Basel) := 94
  travel_cost(Basel, Bremen) := 94
  destination(Gabi, Basel) := false
  destination(Gabi, Prague) := true
  destination(Gabi, Trento) := false
  destination(Gabi, Toulouse) := false
  destination(Gabi, Bremen) := false
  destination(Andrea, Basel) := false
  destination(Andrea, Prague) := true
  destination(Andrea, Trento) := false
  destination(Andrea, Toulouse) := false
  destination(Andrea, Bremen) := false
  destination(Arthur, Basel) := false
  destination(Arthur, Prague) := true
  destination(Arthur, Trento) := false
  destination(Arthur, Toulouse) := false
  destination(Arthur, Bremen) := false
  destination(Sebastian, Basel) := false
  destination(Sebastian, Prague) := true
  destination(Sebastian, Trento) := false
  destination(Sebastian, Toulouse) := false
  destination(Sebastian, Bremen) := false
  travel_cost(Basel, Basel) := 0
  travel_cost(Prague, Prague) := 0
  travel_cost(Trento, Trento) := 0
  travel_cost(Toulouse, Toulouse) := 0
  travel_cost(Bremen, Bremen) := 0
]

goals = [
  Forall (Attendee - Person a) Exists (Location destination_location) (at(a, destination_location) and destination(a, destination_location))
]

quality metrics = [
  minimize actions-cost: {'travel': travel_cost(l_from, l_to), 'default': None}
]


[26]:
utr_map_back = utr_result.map_back_action_instance
print(utr_map_back)
functools.partial(<function replace_action at 0x7f7c5f3965f0>, map={action travel(Person p, Location l_from, Location l_to) {
    preconditions = [
      at(p, l_from)
    ]
    effects = [
      if (l_to == Basel) then at(p, Basel) := true
      if (not (l_to == Basel)) then at(p, Basel) := false
      if (l_to == Prague) then at(p, Prague) := true
      if (not (l_to == Prague)) then at(p, Prague) := false
      if (l_to == Trento) then at(p, Trento) := true
      if (not (l_to == Trento)) then at(p, Trento) := false
      if (l_to == Toulouse) then at(p, Toulouse) := true
      if (not (l_to == Toulouse)) then at(p, Toulouse) := false
      if (l_to == Bremen) then at(p, Bremen) := true
      if (not (l_to == Bremen)) then at(p, Bremen) := false
    ]
  }: action travel(Person p, Location l_from, Location l_to) {
    preconditions = [
      (at(p) == l_from)
    ]
    effects = [
      at(p) := l_to
    ]
  }})

Let’s use this to find a plan for the original problem.

[27]:
with OneshotPlanner(problem_kind=utr_problem.kind) as planner:
  res = planner.solve(utr_problem)
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_173045/1887964203.py`, you are using the following planning engine:
  * Engine name: Fast Downward
  * Developers:  Uni Basel team and contributors (cf. https://github.com/aibasel/downward/blob/main/README.md)
  * Description: Fast Downward is a domain-independent classical planning system.


[28]:
print(res.status)
PlanGenerationResultStatus.SOLVED_SATISFICING
[30]:
utr_plan = res.plan
print(utr_plan)
SequentialPlan:
    travel(Sebastian, Bremen, Prague)
    travel(Gabi, Basel, Prague)
    travel(Arthur, Toulouse, Prague)
    travel(Andrea, Trento, Prague)

This is a plan for the compiled task. To transform it into a task for the original problem, we need to map the actions back (not immediately visible because the actions have the same names):

[31]:
plan = utr_plan.replace_action_instances(utr_map_back)
plan
[31]:
SequentialPlan([travel(Sebastian, Bremen, Prague), travel(Gabi, Basel, Prague), travel(Arthur, Toulouse, Prague), travel(Andrea, Trento, Prague)])
[32]:
act = plan.actions[-1]  # TRY: compare last action of utr_plan and plan
act.action
[32]:
action travel(Person p, Location l_from, Location l_to) {
    preconditions = [
      (at(p) == l_from)
    ]
    effects = [
      at(p) := l_to
    ]
  }

Currently supported compilation kinds:

  • GROUNDING

  • CONDITIONAL_EFFECTS_REMOVING

  • DISJUNCTIVE_CONDITIONS_REMOVING

  • NEGATIVE_CONDITIONS_REMOVING

  • QUANTIFIERS_REMOVING

  • TRAJECTORY_CONSTRAINTS_REMOVING

  • USERTYPE_FLUENTS_REMOVING

  • BOUNDED_TYPES_REMOVING

  • STATE_INVARIANTS_REMOVING

Solving a task optimally

Remember the result status of our PlanGenerationResult?

[33]:
print(res.status)
PlanGenerationResultStatus.SOLVED_SATISFICING

We would like to find an optimal plan (with guarantee!). This can be requested with:

[34]:
from unified_planning.engines import PlanGenerationResultStatus

with OneshotPlanner(problem_kind=utr_problem.kind,
                    optimality_guarantee=PlanGenerationResultStatus.SOLVED_OPTIMALLY) as planner:
  res = planner.solve(utr_problem)

  *** Credits ***
  * In operation mode `OneshotPlanner` at line 3 of `/tmp/ipykernel_173045/1418646784.py`, you are using the following planning engine:
  * Engine name: SymK
  * Developers:  David Speck (cf. https://github.com/speckdavid/symk/blob/master/README.md )
  * Description: SymK is a state-of-the-art domain-independent classical optimal and top-k planner.


[35]:
print(res.plan)
SequentialPlan:
    travel(Sebastian, Bremen, Trento)
    travel(Sebastian, Trento, Prague)
    travel(Andrea, Trento, Prague)
    travel(Gabi, Basel, Prague)
    travel(Arthur, Toulouse, Basel)
    travel(Arthur, Basel, Prague)

More on Actions

InstantaneousActions can have arbitrary formulas as preconditions as well as universal and conditional effects. Let’s consider another action that exploits these features.

Formulas are most easily build using shortcuts. Here we will use Equals.

[36]:
can_beam = Fluent("can_beam", BoolType(), p=person)


beam = InstantaneousAction('beam_persons_to_prague')
# reminder: a was a variable of type person
beam.add_precondition(Exists(Not(Equals(fluent_at(a), prague)), a))
beam.add_effect(fluent_at(a), prague, condition=can_beam(a), forall=[a])
print(beam)
action beam_persons_to_prague {
    preconditions = [
      Exists (Attendee - Person a) (not (at(a) == Prague))
    ]
    effects = [
      forall Attendee - Person a if can_beam(a) then at(a) := Prague
    ]
  }

Getting Help

We can use python’s built-in help functionality to get help on any object.

[37]:
help(beam.add_effect)   # also try beam.add_effect
Help on method add_effect in module unified_planning.model.action:

add_effect(fluent: Union[ForwardRef('up.model.fnode.FNode'), ForwardRef('up.model.fluent.Fluent')], value: 'up.model.expression.Expression', condition: 'up.model.expression.BoolExpression' = True, forall: Iterable[ForwardRef('up.model.variable.Variable')] = ()) method of unified_planning.model.action.InstantaneousAction instance
    Adds the given `assignment` to the `action's effects`.

    :param fluent: The `fluent` of which `value` is modified by the `assignment`.
    :param value: The `value` to assign to the given `fluent`.
    :param condition: The `condition` in which this `effect` is applied; the default
        value is `True`.
    :param forall: The 'Variables' that are universally quantified in this
        effect; the default value is empty.

Numeric Planning

  • No formal distinction of classical and numeric planning

  • It’s all in the problem kind: if you use numeric features, you will need a numeric planner that supports them.

[38]:
acceptable_changes = Fluent("acceptable_changes", IntType(), p=person)
total_travel_time = Fluent("travel_time", IntType())
problem.add_fluent(acceptable_changes, default_initial_value=2)
problem.add_fluent(total_travel_time, default_initial_value=0)
[38]:
integer travel_time

Extension of formulas (for preconditions and goals) * Numeric expressions can be combined by operands +, -, , / Numeric expressions can be compared with \(\le\) (LE), \(\ge\) (GE), \(<\) (LT), \(>\) (GT), \(=\) (Equals)

[39]:
travel.add_precondition(acceptable_changes(p) >= 1)
print(travel)
action travel(Person p, Location l_from, Location l_to) {
    preconditions = [
      (at(p) == l_from)
      (1 <= acceptable_changes(p))
    ]
    effects = [
      at(p) := l_to
    ]
  }

Besides the normal (assignment) effects, we can also have increase and decrease effects:

[40]:
travel.add_decrease_effect(acceptable_changes(travel.p), 1)
travel.add_increase_effect(total_travel_time(), travel_cost(travel.l_from, travel.l_to))
print(travel)
action travel(Person p, Location l_from, Location l_to) {
    preconditions = [
      (at(p) == l_from)
      (1 <= acceptable_changes(p))
    ]
    effects = [
      at(p) := l_to
      acceptable_changes(p) -= 1
      travel_time += travel_cost(l_from, l_to)
    ]
  }

Let’s also use a new optimization metric…

[41]:
problem.clear_quality_metrics()
m = MaximizeExpressionOnFinalState(acceptable_changes(problem.object("Andrea")) +
                                   acceptable_changes(problem.object("Sebastian")) +
                                   acceptable_changes(problem.object("Arthur")) +
                                   acceptable_changes(problem.object("Gabi")) -
                                   total_travel_time()/100)
problem.add_quality_metric(m)
m
[41]:
maximize ((((acceptable_changes(Andrea) + acceptable_changes(Sebastian)) + acceptable_changes(Arthur)) + acceptable_changes(Gabi)) - (travel_time / 100))
[42]:
with Compiler(
        problem_kind = problem.kind,
        compilation_kind = CompilationKind.USERTYPE_FLUENTS_REMOVING
    ) as usertype_remover:
    utr_result = usertype_remover.compile(
        problem,
        CompilationKind.USERTYPE_FLUENTS_REMOVING
    )
utr_problem = utr_result.problem
with OneshotPlanner(problem_kind=utr_problem.kind) as planner:
  res = planner.solve(utr_problem)
  print(res)
  *** Credits ***
  * In operation mode `OneshotPlanner` at line 10 of `/tmp/ipykernel_173045/277381419.py`, you are using the following planning engine:
  * Engine name: ENHSP
  * Developers:  Enrico Scala
  * Description: Expressive Numeric Heuristic Search Planner.

status: SOLVED_SATISFICING
engine: enhsp
plan: SequentialPlan:
    travel(Sebastian, Bremen, Prague)
    travel(Arthur, Toulouse, Prague)
    travel(Andrea, Trento, Prague)
    travel(Gabi, Basel, Prague)