GSLで数値積分
GSLでは, いろいろな数値積分スキームが準備されています.
Gauss-Legendre積分
基本中の基本, ガウス求積法である. 古いものが好きな人が使うらしい.
Gauss-Kronrod積分
ガウス求積法で, 区分点を入れ子型に選ぶ工夫を行うと, 関数の評価点数を小さくできます. これがGauss-Kronrod積分だそうです.
QNG 基本の積分法
QAG 誤差評価を行って,関数の変化に追従する適応型積分.
QAGS QAGにおいて特異点の自動検出を試みるQAG
QAGP 特異点リストを与えるQAG
QAGI 変数変換 x=(1-t)/t などを用いて無限積分, 半無限積分を行うQAG
QAWC コーシーの主値積分を行うQAG
QAWS 端点においてべきおよび対数形特異点を持つ関数のQAG
QAWO 周期関数cos, sinを含む関数のQAG
QAWF フーリエ変換型の積分に対するQAG
二重適応型積分
CQUAD なんだか知らんが, 精度がいいらしい.
Romberg積分
- なんだかよくわからんが, 積分するらしい
なぜか, スーパー性能という京都大学数理研の二重指数型積分は無いんですね・・・あ?変数変換したら,台形則が超高性能になるという理論だから,ライブラリーで自動積分とかいう話ではないんですね.これは・・・お?お弟子さんのWebページになんか寂れているので, そいつも合わせて説明しよう.
京風数値積分
添付のIntDEクラスにより, 二重指数型積分が可能です. GSLとは関係がありません.
#include "intde.hpp" struct my_type { double K=1.0; } myParameter; IntDE IDE; IDE.Parameter=&myParameter; //パラメータのポインタを指定してもよい IDE.Initialize(1e-15);//ご希望の精度で初期化 double xmin=0.0,xmax=0.5, err; double v=IDE.Integrate([](double x, void *p){ my_type& P=(my_type *)p;//パラメータのポインタを指定したなら,復元できる return P.K/std::sqrt(x*(1.-x)); }, xmin, xmax, err)*2;
格別説明は不要だと思います. 上の例では被積分関数をラムダ式で書いていますが, もちろん, グローバルな関数の関数ポインタでもOKです. 関数の他にもいくつかあって,
- class IntDEI(err_rel)
- Integrate([](double x){...},double a, double & err) [a,∞)半無限積分を行う
- class IntDEO(err_rel)
- Integrate([](double x){...},double a, double omega, double & err) f(x)は振動項を含み,xの大きいところで漸近的に(単調な関数)* sin(omega*x+theta)となっている
格子点(デフォルト8000)が不足する場合, コンストラクタでIntDE(n) と指定して増やせば良い.
errは推定絶対誤差で収束に失敗する(長さnの表で許容誤差に 達しない)と負で返す. このときの原因と対策は
- f(x)に積分端点以外で微分不可能や不連続な点,あるいは それに近い点(鋭いピークなど)がある. この場合そのような点で積分区間を分割すれば計算可能.
- f(x)の計算に大きな誤差が含まれる. この場合桁落ちなどが起きないようにf(x)を変形すればよい. ただし端点で発散するような積分の場合はさらに特異点を原点にずらしておく必要がある.
- これは意外と重要です.上の例では 2/sqrt(x*(1-x)) を(0,1/2]で積分しているので結果はπですが, 実際に実行すると3.14159265358979が得られます. ところが, 1/sqrt(x*(1-x) を(0,1)で積分すると, NANになってしまい積分できません.
- 当然ですが, x=1で特異性を持つ場合, g(x)-g(1) という形が含まれてはならないんで, そうなるように式変形するんですよ?変数の定義をずらして特異点をx=0にして, 第0項は解析でキャンセル
キャンセル
のこりを h(x)*x という形にできたらグッドだにゃー
- intdeo以外でf(x)が激しく振動している. この場合はintdeoが使える形に変形できれば計算可能. 変形できない複雑な振動積分は別のルーチンが必要. 積分の和の計算で桁落ちが起きると, 実際の相対誤差は epsで指定した精度よりも悪くなる. 例えばeps=1.0e-15と 指定したときに5桁の桁落ちが起きた場合10桁の精度になる. 誤差の確認はerrを参照すること.
- 推定誤差errはあくまで推定であり厳密な誤差の上限ではない. 計算が遅くても高い信頼性を要求する場合にはパッケージ中の efsの値を小さめ(例えばefs=sqrt(eps)程度)に選ぶとよい.
QAGS
GSLのQAGS積分を行ってみましょう.
#includestruct my_type { double K=1.0; } myParameter; gsl_function QAGS_f; QAGS_f.Parameter=&myParameter; //パラメータのポインタを指定してもよいunc QAGS_f.function=[](double x, void *p){ my_type& P=(my_type *)p;//パラメータのポインタを指定したなら,復元できる return P.K/std::sqrt(x*(1.-x)); }; gsl_integration_workspace *QAGS_ws=gsl_integration_workspace_alloc (1000); double xmin=0.0,xmax=0.5, err; double v; gsl_integration_qags(&QAGS_f,xmin,xmax,0.0,1.e-12,1000,QAGS_ws,&v,&err);
格別説明は不要ですよね?
QAGP
GSLのQAGP積分を行ってみましょう. これは, 特異点が既知の場合に有用です. 特異点には, 無条件で積分範囲の両端を入れなければなりません.
#includestruct my_type { double K=1.0; } myParameter; gsl_function QAGP_f; QAGP_f.Parameter=&myParameter; //パラメータのポインタを指定してもよいunc QAGP_f.function=[](double x, void *p){ my_type& P=(my_type *)p;//パラメータのポインタを指定したなら,復元できる return P.K/std::sqrt(x*(1.-x)); }; std::vector<double> QAGP_pts; QAGP_pts.resize(2); QAGP_pts.front()=0.0;QAGP_pts.back()=0.5; double err; gsl_integration_workspace *QAGP_ws=gsl_integration_workspace_alloc (1000); double v; gsl_integration_qagp(&QAGS_f,QAGP_pts.data(),QAGP_pts.size(),0.0,1.e-12,1000,QAGS_ws,&v,&err);
格別説明は不要ですよね?
スピード競走
いろいろ公式があって,どれにしようかな,というところですね.そこで,スピード競走例を示しましょう.
例題:2つのLennard-Jones分子が2体衝突を行うとき, その散乱角θは, 以下の積分で与えられる.
f(x) = 1 - a x + b x^3 (x^3-1), x>0であり, a, bは非負定数である.
xc=min{ x | f(x)=0, x>0}
θ=[sqrt(a)/2] int_0^xc dx/sqrt(x f(x))
被積分関数は x=0, xc で特異点を持つ. 運が悪いと(b>5で, aの値が,とある関数a(b)の値と一致すると), 積分が発散する(惑星の周りでスイングばい). まあ普通に選んでたら当たらないけど, ギリギリまずいケースもあるかも,です
これを2268000点の(a,b)で行った場合のCPU時間は,
積分ライブラリ |
CPU(sec) |
結果θの平均 | err | NAN / SIGABRT |
QAGS | 41.897 / 27.662 | 0.589963 | 1.19e-13 | 0 / 1 |
QAGP | 41.553 / 27.526 | 0.589963 | 1.19e-13 | 0 / 0 |
IntDE | 12.350 / 8.363 | 0.589925 | 1.0e-13 | 16 / 0 |
QAGS | 31.450 / 27.144 | 0589963 | 1.5e-10 | 0 / 0 |
QAGP | 29.886 / 18.538 | 0.589963 | 1.5e-10 | 0 / 0 |
IntDE | 10.169 / 7.396 | 0.589952 | 1.0e-9 | 4 / 0 |
なお, errは, ライブラリーが自分で推定した誤差の言い値の平均ですから, どこまで信じられるかは不明. SIGABRTはテスト中に計算が止まってしまった総数(出たケースでは, パラメータをずらして回避).
- IntDEが3倍以上早いが, 特異点にちょいと弱い感じですね. isnan(結果変数) で検出可能ですが.
- まあ被積分関数を漸近展開するとかして,テキトーに回避してください
- IntDEでは破局ケース(SIGABORTによる計算停止)は出なかった.
ついでにLinuxでも計算時間を見てみると(ic++ -Ofast --gsl --boost -std=c++11)
積分ライブラリ |
is2015e |
ig2016b XeonE5-269 |
ig2017a |
ib2014b Corei7-4790K |
QAGS | *1 | *1 | *1 | *1 |
QAGP | *1 | *1 | *1 | *1 |
IntDE | 4.0069 sec | 3.547 sec | 3.483 sec | 3.253 sec |
*1 このやろう, エラー吐きまくり動かねえ・・・roundoffエラーに引っかかるってことらしいが
さすがDevil's Canyonで鳴らした4790K, 老いたりとはいえ負けてねえな.
Linuxにコピーする際に, 最初isnan()だったら特異点前/中/後の3領域に分けて積分し直すように改造したらNANが消えた
やっぱり積分は和風の味が似合うようだな.
添付 | サイズ |
---|---|
intde.zip | 23.02 KB |
intde++.zip | 6.39 KB |