2. Numeric Planning

This python notebook shows how to use the unified planning library to model a simple numeric planning problem.

Open In GitHub Open In Colab

2.1. Setup

We install java (used for the numeric planner ENHSP) and the unified planning library.

[ ]:
!apt-get install openjdk-17-jdk
%pip install unified-planning[enhsp]

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

2.2. Demo

We start importing the shortcuts.

[ ]:
from unified_planning.shortcuts import *

Now we start to model a problem involving three numeric variables \(c_0\), \(c_1\) and \(c_2\) that can be increased and decreased. The goal of this problem is to change the variables values such that \(c_0 < c_1 < c_2\). We name with value the lifted fluent that lets us access to the value of a given counter \(c\).

2.2.1. Creating the fluent

First, we define the UserTypes and the Fluents.

[ ]:
Counter = UserType('Counter')

value = Fluent('value', IntType(), m=Counter)

2.2.2. Creating the actions

[ ]:
inc = InstantaneousAction('increment',c=Counter)
c = inc.parameter('c')
inc.add_precondition(LE(value(c), 10))
inc.add_increase_effect(value(c), 1)

dec = InstantaneousAction('decrement',c=Counter)
c = dec.parameter('c')
dec.add_precondition(GT(value(c), 0))
dec.add_decrease_effect(value(c),1)

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. Note here that we do not need to specify all values for each object. These are set to 0 using the default intial value parameter.

[ ]:
problem = Problem('problem')

problem.add_fluent(value, default_initial_value=0)
C0 = Object('c0', Counter)
C1 = Object('c1', Counter)
C2 = Object('c2', Counter)
problem.add_object(C0)
problem.add_object(C1)
problem.add_object(C2)
problem.add_action(inc)
problem.add_action(dec)
problem.add_goal(And( GE(value(C2),Plus(value(C1),1)), GE(value(C1),Plus(value(C0),1))))
problem

problem name = problem

types = [Counter]

fluents = [
  integer value[m=Counter]
]

actions = [
  action increment(Counter c) {
    preconditions = [
      (value(c) <= 10)
    ]
    effects = [
      value(c) += 1
    ]
  }
  action decrement(Counter c) {
    preconditions = [
      (0 < value(c))
    ]
    effects = [
      value(c) -= 1
    ]
  }
]

objects = [
  Counter: [c0, c1, c2]
]

initial fluents default = [
  integer value[m=Counter] := 0
]

initial values = [
]

goals = [
  (((value(c1) + 1) <= value(c2)) and ((value(c0) + 1) <= value(c1)))
]

Now we see how we can generate another, larger problem, much more compactly using a more programmatic definition

[ ]:
N = 9 # This is the number of counters

p2 = Problem('Large_problems')

p2.add_fluent(value, default_initial_value=0)
p2.add_objects([Object(f'c{i}',Counter) for i in range(N)])
p2.add_action(inc)
p2.add_action(dec)

for i in range(N-1):
    p2.add_goal(GE(value(p2.object(f'c{i+1}')),Plus(value(p2.object(f'c{i}')),1)))

p2
problem name = Large_problems

types = [Counter]

fluents = [
  integer value[m=Counter]
]

actions = [
  action increment(Counter c) {
    preconditions = [
      (value(c) <= 10)
    ]
    effects = [
      value(c) += 1
    ]
  }
  action decrement(Counter c) {
    preconditions = [
      (0 < value(c))
    ]
    effects = [
      value(c) -= 1
    ]
  }
]

objects = [
  Counter: [c0, c1, c2, c3, c4, c5, c6, c7, c8]
]

initial fluents default = [
  integer value[m=Counter] := 0
]

initial values = [
]

goals = [
  ((value(c0) + 1) <= value(c1))
  ((value(c1) + 1) <= value(c2))
  ((value(c2) + 1) <= value(c3))
  ((value(c3) + 1) <= value(c4))
  ((value(c4) + 1) <= value(c5))
  ((value(c5) + 1) <= value(c6))
  ((value(c6) + 1) <= value(c7))
  ((value(c7) + 1) <= value(c8))
]

