長時間の計算をしてみよう. \(|v_0|=1.0\) の初期条件であれば円軌道を回り続けるはずである.
tmax = 300000.0d0,n=3000000(時間刻みは0.1)まで計算を行ってみよう.
もちろん,先のプログラムで上記のようにパラメータを変更すれば計算をすることはできる.しかし,いくつか注意がある. これまでのプログラムでは毎ステップごとに時間,位置,速度を出力している.それをそのまま3000000ステップ分を出力すると300Mbを超える容量のファイルになってしまう. また実はファイルへの出力は意外と計算時間を要する.私の環境では,この計算に120秒程度の時間がかかった. これくらいなら待てると思うかもしれないが,たかが二体の計算でこの時間は粒子の数が増えた時のことを考えると恐ろしい. ここで興味があるのは,長時間の間,円軌道が維持されるのかどうかである.毎ステップごとに出力しなくてもよいだろう. ということで,出力部分に以下のような変更を行う(前ページの102~107行目あたり).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
integer i,dout
integer,parameter:: n=3000000
integer,parameter:: noutmax=1000
!~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if(n .gt. noutmax) then
dout = int(n/noutmax)
else
dout = 1
end if
open(unit=10,file='outputRK.dat',status='replace')
write(10,*)'t_rk, x_rk, y_rk, E_rk'
do i = 1,n
if(mod(i,dout) .eq. 0) then
write(10,*)t(i),',',x(i),',',y(i),',',E(i)
end if
end do
close(10)
|
この変更では,データの最大出力数(noutmax=1000)を設定する. 全計算ステップがこの数を超える場合は,この出力数内に収まるように出力間隔を調整する.
if(n .gt. noutmax) then
dout = int(n/noutmax)
else
dout = 1
end if
まずは,とうとう条件分岐if文の登場. 眺めていればわかると思うが,ifの後の括弧内の条件が真の時, つまりnがnoutmaxよりも大きければ(.gt.はgreater thanの意,n > noutmaxと書いてもよい,比較演算子という) その下にかかれた処理(dout = int(n/noutmax))を実行せよ. そうではない(偽)ならば,else以下の処理(dout = 1)を実行せよ.である. 最後にend ifを書いて条件分岐の処理を終える.
n=300000はnoutmax=1000よりも大きいので,dout=int(300000/1000)=300を計算する.
Note
「if (~) then」の次に「else if (*) then」を加えて,条件を重ねていくことができる.
Note
比較演算子には,「.lt., <(より小さい)」 「.le., <=(以下)」「.eq.,==(等しい)」「.ne.,/=(等しくない)」「.ge.,>=(以上)」がある.
Note
論理演算子には「.and.」「.or.」などがある.条件の結合などが行える.「 \(0 \lt n \leq 10\)」は「if(n .gt. 0.0d0 .and. n .le. 10.0d0) then ...」と書ける.
Note
int()は実数を整数に切り捨てで変換する.ex. int(10.0/3.0)=int(3.33)=3
do i = 1,n
if(mod(i,dout) .eq. 0) then
write(10,*)t(i),',',x(i),',',y(i),',',E(i)
end if
end do
次に出力部は,doループの中にif文をいれて「mod(i,dout) .eq. 0」が真の時だけ出力をしないさいとする.
mod(i,dout)は余り関数で,iをdoutで割った時の余りを返す. 今,dout=300なので,iが300の倍数のときだけmod(i,dout)はゼロになる.この時if文の条件が真になり出力の処理が実行される. つまり,iが300の倍数のときだけ出力される.
このようにすることで,どんなにステップ数を増やしても出力数は常にnoutmaxを維持する.
tmax = 300000.0d0,n=3000000で計算した軌道である.
Note
データの出力間隔が広い時に,折れ線をつかってプロットすると軌道ではないところを線で結んでしまうことがある.そのためここでは点でプロットした.
軌道に厚みが出てしまっている.半径1の円を周回するはずが,徐々に半径が小さくなっている. これは明らかにおかしい.
時間刻みを小さくしてみよう.
赤い点がtmax = 300000.0d0,n=30000000(時間刻み0.01)で計算した結果である. 半径1の周回軌道を維持しているようである.時間刻み0.1の場合には,長時間の計算による計算誤差の蓄積によって不自然な軌道が現れていたのだ. ただし当然ながら計算ステップを増やした分だけ計算時間は増える.約10秒もかかった.
万有引力の作用する(太陽を中心に固定した)系の全エネルギーEは,
である.以下のような計算を追加して時間変化をプロットした.
E = 0.5d0*(u**2+v**2) - 1.0d0/sqrt(x**2+y**2)
時間刻み0.1の場合,時間とともにエネルギーが減少してしまっている.それに比べれば時間刻み0.01は一定を維持しているように見える.
Note
ルンゲ-クッタ法は長時間の計算を行うとエネルギーが減少してしまうことが知られている.〔 ハミルトン力学系のためのシンプレクティック数値積分法,吉田春夫 〕
時間刻み0.01でも,さらに長時間計算すればいずれはエネルギーが減少するのだろう,と予測して, tmax = 1000000.0d0,n=10000000(時間刻み0.01)を行おうとすると,コンパイルのところでエラーがでる.
$ gfortran.exe celestial_RK.f90
/tmp/ccwZ2sLL.s: Assembler messages:
/tmp/ccwZ2sLL.s:1058: Error: value of 4800005200 too large for field of 4 bytes at 5136
/tmp/ccwZ2sLL.s:1176: Error: value of 4800005793 too large for field of 4 bytes at 5729
数を変えてみるとtmax = 380000.0d0,n=3800000まではコンパイルできるが, tmax = 390000.0d0,n=3900000だとエラーがでる.ただしこの場合はエラーメッセージが異なる.
*/libgfortran.dll.a(d001123.o):(.text+0x2): 再配置がオーバーフローしないように切り詰められました: *
コードをよく眺めていると,配列を使う必要がなかったような気がしてくる. これまでのコードでは,位置と速度などの情報を配列に記憶した後,それを再び呼び出してエネルギーを計算したりファイルに出力したりしていた. 最初のdoループですべて済むのではないか?doループの中でエネルギーを計算し,ファイルに出力すれば良さそうだ.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | !+++++++++++++++++++++++++++++++++++++++++++++++
program celestial
use globals
use subfunc
implicit none
integer i,dout
integer,parameter:: n=10**9
integer,parameter:: noutmax=10000
double precision tmax
double precision t,x,y,u,v
double precision E,E0,dE
double precision kx1,kx2,kx3,kx4,ku1,ku2,ku3,ku4
double precision ky1,ky2,ky3,ky4,kv1,kv2,kv3,kv4
double precision start_time,finish_time
!-----CPU time
call CPU_TIME(start_time)
x = 1.0d0
y = 0.0d0
u = 0.0d0
v = 1.0d0
t = 0.0d0
tmax = 10.0d6
delta_t = (tmax-t)/dble(n)
open(unit=10,file='outputRK.dat',status='replace')
write(10,*)'t_rk, x_rk, y_rk, E_rk, dE_rk'
if(n .gt. noutmax) then
dout = int(n/noutmax)
else
dout = 1
end if
do i = 1, n-1
E = 0.5d0*(u**2+v**2) - 1.0d0/dsqrt(x**2+y**2)
if(i .eq. 1) then
E0 = E
end if
dE = abs((E - E0)/E0)
if(mod(i-1,dout) .eq. 0) then
write(10,*)t,',',x,',',y,',',E,',',dE
end if
kx1 = fx(x,y,u)*delta_t
ky1 = fy(x,y,v)*delta_t
ku1 = fu(x,y,u)*delta_t
kv1 = fv(x,y,v)*delta_t
kx2 = fx(x+0.5d0*kx1,y+0.5d0*ky1,u+0.5d0*ku1)*delta_t
ky2 = fy(x+0.5d0*kx1,y+0.5d0*ky1,v+0.5d0*kv1)*delta_t
ku2 = fu(x+0.5d0*kx1,y+0.5d0*ky1,u+0.5d0*ku1)*delta_t
kv2 = fv(x+0.5d0*kx1,y+0.5d0*ky1,v+0.5d0*kv1)*delta_t
kx3 = fx(x+0.5d0*kx2,y+0.5d0*ky2,u+0.5d0*ku2)*delta_t
ky3 = fy(x+0.5d0*kx2,y+0.5d0*ky2,v+0.5d0*kv2)*delta_t
ku3 = fu(x+0.5d0*kx2,y+0.5d0*ky2,u+0.5d0*ku2)*delta_t
kv3 = fv(x+0.5d0*kx2,y+0.5d0*ky2,v+0.5d0*kv2)*delta_t
kx4 = fx(x+kx3,y+ky3,u+ku3)*delta_t
ky4 = fy(x+kx3,y+ky3,v+kv3)*delta_t
ku4 = fu(x+kx3,y+ky3,u+ku3)*delta_t
kv4 = fv(x+kx3,y+ky3,v+kv3)*delta_t
x = x + (kx1+2.0d0*kx2+2.0d0*kx3+kx4)/6.0d0
u = u + (ku1+2.0d0*ku2+2.0d0*ku3+ku4)/6.0d0
y = y + (ky1+2.0d0*ky2+2.0d0*ky3+ky4)/6.0d0
v = v + (kv1+2.0d0*kv2+2.0d0*kv3+kv4)/6.0d0
t = t + delta_t
end do
close(10)
!-----Output CPU time
call CPU_TIME(finish_time)
write(*,*) 'CPU time:',finish_time - start_time
end program celestial
!+++++++++++++++++++++++++++++++++++++++++++++++
|
call CPU_TIME(start_time)
!main process!
call CPU_TIME(finish_time)
write(*,*) 'CPU time:',finish_time - start_time
Note
CPU_TIME()は処理系の時間を返してくれる組み込みサブルーチンである.上記のようにプログラムのはじめとおわりで2回呼び出し時刻を記録し,その差を計算することで計算にかかったおおよその時間を知ることができる.
さてtmax = 10000000.0d0,n=1000000000(時間刻みは0.01)の計算が無事に実行できた.軌道と全エネルギーは,
integer,parameter:: n=10**10
1
エラー: Arithmetic overflow at (1)
整数型(4byte)の扱える数の範囲は-2,147,483,648 ~ 2,147,483,647(≒2×10 9 )である. これを超えてしまったためである.