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