@@ -21,10 +21,8 @@ import Mathlib.Tactic.IntervalCases
2121We define `lucasLehmerResidue : Π p : ℕ, ZMod (2^p - 1)`, and
2222prove `lucasLehmerResidue p = 0 → Prime (mersenne p)`.
2323
24- Porting note: the tactics have not been ported yet.
25-
26- We construct a tactic `LucasLehmer.run_test`, which iteratively certifies the arithmetic
27- required to calculate the residue, and enables us to prove
24+ We construct a `norm_num` extension to calculate this residue to certify primality of Mersenne
25+ primes using `lucas_lehmer_sufficiency`.
2826
2927
3028## TODO
@@ -38,6 +36,8 @@ required to calculate the residue, and enables us to prove
3836This development began as a student project by Ainsley Pahljina,
3937and was then cleaned up for mathlib by Scott Morrison.
4038The tactic for certified computation of Lucas-Lehmer residues was provided by Mario Carneiro.
39+ This tactic was ported by Thomas Murrills to Lean 4, and then it was converted to a `norm_num`
40+ extension and made to use kernel reductions by Kyle Miller.
4141-/
4242
4343
@@ -170,8 +170,12 @@ def LucasLehmerTest (p : ℕ) : Prop :=
170170 lucasLehmerResidue p = 0
171171#align lucas_lehmer.lucas_lehmer_test LucasLehmer.LucasLehmerTest
172172
173+ -- Porting note: We have a fast `norm_num` extension, and we would rather use that than accidentally
174+ -- have `simp` use `decide`!
175+ /-
173176instance : DecidablePred LucasLehmerTest :=
174177 inferInstanceAs (DecidablePred (lucasLehmerResidue · = 0))
178+ -/
175179
176180/-- `q` is defined as the minimum factor of `mersenne p`, bundled as an `ℕ+`. -/
177181def q (p : ℕ) : ℕ+ :=
@@ -517,71 +521,105 @@ theorem lucas_lehmer_sufficiency (p : ℕ) (w : 1 < p) : LucasLehmerTest p → (
517521 exact not_lt_of_ge (Nat.sub_le _ _) h
518522#align lucas_lehmer_sufficiency lucas_lehmer_sufficiency
519523
520- -- Here we calculate the residue, very inefficiently, using `dec_trivial`. We can do much better.
521- example : (mersenne 5 ).Prime :=
522- lucas_lehmer_sufficiency 5 (by norm_num) (by decide)
523-
524- -- Next we use `norm_num` to calculate each `s p i`.
525524namespace LucasLehmer
526525
527- open Tactic
526+ /-!
527+ ### `norm_num` extension
528+
529+ Next we define a `norm_num` extension that calculates `LucasLehmerTest p` for `1 < p`.
530+ It makes use of a version of `sMod` that is specifically written to be reducible by the
531+ Lean 4 kernel, which has the capability of efficiently reducing natural number expressions.
532+ With this reduction in hand, it's a simple matter of applying the lemma
533+ `LucasLehmer.residue_eq_zero_iff_sMod_eq_zero`.
534+
535+ See [ Archive/Examples/MersennePrimes.lean ] for certifications of all Mersenne primes
536+ up through `mersenne 4423`.
537+ -/
528538
529- theorem sMod_succ {p a i b c} (h1 : (2 ^ p - 1 : ℤ) = a) (h2 : sMod p i = b)
530- (h3 : (b * b - 2 ) % a = c) : sMod p (i + 1 ) = c := by
531- substs a b c
532- rw [← sq]
539+ namespace norm_num_ext
540+ open Qq Lean Elab.Tactic Mathlib.Meta.NormNum
541+
542+ /-- Version of `sMod` that is `ℕ`-valued. One should have `q = 2 ^ p - 1`.
543+ This can be reduced by the kernel. -/
544+ def sMod' (q : ℕ) : ℕ → ℕ
545+ | 0 => 4 % q
546+ | i + 1 => (sMod' q i ^ 2 + (q - 2 )) % q
547+
548+ theorem sMod'_eq_sMod (p k : ℕ) (hp : 2 ≤ p) : (sMod' (2 ^ p - 1 ) k : ℤ) = sMod p k := by
549+ have h1 := calc
550+ 4 = 2 ^ 2 := by norm_num
551+ _ ≤ 2 ^ p := Nat.pow_le_pow_of_le_right (by norm_num) hp
552+ have h2 : 1 ≤ 2 ^ p := by linarith
553+ induction k with
554+ | zero =>
555+ rw [sMod', sMod, Int.ofNat_emod]
556+ simp [h2]
557+ | succ k ih =>
558+ rw [sMod', sMod, ← ih]
559+ have h3 : 2 ≤ 2 ^ p - 1 := by
560+ zify [h2]
561+ calc
562+ (2 : Int) ≤ 4 - 1 := by norm_num
563+ _ ≤ 2 ^ p - 1 := by zify at h1; exact Int.sub_le_sub_right h1 _
564+ zify [h2, h3]
565+ rw [← add_sub_assoc, sub_eq_add_neg, add_assoc, add_comm _ (-2 ), ← add_assoc,
566+ Int.add_emod_self, ← sub_eq_add_neg]
567+
568+ lemma testTrueHelper (p : ℕ) (hp : Nat.blt 1 p = true ) (h : sMod' (2 ^ p - 1 ) (p - 2 ) = 0 ) :
569+ LucasLehmerTest p := by
570+ rw [Nat.blt_eq] at hp
571+ rw [LucasLehmerTest, LucasLehmer.residue_eq_zero_iff_sMod_eq_zero p hp, ← sMod'_eq_sMod p _ hp, h]
533572 rfl
534- #align lucas_lehmer.s_mod_succ LucasLehmer.sMod_succ
535-
536- -- /- ./././Mathport/Syntax/Translate/Expr.lean:330:4: warning: unsupported (TODO): `[ tacs ] -/
537- -- /- ./././Mathport/Syntax/Translate/Expr.lean:330:4: warning: unsupported (TODO): `[ tacs ] -/
538- -- /- ./././Mathport/Syntax/Translate/Expr.lean:330:4: warning: unsupported (TODO): `[ tacs ] -/
539- -- /-- Given a goal of the form `lucas_lehmer_test p`,
540- -- attempt to do the calculation using `norm_num` to certify each step.
541- -- -/
542- -- unsafe def run_test : tactic Unit := do
543- -- let q(LucasLehmerTest $(p)) ← target
544- -- sorry
545- -- sorry
546- -- let p ← eval_expr ℕ p
547- -- let-- Calculate the candidate Mersenne prime
548- -- M : ℤ := 2 ^ p - 1
549- -- let t ← to_expr ``(2 ^ $(q(p)) - 1 = $(q(M)))
550- -- let v ← to_expr ``((by norm_num : 2 ^ $(q(p)) - 1 = $(q(M))))
551- -- let w ← assertv `w t v
552- -- let t
553- -- ←-- base case
554- -- to_expr
555- -- ``(sMod $(q(p)) 0 = 4)
556- -- let v ← to_expr ``((by norm_num [ LucasLehmer.sMod ] : sMod $(q(p)) 0 = 4))
557- -- let h ← assertv `h t v
558- -- -- step case, repeated p-2 times
559- -- iterate_exactly
560- -- (p - 2) sorry
561- -- let h
562- -- ←-- now close the goal
563- -- get_local
564- -- `h
565- -- exact h
566- -- #align lucas_lehmer.run_test lucas_lehmer.run_test
567573
568- end LucasLehmer
574+ lemma testFalseHelper (p : ℕ) (hp : Nat.blt 1 p = true )
575+ (h : Nat.ble 1 (sMod' (2 ^ p - 1 ) (p - 2 ))) : ¬ LucasLehmerTest p := by
576+ rw [Nat.blt_eq] at hp
577+ rw [Nat.ble_eq, Nat.succ_le, Nat.pos_iff_ne_zero] at h
578+ rw [LucasLehmerTest, LucasLehmer.residue_eq_zero_iff_sMod_eq_zero p hp, ← sMod'_eq_sMod p _ hp]
579+ simpa using h
580+
581+ theorem isNat_lucasLehmerTest : {p np : ℕ} →
582+ IsNat p np → LucasLehmerTest np → LucasLehmerTest p
583+ | _, _, ⟨rfl⟩, h => h
584+
585+ theorem isNat_not_lucasLehmerTest : {p np : ℕ} →
586+ IsNat p np → ¬ LucasLehmerTest np → ¬ LucasLehmerTest p
587+ | _, _, ⟨rfl⟩, h => h
588+
589+ /-- Calculate `LucasLehmer.LucasLehmerTest p` for `2 ≤ p` by using kernel reduction for the
590+ `sMod'` function. -/
591+ @ [norm_num LucasLehmer.LucasLehmerTest (_ : ℕ)]
592+ def evalLucasLehmerTest : NormNumExt where eval {u α} e := do
593+ let .app _ (p : Q(ℕ)) ← Meta.whnfR e | failure
594+ let sℕ : Q(AddMonoidWithOne ℕ) := q(instAddMonoidWithOneNat)
595+ let ⟨ep, hp⟩ ← deriveNat p
596+ let np := ep.natLit!
597+ unless 1 < np do
598+ failure
599+ have h1ltp : Q(Nat.blt 1 $ep) := (q(Eq.refl true ) : Expr)
600+ if sMod' (2 ^ np - 1 ) (np - 2 ) = 0 then
601+ have hs : Q(sMod' (2 ^ $ep - 1 ) ($ep - 2 ) = 0 ) := (q(Eq.refl 0 ) : Expr)
602+ have pf : Q(LucasLehmerTest $ep) := q(testTrueHelper $ep $h1ltp $hs)
603+ have pf' : Q(LucasLehmerTest $p) := q(isNat_lucasLehmerTest $hp $pf)
604+ return .isTrue pf'
605+ else
606+ have hs : Q(Nat.ble 1 (sMod' (2 ^ $ep - 1 ) ($ep - 2 )) = true ) := (q(Eq.refl true ) : Expr)
607+ have pf : Q(¬ LucasLehmerTest $ep) := q(testFalseHelper $ep $h1ltp $hs)
608+ have pf' : Q(¬ LucasLehmerTest $p) := q(isNat_not_lucasLehmerTest $hp $pf)
609+ return .isFalse pf'
610+
611+ end norm_num_ext
569612
570- -- /-- We verify that the tactic works to prove `127.prime`. -/
571- -- example : (mersenne 7).Prime :=
572- -- lucas_lehmer_sufficiency _ (by norm_num)
573- -- (by
574- -- run_tac
575- -- lucas_lehmer.run_test)
613+ end LucasLehmer
576614
577615/-!
578- This implementation works successfully to prove `(2^127 - 1).prime`,
579- and all the Mersenne primes up to this point appear in [ archive/examples/mersenne_primes.lean ] .
616+ This implementation works successfully to prove `(2^4423 - 1).Prime`,
617+ and all the Mersenne primes up to this point appear in [ Archive/Examples/MersennePrimes.lean ] .
618+ These can be calculated nearly instantly, and `(2^9689 - 1).Prime` only fails due to deep
619+ recursion.
580620
581- `(2^127 - 1).prime` takes about 5 minutes to run (depending on your CPU!),
582- and unfortunately the next Mersenne prime `(2^521 - 1)`,
583- which was the first "computer era" prime,
584- is out of reach with the current implementation.
621+ (Note by kmill: the following notes were for the Lean 3 version. They seem like they could still
622+ be useful, so I'm leaving them here.)
585623
586624There's still low hanging fruit available to do faster computations
587625based on the formula
0 commit comments