use crate::traits::{BitTest, ExactRoots, PrimalityUtils};
use either::Either;
use num_integer::{Integer, Roots};
use num_modular::{ModularCoreOps, ModularRefOps, ModularUnaryOps};
use num_traits::{FromPrimitive, NumRef, RefNum, ToPrimitive};
pub trait LucasUtils {
fn lucasm(p: usize, q: isize, m: Self, n: Self) -> (Self, Self)
where
Self: Sized;
fn pq_selfridge(n: &Self) -> (usize, isize);
fn p_bruteforce(n: &Self) -> usize;
}
impl<T> LucasUtils for T
where
T: Integer
+ FromPrimitive
+ ToPrimitive
+ NumRef
+ BitTest
+ ExactRoots
+ Clone
+ ModularRefOps,
for<'r> &'r T:
RefNum<T> + ModularUnaryOps<&'r T, Output = T> + ModularCoreOps<&'r T, &'r T, Output = T>,
{
fn lucasm(p: usize, q: isize, m: Self, n: Self) -> (Self, Self) {
let p = T::from_usize(p).unwrap() % &m;
let q = if q >= 0 {
T::from_isize(q).unwrap() % &m
} else {
T::from_isize(-q).unwrap().negm(&m)
};
let mut uk = T::zero() % &m; let mut uk1 = T::one() % &m;
for i in (0..n.bits()).rev() {
if n.bit(i) {
let uk1sq = (&uk1).sqm(&m);
let t1 = (&p).mulm(&uk1sq, &m);
let t2 = (&q).mulm(&uk1, &m).mulm(&uk, &m).dblm(&m);
let new_uk1 = t1.subm(&t2, &m);
let t1 = uk1sq;
let t2 = (&q).mulm(&uk.sqm(&m), &m);
let new_uk = t1.subm(&t2, &m);
uk1 = new_uk1;
uk = new_uk;
} else {
let uksq = (&uk).sqm(&m);
let t1 = (&uk1).sqm(&m);
let t2 = (&uksq).mulm(&q, &m);
let new_uk1 = t1.subm(&t2, &m);
let t1 = uk1.mulm(&uk, &m).dblm(&m);
let t2 = uksq.mulm(&p, &m);
let new_uk = t1.subm(&t2, &m);
uk1 = new_uk1;
uk = new_uk;
}
}
let vk = (&uk1).dblm(&m).subm(&p.mulm(&uk, &m), &m);
(uk, vk)
}
fn pq_selfridge(n: &Self) -> (usize, isize) {
let mut d = T::from_u8(5).unwrap();
let mut neg = false;
loop {
if d == T::from_u8(13).unwrap() && (*n).is_square() {
break (0, 0);
}
let sd = if neg { (&d).negm(n) } else { d.clone() };
let j = sd.jacobi(n);
if j == 0 && &d != n {
break (0, 0);
} if j == -1 {
let d = if neg {
-d.to_isize().unwrap()
} else {
d.to_isize().unwrap()
};
break (1, (1 - d) / 4);
}
d = d + T::from_u8(2).unwrap();
neg = !neg;
}
}
fn p_bruteforce(n: &Self) -> usize {
let mut p: usize = 3;
loop {
if p == 10 && (*n).is_square() {
break 0;
}
let d = T::from_usize(p * p - 4).unwrap();
let j = d.jacobi(n);
if j == 0 && &d != n {
break 0;
}
if j == -1 {
break p;
}
p += 1;
}
}
}
impl<T: Integer + NumRef + Clone + FromPrimitive + LucasUtils + BitTest + ModularRefOps>
PrimalityUtils for T
where
for<'r> &'r T:
RefNum<T> + std::ops::Shr<usize, Output = T> + ModularUnaryOps<&'r T, Output = T>,
{
#[inline]
fn is_prp(&self, base: Self) -> bool {
if self < &Self::one() {
return false;
}
let tm1 = self - Self::one();
base.powm(&tm1, self).is_one()
}
#[inline]
fn is_sprp(&self, base: Self) -> bool {
self.test_sprp(base).either(|v| v, |_| false)
}
fn test_sprp(&self, base: T) -> Either<bool, T> {
if self < &Self::one() {
return Either::Left(false);
}
let tm1 = self - T::one();
let shift = tm1.trailing_zeros();
let u = &tm1 >> shift;
let m1 = T::one() % self;
let mm1 = (&m1).negm(self);
let mut x = base.powm(&u, self);
if x == m1 || x == mm1 {
return Either::Left(true);
}
for _ in 0..shift {
let y = (&x).sqm(self);
if y == m1 {
return Either::Right(self.gcd(&(x - T::one())));
}
if y == mm1 {
return Either::Left(true);
}
x = y;
}
Either::Left(x == m1)
}
fn is_lprp(&self, p: Option<usize>, q: Option<isize>) -> bool {
if self < &Self::one() {
return false;
}
if self.is_even() {
return false;
}
let (p, q) = match (p, q) {
(Some(sp), Some(sq)) => (sp, sq),
(_, _) => {
let (sp, sq) = LucasUtils::pq_selfridge(self);
if sp == 0 {
return false;
}; (sp, sq)
}
};
let d = (p * p) as isize - 4 * q;
let d = if d > 0 {
Self::from_isize(d).unwrap()
} else {
Self::from_isize(-d).unwrap().negm(self)
};
let delta = match d.jacobi(self) {
0 => self.clone(),
-1 => self + Self::one(),
1 => self - Self::one(),
_ => unreachable!(),
};
let (u, _) = LucasUtils::lucasm(p, q, self.clone(), delta);
u.is_zero()
}
fn is_slprp(&self, p: Option<usize>, q: Option<isize>) -> bool {
if self < &Self::one() {
return false;
}
if self.is_even() {
return false;
}
let (p, q) = match (p, q) {
(Some(sp), Some(sq)) => (sp, sq),
(_, _) => {
let (sp, sq) = LucasUtils::pq_selfridge(self);
if sp == 0 {
return false;
}; (sp, sq)
}
};
let d = (p * p) as isize - 4 * q;
let d = if d > 0 {
Self::from_isize(d).unwrap()
} else {
Self::from_isize(-d).unwrap().negm(self)
};
let delta = match d.jacobi(self) {
0 => self.clone(),
-1 => self + Self::one(),
1 => self - Self::one(),
_ => unreachable!(),
};
let shift = delta.trailing_zeros();
let base = &delta >> shift;
let (ud, vd) = LucasUtils::lucasm(p, q, self.clone(), base.clone());
if ud.is_zero() || vd.is_zero() {
return true;
}
if shift == 0 {
return false;
}
let mut qk = Self::from_isize(q.abs()).unwrap().powm(&base, self);
let mut vd = if q >= 0 {
vd.sqm(self).subm(&(&qk).dblm(self), self)
} else {
vd.sqm(self).addm(&(&qk).dblm(self), self)
};
if vd.is_zero() {
return true;
}
for _ in 1..shift {
qk = qk.sqm(self);
vd = vd.sqm(self).subm(&(&qk).dblm(self), self);
if vd.is_zero() {
return true;
}
}
false
}
fn is_eslprp(&self, p: Option<usize>) -> bool {
if self < &Self::one() {
return false;
}
if self.is_even() {
return false;
}
let p = match p {
Some(sp) => sp,
None => {
let sp = LucasUtils::p_bruteforce(self);
if sp == 0 {
return false;
}; sp
}
};
let d = (p * p) as isize - 4;
let d = if d > 0 {
Self::from_isize(d).unwrap()
} else {
Self::from_isize(-d).unwrap().negm(self)
};
let delta = match d.jacobi(self) {
0 => self.clone(),
-1 => self + Self::one(),
1 => self - Self::one(),
_ => unreachable!(),
};
let shift = delta.trailing_zeros();
let base = &delta >> shift;
let (ud, mut vd) = LucasUtils::lucasm(p, 1, self.clone(), base.clone());
let two = Self::from_u8(2).unwrap();
if ud.is_zero() && (vd == two || vd == self - &two) {
return true;
}
if vd.is_zero() {
return true;
}
for _ in 1..(shift - 1) {
vd = vd.sqm(self).subm(&two, self);
if vd.is_zero() {
return true;
}
}
false
}
}
pub trait PrimalityBase:
Integer
+ Roots
+ NumRef
+ Clone
+ FromPrimitive
+ ToPrimitive
+ ExactRoots
+ BitTest
+ ModularRefOps
{
}
impl<
T: Integer
+ Roots
+ NumRef
+ Clone
+ FromPrimitive
+ ToPrimitive
+ ExactRoots
+ BitTest
+ ModularRefOps,
> PrimalityBase for T
{
}
pub trait PrimalityRefBase<Base>:
RefNum<Base>
+ std::ops::Shr<usize, Output = Base>
+ for<'r> ModularUnaryOps<&'r Base, Output = Base>
+ for<'r> ModularCoreOps<&'r Base, &'r Base, Output = Base>
{
}
impl<T, Base> PrimalityRefBase<Base> for T where
T: RefNum<Base>
+ std::ops::Shr<usize, Output = Base>
+ for<'r> ModularUnaryOps<&'r Base, Output = Base>
+ for<'r> ModularCoreOps<&'r Base, &'r Base, Output = Base>
{
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mint::SmallMint;
use num_modular::{ModularAbs, ModularSymbols};
use rand::random;
#[cfg(feature = "num-bigint")]
use num_bigint::BigUint;
#[test]
fn fermat_prp_test() {
assert!(341u16.is_prp(2));
assert!(!340u16.is_prp(2));
assert!(!105u16.is_prp(2));
for p in [2, 5, 7, 13, 19] {
assert!(561u32.is_prp(p));
}
}
#[test]
fn sprp_test() {
let spsp: [u16; 5] = [2047, 3277, 4033, 4681, 8321];
for psp in spsp {
assert!(psp.is_sprp(2));
assert!(SmallMint::from(psp).is_sprp(2.into())); }
assert_eq!(341u16.test_sprp(2), Either::Right(31));
assert_eq!(
SmallMint::from(341u16).test_sprp(2.into()),
Either::Right(31.into())
);
}
#[test]
fn lucas_mod_test() {
let p3qm1: [u64; 26] = [
0,
1,
3,
10,
33,
109,
360,
1189,
3927,
12970,
42837,
141_481,
467_280,
1_543_321,
5_097_243,
16_835_050,
55_602_393,
183_642_229,
606_529_080,
2_003_229_469,
6_616_217_487,
21_851_881_930,
72_171_863_277,
238_367_471_761,
787_274_278_560,
2_600_190_307_441,
];
let m = random::<u16>();
for (n, &p3qm1_val) in p3qm1.iter().enumerate().skip(2) {
let (uk, _) = LucasUtils::lucasm(3, -1, u64::from(m), n as u64);
assert_eq!(uk, p3qm1_val % u64::from(m));
#[cfg(feature = "num-bigint")]
{
let (uk, _) = LucasUtils::lucasm(3, -1, BigUint::from(m), BigUint::from(n));
assert_eq!(uk, BigUint::from(p3qm1_val % u64::from(m)));
}
}
fn lucasm_naive(p: usize, q: isize, m: u16, n: u16) -> (u16, u16) {
if n == 0 {
return (0, 2);
}
let m = m as usize;
let q = q.absm(&m);
let (mut um1, mut u) = (0, 1); let (mut vm1, mut v) = (2, p % m);
for _ in 1..n {
let new_u = p.mulm(u, &m).subm(q.mulm(um1, &m), &m);
um1 = u;
u = new_u;
let new_v = p.mulm(v, &m).subm(q.mulm(vm1, &m), &m);
vm1 = v;
v = new_v;
}
(u as u16, v as u16)
}
for _ in 0..10 {
let n = u16::from(random::<u8>());
let m = random::<u16>();
let p = random::<u16>() as usize;
let q = random::<i16>() as isize;
assert_eq!(
LucasUtils::lucasm(p, q, m, n),
lucasm_naive(p, q, m, n),
"failed with Lucas settings: p={p}, q={q}, m={m}, n={n}"
);
}
}
#[test]
fn lucas_prp_test() {
assert!(19u8.is_lprp(Some(3), Some(-1)));
assert!(5u8.is_lprp(None, None));
assert!(7u8.is_lprp(None, None));
assert!(!9u8.is_lprp(None, None));
assert!(!5719u16.is_lprp(None, None));
assert!(!1239u16.is_eslprp(None));
let plimit: [u16; 5] = [323, 35, 119, 9, 9];
for (i, l) in plimit.iter().copied().enumerate() {
let p = i + 1;
assert!(l.is_lprp(Some(p), Some(-1)));
for _ in 0..10 {
let n = random::<u16>() % l;
if n <= 3 || n.is_sprp(2) {
continue;
} let d = (p * p + 4) as u16;
if n.is_odd() && d.jacobi(&n) != -1 {
continue;
}
assert!(
!n.is_lprp(Some(p), Some(-1)),
"lucas prp test on {} with p = {}",
n,
p
);
}
}
let plimit: [u16; 3] = [4181, 169, 119];
for (i, l) in plimit.iter().copied().enumerate() {
let p = i + 1;
assert!(l.is_slprp(Some(p), Some(-1)));
for _ in 0..10 {
let n = random::<u16>() % l;
if n <= 3 || (n.is_sprp(2) && n.is_sprp(3)) {
continue;
} let d = (p * p + 4) as u16;
if n.is_odd() && d.jacobi(&n) != -1 {
continue;
}
assert!(
!n.is_slprp(Some(p), Some(-1)),
"strong lucas prp test on {} with p = {}",
n,
p
);
}
}
let lpsp: [u16; 9] = [323, 377, 1159, 1829, 3827, 5459, 5777, 9071, 9179];
for psp in lpsp {
assert!(
psp.is_lprp(None, None),
"lucas prp test on pseudoprime {}",
psp
);
}
for _ in 0..50 {
let n = random::<u16>() % 10000;
if n <= 3 || (n.is_sprp(2) && n.is_sprp(3)) {
continue;
} if lpsp.iter().any(|x| x == &n) {
continue;
} assert!(!n.is_lprp(None, None), "lucas prp test on {}", n);
}
let slpsp: [u16; 2] = [5459, 5777];
for psp in slpsp {
assert!(
psp.is_slprp(None, None),
"strong lucas prp test on pseudoprime {}",
psp
);
}
for _ in 0..50 {
let n = random::<u16>() % 10000;
if n <= 3 || (n.is_sprp(2) && n.is_sprp(3)) {
continue;
} if slpsp.iter().any(|x| x == &n) {
continue;
} assert!(!n.is_slprp(None, None), "strong lucas prp test on {}", n);
}
let eslpsp: [u16; 3] = [989, 3239, 5777];
for psp in eslpsp {
assert!(
psp.is_eslprp(None),
"extra strong lucas prp test on pseudoprime {}",
psp
);
}
for _ in 0..50 {
let n = random::<u16>() % 10000;
if n <= 3 || (n.is_sprp(2) && n.is_sprp(3)) {
continue;
} if eslpsp.iter().any(|x| x == &n) {
continue;
} assert!(!n.is_eslprp(None), "extra strong lucas prp test on {}", n);
}
}
}