Back to Lecture 9

STRIPS Action Representation

Understanding how actions transform states in classical planning

What is STRIPS?

STRIPS: STanford Research Institute Problem Solver

STRIPS is a language for representing actions in classical planning. It defines HOW actions change the world by specifying what must be true before an action can be executed and what changes after execution.

Historical Context
  • Developed: 1971 at Stanford Research Institute
  • Created by: Richard Fikes and Nils Nilsson
  • Purpose: Control the Shakey robot
  • Legacy: Foundation for modern planning languages (PDDL)
πŸ€–

Shakey the Robot
First mobile robot controlled by STRIPS

Why STRIPS?

Before STRIPS, actions were represented as arbitrary procedures (code). STRIPS introduced:

  • βœ… Declarative representation: Say WHAT changes, not HOW
  • βœ… Domain-independent planning: Same algorithms work for any domain
  • βœ… Simple and efficient: Set operations for state transitions
  • βœ… Formal semantics: Mathematical foundation for reasoning

Three Components of a STRIPS Action

Every STRIPS action consists of exactly three parts:

1. Preconditions

What must be TRUE before the action can execute

A set of fluents that must exist in the current state. If ANY precondition is missing, the action CANNOT be applied.

Example:
Grasp(Box)
Precond: {On(Box, Table), Clear(Box), Empty(Hand)}
All three must be true!
Represented as: Precond(a)
2. Add List (Effects)

What becomes TRUE after the action executes

A set of fluents that will be ADDED to the state. These represent the positive effects of the action.

Example:
Grasp(Box)
Add: {Holding(Box)}
βœ… Robot now holds the box
Represented as: Add(a) or Effects+(a)
3. Delete List

What becomes FALSE after the action executes

A set of fluents that will be REMOVED from the state. These represent what the action makes false.

Example:
Grasp(Box)
Del: {On(Box, Table), Clear(Box), Empty(Hand)}
❌ These are no longer true
Represented as: Del(a) or Effectsβˆ’(a)
Key Point: Preconditions, Add List, and Delete List are all sets of ground atomic fluents (from Topic 02). No variables, no quantifiers, no complex formulas!

The Result Function: How Actions Transform States

The STRIPS Result Function

The Result function computes the new state after applying an action to the current state.

Result(s, a) = (s βˆ’ Del(a)) βˆͺ Add(a)
Step-by-Step Breakdown:
Step 1: Start State
s

Current state (set of fluents)

Step 2: Delete
s βˆ’ Del(a)

Remove delete list from state

Step 3: Add
(...) βˆͺ Add(a)

Add add list to result

Concrete Example: Remove(Flat, Axle)
Initial State s:
{
  Tire(Flat, Axle),
  Tire(Spare, Trunk)
}
Precond:
Tire(Flat, Axle)
Del:
Tire(Flat, Axle)
Add:
Tire(Flat, Ground)
s βˆ’ Del(a):
{
  Tire(Flat, Axle)
  Tire(Spare, Trunk)
}
Result State:
{
  Tire(Spare, Trunk),
  Tire(Flat, Ground)
}
Formula Applied:
Result(s, Remove(Flat, Axle)) = (s βˆ’ {Tire(Flat, Axle)}) βˆͺ {Tire(Flat, Ground)}

Example STRIPS Actions

Example 1: Fly(plane, from, to)

An airplane flies from one city to another (Saudi Air Cargo context)

Action: Fly(Plane1, Riyadh, Jeddah)
Preconditions:
  • At(Plane1, Riyadh)
Plane must be at departure city
Add List:
  • At(Plane1, Jeddah)
Plane arrives at destination
Delete List:
  • At(Plane1, Riyadh)
Plane no longer at departure

Example 2: Load(cargo, plane, city)

Loading cargo into an airplane

Action: Load(Cargo1, Plane1, Riyadh)
Preconditions:
  • At(Cargo1, Riyadh)
  • At(Plane1, Riyadh)
Both must be at same city
Add List:
  • In(Cargo1, Plane1)
Cargo now in plane
Delete List:
  • At(Cargo1, Riyadh)
Cargo no longer at city

Example 3: Move(robot, from, to)

A robot moves from one location to another

Action: Move(Robot, Kitchen, Bedroom)
Preconditions:
  • At(Robot, Kitchen)
