HOME > natural science Laboratory > コンピュータ・シミュレーション講座 > TIPS 集

モンテカルロ法を試す―円周率の評価―

文責:遠藤 理平 (2009年2月21日) カテゴリ:TIPS 集(103)

確率法則を用いて問題を解くモンテカルロ法(Monte Carlo meyhod)では、質のよい乱数が必要である。 C言語で用意されている関数randと、高品質で高速に乱数を生成できるとして知られるメルセンヌ・ツイスタを利用して、円周率を評価してみる。 また、その際のアルゴリズムとして「あたりはずれ法」と「粗いモンテカルロ法」を用いて計算する。

円周率の評価

図1のような単位円の第1象限領域の面積をモンテカルロ法にて求め、 解析的に求められる面積$\pi/4$と比較することによって、円周率の評価を行うことにする。


図1.単位円の第1象限領域

あたりはずれ法

あたりはずれ法とは、面積を評価したい領域$S_A$を面積が既知の領域$S$で囲み、 領域$S$上に分布が一様になるように$N$個の点を降らせる。 領域$S_A$上に落ちた点の数が$N_A$であれば、$S_A$の面積は、

で評価することができる。
図1の第1象限領域$S_A$の面積は、既知の領域$S$を正方形OABCとすると面積が1なので都合よく$N_A/N$で求まる。
つまり、区間$[0,1]$の一様乱数のペア$(\xi,\eta)$を$N$個用意したとき、$S_A$の内部、つまり$\xi^2+\eta^2<1$を満たす個数を$N_A$とすることで、 $\pi/4=N_A/N$より$\pi$を評価することができる。

粗いモンテカルロ法

粗いモンテカルロ法は、平均値の定理

を利用する。これは「$f$の区間$[a,b]$の積分値は、$f$の区間$[a,b]$の平均値$\left\langle f \right\rangle$に幅$b-a$を掛けたものに等しい」ことを意味している。 $\left\langle f \right\rangle$を、区間$[a,b]$上に分布が一様になるように$N$個の点を降らせ、その平均を取ることで評価する。

但し、$\xi$は乱数。
図1の場合、

区間は$[0,1]$である。乱数の組$\{x_i\} (i=1,2,\cdots ,N)$から$\left\langle f \right\rangle$を見積もり、面積$S_A$を求め、 $\pi/4=S_A$より$\pi$を評価することができる。

Visual C++ 2008で数値計算

円周率を2つのアルゴリズム「あたりはずれ法」と「粗いモンテカルロ法」の場合において、
2つの擬似乱数生成器、「C言語の関数rand」と「メルセンヌ・ツイスタ(MT)」を利用して、それぞれの計算結果を比較してみる。


図2.計算結果

図2は、$N$の値を$10^1~10^9$まで、それぞれの$N$に対して100回計算しその平均から見積もった円周率$\pi^{*}$と、 有効桁数9の円周率の真の値$\pi$(=3.14159265)とのずれをプロットした。 図2の結果から、C言語のrandを利用した場合、Nが$10^7$より大きな値でも精度が上がっていないことが分かる。 これは、C言語のrandで生成される乱数の列に何らかの偏りがあるためであろう。

今回は、「あたりはずれ法」とそれよりも計算精度の良い「粗いモンテカルロ法」の計算結果を比較すると、 $N$の小さなところでは後者のほうが一桁精度がよいが、$N$の大きなところでは、ほとんど差がない結果となった。

プログラム

今回の数値計算で使用したメルセンヌ・ツイスタは、
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/mt19937ar.html
にある「mt19937ar.c」のメイン関数を削除したファイルを「mt19937ar.h」とし、利用させていただいた。

あたりはずれ法による円周率の計算

/*
当たり外れ法による円周率の計算
*/
#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <fstream>
#include <iostream>
#include <string> 
#include "mt19937ar.h"  //メルセンヌ・ツイスタ
using namespace std;

double PI = 3.14159265358979323846;

