定番コードいろいろ
能書き
基本さえ押さえておけば、あとは知恵と工夫で何でもできる!・・・と言っても、全てを自分で一から考えるのは不毛です。
定番のやり方はいくつか覚えておきましょう。
能書き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 functionrecursiveは副プログラムを再帰的に呼び出す時に使う属性です。が、使う場面は非常に限られるので「そんなこともできるのか」くらいで大丈夫です。
下手に使うと無限ループの原因になる上、スピードも遅くなる。
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のサイトを見ると良い。