sum_of_two_squares_solutions_tonelli-shanks.sf 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. #!/usr/bin/ruby
  2. # Daniel "Trizen" Șuteu
  3. # Date: 26 October 2017
  4. # https://github.com/trizen
  5. # A recursive algorithm for finding all the non-negative integer solutions to the equation:
  6. # a^2 + b^2 = n
  7. # for any given positive integer `n` for which such a solution exists.
  8. # Example:
  9. # 99025 = 41^2 + 312^2 = 48^2 + 311^2 = 95^2 + 300^2 = 104^2 + 297^2 = 183^2 + 256^2 = 220^2 + 225^2
  10. # Blog post:
  11. # https://trizenx.blogspot.com/2017/10/representing-integers-as-sum-of-two.html
  12. # This algorithm is efficient when the factorization of `n` can be computed.
  13. # See also:
  14. # https://oeis.org/A001481
  15. func tonelli(n, p) {
  16. var q = p-1
  17. var s = valuation(q, 2)
  18. s == 1 && return powmod(n, (p + 1) >> 2, p)
  19. q >>= s
  20. var c = powmod(2 ..^ p -> first {|z| legendre(z, p) == -1 }, q, p)
  21. var r = powmod(n, (q + 1) >> 1, p)
  22. var t = powmod(n, q, p)
  23. while (!p.divides(t - 1)) {
  24. var b = 1
  25. var t2 = (t*t % p)
  26. for i in (1 ..^ s) {
  27. if (p.divides(t2 - 1)) {
  28. b = powmod(c, 1 << (s - i - 1), p)
  29. s = i
  30. break
  31. }
  32. t2 = (t2*t2 % p)
  33. }
  34. r = (r*b % p)
  35. c = (b*b % p)
  36. t = (t*c % p)
  37. }
  38. return r
  39. }
  40. func sqrt_mod_n(a, n) is cached {
  41. a = (a % n)
  42. kronecker(a, n) == 1 || return []
  43. if ((n & (n - 1)) == 0) { # n is a power of 2
  44. if (a % 8 == 1) {
  45. var k = n.valuation(2)
  46. k == 1 && return [1]
  47. k == 2 && return [1, 3]
  48. k == 3 && return [1, 3, 5, 7]
  49. if (a == 1) {
  50. return [1, (n>>1) - 1, (n>>1) + 1, n - 1]
  51. }
  52. return gather {
  53. for s in (sqrt_mod_n(a, n >> 1)) {
  54. var i = (((s*s - a) >> (k - 1)) % 2)
  55. var r = (s + (i << (k - 2)))
  56. take(r, n - r)
  57. }
  58. }.uniq
  59. }
  60. return []
  61. }
  62. if (n.is_prime) { # n is a prime
  63. return gather {
  64. var r = tonelli(a, n)
  65. take(r, n - r)
  66. }
  67. }
  68. var pe = n.factor_exp # factorize `n` into prime powers
  69. if (pe.len == 1) { # `n` is an odd prime power
  70. var p = pe[0][0]
  71. var k = pe[0][1]
  72. kronecker(a, p) == 1 || return []
  73. var (r1, r2) = with( tonelli(a, p) ) { |r|
  74. (r, n - r)
  75. }
  76. var pk = p
  77. var pi = p*p
  78. (k-1).times {
  79. var x = r1
  80. var y = (invmod(2, pk) * invmod(x, pk))
  81. r1 = ((pi + x - y*(x*x - a + pi)) % pi)
  82. r2 = (pi - r1)
  83. pk *= p
  84. pi *= p
  85. }
  86. return [r1, r2]
  87. }
  88. var solutions = []
  89. for p,e in (pe) {
  90. var m = p**e
  91. var r = sqrt_mod_n(a, m)
  92. solutions << r.map {|r0| [r0, m] }
  93. }
  94. gather {
  95. solutions.cartesian {|*a|
  96. take(Math.chinese(a...))
  97. }
  98. }.uniq
  99. }
  100. func sum_of_two_squares_solutions(n) is cached {
  101. n < 0 && return []
  102. n == 0 && return [[0, 0]]
  103. var prod1 = 1
  104. var prod2 = 1
  105. var prime_powers = []
  106. for p,e in (n.factor_exp) {
  107. if (p % 4 == 3) { # p = 3 (mod 4)
  108. e.is_even || return [] # power must be even
  109. prod2 *= p**(e >> 1)
  110. }
  111. elsif (p == 2) { # p = 2
  112. if (e.is_even) { # power is even
  113. prod2 *= p**(e >> 1)
  114. }
  115. else { # power is odd
  116. prod1 *= p
  117. prod2 *= p**((e - 1) >> 1)
  118. prime_powers << [p, 1]
  119. }
  120. }
  121. else { # p = 1 (mod 4)
  122. prod1 *= p**e
  123. prime_powers << [p, e]
  124. }
  125. }
  126. prod1 == 1 && return [[prod2, 0]]
  127. prod1 == 2 && return [[prod2, prod2]]
  128. var solutions = []
  129. for r in (sqrt_mod_n(-1, prod1)) {
  130. var s = r
  131. var q = prod1
  132. while (s*s > prod1) {
  133. (s, q) = (q % s, s)
  134. }
  135. solutions << [prod2 * s, prod2 * (q % s)]
  136. }
  137. for p,e in (prime_powers) {
  138. for (var i = e%2; i < e; i += 2) {
  139. var sq = p**((e - i) >> 1)
  140. var pp = p**(e - i)
  141. solutions += __FUNC__(prod1 / pp).map { |pair|
  142. pair.map {|r| sq * prod2 * r }
  143. }
  144. }
  145. }
  146. solutions.map {|pair| pair.sort } \
  147. .uniq_by {|pair| pair[0] } \
  148. .sort_by {|pair| pair[0] }
  149. }
  150. 50.times {
  151. var n = 1e10.irand
  152. var solutions = sum_of_two_squares_solutions(n) || next
  153. say %Q(#{n} = #{solutions.map {|a| "#{a[0]}^2 + #{a[1]}^2" }.join(' = ') })
  154. }
  155. assert_eq(sum_of_two_squares_solutions(2025), [[0, 45], [27, 36]])
  156. assert_eq(sum_of_two_squares_solutions(164025), [[0, 405], [243, 324]])
  157. assert_eq(sum_of_two_squares_solutions(99025), [[41, 312], [48, 311], [95, 300], [104, 297], [183, 256], [220, 225]])
  158. assert_eq(
  159. -10 .. 160 -> grep { sum_of_two_squares_solutions(_).len > 0 },
  160. %n[0, 1, 2, 4, 5, 8, 9, 10, 13, 16, 17, 18, 20, 25, 26, 29, 32, 34, 36, 37, 40, 41, 45, 49, 50, 52, 53, 58, 61, 64, 65, 68, 72, 73, 74, 80, 81, 82, 85, 89, 90, 97, 98, 100, 101, 104, 106, 109, 113, 116, 117, 121, 122, 125, 128, 130, 136, 137, 144, 145, 146, 148, 149, 153, 157, 160]
  161. )
  162. assert_eq(
  163. sum_of_two_squares_solutions(11392163240756069707031250),
  164. [[39309472125, 3374998963875], [216763660575, 3368260197225], [477329304375, 3341305130625], [729359177085, 3295481517405], [735019741071, 3294223614297], [907262616645, 3251005657515], [982736803125, 3228992353125], [1151205969375, 3172835964375], [1224793301193, 3145162095999], [1393801568775, 3074000720175], [1622919634875, 2959441687125], [1847545189875, 2824666354125], [1993551800625, 2723584854375], [2056446956025, 2676413487825], [2194367046795, 2564549961435], [2198769707673, 2560776252111], [2386646521875, 2386646521875]]
  165. )