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/laplace.rb
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCAS
|
|
4
|
+
# The Laplace transform and its inverse.
|
|
5
|
+
#
|
|
6
|
+
# laplace(exp(3*t), t, s) # => 1/(s - 3)
|
|
7
|
+
# laplace(t**2, t, s) # => 2/s**3
|
|
8
|
+
# laplace(exp(-t)*sin(2*t), t, s) # => 2/((1 + s)**2 + 4)
|
|
9
|
+
# inverse_laplace(1/(s**2 + 1), s, t) # => sin(t)
|
|
10
|
+
#
|
|
11
|
+
# The transform turns differentiation into multiplication by s, which is
|
|
12
|
+
# why it solves linear differential equations by algebra. rcas computes it
|
|
13
|
+
# from the table plus two rules: the first shift, exp(a*t)*f(t) at s - a,
|
|
14
|
+
# and multiplication by t, which differentiates in s. The inverse goes
|
|
15
|
+
# through partial fractions, so it covers the rational transforms a course
|
|
16
|
+
# produces.
|
|
17
|
+
#
|
|
18
|
+
# Sources (keys: MANUAL.md, Sources): the table and the shift rules as in
|
|
19
|
+
# [BD12, ch. 6]; partial fractions from rational_function.rb.
|
|
20
|
+
module Laplace
|
|
21
|
+
class Error < ArgumentError; end
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# ---- the transform -----------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
def transform(f, t, s)
|
|
28
|
+
t = Expression.lift(t)
|
|
29
|
+
s = Expression.lift(s)
|
|
30
|
+
f = Expression.lift(f).simplify
|
|
31
|
+
constant, terms = Simplify.termize(f, simplify: true)
|
|
32
|
+
result = Scalar.zero?(Num.new(constant)) ? Num.new(0) : (Num.new(constant) / s)
|
|
33
|
+
terms.each do |factors, coefficient|
|
|
34
|
+
# a factor free of t is a constant of the transform: a*sin(t) is
|
|
35
|
+
# a/(1 + s**2) (the fourth review, L7, found it refused)
|
|
36
|
+
constants, moving = factors.partition { |base, exp| !base.variables.include?(t.name) && !Expression.lift(exp).variables.include?(t.name) }
|
|
37
|
+
term = Simplify.rebuild_product(1, moving.to_h)
|
|
38
|
+
scale = Simplify.rebuild_product(coefficient, constants.to_h)
|
|
39
|
+
result += scale * term_transform(term, t, s)
|
|
40
|
+
end
|
|
41
|
+
result.simplify
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# A single term: t**n times something the table knows.
|
|
45
|
+
def term_transform(term, t, s)
|
|
46
|
+
power, rest = split_power(term, t)
|
|
47
|
+
base = table_transform(rest, t, s)
|
|
48
|
+
raise Error, "laplace: no transform known for #{term}" if base.nil?
|
|
49
|
+
return base if power.zero?
|
|
50
|
+
(Num.new((-1)**power) * base.diff(s, power)).simplify
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# t**n * rest => [n, rest]
|
|
54
|
+
def split_power(term, t)
|
|
55
|
+
coefficient, factors = Simplify.factorize(term)
|
|
56
|
+
power = factors[t]
|
|
57
|
+
return [0, term] unless power.is_a?(Integer) && power.positive?
|
|
58
|
+
rest = factors.reject { |base, _| base == t }
|
|
59
|
+
[power, Simplify.rebuild_product(coefficient, rest)]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# 1, exp(a t), sin/cos(w t), sinh/cosh(w t), and exp(a t) times those.
|
|
63
|
+
def table_transform(f, t, s)
|
|
64
|
+
f = f.simplify
|
|
65
|
+
return f / s unless Solve.depends?(f, t) # a constant a is a/s, not 1/s
|
|
66
|
+
shift, rest = split_exponential(f, t)
|
|
67
|
+
shifted = shift ? s - shift : s
|
|
68
|
+
value =
|
|
69
|
+
if !Solve.depends?(rest, t) then rest / shifted
|
|
70
|
+
elsif rest.is_a?(Fn) && (rate = linear_rate(rest.args.first, t))
|
|
71
|
+
case rest.name
|
|
72
|
+
when :sin then rate / (shifted**2 + rate**2)
|
|
73
|
+
when :cos then shifted / (shifted**2 + rate**2)
|
|
74
|
+
when :sinh then rate / (shifted**2 - rate**2)
|
|
75
|
+
when :cosh then shifted / (shifted**2 - rate**2)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
value&.simplify
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# exp(a*t) * rest => [a, rest]
|
|
82
|
+
def split_exponential(f, t)
|
|
83
|
+
coefficient, factors = Simplify.factorize(f)
|
|
84
|
+
exponent = factors[Simplify.exp_base]
|
|
85
|
+
return [nil, f] if exponent.nil?
|
|
86
|
+
rate = linear_rate(Expression.lift(exponent), t) or return [nil, f]
|
|
87
|
+
rest = factors.reject { |base, _| base == Simplify.exp_base }
|
|
88
|
+
[rate, Simplify.rebuild_product(coefficient, rest)]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# a*t => a, anything else => nil
|
|
92
|
+
def linear_rate(u, t)
|
|
93
|
+
coefficients = Solve.polynomial_coefficients(u, t)
|
|
94
|
+
return nil unless coefficients && coefficients.size == 2 && Scalar.zero?(coefficients[0])
|
|
95
|
+
coefficients[1]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# ---- the inverse --------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
def inverse(f, s, t)
|
|
101
|
+
s = Expression.lift(s)
|
|
102
|
+
t = Expression.lift(t)
|
|
103
|
+
f = Expression.lift(f).simplify
|
|
104
|
+
pieces = begin
|
|
105
|
+
RationalFunction.apart(f, s)
|
|
106
|
+
rescue DomainError
|
|
107
|
+
f
|
|
108
|
+
end
|
|
109
|
+
constant, terms = Simplify.termize(pieces, simplify: true)
|
|
110
|
+
raise Error, "inverse_laplace: #{f} has a constant part, which is a delta" unless constant.zero?
|
|
111
|
+
terms.map { |factors, coefficient| Num.new(coefficient) * piece_inverse(Simplify.rebuild_product(1, factors), s, t) }
|
|
112
|
+
.reduce(Num.new(0)) { |a, b| a + b }.simplify
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def piece_inverse(piece, s, t)
|
|
116
|
+
numerator = RationalFunction.numer(piece)
|
|
117
|
+
denominator = RationalFunction.denom(piece)
|
|
118
|
+
raise Error, "inverse_laplace: #{piece} is not a proper rational function of #{s}" unless Solve.depends?(denominator, s)
|
|
119
|
+
factorization = denominator.to_poly(QQ[s.name]).factor
|
|
120
|
+
raise Error, "inverse_laplace: #{piece} has more than one kind of factor; give it as partial fractions" unless factorization.factors.size == 1
|
|
121
|
+
|
|
122
|
+
factor, multiplicity = factorization.factors.first
|
|
123
|
+
unit = factorization.unit
|
|
124
|
+
degree = factor.degree
|
|
125
|
+
coefficients = (0..degree).map { |k| factor.coeff(k).value }
|
|
126
|
+
case degree
|
|
127
|
+
when 1 then linear_inverse(numerator, coefficients, multiplicity, unit, s, t)
|
|
128
|
+
when 2 then quadratic_inverse(numerator, coefficients, multiplicity, unit, s, t)
|
|
129
|
+
else raise Error, "inverse_laplace: the denominator factor #{factor} has degree #{degree}"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# c/(a*s + b)**n => c * t**(n-1) * exp(-b/a * t) / (a**n * (n-1)!)
|
|
134
|
+
def linear_inverse(numerator, coefficients, multiplicity, unit, s, t)
|
|
135
|
+
raise Error, "inverse_laplace: the numerator #{numerator} is not constant" if Solve.depends?(numerator, s)
|
|
136
|
+
b, a = coefficients
|
|
137
|
+
rate = Num.new(Simplify.normalize_number(Rational(-b, a)))
|
|
138
|
+
scale = (numerator / (unit * Num.new(a)**multiplicity * RCAS.factorial(multiplicity - 1))).simplify
|
|
139
|
+
(scale * t**(multiplicity - 1) * Fn.new(:exp, [rate * t])).simplify
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# (A*s + B) / ((s - p)**2 + w**2) => exp(p*t) * (A*cos(w*t) + (B + A*p)/w * sin(w*t))
|
|
143
|
+
def quadratic_inverse(numerator, coefficients, multiplicity, unit, s, t)
|
|
144
|
+
raise Error, "inverse_laplace: a repeated quadratic factor is not supported" unless multiplicity == 1
|
|
145
|
+
c, b, a = coefficients
|
|
146
|
+
centre = Num.new(Simplify.normalize_number(Rational(-b, 2 * a)))
|
|
147
|
+
square = Num.new(Simplify.normalize_number(Rational(c, a) - Rational(b, 2 * a)**2))
|
|
148
|
+
raise Error, "inverse_laplace: the quadratic factor is not irreducible" unless square.value.positive?
|
|
149
|
+
omega = RCAS.sqrt(square).simplify
|
|
150
|
+
linear = Solve.polynomial_coefficients(numerator, s) or raise Error, "inverse_laplace: #{numerator} is not linear in #{s}"
|
|
151
|
+
slope = linear[1] || Num.new(0)
|
|
152
|
+
offset = linear[0]
|
|
153
|
+
scale = (Num.new(1) / (unit * Num.new(a))).simplify
|
|
154
|
+
cosine = slope
|
|
155
|
+
sine = ((offset + slope * centre) / omega).simplify
|
|
156
|
+
(scale * Fn.new(:exp, [centre * t]) * (cosine * Fn.new(:cos, [omega * t]) + sine * Fn.new(:sin, [omega * t]))).simplify
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|