carmichael_almost_lucas-carmichael_from_prime_factors.pl 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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 carmichael_numbers_in_range ($A, $k, $primes, $callback) {
  19. if (vecprod(@$primes) < $A) {
  20. say "# Too few and too small primes...";
  21. return;
  22. }
  23. $A = vecmax($A, pn_primorial($k));
  24. $A = Math::GMPz->new("$A");
  25. my $end = $#{$primes};
  26. sub ($m, $lambda1, $lambda2, $j, $k) {
  27. #my $y = rootint($B / $m, $k);
  28. if ($k == 1) {
  29. my $x = divceil($A, $m);
  30. if ($primes->[-1] < $x) {
  31. return;
  32. }
  33. foreach my $i ($j .. $end) {
  34. my $p = $primes->[$i];
  35. #last if ($p > $y);
  36. next if ($p < $x);
  37. my $t = $m * $p;
  38. if (
  39. (($t-1) % $lambda1 == 0 and ($t-1) % ($p-1) == 0)
  40. or (($t+1) % $lambda2 == 0 and ($t+1) % ($p+1) == 0)
  41. ) {
  42. $callback->($t);
  43. }
  44. }
  45. return;
  46. }
  47. foreach my $i ($j .. $end) {
  48. my $p = $primes->[$i];
  49. #last if ($p > $y);
  50. my $L1 = lcm($lambda1, $p-1);
  51. gcd($L1, $m) == 1 or next;
  52. my $L2 = lcm($lambda2, $p+1);
  53. gcd($L2, $m) == 1 or next;
  54. # gcd($m*$p, divisor_sum($m*$p)) == 1 or die "$m*$p: not Lucas-cyclic";
  55. #~ my $t = $m * $p;
  56. #~ my $u = divceil($A, $t);
  57. #my $v = $B / $t;
  58. #if ($u <= $v) {
  59. __SUB__->($m*$p, $L1, $L2, $i + 1, $k - 1);
  60. #}
  61. }
  62. }
  63. ->(Math::GMPz->new(1), 1, 1, 0, $k);
  64. }
  65. sub is_pomerance_prime ($p) {
  66. # p == 3 (mod 8) and (5/p) = -1
  67. # is_congruent(p, 3, 8) && (kronecker(5, p) == -1) &&
  68. # (p-1)/2 and (p+1)/4 are squarefree
  69. # is_squarefree((p-1)/2) && is_squarefree((p+1)/4) &&
  70. # all factors q of (p-1)/2 are q == 1 (mod 4)
  71. # factor((p-1)/2).all { |q|
  72. # is_congruent(q, 1, 4)
  73. # } &&
  74. # all factors q of (p+1)/4 are q == 3 (mod 4)
  75. # factor((p+1)/4).all {|q|
  76. # is_congruent(q, 3, 4)
  77. # }
  78. # p == 3 (mod 8)
  79. $p%8 == 3 or return;
  80. # (5/p) = -1
  81. #kronecker(5, $p) == -1 or return;
  82. # (p-1)/2 and (p+1)/4 are squarefree
  83. (is_square_free(($p-1)>>1) and is_square_free(($p+1)>>2)) || return;
  84. # all prime factors q of (p-1)/2 are q == 1 (mod 4)
  85. (vecall { $_%4 == 1 } factor(($p-1)>>1)) || return;
  86. # all prime factors q of (p+1)/4 are q == 3 (mod 4)
  87. (vecall { $_%4 == 3 } factor(($p+1)>>2)) || return;
  88. return 1;
  89. }
  90. use IO::Handle;
  91. open my $fh, '>>', 'carmichael-lucas-carmichael.txt';
  92. $fh->autoflush(1);
  93. #foreach my $lambda (2..1e6) {
  94. while (<>) {
  95. chomp(my $lambda = $_);
  96. #$lambda >= 1e10 or next;
  97. #$lambda > 2205403200 or next;
  98. #$lambda > 4532871872400564 or next;
  99. say "# Generating: $lambda";
  100. my @D = divisors($lambda);
  101. my @primes_pp1 = grep { $_ >= 5 and $lambda % $_ != 0 and is_prime($_) } map { addint($_, 1) } @D;
  102. my @primes_pm1 = grep { $_ >= 5 and $lambda % $_ != 0 and is_prime($_) } map { subint($_, 1) } @D;
  103. my %table;
  104. @table{@primes_pm1} = ();
  105. # (p^2 - 1)/2 == 0 (mod 12)
  106. my @primes = grep { (($_*$_ - 1)>>1) % 12 == 0 } grep { exists($table{$_}) } @primes_pp1;
  107. #my @primes = grep { exists($table{$_}) } @primes_pp1;
  108. # All primes must be congruent to each other mod 12.
  109. my %groups;
  110. foreach my $p(@primes) {
  111. push @{$groups{$p % 12}}, $p;
  112. }
  113. my @groups = values %groups;
  114. foreach my $k (5..320) {
  115. last if ($k > scalar(@primes));
  116. # $k % 2 == 1 or next;
  117. # if (binomial(scalar(@primes), $k) < 1e6) {
  118. foreach my $group (@groups) {
  119. next if ($k > scalar(@$group));
  120. say "# k = $k -- primes: ", scalar(@$group);
  121. carmichael_numbers_in_range(Math::GMPz->new(~0), $k, $group, sub ($n) { say $n; say $fh $n; });
  122. }
  123. # }
  124. }
  125. }