GameTheory 2b - Formalisation Lean : Definitions de Base

Navigation : << 2-NormalForm (track principal) | Index

Kernel : Lean 4 (WSL)


Introduction

Ce notebook inaugure la Partie 4 de la serie : la formalisation en Lean 4 des concepts de théorie des jeux. Après avoir explore les algorithmes et simulations en Python (notebooks 1-16), nous passons maintenant a la verification formelle.

Pourquoi formaliser en Lean ?

Aspect Python (simulation) Lean (formalisation)
Objectif Calculer, simuler, visualiser Prouver, verifier, garantir
Erreurs Detectees a l’exécution Impossibles si compile
Theoremes Illustres par exemples Prouves mathematiquement
Confiance Tests, intuition Preuves formelles

Isomorphisme de Curry-Howard

Lean repose sur l’isomorphisme de Curry-Howard : - Propositions = Types : Une proposition P est un type - Preuves = Termes : Une preuve de P est un terme de type P - Implication = Fonction : Prouver P → Q revient a construire une fonction P → Q

Objectifs pedagogiques

  1. Définir formellement les structures Game, Strategy, Payoff
  2. Formaliser les stratégies mixtes via le simplexe standard
  3. Définir l’equilibre de Nash et la notion de meilleure reponse
  4. Encoder le Dilemme du Prisonnier et verifier ses proprietes

Prerequis

  • Avoir complete les notebooks Python 01-02 (concepts de base)
  • Notions de base en Lean 4 (serie SymbolicAI/Lean recommandee)
  • Kernel Lean 4 (WSL) installe (voir scripts/README.md)

Duree estimee : 45 minutes


Plan de ce Notebook

  1. Configuration et Imports
  2. Structure Game
  3. Stratégies Pures et Mixtes
  4. Equilibre de Nash
  5. Exemple : Dilemme du Prisonnier
  6. Exemples guides
  7. Exercices
  8. Solutions
  9. Resume

Références

  • Nash 1950 Equilibrium Points in n-Person Games PNAS
  • Osborne & Rubinstein 1994 A Course in Game Theory MIT Press
  • Leyton-Brown & Shoham 2008 Essentials of Game Theory (livre libre en ligne)
  • Myerson 1991 Game Theory: Analysis of Conflict Harvard UP
  • Fudenberg & Tirole 1991 Game Theory MIT Press
  • Lean 4 référence manual (leanprover.github.io)

Conventions

  • Les actions sont codees par Fin 2 (0 ou 1) pour les jeux 2x2
  • Les payoffs sont Int (entiers) pour la simplicité
  • Les stratégies mixtes utilisent Float (alternative : Rat ou Real pour plus de rigueur)
  • Les preuves sont par decide, omega, ou pattern matching explicite

## 1. Configuration

Ce notebook utilise le kernel Lean 4 (WSL) qui execute Lean directement dans WSL Ubuntu.

Configuration requise

Pour utiliser ce notebook : 1. Assurez-vous que le kernel “Lean 4 (WSL)” est installe (voir install_wsl_kernel.md) 2. Selectionnez le kernel “Lean 4 (WSL)” dans VSCode

Alternative : lean_runner.py

Si vous preferez utiliser Python, les notebooks 17 et 18 utilisent lean_runner.py qui permet d’executer du code Lean depuis Python.

Pourquoi Lean 4 plutot que Coq / Agda / Isabelle

  • Lean 4 = dependently typed programming language avec tactiques d’automatisation
  • Mathlib = bibliothèque de mathématiques formalisees (~1M de lignes)
  • vs Coq : tactiques plus lisibles, syntaxe plus proche des maths
  • vs Agda : meilleur support de l’automatisation via decide, simp, omega
  • vs Isabelle/HOL : logique constructive (vs logique classique), types dépendants

Le kernel lean4-wsl

Ce notebook utilise un kernel Jupyter pour Lean 4 installable via :


### Test rapide

Verifions que Lean fonctionne correctement.

La cellule suivante est un smoke test :
- `#eval 2 + 2` doit retourner `4`
- `#check Nat` doit retourner `Nat : Type`

Ces 2 commandes n'ont pas d'effets de bord, juste de la vérification.

### Pourquoi ce test

Le test rapide est crucial pour valider l'environnement avant d'exécuter du code
plus complexe. Si `#eval 2 + 2` ne retourne pas `4`, le kernel Lean n'est pas
correctement configuré (PATH, lake, elan).

### Sortie attendue

#eval 2 + 2 ─▶ 4 #check Nat ─▶ Nat : Type


Si la sortie diffère, le compilateur Lean a un problème.

### Coût

Ce test prend ~0.1s (juste `#eval` + `#check` sur des constantes).

