Fortran

Fortran 周辺のこと

Fortran について。

(一部 MacOS を想定)

Fortran は数値計算を主に取り扱うプログラミング言語である。歴史は古く、現在は数値計算の分野でしか使われないが、今でも物理などの分野では現役である (例えば Intel のコンパイラーは C/C++ と Fortran をサポートしている。)。実際長く使われている歴史があるので、信頼のおける言語である。また、言語構造が簡単なので、早く習得できると思う。

Fortran ももちろん、できてから今まで言語の更新が行われてきた。紙媒体に穴を空けたものからデータを読み取っていたときの名残が、Fortran 77 というバージョンには残っており、左を 6 マス空けなければならない。しかし、Fortran 90 や 95 といった後継の Fortran ではそれらは解消され、モジュールや、ポインターなども使えるようになり、今では現代的なプログラミング言語となっている。

目次

導入 (Mac)

Fortran を含め、多くの言語では、人間の目に易しい書き方が可能である一方、コンピュータからすれば非常に効率の悪い書き方になっている。なので、人間が書くときには人間に分かりやすく、コンピュータが実行するときにはより効率的な書き方にする必要がある。そのように変換することをコンパイルするという。コンパイルするツールをコンパイラーと呼び、Fortran ではこのコンパイラーが必要である。

環境準備

以下の手順でコンパイラーを導入する準備を行う。

  1. Xcode を App Store からインストールする。
  2. 以下のコマンドで Command Line Tools をインストールする。
    1xcode-select --install
  3. XQuartz をインストールする。
  4. MacPorts または、 Homebrew または、 Fink などを用いて GNU の Fortran コンパイラーをインストールする。
  5. ここでは、Homebrew の方法で試みる。

    Homebrew のインストール

    Homebrew のトップページにあるように、以下のコマンド (変更があり得るので確認すること) にて Homebrew をインストールする。

    うまくいったら、以下のコマンドで最新状態にアップデートする。
    1brew update
    さらにうまくいったら、以下のコマンドで問題がないかチェックする。
    1brew doctor
    後々問題が起こらないように、ここで得られたエラーや警告は全て解決しておこう。 途中、ごちゃごちゃ言われたらその都度内容を読んで対処する。以下、起こりえる問題点を示す。

    • Xcode の使用許諾に同意していない。

      1Agreeing to the Xcode/iOS license requires admin privileges, please re-runs as root via sudo

      → Xcode を起動してライセンス契約にサインする。

    • XQuartz が古い。

      1Your XQuartz (x.x.x) is outdated
      2Please install XQuartz x.x.x:

      → 最新のものにアップデートする。

    gfortran (GNU の Fortran コンパイラー) のインストール

    gfortran は gcc のパッケージに含まれる。よって、以下のコマンドで、gcc をインストールする。

    1brew install gcc
    インストールが完了したら、 と実行してみて、
    1gfortran: fatal error: no input files
    2compilation terminated.
    (インプットファイルが無いよ!) などと出力されたら、うまくいっている。
    1gfortran --version
    とすれば、コンパイラーのバージョンが分かる。

    コンパイル

    以下のようにプログラムファイル (test.f90) をコンパイルする。

    1gfortran test.f90
    すると、a.out というファイルができる。これを実行するには、 とする。ここで、a.out は実行ファイルと呼ばれる。 実行ファイルの名前を変えたければ、
    1gfortran test.f90 -o test.exe
    とすればよい。実行ファイルとして、test.exe ができる。

基本的な書き方・ルール

