Planners-6-Domains - Domaines Classiques de Planification

Navigation : Index | << Heuristiques | OR-Tools >>

Objectifs d’apprentissage

A la fin de ce notebook, vous saurez : 1. Modeliser le domaine Block World en PDDL 2. Modeliser le domaine Logistics avec transport multi-agent 3. Resoudre ces problemes avec Fast Downward 4. Analyser la qualite des plans et comparer les domaines 5. Connaitre d’autres domaines classiques (Gripper, Ferry, Hanoi)

Prerequis

Duree estimee : 50 minutes


1. Introduction aux domaines classiques

Les domaines classiques de planification sont des benchmarks standardises utilises depuis les premières competitions IPC (International Planning Competition) en 1998. Ils permettent de comparer les performances des planificateurs sur des problemes bien définis.

1.1 Taxonomie des domaines

Domaine Type Complexite Usage principal
Block World Manipulation Simple Tutoriel, demo
Gripper Transport simple Simple Test algorithmes
Logistics Transport multi-agent Moyen Applications reelles
Depots Gestion stock Moyen Planification industrielle
Ferry Transport séquentiel Simple Pedagogie
Hanoi Puzzle Moyen Test récursive

1.2 Pourquoi etudier ces domaines ?

  1. Standardisation : Benchmarks reconnus par la communaute
  2. Progression pedagogique : Du simple au complexe
  3. Applications reelles : Logistics = livraison, Depots = entrepot
  4. Comparaison : Performance relative des heuristiques
# Verification de l'environnement
import os
import sys

try:
    import unified_planning as up
    from unified_planning.shortcuts import *
    print(f"unified-planning version: {up.__version__}")
    UP_AVAILABLE = True
    # Supprimer les credits du moteur (bruit + fuite de chemin local)
    get_environment().credits_stream = None
except ImportError:
    print("ERREUR: unified-planning non installe")
    print("pip install unified-planning")
    UP_AVAILABLE = False

# Configuration pour l'affichage
import warnings
warnings.filterwarnings('ignore')
unified-planning version: 1.3.0

Interpretation : Verification de l’environnement

Sortie obtenue : unified-planning version 1.3.0 est correctement installe et operationnel.

Composant Version Statut
unified-planning 1.3.0 OK

Points cles : 1. L’environnement est pret pour modeliser et resoudre des problemes de planification 2. Les warnings sont filtres pour une sortie plus propre 3. Tous les domaines classiques peuvent etre explores avec cette version

Note technique : unified-planning supporte de nombreux planificateurs. Pour les domaines classiques, pyperplan est rapide mais Fast Downward offre de meilleures performances sur les problemes complexes.


2. Block World - Le domaine de reference

Le Block World (monde des blocs) est le domaine le plus etudie en planification. Un bras robotique manipule des blocs pour construire des tours.

2.1 Description du domaine

Éléments du monde : - Blocs : Objets manipulables - Table : Surface de base (infinie) - Bras robotique : Peut tenir un bloc a la fois

Actions disponibles :

Action Preconditions Effets
pick(x) clear(x), ontable(x), handempty holding(x)
putdown(x) holding(x) ontable(x), clear(x), handempty
stack(x,y) holding(x), clear(y) on(x,y), clear(x), handempty
unstack(x,y) on(x,y), clear(x), handempty holding(x), clear(y)

2.2 Domaine PDDL Block World

# Domaine PDDL - Block World
BLOCKS_DOMAIN = """
(define (domain blocks)
  (:requirements :strips :typing)
  (:types block)
  
  (:predicates
    ;; Relations spatiales
    (on ?x ?y - block)       ;; x est sur y
    (ontable ?x - block)     ;; x est sur la table
    (clear ?x - block)       ;; rien n'est sur x
    
    ;; Etat du bras
    (holding ?x - block)     ;; le bras tient x
    (handempty)              ;; le bras est vide
  )
  
  ;; Action : Prendre un bloc de la table
  (:action pick-up
    :parameters (?x - block)
    :precondition (and (clear ?x) (ontable ?x) (handempty))
    :effect (and 
              (holding ?x) 
              (not (ontable ?x)) 
              (not (clear ?x)) 
              (not (handempty)))
  )
  
  ;; Action : Poser un bloc sur la table
  (:action put-down
    :parameters (?x - block)
    :precondition (holding ?x)
    :effect (and 
              (ontable ?x) 
              (clear ?x) 
              (handempty) 
              (not (holding ?x)))
  )
  
  ;; Action : Empiler un bloc sur un autre
  (:action stack
    :parameters (?x ?y - block)
    :precondition (and (holding ?x) (clear ?y))
    :effect (and 
              (on ?x ?y) 
              (clear ?x) 
              (handempty) 
              (not (holding ?x)) 
              (not (clear ?y)))
  )
  
  ;; Action : Deseempiler un bloc d'un autre
  (:action unstack
    :parameters (?x ?y - block)
    :precondition (and (on ?x ?y) (clear ?x) (handempty))
    :effect (and 
              (holding ?x) 
              (clear ?y) 
              (not (on ?x ?y)) 
              (not (clear ?x)) 
              (not (handempty)))
  )
)
"""

print("Domaine PDDL Block World defini")
print("Actions: pick-up, put-down, stack, unstack")
Domaine PDDL Block World defini
Actions: pick-up, put-down, stack, unstack

Interpretation : Domaine Block World

Sortie obtenue : Le domaine PDDL Block World est défini avec 4 actions et 5 predicats.

Élément Type Signification
Types block Objets manipulables
Predicats on, ontable, clear, holding, handempty Relations spatiales et etat du bras
Actions pick-up, put-down, stack, unstack Opérations de manipulation

Structure des actions : - pick-up(x) : Prendre un bloc de la table (doit etre libre, bras vide) - put-down(x) : Poser un bloc sur la table (bras tient le bloc) - stack(x,y) : Empiler x sur y (bras tient x, y est libre) - unstack(x,y) : Deseempiler x de y (x sur y, x libre, bras vide)

Note technique : Ce domaine est le “Hello World” de la planification. Il a ete introduit en 1971 dans le papier original sur STRIPS et reste utilise aujourd’hui pour tester les nouveaux planificateurs.

2.3 Problemes Block World

# Probleme simple : construire une tour de 3 blocs
BLOCKS_PROBLEM_TOWER = """
(define (problem blocks-tower)
  (:domain blocks)
  (:objects a b c - block)
  
  (:init
    ;; Tous les blocs sont sur la table
    (ontable a) (ontable b) (ontable c)
    ;; Tous sont libres
    (clear a) (clear b) (clear c)
    ;; Le bras est vide
    (handempty)
  )
  
  (:goal (and 
    (on a b)   ;; a sur b
    (on b c)   ;; b sur c
  ))
)
"""

print("Probleme 'blocks-tower' : Construire une tour c <- b <- a")
print("\nEtat initial: a, b, c sur la table")
print("But: a sur b, b sur c (tour de 3)")
Probleme 'blocks-tower' : Construire une tour c <- b <- a

Etat initial: a, b, c sur la table
But: a sur b, b sur c (tour de 3)

Interpretation : Problemes Block World

Sortie obtenue : Deux problemes sont définis avec des niveaux de complexite différents.

Problème Objets Etat initial But Complexite
blocks-tower 3 blocs (a,b,c) Tous sur table Tour a-b-c Simple (4 actions)
blocks-reverse 4 blocs 2 tours existantes Inverser tours Moyen (8+ actions)

Analyse comparative : - blocks-tower : Construction depuis zero. La solution est directe (empiler b sur c, puis a sur b). - blocks-reverse : Necessite de defaire les tours existantes avant de reconstruire. Illustration du besoin de “deseempiler” (action unstack).

Note pedagogique : Le problème “blocks-reverse” montre que parfois il faut reculer (deseempiler) pour avancer (reconstruire). C’est un pattern recurrent en planification.

Definition d’un problème Block World plus complexe impliquant l’inversion de deux tours, ce qui necessite de defaire une configuration existante avant de reconstruire.

# Probleme plus complexe : inverser deux tours
BLOCKS_PROBLEM_REVERSE = """
(define (problem blocks-reverse)
  (:domain blocks)
  (:objects a b c d - block)
  
  (:init
    ;; Tour 1 : d sur c (c sur table)
    (ontable c) (on d c) (clear d)
    ;; Tour 2 : b sur a (a sur table)
    (ontable a) (on b a) (clear b)
    ;; Bras vide
    (handempty)
  )
  
  (:goal (and 
    ;; Inverser : c sur d, a sur b
    (ontable d) (on c d)
    (ontable b) (on a b)
  ))
)
"""

print("Probleme 'blocks-reverse' : Inverser deux tours")
print("\nEtat initial:")
print("  Tour 1: table <- c <- d")
print("  Tour 2: table <- a <- b")
print("\nBut:")
print("  Tour 1: table <- d <- c")
print("  Tour 2: table <- b <- a")
Probleme 'blocks-reverse' : Inverser deux tours

Etat initial:
  Tour 1: table <- c <- d
  Tour 2: table <- a <- b

But:
  Tour 1: table <- d <- c
  Tour 2: table <- b <- a

