Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Numpy array interface with ctypes function

I'm trying to interface a shared C library to some python code. The interface with the library is something like

typedef struct{
    int v1;
    double* v2} input;

There are two other types like this one: for configuration and the output type.

I set up these structs in python using ctypes Structures like this:

class input(Structure):
    _fields_ = [("v1",c_int),("v2",POINTER(c_double)]

The C code have some functions that receive a pointer to that structure and the argtypes are defined as in:

fun.argtypes = [constraints,input,POINTER(input)]

constraints is another structure with some int fields for configuration.

First I update the v2 field in the input struct

input.v2 = generated_array.ctypes.data_as(POINTER(c_double))

Then I call it:

fun(constraints,input,byref(output))

The function prototype asks for struct and * to struct (output struct type is assumed to be the same as the input struct type).

Then I want to access the results of fun stored in the v2 field of output. But I'm getting unexpected results. Is there a better/correct way of do this?

I searched a lot here and read the documentation but I can't find what's wrong. I don't have any error messages, but the warnings that I receive from the shared lib seems to show that there are mistakes on these interfaces.


I guess I have found the problem:

When I call the method a numpy array of complex is called. Then I create 4 vectors:

    out_real = ascontiguousarray(zeros(din.size,dtype=c_double))
    out_imag = ascontiguousarray(zeros(din.size,dtype=c_double))
    in_real  = ascontiguousarray(din.real,dtype = c_double)
    in_imag  = ascontiguousarray(din.imag,dtype = c_double) 

where din is the input vector. I had tested the method this way:

    print in_real.ctypes.data_as(POINTER(c_double))    
    print in_imag.ctypes.data_as(POINTER(c_double))
    print out_real.ctypes.data_as(POINTER(c_double))
    print out_imag.ctypes.data_as(POINTER(c_double))

and the result was:

    <model.LP_c_double object at 0x1d81f80>
    <model.LP_c_double object at 0x1d81f80>
    <model.LP_c_double object at 0x1d81f80>
    <model.LP_c_double object at 0x1d81f80>

It seems that all of them are pointing to the same place.

After some changes, it works as expected...


After several tests I found that the code was nearly correct the first time. I was creating the Structure instance once and updating its fields. I changed to creating a new instance at each call of fun. I also changed all arrays types to be the equivalent ctypes type; this seemed to make the function work as expected.

The print behavior still holds as in the test above, but the function seems to work even with this strange behavior. It is correct as pointed by @ericsun comment below.

like image 943
Euripedes Rocha Filho Avatar asked Mar 14 '13 21:03

Euripedes Rocha Filho


1 Answers

The struct has an int field that's possibly for the array length, though I'm just guessing without the complete function prototype. If this is indeed the case, here's an example that may help.

First, I need to compile a test function in a shared library. I'll simply multiply the input array by 2:

import os
import numpy as np
from ctypes import *

open('tmp.c', 'w').write('''\
typedef struct {
    int v1; double *v2;
} darray;
int test(darray *input, darray *output) {
    int i;
    /* note: this should first test for compatible size */
    for (i=0; i < input->v1; i++)
        *(output->v2 + i) = *(input->v2 + i) * 2;
    return 0;
}
''')
os.system('gcc -shared -o tmp.so tmp.c')

Next create the ctypes definitions. I added a classmethod to make a darray from a numpy.ndarray:

c_double_p = POINTER(c_double)

class darray(Structure):
    _fields_ = [
      ('v1', c_int),
      ('v2', c_double_p),
    ]
    @classmethod
    def fromnp(cls, a):
        return cls(len(a), a.ctypes.data_as(c_double_p))

lib = CDLL('./tmp.so')
lib.test.argtypes = POINTER(darray), POINTER(darray)

Test:

a1 = np.arange(3) + 1.0
a2 = np.zeros(3)
print 'before:', '\na1 =', a1, '\na2 =', a2 

lib.test(darray.fromnp(a1), darray.fromnp(a2))
print 'after:', '\na1 =', a1, '\na2 =', a2 

Output:

before: 
a1 = [ 1.  2.  3.] 
a2 = [ 0.  0.  0.]
after: 
a1 = [ 1.  2.  3.] 
a2 = [ 2.  4.  6.]
like image 92
Eryk Sun Avatar answered Nov 05 '22 06:11

Eryk Sun