2014-11-24 12 views
7

Ich versuche, die cholesky-Zerlegung des Produkts einer Matrix mit seiner Transponierung zu nehmen, unter Verwendung von Eigen und C++ 11 "Auto" -Typ. Das Problem kommt, wenn ich versuche zu tunEigen- und C++ 11-Typ-Inferenz scheitert für Cholesky des Matrixprodukts

auto c = a * b 
auto cTc = c.tranpose() * c; 
auto chol = cTc.llt(); 

Ich verwende XCode 6.1, Eigen 3.2.2. Der Typfehler, den ich bekomme, ist here.

Dieses minimale Beispiel zeigt das Problem an meinem Computer. Ändern Sie den Typ c von auto zu MatrixXd, damit es funktioniert.

#include <iostream> 
#include <Eigen/Eigen> 
using namespace std; 
using namespace Eigen; 


int main(int argc, const char * argv[]) { 
    MatrixXd a = MatrixXd::Random(100, 3); 
    MatrixXd b = MatrixXd::Random(3, 100); 
    auto c = a * b; 
    auto cTc = c.transpose() * c; 
    auto chol = cTc.llt(); 
    return 0; 
} 

Gibt es eine Möglichkeit, dies zu tun, während immer noch Auto verwenden?

Als eine Nebenfrage, gibt es einen Leistungsgrund, die Matrix nicht zu bestätigen ist ein MatrixXd in jeder Phase? Die Verwendung von Auto würde es Eigen erlauben, die Antwort als irgendeinen seltsamen Template-Ausdruck zu behalten, den es sich vorstellt. Ich bin mir nicht sicher, ob das Tippen als MatrixXd Probleme verursachen würde oder nicht.

Antwort

4

Lassen Sie mich zusammenfassen, was passiert und warum es falsch ist. Zunächst einmal wollen wir die auto Schlüsselwörter mit den Typen instanziiert sie einnehmen:

typedef GeneralProduct<MatrixXd,MatrixXd> Prod; 
Prod c = a * b; 
GeneralProduct<Transpose<Prod>,Prod> cTc = c.transpose() * c; 

Beachten Sie, dass Eigen Ausdruck Template-Bibliothek ist.Hier ist GeneralProduct<> ein abstrakter Typ, der das Produkt darstellt. Keine Berechnung wird ausgeführt. Deshalb, wenn Sie cTc auf eine MatrixXd als Kopie:

MatrixXd d = cTc; 

dem entspricht:

MatrixXd d = c.transpose() * c; 

dann das Produkt a*b wird zweimal durchgeführt! Also in jedem Fall ist es sehr bevorzugt, a*b innerhalb einer expliziten vorübergehend zu bewerten, und gleich für c^T*c:

MatrixXd c = a * b; 
MatrixXd cTc = c.transpose() * c; 

Die letzte Zeile:

auto chol = cTc.llt(); 

ist auch ziemlich falsch. Wenn cTc ein abstrakter Produkttyp ist, versucht es eine Cholesky-Faktorisierung zu instanziieren, die an einem abstrakten Produkttyp arbeitet, was nicht möglich ist. Nun, wenn cTc ein MatrixXd ist, dann sollten Sie den Code arbeiten, aber dies noch nicht die bevorzugte Art und Weise wie das Verfahren llt() ist eher Einzeiler Ausdruck zu implementieren wie:

VectorXd b = ...; 
VectorXd x = cTc.llt().solve(b); 

Wenn Sie ein benannte Cholesky Objekt mögen, dann verwenden lieber den Konstruktor:

LLT<MatrixXd> chol(cTc); 

oder sogar:

LLT chol (c.transpose() * c);

was äquivalent ist, es sei denn, Sie müssen c.transpose() * c in anderen Berechnungen verwenden.

Schließlich abhängig von der Größe der a und b, könnte es vorzuziehen sein zu berechnen cTc als:

MatrixXd cTc = b.transpose() * (a.transpose() * a) * b; 

In Zukunft (dh Eigen 3.3), wird Eigen Lage sein zu sehen:

auto c = a * b; 
MatrixXd cTc = c.transpose() * c; 

als ein Produkt aus vier Matrizen m0.transpose() * m1.transpose() * m2 * m3 und setzen Sie die Klammer an der richtigen Stelle. Allerdings kann es nicht wissen, m0==m3 und m1==m2, und daher, wenn der bevorzugte Weg ist, a*b in einem vorläufigen auszuwerten, dann müssen Sie es immer noch tun.

+0

