- 重積分におけるモンテカルロ法の有効性 -

yosirin-hello / yosi-s@galaxy.ocn.ne.jp

1. はじめに

 私がお世話になっているプログラマの隠れ里のけんいちさんからモンテカルロ法でπの値の収束について実験してみれば面白いかもしれないと言われたので数値計算の本を読んでみたら, モンテカルロ法は重積分に非常に有効だということがわかりました. それでモンテカルロ法に関心を持つようになり, 研究をすることにしました.
 前回の記事ではSSEで数値積分の高速化をしましたが, 今回はモンテカルロ法でアルゴリズムの面から数値積分の高速化をしてみます.
2. モンテカルロ法とは

 モンテカルロ法とは, 乱数などの偶発的な確率変数を用いて試行錯誤的に問題を解いていく数値計算法のことです.[1.] モンテカルロ法にもいくつかの手法があってhit-or-miss法(Rejection Method)(Fig1.), 単純モンテカルロ法(Basic MC)(Fig2.), 層別サンプリング法(Stratified sampling)([2.]を参照)のようなものがあります.





 モンテカルロ法の面白い所は, 解析学と全く方向性が違う分野である統計学の考え方で, 面積や体積の問題から最先端の科学の問題までも解決できる所だと思います. モンテカルロ法の長所は何かと言うと, 問題の次元数Nに関係なく計算量はいつもサンプリング数に比例するということです. しかし, モンテカルロ法は短所があって, 乱数を使うので精度を高くするにはサンプリング数をかなり増やさなければならないということです. でも, 長所である計算量のオーダーがO(n)になることと比べれば短所はそれほど問題ではないと言えます.
 もし, N重積分を真面目に数値積分で解こうとすると計算量のオーダーはO(n^N)なりますが, モンテカルロ法では例えNがどんなに大きくてもO(n)となり有限時間内に解を求めることが可能になるのです.
3. 問題の設定

 今回はFig1.のようなカマボコ形の物体の体積をモンテカルロ法で求めて, 収束のしかたを実験してみます. またモンテカルロ法との比較するために数値積分でも体積を求めてみます.



この物体の体積は解析的に求めることは簡単で, 計算は以下のようになります.

4.任意の区間の乱数(倍精度浮動小数)を生成する関数

 stdlib.hに定義されているrand()は0からRAND_MAXまでの整数を返してくれます. 今回は任意の区間の倍精度浮動小数の乱数を返す関数がほしいので, この関数をラッピングして作ります.
 
List1. i0からi1までの倍精度浮動小数を返す関数
#include <stdlib.h>

double Rand_0to1()
{
        return ( (double)rand() / (double)RAND_MAX );
}

double Rand_i0toi1( double i0, double i1 )
{
        double rand_0to1;

        rand_0to1 = (double)rand() / (double)RAND_MAX;

        return i0 + ( i1 - i0 ) * rand_0to1;
}

Rand_i0toi1( )のi0, i1は i0<i1の条件を満たしているものとします.
5. Rand_i0toi1関数が生成する乱数の特性



Rand_i0toi1関数の特性を調べてみるとGraph1. のような特性を持っていることがわかりました.
80から100の間の出現回数が多いのが気になりますが, 期待値よりもかけ離れているわけではないので合格レベルだと判断しました.
もし, もっと精度の高い一様乱数を利用したいのなら, Mersenne Twisterという有名な疑似乱数生成アルゴリズムを使ってみるのもよいかもしれません.
6. Cによる実装

List2. カマボコ形の物体の体積をhit-or-miss法, Basic MC法で求めるプログラム
typedef struct {
        double x0, x1;
        double y0, y1;
        double z0, z1;
} INTERVAL;

double KamabokoFunc( double x, double y )
{
        return sin(x);
}

double CalcVolume_MC_HitOrMiss( INTERVAL I, int sample )
{
        double x, y, z, V0, V;
        int in = 0;

        // 直積空間[I.x0, I.x1] x [I.y0, Iy1] x [I.z0, I.z1]の体積
        V0 = ( I.x1 - I.x0 ) * ( I.y1 - I.y0 ) * ( I.z1 - I.z0 );

        for( int i = 0; i < sample; i++ ) {
                x = Rand_i0toi1( I.x0, I.x1 );
                y = Rand_i0toi1( I.y0, I.y1 ); // (これは不要)
                z = Rand_i0toi1( I.z0, I.z1 );
                if( z <= KamabokoFunc(x, y) )
                        in++;
        }

        // [I.x0, I.x1] x [I.y0, Iy1]の領域のf1の体積
        V = V0 * (double) in / (double) sample;

        return V;
}

