2

I am trying to learn how to integrate python SymPy with SciPy for numerically solving ordinary differential equations. However, I was kinda lost about how to actually get the SymPy form of a first order system of ODEs into a format that I can process with scipy.integrate.odeint()

Note, some people suggested that this is similar to another post, but that is not the case. The other post is here.
Convert sympy expressions to function of numpy arrays

So this other post is a much more complicated case where the user wants to accelerate computation of an ODE using Theano or some other libraries. I am just trying to understand the basic interface between SymPy and SciPy, so this other post is not at all helpful.

As a toy example, I was using the Lotka-Volterra equation to test the use of SymPy. The equations are:

\dot{x} = ax - bxy

\dot{y} = cxy - dy

I can solve this in the conventional way with Scipy and it works. Here is the working code.

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()

Now I was kind lost about how to do this with SymPy. I have a sense of what needs to be done, but was not sure how to proceed. The only example I have found is way too complicated to learn from. Here is what I have.

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)

I am supposed to put these equations into some sort of system form and then use the sympy.lambdify function to return a new function that I can pass to odeint

So can anyone fill in the blanks here on how I set up this ode1,ode2 system to process with SymPy.

krishnab
  • 9,270
  • 12
  • 66
  • 123
  • Is [this question](https://stackoverflow.com/questions/35430479/convert-sympy-expressions-to-function-of-numpy-arrays) a duplicate of yours? – Warren Weckesser Apr 11 '18 at 01:48
  • @Warren thanks for finding this additional question. I think the question you posted is a bit more advanced than what I am doing. I was trying to solve a relatively simple ODE to learn the API. In the question you posted the user is trying to use the GPU or such to accelerate the computation of the numerical solution. So I can use the stuff from that more complicated answer till I understand how to do this simpler one first. – krishnab Apr 11 '18 at 01:52
  • Possible duplicate of [Convert sympy expressions to function of numpy arrays](https://stackoverflow.com/questions/35430479/convert-sympy-expressions-to-function-of-numpy-arrays) – eyllanesc Apr 11 '18 at 01:52

2 Answers2

4

There is rarely need to use Eq objects in SymPy; equations are best represented by expressions, which are understood to be equated to zero in the context of solvers. So, ode1 and ode2 should just be expressions for the right-hand side of the ODE system. Then they can be lambdified together, as follows:

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)

The additional step after lambdification is needed because SciPy's solver passes some arrays, which the lambdified function will not know how to unpack. Example:

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

returns [-5, -2] which is a Python list rather than a NumPy array, but the ODE solver should handle that. (Or you can return np.array(F_sympy(x, y, a, b, c, d))).)

  • Excellent, I will give this a shot. Yeah, thanks for the `scipy` tips too. I have not found a good bunch of examples on this `sympy` to `scipy` connection. A lot of the examples I have found are just too complicated to initially learn from -- though they will be useful after I get more accustomed to this. Thanks for the help. – krishnab Apr 11 '18 at 03:27
3

I wrote a module named JiTCODE, which creates ODE integrator objects (with a handling similar to scipy.integrate.ode) from symbolic expressions (SymPy or SymEngine) describing the right-hand side. Under the hood it uses scipy.integrate.ode and scipy.integrate.solve_ivp for the integration.

The only drawback in your case is that the symbol for the dynamical variables and time is prescribed, so you might have to do a symbolic substitution – which should not be a big issue, however. Below, is your Lotka–Volterra equation as an example, using y(0) instead of x and y(1) instead of 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)

Note that the main feature of this module is that the right-hand side is compiled for efficiency. Depending on what you need to do, this may be overkill, but it doesn’t harm if it works (you can also disable this and use lambdification as detailed in the other answer).

Wrzlprmft
  • 4,234
  • 1
  • 28
  • 54
  • Oh @Wrzlprmft thanks so much for the answer. This is great, I can give JiTCODE a try. Actually, I have to do some work with delay differential equations as well, so your JiTCDDE module looks great as well. If I can have a consistent interface for both standard ODES and delay differential equations, then that is a tremendous help. Thanks again. – krishnab Apr 11 '18 at 16:00
  • Hey @Wrzlprmft Just a question I was not clear about. Does the user need to specify the Jacobian for the integration routine, or does JiTCODE produce that automatically? I saw that there is a `wants_jacobian` argument and `generate_jac_sym` method on the class. But I was not clear on whether I needed to specify the Jacobian or if that was handled automatically. Thanks. – krishnab Apr 11 '18 at 17:32
  • 1
    @krishnab: The Jacobian will be calculated automatically – if the integrator can make use of it. The things you mention just exist for fine tuning. You usually do not need to bother about this. – Wrzlprmft Apr 11 '18 at 17:46
  • Wow, that is great. Thanks so much for the package :). – krishnab Apr 11 '18 at 17:47