前回の基本文法編ではFORTRAN77の変数の宣言、制御構文に触れました。今回は文字や配列の扱い、サブルーチンの使い方、ファイル入出力などの実用的なことをまとめていきたいと思います。(テーマに一貫性がなくなってしまったらすみません。)なお記事中でプログラム名が定義されていないコードはプログラム開始文などを省略しており不完全となっています。
#配列とその演算
基本文法編の例文にも配列を登場させていましたが、配列として宣言した変数は複数の値を保持することができます。Fortranでは1次元だけでなく多次元の配列もシンプルかつ高速に扱うことができ、これがFortranが数値解析の分野で現役たらしめている特徴の1つと言ってもいいでしょう。(※もちろん多次元配列はFORTRAN77に固有の特徴ではありません。)
##配列宣言の基本
配列の宣言は以下の通りです。下限、上限の値は整数(負の値も可)で与える必要があります。
<下限>を指定しない場合はデフォルトで1が下限になります。
型 配列名(<下限:>上限)
###配列宣言いろいろ
INTEGER iarr(10) !配列番号は1~10
INTEGER iarr0(0:9) !配列番号を0~9に指定(C言語式)
CHARACTER carr(64)*16 !64文字16行の1次元配列
REAL farr(10,10) !実数型の2次元配列
!!!!!!parameter文で定数を使って宣言
INTEGER idim,jdim,kmax
PARAMETER (idim=128,jdim=64,kmax=32)
REAL*8 dat(idim,jdim,kmax)
!!!!!!配列要素にアクセス
INTEGER arr(3)
DATA arr / 3, 2, 1 /
WRITE(*,*)arr(1) !arr(1)=3が画面に出力されます。
##配列の演算と出力
配列について細かいところまで書き出すと果てしないので、なるべく網羅した例を挙げて終わりにします。すみません。
PROGRAM main
IMPLICIT NONE
INTEGER arr(20,10),i,j
INTEGER new(10,5)
arr=0 !配列の全ての要素に0を一度で代入、初期化によく使う
DO i=1,20
DO j=1,10
arr(i,j)=i*j !i行j列の配列要素にi*jを代入
ENDDO
ENDDO
new(:,:) = arr(1:10,1:5) !arr配列の1~10行、1~5列をnew配列に代入
new(:,:) = new(:,:)*2 !new配列の全ての要素に2をかけたものをnew配列とする
!ex1) 1行に1個、50行で出力
DO j=1,5
DO i=1,10
WRITE(*,*)arr(i,j) !配列は内側の要素からアクセスしていくと、メモリに連続的にアクセスできるので処理が早くなる
ENDDO
ENDDO
!ex2) 1行あたり5個、10行で出力
DO i=1,10
WRITE(*,100)(new(i,j),j=1,5) !この書き方で横方向に配列を展開できます
ENDDO
!ex3) ex2+全ての配列要素を10倍して出力
DO i=1,10
WRITE(*,100)(new(i,j)*10,j=1,5)
ENDDO
100 FORMAT(10I5)
END
#ファイル入出力
まずはじめにプログラムの外部からのデータを読み取や外部ファイルへのデータの書き込み方法について記述していきます。ファイル入出力は(1)OPEN, (2)READ or WRITE, (3)CLOSEの3ステップで行われます。以下詳しく解説していきます。
##OPEN文による外部ファイルのロード
外部ファイルに入出力するにはOPEN文を用いてそのファイルをプログラムにロードする必要があります。
OPEN(<ファイル装置番号>,file='<ファイル名>')
!バイナリ形式で入出力したいとき
OPEN(<ファイル装置番号>,file='<ファイル名>',form='unformatted')
ファイルが存在していなければ新しくファイルを作成します。
formを指定しない場合はファイルをテキスト形式で開きます。この場合、データ入出力の際はフォーマットを指定してあげなければなりません。form='unformatted'
を記述した場合、データはバイナリ形式で読み書きされます。この場合はそのファイルは2進数で記録されているので直接開くことはできなくなりますが、入出力が高速になる・出力ファイルの容量に無駄がなくなる、などのメリットもあります。
###装置番号
OPEN文で指定する装置番号は整数(整数型変数でも可)で記述します。(前回の記事に出てきた文番号とは異なるので注意してください)
5番と6番はREAD,WRITE文で予約されているので避けた方がよいでしょう。
以後開いたファイルへの入出力はこの装置番号を用います
##READ文・WRITE文によるデータの読み書き
READ文もWRITE文も基本的にとる引数は同じで第1引数には装置識別子を、第2引数にはフォーマットを定める書式識別子をとります。
READ(<装置識別子>,<書式識別子>)<読み取ったデータを代入するリスト(変数とか)>
WRITE(<装置識別子>,<書式識別子>)<ファイルに書き込むデータのリスト(変数とか)>
###装置識別子
装置識別子 | 意味 |
---|---|
* | コンソール入出力 |
装置番号 | OPEN文でこの番号と関連づけられているファイルに対して入出力 |
5 | パンチカード時代の名残(らしい),使ったことないです |
6 | 同上 |
文字型変数 | 入出力を文字変数に対して行う(詳しくは「数値と数字の変換」で紹介します) |
###書式識別子の書き方
書式識別子は編集記述子を用いて記述されます。書式識別子をREAD・WRITE文の第2引数渡す方法は2つあり、1つは直接記述する方法、もう一つはFORMAT文に書式識別子を記述し、その文番号を第2引数に渡す方法です。
ここではどちらも紹介していきます。
####編集記述子
編集記述子の表中のw,d,m,r
は正の整数値で以下の意味を持ちます。
記号 | 意味 |
---|---|
w | 記述欄の桁数 |
d | 実数型で、小数点より右側の桁数 |
m | 整数型で、数字を書く桁数 |
r | 反復回数 |
編集記述子
編集記述子 | 意味 | 備考 |
---|---|---|
[r]__I__w[.m] | 10進整数をw桁の欄に右詰め,m桁の欄に空白がある場合0を代入 | w>=m |
[r]__E__w[.d] | 実数型をw桁の欄に小数点以下d桁で指数形式で表示,仮数部は0.1~1.0になる | w>=d+7 |
[r]A[w] | 文字型をw桁の欄で右詰め | |
[r]X | スペース |
####編集記述子を直接渡す方法
INTEGER i
REAL*8 a
CHARACTER month*16,label*16,formt*32
i = 8
a = 123.674e10
month = 'monthly'
label = 'value'
OPEN(10,file='output.text')
WRITE(10,'(2A16)')month,label
WRITE(10,'(I2.2,1X,E12.5)')i,a
!書式識別子は文字型変数で与えてもよい
formt = '(I2,E14.3)'
WRITE(10,formt)i,a
monthly value
08 0.12367E+13
8 0.124E+13
####FORMAT文を経由する方法
<文番号> FORMAT(<書式識別子>) !FORMATに渡す書式識別子は文字型ではない
先ほどと同様の例で書いてみます。
INTEGER i
REAL*8 a
CHARACTER month*16,label*16,formt*32
i = 8
a = 123.674e10
month = 'monthly'
label = 'value'
OPEN(10,file='output.text')
WRITE(10,100)month,label
WRITE(10,200)i,a
WRITE(10,300)i,a
100 FORMAT(2A16) !FORMAT文はプログラムのどこに書いてもOK
200 FORMAT(I2.2,1X,E12.5)
300 FORMAT(I2,E14.3)
結果は先ほどと同じになるので省略します。
どちらの方法が良いかはよくわかりません。
##CLOSE文でファイルを閉じる
読み書きの必要がなくなったファイルはCLOSE文で閉じておきます。
CLOSE(<装置番号>)
#文字の連結
FORTRANで文字の連結を行うときは'+
'の代わりに'//
'を使います。
PROGRAM MAIN
IMPLICIT NONE
CHARACTER text*7
DATA text /'FORTRAN'/
CHARACTER ver /' 77'/
WRITE(*,*)text//ver !'FORTRAN77'が出力される
END
##数値と数字の変換
文字の連結は当然文字同士でしか行うことができないので、下の例のように、整数型や実数型の数値を文字として扱うには型の変換を行う必要があります。ところが、Fortranでは宣言した変数の型自体を変えることができないので、あらかじめ文字型の変数も宣言しておくことで、この問題に対処します。
このとき数値=>数字
の変換にはWRITE文を使います。
WRITE(<出力先の文字型変数>,<数値型の書式識別子>)<文字型に変換したい数値型の変数>
!具体例
INTEGER iyy
CHARACTER cyy*4
iyy = 2018
WRITE(cyy,('I4.4'))iyy
整数型変数のiyyをWRITE文で数字として読み取って、文字型変数のcyyに出力しています。
##文字の連結の実例
文字の連結は個人的には、モデルのアウトプットを、ある座標における時系列データに整理するときに、日付ラベルを作成するために利用しています。日付ラベルは一般的によく使われるYYYY-MM-DD
型(ISO 8601の拡張型と呼ばれるらしい)になるようにします。
INTEGER iyy,imm,idd
INTEGER mday(12)
DATA mday /31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 /
CHARACTER cyy*4,cmm*2,cdd*2
CHARACTER date*10
DO iyy=2000,2005 !年
WRITE(cyy,'(I4.4)')iyy
IF (iyy % 4 == 0) THEN !うるう年の判定
mday(2) = 29
ELSE THEN
mday(2) = 28
ENDIF
DO imm=1,12 !月
WRITE(cmm,'(I2.2)')imm
DO idd=1,mday(imm) !日
WRITE(cdd,'(I2.2)')idd
date=cyy//'-'//cmm//'-'//cdd !ここでカウンタ変数をもとに日付ラベルを作成
!!~~~~~ファイルへの書き込み処理等は省略~~~~~~~~
ENDDO
ENDDO
ENDDO
(可読性を高めるためにDOループを階層にするとすぐに文字数制限に到達してしまうところが悩みどころ...)
#サブルーチンを使う
数値モデルではしばしば1つのメインプログラムとその他膨大な数の副プログラムで構成されます。ここでは副プログラムを構成するサブルーチンの使い方についてまとめていきます。
サブルーチンのイメージを下図に書いてみました。
ここでは渡された素材に対して色を塗るというサブルーチンがあったとします。メインプログラムからはCALL文
によって素材をサブルーチンに渡すことで、素材の色塗りを依頼することができます。サブルーチンでの処理が終わると加工された素材がメインプログラムに返却されます。
このとき渡した素材の個数と返却されるものの個数は一致していなければなりません。 ここでいう素材は変数に対応します。
それではサブルーチンの具体的な書き方に移っていきます。
##サブルーチンの構造
サブルーチンは呼び出し側のプログラムとは独立して書かれます。そのためサブルーチン内で用いる変数や定数は宣言されなければなりません。また基本的に呼び出し側のプログラムで使われている変数は共有されません。同じ変数名であっても別のものと考えなければなりません。
サブルーチンを呼び出すときは呼び出し側からCALL文
を用いて呼び出します。
PROGRAM MAIN
IMPLICIT NONE
INTEGER <実引数1>
!~~~~~~~~~中略~~~~~~~~~!
CALL sub(<実引数1>,<実引数2>,...,)
END
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
SUBROUTINE sub(<仮引数1>,<仮引数2>,...,<仮引数n>)
IMPLICIT NONE
INTEGER <仮引数1> !仮引数も含めて変数を宣言する
!~~~~~~~~~中略~~~~~~~~~!
RETURN
END
実引数には呼び出し側のプログラムで宣言している変数および定数を指定します。仮引数にはサブルーチン内で宣言される変数および定数を指定します。実引数と仮引数は書かれた順に1対1に対応します。そのため対応する実引数と仮引数の型は一致していなければならず、また引数の数も一致していなければなりません。
CALL文でサブルーチンへ処理が移ったとき、仮引数は対応する実引数と同じ値を保持します。その後RETURN文に到達したときの仮引数と値を実引数へと渡します。
引数の個数が多くなってくると誤りを起こしやすくなるので、下記のように継続行をうまく使って書くこともできます。
継続行を用いて引数を見やすくした書き方
PROGRAM MAIN
!~~~~~~~~中略~~~~~~~~~~~~~!
CALL sub
I ( <実引数1>,
M <実引数2>,.....,<実引数n-1>,
O <実引数n> )
!~~~~~~~~中略~~~~~~~~~~~~!
SUBROUTINE sub
I ( <仮引数1>,
M <仮引数2>,.....,<仮引数n-1>,
O <仮引数n>)
!~~~~~~~~省略~~~~~~~~~~~~!
(書きながら気づいたのですが、どうやら6桁目の継続行記号は0以外なら何でもいいみたいですね...)
この例ではI
は入力専用(ルーチンの中での書き換えはない),M
は入力値を変更して出力,O
は出力専用(入力時は不定でよい)といった使い方をしてみました。ところがこの書き方では引数の値の変更を制限してくれません。もしそのような制限を加えたい場合はINTENT文
を使うのが良いでしょう。(ソースコードには出てこないので割愛)
##具体例
かなり回りくどい書き方ですがサブルーチンからさらに別のサブルーチンを呼び出すパターンも紹介しておきます。
ここではメインプログラムから実引数としてi,j,totalをサブルーチンに渡し、i,jを入れ替え、iとjの和をtotalに代入するという処理を行っています。
PROGRAM MAIN
IMPLICIT NONE
INTEGER i,j,total
i=1 ; j=2 !! ; 記号で改行したことにできます。
WRITE(*,*)'before'
WRITE(*,100)'i=',i,'j=',j,'total=',total
CALL swap(i,j,total)
WRITE(*,*)'after'
WRITE(*,100)'i=',i,'j=',j,'total=',total
100 FORMAT(A3,I2,A3,I2,A6,I2)
END
SUBROUTINE swap(ii,jj,total)
IMPLICIT NONE
INTEGER ii,jj,total,temp
temp = jj
jj = ii
ii = temp
CALL add(ii,jj,total)
RETURN
END
SUBROUTINE add(ii,jj,total)
IMPLICIT NONE
INTEGER ii,jj,total
total = ii + jj
RETURN
END
ファイルを2つに分けた場合ifort main.F sub.F
でまとめてコンパイルできます。
before
i= 1 j= 2 total= 1
after
i= 2 j= 1 total= 3
#まとめ
思っていた以上に長くなりましたが、今回の記事で数値モデルに登場するFORTRAN77文法の8割くらいは網羅できました。残りは番外編で消化します。
FORTRAN77は一行あたりの文字数制限と行頭6桁にコードを記述できないことに気を付ければ、古い言語だけに機能も少なく、仕様も単純なので習得はさほど難しくないのではないでしょうか...
私自身そこまで知識や技術に乏しいところがあるので、表現がおかしかったり、文法的に間違っている所とかがありましたらご指摘いただけると助かります。
(正直サブルーチンはうまくまとめきれていないと感じています。)
#次回の予定
サブルーチンに関わるCOMMON文,SAVE文,ENTRY文、推奨されていないのでスルーしたGOTO文、あとはNAMELISTとかプリプロセッサについて記述する予定です。
(いい加減次で終わりにします。)
#参考文献
富田博之・斎藤泰洋 共著「Fortran 90/95プログラミング」(2011年 改訂新版)