Pregunta ¿Hay alguna forma de utilizar pythonappend con la nueva función incorporada de SWIG?


Tengo un pequeño proyecto que funciona muy bien con SWIG. En particular, algunas de mis funciones regresan std::vectors, que se traducen a tuplas en Python. Ahora hago muchos numerics, por lo que solo hago que SWIG los convierta en numpy arrays una vez que se devuelven desde el código de c ++. Para hacer esto, uso algo como lo siguiente en SWIG.

%feature("pythonappend") My::Cool::Namespace::Data() const %{ if isinstance(val, tuple) : val = numpy.array(val) %}

(En realidad, hay varias funciones llamadas Datos, algunas de las cuales devuelven carrozas, por eso compruebo que val es en realidad una tupla.) Esto funciona muy bien.

Pero, también me gustaría usar el -builtin bandera que ahora está disponible. Las llamadas a estas funciones de Datos son raras y en su mayoría interactivas, por lo que su lentitud no es un problema, pero existen otros bucles lentos que se aceleran significativamente con la opción incorporada.

El problema es que cuando uso ese indicador, la función pythonappend se ignora silenciosamente. Ahora, Data simplemente devuelve una tupla nuevamente. ¿Hay alguna manera de que todavía pueda devolver matrices numpy? Intenté usar mapas de tipos, pero se convirtió en un desastre gigante.

Editar:

Borealid ha respondido la pregunta muy bien. Solo para completar, incluyo un par de tipos de mapas relacionados pero sutilmente diferentes que necesito porque vuelvo por referencia constante y uso vectores de vectores (¡no empieces!). Estos son lo suficientemente diferentes como para que no quisiera que nadie más tropezara tratando de descubrir las pequeñas diferencias.

%typemap(out) std::vector<int>& {
  npy_intp result_size = $1->size();
  npy_intp dims[1] = { result_size };
  PyArrayObject* npy_arr = (PyArrayObject*)PyArray_SimpleNew(1, dims, NPY_INT);
  int* dat = (int*) PyArray_DATA(npy_arr);
  for (size_t i = 0; i < result_size; ++i) { dat[i] = (*$1)[i]; }
  $result = PyArray_Return(npy_arr);
}
%typemap(out) std::vector<std::vector<int> >& {
  npy_intp result_size = $1->size();
  npy_intp result_size2 = (result_size>0 ? (*$1)[0].size() : 0);
  npy_intp dims[2] = { result_size, result_size2 };
  PyArrayObject* npy_arr = (PyArrayObject*)PyArray_SimpleNew(2, dims, NPY_INT);
  int* dat = (int*) PyArray_DATA(npy_arr);
  for (size_t i = 0; i < result_size; ++i) { for (size_t j = 0; j < result_size2; ++j) { dat[i*result_size2+j] = (*$1)[i][j]; } }
  $result = PyArray_Return(npy_arr);
}

Editar 2:

Aunque no es exactamente lo que estaba buscando, problemas similares también pueden ser resueltos usando el enfoque de @ MONK (explicado aquí)


75
2018-02-14 00:19


origen


Respuestas:


Estoy de acuerdo contigo en que usando typemap se pone un poco desordenado, pero es la manera correcta de lograr esta tarea. También tiene razón en que la documentación de SWIG no dice directamente que %pythonappend es incompatible con -builtin, pero está fuertemente implícito: %pythonappend  se agrega a la clase de proxy de Python, y la clase proxy de Python no existe en conjunto con el -builtin bandera.

Antes, lo que estabas haciendo era hacer que SWIG convirtiera el C ++ std::vector objetos en tuplas de Python, y luego pasar esas tuplas de vuelta a numpy - donde fueron convertidos de nuevo.

Lo que realmente quieres hacer es convertirlos una vez, en el nivel C.

Aquí hay un código que convertirá todo std::vector<int> objetos en matrices de enteros NumPy:

%{
#include "numpy/arrayobject.h"
%}

%init %{
    import_array();
%}

%typemap(out) std::vector<int> {
    npy_intp result_size = $1.size();

    npy_intp dims[1] = { result_size };

    PyArrayObject* npy_arr = (PyArrayObject*)PyArray_SimpleNew(1, dims, NPY_INT);
    int* dat = (int*) PyArray_DATA(npy_arr);

    for (size_t i = 0; i < result_size; ++i) {
        dat[i] = $1[i];
    }

    $result = PyArray_Return(npy_arr);
}

Esto usa las funciones numpy de nivel C para construir y devolver una matriz. En orden, esto:

  • Asegura NumPy de arrayobject.h el archivo está incluido en el archivo de salida de C ++
  • Causas import_array para llamar cuando se carga el módulo de Python (de lo contrario, todos los métodos de NumPy segfault)
  • Mapas cualquier devolución de std::vector<int> en matrices NumPy con un typemap

Este código debe ser colocado antes de tú %import los encabezados que contienen las funciones que regresan std::vector<int>. Aparte de esa restricción, es totalmente independiente, por lo que no debería agregar demasiado "desorden" subjetivo a su base de código.

Si necesita otros tipos de vectores, puede simplemente cambiar el NPY_INT y todo el int* y int bits, de lo contrario duplicar la función anterior.


6
2018-01-10 17:18