4

I want to call a c function from python using ctypes. From the documentation I don't understand how to pass pointer to vectors. The function I want to call is:

double f(int n, double* x)
  {
    int i;
    double p=1;
    for (i=0; i< n; i ++) 
      p = p * x[i];
    return p;
  }

I have modified the function with void pointer, so it becomes f(int, void*) with an internal cast to double. I do the following:

def f(x):
  n = len(x)
  libc = '/path/to/lib.so'
  cn = c_int(n)
  px = pointer(x)
  cx = c_void_p(px)
  libc.restype = c_double
  L = libc.f(cn, cx)
  return L

I assume that x is a numpy array, but I am not sure how the numpy array are organized in the memory and if this is the best solution.


Edit:

None of the proposed methods work with my numpy array, maybe it is due to how I am defining the array:

x = np.array([], 'float64')
f = open(file,'r')
for line in f:
  x = np.append(x,float(line))

but some of them work if I have an explicit list like [1,2,3,4,5], rather than a list that has been defined somewhere else and it is referred as x

simona
  • 2,009
  • 6
  • 29
  • 41
  • 1
    Have a look at this question: http://stackoverflow.com/questions/5862915/passing-numpy-arrays-to-a-c-function-for-input-and-output – ThePhysicist Mar 24 '14 at 18:14

3 Answers3

4

Based on @Sven Marnach's answer:

#!/usr/bin/env python
import ctypes
import numpy as np
from numpy.ctypeslib import ndpointer

libf = ctypes.cdll.LoadLibrary('/path/to/lib.so')
libf.f.restype = ctypes.c_double
libf.f.argtypes = [ctypes.c_int, ndpointer(ctypes.c_double)]

def f(a):
    return libf.f(a.size, np.ascontiguousarray(a, np.float64))

if __name__=="__main__":
    # slice to create non-contiguous array
    a = np.arange(1, 7, dtype=np.float64)[::2]
    assert not a.flags['C_CONTIGUOUS']
    print(a)
    print(np.multiply.reduce(a))
    print(f(a))

Output

[ 1.  3.  5.]
15.0
15.0

Removing np.ascontiguousarray() call produces the wrong result (6.0 on my machine).

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
2

You can call it like this:

#!python
from ctypes import *

#!python
from ctypes import *

# double f(int n, double* x)
f = CDLL('/path/to/lib.so').f
f.argtypes = [c_int, POINTER(c_double)]
f.restype = c_double

if __name__ == '__main__':
    array = (c_double * 5)(1, 2, 3, 4, 5)
    r = f(len(array), array)
    print(r)

If you have numpy array, you can use numpy.array.ctypes.data_as:

#!python
from ctypes import *
import numpy

# double f(int n, double* x)
f = CDLL('/path/to/lib.so').f
f.argtypes = [c_int, POINTER(c_double)]
f.restype = c_double

if __name__ == '__main__':
    array = numpy.array([1, 2, 3, 4, 5])
    r = f(array.size, array.astype(numpy.double).ctypes.data_as(POINTER(c_double)))
    print(r)

or:

#!python
from ctypes import *
import numpy

# double f(int n, double* x)
f = CDLL('/path/to/lib.so').f
f.argtypes = [c_int, POINTER(c_double)]
f.restype = c_double

if __name__ == '__main__':
    array = numpy.double([1, 2, 3, 4, 5])
    r = f(array.size, array.ctypes.data_as(POINTER(c_double)))
    print(r)
  • 1
    Or `f.argtypes = [c_int, numpy.ctypeslib.ndpointer('float64')]; r = f(array.size, array)`. – Eryk Sun Mar 24 '14 at 19:36
  • @eryksun I get `Argument Error: "argument 2:: Don't know how to convert parameter 2"` – simona Mar 25 '14 at 20:38
  • @BSH I have only tried method two so far and the values are not passed correctly – simona Mar 25 '14 at 20:42
  • @simona I'm not sure what you mean by not passed correctly, how you pass it? –  Mar 25 '14 at 21:00
  • @BSH I have used method 2 of your three methods, and the values passed to the C function are not what they should be – simona Mar 25 '14 at 21:17
  • @simona make sure you pass the right size and the type, `5` is hardcoded size. It works fine to me. –  Mar 25 '14 at 21:24
  • @BSH actually my function has the following arguments: one `int`,four `numpy array` and eight `double`. the int and the doubles are passed ok but the arrays have just the wrong values – simona Mar 25 '14 at 22:03
  • @BSH I cannot find a way to make that work with numpy array, but method 1 works with the declaration `array = (c_double * 5)(1,2,3,4,5)`. But say that I have a generic list `x`, the declaration `array = (c_double * 5) x` doesn't work. How can I write something that works? – simona Mar 26 '14 at 18:05
  • @BSH and also method 3 works if I declare `array=numpy.double([1,2,3,4])`, but if I say `x = [1,2,3,4]` and then `array = numpy.double(x)` the values are not passed correctly. the question is how to do that for a generic list – simona Mar 26 '14 at 18:34
  • @BSH interestingly, method 2 does not work for me. All this is very charming. now how do I do that for a generic list? – simona Mar 26 '14 at 19:27