::: {#82e2a394 .cell quarto-private-1='{"key":"papermill","value":{"duration":0.236184,"end_time":"2026-06-08T06:02:30.841032+00:00","exception":false,"start_time":"2026-06-08T06:02:30.604848+00:00","status":"completed"}}' tags='[]' execution_count=1}
``` {.lean4 .cell-code}
#eval 2 + 2
#check Nat
4
Nat : Type
--% env 0
Raw input {"cmd": "#eval 2 + 2\n#check Nat"}
Raw output {"messages": [{"severity": "info", "pos": {"line": 1, "column": 0}, "endPos": {"line": 1, "column": 5}, "data": "4"}, {"severity": "info", "pos": {"line": 2, "column": 0}, "endPos": {"line": 2, "column": 6}, "data": "Nat : Type"}], "env": 0}

:::

Lecture du smoke test

La cellule produit la sortie :

#eval 2 + 2   ─▶  4
#check Nat    ─▶  Nat : Type

C’est la confirmation rapide que le kernel Lean fonctionne. Si la sortie diffère, le kernel n’est pas correctement configuré.

Vérification

Le #eval 2 + 2 doit retourner exactement 4 (pas 4.0, pas 4 : Nat, juste 4). Le #check Nat doit retourner Nat : Type (le type Nat est dans l’univers Type).

Si la sortie diffère

  1. Vérifier que elan est installe : elan --version
  2. Vérifier que le kernel lean4-wsl est actif : jupyter kernelspec list | grep lean4
  3. Vérifier que le toolchain stable est selectionne : lean --version

Coût

Ce test prend ~0.1s.

## 2. Structure Game

2.1 Definition minimale

Un jeu en forme normale est défini par : - Un ensemble de joueurs I - Pour chaque joueur i, un ensemble d’actions (ou stratégies pures) A i - Pour chaque joueur i, une fonction de gain qui associe un reel a chaque profil d’actions

En Lean 4, nous pouvons encoder cela avec une structure :

Inspiration

Cette définition est inspirée de : - math-xmum/Brouwer (Brouwer : point fixe formalisé) - MixedMatched/formalizing-game-theory (GitHub repo avec formalisation Nash)

Différence avec Osborne & Rubinstein

Osborne & Rubinstein définissent un jeu comme (N, (A_i), (u_i)) ou : - N = ensemble des joueurs (fini en pratique) - A_i = actions du joueur i - u_i = fonction d’utilité du joueur i (mapping profil → réel)

Notre NormalFormGame suit la même convention, avec : - Players : Type au lieu de N : Type (Lean’s Type est l’univers le plus général) - Actions : Players → Type au lieu de (A_i : Set i) (dependently typed) - payoff : ... → Int (entiers au lieu de réels, pour la simplicité)

Pourquoi Int et pas Float / Rat

  • Int : suffisant pour les exemples classiques (PD, Chicken, etc.) ou les payoffs sont des entiers
  • Float : nécessaire pour les stratégies mixtes (probabilités continues)
  • Rat : ratio exact (p/q), idéal pour les stratégies mixtes rationnelles
  • Real : réels, mais nécessite analysis (Mathlib) pour les manipulations

Pour ce notebook, on utilise Int pour les payoffs et Float pour les stratégies mixtes.

Coût

Compilation de NormalFormGame : ~0.5s (vérification de type).

-- Definition de base d'un jeu en forme normale
-- Inspire de math-xmum/Brouwer et MixedMatched/formalizing-game-theory

structure NormalFormGame where
  /-- Ensemble des joueurs -/
  Players : Type
  /-- Ensemble des actions pour chaque joueur -/
  Actions : Players → Type
  /-- Fonction de gain pour chaque joueur -/
  payoff : (i : Players) → ((j : Players) → Actions j) → Int

#check NormalFormGame
-- Definition de base d'un jeu en forme normale
-- Inspire de math-xmum/Brouwer et MixedMatched/formalizing-game-theory
structure NormalFormGame where
  /-- Ensemble des joueurs -/
  Players : Type
  /-- Ensemble des actions pour chaque joueur -/
  Actions : Players → Type
  /-- Fonction de gain pour chaque joueur -/
  payoff : (i : Players) → ((j : Players) → Actions j) → Int
NormalFormGame : Type 1
--% env 1
Raw input {"cmd": "-- Definition de base d'un jeu en forme normale\n-- Inspire de math-xmum/Brouwer et MixedMatched/formalizing-game-theory\n\nstructure NormalFormGame where\n /-- Ensemble des joueurs -/\n Players : Type\n /-- Ensemble des actions pour chaque joueur -/\n Actions : Players \u2192 Type\n /-- Fonction de gain pour chaque joueur -/\n payoff : (i : Players) \u2192 ((j : Players) \u2192 Actions j) \u2192 Int\n\n#check NormalFormGame", "env": 0}
Raw output {"messages": [{"severity": "info", "pos": {"line": 12, "column": 0}, "endPos": {"line": 12, "column": 6}, "data": "NormalFormGame : Type 1"}], "env": 1}

Lecture de la définition NormalFormGame

La cellule précédente a défini la structure fondamentale :

structure NormalFormGame where
  Players : Type
  Actions : Players → Type
  payoff : (i : Players) → ((j : Players) → Actions j) → Int

Un jeu en forme normale est un triplet (Players, Actions, payoff) — le champ de gain est payoff en minuscule en Lean — où le gain de chaque joueur dépend du profil complet d’actions.

Lean 4 vs Coq

  • Lean 4 : structure Foo where ... avec champs automatiquement générés
  • Coq : Record Foo : Type := mkFoo { ... } avec projection explicite
  • Agda : record Foo : Set ... similaire a Lean
  • Isabelle : record Foo = ... syntaxe différente

Lean 4 est plus proche de la syntaxe mathématique moderne, ce qui le rend plus lisible.

Coût

Compilation : ~0.5s (vérification de type de la structure).

2.2 Jeu fini

Pour les theoremes importants (existence de Nash), nous avons besoin de jeux finis ou les ensembles de joueurs et d’actions sont finis.

Définition formelle

structure FiniteGame where
  numPlayers : Nat
  numActions : Fin numPlayers → Nat
  payoff : (i : Fin numPlayers) → (s : (j : Fin numPlayers) → Fin (numActions j)) → Int

Pourquoi Fin n

Fin n est le type des entiers {0, 1, ..., n-1} en Lean. C’est la représentation canonique d’un ensemble fini de taille n : - Fin 0 est vide - Fin 3 contient 0, 1, 2 - Impossible d’avoir Fin 3 avec une valeur 5 (typage le refuse)

C’est crucial pour les preuves : on peut itérer sur Fin n avec Finset.univ, prouver par induction sur n, etc.

Coût

Compilation de FiniteGame : ~0.5s.

-- Jeu fini avec contraintes de finitude
structure FiniteGame where
  /-- Nombre de joueurs (utilise Fin n pour avoir exactement n joueurs) -/
  numPlayers : Nat
  /-- Nombre d'actions pour chaque joueur -/
  numActions : Fin numPlayers → Nat
  /-- Fonction de gain : pour chaque joueur, retourne le gain en fonction du profil d'actions -/
  payoff : (i : Fin numPlayers) → ((j : Fin numPlayers) → Fin (numActions j)) → Int

#check FiniteGame

-- Exemple : jeu a 2 joueurs, 2 actions chacun
def twoPlayerTwoActions : FiniteGame := {
  numPlayers := 2
  numActions := fun _ => 2  -- Chaque joueur a 2 actions
  payoff := fun i profile =>
    -- Gains arbitraires pour l'exemple
    if i.val == 0 then
      if profile ⟨0, by omega⟩ == ⟨0, by omega⟩ && profile ⟨1, by omega⟩ == ⟨0, by omega⟩ then 3
      else 0
    else 0
}

#check twoPlayerTwoActions
-- Jeu fini avec contraintes de finitude
structure FiniteGame where
  /-- Nombre de joueurs (utilise Fin n pour avoir exactement n joueurs) -/
  numPlayers : Nat
  /-- Nombre d'actions pour chaque joueur -/
  numActions : Fin numPlayers → Nat
  /-- Fonction de gain : pour chaque joueur, retourne le gain en fonction du profil d'actions -/
  payoff : (i : Fin numPlayers) → ((j : Fin numPlayers) → Fin (numActions j)) → Int
FiniteGame : Type
-- Exemple : jeu a 2 joueurs, 2 actions chacun
def twoPlayerTwoActions : FiniteGame := {
  numPlayers := 2
  numActions := fun _ => 2  -- Chaque joueur a 2 actions
  payoff := fun i profile =>
    -- Gains arbitraires pour l'exemple
    if i.val == 0 then
      if profile ⟨0, by omega⟩ == ⟨0, by omega⟩ && profile ⟨1, by omega⟩ == ⟨0, by omega⟩ then 3
      else 0
    else 0
}
twoPlayerTwoActions : FiniteGame
--% env 2
Raw input {"cmd": "-- Jeu fini avec contraintes de finitude\nstructure FiniteGame where\n /-- Nombre de joueurs (utilise Fin n pour avoir exactement n joueurs) -/\n numPlayers : Nat\n /-- Nombre d'actions pour chaque joueur -/\n numActions : Fin numPlayers \u2192 Nat\n /-- Fonction de gain : pour chaque joueur, retourne le gain en fonction du profil d'actions -/\n payoff : (i : Fin numPlayers) \u2192 ((j : Fin numPlayers) \u2192 Fin (numActions j)) \u2192 Int\n\n#check FiniteGame\n\n-- Exemple : jeu a 2 joueurs, 2 actions chacun\ndef twoPlayerTwoActions : FiniteGame := {\n numPlayers := 2\n numActions := fun _ => 2 -- Chaque joueur a 2 actions\n payoff := fun i profile =>\n -- Gains arbitraires pour l'exemple\n if i.val == 0 then\n if profile \u27e80, by omega\u27e9 == \u27e80, by omega\u27e9 && profile \u27e81, by omega\u27e9 == \u27e80, by omega\u27e9 then 3\n else 0\n else 0\n}\n\n#check twoPlayerTwoActions", "env": 1}
Raw output {"messages": [{"severity": "info", "pos": {"line": 10, "column": 0}, "endPos": {"line": 10, "column": 6}, "data": "FiniteGame : Type"}, {"severity": "info", "pos": {"line": 24, "column": 0}, "endPos": {"line": 24, "column": 6}, "data": "twoPlayerTwoActions : FiniteGame"}], "env": 2}

2.3 Jeu 2x2 simplifie

Pour les exemples classiques (Prisonnier, Chicken, etc.), un jeu 2x2 est plus pratique :

structure Game2x2 where
  payoff1 : Fin 2 → Fin 2 → Int
  payoff2 : Fin 2 → Fin 2 → Int

Convention d’indices

  • payoff1 i j : gain du joueur 1 s’il joue action i et le joueur 2 joue action j
  • payoff2 i j : gain du joueur 2 (notation duale)
  • Actions : 0 = première action, 1 = deuxième action

Pourquoi pas une matrice 2x2

On pourrait utiliser une matrice :

payoff1 : Matrix (Fin 2) (Fin 2) Int

Mais cela ajoute une couche d’indirection. La signature Fin 2 → Fin 2 → Int est plus directe et permet le pattern matching :

match i, j with
| 0, 0 => 3  -- (Ceder, Ceder)
| 0, 1 => 2  -- (Ceder, Foncer)
| 1, 0 => 2
| 1, 1 => 1

Coût

Compilation de Game2x2 : ~0.3s (structure simple).

-- Jeu 2x2 : 2 joueurs, 2 actions chacun
-- Actions : 0 = première action, 1 = deuxieme action
structure Game2x2 where
  /-- Matrice des gains du joueur 1 (lignes) -/
  payoff1 : Fin 2 → Fin 2 → Int
  /-- Matrice des gains du joueur 2 (colonnes) -/
  payoff2 : Fin 2 → Fin 2 → Int

-- Helper pour créer un jeu 2x2 a partir de 4 paires de gains
def mkGame2x2 (a11 b11 a12 b12 a21 b21 a22 b22 : Int) : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => a11 | 0, 1 => a12
    | 1, 0 => a21 | 1, 1 => a22
    | _, _ => 0  -- Ne devrait pas arriver
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => b11 | 0, 1 => b12
    | 1, 0 => b21 | 1, 1 => b22
    | _, _ => 0
}

#check Game2x2
#check mkGame2x2
-- Jeu 2x2 : 2 joueurs, 2 actions chacun
-- Actions : 0 = premiere action, 1 = deuxieme action
structure Game2x2 where
  /-- Matrice des gains du joueur 1 (lignes) -/
  payoff1 : Fin 2 → Fin 2 → Int
  /-- Matrice des gains du joueur 2 (colonnes) -/
  payoff2 : Fin 2 → Fin 2 → Int
-- Helper pour creer un jeu 2x2 a partir de 4 paires de gains
def mkGame2x2 (a11 b11 a12 b12 a21 b21 a22 b22 : Int) : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => a11 | 0, 1 => a12
    | 1, 0 => a21 | 1, 1 => a22
    | _, _ => 0  -- Ne devrait pas arriver
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => b11 | 0, 1 => b12
    | 1, 0 => b21 | 1, 1 => b22
    | _, _ => 0
}
Game2x2 : Type
mkGame2x2 (a11 b11 a12 b12 a21 b21 a22 b22 : Int) : Game2x2
--% env 3
Raw input {"cmd": "-- Jeu 2x2 : 2 joueurs, 2 actions chacun\n-- Actions : 0 = premiere action, 1 = deuxieme action\nstructure Game2x2 where\n /-- Matrice des gains du joueur 1 (lignes) -/\n payoff1 : Fin 2 \u2192 Fin 2 \u2192 Int\n /-- Matrice des gains du joueur 2 (colonnes) -/\n payoff2 : Fin 2 \u2192 Fin 2 \u2192 Int\n\n-- Helper pour creer un jeu 2x2 a partir de 4 paires de gains\ndef mkGame2x2 (a11 b11 a12 b12 a21 b21 a22 b22 : Int) : Game2x2 := {\n payoff1 := fun i j =>\n match i.val, j.val with\n | 0, 0 => a11 | 0, 1 => a12\n | 1, 0 => a21 | 1, 1 => a22\n | _, _ => 0 -- Ne devrait pas arriver\n payoff2 := fun i j =>\n match i.val, j.val with\n | 0, 0 => b11 | 0, 1 => b12\n | 1, 0 => b21 | 1, 1 => b22\n | _, _ => 0\n}\n\n#check Game2x2\n#check mkGame2x2", "env": 2}
Raw output {"messages": [{"severity": "info", "pos": {"line": 23, "column": 0}, "endPos": {"line": 23, "column": 6}, "data": "Game2x2 : Type"}, {"severity": "info", "pos": {"line": 24, "column": 0}, "endPos": {"line": 24, "column": 6}, "data": "mkGame2x2 (a11 b11 a12 b12 a21 b21 a22 b22 : Int) : Game2x2"}], "env": 3}

## 3. Stratégies Pures et Mixtes

3.1 Stratégie pure

Une stratégie pure est simplement une action choisie par le joueur.

Un profil est une stratégie pour chaque joueur :

def PureStrategyProfile (g : FiniteGame) := (i : Fin g.numPlayers) → Fin (g.numActions i)

C’est une fonction dépendante : pour chaque joueur i, on choisit une stratégie pure.

Notation

Dans la littérature, un profil est souvent note s = (s_1, ..., s_n) ou s_i est la stratégie du joueur i. Notre formalisation Lean suit la même convention.

Coût

Compilation : ~0.5s.

-- Une stratégie pure est juste une action
def PureStrategy (g : FiniteGame) (i : Fin g.numPlayers) := Fin (g.numActions i)

-- Un profil de stratégies pures : une stratégie pour chaque joueur
def PureStrategyProfile (g : FiniteGame) := (i : Fin g.numPlayers) → Fin (g.numActions i)

#check @PureStrategy
#check @PureStrategyProfile
-- Une strategie pure est juste une action
def PureStrategy (g : FiniteGame) (i : Fin g.numPlayers) := Fin (g.numActions i)
-- Un profil de strategies pures : une strategie pour chaque joueur
def PureStrategyProfile (g : FiniteGame) := (i : Fin g.numPlayers) → Fin (g.numActions i)
PureStrategy : (g : FiniteGame) → Fin g.numPlayers → Type
PureStrategyProfile : FiniteGame → Type
--% env 4
Raw input {"cmd": "-- Une strategie pure est juste une action\ndef PureStrategy (g : FiniteGame) (i : Fin g.numPlayers) := Fin (g.numActions i)\n\n-- Un profil de strategies pures : une strategie pour chaque joueur\ndef PureStrategyProfile (g : FiniteGame) := (i : Fin g.numPlayers) \u2192 Fin (g.numActions i)\n\n#check @PureStrategy\n#check @PureStrategyProfile", "env": 3}
Raw output {"messages": [{"severity": "info", "pos": {"line": 7, "column": 0}, "endPos": {"line": 7, "column": 6}, "data": "PureStrategy : (g : FiniteGame) → Fin g.numPlayers → Type"}, {"severity": "info", "pos": {"line": 8, "column": 0}, "endPos": {"line": 8, "column": 6}, "data": "PureStrategyProfile : FiniteGame → Type"}], "env": 4}

Lecture des stratégies pures

La cellule a défini :

def PureStrategy (g : FiniteGame) (i : Fin g.numPlayers) := Fin (g.numActions i)

Une stratégie pure pour le joueur i dans le jeu g est juste une action : un élément de Fin (g.numActions i).

Profil

Un profil de stratégies pures est une stratégie pour chaque joueur :

def PureStrategyProfile (g : FiniteGame) := (i : Fin g.numPlayers) → Fin (g.numActions i)

C’est une fonction dépendante : pour chaque joueur, on choisit une action.

Notation

  • s_i : stratégie pure du joueur i
  • s = (s_1, ..., s_n) : profil de stratégies pures

Coût

Compilation : ~0.5s.

3.2 Stratégie mixte et simplexe standard

Une stratégie mixte est une distribution de probabilite sur les actions. Mathematiquement, c’est un point du simplexe standard :

\[\Delta^{n-1} = \{(p_1, ..., p_n) \in \mathbb{R}^n : p_i \geq 0, \sum_i p_i = 1\}\]

En Lean, nous encodons cela avec un sous-type :

Définition Lean

La cellule suivante définit le simplexe standard avec ses deux conditions :

def isNonNeg (f : Fin n → Float) : Prop := ∀ i, f i >= 0

def sumToOne (f : Fin n → Float) : Prop := 
  (List.finRange n).foldl (fun acc i => acc + f i) 0 = 1

structure Simplex (n : Nat) where
  prob : Fin n → Float
  nonNeg : ∀ i, prob i >= 0 := by decide
  sumOne : (List.finRange n).foldl (fun acc i => acc + prob i) 0 = 1 := by native_decide

Pourquoi Float

  • Float : IEEE 754 double precision, supporte par Lean nativement
  • Rat : ratio exact p/q, ideal pour la rigueur mathématique mais verbose
  • Real : réels abstraits, nécessite Mathlib analysis

Pour ce notebook, on utilise Float pour la simplicité. Pour des preuves formelles plus rigoureuses, on utiliserait Rat.

Coût

Compilation de MixedStrategy : ~1s (vérification des propriétés du sous-type).

-- Pour les stratégies mixtes, nous avons besoin de nombres reels
-- Utilisons Float pour la simplicite (Rat ou Real pour plus de rigueur)

-- Simplexe standard : distribution de probabilite sur n éléments
-- C'est un sous-type avec deux conditions :
-- 1. Toutes les probabilites sont >= 0
-- 2. La somme des probabilites = 1

def isNonNeg (f : Fin n → Float) : Prop := ∀ i, f i >= 0

def sumToOne (f : Fin n → Float) : Prop := 
  (List.finRange n).foldl (fun acc i => acc + f i) 0 = 1

-- Le simplexe standard de dimension n-1 (n points)
structure Simplex (n : Nat) where
  prob : Fin n → Float
  nonNeg : ∀ i, prob i >= 0 := by decide
  sumOne : (List.finRange n).foldl (fun acc i => acc + prob i) 0 = 1 := by native_decide

#check Simplex
#check @Simplex.prob
-- Pour les strategies mixtes, nous avons besoin de nombres reels
-- Utilisons Float pour la simplicite (Rat ou Real pour plus de rigueur)
-- Simplexe standard : distribution de probabilite sur n elements
-- C'est un sous-type avec deux conditions :
-- 1. Toutes les probabilites sont >= 0
-- 2. La somme des probabilites = 1
def isNonNeg (f : Fin n → Float) : Prop := ∀ i, f i >= 0
def sumToOne (f : Fin n → Float) : Prop := 
  (List.finRange n).foldl (fun acc i => acc + f i) 0 = 1
-- Le simplexe standard de dimension n-1 (n points)
structure Simplex (n : Nat) where
  prob : Fin n → Float
  nonNeg : ∀ i, prob i >= 0 := by decide
  sumOne : (List.finRange n).foldl (fun acc i => acc + prob i) 0 = 1 := by native_decide
Simplex (n : Nat) : Type
@Simplex.prob : {n : Nat} → Simplex n → Fin n → Float
--% env 5
Raw input {"cmd": "-- Pour les strategies mixtes, nous avons besoin de nombres reels\n-- Utilisons Float pour la simplicite (Rat ou Real pour plus de rigueur)\n\n-- Simplexe standard : distribution de probabilite sur n elements\n-- C'est un sous-type avec deux conditions :\n-- 1. Toutes les probabilites sont >= 0\n-- 2. La somme des probabilites = 1\n\ndef isNonNeg (f : Fin n \u2192 Float) : Prop := \u2200 i, f i >= 0\n\ndef sumToOne (f : Fin n \u2192 Float) : Prop := \n (List.finRange n).foldl (fun acc i => acc + f i) 0 = 1\n\n-- Le simplexe standard de dimension n-1 (n points)\nstructure Simplex (n : Nat) where\n prob : Fin n \u2192 Float\n nonNeg : \u2200 i, prob i >= 0 := by decide\n sumOne : (List.finRange n).foldl (fun acc i => acc + prob i) 0 = 1 := by native_decide\n\n#check Simplex\n#check @Simplex.prob", "env": 4}
Raw output {"messages": [{"severity": "info", "pos": {"line": 20, "column": 0}, "endPos": {"line": 20, "column": 6}, "data": "Simplex (n : Nat) : Type"}, {"severity": "info", "pos": {"line": 21, "column": 0}, "endPos": {"line": 21, "column": 6}, "data": "@Simplex.prob : {n : Nat} → Simplex n → Fin n → Float"}], "env": 5}

3.3 Stratégie mixte pour un jeu

Une stratégie mixte pour un joueur dans un jeu fini :

Notation

  • sigma_i : stratégie mixte du joueur i
  • sigma : profil de stratégies mixtes (une pour chaque joueur)
  • sigma_-i : profil de stratégies mixtes pour tous les joueurs SAUF i

Coût

Compilation : ~0.3s.

-- Stratégie mixte : distribution sur les actions d'un joueur
def MixedStrategy (numActions : Nat) := 
  { f : Fin numActions → Float // (∀ i, f i >= 0) ∧ 
    (List.ofFn f).foldl (· + ·) 0 = 1 }

-- Profil de stratégies mixtes pour un jeu a 2 joueurs
structure MixedProfile2 (n1 n2 : Nat) where
  sigma1 : Fin n1 → Float  -- Distribution joueur 1
  sigma2 : Fin n2 → Float  -- Distribution joueur 2
  -- Conditions de validite (simplifiees)
  h1_pos : ∀ i, sigma1 i >= 0 := by decide
  h2_pos : ∀ i, sigma2 i >= 0 := by decide

#check @MixedStrategy
#check MixedProfile2
-- Strategie mixte : distribution sur les actions d'un joueur
def MixedStrategy (numActions : Nat) := 
  { f : Fin numActions → Float // (∀ i, f i >= 0) ∧ 
    (List.ofFn f).foldl (· + ·) 0 = 1 }
-- Profil de strategies mixtes pour un jeu a 2 joueurs
structure MixedProfile2 (n1 n2 : Nat) where
  sigma1 : Fin n1 → Float  -- Distribution joueur 1
  sigma2 : Fin n2 → Float  -- Distribution joueur 2
  -- Conditions de validite (simplifiees)
  h1_pos : ∀ i, sigma1 i >= 0 := by decide
  h2_pos : ∀ i, sigma2 i >= 0 := by decide
MixedStrategy : Nat → Type
MixedProfile2 (n1 n2 : Nat) : Type
--% env 6
Raw input {"cmd": "-- Strategie mixte : distribution sur les actions d'un joueur\ndef MixedStrategy (numActions : Nat) := \n { f : Fin numActions \u2192 Float // (\u2200 i, f i >= 0) \u2227 \n (List.ofFn f).foldl (\u00b7 + \u00b7) 0 = 1 }\n\n-- Profil de strategies mixtes pour un jeu a 2 joueurs\nstructure MixedProfile2 (n1 n2 : Nat) where\n sigma1 : Fin n1 \u2192 Float -- Distribution joueur 1\n sigma2 : Fin n2 \u2192 Float -- Distribution joueur 2\n -- Conditions de validite (simplifiees)\n h1_pos : \u2200 i, sigma1 i >= 0 := by decide\n h2_pos : \u2200 i, sigma2 i >= 0 := by decide\n\n#check @MixedStrategy\n#check MixedProfile2", "env": 5}
Raw output {"messages": [{"severity": "info", "pos": {"line": 14, "column": 0}, "endPos": {"line": 14, "column": 6}, "data": "MixedStrategy : Nat → Type"}, {"severity": "info", "pos": {"line": 15, "column": 0}, "endPos": {"line": 15, "column": 6}, "data": "MixedProfile2 (n1 n2 : Nat) : Type"}], "env": 6}

Lecture de MixedStrategy

La cellule produit la sortie :

def MixedStrategy (numActions : Nat) :=
  { f : Fin numActions → Float // (∀ i, f i >= 0) ∧
    (List.ofFn f).foldl (· + ·) 0 = 1 }

Un sous-type : une fonction Fin numActions → Float avec 2 propriétés : 1. Toutes les valeurs >= 0 (probabilités non-négatives) 2. La somme = 1 (normalisation)

Subtype pattern

Lean 4 permet de définir des sous-types avec { f : T // P f } ou P est une propriété. C’est crucial pour les stratégies mixtes : on ne peut pas creer une distribution de probabilités invalide.

Vérification des propriétés

Quand on cree une MixedStrategy, Lean vérifie automatiquement : - ∀ i, f i >= 0 : propriété de non-négativité - (List.ofFn f).foldl (· + ·) 0 = 1 : somme = 1

Si l’une de ces propriétés n’est pas vérifiée, la compilation echoue.

Coût

Compilation : ~1s (vérification des sous-types).

3.4 Gain espere

Le gain espere d’un joueur sous un profil de stratégies mixtes :

La formule générale (commentaire de la cellule suivante) :

-- E[u1] = sum_i sum_j sigma1(i) * sigma2(j) * payoff1(i, j)

Pour un jeu 2x2, cela se simplifie en 4 profils pondérés :

def intToFloat (n : Int) : Float := Float.ofInt n

def expectedPayoff1 (g : Game2x2) (s1 : Fin 2 → Float) (s2 : Fin 2 → Float) : Float :=
  let i0 : Fin 2 := ⟨0, by omega⟩
  let i1 : Fin 2 := ⟨1, by omega⟩
  s1 i0 * s2 i0 * intToFloat (g.payoff1 i0 i0) +
  s1 i0 * s2 i1 * intToFloat (g.payoff1 i0 i1) +
  s1 i1 * s2 i0 * intToFloat (g.payoff1 i1 i0) +
  s1 i1 * s2 i1 * intToFloat (g.payoff1 i1 i1)

Pourquoi 4 termes

Pour un jeu 2x2, il y a 4 profils possibles : (0,0), (0,1), (1,0), (1,1). Le gain espéré est la somme des 4 contributions pondérées.

Coût

Compilation : ~0.5s (Float operations).

-- Gain espere pour un jeu 2x2 avec stratégies mixtes
-- E[u1] = sum_i sum_j sigma1(i) * sigma2(j) * payoff1(i,j)

-- Helper pour convertir Int en Float
def intToFloat (n : Int) : Float := Float.ofInt n

def expectedPayoff1 (g : Game2x2) (s1 : Fin 2 → Float) (s2 : Fin 2 → Float) : Float :=
  let i0 : Fin 2 := ⟨0, by omega⟩
  let i1 : Fin 2 := ⟨1, by omega⟩
  s1 i0 * s2 i0 * intToFloat (g.payoff1 i0 i0) +
  s1 i0 * s2 i1 * intToFloat (g.payoff1 i0 i1) +
  s1 i1 * s2 i0 * intToFloat (g.payoff1 i1 i0) +
  s1 i1 * s2 i1 * intToFloat (g.payoff1 i1 i1)

def expectedPayoff2 (g : Game2x2) (s1 : Fin 2 → Float) (s2 : Fin 2 → Float) : Float :=
  let i0 : Fin 2 := ⟨0, by omega⟩
  let i1 : Fin 2 := ⟨1, by omega⟩
  s1 i0 * s2 i0 * intToFloat (g.payoff2 i0 i0) +
  s1 i0 * s2 i1 * intToFloat (g.payoff2 i0 i1) +
  s1 i1 * s2 i0 * intToFloat (g.payoff2 i1 i0) +
  s1 i1 * s2 i1 * intToFloat (g.payoff2 i1 i1)

#check @expectedPayoff1
#check @expectedPayoff2
-- Gain espere pour un jeu 2x2 avec strategies mixtes
-- E[u1] = sum_i sum_j sigma1(i) * sigma2(j) * payoff1(i,j)
-- Helper pour convertir Int en Float
def intToFloat (n : Int) : Float := Float.ofInt n
def expectedPayoff1 (g : Game2x2) (s1 : Fin 2 → Float) (s2 : Fin 2 → Float) : Float :=
  let i0 : Fin 2 := ⟨0, by omega⟩
  let i1 : Fin 2 := ⟨1, by omega⟩
  s1 i0 * s2 i0 * intToFloat (g.payoff1 i0 i0) +
  s1 i0 * s2 i1 * intToFloat (g.payoff1 i0 i1) +
  s1 i1 * s2 i0 * intToFloat (g.payoff1 i1 i0) +
  s1 i1 * s2 i1 * intToFloat (g.payoff1 i1 i1)
def expectedPayoff2 (g : Game2x2) (s1 : Fin 2 → Float) (s2 : Fin 2 → Float) : Float :=
  let i0 : Fin 2 := ⟨0, by omega⟩
  let i1 : Fin 2 := ⟨1, by omega⟩
  s1 i0 * s2 i0 * intToFloat (g.payoff2 i0 i0) +
  s1 i0 * s2 i1 * intToFloat (g.payoff2 i0 i1) +
  s1 i1 * s2 i0 * intToFloat (g.payoff2 i1 i0) +
  s1 i1 * s2 i1 * intToFloat (g.payoff2 i1 i1)
expectedPayoff1 : Game2x2 → (Fin 2 → Float) → (Fin 2 → Float) → Float
expectedPayoff2 : Game2x2 → (Fin 2 → Float) → (Fin 2 → Float) → Float
--% env 7
Raw input {"cmd": "-- Gain espere pour un jeu 2x2 avec strategies mixtes\n-- E[u1] = sum_i sum_j sigma1(i) * sigma2(j) * payoff1(i,j)\n\n-- Helper pour convertir Int en Float\ndef intToFloat (n : Int) : Float := Float.ofInt n\n\ndef expectedPayoff1 (g : Game2x2) (s1 : Fin 2 \u2192 Float) (s2 : Fin 2 \u2192 Float) : Float :=\n let i0 : Fin 2 := \u27e80, by omega\u27e9\n let i1 : Fin 2 := \u27e81, by omega\u27e9\n s1 i0 * s2 i0 * intToFloat (g.payoff1 i0 i0) +\n s1 i0 * s2 i1 * intToFloat (g.payoff1 i0 i1) +\n s1 i1 * s2 i0 * intToFloat (g.payoff1 i1 i0) +\n s1 i1 * s2 i1 * intToFloat (g.payoff1 i1 i1)\n\ndef expectedPayoff2 (g : Game2x2) (s1 : Fin 2 \u2192 Float) (s2 : Fin 2 \u2192 Float) : Float :=\n let i0 : Fin 2 := \u27e80, by omega\u27e9\n let i1 : Fin 2 := \u27e81, by omega\u27e9\n s1 i0 * s2 i0 * intToFloat (g.payoff2 i0 i0) +\n s1 i0 * s2 i1 * intToFloat (g.payoff2 i0 i1) +\n s1 i1 * s2 i0 * intToFloat (g.payoff2 i1 i0) +\n s1 i1 * s2 i1 * intToFloat (g.payoff2 i1 i1)\n\n#check @expectedPayoff1\n#check @expectedPayoff2", "env": 6}
Raw output {"messages": [{"severity": "info", "pos": {"line": 23, "column": 0}, "endPos": {"line": 23, "column": 6}, "data": "expectedPayoff1 : Game2x2 → (Fin 2 → Float) → (Fin 2 → Float) → Float"}, {"severity": "info", "pos": {"line": 24, "column": 0}, "endPos": {"line": 24, "column": 6}, "data": "expectedPayoff2 : Game2x2 → (Fin 2 → Float) → (Fin 2 → Float) → Float"}], "env": 7}

## 4. Equilibre de Nash

4.1 Definition formelle

Un equilibre de Nash est un profil de stratégies tel qu’aucun joueur ne peut ameliorer son gain en changeant unilateralement de stratégie.

Formellement, \((\sigma_1^*, \sigma_2^*)\) est un equilibre de Nash si : - \(\forall \sigma_1, E[u_1(\sigma_1^*, \sigma_2^*)] \geq E[u_1(\sigma_1, \sigma_2^*)]\) - \(\forall \sigma_2, E[u_2(\sigma_1^*, \sigma_2^*)] \geq E[u_2(\sigma_1^*, \sigma_2)]\)

Définition pour Game2x2

def isBestResponse1 (g : Game2x2) (s1 s2 : Fin 2 → Float) : Prop :=
  ∀ s1' : Fin 2 → Float, expectedPayoff1 g s1 s2 >= expectedPayoff1 g s1' s2

Différence avec meilleures réponses

  • Meilleure réponse : BR(s_-i) = argmax_{s_i} u_i(s_i, s_-i)
  • Équilibre de Nash : s* = (s*_1, …, s*_n) ou s*_i = BR(s*_{-i}) pour tout i

Autrement dit, chaque joueur joue une meilleure réponse aux autres.

Existence

Nash (1950) a prouvé que tout jeu fini admet au moins un équilibre de Nash (en stratégies mixtes). C’est le théorème de Nash. La preuve utilise le théorème du point fixe de Brouwer.

Coût

Compilation : ~0.5s.

-- Definition de l'equilibre de Nash pour un jeu 2x2

-- Le joueur 1 joue une meilleure reponse a s2
def isBestResponse1 (g : Game2x2) (s1 : Fin 2 → Float) (s2 : Fin 2 → Float) : Prop :=
  ∀ s1' : Fin 2 → Float, 
    expectedPayoff1 g s1 s2 >= expectedPayoff1 g s1' s2

-- Le joueur 2 joue une meilleure reponse a s1
def isBestResponse2 (g : Game2x2) (s1 : Fin 2 → Float) (s2 : Fin 2 → Float) : Prop :=
  ∀ s2' : Fin 2 → Float,
    expectedPayoff2 g s1 s2 >= expectedPayoff2 g s1 s2'

-- Equilibre de Nash : chaque joueur joue une meilleure reponse
def isNashEquilibrium (g : Game2x2) (s1 : Fin 2 → Float) (s2 : Fin 2 → Float) : Prop :=
  isBestResponse1 g s1 s2 ∧ isBestResponse2 g s1 s2

#check @isNashEquilibrium
-- Definition de l'equilibre de Nash pour un jeu 2x2
-- Le joueur 1 joue une meilleure reponse a s2
def isBestResponse1 (g : Game2x2) (s1 : Fin 2 → Float) (s2 : Fin 2 → Float) : Prop :=
  ∀ s1' : Fin 2 → Float, 
    expectedPayoff1 g s1 s2 >= expectedPayoff1 g s1' s2
-- Le joueur 2 joue une meilleure reponse a s1
def isBestResponse2 (g : Game2x2) (s1 : Fin 2 → Float) (s2 : Fin 2 → Float) : Prop :=
  ∀ s2' : Fin 2 → Float,
    expectedPayoff2 g s1 s2 >= expectedPayoff2 g s1 s2'
-- Equilibre de Nash : chaque joueur joue une meilleure reponse
def isNashEquilibrium (g : Game2x2) (s1 : Fin 2 → Float) (s2 : Fin 2 → Float) : Prop :=
  isBestResponse1 g s1 s2 ∧ isBestResponse2 g s1 s2
isNashEquilibrium : Game2x2 → (Fin 2 → Float) → (Fin 2 → Float) → Prop
--% env 8
Raw input {"cmd": "-- Definition de l'equilibre de Nash pour un jeu 2x2\n\n-- Le joueur 1 joue une meilleure reponse a s2\ndef isBestResponse1 (g : Game2x2) (s1 : Fin 2 \u2192 Float) (s2 : Fin 2 \u2192 Float) : Prop :=\n \u2200 s1' : Fin 2 \u2192 Float, \n expectedPayoff1 g s1 s2 >= expectedPayoff1 g s1' s2\n\n-- Le joueur 2 joue une meilleure reponse a s1\ndef isBestResponse2 (g : Game2x2) (s1 : Fin 2 \u2192 Float) (s2 : Fin 2 \u2192 Float) : Prop :=\n \u2200 s2' : Fin 2 \u2192 Float,\n expectedPayoff2 g s1 s2 >= expectedPayoff2 g s1 s2'\n\n-- Equilibre de Nash : chaque joueur joue une meilleure reponse\ndef isNashEquilibrium (g : Game2x2) (s1 : Fin 2 \u2192 Float) (s2 : Fin 2 \u2192 Float) : Prop :=\n isBestResponse1 g s1 s2 \u2227 isBestResponse2 g s1 s2\n\n#check @isNashEquilibrium", "env": 7}
Raw output {"messages": [{"severity": "info", "pos": {"line": 17, "column": 0}, "endPos": {"line": 17, "column": 6}, "data": "isNashEquilibrium : Game2x2 → (Fin 2 → Float) → (Fin 2 → Float) → Prop"}], "env": 8}

4.2 Equilibre de Nash en stratégies pures

Pour les stratégies pures, la definition est plus simple :

def isPureNashEquilibrium (g : Game2x2) (a1 a2 : Fin 2) : Prop :=
  (∀ a1' : Fin 2, g.payoff1 a1 a2 >= g.payoff1 a1' a2) ∧
  (∀ a2' : Fin 2, g.payoff2 a1 a2 >= g.payoff2 a1 a2')

Avantage des stratégies pures

  • Plus simple a raisonner (pas de distributions)
  • Correspond a l’intuition “qu’est-ce que je joue si je sais ce que l’autre joue”
  • Les classiques (PD, Chicken, Stag Hunt) sont souvent en stratégies pures

Limitation

Tous les jeux n’ont pas d’équilibre en stratégies pures. Par exemple, Matching Pennies (Pierre-Feuille-Ciseaux simplifie) n’a pas d’équilibre en stratégies pures : il faut des stratégies mixtes (50/50).

Coût

Compilation : ~0.5s.

-- Equilibre de Nash en stratégies pures pour jeu 2x2
def isPureNashEquilibrium (g : Game2x2) (a1 : Fin 2) (a2 : Fin 2) : Prop :=
  -- Joueur 1 ne peut pas ameliorer en changeant d'action
  (∀ a1' : Fin 2, g.payoff1 a1 a2 >= g.payoff1 a1' a2) ∧
  -- Joueur 2 ne peut pas ameliorer en changeant d'action
  (∀ a2' : Fin 2, g.payoff2 a1 a2 >= g.payoff2 a1 a2')

#check @isPureNashEquilibrium
-- Equilibre de Nash en strategies pures pour jeu 2x2
def isPureNashEquilibrium (g : Game2x2) (a1 : Fin 2) (a2 : Fin 2) : Prop :=
  -- Joueur 1 ne peut pas ameliorer en changeant d'action
  (∀ a1' : Fin 2, g.payoff1 a1 a2 >= g.payoff1 a1' a2) ∧
  -- Joueur 2 ne peut pas ameliorer en changeant d'action
  (∀ a2' : Fin 2, g.payoff2 a1 a2 >= g.payoff2 a1 a2')
isPureNashEquilibrium : Game2x2 → Fin 2 → Fin 2 → Prop
--% env 9
Raw input {"cmd": "-- Equilibre de Nash en strategies pures pour jeu 2x2\ndef isPureNashEquilibrium (g : Game2x2) (a1 : Fin 2) (a2 : Fin 2) : Prop :=\n -- Joueur 1 ne peut pas ameliorer en changeant d'action\n (\u2200 a1' : Fin 2, g.payoff1 a1 a2 >= g.payoff1 a1' a2) \u2227\n -- Joueur 2 ne peut pas ameliorer en changeant d'action\n (\u2200 a2' : Fin 2, g.payoff2 a1 a2 >= g.payoff2 a1 a2')\n\n#check @isPureNashEquilibrium", "env": 8}
Raw output {"messages": [{"severity": "info", "pos": {"line": 8, "column": 0}, "endPos": {"line": 8, "column": 6}, "data": "isPureNashEquilibrium : Game2x2 → Fin 2 → Fin 2 → Prop"}], "env": 9}

4.3 Proprietes de base

Quelques proprietes simples des equilibres de Nash :

Propriété 1 : un équilibre en stratégies pures est aussi un équilibre en stratégies mixtes (quand on identifie une stratégie pure avec la distribution dégénérée).

Propriété 2 : si un jeu est à somme nulle (u_1 + u_2 = 0), les stratégies mixtes d’équilibre sont uniques (théorème de minimax).

Propriété 3 : les NE en stratégies mixtes peuvent être trouvés par élimination de stratégies dominées.

Démonstration

La Propriété 1 est triviale : une stratégie pure a peut être vue comme la distribution dégénérée delta_a (100% sur a, 0% ailleurs). Si a est NE, alors delta_a est aussi NE.

Coût

Vérification de la propriété 1 : ~0.5s.

-- Propriete : un equilibre en stratégies pures est aussi un equilibre en stratégies mixtes
-- (quand on identifie une stratégie pure avec la distribution degeneree)

-- Stratégie pure comme stratégie mixte degeneree
def pureToMixed (a : Fin 2) : Fin 2 → Float :=
  fun i => if i == a then 1.0 else 0.0

#check @pureToMixed

-- Theoreme (enonce) : si (a1, a2) est un equilibre de Nash pur,
-- alors (pureToMixed a1, pureToMixed a2) est un equilibre de Nash mixte
-- La preuve complete necessite plus de travail sur les Float
theorem pure_nash_implies_mixed_nash (g : Game2x2) (a1 a2 : Fin 2)
    (h : isPureNashEquilibrium g a1 a2) :
    True := by  -- Simplifie pour l'exemple
  trivial
-- Propriete : un equilibre en strategies pures est aussi un equilibre en strategies mixtes
-- (quand on identifie une strategie pure avec la distribution degeneree)
-- Strategie pure comme strategie mixte degeneree
def pureToMixed (a : Fin 2) : Fin 2 → Float :=
  fun i => if i == a then 1.0 else 0.0
pureToMixed : Fin 2 → Fin 2 → Float
-- Theoreme (enonce) : si (a1, a2) est un equilibre de Nash pur,
-- alors (pureToMixed a1, pureToMixed a2) est un equilibre de Nash mixte
-- La preuve complete necessite plus de travail sur les Float
theorem pure_nash_implies_mixed_nash (g : Game2x2) (a1 a2 : Fin 2)
🟨 unused variable `h` Note: This linter can be disabled with `set_option linter.unusedVariables false`
    True := by  -- Simplifie pour l'exemple
  trivial
--% env 10
Raw input {"cmd": "-- Propriete : un equilibre en strategies pures est aussi un equilibre en strategies mixtes\n-- (quand on identifie une strategie pure avec la distribution degeneree)\n\n-- Strategie pure comme strategie mixte degeneree\ndef pureToMixed (a : Fin 2) : Fin 2 \u2192 Float :=\n fun i => if i == a then 1.0 else 0.0\n\n#check @pureToMixed\n\n-- Theoreme (enonce) : si (a1, a2) est un equilibre de Nash pur,\n-- alors (pureToMixed a1, pureToMixed a2) est un equilibre de Nash mixte\n-- La preuve complete necessite plus de travail sur les Float\ntheorem pure_nash_implies_mixed_nash (g : Game2x2) (a1 a2 : Fin 2)\n (h : isPureNashEquilibrium g a1 a2) :\n True := by -- Simplifie pour l'exemple\n trivial", "env": 9}
Raw output {"messages": [{"severity": "info", "pos": {"line": 8, "column": 0}, "endPos": {"line": 8, "column": 6}, "data": "pureToMixed : Fin 2 → Fin 2 → Float"}, {"severity": "warning", "pos": {"line": 14, "column": 5}, "endPos": {"line": 14, "column": 6}, "data": "unused variable `h`\n\nNote: This linter can be disabled with `set_option linter.unusedVariables false`"}], "env": 10}

## 5. Exemple : Dilemme du Prisonnier

5.1 Definition du jeu

Le Dilemme du Prisonnier classique :

Cooperer (0) Trahir (1)
Cooperer (0) (3, 3) (0, 5)
Trahir (1) (5, 0) (1, 1)

Gains

  • (C, C) = (3, 3) : recompense de cooperation mutuelle
  • (C, T) = (0, 5) : tentation (trahison exploitee)
  • (T, C) = (5, 0) : recompense du traitre (gain max)
  • (T, T) = (1, 1) : punition de defection mutuelle

Définition Lean

def prisonersDilemma : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 3   -- (Cooperer, Cooperer)
    | 0, 1 => 0   -- (Cooperer, Trahir)
    | 1, 0 => 5   -- (Trahir, Cooperer)
    | 1, 1 => 1   -- (Trahir, Trahir)
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 3
    | 0, 1 => 5
    | 1, 0 => 0
    | 1, 1 => 1
}

Coût

Compilation : ~0.5s.

-- Dilemme du Prisonnier
-- Actions : 0 = Cooperer, 1 = Trahir
-- Gains : (C,C)=(3,3), (C,T)=(0,5), (T,C)=(5,0), (T,T)=(1,1)

def prisonersDilemma : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 3  -- (C, C)
    | 0, 1 => 0  -- (C, T)
    | 1, 0 => 5  -- (T, C)
    | 1, 1 => 1  -- (T, T)
    | _, _ => 0
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 3  -- (C, C)
    | 0, 1 => 5  -- (C, T) - Joueur 2 trahit
    | 1, 0 => 0  -- (T, C)
    | 1, 1 => 1  -- (T, T)
    | _, _ => 0
}

#check prisonersDilemma

-- Verification des gains
#eval prisonersDilemma.payoff1 ⟨0, by omega⟩ ⟨0, by omega⟩  -- C,C -> 3
#eval prisonersDilemma.payoff1 ⟨1, by omega⟩ ⟨0, by omega⟩  -- T,C -> 5
#eval prisonersDilemma.payoff1 ⟨1, by omega⟩ ⟨1, by omega⟩  -- T,T -> 1
-- Dilemme du Prisonnier
-- Actions : 0 = Cooperer, 1 = Trahir
-- Gains : (C,C)=(3,3), (C,T)=(0,5), (T,C)=(5,0), (T,T)=(1,1)
def prisonersDilemma : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 3  -- (C, C)
    | 0, 1 => 0  -- (C, T)
    | 1, 0 => 5  -- (T, C)
    | 1, 1 => 1  -- (T, T)
    | _, _ => 0
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 3  -- (C, C)
    | 0, 1 => 5  -- (C, T) - Joueur 2 trahit
    | 1, 0 => 0  -- (T, C)
    | 1, 1 => 1  -- (T, T)
    | _, _ => 0
}
prisonersDilemma : Game2x2
-- Verification des gains
3
5
1
--% env 11
Raw input {"cmd": "-- Dilemme du Prisonnier\n-- Actions : 0 = Cooperer, 1 = Trahir\n-- Gains : (C,C)=(3,3), (C,T)=(0,5), (T,C)=(5,0), (T,T)=(1,1)\n\ndef prisonersDilemma : Game2x2 := {\n payoff1 := fun i j =>\n match i.val, j.val with\n | 0, 0 => 3 -- (C, C)\n | 0, 1 => 0 -- (C, T)\n | 1, 0 => 5 -- (T, C)\n | 1, 1 => 1 -- (T, T)\n | _, _ => 0\n payoff2 := fun i j =>\n match i.val, j.val with\n | 0, 0 => 3 -- (C, C)\n | 0, 1 => 5 -- (C, T) - Joueur 2 trahit\n | 1, 0 => 0 -- (T, C)\n | 1, 1 => 1 -- (T, T)\n | _, _ => 0\n}\n\n#check prisonersDilemma\n\n-- Verification des gains\n#eval prisonersDilemma.payoff1 \u27e80, by omega\u27e9 \u27e80, by omega\u27e9 -- C,C -> 3\n#eval prisonersDilemma.payoff1 \u27e81, by omega\u27e9 \u27e80, by omega\u27e9 -- T,C -> 5\n#eval prisonersDilemma.payoff1 \u27e81, by omega\u27e9 \u27e81, by omega\u27e9 -- T,T -> 1", "env": 10}
Raw output {"messages": [{"severity": "info", "pos": {"line": 22, "column": 0}, "endPos": {"line": 22, "column": 6}, "data": "prisonersDilemma : Game2x2"}, {"severity": "info", "pos": {"line": 25, "column": 0}, "endPos": {"line": 25, "column": 5}, "data": "3"}, {"severity": "info", "pos": {"line": 26, "column": 0}, "endPos": {"line": 26, "column": 5}, "data": "5"}, {"severity": "info", "pos": {"line": 27, "column": 0}, "endPos": {"line": 27, "column": 5}, "data": "1"}], "env": 11}

5.2 Verification que (Trahir, Trahir) est un equilibre de Nash

Prouvons formellement que (T, T) est l’unique equilibre de Nash en stratégies pures :

theorem prisoners_dilemma_nash : isPureNashEquilibrium prisonersDilemma Trahir Trahir := by
  -- constructor ; pour chaque joueur :
  -- intro a ; simp only [prisonersDilemma, Trahir] ;
  -- cases Decidable.em (a.val = 0) ; omega

Stratégie de preuve

  1. constructor : sépare la conjonction en 2 sous-objectifs (un par joueur)
  2. intro a : considère une action alternative quelconque du joueur
  3. simp only [prisonersDilemma, Trahir] : développe les gains du PD
  4. cases Decidable.em (a.val = 0) : énumère les 2 cas de Fin 2 (0 ou 1)
  5. omega : conclut l’arithmétique linéaire

Sortie attendue

prisoners_dilemma_nash : isPureNashEquilibrium prisonersDilemma Trahir Trahir

Le type est vérifié par Lean (pas de sorry, pas d’axiome non-standard).

Coût

Vérification du théorème : ~2s (le plus long du notebook).

-- Theoreme : (Trahir, Trahir) est un equilibre de Nash du Dilemme du Prisonnier

-- D'abord, definissons les actions
def Cooperer : Fin 2 := ⟨0, by omega⟩
def Trahir : Fin 2 := ⟨1, by omega⟩

-- Theoreme principal : (Trahir, Trahir) est un equilibre de Nash
-- On prouve en utilisant cases sur les valeurs de Fin 2 et omega pour conclure
theorem prisoners_dilemma_nash : isPureNashEquilibrium prisonersDilemma Trahir Trahir := by
  constructor
  -- Joueur 1 : Trahir est meilleure reponse quand J2 trahit
  · intro a1
    simp only [prisonersDilemma, Trahir]
    cases Decidable.em (a1.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a1.val = 1 := by omega
      simp only [this]
      omega
  -- Joueur 2 : Trahir est meilleure reponse quand J1 trahit
  · intro a2
    simp only [prisonersDilemma, Trahir]
    cases Decidable.em (a2.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a2.val = 1 := by omega
      simp only [this]
      omega

#check prisoners_dilemma_nash
-- Theoreme : (Trahir, Trahir) est un equilibre de Nash du Dilemme du Prisonnier
-- D'abord, definissons les actions
def Cooperer : Fin 2 := ⟨0, by omega⟩
def Trahir : Fin 2 := ⟨1, by omega⟩
-- Theoreme principal : (Trahir, Trahir) est un equilibre de Nash
-- On prouve en utilisant cases sur les valeurs de Fin 2 et omega pour conclure
theorem prisoners_dilemma_nash : isPureNashEquilibrium prisonersDilemma Trahir Trahir := by
  constructor
  -- Joueur 1 : Trahir est meilleure reponse quand J2 trahit
  · intro a1
    simp only [prisonersDilemma, Trahir]
    cases Decidable.em (a1.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a1.val = 1 := by omega
      simp only [this]
      omega
  -- Joueur 2 : Trahir est meilleure reponse quand J1 trahit
  · intro a2
    simp only [prisonersDilemma, Trahir]
    cases Decidable.em (a2.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a2.val = 1 := by omega
      simp only [this]
      omega
prisoners_dilemma_nash : isPureNashEquilibrium prisonersDilemma Trahir Trahir
--% env 12
Raw input {"cmd": "-- Theoreme : (Trahir, Trahir) est un equilibre de Nash du Dilemme du Prisonnier\n\n-- D'abord, definissons les actions\ndef Cooperer : Fin 2 := \u27e80, by omega\u27e9\ndef Trahir : Fin 2 := \u27e81, by omega\u27e9\n\n-- Theoreme principal : (Trahir, Trahir) est un equilibre de Nash\n-- On prouve en utilisant cases sur les valeurs de Fin 2 et omega pour conclure\ntheorem prisoners_dilemma_nash : isPureNashEquilibrium prisonersDilemma Trahir Trahir := by\n constructor\n -- Joueur 1 : Trahir est meilleure reponse quand J2 trahit\n \u00b7 intro a1\n simp only [prisonersDilemma, Trahir]\n cases Decidable.em (a1.val = 0) with\n | inl h =>\n simp only [h]\n omega\n | inr h =>\n have : a1.val = 1 := by omega\n simp only [this]\n omega\n -- Joueur 2 : Trahir est meilleure reponse quand J1 trahit\n \u00b7 intro a2\n simp only [prisonersDilemma, Trahir]\n cases Decidable.em (a2.val = 0) with\n | inl h =>\n simp only [h]\n omega\n | inr h =>\n have : a2.val = 1 := by omega\n simp only [this]\n omega\n\n#check prisoners_dilemma_nash", "env": 11}
Raw output {"messages": [{"severity": "info", "pos": {"line": 34, "column": 0}, "endPos": {"line": 34, "column": 6}, "data": "prisoners_dilemma_nash : isPureNashEquilibrium prisonersDilemma Trahir Trahir"}], "env": 12}

Lecture du théorème prisoners_dilemma_nash

Le #check final de la cellule confirme le type :

prisoners_dilemma_nash : isPureNashEquilibrium prisonersDilemma Trahir Trahir

C’est un théorème : Lean a vérifié la preuve complète. Pas de sorry, pas d’axiome non-standard.

Stratégie de preuve

La preuve réelle (cellule précédente) enchaîne : 1. constructor : les 2 composantes de la conjonction (une par joueur) 2. intro a : une action alternative quelconque 3. simp only [prisonersDilemma, Trahir] : développe les gains 4. cases Decidable.em (a.val = 0) : énumère les cas de Fin 2 5. omega : décide l’inégalité finale

Pourquoi omega

omega est une tactique d’arithmétique linéaire sur les entiers (Z). Elle decide automatiquement les inégalités comme 1 > 0, 3 < 5, etc. C’est le sous-ensemble decidable de l’arithmétique linéaire.

Coût

Vérification : ~2s (la plus longue du notebook, car elle énumère 4 cas).

5.3 Verification que (C, C) n’est PAS un equilibre

Montrons que (Cooperer, Cooperer) n’est pas un equilibre car chaque joueur a intérêt a devier :

theorem cooperer_not_nash : ¬ isPureNashEquilibrium prisonersDilemma Cooperer Cooperer := by
  -- intro h ; have h1 := h.1 Trahir ;
  -- simp only [Cooperer, Trahir, prisonersDilemma] at h1 ; omega

Stratégie de preuve

Le joueur 1 peut ameliorer en passant a Trahir : - Si J1 joue C et J2 joue C, gain de J1 = 3 - Si J1 joue T et J2 joue C, gain de J1 = 5 - Donc C n’est pas une meilleure réponse a (C) pour J1

Sortie attendue

cooperer_not_nash : ¬ isPureNashEquilibrium prisonersDilemma Cooperer Cooperer

Coût

Vérification : ~1.5s.

-- (Cooperer, Cooperer) n'est PAS un equilibre de Nash
-- Car le joueur 1 peut ameliorer en passant a Trahir : 3 < 5

theorem cooperer_not_nash : ¬ isPureNashEquilibrium prisonersDilemma Cooperer Cooperer := by
  intro h
  -- h.1 dit que Cooperer est meilleure reponse pour J1
  -- Donc payoff1(Cooperer, Cooperer) >= payoff1(Trahir, Cooperer)
  -- Mais payoff1(C,C) = 3 et payoff1(T,C) = 5, donc 3 >= 5 est faux
  have h1 := h.1 Trahir
  simp only [Cooperer, Trahir, prisonersDilemma] at h1
  omega

#check cooperer_not_nash
-- (Cooperer, Cooperer) n'est PAS un equilibre de Nash
-- Car le joueur 1 peut ameliorer en passant a Trahir : 3 < 5
theorem cooperer_not_nash : ¬ isPureNashEquilibrium prisonersDilemma Cooperer Cooperer := by
  intro h
  -- h.1 dit que Cooperer est meilleure reponse pour J1
  -- Donc payoff1(Cooperer, Cooperer) >= payoff1(Trahir, Cooperer)
  -- Mais payoff1(C,C) = 3 et payoff1(T,C) = 5, donc 3 >= 5 est faux
  have h1 := h.1 Trahir
  simp only [Cooperer, Trahir, prisonersDilemma] at h1
  omega
cooperer_not_nash : ¬isPureNashEquilibrium prisonersDilemma Cooperer Cooperer
--% env 13
Raw input {"cmd": "-- (Cooperer, Cooperer) n'est PAS un equilibre de Nash\n-- Car le joueur 1 peut ameliorer en passant a Trahir : 3 < 5\n\ntheorem cooperer_not_nash : \u00ac isPureNashEquilibrium prisonersDilemma Cooperer Cooperer := by\n intro h\n -- h.1 dit que Cooperer est meilleure reponse pour J1\n -- Donc payoff1(Cooperer, Cooperer) >= payoff1(Trahir, Cooperer)\n -- Mais payoff1(C,C) = 3 et payoff1(T,C) = 5, donc 3 >= 5 est faux\n have h1 := h.1 Trahir\n simp only [Cooperer, Trahir, prisonersDilemma] at h1\n omega\n\n#check cooperer_not_nash", "env": 12}
Raw output {"messages": [{"severity": "info", "pos": {"line": 13, "column": 0}, "endPos": {"line": 13, "column": 6}, "data": "cooperer_not_nash : ¬isPureNashEquilibrium prisonersDilemma Cooperer Cooperer"}], "env": 13}

5.4 Dominance stricte

Dans le Dilemme du Prisonnier, Trahir domine strictement Cooperer :

def strictlyDominates1 (g : Game2x2) (a a' : Fin 2) : Prop :=
  ∀ a2 : Fin 2, g.payoff1 a a2 > g.payoff1 a' a2

theorem trahir_dominates_cooperer : strictlyDominates1 prisonersDilemma Trahir Cooperer := by
  -- intro a2 ; simp only [prisonersDilemma, Trahir, Cooperer] ;
  -- cases Decidable.em (a2.val = 0) ; omega

Définition

a domine strictement a' si pour toute stratégie adverse a2, le gain de a est strictement superieur au gain de a'.

Théorème

Dans le PD : - Pour J1 : Trahir (1) > Cooperer (0) pour a2 = 0 (5 > 3) et pour a2 = 1 (1 > 0) - Pour J2 : symétrique

Coût

Vérification : ~1s.

-- Definition de la dominance stricte
def strictlyDominates1 (g : Game2x2) (a a' : Fin 2) : Prop :=
  ∀ a2 : Fin 2, g.payoff1 a a2 > g.payoff1 a' a2

-- Theoreme : Dans le Dilemme du Prisonnier, Trahir domine strictement Cooperer
-- Trahir donne toujours un meilleur gain que Cooperer, peu importe ce que fait J2
theorem trahir_dominates_cooperer : strictlyDominates1 prisonersDilemma Trahir Cooperer := by
  intro a2
  simp only [prisonersDilemma, Trahir, Cooperer]
  cases Decidable.em (a2.val = 0) with
  | inl h =>
    simp only [h]
    omega
  | inr h =>
    have : a2.val = 1 := by omega
    simp only [this]
    omega

#check trahir_dominates_cooperer

-- Corollaire : Une stratégie strictement dominante est toujours jouee a l'equilibre
-- (Ceci est une propriete générale, pas spécifique au PD)
-- Definition de la dominance stricte
def strictlyDominates1 (g : Game2x2) (a a' : Fin 2) : Prop :=
  ∀ a2 : Fin 2, g.payoff1 a a2 > g.payoff1 a' a2
-- Theoreme : Dans le Dilemme du Prisonnier, Trahir domine strictement Cooperer
-- Trahir donne toujours un meilleur gain que Cooperer, peu importe ce que fait J2
theorem trahir_dominates_cooperer : strictlyDominates1 prisonersDilemma Trahir Cooperer := by
  intro a2
  simp only [prisonersDilemma, Trahir, Cooperer]
  cases Decidable.em (a2.val = 0) with
  | inl h =>
    simp only [h]
    omega
  | inr h =>
    have : a2.val = 1 := by omega
    simp only [this]
    omega
trahir_dominates_cooperer : strictlyDominates1 prisonersDilemma Trahir Cooperer
-- Corollaire : Une strategie strictement dominante est toujours jouee a l'equilibre
-- (Ceci est une propriete generale, pas specifique au PD)
--% env 14
Raw input {"cmd": "-- Definition de la dominance stricte\ndef strictlyDominates1 (g : Game2x2) (a a' : Fin 2) : Prop :=\n \u2200 a2 : Fin 2, g.payoff1 a a2 > g.payoff1 a' a2\n\n-- Theoreme : Dans le Dilemme du Prisonnier, Trahir domine strictement Cooperer\n-- Trahir donne toujours un meilleur gain que Cooperer, peu importe ce que fait J2\ntheorem trahir_dominates_cooperer : strictlyDominates1 prisonersDilemma Trahir Cooperer := by\n intro a2\n simp only [prisonersDilemma, Trahir, Cooperer]\n cases Decidable.em (a2.val = 0) with\n | inl h =>\n simp only [h]\n omega\n | inr h =>\n have : a2.val = 1 := by omega\n simp only [this]\n omega\n\n#check trahir_dominates_cooperer\n\n-- Corollaire : Une strategie strictement dominante est toujours jouee a l'equilibre\n-- (Ceci est une propriete generale, pas specifique au PD)", "env": 13}
Raw output {"messages": [{"severity": "info", "pos": {"line": 19, "column": 0}, "endPos": {"line": 19, "column": 6}, "data": "trahir_dominates_cooperer : strictlyDominates1 prisonersDilemma Trahir Cooperer"}], "env": 14}

## 6. Exemples guides

Exemple guide 1 : Jeu de la Poule Mouillee (Chicken)

Définir le jeu Chicken et prouver qu’il a deux equilibres de Nash en stratégies pures.

Ceder (0) Foncer (1)
Ceder (0) (3, 3) (2, 4)
Foncer (1) (4, 2) (0, 0)

Questions : 1. Définir chickenGame : Game2x2 2. Prouver que (Foncer, Ceder) est un equilibre de Nash 3. Prouver que (Ceder, Foncer) est un equilibre de Nash

Exemple guide 2 : Matching Pennies

Définir le jeu Matching Pennies (jeu a somme nulle) et montrer qu’il n’a pas d’equilibre de Nash en stratégies pures.

Pile (0) Face (1)
Pile (0) (1, -1) (-1, 1)
Face (1) (-1, 1) (1, -1)

Questions : 1. Définir matchingPennies : Game2x2 2. Prouver qu’aucune des 4 paires d’actions pures n’est un equilibre

Exemple guide 3 : Chasse au Cerf (Stag Hunt)

Définir le jeu Stag Hunt et prouver qu’il a deux equilibres de Nash purs.

Cerf (0) Lievre (1)
Cerf (0) (4, 4) (0, 3)
Lievre (1) (3, 0) (2, 2)

Questions : 1. Définir stagHunt : Game2x2 2. Prouver que (Cerf, Cerf) est un equilibre (Pareto-optimal) 3. Prouver que (Lievre, Lievre) est aussi un equilibre (risk-dominant)

## 7. Exercices

Après avoir etudie les exemples guides (Chicken, Matching Pennies, Stag Hunt), voici trois exercices pour mettre en pratique les concepts de definition de jeux, preuve d’equilibre de Nash et preuve de non-equilibre.

Exercice 1 : Bataille des Sexes

Définir le jeu Battle of the Sexes et prouver que l’un de ses equilibres de Nash purs.

Opera (0) Foot (1)
Opera (0) (3, 2) (0, 0)
Foot (1) (0, 0) (2, 3)

Questions : 1. Définir battleOfSexes : Game2x2 2. Prouver que (Opera, Opera) est un equilibre de Nash

Indice : Suivre le même pattern que prisoners_dilemma_nash — utiliser constructor, puis intro et cases Decidable.em pour enumerer les actions possibles.

Références

  • Skyrms 2003 The Stag Hunt and the Evolution of Social Structure (Stag Hunt evolution)
  • Rasmusen 2007 Games and Information (Chicken game analysis)

Coût total des exercices

~5 minutes par exercice pour un étudiant connaissant Lean.

-- Exercice 1 : Bataille des Sexes
-- TODO etudiant : définir le jeu battleOfSexes
-- La matrice de gains est : (Opera,Opera)=(3,2), (Opera,Foot)=(0,0), (Foot,Opera)=(0,0), (Foot,Foot)=(2,3)
def battleOfSexes : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | _, _ => 0  -- TODO etudiant : remplacer par les bons gains
  payoff2 := fun i j =>
    match i.val, j.val with
    | _, _ => 0  -- TODO etudiant : remplacer par les bons gains
}

-- Actions pour la Bataille des Sexes
def Opera : Fin 2 := ⟨0, by omega⟩
def Foot : Fin 2 := ⟨1, by omega⟩

-- TODO etudiant : prouver que (Opera, Opera) est un equilibre de Nash
-- Indice : suivre le pattern de prisoners_dilemma_nash
-- Étape 1 : utiliser constructor pour separer les deux joueurs
-- Étape 2 : pour chaque joueur, utiliser intro, simp only, cases Decidable.em, omega
theorem battleOfSexes_nash_opera : isPureNashEquilibrium battleOfSexes Opera Opera := by
  constructor
  · intro a1
    simp only [battleOfSexes, Opera]
    -- TODO etudiant : completer la preuve pour le joueur 1
    sorry
  · intro a2
    simp only [battleOfSexes, Opera]
    -- TODO etudiant : completer la preuve pour le joueur 2
    sorry

#check battleOfSexes_nash_opera  -- Exercice 1 a completer
-- Exercice 1 : Bataille des Sexes
-- TODO etudiant : definir le jeu battleOfSexes
-- La matrice de gains est : (Opera,Opera)=(3,2), (Opera,Foot)=(0,0), (Foot,Opera)=(0,0), (Foot,Foot)=(2,3)
def battleOfSexes : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | _, _ => 0  -- TODO etudiant : remplacer par les bons gains
  payoff2 := fun i j =>
    match i.val, j.val with
    | _, _ => 0  -- TODO etudiant : remplacer par les bons gains
}
-- Actions pour la Bataille des Sexes
def Opera : Fin 2 := ⟨0, by omega⟩
def Foot : Fin 2 := ⟨1, by omega⟩
-- TODO etudiant : prouver que (Opera, Opera) est un equilibre de Nash
-- Indice : suivre le pattern de prisoners_dilemma_nash
-- Etape 1 : utiliser constructor pour separer les deux joueurs
-- Etape 2 : pour chaque joueur, utiliser intro, simp only, cases Decidable.em, omega
🟨 declaration uses `sorry`
  constructor
  · intro a1
    simp only [battleOfSexes, Opera]
    -- TODO etudiant : completer la preuve pour le joueur 1
    sorry
  · intro a2
    simp only [battleOfSexes, Opera]
    -- TODO etudiant : completer la preuve pour le joueur 2
    sorry
battleOfSexes_nash_opera : isPureNashEquilibrium battleOfSexes Opera Opera
--% env 15
--% prove 1
Raw input {"cmd": "-- Exercice 1 : Bataille des Sexes\n-- TODO etudiant : definir le jeu battleOfSexes\n-- La matrice de gains est : (Opera,Opera)=(3,2), (Opera,Foot)=(0,0), (Foot,Opera)=(0,0), (Foot,Foot)=(2,3)\ndef battleOfSexes : Game2x2 := {\n payoff1 := fun i j =>\n match i.val, j.val with\n | _, _ => 0 -- TODO etudiant : remplacer par les bons gains\n payoff2 := fun i j =>\n match i.val, j.val with\n | _, _ => 0 -- TODO etudiant : remplacer par les bons gains\n}\n\n-- Actions pour la Bataille des Sexes\ndef Opera : Fin 2 := \u27e80, by omega\u27e9\ndef Foot : Fin 2 := \u27e81, by omega\u27e9\n\n-- TODO etudiant : prouver que (Opera, Opera) est un equilibre de Nash\n-- Indice : suivre le pattern de prisoners_dilemma_nash\n-- Etape 1 : utiliser constructor pour separer les deux joueurs\n-- Etape 2 : pour chaque joueur, utiliser intro, simp only, cases Decidable.em, omega\ntheorem battleOfSexes_nash_opera : isPureNashEquilibrium battleOfSexes Opera Opera := by\n constructor\n \u00b7 intro a1\n simp only [battleOfSexes, Opera]\n -- TODO etudiant : completer la preuve pour le joueur 1\n sorry\n \u00b7 intro a2\n simp only [battleOfSexes, Opera]\n -- TODO etudiant : completer la preuve pour le joueur 2\n sorry\n\n#check battleOfSexes_nash_opera -- Exercice 1 a completer", "env": 14}
Raw output {"sorries": [{"proofState": 0, "pos": {"line": 26, "column": 4}, "goal": "case left\na1 : Fin 2\n⊢ 0 ≥ 0", "endPos": {"line": 26, "column": 9}}, {"proofState": 1, "pos": {"line": 30, "column": 4}, "goal": "case right\na2 : Fin 2\n⊢ 0 ≥ 0", "endPos": {"line": 30, "column": 9}}], "messages": [{"severity": "warning", "pos": {"line": 21, "column": 8}, "endPos": {"line": 21, "column": 32}, "data": "declaration uses `sorry`"}, {"severity": "info", "pos": {"line": 32, "column": 0}, "endPos": {"line": 32, "column": 6}, "data": "battleOfSexes_nash_opera : isPureNashEquilibrium battleOfSexes Opera Opera"}], "env": 15}

Exercice 2 : Non-equilibre dans le Dilemme du Prisonnier

Prouver que (Cooperer, Trahir) n’est PAS un equilibre de Nash du Dilemme du Prisonnier.

Question : Prouver ¬ isPureNashEquilibrium prisonersDilemma Cooperer Trahir

Indice : Suivre le pattern de cooperer_not_nash — utiliser intro h, puis have h1 := h.1 Trahir (ou h.2 Cooperer), simp only [...] at h1, omega.

-- Exercice 2 : (Cooperer, Trahir) n'est PAS un equilibre de Nash
-- TODO etudiant : completer la preuve
-- Indice : suivre le pattern de cooperer_not_nash
-- Étape 1 : intro h pour supposer que c'est un equilibre
-- Étape 2 : have h1 := h.1 Trahir (le joueur 1 peut devier vers Trahir)
-- Étape 3 : simp only [Cooperer, Trahir, prisonersDilemma] at h1
-- Étape 4 : omega (car payoff1(Trahir, Trahir)=1 >= payoff1(Cooperer, Trahir)=0 est vrai, mais on peut utiliser h.2)
theorem cooperer_trahir_not_nash : ¬ isPureNashEquilibrium prisonersDilemma Cooperer Trahir := by
  intro h
  -- TODO etudiant : completer la preuve
  -- Indice : utiliser have h1 := h.1 Trahir ou have h2 := h.2 Cooperer
  sorry

#check cooperer_trahir_not_nash  -- Exercice 2 a completer
-- Exercice 2 : (Cooperer, Trahir) n'est PAS un equilibre de Nash
-- TODO etudiant : completer la preuve
-- Indice : suivre le pattern de cooperer_not_nash
-- Etape 1 : intro h pour supposer que c'est un equilibre
-- Etape 2 : have h1 := h.1 Trahir (le joueur 1 peut devier vers Trahir)
-- Etape 3 : simp only [Cooperer, Trahir, prisonersDilemma] at h1
-- Etape 4 : omega (car payoff1(Trahir, Trahir)=1 >= payoff1(Cooperer, Trahir)=0 est vrai, mais on peut utiliser h.2)
🟨 declaration uses `sorry`
  intro h
  -- TODO etudiant : completer la preuve
  -- Indice : utiliser have h1 := h.1 Trahir ou have h2 := h.2 Cooperer
  sorry
cooperer_trahir_not_nash : ¬isPureNashEquilibrium prisonersDilemma Cooperer Trahir
--% env 16
--% prove 2
Raw input {"cmd": "-- Exercice 2 : (Cooperer, Trahir) n'est PAS un equilibre de Nash\n-- TODO etudiant : completer la preuve\n-- Indice : suivre le pattern de cooperer_not_nash\n-- Etape 1 : intro h pour supposer que c'est un equilibre\n-- Etape 2 : have h1 := h.1 Trahir (le joueur 1 peut devier vers Trahir)\n-- Etape 3 : simp only [Cooperer, Trahir, prisonersDilemma] at h1\n-- Etape 4 : omega (car payoff1(Trahir, Trahir)=1 >= payoff1(Cooperer, Trahir)=0 est vrai, mais on peut utiliser h.2)\ntheorem cooperer_trahir_not_nash : \u00ac isPureNashEquilibrium prisonersDilemma Cooperer Trahir := by\n intro h\n -- TODO etudiant : completer la preuve\n -- Indice : utiliser have h1 := h.1 Trahir ou have h2 := h.2 Cooperer\n sorry\n\n#check cooperer_trahir_not_nash -- Exercice 2 a completer", "env": 15}
Raw output {"sorries": [{"proofState": 2, "pos": {"line": 12, "column": 2}, "goal": "h : isPureNashEquilibrium prisonersDilemma Cooperer Trahir\n⊢ False", "endPos": {"line": 12, "column": 7}}], "messages": [{"severity": "warning", "pos": {"line": 8, "column": 8}, "endPos": {"line": 8, "column": 32}, "data": "declaration uses `sorry`"}, {"severity": "info", "pos": {"line": 14, "column": 0}, "endPos": {"line": 14, "column": 6}, "data": "cooperer_trahir_not_nash : ¬isPureNashEquilibrium prisonersDilemma Cooperer Trahir"}], "env": 16}

Exercice 3 : Dominance stricte pour le joueur 2

Définir la dominance stricte pour le joueur 2 (symetrique de strictlyDominates1) et prouver que Trahir domine strictement Cooperer pour le joueur 2 dans le Dilemme du Prisonnier.

Questions : 1. Définir strictlyDominates2 : (g : Game2x2) → (a : Fin 2) → (a' : Fin 2) → Prop analogue a strictlyDominates1 mais pour le joueur 2 2. Prouver strictlyDominates2 prisonersDilemma Trahir Cooperer

Indice : La definition strictlyDominates1 quantifie sur a2 : Fin 2 et utilise g.payoff1. Pour le joueur 2, quantifier sur a1 : Fin 2 et utiliser g.payoff2.

-- Exercice 3 : Dominance stricte pour le joueur 2

-- TODO etudiant : définir strictlyDominates2 (symetrique de strictlyDominates1)
-- Indice : strictlyDominates1 utilise payoff1 et quantifie sur a2
-- Pour le joueur 2, utiliser payoff2 et quantifier sur a1
def strictlyDominates2 (g : Game2x2) (a a' : Fin 2) : Prop :=
  -- TODO etudiant : completer la definition
  True  -- placeholder

-- TODO etudiant : prouver que Trahir domine strictement Cooperer pour J2 dans le PD
-- Indice : suivre le pattern de trahir_dominates_cooperer
-- Pour chaque action de J1, verifier que payoff2(Trahir, a1) > payoff2(Cooperer, a1)
theorem trahir_dominates_cooperer_j2 : strictlyDominates2 prisonersDilemma Trahir Cooperer := by
  -- TODO etudiant : completer la preuve
  sorry

#check trahir_dominates_cooperer_j2  -- Exercice 3 a completer
-- Exercice 3 : Dominance stricte pour le joueur 2
-- TODO etudiant : definir strictlyDominates2 (symetrique de strictlyDominates1)
-- Indice : strictlyDominates1 utilise payoff1 et quantifie sur a2
-- Pour le joueur 2, utiliser payoff2 et quantifier sur a1
🟨 unused variable `g` Note: This linter can be disabled with `set_option linter.unusedVariables false`
🟨 unused variable `a` Note: This linter can be disabled with `set_option linter.unusedVariables false`
🟨 unused variable `a'` Note: This linter can be disabled with `set_option linter.unusedVariables false`
  -- TODO etudiant : completer la definition
  True  -- placeholder
-- TODO etudiant : prouver que Trahir domine strictement Cooperer pour J2 dans le PD
-- Indice : suivre le pattern de trahir_dominates_cooperer
-- Pour chaque action de J1, verifier que payoff2(Trahir, a1) > payoff2(Cooperer, a1)
🟨 declaration uses `sorry`
  -- TODO etudiant : completer la preuve
  sorry
trahir_dominates_cooperer_j2 : strictlyDominates2 prisonersDilemma Trahir Cooperer
--% env 17
--% prove 3
Raw input {"cmd": "-- Exercice 3 : Dominance stricte pour le joueur 2\n\n-- TODO etudiant : definir strictlyDominates2 (symetrique de strictlyDominates1)\n-- Indice : strictlyDominates1 utilise payoff1 et quantifie sur a2\n-- Pour le joueur 2, utiliser payoff2 et quantifier sur a1\ndef strictlyDominates2 (g : Game2x2) (a a' : Fin 2) : Prop :=\n -- TODO etudiant : completer la definition\n True -- placeholder\n\n-- TODO etudiant : prouver que Trahir domine strictement Cooperer pour J2 dans le PD\n-- Indice : suivre le pattern de trahir_dominates_cooperer\n-- Pour chaque action de J1, verifier que payoff2(Trahir, a1) > payoff2(Cooperer, a1)\ntheorem trahir_dominates_cooperer_j2 : strictlyDominates2 prisonersDilemma Trahir Cooperer := by\n -- TODO etudiant : completer la preuve\n sorry\n\n#check trahir_dominates_cooperer_j2 -- Exercice 3 a completer", "env": 16}
Raw output {"sorries": [{"proofState": 3, "pos": {"line": 15, "column": 2}, "goal": "⊢ strictlyDominates2 prisonersDilemma Trahir Cooperer", "endPos": {"line": 15, "column": 7}}], "messages": [{"severity": "warning", "pos": {"line": 6, "column": 24}, "endPos": {"line": 6, "column": 25}, "data": "unused variable `g`\n\nNote: This linter can be disabled with `set_option linter.unusedVariables false`"}, {"severity": "warning", "pos": {"line": 6, "column": 38}, "endPos": {"line": 6, "column": 39}, "data": "unused variable `a`\n\nNote: This linter can be disabled with `set_option linter.unusedVariables false`"}, {"severity": "warning", "pos": {"line": 6, "column": 40}, "endPos": {"line": 6, "column": 42}, "data": "unused variable `a'`\n\nNote: This linter can be disabled with `set_option linter.unusedVariables false`"}, {"severity": "warning", "pos": {"line": 13, "column": 8}, "endPos": {"line": 13, "column": 36}, "data": "declaration uses `sorry`"}, {"severity": "info", "pos": {"line": 17, "column": 0}, "endPos": {"line": 17, "column": 6}, "data": "trahir_dominates_cooperer_j2 : strictlyDominates2 prisonersDilemma Trahir Cooperer"}], "env": 17}

## 8. Solutions — Reference enseignant

Correction Exemple guide 1 : Chicken

def chickenGame : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 3  -- (Ceder, Ceder)
    | 0, 1 => 2  -- (Ceder, Foncer)
    | 1, 0 => 4  -- (Foncer, Ceder)
    | 1, 1 => 0  -- (Foncer, Foncer) - crash!
    | _, _ => 0
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 3
    | 0, 1 => 4
    | 1, 0 => 2
    | 1, 1 => 0
    | _, _ => 0
}

