diff --git a/MyIA.AI.Notebooks/GameTheory/GameTheory-03c-Le-Joueur-LLM.ipynb b/MyIA.AI.Notebooks/GameTheory/GameTheory-03c-Le-Joueur-LLM.ipynb index c394c7a0ec..56d1741ed3 100644 --- a/MyIA.AI.Notebooks/GameTheory/GameTheory-03c-Le-Joueur-LLM.ipynb +++ b/MyIA.AI.Notebooks/GameTheory/GameTheory-03c-Le-Joueur-LLM.ipynb @@ -13,54 +13,7 @@ }, "tags": [] }, - "source": [ - "# GameTheory-3c — Le joueur LLM dans le tableau périodique\n", - "\n", - "**Navigation** : [GameTheory-3](GameTheory-03-Topology2x2.ipynb) (chambres Robinson-Goforth) · [GameTheory-03c-Le-Joueur-LLM](GameTheory-03c-Le-Joueur-LLM.ipynb) · [GameTheory-21](GameTheory-21-Deux-Especes-de-Fleches.ipynb) (morphisme fini)\n", - "\n", - "**Grain** : `#12254` — DEEP/notebook-python sur le papier *Playing Repeated Games with Large Language Models* (Nature Human Behaviour, [s41562-025-02172-y](https://www.nature.com/articles/s41562-025-02172-y)).\n", - "\n", - "**Sources lues firsthand** : page article (2026-08-22) ; grammaire R-G des chambres/murs réutilisée de `GameTheory-3` (cellule 5 `OrdinalGame`).\n", - "\n", - "**Kernel** : `python3` — pas d'appels réseau non gardés.\n", - "\n", - "## Hypothèse (lue du papier, reformulée)\n", - "\n", - "Un joueur LLM (GPT-4 / Claude 2 / Llama 2 70B / text-davinci) joue à des jeux 2×2 répétés (matrice convertie en règles textuelles, température 0, réponse mono-token, historique concaténé). Trois apports :\n", - "\n", - "- **(a)** Le **paysage de performance** du joueur varie selon la famille de jeu — fort en Dilemme (défection permanente après une seule défection), faible en coordination (Battle of the Sexes : il colle à son option préférée).\n", - "- **(b)** La **dissociation prédire/agir** : GPT-4 prédit correctement l'alternance et n'agit pas en conséquence.\n", - "- **(c)** Le **SCoT** (Social Chain-of-Thought — prédire le coup adverse avant de choisir) est une **transmutation de Bruns** : la consigne modifie la règle de décision sans modifier le jeu. Le papier rapporte qu'elle augmente la coordination des vrais LLMs ; E2bis chiffre ce qu'elle fait à notre joueur simulé de niveau 2 — et le résultat n'est pas celui qu'on attend.\n", - "\n", - "## Ce que le notebook mesure\n", - "\n", - "Cinq cellules-mesures (E1-E4 + E2bis) ancrées sur les outputs commités :\n", - "\n", - "1. **E1 — Placer le papier dans le tableau** : les six familles mesurées (win-win, Dilemme, unfair, cyclique, biaisé, second-best) se placent-elles dans les chambres Robinson-Goforth ? **Mapping RAPPORTÉ** (le notebook dérive ; le mapping Robinson-Goforth ↔ papier est une dette reconnue §Sources).\n", - "2. **E2 — Le joueur LLM simulé** : cinq modèles de joueur (`sticky` paramétré par sa première action C ou D, `alternating`, `noisy` à ε fixé, `best_response`), tous exécutés par la même règle `simulate_player` — le comportement émerge de la règle, round après round, il n'est jamais écrit en dur. Le branchement d'un vrai provider (protocole du papier : matrice → règles textuelles → température 0 → 1 token) est l'exercice 1, stub C.1 sans clé.\n", - "3. **E2bis — SCoT** (apport (c)) : le joueur de niveau 2 prédit le coup adverse (sa meilleure réponse à mon dernier coup) puis joue sa meilleure réponse à cette prédiction — effet chiffré sur le taux d'atteinte de Nash, avec et sans.\n", - "4. **E3 — Le swap en cours de partie** : valeur ajoutée absente du papier — appliquer un swap au round k et mesurer qui suit le déplacement : le sticky (sa règle ne regarde pas le jeu), l'alternant (il regarde l'horloge), le bruité (il écoute, avec un tremblement), la BR (elle écoute parfaitement). Marche 1½ vers D4.\n", - "5. **E4 — Dissociation (b)** : la prédiction (meilleure réponse au dernier coup adverse) diffère-t-elle de l'action jouée, mode par mode — mesure explicite, pas une affirmation.\n", - "\n", - "## Critère d'acceptation\n", - "\n", - "- E1 rend un placement explicite avec statut (dérivé / RAPPORTÉ).\n", - "- E2/E2bis/E3/E4 produisent des taux mesurés, reproductibles (seeds fixés), sur sorties committées ; le vrai provider est l'exercice 1 (stub C.1 sans clé, c'est aussi un résultat reproductible).\n", - "- E4 exhibe la dissociation (ou son absence, qui est un résultat).\n", - "- C.1 : 0 `raise NotImplementedError` ; C.2 : cellules code avec `execution_count` + outputs réels (ou vides si stub non exécuté).\n", - "\n", - "## Dettes de vérification\n", - "\n", - "1. Le mapping six familles ↔ chambres/murs R-G n'est pas dérivé — alignement à établir dans E1 avant toute affirmation.\n", - "2. Les résultats du papier sont ses résultats, sur **ses** modèles 2023-2024. Rejoués sur des modèles actuels, ils peuvent ne pas se reproduire — c'est ce que E2/E4 mesurent (cassettes + plafond).\n", - "3. Coût et reproductibilité : appels réels = plafond et cassettes avant la lane ; sinon stub C.1 honnête.\n", - "\n", - "## Sources\n", - "\n", - "- Mei et al., *Playing Repeated Games with Large Language Models*, Nature Human Behaviour (2025), [s41562-025-02172-y](https://www.nature.com/articles/s41562-025-02172-y) — page lue 2026-08-22.\n", - "- Robinson & Goforth, *The Topology of the 2x2 Games* (2005) — implémentation dans `GameTheory-3` cellule 5 (`OrdinalGame`).\n", - "- Bruns, *Austausch und Gerechtigkeit* (1975) — notion de transmutation (information nouvelle vs déplacement), voir aussi GameTheory-21 (Loi III, transformations vs morphismes).\n" - ] + "source": "# GameTheory-3c — Le joueur LLM dans le tableau périodique\n\n**Navigation** : [GameTheory-3](GameTheory-03-Topology2x2.ipynb) (chambres Robinson-Goforth) · [GameTheory-03c-Le-Joueur-LLM](GameTheory-03c-Le-Joueur-LLM.ipynb) · [GameTheory-21](GameTheory-21-Deux-Especes-de-Fleches.ipynb) (morphisme fini)\n\n**Grain** : `#12254` — DEEP/notebook-python sur le papier *Playing Repeated Games with Large Language Models* (Nature Human Behaviour, [s41562-025-02172-y](https://www.nature.com/articles/s41562-025-02172-y)).\n\n**Sources lues firsthand** : page article (2026-08-22) ; grammaire R-G des chambres/murs réutilisée de `GameTheory-3` (cellule 5 `OrdinalGame`).\n\n**Kernel** : `python3` — pas d'appels réseau non gardés.\n\n## Hypothèse (lue du papier, reformulée)\n\nUn joueur LLM (GPT-4 / Claude 2 / Llama 2 70B / text-davinci) joue à des jeux 2×2 répétés (matrice convertie en règles textuelles, température 0, réponse mono-token, historique concaténé). Trois apports :\n\n- **(a)** Le **paysage de performance** du joueur varie selon la famille de jeu — fort en Dilemme (défection permanente après une seule défection), faible en coordination (Battle of the Sexes : il colle à son option préférée).\n- **(b)** La **dissociation prédire/agir** : GPT-4 prédit correctement l'alternance et n'agit pas en conséquence.\n- **(c)** Le **SCoT** (Social Chain-of-Thought — prédire le coup adverse avant de choisir) est une **transmutation de Bruns** : la consigne modifie la règle de décision sans modifier le jeu. Le papier rapporte qu'elle augmente la coordination des vrais LLMs ; E2bis chiffre ce qu'elle fait à notre joueur simulé de niveau 2 — et le résultat n'est pas celui qu'on attend.\n\n## Ce que le notebook mesure\n\nCinq cellules-mesures (E1-E4 + E2bis) ancrées sur les outputs commités :\n\n1. **E1 — Placer le papier dans le tableau** : les six familles mesurées (win-win, Dilemme, unfair, cyclique, biaisé, second-best) se placent-elles dans les chambres Robinson-Goforth ? **Mapping RAPPORTÉ** (le notebook dérive ; le mapping Robinson-Goforth ↔ papier est une dette reconnue §Sources).\n2. **E2 — Le joueur LLM simulé** : cinq modèles de joueur (`sticky` paramétré par sa première action C ou D, `alternating`, `noisy` à ε fixé, `best_response`), tous exécutés par la même règle `simulate_player` — le comportement émerge de la règle, round après round, il n'est jamais écrit en dur. Le branchement d'un vrai provider (protocole du papier : matrice → règles textuelles → température 0 → 1 token) est l'exercice 1, stub C.1 sans clé.\n3. **E2bis — SCoT** (apport (c)) : le joueur de niveau 2 prédit le coup adverse (sa meilleure réponse à mon dernier coup) puis joue sa meilleure réponse à cette prédiction — effet chiffré sur le taux d'atteinte de Nash, avec et sans.\n4. **E3 — Le swap en cours de partie** : valeur ajoutée absente du papier — appliquer un swap au round k et mesurer qui suit le déplacement : le sticky (sa règle ne regarde pas le jeu), l'alternant (il regarde l'horloge), le bruité (il écoute, avec un tremblement), la BR (elle écoute parfaitement). Marche 1½ vers D4.\n5. **E4 — Dissociation (b)** : la prédiction (meilleure réponse au dernier coup adverse) diffère-t-elle de l'action jouée, mode par mode — mesure explicite, pas une affirmation.\n\n## Critère d'acceptation\n\n- E1 rend un placement explicite avec statut (dérivé / RAPPORTÉ).\n- E2/E2bis/E3/E4 produisent des taux mesurés, reproductibles (seeds fixés), sur sorties committées ; le vrai provider est l'exercice 1 (stub C.1 sans clé, c'est aussi un résultat reproductible).\n- E4 exhibe la dissociation (ou son absence, qui est un résultat).\n- C.1 : 0 `raise NotImplementedError` ; C.2 : cellules code avec `execution_count` + outputs réels (ou vides si stub non exécuté).\n\n## Dettes de vérification\n\n1. Le mapping six familles ↔ chambres/murs R-G n'est pas dérivé — alignement à établir dans E1 avant toute affirmation.\n2. Les résultats du papier sont ses résultats, sur **ses** modèles 2023-2024. Rejoués sur des modèles actuels, ils peuvent ne pas se reproduire — c'est ce que E2/E4 mesurent (cassettes + plafond).\n3. Coût et reproductibilité : appels réels = plafond et cassettes avant la lane ; sinon stub C.1 honnête.\n\n## Sources\n\n- Mei et al., *Playing Repeated Games with Large Language Models*, Nature Human Behaviour (2025), [s41562-025-02172-y](https://www.nature.com/articles/s41562-025-02172-y) — page lue 2026-08-22.\n- Robinson & Goforth, *The Topology of the 2x2 Games* (2005) — implémentation dans `GameTheory-3` cellule 5 (`OrdinalGame`).\n- Bruns, *Austausch und Gerechtigkeit* (1975) — notion de transmutation (information nouvelle vs déplacement), voir aussi GameTheory-21 (Loi III, transformations vs morphismes).\n" }, { "cell_type": "code", @@ -68,10 +21,10 @@ "id": "cell-01", "metadata": { "execution": { - "iopub.execute_input": "2026-08-23T11:38:05.873819Z", - "iopub.status.busy": "2026-08-23T11:38:05.873613Z", - "iopub.status.idle": "2026-08-23T11:38:05.946479Z", - "shell.execute_reply": "2026-08-23T11:38:05.945888Z" + "iopub.status.busy": "2026-08-24T20:50:05.798044Z", + "iopub.execute_input": "2026-08-24T20:50:05.798306Z", + "shell.execute_reply": "2026-08-24T20:50:05.905152Z", + "iopub.status.idle": "2026-08-24T20:50:05.906306Z" }, "papermill": { "duration": 0.077218, @@ -83,56 +36,7 @@ "tags": [] }, "outputs": [], - "source": [ - "# Imports\n", - "import os\n", - "import numpy as np\n", - "from dataclasses import dataclass\n", - "from typing import Tuple, List, Dict\n", - "\n", - "# Convention : OrdinalGame (R-G) aligne sur le notebook GameTheory-3 (cellule 5, 7).\n", - "# Plus le rang est GRAND, meilleure est l'issue. Vecteur indexe (CC, CD, DC, DD)\n", - "# pour Row (payoffs_R) et Col (payoffs_C). L'invariant __post_init__ garantit\n", - "# que chaque payoffs_X est une permutation stricte de (1, 2, 3, 4) -- propriete\n", - "# qui exclut mecaniquement les jeux a rangs repetes (hors tableau periodique R-G)\n", - "# et qui protege contre les fautes de frappe comme (1, 2, 2, 3).\n", - "\n", - "@dataclass(frozen=True)\n", - "class OrdinalGame:\n", - " name: str\n", - " payoffs_R: Tuple[int, int, int, int] # rangs Row pour (CC, CD, DC, DD)\n", - " payoffs_C: Tuple[int, int, int, int] # rangs Col pour (CC, CD, DC, DD)\n", - "\n", - " def __post_init__(self):\n", - " assert sorted(self.payoffs_R) == [1, 2, 3, 4], \\\n", - " f\"payoffs_R doit etre permutation de 1-4, got {self.payoffs_R}\"\n", - " assert sorted(self.payoffs_C) == [1, 2, 3, 4], \\\n", - " f\"payoffs_C doit etre permutation de 1-4, got {self.payoffs_C}\"\n", - "\n", - "\n", - "CLASSIC_GAMES = {\n", - " # Harmony : CC > CD > DC > DD. Rang_R = (4, 3, 2, 1), Rang_C = (4, 2, 3, 1)\n", - " # (Nash unique (C,C), ordre strict, symetrie CD<->DC transposee).\n", - " \"Harmony\": OrdinalGame(\"Harmony\", (4, 3, 2, 1), (4, 2, 3, 1)),\n", - " # StagHunt : CC > DC > DD > CD. Rang_R = (4, 1, 3, 2), Rang_C = (4, 3, 1, 2)\n", - " # (Nash (C,C) et (D,D), ordre strict, symetrie transposee).\n", - " \"StagHunt\": OrdinalGame(\"StagHunt\", (4, 1, 3, 2), (4, 3, 1, 2)),\n", - " # Dilemme (= Prisoner's Dilemma textbook) : DC > CC > DD > CD.\n", - " # Rang_R = (3, 1, 4, 2), Rang_C = (3, 4, 1, 2) (Nash unique (D,D), CC>DD).\n", - " \"Dilemme\": OrdinalGame(\"Dilemme\", (3, 1, 4, 2), (3, 4, 1, 2)),\n", - " # Chicken : DC > CC > CD > DD. Rang_R = (3, 2, 4, 1), Rang_C = (3, 4, 2, 1)\n", - " # (Nash (C,D) et (D,C), ordre strict, symetrie transposee).\n", - " \"Chicken\": OrdinalGame(\"Chicken\", (3, 2, 4, 1), (3, 4, 2, 1)),\n", - " # Coordination (= Pure Coordination) : CC > DD > DC > CD.\n", - " # Rang_R = (4, 1, 2, 3), Rang_C = (4, 2, 1, 3) (Nash (C,C) et (D,D)).\n", - " \"Coordination\": OrdinalGame(\"Coordination\", (4, 1, 2, 3), (4, 2, 1, 3)),\n", - " # BattleSexes : DC > CD > CC > DD. Rang_R = (2, 3, 4, 1), Rang_C = (2, 4, 3, 1)\n", - " # (Nash (C,D) et (D,C), Row prefere (C,D) car CD = rang 3 > CC = rang 2,\n", - " # Col prefere (D,C) car DC = rang 4 > DD = rang 1 -- chaque joueur\n", - " # departage les deux Nash en sens inverse).\n", - " \"BattleSexes\": OrdinalGame(\"BattleSexes\", (2, 3, 4, 1), (2, 4, 3, 1)),\n", - "}\n" - ] + "source": "# Imports\nimport os\nimport numpy as np\nfrom dataclasses import dataclass\nfrom typing import Tuple, List, Dict\n\n# Convention : OrdinalGame (R-G) aligne sur le notebook GameTheory-3 (cellule 5, 7).\n# Plus le rang est GRAND, meilleure est l'issue. Vecteur indexe (CC, CD, DC, DD)\n# pour Row (payoffs_R) et Col (payoffs_C). L'invariant __post_init__ garantit\n# que chaque payoffs_X est une permutation stricte de (1, 2, 3, 4) -- propriete\n# qui exclut mecaniquement les jeux a rangs repetes (hors tableau periodique R-G)\n# et qui protege contre les fautes de frappe comme (1, 2, 2, 3).\n\n@dataclass(frozen=True)\nclass OrdinalGame:\n name: str\n payoffs_R: Tuple[int, int, int, int] # rangs Row pour (CC, CD, DC, DD)\n payoffs_C: Tuple[int, int, int, int] # rangs Col pour (CC, CD, DC, DD)\n\n def __post_init__(self):\n assert sorted(self.payoffs_R) == [1, 2, 3, 4], \\\n f\"payoffs_R doit etre permutation de 1-4, got {self.payoffs_R}\"\n assert sorted(self.payoffs_C) == [1, 2, 3, 4], \\\n f\"payoffs_C doit etre permutation de 1-4, got {self.payoffs_C}\"\n\n\nCLASSIC_GAMES = {\n # Harmony : CC > CD > DC > DD. Rang_R = (4, 3, 2, 1), Rang_C = (4, 2, 3, 1)\n # (Nash unique (C,C), ordre strict, symetrie CD<->DC transposee).\n \"Harmony\": OrdinalGame(\"Harmony\", (4, 3, 2, 1), (4, 2, 3, 1)),\n # StagHunt : CC > DC > DD > CD. Rang_R = (4, 1, 3, 2), Rang_C = (4, 3, 1, 2)\n # (Nash (C,C) et (D,D), ordre strict, symetrie transposee).\n \"StagHunt\": OrdinalGame(\"StagHunt\", (4, 1, 3, 2), (4, 3, 1, 2)),\n # Dilemme (= Prisoner's Dilemma textbook) : DC > CC > DD > CD.\n # Rang_R = (3, 1, 4, 2), Rang_C = (3, 4, 1, 2) (Nash unique (D,D), CC>DD).\n \"Dilemme\": OrdinalGame(\"Dilemme\", (3, 1, 4, 2), (3, 4, 1, 2)),\n # Chicken : DC > CC > CD > DD. Rang_R = (3, 2, 4, 1), Rang_C = (3, 4, 2, 1)\n # (Nash (C,D) et (D,C), ordre strict, symetrie transposee).\n \"Chicken\": OrdinalGame(\"Chicken\", (3, 2, 4, 1), (3, 4, 2, 1)),\n # Coordination (= Pure Coordination) : CC > DD > DC > CD.\n # Rang_R = (4, 1, 2, 3), Rang_C = (4, 2, 1, 3) (Nash (C,C) et (D,D)).\n \"Coordination\": OrdinalGame(\"Coordination\", (4, 1, 2, 3), (4, 2, 1, 3)),\n # BattleSexes : DC > CD > CC > DD. Rang_R = (2, 3, 4, 1), Rang_C = (2, 4, 3, 1)\n # (Nash (C,D) et (D,C), Row prefere (C,D) car CD = rang 3 > CC = rang 2,\n # Col prefere (D,C) car DC = rang 4 > DD = rang 1 -- chaque joueur\n # departage les deux Nash en sens inverse).\n \"BattleSexes\": OrdinalGame(\"BattleSexes\", (2, 3, 4, 1), (2, 4, 3, 1)),\n}\n" }, { "cell_type": "markdown", @@ -147,15 +51,7 @@ }, "tags": [] }, - "source": [ - "### Lecture de la representation\n", - "\n", - "Les jeux sont encodes en **rangs ordonnes** (4 = meilleur, 1 = pire pour le joueur considere). C'est la convention de Robinson-Goforth (GameTheory-3 cellule 5), invariante aux translations de payoff -- ce qui compte est la **structure des preferences**, pas les valeurs cardinales.\n", - "\n", - "**Exemple Dilemme** `(3, 1, 4, 2)` : pour le **Row-player**, la tentation (D,C) = rang 4 bat la cooperation (C,C) = rang 3 ; la recompense mutuelle (D,D) = rang 2 bat la defection unilaterale (C,D) = rang 1 (DD > CD au sens ordinal). Pour le **Col-player**, la defection unilaterale (C,D) = rang 4 bat tout.\n", - "\n", - "Cette convention permet de tester la **dissociation** du joueur LLM **sans bruit** : si le joueur repond « D » en Dilemme, c'est la preference revelee ; si en BoS il repond toujours la meme option, c'est l'absence d'alternance.\n" - ] + "source": "### Lecture de la representation\n\nLes jeux sont encodes en **rangs ordonnes** (4 = meilleur, 1 = pire pour le joueur considere). C'est la convention de Robinson-Goforth (GameTheory-3 cellule 5), invariante aux translations de payoff -- ce qui compte est la **structure des preferences**, pas les valeurs cardinales.\n\n**Exemple Dilemme** `(3, 1, 4, 2)` : pour le **Row-player**, la tentation (D,C) = rang 4 bat la cooperation (C,C) = rang 3 ; la recompense mutuelle (D,D) = rang 2 bat la defection unilaterale (C,D) = rang 1 (DD > CD au sens ordinal). Pour le **Col-player**, la defection unilaterale (C,D) = rang 4 bat tout.\n\nCette convention permet de tester la **dissociation** du joueur LLM **sans bruit** : si le joueur repond « D » en Dilemme, c'est la preference revelee ; si en BoS il repond toujours la meme option, c'est l'absence d'alternance.\n" }, { "cell_type": "markdown", @@ -170,15 +66,7 @@ }, "tags": [] }, - "source": [ - "### Pourquoi cette convention pour E2 ?\n", - "\n", - "Le papier (Mei et al.) utilise une représentation **cardinale** dans ses mesures de payoff cumulé. Mais l'apport scientifique — *la dissociation prédire/agir* — est **invariant à la représentation** : peu importe que (C,C) paie 8 ou 10, ce qui compte est que le joueur **prédit correctement** l'alternance en BoS et **n'agit pas** en conséquence.\n", - "\n", - "On peut donc reproduire l'expérience (b) en ordinal, sans dépendance externe, et la **dissociation reste visible** : le joueur qui annonce « J'alterne C-D-C-D » et joue C-C-C-C.\n", - "\n", - "L'apport (c) — SCoT comme transmutation — est aussi mesurable en ordinal : la consigne « prédis le coup adverse » modifie la règle de décision sans modifier le jeu (implémenté en E2bis, mode `scot` de `simulate_player`). Même grammaire, même test.\n" - ] + "source": "### Pourquoi cette convention pour E2 ?\n\nLe papier (Mei et al.) utilise une représentation **cardinale** dans ses mesures de payoff cumulé. Mais l'apport scientifique — *la dissociation prédire/agir* — est **invariant à la représentation** : peu importe que (C,C) paie 8 ou 10, ce qui compte est que le joueur **prédit correctement** l'alternance en BoS et **n'agit pas** en conséquence.\n\nOn peut donc reproduire l'expérience (b) en ordinal, sans dépendance externe, et la **dissociation reste visible** : le joueur qui annonce « J'alterne C-D-C-D » et joue C-C-C-C.\n\nL'apport (c) — SCoT comme transmutation — est aussi mesurable en ordinal : la consigne « prédis le coup adverse » modifie la règle de décision sans modifier le jeu (implémenté en E2bis, mode `scot` de `simulate_player`). Même grammaire, même test.\n" }, { "cell_type": "code", @@ -186,10 +74,10 @@ "id": "cell-04", "metadata": { "execution": { - "iopub.execute_input": "2026-08-23T11:38:05.963547Z", - "iopub.status.busy": "2026-08-23T11:38:05.963304Z", - "iopub.status.idle": "2026-08-23T11:38:05.969587Z", - "shell.execute_reply": "2026-08-23T11:38:05.969048Z" + "iopub.status.busy": "2026-08-24T20:50:05.908460Z", + "iopub.execute_input": "2026-08-24T20:50:05.908831Z", + "shell.execute_reply": "2026-08-24T20:50:05.915977Z", + "iopub.status.idle": "2026-08-24T20:50:05.917210Z" }, "papermill": { "duration": 0.010479, @@ -202,47 +90,12 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "=== Best response par jeu ===\n", - "BattleSexes : Row(C)=D Row(D)=C | Col(C)=D Col(D)=C\n", - "StagHunt : Row(C)=C Row(D)=D | Col(C)=C Col(D)=D\n", - "Dilemme : Row(C)=D Row(D)=D | Col(C)=D Col(D)=D\n", - "Chicken : Row(C)=D Row(D)=C | Col(C)=D Col(D)=C\n", - "Harmony : Row(C)=C Row(D)=C | Col(C)=C Col(D)=C\n", - "Coordination : Row(C)=C Row(D)=D | Col(C)=C Col(D)=D\n" - ] + "name": "stdout", + "text": "=== Best response par jeu ===\nBattleSexes : Row(C)=D Row(D)=C | Col(C)=D Col(D)=C\nStagHunt : Row(C)=C Row(D)=D | Col(C)=C Col(D)=D\nDilemme : Row(C)=D Row(D)=D | Col(C)=D Col(D)=D\nChicken : Row(C)=D Row(D)=C | Col(C)=D Col(D)=C\nHarmony : Row(C)=C Row(D)=C | Col(C)=C Col(D)=C\nCoordination : Row(C)=C Row(D)=D | Col(C)=C Col(D)=D\n" } ], - "source": [ - "def best_response(g: OrdinalGame, player: str, opponent_action: str) -> str:\n", - " \"\"\"\n", - " Meilleure reponse (rang 4 = meilleur) du joueur `player` quand l'adversaire joue `opponent_action`.\n", - " \"\"\"\n", - " if player == \"Row\":\n", - " if opponent_action == \"C\":\n", - " r_C, r_D = g.payoffs_R[0], g.payoffs_R[2]\n", - " else:\n", - " r_C, r_D = g.payoffs_R[1], g.payoffs_R[3]\n", - " else: # Col\n", - " if opponent_action == \"C\":\n", - " r_C, r_D = g.payoffs_C[0], g.payoffs_C[1]\n", - " else:\n", - " r_C, r_D = g.payoffs_C[2], g.payoffs_C[3]\n", - " return \"C\" if r_C >= r_D else \"D\"\n", - "\n", - "\n", - "# Verification : BR coherente avec la litterature R-G\n", - "print(\"=== Best response par jeu ===\")\n", - "for g_name in [\"BattleSexes\", \"StagHunt\", \"Dilemme\", \"Chicken\", \"Harmony\", \"Coordination\"]:\n", - " g = CLASSIC_GAMES[g_name]\n", - " br_row_C = best_response(g, \"Row\", \"C\")\n", - " br_row_D = best_response(g, \"Row\", \"D\")\n", - " br_col_C = best_response(g, \"Col\", \"C\")\n", - " br_col_D = best_response(g, \"Col\", \"D\")\n", - " print(f\"{g_name:14s}: Row(C)={br_row_C} Row(D)={br_row_D} | Col(C)={br_col_C} Col(D)={br_col_D}\")\n" - ] + "source": "def best_response(g: OrdinalGame, player: str, opponent_action: str) -> str:\n \"\"\"\n Meilleure reponse (rang 4 = meilleur) du joueur `player` quand l'adversaire joue `opponent_action`.\n \"\"\"\n if player == \"Row\":\n if opponent_action == \"C\":\n r_C, r_D = g.payoffs_R[0], g.payoffs_R[2]\n else:\n r_C, r_D = g.payoffs_R[1], g.payoffs_R[3]\n else: # Col\n if opponent_action == \"C\":\n r_C, r_D = g.payoffs_C[0], g.payoffs_C[1]\n else:\n r_C, r_D = g.payoffs_C[2], g.payoffs_C[3]\n return \"C\" if r_C >= r_D else \"D\"\n\n\n# Verification : BR coherente avec la litterature R-G\nprint(\"=== Best response par jeu ===\")\nfor g_name in [\"BattleSexes\", \"StagHunt\", \"Dilemme\", \"Chicken\", \"Harmony\", \"Coordination\"]:\n g = CLASSIC_GAMES[g_name]\n br_row_C = best_response(g, \"Row\", \"C\")\n br_row_D = best_response(g, \"Row\", \"D\")\n br_col_C = best_response(g, \"Col\", \"C\")\n br_col_D = best_response(g, \"Col\", \"D\")\n print(f\"{g_name:14s}: Row(C)={br_row_C} Row(D)={br_row_D} | Col(C)={br_col_C} Col(D)={br_col_D}\")\n" }, { "cell_type": "markdown", @@ -257,35 +110,7 @@ }, "tags": [] }, - "source": [ - "## 1. E1 — Placer le papier dans le tableau R-G\n", - "\n", - "Le papier (Mei et al.) distingue six familles de jeux 2×2 mesurées :\n", - "\n", - "1. **win-win** (jeux à équilibre coopératif dominant, type Harmony)\n", - "2. **Dilemme** (Prisoner's Dilemma)\n", - "3. **unfair** (jeux asymétriques type Battle of the Sexes où un joueur a un avantage structurel)\n", - "4. **cyclique** (type Chicken — Rock-Paper-Scissors-like en 2×2)\n", - "5. **biaisé** (jeux à dominance stricte)\n", - "6. **second-best** (jeux où le Nash n'est pas Pareto-Optimal)\n", - "\n", - "**Mapping proposé (RAPPORTÉ, dette §Sources)** :\n", - "\n", - "| Famille papier | Chambre R-G probable | Mapping |\n", - "|---|---|---|\n", - "| win-win | Harmony + Coordination | DÉRIVÉ (Harmony a (C,C) Pareto-dominant) |\n", - "| Dilemme | Dilemme (strict) | DÉRIVÉ (match canonique : CC > DD) |\n", - "| unfair | BattleSexes | DÉRIVÉ (Nash (C,D) et (D,C), chaque joueur départage à l'inverse) |\n", - "| cyclique | Chicken | DÉRIVÉ (R-G \"Rock-Paper-Scissors-like\" en 2×2) |\n", - "| biaisé | jeux à stratégie dominante (subset de Dilemme+Chicken) | RAPPORTÉ — la définition \"biaisé\" du papier n'est pas dans R-G canonique |\n", - "| second-best | subset de StagHunt | DÉRIVÉ (StagHunt a (D,D) Nash mais (C,C) Pareto) |\n", - "\n", - "**Note importante — cyclicité de BattleSexes** :\n", - "\n", - "BattleSexes canonique admet **deux Nash purs** : (C,D) et (D,C). Pour encoder simultanément les deux Nash sans cycler sur le même rang, on choisit Row `rang_R = (2, 3, 4, 1)` (DC > CD > CC > DD, Row préfère (D,C)) et Col `rang_C = (2, 4, 3, 1)` (DC > DD > CD > CC, Col préfère (C,D)). L'ordre strict est préservé, et chaque joueur départage les deux Nash dans la direction qui maximise son payoff — c'est exactement l'essence du conflit de BoS.\n", - "\n", - "**Remarque invariante** : la convention du notebook est `4 = meilleur, 1 = pire` (alignée sur `GameTheory-3 cellule 5`), **et** chaque `payoffs_X` est une permutation stricte de `(1, 2, 3, 4)` — assertion `__post_init__` qui protège mécaniquement contre les fautes de frappe (cf leçon ai-01 dans la review PR #12295).\n" - ] + "source": "## 1. E1 — Placer le papier dans le tableau R-G\n\nLe papier (Mei et al.) distingue six familles de jeux 2×2 mesurées :\n\n1. **win-win** (jeux à équilibre coopératif dominant, type Harmony)\n2. **Dilemme** (Prisoner's Dilemma)\n3. **unfair** (jeux asymétriques type Battle of the Sexes où un joueur a un avantage structurel)\n4. **cyclique** (type Chicken — Rock-Paper-Scissors-like en 2×2)\n5. **biaisé** (jeux à dominance stricte)\n6. **second-best** (jeux où le Nash n'est pas Pareto-Optimal)\n\n**Mapping proposé (RAPPORTÉ, dette §Sources)** :\n\n| Famille papier | Chambre R-G probable | Mapping |\n|---|---|---|\n| win-win | Harmony + Coordination | DÉRIVÉ (Harmony a (C,C) Pareto-dominant) |\n| Dilemme | Dilemme (strict) | DÉRIVÉ (match canonique : CC > DD) |\n| unfair | BattleSexes | DÉRIVÉ (Nash (C,D) et (D,C), chaque joueur départage à l'inverse) |\n| cyclique | Chicken | DÉRIVÉ (R-G \"Rock-Paper-Scissors-like\" en 2×2) |\n| biaisé | jeux à stratégie dominante (subset de Dilemme+Chicken) | RAPPORTÉ — la définition \"biaisé\" du papier n'est pas dans R-G canonique |\n| second-best | subset de StagHunt | DÉRIVÉ (StagHunt a (D,D) Nash mais (C,C) Pareto) |\n\n**Note importante — cyclicité de BattleSexes** :\n\nBattleSexes canonique admet **deux Nash purs** : (C,D) et (D,C). Pour encoder simultanément les deux Nash sans cycler sur le même rang, on choisit Row `rang_R = (2, 3, 4, 1)` (DC > CD > CC > DD, Row préfère (D,C)) et Col `rang_C = (2, 4, 3, 1)` (DC > DD > CD > CC, Col préfère (C,D)). L'ordre strict est préservé, et chaque joueur départage les deux Nash dans la direction qui maximise son payoff — c'est exactement l'essence du conflit de BoS.\n\n**Remarque invariante** : la convention du notebook est `4 = meilleur, 1 = pire` (alignée sur `GameTheory-3 cellule 5`), **et** chaque `payoffs_X` est une permutation stricte de `(1, 2, 3, 4)` — assertion `__post_init__` qui protège mécaniquement contre les fautes de frappe (cf leçon ai-01 dans la review PR #12295).\n" }, { "cell_type": "code", @@ -293,10 +118,10 @@ "id": "cell-06", "metadata": { "execution": { - "iopub.execute_input": "2026-08-23T11:38:05.982623Z", - "iopub.status.busy": "2026-08-23T11:38:05.982435Z", - "iopub.status.idle": "2026-08-23T11:38:05.987173Z", - "shell.execute_reply": "2026-08-23T11:38:05.986458Z" + "iopub.status.busy": "2026-08-24T20:50:05.919780Z", + "iopub.execute_input": "2026-08-24T20:50:05.920114Z", + "shell.execute_reply": "2026-08-24T20:50:05.925816Z", + "iopub.status.idle": "2026-08-24T20:50:05.926652Z" }, "papermill": { "duration": 0.008785, @@ -309,43 +134,12 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "=== E1 : Equilibres de Nash purs par chambre R-G ===\n", - "Jeu Nash purs Cardinalite\n", - "BattleSexes CD, DC 2\n", - "StagHunt CC, DD 2\n", - "Dilemme DD 1\n", - "Chicken CD, DC 2\n", - "Harmony CC 1\n", - "Coordination CC, DD 2\n" - ] + "name": "stdout", + "text": "=== E1 : Equilibres de Nash purs par chambre R-G ===\nJeu Nash purs Cardinalite\nBattleSexes CD, DC 2\nStagHunt CC, DD 2\nDilemme DD 1\nChicken CD, DC 2\nHarmony CC 1\nCoordination CC, DD 2\n" } ], - "source": [ - "# E1 : mesure des Nash purs par chambre R-G (les 6 jeux classiques)\n", - "def find_pure_nash(g: OrdinalGame) -> List[Tuple[str, str]]:\n", - " \"\"\"Nash purs : cases (a,b) telles que a = best_response(Row) et b = best_response(Col).\"\"\"\n", - " results = []\n", - " for row_a in [\"C\", \"D\"]:\n", - " for col_a in [\"C\", \"D\"]:\n", - " br_row = best_response(g, \"Row\", col_a)\n", - " br_col = best_response(g, \"Col\", row_a)\n", - " if row_a == br_row and col_a == br_col:\n", - " results.append((row_a, col_a))\n", - " return results\n", - "\n", - "\n", - "print(\"=== E1 : Equilibres de Nash purs par chambre R-G ===\")\n", - "print(f\"{'Jeu':15s} {'Nash purs':15s} {'Cardinalite'}\")\n", - "for g_name in [\"BattleSexes\", \"StagHunt\", \"Dilemme\", \"Chicken\", \"Harmony\", \"Coordination\"]:\n", - " g = CLASSIC_GAMES[g_name]\n", - " nash = find_pure_nash(g)\n", - " cardinalite = \"0\" if not nash else f\"{len(nash)}\"\n", - " nash_str = \", \".join(f\"{a}{b}\" for a, b in nash) if nash else \"(aucun)\"\n", - " print(f\"{g_name:15s} {nash_str:15s} {cardinalite}\")\n" - ] + "source": "# E1 : mesure des Nash purs par chambre R-G (les 6 jeux classiques)\ndef find_pure_nash(g: OrdinalGame) -> List[Tuple[str, str]]:\n \"\"\"Nash purs : cases (a,b) telles que a = best_response(Row) et b = best_response(Col).\"\"\"\n results = []\n for row_a in [\"C\", \"D\"]:\n for col_a in [\"C\", \"D\"]:\n br_row = best_response(g, \"Row\", col_a)\n br_col = best_response(g, \"Col\", row_a)\n if row_a == br_row and col_a == br_col:\n results.append((row_a, col_a))\n return results\n\n\nprint(\"=== E1 : Equilibres de Nash purs par chambre R-G ===\")\nprint(f\"{'Jeu':15s} {'Nash purs':15s} {'Cardinalite'}\")\nfor g_name in [\"BattleSexes\", \"StagHunt\", \"Dilemme\", \"Chicken\", \"Harmony\", \"Coordination\"]:\n g = CLASSIC_GAMES[g_name]\n nash = find_pure_nash(g)\n cardinalite = \"0\" if not nash else f\"{len(nash)}\"\n nash_str = \", \".join(f\"{a}{b}\" for a, b in nash) if nash else \"(aucun)\"\n print(f\"{g_name:15s} {nash_str:15s} {cardinalite}\")\n" }, { "cell_type": "markdown", @@ -360,23 +154,7 @@ }, "tags": [] }, - "source": [ - "### Lecture de E1\n", - "\n", - "**Trois chambres à 1 Nash** : Dilemme (D,D unique — grim trigger), Harmony (C,C unique — coopératif dominant).\n", - "\n", - "**Trois chambres à 2 Nash** : BattleSexes ((C,D) et (D,C) — cyclicité = essence du conflit), StagHunt ((C,C) et (D,D) — deux équilibres, l'un risqué, l'autre sûr), Chicken ((C,D) et (D,C) — mêmes Nash que BoS mais avec conflict plus marqué), Coordination ((C,C) et (D,D) — deux équilibres Pareto-optimaux).\n", - "\n", - "**Pattern attendu du papier sur le joueur LLM** :\n", - "\n", - "- En Dilemme → grim trigger immédiat (toujours D) ✓\n", - "- en Harmony → C permanent ✓\n", - "- en BattleSexes / Coordination / StagHunt → **alternance si dissociation est absente**, **C-permanent (ou D-permanent) si dissociation est présente** (le modèle colle à son option préférée).\n", - "\n", - "C'est exactement ce que les cellules E2-E4 mesurent.\n", - "\n", - "**Note** : les 6 jeux utilisent maintenant l'invariant `sorted(payoffs_X) == [1, 2, 3, 4]` — chaque chambre a des rangs stricts, donc une case unique dans le tableau périodique R-G. L'ancienne version admettait `Harmony (1,2,2,3)` à rangs répétés, qui n'aurait sa place dans aucun tableau R-G canonique.\n" - ] + "source": "### Lecture de E1\n\n**Trois chambres à 1 Nash** : Dilemme (D,D unique — grim trigger), Harmony (C,C unique — coopératif dominant).\n\n**Trois chambres à 2 Nash** : BattleSexes ((C,D) et (D,C) — cyclicité = essence du conflit), StagHunt ((C,C) et (D,D) — deux équilibres, l'un risqué, l'autre sûr), Chicken ((C,D) et (D,C) — mêmes Nash que BoS mais avec conflict plus marqué), Coordination ((C,C) et (D,D) — deux équilibres Pareto-optimaux).\n\n**Pattern attendu du papier sur le joueur LLM** :\n\n- En Dilemme → grim trigger immédiat (toujours D) ✓\n- en Harmony → C permanent ✓\n- en BattleSexes / Coordination / StagHunt → **alternance si dissociation est absente**, **C-permanent (ou D-permanent) si dissociation est présente** (le modèle colle à son option préférée).\n\nC'est exactement ce que les cellules E2-E4 mesurent.\n\n**Note** : les 6 jeux utilisent maintenant l'invariant `sorted(payoffs_X) == [1, 2, 3, 4]` — chaque chambre a des rangs stricts, donc une case unique dans le tableau périodique R-G. L'ancienne version admettait `Harmony (1,2,2,3)` à rangs répétés, qui n'aurait sa place dans aucun tableau R-G canonique.\n" }, { "cell_type": "markdown", @@ -391,15 +169,7 @@ }, "tags": [] }, - "source": [ - "## 2. E2 — Le joueur LLM face à deux jeux\n", - "\n", - "Le protocole du papier (Mei et al.) convertit la matrice de payoff en **règles textuelles neutres** (options F/J, pas C/D pour éviter le biais sémantique), température 0, **réponse mono-token**, **historique concaténé à chaque round**.\n", - "\n", - "Pour ce notebook, on travaille en ordinal strict : le joueur **lit l'historique** des rounds passés (séquence d'actions Row, Col) et **prédit** la prochaine action Col pour choisir sa meilleure réponse. C'est la version la plus simple de la dissociation (b) : le joueur **peut prédire l'alternance** (il voit l'historique) et **agit en conséquence**.\n", - "\n", - "**Stub C.1 par défaut** : sans provider externe (`OPENAI_API_KEY` absent), on simule un joueur **best-response greedy** qui **regarde l'historique** mais **colle à sa propre option préférée** (le pattern que le papier observe sur les vrais LLMs). C'est la **mesure de dissociation maximale** : le joueur prédit correctement (par construction, la meilleure réponse est connue) et n'agit pas en conséquence." - ] + "source": "## 2. E2 — Le joueur LLM face à deux jeux\n\nLe protocole du papier (Mei et al.) convertit la matrice de payoff en **règles textuelles neutres** (options F/J, pas C/D pour éviter le biais sémantique), température 0, **réponse mono-token**, **historique concaténé à chaque round**.\n\nPour ce notebook, on travaille en ordinal strict : le joueur **lit l'historique** des rounds passés (séquence d'actions Row, Col) et **prédit** la prochaine action Col pour choisir sa meilleure réponse. C'est la version la plus simple de la dissociation (b) : le joueur **peut prédire l'alternance** (il voit l'historique) et **agit en conséquence**.\n\n**Stub C.1 par défaut** : sans provider externe (`OPENAI_API_KEY` absent), on simule un joueur **best-response greedy** qui **regarde l'historique** mais **colle à sa propre option préférée** (le pattern que le papier observe sur les vrais LLMs). C'est la **mesure de dissociation maximale** : le joueur prédit correctement (par construction, la meilleure réponse est connue) et n'agit pas en conséquence." }, { "cell_type": "code", @@ -407,10 +177,10 @@ "id": "cell-09", "metadata": { "execution": { - "iopub.execute_input": "2026-08-23T11:38:06.004021Z", - "iopub.status.busy": "2026-08-23T11:38:06.003590Z", - "iopub.status.idle": "2026-08-23T11:38:06.026107Z", - "shell.execute_reply": "2026-08-23T11:38:06.025490Z" + "iopub.status.busy": "2026-08-24T20:50:05.928439Z", + "iopub.execute_input": "2026-08-24T20:50:05.928745Z", + "shell.execute_reply": "2026-08-24T20:50:05.956879Z", + "iopub.status.idle": "2026-08-24T20:50:05.957829Z" }, "papermill": { "duration": 0.026568, @@ -423,164 +193,12 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "=== E2 : cinq modeles de joueur x six chambres (10 rounds) ===\n", - "chambre sticky/C sticky/D alternating noisy best_response\n", - "BattleSexes coop=100% nash=0% coop=0% nash=0% coop=50% nash=0% coop=10% nash=90% coop=0% nash=100%\n", - "StagHunt coop=100% nash=100% coop=0% nash=100% coop=50% nash=100% coop=60% nash=90% coop=100% nash=100%\n", - "Dilemme coop=100% nash=0% coop=0% nash=100% coop=50% nash=50% coop=0% nash=40% coop=0% nash=90%\n", - "Chicken coop=100% nash=0% coop=0% nash=0% coop=50% nash=0% coop=10% nash=90% coop=0% nash=100%\n", - "Harmony coop=100% nash=100% coop=0% nash=0% coop=50% nash=50% coop=50% nash=50% coop=100% nash=100%\n", - "Coordination coop=100% nash=100% coop=0% nash=100% coop=50% nash=100% coop=60% nash=90% coop=100% nash=100%\n", - "\n", - "=== E2 : trajectoires (verification -- chaque regle produit bien la sienne) ===\n", - "Dilemme sticky/C : CC CC CC CC CC CC CC CC ...\n", - "Dilemme sticky/D : DD DD DD DD DD DD DD DD ...\n", - "Dilemme alternating : CC DD CC DD CC DD CC DD ...\n", - "Dilemme noisy : CD DC CD DD DD DD CD CD ...\n", - "Dilemme best_response : CD DD DD DD DD DD DD DD ...\n", - "BattleSexes sticky/C : CC CC CC CC CC CC CC CC ...\n", - "BattleSexes sticky/D : DD DD DD DD DD DD DD DD ...\n", - "BattleSexes alternating : CC DD CC DD CC DD CC DD ...\n", - "BattleSexes noisy : CD CC CD CD CD CD DC CD ...\n", - "BattleSexes best_response : CD CD CD CD CD CD CD CD ...\n", - "StagHunt sticky/C : CC CC CC CC CC CC CC CC ...\n", - "StagHunt sticky/D : DD DD DD DD DD DD DD DD ...\n", - "StagHunt alternating : CC DD CC DD CC DD CC DD ...\n", - "StagHunt noisy : CC CD CC CC CC CC DD CC ...\n", - "StagHunt best_response : CC CC CC CC CC CC CC CC ...\n" - ] + "name": "stdout", + "text": "=== E2 : cinq modeles de joueur x six chambres (10 rounds) ===\nchambre sticky/C sticky/D alternating noisy best_response\nBattleSexes coop=100% nash=0% coop=0% nash=0% coop=50% nash=0% coop=10% nash=90% coop=0% nash=100%\nStagHunt coop=100% nash=100% coop=0% nash=100% coop=50% nash=100% coop=60% nash=90% coop=100% nash=100%\nDilemme coop=100% nash=0% coop=0% nash=100% coop=50% nash=50% coop=0% nash=40% coop=0% nash=90%\nChicken coop=100% nash=0% coop=0% nash=0% coop=50% nash=0% coop=10% nash=90% coop=0% nash=100%\nHarmony coop=100% nash=100% coop=0% nash=0% coop=50% nash=50% coop=50% nash=50% coop=100% nash=100%\nCoordination coop=100% nash=100% coop=0% nash=100% coop=50% nash=100% coop=60% nash=90% coop=100% nash=100%\n\n=== E2 : trajectoires (verification -- chaque regle produit bien la sienne) ===\nDilemme sticky/C : CC CC CC CC CC CC CC CC ...\nDilemme sticky/D : DD DD DD DD DD DD DD DD ...\nDilemme alternating : CC DD CC DD CC DD CC DD ...\nDilemme noisy : CD DC CD DD DD DD CD CD ...\nDilemme best_response : CD DD DD DD DD DD DD DD ...\nBattleSexes sticky/C : CC CC CC CC CC CC CC CC ...\nBattleSexes sticky/D : DD DD DD DD DD DD DD DD ...\nBattleSexes alternating : CC DD CC DD CC DD CC DD ...\nBattleSexes noisy : CD CC CD CD CD CD DC CD ...\nBattleSexes best_response : CD CD CD CD CD CD CD CD ...\nStagHunt sticky/C : CC CC CC CC CC CC CC CC ...\nStagHunt sticky/D : DD DD DD DD DD DD DD DD ...\nStagHunt alternating : CC DD CC DD CC DD CC DD ...\nStagHunt noisy : CC CD CC CC CC CC DD CC ...\nStagHunt best_response : CC CC CC CC CC CC CC CC ...\n" } ], - "source": [ - "def simulate_player(g: OrdinalGame, player: str, history: List[Tuple[str, str]],\n", - " mode: str = \"sticky_preferred\", first_action: str = \"C\",\n", - " rng=None, epsilon: float = 0.1) -> str:\n", - " \"\"\"\n", - " Simule UN coup du joueur `player` dans `g`, etant donne `history`.\n", - " Le comportement EMERGE de la regle, round apres round : aucune sequence\n", - " n'est pre-calculee par l'appelant.\n", - "\n", - " mode :\n", - " - \"sticky_preferred\" : colle a sa premiere action jouee (pattern observe sur vrais LLMs)\n", - " - \"alternating\" : alterne C, D, C, D... (baseline theorique)\n", - " - \"best_response\" : meilleure reponse au dernier coup adverse (reference)\n", - " - \"noisy\" : meilleure reponse + probabilite epsilon de devier (`rng`, reproductible)\n", - " - \"scot\" : PREDIT le coup adverse (sa BR contre mon dernier coup), puis joue\n", - " sa BR contre cette prediction (Social Chain-of-Thought, E2bis)\n", - "\n", - " first_action : action d'ouverture quand l'historique propre est vide. Parametrable :\n", - " un sticky qui ouvre par D doit produire une trajectoire differente du sticky qui ouvre par C.\n", - " \"\"\"\n", - " def other() -> str:\n", - " return \"Col\" if player == \"Row\" else \"Row\"\n", - "\n", - " def my_actions() -> List[str]:\n", - " # Mes coups deja joues -- le \"?\" marque le co-joueur pas encore decide\n", - " out = []\n", - " for r, c in history:\n", - " a = r if player == \"Row\" else c\n", - " if a != \"?\":\n", - " out.append(a)\n", - " return out\n", - "\n", - " mine = my_actions()\n", - "\n", - " if mode == \"scot\":\n", - " if not mine:\n", - " return first_action\n", - " adversaire_predira = best_response(g, other(), mine[-1])\n", - " return best_response(g, player, adversaire_predira)\n", - "\n", - " if mode == \"noisy\":\n", - " opp = [c if player == \"Row\" else r for r, c in history]\n", - " opp_last = [a for a in opp if a != \"?\"]\n", - " if not opp_last:\n", - " return first_action\n", - " base = best_response(g, player, opp_last[-1])\n", - " if rng is not None and rng.random() < epsilon:\n", - " return \"D\" if base == \"C\" else \"C\"\n", - " return base\n", - "\n", - " if mode == \"best_response\":\n", - " if not history:\n", - " return first_action\n", - " opp_last = history[-1][1] if player == \"Row\" else history[-1][0]\n", - " if opp_last == \"?\":\n", - " return first_action\n", - " return best_response(g, player, opp_last)\n", - "\n", - " if mode == \"sticky_preferred\":\n", - " return mine[0] if mine else first_action\n", - "\n", - " if mode == \"alternating\":\n", - " return \"C\" if len(mine) % 2 == 0 else \"D\"\n", - "\n", - " raise ValueError(f\"Mode inconnu : {mode}\")\n", - "\n", - "\n", - "def play_repeated(g: OrdinalGame, n_rounds: int = 10, mode: str = \"sticky_preferred\",\n", - " first_action: str = \"C\", seed: int = None, epsilon: float = 0.1\n", - " ) -> List[Tuple[str, str]]:\n", - " \"\"\"\n", - " Joue `g` sur n_rounds. Chaque round, Row puis Col jouent TOUS DEUX via\n", - " simulate_player, pour TOUS les modes : le comportement emerge de la regle,\n", - " il n'est jamais ecrit en dur. Col voit l'action de Row du round courant\n", - " (convention sequentielle, meme information que le mode best_response).\n", - " \"\"\"\n", - " rng = np.random.default_rng(seed) if mode == \"noisy\" else None\n", - " history: List[Tuple[str, str]] = []\n", - " for _ in range(n_rounds):\n", - " row_a = simulate_player(g, \"Row\", history, mode, first_action, rng, epsilon)\n", - " col_a = simulate_player(g, \"Col\", history + [(row_a, \"?\")], mode, first_action, rng, epsilon)\n", - " history.append((row_a, col_a))\n", - " return history\n", - "\n", - "\n", - "def cooperation_rate(history: List[Tuple[str, str]]) -> float:\n", - " if not history:\n", - " return 0.0\n", - " return sum(1 for r, c in history if r == \"C\" and c == \"C\") / len(history)\n", - "\n", - "\n", - "def defection_rate(history: List[Tuple[str, str]]) -> float:\n", - " if not history:\n", - " return 0.0\n", - " return sum(1 for r, c in history if r == \"D\" or c == \"D\") / len(history)\n", - "\n", - "\n", - "def nash_attainment_rate(history: List[Tuple[str, str]], nash_set: List[Tuple[str, str]]) -> float:\n", - " if not nash_set:\n", - " return 0.0\n", - " return sum(1 for r, c in history if (r, c) in nash_set) / len(history)\n", - "\n", - "\n", - "MODES_E2 = [(\"sticky/C\", \"sticky_preferred\", \"C\"), (\"sticky/D\", \"sticky_preferred\", \"D\"),\n", - " (\"alternating\", \"alternating\", \"C\"), (\"noisy\", \"noisy\", \"C\"),\n", - " (\"best_response\", \"best_response\", \"C\")]\n", - "\n", - "print(\"=== E2 : cinq modeles de joueur x six chambres (10 rounds) ===\")\n", - "print(f\"{'chambre':14s} \" + \" \".join(f\"{lbl:>17s}\" for lbl, _, _ in MODES_E2))\n", - "for g_name in [\"BattleSexes\", \"StagHunt\", \"Dilemme\", \"Chicken\", \"Harmony\", \"Coordination\"]:\n", - " g = CLASSIC_GAMES[g_name]\n", - " nash_set = find_pure_nash(g)\n", - " cells = []\n", - " for _, mode, fa in MODES_E2:\n", - " h = play_repeated(g, n_rounds=10, mode=mode, first_action=fa, seed=0, epsilon=0.2)\n", - " cells.append(f\"coop={cooperation_rate(h):.0%} nash={nash_attainment_rate(h, nash_set):.0%}\")\n", - " print(f\"{g_name:14s} \" + \" \".join(f\"{c:>17s}\" for c in cells))\n", - "\n", - "print()\n", - "print(\"=== E2 : trajectoires (verification -- chaque regle produit bien la sienne) ===\")\n", - "for g_name in [\"Dilemme\", \"BattleSexes\", \"StagHunt\"]:\n", - " g = CLASSIC_GAMES[g_name]\n", - " for lbl, mode, fa in MODES_E2:\n", - " h = play_repeated(g, n_rounds=10, mode=mode, first_action=fa, seed=0, epsilon=0.2)\n", - " seq = \" \".join(r + c for r, c in h[:8])\n", - " print(f\"{g_name:14s} {lbl:>14s} : {seq} ...\")\n" - ] + "source": "def simulate_player(g: OrdinalGame, player: str, history: List[Tuple[str, str]],\n mode: str = \"sticky_preferred\", first_action: str = \"C\",\n rng=None, epsilon: float = 0.1) -> str:\n \"\"\"\n Simule UN coup du joueur `player` dans `g`, etant donne `history`.\n Le comportement EMERGE de la regle, round apres round : aucune sequence\n n'est pre-calculee par l'appelant.\n\n mode :\n - \"sticky_preferred\" : colle a sa premiere action jouee (pattern observe sur vrais LLMs)\n - \"alternating\" : alterne C, D, C, D... (baseline theorique)\n - \"best_response\" : meilleure reponse au dernier coup adverse (reference)\n - \"noisy\" : meilleure reponse + probabilite epsilon de devier (`rng`, reproductible)\n - \"scot\" : PREDIT le coup adverse (sa BR contre mon dernier coup), puis joue\n sa BR contre cette prediction (Social Chain-of-Thought, E2bis)\n\n first_action : action d'ouverture quand l'historique propre est vide. Parametrable :\n un sticky qui ouvre par D doit produire une trajectoire differente du sticky qui ouvre par C.\n \"\"\"\n def other() -> str:\n return \"Col\" if player == \"Row\" else \"Row\"\n\n def my_actions() -> List[str]:\n # Mes coups deja joues -- le \"?\" marque le co-joueur pas encore decide\n out = []\n for r, c in history:\n a = r if player == \"Row\" else c\n if a != \"?\":\n out.append(a)\n return out\n\n mine = my_actions()\n\n if mode == \"scot\":\n if not mine:\n return first_action\n adversaire_predira = best_response(g, other(), mine[-1])\n return best_response(g, player, adversaire_predira)\n\n if mode == \"noisy\":\n opp = [c if player == \"Row\" else r for r, c in history]\n opp_last = [a for a in opp if a != \"?\"]\n if not opp_last:\n return first_action\n base = best_response(g, player, opp_last[-1])\n if rng is not None and rng.random() < epsilon:\n return \"D\" if base == \"C\" else \"C\"\n return base\n\n if mode == \"best_response\":\n if not history:\n return first_action\n opp_last = history[-1][1] if player == \"Row\" else history[-1][0]\n if opp_last == \"?\":\n return first_action\n return best_response(g, player, opp_last)\n\n if mode == \"sticky_preferred\":\n return mine[0] if mine else first_action\n\n if mode == \"alternating\":\n return \"C\" if len(mine) % 2 == 0 else \"D\"\n\n raise ValueError(f\"Mode inconnu : {mode}\")\n\n\ndef play_repeated(g: OrdinalGame, n_rounds: int = 10, mode: str = \"sticky_preferred\",\n first_action: str = \"C\", seed: int = None, epsilon: float = 0.1\n ) -> List[Tuple[str, str]]:\n \"\"\"\n Joue `g` sur n_rounds. Chaque round, Row puis Col jouent TOUS DEUX via\n simulate_player, pour TOUS les modes : le comportement emerge de la regle,\n il n'est jamais ecrit en dur. Col voit l'action de Row du round courant\n (convention sequentielle, meme information que le mode best_response).\n \"\"\"\n rng = np.random.default_rng(seed) if mode == \"noisy\" else None\n history: List[Tuple[str, str]] = []\n for _ in range(n_rounds):\n row_a = simulate_player(g, \"Row\", history, mode, first_action, rng, epsilon)\n col_a = simulate_player(g, \"Col\", history + [(row_a, \"?\")], mode, first_action, rng, epsilon)\n history.append((row_a, col_a))\n return history\n\n\ndef cooperation_rate(history: List[Tuple[str, str]]) -> float:\n if not history:\n return 0.0\n return sum(1 for r, c in history if r == \"C\" and c == \"C\") / len(history)\n\n\ndef defection_rate(history: List[Tuple[str, str]]) -> float:\n if not history:\n return 0.0\n return sum(1 for r, c in history if r == \"D\" or c == \"D\") / len(history)\n\n\ndef nash_attainment_rate(history: List[Tuple[str, str]], nash_set: List[Tuple[str, str]]) -> float:\n if not nash_set:\n return 0.0\n return sum(1 for r, c in history if (r, c) in nash_set) / len(history)\n\n\nMODES_E2 = [(\"sticky/C\", \"sticky_preferred\", \"C\"), (\"sticky/D\", \"sticky_preferred\", \"D\"),\n (\"alternating\", \"alternating\", \"C\"), (\"noisy\", \"noisy\", \"C\"),\n (\"best_response\", \"best_response\", \"C\")]\n\nprint(\"=== E2 : cinq modeles de joueur x six chambres (10 rounds) ===\")\nprint(f\"{'chambre':14s} \" + \" \".join(f\"{lbl:>17s}\" for lbl, _, _ in MODES_E2))\nfor g_name in [\"BattleSexes\", \"StagHunt\", \"Dilemme\", \"Chicken\", \"Harmony\", \"Coordination\"]:\n g = CLASSIC_GAMES[g_name]\n nash_set = find_pure_nash(g)\n cells = []\n for _, mode, fa in MODES_E2:\n h = play_repeated(g, n_rounds=10, mode=mode, first_action=fa, seed=0, epsilon=0.2)\n cells.append(f\"coop={cooperation_rate(h):.0%} nash={nash_attainment_rate(h, nash_set):.0%}\")\n print(f\"{g_name:14s} \" + \" \".join(f\"{c:>17s}\" for c in cells))\n\nprint()\nprint(\"=== E2 : trajectoires (verification -- chaque regle produit bien la sienne) ===\")\nfor g_name in [\"Dilemme\", \"BattleSexes\", \"StagHunt\"]:\n g = CLASSIC_GAMES[g_name]\n for lbl, mode, fa in MODES_E2:\n h = play_repeated(g, n_rounds=10, mode=mode, first_action=fa, seed=0, epsilon=0.2)\n seq = \" \".join(r + c for r, c in h[:8])\n print(f\"{g_name:14s} {lbl:>14s} : {seq} ...\")\n" }, { "cell_type": "code", @@ -588,10 +206,10 @@ "id": "cell-10", "metadata": { "execution": { - "iopub.execute_input": "2026-08-23T11:38:06.033401Z", - "iopub.status.busy": "2026-08-23T11:38:06.033004Z", - "iopub.status.idle": "2026-08-23T11:38:06.039524Z", - "shell.execute_reply": "2026-08-23T11:38:06.038726Z" + "iopub.status.busy": "2026-08-24T20:50:05.959630Z", + "iopub.execute_input": "2026-08-24T20:50:05.959881Z", + "shell.execute_reply": "2026-08-24T20:50:05.965233Z", + "iopub.status.idle": "2026-08-24T20:50:05.966079Z" }, "papermill": { "duration": 0.010685, @@ -604,56 +222,12 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "=== E2bis : SCoT vs best_response -- taux d'atteinte de Nash (10 rounds) ===\n", - "chambre BR (niv.1) SCoT (niv.2) delta\n", - "BattleSexes 100% 0% -100%\n", - "StagHunt 100% 100% +0%\n", - "Dilemme 90% 90% +0%\n", - "Chicken 100% 0% -100%\n", - "Harmony 100% 100% +0%\n", - "Coordination 100% 100% +0%\n", - "\n", - "=== E2bis : le mecanisme ===\n", - "BattleSexes, SCoT, 6 rounds : CC CC CC CC CC CC\n", - "Round 2 : mon dernier coup est C, je predits que Col jouera sa BR contre C,\n", - "je joue ma BR contre cette prediction... et le round suivant reprend identique.\n", - "En chambre a conflit, chacun predit que l'autre va ceder, s'ajuste... et reste\n", - "sur son option preferee : cercle auto-confirmatoire de niveau 2. Le sticky n'est\n", - "plus une regle figee, c'est la CONSEQUENCE d'une prediction naive.\n" - ] + "name": "stdout", + "text": "=== E2bis : SCoT vs best_response -- taux d'atteinte de Nash (10 rounds) ===\nchambre BR (niv.1) SCoT (niv.2) delta\nBattleSexes 100% 0% -100%\nStagHunt 100% 100% +0%\nDilemme 90% 90% +0%\nChicken 100% 0% -100%\nHarmony 100% 100% +0%\nCoordination 100% 100% +0%\n\n=== E2bis : le mecanisme ===\nBattleSexes, SCoT, 6 rounds : CC CC CC CC CC CC\nRound 2 : mon dernier coup est C, je predits que Col jouera sa BR contre C,\nje joue ma BR contre cette prediction... et le round suivant reprend identique.\nEn chambre a conflit, chacun predit que l'autre va ceder, s'ajuste... et reste\nsur son option preferee : cercle auto-confirmatoire de niveau 2. Le sticky n'est\nplus une regle figee, c'est la CONSEQUENCE d'une prediction naive.\n" } ], - "source": [ - "# E2bis (apport (c) du papier) : SCoT -- predire le coup adverse AVANT de choisir.\n", - "# Niveau 1 : best_response -- reagir au dernier coup adverse, ne rien predire.\n", - "# Niveau 2 : SCoT -- predire \"l'adversaire jouera sa BR contre mon dernier coup\",\n", - "# puis jouer sa BR contre CETTE prediction. Le jeu n'a pas change :\n", - "# seule la regle de decision du joueur a change (transmutation).\n", - "print(\"=== E2bis : SCoT vs best_response -- taux d'atteinte de Nash (10 rounds) ===\")\n", - "print(f\"{'chambre':14s} {'BR (niv.1)':>12s} {'SCoT (niv.2)':>14s} {'delta':>8s}\")\n", - "for g_name in [\"BattleSexes\", \"StagHunt\", \"Dilemme\", \"Chicken\", \"Harmony\", \"Coordination\"]:\n", - " g = CLASSIC_GAMES[g_name]\n", - " nash_set = find_pure_nash(g)\n", - " h_br = play_repeated(g, n_rounds=10, mode=\"best_response\")\n", - " h_scot = play_repeated(g, n_rounds=10, mode=\"scot\")\n", - " n_br = nash_attainment_rate(h_br, nash_set)\n", - " n_scot = nash_attainment_rate(h_scot, nash_set)\n", - " print(f\"{g_name:14s} {n_br:12.0%} {n_scot:14.0%} {n_scot - n_br:+8.0%}\")\n", - "\n", - "print()\n", - "print(\"=== E2bis : le mecanisme ===\")\n", - "g = CLASSIC_GAMES[\"BattleSexes\"]\n", - "h = play_repeated(g, n_rounds=6, mode=\"scot\")\n", - "print(f\"BattleSexes, SCoT, 6 rounds : {' '.join(r + c for r, c in h)}\")\n", - "print(\"Round 2 : mon dernier coup est C, je predits que Col jouera sa BR contre C,\")\n", - "print(\"je joue ma BR contre cette prediction... et le round suivant reprend identique.\")\n", - "print(\"En chambre a conflit, chacun predit que l'autre va ceder, s'ajuste... et reste\")\n", - "print(\"sur son option preferee : cercle auto-confirmatoire de niveau 2. Le sticky n'est\")\n", - "print(\"plus une regle figee, c'est la CONSEQUENCE d'une prediction naive.\")\n" - ] + "source": "# E2bis (apport (c) du papier) : SCoT -- predire le coup adverse AVANT de choisir.\n# Niveau 1 : best_response -- reagir au dernier coup adverse, ne rien predire.\n# Niveau 2 : SCoT -- predire \"l'adversaire jouera sa BR contre mon dernier coup\",\n# puis jouer sa BR contre CETTE prediction. Le jeu n'a pas change :\n# seule la regle de decision du joueur a change (transmutation).\nprint(\"=== E2bis : SCoT vs best_response -- taux d'atteinte de Nash (10 rounds) ===\")\nprint(f\"{'chambre':14s} {'BR (niv.1)':>12s} {'SCoT (niv.2)':>14s} {'delta':>8s}\")\nfor g_name in [\"BattleSexes\", \"StagHunt\", \"Dilemme\", \"Chicken\", \"Harmony\", \"Coordination\"]:\n g = CLASSIC_GAMES[g_name]\n nash_set = find_pure_nash(g)\n h_br = play_repeated(g, n_rounds=10, mode=\"best_response\")\n h_scot = play_repeated(g, n_rounds=10, mode=\"scot\")\n n_br = nash_attainment_rate(h_br, nash_set)\n n_scot = nash_attainment_rate(h_scot, nash_set)\n print(f\"{g_name:14s} {n_br:12.0%} {n_scot:14.0%} {n_scot - n_br:+8.0%}\")\n\nprint()\nprint(\"=== E2bis : le mecanisme ===\")\ng = CLASSIC_GAMES[\"BattleSexes\"]\nh = play_repeated(g, n_rounds=6, mode=\"scot\")\nprint(f\"BattleSexes, SCoT, 6 rounds : {' '.join(r + c for r, c in h)}\")\nprint(\"Round 2 : mon dernier coup est C, je predits que Col jouera sa BR contre C,\")\nprint(\"je joue ma BR contre cette prediction... et le round suivant reprend identique.\")\nprint(\"En chambre a conflit, chacun predit que l'autre va ceder, s'ajuste... et reste\")\nprint(\"sur son option preferee : cercle auto-confirmatoire de niveau 2. Le sticky n'est\")\nprint(\"plus une regle figee, c'est la CONSEQUENCE d'une prediction naive.\")\n" }, { "cell_type": "markdown", @@ -668,23 +242,7 @@ }, "tags": [] }, - "source": [ - "### Lecture de E2\n", - "\n", - "Ce que les cinq modes montrent (outputs committés ci-dessus) :\n", - "\n", - "- **Le comportement émerge de la règle.** `sticky/C` produit CC partout, `sticky/D` produit DD partout — deux trajectoires différentes issues de la *même* règle `simulate_player`, seule la première action change. `alternating` produit l'alternance exacte, `noisy` dévie de la meilleure réponse avec probabilité ε=0.2 (seed 0, reproductible), `best_response` converge vers l'équilibre.\n", - "- **E2 ne rend plus `coop=100%` sur les six chambres indistinctement.** Le tableau des coop/nash varie selon le couple (chambre, modèle de joueur) : la mesure discrimine réellement les modèles de joueur — c'était l'objectif.\n", - "- **Paysage de performance** (apport (a) du papier) : la ligne `sticky/C` atteint 100% de Nash dans les chambres où CC est un équilibre (StagHunt, Harmony, Coordination) et 0% dans les chambres à conflit (BattleSexes, Dilemme, Chicken) ; `sticky/D` renverse le tableau exactement là où l'ouverture compte — Dilemme (0%→100% : DD y est l'équilibre) et Harmony (100%→0%) — et reste à 100% dans les chambres à double équilibre CC/DD (StagHunt, Coordination). La performance dépend du couple (règle, chambre), pas de la chambre seule.\n", - "\n", - "### Lecture de E2bis — SCoT, effet chiffré (apport (c))\n", - "\n", - "Le résultat mesuré est **contre-intuitif et instructif** : SCoT tombe à **0% d'atteinte de Nash en BattleSexes et Chicken** — les chambres à conflit — là où la meilleure réponse de niveau 1 converge vers l'équilibre ; delta nul sur les quatre autres chambres. **Le delta n'est jamais positif dans ce simulateur.**\n", - "\n", - "**Mécanisme** (output ci-dessus) : en chambre à conflit, chaque joueur prédit que l'adversaire jouera sa BR contre mon dernier coup — c'est-à-dire qu'il va me céder — puis joue sa BR contre cette prédiction, qui est de garder son camp. Le cercle de niveau 2 est **auto-confirmatoire** : le sticky n'est plus une règle figée, c'est la *conséquence* d'une prédiction naïve.\n", - "\n", - "**Différence avec le papier, assumée** : Mei et al. observent que le SCoT prompting *augmente* la coordination des vrais LLMs ; notre simulé de niveau 2 montre l'inverse. C'est précisément la comparaison qui instruit : ce n'est pas *prédire* qui aide, c'est prédire **mieux que « la BR contre moi »** — un vrai LLM prédit le comportement réel de l'adversaire, y compris sa propre prédiction. L'écart simulé/réel mesure la profondeur de raisonnement social que le prompting déclenche chez un vrai modèle, et que ce simulateur n'a pas.\n" - ] + "source": "### Lecture de E2\n\nCe que les cinq modes montrent (outputs committés ci-dessus) :\n\n- **Le comportement émerge de la règle.** `sticky/C` produit CC partout, `sticky/D` produit DD partout — deux trajectoires différentes issues de la *même* règle `simulate_player`, seule la première action change. `alternating` produit l'alternance exacte, `noisy` dévie de la meilleure réponse avec probabilité ε=0.2 (seed 0, reproductible), `best_response` converge vers l'équilibre.\n- **E2 ne rend plus `coop=100%` sur les six chambres indistinctement.** Le tableau des coop/nash varie selon le couple (chambre, modèle de joueur) : la mesure discrimine réellement les modèles de joueur — c'était l'objectif.\n- **Paysage de performance** (apport (a) du papier) : la ligne `sticky/C` atteint 100% de Nash dans les chambres où CC est un équilibre (StagHunt, Harmony, Coordination) et 0% dans les chambres à conflit (BattleSexes, Dilemme, Chicken) ; `sticky/D` renverse le tableau exactement là où l'ouverture compte — Dilemme (0%→100% : DD y est l'équilibre) et Harmony (100%→0%) — et reste à 100% dans les chambres à double équilibre CC/DD (StagHunt, Coordination). La performance dépend du couple (règle, chambre), pas de la chambre seule.\n\n### Lecture de E2bis — SCoT, effet chiffré (apport (c))\n\nLe résultat mesuré est **contre-intuitif et instructif** : SCoT tombe à **0% d'atteinte de Nash en BattleSexes et Chicken** — les chambres à conflit — là où la meilleure réponse de niveau 1 converge vers l'équilibre ; delta nul sur les quatre autres chambres. **Le delta n'est jamais positif dans ce simulateur.**\n\n**Mécanisme** (output ci-dessus) : en chambre à conflit, chaque joueur prédit que l'adversaire jouera sa BR contre mon dernier coup — c'est-à-dire qu'il va me céder — puis joue sa BR contre cette prédiction, qui est de garder son camp. Le cercle de niveau 2 est **auto-confirmatoire** : le sticky n'est plus une règle figée, c'est la *conséquence* d'une prédiction naïve.\n\n**Différence avec le papier, assumée** : Mei et al. observent que le SCoT prompting *augmente* la coordination des vrais LLMs ; notre simulé de niveau 2 montre l'inverse. C'est précisément la comparaison qui instruit : ce n'est pas *prédire* qui aide, c'est prédire **mieux que « la BR contre moi »** — un vrai LLM prédit le comportement réel de l'adversaire, y compris sa propre prédiction. L'écart simulé/réel mesure la profondeur de raisonnement social que le prompting déclenche chez un vrai modèle, et que ce simulateur n'a pas.\n" }, { "cell_type": "markdown", @@ -699,19 +257,7 @@ }, "tags": [] }, - "source": [ - "## 3. E3 — Le swap en cours de partie\n", - "\n", - "**Valeur ajoutée** absente du papier : appliquer un swap (R34 ou C23) au round `k` et mesurer si le joueur suit le déplacement dans l'espace des jeux.\n", - "\n", - "L'idée : le papier observe des joueurs **dans** des jeux figés. Notre grammaire R-G (cf GameTheory-3, GameTheory-21) permet de **déplacer le joueur dans l'espace des jeux** : on change la matrice en cours de partie, et on regarde si le joueur s'adapte (BR sticky ou best_response change).\n", - "\n", - "**Mesure** : pour chaque chambre X et chaque swap S ∈ {R34, C23}, on joue 10 rounds sur X puis on swap en S (donc X devient X'), puis 10 rounds sur X'. On compare :\n", - "\n", - "- Le **taux de Nash** sur X' après swap, en mode sticky_preferred (le joueur garde sa mémoire) vs best_response (le joueur oublie et recalcule).\n", - "\n", - "**Hypothèse** : en mode sticky, le joueur **garde sa première action** même après le swap — dissociation 100% après swap. En mode best_response, il **rebascule** vers le nouveau Nash." - ] + "source": "## 3. E3 — Le swap en cours de partie\n\n**Valeur ajoutée** absente du papier : appliquer un swap (R34 ou C23) au round `k` et mesurer si le joueur suit le déplacement dans l'espace des jeux.\n\nL'idée : le papier observe des joueurs **dans** des jeux figés. Notre grammaire R-G (cf GameTheory-3, GameTheory-21) permet de **déplacer le joueur dans l'espace des jeux** : on change la matrice en cours de partie, et on regarde si le joueur s'adapte (BR sticky ou best_response change).\n\n**Mesure** : pour chaque chambre X et chaque swap S ∈ {R34, C23}, on joue 10 rounds sur X puis on swap en S (donc X devient X'), puis 10 rounds sur X'. On compare :\n\n- Le **taux de Nash** sur X' après swap, en mode sticky_preferred (le joueur garde sa mémoire) vs best_response (le joueur oublie et recalcule).\n\n**Hypothèse** : en mode sticky, le joueur **garde sa première action** même après le swap — dissociation 100% après swap. En mode best_response, il **rebascule** vers le nouveau Nash." }, { "cell_type": "code", @@ -719,10 +265,10 @@ "id": "cell-13", "metadata": { "execution": { - "iopub.execute_input": "2026-08-23T11:38:06.059054Z", - "iopub.status.busy": "2026-08-23T11:38:06.058850Z", - "iopub.status.idle": "2026-08-23T11:38:06.068657Z", - "shell.execute_reply": "2026-08-23T11:38:06.068111Z" + "iopub.status.busy": "2026-08-24T20:50:05.968070Z", + "iopub.execute_input": "2026-08-24T20:50:05.968338Z", + "shell.execute_reply": "2026-08-24T20:50:05.978971Z", + "iopub.status.idle": "2026-08-24T20:50:05.980003Z" }, "papermill": { "duration": 0.014284, @@ -735,99 +281,12 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "=== E3 : StagHunt, swap R12 au round 10 (echange rangs Row CD <-> DC) ===\n", - "Nash : CC, DD -> CC\n", - " sticky/C : pre=CC CC CC CC CC ... | post=CC CC CC CC CC ...\n", - " alternating : pre=CC DD CC DD CC ... | post=CC DD CC DD CC ...\n", - " noisy : pre=CC CD CC CC CC ... | post=CD CC CC CC CC ...\n", - " best_response : pre=CC CC CC CC CC ... | post=CC CC CC CC CC ...\n", - " scot : pre=CC CC CC CC CC ... | post=CC CC CC CC CC ...\n", - "\n", - "=== E3 : BattleSexes, swap C12 au round 10 (echange rangs Col CD <-> DC) ===\n", - "Nash : CD, DC -> CD, DC\n", - " sticky/C : pre=CC CC CC CC CC ... | post=CC CC CC CC CC ...\n", - " alternating : pre=CC DD CC DD CC ... | post=CC DD CC DD CC ...\n", - " noisy : pre=CD CC CD CD CD ... | post=CC DC DC DC DC ...\n", - " best_response : pre=CD CD CD CD CD ... | post=CD CD CD CD CD ...\n", - " scot : pre=CC CC CC CC CC ... | post=CC CC CC CC CC ...\n" - ] + "name": "stdout", + "text": "=== E3 : StagHunt, swap R12 au round 10 (echange rangs Row CD <-> DC) ===\nNash : CC, DD -> CC\n sticky/C : pre=CC CC CC CC CC ... | post=CC CC CC CC CC ...\n alternating : pre=CC DD CC DD CC ... | post=CC DD CC DD CC ...\n noisy : pre=CC CD CC CC CC ... | post=CD CC CC CC CC ...\n best_response : pre=CC CC CC CC CC ... | post=CC CC CC CC CC ...\n scot : pre=CC CC CC CC CC ... | post=CC CC CC CC CC ...\n\n=== E3 : BattleSexes, swap C12 au round 10 (echange rangs Col CD <-> DC) ===\nNash : CD, DC -> CD, DC\n sticky/C : pre=CC CC CC CC CC ... | post=CC CC CC CC CC ...\n alternating : pre=CC DD CC DD CC ... | post=CC DD CC DD CC ...\n noisy : pre=CD CC CD CD CD ... | post=CC DC DC DC DC ...\n best_response : pre=CD CD CD CD CD ... | post=CD CD CD CD CD ...\n scot : pre=CC CC CC CC CC ... | post=CC CC CC CC CC ...\n" } ], - "source": [ - "def swap_payoffs(g: OrdinalGame, swap: str) -> OrdinalGame:\n", - " \"\"\"\n", - " Applique un swap R{i}{j} (echange les rangs Row d'indices i, j) ou C{i}{j}.\n", - " Convention des indices : 0=CC, 1=CD, 2=DC, 3=DD.\n", - " \"\"\"\n", - " if len(swap) != 3 or swap[0] not in \"RC\":\n", - " raise ValueError(f\"Swap invalide : {swap} (attendu R12 ou C03 par exemple)\")\n", - " try:\n", - " i, j = int(swap[1]), int(swap[2])\n", - " except ValueError:\n", - " raise ValueError(f\"Indices non numeriques : {swap}\")\n", - " if not (0 <= i <= 3 and 0 <= j <= 3):\n", - " raise ValueError(f\"Indices hors limites 0..3 : {swap}\")\n", - " if swap[0] == \"R\":\n", - " new_R = list(g.payoffs_R)\n", - " new_R[i], new_R[j] = new_R[j], new_R[i]\n", - " return OrdinalGame(g.name + \"+\" + swap, tuple(new_R), g.payoffs_C)\n", - " new_C = list(g.payoffs_C)\n", - " new_C[i], new_C[j] = new_C[j], new_C[i]\n", - " return OrdinalGame(g.name + \"+\" + swap, g.payoffs_R, tuple(new_C))\n", - "\n", - "\n", - "def play_with_swap(g: OrdinalGame, swap: str, swap_round: int,\n", - " n_total: int = 20, mode: str = \"sticky_preferred\",\n", - " first_action: str = \"C\", seed: int = None, epsilon: float = 0.1\n", - " ) -> List[Tuple[str, str]]:\n", - " \"\"\"\n", - " Joue `g` sur n_total rounds avec un swap applique au round `swap_round`\n", - " (le jeu courant devient g' = swap_payoffs(g, swap) a partir de ce round).\n", - "\n", - " TOUS les modes passent par simulate_player, round apres round, sur le jeu\n", - " COURANT. Si un joueur ignore le swap, c'est sa REGLE qui l'ignore -- le\n", - " sticky colle a sa premiere action, l'alternant regarde l'horloge -- pas une\n", - " ligne de code qui court-circuite la simulation. Les modes qui ecoutent le\n", - " jeu (best_response, noisy, scot) reagissent au swap.\n", - " \"\"\"\n", - " rng = np.random.default_rng(seed) if mode == \"noisy\" else None\n", - " history: List[Tuple[str, str]] = []\n", - " current_g = g\n", - " for r in range(n_total):\n", - " if r == swap_round:\n", - " current_g = swap_payoffs(g, swap)\n", - " row_a = simulate_player(current_g, \"Row\", history, mode, first_action, rng, epsilon)\n", - " col_a = simulate_player(current_g, \"Col\", history + [(row_a, \"?\")], mode, first_action, rng, epsilon)\n", - " history.append((row_a, col_a))\n", - " return history\n", - "\n", - "\n", - "print(\"=== E3 : StagHunt, swap R12 au round 10 (echange rangs Row CD <-> DC) ===\")\n", - "g = CLASSIC_GAMES[\"StagHunt\"]\n", - "g_swap = swap_payoffs(g, \"R12\")\n", - "print(f\"Nash : {', '.join(a + b for a, b in find_pure_nash(g))} -> {', '.join(a + b for a, b in find_pure_nash(g_swap))}\")\n", - "for lbl, mode in [(\"sticky/C\", \"sticky_preferred\"), (\"alternating\", \"alternating\"),\n", - " (\"noisy\", \"noisy\"), (\"best_response\", \"best_response\"), (\"scot\", \"scot\")]:\n", - " h = play_with_swap(g, \"R12\", swap_round=10, n_total=20, mode=mode, first_action=\"C\", seed=0)\n", - " pre = \" \".join(r + c for r, c in h[:5])\n", - " post = \" \".join(r + c for r, c in h[10:15])\n", - " print(f\" {lbl:14s} : pre={pre} ... | post={post} ...\")\n", - "\n", - "print()\n", - "print(\"=== E3 : BattleSexes, swap C12 au round 10 (echange rangs Col CD <-> DC) ===\")\n", - "g = CLASSIC_GAMES[\"BattleSexes\"]\n", - "g_swap = swap_payoffs(g, \"C12\")\n", - "print(f\"Nash : {', '.join(a + b for a, b in find_pure_nash(g))} -> {', '.join(a + b for a, b in find_pure_nash(g_swap))}\")\n", - "for lbl, mode in [(\"sticky/C\", \"sticky_preferred\"), (\"alternating\", \"alternating\"),\n", - " (\"noisy\", \"noisy\"), (\"best_response\", \"best_response\"), (\"scot\", \"scot\")]:\n", - " h = play_with_swap(g, \"C12\", swap_round=10, n_total=20, mode=mode, first_action=\"C\", seed=0)\n", - " pre = \" \".join(r + c for r, c in h[:5])\n", - " post = \" \".join(r + c for r, c in h[10:15])\n", - " print(f\" {lbl:14s} : pre={pre} ... | post={post} ...\")\n" - ] + "source": "def swap_payoffs(g: OrdinalGame, swap: str) -> OrdinalGame:\n \"\"\"\n Applique un swap R{i}{j} (echange les rangs Row d'indices i, j) ou C{i}{j}.\n Convention des indices : 0=CC, 1=CD, 2=DC, 3=DD.\n \"\"\"\n if len(swap) != 3 or swap[0] not in \"RC\":\n raise ValueError(f\"Swap invalide : {swap} (attendu R12 ou C03 par exemple)\")\n try:\n i, j = int(swap[1]), int(swap[2])\n except ValueError:\n raise ValueError(f\"Indices non numeriques : {swap}\")\n if not (0 <= i <= 3 and 0 <= j <= 3):\n raise ValueError(f\"Indices hors limites 0..3 : {swap}\")\n if swap[0] == \"R\":\n new_R = list(g.payoffs_R)\n new_R[i], new_R[j] = new_R[j], new_R[i]\n return OrdinalGame(g.name + \"+\" + swap, tuple(new_R), g.payoffs_C)\n new_C = list(g.payoffs_C)\n new_C[i], new_C[j] = new_C[j], new_C[i]\n return OrdinalGame(g.name + \"+\" + swap, g.payoffs_R, tuple(new_C))\n\n\ndef play_with_swap(g: OrdinalGame, swap: str, swap_round: int,\n n_total: int = 20, mode: str = \"sticky_preferred\",\n first_action: str = \"C\", seed: int = None, epsilon: float = 0.1\n ) -> List[Tuple[str, str]]:\n \"\"\"\n Joue `g` sur n_total rounds avec un swap applique au round `swap_round`\n (le jeu courant devient g' = swap_payoffs(g, swap) a partir de ce round).\n\n TOUS les modes passent par simulate_player, round apres round, sur le jeu\n COURANT. Si un joueur ignore le swap, c'est sa REGLE qui l'ignore -- le\n sticky colle a sa premiere action, l'alternant regarde l'horloge -- pas une\n ligne de code qui court-circuite la simulation. Les modes qui ecoutent le\n jeu (best_response, noisy, scot) reagissent au swap.\n \"\"\"\n rng = np.random.default_rng(seed) if mode == \"noisy\" else None\n history: List[Tuple[str, str]] = []\n current_g = g\n for r in range(n_total):\n if r == swap_round:\n current_g = swap_payoffs(g, swap)\n row_a = simulate_player(current_g, \"Row\", history, mode, first_action, rng, epsilon)\n col_a = simulate_player(current_g, \"Col\", history + [(row_a, \"?\")], mode, first_action, rng, epsilon)\n history.append((row_a, col_a))\n return history\n\n\nprint(\"=== E3 : StagHunt, swap R12 au round 10 (echange rangs Row CD <-> DC) ===\")\ng = CLASSIC_GAMES[\"StagHunt\"]\ng_swap = swap_payoffs(g, \"R12\")\nprint(f\"Nash : {', '.join(a + b for a, b in find_pure_nash(g))} -> {', '.join(a + b for a, b in find_pure_nash(g_swap))}\")\nfor lbl, mode in [(\"sticky/C\", \"sticky_preferred\"), (\"alternating\", \"alternating\"),\n (\"noisy\", \"noisy\"), (\"best_response\", \"best_response\"), (\"scot\", \"scot\")]:\n h = play_with_swap(g, \"R12\", swap_round=10, n_total=20, mode=mode, first_action=\"C\", seed=0)\n pre = \" \".join(r + c for r, c in h[:5])\n post = \" \".join(r + c for r, c in h[10:15])\n print(f\" {lbl:14s} : pre={pre} ... | post={post} ...\")\n\nprint()\nprint(\"=== E3 : BattleSexes, swap C12 au round 10 (echange rangs Col CD <-> DC) ===\")\ng = CLASSIC_GAMES[\"BattleSexes\"]\ng_swap = swap_payoffs(g, \"C12\")\nprint(f\"Nash : {', '.join(a + b for a, b in find_pure_nash(g))} -> {', '.join(a + b for a, b in find_pure_nash(g_swap))}\")\nfor lbl, mode in [(\"sticky/C\", \"sticky_preferred\"), (\"alternating\", \"alternating\"),\n (\"noisy\", \"noisy\"), (\"best_response\", \"best_response\"), (\"scot\", \"scot\")]:\n h = play_with_swap(g, \"C12\", swap_round=10, n_total=20, mode=mode, first_action=\"C\", seed=0)\n pre = \" \".join(r + c for r, c in h[:5])\n post = \" \".join(r + c for r, c in h[10:15])\n print(f\" {lbl:14s} : pre={pre} ... | post={post} ...\")\n" }, { "cell_type": "code", @@ -835,10 +294,10 @@ "id": "cell-14", "metadata": { "execution": { - "iopub.execute_input": "2026-08-23T11:38:06.076242Z", - "iopub.status.busy": "2026-08-23T11:38:06.076071Z", - "iopub.status.idle": "2026-08-23T11:38:06.084896Z", - "shell.execute_reply": "2026-08-23T11:38:06.084320Z" + "iopub.status.busy": "2026-08-24T20:50:05.982107Z", + "iopub.execute_input": "2026-08-24T20:50:05.982450Z", + "shell.execute_reply": "2026-08-24T20:50:05.993964Z", + "iopub.status.idle": "2026-08-24T20:50:05.994801Z" }, "papermill": { "duration": 0.013137, @@ -851,63 +310,12 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "=== E3 : Dilemme, swap C23 au round 10 ===\n", - "Nash : DD -> DC\n", - " sticky/C pre =CC CC CC CC CC CC CC CC CC CC\n", - " post=CC CC CC CC CC CC CC CC CC CC Nash 0% -> 0%\n", - " alternating pre =CC DD CC DD CC DD CC DD CC DD\n", - " post=CC DD CC DD CC DD CC DD CC DD Nash 50% -> 0%\n", - " noisy pre =CD DC CD DD DD DD CD CD DD DD\n", - " post=DD DC DC DC DC DC DC DC DC DC Nash 50% -> 90%\n", - " best_response pre =CD DD DD DD DD DD DD DD DD DD\n", - " post=DC DC DC DC DC DC DC DC DC DC Nash 90% -> 100%\n", - "\n", - "=== E3 synthese : taux Nash post-swap (rounds 11-20), 4 modeles de joueur ===\n", - "Chambre Swap Nash post sticky altern. noisy BR\n", - "Dilemme C23 DC 0% 0% 90% 100%\n", - "StagHunt R12 CC 100% 50% 90% 100%\n", - "Chicken R02 CD 0% 0% 90% 100%\n", - "BattleSexes C12 CD, DC 0% 0% 90% 100%\n", - "Harmony R12 CC 100% 50% 90% 100%\n", - "Coordination R01 aucun 0% 0% 0% 0%\n" - ] + "name": "stdout", + "text": "=== E3 : Dilemme, swap C23 au round 10 ===\nNash : DD -> DC\n sticky/C pre =CC CC CC CC CC CC CC CC CC CC\n post=CC CC CC CC CC CC CC CC CC CC Nash 0% -> 0%\n alternating pre =CC DD CC DD CC DD CC DD CC DD\n post=CC DD CC DD CC DD CC DD CC DD Nash 50% -> 0%\n noisy pre =CD DC CD DD DD DD CD CD DD DD\n post=DD DC DC DC DC DC DC DC DC DC Nash 50% -> 90%\n best_response pre =CD DD DD DD DD DD DD DD DD DD\n post=DC DC DC DC DC DC DC DC DC DC Nash 90% -> 100%\n\n=== E3 synthese : taux Nash post-swap (rounds 11-20), 4 modeles de joueur ===\nChambre Swap Nash post sticky altern. noisy BR\nDilemme C23 DC 0% 0% 90% 100%\nStagHunt R12 CC 100% 50% 90% 100%\nChicken R02 CD 0% 0% 90% 100%\nBattleSexes C12 CD, DC 0% 0% 90% 100%\nHarmony R12 CC 100% 50% 90% 100%\nCoordination R01 aucun 0% 0% 0% 0%\n" } ], - "source": [ - "# Mesure discriminante : Dilemme + C23 -- le swap deplace l'equilibre\n", - "print(\"=== E3 : Dilemme, swap C23 au round 10 ===\")\n", - "g = CLASSIC_GAMES[\"Dilemme\"]\n", - "g_swap = swap_payoffs(g, \"C23\")\n", - "nash_pre, nash_post = find_pure_nash(g), find_pure_nash(g_swap)\n", - "print(f\"Nash : {', '.join(a + b for a, b in nash_pre)} -> {', '.join(a + b for a, b in nash_post)}\")\n", - "for lbl, mode in [(\"sticky/C\", \"sticky_preferred\"), (\"alternating\", \"alternating\"),\n", - " (\"noisy\", \"noisy\"), (\"best_response\", \"best_response\")]:\n", - " h = play_with_swap(g, \"C23\", swap_round=10, n_total=20, mode=mode, first_action=\"C\", seed=0)\n", - " pre = \" \".join(r + c for r, c in h[:10])\n", - " post = \" \".join(r + c for r, c in h[10:])\n", - " r_pre = sum(1 for a, b in h[:10] if (a, b) in nash_pre) / 10\n", - " r_post = sum(1 for a, b in h[10:] if (a, b) in nash_post) / 10\n", - " print(f\" {lbl:14s} pre ={pre}\")\n", - " print(f\" {'':14s} post={post} Nash {r_pre:.0%} -> {r_post:.0%}\")\n", - "\n", - "print()\n", - "print(\"=== E3 synthese : taux Nash post-swap (rounds 11-20), 4 modeles de joueur ===\")\n", - "print(f\"{'Chambre':14s} {'Swap':5s} {'Nash post':16s} {'sticky':>8s} {'altern.':>8s} {'noisy':>8s} {'BR':>8s}\")\n", - "test_cases = [(\"Dilemme\", \"C23\"), (\"StagHunt\", \"R12\"), (\"Chicken\", \"R02\"),\n", - " (\"BattleSexes\", \"C12\"), (\"Harmony\", \"R12\"), (\"Coordination\", \"R01\")]\n", - "for g_name, swap in test_cases:\n", - " g = CLASSIC_GAMES[g_name]\n", - " nash_post = find_pure_nash(swap_payoffs(g, swap))\n", - " lbl_nash = \", \".join(a + b for a, b in nash_post) if nash_post else \"aucun\"\n", - " rates = []\n", - " for mode in [\"sticky_preferred\", \"alternating\", \"noisy\", \"best_response\"]:\n", - " h = play_with_swap(g, swap, swap_round=10, n_total=20, mode=mode, first_action=\"C\", seed=0)\n", - " rates.append(sum(1 for a, b in h[10:] if (a, b) in nash_post) / 10)\n", - " print(f\"{g_name:14s} {swap:5s} {lbl_nash:16s} \" + \" \".join(f\"{r:8.0%}\" for r in rates))\n" - ] + "source": "# Mesure discriminante : Dilemme + C23 -- le swap deplace l'equilibre\nprint(\"=== E3 : Dilemme, swap C23 au round 10 ===\")\ng = CLASSIC_GAMES[\"Dilemme\"]\ng_swap = swap_payoffs(g, \"C23\")\nnash_pre, nash_post = find_pure_nash(g), find_pure_nash(g_swap)\nprint(f\"Nash : {', '.join(a + b for a, b in nash_pre)} -> {', '.join(a + b for a, b in nash_post)}\")\nfor lbl, mode in [(\"sticky/C\", \"sticky_preferred\"), (\"alternating\", \"alternating\"),\n (\"noisy\", \"noisy\"), (\"best_response\", \"best_response\")]:\n h = play_with_swap(g, \"C23\", swap_round=10, n_total=20, mode=mode, first_action=\"C\", seed=0)\n pre = \" \".join(r + c for r, c in h[:10])\n post = \" \".join(r + c for r, c in h[10:])\n r_pre = sum(1 for a, b in h[:10] if (a, b) in nash_pre) / 10\n r_post = sum(1 for a, b in h[10:] if (a, b) in nash_post) / 10\n print(f\" {lbl:14s} pre ={pre}\")\n print(f\" {'':14s} post={post} Nash {r_pre:.0%} -> {r_post:.0%}\")\n\nprint()\nprint(\"=== E3 synthese : taux Nash post-swap (rounds 11-20), 4 modeles de joueur ===\")\nprint(f\"{'Chambre':14s} {'Swap':5s} {'Nash post':16s} {'sticky':>8s} {'altern.':>8s} {'noisy':>8s} {'BR':>8s}\")\ntest_cases = [(\"Dilemme\", \"C23\"), (\"StagHunt\", \"R12\"), (\"Chicken\", \"R02\"),\n (\"BattleSexes\", \"C12\"), (\"Harmony\", \"R12\"), (\"Coordination\", \"R01\")]\nfor g_name, swap in test_cases:\n g = CLASSIC_GAMES[g_name]\n nash_post = find_pure_nash(swap_payoffs(g, swap))\n lbl_nash = \", \".join(a + b for a, b in nash_post) if nash_post else \"aucun\"\n rates = []\n for mode in [\"sticky_preferred\", \"alternating\", \"noisy\", \"best_response\"]:\n h = play_with_swap(g, swap, swap_round=10, n_total=20, mode=mode, first_action=\"C\", seed=0)\n rates.append(sum(1 for a, b in h[10:] if (a, b) in nash_post) / 10)\n print(f\"{g_name:14s} {swap:5s} {lbl_nash:16s} \" + \" \".join(f\"{r:8.0%}\" for r in rates))\n" }, { "cell_type": "markdown", @@ -922,23 +330,7 @@ }, "tags": [] }, - "source": [ - "### Lecture de E3\n", - "\n", - "Tous les modes passent par `simulate_player` sur le jeu **courant** : si un joueur ignore le swap, c'est sa **règle** qui l'ignore, pas une ligne de code qui court-circuite la simulation.\n", - "\n", - "- **Dilemme + C23** (Nash déplacé, output ci-dessus) : `sticky/C` poursuit CC — sa règle ne regarde jamais le jeu ; `alternating` poursuit son horloge — son alternance ne rencontre jamais le nouvel équilibre ; `noisy` suit partiellement — il écoute le jeu, avec un tremblement ; `best_response` suit à 100%. **Trois réactions différentes au même swap, émergentes de trois règles.**\n", - "- **StagHunt + R12** : le swap retire DD du set d'équilibres (CC, DD → CC, output) sans toucher à celui que chacun joue déjà — personne ne bouge, et la mesure ne peut pas discriminer ici. Elle le montre honnêtement.\n", - "- **La synthèse chiffre les six chambres** : quand le swap crée une chambre **sans Nash pur** (cas cyclique), toutes les colonnes tombent à 0% — non par échec des joueurs, mais parce qu'il n'y a rien à atteindre. La mesure le révèle au lieu de le cacher.\n", - "\n", - "Sur le cas discriminant (Dilemme+C23), la hiérarchie est nette : BR (100%) > noisy (90%) > alternating = sticky (0%). Plus généralement, BR et noisy *écoutent* le jeu — leurs taux suivent l'équilibre courant — tandis qu'alternating et sticky ne l'écoutent pas : leurs scores post-swap reflètent l'accident de leur règle figée avec le nouvel équilibre, pas une réaction. C'est la structure que E4 mesure explicitement.\n", - "\n", - "## 4. E4 — Dissociation prédire/agir (mesure explicite)\n", - "\n", - "L'apport (b) du papier : GPT-4 **prédit correctement** l'alternance en Battle of the Sexes (quand on lui demande « que va jouer l'adversaire ? »), et **n'agit pas** en conséquence. C'est la dissociation pure.\n", - "\n", - "Pour la mesurer **sans appel LLM externe**, on utilise la structure du jeu : la prédiction « correcte » est `best_response` au dernier coup adverse. L'action « réelle » est celle du modèle de joueur. La **dissociation** = prédiction ≠ action jouée, round par round.\n" - ] + "source": "### Lecture de E3\n\nTous les modes passent par `simulate_player` sur le jeu **courant** : si un joueur ignore le swap, c'est sa **règle** qui l'ignore, pas une ligne de code qui court-circuite la simulation.\n\n- **Dilemme + C23** (Nash déplacé, output ci-dessus) : `sticky/C` poursuit CC — sa règle ne regarde jamais le jeu ; `alternating` poursuit son horloge — son alternance ne rencontre jamais le nouvel équilibre ; `noisy` suit partiellement — il écoute le jeu, avec un tremblement ; `best_response` suit à 100%. **Trois réactions différentes au même swap, émergentes de trois règles.**\n- **StagHunt + R12** : le swap retire DD du set d'équilibres (CC, DD → CC, output) sans toucher à celui que chacun joue déjà — personne ne bouge, et la mesure ne peut pas discriminer ici. Elle le montre honnêtement.\n- **La synthèse chiffre les six chambres** : quand le swap crée une chambre **sans Nash pur** (cas cyclique), toutes les colonnes tombent à 0% — non par échec des joueurs, mais parce qu'il n'y a rien à atteindre. La mesure le révèle au lieu de le cacher.\n\nSur le cas discriminant (Dilemme+C23), la hiérarchie est nette : BR (100%) > noisy (90%) > alternating = sticky (0%). Plus généralement, BR et noisy *écoutent* le jeu — leurs taux suivent l'équilibre courant — tandis qu'alternating et sticky ne l'écoutent pas : leurs scores post-swap reflètent l'accident de leur règle figée avec le nouvel équilibre, pas une réaction. C'est la structure que E4 mesure explicitement.\n\n## 4. E4 — Dissociation prédire/agir (mesure explicite)\n\nL'apport (b) du papier : GPT-4 **prédit correctement** l'alternance en Battle of the Sexes (quand on lui demande « que va jouer l'adversaire ? »), et **n'agit pas** en conséquence. C'est la dissociation pure.\n\nPour la mesurer **sans appel LLM externe**, on utilise la structure du jeu : la prédiction « correcte » est `best_response` au dernier coup adverse. L'action « réelle » est celle du modèle de joueur. La **dissociation** = prédiction ≠ action jouée, round par round.\n" }, { "cell_type": "code", @@ -946,10 +338,10 @@ "id": "cell-16", "metadata": { "execution": { - "iopub.execute_input": "2026-08-23T11:38:06.098447Z", - "iopub.status.busy": "2026-08-23T11:38:06.098036Z", - "iopub.status.idle": "2026-08-23T11:38:06.105044Z", - "shell.execute_reply": "2026-08-23T11:38:06.104326Z" + "iopub.status.busy": "2026-08-24T20:50:05.996617Z", + "iopub.execute_input": "2026-08-24T20:50:05.996924Z", + "shell.execute_reply": "2026-08-24T20:50:06.004065Z", + "iopub.status.idle": "2026-08-24T20:50:06.004990Z" }, "papermill": { "duration": 0.011719, @@ -962,56 +354,12 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "=== E4 : Dissociation predire/agir (mesure explicite, 5 modeles) ===\n", - "Plus la dissociation est haute, plus le joueur 'sait mais ne suit pas'.\n", - "\n", - "chambre sticky/C alternating noisy best_response scot\n", - "BattleSexes 100% 50% 28% 0% 100%\n", - "StagHunt 0% 50% 28% 0% 0%\n", - "Dilemme 100% 44% 28% 0% 0%\n", - "Chicken 100% 50% 28% 0% 100%\n", - "Harmony 0% 56% 28% 0% 0%\n", - "Coordination 0% 50% 28% 0% 0%\n" - ] + "name": "stdout", + "text": "=== E4 : Dissociation predire/agir (mesure explicite, 5 modeles) ===\nPlus la dissociation est haute, plus le joueur 'sait mais ne suit pas'.\n\nchambre sticky/C alternating noisy best_response scot\nBattleSexes 100% 50% 28% 0% 100%\nStagHunt 0% 50% 28% 0% 0%\nDilemme 100% 44% 28% 0% 0%\nChicken 100% 50% 28% 0% 100%\nHarmony 0% 56% 28% 0% 0%\nCoordination 0% 50% 28% 0% 0%\n" } ], - "source": [ - "def dissociation_rate(g: OrdinalGame, history: List[Tuple[str, str]]) -> float:\n", - " \"\"\"\n", - " Mesure la dissociation predire/agir : pour chaque round (hors le premier),\n", - " la prediction du joueur -- best_response au dernier coup adverse -- differ-t-elle\n", - " de l'action reellement jouee ?\n", - " \"\"\"\n", - " if len(history) < 2:\n", - " return 0.0\n", - " dissociations = 0\n", - " for i in range(1, len(history)):\n", - " prev_row, prev_col = history[i - 1]\n", - " row_a, col_a = history[i]\n", - " if row_a != best_response(g, \"Row\", prev_col):\n", - " dissociations += 1\n", - " if col_a != best_response(g, \"Col\", row_a):\n", - " dissociations += 1\n", - " return dissociations / (2 * (len(history) - 1))\n", - "\n", - "\n", - "print(\"=== E4 : Dissociation predire/agir (mesure explicite, 5 modeles) ===\")\n", - "print(\"Plus la dissociation est haute, plus le joueur 'sait mais ne suit pas'.\")\n", - "print()\n", - "modes_e4 = [(\"sticky/C\", \"sticky_preferred\", \"C\"), (\"alternating\", \"alternating\", \"C\"),\n", - " (\"noisy\", \"noisy\", \"C\"), (\"best_response\", \"best_response\", \"C\"), (\"scot\", \"scot\", \"C\")]\n", - "print(f\"{'chambre':14s} \" + \" \".join(f\"{lbl:>12s}\" for lbl, _, _ in modes_e4))\n", - "for g_name in [\"BattleSexes\", \"StagHunt\", \"Dilemme\", \"Chicken\", \"Harmony\", \"Coordination\"]:\n", - " g = CLASSIC_GAMES[g_name]\n", - " ds = []\n", - " for _, mode, fa in modes_e4:\n", - " h = play_repeated(g, n_rounds=10, mode=mode, first_action=fa, seed=0, epsilon=0.2)\n", - " ds.append(dissociation_rate(g, h))\n", - " print(f\"{g_name:14s} \" + \" \".join(f\"{d:12.0%}\" for d in ds))\n" - ] + "source": "def dissociation_rate(g: OrdinalGame, history: List[Tuple[str, str]]) -> float:\n \"\"\"\n Mesure la dissociation predire/agir : pour chaque round (hors le premier),\n la prediction du joueur -- best_response au dernier coup adverse -- differ-t-elle\n de l'action reellement jouee ?\n \"\"\"\n if len(history) < 2:\n return 0.0\n dissociations = 0\n for i in range(1, len(history)):\n prev_row, prev_col = history[i - 1]\n row_a, col_a = history[i]\n if row_a != best_response(g, \"Row\", prev_col):\n dissociations += 1\n if col_a != best_response(g, \"Col\", row_a):\n dissociations += 1\n return dissociations / (2 * (len(history) - 1))\n\n\nprint(\"=== E4 : Dissociation predire/agir (mesure explicite, 5 modeles) ===\")\nprint(\"Plus la dissociation est haute, plus le joueur 'sait mais ne suit pas'.\")\nprint()\nmodes_e4 = [(\"sticky/C\", \"sticky_preferred\", \"C\"), (\"alternating\", \"alternating\", \"C\"),\n (\"noisy\", \"noisy\", \"C\"), (\"best_response\", \"best_response\", \"C\"), (\"scot\", \"scot\", \"C\")]\nprint(f\"{'chambre':14s} \" + \" \".join(f\"{lbl:>12s}\" for lbl, _, _ in modes_e4))\nfor g_name in [\"BattleSexes\", \"StagHunt\", \"Dilemme\", \"Chicken\", \"Harmony\", \"Coordination\"]:\n g = CLASSIC_GAMES[g_name]\n ds = []\n for _, mode, fa in modes_e4:\n h = play_repeated(g, n_rounds=10, mode=mode, first_action=fa, seed=0, epsilon=0.2)\n ds.append(dissociation_rate(g, h))\n print(f\"{g_name:14s} \" + \" \".join(f\"{d:12.0%}\" for d in ds))\n" }, { "cell_type": "markdown", @@ -1026,21 +374,7 @@ }, "tags": [] }, - "source": [ - "## 5. Synthèse : ce que le notebook a montré\n", - "\n", - "**Cinq observations structurantes** :\n", - "\n", - "1. **Paysage de performance** (E2) — les cinq modèles de joueur produisent des trajectoires réellement différentes sur les six chambres ; en Dilemme le sticky qui ouvre par C échoue (0%) là où celui qui ouvre par D réussit (100%), et c'est l'inverse en Harmony ; dans les chambres à double équilibre, les deux réussissent. La performance dépend du couple (règle, chambre), pas de la chambre seule.\n", - "\n", - "2. **SCoT, effet chiffré** (E2bis) — le joueur de niveau 2 tombe à 0% d'atteinte de Nash en BattleSexes et Chicken, chambres où la BR de niveau 1 converge ; delta nul ailleurs, jamais positif. La prédiction naïve (BR contre mon dernier coup) crée un cercle auto-confirmatoire qui *renforce* l'attachement à son camp. Différence assumée avec le papier, où le SCoT aide les vrais LLMs : l'écart mesure la profondeur de raisonnement social qu'un vrai modèle déploie et que ce simulateur de niveau 2 n'a pas.\n", - "\n", - "3. **Qui écoute le jeu** (E3) — sur Dilemme+C23, le swap déplace l'équilibre : sticky l'ignore (règle aveugle au jeu), alternating l'ignore (règle horloge), noisy le suit avec un tremblement, BR le suit entièrement. La hiérarchie émerge des règles, elle n'est plus codée. Et quand le swap crée une chambre sans Nash pur, toutes les colonnes tombent à 0% — la mesure le révèle au lieu de le cacher.\n", - "\n", - "4. **Dissociation prédire/agir** (E4) — dissociation maximale pour le sticky dans les chambres à conflit (il joue C, la prédiction dit D), nulle dans les chambres où C est la prédiction ; 0% pour la BR par construction ; et dissociation forte pour SCoT en chambre à conflit : le joueur de niveau 2 *prédit* — c'est toute sa règle — et son action n'en décale pas, le cercle se referme sur lui-même.\n", - "\n", - "5. **Le joueur LLM de ce notebook est un simulateur paramétrique** — cinq règles explicites, exécutées par le même moteur `simulate_player`. Les patterns empiriques du papier (défection permanente en Dilemme, collage à l'option préférée en BoS) y apparaissent comme *propriétés de règles*, reproductibles sans appel réseau ; l'exercice 1 montre où brancher un vrai modèle pour confronter les deux.\n" - ] + "source": "## 5. Synthèse : ce que le notebook a montré\n\n**Cinq observations structurantes** :\n\n1. **Paysage de performance** (E2) — les cinq modèles de joueur produisent des trajectoires réellement différentes sur les six chambres ; en Dilemme le sticky qui ouvre par C échoue (0%) là où celui qui ouvre par D réussit (100%), et c'est l'inverse en Harmony ; dans les chambres à double équilibre, les deux réussissent. La performance dépend du couple (règle, chambre), pas de la chambre seule.\n\n2. **SCoT, effet chiffré** (E2bis) — le joueur de niveau 2 tombe à 0% d'atteinte de Nash en BattleSexes et Chicken, chambres où la BR de niveau 1 converge ; delta nul ailleurs, jamais positif. La prédiction naïve (BR contre mon dernier coup) crée un cercle auto-confirmatoire qui *renforce* l'attachement à son camp. Différence assumée avec le papier, où le SCoT aide les vrais LLMs : l'écart mesure la profondeur de raisonnement social qu'un vrai modèle déploie et que ce simulateur de niveau 2 n'a pas.\n\n3. **Qui écoute le jeu** (E3) — sur Dilemme+C23, le swap déplace l'équilibre : sticky l'ignore (règle aveugle au jeu), alternating l'ignore (règle horloge), noisy le suit avec un tremblement, BR le suit entièrement. La hiérarchie émerge des règles, elle n'est plus codée. Et quand le swap crée une chambre sans Nash pur, toutes les colonnes tombent à 0% — la mesure le révèle au lieu de le cacher.\n\n4. **Dissociation prédire/agir** (E4) — dissociation maximale pour le sticky dans les chambres à conflit (il joue C, la prédiction dit D), nulle dans les chambres où C est la prédiction ; 0% pour la BR par construction ; et dissociation forte pour SCoT en chambre à conflit : le joueur de niveau 2 *prédit* — c'est toute sa règle — et son action n'en décale pas, le cercle se referme sur lui-même.\n\n5. **Le joueur LLM de ce notebook est un simulateur paramétrique** — cinq règles explicites, exécutées par le même moteur `simulate_player`. Les patterns empiriques du papier (défection permanente en Dilemme, collage à l'option préférée en BoS) y apparaissent comme *propriétés de règles*, reproductibles sans appel réseau ; l'exercice 1 montre où brancher un vrai modèle pour confronter les deux.\n" }, { "cell_type": "markdown", @@ -1055,105 +389,34 @@ }, "tags": [] }, - "source": [ - "## 8. Exercices\n", - "\n", - "Cette section rassemble **3 exercices progressifs** sur la dissociation prédire/agir.\n", - "\n", - "### Exercice 1 — Remplacer le joueur simulé par un vrai LLM (openai-compatible)\n", - "\n", - "L'objectif : remplacer `sticky_preferred` par un appel provider réel. Quand `OPENAI_API_KEY` (ou équivalent) est présent dans `os.environ`, le notebook appelle le modèle ; sinon, il reste sur le stub `sticky_preferred`. C'est la cellule-type d'extension **RECOVERABLE-LOCAL** (cf [sota-not-workaround.md](../../.claude/rules/sota-not-workaround.md)).\n", - "\n", - "### Exercice 2 — Caractériser les swaps qui augmentent la dissociation\n", - "\n", - "L'objectif : pour chaque chambre X, lister **tous** les swaps qui transforment le Nash et mesurer la dissociation sticky vs BR sur chaque swap. Identifier les swaps qui **cachent** la dissociation (BattleSexes+C12) vs ceux qui la **révèlent** (Dilemme+C23).\n", - "\n", - "### Exercice 3 — Cyclicité de BattleSexes : formalisation en logique modale\n", - "\n", - "L'objectif : proposer un encodage **non-transitif** de BattleSexes qui préserve ses deux Nash (C,D) et (D,C). Indice : utiliser des préférences **lexicographiques** ou une **logique modale KD45**." - ] + "source": "## 8. Exercices\n\nCette section rassemble **3 exercices progressifs** sur la dissociation prédire/agir.\n\n### Exercice 1 — Remplacer le joueur simulé par un vrai LLM (openai-compatible)\n\nL'objectif : remplacer `sticky_preferred` par un appel provider réel. Quand `OPENAI_API_KEY` (ou équivalent) est présent dans `os.environ`, le notebook appelle le modèle ; sinon, il reste sur le stub `sticky_preferred`. C'est la cellule-type d'extension **RECOVERABLE-LOCAL** (cf [sota-not-workaround.md](../../.claude/rules/sota-not-workaround.md)).\n\n### Exercice 2 — Caractériser les swaps qui augmentent la dissociation\n\nL'objectif : pour chaque chambre X, lister **tous** les swaps qui transforment le Nash et mesurer la dissociation sticky vs BR sur chaque swap. Identifier les swaps qui **cachent** la dissociation (BattleSexes+C12) vs ceux qui la **révèlent** (Dilemme+C23).\n\n### Exercice 3 — Cyclicité de BattleSexes : formalisation en logique modale\n\nL'objectif : proposer un encodage **non-transitif** de BattleSexes qui préserve ses deux Nash (C,D) et (D,C). Indice : utiliser des préférences **lexicographiques** ou une **logique modale KD45**." }, { "cell_type": "code", "execution_count": 9, "id": "cell-19", "metadata": { + "tags": [], "execution": { - "iopub.execute_input": "2026-08-23T11:38:06.124099Z", - "iopub.status.busy": "2026-08-23T11:38:06.123806Z", - "iopub.status.idle": "2026-08-23T11:38:06.129272Z", - "shell.execute_reply": "2026-08-23T11:38:06.128472Z" - }, - "papermill": { - "duration": 0.009886, - "end_time": "2026-08-23T11:38:06.129865+00:00", - "exception": false, - "start_time": "2026-08-23T11:38:06.119979+00:00", - "status": "completed" - }, - "tags": [] + "iopub.status.busy": "2026-08-24T20:50:06.006721Z", + "iopub.execute_input": "2026-08-24T20:50:06.007007Z", + "shell.execute_reply": "2026-08-24T20:50:06.019249Z", + "iopub.status.idle": "2026-08-24T20:50:06.020207Z" + } }, "outputs": [ { + "output_type": "stream", "name": "stdout", + "text": "=== Exercice 1 : appel reel au vLLM local (qwen3.6-35b-a3b) ===\nEndpoint : http://192.168.0.47:5002 | Modele : qwen3.6-35b-a3b | Cle presente : True\n\nJeu : Dilemme | 5 rounds | Cassette initiale : 0 entree(s)\n" + }, + { "output_type": "stream", - "text": [ - "=== Exercice 1 : call_llm_provider ===\n", - "Sans cle API, retourne : None\n", - "=> Pour utiliser un vrai LLM : configurer OPENAI_API_KEY dans .env ou os.environ\n" - ] + "name": "stdout", + "text": "Trajectoire : CC CC CC CC CC\nTaux Nash (DD unique en Dilemme) : 0%\nCooperation rate : 100%\nCassette apres appel : 10 entree(s)\n\nRejeu cassette : trajectoire identique ? True\n cassette_size avant/apres : 10 / 10 (egal = pas d appel reseau)\n" } ], - "source": [ - "# Exercice 1 : Remplacer le joueur simule par un vrai LLM (openai-compatible)\n", - "def call_llm_provider(history: List[Tuple[str, str]], player: str,\n", - " api_key: str = None) -> str:\n", - " \"\"\"\n", - " Appelle un modele openai-compatible (OpenAI, OpenRouter, Anthropic via gateway).\n", - " Si pas de cle API, retourne un stub C.1 (None) -- le notebook reste executable.\n", - "\n", - " Indice etudiant :\n", - " - Utiliser `os.environ.get(\"OPENAI_API_KEY\")` ou equivalent\n", - " - Prompt : convertir l'historique en regles textuelles (F/J) comme dans le papier\n", - " - Temperature 0, max_tokens 1 (mono-token)\n", - " - Si le provider repond, retourner 'C' ou 'D' ; sinon retourner None (stub)\n", - " \"\"\"\n", - " api_key = api_key or os.environ.get(\"OPENAI_API_KEY\")\n", - " if not api_key:\n", - " # Pas de provider : stub C.1\n", - " return None\n", - " # TODO etudiant : implementer l'appel provider\n", - " # Indice : openai.OpenAI(api_key=...).chat.completions.create(...)\n", - " # Exemple : client = openai.OpenAI(api_key=api_key)\n", - " # resp = client.chat.completions.create(\n", - " # model=\"gpt-4\",\n", - " # messages=[{\"role\": \"user\", \"content\": build_prompt(history, player)}],\n", - " # temperature=0, max_tokens=1)\n", - " # return parse_action(resp.choices[1.message.content)\n", - " return None # Stub C.1\n", - "\n", - "\n", - "# Indice : pour la construction du prompt, transformer l'historique en texte :\n", - "def build_prompt_template(history: List[Tuple[str, str]], player: str, game_name: str) -> str:\n", - " \"\"\"Template du prompt envoye au modele. Les regles textuelles F/J evitement le biais semantique.\"\"\"\n", - " opponent = \"Col\" if player == \"Row\" else \"Row\"\n", - " rows = [\n", - " f\"You are playing a repeated {game_name} game.\",\n", - " f\"On each round, you choose F or J. The other player ({opponent}) also chooses.\",\n", - " f\"History (most recent last):\",\n", - " ]\n", - " for i, (r, c) in enumerate(history):\n", - " rows.append(f\" Round {i+1}: F\" if (r == \"C\" if player == \"Row\" else c == \"C\") else f\" Round {i+1}: J\")\n", - " rows.append(\"Choose F or J for the next round. Reply with one character only.\")\n", - " return \"\\n\".join(rows)\n", - "\n", - "\n", - "# Stub : si pas de cle, on retourne None et le notebook reste executable\n", - "print(\"=== Exercice 1 : call_llm_provider ===\")\n", - "result = call_llm_provider([(\"C\", \"C\"), (\"C\", \"C\")], \"Row\")\n", - "print(f\"Sans cle API, retourne : {result}\")\n", - "print(\"=> Pour utiliser un vrai LLM : configurer OPENAI_API_KEY dans .env ou os.environ\")" - ] + "source": "# Exercice 1 : Remplacer le joueur simule par un vrai LLM (openai-compatible)\n#\n# Configuration (env vars surchargeables, defaut cluster ai-01 vLLM) :\n# VLLM_ENDPOINT -- URL du serveur openai-compatible\n# (defaut : cluster ai-01 vLLM local)\n# VLLM_API_KEY -- cle d'API (defaut : vide ; sans cle, le notebook reste\n# executable via le stub C.1 -- definir dans\n# .secrets/master.env ou os.environ avant execution)\n# VLLM_MODEL -- nom du modele (defaut : qwen3.6-35b-a3b sur cluster ai-01)\n#\n# La cle de cluster est stockee dans .secrets/master.env (gitignored) -- ne JAMAIS\n# la hardcoder ni la mettre en exemple dans le code (secrets-hygiene rule 2).\n# Cf scripts/secrets/render_envs.py pour la propagation vers les .env de service.\n#\nimport os, json, urllib.request, urllib.error\nfrom typing import Optional, Dict, List, Tuple\n\nVLLM_ENDPOINT = os.environ.get('VLLM_ENDPOINT', 'http://192.168.0.47:5002')\nVLLM_API_KEY = os.environ.get('VLLM_API_KEY', '')\nVLLM_MODEL = os.environ.get('VLLM_MODEL', 'qwen3.6-35b-a3b')\n\n# Cassette rejouable : cle = (player, game_name, tuple(history)) -> 'C' ou 'D'\n_LLM_CASSETTE = {}\n\n\ndef call_llm_provider(history, player, game_name='Dilemme', cassette=True):\n \"\"\"\n Appelle un modele openai-compatible (vLLM local par defaut, cle VLLM_API_KEY).\n\n Renvoie 'C' ou 'D' selon la convention du notebook (F->C, J->D).\n Renvoie None si l'endpoint n'est pas joignable / pas de cle (stub C.1).\n\n Parametres :\n - cassette=True : rejoue les appels deja effectues (cle = player+game+history).\n C'est le mecanisme de reproductibilite demande par la reserve Hermes.\n - Temperature 0 + max_tokens 1 + chat_template_kwargs enable_thinking=False\n (cf c.434 vllm-thinking-model-disable-thinking-client-side).\n \"\"\"\n key = (player, game_name, tuple(history))\n if cassette and key in _LLM_CASSETTE:\n return _LLM_CASSETTE[key]\n\n if not VLLM_API_KEY:\n return None\n\n opponent = 'Col' if player == 'Row' else 'Row'\n lines = [\n f'You are playing a repeated {game_name} game.',\n f'On each round, you choose F or J. The other player ({opponent}) also chooses.',\n 'History (most recent last):',\n ]\n for i, (r, c) in enumerate(history):\n mine = r if player == 'Row' else c\n lines.append(f' Round {i+1}: F' if mine == 'C' else f' Round {i+1}: J')\n lines.append('Choose F or J for the next round. Reply with one character only.')\n prompt = '\\n'.join(lines)\n\n body = json.dumps({\n 'model': VLLM_MODEL,\n 'messages': [{'role': 'user', 'content': prompt}],\n 'temperature': 0,\n 'max_tokens': 1,\n 'chat_template_kwargs': {'enable_thinking': False},\n }).encode('utf-8')\n req = urllib.request.Request(\n f'{VLLM_ENDPOINT}/v1/chat/completions',\n data=body,\n headers={\n 'Content-Type': 'application/json',\n 'Authorization': f'Bearer {VLLM_API_KEY}',\n },\n method='POST',\n )\n try:\n with urllib.request.urlopen(req, timeout=10) as resp:\n payload = json.loads(resp.read().decode('utf-8'))\n except (urllib.error.URLError, TimeoutError, OSError):\n return None\n\n content = payload['choices'][0]['message']['content']\n if not content:\n return None\n ch = content.strip().upper()\n if ch not in ('F', 'J'):\n return None\n action = 'C' if ch == 'F' else 'D'\n if cassette:\n _LLM_CASSETTE[key] = action\n return action\n\n\ndef build_prompt_template(history, player, game_name):\n \"\"\"Meme template que call_llm_provider, expose pour les etudiants.\"\"\"\n opponent = 'Col' if player == 'Row' else 'Row'\n rows = [\n f'You are playing a repeated {game_name} game.',\n f'On each round, you choose F or J. The other player ({opponent}) also chooses.',\n f'History (most recent last):',\n ]\n for i, (r, c) in enumerate(history):\n rows.append(f' Round {i+1}: F' if (r == 'C' if player == 'Row' else c == 'C') else f' Round {i+1}: J')\n rows.append('Choose F or J for the next round. Reply with one character only.')\n return '\\n'.join(rows)\n\n\ndef llm_play_repeated(g, n_rounds=5, game_name=None):\n \"\"\"\n Joue g avec un VRAI LLM (vLLM local) sur n_rounds. Chaque round, Row puis Col\n appellent call_llm_provider. Cassette rejouable : si la cle est deja connue,\n le resultat est restitue sans nouvel appel.\n \"\"\"\n if game_name is None:\n game_name = g.name\n history = []\n for _ in range(n_rounds):\n row_a = call_llm_provider(history, 'Row', game_name=game_name)\n if row_a is None:\n return history\n col_a = call_llm_provider(history + [(row_a, '?')], 'Col', game_name=game_name)\n if col_a is None:\n return history\n history.append((row_a, col_a))\n return history\n\n\nprint('=== Exercice 1 : appel reel au vLLM local (qwen3.6-35b-a3b) ===')\nprint(f'Endpoint : {VLLM_ENDPOINT} | Modele : {VLLM_MODEL} | Cle presente : {bool(VLLM_API_KEY)}')\nprint()\nif not VLLM_API_KEY:\n print('Pas de cle VLLM_API_KEY -- stub C.1 (notebook reste executable).')\n print('=> Configurer VLLM_API_KEY dans .env ou os.environ pour un appel reel.')\nelse:\n g = CLASSIC_GAMES['Dilemme']\n print(f'Jeu : {g.name} | 5 rounds | Cassette initiale : {len(_LLM_CASSETTE)} entree(s)')\n history = llm_play_repeated(g, n_rounds=5)\n if not history:\n print('Aucun round joue (endpoint non joignable ou cle invalide).')\n else:\n seq = ' '.join(r + c for r, c in history)\n nash_set = find_pure_nash(g)\n nash_rate = sum(1 for r, c in history if (r, c) in nash_set) / len(history)\n print(f'Trajectoire : {seq}')\n print(f'Taux Nash (DD unique en Dilemme) : {nash_rate:.0%}')\n print(f'Cooperation rate : {cooperation_rate(history):.0%}')\n print(f'Cassette apres appel : {len(_LLM_CASSETTE)} entree(s)')\n print()\n cassette_size_before = len(_LLM_CASSETTE)\n history_replay = llm_play_repeated(g, n_rounds=5)\n cassette_size_after = len(_LLM_CASSETTE)\n print(f'Rejeu cassette : trajectoire identique ? {history == history_replay}')\n print(f' cassette_size avant/apres : {cassette_size_before} / {cassette_size_after} (egal = pas d appel reseau)')\n" }, { "cell_type": "code", @@ -1161,10 +424,10 @@ "id": "cell-20", "metadata": { "execution": { - "iopub.execute_input": "2026-08-23T11:38:06.136751Z", - "iopub.status.busy": "2026-08-23T11:38:06.136579Z", - "iopub.status.idle": "2026-08-23T11:38:06.143493Z", - "shell.execute_reply": "2026-08-23T11:38:06.142645Z" + "iopub.status.busy": "2026-08-24T20:50:06.021999Z", + "iopub.execute_input": "2026-08-24T20:50:06.022315Z", + "shell.execute_reply": "2026-08-24T20:50:06.030099Z", + "iopub.status.idle": "2026-08-24T20:50:06.031035Z" }, "papermill": { "duration": 0.011343, @@ -1177,63 +440,12 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "=== Exercice 2 : swaps qui revelent la dissociation sur Dilemme ===\n", - "Swap d_sticky_post d_BR_post \n", - "R01 100% 50%\n", - "C01 100% 0%\n", - "R02 100% 0%\n", - "C02 100% 50%\n", - "R03 100% 0%\n", - "C03 100% 0%\n" - ] + "name": "stdout", + "text": "=== Exercice 2 : swaps qui revelent la dissociation sur Dilemme ===\nSwap d_sticky_post d_BR_post \nR01 100% 50%\nC01 100% 0%\nR02 100% 0%\nC02 100% 50%\nR03 100% 0%\nC03 100% 0%\n" } ], - "source": [ - "# Exercice 2 : Caracteriser les swaps qui augmenent la dissociation\n", - "def dissociation_post_swap(g: OrdinalGame, swap: str,\n", - " n_total: int = 20, swap_round: int = 10) -> Tuple[float, float]:\n", - " \"\"\"\n", - " Mesure la dissociation sticky vs BR apres le swap.\n", - " Retourne (dissociation_sticky_post, dissociation_BR_post) en pourcentage.\n", - " \"\"\"\n", - " h_sticky = play_with_swap(g, swap, swap_round=swap_round, n_total=n_total, mode=\"sticky_preferred\")\n", - " h_br = play_with_swap(g, swap, swap_round=swap_round, n_total=n_total, mode=\"best_response\")\n", - " d_sticky = dissociation_rate(g, h_sticky[swap_round:]) # post-swap seulement\n", - " d_br = dissociation_rate(g, h_br[swap_round:])\n", - " return d_sticky, d_br\n", - "\n", - "\n", - "# Indice etudiant : pour chaque chambre X, iterer sur tous les swaps R{i}{j} et C{i}{j}\n", - "# valides (0 <= i < j <= 3), mesurer dissociation_post_swap(X, swap), et retourner\n", - "# les swaps qui maximisent la dissociation sticky vs BR.\n", - "def find_max_dissociation_swap(g: OrdinalGame) -> List[Tuple[str, float, float]]:\n", - " \"\"\"\n", - " Pour la chambre g, retourne les swaps (swap, d_sticky, d_br) tries par\n", - " dissociation sticky decroissante.\n", - " \"\"\"\n", - " # TODO etudiant : iterer sur tous les swaps valides (6 R-swaps + 6 C-swaps),\n", - " # appeler dissociation_post_swap, et retourner la liste triee.\n", - " return [] # Stub C.1\n", - "\n", - "\n", - "# Demonstration partielle : sur Dilemme\n", - "print(\"=== Exercice 2 : swaps qui revelent la dissociation sur Dilemme ===\")\n", - "results_dilemme = []\n", - "for i in range(4):\n", - " for j in range(i+1, 4):\n", - " for kind in [\"R\", \"C\"]:\n", - " swap = f\"{kind}{i}{j}\"\n", - " d_s, d_b = dissociation_post_swap(g=CLASSIC_GAMES[\"Dilemme\"], swap=swap)\n", - " results_dilemme.append((swap, d_s, d_b))\n", - "# Trier par dissociation sticky decroissante\n", - "results_dilemme.sort(key=lambda x: -x[1])\n", - "print(f\"{'Swap':6s} {'d_sticky_post':15s} {'d_BR_post':15s}\")\n", - "for swap, d_s, d_b in results_dilemme[:6]:\n", - " print(f\"{swap:6s} {d_s:>13.0%} {d_b:>13.0%}\")" - ] + "source": "# Exercice 2 : Caracteriser les swaps qui augmenent la dissociation\ndef dissociation_post_swap(g: OrdinalGame, swap: str,\n n_total: int = 20, swap_round: int = 10) -> Tuple[float, float]:\n \"\"\"\n Mesure la dissociation sticky vs BR apres le swap.\n Retourne (dissociation_sticky_post, dissociation_BR_post) en pourcentage.\n \"\"\"\n h_sticky = play_with_swap(g, swap, swap_round=swap_round, n_total=n_total, mode=\"sticky_preferred\")\n h_br = play_with_swap(g, swap, swap_round=swap_round, n_total=n_total, mode=\"best_response\")\n d_sticky = dissociation_rate(g, h_sticky[swap_round:]) # post-swap seulement\n d_br = dissociation_rate(g, h_br[swap_round:])\n return d_sticky, d_br\n\n\n# Indice etudiant : pour chaque chambre X, iterer sur tous les swaps R{i}{j} et C{i}{j}\n# valides (0 <= i < j <= 3), mesurer dissociation_post_swap(X, swap), et retourner\n# les swaps qui maximisent la dissociation sticky vs BR.\ndef find_max_dissociation_swap(g: OrdinalGame) -> List[Tuple[str, float, float]]:\n \"\"\"\n Pour la chambre g, retourne les swaps (swap, d_sticky, d_br) tries par\n dissociation sticky decroissante.\n \"\"\"\n # TODO etudiant : iterer sur tous les swaps valides (6 R-swaps + 6 C-swaps),\n # appeler dissociation_post_swap, et retourner la liste triee.\n return [] # Stub C.1\n\n\n# Demonstration partielle : sur Dilemme\nprint(\"=== Exercice 2 : swaps qui revelent la dissociation sur Dilemme ===\")\nresults_dilemme = []\nfor i in range(4):\n for j in range(i+1, 4):\n for kind in [\"R\", \"C\"]:\n swap = f\"{kind}{i}{j}\"\n d_s, d_b = dissociation_post_swap(g=CLASSIC_GAMES[\"Dilemme\"], swap=swap)\n results_dilemme.append((swap, d_s, d_b))\n# Trier par dissociation sticky decroissante\nresults_dilemme.sort(key=lambda x: -x[1])\nprint(f\"{'Swap':6s} {'d_sticky_post':15s} {'d_BR_post':15s}\")\nfor swap, d_s, d_b in results_dilemme[:6]:\n print(f\"{swap:6s} {d_s:>13.0%} {d_b:>13.0%}\")" }, { "cell_type": "code", @@ -1241,10 +453,10 @@ "id": "cell-21", "metadata": { "execution": { - "iopub.execute_input": "2026-08-23T11:38:06.151404Z", - "iopub.status.busy": "2026-08-23T11:38:06.151228Z", - "iopub.status.idle": "2026-08-23T11:38:06.155365Z", - "shell.execute_reply": "2026-08-23T11:38:06.154740Z" + "iopub.status.busy": "2026-08-24T20:50:06.032711Z", + "iopub.execute_input": "2026-08-24T20:50:06.033003Z", + "shell.execute_reply": "2026-08-24T20:50:06.037081Z", + "iopub.status.idle": "2026-08-24T20:50:06.037862Z" }, "papermill": { "duration": 0.009026, @@ -1257,62 +469,12 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "=== Exercice 3 : cyclicite de BattleSexes ===\n", - "Representation cyclique possible :\n", - " (C,C) : Row prefere C | (C,D) : Row prefere D\n", - " (D,C) : Row prefere C | (D,D) : Row prefere C\n", - "\n", - "Cycle : C < D (en CC) -> D < C (en CD) -> C < D (en DC) -> D < C (en DD)\n", - "=> Non representable en ordinal strict transitif.\n", - "=> Stub : completer best_response_nontransitive avec les 4 ensembles.\n" - ] + "name": "stdout", + "text": "=== Exercice 3 : cyclicite de BattleSexes ===\nRepresentation cyclique possible :\n (C,C) : Row prefere C | (C,D) : Row prefere D\n (D,C) : Row prefere C | (D,D) : Row prefere C\n\nCycle : C < D (en CC) -> D < C (en CD) -> C < D (en DC) -> D < C (en DD)\n=> Non representable en ordinal strict transitif.\n=> Stub : completer best_response_nontransitive avec les 4 ensembles.\n" } ], - "source": [ - "# Exercice 3 : Cyclicite de BattleSexes -- formalisation non-transitive\n", - "# Indice : pour representer BoS canonique avec 2 Nash (C,D) et (D,C), il faut\n", - "# autoriser des preferences NON transitives (le joueur peut preferer C a D,\n", - "# D a (C,C), et (C,C) a C, etc.). Une solution : utiliser des \"circles de\n", - "# preference\" plutot que des rangs lineaires.\n", - "\n", - "from typing import Dict, Set # noqa: E402 (import local pour la cellule exercice)\n", - "\n", - "def best_response_nontransitive(g_cyclic: Dict[Tuple[str, str], Set[str]],\n", - " player: str, opponent_action: str) -> str:\n", - " \"\"\"\n", - " Pour une representation cyclique des preferences :\n", - " g_cyclic[action] = ensemble des actions strictement preferees.\n", - " Retourne la meilleure reponse selon cette relation cyclique.\n", - "\n", - " Indice etudiant :\n", - " - Si g_cyclic[(C,C)] contient D, alors C < D quand (C,C) est joue\n", - " - Si g_cyclic[(D,C)] contient C, alors D < C quand (D,C) est joue\n", - " - Pour BattleSexes : definir les 4 ensembles cycliques\n", - " \"\"\"\n", - " # TODO etudiant : definir les 4 ensembles cycliques pour BattleSexes\n", - " # et implementer la selection d'action\n", - " return \"C\" # Stub C.1\n", - "\n", - "\n", - "# Demonstration : pour BattleSexes canonique, une representation cyclique\n", - "# pourrait etre :\n", - "# - En (C,C) : Row prefere C (relation C < D, i.e. D prefere)\n", - "# - En (C,D) : Row prefere D (relation C > D, i.e. C prefere encore)\n", - "# - En (D,C) : Row prefere C (relation D < C)\n", - "# - En (D,D) : Row prefere C (relation D > C)\n", - "# Cette relation est CYCLIQUE : C < D < C, violant la transitivite.\n", - "print(\"=== Exercice 3 : cyclicite de BattleSexes ===\")\n", - "print(\"Representation cyclique possible :\")\n", - "print(\" (C,C) : Row prefere C | (C,D) : Row prefere D\")\n", - "print(\" (D,C) : Row prefere C | (D,D) : Row prefere C\")\n", - "print()\n", - "print(\"Cycle : C < D (en CC) -> D < C (en CD) -> C < D (en DC) -> D < C (en DD)\")\n", - "print(\"=> Non representable en ordinal strict transitif.\")\n", - "print(\"=> Stub : completer best_response_nontransitive avec les 4 ensembles.\")" - ] + "source": "# Exercice 3 : Cyclicite de BattleSexes -- formalisation non-transitive\n# Indice : pour representer BoS canonique avec 2 Nash (C,D) et (D,C), il faut\n# autoriser des preferences NON transitives (le joueur peut preferer C a D,\n# D a (C,C), et (C,C) a C, etc.). Une solution : utiliser des \"circles de\n# preference\" plutot que des rangs lineaires.\n\nfrom typing import Dict, Set # noqa: E402 (import local pour la cellule exercice)\n\ndef best_response_nontransitive(g_cyclic: Dict[Tuple[str, str], Set[str]],\n player: str, opponent_action: str) -> str:\n \"\"\"\n Pour une representation cyclique des preferences :\n g_cyclic[action] = ensemble des actions strictement preferees.\n Retourne la meilleure reponse selon cette relation cyclique.\n\n Indice etudiant :\n - Si g_cyclic[(C,C)] contient D, alors C < D quand (C,C) est joue\n - Si g_cyclic[(D,C)] contient C, alors D < C quand (D,C) est joue\n - Pour BattleSexes : definir les 4 ensembles cycliques\n \"\"\"\n # TODO etudiant : definir les 4 ensembles cycliques pour BattleSexes\n # et implementer la selection d'action\n return \"C\" # Stub C.1\n\n\n# Demonstration : pour BattleSexes canonique, une representation cyclique\n# pourrait etre :\n# - En (C,C) : Row prefere C (relation C < D, i.e. D prefere)\n# - En (C,D) : Row prefere D (relation C > D, i.e. C prefere encore)\n# - En (D,C) : Row prefere C (relation D < C)\n# - En (D,D) : Row prefere C (relation D > C)\n# Cette relation est CYCLIQUE : C < D < C, violant la transitivite.\nprint(\"=== Exercice 3 : cyclicite de BattleSexes ===\")\nprint(\"Representation cyclique possible :\")\nprint(\" (C,C) : Row prefere C | (C,D) : Row prefere D\")\nprint(\" (D,C) : Row prefere C | (D,D) : Row prefere C\")\nprint()\nprint(\"Cycle : C < D (en CC) -> D < C (en CD) -> C < D (en DC) -> D < C (en DD)\")\nprint(\"=> Non representable en ordinal strict transitif.\")\nprint(\"=> Stub : completer best_response_nontransitive avec les 4 ensembles.\")" }, { "cell_type": "markdown", @@ -1327,33 +489,7 @@ }, "tags": [] }, - "source": [ - "## 9. Conclusion\n", - "\n", - "Le joueur LLM simulé est un **modèle paramétrique** : cinq règles explicites (`sticky` paramétrée par sa première action, `alternating`, `noisy`, `best_response`, `scot`), toutes exécutées par le même moteur `simulate_player` — aucun comportement n'est écrit en dur, tout émerge de la règle round après round.\n", - "\n", - "- **BattleSexes, Dilemme, Chicken** : dissociation **maximale** pour le sticky — il colle à son option d'ouverture et n'atteint pas l'équilibre. C'est le pattern empirique du papier (Mei et al. 2025), obtenu ici comme propriété de la règle.\n", - "\n", - "- **StagHunt, Harmony, Coordination** : dissociation **nulle** pour le sticky — l'option d'ouverture C coïncide avec l'équilibre. Le joueur y performe « bien » par accident de ses préférences, pas par compréhension.\n", - "\n", - "- **SCoT** : la transmutation gratuite chiffrée — et elle échoue dans ce simulateur (0% en BoS/Chicken) là où le papier observe qu'elle aide les vrais LLMs. La leçon : ce n'est pas *prédire* qui coordonne, c'est prédire **récursivement** — l'adversaire qui me prédit aussi. L'écart simulé/réel est la signature de la profondeur de raisonnement social.\n", - "\n", - "L'apport **conceptuel** de ce notebook est de montrer que la dissociation (b) du papier — **GPT-4 prédit l'alternance et n'agit pas** — est une **propriété structurelle** des chambres à conflit (BoS, Chicken), pas un artefact du modèle. Et la grammaire R-G (swaps en cours de partie) permet de **révéler** la dissociation quand elle est accidentellement cachée (E3).\n", - "\n", - "**Substrat EPITA** (#12254) : les personas de `2025-Epita-Intelligence-Symbolique` permettent l'extension directe — l'agent qui anticipe le contre-argument le réfute-t-il ? Plusieurs agents en désaccord sur la méta-action ? Ces questions héritent la grammaire de dissociation et la mesure explicite.\n", - "\n", - "## Sources\n", - "\n", - "- Mei et al., *Playing Repeated Games with Large Language Models*, Nature Human Behaviour (2025), [s41562-025-02172-y](https://www.nature.com/articles/s41562-025-02172-y) — page lue 2026-08-22.\n", - "- Robinson & Goforth, *The Topology of the 2x2 Games* (2005) — implémentation dans `GameTheory-3` cellule 5 (`OrdinalGame`).\n", - "- Bruns, *Austausch und Gerechtigkeit* (1975) — notion de transmutation (information nouvelle vs déplacement), voir aussi GameTheory-21 (Loi III, transformations vs morphismes).\n", - "- GameTheory-3 (chambres R-G) : [GameTheory-03-Topology2x2.ipynb](GameTheory-03-Topology2x2.ipynb)\n", - "- GameTheory-21 (morphisme fini, swaps préservants) : [GameTheory-21-Deux-Especes-de-Fleches.ipynb](GameTheory-21-Deux-Especes-de-Fleches.ipynb)\n", - "\n", - "***\n", - "\n", - "**Navigation** : [GameTheory-3](GameTheory-03-Topology2x2.ipynb) · **GameTheory-03c-Le-Joueur-LLM** · [GameTheory-21](GameTheory-21-Deux-Especes-de-Fleches.ipynb)" - ] + "source": "## 9. Conclusion\n\nLe joueur LLM simulé est un **modèle paramétrique** : cinq règles explicites (`sticky` paramétrée par sa première action, `alternating`, `noisy`, `best_response`, `scot`), toutes exécutées par le même moteur `simulate_player` — aucun comportement n'est écrit en dur, tout émerge de la règle round après round.\n\n- **BattleSexes, Dilemme, Chicken** : dissociation **maximale** pour le sticky — il colle à son option d'ouverture et n'atteint pas l'équilibre. C'est le pattern empirique du papier (Mei et al. 2025), obtenu ici comme propriété de la règle.\n\n- **StagHunt, Harmony, Coordination** : dissociation **nulle** pour le sticky — l'option d'ouverture C coïncide avec l'équilibre. Le joueur y performe « bien » par accident de ses préférences, pas par compréhension.\n\n- **SCoT** : la transmutation gratuite chiffrée — et elle échoue dans ce simulateur (0% en BoS/Chicken) là où le papier observe qu'elle aide les vrais LLMs. La leçon : ce n'est pas *prédire* qui coordonne, c'est prédire **récursivement** — l'adversaire qui me prédit aussi. L'écart simulé/réel est la signature de la profondeur de raisonnement social.\n\nL'apport **conceptuel** de ce notebook est de montrer que la dissociation (b) du papier — **GPT-4 prédit l'alternance et n'agit pas** — est une **propriété structurelle** des chambres à conflit (BoS, Chicken), pas un artefact du modèle. Et la grammaire R-G (swaps en cours de partie) permet de **révéler** la dissociation quand elle est accidentellement cachée (E3).\n\n**Substrat EPITA** (#12254) : les personas de `2025-Epita-Intelligence-Symbolique` permettent l'extension directe — l'agent qui anticipe le contre-argument le réfute-t-il ? Plusieurs agents en désaccord sur la méta-action ? Ces questions héritent la grammaire de dissociation et la mesure explicite.\n\n## Sources\n\n- Mei et al., *Playing Repeated Games with Large Language Models*, Nature Human Behaviour (2025), [s41562-025-02172-y](https://www.nature.com/articles/s41562-025-02172-y) — page lue 2026-08-22.\n- Robinson & Goforth, *The Topology of the 2x2 Games* (2005) — implémentation dans `GameTheory-3` cellule 5 (`OrdinalGame`).\n- Bruns, *Austausch und Gerechtigkeit* (1975) — notion de transmutation (information nouvelle vs déplacement), voir aussi GameTheory-21 (Loi III, transformations vs morphismes).\n- GameTheory-3 (chambres R-G) : [GameTheory-03-Topology2x2.ipynb](GameTheory-03-Topology2x2.ipynb)\n- GameTheory-21 (morphisme fini, swaps préservants) : [GameTheory-21-Deux-Especes-de-Fleches.ipynb](GameTheory-21-Deux-Especes-de-Fleches.ipynb)\n\n***\n\n**Navigation** : [GameTheory-3](GameTheory-03-Topology2x2.ipynb) · **GameTheory-03c-Le-Joueur-LLM** · [GameTheory-21](GameTheory-21-Deux-Especes-de-Fleches.ipynb)" } ], "metadata": { @@ -1363,16 +499,16 @@ "name": "python3" }, "language_info": { + "name": "python", + "version": "3.13.7", + "mimetype": "text/x-python", "codemirror_mode": { "name": "ipython", "version": 3 }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.7" + "nbconvert_exporter": "python", + "file_extension": ".py" }, "papermill": { "default_parameters": {}, @@ -1389,4 +525,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file