rcas 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CITATION.cff +17 -0
- data/DESIGN.md +783 -0
- data/LICENSE +21 -0
- data/MANUAL.md +6265 -0
- data/README.md +267 -0
- data/bin/rcas +9 -0
- data/bin/rcas-app +9 -0
- data/bin/rcas-chat +9 -0
- data/lib/rcas/algebraic.rb +481 -0
- data/lib/rcas/analysis.rb +966 -0
- data/lib/rcas/app/launcher.rb +203 -0
- data/lib/rcas/app/public/app.css +402 -0
- data/lib/rcas/app/public/app.js +449 -0
- data/lib/rcas/app/public/index.html +46 -0
- data/lib/rcas/app/server.rb +220 -0
- data/lib/rcas/app/window.rb +94 -0
- data/lib/rcas/app/worksheet.rb +290 -0
- data/lib/rcas/app.rb +168 -0
- data/lib/rcas/background.rb +758 -0
- data/lib/rcas/chat/assistant.rb +199 -0
- data/lib/rcas/chat/picker.rb +164 -0
- data/lib/rcas/chat/repl.rb +583 -0
- data/lib/rcas/chat/session.rb +137 -0
- data/lib/rcas/chat/settings.rb +71 -0
- data/lib/rcas/chat/style.rb +30 -0
- data/lib/rcas/chat/tool.rb +53 -0
- data/lib/rcas/chat/ui.rb +316 -0
- data/lib/rcas/chat/usage.rb +62 -0
- data/lib/rcas/chat/workspace.rb +132 -0
- data/lib/rcas/chat.rb +54 -0
- data/lib/rcas/coefficients.rb +170 -0
- data/lib/rcas/combinatorics.rb +274 -0
- data/lib/rcas/complex_parts.rb +160 -0
- data/lib/rcas/constants.rb +129 -0
- data/lib/rcas/core_ext.rb +35 -0
- data/lib/rcas/decide.rb +501 -0
- data/lib/rcas/decompositions.rb +241 -0
- data/lib/rcas/differentiate.rb +144 -0
- data/lib/rcas/discussion.rb +558 -0
- data/lib/rcas/distributions.rb +980 -0
- data/lib/rcas/dixon.rb +95 -0
- data/lib/rcas/docs.rb +321 -0
- data/lib/rcas/domains.rb +728 -0
- data/lib/rcas/expand.rb +174 -0
- data/lib/rcas/expression.rb +613 -0
- data/lib/rcas/factor.rb +605 -0
- data/lib/rcas/finite_field.rb +577 -0
- data/lib/rcas/fourier.rb +118 -0
- data/lib/rcas/fps.rb +678 -0
- data/lib/rcas/fraction.rb +126 -0
- data/lib/rcas/functions.rb +1136 -0
- data/lib/rcas/gcd.rb +112 -0
- data/lib/rcas/geometry.rb +266 -0
- data/lib/rcas/groebner.rb +162 -0
- data/lib/rcas/hold.rb +277 -0
- data/lib/rcas/hypothesis.rb +364 -0
- data/lib/rcas/inequalities.rb +689 -0
- data/lib/rcas/integral_functions.rb +260 -0
- data/lib/rcas/integrate.rb +1589 -0
- data/lib/rcas/integrate_substitutions.rb +434 -0
- data/lib/rcas/interpolate.rb +40 -0
- data/lib/rcas/irb.rb +146 -0
- data/lib/rcas/laplace.rb +159 -0
- data/lib/rcas/latex.rb +556 -0
- data/lib/rcas/lattice.rb +172 -0
- data/lib/rcas/linear_algebra.rb +117 -0
- data/lib/rcas/linear_program.rb +416 -0
- data/lib/rcas/lint.rb +79 -0
- data/lib/rcas/matrix.rb +531 -0
- data/lib/rcas/matrix_multiply.rb +202 -0
- data/lib/rcas/multimodular.rb +286 -0
- data/lib/rcas/named_polynomials.rb +274 -0
- data/lib/rcas/number_theory.rb +443 -0
- data/lib/rcas/numerics.rb +825 -0
- data/lib/rcas/ode.rb +488 -0
- data/lib/rcas/openmath/objects.rb +364 -0
- data/lib/rcas/openmath/phrasebook.rb +551 -0
- data/lib/rcas/openmath/popcorn.rb +518 -0
- data/lib/rcas/openmath/xml.rb +309 -0
- data/lib/rcas/openmath.rb +49 -0
- data/lib/rcas/petkovsek.rb +165 -0
- data/lib/rcas/piecewise.rb +488 -0
- data/lib/rcas/plot.rb +763 -0
- data/lib/rcas/plot3d.rb +419 -0
- data/lib/rcas/poly_matrix.rb +318 -0
- data/lib/rcas/poly_recurrence.rb +117 -0
- data/lib/rcas/polynomial.rb +466 -0
- data/lib/rcas/precision.rb +925 -0
- data/lib/rcas/printer.rb +150 -0
- data/lib/rcas/product.rb +155 -0
- data/lib/rcas/q_difference.rb +296 -0
- data/lib/rcas/q_functions.rb +158 -0
- data/lib/rcas/q_summation.rb +308 -0
- data/lib/rcas/q_zeilberger.rb +199 -0
- data/lib/rcas/random.rb +506 -0
- data/lib/rcas/rational_function.rb +186 -0
- data/lib/rcas/recurrence.rb +323 -0
- data/lib/rcas/render.rb +431 -0
- data/lib/rcas/results.rb +192 -0
- data/lib/rcas/scalar.rb +219 -0
- data/lib/rcas/series.rb +726 -0
- data/lib/rcas/simplify.rb +649 -0
- data/lib/rcas/solve.rb +2002 -0
- data/lib/rcas/special.rb +163 -0
- data/lib/rcas/statistics.rb +175 -0
- data/lib/rcas/steps.rb +835 -0
- data/lib/rcas/summation.rb +532 -0
- data/lib/rcas/trig.rb +264 -0
- data/lib/rcas/van_hoeij.rb +241 -0
- data/lib/rcas/vector.rb +175 -0
- data/lib/rcas/vector_calculus.rb +411 -0
- data/lib/rcas/version.rb +5 -0
- data/lib/rcas/zeilberger.rb +358 -0
- data/lib/rcas.rb +91 -0
- data/package.json +8 -0
- metadata +206 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module RCAS
|
|
5
|
+
# Integer arithmetic beyond what Ruby ships with: prime factorization,
|
|
6
|
+
# primality, divisors, Euler's totient, modular inverses and the Chinese
|
|
7
|
+
# remainder theorem.
|
|
8
|
+
#
|
|
9
|
+
# factor(360) # => 2**3*3**2*5
|
|
10
|
+
# isprime(2**61 - 1) # => true
|
|
11
|
+
# divisors(12) # => [1, 2, 3, 4, 6, 12]
|
|
12
|
+
# chrem([2, 3], [3, 5]) # => 8
|
|
13
|
+
#
|
|
14
|
+
# Ruby's Integer already provides gcd, lcm, pow(e, m), Integer.sqrt and
|
|
15
|
+
# bit_length, and Integer#prime? for numbers below about 3e24. Above that
|
|
16
|
+
# its prime? falls back to trial division, so primality here is a
|
|
17
|
+
# Miller-Rabin test (deterministic below 3.3e24, probabilistic with 24
|
|
18
|
+
# bases beyond) and factoring uses trial division by small primes
|
|
19
|
+
# followed by Pollard-Brent rho.
|
|
20
|
+
#
|
|
21
|
+
# Sources (keys refer to MANUAL.md, section Sources): Miller-Rabin
|
|
22
|
+
# [Mil76], [Rab80b], [Knu98, §4.5.4, Algorithm P]; the 13-base bound
|
|
23
|
+
# 3.3e24 [SW17]; Pollard rho [Pol75] with Brent's cycle finding [Bre80],
|
|
24
|
+
# [Knu98, §4.5.4, Algorithm B]; Chinese remainder theorem and modular
|
|
25
|
+
# inverse [Coh93, §1.3], [Knu98, §4.3.2, §4.5.2]; totient [HW08, §5.5].
|
|
26
|
+
module NumberTheory
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# The primes below 1000, by sieve: enough to divide out the small
|
|
30
|
+
# factors before Miller-Rabin and rho take over. (`require "prime"` is
|
|
31
|
+
# trial division all the way up and cost 27 ms of the load.)
|
|
32
|
+
SMALL_PRIMES = begin
|
|
33
|
+
sieve = Array.new(1000, true)
|
|
34
|
+
sieve[0] = sieve[1] = false
|
|
35
|
+
(2..31).each { |i| (i * i).step(999, i) { |j| sieve[j] = false } if sieve[i] }
|
|
36
|
+
sieve.each_index.select { |i| sieve[i] }.freeze
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Above this many bits a composite cofactor is left alone when the
|
|
40
|
+
# caller did not ask for a factorization in the first place.
|
|
41
|
+
EASY_BITS = 64
|
|
42
|
+
|
|
43
|
+
# Bases that make Miller-Rabin deterministic below 3 317 044 064 679 887 385 961 981.
|
|
44
|
+
DETERMINISTIC_BASES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41].freeze
|
|
45
|
+
DETERMINISTIC_LIMIT = 3_317_044_064_679_887_385_961_981
|
|
46
|
+
|
|
47
|
+
# ---- factorization ----------------------------------------------------
|
|
48
|
+
|
|
49
|
+
# Prime factorization of an integer or rational as an IntegerFactorization.
|
|
50
|
+
def factor(n)
|
|
51
|
+
n = integer_or_rational(n, "factor")
|
|
52
|
+
raise ArgumentError, "factor: 0 has no prime factorization" if n.zero?
|
|
53
|
+
unit = n.negative? ? -1 : 1
|
|
54
|
+
if n.is_a?(Rational)
|
|
55
|
+
num = prime_division(n.numerator.abs)
|
|
56
|
+
den = prime_division(n.denominator).map { |p, e| [p, -e] }
|
|
57
|
+
IntegerFactorization.new(unit, (num + den).sort)
|
|
58
|
+
else
|
|
59
|
+
IntegerFactorization.new(unit, prime_division(n.abs))
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The primes in ascending order, without end: what `Prime.each` was.
|
|
64
|
+
def each_prime
|
|
65
|
+
SMALL_PRIMES.each { |p| yield p }
|
|
66
|
+
p = SMALL_PRIMES.last
|
|
67
|
+
loop { yield(p = nextprime(p)) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# [[prime, multiplicity], ...] in ascending order; [] for 1.
|
|
71
|
+
# With hard: false a large composite cofactor is not pursued and the
|
|
72
|
+
# answer is nil instead: that is for callers that are only tidying a
|
|
73
|
+
# number they were handed - sqrt(n), log(n) - and must not disappear
|
|
74
|
+
# into a factorization nobody asked for.
|
|
75
|
+
def prime_division(n, hard: true)
|
|
76
|
+
raise ArgumentError, "prime_division: expected a positive integer, got #{n.inspect}" unless n.is_a?(Integer) && n.positive?
|
|
77
|
+
counts = Hash.new(0)
|
|
78
|
+
SMALL_PRIMES.each do |p|
|
|
79
|
+
break if p * p > n
|
|
80
|
+
while (n % p).zero?
|
|
81
|
+
counts[p] += 1
|
|
82
|
+
n /= p
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
if n > 1
|
|
86
|
+
if n < SMALL_PRIMES.last**2 || prime?(n)
|
|
87
|
+
counts[n] += 1
|
|
88
|
+
else
|
|
89
|
+
return nil if !hard && n.bit_length > EASY_BITS
|
|
90
|
+
split_composite(n, counts)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
counts.sort
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def split_composite(n, counts)
|
|
97
|
+
stack = [n]
|
|
98
|
+
until stack.empty?
|
|
99
|
+
m = stack.pop
|
|
100
|
+
if prime?(m)
|
|
101
|
+
counts[m] += 1
|
|
102
|
+
elsif (r = Integer.sqrt(m)) && r * r == m
|
|
103
|
+
stack.push(r, r)
|
|
104
|
+
else
|
|
105
|
+
d = rho(m)
|
|
106
|
+
stack.push(d, m / d)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Pollard-Brent rho: a nontrivial factor of a composite n.
|
|
112
|
+
def rho(n)
|
|
113
|
+
return 2 if n.even?
|
|
114
|
+
c = 1
|
|
115
|
+
loop do
|
|
116
|
+
y = 2
|
|
117
|
+
m = 128
|
|
118
|
+
g = r = q = 1
|
|
119
|
+
x = ys = y
|
|
120
|
+
while g == 1
|
|
121
|
+
x = y
|
|
122
|
+
r.times { y = (y * y + c) % n }
|
|
123
|
+
k = 0
|
|
124
|
+
while k < r && g == 1
|
|
125
|
+
ys = y
|
|
126
|
+
[m, r - k].min.times do
|
|
127
|
+
y = (y * y + c) % n
|
|
128
|
+
q = q * (x - y).abs % n
|
|
129
|
+
end
|
|
130
|
+
g = q.gcd(n)
|
|
131
|
+
k += m
|
|
132
|
+
end
|
|
133
|
+
r *= 2
|
|
134
|
+
end
|
|
135
|
+
if g == n
|
|
136
|
+
loop do
|
|
137
|
+
ys = (ys * ys + c) % n
|
|
138
|
+
g = (x - ys).abs.gcd(n)
|
|
139
|
+
break if g > 1
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
return g if g != n
|
|
143
|
+
c += 1
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# ---- primes -----------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
# Miller-Rabin; exact below DETERMINISTIC_LIMIT, a strong probable-prime
|
|
150
|
+
# test with 24 bases above it.
|
|
151
|
+
def prime?(n)
|
|
152
|
+
n = integer_or_rational(n, "isprime")
|
|
153
|
+
return false unless n.is_a?(Integer) && n >= 2
|
|
154
|
+
SMALL_PRIMES.each do |p|
|
|
155
|
+
return n == p if (n % p).zero?
|
|
156
|
+
end
|
|
157
|
+
return true if n < SMALL_PRIMES.last**2
|
|
158
|
+
bases = n < DETERMINISTIC_LIMIT ? DETERMINISTIC_BASES : SMALL_PRIMES.first(24)
|
|
159
|
+
d = n - 1
|
|
160
|
+
s = 0
|
|
161
|
+
while d.even?
|
|
162
|
+
d >>= 1
|
|
163
|
+
s += 1
|
|
164
|
+
end
|
|
165
|
+
bases.all? { |a| strong_probable_prime?(n, a, d, s) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def strong_probable_prime?(n, a, d, s)
|
|
169
|
+
x = a.pow(d, n)
|
|
170
|
+
return true if x == 1 || x == n - 1
|
|
171
|
+
(s - 1).times do
|
|
172
|
+
x = x.pow(2, n)
|
|
173
|
+
return true if x == n - 1
|
|
174
|
+
end
|
|
175
|
+
false
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# The smallest prime greater than n.
|
|
179
|
+
def nextprime(n)
|
|
180
|
+
n = integer_or_rational(n, "nextprime").floor
|
|
181
|
+
return 2 if n < 2
|
|
182
|
+
candidate = n.even? ? n + 1 : n + 2
|
|
183
|
+
candidate += 2 until prime?(candidate)
|
|
184
|
+
candidate
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# The largest prime smaller than n.
|
|
188
|
+
def prevprime(n)
|
|
189
|
+
n = integer_or_rational(n, "prevprime").ceil
|
|
190
|
+
raise ArgumentError, "prevprime: there is no prime below #{n}" if n <= 2
|
|
191
|
+
return 2 if n == 3
|
|
192
|
+
candidate = n.even? ? n - 1 : n - 2
|
|
193
|
+
candidate -= 2 until prime?(candidate)
|
|
194
|
+
candidate
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# ---- divisors and multiplicative functions ----------------------------
|
|
198
|
+
|
|
199
|
+
# All positive divisors of n in ascending order.
|
|
200
|
+
def divisors(n)
|
|
201
|
+
n = integer_or_rational(n, "divisors")
|
|
202
|
+
raise ArgumentError, "divisors: expected a nonzero integer, got #{n.inspect}" unless n.is_a?(Integer) && !n.zero?
|
|
203
|
+
prime_division(n.abs).reduce([1]) do |list, (p, e)|
|
|
204
|
+
list.flat_map { |d| (0..e).map { |k| d * p**k } }
|
|
205
|
+
end.sort
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Euler's totient: the number of integers in 1..n coprime to n.
|
|
209
|
+
def totient(n)
|
|
210
|
+
n = integer_or_rational(n, "totient")
|
|
211
|
+
raise ArgumentError, "totient: expected a positive integer, got #{n.inspect}" unless n.is_a?(Integer) && n.positive?
|
|
212
|
+
prime_division(n).reduce(1) { |acc, (p, e)| acc * p**(e - 1) * (p - 1) }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# ---- modular arithmetic -----------------------------------------------
|
|
216
|
+
|
|
217
|
+
# The inverse of a modulo m, in 0...m.
|
|
218
|
+
def invmod(a, m)
|
|
219
|
+
raise ArgumentError, "invmod: the modulus must be a positive integer" unless m.is_a?(Integer) && m.positive?
|
|
220
|
+
a = a % m
|
|
221
|
+
g, x, = extended_gcd(a, m)
|
|
222
|
+
raise ZeroDivisionError, "#{a} has no inverse modulo #{m} (gcd #{g})" unless g == 1
|
|
223
|
+
x % m
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# [g, s, t] with s*a + t*b = g = gcd(a, b).
|
|
227
|
+
def extended_gcd(a, b)
|
|
228
|
+
s0, s1, t0, t1 = 1, 0, 0, 1
|
|
229
|
+
until b.zero?
|
|
230
|
+
q, r = a.divmod(b)
|
|
231
|
+
a, b = b, r
|
|
232
|
+
s0, s1 = s1, s0 - q * s1
|
|
233
|
+
t0, t1 = t1, t0 - q * t1
|
|
234
|
+
end
|
|
235
|
+
a.negative? ? [-a, -s0, -t0] : [a, s0, t0]
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Chinese remainder theorem: the smallest x >= 0 with x = r_i (mod m_i)
|
|
239
|
+
# for every i. Moduli need not be coprime; an inconsistent system raises.
|
|
240
|
+
# Solutions of f(x) = 0 mod m, as the residues in 0...m. A linear
|
|
241
|
+
# congruence goes through the extended Euclidean algorithm, anything else
|
|
242
|
+
# is tested residue by residue, which is what a first course does.
|
|
243
|
+
def congruence(f, x, m)
|
|
244
|
+
m = integer_or_rational(m, "congruence").to_i
|
|
245
|
+
raise ArgumentError, "congruence: the modulus must be positive" unless m.positive?
|
|
246
|
+
x = Expression.lift(x)
|
|
247
|
+
coefficients = Solve.polynomial_coefficients(Solve.to_zero(f).simplify, x)
|
|
248
|
+
raise ArgumentError, "congruence: #{f} is not a polynomial in #{x}" if coefficients.nil?
|
|
249
|
+
values = coefficients.map { |c| integer_or_rational(c, "congruence").to_i }
|
|
250
|
+
return linear_congruence(values, m) if values.size == 2
|
|
251
|
+
raise ArgumentError, "congruence: the modulus #{m} is too large to search" if m > 100_000
|
|
252
|
+
(0...m).select { |r| horner(values, r, m).zero? }
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def horner(coefficients, r, m) = coefficients.reverse.reduce(0) { |acc, c| (acc * r + c) % m }
|
|
256
|
+
|
|
257
|
+
# a*x + b = 0 mod m has gcd(a, m) solutions when that gcd divides b.
|
|
258
|
+
def linear_congruence(coefficients, m)
|
|
259
|
+
b, a = coefficients
|
|
260
|
+
g = a.gcd(m)
|
|
261
|
+
return [] unless (-b % m % g).zero?
|
|
262
|
+
step = m / g
|
|
263
|
+
first = (invmod(a / g, step) * (-b / g)) % step
|
|
264
|
+
(0...g).map { |i| (first + i * step) % m }
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# The Legendre symbol (a/p): 0, 1 when a is a square modulo the odd prime
|
|
268
|
+
# p, and -1 when it is not (Euler's criterion).
|
|
269
|
+
def legendre(a, p)
|
|
270
|
+
a = integer_or_rational(a, "legendre").to_i
|
|
271
|
+
p = integer_or_rational(p, "legendre").to_i
|
|
272
|
+
raise ArgumentError, "legendre: p must be an odd prime" unless p > 2 && prime?(p)
|
|
273
|
+
value = a.pow((p - 1) / 2, p)
|
|
274
|
+
value > 1 ? value - p : value
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# The Jacobi symbol, the Legendre symbol extended to odd composite n.
|
|
278
|
+
def jacobi(a, n)
|
|
279
|
+
a = integer_or_rational(a, "jacobi").to_i
|
|
280
|
+
n = integer_or_rational(n, "jacobi").to_i
|
|
281
|
+
raise ArgumentError, "jacobi: n must be positive and odd" unless n.positive? && n.odd?
|
|
282
|
+
a %= n
|
|
283
|
+
result = 1
|
|
284
|
+
while a != 0
|
|
285
|
+
while a.even?
|
|
286
|
+
a /= 2
|
|
287
|
+
result = -result if [3, 5].include?(n % 8)
|
|
288
|
+
end
|
|
289
|
+
a, n = n, a
|
|
290
|
+
result = -result if a % 4 == 3 && n % 4 == 3
|
|
291
|
+
a %= n
|
|
292
|
+
end
|
|
293
|
+
n == 1 ? result : 0
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# The multiplicative order of a modulo m: the least k > 0 with a**k = 1.
|
|
297
|
+
def order(a, m)
|
|
298
|
+
a = integer_or_rational(a, "order").to_i
|
|
299
|
+
m = integer_or_rational(m, "order").to_i
|
|
300
|
+
raise ArgumentError, "order: a and m must be coprime" unless a.gcd(m) == 1
|
|
301
|
+
phi = totient(m)
|
|
302
|
+
divisors(phi).find { |d| a.pow(d, m) == 1 }
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# A generator of the multiplicative group modulo m, when one exists.
|
|
306
|
+
def primitive_root(m)
|
|
307
|
+
m = integer_or_rational(m, "primitive_root").to_i
|
|
308
|
+
phi = totient(m)
|
|
309
|
+
# from 1: the unit group mod 2 is {1}, and 1 generates it (A8)
|
|
310
|
+
candidate = (1...m).find { |a| a.gcd(m) == 1 && order(a, m) == phi }
|
|
311
|
+
raise ArgumentError, "primitive_root: there is none modulo #{m}" if candidate.nil? && m > 1
|
|
312
|
+
m == 1 ? 0 : candidate
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# The continued fraction [a0; a1, a2, ...] of a rational or a real number.
|
|
316
|
+
def continued_fraction(value, terms = 10)
|
|
317
|
+
value = value.simplify if value.is_a?(Expression)
|
|
318
|
+
value = value.value if value.is_a?(Num)
|
|
319
|
+
return fraction_terms(Rational(value), terms) if value.is_a?(Integer) || value.is_a?(Rational)
|
|
320
|
+
return fraction_terms(value, terms) if value.is_a?(Float)
|
|
321
|
+
certified_terms(Expression.lift(value), terms)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def fraction_terms(x, terms)
|
|
325
|
+
out = []
|
|
326
|
+
terms.times do
|
|
327
|
+
whole = x.floor
|
|
328
|
+
out << whole
|
|
329
|
+
rest = x - whole
|
|
330
|
+
break if rest.zero? || (rest.is_a?(Float) && rest.abs < 1e-12)
|
|
331
|
+
x = 1 / rest
|
|
332
|
+
end
|
|
333
|
+
out
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# An irrational constant's terms from its digits, and only the terms
|
|
337
|
+
# two precisions agree on: a Float gave sqrt(1000001) wrong from the
|
|
338
|
+
# third term and pi from the fourteenth (third review, A6).
|
|
339
|
+
def certified_terms(expr, terms)
|
|
340
|
+
digits = 20 + 8 * terms
|
|
341
|
+
4.times do
|
|
342
|
+
coarse = fraction_terms(Precision.evalf(expr, digits, certify: false).value.to_r, terms + 2)
|
|
343
|
+
fine = fraction_terms(Precision.evalf(expr, 2 * digits, certify: false).value.to_r, terms + 2)
|
|
344
|
+
common = coarse.zip(fine).take_while { |a, b| a == b }.map(&:first)
|
|
345
|
+
return common.first(terms) if common.size > terms || (common.size == terms && coarse.size == fine.size)
|
|
346
|
+
digits *= 2
|
|
347
|
+
end
|
|
348
|
+
raise ArgumentError, "continued_fraction: #{expr} could not be expanded to #{terms} terms"
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# The fractions [a0], [a0; a1], ... of a continued fraction, the best
|
|
352
|
+
# rational approximations of the number.
|
|
353
|
+
def convergents(value, terms = 10)
|
|
354
|
+
coefficients = value.is_a?(Array) ? value : continued_fraction(value, terms)
|
|
355
|
+
previous = [1, 0] # numerator, denominator of the fraction before
|
|
356
|
+
current = [coefficients.first, 1]
|
|
357
|
+
out = [Rational(current[0], current[1])]
|
|
358
|
+
coefficients.drop(1).each do |a|
|
|
359
|
+
nxt = [a * current[0] + previous[0], a * current[1] + previous[1]]
|
|
360
|
+
previous = current
|
|
361
|
+
current = nxt
|
|
362
|
+
out << Rational(current[0], current[1])
|
|
363
|
+
end
|
|
364
|
+
out
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def chrem(residues, moduli)
|
|
368
|
+
raise ArgumentError, "chrem: give as many residues as moduli" unless residues.size == moduli.size && !moduli.empty?
|
|
369
|
+
x = 0
|
|
370
|
+
m = 1
|
|
371
|
+
residues.zip(moduli).each do |r, mod|
|
|
372
|
+
mod = mod.value if mod.is_a?(Num)
|
|
373
|
+
raise ArgumentError, "chrem: the moduli must be positive integers" unless mod.is_a?(Integer) && mod.positive?
|
|
374
|
+
r = residue(r, mod)
|
|
375
|
+
g, s, = extended_gcd(m, mod)
|
|
376
|
+
diff = r - x
|
|
377
|
+
raise ArgumentError, "chrem: no solution, #{x} mod #{m} and #{r} mod #{mod} are incompatible" unless (diff % g).zero?
|
|
378
|
+
lcm = m / g * mod
|
|
379
|
+
x = (x + m * ((diff / g) * s % (mod / g))) % lcm
|
|
380
|
+
m = lcm
|
|
381
|
+
end
|
|
382
|
+
x
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# An Integer, the rcas number holding one, or an element of GF(mod) -
|
|
386
|
+
# what a determinant over GF(p) is (it was a NoMethodError, 24 Sept 2026).
|
|
387
|
+
def residue(r, mod)
|
|
388
|
+
r = r.value if r.is_a?(Num)
|
|
389
|
+
if r.is_a?(Mod)
|
|
390
|
+
raise ArgumentError, "chrem: #{r.value} is a residue modulo #{r.p}, not modulo #{mod}" unless r.p == mod
|
|
391
|
+
r = r.value
|
|
392
|
+
end
|
|
393
|
+
raise ArgumentError, "chrem: the residues must be integers, got #{r.inspect}" unless r.is_a?(Integer)
|
|
394
|
+
r
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def integer_or_rational(n, name)
|
|
398
|
+
n = n.value if n.is_a?(Num)
|
|
399
|
+
return n if n.is_a?(Integer) || n.is_a?(Rational)
|
|
400
|
+
raise ArgumentError, "#{name}: expected an integer, got #{n.inspect}"
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# The result of factoring an integer or rational: a unit (1 or -1) and
|
|
405
|
+
# [prime, exponent] pairs; rationals carry negative exponents.
|
|
406
|
+
class IntegerFactorization
|
|
407
|
+
include Enumerable
|
|
408
|
+
|
|
409
|
+
attr_reader :unit, :factors
|
|
410
|
+
|
|
411
|
+
def initialize(unit, factors)
|
|
412
|
+
@unit = unit
|
|
413
|
+
@factors = factors.freeze
|
|
414
|
+
freeze
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def each(&block) = factors.each(&block)
|
|
418
|
+
def size = factors.size
|
|
419
|
+
def [](i) = factors[i]
|
|
420
|
+
def prime? = factors.size == 1 && factors.first.last == 1 && unit == 1
|
|
421
|
+
def primes = factors.map(&:first)
|
|
422
|
+
|
|
423
|
+
# Multiply back out: the Integer or Rational.
|
|
424
|
+
def expand
|
|
425
|
+
factors.reduce(unit) { |acc, (p, e)| acc * Rational(p)**e }.then { |v| v.is_a?(Rational) && v.denominator == 1 ? v.numerator : v }
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def to_expr
|
|
429
|
+
up = factors.select { |_, e| e.positive? }.map { |p, e| Simplify.power_node(Num.new(p), e) }
|
|
430
|
+
down = factors.select { |_, e| e.negative? }.map { |p, e| Simplify.power_node(Num.new(p), -e) }
|
|
431
|
+
body = up.empty? ? Num.new(1) : Simplify.product_node(up)
|
|
432
|
+
body = Div.new(body, Simplify.product_node(down)) unless down.empty?
|
|
433
|
+
unit == -1 ? Neg.new(body) : body
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def to_s = to_expr.to_s
|
|
437
|
+
alias inspect to_s
|
|
438
|
+
|
|
439
|
+
def to_latex(wrap: nil) = LaTeX.of(to_expr, wrap: wrap)
|
|
440
|
+
|
|
441
|
+
def ==(other) = other.is_a?(IntegerFactorization) && other.unit == unit && other.factors == factors
|
|
442
|
+
end
|
|
443
|
+
end
|