Équilibres de Nash en stratégies pures

Avec ces gains : - (Ceder, Ceder) = (3, 3) : J1 peut dévier vers Foncer (3 -> 4). PAS NE. - (Foncer, Foncer) = (0, 0) : J1 peut dévier vers Ceder (0 -> 3). PAS NE. - (Foncer, Ceder) = (4, 2) : personne ne peut améliorer (J1 : 4 >= 3 ; J2 : 2 >= 0). NE. - (Ceder, Foncer) = (2, 4) : symétrique. NE.

Donc 2 NE en stratégies pures : (Foncer, Ceder) et (Ceder, Foncer), prouvés par chicken_nash1 et chicken_nash2 dans la cellule suivante.

-- Solution Exemple guide 1 : Jeu de la Poule Mouillee (Chicken)

def chickenGame : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 3  -- (Ceder, Ceder)
    | 0, 1 => 2  -- (Ceder, Foncer)
    | 1, 0 => 4  -- (Foncer, Ceder)
    | 1, 1 => 0  -- (Foncer, Foncer) - crash!
    | _, _ => 0
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 3
    | 0, 1 => 4  -- J2 fonce, J1 cede
    | 1, 0 => 2  -- J1 fonce, J2 cede
    | 1, 1 => 0
    | _, _ => 0
}

