web-dev-qa-db-ja.com

scipy.integrate.quadを使用して複素数を積分します

私は現在、scipy.integrate.quadを使用して、いくつかの実際の被積分関数を正常に統合しています。今、私は複雑な被積分関数を統合する必要があるという状況が現れました。 quadは他のscipy.integrateルーチンのようにそれを行うことができないようです、それで私は尋ねます:実数部と虚数部の積分を分離する必要なしに、scipy.integrateを使用して複雑な被積分関数を統合する方法はありますか?

27
Ivan

それを実数部と虚数部に分けるだけで何が問題になりますか? scipy.integrate.quadは、使用するアルゴリズムの統合関数return float(別名実数)を必要とします。

import scipy
from scipy.integrate import quad

def complex_quadrature(func, a, b, **kwargs):
    def real_func(x):
        return scipy.real(func(x))
    def imag_func(x):
        return scipy.imag(func(x))
    real_integral = quad(real_func, a, b, **kwargs)
    imag_integral = quad(imag_func, a, b, **kwargs)
    return (real_integral[0] + 1j*imag_integral[0], real_integral[1:], imag_integral[1:])

例えば。、

>>> complex_quadrature(lambda x: (scipy.exp(1j*x)), 0,scipy.pi/2)
((0.99999999999999989+0.99999999999999989j),
 (1.1102230246251564e-14,),
 (1.1102230246251564e-14,))

これは、丸め誤差に期待するものです-0からのexp(ix)の整数、pi/2は(1/i)(e ^ i pi/2 --e ^ 0)= -i(i --1)= 1 + i〜(0.99999999999999989 + 0.99999999999999989j)。

そして、すべての人に100%明確ではない場合の記録として、積分は線形汎関数です。つまり、∫{f(x) + k g(x)} dx =∫f(x) dx +k∫g(x) dx(kはxに関する定数)または、特定のケースでは∫z(x) dx =∫Rez(x) dx +i∫Imz(x) dx as z(x) = Re z(x) + i Im z(x)。

複素平面(実軸に沿ったもの以外)のパスまたは複素平面の領域で積分を実行しようとしている場合は、より高度なアルゴリズムが必要になります。

注:Scipy.integrateは複雑な統合を直接処理しません。どうして? FORTRAN [〜#〜] quadpack [〜#〜] ライブラリ、特に関数/変数が実数であることが明示的に必要な qagse.f で手間のかかる作業を行います「各サブインターバル内の21ポイントのガウス-クロンロッド直交に基づくグローバル適応直交、PeterWynnのイプシロンアルゴリズムによる加速」を実行する前。したがって、基礎となるFORTRANを変更して複素数を処理できるようにし、新しいライブラリにコンパイルしない限り、それを機能させることはできません。

複素数を1つの積分でガウス・クロンロッド法を実行したい場合は、 ウィキペディアのページ を参照して、以下のように直接実装してください(15ポイント、7ポイントのルールを使用)。共通変数への共通呼び出しを繰り返すように関数をメモ化することに注意してください(関数呼び出しが非常に複雑であるかのように遅いと仮定します)。また、ノード/重みを自分で計算する気がなく、それらはウィキペディアにリストされているため、7ポイントと15ポイントのルールのみを実行しましたが、テストケースで妥当なエラーが発生しました(〜1e-14)

import scipy
from scipy import array

def quad_routine(func, a, b, x_list, w_list):
    c_1 = (b-a)/2.0
    c_2 = (b+a)/2.0
    eval_points = map(lambda x: c_1*x+c_2, x_list)
    func_evals = map(func, eval_points)
    return c_1 * sum(array(func_evals) * array(w_list))

def quad_gauss_7(func, a, b):
    x_gauss = [-0.949107912342759, -0.741531185599394, -0.405845151377397, 0, 0.405845151377397, 0.741531185599394, 0.949107912342759]
    w_gauss = array([0.129484966168870, 0.279705391489277, 0.381830050505119, 0.417959183673469, 0.381830050505119, 0.279705391489277,0.129484966168870])
    return quad_routine(func,a,b,x_gauss, w_gauss)

def quad_kronrod_15(func, a, b):
    x_kr = [-0.991455371120813,-0.949107912342759, -0.864864423359769, -0.741531185599394, -0.586087235467691,-0.405845151377397, -0.207784955007898, 0.0, 0.207784955007898,0.405845151377397, 0.586087235467691, 0.741531185599394, 0.864864423359769, 0.949107912342759, 0.991455371120813]
    w_kr = [0.022935322010529, 0.063092092629979, 0.104790010322250, 0.140653259715525, 0.169004726639267, 0.190350578064785, 0.204432940075298, 0.209482141084728, 0.204432940075298, 0.190350578064785, 0.169004726639267, 0.140653259715525,  0.104790010322250, 0.063092092629979, 0.022935322010529]
    return quad_routine(func,a,b,x_kr, w_kr)

class Memoize(object):
    def __init__(self, func):
        self.func = func
        self.eval_points = {}
    def __call__(self, *args):
        if args not in self.eval_points:
            self.eval_points[args] = self.func(*args)
        return self.eval_points[args]

def quad(func,a,b):
    ''' Output is the 15 point estimate; and the estimated error '''
    func = Memoize(func) #  Memoize function to skip repeated function calls.
    g7 = quad_gauss_7(func,a,b)
    k15 = quad_kronrod_15(func,a,b)
    # I don't have much faith in this error estimate taken from wikipedia
    # without incorporating how it should scale with changing limits
    return [k15, (200*scipy.absolute(g7-k15))**1.5]

テストケース:

>>> quad(lambda x: scipy.exp(1j*x), 0,scipy.pi/2.0)
[(0.99999999999999711+0.99999999999999689j), 9.6120083407040365e-19]

私はエラー推定値を信頼していません-[-1から1]に統合するときに推奨されるエラー推定値としてウィキから何かを取得しましたが、値は私には合理的ではないようです。たとえば、上記のエラーを真実と比較すると、〜1e-19ではなく〜5e-15になります。誰かがnumレシピを調べれば、もっと正確な見積もりを得ることができると確信しています。 (おそらく、(a-b)/2をなんらかの累乗または同様のものに乗算する必要があります)。

pythonバージョンは、scipyのQUADPACKベースの統合を2回呼び出すよりも精度が低くなります(必要に応じて改善できます)。

46
dr jimbob

私はパーティーに遅れていることに気づきましたが、おそらく quadpy (私のプロジェクト)が助けになるでしょう。この

_import quadpy
import scipy

scheme = quadpy.line_segment.gauss_kronrod(3)
val = scheme.integrate(lambda x: scipy.exp(1j*x), [0, 1])
print(val)
_

正しく与える

_(0.841470984807897+0.4596976941318605j)
_

gauss_kronrod(3)の代わりに、他の利用可能なスキームを使用できます。

3
Nico Schlömer