fortran90においての乱数の発生方法です。
乱数を1から作るのは、乱数を研究するとかでない限り止めましょう。
ここではfortran90で乱数を発生させるためにどうすればいいか?を記述します。
私は乱数について詳しくありませんが、調べた限りメルセンヌツイスタという乱数発生法が良い乱数を発生するようです。
メルセンヌツイスタの詳細はMersenne Twister Home Page にあるのでご参照ください。
fortran90コードはhttps://gist.github.com/ykonishi/5569005にあります。
上記のリンクから、メルセンヌツイスタのfortran90コード(mt19937.f90)をダウンロードします。
そして、メインプログラム
program main
use mt19937
implicit none
integer::i
double precision::d
call sgrnd(2) ! seed
do i=1,10
write(6,*)grnd()
enddo
stop
end program main
をmt19937.f90と一緒に
gfortran -fno-range-check mt19937.f90 main2.f90
というオプションを付けてコンパイル(※1)します。実行すると、
$ ./a.out
6.4690865343436599E-002
0.40996560384519398
0.14283204451203346
0.74749888177029788
0.59957117028534412
0.53676494420506060
0.38914293772540987
0.94303490873426199
0.15882158395834267
0.15933134709484875
$
という結果を得ることが出来ます。
(※1)
このオプションが、型が扱える範囲を超えていても無視するためのオプションです。
何故かgfortranコンパイラ(ver4.8.4で確認)で出ます。
そして、これはコンパイラの問題だと私は結論付けました。
fortranの整数は
-2147483648から2147483647までで定義されているのにもかかわらず、
gfortran(ver4.8.4)は
-2147483647から2147483647の範囲でなければエラーを出すようです。
しかし、実際に整数に-2147483648を代入し、このエラーを無視する上のオプションを付けて実行すると、ちゃんと値が入ります。なので、コンパイラに問題があると判断しました。
[adsense1]
任意の関数に従う乱数(von Neumannの棄却法)
このページのサムネイル画像の右は等方的2次元調和振動子励起状態の密度
\(
\begin{align}
f(x,y)=\frac{4}{\pi}x^2y^2e^{-(x^2+y^2)}
\end{align}
\)
を点の濃さで表現するためのプログラムです。
これは、上の関数に従う乱数分布を書くことにほかなりません。
一様乱数を任意の関数に従う乱数に変換する方法として、
von Neumannの棄却法と呼ばれる方法があります。
以下のようにすると一様乱数を関数\(f(x)\)に従う乱数に変えることが出来ます。
\(x\)の範囲\([x_a,x_b]\)で定義された関数\(f(x)\)に従う乱数を発生させたい。
区間\([x_a,x_b]\)で関数は\([f_a,f_b]\)の値を持つとすると、
1. 範囲\([x_a,x_b]\)の一様乱数\(x_r\)を生成する。
2. 範囲\([f_a,f_b]\)の一様乱数\(f_r\)を生成する。
3. もしも\(f_r\le f(x_r)\)ならば、その\(x_r\)は\(f(x)\)に従う乱数である。そうでなければ、発生させた乱数は捨てる。
4. 1-3を繰り返す。
という方法です。アルゴリズムを見て分かる通り、この方法は無駄が多いです。
発生させた乱数を捨てる操作は出来ればしたくありません。
ですが、この方法は非常に簡単に実装できるのでとりあえず実装したいという場合には良い方法です。
以下は、上の考えの二次元版です。
program main
use mt19937
implicit none
integer::i,count,N
double precision::xp,yp,fp
double precision::a,b,fmin,fmax
double precision,external::f
call sgrnd(2)
N=2000
! fmin :: minimum value of function f(x,...)
! fmax :: maxmum value of function f(x,...)
fmin=0.d0
fmax=1.d0
! range of random number [a,b]
a=-5d0
b= 5d0
open(13,file="./density.d")
count=0
do while(count.lt.N)
xp=grnd()*(b-a)+a
yp=grnd()*(b-a)+a
fp=grnd()*(fmax-fmin)+fmin
if(fp.le.f(xp,yp))then
write(13,*)xp,yp
count=count+1
endif
enddo
close(13)
stop
end program main
function f(x,y)
implicit none
double precision::f
double precision,intent(in)::x,y
double precision::pi=acos(-1.d0)
f=abs(sqrt((1.d0/pi))*2d0*x*y*exp(-0.5d0*(x*x+y*y)))**2
return
end function f
カラーマップと3次元での表現はこうなります。
乱数を用いた密度表示(下のプログラム)ではこう表現されます。
[adsense2]
参考
任意分布の乱数生成 https://www.slideshare.net/shogoosawa581/ss-65259938
以降はfortranの標準組み込み関数を用いる場合のものです。
消すのは忍びないため、一応おいておきます。
乱数を発生させるための組み込み関数が既に組み込まれているはずなのでそれを使いましょう。
使うために3つの組み込み関数を使います。それぞれ、
system_clock
→ 毎回違う乱数を発生させるため、時間を利用する。時間取得のための組み込み関数
random_seed
→ 乱数発生のためのシード値を設定する。
random_number
→ 乱数をシード値に従って(倍精度ならば)0~1の範囲で発生させる
となっています。
実際のプログラム
gfortranでコンパイルを行いました。
プログラム本文にその設定を書いてもいいですが、簡単にするために外部サブルーチンを書きます。
下のサブルーチン ” pre_random ” を乱数を発生させる前に1度だけ宣言しておけば、簡単に毎回違った乱数を発生させることが可能になります。
program main
implicit none
integer::i
double precision::d1
call pre_random
do i=1,10
call random_number(d1)
write(6,*)d1
enddo
stop
end program main
subroutine pre_random
! sikinote
! Date : 2015/03/15
! : 2015/09/07
!
!How to use?
!===================
!call random_number(a)
!===================
! random_number produce
! value between 0~1
!
implicit none
integer::seedsize,c
integer,allocatable::seed(:)
!In fortran90, seed is array.
! To get seedsize, use below.
call random_seed(size=seedsize)
!Allocate seed array.
allocate(seed(1:seedsize))
!Get system time.
call system_clock(count=c)
!Substitute "seed" using system time.
seed=c
!Set "seed" to produce random number obey to system time.
call random_seed(put=seed)
return
end subroutine pre_random
実行結果は
$ gfortran main.f90
$ ./a.out
0.90865489451945525
0.22180454859685728
0.81469035800073897
7.7634880900457670E-002
0.90463054932998321
0.36944859829729970
0.19441279383012522
0.53540450509133775
0.36951941432124158
0.97393602500617960
となります。
2次元で乱数を作り出したい場合は
program main
implicit none
integer::i
double precision::d2
call pre_random
do i=1,800
call random_number(d1)
call random_number(d2)
write(11,*)d1,d2
enddo
stop
end program main
とすれば、fort.11にファイルが出力され、プロットすれば
という出力が得られます。
同じ乱数を再び発生させたいのであれば、値 “c” を記憶させておいて、
seed=c
call random_seed(put=seed)
を行えば同じ乱数を発生させることができます。
範囲を決めて乱数を振る
範囲を決めて乱数を発生させるためのプログラムを記述します。
module random1
! sikinote
! Date : 2015/03/17
! : 2015/09/07
implicit none
interface random_range
module procedure &
random_irange, &
random_drange
end interface random_range
contains
subroutine random_irange(iout,imin,imax)
integer,intent(in)::imin,imax
integer,intent(out)::iout
double precision::d
call random_number(d)
d=d*dble(imax-imin+1)+dble(imin)-0.5d0
iout=floor(d)
if(d-dble(iout).ge.0.5d0)then
iout=iout+1
endif
end subroutine random_irange
subroutine random_drange(dout,dmin,dmax)
double precision,intent(in)::dmin,dmax
double precision,intent(out)::dout
double precision::d
call random_number(d)
dout=d*(dmax-dmin)+dmin
end subroutine random_drange
end module random1
をモジュールとして記述します。
サブルーチンとして書いてもいいですが、引数の型によって使うサブルーチンを変える、に挑戦しました。
総称名random_rangeを使ってください。整数の乱数と倍精度の乱数を発生させることができます。
実際に使うには、上のモジュールと上で記述したサブルーチン”pre_random”と一緒に
program main
use random1
implicit none
integer::i,j
double precision::d1
call pre_random
! random_range(output,min,max)
! which produce random_number between min ~ max
do i=1,10
call random_range(j,-4,10)
write(6,*)j
enddo
do i=1,10
call random_range(d1,3d0,7d0)
write(6,*)d1
enddo
stop
end program main
と記述してください。実行すると
$ ./a.out
3
-2
8
7
-1
8
5
10
0
-4
3.1659471252818343
4.2091704097365259
6.0449715412284366
6.4957628178220883
6.1839820024342194
4.7451092204337879
6.5345995439361548
5.4452291971734148
5.4086079853034281
3.7174563929720903
となります。
密度表示のプログラム
このページのサムネイル画像の右は等方的2次元調和振動子励起状態の密度
\(
\begin{align}
|\psi(x,y)^2|&=\frac{4}{\pi}x^2y^2e^{-(x^2+y^2)} \\
(n_x,n_y)&=(1,1)
\end{align}
\)
を密度の濃さで出力するためのプログラムです。
カラーマップと3次元での表現はこうなります。
乱数を用いた密度表示(下のプログラム)ではこう表現されます。
program main
use random1
implicit none
integer::i,c,count,N
double precision::d
double precision::xp,yp,fp
double precision::xmax,xmin,ymax,ymin,fmin,fmax
complex(kind(0d0))::f
external::f
call pre_random
open(13,file="./rd.2")
N=2000
! fmin :: minimum value of function f(x,...)
! fmax :: maxmum value of function f(x,...)
fmin=0.d0
fmax=1.d0
count=0
do while(count.lt.N)
call random_range(xp,-5d0,5d0)
call random_range(yp,-5d0,5d0)
call random_range(fp,-5d0,5d0)
call random_number(d)
if(d*abs(fp)**2.le.abs(f(xp,yp))**2)then
write(13,*)xp,yp
count=count+1
endif
enddo
close(13)
stop
end program main
function f(x,y)
implicit none
complex(kind(0d0))::f
double precision::y,x
double precision::pi=acos(-1.d0)
f=cmplx((1.d0/pi)**0.5d0*2.d0*x*y*exp(-x*x*0.5d0-y*y*0.5d0),0.d0)
return
end function f
参考
fortranでの擬似乱数生成
11 乱数発生の組込み手続random_number
ここより以下は以前のものです。
intel®fortran コンパイラなら問題なく動くのかな、と思います。gfortranコンパイラではエラーが出てしまいました。
詳しい理由は分かりません。一応おいておきます。
使用している組み込み関数の意味はそれぞれ、
system_clock
→ 時間を取得する。(”c” に代入される)
random_seed
→ 値”c” をシード値として使うための操作
random_number
→ 乱数をシード値に従って0~1の範囲で発生させる
となっています。
program main
implicit none
integer::i,c
double precision::d1
call system_clock(count=c)
call random_seed(put=(/c/))
do i=1,10
!random_number produce
! value between 0~1
call random_number(d1)
write(6,*)d1
enddo
stop
end program main