1

I am trying to create a python interface to a C function with the following structure: (Full code can be found here)

void get_pi_typed (int *type,
           double *x,
           double *y,
           int *len,
           int *typeA,
           int *typeB,
           double *r_low,
           double *r,
           int *len_r,
           int *inds,
           double *rc) {

\*DETAILS LEFT OUT

for (i=0;i<*len_r;i++) {

    \*DETAILS LEFT OUT

    rc[i] = (double)num_cnt/denom_cnt;
    }
}

My Python code looks like this:

import numpy as np
import ctypes as ct


# must be a double array, with single dimension that is contiguous
array_1d_int = np.ctypeslib.ndpointer(dtype=np.int32, ndim=1, flags='CONTIGUOUS')
array_1d_double = np.ctypeslib.ndpointer(dtype=np.double, ndim=1, flags='CONTIGUOUS')

# Load the library as _libspfc.
_libspfc = np.ctypeslib.load_library('../src/libspatialfuncs', '.')

_libspfc.get_pi_typed.argtypes = [array_1d_int,\
                                    array_1d_double,\
                                    array_1d_double,\
                                    ct.c_int,\
                                    ct.c_int,\
                                    ct.c_int,\
                                    array_1d_double,\
                                    array_1d_double,\
                                    ct.c_int,\
                                    ct.c_int,\
                                    array_1d_double,\
                                    ]

_libspfc.get_pi_typed.restype  = None

def getPiTyped(posmat,typeA=-1,typeB=-1,r=np.array([1.]),rLow=None):
    """
    Python equivalent to get_pi_typed.

    posmat:  a matrix with columns type, x and y
    typeA:   the "from" type that we are interested in, -1 is wildcard
    typeB:   the "to" type that we are interested i, -1 is wildcard
    r:       the series of spatial distances wer are interested in
    rLow:    the low end of each range....0  by default
    """

    if not isinstance(r, np.ndarray): #if it is not a 1D numpy array (for ex a scalar or a list), bring it into that shape
        r=np.array(r)
        r=r.reshape((-1))

    if rLow is None:
        rLow = np.zeros_like(r)

    if not isinstance(rLow, np.ndarray): #if it is not a 1D numpy array (for ex a scalar or a list), bring it into that shape
        rLow=np.array(rLow)
        rLow=rLow.reshape((-1))


    #prepare output array
    rc = np.empty_like(r, dtype=np.double)

    _libspfc.get_theta_typed(posmat[:,0],posmat[:,1],posmat[:,2],posmat.shape[0],typeA,typeB,rLow,r,r.shape[0],np.arange(1,r.shape[0]+1),rc)

    return rc

However, when I try to run the code I get the following error, which seems to be related to the type conversion of the 1st parameter:

x =np.array([[1.,0.,0.],[1.,1.,0.],[2.,0.5,np.sqrt(.75)]])
sf.getPiTyped(x,1,2,1.5)

ArgumentError: argument 1: <type 'exceptions.TypeError'>: Don't know how to convert parameter 1

I tried many variations of argtypes, as well as to convert posmat[:,0] to int or int32 via .astype, however I always get the same error. What am I doing wrong?

EDIT: According to the 1st comment below I added .ctypes.data to all array input arguments. The ArgumentError is now gone. However I get a Segmentation Fault, very difficult to investigate because python crashes

EDIT2: I tried to make the array column-contiguous

posmat=np.ascontiguousarray(np.asfortranarray(posmat))

but I still get the seg fault

  • Does [this answer](http://stackoverflow.com/a/5863539/3005167) help? If I remember correctly ctypes arrays are pointser. Have you tried passing the arrays as `posmat[:, 0].ctypes.data` to the c function? – MB-F Jan 25 '16 at 15:44
  • Just tried to add `.ctypes.data` to all numpy array parameters. The ArgumentError is now gone, however I get a SegmentationFault. –  Jan 25 '16 at 16:00
  • Well, then that's a step in the right direction, probably. Try to transpose `x` and pass rows instead of columns. The c function is likely to expect contiguous arrays. Btw, note that I have not done this before and just taking guesses in the dark :) – MB-F Jan 25 '16 at 16:03
  • Thanks for your help! I tried to make the array column-contiguous `posmat=np.ascontiguousarray(np.asfortranarray(posmat))`, but I still get the seg fault –  Jan 25 '16 at 16:14
  • You've told ctypes that some arguments are type `c_int`, but the actual arguments of the C function are *pointers* to ints. That could certainly trigger a segfault. – Warren Weckesser Jan 25 '16 at 16:49
  • Yes, just noticed that. But I can not find out how to tell python that it should pass pointers instead of values. –  Jan 25 '16 at 16:53
  • See if this helps: https://docs.python.org/2/library/ctypes.html#passing-pointers-or-passing-parameters-by-reference – Warren Weckesser Jan 25 '16 at 16:54

1 Answers1

1

The error was highlighted by Warren above, the int arguments had to be passed by reference. Note also that the arrays have to be contiguous. Here is the final code:

import numpy as np
import ctypes as ct

# Load the library as _libspfc.
_libspfc = np.ctypeslib.load_library('../src/libspatialfuncs', '.')

def getPiTyped(posmat,typeA=-1,typeB=-1,r=np.array([1.]),rLow=None):
    """
    Python equivalent to get_pi_typed.

    posmat:  a matrix with columns type, x and y
    typeA:   the "from" type that we are interested in, -1 is wildcard
    typeB:   the "to" type that we are interested i, -1 is wildcard
    r:       the series of spatial distances wer are interested in
    rLow:    the low end of each range....0  by default
    """

    #prepare inputs

    # argument 1 to 3: make a copy, so the matrix is C contiguous (already included in astype)
    ty=posmat[:,0].astype(np.int32) 
    x=posmat[:,1].copy()
    y=posmat[:,2].copy()

    n = ct.c_int(posmat.shape[0])
    typeA = ct.c_int(typeA)
    typeB = ct.c_int(typeB)

    if not isinstance(r, np.ndarray): #if it is not a 1D numpy array (for ex a scalar or a list), bring it into that shape
        r=np.array(r)
        r=r.reshape((-1))

    if rLow is None:
        rLow = np.zeros_like(r)

    if not isinstance(rLow, np.ndarray): #if it is not a 1D numpy array (for ex a scalar or a list), bring it into that shape
        rLow=np.array(rLow)
        rLow=rLow.reshape((-1))

    rLen=ct.c_int(r.shape[0])
    ind=np.arange(1,r.shape[0]+1,dtype=np.int32)

    #prepare output array
    rc = np.empty_like(r, dtype=np.double)

    _libspfc.get_pi_typed(ty,\
                            x,\
                            y,\
                            ct.byref(n),\
                            ct.byref(typeA),\
                            ct.byref(typeB),\
                            rLow,\
                            r,\
                            ct.byref(rLen),\
                            ind,\
                            rc)
    return rc