まずは基本中の基本の話から

  • プログラムのファイル名は、xxx.f90 とする。 xxx の部分はアルファベット [a-z,A-Z] から始まり、アルファベット [a-z,A-Z] 、数字 [0-9] 、およびアンダーバー _ 、ハイフン - 、プラス記号 + 等が使える。あまり複雑な記号を使うのはエラーの元なので控える。空白文字 (スペース) は使わない。日本語も使わない。
  • Fortran 内部では、大文字と小文字の区別はない。パラメーターは大文字で書くなどの統一性を持たせると良い。
  • コメント文はびっくりマーク ! で記述し、その行のそれ以降は無視される。
  • 長いプログラム文は読みにくいので、適宜改行を入れる。改行を入れるには、 & 記号を行末に書く。また、分かりやすいように、改行後の行頭にも & を入れると良い。
    1data = a + b + c + d + e + f + g + h & ! 改行
    2   & + i + j + k + l + m + n + o + p & ! 改行
    3   & + q + r + s + t + u + v + w + x & ! 改行
    4   & + y + z
  • 基本的に 1 コマンド 1 行だが、セミコロン ; を使って複数のコマンドを 1 行に書くこともできる。
    1a = 1d0; b = 2d0; c = 3d0
  • 配列を使うことができる。配列とは変数名が一つで複数の変数を格納することができる箱。型名及び、次元を与える。
    1integer, dimension(10) :: iarray ! 以下でも同じ
    2integer :: jarray(10) ! Fortran では配列のインデックス (番号) は 1 から始まる。
    3integer, dimension(2:10) :: narray ! 配列の番号を好きな数から始めることもできる。
    4integer :: marray(1:10,0:9) ! 2 次元以上の配列の定義可能。
  • 文章の構成
    1. プログラム名を書く。
    2. 明示的に型宣言をするように指定する。
    3. 変数を定義する。 型名::変数名 の形で定義する。パラメーターの場合は値も代入する。
      • パラメーター (定数) の定義。全て一般変数で定義してもいいが、パラメーター指定にすると、プログラムの実行速度が早くなり、また、バグの発見が容易になる。
      • 一般変数の定義。
      • よく使う型は以下である。
        記述方法 値の例
        整数 integer 1, 2, 5, 10
        実数 (倍精度) real(kind(0d0)), couble precision 1d0, 2.0d0, 5d-1
        複素数 (倍精度) complex(kind(0d0)) cmplx(1.0,0.0,kind(0d0)), cmplx(0d0,1d0)
        文字 character(1), character(100), character(*) 'abc', "xyz", "123"
        論理 logical .true., .false.
      • 変数には対応する形式で代入する。形式は、上の表の「値の例」参照。
      • 変数名はアルファベットから始まれば何でも良いが、以下のルールをオススメする。
        1. 整数は、 i, j, n, m を変数の前に付ける。
          1integer :: imax, imin, jsite, num_elements, max_width
        2. 実数は、 d を変数の前につけdる。
          1real(kind(0d0)) :: dval, data, dsize
        3. 複素数は、 c を変数の前に付ける。
          1complex(kind(0d0)) :: cdata, cpsi, cphi
        4. 文字列は、 s を変数の前に付ける。
          1character(10) :: sfile, sname
        5. 論理型変数は、 l を変数の前に付ける。
          1logical :: lswitch, lflag
    4. 実行するコマンドを書く。
    5. プログラムの終了を記述する。
    6. プログラム例
      01program test ! プログラム名 test
      02implicit none ! 明示的型宣言
      03 
      04integer, parameter :: dp = kind(0d0) ! 型宣言でよく使うので、あらかじめ定義。
      05! 上で定義した変数 (dp) は直ちに使える。
      06complex(dp), parameter :: zi = cmplx(0.0,1.0,dp) ! 純虚数
      07real(dp), parameter :: dpi = 3.1415926535897932d0 ! 円周率
      08 
      09integer, parameter :: iNx = 50, iNy = 50 ! 例えばシステムのグリッド数
      10real(dp), parameter :: dxmax = 10d0, dymax = 10d0 ! 例えばシステムサイズ
      11real(dp), parameter :: dxstep = dxmax / iNx ! 例えばグリッド幅。
      12real(dp), parameter :: dystep = dymax / iNy
      13 
      14real(dp) :: dphi(iNx,iNy)
      15integer :: i, j
      16 
      17!----- プログラム本体 -----
      18! いろいろコマンドを書く。
      19!----- プログラム終了 -----
      20 
      21end program test ! プログラムの終了。

繰り返しの文

繰り返しの方法は、 doenddo (end do) を用いる。

i に 1 から 100 までを順番に入れて、それを足しあわせる例。

1ival = 0
2do i = 1, 100
3  ival = ival + 1
4enddo

複数の入れ子構造も可能。

i にまず 1 を入れて、j に 1 から100 までを入れながらその合計を行い、終わったら、i に 2 を入れて、j に同じ操作、という感じに i に 100 が入って、j のループがなされるまで続ける例。