Robot must be at source
Add List:
  • At(Robot, Bedroom)
Robot at destination
Delete List:
  • At(Robot, Kitchen)
Robot left source

Action Schemas: From Specific to General

The Problem with Ground Actions

So far, we've defined STRIPS actions for specific objects like Remove(Flat, Axle). But what if we have 10 tires and 5 locations? We'd need 50 different actions!

Solution: Action Schemas

An Action Schema is a template for actions that uses variables instead of specific objects. One schema can represent many ground actions!

Think of it as a "function" with parameters that can be filled in with different values.

From Ground Action to Action Schema
Ground Action (Specific)
Remove(Flat, Axle)
Precond: {At(Flat, Axle)}
Add: {At(Flat, Ground)}
Del: {At(Flat, Axle)}

❌ Works only for "Flat" tire and "Axle" location

Generalize!

Action Schema (General)
Remove(?tire, ?loc)
Precond: {At(?tire, ?loc)}
Add: {At(?tire, Ground)}
Del: {At(?tire, ?loc)}

βœ… Works for ANY tire and ANY location

One Schema β†’ Many Ground Actions:
Remove(Flat, Axle), Remove(Spare, Trunk), Remove(Flat, Ground), ... and more!
What's in an Action Schema?
  1. Action Name: What the action does
  2. Parameters: Variables (like ?tire, ?loc)
  3. Preconditions: Using variables
  4. Effects: Add and Delete lists with variables
Key Concepts
  • Variables: Marked with ? (e.g., ?x, ?robot)
  • Grounding: Replacing variables with specific objects
  • Instantiation: Creating ground actions from schema
  • One schema β†’ Many actions
More Action Schema Examples
Move Action
Move(?robot, ?from, ?to)
Precond: {At(?robot, ?from)}
Add: {At(?robot, ?to)}
Del: {At(?robot, ?from)}
Grounds to: Move(Robot1, Kitchen, Bedroom), Move(Robot2, Hall, Office), ...
Grasp Action
Grasp(?robot, ?box)
Precond: {At(?robot, ?loc), At(?box, ?loc), Empty(?robot)}
Add: {Holding(?robot, ?box)}
Del: {Empty(?robot), At(?box, ?loc)}
Grounds to: Grasp(Robot1, BoxA), Grasp(Robot2, BoxB), ...
Why Action Schemas Matter
Compact Representation

One schema instead of hundreds of actions

Reusability

Same schema works with different objects

Foundation for PDDL

This concept is central to PDDL syntax

Next Step: In Topic 04, you'll learn how PDDL formalizes action schemas with typed variables and structured syntax!

Interactive STRIPS Action Executor

Try applying STRIPS actions yourself and see the Result function in action!

Scenario 1: Spare Tire Replacement

Current State:
Tire(Flat, Axle) Tire(Spare, Trunk)
Goal:
Tire(Spare, Axle)
Available Actions:
Precond: Tire(Flat, Axle)
Del: Tire(Flat, Axle)
Add: Tire(Flat, Ground)
Precond: Tire(Spare, Trunk)
Del: Tire(Spare, Trunk)
Add: Tire(Spare, Ground)
Precond: Tire(Spare, Ground), Β¬Tire(Flat, Axle)
Del: Tire(Spare, Ground)
Add: Tire(Spare, Axle)
Important: Negative Preconditions

Notice that PutOn(Spare, Axle) has a negative precondition: Β¬Tire(Flat, Axle). This ensures the axle is empty before putting the spare on it. Under the Closed-World Assumption (CWA), we check that Tire(Flat, Axle) is NOT in the state. This prevents physically impossible situations like having two tires on the same axle!

Scenario 2: Robot Grasping

Current State:
On(Box, Table) Clear(Box) Empty(Hand)
Goal:
On(Box, Target)
Available Actions:
Precond: On(Box, Table), Clear(Box), Empty(Hand)
Del: On(Box, Table), Clear(Box), Empty(Hand)
Add: Holding(Box)
Precond: Holding(Box)
Del: Holding(Box)
Add: On(Box, Target), Clear(Box), Empty(Hand)

Python STRIPS Representation

Implementing STRIPS in Python

You can represent STRIPS models (states, actions, goals) programmatically in Python using scoped classes and objects β€” without relying on PDDL files!

