logo

줄리아 미분방정식 패키지 DiffetentialEquations 튜토리얼 📂ジュリア

줄리아 미분방정식 패키지 DiffetentialEquations 튜토리얼

説明

DifferentialEquations.jlは、SciMLグループに属するパッケージの一つで、微分方程式の数値的解法のために開発された。このパッケージで解ける方程式は以下の通りだ。

  • Discrete equations (function maps, discrete stochastic (Gillespie/Markov) simulations)
  • 常微分方程式(ODE)
  • Split and Partitioned ODEs (Symplectic integrators, IMEX Methods)
  • 確率微分方程式(SDE)
  • Stochastic differential-algebraic equations (SDAEs)
  • Random differential equations (RODEs or RDEs)
  • Differential algebraic equations (DAEs)
  • Delay differential equations (DDEs)
  • Neutral, retarded, and algebraic delay differential equations (NDDEs, RDDEs, and DDAEs)
  • Stochastic delay differential equations (SDDEs)
  • Experimental support for stochastic neutral, retarded, and algebraic delay differential equations (SNDDEs, SRDDEs, and SDDAEs)
  • Mixed discrete and continuous equations (Hybrid Equations, Jump Diffusions)
  • (Stochastic) partial differential equations ((S)PDEs) (with both finite difference and finite element methods)

チュートリアルを超えた使い方は以下を参考に。

インストール

Julia REPLで次のように入力してインストールする。

julia> using Pkg
julia> Pkg.add("DifferentialEquations")

または]を押してパッケージモードに入り、次のように入力してインストールする。

(@v1.10) pkg> add DifferentialEquations

ODEProblem

DifferentialEquations.jlでの常微分方程式の解法は次のように簡単に整理できる。

problem = ODEProblem(f, u0, tspan, p)
solution = solve(prob)

この過程は下で具体的に扱うとして、まずはODEProblemについて詳しく見てみよう。関数ODEProblemは、その名の通り常微分方程式問題を定義する関数だ。基本적으로4つの引数を受け取ることができるので、一つずつ見ていこう。

  • f: SciMLBase.ODEFunctionタイプの関数だ。常微分方程式を以下のように整理したとき、f(u,p,t)f(u, p, t)に該当する部分を定義すればよい。 dudt=f(u,p,t) \dfrac{du}{dt} = f(u, p, t) このときメモリ効率のためにin-place方式で定義できるが、この場合はf!(du, u, p, t)のように定義すればよい。
  • u0: 初期条件だ。uuがスカラー関数ならu0 = 1.0のように、ベクトル関数ならu0 = [1.0, 1.0, 1.0]のようにすれば良い。
  • tspan: 問題で定義されたuuのドメインをタプルで入力する。tspan = (t_start, t_end)
  • p: ソリューションuuを除くパラメータ(外力など)を入力する。

さて、以下のような常微分方程式が与えられたとしよう。

du(t)dt=u(t)+sin(t)t[0,1]u(0)=0.0 \begin{align*} \dfrac{du(t)}{dt} &= u(t) + \sin(t) \qquad t \in [0, 1] \\ u(0) &= 0.0 \end{align*}

すると、次のようなコードで問題を定義する。

using DifferentialEquations

f(u, p, t) = u + p(t)
u0 = 0.0
tspan = (0.0, 1.0)
p(t) = sin(t)
prob = ODEProblem(f, u0, tspan, p)

probsolve関数の引数に入れると方程式のソリューションを得ることができる。次の例を通じてさらに具体的に見てみよう。

常微分方程式の解法1

最も簡単な常微分方程式の例として、次のような指数成長方程式を考えよう。

dudt=αu \dfrac{du}{dt} = \alpha u

この方程式の解は、ある定数AAについてAeαtA e^{\alpha t}と等しいことが既に分かっているが、例のために最も単純な形の微分方程式を持ってきた。具体的には次のような初期値問題を考えよう。

du(t)dt=4u(t)t[0,1]u(0)=0.5 \begin{align*} \dfrac{du(t)}{dt} &= 4 u(t) \qquad t \in [0, 1] \\ u(0) &= 0.5 \end{align*}

