Search code examples
pythonarraysscipynumbanumerical-integration

How to use numba for speeding up integration using values from array to redefine the integral


I have tried to get a faster computation of integrals using python numba. Even though the timing using numba is almost 10 times faster for a single computation, when I loop the process for redefining the integral, it becomes awfully slow. I have tried using other decorators like @vectorize or @jit but with no success. Any tips on how to do it?

import numpy as np
import datetime as dd
from scipy.integrate import quad
from numba import cfunc, types, carray
tempText = 'Time Elapsed: {0:.6f} sec'
arr = np.arange(0.01,1.01,0.01)
out = np.zeros_like(arr)
def tryThis():           # beginner's solution
    for i in range(len(arr)):
        def integrand(t):
            return np.exp(-arr[i]*t)/t**2
        def do_integrate(func):
            return quad(func,1,np.inf)[0]
        out[i] = do_integrate(integrand)
    # print (out)
init = dd.datetime.now()
tryThis()
print (tempText.format((dd.datetime.now()-init).total_seconds()))

Time Elapsed: 0.047950 sec

def try2VectorizeThat(): # using numpy
    def do_integrate(arr):
        def integrand(t):
            return np.exp(-arr*t)/t**2
        return quad(integrand,1,np.inf)[0]
    do_integrate = np.vectorize(do_integrate)
    out = do_integrate(arr)
    # print (out)
init = dd.datetime.now()
try2VectorizeThat()
print (tempText.format((dd.datetime.now()-init).total_seconds()))

Time Elapsed: 0.026424 sec

def tryThisFaster():    # attempting to use numba
    for i in range(len(arr)):
        def get_integrand(*args):
            a = args[0]
            def integrand(t):
                return np.exp(-a*t)/t**2
            return integrand
        nb_integrand = cfunc("float64(float64)")(get_integrand(arr[i]))
        def do_integrate(func):
            return quad(func,1,np.inf)[0]
        out[i] = do_integrate(nb_integrand.ctypes)
    # print (out)
 init = dd.datetime.now()
tryThisFaster()
print (tempText.format((dd.datetime.now()-init).total_seconds()))

Time Elapsed: 1.905140 sec


Solution

  • Note that you are measuring time for assigning variables and defining function included.

    Also, numba can become (or seem) slower when the job is too small, as it needs time to compile itself and then applied.

    Placing integrand outside the loop and decorating with @njit can give you some performance boost. Let's see some comparisons:

    from numba import njit
    @njit
    def integrand(t, i):
        return np.exp(-arr[i]*t)/t**2
    
    def tryFaster():     
        for i in range(len(arr)):
            out[i] = quad(integrand, 1, np.inf, args=(i))[0]
    

    Time taken when len(arr) = 100:

    arr = np.arange(0.01,1.01,0.01)
    
    %timeit tryThis()
    # 29.9 ms ± 4.59 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    %timeit tryFaster()
    # 4.99 ms ± 11.5 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    Time taken when len(arr) = 10,000:

    arr = np.arange(0.01,100.01,0.01)
    
    %timeit tryThis()
    # 1.43 s ± 208 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    %timeit tryFaster()
    # 142 ms ± 17.7 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)