Zulip Chat Archive
Stream: combinatorial-games
Topic: Loopy games
Violeta Hernández (Jul 27 2025 at 02:09):
Aaron Liu said:
Django Peeters said:
Violeta Hernández said:
(something similar applies to loopy games, we'll eventually have to define a new data type to represent those as well!)
How about a pointed, directed graph where the directed edges can only have 1 of 2 colours, where the chosen point is a vertex representing the starting position.
I have a work in progress definition of loopy games as a subquotient of countably deep u-small branching trees
Violeta Hernández (Jul 27 2025 at 02:09):
Do you have any Lean code? I'm interested to see how this looks.
Aaron Liu (Jul 27 2025 at 02:09):
I have some code
Aaron Liu (Jul 27 2025 at 02:10):
It's only on my local device at the moment
Aaron Liu (Jul 27 2025 at 02:10):
Since the class field theory workshop is over now I can start working on this again
Aaron Liu (Jul 27 2025 at 11:59):
So after IGame is constructed there is some API, some of which I will list out here
IGame.leftMovesIGame.rightMovesIGame.extIGame.IsOptionIGame.IsOption.of_mem_leftMovesIGame.IsOption.of_mem_rightMovesIGame.instSmallSubtypeIsOptionIGame.isOption_wfIGame.ofSetsIGame.leftMoves_ofSetsIGame.rightMoves_ofSets
This list of API is sufficient to characterize IGame, so that you never have to touch PGame ever again. This is because between any two types which both implement this API there exists a unique API-preserving isomorphism between them.
Aaron Liu (Jul 27 2025 at 12:00):
So after I make loopy games, what API should I provide which uniquely characterizes them? I'm thinking of providing a destructor and some corecursion, but this is not sufficient since you can prove an isomorphism but not the fact that it's unique.
Violeta Hernández (Jul 27 2025 at 12:20):
I'm still having a hard time figuring out what a loopy game even is, as a mathematical structure.
Violeta Hernández (Jul 27 2025 at 12:22):
The literature seems to define them in terms of graphs, but I can get not wanting to do that since it leads to some degenerate cases, such as a game 0 -> 1 -> ... -> infty where the last node isn't even accessible in finitely many moves
Aaron Liu (Jul 27 2025 at 12:22):
It's like a game but coinductive, right?
Violeta Hernández (Jul 27 2025 at 12:23):
I think so, but I don't really know how that translates in Lean
Aaron Liu (Jul 27 2025 at 12:23):
However it translates into Lean doesn't matter as long as you have all the API
Aaron Liu (Jul 27 2025 at 12:23):
The question is, what should the API be?
Violeta Hernández (Jul 27 2025 at 12:26):
Well, I presume we should still have left and right move sets, which should still be small, and should still satisfy extentionality
Violeta Hernández (Jul 27 2025 at 12:26):
I presume we would also have an ofSets constructor
Violeta Hernández (Jul 27 2025 at 12:26):
But we'd also need some other way to construct games which are subsequent to themselves
Aaron Liu (Jul 27 2025 at 12:27):
some sort of fixpoint combinator, then?
Aaron Liu (Jul 27 2025 at 12:27):
or corecursion like I mentioned
Violeta Hernández (Jul 27 2025 at 12:28):
I still don't quite understand how to construct terms in a coinductive type
Aaron Liu (Jul 27 2025 at 12:28):
you do it with corecursion
Aaron Liu (Jul 27 2025 at 12:29):
in mathlib for example we have docs#Computation.corec docs#Stream'.corec' docs#Stream'.Seq.corec docs#PFunctor.M.corec
Aaron Liu (Jul 27 2025 at 12:30):
oh if this is a polynomial functor then we can use what's in mathlib already
Violeta Hernández (Jul 27 2025 at 12:30):
I'll go take a look at that, Wikipedia really isn't helping me much
Aaron Liu (Jul 27 2025 at 12:35):
hold on I'm unpacking what it means to be a final coalgebra
Aaron Liu (Jul 27 2025 at 12:35):
maybe if this is correct we can use docs#PFunctor.M
Aaron Liu (Jul 27 2025 at 12:38):
I think what we actually need is M-types for quotients of polynomial functors
Aaron Liu (Jul 27 2025 at 12:41):
oh it's docs#QPF.Cofix
Aaron Liu (Jul 27 2025 at 12:41):
amazing
Django Peeters (Jul 27 2025 at 16:08):
Violeta Hernández said:
The literature seems to define them in terms of graphs, but I can get not wanting to do that since it leads to some degenerate cases, such as a game 0 -> 1 -> ... -> infty where the last node isn't even accessible in finitely many moves
How does it lead to that degenerate case?
Violeta Hernández (Jul 27 2025 at 19:38):
Well, the graph I describe is simple enough to define.
Aaron Liu (Jul 27 2025 at 19:39):
you have to quotient to get the right notion of equality
Aaron Liu (Jul 27 2025 at 19:40):
I have a half-working definition using docs#QPF.Cofix
here it is
Violeta Hernández (Jul 27 2025 at 19:52):
I'm still struggling to understand what a "polynomial functor" is
Aaron Liu (Jul 27 2025 at 19:52):
it's a sum of products
Violeta Hernández (Jul 27 2025 at 19:53):
Aaron Liu said:
you have to quotient to get the right notion of equality
Oh you're right, the graph I describe would just be equivalent to the game x = {x | x}, right? I think the magic word is bisimulation?
Aaron Liu (Jul 27 2025 at 19:53):
yes that's the word
Aaron Liu (Jul 27 2025 at 19:55):
Violeta Hernández said:
I'm still struggling to understand what a "polynomial functor" is
The example I gave to Jovan Gerbscheid is the list functor, it looks like () ⊕ α ⊕ α × α ⊕ α × α × α ⊕ ... where the nth component of the sum is n-length lists
Aaron Liu (Jul 27 2025 at 19:56):
this is the same as Fin 0 → α ⊕ Fin 1 → α ⊕ Fin 2 → α ⊕ ..., which is (n : Nat) × (Fin n → α)
Aaron Liu (Jul 27 2025 at 19:56):
polynomial functors are ones that look like P α = (a : A) × (B a → α)
Aaron Liu (Jul 27 2025 at 19:57):
for lists you have A := Nat and B := Fin
Violeta Hernández (Jul 27 2025 at 19:57):
Well, in that case, I think the simplest way to construct an arbitrary loopy game is something like
def mk {a : Type u} (l : a -> a -> Prop) (r : a -> a -> Prop) (x : a) : LGame.{u} :=
sorry
theorem leftMoves_mk {a l r x} : (mk l r a).leftMoves = leftMoves '' {y | l y x} :=
sorry
theorem rightMoves_mk {a l r x} : (mk l r a).rightMoves = rightMoves '' {y | r y x} :=
sorry
Aaron Liu (Jul 27 2025 at 20:00):
90% sure this allows you to construct the loopy game with options all loopy games not subsequent to themselves
Violeta Hernández (Jul 27 2025 at 20:01):
LGame.{u} : Type (u + 1) so there shouldn't be any Bertrand nonsense
Aaron Liu (Jul 27 2025 at 20:02):
oh you're taking a : Type u
Aaron Liu (Jul 27 2025 at 20:02):
not sure that's a good idea but it does make everything a lot simpler
Violeta Hernández (Jul 27 2025 at 20:02):
I think the correct thing to do is take Small.{u} a
Aaron Liu (Jul 27 2025 at 20:03):
that also works
Aaron Liu (Jul 27 2025 at 20:03):
neither of those lets you define ofSets very easily though
Aaron Liu (Jul 27 2025 at 20:04):
I'm trying to do a version where you have
variable {α : Type v} (leftMoves : α → Set α) (rightMoves : α → Set α)
[∀ a, Small.{u} (leftMoves a)] [∀ a, Small.{u} (rightMoves a)] (init : α)
Aaron Liu (Jul 27 2025 at 20:05):
it's a bit tricky
Aaron Liu (Jul 27 2025 at 20:07):
I think the problem is not enough stuff in mathlib
Violeta Hernández (Jul 27 2025 at 20:07):
Take two small sets, build the sigma type of subpositions, and I think that should work?
Aaron Liu (Jul 27 2025 at 20:09):
explain "the sigma type of subpositions"
Violeta Hernández (Jul 27 2025 at 20:13):
Not a sigma type, sorry. Let me write this down.
Aaron Liu (Jul 27 2025 at 20:14):
good luck
Violeta Hernández (Jul 27 2025 at 20:14):
Take s and t small. The type Option (Subpositions s U Subpositions t) is small, and you can define left move and right move relations on some x in the obvious way, and on none so that it relates to the games in s and t. Then mk l r none should be what you want.
Aaron Liu (Jul 27 2025 at 20:15):
you will have to be a bit more explicit than that
Aaron Liu (Jul 27 2025 at 20:15):
how do I get s and t
Aaron Liu (Jul 27 2025 at 20:15):
how do I prove the left and right moves of this is correct
Violeta Hernández (Jul 27 2025 at 20:15):
Those are the sets you input into ofSets
Violeta Hernández (Jul 27 2025 at 20:16):
I'm struggling to write on my cellphone, let me go to my laptop
Django Peeters (Jul 27 2025 at 20:20):
Violeta Hernández said:
Well, the graph I describe is simple enough to define.
I'm not sure how to make a graph from this. What game do you mean?
Aaron Liu (Jul 27 2025 at 20:21):
Violeta Hernández said:
The literature seems to define them in terms of graphs, but I can get not wanting to do that since it leads to some degenerate cases, such as a game 0 -> 1 -> ... -> infty where the last node isn't even accessible in finitely many moves
I think infty is actually isolated here
Django Peeters (Jul 27 2025 at 20:21):
Also, do you mean degenerate because a normal game is also a loopy game? Or do we want those to be disjoint?
Violeta Hernández (Jul 27 2025 at 20:25):
def Subposition (x y : LGame.{u}) : Prop := sorry
private def Subpositions (x : LGame.{u}) : Set LGame.{u} := insert x {y | Subposition y x}
instance (x : LGame.{u}) : Small.{u} (Subpositions x) := sorry
def ofSets (s t : Set LGame.{u}) [Small.{u} s] [Small.{u} t] : LGame.{u} :=
let α := Option (⋃₀ (Subpositions '' (s ∪ t)))
have : Small.{u} α := sorry
let l : α → α → Prop := fun x y ↦ match x y with
| _, none => False
| none, some x => x ∈ s
| some x, some y => x ∈ y.leftMoves
let r : α → α → Prop := fun x y ↦ match x y with
| _, none => False
| none, some x => x ∈ t
| some x, some y => x ∈ y.rightMoves
mk l r none
Violeta Hernández (Jul 27 2025 at 20:26):
Aaron Liu said:
Violeta Hernández said:
The literature seems to define them in terms of graphs, but I can get not wanting to do that since it leads to some degenerate cases, such as a game 0 -> 1 -> ... -> infty where the last node isn't even accessible in finitely many moves
I think
inftyis actually isolated here
You're right, it is. I should be more careful with notation!
Violeta Hernández (Jul 27 2025 at 20:27):
Or in other words, the directed graph on ENat relating n with n + 1 for n : ℕ has no incoming edges on ⊤.
Aaron Liu (Jul 27 2025 at 20:28):
Violeta Hernández said:
def Subposition (x y : LGame.{u}) : Prop := sorry private def Subpositions (x : LGame.{u}) : Set LGame.{u} := insert x {y | Subposition y x} instance (x : LGame.{u}) : Small.{u} (Subpositions x) := sorry def ofSets (s t : Set LGame.{u}) [Small.{u} s] [Small.{u} t] : LGame.{u} := let α := Option (⋃₀ (Subpositions '' (s ∪ t))) have : Small.{u} α := sorry let l : α → α → Prop := fun x y ↦ match x y with | _, none => False | none, some x => x ∈ s | some x, some y => x ∈ y.leftMoves let r : α → α → Prop := fun x y ↦ match x y with | _, none => False | none, some x => x ∈ t | some x, some y => x ∈ y.rightMoves mk l r none
this looks like it might actually work
Violeta Hernández (Jul 27 2025 at 20:32):
Aaron Liu said:
I'm trying to do a version where you have
variable {α : Type v} (leftMoves : α → Set α) (rightMoves : α → Set α) [∀ a, Small.{u} (leftMoves a)] [∀ a, Small.{u} (rightMoves a)] (init : α)
This is a generalization of what I'm doing, but I'm a bit skeptical that we'll ever have a situation where we want to construct a small game tree out of a type that isn't.
Aaron Liu (Jul 27 2025 at 20:32):
I will use it to define mk
Aaron Liu (Jul 27 2025 at 20:33):
and ofSets
Violeta Hernández (Jul 27 2025 at 20:33):
I think you might be able to do it the other way around
Violeta Hernández (Jul 27 2025 at 20:34):
Given these functions, you can construct the set of all games accessible from init from leftMoves and rightMoves, and it should be Small
Violeta Hernández (Jul 27 2025 at 20:34):
Then you can just use mk on that subtype
Aaron Liu (Jul 27 2025 at 20:35):
yes but I haven't defined mk yet so I'm using to corecursor directly
Aaron Liu (Jul 27 2025 at 20:35):
now I'm thinking maybe not the best idea
Violeta Hernández (Jul 27 2025 at 20:35):
I was thinking mk might be a bit easier to define than your map
Aaron Liu (Jul 27 2025 at 20:35):
but I can still see the same problem
Aaron Liu (Jul 27 2025 at 20:36):
that is proving that mk on the subtype corresponds to mk on the other subtype
Violeta Hernández (Jul 27 2025 at 20:37):
What do you mean?
Aaron Liu (Jul 27 2025 at 20:38):
when you do the thing where you restrict to the subtype of games accessible from init
Aaron Liu (Jul 27 2025 at 20:40):
you would like to prove that mk on the subtype of elements accessible from init fed with a l is the same as mk on the subtype of elements accessible from l fed with a l
Aaron Liu (Jul 27 2025 at 20:40):
I'm currently trying to figure out the best generality to state this in that makes it the easiest to prove
Violeta Hernández (Jul 27 2025 at 20:42):
I don't think you actually need to prove that?
Violeta Hernández (Jul 27 2025 at 20:43):
You should have extensionality on LGame
Aaron Liu (Jul 27 2025 at 20:43):
the extensionality is stated in terms of bisimulation
Violeta Hernández (Jul 27 2025 at 20:43):
If x.leftMoves = y.leftMoves and x.rightMoves = y.rightMoves then x = y
Violeta Hernández (Jul 27 2025 at 20:43):
I mean, this is true, as is the bisimulation thing
Aaron Liu (Jul 27 2025 at 20:44):
I do have that version
Aaron Liu (Jul 27 2025 at 20:44):
but you can't prove x.leftMoves = y.leftMoves
Aaron Liu (Jul 27 2025 at 20:44):
unless you have a way?
Violeta Hernández (Jul 27 2025 at 20:45):
So if you can prove leftMoves (ofSets s t) = s and rightMoves (ofSets s t) = t then that should be enough to prove the definition is correct
Violeta Hernández (Jul 27 2025 at 20:45):
Or do I misunderstand the issue?
Aaron Liu (Jul 27 2025 at 20:47):
the issue is that you can't use ofSets to construct loopy games that are subpositions of themselves
Aaron Liu (Jul 27 2025 at 20:47):
you need another constructor
Violeta Hernández (Jul 27 2025 at 20:47):
Oh yeah, I know that
Violeta Hernández (Jul 27 2025 at 20:47):
These are two separate constructors
Aaron Liu (Jul 27 2025 at 20:48):
what's the other constructor
Violeta Hernández (Jul 27 2025 at 20:48):
Either (or both!) versions of mk
Aaron Liu (Jul 27 2025 at 20:48):
the issue is proving the defining equations for mk
Violeta Hernández (Jul 27 2025 at 20:48):
ofSets is a convenience constructor built on top of it
Violeta Hernández (Jul 27 2025 at 20:49):
In practice I imagine ofSets as well as dud, on, off can already describe most games of interest
Violeta Hernández (Jul 27 2025 at 20:52):
Aaron Liu said:
the issue is proving the defining equations for
mk
I think there's two different types at play here. We should first define some auxiliary type (PLGame?) as the coinductive type with constructor mk (a : Type u) (f : a -> PLGame) : PLGame. Then you can quotient over identity to get LGame, which should satisfy extentionality as well as a coinduction principle.
Aaron Liu (Jul 27 2025 at 20:52):
that's a bad idea
Aaron Liu (Jul 27 2025 at 20:52):
well, not a bad idea but inefficient since I can directly go to LGame without having to pass through PLGame
Aaron Liu (Jul 27 2025 at 20:53):
and I've proved extensionality and bisimulation but coinduction is tricky
Aaron Liu (Jul 27 2025 at 20:53):
and it won't get any easier if I go to PLGame since I don't get anything new there
Violeta Hernández (Jul 27 2025 at 20:54):
How did you define LGame without PLGame?
Aaron Liu (Jul 27 2025 at 20:54):
Using QPF.Cofix
Violeta Hernández (Jul 27 2025 at 20:55):
Huh, now I'm wondering if there's some way to define IGame without PGame using functor shenanigans
Aaron Liu (Jul 27 2025 at 20:55):
I prove that fun X => { s : Set X × Set X // Small.{u} s.1 ∧ Small.{u} s.2 } with the obvious map is the quotient of a polynomial functor
Aaron Liu (Jul 27 2025 at 20:55):
Violeta Hernández said:
Huh, now I'm wondering if there's some way to define
IGamewithoutPGameusing functor shenanigans
it's almost certainly just the dual (fixpoint instead of cofixpoint)
Violeta Hernández (Jul 27 2025 at 20:55):
Add that to do the TODO list!
Violeta Hernández (Jul 27 2025 at 20:56):
Aaron Liu said:
I prove that
fun X => { s : Set X × Set X // Small.{u} s.1 ∧ Small.{u} s.2 }with the obvious map is the quotient of a polynomial functor
Does quotient here mean the type has the extensional form of equality?
Aaron Liu (Jul 27 2025 at 20:56):
actually not quite
Aaron Liu (Jul 27 2025 at 20:58):
the polynomial functor in question is (c : Cardinal.{u} × Cardinal.{u}) × ((PLift c.1.out ⊕ PLift c.2.out) → α) with the obvious map
Aaron Liu (Jul 27 2025 at 20:59):
and the quotient part mean that tuples are identified when they have all the same entries, and you ignore the order and multiplicity of the entries
Aaron Liu (Jul 27 2025 at 20:59):
it does turn out to give you the correct extensionality
Violeta Hernández (Jul 27 2025 at 21:01):
Interesting!
Aaron Liu (Jul 27 2025 at 21:02):
it has occurred to me that I could do this without Cardinal
Aaron Liu (Jul 27 2025 at 21:02):
and just use types
Aaron Liu (Jul 27 2025 at 21:03):
and then the polynomial functor will be the same as the one defining PGame and the quotient will be the same as PGame.Identical
Violeta Hernández (Jul 27 2025 at 21:04):
So just to conceptualize this, the functor Finset would be the quotient of the polynomial functor (a : Nat) x Fin a, right?
Aaron Liu (Jul 27 2025 at 21:05):
that's not a functor
Aaron Liu (Jul 27 2025 at 21:05):
that's just a type
Aaron Liu (Jul 27 2025 at 21:05):
I've been making my types into functors by abstracting over α in a covariant way
Aaron Liu (Jul 27 2025 at 21:06):
you don't have an α
Violeta Hernández (Jul 27 2025 at 21:06):
Oh yeah would it be (n : Nat) x Fin n -> a then
Aaron Liu (Jul 27 2025 at 21:07):
yes that's correct
Aaron Liu (Jul 27 2025 at 21:07):
it's a quotient of lists by the relation "contains all the same elements"
Aaron Liu (Jul 27 2025 at 21:07):
so you've built lists
Violeta Hernández (Jul 27 2025 at 21:07):
Isn't it finsets, rather?
Aaron Liu (Jul 27 2025 at 21:09):
well fun α => (n : ℕ) × (Fin n → α) looks like the list functor to me
Violeta Hernández (Jul 27 2025 at 21:10):
Oh yeah, I mean that lists quotiented by containing the same elements are what finsets are
Aaron Liu (Jul 27 2025 at 21:10):
yes that's correct
Violeta Hernández (Jul 27 2025 at 21:11):
Interesting! So then you can define inductive and coinductive types from this polynomial functor, right?
Aaron Liu (Jul 27 2025 at 21:11):
if you take the fixpoint you get hereditarily finite sets
Violeta Hernández (Jul 27 2025 at 21:12):
And the cofixpoint would be... hereditarily finite non-well-founded sets?
Aaron Liu (Jul 27 2025 at 21:12):
I guess that's one way to think about it
Aaron Liu (Jul 27 2025 at 21:12):
they're not really finite anymore
Aaron Liu (Jul 27 2025 at 21:12):
they're finitely branching
Violeta Hernández (Jul 27 2025 at 21:13):
I think they should still be finite, though
Aaron Liu (Jul 27 2025 at 21:14):
they be related to infinitely many distinct elements under the relation "transitive closure of membership"
Violeta Hernández (Jul 27 2025 at 21:14):
A quine a = {a} would only be related to itself
Violeta Hernández (Jul 27 2025 at 21:15):
(deleted)
Aaron Liu (Jul 27 2025 at 21:15):
use a = {0, {1, {2, {3, {4, {5, ...
Aaron Liu (Jul 27 2025 at 21:15):
a tower of pairs
Aaron Liu (Jul 27 2025 at 21:16):
but all distinct
Violeta Hernández (Jul 27 2025 at 21:16):
Whoa
Violeta Hernández (Jul 27 2025 at 21:18):
Wait, follow-up question
Violeta Hernández (Jul 27 2025 at 21:19):
Say you defined the coinductive type of the functor fun a => (n : ENat) x (toType n -> a) where ENat.toType is Fin n for a natural and Nat for top
Violeta Hernández (Jul 27 2025 at 21:19):
Would it be correct to call these sets hereditarily countable?
Violeta Hernández (Jul 27 2025 at 21:19):
Or do you run into the same issue?
Aaron Liu (Jul 27 2025 at 21:20):
since the cofinality of ℵ₁ is ω₁ which is uncountable
Aaron Liu (Jul 27 2025 at 21:20):
I think these are "hereditarily countable"
Violeta Hernández (Jul 27 2025 at 21:21):
Basically I'm just trying to tune my intuition for the case fun a => (b : Type u) x (b -> a)
Django Peeters (Jul 27 2025 at 21:21):
Violeta Hernández said:
Or in other words, the directed graph on
ENatrelatingnwithn + 1forn : ℕhas no incoming edges on⊤.
Thank you very much! That is indeed a clear degenerate case... I'm gonna try to refine my definition.
Aaron Liu (Jul 27 2025 at 21:22):
have you seen docs#WType
Violeta Hernández (Jul 27 2025 at 21:22):
And by extension fun a => (b c : Type u) x (b -> c -> a) which is I think what games are
Violeta Hernández (Jul 27 2025 at 21:22):
I've worked with W-types, yes
Violeta Hernández (Jul 27 2025 at 21:22):
I haven't worked with the dual M types though
Violeta Hernández (Jul 27 2025 at 21:23):
Django Peeters said:
Violeta Hernández said:
Or in other words, the directed graph on
ENatrelatingnwithn + 1forn : ℕhas no incoming edges on⊤.Thank you very much! That is indeed a clear degenerate case... I'm gonna try to regine my definition.
I'm pretty sure the game I describe is equivalent (through bisimulation) to the game where top doesn't exist at all
Violeta Hernández (Jul 27 2025 at 21:23):
So it's not really a degenerate case at all
Violeta Hernández (Jul 27 2025 at 21:29):
I might be getting ahead of myself, but I'd love to see a Functor file in the repository, where we prove that fun α ↦ {s : Set α × Set α // Small.{u} s.1 ∧ Small.{u} s.2} is a QPF. Then we could define IGame := QPF.fix and LGame := QPF.cofix and hopefully skip a lot of setup in both cases!
Aaron Liu (Jul 27 2025 at 21:29):
we could try that
Violeta Hernández (Jul 27 2025 at 21:30):
(I imagine a similar approach might be helpful for docs#ZFSet but I also imagine there are other computability concerns in that case)
Violeta Hernández (Jul 27 2025 at 21:40):
I'll try to reprove QPF F as a bit of a personal excercise
Violeta Hernández (Jul 27 2025 at 22:28):
How's this?
def GameFunctor : Type (u + 1) → Type (u + 1) :=
fun α => {s : Set α × Set α // Small.{u} s.1 ∧ Small.{u} s.2}
namespace GameFunctor
@[ext] theorem ext {α : Type (u + 1)} {x y : GameFunctor α} : x.1 = y.1 → x = y := Subtype.ext
instance {α : Type (u + 1)} (x : GameFunctor α) : Small.{u} x.1.1 := x.2.1
instance {α : Type (u + 1)} (x : GameFunctor α) : Small.{u} x.1.2 := x.2.2
instance : Functor GameFunctor where
map f s := ⟨⟨f '' s.1.1, f '' s.1.2⟩, ⟨inferInstance, inferInstance⟩⟩
noncomputable instance : QPF GameFunctor where
P := ⟨Type u × Type u, fun x ↦ PLift x.1 ⊕ PLift x.2⟩
abs x := ⟨⟨Set.range (x.2 ∘ .inl ∘ PLift.up), Set.range (x.2 ∘ .inr ∘ PLift.up)⟩,
⟨inferInstance, inferInstance⟩⟩
repr x := ⟨⟨Shrink x.1.1, Shrink x.1.2⟩,
Sum.rec (fun y ↦ ((equivShrink _).symm y.1).1) (fun y ↦ ((equivShrink _).symm y.1).1)⟩
abs_repr x := by ext <;> simp [← (equivShrink _).exists_congr_right]
abs_map f := by intro ⟨x, f⟩; ext <;> simp [PFunctor.map, Functor.map]
__ := instFunctor
end GameFunctor
Violeta Hernández (Jul 27 2025 at 22:30):
I think this golfs your code a bit :slight_smile:
Aaron Liu (Jul 27 2025 at 22:44):
It golfs it quite a lot
Aaron Liu (Jul 27 2025 at 22:45):
never mind it's barely shorter
Aaron Liu (Jul 27 2025 at 22:45):
since you made it structured
Violeta Hernández (Jul 27 2025 at 22:45):
I would not like to see this written down using only angle brackets
Violeta Hernández (Jul 27 2025 at 22:48):
A few thoughts in my head:
- It would be nice if someone went through the
QPFfile and addedprivateto everything that isn't the inductive/coinductive principles, it would really cut down on noise. - Apparently the notion of a
QPFwas introduced by members of the Zulip at a conference in 2019? That's awesome.
Violeta Hernández (Jul 27 2025 at 22:50):
I'll give this a read: https://drops.dagstuhl.de/entities/document/10.4230/LIPIcs.ITP.2019.6
Violeta Hernández (Jul 27 2025 at 23:11):
You never never in a million years would I have guessed that I'd be reading category theory in order to formalize game theory of all things
Aaron Liu (Jul 27 2025 at 23:14):
it's the initial and final algebras
Violeta Hernández (Jul 27 2025 at 23:18):
I'm flabbergasted to learn that inductive types boil down to a commutative square
Violeta Hernández (Jul 27 2025 at 23:28):
I've been staring at commutative diagrams for about half an hour I think I finally get this
Violeta Hernández (Jul 27 2025 at 23:30):
So IGame is the initial algebra of the functor F(α) = SmallSet α × SmallSet α and LGame is the terminal coalgebra of the same functor
Violeta Hernández (Jul 27 2025 at 23:31):
The QPF machinery is really only there to guarantee that these algebras/coalgebras exist and to construct them
Violeta Hernández (Jul 27 2025 at 23:36):
On a related note, surely docs#PFunctor.Approx.Agree.continu is a typo
Aaron Liu (Jul 27 2025 at 23:48):
Well I've proved the left and right moves of corec are correct
Aaron Liu (Jul 27 2025 at 23:49):
now I guess I build ofSets?
Violeta Hernández (Jul 28 2025 at 00:01):
That makes sense
Aaron Liu (Jul 28 2025 at 00:19):
ok ofSets is done
Aaron Liu (Jul 28 2025 at 00:19):
is there anything else I might be missing
Violeta Hernández (Jul 28 2025 at 00:20):
Did you manage to build anything similar to the mk graph constructor from earlier?
Aaron Liu (Jul 28 2025 at 00:21):
yeah
Aaron Liu (Jul 28 2025 at 00:21):
it's corec
Aaron Liu (Jul 28 2025 at 00:21):
if you abuse the defeq between Set X and X → Prop
Violeta Hernández (Jul 28 2025 at 00:25):
We should maybe not do that
Violeta Hernández (Jul 28 2025 at 00:26):
But if you have some version of that then it's fine
Violeta Hernández (Jul 28 2025 at 00:27):
Btw, I'm currently working on redefining IGame as a QPF.Fix. I think we could start there, then PR the definition of LGame as QPF.Cofix.
Violeta Hernández (Jul 28 2025 at 00:27):
It would inform the API a bit better
Aaron Liu (Jul 28 2025 at 00:33):
I made vihdzp/combinatorial-games#156 so you can see my code
Violeta Hernández (Jul 28 2025 at 01:02):
Something I find peculiar is that the recursor for IGame seems... not very useful? This is what you get if you directly use docs#QPF.Fix.rec
def rec {α : Type (u + 1)}
(g : ∀ (s t : Set α) [Small.{u} s] [Small.{u} t], α) : IGame → α :=
QPF.Fix.rec fun x ↦ g x.1.1 x.1.2
The fact that α is limited to Type (u + 1) really limits what you can do with this. I found it easier to instead go the long way around and prove WellFounded IsOption, at which point well-founded recursion gives you a much better recursor.
Aaron Liu (Jul 28 2025 at 01:02):
yeah I'm pretty sure those can be generalized to arbitrary universes
Violeta Hernández (Jul 28 2025 at 01:37):
Aaron Liu said:
I made vihdzp/combinatorial-games#156 so you can see my code
I just opened https://github.com/vihdzp/combinatorial-games/pull/157 which defines GameFunctor and uses it to completely get rid of PGame.
Violeta Hernández (Jul 28 2025 at 01:40):
I think you should merge my PR into yours
Violeta Hernández (Jul 28 2025 at 01:53):
@Aaron Liu Is there ever a circumstance where you'd use eq_of_bisim over the extensionality principle? Like, what kind of a relation r could you put into the theorem that isn't just equality?
Violeta Hernández (Jul 28 2025 at 01:53):
I might be misunderstanding how bisimulations are used
Aaron Liu (Jul 28 2025 at 01:56):
How would you prove that forall f : α → α and x : α, corec (fun i => {f i}) (fun i => {f i}) x is equal to corec (fun _ => {()}) (fun _ => {()}) ()
Violeta Hernández (Jul 28 2025 at 01:57):
Oh I see, extensionality alone doesn't cut it
Violeta Hernández (Jul 28 2025 at 02:00):
Wait, let me translate into plaintext. Does that mean that the game x = {x | x} is equal to every game yᵢ where yᵢ = {y₁, ... yₙ | y₁, ... yₙ}?
Aaron Liu (Jul 28 2025 at 02:00):
I am 70% sure that out of the two theorems eq_of_bisim and hom_unique in my PR, they are both enough for extensionality and you can prove either one from the other
Aaron Liu (Jul 28 2025 at 02:01):
Violeta Hernández said:
Wait, let me translate into plaintext. Does that mean that the game x = {x | x} is equal to every game yᵢ where yᵢ = {y₁, ... yₙ | y₁, ... yₙ}?
I think so? If I'm understanding what you mean correctly
Aaron Liu (Jul 28 2025 at 02:01):
both of those play out as
Violeta Hernández (Jul 28 2025 at 02:02):
Yeah, and if you tried to prove it with extensionality you'd get the exact same result you're trying to prove!
Violeta Hernández (Jul 28 2025 at 02:02):
But how do you prove this using a bisimulation?
Aaron Liu (Jul 28 2025 at 02:04):
you have to encode "this game is dud" into a relation
Violeta Hernández (Jul 28 2025 at 02:08):
Let me try
Violeta Hernández (Jul 28 2025 at 02:18):
Wait
Violeta Hernández (Jul 28 2025 at 02:18):
If you assume yᵢ = {y₁, ... yₙ | y₁, ... yₙ} for all i then yᵢ = yⱼ by extensionality
Aaron Liu (Jul 28 2025 at 02:18):
yes
Violeta Hernández (Jul 28 2025 at 02:19):
So really all you're trying to prove is that any two games with x = {x | x} are equal
Aaron Liu (Jul 28 2025 at 02:19):
yes
Violeta Hernández (Jul 28 2025 at 02:19):
But I can see how that can't be proven by extensionality
Violeta Hernández (Jul 28 2025 at 02:20):
I still don't quite see how to prove it by bisimulation, though.
Aaron Liu (Jul 28 2025 at 02:25):
theorem dud_eq (x y : LGame.{u})
(hx : x = .ofSets {x} {x}) (hy : y = .ofSets {y} {y}) : x = y := by
refine LGame.eq_of_bisim
(fun i j => i = LGame.ofSets.{u} {i} {i} ∧ j = LGame.ofSets.{u} {j} {j})
(fun x y hxy => ?_) (fun x y hxy => ?_) x y ⟨hx, hy⟩
· rw [hxy.left, hxy.right, LGame.leftMoves_ofSets, LGame.leftMoves_ofSets]
refine ⟨(Equiv.Set.singleton.{0} x).trans (Equiv.Set.singleton.{0} y).symm, fun i => ?_⟩
obtain ⟨x, rfl⟩ := i
simpa [Equiv.Set.singleton] using hxy
· rw [hxy.left, hxy.right, LGame.rightMoves_ofSets, LGame.rightMoves_ofSets]
refine ⟨(Equiv.Set.singleton.{0} x).trans (Equiv.Set.singleton.{0} y).symm, fun i => ?_⟩
obtain ⟨x, rfl⟩ := i
simpa [Equiv.Set.singleton] using hxy
Aaron Liu (Jul 28 2025 at 02:25):
tada
Aaron Liu (Jul 28 2025 at 02:25):
bisimulation
Violeta Hernández (Jul 28 2025 at 02:35):
Ohhh wait I get my confusion
Violeta Hernández (Jul 28 2025 at 02:35):
I was reading the definition of coinduction rather than bisimulation
Violeta Hernández (Jul 28 2025 at 02:35):
No wonder I was confused
Violeta Hernández (Jul 28 2025 at 02:38):
So bisimulation for loopy games tells you that if you have a relation on loopy games such that when x and y are related... There's a pairing between left options of x and left options of y so that every pair is related, and likewise for right options? Then r x y implies x = y.
Violeta Hernández (Jul 28 2025 at 04:10):
I guess one way to think about this is that the existence of such a relation allows you to build an isomorphism between game trees
Violeta Hernández (Jul 28 2025 at 04:18):
If r x y then there's a pairing between options of x and y, which means the game trees up to depth one are isomorphic. Then there must also be a pairing between the corresponding options of those options, which means the game tree up to depth two are isomorphic. And so on.
Violeta Hernández (Jul 28 2025 at 04:18):
Aaron Liu said:
theorem dud_eq (x y : LGame.{u}) (hx : x = .ofSets {x} {x}) (hy : y = .ofSets {y} {y}) : x = y := by refine LGame.eq_of_bisim (fun i j => i = LGame.ofSets.{u} {i} {i} ∧ j = LGame.ofSets.{u} {j} {j}) (fun x y hxy => ?_) (fun x y hxy => ?_) x y ⟨hx, hy⟩ · rw [hxy.left, hxy.right, LGame.leftMoves_ofSets, LGame.leftMoves_ofSets] refine ⟨(Equiv.Set.singleton.{0} x).trans (Equiv.Set.singleton.{0} y).symm, fun i => ?_⟩ obtain ⟨x, rfl⟩ := i simpa [Equiv.Set.singleton] using hxy · rw [hxy.left, hxy.right, LGame.rightMoves_ofSets, LGame.rightMoves_ofSets] refine ⟨(Equiv.Set.singleton.{0} x).trans (Equiv.Set.singleton.{0} y).symm, fun i => ?_⟩ obtain ⟨x, rfl⟩ := i simpa [Equiv.Set.singleton] using hxy
Can't you do this more directly by defining r a b as a = x and b = y?
Violeta Hernández (Jul 28 2025 at 09:25):
@Aaron Liu I think all the stuff you did with reachable can be done much more simply using Relation.ReflTransGen.
Violeta Hernández (Jul 28 2025 at 09:26):
I just PR'd the theorem that subpositions of an IGame are a small set in #159. As a corollary, we can do this:
private def T : Type v :=
{a : α // Relation.ReflTransGen (fun x y ↦ x ∈ leftMoves y ∪ rightMoves y) a init}
private local instance small_T : Small.{u} (T leftMoves rightMoves init) :=
small_setOf_reflTransGen' ..
Violeta Hernández (Jul 28 2025 at 09:35):
I'll rebuild the coinduction principle with this, I think it should be quite a bit easier
Violeta Hernández (Jul 28 2025 at 12:14):
This is going well. I'll continue golfing tomorrow.
Aaron Liu (Jul 28 2025 at 12:15):
are you done for today?
Violeta Hernández (Jul 28 2025 at 12:23):
Yeah
Violeta Hernández (Jul 28 2025 at 23:29):
@Aaron Liu Hi! Just finished refactoring your PR. I think I did a pretty decent job at cleaning up the definition of corecursion; I also added some basic results on on, off, and dud to test out our new definitions.
https://github.com/vihdzp/combinatorial-games/pull/160
Aaron Liu (Jul 28 2025 at 23:30):
I guess if you do it for me then I don't have to do it myself
Aaron Liu (Jul 28 2025 at 23:30):
thanks
Aaron Liu (Jul 28 2025 at 23:31):
it feels like my PRs are only there so you can golf them and open another one
Violeta Hernández (Jul 28 2025 at 23:31):
I think we make a good team
Violeta Hernández (Jul 28 2025 at 23:34):
Aaron Liu said:
So after I make loopy games, what API should I provide which uniquely characterizes them? I'm thinking of providing a destructor and some corecursion, but this is not sufficient since you can prove an isomorphism but not the fact that it's unique.
It's a bit late, but I think I have an answer to this:
LGame.leftMovesLGame.rightMovesLGame.instSmallLeftMovesLGame.instSmallRightMovesLGame.ofSetsLGame.leftMoves_ofSetsLGame.rightMoves_ofSetsLGame.eq_of_bisimLGame.corecLGame.leftMoves_corecLGame.rightMoves_corec
Violeta Hernández (Jul 28 2025 at 23:36):
And perhaps LGame.hom_unique too, I'm not sure if it can be proven from the others
Aaron Liu (Jul 28 2025 at 23:36):
It can probably be proved by bisimulation
Aaron Liu (Jul 28 2025 at 23:37):
bisimulation is what you get from constructing approximations that agree and hom_unique is what category theory says you should get for a final coalgebra
Aaron Liu (Jul 28 2025 at 23:39):
the hom in the theorem name refers to coalgebra homomorphisms
Violeta Hernández (Jul 28 2025 at 23:39):
I figured as much, as soon as I saw the compositions in the theorem statement I knew there was some commutative diagram at play
Aaron Liu (Jul 28 2025 at 23:40):
I considered making them the applied version but decided against it
Violeta Hernández (Jul 28 2025 at 23:41):
I like the unapplied version better, aesthetically speaking
Violeta Hernández (Jul 28 2025 at 23:41):
If we need the applied version we can add it later
Violeta Hernández (Jul 28 2025 at 23:48):
Well, we've discussed a lot about the implementation details of loopy games, but what about their actual theory? What can we do with our new toy?
Aaron Liu (Jul 28 2025 at 23:50):
we can start by defining addition corecursively
Aaron Liu (Jul 28 2025 at 23:50):
or maybe the map from games to loopy games
Aaron Liu (Jul 28 2025 at 23:50):
show that they commute
Violeta Hernández (Jul 28 2025 at 23:52):
Here's a rough to-do from things I have in mind
- Define other basic games like
0and1 - Define the embedding
IGame ↪ LGame - Define negation and addition of loopy games
- Define... multiplication? Can you do that? Does it satisfy anything interesting?
- Define outcome classes
- Build some "real life" games like Tic-tac-toe or chess; you can't really define them within
IGamebecause there's ties, but I heardudcan be used to represent a tied state?
Violeta Hernández (Jul 29 2025 at 02:12):
@Aaron Liu I think the bisimulation principle you defined might not be the most general one. What do you think about this?
theorem eq_of_bisim (r : LGame → LGame → Prop)
(hl : ∀ x y, r x y → ∃ s : Set (LGame × LGame),
Prod.fst '' s = x.leftMoves ∧ Prod.snd '' s = y.leftMoves ∧ ∀ z ∈ s, r z.1 z.2)
(hr : ∀ x y, r x y → ∃ t : Set (LGame × LGame),
Prod.fst '' t = x.rightMoves ∧ Prod.snd '' t = y.rightMoves ∧ ∀ z ∈ t, r z.1 z.2)
(x y : LGame.{u}) (hxy : r x y) : x = y := sorry
Violeta Hernández (Jul 29 2025 at 02:15):
This generalizes the principle stated with equivalences, since the pairings specified by s and t are not a priori one to one. For instance, to prove that a + dud = dud, you need to use the bisimulation r x y ↔ (∃ b, x = b + dud) ∧ y = dud. Then assuming r x y, the set s is x.leftMoves ×ˢ {dud} and the set t is x.rightMoves ×ˢ {dud}. You don't know that these are singleton sets until you prove the theorem!
Violeta Hernández (Jul 29 2025 at 03:17):
Oh and also we don't have to deal with equivalences on subtypes, thank goodness
Violeta Hernández (Jul 29 2025 at 03:18):
PGame PTSD
Violeta Hernández (Jul 29 2025 at 03:22):
I replaced the old form of bisimulation by this. I'm not a fan of having complicated theorems that end up being weaker than other theorems in nontrivial ways. (Not fun being halfway through a proof when you realize you're using the wrong result!)
Violeta Hernández (Jul 29 2025 at 03:45):
I feel like working on loopy arithmetic
Violeta Hernández (Jul 29 2025 at 03:45):
I find this immensely satisfying:
instance : Neg LGame where
neg := corec rightMoves leftMoves
Violeta Hernández (Jul 29 2025 at 05:16):
Hmm, I think I've hit somewhat of a wall. How do you prove this?
instance : Add LGame where
add x y := corec
(fun x ↦ (fun y ↦ (y, x.2)) '' leftMoves x.1 ∪ (fun y ↦ (x.1, y)) '' leftMoves x.2)
(fun x ↦ (fun y ↦ (y, x.2)) '' rightMoves x.1 ∪ (fun y ↦ (x.1, y)) '' rightMoves x.2)
(x, y)
theorem corec_add_corec
{leftMovesα rightMovesα : α → Set α} {leftMovesβ rightMovesβ : β → Set β}
[∀ x, Small.{u} (leftMovesα x)] [∀ x, Small.{u} (rightMovesα x)]
[∀ x, Small.{u} (leftMovesβ x)] [∀ x, Small.{u} (rightMovesβ x)] (initα : α) (initβ : β) :
corec leftMovesα rightMovesα initα + corec leftMovesβ rightMovesβ initβ =
corec
(fun x ↦ (fun y ↦ (y, x.2)) '' leftMovesα x.1 ∪ (fun y ↦ (x.1, y)) '' leftMovesβ x.2)
(fun x ↦ (fun y ↦ (y, x.2)) '' rightMovesα x.1 ∪ (fun y ↦ (x.1, y)) '' rightMovesβ x.2)
(initα, initβ) := by
sorry
Violeta Hernández (Jul 29 2025 at 05:17):
The analogous theorem on negation was really easy, it was an immediate application of corec_comp_hom
Violeta Hernández (Jul 29 2025 at 05:17):
I'm not really sure how to do this one, though
Violeta Hernández (Jul 29 2025 at 05:30):
It's probably doable with a bisimulation but I'd love if there was a more direct argument through the universal property
Aaron Liu (Jul 29 2025 at 10:01):
Violeta Hernández said:
This generalizes the principle stated with equivalences, since the pairings specified by
sandtare not a priori one to one. For instance, to prove thata + dud = dud, you need to use the bisimulationr x y ↔ (∃ b, x = b + dud) ∧ y = dud. Then assumingr x y, the setsisx.leftMoves ×ˢ {dud}and the settisx.rightMoves ×ˢ {dud}. You don't know that these are singleton sets until you prove the theorem!
This is great but I was having trouble proving this version
Aaron Liu (Jul 29 2025 at 10:02):
Violeta Hernández said:
I'm not really sure how to do this one, though
Just ext, their left and right moves are the same
Aaron Liu (Jul 29 2025 at 10:07):
Hmm I'm not actually 100% sure that that works
Aaron Liu (Jul 29 2025 at 10:19):
oh I think this is a case of corec_comp_hom where you set (f := Prod.map (corec leftMovesα rightMovesα) (corec leftMovesβ rightMovesβ))
Aaron Liu (Jul 29 2025 at 10:30):
theorem corec_add_corec
{leftMovesα rightMovesα : α → Set α} {leftMovesβ rightMovesβ : β → Set β}
[∀ x, Small.{u} (leftMovesα x)] [∀ x, Small.{u} (rightMovesα x)]
[∀ x, Small.{u} (leftMovesβ x)] [∀ x, Small.{u} (rightMovesβ x)] (initα : α) (initβ : β) :
corec leftMovesα rightMovesα initα + corec leftMovesβ rightMovesβ initβ =
corec
(fun x ↦ (fun y ↦ (y, x.2)) '' leftMovesα x.1 ∪ (fun y ↦ (x.1, y)) '' leftMovesβ x.2)
(fun x ↦ (fun y ↦ (y, x.2)) '' rightMovesα x.1 ∪ (fun y ↦ (x.1, y)) '' rightMovesβ x.2)
(initα, initβ) := by
refine congrFun (corec_comp_hom
(f := Prod.map (corec leftMovesα rightMovesα) (corec leftMovesβ rightMovesβ))
_ _ _ _ ?_ ?_) (initα, initβ)
· refine funext fun ⟨a, b⟩ => ?_
simp [Set.image_image, Set.image_union, leftMoves_corec]
· refine funext fun ⟨a, b⟩ => ?_
simp [Set.image_image, Set.image_union, rightMoves_corec]
Aaron Liu (Jul 29 2025 at 10:36):
Aaron Liu said:
Violeta Hernández said:
This generalizes the principle stated with equivalences, since the pairings specified by
sandtare not a priori one to one. For instance, to prove thata + dud = dud, you need to use the bisimulationr x y ↔ (∃ b, x = b + dud) ∧ y = dud. Then assumingr x y, the setsisx.leftMoves ×ˢ {dud}and the settisx.rightMoves ×ˢ {dud}. You don't know that these are singleton sets until you prove the theorem!This is great but I was having trouble proving this version
I just realized it's actually very easy to prove this version and I was just being stupid
Violeta Hernández (Jul 29 2025 at 10:36):
Aaron Liu said:
Violeta Hernández said:
This generalizes the principle stated with equivalences, since the pairings specified by
sandtare not a priori one to one. For instance, to prove thata + dud = dud, you need to use the bisimulationr x y ↔ (∃ b, x = b + dud) ∧ y = dud. Then assumingr x y, the setsisx.leftMoves ×ˢ {dud}and the settisx.rightMoves ×ˢ {dud}. You don't know that these are singleton sets until you prove the theorem!This is great but I was having trouble proving this version
You can prove the equivalence version in terms of the more general principle I gave, the sets s and t you take are range fun x => (x, el x) and range fun x => (x, er x)
Violeta Hernández (Jul 29 2025 at 10:37):
But given how much I was able to golf using the non-equiv version I really doubt we want the equiv version
Aaron Liu (Jul 29 2025 at 10:37):
The reason I didn't use the non-equiv version is that I thought it was very hard to prove
Aaron Liu (Jul 29 2025 at 10:38):
it turns out it's just not hard at all
Violeta Hernández (Jul 29 2025 at 10:38):
Yeah, the proof mostly boils down to unfolding Functor.Liftr
Aaron Liu (Jul 29 2025 at 11:03):
Could the bisimulation theorem be stated using docs#Relator.BiTotal
Violeta Hernández (Jul 30 2025 at 09:54):
It could. A bisimulation is relation when r x y implies that r is bitotal when restricted to the left resp. right options of x and y.
Violeta Hernández (Jul 30 2025 at 09:56):
That needlessly introduces subtypes though, so I'm not sure it's better than what I wrote down.
Violeta Hernández (Jul 30 2025 at 09:56):
It might be worth adding that to the docstring, though.
Violeta Hernández (Jul 30 2025 at 09:58):
Actually, it's a bit more subtle than that. A bisimulation requires that some subrelation of r is bitotal on the left resp. right sets, not necessarily that the entire thing is.
Bhavik Mehta (Jul 30 2025 at 10:31):
just mentioning , in case there's opportunity for code reuse here
Violeta Hernández (Jul 30 2025 at 10:33):
The problem isn't really defining bisimulations, but rather how to best convert the horrible functorial mess docs#Functor.Liftr gives you into something nice for our specific case.
Violeta Hernández (Jul 31 2025 at 00:33):
Bisimulation proofs are so funny, as soon as you finish them you go back and realize "hey the clever bisimulation i built is literally just an equality"
Violeta Hernández (Jul 31 2025 at 00:49):
I've been pretty successful with my lemma eq_of_bisim. Check this out:
private theorem add_assoc' (x y z : LGame) : x + y + z = x + (y + z) := by
apply eq_of_bisim (fun x y ↦ ∃ a b c, x = a + b + c ∧ y = a + (b + c)) ?_ ⟨x, y, z, rfl, rfl⟩
rintro _ _ ⟨a, b, c, rfl, rfl⟩
refine
⟨⟨(fun x ↦ (x + b + c, x + (b + c))) '' a.leftMoves ∪
(fun x ↦ (a + x + c, a + (x + c))) '' b.leftMoves ∪
(fun x ↦ (a + b + x, a + (b + x))) '' c.leftMoves, ?_, ?_⟩,
⟨(fun x ↦ (x + b + c, x + (b + c))) '' a.rightMoves ∪
(fun x ↦ (a + x + c, a + (x + c))) '' b.rightMoves ∪
(fun x ↦ (a + b + x, a + (b + x))) '' c.rightMoves, ?_, ?_⟩⟩
all_goals aesop (add simp [image_union])
Violeta Hernández (Jul 31 2025 at 00:58):
I managed to prove the SubtractionCommMonoidstructure on LGame, and prove a few simple equations like on + off = dud and x + dud = dud.
https://github.com/vihdzp/combinatorial-games/pull/166
Aaron Liu (Jul 31 2025 at 01:15):
If you think of equality in QPF.Cofix as a coinductive type with destructor
theorem eq_liftr_dest {F : Type u → Type u} [QPF F] {x y : QPF.Cofix F} (h : x = y) :
Functor.Liftr Eq x.dest y.dest := by
subst h
refine ⟨(fun x => ⟨(x, x), rfl⟩) <$> x.dest, ?_, ?_⟩
· simp_rw [← QPF.comp_map, Function.comp_def]
exact QPF.id_map x.dest
· simp_rw [← QPF.comp_map, Function.comp_def]
exact QPF.id_map x.dest
then bisimulation provides a corecursion principle for equality
Violeta Hernández (Jul 31 2025 at 01:33):
Interesting! I hadn't really understood that link between corecursion and bisimulations.
Violeta Hernández (Jul 31 2025 at 02:38):
Is it possible to define multiplication of loopy games?
Violeta Hernández (Jul 31 2025 at 02:39):
In some "natural" way that gives you the theorem toLGame (x * y) = toLGame x * toLGame y
Violeta Hernández (Jul 31 2025 at 02:41):
Or perhaps more explicitly. Say that you have two games corec α lα rα x and corec β lβ rβ y. If you wanted to multiply them as corec γ lγ rγ z, what would the type γ be?
Violeta Hernández (Jul 31 2025 at 02:41):
For addition, γ = α × β, representing that a state in the addition is a pair of states, one in α, one in β.
Violeta Hernández (Jul 31 2025 at 02:45):
I guess you could define γ as some appropriate inductive type. Wouldn't be pretty, though.
Violeta Hernández (Jul 31 2025 at 02:52):
Oh actually, it might work to define γ = Multiset (α × β).
Violeta Hernández (Jul 31 2025 at 03:54):
No, it's worse than that.
Violeta Hernández (Jul 31 2025 at 07:28):
Correct me if I'm wrong but, every subposition of x * y looks like a finite sum of ± a_i * b_i for a_i a subposition of x and b_i a subposition of y
Violeta Hernández (Jul 31 2025 at 07:50):
(deleted)
Violeta Hernández (Jul 31 2025 at 08:00):
So my idea is, define x * y as corec (Multiset (Bool × LGame × LGame)) lγ rγ (true, x, y), where lγ and rγ are the bespoke functions that assign a multiset (interpreted as a game Σ ±aᵢ × bᵢ) to the other multisets for their left and right options.
Violeta Hernández (Jul 31 2025 at 08:00):
This is an awful definition, and it will be awful to prove that the left and right moves of x * y are what we expect, but hopefully once we do that we never have to work with the multiset definition again.
Violeta Hernández (Jul 31 2025 at 08:00):
Lmk if you have simpler ideas
Violeta Hernández (Jul 31 2025 at 19:51):
I honestly doubt there's much to say about multiplication of loopy games in general. But at the very least we should still be able to prove the basic stuff like 0 * x = 0 and 1 * x = 1.
Violeta Hernández (Aug 01 2025 at 01:44):
Question, is this true?
proof_wanted eq_iGame_of_not_subposition {x : LGame}
(h : ∀ y, Subposition y x, ¬ Subposition y y) : ∃ z : IGame, x = z.toLGame
Aaron Liu (Aug 01 2025 at 01:59):
no
Violeta Hernández (Aug 01 2025 at 02:00):
Is anything similar to this true?
Violeta Hernández (Aug 01 2025 at 02:00):
Basically I'm trying to find the conditions for a loopy game to not be loopy
Aaron Liu (Aug 01 2025 at 02:00):
the condition is that the subpositions are well founded
Violeta Hernández (Aug 01 2025 at 02:01):
Oh, you're right. It is possible for a game to go on forever without repeating subpositions
Aaron Liu (Aug 01 2025 at 02:01):
Violeta Hernández said:
Is anything similar to this true?
the counterexample is but two sided
Aaron Liu (Aug 01 2025 at 02:09):
I think it's the same as Acc Subposition x
Violeta Hernández (Aug 01 2025 at 02:16):
That's a very elegant way to put it. So IGame is in a sense the subtype of accessible LGames
Violeta Hernández (Aug 01 2025 at 19:08):
I gave a definition of multiplication. But I'm having a really hard time proving corec_mul_corec and leftMoves_mul: https://github.com/vihdzp/combinatorial-games/blob/a60241d87afbc07c390c9b0b19ba38937e766c28/CombinatorialGames/Game/Loopy/Basic.lean#L576
Aaron Liu (Aug 01 2025 at 19:09):
it can't be that hard right
Aaron Liu (Aug 01 2025 at 19:09):
I'll try
Violeta Hernández (Aug 01 2025 at 19:10):
It may be that I'm not using the simplest/nicest definition for it, if you can find any improvements please push them to the branch!
Aaron Liu (Aug 01 2025 at 19:11):
I have an aversion to changing definitions even if it makes everything better
Aaron Liu (Aug 01 2025 at 19:12):
also I'm 90% sure that I don't have push access
Violeta Hernández (Aug 01 2025 at 19:12):
Oh yeah you're right
Violeta Hernández (Aug 01 2025 at 19:12):
uhhh you can clone my branch ig
Aaron Liu (Aug 01 2025 at 19:42):
try doing something simpler first
Aaron Liu (Aug 01 2025 at 19:42):
can you prove leftMovesAux commutes with the map
Violeta Hernández (Aug 01 2025 at 19:43):
Can you write the type signature for that?
Violeta Hernández (Aug 01 2025 at 19:44):
It would look like
leftMovesAux lα rα lβ rβ (map f g x) = map f g '' leftMovesAux lα' rα' lβ' rβ' x
but I don't know what the relation between lα and lα' etc would be
Aaron Liu (Aug 01 2025 at 19:45):
the goal is to show that leftMovesAux is a natural transformation of functors
Aaron Liu (Aug 01 2025 at 19:46):
I'm currently trying to figure out what that translates to in Lean
Aaron Liu (Aug 01 2025 at 20:02):
I think this is correct
/--
If `f : α₁ → α₂` and `g : β₁ → β₂` are morphisms in the category of `GameFunctor`-coalgebras then
the diagram
```
leftMovesAux
(Bool × α₁ × β₁) → Set (MulTy α₁ β₁)
(id, f, g) ↓ ↓ Set.image (MulTy.map f g)
(Bool × α₂ × β₂) → Set (MulTy α₂ β₂)
leftMovesAux
```
commutes
-/
theorem leftMovesAux_comp_prodMap {α₁ α₂ β₁ β₂ : Type*}
(leftMovesα₁ rightMovesα₁ : α₁ → Set α₁)
(leftMovesβ₁ rightMovesβ₁ : β₁ → Set β₁)
(leftMovesα₂ rightMovesα₂ : α₂ → Set α₂)
(leftMovesβ₂ rightMovesβ₂ : β₂ → Set β₂)
(f : α₁ → α₂) (g : β₁ → β₂)
(fl : leftMovesα₂ ∘ f = Set.image f ∘ leftMovesα₁)
(fr : rightMovesα₂ ∘ f = Set.image f ∘ rightMovesα₁)
(gl : leftMovesβ₂ ∘ g = Set.image g ∘ leftMovesβ₁)
(gr : rightMovesβ₂ ∘ g = Set.image g ∘ rightMovesβ₁) :
MulTy.leftMovesAux leftMovesα₂ rightMovesα₂ leftMovesβ₂ rightMovesβ₂ ∘
Prod.map id (Prod.map f g) =
Set.image (MulTy.map f g) ∘
MulTy.leftMovesAux leftMovesα₁ rightMovesα₁ leftMovesβ₁ rightMovesβ₁ := by
sorry
Violeta Hernández (Aug 01 2025 at 20:02):
wow, and yikes
Aaron Liu (Aug 01 2025 at 20:03):
the diagram makes it easy to understand
Violeta Hernández (Aug 01 2025 at 20:03):
That's appreciated
Violeta Hernández (Aug 01 2025 at 20:22):
Here you go!
@[simp]
theorem map_mulOption (b x y) :
map f g (mulOption b x y) = mulOption b (Prod.map f g x) (Prod.map f g y) := by
simp [mulOption, map, Prod.map]
set_option maxHeartBeats 500000 in
theorem leftMovesAux'_comp_prodMap
(hlf : leftMovesα₂ ∘ f = Set.image f ∘ leftMovesα₁)
(hrf : rightMovesα₂ ∘ f = Set.image f ∘ rightMovesα₁)
(hlg : leftMovesβ₂ ∘ g = Set.image g ∘ leftMovesβ₁)
(hrg : rightMovesβ₂ ∘ g = Set.image g ∘ rightMovesβ₁) :
MulTy.leftMovesAux' leftMovesα₂ rightMovesα₂ leftMovesβ₂ rightMovesβ₂ ∘
Prod.map id (Prod.map f g) =
Set.image (MulTy.map f g) ∘
MulTy.leftMovesAux' leftMovesα₁ rightMovesα₁ leftMovesβ₁ rightMovesβ₁ := by
simp_rw [funext_iff, Function.comp_apply, leftMovesAux'] at *
aesop
Violeta Hernández (Aug 01 2025 at 20:23):
God bless aesop
Aaron Liu (Aug 01 2025 at 20:23):
500000???
Aaron Liu (Aug 01 2025 at 20:23):
wow
Violeta Hernández (Aug 01 2025 at 20:23):
that's for the primed version but the unprimed version should follow directly from this and the analogous result for right moves
Aaron Liu (Aug 01 2025 at 20:24):
great
Violeta Hernández (Aug 01 2025 at 20:24):
Aaron Liu said:
500000???
Not even the highest number in our repo :sob:
Aaron Liu (Aug 01 2025 at 20:26):
It seems you have already done the appropriate square for split so next is leftMoves and rightMoves
Violeta Hernández (Aug 01 2025 at 20:39):
theorem leftMovesAux_comp_prodMap
(hlf : leftMovesα₂ ∘ f = Set.image f ∘ leftMovesα₁)
(hrf : rightMovesα₂ ∘ f = Set.image f ∘ rightMovesα₁)
(hlg : leftMovesβ₂ ∘ g = Set.image g ∘ leftMovesβ₁)
(hrg : rightMovesβ₂ ∘ g = Set.image g ∘ rightMovesβ₁) :
MulTy.leftMovesAux leftMovesα₂ rightMovesα₂ leftMovesβ₂ rightMovesβ₂ ∘
Prod.map id (Prod.map f g) =
image (MulTy.map f g) ∘
MulTy.leftMovesAux leftMovesα₁ rightMovesα₁ leftMovesβ₁ rightMovesβ₁ := by
apply funext
rintro ⟨(_ | _), x⟩
· exact congrFun (rightMovesAux'_comp_prodMap hlf hrf hlg hrg) (false, x)
· exact congrFun (leftMovesAux'_comp_prodMap hlf hrf hlg hrg) (true, x)
Violeta Hernández (Aug 01 2025 at 20:39):
Yeah this one was easy
Violeta Hernández (Aug 01 2025 at 20:39):
I should be able to prove the actual result now maybe?
Aaron Liu (Aug 01 2025 at 20:39):
that's the idea
Violeta Hernández (Aug 01 2025 at 21:00):
Awesome!
theorem leftMoves_comp_map
(hlf : leftMovesα₂ ∘ f = Set.image f ∘ leftMovesα₁)
(hrf : rightMovesα₂ ∘ f = Set.image f ∘ rightMovesα₁)
(hlg : leftMovesβ₂ ∘ g = Set.image g ∘ leftMovesβ₁)
(hrg : rightMovesβ₂ ∘ g = Set.image g ∘ rightMovesβ₁) :
leftMoves leftMovesα₂ rightMovesα₂ leftMovesβ₂ rightMovesβ₂ ∘ map f g =
image (map f g) ∘ leftMoves leftMovesα₁ rightMovesα₁ leftMovesβ₁ rightMovesβ₁ := by
apply funext fun x ↦ ?_
simp_rw [Function.comp_apply, leftMoves, image_sUnion, split_map, Finset.coe_image, image_image,
Prod.map_fst, Prod.map_snd, ← Function.comp_apply (g := Prod.map id (Prod.map f g)),
leftMovesAux_comp_prodMap hlf hrf hlg hrg]
aesop
Violeta Hernández (Aug 01 2025 at 21:03):
Just managed to prove corec_mul_corec too! Pushed it to the branch.
Aaron Liu (Aug 01 2025 at 21:03):
yay
Aaron Liu (Aug 01 2025 at 21:03):
leftMoves_mul should follow then I think
Violeta Hernández (Aug 01 2025 at 21:04):
On a related note. I'm a bit unsure about how much of MulTy should be private API and how much should be public.
Aaron Liu (Aug 01 2025 at 21:29):
I think to prove leftMoves_mul you might have to use hom_unique again
Violeta Hernández (Aug 01 2025 at 21:29):
Oh oops turns out my definition of leftMovesAuxis wrong :frown:
Aaron Liu (Aug 01 2025 at 21:29):
:(
Violeta Hernández (Aug 01 2025 at 21:30):
It should be
def leftMovesAux (x : Bool × α × β) : Set (MulTy α β) :=
x.1.rec
(rightMovesAux' leftMovesα rightMovesα leftMovesβ rightMovesβ (!x.1, x.2))
(leftMovesAux' leftMovesα rightMovesα leftMovesβ rightMovesβ x)
(the (!x.1, x.2) bit was just an x previously)
Violeta Hernández (Aug 01 2025 at 21:31):
Since left moves of -x * y are negatives of right moves of x * y
Violeta Hernández (Aug 01 2025 at 21:31):
thankfully all the other proofs compile with minimal changes
Aaron Liu (Aug 01 2025 at 21:31):
I'm glad I don't have to think about this
Violeta Hernández (Aug 01 2025 at 21:33):
I'm thinking about the privacy thing still. What do you think about making leftMovesAux' private, but making leftMovesAux public (with a better name) as well as leftMoves?
Aaron Liu (Aug 01 2025 at 21:33):
no idea
Violeta Hernández (Aug 01 2025 at 21:38):
Specifically, we'd make this public API:
theorem leftMovesSingle_def (x : Bool × α × β) :
leftMovesSingle leftMovesα rightMovesα leftMovesβ rightMovesβ x = x.1.rec
((mulOption (!x.1) x.2) ''
(leftMovesα x.2.1 ×ˢ rightMovesβ x.2.2 ∪ rightMovesα x.2.1 ×ˢ leftMovesβ x.2.2))
(mulOption x.1 x.2 ''
(leftMovesα x.2.1 ×ˢ leftMovesβ x.2.2 ∪ rightMovesα x.2.1 ×ˢ rightMovesβ x.2.2)) := by
obtain ⟨(_ | _), _⟩ := x <;> rfl
Violeta Hernández (Aug 01 2025 at 21:39):
(leftMovesAux → leftMovesSingle)
Aaron Liu (Aug 01 2025 at 21:40):
sure I guess
Violeta Hernández (Aug 01 2025 at 21:40):
Wait I'm realizing something isn't
Violeta Hernández said:
Since left moves of
-x * yare negatives of right moves ofx * y
oh also nvm about this I just realized that I don't have to negate x.1 like that since that's already the case where it's false
Violeta Hernández (Aug 01 2025 at 21:40):
you can tell I barely have a grasp on this
Aaron Liu (Aug 01 2025 at 21:41):
I might understand it if you translate it into category theory
Violeta Hernández (Aug 01 2025 at 21:42):
I've no idea how to do that
Violeta Hernández (Aug 01 2025 at 21:44):
I think this makes more sense if you think of MulTy α β as the type of formal signed sums of products in α × β
Aaron Liu (Aug 01 2025 at 21:45):
so it's the free something on α × β
Violeta Hernández (Aug 01 2025 at 21:46):
Multiset α is the free commutative monoid on α I believe
Violeta Hernández (Aug 01 2025 at 21:46):
so MulTy α β is the free commutative monoid on Bool × α × β
Aaron Liu (Aug 01 2025 at 21:47):
...sure
Violeta Hernández (Aug 01 2025 at 22:02):
Aaron Liu said:
I think to prove
leftMoves_mulyou might have to usehom_uniqueagain
Yeah, I think I need to prove that mulOption commutes with corec
Violeta Hernández (Aug 01 2025 at 22:08):
More specifically this (I think h is redundant here)
image.png
Violeta Hernández (Aug 01 2025 at 22:27):
Actually this looks more like corec_comp_hom.
Violeta Hernández (Aug 01 2025 at 22:41):
In either case I have no idea what the map is supposed to be
Violeta Hernández (Aug 01 2025 at 22:41):
help welcome
Violeta Hernández (Aug 01 2025 at 23:12):
Oh NVM I get it
Violeta Hernández (Aug 01 2025 at 23:13):
I have to define the function MulTy LGame LGame -> LGame in the obvious way
Violeta Hernández (Aug 01 2025 at 23:13):
And use that in corec_comp_hom
Violeta Hernández (Aug 02 2025 at 01:35):
I've kept deliberating on this. I really think MulTy should be public API. There's a sort of naturality here: addition of two games is described by a pair of states from both games. Multiplication of two games is described by a signed finite sum of pairs of states of both games.
Violeta Hernández (Aug 02 2025 at 01:35):
Following this, we could add ConcreteGame (α × β) and ConcreteGame (MulTy α β) instances.
Violeta Hernández (Aug 02 2025 at 01:58):
Speaking of. I was thinking about splitting ConcreteGame into ConcreteLGame and ConcreteIGame, with the latter extending the former with proofs that relLeft and relRight are wf.
Aaron Liu (Aug 02 2025 at 02:16):
that sounds not bad
Violeta Hernández (Aug 02 2025 at 04:01):
I managed to find a bit of a simplification in definitions. Turns out you don't need MulTy.split, you can just do this:
def leftMoves (x : MulTy α β) : Set (MulTy α β) :=
⋃ y ∈ x, (x.erase y + ·) '' leftMovesSingle leftMovesα rightMovesα leftMovesβ rightMovesβ y
Violeta Hernández (Aug 02 2025 at 04:13):
I like how when you simplify a definition the proofs follow neatly
Violeta Hernández (Aug 03 2025 at 01:37):
I'm still breaking my head over this. I haven't been able to prove the main theorem about MulTy yet (corec of x : MulTy α β equals Σ ±aᵢ * bᵢ), but I feel like I'm making progress.
Violeta Hernández (Aug 03 2025 at 01:38):
Can't continue today though, and probably can't continue tomorrow either, but I'll see what I can do over the week.
Aaron Liu (Aug 03 2025 at 01:38):
good luck
Violeta Hernández (Aug 04 2025 at 22:26):
Call an LGame short if Finite {y | Subposition y x}. Is the product of two short LGames short?
Violeta Hernández (Aug 04 2025 at 22:27):
At face value it seems like the answer should be no, because MulTy α β is always infinite for nonempty types. But it is possible for a lot of the states to collapse. For instance, I'm pretty sure dud * x = dud for all x ≠ 0.
Violeta Hernández (Aug 04 2025 at 22:28):
At the very least, I'm not aware of counterexamples.
Aaron Liu (Aug 04 2025 at 23:05):
Consider and
Aaron Liu (Aug 04 2025 at 23:07):
then I claim the product has infinitely many distinct subpositions
Violeta Hernández (Aug 05 2025 at 00:35):
What does tis stand for?
Aaron Liu (Aug 05 2025 at 00:37):
Aaron Liu (Aug 05 2025 at 00:37):
from Winning Ways volume 2
Violeta Hernández (Aug 05 2025 at 00:37):
Oh nice
Violeta Hernández (Aug 05 2025 at 00:38):
Someone should make a small PR adding those two
Violeta Hernández (Aug 05 2025 at 00:38):
(deleted)
Violeta Hernández (Aug 05 2025 at 00:39):
So clearly tisn= -tis and thus tis * tisn = -(tis * tis)
Aaron Liu (Aug 05 2025 at 00:40):
I have to go for now
Violeta Hernández (Aug 05 2025 at 00:40):
The only left move of tis * tis is -3 • (tis * tis), it has no right moves
Violeta Hernández (Aug 05 2025 at 00:41):
It doesn't seem completely implausible that tis * tis = tis
Violeta Hernández (Aug 05 2025 at 00:42):
I gtg too, I'll come back to this
Violeta Hernández (Aug 05 2025 at 01:06):
So then 3 • (tis * tis) = {2 • (tis * tis) - 3 • (tis * tis) | }
Violeta Hernández (Aug 05 2025 at 01:06):
Interesting
Aaron Liu (Aug 05 2025 at 01:07):
I'm back
Violeta Hernández (Aug 05 2025 at 01:07):
So am I
Aaron Liu (Aug 05 2025 at 01:08):
welcome back
Aaron Liu (Aug 05 2025 at 01:09):
so the infinitely many subpositions I was talking about is 3 ^ n • (tis * tis) for every odd n
Aaron Liu (Aug 05 2025 at 01:10):
clearly these are all distinct
Aaron Liu (Aug 05 2025 at 01:10):
and they also all arise as subpositions
Violeta Hernández (Aug 05 2025 at 01:10):
It's not clear to me
Violeta Hernández (Aug 05 2025 at 01:10):
There's idempotent games such as dud
Aaron Liu (Aug 05 2025 at 01:11):
count how many moves you can make in a row as left before you run out
Violeta Hernández (Aug 05 2025 at 01:13):
The only left option of 3 ^ n • (tis * tis) is (3 ^ n - 1) • (tis * tis) - 3 • (tis * tis)
Violeta Hernández (Aug 05 2025 at 01:14):
The left options of that I'm not too eager to calculate
Aaron Liu (Aug 05 2025 at 01:14):
what are the left options of - 3 • (tis * tis)?
Violeta Hernández (Aug 05 2025 at 01:14):
Oh, there's none
Violeta Hernández (Aug 05 2025 at 01:14):
I see
Violeta Hernández (Aug 05 2025 at 01:15):
So you can do 3 ^ n consecutive moves as left, no less and no more
Violeta Hernández (Aug 05 2025 at 01:16):
And obviously this value is well-defined so that shows all these games are distinct
Violeta Hernández (Aug 05 2025 at 01:16):
Oh and also obviously they're all achievable as subpositions
Violeta Hernández (Aug 05 2025 at 01:17):
You just keep dividing tis * tis into -3 copies of itself over and over
Violeta Hernández (Aug 05 2025 at 01:17):
Awesome!
Violeta Hernández (Aug 05 2025 at 01:17):
I'd love to see this in the counterexample folder
Violeta Hernández (Aug 05 2025 at 01:17):
...as soon as I actually PR multiplication of loopy games, that is
Violeta Hernández (Aug 05 2025 at 07:23):
I did it!
image.png
Violeta Hernández (Aug 05 2025 at 07:24):
Only took 400 lines of code + two maxHeartbeats 1000000
Violeta Hernández (Aug 05 2025 at 07:24):
(feel inclined to tag Bhavik just to annoy him :stuck_out_tongue:)
Violeta Hernández (Aug 05 2025 at 07:42):
https://github.com/vihdzp/combinatorial-games/pull/168
Violeta Hernández (Aug 05 2025 at 09:56):
I made a best effort to organize and explain my code, though it's still a bit rough around the edges. I'd appreciate if you guys could take a good look.
Violeta Hernández (Aug 05 2025 at 10:12):
Violeta Hernández said:
Someone should make a small PR adding those two
I truly can't help myself
https://github.com/vihdzp/combinatorial-games/pull/171/files
Violeta Hernández (Aug 06 2025 at 07:49):
@Tristan Figueroa-Reid asking you as someone more familiar with literature. How should we define the preorder of loopy games?
Violeta Hernández (Aug 06 2025 at 07:51):
I've been reluctant about adding definitions of outcomes to IGame, since they're entirely determined by the order relations of a game with 0. But in the loopy case this isn't true anymore.
Tristan Figueroa-Reid (Aug 06 2025 at 07:51):
No clue for that one. Outcome classes are the only way I know how to reason with ordering for loopy games.
Tristan Figueroa-Reid (Aug 06 2025 at 07:54):
You could also try defining ordering with stoppers? Though it seems more intuitive to define ordering with outcome classes, then formalize stoppers and reason with those after the fact.
Violeta Hernández (Aug 06 2025 at 07:57):
In any case we need some way to state what the outcome of a game is.
Violeta Hernández (Aug 06 2025 at 08:00):
Tristan Figueroa-Reid said:
You could also try defining ordering with stoppers? Though it seems more intuitive to define ordering with outcome classes, then formalize stoppers and reason with those after the fact.
How do you define ordering with stoppers?
Tristan Figueroa-Reid (Aug 06 2025 at 08:10):
I think this trick was done in Aaron Siegel's masters thesis (this might not be terminating on long loopy games?), but for determining G <= H, you can show that right can not force left to run out of games in H - G by traversing H - G starting at its start vertex:
When traversing a vertex V with player P, you store a counter c to V for the number of its outgoing edges that are not marked as wins for the opposing player of P. If c is 0 then V is a win, and all nodes connecting incoming edges get their counters decremented (where this c = 0 check gets propagated backwards). Then you repeat this for all outgoing neighbors of V.
(I'll just try to find the reference but I don't have a copy of Aaron Siegel's thesis anymore?)
Tristan Figueroa-Reid (Aug 06 2025 at 08:15):
(Then, since this works only for stoppers, you can decompose a game into its sides and do this check for both sides when either one of G and H aren't loopy)
Violeta Hernández (Aug 06 2025 at 08:15):
But not all loopy games have such a decomposition, do they?
Violeta Hernández (Aug 06 2025 at 08:16):
I recall seeing that in another of Siegel's papers
Tristan Figueroa-Reid (Aug 06 2025 at 08:17):
Right - Bach's Carousel can't be decomposed with sides.
Violeta Hernández (Aug 06 2025 at 08:18):
So I think the question now is how to formalize outcomes.
Violeta Hernández (Aug 06 2025 at 08:18):
Do we need to first formalize strategies? That sounds somewhat painful.
Tristan Figueroa-Reid (Aug 06 2025 at 08:20):
I don't think so? At least for non-tied loopy games, you can say that a loopy game is winnable by Left or Right recursively by looking for empty sets of right or left moves respectively (then for games with no empty right/left sets, looking at the right/left options and checking if any of it's left/right options are in the desired outcome class).
Violeta Hernández (Aug 06 2025 at 08:22):
Well that's the thing, we can't define things recursively if the games aren't well-founded
Tristan Figueroa-Reid (Aug 06 2025 at 08:23):
Perhaps if there exists some amount of fuel which can be used to traverse these games?
Violeta Hernández (Aug 06 2025 at 08:36):
Actually. What even is the mathematical definition of saying that a loopy game is tied, say, if left starts?
Tristan Figueroa-Reid (Aug 06 2025 at 08:39):
Good question! I've always understood it as games that don't belong in any of the winning outcome classes, which is why I suggested using fuel.
Violeta Hernández (Aug 06 2025 at 08:40):
I'm unsure if using fuel works even in the case of an infinite well-founded game.
Violeta Hernández (Aug 06 2025 at 08:41):
At least I can't figure out how to complete the idea. Is it "a game is tied iff it isn't won by left or right in n moves, for any n"?
Violeta Hernández (Aug 06 2025 at 08:51):
Take some positive game x such that for every n, there exists some right option of x that cannot end in less than n turns. This definitely exists, I think every positive irrational should work.
Then, if right starts, the game x is won by left, but it's not necessarily won by left in n turns, for any given n.
Violeta Hernández (Aug 06 2025 at 08:55):
From working with loopy vs. well-founded games, I get the impression that the theory of loopy games is somehow more global - for instance, to define a general loopy game, you need to use the corecursor, which requires you to build the entire game graph. By contrast, the theory of well-founded games is more local. To define a game you only need consider its immediate options.
Violeta Hernández (Aug 06 2025 at 08:56):
From that perspective, it seems quite reasonable that strategies would be required to talk about loopy games, even if they can be omitted from a treatment of well-founded games.
Violeta Hernández (Aug 06 2025 at 09:08):
Though perhaps a better question to ask: how do you define survivability?
Violeta Hernández (Aug 06 2025 at 09:09):
I think that is what we need to formalize. A strategy is just a proof of survivability on a well-founded game.
Violeta Hernández (Aug 06 2025 at 09:13):
Informally a strategy is a sort of recursive object. In a strategy for left, for every subposition reachable by right under the strategy, it assigns a left move. (Which in particular implies a left move exists, under these circumstances)
Violeta Hernández (Aug 06 2025 at 09:13):
As to how to translate this into Lean...
Violeta Hernández (Aug 06 2025 at 09:15):
I feel like this might be a coinductive type? Not sure.
Tristan Figueroa-Reid (Aug 06 2025 at 09:15):
You can (terribly, with dependent types and all) formalize strategies by making a function from an LGame subtype of all possible options of x to its subpositions. However, I don't think having a definition of strategy is the biggest concern?
Violeta Hernández (Aug 06 2025 at 09:15):
Well, I don't see a way to define survivability otherwise.
Tristan Figueroa-Reid (Aug 06 2025 at 09:16):
How would survivability be defined?
Violeta Hernández (Aug 06 2025 at 09:16):
Left survives a loopy game if there exists a strategy for him.
Violeta Hernández (Aug 06 2025 at 09:19):
Tristan Figueroa-Reid said:
You can (terribly, with dependent types and all) formalize strategies by making a function from an LGame subtype of all possible options of
xto its subpositions. However, I don't think having a definition of strategy is the biggest concern?
That's not quite it, I think. What would be a left strategy for 1? It exists, of course. But there's no function that maps all subpositions of 1 to a left option of them, because 0 has no left option.
Violeta Hernández (Aug 06 2025 at 09:19):
Perhaps I misunderstand what you mean?
Tristan Figueroa-Reid (Aug 06 2025 at 09:19):
You're right, it should be a partial function.
Violeta Hernández (Aug 06 2025 at 09:20):
Oh, that might actually work
Tristan Figueroa-Reid (Aug 06 2025 at 09:20):
Violeta Hernández said:
Left survives a loopy game if there exists a strategy for him.
Strategies can exist without them being good ones.
Violeta Hernández (Aug 06 2025 at 09:20):
So a partial function such that if you follow it, the output is never none?
Tristan Figueroa-Reid (Aug 06 2025 at 09:21):
Violeta Hernández said:
So a partial function such that if you follow it, the output is never
none?
Hm, that might work for properly loopy games, but I believe that fails for well-founded games.
Violeta Hernández (Aug 06 2025 at 09:21):
Tristan Figueroa-Reid said:
Violeta Hernández said:
Left survives a loopy game if there exists a strategy for him.
Strategies can exist without them being good ones.
I'm not saying Left has to win. To survive a game is simply not to lose.
Violeta Hernández (Aug 06 2025 at 09:22):
Tristan Figueroa-Reid said:
Violeta Hernández said:
So a partial function such that if you follow it, the output is never
none?Hm, that might work for properly loopy games, but I believe that fails for well-founded games.
Take 1. A strategy for left is the function 1 -> 0 and 0 -> none. If you follow the strategy, after any right move, the function doesn't return none, because there's no move right can make if you follow the strategy!
Tristan Figueroa-Reid (Aug 06 2025 at 09:22):
Hm. How about a Left strategy on -1?
Violeta Hernández (Aug 06 2025 at 09:23):
That won't exist, because -1 is won by right.
Tristan Figueroa-Reid (Aug 06 2025 at 09:23):
Right. Left's strategy on {-1|}?
Violeta Hernández (Aug 06 2025 at 09:26):
That's equivalent to 0, right? So if left starts he doesn't have a strategy, because right wins.
Violeta Hernández (Aug 06 2025 at 09:32):
Let me formalize what I mean. Let x be a game. Let f be a partial function mapping subpositions of x to either left options of them, or none. Define a (left) "history" of f as a list of pairs p_i = (a_i, b_i), where a_0 = x, b_i is some arbitrary right move of a_i, and a_(i+1) = f(b_i). Then f is a strategy for left (starting first) if for every history of f ending in (a_i, b_i), we have f(b_i) ≠ none.
Violeta Hernández (Aug 06 2025 at 09:34):
A strategy for left starting second can be defined similarly. It can also be defined as a function from right options of x to strategies of left starting first.
Tristan Figueroa-Reid (Aug 06 2025 at 09:37):
This definition seems to work :+1:
Violeta Hernández (Aug 06 2025 at 10:12):
Here's a stub:
import CombinatorialGames.Game.Loopy.Basic
import CombinatorialGames.Game.IGame
namespace LGame
/-- A sequence of moves following a left strategy `f`. -/
def LeftHistory (f : LGame → Option LGame) (l : List (LGame × LGame)) : Prop :=
match l with
| [] => False
| [(a, b)] => b ∈ a.rightMoves
| (a, b) :: (c, d) :: l => b ∈ a.rightMoves ∧ a ∈ f d ∧ LeftHistory f ((c, d) :: l)
theorem LeftHistory.ne_nil {f : LGame → Option LGame} {l : List (LGame × LGame)}
(hl : LeftHistory f l) : l ≠ [] := by
cases l with
| nil => contradiction
| cons a l => exact List.cons_ne_nil a l
/-- A strategy for Left, when they go second. -/
structure LeftStrategy (x : LGame) (f : LGame → Option LGame) : Prop where
forall_mem_leftMoves {y z : LGame} (h : z ∈ f y) : z ∈ y.leftMoves
exists_forall_leftHistory {l : List (LGame × LGame)}
(hl : LeftHistory f l) (hl₀ : (l.getLast hl.ne_nil).1 = x) : (f (l.head hl.ne_nil).2).isSome
proof_wanted zero_le_of_leftStrategy {x : IGame} {f : LGame → Option LGame}
(hf : LeftStrategy x.toLGame f) : 0 ≤ x
end LGame
Violeta Hernández (Aug 06 2025 at 10:12):
Does this seem reasonable?
Aaron Liu (Aug 06 2025 at 10:12):
I have a different idea
Violeta Hernández (Aug 06 2025 at 10:12):
I'm all ears
Aaron Liu (Aug 06 2025 at 10:13):
Instead of asking left to win in n moves for some n
Aaron Liu (Aug 06 2025 at 10:14):
wait what was the goal again?
Tristan Figueroa-Reid (Aug 06 2025 at 10:14):
To define a Preorder on LGame
Aaron Liu (Aug 06 2025 at 10:14):
what's the order?
Violeta Hernández (Aug 06 2025 at 10:15):
The one from Misere games
Violeta Hernández (Aug 06 2025 at 10:15):
x ≤ y when o(x + z) ≤ o(y + z) for all z
Aaron Liu (Aug 06 2025 at 10:15):
so the goal is to define outcomes
Violeta Hernández (Aug 06 2025 at 10:16):
Indeed
Violeta Hernández (Aug 06 2025 at 10:16):
I was trying to define what it meant for a player to survive a game
Violeta Hernández (Aug 06 2025 at 10:16):
I don't see a better way to do it than strategies
Aaron Liu (Aug 06 2025 at 10:17):
Define survive as "not die"?
Aaron Liu (Aug 06 2025 at 10:17):
I have an idea that I think works for dying on stoppers
Tristan Figueroa-Reid (Aug 06 2025 at 10:18):
Stoppers are fine, but I think it was brought up earlier that we can't decompose all LGames to their sides.
Aaron Liu (Aug 06 2025 at 10:18):
yeah not all loopy games have stopper sides
Aaron Liu (Aug 06 2025 at 10:18):
I think
Violeta Hernández (Aug 06 2025 at 10:18):
(yet another thing to add to the counterexample folder)
Aaron Liu (Aug 06 2025 at 10:18):
I'm a bit hazy on everything
Tristan Figueroa-Reid (Aug 06 2025 at 10:19):
I think we had an explicit counterexample of Bach's Carousel. That's going to be fun to encode in LGame.
Violeta Hernández (Aug 06 2025 at 10:24):
So yeah, the best idea I've got is to define strategies
Aaron Liu (Aug 06 2025 at 10:25):
Here's my spelling of "left kills right when right goes first on x":
∃ f : LGame.{u} → (Option Ordinal.{u} × Option Ordinal.{u}),
(f x).2.isSome ∧ ∀ : LGame.{u} y,
(∀ o ∈ (f y).1, ∃ z ∈ y.leftMoves, ∃ o' ∈ (f z).2, o' < o) ∧
(∀ o ∈ (f y).2, ∀ z ∈ y.rightMoves, ∃ o' ∈ (f z).1, o' ≤ o)
Aaron Liu (Aug 06 2025 at 10:26):
this also works with slight modifications for left going first, or for right killing left
Violeta Hernández (Aug 06 2025 at 10:30):
I think I sort of get it. f x is supposed to represent the number of left/right moves remaining?
Aaron Liu (Aug 06 2025 at 10:30):
yes
Violeta Hernández (Aug 06 2025 at 10:31):
∀ y, (∀ o ∈ y.1, ∃ z ∈ y.leftMoves, ∃ o' ∈ z.2, o' < o)
I have no idea what the types are supposed to be here.
Aaron Liu (Aug 06 2025 at 10:32):
sorry, editited
Aaron Liu (Aug 06 2025 at 10:32):
I think it should be good now
Aaron Liu (Aug 06 2025 at 10:33):
I was typing this without an infoview
Violeta Hernández (Aug 06 2025 at 10:38):
Shouldn't it be o ≤ o' in the last inequality?
Aaron Liu (Aug 06 2025 at 10:38):
explain?
Violeta Hernández (Aug 06 2025 at 10:38):
Every right move of y should be no better for right than for left
Aaron Liu (Aug 06 2025 at 10:38):
I don't get it
Violeta Hernández (Aug 06 2025 at 10:39):
o is the value for right, o' is the value for left
Violeta Hernández (Aug 06 2025 at 10:39):
I think
Aaron Liu (Aug 06 2025 at 10:39):
not true
Aaron Liu (Aug 06 2025 at 10:39):
the ordinals need to decrease to ensure termination
Violeta Hernández (Aug 06 2025 at 10:41):
What do the ordinals represent, though?
Aaron Liu (Aug 06 2025 at 10:42):
they're a death clock
Aaron Liu (Aug 06 2025 at 10:42):
they keep decreasing, and when they hit zero then right loses
Violeta Hernández (Aug 06 2025 at 10:43):
And left never loses before right does, because...?
Tristan Figueroa-Reid (Aug 06 2025 at 10:43):
Violeta Hernández said:
I think I sort of get it.
f xis supposed to represent the number of left/right moves remaining?
How is that consistent with the above statement? I don't see how this + the ordinals decreasing + o ≤ o' being wrong can all coexist.
Aaron Liu (Aug 06 2025 at 10:44):
Violeta Hernández said:
And left never loses before right does, because...?
because left always has another move (∀ o ∈ (f y).1, ∃ z ∈ y.leftMoves, ...)
Aaron Liu (Aug 06 2025 at 10:44):
when left moves the death clock goes down
Violeta Hernández (Aug 06 2025 at 10:48):
Oh, so f x is really just a single clock, and you read the entry according to which player is playing. If left plays, he has some move decreasing the clock. If right plays, none of her moves can increase the clock.
Aaron Liu (Aug 06 2025 at 10:49):
It's not side weird two sided clock
Aaron Liu (Aug 06 2025 at 10:49):
The only reason I needed two sides is that you can arrive at a game with either player to move
Violeta Hernández (Aug 06 2025 at 10:50):
Why do we need the (f x).2.isSome condition, though?
Aaron Liu (Aug 06 2025 at 10:51):
So you can't just supply fun _ => (none, none)
Aaron Liu (Aug 06 2025 at 10:51):
This satisfies all the other conditions
Violeta Hernández (Aug 06 2025 at 10:53):
Oh, of course. Your clock needs to at the very least consider every position that right could get to under this strategy.
Violeta Hernández (Aug 06 2025 at 10:53):
And for that it's sufficient that it considers x itself.
Violeta Hernández (Aug 06 2025 at 10:55):
This is a very elegant definition. I wouldn't mind just going ahead and calling this a strategy.
Aaron Liu (Aug 06 2025 at 10:55):
I guess it is a strategy
Violeta Hernández (Aug 06 2025 at 10:56):
(I'm a bit confused though, are strategies in the literature supposed to win? Or just survive?)
Aaron Liu (Aug 06 2025 at 10:56):
It's a strategy to win
Aaron Liu (Aug 06 2025 at 10:56):
You can probably cook up a different definition for a strategy to survive
Tristan Figueroa-Reid (Aug 06 2025 at 10:56):
Strategies in the literature can either win or survive (you can define a variant for either one, so this construction seems natural).
Violeta Hernández (Aug 06 2025 at 10:57):
Aaron Liu said:
You can probably cook up a different definition for a strategy to survive
Do we need to? Left survives if there exists no winning strategy for right.
Aaron Liu (Aug 06 2025 at 10:57):
We don't need to
Aaron Liu (Aug 06 2025 at 10:58):
But we can do it
Aaron Liu (Aug 06 2025 at 10:58):
It could be useful
Tristan Figueroa-Reid (Aug 06 2025 at 10:59):
Defining (a surviving strategy) using the existing winning strategy, then adding a theorem that defines it directly seems nicer from a trusted code perspective.
Aaron Liu (Aug 06 2025 at 10:59):
Surviving strategies are simpler
Aaron Liu (Aug 06 2025 at 11:00):
They don't have ordinals since there's no need to force an end
Aaron Liu (Aug 06 2025 at 11:00):
It's just a set of games satisfying some properties
Tristan Figueroa-Reid (Aug 06 2025 at 11:01):
Then perhaps we can define winning strategies from surviving strategies? A winning strategy for Left is when Left survives but Right doesn't.
Aaron Liu (Aug 06 2025 at 11:01):
Well, two sets
Aaron Liu (Aug 06 2025 at 11:02):
You can also drop the second set
Aaron Liu (Aug 06 2025 at 11:02):
Just like you can define LE without LF
Violeta Hernández (Aug 06 2025 at 11:02):
Can you type that down?
Aaron Liu (Aug 06 2025 at 11:04):
"right goes first and left survives"
∃ s : Set LGame.{u}, x ∈ s ∧ ∀ y ∈ s, ∀ z ∈ y.rightMoves, ∃ r ∈ z.leftMoves, r ∈ s
Aaron Liu (Aug 06 2025 at 11:05):
you can probably define it coinductively
Aaron Liu (Aug 06 2025 at 11:05):
wasn't there some sort of greatest_fixpoint thing?
Violeta Hernández (Aug 06 2025 at 11:06):
Coinductive types are primitives in Coq, not in Lean
Aaron Liu (Aug 06 2025 at 11:07):
Violeta Hernández (Aug 06 2025 at 11:09):
This is news to me
Aaron Liu (Aug 06 2025 at 11:09):
I haven't touched Coq (or Rocq) so I wouldn't be confusing it
Violeta Hernández (Aug 06 2025 at 11:10):
Aaron Liu said:
"right goes first and left survives"
∃ s : Set LGame.{u}, x ∈ s ∧ ∀ y ∈ s, ∀ z ∈ y.rightMoves, ∃ r ∈ z.leftMoves, r ∈ s
Oh, I get it. s is precisely the set in which the partial function of the strategy is defined.
Violeta Hernández (Aug 06 2025 at 11:13):
This does smell coinductive to me, but I don't know what we'd gain out of defining it as a coinductive predicate.
Aaron Liu (Aug 06 2025 at 11:13):
The coinduction?
Aaron Liu (Aug 06 2025 at 11:13):
I guess we already get the coinduction
Violeta Hernández (Aug 06 2025 at 11:16):
What would coinduction say here?
Aaron Liu (Aug 06 2025 at 11:17):
It's exactly the Exists.intro on the definition I put above
Violeta Hernández (Aug 06 2025 at 11:17):
Hm yeah that makes sense
Aaron Liu (Aug 06 2025 at 11:21):
I've just realized the construction I have is essentially what Tarski's fixpoint theorem tells you
Aaron Liu (Aug 06 2025 at 11:21):
All that's left is to prove it's a fixpoint
Violeta Hernández (Aug 06 2025 at 11:22):
Not sure what you mean by that.
Violeta Hernández (Aug 06 2025 at 11:24):
This is genuinely so nice.
def LeftStrategy (x : LGame) (s : Set LGame) : Prop :=
x ∈ s ∧ ∀ y ∈ s, ∀ z ∈ y.rightMoves, ∃ r ∈ z.leftMoves, r ∈ s
def LeftSurvivesSnd (x : LGame) : Prop :=
∃ s, LeftStrategy x s
def LeftSurvivesFst (x : LGame) : Prop :=
∃ y ∈ x.leftMoves, LeftSurvivesSnd y
Violeta Hernández (Aug 06 2025 at 11:26):
I think we should now consider API.
Violeta Hernández (Aug 06 2025 at 11:27):
I think something important is to link these definitions to IGame. Prove that LeftSurvivesSnd (toLGame x) ↔ 0 ≤ x and LeftSurvivesFst (toIGame x) ↔ 0 ⧏ x.
Violeta Hernández (Aug 06 2025 at 11:27):
We should also prove that either left or right survives any given game.
Tristan Figueroa-Reid (Aug 06 2025 at 11:31):
Violeta Hernández said:
I think we should now consider API.
I think I am in favor of just defining a bunch of explicitly-named definitions for surviving/outcomes, in hopes that Left/RightSurvivesFst/Snd, and their tie and win variants eventually become either notation or auxiliary.
Violeta Hernández (Aug 06 2025 at 11:33):
I'm unsure if we need tie/win variants.
Violeta Hernández (Aug 06 2025 at 11:37):
I think we could introduce notation on top of this. Define leftOutcome x as either of L, D, or R. Likewise for rightOutcome. And then we get our outcome function by taking the product of these linear orders.
Violeta Hernández (Aug 06 2025 at 11:38):
So maybe instead of LeftSurvivesSnd x we simply write D ≤ rightOutcome x.
Violeta Hernández (Aug 06 2025 at 11:39):
Yeah, the API should definitely be written in terms of this outcome function.
Tristan Figueroa-Reid (Aug 06 2025 at 11:40):
Hm, it being given a linear order feels a little confusing when trying to differentiate between left and right outcomes.
Tristan Figueroa-Reid (Aug 06 2025 at 11:41):
(Though I suppose this is fine since the scope of left/rightOutcome would be near auxiliary)
Aaron Liu (Aug 06 2025 at 11:41):
what instead then?
Tristan Figueroa-Reid (Aug 06 2025 at 11:42):
Aaron Liu said:
what instead then?
Two distinct enums for each left and right outcome with explicit conversions between the two, which would make ordering quickly canonical for each one. Again, though, that is a lot for this small-scoped construction.
Violeta Hernández (Aug 06 2025 at 11:42):
Should left/rightOutcome be auxiliary? I feel like in a sense they're more natural definitions than outcome itself.
Tristan Figueroa-Reid (Aug 06 2025 at 11:43):
They are more natural, but I was under the impression that outcome itself was very auxiliary.
Violeta Hernández (Aug 06 2025 at 11:43):
I'm pretty sure outcome is what this entire theory is all about!
Tristan Figueroa-Reid (Aug 06 2025 at 11:43):
It is! Though outside of outcome itself, LGame would mainly be using outcome through *LGame ordering?
Violeta Hernández (Aug 06 2025 at 11:45):
I don't think so. In the case of IGame the four outcomes very neatly correspond to the four possible order relations a game can have with zero. With LGame there's nine outcomes, and there's no such known characterization.
Tristan Figueroa-Reid (Aug 06 2025 at 11:52):
Tristan Figueroa-Reid said:
Aaron Liu said:
what instead then?
Two distinct enums for each
leftandrightoutcome with explicit conversions between the two, which would make ordering quickly canonical for each one. Again, though, that is a lot for this small-scoped construction.
I think we get a slightly nicer API if we have two distinct enums, and our final outcome enum be the 9 possible outcomes, where we can then define the usual lattice on loopy outcomes.
Violeta Hernández (Aug 06 2025 at 11:53):
I don't really see what we get out of duplicating our outcome order.
Tristan Figueroa-Reid (Aug 06 2025 at 11:54):
Violeta Hernández said:
I don't really see what we get out of duplicating our outcome order.
Just to clearly type-differentiate between the left/right outcomes. I don't really see a benefit when we don't differentiate.
Violeta Hernández (Aug 06 2025 at 11:55):
Presumably there's little ambiguity as is? leftOutcome and rightOutcome tell you outright. As for the outcome function, the left entry would be leftOutcome, and the right entry would be rightOutcome. You don't even need a mnemonic for that one.
Aaron Liu (Aug 06 2025 at 11:56):
Not like intent and extent
Tristan Figueroa-Reid (Aug 06 2025 at 11:58):
Violeta Hernández said:
Presumably there's little ambiguity as is?
leftOutcomeandrightOutcometell you outright. As for theoutcomefunction, the left entry would beleftOutcome, and the right entry would berightOutcome. You don't even need a mnemonic for that one.
I forgot that we'll know the origin of our data as we're working in a theorem prover. :+1:
Violeta Hernández (Aug 06 2025 at 12:00):
I'm thinking, LeftStrategy and RightStrategy can be public API. We'll then keep everything private until we can define the outcome function, and state all the results on outcomes in terms of it.
Violeta Hernández (Aug 06 2025 at 12:03):
So, how do we actually prove this?
private theorem left_or_right_survive_left (x : LGame) :
(∃ y ∈ x.leftMoves, ∃ s, LeftStrategy y s) ∨ ∃ s, RightStrategy x s := by
sorry
Aaron Liu (Aug 06 2025 at 12:05):
It's probably probable
Aaron Liu (Aug 06 2025 at 12:05):
*provable
Aaron Liu (Aug 06 2025 at 12:06):
If you put it on a branch I can attempt it today
Violeta Hernández (Aug 06 2025 at 12:06):
Way ahead of you
https://github.com/vihdzp/combinatorial-games/tree/vi.outcome
Violeta Hernández (Aug 06 2025 at 12:09):
I gtg, I'll come back to this in a few hours
Aaron Liu (Aug 06 2025 at 13:48):
I have an idea
Violeta Hernández (Aug 06 2025 at 13:51):
I'm interested
Violeta Hernández (Aug 06 2025 at 13:52):
I was thinking about this as well
Violeta Hernández (Aug 06 2025 at 13:52):
My class doesn't start for another eight minutes
Aaron Liu (Aug 06 2025 at 13:52):
you have class???
Violeta Hernández (Aug 06 2025 at 13:52):
I'm a uni student yeah
Aaron Liu (Aug 06 2025 at 13:59):
this is more complicated than I thought
Violeta Hernández (Aug 06 2025 at 13:59):
There's probably some auxiliary theorems you need to prove first
Violeta Hernández (Aug 06 2025 at 14:00):
Like, if there's a LeftStrategy for x, there's one for some left move of every right move of x.
Violeta Hernández (Aug 06 2025 at 14:13):
In fact this should be an iff
Aaron Liu (Aug 06 2025 at 14:13):
Here's what I'm thinking about
noncomputable
def stoppingTimeLeftApprox : (LGame.{u} → WithTop NatOrdinal.{u} × WithTop NatOrdinal.{u}) →o
(LGame.{u} → WithTop NatOrdinal.{u} × WithTop NatOrdinal.{u}) where
toFun f x := (⨅ i ∈ x.leftMoves, (f i).2, ⨆ i ∈ x.rightMoves, (f i).1 + 1)
monotone' := sorry
noncomputable
def stoppingTimeRightApprox : (LGame.{u} → WithTop NatOrdinal.{u} × WithTop NatOrdinal.{u}) →o
(LGame.{u} → WithTop NatOrdinal.{u} × WithTop NatOrdinal.{u}) where
toFun f x := (⨆ i ∈ x.leftMoves, (f i).2 + 1, ⨅ i ∈ x.rightMoves, (f i).1)
monotone' := sorry
theorem consistent_le_closed_left {x y}
(hx : x ≤ stoppingTimeLeftApprox x)
(hy : stoppingTimeLeftApprox y ≤ y) :
x ≤ y := sorry
theorem consistent_le_closed_right {x y}
(hx : x ≤ stoppingTimeRightApprox x)
(hy : stoppingTimeRightApprox y ≤ y) :
x ≤ y := sorry
theorem lfp_eq_gfp_left : stoppingTimeLeftApprox.lfp = stoppingTimeLeftApprox.gfp := sorry
theorem lfp_eq_gfp_right : stoppingTimeRightApprox.lfp = stoppingTimeRightApprox.gfp := sorry
/--
The ordinal corresponding to the time it takes for left to force a win going first,
counted in right moves.
-/
noncomputable
def stoppingTimeLeftLeft (x : LGame.{u}) : WithTop NatOrdinal.{u} :=
(stoppingTimeLeftApprox.lfp x).1
/--
The ordinal corresponding to the time it takes for left to force a win going second,
counted in right moves.
-/
noncomputable
def stoppingTimeLeftRight (x : LGame.{u}) : WithTop NatOrdinal.{u} :=
(stoppingTimeLeftApprox.lfp x).2
/--
The ordinal corresponding to the time it takes for right to force a win going second,
counted in left moves.
-/
noncomputable
def stoppingTimeRightLeft (x : LGame.{u}) : WithTop NatOrdinal.{u} :=
(stoppingTimeRightApprox.lfp x).1
/--
The ordinal corresponding to the time it takes for right to force a win going first,
counted in left moves.
-/
noncomputable
def stoppingTimeRightRight (x : LGame.{u}) : WithTop NatOrdinal.{u} :=
(stoppingTimeRightApprox.lfp x).2
Aaron Liu (Aug 06 2025 at 14:15):
I hope all the sorrys are actually true
Aaron Liu (Aug 06 2025 at 14:15):
I didn't check that carefully
Junyan Xu (Aug 06 2025 at 14:15):
For loopy games you can still define winning positions as an inductive predicate. These should be contained in the surviving positions (i.e. those contained in some strategy). The right surviving positions should be the complement of the left winning positions.
Aaron Liu (Aug 06 2025 at 14:16):
My reasoning is: if you prove the least and greatest fixpoints are equal, then you can use both induction and coinduction
Violeta Hernández (Aug 06 2025 at 14:26):
Aaron Liu said:
I hope all the
sorrys are actually true
I've no idea
Violeta Hernández (Aug 06 2025 at 14:27):
I admit I liked the other definition we had better.
Aaron Liu (Aug 06 2025 at 14:27):
but you get an ordinal out this way
Aaron Liu (Aug 06 2025 at 14:27):
that's more information than just "yes" or "no"
Violeta Hernández (Aug 06 2025 at 14:43):
These are nice definitions, assuming they work. But perhaps they should be a separate development from strategies.
Aaron Liu (Aug 06 2025 at 14:43):
maybe
Aaron Liu (Aug 06 2025 at 14:43):
but you can directly extract a strategy from them
Violeta Hernández (Aug 06 2025 at 15:08):
Violeta Hernández said:
Way ahead of you
https://github.com/vihdzp/combinatorial-games/tree/vi.outcome
I think I know how to prove this. You need two lemmas:
- A game has a left strategy iff every right move has a left move with a left strategy. Simple enough.
- If every right move of a game has no right strategy, then the game has a left strategy. The left strategy is really silly: for every single game with no right strategy, take an arbitrary left move such that every right move has no right strategy.
Violeta Hernández (Aug 06 2025 at 15:19):
Aaron Liu said:
but you can directly extract a strategy from them
The more I think about it the more I like your idea. It feels weird to use this to define strategies rather than the much easier set definition. But it might be worth putting this in its own file.
Violeta Hernández (Aug 06 2025 at 15:19):
Surely, surely this definition exists somewhere in the literature?
Violeta Hernández (Aug 06 2025 at 15:19):
(again, assuming it works)
Aaron Liu (Aug 06 2025 at 15:20):
I don't read enough literature to know if this exists somewhere
Violeta Hernández (Aug 06 2025 at 15:20):
Glad we have our resident reader @Tristan Figueroa-Reid
Junyan Xu (Aug 06 2025 at 17:25):
It's interesting that Lean doesn't accept this inductive predicate:
inductive IsLeftWinning : LGame → Prop
| intro (x : LGame) : (∀ y ∈ x.rightMoves, ∃ z ∈ y.leftMoves, IsLeftWinning z) → IsLeftWinning x
/- (kernel) invalid nested inductive datatype 'Exists', nested inductive datatypes parameters cannot contain local variables. -/
In principle this is similar to docs#Acc. Let me think of another way to construct it ...
Junyan Xu (Aug 06 2025 at 17:34):
If I break it down then it works:
inductive IsLeftWinning : LGame → Prop where
| intro (x : LGame) (f : ∀ y ∈ x.rightMoves, LGame) :
(∀ y (hy : y ∈ x.rightMoves), f y hy ∈ y.leftMoves) →
(∀ y (hy : y ∈ x.rightMoves), IsLeftWinning (f y hy)) → IsLeftWinning x
Violeta Hernández (Aug 06 2025 at 17:39):
Interesting! Yeah, this seems like it should work.
Violeta Hernández (Aug 06 2025 at 17:41):
I think it's by far the most natural definition in this thread.
Violeta Hernández (Aug 06 2025 at 17:46):
Presumably we can prove whatever the inductive principle for the exists version should be.
Aaron Liu (Aug 06 2025 at 17:46):
what about the coinductive principle
Violeta Hernández (Aug 06 2025 at 17:47):
I don't think this definition is supposed to be coinductive?
Aaron Liu (Aug 06 2025 at 17:47):
there is an inductive definition and a coinductive definition and they are complements
Aaron Liu (Aug 06 2025 at 17:47):
I think
Violeta Hernández (Aug 06 2025 at 17:51):
I think the coinductive version might correspond to games that left survives
Aaron Liu (Aug 06 2025 at 17:52):
yes that's what I was thinking I think
Aaron Liu (Aug 06 2025 at 17:52):
not sure anymore
Violeta Hernández (Aug 06 2025 at 17:56):
I mean, as nice as it would be to have this inductive/coinductive duality
Violeta Hernández (Aug 06 2025 at 17:57):
We really only need one or the other
Aaron Liu (Aug 06 2025 at 17:57):
I'm trying to prove it right now
Violeta Hernández (Aug 06 2025 at 17:57):
Oh nice
Junyan Xu (Aug 06 2025 at 18:10):
proven half of it
image.png
Junyan Xu (Aug 06 2025 at 19:03):
I changed the definition of LeftStrategy in order to state leftStrategy_iUnion to prove a key lemma isLeftSurviving_iff_forall:
image.png
Aaron Liu (Aug 06 2025 at 20:55):
I did it!
Aaron Liu (Aug 06 2025 at 20:55):
now for the easy part
Junyan Xu (Aug 06 2025 at 20:56):
I just pushed a proof of left_or_right_survive_left at this branch
Aaron Liu (Aug 06 2025 at 20:58):
I proved stoppingTimeLeftApprox.lfp = stoppingTimeLeftApprox.gfp
Aaron Liu (Aug 06 2025 at 21:59):
I might make a PR
Violeta Hernández (Aug 07 2025 at 01:06):
I'm not opposed to having these definitions in the repo, because they do seem like pretty natural notions (they straightforwardly generalize the number of moves left in a short game). But I would prefer to first see them used somewhere else. That would then inform the names of definitions and the results to be proven.
Aaron Liu (Aug 07 2025 at 01:08):
I have never seen these used anywhere I saw them once in The Search for the Longest Infinite Chess Game
Violeta Hernández (Aug 07 2025 at 01:09):
Holy **** you're right they're exactly that!
Violeta Hernández (Aug 07 2025 at 01:12):
Well actually I'm not sure if they're exactly that
Aaron Liu (Aug 07 2025 at 01:13):
might be off by one in some places
Violeta Hernández (Aug 07 2025 at 01:15):
https://neugierde.github.io/cantors-attic/Omega_one_chess
Violeta Hernández (Aug 07 2025 at 01:15):
(deleted)
Violeta Hernández (Aug 07 2025 at 01:18):
There's a mismatch in definitions. You measure the left stopping time in terms of right moves, while that definition measures it in terms of left moves
Aaron Liu (Aug 07 2025 at 01:19):
yes that is true
Aaron Liu (Aug 07 2025 at 01:19):
off by one in some places
Violeta Hernández (Aug 07 2025 at 01:19):
And now we play the game of figuring out which definition is better
Aaron Liu (Aug 07 2025 at 01:20):
I will of course advocate for my definition
Aaron Liu (Aug 07 2025 at 01:20):
its range is Set.univ
Violeta Hernández (Aug 07 2025 at 01:21):
Does the other one not have that property?
Aaron Liu (Aug 07 2025 at 01:21):
it doesn't hit ω
Aaron Liu (Aug 07 2025 at 01:22):
if white is to move
Aaron Liu (Aug 07 2025 at 01:22):
white to move misses all the limit ordinals
Aaron Liu (Aug 07 2025 at 01:22):
black to move hits everything
Violeta Hernández (Aug 07 2025 at 01:23):
Huh
Violeta Hernández (Aug 07 2025 at 01:33):
To be frank, I also find "supremum of the successors" to be a more mathematically satisfying definition than "successor of the supremum".
Aaron Liu (Aug 07 2025 at 01:36):
It's an infimum though
Violeta Hernández (Aug 07 2025 at 01:36):
Huh
Aaron Liu (Aug 07 2025 at 01:36):
infimum over all possible moves of how long to kill the opponent in each position
Aaron Liu (Aug 07 2025 at 01:36):
since you always choose the best move which is the minimum time to kill
Violeta Hernández (Aug 07 2025 at 01:37):
No you wrote down supremum of the succesors
Violeta Hernández (Aug 07 2025 at 01:37):
Supremum is the union thing isn't it
Aaron Liu (Aug 07 2025 at 01:37):
that's the other one
Aaron Liu (Aug 07 2025 at 01:37):
supremum over all the positions of how long to die
Violeta Hernández (Aug 07 2025 at 01:41):
Hm yeah the thing is that the "successor of supremum" definition is the one used in the article
https://arxiv.org/pdf/1302.4377
Violeta Hernández (Aug 07 2025 at 01:42):
Do we know that these definitions are only off by one?
Aaron Liu (Aug 07 2025 at 01:43):
I know they are only off by one
Junyan Xu (Aug 07 2025 at 10:44):
Supremum of successors sound more natural as it's also used in PSet.rank.
Junyan Xu (Aug 07 2025 at 10:44):
I'm currently working on outcomes that incorporate misere play convention
image.png
Junyan Xu (Aug 07 2025 at 10:51):
Just recording that this also doesn't work with a different error message:
inductive IsLeftWinning : LGame → Prop where
| intro' (x : LGame) :
(∀ y ∈ x.rightMoves, ¬ (∀ z ∈ y.leftMoves, ¬ IsLeftWinning z)) → IsLeftWinning x
/- (kernel) arg #2 of 'LGame.IsLeftWinning.intro'' has a non positive occurrence of the datatypes being declared -/
Aaron Liu (Aug 07 2025 at 10:52):
Yeah
Aaron Liu (Aug 07 2025 at 10:53):
There are reasons
Violeta Hernández (Aug 07 2025 at 10:53):
Junyan Xu said:
I'm currently working on outcomes that incorporate misere play convention
image.png
Does the toMisere idea I had earlier not work here? Take any state without moves for some player and add a single move for that player to 0
Aaron Liu (Aug 07 2025 at 10:54):
This one happens to work out but if you modify it slightly you get a contradiction
Junyan Xu (Aug 07 2025 at 10:54):
It doesn't seem possible to make IsLeftWinning and IsRightLosing mutual recursives either.
Aaron Liu (Aug 07 2025 at 10:54):
I have something just as good
Aaron Liu (Aug 07 2025 at 10:55):
It's the stopping times I proved yesterday
Junyan Xu (Aug 07 2025 at 11:09):
Violeta Hernández said:
Does the
toMisereidea I had earlier not work here? Take any state without moves for some player and add a single move for that player to 0
Yes, I think it works and is a better solution!
Junyan Xu (Aug 07 2025 at 12:05):
It's the stopping times I proved yesterday
If you want to prove something about the "stopping time" you might start with some hydra game which should be easier to formalize than chess. And we can certainly show e.g. that the left stopping time isn't top iff IsLeftWinning.
Junyan Xu (Aug 07 2025 at 12:13):
Junyan Xu said:
It doesn't seem possible to make
IsLeftWinningandIsRightLosingmutual recursives either.
Oops I was wrong, this actually works
mutual
inductive IsLeftWinning : LGame → Prop where
| intro (x : LGame) : (∀ y ∈ x.rightMoves, IsRightLosing y) → IsLeftWinning x
inductive IsRightLosing : LGame → Prop where
| intro (x y : LGame) : y ∈ x.leftMoves → IsLeftWinning y → IsRightLosing x
end
Aaron Liu (Aug 07 2025 at 12:39):
Junyan Xu said:
It's the stopping times I proved yesterday
If you want to prove something about the "stopping time" you might start with some hydra game which should be easier to formalize than chess. And we can certainly show e.g. that the left stopping time isn't top iff
IsLeftWinning.
I proved something about the stopping times of loopy games (that you can do both induction and coinduction)
Junyan Xu (Aug 07 2025 at 14:29):
Using mutual inductives shortens the code a bit. The same definition works also for IGame and in fact for ConcreteGame as well. For IGames we should show that IsLeftSurviving is the same as IsLeftWinning, or maybe we should show this for any position such that the union of the left and right relations (or the appropriate composition of the left and right relations) is well founded on its subpositions.
Junyan Xu (Aug 07 2025 at 20:00):
Pushed a proof of theorem mem_range_toLGame_iff_acc {x : LGame} : x ∈ range IGame.toLGame ↔ Acc LGame.IsOption x
Junyan Xu (Aug 08 2025 at 14:58):
Opened PR#179 for loopy outcomes.
Junyan Xu (Aug 08 2025 at 18:26):
I'm considering dropping the IsLeft/RightSurviving definitions since they're equivalent to the negation of IsLeft/RightLosing. I think it's also more intuitive to switch left and right so that whenever a decl name mentions "left", it always means that left is going first rather than second.
Violeta Hernández (Aug 08 2025 at 22:57):
I'll check it out in a bit.
Last updated: Dec 20 2025 at 21:32 UTC