Gibt es eine Möglichkeit, Pythonappend mit der neuen integrierten Funktion von SWIG zu verwenden?

77

Ich habe ein kleines Projekt, das wunderbar mit SWIG zusammenarbeitet. Insbesondere geben einige meiner Funktionen std::vectors zurück, die in Python in Tupel übersetzt werden. Jetzt mache ich viele Zahlen, also muss SWIG diese nur in Numpy-Arrays konvertieren, nachdem sie vom C ++ - Code zurückgegeben wurden. Dazu verwende ich in SWIG etwa Folgendes.

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

(Tatsächlich gibt es mehrere Funktionen mit dem Namen "Daten", von denen einige " valfloat" zurückgeben. Deshalb überprüfe ich, ob es sich tatsächlich um ein Tupel handelt.) Dies funktioniert einfach wunderbar.

Aber ich würde auch gerne das benutzen -builtin Flagge verwenden, die jetzt verfügbar ist. Aufrufe dieser Datenfunktionen sind selten und meist interaktiv, sodass ihre Langsamkeit kein Problem darstellt. Es gibt jedoch auch andere langsame Schleifen, die mit der integrierten Option erheblich beschleunigt werden.

Das Problem ist, dass wenn ich dieses Flag verwende, die Pythonappend-Funktion stillschweigend ignoriert wird. Jetzt gibt Data nur noch einmal ein Tupel zurück. Gibt es eine Möglichkeit, wie ich noch numpy Arrays zurückgeben kann? Ich habe versucht, Typemaps zu verwenden, aber es wurde zu einem riesigen Durcheinander.

Bearbeiten:

Borealid hat die Frage sehr gut beantwortet. Der Vollständigkeit halber füge ich ein paar verwandte, aber subtil unterschiedliche Typkarten hinzu, die ich benötige, da ich als konstante Referenz zurückkehre und Vektoren von Vektoren verwende (nicht starten!). Diese sind so unterschiedlich, dass ich nicht möchte, dass jemand anders herumstolpert, um die kleinen Unterschiede herauszufinden.

%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);
}

Bearbeiten 2:

Obwohl nicht ganz das, wonach ich gesucht habe, können ähnliche Probleme auch mit dem Ansatz von @ MONK ( hier erklärt ) gelöst werden .

Mike
quelle
4
Ich glaube nicht, dass Sie dies tun können, ohne eine Typemap zu schreiben und dies auf der C-Seite zu tun, gerade weil -builtin den Code entfernt, in dem Pythonappend normalerweise platziert wird. Sind Sie sicher, dass -builtin viel schneller ist (dh hat Sie die Profilerstellung dazu veranlasst, es zu verwenden?)? Ich wäre versucht, zwei Module zu verwenden, eines mit und eines ohne -builtin.
Flexo
Ich bin überrascht, dass es keine Warnung gibt, -builtindie Pythonappend ignoriert. Ich bin der Herausforderung nicht gewachsen, std::vectors in numpy Arrays zu typen. Ich habe ein Profil erstellt und es hat die nervigste Schleife in meiner Benutzeroberfläche erheblich beschleunigt (nicht lang genug, um eine Pause einzulegen; zu lange, um häufig zu warten). Aber mir wurde auch klar, dass ich diese Schleife in meinen C ++ - Code verschieben kann - wenn auch etwas umständlich. So werde ich also gehen. Dennoch ist Ihr Vorschlag für zwei Module interessant und kann in anderen Fällen hilfreich sein.
Mike
Hast du SWIG mit -Wall angerufen? Ich nahm an, dass es in diesem Fall warnen würde.
Flexo
Keine Warnung, auch mit -Wall(obwohl dies groß genug ist, um zu ignorieren, dass ich nicht denke, dass es das überhaupt erfordern sollte).
Mike
Versuchen Sie, die DataMethoden mit Cython zu verpacken ?
Paul Price

Antworten:

8

Ich stimme Ihnen zu, dass die Verwendung typemapetwas chaotisch wird, aber es ist der richtige Weg, um diese Aufgabe zu erfüllen. Sie haben auch Recht, dass die SWIG-Dokumentation nicht direkt sagt, dass dies nicht %pythonappendkompatibel ist -builtin, aber es ist stark impliziert: %pythonappend fügt der Python-Proxy-Klasse hinzu , und die Python-Proxy-Klasse existiert überhaupt nicht in Verbindung mit der-builtin Flag überhaupt nicht.

Zuvor haben Sie SWIG die C ++ - std::vectorObjekte in Python-Tupel konvertieren lassen und diese Tupel dann wieder an übergebennumpy - übergeben, wo sie erneut konvertiert wurden.

Was Sie wirklich tun möchten, ist, sie einmal auf der C-Ebene zu konvertieren.

Hier ist ein Code, der alle std::vector<int>Objekte in NumPy-Integer-Arrays umwandelt :

%{
#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);
}

Dies verwendet die Numpy-Funktionen der C-Ebene, um ein Array zu erstellen und zurückzugeben. In der Reihenfolge, es:

  • Gewährleistet NumPy's arrayobject.h Datei von in der C ++ - Ausgabedatei enthalten ist
  • Ursachen import_arraygenannt zu werden , wenn das Python - Modul geladen wird (andernfalls werden alle NumPy Methoden segfault)
  • Ordnet alle Rückgaben von std::vector<int>NumPy-Arrays mit a zutypemap

Dieser Code sollte gesetzt werden , bevor Sie auf %importdie Überschriften , die die Funktionen der Rückkehr enthalten std::vector<int>. Abgesehen von dieser Einschränkung ist es vollständig in sich geschlossen, sodass es Ihrer Codebasis nicht zu viel subjektives "Durcheinander" hinzufügen sollte.

Wenn Sie andere Vektortypen benötigen, können Sie einfach die NPY_INTund alle int*und intBits ändern , andernfalls duplizieren Sie die obige Funktion.

Borealid
quelle
Hervorragend! Ich hatte alle Elemente, die Sie für die Typkarte haben, aber ich hatte sie nicht richtig zusammengestellt. Obwohl dies offiziell noch nicht mit meinem Projekt funktioniert, habe ich einen ziemlich gründlichen Test durchgeführt, indem ich ein einfacheres Modul erstellt habe. Vielen Dank!
Mike