Search code examples
pythoncscipynumerical-integration

How to use scipy.integrate.nquad and passing arguments to it while using LowLevelCallable cfunc


I'm quite desperate trying to implement an integration which result I wanna use as initial data for a PDE. The integral itself is scalar-valued and needs to be evaluated on the whole space $\mathbb{R}^3$. First of all, I do not expect a very fast super easy integration but hope that it can be done in a reasonable amount of time. Therefore I decided to use nquad in connection the LowLvelCallable option. I'm trying to do the implementation of my code in Python.

The LowLevelCallable option of scipy calls cfuncs with less overhead to save time in each individual integration step. Now I found a sample implementation for my purpose where quad is used instead of nquad, Sample. As far as I understood the idea of this sample is it to use a decorator to "trick" the signature of quad which demands an input of a cfunc in the form of sig = (types.CPointer(types.double), types.CPointer(types.void)) to bypass the problem that there isn't a possibility to extract data from a viod type when the code is written in Python.

That works pretty well, but now I need to adapt this approach for nquad nothing should change in principle, the only difference is that nquad demands a signature of the form sig=(types.intc, types.CPointer(types.double), types.CPointer(types.void)). Unfortunately, it turns out that just to edit the signature of the cfunc doesn't do the trick, and therefore without any background in high-performance computing and in C this turned into a very nasty try and error game. My hope is that someone can help me out and saying me what my mistake is.

Here is the sample of my code

import numpy as np
import numba as nb
from numba import types
from scipy import integrate, LowLevelCallable
import ctypes
from math import sqrt, exp

#define some constants
c = 137
sigma = 10
p_boost = 100
m = 1
gamma = sqrt(1 + p_boost ** 2 / c ** 2)
beta_x = p_boost / (gamma * m * c)

x = np.linspace(-4, 4, 32, endpoint = False)
y = np.linspace(-4, 4, 32, endpoint = False)
z = np.linspace(-4, 4, 32, endpoint = False)

#define arg data type
args_dtype = types.Record.make_c_struct([
    ('x_coord', types.float64),
    ('y_coord', types.float64),
    ('z_coord', types.float64),])


def create_jit_integrand_function(integrand_function,args,args_dtype):
    jitted_function = nb.njit(integrand_function)

    @nb.cfunc(types.intc,types.float64(types.float64,types.CPointer(args_dtype)))
    def wrapped(n,x1,user_data_p):
        #Array of structs
        user_data = nb.carray(user_data_p, 1)

        #Extract arg data
        x_co=user_data[0].x_co
        y_co=user_data[0].y_co
        z_co=user_data[0].z_co

        return jitted_function(n,x1, x_co, y_co, z_co)
    return wrapped

def function_using_arrays(n ,p, x_co, y_co, z_co):
    p_square = 0.0
    for i in range(n):
        p_square += p[i]**2
    rel_energy = sqrt(p_square + 1)
    px_new = beta_x * gamma * rel_energy / c + gamma * p[0]
    p_square_new =  px_new**2 + p[1]**2 + p[2]**2
    rel_energy_new = sqrt(p_square_new +1)
    return exp(-p_square_new/(2*sigma**2) - 1j * (p[0]*x_co + p[1]*y_co + p[2]*z_co)) * (rel_energy_new - beta_x * p[boost_index] + 0j)**(1/2) * rel_energy_new ** (-1)


def jit_with_dummy_data(args,args_dtype):
    func=create_jit_integrand_function(function_using_arrays,args,args_dtype)
    return func

def do_integrate_w_arrays(func,args,ranges):
    integrand_func=LowLevelCallable(func.ctypes,user_data=args.ctypes.data_as(ctypes.c_void_p))
    return integrate.nquad(integrand_func, ranges)


output = np.zeros((len(x),len(y),len(z)), dtype=complex)
for i in range(len(x)):
    for j in range(len(y)):
        for k in range(len(z)):
            #creating args
            x_co = x[i]
            y_co = y[j]
            z_co = z[k]
            #hand over args
            args=np.array((x_co, y_co, z_co),dtype=args_dtype)
            func=jit_with_dummy_data(args,args_dtype)
            #perform integration
            ranges = ((-np.inf, np.inf), (-np.inf, np.inf), (-np.inf, np.inf))
            output[i,j,k] = do_integrate_w_arrays(func,args,ranges)[0]


Solution

  • Unfortunately, it turns out that just to edit the signature of the cfunc doesn't do the trick, … My hope is that someone can help me out and saying me what my mistake is.

    The mistake was to insert the additional integer parameter before the function signature instead of into the parameter list; correct:

        @nb.cfunc(types.float64(types.intc, types.float64, types.CPointer(args_dtype)))
    

    This correction reveals a naming mismatch in wrapped; correct:

            x_co=user_data[0].x_coord
            y_co=user_data[0].y_coord
            z_co=user_data[0].z_coord
    

    That correction allows the program to proceed to the next error:

    NameError: name 'boost_index' is not defined
    

    boost_index is used at the end of function_using_arrays; you have to see where this value shall come from.