Interpretation : Problème Block World “inverse”

Sortie obtenue : Definition d’un problème plus complexe necessitant d’inverser deux tours existantes.

Etat initial But
Tour 1: table <- c <- d Tour 1: table <- d <- c
Tour 2: table <- a <- b Tour 2: table <- b <- a

Complexite par rapport au problème simple : - blocks-tower : Construction depuis zero (tout est sur la table) - blocks-reverse : Deconstruire avant de reconstruire (actions unstack necessaires)

Actions necessaires (plan typique) : 1. unstack(d, c) - deplacer d de c 2. put-down(d) - poser d sur la table 3. unstack(c, table) - prendre c (mais c est déjà sur table!) → erreur de reflexion 4. Correction: pick-up(c) - prendre c sur la table 5. stack(c, d) - empiler c sur d 6. unstack(b, a) - deplacer b de a 7. put-down(b) - poser b 8. pick-up(a) - prendre a 9. stack(a, b) - empiler a sur b

Note pedagogique : Ce problème illustre que parfois il faut “défaire” pour “refaire”. C’est un concept important en planification : l’etat initial n’est pas toujours vide, et le planificateur doit choisir l’ordre optimal de déconstruction/reconstruction.

2.4 Resolution avec unified-planning

if UP_AVAILABLE:
    # Definition du domaine Block World avec unified-planning
    from collections import OrderedDict
    
    Block = UserType('Block')
    
    # Predicats - IMPORTANT: utiliser OrderedDict pour le signature (API v1.3+)
    on = Fluent('on', BoolType(), OrderedDict([('x', Block), ('y', Block)]))
    ontable = Fluent('ontable', BoolType(), OrderedDict([('x', Block)]))
    clear = Fluent('clear', BoolType(), OrderedDict([('x', Block)]))
    holding = Fluent('holding', BoolType(), OrderedDict([('x', Block)]))
    handempty = Fluent('handempty', BoolType())
    
    # Variables pour les actions
    x = Variable('x', Block)
    y = Variable('y', Block)
    
    # Action pick-up
    pick_up = InstantaneousAction('pick-up', OrderedDict([('x', Block)]))
    x_pu = pick_up.parameter('x')  # IMPORTANT: utiliser action.parameter()
    pick_up.add_precondition(clear(x_pu))
    pick_up.add_precondition(ontable(x_pu))
    pick_up.add_precondition(handempty)
    pick_up.add_effect(holding(x_pu), True)
    pick_up.add_effect(ontable(x_pu), False)
    pick_up.add_effect(clear(x_pu), False)
    pick_up.add_effect(handempty, False)
    
    # Action put-down
    put_down = InstantaneousAction('put-down', OrderedDict([('x', Block)]))
    x_pd = put_down.parameter('x')
    put_down.add_precondition(holding(x_pd))
    put_down.add_effect(ontable(x_pd), True)
    put_down.add_effect(clear(x_pd), True)
    put_down.add_effect(handempty, True)
    put_down.add_effect(holding(x_pd), False)
    
    # Action stack
    stack = InstantaneousAction('stack', OrderedDict([('x', Block), ('y', Block)]))
    x_s = stack.parameter('x')
    y_s = stack.parameter('y')
    stack.add_precondition(holding(x_s))
    stack.add_precondition(clear(y_s))
    stack.add_effect(on(x_s, y_s), True)
    stack.add_effect(clear(x_s), True)
    stack.add_effect(handempty, True)
    stack.add_effect(holding(x_s), False)
    stack.add_effect(clear(y_s), False)
    
    # Action unstack
    unstack = InstantaneousAction('unstack', OrderedDict([('x', Block), ('y', Block)]))
    x_u = unstack.parameter('x')
    y_u = unstack.parameter('y')
    unstack.add_precondition(on(x_u, y_u))
    unstack.add_precondition(clear(x_u))
    unstack.add_precondition(handempty)
    unstack.add_effect(holding(x_u), True)
    unstack.add_effect(clear(y_u), True)
    unstack.add_effect(on(x_u, y_u), False)
    unstack.add_effect(clear(x_u), False)
    unstack.add_effect(handempty, False)
    
    # Creation du probleme
    blocks_problem = Problem('blocks-tower')
    
    # Objets
    a = Object('a', Block)
    b = Object('b', Block)
    c = Object('c', Block)
    blocks_problem.add_objects([a, b, c])
    
    # Etat initial
    blocks_problem.set_initial_value(ontable(a), True)
    blocks_problem.set_initial_value(ontable(b), True)
    blocks_problem.set_initial_value(ontable(c), True)
    blocks_problem.set_initial_value(clear(a), True)
    blocks_problem.set_initial_value(clear(b), True)
    blocks_problem.set_initial_value(clear(c), True)
    blocks_problem.set_initial_value(handempty, True)
    
    # But : a sur b, b sur c
    blocks_problem.add_goal(on(a, b))
    blocks_problem.add_goal(on(b, c))
    
    # Ajout des actions
    blocks_problem.add_action(pick_up)
    blocks_problem.add_action(put_down)
    blocks_problem.add_action(stack)
    blocks_problem.add_action(unstack)
    
    print("Probleme Block World cree avec unified-planning")
    print(f"Objets: {[o.name for o in blocks_problem.all_objects]}")
    print(f"Actions: {[a.name for a in blocks_problem.actions]}")
Probleme Block World cree avec unified-planning
Objets: ['a', 'b', 'c']
Actions: ['pick-up', 'put-down', 'stack', 'unstack']

Interpretation : Creation du problème Block World avec unified-planning

Sortie obtenue : Problème Block World défini avec l’API Python (sans fichiers PDDL).

Élément Implementation
Types UserType('Block')
Fluents on, ontable, clear, holding, handempty
Actions pick-up, put-down, stack, unstack
Objets a, b, c (3 blocs)
Etat initial tous sur table, bras vide
But a sur b, b sur c

API unified-planning vs PDDL :

Concept PDDL unified-planning
Type (:types block) UserType('Block')
Predicat (:predicates (on ?x ?y)) Fluent('on', BoolType(), x=Block, y=Block)
Action (:action pick ...) InstantaneousAction('pick', ...)
Preconditions :precondition (and ...) action.add_precondition(...)
Effets :effect (and ...) action.add_effect(...)

Note technique : L’avantage de l’API Python est la possibilite de generer des problemes dynamiquement. Par exemple, on peut créer 100 blocs avec une boucle for au lieu d’ecrire le PDDL a la main.

Resolution du problème Block World avec pyperplan et affichage du plan trouve, en verifiant que le planificateur produce une solution valide pour les deux instances.

if UP_AVAILABLE:
    from unified_planning.engines import PlanGenerationResultStatus
    
    try:
        with OneshotPlanner(name='pyperplan') as planner:
            print(f"Planificateur: {planner.name}")
            print("Resolution du probleme Block World...\n")
            
            result = planner.solve(blocks_problem)
            
            if result.status == PlanGenerationResultStatus.SOLVED_SATISFICING:
                print("Solution trouvee !")
                print("=" * 50)
                for i, action in enumerate(result.plan.actions):
                    params = ', '.join(str(p.object()) for p in action.actual_parameters)
                    print(f"  {i+1}. {action.action.name}({params})")
                print("=" * 50)
                print(f"Longueur du plan: {len(result.plan.actions)} actions")
            else:
                print(f"Statut: {result.status}")
    except Exception as e:
        print(f"Erreur: {e}")
        print("\nSolution theorique:")
        print("  1. pick-up(b)")
        print("  2. put-down(b) [sur c] -> stack(b, c)")
        print("  3. pick-up(a)")
        print("  4. stack(a, b)")
Planificateur: Pyperplan
Resolution du probleme Block World...

Solution trouvee !
==================================================
  1. pick-up(b)
  2. stack(b, c)
  3. pick-up(a)
  4. stack(a, b)
==================================================
Longueur du plan: 4 actions

Interpretation du plan Block World

Analyse de la solution :

Étape Action Effet sur l’etat
0 (initial) a,b,c sur table
1 pick-up(b) holding(b)
2 stack(b,c) b sur c
3 pick-up(a) holding(a)
4 stack(a,b) a sur b (but atteint)

Note : Le plan optimal a 4 actions. La construction se fait de bas en haut (d’abord b sur c, puis a sur b).


3. Domaine Logistics - Transport multi-agent

Le domaine Logistics modelise le transport de colis entre lieux avec différents types de vehicules. C’est un benchmark classique plus realiste que Block World.

3.1 Description du domaine

Éléments du monde : - Colis (packages) : Objets a transporter - Vehicules : Camions (routes) et avions (aeroports) - Lieux : Positions avec connexions

Hiérarchie des types :

object
  |-- location (place, city)
  |-- vehicle (truck, airplane)
  |-- package

3.2 Domaine PDDL Logistics complet

