805 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			805 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /**
 | |
|  * \file    matrix.hpp
 | |
|  * \brief   A matrix abstraction implementation
 | |
|  *
 | |
|  * \author
 | |
|  *    Christos Choutouridis AEM:8997
 | |
|  *    <cchoutou@ece.auth.gr>
 | |
|  */
 | |
| #ifndef MATRIX_HPP_
 | |
| #define MATRIX_HPP_
 | |
| 
 | |
| #include <type_traits>
 | |
| #include <utility>
 | |
| #include <algorithm>
 | |
| #include <vector>
 | |
| #include <tuple>
 | |
| 
 | |
| namespace mtx {
 | |
| 
 | |
| using std::size_t;
 | |
| 
 | |
| /*
 | |
|  * Small helper to strip types
 | |
|  */
 | |
| template<typename T>
 | |
| struct remove_cvref {
 | |
|     typedef std::remove_cv_t<std::remove_reference_t<T>> type;
 | |
| };
 | |
| template<typename T>
 | |
| using remove_cvref_t = typename remove_cvref<T>::type;
 | |
| 
 | |
| /*!
 | |
|  * Enumerator to denote the storage type of the array to use.
 | |
|  */
 | |
| enum class MatrixType {
 | |
|    DENSE,      /*!< Matrix is dense */
 | |
|    SPARSE,     /*!< Matrix is sparse */
 | |
| };
 | |
| 
 | |
| /*!
 | |
|  * Enumerator to denote the storage type of the array to use.
 | |
|  */
 | |
| enum class MatrixOrder {
 | |
|    COLMAJOR,     /*!< Matrix is column major */
 | |
|    ROWMAJOR,     /*!< Matrix is row major */
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * Forward type declarations
 | |
|  */
 | |
| 
 | |
| template<typename MatrixType> struct MatCol;
 | |
| template<typename MatrixType> struct MatRow;
 | |
| template<typename MatrixType> struct MatVal;
 | |
| 
 | |
|  /*!
 | |
|  * A 2-D matrix functionality over a 1-D array
 | |
|  *
 | |
|  * This is a very thin abstraction layer over a native array.
 | |
|  * This is tested using compiler explorer and our template produce
 | |
|  * almost identical assembly.
 | |
|  *
 | |
|  * The penalty hit we have is due to the fact that we use a one dimension array
 | |
|  * and we have to calculate the actual position from an (i,j) pair.
 | |
|  * The use of 1D array was our intention from the beginning, so the penalty
 | |
|  * was pretty much unavoidable.
 | |
|  *
 | |
|  * \tparam DataType  The underling data type of the array
 | |
|  * \tparam IndexType The underling type for the index variables and sizes
 | |
|  * \tparam Type      The storage type of the array
 | |
|  *    \arg  FULL              For full matrix
 | |
|  *    \arg  SYMMETRIC         For symmetric matrix (we use only the lower part)
 | |
|  */
 | |
| template<typename DataType,
 | |
|          typename IndexType = size_t,
 | |
|          MatrixType Type    = MatrixType::DENSE,
 | |
|          MatrixOrder Order  = MatrixOrder::ROWMAJOR,
 | |
|          bool Symmetric     = false>
 | |
| struct Matrix {
 | |
| 
 | |
|    using dataType    = DataType;                   //!< meta:export of underling data type
 | |
|    using indexType   = IndexType;                  //!< meta:export of underling index type
 | |
|    static constexpr MatrixOrder matrixOrder = Order; //!< meta:export of array order
 | |
|    static constexpr MatrixType matrixType = Type;  //!< meta:export of array type
 | |
|    static constexpr bool symmetric = Symmetric;    //!< meta:export symmetric flag
 | |
| 
 | |
|    /*!
 | |
|     * \name Obj lifetime
 | |
|     */
 | |
|    //! @{
 | |
| 
 | |
|    //! Construct an empty matrix with dimensions rows x columns
 | |
|    Matrix(IndexType rows = IndexType{}, IndexType columns = IndexType{}) noexcept
 | |
|        : vector_storage_(capacity(rows, columns)),
 | |
|          raw_storage_(nullptr),
 | |
|          use_vector_(true),
 | |
|          rows_(rows),
 | |
|          cols_(columns) {
 | |
|        data_ = vector_storage_.data();
 | |
|     }
 | |
| 
 | |
|    //! Construct a matrix by copying existing data with dimensions rows x columns
 | |
|    Matrix(DataType* data, IndexType major_start, IndexType major_length, IndexType minor_length) noexcept
 | |
|       :  vector_storage_(),
 | |
|          raw_storage_ (data + major_start * minor_length),
 | |
|          use_vector_ (false) {
 | |
|       if constexpr (Order == MatrixOrder::ROWMAJOR) {
 | |
|          rows_ = major_length;
 | |
|          cols_ = minor_length;
 | |
|       }
 | |
|       else {
 | |
|          rows_ = minor_length;
 | |
|          cols_ = major_length;
 | |
|       }
 | |
|       data_ = raw_storage_;
 | |
|    }
 | |
| 
 | |
|    //! Construct a matrix using an initializer list
 | |
|    Matrix(IndexType rows, IndexType columns, std::initializer_list<DataType> list)
 | |
|       : vector_storage_(list),
 | |
|         raw_storage_(nullptr),
 | |
|         use_vector_(true),
 | |
|         rows_(rows),
 | |
|         cols_(columns) {
 | |
|       if (list.size() != capacity(rows, columns)) {
 | |
|           throw std::invalid_argument("Matrix initializer list size does not match matrix dimensions.");
 | |
|       }
 | |
|       data_ = vector_storage_.data();
 | |
|    }
 | |
| 
 | |
|    //! move ctor
 | |
|    Matrix(Matrix&& m) noexcept { moves(std::move(m)); }
 | |
|    //! move
 | |
|    Matrix& operator=(Matrix&& m) noexcept { moves(std::move(m)); return *this; }
 | |
|    Matrix(const Matrix& m)             = delete;  //!< No copy ctor
 | |
|    Matrix& operator=(const Matrix& m)  = delete;  //!< No copy
 | |
|    //Matrix(const Matrix& m);
 | |
|    //Matrix& operator=(const Matrix& m) { copy(m); }
 | |
| 
 | |
|    //! @}
 | |
| 
 | |
|    //! \name Data exposure
 | |
|    //! @{
 | |
| 
 | |
| 
 | |
|    //! Get/Set the size of each dimension
 | |
|    IndexType rows() const noexcept { return rows_; }
 | |
|    IndexType columns() const noexcept { return cols_; }
 | |
| 
 | |
|    //! Get the interface size of the Matrix (what appears to be the size)
 | |
|    IndexType size() const {
 | |
|       return rows_ * cols_;
 | |
|    }
 | |
|    //! Set the interface size of the Matrix (what appears to be the size)
 | |
|    IndexType resize(IndexType rows, IndexType columns) {
 | |
|       if (use_vector_) {
 | |
|          rows_ = rows;
 | |
|          cols_ = columns;
 | |
|          vector_storage_.resize(capacity(rows_, cols_));
 | |
|          data_ = vector_storage_.data();
 | |
|       }
 | |
|       return capacity(rows_, cols_);
 | |
|    }
 | |
| 
 | |
|    //! Actual memory capacity of the symmetric matrix
 | |
|    static constexpr IndexType capacity(IndexType M, IndexType N) {
 | |
|       if constexpr (Symmetric)
 | |
|          return (M+1)*N/2;
 | |
|       else
 | |
|          return M*N;
 | |
|    }
 | |
| 
 | |
|    /*
 | |
|     * virtual 2D accessors
 | |
|     */
 | |
|    DataType get (IndexType i, IndexType j) {
 | |
|       if constexpr (Symmetric) {
 | |
|          auto T = [](size_t i)->size_t { return i*(i+1)/2; };          // Triangular number of i
 | |
|          if constexpr (Order == MatrixOrder::COLMAJOR) {
 | |
|             // In column major we use the lower triangle of the matrix
 | |
|             if (i>=j)   return data_[j*rows_ - T(j) + i];  // Lower, use our notation
 | |
|             else        return data_[i*rows_ - T(i) + j];  // Upper, use opposite index
 | |
|          }
 | |
|          else {
 | |
|             // In row major we use the upper triangle of the matrix
 | |
|             if (i<=j)   return data_[i*cols_ - T(i) + j];  // Upper, use our notation
 | |
|             else        return data_[j*cols_ - T(j) + i];  // Lower, use opposite index
 | |
|          }
 | |
|       }
 | |
|       else {
 | |
|          if constexpr (Order == MatrixOrder::COLMAJOR)
 | |
|             return data_[i + j*rows_];
 | |
|          else
 | |
|             return data_[i*cols_ + j];
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    /*!
 | |
|     * \fn DataType set(DataType, IndexType, IndexType)
 | |
|     * \param v
 | |
|     * \param i
 | |
|     * \param j
 | |
|     * \return
 | |
|     */
 | |
|    DataType set (DataType v, IndexType i, IndexType j) {
 | |
|       if constexpr (Symmetric) {
 | |
|          auto T = [](size_t i)->size_t { return i*(i+1)/2; };          // Triangular number of i
 | |
|          if constexpr (Order == MatrixOrder::COLMAJOR) {
 | |
|             // In column major we use the lower triangle of the matrix
 | |
|             if (i>=j)   return data_[j*rows_ - T(j) + i] = v;  // Lower, use our notation
 | |
|             else        return data_[i*rows_ - T(i) + j] = v;  // Upper, use opposite index
 | |
|          }
 | |
|          else {
 | |
|             // In row major we use the upper triangle of the matrix
 | |
|             if (i<=j)   return data_[i*cols_ - T(i) + j] = v;  // Upper, use our notation
 | |
|             else        return data_[j*cols_ - T(j) + i] = v;  // Lower, use opposite index
 | |
|          }
 | |
|       }
 | |
|       else {
 | |
|          if constexpr (Order == MatrixOrder::COLMAJOR)
 | |
|             return data_[i + j*rows_] = v;
 | |
|          else
 | |
|             return data_[i*cols_ + j] = v;
 | |
|       }
 | |
|    }
 | |
| //   DataType operator()(IndexType i, IndexType j) { return get(i, j); }
 | |
|    /*!
 | |
|     * Return a proxy MatVal object with read and write capabilities.
 | |
|     * @param i    The row number
 | |
|     * @param j    The column number
 | |
|     * @return     tHE MatVal object
 | |
|     */
 | |
|    MatVal<Matrix> operator()(IndexType i, IndexType j) noexcept {
 | |
|       return MatVal<Matrix>(this, get(i, j), i, j);
 | |
|    }
 | |
| 
 | |
|    // a basic serial iterator support
 | |
|    DataType* data() noexcept { return data_; }
 | |
|    DataType* begin() noexcept { return data_; }
 | |
|    const DataType* begin() const noexcept { return data_; }
 | |
|    DataType* end() noexcept { return data_ + capacity(rows_, cols_); }
 | |
|    const DataType* end() const noexcept { return data_ + capacity(rows_, cols_); }
 | |
| 
 | |
| //   IndexType begin_idx() noexcept { return 0; }
 | |
| //   IndexType end_idx()   noexcept { return capacity(rows_, cols_); }
 | |
| 
 | |
|    const DataType* data() const noexcept { return data_; }
 | |
|    const IndexType begin_idx() const noexcept { return 0; }
 | |
|    const IndexType end_idx()   const noexcept { return capacity(rows_, cols_); }
 | |
|    //! @}
 | |
| 
 | |
|    /*!
 | |
|     * \name Safe iteration API
 | |
|     *
 | |
|     * This api automates the iteration over the array based on
 | |
|     * MatrixType
 | |
|     */
 | |
|    //! @{
 | |
|    template<typename F, typename... Args>
 | |
|    void for_each_in (IndexType begin, IndexType end, F&& lambda, Args&&... args) {
 | |
|       for (IndexType it=begin ; it<end ; ++it) {
 | |
|          std::forward<F>(lambda)(std::forward<Args>(args)..., it);
 | |
|       }
 | |
|    }
 | |
|    //! @}
 | |
| 
 | |
|    //
 | |
|    void swap(Matrix& src) noexcept {
 | |
|       std::swap(vector_storage_, src.vector_storage_);
 | |
|       std::swap(raw_storage_, src.raw_storage_);
 | |
|       std::swap(data_, src.data_);
 | |
|       std::swap(use_vector_, src.use_vector_);
 | |
|       std::swap(rows_, src.rows_);
 | |
|       std::swap(cols_, src.cols_);
 | |
|    }
 | |
| 
 | |
| private:
 | |
|    //! move helper
 | |
|    void moves(Matrix&& src) noexcept {
 | |
|       vector_storage_ = std::move(src.vector_storage_);
 | |
|       raw_storage_    = std::move(src.raw_storage_);
 | |
|       data_           = std::move(src.data_);
 | |
|       use_vector_     = std::move(src.use_vector_);
 | |
|       rows_           = std::move(src.rows_);
 | |
|       cols_           = std::move(src.cols_);
 | |
|    }
 | |
| 
 | |
|    // Storage
 | |
|    std::vector<DataType>
 | |
|                vector_storage_;  //!< Internal storage (if used).
 | |
|    DataType*   raw_storage_;     //!< External storage (if used).
 | |
|    DataType*   data_;            //!< Pointer to active storage.
 | |
|    bool        use_vector_;      //!< True if using vector storage, false for raw pointer.
 | |
|    IndexType   rows_{};          //!< the virtual size of rows.
 | |
|    IndexType   cols_{};          //!< the virtual size of columns.
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * A simple sparse matrix specialization.
 | |
|  *
 | |
|  * We use CSC format and provide get/set functionalities for each (i,j) item
 | |
|  * on the matrix. We also provide a () overload using a proxy MatVal object.
 | |
|  * This way the user can:
 | |
|  * \code
 | |
|  *    auto v = A(3,4);
 | |
|  *    A(3, 4) = 7;
 | |
|  * \endcode
 | |
|  *
 | |
|  * We also provide getCol() and getRow() functions witch return a viewer/iterator to rows and
 | |
|  * columns of the matrix. In the case of a symmetric matrix instead of a row we return the
 | |
|  * equivalent column. This way we gain speed due to CSC format nature.
 | |
|  *
 | |
|  * @tparam DataType  The type for values
 | |
|  * @tparam IndexType The type for indexes
 | |
|  * @tparam Type      The Matrix type (FULL or SYMMETRIC)
 | |
|  */
 | |
| template<typename DataType, typename IndexType,
 | |
|          MatrixOrder Order,
 | |
|          bool Symmetric>
 | |
| struct Matrix<DataType, IndexType, MatrixType::SPARSE, Order, Symmetric> {
 | |
| 
 | |
|    using dataType    = DataType;                   //!< meta:export of underling data type
 | |
|    using indexType   = IndexType;                  //!< meta:export of underling index type
 | |
|    static constexpr MatrixOrder matrixOrder = Order; //!< meta:export of array order
 | |
|    static constexpr MatrixType matrixType = MatrixType::SPARSE;  //!< meta:export of array type
 | |
|    static constexpr bool symmetric = Symmetric;    //!< meta:export symmetric flag
 | |
| 
 | |
|    friend struct MatCol<Matrix>;
 | |
|    friend struct MatRow<Matrix>;
 | |
|    friend struct MatVal<Matrix>;
 | |
| 
 | |
|    /*!
 | |
|     * \name Obj lifetime
 | |
|     */
 | |
|    //! @{
 | |
| 
 | |
|    //! Default ctor with optional memory allocations
 | |
|    Matrix(IndexType n=IndexType{}) noexcept:
 | |
|       values{},
 | |
|       rows{},
 | |
|       col_ptr((n)? n+1:2, IndexType{}),
 | |
|       N(n),
 | |
|       NNZ(0) { }
 | |
| 
 | |
|    //! A ctor using csc array data
 | |
|    Matrix(IndexType n, IndexType nnz, const IndexType* row, const IndexType* col) noexcept:
 | |
|       values(nnz, 1),
 | |
|       rows(row, row+nnz),
 | |
|       col_ptr(col, col+n+1),
 | |
|       N(n),
 | |
|       NNZ(nnz) { }
 | |
| 
 | |
|    //! ctor using csc array data with value array
 | |
|    Matrix(IndexType n, IndexType nnz, const DataType* v, const IndexType* row, const IndexType* col) noexcept:
 | |
|       values(v, v+nnz),
 | |
|       rows(row, row+nnz),
 | |
|       col_ptr(col, col+n+1),
 | |
|       N(n),
 | |
|       NNZ(nnz) { }
 | |
| 
 | |
|    //! ctor vectors of row/col and default value for values array
 | |
|    Matrix(IndexType n, IndexType nnz, const DataType v,
 | |
|           const std::vector<IndexType>& row, const std::vector<IndexType>& col) noexcept:
 | |
|       values(nnz, v),
 | |
|       rows (row),
 | |
|       col_ptr(col),
 | |
|       N(n),
 | |
|       NNZ(nnz) { }
 | |
| 
 | |
|    //! move ctor
 | |
|    Matrix(Matrix&& m) noexcept { moves(std::move(m)); }
 | |
|    //! move
 | |
|    Matrix& operator=(Matrix&& m) noexcept { moves(std::move(m)); return *this; }
 | |
|    Matrix(const Matrix& m)             = delete;  //!< make sure there are no copies
 | |
|    Matrix& operator=(const Matrix& m)  = delete;  //!< make sure there are no copies
 | |
|    //! @}
 | |
| 
 | |
|    //! \name Data exposure
 | |
|    //! @{
 | |
| 
 | |
|    //! \return the dimension of the matrix
 | |
|    IndexType size()     noexcept { return N; }
 | |
|    //! After construction size configuration tool
 | |
|    IndexType resize(IndexType n) {
 | |
|       col_ptr.resize(n+1);
 | |
|       return N = n;
 | |
|    }
 | |
|    //! \return the NNZ of the matrix
 | |
|    IndexType capacity() noexcept { return NNZ; }
 | |
|    //! After construction NNZ size configuration tool
 | |
|    IndexType capacity(IndexType nnz) noexcept {
 | |
|       values.reserve(nnz);
 | |
|       rows.reserve(nnz);
 | |
|       return NNZ;
 | |
|    }
 | |
|    // getters for row arrays of the struct (unused)
 | |
|    std::vector<DataType>&  getValues() noexcept { return values; }
 | |
|    std::vector<IndexType>& getRows() noexcept { return rows; }
 | |
|    std::vector<IndexType>& getCols() noexcept { return col_ptr; }
 | |
| 
 | |
|    /*!
 | |
|     * Return a proxy MatVal object with read and write capabilities.
 | |
|     * @param i    The row number
 | |
|     * @param j    The column number
 | |
|     * @return     tHE MatVal object
 | |
|     */
 | |
|    MatVal<Matrix> operator()(IndexType i, IndexType j) noexcept {
 | |
|       return MatVal<Matrix>(this, get(i, j), i, j);
 | |
|    }
 | |
| 
 | |
|    /*!
 | |
|     * A read item functionality using binary search to find the correct row
 | |
|     *
 | |
|     * @param i    The row number
 | |
|     * @param j    The column number
 | |
|     * @return     The value of the item or DataType{} if is not present.
 | |
|     */
 | |
|    DataType get(IndexType i, IndexType j) noexcept {
 | |
|       IndexType idx; bool found;
 | |
|       std::tie(idx, found) =find_idx(rows, col_ptr[j], col_ptr[j+1], i);
 | |
|       return (found) ? values[idx] : 0;
 | |
|    }
 | |
| 
 | |
|    /*!
 | |
|     * A write item functionality.
 | |
|     *
 | |
|     * First we search if the matrix has already a value in (i, j) position.
 | |
|     * If so we just change it to a new value. If not we add the item on the matrix.
 | |
|     *
 | |
|     * @note
 | |
|     *    When change a value, we don't increase the NNZ value of the struct. We expect the user has already
 | |
|     *    change the NNZ value to the right one using @see capacity() function. When adding a value we
 | |
|     *    increase the NNZ.
 | |
|     *
 | |
|     * @param i    The row number
 | |
|     * @param j    The column number
 | |
|     * @return     The new value of the item .
 | |
|     */
 | |
|    DataType set(DataType v, IndexType i, IndexType j) {
 | |
|       IndexType idx; bool found;
 | |
|       std::tie(idx, found) = find_idx(rows, col_ptr[j], col_ptr[j+1], i);
 | |
|       if (found)
 | |
|          return values[idx] = v;    // we don't change NNZ even if we write "0"
 | |
|       else {
 | |
|          values.insert(values.begin()+idx, v);
 | |
|          rows.insert(rows.begin()+idx, i);
 | |
|          std::transform(col_ptr.begin()+j+1, col_ptr.end(), col_ptr.begin()+j+1, [](IndexType it) {
 | |
|             return ++it;
 | |
|          });
 | |
|          ++NNZ;                     // we increase the NNZ even if we write "0"
 | |
|          return v;
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    /*!
 | |
|     * Get a view of a CSC column
 | |
|     * @param j    The column to get
 | |
|     * @return     The MatCol object @see MatCol
 | |
|     */
 | |
|    MatCol<Matrix> getCol(IndexType j) noexcept {
 | |
|       return MatCol<Matrix>(this, col_ptr[j], col_ptr[j+1]);
 | |
|    }
 | |
| 
 | |
|    /*!
 | |
|     * Get a view of a CSC row
 | |
|     *
 | |
|     * In case of a SYMMETRIC matrix we can return a column instead.
 | |
|     *
 | |
|     * @param j    The row to get
 | |
|     * @return     On symmetric matrix MatCol otherwise a MatRow
 | |
|     */
 | |
| 
 | |
|    MatCol<Matrix> getRow(IndexType i) noexcept {
 | |
|       if constexpr (Symmetric)
 | |
|          return getCol(i);
 | |
|       else
 | |
|          return MatRow<Matrix>(this, i);
 | |
|    }
 | |
| 
 | |
|    // values only iterator support
 | |
|    DataType* begin() noexcept { return values.begin(); }
 | |
|    DataType* end()   noexcept { return values.end(); }
 | |
|    //! @}
 | |
| 
 | |
|    //! A small iteration helper
 | |
|    template<typename F, typename... Args>
 | |
|    void for_each_in (IndexType begin, IndexType end, F&& lambda, Args&&... args) {
 | |
|       for (IndexType it=begin ; it<end ; ++it) {
 | |
|          std::forward<F>(lambda)(std::forward<Args>(args)..., it);
 | |
|       }
 | |
|    }
 | |
| 
 | |
| private:
 | |
|    /*!
 | |
|     * A small binary search implementation using index for begin-end instead of iterators.
 | |
|     *
 | |
|     * \param   v     Reference to vector to search
 | |
|     * \param   begin The vector's index to begin
 | |
|     * \param   end   The vector's index to end
 | |
|     * \param   match What to search
 | |
|     * \return  An <index, status> pair.
 | |
|     *                index    is the index of the item or end if not found
 | |
|     *                status   is true if found, false otherwise
 | |
|     */
 | |
|    std::pair<IndexType, bool> find_idx(const std::vector<IndexType>& v, IndexType begin, IndexType end, IndexType match) {
 | |
|       if (v.capacity() != 0 && begin < end) {
 | |
|          IndexType b = begin, e = end-1;
 | |
|          while (b <= e) {
 | |
|             IndexType m = (b+e)/2;
 | |
|             if       (v[m] == match)   return  std::make_pair(m, true);
 | |
|             else if  (b >= e)          return  std::make_pair(end, false);
 | |
|             else {
 | |
|                if    (v[m] <  match)   b = m +1;
 | |
|                else                    e = m -1;
 | |
|             }
 | |
|          }
 | |
|       }
 | |
|       return std::make_pair(end, false);
 | |
|    }
 | |
| 
 | |
|    // move helper
 | |
|    void moves(Matrix&& src) noexcept {
 | |
|       values  = std::move(src.values);
 | |
|       rows    = std::move(src.rows);
 | |
|       col_ptr = std::move(src.col_ptr);
 | |
|       N       = std::move(src.N);   // redundant for primitives
 | |
|       NNZ     = std::move(src.NNZ); //
 | |
|    }
 | |
|    //! \name Data
 | |
|    //! @{
 | |
|    std::vector<DataType>   values {};     //!< vector to store the values of the matrix
 | |
|    std::vector<IndexType>  rows{};        //!< vector to store the row information
 | |
|    std::vector<IndexType>  col_ptr{1,0};  //!< vector to store the column pointers
 | |
|    IndexType   N{0};                      //!< The dimension of the matrix (square)
 | |
|    IndexType   NNZ{0};                    //!< The NNZ (capacity of the matrix)
 | |
|    //! @}
 | |
| };
 | |
| 
 | |
| 
 | |
| /*!
 | |
|  * A view/iterator hybrid object for Matrix columns.
 | |
|  *
 | |
|  * This object provides access to a column of a Matrix. The public functionalities
 | |
|  * allow data access using indexes instead of iterators. We prefer indexes over iterators
 | |
|  * because we can apply the same index to different inner vector of Matrix without conversion.
 | |
|  *
 | |
|  * @tparam DataType
 | |
|  * @tparam IndexType
 | |
|  */
 | |
| template<typename MatrixType>
 | |
| struct MatCol {
 | |
|    using owner_t = MatrixType;
 | |
| 
 | |
|    using DataType  = typename MatrixType::dataType;
 | |
|    using IndexType = typename MatrixType::indexType;
 | |
| 
 | |
|    /*!
 | |
|     * ctor using column pointers for begin-end. own is pointer to Matrix.
 | |
|     */
 | |
|    MatCol(owner_t* own, const IndexType begin, const IndexType end) noexcept :
 | |
|       owner_(own), index_(begin), begin_(begin), end_(end) {
 | |
|       vindex_ = vIndexCalc(index_);
 | |
|    }
 | |
|    MatCol()                        = default;
 | |
|    MatCol(const MatCol&)           = delete;   //!< make sure there are no copies
 | |
|    MatCol& operator=(const MatCol&)= delete;   //!< make sure there are no copies
 | |
|    MatCol(MatCol&&)                = default;
 | |
|    MatCol& operator=(MatCol&&)     = default;
 | |
| 
 | |
|    //! a simple dereference operator, like an iterator
 | |
|    DataType operator* () {
 | |
|       return get();
 | |
|    }
 | |
|    //! Increment operator acts on index(), like an iterator
 | |
|    MatCol& operator++ ()    { advance(); return *this; }
 | |
|    MatCol& operator++ (int) { MatCol& p = *this; advance(); return p; }
 | |
| 
 | |
|    //! () operator acts as member access (like a view)
 | |
|    DataType operator()(IndexType x) {
 | |
|       return (x == index())? get() : DataType{};
 | |
|    }
 | |
|    //! = operator acts as member assignment (like a view)
 | |
|    DataType operator= (DataType v) { return owner_->values[index_] = v; }
 | |
|    // iterator like handlers
 | |
|    // these return a virtual index value based on the items position on the full matrix
 | |
|    // but the move of the index is just a ++ away.
 | |
|    IndexType       index()       noexcept { return vindex_; }
 | |
|    const IndexType index() const noexcept { return vindex_; }
 | |
|    IndexType       begin()       noexcept { return vIndexCalc(begin_); }
 | |
|    const IndexType begin() const noexcept { return vIndexCalc(begin_); }
 | |
|    IndexType       end()         noexcept { return owner_->N; }
 | |
|    const IndexType end()   const noexcept { return owner_->N; }
 | |
| 
 | |
|    /*!
 | |
|     * Multiplication operator
 | |
|     *
 | |
|     * We follow only the non-zero values and multiply only the common indexes.
 | |
|     *
 | |
|     * @tparam C   Universal reference for the type right half site column
 | |
|     *
 | |
|     * @param c    The right hand site matrix
 | |
|     * @return     The value of the inner product of two vectors
 | |
|     * @note       The time complexity is \$ O(nnz1+nnz2) \$.
 | |
|     *             Where the nnz is the max NNZ elements of the column of the matrix
 | |
|     */
 | |
|    template <typename C>
 | |
|    DataType operator* (C&& c) {
 | |
|       static_assert(std::is_same<remove_cvref_t<C>, MatCol<MatrixType>>(), "");
 | |
|       DataType v{};
 | |
|       while (index() != end() && c.index() != c.end()) {
 | |
|          if      (index() < c.index())  advance(); // advance me
 | |
|          else if (index() > c.index())  ++c;       // advance other
 | |
|          else { //index() == c.index()
 | |
|             v += get() * *c;                       // multiply and advance both
 | |
|             ++c;
 | |
|             advance();
 | |
|          }
 | |
|       }
 | |
|       return v;
 | |
|    }
 | |
| 
 | |
| private:
 | |
|    //! small tool to increase the index pointers to Matrix
 | |
|    void advance() noexcept {
 | |
|       ++index_;
 | |
|       vindex_ = vIndexCalc(index_);
 | |
|    }
 | |
|    //! tool to translate between col_ptr indexes and Matrix "virtual" full matrix indexes
 | |
|    IndexType vIndexCalc(IndexType idx) {
 | |
|       return (idx < end_) ? owner_->rows[idx] : end();
 | |
|    }
 | |
|    //! small get tool
 | |
|    DataType get() { return owner_->values[index_]; }
 | |
| 
 | |
|    owner_t*    owner_   {nullptr};     //!< Pointer to owner Matrix. MatCol is just a view
 | |
|    IndexType   vindex_  {IndexType{}}; //!< Virtual index of full matrix
 | |
|    IndexType   index_   {IndexType{}}; //!< index to Matrix::rows
 | |
|    IndexType   begin_   {IndexType{}}; //!< beginning index of the column in Matrix::rows
 | |
|    IndexType   end_     {IndexType{}}; //!< ending index of the column in Matrix::rows
 | |
| };
 | |
| 
 | |
| /*!
 | |
|  * A view/iterator hybrid object for Matrix rows.
 | |
|  *
 | |
|  * This object provides access to a column of a Matrix. The public functionalities
 | |
|  * allow data access using indexes instead of iterators. We prefer indexes over iterators
 | |
|  * because we can apply the same index to different inner vector of Matrix without conversion.
 | |
|  *
 | |
|  * @tparam DataType
 | |
|  * @tparam IndexType
 | |
|  */
 | |
| template<typename MatrixType>
 | |
| struct MatRow {
 | |
|    using owner_t = MatrixType;
 | |
| 
 | |
|    using DataType  = typename MatrixType::dataType;
 | |
|    using IndexType = typename MatrixType::indexType;
 | |
| 
 | |
|    /*!
 | |
|     * ctor using virtual full matrix row index. own is pointer to Matrix.
 | |
|     */
 | |
|    MatRow(owner_t* own, const IndexType row) noexcept :
 | |
|       owner_(own), vindex_(IndexType{}), row_(row), index_(IndexType{}),
 | |
|       begin_(IndexType{}), end_(owner_->NNZ) {
 | |
|       // place begin
 | |
|       while(begin_ != end_ && owner_->rows[begin_] != row_)
 | |
|          ++begin_;
 | |
|       // place index_ and vindex_
 | |
|       if (owner_->rows[index_] != row_)
 | |
|          advance();
 | |
|    }
 | |
|    MatRow()                        = default;
 | |
|    MatRow(const MatRow&)           = delete;   //!< make sure there are no copies
 | |
|    MatRow& operator=(const MatRow&)= delete;   //!< make sure there are no copies
 | |
|    MatRow(MatRow&&)                = default;
 | |
|    MatRow& operator=(MatRow&&)     = default;
 | |
| 
 | |
|    //! a simple dereference operator, like an iterator
 | |
|    DataType operator* () {
 | |
|       return get();
 | |
|    }
 | |
|    //! Increment operator acts on index(), like an iterator
 | |
|    //! here the increment is a O(N) process.
 | |
|    MatRow& operator++ ()    { advance(); return *this; }
 | |
|    MatRow& operator++ (int) { MatRow& p = *this; advance(); return p; }
 | |
| 
 | |
|    //! () operator acts as member access (like a view)
 | |
|    DataType operator()(IndexType x) {
 | |
|       return (x == index())? get() : DataType{};
 | |
|    }
 | |
|    //! = operator acts as member assignment (like a view)
 | |
|    DataType operator= (DataType v) { return owner_->values[index_] = v; }
 | |
|    // iterator like handlers
 | |
|    // these return a virtual index value based on the items position on the full matrix
 | |
|    // but the move of the index is just a ++ away.
 | |
|    IndexType       index()       noexcept { return vindex_; }
 | |
|    const IndexType index() const noexcept { return vindex_; }
 | |
|    IndexType       begin()       noexcept { return vIndexCalc(begin_); }
 | |
|    const IndexType begin() const noexcept { return vIndexCalc(begin_); }
 | |
|    IndexType       end()         noexcept { return owner_->N; }
 | |
|    const IndexType end()   const noexcept { return owner_->N; }
 | |
| 
 | |
|    /*!
 | |
|     * Multiplication operator
 | |
|     *
 | |
|     * We follow only the non-zero values and multiply only the common indexes.
 | |
|     *
 | |
|     * @tparam C   Universal reference for the type right half site column
 | |
|     *
 | |
|     * @param c    The right hand site matrix
 | |
|     * @return     The value of the inner product of two vectors
 | |
|     * @note       The time complexity is \$ O(N+nnz2) \$ and way heavier the ColxCol multiplication.
 | |
|     *             Where the nnz is the max NNZ elements of the column of the matrix
 | |
|     */
 | |
|    template <typename C>
 | |
|    DataType operator* (C&& c) {
 | |
|       static_assert(std::is_same<remove_cvref_t<C>, MatCol<MatrixType>>(), "");
 | |
|       DataType v{};
 | |
|       while (index() != end() && c.index() != c.end()) {
 | |
|          if      (index() < c.index())  advance(); // advance me
 | |
|          else if (index() > c.index())  ++c;       // advance other
 | |
|          else { //index() == c.index()
 | |
|             v += get() * *c;                       // multiply and advance both
 | |
|             ++c;
 | |
|             advance();
 | |
|          }
 | |
|       }
 | |
|       return v;
 | |
|    }
 | |
| private:
 | |
|    //! small tool to increase the index pointers to Matrix matrix
 | |
|    //! We have to search the entire rows vector in Matrix to find the next
 | |
|    //! virtual row position.
 | |
|    //! time complexity O(N)
 | |
|    void advance() noexcept {
 | |
|       do
 | |
|          ++index_;
 | |
|       while(index_ != end_ && owner_->rows[index_] != row_);
 | |
|       vindex_ = vIndexCalc(index_);
 | |
|    }
 | |
|    //! tool to translate between col_ptr indexes and Matrix "virtual" full matrix indexes
 | |
|    IndexType vIndexCalc(IndexType idx) {
 | |
|       for(IndexType i =0 ; i<(owner_->N+1) ; ++i)
 | |
|          if (idx < owner_->col_ptr[i])
 | |
|             return i-1;
 | |
|       return end();
 | |
|    }
 | |
|    //! small get tool
 | |
|    DataType get() { return owner_->values[index_]; }
 | |
| 
 | |
|    owner_t*    owner_   {nullptr};     //!< Pointer to owner Matrix. MatCol is just a view
 | |
|    IndexType   vindex_  {IndexType{}}; //!< Virtual index of full matrix
 | |
|    IndexType   row_     {IndexType{}}; //!< The virtual full matrix row of the object
 | |
|    IndexType   index_   {IndexType{}}; //!< index to Matrix::rows
 | |
|    IndexType   begin_   {IndexType{}}; //!< beginning index of the column in Matrix::rows
 | |
|    IndexType   end_     {IndexType{}}; //!< ending index of the column in Matrix::rows
 | |
| };
 | |
| 
 | |
| /*!
 | |
|  * A proxy Matrix value object/view.
 | |
|  *
 | |
|  * This object acts as proxy to provide read/write access to an Matrix item.
 | |
|  *
 | |
|  * @tparam DataType     The type of the values of the Matrix matrix
 | |
|  * @tparam IndexType    The type of the indexes of the Matrix matrix
 | |
|  */
 | |
| template<typename MatrixType>
 | |
| struct MatVal {
 | |
|    using owner_t = MatrixType;
 | |
| 
 | |
|    using DataType  = typename MatrixType::dataType;
 | |
|    using IndexType = typename MatrixType::indexType;
 | |
| 
 | |
|    //!< ctor using all value-row-column data, plus a pointer to owner Matrix object
 | |
|    MatVal(owner_t* own, DataType v, IndexType i, IndexType j) :
 | |
|       owner_(own), v_(v), i_(i), j_(j) { }
 | |
|    MatVal()                         = default;
 | |
|    MatVal(const MatVal&)            = delete;  //!< make sure there are no copies
 | |
|    MatVal& operator=(const MatVal&) = delete;  //!< make sure there are no copies
 | |
|    MatVal(MatVal&&)                 = default;
 | |
|    MatVal& operator=(MatVal&&)      = default;
 | |
| 
 | |
|    //! Operator to return the DataType value implicitly
 | |
|    operator DataType() { return v_; }
 | |
|    //! Operator to write back to owner the assigned value
 | |
|    //! for ex: A(2,3) = 5;
 | |
|    MatVal& operator=(DataType v) {
 | |
|       v_ = v;
 | |
|       owner_->set(v_, i_, j_);
 | |
|       return *this;
 | |
|    }
 | |
| private:
 | |
|    owner_t*    owner_{nullptr};  //!< Pointer to owner Matrix. MatVal is just a view.
 | |
|    DataType    v_{DataType{}};   //!< The value of the row-column pair (for speed)
 | |
|    IndexType   i_{IndexType{}};  //!< The row
 | |
|    IndexType   j_{IndexType{}};  //!< the column
 | |
| };
 | |
| 
 | |
| 
 | |
| } // namespace mtx
 | |
| 
 | |
| 
 | |
| #endif /* MATRIX_HPP_ */
 |