3

Currently I'm learning about C types. My goal is to generate an numpy array A in python from 0 to 4*pi in 500 steps. That array is passed to C code which calculates the tangent of those values. The C code also passes those values back to an numpy array B in python.

Yesterday I tried simply to convert one value from python to C and (after some help) succeeded. Today I try to pass a whole array, not a value.

I think it's an good idea to add another function to the C library to process the array. The new function should in a loop pass each value of A to the function tan1() and store that value in array B.

I have two issues:

  • writing the function that processes the numpy array A
  • Passing the numpy array between python and C code.

I read the following info:

Helpful, but I still don't know how to solve my problem.

C code (Only the piece that seems relevant):

double tan1(f) double f;
{
    return sin1(f)/cos1(f); 
}


void loop(double A, int n);
{
    double *B;
    B = (double*) malloc(n * sizeof(double));
    for(i=0; i<= n, i++)
    {
        B[i] = tan1(A[i])
    }
}

Python code:

import numpy as np
import ctypes


A = np.array(np.linspace(0,4*np.pi,500), dtype=np.float64)

testlib = ctypes.CDLL('./testlib.so')
testlib.loop.argtypes = ctypes.c_double,
testlib.loop.restype = ctypes.c_double


#print(testlib.tan1(3))
    

I'm aware that ctypes.c_double is wrong in this context, but that is what I had in the 1 value version and don't know yet for what to substitute.

Could I please get some feedback on how to achieve this goal?

Jem Tucker
  • 1,143
  • 16
  • 35
Tim
  • 415
  • 2
  • 10
  • 1
    You're not working with *NumPy* from *C* (only with basic types), so I see no reason to use it in *Python* either. It just brings an extra complexity layer to your goal (learn *CTypes*). – CristiFati Oct 22 '20 at 09:31
  • @CristiFati it's part of the assignment to do it with numpy. You're right that I'm not working with Numpy yet, but I want to pass A to the C library. – Tim Oct 22 '20 at 09:34
  • Does this answer your question? [How to use NumPy array with ctypes?](https://stackoverflow.com/questions/3195660/how-to-use-numpy-array-with-ctypes) – ead Oct 22 '20 at 12:28
  • The dupe answers your question (see the title of your question) but doesn’t solve your problem, because your first problem is that your c code doesn’t compile and doesn’t make much sense, as it has no visible effect whatsoever ( if Introduced memory leak is not taken into account) – ead Oct 22 '20 at 12:35

1 Answers1

4

You need to return the dynamically allocated memory, e.g. change your C code to something like:

#include <math.h>
#include <stdlib.h>
#include <stdio.h>

double tan1(double f) {
    return sin(f)/cos(f);
}

double *loop(double *arr, int n) {
    double *b = malloc(n * sizeof(double));
    for(int i = 0; i < n; i++) {
        b[i] = tan(arr[i]);
    }
    return b;
}

void freeArray(double *b) {
    free(b);
}

On the Python side you have to declare parameter and return types. As mentioned by others in comments, you should also free dynamically allocated memory. Note that on the C side, arrays always decay into pointers. Therefore, you need an additional parameter which tells you the number of elements in the array.

Also if you return a pointer to double to the Python page, you must specify the size of the array. With np.frombuffer you can work with the data without making a copy of it.

import numpy as np
from ctypes import *

testlib = ctypes.CDLL('./testlib.so')

n = 500
dtype = np.float64
input_array = np.array(np.linspace(0, 4 * np.pi, n), dtype=dtype)
input_ptr = input_array.ctypes.data_as(POINTER(c_double))

testlib.loop.argtypes = (POINTER(c_double), c_int)
testlib.loop.restype = POINTER(c_double * n)
testlib.freeArray.argtypes = POINTER(c_double * n),

result_ptr = testlib.loop(input_ptr, n)
result_array = np.frombuffer(result_ptr.contents)

# ...do some processing
for value in result_array:
    print(value)

# free buffer
testlib.freeArray(result_ptr)
Stephan Schlecht
  • 26,556
  • 1
  • 33
  • 47
  • `testlib.freeArray.argtypes = POINTER(c_double * n),` is incorrect (at least it doesn't reflect *C*). – CristiFati Oct 24 '20 at 20:45
  • result_ptr (allocated on C side via malloc) is a pointer to 500 doubles. The very same pointer is passed to `freeArray`. Arrays decay into pointers on C anyway. So the type is implicitly converted from an array of 500 doubles to a pointer to double, the values of the function argument is the address of the first double value. – Stephan Schlecht Oct 24 '20 at 21:18