我有一个用sympy编写的ODE系统:
from sympy.parsing.sympy_parser import parse_expr
xs = symbols('x1 x2')
ks = symbols('k1 k2')
strs = ['-k1 * x1**2 + k2 * x2', 'k1 * x1**2 - k2 * x2']
syms = [parse_expr(item) for item in strs]
我想将这转换为一个向量值函数,接受一个一维numpy数组的x值,一个一维numpy数组的k值,返回一个在这些点上计算的方程的一维numpy数组。签名可能看起来像这样:
import numpy as np
x = np.array([3.5, 1.5])
k = np.array([4, 2])
xdot = my_odes(x, k)
我需要像这样做的原因是要将这个函数提供给
scipy.integrate.odeint
,所以它需要快速运行。
尝试1:subs
当然,我可以写一个subs
的包装器:def my_odes(x, k):
all_dict = dict(zip(xs, x))
all_dict.update(dict(zip(ks, k)))
return np.array([sym.subs(all_dict) for sym in syms])
但是这很慢,尤其是对于我的真实系统来说,它要大得多,并且需要多次运行。我需要将此操作编译为C代码。
尝试2:theano
我可以使用sympy与theano的集成接近目标:
from sympy.printing.theanocode import theano_function
f = theano_function(xs + ks, syms)
def my_odes(x, k):
return np.array(f(*np.concatenate([x,k]))))
这会编译每个表达式,但是所有输入和输出的打包和解包会减慢速度。由theano_function
返回的函数接受numpy数组作为参数,但它需要一个数组来表示每个符号,而不是每个符号的一个元素。这对于functify
和ufunctify
也是相同的行为。我不需要广播行为;我需要将数组的每个元素解释为不同的符号。
尝试3:DeferredVector
如果我使用DeferredVector
,我可以制作一个接受numpy数组的函数,但我无法将其编译为C代码或返回numpy数组,除非自己打包它。
import numpy as np
import sympy as sp
from sympy import DeferredVector
x = sp.DeferredVector('x')
k = sp.DeferredVector('k')
deferred_syms = [s.subs({'x1':x[0], 'x2':x[1], 'k1':k[0], 'k2':k[1]}) for s in syms]
f = [lambdify([x,k], s) for s in deferred_syms]
def my_odes(x, k):
return np.array([f_i(x, k) for f_i in f])
使用DeferredVector
,我不需要解包输入,但仍需打包输出。同时,我可以使用lambdify
,但ufuncify
和theano_function
版本将被淘汰,因此无法生成快速的C代码。
from sympy.utilities.autowrap import ufuncify
f = [ufuncify([x,k], s) for s in deferred_syms] # error
from sympy.printing.theanocode import theano_function
f = theano_function([x,k], deferred_syms) # error
subs
120毫秒,theano_function
8.7毫秒和lambdify
0.6毫秒。 - drhagenlambdafied_odes
中的调用转换为使用splatting[f(*np.concatenate([x,k])) for f in funcs]
,当状态数量变化时这是必要的,时间会稍微增加到1.4毫秒 - 仍然是最好的。 - drhagenautowrap
或ufuncify
。 - asmeurerufuncify
和autowrap
都需要对输入参数进行解包(如果您得到一个numpy数组)并重新打包输出值(如果您想要一个numpy数组)。 - drhagen