Skip to content

Commit 5f01f6a

Browse files
committed
Make perturbation tutorial more understandable
1 parent 5ade47f commit 5f01f6a

File tree

1 file changed

+48
-35
lines changed

1 file changed

+48
-35
lines changed

docs/src/tutorials/perturbation.md

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -47,52 +47,55 @@ quintic = x^5 + ϵ*x ~ 1
4747
```
4848
If $ϵ = 1$, we get our original problem. With $ϵ = 0$, the problem transforms to the easy quintic equation $x^5 = 1$ with the trivial real solution $x = 1$ (and four complex solutions which we ignore). Next, expand $x$ as a power series in $ϵ$:
4949
```@example perturb
50-
x_taylor = series(x, ϵ, 0:3) # expand x to third order
50+
x_taylor = series(x, ϵ, 0:7) # expand x in a power series in ϵ
51+
x_coeffs = taylor_coeff(x_taylor, ϵ) # TODO: get coefficients at series creation
52+
x_taylor
5153
```
5254
Then insert this into the quintic equation and expand it, too, to the same order:
5355
```@example perturb
5456
quintic_taylor = substitute(quintic, x => x_taylor)
55-
quintic_taylor = taylor(quintic_taylor, ϵ, 0:3)
57+
quintic_taylor = taylor(quintic_taylor, ϵ, 0:7)
5658
```
57-
This equation must hold for each power of $ϵ$, so we can separate it into one equation per order:
59+
This messy equation must hold for each power of $ϵ$, so we can separate it into one nicer equation per order:
5860
```@example perturb
5961
quintic_eqs = taylor_coeff(quintic_taylor, ϵ)
62+
quintic_eqs[1:5] # for readability, show only 5 shortest equations
6063
```
6164
These equations show three important features of perturbation theory:
6265
1. The $0$-th order equation is *trivial* in $x_0$: here $x_0^5 = 1$ has the trivial real solution $x_0 = 1$.
6366
2. The $n$-th order equation is *linear* in $x_n$ (except the trivial $0$-th order equation).
6467
3. The equations are *triangular* in $x_n$: the $n$-th order equation can be solved for $x_n$ given only $x_m$ for $m<n$.
65-
This structure is what makes the perturbation theory so attractive: we can start with the trivial solution $x_0 = 1$, then linearly solve for $x_n$ order-by-order and substitute in the solutions of $x_m$ for $m<n$ obtained so far. Let us write a function that solves a general equation `eq` for the variable `x` perturbatively with this *cascading* process:
68+
This structure is what makes the perturbation theory so attractive: we can start with the trivial solution $x_0 = 1$, then linearly solve for $x_n$ order-by-order and substitute in the solutions of $x_m$ for $m<n$ obtained so far.
69+
70+
Here is a simple function that uses this *cascading* strategy to solve such a set of equations `eqs` for the variables `xs`, given a solution `x₀` of the first equation `eqs[begin]`:
6671
```@example perturb
67-
function solve_perturbed(eq, x, x₀, ϵ, order)
68-
x_taylor = series(x, ϵ, 0:order) # expand unknown in a taylor series
69-
x_coeffs = taylor_coeff(x_taylor, ϵ, 0:order) # array of coefficients
70-
eq_taylor = substitute(eq, x => x_taylor) # expand equation in taylor series
71-
eqs = taylor_coeff(eq_taylor, ϵ, 0:order) # separate into order-by-order equations
72-
sol = Dict(x_coeffs[1] => x₀) # store solutions in a symbolic-numeric map
72+
function solve_cascade(eqs, xs, x₀, ϵ)
73+
sol = Dict(xs[begin] => x₀) # store solutions in a map
7374
74-
# verify that x₀ is a solution of the 0-th order equation
75+
# verify that x₀ is a solution of the first equation
7576
eq0 = substitute(eqs[1], sol)
76-
if !isequal(eq0.lhs, eq0.rhs)
77-
error("$sol is not a 0-th order solution of $(eqs[1])")
78-
end
77+
isequal(eq0.lhs, eq0.rhs) || error("$sol does not solve $(eqs[1])")
7978
80-
# solve higher-order equations order-by-order
81-
for i in 2:length(eqs)
82-
eqs[i] = substitute(eqs[i], sol) # substitute lower-order solutions
83-
x_coeff = Symbolics.symbolic_linear_solve(eqs[i], x_coeffs[i]) # solve linear n-th order equation for x_n
84-
sol = merge(sol, Dict(x_coeffs[i] => x_coeff)) # store solution
79+
# solve remaining equations sequentially
80+
for i in 2:lastindex(eqs)
81+
eq = substitute(eqs[i], sol) # insert previous solutions
82+
x = Symbolics.symbolic_linear_solve(eq, xs[i]) # solve current equation
83+
sol = merge(sol, Dict(xs[i] => x)) # store solution
8584
end
8685
87-
return substitute(x_taylor, sol) # evalaute series with solved coefficients
86+
return sol
8887
end
89-
90-
x_pert = solve_perturbed(quintic, x, 1, ϵ, 7)
88+
```
89+
Let us solve our order-separated quintics for the coefficients, and substitute them into the full series for $x$:
90+
```@example perturb
91+
x_coeffs_sol = solve_cascade(quintic_eqs, x_coeffs, 1, ϵ)
92+
x_pert = substitute(x_taylor, x_coeffs_sol)
9193
```
9294
The $n$-th order solution of our original quintic equation is the sum up to the $ϵ^n$-th order term, evaluated at $ϵ=1$:
9395
```@example perturb
9496
for n in 0:7
95-
println("$n-th order solution: x = ", substitute(taylor(x_pert, ϵ, 0:n), ϵ => 1.0))
97+
x_pert_sol = substitute(taylor(x_pert, ϵ, 0:n), ϵ => 1)
98+
println("$n-th order solution: x = $x_pert_sol = $(x_pert_sol * 1.0)")
9699
end
97100
```
98101
This is close to the solution from Newton's method!
@@ -112,30 +115,40 @@ E_newton = solve_newton(substitute(kepler, vals_earth), E, π/2)
112115
println("Newton's method solution: E = ", E_newton)
113116
```
114117

