The Lucas-Lehmer test for Mersenne primes.
We define lucas_lehmer_residue : Π p : ℕ, zmod (2^p - 1)
, and
prove lucas_lehmer_residue p = 0 → prime (mersenne p)
.
We construct a tactic lucas_lehmer.run_test
, which iteratively certifies the arithmetic
required to calculate the residue, and enables us to prove
example : prime (mersenne 127) :=
lucas_lehmer_sufficiency _ (by norm_num) (by lucas_lehmer.run_test)
TODO
- Show reverse implication.
- Speed up the calculations using
n ≡ (n % 2^p) + (n / 2^p) [MOD 2^p - 1]
. - Find some bigger primes!
History
This development began as a student project by Ainsley Pahljina, and was then cleaned up for mathlib by Scott Morrison. The tactic for certified computation of Lucas-Lehmer residues was provided by Mario Carneiro.
We now define three(!) different versions of the recurrence
s (i+1) = (s i)^2 - 2
.
These versions take values either in ℤ
, in zmod (2^p - 1)
, or
in ℤ
but applying % (2^p - 1)
at each step.
They are each useful at different points in the proof, so we take a moment setting up the lemmas relating them.
The recurrence s (i+1) = (s i)^2 - 2
in ℤ
.
Equations
- lucas_lehmer.s (i + 1) = lucas_lehmer.s i ^ 2 - 2
- lucas_lehmer.s 0 = 4
The recurrence s (i+1) = (s i)^2 - 2
in zmod (2^p - 1)
.
Equations
- lucas_lehmer.s_zmod p (i + 1) = lucas_lehmer.s_zmod p i ^ 2 - 2
- lucas_lehmer.s_zmod p 0 = 4
The recurrence s (i+1) = ((s i)^2 - 2) % (2^p - 1)
in ℤ
.
Equations
- lucas_lehmer.s_mod p (i + 1) = (lucas_lehmer.s_mod p i ^ 2 - 2) % (2 ^ p - 1)
- lucas_lehmer.s_mod p 0 = 4 % (2 ^ p - 1)
The Lucas-Lehmer residue is s p (p-2)
in zmod (2^p - 1)
.
Equations
A Mersenne number 2^p-1
is prime if and only if
the Lucas-Lehmer residue s p (p-2) % (2^p - 1)
is zero.
Equations
We construct the ring X q
as ℤ/qℤ + √3 ℤ/qℤ.
Equations
- lucas_lehmer.X.has_one = {one := (1, 0)}
Equations
- lucas_lehmer.X.monoid = {mul := has_mul.mul infer_instance, mul_assoc := _, one := (1, 0), one_mul := _, mul_one := _}
Equations
- lucas_lehmer.X.ring = {add := add_comm_group.add infer_instance, add_assoc := _, zero := add_comm_group.zero infer_instance, zero_add := _, add_zero := _, neg := add_comm_group.neg infer_instance, add_left_neg := _, add_comm := _, mul := monoid.mul infer_instance, mul_assoc := _, one := monoid.one infer_instance, one_mul := _, mul_one := _, left_distrib := _, right_distrib := _}
Equations
- lucas_lehmer.X.comm_ring = {add := ring.add infer_instance, add_assoc := _, zero := ring.zero infer_instance, zero_add := _, add_zero := _, neg := ring.neg infer_instance, add_left_neg := _, add_comm := _, mul := ring.mul infer_instance, mul_assoc := _, one := ring.one infer_instance, one_mul := _, mul_one := _, left_distrib := _, right_distrib := _, mul_comm := _}
Equations
The cardinality of X
is q^2
.
There are strictly fewer than q^2
units, since 0
is not a unit.
We define ω = 2 + √3
.
Equations
- lucas_lehmer.X.ω = (2, 1)
We define ωb = 2 - √3
, which is the inverse of ω
.
Equations
- lucas_lehmer.X.ωb = (2, -1)
A closed form for the recurrence relation.
Here and below, we introduce p' = p - 2
, in order to avoid using subtraction in ℕ
.
If 1 < p
, then q p
, the smallest prime factor of mersenne p
, is more than 2.
ω
as an element of the group of units.
Equations
- lucas_lehmer.ω_unit p = {val := lucas_lehmer.X.ω (lucas_lehmer.q p), inv := lucas_lehmer.X.ωb (lucas_lehmer.q p), val_inv := _, inv_val := _}
The order of ω
in the unit group is exactly 2^p
.
Given a goal of the form lucas_lehmer_test p
,
attempt to do the calculation using norm_num
to certify each step.
This implementation works successfully to prove (2^127 - 1).prime
,
and all the Mersenne primes up to this point appear in [archive/examples/mersenne_primes.lean].
(2^127 - 1).prime
takes about 5 minutes to run (depending on your CPU!),
and unfortunately the next Mersenne prime (2^521 - 1)
,
which was the first "computer era" prime,
is out of reach with the current implementation.
There's still low hanging fruit available to do faster computations
based on the formula
n ≡ (n % 2^p) + (n / 2^p) [MOD 2^p - 1]
and the fact that % 2^p
and / 2^p
can be very efficient on the binary representation.
Someone should do this, too!