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/series.rb
ADDED
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCAS
|
|
4
|
+
OO = Const.new(:oo, Float::INFINITY)
|
|
5
|
+
register_infinity
|
|
6
|
+
|
|
7
|
+
# Unevaluated limit.
|
|
8
|
+
class Limit < Expression
|
|
9
|
+
attr_reader :expr, :var, :point
|
|
10
|
+
|
|
11
|
+
def initialize(expr, var, point)
|
|
12
|
+
@expr = expr
|
|
13
|
+
@var = var
|
|
14
|
+
@point = point
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def bound_variable = var
|
|
19
|
+
def children = [expr, var, point]
|
|
20
|
+
def rebuild(expr, var, point) = Limit.new(expr, var, point)
|
|
21
|
+
def to_sexp = [:limit, expr.to_sexp, var.to_sexp, point.to_sexp]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class SeriesError < StandardError; end
|
|
25
|
+
|
|
26
|
+
# Truncated Puiseux series in a local variable t: sum of coeff * t**exp for
|
|
27
|
+
# rational exponents below +order+. Coefficients are Expressions and may
|
|
28
|
+
# contain the marker variable LOG for log(t).
|
|
29
|
+
#
|
|
30
|
+
# Sources: series arithmetic (product, quotient, composition, powers) as
|
|
31
|
+
# in [Knu98, §4.7]. Limits take the leading term of this expansion at the
|
|
32
|
+
# point; that is the textbook strategy, not Gruntz's MRV algorithm
|
|
33
|
+
# [Gru96]. What has no series at the point is left to the squeeze rule
|
|
34
|
+
# and to the fallbacks in Limits. Keys: MANUAL.md, Sources.
|
|
35
|
+
class Series
|
|
36
|
+
LOG = Var.new(:_L)
|
|
37
|
+
|
|
38
|
+
attr_reader :terms, :order
|
|
39
|
+
|
|
40
|
+
def initialize(terms, order)
|
|
41
|
+
@order = terms.is_a?(Hash) ? order : order
|
|
42
|
+
@terms = terms.select { |e, c| e < order && !Scalar.zero?(c) }.sort.to_h.freeze
|
|
43
|
+
freeze
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.constant(c, order)
|
|
47
|
+
c = Expression.lift(c)
|
|
48
|
+
new(Scalar.zero?(c) ? {} : { 0 => c }, order)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.variable(order) = new({ 1 => Num.new(1) }, order)
|
|
52
|
+
|
|
53
|
+
def zero? = terms.empty?
|
|
54
|
+
def min_exponent = terms.keys.min
|
|
55
|
+
def low = min_exponent || order
|
|
56
|
+
def constant_term = terms[0] || Num.new(0)
|
|
57
|
+
def leading = terms.first
|
|
58
|
+
|
|
59
|
+
def +(other) = Series.new(terms.merge(other.terms) { |_, a, b| (a + b).simplify }, [order, other.order].min)
|
|
60
|
+
def -(other) = self + other.scale(Num.new(-1))
|
|
61
|
+
def scale(c) = Series.new(terms.transform_values { |v| (v * c).simplify }, order)
|
|
62
|
+
def shift(k) = Series.new(terms.to_h { |e, c| [norm(e + k), c] }, norm(order + k))
|
|
63
|
+
def truncate(n) = Series.new(terms, [order, n].min)
|
|
64
|
+
|
|
65
|
+
def *(other)
|
|
66
|
+
new_order = [order + other.low, other.order + low].min
|
|
67
|
+
out = {}
|
|
68
|
+
terms.each do |e1, c1|
|
|
69
|
+
other.terms.each do |e2, c2|
|
|
70
|
+
e = norm(e1 + e2)
|
|
71
|
+
next if e >= new_order
|
|
72
|
+
out[e] = out[e] ? (out[e] + c1 * c2).simplify : (c1 * c2).simplify
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
Series.new(out, new_order)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def **(n)
|
|
79
|
+
return inverse**(-n) if n.negative?
|
|
80
|
+
result = Series.constant(1, order + low * [n - 1, 0].max)
|
|
81
|
+
n.times { result *= self }
|
|
82
|
+
result
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# 1 / self: pull out c*t**m, then a geometric series in the rest.
|
|
86
|
+
def inverse
|
|
87
|
+
raise SeriesError, "cannot invert a series that is zero to the known order" if zero?
|
|
88
|
+
m = min_exponent
|
|
89
|
+
c = terms[m]
|
|
90
|
+
u = shift(-m).scale((Num.new(1) / c).simplify) - Series.constant(1, order - m)
|
|
91
|
+
Series.compose(u, order - m) { |k| Num.new((-1)**k) }.scale((Num.new(1) / c).simplify).shift(-m)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# sum_{k>=0} coeff(k) * u**k, u with positive minimal exponent.
|
|
95
|
+
def self.compose(u, order)
|
|
96
|
+
c0 = Expression.lift(yield(0))
|
|
97
|
+
# u is known only to its own order, and so is anything built from it:
|
|
98
|
+
# sqrt(x**2 + x) at oo claimed O(1/x**3) with the 1/2 missing (D6)
|
|
99
|
+
order = [order, u.order].min
|
|
100
|
+
return Series.constant(c0, order) if u.zero?
|
|
101
|
+
mu = u.min_exponent
|
|
102
|
+
raise SeriesError, "composition needs a series without constant term" unless mu.positive?
|
|
103
|
+
kmax = (order / mu).ceil
|
|
104
|
+
acc = Series.constant(c0, order)
|
|
105
|
+
pow = Series.constant(1, order)
|
|
106
|
+
(1..kmax).each do |k|
|
|
107
|
+
pow = (pow * u).truncate(order)
|
|
108
|
+
break if pow.zero?
|
|
109
|
+
acc += pow.scale(Expression.lift(yield(k)))
|
|
110
|
+
end
|
|
111
|
+
acc
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def to_expr(t, log_of_t)
|
|
115
|
+
sum = terms.reduce(Num.new(0)) { |acc, (e, c)| acc + c * Simplify.power_node(t, e) }
|
|
116
|
+
sum.subs(LOG => log_of_t).simplify
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def norm(e) = Simplify.normalize_number(e.is_a?(Rational) ? e : Rational(e))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Series expansion and limits.
|
|
125
|
+
#
|
|
126
|
+
# series(sin(x), x, 0, 6) # => x - x**3/6 + x**5/120 + O(x**6)
|
|
127
|
+
# limit(sin(x)/x, x, 0) # => 1
|
|
128
|
+
# limit((1 + 1/x)**x, x, oo) # => e
|
|
129
|
+
module Limits
|
|
130
|
+
module_function
|
|
131
|
+
|
|
132
|
+
T = Var.new(:_t)
|
|
133
|
+
|
|
134
|
+
def series(f, x, a = 0, n = 6, big_o: true)
|
|
135
|
+
f = Expression.lift(f)
|
|
136
|
+
x = Expression.lift(x)
|
|
137
|
+
a = Expression.lift(a)
|
|
138
|
+
g, back, oh = localize(f, x, a)
|
|
139
|
+
s = adaptive(g, n)
|
|
140
|
+
result = back.call(s.to_expr(T, Fn.new(:log, [T])))
|
|
141
|
+
result = result + Fn.new(:O, [oh.call(s.order)]) if big_o
|
|
142
|
+
result.simplify
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def taylor(f, x, a = 0, n = 6) = series(f, x, a, n, big_o: false)
|
|
146
|
+
|
|
147
|
+
# dir: :both (default for finite points), :right or :left
|
|
148
|
+
def limit(f, x, a, dir = nil)
|
|
149
|
+
f = Expression.lift(f)
|
|
150
|
+
x = Expression.lift(x)
|
|
151
|
+
a = Expression.lift(a)
|
|
152
|
+
f = Piecewises.hoist(f)
|
|
153
|
+
return Piecewises.limit(f, x, a, dir) if f.is_a?(Piecewise)
|
|
154
|
+
known = IntegralFunctions.limit(f, x, a)
|
|
155
|
+
return known if known
|
|
156
|
+
if infinite?(a) && (through = through_exponential(f, x, a))
|
|
157
|
+
return through
|
|
158
|
+
end
|
|
159
|
+
# erf(u) = 1 - erfc(u): as a constant up to an exponentially small tail
|
|
160
|
+
# erf lost that tail, which exp(x**2) multiplies back - x*(1 - erf(x))*
|
|
161
|
+
# exp(x**2) came out as oo, not 1/sqrt(pi) (fourth review, D4)
|
|
162
|
+
if infinite?(a) && f.each_node.any? { |n| n.is_a?(Fn) && n.name == :erf }
|
|
163
|
+
tail = f.subs(f.each_node.select { |n| n.is_a?(Fn) && n.name == :erf }.uniq.to_h { |n| [n, Num.new(1) - Fn.new(:erfc, n.args)] }).simplify
|
|
164
|
+
found = limit(tail, x, a, dir)
|
|
165
|
+
return found unless found.is_a?(Limit)
|
|
166
|
+
end
|
|
167
|
+
if (through = through_logarithm(f, x, a, dir))
|
|
168
|
+
return through
|
|
169
|
+
end
|
|
170
|
+
g, = localize(f, x, a)
|
|
171
|
+
side = infinite?(a) ? :right : (dir || :both)
|
|
172
|
+
if side == :both
|
|
173
|
+
right = one_sided(g, :right)
|
|
174
|
+
left = one_sided(g, :left)
|
|
175
|
+
return right if right == left || (right.is_a?(Num) && left.is_a?(Num) && right == left)
|
|
176
|
+
return Limit.new(f, x, a)
|
|
177
|
+
end
|
|
178
|
+
one_sided(g, side)
|
|
179
|
+
rescue SeriesError, ZeroDivisionError, NotImplementedError, RCAS::Unsupported
|
|
180
|
+
Limit.new(f, x, a)
|
|
181
|
+
rescue ArgumentError => e
|
|
182
|
+
# An integrand rcas cannot differentiate reaches here through the
|
|
183
|
+
# series expansion; "don't know the derivative of floor" is the
|
|
184
|
+
# l'Hopital attempt talking, not an answer about the limit.
|
|
185
|
+
raise unless e.message.start_with?(Integrate::NO_DERIVATIVE)
|
|
186
|
+
Limit.new(f, x, a)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# f depends on x only through exp(a_i*x + b_i) with rational a_i: with
|
|
190
|
+
# u = exp(l*x), l the greatest common measure of the a_i, it is a
|
|
191
|
+
# function of u alone, and x -> +-oo is u -> oo or u -> 0+. The logistic
|
|
192
|
+
# exp(x)/(1 + exp(x)) tends to 1 that way; the series at infinity meets
|
|
193
|
+
# exp(1/t), which has none, and discuss reported "no asymptote" for a
|
|
194
|
+
# limit it had not taken (fourth review, S11). nil when f is not such a
|
|
195
|
+
# function or the limit in u is not taken either.
|
|
196
|
+
def through_exponential(f, x, a)
|
|
197
|
+
exps = f.each_node.select { |n| n.is_a?(Fn) && n.name == :exp && n.args.first.variables.include?(x.name) }.uniq
|
|
198
|
+
return nil if exps.empty?
|
|
199
|
+
slopes = exps.map do |e|
|
|
200
|
+
coefficients = Solve.polynomial_coefficients(e.args.first, x)
|
|
201
|
+
return nil unless coefficients&.size == 2 && coefficients.all? { |c| !c.variables.include?(x.name) }
|
|
202
|
+
slope = coefficients[1].simplify
|
|
203
|
+
return nil unless slope.is_a?(Num) && (slope.value.is_a?(Integer) || slope.value.is_a?(Rational))
|
|
204
|
+
[e, slope.value.to_r, coefficients[0]]
|
|
205
|
+
end
|
|
206
|
+
measure = slopes.map { |_, m, _| m.abs }.reduce { |g, m| Rational(g.numerator.gcd(m.numerator), g.denominator.lcm(m.denominator)) }
|
|
207
|
+
u = Var.new(:_u)
|
|
208
|
+
replaced = slopes.reduce(f) { |acc, (e, m, b)| acc.subs(e => Fn.new(:exp, [b]) * u**Num.new(m / measure)) }.simplify
|
|
209
|
+
return nil if replaced.variables.include?(x.name)
|
|
210
|
+
value = a == OO ? limit(replaced, u, OO) : limit(replaced, u, Num.new(0), :right)
|
|
211
|
+
value.is_a?(Limit) ? nil : value
|
|
212
|
+
rescue SeriesError, ZeroDivisionError, ArgumentError, NotImplementedError, RCAS::Unsupported
|
|
213
|
+
nil
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# f depends on x only through log(x): with u = log(x), x -> 0+ is
|
|
217
|
+
# u -> -oo and x -> oo is u -> oo. 1/log(x) tends to 0 at 0 from the
|
|
218
|
+
# right, which the series cannot say (1/log(t) is below every constant
|
|
219
|
+
# and above every power). Only from the right at 0: on the left log(x)
|
|
220
|
+
# is not real.
|
|
221
|
+
def through_logarithm(f, x, a, dir)
|
|
222
|
+
target = if a == OO then OO
|
|
223
|
+
elsif a.is_a?(Num) && a.value.zero? && dir == :right then Neg.new(OO).simplify
|
|
224
|
+
end
|
|
225
|
+
return nil if target.nil?
|
|
226
|
+
logs = f.each_node.select { |n| n.is_a?(Fn) && n.name == :log && n.args.first.variables.include?(x.name) }.uniq
|
|
227
|
+
return nil unless logs == [Fn.new(:log, [x])]
|
|
228
|
+
u = Var.new(:_v)
|
|
229
|
+
replaced = f.subs(logs.first => u)
|
|
230
|
+
return nil if replaced.variables.include?(x.name)
|
|
231
|
+
value = limit(replaced, u, target)
|
|
232
|
+
value.is_a?(Limit) ? nil : value
|
|
233
|
+
rescue SeriesError, ZeroDivisionError, ArgumentError, NotImplementedError, RCAS::Unsupported
|
|
234
|
+
nil
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# ---- helpers -----------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
def infinite?(a) = a == OO || a == Neg.new(OO)
|
|
240
|
+
|
|
241
|
+
# [g(t), back-substitution, O-term builder] with the limit at t -> 0+
|
|
242
|
+
def localize(f, x, a)
|
|
243
|
+
if a == OO
|
|
244
|
+
[f.subs(x => 1 / T), ->(e) { e.subs(T => 1 / x) }, ->(o) { Pow.new(x, Num.new(-o)) }]
|
|
245
|
+
elsif a == Neg.new(OO)
|
|
246
|
+
[f.subs(x => -1 / T), ->(e) { e.subs(T => -1 / x) }, ->(o) { Pow.new(x, Num.new(-o)) }]
|
|
247
|
+
else
|
|
248
|
+
shift = Scalar.zero?(a) ? x : x - a
|
|
249
|
+
[f.subs(x => T + a), ->(e) { e.subs(T => shift) }, ->(o) { Simplify.power_node(shift, o) }]
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def adaptive(g, n)
|
|
254
|
+
order = n
|
|
255
|
+
s = nil
|
|
256
|
+
6.times do
|
|
257
|
+
s = expand(g, order)
|
|
258
|
+
return s.truncate(n) if s.order >= n
|
|
259
|
+
order += (n - s.order) + 1
|
|
260
|
+
end
|
|
261
|
+
s
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def one_sided(g, side)
|
|
265
|
+
outer = Thread.current[:rcas_one_sided]
|
|
266
|
+
Thread.current[:rcas_one_sided] = true
|
|
267
|
+
one_sided_limit(g, side)
|
|
268
|
+
ensure
|
|
269
|
+
Thread.current[:rcas_one_sided] = outer
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def one_sided_limit(g, side)
|
|
273
|
+
g = g.subs(T => -T) if side == :left
|
|
274
|
+
s = nil
|
|
275
|
+
begin
|
|
276
|
+
[4, 8, 16, 32].each do |n|
|
|
277
|
+
s = adaptive(g, n)
|
|
278
|
+
break unless s.zero?
|
|
279
|
+
end
|
|
280
|
+
rescue SeriesError
|
|
281
|
+
return squeeze(g) || termwise(g) || dominant(g) || exponential_fallback(g)
|
|
282
|
+
end
|
|
283
|
+
return Num.new(0) if s.zero?
|
|
284
|
+
|
|
285
|
+
e, c = s.leading
|
|
286
|
+
if c.variables.include?(Series::LOG.name)
|
|
287
|
+
m = Var.new(:_M)
|
|
288
|
+
poly = Solve.polynomial_coefficients(c.subs(Series::LOG => -m).expand, m) or raise SeriesError, "log coefficient"
|
|
289
|
+
degree = poly.size - 1
|
|
290
|
+
lc = poly.last
|
|
291
|
+
return Num.new(0) if e.positive?
|
|
292
|
+
return c.simplify if e.zero? && degree.zero?
|
|
293
|
+
return signed_infinity(lc)
|
|
294
|
+
end
|
|
295
|
+
return Num.new(0) if e.positive?
|
|
296
|
+
return c.simplify if e.zero?
|
|
297
|
+
signed_infinity(c)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Functions whose real values stay in a bounded interval.
|
|
301
|
+
BOUNDED = %i[sin cos sign atan erf erfc tanh].freeze
|
|
302
|
+
|
|
303
|
+
# The squeeze rule [Rud76, th. 3.19]: |sin(u)| <= 1, so a bounded factor
|
|
304
|
+
# times a factor that tends to zero tends to zero, however wildly the
|
|
305
|
+
# first one oscillates. x*sin(1/x) at 0 and exp(-x)*cos(3*x) at infinity
|
|
306
|
+
# need this - the oscillating factor has no series at the point, so the
|
|
307
|
+
# expansion above cannot see them. nil when the rule does not apply.
|
|
308
|
+
def squeeze(g)
|
|
309
|
+
coeff, factors = Simplify.factorize(g)
|
|
310
|
+
bounded, rest = factors.partition { |base, exp| bounded?(base, exp) }
|
|
311
|
+
return nil unless bounded.any? { |base, _| base.variables.include?(T.name) }
|
|
312
|
+
value = one_sided(Simplify.rebuild_product(coeff, rest.to_h), :right)
|
|
313
|
+
value.is_a?(Num) && value.value.zero? ? Num.new(0) : nil
|
|
314
|
+
rescue SeriesError, ZeroDivisionError
|
|
315
|
+
nil
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# A non-negative integer power of a bounded function is bounded again.
|
|
319
|
+
def bounded?(base, exponent)
|
|
320
|
+
base.is_a?(Fn) && BOUNDED.include?(base.name) && base.args.size == 1 &&
|
|
321
|
+
exponent.is_a?(Integer) && !exponent.negative?
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# exp(-1/t)*t and friends: look at log(g) instead, whose leading term
|
|
325
|
+
# decides between 0, oo and exp(finite).
|
|
326
|
+
def exponential_fallback(g)
|
|
327
|
+
coeff, factors = Simplify.factorize(g)
|
|
328
|
+
exponential = factors.any? { |base, exp| base == Simplify.exp_base || (base.is_a?(Fn) && base.name == :exp) || (exp.is_a?(Expression) && exp.variables.include?(T.name)) }
|
|
329
|
+
raise SeriesError, "no exponential factor in #{g}" unless exponential
|
|
330
|
+
negative = Simplify.negative?(coeff)
|
|
331
|
+
positive = negative ? Simplify.rebuild_product(-coeff, factors) : g
|
|
332
|
+
value = one_sided(log_of(positive), :right)
|
|
333
|
+
raise SeriesError, "unexpected log limit #{value}" if value.is_a?(Limit)
|
|
334
|
+
result =
|
|
335
|
+
if value == Neg.new(OO) then Num.new(0)
|
|
336
|
+
elsif value == OO then OO
|
|
337
|
+
else Fn.new(:exp, [value]).simplify
|
|
338
|
+
end
|
|
339
|
+
negative ? Neg.new(result).simplify : result
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# The limit of a sum is the sum of the limits when every term has a
|
|
343
|
+
# finite one (exp(-1/t)/t + erf(1/t) at t -> 0+, say); nil otherwise, and
|
|
344
|
+
# nil rather than the error when a term has no series at all, so that the
|
|
345
|
+
# rules after this one still get their turn.
|
|
346
|
+
def termwise(g)
|
|
347
|
+
constant, terms = sum_terms(g) || return
|
|
348
|
+
total = Num.new(constant)
|
|
349
|
+
terms.each do |factors, coeff|
|
|
350
|
+
value = one_sided(Simplify.rebuild_product(coeff, factors), :right)
|
|
351
|
+
return nil if value.is_a?(Limit) || infinite?(value) || value.each_node.any? { |n| n == OO }
|
|
352
|
+
total += value
|
|
353
|
+
end
|
|
354
|
+
total.simplify
|
|
355
|
+
rescue SeriesError, ZeroDivisionError
|
|
356
|
+
nil
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# The terms of a sum; c * (a + b) is distributed first, because a single
|
|
360
|
+
# term is nothing to compare. A sum that cancels away entirely comes back
|
|
361
|
+
# with no terms and is its constant - sin(x) - sin(x) is 0 wherever both
|
|
362
|
+
# halves are defined, whether or not either half has a series there.
|
|
363
|
+
# nil when what is left is one term and nothing else.
|
|
364
|
+
def sum_terms(g)
|
|
365
|
+
constant, terms = Simplify.termize(g)
|
|
366
|
+
return [constant, terms] if sum?(constant, terms)
|
|
367
|
+
return nil unless g.each_node.any? { |n| n.is_a?(Add) || n.is_a?(Sub) }
|
|
368
|
+
constant, terms = Simplify.termize(Expand.expand(g))
|
|
369
|
+
sum?(constant, terms) ? [constant, terms] : nil
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def sum?(constant, terms) = terms.empty? || terms.size + (constant.zero? ? 0 : 1) >= 2
|
|
373
|
+
|
|
374
|
+
# The squeeze rule additively [Rud76, th. 3.19]: a sum follows the term
|
|
375
|
+
# that runs away, as long as the others cannot catch it. Three criteria,
|
|
376
|
+
# each of them sound and each doing what the one before it cannot - let
|
|
377
|
+
# the oscillation grow, and compare two runaways:
|
|
378
|
+
#
|
|
379
|
+
# x**2/4 - sin(x) every other term is bounded, so the sum is caught
|
|
380
|
+
# between x**2/4 - 1 and x**2/4 + 1
|
|
381
|
+
# x**2 - x*sin(x) |x*sin(x)| <= |x| is of strictly smaller order than
|
|
382
|
+
# x**2, so the sum is caught between x**2 - x and
|
|
383
|
+
# x**2 + x
|
|
384
|
+
# exp(x) - x x/exp(x) is 0, so what is left is exp(x)*(1 + o(1))
|
|
385
|
+
#
|
|
386
|
+
# nil as soon as a term cannot be placed, and nil whenever two of them
|
|
387
|
+
# could cancel: x + x*sin(x) swings between 0 and 2*x, x - x**2*sin(x)
|
|
388
|
+
# swings through both infinities, and neither of them has a limit.
|
|
389
|
+
def dominant(g)
|
|
390
|
+
terms = (sum_terms(g) || return).last
|
|
391
|
+
measured = terms.map do |factors, coeff|
|
|
392
|
+
term = Simplify.rebuild_product(coeff, factors)
|
|
393
|
+
(measure(term) || return) + [term]
|
|
394
|
+
end
|
|
395
|
+
bounded_rest(measured) || least_order(measured) || ratio_dominance(measured)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# [kind, order, value] for one term of the sum: :divergent with the signed
|
|
399
|
+
# infinity it goes to, :bounded for one that stays in an interval,
|
|
400
|
+
# :oscillating for a bounded factor times something that does not stay in
|
|
401
|
+
# one (x*sin(x)). The order is the exponent of the leading term in t and
|
|
402
|
+
# is nil for a :divergent term that only exponential_fallback could
|
|
403
|
+
# decide - exp(1/t) has no series to read an order off. nil for a term
|
|
404
|
+
# that cannot be placed at all.
|
|
405
|
+
def measure(term)
|
|
406
|
+
if (value = value_of(term))
|
|
407
|
+
return nil if !infinite?(value) && value.each_node.any? { |n| n == OO }
|
|
408
|
+
return [infinite?(value) ? :divergent : :bounded, order_of(term), value]
|
|
409
|
+
end
|
|
410
|
+
coeff, factors = Simplify.factorize(term)
|
|
411
|
+
bounded, rest = factors.partition { |base, exp| bounded?(base, exp) }
|
|
412
|
+
return nil if bounded.empty?
|
|
413
|
+
rest = Simplify.rebuild_product(coeff, rest.to_h)
|
|
414
|
+
value = value_of(rest) || return
|
|
415
|
+
order = order_of(rest) || return
|
|
416
|
+
runaway = infinite?(value) || value.each_node.any? { |n| n == OO }
|
|
417
|
+
[runaway ? :oscillating : :bounded, order, nil]
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# Everything that is not a runaway stays in a bounded interval and the
|
|
421
|
+
# runaways agree on a direction; the constant of the sum is bounded too,
|
|
422
|
+
# so the terms decide alone. exp(x) + 1 needs this rather than the orders:
|
|
423
|
+
# exp has no series at infinity, so only its limit is known.
|
|
424
|
+
def bounded_rest(measured)
|
|
425
|
+
return nil if measured.any? { |kind,| kind == :oscillating }
|
|
426
|
+
values = measured.filter_map { |kind, _, value| value if kind == :divergent }
|
|
427
|
+
return nil if values.empty?
|
|
428
|
+
values.all? { |value| value == values.first } ? values.first : nil
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# One term is of strictly smallest order (most negative in t, so fastest
|
|
432
|
+
# in x) and runs away; every other term, oscillation included, is o() of
|
|
433
|
+
# it and cannot reach back.
|
|
434
|
+
def least_order(measured)
|
|
435
|
+
orders = measured.map { |_, order,| order }
|
|
436
|
+
return nil if orders.any?(&:nil?)
|
|
437
|
+
least = orders.min
|
|
438
|
+
return nil unless orders.count(least) == 1
|
|
439
|
+
kind, _, value = measured[orders.index(least)]
|
|
440
|
+
kind == :divergent ? value : nil
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# The last resort: divide by a term that runs away and ask whether all the
|
|
444
|
+
# others vanish against it, which leaves the sum as that term times
|
|
445
|
+
# 1 + o(1). exp(x) - x needs this - the exponential has no series at
|
|
446
|
+
# infinity, so there is no order to compare it by, but x/exp(x) is a limit
|
|
447
|
+
# of its own and it is zero. The rule does not re-enter itself: the
|
|
448
|
+
# quotients it builds are limits again, and one comparison is enough.
|
|
449
|
+
def ratio_dominance(measured)
|
|
450
|
+
return nil if @comparing
|
|
451
|
+
|
|
452
|
+
begin
|
|
453
|
+
@comparing = true
|
|
454
|
+
measured.each do |kind, _, value, term|
|
|
455
|
+
next unless kind == :divergent
|
|
456
|
+
others = measured.reject { |_, _, _, other| other.equal?(term) }
|
|
457
|
+
return value if others.all? { |_, _, _, other| vanishes?(other, term) }
|
|
458
|
+
end
|
|
459
|
+
nil
|
|
460
|
+
ensure
|
|
461
|
+
@comparing = false
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def vanishes?(term, against)
|
|
466
|
+
value = value_of((term / against).simplify)
|
|
467
|
+
value.is_a?(Num) && value.value.zero?
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# The one-sided limit at t -> 0+, and the leading exponent of the series
|
|
471
|
+
# there; nil when the expansion cannot supply them.
|
|
472
|
+
def value_of(e)
|
|
473
|
+
value = one_sided(e, :right)
|
|
474
|
+
value.is_a?(Limit) ? nil : value
|
|
475
|
+
rescue SeriesError, ZeroDivisionError
|
|
476
|
+
nil
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def order_of(e)
|
|
480
|
+
s = nil
|
|
481
|
+
[4, 8, 16, 32].each do |n|
|
|
482
|
+
s = adaptive(e, n)
|
|
483
|
+
break unless s.zero?
|
|
484
|
+
end
|
|
485
|
+
s.zero? ? nil : s.leading.first
|
|
486
|
+
rescue SeriesError, ZeroDivisionError
|
|
487
|
+
nil
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def log_of(g)
|
|
491
|
+
coeff, factors = Simplify.factorize(g)
|
|
492
|
+
raise SeriesError, "sign of #{g}" unless coeff.is_a?(Numeric) && coeff.real? && coeff.positive?
|
|
493
|
+
factors.reduce(Fn.new(:log, [Num.new(coeff)])) do |acc, (base, exp)|
|
|
494
|
+
piece =
|
|
495
|
+
if base == Simplify.exp_base then Expression.lift(exp)
|
|
496
|
+
elsif (tail = erfc_tail_log(base)) then Expression.lift(exp) * tail
|
|
497
|
+
else Expression.lift(exp) * Fn.new(:log, [base])
|
|
498
|
+
end
|
|
499
|
+
acc + piece
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# log(erfc(u)) for u -> +oo is -u**2 - log(u) - log(pi)/2 + o(1)
|
|
504
|
+
# [AS64, 7.1.23]. erfc(u) itself expands to the zero series there - it
|
|
505
|
+
# is smaller than every power - and the logarithm of that zero was how
|
|
506
|
+
# erfc(x)*exp(x**2)*x came out as oo instead of 1/sqrt(pi) (third
|
|
507
|
+
# review, D4). nil when the factor is not such an erfc.
|
|
508
|
+
def erfc_tail_log(base)
|
|
509
|
+
return nil unless base.is_a?(Fn) && base.name == :erfc && base.args.size == 1
|
|
510
|
+
u = base.args.first
|
|
511
|
+
return nil unless value_of(u) == OO
|
|
512
|
+
(Neg.new(u**2) - Fn.new(:log, [u]) - Fn.new(:log, [PI]) / 2).simplify
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def signed_infinity(c)
|
|
516
|
+
coefficient_sign(c).negative? ? Neg.new(OO).simplify : OO
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# -1 or 1 for a coefficient whose sign is decided - exactly (Decide) for
|
|
520
|
+
# a constant, by the assumptions for a parameter; a Float read off
|
|
521
|
+
# evalf took cos(10**-30) - 1 for 0 (fourth review). SeriesError when
|
|
522
|
+
# the sign is not known.
|
|
523
|
+
def coefficient_sign(c)
|
|
524
|
+
c = Expression.lift(c)
|
|
525
|
+
sign = c.variables.empty? ? Decide.sign(c) : RCAS.sign_of(c)
|
|
526
|
+
return 1 if sign == :positive
|
|
527
|
+
return -1 if sign == :negative
|
|
528
|
+
raise SeriesError, "sign of #{c} unknown"
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
# ---- the expansion ----------------------------------------------------------
|
|
532
|
+
|
|
533
|
+
def expand(f, order)
|
|
534
|
+
case f
|
|
535
|
+
when Num, Const then Series.constant(f, order)
|
|
536
|
+
when Var then f == T ? Series.variable(order) : Series.constant(f, order)
|
|
537
|
+
when Add then expand(f.left, order) + expand(f.right, order)
|
|
538
|
+
when Sub then expand(f.left, order) - expand(f.right, order)
|
|
539
|
+
when Neg then expand(f.arg, order).scale(Num.new(-1))
|
|
540
|
+
when Mul then product(f, order)
|
|
541
|
+
when Div then product(Mul.new(f.left, Pow.new(f.right, Num.new(-1))), order)
|
|
542
|
+
when Pow then power(f, order)
|
|
543
|
+
when Fn then function(f, order)
|
|
544
|
+
else raise SeriesError, "no series for #{f.class}"
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
# Factors are expanded to an order that keeps the product exact to +order+.
|
|
549
|
+
def product(f, order)
|
|
550
|
+
_, factors = Simplify.factorize(f)
|
|
551
|
+
coeff, = Simplify.factorize(f)
|
|
552
|
+
parts = factors.map { |base, exp| [base, exp] }
|
|
553
|
+
known = parts.map { |base, exp| expand_power(base, exp, order) }
|
|
554
|
+
lows = known.map(&:low)
|
|
555
|
+
total_low = lows.sum
|
|
556
|
+
series = parts.each_with_index.map do |(base, exp), i|
|
|
557
|
+
needed = order - (total_low - lows[i])
|
|
558
|
+
needed > order ? expand_power(base, exp, needed) : known[i]
|
|
559
|
+
end
|
|
560
|
+
series.reduce(Series.constant(coeff, order + [total_low, 0].min)) { |acc, s| acc * s }.truncate(order)
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def expand_power(base, exp, order)
|
|
564
|
+
return expand(base, order) if exp == 1
|
|
565
|
+
power(Pow.new(base, Expression.lift(exp)), order)
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def power(f, order)
|
|
569
|
+
base, exp = f.base, f.exponent
|
|
570
|
+
if exp.is_a?(Num) && exp.integer?
|
|
571
|
+
n = exp.value
|
|
572
|
+
s = expand(base, order)
|
|
573
|
+
return s.truncate(order)**0 if n.zero?
|
|
574
|
+
return (expand(base, order + [n - 1, 0].max)**n).truncate(order) if n.positive?
|
|
575
|
+
return (expand(base, order + 2 * s.low.abs)**n).truncate(order) if n.negative?
|
|
576
|
+
end
|
|
577
|
+
if base.is_a?(Fn) && base.name == :exp
|
|
578
|
+
return function(Fn.new(:exp, [base.args.first * exp]), order)
|
|
579
|
+
end
|
|
580
|
+
if exp.variables.include?(T.name)
|
|
581
|
+
return function(Fn.new(:exp, [exp * Fn.new(:log, [base])]), order)
|
|
582
|
+
end
|
|
583
|
+
s = expand(base, order)
|
|
584
|
+
raise SeriesError, "power of a series that is zero to the known order" if s.zero?
|
|
585
|
+
m = s.min_exponent
|
|
586
|
+
c = s.terms[m]
|
|
587
|
+
r = exp.is_a?(Num) ? exp.value : exp
|
|
588
|
+
raise SeriesError, "#{f}: symbolic exponent on a vanishing base" if !r.is_a?(Numeric) && !m.zero?
|
|
589
|
+
u = s.shift(-m).scale((Num.new(1) / c).simplify) - Series.constant(1, order - m)
|
|
590
|
+
binomial = ->(k) { (0...k).reduce(Num.new(1)) { |acc, j| (acc * (Expression.lift(r) - j) / (j + 1)).simplify } }
|
|
591
|
+
lead = Simplify.power_node(c, r)
|
|
592
|
+
Series.compose(u, order - m, &binomial).scale(lead.simplify).shift(r.is_a?(Numeric) ? m * r : 0)
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
# Functions that have no Taylor series at some points: abs and sign at 0
|
|
596
|
+
# (and abs anywhere off the real line), floor, ceil and round where they
|
|
597
|
+
# jump. Differentiating them there gave abs(x) the series 0 and
|
|
598
|
+
# sign(x) the limit 0 at 0 (third review, D3).
|
|
599
|
+
KINKED = %i[abs sign floor ceil round].freeze
|
|
600
|
+
|
|
601
|
+
# |u| and sign(u) where u -> 0 have no series, but on one side of the
|
|
602
|
+
# point they do: for t -> 0+ the sign of u is the sign of its leading
|
|
603
|
+
# coefficient, so |u| is that sign times u. one_sided sets the side
|
|
604
|
+
# (the fourth review: limit(abs(x), x, 0) had become formal, and abs is
|
|
605
|
+
# continuous there). nil where this does not apply.
|
|
606
|
+
def one_sided_kink(name, s, order)
|
|
607
|
+
return nil unless Thread.current[:rcas_one_sided] && %i[abs sign].include?(name)
|
|
608
|
+
# u -> 0 (the kink) or u -> oo (|1/t| at t -> 0+): the leading term decides
|
|
609
|
+
return nil unless s.min_exponent.negative? || (s.constant_term.is_a?(Num) && s.constant_term.value.zero?)
|
|
610
|
+
lead = s.terms[s.min_exponent]
|
|
611
|
+
return nil if lead.variables.include?(Series::LOG.name)
|
|
612
|
+
sign = begin
|
|
613
|
+
coefficient_sign(lead)
|
|
614
|
+
rescue SeriesError
|
|
615
|
+
return nil
|
|
616
|
+
end
|
|
617
|
+
name == :abs ? s.scale(Num.new(sign)) : Series.constant(Num.new(sign), order)
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
def removable(derivative, w, c)
|
|
621
|
+
value = Limits.limit(derivative, w, c)
|
|
622
|
+
return nil if value.is_a?(Limit) || infinite?(value) || value == UNDEFINED || !value.variables.empty? && value.variables != c.variables
|
|
623
|
+
value
|
|
624
|
+
rescue SeriesError, ZeroDivisionError, NotImplementedError, RCAS::Unsupported
|
|
625
|
+
nil
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def non_analytic!(f, c)
|
|
629
|
+
return unless KINKED.include?(f.name)
|
|
630
|
+
if %i[abs sign].include?(f.name) && !c.variables.empty? && %i[positive negative].include?(RCAS.sign_of(c))
|
|
631
|
+
return # |x + a| at x = 0 for a > 0 is x + a
|
|
632
|
+
end
|
|
633
|
+
value = begin
|
|
634
|
+
Expression.lift(c).evalf
|
|
635
|
+
rescue StandardError => rescued
|
|
636
|
+
RCAS.guard!(rescued)
|
|
637
|
+
nil
|
|
638
|
+
end
|
|
639
|
+
value = value.value if value.is_a?(Num)
|
|
640
|
+
real = value.is_a?(Numeric) && value.real?
|
|
641
|
+
at_kink =
|
|
642
|
+
case f.name
|
|
643
|
+
when :abs, :sign then !real || Decide.zero?(c) != false
|
|
644
|
+
when :floor, :ceil then !real || (Decide.zero?((c - Fn.new(:round, [c])).simplify) != false)
|
|
645
|
+
when :round then !real || Decide.zero?((c - Fn.new(:floor, [c]) - Num.new(Rational(1, 2))).simplify) != false
|
|
646
|
+
end
|
|
647
|
+
raise SeriesError, "#{f} has no series where its argument is #{c}" if at_kink
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
# The argument's series to the order asked for: a cancellation costs
|
|
651
|
+
# known terms (sqrt(x**2 + x) - x at infinity is 1/2 - 1/(8x) + ...,
|
|
652
|
+
# and at order 3 nothing of it was known), and a function of it then
|
|
653
|
+
# read an unknown constant term as 0 - exp of it came out as 1 + O(1/x**3)
|
|
654
|
+
# instead of starting with e**(1/2) (fourth review, D6).
|
|
655
|
+
# Only the constant term has to be known for the answer to be right; a
|
|
656
|
+
# shorter known order is carried by the result, and the adaptive loop at
|
|
657
|
+
# the top asks again for more.
|
|
658
|
+
def known_argument(argument, order)
|
|
659
|
+
s = expand(argument, order)
|
|
660
|
+
extra = order
|
|
661
|
+
4.times do
|
|
662
|
+
break if s.order >= 1
|
|
663
|
+
extra += 2 - s.order
|
|
664
|
+
s = expand(argument, extra)
|
|
665
|
+
end
|
|
666
|
+
s.order > order ? s.truncate(order) : s
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
def function(f, order)
|
|
670
|
+
raise SeriesError, "#{f.name} takes one argument" unless f.args.size == 1
|
|
671
|
+
name = f.name
|
|
672
|
+
return expand(Fn.new(:sin, f.args) / Fn.new(:cos, f.args), order) if name == :tan
|
|
673
|
+
|
|
674
|
+
s = known_argument(f.args.first, order)
|
|
675
|
+
return Series.constant(Fn.new(name, [s.constant_term]).simplify, order) if s.zero? || (s.terms.keys - [0]).empty?
|
|
676
|
+
|
|
677
|
+
if name == :log
|
|
678
|
+
m = s.min_exponent
|
|
679
|
+
c = s.terms[m]
|
|
680
|
+
u = s.shift(-m).scale((Num.new(1) / c).simplify) - Series.constant(1, order - m)
|
|
681
|
+
tail = Series.compose(u, order - m) { |k| k.zero? ? Num.new(0) : Num.new(Rational((-1)**(k + 1), k)) }
|
|
682
|
+
head = { 0 => (Fn.new(:log, [c]) + Num.new(m) * Series::LOG).simplify }
|
|
683
|
+
return Series.new(head, order) + tail.truncate(order)
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
if name == :atan && s.min_exponent.negative?
|
|
687
|
+
sign = coefficient_sign(s.terms[s.min_exponent])
|
|
688
|
+
half_pi = Series.constant((sign.negative? ? -PI / 2 : PI / 2).simplify, order)
|
|
689
|
+
return half_pi - function(Fn.new(:atan, [Pow.new(f.args.first, Num.new(-1))]), order)
|
|
690
|
+
end
|
|
691
|
+
if %i[erf erfc].include?(name) && s.min_exponent.negative?
|
|
692
|
+
# erf(+-oo) = +-1 up to an exponentially small tail: a constant, for limits
|
|
693
|
+
sign = coefficient_sign(s.terms[s.min_exponent])
|
|
694
|
+
value = name == :erf ? (sign.negative? ? -1 : 1) : (sign.negative? ? 2 : 0)
|
|
695
|
+
return Series.constant(Num.new(value), order)
|
|
696
|
+
end
|
|
697
|
+
if (kink = one_sided_kink(name, s, order))
|
|
698
|
+
return kink
|
|
699
|
+
end
|
|
700
|
+
raise SeriesError, "#{f} has an essential singularity" if s.min_exponent.negative?
|
|
701
|
+
c = s.constant_term
|
|
702
|
+
non_analytic!(f, c)
|
|
703
|
+
s0 = s - Series.constant(c, order)
|
|
704
|
+
w = Var.new(:_w)
|
|
705
|
+
derivative = Fn.new(name, [w])
|
|
706
|
+
factorial = 1
|
|
707
|
+
coefficients = []
|
|
708
|
+
Series.compose(s0, order) do |k|
|
|
709
|
+
while coefficients.size <= k
|
|
710
|
+
value = begin
|
|
711
|
+
derivative.subs(w => c).simplify
|
|
712
|
+
rescue ZeroDivisionError
|
|
713
|
+
# Si's derivative sin(t)/t at 0 has a removable hole, whose value
|
|
714
|
+
# is the limit; asin's 1/sqrt(1 - t**2) at 1 has a pole, and a
|
|
715
|
+
# derivative with no finite value is no Taylor coefficient
|
|
716
|
+
removable(derivative, w, c) or raise SeriesError, "#{f} has no Taylor series where its argument is #{c}"
|
|
717
|
+
end
|
|
718
|
+
coefficients << (value / factorial).simplify
|
|
719
|
+
derivative = derivative.diff(w)
|
|
720
|
+
factorial *= coefficients.size
|
|
721
|
+
end
|
|
722
|
+
coefficients[k]
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
end
|