はじめに
科学技術計算の主な目的は、現象を記述する支配方程式を解くことですが、実用的な問題の多くが簡単には解けません。そのため、乱数による確率変数のサンプリングを利用して、問題を数値的に解く(必要な精度で解を得る)ことがあります。材料シミュレーションでは、分子動力学計算において有限温度の乱雑さを取り入れるため(等)に乱数を利用します。
乱数といえども、規則性がある方が好ましい場面があります。例えば、プログラムのデバッグ時には、同じ系列の乱数を繰り返し発生して欲しいです。
また、大規模なシミュレーションでは MPI 並列を利用します。MPI の各プロセスは協調して動作することが求められますので、処理内容によっては、全プロセスが同一の乱数を使う必要があります。それを実現する代表的な方法は以下の二つでしょう。
- あるプロセスが代表して乱数を発生させ、その結果を他プロセスに転送する
- 全プロセス同条件で乱数を初期化した後に、各プロセスが乱数を発生させる
プログラミングが容易なのは後者(乱数の初期化)ですが、Intel の Fortran は、明示的に初期化せずとも、毎回同じ系列の乱数を生成します。この仕組みを(意図していなくても)利用しているプログラムを、GNU 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
の結果も全く同じでした、
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
初期化しなければ、毎回異なる乱数を発生します。
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
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
初期化により、同一系列の乱数を繰り返し発生させることができます。
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
富士通コンパイラ
富岳で分子動力学計算をしています。毎回同じ乱数を発生します。
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 と大きいです。
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 規格は、乱数が毎回同じ/異なる、どちらの動作が正しいかを定めていない、ように思います。
どの計算機でも動作するプログラムを書きたければ、同系列の乱数が必要な場面では、明示的に初期化しましょう。
参考にしました