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 141481,
445 467280,
446 1543321,
447 5097243,
448 16835050,
449 55602393,
450 183642229,
451 606529080,
452 2003229469,
453 6616217487,
454 21851881930,
455 72171863277,
456 238367471761,
457 787274278560,
458 2600190307441,
459 ];
460 let m = random::<u16>();
461 for n in 2..p3qm1.len() {
462 let (uk, _) = LucasUtils::lucasm(3, -1, m as u64, n as u64);
463 assert_eq!(uk, p3qm1[n] % (m as u64));
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[n] % (m as u64)));
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 = random::<u8>() as u16;
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={}, q={}, m={}, n={}",
502 p,
503 q,
504 m,
505 n
506 );
507 }
508 }
509
510 #[test]
511 fn lucas_prp_test() {
512 assert!(19u8.is_lprp(Some(3), Some(-1)));
514 assert!(5u8.is_lprp(None, None));
515 assert!(7u8.is_lprp(None, None));
516 assert!(!9u8.is_lprp(None, None));
517 assert!(!5719u16.is_lprp(None, None));
518 assert!(!1239u16.is_eslprp(None));
519
520 let plimit: [u16; 5] = [323, 35, 119, 9, 9];
522 for (i, l) in plimit.iter().cloned().enumerate() {
523 let p = i + 1;
524 assert!(l.is_lprp(Some(p), Some(-1)));
525
526 for _ in 0..10 {
528 let n = random::<u16>() % l;
529 if n <= 3 || n.is_sprp(2) {
530 continue;
531 } let d = (p * p + 4) as u16;
533 if n.is_odd() && d.jacobi(&n) != -1 {
534 continue;
535 }
536 assert!(
537 !n.is_lprp(Some(p), Some(-1)),
538 "lucas prp test on {} with p = {}",
539 n,
540 p
541 );
542 }
543 }
544
545 let plimit: [u16; 3] = [4181, 169, 119];
547 for (i, l) in plimit.iter().cloned().enumerate() {
548 let p = i + 1;
549 assert!(l.is_slprp(Some(p), Some(-1)));
550
551 for _ in 0..10 {
553 let n = random::<u16>() % l;
554 if n <= 3 || (n.is_sprp(2) && n.is_sprp(3)) {
555 continue;
556 } let d = (p * p + 4) as u16;
558 if n.is_odd() && d.jacobi(&n) != -1 {
559 continue;
560 }
561 assert!(
562 !n.is_slprp(Some(p), Some(-1)),
563 "strong lucas prp test on {} with p = {}",
564 n,
565 p
566 );
567 }
568 }
569
570 let lpsp: [u16; 9] = [323, 377, 1159, 1829, 3827, 5459, 5777, 9071, 9179];
572 for psp in lpsp {
573 assert!(
574 psp.is_lprp(None, None),
575 "lucas prp test on pseudoprime {}",
576 psp
577 );
578 }
579 for _ in 0..50 {
580 let n = random::<u16>() % 10000;
581 if n <= 3 || (n.is_sprp(2) && n.is_sprp(3)) {
582 continue;
583 } if lpsp.iter().find(|&x| x == &n).is_some() {
585 continue;
586 } assert!(!n.is_lprp(None, None), "lucas prp test on {}", n);
588 }
589
590 let slpsp: [u16; 2] = [5459, 5777];
592 for psp in slpsp {
593 assert!(
594 psp.is_slprp(None, None),
595 "strong lucas prp test on pseudoprime {}",
596 psp
597 );
598 }
599 for _ in 0..50 {
600 let n = random::<u16>() % 10000;
601 if n <= 3 || (n.is_sprp(2) && n.is_sprp(3)) {
602 continue;
603 } if slpsp.iter().find(|&x| x == &n).is_some() {
605 continue;
606 } assert!(!n.is_slprp(None, None), "strong lucas prp test on {}", n);
608 }
609
610 let eslpsp: [u16; 3] = [989, 3239, 5777];
612 for psp in eslpsp {
613 assert!(
614 psp.is_eslprp(None),
615 "extra strong lucas prp test on pseudoprime {}",
616 psp
617 );
618 }
619 for _ in 0..50 {
620 let n = random::<u16>() % 10000;
621 if n <= 3 || (n.is_sprp(2) && n.is_sprp(3)) {
622 continue;
623 } if eslpsp.iter().find(|&x| x == &n).is_some() {
625 continue;
626 } assert!(!n.is_eslprp(None), "extra strong lucas prp test on {}", n);
628 }
629 }
630}