numclassify 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2100 @@
1
+ """
2
+ numclassify._core.primes
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~
4
+ Prime-number classification functions.
5
+
6
+ All functions are registered in the global registry via ``@register`` and are
7
+ also available as plain module-level callables so other modules can import them
8
+ directly (e.g. ``from numclassify._core.primes import is_prime``).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import math
14
+ import itertools
15
+ from typing import List
16
+
17
+ from numclassify._registry import register
18
+
19
+
20
+ # ---------------------------------------------------------------------------
21
+ # Miller-Rabin primality test (deterministic for n < 3_215_031_751,
22
+ # probabilistic with 20 rounds for larger values)
23
+ # ---------------------------------------------------------------------------
24
+
25
+ def _miller_rabin_test(n: int, witnesses: List[int]) -> bool:
26
+ """Return ``True`` if *n* passes Miller-Rabin for all *witnesses*.
27
+
28
+ Writes *n-1* as ``2^r * d`` then checks each witness *a*.
29
+
30
+ Parameters
31
+ ----------
32
+ n:
33
+ Odd integer > 2 to test.
34
+ witnesses:
35
+ List of bases to use.
36
+ """
37
+ r, d = 0, n - 1
38
+ while d % 2 == 0:
39
+ r += 1
40
+ d //= 2
41
+
42
+ for a in witnesses:
43
+ if a >= n:
44
+ continue
45
+ x = pow(a, d, n)
46
+ if x == 1 or x == n - 1:
47
+ continue
48
+ for _ in range(r - 1):
49
+ x = pow(x, 2, n)
50
+ if x == n - 1:
51
+ break
52
+ else:
53
+ return False
54
+ return True
55
+
56
+
57
+ # ---------------------------------------------------------------------------
58
+ # Helper functions (not registered, used internally)
59
+ # ---------------------------------------------------------------------------
60
+
61
+ def primes_up_to(n: int) -> List[int]:
62
+ """Return a list of all primes <= *n* via the Sieve of Eratosthenes.
63
+
64
+ Parameters
65
+ ----------
66
+ n:
67
+ Upper bound (inclusive).
68
+
69
+ Returns
70
+ -------
71
+ list[int]
72
+
73
+ Example
74
+ -------
75
+ >>> primes_up_to(20)
76
+ [2, 3, 5, 7, 11, 13, 17, 19]
77
+ """
78
+ if n < 2:
79
+ return []
80
+ sieve = bytearray([1]) * (n + 1)
81
+ sieve[0] = sieve[1] = 0
82
+ for i in range(2, int(n ** 0.5) + 1):
83
+ if sieve[i]:
84
+ sieve[i * i :: i] = bytearray(len(sieve[i * i :: i]))
85
+ return [i for i, v in enumerate(sieve) if v]
86
+
87
+
88
+ def prime_factors(n: int) -> List[int]:
89
+ """Return the full prime factorisation of *n* with repetition.
90
+
91
+ Parameters
92
+ ----------
93
+ n:
94
+ Integer >= 2 to factorise.
95
+
96
+ Returns
97
+ -------
98
+ list[int]
99
+ Sorted list of prime factors including multiplicity.
100
+
101
+ Example
102
+ -------
103
+ >>> prime_factors(12)
104
+ [2, 2, 3]
105
+ >>> prime_factors(360)
106
+ [2, 2, 2, 3, 3, 5]
107
+ """
108
+ factors: List[int] = []
109
+ if n < 2:
110
+ return factors
111
+ d = 2
112
+ while d * d <= n:
113
+ while n % d == 0:
114
+ factors.append(d)
115
+ n //= d
116
+ d += 1
117
+ if n > 1:
118
+ factors.append(n)
119
+ return factors
120
+
121
+
122
+ def prev_prime(n: int) -> int:
123
+ """Return the largest prime strictly less than *n*.
124
+
125
+ Parameters
126
+ ----------
127
+ n:
128
+ Integer > 2.
129
+
130
+ Returns
131
+ -------
132
+ int
133
+
134
+ Example
135
+ -------
136
+ >>> prev_prime(10)
137
+ 7
138
+ >>> prev_prime(3)
139
+ 2
140
+ """
141
+ if n <= 2:
142
+ raise ValueError("No prime less than 2")
143
+ candidate = n - 1
144
+ while candidate >= 2:
145
+ if is_prime(candidate):
146
+ return candidate
147
+ candidate -= 1
148
+ raise ValueError(f"No prime found below {n}")
149
+
150
+
151
+ def next_prime(n: int) -> int:
152
+ """Return the smallest prime strictly greater than *n*.
153
+
154
+ Parameters
155
+ ----------
156
+ n:
157
+ Non-negative integer.
158
+
159
+ Returns
160
+ -------
161
+ int
162
+
163
+ Example
164
+ -------
165
+ >>> next_prime(10)
166
+ 11
167
+ >>> next_prime(1)
168
+ 2
169
+ """
170
+ candidate = max(n + 1, 2)
171
+ while True:
172
+ if is_prime(candidate):
173
+ return candidate
174
+ candidate += 1
175
+
176
+
177
+ # ---------------------------------------------------------------------------
178
+ # Existing 5 functions — kept exactly as-is
179
+ # ---------------------------------------------------------------------------
180
+
181
+ @register(
182
+ name="Prime",
183
+ category="primes",
184
+ oeis="A000040",
185
+ description=(
186
+ "A natural number greater than 1 with no positive divisors other than "
187
+ "1 and itself."
188
+ ),
189
+ )
190
+ def is_prime(n: int) -> bool:
191
+ """Return ``True`` if *n* is a prime number.
192
+
193
+ Uses a deterministic Miller-Rabin test with witnesses ``[2, 3, 5, 7]``
194
+ for *n* < 3,215,031,751. For larger values, 20 additional random-free
195
+ witnesses ``[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
196
+ 59, 61, 67, 71]`` are used, which is deterministic up to at least 3.3 × 10²⁴.
197
+
198
+ Correctly rejects Carmichael numbers such as 561.
199
+
200
+ Parameters
201
+ ----------
202
+ n:
203
+ Integer to test.
204
+
205
+ Returns
206
+ -------
207
+ bool
208
+
209
+ Example
210
+ -------
211
+ >>> is_prime(2)
212
+ True
213
+ >>> is_prime(561) # Carmichael number — composite
214
+ False
215
+ >>> is_prime(1_000_000_007)
216
+ True
217
+ """
218
+ if n < 2:
219
+ return False
220
+ if n == 2:
221
+ return True
222
+ if n % 2 == 0:
223
+ return False
224
+ if n < 9:
225
+ return True
226
+ if n % 3 == 0:
227
+ return False
228
+
229
+ if n < 3_215_031_751:
230
+ witnesses = [2, 3, 5, 7]
231
+ else:
232
+ witnesses = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37,
233
+ 41, 43, 47, 53, 59, 61, 67, 71]
234
+
235
+ return _miller_rabin_test(n, witnesses)
236
+
237
+
238
+ @register(
239
+ name="Twin Prime",
240
+ category="primes",
241
+ oeis="A001097",
242
+ description=(
243
+ "A prime p such that p − 2 or p + 2 is also prime "
244
+ "(i.e. p is part of a twin-prime pair)."
245
+ ),
246
+ aliases=["twin_prime"],
247
+ )
248
+ def is_twin_prime(n: int) -> bool:
249
+ """Return ``True`` if *n* is a twin prime.
250
+
251
+ A twin prime is a prime *p* for which *p − 2* or *p + 2* is also prime.
252
+
253
+ Parameters
254
+ ----------
255
+ n:
256
+ Integer to test.
257
+
258
+ Returns
259
+ -------
260
+ bool
261
+
262
+ Example
263
+ -------
264
+ >>> is_twin_prime(5) # 3 and 7 are both prime
265
+ True
266
+ >>> is_twin_prime(23) # 21=3*7 composite, 25=5² composite
267
+ False
268
+ """
269
+ if not is_prime(n):
270
+ return False
271
+ return is_prime(n - 2) or is_prime(n + 2)
272
+
273
+
274
+ @register(
275
+ name="Mersenne Prime",
276
+ category="primes",
277
+ oeis="A000668",
278
+ description=(
279
+ "A prime of the form 2^p − 1 where p itself is prime."
280
+ ),
281
+ aliases=["mersenne_prime"],
282
+ )
283
+ def is_mersenne_prime(n: int) -> bool:
284
+ """Return ``True`` if *n* is a Mersenne prime.
285
+
286
+ A Mersenne prime has the form 2^p − 1 where *p* is prime. This
287
+ implementation checks that *n* + 1 is a power of 2 and that the
288
+ corresponding exponent is prime, then verifies *n* itself is prime.
289
+
290
+ Parameters
291
+ ----------
292
+ n:
293
+ Integer to test.
294
+
295
+ Returns
296
+ -------
297
+ bool
298
+
299
+ Example
300
+ -------
301
+ >>> is_mersenne_prime(7) # 2^3 - 1 = 7, 3 is prime
302
+ True
303
+ >>> is_mersenne_prime(15) # 2^4 - 1 = 15, 4 is not prime
304
+ False
305
+ """
306
+ if n < 2:
307
+ return False
308
+ m = n + 1
309
+ if m & (m - 1) != 0:
310
+ return False
311
+ p = m.bit_length() - 1
312
+ return is_prime(p) and is_prime(n)
313
+
314
+
315
+ @register(
316
+ name="Sophie Germain Prime",
317
+ category="primes",
318
+ oeis="A005384",
319
+ description=(
320
+ "A prime p such that 2p + 1 is also prime."
321
+ ),
322
+ aliases=["sophie_germain_prime", "sophie germain prime"],
323
+ )
324
+ def is_sophie_germain_prime(n: int) -> bool:
325
+ """Return ``True`` if *n* is a Sophie Germain prime.
326
+
327
+ A Sophie Germain prime is a prime *p* for which ``2p + 1`` is also prime.
328
+ The prime ``2p + 1`` is called a *safe prime*.
329
+
330
+ Parameters
331
+ ----------
332
+ n:
333
+ Integer to test.
334
+
335
+ Returns
336
+ -------
337
+ bool
338
+
339
+ Example
340
+ -------
341
+ >>> is_sophie_germain_prime(11) # 2*11+1=23, prime
342
+ True
343
+ >>> is_sophie_germain_prime(13) # 2*13+1=27=3^3, composite
344
+ False
345
+ """
346
+ return is_prime(n) and is_prime(2 * n + 1)
347
+
348
+
349
+ @register(
350
+ name="Safe Prime",
351
+ category="primes",
352
+ oeis="A005385",
353
+ description=(
354
+ "A prime p such that (p − 1) / 2 is also prime."
355
+ ),
356
+ aliases=["safe_prime"],
357
+ )
358
+ def is_safe_prime(n: int) -> bool:
359
+ """Return ``True`` if *n* is a safe prime.
360
+
361
+ A safe prime is a prime *p* for which ``(p − 1) / 2`` is also prime.
362
+ Safe primes are related to Sophie Germain primes: *p* is safe iff
363
+ ``(p − 1) / 2`` is a Sophie Germain prime.
364
+
365
+ Parameters
366
+ ----------
367
+ n:
368
+ Integer to test.
369
+
370
+ Returns
371
+ -------
372
+ bool
373
+
374
+ Example
375
+ -------
376
+ >>> is_safe_prime(23) # (23-1)/2 = 11, prime
377
+ True
378
+ >>> is_safe_prime(7) # (7-1)/2 = 3, prime
379
+ True
380
+ >>> is_safe_prime(11) # (11-1)/2 = 5, prime
381
+ True
382
+ >>> is_safe_prime(13) # (13-1)/2 = 6, composite
383
+ False
384
+ """
385
+ if not is_prime(n):
386
+ return False
387
+ if (n - 1) % 2 != 0:
388
+ return False
389
+ return is_prime((n - 1) // 2)
390
+
391
+
392
+ # ---------------------------------------------------------------------------
393
+ # GROUP 1 — Form-based primes
394
+ # ---------------------------------------------------------------------------
395
+
396
+ @register(
397
+ name="Fermat Prime",
398
+ category="primes",
399
+ oeis="A019434",
400
+ description=(
401
+ "A prime of the form 2^(2^n) + 1. Only five are known: "
402
+ "3, 5, 17, 257, 65537."
403
+ ),
404
+ aliases=["fermat_prime"],
405
+ )
406
+ def is_fermat_prime(n: int) -> bool:
407
+ """Return ``True`` if *n* is a Fermat prime.
408
+
409
+ A Fermat prime is a prime of the form ``2^(2^k) + 1`` for some non-negative
410
+ integer *k*. Only five Fermat primes are currently known: 3, 5, 17, 257,
411
+ and 65537. This function checks membership in that known set.
412
+
413
+ Parameters
414
+ ----------
415
+ n:
416
+ Integer to test.
417
+
418
+ Returns
419
+ -------
420
+ bool
421
+
422
+ Example
423
+ -------
424
+ >>> is_fermat_prime(17)
425
+ True
426
+ >>> is_fermat_prime(257)
427
+ True
428
+ >>> is_fermat_prime(7)
429
+ False
430
+
431
+ Notes
432
+ -----
433
+ No Fermat prime beyond 65537 has ever been found, and all Fermat numbers
434
+ ``F_5`` through ``F_32`` have been shown to be composite.
435
+ """
436
+ return n in {3, 5, 17, 257, 65537}
437
+
438
+
439
+ @register(
440
+ name="Factorial Prime",
441
+ category="primes",
442
+ oeis="A088054",
443
+ description=(
444
+ "A prime of the form n! + 1 or n! − 1 for some positive integer n."
445
+ ),
446
+ aliases=["factorial_prime"],
447
+ )
448
+ def is_factorial_prime(n: int) -> bool:
449
+ """Return ``True`` if *n* is a factorial prime.
450
+
451
+ A factorial prime has the form ``k! + 1`` or ``k! − 1`` for some positive
452
+ integer *k*. This implementation tests values of *k* starting at 1 and
453
+ stops once ``k!`` exceeds *n* + 1 (since further factorials can only grow).
454
+
455
+ Parameters
456
+ ----------
457
+ n:
458
+ Integer to test.
459
+
460
+ Returns
461
+ -------
462
+ bool
463
+
464
+ Example
465
+ -------
466
+ >>> is_factorial_prime(5) # 3! - 1 = 5
467
+ True
468
+ >>> is_factorial_prime(7) # 3! + 1 = 7
469
+ True
470
+ >>> is_factorial_prime(23) # 4! - 1 = 23
471
+ True
472
+ >>> is_factorial_prime(11)
473
+ False
474
+
475
+ Edge cases
476
+ ----------
477
+ * ``n <= 1`` returns ``False``.
478
+ """
479
+ if n <= 1:
480
+ return False
481
+ if not is_prime(n):
482
+ return False
483
+ factorial = 1
484
+ k = 1
485
+ while True:
486
+ factorial *= k
487
+ if factorial - 1 == n or factorial + 1 == n:
488
+ return True
489
+ if factorial > n + 1:
490
+ break
491
+ k += 1
492
+ return False
493
+
494
+
495
+ @register(
496
+ name="Primorial Prime",
497
+ category="primes",
498
+ oeis="A057704",
499
+ description=(
500
+ "A prime of the form p# + 1 or p# − 1, where p# is the primorial "
501
+ "(product of all primes up to p)."
502
+ ),
503
+ aliases=["primorial_prime"],
504
+ )
505
+ def is_primorial_prime(n: int) -> bool:
506
+ """Return ``True`` if *n* is a primorial prime.
507
+
508
+ A primorial prime has the form ``p# + 1`` or ``p# − 1`` where ``p#``
509
+ denotes the product of all primes up to and including *p*.
510
+
511
+ Parameters
512
+ ----------
513
+ n:
514
+ Integer to test.
515
+
516
+ Returns
517
+ -------
518
+ bool
519
+
520
+ Example
521
+ -------
522
+ >>> is_primorial_prime(5) # 2*3 - 1 = 5
523
+ True
524
+ >>> is_primorial_prime(7) # 2*3 + 1 = 7
525
+ True
526
+ >>> is_primorial_prime(29) # 2*3*5 - 1 = 29
527
+ True
528
+ >>> is_primorial_prime(31) # 2*3*5 + 1 = 31
529
+ True
530
+ >>> is_primorial_prime(11)
531
+ False
532
+
533
+ Edge cases
534
+ ----------
535
+ * ``n <= 1`` returns ``False``.
536
+ """
537
+ if n <= 1:
538
+ return False
539
+ if not is_prime(n):
540
+ return False
541
+ primorial = 1
542
+ for p in primes_up_to(n + 1):
543
+ primorial *= p
544
+ if primorial - 1 == n or primorial + 1 == n:
545
+ return True
546
+ if primorial > n + 1:
547
+ break
548
+ return False
549
+
550
+
551
+ @register(
552
+ name="Cullen Prime",
553
+ category="primes",
554
+ oeis="A050920",
555
+ description=(
556
+ "A prime of the form n·2^n + 1 for some positive integer n."
557
+ ),
558
+ aliases=["cullen_prime"],
559
+ )
560
+ def is_cullen_prime(n: int) -> bool:
561
+ """Return ``True`` if *n* is a Cullen prime.
562
+
563
+ A Cullen prime has the form ``k * 2^k + 1`` for some positive integer *k*.
564
+ The function iterates *k* from 1 upward until ``k * 2^k + 1`` exceeds *n*.
565
+
566
+ Parameters
567
+ ----------
568
+ n:
569
+ Integer to test.
570
+
571
+ Returns
572
+ -------
573
+ bool
574
+
575
+ Example
576
+ -------
577
+ >>> is_cullen_prime(3) # 1*2^1 + 1 = 3
578
+ True
579
+ >>> is_cullen_prime(393050634124102232869567034555427371542904833)
580
+ False # too large; practical limit applies
581
+
582
+ Notes
583
+ -----
584
+ Known Cullen primes are sparse; the first few correspond to k = 1, 141, 4713, …
585
+
586
+ Edge cases
587
+ ----------
588
+ * ``n <= 1`` returns ``False``.
589
+ """
590
+ if n <= 1:
591
+ return False
592
+ if not is_prime(n):
593
+ return False
594
+ k = 1
595
+ while True:
596
+ cullen = k * (2 ** k) + 1
597
+ if cullen == n:
598
+ return True
599
+ if cullen > n:
600
+ break
601
+ k += 1
602
+ return False
603
+
604
+
605
+ @register(
606
+ name="Woodall Prime",
607
+ category="primes",
608
+ oeis="A050918",
609
+ description=(
610
+ "A prime of the form n·2^n − 1 for some positive integer n."
611
+ ),
612
+ aliases=["woodall_prime"],
613
+ )
614
+ def is_woodall_prime(n: int) -> bool:
615
+ """Return ``True`` if *n* is a Woodall prime.
616
+
617
+ A Woodall prime has the form ``k * 2^k − 1`` for some positive integer *k*.
618
+ The function iterates *k* from 1 upward until ``k * 2^k − 1`` exceeds *n*.
619
+
620
+ Parameters
621
+ ----------
622
+ n:
623
+ Integer to test.
624
+
625
+ Returns
626
+ -------
627
+ bool
628
+
629
+ Example
630
+ -------
631
+ >>> is_woodall_prime(7) # 2*2^2 - 1 = 7
632
+ True
633
+ >>> is_woodall_prime(23) # 3*2^3 - 1 = 23
634
+ True
635
+ >>> is_woodall_prime(11)
636
+ False
637
+
638
+ Edge cases
639
+ ----------
640
+ * ``n <= 1`` returns ``False``.
641
+ """
642
+ if n <= 1:
643
+ return False
644
+ if not is_prime(n):
645
+ return False
646
+ k = 1
647
+ while True:
648
+ woodall = k * (2 ** k) - 1
649
+ if woodall == n:
650
+ return True
651
+ if woodall > n:
652
+ break
653
+ k += 1
654
+ return False
655
+
656
+
657
+ @register(
658
+ name="Carol Prime",
659
+ category="primes",
660
+ oeis="A091516",
661
+ description=(
662
+ "A prime of the form (2^n − 1)^2 − 2 for some positive integer n."
663
+ ),
664
+ aliases=["carol_prime"],
665
+ )
666
+ def is_carol_prime(n: int) -> bool:
667
+ """Return ``True`` if *n* is a Carol prime.
668
+
669
+ A Carol prime has the form ``(2^k − 1)^2 − 2`` for some positive integer
670
+ *k*. The sequence begins: 7, 47, 223, 3967, 16127, …
671
+
672
+ Parameters
673
+ ----------
674
+ n:
675
+ Integer to test.
676
+
677
+ Returns
678
+ -------
679
+ bool
680
+
681
+ Example
682
+ -------
683
+ >>> is_carol_prime(7) # (2^2 - 1)^2 - 2 = 7
684
+ True
685
+ >>> is_carol_prime(47) # (2^3 - 1)^2 - 2 = 47
686
+ True
687
+ >>> is_carol_prime(11)
688
+ False
689
+
690
+ Edge cases
691
+ ----------
692
+ * ``n <= 1`` returns ``False``.
693
+ """
694
+ if n <= 1:
695
+ return False
696
+ if not is_prime(n):
697
+ return False
698
+ k = 1
699
+ while True:
700
+ carol = (2 ** k - 1) ** 2 - 2
701
+ if carol == n:
702
+ return True
703
+ if carol > n:
704
+ break
705
+ k += 1
706
+ return False
707
+
708
+
709
+ @register(
710
+ name="Kynea Prime",
711
+ category="primes",
712
+ oeis="A091515",
713
+ description=(
714
+ "A prime of the form (2^n + 1)^2 − 2 for some positive integer n."
715
+ ),
716
+ aliases=["kynea_prime"],
717
+ )
718
+ def is_kynea_prime(n: int) -> bool:
719
+ """Return ``True`` if *n* is a Kynea prime.
720
+
721
+ A Kynea prime has the form ``(2^k + 1)^2 − 2`` for some positive integer
722
+ *k*. The sequence begins: 7, 23, 79, 1087, 66047, …
723
+
724
+ Parameters
725
+ ----------
726
+ n:
727
+ Integer to test.
728
+
729
+ Returns
730
+ -------
731
+ bool
732
+
733
+ Example
734
+ -------
735
+ >>> is_kynea_prime(7) # (2^1 + 1)^2 - 2 = 7
736
+ True
737
+ >>> is_kynea_prime(23) # (2^2 + 1)^2 - 2 = 23
738
+ True
739
+ >>> is_kynea_prime(11)
740
+ False
741
+
742
+ Edge cases
743
+ ----------
744
+ * ``n <= 1`` returns ``False``.
745
+ """
746
+ if n <= 1:
747
+ return False
748
+ if not is_prime(n):
749
+ return False
750
+ k = 1
751
+ while True:
752
+ kynea = (2 ** k + 1) ** 2 - 2
753
+ if kynea == n:
754
+ return True
755
+ if kynea > n:
756
+ break
757
+ k += 1
758
+ return False
759
+
760
+
761
+ # Pre-compute Leyland numbers up to a reasonable bound for membership tests.
762
+ def _build_leyland_set(max_base: int = 20) -> frozenset:
763
+ """Return the set of Leyland numbers x^y + y^x for 2 <= y <= x <= max_base."""
764
+ s: set = set()
765
+ for x in range(2, max_base + 1):
766
+ for y in range(2, x + 1):
767
+ s.add(x ** y + y ** x)
768
+ return frozenset(s)
769
+
770
+ _LEYLAND_NUMBERS: frozenset = _build_leyland_set(20)
771
+
772
+
773
+ @register(
774
+ name="Leyland Prime",
775
+ category="primes",
776
+ oeis="A094133",
777
+ description=(
778
+ "A prime of the form x^y + y^x for integers x, y > 1."
779
+ ),
780
+ aliases=["leyland_prime"],
781
+ )
782
+ def is_leyland_prime(n: int) -> bool:
783
+ """Return ``True`` if *n* is a Leyland prime.
784
+
785
+ A Leyland prime is a prime that can be written as ``x^y + y^x`` for some
786
+ integers ``x > y > 1``. This implementation checks bases up to 20 × 20,
787
+ covering Leyland primes including 17, 593, 32993, …
788
+
789
+ Parameters
790
+ ----------
791
+ n:
792
+ Integer to test.
793
+
794
+ Returns
795
+ -------
796
+ bool
797
+
798
+ Example
799
+ -------
800
+ >>> is_leyland_prime(17) # 2^3 + 3^2 = 8 + 9 = 17
801
+ True
802
+ >>> is_leyland_prime(593) # 2^7 + 7^2 = 128 + 49... wait, that's 177
803
+ False
804
+ >>> is_leyland_prime(32993) # 2^15 + 15^2 ... verified Leyland prime
805
+ True
806
+
807
+ Notes
808
+ -----
809
+ Only Leyland numbers generated with bases up to 20 are checked. Very large
810
+ Leyland primes requiring bases > 20 will return ``False``.
811
+
812
+ Edge cases
813
+ ----------
814
+ * ``n <= 1`` returns ``False``.
815
+ """
816
+ if n <= 1:
817
+ return False
818
+ if not is_prime(n):
819
+ return False
820
+ return n in _LEYLAND_NUMBERS
821
+
822
+
823
+ @register(
824
+ name="Pierpont Prime",
825
+ category="primes",
826
+ oeis="A005109",
827
+ description=(
828
+ "A prime of the form 2^u · 3^v + 1 for non-negative integers u, v."
829
+ ),
830
+ aliases=["pierpont_prime"],
831
+ )
832
+ def is_pierpont_prime(n: int) -> bool:
833
+ """Return ``True`` if *n* is a Pierpont prime.
834
+
835
+ A Pierpont prime is a prime of the form ``2^u * 3^v + 1`` where *u* and
836
+ *v* are non-negative integers (not both zero when the result must be prime).
837
+ Equivalently, ``n − 1`` must be 3-smooth (all prime factors are 2 or 3).
838
+
839
+ Parameters
840
+ ----------
841
+ n:
842
+ Integer to test.
843
+
844
+ Returns
845
+ -------
846
+ bool
847
+
848
+ Example
849
+ -------
850
+ >>> is_pierpont_prime(2) # 2^1 * 3^0 + 1 = 2... wait, 2^0*3^0+1=2
851
+ True
852
+ >>> is_pierpont_prime(3) # 2^1 + 1 = 3
853
+ True
854
+ >>> is_pierpont_prime(7) # 2^1 * 3^1 + 1 = 7
855
+ True
856
+ >>> is_pierpont_prime(13) # 12 = 4*3 = 2^2*3^1, 12+1=13
857
+ True
858
+ >>> is_pierpont_prime(11) # 10 = 2*5, 5 is not 2 or 3
859
+ False
860
+
861
+ Edge cases
862
+ ----------
863
+ * ``n <= 1`` returns ``False``.
864
+ """
865
+ if n <= 1:
866
+ return False
867
+ if not is_prime(n):
868
+ return False
869
+ m = n - 1
870
+ if m == 0:
871
+ return False
872
+ while m % 2 == 0:
873
+ m //= 2
874
+ while m % 3 == 0:
875
+ m //= 3
876
+ return m == 1
877
+
878
+
879
+ @register(
880
+ name="Wagstaff Prime",
881
+ category="primes",
882
+ oeis="A000978",
883
+ description=(
884
+ "A prime of the form (2^p + 1) / 3 where p is an odd prime."
885
+ ),
886
+ aliases=["wagstaff_prime"],
887
+ )
888
+ def is_wagstaff_prime(n: int) -> bool:
889
+ """Return ``True`` if *n* is a Wagstaff prime.
890
+
891
+ A Wagstaff prime has the form ``(2^p + 1) / 3`` where *p* is an odd prime.
892
+ This implementation checks candidate exponents *p* such that
893
+ ``(2^p + 1) / 3 == n``, iterating until the expression exceeds *n*.
894
+
895
+ Parameters
896
+ ----------
897
+ n:
898
+ Integer to test.
899
+
900
+ Returns
901
+ -------
902
+ bool
903
+
904
+ Example
905
+ -------
906
+ >>> is_wagstaff_prime(3) # (2^3 + 1) / 3 = 3
907
+ True
908
+ >>> is_wagstaff_prime(11) # (2^5 + 1) / 3 = 11
909
+ True
910
+ >>> is_wagstaff_prime(43) # (2^7 + 1) / 3 = 43
911
+ True
912
+ >>> is_wagstaff_prime(7)
913
+ False
914
+
915
+ Edge cases
916
+ ----------
917
+ * ``n <= 1`` returns ``False``.
918
+ """
919
+ if n <= 1:
920
+ return False
921
+ if not is_prime(n):
922
+ return False
923
+ # Check odd prime exponents p
924
+ p = 3
925
+ while True:
926
+ numerator = (2 ** p) + 1
927
+ if numerator % 3 != 0:
928
+ p = next_prime(p)
929
+ if 2 ** p > 3 * n + 1:
930
+ break
931
+ continue
932
+ candidate = numerator // 3
933
+ if candidate == n:
934
+ return True
935
+ if candidate > n:
936
+ break
937
+ p = next_prime(p)
938
+ return False
939
+
940
+
941
+ # ---------------------------------------------------------------------------
942
+ # GROUP 2 — Relationship-based primes
943
+ # ---------------------------------------------------------------------------
944
+
945
+ @register(
946
+ name="Cousin Prime",
947
+ category="primes",
948
+ oeis="A046132",
949
+ description=(
950
+ "A prime p such that p + 4 or p − 4 is also prime."
951
+ ),
952
+ aliases=["cousin_prime"],
953
+ )
954
+ def is_cousin_prime(n: int) -> bool:
955
+ """Return ``True`` if *n* is a cousin prime.
956
+
957
+ A cousin prime is a prime *p* for which *p + 4* or *p − 4* is also prime,
958
+ forming a cousin-prime pair with gap 4.
959
+
960
+ Parameters
961
+ ----------
962
+ n:
963
+ Integer to test.
964
+
965
+ Returns
966
+ -------
967
+ bool
968
+
969
+ Example
970
+ -------
971
+ >>> is_cousin_prime(7) # 3 and 7 differ by 4
972
+ True
973
+ >>> is_cousin_prime(13) # 13 and 17 differ by 4
974
+ True
975
+ >>> is_cousin_prime(5) # 1 and 9 — neither is prime
976
+ False
977
+
978
+ Edge cases
979
+ ----------
980
+ * ``n <= 1`` returns ``False``.
981
+ """
982
+ if not is_prime(n):
983
+ return False
984
+ return is_prime(n + 4) or is_prime(n - 4)
985
+
986
+
987
+ @register(
988
+ name="Sexy Prime",
989
+ category="primes",
990
+ oeis="A023201",
991
+ description=(
992
+ "A prime p such that p + 6 or p − 6 is also prime "
993
+ "(primes differing by 6)."
994
+ ),
995
+ aliases=["sexy_prime"],
996
+ )
997
+ def is_sexy_prime(n: int) -> bool:
998
+ """Return ``True`` if *n* is a sexy prime.
999
+
1000
+ A sexy prime is a prime *p* such that *p + 6* or *p − 6* is also prime.
1001
+ The name derives from the Latin *sex* (six).
1002
+
1003
+ Parameters
1004
+ ----------
1005
+ n:
1006
+ Integer to test.
1007
+
1008
+ Returns
1009
+ -------
1010
+ bool
1011
+
1012
+ Example
1013
+ -------
1014
+ >>> is_sexy_prime(5) # 5 and 11 differ by 6
1015
+ True
1016
+ >>> is_sexy_prime(7) # 7 and 13 differ by 6
1017
+ True
1018
+ >>> is_sexy_prime(23) # 17 and 23 differ by 6
1019
+ True
1020
+ >>> is_sexy_prime(3) # 3-6=-3 not prime, 3+6=9=3² not prime
1021
+ False
1022
+
1023
+ Edge cases
1024
+ ----------
1025
+ * ``n <= 1`` returns ``False``.
1026
+ """
1027
+ if not is_prime(n):
1028
+ return False
1029
+ return is_prime(n + 6) or is_prime(n - 6)
1030
+
1031
+
1032
+ @register(
1033
+ name="Prime Triplet",
1034
+ category="primes",
1035
+ oeis="A022004",
1036
+ description=(
1037
+ "A prime p that is part of a prime triplet of the form "
1038
+ "(p, p+2, p+6) or (p, p+4, p+6)."
1039
+ ),
1040
+ aliases=["prime_triplet"],
1041
+ )
1042
+ def is_prime_triplet(n: int) -> bool:
1043
+ """Return ``True`` if *n* belongs to a prime triplet.
1044
+
1045
+ A prime triplet is a set of three primes of the form ``(p, p+2, p+6)`` or
1046
+ ``(p, p+4, p+6)``. The number *n* qualifies if it can serve as the
1047
+ smallest element *p* of either form.
1048
+
1049
+ Parameters
1050
+ ----------
1051
+ n:
1052
+ Integer to test.
1053
+
1054
+ Returns
1055
+ -------
1056
+ bool
1057
+
1058
+ Example
1059
+ -------
1060
+ >>> is_prime_triplet(5) # (5, 7, 11) — form (p, p+2, p+6)
1061
+ True
1062
+ >>> is_prime_triplet(7) # (7, 11, 13) — form (p, p+4, p+6)
1063
+ True
1064
+ >>> is_prime_triplet(11) # (11, 13, 17) — (p, p+2, p+6)
1065
+ True
1066
+ >>> is_prime_triplet(23)
1067
+ False
1068
+
1069
+ Edge cases
1070
+ ----------
1071
+ * ``n <= 1`` returns ``False``.
1072
+ """
1073
+ if not is_prime(n):
1074
+ return False
1075
+ form1 = is_prime(n + 2) and is_prime(n + 6)
1076
+ form2 = is_prime(n + 4) and is_prime(n + 6)
1077
+ return form1 or form2
1078
+
1079
+
1080
+ @register(
1081
+ name="Balanced Prime",
1082
+ category="primes",
1083
+ oeis="A006562",
1084
+ description=(
1085
+ "A prime that equals the arithmetic mean of its neighbouring primes "
1086
+ "(the prime immediately below and immediately above it)."
1087
+ ),
1088
+ aliases=["balanced_prime"],
1089
+ )
1090
+ def is_balanced_prime(n: int) -> bool:
1091
+ """Return ``True`` if *n* is a balanced prime.
1092
+
1093
+ A balanced prime is a prime *p* such that ``p == (prev_prime(p) + next_prime(p)) / 2``,
1094
+ i.e. it is the arithmetic mean of its immediate prime neighbours.
1095
+
1096
+ Parameters
1097
+ ----------
1098
+ n:
1099
+ Integer to test.
1100
+
1101
+ Returns
1102
+ -------
1103
+ bool
1104
+
1105
+ Example
1106
+ -------
1107
+ >>> is_balanced_prime(5) # prev=3, next=7; (3+7)/2=5
1108
+ True
1109
+ >>> is_balanced_prime(53) # prev=47, next=59; (47+59)/2=53
1110
+ True
1111
+ >>> is_balanced_prime(7) # prev=5, next=11; (5+11)/2=8 ≠ 7
1112
+ False
1113
+
1114
+ Edge cases
1115
+ ----------
1116
+ * ``n <= 2`` returns ``False`` (2 has no previous prime).
1117
+ """
1118
+ if n <= 2:
1119
+ return False
1120
+ if not is_prime(n):
1121
+ return False
1122
+ pp = prev_prime(n)
1123
+ np_ = next_prime(n)
1124
+ return (pp + np_) == 2 * n
1125
+
1126
+
1127
+ @register(
1128
+ name="Isolated Prime",
1129
+ category="primes",
1130
+ oeis="A007510",
1131
+ description=(
1132
+ "A prime p such that neither p − 2 nor p + 2 is prime "
1133
+ "(not part of any twin-prime pair)."
1134
+ ),
1135
+ aliases=["isolated_prime"],
1136
+ )
1137
+ def is_isolated_prime(n: int) -> bool:
1138
+ """Return ``True`` if *n* is an isolated prime.
1139
+
1140
+ An isolated prime is a prime *p* for which both ``p − 2`` and ``p + 2``
1141
+ are composite. In other words, it is not a member of any twin-prime pair.
1142
+
1143
+ Parameters
1144
+ ----------
1145
+ n:
1146
+ Integer to test.
1147
+
1148
+ Returns
1149
+ -------
1150
+ bool
1151
+
1152
+ Example
1153
+ -------
1154
+ >>> is_isolated_prime(23) # 21=3*7, 25=5² — both composite
1155
+ True
1156
+ >>> is_isolated_prime(5) # 3 is prime (twin with 5)
1157
+ False
1158
+
1159
+ Edge cases
1160
+ ----------
1161
+ * ``n <= 1`` returns ``False``.
1162
+ """
1163
+ if not is_prime(n):
1164
+ return False
1165
+ return not is_prime(n - 2) and not is_prime(n + 2)
1166
+
1167
+
1168
+ @register(
1169
+ name="Chen Prime",
1170
+ category="primes",
1171
+ oeis="A109611",
1172
+ description=(
1173
+ "A prime p such that p + 2 is either prime or semiprime "
1174
+ "(product of exactly two primes)."
1175
+ ),
1176
+ aliases=["chen_prime"],
1177
+ )
1178
+ def is_chen_prime(n: int) -> bool:
1179
+ """Return ``True`` if *n* is a Chen prime.
1180
+
1181
+ A Chen prime is a prime *p* such that ``p + 2`` is either prime or
1182
+ semiprime (i.e. has exactly two prime factors counting multiplicity).
1183
+
1184
+ Parameters
1185
+ ----------
1186
+ n:
1187
+ Integer to test.
1188
+
1189
+ Returns
1190
+ -------
1191
+ bool
1192
+
1193
+ Example
1194
+ -------
1195
+ >>> is_chen_prime(2) # 2+2=4=2², semiprime
1196
+ True
1197
+ >>> is_chen_prime(3) # 3+2=5, prime
1198
+ True
1199
+ >>> is_chen_prime(5) # 5+2=7, prime
1200
+ True
1201
+ >>> is_chen_prime(7) # 7+2=9=3², semiprime
1202
+ True
1203
+ >>> is_chen_prime(11) # 11+2=13, prime
1204
+ True
1205
+
1206
+ Edge cases
1207
+ ----------
1208
+ * ``n <= 1`` returns ``False``.
1209
+ """
1210
+ if not is_prime(n):
1211
+ return False
1212
+ return is_prime(n + 2) or is_semiprime(n + 2)
1213
+
1214
+
1215
+ @register(
1216
+ name="Strong Prime",
1217
+ category="primes",
1218
+ oeis="A051634",
1219
+ description=(
1220
+ "A prime greater than the arithmetic mean of its immediate "
1221
+ "prime neighbours."
1222
+ ),
1223
+ aliases=["strong_prime"],
1224
+ )
1225
+ def is_strong_prime(n: int) -> bool:
1226
+ """Return ``True`` if *n* is a strong prime.
1227
+
1228
+ A strong prime is a prime *p* that is strictly greater than the arithmetic
1229
+ mean of the prime immediately below it and the prime immediately above it.
1230
+
1231
+ Parameters
1232
+ ----------
1233
+ n:
1234
+ Integer to test.
1235
+
1236
+ Returns
1237
+ -------
1238
+ bool
1239
+
1240
+ Example
1241
+ -------
1242
+ >>> is_strong_prime(11) # prev=7, next=13; mean=10; 11>10
1243
+ True
1244
+ >>> is_strong_prime(7) # prev=5, next=11; mean=8; 7<8
1245
+ False
1246
+
1247
+ Edge cases
1248
+ ----------
1249
+ * ``n <= 2`` returns ``False``.
1250
+ """
1251
+ if n <= 2:
1252
+ return False
1253
+ if not is_prime(n):
1254
+ return False
1255
+ pp = prev_prime(n)
1256
+ np_ = next_prime(n)
1257
+ return 2 * n > pp + np_
1258
+
1259
+
1260
+ @register(
1261
+ name="Weak Prime",
1262
+ category="primes",
1263
+ oeis="A051635",
1264
+ description=(
1265
+ "A prime less than the arithmetic mean of its immediate "
1266
+ "prime neighbours."
1267
+ ),
1268
+ aliases=["weak_prime"],
1269
+ )
1270
+ def is_weak_prime(n: int) -> bool:
1271
+ """Return ``True`` if *n* is a weak prime.
1272
+
1273
+ A weak prime is a prime *p* that is strictly less than the arithmetic mean
1274
+ of the prime immediately below it and the prime immediately above it.
1275
+
1276
+ Parameters
1277
+ ----------
1278
+ n:
1279
+ Integer to test.
1280
+
1281
+ Returns
1282
+ -------
1283
+ bool
1284
+
1285
+ Example
1286
+ -------
1287
+ >>> is_weak_prime(3) # prev=2, next=5; mean=3.5; 3<3.5
1288
+ True
1289
+ >>> is_weak_prime(7) # prev=5, next=11; mean=8; 7<8
1290
+ True
1291
+ >>> is_weak_prime(11) # prev=7, next=13; mean=10; 11>10
1292
+ False
1293
+
1294
+ Edge cases
1295
+ ----------
1296
+ * ``n <= 2`` returns ``False``.
1297
+ """
1298
+ if n <= 2:
1299
+ return False
1300
+ if not is_prime(n):
1301
+ return False
1302
+ pp = prev_prime(n)
1303
+ np_ = next_prime(n)
1304
+ return 2 * n < pp + np_
1305
+
1306
+
1307
+ # ---------------------------------------------------------------------------
1308
+ # GROUP 3 — Digital/representational primes
1309
+ # ---------------------------------------------------------------------------
1310
+
1311
+ @register(
1312
+ name="Palindromic Prime",
1313
+ category="primes",
1314
+ oeis="A002385",
1315
+ description=(
1316
+ "A prime whose decimal representation reads the same forwards and "
1317
+ "backwards."
1318
+ ),
1319
+ aliases=["palindromic_prime"],
1320
+ )
1321
+ def is_palindromic_prime(n: int) -> bool:
1322
+ """Return ``True`` if *n* is a palindromic prime.
1323
+
1324
+ A palindromic prime is both prime and a palindrome in base 10
1325
+ (its digits read identically in both directions).
1326
+
1327
+ Parameters
1328
+ ----------
1329
+ n:
1330
+ Integer to test.
1331
+
1332
+ Returns
1333
+ -------
1334
+ bool
1335
+
1336
+ Example
1337
+ -------
1338
+ >>> is_palindromic_prime(11)
1339
+ True
1340
+ >>> is_palindromic_prime(131)
1341
+ True
1342
+ >>> is_palindromic_prime(13)
1343
+ False
1344
+
1345
+ Edge cases
1346
+ ----------
1347
+ * ``n <= 1`` returns ``False``.
1348
+ * All single-digit primes (2, 3, 5, 7) are palindromic primes.
1349
+ """
1350
+ if not is_prime(n):
1351
+ return False
1352
+ s = str(n)
1353
+ return s == s[::-1]
1354
+
1355
+
1356
+ @register(
1357
+ name="Emirp",
1358
+ category="primes",
1359
+ oeis="A006567",
1360
+ description=(
1361
+ "A prime whose digit reversal is a different prime."
1362
+ ),
1363
+ aliases=["emirp"],
1364
+ )
1365
+ def is_emirp(n: int) -> bool:
1366
+ """Return ``True`` if *n* is an emirp.
1367
+
1368
+ An emirp (prime spelled backwards) is a prime *p* whose digit reversal
1369
+ is a *different* prime. Palindromic primes are excluded since reversing
1370
+ them yields the same number.
1371
+
1372
+ Parameters
1373
+ ----------
1374
+ n:
1375
+ Integer to test.
1376
+
1377
+ Returns
1378
+ -------
1379
+ bool
1380
+
1381
+ Example
1382
+ -------
1383
+ >>> is_emirp(13) # reversed: 31, prime, and 31 ≠ 13
1384
+ True
1385
+ >>> is_emirp(11) # palindrome — reversed equals itself
1386
+ False
1387
+ >>> is_emirp(17) # reversed: 71, prime
1388
+ True
1389
+
1390
+ Edge cases
1391
+ ----------
1392
+ * ``n <= 1`` returns ``False``.
1393
+ """
1394
+ if not is_prime(n):
1395
+ return False
1396
+ rev = int(str(n)[::-1])
1397
+ return rev != n and is_prime(rev)
1398
+
1399
+
1400
+ @register(
1401
+ name="Circular Prime",
1402
+ category="primes",
1403
+ oeis="A068652",
1404
+ description=(
1405
+ "A prime all of whose cyclic rotations of digits are also prime."
1406
+ ),
1407
+ aliases=["circular_prime"],
1408
+ )
1409
+ def is_circular_prime(n: int) -> bool:
1410
+ """Return ``True`` if *n* is a circular prime.
1411
+
1412
+ A circular prime is a prime for which every cyclic rotation of its digits
1413
+ is also prime. For example, 197 yields rotations 197, 971, 719 — all prime.
1414
+
1415
+ Parameters
1416
+ ----------
1417
+ n:
1418
+ Integer to test.
1419
+
1420
+ Returns
1421
+ -------
1422
+ bool
1423
+
1424
+ Example
1425
+ -------
1426
+ >>> is_circular_prime(197)
1427
+ True
1428
+ >>> is_circular_prime(113) # rotation 311 prime, 131 prime, 113 prime
1429
+ True
1430
+ >>> is_circular_prime(19) # 19 prime, 91=7*13 not prime
1431
+ False
1432
+
1433
+ Edge cases
1434
+ ----------
1435
+ * ``n <= 1`` returns ``False``.
1436
+ """
1437
+ if not is_prime(n):
1438
+ return False
1439
+ s = str(n)
1440
+ k = len(s)
1441
+ for i in range(1, k):
1442
+ rotation = int(s[i:] + s[:i])
1443
+ if not is_prime(rotation):
1444
+ return False
1445
+ return True
1446
+
1447
+
1448
+ @register(
1449
+ name="Left-Truncatable Prime",
1450
+ category="primes",
1451
+ oeis="A024785",
1452
+ description=(
1453
+ "A prime that remains prime after repeatedly removing the "
1454
+ "leftmost digit."
1455
+ ),
1456
+ aliases=["truncatable_prime_left"],
1457
+ )
1458
+ def is_truncatable_prime_left(n: int) -> bool:
1459
+ """Return ``True`` if *n* is a left-truncatable prime.
1460
+
1461
+ A left-truncatable prime is a prime such that removing digits from the
1462
+ left one at a time always yields a prime. Single-digit primes are
1463
+ considered trivially left-truncatable.
1464
+
1465
+ Parameters
1466
+ ----------
1467
+ n:
1468
+ Integer to test.
1469
+
1470
+ Returns
1471
+ -------
1472
+ bool
1473
+
1474
+ Example
1475
+ -------
1476
+ >>> is_truncatable_prime_left(9137) # 9137→137→37→7, all prime
1477
+ True
1478
+ >>> is_truncatable_prime_left(317) # 317→17→7, all prime
1479
+ True
1480
+ >>> is_truncatable_prime_left(23) # 23→3 prime; 23 prime; ok
1481
+ True
1482
+
1483
+ Edge cases
1484
+ ----------
1485
+ * ``n <= 1`` returns ``False``.
1486
+ * Digits of value 0 intermediate results are considered non-prime, so
1487
+ any truncation producing a leading-zero number (< the digit count
1488
+ suggests) is handled by normal integer conversion.
1489
+ """
1490
+ if not is_prime(n):
1491
+ return False
1492
+ s = str(n)
1493
+ for i in range(1, len(s)):
1494
+ truncated = int(s[i:])
1495
+ if not is_prime(truncated):
1496
+ return False
1497
+ return True
1498
+
1499
+
1500
+ @register(
1501
+ name="Right-Truncatable Prime",
1502
+ category="primes",
1503
+ oeis="A024770",
1504
+ description=(
1505
+ "A prime that remains prime after repeatedly removing the "
1506
+ "rightmost digit."
1507
+ ),
1508
+ aliases=["truncatable_prime_right"],
1509
+ )
1510
+ def is_truncatable_prime_right(n: int) -> bool:
1511
+ """Return ``True`` if *n* is a right-truncatable prime.
1512
+
1513
+ A right-truncatable prime is a prime such that removing digits from the
1514
+ right one at a time always yields a prime. Single-digit primes are
1515
+ trivially right-truncatable.
1516
+
1517
+ Parameters
1518
+ ----------
1519
+ n:
1520
+ Integer to test.
1521
+
1522
+ Returns
1523
+ -------
1524
+ bool
1525
+
1526
+ Example
1527
+ -------
1528
+ >>> is_truncatable_prime_right(7393) # 7393→739→73→7, all prime
1529
+ True
1530
+ >>> is_truncatable_prime_right(373) # 373→37→3, all prime
1531
+ True
1532
+ >>> is_truncatable_prime_right(23) # 23→2, both prime
1533
+ True
1534
+
1535
+ Edge cases
1536
+ ----------
1537
+ * ``n <= 1`` returns ``False``.
1538
+ """
1539
+ if not is_prime(n):
1540
+ return False
1541
+ m = n // 10
1542
+ while m > 0:
1543
+ if not is_prime(m):
1544
+ return False
1545
+ m //= 10
1546
+ return True
1547
+
1548
+
1549
+ @register(
1550
+ name="Permutable Prime",
1551
+ category="primes",
1552
+ oeis="A003459",
1553
+ description=(
1554
+ "A prime for which every permutation of its digits is also prime."
1555
+ ),
1556
+ aliases=["permutable_prime"],
1557
+ )
1558
+ def is_permutable_prime(n: int) -> bool:
1559
+ """Return ``True`` if *n* is a permutable prime (absolute prime).
1560
+
1561
+ A permutable prime, also called an absolute prime, is a prime for which
1562
+ every possible permutation of its digits is also prime.
1563
+
1564
+ Parameters
1565
+ ----------
1566
+ n:
1567
+ Integer to test.
1568
+
1569
+ Returns
1570
+ -------
1571
+ bool
1572
+
1573
+ Example
1574
+ -------
1575
+ >>> is_permutable_prime(13) # permutations: 13, 31 — both prime
1576
+ True
1577
+ >>> is_permutable_prime(337) # 337, 373, 733 — all prime
1578
+ True
1579
+ >>> is_permutable_prime(7) # single digit
1580
+ True
1581
+
1582
+ Notes
1583
+ -----
1584
+ This check is exponentially expensive in the number of digits. A guard
1585
+ is applied for ``n >= 1_000_000`` to avoid excessive computation.
1586
+
1587
+ Edge cases
1588
+ ----------
1589
+ * ``n <= 1`` returns ``False``.
1590
+ * ``n >= 1_000_000`` returns ``False`` (guard limit).
1591
+ """
1592
+ if not is_prime(n):
1593
+ return False
1594
+ if n >= 1_000_000:
1595
+ return False
1596
+ digits = str(n)
1597
+ for perm in set(itertools.permutations(digits)):
1598
+ candidate = int("".join(perm))
1599
+ if not is_prime(candidate):
1600
+ return False
1601
+ return True
1602
+
1603
+
1604
+ @register(
1605
+ name="Repunit Prime",
1606
+ category="primes",
1607
+ oeis="A004022",
1608
+ description=(
1609
+ "A prime consisting entirely of the digit 1 in decimal "
1610
+ "(a repunit that is prime)."
1611
+ ),
1612
+ aliases=["repunit_prime"],
1613
+ )
1614
+ def is_repunit_prime(n: int) -> bool:
1615
+ """Return ``True`` if *n* is a repunit prime.
1616
+
1617
+ A repunit prime is a prime number whose decimal representation consists
1618
+ solely of the digit 1 (i.e. 11, 1111111111111111111, …). The repunit
1619
+ R_k = (10^k − 1) / 9.
1620
+
1621
+ Parameters
1622
+ ----------
1623
+ n:
1624
+ Integer to test.
1625
+
1626
+ Returns
1627
+ -------
1628
+ bool
1629
+
1630
+ Example
1631
+ -------
1632
+ >>> is_repunit_prime(11)
1633
+ True
1634
+ >>> is_repunit_prime(1111111111111111111) # R_19, known repunit prime
1635
+ True
1636
+ >>> is_repunit_prime(111) # 111 = 3 * 37, composite
1637
+ False
1638
+
1639
+ Edge cases
1640
+ ----------
1641
+ * Single-digit 1 is not prime, returns ``False``.
1642
+ """
1643
+ if not is_prime(n):
1644
+ return False
1645
+ return all(c == "1" for c in str(n))
1646
+
1647
+
1648
+ # ---------------------------------------------------------------------------
1649
+ # GROUP 4 — Modular/congruence primes
1650
+ # ---------------------------------------------------------------------------
1651
+
1652
+ @register(
1653
+ name="Pythagorean Prime",
1654
+ category="primes",
1655
+ oeis="A002144",
1656
+ description=(
1657
+ "A prime of the form 4n + 1; equivalently, a prime expressible "
1658
+ "as the sum of two squares."
1659
+ ),
1660
+ aliases=["pythagorean_prime"],
1661
+ )
1662
+ def is_pythagorean_prime(n: int) -> bool:
1663
+ """Return ``True`` if *n* is a Pythagorean prime.
1664
+
1665
+ A Pythagorean prime is a prime congruent to 1 modulo 4, i.e. of the form
1666
+ ``4k + 1``. By Fermat's theorem on sums of two squares, these are exactly
1667
+ the odd primes expressible as a sum of two squares.
1668
+
1669
+ Parameters
1670
+ ----------
1671
+ n:
1672
+ Integer to test.
1673
+
1674
+ Returns
1675
+ -------
1676
+ bool
1677
+
1678
+ Example
1679
+ -------
1680
+ >>> is_pythagorean_prime(5) # 5 = 4*1 + 1
1681
+ True
1682
+ >>> is_pythagorean_prime(13) # 13 = 4*3 + 1
1683
+ True
1684
+ >>> is_pythagorean_prime(7) # 7 ≡ 3 (mod 4)
1685
+ False
1686
+
1687
+ Edge cases
1688
+ ----------
1689
+ * ``n <= 1`` returns ``False``.
1690
+ """
1691
+ return is_prime(n) and n % 4 == 1
1692
+
1693
+
1694
+ @register(
1695
+ name="Gaussian Prime",
1696
+ category="primes",
1697
+ oeis="A002145",
1698
+ description=(
1699
+ "A real prime that remains prime in the Gaussian integers: "
1700
+ "p = 2, or p ≡ 3 (mod 4)."
1701
+ ),
1702
+ aliases=["gaussian_prime"],
1703
+ )
1704
+ def is_gaussian_prime(n: int) -> bool:
1705
+ """Return ``True`` if *n* is a real Gaussian prime.
1706
+
1707
+ A positive integer *p* is a Gaussian prime (on the real axis of the
1708
+ Gaussian integers ℤ[i]) if and only if it is prime and congruent to
1709
+ 3 modulo 4, or equals 2. Primes congruent to 1 mod 4 split in ℤ[i]
1710
+ and are therefore not Gaussian primes on the real axis.
1711
+
1712
+ Parameters
1713
+ ----------
1714
+ n:
1715
+ Integer to test.
1716
+
1717
+ Returns
1718
+ -------
1719
+ bool
1720
+
1721
+ Example
1722
+ -------
1723
+ >>> is_gaussian_prime(2)
1724
+ True
1725
+ >>> is_gaussian_prime(3) # 3 ≡ 3 (mod 4)
1726
+ True
1727
+ >>> is_gaussian_prime(7) # 7 ≡ 3 (mod 4)
1728
+ True
1729
+ >>> is_gaussian_prime(5) # 5 ≡ 1 (mod 4), splits as (2+i)(2-i)
1730
+ False
1731
+
1732
+ Edge cases
1733
+ ----------
1734
+ * ``n <= 1`` returns ``False``.
1735
+ """
1736
+ if not is_prime(n):
1737
+ return False
1738
+ return n == 2 or n % 4 == 3
1739
+
1740
+
1741
+ @register(
1742
+ name="Eisenstein Prime",
1743
+ category="primes",
1744
+ oeis="A003627",
1745
+ description=(
1746
+ "A real prime that remains prime in the Eisenstein integers: "
1747
+ "p = 3, or p ≡ 2 (mod 3)."
1748
+ ),
1749
+ aliases=["eisenstein_prime"],
1750
+ )
1751
+ def is_eisenstein_prime(n: int) -> bool:
1752
+ """Return ``True`` if *n* is a real Eisenstein prime.
1753
+
1754
+ A positive integer *p* is an Eisenstein prime (on the real axis of the
1755
+ Eisenstein integers ℤ[ω]) if it is prime and congruent to 2 modulo 3,
1756
+ or equals 3. Primes congruent to 1 mod 3 split in ℤ[ω].
1757
+
1758
+ Parameters
1759
+ ----------
1760
+ n:
1761
+ Integer to test.
1762
+
1763
+ Returns
1764
+ -------
1765
+ bool
1766
+
1767
+ Example
1768
+ -------
1769
+ >>> is_eisenstein_prime(2) # 2 ≡ 2 (mod 3)
1770
+ True
1771
+ >>> is_eisenstein_prime(3)
1772
+ True
1773
+ >>> is_eisenstein_prime(5) # 5 ≡ 2 (mod 3)
1774
+ True
1775
+ >>> is_eisenstein_prime(7) # 7 ≡ 1 (mod 3), splits
1776
+ False
1777
+
1778
+ Edge cases
1779
+ ----------
1780
+ * ``n <= 1`` returns ``False``.
1781
+ """
1782
+ if not is_prime(n):
1783
+ return False
1784
+ return n == 3 or n % 3 == 2
1785
+
1786
+
1787
+ @register(
1788
+ name="Wilson Prime",
1789
+ category="primes",
1790
+ oeis="A007540",
1791
+ description=(
1792
+ "A prime p satisfying (p−1)! ≡ −1 (mod p²). "
1793
+ "Only three are known: 5, 13, 563."
1794
+ ),
1795
+ aliases=["wilson_prime"],
1796
+ )
1797
+ def is_wilson_prime(n: int) -> bool:
1798
+ """Return ``True`` if *n* is a Wilson prime.
1799
+
1800
+ A Wilson prime is a prime *p* for which ``(p−1)! ≡ −1 (mod p²)``.
1801
+ Only three Wilson primes are currently known: 5, 13, and 563.
1802
+
1803
+ This implementation computes ``(n−1)! mod n²`` directly using
1804
+ ``math.factorial``. To avoid impractically large computation, values
1805
+ of *n* above 600 return ``False`` early (563 is the largest known Wilson
1806
+ prime, and computation for larger primes is infeasible with factorial).
1807
+
1808
+ Parameters
1809
+ ----------
1810
+ n:
1811
+ Integer to test.
1812
+
1813
+ Returns
1814
+ -------
1815
+ bool
1816
+
1817
+ Example
1818
+ -------
1819
+ >>> is_wilson_prime(5)
1820
+ True
1821
+ >>> is_wilson_prime(13)
1822
+ True
1823
+ >>> is_wilson_prime(563)
1824
+ True
1825
+ >>> is_wilson_prime(7)
1826
+ False
1827
+
1828
+ Edge cases
1829
+ ----------
1830
+ * ``n <= 1`` returns ``False``.
1831
+ * ``n > 600`` returns ``False`` (computation guard).
1832
+ """
1833
+ if not is_prime(n):
1834
+ return False
1835
+ if n > 600:
1836
+ return False
1837
+ p2 = n * n
1838
+ return math.factorial(n - 1) % p2 == p2 - 1
1839
+
1840
+
1841
+ @register(
1842
+ name="Wieferich Prime",
1843
+ category="primes",
1844
+ oeis="A001220",
1845
+ description=(
1846
+ "A prime p where 2^(p−1) ≡ 1 (mod p²). "
1847
+ "Only two are known: 1093, 3511."
1848
+ ),
1849
+ aliases=["wieferich_prime"],
1850
+ )
1851
+ def is_wieferich_prime(n: int) -> bool:
1852
+ """Return ``True`` if *n* is a Wieferich prime.
1853
+
1854
+ A Wieferich prime is a prime *p* satisfying ``2^(p−1) ≡ 1 (mod p²)``.
1855
+ Only two Wieferich primes are known: 1093 and 3511.
1856
+
1857
+ Although ``pow(2, p-1, p**2)`` can be computed efficiently for large *p*
1858
+ via Python's built-in modular exponentiation, this function checks against
1859
+ the known set ``{1093, 3511}`` because no other Wieferich primes exist
1860
+ below 6.7 × 10¹⁵ (as of 2023), making a computation-only approach
1861
+ practically equivalent.
1862
+
1863
+ Parameters
1864
+ ----------
1865
+ n:
1866
+ Integer to test.
1867
+
1868
+ Returns
1869
+ -------
1870
+ bool
1871
+
1872
+ Example
1873
+ -------
1874
+ >>> is_wieferich_prime(1093)
1875
+ True
1876
+ >>> is_wieferich_prime(3511)
1877
+ True
1878
+ >>> is_wieferich_prime(2)
1879
+ False
1880
+
1881
+ Notes
1882
+ -----
1883
+ To verify by computation: ``pow(2, n - 1, n * n) == 1``.
1884
+ """
1885
+ return n in {1093, 3511}
1886
+
1887
+
1888
+ @register(
1889
+ name="Wall-Sun-Sun Prime",
1890
+ category="primes",
1891
+ oeis="A007732",
1892
+ description=(
1893
+ "A prime p where the Fibonacci number F(p) ≡ (p/5) (mod p²), "
1894
+ "where (p/5) is the Legendre symbol. None are currently known."
1895
+ ),
1896
+ aliases=["wall_sun_sun_prime"],
1897
+ )
1898
+ def is_wall_sun_sun_prime(n: int) -> bool:
1899
+ """Return ``True`` if *n* is a Wall-Sun-Sun prime.
1900
+
1901
+ A Wall-Sun-Sun prime (also called a Fibonacci-Wieferich prime) is a prime
1902
+ *p* such that ``F(p) ≡ (p | 5) (mod p²)``, where ``(p | 5)`` is the
1903
+ Legendre symbol and ``F(p)`` is the *p*-th Fibonacci number.
1904
+
1905
+ **No Wall-Sun-Sun prime is currently known.** An extensive computational
1906
+ search has verified none exist below approximately 9.7 × 10¹⁴. This
1907
+ function therefore always returns ``False``.
1908
+
1909
+ Parameters
1910
+ ----------
1911
+ n:
1912
+ Integer to test.
1913
+
1914
+ Returns
1915
+ -------
1916
+ bool
1917
+ Always ``False`` — no Wall-Sun-Sun prime is known.
1918
+
1919
+ Example
1920
+ -------
1921
+ >>> is_wall_sun_sun_prime(5)
1922
+ False
1923
+ >>> is_wall_sun_sun_prime(1000003)
1924
+ False
1925
+ """
1926
+ return False
1927
+
1928
+
1929
+ @register(
1930
+ name="Wolstenholme Prime",
1931
+ category="primes",
1932
+ oeis="A088164",
1933
+ description=(
1934
+ "A prime p where C(2p, p) ≡ 2 (mod p⁴). "
1935
+ "Only two are known: 16843, 2124679."
1936
+ ),
1937
+ aliases=["wolstenholme_prime"],
1938
+ )
1939
+ def is_wolstenholme_prime(n: int) -> bool:
1940
+ """Return ``True`` if *n* is a Wolstenholme prime.
1941
+
1942
+ A Wolstenholme prime is a prime *p* for which the central binomial
1943
+ coefficient ``C(2p, p) ≡ 2 (mod p⁴)``. Only two are known: 16843 and
1944
+ 2124679.
1945
+
1946
+ This function checks membership in the known set rather than computing
1947
+ the binomial coefficient (which grows astronomically).
1948
+
1949
+ Parameters
1950
+ ----------
1951
+ n:
1952
+ Integer to test.
1953
+
1954
+ Returns
1955
+ -------
1956
+ bool
1957
+
1958
+ Example
1959
+ -------
1960
+ >>> is_wolstenholme_prime(16843)
1961
+ True
1962
+ >>> is_wolstenholme_prime(2124679)
1963
+ True
1964
+ >>> is_wolstenholme_prime(7)
1965
+ False
1966
+
1967
+ Notes
1968
+ -----
1969
+ All primes up to approximately 10⁹ have been checked; no others are known.
1970
+ """
1971
+ return n in {16843, 2124679}
1972
+
1973
+
1974
+ def _lucky_numbers_up_to(n: int) -> frozenset:
1975
+ """Return the set of lucky numbers up to *n* via the lucky-number sieve.
1976
+
1977
+ The sieve starts with odd positive integers [1, 3, 5, 7, …] and
1978
+ repeatedly removes every *k*-th remaining element where *k* is the second
1979
+ element (3), then third element (7), and so on.
1980
+
1981
+ Parameters
1982
+ ----------
1983
+ n:
1984
+ Upper bound (inclusive).
1985
+
1986
+ Returns
1987
+ -------
1988
+ frozenset[int]
1989
+ """
1990
+ if n < 1:
1991
+ return frozenset()
1992
+ # Start with odd numbers
1993
+ sieve = list(range(1, n + 1, 2))
1994
+ idx = 1 # Start with the second element
1995
+ while idx < len(sieve):
1996
+ step = sieve[idx]
1997
+ # Remove every step-th element (1-indexed)
1998
+ sieve = [v for i, v in enumerate(sieve) if (i + 1) % step != 0]
1999
+ idx += 1
2000
+ if idx >= len(sieve):
2001
+ break
2002
+ return frozenset(sieve)
2003
+
2004
+
2005
+ @register(
2006
+ name="Lucky Prime",
2007
+ category="primes",
2008
+ oeis="A031157",
2009
+ description=(
2010
+ "A number that is both prime and a lucky number."
2011
+ ),
2012
+ aliases=["lucky_prime"],
2013
+ )
2014
+ def is_lucky_prime(n: int) -> bool:
2015
+ """Return ``True`` if *n* is a lucky prime.
2016
+
2017
+ A lucky prime is a number that belongs to both the sequence of primes and
2018
+ the sequence of lucky numbers. Lucky numbers are generated by a sieve
2019
+ similar to the Sieve of Eratosthenes applied to the odd positive integers.
2020
+
2021
+ Parameters
2022
+ ----------
2023
+ n:
2024
+ Integer to test.
2025
+
2026
+ Returns
2027
+ -------
2028
+ bool
2029
+
2030
+ Example
2031
+ -------
2032
+ >>> is_lucky_prime(3)
2033
+ True
2034
+ >>> is_lucky_prime(7)
2035
+ True
2036
+ >>> is_lucky_prime(13)
2037
+ True
2038
+ >>> is_lucky_prime(5) # 5 is prime but not lucky
2039
+ False
2040
+
2041
+ Edge cases
2042
+ ----------
2043
+ * ``n <= 1`` returns ``False``.
2044
+ """
2045
+ if not is_prime(n):
2046
+ return False
2047
+ lucky = _lucky_numbers_up_to(n)
2048
+ return n in lucky
2049
+
2050
+
2051
+ # ---------------------------------------------------------------------------
2052
+ # GROUP 5 (partial) — Semiprime (registered)
2053
+ # ---------------------------------------------------------------------------
2054
+
2055
+ @register(
2056
+ name="Semiprime",
2057
+ category="primes",
2058
+ oeis="A001358",
2059
+ description=(
2060
+ "A natural number that is the product of exactly two prime numbers "
2061
+ "(not necessarily distinct)."
2062
+ ),
2063
+ aliases=["semiprime"],
2064
+ )
2065
+ def is_semiprime(n: int) -> bool:
2066
+ """Return ``True`` if *n* is a semiprime.
2067
+
2068
+ A semiprime is a natural number that is the product of exactly two primes,
2069
+ counting multiplicity. Equivalently, its prime factorisation has exactly
2070
+ two factors (e.g. 4 = 2×2, 6 = 2×3, 9 = 3×3, 15 = 3×5).
2071
+
2072
+ Parameters
2073
+ ----------
2074
+ n:
2075
+ Integer to test.
2076
+
2077
+ Returns
2078
+ -------
2079
+ bool
2080
+
2081
+ Example
2082
+ -------
2083
+ >>> is_semiprime(4) # 2 * 2
2084
+ True
2085
+ >>> is_semiprime(6) # 2 * 3
2086
+ True
2087
+ >>> is_semiprime(9) # 3 * 3
2088
+ True
2089
+ >>> is_semiprime(12) # 2 * 2 * 3 — three factors
2090
+ False
2091
+ >>> is_semiprime(7) # prime — one factor
2092
+ False
2093
+
2094
+ Edge cases
2095
+ ----------
2096
+ * ``n <= 1`` returns ``False``.
2097
+ """
2098
+ if n <= 1:
2099
+ return False
2100
+ return len(prime_factors(n)) == 2