// 関数のプロトタイプ宣言
double f( double x);
//ファイル出力
ofstream ofs1( "a_MT.data" );   //メルセンヌ・ツイスタ利用の場合
ofstream ofs2( "a_rand.data" ); //c言語rand利用の場合

int main(){
	srand((unsigned)time(NULL));
	init_genrand((unsigned)time(NULL));

	int N_max = 1000000000;
	int ave = 100;
	int N_A = 0;
	int N_A2 = 0;
	int step = 1;
	double N_sum[10] = {0.0};
	double N_sum2[10] = {0.0};

	for(int j=1; j<=ave; j++){
		step = 1;
		N_A = 0;
		N_A2 = 0;
		for(int i=1; i<=N_max; i++){
			double zi = genrand_real1();
			double eta = genrand_real1();
			if(f(zi) > eta ) N_A++;

			zi = (double)rand()/(double)RAND_MAX;
			eta = (double)rand()/(double)RAND_MAX;
			if(f(zi) > eta ) N_A2++;

			if(i==10 || i==100 || i==1000 || i==10000 || i==100000 || i==1000000 || i==10000000 || i==100000000 || i==1000000000){
				N_sum[step] += double(N_A)/double(i) *4.0;
				N_sum2[step] += double(N_A2)/double(i) *4.0;
				step++;
			}
		}
		cout << j <<endl;
	}

	for(int j=1; j<=9; j++){
		ofs1 << pow(10.0,j) << " " << N_sum[j]/double(ave) << " " << fabs( N_sum[j]/double(ave) - PI) << endl;
		ofs2 << pow(10.0,j) << " " << N_sum2[j]/double(ave) << " " <<  fabs( N_sum2[j]/double(ave) - PI) << endl;
	}


}
double f(double x){
	return  sqrt( 1.0 - pow(x ,2));
}

粗いモンテカルロ法による円周率の計算

;
/*
粗いモンテカルロ法を用いた求積
*/
#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <fstream>
#include <iostream>
#include <string> 
#include "mt19937ar.h"
using namespace std;

double PI = 3.14159265358979323846;

// 関数のプロトタイプ宣言
double f( double x);
//ファイル出力
ofstream ofs1( "result.data" );
ofstream ofs2( "result2.data" );

int main(){
	srand((unsigned)time(NULL));
	init_genrand((unsigned)time(NULL));

	int N_max = 1000000000;
	int ave = 100;
	int N_A = 0;
	int N_A2 = 0;
	int step = 1;
	double N_sum[10] = {0.0};
	double N_sum2[10] = {0.0};
	double F_sum = 0.0;
	double F_sum2 = 0.0;

	for(int j=1; j<=ave; j++){
		step = 1;
		N_A = 0;
		N_A2 = 0;
		F_sum = 0.0;
		F_sum2 = 0.0;

		for(int i=1; i<=N_max; i++){
			F_sum += f(genrand_real1());
			F_sum2 += f((double)rand()/(double)RAND_MAX);

			if(i==10 || i==100 || i==1000 || i==10000 || i==100000 || i==1000000 || i==10000000 || i==100000000 || i==1000000000){
				N_sum[step] += F_sum/double(i) *4.0;
				N_sum2[step] += F_sum2/double(i) *4.0;
				step++;
			}
		}
		cout << j <<endl;
	}

	for(int j=1; j<=9; j++){
		ofs1 << pow(10.0,j) << " " << N_sum[j]/double(ave) << " " << fabs( N_sum[j]/double(ave) - PI) << endl;
		ofs2 << pow(10.0,j) << " " << N_sum2[j]/double(ave) << " " <<  fabs( N_sum2[j]/double(ave) - PI) << endl;
	}


}
double f(double x){
	return  sqrt( 1.0 - pow(x ,2));
}

今後の予定

モンテカルロ法を用いて、熱力学量を計算してみる



▲このページのトップNPO法人 natural science トップ

▲このページのトップNPO法人 natural science トップ