-- Actions
def Ceder : Fin 2 := ⟨0, by omega⟩
def Foncer : Fin 2 := ⟨1, by omega⟩

-- Equilibre 1 : (Foncer, Ceder)
-- J1 joue Foncer, J2 joue Ceder : gains (4, 2)
-- J1 ne peut pas ameliorer : 4 >= 3 (si Ceder)
-- J2 ne peut pas ameliorer : 2 >= 0 (si Foncer)
theorem chicken_nash1 : isPureNashEquilibrium chickenGame Foncer Ceder := by
  constructor
  · intro a1
    simp only [chickenGame, Foncer, Ceder]
    cases Decidable.em (a1.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a1.val = 1 := by omega
      simp only [this]
      omega
  · intro a2
    simp only [chickenGame, Foncer, Ceder]
    cases Decidable.em (a2.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a2.val = 1 := by omega
      simp only [this]
      omega

-- Equilibre 2 : (Ceder, Foncer)
theorem chicken_nash2 : isPureNashEquilibrium chickenGame Ceder Foncer := by
  constructor
  · intro a1
    simp only [chickenGame, Foncer, Ceder]
    cases Decidable.em (a1.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a1.val = 1 := by omega
      simp only [this]
      omega
  · intro a2
    simp only [chickenGame, Foncer, Ceder]
    cases Decidable.em (a2.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a2.val = 1 := by omega
      simp only [this]
      omega

#check chicken_nash1
#check chicken_nash2
-- Solution Exemple guide 1 : Jeu de la Poule Mouillee (Chicken)
def chickenGame : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 3  -- (Ceder, Ceder)
    | 0, 1 => 2  -- (Ceder, Foncer)
    | 1, 0 => 4  -- (Foncer, Ceder)
    | 1, 1 => 0  -- (Foncer, Foncer) - crash!
    | _, _ => 0
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 3
    | 0, 1 => 4  -- J2 fonce, J1 cede
    | 1, 0 => 2  -- J1 fonce, J2 cede
    | 1, 1 => 0
    | _, _ => 0
}
-- Actions
def Ceder : Fin 2 := ⟨0, by omega⟩
def Foncer : Fin 2 := ⟨1, by omega⟩
-- Equilibre 1 : (Foncer, Ceder)
-- J1 joue Foncer, J2 joue Ceder : gains (4, 2)
-- J1 ne peut pas ameliorer : 4 >= 3 (si Ceder)
-- J2 ne peut pas ameliorer : 2 >= 0 (si Foncer)
theorem chicken_nash1 : isPureNashEquilibrium chickenGame Foncer Ceder := by
  constructor
  · intro a1
    simp only [chickenGame, Foncer, Ceder]
    cases Decidable.em (a1.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a1.val = 1 := by omega
      simp only [this]
      omega
  · intro a2
    simp only [chickenGame, Foncer, Ceder]
    cases Decidable.em (a2.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a2.val = 1 := by omega
      simp only [this]
      omega
-- Equilibre 2 : (Ceder, Foncer)
theorem chicken_nash2 : isPureNashEquilibrium chickenGame Ceder Foncer := by
  constructor
  · intro a1
    simp only [chickenGame, Foncer, Ceder]
    cases Decidable.em (a1.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a1.val = 1 := by omega
      simp only [this]
      omega
  · intro a2
    simp only [chickenGame, Foncer, Ceder]
    cases Decidable.em (a2.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a2.val = 1 := by omega
      simp only [this]
      omega
chicken_nash1 : isPureNashEquilibrium chickenGame Foncer Ceder
chicken_nash2 : isPureNashEquilibrium chickenGame Ceder Foncer
--% env 18
Raw input {"cmd": "-- Solution Exemple guide 1 : Jeu de la Poule Mouillee (Chicken)\n\ndef chickenGame : Game2x2 := {\n payoff1 := fun i j =>\n match i.val, j.val with\n | 0, 0 => 3 -- (Ceder, Ceder)\n | 0, 1 => 2 -- (Ceder, Foncer)\n | 1, 0 => 4 -- (Foncer, Ceder)\n | 1, 1 => 0 -- (Foncer, Foncer) - crash!\n | _, _ => 0\n payoff2 := fun i j =>\n match i.val, j.val with\n | 0, 0 => 3\n | 0, 1 => 4 -- J2 fonce, J1 cede\n | 1, 0 => 2 -- J1 fonce, J2 cede\n | 1, 1 => 0\n | _, _ => 0\n}\n\n-- Actions\ndef Ceder : Fin 2 := \u27e80, by omega\u27e9\ndef Foncer : Fin 2 := \u27e81, by omega\u27e9\n\n-- Equilibre 1 : (Foncer, Ceder)\n-- J1 joue Foncer, J2 joue Ceder : gains (4, 2)\n-- J1 ne peut pas ameliorer : 4 >= 3 (si Ceder)\n-- J2 ne peut pas ameliorer : 2 >= 0 (si Foncer)\ntheorem chicken_nash1 : isPureNashEquilibrium chickenGame Foncer Ceder := by\n constructor\n \u00b7 intro a1\n simp only [chickenGame, Foncer, Ceder]\n cases Decidable.em (a1.val = 0) with\n | inl h =>\n simp only [h]\n omega\n | inr h =>\n have : a1.val = 1 := by omega\n simp only [this]\n omega\n \u00b7 intro a2\n simp only [chickenGame, Foncer, Ceder]\n cases Decidable.em (a2.val = 0) with\n | inl h =>\n simp only [h]\n omega\n | inr h =>\n have : a2.val = 1 := by omega\n simp only [this]\n omega\n\n-- Equilibre 2 : (Ceder, Foncer)\ntheorem chicken_nash2 : isPureNashEquilibrium chickenGame Ceder Foncer := by\n constructor\n \u00b7 intro a1\n simp only [chickenGame, Foncer, Ceder]\n cases Decidable.em (a1.val = 0) with\n | inl h =>\n simp only [h]\n omega\n | inr h =>\n have : a1.val = 1 := by omega\n simp only [this]\n omega\n \u00b7 intro a2\n simp only [chickenGame, Foncer, Ceder]\n cases Decidable.em (a2.val = 0) with\n | inl h =>\n simp only [h]\n omega\n | inr h =>\n have : a2.val = 1 := by omega\n simp only [this]\n omega\n\n#check chicken_nash1\n#check chicken_nash2", "env": 17}
Raw output {"messages": [{"severity": "info", "pos": {"line": 75, "column": 0}, "endPos": {"line": 75, "column": 6}, "data": "chicken_nash1 : isPureNashEquilibrium chickenGame Foncer Ceder"}, {"severity": "info", "pos": {"line": 76, "column": 0}, "endPos": {"line": 76, "column": 6}, "data": "chicken_nash2 : isPureNashEquilibrium chickenGame Ceder Foncer"}], "env": 18}

Correction Exemple guide 2 : Matching Pennies

def matchingPennies : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 1   -- (Pile, Pile) - J1 gagne
    | 0, 1 => -1  -- (Pile, Face)
    | 1, 0 => -1  -- (Face, Pile)
    | 1, 1 => 1   -- (Face, Face) - J1 gagne
    | _, _ => 0
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => -1
    | 0, 1 => 1
    | 1, 0 => 1
    | 1, 1 => -1
    | _, _ => 0
}

Pas de NE en stratégies pures

Vérification (chaque cas est prouvé par un matching_pennies_no_pure_nash_* de la cellule) : - (Pile, Pile) : J2 dévie vers Face, payoff2 passe de -1 à 1. PAS NE. - (Pile, Face) : J1 dévie vers Face, payoff1 passe de -1 à 1. PAS NE. - (Face, Pile) : J1 dévie vers Pile, payoff1 passe de -1 à 1. PAS NE. - (Face, Face) : J2 dévie vers Pile, payoff2 passe de -1 à 1. PAS NE.

NE en stratégies mixtes

L’unique équilibre de Nash est (0.5, 0.5) pour chaque joueur : chaque joueur joue Pile avec probabilité 0.5 et Face avec probabilité 0.5. Le gain espéré est alors 0 pour les deux.

-- Solution Exemple guide 2 : Matching Pennies

def matchingPennies : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 1   -- (Pile, Pile) - J1 gagne
    | 0, 1 => -1  -- (Pile, Face)
    | 1, 0 => -1  -- (Face, Pile)
    | 1, 1 => 1   -- (Face, Face) - J1 gagne
    | _, _ => 0
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => -1  -- J2 veut des résultats différents
    | 0, 1 => 1
    | 1, 0 => 1
    | 1, 1 => -1
    | _, _ => 0
}

def Pile : Fin 2 := ⟨0, by omega⟩
def Face : Fin 2 := ⟨1, by omega⟩

-- Aucune des 4 paires n'est un equilibre
-- (Pile, Pile) : J2 prefere devier vers Face (-1 -> 1)
theorem matching_pennies_no_pure_nash_00 : 
    ¬ isPureNashEquilibrium matchingPennies Pile Pile := by
  intro h
  have := h.2 Face  -- J2 prefere Face quand J1 joue Pile: payoff2(Pile, Face) = 1 > -1 = payoff2(Pile, Pile)
  simp only [Pile, Face, matchingPennies] at this
  omega

-- (Pile, Face) : J1 prefere devier vers Face (car Face, Face gagne pour J1)
theorem matching_pennies_no_pure_nash_01 :
    ¬ isPureNashEquilibrium matchingPennies Pile Face := by
  intro h
  have := h.1 Face  -- J1 prefere Face quand J2 joue Face
  simp only [Pile, Face, matchingPennies] at this
  omega

-- (Face, Pile) : J1 prefere devier vers Pile
theorem matching_pennies_no_pure_nash_10 :
    ¬ isPureNashEquilibrium matchingPennies Face Pile := by
  intro h
  have := h.1 Pile  -- J1 prefere Pile quand J2 joue Pile
  simp only [Pile, Face, matchingPennies] at this
  omega

-- (Face, Face) : J2 prefere devier vers Pile
theorem matching_pennies_no_pure_nash_11 :
    ¬ isPureNashEquilibrium matchingPennies Face Face := by
  intro h
  have := h.2 Pile  -- J2 prefere Pile quand J1 joue Face
  simp only [Pile, Face, matchingPennies] at this
  omega

#check matching_pennies_no_pure_nash_00
#check matching_pennies_no_pure_nash_01
#check matching_pennies_no_pure_nash_10
#check matching_pennies_no_pure_nash_11
-- Solution Exemple guide 2 : Matching Pennies
def matchingPennies : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 1   -- (Pile, Pile) - J1 gagne
    | 0, 1 => -1  -- (Pile, Face)
    | 1, 0 => -1  -- (Face, Pile)
    | 1, 1 => 1   -- (Face, Face) - J1 gagne
    | _, _ => 0
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => -1  -- J2 veut des resultats differents
    | 0, 1 => 1
    | 1, 0 => 1
    | 1, 1 => -1
    | _, _ => 0
}
def Pile : Fin 2 := ⟨0, by omega⟩
def Face : Fin 2 := ⟨1, by omega⟩
-- Aucune des 4 paires n'est un equilibre
-- (Pile, Pile) : J2 prefere devier vers Face (-1 -> 1)
theorem matching_pennies_no_pure_nash_00 : 
    ¬ isPureNashEquilibrium matchingPennies Pile Pile := by
  intro h
  have := h.2 Face  -- J2 prefere Face quand J1 joue Pile: payoff2(Pile, Face) = 1 > -1 = payoff2(Pile, Pile)
  simp only [Pile, Face, matchingPennies] at this
  omega
-- (Pile, Face) : J1 prefere devier vers Face (car Face, Face gagne pour J1)
theorem matching_pennies_no_pure_nash_01 :
    ¬ isPureNashEquilibrium matchingPennies Pile Face := by
  intro h
  have := h.1 Face  -- J1 prefere Face quand J2 joue Face
  simp only [Pile, Face, matchingPennies] at this
  omega
-- (Face, Pile) : J1 prefere devier vers Pile
theorem matching_pennies_no_pure_nash_10 :
    ¬ isPureNashEquilibrium matchingPennies Face Pile := by
  intro h
  have := h.1 Pile  -- J1 prefere Pile quand J2 joue Pile
  simp only [Pile, Face, matchingPennies] at this
  omega
-- (Face, Face) : J2 prefere devier vers Pile
theorem matching_pennies_no_pure_nash_11 :
    ¬ isPureNashEquilibrium matchingPennies Face Face := by
  intro h
  have := h.2 Pile  -- J2 prefere Pile quand J1 joue Face
  simp only [Pile, Face, matchingPennies] at this
  omega
matching_pennies_no_pure_nash_00 : ¬isPureNashEquilibrium matchingPennies Pile Pile
matching_pennies_no_pure_nash_01 : ¬isPureNashEquilibrium matchingPennies Pile Face
matching_pennies_no_pure_nash_10 : ¬isPureNashEquilibrium matchingPennies Face Pile
matching_pennies_no_pure_nash_11 : ¬isPureNashEquilibrium matchingPennies Face Face
--% env 19
Raw input {"cmd": "-- Solution Exemple guide 2 : Matching Pennies\n\ndef matchingPennies : Game2x2 := {\n payoff1 := fun i j =>\n match i.val, j.val with\n | 0, 0 => 1 -- (Pile, Pile) - J1 gagne\n | 0, 1 => -1 -- (Pile, Face)\n | 1, 0 => -1 -- (Face, Pile)\n | 1, 1 => 1 -- (Face, Face) - J1 gagne\n | _, _ => 0\n payoff2 := fun i j =>\n match i.val, j.val with\n | 0, 0 => -1 -- J2 veut des resultats differents\n | 0, 1 => 1\n | 1, 0 => 1\n | 1, 1 => -1\n | _, _ => 0\n}\n\ndef Pile : Fin 2 := \u27e80, by omega\u27e9\ndef Face : Fin 2 := \u27e81, by omega\u27e9\n\n-- Aucune des 4 paires n'est un equilibre\n-- (Pile, Pile) : J2 prefere devier vers Face (-1 -> 1)\ntheorem matching_pennies_no_pure_nash_00 : \n \u00ac isPureNashEquilibrium matchingPennies Pile Pile := by\n intro h\n have := h.2 Face -- J2 prefere Face quand J1 joue Pile: payoff2(Pile, Face) = 1 > -1 = payoff2(Pile, Pile)\n simp only [Pile, Face, matchingPennies] at this\n omega\n\n-- (Pile, Face) : J1 prefere devier vers Face (car Face, Face gagne pour J1)\ntheorem matching_pennies_no_pure_nash_01 :\n \u00ac isPureNashEquilibrium matchingPennies Pile Face := by\n intro h\n have := h.1 Face -- J1 prefere Face quand J2 joue Face\n simp only [Pile, Face, matchingPennies] at this\n omega\n\n-- (Face, Pile) : J1 prefere devier vers Pile\ntheorem matching_pennies_no_pure_nash_10 :\n \u00ac isPureNashEquilibrium matchingPennies Face Pile := by\n intro h\n have := h.1 Pile -- J1 prefere Pile quand J2 joue Pile\n simp only [Pile, Face, matchingPennies] at this\n omega\n\n-- (Face, Face) : J2 prefere devier vers Pile\ntheorem matching_pennies_no_pure_nash_11 :\n \u00ac isPureNashEquilibrium matchingPennies Face Face := by\n intro h\n have := h.2 Pile -- J2 prefere Pile quand J1 joue Face\n simp only [Pile, Face, matchingPennies] at this\n omega\n\n#check matching_pennies_no_pure_nash_00\n#check matching_pennies_no_pure_nash_01\n#check matching_pennies_no_pure_nash_10\n#check matching_pennies_no_pure_nash_11", "env": 18}
Raw output {"messages": [{"severity": "info", "pos": {"line": 56, "column": 0}, "endPos": {"line": 56, "column": 6}, "data": "matching_pennies_no_pure_nash_00 : ¬isPureNashEquilibrium matchingPennies Pile Pile"}, {"severity": "info", "pos": {"line": 57, "column": 0}, "endPos": {"line": 57, "column": 6}, "data": "matching_pennies_no_pure_nash_01 : ¬isPureNashEquilibrium matchingPennies Pile Face"}, {"severity": "info", "pos": {"line": 58, "column": 0}, "endPos": {"line": 58, "column": 6}, "data": "matching_pennies_no_pure_nash_10 : ¬isPureNashEquilibrium matchingPennies Face Pile"}, {"severity": "info", "pos": {"line": 59, "column": 0}, "endPos": {"line": 59, "column": 6}, "data": "matching_pennies_no_pure_nash_11 : ¬isPureNashEquilibrium matchingPennies Face Face"}], "env": 19}

Correction Exemple guide 3 : Stag Hunt

def stagHunt : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 4  -- (Cerf, Cerf) - succes!
    | 0, 1 => 0  -- (Cerf, Lievre) - J1 echoue seul
    | 1, 0 => 3  -- (Lievre, Cerf)
    | 1, 1 => 2  -- (Lievre, Lievre)
    | _, _ => 0
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 4
    | 0, 1 => 3
    | 1, 0 => 0
    | 1, 1 => 2
    | _, _ => 0
}

2 NE en stratégies pures

  • (Cerf, Cerf) = (4, 4) : J1 ne peut pas améliorer (dévier vers Lievre : 4 -> 3). NE Pareto-optimal, prouvé par stag_hunt_nash_cerf.
  • (Lievre, Lievre) = (2, 2) : J1 ne peut pas améliorer (dévier vers Cerf : 2 -> 0). NE risk-dominant, prouvé par stag_hunt_nash_lievre.

Sélection par risque

Les 2 NE diffèrent par le risque : - (Cerf, Cerf) : gain élevé (4, 4) mais si l’autre chasse le Lievre, gain 0 - (Lievre, Lievre) : gain modéré (2, 2) garanti sans coordination

C’est un exemple classique de coordination avec risque. Skyrms (2003) a montré comment la sélection évolutionnaire peut converger vers (Lievre, Lievre) sous certaines conditions (jeu symétrique, dynamique replicator).

-- Solution Exemple guide 3 : Chasse au Cerf (Stag Hunt)

def stagHunt : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 4  -- (Cerf, Cerf) - succes!
    | 0, 1 => 0  -- (Cerf, Lievre) - J1 echoue seul
    | 1, 0 => 3  -- (Lievre, Cerf)
    | 1, 1 => 2  -- (Lievre, Lievre)
    | _, _ => 0
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 4
    | 0, 1 => 3
    | 1, 0 => 0
    | 1, 1 => 2
    | _, _ => 0
}

def Cerf : Fin 2 := ⟨0, by omega⟩
def Lievre : Fin 2 := ⟨1, by omega⟩

-- Equilibre Pareto-optimal : (Cerf, Cerf) - gains (4, 4)
theorem stag_hunt_nash_cerf : isPureNashEquilibrium stagHunt Cerf Cerf := by
  constructor
  · intro a1
    simp only [stagHunt, Cerf]
    cases Decidable.em (a1.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a1.val = 1 := by omega
      simp only [this]
      omega
  · intro a2
    simp only [stagHunt, Cerf]
    cases Decidable.em (a2.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a2.val = 1 := by omega
      simp only [this]
      omega

-- Equilibre risk-dominant : (Lievre, Lievre) - gains (2, 2)
theorem stag_hunt_nash_lievre : isPureNashEquilibrium stagHunt Lievre Lievre := by
  constructor
  · intro a1
    simp only [stagHunt, Lievre]
    cases Decidable.em (a1.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a1.val = 1 := by omega
      simp only [this]
      omega
  · intro a2
    simp only [stagHunt, Lievre]
    cases Decidable.em (a2.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a2.val = 1 := by omega
      simp only [this]
      omega

#check stag_hunt_nash_cerf
#check stag_hunt_nash_lievre
-- Solution Exemple guide 3 : Chasse au Cerf (Stag Hunt)
def stagHunt : Game2x2 := {
  payoff1 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 4  -- (Cerf, Cerf) - succes!
    | 0, 1 => 0  -- (Cerf, Lievre) - J1 echoue seul
    | 1, 0 => 3  -- (Lievre, Cerf)
    | 1, 1 => 2  -- (Lievre, Lievre)
    | _, _ => 0
  payoff2 := fun i j =>
    match i.val, j.val with
    | 0, 0 => 4
    | 0, 1 => 3
    | 1, 0 => 0
    | 1, 1 => 2
    | _, _ => 0
}
def Cerf : Fin 2 := ⟨0, by omega⟩
def Lievre : Fin 2 := ⟨1, by omega⟩
-- Equilibre Pareto-optimal : (Cerf, Cerf) - gains (4, 4)
theorem stag_hunt_nash_cerf : isPureNashEquilibrium stagHunt Cerf Cerf := by
  constructor
  · intro a1
    simp only [stagHunt, Cerf]
    cases Decidable.em (a1.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a1.val = 1 := by omega
      simp only [this]
      omega
  · intro a2
    simp only [stagHunt, Cerf]
    cases Decidable.em (a2.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a2.val = 1 := by omega
      simp only [this]
      omega
-- Equilibre risk-dominant : (Lievre, Lievre) - gains (2, 2)
theorem stag_hunt_nash_lievre : isPureNashEquilibrium stagHunt Lievre Lievre := by
  constructor
  · intro a1
    simp only [stagHunt, Lievre]
    cases Decidable.em (a1.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a1.val = 1 := by omega
      simp only [this]
      omega
  · intro a2
    simp only [stagHunt, Lievre]
    cases Decidable.em (a2.val = 0) with
    | inl h =>
      simp only [h]
      omega
    | inr h =>
      have : a2.val = 1 := by omega
      simp only [this]
      omega
stag_hunt_nash_cerf : isPureNashEquilibrium stagHunt Cerf Cerf
stag_hunt_nash_lievre : isPureNashEquilibrium stagHunt Lievre Lievre
--% env 20
Raw input {"cmd": "-- Solution Exemple guide 3 : Chasse au Cerf (Stag Hunt)\n\ndef stagHunt : Game2x2 := {\n payoff1 := fun i j =>\n match i.val, j.val with\n | 0, 0 => 4 -- (Cerf, Cerf) - succes!\n | 0, 1 => 0 -- (Cerf, Lievre) - J1 echoue seul\n | 1, 0 => 3 -- (Lievre, Cerf)\n | 1, 1 => 2 -- (Lievre, Lievre)\n | _, _ => 0\n payoff2 := fun i j =>\n match i.val, j.val with\n | 0, 0 => 4\n | 0, 1 => 3\n | 1, 0 => 0\n | 1, 1 => 2\n | _, _ => 0\n}\n\ndef Cerf : Fin 2 := \u27e80, by omega\u27e9\ndef Lievre : Fin 2 := \u27e81, by omega\u27e9\n\n-- Equilibre Pareto-optimal : (Cerf, Cerf) - gains (4, 4)\ntheorem stag_hunt_nash_cerf : isPureNashEquilibrium stagHunt Cerf Cerf := by\n constructor\n \u00b7 intro a1\n simp only [stagHunt, Cerf]\n cases Decidable.em (a1.val = 0) with\n | inl h =>\n simp only [h]\n omega\n | inr h =>\n have : a1.val = 1 := by omega\n simp only [this]\n omega\n \u00b7 intro a2\n simp only [stagHunt, Cerf]\n cases Decidable.em (a2.val = 0) with\n | inl h =>\n simp only [h]\n omega\n | inr h =>\n have : a2.val = 1 := by omega\n simp only [this]\n omega\n\n-- Equilibre risk-dominant : (Lievre, Lievre) - gains (2, 2)\ntheorem stag_hunt_nash_lievre : isPureNashEquilibrium stagHunt Lievre Lievre := by\n constructor\n \u00b7 intro a1\n simp only [stagHunt, Lievre]\n cases Decidable.em (a1.val = 0) with\n | inl h =>\n simp only [h]\n omega\n | inr h =>\n have : a1.val = 1 := by omega\n simp only [this]\n omega\n \u00b7 intro a2\n simp only [stagHunt, Lievre]\n cases Decidable.em (a2.val = 0) with\n | inl h =>\n simp only [h]\n omega\n | inr h =>\n have : a2.val = 1 := by omega\n simp only [this]\n omega\n\n#check stag_hunt_nash_cerf\n#check stag_hunt_nash_lievre", "env": 19}
Raw output {"messages": [{"severity": "info", "pos": {"line": 71, "column": 0}, "endPos": {"line": 71, "column": 6}, "data": "stag_hunt_nash_cerf : isPureNashEquilibrium stagHunt Cerf Cerf"}, {"severity": "info", "pos": {"line": 72, "column": 0}, "endPos": {"line": 72, "column": 6}, "data": "stag_hunt_nash_lievre : isPureNashEquilibrium stagHunt Lievre Lievre"}], "env": 20}

## 9. Resume

Concepts formalises

Concept Definition Lean Description
Jeu en forme normale NormalFormGame, FiniteGame, Game2x2 Joueurs, actions, gains
Stratégie pure Fin n Une action déterministe
Stratégie mixte Simplex n Distribution sur les actions
Gain espere expectedPayoff1, expectedPayoff2 Esperance sous stratégies mixtes
Equilibre de Nash isNashEquilibrium, isPureNashEquilibrium Aucune deviation profitable
Dominance stricte strictlyDominates1 Une action toujours meilleure

Theoremes prouves

Theoreme Enonce
prisoners_dilemma_nash (T, T) est equilibre du PD
cooperer_not_nash (C, C) n’est pas equilibre du PD
trahir_dominates_cooperer Trahir domine strictement Cooperer
chicken_nash1, chicken_nash2 Chicken a 2 equilibres purs
matching_pennies_no_pure_nash_* MP n’a pas d’equilibre pur
stag_hunt_nash_cerf, stag_hunt_nash_lievre Stag Hunt a 2 equilibres purs

Prochaines étapes

Dans le notebook suivant (GameTheory-04b-Lean-NashExistence-Lean), nous etudierons : - Le theoreme de Brouwer (point fixe) - L’existence d’un equilibre de Nash dans tout jeu fini - Lecture guidee du repo math-xmum/Brouwer


Navigation : <- GameTheory-02-NormalForm-Python (palier parent) | Index | GameTheory-04b-Lean-NashExistence-Lean ->

Lien avec lean_game_defs : Les definitions de ce notebook sont le cœur du module lean_game_defs/Basic.lean (0 sorry), qui fournit les structures NormalFormGame (joueurs, actions, fonctions de gain), FiniteGame (jeu fini avec indices Fin n), et Game2x2 (jeu 2x2 simplifié avec gains concrets). Les gains espérés (section 3.4) y sont également formalisés. Le module associé lean_game_defs/Nash.lean (0 sorry) reprend les concepts de la section 4 : meilleure réponse (isBestResponse), équilibre de Nash pur (isPureNash), équilibre de Nash mixte (isMixedNash), et dominance stricte (isStrictlyDominated). Ces deux fichiers constituent le socle formel sur lequel s’appuient tous les autres modules lean_game_defs/ (Bayesian, Regret, Combinatorial, SocialChoice).

Retour au sommet