1use crate::traits::{BitTest, ExactRoots, PrimalityUtils};
2use either::Either;
3use num_integer::{Integer, Roots};
4use num_modular::{ModularCoreOps, ModularRefOps, ModularUnaryOps};
5use num_traits::{FromPrimitive, NumRef, RefNum, ToPrimitive};
6
7pub trait LucasUtils {
9 fn lucasm(p: usize, q: isize, m: Self, n: Self) -> (Self, Self)
11 where
12 Self: Sized;
13
14 fn pq_selfridge(n: &Self) -> (usize, isize);
17
18 fn p_bruteforce(n: &Self) -> usize;
21}
22
23impl<T> LucasUtils for T
24where
25 T: Integer
26 + FromPrimitive
27 + ToPrimitive
28 + NumRef
29 + BitTest
30 + ExactRoots
31 + Clone
32 + ModularRefOps,
33 for<'r> &'r T:
34 RefNum<T> + ModularUnaryOps<&'r T, Output = T> + ModularCoreOps<&'r T, &'r T, Output = T>,
35{
36 fn lucasm(p: usize, q: isize, m: Self, n: Self) -> (Self, Self) {
37 let p = T::from_usize(p).unwrap() % &m;
41 let q = if q >= 0 {
42 T::from_isize(q).unwrap() % &m
43 } else {
44 T::from_isize(-q).unwrap().negm(&m)
45 };
46
47 let mut uk = T::zero() % &m; let mut uk1 = T::one() % &m; for i in (0..n.bits()).rev() {
51 if n.bit(i) {
52 let uk1sq = (&uk1).sqm(&m);
55 let t1 = (&p).mulm(&uk1sq, &m);
56 let t2 = (&q).mulm(&uk1, &m).mulm(&uk, &m).dblm(&m);
57 let new_uk1 = t1.subm(&t2, &m);
58 let t1 = uk1sq;
60 let t2 = (&q).mulm(&uk.sqm(&m), &m);
61 let new_uk = t1.subm(&t2, &m);
62 uk1 = new_uk1;
63 uk = new_uk;
64 } else {
65 let uksq = (&uk).sqm(&m);
68 let t1 = (&uk1).sqm(&m);
69 let t2 = (&uksq).mulm(&q, &m);
70 let new_uk1 = t1.subm(&t2, &m);
71 let t1 = uk1.mulm(&uk, &m).dblm(&m);
73 let t2 = uksq.mulm(&p, &m);
74 let new_uk = t1.subm(&t2, &m);
75 uk1 = new_uk1;
76 uk = new_uk;
77 }
78 }
79
80 let vk = (&uk1).dblm(&m).subm(&p.mulm(&uk, &m), &m);
82 (uk, vk)
83 }
84
85 fn pq_selfridge(n: &Self) -> (usize, isize) {
86 let mut d = T::from_u8(5).unwrap();
87 let mut neg = false;
88 loop {
89 if d == T::from_u8(13).unwrap() && (*n).is_square() {
91 break (0, 0);
92 }
93
94 let sd = if neg { (&d).negm(n) } else { d.clone() };
95 let j = sd.jacobi(n);
96 if j == 0 && &d != n {
97 break (0, 0);
98 } if j == -1 {
100 let d = if neg {
101 -d.to_isize().unwrap()
102 } else {
103 d.to_isize().unwrap()
104 };
105 break (1, (1 - d) / 4);
106 }
107
108 d = d + T::from_u8(2).unwrap();
109 neg = !neg;
110 }
111 }
112
113 fn p_bruteforce(n: &Self) -> usize {
114 let mut p: usize = 3;
115 loop {
116 if p == 10 && (*n).is_square() {
118 break 0;
119 }
120
121 let d = T::from_usize(p * p - 4).unwrap();
122 let j = d.jacobi(n);
123 if j == 0 && &d != n {
124 break 0;
125 }
126 if j == -1 {
127 break p;
128 }
129 p += 1;
130 }
131 }
132}
133
134impl<T: Integer + NumRef + Clone + FromPrimitive + LucasUtils + BitTest + ModularRefOps>
135 PrimalityUtils for T
136where
137 for<'r> &'r T:
138 RefNum<T> + std::ops::Shr<usize, Output = T> + ModularUnaryOps<&'r T, Output = T>,
139{
140 #[inline]
141 fn is_prp(&self, base: Self) -> bool {
142 if self < &Self::one() {
143 return false;
144 }
145 let tm1 = self - Self::one();
146 base.powm(&tm1, self).is_one()
147 }
148
149 #[inline]
150 fn is_sprp(&self, base: Self) -> bool {
151 self.test_sprp(base).either(|v| v, |_| false)
152 }
153
154 fn test_sprp(&self, base: T) -> Either<bool, T> {
155 if self < &Self::one() {
156 return Either::Left(false);
157 }
158
159 let tm1 = self - T::one();
161 let shift = tm1.trailing_zeros();
162 let u = &tm1 >> shift;
163
164 let m1 = T::one() % self;
166 let mm1 = (&m1).negm(self);
167
168 let mut x = base.powm(&u, self);
169 if x == m1 || x == mm1 {
170 return Either::Left(true);
171 }
172
173 for _ in 0..shift {
174 let y = (&x).sqm(self);
175 if y == m1 {
176 return Either::Right(self.gcd(&(x - T::one())));
177 }
178 if y == mm1 {
179 return Either::Left(true);
180 }
181 x = y;
182 }
183
184 Either::Left(x == m1)
185 }
186
187 fn is_lprp(&self, p: Option<usize>, q: Option<isize>) -> bool {
188 if self < &Self::one() {
189 return false;
190 }
191 if self.is_even() {
192 return false;
193 }
194
195 let (p, q) = match (p, q) {
196 (Some(sp), Some(sq)) => (sp, sq),
197 (_, _) => {
198 let (sp, sq) = LucasUtils::pq_selfridge(self);
199 if sp == 0 {
200 return false;
201 }; (sp, sq)
203 }
204 };
205
206 let d = (p * p) as isize - 4 * q;
207 let d = if d > 0 {
208 Self::from_isize(d).unwrap()
209 } else {
210 Self::from_isize(-d).unwrap().negm(self)
211 };
212 let delta = match d.jacobi(self) {
213 0 => self.clone(),
214 -1 => self + Self::one(),
215 1 => self - Self::one(),
216 _ => unreachable!(),
217 };
218 let (u, _) = LucasUtils::lucasm(p, q, self.clone(), delta);
219 u.is_zero()
220 }
221
222 fn is_slprp(&self, p: Option<usize>, q: Option<isize>) -> bool {
223 if self < &Self::one() {
224 return false;
225 }
226 if self.is_even() {
227 return false;
228 }
229
230 let (p, q) = match (p, q) {
231 (Some(sp), Some(sq)) => (sp, sq),
232 (_, _) => {
233 let (sp, sq) = LucasUtils::pq_selfridge(self);
234 if sp == 0 {
235 return false;
236 }; (sp, sq)
238 }
239 };
240
241 let d = (p * p) as isize - 4 * q;
242 let d = if d > 0 {
243 Self::from_isize(d).unwrap()
244 } else {
245 Self::from_isize(-d).unwrap().negm(self)
246 };
247 let delta = match d.jacobi(self) {
248 0 => self.clone(),
249 -1 => self + Self::one(),
250 1 => self - Self::one(),
251 _ => unreachable!(),
252 };
253
254 let shift = delta.trailing_zeros();
255 let base = &delta >> shift;
256
257 let (ud, vd) = LucasUtils::lucasm(p, q, self.clone(), base.clone());
258 if ud.is_zero() || vd.is_zero() {
259 return true;
260 }
261
262 if shift == 0 {
264 return false;
265 }
266 let mut qk = Self::from_isize(q.abs()).unwrap().powm(&base, self);
267 let mut vd = if q >= 0 {
269 vd.sqm(self).subm(&(&qk).dblm(self), self)
270 } else {
271 vd.sqm(self).addm(&(&qk).dblm(self), self)
272 };
273 if vd.is_zero() {
274 return true;
275 }
276
277 for _ in 1..shift {
278 qk = qk.sqm(self);
280 vd = vd.sqm(self).subm(&(&qk).dblm(self), self);
281 if vd.is_zero() {
282 return true;
283 }
284 }
285 false
286 }
287
288 fn is_eslprp(&self, p: Option<usize>) -> bool {
289 if self < &Self::one() {
290 return false;
291 }
292 if self.is_even() {
293 return false;
294 }
295
296 let p = match p {
297 Some(sp) => sp,
298 None => {
299 let sp = LucasUtils::p_bruteforce(self);
300 if sp == 0 {
301 return false;
302 }; sp
304 }
305 };
306
307 let d = (p * p) as isize - 4;
308 let d = if d > 0 {
309 Self::from_isize(d).unwrap()
310 } else {
311 Self::from_isize(-d).unwrap().negm(self)
312 };
313 let delta = match d.jacobi(self) {
314 0 => self.clone(),
315 -1 => self + Self::one(),
316 1 => self - Self::one(),
317 _ => unreachable!(),
318 };
319
320 let shift = delta.trailing_zeros();
321 let base = &delta >> shift;
322
323 let (ud, mut vd) = LucasUtils::lucasm(p, 1, self.clone(), base.clone());
324 let two = Self::from_u8(2).unwrap();
325 if ud.is_zero() && (vd == two || vd == self - &two) {
327 return true;
328 }
329 if vd.is_zero() {
330 return true;
331 }
332
333 for _ in 1..(shift - 1) {
334 vd = vd.sqm(self).subm(&two, self);
336 if vd.is_zero() {
337 return true;
338 }
339 }
340 false
341 }
342}
343
344pub trait PrimalityBase:
347 Integer
348 + Roots
349 + NumRef
350 + Clone
351 + FromPrimitive
352 + ToPrimitive
353 + ExactRoots
354 + BitTest
355 + ModularRefOps
356{
357}
358impl<
359 T: Integer
360 + Roots
361 + NumRef
362 + Clone
363 + FromPrimitive
364 + ToPrimitive
365 + ExactRoots
366 + BitTest
367 + ModularRefOps,
368 > PrimalityBase for T
369{
370}
371
372pub trait PrimalityRefBase<Base>:
375 RefNum<Base>
376 + std::ops::Shr<usize, Output = Base>
377 + for<'r> ModularUnaryOps<&'r Base, Output = Base>
378 + for<'r> ModularCoreOps<&'r Base, &'r Base, Output = Base>
379{
380}
381impl<T, Base> PrimalityRefBase<Base> for T where
382 T: RefNum<Base>
383 + std::ops::Shr<usize, Output = Base>
384 + for<'r> ModularUnaryOps<&'r Base, Output = Base>
385 + for<'r> ModularCoreOps<&'r Base, &'r Base, Output = Base>
386{
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392 use crate::mint::SmallMint;
393 use num_modular::{ModularAbs, ModularSymbols};
394 use rand::random;
395
396 #[cfg(feature = "num-bigint")]
397 use num_bigint::BigUint;
398
399 #[test]
400 fn fermat_prp_test() {
401 assert!(341u16.is_prp(2));
403 assert!(!340u16.is_prp(2));
404 assert!(!105u16.is_prp(2));
405
406 for p in [2, 5, 7, 13, 19] {
408 assert!(561u32.is_prp(p));
409 }
410 }
411
412 #[test]
413 fn sprp_test() {
414 let spsp: [u16; 5] = [2047, 3277, 4033, 4681, 8321];
416 for psp in spsp {
417 assert!(psp.is_sprp(2));
418 assert!(SmallMint::from(psp).is_sprp(2.into())); }
420
421 assert_eq!(341u16.test_sprp(2), Either::Right(31));
423 assert_eq!(
424 SmallMint::from(341u16).test_sprp(2.into()),
425 Either::Right(31.into())
426 );
427 }
428
429 #[test]
430 fn lucas_mod_test() {
431 let p3qm1: [u64; 26] = [
433 0,
434 1,
435 3,
436 10,
437 33,
438 109,
439 360,
440 1189,
441 3927,
442 12970,
443 42837,
444 141_481,
445 467_280,
446 1_543_321,
447 5_097_243,
448 16_835_050,
449 55_602_393,
450 183_642_229,
451 606_529_080,
452 2_003_229_469,
453 6_616_217_487,
454 21_851_881_930,
455 72_171_863_277,
456 238_367_471_761,
457 787_274_278_560,
458 2_600_190_307_441,
459 ];
460 let m = random::<u16>();
461 for (n, &p3qm1_val) in p3qm1.iter().enumerate().skip(2) {
462 let (uk, _) = LucasUtils::lucasm(3, -1, u64::from(m), n as u64);
463 assert_eq!(uk, p3qm1_val % u64::from(m));
464
465 #[cfg(feature = "num-bigint")]
466 {
467 let (uk, _) = LucasUtils::lucasm(3, -1, BigUint::from(m), BigUint::from(n));
468 assert_eq!(uk, BigUint::from(p3qm1_val % u64::from(m)));
469 }
470 }
471
472 fn lucasm_naive(p: usize, q: isize, m: u16, n: u16) -> (u16, u16) {
473 if n == 0 {
474 return (0, 2);
475 }
476
477 let m = m as usize;
478 let q = q.absm(&m);
479 let (mut um1, mut u) = (0, 1); let (mut vm1, mut v) = (2, p % m); for _ in 1..n {
483 let new_u = p.mulm(u, &m).subm(q.mulm(um1, &m), &m);
484 um1 = u;
485 u = new_u;
486
487 let new_v = p.mulm(v, &m).subm(q.mulm(vm1, &m), &m);
488 vm1 = v;
489 v = new_v;
490 }
491 (u as u16, v as u16)
492 }
493 for _ in 0..10 {
494 let n = u16::from(random::<u8>());
495 let m = random::<u16>();
496 let p = random::<u16>() as usize;
497 let q = random::<i16>() as isize;
498 assert_eq!(
499 LucasUtils::lucasm(p, q, m, n),
500 lucasm_naive(p, q, m, n),
501 "failed with Lucas settings: p={p}, q={q}, m={m}, n={n}"
502 );
503 }
504 }
505
506 #[test]
507 fn lucas_prp_test() {
508 assert!(19u8.is_lprp(Some(3), Some(-1)));
510 assert!(5u8.is_lprp(None, None));
511 assert!(7u8.is_lprp(None, None));
512 assert!(!9u8.is_lprp(None, None));
513 assert!(!5719u16.is_lprp(None, None));
514 assert!(!1239u16.is_eslprp(None));
515
516 let plimit: [u16; 5] = [323, 35, 119, 9, 9];
518 for (i, l) in plimit.iter().copied().enumerate() {
519 let p = i + 1;
520 assert!(l.is_lprp(Some(p), Some(-1)));
521
522 for _ in 0..10 {
524 let n = random::<u16>() % l;
525 if n <= 3 || n.is_sprp(2) {
526 continue;
527 } let d = (p * p + 4) as u16;
529 if n.is_odd() && d.jacobi(&n) != -1 {
530 continue;
531 }
532 assert!(
533 !n.is_lprp(Some(p), Some(-1)),
534 "lucas prp test on {} with p = {}",
535 n,
536 p
537 );
538 }
539 }
540
541 let plimit: [u16; 3] = [4181, 169, 119];
543 for (i, l) in plimit.iter().copied().enumerate() {
544 let p = i + 1;
545 assert!(l.is_slprp(Some(p), Some(-1)));
546
547 for _ in 0..10 {
549 let n = random::<u16>() % l;
550 if n <= 3 || (n.is_sprp(2) && n.is_sprp(3)) {
551 continue;
552 } let d = (p * p + 4) as u16;
554 if n.is_odd() && d.jacobi(&n) != -1 {
555 continue;
556 }
557 assert!(
558 !n.is_slprp(Some(p), Some(-1)),
559 "strong lucas prp test on {} with p = {}",
560 n,
561 p
562 );
563 }
564 }
565
566 let lpsp: [u16; 9] = [323, 377, 1159, 1829, 3827, 5459, 5777, 9071, 9179];
568 for psp in lpsp {
569 assert!(
570 psp.is_lprp(None, None),
571 "lucas prp test on pseudoprime {}",
572 psp
573 );
574 }
575 for _ in 0..50 {
576 let n = random::<u16>() % 10000;
577 if n <= 3 || (n.is_sprp(2) && n.is_sprp(3)) {
578 continue;
579 } if lpsp.iter().any(|x| x == &n) {
581 continue;
582 } assert!(!n.is_lprp(None, None), "lucas prp test on {}", n);
584 }
585
586 let slpsp: [u16; 2] = [5459, 5777];
588 for psp in slpsp {
589 assert!(
590 psp.is_slprp(None, None),
591 "strong lucas prp test on pseudoprime {}",
592 psp
593 );
594 }
595 for _ in 0..50 {
596 let n = random::<u16>() % 10000;
597 if n <= 3 || (n.is_sprp(2) && n.is_sprp(3)) {
598 continue;
599 } if slpsp.iter().any(|x| x == &n) {
601 continue;
602 } assert!(!n.is_slprp(None, None), "strong lucas prp test on {}", n);
604 }
605
606 let eslpsp: [u16; 3] = [989, 3239, 5777];
608 for psp in eslpsp {
609 assert!(
610 psp.is_eslprp(None),
611 "extra strong lucas prp test on pseudoprime {}",
612 psp
613 );
614 }
615 for _ in 0..50 {
616 let n = random::<u16>() % 10000;
617 if n <= 3 || (n.is_sprp(2) && n.is_sprp(3)) {
618 continue;
619 } if eslpsp.iter().any(|x| x == &n) {
621 continue;
622 } assert!(!n.is_eslprp(None), "extra strong lucas prp test on {}", n);
624 }
625 }
626}