式としては以下の様な常微分方程式である. $$ \left\{ \begin{array}{rcl} \displaystyle \frac{d u}{dt} & = & \sigma (-u + v), \\ \displaystyle \frac{d v}{dt} & = & u (-w + r) - v, \\ \displaystyle \frac{d w}{dt} & = & u v - b w . \end{array} \right. $$
ただし、パラメータ(ザルツマンのパラメータ)は、 $ \sigma = 10, $ $ r = 28, $ $ b = 8/3 . $
const σ, r, b = 10, 28, 8/3 # まずパラメータを設定してしまおう. 定数の場合、const を宣言しておくと参照が速い!
方程式の右辺を定義してしまおう.
function f(U)
(u, v, w) = U # こうやって成分毎に受け取ることも出来る.
return [
σ * (-u + v)
u * (- w + r) - v
u*v - b*w
]
end
初期値の幾つかは Runge-Kutta 法によるので、その時間発展も、定義してしまおう.
Δt = 0.01
function RK(U)
f1 = f(U)
f2 = f(U + Δt/2 * f1)
f3 = f(U + Δt/2 * f2)
f4 = f(U + Δt * f3)
return U + Δt * (f1 + 2*f2 + 2*f3 + f4)/6
end
Runge-Kutta 法を使って、$U_2,U_1,U_0, f_2, f_1, f_0$ を返す関数を作ろう
function make_init(U)
V = copy(U)
sq_V = copy(U)
sq_f = f(U)
for n in 1:2
V = RK(V)
sq_V = hcat(sq_V, V)
sq_f = hcat(sq_f, f(V))
end
return sq_V, sq_f
end
初期値を適当に用意する
U0 = [1.0, 1, 1]
試しに、初期値がどうなっているか見てみよう
make_init(U0)[1] # これが $U_0, U_1, U_2$ のはず
make_init(U0)[2] # これが $f_0, f_1, f_2$ のはず.
さて、線形多段解法 + 予測子修正子法のプログラムだ.
線形計算なので、線形代数の記号を使うと簡単にかける ことに着目しよう.
function LM(U, vF) # vF = Fn, Fn-1, Fn-2 のつもり.
# P
up = U + Δt* ( vF * [23/12, -4/3, 5/12] )
# E
fp = f(up)
# C
up = U + Δt* ( hcat(fp, vF) * [3/8, 19/24, -5/24, 1/24] )
# E
fp = f(up)
return up, hcat(fp, vF[:,1:2]) # 入力と同じ型のデータで更新したものを返す
end
早速、計算してみよう
sq_U, sq_f = make_init(U0) # 初期値を作成.
U = sq_U[:,3]
vF = copy(sq_f)
for i in 1:3000
U, vF = LM(U,vF)
sq_U = hcat(sq_U, U)
end
試しに結果の数字をちょろっと見てみる.
sq_U
あまり変なことにはなっていなさそうなので、プロットしてみよう.
using Plots
gr()
plot(sq_U', fmt = :png) # 描画点が多く、web browser に負荷が大きいので、画像形式を png にして負荷を下げよう
大丈夫そうだ.では、相図にして見てみよう.
X, Y, Z = sq_U[1,:], sq_U[2,:], sq_U[3,:]
plot(X, Y, Z, fmt = :png)
おお、大丈夫そうだな.
そこで、初期値をいれたら解を出力する形で両方の関数を作ろう.
# まずは Runge-Kutta 法.
function RKout(init_U, N) # 初期値と最終ステップ数だけを与えれば良いとしよう.
U = copy(init_U)
sq_U = copy(init_U)
for i in 1:N-1 # 正確には、これで最終ステップが N になる
U = RK(U)
sq_U = hcat(sq_U, U)
end
return sq_U
end
# 次に線形多段階法
function LMout(init_U, N) # 初期値と最終ステップ数だけを与えれば良いとしよう.
sq_U, sq_f = make_init(init_U) # 初期値を作成.Runge-Kutta が2回動作する.
U = sq_U[:,3]
vF = copy(sq_f)
for i in 1:N-3 # 正確には、これで最終ステップが N になる
U, vF = LM(U,vF)
sq_U = hcat(sq_U, U)
end
return sq_U
end
試しに、両方動かしてみよう.データのサイズが一緒になることを確認しよう.
RKout(U0,3000)
LMout(U0,3000)
では、両方の速度を測ってみよう.それには、BenchmarkTools というパッケージに入っている、@benchmark
というマクロが便利だ.
using BenchmarkTools
# 使う準備をする.インストールしてない人は、Pkg.add("BenchmarkTools") としてインストールだ.
@benchmark RKout(U0,3000) # まずは Runge-Kutta 法の速度を見よう.
@benchmark LMout(U0,3000) # 次に、線形多段解法の速度をみよう.
ふむ、理論的には、微分値 $f(t,u)$ の評価回数が半分の線形多段解法が有理なはずなのだが、こんな単純な $f$ だと差がでないみたいだな…
線形多段解法で、Lorenz 方程式の初期値鋭敏性について一通り実験してみよ.