# Domaine PDDL - Logistics complet
LOGISTICS_DOMAIN = """
(define (domain logistics)
  (:requirements :strips :typing)
  (:types 
    location - object
    city place - location
    vehicle - object
    truck airplane - vehicle
    package - object
  )
  
  (:predicates
    ;; Position des objets
    (at ?p - package ?l - location)
    (in ?p - package ?v - vehicle)
    (at-vehicle ?v - vehicle ?l - location)
    
    ;; Connexions
    (in-city ?l - place ?c - city)
    
    ;; Aeroports
    (airport ?l - location)
  )
  
  ;; Action : Charger un colis
  (:action load
    :parameters (?p - package ?v - vehicle ?l - location)
    :precondition (and (at ?p ?l) (at-vehicle ?v ?l))
    :effect (and 
              (in ?p ?v) 
              (not (at ?p ?l)))
  )
  
  ;; Action : Decharger un colis
  (:action unload
    :parameters (?p - package ?v - vehicle ?l - location)
    :precondition (and (in ?p ?v) (at-vehicle ?v ?l))
    :effect (and 
              (at ?p ?l) 
              (not (in ?p ?v)))
  )
  
  ;; Action : Conduire un camion
  (:action drive
    :parameters (?t - truck ?from ?to - place)
    :precondition (and 
                    (at-vehicle ?t ?from) 
                    (in-city ?from ?c) 
                    (in-city ?to ?c))
    :effect (and 
              (at-vehicle ?t ?to) 
              (not (at-vehicle ?t ?from)))
  )
  
  ;; Action : Faire voler un avion
  (:action fly
    :parameters (?a - airplane ?from ?to - location)
    :precondition (and 
                    (at-vehicle ?a ?from) 
                    (airport ?from) 
                    (airport ?to))
    :effect (and 
              (at-vehicle ?a ?to) 
              (not (at-vehicle ?a ?from)))
  )
)
"""

print("Domaine PDDL Logistics defini")
print("Actions: load, unload, drive, fly")
Domaine PDDL Logistics defini
Actions: load, unload, drive, fly

Interpretation : Domaine Logistics complet

Sortie obtenue : Definition PDDL du domaine Logistics avec 4 actions.

Élément Description
Types location (place, city), vehicle (truck, airplane), package
Hiérarchie city/place heritent de location, truck/airplane heritent de vehicle
Predicats at, in, at-vehicle, in-city, airport
Actions load, unload, drive, fly

Actions detaillees :

Action Rôle Contraintes spécifiques
load Charger colis dans vehicule Colis et vehicule au même lieu
unload Decharger colis Colis dans vehicule, vehicule au lieu
drive Deplacer camion Lieux dans la même ville (in-city)
fly Deplacer avion Lieux sont des aeroports (airport)

Comparaison avec Block World :

Aspect Block World Logistics
Entites Blocs, bras, table Colis, vehicules, lieux
Actions Manipulation (pick, put, stack) Transport (load, drive, fly)
Contraintes Bras vide, bloc libre Vehicule au lieu, capacite
Complexite O(n²) O(n × m × l)

Note technique : La hiérarchie de types est cruciale dans Logistics. truck et airplane heritent de vehicle, donc toutes les actions sur vehicle s’appliquent aux deux. Les contraintes in-city et airport restreignent les mouvements.

3.3 Problème Logistics multi-ville

# Probleme Logistics avec 2 villes
LOGISTICS_PROBLEM = """
(define (problem logistics-2cities)
  (:domain logistics)
  
  (:objects
    ;; Villes
    paris lyon - city
    
    ;; Lieux
    paris-airport paris-center - place
    lyon-airport lyon-center - place
    
    ;; Vehicules
    truck1 - truck
    plane1 - airplane
    
    ;; Colis
    pkg1 pkg2 - package
  )
  
  (:init
    ;; Appartenance aux villes
    (in-city paris-airport paris)
    (in-city paris-center paris)
    (in-city lyon-airport lyon)
    (in-city lyon-center lyon)
    
    ;; Aeroports
    (airport paris-airport)
    (airport lyon-airport)
    
    ;; Positions initiales
    (at pkg1 paris-center)
    (at pkg2 lyon-center)
    (at-vehicle truck1 paris-center)
    (at-vehicle plane1 paris-airport)
  )
  
  (:goal (and
    ;; pkg1 doit aller a lyon-center
    (at pkg1 lyon-center)
    ;; pkg2 doit aller a paris-center
    (at pkg2 paris-center)
  ))
)
"""

print("Probleme 'logistics-2cities' : Echange de colis entre Paris et Lyon")
print("\nScenario:")
print("  pkg1: Paris-center -> Lyon-center")
print("  pkg2: Lyon-center -> Paris-center")
print("\nVehicules disponibles:")
print("  truck1: camion a Paris-center")
print("  plane1: avion a Paris-airport")
Probleme 'logistics-2cities' : Echange de colis entre Paris et Lyon

Scenario:
  pkg1: Paris-center -> Lyon-center
  pkg2: Lyon-center -> Paris-center

Vehicules disponibles:
  truck1: camion a Paris-center
  plane1: avion a Paris-airport

Interpretation : Problème Logistics multi-ville

Sortie obtenue : Problème PDDL definissant un echange de colis entre Paris et Lyon.

Élément Description
Villes Paris (airport + center), Lyon (airport + center)
Vehicules truck1 (a Paris-center), plane1 (a Paris-airport)
Colis pkg1 (Paris-center -> Lyon-center), pkg2 (Lyon-center -> Paris-center)

Structure des transports :

Paris          Lyon
--------       --------
center  <->  airport  <->  airport  <->  center
(truck1)      (plane1)

Plan necessaire (pour pkg1) : 1. load(pkg1, truck1, paris-center) - charger le colis dans le camion 2. drive(truck1, paris-center, paris-airport) - aller a l’aeroport 3. unload(pkg1, truck1, paris-airport) - decharger du camion 4. load(pkg1, plane1, paris-airport) - charger dans l’avion 5. fly(plane1, paris-airport, lyon-airport) - voler vers Lyon 6. unload(pkg1, plane1, lyon-airport) - decharger de l’avion 7. load(pkg1, truck2, lyon-airport) - charger dans le camion lyonnais 8. drive(truck2, lyon-airport, lyon-center) - aller au centre 9. unload(pkg1, truck2, lyon-center) - decharger au centre

Note pedagogique : Ce problème illustre la coordination multi-modale (camion + avion) et le transfert de colis entre vehicules. C’est un modèle simplifie de la logistique reelle.

3.4 Resolution avec unified-planning

if UP_AVAILABLE:
    from collections import OrderedDict
    
    # Definition du domaine Logistics
    Location = UserType('Location')
    City = UserType('City', father=Location)
    Place = UserType('Place', father=Location)
    Vehicle = UserType('Vehicle')
    Truck = UserType('Truck', father=Vehicle)
    Airplane = UserType('Airplane', father=Vehicle)
    Package = UserType('Package')
    
    # Predicats - IMPORTANT: utiliser OrderedDict pour le signature (API v1.3+)
    at_pkg = Fluent('at', BoolType(), OrderedDict([('p', Package), ('l', Location)]))
    in_pkg = Fluent('in', BoolType(), OrderedDict([('p', Package), ('v', Vehicle)]))
    at_veh = Fluent('at-vehicle', BoolType(), OrderedDict([('v', Vehicle), ('l', Location)]))
    # Note: in-city requires specific city objects, not used in simplified model
    is_airport = Fluent('airport', BoolType(), OrderedDict([('l', Location)]))
    
    # Variables
    p = Variable('p', Package)
    v = Variable('v', Vehicle)
    l = Variable('l', Location)
    
    # Actions
    load = InstantaneousAction('load', OrderedDict([('p', Package), ('v', Vehicle), ('l', Location)]))
    p_l = load.parameter('p')
    v_l = load.parameter('v')
    l_l = load.parameter('l')
    load.add_precondition(at_pkg(p_l, l_l))
    load.add_precondition(at_veh(v_l, l_l))
    load.add_effect(in_pkg(p_l, v_l), True)
    load.add_effect(at_pkg(p_l, l_l), False)
    
    unload = InstantaneousAction('unload', OrderedDict([('p', Package), ('v', Vehicle), ('l', Location)]))
    p_u = unload.parameter('p')
    v_u = unload.parameter('v')
    l_u = unload.parameter('l')
    unload.add_precondition(in_pkg(p_u, v_u))
    unload.add_precondition(at_veh(v_u, l_u))
    unload.add_effect(at_pkg(p_u, l_u), True)
    unload.add_effect(in_pkg(p_u, v_u), False)
    
    # Drive action - simplified without city constraint
    drive = InstantaneousAction('drive', OrderedDict([('v', Vehicle), ('from', Location), ('to', Location)]))
    v_d = drive.parameter('v')
    from_d = drive.parameter('from')
    to_d = drive.parameter('to')
    drive.add_precondition(at_veh(v_d, from_d))
    drive.add_effect(at_veh(v_d, to_d), True)
    drive.add_effect(at_veh(v_d, from_d), False)
    
    # Fly action
    fly = InstantaneousAction('fly', OrderedDict([('a', Airplane), ('from', Location), ('to', Location)]))
    a_f = fly.parameter('a')
    from_f = fly.parameter('from')
    to_f = fly.parameter('to')
    fly.add_precondition(at_veh(a_f, from_f))
    fly.add_precondition(is_airport(from_f))
    fly.add_precondition(is_airport(to_f))
    fly.add_effect(at_veh(a_f, to_f), True)
    fly.add_effect(at_veh(a_f, from_f), False)
    
    print("Actions Logistics definies: load, unload, drive, fly")
