8日目「テキストファイル」:準備

能書き

これまで、コンピュータとのデータのやりとりは黒い画面を通して行ってきました。
しかし実際に研究で使うとなるとパラメータを毎回キーボードから入力するのは非効率的ですし、 膨大な量のデータは黒い画面の中では収まりません。
ここでは、テキストファイルを使ってデータやりとりする方法を学びます。

ファイルを作る

program textfile
 !------------------------
 ! Fortran template
 !------------------------
 use bacs
 implicit none
 !--- begin header ---

 !--- end header ---
 !--- begin main ---
  
 !--- end main ---
end program
Lesson 22. 外部ファイルの読み書き

定義:入出力文

最初に1回
外部ファイル開き、IDを振る
 open (ファイルID,file="ファイル名") 
ファイルが存在しない場合は、そのファイル名で新規作成される。
ファイルIDは10以上の番号を使いましょう。
途中、必要な回数
『ファイルID』のファイルから値を読み込み、変数に代入
 read (ファイルID,*) 変数名[,変数名2,…]
ファイルに値を書き込む
 write(ファイルID,*) 値[,値2,…]
最後に1回
開いた外部ファイルを閉じる
 close(ファイルID) 
実際にはcloseしなくても困らないことが多いが、使い終わったら閉じるのがマナー。

実践

実際にやってみるとわかりやすいでしょう。
 "input.txt"という名前のファイルを作り(本体と同じ場所)、次のように書き込んで下さい。
 # random number seed: iSeed
 2311
 # system size: Lx, Ly
 2 2
1、3行目は人間用の説明文です。
これを読み込むプログラムがこちら。
 implicit none
 integer :: iSeed,Lx,Ly
 
 open(10,file="input.txt")  !"input.txt"を開く
  read (10,*)                !1行飛ばす
  read (10,*) iSeed          !2行目の値をiSeedに代入
  read (10,*) 
  read (10,*) Lx, Ly         !2つ連続で読み込む
 close(10)
 
 !-- calculation --
 !{メインの計算部分} 
 
 open(11,file="result.txt")  !"result.txt"を開く(新しく作る)
  write(11,*) "# input parameters #"
  write(11,*) " iSeed = ", iSeed
  write(11,*) " Lx = ", Lx, ", Ly = ", Ly
 close(11)
 
 print *, " output > result.txt"  !細かい配慮
これを実行すると、"input.txt"を読んで"result.txt"に書き込んでくれます。
先に『open』が必要なこと以外は、画面に出力する時と違いはありません。
もう少し実用的な例を次に示します。
Lesson 23. 文字列型

定義:文字列型

 "文字列定数" もしくは '文字列定数'
 character(len=文字数) :: 変数名
header部分に書く(宣言する)ことで、『変数名』を『文字数』字の大きさの文字列型の変数として使えるようになる。

文字列の結合
  文字列//文字列
文字列の右側の余白をトリミングする関数
  trim(文字列)
文字列関数は他にもありますが、使うことはないでしょう。

学習

卒研における文字列型変数の使い道はただ一つ。出力先ファイル名を指定するために使います。
出力データのファイル名に、使用したパラメータを含める
ことによって、データファイルを管理しやすくなります。
Fortranは文字列を扱うのが基本的に苦手なので、それ以外には使わない方が無難です。
 "input.txt"
 # random number seed: iSeed
 2311
 # system size: Lx, Ly
 20 30
 # output file
 "wavefunction_Lx20Ly30_2311.txt"
本体
 implicit none
 integer :: iSeed, Lx,Ly
 real(kind=double), allocatable :: Amat(:,:)
 character(LEN=99) :: outputFile

 !-- read parameters --
 open(10,file="input.txt")
  read (10,*)
  read (10,*) iSeed
  read (10,*) 
  read (10,*) Lx, Ly
  read (10,*) 
  read (10,*) outputFile
 close(10)

 !-- calculation --
 !{メインの計算部分} 
 
 !-- write result --
 open(11,file=outputFile)
  write(11,*) "# input parameters #"
  write(11,*) " iSeed = ", iSeed
  write(11,*) " Lx = ", Lx, ", Ly = ", Ly
 close(11)

 print *, " output > ", trim(outputFile)
これで何の問題もないのですが、だんだんと「空きすぎたスペースを詰めたい」などと欲が出てきます。 そういう場合は次の項目へ。
Lesson 24. 出力形式を指定する

定義:フォーマット

 print '(フォーマット)', {値}
 write(ファイルID,'(フォーマット)') {値}