まず右辺を関数f(u,p,t)f(u, p, t)で定義する。ここでppはパラメータparameterで通常は各項の係数、この方程式ではα=2\alpha = 2を意味する。また、初期値と時間の範囲を設定する。

より具体的に説明すると、uuを更新するルールを決めることだ。duduがどう表現されるかを関数で定義し、それをODEProblemの引数に入れる。dtdtは入力しなくても自動で設定される。つまり、微分方程式をdu=f(u,p,t)dtdu = f(u,p,t)dtの形に変えた後、f(u,p,t)f(u,p,t)部分を定義すれば良いということだ。このようにパラメータが定数の場合は、ppを明示的に関数内部で使用する必要はない。

using DifferentialEquations

f(u, p, t) =  4u
u0 = 0.5
tspan = (0.0, 1.0)

これらをODEProblemの引数に入れて、解くべき問題problemを作る。

julia> problem = ODEProblem(f, u0, tspan)
ODEProblem with uType Float64 and tType Float64. In-place: false
timespan: (0.0, 1.0)
u0: 0.5

problemsolveに代入すると、微分方程式の数値的な解を得られる。ソルバーがソリューションを7次元ベクトルとして計算してくれた。

julia> @time solution = solve(problem)
  0.000074 seconds (192 allocations: 28.750 KiB)
retcode: Success
Interpolation: 3rd order Hermite
t: 7-element Vector{Float64}:
 0.0
 0.07581611843444613
 0.21771610504885602
 0.39336627353058856
 0.6101928843972066
 0.8638706427431251
 1.0
u: 7-element Vector{Float64}:
 0.5
 0.581866090044407
 0.7728154714821961
 1.0981042913641448
 1.6942469046842847
 2.8139612733277692
 3.6945246786578037

proertynamesを使うとsolutionのプロパティを確認できる。

julia> propertynames(solution)
(:u, :u_analytic, :errors, :t, :k, :discretes, :prob, :alg, :interp, :dense, :tslocation, :stats, :alg_choice, :retcode, :resid, :original, :saved_subsystem)

ソリューションuuとドメインttをそれぞれ得たい場合は、次のようにできる。

julia> solution.u
7-element Vector{Float64}:
 0.5
 0.581866090044407
 0.7728154714821961
 1.0981042913641448
 1.6942469046842847
 2.8139612733277692
 3.6945246786578037

julia> solution.t
7-element Vector{Float64}:
 0.0
 0.07581611843444613
 0.21771610504885602
 0.39336627353058856
 0.6101928843972066
 0.8638706427431251
 1.0

可視化

solutionに対するレシピが既に定義されているため、簡単にplotに代入するとグラフを描くことができる。よく見ると、solution.usolution.tを直接代入して描いたときとは異なり、自動的に値を補間interpolationしてくれることが分かる。またx軸がttであることも自動的に表示される。

using Plots
p1 = plot(solution)
p2 = plot(solution.t, solution.u)
plot(p1, p2, layout = (2, 1))

実際の解であるu(t)=0.5e4tu(t) = 0.5 e^{4t}と比較してみよう。

plot(solution, label="solver", lw=4)
plot!(LinRange(0,1,100), t->0.5*exp(4t), label="exact", lw=3, ls=:dash, lc=:red, dpi=300)

詳細設定

キーワード引数を直接設定してソルバーを選択したり、許容誤差を設定することができる。例えば0.050.05のタイプステップごとにソリューションを計算したい場合は、saveat = 0.05save at と設定すれば良い。詳しくはリンクで確認できる。

# 솔버를 Tsit5()로 설정
# 매 타임스텝 0.05마다 솔루션을 저장.
# 허용 절대오차는 1e-8로 설정.

julia> solve(problem, Tsit5(), saveat=0.05, abstol=1e-8)
retcode: Success
Interpolation: 1st order linear
t: 21-element Vector{Float64}:
 0.0
 0.05
 0.1
 0.15
 0.2
 0.25
 0.3
 0.35
 0.4
 0.45
 0.5
 0.55
 0.6
 0.65
 0.7
 0.75
 0.8
 0.85
 0.9
 0.95
 1.0
