Make a C ++ class look like a numpy array with swig

What a good way to open a C ++ class that provides an array type interface for use with numpy (scipy)?

By a massive interface, I mean something like:

//file:Arr.h class Arr{ public: int n_rows; int n_cols; float* m_data; Arr(int r, int c, float v); virtual ~Arr(); float get(int i, int j); void set(int i, int j, float v); long data_addr(){ return (long)(m_data); } }; 

Limitations:

  • I'm only interested in classes that store their underlying data as solid flat arrays,
  • The class will provide open access to the raw storage (possibly through a function),
  • I cannot add specific python code to the C ++ header / source files (we don’t want to have a Python dependency for C ++ code), so any modifications to the C ++ side must be done through SWIG (e.g. %extend ).

My current approach is to place the pythoncode block in my SWIG .i file, it looks like

 %pythoncode{ def arraylike_getitem(self, arg1,arg2 ): # the actual implementation to handle slices # is pretty complicated but involves: # 1. constructing an uninitialized numpy array for return value # 2. iterating over the indices indicated by the slices, # 3. calling self.getValue for each of the index pairs, # 4. returning the array # add the function to the ArrayLike class Arr.__getitem__=arraylike_getitem %} 

where ArrayLike is a C ++ class that contains numeric data (like a flat array) and provides member functions for getting / setting individual values.

The main disadvantage is step 1. above: I need to make a copy of any fragment that I take from my c-array class. (The main advantage is that when returning numpy, I know that I can use it in any numpy operations I want.)

I can introduce two approaches to improve this:

  • Adding (via SWIG %extend ) additional functions for class c and / or
  • when the python function returns an array proxy object,

My main hang is not to know which interface needs to be implemented (efficiently) in order to plunge to zero, like a numpy array.

Test case

Here is my test setup:

 //file:Arr.h class Arr{ public: int n_rows; int n_cols; float* m_data; Arr(int r, int c, float v); virtual ~Arr(); float get(int i, int j); void set(int i, int j, float v); long data_addr(){ return (long)(m_data); } }; //----------------------------------------------------------- //file Arr.cpp #include "Arr.h" Arr::Arr(int r, int c, float v): n_rows(r), n_cols(c), m_data(0){ m_data=new float[ r*c ]; for( int i=0; i<r*c; ++i){ m_data[i]=v; } } Arr::~Arr(){ delete[] m_data; } float Arr::get(int i, int j){ return m_data[ i*n_cols+j]; } void Arr::set(int i, int j, float v){ m_data[i*n_cols+j]=v; } //-------------------------------------------------------------------- //file:arr.i %module arr %{ #include "Arr.h" #include </usr/lib64/python2.7/site-packages/numpy/core/include/numpy/ndarrayobject.h> #include <python2.7/Python.h> %} %include "Arr.h" %pythoncode{ # Partial solution (developed in constructing the question): allows operations between # arr objects and numpy arrays (eg numpy_array+arr_object is OK) # but does not allow slicing (eg numpy_array[::2,::2]+arr_objec[::2,::2]) # TODO: figure out how to get slices without copy memory def arr_interface_map(self): res={ 'shape':(self.n_rows, self.n_cols), 'typestr':'<f4', 'data': self.data_addr(),0), 'version':3 } return res Arr.__array_interface__=property( arr_interface_map ) } //--------------------------------------------------------- #file: Makefile INCLUDE_FLAGS = -I/usr/include/python2.7 arr_wrap.cpp: arr.i Arr.h swig -c++ -python -o $@ ${INCLUDE_FLAGS} arr.i _arr.so: arr_wrap.o Arr.o g++ -shared -o _arr.so arr_wrap.o Arr.o clean: rm -f *.o *_wrap.cpp *.so all: _arr.so 

If I can get this Arr class to work with numpy , then I succeeded.

Edit: From this related question, it looks like this: __array_interface__ will be part of the solution (TBD: how to use it?)

+4
source share
1 answer

If n_cols and n_rows (efficiently) immutable, your best way to do this is to simply create a real numpy array, specifying m_data as storage and (n_rows, n_cols) as the form. This way you get all numpy arrays without any copies and without the need to redefine them in your own code (which would be a lot of quacks to simulate).

 PyObject* array_like_to_numpy(ArrayLike& obj) { npy_intp dims[] = { obj.n_rows, obj.n_cols }; return PyArray_SimpleNewFromData(2, dims, NPY_FLOAT, obj.m_data); } 

Of course, this will not work as it is written, since your m_data member is protected. But it would be nice to either make it publicly available, or provide an accessory to receive it (or inherit from ArrayLike and provide such functions in your subclass).

+4
source

Source: https://habr.com/ru/post/1496824/


All Articles