今週のプログラムその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