Actions Logistics definies: load, unload, drive, fly

Interpretation : Actions Logistics avec unified-planning

Sortie obtenue : 4 actions définies : load, unload, drive, fly.

Action Preconditions Effets
load package et vehicle au même lieu package -> in vehicle, not at package
unload package in vehicle, vehicle au lieu package at lieu, not in vehicle
drive vehicle au lieu de depart vehicle au lieu d’arrivee
fly vehicle est airplane, aeroports airplane au lieu d’arrivee

Modelisation des contraintes : - Load : Le colis et le vehicule doivent etre au même endroit (at_pkg et at_veh sur le même lieu) - Unload : Le colis doit etre dans le vehicule, le vehicule au lieu de dechargement - Drive : Deplacement simple (sans contrainte de ville dans la version simplifiee) - Fly : Necessite que les lieux soient des aeroports (is_airport)

Note technique : Dans la version complete PDDL, drive a une contrainte in-city (les deux lieux doivent etre dans la même ville). Ici, nous avons simplifie en supprimant cette contrainte pour se concentrer sur l’essentiel du transport.

Creation du problème Logistics concret avec les objets (colis, vehicules, locations), l’etat initial et le but, en utilisant l’API unified-planning.

if UP_AVAILABLE:
    from collections import OrderedDict
    
    # Creation du probleme simplifie
    logistics_problem = Problem('logistics-simple')
    
    # Objets
    depot = Object('depot', Location)
    warehouse = Object('warehouse', Location)
    store = Object('store', Location)
    box1 = Object('box1', Package)
    box2 = Object('box2', Package)
    truck1 = Object('truck1', Truck)
    
    logistics_problem.add_objects([depot, warehouse, store, box1, box2, truck1])
    
    # Etat initial
    logistics_problem.set_initial_value(at_pkg(box1, depot), True)
    logistics_problem.set_initial_value(at_pkg(box2, depot), True)
    logistics_problem.set_initial_value(at_veh(truck1, depot), True)
    
    # But
    logistics_problem.add_goal(at_pkg(box1, store))
    logistics_problem.add_goal(at_pkg(box2, warehouse))
    
    # Ajout des actions (version simplifiee sans types hierarchiques)
    logistics_problem.add_action(load)
    logistics_problem.add_action(unload)
    logistics_problem.add_action(drive)
    
    print("Probleme Logistics simplifie cree")
Probleme Logistics simplifie cree

Interpretation : Problème Logistics avec unified-planning

Sortie obtenue : Problème Logistics simplifie créé avec l’API Python unified-planning.

Élément Valeur
Objets 3 locations, 2 packages, 1 truck
Actions load, unload, drive
Etat initial box1, box2 au depot, truck1 au depot
But box1 au store, box2 au warehouse

Simplification par rapport au PDDL complet : - Suppression de la hiérarchie de types (City, Place) - Suppression des contraintes in-city (connectivite universelle) - Un seul vehicule au lieu de plusieurs (truck + airplane)

Planification necessaire : 1. Charger box1 et box2 (un par un ou ensemble si capacite le permet) 2. Deplacer truck vers les destinations 3. Decharger aux bons endroits

Note technique : La version simplifiee permet de tester le concept de Logistics sans la complexite des types hiérarchiques de PDDL. Le problème complet avec avions et camions necessiterait des actions différentes pour drive (route) et fly (aeroport).

Lancement du planificateur sur le problème Logistics et affichage du plan genere avec les actions de chargement, deplacement et dechargement des colis.

if UP_AVAILABLE:
    try:
        with OneshotPlanner(name='pyperplan') as planner:
            print(f"Planificateur: {planner.name}")
            print("Resolution du probleme Logistics...\n")
            
            result = planner.solve(logistics_problem)
            
            if result.status == PlanGenerationResultStatus.SOLVED_SATISFICING:
                print("Solution trouvee !")
                print("=" * 50)
                for i, action in enumerate(result.plan.actions):
                    params = ', '.join(str(p.object()) for p in action.actual_parameters)
                    print(f"  {i+1}. {action.action.name}({params})")
                print("=" * 50)
                print(f"Longueur du plan: {len(result.plan.actions)} actions")
            else:
                print(f"Statut: {result.status}")
    except Exception as e:
        print(f"Note: {e}")
        print("\nSolution theorique pour le probleme complet:")
        print("  1. load(pkg1, truck1, paris-center)")
        print("  2. drive(truck1, paris-center, paris-airport)")
        print("  3. unload(pkg1, truck1, paris-airport)")
        print("  4. load(pkg1, plane1, paris-airport)")
        print("  5. fly(plane1, paris-airport, lyon-airport)")
        print("  6. unload(pkg1, plane1, lyon-airport)")
        print("  7. load(pkg1, truck2, lyon-airport)")
        print("  8. drive(truck2, lyon-airport, lyon-center)")
        print("  9. unload(pkg1, truck2, lyon-center)")
        print("  (Similaire pour pkg2 dans l'autre sens)")
Planificateur: Pyperplan
Resolution du probleme Logistics...

Solution trouvee !
==================================================
  1. load(box1, truck1, depot)
  2. load(box2, truck1, depot)
  3. drive(truck1, depot, warehouse)
  4. unload(box2, truck1, warehouse)
  5. drive(truck1, warehouse, store)
  6. unload(box1, truck1, store)
==================================================
Longueur du plan: 6 actions

Interpretation du plan Logistics

Analyse de la solution :

Le plan trouve par pyperplan comporte 6 actions pour livrer les deux colis (box1 vers store, box2 vers warehouse) depuis le depot, avec un seul camion (truck1) :

Étape Action Effet sur l’etat
1-2 load(box1), load(box2) les deux colis charges dans le camion au depot
3 drive(depot -> warehouse) le camion arrive au warehouse
4 unload(box2) box2 livre au warehouse
5 drive(warehouse -> store) le camion arrive au store
6 unload(box1) box1 livre au store (but atteint)

Note : Avec un seul vehicule, les deux livraisons sont necessairement séquentielles (le camion effectue depot -> warehouse -> store dans cette exécution). Le plan est optimal en nombre d’actions : les deux colis sont charges des le depart, aucun retour au depot. L’ordre exact des destinations peut varier d’une exécution a l’autre (pyperplan est non déterministe), mais la longueur optimale de 6 actions est invariante. La version complete du domaine Logistics ajoute plusieurs camions, des avions et la notion de villes, ce qui rendrait une partie des transferts parallelisables ; ce notebook utilise un modèle simplifie pour garder la sortie lisible.


4. Autres domaines classiques

En plus de Block World et Logistics, plusieurs autres domaines sont etudies en planification classique.

4.1 Domaine Gripper

Un robot avec deux pinces doit deplacer des balles entre deux pieces.

# Domaine PDDL - Gripper (extrait)
GRIPPER_DOMAIN = """
(define (domain gripper)
  (:requirements :strips :typing)
  (:types room ball gripper)
  
  (:predicates
    (at-robby ?r - room)        ;; position du robot
    (at ?b - ball ?r - room)    ;; position d'une balle
    (free ?g - gripper)         ;; pince libre
    (carry ?b - ball ?g - gripper)  ;; balle tenue
  )
  
  (:action move
    :parameters (?from ?to - room)
    :precondition (at-robby ?from)
    :effect (and (at-robby ?to) (not (at-robby ?from)))
  )
  
  (:action pick
    :parameters (?b - ball ?r - room ?g - gripper)
    :precondition (and (at ?b ?r) (at-robby ?r) (free ?g))
    :effect (and (carry ?b ?g) (not (at ?b ?r)) (not (free ?g)))
  )
  
  (:action drop
    :parameters (?b - ball ?r - room ?g - gripper)
    :precondition (and (carry ?b ?g) (at-robby ?r))
    :effect (and (at ?b ?r) (free ?g) (not (carry ?b ?g)))
  )
)
"""

print("Domaine Gripper : Robot avec 2 pinces")
print("Caracteristique : Le robot peut porter 2 balles a la fois !")
Domaine Gripper : Robot avec 2 pinces
Caracteristique : Le robot peut porter 2 balles a la fois !

Interpretation : Domaine Gripper

Sortie obtenue : Definition PDDL d’un robot avec deux pinces deplacant des balles entre deux pieces.

Élément Description
Types room (2 pieces), ball (balles), gripper (2 pinces)
Predicats at-robby, at, free, carry
Actions move (deplacer robot), pick (prendre balle), drop (poser balle)

Capacite unique : Le robot a 2 pinces, donc peut porter 2 balles simultanement.

Avantages du parallelisme : - Une main peut prendre une balle pendant que l’autre tient déjà une balle - Cela reduit le nombre de deplacements necessaires

Comparaison Block World vs Gripper :

Aspect Block World Gripper
Entite manipulante 1 bras (1 bloc a la fois) 1 robot (2 balles possibles)
Espacement Table infinie 2 pieces seulement
Capacite 1 objet 2 objets (pinces)
  • Necessite de modeliser les deux pinces explicitement
  • Offre un bon exercice sur les contraintes de ressource (les pinces sont des ressources limitees)

