1

First of all, please do excuse my poor use of programming terminology and bad (or even completely wrong) programming practices; i am still trying to find my feet :-)

In summary, i am trying to write a version of the multidimensional integration routine in Numerical Recipes, 3rd Edition, pgs. 198 - 199. in Cython, using the GSL numerical integration library. I have reproduced the relevant snippet from NR book below:

struct NRf3 {
double xsav, ysav;
double (*func3d)(const double, const double, const double);
double operator()(const double z)
{
    return func3d(xsav, ysav, z);
}
    }

struct NRf2 {
    NRf3 f3;
    double (*z1)(double, double);
    double (*z2)(double, double);
    NRf2(double zz1(double, double), double zz2(double, double)):    z1(zz1), z2(zz2)} {}
    double operator()(const double y)
    {
        f3.ysav = y;
        return qgaus(f3, z1(f3.xsav, y), z2(f3.xsav, y));
    }
}

struct NRf1 {
    double (*y1)(double);
    double (*y2)(double);
    NRf2 f2;
    NRf1(double yy1(double), yy2(double), z1(double, double), z2(double,  double)) : y1(yy1), y2(yy2), f2(z1, z2) {}
    double operator()(const double x){
        f2.f3.xsav = x;
        return qgaus(f2, y1(x), y2(x));
    }
}

template <class T>
double quad3d(T &func, const double x1, const double x2, double     y1(double), double y2(double), double z1(double, double), double z2(double, double))
{
    NRf1 f1(y1, y2, z1, z2);
    f1.f2.f3.func3d = func;
    return qgaus(f1, x1, x2)
}

This script does applies a previously written integration routine qgaus (which I will later substitute with a routine from GSL) which takes a double function and two doubles representing the integration limits as input parameters.

Mathematically, the code performs the integration as displayed in Eq. (4.6.2) of this link. The integration limits y[i] and z[i] have (x) and (x,y) dependencies, respectively, because the integration runs over a predefined region in the (x,y) plane (see see Fig. 4.6.1 of the same link above).

My own attempt in Cython is below (note my problem has, i think, nothing to do with interfacing to GSL, but is still at the level of correct Cythoning):

from cython_gsl cimport *
from numpy import *

ctypedef double * double_ptr
ctypedef void * void_ptr

cdef double sigma = 0.045
cdef double mu = 0.37

## Test : a test function in x, y, z
    cdef double gauss(double x, double y, double z, void * params) nogil:
    cdef double mu = (<double_ptr> params)[0]
    cdef double sigma = (<double_ptr> params)[1]
    return mu * x + sigma * y + z

cdef class NRf3:
    cdef double xsav, ysav
    cdef double (*func3d)(double x, double y, double z, void * params)

    def __init__(self, xsav, ysav):
        self.xsav = xsav
        self.ysav = ysav

    def func(self, double z):
        xsav = self.xsav
        ysav = self.ysav
        return func3d(xsav, ysav, z, void * params)

## Calling the gsl integrator qags
## Function: int gsl_integration_qags (const gsl_function * f, double a,     double b, double epsabs, double epsrel, size_t limit,     gsl_integration_workspace * workspace, double * result, double * abserr)

cdef class NRf2:
    cdef NRf3 f3
    cdef double z1
    cdef double z2
    cdef double paramsf2[1]
    cdef size_t nevalf2

    def __init__(self, double z1, double z2):
        self.z1 = z1
        self.z2 = z2

    def __call__(self, double y):
        cdef double resultf2, errorf2
        cdef gsl_integration_workspace * W
        W = gsl_integration_workspace_alloc(1000)
        cdef gsl_function F2
        z1 = self.z1
        z2 = self.z2

        f3.ysav = y
        f3_temp = f3(f3.xsav, y)
        F2.function = f3_temp
        F2.params = self.paramsf2
        gsl_integration_qags(&F2, z1, z2, 1e-2, 1e-2, 1000, W, &resultf2, &errorf2) # this line calls the GSL routine
        gsl_integration_workspace_free(W) 
        return resultf2

cdef class NRf1:
    cdef NRf2 f2
    cdef double y1
    cdef double y2
    cdef double paramsf1[1]
    cdef size_t nevalf1

    def __init__(self, double yy1, double yy2, double z1, double z2):
        self.y1 = yy1
        self.y2 = yy2
        self.z1 = z1
        self.z2 = z2
        self.f2 = f2(self.z1, self.z2)

    def __call__(self, func, double x):
        cdef double result, error
        cdef gsl_integration_workspace * W
        W = gsl_integration_workspace_alloc(1000)
        cdef gsl_function F1
        f2 = self.f2

        f2.f3.func3d = func
        f2.f3.xsav = x
        y1 = self.y1
        y2 = self.y2
        F1.function = f2
        F1.params = self.paramsf1
        gsl_integration_qags(&F1, y1, y2, 1e-2, 1e-2, 1000, W, &result, &error)
        gsl_integration_workspace_free(W)
        return result

