ntheory-sqrtmod.pl 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. #!/usr/bin/perl
  2. # a(n) is the least number such that the n-th prime is the least coprime quadratic nonresidue modulo a(n).
  3. # https://oeis.org/A306493
  4. #b(p, k) = gcd(p, k)==1&&!issquare(Mod(p, k))
  5. #a(n) = my(k=1); while(sum(i=1, n-1, b(prime(i), k))!=0 || !b(prime(n), k), k++); k
  6. use 5.014;
  7. use ntheory qw(:all);
  8. sub a {
  9. my ($n) = @_;
  10. my $p = nth_prime($n);
  11. my @primes = @{primes($p - 1)};
  12. for (my $k = 1 ; ; ++$k) {
  13. if (
  14. !((gcd($p, $k) == 1) && !defined(sqrtmod($p, $k)))
  15. || (
  16. vecany {
  17. (gcd($_, $k) == 1) && !defined(sqrtmod($_, $k))
  18. }
  19. @primes
  20. )
  21. ) {
  22. }
  23. else {
  24. return $k;
  25. }
  26. }
  27. }
  28. # a(21) = 86060762
  29. # a(22) = 326769242
  30. # a(23) = 131486759
  31. # Found by Jinyuan Wang:
  32. # a(24) = 84286438
  33. # a(25) = 937435558
  34. foreach my $n (26) {
  35. say "a($n) = ", a($n);
  36. }
  37. __END__
  38. a(17) = 6924458
  39. a(18) = 13620602
  40. 175244281
  41. a(1) = 3
  42. a(2) = 4
  43. a(3) = 6
  44. a(4) = 22
  45. a(5) = 118
  46. a(6) = 479
  47. a(7) = 262
  48. a(8) = 3622
  49. a(9) = 5878
  50. a(10) = 18191
  51. a(11) = 24022
  52. a(12) = 132982
  53. a(13) = 296278
  54. a(14) = 366791
  55. a(15) = 1289738
  56. a(16) = 4539478
  57. a(17) = 6924458
  58. a(18) = 13620602
  59. a(19) = 32290442
  60. a(20) = 175244281
  61. ^C
  62. perl v.pl 560.52s user 0.18s system 99% cpu 9:22.30 total
  63. % perl ntheory-sqrtmod.pl » /tmp «
  64. a(21) = 86060762
  65. perl ntheory-sqrtmod.pl 206.26s user 0.06s system 99% cpu 3:26.88 total
  66. % perl ntheory-sqrtmod.pl » /tmp «
  67. a(22) = 326769242
  68. perl ntheory-sqrtmod.pl 877.71s user 0.60s system 99% cpu 14:42.68 total
  69. % perl ntheory-sqrtmod.pl » /tmp «
  70. a(23) = 131486759
  71. perl ntheory-sqrtmod.pl 306.80s user 0.36s system 99% cpu 5:08.25 total
  72. [3, 4, 6, 22, 118, 479, 262, 3622, 5878, 18191, 24022, 132982, 296278, 366791, 1289738, 4539478, 6924458, 13620602, 32290442, 175244281]
  73. # Daniel "Trizen" Șuteu
  74. # Date: 30 October 2017
  75. # https://github.com/trizen
  76. # Find all the solutions to the congruence equation:
  77. # x^2 = a (mod n)
  78. # Defined for any values of `a` and `n` for which `kronecker(a, n) = 1`.
  79. # When `kronecker(a, n) != 1`, for example:
  80. #
  81. # a = 472
  82. # n = 972
  83. #
  84. # which represents:
  85. # x^2 = 472 (mod 972)
  86. #
  87. # this algorithm is not able to find a solution, although there exist four solutions:
  88. # x = {38, 448, 524, 934}
  89. # Code inspired from:
  90. # https://github.com/Magtheridon96/Square-Root-Modulo-N
  91. use 5.020;
  92. use warnings;
  93. use experimental qw(signatures);
  94. use List::Util qw(uniq);
  95. use Set::Product::XS qw(product);
  96. use ntheory qw(factor_exp is_prime chinese);
  97. use Math::AnyNum qw(:overload kronecker powmod invmod valuation ipow);
  98. sub tonelli_shanks ($n, $p) {
  99. my $q = $p - 1;
  100. my $s = valuation($q, 2);
  101. $s == 1
  102. and return powmod($n, ($p + 1) >> 2, $p);
  103. $q >>= $s;
  104. my $z = 1;
  105. for (my $i = 2 ; $i < $p ; ++$i) {
  106. if (kronecker($i, $p) == -1) {
  107. $z = $i;
  108. last;
  109. }
  110. }
  111. my $c = powmod($z, $q, $p);
  112. my $r = powmod($n, ($q + 1) >> 1, $p);
  113. my $t = powmod($n, $q, $p);
  114. while (($t - 1) % $p != 0) {
  115. my $k = 1;
  116. my $v = $t * $t % $p;
  117. for (my $i = 1 ; $i < $s ; ++$i) {
  118. if (($v - 1) % $p == 0) {
  119. $k = powmod($c, 1 << ($s - $i - 1), $p);
  120. $s = $i;
  121. last;
  122. }
  123. $v = $v * $v % $p;
  124. }
  125. $r = $r * $k % $p;
  126. $c = $k * $k % $p;
  127. $t = $t * $c % $p;
  128. }
  129. return $r;
  130. }
  131. sub sqrt_mod_n ($a, $n) {
  132. kronecker($a, $n) == 1 or return;
  133. $a %= $n;
  134. if (($n & ($n - 1)) == 0) { # n is a power of 2
  135. if ($a % 8 == 1) {
  136. my $k = valuation($n, 2);
  137. $k == 1 and return (1);
  138. $k == 2 and return (1, 3);
  139. $k == 3 and return (1, 3, 5, 7);
  140. if ($a == 1) {
  141. return (1, ($n >> 1) - 1, ($n >> 1) + 1, $n - 1);
  142. }
  143. my @roots;
  144. foreach my $s (sqrt_mod_n($a, $n >> 1)) {
  145. my $i = ((($s * $s - $a) >> ($k - 1)) % 2);
  146. my $r = ($s + ($i << ($k - 2)));
  147. push(@roots, $r, $n - $r);
  148. }
  149. return uniq(@roots);
  150. }
  151. return;
  152. }
  153. if (is_prime($n)) { # n is a prime
  154. my $r = tonelli_shanks($a, $n);
  155. return ($r, $n - $r);
  156. }
  157. my @pe = factor_exp($n); # factorize `n` into prime powers
  158. if (@pe == 1) { # `n` is an odd prime power
  159. my $p = Math::AnyNum->new($pe[0][0]);
  160. kronecker($a, $p) == 1 or return;
  161. my $r = tonelli_shanks($a, $p);
  162. my ($r1, $r2) = ($r, $n - $r);
  163. my $pk = $p;
  164. my $pi = $p * $p;
  165. for (1 .. $pe[0][1]-1) {
  166. my $x = $r1;
  167. my $y = invmod(2, $pk) * invmod($x, $pk);
  168. $r1 = ($pi + $x - $y * ($x * $x - $a + $pi)) % $pi;
  169. $r2 = ($pi - $r1);
  170. $pk *= $p;
  171. $pi *= $p;
  172. }
  173. return ($r1, $r2);
  174. }
  175. my @chinese;
  176. foreach my $f (@pe) {
  177. my $m = ipow($f->[0], $f->[1]);
  178. my @r = sqrt_mod_n($a, $m);
  179. push @chinese, [map { [$_, $m] } @r];
  180. }
  181. my @roots;
  182. product {
  183. push @roots, chinese(@_);
  184. } @chinese;
  185. return uniq(@roots);
  186. }
  187. use ntheory qw(:all);
  188. sub foo {
  189. my ($n) = @_;
  190. my $p = nth_prime($n);
  191. foreach my $k(2..1e5) {
  192. my @a = sqrt_mod($p, $k);
  193. }
  194. }
  195. my @a = sqrt_mod_n(19**2, 118);
  196. say "@a";
  197. #say foo(5);
  198. __END__
  199. my $p = nth_prime(5);
  200. my $k = 118;
  201. foreach my $n(1..$k) {
  202. my @a = sqrt_mod_n($n**2, $k);
  203. @a || next;
  204. say "a($n) = ", join(' ', map{sprintf("[%s, %s]", $_, gcd($_, $p))} @a);
  205. }
  206. __END__
  207. say join(' ', sqrt_mod_n(993, 2048)); #=> 369 1679 655 1393
  208. say join(' ', sqrt_mod_n(441, 920)); #=> 761 481 209 849 531 251 899 619 301 21 669 389 71 711 439 159
  209. say join(' ', sqrt_mod_n(841, 905)); #=> 391 876 29 514
  210. say join(' ', sqrt_mod_n(289, 992)); #=> 417 513 975 79 913 17 479 575
  211. # The algorithm works for arbitrary large integers
  212. say join(' ', sqrt_mod_n(13**18 * 5**7 - 1, 13**18 * 5**7)); #=> 633398078861605286438568 2308322911594648160422943 6477255756527023177780182 8152180589260066051764557