blob: 31ba2b3fc48b50126a10f4ab2b02daa130b60f93 [file]
// 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