微分方程式なら任せろーバリバリバリー
ルンゲ=クッタ法ってもともとどういうもの?理論は?刻み幅\(h\)を自動的に制御する方法について知りたい方は、ルンゲ=クッタ法の系統的扱いと刻み幅制御へどうぞ。
4次ルンゲ=クッタ法は微分方程式を数値的に解く手段です。
ルンゲ=クッタ法が良く使われる理由は、ひとえにプログラムの実装のしやすさです。ルンゲクッタ法は非常に良い!というアルゴリズムではありませんが、他の方法よりもシンプルで、プログラムに組み込みやすいのです。
”4次”は問題の解の関数をテイラー展開した場合、4次までは一致するように作られた、という意味です。
例えば微分方程式
\(
\displaystyle \frac{dy}{dx}=g(x,y)
\)
を考えます。
数値計算では初期値(例えばx=0の時、y=1など)を決めて、そこからxをhだけ増やし、微分方程式というルールに従って関数\(y(0+h),y(0+h+h),y(0+h+h+h),\cdots\)を作り上げていきます。
この時、4次ルンゲ=クッタ法で求められる答えの関数というのは
\(
\displaystyle y(x+h)=y(x)+hg(x,y)+\frac{h^2}{2!}\frac{dg}{dx}+\frac{h^3}{3!}\frac{d^2g}{dx^2}+\frac{h^4}{4!}\frac{d^3g}{dx^3}+O(h^5)
\)
という関数になります。
\(
g(x,y)=-xy
\)
(ただし、初期条件\(x=0\)で\(y=1\))
である場合、微分方程式の解析解は
\(
\displaystyle y=e^{-x^2/2}
\)
であるため、4次ルンゲ=クッタ法によって導かれる答えは、
\(
\displaystyle y(x+h)=y(x)\left[1-hx+\frac{h^2}{2}(x^2-1)+\frac{h^3}{6}(-x^3+3x)+\frac{h^4}{24}(x^4-6x^2+3)\right]+O(h^5)
\)
となります。
本題に入りましょう。
4次ルンゲ=クッタ法は6つのステップが必要となります。
初期値を\((x_0,y_0)\)と書くと、(\(y_0=y(x_0)\)です。)
- \((x_0,y_0)\)より\(k_a\)を求める
 
- \(k_a\)より\(k_b\)を求める
 
- \(k_b\)より\(k_c\)を求める
 
- \(k_c\)より\(k_d\)を求める
 
- \(k_a,k_b,k_c,k_d\)より\(y(x_0+h)\)を求める
 
- \((x_0+h,y(x_0+h))\)を初期値だと思って手順1に戻る。
 