1ival = 0
2do i = 1, 100
3  do j = 1, 100
4    ival = ival + i + j
5  enddo
6enddo

配列の値を操作する場合、Fortran では左側の変数から操作した方が処理が早い。

2 次元配列 darray の i, j 成分をどんどん足していく例。

01!--- 遅い ---
02dval = 0d0
03do i = 1, 100
04  do j = 1, 100
05    dval = dval + darray(i,j)
06  enddo
07enddo
08!--- 早い ---
09dval = 0d0
10do j = 1, 100
11  do i = 1, 100
12    dval = dval + darray(i,j)
13  enddo
14enddo

条件分岐

結果によって処理を変えたいときには、if 文を用いて条件分岐を行う。

dval が 50 以上なら "pass" と書き出し、それ以外なら、"bad" と書き出す例。

1if (dval >= 50d0) then
2  write(*,*) "pass"
3else
4  write(*,*) "bad"
5endif

dval が 80 以上なら "good" と書き出し、50 以上 80 未満なら "soso" と書き出し、50 未満なら "bad" と書き出す例。

1if (dval >= 80d0) then
2  write(*,*) "good"
3elseif (dval >= 50d0) then
4  write(*,*) "soso"
5else
6  write(*,*) "bad"
7endif

書き出しのコマンド

ファイルへの書き出し

ファイルへの書き出しはまずはファイルを開く (作る) open コマンドから始める。

1open(10, file = "test.txt")
ここでは、10 番という装置番号で test.txt というファイルを開く (作る) ことを行っている。装置番号というのは open に関連付けられた番号で、
1write(10,*) 'this is a test.'
とすれば、先ほど 10 番で開いた test.txt に書き込むことができる。 ファイルを開いたら、最後に必ず close 文で閉じなければならない。

既存のファイルを開いて、その一番最後から追加で記述したい場合は、 open にさらに position = 'append' という引数を追加する。

1open(10, file = 'test.txt', position = 'append')
これで、再度 write 文で書き込みを行ったとき、ファイルの途中から書き込みを行ってくれる。

乱数の取り扱い (Intel MKL)

Intel コンパイラーを購入している場合、本格的に乱数を扱いたい場合や、テストで適当な乱数の配列を作りたい場合などに、組み込みの乱数発生装置ではなくて、Intel 謹製の乱数発生装置を用いることができる。その方法について述べる。詳しくはオフィシャルなドキュメントを参照のこと。

以下のようなモジュールを自分で準備して使うと便利。

01!--- compile this file with ---
02! ifort \
03! -I ${MKLROOT}/include ${MKLROOT}/include/mkl_vsl.f90 \
04! -mkl randomnumber.f90
05module randomnumber
06  use mkl_vsl_type
07  use mkl_vsl
08  implicit none
09 
10  private
11  public randomnumber_ini, randomgenerate_gauss, randomgenerate_uniform
12 
13  integer, save, private :: statvsl, irand
14  type(vsl_stream_state), save, private :: planvsl
15  integer, parameter, private :: dp = kind(0d0)
16!---------------------------------------------------------------------
17  contains
18    subroutine randomnumber_ini
19      integer :: cr, cm
20      call system_clock(irand,cr,cm) ! use clock time for creaating seed
21      statvsl=vslnewstream(planvsl, vsl_brng_mt19937, irand) ! create seed
22      return
23    end subroutine randomnumber_ini
24!---------------------------------------------------------------------
25    subroutine randomgenerate_gauss(inr, faverage, fsigma, frand)
26      integer, intent(in) :: inr
27      real(dp), intent(in) :: faverage, fsigma
28      real(dp), intent(out) :: frand(inr)
29      statvsl = vdrnggaussian(vsl_rng_method_gaussian_boxmuller,planvsl,inr,frand,faverage,fsigma)
30      return
31    end subroutine randomgenerate_gauss
32!---------------------------------------------------------------------
33    subroutine randomgenerate_uniform(inr, fmin, fmax, frand)
34      integer, intent(in) :: inr
35      real(dp), intent(in) :: fmin, fmax
36      real(dp), intent(out) :: frand(inr)
37      statvsl = vdrnguniform(vsl_rng_method_uniform_std,planvsl,inr,frand,fmin,fmax)
38      return
39    end subroutine randomgenerate_uniform
40 
41end module randomnumber

