strong_fermat_pseudoprimes_in_range_mpz.pl 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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.036;
  20. use Math::GMPz;
  21. use ntheory qw(:all);
  22. sub divceil ($x, $y) { # ceil(x/y)
  23. (($x % $y == 0) ? 0 : 1) + divint($x, $y);
  24. }
  25. sub strong_fermat_pseudoprimes_in_range ($A, $B, $k, $base) {
  26. $A = vecmax($A, pn_primorial($k));
  27. $A = Math::GMPz->new("$A");
  28. $B = Math::GMPz->new("$B");
  29. my $u = Math::GMPz::Rmpz_init();
  30. my $v = Math::GMPz::Rmpz_init();
  31. my %seen;
  32. my @list;
  33. my $generator = sub ($m, $L, $lo, $j, $k_exp, $congr) {
  34. Math::GMPz::Rmpz_tdiv_q($u, $B, $m);
  35. Math::GMPz::Rmpz_root($u, $u, $j);
  36. my $hi = Math::GMPz::Rmpz_get_ui($u);
  37. if ($lo > $hi) {
  38. return;
  39. }
  40. if ($j == 1) {
  41. Math::GMPz::Rmpz_invert($v, $m, $L);
  42. if (Math::GMPz::Rmpz_cmp_ui($v, $hi) > 0) {
  43. return;
  44. }
  45. if (Math::GMPz::Rmpz_fits_ulong_p($L)) {
  46. $L = Math::GMPz::Rmpz_get_ui($L);
  47. }
  48. my $t = Math::GMPz::Rmpz_get_ui($v);
  49. $t > $hi && return;
  50. $t += $L * divceil($lo - $t, $L) if ($t < $lo);
  51. for (my $p = $t ; $p <= $hi ; $p += $L) {
  52. if (is_prime_power($p) and Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $m, $p) == 1 and gcd($base, $p) == 1) {
  53. my $val = valuation($p - 1, 2);
  54. $val > $k_exp or next;
  55. powmod($base, ($p - 1) >> ($val - $k_exp), $p) == ($congr % $p) or next;
  56. Math::GMPz::Rmpz_mul_ui($v, $m, $p);
  57. if ($k == 1 and is_prime($p) and Math::GMPz::Rmpz_cmp_ui($m, 1) == 0) {
  58. ## ok
  59. }
  60. elsif (Math::GMPz::Rmpz_cmp($v, $A) >= 0) {
  61. Math::GMPz::Rmpz_sub_ui($u, $v, 1);
  62. if (Math::GMPz::Rmpz_divisible_ui_p($u, znorder($base, $p))) {
  63. push(@list, Math::GMPz::Rmpz_init_set($v)) if !$seen{Math::GMPz::Rmpz_get_str($v, 10)}++;
  64. }
  65. }
  66. }
  67. }
  68. return;
  69. }
  70. my $u = Math::GMPz::Rmpz_init();
  71. my $v = Math::GMPz::Rmpz_init();
  72. my $lcm = Math::GMPz::Rmpz_init();
  73. foreach my $p (@{primes($lo, $hi)}) {
  74. $base % $p == 0 and next;
  75. my $val = valuation($p - 1, 2);
  76. $val > $k_exp or next;
  77. powmod($base, ($p - 1) >> ($val - $k_exp), $p) == ($congr % $p) or next;
  78. my $z = znorder($base, $p);
  79. Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $m, $z) == 1 or next;
  80. Math::GMPz::Rmpz_lcm_ui($lcm, $L, $z);
  81. Math::GMPz::Rmpz_set_ui($u, $p);
  82. for (Math::GMPz::Rmpz_mul_ui($v, $m, $p) ; Math::GMPz::Rmpz_cmp($v, $B) <= 0 ; Math::GMPz::Rmpz_mul_ui($v, $v, $p)) {
  83. __SUB__->($v, $lcm, $p + 1, $j - 1, $k_exp, $congr);
  84. Math::GMPz::Rmpz_mul_ui($u, $u, $p);
  85. powmod($base, $z, $u) == 1 or last;
  86. }
  87. }
  88. };
  89. # Case where 2^d == 1 (mod p), where d is the odd part of p-1.
  90. $generator->(Math::GMPz->new(1), Math::GMPz->new(1), 2, $k, 0, 1);
  91. # Cases where 2^(d * 2^v) == -1 (mod p), for some v >= 0.
  92. foreach my $v (0 .. logint($B, 2)) {
  93. $generator->(Math::GMPz->new(1), Math::GMPz->new(1), 2, $k, $v, -1);
  94. }
  95. return sort { $a <=> $b } @list;
  96. }
  97. # Generate all the strong Fermat pseudoprimes to base 3 in range [1, 10^5]
  98. my $from = 1;
  99. my $upto = 1e5;
  100. my $base = 3;
  101. my @arr;
  102. foreach my $k (1 .. 100) {
  103. last if pn_primorial($k) > $upto;
  104. push @arr, strong_fermat_pseudoprimes_in_range($from, $upto, $k, $base);
  105. }
  106. say join(', ', sort { $a <=> $b } @arr);
  107. # Run some tests
  108. if (0) { # true to run some tests
  109. foreach my $k (1 .. 5) {
  110. say "Testing k = $k";
  111. my $lo = pn_primorial($k) * 4;
  112. my $hi = mulint($lo, 1000);
  113. my $omega_primes = omega_primes($k, $lo, $hi);
  114. foreach my $base (2 .. 100) {
  115. my @this = grep { is_strong_pseudoprime($_, $base) and !is_prime($_) } @$omega_primes;
  116. my @that = strong_fermat_pseudoprimes_in_range($lo, $hi, $k, $base);
  117. join(' ', @this) eq join(' ', @that)
  118. or die "Error for k = $k and base = $base with hi = $hi\n(@this) != (@that)";
  119. }
  120. }
  121. }
  122. __END__
  123. 121, 703, 1891, 3281, 8401, 8911, 10585, 12403, 16531, 18721, 19345, 23521, 31621, 44287, 47197, 55969, 63139, 74593, 79003, 82513, 87913, 88573, 97567