2012-02-14 7 views
75

Tengo un pequeño proyecto que funciona muy bien con SWIG. En particular, algunas de mis funciones devuelven std::vector s, 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.¿Hay alguna forma de utilizar pythonappend con la nueva función incorporada de SWIG?

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

(En realidad, hay varias funciones llamadas de datos, algunos de los cuales flotadores de retorno, por lo que puedo comprobar que val es en realidad una tupla.) Esto funciona muy bien.

Pero, también me gustaría usar la bandera -builtin que ya 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 característica 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 a 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); 
} 

Edición 2:

Aunque no es exactamente lo que estaba buscando, problemas similares también pueden ser resueltos usando @ enfoque de Monk (explained here).

+4

No creo que pueda hacer esto sin escribir un mapa de tipos y hacerlo en el lado C, precisamente porque -builtin elimina el código donde normalmente se coloca pythonappend. ¿Está seguro de que la capacidad de compilación es mucho más rápida (es decir, si los perfiles lo llevaron a usarlo?) Me sentiría tentado de utilizar dos módulos, uno con y otro sin -builtin. – Flexo

+0

Me sorprende que no haya ninguna advertencia de que '-builtin' ignora pythonappend. No estoy a la altura del desafío del mapeo tipográfico 'std :: vector's en matrices numpy. Hice un perfil, y aceleró significativamente el ciclo más molesto en mi interfaz (no lo suficiente como para tomar un descanso, demasiado tiempo para esperar con frecuencia). Pero también me di cuenta de que puedo mover este bucle en mi código de C++, aunque de forma algo torpe. Así que así es como iré. Aún así, su sugerencia de "dos módulos" es interesante y podría ser útil en otros casos. – Mike

+0

¿Ha llamado a SWIG con -Wall? Supuse que advertiría en ese caso. – Flexo

Respuesta

6

Estoy de acuerdo con usted en que usar typemap se pone un poco desordenado, pero es la manera correcta de realizar 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: %pythonappendse agrega a la clase de proxy de Python, y la clase de proxy de Python no existe en conjunto con -builtin bandera.

Antes, lo que hacían era hacer que SWIG convirtiera los objetos C++ std::vector en tuplas de Python, y luego pasar esas tuplas de nuevo a numpy - donde se convirtieron nuevamente.

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

Aquí hay un código que convertirá todas 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 utiliza las funciones numpy de nivel C de construir y devolver una matriz.Con el fin, es:

  • Asegura archivo de NumPy arrayobject.h se incluye en el archivo de salida de la C++
  • Causas import_array a ser llamados cuando se carga el módulo de Python (de lo contrario, todos los métodos NumPy se segfault)
  • Mapas cualquier devoluciones de std::vector<int> en matrices NumPy con un typemap

Este código debe ser colocado antes que %import las cabeceras WH que contiene las funciones que devuelven 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 todos los int* y int bits, de lo contrario se duplicará la función anterior.

+0

¡Magnífico! Tenía todos los elementos que tenías para el mapa de tipos, pero no los había reunido correctamente. Aunque oficialmente todavía no tengo esto funcionando con mi proyecto, hice una prueba bastante exhaustiva al crear un módulo más simple. ¡Muchas gracias! – Mike

Cuestiones relacionadas