Search
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.
Problem
Fluent
InstantaneousAction
domain.pddl
problem.pddl
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"))
The student notebook uses a gradual modeling workflow. For each domain, students:
The first action in each domain is already implemented as a worked example. The remaining actions and missing facts are marked with TODO comments.
TODO
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.
at-agent(l)
l
connected(l1, l2)
at-res(res, l)
res
taken(res)
accessible(l)
alive
dead
level(l, lvl)
next(lvl1, lvl2)
level-max(l, lvl)
level-min(lvl)
free(l)
move
collect
shrink
lvl2
lvl1
shrink-small-empty
shrink-small-agent
create
perestroika_problem.add_goal( And( perestroika_fluents["alive"](), perestroika_fluents["taken"](c1), perestroika_fluents["taken"](c2), ) )
The AUV moves through underwater locations, collects samples, and interacts with a ship that can occupy locations and cause destructive collisions if modeled incorrectly.
at(v, l)
v
at-res(r, l)
r
sampled®
operational(a)
connected-ship(s, l1, l2)
s
outside(s)
entry(s, l)
exit(s, l)
sample
enter-ship-free
enter-ship-auv
leave-ship
move-ship-free
move-ship-auv
auv_problem.add_goal( And( auv_fluents["operational"](auv1), auv_fluents["sampled"](r1), auv_fluents["sampled"](r2), ) )
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.
engine = None
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))