3. Optimal Planning

This python notebook shows how to use the unified planning library to solve problems with a given optimality metric.

Open In GitHub Open In Colab

3.1. Setup

We install the unified planning library.

[11]:
%pip install unified-planning[fast-downward]

We are now ready to use the Unified-Planning library!

3.2. Problem Definition

In this demo we show how to model a problem with an optimality metric and how to solve it following the given metric.

We start importing the shortcuts.

[12]:
from unified_planning.shortcuts import *
from unified_planning.engines import PlanGenerationResultStatus

Now we start to model a basic problem with action costs.

In the following code we will create a simple problem that can be solved in 2 different ways depending on the given optimality metric.

[13]:
# basic with actions cost
x = Fluent("x")
y = Fluent("y")

a = InstantaneousAction("a")
a.add_precondition(Not(x))
a.add_effect(x, True)

b = InstantaneousAction("b")
b.add_precondition(Not(y))
b.add_effect(y, True)

c = InstantaneousAction("c")
c.add_precondition(y)
c.add_effect(x, True)

problem = Problem("simple_with_costs")

problem.add_fluent(x)
problem.add_fluent(y)

problem.add_action(a)
problem.add_action(b)
problem.add_action(c)

problem.set_initial_value(x, False)
problem.set_initial_value(y, False)

problem.add_goal(x)

3.3. Quality Metric

Now, we add to the problem the quality metric that we want the planner to use.

In this case, we will use the metric that minimizes the total action costs of the plan.

[14]:
problem.add_quality_metric(
    up.model.metrics.MinimizeActionCosts({a: 10, b: 1, c: 1})
)

3.4. Getting the optimal plan

As we can see, the action a would satisfy the goal in just one action, but the plan would have cost 10. Instead, if we do b then c, we also satisfy the goal, but the plan total cost would be 2.

[15]:
expected_plan = up.plans.SequentialPlan(
    [up.plans.ActionInstance(b), up.plans.ActionInstance(c)]
)

Then, we get a solver that guarantees that the problem can be solved optimally.

[16]:
with OneshotPlanner(
    problem_kind=problem.kind,
    optimality_guarantee=PlanGenerationResultStatus.SOLVED_OPTIMALLY,
) as planner:
    final_report = planner.solve(problem)
    plan = final_report.plan
  *** Credits ***
  * In operation mode `OneshotPlanner` at line 1 of `/tmp/ipykernel_152691/2144990037.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.


Here, we assume that the plan is the one we expected, and not the shortest one, and that it is SOLVED_OPTIMALLY.

[17]:
assert final_report.status == PlanGenerationResultStatus.SOLVED_OPTIMALLY
assert plan == expected_plan

3.5. Change quality metric and get different optimal plan

We change the problem quality metrics by removing all the current metrics and add a new one; we will ask for the shortest possible plan.

[18]:
problem.clear_quality_metrics()
problem.add_quality_metric(up.model.metrics.MinimizeSequentialPlanLength())

We get a planner from the factory that is capable of handling the new type of optimality requirement.

[19]:
with OneshotPlanner(
    problem_kind=problem.kind,
    optimality_guarantee=PlanGenerationResultStatus.SOLVED_OPTIMALLY,
) as planner:
    final_report = planner.solve(problem)
    plan = final_report.plan
  *** Credits ***
  * In operation mode `OneshotPlanner` at line 1 of `/tmp/ipykernel_152691/2144990037.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.


Then, we define the shortest plan possible (only the action a) and we assume that the result is SOLVED_OPTIMALLY.

[20]:
expected_plan = up.plans.SequentialPlan(
    [up.plans.ActionInstance(a)]
)
assert final_report.status == PlanGenerationResultStatus.SOLVED_OPTIMALLY
assert plan == expected_plan