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.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/CITATION.cff +17 -0
  3. data/DESIGN.md +783 -0
  4. data/LICENSE +21 -0
  5. data/MANUAL.md +6265 -0
  6. data/README.md +267 -0
  7. data/bin/rcas +9 -0
  8. data/bin/rcas-app +9 -0
  9. data/bin/rcas-chat +9 -0
  10. data/lib/rcas/algebraic.rb +481 -0
  11. data/lib/rcas/analysis.rb +966 -0
  12. data/lib/rcas/app/launcher.rb +203 -0
  13. data/lib/rcas/app/public/app.css +402 -0
  14. data/lib/rcas/app/public/app.js +449 -0
  15. data/lib/rcas/app/public/index.html +46 -0
  16. data/lib/rcas/app/server.rb +220 -0
  17. data/lib/rcas/app/window.rb +94 -0
  18. data/lib/rcas/app/worksheet.rb +290 -0
  19. data/lib/rcas/app.rb +168 -0
  20. data/lib/rcas/background.rb +758 -0
  21. data/lib/rcas/chat/assistant.rb +199 -0
  22. data/lib/rcas/chat/picker.rb +164 -0
  23. data/lib/rcas/chat/repl.rb +583 -0
  24. data/lib/rcas/chat/session.rb +137 -0
  25. data/lib/rcas/chat/settings.rb +71 -0
  26. data/lib/rcas/chat/style.rb +30 -0
  27. data/lib/rcas/chat/tool.rb +53 -0
  28. data/lib/rcas/chat/ui.rb +316 -0
  29. data/lib/rcas/chat/usage.rb +62 -0
  30. data/lib/rcas/chat/workspace.rb +132 -0
  31. data/lib/rcas/chat.rb +54 -0
  32. data/lib/rcas/coefficients.rb +170 -0
  33. data/lib/rcas/combinatorics.rb +274 -0
  34. data/lib/rcas/complex_parts.rb +160 -0
  35. data/lib/rcas/constants.rb +129 -0
  36. data/lib/rcas/core_ext.rb +35 -0
  37. data/lib/rcas/decide.rb +501 -0
  38. data/lib/rcas/decompositions.rb +241 -0
  39. data/lib/rcas/differentiate.rb +144 -0
  40. data/lib/rcas/discussion.rb +558 -0
  41. data/lib/rcas/distributions.rb +980 -0
  42. data/lib/rcas/dixon.rb +95 -0
  43. data/lib/rcas/docs.rb +321 -0
  44. data/lib/rcas/domains.rb +728 -0
  45. data/lib/rcas/expand.rb +174 -0
  46. data/lib/rcas/expression.rb +613 -0
  47. data/lib/rcas/factor.rb +605 -0
  48. data/lib/rcas/finite_field.rb +577 -0
  49. data/lib/rcas/fourier.rb +118 -0
  50. data/lib/rcas/fps.rb +678 -0
  51. data/lib/rcas/fraction.rb +126 -0
  52. data/lib/rcas/functions.rb +1136 -0
  53. data/lib/rcas/gcd.rb +112 -0
  54. data/lib/rcas/geometry.rb +266 -0
  55. data/lib/rcas/groebner.rb +162 -0
  56. data/lib/rcas/hold.rb +277 -0
  57. data/lib/rcas/hypothesis.rb +364 -0
  58. data/lib/rcas/inequalities.rb +689 -0
  59. data/lib/rcas/integral_functions.rb +260 -0
  60. data/lib/rcas/integrate.rb +1589 -0
  61. data/lib/rcas/integrate_substitutions.rb +434 -0
  62. data/lib/rcas/interpolate.rb +40 -0
  63. data/lib/rcas/irb.rb +146 -0
  64. data/lib/rcas/laplace.rb +159 -0
  65. data/lib/rcas/latex.rb +556 -0
  66. data/lib/rcas/lattice.rb +172 -0
  67. data/lib/rcas/linear_algebra.rb +117 -0
  68. data/lib/rcas/linear_program.rb +416 -0
  69. data/lib/rcas/lint.rb +79 -0
  70. data/lib/rcas/matrix.rb +531 -0
  71. data/lib/rcas/matrix_multiply.rb +202 -0
  72. data/lib/rcas/multimodular.rb +286 -0
  73. data/lib/rcas/named_polynomials.rb +274 -0
  74. data/lib/rcas/number_theory.rb +443 -0
  75. data/lib/rcas/numerics.rb +825 -0
  76. data/lib/rcas/ode.rb +488 -0
  77. data/lib/rcas/openmath/objects.rb +364 -0
  78. data/lib/rcas/openmath/phrasebook.rb +551 -0
  79. data/lib/rcas/openmath/popcorn.rb +518 -0
  80. data/lib/rcas/openmath/xml.rb +309 -0
  81. data/lib/rcas/openmath.rb +49 -0
  82. data/lib/rcas/petkovsek.rb +165 -0
  83. data/lib/rcas/piecewise.rb +488 -0
  84. data/lib/rcas/plot.rb +763 -0
  85. data/lib/rcas/plot3d.rb +419 -0
  86. data/lib/rcas/poly_matrix.rb +318 -0
  87. data/lib/rcas/poly_recurrence.rb +117 -0
  88. data/lib/rcas/polynomial.rb +466 -0
  89. data/lib/rcas/precision.rb +925 -0
  90. data/lib/rcas/printer.rb +150 -0
  91. data/lib/rcas/product.rb +155 -0
  92. data/lib/rcas/q_difference.rb +296 -0
  93. data/lib/rcas/q_functions.rb +158 -0
  94. data/lib/rcas/q_summation.rb +308 -0
  95. data/lib/rcas/q_zeilberger.rb +199 -0
  96. data/lib/rcas/random.rb +506 -0
  97. data/lib/rcas/rational_function.rb +186 -0
  98. data/lib/rcas/recurrence.rb +323 -0
  99. data/lib/rcas/render.rb +431 -0
  100. data/lib/rcas/results.rb +192 -0
  101. data/lib/rcas/scalar.rb +219 -0
  102. data/lib/rcas/series.rb +726 -0
  103. data/lib/rcas/simplify.rb +649 -0
  104. data/lib/rcas/solve.rb +2002 -0
  105. data/lib/rcas/special.rb +163 -0
  106. data/lib/rcas/statistics.rb +175 -0
  107. data/lib/rcas/steps.rb +835 -0
  108. data/lib/rcas/summation.rb +532 -0
  109. data/lib/rcas/trig.rb +264 -0
  110. data/lib/rcas/van_hoeij.rb +241 -0
  111. data/lib/rcas/vector.rb +175 -0
  112. data/lib/rcas/vector_calculus.rb +411 -0
  113. data/lib/rcas/version.rb +5 -0
  114. data/lib/rcas/zeilberger.rb +358 -0
  115. data/lib/rcas.rb +91 -0
  116. data/package.json +8 -0
  117. metadata +206 -0