Compiling this script with the usual

python min_example.py build_ext --inplace

as found on the Cython website gives the following errors:

Error compiling Cython file:
------------------------------------------------------------ ...

        z1 = self.z1
        z2 = self.z2

        f3.ysav = y
        f3_temp = f3(f3.xsav, y)
        F2.function = f3_temp
                            ^
------------------------------------------------------------

min_example.pyx:61:29: Cannot convert Python object to 'double
(*)(double, void *) nogil'

Error compiling Cython file:
------------------------------------------------------------ ...

        f2.f3.func3d = func
        f2.f3.xsav = x
        y1 = self.y1
        y2 = self.y2
        F1.function = f2
                       ^
------------------------------------------------------------

min_example.pyx:92:24: Cannot convert Python object to 'double
(*)(double, void *) nogil'

Errors refer to the lines F2.function = f3_temp and F1.function = f2 respectively. In this example gsl_integration_qags is a library from gsl, but for the purposes of this problem the correctness of the interfacing does not matter as (I think) the problem lies with the incorrect Cython syntax I have used.

My intuition tells me that this is a very trivial novice error, but I have been unable to find the reason. Any input (on the problem, on my code in general) would be very welcome.

Edit 1 : to reflect actual error messages

Edit 2 : Edited title to reflect nature of error messages

Edit 3 : I should say that i understand that the error comes from me trying to assign a Python instance object, i.e., in the lines F2.function = f3_temp and F1.function = f2, to a struct member that is a function taking two arguments. As this seems like a fairly trivial porting of code from C to Cython, and assuming that one sticks closely to the original C implementation, i am just wondering how would one write those lines correctly.

Edit 4: A bit of searching turns up this thread. I think my confusion can be distilled in the same way: How do I pass a Python class member (which if i have instantiated it correctly) to a C function ?

Community
  • 1
  • 1
Zhong Yuan Lai
  • 123
  • 1
  • 6
  • Note that the respective lines in the code you've given are slightly different than what the error shows: your code shows `F2.function = f3_temp` instead of `F2.function = f3_temp.func`. That can be overlooked, but it does show (to me at least) that you didn't properly copy-paste things (and made a self-contained example), which means there could be other copy-paste errors in the code. –  Aug 22 '15 at 11:16
  • 1
    Overall, the issue *might* be that you're trying to assing a instance method to, what I think is, a function (because of the line `from cython_gsl cimport *`, I have no idea about `F2.function`. This may cause problems with the use of `self` later on, if not already at this stage. Is there a way you can assign a *function* (with the proper signature) to `F2.function`? –  Aug 22 '15 at 11:18
  • @Evert Thank you for the comment. I have rerun the most current code and posted the actual error messages. As you said, it turns out that the discrepancy (at least for this problem) can be overlooked as the errors are the same. – Zhong Yuan Lai Aug 22 '15 at 14:02
  • @Evert As to your last points, the line `F2.function` reflects the way that functions are written to the gsl integration library (see [here](https://www.gnu.org/software/gsl/manual/html_node/Numerical-integration-examples.html#Numerical-integration-examples)) and it is defined in such a way because `gsl_function` is a `struct` with the form `struct gsl_function_struct { double (* function) (double x, void * params); void * params; };`. I _can_ assign, for example, the Gaussian function directly to `F2.function`, but as i need to call the integration routine repeatedly, i can't hard-code. – Zhong Yuan Lai Aug 22 '15 at 14:10
  • Typically `numpy` is imported to `cython` with: `import numpy as np` and `cimport numpy as cnp` (the last could also be `as np`. It keeps the identity of `numpy` calls clear. The `cimport` may be significant to your case. – hpaulj Aug 22 '15 at 16:29
  • 1
    I don't get your last point ("I can't hard-code"), but from what I can see, you're now attempting to assign a class to a function declaration, in both cases. I.e., you're assigning a `Python object` (class) to a `double (*)(double, void *)` function. –  Aug 23 '15 at 00:45
  • @Evert Sorry for the confusion, what i meant was that since this is a multidimensional integration routine, i.e., for a function of x, y, z, i apply a 1D integration routine on those 3 variables in turn, and at each of those calls the form of the function is different (since they have one variable 'less'), i cannot hard-code my function (by calling, for example, `F2.function = some_valid_func`, where `some_valid_func` is a pre-defined function that gsl takes directly). This is the reason that the functions are coded as members of `structs`, as they vary with each call to the routine. – Zhong Yuan Lai Aug 23 '15 at 13:44

0 Answers0