1-- Pure strategy profiles: choosing one pure strategy per player gives a2-- point of the product of simplices, the anchor Nash's theorem needs3-- Part of Cumulus, the Grace17 mathematics library.4-- Copyright (c) 2026 Grant Passmore.5-- Released under the Apache License 2.0. See LICENSE in the repository root.6-- Authors: Grant Passmore78module Grace.Cumulus.GameTheory.PureProfile9import FixedPoint.BlockSimplex1011-- The sequence that is one at r and zero elsewhere.12defgraph pointMass (r : Set) (i ∈ ℕ) : ℝ := (realNat(indicator({r}, i)):R)13proof14have natural := indicator.natural at {r}, i15exact realNat.real at indicator({r}, i)1617theorem pointMass.sequence (r : Set)18shows RealSequence(pointMass(r))19proof20exact pointMass.function at r2122theorem realPartialSum.pointMass_outside (r : Set) (m : Nat)23shows¬r ∈ m → realPartialSum(pointMass(r), m) = (0:R)24proof25induction m26| zero =>27intro outside28exact realPartialSum.zero at pointMass(r)29| succ(j) using ih =>30intro outside31have jNat : Nat(j) bytypecheck32have jIn := nat.omega.forward with jNat33have notInJ : ¬r ∈ j34intro inJ35apply outside36apply bootstrap.succ.membership.backward37left38exact inJ39have notEqual : r ≠ j40intro equal41apply outside42rw equal43exact bootstrap.succ.self at j44have previous := ih with notInJ45have massValue := pointMass.value at r, j46have other := indicator.singleton_other at r, j with notEqual47have step := realPartialSum.succ at pointMass(r), j48have sequence := pointMass.sequence at r49have termReal := RealSequence.value at pointMass(r), j with sequence50have termZero : pointMass(r)(j) = (0:R)51rw [massValue, other]52exact realNat.zero53rw step54linarith5556theorem realPartialSum.pointMass_below (r : Set) (m : Nat)57shows r ∈ m → realPartialSum(pointMass(r), m) = (1:R)58proof59induction m60| zero =>61intro below62exfalso63rw nat.zero at below64 simpa using below65| succ(j) using ih =>66intro below67have jNat : Nat(j) bytypecheck68have jIn := nat.omega.forward with jNat69have massValue := pointMass.value at r, j70have step := realPartialSum.succ at pointMass(r), j71have sequence := pointMass.sequence at r72have sumReal := realPartialSum.real at pointMass(r), j with sequence73have termReal := RealSequence.value at pointMass(r), j with sequence74havesplit:= bootstrap.succ.membership.forward with below75casessplitwith inJ | same76 . have previous := ih with inJ77have notEqual : r ≠ j78intro equal79rw equal at inJ80have irreflexive := membership.irreflexive at j81exact irreflexive with inJ82have other := indicator.singleton_other at r, j with notEqual83have termZero : pointMass(r)(j) = (0:R)84rw [massValue, other]85exact realNat.zero86rw step87linarith88 . have notInJ : ¬r ∈ j89intro inJ90rw same at inJ91have irreflexive := membership.irreflexive at j92exact irreflexive with inJ93have outside := realPartialSum.pointMass_outside at r, j94have previous := outside with notInJ95have termOne : pointMass(r)(j) = (1:R)96rw [massValue, same]97haveself:= indicator.singleton_self at j98rwself99exact realNat.one100rw step101linarith102103-- The profile that plays the strategies in R with probability one.104defgraph pureProfile (n R : Set) (i ∈ n) : ℝ := (realNat(indicator(R, i)):R)105requires nNat : Nat(n)106proof107have natural := indicator.natural at R, i108exact realNat.real at indicator(R, i)109110theorem pureProfile.in_cube (n : Nat) (R : Set)111shows pureProfile(n, R) ∈ unitCube(n)112proof113have function := pureProfile.function at n, R with*114have zeroIn : (0:R) ∈ ℝ bytypecheck115have oneIn : (1:R) ∈ ℝ bytypecheck116apply unitCube.membership.backward117constructor118 . exact functionSpace.mem_iff.backward with function119 . intros i hi120have value := pureProfile.value at n, R, i with*121apply closedInterval.membership.backward122rw value123have alternatives : i ∈ R ∨¬i ∈ R bytauto124cases alternatives with inR | outside125 . have one := indicator.member with inR126rw [one, realNat.one]127have oneLe : (1:R) ≤ (1:R) bylinarith128have zeroLe : (0:R) ≤ (1:R) bylinarith129exact⟨oneIn, ⟨zeroLe, oneLe⟩⟩130 . have zero := indicator.absent with outside131rw [zero, realNat.zero]132have zeroLe : (0:R) ≤ (0:R) bylinarith133have zeroLeOne : (0:R) ≤ (1:R) bylinarith134exact⟨zeroIn, ⟨zeroLe, zeroLeOne⟩⟩135136-- With one representative strategy per player, the masked terms of a block137-- form the point mass at that player's representative.138theorem pureProfile.masked (n : Nat) (m blk rep p i : Set)139requires blkFunction : blk : n → m140requires repFunction : rep : m → n141requiressection : ∀ q ∈ m, blk(rep(q)) = q142requires hp : p ∈ m143requires hi : i ∈ n144shows maskedPoint(n, blockSet(n, blk, p), pureProfile(n, functionImage(rep, m, n)))(i) = pointMass(rep(p))(i)145proof146have aFunction := pureProfile.function at n, functionImage(rep, m, n) with*147have iNat := natural.member at n with hi148have iIn := nat.omega.forward with iNat149have massValue := pointMass.value at rep(p), i150have alternatives : i = rep(p) ∨ i ≠ rep(p) bytauto151cases alternatives with same | different152 . have inBlock : i ∈ blockSet(n, blk, p)153apply blockSet.membership.backward154constructor155 . exact hi156 . rw same157exactsectionwith hp158have onBlock := maskedPoint.on_block with aFunction, hi, inBlock159have value := pureProfile.value at n, functionImage(rep, m, n), i with*160have inImage : i ∈ functionImage(rep, m, n)161apply functionImage.membership.backward162constructor163 . exact hi164 . witness p165exact⟨hp, same⟩166have one := indicator.member with inImage167rw [onBlock, value, one, massValue, same]168haveself:= indicator.singleton_self at rep(p)169rwself170rfl171 . have differentReversed : rep(p) ≠ i172intro equal173apply different174rw equal175rfl176have other := indicator.singleton_other with differentReversed177have massZero : pointMass(rep(p))(i) = (0:R)178rw [massValue, other]179exact realNat.zero180rw massZero181have blockAlternatives : i ∈ blockSet(n, blk, p) ∨¬i ∈ blockSet(n, blk, p) bytauto182cases blockAlternatives with inBlock | outside183 . have onBlock := maskedPoint.on_block with aFunction, hi, inBlock184have value := pureProfile.value at n, functionImage(rep, m, n), i with*185have notImage : ¬i ∈ functionImage(rep, m, n)186intro inImage187have parts := functionImage.membership.forward with inImage188obtain⟨q, hq, equal⟩:= parts.right189have blockParts := blockSet.membership.forward with inBlock190have blockI := blockParts.right191have sectionQ :=sectionwith hq192rw [equal, sectionQ] at blockI193apply different194rw [equal, blockI]195rfl196have zero := indicator.absent with notImage197rw [onBlock, value, zero]198exact realNat.zero199 . exact maskedPoint.off_block with aFunction, hi, outside200201theorem pureProfile.in_product (n : Nat) (m blk rep : Set)202requires blkFunction : blk : n → m203requires repFunction : rep : m → n204requiressection : ∀ q ∈ m, blk(rep(q)) = q205shows pureProfile(n, functionImage(rep, m, n)) ∈ simplexProduct(n, m, blk)206proof207have aCube := pureProfile.in_cube at n, functionImage(rep, m, n)208apply simplexProduct.membership.backward209constructor210 . exact aCube211 . intros p hp212have aFunction := unitCube.point with aCube213have sequence := maskedPoint.sequence at n, blockSet(n, blk, p), pureProfile(n, functionImage(rep, m, n)) with aFunction214have mass := pointMass.sequence at rep(p)215have agree : ∀ k ∈ ℕ, k < n → maskedPoint(n, blockSet(n, blk, p), pureProfile(n, functionImage(rep, m, n)))(k) = pointMass(rep(p))(k)216intros k hk hkn217have hkIn : k ∈ n byexact membership.of_Less with hkn218exact pureProfile.masked with blkFunction, repFunction, section, hp, hkIn219have equal := realPartialSum.eq_prefix with sequence, mass, agree220have repIn := FunctionBetween.apply_closed with repFunction, hp221have one := realPartialSum.pointMass_below at rep(p), n222have blockValue := blockSum.definition at n, blk, p223have cubeValue := sumCube.value at n, blockSet(n, blk, p), pureProfile(n, functionImage(rep, m, n)) with aCube224rw blockValue at cubeValue225rw [cubeValue, equal]226exact one with repIn227