upper-bounds.pl 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. #!/usr/bin/perl
  2. # Erdos construction method for Carmichael numbers:
  3. # 1. Choose an even integer L with many prime factors.
  4. # 2. Let P be the set of primes d+1, where d|L and d+1 does not divide L.
  5. # 3. Find a subset S of P such that prod(S) == 1 (mod L). Then prod(S) is a Carmichael number.
  6. # Alternatively:
  7. # 3. Find a subset S of P such that prod(S) == prod(P) (mod L). Then prod(P) / prod(S) is a Carmichael number.
  8. use 5.020;
  9. use warnings;
  10. use ntheory qw(:all);
  11. use List::Util qw(shuffle);
  12. use experimental qw(signatures);
  13. # Modular product of a list of integers
  14. sub vecprodmod ($arr, $mod) {
  15. #~ if ($mod > ~0) {
  16. #~ my $prod = Math::GMPz->new(1);
  17. #~ foreach my $k(@$arr) {
  18. #~ $prod = ($prod * $k) % $mod;
  19. #~ }
  20. #~ return $prod;
  21. #~ }
  22. if ($mod < ~0) {
  23. my $prod = 1;
  24. foreach my $k(@$arr) {
  25. $prod = mulmod($prod, $k, $mod);
  26. }
  27. return $prod;
  28. }
  29. my $prod = 1;
  30. foreach my $k (@$arr) {
  31. $prod = Math::Prime::Util::GMP::mulmod($prod, $k, $mod);
  32. }
  33. #Math::GMPz->new($prod);
  34. Math::GMPz::Rmpz_init_set_str($prod, 10);
  35. }
  36. # Primes p such that p-1 divides L and p does not divide L
  37. sub lambda_primes ($L) {
  38. my @divisors = divisors($L);
  39. if (ref $L) {
  40. @divisors = map{Math::GMPz->new($_)} @divisors;
  41. }
  42. grep { ($L % $_) != 0 } grep { $_ > 2 and is_prime($_) } map { $_ - 1 } @divisors;
  43. }
  44. my @prefix = factor(2019);
  45. my $prefix_prod = Math::GMPz->new(vecprod(@prefix));
  46. sub isok ($prefix_prod, $p) {
  47. (($prefix_prod % $p) == 0)
  48. ? 1
  49. : Math::Prime::Util::GMP::gcd(
  50. # Note: sigma(n) = DedekindPsi(n), when n is squarefree
  51. Math::Prime::Util::GMP::sigma(Math::Prime::Util::GMP::mulint($prefix_prod, $p)),
  52. Math::Prime::Util::GMP::mulint($prefix_prod, $p)
  53. ) eq '1';
  54. }
  55. sub method_1 ($L) {
  56. (vecall { ($L % ($_+1)) == 0 } @prefix) or return;
  57. my @P = lambda_primes($L);
  58. @P = grep {
  59. isok($prefix_prod, $_)
  60. } @P;
  61. if (@prefix) {
  62. #~ $prefix_prod = gcd($prefix_prod, vecprod(@P));
  63. #~ if ($prefix_prod > ~0) {
  64. #~ $prefix_prod = Math::GMPz->new($prefix_prod);
  65. #~ }
  66. vecprodmod(\@P, $prefix_prod) == 0 or return;
  67. }
  68. @P = grep { gcd($prefix_prod, $_) == 1 } @P;
  69. my $n = scalar(@P);
  70. my @orig = @P;
  71. my $max = 1e5;
  72. my $max_k = @P>>1;
  73. my $L_rem = invmod(-$prefix_prod, $L);
  74. foreach my $k (1 .. @P>>1) {
  75. #next if (binomial($n, $k) > 1e6);
  76. next if ($k > $max_k);
  77. @P = @orig;
  78. my $count = 0;
  79. forcomb {
  80. if (vecprodmod([@P[@_]], $L) == $L_rem) {
  81. say vecprod(@P[@_], $prefix_prod);
  82. }
  83. lastfor if (++$count > $max);
  84. } $n, $k;
  85. next if (binomial($n, $k) < $max);
  86. @P = reverse(@P);
  87. $count = 0;
  88. forcomb {
  89. if (vecprodmod([@P[@_]], $L) == $L_rem) {
  90. say vecprod(@P[@_], $prefix_prod);
  91. }
  92. lastfor if (++$count > $max);
  93. } $n, $k;
  94. @P = shuffle(@P);
  95. $count = 0;
  96. forcomb {
  97. if (vecprodmod([@P[@_]], $L) == $L_rem) {
  98. say vecprod(@P[@_], $prefix_prod);
  99. }
  100. lastfor if (++$count > $max);
  101. } $n, $k;
  102. }
  103. my $B = vecprodmod([@P, $prefix_prod], $L);
  104. my $T = Math::GMPz->new(Math::Prime::Util::GMP::vecprod(@P));
  105. foreach my $k (1 .. @P>>1) {
  106. #next if (binomial($n, $k) > 1e6);
  107. last if ($k > $max_k);
  108. @P = @orig;
  109. my $count = 0;
  110. forcomb {
  111. if (vecprodmod([@P[@_]], $L) == $L-$B) {
  112. my $S = Math::GMPz->new(Math::Prime::Util::GMP::vecprod(@P[@_]));
  113. say vecprod($prefix_prod, $T / $S) if ($T != $S);
  114. }
  115. lastfor if (++$count > $max);
  116. } $n, $k;
  117. next if (binomial($n, $k) < $max);
  118. @P = reverse(@P);
  119. $count = 0;
  120. forcomb {
  121. if (vecprodmod([@P[@_]], $L) == $L-$B) {
  122. my $S = Math::GMPz->new(Math::Prime::Util::GMP::vecprod(@P[@_]));
  123. say vecprod($prefix_prod, $T / $S) if ($T != $S);
  124. }
  125. lastfor if (++$count > $max);
  126. } $n, $k;
  127. @P = shuffle(@P);
  128. $count = 0;
  129. forcomb {
  130. if (vecprodmod([@P[@_]], $L) == $L-$B) {
  131. my $S = Math::GMPz->new(Math::Prime::Util::GMP::vecprod(@P[@_]));
  132. say vecprod($prefix_prod, $T / $S) if ($T != $S);
  133. }
  134. lastfor if (++$count > $max);
  135. } $n, $k;
  136. }
  137. }
  138. use Math::GMPz;
  139. my %seen;
  140. foreach my $n(
  141. # 579 633 831 939 993
  142. # 471 489 579 633 831 849 939 993 997
  143. # 1011 1191 1263 1299 1371 1569 1623 1641 1731 1803 1839 1983
  144. #"6129808992374767344930533023281723644735"
  145. #"949345608369235381501270412185"
  146. #"4433176618644502422330768777221231465169661313213350633114707186944405"
  147. #"127466063752826626324435698484540540558295"
  148. #"106653733772365052643166285665180547235"
  149. #"119024709829745330011481322067838705"
  150. #"46959971109945694430499962276105606663529719792044602335"
  151. #"1294274990131688023696493933140236381455"
  152. #"8799790266329193440224926938725171796106819393467506141512515"
  153. #"38030534297754067685"
  154. #"23377842423989657076602266569873101952013003504685"
  155. #"772179967364902960050618202156261415"
  156. #"194602156386224629422552469787952346372498846530682289766253612669"
  157. #"36132677625950010937561463013714222491519325355"
  158. #"3542836771466567999860458329480884827088068308135"
  159. #"8914187539791472954947850988928599029456208045582583721352177695"
  160. #"42234031537459802832396752081596353633218790964930635162242005"
  161. #"85503937166438882248863699301498021031999539215858124013108319656635"
  162. #"20864706932178785769917898828869523357817136902558894449789452472928979874601533310644145"
  163. #"1902297977801930015025944798668484422031845"
  164. #"66266715510015661319570176140423050370668804770560256135"
  165. #"234654549136285265933061171738168939714130942284339894585402033882355"
  166. #"3397999184681623808612078197077715654790193013496329658488604896631594512825115"
  167. #"7091624298430548888573407197301192571547132819166839997265718419270137748266015005"
  168. #"996237336944266637684131177655932042052540541708236568160506508712562155"
  169. #"584429907085032853259228054432854994454835411128998925961399569005"
  170. #"672678823054872814101371490652216098617515558209477763781570903924755"
  171. #"112802298561224695246100457357784567481908143295"
  172. #"2019"
  173. # Couldn't find values for the following multiples: 2019 2199 2433 2469 2559 2631 2721 2811 2973 2991
  174. 2991
  175. #"123822501164900872937541665595811819409339345"
  176. #"12842840413387450039118922094150507198373150341080498833706488673768451737215"
  177. #"1339507056334249374254866105459537342347229669520638067448048963935"
  178. #"178911053337017413417238694465010998042904991254259124809409505"
  179. #"24310698014160010310749313013068622919013802712267027259077787135"
  180. #"31425289217027351639987581502487222669785151702187074325045"
  181. #"1012591408428327888883952080728349448745451794025524955777432246705535"
  182. #"3729600465492886035713190910355240492580651955624733123395115"
  183. #"1552617717262956290883155669454007199664570320354223395"
  184. #"4909509710014502243130071334918614879080498219285"
  185. ) {
  186. say "# Searching upper-bounds for multiple k = $n";
  187. @prefix = factor($n);
  188. #@prefix = grep { gcd($n, subint($_,1)) == 1 } @prefix;
  189. #@prefix = grep { gcd($n, addint($_,1)) == 1 } @prefix;
  190. #$#prefix = 20;
  191. $prefix_prod = vecprod(@prefix);
  192. if ($prefix_prod > ~0) {
  193. $prefix_prod = Math::GMPz->new($prefix_prod);
  194. }
  195. foreach my $k(1..1e6) {
  196. my $L = lcm(map { addint($_, 1) } factor(vecprod($k, $prefix_prod)));
  197. if ($L > ~0) {
  198. $L = Math::GMPz->new("$L");
  199. }
  200. $L % 2 == 0 or next;
  201. next if $seen{$L}++;
  202. method_1($L);
  203. }
  204. }