generate_psw_counter-example_from_prime_factors.pl 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. #!/usr/bin/perl
  2. # Daniel "Trizen" Șuteu
  3. # Date: 27 August 2022
  4. # https://github.com/trizen
  5. # Generate all the Lucas-Carmichael numbers with n prime factors in a given range [A,B], using a given list of prime factors. (not in sorted order)
  6. # See also:
  7. # https://en.wikipedia.org/wiki/Almost_prime
  8. # https://trizenx.blogspot.com/2020/08/pseudoprimes-construction-methods-and.html
  9. use 5.020;
  10. use warnings;
  11. use ntheory qw(:all);
  12. use experimental qw(signatures);
  13. use Math::GMPz;
  14. sub divceil ($x, $y) { # ceil(x/y)
  15. my $q = ($x / $y);
  16. ($q * $y == $x) ? $q : ($q + 1);
  17. }
  18. sub lucas_znorder ($P, $Q, $p) {
  19. my $D = $P*$P - 4*$Q;
  20. foreach my $d (divisors($p - kronecker($D, $p))) {
  21. if (lucasumod($P, $Q, $d, $p) == 0) {
  22. return $d;
  23. }
  24. }
  25. return undef;
  26. }
  27. sub carmichael_numbers_in_range ($A, $k, $primes, $callback) {
  28. $A = vecmax($A, pn_primorial($k));
  29. $A = Math::GMPz->new("$A");
  30. my $end = $#{$primes};
  31. sub ($m, $lambda1, $lambda2, $j, $k) {
  32. #say "$m -- $lambda1 -- $lambda2 -- $j -- $k";
  33. #my $y = rootint($B / $m, $k);
  34. if ($k == 1) {
  35. #say "$A -- $m";
  36. my $x = divceil($A, $m);
  37. if ($primes->[-1] < $x) {
  38. return;
  39. }
  40. foreach my $i ($j .. $end) {
  41. my $p = $primes->[$i];
  42. #last if ($p > $y);
  43. next if ($p < $x);
  44. my $t = $m * $p;
  45. if (($t - 1) % $lambda1 == 0 and ($t - 1) % znorder(2, $p) == 0) {
  46. say $t;
  47. if (($t + 1) % $lambda2 == 0 and ($t + 1) % lucas_znorder(1, -1, $p) == 0) {
  48. say ":: Found PSW counter-example: $t";
  49. $callback->($t);
  50. }
  51. }
  52. }
  53. return;
  54. }
  55. foreach my $i ($j .. $end) {
  56. my $p = $primes->[$i];
  57. #last if ($p > $y);
  58. my $L1 = lcm($lambda1, znorder(2, $p));
  59. gcd($L1, $m) == 1 or next;
  60. my $L2 = lcm($lambda2, lucas_znorder(1, -1, $p));
  61. gcd($L2, $m) == 1 or next;
  62. # gcd($m*$p, divisor_sum($m*$p)) == 1 or die "$m*$p: not Lucas-cyclic";
  63. #~ my $t = $m * $p;
  64. #~ my $u = divceil($A, $t);
  65. #my $v = $B / $t;
  66. #if ($u <= $v) {
  67. __SUB__->($m * $p, $L1, $L2, $i + 1, $k - 1);
  68. #}
  69. }
  70. }
  71. ->(Math::GMPz->new(1), 1, 1, 0, $k);
  72. }
  73. sub is_pomerance_prime ($p) {
  74. # p == 3 (mod 8) and (5/p) = -1
  75. # is_congruent(p, 3, 8) && (kronecker(5, p) == -1) &&
  76. # (p-1)/2 and (p+1)/4 are squarefree
  77. # is_squarefree((p-1)/2) && is_squarefree((p+1)/4) &&
  78. # all factors q of (p-1)/2 are q == 1 (mod 4)
  79. # factor((p-1)/2).all { |q|
  80. # is_congruent(q, 1, 4)
  81. # } &&
  82. # all factors q of (p+1)/4 are q == 3 (mod 4)
  83. # factor((p+1)/4).all {|q|
  84. # is_congruent(q, 3, 4)
  85. # }
  86. # p == 3 (mod 8)
  87. $p % 8 == 3 or return;
  88. # (5/p) = -1
  89. kronecker(5, $p) == -1 or return;
  90. # (p-1)/2 and (p+1)/4 are squarefree
  91. #(is_square_free(($p - 1) >> 1) and is_square_free(($p + 1) >> 2)) || return;
  92. # all prime factors q of (p-1)/2 are q == 1 (mod 4)
  93. #(vecall { $_ % 4 == 1 } factor(($p - 1) >> 1)) || return;
  94. # all prime factors q of (p+1)/4 are q == 3 (mod 4)
  95. #(vecall { $_ % 4 == 3 } factor(($p + 1) >> 2)) || return;
  96. return 1;
  97. }
  98. use IO::Handle;
  99. open my $fh, '>>', 'carmichael_with_many_factors_10.txt';
  100. $fh->autoflush(1);
  101. my @primes_620;
  102. while (<>) {
  103. next if /^#/;
  104. my $p = (split(' '))[-1];
  105. $p = Math::GMPz->new($p) if ($p > ~0);
  106. #is_pomerance_prime($p) || next;
  107. is_smooth($p+1, 8*logint($p, 2)) || next;
  108. is_smooth($p-1, 8*logint($p, 2)) || next;
  109. #$p > 2**64 or next;
  110. push @primes_620, $p;
  111. }
  112. # (p^2 - 1)/2 == 0 (mod 12)
  113. my @primes = grep { (($_ * $_ - 1) >> 1) % 12 == 0 } @primes_620;
  114. say "@primes";
  115. # All primes must be congruent to each other mod 12.
  116. my %groups;
  117. foreach my $p (@primes) {
  118. push @{$groups{$p % 12}}, $p;
  119. }
  120. my @groups = values %groups;
  121. foreach my $k ((5..scalar(@primes_620))) {
  122. next if ($k > scalar(@primes));
  123. $k % 2 == 1 or next;
  124. foreach my $group (@groups) {
  125. next if ($k > scalar(@$group));
  126. say "# k = $k -- primes: ", scalar(@$group);
  127. carmichael_numbers_in_range(Math::GMPz->new(10)**20, $k, $group, sub ($n) { say $n; say $fh $n; });
  128. }
  129. }