定番コードいろいろ
能書き
基本さえ押さえておけば、あとは知恵と工夫で何でもできる!・・・と言っても、全てを自分で一から考えるのは不毛です。
定番のやり方はいくつか覚えておきましょう。
能書き2
C/C++やPythonのサンプルコードは検索すればいくらでも出てくるのですが、Fortranで書かれたものはなかなか見つからないですね。著者はFortran使いの皆さんを応援しています。
変数の入れ替え(swap)
swap
2つの変数『a』と『b』の値を入れ替えるtemp = a a = b b = temp1. 『a』の値を一時変数『temp』に待避させる
2. 『b』の値を『a』に代入 3. 『temp』の値(元の『a』の値)を『b』に代入 という手順。
一見無駄なことをやっているようだが、この手順を踏まないと値が上書きされて消えてしまう。
swapサブルーチン
このコードは既出ですが、大事な話なので2回目です。値を入れ替えるだけの単純作業ですが、コードにすると3行になる上、一時変数を定義する必要があります。
多用するのであれば、サブルーチンにしてしまうと良いでしょう。
subroutine swap(a,b)
real(kind(0d0)), intent(inout) :: a, b
real(kind(0d0)) :: temp
temp = a
a = b
b = temp
end subroutine
入れ替える変数の型が違ったり、ベクトルだったりする場合は適宜定義を変更して下さい。
2次元以上の巨大な配列のswapは、作業領域が大きくなるので避けましょう。アルゴリズム自体を見直すか、一部分ずつswapしましょう。
swapモジュール
複数のプログラムで使うなら、モジュールにしてしまうと楽です。swaps.f90
ここでは、総称名とelemental属性を使ってどんな型の変数でも『swap』で入れ替えられるようにしています。
セミコロン『;』で複数の行を一行にまとめて書くのは、コードが長くなるのが嫌な著者の癖です。他人に見せるなら、セミコロンは使わない方が読みやすいと思います。
並べ替え(sort)
バブルソート
一次元配列(要素数nのベクトル)の中身を小さい順に並べ替える do i=1,n-1
do j=2,n-i+1
if( vec(j-1) > vec(j) ) then
temp = vec(j-1)
vec(j-1) = vec(j)
vec(j) = temp
end if
end do
end do
最も簡単で基本的なソートアルゴリズム。隣同士を比較して入れ替える、を繰り返す。Wikipediaのアニメーションを見るとイメージが掴みやすい。
2重ループなので、計算量はO(n2)。
nが大きい場合は他のアルゴリズムも検討すると良い。
バブルソート:サブルーチン
こういう定型コードは、変数名の干渉を避けるためにもサブルーチンにしてしまいましょう。この例では上のswapモジュールを使っています。
subroutine bubbleSort(vec)
real(kind(0d0)), intent(inout) :: vec(:)
integer :: i,j,n
n = size(vec)
do i=1,n-1
do j=2,n-i+1
if( vec(j-1) > vec(j) ) then
call swap(vec(j-1),vec(j))
end if
end do
end do
end subroutine
簡単なので、自分で作ってみても練習になります。
クイックソート:サブルーチン
計算量が平均O(n log n)となる、一般に速いとされているソートアルゴリズム。このレベルになると、自分で考えて自作する意味はあまり無い。
recursive subroutine quickSort_d(a,left,right)
real(kind(0d0)), intent(inout) :: a(:)
integer, intent(in), optional :: left, right
real(kind(0d0)) :: pivot
integer :: i, j, imin, jmax
if(present(left)) then; imin = left
else; imin = lbound(a,1); end if
if(present(right)) then; jmax = right
else; jmax = ubound(a,1); end if
if (imin < jmax) then
i = imin; j = jmax
pivot = this__median3_d(a(i), a(i+(j-i)/2), a(j))
do
do while( a(i) < pivot )
i = i + 1
end do
do while( a(j) > pivot )
j = j - 1
end do
if ( i >= j ) EXIT
call swap(a(i),a(j))
i = i + 1
j = j - 1
end do
call quickSort_d(a(:),imin,i-1) !-- sort smaller part
call quickSort_d(a(:),j+1,jmax) !-- sort larger part
end if
end subroutine
function this__median3_d(a,b,c) result(med)
real(kind(0d0)), intent(in) :: a,b,c
real(kind(0d0)) :: med
if(a < b) then
if(c < a) then
med = a; RETURN
else if(c < b) then
med = b; RETURN
end if
else if(a < c) then
med = a; RETURN
else if(c < b) then
med = b; RETURN
end if
med = c
end function
recursiveは副プログラムを再帰的に呼び出す時に使う属性です。が、使う場面は非常に限られるので「そんなこともできるのか」くらいで大丈夫です。
下手に使うと無限ループの原因になる上、スピードも遅くなる。
sortモジュール
これもモジュールにしておきます。sort.f90
ファイルを開くと"USAGE"のところに使い方のサンプルが書いてあるので参考にして下さい。
小林ルールで、ファイル名とモジュール名は共通で"sort"、かつサブルーチンは"sort__"で始まる名前にしてあります。ルーチンの所在(持ち主)を明確にする工夫です。
球面上の一様乱数
ランダムな向きの3次元ベクトルを作る
単位球面上に一様に分布する乱数(ベクトル)を生成する。単純に極座標表示でθとφを一様乱数にしても、球面上に一様分布しないので注意。
!----------------------------------
! Generate a random vector on the unit sphere
! Marsaglia algorithm (1972)
!----------------------------------
subroutine rand_sphere(vec)
real(kind(0d0)), intent(out) :: vec(3)
real(kind(0d0)) :: r2, r, xx, yy
r2 = 2.0d0
do while(r2 > 1.0d0)
xx = 2.0d0*ran__drnd() - 1.0d0
yy = 2.0d0*ran__drnd() - 1.0d0
r2 = xx**2 + yy**2
end do
r = sqrt(1.0d0 - r2)
vec(1) = 2.0d0 * xx * r
vec(2) = 2.0d0 * yy * r
vec(3) = 1.0d0 - 2.0d0 * r2
end subroutine
(『ran__drnd()』は一様乱数。これはranpackを使用)単位球面上に一様に分布させる方法は色々提案されているが、実用上はこのアルゴリズムが速い。
他のアルゴリズムや注意点を知りたい人は、 Wolframのサイトを見ると良い。