The Lucas-Lehmer test for Mersenne primes. #
THIS FILE IS SYNCHRONIZED WITH MATHLIB4. Any changes to this file require a corresponding PR to mathlib4.
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
Instances for lucas_lehmer.lucas_lehmer_test
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 := _, npow := npow_rec (mul_one_class.to_has_mul (lucas_lehmer.X q)), npow_zero' := _, npow_succ' := _}
Equations
- lucas_lehmer.X.add_group_with_one = {int_cast := λ (n : ℤ), (↑n, 0), add := add_comm_group.add (lucas_lehmer.X.add_comm_group q), add_assoc := _, zero := add_comm_group.zero (lucas_lehmer.X.add_comm_group q), zero_add := _, add_zero := _, nsmul := add_comm_group.nsmul (lucas_lehmer.X.add_comm_group q), nsmul_zero' := _, nsmul_succ' := _, neg := add_comm_group.neg (lucas_lehmer.X.add_comm_group q), sub := add_comm_group.sub (lucas_lehmer.X.add_comm_group q), sub_eq_add_neg := _, zsmul := add_comm_group.zsmul (lucas_lehmer.X.add_comm_group q), zsmul_zero' := _, zsmul_succ' := _, zsmul_neg' := _, add_left_neg := _, nat_cast := λ (n : ℕ), (↑n, 0), one := monoid.one lucas_lehmer.X.monoid, nat_cast_zero := _, nat_cast_succ := _, int_cast_of_nat := _, int_cast_neg_succ_of_nat := _}
Equations
- lucas_lehmer.X.ring = {add := add_group_with_one.add lucas_lehmer.X.add_group_with_one, add_assoc := _, zero := add_group_with_one.zero lucas_lehmer.X.add_group_with_one, zero_add := _, add_zero := _, nsmul := add_group_with_one.nsmul lucas_lehmer.X.add_group_with_one, nsmul_zero' := _, nsmul_succ' := _, neg := add_group_with_one.neg lucas_lehmer.X.add_group_with_one, sub := add_group_with_one.sub lucas_lehmer.X.add_group_with_one, sub_eq_add_neg := _, zsmul := add_group_with_one.zsmul lucas_lehmer.X.add_group_with_one, zsmul_zero' := _, zsmul_succ' := _, zsmul_neg' := _, add_left_neg := _, add_comm := _, int_cast := add_group_with_one.int_cast lucas_lehmer.X.add_group_with_one, nat_cast := add_group_with_one.nat_cast lucas_lehmer.X.add_group_with_one, one := add_group_with_one.one lucas_lehmer.X.add_group_with_one, nat_cast_zero := _, nat_cast_succ := _, int_cast_of_nat := _, int_cast_neg_succ_of_nat := _, mul := monoid.mul infer_instance, mul_assoc := _, one_mul := _, mul_one := _, npow := monoid.npow infer_instance, npow_zero' := _, npow_succ' := _, 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 := _, nsmul := ring.nsmul infer_instance, nsmul_zero' := _, nsmul_succ' := _, neg := ring.neg infer_instance, sub := ring.sub infer_instance, sub_eq_add_neg := _, zsmul := ring.zsmul infer_instance, zsmul_zero' := _, zsmul_succ' := _, zsmul_neg' := _, add_left_neg := _, add_comm := _, int_cast := ring.int_cast infer_instance, nat_cast := ring.nat_cast infer_instance, one := ring.one infer_instance, nat_cast_zero := _, nat_cast_succ := _, int_cast_of_nat := _, int_cast_neg_succ_of_nat := _, mul := ring.mul infer_instance, mul_assoc := _, one_mul := _, mul_one := _, npow := ring.npow infer_instance, npow_zero' := _, npow_succ' := _, left_distrib := _, right_distrib := _, mul_comm := _}
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
.
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!