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,260 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCAS
|
|
4
|
+
# The integrals that are not elementary but have names: the exponential
|
|
5
|
+
# integral Ei, the sine and cosine integrals Si and Ci, and the
|
|
6
|
+
# logarithmic integral li.
|
|
7
|
+
#
|
|
8
|
+
# Ei(x) = integral(exp(t)/t, t, -oo, x) (principal value)
|
|
9
|
+
# Si(x) = integral(sin(t)/t, t, 0, x)
|
|
10
|
+
# Ci(x) = gamma + log(x) + integral((cos(t) - 1)/t, t, 0, x)
|
|
11
|
+
# li(x) = Ei(log(x))
|
|
12
|
+
#
|
|
13
|
+
# With them `integrate(sin(x)/x, x)` has an answer instead of staying an
|
|
14
|
+
# integral(...) node, which is the point: a student who meets sin(x)/x
|
|
15
|
+
# should be told that its antiderivative has a name, not that rcas gave up.
|
|
16
|
+
#
|
|
17
|
+
# The numbers are Floats. Ei by its series for moderate arguments and by
|
|
18
|
+
# the asymptotic expansion beyond; Si and Ci by the series below 2 and by
|
|
19
|
+
# a complex continued fraction (modified Lentz) above it.
|
|
20
|
+
#
|
|
21
|
+
# Sources (keys: MANUAL.md, Sources): [AS64, §5.1, §5.2] for the
|
|
22
|
+
# definitions and the series; [PTVF07, §6.3] for the two routines;
|
|
23
|
+
# Lentz's method [Len76].
|
|
24
|
+
module IntegralFunctions
|
|
25
|
+
NAMES = %i[Ei Si Ci li].freeze
|
|
26
|
+
EULER = 0.5772156649015328606
|
|
27
|
+
EPSILON = 1e-16
|
|
28
|
+
TINY = 1e-300
|
|
29
|
+
MAX_ITERATIONS = 200
|
|
30
|
+
SERIES_LIMIT = 36.0 # beyond this the series for Ei loses to cancellation
|
|
31
|
+
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
# ---- numerics -----------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
# Ei(x), the principal value of integral(exp(t)/t, t, -oo, x).
|
|
37
|
+
def ei(x)
|
|
38
|
+
x = x.to_f
|
|
39
|
+
raise ArgumentError, "Ei(0) is infinite" if x.zero?
|
|
40
|
+
return Math.log(x.abs) + EULER if x.abs < TINY
|
|
41
|
+
x.abs <= SERIES_LIMIT ? ei_series(x) : ei_asymptotic(x)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Ei(x) = gamma + log|x| + sum_k x**k/(k*k!)
|
|
45
|
+
def ei_series(x)
|
|
46
|
+
sum = 0.0
|
|
47
|
+
fact = 1.0
|
|
48
|
+
(1..MAX_ITERATIONS).each do |k|
|
|
49
|
+
fact *= x / k
|
|
50
|
+
term = fact / k
|
|
51
|
+
sum += term
|
|
52
|
+
break if term.abs < EPSILON * sum.abs
|
|
53
|
+
end
|
|
54
|
+
sum + Math.log(x.abs) + EULER
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Ei(x) ~ exp(x)/x * sum_k k!/x**k, summed while the terms shrink.
|
|
58
|
+
def ei_asymptotic(x)
|
|
59
|
+
sum = 0.0
|
|
60
|
+
term = 1.0
|
|
61
|
+
(1..MAX_ITERATIONS).each do |k|
|
|
62
|
+
previous = term
|
|
63
|
+
term *= k / x
|
|
64
|
+
break if term.abs < EPSILON
|
|
65
|
+
if term.abs < previous.abs
|
|
66
|
+
sum += term
|
|
67
|
+
else
|
|
68
|
+
sum -= previous
|
|
69
|
+
break
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
Math.exp(x) * (1.0 + sum) / x
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def si(x) = cisi(x).last
|
|
76
|
+
# On the negative axis Ci is on its cut: the principal value is
|
|
77
|
+
# Ci(|x|) + i*pi, from the log(x) in its definition. A real Float there
|
|
78
|
+
# was a wrong answer on either side of the cut (third review, D12).
|
|
79
|
+
def ci(x)
|
|
80
|
+
value = cisi(x).first
|
|
81
|
+
x.to_f.negative? ? Complex(value, Math::PI) : value
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# [Ci(x), Si(x)] for a real x; Ci is real only for x > 0.
|
|
85
|
+
def cisi(x)
|
|
86
|
+
x = x.to_f
|
|
87
|
+
return [-Float::INFINITY, 0.0] if x.zero?
|
|
88
|
+
return cisi(-x).then { |c, s| [c, -s] } if x.negative? # Si is odd, Ci is not real
|
|
89
|
+
return [Math.log(x) + EULER, x] if x < Math.sqrt(TINY)
|
|
90
|
+
x <= 2.0 ? cisi_series(x) : cisi_fraction(x)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def cisi_series(x)
|
|
94
|
+
sum = 0.0
|
|
95
|
+
sums = 0.0
|
|
96
|
+
sumc = 0.0
|
|
97
|
+
sign = 1.0
|
|
98
|
+
fact = 1.0
|
|
99
|
+
odd = true
|
|
100
|
+
(1..MAX_ITERATIONS).each do |k|
|
|
101
|
+
fact *= x / k
|
|
102
|
+
term = fact / k
|
|
103
|
+
sum += sign * term
|
|
104
|
+
error = term / sum.abs
|
|
105
|
+
if odd
|
|
106
|
+
sign = -sign
|
|
107
|
+
sums = sum
|
|
108
|
+
sum = sumc
|
|
109
|
+
else
|
|
110
|
+
sumc = sum
|
|
111
|
+
sum = sums
|
|
112
|
+
end
|
|
113
|
+
break if error < EPSILON
|
|
114
|
+
odd = !odd
|
|
115
|
+
end
|
|
116
|
+
[sumc + Math.log(x) + EULER, sums]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The continued fraction for exp(i*x)*(Ci + i*(Si - pi/2)) [PTVF07, §6.3].
|
|
120
|
+
def cisi_fraction(x)
|
|
121
|
+
b = Complex(1.0, x)
|
|
122
|
+
c = Complex(1.0 / TINY, 0.0)
|
|
123
|
+
d = 1.0 / b
|
|
124
|
+
h = d
|
|
125
|
+
(2..MAX_ITERATIONS).each do |i|
|
|
126
|
+
a = -((i - 1)**2).to_f
|
|
127
|
+
b += 2.0
|
|
128
|
+
d = 1.0 / (a * d + b)
|
|
129
|
+
c = b + a / c
|
|
130
|
+
delta = c * d
|
|
131
|
+
h *= delta
|
|
132
|
+
break if (delta.real - 1.0).abs + delta.imaginary.abs < EPSILON
|
|
133
|
+
end
|
|
134
|
+
h = Complex(Math.cos(x), -Math.sin(x)) * h
|
|
135
|
+
[-h.real, Math::PI / 2 + h.imaginary]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# li(x) = Ei(log(x)), the count of primes' companion.
|
|
139
|
+
def li(x)
|
|
140
|
+
x = x.to_f
|
|
141
|
+
return 0.0 if x.zero?
|
|
142
|
+
raise ArgumentError, "li: a positive argument is needed, got #{x}" unless x.positive?
|
|
143
|
+
raise ArgumentError, "li(1) is infinite" if x == 1.0
|
|
144
|
+
ei(Math.log(x))
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def evaluate(name, value)
|
|
148
|
+
case name
|
|
149
|
+
when :Ei then ei(value)
|
|
150
|
+
when :Si then si(value)
|
|
151
|
+
when :Ci then ci(value)
|
|
152
|
+
when :li then li(value)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# ---- exact values -------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
# The values a table lists, and Floats; nil when there is nothing to say.
|
|
159
|
+
def value(name, arg)
|
|
160
|
+
return Num.new(evaluate(name, arg.value)) if arg.is_a?(Num) && arg.value.is_a?(Float)
|
|
161
|
+
return infinite_value(name, arg) if arg == OO || arg == Neg.new(OO).simplify
|
|
162
|
+
return nil unless arg.is_a?(Num) && arg.value.is_a?(Numeric) && arg.value.real?
|
|
163
|
+
zero = arg.value.zero?
|
|
164
|
+
case name
|
|
165
|
+
when :Si then zero ? Num.new(0) : nil
|
|
166
|
+
when :Ci, :Ei then zero ? Neg.new(OO).simplify : nil
|
|
167
|
+
when :li
|
|
168
|
+
return Num.new(0) if zero
|
|
169
|
+
arg.value == 1 ? Neg.new(OO).simplify : nil
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def infinite_value(name, arg)
|
|
174
|
+
positive = arg == OO
|
|
175
|
+
case name
|
|
176
|
+
when :Si then positive ? (PI / 2).simplify : Neg.new(PI / 2).simplify
|
|
177
|
+
when :Ci then positive ? Num.new(0) : nil
|
|
178
|
+
when :Ei then positive ? OO : Num.new(0)
|
|
179
|
+
when :li then positive ? OO : nil
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# ---- calculus -----------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
# The derivatives: the integrands these functions were named for.
|
|
186
|
+
def derivative(name, u)
|
|
187
|
+
case name
|
|
188
|
+
when :Ei then Div.new(Fn.new(:exp, [u]), u)
|
|
189
|
+
when :Si then Div.new(Fn.new(:sin, [u]), u)
|
|
190
|
+
when :Ci then Div.new(Fn.new(:cos, [u]), u)
|
|
191
|
+
when :li then Div.new(Num.new(1), Fn.new(:log, [u]))
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# exp(u)/u, sin(u)/u, cos(u)/u and 1/log(u) with u = a*x + b.
|
|
196
|
+
# Everything else (exp(x)/(x + 1), say) reaches this through the linear
|
|
197
|
+
# substitution Integrate.shift makes.
|
|
198
|
+
INTEGRANDS = { exp: :Ei, sin: :Si, cos: :Ci }.freeze
|
|
199
|
+
|
|
200
|
+
def antiderivative(f, x)
|
|
201
|
+
coeff, factors = Simplify.factorize(f)
|
|
202
|
+
return nil unless coeff == 1
|
|
203
|
+
log_case = logarithmic(factors, x)
|
|
204
|
+
return log_case if log_case
|
|
205
|
+
return nil unless factors.size == 2
|
|
206
|
+
name, u = numerator_of(factors)
|
|
207
|
+
return nil if name.nil?
|
|
208
|
+
denominator = factors.find { |_, exp| exp == -1 }&.first
|
|
209
|
+
return nil if denominator.nil?
|
|
210
|
+
a, = Integrate.linear(u, x)
|
|
211
|
+
return nil if a.nil?
|
|
212
|
+
return Div.new(Fn.new(name, [u]), a) if denominator == u || Scalar.zero?(denominator - u)
|
|
213
|
+
shifted_exponential(name, u, denominator, x)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# integral(exp(a*x + b)/(c*x + d)) = exp(b - a*d/c)/c * Ei(a*x + a*d/c):
|
|
217
|
+
# the substitution t = c*x + d leaves a constant factor behind. Only the
|
|
218
|
+
# exponential has this form; sin and cos would need both Si and Ci.
|
|
219
|
+
def shifted_exponential(name, u, denominator, x)
|
|
220
|
+
return nil unless name == :Ei
|
|
221
|
+
a, b = Integrate.linear(u, x)
|
|
222
|
+
c, d = Integrate.linear(denominator, x)
|
|
223
|
+
return nil if a.nil? || c.nil?
|
|
224
|
+
factor = Fn.new(:exp, [(b - a * d / c).simplify])
|
|
225
|
+
argument = (a * x + a * d / c).simplify
|
|
226
|
+
(factor * Fn.new(:Ei, [argument]) / c).simplify
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# [the function to name the integral, its argument]. An exponential is
|
|
230
|
+
# kept as exp_base**u in a factor table (simplify.rb), not as Fn(:exp).
|
|
231
|
+
def numerator_of(factors)
|
|
232
|
+
factors.each do |base, exp|
|
|
233
|
+
return [:Ei, Expression.lift(exp)] if base == Simplify.exp_base
|
|
234
|
+
next unless exp == 1 && base.is_a?(Fn) && INTEGRANDS.key?(base.name) && base.args.size == 1
|
|
235
|
+
return [INTEGRANDS[base.name], base.args.first]
|
|
236
|
+
end
|
|
237
|
+
nil
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# 1/log(a*x + b) = li(a*x + b)/a
|
|
241
|
+
def logarithmic(factors, x)
|
|
242
|
+
return nil unless factors.size == 1
|
|
243
|
+
base, exp = factors.first
|
|
244
|
+
return nil unless exp == -1 && base.is_a?(Fn) && base.name == :log && base.args.size == 1
|
|
245
|
+
u = base.args.first
|
|
246
|
+
a, = Integrate.linear(u, x)
|
|
247
|
+
a.nil? ? nil : Div.new(Fn.new(:li, [u]), a)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# The limits at the ends of the real line, which is what a definite
|
|
251
|
+
# integral over an infinite range needs. nil when this is not one of ours.
|
|
252
|
+
def limit(f, x, point)
|
|
253
|
+
return nil unless f.is_a?(Fn) && NAMES.include?(f.name) && f.args.size == 1
|
|
254
|
+
return nil unless Limits.infinite?(point)
|
|
255
|
+
inner = Limits.limit(f.args.first, x, point)
|
|
256
|
+
return nil unless inner == OO || inner == Neg.new(OO).simplify
|
|
257
|
+
value(f.name, inner)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|