2.2.3. Solving the small and the parametric problem

The unified_planning can either select among the available planners one which is suited for the task at hand (looking at the problem kind), or use the user defined planning. In what follows we first attempt to solve the small problem with three counters and ask the UP to use a specific planning system (ENHSP), and then one with N=9 counters (problem p2) asking the UP to automatically select the engine

[ ]:
with OneshotPlanner(name='enhsp') as planner:
    result = planner.solve(problem)
    plan = result.plan
    if plan is not None:
        print("%s returned:" % planner.name)
        print(plan)
    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 `<ipython-input-8-704bb41341bd>`, you are using the following planning engine:
  * Engine name: ENHSP
  * Developers:  Enrico Scala
  * Description: Expressive Numeric Heuristic Search Planner.

SAT-enhsp returned:
SequentialPlan:
    increment(c2)
    increment(c1)
    increment(c2)
[ ]:
with OneshotPlanner(problem_kind=problem.kind) as planner:
    result = planner.solve(p2)
    plan = result.plan
    if plan is not None:
        print("%s returned:" % planner.name)
        print(plan)
    else:
        print("No plan found.")
  *** Credits ***
  * In operation mode `OneshotPlanner` at line 1 of `<ipython-input-9-9a3c3faa285e>`, you are using the following planning engine:
  * Engine name: ENHSP
  * Developers:  Enrico Scala
  * Description: Expressive Numeric Heuristic Search Planner.

SAT-enhsp returned:
SequentialPlan:
    increment(c8)
    increment(c7)
    increment(c8)
    increment(c8)
    increment(c7)
    increment(c6)
    increment(c8)
    increment(c5)
    increment(c6)
    increment(c7)
    increment(c8)
    increment(c4)
    increment(c5)
    increment(c6)
    increment(c7)
    increment(c8)
    increment(c8)
    increment(c3)
    increment(c4)
    increment(c5)
    increment(c7)
    increment(c6)
    increment(c7)
    increment(c8)
    increment(c6)
    increment(c5)
    increment(c4)
    increment(c2)
    increment(c3)
    increment(c7)
    increment(c6)
    increment(c5)
    increment(c4)
    increment(c3)
    increment(c1)
    increment(c2)

Now let us create a problem medium-sized problem, set up a minimisation function as minimize the number of actions, and see how this can be solved optimally.

[ ]:
from unified_planning.model.metrics import MinimizeSequentialPlanLength

N = 7 #This is the number of counters

mediumSizeProblem = Problem('Medium_sized_problem')

mediumSizeProblem.add_fluent(value, default_initial_value=0)
mediumSizeProblem.add_objects([Object(f'c{i}',Counter) for i in range(N)])
mediumSizeProblem.add_action(inc)
mediumSizeProblem.add_action(dec)
metric = MinimizeSequentialPlanLength()
mediumSizeProblem.add_quality_metric(metric)

for i in range(N-1):
    mediumSizeProblem.add_goal(GE(value(p2.object(f'c{i+1}')),Plus(value(p2.object(f'c{i}')),1)))

with OneshotPlanner(problem_kind=problem.kind,optimality_guarantee=True) as planner:
    result = planner.solve(mediumSizeProblem)
    plan = result.plan
    if plan is not None:
        print("%s returned:" % planner.name)
        print(plan)
    else:
        print("No plan found.")

  *** Credits ***
  * In operation mode `OneshotPlanner` at line 17 of `<ipython-input-10-a567e7dd9139>`, you are using the following planning engine:
  * Engine name: ENHSP
  * Developers:  Enrico Scala
  * Description: Expressive Numeric Heuristic Search Planner.

OPT-enhsp returned:
SequentialPlan:
    increment(c6)
    increment(c5)
    increment(c4)
    increment(c3)
    increment(c6)
    increment(c5)
    increment(c4)
    increment(c3)
    increment(c6)
    increment(c5)
    increment(c4)
    increment(c6)
    increment(c5)
    increment(c2)
    increment(c6)
    increment(c6)
    increment(c5)
    increment(c4)
    increment(c3)
    increment(c2)
    increment(c1)