Skip to content

Commit 69ef6f5

Browse files
jcommelinthorimurkmill
committed
feat: port Archive.Examples.MersennePrimes (leanprover-community#5704)
Co-authored-by: thorimur <68410468+thorimur@users.noreply.github.com> Co-authored-by: Kyle Miller <kmill31415@gmail.com>
1 parent 85af2e4 commit 69ef6f5

File tree

3 files changed

+191
-60
lines changed

3 files changed

+191
-60
lines changed

Archive.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Archive.Arithcc
2+
import Archive.Examples.MersennePrimes
23
import Archive.Examples.PropEncodable
34
import Archive.Imo.Imo1959Q1
45
import Archive.Imo.Imo1960Q1
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/-
2+
Copyright (c) 2020 Scott Morrison. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Scott Morrison
5+
6+
! This file was ported from Lean 3 source module examples.mersenne_primes
7+
! leanprover-community/mathlib commit 58581d0fe523063f5651df0619be2bf65012a94a
8+
! Please do not edit these lines, except to modify the commit id
9+
! if you have ported upstream changes.
10+
-/
11+
import Mathlib.NumberTheory.LucasLehmer
12+
13+
/-!
14+
# Explicit Mersenne primes
15+
16+
We run some Lucas-Lehmer tests to prove the first Mersenne primes are prime.
17+
18+
See the discussion at the end of [Mathlib/NumberTheory/LucasLehmer.lean]
19+
for ideas about extending this to larger Mersenne primes.
20+
-/
21+
22+
-- The Lucas-Lehmer test does not apply to `mersenne 2`
23+
example : ¬ LucasLehmerTest 2 := by norm_num
24+
25+
example : (mersenne 2).Prime := by decide
26+
27+
example : (mersenne 3).Prime :=
28+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
29+
30+
example : (mersenne 5).Prime :=
31+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
32+
33+
example : (mersenne 7).Prime :=
34+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
35+
36+
example : (mersenne 13).Prime :=
37+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
38+
39+
example : (mersenne 17).Prime :=
40+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
41+
42+
example : (mersenne 19).Prime :=
43+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
44+
45+
/-- 2147483647.Prime, Euler (1772) -/
46+
example : (mersenne 31).Prime :=
47+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
48+
49+
/-- Pervushin (1883), Seelhoff (1886) -/
50+
example : (mersenne 61).Prime :=
51+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
52+
53+
example : (mersenne 89).Prime :=
54+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
55+
56+
example : (mersenne 107).Prime :=
57+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
58+
59+
/-- Édouard Lucas (1876) -/
60+
example : (mersenne 127).Prime :=
61+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
62+
63+
/-- Lehmer and Robinson using SWAC computer, (1952) -/
64+
example : (mersenne 521).Prime :=
65+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
66+
67+
example : (mersenne 607).Prime :=
68+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
69+
70+
example : (mersenne 1279).Prime :=
71+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
72+
73+
example : (mersenne 2203).Prime :=
74+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
75+
76+
example : (mersenne 2281).Prime :=
77+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
78+
79+
example : (mersenne 3217).Prime :=
80+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
81+
82+
example : (mersenne 4253).Prime :=
83+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
84+
85+
example : (mersenne 4423).Prime :=
86+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
87+
88+
-- First failure ("deep recursion detected")
89+
/-
90+
example : (mersenne 9689).Prime :=
91+
lucas_lehmer_sufficiency _ (by norm_num) (by norm_num)
92+
-/

Mathlib/NumberTheory/LucasLehmer.lean

Lines changed: 98 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@ import Mathlib.Tactic.IntervalCases
2121
We define `lucasLehmerResidue : Π p : ℕ, ZMod (2^p - 1)`, and
2222
prove `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
3836
This development began as a student project by Ainsley Pahljina,
3937
and was then cleaned up for mathlib by Scott Morrison.
4038
The 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+
/-
173176
instance : DecidablePred LucasLehmerTest :=
174177
inferInstanceAs (DecidablePred (lucasLehmerResidue · = 0))
178+
-/
175179

176180
/-- `q` is defined as the minimum factor of `mersenne p`, bundled as an `ℕ+`. -/
177181
def 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`.
525524
namespace 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 : 12 ^ 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 : 22 ^ 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
586624
There's still low hanging fruit available to do faster computations
587625
based on the formula

0 commit comments

Comments
 (0)