web-dev-qa-db-ja.com

scipy.integrate.odeでの適応ステップサイズの使用

scipy.integrate.odeの(簡単な)ドキュメントには、2つのメソッド(dopri5dop853)にステップサイズ制御と高密度出力があると記載されています。例とコード自体を見ると、インテグレーターから出力を取得する非常に簡単な方法しかわかりません。つまり、ある固定dtだけ積分器を前進させ、その時点で関数値を取得して、繰り返すように見えます。

私の問題はかなり可変のタイムスケールを持っているので、必要な許容誤差を達成するために評価する必要がある任意のタイムステップで値を取得したいだけです。つまり、早い段階で物事はゆっくりと変化しているため、出力時間ステップが大きくなる可能性があります。しかし、物事が面白くなるにつれ、出力のタイムステップを小さくする必要があります。実際には等間隔で密な出力が必要ではなく、適応関数が使用するタイムステップだけが必要です。

編集:高密度出力

関連する概念(ほぼ反対)は「高密度出力」です。これにより、実行されるステップはステッパーが実行するのと同じ大きさになりますが、関数の値は(通常はステッパーの精度に匹敵する精度で)補間されます。あなたが欲しい。 scipy.integrate.odeの基になっているFortranは明らかにこれを実行できますが、odeにはインターフェースがありません。一方、odeintは別のコードに基づいており、明らかに密な出力を行います。 (右側が呼び出されるたびに出力して、それがいつ発生するかを確認し、出力時間とは関係がないことを確認できます。)

したがって、必要な出力タイムステップを事前に決定できる限り、適応性を利用できます。残念ながら、私のお気に入りのシステムでは、統合を実行するまで、時間の関数としてのおおよそのタイムスケールが何であるかさえわかりません。したがって、1つのインテグレータステップを実行するというアイデアと、この高密度出力の概念を組み合わせる必要があります。

編集2:再び高密度出力

どうやら、scipy 1.0.0は新しいインターフェースを介した高密度出力のサポートを導入しました。特に、scipy.integrate.odeintから離れて scipy.integrate.solve_ivp に移動することをお勧めします。これは、キーワードdense_outputです。 Trueに設定されている場合、返されるオブジェクトには属性solがあり、これを時間の配列で呼び出すことができます。これにより、その時間の統合関数の値が返されます。それでもこの質問の問題は解決しませんが、多くの場合に役立ちます。

23
Mike

SciPy 0.13. なので、

ODEソルバーのdopriファミリーからの中間結果は、soloutコールバック関数からアクセスできるようになりました。

import numpy as np
from scipy.integrate import ode
import matplotlib.pyplot as plt


def logistic(t, y, r):
    return r * y * (1.0 - y)

r = .01
t0 = 0
y0 = 1e-5
t1 = 5000.0

backend = 'dopri5'
# backend = 'dop853'
solver = ode(logistic).set_integrator(backend)

sol = []
def solout(t, y):
    sol.append([t, *y])
solver.set_solout(solout)
solver.set_initial_value(y0, t0).set_f_params(r)
solver.integrate(t1)

sol = np.array(sol)

plt.plot(sol[:,0], sol[:,1], 'b.-')
plt.show()

結果: Logistic function solved using DOPRI5

結果はTim Dとは少し異なるようですが、どちらも同じバックエンドを使用しています。これはdopri5のFSALプロパティに関係していると思います。ティムのアプローチでは、第7段階の結果k7は破棄されると思うので、k1は新たに計算されます。

注:既知の 初期値の設定後に設定した場合、set_soloutのバグが機能しない があります。 SciPy 0.17. の時点で修正されました。

13
syockit

私は同じ結果を得るためにこれを見てきました。 odeのインスタンス化でnsteps = 1を設定することで、ハックを使用して段階的な結果を取得できることがわかりました。すべてのステップでUserWarningが生成されます(これはキャッチして抑制できます)。

import numpy as np
from scipy.integrate import ode
import matplotlib.pyplot as plt
import warnings


def logistic(t, y, r):
    return r * y * (1.0 - y)

r = .01
t0 = 0
y0 = 1e-5
t1 = 5000.0

#backend = 'vode'
backend = 'dopri5'
#backend = 'dop853'

solver = ode(logistic).set_integrator(backend, nsteps=1)
solver.set_initial_value(y0, t0).set_f_params(r)
# suppress Fortran-printed warning
solver._integrator.iWork[2] = -1

sol = []
warnings.filterwarnings("ignore", category=UserWarning)
while solver.t < t1:
    solver.integrate(t1, step=True)
    sol.append([solver.t, solver.y])
warnings.resetwarnings()
sol = np.array(sol)

plt.plot(sol[:,0], sol[:,1], 'b.-')
plt.show()

結果: 

12
Tim D

integrateメソッドは、単一の内部ステップを返すようにメソッドに指示するブール引数stepを受け入れます。ただし、「dopri5」および「dop853」ソルバーはそれをサポートしていないようです。

次のコードは、「vode」ソルバーが使用されている場合にソルバーが実行する内部ステップを取得する方法を示しています。

import numpy as np
from scipy.integrate import ode
import matplotlib.pyplot as plt


def logistic(t, y, r):
    return r * y * (1.0 - y)

r = .01

t0 = 0
y0 = 1e-5
t1 = 5000.0

backend = 'vode'
#backend = 'dopri5'
#backend = 'dop853'
solver = ode(logistic).set_integrator(backend)
solver.set_initial_value(y0, t0).set_f_params(r)

sol = []
while solver.successful() and solver.t < t1:
    solver.integrate(t1, step=True)
    sol.append([solver.t, solver.y])

sol = np.array(sol)

plt.plot(sol[:,0], sol[:,1], 'b.-')
plt.show()

結果: Plot of the solution

8

参考までに、回答はすでに受け入れられていますが、計算された軌道に沿ったどこでもからの密な出力と任意のサンプリングがネイティブであるという歴史的記録を指摘する必要があります PyDSTool でサポートされています。これには、ソルバーによって内部的に使用される、適応的に決定されたすべての時間ステップの記録も含まれます。これは両方とインターフェースします dopri853とradau5 そして(はるかに遅い)python右の関数コールバックに依存するのではなく、それらとインターフェースするために必要なCコードを自動生成します-私の知る限り、これらの機能はいずれも、他のPythonに焦点を当てたソルバーではネイティブにまたは効率的に提供されていません。

2
RHC