A Lean Companion to Conceptual Mathematics

Session 12: Categories of diagrams🔗

1. Dynamical systems or automata🔗

Exercise 1 (p. 161)

Suppose that {x' = \alpha^3(x)} and that {X^{↻\alpha} \xrightarrow{f} Y^{↻\beta}} is a map in 𝑺↻. Let {y = f(x)} and {y' = \beta^3(y)}. Prove that {f(x') = y'}.

Solution: Exercise 1

We proceed by repeatedly applying the property {f \circ \alpha = \beta \circ f}.

example { : SetWithEndomap} (f : ) {x x' : .carrier} {y y' : .carrier} (hx' : x' = (.toEnd .toEnd .toEnd) x) (hy : y = f.val x) (hy' : y' = (.toEnd .toEnd .toEnd) y) : f.val x' = y' := :SetWithEndomap:SetWithEndomapf: x:.carrierx':.carriery:.carriery':.carrierhx':x' = (.toEnd .toEnd .toEnd) xhy:y = f xhy':y' = (.toEnd .toEnd .toEnd) yf x' = y' :SetWithEndomap:SetWithEndomapf: x:.carrierx':.carriery:.carriery':.carrierhx':x' = (.toEnd .toEnd .toEnd) xhy:y = f xhy':y' = (.toEnd .toEnd .toEnd) yhf_comm:f .toEnd = .toEnd f := f.propertyf x' = y' :SetWithEndomap:SetWithEndomapf: x:.carrierx':.carriery:.carriery':.carrierhx':x' = (.toEnd .toEnd .toEnd) xhy:y = f xhy':y' = (.toEnd .toEnd .toEnd) yhf_comm:f .toEnd = .toEnd f := f.propertyf x' = (.toEnd .toEnd .toEnd) (f x) :SetWithEndomap:SetWithEndomapf: x:.carrierx':.carriery:.carriery':.carrierhx':x' = (.toEnd .toEnd .toEnd) xhy:y = f xhy':y' = (.toEnd .toEnd .toEnd) yhf_comm:f .toEnd = .toEnd f := f.propertyf x' = ((.toEnd .toEnd .toEnd) f) x :SetWithEndomap:SetWithEndomapf: x:.carrierx':.carriery:.carriery':.carrierhx':x' = (.toEnd .toEnd .toEnd) xhy:y = f xhy':y' = (.toEnd .toEnd .toEnd) yhf_comm:f .toEnd = .toEnd f := f.propertyf x' = (.toEnd .toEnd .toEnd f) x :SetWithEndomap:SetWithEndomapf: x:.carrierx':.carriery:.carriery':.carrierhx':x' = (.toEnd .toEnd .toEnd) xhy:y = f xhy':y' = (.toEnd .toEnd .toEnd) yhf_comm:f .toEnd = .toEnd f := f.propertyf x' = (.toEnd .toEnd f .toEnd) x :SetWithEndomap:SetWithEndomapf: x:.carrierx':.carriery:.carriery':.carrierhx':x' = (.toEnd .toEnd .toEnd) xhy:y = f xhy':y' = (.toEnd .toEnd .toEnd) yhf_comm:f .toEnd = .toEnd f := f.propertyf x' = (.toEnd (.toEnd f) .toEnd) x :SetWithEndomap:SetWithEndomapf: x:.carrierx':.carriery:.carriery':.carrierhx':x' = (.toEnd .toEnd .toEnd) xhy:y = f xhy':y' = (.toEnd .toEnd .toEnd) yhf_comm:f .toEnd = .toEnd f := f.propertyf x' = (.toEnd (f .toEnd) .toEnd) x :SetWithEndomap:SetWithEndomapf: x:.carrierx':.carriery:.carriery':.carrierhx':x' = (.toEnd .toEnd .toEnd) xhy:y = f xhy':y' = (.toEnd .toEnd .toEnd) yhf_comm:f .toEnd = .toEnd f := f.propertyf x' = (((.toEnd f) .toEnd) .toEnd) x :SetWithEndomap:SetWithEndomapf: x:.carrierx':.carriery:.carriery':.carrierhx':x' = (.toEnd .toEnd .toEnd) xhy:y = f xhy':y' = (.toEnd .toEnd .toEnd) yhf_comm:f .toEnd = .toEnd f := f.propertyf x' = (((f .toEnd) .toEnd) .toEnd) x :SetWithEndomap:SetWithEndomapf: x:.carrierx':.carriery:.carriery':.carrierhx':x' = (.toEnd .toEnd .toEnd) xhy:y = f xhy':y' = (.toEnd .toEnd .toEnd) yhf_comm:f .toEnd = .toEnd f := f.propertyf x' = (f .toEnd .toEnd .toEnd) x :SetWithEndomap:SetWithEndomapf: x:.carrierx':.carriery:.carriery':.carrierhx':x' = (.toEnd .toEnd .toEnd) xhy:y = f xhy':y' = (.toEnd .toEnd .toEnd) yhf_comm:f .toEnd = .toEnd f := f.propertyf x' = f ((.toEnd .toEnd .toEnd) x) All goals completed! 🐙
Exercise 2 (p. 162)

'With age comes stability'. In a finite dynamical system, every state eventually settles into a cycle.

...

For two units of time, x is living on the fringes, but after that he settles into an organized periodic behaviour, repeating the same routine every four units of time. What about y and z? Don’t take the title seriously; humans can change the system! This sort of thing applies to light bulbs, though. If a particular light bulb can only be lit four times before burning out, after which pressing the on—off button has no effect, draw the automaton modeling its behavior.

Solution: Exercise 2

The automaton modelling the behaviour of a light bulb that can only be lit four times before burning out may be represented as follows:

inductive BulbState | off₀ | on₁ | off₁ | on₂ | off₂ | on₃ | off₃ | on₄ | burntOut deriving Fintype, DecidableEq open BulbState def pressButton : BulbState BulbState | off₀ => on₁ -- lit 1st time | on₁ => off₁ | off₁ => on₂ -- lit 2nd time | on₂ => off₂ | off₂ => on₃ -- lit 3rd time | on₃ => off₃ | off₃ => on₄ -- lit 4th time | on₄ => burntOut | burntOut => burntOut

To verify correct operation, we begin by setting up a simulation of pressing the on-off button eight times, starting from off₀, and counting how many times the bulb lights up.

abbrev LitCount := Nat def LitStates : List BulbState := [on₁, on₂, on₃, on₄] def pressAndCount (this : BulbState) : StateM LitCount BulbState := do let next := pressButton this if next LitStates then modify (· + 1) return next def EightPresses : List Unit := List.replicate 8 () def simulateEightPresses : StateM LitCount BulbState := EightPresses.foldlM (fun s _ pressAndCount s) off₀

Run the simulation starting with a lit count of 0, and return the final bulb state and lit count.

def getResult : BulbState × LitCount := simulateEightPresses.run 0

Confirm that at the end of the run, the bulb has been lit four times and is now burntOut.

example : getResult = (burntOut, 4) := rfl

Lastly, confirm that once the bulb is burntOut, pressing the on—off button has no effect (i.e., burntOut is a fixed point).

example : Function.IsFixedPt pressButton burntOut := rfl

2. Family trees🔗

We implement the category 𝑺↻↻ in which an object is a set with a specified pair of endomaps, as described on p. 162.

structure SetWithTwoEndomaps extends SetWithEndomap where toEnd2 : carrier carrier instance instCatSetWithTwoEndomaps : Category SetWithTwoEndomaps where Hom X Y := { f : X.carrier Y.carrier // f X.toEnd = Y.toEnd f -- first endomap commutes f X.toEnd2 = Y.toEnd2 f -- second endomap commutes } id X := 𝟙 X.carrier, rfl, rfl comp := {X Y Z : SetWithTwoEndomaps} { f // f X.toEnd = Y.toEnd f f X.toEnd2 = Y.toEnd2 f } { f // f Y.toEnd = Z.toEnd f f Y.toEnd2 = Z.toEnd2 f } { f // f X.toEnd = Z.toEnd f f X.toEnd2 = Z.toEnd2 f } X✝:SetWithTwoEndomapsY✝:SetWithTwoEndomapsZ✝:SetWithTwoEndomapsf:X✝.carrier Y✝.carrierhf:f X✝.toEnd = Y✝.toEnd f f X✝.toEnd2 = Y✝.toEnd2 fg:Y✝.carrier Z✝.carrierhg:g Y✝.toEnd = Z✝.toEnd g g Y✝.toEnd2 = Z✝.toEnd2 g{ f // f X✝.toEnd = Z✝.toEnd f f X✝.toEnd2 = Z✝.toEnd2 f } exact g f, X✝:SetWithTwoEndomapsY✝:SetWithTwoEndomapsZ✝:SetWithTwoEndomapsf:X✝.carrier Y✝.carrierhf:f X✝.toEnd = Y✝.toEnd f f X✝.toEnd2 = Y✝.toEnd2 fg:Y✝.carrier Z✝.carrierhg:g Y✝.toEnd = Z✝.toEnd g g Y✝.toEnd2 = Z✝.toEnd2 g(g f) X✝.toEnd = Z✝.toEnd g f (g f) X✝.toEnd2 = Z✝.toEnd2 g f X✝:SetWithTwoEndomapsY✝:SetWithTwoEndomapsZ✝:SetWithTwoEndomapsf:X✝.carrier Y✝.carrierg:Y✝.carrier Z✝.carrierhg:g Y✝.toEnd = Z✝.toEnd g g Y✝.toEnd2 = Z✝.toEnd2 ghf_comm:f X✝.toEnd = Y✝.toEnd fhf2_comm:f X✝.toEnd2 = Y✝.toEnd2 f(g f) X✝.toEnd = Z✝.toEnd g f (g f) X✝.toEnd2 = Z✝.toEnd2 g f X✝:SetWithTwoEndomapsY✝:SetWithTwoEndomapsZ✝:SetWithTwoEndomapsf:X✝.carrier Y✝.carrierg:Y✝.carrier Z✝.carrierhf_comm:f X✝.toEnd = Y✝.toEnd fhf2_comm:f X✝.toEnd2 = Y✝.toEnd2 fhg_comm:g Y✝.toEnd = Z✝.toEnd ghg2_comm:g Y✝.toEnd2 = Z✝.toEnd2 g(g f) X✝.toEnd = Z✝.toEnd g f (g f) X✝.toEnd2 = Z✝.toEnd2 g f X✝:SetWithTwoEndomapsY✝:SetWithTwoEndomapsZ✝:SetWithTwoEndomapsf:X✝.carrier Y✝.carrierg:Y✝.carrier Z✝.carrierhf_comm:f X✝.toEnd = Y✝.toEnd fhf2_comm:f X✝.toEnd2 = Y✝.toEnd2 fhg_comm:g Y✝.toEnd = Z✝.toEnd ghg2_comm:g Y✝.toEnd2 = Z✝.toEnd2 g(g f) X✝.toEnd = Z✝.toEnd g fX✝:SetWithTwoEndomapsY✝:SetWithTwoEndomapsZ✝:SetWithTwoEndomapsf:X✝.carrier Y✝.carrierg:Y✝.carrier Z✝.carrierhf_comm:f X✝.toEnd = Y✝.toEnd fhf2_comm:f X✝.toEnd2 = Y✝.toEnd2 fhg_comm:g Y✝.toEnd = Z✝.toEnd ghg2_comm:g Y✝.toEnd2 = Z✝.toEnd2 g(g f) X✝.toEnd2 = Z✝.toEnd2 g f X✝:SetWithTwoEndomapsY✝:SetWithTwoEndomapsZ✝:SetWithTwoEndomapsf:X✝.carrier Y✝.carrierg:Y✝.carrier Z✝.carrierhf_comm:f X✝.toEnd = Y✝.toEnd fhf2_comm:f X✝.toEnd2 = Y✝.toEnd2 fhg_comm:g Y✝.toEnd = Z✝.toEnd ghg2_comm:g Y✝.toEnd2 = Z✝.toEnd2 g(g f) X✝.toEnd = Z✝.toEnd g f All goals completed! 🐙 X✝:SetWithTwoEndomapsY✝:SetWithTwoEndomapsZ✝:SetWithTwoEndomapsf:X✝.carrier Y✝.carrierg:Y✝.carrier Z✝.carrierhf_comm:f X✝.toEnd = Y✝.toEnd fhf2_comm:f X✝.toEnd2 = Y✝.toEnd2 fhg_comm:g Y✝.toEnd = Z✝.toEnd ghg2_comm:g Y✝.toEnd2 = Z✝.toEnd2 g(g f) X✝.toEnd2 = Z✝.toEnd2 g f All goals completed! 🐙
Exercise 3 (p. 163)

(a) Suppose {\mathbf{P} = {}^{m↻}P^{↻f}} is the set P of all people together with the endomaps {m = \mathit{mother}} and {f = \mathit{father}}. Show that 'gender' is a map in the category 𝑺↻↻ from \mathbf{P} to the object

inductive Gender | female | male def m₁ : Gender Gender := fun _ Gender.female def f₁ : Gender Gender := fun _ Gender.male def G : SetWithTwoEndomaps := { carrier := Gender toEnd := m₁ toEnd2 := f₁ }

(b) In a certain society, all the people have always been divided into two 'clans', the Wolf-clan and the Bear-clan. Marriages within a clan are forbidden, so that a Wolf may not marry a Wolf. A child's clan is the same as that of its mother. Show that the sorting of people into clans is actually a map in 𝑺↻↻ from \mathbf{P} to the object

inductive Clan | wolf | bear def m₂ : Clan Clan := fun c c def f₂ : Clan Clan | Clan.wolf => Clan.bear | Clan.bear => Clan.wolf def C : SetWithTwoEndomaps := { carrier := Clan toEnd := m₂ toEnd2 := f₂ }

(c) Find appropriate 'father' and 'mother' maps to make {\mathbf{G} \times \mathbf{C}} into an object in 𝑺↻↻ so that 'clan' and 'gender' can be combined into a single map {\mathbf{P} \rightarrow \mathbf{G} \times \mathbf{C}}. (Later, when we have the precise definition of multiplication of objects in categories, you will see that {\mathbf{G} \times \mathbf{C}} really is the product of \mathbf{G} and \mathbf{C}.)

Solution: Exercise 3

(a) For \mathbf{P} in 𝑺↻↻, we first define a Person type to use in place of the set P of all people, and we identity each Person as being either a mother or a father.

inductive ParentType | isMother | isFather structure Person₁ where parentType : ParentType

We next define the mother and father endomaps on Person, which ignore their input (as irrelevant) and simply return a Person of the appropriate ParentType.

def mother₁ : Person₁ Person₁ := fun _ ParentType.isMother def father₁ : Person₁ Person₁ := fun _ ParentType.isFather

Now we can define the object \mathbf{P}.

def P₁ : SetWithTwoEndomaps := { carrier := Person₁ toEnd := mother₁ toEnd2 := father₁ }

Lastly, we define the map gender, which sends each Person to their corresponding Gender.

def gender : P₁.carrier G.carrier | ParentType.isMother => Gender.female | ParentType.isFather => Gender.male

Since we can form a valid morphism using our categorical framework, it follows that gender is a map in the category 𝑺↻↻ from \mathbf{P} to \mathbf{G}.

def gender' : P₁ G := gender, gender P₁.toEnd = G.toEnd gender gender P₁.toEnd2 = G.toEnd2 gender gender P₁.toEnd = G.toEnd gendergender P₁.toEnd2 = G.toEnd2 gender gender P₁.toEnd = G.toEnd gendergender P₁.toEnd2 = G.toEnd2 gender All goals completed! 🐙

For good measure, we can also verify the two commutative properties explicitly.

example : gender mother₁ = m₁ gender := rfl example : gender father₁ = f₁ gender := rfl

(b) The implementation of part (b) is similar to that of part (a), with an appropriate change to our Person structure to handle clan.

inductive ParentClan | isWolf | isBear structure Person₂ where parentClan : ParentClan def mother₂ : Person₂ Person₂ := fun p p.parentClan def father₂ : Person₂ Person₂ | ParentClan.isWolf => ParentClan.isBear | ParentClan.isBear => ParentClan.isWolf def P₂ : SetWithTwoEndomaps := { carrier := Person₂ toEnd := mother₂ toEnd2 := father₂ } def clan : P₂.carrier C.carrier | ParentClan.isWolf => Clan.wolf | ParentClan.isBear => Clan.bear def clan' : P₂ C := clan, clan P₂.toEnd = C.toEnd clan clan P₂.toEnd2 = C.toEnd2 clan clan P₂.toEnd = C.toEnd clanclan P₂.toEnd2 = C.toEnd2 clan all_goals p:P₂.carrier(clan P₂.toEnd2) p = (C.toEnd2 clan) p match p with p:P₂.carrier(clan P₂.toEnd2) { parentClan := ParentClan.isWolf } = (C.toEnd2 clan) { parentClan := ParentClan.isWolf }p:P₂.carrier(clan P₂.toEnd) { parentClan := ParentClan.isWolf } = (C.toEnd clan) { parentClan := ParentClan.isWolf } All goals completed! 🐙 p:P₂.carrier(clan P₂.toEnd2) { parentClan := ParentClan.isBear } = (C.toEnd2 clan) { parentClan := ParentClan.isBear }p:P₂.carrier(clan P₂.toEnd) { parentClan := ParentClan.isBear } = (C.toEnd clan) { parentClan := ParentClan.isBear } All goals completed! 🐙

(c) The implementation of part (c) is a combination of parts (a) and (b).

structure Person₃ where parentType : ParentType parentClan : ParentClan def mother₃ : Person₃ Person₃ := fun p ParentType.isMother, p.parentClan def father₃ : Person₃ Person₃ | _, ParentClan.isWolf => ParentType.isFather, ParentClan.isBear | _, ParentClan.isBear => ParentType.isFather, ParentClan.isWolf def P₃ : SetWithTwoEndomaps := { carrier := Person₃ toEnd := mother₃ toEnd2 := father₃ }

As required, we define appropriate 'mother' and 'father' maps (m and f, respectively, to align with the book).

def m₃ : (Gender × Clan) (Gender × Clan) := fun (_, c) (Gender.female, c) def f₃ : (Gender × Clan) (Gender × Clan) | (_, Clan.wolf) => (Gender.male, Clan.bear) | (_, Clan.bear) => (Gender.male, Clan.wolf) def GC : SetWithTwoEndomaps := { carrier := Gender × Clan toEnd := m₃ toEnd2 := f₃ } def gender_and_clan : P₃.carrier GC.carrier | ParentType.isMother, ParentClan.isWolf => Gender.female, Clan.wolf | ParentType.isMother, ParentClan.isBear => Gender.female, Clan.bear | ParentType.isFather, ParentClan.isWolf => Gender.male, Clan.wolf | ParentType.isFather, ParentClan.isBear => Gender.male, Clan.bear def gender_and_clan' : P₃ GC := gender_and_clan, gender_and_clan P₃.toEnd = GC.toEnd gender_and_clan gender_and_clan P₃.toEnd2 = GC.toEnd2 gender_and_clan gender_and_clan P₃.toEnd = GC.toEnd gender_and_clangender_and_clan P₃.toEnd2 = GC.toEnd2 gender_and_clan all_goals p:P₃.carrier(gender_and_clan P₃.toEnd2) p = (GC.toEnd2 gender_and_clan) p match p with p:P₃.carrier(gender_and_clan P₃.toEnd2) { parentType := ParentType.isMother, parentClan := ParentClan.isWolf } = (GC.toEnd2 gender_and_clan) { parentType := ParentType.isMother, parentClan := ParentClan.isWolf }p:P₃.carrier(gender_and_clan P₃.toEnd) { parentType := ParentType.isMother, parentClan := ParentClan.isWolf } = (GC.toEnd gender_and_clan) { parentType := ParentType.isMother, parentClan := ParentClan.isWolf } All goals completed! 🐙 p:P₃.carrier(gender_and_clan P₃.toEnd2) { parentType := ParentType.isMother, parentClan := ParentClan.isBear } = (GC.toEnd2 gender_and_clan) { parentType := ParentType.isMother, parentClan := ParentClan.isBear }p:P₃.carrier(gender_and_clan P₃.toEnd) { parentType := ParentType.isMother, parentClan := ParentClan.isBear } = (GC.toEnd gender_and_clan) { parentType := ParentType.isMother, parentClan := ParentClan.isBear } All goals completed! 🐙 p:P₃.carrier(gender_and_clan P₃.toEnd2) { parentType := ParentType.isFather, parentClan := ParentClan.isWolf } = (GC.toEnd2 gender_and_clan) { parentType := ParentType.isFather, parentClan := ParentClan.isWolf }p:P₃.carrier(gender_and_clan P₃.toEnd) { parentType := ParentType.isFather, parentClan := ParentClan.isWolf } = (GC.toEnd gender_and_clan) { parentType := ParentType.isFather, parentClan := ParentClan.isWolf } All goals completed! 🐙 p:P₃.carrier(gender_and_clan P₃.toEnd2) { parentType := ParentType.isFather, parentClan := ParentClan.isBear } = (GC.toEnd2 gender_and_clan) { parentType := ParentType.isFather, parentClan := ParentClan.isBear }p:P₃.carrier(gender_and_clan P₃.toEnd) { parentType := ParentType.isFather, parentClan := ParentClan.isBear } = (GC.toEnd gender_and_clan) { parentType := ParentType.isFather, parentClan := ParentClan.isBear } All goals completed! 🐙