計算物理学II (第7回)
•関数副プログラム・サブルーチン副プログラム(Fortran) •関数(C) •何度も使う一連の実行文をまとめて主プログラムの外にまとめる。 •数学の関数と考えてよいが、もっと複雑なものも関数にできる。
•関数：与えられた入力変数に対して操作をして値を返す一連の手続き•例えば4x³-5xを何度も様々なxに対して計算したいとする。プログラムのあちこちに4x³-5xと繰り返して書くとミスのもとになる。また後から関数形に変更が必要になったときにすべて探し出して変更する必要がある。 •関数副プログラムとしてf(x)=4x³-5xを計算する部分を独立させる。 •関数副プログラムを置く場所1.主プログラム(program ~end program)の中に置く(内部副プログラム) 2.主プログラムの外(end programの後)に置く(外部副プログラム) 3.モジュールの中に置く(モジュール副プログラム)
例1 ：主プログラムの中に関数副プログラムを置く場合(内部副プログラム) program example2 implicit none double precision :: x x = 0.0d0 do print *, x, func(x)     !ここでfunc(x)が呼び出される。関数はcontainsより下で定義されている。 x = x + 0.10d0 if( x > 5.0d0) exit end do ！contains以下に副プログラムを列挙するcontains function func(x)          !関数見出しdouble precision, intent(in) :: x  ！xを倍精度実数として宣言。intent(in)がつくと関数内で値を変更できない！関数の型を宣言する(倍精度実数型) double precision :: func func =  4.0d0*x**3 – 5.0d0*x ！関数を呼び出された元のプログラムに戻るreturn end function func end program example2
例2：主プログラムの外に関数副プログラムを置く場合(外部副プログラム) program example2 implicit none double precision :: x double precision :: func ！外部副プログラムは主プログラムから見えないので型宣言が必要x = 0.0d0 do print *, x, func(x)     !ここでfunc(x)が呼び出される。 x = x + 0.10d0 if( x > 5.0d0) exit end do end program example2 function func(x)           !関数見出しdouble precision, intent(in) :: x    ! xを倍精度実数として宣言。intent(in)をつけると関数内で値を変更できないdouble precision :: func ！関数の型を宣言する(倍精度実数型) func =  4.0d0*x**3 – 5.0d0*x ！関数を呼び出された元のプログラムに戻るreturn end function func
例3:モジュールの中に置く(モジュール副プログラム) ! programよりも手前にmoduleを配置。mod_funcは適当につけたモジュール名module mod_func ! module内でもimplicit none宣言implicit none contains ！containsより下にモジュール副プログラムを配置function func(x) !関数見出しdouble precision, intent(in) :: x    ! xを倍精度実数として宣言。intent(in)をつけると関数内で値を変更できないdouble precision :: func                  !関数の型を宣言する(倍精度実数型) func =  4.0d0*x**3 - 5.0d0*x ！関数を呼び出された元のプログラムに戻るreturn end function func end module mod_func program example2 use mod_func implicit none double precision :: x = 0.0d0 do print *, x, func(x)    !ここでfunc(x)が呼び出される。 x = x + 0.10d0 if( x > 5.0d0) exit end do end program example2
program test implicit none double precision :: a(1:3), b(1:3), c(1:3) a(1) = 6.0d0; a(2) = 3.0d0; a(3) = 4.0d0 b(1) = 3.0d0; b(2) = -2.0d0; b(3) = -4.0d0 c(1:3) = vectorproduct( a(1:3), b(1:3) )　！3要素の1次元配列型の関数write(*,*) c(1:3) contains function vectorproduct(a, b) double precision, intent(in) :: a(1:3),  b(1:3) double precision :: vectorproduct(1:3) ！関数の型を要素数3の1次元配列として宣言vectorproduct(1) = a(2)*b(3)-a(3)*b(2) vectorproduct(2) = a(3)*b(1)-a(1)*b(3) vectorproduct(3) = a(1)*b(2)-a(2)*b(1) return end function vectorproduct end program test
•再帰(recurrsion)とは：関数副プログラム(やサブルーチン)の中で自分自身(関数副プログラムやサブルーチン)を引用すること。 •再帰的に関数副プログラム(やサブルーチン)を呼び出す場合はrecursiveをつける。 •関数の実行結果は関数名ではなくresult句で指定された変数に代入される。フィボナッチ数列の例| recursive fuction fibonacci(n) result(fibo) integer, intent(in) :: n integer :: fibo ！結果を代入する変数if (n<0) then fibo = -1 return end if if (n == 0) then fibo = 0 else if (n == 1) then fibo = 1 else fibo = fibonacci(n-1) + fibonacci(n-2) ！自分自身を呼び出すend if return end function fibonacci
•関数副プログラムとほぼ同じ。違う点は•値を返さない(関数には型があるがサブルーチンにはない) •ただし引数として(複数の)値を返すことができる。 • call文で呼び出す•サブルーチン副プログラムは1つの実行文となるが、関数副プログラムは実行文ではない•長いプログラムを書く時は主プログラムに長々と書かずサブルーチンに分割する
program example2 implicit none double precision :: x, y x = 0.0d0 do ! call文でfuncサブルーチンを呼び出しcall func(x,y) print *, x, y x = x + 0.10d0 if( x > 5.0d0) exit end do ! contains以下に副プログラムを列挙するcontains subroutine func(x, y) implicit none double precision, intent(in) :: x        ! intent(in)がつくとサブルーチン内で値を変更できないdouble precision, intent(out) :: y     ! intent(out)がつくとサブルーチン内で値をセットしなければならないy =  4.0d0*x**3 – 5.0d0*x ！関数を呼び出された元のプログラムに戻るreturn end subroutine func end program example2
•サブルーチンの引数にはintent属性をつけることを推奨• intent(in) :入力引数、サブルーチンに値が渡され、サブルーチン内で値の変更ができない• intent(out) :出力変数、サブルーチンで値がセットされる変数• intent(inout) :入出力変数、サブルーチンに値が渡され、サブルーチンで値を変更できる変数•サブルーチン内だけで使う変数には何もつけない。
program test implicit none double precision :: a(1:3), b(1:3), c(1:3) a(1) = 6.0d0; a(2) = 3.0d0; a(3) = 4.0d0
end program test
•変数を主プログラムと副プログラム(関数やサブルーチン)で共有する
短いプログラムの場合は内部副プログラムが簡単に書けるが、内部副プログラムは独立性が低い(この副プログラムだけ他のコードにコピーしても使えない(a,b,cが中で未定義なので))
•基本は引数にすべて書く。引数に書かれていないものは参照できないprogram test implicit none real*8 :: x0, a=1.0d0, b=2.0d0, c=3.0d0, f   ! a,b,cの値は外部プログラムからは直接参照できないx0 = 1.0d0 print *, x0, f(x0,a,b,c) a = 2.0d0 print *, x0, f(x0,a,b,c) end program test function f(x,a,b,c) real*8, intent(in) :: x,a,b,c real*8 :: f f = a*x**2 + b*x + c return end function f外部プログラムは独立性が高い。計算に必要な変数がすべて引数に入っているので外部プログラムだけを他のコードで使うことができる。ただし引数が煩雑になる。
•モジュール内に変数を記述し、モジュールを参照する•引数には色々な値で計算することを想定されるものだけを入れるmodule func_mod !モジュールはprogramより手前に配置implicit none real*8, save :: a, b, c !副プログラムで使う変数をsave属性つきで定義!save属性をつけておくと前に代入された値を保持し続けるcontains function f(x)！モジュール副プログラムreal*8, intent(in) :: x real*8 :: f f = a*x**2 + b*x + c !モジュール副プログラムはモジュール内変数を参照できるreturn end function f end module func_mod program test use func_mod !implicit noneの手間でモジュールを参照implicit none real*8 :: x0 a=1.0d0; b=2.0d0; c=3.0d0 !モジュール変数に値をセットできるx0 = 1.0d0 print *, x0, f(x0) !モジュール関数も呼び出せるa = 2.0d0 print *, x0, f(x0) end program testモジュールは独立性が高い。モジュールの中で他のモジュールをuse文で参照することも可能module, programの順番に書く(別のファイルに分ける場合はこの順番にコンパイル)複数のmoduleがある場合は順番(依存関係)に注意•関数：与えられた入力変数に対して操作をして値を返す一連の手続き• mainも関数。int型であれば整数値をreturnで返す。 •例えば4x³-5xを何度も様々なxに対して計算したいとする。プログラムのあちこちに4x³-5xと繰り返して書くとミスのもとになる。また後から関数形に変更が必要になったときにすべて探し出して変更する必要がある。 •関数としてf(x)=4x³-5xを計算する部分を独立させる。 •関数を置く場所1. main関数の手前2. main関数の後ろ• return文は関数の処理を中断してその段階の値を返す。
例1 ：関数funcがmain関数より前に読み込まれる場合
#include<stdio.h> double func(double x){ double y; y = 4.0*x*x − 5.0*x; return(y); ー int main(){ double x; for(x=0.0;;){ printf(“%f %f¥n”, x, func(x));  //ここで関数にxの値を渡して呼び出すX += 0.1; if( x > 5.0) break; ー }例2：関数funcがmain関数の後に読み込まれる場合#include<stdio.h> int main(){ double x; double func(double);   //関数プロトタイプ宣言。mainより手前でもよい。 for(x=0.0;;){ printf(“%f %f¥n”, x, func(x));  //ここで関数にxの値を渡して呼び出すX += 0.1; if( x > 5.0) break; } } double func(double x){ double y; y = 4.0*x*x – 5.0*x; //倍精度実数型変数γに入っている値を返すreturn(y); }
•再帰(recursion)とは：関数で自分自身を引用することフィボナッチ数列int fibonacci(int n){ int fibo; if( n<0 ) { fibo = -1; return(fibo);大if( n==0 ){ fibo = 0; }else if(n == 1) { fibo = 1; } else { fibo = fibonacci(n-1) + fibonacci(n-2);  //自分自身を呼び出す- return(fibo); }
関数の戻り値は一変数なので複数変数、あるいは配列の値を戻り値としてほしいときは引数に変数や配列のアドレスを渡し、関数の中でその変数の値を更新する。アドレス：メモリ上での番地。変数aの場合は&aがaのアドレス。配列a[n]の場合はa、または&a[0]が先頭のアドレス例：外積を計算#include<stdio.h> int main(){ double a[3], b[3], c[3]; void vectorproduct(double [3], double [3]); // voidは戻り値がないタイプの関数a[0] = 6.0; a[1] = 3.0; a[2] = 4.0; b[0] = 3.0; b[1] = -2.0; b[2] = -4.0; //配列のアドレスを渡す。(配列名が配列のアドレス) vectorproduct( a, b, c ); printf(“%f %f %f ¥n”, c[0], c[1], c[2]); return 0; void vectorproduct(double a[3], double b[3], double c[3]){  // double *a, double *b, double *cでもよいc[0] = a[1]*b[2]-a[2]*b[1]; c[1] = a[2]*b[0]-a[0]*b[2]; c[2] = a[0]*b[1]-a[1]*b[0]; //関数に戻り値はないreturn;
•基本は引数にすべて書く。引数に書かれていないものは参照できない#include<stdio.h> double f(double x, double a, double b, double c){    // 2次関数を計算する関数return(a*x*x + b*x + c); ー int main(){ double x0; x0 = 1.0; a = 1.0; b = 2.0; c = 3.0; printf(“%f %f¥n”, x0, f(x0,a,b,c));  //関数にx0,a,b,cを渡すa = 2.0; printf(“%f %f¥n”, x0, f(x0,a,b,c));  // aの値が変わったのでf(x0,a,b,c)の値が変わる関数fの独立性が高い。計算に必要な変数がすべて引数に入っているのでこの関数だけを他のコードで使うことができる。ただし引数が煩雑になる。
・グローバル変数をmain関数とその他の関数で共有する。 •関数はグローバル変数に依存するため独立性が低くなる•基本は引数として渡すのが間違いが少ない
ー int main(){ double x0; x0 = 1.0; a = 1.0; b = 2.0; c = 3.0; printf(“%f %f¥n”, x0, f(x0));  //関数のxとしてx0を渡すa = 2.0; printf(“%f %f¥n”, x0, f(x0));  // aの値が変わったのでf(x0)の値が変わる
main関数は整数型正常終了は0を返し、異常終了の場合は0以外を返す返り値はシェルから参照することができる。省略してもよい。 int main(){ return (1);   //エラーの場合などは1を返すー [hinohara.nobuo.ga@icho:~$ cat test.c int main() return(1); hinohara.nobuo.ga@icho:~$ gcc test.c hinohara.nobuo.ga@icho:~$ ./a.out |hinohara.nobuo.ga@icho:~$ echo $? hinohara.nobuo.ga@icho:~$変数$?に一つ手前のコマンドの返り値が格納される
•締め切り12月13日(月) •講義資料のページに掲載(https://wwwnucl.ph.tsukuba.ac.jp/~hinohara/compphys2-21/) •作ったプログラムと回答をpdfファイルにしてmanabaで提出。 •講義第7回までの内容です。 • FortranまたはCで作成(C++やPythonでもOK) •質問OK(歓迎)ですが、この答えであってるか？などはダメ。