Zulip Chat Archive
Stream: Is there code for X?
Topic: ZMod.val and numerals
Jeremy Avigad (Mar 29 2025 at 16:12):
Consider the following:
import Mathlib.Data.ZMod.Basic
import Mathlib.Tactic.NormNum.Prime
def BIG_PRIME := 89
example : ZMod.val (16 : ZMod BIG_PRIME) = 16 := by
rfl
I am in a setting where I have goals with expressions like the one that appears on the LHS and I want to simplify them to the RHS with rw
or simp
. I don't mind rewriting rw [foo 16]
or something like that if necessary, but the goals are too big to use change
and there are too many of them to conveniently state each instance with a have
or as a separate simp
lemma.
In analogy to Fin.coe_ofNat_eq_mod
, I tried this:
theorem bar (n : Nat) (h : n < BIG_PRIME) :
ZMod.val (ofNat(n) : ZMod BIG_PRIME) = n
but Lean can't even parse the ofNat(n)
; the error message I get is
failed to synthesize
OfNat (ZMod BIG_PRIME) n
although it somehow manages to do it when n
is replaced by a concrete numeral like 16
.
Any thoughts about how I can do this?
Jeremy Avigad (Mar 29 2025 at 16:17):
Sorry, I forgot to mention that I also did this:
instance : Fact (Nat.Prime BIG_PRIME) := by
norm_num [BIG_PRIME]; exact ⟨trivial⟩
So Lean ought to be able to infer that BIG_PRIME
is not 0 or 1. For example, this works:
theorem foo (n : Nat) (h : n < BIG_PRIME) :
ZMod.val (↑(n) : ZMod BIG_PRIME) = n :=
Nat.mod_eq_of_lt h
Yaël Dillies (Mar 29 2025 at 16:18):
(did you mean to post this in #Is there code for X??)
Notification Bot (Mar 29 2025 at 16:19):
3 messages were moved here from #general > Is there code for X by Jeremy Avigad.
Jeremy Avigad (Mar 29 2025 at 16:20):
@Yaël Dillies Yes, sorry -- I just moved them.
Yaël Dillies (Mar 29 2025 at 16:26):
Jeremy Avigad said:
In analogy to
Fin.coe_ofNat_eq_mod
, I tried this:theorem bar (n : Nat) (h : n < BIG_PRIME) : ZMod.val (ofNat(n) : ZMod BIG_PRIME) = n
but Lean can't even parse the
ofNat(n)
; the error message I get isfailed to synthesize OfNat (ZMod BIG_PRIME) n
although it somehow manages to do it when
n
is replaced by a concrete numeral like16
.Any thoughts about how I can do this?
Numerals fall into three categories:
- 0
- 1
- big
Therefore to handle all numerals you need to write three lemmas:
ZMod.val 0 = 0
ZMod.val 1 = 1
∀ n < BIG_PRIME, n.AtLeastTwo → ZMod.val ofNat(n) = n
Of course, you should also write a lemma in terms of Nat.cast
, and this lemma can then be used to prove the other three lemmas
Yaël Dillies (Mar 29 2025 at 16:28):
A possibly better answer is that this sounds like a job for a (d)simproc, akin to docs#Fin.isValue
Yaël Dillies (Mar 29 2025 at 16:33):
Yaël Dillies said:
Therefore to handle all numerals you need to write three lemmas: [...]
I must say that docs#Fin.coe_ofNat_eq_mod looks like an exception to what I just claimed, but that's because Fin
has a custom OfNat
instance that covers all numerals at once: docs#Fin.instOfNat. If you ask me, this instance looks a bit odd since it forces 0 : Fin n
and 1 : Fin n
instances to be defeq to ⟨0 % n, _⟩
and ⟨1 % n, _⟩
instead of the more natural ⟨0, _⟩
and ⟨1, _⟩
.
Jeremy Avigad (Mar 29 2025 at 16:38):
Your solution doesn't work.
theorem foo' : ∀ n < BIG_PRIME, n.AtLeastTwo → ZMod.val (n := BIG_PRIME) n = n := fun _ h _ => Nat.mod_eq_of_lt h
is the same as my theorem foo
. It is stated in terms of a cast from Nat
to ZMod BIG_PRIME
, not numerals. You can't use it to rewrite ZMod.val (n := BIG_PRIME) 16
.
Antoine Chambert-Loir (Mar 29 2025 at 16:39):
Trial and error gave me this, but I don't understand what I wrote.
theorem junk (n : ℕ) (h : n < BIG_PRIME) :
ZMod.val (@OfNat.ofNat (ZMod BIG_PRIME) n
(OfNat.mk (n : ZMod BIG_PRIME)) : ZMod BIG_PRIME) = n :=
Nat.mod_eq_of_lt h
example : ZMod.val (16 : ZMod BIG_PRIME) = 16 := by
rw [junk]
norm_num [BIG_PRIME]
exact Nat.lt_of_sub_eq_succ rfl
Jeremy Avigad (Mar 29 2025 at 16:42):
Aha! That's great. I should have thought of manually forcing Lean to synthesize the instance. Thanks! I should even be able to make junk
do the calculation with (h : n < BIG_PRIME := by simp [BIG_PRIME])
.
Antoine Chambert-Loir (Mar 29 2025 at 16:48):
and this works as well:
theorem gen_junk (p : ℕ) (n : ℕ) (h : n < p) :
ZMod.val (@OfNat.ofNat (ZMod p) n
(OfNat.mk (n : ZMod p)) : ZMod p) = n := by
simp only [ZMod.val_natCast]
rw [Nat.mod_eq, if_neg]
simp only [not_and, not_le]
exact fun _ => h
Yaël Dillies (Mar 29 2025 at 16:50):
Jeremy Avigad said:
theorem foo' {n : ℕ : ∀ n < BIG_PRIME, n.AtLeastTwo → ZMod.val (n := BIG_PRIME) n = n := fun _ h _ => Nat.mod_eq_of_lt h
is the same as my theorem
foo
. It is stated in terms of a cast fromNat
toZMod BIG_PRIME
, not numerals.
That's because you used n
instead of ofNat(n)
:
import Mathlib.Data.ZMod.Basic
import Mathlib.Tactic.NormNum
def BIG_PRIME := 89
theorem ZMod.val_ofNat {n : ℕ} [n.AtLeastTwo] (hn : n < BIG_PRIME) :
ZMod.val (n := BIG_PRIME) ofNat(n) = ofNat(n) := Nat.mod_eq_of_lt hn
example : ZMod.val (16 : ZMod BIG_PRIME) = 16 := by
rw [ZMod.val_ofNat]
norm_num [BIG_PRIME]
Yaël Dillies (Mar 29 2025 at 16:52):
Sorry, I realise that was a typo in my original message. Fixed
Edward van de Meent (Mar 29 2025 at 16:53):
what is hn : BIG_PRIME
supposed to mean?
Edward van de Meent (Mar 29 2025 at 16:54):
btw, presumably you want ofNat(n)
in the type of foo'
Yaël Dillies (Mar 29 2025 at 16:54):
Fixed
Yaël Dillies (Mar 29 2025 at 16:55):
Argh, I got confused between Jeremy's snippet and mine when copying from gitpod
Yaël Dillies (Mar 29 2025 at 16:58):
Fixed for good this time
Edward van de Meent (Mar 29 2025 at 16:59):
btw, you can change foo
to drop the lt
assumption, presuming norm_num
does some reduction later:
theorem foo'' {n : ℕ} [n.AtLeastTwo] :
ZMod.val (n := BIG_PRIME) ofNat(n) = ofNat(n) % BIG_PRIME := rfl
example : ZMod.val (16 : ZMod BIG_PRIME) = 16 := by
rw [foo'']
norm_num [BIG_PRIME]
-- exact Nat.lt_of_sub_eq_succ rfl -- no longer necessary
Yaël Dillies (Mar 29 2025 at 17:01):
You're outdated compared to my snippet :wink:
Antoine Chambert-Loir (Mar 29 2025 at 17:01):
I think I understand why that worked. OfNat.ofNat
has the following form:
{α : Type u} → (x : ℕ) → [self : OfNat α x] → α
so one needs to produce the instance. (For a natural number, there is docs#instOfNatNat.)
The following gives it, but, again, I don't know what I'm doing:
instance instOfNatZMod (p n : Nat) : OfNat (ZMod p) n := by
exact ⟨n⟩
theorem junk' (p : ℕ) (n : ℕ) (h : n < p) :
ZMod.val (OfNat.ofNat n : ZMod p) = n := by
simp only [OfNat.ofNat, ZMod.val_natCast]
rw [Nat.mod_eq, if_neg]
simp only [not_and, not_le]
exact fun _ => h
example : ZMod.val (16 : ZMod BIG_PRIME) = 16 := by
apply junk'
simp [BIG_PRIME]
Edward van de Meent (Mar 29 2025 at 17:02):
also of interest maybe: docs#ZMod.val_natCast
Yaël Dillies (Mar 29 2025 at 17:05):
You really shouldn't be writing any more OfNat
instance, as these are very prone to causing diamonds
Yaël Dillies (Mar 29 2025 at 17:06):
Here is the mathlib-approved version of everything I've said so far:
import Mathlib.Data.ZMod.Basic
import Mathlib.Tactic.NormNum
lemma ZMod.val_ofNat (n a : ℕ) [a.AtLeastTwo] :
ZMod.val (n := n) ofNat(a) = ofNat(a) % n := ZMod.val_natCast _
lemma ZMod.val_ofNat_of_lt {n a : ℕ} [a.AtLeastTwo] (han : a < n) :
ZMod.val (n := n) ofNat(a) = ofNat(a) := ZMod.val_natCast_of_lt han
def BIG_PRIME := 89
example : ZMod.val (16 : ZMod BIG_PRIME) = 16 := by
rw [ZMod.val_ofNat]; norm_num [BIG_PRIME]
Edward van de Meent (Mar 29 2025 at 17:09):
for completeness, we also have:
example : ZMod.val (16 : ZMod BIG_PRIME) = 16 := by
apply ZMod.val_ofNat_of_lt; norm_num [BIG_PRIME]
(assuming yaël's snippet)
Jeremy Avigad (Mar 29 2025 at 17:16):
This solves all my problems. Thanks!
Yaël Dillies (Mar 29 2025 at 17:39):
I've opened #23441 for the two lemmas
Edward van de Meent (Mar 29 2025 at 18:07):
CI complains about an error in Mathlib.NumberTheory.LegendreSymbol.AddCharacter
Yaël Dillies (Mar 29 2025 at 18:07):
Just fixed it now
Antoine Chambert-Loir (Mar 29 2025 at 20:07):
Additional question: What does the syntax ofNat(n)
means? — with parentheses and no space?
Ruben Van de Velde (Mar 29 2025 at 20:25):
It means OfNat.ofNat n
Edward van de Meent (Mar 29 2025 at 20:27):
i think it does a tiny bit more than just that though? I seem to recall it adds something which makes stuff like simp
ignore the OfNat.ofNat
or something? something to do with no_index
Edward van de Meent (Mar 29 2025 at 20:35):
looking at the thread mentioned at the docs of ofNat(.)
, it seems to explain that simp
won't match on something like 3
if the simp lemma mentions a plain OfNat.ofNat
. It is verified experimentally that indeed using ofNat(.)
instead on both sides of such simp lemmas improves simp
behaviour
Edward van de Meent (Mar 29 2025 at 20:36):
#mathlib4 > writing lemmas about `ofNat` @ 💬 <- this thread
Last updated: May 02 2025 at 03:31 UTC