Fortran 組込み乱数の初期化

# はじめに

科学技術計算の主な目的は、現象を記述する支配方程式を解くことですが、実用的な問題の多くが簡単には解けません。そのため、乱数による確率変数のサンプリングを利用して、問題を数値的に解く（必要な精度で解を得る）ことがあります。材料シミュレーションでは、分子動力学計算において有限温度の乱雑さを取り入れるため（等）に乱数を利用します。

乱数といえども、規則性がある方が好ましい場面があります。例えば、プログラムのデバッグ時には、同じ系列の乱数を繰り返し発生して欲しいです。

また、大規模なシミュレーションでは MPI 並列を利用します。MPI の各プロセスは協調して動作することが求められますので、処理内容によっては、全プロセスが同一の乱数を使う必要があります。それを実現する代表的な方法は以下の二つでしょう。

- あるプロセスが代表して乱数を発生させ、その結果を他プロセスに転送する
- 全プロセス同条件で乱数を初期化した後に、各プロセスが乱数を発生させる

プログラミングが容易なのは後者（乱数の初期化）ですが、Intel の Fortran は、明示的に初期化せずとも、毎回同じ系列の乱数を生成します。この仕組みを（意図していなくても）利用しているプログラムを、GNU Fortran でコンパイルすると正しく動かない場合があります。

本稿における「乱数」は、すべて疑似乱数を指します。

# 動作確認プログラム (Fortran)

```Fortran
program randomseed
  implicit none
  real :: rnd(10)
  integer, allocatable :: seeds(:), init(:)
  integer :: i, seedsize

  call random_seed(size=seedsize)  ! 乱数の種のサイズ（配列）
  allocate(seeds(seedsize), init(seedsize))

  read *  ! 待ち

  call random_seed(get=seeds)  ! 乱数初期化しない
  init = seeds  ! 最初の種を保存
  print *, "SEEDs", seeds  ! 最初の種を表示（配列）
  call random_number(rnd)
  print *, "RND  1-10", rnd  ! 乱数最初の10個

  read *  ! 待ち

  call random_seed(get=seeds)
  print *, "SEEDs", seeds  ! 更新された種を表示
  call random_number(rnd)
  print *, "RND 11-20", rnd  ! 乱数次の10個

  read *  ! 待ち

  print *, "re-initialize"
  call random_seed(put=init)  ! 保存しておいた種で（再）初期化
  call random_number(rnd)
  print *, "RND again", rnd  ! 最初と同じ乱数
end program randomseed
```

`read *` は、リターンキーが入力されるまで待ちます。動作のタイミング（経過時間）が乱数に影響を及ぼすか？と考えて入れたのですが、そのような影響は確認できませんでした。

# 結果

試したのは、以下のコンパイラです。

- インテルのコンパイラ（ifort, ifx）
  - ifx (IFX) 2025.0.4 20241205
  - ifort (IFORT) 18.0.5 20180823
- GNU Fortran (gfortran)
  - GNU Fortran (Homebrew GCC 14.2.0_1) 14.2.0
  - GNU Fortran (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
- 富士通コンパイラ
  - frtpx (FRT) 4.11.2 20240524 （富岳）
- NVIDIA Compilers and Tools
  - nvfortran 21.9-0 linuxarm64 target on aarch64 Linux （古い）
## Intel Fortran

初期化処理なしで、毎回同じ乱数を発生します。何度実行しても結果は同じです。`ifx 2025`と`ifort 2018`の結果も全く同じでした、

```c

 SEEDs  2147483562  2147483398
 RND  1-10  3.9208680E-07  2.5480442E-02  0.3525161      0.6669145
  0.9630555      0.8382882      0.3353550      0.9153272      0.7958636
  0.8326931

 SEEDs   451678520   810967243
 RND 11-20  0.3450427      0.8711839      8.9918353E-02  0.8882838
  0.7009789      0.7345526      0.3001758      4.9717721E-02  0.9081894
  9.7658597E-02

 re-initialize
 RND again  3.9208680E-07  2.5480442E-02  0.3525161      0.6669145
  0.9630555      0.8382882      0.3353550      0.9153272      0.7958636
  0.8326931
```

（再）初期化すると、最初と同じ乱数を発生していることが確認できます。

## GNU Fortran

初期化しなければ、毎回異なる乱数を発生します。

```c
 SEEDs  -648140346  -235008366  1665269076 -1044441948  -584360807 -1135136558  -688499446  -649136658
 RND  1-10  0.112853527      0.832141459      0.787938297      0.196493506      0.497647822      0.306901217      0.213199854       7.94767737E-02  0.144986868      0.273290634
```

```c
 SEEDs  1732831996  1112263554 -1092304302   538342802  -544739935  -255159788  1610660824  1157602506
 RND  1-10  0.850945354      0.203200221      0.417815685       7.41949081E-02  0.680155277      0.703589380      0.197036982      0.369669735      0.582059264      0.101300240
```

初期化により、同一系列の乱数を繰り返し発生させることができます。

```c
 re-initialize
 RND again  0.850945354      0.203200221      0.417815685       7.41949081E-02  0.680155277      0.703589380      0.197036982      0.369669735      0.582059264      0.101300240
```

## 富士通コンパイラ

富岳で分子動力学計算をしています。毎回同じ乱数を発生します。

```c
 SEEDs 1234567891 234567891
 RND  1-10 0.675249577 0.940953672 0.304833174 0.270227402 0.766902566 0.247423500 0.762805939 0.143197224 0.912934959 7.81079009E-02
 ```

`SEEDs`の数字の並びが規則的でした。

## NVIDIA Compilers and Tools

毎回同じ乱数を発生します。種の配列サイズが 34 と大きいです。

```c
 SEEDs      3935565      2556217      7161241       619582      2329578
       431781      3632682      6355368      3900995      6516640      3315613
      6114515      5730631      3339426      5857968       770915      4542500
      4366800      7610154      5459821      7062514       177168      6052756
       157396      2591103      5059992      1275832       980058       486656
       131642      7330869       356226      2040732       171719
 RND  1-10   0.9079230       0.1906921       6.7165293E-02   0.8000845
   0.7973146       0.6368300       0.5887827       0.1590656
   0.3206477       0.4481761
```

# まとめ

試した範囲では、実行ごとに異なる乱数を発生するのは GNU Fortran のみでした。Fortran 規格は、乱数が毎回同じ／異なる、どちらの動作が正しいかを定めていない、ように思います。
どの計算機でも動作するプログラムを書きたければ、同系列の乱数が必要な場面では、明示的に初期化しましょう。

# 参考にしました

https://nag-j.co.jp/fortran/tips/tips_RandomNumberInFortran.html