「プログラミングと数値計算」カテゴリーアーカイブ

フォントサイズ変更

emacsでフォントサイズを変更したい場合は?環境はemacs24。

(調べてもよくわからないし出てこない…。)
一度だけ変更したい場合は、Ctrl-x-± で可能です。
ではずっと変更したい場合は?
emacsの設定ファイルに記述します。emacsの設定ファイルの場所は
emacswiki – InitFileより

  1. For GnuEmacs, it is ~/.emacs or _emacs or ~/.emacs.d/init.el.
  2. For XEmacs, it is ~/.xemacs or ~/.xemacs/init.el.
  3. For AquamacsEmacs, it is ~/.emacs or ~/Library/Preferences/Aquamacs Emacs/Preferences.el

にあります。環境によって場所が異なります。
Linuxmint17.1,”sudo apt-get install emacs24″で行った場合、おそらく~/.emacsでしょう。

21.8 Fontsを見るとデフォルトでは
フォント(の種類) : monospace font
フォントサイズ : 10pt
として設定されているそうです。

テキストエディタで~/.emacsを開き、
(custom-set-variables


)
の括弧の

(add-to-list 'default-frame-alist
                       '(font . "DejaVu Sans Mono-12"))

という文を記述します。
現在、フォントのタイプの変更は僕は分かりません。多分DejaVu…を変えればよさそう。
12のところを10とか14とかに変えればデフォルトのフォントサイズを変更することができます。

見出しに使っている画像は8,9,10,11,12,20の場合の比較です。参考にどうぞ。

僕の使ってる~./emacsはこちら

組み合わせ

組み合わせ\(_nC_r\)、重複組み合わせ\(_nH_r\)の一覧を求めるfortranコードです。

\(_nH_r=~_{n+r-1}C_r\)


組み合わせ\(_nC_r\)の場合


  1  2  3  4
  1  2  3  5
  1  2  3  6
  1  2  4  5
  1  2  4  6
  1  2  5  6
  1  3  4  5
  1  3  4  6
  1  3  5  6
  1  4  5  6
  2  3  4  5
  2  3  4  6
  2  3  5  6
  2  4  5  6
  3  4  5  6

を得たい場合

重複組み合わせ\(_nH_r\)の場合


  1  1  1  1
  1  1  1  2
  1  1  1  3
  1  1  2  2
  1  1  2  3
  1  1  3  3
  1  2  2  2
  1  2  2  3
  1  2  3  3
  1  3  3  3
  2  2  2  2
  2  2  2  3
  2  2  3  3
  2  3  3  3
  3  3  3  3

を得たい場合

組み合わせをrecursionを使わないで得るために、全ての行を初めの値で引いてみました。

+------------+
  0  0  0  0
  0  0  0  1
  0  0  0  2
  0  0  1  1
  0  0  1  2
  0  0  2  2
  0  1  1  1
  0  1  1  2
  0  1  2  2
  0  2  2  2
  1  1  1  1
  1  1  1  2
  1  1  2  2
  1  2  2  2
  2  2  2  2
+------------+

すると、ちょうどr進数と同じになります。なので、これを復元するプログラムを書きました。

時間計測

fortran90において、
このプログラムは何秒かかるか?
早い?遅い?
を調べるためにはもちろん計算時間を計るのが一番です。

  1. 実時間とCPU時間の違い
  2. 実時間計測
  3. CPU時間計測

実時間とCPU時間の違い


『計算時間』には大きく2種類あります。実時間CPU時間です。

  • 実時間 → 現実の世界での経過時間
  • CPU時間 → プログラムの実行でCPUを使った時間

※[1]より。参考文献とは程遠いですが、僕の認識とあっていて、綺麗な説明と感じたので載せておきます。

例えば、計算時間はそんなにかかってないのに詳細な画像を得たいがために書き出すデータ量を増やしている場合、実時間は長く、CPU時間は短くなります。
また、あるデータ点1つを得るために計算時間は膨大にかかる場合、実時間≈CPU時間になります。

fortranでは時刻を得るためのサブルーチンが既に用意されています。
ここで紹介するのは、実時間を得るための
date_and_time

system_clock
の2種類。
CPU時間を得るためのサブルーチンは
cpu_time
の1種類です。

実時間計測(date_and_time)


その場所で何時何分何秒を出力させたい時、その場所に

  write(6,'(3A,i0,A,i0,A,i0,A,i0,A,i0,A,i0,A)')"  == ", &
       c," ",ti(1),"/",ti(2),"/",ti(3), &
       "  ",ti(5),":",ti(6),":",ti(7),", (yyyy/mm/dd  hh:mm:ss)"

を入れましょう[2]。character(10)::b(1:3), integer::ti(1:8)です。

使う際はこうすると良いでしょう。

program main
  implicit none

  call current_time("program start ")  

  !program here
  write(6,*)"press Enter"
  read *
   
  call current_time("program finish")  
 
  stop
end program main

subroutine current_time(c)
  implicit none
  character(*),intent(in)::c
  integer::ti(1:8)
  character(10)::b(1:3)
 
  call date_and_time(b(1),b(2),b(3),ti)
  write(6,*)
  write(6,'(3A,i0,A,i0,A,i0,A,i0,A,i0,A,i0,A)')"  == ", &
       c," ",ti(1),"/",ti(2),"/",ti(3), &
       "  ",ti(5),":",ti(6),":",ti(7),", (yyyy/mm/dd  hh:mm:ss)"
  write(6,*)
 
  return
end subroutine current_time

サブルーチンcurrent_timeを定義して、それを呼び出すだけのほうが分かりやすいかと思います。
引数は何という文言をその場所に指定したいかを示すものです。
実行して適当なときにEnterキーを押してプログラムを進めると、

$ gfortran main2.f90
$ ./a.out
   
  == program start  2016/2/15  9:16:38, (yyyy/mm/dd  hh:mm:ss)

 press Enter

  == program finish 2016/2/15  9:16:40, (yyyy/mm/dd  hh:mm:ss)

$

という結果が得られるでしょう。もちろん、プログラムを動かした時点での時間が表示されます。
一瞬で終わるプログラムの場合は差は見られ無いと思います。

実時間計測(system_clock)


system_clockはfortran標準実装なので汎用性は高いです[4]。system_clockは組み込まれているサブルーチンなので下のプログラムをそのままコピペして使う事が出来ます。

program main
  implicit none
  integer::ti,tf,tr ! ti: initial time, tf: finish time, tr: time rate
  integer::i

  call system_clock(ti)

  !program here
  write(6,*)"press Enter"
  read *

  call system_clock(tf,tr)
 
  write(6,'(f10.3,A)')(tf-ti)/dble(tr),"[s]"
 
  return
end program main

上の文を実行して適当なときにEnterキーを押してプログラムを進めると、

$ gfortran main2.f90
$ ./a.out
    press Enter
      1.283[s]
$

という出力が得られます。

CPU時間の計測(cpu_time)


CPU時間の計測するには

    call cpu_time(t0)
    ...
    call cpu_time(t1)
    write(6,'(f10.3,A)')(t1-t0),"[CPU sec]"

を入れましょう[3]。real::t1,t2です。

program main
  implicit none
  real::t0,t1
  integer::i

  call cpu_time(t0)

  !program here
  write(6,*)"press Enter"
  read *
 
  call cpu_time(t1)
 
  write(6,'(f10.3,A)')(t1-t0),"[CPU sec]"
 
  stop
end program main

でokです。適当な時間待って、Enterキーを押すと

$ gfortran main2.f90
$ ./a.out
    press Enter
     0.000[s]

という出力が得られるでしょう。
これはcpuを動かしていないためであり、cpuが動いていた時間というのは限りなく0だ、ということです。

参考文献

[1]ユーザ時間とシステム時間の違いを教えてください。
[2]1.4.7.1 date_and_time: 日付と時刻の取得 -ORACLE®
[3]8 移植性のある時間計測の方法 -nag
[4]9.254 SYSTEM_CLOCK — Time function

大きな数を扱う

大きい数=工夫or対数!

大きい数を扱う…例えば階乗を計算しろ、という問題や組み合わせ\(_nC_r\)を計算しろ、という問題に時々あたります。

まず、典型的な例として組み合わせ
\(\displaystyle _nC_r=\frac{n!}{(n-r)!r!}\)
を計算しましょう。
この計算はそのまま定義のまま計算しようとすると階乗が出てくるので、途中の値は物凄くでかい数字になるのですが、最後の答えは小さい値になるはずです。それなのに計算できないのはおかしくない!?工夫次第で何とかなるものです。
下の表は3つの計算方法についてまとめたものです。

integer(4バイト整数型±2 147 483 647)を用いた計算
定義のまま計算 工夫して計算 対数を利用して計算
最大のn,r \(_{12}C_6\) \(_{29}C_{14}\) \(_{33}C_{16}\)
理由 階乗の計算によるオーバーフロー “割り切れる”ための制約 (限界値)

integer(8バイト整数型±9 223 372 036 854 775 807)を用いた計算
計算方法 定義のまま計算 工夫して計算 対数を利用して計算
最大のn,r \(_{20}C_{10}\) \(_{61}C_{30}\) \(_{48}C_{24}\)
理由 階乗の計算によるオーバーフロー “割り切れる”ための制約 倍精度型変数の有効桁数が足りない
定義のまま計算

例えばfortranでintegerの宣言により定義した場合、階乗が正しく計算できる範囲(オーバーフローしない範囲)は、\(12!=479\ 001\ 600\)
まで計算できます。
変数の型がいくつまで計算できるかは、組み込み関数hugeを使うことで確認できます。

program main
  implicit none
  integer::n

  write(6,*)"huge",huge(n)

  stop
end program main

これで確認すると、整数型は
\(\pm 2\ 147\ 483\ 647\)
までの値なら代入することができるため、
\(13!=6\ 227\ 020\ 800\)は計算できないことがわかります。
一応関数はこんな感じ。

function nCr_fact(n,r)
  !sikinote
  implicit none
  integer::nCr_fact,fact,n,r
  external::fact
 
  nCr_fact=fact(n)/(fact(n-r)*fact(r))
 
  return
end function nCr_fact
function fact(n)
  implicit none
  integer::fact,i,n
 
  if(n.le.-1)write(6,*)"####warning#### parameter of factorial has negative value"
  fact=1
  do i=2,n
     fact=fact*i
  enddo

  return
end function fact

( ※余談ですが、8バイト型整数で宣言、すなわちinteger(8)で宣言すると
\(9\ 223\ 372\ 036\ 854\ 775\ 807\)
まで計算できます(19桁、\(20!=2\ 432\ 902\ 008\ 176\ 640\ 000\)まで)。)

なので定義通りの計算はn=12までが限界です。実用的じゃありません。

工夫して計算

\(\displaystyle _nC_r=\frac{n!}{(n-r)!r!}\)を工夫します。

\(
\begin{align}
\displaystyle _nC_r&=\frac{n!}{(n-r)!r!} \\
&=\frac{n(n-1)(n-2)\cdots(n-r+2)(n-r+1)}{r(r-1)(r-2)\cdots2\cdot 1} \\
&=\frac{n}{r}\cdot \frac{n-1}{r-1}\cdot \frac{n-2}{r-2}\cdots \frac{n-r+2}{2}\cdot \frac{n-r+1}{1} \\
&=\prod_{i=1}^r\frac{n-r+i}{i}
\end{align}
\)
として計算します。こうすれば階乗みたいに大きな数を計算する必要がなくなります。
関数を書けばこうなります。

function nCr(n,r)
  !sikinote
  implicit none
  integer::n,r,i,r0,nCr

  r0=n-r
  if(r.le.r0)r0=r

  nCr=1
  do i=1,r0
     nCr=nCr*(n-r0+i)
     nCr=nCr/i
  enddo
 
  return
end function nCr

で\(_nC_r\)を求めることができます。
上のプログラムでは、最大\(_{29}C_{14}=77\ 558\ 760\)まで求めることができます。
これは絶対に割り切れることを前提にしているためにおこるもので、

nCr=nCr*(n-r0+i)

の文のところで最大値が決まってしまうためです。
定式化すれば、
\(_nC_r\)に対し、\(_{n-1}C_{r-1}\cdot n\)が扱える整数値を超えないこと
と表現できます。

\(_{29}C_{14}=77\ 558\ 760\)が計算できるのは、
\(_{29-1}C_{14-1}*29=1\ 085\ 822\ 640\)
であるので計算可能です。次の\(_{30}C_{15}\)を計算するためには
\(_{30-1}C_{15-1}*30=2\ 326\ 762\ 800\)が
integerの最大の整数値\(\pm 2\ 147\ 483\ 647\)
を超えてしまうのでこれ以上計算はできません。
※もしもinteger(8)を用いると\(_{61}C_{30}\)まで計算できます。

対数を利用して計算

もう一つ対数を利用した方法を紹介します。
対数を利用すると、
\(\displaystyle _nC_r=\frac{n!}{(n-r)!r!}\)は、\(S= _nC_r\)とおくと
\(
\begin{align}
\displaystyle \ln(S)&=\ln\left[\frac{n!}{(n-r)!r!}\right] \\
&=\ln(n!)-\ln\{(n-r)!\}-\ln(r!) \\
&=\sum_{i=n-r+1}^n\ln(i)-\sum_{i=2}^{r}\ln(i)
\end{align}
\)
と書けます。よって、
\(\displaystyle S=\exp\left[\sum_{i=n-r+1}^n\ln(i)-\sum_{i=2}^{r}\ln(i)\right]\)
です。プログラムを書けば

function nCr_log(n,r)
  !sikinote
  implicit none
  integer::n,r,i,r0,nCr2
  double precision::tmp
 
  r0=n-r
  if(r.le.r0)r0=r

  tmp=0.d0
  do i=n-r0+1,n
     tmp=tmp+log(dble(i))
  enddo
  do i=2,r0
     tmp=tmp-log(dble(i))
  enddo

  nCr2=nint(exp(tmp))  

  return
end function nCr_log

となります。この場合は\(_{n}C_{r}\)が整数型で扱える範囲を越えなければいいという条件だけになるので、
integerでは\(_{33}C_{16}\)までokです。

integer(8)では\(_{66}C_{33}\)までできます…が、上のプログラムそのままではできません。
おそらく、組み込み関数nint(実数型を整数型に四捨五入して代入する)がinteger(8)に対応していない(かも)ということと、倍精度の有効桁数がだんだんと足らなくなっていくので厳しいです。後者による問題によって、確認した範囲では\(_{48}C_{24}\)位が限界です。

乱数

fortran90においての乱数の発生方法です。

乱数を1から作るのは、乱数を研究するとかでない限り止めましょう。
ここではfortran90で乱数を発生させるためにどうすればいいか?を記述します。

私は乱数について詳しくありませんが、調べた限りメルセンヌツイスタという乱数発生法が良い乱数を発生するようです。
メルセンヌツイスタの詳細はMersenne Twister Home Page にあるのでご参照ください。
fortran90コードはhttps://gist.github.com/ykonishi/5569005にあります。

上記のリンクから、メルセンヌツイスタのfortran90コード(mt19937.f90)をダウンロードします。
そして、メインプログラム

program main
  use mt19937
  implicit none
  integer::i
  double precision::d

  call sgrnd(2) ! seed
 
  do i=1,10
     write(6,*)grnd()
  enddo
 
  stop
end program main

をmt19937.f90と一緒に

gfortran -fno-range-check mt19937.f90 main2.f90

というオプションを付けてコンパイル(※1)します。実行すると、

$ ./a.out
   6.4690865343436599E-002
  0.40996560384519398    
  0.14283204451203346    
  0.74749888177029788    
  0.59957117028534412    
  0.53676494420506060    
  0.38914293772540987    
  0.94303490873426199    
  0.15882158395834267    
  0.15933134709484875    
$

という結果を得ることが出来ます。

(※1)
このオプションが、型が扱える範囲を超えていても無視するためのオプションです。
何故かgfortranコンパイラ(ver4.8.4で確認)で出ます。
そして、これはコンパイラの問題だと私は結論付けました。
fortranの整数は
-2147483648から2147483647までで定義されているのにもかかわらず、
gfortran(ver4.8.4)は
-2147483647から2147483647の範囲でなければエラーを出すようです。
しかし、実際に整数に-2147483648を代入し、このエラーを無視する上のオプションを付けて実行すると、ちゃんと値が入ります。なので、コンパイラに問題があると判断しました。

[adsense1]

任意の関数に従う乱数(von Neumannの棄却法)


このページのサムネイル画像の右は等方的2次元調和振動子励起状態の密度
\(
\begin{align}
f(x,y)=\frac{4}{\pi}x^2y^2e^{-(x^2+y^2)}
\end{align}
\)
を点の濃さで表現するためのプログラムです。

これは、上の関数に従う乱数分布を書くことにほかなりません。
一様乱数を任意の関数に従う乱数に変換する方法として、
von Neumannの棄却法と呼ばれる方法があります。

以下のようにすると一様乱数を関数\(f(x)\)に従う乱数に変えることが出来ます。

\(x\)の範囲\([x_a,x_b]\)で定義された関数\(f(x)\)に従う乱数を発生させたい。
区間\([x_a,x_b]\)で関数は\([f_a,f_b]\)の値を持つとすると、
 1. 範囲\([x_a,x_b]\)の一様乱数\(x_r\)を生成する。
 2. 範囲\([f_a,f_b]\)の一様乱数\(f_r\)を生成する。
 3. もしも\(f_r\le f(x_r)\)ならば、その\(x_r\)は\(f(x)\)に従う乱数である。そうでなければ、発生させた乱数は捨てる。
 4. 1-3を繰り返す。
という方法です。アルゴリズムを見て分かる通り、この方法は無駄が多いです。
発生させた乱数を捨てる操作は出来ればしたくありません。
ですが、この方法は非常に簡単に実装できるのでとりあえず実装したいという場合には良い方法です。

以下は、上の考えの二次元版です。

program main
  use mt19937
  implicit none
  integer::i,count,N
  double precision::xp,yp,fp
  double precision::a,b,fmin,fmax
  double precision,external::f
 
  call sgrnd(2)

  N=2000
 
  ! fmin :: minimum value of function f(x,...)
  ! fmax :: maxmum value of function f(x,...)
  fmin=0.d0
  fmax=1.d0

  ! range of random number [a,b]
  a=-5d0
  b= 5d0
 
  open(13,file="./density.d")
  count=0
  do while(count.lt.N)
     xp=grnd()*(b-a)+a
     yp=grnd()*(b-a)+a
     fp=grnd()*(fmax-fmin)+fmin
     
     if(fp.le.f(xp,yp))then
        write(13,*)xp,yp
        count=count+1
     endif
  enddo
  close(13)

  stop
end program main

function f(x,y)
  implicit none
  double precision::f
  double precision,intent(in)::x,y

  double precision::pi=acos(-1.d0)
 
  f=abs(sqrt((1.d0/pi))*2d0*x*y*exp(-0.5d0*(x*x+y*y)))**2
 
  return
end function f

カラーマップと3次元での表現はこうなります。
20150524-210920_1_c

乱数を用いた密度表示(下のプログラム)ではこう表現されます。
nxny11density_c

[adsense2]

参考

任意分布の乱数生成 https://www.slideshare.net/shogoosawa581/ss-65259938

以降はfortranの標準組み込み関数を用いる場合のものです。
消すのは忍びないため、一応おいておきます。

4次ルンゲ・クッタ法

微分方程式なら任せろーバリバリバリー

ルンゲ=クッタ法ってもともとどういうもの?理論は?刻み幅\(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)\)です。)

  1. \((x_0,y_0)\)より\(k_a\)を求める
  2. \(k_a\)より\(k_b\)を求める
  3. \(k_b\)より\(k_c\)を求める
  4. \(k_c\)より\(k_d\)を求める
  5. \(k_a,k_b,k_c,k_d\)より\(y(x_0+h)\)を求める
  6. \((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次ルンゲ=クッタ法を用いて解くには、以下のプログラムで実現できます。

解いて、gnuplotで
plot “fort.10”
数値計算解(赤点)解析解(緑線)と共に出力すると、こんなグラフが得られます。
4次ルンゲクッタ法による計算1

連立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の一部を変えればおしまいです。
変えた場所をコメントで
!+—+
を入れたので確認してください。

gnuplotで数値計算解(赤点)解析解(緑線)のグラフを書くとこうでなります。
4次ルンゲクッタ法による計算2

[adsense2]