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,501 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RCAS
4
+ # Questions about a constant expression - is it zero, what is its sign -
5
+ # answered by a proof or not at all.
6
+ #
7
+ # Decide.sign(exp(-100)) # => :positive
8
+ # Decide.zero?(I*exp(-40)) # => false
9
+ # Decide.zero?(sqrt(2)**2 - 2) # => true
10
+ # Decide.sign(exp(pi*sqrt(163)) - 640320**3 - 744) # => :negative
11
+ #
12
+ # The routes, in order: a number is read off; a constant in one or two
13
+ # radicals is decided exactly (Algebraic.exact); anything else is
14
+ # evaluated at two precisions (Precision.evalf, 30 and 60 digits, then 60
15
+ # and 120), which tells a true zero - cancellation, shrinking as the
16
+ # digits rise - from a small number that stays where it is; and last a
17
+ # Float, trusted only when it stands well clear of the rounding its own
18
+ # largest intermediate value could have produced. A complex constant is
19
+ # split into its real and imaginary parts (ComplexParts) and each is
20
+ # decided on its own.
21
+ #
22
+ # nil means undecided, and a caller has to treat it as such: "no sign
23
+ # found" is not "positive", and "not shown to be zero" is not "non-zero".
24
+ # This is the one place where a numeric test may settle an exact
25
+ # question; the third review (22 Sept 2026) found a dozen private copies
26
+ # with absolute tolerances, each wrong somewhere.
27
+ module Decide
28
+ module_function
29
+
30
+ LEVELS = [30, 60, 120].freeze
31
+ # A zero shrinks by the ratio of the precisions (1e-30 per step); a
32
+ # value that shrinks by less than this is not cancellation.
33
+ SHRINK = BigDecimal("1e-20")
34
+ # Two precisions agree on a value when they differ by less than this
35
+ # relative amount.
36
+ AGREE = BigDecimal("1e-10")
37
+
38
+ # :positive, :negative or :zero for a real constant; nil when that is
39
+ # not decided (not constant, not real, or beyond the routes above).
40
+ def sign(e)
41
+ e = Expression.lift(e)
42
+ return nil unless decidable?(e)
43
+ return number_sign(e.value) if e.is_a?(Num)
44
+ exact = exact_value(e)
45
+ return :zero if exact&.zero?
46
+ found, value, bound = precise(e)
47
+ return found if SIGNS.include?(found)
48
+ if found == :unsupported
49
+ found = float_sign(e)
50
+ return found if found
51
+ end
52
+ # every evaluation was consistent with 0, and no evaluation can
53
+ # prove that: a root separation bound or a normal form can
54
+ return :zero if value && algebraic_zero?(e, value, bound)
55
+ :zero if exact.nil? && symbolic_zero?(e)
56
+ end
57
+
58
+ SIGNS = %i[positive negative].freeze
59
+
60
+ # true, false, or nil when undecided. Complex constants are welcome.
61
+ def zero?(e)
62
+ e = Expression.lift(e)
63
+ return nil unless decidable?(e)
64
+ return e.value.zero? if e.is_a?(Num) && e.value.is_a?(Numeric)
65
+ exact = exact_value(e)
66
+ return exact.zero? if exact
67
+ if complex?(e)
68
+ re, im = begin
69
+ ComplexParts.parts(e)
70
+ rescue StandardError => rescued
71
+ RCAS.guard!(rescued)
72
+ [nil, nil]
73
+ end
74
+ if re && !parts_symbolic?(re) && !parts_symbolic?(im)
75
+ signs = [sign(re), sign(im)]
76
+ return false if signs.any? { |s| s && s != :zero }
77
+ return true if signs.all?(:zero)
78
+ return nil
79
+ end
80
+ return complex_zero?(e)
81
+ end
82
+ s = sign(e)
83
+ return s == :zero if s
84
+ return false if annihilator_at_zero(e) == :nonzero
85
+ complex_zero?(e) # log(-1) is complex without an i in sight
86
+ end
87
+
88
+ def complex_zero?(e)
89
+ verdict = complex_float_zero?(e)
90
+ return verdict unless verdict.nil?
91
+ symbolic_zero?(e) ? true : nil
92
+ end
93
+
94
+ # An expression in indeterminates that no normal form reduces
95
+ # (gamma(1 - a)/gamma(-a) + a): true when it vanishes at every one of
96
+ # several random rational points, each decided exactly; false as soon
97
+ # as one point shows it does not; nil otherwise. A point that is a pole
98
+ # or cannot be decided is no evidence for zero, and a fresh one is
99
+ # drawn in its place. The denominators are primes above 100: with
100
+ # denominators 2..11, sin(30*pi*x) vanished at all four points, and one
101
+ # decided zero among poles was enough for a true (the sixth review's
102
+ # preflight). A nonzero rational function vanishing at four random
103
+ # rational points is not a risk worth naming; a Float residue under
104
+ # 1e-9 was, and four copies of that test with their own seeds and
105
+ # tolerances were one of the third review's duplications (section 5).
106
+ SAMPLE_DENOMINATORS = [101, 103, 107, 109, 113, 127, 131, 137, 139, 149].freeze
107
+
108
+ def identically_zero?(expr, points: 4, seed: 20260922)
109
+ e = Expression.lift(expr)
110
+ return zero?(e) if e.variables.empty?
111
+ return false if Scalar.nonzero_somewhere?(e)
112
+ return true if Scalar.identically_zero?(e) || factorial_zero?(e)
113
+ random = Random.new(seed)
114
+ decided = 0
115
+ (3 * points).times do
116
+ point = e.variables.to_h { |name| [name, Num.new(Rational(random.rand(3..997), SAMPLE_DENOMINATORS.sample(random: random)))] }
117
+ value = begin
118
+ e.subs(point).simplify
119
+ rescue ZeroDivisionError
120
+ next # a pole: this point says nothing
121
+ end
122
+ verdict = zero?(value)
123
+ return false if verdict == false
124
+ decided += 1 if verdict
125
+ return true if decided == points
126
+ end
127
+ nil
128
+ rescue StandardError, NotImplementedError, RCAS::Unsupported => rescued
129
+ RCAS.guard!(rescued, refused: true) if rescued.is_a?(StandardError)
130
+ nil
131
+ end
132
+
133
+ def positive?(e) = sign(e) == :positive
134
+ def negative?(e) = sign(e) == :negative
135
+ def nonzero?(e) = zero?(e) == false
136
+
137
+ # ---- routes -------------------------------------------------------------
138
+
139
+ def decidable?(e)
140
+ e.is_a?(Expression) && e.variables.empty? &&
141
+ e.each_node.none? { |n| n.is_a?(Integral) || n.is_a?(Derivative) || n.is_a?(Limit) || n == UNDEFINED }
142
+ end
143
+
144
+ def number_sign(v)
145
+ return nil unless v.is_a?(Numeric) && v.real?
146
+ return nil if v.respond_to?(:nan?) && v.nan?
147
+ return :zero if v.zero?
148
+ v.positive? ? :positive : :negative
149
+ end
150
+
151
+ def exact_value(e)
152
+ Algebraic.exact(e)
153
+ rescue StandardError => rescued
154
+ RCAS.guard!(rescued)
155
+ nil
156
+ end
157
+
158
+ def complex?(e)
159
+ e.each_node.any? { |n| n.is_a?(Num) && n.value.is_a?(Complex) }
160
+ end
161
+
162
+ def parts_symbolic?(part) = part.each_node.any? { |n| n.is_a?(Fn) && %i[re im].include?(n.name) }
163
+
164
+ # :positive or :negative when two precisions agree on a value well above
165
+ # the rounding error of the largest magnitude in the walk (a value that
166
+ # is smaller could be what is left of an exact 0 whose terms each
167
+ # rounded); nil when every level is consistent with 0 - which is not a
168
+ # proof of 0 (exp(10**-100) - 1 is exactly 0 at 30 and 60 digits); and
169
+ # :unsupported when Precision cannot evaluate the expression at all, or
170
+ # only to the digits of a Float inside it.
171
+ def precise_sign(e) = precise(e).first
172
+
173
+ # [verdict, value, bound] - the last value and its error bound come
174
+ # along for algebraic_zero?
175
+ def precise(e)
176
+ previous = nil
177
+ LEVELS.each do |digits|
178
+ value, scale, keep = begin
179
+ Precision.evalf_with_scale(e, digits)
180
+ rescue ZeroDivisionError
181
+ return nil
182
+ rescue StandardError, NotImplementedError, RCAS::Unsupported => rescued
183
+ RCAS.guard!(rescued, refused: true)
184
+ return :unsupported
185
+ end
186
+ return :unsupported if keep < digits
187
+ bound = scale.mult(BigDecimal("1e-#{digits}"), 5)
188
+ if previous
189
+ coarse, coarse_bound = previous
190
+ consistent = (value - coarse).abs <= coarse_bound + bound
191
+ return [value.positive? ? :positive : :negative] if consistent && value.abs > bound
192
+ end
193
+ previous = [value, bound]
194
+ end
195
+ [nil, *previous]
196
+ end
197
+
198
+ # An algebraic constant - rationals, radicals, i, RootOf - is a root of
199
+ # the polynomial Algebraic.annihilator builds. If 0 is not a root the
200
+ # constant is not 0; if it is, the other roots are at least
201
+ # |q0|/(|q0| + max|qi|) away from it (Cauchy's bound for q = p/x**m),
202
+ # and a value evaluated below that distance is 0. That proves
203
+ # sqrt(2)*sqrt(3) - sqrt(6) = 0, which has three radicals and is beyond
204
+ # Algebraic.exact.
205
+ def algebraic_zero?(e, value, bound)
206
+ q = annihilator_at_zero(e)
207
+ return false if q.nil? || q == :nonzero
208
+ separation = Rational(q.first) / (q.first + q.drop(1).max.to_r)
209
+ value.abs + bound < BigDecimal(separation.numerator).div(separation.denominator, 20) / 2
210
+ end
211
+
212
+ # The coefficients of p/x**m (absolute values, constant term first) when
213
+ # 0 is a root of the annihilating polynomial p of an algebraic constant,
214
+ # :nonzero when it is not - a proof that the constant is not 0 - and
215
+ # nil when the constant is not algebraic in that sense or too large.
216
+ def annihilator_at_zero(e)
217
+ return nil unless e.each_node.all? { |n| ALGEBRAIC_NODES.any? { |c| n.is_a?(c) } } && e.each_node.count <= 40
218
+ x = Var.new(:_decide)
219
+ coefficients = Coefficients.coeffs(Algebraic.annihilator(e, x), x)
220
+ return nil unless coefficients.all? { |c| c.is_a?(Num) && (c.value.is_a?(Integer) || c.value.is_a?(Rational)) }
221
+ m = coefficients.index { |c| !c.value.zero? }
222
+ return nil if m.nil?
223
+ return :nonzero if m.zero?
224
+ coefficients.drop(m).map { |c| c.value.abs }
225
+ rescue StandardError, NotImplementedError, RCAS::Unsupported => rescued
226
+ RCAS.guard!(rescued, refused: true) if rescued.is_a?(StandardError)
227
+ nil
228
+ end
229
+
230
+ ALGEBRAIC_NODES = [Num, Add, Sub, Mul, Div, Neg, Pow, RootOf].freeze
231
+
232
+ # A constant that the normal forms reduce to 0: expand, cancel, the
233
+ # Pythagorean identity. Asked only after every evaluation came out
234
+ # consistent with 0, so the cost is paid where a proof is wanted.
235
+ def symbolic_zero?(e)
236
+ return false if Thread.current[:rcas_decide_symbolic] # the normal forms asked us
237
+ begin
238
+ Thread.current[:rcas_decide_symbolic] = true
239
+ simplified = e.simplify
240
+ return true if simplified.is_a?(Num) && simplified.value == 0
241
+ return true if factorial_zero?(e)
242
+ return true if log_zero?(simplified)
243
+ !simplified.is_a?(Num) && Scalar.identically_zero?(simplified)
244
+ ensure
245
+ Thread.current[:rcas_decide_symbolic] = nil
246
+ end
247
+ rescue StandardError => rescued
248
+ RCAS.guard!(rescued)
249
+ false
250
+ end
251
+
252
+ # log(6) - log(2) - log(3) is 0, and neither simplify nor the numerics
253
+ # can say so (the numerics never prove a zero). The log of a positive
254
+ # rational is the sum of the logs of its primes, each counted with its
255
+ # exponent - log(p/q) = log(p) - log(q) holds for positive p and q -
256
+ # and the logs of distinct primes are linearly independent over QQ
257
+ # (unique factorization), so once every such log is written over its
258
+ # primes, simplify merging the like terms to 0 is a proof. A number too
259
+ # hard to factor quickly keeps its log whole, which is still an equal
260
+ # expression, only a less decisive one.
261
+ def log_zero?(e)
262
+ logs = e.each_node.select do |n|
263
+ n.is_a?(Fn) && n.name == :log && n.args.size == 1 && n.args.first.is_a?(Num) &&
264
+ (v = n.args.first.value).is_a?(Numeric) && (v.is_a?(Integer) || v.is_a?(Rational)) && v.positive? && v != 1
265
+ end.uniq
266
+ return false if logs.empty?
267
+ split = logs.to_h do |n|
268
+ value = n.args.first.value.to_r
269
+ terms = [[value.numerator, 1], [value.denominator, -1]].flat_map do |m, sign|
270
+ next [] if m == 1
271
+ primes = NumberTheory.prime_division(m, hard: false)
272
+ next [[m, sign]] unless primes
273
+ primes.map { |p, k| [p, sign * k] }
274
+ end
275
+ [n, terms.map { |p, k| Num.new(k) * Fn.new(:log, [Num.new(p)]) }.reduce(:+)]
276
+ end
277
+ zero_number?(e.subs(split).simplify)
278
+ end
279
+
280
+ # gamma(u) is (u - 1)!, and simplify cancels factorials whose arguments
281
+ # differ by an integer: gamma(-42/5)/gamma(-47/5) + 47/5 is 0 that way
282
+ # and undecided every other way. A sum of factorials is divided by one
283
+ # of them first, which makes every term such a ratio: (1 + k)! - k! -
284
+ # k*k! over k! expands to 0. (It had been "shown" zero by the sampled
285
+ # points that happened to be integers, which was also what called
286
+ # sin(30*pi*x) zero.) A factorial has no zeros, so the quotient
287
+ # vanishes exactly where the sum does.
288
+ def factorial_zero?(e)
289
+ return false unless e.each_node.any? { |n| n.is_a?(Fn) && %i[gamma factorial].include?(n.name) }
290
+ rewritten = as_factorials(e)
291
+ return true if zero_number?(rewritten.simplify)
292
+ rewritten.each_node.select { |n| n.is_a?(Fn) && n.name == :factorial }.uniq.first(3).any? do |f|
293
+ zero_number?(Expand.expand(rewritten / f).simplify)
294
+ end
295
+ end
296
+
297
+ def zero_number?(v) = v.is_a?(Num) && v.value.is_a?(Numeric) && v.value.zero?
298
+
299
+ def as_factorials(e)
300
+ e = e.map_children { |c| as_factorials(c) }
301
+ e.is_a?(Fn) && e.name == :gamma && e.args.size == 1 ? Fn.new(:factorial, [(e.args.first - 1).simplify]) : e
302
+ end
303
+
304
+ def float_sign(e)
305
+ v, error = float_with_error(e)
306
+ return nil unless v.is_a?(Float) && v.abs > ERROR_MARGIN * error
307
+ v.positive? ? :positive : :negative
308
+ end
309
+
310
+ # A complex Float that stands clear of rounding is not zero; nothing
311
+ # smaller is decided.
312
+ def complex_float_zero?(e) = float_nonzero?(e) ? false : nil
313
+
314
+ # The value as a Float or Complex and a bound on its rounding error, in
315
+ # one walk: an exact number is off by half an ulp, a sum by the errors
316
+ # of its terms, a product relatively, a function by its derivative
317
+ # times the error of its argument (a first-order running error bound).
318
+ # A value well above its bound is not rounding. nil when there is no
319
+ # number. (The largest intermediate magnitude stood in for the bound
320
+ # before, which counted the 92160 of sqrt(2)/92160 as a magnitude that
321
+ # might have cancelled; and it evaluated every node separately.)
322
+ def float_with_error(e)
323
+ float_bound(e)
324
+ rescue ZeroDivisionError, Math::DomainError, FloatDomainError
325
+ nil
326
+ end
327
+
328
+ # A Float that stands clear of its rounding error is not 0: the quick
329
+ # half of every zero test, asked before the exact routes, which can cost
330
+ # a factorization over a number field.
331
+ def float_nonzero?(e)
332
+ value, error = float_with_error(e)
333
+ !value.nil? && value.abs > ERROR_MARGIN * error
334
+ end
335
+
336
+ # How far above its error bound a value has to be (the bound is first
337
+ # order, and the functions' derivatives are taken at the value).
338
+ ERROR_MARGIN = 64
339
+ EPS = Float::EPSILON
340
+
341
+ def float_bound(e)
342
+ value, error =
343
+ case e
344
+ when Num
345
+ x = e.value
346
+ case x
347
+ when Integer, Rational then [x.to_f, x.to_f.abs * EPS]
348
+ when Float then [x, x.abs * EPS]
349
+ when Complex then [Complex(x.real.to_f, x.imaginary.to_f), x.abs.to_f * EPS]
350
+ end
351
+ when Const then e.name == :pi ? [Math::PI, Math::PI * EPS] : nil
352
+ when Neg
353
+ a, ea = float_bound(e.arg)
354
+ a && [-a, ea]
355
+ when Add, Sub, Mul, Div
356
+ a, ea = float_bound(e.left)
357
+ return nil if a.nil?
358
+ b, eb = float_bound(e.right)
359
+ return nil if b.nil?
360
+ case e
361
+ when Add then (v = a + b) && [v, ea + eb + v.abs * EPS]
362
+ when Sub then (v = a - b) && [v, ea + eb + v.abs * EPS]
363
+ when Mul then (v = a * b) && [v, a.abs * eb + b.abs * ea + ea * eb + v.abs * EPS]
364
+ else
365
+ return nil if b.abs <= eb
366
+ v = a / b
367
+ [v, (ea + v.abs * eb) / (b.abs - eb) + v.abs * EPS]
368
+ end
369
+ when Pow then power_bound(e)
370
+ when Fn
371
+ return nil unless e.args.size == 1
372
+ a, ea = float_bound(e.args.first)
373
+ a && function_bound(e.name, a, ea)
374
+ end
375
+ return nil unless value.is_a?(Numeric) && value.abs.finite? && error.finite?
376
+ [value, error]
377
+ end
378
+
379
+ def power_bound(e)
380
+ a, ea = float_bound(e.base)
381
+ return nil if a.nil?
382
+ n = e.exponent
383
+ if n.is_a?(Num) && n.value.is_a?(Integer)
384
+ k = n.value
385
+ return nil if k.negative? && a.abs <= ea
386
+ v = a**k
387
+ return [v, v.abs * EPS] if ea.zero?
388
+ # |d(a**k)| = |k|*|a|**(k - 1)*|da|, over the whole interval a +- ea
389
+ reach = k.negative? ? a.abs - ea : a.abs + ea
390
+ return [v, k.abs * reach**(k - 1) * ea + v.abs * EPS * (k.abs.bit_length + 1)]
391
+ end
392
+ b, eb = float_bound(n)
393
+ return nil if b.nil? || a.abs <= ea
394
+ v = complex_power(a, b)
395
+ logs = Math.log(a.abs).abs + Math::PI
396
+ [v, v.abs * (b.abs * ea / (a.abs - ea) + logs * eb + 4 * EPS)]
397
+ end
398
+
399
+ def complex_power(a, b)
400
+ return a**b if a.is_a?(Float) && a.positive? && b.is_a?(Float)
401
+ CMath_lite.exp(Complex(b) * CMath_lite.log(Complex(a)))
402
+ end
403
+
404
+ # [f(a), error] from the error of a: |f'| over a +- ea bounds the step.
405
+ def function_bound(name, a, ea)
406
+ v = function_value(name, a)
407
+ return nil if v.nil?
408
+ imaginary = a.is_a?(Complex) ? a.imaginary.abs : 0.0
409
+ slope =
410
+ case name
411
+ when :re, :im, :conj, :abs then 1.0
412
+ when :arg then a.abs > ea ? 1.0 / (a.abs - ea) : nil
413
+ when :sign then a.abs > ea ? 0.0 : nil
414
+ when :sin, :cos then Math.cosh(imaginary + ea)
415
+ when :erf, :erfc then imaginary.zero? ? 1.2 : nil
416
+ when :atan
417
+ # 1/(1 + z**2), bounded away from the poles at +-i
418
+ room = (1 + a * a).abs - 2 * a.abs * ea - ea * ea
419
+ room.positive? ? 1.0 / room : nil
420
+ when :exp then Math.exp(a.real + ea)
421
+ when :sinh, :cosh then Math.cosh(a.real.abs + ea) + 1
422
+ when :log then a.abs > ea ? 1.0 / (a.abs - ea) : nil
423
+ when :tan
424
+ c = Math.cos(a.real).abs
425
+ c > ea ? 1.0 / (c - ea)**2 : nil
426
+ when :asin, :acos
427
+ # 1/sqrt(1 - z**2), bounded away from +-1 (asin(2) is fine)
428
+ room = (1 - a * a).abs - 2 * a.abs * ea - ea * ea
429
+ room.positive? ? 1.0 / Math.sqrt(room) : nil
430
+ else
431
+ # re, im, gamma, ... by evalf: a slope we do not know is only
432
+ # harmless when the argument carries no error beyond an ulp, and
433
+ # far from where the function is singular or jumps
434
+ ea <= a.abs * 4 * EPS ? unknown_slope(name, a, ea, v) : nil
435
+ end
436
+ return nil if slope.nil?
437
+ [v, slope * ea + v.abs * 8 * EPS]
438
+ end
439
+
440
+ # The points where a function evaluated by evalf is not smooth: the
441
+ # poles of gamma at 0, -1, -2, ... and the rest. An ulp of error in the
442
+ # argument is a large relative error in the distance to a pole, and
443
+ # gamma(-1 + 10**-15) has a slope near 10**30, where 1e3*(|v| + 1) said
444
+ # 10**18 - an exact zero came out as -8e11 and "not zero" (the sixth
445
+ # review's preflight). Near such a point the bound grows like the
446
+ # slope of a simple pole, 1/d**2 over the distance d; within the error
447
+ # of the argument there is no bound at all.
448
+ SINGULAR = {
449
+ gamma: :nonpositive, digamma: :nonpositive, psi: :nonpositive,
450
+ factorial: :negative, harmonic: :negative,
451
+ zeta: [1.0], li: [1.0, 0.0], Ei: [0.0], Ci: [0.0],
452
+ floor: :integers, ceil: :integers, round: :half_integers
453
+ }.freeze
454
+ JUMPING = %i[floor ceil round].freeze
455
+
456
+ def unknown_slope(name, a, ea, v)
457
+ d = singular_distance(SINGULAR[name], a)
458
+ return 1e3 * (v.abs + 1) if d.nil?
459
+ return nil if d <= 2 * ea
460
+ return 0.0 if JUMPING.include?(name)
461
+ 1e3 * (v.abs + 1) * (1 + 1 / d) + 1 / (d * d)
462
+ end
463
+
464
+ def singular_distance(points, a)
465
+ return nil if points.nil?
466
+ re = a.real.to_f
467
+ nearest =
468
+ case points
469
+ when Array then points.min_by { |p| (a - p).abs }
470
+ when :nonpositive then [re.round, 0].min
471
+ when :negative then [re.round, -1].min
472
+ when :integers then re.round
473
+ when :half_integers then re.floor + 0.5
474
+ end
475
+ (a - nearest).abs
476
+ end
477
+
478
+ def function_value(name, a)
479
+ if a.is_a?(Float)
480
+ case name
481
+ when :exp then return Math.exp(a)
482
+ when :sin, :cos, :atan, :sinh, :cosh, :tan, :erf, :erfc then return Math.public_send(name, a)
483
+ when :log then return a.positive? ? Math.log(a) : CMath_lite.log(Complex(a))
484
+ when :abs then return a.abs
485
+ when :sign then return (a <=> 0).to_f
486
+ when :asin, :acos then return Math.public_send(name, a) if a.abs <= 1
487
+ end
488
+ end
489
+ return a.abs if name == :abs
490
+ return a.real.to_f if name == :re
491
+ return a.imaginary.to_f if name == :im
492
+ return a.conj if name == :conj
493
+ return Math.atan2(a.imaginary.to_f, a.real.to_f) if name == :arg
494
+ return CMath_lite.public_send(name, Complex(a)) if CMath_lite::NAMES.include?(name)
495
+ # re, im, gamma, ...: evalf takes the function of the number
496
+ v = Fn.new(name, [Num.new(a)]).evalf
497
+ v = v.value if v.is_a?(Num)
498
+ v.is_a?(Numeric) ? (v.is_a?(Complex) ? Complex(v.real.to_f, v.imaginary.to_f) : v.to_f) : nil
499
+ end
500
+ end
501
+ end