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,925 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+ require "bigdecimal/math"
5
+
6
+ module RCAS
7
+ # A decimal number that knows how many significant digits it is good for.
8
+ #
9
+ # evalf(pi, 50) # => 3.1415926535897932384626433832795028841971693993751
10
+ #
11
+ # It is a Numeric, so it goes back into an expression and through Scalar,
12
+ # Printer and the domains like any other number; what it adds is an
13
+ # honest `digits` and a `to_s` that prints the digits instead of
14
+ # BigDecimal's own 0.31415e1.
15
+ class Decimal < Numeric
16
+ attr_reader :value, :digits
17
+
18
+ def initialize(value, digits)
19
+ @value = value.is_a?(BigDecimal) ? value : BigDecimal(value, digits)
20
+ @digits = digits
21
+ freeze
22
+ end
23
+
24
+ def to_s
25
+ return "0.0" if value.zero?
26
+ rounded = value.mult(1, digits) # rounded to `digits` significant digits, still a BigDecimal
27
+ exponent = rounded.exponent
28
+ return scientific(rounded) if exponent > digits + 6 || exponent < -5 # 1.0e-20, not 0.1e-19
29
+ whole, fraction = rounded.to_s("F").split(".")
30
+ integer_digits = whole.delete("-").sub(/\A0\z/, "").size
31
+ # The zeros after the point of a number below 1 are not significant
32
+ # digits and must not be charged to the budget: evalf(1/3000, 20) lost
33
+ # one digit per leading zero.
34
+ keep = digits - integer_digits + (integer_digits.zero? ? fraction[/\A0*/].size : 0)
35
+ fraction = fraction[0, [keep, 1].max].to_s
36
+ "#{whole}.#{fraction.empty? ? '0' : fraction}"
37
+ end
38
+
39
+ # One digit before the point, the exponent after the e.
40
+ def scientific(rounded = value)
41
+ sign, mantissa, _, exponent = rounded.split
42
+ mantissa = mantissa[0, digits]
43
+ tail = mantissa[1..].to_s.sub(/0+\z/, "")
44
+ "#{'-' if sign.negative?}#{mantissa[0]}.#{tail.empty? ? '0' : tail}e#{exponent - 1}"
45
+ end
46
+ alias inspect to_s
47
+ def to_latex(wrap: nil) = to_s
48
+
49
+ def to_f = value.to_f
50
+ def to_i = value.to_i
51
+ def to_r = value.to_r
52
+ def to_d = value
53
+ def to_c = Complex(self, 0)
54
+
55
+ def zero? = value.zero?
56
+ def negative? = value.negative?
57
+ def positive? = value.positive?
58
+ def finite? = value.finite?
59
+ def real? = true
60
+ def real = self
61
+ def imaginary = 0
62
+ def abs = Decimal.new(value.abs, digits)
63
+ def -@ = Decimal.new(-value, digits)
64
+ # Integer#round, Float#round and BigDecimal#round give an Integer
65
+ # unless digits after the point are asked for; so does this, or
66
+ # (evalf(a, 40)*10**20).round is no integer to build a lattice from
67
+ def round(n = 0) = n.positive? ? Decimal.new(value.round(n), digits) : value.round(n).to_i
68
+ def truncate(n = 0) = value.truncate(n)
69
+ def floor(n = 0) = value.floor(n)
70
+ def ceil(n = 0) = value.ceil(n)
71
+ def integer? = false
72
+
73
+ # Arithmetic keeps the smaller number of digits: a sum is no better than
74
+ # its worst summand.
75
+ %i[+ - *].each do |op|
76
+ define_method(op) do |other|
77
+ d = Decimal.wrap(other, digits) or return coerce_fallback(op, other)
78
+ Decimal.new(value.public_send(op, d.value), [digits, d.digits].min)
79
+ end
80
+ end
81
+
82
+ def /(other)
83
+ d = Decimal.wrap(other, digits) or return coerce_fallback(:/, other)
84
+ raise ZeroDivisionError, "divided by 0" if d.value.zero?
85
+ keep = [digits, d.digits].min
86
+ Decimal.new(value.div(d.value, keep + Precision::GUARD).mult(1, keep), keep)
87
+ end
88
+
89
+ def **(other)
90
+ d = Decimal.wrap(other, digits) or return coerce_fallback(:**, other)
91
+ keep = [digits, d.digits].min
92
+ Decimal.new(Precision.power_of(value, d.value, keep + Precision::GUARD).mult(1, keep), keep)
93
+ end
94
+
95
+ def <=>(other)
96
+ d = Decimal.wrap(other, digits)
97
+ d ? value <=> d.value : nil
98
+ end
99
+
100
+ def ==(other)
101
+ d = Decimal.wrap(other, digits)
102
+ d ? value == d.value : false
103
+ end
104
+ alias eql? ==
105
+ def hash = [Decimal, value].hash
106
+
107
+ def coerce(other)
108
+ d = Decimal.wrap(other, digits) or raise TypeError, "can't coerce #{other.class} into a Decimal"
109
+ [d, self]
110
+ end
111
+
112
+ # The precedence hook Printer asks for: only the sign matters.
113
+ def printer_precedence = negative? ? Printer::UNARY : Printer::ATOM
114
+
115
+ # A Numeric as a Decimal of at most +digits+ digits, or nil.
116
+ def self.wrap(other, digits)
117
+ case other
118
+ when Decimal then other
119
+ when Integer then new(BigDecimal(other), digits)
120
+ when Rational then new(BigDecimal(other.numerator).div(other.denominator, digits + Precision::GUARD), digits)
121
+ when Float then new(BigDecimal(other, Precision::FLOAT_DIGITS), [digits, Precision::FLOAT_DIGITS].min)
122
+ when BigDecimal then new(other, digits)
123
+ end
124
+ end
125
+
126
+ private
127
+
128
+ def coerce_fallback(op, other)
129
+ a, b = other.coerce(to_f)
130
+ a.public_send(op, b)
131
+ end
132
+ end
133
+
134
+ # evalf with as many digits as you ask for.
135
+ #
136
+ # evalf(pi, 50) # 3.14159265358979323846264338327950288419716939937510
137
+ # evalf(sqrt(2), 40)
138
+ # evalf(exp(1) - 1, x: 2, digits: 30)
139
+ #
140
+ # The tree is walked in BigDecimal with ten guard digits and rounded once
141
+ # at the end. Everything elementary is there (the constants, exp, log, the
142
+ # trigonometric and hyperbolic functions and their inverses, roots, and a
143
+ # real RootOf refined by Newton's method); anything that exists only in
144
+ # double precision - erf, Si, Ci, Ei, li, zeta, an unevaluated integral -
145
+ # raises Precision::Unsupported instead of dressing up sixteen good digits
146
+ # as fifty.
147
+ #
148
+ # A Float in the expression does the same in miniature: it carries only
149
+ # its own sixteen digits, so the answer is reported with sixteen. Ask for
150
+ # more and you get what is true, not what was requested.
151
+ #
152
+ # Sources (keys: MANUAL.md, Sources): the series behind BigMath are the
153
+ # standard ones [AS64, §4.1, §4.3]; Newton's method for the roots
154
+ # [PTVF07, §9.4].
155
+ module Precision
156
+ # Raised for anything that has no arbitrary-precision implementation.
157
+ # A refusal like any other since the fifth review (RCAS::Unsupported, a
158
+ # StandardError): it was an ArgumentError, which a caller who rescues
159
+ # refusals did not expect.
160
+ class Unsupported < RCAS::Unsupported; end
161
+
162
+ # The quadrature ran out of levels: the integrand is too hard for the
163
+ # digits asked, or the integral diverges. Told apart from Unsupported so
164
+ # that a caller knows retrying in Floats would not help either.
165
+ class NoConvergence < Unsupported; end
166
+
167
+ # A magnitude beyond arbitrary precision (exp of more than EXP_LIMIT):
168
+ # far out in the tail of an infinite range this is where the weights
169
+ # have already died, and the quadrature skips the point.
170
+ class Overflow < Unsupported; end
171
+
172
+ GUARD = 10
173
+ FLOAT_DIGITS = Float::DIG + 1
174
+ MAX_TERMS = 100_000
175
+ MAX_NEWTON = 200
176
+
177
+ module_function
178
+
179
+ # The digits are certified, not promised: the value is computed with
180
+ # GUARD extra digits and again with more, and only the digits the two
181
+ # agree on are reported - raising the guard until they agree on all
182
+ # that were asked for. A fixed guard printed 0.0 for exp(x) - 1 at
183
+ # 10**-30, 1 - erf(10) for erfc(10) and a wrong Ei(-50) from digit 14
184
+ # (third review, P-3): cancellation eats guard digits, and how many is
185
+ # not known in advance. A value that shrinks with every step is a zero
186
+ # (sin(pi*10**15)). `certify: false` is the single walk, for a caller
187
+ # that compares precisions itself (Decide).
188
+ GUARDS = [GUARD, 3 * GUARD, 7 * GUARD, 15 * GUARD, 31 * GUARD, 63 * GUARD].freeze
189
+
190
+ def evalf(expr, digits, bindings = {}, certify: true, **more)
191
+ digits = Integer(digits)
192
+ raise ArgumentError, "evalf: digits must be positive, got #{digits}" unless digits.positive?
193
+ expr = Expression.lift(expr)
194
+ table = table_of(bindings.merge(more))
195
+ unless certify
196
+ state = { limit: digits }
197
+ value = walk(expr, digits + GUARD, table, state)
198
+ keep = [digits, state[:limit]].min
199
+ return Decimal.new(value.mult(1, keep), keep)
200
+ end
201
+ previous = nil
202
+ shrinking = 0
203
+ proof = nil # asked once, when the numbers first look like a zero
204
+ GUARDS.each_with_index do |guard, level|
205
+ state = { limit: digits }
206
+ value = walk(expr, digits + guard, table, state)
207
+ keep = [digits, state[:limit]].min
208
+ # a Float inside carries its own sixteen digits, and more precision
209
+ # cannot add to them
210
+ return Decimal.new(value.mult(1, keep), keep) if keep < digits
211
+ if previous
212
+ return Decimal.new(value.mult(1, digits), digits) if agree?(value, previous, digits)
213
+ # Two zeros, or a value that shrinks with every step, is what a
214
+ # zero looks like - and also what a cancellation deeper than the
215
+ # working precision looks like: exp(10**-1000) - 1 is 0 at every
216
+ # guard, and sin(pi + 10**-200) shrank twice before the guard
217
+ # reached the 200 digits that show it. Numbers never prove a zero
218
+ # (the fourth review's rule, which this loop had kept breaking):
219
+ # `Decide.zero?` is asked for a proof, a root separation bound or
220
+ # a normal form, and without one the guard is raised further.
221
+ shrinking = !previous.zero? && value.abs < previous.abs * BigDecimal("1e-#{guard / 3}") ? shrinking + 1 : 0
222
+ if (value.zero? && previous.zero?) || shrinking >= 2
223
+ proof = proved_zero?(expr, table) if proof.nil?
224
+ return Decimal.new(BigDecimal(0), digits) if proof
225
+ end
226
+ end
227
+ previous = value
228
+ end
229
+ raise NoConvergence, "evalf: #{expr} could not be certified to #{digits} digits; the working precision ran out before two evaluations agreed" \
230
+ "#{previous&.zero? ? " (it is 0 to #{digits + GUARDS.last} digits, and rcas cannot prove it is exactly 0)" : ''}"
231
+ end
232
+
233
+ # Only a constant can be proved zero; bindings are substituted first.
234
+ # An expression with a Float in it has no exact value to prove anything
235
+ # about: the Float is as exact as it gets, and 0 to more than 600 digits
236
+ # is its honest value (a Float residual that cancels, as in checking a
237
+ # solution at a sample point, must still come out as 0.0).
238
+ def proved_zero?(expr, table)
239
+ return true if expr.each_node.any? { |n| n.is_a?(Num) && n.value.is_a?(Float) } ||
240
+ table.values.any? { |v| v.is_a?(Float) || (v.is_a?(Num) && v.value.is_a?(Float)) }
241
+ constant = table.empty? ? expr : expr.subs(table.transform_values { |v| Expression.lift(v) })
242
+ constant.variables.empty? && Decide.zero?(constant) == true
243
+ rescue StandardError => rescued
244
+ RCAS.guard!(rescued)
245
+ false
246
+ end
247
+
248
+ # The value at `digits` (plus the guard) and the largest magnitude met
249
+ # on the way, which bounds its absolute error: Decide believes a value
250
+ # only well above scale*10**-digits.
251
+ def evalf_with_scale(expr, digits)
252
+ state = { limit: digits, scale: BigDecimal(0) }
253
+ value = walk(Expression.lift(expr), digits + GUARD, {}, state)
254
+ [value, state[:scale], [digits, state[:limit]].min]
255
+ end
256
+
257
+ def agree?(a, b, digits)
258
+ return false if a.zero? || b.zero?
259
+ (a - b).abs <= [a.abs, b.abs].max * BigDecimal("1e-#{digits}")
260
+ end
261
+
262
+ def table_of(bindings) = bindings.to_h { |name, value| [name.to_sym, value] }
263
+
264
+ # ---- the walk -----------------------------------------------------------
265
+
266
+ def walk(node, prec, bindings, state)
267
+ value = step(node, prec, bindings, state)
268
+ # the largest magnitude met on the way bounds the absolute error of
269
+ # the result (Decide asks for it; a cancellation cannot be finer)
270
+ state[:scale] = [state[:scale], value.abs].max if state.key?(:scale) && value.is_a?(BigDecimal) && value.finite?
271
+ value
272
+ end
273
+
274
+ def step(node, prec, bindings, state)
275
+ case node
276
+ when Num then number(node.value, prec, state)
277
+ when Const then constant(node, prec, state)
278
+ when Var then variable(node, prec, bindings, state)
279
+ when Neg then -walk(node.arg, prec, bindings, state)
280
+ when Add then walk(node.left, prec, bindings, state) + walk(node.right, prec, bindings, state)
281
+ when Sub then walk(node.left, prec, bindings, state) - walk(node.right, prec, bindings, state)
282
+ when Mul then walk(node.left, prec, bindings, state).mult(walk(node.right, prec, bindings, state), prec)
283
+ when Div then divide(walk(node.left, prec, bindings, state), walk(node.right, prec, bindings, state), prec)
284
+ when Pow then power(node, prec, bindings, state)
285
+ when Fn then function(node, prec, bindings, state)
286
+ when RootOf then root_of(node, prec)
287
+ when Sum then series_sum(node, prec, bindings, state)
288
+ when Integral then integral(node, prec, bindings, state)
289
+ when Piecewise then piecewise(node, prec, bindings, state)
290
+ else unsupported!(node.class.name.split("::").last.downcase, node)
291
+ end
292
+ end
293
+
294
+ def unsupported!(what, node)
295
+ raise Unsupported, "evalf: no arbitrary-precision #{what} (#{node}); " \
296
+ "evalf without digits: gives the double-precision value"
297
+ end
298
+
299
+ def number(value, prec, state)
300
+ case value
301
+ when Integer then BigDecimal(value)
302
+ when Rational then BigDecimal(value.numerator).div(value.denominator, prec)
303
+ when Float then float(value, state)
304
+ when BigDecimal then value
305
+ when Decimal then lower(state, value.digits) { value.value }
306
+ when Complex then raise Unsupported, "evalf: #{value} is not real, and only the real numbers are arbitrary-precision here"
307
+ else unsupported!("value", value)
308
+ end
309
+ end
310
+
311
+ # A Float knows sixteen digits and no more; the answer says so.
312
+ def float(value, state)
313
+ raise Unsupported, "evalf: #{value} is not finite" unless value.finite?
314
+ lower(state, FLOAT_DIGITS) { BigDecimal(value, FLOAT_DIGITS) }
315
+ end
316
+
317
+ def lower(state, digits)
318
+ state[:limit] = [state[:limit], digits].min
319
+ yield
320
+ end
321
+
322
+ def constant(node, prec, state)
323
+ return BigMath.PI(prec) if node.name == :pi
324
+ unsupported!("undefined", node) if node.name == :undefined
325
+ unsupported!("constant", node) unless node.value.is_a?(Numeric) && node.value.finite?
326
+ float(node.value, state)
327
+ end
328
+
329
+ def variable(node, prec, bindings, state)
330
+ value = bindings[node.name]
331
+ raise ArgumentError, "evalf: #{node.name} has no value" if value.nil?
332
+ value.is_a?(Numeric) ? number(value, prec, state) : walk(Expression.lift(value), prec, bindings, state)
333
+ end
334
+
335
+ def divide(a, b, prec)
336
+ raise ZeroDivisionError, "divided by 0" if b.zero?
337
+ a.div(b, prec)
338
+ end
339
+
340
+ # ---- powers and roots ---------------------------------------------------
341
+
342
+ def power(node, prec, bindings, state)
343
+ base = walk(node.base, prec, bindings, state)
344
+ exponent = node.exponent
345
+ if exponent.is_a?(Num) && exponent.value.is_a?(Integer)
346
+ return integer_power(base, exponent.value, prec)
347
+ end
348
+ # (-8)**(1/3) is the principal root 1 + i*sqrt(3), as the Float evalf
349
+ # has it, and not real: this walk took the real root -2 until the
350
+ # fifth review's decision (surd(-8, 3) is the real one)
351
+ power_of(base, walk(exponent, prec, bindings, state), prec, node)
352
+ end
353
+
354
+ # surd(x, n): the real n-th root, -|x|**(1/n) below 0 for odd n.
355
+ def surd(node, prec, bindings, state)
356
+ x = walk(node.args.first, prec, bindings, state)
357
+ n = node.args.last
358
+ raise Unsupported, "evalf: #{node} needs a whole positive n" unless n.is_a?(Num) && n.value.is_a?(Integer) && n.value.positive?
359
+ k = BigDecimal(1).div(n.value, prec)
360
+ return power_of(x, k, prec, node) unless x.negative?
361
+ raise Unsupported, "evalf: #{node} is not real (an even root of a negative number)" if n.value.even?
362
+ -power_of(-x, k, prec, node)
363
+ end
364
+
365
+ def integer_power(base, n, prec)
366
+ raise ZeroDivisionError, "divided by 0" if base.zero? && n.negative?
367
+ base.power(n, prec)
368
+ end
369
+
370
+ # b**e for decimals: exact roots where they are exact, exp(e*log(b))
371
+ # otherwise. A negative base is a real number only for an odd root.
372
+ def power_of(base, exponent, prec, node = nil)
373
+ return BigDecimal(1) if exponent.zero?
374
+ return integer_power(base, exponent.to_i, prec) if exponent.frac.zero?
375
+ if base.negative?
376
+ raise Unsupported, "evalf: #{node || "#{base}**#{exponent}"} is not real"
377
+ end
378
+ return BigDecimal(0) if base.zero?
379
+ return base.sqrt(prec) if exponent == BigDecimal("0.5")
380
+ BigMath.exp(exponent.mult(BigMath.log(base, prec), prec), prec)
381
+ end
382
+
383
+ # ---- functions ----------------------------------------------------------
384
+
385
+ ELEMENTARY = %i[exp log sin cos tan atan asin acos sinh cosh tanh abs sign floor ceil round factorial gamma].freeze
386
+
387
+ # The ones BigMath does not have, written out below.
388
+ SPECIAL = %i[erf erfc Si Ci Ei li zeta].freeze
389
+
390
+ def function(node, prec, bindings, state)
391
+ return surd(node, prec, bindings, state) if node.name == :surd && node.args.size == 2
392
+ unless (ELEMENTARY + SPECIAL).include?(node.name) && node.args.size == 1
393
+ unsupported!("#{node.name}", node)
394
+ end
395
+ arg = node.args.first
396
+ # |b**r| = |b|**r for real b and r: the modulus of a principal root of
397
+ # a negative number is real, though the root is not
398
+ if node.name == :abs && arg.is_a?(Pow) && arg.exponent.is_a?(Num) && arg.exponent.value.is_a?(Rational)
399
+ return power_of(walk(arg.base, prec, bindings, state).abs, walk(arg.exponent, prec, bindings, state), prec, arg)
400
+ end
401
+ x = walk(arg, prec, bindings, state)
402
+ SPECIAL.include?(node.name) ? special(node.name, x, prec, node) : apply(node.name, x, prec, node)
403
+ end
404
+
405
+ def apply(name, x, prec, node)
406
+ case name
407
+ when :exp then exponential(x, prec)
408
+ when :log then positive!(x, node) && BigMath.log(x, prec)
409
+ when :sin then BigMath.sin(x, prec)
410
+ when :cos then BigMath.cos(x, prec)
411
+ when :tan then divide(BigMath.sin(x, prec), BigMath.cos(x, prec), prec)
412
+ when :atan then BigMath.atan(x, prec)
413
+ when :asin then arcsin(x, prec, node)
414
+ when :acos then BigMath.PI(prec).div(2, prec) - arcsin(x, prec, node)
415
+ when :sinh then (BigMath.exp(x, prec) - BigMath.exp(-x, prec)).div(2, prec)
416
+ when :cosh then (BigMath.exp(x, prec) + BigMath.exp(-x, prec)).div(2, prec)
417
+ when :tanh then divide(BigMath.exp(x, prec) - BigMath.exp(-x, prec), BigMath.exp(x, prec) + BigMath.exp(-x, prec), prec)
418
+ when :abs then x.abs
419
+ when :sign then BigDecimal(x <=> 0)
420
+ when :floor then BigDecimal(x.floor)
421
+ when :ceil then BigDecimal(x.ceil)
422
+ when :round then BigDecimal(x.round)
423
+ when :factorial, :gamma then whole_factorial(x, name, node)
424
+ end
425
+ end
426
+
427
+ # Beyond this the exponential is not a number anyone is waiting for,
428
+ # and BigMath would grind for a very long time on the way to saying so.
429
+ EXP_LIMIT = 1_000_000
430
+
431
+ def exponential(x, prec)
432
+ raise Overflow, "evalf: exp(#{x.to_f}) is beyond arbitrary precision" if x.abs > EXP_LIMIT
433
+ BigMath.exp(x, prec)
434
+ end
435
+
436
+ def positive!(x, node)
437
+ raise Unsupported, "evalf: log of #{node.args.first} is not real" unless x.positive?
438
+ true
439
+ end
440
+
441
+ # asin(x) = atan(x/sqrt(1 - x**2)), and the two ends by hand.
442
+ def arcsin(x, prec, node)
443
+ raise Unsupported, "evalf: #{node} is not real" if x.abs > 1
444
+ return BigMath.PI(prec).div(2, prec).mult(x <=> 0, prec) if x.abs == 1
445
+ BigMath.atan(x.div((BigDecimal(1) - x.mult(x, prec)).sqrt(prec), prec), prec)
446
+ end
447
+
448
+ # gamma and factorial of a whole number are exact integers; anything
449
+ # else would need Lanczos or Spouge at this precision.
450
+ def whole_factorial(x, name, node)
451
+ n = x.to_i
452
+ n -= 1 if name == :gamma
453
+ raise Unsupported, "evalf: #{node} is only available for whole numbers" unless x.frac.zero? && n >= 0
454
+ BigDecimal((1..n).reduce(1, :*))
455
+ end
456
+
457
+ # ---- the functions BigMath does not have ---------------------------------
458
+
459
+ # An alternating series whose terms grow to e**growth before they shrink
460
+ # cancels away that many digits; the working precision has to make them
461
+ # up. Past MAX_CANCELLATION the series is the wrong method and saying so
462
+ # is better than running it.
463
+ MAX_CANCELLATION = 400
464
+
465
+ def cancellation(growth, prec)
466
+ extra = (growth * Math.log10(Math::E)).ceil
467
+ if extra > MAX_CANCELLATION
468
+ raise Unsupported, "evalf: the argument is too large for the series (it would cancel " \
469
+ "#{extra} digits away); the asymptotic expansion is not implemented"
470
+ end
471
+ prec + [extra, 0].max + 5
472
+ end
473
+
474
+ def tolerance(prec) = BigDecimal("1e-#{prec}")
475
+
476
+ # Euler's constant by Brent and McMillan's algorithm B1 [BM80]: the two
477
+ # Bessel-like sums U and V, whose ratio is gamma up to exp(-4*n).
478
+ def euler_gamma(prec)
479
+ work = prec + 10
480
+ n = (work * Math.log(10) / 4).ceil + 1
481
+ squared = BigDecimal(n * n)
482
+ a = -BigMath.log(BigDecimal(n), work)
483
+ b = BigDecimal(1)
484
+ u = a
485
+ v = b
486
+ k = 1
487
+ limit = 20 * n + 100
488
+ loop do
489
+ b = b.mult(squared, work).div(k * k, work)
490
+ a = (a.mult(squared, work).div(k, work) + b).div(k, work)
491
+ u += a
492
+ v += b
493
+ break if k > n && a.abs < tolerance(work) * u.abs
494
+ k += 1
495
+ break if k > limit
496
+ end
497
+ u.div(v, work)
498
+ end
499
+
500
+ def special(name, x, prec, node)
501
+ case name
502
+ when :erf then erf(x, prec)
503
+ when :erfc then x > 3 ? erfc_fraction(x, prec) : BigDecimal(1) - erf(x, prec)
504
+ when :Si then sine_integral(x, prec)
505
+ when :Ci then cosine_integral(x, prec, node)
506
+ when :Ei then exponential_integral(x, prec, node)
507
+ when :li then logarithmic_integral(x, prec, node)
508
+ when :zeta then zeta(x, prec, node)
509
+ end
510
+ end
511
+
512
+ # erf(x) = 2/sqrt(pi) * sum (-1)**n x**(2n+1)/(n!*(2n+1)), which is the
513
+ # whole story until the cancellation bites; beyond the point where erfc
514
+ # is below the last digit, erf is 1.
515
+ def erf(x, prec)
516
+ return -erf(-x, prec) if x.negative?
517
+ return BigDecimal(1) if x > Math.sqrt((prec + 5) * Math.log(10))
518
+ work = cancellation(x.to_f**2, prec)
519
+ squared = x.mult(x, work)
520
+ term = x
521
+ sum = x
522
+ n = 1
523
+ loop do
524
+ term = term.mult(squared, work).div(n, work)
525
+ piece = term.div(2 * n + 1, work)
526
+ sum += n.odd? ? -piece : piece
527
+ break if piece.abs < tolerance(work)
528
+ n += 1
529
+ end
530
+ sum.mult(2, work).div(BigMath.PI(work).sqrt(work), work)
531
+ end
532
+
533
+ # Si(x) = sum (-1)**k x**(2k+1)/((2k+1)*(2k+1)!)
534
+ def sine_integral(x, prec)
535
+ return -sine_integral(-x, prec) if x.negative?
536
+ work = cancellation(x.to_f, prec)
537
+ squared = x.mult(x, work)
538
+ term = x
539
+ sum = x
540
+ k = 1
541
+ loop do
542
+ term = term.mult(squared, work).div((2 * k) * (2 * k + 1), work)
543
+ piece = term.div(2 * k + 1, work)
544
+ sum += k.odd? ? -piece : piece
545
+ break if piece.abs < tolerance(work)
546
+ k += 1
547
+ end
548
+ sum
549
+ end
550
+
551
+ # Ci(x) = gamma + log(x) + sum (-1)**k x**(2k)/(2k*(2k)!)
552
+ def cosine_integral(x, prec, node)
553
+ raise Unsupported, "evalf: #{node} is real only for a positive argument" unless x.positive?
554
+ work = cancellation(x.to_f, prec)
555
+ squared = x.mult(x, work)
556
+ term = BigDecimal(1)
557
+ sum = BigDecimal(0)
558
+ k = 1
559
+ loop do
560
+ term = term.mult(squared, work).div((2 * k - 1) * (2 * k), work)
561
+ piece = term.div(2 * k, work)
562
+ sum += k.odd? ? -piece : piece
563
+ break if piece.abs < tolerance(work)
564
+ k += 1
565
+ end
566
+ euler_gamma(work) + BigMath.log(x, work) + sum
567
+ end
568
+
569
+ # erfc(x) = exp(-x**2)/sqrt(pi) / (x + (1/2)/(x + 1/(x + (3/2)/(x + ...))))
570
+ # [AS64, 7.1.14], by the modified Lentz method: no 1 - erf(x) to cancel,
571
+ # so erfc(12) and erfc(28) have all their digits (fourth review, P-3).
572
+ def erfc_fraction(x, prec)
573
+ work = prec + GUARD
574
+ tiny = BigDecimal("1e-#{3 * work}")
575
+ f = x
576
+ c = x
577
+ d = BigDecimal(0)
578
+ n = 1
579
+ loop do
580
+ a = BigDecimal(n).div(2, work)
581
+ d = x + a.mult(d, work)
582
+ d = tiny if d.zero?
583
+ d = BigDecimal(1).div(d, work)
584
+ c = x + a.div(c, work)
585
+ c = tiny if c.zero?
586
+ delta = c.mult(d, work)
587
+ f = f.mult(delta, work)
588
+ break if (delta - 1).abs < tolerance(work)
589
+ n += 1
590
+ raise NoConvergence, "evalf: the erfc continued fraction did not settle at #{x.to_f}" if n > MAX_TERMS
591
+ end
592
+ exponential(-x.mult(x, work), work).div(BigMath.PI(work).sqrt(work).mult(f, work), work)
593
+ end
594
+
595
+ # E1(z) = exp(-z) / (z + 1 - 1/(z + 3 - 4/(z + 5 - 9/(z + 7 - ...)))) for
596
+ # z > 0 [AS64, 5.1.22], Lentz again: Ei(-700) = -E1(700) is 1.4e-307,
597
+ # which the power series reaches only through 600 cancelled digits.
598
+ def exponential_integral_fraction(z, prec)
599
+ work = prec + GUARD
600
+ tiny = BigDecimal("1e-#{3 * work}")
601
+ f = z + 1
602
+ c = f
603
+ d = BigDecimal(0)
604
+ n = 1
605
+ loop do
606
+ a = BigDecimal(-(n * n))
607
+ b = z + 2 * n + 1
608
+ d = b + a.mult(d, work)
609
+ d = tiny if d.zero?
610
+ d = BigDecimal(1).div(d, work)
611
+ c = b + a.div(c, work)
612
+ c = tiny if c.zero?
613
+ delta = c.mult(d, work)
614
+ f = f.mult(delta, work)
615
+ break if (delta - 1).abs < tolerance(work)
616
+ n += 1
617
+ raise NoConvergence, "evalf: the E1 continued fraction did not settle at #{z.to_f}" if n > MAX_TERMS
618
+ end
619
+ exponential(-z, work).div(f, work)
620
+ end
621
+
622
+ # Ei(x) = gamma + log|x| + sum x**k/(k*k!)
623
+ def exponential_integral(x, prec, node)
624
+ raise Unsupported, "evalf: #{node} is infinite at 0" if x.zero?
625
+ return -exponential_integral_fraction(-x, prec) if x < -4
626
+ work = cancellation(x.negative? ? x.abs.to_f : 0.0, prec)
627
+ term = BigDecimal(1)
628
+ sum = BigDecimal(0)
629
+ k = 1
630
+ loop do
631
+ term = term.mult(x, work).div(k, work)
632
+ piece = term.div(k, work)
633
+ sum += piece
634
+ break if piece.abs < tolerance(work) && k > x.abs
635
+ k += 1
636
+ end
637
+ euler_gamma(work) + BigMath.log(x.abs, work) + sum
638
+ end
639
+
640
+ def logarithmic_integral(x, prec, node)
641
+ raise Unsupported, "evalf: #{node} needs a positive argument" unless x.positive?
642
+ return BigDecimal(0) if x.zero?
643
+ raise Unsupported, "evalf: li(1) is infinite" if x == 1
644
+ exponential_integral(BigMath.log(x, prec + GUARD), prec, node)
645
+ end
646
+
647
+ # zeta(s) for a whole s > 1: the even ones are a rational multiple of
648
+ # pi**s, the odd ones come from Euler-Maclaurin [AS64, §23.2] with the
649
+ # exact Bernoulli numbers rcas already has.
650
+ def zeta(s, prec, node)
651
+ unless s.frac.zero? && s > 1
652
+ raise Unsupported, "evalf: #{node} is only available for a whole s > 1"
653
+ end
654
+ s = s.to_i
655
+ work = prec + GUARD
656
+ return even_zeta(s, work) if s.even?
657
+ n = [prec, 20].max
658
+ head = (1...n).reduce(BigDecimal(0)) { |acc, i| acc + BigDecimal(1).div(BigDecimal(i)**s, work) }
659
+ power = BigDecimal(n)**s
660
+ total = head + BigDecimal(n).div(power.mult(s - 1, work), work) + BigDecimal(1).div(power.mult(2, work), work)
661
+ product = BigDecimal(s)
662
+ (1..work).each do |k|
663
+ bernoulli = Summation.bernoulli(2 * k)
664
+ piece = BigDecimal(bernoulli.numerator).div(bernoulli.denominator, work)
665
+ .mult(product, work)
666
+ .div(factorial(2 * k).mult(power.mult(BigDecimal(n)**(2 * k - 1), work), work), work)
667
+ total += piece
668
+ break if piece.abs < tolerance(work)
669
+ product = product.mult((s + 2 * k - 1) * (s + 2 * k), work)
670
+ end
671
+ total
672
+ end
673
+
674
+ def even_zeta(s, work)
675
+ value = Summation.zeta_even(s) # a rational times pi**s
676
+ coefficient, = Simplify.factorize(value)
677
+ BigDecimal(coefficient.numerator).div(coefficient.denominator, work).mult(BigMath.PI(work)**s, work)
678
+ end
679
+
680
+ def factorial(n) = BigDecimal((1..n).reduce(1, :*))
681
+
682
+ # ---- quadrature ----------------------------------------------------------
683
+
684
+ MAX_LEVELS = 8
685
+
686
+ # Double-exponential (tanh-sinh) quadrature [TM74]: the substitution
687
+ # x = tanh(pi/2*sinh(t)) makes the integrand and all its derivatives die
688
+ # away so fast at the ends that the trapezoidal rule in t converges
689
+ # doubly exponentially - and an endpoint singularity is smothered with
690
+ # them. The two infinite ranges use exp(pi/2*sinh(t)) and
691
+ # sinh(pi/2*sinh(t)) in the same skeleton.
692
+ def quadrature(integrand, var, from, to, prec, bindings = {}, state = { limit: prec })
693
+ work = prec + 2 * GUARD
694
+ map = transformation(from, to, work, bindings, state)
695
+ f = ->(point) { walk(integrand, work, bindings.merge(var.name => point), state) }
696
+ half = BigMath.PI(work).div(2, work)
697
+ step = BigDecimal(1)
698
+ total = level_sum(f, map, half, step, work, 0)
699
+ previous = nil
700
+ (1..MAX_LEVELS).each do |level|
701
+ step = step.div(2, work)
702
+ # halving the step keeps every point of the level before it
703
+ total = total.div(2, work) + level_sum(f, map, half, step, work, 1)
704
+ settled = previous && (total - previous).abs < tolerance(prec) * [total.abs, BigDecimal(1)].max
705
+ return total.mult(1, prec) if settled && level > 1
706
+ previous = total
707
+ end
708
+ raise NoConvergence, "evalf: the quadrature did not settle to #{prec} digits; " \
709
+ "the integral may diverge, or oscillate faster than the rule resolves"
710
+ end
711
+
712
+ # One trapezoidal sum in t, over every k (parity 0) or only the odd ones
713
+ # (parity 1), stopping when the weights have died away.
714
+ def level_sum(f, map, half, step, work, parity)
715
+ total = BigDecimal(0)
716
+ k = parity.zero? ? 0 : 1
717
+ loop do
718
+ contribution = BigDecimal(0)
719
+ [1, -1].each do |sign|
720
+ next if k.zero? && sign.negative?
721
+ t = step.mult(sign * k, work)
722
+ point, weight = map.call(t, half, work)
723
+ next if weight.zero?
724
+ value = begin
725
+ f.call(point)
726
+ rescue Overflow
727
+ next
728
+ rescue ZeroDivisionError, Unsupported
729
+ # at the very ends the point can round onto a singular end, and
730
+ # the weight there is below any precision; inside the range an
731
+ # undefined sample is a hole or a pole, and skipping it made
732
+ # the integral of 1/x over -1..1 come out as 0
733
+ next if weight.abs < tolerance(work)
734
+ hole(f, point, work)
735
+ end
736
+ contribution += weight.mult(value, work)
737
+ end
738
+ total += contribution
739
+ break if k.positive? && contribution.abs < tolerance(work) && k * step > 2
740
+ k += parity.zero? ? 1 : 2
741
+ break if k * step > 8 # the weights are below any precision by here
742
+ end
743
+ total.mult(step, work)
744
+ end
745
+
746
+ # The value at a point where the integrand has none, when the point is a
747
+ # removable hole (sin(x)/x at 0): the samples just either side agree.
748
+ # Anything else - a pole, a jump - is no integrand this rule can take.
749
+ def hole(f, point, work)
750
+ eps = BigDecimal("1e-#{work / 2}") * [point.abs, BigDecimal(1)].max
751
+ left = f.call(point - eps)
752
+ right = f.call(point + eps)
753
+ scale = [left.abs, right.abs, BigDecimal(1)].max
754
+ return (left + right).div(2, work) if (left - right).abs <= scale * BigDecimal("1e-#{work / 4}")
755
+ raise NoConvergence, "evalf: the integrand has no value at #{point.round(12).to_s('F')} inside the range"
756
+ rescue ZeroDivisionError
757
+ raise NoConvergence, "evalf: the integrand has no value near #{point.round(12).to_s('F')} inside the range"
758
+ end
759
+
760
+ # [point, weight] as a function of t, for the three shapes of range.
761
+ def transformation(from, to, work, bindings, state)
762
+ lower = Limits.infinite?(from)
763
+ upper = Limits.infinite?(to)
764
+ if lower && upper then sinh_sinh
765
+ elsif upper then exp_sinh(walk(from, work, bindings, state), 1)
766
+ elsif lower then exp_sinh(walk(to, work, bindings, state), -1)
767
+ else finite(walk(from, work, bindings, state), walk(to, work, bindings, state))
768
+ end
769
+ end
770
+
771
+ # The point is measured from the near end, never as centre + span*tanh(u):
772
+ # 1 - tanh(u) is 2/(1 + exp(2*u)) and loses nothing, while the difference
773
+ # would cancel away every digit that an endpoint singularity needs.
774
+ def finite(a, b)
775
+ lambda do |t, half, work|
776
+ span = (b - a).div(2, work)
777
+ u = half.mult(sinh(t, work), work)
778
+ next [a, BigDecimal(0)] if u.abs > EXP_LIMIT
779
+ gap = span.mult(2, work).div(BigDecimal(1) + BigMath.exp(u.mult(2, work).abs, work), work)
780
+ point = t.negative? ? a + gap : b - gap
781
+ [point, half.mult(cosh(t, work), work).div(cosh(u, work)**2, work).mult(span, work)]
782
+ end
783
+ end
784
+
785
+ def exp_sinh(edge, direction)
786
+ lambda do |t, half, work|
787
+ u = half.mult(sinh(t, work), work)
788
+ next [edge, BigDecimal(0)] if u.abs > EXP_LIMIT || u > Math.log(10) * (work + 30)
789
+ growth = BigMath.exp(u, work)
790
+ [edge + growth.mult(direction, work), half.mult(cosh(t, work), work).mult(growth, work)]
791
+ end
792
+ end
793
+
794
+ def sinh_sinh
795
+ lambda do |t, half, work|
796
+ u = half.mult(sinh(t, work), work)
797
+ next [BigDecimal(0), BigDecimal(0)] if u.abs > Math.log(10) * (work + 30)
798
+ [sinh(u, work), half.mult(cosh(t, work), work).mult(cosh(u, work), work)]
799
+ end
800
+ end
801
+
802
+ def sinh(t, work) = (exponential(t, work) - exponential(-t, work)).div(2, work)
803
+ def cosh(t, work) = (exponential(t, work) + exponential(-t, work)).div(2, work)
804
+
805
+ def tanh(t, work)
806
+ up = BigMath.exp(t, work)
807
+ down = BigMath.exp(-t, work)
808
+ (up - down).div(up + down, work)
809
+ end
810
+
811
+ # ---- the rest -----------------------------------------------------------
812
+
813
+ # A real root of a polynomial, refined from its double-precision value
814
+ # by Newton's method: the number of correct digits doubles each step.
815
+ def root_of(node, prec)
816
+ raise Unsupported, "evalf: #{node} is not real" unless node.real?
817
+ coefficients = (0..node.poly.degree).map { |k| Rational(node.poly.coeff(k)) }
818
+ derivative = coefficients.each_with_index.drop(1).map { |c, k| c * k }
819
+ x = BigDecimal(node.value, FLOAT_DIGITS)
820
+ tolerance = BigDecimal("1e-#{prec}")
821
+ MAX_NEWTON.times do
822
+ slope = horner(derivative, x, prec)
823
+ break if slope.zero?
824
+ step = horner(coefficients, x, prec).div(slope, prec)
825
+ x -= step
826
+ break if step.abs < tolerance
827
+ end
828
+ x
829
+ end
830
+
831
+ def horner(coefficients, x, prec)
832
+ coefficients.reverse.reduce(BigDecimal(0)) do |acc, c|
833
+ acc.mult(x, prec) + BigDecimal(c.numerator).div(c.denominator, prec)
834
+ end
835
+ end
836
+
837
+ # A definite integral is quadrature; an indefinite one is not a number.
838
+ def integral(node, prec, bindings, state)
839
+ unsupported!("indefinite integral", node) unless node.definite?
840
+ quadrature(node.integrand, node.var, node.from, node.to, prec, bindings, state)
841
+ end
842
+
843
+ # A root to the digits asked for: Newton from the double-precision one,
844
+ # or the secant method when there is no derivative to be had [PTVF07,
845
+ # §9.4, §9.2]. The number of correct digits doubles at every step, so a
846
+ # handful of them is the whole cost.
847
+ # Refined at two working precisions and reported with the digits the
848
+ # two agree on, raising the precision until those are the digits asked
849
+ # for: at a double root f is only known to half the working digits,
850
+ # and a single run claimed 30 digits where 25 were right (P-3).
851
+ def refine(expr, var, guess, digits, bindings = {})
852
+ previous = nil
853
+ [digits + 2 * GUARD, 2 * digits + 2 * GUARD, 4 * digits + 2 * GUARD, 8 * digits + 2 * GUARD].each do |work|
854
+ x = refine_at(expr, var, guess, work, bindings)
855
+ return Decimal.new(x.mult(1, digits), digits) if previous && agree?(x, previous, digits)
856
+ previous = x
857
+ end
858
+ raise NoConvergence, "nsolve: the root could not be certified to #{digits} digits"
859
+ end
860
+
861
+ def refine_at(expr, var, guess, work, bindings)
862
+ state = { limit: work }
863
+ f = ->(point) { walk(expr, work, bindings.merge(var.name => point), state) }
864
+ slope = begin
865
+ derivative = Expression.lift(expr).diff(var)
866
+ ->(point) { walk(derivative, work, bindings.merge(var.name => point), state) }
867
+ rescue StandardError => rescued
868
+ RCAS.guard!(rescued)
869
+ nil
870
+ end
871
+ x = BigDecimal(guess, FLOAT_DIGITS)
872
+ slope ? newton_steps(f, slope, x, work) : secant_steps(f, x, work)
873
+ end
874
+
875
+ def newton_steps(f, slope, x, work)
876
+ MAX_NEWTON.times do
877
+ divisor = slope.call(x)
878
+ raise Unsupported, "evalf: the derivative is zero at the root" if divisor.zero?
879
+ step = f.call(x).div(divisor, work)
880
+ x -= step
881
+ break if step.abs < tolerance(work) * [x.abs, BigDecimal(1)].max
882
+ end
883
+ x
884
+ end
885
+
886
+ def secant_steps(f, x, work)
887
+ previous = x + tolerance(FLOAT_DIGITS)
888
+ before = f.call(previous)
889
+ MAX_NEWTON.times do
890
+ value = f.call(x)
891
+ divisor = value - before
892
+ break if divisor.zero?
893
+ step = value.mult(x - previous, work).div(divisor, work)
894
+ previous = x
895
+ before = value
896
+ x -= step
897
+ break if step.abs < tolerance(work) * [x.abs, BigDecimal(1)].max
898
+ end
899
+ x
900
+ end
901
+
902
+ # A sum with whole bounds, term by term.
903
+ def series_sum(node, prec, bindings, state)
904
+ from = walk(node.from, prec, bindings, state)
905
+ to = walk(node.to, prec, bindings, state)
906
+ unless from.frac.zero? && to.frac.zero? && from.finite? && to.finite?
907
+ unsupported!("sum", node)
908
+ end
909
+ count = to.to_i - from.to_i + 1
910
+ raise Unsupported, "evalf: #{count} terms is too many for #{node}" if count > MAX_TERMS
911
+ (from.to_i..to.to_i).reduce(BigDecimal(0)) do |acc, k|
912
+ acc + walk(node.term, prec, bindings.merge(node.var.name => k), state)
913
+ end
914
+ end
915
+
916
+ # The branch is chosen the ordinary way and then evaluated to the digits
917
+ # asked for; only a point exactly on a breakpoint could be decided
918
+ # differently at higher precision.
919
+ def piecewise(node, prec, bindings, state)
920
+ chosen = node.subs(bindings.to_h { |name, value| [Var.new(name), Expression.lift(value)] }).simplify
921
+ unsupported!("piecewise", node) if chosen.is_a?(Piecewise)
922
+ walk(chosen, prec, bindings, state)
923
+ end
924
+ end
925
+ end