これはFortran77の知識があるuserがFortran 90 を使うためのtipsです.
write(*,*) 'xxx=',xxx, & & 'yyy=',yyyという具合だ.
program test_pro implicit none integer::im read(*,*) im call sub1(im) stop end program subroutine sub1(im) implicit none integer::im if (im==1) then write(*,*) 'im is one' else write(*,*) 'im is not one' endif end subroutine
1行は7-72列目を使う.Fortran 77 ではこれしかなかった.でももうやめやめ.とはいえ,ライブラリーなどで固定形式で書かれたプログラムがあるので,それをコンパイルするには,自由形式と別ファイルにする必要があることに注意しよう.
型宣言文は次の構造を持つ 型指定子[[,属性指定子]...::]データ宣言要素ならび
intent(in), intent(out), intent(inout)をsubroutineで引き渡された変数につける.プログラムを読みやすくすると共に,コンパイル時のエラーチェックを強力にする.
例)
subroutine sub1(dim1,dim2,dim3,im,jm) integer, intent(in) :: im,jm real*4, intent(out) :: dim1(im,jm) !出力される配列であって,入力には使わない real*4, intent(inout) :: dim2(im,jm) !出力される配列であって,入力にも使う real*4, intent(in) :: dim3(im,jm) !入力にのみ使われる配列で,値を変更しない
プログラム単位の最初(最初でなくてはならない)にimplicit noneと置くと、暗黙の型宣言は用いられず、すべての変数を宣言する必要がある。一般にプログラミングの手間は増加するが、文法エラーのチェックが強力となるので、エラーの除去は高速に行える。強く推奨。
integer,parameter ::m=2,n=3 というように,型宣言とパラメータ値の設定を1行でやろう
real::A0(3)=(/2.0,3.0,4.0/) data文を使わずにこうする方が良い character::cdm(2)=(/'ab','c '/) 文字型の初期値文は同じサイズでないとダメなので,'c'ではなく'c 'にしている real,dimension(3,4)::A,B,C,E 多数の配列が同じサイズの場合には,こうしよう.dimension属性の使い道はこの場合だけか. real::D(2,2),F(3,3) 配列のサイズがまちまちなら,こう.
include 'parm.f'
などとして、ファイル名を指定することで、プログラムの任意の場所に、外部ファイルをincludeすることができる。例えば、parameter文をinclude fileの中に入れておく、subroutineをinclude file の中に入れておくなどの使いかたができる。もっとも,Fortran 90ではuse文を使う方が気象庁では推奨されている.use文はまだ使いこなしていない.
かつてFortranで,書式付入出力(readやwrite)を行う場合には,書式を別な行にFormat文を使って書くことが一般的だった.しかしこれはprint, write, read 文の中で次のように書く方がよい.なお,print "書式"はwrite(*,"書式")と同じ意味だが,printは標準出力への出力,writeはファイルへの出力として使い分ける方が良い.よほどのことがない限り,独立したformat文は使わないことにしよう.なお,format文はCやRubyやC#には無い,古きただし良くはないFortran の名残と思う.
program test real::a=1.2 integer::ib=123 print "('a=',f6.2,' b=',i4)", a, ib !太字が書式部分 open(21,file='x.txt') write(21,"('a=',f6.2,' b=',i4)") a,ib !太字が書式部分 close(21) end program
名称等 | 説明 | 出力 |
配列同士の+-*/,dim1*dim2など | 配列の個々の要素に対する演算。2つの配列は同サイズであること。 | 配列 |
dot_product(vector_a,vector_b) | 数値型・論理型のベクトル(一次元配列)の内積 | スカラー |
matmul(matrix_a,matrix_b) | 数値型・論理型行列(2次元配列)の積 | 配列 |
transpose(matrix) | 行列(2次元配列)の転置 | 行列 |
sum(array[,nth,mask]) | 和の計算。nthには第何次元目について和をとるかかを指定する。nthを指定しない場合は全要素の和。 maskはarrayと同じ大きさの論理型配列で、これが指定されている場合は、 maskがtrueの要素について演算を行い、falseの要素は無視する。 | nthを指定する場合は入力配列よりも1次元少ない配列。nthを指定しない場合は、スカラー。 |
sum(array[,nth,mask]) | 積の計算。nth,maskについてはsumと同じ。 | sumと同様。 |
maxval(array[,nth,mask]) | 最大値の計算。nth,maskについてはsumと同じ。 | sumと同様。 |
minval(array[,nth,mask]) | 最小値の計算。nth,maskについてはsumと同じ。 | sumと同様。 |
size(array[,nth]) | nthを省略するとarrayの要素の総数、nthを指定すれば、nth次元の要素数。 | スカラー。 |
shape(array) | arrayの形状を出力 | 整数一次元配列 |
lbound(array[,nth]) | nthを省略するとarrayの各次元の下限の要素番号 | 整数一次元配列 |
以下のようにして,任意サイズの配列を返すユーザー定義関数を作ることができる.
module matrix contains function square(dimin) !各要素の二乗を返す関数 real,dimension(:,:):: dimin real,dimension(size(dimin,1),size(dimin,2))::square ! sizeが配列宣言文で使えるのだ square=dimin*dimin end function end module program main use matrix real,dimension(2,3)::a,b do j=1,3 do i=1,2 b(i,j)=i+(j-1)*10 enddo enddo a=square(b) print *,a end program
やっとFortranの文字操作関数も,少しは使えるようになったか.
名称 | 説明 | 出力 | 使用例 |
len | 文字列の長さを整数で返す | 整数 | |
len_trim | 末尾の空白を除いた文字列の長さを整数で返す | 整数 | |
trim | 末尾の空白を削除 | 文字列 | プログラム character cv*4,cg*8 cv='u '; cg=trim(cv)//'.grd'; print *,cg 結果 u.grd |
repeat | 文字列の連結 | 文字列 | repeat('o',10) |
adjust | 文字列の先頭空白を除いて左に寄せる | ||
adjustr | 文字列の最後の空白を除いて右に寄せる | ||
index | STRING中のSUBSTRINGの最初の場所 | ||
rindex | STRING中のSUBSTRINGの最後の場所 | ||
leg | 文字列が大きいか同じか | LGE(STRING_A,STRING_B) | |
lgt | 文字列が大きいか | LGT(STRING_A,STRING_B) | |
lle | 文字列が小さいか同じか | LLE(STRING_A,STRING_B | |
llt | 文字列が小さいか | LLT(STRING_A,STRING_B) | |
scan | 文字列の中にSETの文字があるかどうかを調べる | ||
verify |
Fortran 90の文字列処理はFortran 77に比べるとはるーかに良くなっているが,やはりもっと新しい言語(RubyだとかC#だとか)に比べると,とっても見劣りする.問題の源は,文字変数の長さを最初に定義しなくてはならないことで,allocate も文字変数(文字変数の配列ではなく)には使えない.もっともこれはFortran 2000転じて2005?で改善される予定.当面は文字列を十分大きく宣言はしておき,実際に使うところはtrimやlen_trimで規定する,というのがよさそうである.
==, >, <, >=, <=, /= が利用できる。if の後にはカッコが必要。多分条件のand(or)は,.and. (.or.) と書かなくてはならなく,C風の&& (||) は使えない模様.
一つの変数について複数の条件分岐があるならば,if then, else if でつながずに,select case を使う.
select case (ctaper) case ('hanning') taper=0.5*(1-cos(2*pi*taper/isz)) case ('hamming') taper=0.54-0.46*cos(2*pi*taper/isz) case default stop 'invalid ctaper=' endselect
条件を満たす配列要素に対する演算。強力なのでなるべく使用すること
real dim1(10) do i=1,10; dim1(i)=i-5; enddo where(dim1>0) dim2=dim1+1 where(dim1< 0) dim2="abs(dim1)" endwhere
do i=1,10 ......... enddo
do while(i<10) ......... enddo
ループから抜けるには、exit文を使うことができる。最内側ループから抜けるには,"exit" のみでよく,そうでない場合には次項で説明するdo構造名を使う. 文条件が一つであれば、exit文ではなく、do while を使うのも,より可読性が増すのでお奨め。ループから抜ける目的でgoto文を使ってはならない。
do i=1:1000 read(31,rec=irec,iostat=ierrd) if (ierrd/=0) exit !データがもう無いなどでエラーになったらループを抜ける. enddoループの途中で、ループの最後に実行を飛ばすには、cycle 文を使う。
do構造名を使うことで、多重ループにおいて任意のloopから抜けさせる ことができる。do構造名は下のloop1,loop2で、do 文とenddo文の両方に 付ける必要がある。
loop1: do j=1,3 loop2: do i=1,3 if (i==2) exit loop1 enddo loop2 enddo loop1
宣言部で、
real x1(:,:),y1(:,:) allocatable :: x1,y1 real, allocatable :: x2(:,:),y2(:,:) real, allocatable(:,:) :: x2,y2
と宣言して、動的にメモリーを確保する配列であることを明示する。実際のメモリーの確保は、allocate文で行う。
n=2; m=3; allocate (x(n,m),y(n,m),stat=ier) if (ier/=0) then write(*,*) 'fail to allocate stat=',ier; stop endif
などとする。メモリーを解放(allocateの反対)はdeallocate文で行う。
deallocate (x,y,stat=ier) if (ier/=0) then write(*,*) 'fail to deallocate stat=',ier; stop endif
allocate/deallocateの機能をうまく利用すると、作業配列をmainで宣言する必要が無いなどのメリットがある。 しかしデバッグが困難になる場合もある。例えば、deallocateした後で、その配列を使用すると、'access violation' などのエラーが生じる。また、配列の大きさが実行時に定まるので、一般に配列の領域外参照を行っていることを、コンパイラが検出するのは困難であろう。 したがって、allocate/deallocateの機能は便利ではあるが取り扱いには十分注意することが必要である。
Fortran90ではModuleというものを使って,再利用なプログラムの部品をつくりやすくなっている.例えば,test.f90というメインプログラムと,それで利用されるmoduleであるpackage1.f90というファイルの中身が次のようになっているとする.大事なことは,main program で配列宣言すらしていなくても,moduleで宣言されている配列を使えるということである.moduleはまだあまり使いこなすにいたっていないが,非常に強力である.どの情報をmoduleの外とやりとりし,どの情報をmoduleの中だけで使うかは細かく決めることができる.
!----- test.f90 の中身 program test use package1 call package1_ini(3) call package1_run end program !----- package1.f90 の中身 module package1 implicit none private ! private属性をdefaultに integer,save,allocatable::dim1(:) public :: package1_ini, package1_run ! 外部から使用可 ! contains subroutine package1_ini(nsz) ! 初期化サブルーチン integer::ier,nsz allocate (dim1(nsz),stat=ier) if (ier/=0) then write(*,*) '*package1_ini* fail to allocate stat=',ier; stop endif dim1=1 !実際にはもっと複雑な初期値を与える. end subroutine package1_ini ! subroutine package1_run() ! 実行サブルーチン integer::ier write(*,*) dim1 !実際にはもっと複雑な処理をする. end subroutine package1_run ! end module package1
複数のプログラム単位間で配列や変数を共有するのに,moduleを使うことができる(下の例参照).なお同様の機能はcommon文でも実現できるが,common文は解決困難なエラーの原因となりやすいので使用しないこと.
module defpara real,parameter::undef=-1.e+10 integer,parameter::nsz=2,msz=3 real::dim(nsz,msz) end module defpara program test use defpara ! use 文はimplicit文よりも前(要は一番前) implicit none print *,undef print *,dim print *,size(dim) print *,shape(dim) stop end program
構造体とは,複数のデーターを一つの名前に集約するものである.例えば,名前と体重・身長のデータは次のようにまとめることができる.構造体を用いない場合は,name, weight, height という別々な配列を定義し,それが個人をあらわすデータであるということを,プログラムをする際に覚えておかなくてはならないが,structure を使うことで個人を表すデータであることが明示的になる.またサブルーチンなどへの引渡しでも,別々な配列を引き渡すのではなく,structure を渡せばよいので楽である.なお,structureの型宣言で,allocatable や大きさ引継ぎ配列(Character*(*)を含め)を使うことはできないので,配列要素がいくつまで使われるか不明な場合は,大きめに宣言しておくことになる.structureの宣言は原則としてmoduleで行うべきである.そこでこのドキュメントでは,構造体の解説をmoduleの解説の後にしている.
module defstrct structure /bodydata/ !構造体の型の定義 character(LEN=20):: name real:: height, weight end structure end module program test use defstrct !構造体の型が定義されているmoduleの利用を宣言 integer,parameter::nsz=10 record /bodydata/ bdata(nsz) !構造体の実体の定義 bdata(1).name='hogehoge' !構造体への代入 bdata(1).height=160. bdata(1).weight=70. call test1(bdata,nsz) end program subroutine test1(bdata,nsz) use defstrct record /bodydata/ bdata(nsz) !構造体の型が定義されているmoduleの利用を宣言 write(*,*) bdata(1) end subroutine
上の例は構造体の基本を分かりやすくするために,単純な場合を示したので,ありがたみを実感できなかっただろう.可視化ソフトのGradsを使ったことがある人なら,そのctl file の情報を次のような構造体にまとめられると考えれば,ありがたみが実感できるかと思う.
structure /gradsctl/ character(LEN=256)::grdname,title,options,varname(100),varexpl(100) character(LEN=6)::xctl, yctl, zctl, tctl !'linear'か'levels' character(LEN=32)::tstrt,tstp real::undef integer::xsz,ysz,zsz,tsz,tstrtno,tstpno,vars,varz(100) integer::xdf(1000),ydf(1000),zdf(1000) end structure
構造体 TYPE
Structureと同様の機能を,TYPEを使っても実現できる.例えば,上の名前と身長・体重の例であれば,次のようになる.TYPEとSTRUCTUREとをどう使い分けるべきなのかは,よく分からない.TYPE であれば,intentを指定できるようだから,intentを指定したいのであれば,TYPEの方が良いようだ.STRUCTUREの実体の宣言であるrecord文ではintentは指定できなかったので.しかしSTRUCTUREにも何らかのメリットがなければ,それが用意されている理由が分からない.
module defstrct type bodydata !構造体の型の定義 character(LEN=20):: name real:: height, weight end type bodydata end module program test use defstrct !構造体の型が定義されているmoduleの利用を宣言 integer,parameter::nsz=10 type(bodydata) :: bdata(nsz) !構造体の実体の定義 bdata(1)%name='hogehoge' !構造体への代入 bdata(1)%height=160. bdata(1)%weight=70. call test1(bdata,nsz) end program subroutine test1(bdata,nsz) use defstrct type(bodydata),intent(in):: bdata(nsz) !構造体の型が定義されているmoduleの利用を宣言 write(*,*) bdata(1) end subroutine
デバックをする場合には
ifort -check -traceback xxxx.f90
と,check option と traceback option をつけよう.check option は,実行時に様々なチェックを行うことを,traceback option はメッセージに行番号を示すことを意味する.
Intel Fortran V8.xでは,それ以前のバージョンよりも多くの stack (メモリーの利用方法の一種) を使用する.このため,stack が足りなくなると,プログラム実行中にsegmentation fault や signal 11 というエラーを出して,プログラムが終了する.この場合,プログラムを走らせる前に,コマンド・ラインから, シェルがbashの場合は ulimit -s umlimited を,シェルがcshの場合は limit stacksize unlimited 実行して,スタックサイズを増やすと良い.
segmentation fault エラーが出て,上の方法でスタックを増やしても状況が変わらないのであれば,たいていの場合はメモリーの領域外を参照しているなどのエラーである.まずエラーが出る場所を,サブルーチン⇒ループ⇒特定の場所,というように,print 文を用いて絞り込んで行こう.つまり,怪しい行の前後にprint *,'A', print *,'B'などを置き,例えば,'A'だけが出力されるのなら,まさにそこが問題の場所だということが分かる.このように,問題を特定の行にまで絞りこめたら,原因を探るためにさらにprint文で情報を取得して,何が原因であるのかをよく考えよう.
新しく書いたプログラムはそのままでは普通は動かないので,デバッグが必要である.文法エラーは通常すぐ取れるので,デバッグの本体は,実行時エラーをどう取るかということになる.実行時エラーを取るには,プログラムの挙動を把握することが必要で,そのために特別な条件で出力させたりということを行う.このデバッグ用の計算は,通常デバッグが終われば不要となる.そういう作業を簡単にするのがプリプロセッサーで,intel fortran 自由形式の場合,拡張子がF90である場合に,プリプロセッサーを適用される.例えば,
program test #ifdef debug print *,'debug print' #endif end
というプログラムでは,ifort -Ddebug test.F90 とすると,#ifdef debug ~ #endif の間が,コンパイラによって解釈され,この場合はそこにあるprint文が生きる.一方単に ifort test.F90とすると #ifdef debug ~ #endif の間はコンパイラによって無視される.なお,#は行頭から始まらなくてはならない.また,上の例では,debugが定義されているかどうか(ifdef)によってコンパイラの解釈が変わったが,プリプロセッサに特定の値を渡すこともできる.例えば,
program test #if debug==1 print *,'debug is 1' #endif #if debug==2 print *,'debug is 2' #endif end
に対して,ifort -Ddebug=1 test.F90とすれば上の#if文が,debugに2を指定すれば下の#if文がコンパイラによって解釈される.また愛用のプリプロセッサーの使用法は,test用のmainを#ifdef ~ #endifで囲ってしまうことである.ちょっと複雑なプログラムだと,部品の一つ一つをテストしながら組んで行くことになる.部品それぞれをテストするのに,その部品にtest用のmain program を付けて試すのが,効率が良い.このテスト用mainは,完成した部品には無いことになってないと困る.なぜなら,mainはリンクするプログラム中に一つしか許容されないからである.そこで,mainを#ifdef debug~ #endifの中に入れてしまえば,debugを指定すればそのmainが利用でき,debugを指定しなければmainはなかったことになるのである.とっても便利!
良いプログラミングスタイルを採用することで、プログラムのわかりやすさ(可読性、readability)を向上させ、メインテナンスの手間と間違いを減らすことができる。結果として、早く研究を進めることができる。研究の速さは特に、国際的な競争の激しい分野では非常に重要である。
基本的に心がけることは以下の 4 点である.>
より具体的に注意するべきことは次の通り
program main integer,parameter::nmx=10 integer::n do n=1,10 if ( enddo end program
気象庁でのFortran90 コーディングルール
上のコーディングルールで禁じている,subroutine渡し時の配列形状の変更は本研究室では行ってよい.データ解析では,空間が何次元であっても,空間方向と時間方向の2方向のみが本質的な違いを持つ場合が多い.このような処理を容易にするために,dim(im,jm,km,tsz)とx,y,z,t配列を読み込んて厚さや緯度に応じた重み付けをした後に,EOF解析などでは,dm(im*jm*km,tsz)の配列とすることは実用上よくある.
Digital Fortran 和文オンラインドキュメント(リンク切れ)