| // This file is part of Eigen, a lightweight C++ template library |
| // for linear algebra. |
| // |
| // This Source Code Form is subject to the terms of the Mozilla |
| // Public License v. 2.0. If a copy of the MPL was not distributed |
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| // SPDX-FileCopyrightText: The Eigen Authors |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| #ifndef EIGEN_BLOCKSPARSEMATRIX_H |
| #define EIGEN_BLOCKSPARSEMATRIX_H |
| |
| // IWYU pragma: private |
| #include "./InternalHeaderCheck.h" |
| |
| #include <algorithm> |
| #include <numeric> |
| #include <utility> |
| |
| namespace Eigen { |
| |
| // Forward declarations |
| template <typename, int, bool> |
| class BlockSparseTriangularView; |
| template <typename, int, bool> |
| class BlockSparseSelfAdjointView; |
| template <typename Scalar_, int Options_, int BlockRows_, int BlockCols_, typename StorageIndex_> |
| class BlockSparseMatrix; |
| |
| /** Storage-kind tag for BlockSparseMatrix. */ |
| struct BlockSparse {}; |
| |
| /** Evaluator shape tag for BlockSparseMatrix product dispatch. */ |
| struct BlockSparseShape { |
| static std::string debugName() { return "BlockSparseShape"; } |
| }; |
| |
| namespace internal { |
| // Returns m.adjoint() when Conj==true, m.transpose() otherwise. |
| // SFINAE overloads keep the return type concrete under C++14 (no if constexpr). |
| template <bool Conj, typename T> |
| std::enable_if_t<Conj, decltype(std::declval<const T&>().adjoint())> adjoint_if(const T& m) { |
| return m.adjoint(); |
| } |
| |
| template <bool Conj, typename T> |
| std::enable_if_t<!Conj, decltype(std::declval<const T&>().transpose())> adjoint_if(const T& m) { |
| return m.transpose(); |
| } |
| template <> |
| struct storage_kind_to_evaluator_kind<BlockSparse> { |
| using Kind = IndexBased; |
| }; |
| |
| template <> |
| struct storage_kind_to_shape<BlockSparse> { |
| using Shape = BlockSparseShape; |
| }; |
| |
| template <typename Scalar_, int Options_, int BlockRows_, int BlockCols_, typename StorageIndex_> |
| struct traits<BlockSparseMatrix<Scalar_, Options_, BlockRows_, BlockCols_, StorageIndex_>> { |
| using Scalar = Scalar_; |
| using StorageIndex = StorageIndex_; |
| using StorageKind = BlockSparse; |
| using XprKind = MatrixXpr; |
| |
| static constexpr Index RowsAtCompileTime = Dynamic; |
| static constexpr Index ColsAtCompileTime = Dynamic; |
| static constexpr Index MaxRowsAtCompileTime = Dynamic; |
| static constexpr Index MaxColsAtCompileTime = Dynamic; |
| static constexpr int Options = Options_; |
| static constexpr unsigned int Flags = Options_ | NestByRefBit | LvalueBit; |
| }; |
| |
| } // namespace internal |
| |
| /** \class BlockTriplet |
| * \ingroup SparseCore_Module |
| * \brief A (blockRow, blockCol, blockValue) triplet for assembling a BlockSparseMatrix. |
| * |
| * Coordinates are in \em block space, not element space. |
| * |
| * \tparam Scalar_ Numeric scalar type. |
| * \tparam BlockRows_ Number of rows in each block. |
| * \tparam BlockCols_ Number of columns in each block. |
| * \tparam StorageIndex_ Signed integer index type (default: int). |
| */ |
| template <typename Scalar_, int BlockRows_, int BlockCols_, int Options_ = ColMajor, typename StorageIndex_ = int> |
| class BlockTriplet { |
| public: |
| using Scalar = Scalar_; |
| using StorageIndex = StorageIndex_; |
| using BlockType = Matrix<Scalar, BlockRows_, BlockCols_, Options_>; |
| using BlockMapType = Map<BlockType, Unaligned>; |
| using ConstBlockMapType = Map<const BlockType, Unaligned>; |
| |
| static constexpr int BlockSize = BlockRows_ * BlockCols_; |
| |
| BlockTriplet() = default; |
| |
| BlockTriplet(StorageIndex blockRow, StorageIndex blockCol, const BlockType& block) |
| : m_row(blockRow), m_col(blockCol) { |
| BlockMapType{m_value} = block; |
| } |
| |
| StorageIndex row() const { return m_row; } |
| StorageIndex col() const { return m_col; } |
| // Implicitly usable wherever a MatrixBase expression is expected. |
| ConstBlockMapType value() const { return ConstBlockMapType(m_value); } |
| |
| private: |
| StorageIndex m_row = 0; |
| StorageIndex m_col = 0; |
| // Flat array avoids the alignment padding that a Matrix<> member would incur. |
| Scalar m_value[BlockSize]; |
| }; |
| |
| /** \class BlockSparseMatrix |
| * \ingroup SparseCore_Module |
| * \brief A sparse matrix whose stored nonzeros are fixed-size dense blocks. |
| * |
| * Each nonzero entry is a \c BlockRows x \c BlockCols dense matrix. The |
| * block sparsity pattern is stored in block-level compressed-column |
| * (ColMajor) or compressed-row (RowMajor) format. |
| * |
| * \tparam Scalar_ Numeric scalar type. |
| * \tparam Options_ ColMajor (0) or RowMajor. Controls both the outer |
| * iteration direction over blocks and the storage |
| * layout within each block. Note: vector-shaped blocks |
| * must use a compatible order — \c BlockCols_=1 requires |
| * ColMajor, \c BlockRows_=1 requires RowMajor. |
| * \tparam BlockRows_ Rows per block; must be a fixed positive integer. |
| * \tparam BlockCols_ Columns per block; must be a fixed positive integer. |
| * \tparam StorageIndex_ Signed integer type for internal index arrays |
| * (default: int). |
| * |
| * ### Assembly |
| * Populate the matrix via setFromTriplets(), passing an iterator range of |
| * BlockTriplet objects in block coordinates. Triplets with the same |
| * (blockRow, blockCol) are summed. |
| * |
| * ### Arithmetic |
| * Addition, subtraction, and matrix product are supported between compatible |
| * BlockSparseMatrix instances. Scalar multiplication is also available. |
| * |
| * The matrix product \c C = A * B requires |
| * \c A.BlockCols == B.BlockRows (enforced at compile time by the template |
| * constraint) and \c A.blockCols() == B.blockRows() (checked at runtime). |
| * The result has block type \c Matrix<Scalar,A.BlockRows,B.BlockCols>. |
| * |
| * ### Conversion |
| * toSparse() converts to a standard SparseMatrix with element-level |
| * sparsity. fromSparse() reconstructs a BlockSparseMatrix from an |
| * element-level SparseMatrix whose dimensions are divisible by BlockRows |
| * and BlockCols. An implicit conversion operator to SparseMatrix is |
| * also provided. |
| */ |
| template <typename Scalar_, int Options_, int BlockRows_, int BlockCols_, typename StorageIndex_ = int> |
| class BlockSparseMatrix |
| : public EigenBase<BlockSparseMatrix<Scalar_, Options_, BlockRows_, BlockCols_, StorageIndex_>> { |
| EIGEN_STATIC_ASSERT(BlockRows_ >= 1, BLOCKROWS_MUST_BE_A_POSITIVE_COMPILE_TIME_SIZE) |
| EIGEN_STATIC_ASSERT(BlockCols_ >= 1, BLOCKCOLS_MUST_BE_A_POSITIVE_COMPILE_TIME_SIZE) |
| EIGEN_STATIC_ASSERT(std::is_integral<StorageIndex_>::value&& std::is_signed<StorageIndex_>::value, |
| STORAGEINDEX_MUST_BE_A_SIGNED_INTEGRAL_TYPE) |
| // Eigen's Matrix<> requires: a column vector (Cols==1, Rows>1) must be ColMajor; |
| // a row vector (Rows==1, Cols>1) must be RowMajor. Guard those cases here so |
| // the error fires at BlockSparseMatrix instantiation rather than inside BlockType. |
| EIGEN_STATIC_ASSERT(!(BlockCols_ == 1 && BlockRows_ != 1 && bool(Options_ & RowMajorBit)), |
| INVALID_MATRIX_TEMPLATE_PARAMETERS) |
| EIGEN_STATIC_ASSERT(!(BlockRows_ == 1 && BlockCols_ != 1 && !bool(Options_ & RowMajorBit)), |
| INVALID_MATRIX_TEMPLATE_PARAMETERS) |
| |
| public: |
| // ------------------------------------------------------------------------- |
| // Type aliases & compile-time constants |
| // ------------------------------------------------------------------------- |
| using Scalar = Scalar_; |
| using StorageIndex = StorageIndex_; |
| using BlockType = Matrix<Scalar, BlockRows_, BlockCols_, Options_>; |
| using TripletType = BlockTriplet<Scalar, BlockRows_, BlockCols_, Options_, StorageIndex>; |
| |
| static constexpr int Options = Options_; |
| static constexpr Index BlockRows = BlockRows_; |
| static constexpr Index BlockCols = BlockCols_; |
| static constexpr bool IsRowMajor = Options_ & RowMajorBit; |
| static constexpr Index BlockSize = BlockRows_ * BlockCols_; |
| |
| // If one block occupies a power-of-two number of bytes, and the values array |
| // is Eigen-allocated (guaranteed aligned to EIGEN_MAX_ALIGN_BYTES), then every |
| // block pointer is aligned to min(BlockBytes, EIGEN_MAX_ALIGN_BYTES). |
| static constexpr std::size_t BlockBytes = std::size_t(BlockSize) * sizeof(Scalar); |
| static constexpr int BlockMapAlignment = ((BlockBytes & (BlockBytes - 1)) == 0 && BlockBytes >= 8) |
| ? int(numext::mini(BlockBytes, std::size_t(EIGEN_MAX_ALIGN_BYTES))) |
| : 0; |
| |
| using BlockMap = Map<BlockType, BlockMapAlignment>; |
| using ConstBlockMap = Map<const BlockType, BlockMapAlignment>; |
| |
| // ------------------------------------------------------------------------- |
| // Constructors / copy / move |
| // ------------------------------------------------------------------------- |
| |
| /** Default constructor; creates a 0×0 matrix. */ |
| BlockSparseMatrix() = default; |
| |
| /** Construct a zero matrix with the given number of block-rows and block-columns. */ |
| BlockSparseMatrix(Index blockRows, Index blockCols) |
| : m_blockOuterSize(IsRowMajor ? blockRows : blockCols), m_blockInnerSize(IsRowMajor ? blockCols : blockRows) {} |
| |
| // ------------------------------------------------------------------------- |
| // Dimensions |
| // ------------------------------------------------------------------------- |
| |
| /** Total number of element rows. */ |
| Index rows() const noexcept { return (IsRowMajor ? m_blockOuterSize : m_blockInnerSize) * BlockRows_; } |
| /** Total number of element columns. */ |
| Index cols() const noexcept { return (IsRowMajor ? m_blockInnerSize : m_blockOuterSize) * BlockCols_; } |
| |
| /** Number of block-rows. */ |
| Index blockRows() const { return IsRowMajor ? m_blockOuterSize : m_blockInnerSize; } |
| /** Number of block-columns. */ |
| Index blockCols() const { return IsRowMajor ? m_blockInnerSize : m_blockOuterSize; } |
| |
| /** Outer block dimension (block-cols for ColMajor, block-rows for RowMajor). */ |
| Index blockOuterSize() const { return m_blockOuterSize; } |
| /** Inner block dimension (block-rows for ColMajor, block-cols for RowMajor). */ |
| Index blockInnerSize() const { return m_blockInnerSize; } |
| |
| /** Element-level outer size: cols() for ColMajor, rows() for RowMajor. */ |
| Index outerSize() const { return IsRowMajor ? rows() : cols(); } |
| /** Element-level inner size: rows() for ColMajor, cols() for RowMajor. */ |
| Index innerSize() const { return IsRowMajor ? cols() : rows(); } |
| |
| /** Number of stored (structurally non-zero) blocks. */ |
| Index nonZeroBlocks() const { return m_outerIndex(m_blockOuterSize); } |
| /** Total number of stored scalar coefficients (= nonZeroBlocks() * BlockRows * BlockCols). */ |
| Index nonZeros() const { return nonZeroBlocks() * BlockSize; } |
| /** Number of blocks for which storage is currently allocated (capacity). */ |
| Index allocatedBlocks() const { return Index(m_innerIndex.size()); } |
| |
| // ------------------------------------------------------------------------- |
| // Raw pointer access (for interoperability) |
| // ------------------------------------------------------------------------- |
| const StorageIndex* outerIndexPtr() const { return m_outerIndex.data(); } |
| StorageIndex* outerIndexPtr() { return m_outerIndex.data(); } |
| const StorageIndex* innerIndexPtr() const { return m_innerIndex.data(); } |
| StorageIndex* innerIndexPtr() { return m_innerIndex.data(); } |
| const Scalar* valuePtr() const { return m_values.data(); } |
| Scalar* valuePtr() { return m_values.data(); } |
| |
| // ------------------------------------------------------------------------- |
| // Block access by sequential nonzero index |
| // ------------------------------------------------------------------------- |
| |
| /** Read-only Map to the \a k-th stored block (block storage follows \c Options_). */ |
| ConstBlockMap blockRef(Index k) const { return ConstBlockMap(m_values.data() + k * BlockSize); } |
| /** Mutable Map to the \a k-th stored block. */ |
| BlockMap blockRef(Index k) { return BlockMap(m_values.data() + k * BlockSize); } |
| |
| // ------------------------------------------------------------------------- |
| // Inner iterator over blocks within one outer vector |
| // ------------------------------------------------------------------------- |
| |
| /** \brief Iterates over stored blocks in outer vector \a outer. |
| * |
| * Usage mirrors SparseMatrix::InnerIterator but value() returns a |
| * Map to a BlockRows×BlockCols matrix, not a scalar. |
| */ |
| class InnerIterator { |
| public: |
| EIGEN_STRONG_INLINE InnerIterator(const BlockSparseMatrix& mat, Index outer) |
| : m_mat(mat), m_id(mat.m_outerIndex(outer)), m_end(mat.m_outerIndex(outer + 1)), m_outer(outer) {} |
| |
| EIGEN_STRONG_INLINE operator bool() const { return m_id < m_end; } |
| EIGEN_STRONG_INLINE InnerIterator& operator++() { |
| ++m_id; |
| return *this; |
| } |
| |
| /** Current block outer index (block-col for ColMajor, block-row for RowMajor). */ |
| EIGEN_STRONG_INLINE Index outer() const { return m_outer; } |
| /** Current block inner index (block-row for ColMajor, block-col for RowMajor). */ |
| EIGEN_STRONG_INLINE Index index() const { return m_mat.m_innerIndex(m_id); } |
| /** Block-row of the current block. */ |
| EIGEN_STRONG_INLINE Index blockRow() const { return IsRowMajor ? m_outer : index(); } |
| /** Block-column of the current block. */ |
| EIGEN_STRONG_INLINE Index blockCol() const { return IsRowMajor ? index() : m_outer; } |
| |
| /** Read-only Map to the current block value. */ |
| EIGEN_STRONG_INLINE ConstBlockMap value() const { return m_mat.blockRef(m_id); } |
| /** Mutable Map to the current block value. */ |
| EIGEN_STRONG_INLINE BlockMap valueRef() { |
| return BlockMap(const_cast<Scalar*>(m_mat.m_values.data()) + m_id * BlockSize); |
| } |
| |
| private: |
| const BlockSparseMatrix& m_mat; |
| Index m_id; |
| Index m_end; |
| Index m_outer; |
| }; |
| |
| // ------------------------------------------------------------------------- |
| // Resize / clear |
| // ------------------------------------------------------------------------- |
| |
| /** Resize to \a blockRows × \a blockCols blocks and set the logical nnz to zero. |
| * Allocated block storage is retained; call squeeze() to release it. */ |
| void resize(Index blockRows, Index blockCols) { |
| m_blockOuterSize = IsRowMajor ? blockRows : blockCols; |
| m_blockInnerSize = IsRowMajor ? blockCols : blockRows; |
| m_outerIndex.resize(m_blockOuterSize + 1); |
| m_outerIndex.setZero(); |
| } |
| |
| /** Clear all stored blocks (logical nnz → 0) while keeping dimensions and allocated storage. */ |
| void setZero() { |
| m_outerIndex.resize(m_blockOuterSize + 1); |
| m_outerIndex.setZero(); |
| } |
| |
| /** Pre-allocate storage for at least \a n blocks without changing the logical sparsity pattern. |
| * Existing block data is preserved up to min(n, nonZeroBlocks()). */ |
| void reserve(Index n) { |
| if (n > Index(m_innerIndex.size())) conservativeResizeBlockStorage_(n); |
| } |
| |
| /** Release any excess allocated block storage so that allocatedBlocks() == nonZeroBlocks(). */ |
| void squeeze() { |
| Index nnz = nonZeroBlocks(); |
| if (nnz < Index(m_innerIndex.size())) conservativeResizeBlockStorage_(nnz); |
| } |
| |
| /** Fill the matrix with the block identity: the min(blockRows,blockCols) diagonal blocks |
| * are set to the B×B identity; all other blocks are absent. |
| * |
| * \pre BlockRows == BlockCols (square blocks). |
| */ |
| void setIdentity() { |
| EIGEN_STATIC_ASSERT(BlockRows_ == BlockCols_, THIS_METHOD_IS_ONLY_FOR_SQUARE_BLOCK_MATRICES) |
| Index n = (std::min)(m_blockOuterSize, m_blockInnerSize); |
| m_outerIndex.resize(m_blockOuterSize + 1); |
| resizeBlockStorage_(n); |
| for (Index i = 0; i <= m_blockOuterSize; ++i) m_outerIndex(i) = StorageIndex((std::min)(i, n)); |
| for (StorageIndex i = 0; i < n; ++i) { |
| m_innerIndex(i) = i; |
| blockRef(i).setIdentity(); |
| } |
| } |
| |
| /** Initialize the block structure directly from compressed outer/inner index arrays, |
| * zero-initializing all block values. |
| * |
| * \p outerPtr has size blockCols+1 (ColMajor) or blockRows+1 (RowMajor). |
| * \p innerPtr has size nnzBlocks. |
| */ |
| void setFromOuterInner(Index blockRows, Index blockCols, Index nnzBlocks, const StorageIndex_* outerPtr, |
| const StorageIndex_* innerPtr) { |
| m_blockOuterSize = IsRowMajor ? blockRows : blockCols; |
| m_blockInnerSize = IsRowMajor ? blockCols : blockRows; |
| m_outerIndex = Map<const decltype(m_outerIndex)>(outerPtr, m_blockOuterSize + 1); |
| m_innerIndex = Map<const decltype(m_innerIndex)>(innerPtr, nnzBlocks); |
| resizeBlockStorage_(nnzBlocks); |
| m_values.setZero(); |
| } |
| |
| // ------------------------------------------------------------------------- |
| // Assembly |
| // ------------------------------------------------------------------------- |
| |
| /** Fill the matrix from an iterator range of BlockTriplet objects. |
| * |
| * Triplet coordinates are in block space. Triplets with the same |
| * (blockRow, blockCol) pair are summed (their block values are added). |
| * The input range may be in any order. |
| * |
| * \tparam InputIterator Must dereference to a type with \c row(), |
| * \c col(), and \c value() members, matching |
| * BlockTriplet's interface. |
| */ |
| template <typename InputIterator> |
| void setFromTriplets(InputIterator begin, InputIterator end); |
| |
| // ------------------------------------------------------------------------- |
| // Conversion to / from SparseMatrix |
| // ------------------------------------------------------------------------- |
| |
| /** Convert to a SparseMatrix with scalar nonzeros. |
| * |
| * Each stored block of size BlockRows×BlockCols expands into up to |
| * BlockRows*BlockCols scalar nonzeros. The resulting SparseMatrix has |
| * the same storage order as \c *this. |
| */ |
| SparseMatrix<Scalar, Options_, StorageIndex_> toSparse() const; |
| |
| /** Construct a BlockSparseMatrix from an element-level SparseMatrix. |
| * |
| * \pre \c sp.rows() % BlockRows == 0 and \c sp.cols() % BlockCols == 0. |
| * |
| * Each scalar entry \c sp(i,j) is placed into position |
| * \c (i%BlockRows, j%BlockCols) of block \c (i/BlockRows, j/BlockCols). |
| * Multiple entries mapping to the same element of the same block are |
| * accumulated with \c +=. |
| */ |
| static BlockSparseMatrix fromSparse(const SparseMatrix<Scalar_, Options_, StorageIndex_>& sp); |
| |
| /** Implicit conversion to SparseMatrix. */ |
| operator SparseMatrix<Scalar_, Options_, StorageIndex_>() const { return toSparse(); } |
| |
| // ------------------------------------------------------------------------- |
| // Element access |
| // ------------------------------------------------------------------------- |
| |
| /** Read element \c (row, col); returns 0 if no block covers that position. */ |
| Scalar coeff(Index row, Index col) const { |
| eigen_assert(row >= 0 && row < rows() && col >= 0 && col < cols()); |
| Index bOuter = IsRowMajor ? (row / BlockRows_) : (col / BlockCols_); |
| Index bInner = IsRowMajor ? (col / BlockCols_) : (row / BlockRows_); |
| Index localRow = row % BlockRows_; |
| Index localCol = col % BlockCols_; |
| const StorageIndex* beg = m_innerIndex.data() + m_outerIndex(bOuter); |
| const StorageIndex* fin = m_innerIndex.data() + m_outerIndex(bOuter + 1); |
| const StorageIndex* it = std::lower_bound(beg, fin, StorageIndex(bInner)); |
| if (it == fin || *it != bInner) return Scalar(0); |
| return blockRef(static_cast<Index>(it - m_innerIndex.data()))(localRow, localCol); |
| } |
| |
| /** Extract the main scalar diagonal as a dense vector. |
| * |
| * Iterates outer slices once and binary-searches for the diagonal block in each |
| * slice, then copies the relevant entries from that block — one search per outer |
| * slice (square blocks) or per unique inner-block boundary (non-square blocks), |
| * vs. one search per scalar element for the coeff-by-coeff approach. |
| */ |
| Matrix<Scalar, Dynamic, 1> diagonal() const { |
| constexpr Index OuterB = IsRowMajor ? BlockRows_ : BlockCols_; |
| constexpr Index InnerB = IsRowMajor ? BlockCols_ : BlockRows_; |
| const Index diagSize = numext::mini(rows(), cols()); |
| Matrix<Scalar, Dynamic, 1> diag = Matrix<Scalar, Dynamic, 1>::Zero(diagSize); |
| |
| for (Index out = 0; out < m_blockOuterSize; ++out) { |
| const Index scalarOuterBegin = out * OuterB; |
| if (scalarOuterBegin >= diagSize) break; |
| const Index scalarOuterEnd = numext::mini(scalarOuterBegin + OuterB, diagSize); |
| |
| // Group consecutive scalar positions that share the same inner block, then |
| // binary-search once per group rather than once per scalar element. |
| // For square blocks this loop runs exactly once per outer slice. |
| Index i = scalarOuterBegin; |
| while (i < scalarOuterEnd) { |
| const Index bInner = i / InnerB; |
| const Index groupEnd = numext::mini((bInner + 1) * InnerB, scalarOuterEnd); |
| |
| const StorageIndex* beg = m_innerIndex.data() + m_outerIndex(out); |
| const StorageIndex* fin = m_innerIndex.data() + m_outerIndex(out + 1); |
| const StorageIndex* it = std::lower_bound(beg, fin, StorageIndex(bInner)); |
| if (it != fin && *it == StorageIndex(bInner)) { |
| const ConstBlockMap blk = blockRef(static_cast<Index>(it - m_innerIndex.data())); |
| for (Index j = i; j < groupEnd; ++j) { |
| const Index localRow = IsRowMajor ? (j % OuterB) : (j % InnerB); |
| const Index localCol = IsRowMajor ? (j % InnerB) : (j % OuterB); |
| diag(j) = blk(localRow, localCol); |
| } |
| } |
| i = groupEnd; |
| } |
| } |
| return diag; |
| } |
| |
| // ------------------------------------------------------------------------- |
| // Arithmetic |
| // ------------------------------------------------------------------------- |
| |
| /** Element-wise addition. Both matrices must have the same block dimensions. */ |
| BlockSparseMatrix operator+(const BlockSparseMatrix& other) const { return disjunctionWith_(other, AddOp_{}); } |
| |
| /** Element-wise subtraction. */ |
| BlockSparseMatrix operator-(const BlockSparseMatrix& other) const { return disjunctionWith_(other, SubOp_{}); } |
| |
| /** Element-wise product. Only blocks present in \em both operands contribute to the result. */ |
| BlockSparseMatrix cwiseProduct(const BlockSparseMatrix& other) const { |
| return conjunctionWith_(other, CwiseMulOp_{}); |
| } |
| |
| /** Applies a scalar unary functor to every stored nonzero, preserving the sparsity pattern. */ |
| template <typename ScalarFunc> |
| BlockSparseMatrix unaryExpr(ScalarFunc func) const { |
| return withValues_([&func](const auto& v) { return v.unaryExpr(func); }); |
| } |
| |
| /** Applies a scalar binary functor with union sparsity. |
| * |
| * \p func must provide three members: |
| * \code |
| * Scalar func(Scalar a, Scalar b) // both present |
| * Scalar func.lhs(Scalar a) // only lhs present; rhs is implicitly zero |
| * Scalar func.rhs(Scalar b) // only rhs present; lhs is implicitly zero |
| * \endcode |
| */ |
| template <typename ScalarFunc> |
| BlockSparseMatrix disjunctionExpr(const BlockSparseMatrix& other, ScalarFunc func) const { |
| return disjunctionWith_(other, DisjExprAdapter_<ScalarFunc>{func}); |
| } |
| |
| /** Applies a scalar binary functor with intersection sparsity: only block positions present |
| * in \em both matrices contribute; \p func is called as \c func(Scalar a, Scalar b). */ |
| template <typename ScalarFunc> |
| BlockSparseMatrix conjunctionExpr(const BlockSparseMatrix& other, ScalarFunc func) const { |
| return conjunctionWith_(other, [&func](const auto& a, const auto& b) { return a.binaryExpr(b, func); }); |
| } |
| |
| /** Unary negation. */ |
| BlockSparseMatrix operator-() const { |
| return withValues_([](const auto& v) { return -v; }); |
| } |
| |
| BlockSparseMatrix& operator+=(const BlockSparseMatrix& other) { return *this = *this + other; } |
| BlockSparseMatrix& operator-=(const BlockSparseMatrix& other) { return *this = *this - other; } |
| |
| /** Scalar multiplication (returns a new matrix). */ |
| BlockSparseMatrix operator*(const Scalar& s) const { |
| return withValues_([&s](const auto& v) { return v * s; }); |
| } |
| BlockSparseMatrix& operator*=(const Scalar& s) { |
| m_values.head(nonZeros()) *= s; |
| return *this; |
| } |
| BlockSparseMatrix operator/(const Scalar& s) const { |
| return withValues_([&s](const auto& v) { return v / s; }); |
| } |
| BlockSparseMatrix& operator/=(const Scalar& s) { return *this *= (Scalar(1) / s); } |
| |
| /** Scalar-on-left multiplication. */ |
| friend BlockSparseMatrix operator*(const Scalar& s, const BlockSparseMatrix& m) { return m * s; } |
| |
| /** Block-sparse times dense matrix (or vector) product. |
| * |
| * Returns a lazy \c Product<> expression evaluated via \c generic_product_impl. |
| * This enables fused accumulation: |
| * \code |
| * b.noalias() = A * x; // no temporary |
| * b.noalias() += A * x; // fused add |
| * b.noalias() += alpha * (A * x); // scale then add via evaluator |
| * \endcode |
| * |
| * \pre \c this->cols() == rhs.rows(). |
| * \pre Scalar types must match. |
| * \warning The result uses \c AliasFreeProduct, so assignment goes directly |
| * through \c generic_product_impl::evalTo with no aliasing temporary. |
| * \c x = A * x silently corrupts; use an explicit temporary if needed. |
| */ |
| template <typename OtherDerived> |
| Product<BlockSparseMatrix, OtherDerived, AliasFreeProduct> operator*(const MatrixBase<OtherDerived>& rhs) const { |
| EIGEN_STATIC_ASSERT( |
| (std::is_same<Scalar, typename OtherDerived::Scalar>::value), |
| YOU_MIXED_DIFFERENT_NUMERIC_TYPES__YOU_NEED_TO_USE_THE_CAST_METHOD_OF_MATRIXBASE_TO_CAST_NUMERIC_TYPES_EXPLICITLY) |
| return Product<BlockSparseMatrix, OtherDerived, AliasFreeProduct>(*this, rhs.derived()); |
| } |
| |
| /** Dense matrix (or vector) times block-sparse product (hidden friend). |
| * |
| * Returns a lazy \c Product<> expression; evaluated via \c generic_product_impl. |
| * |
| * \pre \c lhs.cols() == bsm.rows(). |
| * \pre Scalar types must match. |
| * \warning The result uses \c AliasFreeProduct, so assignment goes directly |
| * through \c generic_product_impl::evalTo with no aliasing temporary. |
| * \c x = x * A silently corrupts; use an explicit temporary if needed. |
| */ |
| template <typename OtherDerived> |
| friend Product<OtherDerived, BlockSparseMatrix, AliasFreeProduct> operator*(const MatrixBase<OtherDerived>& lhs, |
| const BlockSparseMatrix& bsm) { |
| EIGEN_STATIC_ASSERT( |
| (std::is_same<Scalar_, typename OtherDerived::Scalar>::value), |
| YOU_MIXED_DIFFERENT_NUMERIC_TYPES__YOU_NEED_TO_USE_THE_CAST_METHOD_OF_MATRIXBASE_TO_CAST_NUMERIC_TYPES_EXPLICITLY) |
| return Product<OtherDerived, BlockSparseMatrix, AliasFreeProduct>(lhs.derived(), bsm); |
| } |
| |
| /** Block-sparse matrix product. |
| * |
| * \tparam RhsBlockCols Block-column count of the right-hand side. |
| * |
| * The left-hand side has block type \c Matrix<Scalar,BlockRows,BlockCols> |
| * and the right-hand side must have block type |
| * \c Matrix<Scalar,BlockCols,RhsBlockCols> (enforced by the template |
| * parameter). The result has block type |
| * \c Matrix<Scalar,BlockRows,RhsBlockCols>. |
| * |
| * \pre \c this->blockCols() == rhs.blockRows() (checked at runtime). |
| */ |
| template <int RhsBlockCols> |
| BlockSparseMatrix<Scalar_, Options_, BlockRows_, RhsBlockCols, StorageIndex_> operator*( |
| const BlockSparseMatrix<Scalar_, Options_, BlockCols_, RhsBlockCols, StorageIndex_>& rhs) const; |
| |
| // ------------------------------------------------------------------------- |
| // Approximate equality (useful for testing) |
| // ------------------------------------------------------------------------- |
| bool isApprox(const BlockSparseMatrix& other, |
| const typename NumTraits<Scalar>::Real& prec = NumTraits<Scalar>::dummy_precision()) const { |
| using RealScalar = typename NumTraits<Scalar>::Real; |
| // Frobenius-norm comparison, matching SparseMatrixBase::isApprox semantics |
| // but computed directly from the block values — no scalar-level SparseMatrix |
| // materialization of either operand. Explicit zero blocks contribute 0 to |
| // every norm, so the result is independent of structural differences. |
| RealScalar n2a = m_values.head(nonZeros()).matrix().squaredNorm(); |
| RealScalar n2b = other.m_values.head(other.nonZeros()).matrix().squaredNorm(); |
| BlockSparseMatrix diff = *this - other; |
| RealScalar d2 = diff.m_values.head(diff.nonZeros()).matrix().squaredNorm(); |
| return d2 <= prec * prec * numext::mini(n2a, n2b); |
| } |
| |
| // ------------------------------------------------------------------------- |
| // Transpose / adjoint |
| // ------------------------------------------------------------------------- |
| |
| /** Returns a new BSM whose block dimensions are swapped (BlockRows ↔ BlockCols) |
| * and each stored block is transposed. Storage order is preserved. */ |
| BlockSparseMatrix<Scalar_, Options_, BlockCols_, BlockRows_, StorageIndex_> transpose() const; |
| |
| /** Returns a new BSM whose block dimensions are swapped and each stored block |
| * is conjugate-transposed (adjoint). Identical to transpose() for real scalars. */ |
| BlockSparseMatrix<Scalar_, Options_, BlockCols_, BlockRows_, StorageIndex_> adjoint() const; |
| |
| // ------------------------------------------------------------------------- |
| // View factories |
| // ------------------------------------------------------------------------- |
| |
| /** Returns a block-level triangular view. |
| * |
| * \tparam Mode \c Eigen::Upper or \c Eigen::Lower. |
| * \tparam DiagIsTriangular When \c false (default), diagonal blocks are |
| * treated as triangular regardless of what is stored in the unused |
| * triangle — products use \c triangularView on the block and \c eval() |
| * explicitly zeros the unused triangle. When \c true, the caller |
| * guarantees that the unused triangle of every diagonal block is already |
| * zero; products then use a full vectorised GEMV (faster, no zeroing). |
| */ |
| template <int Mode, bool DiagIsTriangular = false> |
| BlockSparseTriangularView<BlockSparseMatrix, Mode, DiagIsTriangular> triangularView() const { |
| return BlockSparseTriangularView<BlockSparseMatrix, Mode, DiagIsTriangular>(*this); |
| } |
| |
| /** Returns a block-level self-adjoint view. |
| * |
| * Only the triangle selected by \p UpLo is read; the opposite triangle is |
| * reconstructed on-the-fly as the adjoint of each stored off-diagonal block. |
| * |
| * \tparam UpLo \c Eigen::Upper or \c Eigen::Lower. |
| * \tparam DiagIsSelfAdjoint Set to \c true when every diagonal block is |
| * itself Hermitian \em and both triangles are explicitly stored. |
| * The dense sub-product then uses a plain product rather than |
| * \c selfadjointView, which is more efficient for the small |
| * fixed-size blocks typical here. When \c false (the default), |
| * only the \p UpLo triangle of each diagonal block is assumed |
| * valid; \c selfadjointView<UpLo>() reconstructs the full block. |
| * |
| * \pre BlockRows == BlockCols (diagonal blocks must be square). |
| */ |
| template <int UpLo, bool DiagIsSelfAdjoint = false> |
| BlockSparseSelfAdjointView<BlockSparseMatrix, UpLo, DiagIsSelfAdjoint> selfadjointView() const { |
| EIGEN_STATIC_ASSERT(BlockRows_ == BlockCols_, THIS_METHOD_IS_ONLY_FOR_SQUARE_BLOCK_MATRICES) |
| return BlockSparseSelfAdjointView<BlockSparseMatrix, UpLo, DiagIsSelfAdjoint>(*this); |
| } |
| |
| private: |
| struct AddOp_ { |
| template <typename A, typename B> |
| BlockType operator()(const A& a, const B& b) const { |
| return a + b; |
| } |
| template <typename A> |
| BlockType lhs(const A& a) const { |
| return a; |
| } |
| template <typename B> |
| BlockType rhs(const B& b) const { |
| return b; |
| } |
| }; |
| |
| struct SubOp_ { |
| template <typename A, typename B> |
| BlockType operator()(const A& a, const B& b) const { |
| return a - b; |
| } |
| template <typename A> |
| BlockType lhs(const A& a) const { |
| return a; |
| } |
| template <typename B> |
| BlockType rhs(const B& b) const { |
| return -b; |
| } |
| }; |
| |
| // Scalar-to-block adapter for disjunctionExpr: translates scalar functor with lhs/rhs methods |
| // to the block level. |
| template <typename ScalarFunc> |
| struct DisjExprAdapter_ { |
| ScalarFunc func_; |
| template <typename A, typename B> |
| BlockType operator()(const A& a, const B& b) const { |
| return a.binaryExpr(b, func_); |
| } |
| template <typename A> |
| BlockType lhs(const A& a) const { |
| return a.unaryExpr([this](const Scalar& x) { return func_.lhs(x); }); |
| } |
| template <typename B> |
| BlockType rhs(const B& b) const { |
| return b.unaryExpr([this](const Scalar& x) { return func_.rhs(x); }); |
| } |
| }; |
| |
| struct CwiseMulOp_ { |
| template <typename A, typename B> |
| BlockType operator()(const A& a, const B& b) const { |
| return a.cwiseProduct(b); |
| } |
| }; |
| |
| // Returns a copy with the same sparsity structure but m_values replaced by f(m_values). |
| // f receives the flat Eigen Array of the logical (non-zero) coefficients and returns any |
| // compatible expression. Only the structure is copied — the source values are never |
| // duplicated, and unused tail capacity is neither copied nor evaluated. |
| template <typename F> |
| BlockSparseMatrix withValues_(F f) const { |
| Index nnz = nonZeroBlocks(); |
| BlockSparseMatrix result(blockRows(), blockCols()); |
| result.m_outerIndex = m_outerIndex; |
| result.m_innerIndex = m_innerIndex.head(nnz); |
| result.m_values = f(m_values.head(nnz * BlockSize)); |
| return result; |
| } |
| |
| // Disjunction (union-pattern): result has a block wherever *this OR other has one. |
| // lhs-only: block copied from *this unchanged |
| // rhs-only: op(b) — unary overload of op |
| // both: op(a, b) — binary overload of op |
| template <typename Op> |
| BlockSparseMatrix disjunctionWith_(const BlockSparseMatrix& other, Op op) const { |
| eigen_assert(blockRows() == other.blockRows() && blockCols() == other.blockCols() && |
| "BlockSparseMatrix size mismatch"); |
| BlockSparseMatrix result(blockRows(), blockCols()); |
| result.resizeBlockStorage_(nonZeroBlocks() + other.nonZeroBlocks()); |
| Index nnz = 0; |
| for (Index j = 0; j < m_blockOuterSize; ++j) { |
| result.m_outerIndex(j) = StorageIndex_(nnz); |
| Index aId = m_outerIndex(j); |
| Index aEnd = m_outerIndex(j + 1); |
| Index bId = other.m_outerIndex(j); |
| Index bEnd = other.m_outerIndex(j + 1); |
| while (aId < aEnd || bId < bEnd) { |
| bool hasA = aId < aEnd; |
| bool hasB = bId < bEnd; |
| Index aInner = hasA ? Index(m_innerIndex(aId)) : -1; |
| Index bInner = hasB ? Index(other.m_innerIndex(bId)) : -1; |
| // Write the result block in place; the op overloads return a BlockType, |
| // which is assigned straight into the result's storage Map. |
| if (hasA && (!hasB || aInner < bInner)) { |
| result.m_innerIndex(nnz) = StorageIndex_(aInner); |
| result.blockRef(nnz) = op.lhs(blockRef(aId++)); |
| } else if (hasB && (!hasA || bInner < aInner)) { |
| result.m_innerIndex(nnz) = StorageIndex_(bInner); |
| result.blockRef(nnz) = op.rhs(other.blockRef(bId++)); |
| } else { |
| result.m_innerIndex(nnz) = StorageIndex_(aInner); |
| result.blockRef(nnz) = op(blockRef(aId++), other.blockRef(bId++)); |
| } |
| ++nnz; |
| } |
| } |
| result.m_outerIndex(m_blockOuterSize) = StorageIndex_(nnz); |
| result.conservativeResizeBlockStorage_(nnz); |
| return result; |
| } |
| |
| // Conjunction (intersection-pattern): result has a block only where *this AND other both have one. |
| template <typename BinaryOp> |
| BlockSparseMatrix conjunctionWith_(const BlockSparseMatrix& other, BinaryOp func) const { |
| eigen_assert(blockRows() == other.blockRows() && blockCols() == other.blockCols() && |
| "BlockSparseMatrix size mismatch"); |
| BlockSparseMatrix result(blockRows(), blockCols()); |
| result.resizeBlockStorage_((std::min)(nonZeroBlocks(), other.nonZeroBlocks())); |
| Index nnz = 0; |
| for (Index j = 0; j < m_blockOuterSize; ++j) { |
| result.m_outerIndex(j) = StorageIndex_(nnz); |
| Index aId = m_outerIndex(j); |
| Index aEnd = m_outerIndex(j + 1); |
| Index bId = other.m_outerIndex(j); |
| Index bEnd = other.m_outerIndex(j + 1); |
| while (aId < aEnd && bId < bEnd) { |
| Index aInner = m_innerIndex(aId); |
| Index bInner = other.m_innerIndex(bId); |
| if (aInner < bInner) { |
| ++aId; |
| } else if (bInner < aInner) { |
| ++bId; |
| } else { |
| result.m_innerIndex(nnz) = StorageIndex_(aInner); |
| result.blockRef(nnz) = func(blockRef(aId++), other.blockRef(bId++)); |
| ++nnz; |
| } |
| } |
| } |
| result.m_outerIndex(m_blockOuterSize) = StorageIndex_(nnz); |
| result.conservativeResizeBlockStorage_(nnz); |
| return result; |
| } |
| |
| template <bool Conjugate> |
| BlockSparseMatrix<Scalar_, Options_, BlockCols_, BlockRows_, StorageIndex_> transposeImpl() const; |
| |
| // Resize both block storage arrays non-conservatively and update the capacity counter. |
| void resizeBlockStorage_(Index n) { |
| m_innerIndex.resize(n); |
| m_values.resize(n * BlockSize); |
| } |
| |
| void conservativeResizeBlockStorage_(Index n) { |
| m_innerIndex.conservativeResize(n); |
| m_values.conservativeResize(n * BlockSize); |
| } |
| |
| // ------------------------------------------------------------------------- |
| // Storage |
| // ------------------------------------------------------------------------- |
| Index m_blockOuterSize = 0; // block-cols (ColMajor) or block-rows (RowMajor) |
| Index m_blockInnerSize = 0; // block-rows (ColMajor) or block-cols (RowMajor) |
| |
| Array<StorageIndex, Dynamic, 1> m_outerIndex = // size: m_blockOuterSize + 1 |
| decltype(m_outerIndex)::Zero(m_blockOuterSize + 1); |
| Array<StorageIndex, Dynamic, 1> m_innerIndex; |
| // Block values stored consecutively; block k occupies |
| // m_values[k*BlockSize .. (k+1)*BlockSize - 1]. |
| Array<Scalar, Dynamic, 1> m_values; |
| |
| // ------------------------------------------------------------------------- |
| // MultiInnerIterator |
| // |
| // Simultaneously walks BlockOuterSize_ consecutive outer vectors of a |
| // compressed SparseMatrix, yielding scalar entries one at a time in |
| // non-decreasing inner-index order. On each step the sub-iterator with |
| // the smallest current inner index is the "active" one. |
| // |
| // BlockOuterSize_ is BlockCols_ for ColMajor (one block-column at a time) |
| // and BlockRows_ for RowMajor (one block-row at a time). |
| // ------------------------------------------------------------------------- |
| template <typename SparseMatrixType> |
| class MultiInnerIterator { |
| using StorageIndex = typename SparseMatrixType::StorageIndex; |
| static constexpr int BlockOuterSize_ = IsRowMajor ? BlockRows_ : BlockCols_; |
| |
| public: |
| MultiInnerIterator(const SparseMatrixType& mat, Index outerBase) |
| : m_outerPtr(mat.outerIndexPtr()), |
| m_innerPtr(mat.innerIndexPtr()), |
| m_valuePtr(mat.valuePtr()), |
| m_outerBase(outerBase) { |
| for (int k = 0; k < BlockOuterSize_; ++k) m_pos[k] = m_outerPtr[outerBase + k]; |
| advance(); |
| } |
| |
| EIGEN_STRONG_INLINE operator bool() const { return m_valid; } |
| |
| EIGEN_STRONG_INLINE MultiInnerIterator& operator++() { |
| ++m_pos[m_active]; |
| advance(); |
| return *this; |
| } |
| |
| // Absolute outer index of the current entry. |
| EIGEN_STRONG_INLINE Index outer() const { return m_outerBase + m_active; } |
| // Inner index of the current entry. |
| EIGEN_STRONG_INLINE StorageIndex index() const { return m_innerPtr[m_pos[m_active]]; } |
| // Scalar value of the current entry. |
| EIGEN_STRONG_INLINE Scalar value() const { return m_valuePtr[m_pos[m_active]]; } |
| |
| private: |
| // Find the sub-iterator with the smallest inner index. |
| void advance() { |
| m_valid = false; |
| for (int k = 0; k < BlockOuterSize_; ++k) { |
| if (m_pos[k] < m_outerPtr[m_outerBase + k + 1]) { |
| if (!m_valid || m_innerPtr[m_pos[k]] < m_innerPtr[m_pos[m_active]]) { |
| m_active = k; |
| m_valid = true; |
| } |
| } |
| } |
| } |
| |
| const StorageIndex* m_outerPtr; |
| const StorageIndex* m_innerPtr; |
| const Scalar* m_valuePtr; |
| Index m_outerBase; |
| StorageIndex m_pos[BlockOuterSize_]; |
| int m_active = 0; |
| bool m_valid = false; |
| }; |
| |
| // Allow other instantiations of BlockSparseMatrix to access private members |
| // (needed by operator*). |
| template <typename, int, int, int, typename> |
| friend class BlockSparseMatrix; |
| template <typename, int, bool> |
| friend class BlockSparseTriangularView; |
| template <typename, int, bool> |
| friend class BlockSparseSelfAdjointView; |
| }; |
| |
| // ============================================================================= |
| // Out-of-line method definitions |
| // ============================================================================= |
| |
| // ----------------------------------------------------------------------------- |
| // setFromTriplets |
| // ----------------------------------------------------------------------------- |
| |
| template <typename Scalar_, int Options_, int BlockRows_, int BlockCols_, typename StorageIndex_> |
| template <typename InputIterator> |
| void BlockSparseMatrix<Scalar_, Options_, BlockRows_, BlockCols_, StorageIndex_>::setFromTriplets(InputIterator begin, |
| InputIterator end) { |
| Index n = static_cast<Index>(std::distance(begin, end)); |
| |
| // Copy triplet coordinates and block values into Eigen arrays. |
| Array<StorageIndex, Dynamic, 1> tOuter(n), tInner(n); |
| Array<Scalar, Dynamic, 1> tValues(n * BlockSize); |
| |
| Index k = 0; |
| for (InputIterator it = begin; it != end; ++it, ++k) { |
| eigen_assert(it->row() >= 0 && it->row() < blockRows() && "setFromTriplets: block row out of range"); |
| eigen_assert(it->col() >= 0 && it->col() < blockCols() && "setFromTriplets: block col out of range"); |
| tOuter(k) = IsRowMajor ? StorageIndex(it->row()) : StorageIndex(it->col()); |
| tInner(k) = IsRowMajor ? StorageIndex(it->col()) : StorageIndex(it->row()); |
| BlockMap(tValues.data() + k * BlockSize) = it->value(); |
| } |
| |
| // Order the triplet indices by (outer, inner) using a stable LSD radix sort: |
| // pass 1 buckets by inner, pass 2 by outer. This runs in |
| // O(n + blockInnerSize + blockOuterSize) with sequential writes, avoiding the |
| // cache-unfriendly indirect comparison sort. |
| Array<Index, Dynamic, 1> order(n), scratch(n); |
| { |
| Array<Index, Dynamic, 1> count = Array<Index, Dynamic, 1>::Zero(m_blockInnerSize + 1); |
| for (Index i = 0; i < n; ++i) count(tInner(i) + 1)++; |
| for (Index i = 0; i < m_blockInnerSize; ++i) count(i + 1) += count(i); |
| for (Index i = 0; i < n; ++i) order(count(tInner(i))++) = i; |
| } |
| { |
| Array<Index, Dynamic, 1> count = Array<Index, Dynamic, 1>::Zero(m_blockOuterSize + 1); |
| for (Index i = 0; i < n; ++i) count(tOuter(i) + 1)++; |
| for (Index i = 0; i < m_blockOuterSize; ++i) count(i + 1) += count(i); |
| for (Index i = 0; i < n; ++i) { |
| Index idx = order(i); |
| scratch(count(tOuter(idx))++) = idx; |
| } |
| order.swap(scratch); |
| } |
| |
| // Reset and pre-allocate (worst case: all n triplets are distinct blocks). |
| m_outerIndex.resize(m_blockOuterSize + 1); |
| m_outerIndex.setZero(); |
| resizeBlockStorage_(n); |
| |
| Index nnz = 0; |
| k = 0; |
| while (k < n) { |
| Index pi = order(k); |
| StorageIndex outer = tOuter(pi); |
| StorageIndex inner = tInner(pi); |
| |
| BlockType block = ConstBlockMap(tValues.data() + pi * BlockSize); |
| ++k; |
| |
| // Accumulate duplicate entries at the same (outer, inner) position. |
| while (k < n) { |
| Index pk = order(k); |
| if (tOuter(pk) != outer || tInner(pk) != inner) break; |
| block += ConstBlockMap(tValues.data() + pk * BlockSize); |
| ++k; |
| } |
| |
| m_innerIndex(nnz) = inner; |
| blockRef(nnz) = block; |
| m_outerIndex(outer + 1)++; |
| ++nnz; |
| } |
| |
| // Trim to actual number of unique blocks. |
| conservativeResizeBlockStorage_(nnz); |
| |
| // Convert per-outer block counts to prefix sums. |
| for (Index j = 0; j < m_blockOuterSize; ++j) { |
| m_outerIndex(j + 1) += m_outerIndex(j); |
| } |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // toSparse |
| // ----------------------------------------------------------------------------- |
| |
| template <typename Scalar_, int Options_, int BlockRows_, int BlockCols_, typename StorageIndex_> |
| SparseMatrix<Scalar_, Options_, StorageIndex_> |
| BlockSparseMatrix<Scalar_, Options_, BlockRows_, BlockCols_, StorageIndex_>::toSparse() const { |
| SparseMatrix<Scalar_, Options_, StorageIndex_> result(rows(), cols()); |
| result.reserve(nonZeroBlocks() * BlockSize); |
| |
| if (!IsRowMajor) { |
| // ColMajor: outer = block-column j. Emit scalar columns j*BlockCols+c |
| // in order c = 0..BlockCols-1. Within each scalar column, blocks are |
| // sorted by bi (block-row), so scalar rows bi*BlockRows+r are increasing. |
| for (Index j = 0; j < m_blockOuterSize; ++j) { |
| for (Index c = 0; c < BlockCols_; ++c) { |
| result.startVec(j * BlockCols_ + c); |
| for (Index id = m_outerIndex(j); id < m_outerIndex(j + 1); ++id) { |
| Index bi = m_innerIndex(id); |
| ConstBlockMap blk = blockRef(id); |
| for (Index r = 0; r < BlockRows_; ++r) { |
| result.insertBack(bi * BlockRows_ + r, j * BlockCols_ + c) = blk(r, c); |
| } |
| } |
| } |
| } |
| } else { |
| // RowMajor: outer = block-row bi. Emit scalar rows bi*BlockRows+r |
| // in order r = 0..BlockRows-1. Within each scalar row, blocks are |
| // sorted by j (block-col), so scalar cols j*BlockCols+c are increasing. |
| for (Index bi = 0; bi < m_blockOuterSize; ++bi) { |
| for (Index r = 0; r < BlockRows_; ++r) { |
| result.startVec(bi * BlockRows_ + r); |
| for (Index id = m_outerIndex(bi); id < m_outerIndex(bi + 1); ++id) { |
| Index j = m_innerIndex(id); |
| ConstBlockMap blk = blockRef(id); |
| for (Index c = 0; c < BlockCols_; ++c) { |
| result.insertBack(bi * BlockRows_ + r, j * BlockCols_ + c) = blk(r, c); |
| } |
| } |
| } |
| } |
| } |
| |
| result.finalize(); |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // fromSparse |
| // ----------------------------------------------------------------------------- |
| |
| template <typename Scalar_, int Options_, int BlockRows_, int BlockCols_, typename StorageIndex_> |
| BlockSparseMatrix<Scalar_, Options_, BlockRows_, BlockCols_, StorageIndex_> |
| BlockSparseMatrix<Scalar_, Options_, BlockRows_, BlockCols_, StorageIndex_>::fromSparse( |
| const SparseMatrix<Scalar_, Options_, StorageIndex_>& sp) { |
| eigen_assert(sp.rows() % BlockRows_ == 0 && "matrix rows not divisible by BlockRows"); |
| eigen_assert(sp.cols() % BlockCols_ == 0 && "matrix cols not divisible by BlockCols"); |
| eigen_assert(sp.isCompressed() && "fromSparse requires a compressed SparseMatrix"); |
| |
| Index bRows = sp.rows() / BlockRows_; |
| Index bCols = sp.cols() / BlockCols_; |
| |
| // BlockOuterSize: how many consecutive outer vectors form one block-outer strip. |
| // BlockInnerSize: the inner dimension of each block. |
| constexpr Index BlockOuterSize = IsRowMajor ? BlockRows_ : BlockCols_; |
| constexpr Index BlockInnerSize = IsRowMajor ? BlockCols_ : BlockRows_; |
| constexpr StorageIndex_ kEmptyIndex = -1; |
| |
| using SpMat = SparseMatrix<Scalar_, Options_, StorageIndex_>; |
| |
| BlockSparseMatrix result(bRows, bCols); |
| |
| // Pass 1: count the number of unique block-inner indices per block-outer, |
| // by scanning each group of BlockOuterSize consecutive outer vectors together. |
| for (Index outerBlock = 0; outerBlock < result.m_blockOuterSize; ++outerBlock) { |
| StorageIndex_ prevInnerBlock = kEmptyIndex; |
| for (MultiInnerIterator<SpMat> it(sp, outerBlock * BlockOuterSize); it; ++it) { |
| StorageIndex_ innerBlock = it.index() / StorageIndex_(BlockInnerSize); |
| if (innerBlock != prevInnerBlock) { |
| result.m_outerIndex(outerBlock + 1)++; |
| prevInnerBlock = innerBlock; |
| } |
| } |
| } |
| |
| // Prefix sum → result.m_outerIndex becomes the standard CSC/CSR outer pointer. |
| for (Index j = 0; j < result.m_blockOuterSize; ++j) result.m_outerIndex(j + 1) += result.m_outerIndex(j); |
| |
| Index nBlocks = result.m_outerIndex(result.m_blockOuterSize); |
| result.resizeBlockStorage_(nBlocks); |
| result.m_values.setZero(); |
| |
| // Pass 2: scatter each scalar entry directly into its position within the |
| // pre-zeroed block value array. |
| for (Index outerBlock = 0; outerBlock < result.m_blockOuterSize; ++outerBlock) { |
| Index blockId = result.m_outerIndex(outerBlock) - 1; // incremented on first new block |
| StorageIndex_ prevInnerBlock = kEmptyIndex; |
| |
| for (MultiInnerIterator<SpMat> it(sp, outerBlock * BlockOuterSize); it; ++it) { |
| Index absOuter = it.outer(); // absolute outer index in sp |
| StorageIndex_ innerIdx = it.index(); // inner index in sp |
| StorageIndex_ innerBlock = innerIdx / StorageIndex_(BlockInnerSize); |
| |
| if (innerBlock != prevInnerBlock) { |
| ++blockId; |
| result.m_innerIndex(blockId) = innerBlock; |
| prevInnerBlock = innerBlock; |
| } |
| |
| // Scatter into block storage (layout matches the BSM's Options_). |
| // ColMajor blocks: col * BlockRows_ + row = localOuter * BlockInnerSize + localInner |
| // RowMajor blocks: row * BlockCols_ + col = localOuter * BlockInnerSize + localInner |
| Index localOuter = absOuter % BlockOuterSize; |
| Index localInner = innerIdx % BlockInnerSize; |
| Index offset = localOuter * BlockInnerSize + localInner; |
| |
| result.m_values(blockId * BlockSize + offset) = it.value(); |
| } |
| } |
| |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // operator* (block-sparse product) |
| // ----------------------------------------------------------------------------- |
| |
| template <typename Scalar_, int Options_, int BlockRows_, int BlockCols_, typename StorageIndex_> |
| template <int RhsBlockCols> |
| BlockSparseMatrix<Scalar_, Options_, BlockRows_, RhsBlockCols, StorageIndex_> |
| BlockSparseMatrix<Scalar_, Options_, BlockRows_, BlockCols_, StorageIndex_>::operator*( |
| const BlockSparseMatrix<Scalar_, Options_, BlockCols_, RhsBlockCols, StorageIndex_>& rhs) const { |
| using RhsMatrix = BlockSparseMatrix<Scalar_, Options_, BlockCols_, RhsBlockCols, StorageIndex_>; |
| using ResultMatrix = BlockSparseMatrix<Scalar_, Options_, BlockRows_, RhsBlockCols, StorageIndex_>; |
| using ResultBlock = Matrix<Scalar_, BlockRows_, RhsBlockCols, Options_>; |
| constexpr int ResultBlockSize = BlockRows_ * RhsBlockCols; |
| |
| eigen_assert(blockCols() == rhs.blockRows() && "BlockSparseMatrix product: lhs.blockCols() != rhs.blockRows()"); |
| |
| Index cBlockRows = blockRows(); |
| Index cBlockCols = rhs.blockCols(); |
| ResultMatrix result(cBlockRows, cBlockCols); |
| |
| // For ColMajor: mask / accum indexed by block-row (size = cBlockRows). |
| // For RowMajor: mask / accum indexed by block-col (size = cBlockCols). |
| Index maskSize = IsRowMajor ? cBlockCols : cBlockRows; |
| Array<uint8_t, Dynamic, 1> mask = Array<uint8_t, Dynamic, 1>::Zero(maskSize); |
| Array<Scalar_, Dynamic, 1> accumData(maskSize * ResultBlockSize); |
| Array<Index, Dynamic, 1> indices(maskSize); |
| Index nIndices = 0; |
| // The mask is read and written through coeff/coeffRef below: the block indices |
| // are in range by construction, and operator()'s bounds check gives GCC 14+ a |
| // false -Wstringop-overflow on a negative StorageIndex_. |
| |
| // Grow result storage geometrically rather than pre-allocating the dense |
| // worst case (cBlockRows*cBlockCols blocks): the product is typically far |
| // sparser than that, so the dense bound would blow up peak memory. |
| // capacity is always kept <= maxResultNnz (the true upper bound), and since |
| // any single outer emits at most maskSize blocks, the initial estimate of |
| // maskSize guarantees the first outer fits before the first grow check. |
| Index cOuterSize = result.m_blockOuterSize; |
| Index maxResultNnz = cBlockRows * cBlockCols; |
| Index capacity = numext::mini(maxResultNnz, numext::maxi(maskSize, nonZeroBlocks() + rhs.nonZeroBlocks())); |
| result.resizeBlockStorage_(capacity); |
| Index nnz = 0; |
| |
| for (Index out = 0; out < cOuterSize; ++out) { |
| result.m_outerIndex(out) = StorageIndex_(nnz); |
| |
| if (!IsRowMajor) { |
| // ColMajor: out is block-column j of the result. |
| // For each block B(k,j) and each block A(bi,k): C(bi,j) += A(bi,k)*B(k,j). |
| Index j = out; |
| for (Index rhsId = rhs.m_outerIndex(j); rhsId < rhs.m_outerIndex(j + 1); ++rhsId) { |
| Index k = rhs.m_innerIndex(rhsId); |
| typename RhsMatrix::ConstBlockMap Bkj = rhs.blockRef(rhsId); |
| for (Index lhsId = m_outerIndex(k); lhsId < m_outerIndex(k + 1); ++lhsId) { |
| Index bi = m_innerIndex(lhsId); |
| if (!mask.coeff(bi)) { |
| mask.coeffRef(bi) = 1; |
| Map<ResultBlock>(accumData.data() + bi * ResultBlockSize).noalias() = blockRef(lhsId) * Bkj; |
| indices(nIndices++) = bi; |
| } else { |
| Map<ResultBlock>(accumData.data() + bi * ResultBlockSize).noalias() += blockRef(lhsId) * Bkj; |
| } |
| } |
| } |
| } else { |
| // RowMajor: out is block-row bi of the result. |
| // For each block A(bi,k) and each block B(k,j): C(bi,j) += A(bi,k)*B(k,j). |
| Index bi = out; |
| for (Index lhsId = m_outerIndex(bi); lhsId < m_outerIndex(bi + 1); ++lhsId) { |
| Index k = m_innerIndex(lhsId); |
| ConstBlockMap Aik = blockRef(lhsId); |
| for (Index rhsId = rhs.m_outerIndex(k); rhsId < rhs.m_outerIndex(k + 1); ++rhsId) { |
| Index j = rhs.m_innerIndex(rhsId); |
| if (!mask.coeff(j)) { |
| mask.coeffRef(j) = 1; |
| Map<ResultBlock>(accumData.data() + j * ResultBlockSize).noalias() = Aik * rhs.blockRef(rhsId); |
| indices(nIndices++) = j; |
| } else { |
| Map<ResultBlock>(accumData.data() + j * ResultBlockSize).noalias() += Aik * rhs.blockRef(rhsId); |
| } |
| } |
| } |
| } |
| |
| // Sort the accumulated indices so the result's inner index array is sorted. |
| std::sort(indices.data(), indices.data() + nIndices); |
| if (nnz + nIndices > capacity) { |
| capacity = numext::mini(maxResultNnz, numext::maxi(2 * capacity, nnz + nIndices)); |
| result.conservativeResizeBlockStorage_(capacity); |
| } |
| for (Index ki = 0; ki < nIndices; ++ki) { |
| Index idx = indices(ki); |
| result.m_innerIndex(nnz) = StorageIndex_(idx); |
| result.blockRef(nnz) = Map<ResultBlock>(accumData.data() + idx * ResultBlockSize); |
| mask.coeffRef(idx) = 0; |
| ++nnz; |
| } |
| nIndices = 0; |
| } |
| result.m_outerIndex(cOuterSize) = StorageIndex_(nnz); |
| |
| // Trim to actual number of result blocks. |
| result.conservativeResizeBlockStorage_(nnz); |
| |
| return result; |
| } |
| |
| // ============================================================================= |
| // BlockSparseTriangularView |
| // ============================================================================= |
| |
| /** \class BlockSparseTriangularView |
| * \ingroup SparseCore_Module |
| * \brief Lazy block-level triangular view of a BlockSparseMatrix. |
| * |
| * Obtained via \c BSM::triangularView<Mode>() or |
| * \c BSM::triangularView<Mode, true>() (the latter asserts that the unused |
| * triangle of every diagonal block is already zero, enabling faster vectorised |
| * products without an explicit triangularView on the block). |
| * |
| * By default (\p DiagIsTriangular = \c false) diagonal blocks are treated as |
| * triangular regardless of what is stored in the unused half: products call |
| * \c block.triangularView<DiagMode>() and \c eval() zeroes the unused |
| * triangle. Set \p DiagIsTriangular = \c true to skip that overhead when you |
| * can guarantee the unused triangle is already zero. |
| */ |
| template <typename BSM, int Mode, bool DiagIsTriangular = false> |
| class BlockSparseTriangularView { |
| public: |
| using Scalar = typename BSM::Scalar; |
| using StorageIndex = typename BSM::StorageIndex; |
| using BlockType = typename BSM::BlockType; |
| using BlockMap = typename BSM::BlockMap; |
| using ConstBlockMap = typename BSM::ConstBlockMap; |
| static constexpr int BlockRows = BSM::BlockRows; |
| static constexpr int BlockCols = BSM::BlockCols; |
| static constexpr int BlockSize = BSM::BlockSize; |
| static constexpr bool IsRowMajor = BSM::IsRowMajor; |
| static constexpr bool IsUpper = (Mode & Upper) != 0; |
| |
| explicit BlockSparseTriangularView(const BSM& m) : m_matrix(m) {} |
| |
| Index rows() const { return m_matrix.rows(); } |
| Index cols() const { return m_matrix.cols(); } |
| |
| // ---- Materialize --------------------------------------------------------- |
| |
| /** Copy the triangular blocks into a new BSM; off-triangle blocks are dropped. |
| * When DiagIsTriangular is false the unused triangle of each diagonal block |
| * is explicitly zeroed in the output. */ |
| BSM eval() const { |
| constexpr int ZeroMode = IsUpper ? StrictlyLower : StrictlyUpper; |
| const BSM& m = m_matrix; |
| BSM result(m.blockRows(), m.blockCols()); |
| result.resizeBlockStorage_(m.nonZeroBlocks()); |
| Index nnz = 0; |
| |
| for (Index out = 0; out < m.m_blockOuterSize; ++out) { |
| result.m_outerIndex(out) = StorageIndex(nnz); |
| for (Index id = m.m_outerIndex(out); id < m.m_outerIndex(out + 1); ++id) { |
| Index inner = m.m_innerIndex(id); |
| Index bi = IsRowMajor ? out : inner; |
| Index bj = IsRowMajor ? inner : out; |
| if (IsUpper ? (bj < bi) : (bj > bi)) continue; |
| result.m_innerIndex(nnz) = StorageIndex(inner); |
| result.m_values.template segment<BlockSize>(nnz * BlockSize) = |
| m.m_values.template segment<BlockSize>(id * BlockSize); |
| EIGEN_IF_CONSTEXPR (!DiagIsTriangular) { |
| if (bi == bj) |
| BlockMap(result.m_values.data() + nnz * BlockSize).template triangularView<ZeroMode>().setZero(); |
| } |
| ++nnz; |
| } |
| } |
| result.m_outerIndex(m.m_blockOuterSize) = StorageIndex(nnz); |
| result.conservativeResizeBlockStorage_(nnz); |
| return result; |
| } |
| |
| /** Convert to a scalar-level SparseMatrix (off-triangle blocks zeroed). */ |
| SparseMatrix<Scalar, BSM::Options, StorageIndex> toSparse() const { return eval().toSparse(); } |
| |
| // ---- Arithmetic ---------------------------------------------------------- |
| |
| BSM operator+(const BlockSparseTriangularView& other) const { return eval() + other.eval(); } |
| BSM operator-(const BlockSparseTriangularView& other) const { return eval() - other.eval(); } |
| |
| /** Tri × BSM product (materialises this view then delegates). */ |
| template <int RhsBlockCols> |
| BlockSparseMatrix<Scalar, BSM::Options, BlockRows, RhsBlockCols, StorageIndex> operator*( |
| const BlockSparseMatrix<Scalar, BSM::Options, BlockCols, RhsBlockCols, StorageIndex>& rhs) const { |
| return eval() * rhs; |
| } |
| |
| // ---- Dense products (no intermediate materialisation) -------------------- |
| |
| template <typename OtherDerived> |
| Matrix<Scalar, Dynamic, OtherDerived::ColsAtCompileTime> operator*(const MatrixBase<OtherDerived>& rhs) const { |
| EIGEN_STATIC_ASSERT( |
| (std::is_same<Scalar, typename OtherDerived::Scalar>::value), |
| YOU_MIXED_DIFFERENT_NUMERIC_TYPES__YOU_NEED_TO_USE_THE_CAST_METHOD_OF_MATRIXBASE_TO_CAST_NUMERIC_TYPES_EXPLICITLY) |
| eigen_assert(m_matrix.cols() == rhs.rows() && "BlockSparseTriangularView * Dense: dimension mismatch"); |
| using ResultType = Matrix<Scalar, Dynamic, OtherDerived::ColsAtCompileTime>; |
| ResultType result = ResultType::Zero(m_matrix.rows(), rhs.cols()); |
| for (Index out = 0; out < m_matrix.m_blockOuterSize; ++out) { |
| for (Index id = m_matrix.m_outerIndex(out); id < m_matrix.m_outerIndex(out + 1); ++id) { |
| Index inner = m_matrix.m_innerIndex(id); |
| Index bi = IsRowMajor ? out : inner; |
| Index bj = IsRowMajor ? inner : out; |
| if (IsUpper ? (bj < bi) : (bj > bi)) continue; |
| constexpr int DiagMode = IsUpper ? Upper : Lower; |
| if (!DiagIsTriangular && bi == bj) |
| result.template middleRows<BlockRows>(bi * BlockRows).noalias() += |
| m_matrix.blockRef(id).template triangularView<DiagMode>() * |
| rhs.template middleRows<BlockCols>(bj * BlockCols); |
| else |
| result.template middleRows<BlockRows>(bi * BlockRows).noalias() += |
| m_matrix.blockRef(id) * rhs.template middleRows<BlockCols>(bj * BlockCols); |
| } |
| } |
| return result; |
| } |
| |
| template <typename OtherDerived> |
| friend Matrix<Scalar, OtherDerived::RowsAtCompileTime, Dynamic> operator*(const MatrixBase<OtherDerived>& lhs, |
| const BlockSparseTriangularView& tri) { |
| EIGEN_STATIC_ASSERT( |
| (std::is_same<Scalar, typename OtherDerived::Scalar>::value), |
| YOU_MIXED_DIFFERENT_NUMERIC_TYPES__YOU_NEED_TO_USE_THE_CAST_METHOD_OF_MATRIXBASE_TO_CAST_NUMERIC_TYPES_EXPLICITLY) |
| eigen_assert(lhs.cols() == tri.m_matrix.rows() && "Dense * BlockSparseTriangularView: dimension mismatch"); |
| constexpr bool isRM = BSM::IsRowMajor; |
| using ResultType = Matrix<Scalar, OtherDerived::RowsAtCompileTime, Dynamic>; |
| // BSM befriends BlockSparseTriangularView, but GCC <= 11 and MSVC do not |
| // extend that friendship to the body of a friend function defined inside |
| // the view (CWG 1699), so only BSM's public interface may be used here. |
| const BSM& m = tri.m_matrix; |
| const StorageIndex* outerPtr = m.outerIndexPtr(); |
| const StorageIndex* innerPtr = m.innerIndexPtr(); |
| ResultType result = ResultType::Zero(lhs.rows(), m.cols()); |
| for (Index out = 0; out < m.blockOuterSize(); ++out) { |
| for (Index id = outerPtr[out]; id < outerPtr[out + 1]; ++id) { |
| Index inner = innerPtr[id]; |
| Index bi = isRM ? out : inner; |
| Index bj = isRM ? inner : out; |
| if (IsUpper ? (bj < bi) : (bj > bi)) continue; |
| constexpr int DiagMode = IsUpper ? Upper : Lower; |
| if (!DiagIsTriangular && bi == bj) |
| result.template middleCols<BlockCols>(bj * BlockCols).noalias() += |
| lhs.template middleCols<BlockRows>(bi * BlockRows) * m.blockRef(id).template triangularView<DiagMode>(); |
| else |
| result.template middleCols<BlockCols>(bj * BlockCols).noalias() += |
| lhs.template middleCols<BlockRows>(bi * BlockRows) * m.blockRef(id); |
| } |
| } |
| return result; |
| } |
| |
| // ---- Triangular solve ------------------------------------------------------- |
| |
| /** Solve T * x = rhs in-place. Requires square blocks. |
| * ColMajor Lower: forward sub, diagonal first per column. |
| * ColMajor Upper: backward sub, diagonal last per column. |
| * RowMajor Lower: forward sub, diagonal last per row. |
| * RowMajor Upper: backward sub, diagonal first per row. |
| */ |
| template <typename Derived> |
| void solveInPlace(MatrixBase<Derived>& x) const { |
| doSolveImpl<false, false>(x.derived()); |
| } |
| |
| /** Proxy returned by transpose(): solveInPlace solves T^T x = b. */ |
| struct TransposeReturnType { |
| const BlockSparseTriangularView& m_tri; |
| template <typename Derived> |
| void solveInPlace(MatrixBase<Derived>& x) const { |
| m_tri.template doSolveImpl<true, false>(x.derived()); |
| } |
| }; |
| |
| /** Proxy returned by adjoint(): solveInPlace solves T^H x = b. */ |
| struct AdjointReturnType { |
| const BlockSparseTriangularView& m_tri; |
| template <typename Derived> |
| void solveInPlace(MatrixBase<Derived>& x) const { |
| m_tri.template doSolveImpl<true, true>(x.derived()); |
| } |
| }; |
| |
| TransposeReturnType transpose() const { return {*this}; } |
| AdjointReturnType adjoint() const { return {*this}; } |
| |
| private: |
| const BSM& m_matrix; |
| |
| // Non-transposed solve for both storage orders. |
| // |
| // diagFirst = (IsUpper == IsRowMajor): ColMajor Lower→first, ColMajor Upper→last, |
| // RowMajor Lower→last, RowMajor Upper→first. |
| // Loop direction: forward for Lower, backward for Upper (same for both storage orders). |
| // ColMajor: solve diagonal first, then scatter x[inner] -= blk * x[k]. |
| // RowMajor: gather x[k] -= blk * x[inner] first, then solve diagonal. |
| template <typename Derived> |
| void doSolveDirect(Derived& x) const { |
| EIGEN_STATIC_ASSERT(BlockRows == BlockCols, THIS_METHOD_IS_ONLY_FOR_SQUARE_BLOCK_MATRICES) |
| constexpr int DiagMode = IsUpper ? Upper : Lower; |
| constexpr bool diagFirst = (IsUpper == BSM::IsRowMajor); |
| Index nb = m_matrix.blockCols(); // == blockRows() for square matrices |
| eigen_assert(x.rows() == m_matrix.rows() && "solveInPlace: size mismatch"); |
| |
| const StorageIndex* innerPtr = m_matrix.innerIndexPtr(); |
| const StorageIndex* outerPtr = m_matrix.outerIndexPtr(); |
| |
| Index outerStart = IsUpper ? nb - 1 : 0; |
| Index outerEnd = IsUpper ? -1 : nb; |
| constexpr Index kStep = IsUpper ? -1 : 1; |
| |
| for (Index k = outerStart; k != outerEnd; k += kStep) { |
| const StorageIndex* beg = innerPtr + outerPtr[k]; |
| const StorageIndex* end = innerPtr + outerPtr[k + 1]; |
| if (beg == end) continue; |
| const StorageIndex* diag_ptr = diagFirst ? beg : end - 1; |
| const StorageIndex* off_beg = diagFirst ? beg + 1 : beg; |
| const StorageIndex* off_end = diagFirst ? end : end - 1; |
| eigen_assert(*diag_ptr == k); |
| EIGEN_IF_CONSTEXPR (!BSM::IsRowMajor) { |
| m_matrix.blockRef(diag_ptr - innerPtr) |
| .template triangularView<DiagMode>() |
| .solveInPlace(x.template middleRows<BlockRows>(k * BlockRows)); |
| for (const StorageIndex* it = off_beg; it != off_end; ++it) |
| x.template middleRows<BlockRows>(*it * BlockRows).noalias() -= |
| m_matrix.blockRef(it - innerPtr) * x.template middleRows<BlockRows>(k * BlockRows); |
| } else { |
| for (const StorageIndex* it = off_beg; it != off_end; ++it) |
| x.template middleRows<BlockRows>(k * BlockRows).noalias() -= |
| m_matrix.blockRef(it - innerPtr) * x.template middleRows<BlockRows>(*it * BlockRows); |
| m_matrix.blockRef(diag_ptr - innerPtr) |
| .template triangularView<DiagMode>() |
| .solveInPlace(x.template middleRows<BlockRows>(k * BlockRows)); |
| } |
| } |
| } |
| |
| // Transposed/adjoint solve for both storage orders. |
| // |
| // Loop direction: forward for Upper, backward for Lower (same for both storage orders). |
| // ColMajor: gather x[k] -= adj(blk) * x[inner] first, then solve adj(diagonal). |
| // RowMajor: solve adj(diagonal) first, then scatter x[inner] -= adj(blk) * x[k]. |
| template <bool Conjugate, typename Derived> |
| void doSolveTransposed(Derived& x) const { |
| EIGEN_STATIC_ASSERT(BlockRows == BlockCols, THIS_METHOD_IS_ONLY_FOR_SQUARE_BLOCK_MATRICES) |
| constexpr int DiagMode = IsUpper ? Upper : Lower; |
| constexpr bool diagFirst = (IsUpper == BSM::IsRowMajor); |
| Index nb = m_matrix.blockCols(); // == blockRows() for square matrices |
| eigen_assert(x.rows() == m_matrix.rows() && "solveInPlace: size mismatch"); |
| |
| const StorageIndex* innerPtr = m_matrix.innerIndexPtr(); |
| const StorageIndex* outerPtr = m_matrix.outerIndexPtr(); |
| |
| Index outerStart = IsUpper ? 0 : nb - 1; |
| Index outerEnd = IsUpper ? nb : -1; |
| constexpr Index kStep = IsUpper ? 1 : -1; |
| |
| for (Index k = outerStart; k != outerEnd; k += kStep) { |
| const StorageIndex* beg = innerPtr + outerPtr[k]; |
| const StorageIndex* end = innerPtr + outerPtr[k + 1]; |
| if (beg == end) continue; |
| const StorageIndex* diag_ptr = diagFirst ? beg : end - 1; |
| const StorageIndex* off_beg = diagFirst ? beg + 1 : beg; |
| const StorageIndex* off_end = diagFirst ? end : end - 1; |
| eigen_assert(*diag_ptr == k); |
| EIGEN_IF_CONSTEXPR (!BSM::IsRowMajor) { |
| for (const StorageIndex* it = off_beg; it != off_end; ++it) |
| x.template middleRows<BlockRows>(k * BlockRows).noalias() -= |
| internal::adjoint_if<Conjugate>(m_matrix.blockRef(it - innerPtr)) * |
| x.template middleRows<BlockRows>(*it * BlockRows); |
| internal::adjoint_if<Conjugate>(m_matrix.blockRef(diag_ptr - innerPtr).template triangularView<DiagMode>()) |
| .solveInPlace(x.template middleRows<BlockRows>(k * BlockRows)); |
| } else { |
| internal::adjoint_if<Conjugate>(m_matrix.blockRef(diag_ptr - innerPtr).template triangularView<DiagMode>()) |
| .solveInPlace(x.template middleRows<BlockRows>(k * BlockRows)); |
| for (const StorageIndex* it = off_beg; it != off_end; ++it) |
| x.template middleRows<BlockRows>(*it * BlockRows).noalias() -= |
| internal::adjoint_if<Conjugate>(m_matrix.blockRef(it - innerPtr)) * |
| x.template middleRows<BlockRows>(k * BlockRows); |
| } |
| } |
| } |
| |
| template <bool Transposed, bool Conjugate, typename Derived> |
| void doSolveImpl(Derived& x) const { |
| EIGEN_IF_CONSTEXPR (!Transposed) |
| doSolveDirect(x); |
| else |
| doSolveTransposed<Conjugate>(x); |
| } |
| }; |
| |
| // ============================================================================= |
| // BlockSparseSelfAdjointView |
| // ============================================================================= |
| |
| /** \class BlockSparseSelfAdjointView |
| * \ingroup SparseCore_Module |
| * \brief Lazy block-level self-adjoint (Hermitian) view of a BlockSparseMatrix. |
| * |
| * Obtained via \c BSM::selfadjointView<UpLo>() or |
| * \c BSM::selfadjointView<UpLo, true>() (the latter signals that every |
| * diagonal block is itself Hermitian, enabling DSYMM/ZHEMM on those blocks). |
| * |
| * Only the triangle selected by \p UpLo is read; the other triangle is |
| * reconstructed on-the-fly as the adjoint of each stored off-diagonal block. |
| * |
| * \pre BSM::BlockRows == BSM::BlockCols. |
| */ |
| template <typename BSM, int UpLo, bool DiagIsSelfAdjoint> |
| class BlockSparseSelfAdjointView { |
| public: |
| using Scalar = typename BSM::Scalar; |
| using StorageIndex = typename BSM::StorageIndex; |
| using BlockType = typename BSM::BlockType; |
| using BlockMap = typename BSM::BlockMap; |
| using ConstBlockMap = typename BSM::ConstBlockMap; |
| static constexpr int BlockRows = BSM::BlockRows; // == BlockCols |
| static constexpr int BlockCols = BSM::BlockCols; |
| static constexpr int BlockSize = BSM::BlockSize; |
| static constexpr bool IsRowMajor = BSM::IsRowMajor; |
| static constexpr bool IsUpper = (UpLo & Upper) != 0; |
| // UpLo passed to Eigen's dense selfadjointView on diagonal blocks: |
| static constexpr int DiagUpLo = IsUpper ? Upper : Lower; |
| |
| explicit BlockSparseSelfAdjointView(const BSM& m) : m_matrix(m) {} |
| |
| Index rows() const { return m_matrix.rows(); } |
| Index cols() const { return m_matrix.cols(); } |
| |
| // ---- Materialize --------------------------------------------------------- |
| |
| /** Build a full symmetric BSM: stored triangle + adjoint mirror of each |
| * off-diagonal block. Diagonal blocks: when DiagIsSelfAdjoint is false, |
| * both triangles are filled from the stored triangle via selfadjointView; |
| * when true the block is already fully populated and is copied as-is. */ |
| BSM eval() const { |
| const BSM& m = m_matrix; |
| |
| Index nDiag = 0, nOff = 0; |
| for (Index out = 0; out < m.m_blockOuterSize; ++out) |
| for (Index id = m.m_outerIndex(out); id < m.m_outerIndex(out + 1); ++id) { |
| Index inner = m.m_innerIndex(id); |
| Index bi = IsRowMajor ? out : inner; |
| Index bj = IsRowMajor ? inner : out; |
| if (IsUpper ? (bj < bi) : (bj > bi)) continue; |
| if (bi == bj) |
| ++nDiag; |
| else |
| ++nOff; |
| } |
| |
| Index nTotal = nDiag + 2 * nOff; |
| |
| Array<StorageIndex, Dynamic, 1> brows(nTotal), bcols(nTotal); |
| Array<Scalar, Dynamic, 1> bvals(nTotal * BlockSize); |
| |
| Index k = 0; |
| for (Index out = 0; out < m.m_blockOuterSize; ++out) |
| for (Index id = m.m_outerIndex(out); id < m.m_outerIndex(out + 1); ++id) { |
| Index inner = m.m_innerIndex(id); |
| Index bi = IsRowMajor ? out : inner; |
| Index bj = IsRowMajor ? inner : out; |
| if (IsUpper ? (bj < bi) : (bj > bi)) continue; |
| |
| brows(k) = StorageIndex(bi); |
| bcols(k) = StorageIndex(bj); |
| if (!DiagIsSelfAdjoint && bi == bj) |
| BlockMap(bvals.data() + k * BlockSize) = m.blockRef(id).template selfadjointView<DiagUpLo>(); |
| else |
| BlockMap(bvals.data() + k * BlockSize) = m.blockRef(id); |
| ++k; |
| |
| if (bi != bj) { |
| brows(k) = StorageIndex(bj); |
| bcols(k) = StorageIndex(bi); |
| BlockMap(bvals.data() + k * BlockSize) = m.blockRef(id).adjoint(); |
| ++k; |
| } |
| } |
| |
| // Sort by (outer, inner) then build BSM directly (no duplicates by construction). |
| Array<Index, Dynamic, 1> perm(nTotal); |
| std::iota(perm.data(), perm.data() + nTotal, Index(0)); |
| std::sort(perm.data(), perm.data() + nTotal, [&](Index a, Index b) { |
| StorageIndex ao = IsRowMajor ? brows(a) : bcols(a); |
| StorageIndex bo = IsRowMajor ? brows(b) : bcols(b); |
| if (ao != bo) return ao < bo; |
| return (IsRowMajor ? bcols(a) : brows(a)) < (IsRowMajor ? bcols(b) : brows(b)); |
| }); |
| |
| BSM result(m.blockRows(), m.blockCols()); |
| result.resizeBlockStorage_(nTotal); |
| |
| for (Index ki = 0; ki < nTotal; ++ki) { |
| Index pi = perm(ki); |
| StorageIndex outer = IsRowMajor ? brows(pi) : bcols(pi); |
| StorageIndex inner = IsRowMajor ? bcols(pi) : brows(pi); |
| result.m_outerIndex(outer + 1)++; |
| result.m_innerIndex(ki) = inner; |
| result.m_values.template segment<BlockSize>(ki * BlockSize) = bvals.template segment<BlockSize>(pi * BlockSize); |
| } |
| for (Index j = 0; j < result.m_blockOuterSize; ++j) result.m_outerIndex(j + 1) += result.m_outerIndex(j); |
| |
| return result; |
| } |
| |
| /** Convert to a symmetrised scalar-level SparseMatrix. */ |
| SparseMatrix<Scalar, BSM::Options, StorageIndex> toSparse() const { return eval().toSparse(); } |
| |
| // ---- Arithmetic ---------------------------------------------------------- |
| |
| BSM operator+(const BlockSparseSelfAdjointView& other) const { return eval() + other.eval(); } |
| BSM operator-(const BlockSparseSelfAdjointView& other) const { return eval() - other.eval(); } |
| |
| /** SelfAdj × BSM: materialises the view then uses the general SpGEMM. */ |
| template <int RhsBlockCols> |
| BlockSparseMatrix<Scalar, BSM::Options, BlockRows, RhsBlockCols, StorageIndex> operator*( |
| const BlockSparseMatrix<Scalar, BSM::Options, BlockCols, RhsBlockCols, StorageIndex>& rhs) const { |
| return eval() * rhs; |
| } |
| |
| // ---- Dense products (no materialisation; exploits both triangles) --------- |
| |
| /** SelfAdj × Dense. |
| * |
| * Off-diagonal stored block A(bi,bj) contributes: |
| * result(bi) += A(bi,bj) * rhs(bj) [stored triangle] |
| * result(bj) += A(bi,bj)^H * rhs(bi) [implicit mirror] |
| * |
| * When DiagIsSelfAdjoint is true, both triangles of each diagonal block |
| * are valid; a plain product is used (faster for small fixed-size blocks). |
| * Otherwise only the UpLo triangle is assumed valid and selfadjointView |
| * reconstructs the full diagonal-block product. |
| */ |
| template <typename OtherDerived> |
| Matrix<Scalar, Dynamic, OtherDerived::ColsAtCompileTime> operator*(const MatrixBase<OtherDerived>& rhs) const { |
| EIGEN_STATIC_ASSERT( |
| (std::is_same<Scalar, typename OtherDerived::Scalar>::value), |
| YOU_MIXED_DIFFERENT_NUMERIC_TYPES__YOU_NEED_TO_USE_THE_CAST_METHOD_OF_MATRIXBASE_TO_CAST_NUMERIC_TYPES_EXPLICITLY) |
| eigen_assert(m_matrix.cols() == rhs.rows() && "BlockSparseSelfAdjointView * Dense: dimension mismatch"); |
| using ResultType = Matrix<Scalar, Dynamic, OtherDerived::ColsAtCompileTime>; |
| ResultType result = ResultType::Zero(m_matrix.rows(), rhs.cols()); |
| |
| for (Index out = 0; out < m_matrix.m_blockOuterSize; ++out) |
| for (Index id = m_matrix.m_outerIndex(out); id < m_matrix.m_outerIndex(out + 1); ++id) { |
| Index inner = m_matrix.m_innerIndex(id); |
| Index bi = IsRowMajor ? out : inner; |
| Index bj = IsRowMajor ? inner : out; |
| if (IsUpper ? (bj < bi) : (bj > bi)) continue; |
| |
| if (bi == bj) { |
| EIGEN_IF_CONSTEXPR (DiagIsSelfAdjoint) { |
| result.template middleRows<BlockRows>(bi * BlockRows).noalias() += |
| m_matrix.blockRef(id) * rhs.template middleRows<BlockCols>(bj * BlockCols); |
| } else { |
| // Materialize the tiny diagonal block as a fixed-size Hermitian matrix, then use the |
| // ordinary (coeff-based for a vector rhs) product. This avoids the runtime-sized, |
| // EIGEN_DONT_INLINE selfadjoint_matrix_vector_product kernel, which is tuned for large |
| // matrices and is pure overhead for a 2-4 row block. |
| BlockType diag = m_matrix.blockRef(id).template selfadjointView<DiagUpLo>(); |
| result.template middleRows<BlockRows>(bi * BlockRows).noalias() += |
| diag * rhs.template middleRows<BlockCols>(bj * BlockCols); |
| } |
| } else { |
| result.template middleRows<BlockRows>(bi * BlockRows).noalias() += |
| m_matrix.blockRef(id) * rhs.template middleRows<BlockCols>(bj * BlockCols); |
| result.template middleRows<BlockRows>(bj * BlockRows).noalias() += |
| m_matrix.blockRef(id).adjoint() * rhs.template middleRows<BlockRows>(bi * BlockRows); |
| } |
| } |
| return result; |
| } |
| |
| /** Dense × SelfAdj: lhs * A == (A^H * lhs^H)^H == (A * lhs^H)^H for Hermitian A. */ |
| template <typename OtherDerived> |
| friend Matrix<Scalar, OtherDerived::RowsAtCompileTime, Dynamic> operator*(const MatrixBase<OtherDerived>& lhs, |
| const BlockSparseSelfAdjointView& view) { |
| EIGEN_STATIC_ASSERT( |
| (std::is_same<Scalar, typename OtherDerived::Scalar>::value), |
| YOU_MIXED_DIFFERENT_NUMERIC_TYPES__YOU_NEED_TO_USE_THE_CAST_METHOD_OF_MATRIXBASE_TO_CAST_NUMERIC_TYPES_EXPLICITLY) |
| return (view * lhs.adjoint()).adjoint(); |
| } |
| |
| private: |
| const BSM& m_matrix; |
| }; |
| |
| // ============================================================================= |
| // BlockSparseMatrix::transposeImpl / transpose / adjoint (out-of-line) |
| // ============================================================================= |
| |
| template <typename Scalar_, int Options_, int BlockRows_, int BlockCols_, typename StorageIndex_> |
| template <bool Conjugate> |
| BlockSparseMatrix<Scalar_, Options_, BlockCols_, BlockRows_, StorageIndex_> |
| BlockSparseMatrix<Scalar_, Options_, BlockRows_, BlockCols_, StorageIndex_>::transposeImpl() const { |
| using ResultType = BlockSparseMatrix<Scalar_, Options_, BlockCols_, BlockRows_, StorageIndex_>; |
| ResultType result(blockCols(), blockRows()); |
| |
| // Count entries per new outer (= old inner). |
| for (Index id = 0; id < nonZeroBlocks(); ++id) result.m_outerIndex(m_innerIndex(id) + 1)++; |
| |
| // Prefix sum. |
| for (Index j = 0; j < result.m_blockOuterSize; ++j) result.m_outerIndex(j + 1) += result.m_outerIndex(j); |
| |
| Index nnz = nonZeroBlocks(); |
| result.resizeBlockStorage_(nnz); |
| |
| // One insertion cursor per new outer; start at the prefix-sum boundary. |
| // Because we iterate oldOuter in increasing order, for each newOuter = oldInner |
| // the emitted newInner = oldOuter values are automatically sorted. |
| Array<StorageIndex_, Dynamic, 1> pos = result.m_outerIndex.head(result.m_blockOuterSize); |
| |
| for (Index oldOuter = 0; oldOuter < m_blockOuterSize; ++oldOuter) { |
| for (Index id = m_outerIndex(oldOuter); id < m_outerIndex(oldOuter + 1); ++id) { |
| Index newOuter = m_innerIndex(id); |
| Index insertAt = pos(newOuter)++; |
| result.m_innerIndex(insertAt) = StorageIndex_(oldOuter); |
| result.blockRef(insertAt) = internal::adjoint_if<Conjugate>(blockRef(id)); |
| } |
| } |
| return result; |
| } |
| |
| template <typename Scalar_, int Options_, int BlockRows_, int BlockCols_, typename StorageIndex_> |
| BlockSparseMatrix<Scalar_, Options_, BlockCols_, BlockRows_, StorageIndex_> |
| BlockSparseMatrix<Scalar_, Options_, BlockRows_, BlockCols_, StorageIndex_>::transpose() const { |
| return transposeImpl<false>(); |
| } |
| |
| template <typename Scalar_, int Options_, int BlockRows_, int BlockCols_, typename StorageIndex_> |
| BlockSparseMatrix<Scalar_, Options_, BlockCols_, BlockRows_, StorageIndex_> |
| BlockSparseMatrix<Scalar_, Options_, BlockRows_, BlockCols_, StorageIndex_>::adjoint() const { |
| return transposeImpl<true>(); |
| } |
| |
| namespace internal { |
| |
| // --------------------------------------------------------------------------- |
| // generic_product_impl: BlockSparse × Dense → Dense |
| // Provides evalTo / addTo / subTo / scaleAndAddTo via generic_product_impl_base. |
| // --------------------------------------------------------------------------- |
| template <typename Lhs, typename Rhs, int ProductType> |
| struct generic_product_impl<Lhs, Rhs, BlockSparseShape, DenseShape, ProductType> |
| : generic_product_impl_base<Lhs, Rhs, generic_product_impl<Lhs, Rhs, BlockSparseShape, DenseShape, ProductType>> { |
| using Scalar = typename Product<Lhs, Rhs>::Scalar; |
| |
| template <typename Dst> |
| static void scaleAndAddTo(Dst& dst, const Lhs& lhs, const Rhs& rhs, const Scalar& alpha) { |
| constexpr bool IsRM = (Lhs::Options & RowMajorBit) != 0; |
| constexpr int BR = Lhs::BlockRows; |
| constexpr int BC = Lhs::BlockCols; |
| const typename Lhs::StorageIndex* outerPtr = lhs.outerIndexPtr(); |
| const typename Lhs::StorageIndex* innerPtr = lhs.innerIndexPtr(); |
| // Branch on alpha before the loop: alpha==1 and alpha==-1 avoid creating a |
| // CwiseUnaryOp<scalar_multiple, B×B_block>, which defeats SIMD for complex scalars. |
| bool a1 = (alpha == Scalar(1)); |
| bool am1 = (alpha == Scalar(-1)); |
| for (Eigen::Index out = 0; out < lhs.blockOuterSize(); ++out) { |
| for (Eigen::Index id = outerPtr[out]; id < outerPtr[out + 1]; ++id) { |
| Eigen::Index inner = innerPtr[id]; |
| Eigen::Index bi = IsRM ? out : inner; |
| Eigen::Index bj = IsRM ? inner : out; |
| auto dst_seg = dst.template middleRows<BR>(bi * BR); |
| auto rhs_seg = rhs.template middleRows<BC>(bj * BC); |
| if (EIGEN_PREDICT_TRUE(a1)) |
| dst_seg.noalias() += lhs.blockRef(id) * rhs_seg; |
| else if (am1) |
| dst_seg.noalias() -= lhs.blockRef(id) * rhs_seg; |
| else { |
| // Materialize block×rhs_seg into a small fixed-size stack buffer, then |
| // scale by alpha. Keeps the B×B block as a plain Map for vectorization. |
| using TmpType = Matrix<Scalar, BR, Rhs::ColsAtCompileTime>; |
| TmpType tmp(BR, rhs.cols()); |
| tmp.noalias() = lhs.blockRef(id) * rhs_seg; |
| dst_seg.noalias() += alpha * tmp; |
| } |
| } |
| } |
| } |
| }; |
| |
| // --------------------------------------------------------------------------- |
| // generic_product_impl: Dense × BlockSparse → Dense |
| // --------------------------------------------------------------------------- |
| template <typename Lhs, typename Rhs, int ProductType> |
| struct generic_product_impl<Lhs, Rhs, DenseShape, BlockSparseShape, ProductType> |
| : generic_product_impl_base<Lhs, Rhs, generic_product_impl<Lhs, Rhs, DenseShape, BlockSparseShape, ProductType>> { |
| using Scalar = typename Product<Lhs, Rhs>::Scalar; |
| |
| template <typename Dst> |
| static void scaleAndAddTo(Dst& dst, const Lhs& lhs, const Rhs& rhs, const Scalar& alpha) { |
| constexpr bool IsRM = (Rhs::Options & RowMajorBit) != 0; |
| constexpr int BR = Rhs::BlockRows; |
| constexpr int BC = Rhs::BlockCols; |
| const typename Rhs::StorageIndex* outerPtr = rhs.outerIndexPtr(); |
| const typename Rhs::StorageIndex* innerPtr = rhs.innerIndexPtr(); |
| bool a1 = (alpha == Scalar(1)); |
| bool am1 = (alpha == Scalar(-1)); |
| for (Eigen::Index out = 0; out < rhs.blockOuterSize(); ++out) { |
| for (Eigen::Index id = outerPtr[out]; id < outerPtr[out + 1]; ++id) { |
| Eigen::Index inner = innerPtr[id]; |
| Eigen::Index bi = IsRM ? out : inner; |
| Eigen::Index bj = IsRM ? inner : out; |
| auto dst_seg = dst.template middleCols<BC>(bj * BC); |
| auto lhs_seg = lhs.template middleCols<BR>(bi * BR); |
| if (EIGEN_PREDICT_TRUE(a1)) |
| dst_seg.noalias() += lhs_seg * rhs.blockRef(id); |
| else if (am1) |
| dst_seg.noalias() -= lhs_seg * rhs.blockRef(id); |
| else { |
| using TmpType = Matrix<Scalar, Lhs::RowsAtCompileTime, BC>; |
| TmpType tmp(lhs.rows(), BC); |
| tmp.noalias() = lhs_seg * rhs.blockRef(id); |
| dst_seg.noalias() += alpha * tmp; |
| } |
| } |
| } |
| } |
| }; |
| |
| } // namespace internal |
| |
| } // end namespace Eigen |
| |
| #endif // EIGEN_BLOCKSPARSEMATRIX_H |