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.leftMoves
  • IGame.rightMoves
  • IGame.ext
  • IGame.IsOption
  • IGame.IsOption.of_mem_leftMoves
  • IGame.IsOption.of_mem_rightMoves
  • IGame.instSmallSubtypeIsOption
  • IGame.isOption_wf
  • IGame.ofSets
  • IGame.leftMoves_ofSets
  • IGame.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 infty is 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 IGame without PGame using 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 ENat relating n with n + 1 for n : ℕ 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 ENat relating n with n + 1 for n : ℕ 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 QPF file and added private to everything that isn't the inductive/coinductive principles, it would really cut down on noise.
  • Apparently the notion of a QPF was 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 dud\bold{dud}

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.leftMoves
  • LGame.rightMoves
  • LGame.instSmallLeftMoves
  • LGame.instSmallRightMoves
  • LGame.ofSets
  • LGame.leftMoves_ofSets
  • LGame.rightMoves_ofSets
  • LGame.eq_of_bisim
  • LGame.corec
  • LGame.leftMoves_corec
  • LGame.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 0 and 1
  • 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 IGame because there's ties, but I hear dud can 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 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!

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 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!

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 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!

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 #computer science > Bisimulation @ 💬, 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 and 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 #combinatorial-games > Loopy games @ 💬 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     (map f g x) = map f g '' leftMovesAux lα' rα' lβ' rβ' x

but I don't know what the relation between 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):

(leftMovesAuxleftMovesSingle)

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 * y are negatives of right moves of x * 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_mul you might have to use hom_unique again

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 tis={tisn}\bold{tis} = \{\bold{tisn} | \} and tisn={tis}\bold{tisn} = \{ | \bold{tis}\}

Aaron Liu (Aug 04 2025 at 23:07):

then I claim the product tistisn\bold{tis} \cdot \bold{tisn} 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):

image.png

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 x to 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 x is 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):

lean4#8097

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 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.

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? 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.

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 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

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 IsLeftWinning and IsRightLosing mutual 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