今週のプログラムその7
void型関数またはサブルーチンの使い方と、その応用として
調和振動子の運動方程式をRunge-Kutta法で解くプログラムを作る。
変数の受けわたしかたがポイントだが、少々ごちゃごちゃするので、よく
考えてください。
ポイント
受け渡した変数の値を関数の中で変えるとどうなるのだろう
void型関数またはサブルーチン
さて、渡した変数の中身を関数の中で書き換えられるのだとすれば、
それを利用して”結果の値を持たない関数”を書きたくなるかもしれない。
たとえば、上で例にあげた”変数を3倍にする関数”は、変数自体を
3倍にした上に、3倍にした答を関数の値として返している。
目的が変数自体を3倍にすることなら、”関数の値”は不要だろう。
あるいは、関数の結果として配列を返したい場合や配列でなくても
複数の値を返したい場合にも"結果の値を持たない関数"を
使うことになる。
要するに、やりたいのは、プログラム中のある計算をする部分だけを
主プログラムから独立させることだ。
ひとつのプログラム中で同じ計算を何度も行なう場合、
そのたびに同じプログラムを書くのは効率が悪いので、
その部分を関数のように書きたいわけだ。ただし、”関数値”をもつ必要はない。
FORTRANではそれをサブルーチンとよんで、関数とは区別する。
一方、Cではすべてが関数なので、サブルーチンではなく、”関数値を持たない関数”
という意味でvoid型関数とよばれる。
まずはc版
program 16
/*
一次元調和振動子の運動方程式をEuler法で計算する
(エネルギーが発散するので、計算法としてはよくない(^^))
*/
#include<stdio.h>
#include<math.h>
void euler(double *, double *, double);
main()
{
double delta_t,q,p;
int i,step;
/* 時間刻み、ステップ数、初期の座標と運動量を入力 */
scanf("%lf%d%lf%lf",&delta_t,&step,&q,&p);
/* 1ステップ分を計算する関数をステップ数だけ繰り返して呼ぶ */
for(i=1; i<step; ++i){
euler(&q, &p,delta_t);
/* 各ステップごとに、時刻、座標、運動量、エネルギーを出力 */
printf("%f %f %f %f\n",i*delta_t,q,p,(p*p+q*q)/2);
}
}
/* Euler法1ステップ分の計算をする関数 */
void euler(double *q, double *p, double delta_t)
{
double t;
t = *q; qが書き換えられてしまうので、作業変数tに値をおぼえさせておく
*q += *p *delta_t;
*p += -t *delta_t;
}
program 17
#include<stdio.h>
#include<math.h>
/*
16と同じ問題を四次のRunge-Kuttaで解くプログラム
*/
void rungekutta(double *, double *, double);
void onestep(double, double, double *, double *, double);
main()
{
double delta_t,q,p;
int i,step;
/* 時間刻み、ステップ数、初期の座標と運動量を入力 */
scanf("%lf%d%lf%lf",&delta_t,&step,&q,&p);
/* 1ステップ分を計算する関数をステップ数だけ繰り返して呼ぶ */
for(i=1; i<step; ++i){
rungekutta(&q,&p,delta_t);
/* 各ステップごとに、時刻、座標、運動量、エネルギーを出力 */
printf("%f %f %f %f\n",i*delta_t,q,p,(p*p+q*q)/2);
}
}
/* 1ステップ分の計算をする関数 */
void rungekutta(double *q, double *p, double delta_t)
{
double k1q,k1p,k2q,k2p,k3q,k3p,k4q,k4p;
onestep(*q, *p, &k1q, &k1p,delta_t); 同じ計算を四回やる
onestep(*q+k1q/2, *p+k1p/2, &k2q, &k2p, delta_t);
onestep(*q+k2q/2, *p+k2p/2, &k3q, &k3p, delta_t);
onestep(*q+k3q, *p+k3p, &k4q, &k4p, delta_t);
*q += k1q/6 + k2q/3 + k3q/3 + k4q/6;
*p += k1p/6 + k2p/3 + k3p/3 + k4p/6;
}
/* rungekuttaの一段分(Euler法とよく似ている) */
void onestep(double q, double p, double *qq, double *pp, double delta_t)
{
*qq = p*delta_t;
*pp = -q*delta_t;
}
FORTRAN版
program 16
C 一次元調和振動子の運動方程式をEuler法で計算する
C(エネルギーが発散するので、計算法としてはよくない(^^))
program main
real*8 delta_t,q,p
C 時間刻み、ステップ数、初期の座標と運動量を入力
read(*,*)delta_t,istep,q,p
C 1ステップ分を計算する関数をステップ数だけ繰り返して呼ぶ
do i=1,istep
call euler(q,p,delta_t)
C 各ステップごとに、時刻、座標、運動量、エネルギーを出力
write(*,*)i*delta_t,q,p,(p*p+q*q)/2
enddo
end
C 1ステップ分の計算をするサブルーチン
subroutine euler(q, p, delta_t)
real*8 q,p,delta_t
real*8 t
t = q qが書き換えられてしまうので、作業変数tに値をおぼえさせておく
q = q+p*delta_t
p = p-t*delta_t
end
program 17
C 16と同じ問題を四次のRunge-Kuttaで解くプログラム
program main
real*8 delta_t,q,p
C 時間刻み、ステップ数、初期の座標と運動量を入力
read(*,*)delta_t,istep,q,p
C 1ステップ分を計算する関数をステップ数だけ繰り返して呼ぶ
do i=1,istep
call rungekutta(q,p,delta_t)
C 各ステップごとに、時刻、座標、運動量、エネルギーを出力
write(*,*)i*delta_t,q,p,(p*p+q*q)/2
enddo
end
C 1ステップ分の計算をするサブルーチン
subroutine rungekutta(q, p, delta_t)
real*8 q,p,delta_t
real*8 k1q,k1p,k2q,k2p,k3q,k3p,k4q,k4p
call onestep(q, p, k1q, k1p,delta_t) 何度も同じサブルーチンを呼ぶ例
call onestep(q+k1q/2, p+k1p/2, k2q, k2p, delta_t)
call onestep(q+k2q/2, p+k2p/2, k3q, k3p, delta_t)
call onestep(q+k3q, p+k3p, k4q, k4p, delta_t)
q = q + k1q/6 + k2q/3 + k3q/3 + k4q/6
p = p + k1p/6 + k2p/3 + k3p/3 + k4p/6
end
C rungekuttaの一段分(Euler法と似ている)
subroutine onestep(q,p,qq,pp,delta_t)
real*8 q,p,qq,pp,delta_t
qq = p*delta_t
pp = -q*delta_t
end