Greffe 2 de la strate 7 (#13568). La strate 7 dit que les agents « élargissent leur espace ». La formule est nébuleuse — ce notebook la rend mesurable en nommant quatre objets, et en montrant que chacun des quatre peut bouger indépendamment des trois autres. Le banc consomme deux instruments déjà livrés par la série Planners :
Planners-5c — le différentiel d’atteignabilité : ce que l’ajout d’une primitive rend possible (bras élargissement) ;
Planners-10b — le LLM comme réducteur d’espace (bras restriction, moteur aicpp).
Aucun des deux ne nomme le quadruplet complet ; aucun ne mesure le noyau (ce que l’ajout rend inatteignable). C’est ce que cette greffe ajoute — sur le même domaine que Planners-5c, en consommant son socle sans le re-dériver.
Statut épistémique — Sans verdict à ce jour : aucune ligne de la matrice de dissociations ne concerne ce notebook ; son statut épistémique sera porté par la matrice le cas échéant.
Le quadruplet, nommé
Objet
Définition opérationnelle
Se mesure par
\(A\) — espace ambiant
l’ensemble des états atteignables compte tenu du vocabulaire de primitives \(V\)
la part de \(A\) que le solveur explore effectivement sous son budget \(B\) (expansions)
BFS tronqué à \(B\) expansions
\(r\) — liberté résiduelle
le rapport \(\lvert F_B\rvert / \lvert A\rvert\) : combien de l’ambiant reste jouable à budget donné
ratio direct
\(\pi\) — politique
l’ordre d’expansion des successeurs (ici : déterministe, paramétrable)
coût en expansions de la première découverte d’un but
Pourquoi quatre objets : dire « l’agent a progressé » sans dire lequel des quatre a bougé ne dit rien. Élargir \(A\) sans vérifier \(F\) peut contracter\(r\) (l’espace croît plus vite que le budget) ; changer \(\pi\) améliore la navigation sans toucher ni \(A\) ni \(F\). Les trois cas de figure de la strate 7 (contraction certifiée / navigation améliorée / élargissement réel) deviennent trois signatures différentes sur le quadruplet — et la greffe 1 de #13565 (Beck-Fiala) fournira le témoin de contraction certifiée quand il sera livré.
Le socle STRIPS — copie de Planners-5c, garanties du lake
Le moteur est celui de Planners-5c (copie fidèle, non modifiée). Il consomme le lake planning_leansans le re-dériver : les trois résultats certifiés sans sorry qui fondent la lecture sont cités en garantie dans la section suivante.
from collections import dequeclass Operateur:"""Operateur STRIPS : nom, preconditions, ajouts, suppressions (cout 1)."""def__init__(self, nom, precond, ajouts, dels):self.nom, self.precond, self.ajouts, self.dels = nom, frozenset(precond), frozenset(ajouts), frozenset(dels)def applicable(self, etat):returnself.precond <= etatdef__repr__(self):returnself.nomdef successeurs(etat, ops, cle_tri=lambda o: o.nom):"""Dynamique reelle (step) : successeurs dans l'ordre fixe par la politique pi (cle_tri).""" out = []for op insorted(ops, key=cle_tri):if op.applicable(etat): out.append((op, (etat | op.ajouts) - op.dels))return outdef bfs_budget(initial, ops, budget=None, cle_tri=lambda o: o.nom):"""BFS (ordre pi) avec budget d'expansions ; None = illimite. Retourne (dist, expansions_totales).""" dist = {initial: 0}file= deque([initial]) expansions =0whilefileand (budget isNoneor expansions < budget): s =file.popleft() expansions +=1for op, t in successeurs(s, ops, cle_tri):if t notin dist: dist[t] = dist[s] +1file.append(t)return dist, expansionsprint("Moteur STRIPS charge (copie Planners-5c) : step + BFS budgete + politique parametrable.")
Ce que le lake garantit — et pourquoi il n’est pas re-dérivé ici
Le lake planning_lean (consommé par Planners-5b/5c) prouve, sans sorry :
Résultat certifié
Lieu
Ce qu’il garantit ici
\(run\ \pi\ s \subseteq runR\ \pi\ s\) (toute trajectoire réelle est une trajectoire relâchée)
Planning/Relaxation.lean:56
la relaxation ne peut pas « sauver » un but : si \(g\) est inatteignable en relâché, il l’est réellement
\(step \subseteq stepR\)
Planning/Strips.lean:58
le lemme central de la monotonie relâchée
admissibilité \(h^+ \le h^*\)
Planning/Admissibility.lean:41
les minorants heuristiques ne mentent jamais sur la distance
L’interdit de #13568 est explicite : réimplémenter un espace d’états, une relaxation ou une heuristique dans ict/ alors que planning_lean les prouve. Ce notebook n’en réimplémente aucune : le BFS est l’instrument de mesure de \(A\) et \(F\) (c’est le rôle qu’il a déjà dans Planners-5c), les garanties sont citées, pas re-prouvées.
Le domaine : deux rives, un gouffre, un radeau qui n’existe pas encore
Domaine de Planners-5c, copie fidèle : deux rives (colonnes 0-2 et 4-5), un gouffre (colonne 3, infranchissable), une planchette, une clé, un trésor. Trois buts : \(g_1\) (balise sur la rive droite), \(g_2\) (détenir le trésor), \(g_3\) (détenir la clé). Dans le vocabulaire de base \(A_t\), la rive droite est inatteignable — donc \(g_1\) et \(g_2\) aussi.
COLS_GAUCHE = [0, 1, 2]COLS_DROITE = [4, 5]RANGS = [0, 1, 2]CASES =sorted({(c, r) for c in COLS_GAUCHE + COLS_DROITE for r in RANGS}) # colonne 3 = gouffredef prop(c, r):returnf"at-x{c}-y{r}"INITIAL =frozenset({prop(0, 0), "plank-at-x1-y1", "key-at-x2-y0", "treasure-at-x4-y1"})def ops_de_base(): ops = []for (c, r) in CASES:for (dc, dr, nom_dir) in [(1, 0, "est"), (-1, 0, "ouest"), (0, 1, "nord"), (0, -1, "sud")]: cible = (c + dc, r + dr)if cible in CASES: ops.append(Operateur(f"move-{nom_dir}-x{c}-y{r}", {prop(c, r)}, {prop(*cible)}, {prop(c, r)}))for objet, cell in [("plank", (1, 1)), ("key", (2, 0)), ("treasure", (4, 1))]: c, r = cell ops.append(Operateur(f"pickup-{objet}", {prop(c, r), f"{objet}-at-x{c}-y{r}"}, {f"holding-{objet}"}, {f"{objet}-at-x{c}-y{r}"}))return opsBUTS = {"g1: balise (5,2)": frozenset({prop(5, 2)}),"g2: detenir le tresor": frozenset({"holding-treasure"}),"g3: detenir la cle": frozenset({"holding-key"})}print(f"Cases existantes : {len(CASES)} (colonne 3 = gouffre)")print(f"Buts : {list(BUTS)}")
Première mesure : l’espace ambiant \(A(V)\) en entier (BFS exhaustif), et les buts atteignables dans chacun.
atteint = {}for nom, V in VOCABS.items(): dist, exp = bfs_budget(INITIAL, V) atteint[nom] = dist buts_ok = [b for b, cond in BUTS.items() ifany(cond <= s for s in dist)]print(f"{nom:16s} |A| = {len(dist):4d} etats buts atteignables : {len(buts_ok)}/3 {sorted(b[:2] for b in buts_ok)}")
Lecture : le différentiel non nul (critère 2 de #13568)
+raft fait passer de 1/3 à 3/3 buts : \(g_1\) (balise) et \(g_2\) (trésor) sont nouvellement atteignables. C’est l’élargissement réel au niveau du système — exactement le différentiel de Planners-5c, désormais exprimé sur le quadruplet : \(A\) croît (36 → 60 états) et les buts bougent.
Contrôle négatif : la primitive stérile (critère 3)
Un différentiel toujours positif ne mesure pas, il décore. Le contrôle obligatoire : un ajout de primitive qui n’élargit rien. discard-plank (jeter la planchette) est le candidat — dans \(A_t\) il n’y a rien à traverser, la planchette ne sert à rien, la détruire ne peut pas aider.
base_buts = {b for b, cond in BUTS.items() ifany(cond <= s for s in atteint["A_t"])}for nom in VOCABS:if nom =="A_t":continue ok = {b for b, cond in BUTS.items() ifany(cond <= s for s in atteint[nom])} nouveaux, perdus = ok - base_buts, base_buts - okprint(f"A_t -> {nom:16s} nouveaux = {sorted(n[:2] for n in nouveaux) if nouveaux else'AUCUN'} "f"perdus = {sorted(p[:2] for p in perdus) if perdus else'aucun'}")
Le résultat central du contrôle négatif : A_t -> A_t+discard fait croître \(|A|\) de 36 à 54 états (+50 %) sans rendre aucun but nouveau. Une mesure naïve de « progrès » par croissance d’espace (\(|\Delta A| > 0\)) validerait cet ajout — à tort. Le différentiel qui mesure est \(\Delta(\text{buts atteignables})\), et lui reste exactement nul.
Verbatim de la sortie : « A_t -> A_t+discard nouveaux = AUCUN perdus = aucun » puis « A_t -> A_t1 (+raft) nouveaux = [‘g1’, ‘g2’] perdus = aucun » — discard n’ajoute aucun but, raft en ajoute deux sans en perdre. La nuance est dans l’écart entre les deux colonnes : discard ne change pas les buts, mais il change bien \(|A|\) (36 → 54 états) — l’opérateur ajoute des états, pas des objectifs atteignables.
La leçon pour la strate 7 : « élargir son espace » n’a de valeur que relativement à ce que l’agent veut atteindre. Un espace qui croît de 50 % sans ouvrir aucun but est du bruit, pas du progrès.
\(F\) et \(r\) : le solveur a un budget
Le système n’est pas le solveur. Un BFS réel s’arrête à \(B\) expansions — c’est le mur de Planners-10b (BUDGET_NODES = 15 000). Ici \(F_B\) = découvert à budget \(B\), \(r = |F_B|/|A|\).
for B in (30, 60, 120):print(f"budget B = {B} :")for nom, V in VOCABS.items(): dist, _ = bfs_budget(INITIAL, V, B) r =len(dist) /len(atteint[nom]) buts_ok = [b[:2] for b, cond in BUTS.items() ifany(cond <= s for s in dist)]print(f" {nom:16s} |F_B| = {len(dist):4d} r = {r:5.1%} buts dans F : {buts_ok}")
budget B = 30 :
A_t |F_B| = 34 r = 94.4% buts dans F : ['g3']
A_t+discard |F_B| = 38 r = 70.4% buts dans F : ['g3']
A_t1 (+raft) |F_B| = 40 r = 66.7% buts dans F : ['g1', 'g2', 'g3']
A_t1+discard |F_B| = 42 r = 53.8% buts dans F : ['g2', 'g3']
budget B = 60 :
A_t |F_B| = 36 r = 100.0% buts dans F : ['g3']
A_t+discard |F_B| = 54 r = 100.0% buts dans F : ['g3']
A_t1 (+raft) |F_B| = 60 r = 100.0% buts dans F : ['g1', 'g2', 'g3']
A_t1+discard |F_B| = 70 r = 89.7% buts dans F : ['g1', 'g2', 'g3']
budget B = 120 :
A_t |F_B| = 36 r = 100.0% buts dans F : ['g3']
A_t+discard |F_B| = 54 r = 100.0% buts dans F : ['g3']
A_t1 (+raft) |F_B| = 60 r = 100.0% buts dans F : ['g1', 'g2', 'g3']
A_t1+discard |F_B| = 78 r = 100.0% buts dans F : ['g1', 'g2', 'g3']
Lecture : l’élargissement non contrôlé contracte la liberté résiduelle
À budget \(B=30\) : \(r\) chute de 94,4 % (\(A_t\)) à 53,8 % (\(A_{t1}\)+discard). Le prix se paie deux fois : \(r\) descend de 94,4 % (\(A_t\)) à 66,7 % (\(A_{t1}\)), puis à 53,8 % (\(A_{t1}\)+discard). Le tableau réserve un piège : \(A_{t1}\)+discard affiche « buts dans F : [‘g2’, ‘g3’] » — à budget serré, discard fait perdre \(g_1\) du front atteignable, alors même qu’il n’ajoutait aucun but. À \(B=120\), \(r\) remonte à 100,0 % partout : la contraction n’est pas une propriété du système, c’est une propriété du budget.
Le quadruplet isole le paradoxe : élargir \(A\) (le système peut plus) peut réduire\(r\) (le solveur explore une part plus petite de ce qui existe). L’espace croît plus vite que le budget — c’est la face sombre de l’élargissement, invisible si on ne regarde que \(A\).
Le noyau : ce que la primitive ajoutée rend inatteignable (critère 4)
L’obligation méthodologique de #13565/#13568 : ne jamais conclure « élargissement » sans chercher le noyau — ce que l’ajout rend inatteignable. Deux niveaux à distinguer honnêtement :
Noyau système : dans STRIPS, les actions sont optionnelles — un ancien plan reste valide après l’ajout (monotonie de l’atteignabilité). Le noyau système est donc vide par construction : la mesure ci-dessus le confirme (perdus = aucun partout).
Noyau à budget fixe : le solveur, lui, n’a pas cette clémence. À \(B\) constant, élargir le vocabulaire dilue le budget sur plus de successeurs — des buts qui tenaient dans \(F\) peuvent en sortir.
def expansions_pour_but(initial, V, but, cle_tri=lambda o: o.nom):"""Expansions cumulees pour la premiere decouverte du but (BFS ordre pi).""" dist = {initial: 0}file= deque([initial]) expansions =0whilefile: s =file.popleft() expansions +=1if but <= s:return expansions, dist[s]for op, t in successeurs(s, V, cle_tri):if t notin dist: dist[t] = dist[s] +1file.append(t)returnNone, Noneprint("Expansions necessaires a la premiere decouverte de chaque but :")for b_nom, b_cond insorted(BUTS.items()): ligne =" ".join(f"{nom}={expansions_pour_but(INITIAL, V, b_cond)[0]}"for nom, V in VOCABS.items())print(f" {b_nom:24s}{ligne}")
Expansions necessaires a la premiere decouverte de chaque but :
g1: balise (5,2) A_t=None A_t+discard=None A_t1 (+raft)=39 A_t1+discard=50
g2: detenir le tresor A_t=None A_t+discard=None A_t1 (+raft)=33 A_t1+discard=42
g3: detenir la cle A_t=8 A_t+discard=8 A_t1 (+raft)=8 A_t1+discard=8
Lecture : le noyau non vide vit à budget fixe
Verbatim des expansions à la première découverte : \(g_1\) « A_t=None A_t+discard=None A_t1 (+raft)=39 A_t1+discard=50 », \(g_2\) « None None 33 42 », \(g_3\) « 8 8 8 8 » — \(g_1\) et \(g_2\) exigent le raft (indisponible → None), \(g_3\) est atteint partout en 8 expansions. La nécessité du raft se lit sur deux lignes, l’indifférence sur la troisième.
\(g_1\) : inatteignable dans \(A_t\) et \(A_t\)+discard (le gouffre), découvert à 39 expansions dans \(A_{t1}\) — mais à 50 dans \(A_{t1}\)+discard ;
\(g_2\) : 33 dans \(A_{t1}\) contre 42 dans \(A_{t1}\)+discard.
Lecture directe sur le tableau \(F/r\) à \(B=30\) : \(g_1\) est dans\(F(A_{t1})\) mais hors\(F(A_{t1}\)+discard\()\) — l’ajout de discard (une primitive qui ne sert à rien) a rendu \(g_1\) inatteignable au solveur à budget constant. C’est le noyau : pas au système (monotonie), mais au solveur (dilution). Les deux niveaux sont mesurés et distingués — c’est exactement ce que le critère 4 exige.
Cas 3 — navigation : seul \(\pi\) bouge (le bras Planners-10b)
Troisième signature : \(A\) et \(F\)inchangés, seule la politique change. C’est la position de Planners-10b : le LLM ne résout pas, il réduit l’espace exploré en premier (quelles primitives essayer d’abord) ; le solving reste déterministe. Ici, la version jouet : réordonner l’expansion — pickup d’abord, ou raft d’abord, contre l’ordre lexicographique.
pi_lex =lambda o: o.nompi_pickup =lambda o: (0if o.nom.startswith("pickup") else1, o.nom)pi_raft =lambda o: (0if o.nom.startswith("raft") else1, o.nom)print("A_t1, trois politiques -- A et F inchanges par construction (meme dynamique) :")print(f"{'but':24s}{'pi_lex':>8s}{'pi_pickup':>10s}{'pi_raft':>8s} h*")for b_nom, b_cond insorted(BUTS.items()): res = [expansions_pour_but(INITIAL, A_T1, b_cond, cle) for cle in (pi_lex, pi_pickup, pi_raft)]print(f" {b_nom:22s}{res[0][0]:>7d}{res[1][0]:>9d}{res[2][0]:>7d}{res[0][1]} (inchange)")
A_t1, trois politiques -- A et F inchanges par construction (meme dynamique) :
but pi_lex pi_pickup pi_raft h*
g1: balise (5,2) 39 39 39 7 (inchange)
g2: detenir le tresor 33 32 32 6 (inchange)
g3: detenir la cle 8 7 8 3 (inchange)
Lecture : trois signatures, un seul quadruplet
\(\pi_{pickup}\) améliore la découverte de \(g_2\) (33 → 32) et \(g_3\) (8 → 7) ; \(h^*\) est inchangé partout (la distance optimale ne dépend pas de l’ordre d’exploration — garanti par le lake : l’admissibilité de \(h^+\) ne connaît pas \(\pi\)). La table des trois cas de la strate 7, sur le même domaine :
« L’agent a progressé » ne dit rien tant que la ligne du tableau n’est pas nommée. C’est la réponse du quadruplet à la formule nébuleuse.
Synthèse — ce que la greffe ajoute aux deux bras
Planners-5c mesurait le différentiel d’atteignabilité mais n’avait pas le contrôle négatif exécuté (exercice 2 non résolu), ni le noyau, ni \(r\). Planners-10b montrait la réduction LLM sur un autre domaine (grilles ARC-like), sans lien avec l’élargissement. La greffe :
nomme le quadruplet \((A, F, r, \pi)\) — les quatre objets, mesurables séparément ;
exécute le contrôle négatif : +50 % d’états, 0 but nouveau — la croissance d’espace n’est pas le progrès ;
mesure le noyau à deux niveaux : vide au système (monotonie STRIPS, confirmée), non vide au solveur à budget ;
discrimine les trois cas de la strate 7 sur le même banc — le témoin de contraction certifiée (#13565, Beck-Fiala) viendra greffer le quatrième quand livré.
Limites
Le domaine est petit (36-78 états) : les effets de budget sont visibles à \(B \in [30, 120]\), pas à l’échelle du mur de Planners-10b (15 000 nœuds). La structure des trois signatures est identique ; les magnitudes ne se transfèrent pas linéairement.
La monotonie STRIPS (noyau système vide) est une propriété du cadre (actions optionnelles, sans contraintes globales) : un domaine à invariants (capacité, Beck-Fiala) peut avoir un noyau système non vide — c’est l’exercice 3.
\(\pi\) jouet : trois ordres statiques. Le réducteur de Planners-10b est par tâche (le LLM propose le sous-ensemble pertinent) — une politique dépendante de l’état, plus riche que nos clés de tri constantes.
Les proxys \(|\Delta A|\), \(r\) sont des bornes d’exploration, pas des prédicteurs de performance d’agent complet (pas de récompense, pas d’apprentissage ici).
Exercices
# Exercice 1 a completer : la primitive qui piege reellement.# Ajouter burn-plank (detruire la planchette AVANT pickup : etat-piege plus precoce# que discard -- preconditions {plank-at-x1-y1} sans holding), construire A_t1+burn,# mesurer : (a) |A| croit-il davantage qu'avec discard ? (b) exp(g1) et exp(g2) :# le noyau solveur croit-il ? (c) r a B=30 : conclusion sur la difference# entre "ajout inutile" et "ajout nuisible".print("Exercice a completer")resultat =None# TODO etudiant
Exercice a completer
Exercice 2 — La politique dégradante
Construire \(\pi_{degrade}\) qui fait pire que l’ordre lexicographique sur au moins un but (indice : mettre les move inutiles en tête, ou trier par nom décroissant). Mesurer la dégradation, et vérifier que \(h^*\) reste inchangé — la politique ne peut dégrader la découverte, jamais la distance.
# Exercice 2 a completer : pi_degrade = lambda o: ... ; mesurer# expansions_pour_but(INITIAL, A_T1, BUTS[...], pi_degrade) sur les 3 buts,# comparer a pi_lex, confirmer h* inchange.print("Exercice a completer")resultat =None# TODO etudiant
Exercice a completer
Exercice 3 — Le noyau système non vide : la contrainte d’invariant
Le noyau système est vide en STRIPS pur (actions optionnelles). Le rendre non vide : ajouter un invariant global — par exemple carry-capacity-1 (on ne peut tenir qu’un objet à la fois : chaque pickup-x a en précondition hand-free, chaque pickup-y supprime hand-free). Construire ce vocabulaire \(A_{t1}^{cap}\) et montrer qu’un but atteignable dans \(A_{t1}\) devient inatteignable : le noyau système, cette fois, est mesuré non vide. C’est le pont vers la contraction certifiée de #13565 (Beck-Fiala).
# Exercice 3 a completer : construire A_t1_cap (invariant hand-free sur les pickups),# mesurer les buts atteignables, identifier le but perdu vs A_t1 -- le noyau systeme.print("Exercice a completer")resultat =None# TODO etudiant