Exemple guide : Resolution complete du domaine Gripper

Nous allons maintenant illustrer le workflow complet de resolution d’un problème de planification en appliquant le domaine Gripper avec unified-planning. Cet exemple montre les 5 étapes fondamentales :

  1. Définir les types (Room, Ball, Gripper)
  2. Définir les fluents (predicats dynamiques : position du robot, des balles, etat des pinces)
  3. Définir les actions (move, pick, drop avec preconditions et effets)
  4. Créer le problème (objets, etat initial, but)
  5. Resoudre avec un planificateur et analyser le plan obtenu

Scénario : Un robot dans room_a doit deplacer 3 balles vers room_b en utilisant ses 2 pinces (left et right). L’avantage du domaine Gripper est que le robot peut porter 2 balles simultanement, ce qui reduit le nombre de deplacements necessaires par rapport a un transport séquentiel.

# --- Exemple guide : Resolution complete du domaine Gripper ---
# Cet exemple montre le workflow complet : modelisation unified-planning -> resolution -> analyse du plan
from collections import OrderedDict
from unified_planning.shortcuts import *
from unified_planning.engines import PlanGenerationResultStatus
from unified_planning.environment import get_environment
get_environment().credits_stream = None  # silencer les credits pyperplan (sortie pedagogique propre)

# 1. Definition des types
Room = UserType('Room')
Ball = UserType('Ball')
Gripper = UserType('Gripper')

# 2. Definition des fluents (predicats dynamiques)
at_robby = Fluent('at-robby', BoolType(), OrderedDict([('r', Room)]))
at_ball = Fluent('at', BoolType(), OrderedDict([('b', Ball), ('r', Room)]))
free_gripper = Fluent('free', BoolType(), OrderedDict([('g', Gripper)]))
carry_ball = Fluent('carry', BoolType(), OrderedDict([('b', Ball), ('g', Gripper)]))

# 3. Definition des actions
# Action move : deplacer le robot d'une piece a l'autre
move = InstantaneousAction('move', OrderedDict([('from', Room), ('to', Room)]))
from_m = move.parameter('from')
to_m = move.parameter('to')
move.add_precondition(at_robby(from_m))
move.add_effect(at_robby(to_m), True)
move.add_effect(at_robby(from_m), False)

# Action pick : ramasser une balle avec une pince
pick = InstantaneousAction('pick', OrderedDict([('b', Ball), ('r', Room), ('g', Gripper)]))
b_p = pick.parameter('b')
r_p = pick.parameter('r')
g_p = pick.parameter('g')
pick.add_precondition(at_ball(b_p, r_p))
pick.add_precondition(at_robby(r_p))
pick.add_precondition(free_gripper(g_p))
pick.add_effect(carry_ball(b_p, g_p), True)
pick.add_effect(at_ball(b_p, r_p), False)
pick.add_effect(free_gripper(g_p), False)

# Action drop : poser une balle
drop = InstantaneousAction('drop', OrderedDict([('b', Ball), ('r', Room), ('g', Gripper)]))
b_d = drop.parameter('b')
r_d = drop.parameter('r')
g_d = drop.parameter('g')
drop.add_precondition(carry_ball(b_d, g_d))
drop.add_precondition(at_robby(r_d))
drop.add_effect(at_ball(b_d, r_d), True)
drop.add_effect(free_gripper(g_d), True)
drop.add_effect(carry_ball(b_d, g_d), False)

# 4. Creation du probleme
gripper_problem = Problem('gripper-2rooms-3balls')

# Objets : 2 pieces, 3 balles, 2 pinces (left et right)
room_a = Object('room_a', Room)
room_b = Object('room_b', Room)
ball1 = Object('ball1', Ball)
ball2 = Object('ball2', Ball)
ball3 = Object('ball3', Ball)
left = Object('left', Gripper)
right = Object('right', Gripper)
gripper_problem.add_objects([room_a, room_b, ball1, ball2, ball3, left, right])

# Etat initial : toutes les balles dans room_a, robot dans room_a, pinces libres
gripper_problem.set_initial_value(at_robby(room_a), True)
for ball in [ball1, ball2, ball3]:
    gripper_problem.set_initial_value(at_ball(ball, room_a), True)
for gripper_obj in [left, right]:
    gripper_problem.set_initial_value(free_gripper(gripper_obj), True)

# But : deplacer toutes les balles vers room_b
for ball in [ball1, ball2, ball3]:
    gripper_problem.add_goal(at_ball(ball, room_b))

# Ajout des actions au probleme
gripper_problem.add_action(move)
gripper_problem.add_action(pick)
gripper_problem.add_action(drop)

print("Probleme Gripper cree avec unified-planning")
print(f"  Pieces: room_a, room_b")
print(f"  Balles: ball1, ball2, ball3 (toutes dans room_a)")
print(f"  Pinces: left, right (toutes libres)")
print(f"  But: deplacer les 3 balles vers room_b")
print(f"  Actions disponibles: {[a.name for a in gripper_problem.actions]}")

# 5. Resolution avec pyperplan
print("\nResolution en cours...")
try:
    with OneshotPlanner(name='pyperplan') as planner:
        result = planner.solve(gripper_problem)

        if result.status == PlanGenerationResultStatus.SOLVED_SATISFICING:
            plan = result.plan
            print(f"\nPlan trouve ({len(plan.actions)} actions) :")
            print("=" * 55)
            for i, action in enumerate(plan.actions):
                params = ', '.join(str(p.object()) for p in action.actual_parameters)
                print(f"  {i+1:2d}. {action.action.name}({params})")
            print("=" * 55)

            # Analyse du plan : compter les deplacements
            move_count = sum(1 for a in plan.actions if a.action.name == 'move')
            pick_count = sum(1 for a in plan.actions if a.action.name == 'pick')
            drop_count = sum(1 for a in plan.actions if a.action.name == 'drop')
            print(f"\nStatistiques du plan :")
            print(f"  Deplacements (move) : {move_count}")
            print(f"  Ramassages  (pick)  : {pick_count}")
            print(f"  Depots      (drop)  : {drop_count}")
            # Pinces effectivement utilisees (3eme param de pick/drop) + trajets productifs
            grippers = sorted({str(a.actual_parameters[2].object()) for a in plan.actions if a.action.name in ('pick', 'drop')})
            trips_to_b = sum(1 for a in plan.actions if a.action.name == 'move' and str(a.actual_parameters[1].object()) == 'room_b')
            print(f"\nAnalyse du plan trouve :")
            print(f"  Pinces utilisees    : {len(grippers)} ({', '.join(grippers)})")
            print(f"  Trajets vers room_b : {trips_to_b}")
            if len(grippers) == 1:
                print(f"\npyperplan est un planificateur SATISFICING (non optimal) : il renvoie ici un")
                print(f"plan sous-optimal valide utilisant 1 seule pince, 1 balle par trajet")
                print(f"({trips_to_b} allers + {move_count - trips_to_b} retours = {move_count} moves, {len(plan.actions)} actions).")
                print(f"\nLe plan OPTIMAL exploiterait les 2 pinces (left+right) : 2 balles au premier")
                print(f"trajet puis 1 au second = 9 actions (3 pick + 3 drop + 3 move). Cf. le plan")
                print(f"theorique ci-dessus (affiche si pyperplan est indisponible).")
            else:
                print(f"\nLe plan utilise les 2 pinces (left+right) pour porter 2 balles par trajet :")
                print(f"2 trajets aller-retour (2 balles puis 1 balle) = {len(plan.actions)} actions, strategie optimale.")
        else:
            print(f"Statut: {result.status}")
except Exception as e:
    print(f"Planificateur non disponible ({e})")
    print("\nPlan theorique (optimal) :")
    print("  1. pick(ball1, room_a, left)   - prendre ball1 avec pince gauche")
    print("  2. pick(ball2, room_a, right)  - prendre ball2 avec pince droite")
    print("  3. move(room_a, room_b)        - aller en room_b")
    print("  4. drop(ball1, room_b, left)   - poser ball1")
    print("  5. drop(ball2, room_b, right)  - poser ball2")
    print("  6. move(room_b, room_a)        - retour en room_a")
    print("  7. pick(ball3, room_a, left)   - prendre ball3")
    print("  8. move(room_a, room_b)        - aller en room_b")
    print("  9. drop(ball3, room_b, left)   - poser ball3")
    print("  => 9 actions au total (3 pick + 3 drop + 3 move)")
Probleme Gripper cree avec unified-planning
  Pieces: room_a, room_b
  Balles: ball1, ball2, ball3 (toutes dans room_a)
  Pinces: left, right (toutes libres)
  But: deplacer les 3 balles vers room_b
  Actions disponibles: ['move', 'pick', 'drop']

Resolution en cours...

Plan trouve (11 actions) :
=======================================================
   1. pick(ball1, room_a, left)
   2. move(room_a, room_b)
   3. drop(ball1, room_b, left)
   4. move(room_b, room_a)
   5. pick(ball3, room_a, left)
   6. move(room_a, room_b)
   7. drop(ball3, room_b, left)
   8. move(room_b, room_a)
   9. pick(ball2, room_a, left)
  10. move(room_a, room_b)
  11. drop(ball2, room_b, left)
