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,358 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCAS
|
|
4
|
+
# Zeilberger's algorithm (creative telescoping): the recurrence a definite
|
|
5
|
+
# hypergeometric sum S(n) = sum_k F(n, k) obeys.
|
|
6
|
+
#
|
|
7
|
+
# Gosper's algorithm decides whether one term has an antidifference.
|
|
8
|
+
# Zeilberger's runs it on a whole pencil of terms at once: it looks for
|
|
9
|
+
# constants sigma_0, ..., sigma_r (free of k, rational in n) and a rational
|
|
10
|
+
# R with
|
|
11
|
+
#
|
|
12
|
+
# sum_j sigma_j*F(n + j, k) = G(k + 1) - G(k), G(k) = R(k)*F(n, k),
|
|
13
|
+
#
|
|
14
|
+
# and then summing over k makes the right-hand side telescope away, so
|
|
15
|
+
# sum_j sigma_j*S(n + j) = 0. What makes it work is that the whole thing
|
|
16
|
+
# stays linear: dividing by F(n, k) leaves
|
|
17
|
+
#
|
|
18
|
+
# sum_j sigma_j*a_j(k) = R(k + 1)*r(k) - R(k),
|
|
19
|
+
#
|
|
20
|
+
# with a_j(k) = F(n + j, k)/F(n, k) and r(k) = F(n, k + 1)/F(n, k) known
|
|
21
|
+
# rational functions. Writing sum_j sigma_j*a_j = N(k)/D(k), the term ratio
|
|
22
|
+
# of the pencil is r(k)*D(k)/D(k + 1) * N(k + 1)/N(k), whose
|
|
23
|
+
# Gosper-Petkovsek normal form has the sigmas only in the c part: Gosper's
|
|
24
|
+
# equation a(k)*X(k + 1) - b(k - 1)*X(k) = c_0(k)*N(k) is linear in the
|
|
25
|
+
# coefficients of X and in the sigmas together, and one null space of one
|
|
26
|
+
# matrix gives both. The order r is raised until a solution turns up.
|
|
27
|
+
#
|
|
28
|
+
# The telescoping identity is verified as a rational identity before it is
|
|
29
|
+
# returned; the step from it to the recurrence for the sum needs natural
|
|
30
|
+
# boundaries, that is F(n, k) = 0 outside the range summed over (binomial
|
|
31
|
+
# coefficients give that by themselves).
|
|
32
|
+
#
|
|
33
|
+
# Sources (keys: MANUAL.md, Sources): [Zei91]; [Koe14, ch. 7];
|
|
34
|
+
# [PWZ96, ch. 6].
|
|
35
|
+
module Zeilberger
|
|
36
|
+
MAX_ORDER = 4
|
|
37
|
+
|
|
38
|
+
# The recurrence and the proof that goes with it.
|
|
39
|
+
Certificate = Struct.new(:coefficients, :rational, :order, :term, :n, :k) do
|
|
40
|
+
# sum_j coefficients[j]*s(n + j) as an expression, for an unknown
|
|
41
|
+
# sequence named +name+.
|
|
42
|
+
def recurrence(name)
|
|
43
|
+
coefficients.each_with_index.map { |c, j| c * Fn.new(name, [(n + j).simplify]) }.reduce(:+).simplify
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_s = "#{recurrence(:S)} = 0"
|
|
47
|
+
def inspect = to_s
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
module_function
|
|
51
|
+
|
|
52
|
+
# The recurrence for S(n) = sum_k F(n, k), as an Equation in the unknown
|
|
53
|
+
# sequence +s+, which names the sum and its index: sumrecursion(F, k, S(n)).
|
|
54
|
+
def sumrecursion(term, k, s, max_order: MAX_ORDER)
|
|
55
|
+
n = index_of(s)
|
|
56
|
+
k = Expression.lift(k)
|
|
57
|
+
found = certificate(term, n, k, max_order: max_order)
|
|
58
|
+
raise RCAS::Unsupported, "sumrecursion: no recurrence of order #{max_order} or less for #{term}" if found.nil?
|
|
59
|
+
if boundary_terms?(found, n, k)
|
|
60
|
+
raise RCAS::Unsupported, "sumrecursion: the telescoping identity holds for #{term}, but its " \
|
|
61
|
+
"boundary terms do not vanish, so the sum itself does not obey the recurrence"
|
|
62
|
+
end
|
|
63
|
+
Equation.new(found.recurrence(s.name), Num.new(0))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Creative telescoping proves the identity under the summation sign;
|
|
67
|
+
# carrying it over to the sum needs the boundary terms to vanish, and for
|
|
68
|
+
# a term such as binomial(n, k)/(k + 1), with its pole at k = -1, they do
|
|
69
|
+
# not. So sum_{k=0}^{n} F(n, k) is put into the recurrence for the first
|
|
70
|
+
# few n, and only a residue that is demonstrably non-zero counts against
|
|
71
|
+
# it: a check that cannot be carried out says nothing either way.
|
|
72
|
+
#
|
|
73
|
+
# The sum is taken over the term's natural range: 0..2*n for
|
|
74
|
+
# binomial(2*n, k), where 0..n cut it off and refused a true recurrence
|
|
75
|
+
# (third review, S9).
|
|
76
|
+
def boundary_terms?(found, n, k)
|
|
77
|
+
term = found.term
|
|
78
|
+
order = found.coefficients.size - 1
|
|
79
|
+
upper = natural_upper(term, n, k)
|
|
80
|
+
(0..(order + 2)).any? do |i|
|
|
81
|
+
values = (0..order).map { |j| value_at(term, n, k, Num.new(0), upper, i + j) }
|
|
82
|
+
next false if values.any?(&:nil?)
|
|
83
|
+
total = values.each_with_index.reduce(Num.new(0)) do |acc, (value, j)|
|
|
84
|
+
acc + found.coefficients[j].subs(n => i) * value
|
|
85
|
+
end
|
|
86
|
+
nonzero?(total)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The upper end past which the term vanishes: the top of its binomial
|
|
91
|
+
# coefficients binomial(top, k) when they agree on one, n otherwise.
|
|
92
|
+
def natural_upper(term, n, k)
|
|
93
|
+
tops = term.each_node.select { |e| e.is_a?(Fn) && e.name == :binomial && e.args.size == 2 && e.args.last == k }
|
|
94
|
+
.map { |e| e.args.first }.uniq
|
|
95
|
+
tops.size == 1 && tops.first.variables.include?(n.name) ? tops.first : n
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Is this expression definitely not zero? Exactly where it can be
|
|
99
|
+
# decided, at random values for whatever parameters are left otherwise.
|
|
100
|
+
# Only a decided non-zero counts (Decide.identically_zero?): a float
|
|
101
|
+
# residue said "non-zero" for rounding noise and refused a true
|
|
102
|
+
# q-recurrence (third review, S8).
|
|
103
|
+
def nonzero?(value)
|
|
104
|
+
Decide.identically_zero?(value.simplify) == false
|
|
105
|
+
rescue ZeroDivisionError, DomainError, ArgumentError, TypeError
|
|
106
|
+
false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# The certificate that proves it: the rational R with
|
|
110
|
+
# sum_j sigma_j*F(n + j, k) = G(k + 1) - G(k) and G(k) = R(k)*F(n, k).
|
|
111
|
+
def sumcertificate(term, k, s, max_order: MAX_ORDER)
|
|
112
|
+
n = index_of(s)
|
|
113
|
+
k = Expression.lift(k)
|
|
114
|
+
found = certificate(term, n, k, max_order: max_order)
|
|
115
|
+
raise RCAS::Unsupported, "sumcertificate: no recurrence of order #{max_order} or less for #{term}" if found.nil?
|
|
116
|
+
found.rational
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def index_of(s)
|
|
120
|
+
raise ArgumentError, "name the sum and its index, e.g. S(n)" unless s.is_a?(Fn) && s.args.size == 1
|
|
121
|
+
n = s.args.first
|
|
122
|
+
raise ArgumentError, "the index must be a symbol, got #{n}" unless n.is_a?(Var)
|
|
123
|
+
n
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# => Certificate, or nil when no recurrence of order <= max_order exists.
|
|
127
|
+
def certificate(term, n, k, max_order: MAX_ORDER)
|
|
128
|
+
term = Expression.lift(term).simplify
|
|
129
|
+
n = Expression.lift(n)
|
|
130
|
+
k = Expression.lift(k)
|
|
131
|
+
in_k = Summation.term_ratio(term, k)&.first or return nil
|
|
132
|
+
in_n = Summation.term_ratio(term, n)&.first or return nil
|
|
133
|
+
(1..max_order).each do |order|
|
|
134
|
+
found = attempt(term, n, k, in_k, in_n, order)
|
|
135
|
+
return found if found
|
|
136
|
+
end
|
|
137
|
+
nil
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# The closed form of S(n) = sum_k F(n, k) between bounds that are natural
|
|
141
|
+
# boundaries: the recurrence creative telescoping gives, solved with as
|
|
142
|
+
# many initial values as its order and checked against direct summation.
|
|
143
|
+
# nil when any of that fails, so the sum stays unevaluated instead of
|
|
144
|
+
# coming out wrong.
|
|
145
|
+
def closed_form(term, n, k, from, to, max_order: 2)
|
|
146
|
+
found = certificate(term, n, k, max_order: max_order) or return nil
|
|
147
|
+
order = found.coefficients.size - 1
|
|
148
|
+
return nil if order.zero?
|
|
149
|
+
init = {}
|
|
150
|
+
(0...order).each do |i|
|
|
151
|
+
value = value_at(term, n, k, from, to, i) or return nil
|
|
152
|
+
init[i] = value
|
|
153
|
+
end
|
|
154
|
+
equation = Equation.new(found.recurrence(:_S), Num.new(0))
|
|
155
|
+
closed = Recurrence.rsolve(equation, :_S, n, init: init).rhs
|
|
156
|
+
return nil if closed.variables.any? { |v| v.to_s.start_with?("C") } # constants left unfixed
|
|
157
|
+
confirmed?(closed, term, n, k, from, to, order) ? closed.simplify : nil
|
|
158
|
+
rescue NotImplementedError, RCAS::Unsupported, ArgumentError, DomainError, ZeroDivisionError
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
CONFIRMATIONS = 3
|
|
163
|
+
|
|
164
|
+
# sum_k F(i, k) for one concrete i, added up term by term.
|
|
165
|
+
def value_at(term, n, k, from, to, i)
|
|
166
|
+
bounds = [from, to].map { |bound| bound.subs(n => i).simplify }
|
|
167
|
+
return nil unless bounds.all? { |b| b.is_a?(Num) && b.value.is_a?(Integer) }
|
|
168
|
+
Summation.direct_sum(term.subs(n => i), k, *bounds)
|
|
169
|
+
rescue ZeroDivisionError, DomainError
|
|
170
|
+
nil
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# The closed form has to agree with the sum itself past the values that
|
|
174
|
+
# were fitted: a recurrence derived for natural boundaries says nothing
|
|
175
|
+
# about a sum whose boundary terms do not vanish.
|
|
176
|
+
def confirmed?(closed, term, n, k, from, to, order)
|
|
177
|
+
(order..(order + CONFIRMATIONS)).all? do |i|
|
|
178
|
+
expected = value_at(term, n, k, from, to, i)
|
|
179
|
+
next true if expected.nil?
|
|
180
|
+
actual = closed.subs(n => i).simplify
|
|
181
|
+
Scalar.zero?((expected - actual).simplify)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# One order: build Gosper's equation for the pencil and solve it.
|
|
186
|
+
def attempt(term, n, k, in_k, in_n, order)
|
|
187
|
+
sigmas = (0..order).map { |j| Var.new(:"_z#{j}") }
|
|
188
|
+
base = [k.name, n.name] | term.variables.to_a
|
|
189
|
+
ring = QQ[*base]
|
|
190
|
+
big = QQ[*(base + sigmas.map(&:name))]
|
|
191
|
+
|
|
192
|
+
shifted = [Num.new(1)]
|
|
193
|
+
(1...(order + 1)).each { |j| shifted << (shifted.last * in_n.subs(n => n + j - 1)).cancel }
|
|
194
|
+
fractions = shifted.map { |a| Fraction.as_fraction(a, base) or return nil }
|
|
195
|
+
denominator = fractions.map(&:last).reduce(ring.one) { |acc, q| acc.lcm(q) }
|
|
196
|
+
numerators = fractions.map { |(p, q)| p * denominator.exact_div(q) }
|
|
197
|
+
pencil = sigmas.each_with_index.reduce(big.zero) do |acc, (s, j)|
|
|
198
|
+
acc + big.call(s) * numerators[j].to_ring(big)
|
|
199
|
+
end
|
|
200
|
+
return nil if pencil.zero?
|
|
201
|
+
|
|
202
|
+
pair = Fraction.as_fraction(in_k, base) or return nil
|
|
203
|
+
p = pair.first * denominator
|
|
204
|
+
q = pair.last * Summation.shift(denominator, k, 1)
|
|
205
|
+
common = p.gcd(q)
|
|
206
|
+
p = p.exact_div(common)
|
|
207
|
+
q = q.exact_div(common)
|
|
208
|
+
a, b, c0 = Summation.normal_form(p, q, k)
|
|
209
|
+
bm = Summation.shift(b, k, -1)
|
|
210
|
+
c = c0.to_ring(big) * pencil
|
|
211
|
+
bound = Summation.degree_bound(a.to_ring(big), bm.to_ring(big), c, k.name) or return nil
|
|
212
|
+
|
|
213
|
+
xs = (0..bound).map { |i| Var.new(:"_y#{i}") }
|
|
214
|
+
x_of_k = xs.each_with_index.reduce(Num.new(0)) { |acc, (x, i)| acc + x * k**i }
|
|
215
|
+
equation = (a.to_expr * x_of_k.subs(k => k + 1) - bm.to_expr * x_of_k - c.to_expr).expand
|
|
216
|
+
conditions = Solve.polynomial_coefficients(equation, k) or return nil
|
|
217
|
+
values = solve(conditions, xs, sigmas) or return nil
|
|
218
|
+
|
|
219
|
+
coefficients, factor = clear_denominators(sigmas.map { |s| values[s] }, n)
|
|
220
|
+
return nil if coefficients.all? { |c2| Scalar.zero?(c2) }
|
|
221
|
+
# The identity is linear in the sigmas, so the certificate is scaled
|
|
222
|
+
# by whatever clearing the denominators scaled them by.
|
|
223
|
+
rational = certificate_of(values, xs, factor, bm, c0, denominator, k, ring)
|
|
224
|
+
found = Certificate.new(coefficients, rational, order, term, n, k)
|
|
225
|
+
verify(found, in_k, shifted) ? found : nil
|
|
226
|
+
rescue DomainError, NotImplementedError, RCAS::Unsupported, ZeroDivisionError
|
|
227
|
+
nil
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# R(k) = factor*b(k - 1)*X(k) / (c_0(k)*D(k)), kept in the polynomial ring
|
|
231
|
+
# and reduced once at the end: assembling it as an expression and
|
|
232
|
+
# cancelling that costs minutes on a sum of three binomial cubes,
|
|
233
|
+
# because the common denominator is built up by multiplication.
|
|
234
|
+
def certificate_of(values, xs, factor, bm, c0, denominator, k, ring)
|
|
235
|
+
pairs = xs.map { |x| Fraction.as_fraction(values[x], ring.vars) || [ring.call(values[x]), ring.one] }
|
|
236
|
+
common = pairs.map(&:last).reduce(ring.one) { |acc, d| acc.lcm(d) }
|
|
237
|
+
numerator = pairs.each_with_index.reduce(ring.zero) do |acc, ((p, q), i)|
|
|
238
|
+
acc + p * common.exact_div(q) * ring.call(k**i)
|
|
239
|
+
end
|
|
240
|
+
num = ring.call(factor) * bm.to_ring(ring) * numerator
|
|
241
|
+
den = c0.to_ring(ring) * denominator.to_ring(ring) * common
|
|
242
|
+
shared = num.gcd(den)
|
|
243
|
+
num = num.exact_div(shared)
|
|
244
|
+
den = den.exact_div(shared)
|
|
245
|
+
den.constant? ? (num.to_expr / den.to_expr).simplify : num.to_expr / den.to_expr
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# A null space vector of the homogeneous system in which not every sigma
|
|
249
|
+
# is zero: that is the recurrence. Vectors with all sigmas zero only say
|
|
250
|
+
# that Gosper's equation itself has a solution and are of no use here.
|
|
251
|
+
#
|
|
252
|
+
# The coefficients are polynomials in n, so PolyMatrix does the kernel by
|
|
253
|
+
# evaluation and interpolation; row reduction over the rational functions
|
|
254
|
+
# themselves swells the entries badly (an order-2 system was still
|
|
255
|
+
# running after a minute).
|
|
256
|
+
def solve(conditions, xs, sigmas)
|
|
257
|
+
unknowns = xs + sigmas
|
|
258
|
+
rows = conditions.map do |condition|
|
|
259
|
+
unknowns.map { |u| Solve.polynomial_coefficients(condition, u)&.[](1) || Num.new(0) }
|
|
260
|
+
end
|
|
261
|
+
vectors = PolyMatrix.kernel(rows) || row_reduce(conditions, unknowns)
|
|
262
|
+
vectors.each do |vector|
|
|
263
|
+
values = unknowns.each_with_index.to_h { |u, i| [u, vector[i]] }
|
|
264
|
+
next if sigmas.all? { |s| Scalar.zero?(values[s]) }
|
|
265
|
+
return values
|
|
266
|
+
end
|
|
267
|
+
nil
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# The same kernel by plain row reduction, for coefficients PolyMatrix
|
|
271
|
+
# will not take (a second parameter beside n).
|
|
272
|
+
def row_reduce(conditions, unknowns)
|
|
273
|
+
solution = Solve.linear_system(conditions, unknowns).first or return []
|
|
274
|
+
free = unknowns - solution.keys
|
|
275
|
+
free.map do |one|
|
|
276
|
+
assignment = free.to_h { |v| [v, Num.new(v == one ? 1 : 0)] }
|
|
277
|
+
unknowns.map { |v| (solution[v] || v).subs(assignment).cancel }
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# The sigmas come out of the linear algebra as rational functions of n;
|
|
282
|
+
# the recurrence reads better with polynomial coefficients, so multiply
|
|
283
|
+
# by the common denominator and drop the content. Returns the
|
|
284
|
+
# coefficients and the factor they were multiplied by.
|
|
285
|
+
def clear_denominators(coefficients, n)
|
|
286
|
+
vars = coefficients.flat_map { |c| c.variables.to_a }.uniq | [n.name]
|
|
287
|
+
ring = QQ[*vars]
|
|
288
|
+
common = ring.one
|
|
289
|
+
coefficients.each do |c|
|
|
290
|
+
pair = Fraction.as_fraction(c, vars) or return [coefficients, Num.new(1)]
|
|
291
|
+
common = common.lcm(pair.last)
|
|
292
|
+
end
|
|
293
|
+
polynomials = coefficients.map { |c| ring.call((c * common.to_expr).cancel.expand) }
|
|
294
|
+
factor = common
|
|
295
|
+
content = polynomials.reduce(ring.zero) { |acc, poly| acc.gcd(poly) }
|
|
296
|
+
unless content.zero? || content.constant?
|
|
297
|
+
polynomials = polynomials.map { |poly| poly.exact_div(content) }
|
|
298
|
+
factor = factor.exact_div(content)
|
|
299
|
+
end
|
|
300
|
+
polynomials, scale = integral(polynomials)
|
|
301
|
+
factor *= scale
|
|
302
|
+
if negative?(polynomials)
|
|
303
|
+
polynomials = polynomials.map { |poly| -poly }
|
|
304
|
+
factor = -factor
|
|
305
|
+
end
|
|
306
|
+
[polynomials.map(&:to_expr), factor.to_expr]
|
|
307
|
+
rescue DomainError, NotImplementedError, RCAS::Unsupported, ZeroDivisionError
|
|
308
|
+
[coefficients, Num.new(1)]
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Integer coefficients without a common divisor: over the rationals the
|
|
312
|
+
# gcd above is monic and leaves 2*n + 4, n/2 alone.
|
|
313
|
+
def integral(polynomials)
|
|
314
|
+
numbers = polynomials.flat_map { |poly| poly.terms.values }.grep(Num).map(&:value).grep(Numeric)
|
|
315
|
+
return [polynomials, polynomials.first.ring.one] unless numbers.all? { |v| v.is_a?(Integer) || v.is_a?(Rational) }
|
|
316
|
+
scale = numbers.reduce(1) { |l, v| l.lcm(v.is_a?(Rational) ? v.denominator : 1) }
|
|
317
|
+
divisor = numbers.map { |v| (v * scale).to_i.abs }.reduce(0) { |g, v| g.gcd(v) }
|
|
318
|
+
divisor = 1 if divisor.zero?
|
|
319
|
+
value = Rational(scale, divisor)
|
|
320
|
+
return [polynomials, polynomials.first.ring.one] if value == 1
|
|
321
|
+
[polynomials.map { |poly| poly * value }, Polynomial.constant(polynomials.first.ring, value)]
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Does the highest coefficient carry a minus sign?
|
|
325
|
+
def negative?(polynomials)
|
|
326
|
+
lead = polynomials.reverse.find { |poly| !poly.zero? }&.leading_coefficient
|
|
327
|
+
lead.is_a?(Num) && lead.value.is_a?(Numeric) && lead.value.real? && lead.value.negative?
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
CHECKS = 4
|
|
331
|
+
|
|
332
|
+
# sum_j sigma_j*a_j(k) = R(k + 1)*r(k) - R(k). The linear algebra behind
|
|
333
|
+
# the certificate is exact and PolyMatrix checks its own kernel vectors,
|
|
334
|
+
# so this is a check on the derivation rather than on the arithmetic:
|
|
335
|
+
# both sides are rational, and they are compared at exact rational
|
|
336
|
+
# points, which is far cheaper than a normal form of the difference.
|
|
337
|
+
def verify(found, in_k, shifted)
|
|
338
|
+
k = found.k
|
|
339
|
+
left = shifted.each_with_index.reduce(Num.new(0)) { |acc, (a, j)| acc + found.coefficients[j] * a }
|
|
340
|
+
difference = left - (found.rational.subs(k => k + 1) * in_k - found.rational)
|
|
341
|
+
names = difference.variables.to_a
|
|
342
|
+
random = Random.new(20140610) # the book's publication date, for a stable seed
|
|
343
|
+
checked = 0
|
|
344
|
+
40.times do
|
|
345
|
+
point = names.to_h { |v| [v, Num.new(Rational(random.rand(5..97), random.rand(2..7)))] }
|
|
346
|
+
value = begin
|
|
347
|
+
difference.subs(point).simplify
|
|
348
|
+
rescue ZeroDivisionError
|
|
349
|
+
next
|
|
350
|
+
end
|
|
351
|
+
return false unless Scalar.zero?(value)
|
|
352
|
+
checked += 1
|
|
353
|
+
break if checked >= CHECKS
|
|
354
|
+
end
|
|
355
|
+
checked >= CHECKS
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
end
|
data/lib/rcas.rb
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# RCAS - a tiny symbolic algebra system that lives inside plain Ruby.
|
|
4
|
+
#
|
|
5
|
+
# require "rcas"
|
|
6
|
+
# e = (:x + 1) * (1 - :x) # => (x + 1)*(1 - x)
|
|
7
|
+
# e.expand # => 1 - x**2
|
|
8
|
+
# e.diff(:x) # => -2*x
|
|
9
|
+
# e.call(x: 3) # => -8
|
|
10
|
+
require_relative "rcas/version"
|
|
11
|
+
|
|
12
|
+
require_relative "rcas/expression"
|
|
13
|
+
require_relative "rcas/printer"
|
|
14
|
+
require_relative "rcas/simplify"
|
|
15
|
+
require_relative "rcas/expand"
|
|
16
|
+
require_relative "rcas/differentiate"
|
|
17
|
+
require_relative "rcas/functions"
|
|
18
|
+
require_relative "rcas/domains"
|
|
19
|
+
require_relative "rcas/scalar"
|
|
20
|
+
require_relative "rcas/polynomial"
|
|
21
|
+
require_relative "rcas/coefficients"
|
|
22
|
+
require_relative "rcas/gcd"
|
|
23
|
+
require_relative "rcas/factor"
|
|
24
|
+
require_relative "rcas/integrate"
|
|
25
|
+
require_relative "rcas/integrate_substitutions"
|
|
26
|
+
require_relative "rcas/constants"
|
|
27
|
+
require_relative "rcas/fraction"
|
|
28
|
+
require_relative "rcas/rational_function"
|
|
29
|
+
require_relative "rcas/number_theory"
|
|
30
|
+
require_relative "rcas/groebner"
|
|
31
|
+
require_relative "rcas/interpolate"
|
|
32
|
+
require_relative "rcas/solve"
|
|
33
|
+
require_relative "rcas/ode"
|
|
34
|
+
require_relative "rcas/series"
|
|
35
|
+
require_relative "rcas/summation"
|
|
36
|
+
require_relative "rcas/product"
|
|
37
|
+
require_relative "rcas/complex_parts"
|
|
38
|
+
require_relative "rcas/recurrence"
|
|
39
|
+
require_relative "rcas/poly_recurrence"
|
|
40
|
+
require_relative "rcas/petkovsek"
|
|
41
|
+
require_relative "rcas/zeilberger"
|
|
42
|
+
require_relative "rcas/q_functions"
|
|
43
|
+
require_relative "rcas/q_summation"
|
|
44
|
+
require_relative "rcas/q_difference"
|
|
45
|
+
require_relative "rcas/q_zeilberger"
|
|
46
|
+
require_relative "rcas/fps"
|
|
47
|
+
require_relative "rcas/laplace"
|
|
48
|
+
require_relative "rcas/geometry"
|
|
49
|
+
require_relative "rcas/analysis"
|
|
50
|
+
require_relative "rcas/numerics"
|
|
51
|
+
require_relative "rcas/statistics"
|
|
52
|
+
require_relative "rcas/special"
|
|
53
|
+
require_relative "rcas/precision"
|
|
54
|
+
require_relative "rcas/integral_functions"
|
|
55
|
+
require_relative "rcas/distributions"
|
|
56
|
+
require_relative "rcas/hypothesis"
|
|
57
|
+
require_relative "rcas/combinatorics"
|
|
58
|
+
require_relative "rcas/named_polynomials"
|
|
59
|
+
require_relative "rcas/inequalities"
|
|
60
|
+
require_relative "rcas/piecewise"
|
|
61
|
+
require_relative "rcas/fourier"
|
|
62
|
+
require_relative "rcas/trig"
|
|
63
|
+
require_relative "rcas/algebraic"
|
|
64
|
+
require_relative "rcas/decide"
|
|
65
|
+
require_relative "rcas/finite_field"
|
|
66
|
+
require_relative "rcas/discussion"
|
|
67
|
+
require_relative "rcas/hold"
|
|
68
|
+
require_relative "rcas/lint"
|
|
69
|
+
require_relative "rcas/steps"
|
|
70
|
+
require_relative "rcas/vector"
|
|
71
|
+
require_relative "rcas/matrix"
|
|
72
|
+
require_relative "rcas/matrix_multiply"
|
|
73
|
+
require_relative "rcas/multimodular"
|
|
74
|
+
require_relative "rcas/dixon"
|
|
75
|
+
require_relative "rcas/poly_matrix"
|
|
76
|
+
require_relative "rcas/vector_calculus"
|
|
77
|
+
require_relative "rcas/linear_algebra"
|
|
78
|
+
require_relative "rcas/decompositions"
|
|
79
|
+
require_relative "rcas/lattice"
|
|
80
|
+
require_relative "rcas/van_hoeij"
|
|
81
|
+
require_relative "rcas/linear_program"
|
|
82
|
+
require_relative "rcas/random"
|
|
83
|
+
require_relative "rcas/openmath"
|
|
84
|
+
require_relative "rcas/core_ext"
|
|
85
|
+
require_relative "rcas/latex"
|
|
86
|
+
require_relative "rcas/render"
|
|
87
|
+
require_relative "rcas/plot"
|
|
88
|
+
require_relative "rcas/plot3d"
|
|
89
|
+
require_relative "rcas/background"
|
|
90
|
+
require_relative "rcas/results"
|
|
91
|
+
require_relative "rcas/docs"
|
data/package.json
ADDED
metadata
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rcas
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Peter Horn
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-09-26 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bigdecimal
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.1'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '5'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '3.1'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '5'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: irb
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.11'
|
|
40
|
+
- - "<"
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '2'
|
|
43
|
+
type: :runtime
|
|
44
|
+
prerelease: false
|
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '1.11'
|
|
50
|
+
- - "<"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '2'
|
|
53
|
+
description: Symbols are indeterminates, the ordinary operators build expression trees,
|
|
54
|
+
and irb is the REPL. Exact arithmetic first; calculus, solving, polynomial rings,
|
|
55
|
+
finite fields, linear algebra, differential equations, summation and statistics,
|
|
56
|
+
with honest unevaluated answers where there is no closed form.
|
|
57
|
+
email:
|
|
58
|
+
executables:
|
|
59
|
+
- rcas
|
|
60
|
+
- rcas-chat
|
|
61
|
+
- rcas-app
|
|
62
|
+
extensions: []
|
|
63
|
+
extra_rdoc_files: []
|
|
64
|
+
files:
|
|
65
|
+
- CITATION.cff
|
|
66
|
+
- DESIGN.md
|
|
67
|
+
- LICENSE
|
|
68
|
+
- MANUAL.md
|
|
69
|
+
- README.md
|
|
70
|
+
- bin/rcas
|
|
71
|
+
- bin/rcas-app
|
|
72
|
+
- bin/rcas-chat
|
|
73
|
+
- lib/rcas.rb
|
|
74
|
+
- lib/rcas/algebraic.rb
|
|
75
|
+
- lib/rcas/analysis.rb
|
|
76
|
+
- lib/rcas/app.rb
|
|
77
|
+
- lib/rcas/app/launcher.rb
|
|
78
|
+
- lib/rcas/app/public/app.css
|
|
79
|
+
- lib/rcas/app/public/app.js
|
|
80
|
+
- lib/rcas/app/public/index.html
|
|
81
|
+
- lib/rcas/app/server.rb
|
|
82
|
+
- lib/rcas/app/window.rb
|
|
83
|
+
- lib/rcas/app/worksheet.rb
|
|
84
|
+
- lib/rcas/background.rb
|
|
85
|
+
- lib/rcas/chat.rb
|
|
86
|
+
- lib/rcas/chat/assistant.rb
|
|
87
|
+
- lib/rcas/chat/picker.rb
|
|
88
|
+
- lib/rcas/chat/repl.rb
|
|
89
|
+
- lib/rcas/chat/session.rb
|
|
90
|
+
- lib/rcas/chat/settings.rb
|
|
91
|
+
- lib/rcas/chat/style.rb
|
|
92
|
+
- lib/rcas/chat/tool.rb
|
|
93
|
+
- lib/rcas/chat/ui.rb
|
|
94
|
+
- lib/rcas/chat/usage.rb
|
|
95
|
+
- lib/rcas/chat/workspace.rb
|
|
96
|
+
- lib/rcas/coefficients.rb
|
|
97
|
+
- lib/rcas/combinatorics.rb
|
|
98
|
+
- lib/rcas/complex_parts.rb
|
|
99
|
+
- lib/rcas/constants.rb
|
|
100
|
+
- lib/rcas/core_ext.rb
|
|
101
|
+
- lib/rcas/decide.rb
|
|
102
|
+
- lib/rcas/decompositions.rb
|
|
103
|
+
- lib/rcas/differentiate.rb
|
|
104
|
+
- lib/rcas/discussion.rb
|
|
105
|
+
- lib/rcas/distributions.rb
|
|
106
|
+
- lib/rcas/dixon.rb
|
|
107
|
+
- lib/rcas/docs.rb
|
|
108
|
+
- lib/rcas/domains.rb
|
|
109
|
+
- lib/rcas/expand.rb
|
|
110
|
+
- lib/rcas/expression.rb
|
|
111
|
+
- lib/rcas/factor.rb
|
|
112
|
+
- lib/rcas/finite_field.rb
|
|
113
|
+
- lib/rcas/fourier.rb
|
|
114
|
+
- lib/rcas/fps.rb
|
|
115
|
+
- lib/rcas/fraction.rb
|
|
116
|
+
- lib/rcas/functions.rb
|
|
117
|
+
- lib/rcas/gcd.rb
|
|
118
|
+
- lib/rcas/geometry.rb
|
|
119
|
+
- lib/rcas/groebner.rb
|
|
120
|
+
- lib/rcas/hold.rb
|
|
121
|
+
- lib/rcas/hypothesis.rb
|
|
122
|
+
- lib/rcas/inequalities.rb
|
|
123
|
+
- lib/rcas/integral_functions.rb
|
|
124
|
+
- lib/rcas/integrate.rb
|
|
125
|
+
- lib/rcas/integrate_substitutions.rb
|
|
126
|
+
- lib/rcas/interpolate.rb
|
|
127
|
+
- lib/rcas/irb.rb
|
|
128
|
+
- lib/rcas/laplace.rb
|
|
129
|
+
- lib/rcas/latex.rb
|
|
130
|
+
- lib/rcas/lattice.rb
|
|
131
|
+
- lib/rcas/linear_algebra.rb
|
|
132
|
+
- lib/rcas/linear_program.rb
|
|
133
|
+
- lib/rcas/lint.rb
|
|
134
|
+
- lib/rcas/matrix.rb
|
|
135
|
+
- lib/rcas/matrix_multiply.rb
|
|
136
|
+
- lib/rcas/multimodular.rb
|
|
137
|
+
- lib/rcas/named_polynomials.rb
|
|
138
|
+
- lib/rcas/number_theory.rb
|
|
139
|
+
- lib/rcas/numerics.rb
|
|
140
|
+
- lib/rcas/ode.rb
|
|
141
|
+
- lib/rcas/openmath.rb
|
|
142
|
+
- lib/rcas/openmath/objects.rb
|
|
143
|
+
- lib/rcas/openmath/phrasebook.rb
|
|
144
|
+
- lib/rcas/openmath/popcorn.rb
|
|
145
|
+
- lib/rcas/openmath/xml.rb
|
|
146
|
+
- lib/rcas/petkovsek.rb
|
|
147
|
+
- lib/rcas/piecewise.rb
|
|
148
|
+
- lib/rcas/plot.rb
|
|
149
|
+
- lib/rcas/plot3d.rb
|
|
150
|
+
- lib/rcas/poly_matrix.rb
|
|
151
|
+
- lib/rcas/poly_recurrence.rb
|
|
152
|
+
- lib/rcas/polynomial.rb
|
|
153
|
+
- lib/rcas/precision.rb
|
|
154
|
+
- lib/rcas/printer.rb
|
|
155
|
+
- lib/rcas/product.rb
|
|
156
|
+
- lib/rcas/q_difference.rb
|
|
157
|
+
- lib/rcas/q_functions.rb
|
|
158
|
+
- lib/rcas/q_summation.rb
|
|
159
|
+
- lib/rcas/q_zeilberger.rb
|
|
160
|
+
- lib/rcas/random.rb
|
|
161
|
+
- lib/rcas/rational_function.rb
|
|
162
|
+
- lib/rcas/recurrence.rb
|
|
163
|
+
- lib/rcas/render.rb
|
|
164
|
+
- lib/rcas/results.rb
|
|
165
|
+
- lib/rcas/scalar.rb
|
|
166
|
+
- lib/rcas/series.rb
|
|
167
|
+
- lib/rcas/simplify.rb
|
|
168
|
+
- lib/rcas/solve.rb
|
|
169
|
+
- lib/rcas/special.rb
|
|
170
|
+
- lib/rcas/statistics.rb
|
|
171
|
+
- lib/rcas/steps.rb
|
|
172
|
+
- lib/rcas/summation.rb
|
|
173
|
+
- lib/rcas/trig.rb
|
|
174
|
+
- lib/rcas/van_hoeij.rb
|
|
175
|
+
- lib/rcas/vector.rb
|
|
176
|
+
- lib/rcas/vector_calculus.rb
|
|
177
|
+
- lib/rcas/version.rb
|
|
178
|
+
- lib/rcas/zeilberger.rb
|
|
179
|
+
- package.json
|
|
180
|
+
homepage: https://github.com/no-dashes/rcas
|
|
181
|
+
licenses:
|
|
182
|
+
- MIT
|
|
183
|
+
metadata:
|
|
184
|
+
source_code_uri: https://github.com/no-dashes/rcas
|
|
185
|
+
documentation_uri: https://github.com/no-dashes/rcas/blob/main/MANUAL.md
|
|
186
|
+
rubygems_mfa_required: 'true'
|
|
187
|
+
post_install_message:
|
|
188
|
+
rdoc_options: []
|
|
189
|
+
require_paths:
|
|
190
|
+
- lib
|
|
191
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
192
|
+
requirements:
|
|
193
|
+
- - ">="
|
|
194
|
+
- !ruby/object:Gem::Version
|
|
195
|
+
version: '3.3'
|
|
196
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
197
|
+
requirements:
|
|
198
|
+
- - ">="
|
|
199
|
+
- !ruby/object:Gem::Version
|
|
200
|
+
version: '0'
|
|
201
|
+
requirements: []
|
|
202
|
+
rubygems_version: 3.5.22
|
|
203
|
+
signing_key:
|
|
204
|
+
specification_version: 4
|
|
205
|
+
summary: A computer algebra system that lives inside Ruby
|
|
206
|
+
test_files: []
|