strong_fermat_pseudoprimes_in_range.pl 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. #!/usr/bin/perl
  2. # Daniel "Trizen" Șuteu
  3. # Date: 24 September 2022
  4. # https://github.com/trizen
  5. # Generate all the k-omega strong Fermat pseudoprimes in range [A,B]. (not in sorted order)
  6. # Definition:
  7. # k-omega primes are numbers n such that omega(n) = k.
  8. # See also:
  9. # https://en.wikipedia.org/wiki/Almost_prime
  10. # https://en.wikipedia.org/wiki/Prime_omega_function
  11. # https://trizenx.blogspot.com/2020/08/pseudoprimes-construction-methods-and.html
  12. =for comment
  13. # PARI/GP program (slow):
  14. strong_fermat_psp(A, B, k, base) = A=max(A, vecprod(primes(k))); (f(m, l, p, j, k_exp, congr) = my(list=List()); forprime(q=p, sqrtnint(B\m, j), if(base%q != 0, my(tv=valuation(q-1, 2)); if(tv > k_exp && Mod(base, q)^(((q-1)>>tv)<<k_exp) == congr, my(v=m*q, t=q, r=nextprime(q+1)); while(v <= B, my(L=lcm(l, znorder(Mod(base, t)))); if(gcd(L, v) == 1, if(j==1, if(v>=A && if(k==1, !isprime(v), 1) && (v-1)%L == 0, listput(list, v)), if(v*r <= B, list=concat(list, f(v, L, r, j-1, k_exp, congr)))), break); v *= q; t *= q)))); list); my(r=f(1, 1, 2, k, 0, 1)); for(v=0, logint(B, 2), r=concat(r, f(1, 1, 2, k, v, -1))); vecsort(Vec(r));
  15. # PARI/GP program (fast):
  16. strong_check(p, base, e, r) = my(tv=valuation(p-1, 2)); tv > e && Mod(base, p)^((p-1)>>(tv-e)) == r;
  17. strong_fermat_psp(A, B, k, base) = A=max(A, vecprod(primes(k))); (f(m, l, lo, k, e, r) = my(list=List()); my(hi=sqrtnint(B\m, k)); if(lo > hi, return(list)); if(k==1, forstep(p=lift(1/Mod(m, l)), hi, l, if(isprimepower(p) && gcd(m*base, p) == 1 && strong_check(p, base, e, r), my(n=m*p); if(n >= A && (n-1) % znorder(Mod(base, p)) == 0, listput(list, n)))), forprime(p=lo, hi, base%p == 0 && next; strong_check(p, base, e, r) || next; my(z=znorder(Mod(base, p))); gcd(m,z) == 1 || next; my(q=p, v=m*p); while(v <= B, list=concat(list, f(v, lcm(l, z), p+1, k-1, e, r)); q *= p; Mod(base, q)^z == 1 || break; v *= p))); list); my(res=f(1, 1, 2, k, 0, 1)); for(v=0, logint(B, 2), res=concat(res, f(1, 1, 2, k, v, -1))); vecsort(Set(res));
  18. =cut
  19. use 5.020;
  20. use warnings;
  21. use ntheory qw(:all);
  22. use experimental qw(signatures);
  23. sub divceil ($x, $y) { # ceil(x/y)
  24. (($x % $y == 0) ? 0 : 1) + divint($x, $y);
  25. }
  26. sub strong_fermat_pseudoprimes_in_range ($A, $B, $k, $base) {
  27. $A = vecmax($A, pn_primorial($k));
  28. $A > $B and return;
  29. my %seen;
  30. my @list;
  31. my $generator = sub ($m, $L, $lo, $j, $k_exp, $congr) {
  32. my $hi = rootint(divint($B, $m), $j);
  33. if ($lo > $hi) {
  34. return;
  35. }
  36. if ($j == 1) {
  37. if ($L == 1) { # optimization
  38. foreach my $p (@{primes($lo, $hi)}) {
  39. $base % $p == 0 and next;
  40. my $val = valuation($p - 1, 2);
  41. $val > $k_exp or next;
  42. powmod($base, ($p - 1) >> ($val - $k_exp), $p) == ($congr % $p) or next;
  43. for (my $v = (($m == 1) ? ($p * $p) : ($m * $p)) ; $v <= $B ; $v *= $p) {
  44. $v >= $A or next;
  45. powmod($base, $v - 1, $v) == 1 or last;
  46. push(@list, $v) if !$seen{$v}++;
  47. }
  48. }
  49. return;
  50. }
  51. my $t = invmod($m, $L);
  52. $t > $hi && return;
  53. $t += $L * divceil($lo - $t, $L) if ($t < $lo);
  54. for (my $p = $t ; $p <= $hi ; $p += $L) {
  55. if (is_prime_power($p) and gcd($m, $p) == 1 and gcd($base, $p) == 1) {
  56. my $val = valuation($p - 1, 2);
  57. $val > $k_exp or next;
  58. powmod($base, ($p - 1) >> ($val - $k_exp), $p) == ($congr % $p) or next;
  59. my $v = $m * $p;
  60. $v >= $A or next;
  61. ($v - 1) % znorder($base, $p) == 0 or next;
  62. push(@list, $v) if !$seen{$v}++;
  63. }
  64. }
  65. return;
  66. }
  67. foreach my $p (@{primes($lo, $hi)}) {
  68. $base % $p == 0 and next;
  69. my $val = valuation($p - 1, 2);
  70. $val > $k_exp or next;
  71. powmod($base, ($p - 1) >> ($val - $k_exp), $p) == ($congr % $p) or next;
  72. my $z = znorder($base, $p);
  73. gcd($m, $z) == 1 or next;
  74. for (my ($q, $v) = ($p, $m * $p) ; $v <= $B ; ($q, $v) = ($q * $p, $v * $p)) {
  75. if ($q > $p) {
  76. powmod($base, $z, $q) == 1 or last;
  77. }
  78. __SUB__->($v, lcm($L, $z), $p + 1, $j - 1, $k_exp, $congr);
  79. }
  80. }
  81. };
  82. # Case where 2^d == 1 (mod p), where d is the odd part of p-1.
  83. $generator->(1, 1, 2, $k, 0, 1);
  84. # Cases where 2^(d * 2^v) == -1 (mod p), for some v >= 0.
  85. foreach my $v (0 .. logint($B, 2)) {
  86. $generator->(1, 1, 2, $k, $v, -1);
  87. }
  88. return sort { $a <=> $b } @list;
  89. }
  90. # Generate all the Fermat pseudoprimes to base 3 in range [1, 10^5]
  91. my $from = 1;
  92. my $upto = 1e5;
  93. my $base = 3;
  94. my @arr;
  95. foreach my $k (1 .. 100) {
  96. last if pn_primorial($k) > $upto;
  97. push @arr, strong_fermat_pseudoprimes_in_range($from, $upto, $k, $base);
  98. }
  99. say join(', ', sort { $a <=> $b } @arr);
  100. # Run some tests
  101. if (0) { # true to run some tests
  102. foreach my $k (1 .. 5) {
  103. say "Testing k = $k";
  104. my $lo = pn_primorial($k);
  105. my $hi = mulint($lo, 10000);
  106. my $omega_primes = omega_primes($k, $lo, $hi);
  107. foreach my $base (2 .. 100) {
  108. my @this = grep { is_strong_pseudoprime($_, $base) and !is_prime($_) } @$omega_primes;
  109. my @that = strong_fermat_pseudoprimes_in_range($lo, $hi, $k, $base);
  110. join(' ', @this) eq join(' ', @that)
  111. or die "Error for k = $k and base = $base with hi = $hi\n(@this) != (@that)";
  112. }
  113. }
  114. }
  115. __END__
  116. 121, 703, 1891, 3281, 8401, 8911, 10585, 12403, 16531, 18721, 19345, 23521, 31621, 44287, 47197, 55969, 63139, 74593, 79003, 82513, 87913, 88573, 97567