double CalcVolume_BasicMC( INTERVAL I, int sample )
{
        double x, y, BaseArea, V = 0;

        BaseArea = ( I.x1 - I.x0 ) * ( I.y1 - I.y0 ) / (double)sample;

        for( int i = 0; i < sample; i++ ) {
                x = Rand_i0toi1( I.x0, I.x1 );
                y = Rand_i0toi1( I.y0, I.y1 );
                V += KamabokoFunc( x, y );
        }

        V *= BaseArea;

        return V;
}
List3. カマボコ形の物体の体積を中点公式の数値積分で求めるプログラム
double CalcVolume_Midpoint( INTERVAL* pI, int div_count )
{
        double width_x, width_y, x = 0, y = 0, sum_x, sum_y;

        width_x = ( pI->x1 - pI->x0 ) / ( double )div_count;
        width_y = ( pI->y1 - pI->y0 ) / ( double )div_count;

        sum_x = 0;
        sum_y = 0;

        for( int j = 0; j < div_count; j++ ) {
                for( int i = 0; i < div_count; i++ ) {
                        x = pI->x0 + ( i + 0.5 ) * width_x;
                        sum_x += fabs( KamabokoFunc( x, y ) );
                }
                sum_x *= width_x;
                sum_y += sum_x;
                sum_x = 0;
                y = pI->y0 + ( j + 0.5 ) * width_y;
        }

        sum_y *= width_y;

        return sum_y;
}
7. 実験結果



Graph2. を見るとモンテカルロ法は非常にNoisyだということがわかります. 乱数で体積を試行錯誤的に求めているのにもかかわらず, 体積が4に近い結果がでました.




Graph2. とは対照的にGraph3. は振動もせずに一気に収束していることがわかります.
グラフのサンプル数をCalcVolume_Midpoint( )のdiv_countのパラメータに入れました.




Graph4. を見るとモンテカルロ法のサイクル数はサンプル数を増やしていってもそれほどサイクル数は増加していないことがわかります. それに対して, 数値積分の方はかなりの勢いでサイクル数が増加していくことがわかります. Basic MC法のプログラムは, Hit or missと違ってプログラム中に分岐命令がないぶんhit or miss法と比べると若干サイクル数が少なくなっていることがわかります.
このグラフから読み取りにくいですが, モンテカルロ法の計算量のオーダーはO(n)で, 数値計算はO(n^2)になっています.




Graph5. はサンプル数をもっと増やして誤差の収束の仕方を計測した結果です. グラフの負の方がなくなっているのは, 絶対値をとっているからです.
Basic MC法の方がhit or miss法と比べると, 若干誤差が少ないことがわかりました. モンテカルロ法の誤差の収束の仕方はO( 1/sqrt(sampling) )であることが知られていますが, 実験の結果からもそのことがわかります.
8. 結論

実験や文献から以下のことがわかりました.

・できるだけ早く大まかな積分値が知りたい場合にはモンテカルロ法が有利
・陰関数(F(x,y)=0の形)の積分はhit-or-miss法で実装した方がプログラムがシンプルにできる
・積分する領域が長方形でないとき, 中点公式やシンプソンの公式を使う数値積分の実装が複雑になってしまう
・積分する関数が陽関数(z=f(x,y)の形)の場合, Basic MC法は分岐命令が使う必要がなく, また, hit-or-miss法よりも誤差も少ないのでBasic MC法で実装する方がよい

応用できそうなこと
・例えば, 物理学で出てくるエネルギーは, 体積積分の計算する必要があるのでモンテカルロ法が活躍するかもしれない
・ラジオシティのフォームファクタの計算
9. 最後に

 モンテカルロ法が出す結果はNoisyですが, アルゴリズムは単純で, アプローチの仕方が面白いと思います. 最先端のシミュレーションの分野ではモンテカルロ法が活躍しているので, 興味のある方は調べてみると何か発見できるかもしれません.
10. 参考文献

1. 情報工学入門シリーズ5 数値計算法 第2版, 森北出版株式会社
2. プログラミング入門シリーズ7 コンピュータによる数値計算, 朝倉書店
3. Microsoft MSDN Library
11. 改訂履歴

改訂 履歴 日付
1.0 "9. 最後に"を追加して公開 03/03/2002


Copyright © 2002 yosirin-hello All rights reserved.