=======================================================

Statistiques du plan :
  Deplacements (move) : 5
  Ramassages  (pick)  : 3
  Depots      (drop)  : 3

Analyse du plan trouve :
  Pinces utilisees    : 1 (left)
  Trajets vers room_b : 3

pyperplan est un planificateur SATISFICING (non optimal) : il renvoie ici un
plan sous-optimal valide utilisant 1 seule pince, 1 balle par trajet
(3 allers + 2 retours = 5 moves, 11 actions).

Le plan OPTIMAL exploiterait les 2 pinces (left+right) : 2 balles au premier
trajet puis 1 au second = 9 actions (3 pick + 3 drop + 3 move). Cf. le plan
theorique ci-dessus (affiche si pyperplan est indisponible).

4.2 Domaine Ferry

Un ferry transporte des voitures entre deux rives.

# Domaine PDDL - Ferry
FERRY_DOMAIN = """
(define (domain ferry)
  (:requirements :strips :typing)
  (:types location car)
  
  (:predicates
    (at-ferry ?l - location)     ;; position du ferry
    (at-car ?c - car ?l - location)  ;; position d'une voiture
    (empty-ferry)                ;; ferry vide
    (on ?c - car)                ;; voiture sur le ferry
  )
  
  (:action board
    :parameters (?c - car ?l - location)
    :precondition (and (at-car ?c ?l) (at-ferry ?l) (empty-ferry))
    :effect (and (on ?c) (not (at-car ?c ?l)) (not (empty-ferry)))
  )
  
  (:action sail
    :parameters (?from ?to - location)
    :precondition (at-ferry ?from)
    :effect (and (at-ferry ?to) (not (at-ferry ?from)))
  )
  
  (:action debark
    :parameters (?c - car ?l - location)
    :precondition (and (on ?c) (at-ferry ?l))
    :effect (and (at-car ?c ?l) (empty-ferry) (not (on ?c)))
  )
)
"""

print("Domaine Ferry : Transport sequentiel")
print("Contrainte : Le ferry ne peut porter qu'une voiture a la fois")
Domaine Ferry : Transport sequentiel
Contrainte : Le ferry ne peut porter qu'une voiture a la fois

Interpretation : Domaine Ferry

Sortie obtenue : Definition PDDL d’un problème de transport séquentiel avec un ferry.

Élément Description
Types location (2 rives), car (voitures)
Predicats at-ferry, at-car, empty-ferry, on
Actions board (embarquer), sail (naviguer), debark (debarquer)

Contrainte principale : Le ferry ne peut porter qu’une voiture a la fois (empty-ferry).

Comparaison avec d’autres domaines de transport :

Domaine Vehicules Capacite Parallelisme
Ferry 1 seul 1 voiture Séquentiel
Gripper 1 robot 2 balles (2 pinces) Parallele (pinces)
Logistics Plusieurs Variable Multi-agent

Complexite : Bien que simple, ce domaine necessite O(n) actions pour n voitures (chaque voiture necessite 3 actions : board, sail, debark).

Note pedagogique : Ce domaine est utile pour enseigner les contraintes de capacite. Il montre aussi comment l’ordre des actions est critique (on ne peut pas debarquer si le ferry n’est pas au bon endroit).

4.3 Domaine Hanoi (Tours de Hanoi)

Le problème classique des tours de Hanoi.

# Domaine PDDL - Hanoi
HANOI_DOMAIN = """
(define (domain hanoi)
  (:requirements :strips :typing)
  (:types peg disk)
  
  (:predicates
    (on ?d1 ?d2 - disk)       ;; disque d1 sur d2
    (on-peg ?d - disk ?p - peg)  ;; disque sur le peg
    (clear ?d - disk)         ;; rien sur le disque
    (smaller ?d1 ?d2 - disk)  ;; d1 plus petit que d2
    (peg-clear ?p - peg)      ;; peg vide (ou plus grand disque)
  )
  
  (:action move
    :parameters (?d - disk ?from ?to - disk)
    :precondition (and 
                    (clear ?d) 
                    (clear ?to) 
                    (smaller ?d ?to) 
                    (on ?d ?from))
    :effect (and 
              (on ?d ?to) 
              (clear ?from) 
              (not (on ?d ?from)) 
              (not (clear ?to)))
  )
  
  (:action move-to-peg
    :parameters (?d - disk ?from - disk ?to - peg)
    :precondition (and (clear ?d) (on ?d ?from) (peg-clear ?to))
    :effect (and 
              (on-peg ?d ?to) 
              (clear ?from) 
              (not (on ?d ?from)))
  )
)
"""

print("Domaine Hanoi : Tours de Hanoi")
print("Complexite : 2^n - 1 mouvements pour n disques")
Domaine Hanoi : Tours de Hanoi
Complexite : 2^n - 1 mouvements pour n disques

Interpretation : Domaine Hanoi (Tours de Hanoi)

Sortie obtenue : Definition PDDL du problème classique des Tours de Hanoi.

Élément Description
Types peg (pilier), disk (disque)
Predicats on, on-peg, clear, smaller, peg-clear
Actions move (disque vers disque), move-to-peg (disque vers pilier)

Complexite du problème : - Nombre d’etats : O(3ⁿ) pour n disques - Longueur plan optimal : 2ⁿ - 1 mouvements - Exemple : 3 disques = 7 mouvements, 4 disques = 15 mouvements

Particularites : 1. La contrainte smaller (disque plus petit sur disque plus grand) est cruciale 2. Le predicat peg-clear indique qu’un pilier est libre (ou a un disque plus grand) 3. Ce problème illustre bien la recursion : deplacer n disques = deplacer n-1, puis le plus grand, puis n-1

Note pedagogique : Hanoi est souvent utilise pour tester la planification hiérarchique. La solution optimale suit un pattern récursif que les planificateurs classiques peinent a trouver sans heuristiques adaptees.

4.4 Comparaison des domaines

Domaine Type Complexite etats Complexite plans Patron principal
Block World Manipulation O(n^2) O(n^2) Empiler/Deseempiler
Gripper Transport O(n^2) O(n) Collecter/Deposer
Ferry Transport O(n) O(n) Sequencement
Logistics Multi-agent O(n^m) Variable Coordination
Hanoi Puzzle O(3^n) O(2^n) Recursivite

5. Analyse comparative des domaines

Cette section analyse les caractéristiques structurelles des différents domaines.

# Analyse comparative des domaines
domains_analysis = {
    'Block World': {
        'objects_type': 'Homogene (blocs)',
        'actions_per_object': 4,
        'state_size_formula': 'O(n^2)',
        'plan_length_typical': 'O(n^2)',
        'key_challenge': 'Ordre des empilages'
    },
    'Gripper': {
        'objects_type': 'Mixte (balles, pinces)',
        'actions_per_object': 3,
        'state_size_formula': 'O(n x k)',
        'plan_length_typical': 'O(n/k)',
        'key_challenge': 'Utilisation des pinces'
    },
    'Logistics': {
        'objects_type': 'Heterogene (colis, vehicules, lieux)',
        'actions_per_object': 4,
        'state_size_formula': 'O(n x m x l)',
        'plan_length_typical': 'Variable',
        'key_challenge': 'Coordination multi-agent'
    },
    'Ferry': {
        'objects_type': 'Mixte (voitures, lieu)',
        'actions_per_object': 3,
        'state_size_formula': 'O(n)',
        'plan_length_typical': 'O(n)',
        'key_challenge': 'Sequencement optimal'
    },
    'Hanoi': {
        'objects_type': 'Homogene (disques, pegs)',
        'actions_per_object': 2,
        'state_size_formula': 'O(3^n)',
        'plan_length_typical': 'O(2^n)',
        'key_challenge': 'Recursivite'
    }
}

print("Analyse comparative des domaines classiques")
print("=" * 70)
for domain, props in domains_analysis.items():
    print(f"\n{domain}:")
    for key, value in props.items():
        print(f"  - {key}: {value}")
Analyse comparative des domaines classiques
======================================================================

Block World:
  - objects_type: Homogene (blocs)
  - actions_per_object: 4
  - state_size_formula: O(n^2)
  - plan_length_typical: O(n^2)
  - key_challenge: Ordre des empilages

Gripper:
  - objects_type: Mixte (balles, pinces)
  - actions_per_object: 3
  - state_size_formula: O(n x k)
  - plan_length_typical: O(n/k)
  - key_challenge: Utilisation des pinces

Logistics:
  - objects_type: Heterogene (colis, vehicules, lieux)
  - actions_per_object: 4
  - state_size_formula: O(n x m x l)
  - plan_length_typical: Variable
  - key_challenge: Coordination multi-agent

Ferry:
  - objects_type: Mixte (voitures, lieu)
  - actions_per_object: 3
  - state_size_formula: O(n)
  - plan_length_typical: O(n)
  - key_challenge: Sequencement optimal

Hanoi:
  - objects_type: Homogene (disques, pegs)
  - actions_per_object: 2
  - state_size_formula: O(3^n)
  - plan_length_typical: O(2^n)
  - key_challenge: Recursivite