Danke - es ist wirklich großartig, von einem Entwickler der Bibliothek zu hören! Mein Grund dafür war zu sehen, ob Eigen 'm0.transpose() * m1.transpose() * m2 * m3' richtig optimieren konnte, wenn sie brauchbare Formen haben - daher wollte ich alles bis zur letzten Minute im Expression-Raum behalten. Ist es aufgrund der Funktionsweise von Templates so, dass ich keine choleske Dekomposition eines GeneralProdukts nehmen kann, ist es nur, dass sich noch niemand darum gekümmert hat, es zu Eigen hinzuzufügen oder gibt es einen Grund, warum es dumm ist? – c0g

6

Das Problem ist, dass die erste Multiplikation eine Eigen::GeneralProduct anstelle einer MatrixXd zurückgibt und auto den Rückgabetyp abholt. Sie können implizit einen MatrixXd von einem Eigen::GeneralProduct erstellen, also wenn Sie den Typ explizit deklarieren, funktioniert es richtig. Siehe http://eigen.tuxfamily.org/dox/classEigen_1_1GeneralProduct.html

EDIT: Ich bin kein Experte auf das Eigenprodukt oder Leistungsmerkmale des Castings. Ich habe nur die Antwort aus der Fehlermeldung vermutet und aus der Online-Dokumentation bestätigt. Profiling ist immer die beste Option, um die Leistung verschiedener Teile Ihres Codes zu überprüfen.

+0

Gibt es einen Performance-Hit beim Casting auf MatrixXd? Offensichtlich wird es in diesem eingeschränkten Beispiel minimal sein, aber im wirklichen Leben? Was würden Sie sagen, die Lösung ist hier - mit ProductReturnType für diese würde scheinen, ein bisschen verrückt zu gehen, wenn Sie den 'c.tranpose() * c 'Schritt tun. – c0g

2

Ich bin kein Experte bei Eigen, aber Bibliotheken wie diese geben häufig Proxy-Objekte aus Operationen zurück und verwenden dann implizite Konvertierung oder Konstruktoren, um die eigentliche Arbeit zu erzwingen. (Ausdruckvorlagen sind ein extremes Beispiel dafür.) Dies vermeidet unnötiges Kopieren der vollständigen Matrix von Daten in vielen Situationen. Unglücklicherweise ist ziemlich glücklich, einfach ein Objekt des Proxy-Typs zu erstellen, das normalerweise Benutzer der Bibliothek niemals explizit deklarieren würden. Da Sie letztlich die Zahlen berechnen müssen, gibt es keinen performativen Treffer von Casting auf MatrixXd. (Scott Meyers, in "Effective Moderne C++", gibt das zugehörige Beispiel für die Verwendung auto mit vector<bool>, wo operator[](size_t i) einen Proxy zurückgibt.)

0

NICHT auto mit Eigen Ausdrücke verwenden DO. Ich stieß auf noch mehr „dramatisch“ Probleme mit diesem vor, siehe

eigen auto type deduction in general product

und wurde von einem des Eigen Schöpfer (Gael) nicht verwenden auto mit Eigen Ausdrücken beraten.

Die Umwandlung von einem Ausdruck in einen bestimmten Typ wie MatrixXd sollte extrem schnell sein, es sei denn, Sie möchten eine faule Auswertung (da bei der Besetzung der Ergebnis ausgewertet wird).

+0

Ich denke, das ist etwas anders als Ihres. Sie geben ein Objekt von 'Id' zurück, das wiederum mit 'autoresult' referenziert wird - dann verschwindet das Objekt von 'Id' und 'autoresult' verweist auf etwas, das nicht beendet wird. 'c.transpose()' verweist auf 'c', was immer noch existiert, damit mein' auto cTc' nicht auf etwas verweist, das nicht existiert - ähnlich wie ggael als eine Lösung anbietet, um 'auto id = Id (Foo, 4) '. Ich wollte testen, ob Eigen korrekt feststellen kann, dass 'c.transpose() * c = b.transpose() * a.transpose() * a * b' die Mathematik vereinfacht, also idealerweise als Ausdruck beibehalten würde. – c0g

+0

Ja, Sie haben Recht, aber wenn ich die Erklärung nicht bekommen habe, hätte ich absolut keine Ahnung, warum sich der Code abnormal verhielt.Und da Eigen viele verschiedene Typen hat (im Prinzip ist jeder Ausdruck ein anderer Typ), plus indirekter Referenzierung, macht "Auto" die Dinge kompliziert, da Sie nicht wirklich wissen, was unter der Haube passiert und wie diese Ausdrücke ausgewertet werden. – vsoftco