pythonarrayscnumpyctypes

Passing a NumPy 3d array to a C function with a triple pointer as an argument


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.


Solution

  • 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]]]