Interpretation : Analyse comparative des domaines classiques

Sortie obtenue : Tableau detaille de 5 domaines avec leurs caractéristiques structurelles.

Domaine Objets Etat Plan typique Challenge principal
Block World Homogènes O(n²) O(n²) Ordre d’empilage
Gripper Mixtes O(n×k) O(n/k) Utilisation des pinces
Logistics Hétérogènes O(n×m×l) Variable Coordination multi-agent
Ferry Mixtes O(n) O(n) Séquencement optimal
Hanoi Homogènes O(3ⁿ) O(2ⁿ) Récursivité

Observations cles : 1. Heterogeneite des objets : Logistics est plus complexe car il gère colis, véhicules et lieux simultanément 2. Explosion combinatoire : Hanoi a une complexité exponentielle (3ⁿ états, 2ⁿ-1 mouvements) 3. Parallelisme : Gripper peut bénéficier du parallelisme (2 pinces = 2 balles simultanées) 4. Coordination : Logistics necessite de coordonner plusieurs véhicules, ce qui augmente la complexite

Note technique : La complexite de l’espace d’états n’est pas le seul facteur de performance. La structure du problème (existence de sous-buts, decomposition naturelle) influence aussi l’efficacite des heuristiques.

5.1 Efficacite des heuristiques par domaine

# Tableau d'efficacite des heuristiques (donnees empiriques)
heuristic_effectiveness = {
    'Domain': ['Block World', 'Gripper', 'Logistics', 'Ferry', 'Hanoi'],
    'h_add': ['Moyenne', 'Bonne', 'Bonne', 'Moyenne', 'Faible'],
    'h_max': ['Faible', 'Moyenne', 'Moyenne', 'Faible', 'Faible'],
    'h_FF': ['Bonne', 'Tres bonne', 'Tres bonne', 'Bonne', 'Moyenne'],
    'LM-cut': ['Tres bonne', 'Bonne', 'Bonne', 'Bonne', 'Bonne'],
    'Blind': ['Faible', 'Faible', 'Faible', 'Faible', 'Faible']
}

# Affichage
print("Efficacite des heuristiques par domaine")
print("-" * 60)
print(f"{'Domaine':<15} {'h_add':<10} {'h_max':<10} {'h_FF':<12} {'LM-cut':<10}")
print("-" * 60)
for i, domain in enumerate(heuristic_effectiveness['Domain']):
    print(f"{domain:<15} {heuristic_effectiveness['h_add'][i]:<10} "
          f"{heuristic_effectiveness['h_max'][i]:<10} "
          f"{heuristic_effectiveness['h_FF'][i]:<12} "
          f"{heuristic_effectiveness['LM-cut'][i]:<10}")
print("-" * 60)
print("\nRemarque : h_FF est generalement le meilleur compromis vitesse/qualite")
Efficacite des heuristiques par domaine
------------------------------------------------------------
Domaine         h_add      h_max      h_FF         LM-cut    
------------------------------------------------------------
Block World     Moyenne    Faible     Bonne        Tres bonne
Gripper         Bonne      Moyenne    Tres bonne   Bonne     
Logistics       Bonne      Moyenne    Tres bonne   Bonne     
Ferry           Moyenne    Faible     Bonne        Bonne     
Hanoi           Faible     Faible     Moyenne      Bonne     
------------------------------------------------------------

Remarque : h_FF est generalement le meilleur compromis vitesse/qualite

Interpretation : Efficacite des heuristiques par domaine

Sortie obtenue : un tableau qualitatif (etiquettes d’efficacite) croisant 4 heuristiques (h_add, h_max, h_FF, LM-cut) avec les 5 domaines :

Domaine h_add h_max h_FF LM-cut
Block World Moyenne Faible Bonne Très bonne
Gripper Bonne Moyenne Très bonne Bonne
Logistics Bonne Moyenne Très bonne Bonne
Ferry Moyenne Faible Bonne Bonne
Hanoi Faible Faible Moyenne Bonne

(Les etiquettes Très bonne > Bonne > Moyenne > Faible sont purement qualitatives ; le code ne mesure aucun ratio numérique de performances.)

Analyse des résultats (lecture du tableau ci-dessus) : 1. h_FF obtient Très bonne sur les domaines de transport (Gripper, Logistics) et reste Bonne ailleurs - meilleur compromis, comme le souligne la remarque du code 2. LM-cut est la plus reguliere : Bonne ou Très bonne sur tous les domaines, y compris Hanoi 3. Hanoi est le domaine le plus resistant : Faible/Moyenne pour h_add, h_max et h_FF (structure récursive difficile a guider) 4. Blind (sans heuristique, Faible partout dans les données internes du code) n’est pas affichée dans la sortie mais est rappelée ici pour memoire : sans heuristique, l’exploration est exhaustive et inefficace

Note technique : FF (Forward Forward) utilise une extraction de sous-buts relaxés qui fonctionne particulièrement bien quand le problème a beaucoup de sous-objectifs indépendants (comme déplacer plusieurs colis).

5.2 Explosion combinatoire

La taille de l’espace d’etats croit rapidement :

Domaine n=3 n=5 n=10 n=20
Block World 13 841 58.9M astronomique
Gripper 64 1.6K 1.7M 1.8B
Logistics 81 3.9K 1.0G astronomique
Hanoi 27 243 59K 3.5B

Conclusion : Les heuristiques sont essentielles pour explorer efficacement ces espaces.


6. Conseils pratiques de modelisation

Cette section presente les bonnes pratiques pour ecrire des domaines PDDL efficaces.

6.1 Bonnes pratiques

# Bonnes pratiques de modelisation PDDL
best_practices = [
    {
        'titre': 'Utiliser le typage',
        'description': 'Reduit l espace de recherche en contraignant les parametres',
        'exemple': '(:types truck - vehicle) plutot que verifier dans les preconditions'
    },
    {
        'titre': 'Minimiser les predicats',
        'description': 'Chaque predicat augmente la taille de l etat',
        'exemple': 'Deriver (empty ?v) de (not (exists (?p) (in ?p ?v))) plutot que stocker'
    },
    {
        'titre': 'Actions atomiques',
        'description': 'Une action = une transformation elementaire',
        'exemple': 'load et unload separement plutot que move-with-cargo'
    },
    {
        'titre': 'Nommage explicite',
        'description': 'Facilite le debug et la lecture des plans',
        'exemple': 'drive-truck plutot que move2'
    },
    {
        'titre': 'Tester avec petits instances',
        'description': 'Valider le domaine sur 2-3 objets avant de scaler',
        'exemple': 'Commencer avec 2 blocs, 2 lieux avant 20 blocs'
    }
]

print("Bonnes pratiques de modelisation PDDL")
print("=" * 70)
for i, practice in enumerate(best_practices, 1):
    print(f"\n{i}. {practice['titre']}")
    print(f"   Description: {practice['description']}")
    print(f"   Exemple: {practice['exemple']}")
Bonnes pratiques de modelisation PDDL
======================================================================

1. Utiliser le typage
   Description: Reduit l espace de recherche en contraignant les parametres
   Exemple: (:types truck - vehicle) plutot que verifier dans les preconditions

2. Minimiser les predicats
   Description: Chaque predicat augmente la taille de l etat
   Exemple: Deriver (empty ?v) de (not (exists (?p) (in ?p ?v))) plutot que stocker

3. Actions atomiques
   Description: Une action = une transformation elementaire
   Exemple: load et unload separement plutot que move-with-cargo

4. Nommage explicite
   Description: Facilite le debug et la lecture des plans
   Exemple: drive-truck plutot que move2

5. Tester avec petits instances
   Description: Valider le domaine sur 2-3 objets avant de scaler
   Exemple: Commencer avec 2 blocs, 2 lieux avant 20 blocs

Interpretation : Bonnes pratiques de modelisation PDDL

Sortie obtenue : 5 recommandations structurees avec descriptions et exemples concrets.

Pratique Impact Difficulté
Utiliser le typage Réduit espace de recherche Faible
Minimiser les prédicats Etat plus compact Moyenne
Actions atomiques Compréhension plus claire Faible
Nommage explicite Debug plus facile Faible
Tester progressivement Détection précoce des bugs Moyenne

Points cles : 1. Le typage est particulièrement puissant en PDDL pour contraindre les paramètres d’actions 2. Les prédicats dérivés (comme empty calculé depuis les objets transportés) réduisent la taille de l’état 3. L’approche “tester petit” est essentielle : valider avec 2 blocs avant 20

Note technique : En PDDL, les types forment une hiérarchie. Un type fils hérite des prédicats du type père. Par exemple, si truck et airplane héritent de vehicle, une action sur vehicle s’applique aux deux.

6.2 Erreurs courantes