115-
Next, let us solve the same problem with our perturbative solver. It is most common to expand Kepler's equation in $M$ (the trivial solution when $M=0$ is $E=0$):
118+
Next, let us solve the same problem with the perturbative method. It is most common to expand $E$ as a series in $M$. Repeating the procedure from the quintic example, we get these equations:
116119
```@example perturb
117-
E_pert = solve_perturbed(kepler, E, 0, M, 5)
120+
E_taylor = series(E, M, 0:5)
121+
E_coeffs = taylor_coeff(E_taylor, M) # TODO: get coefficients at series creation
122+
kepler_eqs = taylor_coeff(substitute(kepler, E => E_taylor), M, 0:5)
123+
kepler_eqs[1:4] # for readability
118124
```
119-
Numerically, this gives almost the same answer as Newton's method:
125+
The trivial $0$-th order solution (when $M=0$) is $E_0=0$. This gives this full perturbative solution:
126+
```@example perturb
127+
E_coeffs_sol = solve_cascade(kepler_eqs, E_coeffs, 0, M)
128+
E_pert = substitute(E_taylor, E_coeffs_sol)
129+
```
130+
131+
Numerically, the result again converges to that from Newton's method:
120132
```@example perturb
121133
for n in 0:5
122134
println("$n-th order solution: E = ", substitute(taylor(E_pert, M, 0:n), vals_earth))
123135
end
124136
```
125-
But unlike Newtons method, perturbation theory also gives us the power to work with the full *symbolic* series solution for $E$ (*before* numbers for $e$ and $M$ are inserted). Our series matches [this result from Wikipedia](https://en.wikipedia.org/wiki/Kepler%27s_equation#Inverse_Kepler_equation):
137+
But unlike Newtons method, this example shows how perturbation theory also gives us the powerful *symbolic* series solution for $E$ (*before* numbers for $e$ and $M$ are inserted). Our series matches [this result from Wikipedia](https://en.wikipedia.org/wiki/Kepler%27s_equation#Inverse_Kepler_equation):
126138
```@example perturb
127139
E_wiki = 1/(1-e)*M - e/(1-e)^4*M^3/factorial(3) + (9e^2+e)/(1-e)^7*M^5/factorial(5)
128140
```
129141

130-
Alternatively, we can expand Kepler's equation in $e$ instead of $M$ (the trivial solution when $e = 0$ is $E=M$):
131-
```@example perturb
132-
E_pert′ = solve_perturbed(kepler, E, M, e, 5)
133-
```
134-
We can expand the trigonometric functions in $M$:
142+
Alternatively, we can expand $E$ in $e$ instead of $M$, giving the solution (the trivial solution when $e = 0$ is $E_0=M$):
135143
```@example perturb
136-
E_pert′ = taylor(E_pert′, M, 0:5)
144+
E_taylor′ = series(E, e, 0:5)
145+
E_coeffs′ = taylor_coeff(E_taylor′, e) # TODO: get at creation
146+
kepler_eqs′ = taylor_coeff(substitute(kepler, E => E_taylor′), e, 0:5)
147+
E_coeffs_sol′ = solve_cascade(kepler_eqs′, E_coeffs′, M, e)
148+
E_pert′ = substitute(E_taylor′, E_coeffs_sol′)
137149
```
138-
Up to order $e^5 M^5$, we see that this two-variable $(e,M)$-series also matches the result from Wikipedia:
150+
This looks very different from our first series `E_pert`. If they are the same, we should get $0$ if we subtract and expand both as multivariate Taylor series in $(e,M)$. Indeed:
139151
```@example perturb
140-
E_wiki′ = taylor(taylor(E_wiki, e, 0:5), M, 0:5)
152+
@assert taylor(taylor(E_pert′ - E_pert, e, 0:4), M, 0:4) == 0 # use this as a test # hide
153+
taylor(taylor(E_pert′ - E_pert, e, 0:4), M, 0:4)
141154
```

0 commit comments

Comments
 (0)