Numba: vectorize standard SciPy ufunc's and numpy.sum() syntax error

923 views Asked by At

I am relatively new to using numba, and I would like to use it to make my array calculations as efficient as possible. The function in question is a combination of several concepts in the numba documentation.

I am using a unitary function in the Scipy library

scipy.special.eval_laguerre(n, x, out=None) = <ufunc 'eval_laguerre'>

which evaluates a Laguerre polynomial L_n(x) at a point n.

Question 1: The Numba documentation clearly states how to use the decorator @vectorize to optimize a ufunc the user has written. http://numba.pydata.org/numba-doc/0.12/ufuncs.html#generalized-ufuncs

Is there a standard procedure to do this with ufunc provided by python libraries?

Question 2: I would like to evaluate L_n(x) for each entry of a matrix, for an array of n values in an array. I then must sum these values, using the expression:

result = np.sum( [eval_laguerre(n, matrix) for n in array], axis=0)

where I have used import numpy as np.

If I were to use broadcasting, I would instead evaluate:

result = np.sum( eval_laguerre( array[:, None, None], matrix ), axis=0)

where the axis=0 denotes which dimension to sum.

I would like to use '@jit' to compile this section, but I am unsure what the procedure is for 'numpy.sum(). At the moment, the above expression with the @jit expression gives a syntax error.

result = np.sum( eval_laguerre( array[:, None, None], matrix ), axis=0)
                                                                  ^
SyntaxError: invalid syntax

What is the correct way to use @jit and np.sum()?

EDIT: In response to @hpaulj:

My thought was numba could optimize the for loop, i.e.

for n in array: 
    eval_laguerre(n, matrix)

Is this possible at all? If not with numba, then with what? Pythran?

1

There are 1 answers

3
hpaulj On

Let's make this more concrete:

A sample array, which I'll use for both n and x (you can choose more realistic values):

In [782]: A=np.arange(12.).reshape(3,4)

The version, making full use of the ufunc broadcasting abilties

In [790]: special.eval_laguerre(A[:,None,:],A[None,:,:]).shape
Out[790]: (3, 3, 4)

Or summing:

In [784]: np.sum(special.eval_laguerre(A[:,None,:],A[None,:,:]),0)
Out[784]: 
array([[  3.00000000e+00,  -1.56922399e-01,  -4.86843034e-01,
          7.27719156e-02],
       [  1.37460317e+00,  -4.47492284e+00,   5.77714286e+00,
         -9.71780654e-01],
       [ -1.76222222e+01,   7.00178571e+00,   5.55396825e+01,
         -1.32810866e+02]])

equivalent with a list comprension inside the sum:

In [785]: np.sum([special.eval_laguerre(n,A) for n in A],0)
Out[785]: 
array([[  3.00000000e+00,  -1.56922399e-01,  -4.86843034e-01,
          7.27719156e-02],
       [  1.37460317e+00,  -4.47492284e+00,   5.77714286e+00,
         -9.71780654e-01],
       [ -1.76222222e+01,   7.00178571e+00,   5.55396825e+01,
         -1.32810866e+02]])

Or an explicit loop:

In [786]: x=np.zeros_like(A)    
In [787]: for n in A:
    x += special.eval_laguerre(n, A)

The last version has a chance of compiling with numba.

In simple time tests, the ufunc broadcasting is faster:

In [791]: timeit np.sum([special.eval_laguerre(n,A) for n in A],axis=0)
10000 loops, best of 3: 84.8 µs per loop

In [792]: timeit np.sum(special.eval_laguerre(A[:,None,:],A[None,:,:]),0)
10000 loops, best of 3: 43.9 µs per loop

My guess is that a numba version will improve on the comprehension version and the explicit loop, but probably not get faster than the broadcasting one.