Matrixklasse - kleiner Fehler



  • Keine Ahnung was du meinst, aber #define ist (fast) nie eine gute Idee. 😉 - Du stehst unter Zeitdruck? Was genau musst du denn eigentlichen machen? (Wie groß sind deine Matrizen?)



  • Muss viel mit Matrizen hantieren, die wiederrum Ein- und Ausgabe von Funktionen sind. Habe mir auch schon mal das mex-Interface angeschaut. Möchte Matlab und C++ koppeln.

    Die Matrizen sind, mh... in Matlab benutze ich schon Sparematrizen, weil diese schon 240.000x240.000 groß sein können. Das ist aber eher schon das Maximale, was ich benötige.

    Und wegen den Templates: wenn ich weiß, dass ich nur double-Zahlen habe, kann ich mir die ja sparen. Für mich sind Templates da nur logisch, wenn ich ab und zu auch Integer-Zahlen haben, und damit weniger Speicherplatz benötige.



  • Bei der Matrixgröße kannst du jede konventionelle Matrixklasse vergessen. Überleg doch mal, wie viel Speicher das ist. 460800000000 Byte = 429 Gigabyte. Hast du so viel RAM? Wohl nicht. Also das was du da aus dem Forum kopiert hast (und leider auch meine Variante), sind schon mal völlig aus dem Spiel. Die großen Bibliotheken sollten allerdings uU damit zurecht kommen, die müssten das komprimieren. Was genau du machen willst, und wozu du C++ brauchst wenn du Matlab hast, hast du uns allerdings noch nicht verraten.



  • RobertB schrieb:

    Und wegen den Templates: wenn ich weiß, dass ich nur double-Zahlen habe, kann ich mir die ja sparen. Für mich sind Templates da nur logisch, wenn ich ab und zu auch Integer-Zahlen haben, und damit weniger Speicherplatz benötige.

    Vllt, möchtes du ja irgendwann ma was genaueres benutzen als double ... Dann fängst du wieder von vorne an?



  • cooky451 schrieb:

    Wenn du wirklich Performance brauchst, dann nimm eine der großen Mathebibliotheken. (Eigen, MTL, http://scicomp.stackexchange.com/questions/351/recommendations-for-a-usable-fast-c-matrix-library)

    Dass die Klasse schlecht ist sieht man schon daran, dass sie kein Template ist, int statt size_t und rohe Pointer statt std::vector benutzt werden. Das ist nur der erste kleine Blick.

    Ansonsten hier eine Vorlage für eine ordentliche Klasse:

    template <typename T>
    class matrix
    {
      std::vector<T> data_;
      std::size_t y_size_;
    
    public:
      matrix(std::size_t x_size, std::size_t y_size)
        : data_(x_size * y_size)
        , y_size(y_size)
      {}
      
      const T& operator () (std::size_t i, std::size_t j) const
      {
        return data_[i * y_size_ + j];
      }
    
      T& operator () (std::size_t i, std::size_t j)
      {
        return data_[i * y_size_ + j];
      }
    };
    

    Tadaa.. Operatoren kannste selbst basteln. 😉

    Ich habe mal diese Klasse ausprobiert, aber es tritt ein Fehler auf. Folgendes habe ich gemacht:

    #include <iostream>
    using namespace std;
    
    template <typename T>
    class matrix
    {
      vector<T> data_;
      size_t y_size_;
    
    public:
      matrix(size_t x_size, size_t y_size)
        : data_(x_size * y_size)
        , y_size(y_size)
      {}
    
      const T& operator () (size_t i, size_t j) const
      {
        return data_[i * y_size_ + j];
      }
    
      T& operator () (size_t i, size_t j)
      {
        return data_[i * y_size_ + j];
      }
    };
    
    int main() {
    
    	return 0;
    };
    

    Könnt ihr mir bitte sagen, was ich falsch gemacht habe?



  • <vector> nicht #included. https://ideone.com/p6RKY0
    Und meinen Beitrag oben nicht gelesen. Bei der Matrixgröße kannst du diesen Ansatz sicher vergessen.



  • cooky451 schrieb:

    <vector> nicht #included. https://ideone.com/p6RKY0
    Und meinen Beitrag oben nicht gelesen. Bei der Matrixgröße kannst du diesen Ansatz sicher vergessen.

    Deinen Beitrag habe ich gelesen. Der Ansatz war nur aus Interesse. Aber dieser ist dann allen Anschein nicht geeignet.



  • Hallo RobertB,

    bei der angestrebten Größe der Matrix kommst Du nur mit einer Sparse Matrix weiter - vorausgesetzt natürlich, Dein Problem, welches Du lösen willst, erzeugt auch nur dünn besetzte Matrizen.
    boost.ublas bietet sparse matrix an. Ich selber habe aber keine Erfahrungen damit.

    Wenn Du an einer einfach gestrickten Lösung interessiert bist, so kannst Du Dir auch selber was bauen. Im Prinzip lässt sich so eine Matrix mit einem vector von maps lösen. So wäre zumindest die Implementierung relativ straight forward.

    #include <iostream>
    #include <map>
    #include <vector>
    #include <cassert>
    #include <iomanip> // setw
    #include <numeric> // accumulate
    
    template< typename T >
    class SparseMatrix
    {
        typedef std::map< std::size_t, T > Row; // key -> column
        typedef std::vector< Row > Field; // index -> row
    
        struct Ref
        {
            Ref( Row& row, std::size_t columnIdx )
                : row_( &row )
                , column_( columnIdx )
            {}
            Ref& operator=( const T& element )
            {
                if( element == T() )
                {
                    auto iElement = row_->find( column_ );
                    if( iElement != end(*row_) )
                        row_->erase( iElement );
                }
                else
                    (*row_)[column_] = element;
                return *this;
            }
            operator T() const
            {
                auto iElement = row_->find( column_ );
                return iElement == end(*row_)? T(): iElement->second;
            }
        private:
            Row* row_;
            std::size_t column_;
        };
    
    public:
        SparseMatrix( std::size_t rows, std::size_t columns )
            : columns_( columns )
            , field_( rows )
        {}
        std::size_t Columns() const { return columns_; }
        std::size_t Rows() const { return field_.size(); }
    
        // --  Element Zugriff
        T operator()( std::size_t rowIdx, std::size_t columnIdx ) const
        {
            assert( rowIdx < Rows() );
            assert( columnIdx < Columns() );
            auto iElement = field_[rowIdx].find( columnIdx );
            return iElement == end(field_[rowIdx])? T(): iElement->second;
        }
        Ref operator()( std::size_t rowIdx, std::size_t columnIdx )
        {
            assert( rowIdx < Rows() );
            assert( columnIdx < Columns() );
            return Ref( field_[rowIdx], columnIdx );
        }
    
        // --  Arithmetik
        SparseMatrix& operator+=( const SparseMatrix& b );
        SparseMatrix& operator-=( const SparseMatrix& b );
    
        // --  Ausgabe
        template< typename E, typename Traits > friend
            std::basic_ostream< E, Traits >& operator<<( std::basic_ostream< E, Traits >& out, const SparseMatrix& mx )
        {
            std::streamsize width = out.width();
            if( width == 0 )
                width = 4;
            for( auto iRow = begin(mx.field_); iRow != end(mx.field_); ++iRow )
            {
                auto iElement = begin(*iRow);
                for( std::size_t columnIdx = 0; columnIdx < mx.columns_; ++columnIdx )
                {
                    out << " " << std::setw( width );
                    if( iElement != end(*iRow) && iElement->first == columnIdx )
                    {
                        out << iElement->second;
                        ++iElement;
                    }
                    else
                        out << T();
                }
                out << "\n";
            }
            return out << std::flush;
        }
    
        std::size_t NoElements() const // Anzahl belegter Elemente; für Debugzwecke
        {
            return std::accumulate( begin(field_), end(field_), std::size_t(0),
                []( std::size_t sum, const Row& row )-> std::size_t { return sum + row.size(); } );
        }
    
    private:
        std::size_t columns_;
        Field field_;
    };
    

    Was bei dieser Matrixgröße ein No-GO ist, wenn Du jetzt zwei Schleifen baust, die über alle Zeilen und Spalte laufen und dann irgendwelche Operationen ausführen. Bei einer Kantenlänge von 240.000 Elementen einer quadratischen Matrix schlägt jede NanoSekunde der Operation mit einer knappen Minute Laufzeit zu Buche.
    D.h. man muss sich auch bei der Operation die dünne Besetzung der Matrix zu Nutze machen.

    Als Demonstration habe ich die Addition zweier Matrizen implementiert:

    template< typename T >
    SparseMatrix< T >& SparseMatrix< T >::operator+=( const SparseMatrix< T >& b ) {
        assert( Rows() == b.Rows() );
        assert( Columns() == b.Columns() );
        auto iRowDst = begin(field_);
        for( auto iRow = begin(b.field_); iRow != end(b.field_); ++iRow, ++iRowDst )
        {
            auto iElementDst = begin(*iRowDst);
            for( auto iElement = begin(*iRow); iElement != end(*iRow); ++iElement )
            {
                for( ; iElementDst != end(*iRowDst) && iElementDst->first < iElement->first; ++iElementDst )
                    ;
                if( iElementDst != end(*iRowDst) && iElementDst->first == iElement->first )
                {
                    if( (iElementDst->second += iElement->second) == T() ) // Auslöschung beachten
                        iElementDst = iRowDst->erase( iElementDst );
                    else
                        ++iElementDst;
                }
                else
                    iRowDst->insert( iElementDst, *iElement );
            }
        }
        return *this;
    }
    

    Die Subtraktion ist identisch - nur in Zeile 15 das += durch ein -= ersetzen.

    Bei mir (VS10) benötigt eine leere map<size_t,double> 20 Bytes Speicher. Damit hat eine leere SparseMatrix mit 240.000 Zeilen einem Speicherbedarf von knapp 5MByte. Für jedes Element kommen dann in der Größenordnung 20Byte hinzu (ein Knoten in der Map).
    Wahrscheinlich ist es auch schlauer die Map durch eine sortierte Liste von pairstd::size_t,T (evt. forward_list) zu ersetzen. Aber mit der Map war es einfacher zu codieren.

    Abschließend eine Demo, die die Funktion zeigt.

    int main()
    {
        using namespace std;
        SparseMatrix< double > mx( 8, 5 );
        mx( 0, 1 ) = 3.4;
        mx( 7, 4 ) = 99.;
        cout << fixed << setprecision( 1 );
        cout << "\n" << mx << "Anzahl Elemente !=0: " << mx.NoElements() << endl;
        for( std::size_t c = 0; c < mx.Columns(); ++c )
            mx( 6, c ) = (c+1)*1.1;
        cout << "\n" << mx << "Anzahl Elemente !=0: " << mx.NoElements() << endl;
        mx( 6, 1 ) = 0.;
        cout << "\n" << mx << "Anzahl Elemente !=0: " << mx.NoElements() << endl;
        cout << "Der Wert von mx(6,2) ist: " << mx(6,2) << endl;
    
        SparseMatrix< double > m2( 8, 5 );
        m2(2,0) = 2.4;
        m2(6,2) = 3.3;
        m2(6,4) = -5.5;
        mx += m2;
        cout << "\n" << mx << "Anzahl Elemente !=0: " << mx.NoElements() << endl;
    
        return 0;
    }
    

    Wie viele Elemente!=0 enthält Deine Matrix so in etwa? Und welche Operationen benötigst Du für die Matrix?

    Gruß
    Werner



  • Wow, ich bin beeindruckt vom Code! Vielen Dank!

    Dünn besetze Matrizen habe ich. Muss dann noch ein Gleichungssystem lösen. Aber dafür werde ich wahrscheinlich Matlab nutzen und das rüberziehen. Oder ich benutze mit dem Mex-Interface eine Build-in Funktion von Matlab in C++ für das Lösen.

    Weitere Operationen brauche ich eig. nicht. Es geht dann doch hauptsächlich um das Füllen und Bearbeiten einzelner Einträge dieser großen Matrix. Das geht ja alles mit dem Code, vielen Dank. Ich muss diesen nur noch ganz verstehen :).

    Und leider brauche ich eine ganz große Schleife. Die erste geht nämlich über die Dimension der Länge bzw. Breite (habe eine quadratische Matrix) der Matrix, die zweite dann von 1 bis 3. Also im schlimmsten Fall habe ich 240.000*3 Durchläufe.



  • Wenn du nen matlab interface hast bietet sich Werners Implementierung aus Performance Gründen nicht an. Soweit ich weiss brauchst du für Matlab alles in einem zusammenhängenden Speicher. D.h. ein haufen Kopieroperation werden fällig.
    (nanosekunden und so ... :D)

    Google einfach mal nach "Compressed row format" bzw. "Compressed column format".



  • Du hast uns immer noch nicht verraten warum du C++ benutzen willst wenn du doch matlab hast? 😉



  • cooky451 schrieb:

    Du hast uns immer noch nicht verraten warum du C++ benutzen willst wenn du doch matlab hast? 😉

    Aus Performancegründen soll ich C++ benutzen. Man sagte mir, dass wegen den Schleifen C++ schneller ist. Aber wegen den Kopiervorgängen habe ich mir auch schon "Sorgen" gemacht, wie das beim MEX-Interface abläuft.

    Ich soll das Programm so schnell wie möglich machen, das ist das Ziel. Ich würde lieber bei Matlab bleiben :).



  • Mein Tipp: Bleib einfach bei Matlab. Nicht, dass ich groß Ahnung davon hätte, aber ich weiß ein paar Dinge:
    - Die Leute die Matlab implementiert haben, wissen bestimmt was sie tun.
    - Du hast keine Ahnung von C++.
    -> Es dauert wahrscheinlich mindestens einige Wochen bis du gut genug bist, um Matlab performancemäßig zu schlagen, auch mit Bibliotheken die für dich das Rechnen übernehmen.

    Die zeit ist recht grob geschätzt, da ich weder dich noch Matlab gut genug kenne, aber das sollte man trotzdem mal erwähnen. Abgesehen davon: Wenn du wirklich, wirklich Performance brauchst, warum lädst du dann wieder Sachen nach Matlab? Das bringt doch nichts. Wenn, dann rechne gleiche alles in C++ aus. Nimm Eigen oder MTL und lass die möglichst alles rechnen, was die können. Denn deren Algorithmen und Implementierungen sind mit an Sicherheit grenzender Wahrscheinlichkeit deutlich schneller als alles was du oder wir hier mal eben zusammenbasteln könnten.



  • Das mit Matlab entscheide leider nicht ich allein, sondern meine "Chefs". Aber Danke für Deinen Rat.

    Denn, ich habe mir etwas überlegt: ich schmeiße ganz am Anfang in Matlab erstellte Daten in C++ (mit build-in Funktionen von Matlab), und am Ende hole ich mir das Ergebnis und lasse es in Matlab plotten. Dann habe ich nur die beiden Schnittstellen.

    Und, was hälts Du davon, dass ich mein ganzes Matlabprogramm am Ende einfach mit einem Coder in C++-Code übersetze? Matlab bietet dafür eigens eine Lösung. Ob das dann schneller ist?

    Auf jeden Fall ziehe ich den Hut vor euch C++-Programmieren! Das ist doch die wahre Kunst.



  • Das wollt ich gerade vorschlagen. Das moderne Matlab kann C++ Code exportieren (vorausgesetzt ihr habt die entsprechenden Lizenzen).

    Ich habe zwar nur seeehhr wenig ahnung vom mex-Interface. Aber könntest du nicht rauskriegen wie Matlab die Sparse-Matrizen implementiert (ich tippe auf eine von den von mir vorgeschlagenden Formaten) und einfach für diese Implementierung die entsprechend Funktionen schreiben die du brauchst (Addition , Multiplikation, Assemblierung und was sonst noch anfällt. Bei den ersten beiden würd ich ma vermuten das sind schon build-ins).



  • ScottZhang schrieb:

    Wenn du nen matlab interface hast bietet sich Werners Implementierung aus Performance Gründen nicht an. Soweit ich weiss brauchst du für Matlab alles in einem zusammenhängenden Speicher. D.h. ein haufen Kopieroperation werden fällig.
    (nanosekunden und so ... :D)

    Google einfach mal nach "Compressed row format" bzw. "Compressed column format".

    Dass es dafür auch Lösungen gibt, die nur mit Arrays und Indizes funktionieren war ja klar; die gute alte Fortran-Welt lässt grüßen. Aber dass es eine Datenstruktur als Quasi-Standard bis in die Wikipedia geschafft; damit hätte ich jetzt nicht gerechnet.
    Ich habe nicht herausbekommen, ob MatLab die Sparse matrix im CRS abspeichert, aber ich habe mir mal die Mühe gemacht, meine class SparseMatrix auf dieses Format umzustellen.
    Es ist nicht wirklich mehr Code geworden, aber es war schwieriger zu implementieren. Ich habe bestimmt mehr als die doppelte Zeit gebraucht wie für die std::map-Lösung.

    Keine Ahnung, ob das anderen auch so geht, aber mit den STL-Container kann man auf einem höheren Abstraktionsniveau codieren, und das geht bei mir wesentlich flüssiger.

    Die class SparseMatrixc im CRS Datenformat:

    #include <algorithm> // for_each, find_if,..
    #include <cassert>
    #include <iterator> // distance
    #include <ostream>
    #include <vector>
    #include <utility> // std::pair<>
    
    template< typename T >
    class SparseMatrix
    {
        struct Ref
        {
            Ref( SparseMatrix& mx, std::size_t rowIdx, std::size_t columnIdx )
                : mx_( &mx )
                , rowIdx_( rowIdx )
                , columnIdx_( columnIdx )
            {}
            Ref& operator=( const T& val )
            {
                auto element = mx_->find_element( rowIdx_, columnIdx_ );
                if( element.second )
                {
                    if( (mx_->val_[element.first] = val) == T() )
                        mx_->erase_( rowIdx_, element.first );
                }
                else if( val != T() )
                    mx_->insert_( rowIdx_, columnIdx_, element.first, val );
                return *this;
            }
            operator T() const
            {
                auto element = mx_->find_element( rowIdx_, columnIdx_ );
                return element.second? mx_->val_[element.first]: T();
            }
        private:
            SparseMatrix* mx_;
            std::size_t rowIdx_;
            std::size_t columnIdx_;
        };
        friend struct Ref;
    
    public:
        SparseMatrix( std::size_t rows, std::size_t columns )
            : columns_( columns )
            , val_()
            , colInd_()
            , rowPtr_( rows+1 )
        {}
        std::size_t Columns() const { return columns_; }
        std::size_t Rows() const { return rowPtr_.size()-1; }
    
        // --  Element Zugriff
        T operator()( std::size_t rowIdx, std::size_t columnIdx ) const
        {
            assert( rowIdx < Rows() );
            assert( columnIdx < Columns() );
            auto element = find_element( rowIdx, columnIdx );
            return element->second? val_[element->first]: T();
        }
        Ref operator()( std::size_t rowIdx, std::size_t columnIdx )
        {
            assert( rowIdx < Rows() );
            assert( columnIdx < Columns() );
            return Ref( *this, rowIdx, columnIdx );
        }
    
        // --  Ausgabe
        template< typename E, typename Traits > friend
            std::basic_ostream< E, Traits >& operator<<( std::basic_ostream< E, Traits >& out, const SparseMatrix& mx )
        {
            std::streamsize width = out.width();
            if( width == 0 )
                width = 4;
            std::size_t i = 0; // Index in mx.colInd_[] & mx.val_[]
            for( auto iRow = begin(mx.rowPtr_); iRow != end(mx.rowPtr_)-1; ++iRow )
            {
                for( std::size_t columnIdx = 0; columnIdx < mx.columns_; ++columnIdx )
                {
                    (out << " ").width( width );
                    if( i < *(iRow+1) && mx.colInd_[i] == columnIdx )
                        out << mx.val_[i++];
                    else
                        out << T();
                }
                out << "\n";
            }
            return out << std::flush;
        }
    
        std::size_t NoElements() const // Anzahl belegter Elemente; für Debugzwecke
        {
            return val_.size();
        }
    
    private:
        void insert_( std::size_t rowIdx, std::size_t columnIdx, std::size_t i, const T& val )
        {
            assert( rowPtr_[rowIdx] <= i );
            assert( i <= rowPtr_[rowIdx+1] );
            assert( std::none_of( begin(colInd_) + rowPtr_[rowIdx], begin(colInd_) + rowPtr_[rowIdx+1],
                [columnIdx]( std::size_t c )->bool { return c == columnIdx; } ) );
            if( val != T() )
            {
                val_.insert( begin(val_) + i, val );
                colInd_.insert( begin(colInd_) + i, columnIdx );
                std::for_each( begin(rowPtr_) + rowIdx+1, end(rowPtr_), []( std::size_t& idx ) { ++idx; } );
            }
        }
        void erase_( std::size_t rowIdx, std::size_t i )
        {
            assert( i < val_.size() );
            assert( rowPtr_[rowIdx] <= i );
            assert( i < rowPtr_[rowIdx+1] );
            val_.erase( begin(val_) + i );
            colInd_.erase( begin(colInd_) + i );
            std::for_each( begin(rowPtr_) + rowIdx+1, end(rowPtr_), []( std::size_t& idx ) { --idx; } );
        }
        std::pair< std::size_t, bool > find_element( std::size_t rowIdx, std::size_t columnIdx ) const
        {
            auto iColumn = std::find_if( begin(colInd_) + rowPtr_[rowIdx], begin(colInd_) + rowPtr_[rowIdx+1],
                [columnIdx]( std::size_t c )->bool { return columnIdx <= c; } );
            return std::make_pair( std::size_t( std::distance( begin(colInd_), iColumn ) )
                , iColumn != begin(colInd_) + rowPtr_[rowIdx+1] && *iColumn == columnIdx );
        }
    
        // --   Members
        std::size_t columns_;
        // --   Datenstruktur der Sparse Matrix im CRS -> <http://de.wikipedia.org/wiki/Compressed_Row_Storage>
        std::vector< T > val_;
        std::vector< std::size_t > colInd_;
        std::vector< std::size_t > rowPtr_;
    };
    

    Der Speicherbedarf dürfte niedriger sein (ca. bei 20%-30% der vorherigen Lösung) dafür ist das Einfügen neuer Elemente nicht mehr performant. Zumindest nicht wenn man sie einzeln zuweist.

    .. es fehlt noch die Addition. Wenn ihr das Demo-main aus meinem vorherigen Posting benutzt, so lasst die Addition einfach weg, oder schreibt Euch eine - viel Spaß damit.

    Gruß
    Werner



  • Sieht wohl so aus als würde Matlab die columns compressen: Matlab: mxCreateSparse



  • CSR ist auch in ublas implmentiert. das Problem an CSR ist wie gesagt das Einfügen bei matrix-vektor und matrix-matrix produkten.

    Ein netter Trick dafür ist etwas mehr Speicher als benötigt zu reservieren und die Elemente bei der Berechnung des Produkts am Ende unsortiert einzufügen. Am Schluss wird dann gemerged.


Anmelden zum Antworten