-2

apparently I needed to specify the flag contigous to make it work

http://scipy-lectures.github.io/advanced/interfacing_with_c/interfacing_with_c.html

this is an extract from my code:

array_1d_double = numpy.ctypeslib.ndpointer(dtype=numpy.double, ndim=1, 
                                            flags='CONTIGUOUS')
libc.f.argtypes = [c_int, array_1d_double]
libc.f(n, x)
simona
  • 2,009
  • 6
  • 29
  • 41
  • 1
    Link-only answers are generally [frowned upon](http://meta.stackexchange.com/a/8259/245076) on Stack Overflow. In time it is possible for links to atrophy and become unavailable, meaning that your answer is useless to users in the future. It would be best if you could provide the general details of your answer in your actual post, citing your link as a reference. – jfs Mar 26 '14 at 20:43
  • @eryksun look, I don't want to argue anything, just I followed the instructions in the tutorial and now my code works – simona Mar 27 '14 at 09:37
  • @J.F.Sebastian now I have updated my answer, only because you are right, this can be helpful in the future for another user with the same question. I got a -1 for an answer that is the correct one and solves my problem, sometimes I think that people in the stackoverflow community are helpless as humans, seriously. – simona Mar 27 '14 at 09:43
  • If you try to pass [the non-contiguous array from my answer](http://stackoverflow.com/a/22685515/4279) to `libc.f` then it leads to `ctypes.ArgumentError: argument 2: : array must have flags ['C_CONTIGUOUS']`. Do you consider it working? Note: `f()` functions from my answer produces the product. Unrelated: I haven't downvoted the answer. Though I should because the answer is wrong (`TypeError` is not the correct answer) and the author of the answer refuses to change it. – jfs Mar 27 '14 at 11:04
  • @J.F.Sebastian I signed your answer as correct. feel free to downvote me, I am a physicist and we are sometimes a bit cavalier with the minus sign – simona Mar 27 '14 at 11:30
  • Adding a flag check that would raise a `TypeError` is clearly not what you did to make it work. Since you didn't actually get a `TypeError`, I assume your array was actually contiguous. You've marked the answer by J.F. Sebastian as the answer, but I can't see how calling `ascontiguousarray` would affect the result in this case. The accepted "answer" doesn't naturally follow from your question. So it's fairly useless to anyone but you -- or psychics. – Eryk Sun Mar 27 '14 at 12:22
  • I had a code that wouldn't run correctly and with the flag it works. I don't know why adding a flag made it work. I leave it to you as a homework – simona Mar 27 '14 at 13:55
  • Here's the [`_ndptr` base class](https://github.com/numpy/numpy/blob/v1.8.1/numpy/ctypeslib.py#L150) that `ndpointer` uses. The `from_param` method verifies the object's type, dtype, ndim, shape, and flags. If it doesn't raise `TypeError`, then it simply returns the `ctypes` attribute, which is an instance of [`numpy.core._internal._ctypes`](https://github.com/numpy/numpy/blob/v1.8.1/numpy/core/_internal.py#L224). This is initialized with the base address of the array, and its `_as_parameter_` property returns this address as a `c_void_p`. – Eryk Sun Mar 27 '14 at 15:02
  • It is impossible that simply adding this flags check made anything work compared to the original `argtypes` that I gave you or the perfectly functional code in the answer of BSH. – Eryk Sun Mar 27 '14 at 15:05
  • this thing happens also in science: people don't understand things and they say something is impossible. I am a scientist and I have to get things done, and I don't know why python does that and that. I had a precise problem with my code and the previously proposed solution didn't work. that's it. please don't make a fool of yourself. – simona Mar 27 '14 at 16:48
  • 1
    @simona: What happens is people makes many changes to code while trying to fix something and mistakenly attribute a fix to something that did nothing at all. This has to be the case here. I'm sorry that you don't believe me. I had hoped by looking at the source you would see that it is as obvious as 1 + 1 = 2. I was only trying to help you. – Eryk Sun Mar 28 '14 at 00:08