10. Self-Contained PDDL Modeling with Unified Planning

This lab introduces classical planning modeling directly in Python with Unified Planning. Students construct two planning problems, solve them from in-memory objects, and optionally export the same models to PDDL.

Learning Goals

  • Build a planning model with Problem, Fluent, InstantaneousAction, objects, initial facts, and goals.
  • Understand how domain rules are translated into operators with preconditions and effects.
  • Solve a planning problem directly from Python objects without first loading PDDL files.
  • Export the generated model to domain.pddl and problem.pddl.

Setup

Run the following cell first to install the required Python packages:

%pip install \
    "unified-planning>=1.1.0" \
    "unified-planning[engines]>=1.1.0" \
    "pddl>=0.4.3"

The notebook utilities are:

from pathlib import Path
 
from unified_planning.io import PDDLWriter
from unified_planning.shortcuts import (
    And,
    BoolType,
    Fluent,
    InstantaneousAction,
    Object,
    OneshotPlanner,
    Problem,
    UserType,
)
 
ROOT = Path.cwd()
DOMAINS_DIR = ROOT / "domains"
DOMAINS_DIR.mkdir(parents=True, exist_ok=True)
 
 
def solve_problem(problem: Problem, engine: str | None = None):
    planner_kwargs = {}
    if engine is not None:
        planner_kwargs["name"] = engine
 
    with OneshotPlanner(problem_kind=problem.kind, **planner_kwargs) as planner:
        return planner.solve(problem)
 
 
def export_pddl(problem: Problem, out_dir: Path):
    out_dir.mkdir(parents=True, exist_ok=True)
    writer = PDDLWriter(problem)
    writer.write_domain(str(out_dir / "domain.pddl"))
    writer.write_problem(str(out_dir / "problem.pddl"))

Student Tasks

The student notebook uses a gradual modeling workflow. For each domain, students:

  1. keep the provided types and fluents,
  2. complete operator preconditions and effects,
  3. complete the objects, initial state, and goal,
  4. run the model and inspect the produced plan.

The first action in each domain is already implemented as a worked example. The remaining actions and missing facts are marked with TODO comments.

Domain 1: Perestroika

Modeling Idea

The agent moves across locations, collects resources, and must stay alive while tiles shrink and disappear. The model tracks accessibility, occupancy, and tile level changes.

Main Fluents

Fluent Meaning
at-agent(l) The player is at location l.
connected(l1, l2) The two locations are adjacent.
at-res(res, l) Resource res is placed at location l.
taken(res) The resource has been collected.
accessible(l) The tile can still be entered.
alive / dead Survival state of the agent.
level(l, lvl) Current shrinking level of a tile.
next(lvl1, lvl2) Successor relation between levels.
level-max(l, lvl) Maximum regeneration level of a tile.
level-min(lvl) Minimum level in the shrinking chain.
free(l) The location is currently unoccupied.

Main Actions

  • move: move between connected and accessible locations.
  • collect: pick up a resource at the current position.
  • shrink: reduce a tile level from lvl2 to lvl1.
  • shrink-small-empty: remove an empty tile at minimum level.
  • shrink-small-agent: remove a minimum-level tile while the agent is standing on it, killing the agent.
  • create: regenerate a missing tile at its maximum level.

Reference Goal

perestroika_problem.add_goal(
    And(
        perestroika_fluents["alive"](),
        perestroika_fluents["taken"](c1),
        perestroika_fluents["taken"](c2),
    )
)

Domain 2: AUV

Modeling Idea

The AUV moves through underwater locations, collects samples, and interacts with a ship that can occupy locations and cause destructive collisions if modeled incorrectly.

Main Fluents

Fluent Meaning
at(v, l) Vehicle v is at location l.
connected(l1, l2) AUV movement edge between locations.
at-res(r, l) Sample r is located at l.
sampled® The sample has been collected.
free(l) The location is free.
operational(a) The AUV is still functioning.
connected-ship(s, l1, l2) Ship s can move between these locations.
outside(s) The ship is outside the map.
entry(s, l) / exit(s, l) Entry and exit points for the ship.
dead A fatal collision has occurred.

Main Actions

  • move: move the AUV between connected free locations.
  • sample: collect a resource at the current AUV location.
  • enter-ship-free: move a ship into a free location.
  • enter-ship-auv: move a ship into a location occupied by an AUV, destroying it.
  • leave-ship: remove the ship from a location.
  • move-ship-free: move the ship into a free location.
  • move-ship-auv: move the ship into a location occupied by the AUV, destroying it.

Reference Goal

auv_problem.add_goal(
    And(
        auv_fluents["operational"](auv1),
        auv_fluents["sampled"](r1),
        auv_fluents["sampled"](r2),
    )
)

Running the Models

The notebooks use shared run parameters:

engine = None
write_pddl = True

With engine = None, Unified Planning automatically selects any compatible available backend. This is the safest default in environments where a specific engine may not be installed.

To run the two problems:

print("\n=== PERESTROIKA ===")
if write_pddl:
    export_pddl(perestroika_problem, DOMAINS_DIR / "perestroika")
print(solve_problem(perestroika_problem, engine=engine))
 
print("\n=== AUV ===")
if write_pddl:
    export_pddl(auv_problem, DOMAINS_DIR / "auv")
print(solve_problem(auv_problem, engine=engine))

Files

courses/pui/tutorials/tutorial10b.txt · Last modified: 2026/05/03 23:07 by medjaku1