This approach gives you full control over the planning process and helps you understand the mechanics of STRIPS at a deeper level.

Why Python for STRIPS?
  • βœ… No external dependencies: Pure Python implementation
  • βœ… Full control: Customize every aspect
  • βœ… Educational: See exactly how STRIPS works
  • βœ… Modular: Each action is encapsulated
  • βœ… Flexible: Easy to extend and debug
  • βœ… Practical: Integrate with other Python code
Step 1: Define the Action Class

Each STRIPS action is represented as a Python class with preconditions, add list, and delete list:

Action Class 🐍 Python
class Action:
    def __init__(self, name, precond, add, delete):
        """Initialize a STRIPS action"""
        self.name = name
        self.precond = set(precond)   # Preconditions (must be true)
        self.add = set(add)            # Add list (becomes true)
        self.delete = set(delete)      # Delete list (becomes false)
    
    def applicable(self, state):
        """Check if action can be applied to the given state"""
        return self.precond.issubset(state)
    
    def apply(self, state):
        """Apply action: Result(s, a) = (s - Del(a)) βˆͺ Add(a)"""
        return (state - self.delete) | self.add
    
    def __repr__(self):
        return self.name
Key Methods:
  • applicable(state) - Checks if preconditions are satisfied
  • apply(state) - Implements the STRIPS Result function: (s βˆ’ Del) βˆͺ Add
Step 2: Define the STRIPS Problem Class

Encapsulate the initial state, goal, and actions in a problem class:

STRIPSProblem Class 🐍 Python
class STRIPSProblem:
    def __init__(self, initial, goal, actions):
        """Initialize a STRIPS planning problem"""
        self.initial = set(initial)   # Initial state (set of facts)
        self.goal = set(goal)          # Goal state (set of facts)
        self.actions = actions         # List of available actions
    
    def goal_reached(self, state):
        """Check if goal is satisfied in the given state"""
        return self.goal.issubset(state)
    
    def get_applicable_actions(self, state):
        """Get all actions applicable in the given state"""
        return [a for a in self.actions if a.applicable(state)]
Step 3: Complete Example - Blocks World

Let's put it all together with the classic Blocks World problem:

Initial State:
β€’ A and B are on the table
β€’ Both are clear
β€’ Hand is empty
Goal:
β€’ Stack A on B
Complete Blocks World Example 🐍 Python
# Define initial state
initial_state = {
    "ontable(A)", 
    "ontable(B)", 
    "clear(A)", 
    "clear(B)", 
    "handempty"
}

# Define goal
goal = {"on(A,B)"}

# Define actions
actions = [
    Action(
        "pick-up(A)",
        precond={"ontable(A)", "clear(A)", "handempty"},
        add={"holding(A)"},
        delete={"ontable(A)", "clear(A)", "handempty"}
    ),
    Action(
        "pick-up(B)",
        precond={"ontable(B)", "clear(B)", "handempty"},
        add={"holding(B)"},
        delete={"ontable(B)", "clear(B)", "handempty"}
    ),
    Action(
        "stack(A,B)",
        precond={"holding(A)", "clear(B)"},
        add={"on(A,B)", "clear(A)", "handempty"},
        delete={"holding(A)", "clear(B)"}
    ),
    Action(
        "stack(B,A)",
        precond={"holding(B)", "clear(A)"},
        add={"on(B,A)", "clear(B)", "handempty"},
        delete={"holding(B)", "clear(A)"}
    )
]

# Create problem instance
problem = STRIPSProblem(initial_state, goal, actions)

# Test: Check what actions are applicable
print("Initial state:", problem.initial)
print("Applicable actions:", problem.get_applicable_actions(problem.initial))
Step 4: Implement Forward Search Planner

Now let's implement a simple forward search to find a plan:

Forward Search Planner 🐍 Python
from collections import deque

