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,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
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "rcas",
3
+ "private": true,
4
+ "description": "KaTeX for rcas inline typesetting (lib/rcas/render.rb); run `npm install`",
5
+ "dependencies": {
6
+ "katex": "^0.16.11"
7
+ }
8
+ }
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: []