Monday, October 15, 2012

Python - C interfacing with ctypes

Ctypes is a module in Python for facilitating easy interface between Python and C. Interfacing Python with C can be useful for improving the performance of a Python program. Interfacing C with Python can be useful from a testing point of view. Either way, python-c interfacing is going to save our lives and give us better results(most of the time).

The procedure in using ctypes is very simple.
(a) Create a shared library from c-code
(b) Load the shared library in Python using ctypes
(c) Create equivalent data structures in Python
(d) Call the functions in shared object with equivalent data structures

(a) Creating a shared library

gcc -shared -fPIC exlib.c -o libex.so

Here, I am assuming that we have the c code in a file named exlib.c

(b) Loading shared library in Python

# import ctype library
import ctypes as ct
# load the shared library
exlib = ct.cdll.LoadLibrary("./libex.so")

Once, we have these two working, we can proceed to examples.

C code is given first, followed by Python code.

--------------------------------------------------------------------------------
(1) Simplest One: Takes nothing and Returns nothing

void function_01(void)
{
    printf("Hello World\n");
}

exlib.function_01()

--------------------------------------------------------------------------------
(2) Integer and String

void function_02(char *pString, int value)
{
    printf("%s%d\n", pString, value);
}

exlib.function_02("this year is ", 2012)
a = 1024
print "a =", a
exlib.function_02("a = ", a)

--------------------------------------------------------------------------------
(3) Unsigned int, Long, Unsigned Long

void function_03(   unsigned int    ui,
                    long            l,
                    unsigned long   ul )
{
    printf( "uint  = %u\n"
            "long  = %ld\n"
            "ulong = %lu\n",
            ui, l, ul);
}

ui = ct.c_uint ( 2**31   )
l  = ct.c_long ( 2 ** 62 )
ul = ct.c_ulong( 2 ** 63 )
print "uint  = ",   ui.value
print "long  = ",   l .value
print "ulong = ",   ul.value
exlib.function_03(ui, l, ul)
# Setting value directly
ui.value = 1000
l .value = 2000
ul.value = 3000
exlib.function_03(ui, l, ul)

--------------------------------------------------------------------------------
(4) Two Doubles go in, One Double comes out

double function_04(double a, double b)
{
    return a * b;
}

# specifying input types, this will make the function call safe
exlib.function_04.argtypes = [ct.c_double, ct.c_double]
# specifying return type
exlib.function_04.restype = ct.c_double
a = ct.c_double( 10.0 )
b = ct.c_double( 2.0  )
c = exlib.function_04(a, b)
print c

--------------------------------------------------------------------------------
(5) Sending data by reference

void function_05( double * a)
{
    *a = 2 * (*a);
}

a = ct.c_double( 100 )
print a.value
exlib.function_05( ct.byref(a) )
print a.value

--------------------------------------------------------------------------------
(6) Making things complicated with structures

typedef struct
{
    double real;
    double imag;
} complex_t;

void function_06( complex_t a)
{
    printf("Real = %f, Imag = %f\n", a.real, a.imag);
}


class ComplexType(ct.Structure):
    _fields_ = [("real", ct.c_double), ("imag", ct.c_double) ]

cc = ComplexType(10, 20)
exlib.function_06( cc )
cc.imag = 33
exlib.function_06( cc )

--------------------------------------------------------------------------------
(7) Making things more complicated with pointers and array

void function_07( int* a, int len)
{
    int i;
    for( i = 0 ; i < len; i++)
    {
        printf("%d, ", a[i]);
    }
    printf("\n");
}


a_len = 20
# defining array type
array_type = ct.c_int * a_len
# making an array
a = array_type()
# assigning values
for i in xrange(a_len) :
    a[i] = i*i;
#taking the address
ap = ct.pointer(a)
exlib.function_07(ap, a_len)

--------------------------------------------------------------------------------
(8)  With pointers and arrays inside structure

typedef struct
{
    int a_len;
    complex_t a[10];
    int b_len;
    complex_t *b;
} AB_t;

void function_08( AB_t* inp)
{
    int i;
    for(i = 0; i < inp->a_len; i++)
    {
        printf("%f,",  inp->a[i].real);
        printf("%f\n", inp->a[i].imag);
    }
    printf("\n");
    for(i = 0; i < inp->b_len; i++)
    {
        printf("%f,",  inp->b[i].real);
        printf("%f\n", inp->b[i].imag);
    }
}


ComplexArrayType10 = ComplexType * 10
ComplexArrayType100 = ComplexType * 100
class ABType(ct.Structure):
    _fields_ = [ ("a_len", ct.c_int),
                 ("a",ComplexArrayType10),
                 ("b_len", ct.c_int),
                 ("b", ct.POINTER(ComplexType) ) ]

x = ABType()
x.a_len = 8
for ii in xrange( x.a_len ):
    x.a[ii].real = ii * 3;
    x.a[ii].imag = ii * -3;
