はじめに
前回の記事では、1/4円の積分から円周率を求めるプログラムを使って、doループとファイルの書き出しというFortranを使った計算をするときに最も基本的でよく使うであろう機能を紹介しました。
その時には、積分計算の際に使われる分割数については、分割数が書かれただけのテキストファイルを用意し、結果を出力するファイルの名前については、プログラム本体に書いて指定していました。
しかし、実際の計算では、設定すべき変数の数が膨大になる場合があります。前回のような運用では行き詰まること必至です。前回のような書式のテキストファイルで変数の値を与える場合、何行目にどの変数の値を書いておく、というのを覚えておかなければならないですし、設定した値をあとから見直すのも難しいでしょう。
このような場合、namelistという機能を使うと便利です。ある決められた書式で書くことで、たくさんの変数の値を簡単に与えることができます。また、この書式は、可読性が高い(あとから見てもどの変数にどんな値を入力したのかわかる)こともメリットです。このファイルはエディタなどで手動で編集して作成してもよいですし、(Pythonなどで)ファイル生成のためのスクリプトを用意し、変数の値を変えながら実行するといった運用をすることも可能です。
macOS Sonoma Version 14.6.1, gfortran (gcc version 14.1.0)
Pythonで学ぶ偏微分方程式の数値シミュレーションの技術書を販売中
計算方法、グラフやアニメーションの作成方法、計算ログの残し方を惜しみなく解説しています!
理論と実装の溝を埋めてくれる良書です!
namelistファイルの書き方
プログラムに与えたい変数の値を次のように書いて、保存します。ファイル名は input.nmlst としておきます。
&config
n = 1000,
outfile='takusan-bunkatsu.dat',
/
まず、namelist名を&記号のあとに書きます。その後、設定したい変数名とその値を書き並べていきます。
書式は、変数名のあとにイコール、設定したい値、最後にカンマといった形式です。この書式に特に違和感はないと思いますし、人間が読んでもわかる可読性の高いファイルとなっています。ファイルの最後には、スラッシュを書いて、改行してください。これを忘れるとファイルがうまく読み込まれない場合があります。
プログラム本体側では、namelistファイルを読み込むための手続きを書いておく必要があります。
まず、namelistファイルから読み込む変数は、もちろん宣言しておかなければなりません。
integer(4) :: n
character(128) :: outfile
また、どのnamelistにどの変数が属しているかを記述しておきます。
namelist /config/ n, outfile
読み込みは、
open(10, file='input.nmlst')
read(10, nml=config)
close(10)
でできます。
全体のプログラム
以上述べたことをもとに、前回のプログラムを修正して、namelistファイルから分割数と出力ファイル名を受け取って、指定された分割数で計算を実行し、指定された出力ファイル名で結果を出力してみましょう。
次のようなソースプログラムを作成し、適当な場所(~/Desktop/LabCode/fortran/03_read-namelist
とします)に適当な名前(ここでは、read-namelist.f90とします)で保存してください。
program main
implicit none
! declare variables
integer(4) :: i
integer(4) :: n
integer(4) :: lout = 61, lnml = 10
integer(4) :: iost
character(128) :: outfile
real(8) :: x, y
real(8) :: dx
real(8) :: sum, pi
namelist /config/ n, outfile
! read the namelistfile
open (lnml, file="input.nmlst", action="read", iostat=iost)
if (iost /= 0) then
write (*, *) "ERROR: cannot open file!"
stop
end if
read (lnml, nml=config)
close (lnml)
! rough estimation of pi
dx = 1.0d0/dble(n)
sum = 0.0d0
do i = 0, n - 1
x = dx*dble(i)
y = sqrt(1.0d0 - x*x)
sum = sum + dx*y
end do
pi = sum*4.0d0
! save result
open (lout, file=trim(outfile), action="write")
write (lout, "(i8, x, es12.6)") n, pi
close (lout)
! end of program
stop
end program main
コンパイル
保存したら、コンパイルしましょう。まず、プログラムを保存した場所に移動します:
$ cd ~/Desktop/LabCode/fortran/03_read-namelist
続いて、ソースファイルをコンパイルします。次のように打ち込んでください:
$ gfortran read-namelist.f90 -o read-namelist
コンパイルが正常に終了すると、実行ファイル read-namelist ができているはずです!
namelistファイルの準備
次に、namelistファイルを作っておきましょう。分割数として、n = 10000
、出力ファイル名として outfile = ‘takusan-bunkatsu.dat’
とします。
ファイルの中身は以下のようにして、名前をinput.nmlstとしてプログラムを実行するディレクトリ(今回はプログラムを置いた場所と同じ~/Desktop/LabCode/fortran/03_read-namelist
)に保存してください。
最後の /
(バックスラッシュ) のあとの改行も忘れないでください。
&config
n = 1000,
outfile='takusan-bunkatsu.dat',
/
プログラムの実行
プログラムを実行してみましょう。次のようにターミナルに入力してください:
$ ./read-namelist
namelistファイルで設定した takusan-bunkatsu.datが生成され、内容が
1000 3.143555E+00
となっていれば成功です!
Pythonと組み合わせて変数の値を変えてループ実行
変数の値が外部から設定できるようになると、計算は高速なFortranに任せて、フォルダの管理や計算の設定をPythonでループ実行させる、というような運用が可能です。
以下に、分割数を変えながら実行させるスクリプト例を紹介します。このスクリプトはFortranの実行ファイルがあるディレクトリをカレントワーキングディレクトリとして実行することを想定しています。
import os
import re
# set path etc
cwd = os.getcwd()
prog_dir = cwd
prog_name = "read-namelist"
out_dir = cwd + "/" + "results"
# set number of division
n_divs = [10, 50, 100, 500, 1000, 5000]
for n_div in n_divs:
# determine run number
run_no = 0
for d in os.listdir(out_dir):
if re.match("run_\d{3}", d):
run_no += 1
# make run directory
run_dir = out_dir + "/" + f"run_{run_no:03d}"
os.makedirs(run_dir)
# make namelist file
nmlst_path = run_dir + "/" + "input.nmlst"
outfile = f"pi_ndiv{n_div}.dat"
with open(nmlst_path, "w") as fnmlst:
fnmlst.write("&config\n")
fnmlst.write(f"n={n_div},\n")
fnmlst.write(f"outfile='{outfile}',\n")
fnmlst.write(f"/\n")
# execute program
cmds = f"cd {run_dir}; {prog_dir}/{prog_name}"
print(cmds)
os.system(cmds)
カレントワーキングディレクトリの下に、result ディレクトリができ、更にその下に run_[ddd] ([ddd]は三桁の通し番号)ディレクトリができて、namelistファイルとそれに書かれた変数で計算を実行した結果が出力されるはずです。
コードの解説
import os
import re
# set path etc
cwd = os.getcwd()
prog_dir = cwd
prog_name = "read-namelist"
out_dir = cwd + "/" + "results"
# set number of division
n_divs = [10, 50, 100, 500, 1000, 5000]
まず、必要なモジュールを読み込んで、パスやループさせたいパラメータ (分割数) の設定しています。
for n_div in n_divs:
# determine run number
run_no = 0
for d in os.listdir(out_dir):
if re.match("run_\d{3}", d):
run_no += 1
# make run directory
run_dir = out_dir + "/" + f"run_{run_no:03d}"
os.makedirs(run_dir)
# make namelist file
nmlst_path = run_dir + "/" + "input.nmlst"
outfile = f"pi_ndiv{n_div}.dat"
with open(nmlst_path, "w") as fnmlst:
fnmlst.write("&config\n")
fnmlst.write(f"n={n_div},\n")
fnmlst.write(f"outfile='{outfile}',\n")
fnmlst.write(f"/\n")
# execute program
cmds = f"cd {run_dir}; {prog_dir}/{prog_name}"
print(cmds)
os.system(cmds)
上で設定したパラメータを変えながら、
- 結果を出力するディレクトリの作成、
- namelistファイルの実行(分割数
n
と出力ファイル名outfile
をループごとに変えている)、 - プログラムの実行
をループの中で行っています。
結果を出力するディレクトリはrun_
に続いて通し番号をつけるようにしています。
プログラムの実行は、os.system()
で実行できます。作成した実行プログラムread-namelist
は、カレントワーキングディレクトリに input.nmlst を必要としますので、まず cd
で 実行させたい変数の値が記述されたnamelistファイルがあるディレクトリに移動して、プログラムを実行しています。
最後に
以上で説明したように、Fortranにははじめから外部から変数の値を設定できるnamelistという仕組みがそなわっています。これを使うと、大量の変数の値を変えて計算を実行したい場合でも対応できます。また、namelistファイル自体が可読性の高い書式であるというのもありがたいですね。ぜひ利用してみてください。
参考
- https://zenn.dev/bluepost/articles/1b11f695368d7c
- https://hydrocoast.jp/index.php?fortran/namelistの使用方法
- https://qiita.com/aisha/items/47cd2086d1316348b615
今回は、数値計算をする上で必須のdoループとファイルの入出力について紹介しました。今後もFortranを使ったプログラミングの基礎について紹介していきたいと思います。
Pythonで学ぶ偏微分方程式の数値シミュレーションの技術書を販売中
計算方法、グラフやアニメーションの作成方法、計算ログの残し方を惜しみなく解説しています!
理論と実装の溝を埋めてくれる良書です!