{"id":15728851,"url":"https://github.com/mixedmatched/formalizing-game-theory","last_synced_at":"2026-04-06T04:07:24.714Z","repository":{"id":212755236,"uuid":"732157876","full_name":"MixedMatched/formalizing-game-theory","owner":"MixedMatched","description":"Example formalization of Game Theoretic concepts in Lean","archived":false,"fork":false,"pushed_at":"2025-02-14T21:40:24.000Z","size":53,"stargazers_count":15,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-14T22:29:08.564Z","etag":null,"topics":["game-theory","lean4"],"latest_commit_sha":null,"homepage":"","language":"Lean","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MixedMatched.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-15T19:42:27.000Z","updated_at":"2025-02-14T21:40:28.000Z","dependencies_parsed_at":"2023-12-16T04:58:11.659Z","dependency_job_id":"79fe0a5e-800f-45bd-b7a9-4780fa1bc439","html_url":"https://github.com/MixedMatched/formalizing-game-theory","commit_stats":{"total_commits":9,"total_committers":1,"mean_commits":9.0,"dds":0.0,"last_synced_commit":"b7cac61b1c04850a5f6f9dab50dbb81782f84f5b"},"previous_names":["mixedmatched/formalizing-game-theory"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MixedMatched%2Fformalizing-game-theory","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MixedMatched%2Fformalizing-game-theory/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MixedMatched%2Fformalizing-game-theory/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MixedMatched%2Fformalizing-game-theory/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MixedMatched","download_url":"https://codeload.github.com/MixedMatched/formalizing-game-theory/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246403916,"owners_count":20771530,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["game-theory","lean4"],"created_at":"2024-10-03T23:06:57.201Z","updated_at":"2025-10-08T10:52:02.067Z","avatar_url":"https://github.com/MixedMatched.png","language":"Lean","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Formalizing Game Theory in Lean\n\nFormalizing Game Theory in the Lean proof language holds significant promise for advancing both the theoretical foundations and practical applications of game-theoretic models. Lean, known for its precision and rigor in formal verification, offers a robust framework for expressing and proving mathematical theorems. By formalizing Game Theory in Lean, researchers and practitioners can establish a rigorous basis for analyzing strategic interactions, ensuring the correctness of game-theoretic concepts, and facilitating automated verification of complex game structures. \n\nThis repository contains an example of formal structures and properties to represent and reason about games in Lean. While there have been other attempts to make such a formalization, the representation proposed here is novel in that it can represent a much broader class of games than previous attempts. In particular, this formalization is able to represent games with more than 2 players, games with a continuous set of actions, and games with intricate utility functions.\n\nThe relevant code for this repository can be found [here](https://github.com/MixedMatched/formalizing-game-theory/blob/master/FormalizingGameTheory/Basic.lean).\n\n## Formalization\n\nOur formalization starts with a definition of Utility as equivalent to Mathlib.Data.Rat. While many different types could be used to represent preference order, we chose to restrict the formalization to rational numbers because they still have the properties of total order and continuity, while also being easy to work with in Lean (comparisons between the rationals are decidable, while comparisons between the reals are not).\n\nWe then represent a Pure Strategy as:\n\n```lean\nstructure PureStrategy (A : Type) := (val : A)\n```\n\nwhich defines a Pure Strategy as an instance of some type `A`. This means that a player's available strategies are defined with a type `A`, and an individual strategy is defined as an instance of that type. For example, in the game of Stag Hunt, the avalable strategies could be formalized as the type:\n\n```lean\ninductive StagHuntStrategies\n| stag\n| hare\n```\n\nand a strategy for player 1 could be formalized as the instance `PureStrategy StagHuntStrategies.stag`. This representation allows for a wide variety of games to be formalized, including games with a continuous set of actions. For example, the Nash Demand Game could be formalized with the type:\n\n```lean\nstructure NashDemandStrategies :=\n    (demand : Rat)\n    (above_0: demand \u003e 0)\n    (below_1: demand \u003c 1)\n```\n\nand a strategy for player 1 could be formalized as the instance `PureStrategy (NashDemandStrategies 0.5 ... ...)`.\n\nWe then represent a Mixed Strategy as:\n\n```lean\nstructure MixedStrategy (A : Type) :=\n  (strategies: List (PureStrategy A))\n  (probabilities: List Rat)\n  (probabilities_sum_to_one: List.foldl (a + b) 0 probabilities = 1)\n  (probabilities_non_negative: List.all probabilities (λ p =\u003e p \u003e 0))\n  (same_length: List.length strategies = List.length probabilities)\n```\n\nwhich defines a Mixed Strategy as a list of Pure Strategies and a list of probabilities, as well as constraints on those lists.\n\nThen, a Strategy is defined as either a Pure Strategy or a Mixed Strategy:\n\n```lean\ninductive Strategy (A : Type) where\n| pure : PureStrategy A → Strategy A\n| mixed : MixedStrategy A → Strategy A\n```\n\nA Strategy Profile, which is a list of strategies, is then defined as:\n\n```lean\nstructure StrategyProfile (L: List Type) where\n  (strategies: (i: Fin (List.length L)) → Strategy (List.get L i))\n```\n\nThe reason that this must be a function from a natural number to a strategy is that the strategies for each player must be of different types. For example, in the Ultimatum Game, the first player's strategy is a real number between 0 and 1, while the second player's strategy is a essentially a reject or accept answer.\n\nA Utility Profile, which is a list of utilities, is then defined as:\n\n```lean\nstructure UtilityProfile (L: List Type) where\n  (utilities: List Rat)\n  (same_length: List.length L = List.length utilities)\n```\n\nAnd the Utility Function is defined as:\n\n```lean\ninductive UtilityFunction (L: List Type) where\n  | mk (x: PureStrategyProfile L → UtilityProfile L) : UtilityFunction L\n```\n\nwhich essentially maps a pure strategy profile to a utility profile. To map a full strategy profile (i.e. one including mixed strategies) to a utility profile, you use UtilityProfile.apply, which automatically converts a pure function into a mixed one:\n\n```lean\n@[aesop norm unfold]\ndef UtilityFunction.apply : UtilityFunction L → L.length \u003e 0 → PureStrategyProfile L → StrategyProfile L → UtilityProfile L\n  | mk x =\u003e λ l psp sp =\u003e eval_sp sp x psp ⟨0, l⟩\n```\n\nOne thing to note is that this process of automatic conversion, though not very computationally taxing, is quite taxing for proof writing, as it makes statements become quite large.\n\nFinally, a Game is defined as:\n\n```lean\nstructure Game (L: List Type) (N: Nat) where\n  (utility: UtilityFunction L)\n  (same_length: (List.length L) = N)\n  (at_least_one_player: N \u003e 0)\n  (pure_strategy_profile: PureStrategyProfile L)\n```\n\nand an instance of a Game is defined as:\n\n```lean\ndef PlayGame (L: List Type) (N: Nat) (G: Game L N) (S: StrategyProfile L) : UtilityProfile L :=\n  G.utility.apply\n    (by simp_all only [gt_iff_lt]\n        obtain ⟨_, same_length, at_least_one_player, _⟩ := G\n        subst same_length\n        simp_all only [gt_iff_lt])\n    (by exact G.pure_strategy_profile)\n    S\n```\n\nwhich applies the utility function of the game to a given strategy profile to get a utility profile.\n\nWe also define a property of a Strategy Profile being a Nash Equilibrium as:\n\n```lean\ndef NashEquilibrium (L: List Type) (N: Nat) (G: Game L N) (S: StrategyProfile L) : Prop :=\n  ∀ (S': StrategyProfile L)\n    (delta: Fin (List.length L)),\n    UnilateralChange L S S' delta → DoesAtLeastAsWellAs L N G S S' delta\n```\n\nwhich essentially states that, for each player, and for each strategy that the player could switch to, the utility of that player in the new strategy is less than or equal to their utility in the original strategy profile.\n\n## Example: Prisoner's Dilemma\n\nThe Prisoner's Dilemma is a classic game in Game Theory. It is a two-player game where each player can either cooperate or defect. An example payoff matrix which fits the definition of Prisoner's Dilemma is:\n\n| Player 1 / Player 2 | Cooperate | Defect |\n| ------------------- | --------- | ------ |\n| Cooperate           | 3, 3      | 1, 4   |\n| Defect              | 4, 1      | 2, 2   |\n\nWe created an example formalization of the Prisoner's Dilemma in Lean as follows:\n\n```lean\ninductive PrisonersDilemmaStrategies where\n| silent\n| confess\n\ndef PL : List Type := [PrisonersDilemmaStrategies, PrisonersDilemmaStrategies]\n\ndef PL_length : List.length PL = 2 := rfl\n\ndef PrisonersDilemmaUtilityFunction : UtilityFunction PL :=\n  ⟨λ S =\u003e match (S.strategies (Fin.ofNat 0)).val, (S.strategies (Fin.ofNat 1)).val with\n          | PrisonersDilemmaStrategies.silent,  PrisonersDilemmaStrategies.silent  =\u003e { utilities := [3, 3], same_length := rfl }\n          | PrisonersDilemmaStrategies.silent,  PrisonersDilemmaStrategies.confess =\u003e { utilities := [1, 4], same_length := rfl }\n          | PrisonersDilemmaStrategies.confess, PrisonersDilemmaStrategies.silent  =\u003e { utilities := [4, 1], same_length := rfl }\n          | PrisonersDilemmaStrategies.confess, PrisonersDilemmaStrategies.confess =\u003e { utilities := [2, 2], same_length := rfl }\n  ⟩\n\ndef PrisonersDilemmaPureProfile : PureStrategyProfile PL :=\n  { strategies := λ i =\u003e match i with\n                          | ⟨0, _⟩ =\u003e ⟨PrisonersDilemmaStrategies.silent⟩\n                          | ⟨1, _⟩ =\u003e ⟨PrisonersDilemmaStrategies.silent⟩\n  }\n\ndef PrisonersDilemmaSilentSilentProfile : StrategyProfile PL :=\n  { strategies := λ i =\u003e match i with\n                          | ⟨0, _⟩ =\u003e Strategy.pure ⟨PrisonersDilemmaStrategies.silent⟩\n                          | ⟨1, _⟩ =\u003e Strategy.pure ⟨PrisonersDilemmaStrategies.silent⟩\n  }\n\ndef PrisonersDilemmaSilentConfessProfile : StrategyProfile PL :=\n  { strategies := λ i =\u003e match i with\n                          | ⟨0, _⟩ =\u003e Strategy.pure ⟨PrisonersDilemmaStrategies.silent⟩\n                          | ⟨1, _⟩ =\u003e Strategy.pure ⟨PrisonersDilemmaStrategies.confess⟩\n  }\n\ndef PrisonersDilemmaConfessConfessProfile : StrategyProfile PL :=\n  { strategies := λ i =\u003e match i with\n                          | ⟨0, _⟩ =\u003e Strategy.pure ⟨PrisonersDilemmaStrategies.confess⟩\n                          | ⟨1, _⟩ =\u003e Strategy.pure ⟨PrisonersDilemmaStrategies.confess⟩\n  }\n\ndef PrisonersDilemmaGame : Game PL 2 :=\n{ utility := PrisonersDilemmaUtilityFunction,\n  same_length := rfl,\n  at_least_one_player := Nat.zero_lt_succ 1\n  pure_strategy_profile := by exact PrisonersDilemmaPureProfile\n}\n```\n\nThis formalization defines the type `PrisonersDilemmaStrategies` to be the type of strategies for the Prisoner's Dilemma, and defines the list `PL` to be the Strategy to Player mapping. It then defines the utility function for the Prisoner's Dilemma by matching on the strategies of the two players and returning the appropriate utility profile. Finally, it defines the Prisoner's Dilemma game as a game with the Prisoner's Dilemma utility function, 2 players, and a proof that there is at least one player.\n\nYou can then prove that (silent, silent) is not a nash equilibrium as follows:\n```lean\ntheorem PDSilentConfessIsUnilateralOfPDSilentSilent : UnilateralChange PL PrisonersDilemmaSilentConfessProfile PrisonersDilemmaSilentSilentProfile (Fin.mk 1 x)\n  := by unfold UnilateralChange\n        intro i\n        cases i\n        case mk val isLt =\u003e\n          cases val\n          case zero =\u003e left\n                       unfold PrisonersDilemmaSilentSilentProfile\n                       unfold PrisonersDilemmaSilentConfessProfile\n                       simp_all\n          case succ n =\u003e\n            cases n\n            case zero =\u003e right\n                         simp_all\n            case succ m =\u003e rw [PL_length] at isLt\n                           conv at isLt =\u003e lhs\n                                           change m + 2\n                                           rw [add_comm]\n                           simp_all only [add_zero, add_lt_iff_neg_left, not_lt_zero']\n\ntheorem NotNashEquilibriumSilentSilent : ¬ NashEquilibrium PL 2 PrisonersDilemmaGame PrisonersDilemmaSilentSilentProfile\n  := by apply not_nasheq_if_uc_better\n        case i =\u003e\n          rw [PL_length]\n          exact Fin.last 1\n        case a =\u003e\n          constructor\n          case left =\u003e exact PDSilentConfessIsUnilateralOfPDSilentSilent\n          case right =\u003e unfold PL PrisonersDilemmaGame PrisonersDilemmaSilentSilentProfile\n                          PrisonersDilemmaSilentConfessProfile PrisonersDilemmaPureProfile PrisonersDilemmaUtilityFunction\n                          DoesAtLeastAsWellAs PlayGame UtilityFunction.apply eval_sp\n                        simp_all [↓dreduceDIte]\n                        unfold eval_sp\n                        simp_all [↓reduceDIte]\n                        rfl\n```\n\nThe second theorem here is the important one, actually showing that (silent, silent) is not a nash equilibrium. The proof operates by applying a theorem stating that, if any unilateral change (a profile with only a single player changing) performs better than the given strategy, it's not a nash equilibrium. Then we just have to show that we have a unilateral change and that that unilateral change performs better than (silent, silent). For the purposes of this proof, I chose to show that (silent, confess) is this unilateral change. \n\nThe case a.left is where we show that (silent, confess) is a unilateral change of (silent, silent), and we delegate that to the first theorem, which essentially shows that fact by manual brute force. The case a.right is where we show (silent, confess) is better for the second player than (silent, silent), which is shown by unwrapping all of our definitions and reducing to the actual inequality that they represent: 3 \u003c 4.\n\n## Example: Rock, Paper, Scissors\n\nRock, Paper, Scissors is another classic game. It's a two-player game with 3 strategies for each player. An example payoff matrix which fits the definition of Rock, Paper, Scissors is:\n\n| Player 1 / Player 2 | Rock | Paper | Scissors |\n| ------------------- | ---- | ----- | -------- |\n| Rock                | 1, 1 | 0, 2  | 2, 0     |\n| Paper               | 2, 0 | 1, 1  | 0, 2     |\n| Scissors            | 0, 2 | 2, 0  | 1, 1     |\n\nWe created an example formalization of Rock, Paper, Scissors in Lean as follows:\n\n```lean\ninductive RockPaperScissorsStrategies where\n| rock\n| paper\n| scissors\n\ndef RPS : List Type := [RockPaperScissorsStrategies, RockPaperScissorsStrategies]\ndef RPS_length : List.length RPS = 2 := rfl\n\ndef RockPaperScissorsUtilityFunction : UtilityFunction RPS :=\n  { val := λ S =\u003e match S.strategies (Fin.ofNat 0), S.strategies (Fin.ofNat 1) with\n                  | Strategy.pure s1, Strategy.pure s2 =\u003e\n                    have h1 : PureStrategy (List.get RPS (Fin.ofNat 0)) = PureStrategy RockPaperScissorsStrategies := rfl\n                    have h2 : PureStrategy (List.get RPS (Fin.ofNat 1)) = PureStrategy RockPaperScissorsStrategies := rfl\n                    let s1' : PureStrategy RockPaperScissorsStrategies := by { rw [←h1]; exact s1 }\n                    let s2' : PureStrategy RockPaperScissorsStrategies := by { rw [←h2]; exact s2 }\n                    match s1'.val, s2'.val with\n                    | RockPaperScissorsStrategies.rock,     RockPaperScissorsStrategies.rock     =\u003e { utilities := [1, 1], same_length := rfl }\n                    | RockPaperScissorsStrategies.rock,     RockPaperScissorsStrategies.paper    =\u003e { utilities := [0, 2], same_length := rfl }\n                    | RockPaperScissorsStrategies.rock,     RockPaperScissorsStrategies.scissors =\u003e { utilities := [2, 0], same_length := rfl }\n                    | RockPaperScissorsStrategies.paper,    RockPaperScissorsStrategies.rock     =\u003e { utilities := [2, 0], same_length := rfl }\n                    | RockPaperScissorsStrategies.paper,    RockPaperScissorsStrategies.paper    =\u003e { utilities := [1, 1], same_length := rfl }\n                    | RockPaperScissorsStrategies.paper,    RockPaperScissorsStrategies.scissors =\u003e { utilities := [0, 2], same_length := rfl }\n                    | RockPaperScissorsStrategies.scissors, RockPaperScissorsStrategies.rock     =\u003e { utilities := [0, 2], same_length := rfl }\n                    | RockPaperScissorsStrategies.scissors, RockPaperScissorsStrategies.paper    =\u003e { utilities := [2, 0], same_length := rfl }\n                    | RockPaperScissorsStrategies.scissors, RockPaperScissorsStrategies.scissors =\u003e { utilities := [1, 1], same_length := rfl }\n                  | _, _ =\u003e { utilities := [0, 0], same_length := rfl }\n  }\n\ndef RockPaperScissorsGame : Game RPS 2 :=\n  { utility := RockPaperScissorsUtilityFunction,\n    same_length := rfl,\n    at_least_one_player := Nat.zero_lt_succ 1\n  }\n```\n\nThis formalization defines the type `RockPaperScissorsStrategies` to be the type of strategies for Rock, Paper, Scissors, and defines the list `RPS` to be the Strategy to Player mapping. It then defines the utility function for Rock, Paper, Scissors by matching on the strategies of the two players and returning the appropriate utility profile. Finally, it defines the Rock, Paper, Scissors game as a game with the Rock, Paper, Scissors utility function, 2 players, and a proof that there is at least one player.\n\n## Example: Nash Demand Game\n\nOur last example is the Nash Demand Game. It's a two-player game where each player can demand a real number between 0 and 1. If the sum of the demands is greater than 1, then both players get 0. Otherwise, the first player gets their demand and the second player gets theirs.\n\nWe created an example formalization of the Nash Demand Game in Lean as follows:\n\n```lean\nstructure NashDemandChoice where\n  (demand: Real)\n  (demand_nonnegative: demand ≥ 0)\n  (demand_le_one: demand ≤ 1)\n\ndef NashDemandChoiceList : List Type := [NashDemandChoice, NashDemandChoice]\n\nnoncomputable def NashDemandUtilityFunction : UtilityFunction NashDemandChoiceList :=\n  { val := λ S =\u003e match S.strategies (Fin.ofNat 0), S.strategies (Fin.ofNat 1) with\n                  | Strategy.pure s1, Strategy.pure s2 =\u003e\n                    have h1 : PureStrategy (List.get NashDemandChoiceList (Fin.ofNat 0)) = PureStrategy NashDemandChoice := rfl\n                    have h2 : PureStrategy (List.get NashDemandChoiceList (Fin.ofNat 1)) = PureStrategy NashDemandChoice := rfl\n                    let s1' : PureStrategy NashDemandChoice := by { rw [←h1]; exact s1 }\n                    let s2' : PureStrategy NashDemandChoice := by { rw [←h2]; exact s2 }\n                    match s1', s2' with\n                    | ⟨d1, _, _⟩, ⟨d2, _, _⟩ =\u003e\n                      let d12 : Utility := ⟨d1 + d2⟩\n                      let oneUtility : Utility := ⟨1⟩\n                        if d12 ≤ oneUtility then { utilities := [⟨d1⟩, ⟨d2⟩], same_length := rfl }\n                      else { utilities := [0, 0], same_length := rfl }\n                  | _, _ =\u003e { utilities := [0, 0], same_length := rfl }\n  }\n\nnoncomputable def NashDemandGame : Game NashDemandChoiceList 2 :=\n  { utility := NashDemandUtilityFunction,\n    same_length := rfl,\n    at_least_one_player := Nat.zero_lt_succ 1\n  }\n```\n\nThis formalization defines the type `NashDemandChoice` to be the type of strategies for the Nash Demand Game, and defines the list `NashDemandChoiceList` to be the Strategy to Player mapping. It then defines the utility function for the Nash Demand Game by matching on the strategies of the two players and returning the appropriate utility profile. Because of some properties of Real numbers defined using Cauchy sequences, the definitions must be marked `noncomputable`, meaning they can be used in proofs, but not directly calculated. Finally, it defines the Nash Demand Game as a game with the Nash Demand utility function, 2 players, and a proof that there is at least one player.\n\n## Conclusion\n\nThis repository contains an example of formal structures and properties to represent and reason about games in Lean. While there have been other attempts to make such a formalization, the representation proposed here is novel in that it can represent a much broader class of games than previous attempts. In particular, this formalization is able to represent games with more than 2 players, games with a continuous set of actions, and games with intricate utility functions.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmixedmatched%2Fformalizing-game-theory","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmixedmatched%2Fformalizing-game-theory","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmixedmatched%2Fformalizing-game-theory/lists"}