def forward_search(problem, max_depth=10):
    """
    Simple breadth-first forward search planner
    Returns: list of action names forming a plan, or None if no plan found
    """
    # Queue stores: (current_state, plan_so_far)
    frontier = deque([(problem.initial, [])])
    visited = {frozenset(problem.initial)}
    nodes_expanded = 0
    
    while frontier:
        state, plan = frontier.popleft()
        nodes_expanded += 1
        
        # Goal test
        if problem.goal_reached(state):
            print(f"βœ… Solution found!")
            print(f"πŸ“Š Nodes expanded: {nodes_expanded}")
            print(f"πŸ“ Plan length: {len(plan)}")
            return plan
        
        # Don't search beyond max depth
        if len(plan) >= max_depth:
            continue
        
        # Expand node: try all applicable actions
        for action in problem.get_applicable_actions(state):
            new_state = action.apply(state)
            state_signature = frozenset(new_state)
            
            if state_signature not in visited:
                visited.add(state_signature)
                new_plan = plan + [action.name]
                frontier.append((new_state, new_plan))
    
    print("❌ No solution found")
    return None

# Run the planner
print("\nπŸ” Searching for a plan...")
print("=" * 50)
plan = forward_search(problem)

if plan:
    print("\nπŸ“‹ Plan Found:")
    for i, action_name in enumerate(plan, 1):
        print(f"  {i}. {action_name}")
    
    # Verify by executing the plan
    print("\n🎬 Executing plan step-by-step:")
    state = problem.initial
    print(f"Initial: {state}")
    
    for action_name in plan:
        action = next(a for a in problem.actions if a.name == action_name)
        state = action.apply(state)
        print(f"After {action_name}: {state}")
    
    print(f"\n🎯 Goal {'βœ… ACHIEVED' if problem.goal_reached(state) else '❌ NOT ACHIEVED'}!")
Expected Output:
πŸ” Searching for a plan...
==================================================
βœ… Solution found!
πŸ“Š Nodes expanded: 3
πŸ“ Plan length: 2

πŸ“‹ Plan Found:
  1. pick-up(A)
  2. stack(A,B)

🎬 Executing plan step-by-step:
Initial: {'ontable(A)', 'ontable(B)', 'clear(A)', 'clear(B)', 'handempty'}
After pick-up(A): {'ontable(B)', 'clear(B)', 'holding(A)'}
After stack(A,B): {'ontable(B)', 'on(A,B)', 'clear(A)', 'handempty'}

🎯 Goal βœ… ACHIEVED!
Python STRIPS
Advantages:
  • βœ… No external dependencies
  • βœ… Full programmatic control
  • βœ… Easy to debug with print statements
  • βœ… Integrate with any Python code
  • βœ… Custom search strategies
Best For:
  • Learning and understanding STRIPS
  • Prototyping custom planners
  • Integration with existing systems
PDDL
Advantages:
  • βœ… Standard format (widely used)
  • βœ… Domain-independent planners
  • βœ… Typed variables and advanced features
  • βœ… Competition-tested planners
  • βœ… Declarative specification
Best For:
  • Production planning systems
  • Using state-of-the-art planners
  • Sharing problems with community
Python Scoping Benefits
Encapsulation

Each action is scoped in its own class with encapsulated behavior

Modularity

Easy to extend with subclasses (e.g., hierarchical planning)

Flexibility

Full control over search strategies and heuristics

Combining Both Approaches: In practice, you can use PDDL for problem specification and Python for custom reasoning! See Topic 09 for how to use pddlpy to load PDDL files and manipulate them in Python.

Python Libraries for STRIPS Planning

Beyond building from scratch, there are several Python libraries that implement or support STRIPS-style planning:

1
pyddl - Pure Python STRIPS Library
Simple, clean, ideal for teaching
βœ… What it does:
  • Pure Python STRIPS representation
  • Define actions with precond/add/delete
  • Built-in planner (BFS/A*)
  • No PDDL files needed
πŸ“ Installation & Example:
# Install
pip install pyddl
# Example code
from pyddl import Domain, Problem, Action, planner

domain = Domain((
    Action('pick-up', parameters=('x',),
        preconditions=(('ontable', 'x'), ('clear', 'x'), ('handempty',)),
        effects=(('holding', 'x'), ('not', ('ontable', 'x')))
    ),
))

problem = Problem(domain, {'A': 'block'}, 
    init=(('ontable', 'A'), ('clear', 'A'), ('handempty',)),
    goal=(('holding', 'A'),)
)

plan = planner(problem)
# Output: [pick-up(A)]
πŸŽ“ Best For: Students learning STRIPS β€” everything stays in Python, very clear to read!
2
pddlpy - PDDL Parser
Good for reasoning and inference with PDDL files
βœ… What it does:
  • Reads PDDL domain/problem files
  • Supports STRIPS, ADL, PDDL2.1
  • Reason about applicable actions
  • No automatic planning (manual control)
