Skip to content

Commit cde2f9e

Browse files
committed
Make the logic flow for various types of checks more straightforward
1 parent a564a5c commit cde2f9e

File tree

1 file changed

+47
-31
lines changed

1 file changed

+47
-31
lines changed

src/hazmat/lucas.rs

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,10 @@ fn decompose<const L: usize>(n: &Uint<L>) -> (u32, Uint<L>) {
197197
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
198198
pub enum LucasCheck {
199199
/// Introduced by Baillie & Wagstaff[^Baillie1980].
200-
/// If any of `V(d*2^r) == 0` for `0 <= r < s`, and `U(d) == 0`, report the number as prime.
200+
/// If either of the following is true:
201+
/// - any of `V(d*2^r) == 0` for `0 <= r < s`,
202+
/// - `U(d) == 0`,
203+
/// report the number as prime.
201204
///
202205
/// If the base is [`SelfridgeBase`], known false positives constitute OEIS:A217255[^A217255].
203206
///
@@ -210,8 +213,14 @@ pub enum LucasCheck {
210213
/// [^A217255]: <https://oeis.org/A217255>
211214
Strong,
212215

213-
/// If any of `V(d*2^r) == 0` for `0 <= r < s`, and `V(d) == ±2` report the number as prime.
214-
/// The second condition is only checked if `Q == 1`, otherwise it is considered to be true.
216+
/// A [`LucasCheck::ExtraStrong`] without checking for `U(d)`.
217+
/// That is, if either of the following is true:
218+
/// - any of `V(d*2^r) == 0` for `0 <= r < s`,
219+
/// - `V(d) == ±2`,
220+
/// report the number as prime.
221+
///
222+
/// Note: the second condition is only checked if `Q == 1`,
223+
/// otherwise it is considered to be true.
215224
///
216225
/// If the base is [`BruteForceBase`], some known false positives
217226
/// are listed by Jacobsen[^Jacobsen].
@@ -225,7 +234,9 @@ pub enum LucasCheck {
225234
AlmostExtraStrong,
226235

227236
/// Introduced by Mo[^Mo1993], and also described by Grantham[^Grantham2001].
228-
/// If [`LucasCheck::Strong`] check passes, and `V(d) == ±2`,
237+
/// If either of the following is true:
238+
/// - any of `V(d*2^r) == 0` for `0 <= r < s`,
239+
/// - `U(d) == 0` and `V(d) == ±2`,
229240
/// report the number as prime.
230241
///
231242
/// Note that this check only differs from [`LucasCheck::Strong`] if `Q == 1`.
@@ -359,7 +370,7 @@ pub fn lucas_test<const L: usize>(
359370
let d_m = if discriminant < 0 { -abs_d } else { abs_d };
360371

361372
for i in (0..d.bits_vartime()).rev() {
362-
// k = k * 2
373+
// k' = k * 2
363374

364375
let u_2k = uk * vk;
365376
let v_2k = vk.square() - (qk + qk);
@@ -370,7 +381,7 @@ pub fn lucas_test<const L: usize>(
370381
qk = q_2k;
371382

372383
if d.bit_vartime(i) {
373-
// k = k + 1
384+
// k' = k + 1
374385

375386
let (p_uk, p_vk) = if p_is_one { (uk, vk) } else { (p * uk, p * vk) };
376387

@@ -384,35 +395,40 @@ pub fn lucas_test<const L: usize>(
384395
}
385396
}
386397

387-
// Now k=d, so vk = V_d, vk_1 = V_{d+1}.
398+
// Now k=d, so vk = V_d and uk = U_d.
388399

389-
// Extra strong check (from [^Mo1993]): `V_d == ±2 mod n`.
390-
// Do it first since it is cheap.
391-
//
392-
// Note that it only applies if Q = 1, since it is a consequence
393-
// of a property of Lucas series: V_k^2 - 4 Q^k = D U_k^2 mod n.
394-
// If Q = 1 we can easily decompose the left side of the equation leading to the check above.
395-
let vk_equals_two = if q_is_one {
396-
vk == two || vk == minus_two
397-
} else {
398-
true
399-
};
400+
// Check for the first sufficient condition in various strong checks.
400401

401-
if vk_equals_two {
402-
if check == LucasCheck::Strong || check == LucasCheck::ExtraStrong {
403-
// Strong check:`U_d == 0 mod n`.
404-
if uk == zero {
405-
return Primality::ProbablyPrime;
406-
}
407-
} else {
408-
// This is "almost extra strong check": we only checked for `V_d` earlier,
409-
// and the check for `U_d` is skipped.
410-
if check == LucasCheck::AlmostExtraStrong {
411-
return Primality::ProbablyPrime;
412-
}
402+
if check == LucasCheck::Strong && uk == zero {
403+
// Strong check: `U_d == 0 mod n`.
404+
return Primality::ProbablyPrime;
405+
} else if check == LucasCheck::ExtraStrong || check == LucasCheck::AlmostExtraStrong {
406+
// Extra strong check (from [^Mo1993]): `V_d == ±2 mod n` and `U_d == 0 mod n`.
407+
//
408+
// Note that the first identity only applies if `Q = 1`, since it is a consequence
409+
// of a property of Lucas series: `V_k^2 - 4 Q^k = D U_k^2 mod n`.
410+
// If `Q = 1` we can easily decompose the left side of the equation
411+
// leading to the check above.
412+
//
413+
// If `Q != 1` we just consider it passed (we don't have a corresponding
414+
// pseudoprime list anyway).
415+
416+
let vk_equals_two = !q_is_one || (vk == two || vk == minus_two);
417+
418+
if check == LucasCheck::ExtraStrong && uk == zero && vk_equals_two {
419+
return Primality::ProbablyPrime;
420+
}
421+
422+
// "Almost extra strong" check skips the `U_d` check.
423+
// Since we have `U_d` anyway, it does not improve performance,
424+
// so it is only here for testing purposes, since we have a corresponding pseudoprime list.
425+
if check == LucasCheck::AlmostExtraStrong && vk_equals_two {
426+
return Primality::ProbablyPrime;
413427
}
414428
}
415429

430+
// Second sufficient condition requires further propagating `V_k` up to `V_{n+1}`.
431+
416432
// Check if V_{2^t d} == 0 mod n for some 0 <= t < s.
417433
// (unless we're in Lucas-V mode, then we just propagate V_k)
418434

@@ -428,7 +444,7 @@ pub fn lucas_test<const L: usize>(
428444
}
429445

430446
// k' = 2k
431-
// V(k') = V(2k) = V(k)² - 2 * Q^k
447+
// V_{k'} = V_k^2 - 2 Q^k
432448
vk = vk * vk - qk - qk;
433449

434450
if check != LucasCheck::LucasV && vk == zero {

0 commit comments

Comments
 (0)