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
data/lib/rcas/decide.rb
ADDED
|
@@ -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
|