I am trying to optimize this code for solving a system of ODE. It seems Cython
does not speed up the code compared to code using numpy
.
This the python code using numpy
:
"""Solve system of ODEs."""
def solver(ode_sys, I, t, integration_method):
N = len(t)-1
u = np.zeros((N+1, len(I)))
u[0, :] = I
dt = t[1] - t[0]
for n in range(N):
u[n+1, :] = integration_method(u[n, :], t[n], dt, n, ode_sys)
return u, t
def RK2(u, t, dt, n, ode_sys):
K1 = dt * ode_sys(u, t)
K2 = dt * ode_sys(u + 0.5 * K1, t + 0.5 * dt)
unew = u + K2
return unew
def problem1(u, t):
return -u + 1.0
from numpy import exp
def problem2(u, t):
return - u + exp(-2.0 * t)
to run the code:
def run(ode_sys, N, nperiods=40):
I = np.ones(N)
time_points = np.linspace(0, nperiods * 2 * np.pi, nperiods * 30 + 1)
u, t = solver(ode_sys, I, time_points, RK2)
timing
%timeit run(problem1, 1000, 1000)
%timeit run(problem2, 100, 500)
418 ms ± 22.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
233 ms ± 79 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
My Cython
version:
I directly call the functions inside the module to get more speedup
%%cython --annotate
import numpy as np
cimport numpy as np
cimport cython
ctypedef np.float_t DT
cdef extern from "math.h":
double exp(double)
@cython.boundscheck(False) # turn off bounds checking for this func.
@cython.wraparound(False) # Deactivate negative indexing.
cpdef solver(ode_sys, np.ndarray[DT, ndim=1, negative_indices=False, mode='c'] I,
np.ndarray[DT, ndim=1, negative_indices=False, mode='c'] t,
integration_method):
cdef int N = len(t)-1
cdef np.ndarray[DT, ndim=2, negative_indices=False,
mode='c'] u = np.zeros((N+1, len(I)))
u[0, :] = I
cdef double dt = t[1] - t[0]
cdef int n
for n in range(N):
u[n+1, :] = RK2(u[n, :], t[n], dt, n, ode_sys)
return u, t
def RK2(np.ndarray[DT, ndim=1, negative_indices=False, mode='c'] u,
double t, double dt, int n, ode_sys):
cdef np.ndarray[DT, ndim=1, negative_indices=False, mode='c'] K1, K2, unew
K1 = dt * problem1(u, t)
K2 = dt * problem1(u + 0.5 * K1, t + 0.5 * dt)
unew = u + K2
return unew
cdef problem1(np.ndarray[DT, ndim=1, negative_indices=False, mode='c'] u, double t):
return -u + 1.0
cdef problem2(np.ndarray[DT, ndim=1, negative_indices=False, mode='c'] u, double t):
return - u + exp(-2.0 * t)
timing:
%timeit run(problem1, 1000, 1000) # note that to check promlem2 I need to change it inside the modules
424 ms ± 8.23 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Thanks in advance for any guide.