求積アルゴリズムをまとめた記事です。随時更新していきます系の記事欲しさに作りました。
式として表示する際の積分範囲は
あとExcelのワンセルコードもできるだけ載せておきます。
pythonのコードとかは探せば出てくると思うんですけど、Excelの数式コードってあんまり出てこないので。(pythonより非効率なコードを強いられることもあるので当たり前ではある。)
ワンセルコードのテスト積分は(テキトーに)これにしました。端点に特異性のある若干いやーな定積分です。
その下には実際に動かした結果と相対誤差を載せています。
※fx,の後に関数を入力します。色々関数を変えて試してみてください。使い方はExcelまたはGoogleスプレッドシートのセルの数式バーにそのままコピペするだけ。
(求積法には分割回数、次数などを表す
求積と言われて大半の方が思いつくのがこれです。通常複合形式(積分範囲を
=LET(下限,0,上限,PI(),n,50,x,SEQUENCE(n,1,下限,(上限-下限)/n),fx,EXP(COS(x))+SQRT(x),h,(上限-下限)/n,SUM(IFERROR(fx,0)*h))
結果:
相対誤差:
この公式を(等間隔の)
利点:
欠点:
高次になると誤差項が大きくなる場合がある。(ルンゲ現象)
(しかし、推奨されないと分かっていてもやってみたいのがサガですよね。実際やってみると単一適用では全く値が合いませんし、複合形式にしても若干影響が出ます。係数表なら
ここ
にあるので気になる方はぜひやってみてください。)
複合公式にすれば安定感が増します。
数値積分のはじめの一歩のような存在のニュートンコーツですが、この等間隔に点を取ることをやめて、特に誤差がひどいところの積分区間を分割して繰り返し求積を行う適応型にすればかなりの実用性があります。(後述する紹介するガウス求積とは別物です。)
先ほどの公式で言う
=LET(下限,0,上限,PI(),n,50,x,SEQUENCE(n+1,1,下限,(上限-下限)/n),fx,EXP(COS(x))+SQRT(x),h,(上限-下限)/n,SUM(MAP(IFERROR(fx,0),SEQUENCE(n+1),LAMBDA(p,q,IF(OR(q=1,q=n+1),p/2,p))))*h)
結果:
相対誤差:
先ほどの
=LET(下限,0,上限,PI(),n,50,x,SEQUENCE(2*n+1,1,下限,(上限-下限)/(2*n)),fx,EXP(COS(x))+SQRT(x),h,(上限-下限)/(2*n),SUM(MAP(IFERROR(fx,0),SEQUENCE(2*n+1),LAMBDA(p,q,IFS(OR(q=1,q=2*n+1),p,MOD(q,2)=0,4*p,MOD(q,2)=1,2*p))))*h/3)
結果:
相対誤差:
先ほどの
=LET(下限,0,上限,PI(),n,50,x,SEQUENCE(3*n+1,1,下限,(上限-下限)/(3*n)),fx,EXP(COS(x))+SQRT(x),h,(上限-下限)/(3*n),SUM(MAP(IFERROR(fx,0),SEQUENCE(3*n+1),LAMBDA(p,q,IFS(OR(q=1,q=3*n+1),p,MOD(q,3)=1,2*p,OR(MOD(q,3)=0,MOD(q,3)=2),3*p))))*3*h/8)
結果:
相対誤差:
=LET(下限,0,上限,PI(),n,50,x,SEQUENCE(4*n+1,1,下限,(上限-下限)/(4*n)),fx,EXP(COS(x))+SQRT(x),h,(上限-下限)/(4*n),SUM(MAP(IFERROR(fx,0),SEQUENCE(4*n+1),LAMBDA(p,q,IFS(OR(q=1,q=4*n+1),14*p,MOD(q,4)=1,28*p,OR(MOD(q,4)=0,MOD(q,4)=2),64*p,MOD(q,4)=3,24*p))))*h/45)
結果:
相対誤差:
これは先ほどの台形公式にリチャードソンの補外を適用させたものです。
まず、刻み幅を倍々で小さくしながら台形公式を適用し、刻み幅が粗い順に
(
これらに対して、次の漸化式を考えます。
これをネヴィルのアルゴリズムといいます。これを用いて
早速Excelのコードを...といきたいところですが、Excelのワンセルコードにおいて、三項間以上の漸化式を一般の
ただし、
※この
=LET(a,0,b,PI(),n,8,m,2^n,z,SEQUENCE(m-1,1,a+(b-a)/m,(b-a)/m),fx,LAMBDA(x,EXP(COS(x))+SQRT(x)),qbcn,LAMBDA(i,r,q,IF(OR(r=0,r=i),1,PRODUCT(MAKEARRAY(r,1,LAMBDA(v,w,(1-q^(i-r+v))/(1-q^v)))))),C,fx(z),mSeq,SEQUENCE(m-1,1),T,MAKEARRAY(n+1,1,LAMBDA(p,q,(b-a)/2^(p-1)*SUM((fx(a)+fx(b))/2,IF(MOD(mSeq,2^(n-p+1))=0,INDEX(C,mSeq),0)))),qn,PRODUCT(1-4^SEQUENCE(n)),Rn,MAKEARRAY(ROUND((n+1)/2,0),1,LAMBDA(e,f,qbcn(n,e-1,4))),fpow,SCAN(1,SEQUENCE(n+1,1,0),LAMBDA(f,g,f*4^g))/qn,S,MAKEARRAY(n+1,1,LAMBDA(p,q,INDEX(T,p)*INDEX(Rn,INT((n+2)/2-ABS((n+2)/2-p)))*INDEX(fpow,p)*(-1)^(p-1))),SUM(S))
結果:
相対誤差:
これは上記の求積法とは扱いが異なります。上記は全て等間隔の点のみで関数を評価していましたが、この求積法は等間隔でない、
いわゆるデータで取れた
3次スプライン補間のルールとして次のようなものがあります。
・点と点の間を1つの三次関数で補間する。
・あるデータ点で共有する2つの三次関数は、その点での1次微分係数と2次微分係数が両者で等しい。
とりあえずここまでの条件で立てられる式はこんな感じです。
とすると、
しかしこれだけですと係数に対して式が2つ足りないので、その2つを何らかの形で補う必要があります。
今回はその補い方を2つ紹介し、そのExcelのコードを記述します。
公式の日本語訳が分からないので適当に訳してます。
端点の一次微分係数を指定してあげます。始点の一次微分係数をdif_a、終点の一次微分係数をdif_bとします。
dif_a
dif_b
で表されるので、あとはこれらの式を用いて係数を導出すれば、積分値が求まります。(今回は特にテスト関数による例示をしていません。)
具体的なやり方は秋元幸生「プログラミング言語Pascalとその応用」(工学図書, 1984年)pp.133-140や
こちら
や
こちら
が分かりやすいものと思われます。
=LET(x,[ここに
自然スプラインは始点と終点の二次微分係数を
=LET(x,[ここに
まずはガウス・ルジャンドル求積の方から
先ほどのニュートン・コーツの公式の方では、
この求積法にはルジャンドル多項式という直交多項式が関わっています。
こちら
が一番詳しく、また分かりやすい説明となっています。直交多項式の性質の良さがばっちり現れています。
注意点として、ニュートンコーツの公式と同じように、あまりに高次のものを使いすぎるとルンゲ現象が起きるかもしれないので
っていう話をしようと思ったら
こんな論文
を見つけてしまいました。振動する特定の関数では、超高次のガウス・ルジャンドル求積を使った方が遥かに効率的であることを示す論文です。なかなかおもしろいですね。
といってもこれは、一部の関数についてであるので、あくまでその使い分けは経験と勘に任されているとしか言えない状況でもあります。
しかしながらこのガウス・ルジャンドル求積、誤差評価がかなりしづらいのが難点です。余剰項から考える手法を取るにしても、関数の
関数の評価点を使いまわしつつ、誤差評価ができる求積法として、ガウス・クロンロッド求積が考えられました。この求積の分点及び重みに使われている直交多項式はスティルチェイス多項式です。
正直、この求積法を単体で用いる利点は、ガウス・ルジャンドル求積の前ではあまりないと思います。ガウス・クロンロッド求積の
言い忘れましたがこの求積法の分点と重みの計算が重いので、あらかじめ計算されたものを使ったりすることが多いです。pythonとか julia とかだと専用のパッケージとかあるかもしれませんね。
では、ワンセルコードを示します。今回はよく使われている
その他の次数の分点及び重みが知りたい方は
こちら
へ。
=LET(a,0,b,PI(),n,20,xl,VSTACK(-0.949107912342759,-0.741531185599394,-0.405845151377397,0,0.405845151377397,0.741531185599394,0.949107912342759),x,MAKEARRAY(n,7,LAMBDA(s,t,a+(b-a)/(2*n)*(INDEX(xl,t)+2*s-1))),fx,EXP(COS(x))+SQRT(x),sfx,BYCOL(IFERROR(fx,0),LAMBDA(u,SUM(u)))*(b-a)/(2*n),wl,HSTACK(0.12948496616887,0.279705391489277,0.381830050505119,0.417959183673469,0.381830050505119,0.279705391489277,0.12948496616887),gl,SUM(MAP(sfx,wl,LAMBDA(p,q,p*q))),gl)
結果:
相対誤差:
=LET(下限,0,上限,PI(),n,10,xk,VSTACK(-0.991455371120813,-0.949107912342759,-0.864864423359769,-0.741531185599394,-0.586087235469691,-0.405845151377397,-0.207784955007898,0,0.207784955007898,0.405845151377397,0.586087235469691,0.741531185599394,0.864864423359769,0.949107912342759,0.991455371120813),x,MAKEARRAY(n,15,LAMBDA(s,t,下限+(上限-下限)/(2*n)*(INDEX(xk,t)+2*s-1))),fx,EXP(COS(x))+SQRT(x),sfx,BYCOL(IFERROR(fx,0),LAMBDA(u,SUM(u)))*(上限-下限)/(2*n),wk,HSTACK(0.022935322010529,0.063092092629979,0.10479001032225,0.140653259715526,0.169004726639268,0.190350578064785,0.204432940075299,0.209482141084728,0.204432940075299,0.190350578064785,0.169004726639268,0.140653259715526,0.10479001032225,0.063092092629979,0.022935322010529),wl,HSTACK(0,0.12948496616887,0,0.279705391489277,0,0.381830050505119,0,0.417959183673469,0,0.381830050505119,0,0.279705391489277,0,0.12948496616887,0),gk,SUM(MAP(sfx,wk,LAMBDA(p,q,p*q))),gl,SUM(MAP(sfx,wl,LAMBDA(p,q,p*q))),CONCAT(gk,"±",(200*ABS(gk-gl))^(1.5)))
※推定誤差は、
結果:
絶対誤差:
相対誤差:
この誤差推定は、基本的に真の誤差よりも大きく出るので、信頼性が持てます。(例外はあるかもしれない)
ガウス求積は、他の直交多項式を用いることが出来るので様々なバリエーションがありますが、現在は割愛させていただきます。また追記するかもしれません。
今回は分点が偶数(
特に、
クレンショー・カーティス求積は分点と重みの計算が比較的軽く、また実用上においてガウス・ルジャンドル求積と劣らない性能を持つ求積です。また、分点の数を
=LET(a,0,b,PI(),n,75,x,COS(SEQUENCE(2*n+1,1,0)/(2*n)*PI())*(b-a)/2+(a+b)/2,fx,EXP(COS(x))+SQRT(x),w,MAKEARRAY(n+1,1,LAMBDA(s,t,IF(s=1,1/(4*n^2-1),2/n*SUM(MAKEARRAY(1,n+1,LAMBDA(p,q,IFS(q=1,1/2,q=n+1,(-1)^(s-1)/(1-4*n^2)/2,AND(q<>1,q<>n+1),COS((q-1)*(s-1)*PI()/n)/(1-4*(q-1)^2)))))))),u,SUM(MAKEARRAY(2*n+1,1,LAMBDA(g,h,INDEX(IFERROR(fx,0),g)*INDEX(w,n+1-ABS(n+1-g)))))*(b-a)/2,u)
結果:
相対誤差:
ただし、
最推しの求積です。分点、重みの計算が軽いのはさることながら、今回の積分のような、端点に特異性のある関数の積分にめっぽう強いという特性を持ちます。あとどんな関数でも比較的精度良く計算できるため汎用性があります。
少し注意点があるとすれば万能ではないことと、分点の数を多くしすぎると累積の誤差が大きくなって若干有効数字が落ちます。
そして端点が(正負関わらず)無限大に発散する場合、特に桁落ちが顕著なので、その場合は変数変換等を行って適切に対処しましょう。
(何の関数かは忘れてしまいましたが、求積の結果が真の結果と大きくずれて収束してしまって、幻影解が現れたことがあります。)
これは有限区間のものですが、半無限区間や無限区間用の公式も存在します。
半無限区間:
下に有界:
上に有界:
無限区間:
ただし、
これらもなかなか便利です。
ではワンセルコードを示していきます。
=LET(a,0,b,PI(),n,150,ta,3.5,h,2*ta/(n-1),ti,-ta+(SEQUENCE(n,1,0)*h),x,(b-a)/2*TANH(PI()/2*SINH(ti))+(a+b)/2,fx,EXP(COS(x))+SQRT(x),w,PI()/2*COSH(ti)/(COSH(PI()/2*SINH(ti)))^2,S,SUM(MAP(fx,w,LAMBDA(p,q,p*q))),S*(b-a)/2*h)
結果:
相対誤差:
これが二重指数関数型数値積分公式の威力です。
下に有界な半無限区間のテスト積分は
とします。
=LET(a,0,n,150,ta,4,h,2*ta/(n-1),ti,-ta+SEQUENCE(n,1,0)*h,y,EXP(PI()/2*SINH(ti)),x,y+a,fx,LN(x)^2/(1+x^4),w,MAP(y,PI()/2*COSH(ti),LAMBDA(p,q,p*q)),S,SUM(MAP(IFERROR(fx,0),w,LAMBDA(p,q,p*q))),S*h)
結果:
相対誤差:
上に有界な半無限区間のテスト積分はこれにします。
=LET(b,0,n,150,ta,4,h,2*ta/(n-1),ti,-ta+SEQUENCE(n,1,0)*h,y,EXP(PI()/2*SINH(ti)),x,-y+b,fx,SIN(x)*EXP(x),w,MAP(y,PI()/2*COSH(ti),LAMBDA(p,q,p*q)),S,SUM(MAP(IFERROR(fx,0),w,LAMBDA(p,q,p*q))),S*h)
結果:
相対誤差:
無限区間のテスト積分はこれにします。
=LET(n,150,ta,4,h,2*ta/(n-1),ti,-ta+SEQUENCE(n,1,0)*h,x,SINH(PI()/2*SINH(ti)),fx,1/(1+x^2),w,COSH(PI()/2*SINH(ti))*PI()/2*COSH(ti),S,SUM(MAP(IFERROR(fx,0),w,LAMBDA(p,q,p*q))),S*h)
結果:
相対誤差:
ここで、上に有界な半無限区間の積分だけ精度が落ちているのが気になりますが、これはDE公式がもたらす離散化誤差によります。被積分関数がそもそも指数関数オーダーで減衰する場合、二重指数的な変数変換を施すと関数の値が0に近いところを多く評価してしまって、肝心の値がある範囲の評価がおろそかになるため若干精度が落ちます。こういった場合、被積分関数が指数減衰するとき専用のDE公式や、一重指数関数型の数値積分公式(SE公式)を用います。
これと同様に、被積分関数が二重指数減衰する場合は台形則で数値積分を行ったり、これまた専用のDE公式があったりします。
下に有界:
上に有界:
ただし、
=LET(a,0,n,150,ta,4,h,2*ta/(n-1),ti,-ta+SEQUENCE(n,1,0)*h,x,a+EXP(ti-EXP(-ti)),fx,SIN(x)*EXP(-x),w,(EXP(ti)+1)*EXP(-EXP(-ti)),S,SUM(MAP(IFERROR(fx,0),w,LAMBDA(p,q,p*q))),S*h)
テスト積分:
結果:
相対誤差:
=LET(b,0,n,150,ta,4,h,2*ta/(n-1),ti,-ta+SEQUENCE(n,1,0)*h,x,b-EXP(ti-EXP(-ti)),fx,SIN(x)*EXP(x),w,(EXP(ti)+1)*EXP(-EXP(-ti)),S,SUM(MAP(IFERROR(fx,0),w,LAMBDA(p,q,p*q))),S*h)
テスト積分:
結果:
相対誤差:
ただし、
これがIMT型積分公式です。IMTという名前はこの公式の開発に関わった方々の頭文字(Iri, Moriguchi, Takasawa)を取ったものです。詳細は
こちら
に載っています。
こちら
を実装していきます。Excelには誤差関数
をもちます。
さらに、Excelでは単精度から倍精度で計算することがほとんどなのですが、その範囲ではDE公式より高性能であることが、いくつかの数値計算例によって確認されています。
=LET(a,0,b,PI(),n,150,k,2.2,h,2/(n+1),t,-1+SEQUENCE(n)*h,m,LN(n+1)/2,yp,k/(1-t)^m,yq,k/(1+t)^m,y,yp-yq,x,ERF(y)*(b-a)/2+(a+b)/2,fx,EXP(COS(x))+SQRT(x),w,(yp/(1-t)+yq/(1+t))*EXP(-1*y^2),S,SUM(MAP(IFERROR(fx,0),w,LAMBDA(p,q,p*q))),S/SQRT(PI())*h*(b-a)*m)
結果:
相対誤差:
求積アルゴリズムは他にも、モンテカルロ積分などさまざまあります。先ほどのガウス求積も含めて追記していく予定です。
本文、ワンセルコードのミスや、もっと計算量を減らせるなどの提案があればお願いします。
また、これに載っていない求積法などありましたら教えて下さると助かります。
皆さんもExcelやGoogleスプレッドシートを開いて手軽に求積してみてはいかがでしょうか。