Search code examples
pythonc++numpyswig

How can you wrap a C++ function with SWIG that takes in a Python numpy array as input without explicitly giving a size?


I have a library of C++ classes that I am building a Python interface for using SWIG. Many of these classes have methods that take in a double* array or int* array parameter without inputting a size. For example, there are many methods that have a declaration like one of the following:

void func(double* array);

void func2(double* array, double unrelated_parameter, ...);

I would like to be able to use these functions in Python, with the user passing in a Python numpy array. The size of these arrays are never given as a parameter to the function. The size of the input array is given in the constructor of the objects of these C++ classes and it is assumed that every input array that is given as a parameter to these class methods will have the same size. All of the numpy examples I have seen require me to add an int array_size parameter to the C++ method/function being wrapped.

Is there a way to wrap these C++ functions without having change the API of my entire C++ library to include an int array_size parameter for every single function? Ideally, a user should pass in a Python numpy array and SWIG will automatically convert it to a double or int array on the C++ side.

I have already included numpy.i and followed the instructions here: https://numpy.org/doc/stable/reference/swig.interface-file.html but am getting errors like the following:

TypeError: in method 'func', argument 2 of type 'double *'


Solution

  • One way I can think of is to suppress the "no size" version of the function and extend the class to have a version with a throw-away dimension variable that uses the actual parameter in the class.

    Example:

    test.i

    %module test
    
    %{
    #define SWIG_FILE_WITH_INIT
    
    class Test {
    public:
        int _dim; // needs to be public, or have a public accessor.
        Test(int dim) : _dim(dim) {}
        double func(double* array) {
            double sum = 0.0;
            for(int i = 0; i < _dim; ++i)
                sum += array[i];
            return sum;
        }
    };
    %}
    
    %include "numpy.i"
    %init %{
    import_array();
    %}
    
    %apply (double* IN_ARRAY1, int DIM1) {(double* array, int /*unused*/)};
    
    %ignore Test::func; // so the one-parameter version isn't wrapped
    
    class Test {
    public:
        Test(int dim);
        double func(double* array);
    };
    
    %rename("%s") Test::func; // unignore so the two-parameter version will be used.
    
    %extend Test {
        double func(double* array, int /*unused*/) {
            return $self->func(array);
        }
    }
    

    Demo:

    >>> import test
    >>> t = test.Test(5)
    >>> import numpy as np
    >>> a = np.array([1.5,2.0,2.5,3.75,4.25])
    >>> t.func(a)
    14.0