使用SymPy表达式和SciPy求解器

时间:2018-04-11 01:41:24

标签: python scipy sympy ode

我正在尝试学习如何将python SymPy与SciPy集成以数值求解常微分方程。但是,我有点失去了如何将一阶ODE系统的SymPy形式转换为我可以使用scipy.integrate.odeint()

处理的格式

请注意,有些人认为这与其他帖子类似,但事实并非如此。另一篇文章在这里 Convert sympy expressions to function of numpy arrays

所以这篇文章是一个更复杂的案例,用户希望使用Theano或其他一些库来加速ODE的计算。我只是想了解SymPy和SciPy之间的基本接口,所以这篇文章根本没什么用处。

作为一个玩具示例,我使用Lotka-Volterra方程来测试SymPy的使用。方程是:

\dot{x} = ax - bxy

\dot{y} = cxy - dy

我可以用Scipy以常规方式解决这个问题并且它可以工作。这是工作代码。

import numpy as np
import scipy
from scipy.integrate import odeint, ode, solve_ivp
import sympy
import matplotlib.pyplot as plt
sympy.init_printing()

def F_direct(X, t, args):
    F = np.zeros(2)
    a, b, c, d = args
    x,y = X
    F[0] = a*x - b*x*y
    F[1] = c*x*y- d*y
    return F

argst = [0.4,0.002,0.001,0.7]
xy0 = [600, 400]
t = np.linspace(0, 50, 250)
xy_t, infodict = odeint(F_direct, xy0, t, args=(argst,), full_output=True)

plt.plot(t, xy_t[:,1], 'o', t, xy_t[:,0])
plt.grid(True)
plt.xlabel('x'); plt.ylabel('y')
plt.legend(('Numerical', 'Exact'), loc=0)
plt.show()

现在我对如何使用SymPy这样做感到很遗憾。我知道需要做些什么,但不知道该怎么做。我发现的唯一例子是太复杂而无法学习。这就是我所拥有的。

x, y, a, b, c, d = sympy.symbols('x y a b c d')
t = np.linspace(0, 50, 250)
ode1 = sympy.Eq(a*x - b*x*y)
ode2 = sympy.Eq(c*x*y - d*y)

我应该将这些方程式放入某种系统形式,然后使用sympy.lambdify函数返回一个我可以传递给odeint的新函数

所以任何人都可以在这里填写关于我如何设置这个ode1,ode2系统来处理SymPy的空白。

2 个答案:

答案 0 :(得分:2)

很少需要在SymPy中使用Eq对象;方程式最好用表达式表示,在求解器的上下文中,它们被理解为等于零。因此,ode1ode2应该只是ODE系统右侧的表达式。然后他们可以一起被贬低,如下:

ode1 = a*x - b*x*y
ode2 = c*x*y - d*y
F_sympy = sympy.lambdify((x, y, a, b, c, d), [ode1, ode2])
def F_from_sympy(X, t, args):
    a, b, c, d = args
    x, y = X    
    return F_sympy(x, y, a, b, c, d)

需要进行lambdification之后的额外步骤,因为SciPy的求解器传递了一些数组,lambdified函数将不知道如何解包。例如:

F_from_sympy([1, 2], np.linspace(0, 1, 100), (3, 4, 5, 6))

返回[-5, -2]这是一个Python列表而不是NumPy数组,但ODE求解器应该处理它。 (或者您可以返回np.array(F_sympy(x, y, a, b, c, d)))。)

答案 1 :(得分:2)

我写了a module named JiTCODE,它从描述右侧的符号表达式(SymPy或SymEngine)创建ODE集成器对象(处理类似于scipy.integrate.ode)。在幕后,它使用scipy.integrate.odescipy.integrate.solve_ivp进行整合。

在你的情况下,唯一的缺点是规定了动态变量和时间的符号,因此你可能不得不进行符号替换 - 但这不应该是一个大问题。 下面以您的Lotka-Volterra公式为例,使用y(0)代替xy(1)代替y

import numpy as np
from sympy.abc import a,b,c,d
from jitcode import y, jitcode

xy0 = [600,400]
argst = [0.4,0.002,0.001,0.7]

lotka_volterra = [
         a*y(0) - b*y(0)*y(1),
        -d*y(1) + c*y(0)*y(1)
    ]

ODE = jitcode( lotka_volterra, control_pars=[a,b,c,d] )
ODE.set_integrator("dopri5")
ODE.set_initial_value(xy0)
ODE.set_parameters(*argst)

times = np.linspace(0, 50, 250)
xy_t = np.vstack(ODE.integrate(time) for time in times)

请注意,此模块的主要功能是编译右侧以提高效率。根据你需要做的事情,这可能有点过分,但如果它有效则不会造成伤害(你也可以禁用它并按照the other answer中的详细说明使用lambdification。)