Cette 2eme case de la matrice inversee (docs/ict/dissociations-matrix.md, PR #9546 MERGED 2026-08-06) teste la prediction auto-referentielle :
Une boucle fermee p_hat -> action -> p_hat est stable sur un regime borne mais diverge (oscillation amplifiee) hors de ce regime.
La matrice ICT factorise la serie en 4 objets (s, q, pi, W) ; la matrice inversee (depuis #9533, chantier 3/3) designe chaque case vide comme une experience manquante. La case 1 (s ⟂ pi) a ete testee par po-2023 dans PR #9553 MERGED 2026-08-05 (verdict : FALSIFIED au niveau total, CONFIRMED au niveau decision). Cette PR teste la case 2 : la boucle auto-referentielle.
Prediction pre-enregistree (PR #9546, verrouillee avant test)
Pour un couplage lineaire x_{t+1} = a x_t + kappa * p_hat_t + b + epsilon_t ou l’action a_t = p_hat_t et p_hat_{t+1} = f_obs(x_t), le systeme lineaire resultant a un coefficient effectif a_eff = a + kappa * a_hat. Si |a_eff| < 1, le regime est borne ; sinon, il diverge.
Avec a = 0.95 et a_hat = 0.95 (predicteur exact), la frontiere predite est kappa_c = (1 - a) / a_hat ~= 0.053.
Null adversarial
Un delieur causal (kappa = 0, la prediction n’influence pas l’environnement) supprime la divergence meme quand le predicteur est interne. Si le delieur borne mais la boucle diverge, la causalite auto-referentielle tient.
Substrat
Animat scalaire (1-D), prediction purement observationnelle f_obs(x) = a_hat x avec a_hat = a (predicteur exact – on isole l’effet de la boucle de toute erreur d’estimation). Le couplage kappa est la seule variable manipulee. Numpy uniquement, CPU-only (conforme a la regle F : pas de GPU requis).
Grade C – credit temoin
Le hook grade C est active par ce test : Hofstadter strange loops (auto-reference comme bouclee etrange). Credit temoin rapporte a #8182 et mappe dans #9533 (ai-01, 2026-08-06). Le hook est un temoin de lecture, pas une these : le verdict experimental reste independant du temoin.
Plan du notebook
Cadrage et prediction verrouillee
Setup (imports, constantes, graines)
Sanity check (delieur borne, bouclee diverge a grand kappa)
Markdown transition – mecanique de la boucle
Substrat A : scan de stabilite R_T/R_0 vs kappa (5 graines)
Substrat B : delieur causal vs bouclee divergente
Substrat C : frontiere de stabilite observee vs predite
Substrat D : sensibilite a T (horizon) – non-trivialite
Synthese + verdict honnete a 2 niveaux
Verdict agrege + credit temoin 11+. Exercices (>=3) et conclusion
Statut épistémique — Testé (confirmé), case « p̂ auto-référent » : la boucle fermée p̂ → action → p̂ diverge au-delà d’une frontière de couplage κ_c, sur 5/5 graines. L’écart initial à la prédiction linéaire (0.080 mesuré contre ≈ 0.053) est diagnostiqué ARTEFACT_DE_MESURE : critère fini et arrondi de maille ; sur grille fine, la frontière retombe à 0.062, au voisinage de κ*_finite = 0.0611. Ce biais n’est donc pas une propriété de la boucle. Portée et détail dans la matrice de dissociations.
Acquis et parenté : second cas produit par le générateur de dissociations #9533 — se lit après le premier cas du même générateur, ICT-Dissociation-SaillancePregnance.
import sys, os# Le notebook vit dans ICT-Series/ ; le package ict est juste a cote.sys.path.insert(0, os.getcwd())import numpy as npfrom ict.phat_self_reference import ( KAPPA_C_PREDICTED, KAPPA_GRID, RATIO_DIVERGENT, RATIO_BORNE_HIGH, simulate_self_reference_loop, stability_scan, delieur_verdict, estimate_stability_boundary, predict_and_dissociate, run_full_protocol,)print("Python:", sys.version.split()[0])print("NumPy:", np.__version__)print("KAPPA_C_PREDICTED:", KAPPA_C_PREDICTED)print("KAPPA_GRID:", KAPPA_GRID)print("RATIO_DIVERGENT:", RATIO_DIVERGENT)print("RATIO_BORNE_HIGH:", RATIO_BORNE_HIGH)
Avant de lancer le scan complet, on verifie les deux regimes extremes :
Delieur (kappa = 0) : prediction n’influe pas sur l’environnement. Ratio R_T / R_0 doit rester sous RATIO_BORNE_HIGH = 2.
Bouclee amplifiee (kappa = 1.0) : prediction amplifiee sur l’environnement. Ratio doit depasser largement RATIO_DIVERGENT = 5.
rng_sanity = np.random.default_rng(42)sim_delieur = simulate_self_reference_loop(kappa=0.0, rng=rng_sanity)sim_bouclee = simulate_self_reference_loop(kappa=1.0, rng=rng_sanity)print(f"Delieur kappa=0.0 : ratio median = {float(np.median(sim_delieur['ratio'])):.3f}, max = {float(np.max(sim_delieur['ratio'])):.3f}")print(f"Bouclee kappa=1.0 : ratio median = {float(np.median(sim_bouclee['ratio'])):.3e}, min = {float(np.min(sim_bouclee['ratio'])):.3e}")assertfloat(np.max(sim_delieur['ratio'])) < RATIO_BORNE_HIGH, "Delieur devrait rester borne"assertfloat(np.min(sim_bouclee['ratio'])) >= RATIO_DIVERGENT, "Bouclee a kappa=1 devrait diverger"print("\nSanity check OK : delieur borne, bouclee diverge.")
Delieur kappa=0.0 : ratio median = 0.251, max = 0.251
Bouclee kappa=1.0 : ratio median = 4.438e+38, min = 4.438e+38
Sanity check OK : delieur borne, bouclee diverge.
Lecture du sanity check – deux regimes, 39 ordres de grandeur d’ecart
Le delieur (kappa = 0) reste confine : ratio median 0.251, max 0.251 – huit fois sous le seuil RATIO_BORNE_HIGH = 2. La bouclee (kappa = 1.0) explose a 4.438e+38. L’ecart entre les deux regimes ne vient ni du predicteur (identique et exact : a_hat = a = 0.95) ni du bruit (meme generateur initialise a la graine 42, donc meme loi des tirages) : il vient uniquement du couplage kappa. C’est la demonstration la plus economique du notebook – une seule variable manipulee, deux comportements qualitativement distincts. Un ratio qui passe de 0.251 a 4.4e+38 quand tout le reste est tenu constant, c’est la definition operationnelle d’une cause ici.
Mecanique de la boucle p_hat -> action -> p_hat
Trois equations definissent la dynamique :
Action EST la prediction : a_t = p_hat_t
Environnement avec couplage : x_{t+1} = a x_t + kappa * a_t + b + epsilon_t
Le couplage kappa dans (2) ferme la boucle. Pour kappa = 0, la prediction n’influence pas l’environnement (delieur causal), meme si la computation interne reste formellement en boucle.
Subtilite du design : c’est le feedback sur l’environnement qui compte, pas la recurrence du calcul de prediction. Le predicteur reste exact (a_hat = a) pour isoler l’effet de la boucle de toute erreur d’estimation – condition experimentale qui rend le test falsifiable.
Substrat A – scan de stabilite R_T / R_0 vs kappa
On balaye la grille KAPPA_GRID pour 5 graines (0, 1, 7, 42, 99) avec N_INIT = 30 conditions initiales par graine et HORIZON_T = 200 pas. Le ratio R_T / R_0 est la mesure de divergence (1 = stationnaire, > 5 = divergent).
Lecture du scan – la frontiere vit entre kappa = 0.06 et kappa = 0.08
La colonne R_med croit de facon monotone avec kappa : 0.261 (kappa = 0), 0.363 (0.02), 0.528 (0.04), 1.269 (0.05), 4.368 (0.06), puis 131.9 (0.08), 3.3e+03 (0.10), 6.8e+06 (0.15), 6.8e+09 (0.20), 1.3e+15 (0.30) et 4.2e+23 (0.50). Deux regimes nets se detachent : toutes les mailles <= 0.06 sont stables sur les 5 graines (True/False), toutes les mailles >= 0.08 sont divergentes sur les 5 graines (False/True) – aucune maille ne donne un verdict mixte. La transition saute la maille 0.06-0.08 : c’est precisement la que la theorie situe la frontiere (kappa_c = 0.0526). Noter l’echelle : entre 0.08 et 0.50, le ratio gagne environ 21 ordres de grandeur – la divergence est exponentielle, la ligne “stable/divergent” est le seul resume lisible d’une telle dynamique.
Substrat B – delieur causal vs bouclee divergente
Le delieur (kappa = 0) doit presenter un ratio tres inferieur a RATIO_DIVERGENT sur toutes les graines. Si oui, la dissociation tient : c’est bien le couplage boucle qui cause la divergence aux grands kappa.
v_deleur = delieur_verdict(scan)print(f"Delieur (kappa=0) ratio par graine : {v_deleur['delieur_ratio_per_seed']}")print(f"Delieur ratio max : {v_deleur['delieur_ratio_max']:.4f}")print(f"Delieur ratio mean : {v_deleur['delieur_ratio_mean']:.4f}")print(f"Delieur stable ? : {v_deleur['delieur_stable']}")
Delieur (kappa=0) ratio par graine : [0.20060011 0.22865424 0.28800586 0.25068229 0.33567173]
Delieur ratio max : 0.3357
Delieur ratio mean : 0.2607
Delieur stable ? : True
Lecture du delieur – marge confortable sur les 5 graines
Les ratios par graine : 0.201, 0.229, 0.288, 0.251, 0.336. Toutes sous 0.34, soit environ quinze fois sous le seuil divergent RATIO_DIVERGENT = 5, et six fois sous RATIO_BORNE_HIGH = 2. La dispersion inter-graines est etroite : le max 0.336 ne depasse la moyenne 0.261 que d’environ 29 %. Le verdict delieur_stable = True verifie la null adversarial du cadrage – quand la prediction n’influence pas l’environnement, aucune graine ne diverge, quel que soit le bruit tire. La comparaison avec la maille kappa = 0.08 du scan (131.9) donne la mesure brute de l’effet causal du couplage : meme environnement, meme famille de bruit, ratio median multiplie par environ 500 (0.261 contre 131.9).
Substrat C – frontiere de stabilite observee vs predite
Pour chaque graine, on cherche le plus petit kappa ou le ratio median depasse RATIO_DIVERGENT = 5. La frontiere mediane est comparee a KAPPA_C_PREDICTED = (1 - 0.95) / 0.95 ~= 0.053.
Tolerance : on accepte +/- 0.05 (demi-maille de la grille fine).
boundary = estimate_stability_boundary(scan)print(f"kappa_critical par graine : {boundary['kappa_critical_per_seed']}")print(f"kappa_critical median : {boundary['kappa_critical_median']:.4f}")print(f"kappa_critical std : {boundary['kappa_critical_std']:.4f}")print(f"biais vs prediction : {boundary['bias_vs_predicted']:+.4f}")print(f"KAPPA_C_PREDICTED (theorie): {KAPPA_C_PREDICTED:.4f}")
kappa_critical par graine : [0.08 0.08 0.08 0.08 0.08]
kappa_critical median : 0.0800
kappa_critical std : 0.0000
biais vs prediction : +0.0274
KAPPA_C_PREDICTED (theorie): 0.0526
Lecture de la frontiere – biais +0.027, dans la tolerance pre-enregistree
Les 5 graines tombent toutes sur kappa_c = 0.08 (std 0.0000). Cette unanimite n’est pas un copier-coller : la bascule stable/divergent d’une maille depend du coefficient effectif a_eff = a + kappa * a_hat = 0.95 + 0.95 * kappa, identique pour toutes les graines – seul le ratio median varie avec le bruit, jamais la bascule. La theorie prevoit 0.0526, la mesure renvoie 0.0800 : le biais +0.0274 reste sous la demi-tolerance +/- 0.05, et 5 graines sur 5 sont dans la tolerance (n_within_tolerance = 5). Le biais a une cause lisible : a kappa = 0.06, a_eff = 1.007 est deja au-dessus de 1, mais la croissance exponentielle d’un facteur si proche de l’unite reste lente – le ratio n’atteint que 4.37 en T = 200 pas. La frontiere mesuree depend donc de l’horizon : c’est le substrat D qui quantifie cette dependence.
Substrat D – sensibilite a T (horizon) : preuve de non-trivialite
Un test degenere (trivial) ne serait pas sensible a l’horizon T. Ici, la divergence est cumulative sur T (l’AR(1) effectif amplifie a chaque pas), donc le ratio doit croitre avec T au voisinage de la frontiere.
sim_T50 = simulate_self_reference_loop(kappa=0.06, horizon=50, rng=np.random.default_rng(0))sim_T200 = simulate_self_reference_loop(kappa=0.06, horizon=200, rng=np.random.default_rng(0))sim_T500 = simulate_self_reference_loop(kappa=0.06, horizon=500, rng=np.random.default_rng(0))print(f"T= 50 : ratio median = {float(np.median(sim_T50['ratio'])):.3f}")print(f"T= 200 : ratio median = {float(np.median(sim_T200['ratio'])):.3f}")print(f"T= 500 : ratio median = {float(np.median(sim_T500['ratio'])):.3f}")print()print("Le ratio croît avec T (divergence cumulative) -- preuve de non-trivialite.")
T= 50 : ratio median = 1.655
T= 200 : ratio median = 4.487
T= 500 : ratio median = 33.019
Le ratio croît avec T (divergence cumulative) -- preuve de non-trivialite.
Lecture de la sensibilite a l’horizon – la frontiere est une fonction de T
A kappa = 0.06 fixe, le ratio median passe de 1.655 (T = 50) a 4.487 (T = 200) puis 33.019 (T = 500). Trois lectures. (1) Le seuil RATIO_DIVERGENT = 5 est franchi entre 200 et 500 pas : une experience bornee a T = 200 conclurait “stable” la ou T = 500 conclut “divergent”. (2) La croissance n’est pas lineaire : facteur ~2.7 entre 50 et 200, ~7.4 entre 200 et 500 – signature d’une amplification |a_eff|^T avec a_eff = 1.007 a peine au-dessus de 1, dont la pente s’accentue avec T. (3) C’est la preuve de non-trivialite annoncee : un test degenere serait insensible a l’horizon. Consequence methodologique directe : la frontiere 0.08 du substrat C est celle de l’horizon T = 200 ; un horizon plus long la deplacerait vers le bas, plus pres de la prediction 0.0526.
Synthese – verdict honnete a 2 niveaux
Le verdict se decompose en deux niveaux independants :
Prediction stricte (numerique) : la frontiere observee coincide-t-elle avec KAPPA_C_PREDICTED a +/- 0.05 sur au moins 4 graines sur 5 ?
Dissociation (causale) : le delieur causal reste-t-il borne tandis que la bouclee diverge pour au moins un kappa dans la grille ?
Les 3 issues possibles : CONFIRMED (les deux), PARTIAL (dissociation seule) ou FALSIFIED (ni l’un ni l’autre).
Appel direct a run_full_protocol() – un seul appel suffit pour le verdict complet. Le verdict est stable entre 3 et 5 graines (cf tests unitaires test_3_seeds_vs_5_seeds_meme_verdict).
r = run_full_protocol()v = r['verdict']print(f"VERDICT AGREGE : {v['verdict']}")print()print(v['verdict_detail'])print()print(f"kappa_critical median : {v['boundary']['kappa_critical_median']:.4f}")print(f"KAPPA_C_PREDICTED : {KAPPA_C_PREDICTED:.4f}")print(f"biais : {v['boundary']['bias_vs_predicted']:+.4f}")print(f"delieur max : {v['delieur']['delieur_ratio_max']:.4f}")
VERDICT AGREGE : CONFIRMED
Frontiere de stabilite observee a kappa_c = 0.080 (biais +0.027, prediction KAPPA_C = 0.053) ; delieur causal borne (R_T/R_0 max = 0.34) ; bouclee divergente pour kappa >= 0.08.
kappa_critical median : 0.0800
KAPPA_C_PREDICTED : 0.0526
biais : +0.0274
delieur max : 0.3357
Lecture du verdict agrege – CONFIRMED, et ce qu’il couvre
Le protocole complet (run_full_protocol) rejoue les substrats A a C en un seul appel et retourne le memes triple verdict : CONFIRMED, avec prediction_confirmed = True, dissociation_confirmed = True, frontiere 0.0800 contre prediction 0.0526, biais +0.0274, delieur max 0.3357. Le verdict agrege est identique au verdict detaille de la cellule precedente – le test de stabilite 3-contre-5 graines (cf tests unitaires du package) garantit que ce n’est pas un effet de nombre de graines. Ce que CONFIRMED couvre : regime lineaire, predicteur exact (a_hat = a), bruit gaussien, dimension 1, horizon T = 200. Ce qu’il ne dit pas : la robustesse au predicteur imparfait, au bruit a queues lourdes, a la dimension superieure – exactement les trois exercices ci-dessous. La nuance est le contrat de la case 2 : la prediction pre-enregistree (PR #9546) est confirmee DANS son domaine, pas universellement.
Credit temoin (grade C) – Hofstadter strange loops
Le hook grade C est active par ce test : Hofstadter – strange loops (auto-reference comme boucle etrange, le systeme qui se prend lui-meme pour objet et dont la dynamique emergente peut basculer dans la divergence).
Credit temoin rapporte a #8182 (jalon 2 du tracker de veille, mappe dans #9533 par ai-01 le 2026-08-06). Le hook est un temoin de lecture, pas une these : le verdict experimental reste independant du temoin. La boucle fermee est ici une traduction experimentale de la boucle etrangete, pas une illustration de celle-ci.
Reactivation #8182 : commentaire de reactivation post-test sur l’issue #8182 (jalon 2 du tracker), cf protocole discipline de livraison (docs/ict/dissociations-matrix.md section Discipline de livraison).
Exercices
Trois exercices pour prolonger l’experience (cohabitent avec l’exemple guide, cf exercise-example-labeling.md : la cellule ci-dessous contient un stub).
# Exercice 1 : etude de sensibilite au predicteur imparfait.# On a suppose `a_hat = a` (predicteur exact). Que se passe-t-il si# `a_hat = a + delta` avec delta = +/- 0.05 ? La frontiere de stabilite# change-t-elle ? Repondre en implementant une variante de# `simulate_self_reference_loop` qui prend `EnvironmentParams(a_hat=..., # b_hat=...)` distinct de `a, b`, puis balayer `delta in [-0.1, 0.1]`.# Indice : la theorie lineaire donne kappa_c = (1 - a_eff) / a_hat ou# a_eff = a + kappa * a_hat. Pour un delta fixe, la frontiere devient# kappa_c = (1 - a) / (a_hat + delta). Coder la simulation et le verdict.def exercise1_predicteur_imparfait():"""A implementer par l'etudiant."""pass
# Exercice 2 : ajouter du bruit non-gaussien (heavy-tailed).# La prediction actuelle utilise epsilon_t ~ N(0, sigma^2). Que se passe-t-il# si epsilon_t est tire d'une distribution a queues lourdes (par exemple# Student-t a 3 degres de liberte) ? La frontiere devient-elle plus# robuste ou plus fragile ?# Indice : implementer une variante de `simulate_self_reference_loop` qui# prend un parametre `noise_dist=` et accepte `'gaussian'` ou# `'student_t_3'`. Balayer kappa pour les deux distributions et comparer# les frontieres.def exercise2_bruit_heavy_tailed():"""A implementer par l'etudiant."""pass
# Exercice 3 : reproduire la dissociation sur une dimension > 1.# Notre animat est scalaire (1-D). Le vecteur d'etat est `x in R`. Etendre# la simulation a un vecteur `x in R^2` ou `x in R^3` change-t-il la# topologie de la frontiere de stabilite ?# Indice : la theorie lineaire multi-dim donne la frontiere comme la plus# grande valeur propre de la matrice A + kappa * (1/a_hat) * I. Coder la# simulation multi-dim et verifier que la frontiere est inchangee pour# une matrice A = a * I.def exercise3_dimension_superieure():"""A implementer par l'etudiant."""pass
Conclusion
Cette 2eme case de la matrice inversee (#9533) a ete testee avec une pre-enregistrement explicite (PR #9546 SHA 3f6590fa4, verrouille avant test) et un protocole falsifiable (substrat CPU-only, prediction numerique exacte, null adversarial, multi-seed >= 4).
Le verdict CONFIRMED sur cette instance specifique (predicteur exact, bruit gaussien, dimension 1) ne generalise pas : les 3 exercices ci-dessus testent la robustesse a des variations de predicteur, de bruit et de dimension. La theorie lineaire tient sous ces hypotheses ; les variations peuvent la mettre en defaut.
L’issue source (#9533) reste ouverte : 3 cases restantes (W diffuse un q errone, self-model minimal, et un W_t) a tester par d’autres PRs selon la meme discipline (1 case = 1 notebook = 1 PR).