上記モジュールをコンパイル時に読み込んで、使用したいプログラムの中で、

1program main
2  use randomnumber
3  implicit none
4  integer, parameter :: ixsize = 128, iysize = 128, izsize = 128
5  integer, parameter :: ivolume = ixsize * iysize * izsize
6  real(dp), dimension(ixsize,iysize,izsize) :: dmatrix
として、まずは初期化する。
1call randomnumber_ini
それから、一様乱数なら、(範囲 [0,1])
1call randomgenerate_uniform(ivolume,0d0,1d0,dmatrix)
とすれば良い。ガウス分布の乱数なら、(平均 0.5, 分散 0.2)
1call randomgenerate_gauss(ivolume,0.5d0,0.2d0,dmatrix)
とする。

コード集

01function lkiriban(ival,imin,imax)
02  implicit none
03 
04  integer, intent(in) :: ival
05  integer, optional, intent(in) :: imin
06  integer, optional, intent(in) :: imax
07  logical :: lkiriban
08 
09  integer :: i, idev
10  integer :: imod
11 
12  if(present(imin)) then
13    if(ival < imin) then
14      lkiriban = .false.
15      return
16    endif
17  endif
18 
19  if(present(imax)) then
20    if(ival > imax) then
21      lkiriban = .false.
22      return
23    endif
24  endif
25 
26  if(ival /= 0) then
27    do i = 1, 10
28      idev = 10**i
29      imod = mod(ival,idev)
30      if(imod == 0) then
31        cycle
32      else if(imod == ival) then
33        lkiriban = .true.
34        return
35      else
36        lkiriban = .false.
37        return
38      endif
39    enddo
40  endif
41 
42  write(*,*) 'ERROR in lkiriban'
43 
44end function lkiriban
01function basename(aname)
02  implicit none
03 
04  character(*), intent(in) :: aname
05 
06  character(len(aname)) :: basename
07 
08  integer :: index_start
09  !integer :: ilength
10 
11  !ilength = len(aname)
12  index_start = index(aname,".",back = .true.)
13 
14  basename = aname(1:index_start)
15 
16end function basename
01module timecount_mod
02implicit none
03 
04integer, parameter :: dp = kind(1d0)
05 
06private
07public timecount
08 
09interface timecount
10  module procedure timecount, timecount_start
11end interface
12 
13contains
14  subroutine timecount(time,time_spent)
15  implicit none
16 
17    integer, intent(inout) :: time
18    real(dp), intent(out) :: time_spent
19 
20    integer :: time_diff
21    integer :: time_now
22    integer :: time_rate
23    integer :: time_max
24    call system_clock(time_now, time_rate, time_max)
25    if(time < time_now) then
26      time_diff = time_now - time
27    else
28      time_diff = time_max - time + time_now
29    endif
30 
31    time = time_now
32    time_spent = dble(time_diff) / time_rate / 60d0
33 
34  end subroutine timecount
35  !----- just tag a time -----
36  subroutine timecount_start(time)
37  implicit none
38 
39    integer, intent(out) :: time
40 
41    call system_clock(time)
42 
43  end subroutine timecount_start
44end module timecount_mod
001! This num2char convert integer/double to character
002! Usage: num2char(string, number [, options])
003!
004!   options for intger ver.:
005!       FORM:   give a form. Example-> "(i10)"
006!       BLANK:  a (replace the blank with "a". a: any single character)
007!
008!   options for double ver.:
009!       FORM:   give a form. Example-> "(es10.3)"
010!               IGNORing other options except BLANK
011!       HEAD:   N/P (N: trim to left, Z: zero, P: +/-)
012!       IND:    # (digit of index or exponent)
013!       BLANK:  a (replace the blank with a. a: any character)
014!
015!   if BLANK = "", length of 'string' shorten.
016!
017! Default form for integer ver.:
018!       FORM = "(i" // len(string) //")
019!       call num2char(string, number, BLANK = "0")
020!
021! Default form for double ver.:
022!       FORM = "(SP,es" // len(string) //"." //len(string)-(7 or 8) //")"
023!       call num2char(string, number, HEAD="P")
024 
025module num2char_mod
026implicit none
027 
028integer, parameter :: dp = kind(1d0)
029 
030private
031public num2char
032 
033interface num2char
034  module procedure inum2char, dnum2char
035end interface
036 
037contains
038  !===== integer ver. =====
039  subroutine inum2char(schar,inum,BLANK,FORM)
040  implicit none
041    integer,                intent(in)    :: inum
042    character(*),           intent(inout) :: schar
043    character(1), optional, intent(in)    :: BLANK
044    character(*), optional, intent(in)    :: FORM
045 
046    character(100) :: sformat
047    character(1) :: s
048    integer :: ilen
049    character(2) :: slen
050 
051    integer :: i
052 
053    !----- format define : option 'FORM' -----
054    ilen = len(schar)
055    write(slen,'(i2)') ilen
056    if(present(FORM)) then
057      sformat = FORM
058    else
059      sformat = "(i" // trim(adjustl(slen))  // ")"
060    endif
061 
062    !----- " " -> s : option 'BLANK' -----
063    if(present(BLANK)) then
064      s = BLANK
065    else
066      s = "0"
067    endif
068 
069    write(schar,sformat) inum !<--- input data
070 
071    do i = 1, ilen
072      if(schar(i:i) == " ") schar(i:i) = s
073    enddo
074 
075    return
076  end subroutine inum2char
077 
078  !===== dble ver. =====
079  subroutine dnum2char(schar,dnum,HEAD,IND,FORM,BLANK)
080  implicit none
081    real(dp),               intent(in)    :: dnum
082    character(*),           intent(inout) :: schar
083    character(1), optional, intent(in)    :: HEAD
084    integer,      optional, intent(in)    :: IND
085    character(*), optional, intent(in)    :: FORM
086    character(1), optional, intent(in)    :: BLANK
087 
088    character(100) :: sformat
089 
090    ! +x.yyyyyE+zz
091    integer :: ilength_schar   ! length of schar
092    integer :: ilength_sign    ! length of sign (0 or 1)
093    integer :: ilength_front   ! length of +x. (2 or 3)
094    integer :: ilength_decimal ! length of yyyyy
095    integer :: ilength_ind     ! length of zz
096    integer :: ilength_exponent! length of E+xx (ilength_ind + 2)
097 
098    ! for format
099    character(2) :: slength_schar
100    character(2) :: shead ! (SP: show +/-, "": only +)
101    character(2) :: sexpornent ! Ex (x is ilength_ind)
102    character(2) :: slength_decimal
103 
104    integer :: i
105 
106    ilength_schar = len(schar)
107    write(slength_schar,'(i2)') ilength_schar
108 
109    if(present(FORM)) then
110      write(schar,FORM) dnum ! input data
111      schar = adjustl(schar)
112 
113      if(present(BLANK)) then
114        do i = 1, ilength_schar ! replace " " to BLANK
115          if(schar(i:i) == " ") schar(i:i) = BLANK
116        enddo
117      endif
118      return
119    endif
120 
121 
122    if(present(HEAD) .and. (HEAD == "N" .or. HEAD == "n")) then
123      if(dnum >= 0) then
124        ilength_sign = 0
125      else
126        ilength_sign = 1 ! this is used for minus sign
127      endif
128      shead = "  ," ! only '-' shows if necessary
129    else
130      ilength_sign = 1
131      shead = "SP," ! SP shows + and -
132    endif
133    ilength_front = ilength_sign + 2
134 
135    if(present(IND)) then
136      ilength_ind = IND
137      call num2char(sexpornent,IND)
138      sexpornent(1:1) = "E"
139    else
140      ilength_ind = 2
141      sexpornent = "E2"
142    endif
143    ilength_exponent = 2 + ilength_ind
144 
145    ilength_decimal = ilength_schar - ilength_front - ilength_exponent
146    if(ilength_decimal < 0) stop 'ERROR in num2char: not enough length'
147    write(slength_decimal,'(i2)') ilength_decimal
148 
149    sformat = "(" // shead // 'es' // trim(adjustl(slength_schar)) // '.' &
150        // slength_decimal // sexpornent // ")"
151    ! es is format of y.yyyE+xx, |y.yyy| < 10.0
152 
153 
154    write(schar,sformat) dnum ! input data
155 
156    return
157  end subroutine dnum2char
158end module num2char_mod
to the top