Classical Planning: Finding a sequence of actions to reach a goal in discrete, deterministic, static, fully observable environments.
State = Set of ground atomic fluents
At(Robot, Kitchen) — ValidHolding(Box) — ValidAt(x, Kitchen) — Has variable¬Holding(Box) — Has negation
States are represented as sets (unordered, no duplicates)
At(Robot, Kitchen) ∈ S → TRUEAt(Robot, Bedroom) ∉ S → FALSE
Robot1 ≠ Robot2Kitchen ≠ Bedroom
Every action has exactly three parts:
| Component | Description | Example |
|---|---|---|
PreconditionsPrecond(a) |
What must be TRUE before action executes | { At(Robot, Table), Empty(Hand) } |
Add ListAdd(a) |
What becomes TRUE after action | { Holding(Box) } |
Delete ListDel(a) |
What becomes FALSE after action | { On(Box, Table), Empty(Hand) } |
Template with variables → One schema generates many ground actions
Move(?robot, ?from, ?to) → Move(Robot1, Kitchen, Bedroom)
(define (domain logistics)
(:types vehicle location)
(:predicates
(at ?v - vehicle ?l - location))
(:action move ...)
)
(define (problem deliver) (:domain logistics) (:objects truck1 - vehicle) (:init (at truck1 depot)) (:goal (at truck1 store)) )
(:action move :parameters (?v - vehicle ?from ?to - location) :precondition (and (at ?v ?from)) :effect (and (at ?v ?to) (not (at ?v ?from))) )
| PDDL Element | Syntax |
|---|---|
| Variable | ?x ?robot ?from |
| Type | ?x - type |
| Conjunction | (and ...) |
| Negation | (not ...) |
| Universal | (forall (?x) ...) |
| Existential | (exists (?x) ...) |
Progression: Start from initial state, apply actions until goal reached
Regression: Start from goal, work backwards to initial state
Encode as logic: Convert planning to SAT problem
frontier ← {initial_state}
explored ← ∅
while frontier not empty:
node ← Remove(frontier) // BFS, DFS, or A* here
if Goal-Test(node.state):
return Solution(node)
explored ← explored ∪ {node.state}
for action in Applicable-Actions(node.state):
child ← Apply(node.state, action)
if child.state ∉ explored:
Add(child, frontier)
return Failure
| Heuristic | Description | Properties |
|---|---|---|
| Goal Count | h(s) = # of unsatisfied goal atoms | Admissible |
| Delete Relaxation | Ignore delete effects (optimistic) | Admissible |
| hadd | Sum of costs to achieve each goal | Not admissible |
| hmax | Max cost among all goals | Admissible |
| FF (Fast-Forward) | Plan length in relaxed problem | Not admissible Effective |
class Action:
def __init__(self, name, precond, add, delete):
self.precond = set(precond)
self.add = set(add)
self.delete = set(delete)
def applicable(self, state):
return self.precond.issubset(state)
def apply(self, state):
return (state - self.delete) | self.add
pip install pyddl
pip install pddlpy
pip install pyperplan
pip install unified-planning
Goal: Stack blocks in target configuration
Actions: Pickup, Putdown, Stack, Unstack
Predicates: On(x,y), Clear(x), Holding(x), HandEmpty
Goal: Replace flat tire with spare
Actions: Remove(tire,loc), PutOn(tire,axle)
Predicates: Tire(tire, location)
Goal: Transport cargo between cities
Actions: Load, Unload, Fly, Drive
Predicates: At(obj,loc), In(cargo,vehicle)
Goal: Move robot to target location
Actions: Move(from,to), Grasp, Release
Predicates: At(robot,loc), Holding(obj)
| Aspect | STRIPS | PDDL | FOL |
|---|---|---|---|
| Variables | Action schemas only | Action schemas + types | Full support |
| Negation | Delete lists | (not ...) in effects | Full ¬ operator |
| Quantifiers | ❌ No | ✅ forall, exists | ✅ ∀, ∃ |
| Typing | ❌ No | ✅ Yes | Depends |
| Usage | Educational | Standard for planners | General reasoning |
STRIPS Result Function: Result(s, a) = (s − Del(a)) ∪ Add(a) Applicable Action: Applicable(a, s) ⟺ Precond(a) ⊆ s Goal Test: Goal-Test(s) ⟺ Goal ⊆ s Plan Quality: Cost(π) = Σ Cost(aᵢ) for aᵢ ∈ π
Forward Search: s' = Result(s, a) where Applicable(a, s) Backward Search (Regression): g' = (g − Add(a)) ∪ Precond(a) A* Evaluation: f(n) = g(n) + h(n) Branching Factor: b = average # applicable actions