# How can I return a C struct with numba?

Hi,

I’m using numba in conjunction with scipy.LowLevelCallable to pass small callables to numerical integrators.

The following MWE works as expected. It allows me to jit a parametrized function and return the result as a scipy.LowLevelCallable

``````
import cffi
import numba
from numba.core.typing import cffi_utils
import scipy

numba_kwargs = {
'nopython':True     ,
'cache':True        ,
'fastmath':True     ,
'nogil':True        ,
}

src = """
typedef  double (*fun_t)(double) ;
"""

ffi = cffi.FFI()
ffi.cdef(src)

sig = cffi_utils.map_type(ffi.typeof('fun_t'), use_record_dtype=True)

def param_fun(n):

@numba.cfunc(sig)
@numba.jit(signature=sig, **numba_kwargs)
def fun(x):
return n*x

return scipy.LowLevelCallable(fun.ctypes)

toto = param_fun(0.5)
``````

Now, how can I do the same, but returning a C struct ?

``````src = """
typedef struct {
double a;
} a_t;

typedef a_t (*fun_t)(double) ;
"""

ffi = cffi.FFI()
ffi.cdef(src)

sig = cffi_utils.map_type(ffi.typeof('fun_t'), use_record_dtype=True)

def param_fun(n):

@numba.cfunc(sig)
@numba.jit(signature=sig, **numba_kwargs)
def param_fun(x):
# ?????

return n*x

return scipy.LowLevelCallable(param_fun.ctypes)
``````

Hey @gabrielfougeron ,

If you intend to pass only scalars as additional arguments for a SciPy quadrature, you can define your C structure using `make_c_struct` and utilize the `LowLevelCallable` function.

``````import numpy as np
import numba as nb
from numba import types
from scipy import integrate, LowLevelCallable
import ctypes

# Define the C-struct
args_dtype = types.Record.make_c_struct([
('x2', types.float64),
])

# function to integrate
def integrand(x1, x2):
return np.exp(-x1/x2) / x1**2

# function factory
def create_jit_integrand_function(integrand_function, args_dtype):
jitted_function = nb.njit(integrand_function)

# double func(double x, void *user_data)
@nb.cfunc(types.float64(types.float64, types.CPointer(args_dtype)))
def wrapped(x1, user_data_p):
user_data = nb.carray(user_data_p, 1)
x2 = user_data[0].x2
return jitted_function(x1, x2)
return wrapped

# wrapped function call
def do_integrate(func, args, a=0, b=1):
integrand_func = LowLevelCallable(func.ctypes, user_data=args.ctypes.data_as(ctypes.c_void_p))
# quadrature(func, a, b, args=(), tol=1.49e-8, rtol=1.49e-8, maxiter=50, vec_func=True, miniter=1)
return integrate.quad(integrand_func, a, b)

# How to use it:
x2 = 1.0
args = np.array((x2,), dtype=args_dtype)
func = create_jit_integrand_function(integrand, args_dtype)
print(do_integrate(func, args, a=1, b=np.inf))
# (0.14849550677592208, 3.8736750296130505e-10)
``````

If you need to define arrays as arguments, there is an example by @max9111

Thanks, I did not know about `types.Record.make_c_struct`