【Fortran】namelistファイルを使った変数の値の受け渡し

namelistファイルを使った変数の値の受け渡し

はじめに


前回の記事では、1/4円の積分から円周率を求めるプログラムを使って、doループとファイルの書き出しというFortranを使った計算をするときに最も基本的でよく使うであろう機能を紹介しました。

その時には、積分計算の際に使われる分割数については、分割数が書かれただけのテキストファイルを用意し、結果を出力するファイルの名前については、プログラム本体に書いて指定していました。

しかし、実際の計算では、設定すべき変数の数が膨大になる場合があります。前回のような運用では行き詰まること必至です。前回のような書式のテキストファイルで変数の値を与える場合、何行目にどの変数の値を書いておく、というのを覚えておかなければならないですし、設定した値をあとから見直すのも難しいでしょう。

このような場合、namelistという機能を使うと便利です。ある決められた書式で書くことで、たくさんの変数の値を簡単に与えることができます。また、この書式は、可読性が高い(あとから見てもどの変数にどんな値を入力したのかわかる)こともメリットです。このファイルはエディタなどで手動で編集して作成してもよいですし、(Pythonなどで)ファイル生成のためのスクリプトを用意し、変数の値を変えながら実行するといった運用をすることも可能です。

動作検証済み環境

macOS Sonoma Version 14.6.1, gfortran (gcc version 14.1.0)

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)

上で設定したパラメータを変えながら、

  1. 結果を出力するディレクトリの作成、
  2. namelistファイルの実行(分割数 n と出力ファイル名 outfile をループごとに変えている)、
  3. プログラムの実行

をループの中で行っています。

結果を出力するディレクトリはrun_ に続いて通し番号をつけるようにしています。

プログラムの実行は、os.system() で実行できます。作成した実行プログラムread-namelist は、カレントワーキングディレクトリに input.nmlst を必要としますので、まず cd で 実行させたい変数の値が記述されたnamelistファイルがあるディレクトリに移動して、プログラムを実行しています。

最後に


以上で説明したように、Fortranにははじめから外部から変数の値を設定できるnamelistという仕組みがそなわっています。これを使うと、大量の変数の値を変えて計算を実行したい場合でも対応できます。また、namelistファイル自体が可読性の高い書式であるというのもありがたいですね。ぜひ利用してみてください。

参考



今回は、数値計算をする上で必須のdoループとファイルの入出力について紹介しました。今後もFortranを使ったプログラミングの基礎について紹介していきたいと思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です