@@ -0,0 +1,577 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module RCAS
5
+ # An element of GF(p): an integer modulo a prime. Lives inside Num, so all
6
+ # of rcas' arithmetic (simplify, polynomials, matrices) applies unchanged.
7
+ class Mod
8
+ attr_reader :value, :p
9
+
10
+ # GF(p) is where an element modulo p lives; x.in?(GF(7)) asks the same
11
+ # question the other way round, as for every other value.
12
+ def domain = FiniteField.of(p)
13
+ def in?(domain) = domain.include?(self)
14
+
15
+ def initialize(value, p)
16
+ @p = p
17
+ @value = value % p
18
+ freeze
19
+ end
20
+
21
+ def field = FiniteField.of(p)
22
+
23
+ def lift(other)
24
+ case other
25
+ when Mod then raise ArgumentError, "different moduli #{p} and #{other.p}" unless other.p == p
26
+ when Integer then return Mod.new(other, p)
27
+ when Rational then return Mod.new(other.numerator, p) * Mod.new(other.denominator, p).inverse
28
+ when Num then return lift(other.value)
29
+ else raise TypeError, "can't combine #{other.class} with GF(#{p})"
30
+ end
31
+ other
32
+ end
33
+
34
+ def symbolic?(other) = other.is_a?(Expression) || other.is_a?(Symbol) || other.is_a?(Algebraic)
35
+
36
+ def +(other) = symbolic?(other) ? Num.new(self) + other : Mod.new(value + lift(other).value, p)
37
+ def -(other) = symbolic?(other) ? Num.new(self) - other : Mod.new(value - lift(other).value, p)
38
+ def *(other) = symbolic?(other) ? Num.new(self) * other : Mod.new(value * lift(other).value, p)
39
+ def -@ = Mod.new(-value, p)
40
+ def quo(other) = symbolic?(other) ? Num.new(self) / other : self * lift(other).inverse
41
+ alias / quo
42
+
43
+ def inverse
44
+ raise ZeroDivisionError, "0 has no inverse in GF(#{p})" if value.zero?
45
+ Mod.new(value.pow(p - 2, p), p)
46
+ end
47
+
48
+ def **(e)
49
+ return Num.new(self)**e if symbolic?(e)
50
+ raise ArgumentError, "integer exponents only" unless e.is_a?(Integer)
51
+ return inverse**(-e) if e.negative?
52
+ Mod.new(value.pow(e, p), p)
53
+ end
54
+
55
+ def zero? = value.zero?
56
+ def one? = value == 1
57
+ def coerce(other) = [lift(other), self]
58
+ def to_i = value
59
+ def to_f = value.to_f
60
+ def to_s = value.to_s
61
+ alias inspect to_s
62
+ def to_latex(wrap: nil) = value.to_s
63
+
64
+ def ==(other)
65
+ case other
66
+ when Mod then other.p == p && other.value == value
67
+ when Integer then other % p == value
68
+ when Rational then lift(other).value == value
69
+ else false
70
+ end
71
+ end
72
+ alias eql? ==
73
+ def hash = [Mod, value, p].hash
74
+
75
+ # multiplicative order
76
+ def order
77
+ raise ArgumentError, "0 has no multiplicative order" if zero?
78
+ field.element_order(self)
79
+ end
80
+ end
81
+
82
+ # An element of GF(p**n): a polynomial in the generator modulo the field's
83
+ # irreducible polynomial. coeffs: Integers mod p, index = power of the generator.
84
+ class GFElement
85
+ attr_reader :coeffs, :field
86
+
87
+ def domain = field
88
+ def in?(domain) = domain.include?(self)
89
+
90
+ def initialize(coeffs, field)
91
+ @field = field
92
+ c = coeffs.map { |x| x % field.p }
93
+ c.pop while !c.empty? && c.last.zero?
94
+ @coeffs = c.freeze
95
+ freeze
96
+ end
97
+
98
+ def p = field.p
99
+
100
+ def lift(other)
101
+ case other
102
+ when GFElement
103
+ raise ArgumentError, "different fields #{field} and #{other.field}" unless other.field == field
104
+ other
105
+ when Mod then GFElement.new([other.value], field)
106
+ when Integer then GFElement.new([other], field)
107
+ when Rational then GFElement.new([other.numerator], field) * GFElement.new([other.denominator], field).inverse
108
+ when Num then lift(other.value)
109
+ else raise TypeError, "can't combine #{other.class} with #{field}"
110
+ end
111
+ end
112
+
113
+ def symbolic?(other) = other.is_a?(Expression) || other.is_a?(Symbol) || other.is_a?(Algebraic)
114
+
115
+ def +(other) = symbolic?(other) ? Num.new(self) + other : GFElement.new(Factor::Dense.add(coeffs, lift(other).coeffs), field)
116
+ def -(other) = symbolic?(other) ? Num.new(self) - other : GFElement.new(Factor::Dense.sub(coeffs, lift(other).coeffs), field)
117
+ def -@ = GFElement.new(coeffs.map(&:-@), field)
118
+ def *(other) = symbolic?(other) ? Num.new(self) * other : GFElement.new(Factor::Dense.rem_mod(Factor::Dense.mul(coeffs, lift(other).coeffs), field.modulus, p), field)
119
+ def quo(other) = symbolic?(other) ? Num.new(self) / other : self * lift(other).inverse
120
+ alias / quo
121
+
122
+ def inverse
123
+ raise ZeroDivisionError, "0 has no inverse in #{field}" if zero?
124
+ s, = Factor::Dense.bezout_mod(coeffs, field.modulus, p)
125
+ GFElement.new(s, field)
126
+ end
127
+
128
+ def **(e)
129
+ return Num.new(self)**e if symbolic?(e)
130
+ raise ArgumentError, "integer exponents only" unless e.is_a?(Integer)
131
+ return inverse**(-e) if e.negative?
132
+ result = field.one_value
133
+ base = self
134
+ while e.positive?
135
+ result *= base if e.odd?
136
+ base *= base
137
+ e >>= 1
138
+ end
139
+ result
140
+ end
141
+
142
+ def zero? = coeffs.empty?
143
+ def one? = coeffs == [1]
144
+ def constant? = coeffs.size <= 1
145
+ def coerce(other) = [lift(other), self]
146
+ def order = field.element_order(self)
147
+ def frobenius = self**p
148
+
149
+ def ==(other)
150
+ case other
151
+ when GFElement then other.field == field && other.coeffs == coeffs
152
+ when Mod, Integer, Rational then lift(other).coeffs == coeffs
153
+ else false
154
+ end
155
+ end
156
+ alias eql? ==
157
+ def hash = [GFElement, coeffs, field.order].hash
158
+
159
+ def to_s
160
+ return "0" if zero?
161
+ parts = coeffs.each_with_index.reject { |c, _| c.zero? }.map do |c, k|
162
+ base = k.zero? ? nil : (k == 1 ? field.gen_name.to_s : "#{field.gen_name}**#{k}")
163
+ if base.nil? then c.to_s
164
+ elsif c == 1 then base
165
+ else "#{c}*#{base}"
166
+ end
167
+ end
168
+ parts.join(" + ")
169
+ end
170
+ alias inspect to_s
171
+
172
+ def to_latex(wrap: nil)
173
+ return "0" if zero?
174
+ coeffs.each_with_index.reject { |c, _| c.zero? }.map do |c, k|
175
+ base = k.zero? ? "" : (k == 1 ? field.gen_name.to_s : "#{field.gen_name}^{#{k}}")
176
+ c == 1 && !base.empty? ? base : "#{c}#{base}"
177
+ end.join(" + ")
178
+ end
179
+
180
+ # For the printer: a sum needs parentheses inside products.
181
+ def printer_precedence
182
+ nonzero = coeffs.count { |c| !c.zero? }
183
+ return Printer::ATOM if nonzero <= 1 && (coeffs.size <= 1 || coeffs.last == 1) && coeffs.size <= 2
184
+ nonzero > 1 ? Printer::ADDITIVE : Printer::MULTIPLICATIVE
185
+ end
186
+ end
187
+
188
+ # GF(q), q = p**n. GF(7), GF(8), GF(9, :b).
189
+ class FiniteField < Domain
190
+ attr_reader :p, :n, :modulus, :gen_name
191
+
192
+ @registry = {}
193
+
194
+ # GF(q) or GF(p, n); the generator of an extension is called +gen+.
195
+ def self.of(q, gen = :a, n = nil)
196
+ p, n = n ? [q, n] : prime_power(q)
197
+ raise ArgumentError, "#{q} is not a prime power" if p.nil?
198
+ @registry[[p, n, gen]] ||= new(p, n, gen)
199
+ end
200
+
201
+ def self.prime_power(q)
202
+ return nil unless q.is_a?(Integer) && q > 1
203
+ division = NumberTheory.prime_division(q)
204
+ return nil unless division.size == 1
205
+ division.first
206
+ end
207
+
208
+ def initialize(p, n, gen)
209
+ @p = p
210
+ @n = n
211
+ @gen_name = gen
212
+ @modulus = n > 1 ? FiniteField.irreducible(p, n) : nil
213
+ freeze
214
+ end
215
+
216
+ def name = "GF(#{order})"
217
+ def order = p**n
218
+ def characteristic = p
219
+ def degree = n
220
+ def prime? = n == 1
221
+ def field? = true
222
+ def finite? = true
223
+ def fraction_field = self
224
+
225
+ # Smallest (lexicographically, by coefficients from the top) monic
226
+ # irreducible polynomial of degree n over GF(p), as a dense array.
227
+ def self.irreducible(p, n)
228
+ (0...p**n).each do |code|
229
+ digits = Array.new(n) { |i| (code / p**i) % p }
230
+ f = digits + [1]
231
+ return f if irreducible_mod?(f, p)
232
+ end
233
+ raise "no irreducible polynomial found" # unreachable
234
+ end
235
+
236
+ # Rabin's test: f of degree n is irreducible iff x**(p**n) = x mod f and
237
+ # gcd(f, x**(p**(n/r)) - x) = 1 for every prime r dividing n.
238
+ def self.irreducible_mod?(f, p)
239
+ n = f.size - 1
240
+ return false if n < 1
241
+ x = [0, 1]
242
+ return false unless Factor::Dense.rem_mod(Factor::Dense.sub(Factor::Dense.powmod(x, p**n, f, p), x), f, p).empty?
243
+ NumberTheory.prime_division(n).map(&:first).all? do |r|
244
+ h = Factor::Dense.sub(Factor::Dense.powmod(x, p**(n / r), f, p), x)
245
+ Factor::Dense.deg(Factor::Dense.gcd_mod(h, f, p)) <= 0
246
+ end
247
+ end
248
+
249
+ # ---- elements --------------------------------------------------------------
250
+
251
+ def zero_value = prime? ? Mod.new(0, p) : GFElement.new([], self)
252
+ def one_value = prime? ? Mod.new(1, p) : GFElement.new([1], self)
253
+ def gen_value = prime? ? Mod.new(1, p) : GFElement.new([0, 1], self)
254
+
255
+ def zero = zero_value
256
+ def one = one_value
257
+ def gen = gen_value
258
+ alias generator gen
259
+
260
+ # Bring an integer, rational, Num or expression in the generator into the
261
+ # field. Returns the element itself (a Mod or GFElement).
262
+ def call(v)
263
+ case v
264
+ when Num then call(v.value)
265
+ when Mod, GFElement, Integer, Rational then one_value.lift(v)
266
+ when Expression
267
+ e = v.subs(Var.new(gen_name) => Num.new(gen_value)).simplify
268
+ raise DomainError, "#{v} is not an element of #{self}" unless e.is_a?(Num)
269
+ call(e)
270
+ else raise DomainError, "#{v.inspect} is not an element of #{self}"
271
+ end
272
+ end
273
+ alias value_of call
274
+
275
+ def normalize_coefficient(c)
276
+ c.is_a?(Num) && !c.finite_field? ? Num.new(call(c)) : c
277
+ end
278
+
279
+ def include?(obj)
280
+ case obj
281
+ when Mod then prime? && obj.p == p
282
+ when GFElement then obj.field == self
283
+ when Integer, Rational then true
284
+ when Num then include?(obj.value)
285
+ when Expression then obj.variables.empty? && !(call(obj) rescue nil).nil?
286
+ else false
287
+ end
288
+ end
289
+
290
+ def subset?(other)
291
+ case other
292
+ when FiniteField then other == self || (prime? && other.p == p)
293
+ else false
294
+ end
295
+ end
296
+
297
+ def join(other)
298
+ case other
299
+ when FiniteField
300
+ return self if other == self
301
+ return other if prime? && other.p == p
302
+ return self if other.prime? && other.p == p
303
+ raise DomainError, "no common field for #{self} and #{other}"
304
+ when NumberSet then other.rank <= 2 ? self : raise(DomainError, "no common field for #{self} and #{other}")
305
+ when PolynomialRing then PolynomialRing.new(join(other.base), other.vars)
306
+ when FractionField then FractionField.new(join(other.ring))
307
+ else raise DomainError, "no common domain for #{self} and #{other}"
308
+ end
309
+ end
310
+
311
+ def elements
312
+ return (0...p).map { |i| Mod.new(i, p) } if prime?
313
+ (0...order).map { |code| GFElement.new(Array.new(n) { |i| (code / p**i) % p }, self) }
314
+ end
315
+
316
+ def random_value(rng = Random.new)
317
+ prime? ? Mod.new(rng.rand(p), p) : GFElement.new(Array.new(n) { rng.rand(p) }, self)
318
+ end
319
+
320
+ # ---- structure -------------------------------------------------------------
321
+
322
+ def element_order(x)
323
+ m = order - 1
324
+ NumberTheory.prime_division(m).each do |r, _|
325
+ m /= r while (m % r).zero? && (x**(m / r)).one?
326
+ end
327
+ m
328
+ end
329
+
330
+ @primitive = {}
331
+
332
+ def primitive_element
333
+ FiniteField.primitive_cache[self] ||= begin
334
+ candidates = prime? ? (2...p).map { |i| Mod.new(i, p) } : elements.reject(&:constant?)
335
+ candidates = [Mod.new(1, p)] if prime? && p == 2
336
+ candidates.find { |x| element_order(x) == order - 1 }
337
+ end
338
+ end
339
+
340
+ def self.primitive_cache = @primitive
341
+
342
+ # Discrete logarithm: base**k == x (baby-step giant-step).
343
+ def log(x, base = primitive_element)
344
+ x = value_of(x)
345
+ b = value_of(base)
346
+ raise ZeroDivisionError, "log of zero" if x.zero?
347
+ m = Integer.sqrt(order - 1) + 1
348
+ table = {}
349
+ cur = one_value
350
+ m.times do |j|
351
+ table[cur] ||= j
352
+ cur *= b
353
+ end
354
+ factor = (b**m).inverse
355
+ gamma = x
356
+ m.times do |i|
357
+ return i * m + table[gamma] if table.key?(gamma)
358
+ gamma *= factor
359
+ end
360
+ raise ArgumentError, "#{x} is not a power of #{b}"
361
+ end
362
+
363
+ def frobenius(x) = value_of(x)**p
364
+
365
+ # Minimal polynomial over GF(p) of an element, as a polynomial in +var+
366
+ # over GF(p): the product over the distinct conjugates x, x**p, x**(p**2), ...
367
+ def minpoly(x, var = :x)
368
+ x = value_of(x)
369
+ base = prime? ? self : FiniteField.of(p)
370
+ ring = base[var]
371
+ conjugates = [x]
372
+ conjugates << conjugates.last**p while conjugates.last**p != x
373
+ big = PolynomialRing.new(self, [var])
374
+ poly = conjugates.reduce(big.one) { |acc, c| acc * big.call(Var.new(var) - Num.new(c)) }
375
+ Polynomial.new(ring, poly.terms.transform_values { |c| Num.new(Mod.new(c.value.is_a?(Mod) ? c.value.value : (c.value.coeffs.first || 0), p)) })
376
+ end
377
+
378
+ # Roots of a polynomial in this field.
379
+ def solve(expr, var = nil)
380
+ expr = Solve.to_zero(expr)
381
+ var = Solve.variable(expr, var)
382
+ self[var.name].call(expr).roots
383
+ end
384
+
385
+ def ==(other) = other.is_a?(FiniteField) && other.p == p && other.n == n && other.gen_name == gen_name
386
+ alias eql? ==
387
+ def hash = [FiniteField, p, n, gen_name].hash
388
+ end
389
+
390
+ # Factorization over a finite field: squarefree decomposition in
391
+ # characteristic p, then Cantor-Zassenhaus (distinct-degree and equal-degree
392
+ # splitting, with the trace trick in characteristic 2).
393
+ #
394
+ # Sources (keys: MANUAL.md, Sources): [CZ81]; [vzGG13, §14.2-14.3]; the
395
+ # irreducibility test above is Rabin's [Rab80].
396
+ module FiniteFieldFactor
397
+ module_function
398
+
399
+ def factor(poly)
400
+ field = poly.ring.base
401
+ raise ArgumentError, "univariate polynomials only" unless poly.ring.univariate?
402
+ return Factorization.new(poly.ring, field.zero, []) if poly.zero?
403
+ f = to_dense(poly)
404
+ lc = f.last
405
+ unit = Num.new(lc)
406
+ f = FP.monic(f)
407
+ factors = []
408
+ FP.squarefree(f, field).each do |part, mult|
409
+ FP.cantor_zassenhaus(part, field).each { |g| factors << [from_dense(g, poly.ring), mult] }
410
+ end
411
+ Factorization.new(poly.ring, unit, factors.sort_by { |g, m| [g.degree, g.to_s, m] })
412
+ end
413
+
414
+ def to_dense(poly)
415
+ field = poly.ring.base
416
+ FP.trim((0..poly.degree).map { |k| field.value_of(poly.coeff(k)) })
417
+ end
418
+
419
+ def from_dense(a, ring)
420
+ Polynomial.new(ring, a.each_with_index.to_h { |c, k| [[k], Num.new(c)] })
421
+ end
422
+
423
+ # Dense polynomials with coefficients that are field element values.
424
+ module FP
425
+ module_function
426
+
427
+ def trim(a)
428
+ a = a.dup
429
+ a.pop while !a.empty? && a.last.zero?
430
+ a
431
+ end
432
+
433
+ def deg(a) = a.size - 1
434
+
435
+ def add(a, b) = trim(Array.new([a.size, b.size].max) { |i| (a[i] && b[i]) ? a[i] + b[i] : (a[i] || b[i]) })
436
+ def sub(a, b) = trim(Array.new([a.size, b.size].max) { |i| a[i] && b[i] ? a[i] - b[i] : (a[i] || -b[i]) })
437
+
438
+ def mul(a, b)
439
+ return [] if a.empty? || b.empty?
440
+ out = Array.new(a.size + b.size - 1) { nil }
441
+ a.each_with_index do |x, i|
442
+ next if x.zero?
443
+ b.each_with_index { |y, j| out[i + j] = out[i + j] ? out[i + j] + x * y : x * y }
444
+ end
445
+ trim(out.map { |v| v || a.first * 0 })
446
+ end
447
+
448
+ def monic(a)
449
+ return a if a.empty?
450
+ inv = a.last.inverse
451
+ a.map { |c| c * inv }
452
+ end
453
+
454
+ def divmod(a, b)
455
+ raise ZeroDivisionError if b.empty?
456
+ r = a.dup
457
+ q = Array.new([deg(a) - deg(b) + 1, 0].max) { b.last * 0 }
458
+ inv = b.last.inverse
459
+ while !r.empty? && deg(r) >= deg(b)
460
+ shift = deg(r) - deg(b)
461
+ c = r.last * inv
462
+ q[shift] = c
463
+ b.each_with_index { |y, j| r[shift + j] = r[shift + j] - c * y }
464
+ r = trim(r)
465
+ end
466
+ [trim(q), r]
467
+ end
468
+
469
+ def rem(a, b) = divmod(a, b).last
470
+ def div(a, b) = divmod(a, b).first
471
+
472
+ def gcd(a, b)
473
+ a, b = b, rem(a, b) until b.empty?
474
+ monic(a)
475
+ end
476
+
477
+ def powmod(a, e, f)
478
+ result = [a.first * 0 + 1]
479
+ base = rem(a, f)
480
+ while e.positive?
481
+ result = rem(mul(result, base), f) if e.odd?
482
+ base = rem(mul(base, base), f)
483
+ e >>= 1
484
+ end
485
+ result
486
+ end
487
+
488
+ def derivative(a) = trim(a.each_with_index.map { |c, i| c * i }.drop(1))
489
+
490
+ # f = g(x**p) => the g with coefficients' p-th roots (Frobenius inverse)
491
+ def pth_root(a, field)
492
+ p = field.p
493
+ coeffs = (0..deg(a) / p).map do |k|
494
+ c = a[k * p]
495
+ field.prime? ? c : c**(field.order / p)
496
+ end
497
+ trim(coeffs)
498
+ end
499
+
500
+ # [[squarefree part, multiplicity], ...] for a monic f (characteristic p).
501
+ def squarefree(f, field)
502
+ out = []
503
+ return out if deg(f) < 1
504
+ d = derivative(f)
505
+ if d.empty?
506
+ squarefree(pth_root(f, field), field).each { |g, m| out << [g, m * field.p] }
507
+ return out
508
+ end
509
+ g = gcd(f, d)
510
+ w = div(f, g)
511
+ i = 1
512
+ while deg(w) >= 1
513
+ y = gcd(w, g)
514
+ z = div(w, y)
515
+ out << [z, i] if deg(z) >= 1
516
+ w = y
517
+ g = div(g, y)
518
+ i += 1
519
+ end
520
+ if deg(g) >= 1
521
+ squarefree(pth_root(g, field), field).each { |h, m| out << [h, m * field.p] }
522
+ end
523
+ out
524
+ end
525
+
526
+ def cantor_zassenhaus(f, field)
527
+ f = monic(f)
528
+ q = field.order
529
+ factors = []
530
+ x = [f.first * 0, f.first * 0 + 1]
531
+ h = x
532
+ d = 0
533
+ while deg(f) >= 2 * (d + 1)
534
+ d += 1
535
+ h = powmod(h, q, f)
536
+ g = gcd(sub(h, x), f)
537
+ next if deg(g) < 1
538
+ factors.concat(equal_degree(g, d, field))
539
+ f = div(f, g)
540
+ h = rem(h, f)
541
+ end
542
+ factors << f if deg(f) >= 1
543
+ factors
544
+ end
545
+
546
+ def equal_degree(g, d, field, rng = Random.new(42))
547
+ return [g] if deg(g) == d
548
+ q = field.order
549
+ one = g.first * 0 + 1
550
+ loop do
551
+ r = trim(Array.new(deg(g)) { field.random_value(rng) })
552
+ next if deg(r) < 1
553
+ if field.p == 2
554
+ # trace map: r + r**2 + r**4 + ... (d * n terms)
555
+ t = r
556
+ acc = r
557
+ (d * field.n - 1).times do
558
+ t = rem(mul(t, t), g)
559
+ acc = add(acc, t)
560
+ end
561
+ b = acc
562
+ else
563
+ b = sub(powmod(r, (q**d - 1) / 2, g), [one])
564
+ end
565
+ c = gcd(b, g)
566
+ next unless deg(c).between?(1, deg(g) - 1)
567
+ return equal_degree(c, d, field, rng) + equal_degree(div(g, c), d, field, rng)
568
+ end
569
+ end
570
+ end
571
+ end
572
+
573
+ # Wire finite-field elements into the rest of the system.
574
+ class Num
575
+ def finite_field? = value.is_a?(Mod) || value.is_a?(GFElement)
576
+ end
577
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RCAS
4
+ # Fourier series on an interval.
5
+ #
6
+ # fourier(x, x: -pi..pi) # the partial sum, four harmonics
7
+ # fourier(x, x: -pi..pi, formal: true) # the general coefficient
8
+ #
9
+ # On [a, b] with T = b - a and omega = 2*pi/T,
10
+ #
11
+ # f ~ a_0/2 + sum_k a_k*cos(k*omega*x) + b_k*sin(k*omega*x)
12
+ # a_k = (2/T) * integral(f(x)*cos(k*omega*x), x, a, b)
13
+ # b_k = (2/T) * integral(f(x)*sin(k*omega*x), x, a, b)
14
+ #
15
+ # `kind: :sine` and `kind: :cosine` are the half-range expansions on
16
+ # [0, L]: the odd and the even extension of f, with omega = pi/L.
17
+ #
18
+ # The coefficients are the integrals rcas already does; what makes the
19
+ # general one come out is that the index is assumed to be an integer
20
+ # while they are computed, so that sin(k*pi) folds to 0 and cos(k*pi) to
21
+ # (-1)**k (constants.rb, Trig.integer_pi_multiple).
22
+ #
23
+ # Sources (keys: MANUAL.md, Sources): [Spi08, ch. 13] for the series and
24
+ # the half-range expansions; convergence is not checked - the series is
25
+ # written down formally, as every table does.
26
+ module Fourier
27
+ module_function
28
+
29
+ DEFAULT_TERMS = 4
30
+
31
+ def series(f, x, from, to, n: DEFAULT_TERMS, kind: :full, formal: false, index: nil)
32
+ f = Expression.lift(f)
33
+ x = Expression.lift(x)
34
+ from = Expression.lift(from)
35
+ to = Expression.lift(to)
36
+ raise ArgumentError, "fourier: kind must be :full, :sine or :cosine" unless %i[full sine cosine].include?(kind)
37
+ length = (to - from).simplify
38
+ raise ArgumentError, "fourier: the interval needs two finite ends" if length.each_node.any? { |node| node == OO }
39
+ omega = ((kind == :full ? 2 : 1) * PI / length).simplify
40
+ formal ? formal_series(f, x, from, to, omega, length, kind, index) : partial_sum(f, x, from, to, omega, length, kind, n)
41
+ end
42
+
43
+ # The constant term a_0/2: the mean value of f over the interval.
44
+ def constant_term(f, x, from, to, length, kind)
45
+ return Num.new(0) if kind == :sine
46
+ value = coefficient(f, x, from, to, Num.new(1), length) || missing!(f, x, Num.new(1))
47
+ (value / 2).simplify
48
+ end
49
+
50
+ # (2/T) * integral(f*weight, x, from, to), or nil when the integral is
51
+ # not elementary.
52
+ def coefficient(f, x, from, to, weight, length)
53
+ value = Integrate.definite((f * weight).simplify, x, from, to)
54
+ return nil unless value.each_node.none? { |node| node.is_a?(Integral) }
55
+ (2 * value / length).simplify
56
+ end
57
+
58
+ def missing!(f, x, weight)
59
+ raise SeriesError, "fourier: integral(#{(f * weight).simplify}, #{x}) is not elementary, " \
60
+ "so this coefficient has no closed form"
61
+ end
62
+
63
+ # ---- the partial sum ----------------------------------------------------
64
+
65
+ def partial_sum(f, x, from, to, omega, length, kind, n)
66
+ raise ArgumentError, "fourier: n must be a positive integer" unless n.is_a?(Integer) && n.positive?
67
+ total = constant_term(f, x, from, to, length, kind)
68
+ (1..n).each do |k|
69
+ harmonic(kind, Num.new(k) * omega * x).each do |weight|
70
+ value = coefficient(f, x, from, to, weight, length) || missing!(f, x, weight)
71
+ total += value * weight
72
+ end
73
+ end
74
+ total.simplify
75
+ end
76
+
77
+ # The weights of one harmonic: cos and sin for the full series, one of
78
+ # them for a half-range expansion.
79
+ def harmonic(kind, angle)
80
+ case kind
81
+ when :sine then [Fn.new(:sin, [angle.simplify])]
82
+ when :cosine then [Fn.new(:cos, [angle.simplify])]
83
+ else [Fn.new(:cos, [angle.simplify]), Fn.new(:sin, [angle.simplify])]
84
+ end
85
+ end
86
+
87
+ # ---- the general coefficient --------------------------------------------
88
+
89
+ def formal_series(f, x, from, to, omega, length, kind, index)
90
+ k = index_variable(f, x, index)
91
+ angle = (k * omega * x).simplify
92
+ term = integer_index(k.name) do
93
+ harmonic(kind, angle).sum(Num.new(0)) do |weight|
94
+ value = coefficient(f, x, from, to, weight, length) || missing!(f, x, weight)
95
+ value * weight
96
+ end
97
+ end
98
+ (constant_term(f, x, from, to, length, kind) + Sum.new(term.simplify, k, Num.new(1), OO)).simplify
99
+ end
100
+
101
+ def index_variable(f, x, index)
102
+ return Var.new(index.to_sym) if index
103
+ taken = f.variables | [x.name]
104
+ name = %i[k n m j].find { |candidate| !taken.include?(candidate) }
105
+ Var.new(name || :k)
106
+ end
107
+
108
+ # The index is a positive integer while the coefficients are computed:
109
+ # that is what turns sin(k*pi) into 0 and cos(k*pi) into (-1)**k.
110
+ def integer_index(name)
111
+ previous = RCAS.assumption(name)
112
+ RCAS.assume(name => NN)
113
+ yield
114
+ ensure
115
+ previous ? RCAS.assume(name => previous) : RCAS.forget(name)
116
+ end
117
+ end
118
+ end