『フォーマット』のところに「編集記述子」のリストをカンマ区切りで並べることで、出力形式を指定する。
『'(フォーマット)'』の代わりに『*』とすると、自動で設定される。
read文も指定は出来るが、普通は『*』(自動)で良い。
 
編集記述子一覧(もっと詳しく
  Iw    : 整数
  Fw.d  : 小数点表示 (w >= d+3)
  ESw.d : 指数表示  (w >= d+7)
  nX    : 空白n個 (n省略不可)
  Aw    : 文字列  (w省略可能)
  "文字":そのまま出力される
w,dには具体的な数字が入り、「w文字幅に、右詰で、小数点以下d桁で書く」という意味になる。
編集記述子の前にn(具体的な数字)をつけると、n個くりかえす。
丸かっこでくくった中身を繰り返すこともできる。
編集記述子のリストと値のリストは一対一対応する必要がある。
正しく指定しないと、エラーになったり"###"(桁あふれ)になったりする。

学習

使いこなすのはなかなか難しいので、最初のうちは試行錯誤でやってみよう。
 print '(1X,a,": *",f9.2,"*")',  "F ", -123.4
 print '(1X,a,": *",es9.2,"*")', "ES", -123.4
 print '(1X,a,": *",f5.2,"*")',  "F ", -123.4 
 F : *  -123.40*
 ES: *-1.23E+02*
 F : *#####*

実践

次のような形のプログラミングができれば、卒研は安泰でしょう。
"input.txt"
 # random number seed
 2311
 # system size: Lx, Ly
 3 3
 # number of samples: nsmpl
 100
 # output file
 "result_3x3.txt"
本体
 use bacs
 use ranpack
 implicit none
 integer :: i,j, iSeed, iSmpl,nSmpl, Lx,Ly
 real(kind=double), allocatable :: Amat(:,:)
 character(LEN=99) :: outputFile

 !-- read parameters --
 open(10,file="input.txt")  !入力ファイル"input.txt"を開く
  read (10,*)                !(変数の説明)
  read (10,*) iSeed          !乱数の種
  read (10,*) 
  read (10,*) Lx, Ly         !システムサイズ
  read (10,*) 
  read (10,*) nSmpl          !サンプル数
  read (10,*) 
  read (10,*) outputFile     !出力先ファイル名
 close(10)  !使い終わったら閉じておく

 !-- write header on result file --
 open(11,file=outputFile)   ! 出力先を開いておく
  write(11,'(a)')       "### This is YYY of XXX. ###"  !後で見て分かるような説明
  write(11,'(a)')       "# input parameters #"
  write(11,'(a,i8)')    "#  iSeed = ", iSeed
  write(11,'(2(a,i4))') "#  Lx = ", Lx, ", Ly = ", Ly
  write(11,'(a)')       "#"
  write(11,'(a1,2a3,a12)') "#","x","y","A(y,x)  " !ラベル。 データと位置をそろえた

 !-- initialize --
 call ran__dini(iSeed)
 allocate(Amat(Ly,Lx))
 Amat = zero

 !>>>>> sample loop >>>>>
 do iSmpl=1,nSmpl

    !-- calculation --  !何か計算する
    do j=1,Lx
       do i=1,Ly
          Amat(i,j) = ran__drnd()
       end do
    end do
 
    !-- write result --  !計算する度、欲しい結果を書き出す
    write(11,*) "# sample = ", iSmpl
    do j=1,Lx
       do i=1,Ly
          write(11,'(1X,2i3,es12.4)')   j,  i,  Amat(i,j)
       end do
    end do

 end do
 !<<<<< end sample <<<<<

 close(11) !全てが終わったらclose
 print *, " output > ", trim(outputFile)

おまけ:行列の表示を画面内におさめる

例えば0か1で構成される整数型の行列A(10x10くらい)があったとしましょう。これを画面上で確認したい時、
 print *, "matrix A ="
 do iy=1,ny
    print *,  A(iy,1:nx)
 end do
などとすると、表示があふれて何のことやらわからなくなってしまいます。
そこで、表示幅を狭く設定すると
 print *, "matrix A ="
 do iy=1,ny
    print '(99i2)',  A(iy,1:nx)
 end do
ここの『99』は、『nx』より大きい数であれば良い。
ある程度までなら画面の幅に収まるようになります。

桁数を減らすと見やすくなるだけでなく、出力ファイルの容量もスリムになります。有効に使いましょう。
ただし有効桁数に注意。