# Erreurs courantes a eviter
common_mistakes = [
    {
        'erreur': 'Oublier de supprimer les preconditions dans les effets',
        'exemple': '(precondition (at ?x ?l)) -> oublier (not (at ?x ?l)) dans effects',
        'consequence': 'L objet existe a deux endroits simultanement'
    },
    {
        'erreur': 'Conditions cycliques',
        'exemple': 'A requires B, B requires A',
        'consequence': 'Aucun plan possible (deadlock)'
    },
    {
        'erreur': 'Types mal hierarchises',
        'exemple': 'truck et car sans parent commun',
        'consequence': 'Actions dupliquees pour chaque type'
    },
    {
        'erreur': 'Buts inatteignables',
        'exemple': '(goal (and (on a b) (on b a)))',
        'consequence': 'Planificateur tourne indefiniment'
    },
    {
        'erreur': 'Predicats derives non maintenus',
        'exemple': 'clear(x) oublie quand on pose quelque chose sur x',
        'consequence': 'Etats incoherents, plans invalides'
    }
]

print("Erreurs courantes en modelisation PDDL")
print("=" * 70)
for i, mistake in enumerate(common_mistakes, 1):
    print(f"\n{i}. {mistake['erreur']}")
    print(f"   Exemple: {mistake['exemple']}")
    print(f"   Consequence: {mistake['consequence']}")
Erreurs courantes en modelisation PDDL
======================================================================

1. Oublier de supprimer les preconditions dans les effets
   Exemple: (precondition (at ?x ?l)) -> oublier (not (at ?x ?l)) dans effects
   Consequence: L objet existe a deux endroits simultanement

2. Conditions cycliques
   Exemple: A requires B, B requires A
   Consequence: Aucun plan possible (deadlock)

3. Types mal hierarchises
   Exemple: truck et car sans parent commun
   Consequence: Actions dupliquees pour chaque type

4. Buts inatteignables
   Exemple: (goal (and (on a b) (on b a)))
   Consequence: Planificateur tourne indefiniment

5. Predicats derives non maintenus
   Exemple: clear(x) oublie quand on pose quelque chose sur x
   Consequence: Etats incoherents, plans invalides

Interpretation : Erreurs courantes en modelisation

Sortie obtenue : Liste structuree des 5 erreurs les plus frequentes en PDDL avec exemples concrets et consequences.

Erreur Impact Frequence
Preconditions non supprimees Etats incohérents Eleve
Conditions cycliques Deadlock Moyenne
Types mal hiérarchisés Code dupliqué Eleve
Buts inatteignables Planificateur tourne indéfiniment Faible
Prédicats derives non maintenus Incohérence sémantique Eleve

Points cles : 1. Ces erreurs proviennent souvent d’une mauvaise compréhension du modèle STRIPS 2. Elles sont difficiles à détecter car le planificateur ne signale pas toujours l’erreur explicitement 3. La validation systématique avec des petits problèmes est essentielle

Note technique : Utiliser un validateur PDDL (comme celui de Valencia) avant de soumettre un problème à un planificateur permet d’économiser beaucoup de temps de debug.

6.3 Debugging PDDL

# Outils et techniques de debugging PDDL
debug_techniques = [
    "1. Valider la syntaxe avec un validateur PDDL (ex: validator de Valencia)",
    "2. Executer le plan a la main sur l etat initial",
    "3. Verifier que chaque but est accessible individuellement",
    "4. Reduire le probleme (moins d objets) pour isoler le bug",
    "5. Afficher l etat apres chaque action (mode debug)",
    "6. Utiliser un planificateur avec messages d erreur detailles",
    "7. Comparer avec un domaine similaire connu fonctionnel"
]

print("Techniques de debugging PDDL")
print("=" * 50)
for technique in debug_techniques:
    print(f"  {technique}")
Techniques de debugging PDDL
==================================================
  1. Valider la syntaxe avec un validateur PDDL (ex: validator de Valencia)
  2. Executer le plan a la main sur l etat initial
  3. Verifier que chaque but est accessible individuellement
  4. Reduire le probleme (moins d objets) pour isoler le bug
  5. Afficher l etat apres chaque action (mode debug)
  6. Utiliser un planificateur avec messages d erreur detailles
  7. Comparer avec un domaine similaire connu fonctionnel

Exercice : Resoudre le Ferry et analyser le plan

Le domaine Ferry (section 4.2) transporte des voitures entre deux rives avec un ferry.

Objectif : Créez un problème Ferry avec 3 voitures (au lieu de 2), resolvez-le avec unified_planning, et comparez la longueur du plan.

Indices : - # Étape 1 : Reprendre le domaine PDDL Ferry (cellule plus haut) tel quel - # Étape 2 : Ajouter une 3e voiture dans :objects et :init - # Étape 3 : Le but est que toutes les voitures soient sur la rive B - # Étape 4 : Afficher la longueur du plan et comparer avec le problème a 2 voitures

if UP_AVAILABLE:
    from unified_planning.shortcuts import *
    from unified_planning.engines import PlanGenerationResultStatus

    # TODO etudiant: definissez un probleme Ferry a 3 voitures
    # en utilisant le domaine PDDL Ferry de la section 4.2
    ferry_problem_3cars = None  # TODO etudiant

    print(f"Probleme Ferry 3 voitures: {ferry_problem_3cars}")
    print("Exercice a completer")
Probleme Ferry 3 voitures: None
Exercice a completer

Exercice : Modelisation d’un domaine de planification personnalise

Contexte : Entrepot automatise

Un entrepot utilise un robot pour deplacer des colis entre des emplacements. Le robot peut porter un seul colis a la fois.

Objets : - Emplacements : depot, zone_a, zone_b, zone_c - Colis : colis1, colis2 - Robot : robot1

Etat initial : - robot1 est au depot, les bras vides - colis1 est en zone_a, colis2 est en zone_b

But : Les deux colis doivent etre au depot

Actions PDDL a implementer : 1. deplacer(robot, de, vers) : deplace le robot d’un emplacement a un autre 2. charger(robot, colis, lieu) : le robot prend un colis (robot et colis au même endroit, robot libre) 3. decharger(robot, colis, lieu) : le robot depose un colis (robot porte le colis)

Objectifs

  1. Implementer le domaine avec unified_planning (fluents, actions, preconditions, effets)
  2. Resoudre le problème avec Fast Downward
  3. Afficher le plan trouve et calculer le nombre d’étapes
  4. (Bonus) Ajouter une contrainte de carburant (numérique) et resoudre avec un solver qui supporte les fluents numériques

Indices :

  • Fluents booléens : at(robot, lieu), holding(robot, colis), at_box(colis, lieu), free(robot)
  • up.BoolType() pour les fluents booléens, up.UserType("Robot") pour les types
  • Pattern de resolution : with up.OneshotPlanner(name='fast-downward') as planner: result = planner.solve(problem)
# --- Exercice : Domaine Entrepot Automatise ---
# Modelisez le domaine decrit ci-dessus (robots, boxes, locations) avec
# unified-planning : types, fluents, objets, actions (deplacer / charger /
# decharger), etat initial et but. Resolvez ensuite avec un OneshotPlanner.
import unified_planning as up
from unified_planning.shortcuts import *

# A vous de jouer.
print("Exercice a completer")
Exercice a completer

7. Resume et conclusions

7.1 Points cles du notebook

Concept Description
Block World Domaine de reference pour la manipulation, complexite O(n^2)
Logistics Transport multi-agent, coordination camions/avions
Gripper Robot avec pinces multiples, parallelisme possible
Domaines classiques Benchmarks standardises pour comparer les planificateurs
Modelisation PDDL Typage, actions atomiques, predicats minimaux

7.2 Taxonomie des domaines

Domaines de planification
|-- Manipulation
|   |-- Block World (empiler/deseempiler)
|   |-- Hanoi (disques sur pegs)
|-- Transport
|   |-- Gripper (robot + pinces)
|   |-- Ferry (ferry + voitures)
|   |-- Logistics (multi-vehicules)
|-- Gestion
|   |-- Depots (entrepot + grues)
|   |-- Satellite (observations)
|-- Puzzle
    |-- Hanoi
    |-- 8-puzzle

7.3 Prochaines étapes

Dans le notebook Planners-7-OR-Tools, nous explorerons : - La programmation par contraintes (CP-SAT) - L’optimisation combinatoire - La comparaison avec la planification classique


Ressources


Notebook suivant : Planners-7-OR-Tools


Exercice de synthese : Concevoir votre propre domaine de planification

Après avoir explore Block World, Transport et Entrepot, concevez un domaine original.

Contraintes

  • Au moins 2 types d’objets et 3 actions avec preconditions et effets
  • Le problème doit etre soluble avec un plan non-trivial (au minimum 3 actions)
  • Choisissez un domaine du monde reel (cuisine, logistique, jeu de plateau…)

Étapes : 1. Définir les types, fluents et actions avec unified_planning.shortcuts 2. Créer un problème avec etat initial et objectif 3. Resoudre avec OneshotPlanner et afficher le plan 4. (Bonus) Comparer avec un solveur alternatif

# TODO etudiant : concevoir votre propre domaine de planification
from unified_planning.shortcuts import *

# Demarche : (1) types, (2) fluents / predicats, (3) actions (preconditions +
# effets), (4) probleme (objets, etat initial, but), (5) resolution avec un
# OneshotPlanner. Choisissez un domaine original (ni Block World ni Transport).

print("Exercice a completer")
Exercice a completer
Retour au sommet