我试图使用scipy中的odeint来解决二阶ODE问题。我遇到的问题是函数隐式地耦合到二阶项,如简化片段中所示(请忽略示例的假装物理):
import numpy as np
from scipy.integrate import odeint
def integral(y,t,F_l,mass):
dydt = np.zeros_like(y)
x, v = y
F_r = (((1-a)/3)**2 + (2*(1+a)/3)**2) * v # 'a' implicit
a = (F_l - F_r)/mass
dydt = [v, a]
return dydt
y0 = [0,5]
time = np.linspace(0.,10.,21)
F_lon = 100.
mass = 1000.
dydt = odeint(integral, y0, time, args=(F_lon,mass))
在这种情况下,我意识到可以用代数方式求解隐式变量,但是在我的实际场景中,F_r
和a
的评估之间存在很多逻辑,而代数操作失败了。
我相信DAE可以使用MATLAB的ode15i函数来解决,但是如果可能的话,我试图避免这种情况。
我的问题是 - 有没有办法解决python中的隐式ODE函数(DAE)(最好是scipy)?是否有更好的方法来解决上述问题?
作为最后的手段,从上一个时间步骤传递a
是可以接受的。我怎样才能在每个时间步之后将dydt[1]
传递回函数?
答案 0 :(得分:2)
很老,但值得更新,因此对于偶然发现此问题的任何人都可能有用。 python中目前有很少的软件包可以解决隐式ODE。 GEKKO(https://github.com/BYU-PRISM/GEKKO)是其中一个软件包,专门研究混合整数,非线性优化问题的动态优化,但也可以用作通用DAE求解器。
上述“假装物理学”问题可以在GEKKO中按以下方式解决。
m= GEKKO()
m.time = np.linspace(0,100,101)
F_l = m.Param(value=1000)
mass = m.Param(value =1000)
m.options.IMODE=4
m.options.NODES=3
F_r = m.Var(value=0)
x = m.Var(value=0)
v = m.Var(value=0,lb=0)
a = m.Var(value=5,lb=0)
m.Equation(x.dt() == v)
m.Equation(v.dt() == a)
m.Equation (F_r == (((1-a)/3)**2 + (2*(1+a)/3)**2 * v))
m.Equation (a == (1000 - F_l)/mass)
m.solve(disp=False)
plt.plot(x)
答案 1 :(得分:1)
如果代数操作失败,你可以找到约束的数值解,例如在每个时间步运行fsolve
:
import sys
from numpy import linspace
from scipy.integrate import odeint
from scipy.optimize import fsolve
y0 = [0, 5]
time = linspace(0., 10., 1000)
F_lon = 10.
mass = 1000.
def F_r(a, v):
return (((1 - a) / 3) ** 2 + (2 * (1 + a) / 3) ** 2) * v
def constraint(a, v):
return (F_lon - F_r(a, v)) / mass - a
def integral(y, _):
v = y[1]
a, _, ier, mesg = fsolve(constraint, 0, args=[v, ], full_output=True)
if ier != 1:
print "I coudn't solve the algebraic constraint, error:\n\n", mesg
sys.stdout.flush()
return [v, a]
dydt = odeint(integral, y0, time)
显然,这会减慢你的时间整合。始终检查fsolve
是否找到了一个好的解决方案,然后刷新输出,以便在发生时实现它并停止模拟。
关于如何"缓存"在前一个时间步的变量值,您可以利用默认参数仅在函数定义中计算的事实,
from numpy import linspace
from scipy.integrate import odeint
#you can choose a better guess using fsolve instead of 0
def integral(y, _, F_l, M, cache=[0]):
v, preva = y[1], cache[0]
#use value for 'a' from the previous timestep
F_r = (((1 - preva) / 3) ** 2 + (2 * (1 + preva) / 3) ** 2) * v
#calculate the new value
a = (F_l - F_r) / M
cache[0] = a
return [v, a]
y0 = [0, 5]
time = linspace(0., 10., 1000)
F_lon = 100.
mass = 1000.
dydt = odeint(integral, y0, time, args=(F_lon, mass))
请注意,为了使技巧起作用,cache
参数必须是可变的,这就是我使用列表的原因。如果您不熟悉默认参数的工作原理,请参阅this链接。
请注意,这两个代码不会产生相同的结果,您应该非常小心地使用前一个时间步的值,以确保数值稳定性和精度。第二个显然要快得多。