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,532 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RCAS
4
+ # Unevaluated sum.
5
+ class Sum < Expression
6
+ attr_reader :term, :var, :from, :to
7
+
8
+ def initialize(term, var, from, to)
9
+ @term = term
10
+ @var = var
11
+ @from = from
12
+ @to = to
13
+ freeze
14
+ end
15
+
16
+ def bound_variable = var
17
+ def children = [term, var, from, to]
18
+ def rebuild(term, var, from, to) = Sum.new(term, var, from, to)
19
+ def to_sexp = [:sum, term.to_sexp, var.to_sexp, from.to_sexp, to.to_sexp]
20
+ end
21
+
22
+ # Symbolic summation.
23
+ #
24
+ # sum(k, k, 1, n) # => n/2 + n**2/2
25
+ # sum(k**2, k, 1, n) # => n/6 + n**2/2 + n**3/3
26
+ # sum(2**k, k, 0, n) # => -1 + 2*2**n
27
+ # sum(1/(k*(k + 1)), k, 1, oo) # => 1
28
+ #
29
+ # Polynomials in k get their closed form directly; other terms go through
30
+ # Gosper's algorithm, which finds an antidifference whenever the term is
31
+ # hypergeometric and one exists. A definite sum in one other variable is
32
+ # then tried with creative telescoping (zeilberger.rb): the recurrence it
33
+ # yields is solved and the answer checked against the sum itself. Anything
34
+ # else stays an unevaluated Sum.
35
+ #
36
+ # Sources (keys: MANUAL.md, Sources): power sums by Newton interpolation
37
+ # and Bernoulli numbers, zeta(2m) [GKP94, §6.5]; Euler-Maclaurin tail for
38
+ # zeta(s) numerically [GKP94, §9.5]; Gosper [Gos78] with the degree bound
39
+ # for the polynomial ansatz from [PWZ96, ch. 5]; reversed bounds by Karr's
40
+ # convention [Kar81].
41
+ module Summation
42
+ module_function
43
+
44
+ def sum(f, k, from, to)
45
+ f = Expression.lift(f).simplify
46
+ k = Expression.lift(k)
47
+ from = Expression.lift(from)
48
+ to = Expression.lift(to)
49
+ whole_bounds!(from, to)
50
+ if (reversed = karr(from, to))
51
+ # Karr's convention [Kar81]: sum_{a}^{b} = -sum_{b+1}^{a-1} for
52
+ # b < a - 1, and 0 for the empty b = a - 1 - the one under which a
53
+ # closed form holds at every integer and additivity has no
54
+ # conditions, and what the closed forms computed already (the fifth
55
+ # review's decision; the direct sum said 0)
56
+ return Num.new(0) if reversed == :empty
57
+ return Neg.new(sum(f, k, reversed.first, reversed.last)).simplify
58
+ end
59
+ return UNDEFINED if pole_in_range?(f, k, from, to)
60
+ # a fixed pole past the lower bound is inside the range for every upper
61
+ # bound beyond it: sum(1/((k - 3)*(k - 2)), k, 0, n) has no value from
62
+ # n = 2 on, and the telescoped closed form was finite there (fourth
63
+ # review, S4); the sum stays formal and says so at any number
64
+ return Sum.new(f, k, from, to) if pole_past_start?(f, k, from, to)
65
+ hypergeometric_form(summed(f, k, from, to), to)
66
+ end
67
+
68
+ def pole_past_start?(f, k, from, to)
69
+ return false unless from.is_a?(Num) && from.value.is_a?(Integer)
70
+ return false if to.variables.empty? || to.variables.include?(k.name)
71
+ integer_poles(f, k).any? { |p| p >= from.value }
72
+ end
73
+
74
+ def integer_poles(f, k)
75
+ Analysis.denominators(f, k).flat_map do |d|
76
+ roots = begin
77
+ Solve.solve(d, k, domain: RR)
78
+ rescue StandardError, NotImplementedError, RCAS::Unsupported => rescued
79
+ RCAS.guard!(rescued, refused: true) if rescued.is_a?(StandardError)
80
+ next []
81
+ end
82
+ next [] unless roots.is_a?(Array)
83
+ roots.filter_map { |r| r.value if r.is_a?(Num) && r.value.is_a?(Integer) }
84
+ end
85
+ end
86
+
87
+ # [b + 1, a - 1] for integer bounds a > b + 1, :empty for b = a - 1, nil
88
+ # for an ordinary range or bounds that are not whole numbers.
89
+ def karr(from, to)
90
+ difference = (to - from).simplify
91
+ return nil unless difference.is_a?(Num) && difference.value.is_a?(Integer) && difference.value.negative?
92
+ return :empty if difference.value == -1
93
+ return nil unless from.is_a?(Num) && to.is_a?(Num)
94
+ [(to + 1).simplify, (from - 1).simplify]
95
+ end
96
+
97
+ # A sum runs over integers: sum(k, k, 1.5, 3) was 5.625, which is
98
+ # Faulhaber at a bound that is not one (third review, S11).
99
+ def whole_bounds!(from, to)
100
+ [from, to].each do |b|
101
+ next unless b.is_a?(Num) && b.value.is_a?(Numeric) && b.value.real?
102
+ next if b.value == b.value.round
103
+ raise ArgumentError, "sum: the bounds must be integers, got #{b}"
104
+ end
105
+ end
106
+
107
+ # A term with no value inside the range leaves the sum without one:
108
+ # sum(1/(k*(k + 1)), k, -1, 3) telescoped straight across k = -1 and
109
+ # k = 0 to -5/4 (third review, S4, the discrete twin of the poles an
110
+ # integral is split at). Only integer poles count, and only against
111
+ # numeric bounds; a symbolic upper bound is left to the rest.
112
+ def pole_in_range?(f, k, from, to)
113
+ return false unless from.is_a?(Num) && from.value.is_a?(Integer)
114
+ upper = Limits.infinite?(to) ? Float::INFINITY : (to.is_a?(Num) && to.value.is_a?(Integer) ? to.value : nil)
115
+ return false if upper.nil?
116
+ integer_poles(f, k).any? { |p| p >= from.value && p <= upper }
117
+ end
118
+
119
+ def summed(f, k, from, to)
120
+ # 0 + 0 + ... is 0, however many terms: f*(oo - from + 1) said undefined
121
+ return Num.new(0) if f.is_a?(Num) && f.value.zero?
122
+ return (f * (to - from + 1)).simplify unless f.variables.include?(k.name)
123
+
124
+ coeffs = Solve.polynomial_coefficients(f, k)
125
+ return polynomial_sum(coeffs, k, from, to) if coeffs && to != OO
126
+
127
+ if to == OO && (z = zeta_sum(f, k, from))
128
+ return z
129
+ end
130
+
131
+ # sum_{k=0}^{n} binomial(n, k) ... is the full series when the terms
132
+ # vanish beyond n - which binomial(n, k)/(n - k + 1) does not: the
133
+ # pole at k = n + 1 cancels the zero of the binomial there.
134
+ original = upper = to
135
+ to = OO if vanishes_past_upper?(f, k, to)
136
+
137
+ g = gosper(f, k)
138
+ if g
139
+ upper = to == OO ? tail_limit(g, k) : at_bound(g, k, to + 1)
140
+ lower = at_bound(g, k, from)
141
+ return (upper - lower).cancel if upper && lower
142
+ end
143
+ if to == OO && (known = classical_series(f, k, from))
144
+ return known
145
+ end
146
+ if (telescoped = creative_telescoping(f, k, from, upper))
147
+ return telescoped
148
+ end
149
+ direct = direct_sum(f, k, from, original)
150
+ return direct if direct
151
+ return Fn.new(:harmonic, [original]) if from == Num.new(1) && !Limits.infinite?(original) && Scalar.one?((f * k).simplify) # sum 1/k = H_n
152
+ Sum.new(f, k, from, original)
153
+ end
154
+
155
+ # r(k)*f(k), with a pole of r that a binomial(k + a, c) of f cancels
156
+ # cancelled: 2**k*binomial(k, 2)*(k**2 - 5*k + 8)/(k**2 - k) is
157
+ # 2**k*(k**2 - 5*k + 8)/2, which has a value at k = 0 and 1 - and so has
158
+ # the closed form at every n. binomial(k + a, c) with a whole c is the
159
+ # polynomial (k + a)(k + a - 1).../c!.
160
+ def without_removable_poles(r, f, k)
161
+ g = (r * f).simplify
162
+ return g unless denominator_in?(r, k)
163
+ _, factors = Simplify.factorize(f)
164
+ polynomial = Num.new(1)
165
+ rest = {}
166
+ factors.each do |base, exp|
167
+ top, bottom = base.is_a?(Fn) && base.name == :binomial ? base.args : []
168
+ whole = bottom.is_a?(Num) && bottom.value.is_a?(Integer) && bottom.value.between?(1, 12) && exp.is_a?(Integer) && exp.positive?
169
+ if whole && Solve.polynomial_coefficients(top, k)&.size == 2
170
+ falling = (0...bottom.value).map { |i| top - i }.reduce(:*) / Combinatorics.factorial_value(bottom.value)
171
+ polynomial *= falling**exp
172
+ else
173
+ Simplify.add_factor(rest, base, exp)
174
+ end
175
+ end
176
+ return g if polynomial == Num.new(1)
177
+ cancelled = (r * polynomial).cancel
178
+ return g if denominator_in?(cancelled, k)
179
+ (cancelled.factor * Simplify.rebuild_product(Simplify.factorize(f).first, rest)).simplify
180
+ rescue StandardError => rescued
181
+ RCAS.guard!(rescued)
182
+ (r * f).simplify
183
+ end
184
+
185
+ # binomial(k + a, c) with a whole c >= 0: a polynomial in k.
186
+ def polynomial_binomial?(e, k)
187
+ return false unless e.is_a?(Fn) && e.name == :binomial && e.args.size == 2
188
+ top, bottom = e.args
189
+ bottom.is_a?(Num) && bottom.value.is_a?(Integer) && !bottom.value.negative? &&
190
+ !bottom.variables.include?(k.name) && Solve.polynomial_coefficients(top, k)&.size.to_i <= 2
191
+ end
192
+
193
+ def denominator_in?(r, k)
194
+ fraction = Fraction.as_fraction(r)
195
+ !fraction.nil? && fraction.last.to_expr.variables.include?(k.name)
196
+ end
197
+
198
+ # The antidifference at a bound. 2**k*binomial(k, 2)*(8 - 5*k + k**2)/(k**2 - k)
199
+ # has removable poles at 0 and 1, where binomial(k, 2) vanishes; the
200
+ # identity g(k + 1) - g(k) = f(k) holds there by continuity, so the value
201
+ # is the limit (a bare ZeroDivisionError before: fourth review). nil
202
+ # when the limit is not a finite value.
203
+ def at_bound(g, k, bound)
204
+ g.subs(k => bound).simplify
205
+ rescue ZeroDivisionError
206
+ value = Limits.limit(g, k, bound)
207
+ value.is_a?(Limit) || Limits.infinite?(value) || value == UNDEFINED ? nil : value
208
+ end
209
+
210
+ # f = binomial(n, k)*r(k) with a symbolic n, and r has no pole at
211
+ # k = n + 1, n + 2, ...: then every term past n is 0 and the sum may run
212
+ # to infinity. r has to be a product of powers in k (a rational function
213
+ # times c**k); anything else is not examined and keeps the bound.
214
+ def vanishes_past_upper?(f, k, to)
215
+ return false unless to.is_a?(Expression) && !to.variables.empty? && !to.variables.include?(k.name)
216
+ top = f.each_node.find { |e| e.is_a?(Fn) && e.name == :binomial && e.args.first == to && e.args.last == k }
217
+ return false unless top
218
+ rest = (f / top).simplify
219
+ # binomial(k, 2) is the polynomial k*(k - 1)/2 and has no pole
220
+ return false if rest.each_node.any? { |e| e.is_a?(Fn) && e.variables.include?(k.name) && e.name != :exp && !polynomial_binomial?(e, k) }
221
+ _, factors = Simplify.factorize(rest)
222
+ factors.all? do |base, exp|
223
+ next true unless base.variables.include?(k.name)
224
+ next true unless exp.is_a?(Numeric) && exp.negative?
225
+ coeffs = Solve.polynomial_coefficients(base, k) or next false
226
+ next false unless coeffs.size == 2 # a linear factor: its root can be named
227
+ root = (-coeffs[0] / coeffs[1]).simplify
228
+ shift = (root - to).simplify
229
+ !(shift.is_a?(Num) && shift.value.is_a?(Integer) && shift.value.positive?) &&
230
+ !(shift.is_a?(Num) && shift.value.is_a?(Rational) && shift.value.denominator == 1 && shift.value.positive?) &&
231
+ shift.variables.empty?
232
+ end
233
+ end
234
+
235
+ # A definite sum in one other variable, over bounds that make the terms
236
+ # outside them vanish: Zeilberger's algorithm gives a recurrence for the
237
+ # sum, and the recurrence is solved.
238
+ def creative_telescoping(f, k, from, to)
239
+ return nil unless from.is_a?(Num) && from.value.is_a?(Integer)
240
+ outer = (f.variables - [k.name]).to_a
241
+ return nil unless outer.size == 1
242
+ n = Var.new(outer.first)
243
+ return nil unless to == n || Limits.infinite?(to)
244
+ return nil unless term_ratio(f, k) && term_ratio(f, n)
245
+ # Order 1 only: a sum whose closed form is hypergeometric satisfies a
246
+ # first-order recurrence, and the higher orders are expensive on terms
247
+ # like binomial(n, k)**4 that have no closed form anyway. sumrecursion
248
+ # goes further when asked.
249
+ Zeilberger.closed_form(f, n, k, from, to == n ? to : n, max_order: 1)
250
+ rescue DomainError, NotImplementedError, RCAS::Unsupported, ZeroDivisionError
251
+ nil
252
+ end
253
+
254
+ # Ratio of consecutive terms as a rational function of k (binomials and
255
+ # factorials cancelled), or nil.
256
+ def term_ratio(f, k)
257
+ g = Combinatorics.to_factorials(f)
258
+ ratio = (g.subs(k => k + 1) / g).cancel
259
+ pair = Fraction.as_fraction(ratio, [k.name]) or return nil
260
+ [ratio, pair]
261
+ end
262
+
263
+ def classical_series(f, k, from, depth = 0)
264
+ return nil unless from.is_a?(Num) && from.value.is_a?(Integer)
265
+ pair = term_ratio(f, k)
266
+ return nil if pair.nil?
267
+ known = Combinatorics.known_series(pair.first, k)
268
+ if known
269
+ m0, total = known
270
+ return nil if from.value < m0
271
+ first = f.subs(k => m0).simplify
272
+ return nil if first.each_node.any? { |n| n.is_a?(Fn) && %i[factorial gamma].include?(n.name) && n.args.first.is_a?(Num) }
273
+ result = (first * total).simplify
274
+ (m0...from.value).each { |i| result -= f.subs(k => i).simplify }
275
+ return result.simplify
276
+ end
277
+ # leading terms that vanish (k*binomial(n, k) at k = 0): shift the index
278
+ return nil if depth > 2 || !Scalar.zero?(f.subs(k => from).simplify)
279
+ shifted = f.subs(k => k + from.value + 1).simplify
280
+ classical_series(shifted, k, Num.new(0), depth + 1)
281
+ rescue ZeroDivisionError
282
+ nil # the ratio matched a template but the term itself has a pole
283
+ end
284
+
285
+ MAX_DIRECT_TERMS = 100_000
286
+
287
+ # No closed form but integer bounds: add the terms up exactly.
288
+ def direct_sum(f, k, from, to)
289
+ return nil unless [from, to].all? { |b| b.is_a?(Num) && b.value.is_a?(Integer) }
290
+ return nil if to.value - from.value > MAX_DIRECT_TERMS
291
+ # numbers are added as numbers, everything else collected into one
292
+ # term table and rebuilt once: adding to the growing sum and
293
+ # expanding it again each time was quadratic, and sum(sin(k), k, 1,
294
+ # 3000) ran for minutes (third review, section 5)
295
+ number = 0
296
+ constant = 0
297
+ terms = {}
298
+ (from.value..to.value).each do |i|
299
+ term = Expression.lift(f.call(k.name => i))
300
+ if term.is_a?(Num)
301
+ number += term.value
302
+ else
303
+ c, table = Simplify.termize(term.simplify)
304
+ constant += c
305
+ table.each { |factors, coeff| terms[factors] = (terms[factors] || 0) + coeff }
306
+ end
307
+ end
308
+ return Num.new(number) if terms.empty? && constant.zero?
309
+ Simplify.rebuild_sum(Simplify.normalize_number(number + constant), terms.reject { |_, c| c.zero? })
310
+ end
311
+
312
+ # ---- c / k**s from k = m to infinity: c * (zeta(s) - partial sum) -----------
313
+
314
+ def zeta_sum(f, k, from)
315
+ coeff, factors = Simplify.factorize(f)
316
+ return nil unless factors.size == 1 && factors.key?(k)
317
+ s = factors[k]
318
+ return nil unless s.is_a?(Numeric) && s.real? && s < -1
319
+ return nil unless from.is_a?(Num) && from.value.is_a?(Integer) && from.value >= 1
320
+ s = -s
321
+ return nil unless s.is_a?(Integer)
322
+ partial = (1...from.value).sum { |n| Rational(1, n**s) }
323
+ (Num.new(coeff) * (Fn.new(:zeta, [Num.new(s)]) - Num.new(partial))).simplify
324
+ end
325
+
326
+ # zeta(2m) = (-1)^(m+1) B_2m (2 pi)^(2m) / (2 (2m)!)
327
+ def zeta_even(n)
328
+ m = n / 2
329
+ b = bernoulli(n)
330
+ factorial = (1..n).reduce(1, :*)
331
+ c = Rational((-1)**(m + 1) * 2**n, 2 * factorial) * b
332
+ (Num.new(c) * PI**n).simplify
333
+ end
334
+
335
+ def bernoulli(n)
336
+ @bernoulli ||= [Rational(1)]
337
+ while @bernoulli.size <= n
338
+ m = @bernoulli.size
339
+ s = (0...m).sum { |j| binomial(m + 1, j) * @bernoulli[j] }
340
+ @bernoulli << -s / (m + 1)
341
+ end
342
+ @bernoulli[n]
343
+ end
344
+
345
+ def binomial(n, k) = (1..k).reduce(1) { |acc, i| acc * (n - k + i) / i }
346
+
347
+ # Euler-Maclaurin tail after 200 terms; accurate to ~1e-12 for s >= 1.5.
348
+ def zeta_numeric(s)
349
+ raise ArgumentError, "zeta(s) needs s > 1, got #{s}" unless s > 1
350
+ n = 200
351
+ head = (1...n).sum { |i| i.to_f**-s }
352
+ head + n**(1 - s) / (s - 1) + 0.5 * n**-s + s * n**(-s - 1) / 12 - s * (s + 1) * (s + 2) * n**(-s - 3) / 720
353
+ end
354
+
355
+ # ---- polynomials: Faulhaber by interpolation -----------------------------
356
+
357
+ def polynomial_sum(coeffs, k, from, to)
358
+ total = Num.new(0)
359
+ coeffs.each_with_index do |c, j|
360
+ next if Scalar.zero?(c)
361
+ s = power_sum(j)
362
+ total += c * (s.call(n: to) - s.call(n: from - 1))
363
+ end
364
+ tidy(total.expand)
365
+ end
366
+
367
+ # A hypergeometric closed form is often a binomial coefficient in
368
+ # disguise: the sum of binomial(n, k)**2 comes back from the gammas as
369
+ # 2**(2*n)*gamma(1/2 + n)/(pi**(1/2)*n!), which is binomial(2*n, n).
370
+ def hypergeometric_form(value, to)
371
+ return value unless to.is_a?(Var)
372
+ Combinatorics.as_binomial(value, to) || value
373
+ end
374
+
375
+ # A closed form reads better factored, which is the form a course
376
+ # writes: n**2*(1 + n)**2/4 rather than n**2/4 + n**3/2 + n**4/4. A
377
+ # number stays the number it is, and a polynomial that does not factor
378
+ # comes back as it went in.
379
+ def tidy(value)
380
+ return value if value.is_a?(Num) || value.variables.empty?
381
+ value.factor.simplify
382
+ rescue StandardError, NotImplementedError, RCAS::Unsupported => rescued
383
+ RCAS.guard!(rescued, refused: true)
384
+ value
385
+ end
386
+
387
+ # S_j(n) = 1**j + 2**j + ... + n**j as a polynomial in n.
388
+ def power_sum(j)
389
+ @power_sums ||= {}
390
+ @power_sums[j] ||= begin
391
+ n = Var.new(:n)
392
+ points = (0..j + 1).map { |m| [m, (1..m).sum { |i| i**j }] }
393
+ # Newton interpolation with exact rationals
394
+ coefficients = points.map { |_, v| Rational(v) }
395
+ (1..j + 1).each do |level|
396
+ (j + 1).downto(level) { |i| coefficients[i] = (coefficients[i] - coefficients[i - 1]) / (points[i][0] - points[i - level][0]) }
397
+ end
398
+ poly = Num.new(0)
399
+ (j + 1).downto(0) { |i| poly = (poly * (n - points[i][0]) + coefficients[i]) }
400
+ poly.expand
401
+ end
402
+ end
403
+
404
+ # ---- Gosper's algorithm ------------------------------------------------------
405
+
406
+ # Antidifference g with g(k+1) - g(k) = f(k), or nil.
407
+ def gosper(f, k)
408
+ ratio_pair = term_ratio(f, k)
409
+ return nil if ratio_pair.nil?
410
+ p, q = ratio_pair.last
411
+ ring = p.ring
412
+ return nil unless ring.vars.include?(k.name)
413
+ kn = k.name
414
+
415
+ a, b, c = normal_form(p, q, k)
416
+
417
+ bm = shift(b, k, -1)
418
+ d = degree_bound(a, bm, c, kn) or return nil
419
+ xs = (0..d).map { |i| Var.new(:"_x#{i}") }
420
+ xk = xs.each_with_index.reduce(Num.new(0)) { |acc, (v, i)| acc + v * k**i }
421
+ equation = (a.to_expr * xk.subs(k => k + 1) - bm.to_expr * xk - c.to_expr).expand
422
+ conditions = Solve.polynomial_coefficients(equation, k) or return nil
423
+ solution = Solve.linear_system(conditions, xs).first or return nil
424
+ x_expr = xk.subs(solution).subs(xs.to_h { |v| [v, Num.new(0)] }) # free unknowns: any value works
425
+ # the rational factor cancels on its own: with the term inside, a
426
+ # binomial(k, 3) stood between (k - 3)/4 and its common factor
427
+ g = without_removable_poles((bm.to_expr * x_expr / c.to_expr).cancel, f, k)
428
+ check = (g.subs(k => k + 1) - g - f).cancel
429
+ Scalar.zero?(check) || Decide.identically_zero?(check) ? g : nil
430
+ rescue DomainError, NotImplementedError, RCAS::Unsupported, ZeroDivisionError
431
+ nil
432
+ end
433
+
434
+ # Gosper-Petkovsek normal form of a term ratio: polynomials a, b, c with
435
+ # p(k)/q(k) = a(k)/b(k) * c(k + 1)/c(k) and gcd(a(k), b(k + h)) = 1 for
436
+ # every integer h >= 0. Zeilberger's algorithm uses it too.
437
+ def normal_form(p, q, k)
438
+ a, b, c = p, q, p.ring.one
439
+ dispersion(a, b, k.name).each do |h|
440
+ g = a.gcd(shift(b, k, h))
441
+ next if g.constant?
442
+ a = a.exact_div(g)
443
+ b = b.exact_div(shift(g, k, -h))
444
+ (1..h).each { |i| c *= shift(g, k, -i) }
445
+ end
446
+ [a, b, c]
447
+ end
448
+
449
+ def shift(poly, k, h) = poly.ring.call(poly.to_expr.subs(k => k + h))
450
+
451
+ # Non-negative integers h with gcd(a(k), b(k+h)) non-trivial.
452
+ def dispersion(a, b, kn)
453
+ h = Var.new(:_h)
454
+ ringh = QQ[*a.ring.vars, :_h]
455
+ bh = ringh.call(b.to_expr.subs(Var.new(kn) => Var.new(kn) + h))
456
+ res = ringh.call(a.to_expr).resultant(bh, kn)
457
+ return [] if res.zero?
458
+ coeffs = (0..res.degree(:_h)).map { |i| res.coefficient_in(:_h, i).to_expr }
459
+ roots = begin
460
+ Solve.polynomial_roots(coeffs)
461
+ rescue NotImplementedError, RCAS::Unsupported
462
+ []
463
+ end
464
+ roots.select { |r| r.is_a?(Num) && r.value.is_a?(Integer) && r.value >= 0 }.map(&:value).uniq.sort
465
+ end
466
+
467
+ def degree_bound(a, bm, c, kn)
468
+ da, db, dc = a.degree(kn), bm.degree(kn), c.degree(kn)
469
+ lca = a.leading_coefficient_in(kn)
470
+ lcb = bm.leading_coefficient_in(kn)
471
+ candidates = []
472
+ if da != db || lca != lcb
473
+ candidates << dc - [da, db].max
474
+ else
475
+ n = da
476
+ candidates << dc - n + 1
477
+ diff = bm.coefficient_in(kn, n - 1) - a.coefficient_in(kn, n - 1)
478
+ if n >= 1 && diff.constant? && lca.constant?
479
+ ratio = Scalar.div(diff.constant_term, lca.constant_term)
480
+ candidates << ratio.value if ratio.is_a?(Num) && ratio.value.is_a?(Integer)
481
+ end
482
+ end
483
+ d = candidates.max
484
+ d.negative? ? nil : d
485
+ end
486
+
487
+ # Random-point check for identities the canonical form does not prove.
488
+ # lim_{k -> oo} g(k) for the antidifference: geometric factors with
489
+ # |ratio| < 1 vanish, anything else goes to Limits.limit.
490
+ # The geometric factors of a term are taken together: 2**k/3**k is
491
+ # (2/3)**k and converges, while each factor alone said "2**k diverges"
492
+ # (third review, S3).
493
+ def tail_limit(g, k)
494
+ constant, table = Expand.table(g)
495
+ kept = {}
496
+ table.each do |factors, coeff|
497
+ drop = false
498
+ ratio = 1r
499
+ numeric_ratio = true
500
+ factors.each do |base, exp|
501
+ if base.is_a?(Fn) && %i[factorial gamma].include?(base.name) && base.variables.include?(k.name) && exp.is_a?(Integer)
502
+ raise ArgumentError, "the sum diverges" if exp.positive?
503
+ drop = true
504
+ next
505
+ end
506
+ next unless exp.is_a?(Expression) && exp.variables.include?(k.name) && !base.variables.include?(k.name)
507
+ slope = exp.diff(k)
508
+ next unless slope.is_a?(Num) && (slope.value.is_a?(Integer) || slope.value.is_a?(Rational))
509
+ magnitude =
510
+ if base.is_a?(Num) && base.value.is_a?(Numeric) then base.value.abs
511
+ elsif base == Simplify.exp_base then Math::E
512
+ end
513
+ if magnitude.nil?
514
+ numeric_ratio = false # symbolic ratio: convergence (|ratio| < 1) is assumed
515
+ next
516
+ end
517
+ ratio *= magnitude.is_a?(Float) ? magnitude**slope.value : Rational(magnitude)**slope.value
518
+ end
519
+ if !drop && numeric_ratio && ratio != 1
520
+ raise ArgumentError, "the sum diverges" if ratio > 1
521
+ drop = true
522
+ elsif !numeric_ratio
523
+ drop = true
524
+ end
525
+ kept[factors] = coeff unless drop
526
+ end
527
+ rest = Simplify.rebuild_sum(constant, kept)
528
+ value = Limits.limit(rest, k, OO)
529
+ value.is_a?(Limit) ? nil : value
530
+ end
531
+ end
532
+ end