SciPy ODE Integration

SciPy ODE Integration#

The python SciPy library includes a lot of different types of ODE integrators, all available through the solve_ivp() interface.

To work on our system, we need to provide a righthand side function of the form:

rhs(t, y)

where y is the vector of unknowns. We’ll assume it is ordered as \((x, y, u, v)\)

import numpy as np
import matplotlib.pyplot as plt
from scipy import integrate
GM = 4.0 * np.pi**2

def rhs(t, yvec):
    x, y, u, v = yvec
    
    dxdt = u
    dydt = v
    
    r = np.sqrt(x * x + y * y)
    
    dudt = -GM * x / r**3
    dvdt = -GM * y / r**3
    
    return np.array([dxdt, dydt, dudt, dvdt])

Let’s setup the initial conditions

def initial_conditions(a, e):
    x0 = 0
    y0 = a * (1 - e)
    u0 = -np.sqrt(GM / a * (1 + e)/(1 - e))
    v0 = 0
    
    return np.array([x0, y0, u0, v0])

Now we can integrate. We have a choice of solvers:

https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html

We’ll pick RK45, which is the default

a = 1.0
e = 0.3

tmax = 10.0

yvec0 = initial_conditions(a, e)
sol = integrate.solve_ivp(rhs, (0, tmax), yvec0, method="RK45")

The data are returned via the sol object. The two bits we care about the most are t and y, the latter which is an (neqs, nsteps) array.

Since all the data for a single variable is together (row-major storage), this is a struct-of-arrays type layout.

t = sol.t
yvec = sol.y
yvec.shape
(4, 99)
fig, ax = plt.subplots()

ax.plot(yvec[0,:], yvec[1,:])
ax.set_aspect("equal")
../_images/f0a248666d7bba507c83747e8e4b4bfa3015fbc1a48d33c7f810189e05166ce6.png

We see that this solution does not look great.

Tip

solve_ivp takes both a relative and absolute tolerance, and their defaults are rtol=1.e-3, atol=1.e-6.

They are combined into an error tolerance of \(\mathtt{atol} + \mathtt{rtol} |y|\).

Tighter tolerances#

Let’s make the tolerances tighter

sol = integrate.solve_ivp(rhs, (0, tmax), yvec0, method="RK45", rtol=1.e-6)
t = sol.t
yvec = sol.y

fig, ax = plt.subplots()

ax.plot(yvec[0,:], yvec[1,:])
ax.set_aspect("equal")
../_images/87de41459a89a73bf75233f4c587a256efd68cd6e28b56c2409cee3c36a0fc91.png

This looks much better.