πŸ“ Installation & Example:
# Install
pip install pddlpy
# Example code
from pddlpy import DomainProblem

dp = DomainProblem('blocks-domain.pddl', 'blocks-problem.pddl')

# Check applicable actions
for act in dp.ground_operator():
    if dp.is_applicable(act):
        print("βœ… Applicable:", act)

# Manually apply action
dp.apply_operator(('pick-up', 'A'))
print("State after action:", dp.state)
πŸŽ“ Best For: Connecting Python with classical PDDL theory β€” shows how PDDL is parsed and reasoned about.
3
pyperplan - Educational STRIPS Planner
Teachable A* STRIPS planner with heuristics
βœ… What it does:
  • Clean Python STRIPS implementation
  • Forward search with A*
  • Multiple heuristics (FF, h-add, etc.)
  • Designed for teaching/research
πŸ“ Installation & Example:
# Install
pip install pyperplan
# Example code
from pyperplan.pddl.parser import Parser
from pyperplan.planner import _ground, _search

parser = Parser('blocks-domain.pddl', 'blocks-problem.pddl')
domain, problem = parser.parse_domain_problem()

task = _ground(domain, problem)
plan = _search(task, heuristic='hff')  # FF heuristic

for step in plan:
    print(f"βœ… {step}")
πŸŽ“ Best For: Demonstrating search algorithms and heuristics β€” shows how A* and FF work in practice!
4
unified-planning - Modern Unified Framework
Industrial-grade, modular, powerful (by AIPlan4EU)
βœ… What it does:
  • Modern planning framework
  • STRIPS + temporal + hierarchical
  • Multiple planner backends
  • Production-ready
πŸ“ Installation & Example:
# Install
pip install unified-planning[pyperplan]
# Example code
from unified_planning.shortcuts import *

problem = Problem("blocks")
Block = UserType("Block")
A = Object("A", Block)

ontable = Fluent("ontable", BoolType(), x=Block)
clear = Fluent("clear", BoolType(), x=Block)

pickup = InstantaneousAction("pickup", x=Block)
pickup.add_precondition(ontable(pickup.x))
pickup.add_effect(clear(pickup.x), False)

problem.add_action(pickup)
problem.set_initial_value(ontable(A), True)
problem.add_goal(Not(ontable(A)))

with OneshotPlanner(name="pyperplan") as planner:
    result = planner.solve(problem)
    print(result.plan)
πŸŽ“ Best For: Advanced students and research β€” modular architecture, supports multiple backends!
Library Comparison
Library Type Supports PDDL Planning Strength
pyddl Pure Python STRIPS ❌ No βœ… Yes Simple, clean, ideal for teaching
pddlpy PDDL parser βœ… Yes ❌ No Good for reasoning/inference
pyperplan Academic STRIPS planner βœ… Yes βœ… Yes Teachable A* STRIPS planner
unified-planning Modern unified framework βœ… Yes βœ… Yes Industrial-grade, modular, powerful
πŸ“š Recommendations for Learning
For Beginners

Start with pyddl β€” everything stays in Python, no PDDL files, very clear to read.

For Understanding PDDL

Use pddlpy β€” connects Python with classical planning theory and shows how PDDL is parsed.

For Advanced Topics

Try pyperplan β€” demonstrates heuristics (A*, FF) in a teachable implementation.

Pro Tip: You can combine approaches! Use pyddl for quick prototypes, pddlpy to load standard PDDL benchmarks, and pyperplan to experiment with different search strategies and heuristics. See Topic 09 for complete examples of all three approaches!

Key Takeaways

βœ… STRIPS Essentials
  1. Three Components: Preconditions, Add List, Delete List
  2. Result Function: (s βˆ’ Del) βˆͺ Add
  3. Ground Fluents: All elements are ground atomic fluents
  4. Declarative: Says WHAT changes, not HOW
🎯 Why STRIPS Matters
  • Simple: Easy to understand and implement
  • Efficient: Set operations are fast
  • General: Works for many domains
  • Foundation: Basis for PDDL and modern planners
Next: Learn PDDL, the modern language that extends STRIPS with types, variables, and more! Continue to PDDL β†’