/* mmmatrix.hpp * Part of Mathematical library built (ab)using Modern C++ 17 abstractions. * * This library is not intended to be _performant_, it does not contain * hand written SMID / SSE / AVX optimizations. It is instead an example * of highly inefficient (but abstract!) code, where matrices can contain any * data type. * * Naoki Pross * 2018 ~ 2019 */ #pragma once #include #include #include #include #include #include namespace mm { template class basic_matrix; // TODO, not sure it's a good idea //template //class transposed_matrix; /* specialization of basic_matrx for Cols = 1 */ template class row_vec; /* specialization of basic_matrx for Rows = 1 */ template class col_vec; /* shorter name for basic_matrix */ template class matrix; /* specialization of basic_matrix for Rows == Cols */ template class square_matrix; template class diagonal_matrix; /* * Iterators */ template class vector_iterator; template class diag_iterator; template class const_vector_iterator; template class const_diag_iterator; } /* Non-const Iterators */ template class mm::vector_iterator { std::size_t index; // variable index mm::basic_matrix& M; const std::size_t position; // fixed index const bool direction; // true = row, false = column public: template friend class vector_iterator; vector_iterator(mm::basic_matrix& M, std::size_t position, bool direction); mm::vector_iterator operator++() { vector_iterator it = *this; ++index; return it; } mm::vector_iterator operator--() { vector_iterator it = *this; --index; return it; } mm::vector_iterator& operator++(int) { ++index; return *this; } mm::vector_iterator& operator--(int) { --index; return *this; } bool operator==(const mm::vector_iterator& other) const { return index == other.index; } bool operator=!(const mm::vector_iterator& other) const { return index != other.index; } T& operator*() const; T& operator[](std::size_t); }; template class diag_iterator { std::size_t index; // variable index mm::square_matrix& M; const int position; // fixed diagonal index public: template friend class diag_iterator; diag_iterator(mm::square_matrix& M, std::size_t position, bool direction); mm::diag_iterator operator++() { diag_iterator it = *this; ++index; return it; } mm::diag_iterator operator--() { diag_iterator it = *this; --index; return it; } mm::diag_iterator& operator++(int) { ++index; return *this; } mm::diag_iterator& operator--(int) { --index; return *this; } bool operator==(const mm::diag_iterator& other) const { return index == other.index; } bool operator=!(const mm::diag_iterator& other) const { return index != other.index; } T& operator*() const; }; /* Const Iterators */ template class mm::const_vector_iterator { std::size_t index; // variable index const mm::basic_matrix& M; const std::size_t position; // fixed index const bool direction; // true = row, false = column public: const_vector_iterator(mm::basic_matrix& M, std::size_t position, bool direction); mm::const_vector_iterator operator++() { vector_iterator it = *this; ++index; return it; } mm::const_vector_iterator operator--() { vector_iterator it = *this; --index; return it; } mm::const_vector_iterator& operator++(int) { ++index; return *this; } mm::const_vector_iterator& operator--(int) { --index; return *this; } bool operator==(const mm::const_vector_iterator& other) const { return index == other; } bool operator=!(const mm::const_vector_iterator& other) const { return index != other; } const T& operator*() const; const T& operator[](std::size_t) const; }; template class const_diag_iterator { std::size_t index; // variable index const mm::square_matrix& M; const int position; // fixed diagonal index public: template friend class const_diag_iterator; const_diag_iterator(const mm::square_matrix& M, std::size_t position, bool direction); mm::const_diag_iterator operator++() { const_diag_iterator it = *this; ++index; return it; } mm::const_diag_iterator operator--() { const_diag_iterator it = *this; --index; return it; } mm::const_diag_iterator& operator++(int) { ++index; return *this; } mm::const_diag_iterator& operator--(int) { --index; return *this; } bool operator==(const mm::const_diag_iterator& other) const { return index == other.index; } bool operator=!(const mm::const_diag_iterator& other) const { return index != other.index; } const T& operator*() const; }; /* * Matrix class */ template class mm::basic_matrix { public: using type = T; template friend class mm::basic_matrix; template friend class mm::vector_iterator; static constexpr std::size_t rows = Rows; static constexpr std::size_t cols = Cols; basic_matrix(); // from initializer_list basic_matrix(std::initializer_list> l); // copyable and movable basic_matrix(const basic_matrix& other); basic_matrix(basic_matrix&& other); // copy from another matrix template basic_matrix(const basic_matrix& other); // access data virtual T& at(std::size_t row, std::size_t col); virtual const T& at(std::size_t row, std::size_t col) const; // allows to access a matrix M at row j col k with M[j][k] virtual auto operator[](std::size_t index); void swap_rows(std::size_t x, std::size_t y); void swap_cols(std::size_t x, std::size_t y); // mathematical operations // TODO, simply switch iteration mode virtual basic_matrix transposed() const; inline basic_matrix td() const { return transposed(); } /// downcast to square matrix static inline constexpr bool is_square() { return (Rows == Cols); } inline constexpr square_matrix to_square() const { static_assert(is_square()); return static_cast>(*this); } /// downcast to row_vector static inline constexpr bool is_row_vec() { return (Cols == 1); } inline constexpr row_vec to_row_vec() const { static_assert(is_row_vec()); return static_cast>(*this); } /// downcast to col_vector static inline constexpr bool is_col_vec() { return (Rows == 1); } inline constexpr col_vec to_col_vec() const { static_assert(is_col_vec()); return static_cast>(*this); } protected: template basic_matrix(ConstIterator begin, ConstIterator end); private: std::array data; }; template mm::basic_matrix::basic_matrix() { std::fill(data.begin(), data.end(), 0); } template mm::basic_matrix::basic_matrix( std::initializer_list> l ) { assert(l.size() == Rows); auto data_it = data.begin(); for (auto&& row : l) { data_it = std::copy(row.begin(), row.end(), data_it); } } template mm::basic_matrix::basic_matrix( const mm::basic_matrix& other ) : data(other.data) {} template mm::basic_matrix::basic_matrix( mm::basic_matrix&& other ) : data(std::forward(other.data)) {} template template mm::basic_matrix::basic_matrix( const mm::basic_matrix& other ) { static_assert((ORows <= Rows), "cannot copy a taller matrix into a smaller one" ); static_assert((OCols <= Cols), "cannot copy a larger matrix into a smaller one" ); std::fill(data.begin(), data.end(), 0); for (unsigned row = 0; row < Rows; row++) for (unsigned col = 0; col < Cols; col++) this->at(row, col) = other.at(row, col); } /* protected construtor */ template template mm::basic_matrix::basic_matrix( ConstIterator begin, ConstIterator end ) { assert(static_cast(std::distance(begin, end)) >= ((Rows * Cols))); std::copy(begin, end, data.begin()); } /* member functions */ template T& mm::basic_matrix::at(std::size_t row, std::size_t col) { assert(row < Rows); // "out of row bound" assert(col < Cols); // "out of column bound" return data[row * Cols + col]; } template const T& mm::basic_matrix::at(std::size_t row, std::size_t col) const { assert(row < Rows); // "out of row bound" assert(col < Cols); // "out of column bound" return data[row * Cols + col]; } template auto mm::basic_matrix::operator[](std::size_t index) { if constexpr (is_row_vec() || is_col_vec()) { return data.at(index); } else { return row_vec( data.cbegin() + (index * Cols), data.cbegin() + ((index + 1) * Cols) + 1 ); } } template void mm::basic_matrix::swap_rows(std::size_t x, std::size_t y) { if (x == y) return; for (unsigned col = 0; col < Cols; col++) std::swap(this->at(x, col), this->at(y, col)); } template void mm::basic_matrix::swap_cols(std::size_t x, std::size_t y) { if (x == y) return; for (unsigned row = 0; row < rows; row++) std::swap(this->at(row, x), this->at(row, y)); } template mm::basic_matrix mm::basic_matrix::transposed() const { mm::basic_matrix result; for (unsigned row = 0; row < M; row++) for (unsigned col = 0; col < N; col++) result.at(col, row) = this->at(row, col); return result; } /* operator overloading */ template mm::basic_matrix operator+( const mm::basic_matrix& a, const mm::basic_matrix& b ) { mm::basic_matrix result; for (unsigned row = 0; row < Rows; row++) for (unsigned col = 0; col < Cols; col++) result.at(row, col) = a.at(row, col) + b.at(row, col); return result; } template mm::basic_matrix operator*( const mm::basic_matrix& m, const T& scalar ) { mm::basic_matrix result; for (unsigned row = 0; row < Rows; row++) for (unsigned col = 0; col < Cols; col++) result.at(row, col) = m.at(row, col) * scalar; return result; } template mm::basic_matrix operator*( const T& scalar, const mm::basic_matrix& m ) { return m * scalar; } template mm::basic_matrix operator*( const mm::basic_matrix& a, const mm::basic_matrix& b ) { static_assert(P1 == P2, "invalid matrix multiplication"); mm::basic_matrix result; // TODO: use a more efficient algorithm for (unsigned row = 0; row < M; row++) for (unsigned col = 0; col < N; col++) for (unsigned k = 0; k < P1; k++) result.at(row, col) = a.at(row, k) * b.at(k, col); return result; } template mm::basic_matrix operator-( const mm::basic_matrix& a, const mm::basic_matrix& b ) { return a + (static_cast(-1) * b); } template std::ostream& operator<<(std::ostream& os, const mm::basic_matrix& m) { for (unsigned row = 0; row < Rows; row++) { os << "[ "; for (unsigned col = 0; col < (Cols -1); col++) { os << std::setw(NumW) << m.at(row, col) << ", "; } os << std::setw(NumW) << m.at(row, (Cols -1)) << " ]\n"; } return os; } /* * derivated classes */ /* row vector specialization */ template class mm::row_vec : public mm::basic_matrix { public: using mm::basic_matrix::basic_matrix; }; /* column vector specialization */ template class mm::col_vec : public mm::basic_matrix { public: using mm::basic_matrix::basic_matrix; }; /* general specialization (alias) */ template class mm::matrix : public mm::basic_matrix { public: using mm::basic_matrix::basic_matrix; }; /* * transposed matrix format * TODO: write this class, or put a bool flag into the original one */ template class mm::transposed_matrix : public mm::basic_matrix { public: using mm::basic_matrix::basic_matrix; virtual T& at(std::size_t row, std::size_t col) override { return mm::basic_matrix::at(col, row); } virtual const T& at(std::size_t row, std::size_t col) const override { return mm::basic_matrix::at(col, row); } // allows to access a matrix M at row j col k with M[j][k] virtual auto operator[](std::size_t index) override { // TODO, return other direction iterator } } /* square matrix specialization */ template class mm::square_matrix : public mm::basic_matrix { public: using mm::basic_matrix::basic_matrix; /// in place transpose void transpose(); inline void t() { transpose(); } T trace(); inline T tr() { return trace(); } /// in place inverse // TODO, det != 0 // TODO, use gauss jordan for invertible ones void invert(); // TODO, downcast to K-diagonal, user defined cast template operator mm::diagonal_matrix() const { // it's always possible to do it bidirectionally, // without loosing information return dynamic_cast>(*this); } // get the identity of size N static inline constexpr square_matrix identity() { square_matrix i; for (unsigned row = 0; row < N; row++) for (unsigned col = 0; col < N; col++) i.at(row, col) = (row == col) ? 1 : 0; return i; } }; /* * K-diagonal square matrix format * K is bounded between ]-N, N[ */ template class mm::diagonal_matrix : public mm::square_matrix { public: using mm::square_matrix::square_matrix; // TODO, redefine at, operator[] // TODO, matrix multiplication }; template void mm::square_matrix::transpose() { for (unsigned row = 0; row < N; row++) for (unsigned col = 0; col < row; col++) std::swap(this->at(row, col), this->at(col, row)); } template T mm::square_matrix::trace() { T sum = 0; for (unsigned i = 0; i < N; i++) sum += this->at(i, i); return sum; } /* Iterators implementations */ template mm::vector_iterator::vector_iterator(mm::basic_matrix& _M, std::size_t pos, bool dir) index(0), M(_M), position(pos), direction(dir) { assert((dir && pos < Cols) || (!dir && pos < Rows)) } template T& mm::vector_iterator::operator*() const { return (direction) ? M.data[position * Cols + index] : M.data[index * Cols + position]; } template T& mm::vector_iterator::operator[](std::size_t i) { return (direction) ? M.data[position * Cols + i] : M.data[i * Cols + position]; } template mm::diag_iterator::diag_iterator(mm::square_matrix& _M, int pos) index(0), M(_M), position(pos) { assert(abs(pos) < N) // pos bounded between ]-N, N[ } template T& mm::diag_iterator::operator*() const { return (k > 0) ? M.data[(index + position) * Cols + index] : M.data[index * Cols + (index - position)]; } template mm::const_vector_iterator::const_vector_iterator(const mm::basic_matrix& _M, std::size_t pos, bool dir) index(0), M(_M), position(pos), direction(dir) { assert((dir && pos < Cols) || (!dir && pos < Rows)) } template const T& mm::const_vector_iterator::operator*() const { return (direction) ? M.data[position * Cols + index] : M.data[index * Cols + position]; } template const T& mm::const_vector_iterator::operator[](std::size_t i) const { return (direction) ? M.data[position * Cols + i] : M.data[i * Cols + position]; } template mm::const_diag_iterator::const_diag_iterator(const mm::square_matrix& _M, int pos) index(0), M(_M), position(pos) { assert(abs(pos) < N) // pos bounded between ]-N, N[ } template T& mm::const_diag_iterator::operator*() const { return (k > 0) ? M.data[(index + position) * Cols + index] : M.data[index * Cols + (index - position)]; }