Skip to content

Commit e50e01a

Browse files
Merge pull request #1631 from ChrisRackauckas-Claude/cleanup-ode-exports-1627
Clean up symbolic ODE solver exports (fixes #1627)
2 parents 8fdda7e + 7f0adc0 commit e50e01a

File tree

6 files changed

+119
-82
lines changed

6 files changed

+119
-82
lines changed

docs/src/manual/ode.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,27 @@ The analytical solution can be investigated symbolically using `observed(sys)`.
2828
This area is currently under heavy development. More solvers will be available in the near future.
2929

3030
```@docs
31-
Symbolics.LinearODE
31+
Symbolics.SymbolicLinearODE
3232
```
3333

3434
```@docs
3535
Symbolics.symbolic_solve_ode
3636
```
3737

38-
### Continuous Dynamical Systems
3938
```@docs
40-
Symbolics.solve_linear_system
39+
Symbolics.solve_symbolic_IVP
40+
```
41+
42+
```@docs
43+
Symbolics.solve_linear_ode_system
4144
```
4245

4346
### SymPy
4447

4548
```@docs
4649
Symbolics.sympy_ode_solve
4750
```
51+
52+
```@docs
53+
Symbolics.sympy_pythoncall_ode_solve
54+
```

src/Symbolics.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ export symbolic_solve
224224
include("diffeqs/diffeqs.jl")
225225
include("diffeqs/systems.jl")
226226
include("diffeqs/diffeq_helpers.jl")
227-
export LinearODE, IVP, symbolic_solve_ode, solve_linear_system, solve_IVP
227+
export SymbolicLinearODE, symbolic_solve_ode, solve_linear_ode_system, solve_symbolic_IVP
228228

229229
# Sympy Functions
230230

src/diffeqs/diffeq_helpers.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ function is_solution(solution, eq::Equation, x, t)
6767
is_solution(solution, eq.lhs - eq.rhs, x, t)
6868
end
6969

70-
function is_solution(solution, eq::LinearODE)
70+
function is_solution(solution, eq::SymbolicLinearODE)
7171
is_solution(solution, get_expression(eq), eq.x, eq.t)
7272
end
7373

src/diffeqs/diffeqs.jl

Lines changed: 68 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,20 @@ julia> @variables x, t
1818
x
1919
t
2020
21-
julia> eq = LinearODE(x, t, [1, 2, 3], 3exp(4t))
21+
julia> eq = SymbolicLinearODE(x, t, [1, 2, 3], 3exp(4t))
2222
(Dt^3)x + (3)(Dt^2)x + (2)(Dt^1)x + (1)(Dt^0)x ~ 3exp(4t)
2323
```
2424
"""
25-
struct LinearODE
25+
struct SymbolicLinearODE
2626
x::Num
2727
t::Num
2828
p::AbstractArray
2929
q::Any
3030
C::Vector{Num}
3131

32-
LinearODE(x, t, p, q) = new(x, t, p, q, variables(:C, 1:length(p)))
32+
SymbolicLinearODE(x, t, p, q) = new(x, t, p, q, variables(:C, 1:length(p)))
3333

34-
function LinearODE(expr, x, t)
34+
function SymbolicLinearODE(expr, x, t)
3535
if expr isa Equation
3636
expr = expr.lhs - expr.rhs
3737
end
@@ -76,41 +76,41 @@ function is_linear_ode(expr, x, t)
7676
return islinear && all(isempty.(get_variables.(A, x)))
7777
end
7878

79-
Dt(eq::LinearODE) = Differential(eq.t)
80-
order(eq::LinearODE) = length(eq.p)
79+
Dt(eq::SymbolicLinearODE) = Differential(eq.t)
80+
order(eq::SymbolicLinearODE) = length(eq.p)
8181

82-
"""Generates symbolic expression to represent `LinearODE`"""
83-
function get_expression(eq::LinearODE)
82+
"""Generates symbolic expression to represent `SymbolicLinearODE`"""
83+
function get_expression(eq::SymbolicLinearODE)
8484
(Dt(eq)^order(eq))(eq.x) + sum([(eq.p[n]) * (Dt(eq)^(n - 1))(eq.x) for n in 1:length(eq.p)]) ~ eq.q
8585
end
8686

87-
function Base.string(eq::LinearODE)
87+
function Base.string(eq::SymbolicLinearODE)
8888
"(D$(eq.t)^$(order(eq)))$(eq.x) + " *
8989
join(
9090
["($(eq.p[length(eq.p)-n]))(D$(eq.t)^$(length(eq.p)-n-1))$(eq.x)"
9191
for n in 0:(order(eq) - 1)],
9292
" + ") * " ~ $(eq.q)"
9393
end
9494

95-
Base.print(io::IO, eq::LinearODE) = print(io, string(eq))
96-
Base.show(io::IO, eq::LinearODE) = print(io, eq)
97-
Base.isequal(eq1::LinearODE, eq2::LinearODE) =
95+
Base.print(io::IO, eq::SymbolicLinearODE) = print(io, string(eq))
96+
Base.show(io::IO, eq::SymbolicLinearODE) = print(io, eq)
97+
Base.isequal(eq1::SymbolicLinearODE, eq2::SymbolicLinearODE) =
9898
isequal(eq1.x, eq2.x) && isequal(eq1.t, eq2.t) &&
9999
isequal(eq1.p, eq2.p) && isequal(eq1.q, eq2.q)
100100

101101
"""Returns true if q(t) = 0 for linear ODE `eq`"""
102-
is_homogeneous(eq::LinearODE) = isempty(Symbolics.get_variables(eq.q))
102+
is_homogeneous(eq::SymbolicLinearODE) = isempty(Symbolics.get_variables(eq.q))
103103
"""Returns true if all coefficient functions p(t) of `eq` are constant"""
104-
has_const_coeffs(eq::LinearODE) = all(isempty.(Symbolics.get_variables.(eq.p)))
104+
has_const_coeffs(eq::SymbolicLinearODE) = all(isempty.(Symbolics.get_variables.(eq.p)))
105105
"""Returns homgeneous version of `eq` where q(t) = 0"""
106-
to_homogeneous(eq::LinearODE) = LinearODE(eq.x, eq.t, eq.p, 0)
106+
to_homogeneous(eq::SymbolicLinearODE) = SymbolicLinearODE(eq.x, eq.t, eq.p, 0)
107107

108108
"""
109109
Returns the characteristic polynomial p of `eq` (must have constant coefficients) in terms of variable `r`
110110
111111
p(D) = Dⁿ + aₙ₋₁Dⁿ⁻¹ + ... + a₁D + a₀I
112112
"""
113-
function characteristic_polynomial(eq::LinearODE, r)
113+
function characteristic_polynomial(eq::SymbolicLinearODE, r)
114114
poly = 0
115115
@assert has_const_coeffs(eq) "ODE must have constant coefficients to generate characteristic polynomial"
116116
p = [eq.p; 1] # add implied coefficient of 1 to highest order
@@ -122,11 +122,11 @@ function characteristic_polynomial(eq::LinearODE, r)
122122
end
123123

124124
"""
125-
symbolic_solve_ode(eq::LinearODE)
125+
symbolic_solve_ode(eq::SymbolicLinearODE)
126126
Symbolically solve a linear ordinary differential equation
127127
128128
# Arguments
129-
- eq: a `LinearODE` to solve
129+
- eq: a `SymbolicLinearODE` to solve
130130
131131
# Returns
132132
Symbolic solution to the ODE
@@ -149,22 +149,22 @@ julia> @variables x, t
149149
t
150150
151151
# Integrating Factor (note that SymPy is required for integration)
152-
julia> symbolic_solve_ode(LinearODE(x, t, [5/t], 7t))
152+
julia> symbolic_solve_ode(SymbolicLinearODE(x, t, [5/t], 7t))
153153
(C₁ + t^7) / (t^5)
154154
155155
# Constant Coefficients and RRF (note that Nemo is required to find characteristic roots)
156-
julia> symbolic_solve_ode(LinearODE(x, t, [9, -6], 4exp(3t)))
156+
julia> symbolic_solve_ode(SymbolicLinearODE(x, t, [9, -6], 4exp(3t)))
157157
C₁*exp(3t) + C₂*t*exp(3t) + (2//1)*(t^2)*exp(3t)
158158
159-
julia> symbolic_solve_ode(LinearODE(x, t, [6, 5], 2exp(-t)*cos(t)))
159+
julia> symbolic_solve_ode(SymbolicLinearODE(x, t, [6, 5], 2exp(-t)*cos(t)))
160160
C₁*exp(-2t) + C₂*exp(-3t) + (1//5)*cos(t)*exp(-t) + (3//5)*exp(-t)*sin(t)
161161
162162
# Method of Undetermined Coefficients
163-
julia> symbolic_solve_ode(LinearODE(x, t, [-3, 2], 2t - 5))
163+
julia> symbolic_solve_ode(SymbolicLinearODE(x, t, [-3, 2], 2t - 5))
164164
(11//9) - (2//3)*t + C₁*exp(t) + C₂*exp(-3t)
165165
```
166166
"""
167-
function symbolic_solve_ode(eq::LinearODE)
167+
function symbolic_solve_ode(eq::SymbolicLinearODE)
168168
homogeneous_solutions = find_homogeneous_solutions(eq)
169169

170170
if is_homogeneous(eq) && homogeneous_solutions !== nothing
@@ -191,7 +191,7 @@ Symbolically solve an ODE
191191
- t: independent variable
192192
193193
# Supported Methods
194-
- all methods of solving linear ODEs mentioned for `symbolic_solve_ode(eq::LinearODE)`
194+
- all methods of solving linear ODEs mentioned for `symbolic_solve_ode(eq::SymbolicLinearODE)`
195195
- Clairaut's equation
196196
- Bernoulli equations
197197
@@ -208,7 +208,7 @@ julia> @variables x, t
208208
julia> Dt = Differential(t)
209209
Differential(t)
210210
211-
# LinearODE (via constant coefficients and RRF)
211+
# SymbolicLinearODE (via constant coefficients and RRF)
212212
julia> symbolic_solve_ode(9t*x - 6*Dt(x) ~ 4exp(3t), x, t)
213213
C₁*exp(3t) + C₂*t*exp(3t) + (2//1)*(t^2)*exp(3t)
214214
@@ -233,7 +233,7 @@ function symbolic_solve_ode(expr::Equation, x, t)
233233
end
234234

235235
if is_linear_ode(expr, x, t)
236-
eq = LinearODE(expr, x, t)
236+
eq = SymbolicLinearODE(expr, x, t)
237237
return symbolic_solve_ode(eq)
238238
end
239239
end
@@ -243,7 +243,7 @@ Find homogeneous solutions of linear ODE `eq` with integration constants of `eq.
243243
244244
Currently only works for constant coefficient ODEs
245245
"""
246-
function find_homogeneous_solutions(eq::LinearODE)
246+
function find_homogeneous_solutions(eq::SymbolicLinearODE)
247247
if has_const_coeffs(eq)
248248
return const_coeff_solve(to_homogeneous(eq))
249249
end
@@ -254,11 +254,11 @@ Find a particular solution to linear ODE `eq`
254254
255255
Currently works for any linear combination of exponentials, sin, cos, or an exponential times sin or cos (e.g. e^2t * cos(-t) + e^-3t + sin(5t))
256256
"""
257-
function find_particular_solution(eq::LinearODE)
257+
function find_particular_solution(eq::SymbolicLinearODE)
258258
# if q has multiple terms, find a particular solution for each and sum together
259259
terms = Symbolics.terms(eq.q)
260260
if length(terms) != 1
261-
solutions = find_particular_solution.(LinearODE.(Ref(eq.x), Ref(eq.t), Ref(eq.p), terms))
261+
solutions = find_particular_solution.(SymbolicLinearODE.(Ref(eq.x), Ref(eq.t), Ref(eq.p), terms))
262262
if any(s -> s === nothing, solutions)
263263
return nothing
264264
end
@@ -287,7 +287,7 @@ Returns homogeneous solutions to linear ODE `eq` with constant coefficients
287287
288288
xₕ(t) = C₁e^(r₁t) + C₂e^(r₂t) + ... + Cₙe^(rₙt)
289289
"""
290-
function const_coeff_solve(eq::LinearODE)
290+
function const_coeff_solve(eq::SymbolicLinearODE)
291291
@variables 𝓇
292292
p = characteristic_polynomial(eq, 𝓇)
293293
roots = symbolic_solve(p, 𝓇, dropmultiplicity = false)
@@ -319,7 +319,7 @@ end
319319
"""
320320
Solve almost any first order ODE using an integrating factor. Requires SymPy!
321321
"""
322-
function integrating_factor_solve(eq::LinearODE)
322+
function integrating_factor_solve(eq::SymbolicLinearODE)
323323
p = eq.p[1] # only p
324324
v = 0 # integrating factor
325325
if isempty(Symbolics.get_variables(p))
@@ -381,7 +381,7 @@ end
381381
"""
382382
For finding particular solution when q(t) = a*e^(rt)*cos(bt) (or sin(bt))
383383
"""
384-
function exp_trig_particular_solution(eq::LinearODE)
384+
function exp_trig_particular_solution(eq::SymbolicLinearODE)
385385
facs = _true_factors(eq.q)
386386

387387
a = prod(filter(fac -> isempty(Symbolics.get_variables(fac, [eq.t])), facs))
@@ -432,7 +432,7 @@ Exponential Response Formula: x_p(t) = a*e^(rt)/p(r) where p(r) is characteristi
432432
433433
Resonant Response Formula: If r is a characteristic root, multiply by t and take the derivative of p (possibly multiple times)
434434
"""
435-
function resonant_response_formula(eq::LinearODE)
435+
function resonant_response_formula(eq::SymbolicLinearODE)
436436
@assert has_const_coeffs(eq)
437437

438438
# get a and r from q = a*e^(rt)
@@ -455,7 +455,7 @@ function resonant_response_formula(eq::LinearODE)
455455
(substitute(expand_derivatives((Ds^k)(p)), Dict(𝓈 => r)))))
456456
end
457457

458-
function method_of_undetermined_coefficients(eq::LinearODE)
458+
function method_of_undetermined_coefficients(eq::SymbolicLinearODE)
459459
# constant
460460
p = eq.p[1]
461461
if isempty(Symbolics.get_variables(p, eq.t)) && isempty(Symbolics.get_variables(eq.q, eq.t))
@@ -526,16 +526,16 @@ end
526526
Initial value problem (IVP) for a linear ODE
527527
"""
528528
struct IVP
529-
eq::LinearODE
529+
eq::SymbolicLinearODE
530530
initial_conditions::Vector{Num} # values at t = 0 of nth derivative of x
531531

532-
function IVP(eq::LinearODE, initial_conditions::Vector{<:Number})
532+
function IVP(eq::SymbolicLinearODE, initial_conditions::Vector{<:Number})
533533
@assert length(initial_conditions) == order(eq) "# of Initial conditions must match order of ODE"
534534
new(eq, initial_conditions)
535535
end
536536
end
537537

538-
function solve_IVP(ivp::IVP)
538+
function solve_symbolic_IVP(ivp::IVP)
539539
general_solution = symbolic_solve_ode(ivp.eq)
540540
if general_solution === nothing
541541
return nothing
@@ -559,6 +559,35 @@ function solve_IVP(ivp::IVP)
559559
return expand(simplify(substitute(general_solution, symbolic_solve(eqs, ivp.eq.C)[1])))
560560
end
561561

562+
"""
563+
solve_symbolic_IVP(eq::SymbolicLinearODE, initial_conditions::Vector{<:Number})
564+
565+
Solve an initial value problem for a linear ODE with given initial conditions.
566+
567+
# Arguments
568+
- `eq`: A `SymbolicLinearODE` to solve
569+
- `initial_conditions`: Vector of initial conditions for x(0), x'(0), x''(0), etc.
570+
571+
# Returns
572+
Symbolic solution satisfying the initial conditions
573+
574+
# Examples
575+
```jldoctest
576+
julia> using Symbolics
577+
julia> @variables x, t
578+
2-element Vector{Num}:
579+
x
580+
t
581+
582+
julia> eq = SymbolicLinearODE(x, t, [-3, 2], 0) # d²x/dt² + 2dx/dt - 3x = 0
583+
julia> solve_symbolic_IVP(eq, [1, -1]) # x(0) = 1, x'(0) = -1
584+
(1//2)*exp(-3t) + (1//2)*exp(t)
585+
```
586+
"""
587+
function solve_symbolic_IVP(eq::SymbolicLinearODE, initial_conditions::Vector{<:Number})
588+
return solve_symbolic_IVP(IVP(eq, initial_conditions))
589+
end
590+
562591
"""
563592
Solve Clairaut's equation of the form x = x'*t + f(x').
564593
@@ -602,7 +631,7 @@ function solve_clairaut(expr, x, t)
602631
end
603632

604633
"""
605-
Linearize a Bernoulli equation of the form dx/dt + p(t)x = q(t)x^n into a `LinearODE` of the form dv/dt + (1-n)p(t)v = (1-n)q(t) where v = x^(1-n)
634+
Linearize a Bernoulli equation of the form dx/dt + p(t)x = q(t)x^n into a `SymbolicLinearODE` of the form dv/dt + (1-n)p(t)v = (1-n)q(t) where v = x^(1-n)
606635
"""
607636
function linearize_bernoulli(expr, x, t, v)
608637
Dt = Differential(t)
@@ -643,7 +672,7 @@ function linearize_bernoulli(expr, x, t, v)
643672
p //= leading_coeff
644673
q //= leading_coeff
645674

646-
return LinearODE(v, t, [p*(1-n)], q*(1-n)), n
675+
return SymbolicLinearODE(v, t, [p*(1-n)], q*(1-n)), n
647676
end
648677

649678
"""

src/diffeqs/systems.jl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Returns evolution matrix e^(tD)
66
evo_mat(D::Matrix{<:Number}, t::Num) = diagm(exp.(t .* diag(D)))
77

88
"""
9-
solve_linear_system(A::Matrix{<:Number}, x0::Vector{<:Number}, t::Num)
9+
solve_linear_ode_system(A::Matrix{<:Number}, x0::Vector{<:Number}, t::Num)
1010
Solve linear continuous dynamical system of differential equations of the form Ax = x' with initial condition x0
1111
1212
# Arguments
@@ -24,18 +24,18 @@ julia> @variables t
2424
1-element Vector{Num}:
2525
t
2626
27-
julia> solve_linear_system([1 0; 0 -1], [1, -1], t) # requires Nemo
27+
julia> solve_linear_ode_system([1 0; 0 -1], [1, -1], t) # requires Nemo
2828
2-element Vector{Num}:
2929
exp(t)
3030
-exp(-t)
3131
32-
julia> solve_linear_system([-3 4; -2 3], [7, 2], t) # requires Groebner
32+
julia> solve_linear_ode_system([-3 4; -2 3], [7, 2], t) # requires Groebner
3333
2-element Vector{Num}:
3434
(10//1)*exp(-t) - (3//1)*exp(t)
3535
(5//1)*exp(-t) - (3//1)*exp(t)
3636
```
3737
"""
38-
function solve_linear_system(A::Matrix{<:Number}, x0::Vector{<:Number}, t::Num)
38+
function solve_linear_ode_system(A::Matrix{<:Number}, x0::Vector{<:Number}, t::Num)
3939
# Check A is square
4040
if size(A, 1) != size(A, 2)
4141
throw(ArgumentError("Matrix A must be square."))
@@ -112,4 +112,5 @@ function symbolic_eigen(A::Matrix{<:Number})
112112
end
113113

114114
return Eigen(values, S)
115-
end
115+
end
116+

0 commit comments

Comments
 (0)