u: 21-element Vector{Float64}:
  0.5
  0.6107013642059927
  0.7459122224392803
  0.9110596077813341
  1.1127694183818455
  1.3591415739400332
  1.6600546629913426
  2.0275986016325755
  2.4765152918979516
  3.0248023813153906
  3.6945244502370813
  4.512485296082109
  5.511506995093366
  6.731841509374868
  8.22227376044907
 10.042494301852722
 12.266093755920075
 14.981974632546809
 18.298554052093934
 22.34964366466662
 27.298573538274052

コード全文

using DifferentialEquations

f(u, p, t) =  4u
u0 = 0.5
tspan = (0.0, 1.0)

problem = ODEProblem(f, u0, tspan)
@time solution = solve(problem)

using Plots
p1 = plot(solution, title="plot(solution)")
p2 = plot(solution.t, solution.u, title="plot(solution.t, solution.u)")
plot(p1, p2, layout = (2, 1), dpi=300)

plot(solution, label="solver", lw=4)
plot!(LinRange(0,1,100), t->0.5*exp(4t), label="exact", lw=3, ls=:dash, lc=:red, dpi=300)

solve(problem, Tsit5(), saveat=0.05, abstol=1e-8)

連立常微分方程式の解法2

連立1次微分方程式の例として次のようなローレンツ方程式を考えよう。

dxdt=σx+σydydt=xz+ρxydzdt=xyβz \begin{align*} \dfrac{dx}{dt} &= -\sigma x + \sigma y \\ \dfrac{dy}{dt} &= -xz + \rho x - y \\ \dfrac{dz}{dt} &= xy - \beta z \end{align*}

連立微分方程式を解くことも、微分方程式を解くことと似ている。du=(dx,dy,dz)du = (dx, dy, dz)を更新するルールを定義し、それをODEProblemの引数に入れれば終わりだ。パラメータをσ=10\sigma = 10β=8/3\beta = 8/3ρ=28\rho = 28に設定し、du=f(u,p,t)dtdu = f(u,p,t)dtの形に直そう。

dx=(10x+10y)dtdy=(xz+28xy)dtdz=(xy83z)dt \begin{align*} dx &= (-10 x + 10 y)dt \\ dy &= (-xz + 28 x - y)dt \\ dz &= (xy - \frac{8}{3} z)dt \end{align*}

さて右辺の式をdtdtを除いて関数で定義する。このとき、lorenzではなくin-place関数であるlorenz!で定義するのは性能のためだ2。この場合、関数の最初の引数に出力であるduを追加すればよい。u=(x,y,z)u = (x,y,z)du=(dx,dy,dz)du = (dx,dy,dz)としよう。

dx=10(yx)dtdy=[x(28z)y]dtdz=(xy83z)dt    du1=10(u2u1)du2=u1(28u3)u2du3=u1u283u3 \begin{align*} dx &= 10(y-x)dt \\ dy &= \left[x(28-z)- y\right]dt \\ dz &= (xy - \frac{8}{3} z)dt \end{align*} \implies \begin{align*} du_{1} &= 10(u_{2}-u_{1}) \\ du_{2} &= u_{1}(28-u_{3})- u_{2} \\ du_{3} &= u_{1}u_{2} - \frac{8}{3} u_{3} \end{align*}

function lorenz!(du, u, p, t)
du[1] = 10(u[2] - u[1])
du[2] = u[1]*(28-u[3]) - u[2]
du[3] = u[1]*u[2] - (8/3)u[3]
end

初期値をu(0)=(1,1,1)u(0) = (1,1,1)、ドメインを[0,100][0, 100]に設定した後、解を計算する。ソリューションをプロットする時は引数idx=(1,2,3)を必ず追加しなければならない。

u0 = [1.0, 1.0, 1.0]
tspan = (0.0, 100)
prob = ODEProblem(lorenz!, u0, tspan)
sol = solve(prob)

plot(sol, idxs=(1,2,3))

環境

  • OS: Windows11
  • Version: Julia 1.10.0, DifferentialEquations v7.15.0, Plots v1.40.3