use core::num::NonZero;
use crypto_bigint::{Limb, MontyForm, MontyMultiplier, Odd, Square, UnsignedWithMontyForm, Word};
use super::{
Primality,
gcd::gcd_vartime,
jacobi::{JacobiSymbol, jacobi_symbol_vartime},
};
const MAX_ATTEMPTS: usize = 10_000;
const ATTEMPTS_BEFORE_SQRT: usize = 30;
pub trait LucasBase {
fn generate<T: UnsignedWithMontyForm>(&self, n: &Odd<T>) -> Result<(Word, Word, bool), Primality>;
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct SelfridgeBase;
impl LucasBase for SelfridgeBase {
fn generate<T: UnsignedWithMontyForm>(&self, n: &Odd<T>) -> Result<(Word, Word, bool), Primality> {
let mut abs_d = 5;
let mut d_is_negative = false;
let n_is_small = n.bits_vartime() < Word::BITS; let small_n = n.as_ref().as_limbs()[0].0;
let mut attempts = 0;
loop {
if attempts >= MAX_ATTEMPTS {
panic!("internal error: cannot find (D/n) = -1 for {:?}", n)
}
if attempts >= ATTEMPTS_BEFORE_SQRT {
let sqrt_n = n.floor_sqrt_vartime();
if &sqrt_n.wrapping_mul(&sqrt_n) == n.as_ref() {
return Err(Primality::Composite);
}
}
let j = jacobi_symbol_vartime(abs_d, d_is_negative, n);
if j == JacobiSymbol::MinusOne {
break;
}
if j == JacobiSymbol::Zero {
if !(n_is_small && small_n == abs_d) {
return Err(Primality::Composite);
}
}
attempts += 1;
d_is_negative = !d_is_negative;
abs_d += 2;
}
let (abs_q, q_is_negative) = if d_is_negative {
((abs_d + 1) / 4, false)
} else {
((abs_d - 1) / 4, true)
};
Ok((1, abs_q, q_is_negative))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct AStarBase;
impl LucasBase for AStarBase {
fn generate<T: UnsignedWithMontyForm>(&self, n: &Odd<T>) -> Result<(Word, Word, bool), Primality> {
SelfridgeBase.generate(n).map(|(p, abs_q, q_is_negative)| {
if abs_q == 1 && q_is_negative {
(5, 5, false)
} else {
(p, abs_q, q_is_negative)
}
})
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct BruteForceBase;
impl LucasBase for BruteForceBase {
fn generate<T: UnsignedWithMontyForm>(&self, n: &Odd<T>) -> Result<(Word, Word, bool), Primality> {
let mut p = 3;
let mut attempts = 0;
loop {
if attempts >= MAX_ATTEMPTS {
panic!("internal error: cannot find (D/n) = -1 for {:?}", n)
}
if attempts >= ATTEMPTS_BEFORE_SQRT {
let sqrt_n = n.floor_sqrt_vartime();
if &sqrt_n.wrapping_mul(&sqrt_n) == n.as_ref() {
return Err(Primality::Composite);
}
}
let j = jacobi_symbol_vartime(p * p - 4, false, n);
if j == JacobiSymbol::MinusOne {
break;
}
if j == JacobiSymbol::Zero {
let primality = if n.as_ref() == &T::from_limb_like(Limb::from(p + 2), n.as_ref()) {
Primality::Prime
} else {
Primality::Composite
};
return Err(primality);
}
attempts += 1;
p += 1;
}
Ok((p, 1, false))
}
}
fn decompose<T>(n: &Odd<T>) -> (u32, Odd<T>)
where
T: UnsignedWithMontyForm,
{
let one = T::one_like(n);
let s = n.trailing_ones_vartime();
let d = if s < n.bits_precision() {
n.as_ref()
.overflowing_shr_vartime(s)
.expect("shift should be within range by construction")
.checked_add(&one)
.expect("addition should not overflow by construction")
} else {
one
};
(s, Odd::new(d).expect("`d` should be odd by construction"))
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum LucasCheck {
Regular,
Strong,
AlmostExtraStrong,
ExtraStrong,
LucasV,
Bpsw21,
}
pub fn lucas_test<T>(candidate: Odd<T>, base: impl LucasBase, check: LucasCheck) -> Primality
where
T: UnsignedWithMontyForm,
{
let to_integer = |x: Word| T::from_limb_like(Limb::from(x), candidate.as_ref());
let (p, abs_q, q_is_negative) = match base.generate(&candidate) {
Ok(pq) => pq,
Err(primality) => return primality,
};
let (abs_d, d_is_negative) = if q_is_negative {
(p * p + 4 * abs_q, false)
} else {
let t1 = p * p;
let t2 = 4 * abs_q;
if t2 > t1 { (t2 - t1, true) } else { (t1 - t2, false) }
};
let p_is_one = p == 1;
let q_is_one = abs_q == 1 && !q_is_negative;
if abs_q != 1
&& gcd_vartime(
candidate.as_ref(),
NonZero::new(abs_q).expect("q is not zero by construction"),
) != 1
&& candidate.as_ref() > &to_integer(abs_q)
{
return Primality::Composite;
}
let (s, d) = decompose(&candidate);
let params = <T as UnsignedWithMontyForm>::MontyForm::new_params_vartime(candidate.clone());
let zero = <T as UnsignedWithMontyForm>::MontyForm::zero(¶ms);
let one = <T as UnsignedWithMontyForm>::MontyForm::one(¶ms);
let two = one.clone() + &one;
let minus_two = -two.clone();
let q = if q_is_one {
one.clone()
} else {
let abs_q = <T as UnsignedWithMontyForm>::MontyForm::new(to_integer(abs_q), ¶ms);
if q_is_negative { -abs_q } else { abs_q }
};
let p = if p_is_one {
one.clone()
} else {
<T as UnsignedWithMontyForm>::MontyForm::new(to_integer(p), ¶ms)
};
let mut vk = two.clone(); let mut uk = <T as UnsignedWithMontyForm>::MontyForm::zero(¶ms); let mut qk = one.clone();
let mut temp = <T as UnsignedWithMontyForm>::MontyForm::zero(¶ms);
let mut mm = <<T as UnsignedWithMontyForm>::MontyForm as MontyForm>::Multiplier::from(¶ms);
let abs_d = <T as UnsignedWithMontyForm>::MontyForm::new(to_integer(abs_d), ¶ms);
let d_m = if d_is_negative { -abs_d } else { abs_d };
for i in (0..d.bits_vartime()).rev() {
mm.mul_assign(&mut uk, &vk);
mm.square_assign(&mut vk);
vk -= &qk;
vk -= &qk;
mm.square_assign(&mut qk);
if d.bit_vartime(i) {
temp.copy_montgomery_from(&uk);
if !p_is_one {
mm.mul_assign(&mut uk, &p);
}
uk += &vk;
uk.div_by_2_assign();
mm.mul_assign(&mut temp, &d_m);
if !p_is_one {
mm.mul_assign(&mut vk, &p);
};
vk += &temp;
vk.div_by_2_assign();
mm.mul_assign(&mut qk, &q);
}
}
let ud_equals_zero = uk == zero;
let vk_equals_two = !q_is_one || (vk == two || vk == minus_two);
if check == LucasCheck::Strong && ud_equals_zero {
return Primality::ProbablyPrime;
}
if check == LucasCheck::ExtraStrong && ud_equals_zero && vk_equals_two {
return Primality::ProbablyPrime;
}
if check == LucasCheck::AlmostExtraStrong && vk_equals_two {
return Primality::ProbablyPrime;
}
let mut one_of_vk_equals_zero = vk == zero;
if (check == LucasCheck::Strong || check == LucasCheck::ExtraStrong || check == LucasCheck::AlmostExtraStrong)
&& one_of_vk_equals_zero
{
return Primality::ProbablyPrime;
}
for _ in 1..s {
if (check == LucasCheck::Strong
|| check == LucasCheck::ExtraStrong
|| check == LucasCheck::AlmostExtraStrong
|| check == LucasCheck::Bpsw21)
&& q_is_one
&& (vk == two || vk == minus_two)
{
return Primality::Composite;
}
if check == LucasCheck::Regular {
uk *= &vk;
}
vk = vk.square() - &qk.double();
one_of_vk_equals_zero |= vk == zero;
if (check == LucasCheck::Strong || check == LucasCheck::ExtraStrong || check == LucasCheck::AlmostExtraStrong)
&& one_of_vk_equals_zero
{
return Primality::ProbablyPrime;
}
if !q_is_one {
qk = qk.square();
}
}
if check == LucasCheck::Strong || check == LucasCheck::ExtraStrong || check == LucasCheck::AlmostExtraStrong {
return Primality::Composite;
}
if check == LucasCheck::Bpsw21 && !ud_equals_zero && !one_of_vk_equals_zero {
return Primality::Composite;
}
if check == LucasCheck::Regular {
uk *= &vk; if uk == zero {
return Primality::ProbablyPrime;
} else {
return Primality::Composite;
}
}
vk = vk.square() - &qk - &qk;
let lucas_v = vk == q.double();
if check == LucasCheck::LucasV {
if !lucas_v {
return Primality::Composite;
} else {
return Primality::ProbablyPrime;
}
}
debug_assert!(check == LucasCheck::Bpsw21);
if !lucas_v {
return Primality::Composite;
}
let q_jacobi = jacobi_symbol_vartime(abs_q, q_is_negative, &candidate);
let t = match q_jacobi {
JacobiSymbol::Zero => unreachable!("we previously checked that either `Q = 1` or `gcd(Q, n) != 1"),
JacobiSymbol::One => q,
JacobiSymbol::MinusOne => -q,
};
if qk == t {
Primality::ProbablyPrime
} else {
Primality::Composite
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use crypto_bigint::{Odd, U64, U128, Uint, UnsignedWithMontyForm, Word};
#[cfg(feature = "tests-exhaustive")]
use num_prime::nt_funcs::is_prime64;
use super::{AStarBase, BruteForceBase, LucasBase, LucasCheck, SelfridgeBase, decompose, lucas_test};
use crate::hazmat::{Primality, primes, pseudoprimes};
#[test]
fn bases_derived_traits() {
assert_eq!(format!("{SelfridgeBase:?}"), "SelfridgeBase");
assert_eq!(SelfridgeBase.clone(), SelfridgeBase);
assert_eq!(format!("{AStarBase:?}"), "AStarBase");
assert_eq!(AStarBase.clone(), AStarBase);
assert_eq!(format!("{BruteForceBase:?}"), "BruteForceBase");
assert_eq!(BruteForceBase.clone(), BruteForceBase);
assert_eq!(format!("{:?}", LucasCheck::Strong), "Strong");
assert_eq!(LucasCheck::Strong.clone(), LucasCheck::Strong);
}
#[test]
fn base_for_square() {
let num = Odd::new(U64::from(131u32).concatenating_square()).unwrap();
assert_eq!(SelfridgeBase.generate(&num), Err(Primality::Composite));
assert_eq!(AStarBase.generate(&num), Err(Primality::Composite));
assert_eq!(BruteForceBase.generate(&num), Err(Primality::Composite));
}
#[test]
fn base_early_quit() {
assert_eq!(
BruteForceBase.generate(&Odd::new(U64::from(5u32)).unwrap()),
Err(Primality::Prime)
)
}
#[test]
fn gcd_check() {
struct TestBase;
impl LucasBase for TestBase {
fn generate<T: UnsignedWithMontyForm>(&self, _n: &Odd<T>) -> Result<(Word, Word, bool), Primality> {
Ok((5, 5, false))
}
}
assert_eq!(
lucas_test(Odd::new(U64::from(15u32)).unwrap(), TestBase, LucasCheck::Strong),
Primality::Composite
);
}
#[test]
fn decomposition() {
assert_eq!(
decompose(&Odd::new(U128::MAX).unwrap()),
(128, Odd::new(U128::ONE).unwrap())
);
assert_eq!(
decompose(&Odd::new(U128::ONE).unwrap()),
(1, Odd::new(U128::ONE).unwrap())
);
assert_eq!(
decompose(&Odd::new(U128::from(7766015u32)).unwrap()),
(15, Odd::new(U128::from(237u32)).unwrap())
);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BaseType {
Selfridge,
BruteForce,
AStar,
}
trait HasBaseType: LucasBase + Copy {
const BASE_TYPE: BaseType;
}
impl HasBaseType for SelfridgeBase {
const BASE_TYPE: BaseType = BaseType::Selfridge;
}
impl HasBaseType for BruteForceBase {
const BASE_TYPE: BaseType = BaseType::BruteForce;
}
impl HasBaseType for AStarBase {
const BASE_TYPE: BaseType = BaseType::AStar;
}
fn is_pseudoprime<T: HasBaseType>(num: u32, check: LucasCheck) -> bool {
let pseudoprimes = match (T::BASE_TYPE, check) {
(BaseType::Selfridge, LucasCheck::Regular) => pseudoprimes::LUCAS,
(BaseType::Selfridge, LucasCheck::Strong) => pseudoprimes::STRONG_LUCAS,
(BaseType::BruteForce, LucasCheck::AlmostExtraStrong) => pseudoprimes::ALMOST_EXTRA_STRONG_LUCAS,
(BaseType::BruteForce, LucasCheck::ExtraStrong) => pseudoprimes::EXTRA_STRONG_LUCAS,
(BaseType::AStar, LucasCheck::LucasV) => pseudoprimes::LUCAS_V,
(BaseType::AStar, LucasCheck::Bpsw21) => &[],
_ => panic!("We do not have pseudoprimes listed for this combination of base and check"),
};
pseudoprimes.contains(&num)
}
fn test_pseudoprimes<T: HasBaseType>(numbers: &[u32], base: T, check: LucasCheck, expected_result: bool) {
for num in numbers.iter() {
let false_positive = is_pseudoprime::<T>(*num, check);
let actual_expected_result = if false_positive { true } else { expected_result };
let is_prime = lucas_test(Odd::new(Uint::<1>::from(*num)).unwrap(), base, check).is_probably_prime();
assert_eq!(
is_prime, actual_expected_result,
"{num} reported as prime={is_prime}, expected prime={actual_expected_result}"
);
let is_prime = lucas_test(Odd::new(Uint::<2>::from(*num)).unwrap(), base, check).is_probably_prime();
assert_eq!(
is_prime, actual_expected_result,
"{num} reported as prime={is_prime}, expected prime={actual_expected_result}"
);
}
}
#[test]
fn strong_fibonacci_pseudoprimes() {
for num in pseudoprimes::STRONG_FIBONACCI.iter() {
assert!(lucas_test(Odd::new(*num).unwrap(), SelfridgeBase, LucasCheck::Regular).is_composite());
assert!(lucas_test(Odd::new(*num).unwrap(), SelfridgeBase, LucasCheck::Strong).is_composite());
assert!(lucas_test(Odd::new(*num).unwrap(), AStarBase, LucasCheck::LucasV).is_composite());
assert!(lucas_test(Odd::new(*num).unwrap(), BruteForceBase, LucasCheck::AlmostExtraStrong).is_composite());
assert!(lucas_test(Odd::new(*num).unwrap(), BruteForceBase, LucasCheck::ExtraStrong).is_composite());
assert!(lucas_test(Odd::new(*num).unwrap(), AStarBase, LucasCheck::Bpsw21).is_composite());
}
}
#[test]
fn fibonacci_pseudoprimes() {
let nums = pseudoprimes::FIBONACCI;
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Regular, false);
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Strong, false);
test_pseudoprimes(nums, AStarBase, LucasCheck::LucasV, false);
test_pseudoprimes(nums, BruteForceBase, LucasCheck::AlmostExtraStrong, false);
test_pseudoprimes(nums, BruteForceBase, LucasCheck::ExtraStrong, false);
test_pseudoprimes(nums, AStarBase, LucasCheck::Bpsw21, false);
}
#[test]
fn bruckman_lucas_pseudoprimes() {
let nums = pseudoprimes::BRUCKMAN_LUCAS;
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Regular, false);
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Strong, false);
test_pseudoprimes(nums, AStarBase, LucasCheck::LucasV, false);
test_pseudoprimes(nums, BruteForceBase, LucasCheck::AlmostExtraStrong, false);
test_pseudoprimes(nums, BruteForceBase, LucasCheck::ExtraStrong, false);
test_pseudoprimes(nums, AStarBase, LucasCheck::Bpsw21, false);
}
#[test]
fn almost_extra_strong_lucas_pseudoprimes() {
let nums = pseudoprimes::ALMOST_EXTRA_STRONG_LUCAS;
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Regular, false);
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Strong, false);
test_pseudoprimes(nums, AStarBase, LucasCheck::LucasV, false);
test_pseudoprimes(nums, BruteForceBase, LucasCheck::AlmostExtraStrong, true);
test_pseudoprimes(nums, BruteForceBase, LucasCheck::ExtraStrong, false);
test_pseudoprimes(nums, AStarBase, LucasCheck::Bpsw21, false);
}
#[test]
fn extra_strong_lucas_pseudoprimes() {
let nums = pseudoprimes::EXTRA_STRONG_LUCAS;
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Regular, false);
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Strong, false);
test_pseudoprimes(nums, AStarBase, LucasCheck::LucasV, false);
test_pseudoprimes(nums, BruteForceBase, LucasCheck::ExtraStrong, true);
test_pseudoprimes(nums, AStarBase, LucasCheck::Bpsw21, false);
}
#[test]
fn lucas_pseudoprimes() {
let nums = pseudoprimes::LUCAS;
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Regular, true);
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Strong, false);
test_pseudoprimes(nums, AStarBase, LucasCheck::LucasV, false);
test_pseudoprimes(nums, BruteForceBase, LucasCheck::AlmostExtraStrong, false);
test_pseudoprimes(nums, BruteForceBase, LucasCheck::ExtraStrong, false);
test_pseudoprimes(nums, AStarBase, LucasCheck::Bpsw21, false);
}
#[test]
fn strong_lucas_pseudoprimes() {
let nums = pseudoprimes::STRONG_LUCAS;
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Strong, true);
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Regular, false);
test_pseudoprimes(nums, AStarBase, LucasCheck::LucasV, false);
test_pseudoprimes(nums, BruteForceBase, LucasCheck::AlmostExtraStrong, false);
test_pseudoprimes(nums, BruteForceBase, LucasCheck::ExtraStrong, false);
test_pseudoprimes(nums, AStarBase, LucasCheck::Bpsw21, false);
}
#[test]
fn strong_pseudoprimes_base_2() {
let nums = pseudoprimes::STRONG_BASE_2;
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Regular, false);
test_pseudoprimes(nums, SelfridgeBase, LucasCheck::Strong, false);
test_pseudoprimes(nums, AStarBase, LucasCheck::LucasV, false);
test_pseudoprimes(nums, BruteForceBase, LucasCheck::AlmostExtraStrong, false);
test_pseudoprimes(nums, BruteForceBase, LucasCheck::ExtraStrong, false);
test_pseudoprimes(nums, AStarBase, LucasCheck::Bpsw21, false);
}
#[test]
fn large_carmichael_number() {
let p = Odd::new(pseudoprimes::LARGE_CARMICHAEL_NUMBER).unwrap();
assert!(lucas_test(p, SelfridgeBase, LucasCheck::Regular).is_composite());
assert!(lucas_test(p, SelfridgeBase, LucasCheck::Strong).is_composite());
assert!(lucas_test(p, AStarBase, LucasCheck::LucasV).is_composite());
assert!(lucas_test(p, BruteForceBase, LucasCheck::AlmostExtraStrong).is_composite());
assert!(lucas_test(p, BruteForceBase, LucasCheck::ExtraStrong).is_composite());
assert!(lucas_test(p, AStarBase, LucasCheck::Bpsw21).is_composite());
}
fn test_large_primes<const L: usize>(nums: &[Uint<L>]) {
for num in nums {
let num = Odd::new(*num).unwrap();
assert!(lucas_test(num, SelfridgeBase, LucasCheck::Regular).is_probably_prime());
assert!(lucas_test(num, SelfridgeBase, LucasCheck::Strong).is_probably_prime());
assert!(lucas_test(num, AStarBase, LucasCheck::LucasV).is_probably_prime());
assert!(lucas_test(num, BruteForceBase, LucasCheck::AlmostExtraStrong).is_probably_prime());
assert!(lucas_test(num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime());
assert!(lucas_test(num, AStarBase, LucasCheck::Bpsw21).is_probably_prime());
}
}
#[test]
fn large_primes() {
test_large_primes(primes::PRIMES_128);
test_large_primes(primes::PRIMES_256);
test_large_primes(primes::PRIMES_384);
test_large_primes(primes::PRIMES_512);
test_large_primes(primes::PRIMES_1024);
}
#[test]
fn test_lucas_v_pseudoprimes() {
for num in pseudoprimes::LARGE_LUCAS_V {
let num = Odd::new(*num).unwrap();
assert!(lucas_test(num, AStarBase, LucasCheck::LucasV).is_probably_prime());
assert!(lucas_test(num, SelfridgeBase, LucasCheck::Strong).is_composite());
assert!(lucas_test(num, BruteForceBase, LucasCheck::AlmostExtraStrong).is_composite());
assert!(lucas_test(num, BruteForceBase, LucasCheck::ExtraStrong).is_composite());
assert!(lucas_test(num, AStarBase, LucasCheck::Bpsw21).is_composite());
}
}
#[test]
fn corner_cases() {
let res = lucas_test(
Odd::new(U64::ONE).unwrap(),
BruteForceBase,
LucasCheck::AlmostExtraStrong,
);
assert_eq!(res, Primality::Composite);
}
#[cfg(feature = "tests-exhaustive")]
#[test]
fn exhaustive() {
for num in (3..pseudoprimes::EXHAUSTIVE_TEST_LIMIT).step_by(2) {
let res_ref = is_prime64(num.into());
let odd_num = Odd::new(Uint::<1>::from(num)).unwrap();
let lpsp = is_pseudoprime::<SelfridgeBase>(num, LucasCheck::Regular);
let res = lucas_test(odd_num, SelfridgeBase, LucasCheck::Regular).is_probably_prime();
let expected = lpsp || res_ref;
assert_eq!(
res, expected,
"Selfridge base, regular: n={num}, expected={expected}, actual={res}",
);
let aeslpsp = is_pseudoprime::<BruteForceBase>(num, LucasCheck::AlmostExtraStrong);
let res = lucas_test(odd_num, BruteForceBase, LucasCheck::AlmostExtraStrong).is_probably_prime();
let expected = aeslpsp || res_ref;
assert_eq!(
res, expected,
"Brute force base, almost extra strong: n={num}, expected={expected}, actual={res}",
);
let eslpsp = is_pseudoprime::<BruteForceBase>(num, LucasCheck::ExtraStrong);
let res = lucas_test(odd_num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime();
let expected = eslpsp || res_ref;
assert_eq!(
res, expected,
"Brute force base: n={num}, expected={expected}, actual={res}",
);
let slpsp = is_pseudoprime::<SelfridgeBase>(num, LucasCheck::Strong);
let res = lucas_test(odd_num, SelfridgeBase, LucasCheck::Strong).is_probably_prime();
let expected = slpsp || res_ref;
assert_eq!(
res, expected,
"Selfridge base: n={num}, expected={expected}, actual={res}",
);
let vpsp = is_pseudoprime::<AStarBase>(num, LucasCheck::LucasV);
let res = lucas_test(odd_num, AStarBase, LucasCheck::LucasV).is_probably_prime();
let expected = vpsp || res_ref;
assert_eq!(
res, expected,
"A* base, Lucas-V: n={num}, expected={expected}, actual={res}",
);
let bpsw21psp = is_pseudoprime::<AStarBase>(num, LucasCheck::Bpsw21);
let res = lucas_test(odd_num, AStarBase, LucasCheck::Bpsw21).is_probably_prime();
let expected = bpsw21psp || res_ref;
assert_eq!(
res, expected,
"A* base, BPSW'21: n={num}, expected={expected}, actual={res}",
);
}
}
}