| // 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 |
| |
| #include "sparse.h" |
| |
| using namespace Eigen; |
| |
| // --------------------------------------------------------------------------- |
| // Helper: build a BlockSparseMatrix from a dense matrix |
| // --------------------------------------------------------------------------- |
| |
| // Treats each BlockRows x BlockCols tile of `dense` as one block whenever the |
| // tile is non-zero. |
| template <int BlockRows, int BlockCols, typename Scalar, int Options, typename StorageIndex> |
| BlockSparseMatrix<Scalar, Options, BlockRows, BlockCols, StorageIndex> denseToBlock( |
| const Matrix<Scalar, Dynamic, Dynamic>& dense) { |
| using BSM = BlockSparseMatrix<Scalar, Options, BlockRows, BlockCols, StorageIndex>; |
| using Triplet = typename BSM::TripletType; |
| |
| Index bRows = dense.rows() / BlockRows; |
| Index bCols = dense.cols() / BlockCols; |
| |
| std::vector<Triplet> triplets; |
| for (Index bi = 0; bi < bRows; ++bi) { |
| for (Index bj = 0; bj < bCols; ++bj) { |
| Matrix<Scalar, BlockRows, BlockCols> tile = dense.block(bi * BlockRows, bj * BlockCols, BlockRows, BlockCols); |
| if (tile.squaredNorm() > 0) triplets.emplace_back(StorageIndex(bi), StorageIndex(bj), tile); |
| } |
| } |
| |
| BSM bsm(bRows, bCols); |
| bsm.setFromTriplets(triplets.begin(), triplets.end()); |
| return bsm; |
| } |
| |
| template <bool HasSquareBlocks> |
| struct BlockSparseIdentityTester { |
| template <int BlockRows, int BlockCols, int Options, typename Scalar, typename StorageIndex> |
| static void run(int, int, const BlockSparseMatrix<Scalar, Options, BlockRows, BlockCols, StorageIndex>&, |
| const Matrix<Scalar, Dynamic, Dynamic>&) {} |
| }; |
| |
| template <> |
| struct BlockSparseIdentityTester<true> { |
| template <int BlockRows, int BlockCols, int Options, typename Scalar, typename StorageIndex> |
| static void run(int bRows, int bCols, const BlockSparseMatrix<Scalar, Options, BlockRows, BlockCols, StorageIndex>& A, |
| const Matrix<Scalar, Dynamic, Dynamic>& dA) { |
| using BSM = BlockSparseMatrix<Scalar, Options, BlockRows, BlockCols, StorageIndex>; |
| using DenseMat = Matrix<Scalar, Dynamic, Dynamic>; |
| |
| const int rows = bRows * BlockRows; |
| const int cols = bCols * BlockCols; |
| BSM Id(bRows, bCols); |
| Id.setIdentity(); |
| VERIFY_IS_APPROX(DenseMat(Id.toSparse()), DenseMat::Identity(rows, cols)); |
| if (bRows == bCols) { |
| VERIFY_IS_APPROX(DenseMat((Id * A).toSparse()), dA); |
| VERIFY_IS_APPROX(DenseMat((A * Id).toSparse()), dA); |
| } |
| } |
| }; |
| |
| // --------------------------------------------------------------------------- |
| // Core test driver templated on block size, storage order, and scalar type |
| // --------------------------------------------------------------------------- |
| |
| template <int BlockRows, int BlockCols, int Options, typename Scalar = double> |
| void test_block_sparse(int bRows, int bCols) { |
| using StorageIndex = int; |
| using BSM = BlockSparseMatrix<Scalar, Options, BlockRows, BlockCols, StorageIndex>; |
| using SpMat = SparseMatrix<Scalar, Options, StorageIndex>; |
| using DenseMat = Matrix<Scalar, Dynamic, Dynamic>; |
| |
| int rows = bRows * BlockRows; |
| int cols = bCols * BlockCols; |
| |
| // Build two random dense matrices with a coarse block structure. |
| DenseMat dA = DenseMat::Zero(rows, cols); |
| DenseMat dB = DenseMat::Zero(rows, cols); |
| for (int bi = 0; bi < bRows; ++bi) { |
| for (int bj = 0; bj < bCols; ++bj) { |
| if (internal::random<double>(0.0, 1.0) < 0.4) { |
| dA.block(bi * BlockRows, bj * BlockCols, BlockRows, BlockCols) = DenseMat::Random(BlockRows, BlockCols); |
| } |
| if (internal::random<double>(0.0, 1.0) < 0.4) { |
| dB.block(bi * BlockRows, bj * BlockCols, BlockRows, BlockCols) = DenseMat::Random(BlockRows, BlockCols); |
| } |
| } |
| } |
| |
| BSM A = denseToBlock<BlockRows, BlockCols, Scalar, Options, StorageIndex>(dA); |
| BSM B = denseToBlock<BlockRows, BlockCols, Scalar, Options, StorageIndex>(dB); |
| |
| // ---- toSparse / fromSparse round-trip ----------------------------------- |
| { |
| SpMat spA = A.toSparse(); |
| DenseMat dense_spA(spA); |
| VERIFY_IS_APPROX(dense_spA, dA); |
| |
| BSM A2 = BSM::fromSparse(spA); |
| VERIFY_IS_APPROX(A2.toSparse(), spA); |
| |
| // Implicit conversion operator |
| SpMat spA3 = A; |
| VERIFY_IS_APPROX(DenseMat(spA3), dA); |
| } |
| |
| // ---- Addition ----------------------------------------------------------- |
| { |
| BSM C = A + B; |
| DenseMat dC(C.toSparse()); |
| VERIFY_IS_APPROX(dC, dA + dB); |
| |
| BSM D = A; |
| D += B; |
| VERIFY_IS_APPROX(DenseMat(D.toSparse()), dA + dB); |
| } |
| |
| // ---- Subtraction -------------------------------------------------------- |
| { |
| BSM C = A - B; |
| VERIFY_IS_APPROX(DenseMat(C.toSparse()), dA - dB); |
| |
| BSM D = A; |
| D -= B; |
| VERIFY_IS_APPROX(DenseMat(D.toSparse()), dA - dB); |
| } |
| |
| // ---- Unary minus -------------------------------------------------------- |
| { |
| BSM C = -A; |
| VERIFY_IS_APPROX(DenseMat(C.toSparse()), -dA); |
| } |
| |
| // ---- cwiseProduct (conjunction / intersection) -------------------------- |
| { |
| BSM C = A.cwiseProduct(B); |
| // Result must only have blocks where both A and B had blocks. |
| DenseMat dC = dA.cwiseProduct(dB); |
| VERIFY_IS_APPROX(DenseMat(C.toSparse()), dC); |
| } |
| |
| // ---- unaryExpr ---------------------------------------------------------- |
| { |
| BSM C = A.unaryExpr([](const Scalar& x) { return x * x; }); |
| VERIFY_IS_APPROX(DenseMat(C.toSparse()), dA.array().square().matrix()); |
| } |
| |
| // ---- disjunctionExpr (union sparsity) ----------------------------------- |
| { |
| // sum via disjunctionExpr should match operator+ |
| struct AddExpr { |
| Scalar operator()(const Scalar& a, const Scalar& b) const { return a + b; } |
| Scalar lhs(const Scalar& a) const { return a; } |
| Scalar rhs(const Scalar& b) const { return b; } |
| }; |
| BSM C = A.disjunctionExpr(B, AddExpr{}); |
| VERIFY_IS_APPROX(DenseMat(C.toSparse()), dA + dB); |
| } |
| |
| // ---- conjunctionExpr (intersection sparsity) ---------------------------- |
| { |
| BSM C = A.conjunctionExpr(B, [](const Scalar& a, const Scalar& b) { return a * b; }); |
| VERIFY_IS_APPROX(DenseMat(C.toSparse()), dA.cwiseProduct(dB)); |
| } |
| |
| // ---- Scalar multiplication ---------------------------------------------- |
| { |
| Scalar s = Scalar(3.14); |
| BSM C = A * s; |
| VERIFY_IS_APPROX(DenseMat(C.toSparse()), dA * s); |
| |
| BSM D = s * A; |
| VERIFY_IS_APPROX(DenseMat(D.toSparse()), s * dA); |
| |
| BSM E = A; |
| E *= s; |
| VERIFY_IS_APPROX(DenseMat(E.toSparse()), dA * s); |
| } |
| |
| // ---- Element access (coeff) --------------------------------------------- |
| { |
| for (int i = 0; i < rows; ++i) { |
| for (int j = 0; j < cols; ++j) { |
| VERIFY_IS_APPROX(A.coeff(i, j), dA(i, j)); |
| } |
| } |
| } |
| |
| // ---- setIdentity ---------------------------------------------------------- |
| BlockSparseIdentityTester<BlockRows == BlockCols>::template run<BlockRows, BlockCols, Options, Scalar, StorageIndex>( |
| bRows, bCols, A, dA); |
| |
| // ---- setFromTriplets with duplicate blocks (accumulation) --------------- |
| { |
| using Trip = typename BSM::TripletType; |
| using BlockMat = Matrix<Scalar, BlockRows, BlockCols>; |
| BlockMat half = BlockMat::Ones() * Scalar(0.5); |
| |
| std::vector<Trip> trips; |
| trips.emplace_back(StorageIndex(0), StorageIndex(0), half); |
| trips.emplace_back(StorageIndex(0), StorageIndex(0), half); // duplicate -> sum |
| |
| BSM M(bRows, bCols); |
| M.setFromTriplets(trips.begin(), trips.end()); |
| |
| VERIFY(M.nonZeroBlocks() == 1); |
| VERIFY_IS_APPROX(DenseMat(M.blockRef(0)), DenseMat(BlockMat::Ones())); |
| } |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Square-block product test |
| // --------------------------------------------------------------------------- |
| |
| template <int B, int Options, typename Scalar = double> |
| void test_block_sparse_product(int bM, int bK, int bN) { |
| using StorageIndex = int; |
| using BSMA = BlockSparseMatrix<Scalar, Options, B, B, StorageIndex>; |
| using DenseMat = Matrix<Scalar, Dynamic, Dynamic>; |
| |
| int rowsA = bM * B, colsA = bK * B; |
| int colsB = bN * B; |
| |
| DenseMat dA = DenseMat::Zero(rowsA, colsA); |
| DenseMat dB = DenseMat::Zero(colsA, colsB); |
| |
| for (int bi = 0; bi < bM; ++bi) |
| for (int bk = 0; bk < bK; ++bk) |
| if (internal::random<double>(0.0, 1.0) < 0.4) dA.block(bi * B, bk * B, B, B) = DenseMat::Random(B, B); |
| |
| for (int bk = 0; bk < bK; ++bk) |
| for (int bj = 0; bj < bN; ++bj) |
| if (internal::random<double>(0.0, 1.0) < 0.4) dB.block(bk * B, bj * B, B, B) = DenseMat::Random(B, B); |
| |
| BSMA A = denseToBlock<B, B, Scalar, Options, StorageIndex>(dA); |
| BSMA Bmat = denseToBlock<B, B, Scalar, Options, StorageIndex>(dB); |
| |
| BSMA C = A * Bmat; |
| DenseMat dC(C.toSparse()); |
| VERIFY_IS_APPROX(dC, dA * dB); |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Block-sparse * dense and dense * block-sparse products |
| // --------------------------------------------------------------------------- |
| |
| template <int BlockRows, int BlockCols, int Options, typename Scalar = double> |
| void test_block_sparse_dense_product(int bRows, int bCols) { |
| using StorageIndex = int; |
| using BSM = BlockSparseMatrix<Scalar, Options, BlockRows, BlockCols, StorageIndex>; |
| using DenseMat = Matrix<Scalar, Dynamic, Dynamic>; |
| using DenseVec = Matrix<Scalar, Dynamic, 1>; |
| using RowVec = Matrix<Scalar, 1, Dynamic>; |
| |
| int rows = bRows * BlockRows; |
| int cols = bCols * BlockCols; |
| |
| DenseMat dA = DenseMat::Zero(rows, cols); |
| for (int bi = 0; bi < bRows; ++bi) |
| for (int bj = 0; bj < bCols; ++bj) |
| if (internal::random<double>(0.0, 1.0) < 0.5) |
| dA.block(bi * BlockRows, bj * BlockCols, BlockRows, BlockCols) = DenseMat::Random(BlockRows, BlockCols); |
| |
| BSM A = denseToBlock<BlockRows, BlockCols, Scalar, Options, StorageIndex>(dA); |
| |
| // BSM * dense matrix |
| { |
| DenseMat rhs = DenseMat::Random(cols, 5); |
| VERIFY_IS_APPROX(A * rhs, dA * rhs); |
| } |
| |
| // BSM * column vector |
| { |
| DenseVec v = DenseVec::Random(cols); |
| VERIFY_IS_APPROX(A * v, dA * v); |
| } |
| |
| // dense matrix * BSM |
| { |
| DenseMat lhs = DenseMat::Random(5, rows); |
| VERIFY_IS_APPROX(lhs * A, lhs * dA); |
| } |
| |
| // row vector * BSM |
| { |
| RowVec v = RowVec::Random(rows); |
| VERIFY_IS_APPROX(v * A, v * dA); |
| } |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Non-square block product: A(BR x BC) * B(BC x BC2) -> C(BR x BC2) |
| // --------------------------------------------------------------------------- |
| |
| void test_nonsquare_block_product() { |
| using Scalar = double; |
| using StorageIndex = int; |
| constexpr int BR = 2, BC = 3, BC2 = 4; |
| using BSMA = BlockSparseMatrix<Scalar, ColMajor, BR, BC, StorageIndex>; |
| using BSMB = BlockSparseMatrix<Scalar, ColMajor, BC, BC2, StorageIndex>; |
| using DenseMat = Matrix<Scalar, Dynamic, Dynamic>; |
| |
| int bM = 4, bK = 3, bN = 5; |
| DenseMat dA = DenseMat::Zero(bM * BR, bK * BC); |
| DenseMat dB = DenseMat::Zero(bK * BC, bN * BC2); |
| |
| for (int bi = 0; bi < bM; ++bi) |
| for (int bk = 0; bk < bK; ++bk) |
| if (internal::random<double>() > 0.0) dA.block(bi * BR, bk * BC, BR, BC) = DenseMat::Random(BR, BC); |
| |
| for (int bk = 0; bk < bK; ++bk) |
| for (int bj = 0; bj < bN; ++bj) |
| if (internal::random<double>() > 0.0) dB.block(bk * BC, bj * BC2, BC, BC2) = DenseMat::Random(BC, BC2); |
| |
| BSMA A = denseToBlock<BR, BC, Scalar, ColMajor, StorageIndex>(dA); |
| BSMB Bmat = denseToBlock<BC, BC2, Scalar, ColMajor, StorageIndex>(dB); |
| |
| BlockSparseMatrix<Scalar, ColMajor, BR, BC2, StorageIndex> C = A * Bmat; |
| DenseMat dC(C.toSparse()); |
| VERIFY_IS_APPROX(dC, dA * dB); |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Transpose and adjoint |
| // --------------------------------------------------------------------------- |
| |
| template <int BlockRows, int BlockCols, int Options, typename Scalar = double> |
| void test_block_sparse_transpose(int bRows, int bCols) { |
| using StorageIndex = int; |
| using BSM = BlockSparseMatrix<Scalar, Options, BlockRows, BlockCols, StorageIndex>; |
| using BSMT = BlockSparseMatrix<Scalar, Options, BlockCols, BlockRows, StorageIndex>; |
| using DenseMat = Matrix<Scalar, Dynamic, Dynamic>; |
| |
| int rows = bRows * BlockRows, cols = bCols * BlockCols; |
| DenseMat dA = DenseMat::Zero(rows, cols); |
| for (int bi = 0; bi < bRows; ++bi) |
| for (int bj = 0; bj < bCols; ++bj) |
| if (internal::random<double>(0.0, 1.0) < 0.5) |
| dA.block(bi * BlockRows, bj * BlockCols, BlockRows, BlockCols) = DenseMat::Random(BlockRows, BlockCols); |
| |
| BSM A = denseToBlock<BlockRows, BlockCols, Scalar, Options, StorageIndex>(dA); |
| |
| // transpose |
| BSMT At = A.transpose(); |
| VERIFY_IS_APPROX(DenseMat(At.toSparse()), dA.transpose()); |
| |
| // adjoint (conjugate transpose for complex, same as transpose for real) |
| BSMT Ah = A.adjoint(); |
| VERIFY_IS_APPROX(DenseMat(Ah.toSparse()), dA.adjoint()); |
| |
| // (A^T)^T == A |
| BSM AtT = At.transpose(); |
| VERIFY_IS_APPROX(DenseMat(AtT.toSparse()), dA); |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Triangular view: eval, +/-, dense products, DiagIsTriangular path |
| // --------------------------------------------------------------------------- |
| |
| template <int B, int Options, typename Scalar = double> |
| void test_block_sparse_triangular(int bN) { |
| using StorageIndex = int; |
| using BSM = BlockSparseMatrix<Scalar, Options, B, B, StorageIndex>; |
| using DenseMat = Matrix<Scalar, Dynamic, Dynamic>; |
| |
| int N = bN * B; |
| DenseMat dA = DenseMat::Zero(N, N); |
| for (int bi = 0; bi < bN; ++bi) |
| for (int bj = 0; bj < bN; ++bj) |
| if (internal::random<double>(0.0, 1.0) < 0.5) dA.block(bi * B, bj * B, B, B) = DenseMat::Random(B, B); |
| |
| BSM A = denseToBlock<B, B, Scalar, Options, StorageIndex>(dA); |
| |
| // Upper eval |
| { |
| BSM Au = A.template triangularView<Upper>().eval(); |
| DenseMat dAu = dA; |
| for (int bi = 0; bi < bN; ++bi) { |
| for (int bj = 0; bj < bi; ++bj) dAu.block(bi * B, bj * B, B, B).setZero(); |
| dAu.block(bi * B, bi * B, B, B).template triangularView<StrictlyLower>().setZero(); |
| } |
| VERIFY_IS_APPROX(DenseMat(Au.toSparse()), dAu); |
| } |
| |
| // Lower eval |
| { |
| BSM Al = A.template triangularView<Lower>().eval(); |
| DenseMat dAl = dA; |
| for (int bi = 0; bi < bN; ++bi) { |
| for (int bj = bi + 1; bj < bN; ++bj) dAl.block(bi * B, bj * B, B, B).setZero(); |
| dAl.block(bi * B, bi * B, B, B).template triangularView<StrictlyUpper>().setZero(); |
| } |
| VERIFY_IS_APPROX(DenseMat(Al.toSparse()), dAl); |
| } |
| |
| // Tri * dense and dense * Tri |
| { |
| DenseMat dAu = dA; |
| for (int bi = 0; bi < bN; ++bi) { |
| for (int bj = 0; bj < bi; ++bj) dAu.block(bi * B, bj * B, B, B).setZero(); |
| dAu.block(bi * B, bi * B, B, B).template triangularView<StrictlyLower>().setZero(); |
| } |
| |
| DenseMat rhs = DenseMat::Random(N, 4); |
| DenseMat lhs = DenseMat::Random(3, N); |
| |
| VERIFY_IS_APPROX(A.template triangularView<Upper>() * rhs, dAu * rhs); |
| VERIFY_IS_APPROX(lhs * A.template triangularView<Upper>(), lhs * dAu); |
| } |
| |
| // Tri + Tri |
| { |
| DenseMat dB = DenseMat::Zero(N, N); |
| for (int bi = 0; bi < bN; ++bi) |
| for (int bj = 0; bj < bN; ++bj) |
| if (internal::random<double>(0.0, 1.0) < 0.5) dB.block(bi * B, bj * B, B, B) = DenseMat::Random(B, B); |
| BSM Bmat = denseToBlock<B, B, Scalar, Options, StorageIndex>(dB); |
| |
| DenseMat dAu = dA, dBu = dB; |
| for (int bi = 0; bi < bN; ++bi) { |
| for (int bj = 0; bj < bi; ++bj) { |
| dAu.block(bi * B, bj * B, B, B).setZero(); |
| dBu.block(bi * B, bj * B, B, B).setZero(); |
| } |
| dAu.block(bi * B, bi * B, B, B).template triangularView<StrictlyLower>().setZero(); |
| dBu.block(bi * B, bi * B, B, B).template triangularView<StrictlyLower>().setZero(); |
| } |
| BSM C = A.template triangularView<Upper>() + Bmat.template triangularView<Upper>(); |
| VERIFY_IS_APPROX(DenseMat(C.toSparse()), dAu + dBu); |
| } |
| |
| // DiagIsTriangular=true product: build a BSM whose diagonal blocks are already |
| // upper-triangular in storage, and verify the product matches DiagIsTriangular=false. |
| { |
| DenseMat dAu = DenseMat::Zero(N, N); |
| for (int bi = 0; bi < bN; ++bi) { |
| DenseMat blk = DenseMat::Random(B, B); |
| blk.template triangularView<StrictlyLower>().setZero(); |
| dAu.block(bi * B, bi * B, B, B) = blk; |
| for (int bj = bi + 1; bj < bN; ++bj) |
| if (internal::random<double>(0.0, 1.0) < 0.5) dAu.block(bi * B, bj * B, B, B) = DenseMat::Random(B, B); |
| } |
| BSM Au = denseToBlock<B, B, Scalar, Options, StorageIndex>(dAu); |
| DenseMat rhs = DenseMat::Random(N, 3); |
| DenseMat r1 = Au.template triangularView<Upper, false>() * rhs; |
| DenseMat r2 = Au.template triangularView<Upper, true>() * rhs; |
| VERIFY_IS_APPROX(r1, r2); |
| } |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Triangular solve: forward/backward x direct/transposed/adjoint x both layouts |
| // --------------------------------------------------------------------------- |
| |
| template <int B, int Options, typename Scalar> |
| void test_block_sparse_triangular_solve(int bN) { |
| using StorageIndex = int; |
| using BSM = BlockSparseMatrix<Scalar, Options, B, B, StorageIndex>; |
| using DenseMat = Matrix<Scalar, Dynamic, Dynamic>; |
| using RealScalar = typename NumTraits<Scalar>::Real; |
| |
| int N = bN * B; |
| |
| // Build a dense lower-block-triangular matrix. |
| // Diagonal blocks are lower triangular with non-zero diagonal. |
| // Off-diagonal (lower) blocks are random dense. |
| auto makeDenseLower = [&]() { |
| DenseMat dL = DenseMat::Zero(N, N); |
| for (int bi = 0; bi < bN; ++bi) { |
| DenseMat blk = DenseMat::Random(B, B); |
| blk.template triangularView<StrictlyUpper>().setZero(); |
| for (int k = 0; k < B; ++k) blk(k, k) = Scalar(RealScalar(B + k + 1)); |
| dL.block(bi * B, bi * B, B, B) = blk; |
| for (int bj = 0; bj < bi; ++bj) |
| if (internal::random<double>(0.0, 1.0) < 0.6) dL.block(bi * B, bj * B, B, B) = DenseMat::Random(B, B); |
| } |
| return dL; |
| }; |
| |
| auto makeDenseUpper = [&]() { |
| DenseMat dU = DenseMat::Zero(N, N); |
| for (int bi = 0; bi < bN; ++bi) { |
| DenseMat blk = DenseMat::Random(B, B); |
| blk.template triangularView<StrictlyLower>().setZero(); |
| for (int k = 0; k < B; ++k) blk(k, k) = Scalar(RealScalar(B + k + 1)); |
| dU.block(bi * B, bi * B, B, B) = blk; |
| for (int bj = bi + 1; bj < bN; ++bj) |
| if (internal::random<double>(0.0, 1.0) < 0.6) dU.block(bi * B, bj * B, B, B) = DenseMat::Random(B, B); |
| } |
| return dU; |
| }; |
| |
| // Lower triangular: direct, transposed, adjoint |
| { |
| DenseMat dL = makeDenseLower(); |
| BSM L = denseToBlock<B, B, Scalar, Options, StorageIndex>(dL); |
| |
| // L x = b |
| { |
| DenseMat b = DenseMat::Random(N, 3); |
| DenseMat x = b; |
| L.template triangularView<Lower>().solveInPlace(x); |
| VERIFY_IS_APPROX(dL * x, b); |
| } |
| // L^T x = b |
| { |
| DenseMat b = DenseMat::Random(N, 3); |
| DenseMat x = b; |
| L.template triangularView<Lower>().transpose().solveInPlace(x); |
| VERIFY_IS_APPROX(dL.transpose() * x, b); |
| } |
| // L^H x = b |
| { |
| DenseMat b = DenseMat::Random(N, 3); |
| DenseMat x = b; |
| L.template triangularView<Lower>().adjoint().solveInPlace(x); |
| VERIFY_IS_APPROX(dL.adjoint() * x, b); |
| } |
| } |
| |
| // Upper triangular: direct, transposed, adjoint |
| { |
| DenseMat dU = makeDenseUpper(); |
| BSM U = denseToBlock<B, B, Scalar, Options, StorageIndex>(dU); |
| |
| // U x = b |
| { |
| DenseMat b = DenseMat::Random(N, 3); |
| DenseMat x = b; |
| U.template triangularView<Upper>().solveInPlace(x); |
| VERIFY_IS_APPROX(dU * x, b); |
| } |
| // U^T x = b |
| { |
| DenseMat b = DenseMat::Random(N, 3); |
| DenseMat x = b; |
| U.template triangularView<Upper>().transpose().solveInPlace(x); |
| VERIFY_IS_APPROX(dU.transpose() * x, b); |
| } |
| // U^H x = b |
| { |
| DenseMat b = DenseMat::Random(N, 3); |
| DenseMat x = b; |
| U.template triangularView<Upper>().adjoint().solveInPlace(x); |
| VERIFY_IS_APPROX(dU.adjoint() * x, b); |
| } |
| } |
| |
| // DiagIsTriangular=true: diagonal blocks are already properly triangular in storage; |
| // result must match DiagIsTriangular=false (which zeroes the unused triangle first). |
| { |
| DenseMat dL = makeDenseLower(); |
| BSM L = denseToBlock<B, B, Scalar, Options, StorageIndex>(dL); |
| DenseMat b = DenseMat::Random(N, 2); |
| DenseMat x1 = b, x2 = b; |
| { |
| auto tri_false = L.template triangularView<Lower, false>(); |
| tri_false.solveInPlace(x1); |
| } |
| { |
| auto tri_true = L.template triangularView<Lower, true>(); |
| tri_true.solveInPlace(x2); |
| } |
| VERIFY_IS_APPROX(x1, x2); |
| } |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Self-adjoint view: eval, +/-, dense products |
| // --------------------------------------------------------------------------- |
| |
| template <int B, int Options, typename Scalar = double> |
| void test_block_sparse_selfadjoint(int bN) { |
| using StorageIndex = int; |
| using BSM = BlockSparseMatrix<Scalar, Options, B, B, StorageIndex>; |
| using DenseMat = Matrix<Scalar, Dynamic, Dynamic>; |
| |
| int N = bN * B; |
| |
| // Build a Hermitian dense matrix (stored upper triangle only). |
| DenseMat dFull = DenseMat::Zero(N, N); |
| for (int bi = 0; bi < bN; ++bi) { |
| // Diagonal block: Hermitian (symmetric for real, conjugate-symmetric for complex). |
| DenseMat blk = DenseMat::Random(B, B); |
| blk = (blk + blk.adjoint()).eval(); |
| dFull.block(bi * B, bi * B, B, B) = blk; |
| for (int bj = bi + 1; bj < bN; ++bj) |
| if (internal::random<double>(0.0, 1.0) < 0.5) { |
| DenseMat offblk = DenseMat::Random(B, B); |
| dFull.block(bi * B, bj * B, B, B) = offblk; |
| dFull.block(bj * B, bi * B, B, B) = offblk.adjoint(); // conjugate transpose mirror |
| } |
| } |
| |
| // Build BSM from the upper triangle only. |
| DenseMat dUpper = DenseMat::Zero(N, N); |
| for (int bi = 0; bi < bN; ++bi) |
| for (int bj = bi; bj < bN; ++bj) dUpper.block(bi * B, bj * B, B, B) = dFull.block(bi * B, bj * B, B, B); |
| |
| BSM A = denseToBlock<B, B, Scalar, Options, StorageIndex>(dUpper); |
| |
| // eval() must reproduce the full Hermitian matrix |
| { |
| BSM Asym = A.template selfadjointView<Upper>().eval(); |
| VERIFY_IS_APPROX(DenseMat(Asym.toSparse()), dFull); |
| } |
| |
| // selfadjointView * dense |
| { |
| DenseMat rhs = DenseMat::Random(N, 5); |
| VERIFY_IS_APPROX(A.template selfadjointView<Upper>() * rhs, dFull * rhs); |
| } |
| |
| // dense * selfadjointView |
| { |
| DenseMat lhs = DenseMat::Random(4, N); |
| VERIFY_IS_APPROX(lhs * A.template selfadjointView<Upper>(), lhs * dFull); |
| } |
| |
| // selfadjointView + selfadjointView |
| { |
| BSM Bmat = denseToBlock<B, B, Scalar, Options, StorageIndex>(dUpper * Scalar(2)); |
| BSM C = A.template selfadjointView<Upper>() + Bmat.template selfadjointView<Upper>(); |
| VERIFY_IS_APPROX(DenseMat(C.toSparse()), dFull * Scalar(3)); |
| } |
| |
| // DiagIsSelfAdjoint path: diagonal blocks ARE Hermitian, product should match |
| { |
| DenseMat rhs = DenseMat::Random(N, 3); |
| DenseMat result = A.template selfadjointView<Upper, true>() * rhs; |
| VERIFY_IS_APPROX(result, dFull * rhs); |
| } |
| } |
| |
| // --------------------------------------------------------------------------- |
| // BlockTriplet type-trait checks |
| // --------------------------------------------------------------------------- |
| |
| void test_block_triplet_traits() { |
| // Flat scalar array means BlockTriplet should be trivially copyable and |
| // standard-layout for any trivially-copyable Scalar and StorageIndex. |
| EIGEN_STATIC_ASSERT((std::is_trivially_copyable<BlockTriplet<float, 2, 2>>::value), |
| BLOCKTRIPLET_MUST_BE_TRIVIALLY_COPYABLE) |
| EIGEN_STATIC_ASSERT((std::is_trivially_copyable<BlockTriplet<double, 3, 3>>::value), |
| BLOCKTRIPLET_MUST_BE_TRIVIALLY_COPYABLE) |
| EIGEN_STATIC_ASSERT((std::is_trivially_copyable<BlockTriplet<float, 2, 3>>::value), |
| BLOCKTRIPLET_MUST_BE_TRIVIALLY_COPYABLE) |
| EIGEN_STATIC_ASSERT((std::is_standard_layout<BlockTriplet<float, 2, 2>>::value), BLOCKTRIPLET_MUST_BE_STANDARD_LAYOUT) |
| EIGEN_STATIC_ASSERT((std::is_standard_layout<BlockTriplet<double, 4, 4>>::value), |
| BLOCKTRIPLET_MUST_BE_STANDARD_LAYOUT) |
| |
| // No alignment padding: size must equal 2*sizeof(StorageIndex) + BlockSize*sizeof(Scalar). |
| EIGEN_STATIC_ASSERT((sizeof(BlockTriplet<float, 2, 2>) == 2 * sizeof(int) + 4 * sizeof(float)), |
| BLOCKTRIPLET_MUST_HAVE_NO_ALIGNMENT_PADDING) |
| EIGEN_STATIC_ASSERT((sizeof(BlockTriplet<double, 2, 2>) == 2 * sizeof(int) + 4 * sizeof(double)), |
| BLOCKTRIPLET_MUST_HAVE_NO_ALIGNMENT_PADDING) |
| EIGEN_STATIC_ASSERT((sizeof(BlockTriplet<float, 4, 4>) == 2 * sizeof(int) + 16 * sizeof(float)), |
| BLOCKTRIPLET_MUST_HAVE_NO_ALIGNMENT_PADDING) |
| EIGEN_STATIC_ASSERT((sizeof(BlockTriplet<double, 4, 4>) == 2 * sizeof(int) + 16 * sizeof(double)), |
| BLOCKTRIPLET_MUST_HAVE_NO_ALIGNMENT_PADDING) |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Main entry point |
| // --------------------------------------------------------------------------- |
| |
| EIGEN_DECLARE_TEST(block_sparse_matrix) { |
| // ColMajor, real double, various block sizes and matrix sizes |
| CALL_SUBTEST_1((test_block_sparse<1, 1, ColMajor>(6, 8))); |
| CALL_SUBTEST_2((test_block_sparse<2, 2, ColMajor>(4, 6))); |
| CALL_SUBTEST_3((test_block_sparse<3, 3, ColMajor>(4, 5))); |
| CALL_SUBTEST_4((test_block_sparse<4, 4, ColMajor>(3, 3))); |
| CALL_SUBTEST_5((test_block_sparse<2, 3, ColMajor>(5, 4))); |
| |
| // RowMajor, real double |
| CALL_SUBTEST_6((test_block_sparse<2, 2, RowMajor>(4, 6))); |
| CALL_SUBTEST_7((test_block_sparse<3, 3, RowMajor>(4, 5))); |
| CALL_SUBTEST_8((test_block_sparse<2, 3, RowMajor>(5, 4))); |
| |
| // Products (ColMajor) |
| CALL_SUBTEST_9((test_block_sparse_product<2, ColMajor>(4, 5, 3))); |
| CALL_SUBTEST_9((test_block_sparse_product<3, ColMajor>(3, 4, 5))); |
| |
| // Products (RowMajor) |
| CALL_SUBTEST_10((test_block_sparse_product<2, RowMajor>(4, 5, 3))); |
| CALL_SUBTEST_10((test_block_sparse_product<3, RowMajor>(3, 4, 5))); |
| |
| // Non-square block product |
| CALL_SUBTEST_11(test_nonsquare_block_product()); |
| |
| // Block-sparse * dense and dense * block-sparse (ColMajor) |
| CALL_SUBTEST_12((test_block_sparse_dense_product<2, 2, ColMajor>(4, 5))); |
| CALL_SUBTEST_12((test_block_sparse_dense_product<3, 3, ColMajor>(3, 4))); |
| CALL_SUBTEST_12((test_block_sparse_dense_product<2, 3, ColMajor>(4, 3))); |
| |
| // Block-sparse * dense and dense * block-sparse (RowMajor) |
| CALL_SUBTEST_13((test_block_sparse_dense_product<2, 2, RowMajor>(4, 5))); |
| CALL_SUBTEST_13((test_block_sparse_dense_product<3, 3, RowMajor>(3, 4))); |
| CALL_SUBTEST_13((test_block_sparse_dense_product<2, 3, RowMajor>(4, 3))); |
| |
| // Transpose / adjoint (ColMajor and RowMajor, square and non-square blocks) |
| CALL_SUBTEST_14((test_block_sparse_transpose<2, 2, ColMajor>(4, 5))); |
| CALL_SUBTEST_14((test_block_sparse_transpose<2, 3, ColMajor>(5, 4))); |
| CALL_SUBTEST_14((test_block_sparse_transpose<2, 2, RowMajor>(4, 5))); |
| CALL_SUBTEST_14((test_block_sparse_transpose<3, 2, RowMajor>(4, 5))); |
| |
| // Triangular view (ColMajor and RowMajor) |
| CALL_SUBTEST_15((test_block_sparse_triangular<2, ColMajor>(5))); |
| CALL_SUBTEST_15((test_block_sparse_triangular<3, ColMajor>(4))); |
| CALL_SUBTEST_15((test_block_sparse_triangular<2, RowMajor>(5))); |
| |
| // Self-adjoint view (ColMajor and RowMajor) |
| CALL_SUBTEST_16((test_block_sparse_selfadjoint<2, ColMajor>(5))); |
| CALL_SUBTEST_16((test_block_sparse_selfadjoint<3, ColMajor>(4))); |
| CALL_SUBTEST_16((test_block_sparse_selfadjoint<2, RowMajor>(5))); |
| |
| // BlockTriplet type traits |
| CALL_SUBTEST_17(test_block_triplet_traits()); |
| |
| // Complex scalar coverage: conjugation paths in adjoint, selfadjoint, triangular products |
| CALL_SUBTEST_18((test_block_sparse<2, 2, ColMajor, std::complex<double>>(4, 6))); |
| CALL_SUBTEST_18((test_block_sparse<3, 3, ColMajor, std::complex<double>>(4, 5))); |
| CALL_SUBTEST_18((test_block_sparse<2, 2, RowMajor, std::complex<double>>(4, 6))); |
| CALL_SUBTEST_18((test_block_sparse_dense_product<2, 2, ColMajor, std::complex<double>>(4, 5))); |
| CALL_SUBTEST_18((test_block_sparse_dense_product<2, 2, RowMajor, std::complex<double>>(4, 5))); |
| CALL_SUBTEST_18((test_block_sparse_transpose<2, 2, ColMajor, std::complex<double>>(4, 5))); |
| CALL_SUBTEST_18((test_block_sparse_transpose<2, 3, ColMajor, std::complex<double>>(5, 4))); |
| CALL_SUBTEST_18((test_block_sparse_selfadjoint<2, ColMajor, std::complex<double>>(5))); |
| CALL_SUBTEST_18((test_block_sparse_selfadjoint<3, ColMajor, std::complex<double>>(4))); |
| CALL_SUBTEST_18((test_block_sparse_selfadjoint<2, RowMajor, std::complex<double>>(5))); |
| CALL_SUBTEST_18((test_block_sparse_triangular<2, ColMajor, std::complex<double>>(5))); |
| CALL_SUBTEST_18((test_block_sparse_triangular<2, RowMajor, std::complex<double>>(5))); |
| |
| // Triangular solve: forward/backward x direct/transposed/adjoint x ColMajor+RowMajor |
| CALL_SUBTEST_19((test_block_sparse_triangular_solve<2, ColMajor, double>(5))); |
| CALL_SUBTEST_19((test_block_sparse_triangular_solve<3, ColMajor, double>(4))); |
| CALL_SUBTEST_19((test_block_sparse_triangular_solve<2, RowMajor, double>(5))); |
| CALL_SUBTEST_19((test_block_sparse_triangular_solve<3, RowMajor, double>(4))); |
| CALL_SUBTEST_19((test_block_sparse_triangular_solve<2, ColMajor, std::complex<double>>(5))); |
| CALL_SUBTEST_19((test_block_sparse_triangular_solve<3, ColMajor, std::complex<double>>(4))); |
| CALL_SUBTEST_19((test_block_sparse_triangular_solve<2, RowMajor, std::complex<double>>(5))); |
| CALL_SUBTEST_19((test_block_sparse_triangular_solve<3, RowMajor, std::complex<double>>(4))); |
| } |