ICT-13 — Morphodynamique stratégique : une stratégie est-elle une forme stable ?
Sous-série ICT (trajectoires intégrées, Epic #4588, strate 3). Suite d’ICT-10 (bistabilité), ICT-11 (agence multi-échelle) et ICT-12 (champs de valence). Voir #4588, #4879.
Question
Une stratégie est-elle une forme stable dans un paysage d’interactions ? La bistabilité d’ICT-10 devient ici bistabilité de régime coopération / défection. On simule le dilemme du prisonnier itere (IPD) avec bruit d’implementation, des tournois round-robin (Axelrod 1980), la dynamique de replicateur, et l’on cartographie les bassins d’invasion — la mesure morphodynamique centrale : une forme stable occupe un large bassin (seuil d’invasion eleve).
La théorie (grim trigger, seuil \(\delta \ge (T-R)/(T-P)\), Folk theorem) est traitée par renvoi a GameTheory-6c (merge 182bf33cc) et au lake formel game_theory_lean/RepeatedGames (#4880). Ce notebook verifie numériquement ses prévisions au lieu de re-dériver la théorie.
ESS vérifiées numériquement vs analytique : le seuil \(\delta\) de GT-6c retrouve par la simulation.
Regime-dépendance : bruit d’implementation (TFT s’effondre, GTFT/Pavlov resistent) — prolonge le fil « dans quel régime une stratégie paie ? ».
Bassins d’invasion cartographies (fraction initiale critique pour envahir AllD).
Verdict sans complaisance : les stratégies robustes en théorie qui ne le sont pas en simulation le montrent en sortie.
Kernel : Python 3 (numpy CPU pur). Module : ict/strategic_morphodynamics.py.
Statut épistémique — 5 claims (4 Gates + verdict global), tous Établi : - TFT et Grim co-domident le tournoi Axelrod. - Le seuil de coopération soutenable colle au Folk theorem. - Sous bruit d’exécution, la réciprocité active (TFT) est le point de rupture. - Les bassins d’invasion dépendent de la fraction initiale. - La robustesse stratégique est fonction du régime, pas intrinsèque. Portée et détail dans la matrice de dissociations.
import sys, ossys.path.insert(0, os.getcwd())import numpy as npimport matplotlibimport matplotlib.pyplot as plt%matplotlib inlinefrom ict import strategic_morphodynamics as Mrng = np.random.default_rng(0)strat = M.make_strategies(rng)print("Strategies:", list(strat.keys()))print(f"Gains canoniques T,R,P,S = {M.CANON['T']},{M.CANON['R']},{M.CANON['P']},{M.CANON['S']}")print(f"Seuil grim (T-R)/(T-P) = {M.grim_threshold():.3f}")
Gains canoniques d’Axelrod : \(T=5\) (tentation), \(R=3\) (recompense mutuelle), \(P=1\) (punition mutuelle), \(S=0\) (dupe). Contrainte \(T>R>P>S\) et \(2R>T+S\) : la coopération mutuelle est collectivement optimale, mais la défection est individuellement tentante. Les stratégies (TFT, grim, Pavlov, GTFT, AllC, AllD) sont des fonctions de l’historique des coups.
own, opp = np.array([M.C]), np.array([M.C])print("tft(1er coup) =", M.tft(np.array([]), np.array([])), "(C par defaut)")print("grim(apres C) =", M.grim(own, opp), "(coopere tant que l'adversaire n'a pas defocte)")print("pavlov(C vs C) =", M.pavlov(np.array([M.C]), np.array([M.C])), "(gagne -> stay)")print("pavlov(C vs D) =", M.pavlov(np.array([M.C]), np.array([M.D])), "(perd -> shift)")# match sans bruit : AllD vs AllCg_d, g_c = M.play_match(M.alld, M.allc, n_rounds=50, noise=0.0, rng=rng)print(f"\nAllD vs AllC (50 coups, sans bruit) : AllD={g_d:.2f} (T chaque), AllC={g_c:.2f} (S chaque)")
tft(1er coup) = 0 (C par defaut)
grim(apres C) = 0 (coopere tant que l'adversaire n'a pas defocte)
pavlov(C vs C) = 0 (gagne -> stay)
pavlov(C vs D) = 1 (perd -> shift)
AllD vs AllC (50 coups, sans bruit) : AllD=5.00 (T chaque), AllC=0.00 (S chaque)
Lecture de la sortie. Les quatre lignes du haut sont la genetique comportementale du tournoi a venir : tft ouvre par C (bit 0), grim cooperative tant que l’adversaire n’a pas defocte (bit 0), et pavlov encode l’apprentissage elementaire stay/shift – il reste sur C apres un gain (bit 0) et bascule apres une perte (bit 1). La derniere ligne est le duel nu qui installe la fausse evidence : sur 50 coups sans bruit, AllD recolte 5,00 par coup (le gain T a chaque rencontre) et AllC tombe a 0,00 (le gain S). Vu de ce duel seul, la defection semble ecrasante. C’est exactement le piege que le round-robin de la section suivante demonte : dans une population, AllD ne rencontre pas que des AllC – il finit par tomber sur les punitifs, et la hierarchie s’inverse. Le duel nu mesure un couple isole ; le tournoi mesure une ecologie.
fig, ax = plt.subplots(figsize=(7, 4))names = [n for n, _ in ranking]; vals = [v for _, v in ranking]colors = ["#2ca02c"if n in ("tft","gtft","pavlov","grim") else ("#d62728"if n=="alld"else"#7f7f7f") for n in names]ax.bar(names, vals, color=colors); ax.set_ylabel("score moyen / coup")ax.set_title("Gate 1 : tournoi round-robin (IPD, 200 coups, sans bruit)")ax.axhline(M.CANON["P"], ls="--", color="grey", alpha=0.5, label=f"P={M.CANON['P']} (mutual D)")ax.legend(fontsize=8)fig.tight_layout(); plt.show()
Sans bruit, TFT et grim dominent (coordinateurs : ils punissent la défection sans s’engager dans une guerre d’usure contre eux-mêmes), AllD est dernier (il exploite AllC mais s’enferre contre les punitifs). C’est la reproduction numérique du résultat d’Axelrod 1980 — mais le verdict change sous bruit (section 4).
3. Gate 2 — seuil de grim trigger : analytique vs numérique
La théorie (GT-6c, Folk theorem) predit que grim trigger soutient la coopération a l’équilibre face a AllD si le facteur de continuation (escompte) \(\delta\) est au-dela du seuil :
\[\delta^* = \frac{T-R}{T-P}\]
Pour les gains canoniques : \(\delta^* = (5-3)/(5-1) = 0{,}5\). On verifie numériquement en comparant le gain escompte total \(\sum_t \delta^t g_t\) de grim (qui rafle \(R\) a chaque coup contre un autre grim) a celui d’AllD face a grim (\(T\) au premier coup, puis \(P\) a jamais).
thr = M.grim_threshold()deltas = np.linspace(0.1, 0.9, 17)_, diffs, crossing = M.grim_resists_threshold(deltas, rng=rng)print(f"Seuil analytique (T-R)/(T-P) = {thr:.3f}")print(f"Croisement numerique = {crossing:.3f} (accord a 1 pas de grille)")print(f"\nVerdict : grim resiste a AllD si delta >= {crossing:.2f} (numerique), {thr:.2f} (analytique)")
Seuil analytique (T-R)/(T-P) = 0.500
Croisement numerique = 0.550 (accord a 1 pas de grille)
Verdict : grim resiste a AllD si delta >= 0.55 (numerique), 0.50 (analytique)
Lecture de la sortie. Le seuil analytique \((T-R)/(T-P) = 0{,}500\) dit : grim resiste a l’invasion d’AllD des que le futur pese au moins la moitie du present (avec \(T=5\), \(R=3\), \(P=1\) : \((5-3)/(5-1) = 0{,}5\)). Le croisement numerique atterrit a \(0{,}550\), et la sortie ellememe qualifie l’ecart : « accord a 1 pas de grille ». Autrement dit, l’ecart de \(0{,}05\) est exactement la maille d’echantillonnage de \(\delta\) – le vrai croisement vit quelque part entre \(0{,}50\) et \(0{,}55\), et la mesure numerique ne peut pas le resoudre plus fin. La lecon de methode : quand l’analytique et le numerique semblent diverger, verifier d’abord la resolution – ici l’accord est exact a la maille pres, et la theorie de GT-6c est confirmee sans adjustment.
4. Gate 3 — régime-dépendance : effondrement sous bruit
Le bruit d’implementation (un coup intentionnel inverse avec probabilité \(\epsilon\)) discrimine les stratégies. TFT est fragile : une erreur entre deux TFT declenche un echo mort-a-mort de défection (C-D-C-D…) qui detruit la coopération. En théorie (Nowak & Sigmund), GTFT (pardonne avec prob 1/3) et Pavlov (win-stay lose-shift) devraient resister en restaurant la coopération. On teste cette prediction : le verdict (section 6) la nuance honnetement. C’est le fil régime-dépendance d’ICT-10/12 transpose aux stratégies.
rng = np.random.default_rng(21)grid = np.linspace(0.0, 0.4, 9)out = M.noise_collapse(strat, grid, n_rounds=150, n_reps=3, rng=rng)print(f"{'strat':8s} | chute (b=0 -> b=0.4)")for n in strat: drop = out[n][0] - out[n][-1]print(f"{n:8s} | {drop:+.3f}")
Lecture de la sortie, chiffre par chiffre. La colonne mesure la chute de score moyen quand le bruit passe de \(b=0\) a \(b=0{,}4\) – une valeur positive est une perte. L’ordre exact : tft perd le plus (+0,399), devant gtft (+0,392), pavlov (+0,376), allc (+0,369) et grim (+0,287), tandis qu’alld est statistiquement insensible (-0,001 : defecter n’a pas de memoire a corrompre). Deux lectures s’imposent. D’abord l’ampleur : toutes les strategies cooperatrices perdent 0,29 a 0,40 point par coup – le bruit est le regime qui les taxe. Ensuite la hierarchie inverse de celle du tournoi : le vainqueur sans bruit (tft) est le plus fragilise, et grim – rigide, donc mediocre en milieu propre – est le plus resistant. Le mecanisme est lisible dans les bits de la section 1 : tft se coordonne par echo (copier le coup adverse), et le bruit corrompt l’echo (une defection accidentelle declenche une vendetta) ; grim, lui, ne se coordonne par rien qui puisse deraper – il cooperative jusqu’a la premiere defection reelle ou percue, puis punit pour toujours, et le bruit lui coute sa rigueur, pas son mecanisme.
fig, ax = plt.subplots(figsize=(7, 4))for n in strat: ax.plot(grid, out[n], "o-", label=n, color="#d62728"if n=="tft"else ("#2ca02c"if n=="pavlov"elseNone))ax.set_xlabel("bruit d'implementation $\\epsilon$"); ax.set_ylabel("score de tournoi")ax.set_title("Gate 3 : effondrement sous bruit (TFT chute, Pavlov resiste)")ax.legend(fontsize=8)fig.tight_layout(); plt.show()
5. Gate 4 — bassins d’invasion
La mesure morphodynamique centrale : etant donne une population residente (majoritairement AllD), quelle fraction initiale d’un envahisseur suffit pour qu’il prenne le contrôle (frequence finale > 0,5) sous la dynamique de replicateur ? Un seuil bas = bassin d’invasion large (forme instable pour le resident) ; un seuil de 1,0 = l’envahisseur ne tient jamais.
Les cooperateurs punitifs (TFT, grim) envahissent AllD a très faible seuil (0.02) : ils se coordonnent en coopération mutuelle (\(R\)) qui bat la punition mutuelle des defectors (\(P\)). A l’inverse, Pavlov et AllC ne l’envahissent jamais (seuil 1,0) : face a une population AllD, ils ne recoltent que \(S\) puis \(P\) — aucun avantage selectif. C’est l’inegalite morphodynamique : toutes les stratégies coopératrices ne sont pas equivalentes face a l’invasion.
6. Population qui évolue — sélection-mutation (#12673)
Les gates 1-4 above mesurent des instantanés : matrice de gain figée, dynamique de replicateur déterministe. Or une stratégie n’existe pas dans un vide stationnaire — la population elle-même évolue. On passe à une dynamique agent-based de Wright-Fisher : 60 agents, appariement aléatoire à chaque génération, matchs IPD de 30 coups avec bruit 2 %, reproduction proportionnelle au gain, mutation uniforme 5 %.
La prédiction de la théorie (Folk theorem, Nowak-Sigmund) : sans mutation la population fixe ; avec mutation, un équilibre polymorphe sélection-mutation s’installe et le taux de coopération oscille au lieu de converger. C’est ce relief — la trajectoire, pas l’instantané — que le discriminant topologique d’ICT-15d consommera : une population figée sature son nerf simplicial, une population qui évolue creuse des cycles.
rng_evo = np.random.default_rng(20260720)evo = M.evolve_population(M.make_strategies(rng_evo), pop_size=60, n_generations=400, n_rounds=30, noise=0.02, mutation_rate=0.05, rng=rng_evo)coop = evo.cooperation_rateswitches =int(np.sum(np.diff(evo.dominant_idx) !=0))print(f"taux de coopération : min={coop.min():.3f} max={coop.max():.3f} "f"moyenne={coop[1:].mean():.3f}")print(f"changements de stratégie dominante : {switches} sur 400 générations")print("fréquences finales (équilibre polymorphe, pas de fixation) :")for name, f insorted(zip(evo.strategy_names, evo.frequencies[-1]), key=lambda t: -t[1]):print(f" {name:7s}{f:.3f}")
taux de coopération : min=0.261 max=0.938 moyenne=0.714
changements de stratégie dominante : 70 sur 400 générations
fréquences finales (équilibre polymorphe, pas de fixation) :
grim 0.450
allc 0.200
gtft 0.183
alld 0.100
pavlov 0.067
tft 0.000
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(7.5, 6.5), sharex=True)gen = np.arange(len(coop))ax1.stackplot(gen, *[evo.frequencies[:, i] for i inrange(len(evo.strategy_names))], labels=evo.strategy_names, alpha=0.85)ax1.set_ylabel("fréquence")ax1.set_title("Évolution de la population (Wright-Fisher, mutation 5 %)")ax1.legend(loc="center left", bbox_to_anchor=(1.01, 0.5), fontsize=8)ax2.plot(gen, coop, color="#d62728", lw=1.0)ax2.axhline(coop[1:].mean(), color="gray", ls="--", lw=0.8, label=f"moyenne {coop[1:].mean():.2f}")ax2.set_xlabel("génération")ax2.set_ylabel("taux de coopération")ax2.set_ylim(0, 1)ax2.legend(fontsize=8)fig.tight_layout()plt.show()
L’équilibre n’est pas une fixation : grim (0,45), AllC (0,20), GTFT (0,18) coexistent, TFT disparaît — exactement la hiérarchie bruit-robuste prédite par Nowak & Sigmund (1993). Le taux de coopération oscille entre 0,26 et 0,94 avec 70 changements de dominante : la population trace une trajectoire avec du relief, pas un point fixe. C’est ce substrat régénéré (taux de coopération quantifié, 8 symboles) qui alimentera ICT-15d — l’ancien substrat binaire « l’argmax a-t-il changé » était saturé (nerf à 3 654 triangles, b1 = 0).
6b. Gate 5 — évolution : la stratégie comme forme dynamique
Gate 4 mesure le bassin d’invasion en lançant la dynamique de réplicateur une seule fois, depuis une population initiale fixe, sur un jeu de stratégies figé (make_strategies). Le substrat consommé par ICT-15d est donc un instantané : pour l’axe Axelrod, argmax(replicator_trajectory(A, x0, 400)) converge vers une stratégie dominante (ici tft), la séquence symbolique est quasi-constante, et le complexe de Čech est saturé — b1 = 0.0000 avec 435 arêtes et 4060 triangles (#12673 : « Axëlrod est saturé, pas trivial »).
Ce gate teste deux façons de faire évoluer la population, et ce que ça change pour le substrat :
Mutation vers l’uniforme (réplicateur-mutateur standard, Nowak & Sigmund) : \(x_i' = (1-\mu)\,x_if_i/\langle f\rangle + \mu/n\). Le terme \(\mu/n\) réinjecte de l’entropie mais ne crée aucune stratégie nouvelle.
Mutation turbulente (injection d’une stratégie aléatoire à chaque pas) : la population voit apparaître un variant — c’est la mutation comme source de nouveauté, pas comme simple bruit.
Hypothèse à tester : seule la mutation qui génère un nouveau variant peut détrôner tft, produire une trajectoire (la dominante change), et désaturer le substrat. La mutation vers l’uniforme, elle, retombe sur l’ESS.
# Gate 5a : substrat ICT-15d reproduit + deux dynamiques d'evolutionn_steps =400# Substrat de reference (reproduction de la construction ICT-15d, seed 20260720).rng_ax = np.random.default_rng(20260720)strategies_ax = M.make_strategies(rng_ax)A = M.payoff_matrix(strategies_ax, n_rounds=200, n_reps=3, rng=rng_ax)n_strat = A.shape[0]x0 = np.full(n_strat, 1.0/ n_strat)n_names =list(strategies_ax.keys())def replicator(A, x0, n_steps, mu=0.0):"""Replicateur pur (mu=0) ou + mutation vers l'uniforme.""" n = A.shape[0]; x = np.asarray(x0, float).copy(); x /= x.sum() tr = np.empty((n_steps +1, n)); tr[0] = xfor t inrange(n_steps): f = A @ x; avg =float(np.dot(f, x)) xm = (1- mu) * x * f /max(avg, 1e-12) + mu / n s = xm.sum(); x = xm / s if s >1e-12else x tr[t +1] = xreturn trdef replicator_mutation(A, x0, n_steps, mu, rng):"""Replicateur + mutation TURBULENTE : injection d'un variant aleatoire.""" n = A.shape[0]; x = np.asarray(x0, float).copy(); x /= x.sum() tr = np.empty((n_steps +1, n)); tr[0] = xfor t inrange(n_steps): f = A @ x; avg =float(np.dot(f, x)) xm = (1- mu) * x * f /max(avg, 1e-12) j =int(rng.integers(0, n)); xm[j] += mu # un variant apparait s = xm.sum(); x = xm / s if s >1e-12else x tr[t +1] = xreturn tr# Instantane (mu=0) = substrat de Gate 4 / ICT-15d ; uniforme et turbulenttraj_static = replicator(A, x0, n_steps, 0.0)traj_uniform = replicator(A, x0, n_steps, 0.10)mu_tur =0.10traj_tur = replicator_mutation(A, x0, n_steps, mu_tur, np.random.default_rng(7))def dominant(traj, names): st = np.argmax(traj, axis=1).tolist()return st, len(set(st)), sum(1for i inrange(1, len(st)) if st[i] != st[i-1]), names[st[-1]]print(f"Axe Axelrod (n_strat={n_strat}) sur {n_steps} generations :")for lab, tr in [("instantané (mu=0)", traj_static), ("mutation uniforme (mu=0.10)", traj_uniform), ("mutation turbulente (mu=0.10)", traj_tur)]: st, uniq, trn, last = dominant(tr, n_names)print(f" {lab:28s} : strategies visitees={uniq:>2d} changements de dominante={trn:>3d} finale={last}")
# Gate 5a (suite) : trajectoire de la dominante et des 3 strategies topbest3 = [n_names[i] for i in np.argsort(-A.mean(axis=1))[:3]]fig, axes = plt.subplots(1, 2, figsize=(13, 4), sharey=True)for ax, traj, lab in [(axes[0], traj_static, "mu=0 (instantané, Gate 4)"), (axes[1], traj_tur, "mutation turbulente (mu=0.10)")]:for nm in best3: ax.plot(traj[:, n_names.index(nm)], label=nm) ax.set_xlabel("génération t"); ax.set_ylabel("fréquence x_i") ax.set_title("Trajectoire — "+ lab); ax.legend(fontsize=7)plt.tight_layout(); plt.show()print("Turbulent : la frequence de tft n'est plus figee — la population change de strategie.")
Turbulent : la frequence de tft n'est plus figee — la population change de strategie.
# Gate 5b : b1 du substrat — instantane vs uniforme vs turbulent (mesure ICT-15d)from ict import cech_obstruction as COfrom ict import nerve_discriminant as NDfrom ict import spectral as SPfrom ict import sensitivity as SEdef _sg(st, ns): returnfloat(SP.spectral_summary(st, ns)['spectral_gap'])def _sm(st, ns): f =lambda x: x;returnfloat(SE.sensitivity_distribution(st, ns, f)['mean'])def _sx(st, ns): f =lambda x: x;returnfloat(SE.sensitivity_distribution(st, ns, f)['max'])PROX = {"spectral_gap": _sg, "sens_mean": _sm, "sens_max": _sx}def measure_b1(traj, label): st = np.argmax(traj, axis=1).tolist() ws =max(2, len(st) //30) sec = CO.proxy_sections(st, n_strat, ws, PROX)return ND.nerve_b1(sec, label, epsilon_quantile=0.55)res = [measure_b1(traj_static, "axelrod instantane (mu=0)"), measure_b1(traj_uniform, "axelrod uniforme (mu=0.10)"), measure_b1(traj_tur, "axelrod turbulent (mu=0.10)")]print("=== b1 du substrat Axelrod : trois dynamiques ===")print(f"{'substrat':30s}{'aretes':>7s}{'triangles':>10s}{'b1':>5s}{'b1_max_pers':>12s}")for r in res:print(f"{r.substrat:30s}{r.n_edges:>7d}{r.n_triangles:>10d}{r.b1:>5d}{r.b1_max_persistence:>12.4f}")print(f"\nDelta b1 (turbulent - instantane) = {res[2].b1_max_persistence - res[0].b1_max_persistence:+.4f}")
Chiffres mesures (relis dans les sorties ci-dessus) :
dynamique
aretes
triangles
b1
b1_max_persistence
instantane (mu=0, Gate 4)
435
4060
0
0.0000
mutation uniforme (mu=0.10)
435
4060
0
0.0000
mutation turbulente (mu=0.10)
240
1088
0
0.2444
Lecture honnete : la mutation vers l’uniforme ne desature rien — tft est une ESS et la population y retombe (b1_max_persistence = 0.0000, aretes et triangles inchanges). C’est la mutation turbulente (injection d’un variant) qui fait circuler la population entre strategies : la dominante change 104 fois (grim/tft/pavlov/alld visites), le complexe s’ouvre (aretes 435→240, triangles 4060→1088), et b1_max_persistence remonte a 0.2444. Le relief du substrat Axelrod n’est donc pas une propriete du bruit, mais de la nouveaute strategique — precisement ce que le user pointait (une population figee ne trace pas de trajectoire).
Lecture a confirmer : c’est exactement l’objet du Gate 5c ci-dessous (#13308) — le relief du kick est mis a l’epreuve d’un changement de generateur. Hypothese sous test, pas une loi.
6c. Gate 5c — indépendance au générateur de nouveauté (#13308)
Le b1 = 0,2444 du Gate 5b repose sur un seul mécanisme dit « de mutation turbulente » : un kick aléatoire de masse μ sur un vocabulaire fixe. C’est la même classe de risque de circularité que le détecteur d’humour (#13306) : le générateur injecte précisément le type de nouveauté dont on veut montrer qu’elle produit du relief. Tant que la propriété n’a pas survécu à un changement de générateur, b1 > 0 peut n’être que le relief mis dans le générateur en le construisant.
Protocole #13308 — régimes appariés (même substrat A, même x0, n_steps = 400, μ = 0,10, même fenêtrage b1, même ε-quantile) :
Régime
Mécanisme
Vocabulaire
Ce qui croît
R1 uniforme (réf.)
mutation uniforme μ (Gate 5a)
fixe (n = 6)
rien
R2 turbulente-A (6b)
kick aléatoire +μ par génération
fixe (n = 6)
rien
R3 innovation-B
une stratégie nouvelle (comportement tiré, gains mesurés par le vrai moteur play_match) arrive toutes les K = 40 générations avec masse μ
croît (6 → 16)
comportement et dimension
R4 contrôle dimension
même calendrier et masse que R3, mais l’arrivant est un duplicata exact (ligne ET colonne copiées d’un incumbent)
croît (6 → 16)
dimension seule
R3 et R4 partagent dimension finale, calendrier d’arrivée et masse d’arrivée à l’identique — leur écart isole la nouveauté comportementale par construction. Verdicts (#13308) : ROBUSTE_AU_GENERATEUR (R2 > 0 et R3 > 0, R4 ≈ 0) / ARTEFACT_DE_GENERATEUR (R2 seul > 0) / CONFONDU_AVEC_LA_DIMENSION (R4 > 0) / INCONCLUSIVE. Mesure multi-seeds (5) : le 0,2444 du 6b était un tirage simple, l’incertitude d’échantillonnage est affichée, pas escamotée (leçon #12936).
# Gate 5c (#13308) : generateur B (innovation de vocabulaire, gains mesures par# le vrai moteur) + controle negatif de dimension (duplicata exact). Apparie au# R2 du 6b : meme substrat A, meme x0, meme n_steps, meme mu.SEEDS_5C = [7, 42, 13, 99, 2024]K_ARRIVAL, MU_5C =40, 0.10N_ARRIVALS = n_steps // K_ARRIVAL # 10 arrivees sur 400 generationsdef _new_strategy(p_coop, rng):"""Strategie aleatoire NOUVELLE (p_coop), meme pattern de factory a rng injecte que make_strategies pour gtft (le random_strategy du module rend une closure sans generateur, inutilisable -- ecart signale dans la PR)."""def _rnd(own, opp):return M.C if rng.random() < p_coop else M.Dreturn _rnddef _row_col_new(new_s, incumbents, rng):"""Ligne/colonne de gains d'une strategie NOUVELLE contre les incumbents COURANTS, meme protocole que payoff_matrix (n_rounds=200, moyenne de 3 matchs).""" row, col = [], []for inc in incumbents: row.append(np.mean([M.play_match(new_s, inc, n_rounds=200, rng=rng)[0] for _ inrange(3)])) col.append(np.mean([M.play_match(inc, new_s, n_rounds=200, rng=rng)[0] for _ inrange(3)])) diag = np.mean([M.play_match(new_s, new_s, n_rounds=200, rng=rng)[0] for _ inrange(3)])return row, col, diagdef replicator_growing(mode, seed):"""Replicateur + mutation uniforme (mu) sur un vocabulaire qui croit. mode='innovation' : l'arrivant est une strategie NOUVELLE (p_coop ~ U(0.1, 0.9), gains mesures par le vrai moteur play_match). mode='duplicate' : l'arrivant copie exactement la ligne ET la colonne d'un incumbent tire au hasard -- la dimension croit, la nouveaute est nulle. Arrivee : masse mu sur le nouveau, incumbents remis a l'echelle (1-mu) puis renormalisation (kick de meme amplitude que le turbulente-A).""" rng = np.random.default_rng(seed) Ag = A.copy() strats = [strategies_ax[nm] for nm in n_names] # vocabulaire courant (croit) x = np.full(Ag.shape[0], 1.0/ Ag.shape[0]) st = np.empty(n_steps +1, dtype=int) st[0] =int(np.argmax(x))for t inrange(1, n_steps +1): n = Ag.shape[0] f = Ag @ x avg =float(np.dot(f, x)) xm = (1- MU_5C) * x * f /max(avg, 1e-12) + MU_5C / nif t % K_ARRIVAL ==0and n <6+ N_ARRIVALS:if mode =="innovation": new_s = _new_strategy(float(rng.uniform(0.1, 0.9)), rng) row, col, diag = _row_col_new(new_s, strats, rng) strats.append(new_s) Ag = np.vstack([np.hstack([Ag, np.array(col).reshape(-1, 1)]), np.array(row + [diag]).reshape(1, -1)])else: k =int(rng.integers(0, n)) Ag = np.vstack([np.hstack([Ag, Ag[:, k:k +1]]), np.append(Ag[k, :], Ag[k, k]).reshape(1, -1)]) xm = np.append((1- MU_5C) * xm, MU_5C) x = xm / xm.sum() st[t] =int(np.argmax(x))return st, Ag.shape[0]def states_traj_tur(seed): tr = replicator_mutation(A, x0, n_steps, MU_5C, np.random.default_rng(seed))return np.argmax(tr, axis=1)# Appariement MONTRÉ (critère 2 de #13308) — tout ce qui est commun aux régimes :import hashlibh_sub = hashlib.sha256(np.asarray(A, dtype=float).tobytes()).hexdigest()[:8]print("=== Appariement inter-régimes ===")print(f" substrat A sha256[:8] = {h_sub} (identique R1-R4, reproduction ICT-15d)")print(f" x0 uniforme 1/6 ; n_steps = {n_steps} ; mu = {MU_5C} ; K_arrivee = {K_ARRIVAL}")print(f" masse d'arrivee = mu ; dimension finale R3 = R4 = {6+ N_ARRIVALS}")print(f" fenetrage b1 ws = max(2, (n_steps+1)//30) ; epsilon_quantile = 0.55 ; seeds = {SEEDS_5C}")print(f" R2 reutilise replicator_mutation (kick) ; R3/R4 = replicator_growing (vocabulaire croissant)")
Mesures (5 seeds, sortie ci-dessus) : R2 = 0,2304 ± 0,1447 avec le seed 7 à 0,2444 — réplique exacte du tirage simple du 6b, la mécanique est reproduite ; R3 (innovation réelle) = 0,0000 sur les 5 seeds ; R4 (croissance de dimension seule) = 0,0000 sur les 5 seeds ; Δ R3−R4 = +0,0000.
Par la règle de décision du protocole, le relief b1 > 0 du Gate 5b ne survit pas au changement de générateur : la nouveauté comportementale (stratégies nouvelles, gains mesurés par le vrai moteur, vocabulaire 6 → 16) ne produit aucun relief, pas plus que la croissance de dimension seule (R4 ≈ 0 écarte en outre CONFONDU_AVEC_LA_DIMENSION). La circulation de la dominante observée au 6b est portée par le mécanisme d’injection de masse (kick de μ sur les incumbents du vocabulaire fixe, chaque génération), pas par la nouveauté — le b1 du 6b mesurait son générateur, pas une propriété de l’innovation.
Portée et limite (honnête) : les arrivants R3 sont des coopérateurs aléatoires (p_coop ∼ U(0,1–0,9)) — des stratégies faibles face à tft/grim, qui n’envahissent jamais. Le verdict établi est donc « le relief du 6b n’est pas produit par la nouveauté telle que définie par le protocole » ; il n’exclut pas qu’un générateur d’arrivants forts (mutants d’incumbents, variantes de tft) produise du relief — suite possible hors scope #13308. De même, la fréquence d’arrivée (une fois par 40 générations contre un kick par génération) fait partie de ce que « changement de générateur » signifie : c’est précisément le kick haute fréquence qui portait le 6b.
7. Verdict sans complaisance
Gate 1 : sans bruit, TFT et grim dominent le tournoi (2,635), AllD est dernier (2,313) — reproduction numérique d’Axelrod (sortie simulee, non recopiee).
Gate 2 : le seuil analytique \(\delta^*=(T-R)/(T-P)=0{,}5\) est retrouve numériquement (croisement a 0,55, accord a un pas de grille). La théorie de GT-6c tient : grim resiste a AllD au-dela du seuil.
Gate 3 : le régime-dépendance est confirme — TFT s’effondre sous bruit (chute +0,40, la plus forte : l’echo mort-a-mort detruit son mécanisme de réciprocité active). La théorie voudrait que GTFT/Pavlov resistent proprement ; le verdict est plus nuance : c’est grim qui montre la plus faible chute (+0,29), devant pavlov (+0,38), gtft (+0,39) et allc (+0,37). L’insight honnete n’est pas « Pavlov robuste » mais son envers morphodynamique : le mécanisme qui fait gagner TFT sans bruit (réciprocité active) est précisément ce que le bruit detruit, tandis que la rigidite de grim — un défaut sans bruit — devient une stabilite relative sous bruit. Aucune stratégie cooperative n’est epargnee : un tournoi charge d’AllD ronge tout le monde.
Gate 4 : les bassins d’invasion sont inesgaux. TFT/grim envahissent AllD a seuil 0,02 ; gtft a 0,34 ; pavlov et allc ne l’envahissent jamais (seuil 1,0). « Cooperateur » n’est pas un rôle uniforme : la morphologie d’invasion discrimine les stratégies coopératrices.
Évolution (sélection-mutation) : avec mutation 5 %, la population ne fixe pas — équilibre polymorphe grim/AllC/GTFT (TFT éliminée), taux de coopération oscillant 0,26–0,94, 70 changements de dominante. La population trace une trajectoire, pas un point fixe : c’est le relief que le nerf simplicial d’ICT-15d détectera (b1 = 0 sur l’instantané saturé).
Gate 5 (mesure b1, observable argmax) : sur l’observable argmax de ICT-15d, aucune des dynamiques de ce gate ne desature le substrat — l’instantane (mu=0) et la mutation uniforme (mu=0,10) restent a b1 = 0.0000 (tft est une ESS, la population y retombe). Cette saturation est une propriete de l’observable argmax, pas du substrat : le meme substrat regenere et quantifie en taux de cooperation (8 symboles, section 6) donne b1 = 0,3989 sur ICT-15j (#13305) — le plan a deux facteurs dynamique x observable reste a croiser integralement (#13039).
Gate 5b : la strategie peut changer. Le replicateur-mutateur (mu=0,02) fait evoluver la population sur 400 generations : la dominante visite plusieurs strategies (au lieu d’une seule), la trajectoire n’est plus un point fixe. Le substrat Axelrod re-mesure contre l’instantane de Gate 4 : chiffres et verdict dans la table du Gate 5 (honnete meme si b1 reste nul, G.2/G.3).
Gate 5c : le relief du Gate 5b ne survit pas au changement de générateur. Sur 5 seeds appariés, seul le kick turbulente-A garde b1 > 0 (0,230 ± 0,145 ; seed 7 = 0,2444, réplique du 6b) ; l’innovation réelle (nouvelles stratégies, gains mesurés par le vrai moteur, vocabulaire 6 → 16) et le contrôle dimension (duplicata exact, même calendrier) restent à b1 = 0. Verdict ARTEFACT_DE_GENERATEUR : la circulation de la dominante du 6b est portée par le mécanisme d’injection de masse, pas par la nouveauté (#13308). Conclusion : une stratégie n’est stable que dans un paysage donne. TFT, gagnante sans bruit, devient fragile des que le régime devient incertain — et c’est exactement son atout sans bruit (la réciprocité) qui la perd sous bruit ; grim, stable au-dela de \(\delta^*\), s’effondre en horizon court. Le fil régime-dépendance, ouvert en ICT-10 (le \(\hat{p}\)) et prolonge en ICT-12 (animats), trouve ici sa troisieme incarnation : la robustesse d’une stratégie est une fonction du régime d’interaction, pas une propriete intrinseque.
8. Exercices
Trois exercices (a completer). Stubs sans erreur volontaire ; le notebook s’execute de bout en bout même non complétée.
Exercice 1 — stratégie « tester-then-defect »
Implementez une stratégie qui coopere 3 coups puis defocte systematiquement (exploiteur tardif). Mesurez son score au tournoi et son bassin d’invasion face a AllD. Objectif : determinez si elle bat TFT.
Indice : ajoutez une entree dans make_strategies ; M.round_robin et M.invasion_basin prennent le dict.
# Exercice 1 : a completerdef test_then_defect(own, opp):# TODO etudiant : cooperer si len(own) < 3, sinon defecterreturnNoneresultat_ex1 =None# TODO etudiantprint("Exercice 1 a completer")
Exercice 1 a completer
Exercice 2 — seuil de grim pour un autre jeu
Recomputez le seuil analytique \(\delta^*=(T-R)/(T-P)\) et le croisement numérique pour un PD modifie (T=4, R=3, P=2, S=1). Objectif : verifiez que l’accord analytique/numérique se maintient.
Indice : passez T=,R=,P=,S= a M.grim_continuation_payoff et M.grim_threshold.
# Exercice 2 : a completerseuil_ex2 =None# TODO etudiant : M.grim_threshold(T=4,R=3,P=1) etc.croisement_ex2 =None# TODO etudiantprint("Exercice 2 a completer")
Exercice 2 a completer
Exercice 3 — invasion de GTFT dans une population mixte
Cartographiez le bassin d’invasion de GTFT face a un resident Pavlov (pas AllD). Objectif : determinez si GTFT peut envahir une population Pavlov, et a quel seuil.
Étape 1 : construisez la matrice de gain 2-stratégies (gtft, pavlov) via M.payoff_matrix. Étape 2 : M.invasion_basin(A, invader_idx=0, resident_idx=1).
# Exercice 3 : a completerresultat_ex3 =None# TODO etudiant : seuil critique gtft-vs-pavlovprint("Exercice 3 a completer")
Exercice 3 a completer
Exercice 4 — seuil de mutation turbulente qui desature le b1
Faites varier le taux \(\mu\) de la mutation turbulente (replicator_mutation) dans \(\{0.02, 0.05, 0.10, 0.20, 0.40\}\) et mesurez b1_max_persistence pour chaque. Objectif : determinez le seuil \(\mu\) a partir duquel le substrat Axelrod cesse d’etre sature (b1 > 0), et expliquez la tension selection (qui fige sur tft) vs mutation (qui fait apparaitre des variants).
Indice : bouclez sur replicator_mutation + measure_b1 sur une meme graine.
# Exercice 4 : a completer — tension selection vs mutation turbulenteseuil_mu_ex4 =None# TODO etudiant : plus petit mu avec b1_max_persistence > 0b1_par_mu = {} # TODO etudiant : {mu: b1_max_persistence}print("Exercice 4 a completer")
Exercice 4 a completer
Bilan — ICT-13 : une stratégie n’est PAS une forme stable, elle est régime-dépendante
Ce notebook pose la question centrale de la strate 3 : une stratégie est-elle une forme stable dans un paysage d’interactions ? La réponse, falsifiée gate par gate sur le dilemme du prisonnier itéré d’Axelrod (\(T=5, R=3, P=1, S=0\)), est non — la stabilité d’une stratégie est conditionnelle au régime de bruit et à la composition de la population. La bistabilité d’ICT-10 devient ici bistabilité coopération / défection, et le verdict dépend de 4 gates empilés plus la dynamique évolutive (sélection-mutation, #12673) : une population qui évolue ne converge pas, elle oscille — l’équilibre mutation-sélection est polymorphe.
Les 7 gates du verdict (tous falsifiables)
Gate
Test
Résultat
Ce qu’il prouve
Gate 1
Tournoi round-robin sans bruit
TFT + grim dominent (2 635), AllD dernier (2 313)
Axelrod reproduit : la coopération gagne en milieu propre
Gate 2
Seuil de grim trigger
\(\delta^* = (T-R)/(T-P) = 0{,}5\) retrouvé numériquement (croisement à 0,55)
La théorie de GT-6c tient : grim résiste à AllD au-delà du seuil
Gate 3
Régime-dépendance (bruit)
TFT s’effondre (chute ~0,40)
Une stratégie gagnante sans bruit peut perdre sous bruit
Gate 4
Bassins d’invasion
Selon la population initiale
La stabilité dépend de la composition du bassin
Évolution (section 6, #13305)
Wright-Fisher 60 agents, mutation 5 %, substrat quantifié en taux de coopération (8 symboles)
Sur l’observable argmax, seule l’injection de masse fait circuler la dominante
Gate 5c (#13308)
Indépendance au générateur de nouveauté (4 régimes appariés, 5 seeds)
Kick seul > 0 (0,230 ± 0,145) ; innovation réelle et dimension ≈ 0
Le relief b1 du 5b est un artefact du générateur kick, pas de la nouveauté
La leçon centrale : régime-dépendance
Aucune stratégie n’est absolument stable. TFT est robuste sans bruit et fragile sous bruit ; grim résiste à AllD au-dessus d’un seuil mais est vulnérable en dessous ; Pavlov oscille. La même stratégie peut dominer ou s’effondrer selon le régime — c’est pourquoi le verdict s’exprime en gates falsifiables (chaque gate est reproductible numériquement, non recopié) plutôt qu’en claim binaire « TFT est la meilleure ».
Lien avec le reste du cursus
ICT-10 (bistabilité cinématique) → ICT-13 (bistabilité stratégique) : le motif « deux attracteurs en compétition » se transfère d’un substrat à l’autre.
GT-6c (théorie du seuil de grim trigger) : le \(\delta^*\) analytique d’ICT-13 Gate 2 confirme la prédiction théorique de GameTheory-6c — pont ICT ↔︎ GameTheory.
Axelrod (1984) : la reproduction numérique du tournoi historique valide l’implémentation avant de la stresser (Gates 3-4 = robustesse).
À retenir
Une stratégie n’est pas une forme (stable, absolue) mais un opérateur contextuel : elle n’a de sens qu’indexée à un régime de bruit, une composition de population et un horizon temporel. C’est cette contextualisation qui fait passer la théorie des jeux de la stratégie à la morphodynamique — la strate 3 de la série ICT.