rcas 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CITATION.cff +17 -0
- data/DESIGN.md +783 -0
- data/LICENSE +21 -0
- data/MANUAL.md +6265 -0
- data/README.md +267 -0
- data/bin/rcas +9 -0
- data/bin/rcas-app +9 -0
- data/bin/rcas-chat +9 -0
- data/lib/rcas/algebraic.rb +481 -0
- data/lib/rcas/analysis.rb +966 -0
- data/lib/rcas/app/launcher.rb +203 -0
- data/lib/rcas/app/public/app.css +402 -0
- data/lib/rcas/app/public/app.js +449 -0
- data/lib/rcas/app/public/index.html +46 -0
- data/lib/rcas/app/server.rb +220 -0
- data/lib/rcas/app/window.rb +94 -0
- data/lib/rcas/app/worksheet.rb +290 -0
- data/lib/rcas/app.rb +168 -0
- data/lib/rcas/background.rb +758 -0
- data/lib/rcas/chat/assistant.rb +199 -0
- data/lib/rcas/chat/picker.rb +164 -0
- data/lib/rcas/chat/repl.rb +583 -0
- data/lib/rcas/chat/session.rb +137 -0
- data/lib/rcas/chat/settings.rb +71 -0
- data/lib/rcas/chat/style.rb +30 -0
- data/lib/rcas/chat/tool.rb +53 -0
- data/lib/rcas/chat/ui.rb +316 -0
- data/lib/rcas/chat/usage.rb +62 -0
- data/lib/rcas/chat/workspace.rb +132 -0
- data/lib/rcas/chat.rb +54 -0
- data/lib/rcas/coefficients.rb +170 -0
- data/lib/rcas/combinatorics.rb +274 -0
- data/lib/rcas/complex_parts.rb +160 -0
- data/lib/rcas/constants.rb +129 -0
- data/lib/rcas/core_ext.rb +35 -0
- data/lib/rcas/decide.rb +501 -0
- data/lib/rcas/decompositions.rb +241 -0
- data/lib/rcas/differentiate.rb +144 -0
- data/lib/rcas/discussion.rb +558 -0
- data/lib/rcas/distributions.rb +980 -0
- data/lib/rcas/dixon.rb +95 -0
- data/lib/rcas/docs.rb +321 -0
- data/lib/rcas/domains.rb +728 -0
- data/lib/rcas/expand.rb +174 -0
- data/lib/rcas/expression.rb +613 -0
- data/lib/rcas/factor.rb +605 -0
- data/lib/rcas/finite_field.rb +577 -0
- data/lib/rcas/fourier.rb +118 -0
- data/lib/rcas/fps.rb +678 -0
- data/lib/rcas/fraction.rb +126 -0
- data/lib/rcas/functions.rb +1136 -0
- data/lib/rcas/gcd.rb +112 -0
- data/lib/rcas/geometry.rb +266 -0
- data/lib/rcas/groebner.rb +162 -0
- data/lib/rcas/hold.rb +277 -0
- data/lib/rcas/hypothesis.rb +364 -0
- data/lib/rcas/inequalities.rb +689 -0
- data/lib/rcas/integral_functions.rb +260 -0
- data/lib/rcas/integrate.rb +1589 -0
- data/lib/rcas/integrate_substitutions.rb +434 -0
- data/lib/rcas/interpolate.rb +40 -0
- data/lib/rcas/irb.rb +146 -0
- data/lib/rcas/laplace.rb +159 -0
- data/lib/rcas/latex.rb +556 -0
- data/lib/rcas/lattice.rb +172 -0
- data/lib/rcas/linear_algebra.rb +117 -0
- data/lib/rcas/linear_program.rb +416 -0
- data/lib/rcas/lint.rb +79 -0
- data/lib/rcas/matrix.rb +531 -0
- data/lib/rcas/matrix_multiply.rb +202 -0
- data/lib/rcas/multimodular.rb +286 -0
- data/lib/rcas/named_polynomials.rb +274 -0
- data/lib/rcas/number_theory.rb +443 -0
- data/lib/rcas/numerics.rb +825 -0
- data/lib/rcas/ode.rb +488 -0
- data/lib/rcas/openmath/objects.rb +364 -0
- data/lib/rcas/openmath/phrasebook.rb +551 -0
- data/lib/rcas/openmath/popcorn.rb +518 -0
- data/lib/rcas/openmath/xml.rb +309 -0
- data/lib/rcas/openmath.rb +49 -0
- data/lib/rcas/petkovsek.rb +165 -0
- data/lib/rcas/piecewise.rb +488 -0
- data/lib/rcas/plot.rb +763 -0
- data/lib/rcas/plot3d.rb +419 -0
- data/lib/rcas/poly_matrix.rb +318 -0
- data/lib/rcas/poly_recurrence.rb +117 -0
- data/lib/rcas/polynomial.rb +466 -0
- data/lib/rcas/precision.rb +925 -0
- data/lib/rcas/printer.rb +150 -0
- data/lib/rcas/product.rb +155 -0
- data/lib/rcas/q_difference.rb +296 -0
- data/lib/rcas/q_functions.rb +158 -0
- data/lib/rcas/q_summation.rb +308 -0
- data/lib/rcas/q_zeilberger.rb +199 -0
- data/lib/rcas/random.rb +506 -0
- data/lib/rcas/rational_function.rb +186 -0
- data/lib/rcas/recurrence.rb +323 -0
- data/lib/rcas/render.rb +431 -0
- data/lib/rcas/results.rb +192 -0
- data/lib/rcas/scalar.rb +219 -0
- data/lib/rcas/series.rb +726 -0
- data/lib/rcas/simplify.rb +649 -0
- data/lib/rcas/solve.rb +2002 -0
- data/lib/rcas/special.rb +163 -0
- data/lib/rcas/statistics.rb +175 -0
- data/lib/rcas/steps.rb +835 -0
- data/lib/rcas/summation.rb +532 -0
- data/lib/rcas/trig.rb +264 -0
- data/lib/rcas/van_hoeij.rb +241 -0
- data/lib/rcas/vector.rb +175 -0
- data/lib/rcas/vector_calculus.rb +411 -0
- data/lib/rcas/version.rb +5 -0
- data/lib/rcas/zeilberger.rb +358 -0
- data/lib/rcas.rb +91 -0
- data/package.json +8 -0
- metadata +206 -0
|
@@ -0,0 +1,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
|