I am experimenting with the use of the ctypes package in Python. Currently attempting to pass a 3d NumPy array to a C function that takes a triple pointer as an argument and updates the values of the elements of the array. The C function is:
void effect_array(int ***ptr, int rows, int cols, int depth)
{
for(int i = 0; i < rows; i++)
{
for(int j = 0; j < cols; j++)
{
for(int k = 0; k < depth; k++)
{
ptr[i][j][k] *= 2;
}
}
}
}
Currently, I have tried:
import ctypes as ct
import numpy as np
arr = np.array([
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
],
[
[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24]
]
])
arr_c = np.ascontiguousarray(arr)
lib = ct.CDLL("path/to/share_object.so")
_effect_array = lib.effect_array
_effect_array.argtypes = [ct.POINTER(ct.POINTER(ct.POINTER(ct.c_int))), ct.c_int, ct.c_int, ct.c_int]
_effect_array.restype = None
rows, cols, depth = arr.shape
t_ptr = (ct.POINTER(ct.POINTER(ct.c_int)) * rows)()
for i in range(rows):
t_ptr[i] = (ct.POINTER(ct.c_int) * cols)()
for j in range(cols):
t_ptr[i][j] = arr_c[i][j].ctypes.data_as(ct.POINTER(ct.c_int))
print("Original array =")
print(arr_c)
print()
_effect_array(t_ptr, rows, cols, depth)
print("Array after pass to function =")
print(arr_c)
This has resulted in an output of:
Original array =
[[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
[[13 14 15 16]
[17 18 19 20]
[21 22 23 24]]]
Array after pass to function =
[[[ 2 4 3 4]
[10 12 7 8]
[18 20 11 12]]
[[26 28 15 16]
[34 36 19 20]
[42 44 23 24]]]
What I would like to happen would be:
Original array =
[[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
[[13 14 15 16]
[17 18 19 20]
[21 22 23 24]]]
Array after pass to function =
[[[ 2 4 6 8]
[10 12 14 16]
[18 20 22 24]]
[[26 28 30 32]
[34 36 38 40]
[42 44 46 48]]]
I am not sure why the C function is not able to access the elements beyond the first two in a row. My understanding is that my current attempt is provided access to the start of the array and that it should be able to iterate through the rest of the array as can be done in this C example:
#include <stdio.h>
#include <stdlib.h>
void effect_array(int ***ptr, int rows, int cols, int depth)
{
for(int i = 0; i < rows; i++)
{
for(int j = 0; j < cols; j++)
{
for(int k = 0; k < depth; k++)
{
ptr[i][j][k] *= 2;
}
}
}
}
int main()
{
int arr[2][2][2] = {
{
{1,2},
{3,4}
},
{
{5,6},
{7,8}
}
};
int arr2[5];
int *p = arr2;
int ***ptr = (int ***)malloc(2 * sizeof(int **));
for(int i = 0; i < 2; i++)
{
ptr[i] = (int **)malloc(2 * sizeof(int *));
for(int j = 0; j < 2; j++)
{
ptr[i][j] = &arr[i][j][0];
}
}
printf("Print array before:\n");
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 2; j++)
{
for(int k = 0; k < 2; k++)
{
printf("%d ", arr[i][j][k]);
}
printf("\n");
}
printf("\n");
}
effect_array(ptr, 2, 2, 2);
printf("Print array after:\n");
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 2; j++)
{
for(int k = 0; k < 2; k++)
{
printf("%d ", arr[i][j][k]);
}
printf("\n");
}
printf("\n");
}
for(int i = 0; i < 2; i++) free(ptr[i]);
free(ptr);
return 0;
}
This is also based off the discussion on a previous question I asked, credit to @SR143 for the working C example.
Any and all help is greatly appreciated.
In your original Python code, the dtype
of arr
is np.int64
. Add dtype=np.int32
to your np.array
declaration and the code works.
However, be aware that you can pass a multidimensional numpy array directly to C code instead by using an int*
and pointer math on the contiguous C array and be more efficient since it will save you the trouble of creating the pointer array. An np.ctypeslib.ndpointer
can be used with .argtypes
for better type-checking as well.
Example:
test.c (Windows example):
__declspec(dllexport)
void affect_array(int *ptr, int rows, int cols, int depth) {
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
for(int k = 0; k < depth; k++) {
ptr[i*cols*depth + j*depth + k] *= 2;
}
}
}
}
test.py
import ctypes as ct
import numpy as np
arr = np.array([[[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]],
[[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24]]], dtype=np.int32)
lib = ct.CDLL('./test')
affect_array = lib.affect_array
# Require 3D numpy array of int32 as first parameter. It will be type-checked.
affect_array.argtypes = np.ctypeslib.ndpointer(dtype=np.int32, ndim=3), ct.c_int, ct.c_int, ct.c_int
affect_array.restype = None
print('Original array =')
print(arr)
print()
affect_array(arr, *arr.shape) # * unpacks tuple as 3 additional parameters.
print('Array after pass to function =')
print(arr)
Output:
Original array =
[[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
[[13 14 15 16]
[17 18 19 20]
[21 22 23 24]]]
Array after pass to function =
[[[ 2 4 6 8]
[10 12 14 16]
[18 20 22 24]]
[[26 28 30 32]
[34 36 38 40]
[42 44 46 48]]]