x.b_len = 5
b = ComplexArrayType100()
for ii in xrange( x.b_len ):
    b[ii].real = ii * 10
    b[ii].imag = ii * -10
# the following will not work without cast
x.b = ct.cast( ct.pointer(b), ct.POINTER(ComplexType) )
exlib.function_08( ct.pointer(x) )


--------------------------------------------------------------------------------
(9) Playing with Numpy Arrays

double function_09(double* inpVec, int stride, int length)
{
    /*
     * stride - address difference between successive elements
     */

    int i;
    int step = stride / sizeof(double);
    double sum  = 0;
    double elem;
    double* elem_p;
    if(0 != stride % sizeof(double))
    {
        /* not needed if we specify argtypes in python */
        printf("ERROR: Stride should be multiple of sizeof(double)\n");
        return 0.0;
    }

    /*
     * (a) find sum
     * (b) do an inplace value change
     */


     sum = 0;
     printf("stride = %d, length = %d\n", stride, length);
     for( i = 0; i < length; i++)
     {
         /* pointer to i th element */
         elem_p = inpVec + i * step;
         /* value of i th element */
         elem = *elem_p;
         /* calculate sum */
         sum = sum + elem;
         /* inplace value change */
         *elem_p = 1.1 * elem + 0.01;
     }
     return sum;
}


# wrapper function
def wr_function_09( ipvec ):
    # define input argument types
    exlib.function_09.argtypes=[np.ctypeslib.ndpointer(dtype=np.double,
                                                       ndim = 1),
                                ct.c_int,
                                ct.c_int]
    # define retrun type
    exlib.function_09.restype  = ct.c_double
    stride = ipvec.strides[0]
    length = ipvec.size
    sum_r = exlib.function_09( ipvec, stride, length )
    return sum_r

a = np.arange(6.0)
print a
sa = wr_function_09(a)
print sa
print a
sa = wr_function_09(a)
print sa
print a
# our function should work for both rows and columns
b = np.zeros( (6, 6), dtype = np.double )
# sending 4th column
sb = wr_function_09(b[:,4])
print b
# sending 3rd row
sb = wr_function_09(b[3,:])
print b


--------------------------------------------------------------------------------
(10) Handling Numpy Matrices

/* 2 dimensional matrix structure */
typedef struct
{
    double *pData;
    int rstride;
    int cstride;
    int rlen;
    int clen;
} matrix_t;

/* function to get the address of element (i, j) */
double* get_elem_p( matrix_t m, int i, int j)
{
    return (double *) ( (char *)(m.pData) +
                        i * m.rstride +
                        j * m.cstride );
}


void function_10( matrix_t a, matrix_t b)
{
    /*
     * A simple function to do an inplace matrix addition
     */

    /* TODO:
     * check for stride % sizeof(double) has to be done for both a and b
     * check for rlen and clen has to be done for both a and b
     */

    int i, j;
    printf("%d, %d, %d, %d\n" ,
            a.rstride, b.rstride, a.cstride, b.cstride);
    printf("%d, %d, %d, %d\n" ,
            a.rlen, b.rlen, a.clen, b.clen);
    for( i = 0; i < a.rlen; i++ )
    {
        for( j = 0; j < a.clen; j++ )
        {
            *get_elem_p(a, i, j) += *get_elem_p(b, i, j);
        }
    }
}


class MatrixType(ct.Structure):
    _fields_ = [("pData", ct.POINTER(ct.c_double)),
                ("rstride", ct.c_int),
                ("cstride", ct.c_int),
                ("rlen", ct.c_int),
                ("clen", ct.c_int)]


# wrapper function 
def wr_function_10( inpmx1, inpmx2 ):
    m1 = MatrixType()
    m2 = MatrixType()
    m1.pData = inpmx1.ctypes.data_as(ct.POINTER(ct.c_double))
    m2.pData = inpmx2.ctypes.data_as(ct.POINTER(ct.c_double))
    m1.rstride = inpmx1.strides[0]
    m1.cstride = inpmx1.strides[1]
    m2.rstride = inpmx2.strides[0]
    m2.cstride = inpmx2.strides[1]
    m1.rlen    = inpmx1.shape[0]
    m1.clen    = inpmx1.shape[1]
    m2.rlen    = inpmx2.shape[0]
    m2.clen    = inpmx2.shape[1]
    print m1.rstride, m2.rstride, m1.cstride, m2.cstride
    print m1.rlen, m2.rlen, m1.clen, m2.clen
    exlib.function_10(m1, m2)

a1 = np.zeros((5, 4), dtype = np.double)
a2 = a1 + 0.5
print a1
print a2
wr_function_10(a1, a2)
print a1
wr_function_10(a1, a2)
print a1
wr_function_10(a1, a2)
print a1


--------------------------------------------------------------------------------
Notes :
[1] Always make sure that memory allocation is handled by Python. Dynamic allocation inside c function for results required in Python can be problematic. Instead, create the required structures in Python and send their reference to C.