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,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
|
data/lib/rcas/fourier.rb
ADDED
|
@@ -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
|