Zulip Chat Archive
Stream: new members
Topic: Matrix inverse and group structure
gjohn (Mar 31 2025 at 02:30):
Hello everyone,
I have started learning lean recently ( <3 weeks) and need some help with making a proof more readable.
import Mathlib.Algebra.Group.Defs
import Mathlib.Data.Matrix.Basic
import Mathlib.Data.Fin.Basic
import Mathlib.Data.Real.Basic
import Mathlib.Tactic
open Matrix
namespace GTheory_Physics
abbrev M2R := Matrix ( Fin 2) (Fin 2) ℚ
namespace M2R
-- def I2 : M2R := 1
@[simp] def e : M2R := !![1,0; 0,1]
@[simp] def a : M2R := !![0, -1; 1,  0]
@[simp] def b : M2R := !![-1,  0; 0, -1]
@[simp] def c : M2R := !![0,  1;-1, 0]
@[simp] def S: Finset M2R := {e, a, b, c}
abbrev M2R₄ := {m: M2R // m ∈ S}
theorem mul_closed (a b: M2R) (ha: a ∈ S) (hb: b ∈ S ) : (a * b) ∈ S := by
    sorry
instance: Mul M2R₄ where
    mul a b := ⟨a.val * b.val, mul_closed a.val b.val a.prop b.prop ⟩
noncomputable def m2r_inv (x: M2R) : M2R :=
    if x = e then e
    else if x = a then c
    else if x = c then a
    else if x = b then b
    else x⁻¹
theorem inv_closed (a : M2R) (ha: a ∈ S) : m2r_inv a ∈ S := by
    sorry
noncomputable instance: Inv M2R₄ where
    inv a := ⟨m2r_inv a.val, inv_closed a.val a.prop⟩
instance : One M2R₄  :=
    ⟨e, by decide⟩
theorem mul_def (a b: M2R₄) :
    a * b = ⟨a.val * b.val, mul_closed a.val b.val a.prop b.prop ⟩ :=
    rfl
theorem inv_def (a: M2R₄) :
    a⁻¹ = ⟨m2r_inv a.val, inv_closed a.val a.prop⟩ :=
    rfl
theorem one_def : ( 1: M2R₄) = ⟨e, by decide⟩ :=
    rfl
noncomputable def M2RGroup: Group M2R₄ where
  mul := ( · * · )
  mul_assoc a b c := by sorry
  one := 1
  one_mul := by
    intro x
    ext i j
    fin_cases i; fin_cases j
    repeat
        rw [one_def]
        rw[mul_def]
        dsimp [Matrix.mul_apply]
        rw [Fin.sum_univ_two]
        norm_num
  mul_one := by
    intro x
    ext i j
    fin_cases i; fin_cases j
    repeat
        rw [mul_def]
        rw [one_def]
        dsimp [Matrix.mul_apply]
        rw [Fin.sum_univ_two]
        norm_num
    fin_cases j
    repeat
        norm_num
  inv := ( · ⁻¹ )
  inv_mul_cancel := by
    intro x
    obtain ⟨m, hm⟩ := x
    ext i j
    rw [inv_def]
    rw [mul_def]
    rw[one_def]
    dsimp [Matrix.mul_apply]
    dsimp [m2r_inv]
    rw [Fin.sum_univ_two]
    fin_cases i; fin_cases j
    repeat
            fin_cases hm;
            dsimp [m2r_inv]
            norm_num
            repeat
                dsimp [m2r_inv]
                simp
                exact rfl
    repeat
        fin_cases j
        repeat
            dsimp [m2r_inv]
            simp
            exact rfl
- Primary Question: Is there a better definition of inverse - one which doesn't use if else? If i try to use simply $$ x\inv $$ it gets really weird knowing that $$ x\inv $$ doesn't always exist.
- Optional: can i avoid very basic arithmatic and work on a little higher level knowing the equation of matrices can be unbundled to a equation involving individual elements? Maybe it is because i am new to lean, it seems like a lot of work to prove the simple structure.
Thank you for your time and attention.
gjohn (Mar 31 2025 at 09:39):
i could simplify a lot after tagging my theorems with @[simp]
@[simp] def e : M2R := !![1,0; 0,1]
@[simp] def a : M2R := !![0, -1; 1,  0]
@[simp] def b : M2R := !![-1,  0; 0, -1]
@[simp] def c : M2R := !![0,  1;-1, 0]
@[simp] def S: Finset M2R := {e, a, b, c}
abbrev M2R₄ := {m: M2R // m ∈ S}
theorem mul_closed (a b: M2R) (ha: a ∈ S) (hb: b ∈ S ) : (a * b) ∈ S := by
    sorry
instance: Mul M2R₄ where
    mul a b := ⟨a.val * b.val, mul_closed a.val b.val a.prop b.prop ⟩
@[simp]
noncomputable def m2r_inv (x: M2R) : M2R :=
    if x = e then e
    else if x = a then c
    else if x = c then a
    else if x = b then b
    else x⁻¹
theorem inv_closed (a : M2R) (ha: a ∈ S) : m2r_inv a ∈ S := by
    sorry
    -- simp
    -- fin_cases ha
    -- any_goals
    --     simp
noncomputable instance: Inv M2R₄ where
    inv a := ⟨m2r_inv a.val, inv_closed a.val a.prop⟩
instance : One M2R₄  :=
    ⟨e, by decide⟩
@[simp]
theorem mul_def (a b: M2R₄) :
    a * b = ⟨a.val * b.val, mul_closed a.val b.val a.prop b.prop ⟩ :=
    rfl
@[simp]
theorem inv_def (a: M2R₄) :
    a⁻¹ = ⟨m2r_inv a.val, inv_closed a.val a.prop⟩ :=
    rfl
@[simp]
theorem one_def : ( 1: M2R₄) = ⟨e, by decide⟩ :=
    rfl
noncomputable def M2RGroup: Group M2R₄ where
  mul := ( · * · )
  mul_assoc a b c := by
    simp
    apply Matrix.mul_assoc
  one := 1
  one_mul := by
    intro x
    simp
    ext i j
    fin_cases i;
    all_goals
        simp
        fin_cases j;
        all_goals
            simp
            fin_cases x
            all_goals
                simp
  mul_one := by
    intro x
    simp
    ext i j
    fin_cases i;
    all_goals
        simp
        fin_cases j;
        all_goals
            simp
            fin_cases x
            all_goals
                simp
  inv := ( · ⁻¹ )
  inv_mul_cancel := by
    intro x
    simp
    ext i j
    fin_cases i;
    all_goals
        simp
        fin_cases j;
        all_goals
            simp
            fin_cases x
            any_goals
                simp
            any_goals
                exact rfl
some goals get proved using just simp while some need first simp and then exact refl.
is there a tactic which allows specifying a set of tactics and applies them only till a goal is solved and then moves on to next case? so that i would be able to say:
            fin_cases x
            any_partial_goals
                simp          -- stop here if you are done
                exact rfl    -- come here only if you need to
Matt Diamond (Mar 31 2025 at 17:06):
I don't think it's "really weird" to use x⁻¹ when the inverse doesn't always exist. This is actually a common strategy that's used throughout Lean/Mathlib. This blog post might help explain it:
https://xenaproject.wordpress.com/2020/07/05/division-by-zero-in-type-theory-a-faq/
Matt Diamond (Mar 31 2025 at 17:11):
the idea is that the validity of the operation doesn't matter when you're performing it... it only matters when you're trying to prove something about the result of performing it
Matt Diamond (Mar 31 2025 at 17:32):
It's simpler this way:
noncomputable def m2r_inv (x: M2R) : M2R := x⁻¹
theorem inv_closed (a : M2R) (ha: a ∈ S) : m2r_inv a ∈ S := by
  fin_cases ha <;> simp [Matrix.inv_def]
Also, here's a simpler proof of inv_mul_cancel:
inv_mul_cancel := by
  intro x
  simp
  rw [Matrix.nonsing_inv_mul]
  · decide
  · fin_cases x <;> simp
In general you can save some lines by making use of the <;> operator. It takes the result of the left-hand side and applies the right-hand side to all generated goals. For example:
one_mul := by
  intro x
  ext i j
  fin_cases i <;> fin_cases j <;> fin_cases x <;> simp
(and you can use the same proof for mul_one)
Aaron Liu (Mar 31 2025 at 17:32):
gjohn said:
is there a tactic which allows specifying a set of tactics and applies them only till a goal is solved and then moves on to next case? so that i would be able to say:
fin_cases x any_partial_goals simp -- stop here if you are done exact rfl -- come here only if you need to
I wrote one just now:
import Lean
syntax (name := partialGoals)
  "partial_goals" withPosition((ppLine linebreak ppIndent(colEq tactic))+) : tactic
@[tactic partialGoals]
def elabPartialGoals : Lean.Elab.Tactic.Tactic := fun stx => do
  for tac in stx[1].getArgs do
    Lean.Elab.Tactic.evalTactic tac
    if (← Lean.Elab.Tactic.getUnsolvedGoals).length = 0 then return
You can combine this with any_goals to get what you want.
Yakov Pechersky (Mar 31 2025 at 17:58):
any_goals try simp
any_goals try rfl
also?
gjohn (Mar 31 2025 at 19:07):
Thank you @Matt Diamond  it makes sense to use inverse directly. also <;> makes it much cleaner.
Thanks @Aaron Liu  i tried it and it works nicely. also the syntax of defining tactic is pretty readable.
gjohn (Mar 31 2025 at 19:20):
I have a related syntactical question.
-- Z4 is ZMod 4
def m2r_eq_z4 : M2R.M2R₄ ≃ Z4 where
  toFun := fun x =>
    if x.val = M2R.e then 0
    else if x.val = M2R.a then 1
    else if x.val = M2R.b then 2
    else 3  -- This must be M2R.c due to the subset type constraint
  invFun := fun x =>
    if x = 0 then ⟨M2R.e, by decide⟩
    else if x = 1 then ⟨M2R.a, by decide⟩
    else if x = 2 then ⟨M2R.b, by decide⟩
    else ⟨M2R.c, by decide⟩  -- This must be 3 due to Z4 having only 4 elements
  left_inv := by
    sorry
  right_inv := by
    sorry
Can i avoid if else if blocks? Because of it, simp results in some really big if else intermediate statements which makes it harder to reason about where things went wrong. 
I tried following syntax:
  toFun := fun x =>
    match x.val with
    | M2R.e => 0
    | M2R.a => 1
    | M2R.b => 2
    | M2R.c => 3
but compiler complains invalid pattern variable, must be atomic.
Yakov Pechersky (Mar 31 2025 at 19:24):
MWE would help. You can make your own custom recursor first
def M2R.M2R4.rec {motive : M2R.M2R₄ -> Sort*} (he : motive M2R.e) (ha : motive M2R.a) (hb : motive M2R.b) (hc : motive M2R.c) (x : M2R.M2R₄) : motive x := by
  _
Yakov Pechersky (Mar 31 2025 at 19:24):
and stuff the ugly case bashing in there
gjohn (Mar 31 2025 at 19:26):
Thank you @Yakov Pechersky 
Let me try this. 
I would post mwe if this doesn't help directly.
gjohn (Mar 31 2025 at 19:37):
Here it is @Yakov Pechersky
import Mathlib.Algebra.Group.Defs
import Mathlib.Data.Matrix.Basic
import Mathlib.Data.Fin.Basic
import Mathlib.Data.Real.Basic
import Mathlib.Tactic
abbrev M2R := Matrix ( Fin 2) (Fin 2) ℚ
@[simp] def e : M2R := !![1,0; 0,1]
@[simp] def a : M2R := !![0, -1; 1,  0]
@[simp] def b : M2R := !![-1,  0; 0, -1]
@[simp] def c : M2R := !![0,  1;-1, 0]
@[simp] def S: Finset M2R := {e, a, b, c}
abbrev M2R₄ := {m: M2R // m ∈ S}
abbrev Z4 := ZMod 4
def m2r_eq_z4 : M2R₄ ≃ Z4 where
  toFun := fun x =>
    if x.val = e then 0
    else if x.val = a then 1
    else if x.val = b then 2
    else 3  -- This must be M2R.c due to the subset type constraint
  invFun := fun x =>
    if x = 0 then ⟨e, by decide⟩
    else if x = 1 then ⟨a, by decide⟩
    else if x = 2 then ⟨b, by decide⟩
    else ⟨c, by decide⟩  -- This must be 3 due to Z4 having only 4 elements
  left_inv := by
    sorry
  right_inv := by
    sorry
Yakov Pechersky (Mar 31 2025 at 20:41):
import Mathlib.Algebra.Group.Defs
import Mathlib.Data.Matrix.Basic
import Mathlib.Data.Fin.Basic
import Mathlib.Data.Real.Basic
import Mathlib.Tactic
abbrev M2R := Matrix ( Fin 2) (Fin 2) ℚ
namespace M2R
abbrev e' : M2R := !![1,0; 0,1]
abbrev a' : M2R := !![0, -1; 1,  0]
abbrev b' : M2R := !![-1,  0; 0, -1]
abbrev c' : M2R := !![0,  1;-1, 0]
def S : Finset M2R := {e', a', b', c'}
abbrev e : S := ⟨e', by simp [S]⟩
abbrev a : S := ⟨a', by simp [S]⟩
abbrev b : S := ⟨b', by simp [S]⟩
abbrev c : S := ⟨c', by simp [S]⟩
abbrev Z4 := ZMod 4
@[elab_as_elim]
protected def rec {motive : S → Sort*} (he : motive e) (ha : motive a) (hb : motive b) (hc : motive c)
    (x : S) : motive x :=
  if He : x = e then He ▸ he else
  if Ha : x = a then Ha ▸ ha else
  if Hb : x = b then Hb ▸ hb else
  if Hc : x = c then Hc ▸ hc else
  False.elim <| by
    rcases x with ⟨x, hx⟩
    rw [S] at hx
    simp_all
lemma rec_e {motive : S → Sort*} (he ha hb hc) : (M2R.rec he ha hb hc e : motive e) = he := by
  simp [M2R.rec]
lemma rec_a {motive : S → Sort*} (he ha hb hc) : (M2R.rec he ha hb hc a : motive e) = ha := by
  simp only [M2R.rec, Subtype.mk.injEq, EmbeddingLike.apply_eq_iff_eq, eq_rec_constant, ↓reduceDIte,
    dite_eq_ite, ite_eq_right_iff]
  intro H
  simpa using congr_fun₂ H 0 0
lemma rec_b {motive : S → Sort*} (he ha hb hc) : (M2R.rec he ha hb hc b : motive e) = hb := by
  simp only [M2R.rec, Subtype.mk.injEq, EmbeddingLike.apply_eq_iff_eq, eq_rec_constant, ↓reduceDIte,
    dite_eq_ite]
  rw [if_neg, if_neg]
  all_goals
  · intro H
    first
    | simpa using congr_fun₂ H 1 0
    | simpa [neg_eq_iff_add_eq_zero] using congr_fun₂ H 0 0
lemma rec_c {motive : S → Sort*} (he ha hb hc) : (M2R.rec he ha hb hc c : motive e) = hc := by
  simp only [M2R.rec, Subtype.mk.injEq, EmbeddingLike.apply_eq_iff_eq, eq_rec_constant, ↓reduceDIte,
    dite_eq_ite]
  rw [if_neg, if_neg, if_neg]
  all_goals
  · intro H
    simpa [neg_eq_iff_add_eq_zero] using congr_fun₂ H 1 0
lemma S_spec (x : S) : x = e ∨ x = a ∨ x = b ∨ x = c := by
  rcases x with ⟨x, hx⟩
  simpa [S] using hx
def m2r_eq_z4 : S ≃ Z4 where
  toFun := fun x => M2R.rec 0 1 2 3 x
  invFun := fun x => match x with
    | 0 => e
    | 1 => a
    | 2 => b
    | 3 => c
  left_inv := by
    intro x
    dsimp
    generalize hy : (M2R.rec 0 1 2 3 x : Z4) = y
    rcases S_spec x with rfl|rfl|rfl|rfl
    all_goals
    · simp [@rec_e (fun _ ↦ Z4), @rec_a (fun _ ↦ Z4), @rec_b (fun _ ↦ Z4), @rec_c (fun _ ↦ Z4)] at hy
      simp [← hy]
  right_inv := by
    intro y
    fin_cases y
    all_goals
    simp [@rec_e (fun _ ↦ Z4), @rec_a (fun _ ↦ Z4), @rec_b (fun _ ↦ Z4), @rec_c (fun _ ↦ Z4)]
end M2R
gjohn (Mar 31 2025 at 21:58):
Thanks a lot @Yakov Pechersky 
I see it's a little involved - i'll try the definition on my end and see if i can come up with proofs myself.
gjohn (Apr 06 2025 at 19:03):
I ended up using the if else method:
import Mathlib.Algebra.Group.Defs
import Mathlib.Data.Matrix.Basic
import Mathlib.Data.Fin.Basic
import Mathlib.Data.Real.Basic
import Mathlib.Tactic
abbrev M2R := Matrix ( Fin 2) (Fin 2) ℚ
@[simp] def e : M2R := !![1,0; 0,1]
@[simp] def a : M2R := !![0, -1; 1,  0]
@[simp] def b : M2R := !![-1,  0; 0, -1]
@[simp] def c : M2R := !![0,  1;-1, 0]
@[simp] def S: Finset M2R := {e, a, b, c}
abbrev M2R₄ := {m: M2R // m ∈ S}
abbrev Z4 := ZMod 4
-- Z4 is ZMod 4
def m2r_eq_z4 : M2R₄ ≃ Z4 where
  toFun := fun x =>
    if x.val = e then 0
    else if x.val = a then 1
    else if x.val = b then 2
    else if x.val = c then 3
    else 3  -- This must be c due to the subset type constraint
  invFun := fun x =>
    if x = 0 then ⟨e, by decide⟩
    else if x = 1 then ⟨a, by decide⟩
    else if x = 2 then ⟨b, by decide⟩
    else if x = 3 then ⟨c, by decide⟩
    else ⟨c, by decide⟩  -- This must be 3 due to Z4 having only 4 elements
  left_inv := by
    intro x
    fin_cases x
    any_goals try simp
    any_goals try exact rfl
  right_inv := by
    intro x
    fin_cases x
    any_goals try simp
    any_goals try exact rfl
The reason why i was having issues before was my definition of 1 for Z4. In order to define a group on it i was marking 0 as One and thus my toFun and invFun definitions were incorrect. 
that being said the alternative path mentioned above is interesting because it introduces some new syntax to me.
thanks @Yakov Pechersky
Last updated: May 02 2025 at 03:31 UTC