Zulip Chat Archive
Stream: mathlib4
Topic: Working with Nat.factorization
Eric Wieser (Aug 01 2024 at 14:01):
In lean 4.7.0, this proof worked just fine:
import Mathlib
#eval (Nat.factorization 10485760000000000000000000) 5
example : (Nat.factorization 10485760000000000000000000) 5 = 19 := by
rfl -- fails in latest lean
but since docs#Nat.find is now (transitively) irreducible, it no longer goes through. What's the preferred way to prove things like the statement above? Should Nat.find
be globally overriden to be semireducible?
Yaël Dillies (Aug 01 2024 at 14:07):
@Kyle Miller suggested a reimplementation of Nat.find
in the past
Eric Wieser (Aug 01 2024 at 14:11):
here I assume
Eric Wieser (Aug 01 2024 at 14:28):
In the meantime, what's the preferred way to prove the above?
Matthew Ballard (Aug 01 2024 at 14:38):
Eric Wieser said:
since docs#Nat.find is now (transitively) irreducible, it no longer goes through.
Shouldn't this mean that with_unfolding_all rfl
goes through?
Eric Wieser (Aug 01 2024 at 14:43):
This works, but seems pretty silly:
theorem Nat.factorization_list_prod {l : List ℕ} (hS : ∀ x ∈ l, x ≠ 0) :
l.prod.factorization = (l.map Nat.factorization).sum := by
induction l with
| nil => simp
| cons head tail ih =>
rw [List.prod_cons, List.map_cons, Nat.factorization_mul, List.sum_cons, ih]
· aesop
· aesop
· aesop
example : (Nat.factorization 10485760000000000000000000) 5 = 19 := by
change (Nat.factorization (eval% Nat.primeFactorsList 10485760000000000000000000).prod) 5 = 19
rw [Nat.factorization_list_prod]
· simp [List.map_cons, Nat.Prime.factorization, Nat.prime_two, Nat.prime_five, List.sum_cons]
· decide
Michael Stoll (Aug 01 2024 at 15:01):
It does not look like docs#Nat.find is irreducible. Trying
import Mathlib
#eval (Nat.factorization 10485760000000000000000000) 5
unseal Nat.find in
example : (Nat.factorization 10485760000000000000000000) 5 = 19 := by
rfl -- fails in latest lean
gives the error
failed to set `[local semireducible]`, `Nat.find` is currently `[semireducible]`, `[irreducible]` expected
use `set_option allowUnsafeReducibility true` to override reducibility status validation
and indeed, there is no @[irreducible]
in the source.
Ruben Van de Velde (Aug 01 2024 at 15:01):
There was a recent change where definitions based on well-founded recursion became irreducible without being tagged explicitly (if my memory serves me right)
Matthew Ballard (Aug 01 2024 at 15:03):
Ruben Van de Velde said:
There was a recent change where definitions based on well-founded recursion became irreducible without being tagged explicitly (if my memory serves me right)
Does this break with_unfolding_all
?
Eric Wieser (Aug 01 2024 at 15:04):
I think the issue here is that well-founded recursion is used?
Michael Stoll (Aug 01 2024 at 15:04):
But why does Lean claim that `Nat.find` is currently `[semireducible]`
?
Michael Stoll (Aug 01 2024 at 15:09):
Same with docs#Nat.findX, which is the actual definition using well-founded recursion.
Eric Wieser (Aug 01 2024 at 15:09):
They're all semireducible, but wrap Acc.rec
which isn't
Michael Stoll (Aug 01 2024 at 15:40):
...and apparently one cannot make Acc.rec
(semi)reducible; at least unseal Acc.rec in
says
failed to set reducibility status, `Acc.rec` is not a definition
Matthew Ballard (Aug 01 2024 at 15:50):
Matthew Ballard (Aug 01 2024 at 15:51):
I didn't test whether Eric's example now works
Matthew Ballard (Aug 01 2024 at 16:31):
Matthew Ballard said:
I didn't test whether Eric's example now works
It doesn't
Eric Wieser (Aug 01 2024 at 16:31):
I suspect the path forward here includes writing a norm_num
extension for padicValNat
and factorization
; I tried to make a start with
@[norm_num DFunLike.coe (Nat.factorization _) _]
def evalNatFactorization : NormNumExt where eval {u α} e := do
match u, α, e with
| 0, ~q(ℕ), ~q(Nat.factorization $m $p) =>
trace[debug] "Ok"
let ⟨mlit, hm⟩ ← deriveNat m q(inferInstance)
let ⟨plit, hp⟩ ← deriveNat p q(inferInstance)
trace[debug] m!"Testing primality of {p}"
match ← deriveBool q(Prime $plit) with
| ⟨true, (hp : Q(Prime $plit))⟩ =>
trace[debug] "Found a prime"
-- let nlit : Q(ℕ) := mkRawNatLit <| padicValNat plit.natLit! mlit.natLit!
failure
| ⟨false, (hp : Q(¬ Prime $plit))⟩ =>
trace[debug] "Not a prime"
return .isNat q(inferInstance) q(nat_lit 0) q(⟨factorization_eq_zero_of_non_prime _ $hp⟩)
| _, _, _ => failure
set_option trace.debug true
-- test-cases
example : Nat.factorization 24 4 = 0 := by
norm_num
example (x) (hx : x = 3) : Nat.factorization 24 2 = 3 := by
norm_num
rw [hx]
but deriveBool
doesn't seem to work
Matthew Ballard (Aug 01 2024 at 17:34):
Eric Wieser said:
In lean 4.7.0, this proof worked just fine:
Is there specific commit where this works because I went all the way back to v4.2.0
and nothing worked
Eric Wieser (Aug 01 2024 at 17:52):
Hmm, perhaps I minimized badly here :(.
Matthew Ballard (Aug 01 2024 at 18:39):
set_option maxRecDepth 10000 in
example : (Nat.factorization 10485760000000000000000000) 5 = 19 := by
simp (config := { ground := true })
might do it if can finish. It can at least do smaller numbers.
Matthew Ballard (Aug 01 2024 at 19:54):
example : (Nat.factorization 10485760000000000000000000) 5 = 19 := by
have : Nat.Prime 5 := by decide
unfold Nat.factorization; simp
split_ifs
unfold padicValNat; simp
unfold multiplicity; simp
unfold Nat.find; simp
unfold Nat.findX; simp
rw [WellFounded.fix_eq]; dsimp; split_ifs with h'; swap; exact h' (by decide)
iterate 18 (rw [WellFounded.fix_eq]; dsimp; split_ifs with h'; swap; simp; exact h' (by decide))
rw [WellFounded.fix_eq]; dsimp; split_ifs with h'; swap; rfl
exact False.elim <| (by decide : ¬ _ ∣ _) h'
is unpleasant but works.
Michael Stoll (Aug 01 2024 at 20:07):
Would it help to have a version of docs#Nat.find that takes an explicit upper bound? E.g.
def Nat.find' (P : Nat → Prop) [DecidablePred P] : Nat → Nat
| 0 => 0
| n + 1 => let r := find' P n
if r < n then r else
if P n then n
else n + 1
such that Nat.find P bound
is the smallest n < bound
that satisfies P
if such an n
exists, and is bound
otherwise.
I would think that in many uses of Nat.find
, an explicit upper bound is available.
Matthew Ballard (Aug 01 2024 at 20:33):
Probably not I take that back. The following is a more reasonable toy model and works ok
def foo (n : ℕ) (hn : n ≤ 100) : ℕ :=
go n 0 hn
where
go (a b : ℕ) (ha : a + b ≤ 100) :=
if h : a = 0 then b
else go (a - 1) (b + 1) (by omega)
termination_by 100 - b
decreasing_by
simp_wf
omega
#eval foo 5 (by decide)
example : foo 5 (by decide) = 5 := by
rfl
Michael Stoll (Aug 02 2024 at 10:48):
def Nat.find'_aux (P : Nat → Prop) [DecidablePred P] (a n : Nat) : Nat → Nat
| 0 => n
| k + 1 => if P a then a else find'_aux P (a + 1) n k
def Nat.find' (P : Nat → Prop) [DecidablePred P] (n : Nat) : Nat :=
find'_aux P 0 n n
def Nat.mult_aux (k m n : Nat) : Prop := k ^ m ∣ n ∧ ¬ k ^ (m + 1) ∣ n
instance : ∀ k n : Nat, DecidablePred (Nat.mult_aux k · n) :=
fun _ _ _ => instDecidableAnd
def Nat.multiplicity' (m n : Nat) : Nat :=
if n = 0 then 0 else Nat.find' (mult_aux m · n) n
example : Nat.multiplicity' 5 10485760000000000000000000 = 19 := rfl
(using 187 heartbeats)
Michael Stoll (Aug 09 2024 at 15:32):
Here is a more fleshed-out (and somewhat improved) version that includes a proof that Nat.multiplicity'
agrees with docs#multiplicity:
import Mathlib.RingTheory.Multiplicity
/-- A missing API lemma for `PartENat.find`. -/
lemma PartENat.find_eq_natFind {P : Nat → Prop} [DecidablePred P] (h : ∃ n, P n) :
PartENat.find P = Nat.find h := by
apply Part.ext'
· simp only [find_dom _ h, dom_natCast]
· simp only [dom_natCast, find_get, get_natCast', imp_self, implies_true]
namespace Nat
/-- Helper function for `Nat.findWithBound`. -/
@[simp]
def findWithBound_aux (P : Nat → Prop) [DecidablePred P] (n : Nat) : Nat → Nat
| 0 => n
| k + 1 => if P n then n else findWithBound_aux P (n + 1) k
/-- The defining property of `Nat.findWithBound_aux`. -/
lemma findWithBound_aux_spec (P : Nat → Prop) [DecidablePred P] (n k m : Nat) :
findWithBound_aux P n k = m ↔
n ≤ m ∧ m ≤ k + n ∧ (∀ l, n ≤ l → l < m → ¬ P l) ∧ (P m ∨ m = k + n) := by
induction k generalizing n with
| zero =>
simp only [findWithBound_aux, zero_add]
refine ⟨fun H ↦ ?_, fun ⟨H₁, H₂, _⟩ ↦ le_antisymm H₁ H₂⟩
simpa only [H, le_refl, zero_add, or_true, and_true, true_and]
using fun _ hl₁ hl₂ _ ↦ (hl₂.trans_le hl₁).false
| succ k ih =>
simp only [findWithBound_aux]
by_cases hP : P n <;> simp only [hP, ↓reduceIte]
· refine ⟨fun H ↦ ?_, fun ⟨H₁, _, H₃, _⟩ ↦ ?_⟩
· simpa only [H, le_refl, le_add_iff_nonneg_left, _root_.zero_le, self_eq_add_left,
add_eq_zero, one_ne_zero, and_false, or_false, true_and]
using ⟨fun _ hl₁ hl₂ _ ↦ (hl₂.trans_le hl₁).false, H ▸ hP⟩
· exact le_antisymm H₁ <| le_of_not_lt <| (imp_not_comm.mp <| H₃ n le_rfl) hP
· specialize ih (n + 1)
rw [show k + (n + 1) = k + 1 + n by rw [add_comm n, add_assoc]] at ih
refine ⟨fun H ↦ ?_, fun ⟨H₁, H₂, H₃, H₄⟩ ↦ ?_⟩
· obtain ⟨ih₁, ih₂, ih₃, ih₄⟩ := ih.mp H
refine ⟨(le_add_right n 1).trans ih₁, ih₂, fun l hl₁ hl₂ ↦ ?_, ih₄⟩
exact (le_iff_lt_or_eq.mp hl₁).elim (fun h ↦ ih₃ l h hl₂) fun h ↦ h ▸ hP
· have H : n ≠ m := by
rintro rfl
exact (zero_ne_add_one k).symm <| self_eq_add_left.mp <| H₄.resolve_left hP
exact ih.mpr ⟨lt_of_le_of_ne H₁ H, H₂, fun l h ↦ H₃ l ((le_add_right n 1).trans h), H₄⟩
/-- `Nat.findWithBound P n` returns the smallest `m ≤ n` such that `P m` holds if such an `m`
exists; otherwise, `Nat.findWithBound P n = n`. -/
def findWithBound (P : Nat → Prop) [DecidablePred P] (n : Nat) : Nat :=
findWithBound_aux P 0 n
/-- The defining property of `Nat.findWithBound`. -/
lemma findWithBound_eq_iff (P : Nat → Prop) [DecidablePred P] (n m : ℕ) :
findWithBound P n = m ↔ m ≤ n ∧ (∀ k < m, ¬ P k) ∧ (P m ∨ m = n) := by
simpa only [_root_.zero_le, add_zero, true_implies, true_and] using findWithBound_aux_spec P 0 ..
lemma findWithBound_spec (P : Nat → Prop) [DecidablePred P] (n : Nat) :
P (findWithBound P n) ∨ findWithBound P n = n :=
((findWithBound_eq_iff P n _).mp rfl).2.2
lemma findWithBound_le' (P : Nat → Prop) [DecidablePred P] (n : Nat) :
findWithBound P n ≤ n :=
((findWithBound_eq_iff P n _).mp rfl).1
lemma findWithBound_le {P : Nat → Prop} [DecidablePred P] (n : Nat) {m : Nat} (h : P m) :
findWithBound P n ≤ m :=
le_of_not_lt <| (imp_not_comm.mp <| ((findWithBound_eq_iff P n _).mp rfl).2.1 m) h
lemma findWithBound_min {P : Nat → Prop} [DecidablePred P] {n m : Nat}
(hm : m < findWithBound P n) :
¬P m :=
((findWithBound_eq_iff P n _).mp rfl).2.1 m hm
/-- `Nat.findWithBound P n` agrees with `Nat.find (_ : ∃ m, P m)` if `P m` holds
for some `m ≤ n`. -/
lemma findWithBound_eq_find_of_witness {P : Nat → Prop} [DecidablePred P] {m n : Nat} (h : m ≤ n)
(hP : P m) :
findWithBound P n = Nat.find ⟨m, hP⟩ := by
rw [findWithBound_eq_iff]
refine ⟨(find_le_iff ⟨m, hP⟩ n).mpr ⟨m, h, hP⟩, ?_, .inl <| Nat.find_spec ⟨m, hP⟩⟩
exact fun k hk ↦ (lt_find_iff ⟨m, hP⟩ k).mp hk k le_rfl
/-- A constructive version of `multiplicity` for natural numbers. -/
def multiplicity' (m n : Nat) : PartENat :=
if n = 0 ∨ m = 1 then ⊤ else findWithBound (fun k ↦ ¬ m ^ (k + 1) ∣ n) n
/-- `Nat.multiplicity' m n` agrees with `multiplicity m n` for `m n : Nat`. -/
lemma multiplicity'_eq_multiplicity (m n : Nat) : multiplicity' m n = multiplicity m n := by
rcases eq_or_ne n 0 with rfl | hn
· simp only [multiplicity', true_or, ↓reduceIte, multiplicity.zero]
rcases eq_or_ne m 1 with rfl | hm
· simp only [multiplicity', or_true, ↓reduceIte, isUnit_one, multiplicity.isUnit_left]
simp only [multiplicity', hn, hm, or_self, ↓reduceIte]
have key : ¬ m ^ (n + 1) ∣ n := by
intro H
rcases eq_or_ne m 0 with rfl | hm₀
· exact hn <| zero_dvd_iff.mp <| (zero_pow (M₀ := Nat) <| succ_ne_zero n) ▸ H
· exact lt_irrefl n <|
calc n
_ < n + 1 := lt_add_one n
_ < m ^ (n + 1) := lt_pow_self (by omega) _
_ ≤ n := le_of_dvd (zero_lt_of_ne_zero hn) H
rw [findWithBound_eq_find_of_witness le_rfl key]
exact (PartENat.find_eq_natFind ⟨_, key⟩).symm
count_heartbeats in -- 280
example : multiplicity 5 10485760000000000000000000 = 19 := by
rw [← multiplicity'_eq_multiplicity]
rfl
Michael Stoll (Aug 09 2024 at 15:40):
Some questions that came to my mind:
- Would something along the lines of
Nat.findWithBound
be a reasonable addition to Mathlib? - And if so, should docs#padicValNat (and possibly similar and maybe further definitions) be refactored to use it?
- What is the rationale for having both docs#ENat and docs#PartENat ?
Michael Stoll (Aug 10 2024 at 14:54):
@Eric Wieser @Matthew Ballard @Kyle Miller :up: ?
Matthew Ballard (Aug 10 2024 at 15:08):
I have been ruminating
Last updated: May 02 2025 at 03:31 UTC