Skip to content

Commit 3a110df

Browse files
Merge pull request #3098 from karlwessel/master
fix first ODE perturbation example
2 parents 1f81661 + 8eaa427 commit 3a110df

File tree

1 file changed

+45
-39
lines changed

1 file changed

+45
-39
lines changed

docs/src/examples/perturbation.md

+45-39
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,27 @@
22

33
## Prelims
44

5-
In the previous tutorial, [Mixed Symbolic-Numeric Perturbation Theory](https://symbolics.juliasymbolics.org/stable/examples/perturbation/), we discussed how to solve algebraic equations using **Symbolics.jl**. Here, our goal is to extend the method to differential equations. First, we import the following helper functions that were introduced in [Mixed Symbolic/Numerical Methods for Perturbation Theory - Algebraic Equations](@ref perturb_alg):
5+
In the previous tutorial, [Mixed Symbolic-Numeric Perturbation Theory](https://symbolics.juliasymbolics.org/stable/examples/perturbation/), we discussed how to solve algebraic equations using **Symbolics.jl**. Here, our goal is to extend the method to differential equations. First, we import the following helper functions that were introduced in [Mixed Symbolic/Numerical Methods for Perturbation Theory - Algebraic Equations](https://symbolics.juliasymbolics.org/stable/examples/perturbation/):
66

7-
```julia
8-
using Symbolics, SymbolicUtils
7+
```@example perturbation
8+
using Symbolics
99
10-
def_taylor(x, ps) = sum([a * x^i for (i, a) in enumerate(ps)])
11-
def_taylor(x, ps, p₀) = p₀ + def_taylor(x, ps)
10+
def_taylor(x, ps) = sum([a * x^(i - 1) for (i, a) in enumerate(ps)])
1211
1312
function collect_powers(eq, x, ns; max_power = 100)
1413
eq = substitute(expand(eq), Dict(x^j => 0 for j in (last(ns) + 1):max_power))
1514
1615
eqs = []
1716
for i in ns
1817
powers = Dict(x^j => (i == j ? 1 : 0) for j in 1:last(ns))
19-
push!(eqs, substitute(eq, powers))
18+
e = substitute(eq, powers)
19+
20+
# manually remove zeroth order from higher orders
21+
if 0 in ns && i != 0
22+
e = e - eqs[1]
23+
end
24+
25+
push!(eqs, e)
2026
end
2127
eqs
2228
end
@@ -44,77 +50,77 @@ As the first ODE example, we have chosen a simple and well-behaved problem, whic
4450

4551
with the initial conditions $x(0) = 0$, and $\dot{x}(0) = 1$. Note that for $\epsilon = 0$, this equation transforms back to the standard one. Let's start with defining the variables
4652

47-
```julia
53+
```@example perturbation
4854
using ModelingToolkit: t_nounits as t, D_nounits as D
49-
n = 3
50-
@variables ϵ y[1:n](t) ∂∂y[1:n](t)
55+
order = 2
56+
n = order + 1
57+
@variables ϵ (y(t))[1:n] (∂∂y(t))[1:n]
5158
```
5259

5360
Next, we define $x$.
5461

55-
```julia
56-
x = def_taylor(ϵ, y[3:end], y[2])
62+
```@example perturbation
63+
x = def_taylor(ϵ, y)
5764
```
5865

5966
We need the second derivative of `x`. It may seem that we can do this using `Differential(t)`; however, this operation needs to wait for a few steps because we need to manipulate the differentials as separate variables. Instead, we define dummy variables `∂∂y` as the placeholder for the second derivatives and define
6067

61-
```julia
62-
∂∂x = def_taylor(ϵ, ∂∂y[3:end], ∂∂y[2])
68+
```@example perturbation
69+
∂∂x = def_taylor(ϵ, ∂∂y)
6370
```
6471

6572
as the second derivative of `x`. After rearrangement, our governing equation is $\ddot{x}(t)(1 + \epsilon x(t))^{-2} + 1 = 0$, or
6673

67-
```julia
74+
```@example perturbation
6875
eq = ∂∂x * (1 + ϵ * x)^2 + 1
6976
```
7077

7178
The next two steps are the same as the ones for algebraic equations (note that we pass `1:n` to `collect_powers` because the zeroth order term is needed here)
7279

73-
```julia
74-
eqs = collect_powers(eq, ϵ, 1:n)
80+
```@example perturbation
81+
eqs = collect_powers(eq, ϵ, 0:order)
7582
```
7683

7784
and,
7885

79-
```julia
86+
```@example perturbation
8087
vals = solve_coef(eqs, ∂∂y)
8188
```
8289

8390
Our system of ODEs is forming. Now is the time to convert `∂∂`s to the correct **Symbolics.jl** form by substitution:
8491

85-
```julia
92+
```@example perturbation
8693
subs = Dict(∂∂y[i] => D(D(y[i])) for i in eachindex(y))
8794
eqs = [substitute(first(v), subs) ~ substitute(last(v), subs) for v in vals]
8895
```
8996

9097
We are nearly there! From this point on, the rest is standard ODE solving procedures. Potentially, we can use a symbolic ODE solver to find a closed form solution to this problem. However, **Symbolics.jl** currently does not support this functionality. Instead, we solve the problem numerically. We form an `ODESystem`, lower the order (convert second derivatives to first), generate an `ODEProblem` (after passing the correct initial conditions), and, finally, solve it.
9198

92-
```julia
99+
```@example perturbation
93100
using ModelingToolkit, DifferentialEquations
94101
95102
@mtkbuild sys = ODESystem(eqs, t)
96103
unknowns(sys)
97104
```
98105

99-
```julia
106+
```@example perturbation
100107
# the initial conditions
101108
# everything is zero except the initial velocity
102-
u0 = zeros(2n + 2)
103-
u0[3] = 1.0 # y₀ˍt
109+
u0 = Dict([unknowns(sys) .=> 0; D(y[1]) => 1])
104110
105111
prob = ODEProblem(sys, u0, (0, 3.0))
106-
sol = solve(prob; dtmax = 0.01)
112+
sol = solve(prob; dtmax = 0.01);
107113
```
108114

109115
Finally, we calculate the solution to the problem as a function of `ϵ` by substituting the solution to the ODE system back into the defining equation for `x`. Note that `𝜀` is a number, compared to `ϵ`, which is a symbolic variable.
110116

111-
```julia
112-
X = 𝜀 -> sum([𝜀^(i - 1) * sol[y[i]] for i in eachindex(y)])
117+
```@example perturbation
118+
X = 𝜀 -> sum([𝜀^(i - 1) * sol[yi] for (i, yi) in enumerate(y)])
113119
```
114120

115121
Using `X`, we can plot the trajectory for a range of $𝜀$s.
116122

117-
```julia
123+
```@example perturbation
118124
using Plots
119125
120126
plot(sol.t, hcat([X(𝜀) for 𝜀 in 0.0:0.1:0.5]...))
@@ -128,25 +134,26 @@ For the next example, we have chosen a simple example from a very important clas
128134

129135
The goal is to solve $\ddot{x} + 2\epsilon\dot{x} + x = 0$, where the dot signifies time-derivatives and the initial conditions are $x(0) = 0$ and $\dot{x}(0) = 1$. If $\epsilon = 0$, the problem reduces to the simple linear harmonic oscillator with the exact solution $x(t) = \sin(t)$. We follow the same steps as the previous example.
130136

131-
```julia
132-
n = 3
133-
@variables ϵ t y[1:n](t) ∂y[1:n] ∂∂y[1:n]
134-
x = def_taylor(ϵ, y[3:end], y[2])
135-
∂x = def_taylor(ϵ, ∂y[3:end], ∂y[2])
136-
∂∂x = def_taylor(ϵ, ∂∂y[3:end], ∂∂y[2])
137+
```@example perturbation
138+
order = 2
139+
n = order + 1
140+
@variables ϵ (y(t))[1:n] (∂y)[1:n] (∂∂y)[1:n]
141+
x = def_taylor(ϵ, y)
142+
∂x = def_taylor(ϵ, ∂y)
143+
∂∂x = def_taylor(ϵ, ∂∂y)
137144
```
138145

139146
This time we also need the first derivative terms. Continuing,
140147

141-
```julia
148+
```@example perturbation
142149
eq = ∂∂x + 2 * ϵ * ∂x + x
143150
eqs = collect_powers(eq, ϵ, 0:n)
144151
vals = solve_coef(eqs, ∂∂y)
145152
```
146153

147154
Next, we need to replace ``s and `∂∂`s with their **Symbolics.jl** counterparts:
148155

149-
```julia
156+
```@example perturbation
150157
subs1 = Dict(∂y[i] => D(y[i]) for i in eachindex(y))
151158
subs2 = Dict(∂∂y[i] => D(D(y[i])) for i in eachindex(y))
152159
subs = subs1 ∪ subs2
@@ -155,19 +162,18 @@ eqs = [substitute(first(v), subs) ~ substitute(last(v), subs) for v in vals]
155162

156163
We continue with converting 'eqs' to an `ODEProblem`, solving it, and finally plot the results against the exact solution to the original problem, which is $x(t, \epsilon) = (1 - \epsilon)^{-1/2} e^{-\epsilon t} \sin((1- \epsilon^2)^{1/2}t)$,
157164

158-
```julia
165+
```@example perturbation
159166
@mtkbuild sys = ODESystem(eqs, t)
160167
```
161168

162-
```julia
169+
```@example perturbation
163170
# the initial conditions
164-
u0 = zeros(2n + 2)
165-
u0[3] = 1.0 # y₀ˍt
171+
u0 = Dict([unknowns(sys) .=> 0; D(y[1]) => 1])
166172
167173
prob = ODEProblem(sys, u0, (0, 50.0))
168174
sol = solve(prob; dtmax = 0.01)
169175
170-
X = 𝜀 -> sum([𝜀^(i - 1) * sol[y[i]] for i in eachindex(y)])
176+
X = 𝜀 -> sum([𝜀^(i - 1) * sol[yi] for (i, yi) in enumerate(y)])
171177
T = sol.t
172178
Y = 𝜀 -> exp.(-𝜀 * T) .* sin.(sqrt(1 - 𝜀^2) * T) / sqrt(1 - 𝜀^2) # exact solution
173179

0 commit comments

Comments
 (0)