6日目「外部プログラムを使おう」:準備
能書き
前回までの内容で、プログラミングの基本はほぼ終了です。 変数が正しく扱えて、繰り返しと分岐を使いこなせれば、 たいていのことは出来ます。原理的には。しかし、たとえば「行列の固有値」の計算が必要になったとき、 そのプログラムを自力でゼロから作るのは、卒研を仕上げるより困難です。 手段のために目的を見失ってはいけません。 「みんなが必要だけど難しいプログラム(手段)」は、 プログラミングが得意な人が一回作って、 それをみんなで使い回すのがエコですね。
そこで本日のテーマ。
先人の築いた「共有財産」を活用しましょう。
ファイルを作る
program day6 !------------------------ ! Fortran template !------------------------ !--- module --- !--- end module --- implicit none !--- begin header --- !--- end header --- !--- begin main --- !--- end main --- end program
Lesson 15. 外部プログラム
定義:外部プログラム
プログラム本体とは独立な別のプログラムを、本体の中で使うことができる。外部プログラムの形式は3種類あり、それぞれの主な役割は以下の通りである。
関数 : 値を受け取り、それに応じた「値」を返す
サブルーチン: 受け取った値に応じた「処理」を行う
モジュール : 関数・サブルーチン・変数の「器」基本的には、「外部プログラムを使う」=「モジュールの中のサブルーチン、もしくは関数を使う」事である。
「裸の」関数、サブルーチンも使えるが、ここでは推奨しない。
定義:モジュールを使う
『program』の後、『implicit none』の前にuse モジュール名1 use モジュール名2と書くことで、そのモジュールの持つ関数、サブルーチン、変数をメインプログラムで使えるようになる。
useする順番は関係ない。
関数、サブルーチンの呼び出し
関数名([引数])*sin(x)などと同じように、式の一部として使う。引数が0個でも、カッコ省略不可。
call サブルーチン名[(引数)]*引数が0個の場合、カッコ省略可。
「引数」は「ひきすう」と読みます。
定義:モジュールのコンパイル
モジュールを使う際は、本体より先に『-c』をつけてコンパイルし、 "モジュール.o","モジュール.mod"ファイルを生成しておく。$ コンパイラ -c モジュール1.f90 $ コンパイラ -c モジュール2.f90
『コンパイラ』は、大槻研なら『ifort』、コンピュータルームなら『f90』が入る。
一度コンパイルしておけば、モジュールそのものを書き換えない限りコンパイルし直す必要は無い。そのモジュールを使用する"本体.f90"ファイルは、 生成された"モジュール.o"ファイルと一緒にコンパイルする。
$ コンパイラ モジュール1.o モジュール2.o 本体.f90このとき、useされるモジュールを左に、本体を一番右側に書くこと。順番大事!
実践
難しい話は置いておいて、まずは試してみましょう。最初は、「基本的な定数」を提供する「定数モジュール」です。
下のリンクを右クリックして、「リンク先を保存」して下さい。
bacs.f90 (保存先は、"day6.f90"と同じ場所です。)
このモジュールを使うと、円周率『Pi』などが初めから定義された状態になります。
use bacs implicit none real(kind=double) :: a a = zero print *, a a = a + one print *, a print *, "two = ", two, ", half = ", half, ", Pi = ", Pi print *, cZero, cOne, imgまた、
integer, parameter :: double=selected_real_kind(15)と本体でいちいち定義する必要が無くなります。すっきりしますね。
大槻先生の"KindNumbers"+αです。
Lesson 16. 乱数
定義: 乱数生成関数
0.0…から0.999…の間の一様乱数を出す関数を一般に乱数生成関数と呼びます。 ただし実際に使われるのは
あらかじめ与えられた「乱数の種」と「使った回数」を元に、 回数ごとに相関のない0と1の間の実数値を返す関数であり、得られるのは正確には「擬似乱数」です。
実践
いきなり「乱数を発生させろ」といわれても、何をすればいいのかさっぱり分からないでしょう。もちろん自力でやることも不可能ではありませんが、はっきり言って徒労です。
そこで「他人の作ったプログラム」を使わせてもらいましょう。
内部の仕組みが分からなくても使い方を知っていれば使えるのが外部プログラムの利点です。
むしろ、初心者はむやみに仕組みを追求しない方が得策です。
そういうわけで「一様乱数生成関数」を提供する「乱数モジュール」です。下のリンクを右クリックして、「リンク先を保存」して下さい。ranpack.f90
中身は大槻先生の"RandomNumber2"と同じです。
使い方:最初に一回だけ、サブルーチン『ran__dini(適当な整数)』を呼ぶ その後、関数『ran__drnd()』で乱数発生です。 早速使ってみましょう。『ran__』が付いているものが乱数モジュールの命令です。
アンダーバー『_』は1つではなく、2つ。
『ran__drnd()』のカッコの中には何も入れない。
『ran__drnd()』のカッコの中には何も入れない。
use bacs use ranpack ! random number module implicit none integer :: iSeed real(kind=double) :: a iSeed = 2311 ! random number seed call ran__dini(iSeed) ! initialize random number print *, ran__drnd() ! generate random number print *, ran__drnd() print *, ran__drnd()実行してみて、3つの異なる値が出ることを確かめましょう。ただし
実行(初期化)するたびに同じ値が出ますこの値は『call ran__dini(iSeed)』で設定した「乱数の種」で決まります。
『iSeed』を変えて試してみましょう。
call ran__dini(2311) print *, ran__drnd(), ran__drnd(), ran__drnd() call ran__dini(2311) print *, ran__drnd(), ran__drnd(), ran__drnd() call ran__dini(2333) print *, ran__drnd(), ran__drnd(), ran__drnd()
乱数の性質、わかりましたか?
学習
乱数("ranpack")の使い方をもう少し詳しく。call ran__dini(乱数の種)乱数ルーチンの初期化処理を行うサブルーチン。 『乱数の種』によって、この後出てくる乱数の値(乱数列)が決まる。
ran__drnd()0.0…から0.999…までの実数を返す関数。 値は、『乱数の種』と、初期化されて以降『ran__drnd()』が呼ばれた回数によって一意に決まる。
この関数『ran__drnd()』は「ゼロ個の引数をとる関数」なので、中身のないかっこが付いています。省略は出来ません。
このような0から1までの一様乱数があれば、そこから他のいろいろな乱数を作ることができます。
たとえば「0からnまでの乱数」は、単純にn倍すればいいですね。
n*ran__drnd()もう少し頑張れば、ガウス分布乱数などを作ることもできます。
確率的な現象を扱うにはどうしたらいいか分かりますか?例えば
if (ran__drnd() < p) thenとすれば「確率p (0<p<1)で起きる」ことを記述できます。
まとめ:
『use』してしまえば、モジュール内の関数、変数は「メインプログラムに組み込まれたかのように」使えます。
「サブルーチンは『call』」という所だけが新しいですが、難しくはありませんね。 ただ、個別のモジュールの使い方については一概に言えず、 時には解読する必要がある場合もあります。
Lesson 17. 補
学習
mod(割られる値,割る値)この関数を覚えておきましょう。剰余、つまり割った余りが出てきます。
主な使い方は以下の通り。
・ mod(n,2)で偶奇を得る
・ 何回かに一回実行させる
・ if文を使わず「周期境界条件」を表現する
例)結果を間引いて表示する(step回に1回表示させる)
do i=1,10000 if ( mod(i,step)==0 ) then print *, i, x end if end doこれは結構使えるので覚えておきましょう。
例)長さLの系に周期境界条件を課す
このとき、座標ixのひとつ先はix+1、と言いたいところですが、それだとix=LのときにL+1となってはみ出してしまいます。そこで
ixPlus = mod(ix,L)+1 ixMinus = mod(ix-2+L,L)+1のようにすれば、正しい周期境界にすることができる、と言うわけです。頭の中で計算して確かめてみましょう。
このパターンは大槻先生のお気に入りです。が、正しく使うのは難しいので、学生の皆さんは使わない方が良いです。
おまけ
さて、皆さんお待ちかねの(?)、じゃんけんプログラムの改良版です。乱数を使うことでランダムに勝ち負けが決まります。3回勝負。
複雑になりすぎてしまったので興味ない人は見なくてもいいですが、「これまでの内容(+ほんの少しのテクニック)で簡単なゲームくらいは作れる」という例です。
use bacs use ranpack implicit none integer :: i, te_you, te_com, loseTe, kaisuu, iSeed character(LEN=3) :: chTe(3) chTe(1) = "gu " chTe(2) = "chi" chTe(3) = "pa " call system_clock(iSeed) call ran__dini(iSeed) ! initialize random number print *, "### Janken vs. Mr. Zrukkonar ###" kaisuu = 1 do while (kaisuu <= 3) print * print *, kaisuu, "kaisen" print *, "1:gu, 2:chi, 3:pa, 0:end" read *, te_you if ( (te_you == 1) .or. (te_you == 2) .or. (te_you == 3) ) then print *, "you: "//chTe(te_you) else if (te_you == 0) then exit else print *, "again." cycle end if te_com = int(3*ran__drnd()) +1 print *, "me : "//chTe(te_com) loseTe = mod(te_com,3) +1 if (te_you == loseTe) then print *, "I win!!! You lose." else if (te_you == te_com) then print *, "Draw." else print *, "You win. I lose...." end if kaisuu = kaisuu +1 end do少し解説。
call system_clock(iSeed) call ran__dini(iSeed)では、乱数の種をコンピュータ内の時計から取ってきています。こうすることで、実行する度に異なる乱数を得ることが出来ます。
一般の感覚で言うところの「ランダムな」値を得る定番の技なので知っておきましょう。
ただし物理の問題を扱う際には結果の再現性が求められるため、この技は使いません。というか、使ってはいけません。
character(LEN=3) :: chTe(3)
print *, "you: "//chTe(te_you)あたりは、「文字列型」変数を使っています。これについては後で詳しく解説します。
基本編おしまい
Fortran基本編はこれでおしまいです。 基本的な概念は一通り学んだので、 ここまでの知識をしっかり押さえておけば、たいていの課題には対処できるはずです。理解に不安がある場合は、もう一度前に戻って読み直し、それでもすっきりしない場合は質問して下さい。
分厚い参考書を開いてみれば分かりますが、Fortranにはここに書いていない機能がいろいろあります。
しかし、そういった「特殊」な命令をにわか仕込みで乱用するよりは、 ここまでに勉強した基本的な知識を十分使いこなせるようになる方が大事です。
Fortranは科学計算にしか使われていないマイナー言語です。
「Fortran独特の命令を知っている」よりも、基本的なことをしっかりマスターして「どんな言語でもプログラミングができる」ようになった方が後々のためです。
「Fortran独特の命令を知っている」よりも、基本的なことをしっかりマスターして「どんな言語でもプログラミングができる」ようになった方が後々のためです。