という感じです。
3章:連立ルンゲ・クッタ法による微分方程式の解を参考にすると、数値計算での4次ルンゲ=クッタ法の計算スキームは以下のようになります。
解きたい微分方程式を連立1次微分方程式の形で書くと一般的にはこう書けます。
———
\(
\begin{eqnarray}
  \left\{
	\begin{aligned}
		\frac{dy_1}{dx} &= f_1(x,y_1,y_2,\cdots,y_N) \\
		\frac{dy_2}{dx} &= f_2(x,y_1,y_2,\cdots,y_N) \\
       	&\vdots \\
		\frac{dy_N}{dx} &= f_N(x,y_1,y_2,\cdots,y_N) \\
	\end{aligned}
  \right.
\end{eqnarray}
\)
———
まず係数\(k_a\)を求めます。
———
\(
\begin{eqnarray}
  \left\{
	\begin{aligned}
		k_{a1} &= hf_1(x,y_1,y_2,\cdots,y_N) \\
		k_{a2} &= hf_2(x,y_1,y_2,\cdots,y_N) \\
       	&\vdots \\
		k_{aN} &= hf_N(x,y_1,y_2,\cdots,y_N) \\
	\end{aligned}
  \right.
\end{eqnarray}
\)
———
次に\(k_b\)。
———
\(
\begin{eqnarray}
  \left\{
	\begin{aligned}
		k_{b1} &= hf_1(x+\frac{h}{2},y_1+\frac{k_{a1}}{2},y_2+\frac{k_{a2}}{2},\cdots,y_N+\frac{k_{aN}}{2}) \\
		k_{b2} &= hf_2(x+\frac{h}{2},y_1+\frac{k_{a1}}{2},y_2+\frac{k_{a2}}{2},\cdots,y_N+\frac{k_{aN}}{2}) \\
       	&\vdots \\
		k_{bN} &= hf_N(x+\frac{h}{2},y_1+\frac{k_{a1}}{2},y_2+\frac{k_{a2}}{2},\cdots,y_N+\frac{k_{aN}}{2}) \\
	\end{aligned}
  \right.
\end{eqnarray}
\)
———
そして\(k_c\)。
———
\(
\begin{eqnarray}
  \left\{
	\begin{aligned}
		k_{c1} &= hf_1(x+\frac{h}{2},y_1+\frac{k_{b1}}{2},y_2+\frac{k_{b2}}{2},\cdots,y_N+\frac{k_{bN}}{2}) \\
		k_{c2} &= hf_2(x+\frac{h}{2},y_1+\frac{k_{b1}}{2},y_2+\frac{k_{b2}}{2},\cdots,y_N+\frac{k_{bN}}{2}) \\
       	&\vdots \\
		k_{cN} &= hf_N(x+\frac{h}{2},y_1+\frac{k_{b1}}{2},y_2+\frac{k_{b2}}{2},\cdots,y_N+\frac{k_{bN}}{2}) \\
	\end{aligned}
  \right.
\end{eqnarray}
\)
———
最後に\(k_d\)。
———
\(
\begin{eqnarray}
  \left\{
	\begin{aligned}
		k_{d1} &= hf_1(x+h,y_1+k_{c1},y_2+k_{c2},\cdots,y_N+k_{cN}) \\
		k_{d2} &= hf_2(x+h,y_1+k_{c1},y_2+k_{c2},\cdots,y_N+k_{cN}) \\
       	&\vdots \\
		k_{dN} &= hf_N(x+h,y_1+k_{c1},y_2+k_{c2},\cdots,y_N+k_{cN}) \\
	\end{aligned}
  \right.
\end{eqnarray}
\)
———
最後に求めた\(k_a,k_b,k_c,k_d\)を使って\(x+h\)でのそれぞれの関数の値を導きます。、
———
\(
\begin{eqnarray}
  \left\{
	\begin{aligned}
		x &= x+h \\
		y_{1} &= y_{1}+(k_{a1}+2k_{b1}+2k_{c1}+k_{d1})/6 \\
		y_{2} &= y_{2}+(k_{a2}+2k_{b2}+2k_{c2}+k_{d2})/6 \\
       	&\vdots \\
		y_{N} &= y_{N}+(k_{aN}+2k_{bN}+2k_{cN}+k_{dN})/6 \\
	\end{aligned}
  \right.
\end{eqnarray}
\)
———
となります。
4次ルンゲ=クッタ法のプログラム
実際に例題を解きましょう。
2つの例題を解きます。
[adsense1]
1階微分方程式
1つ目は
\(
\displaystyle \frac{dy}{dx}=-y\sin{x}
\)
です。
一般解は
\(
y=Ae^{\cos{x}}
\)
であり、初期条件\(x=0,y=2\)として解けば解析解は
\(
y=2e^{\cos{x}-1}
\)
です。これを4次ルンゲ=クッタ法を用いて解くには、以下のプログラムで実現できます。
program main
  implicit none
  integer::i,N,step,Nmax
  double precision::x,h
  double precision,allocatable::y(:)
  external::grk
  !==============
  h=1.d-3
  Nmax=20000
  step=200
  N=1
  !==============
  x=0d0
  allocate(y(1:N))
  y(1)=2d0
 
  write(10,*)x,y(1)
  do i=1,Nmax
     call rk4(grk,N,x,h,y)
     if(mod(i,step).eq.0)then
        write(10,*)x,y(1)
     endif
  enddo
  stop
end program main
subroutine grk(N,x,y,f)
  implicit none
  integer,intent(in)::N
  double precision,intent(in)::x,y(1:N)
  double precision,intent(out)::f(1:N)
 
  f(1)=-y(1)*sin(x)
  return
end subroutine grk
!===================================
subroutine rk4(grk,N,x,h,y)
  implicit none
  integer,intent(in)::N
  double precision,intent(in)::h
  double precision,intent(inout)::x,y(1:N)
  external::grk
  ! 
  ! N  --> Number of 1st order differential equation
  ! x  --> Variable
  ! h  --> Step size
  ! y  --> Solution
  !
  !   sikinote(https://slpr.sakura.ne/jp/qp)
  !   2020/04/22
  !
  integer::i,j
  double precision,allocatable::tmp(:),K(:,:),f(:)
  double precision::tx,c(1:4)
  c(1:4)=(/0d0,0.5d0,0.5d0,1d0/)
  allocate(tmp(1:N),K(1:N,0:4),f(1:N))
  tmp=0d0
  K=0d0
  f=0d0  
  
  do j=1,4
     do i=1,N
        tmp(i) = y(i) + K(i,j-1)*c(j)
     enddo
     tx = x+c(j)*h
     call grk(N,tx,tmp,f)
     do i=1,N
        K(i,j) = h*f(i)
     enddo
  enddo
 
  x = x+h
  do i=1,N
     y(i) = y(i) + (K(i,1)+K(i,4))/6d0+(K(i,2)+K(i,3))/3d0
  enddo
 
  return
end subroutine rk4
 
 
解いて、gnuplotで
plot “fort.10”
で数値計算解(赤点)と解析解(緑線)と共に出力すると、こんなグラフが得られます。

連立1階微分方程式
もう一つの例題は微分方程式
\(
\displaystyle \frac{d^2y}{dx^2}+2\gamma \frac{dy}{dx}+y=0
\)
を解きます。これは物理の世界ではバネの減衰振動を表す運動方程式で(詳しくは減衰振動へ。)、\(0\lt\gamma\lt1\)の場合、解は
\(
y(x)=Ae^{-\gamma x}\cos(x\sqrt{1-\gamma^2}-\alpha)
\)
となります。
\(\gamma=0.15\), 初期条件を今、
\(
\begin{eqnarray}
  \left\{
	\begin{aligned}
		\left.y\right|_{x=0} &= 1 \\
		\left.\frac{dy}{dx}\right|_{x=0} &= -0.15 \\
	\end{aligned}
  \right.
\end{eqnarray}
\)
とすると、解析解は
\(
y(x)=e^{-\gamma x}\cos{(x\sqrt{1-0.15^2})}
\)
となります。
プログラムで計算する際は、まず連立1次微分方程式に焼きなおす必要があります。すなわち、
\(
\begin{eqnarray}
  \left\{
	\begin{aligned}
		\frac{dy}{dx}&=v \\
		\frac{dv}{dx}&=-2\gamma v -y \\
	\end{aligned}
  \right.
\end{eqnarray}
\)
として解けばいいわけです。
その時の変更するべき場所は少しで、mainとrkfdの一部を変えればおしまいです。
変えた場所をコメントで
!+—+
を入れたので確認してください。
program main
  implicit none
  integer::i,N,step,Nmax
  double precision::x,h
  double precision,allocatable::y(:)
  external::grk
  !==============
  h=1.d-3
  Nmax=20000
  step=200
  N=2   ! +-------+
  !==============
  x=0d0
  allocate(y(1:N))
  y(1)=1d0
  y(2)=-0.15d0   ! +-------+
 
  write(10,*)x,y(1),y(2)   ! +-------+
  do i=1,Nmax
     call rk4(grk,N,x,h,y)
     if(mod(i,step).eq.0)then
        write(10,*)x,y(1),y(2)   ! +-------+
     endif
  enddo
  stop
end program main
subroutine grk(N,x,y,f)
  implicit none
  integer,intent(in)::N
  double precision,intent(in)::x,y(1:N)
  double precision,intent(out)::f(1:N)
 
  f(1)=y(2)
  f(2)=-0.3d0*y(2)-y(1)   ! +-------+
  return
end subroutine grk
!===================================
subroutine rk4(grk,N,x,h,y)
  implicit none
  integer,intent(in)::N
  double precision,intent(in)::h
  double precision,intent(inout)::x,y(1:N)
  external::grk
  ! 
  ! N  --> Number of 1st order differential equation
  ! x  --> Variable
  ! h  --> Step size
  ! y  --> Solution
  !
  !   sikinote(https://slpr.sakura.ne/jp/qp)
  !   2020/04/22
  !
  integer::i,j
  double precision,allocatable::tmp(:),K(:,:),f(:)
  double precision::tx,c(1:4)
  c(1:4)=(/0d0,0.5d0,0.5d0,1d0/)
  allocate(tmp(1:N),K(1:N,0:4),f(1:N))
  tmp=0d0
  K=0d0
  f=0d0  
  
  do j=1,4
     do i=1,N
        tmp(i) = y(i) + K(i,j-1)*c(j)
     enddo
     tx = x+c(j)*h
     call grk(N,tx,tmp,f)
     do i=1,N
        K(i,j) = h*f(i)
     enddo
  enddo
 
  x = x+h
  do i=1,N
     y(i) = y(i) + (K(i,1)+K(i,4))/6d0+(K(i,2)+K(i,3))/3d0
  enddo
 
  return
end subroutine rk4
 
 
gnuplotで数値計算解(赤点)と解析解(緑線)のグラフを書くとこうでなります。

[adsense2]