You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/src/examples/perturbation.md
+45-39
Original file line number
Diff line number
Diff line change
@@ -2,21 +2,27 @@
2
2
3
3
## Prelims
4
4
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/):
6
6
7
-
```julia
8
-
using Symbolics, SymbolicUtils
7
+
```@example perturbation
8
+
using Symbolics
9
9
10
-
def_taylor(x, ps) =sum([a * x^i for (i, a) inenumerate(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)])
12
11
13
12
function collect_powers(eq, x, ns; max_power = 100)
14
13
eq = substitute(expand(eq), Dict(x^j => 0 for j in (last(ns) + 1):max_power))
15
14
16
15
eqs = []
17
16
for i in ns
18
17
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)
20
26
end
21
27
eqs
22
28
end
@@ -44,77 +50,77 @@ As the first ODE example, we have chosen a simple and well-behaved problem, whic
44
50
45
51
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
46
52
47
-
```julia
53
+
```@example perturbation
48
54
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]
51
58
```
52
59
53
60
Next, we define $x$.
54
61
55
-
```julia
56
-
x =def_taylor(ϵ, y[3:end], y[2])
62
+
```@example perturbation
63
+
x = def_taylor(ϵ, y)
57
64
```
58
65
59
66
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
60
67
61
-
```julia
62
-
∂∂x =def_taylor(ϵ, ∂∂y[3:end], ∂∂y[2])
68
+
```@example perturbation
69
+
∂∂x = def_taylor(ϵ, ∂∂y)
63
70
```
64
71
65
72
as the second derivative of `x`. After rearrangement, our governing equation is $\ddot{x}(t)(1 + \epsilon x(t))^{-2} + 1 = 0$, or
66
73
67
-
```julia
74
+
```@example perturbation
68
75
eq = ∂∂x * (1 + ϵ * x)^2 + 1
69
76
```
70
77
71
78
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)
72
79
73
-
```julia
74
-
eqs =collect_powers(eq, ϵ, 1:n)
80
+
```@example perturbation
81
+
eqs = collect_powers(eq, ϵ, 0:order)
75
82
```
76
83
77
84
and,
78
85
79
-
```julia
86
+
```@example perturbation
80
87
vals = solve_coef(eqs, ∂∂y)
81
88
```
82
89
83
90
Our system of ODEs is forming. Now is the time to convert `∂∂`s to the correct **Symbolics.jl** form by substitution:
84
91
85
-
```julia
92
+
```@example perturbation
86
93
subs = Dict(∂∂y[i] => D(D(y[i])) for i in eachindex(y))
87
94
eqs = [substitute(first(v), subs) ~ substitute(last(v), subs) for v in vals]
88
95
```
89
96
90
97
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.
91
98
92
-
```julia
99
+
```@example perturbation
93
100
using ModelingToolkit, DifferentialEquations
94
101
95
102
@mtkbuild sys = ODESystem(eqs, t)
96
103
unknowns(sys)
97
104
```
98
105
99
-
```julia
106
+
```@example perturbation
100
107
# the initial conditions
101
108
# 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])
104
110
105
111
prob = ODEProblem(sys, u0, (0, 3.0))
106
-
sol =solve(prob; dtmax =0.01)
112
+
sol = solve(prob; dtmax = 0.01);
107
113
```
108
114
109
115
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.
110
116
111
-
```julia
112
-
X = 𝜀 ->sum([𝜀^(i -1) * sol[y[i]]fori ineachindex(y)])
117
+
```@example perturbation
118
+
X = 𝜀 -> sum([𝜀^(i - 1) * sol[yi] for (i, yi) in enumerate(y)])
113
119
```
114
120
115
121
Using `X`, we can plot the trajectory for a range of $𝜀$s.
116
122
117
-
```julia
123
+
```@example perturbation
118
124
using Plots
119
125
120
126
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
128
134
129
135
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.
130
136
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)
137
144
```
138
145
139
146
This time we also need the first derivative terms. Continuing,
140
147
141
-
```julia
148
+
```@example perturbation
142
149
eq = ∂∂x + 2 * ϵ * ∂x + x
143
150
eqs = collect_powers(eq, ϵ, 0:n)
144
151
vals = solve_coef(eqs, ∂∂y)
145
152
```
146
153
147
154
Next, we need to replace `∂`s and `∂∂`s with their **Symbolics.jl** counterparts:
148
155
149
-
```julia
156
+
```@example perturbation
150
157
subs1 = Dict(∂y[i] => D(y[i]) for i in eachindex(y))
151
158
subs2 = Dict(∂∂y[i] => D(D(y[i])) for i in eachindex(y))
152
159
subs = subs1 ∪ subs2
@@ -155,19 +162,18 @@ eqs = [substitute(first(v), subs) ~ substitute(last(v), subs) for v in vals]
155
162
156
163
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)$,
157
164
158
-
```julia
165
+
```@example perturbation
159
166
@mtkbuild sys = ODESystem(eqs, t)
160
167
```
161
168
162
-
```julia
169
+
```@example perturbation
163
170
# 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])
166
172
167
173
prob = ODEProblem(sys, u0, (0, 50.0))
168
174
sol = solve(prob; dtmax = 0.01)
169
175
170
-
X = 𝜀 ->sum([𝜀^(i -1) * sol[y[i]]fori ineachindex(y)])
176
+
X = 𝜀 -> sum([𝜀^(i - 1) * sol[yi] for (i, yi) in enumerate(y)])
0 commit comments