我正在使用SciPy的integrate.odeint函数集成一个僵硬的ODE系统。由于集成非常重要且耗时,我也使用相应的雅可比。通过重新排列方程,我可以将雅可比定义为带状矩阵。在API documentation之后,我想用 mu 和 ml 参数定义形状。不幸的是,文档有点含糊不清,所以我无法弄清楚如何实现我的jacobian函数。
为了验证必须如何调用 odeint ,我一直在使用以下(有些愚蠢)代码:
from scipy.integrate import odeint
lmax = 5
def f1(y, t):
ydot = np.zeros(lmax)
for i in range(lmax):
ydot[i] = y[i]**2-y[i]**3
return ydot
def fulljac(y, t,):
J = np.zeros((lmax, lmax))
J[0,0]=-3*y[0]**2 + 2*y[0]
J[1,1]=-3*y[1]**2 + 2*y[1]
J[2,2]=-3*y[2]**2 + 2*y[2]
J[3,3]=-3*y[3]**2 + 2*y[3]
J[4,4]=-3*y[4]**2 + 2*y[4]
return J
## initial conditions and output times
delta = 0.0001;
yini = np.array([delta]*lmax)
times = np.linspace(0, 2/delta, 100)
y, infodict = odeint(f1, yini, times, Dfun=fulljac, full_output=1)
print("f1: nst: {0}, nfe: {1}, nje: {2}".format(infodict["nst"][-1],
infodict["nfe"][-1],
infodict["nje"][-1]))
使用完整的NxN jacobian矩阵,集成成功。仅使用对角线和 mu = 0 和 ml = 0 ,集成也会成功。
为了测试带状矩阵用例我正在使用 mu = 1 和 ml = 1 创建一个人工3xN带状jacobian,其中对角线以外的所有导数都是零。这会导致解算器的奇怪行为(类似于我在原始问题中看到的非对角线非零)。
def bandjac(y, t):
J = np.zeros((lmax, 3))
J[0,1]=-3*y[0]**2 + 2*y[0]
J[1,1]=-3*y[1]**2 + 2*y[1]
J[2,1]=-3*y[2]**2 + 2*y[2]
J[3,1]=-3*y[3]**2 + 2*y[3]
J[4,1]=-3*y[4]**2 + 2*y[4]
return J
y, infodict = odeint(f1, yini, times, Dfun=bandjac, full_output=1, mu=1, ml=1)
print("f1: nst: {0}, nfe: {1}, nje: {2}".format(infodict["nst"][-1],
infodict["nfe"][-1],
infodict["nje"][-1]))
在SciPy的integrate.odeint中使用带状jacobian选项的正确方法是什么?
答案 0 :(得分:1)
为了完整起见,我回答了我自己的问题。
正如Warren Weckesser所指出的那样,在Scipy上有一个bug< 0.14.0,关于odeint如何处理带状雅各比人。
odeint的当前文档声明:
Dfun应返回一个矩阵,其行包含非零带(从最低对角线开始)。
我认为这是不正确的。相反,它应该从最高对角线开始。
以下代码段显示 Dfun 应如何返回jacobian(源自test_integrate.py unit test):
def func(y, t, c):
return c.dot(y)
def jac(y, t, c):
return c
def bjac_rows(y, t, c):
return np.array([[ 0, 75, 1, 0.2], # mu - upper
[ -50, -0.1, -1e-04, -40], # diagonal
[ 0.2, 0.1, -0.2, 0]]) # lower - ml
c = array([[-50, 75, 0, 0],
[0.2, -0.1, 1, 0],
[0, 0.1, -1e-4, 0.2],
[0, 0, -0.2, -40]])
y0 = arange(4)
t = np.linspace(0, 50, 6)
# using the full jacobian
sol0, info0 = odeint(func, y0, t, args=(c,), full_output=True,Dfun=jac)
print("f1: nst: {0}, nfe: {1}, nje: {2}".format(info0["nst"][-1],
info0["nfe"][-1],
info0["nje"][-1]))
# using the row based banded jacobian
sol2, info2 = odeint(func, y0, t, args=(c,), full_output=True,Dfun=bjac_rows, ml=1, mu=1)
print("f1: nst: {0}, nfe: {1}, nje: {2}".format(info2["nst"][-1],
info2["nfe"][-1],
info2["nje"][-1]))
注意:转置的带状矩阵似乎不适用于 col_deriv = True
答案 1 :(得分:0)
我创建了一个函数,可以按照odeint
的预期将jacobian矩阵转换为带状格式,以及mu
和ml
参数。输入应为np.array
。为了获得最佳性能,您可能希望将结果矩阵转换为np.array
,然后让jacobian函数返回其转置,并使用col_der=True
。我认为BrainGrylls是正确的,你应该从最高的对角线开始,与当前的API文档相反。
def make_banded_jacobian(matrix):
'''returns a banded jacobian list (in odeint's format), along with mu and ml parameters'''
# first find the values of mu and ml
dims = matrix.shape[0]
assert dims == matrix.shape[1]
mu = 0
ml = 0
for row in xrange(dims):
for col in xrange(dims):
if matrix[row][col] != 0:
if col > row:
dif = col - row
mu = max(mu, dif)
else:
dif = row - col
ml = max(ml, dif)
banded = []
for yoffset in xrange(-mu, ml+1):
row = []
for diag in xrange(dims):
x_index = diag
y_index = diag + yoffset
if y_index < 0 or y_index >= dims:
row.append(0.0)
else:
row.append(matrix[y_index][x_index])
banded.append(row)
return (banded, mu, ml)