I have a system of ODEs written in sympy:
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]
I would like to convert this into a vector valued function, accepting a 1D numpy array of the x value, a 1D numpy array of the k values, returning a 1D numpy array of the equations evaluated at those points. The signature would look something like this:
import numpy as np
x = np.array([3.5, 1.5])
k = np.array([4, 2])
xdot = my_odes(x, k)
The reason I want something like this is to give this function to scipy.integrate.odeint
, so it needs to be fast.
Attempt 1: subs
Of course, I can write a wrapper around 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])
But this is super slow, especially for my real system which is much bigger and is run many times. I need to compile this operation to C code.
Attempt 2: theano
I can get close with sympy's integration with 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]))))
This compiles each expression, but all this packing and unpacking of the inputs and outputs slows it back down. The function returned by theano_function
accepts numpy arrays as arguments, but it needs one array for each symbol rather than one element for each symbol. This is the same behavior for functify
and ufunctify
as well. I don't need the broadcast behavior; I need it to interpret each element of the array as a different symbol.
Attempt 3: DeferredVector
If I use DeferredVector
I can make a function that accepts numpy arrays, but I can't compile it to C code or return a numpy array without packaging it myself.
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])
Using DeferredVector
I do not need to unpack the inputs, but I still need to pack the outputs. Also, I can use lambdify
, but the ufuncify
and theano_function
versions perish, so no fast C code is generated.
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
See Question&Answers more detail:
os