blob: 930298117e52d0add2862f9a32cde465b4b5bc61 [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
// References:
// [1] R. H. Bartels and G. W. Stewart, "Solution of the matrix equation
// AX + XB = C", Communications of the ACM 15 (1972), 820-826. The Schur
// form solve of the Sylvester equation B X + X A^T = mat(b) that
// BartelsStewart generalizes to Kronecker sums of any number of factors.
// [2] R. E. Lynch, J. R. Rice and D. H. Thomas, "Direct solution of partial
// difference equations by tensor product methods", Numerische Mathematik 6
// (1964), 185-199. The fast diagonalization method: with Hermitian factors
// the Schur forms are diagonal and the solve is a division by the
// eigenvalue sums.
#ifndef EIGEN_STRUCTURED_KRONECKER_SUM_H
#define EIGEN_STRUCTURED_KRONECKER_SUM_H
// IWYU pragma: private
#include "./InternalHeaderCheck.h"
namespace Eigen {
template <typename KroneckerSumType>
class BartelsStewart;
namespace internal {
template <typename LhsMatrix, typename RhsMatrix>
struct traits<KroneckerSum<LhsMatrix, RhsMatrix>> {
using Scalar = typename LhsMatrix::Scalar;
using StorageKind = Dense;
using XprKind = MatrixXpr;
using StorageIndex = int;
static constexpr int RowsAtCompileTime =
size_at_compile_time(traits<LhsMatrix>::RowsAtCompileTime, traits<RhsMatrix>::RowsAtCompileTime);
static constexpr int ColsAtCompileTime = RowsAtCompileTime;
static constexpr int MaxRowsAtCompileTime = RowsAtCompileTime;
static constexpr int MaxColsAtCompileTime = RowsAtCompileTime;
// No NestByRefBit, for the reason given at traits<KroneckerOperator>.
static constexpr unsigned int Flags = 0;
};
template <typename LhsMatrix, typename RhsMatrix>
struct evaluator_traits<KroneckerSum<LhsMatrix, RhsMatrix>> {
using Kind = IndexBased;
using Shape = StructuredShape;
};
template <typename KroneckerSumType>
struct traits<BartelsStewart<KroneckerSumType>> : traits<Matrix<typename KroneckerSumType::Scalar, Dynamic, Dynamic>> {
using XprKind = MatrixXpr;
using StorageKind = SolverStorage;
using StorageIndex = int;
using BaseTraits = traits<Matrix<typename KroneckerSumType::Scalar, Dynamic, Dynamic>>;
static constexpr unsigned int Flags = BaseTraits::Flags & RowMajorBit;
static constexpr int CoeffReadCost = Dynamic;
};
/** \internal A Kronecker sum S = L (+) R as a factor of a KroneckerOperator or
* of another KroneckerSum. Left products go through S itself, right products
* through its factors; the visitors read the sparse materialization, solves go
* through BartelsStewart, and the inverse and determinant through the dense
* matrix. */
template <typename LhsMatrix, typename RhsMatrix>
struct kron_factor_ops<KroneckerSum<LhsMatrix, RhsMatrix>, kKronSumFactor> {
using Factor = KroneckerSum<LhsMatrix, RhsMatrix>;
using Scalar = typename Factor::Scalar;
using LhsOps = kron_factor_ops<LhsMatrix>;
using RhsOps = kron_factor_ops<RhsMatrix>;
using DenseMatrix = Matrix<Scalar, Dynamic, Dynamic, ColMajor>;
using SparseFactor = SparseMatrix<Scalar>;
using TransposedFactor = KroneckerSum<typename LhsOps::TransposedFactor, typename RhsOps::TransposedFactor>;
using InverseFactor = DenseMatrix;
static constexpr bool StoresAllEntries = false;
static void prepare(Factor&) {}
static bool coeffIfStored(const Factor& f, Index row, Index col, Scalar& value) {
const Index n2 = f.rhs().rows();
Scalar a(0), b(0);
const bool storedA = row % n2 == col % n2 && LhsOps::coeffIfStored(f.lhs(), row / n2, col / n2, a);
const bool storedB = row / n2 == col / n2 && RhsOps::coeffIfStored(f.rhs(), row % n2, col % n2, b);
if (!storedA && !storedB) return false;
value = a + b;
return true;
}
template <typename Visitor>
static void forEachNonZero(const Factor& f, Visitor&& visit) {
kron_factor_ops<SparseFactor>::forEachNonZero(kron_factor_visitable<Factor>::get(f), visit);
}
static Matrix<Index, Dynamic, 1> innerNonZeros(const Factor& f, bool rowMajor) {
return kron_factor_ops<SparseFactor>::innerNonZeros(kron_factor_visitable<Factor>::get(f), rowMajor);
}
static TransposedFactor transposed(const Factor& f) { return f.transpose(); }
static Factor conjugated(const Factor& f) { return f.conjugate(); }
static TransposedFactor adjointed(const Factor& f) { return f.adjoint(); }
static InverseFactor inversed(const Factor& f) { return f.solve(DenseMatrix::Identity(f.rows(), f.cols())); }
static DenseMatrix denseFactor(const Factor& f) { return DenseMatrix(f); }
static DenseMatrix blockOperand(const Factor& f) { return denseFactor(f); }
static bool isSquareIdentity(const Factor&) { return false; }
template <typename Dst, typename Alpha, typename Xpr>
static void addLeftProduct(Dst& dst, const Alpha& alpha, const Factor& f, const Xpr& X) {
f.addProduct(dst, X, alpha);
}
// With X_j = X(:, j n2 : (j+1) n2 - 1), j < n1, the p x n2 slices of X,
// X (L (+) R)^T = X (L (x) I)^T + X (I (x) R)^T = X_{[p n2 x n1]} L^T + [X_j R^T]_j.
template <typename Dst, typename Alpha, typename Xpr, typename Work>
static void addRightProduct(Dst& dst, const Alpha& alpha, const Xpr& X, const Factor& f, Work& work) {
const Index p = X.rows(), n1 = f.lhs().rows(), n2 = f.rhs().rows();
auto dstL = dst.reshaped(p * n2, n1);
LhsOps::addRightProduct(dstL, alpha, X.reshaped(p * n2, n1), f.lhs(), work);
for (Index j = 0; j < n1; ++j) {
auto dstj = dst.middleCols(j * n2, n2);
RhsOps::addRightProduct(dstj, alpha, X.middleCols(j * n2, n2), f.rhs(), work);
}
}
// det = prod_{i,j} (lambda_i + mu_j) would cost only the factor Schur forms,
// but every delta lambda_i recurs in n_R factors of the product, and a shift
// split across L and R makes |lambda_i + mu_j| << |lambda_i| + |mu_j|. Against
// a 256-bit reference that product lost 1 to 6 digits to the LU of the
// materialized sum.
static Scalar balancedDet(const Factor& f, Index& exponent) {
return kron_factor_ops<DenseMatrix>::balancedDet(denseFactor(f), exponent);
}
};
template <typename LhsMatrix, typename RhsMatrix>
struct kron_factor_visitable<KroneckerSum<LhsMatrix, RhsMatrix>, kKronSumFactor> {
using Factor = KroneckerSum<LhsMatrix, RhsMatrix>;
using type = SparseMatrix<typename Factor::Scalar>;
static type get(const Factor& f) {
type S;
S = f;
return S;
}
};
/** \internal (L (+) R)(v_i (x) w_j) = (lambda_i + mu_j)(v_i (x) w_j), so the
* eigenvalues and eigenvectors recurse into the factors, as for a Kronecker
* factor. A sum has no separable SVD, which stays the dense one of the primary
* template. */
template <typename LhsMatrix, typename RhsMatrix>
struct kron_factor_spectrum<KroneckerSum<LhsMatrix, RhsMatrix>, kKronSumFactor>
: kron_factor_spectrum<KroneckerSum<LhsMatrix, RhsMatrix>, kKronDenseFactor> {
using Factor = KroneckerSum<LhsMatrix, RhsMatrix>;
using Eigenvectors = KroneckerOperator<typename kron_factor_spectrum<LhsMatrix>::Eigenvectors,
typename kron_factor_spectrum<RhsMatrix>::Eigenvectors>;
static typename Factor::ComplexVector eigenvalues(const Factor& f) { return f.eigenvalues(); }
static Eigenvectors eigenvectors(const Factor& f) { return f.eigenvectors(); }
};
template <typename LhsMatrix, typename RhsMatrix>
class kron_factor_solver<KroneckerSum<LhsMatrix, RhsMatrix>, kKronSumFactor> {
public:
using Factor = KroneckerSum<LhsMatrix, RhsMatrix>;
using Scalar = typename Factor::Scalar;
using DenseMatrix = Matrix<Scalar, Dynamic, Dynamic, ColMajor>;
explicit kron_factor_solver(const Factor& f) : m_solver(f) {}
template <typename Xpr>
DenseMatrix solveLeft(const Xpr& M) const {
return m_solver.solve(M);
}
// X = M S^{-T} solves S X^T = M^T, written through a transposed view of X.
template <typename Xpr>
DenseMatrix solveTransposedRight(const Xpr& M) const {
DenseMatrix X(M.rows(), M.cols());
X.transpose() = m_solver.solve(M.transpose());
return X;
}
private:
BartelsStewart<Factor> m_solver;
};
} // namespace internal
/** \ingroup StructuredMatrices_Module
* \class KroneckerSum
* \brief The Kronecker sum \f$ A \oplus B = A \otimes I + I \otimes B \f$ of two
* square matrices as an implicit operator that is never materialized.
*
* For \c A of size \c n1 and \c B of size \c n2 the Kronecker sum is the
* \c n1*n2 x \c n1*n2 matrix whose block \c (i,j) is \c A(i,j)*I plus \c B on the
* diagonal blocks. It is the matrix of the separable operators that
* finite-difference and spectral discretizations produce on tensor-product
* grids: with \c Dx and \c Dy the 1-D second-difference matrices
* tridiag(1, -2, 1)/h^2, the 2-D Laplacian is \f$ D_y \oplus D_x \f$, the 3-D
* one \f$ D_z \oplus D_y \oplus D_x \f$, and an implicit Euler step of the
* heat equation, \f$ (I - \tau L) u = b \f$ with \f$ L = D_y \oplus D_x \f$, is
* \f$ \big((I - \tau D_y) \oplus (-\tau D_x)\big) u = b \f$, since
* \f$ I \otimes I = I \f$ lets a shift go into either factor.
*
* With \f$ \mathrm{vec} \f$ stacking columns as for \ref KroneckerOperator, the
* product is \f$ (A \oplus B)\,\mathrm{vec}(X) = \mathrm{vec}(B X + X A^T) \f$
* for \c X of size \c n2 x \c n1: one product with each factor, O(n1 n2 (n1 +
* n2)) for dense factors and O(n1 nnz(B) + n2 nnz(A)) for sparse ones, with no
* identity ever formed. The factors may be of any kind \ref KroneckerOperator
* accepts -- dense, diagonal, sparse, \c Identity() or a \c KroneckerOperator --
* or a \c KroneckerSum itself, which is how sums of three or more factors are
* built (\c makeKroneckerSum(a, b, c, ...) nests to the right). A Kronecker sum
* may in turn be a factor of a \ref KroneckerOperator.
*
* The operator is closed under \ref transpose, \ref conjugate and \ref adjoint
* (\f$ (A \oplus B)^T = A^T \oplus B^T \f$), materializes into a dense or a
* sparse matrix on assignment or appears lazily in sparse expressions through
* \ref sparseView, and plugs into the matrix-free iterative solvers (with
* \c IdentityPreconditioner). \ref solve and the reusable
* \ref BartelsStewart solver use the Schur forms of the factors;
* \ref eigenvalues are the pairwise sums \f$ \lambda_i(A) + \mu_j(B) \f$, with
* \ref eigenvectors \f$ V_A \otimes V_B \f$, also as a \ref KroneckerOperator
* factor.
* \code
* SparseMatrix<double> Dx = ..., Dy = ...; // tridiag(1, -2, 1) / h^2
* SparseMatrix<double> Iy(ny, ny); Iy.setIdentity();
* auto M = makeKroneckerSum(Iy - tau * Dy, -tau * Dx); // I - tau (Dy (+) Dx)
* BartelsStewart<decltype(M)> step(M); // Schur forms, once
* for (int k = 0; k < steps; ++k) u = step.solve(u); // implicit Euler
* \endcode
*
* \tparam LhsMatrix the type of the left factor \c A, see \ref KroneckerOperator.
* \tparam RhsMatrix the type of the right factor \c B, under the same
* convention; its scalar type must match that of \c LhsMatrix.
*
* \sa makeKroneckerSum(), class BartelsStewart, class KroneckerOperator
*/
template <typename LhsMatrix, typename RhsMatrix>
class KroneckerSum : public EigenBase<KroneckerSum<LhsMatrix, RhsMatrix>> {
public:
using Scalar = typename LhsMatrix::Scalar;
using RealScalar = typename NumTraits<Scalar>::Real;
using StorageIndex = int;
static_assert(std::is_same<Scalar, typename RhsMatrix::Scalar>::value,
"KroneckerSum requires both factors to have the same scalar type");
static_assert((internal::kron_factor_is_dense_matrix<LhsMatrix>::value ||
internal::kron_factor_kind<LhsMatrix>() != internal::kKronDenseFactor) &&
(internal::kron_factor_is_dense_matrix<RhsMatrix>::value ||
internal::kron_factor_kind<RhsMatrix>() != internal::kKronDenseFactor),
"KroneckerSum factors must be plain Matrix, DiagonalMatrix or SparseMatrix types, identity factors "
"(makeKroneckerSum stores an Identity() expression as one), KroneckerOperators or KroneckerSums "
"(owning their storage)");
private:
using LhsOps = internal::kron_factor_ops<LhsMatrix>;
using RhsOps = internal::kron_factor_ops<RhsMatrix>;
using LhsSpectrum = internal::kron_factor_spectrum<LhsMatrix>;
using RhsSpectrum = internal::kron_factor_spectrum<RhsMatrix>;
public:
using ComplexScalar = std::complex<RealScalar>;
using DenseMatrix = Matrix<Scalar, Dynamic, Dynamic, ColMajor>;
using ComplexVector = Matrix<ComplexScalar, Dynamic, 1>;
static constexpr int RowsAtCompileTime =
internal::size_at_compile_time(LhsMatrix::RowsAtCompileTime, RhsMatrix::RowsAtCompileTime);
static constexpr int ColsAtCompileTime = RowsAtCompileTime;
static constexpr int MaxRowsAtCompileTime = RowsAtCompileTime;
static constexpr int MaxColsAtCompileTime = RowsAtCompileTime;
static constexpr int SizeAtCompileTime = internal::size_at_compile_time(RowsAtCompileTime, ColsAtCompileTime);
static constexpr int MaxSizeAtCompileTime = SizeAtCompileTime;
static constexpr bool IsRowMajor = false;
// Deliberately no IsVectorAtCompileTime, for the reason given in KroneckerOperator.
/** Builds the operator \c A (+) \c B from the two square factors, evaluated
* into the operator's factor types as for \ref KroneckerOperator. */
template <typename LhsDerived, typename RhsDerived>
KroneckerSum(const EigenBase<LhsDerived>& a, const EigenBase<RhsDerived>& b) : m_A(a.derived()), m_B(b.derived()) {
eigen_assert(m_A.size() > 0 && m_B.size() > 0 && "KroneckerSum factors must be non-empty");
eigen_assert(m_A.rows() == m_A.cols() && m_B.rows() == m_B.cols() && "KroneckerSum factors must be square");
LhsOps::prepare(m_A);
RhsOps::prepare(m_B);
}
EIGEN_DEVICE_FUNC Index rows() const { return m_A.rows() * m_B.rows(); }
EIGEN_DEVICE_FUNC Index cols() const { return rows(); }
/** \returns a lazy sparse expression of the operator in the storage order
* \a Options (\c ColMajor or \c RowMajor); see \ref KroneckerSparseView. */
template <int Options = ColMajor>
KroneckerSparseView<KroneckerSum, Options> sparseView() const {
return KroneckerSparseView<KroneckerSum, Options>(*this);
}
/** \returns the left factor \c A. */
const LhsMatrix& lhs() const { return m_A; }
/** \returns the right factor \c B. */
const RhsMatrix& rhs() const { return m_B; }
/** \returns the coefficient at row \a row and column \a col:
* \f$ A(i_1, j_1)\,\delta_{i_2 j_2} + \delta_{i_1 j_1} B(i_2, j_2) \f$ with
* \c row = i1*n2 + i2 and \c col = j1*n2 + j2. */
Scalar coeff(Index row, Index col) const {
eigen_assert(row >= 0 && row < rows() && col >= 0 && col < cols());
Scalar value;
return internal::kron_factor_ops<KroneckerSum>::coeffIfStored(*this, row, col, value) ? value : Scalar(0);
}
/** \returns the transpose \f$ A^T \oplus B^T \f$, itself a Kronecker sum. */
KroneckerSum<typename LhsOps::TransposedFactor, typename RhsOps::TransposedFactor> transpose() const {
return {LhsOps::transposed(m_A), RhsOps::transposed(m_B)};
}
/** \returns the conjugate \f$ \bar A \oplus \bar B \f$, itself a Kronecker sum. */
KroneckerSum conjugate() const { return {LhsOps::conjugated(m_A), RhsOps::conjugated(m_B)}; }
/** \returns the adjoint \f$ A^H \oplus B^H \f$, itself a Kronecker sum. */
KroneckerSum<typename LhsOps::TransposedFactor, typename RhsOps::TransposedFactor> adjoint() const {
return {LhsOps::adjointed(m_A), RhsOps::adjointed(m_B)};
}
/** \returns the solution of \c (*this) * x = b through a \ref BartelsStewart
* solver set up for this call; construct the solver once instead to reuse its
* Schur forms across calls. */
template <typename Rhs>
Matrix<Scalar, ColsAtCompileTime, Rhs::ColsAtCompileTime> solve(const MatrixBase<Rhs>& b) const {
const BartelsStewart<KroneckerSum> solver(*this);
return solver.solve(b);
}
/** \returns the eigenvalues in Kronecker order: entry \c i*n2 + j is
* \f$ \lambda_i(A) + \mu_j(B) \f$, from one eigenvalue solve per factor
* (recursively for a Kronecker-sum factor, and for a Kronecker factor, whose
* own factors must then be square), matching column \c i*n2 + j of
* \ref eigenvectors. The set is not sorted.
*
* Each sum carries the errors of the factor eigenvalues, of order
* \f$ \epsilon\,(\kappa(\lambda_i) \|A\| + \kappa(\mu_j) \|B\|) \f$ for simple
* eigenvalues with condition numbers \f$ \kappa \f$, where a dense eigensolver
* of \f$ A \oplus B \f$ errs by
* \f$ \epsilon\,\kappa(\lambda_i)\,\kappa(\mu_j)\,\|A \oplus B\| \f$. The sums
* are thus the more accurate for ill-conditioned or defective factors, whose
* Jordan blocks lengthen in the sum, and the less accurate when a shift
* splits across the factors, as in \f$ (A + cI) \oplus (B - cI) \f$. */
ComplexVector eigenvalues() const {
const ComplexVector lambda = LhsSpectrum::eigenvalues(m_A), mu = RhsSpectrum::eigenvalues(m_B);
return (mu.replicate(fix<1>, lambda.size()) + lambda.transpose().replicate(mu.size(), fix<1>)).reshaped();
}
/** \returns the matrix of eigenvectors \f$ V_A \otimes V_B \f$ -- a
* \ref KroneckerOperator, never materialized -- since
* \f$ (A \oplus B)(v_i \otimes w_j) = (\lambda_i + \mu_j)(v_i \otimes w_j) \f$:
* column \c i*n2 + j matches \c eigenvalues()[i*n2 + j]. Assign it to a dense
* matrix to materialize. */
KroneckerOperator<typename LhsSpectrum::Eigenvectors, typename RhsSpectrum::Eigenvectors> eigenvectors() const {
return {LhsSpectrum::eigenvectors(m_A), RhsSpectrum::eigenvectors(m_B)};
}
/** \returns the product expression \c (*this) * \a x, evaluated through
* \f$ \mathrm{mat}(y) = B\,\mathrm{mat}(x) + \mathrm{mat}(x)\,A^T \f$. */
template <typename Rhs>
Product<KroneckerSum, Rhs> operator*(const MatrixBase<Rhs>& x) const {
EIGEN_STATIC_ASSERT(ColsAtCompileTime == Dynamic || Rhs::RowsAtCompileTime == Dynamic ||
int(ColsAtCompileTime) == int(Rhs::RowsAtCompileTime),
INVALID_MATRIX_PRODUCT)
eigen_assert(x.rows() == cols() && "invalid product: dimensions do not match");
return Product<KroneckerSum, Rhs>(*this, x.derived());
}
/** \internal Computes \c dst += alpha * (*this) * rhs as
* \f$ \mathrm{mat}(y) \mathrel{+}= \alpha (B X + X A^T) \f$, \f$ X = \mathrm{mat}(x) \f$:
* in place for a single right-hand side, and for several on the stacked layout
* of KroneckerOperator::addProduct(), one product per factor per batch. */
template <typename Dest, typename Rhs, typename ProductScalar>
void addProduct(Dest& dst, const Rhs& rhs, const ProductScalar& alpha) const {
using ProductMatrix = Matrix<ProductScalar, Dynamic, Dynamic, ColMajor>;
const Index n1 = m_A.rows(), n2 = m_B.rows(), r = rhs.cols();
eigen_assert(rhs.rows() == n1 * n2 && "invalid product: dimensions do not match");
typename internal::nested_eval<Rhs, 1>::type actualRhs(rhs);
ProductMatrix X, Y, work;
const Index chunk = internal::kron_rhs_chunk<ProductScalar>(2 * n1 * n2);
for (Index k0 = 0; k0 < r; k0 += chunk) {
const Index c = numext::mini(chunk, r - k0);
if (c == 1) {
const auto Xk = actualRhs.col(k0).reshaped(n2, n1);
auto Yk = dst.col(k0).reshaped(n2, n1);
RhsOps::addLeftProduct(Yk, alpha, m_B, Xk);
LhsOps::addRightProduct(Yk, alpha, Xk, m_A, work);
continue;
}
internal::kron_stack_columns(X, actualRhs.middleCols(k0, c), n2, n1);
Y.setZero(n2 * c, n1);
auto Yflat = Y.reshaped(n2, c * n1);
RhsOps::addLeftProduct(Yflat, ProductScalar(1), m_B, X.reshaped(n2, c * n1));
LhsOps::addRightProduct(Y, ProductScalar(1), X, m_A, work);
for (Index k = 0; k < c; ++k) dst.col(k0 + k).reshaped(n2, n1) += alpha * Y.middleRows(k * n2, n2);
}
}
/** \internal Writes the representation into \a dst, dense or sparse according
* to the destination's storage kind; invoked through \c dense = sum; and
* \c sparse = sum;. */
template <typename Dest>
void evalTo(Dest& dst) const {
evalToImpl(dst, IsSparseDestination<Dest>());
}
/** \internal Computes \c dst += (*this), see evalTo(). */
template <typename Dest>
void addTo(Dest& dst) const {
addToImpl(dst, Scalar(1), IsSparseDestination<Dest>());
}
/** \internal Computes \c dst -= (*this), see evalTo(). */
template <typename Dest>
void subTo(Dest& dst) const {
addToImpl(dst, Scalar(-1), IsSparseDestination<Dest>());
}
private:
template <typename Dest>
using IsSparseDestination = std::is_same<typename internal::traits<Dest>::StorageKind, Sparse>;
template <typename Dest>
void evalToImpl(Dest& dst, std::false_type) const {
dst.setZero();
addToImpl(dst, Scalar(1), std::false_type());
}
/** \internal dst += s (A (x) I + I (x) B): each stored entry of A on the
* diagonal of its block, B on every diagonal block. */
template <typename Dest>
void addToImpl(Dest& dst, const Scalar& s, std::false_type) const {
const Index n1 = m_A.rows(), n2 = m_B.rows();
LhsOps::forEachNonZero(m_A, [&dst, &s, n2](Index i, Index j, const Scalar& a) {
dst.block(i * n2, j * n2, n2, n2).diagonal().array() += s * a;
});
const auto& B = RhsOps::blockOperand(m_B);
for (Index k = 0; k < n1; ++k) dst.block(k * n2, k * n2, n2, n2) += s * B;
}
/** \internal The sparse representation, as the sum of A (x) I and I (x) B
* materialized with exactly reserved inner vectors: inner vector k1*n2 + k2 of
* A (x) I holds the entries of inner vector k1 of A, of I (x) B those of inner
* vector k2 of B. Both visitors insert each inner vector in increasing inner
* index, see kron_factor_ops. */
template <typename Dest>
void evalToImpl(Dest& S, std::true_type) const {
using IndexVector = Matrix<Index, Dynamic, 1>;
using LhsVisitable = internal::kron_factor_visitable<LhsMatrix>;
using RhsVisitable = internal::kron_factor_visitable<RhsMatrix>;
using VisitedLhsOps = internal::kron_factor_ops<typename LhsVisitable::type>;
using VisitedRhsOps = internal::kron_factor_ops<typename RhsVisitable::type>;
const Index n1 = m_A.rows(), n2 = m_B.rows();
const auto& A = LhsVisitable::get(m_A);
const auto& B = RhsVisitable::get(m_B);
const IndexVector nnzA = VisitedLhsOps::innerNonZeros(A, Dest::IsRowMajor);
const IndexVector nnzB = VisitedRhsOps::innerNonZeros(B, Dest::IsRowMajor);
Dest SA(rows(), cols()), SB(rows(), cols());
SA.reserve(IndexVector(nnzA.transpose().replicate(n2, fix<1>).reshaped()));
VisitedLhsOps::forEachNonZero(A, [&SA, n2](Index i, Index j, const Scalar& a) {
for (Index k = 0; k < n2; ++k) SA.insert(i * n2 + k, j * n2 + k) = a;
});
SB.reserve(IndexVector(nnzB.replicate(n1, fix<1>)));
for (Index k = 0; k < n1; ++k)
VisitedRhsOps::forEachNonZero(
B, [&SB, n2, k](Index i, Index j, const Scalar& b) { SB.insert(k * n2 + i, k * n2 + j) = b; });
S = SA + SB;
}
template <typename Dest>
void addToImpl(Dest& dst, const Scalar& s, std::true_type) const {
typename Dest::PlainObject sum;
evalTo(sum);
if (s == Scalar(1))
dst += sum;
else
dst -= sum;
}
LhsMatrix m_A;
RhsMatrix m_B;
};
/** \ingroup StructuredMatrices_Module
* \returns the \ref KroneckerSum \c a (+) \c b, with the factor types deduced as
* for makeKroneckerOperator(). */
template <typename LhsDerived, typename RhsDerived>
KroneckerSum<typename internal::kron_factor_storage<LhsDerived>::type,
typename internal::kron_factor_storage<RhsDerived>::type>
makeKroneckerSum(const EigenBase<LhsDerived>& a, const EigenBase<RhsDerived>& b) {
return {a.derived(), b.derived()};
}
/** \ingroup StructuredMatrices_Module
* \returns the \ref KroneckerSum \c a (+) \c b (+) \c c (+) ..., nested to the
* right: \c makeKroneckerSum(a, makeKroneckerSum(b, c, ...)). */
template <typename D1, typename D2, typename D3, typename... Rest>
auto makeKroneckerSum(const EigenBase<D1>& a, const EigenBase<D2>& b, const EigenBase<D3>& c, const Rest&... rest) {
return makeKroneckerSum(a, makeKroneckerSum(b, c, rest...));
}
/** \ingroup StructuredMatrices_Module
* \class BartelsStewart
* \brief Direct solver for Kronecker-sum systems
* \f$ (A_1 \oplus A_2 \oplus \cdots \oplus A_d)\,x = b \f$.
*
* Flattening the (possibly nested) \ref KroneckerSum into its non-sum factors
* \f$ A_k = Q_k T_k Q_k^H \f$ (complex Schur forms, \f$ Q_k \f$ unitary,
* \f$ T_k \f$ upper triangular) gives
* \f[ A_1 \oplus \cdots \oplus A_d = Q\,(T_1 \oplus \cdots \oplus T_d)\,Q^H,
* \qquad Q = Q_1 \otimes \cdots \otimes Q_d, \f]
* so a solve applies \f$ Q^H \f$, back-substitutes the upper triangular
* \f$ T_1 \oplus \cdots \oplus T_d \f$ and applies \f$ Q \f$. Both transforms
* are one product per factor along its own index; the back substitution
* recurses over the factors with accumulated shifts,
* \f[ \big((\sigma + T_1(i,i)) I + T_2 \oplus \cdots \oplus T_d\big)\,y_i
* = c_i - \textstyle\sum_{j > i} T_1(i,j)\,y_j, \f]
* the Bartels-Stewart algorithm [1] for two factors. When every factor is
* exactly Hermitian, the Schur forms are the eigendecompositions, \f$ T_k \f$
* is real diagonal, and the triangular solve is a division by the eigenvalue
* sums -- the fast diagonalization method [2], in real arithmetic for real
* factors. Real factors that are not all symmetric take real Schur forms
* instead, for up to four factors: \f$ Q_k \f$ is orthogonal and \f$ T_k \f$
* quasi-upper-triangular, with a 2x2 diagonal block per complex conjugate
* eigenvalue pair. The block rows of the recurrence are then solved jointly:
* with \f$ \Sigma \f$ the Kronecker sum of the diagonal blocks chosen at the
* outer factors (at most \f$ 2^{d-1} \f$ wide) in place of \f$ \sigma \f$,
* \f[ \big((\Sigma \oplus T_1(I,I)) \oplus T_2 \oplus \cdots \oplus T_d\big)\,Y_I
* = C_I - \textstyle\sum_{J > I} Y_J\,T_1(I,J)^T \f]
* for each diagonal block \f$ I \f$ of \f$ T_1 \f$, ending in dense solves at
* most \f$ 2^d \f$ wide, as in LAPACK's xTRSYL for two factors. More real
* factors, and complex ones, take the complex Schur form. Either way the setup
* costs one \f$ O(n_k^3) \f$ decomposition per factor, each solve
* \f$ O(N \sum_k n_k) \f$ per right-hand side, \f$ N = \prod_k n_k \f$, plus
* up to \f$ O(4^d N) \f$ for the coupled solves of the real path; sparse
* factors are densified for the decomposition.
* \c transpose().solve() and \c adjoint().solve() reuse the decompositions:
* \f[ (A_1 \oplus \cdots \oplus A_d)^H = Q\,(T_1^H \oplus \cdots \oplus T_d^H)\,Q^H \f]
* is solved with the same transforms around a forward substitution (on the
* complex path, on copies of the \f$ T_k^H \f$ that \ref compute keeps), and
* \f$ M^T x = b \f$ as \f$ M^H \bar x = \bar b \f$.
*
* The system is singular exactly when some sum
* \f$ \lambda_{i_1}(A_1) + \cdots + \lambda_{i_d}(A_d) \f$ vanishes. As with
* \c PartialPivLU nothing detects it: the computed sum is typically of order
* \f$ \epsilon \sum_k \|A_k\| \f$ rather than zero, and the solution huge; it
* is non-finite when a sum vanishes exactly, as it can for diagonal,
* triangular or identity factors, whose decompositions are exact.
*
* The solve is backward stable relative to the factors, with residual
* \f$ \|b - Mx\| = O\big((\sum_k n_k)\,\epsilon\,(\sum_k \|A_k\|)\,\|x\|\big) \f$.
* That is relative to \f$ \sum_k \|A_k\| \f$, not \f$ \|M\| \f$; with a shift
* split across the factors, as in \f$ (A + cI) \oplus (B - cI) \f$, the first
* grows with \f$ c \f$ and the second does not.
*
* \c info() reports \c InvalidInput for a non-finite factor and
* \c NoConvergence when a Schur or eigenvalue iteration fails; the solve then
* returns NaN.
*
* \tparam KroneckerSumType the \ref KroneckerSum type to solve with.
*
* \sa class KroneckerSum
*/
template <typename KroneckerSumType>
class BartelsStewart : public SolverBase<BartelsStewart<KroneckerSumType>> {
public:
using Base = SolverBase<BartelsStewart>;
friend class SolverBase<BartelsStewart>;
EIGEN_GENERIC_PUBLIC_INTERFACE(BartelsStewart)
EIGEN_STATIC_ASSERT_NON_INTEGER(RealScalar)
using MatrixType = KroneckerSumType;
using ComplexScalar = std::complex<RealScalar>;
using DenseMatrix = Matrix<Scalar, Dynamic, Dynamic, ColMajor>;
using ComplexMatrix = Matrix<ComplexScalar, Dynamic, Dynamic, ColMajor>;
using RealVector = Matrix<RealScalar, Dynamic, 1>;
/** Default constructor; call \ref compute before \ref solve. */
BartelsStewart() = default;
/** Computes the factor decompositions of \a op. */
explicit BartelsStewart(const KroneckerSumType& op) { compute(op); }
/** Computes the Schur forms (eigendecompositions, when every factor is
* exactly Hermitian) of the non-sum factors of \a op. */
BartelsStewart& compute(const KroneckerSumType& op) {
std::vector<DenseMatrix> leaves;
collectLeaves(op, leaves);
const std::size_t d = leaves.size();
m_sizes.resize(d);
m_inner.resize(d);
m_unitary.clear();
m_triangular.clear();
m_triangularAdjoint.clear();
m_basis.clear();
m_quasiTriangular.clear();
m_info = Success;
bool hermitian = true;
for (std::size_t k = 0; k < d; ++k) {
m_sizes[k] = leaves[k].rows();
if (!leaves[k].allFinite()) m_info = InvalidInput;
hermitian = hermitian && leaves[k] == leaves[k].adjoint();
}
if (hermitian)
m_path = Path::Diagonal;
else if (!NumTraits<Scalar>::IsComplex && d <= std::size_t(kMaxRealFactors))
m_path = Path::RealSchur;
else
m_path = Path::ComplexSchur;
m_size = 1;
for (std::size_t k = d; k-- > 0;) {
m_inner[k] = m_size;
m_size *= m_sizes[k];
}
m_isInitialized = true;
if (m_info != Success) return *this;
switch (m_path) {
case Path::Diagonal: {
m_spectrum = RealVector::Zero(1);
SelfAdjointEigenSolver<DenseMatrix> es;
for (std::size_t k = 0; k < d; ++k) {
es.compute(leaves[k]);
if (es.info() != Success) m_info = NoConvergence;
m_basis.push_back(es.eigenvectors());
// Kronecker order: the new factor's index runs fastest.
const RealVector& lambda = es.eigenvalues();
const RealVector previous = m_spectrum;
m_spectrum =
(lambda.replicate(fix<1>, previous.size()) + previous.transpose().replicate(lambda.size(), fix<1>))
.reshaped();
}
break;
}
case Path::RealSchur:
computeRealSchur(leaves, internal::bool_constant<!NumTraits<Scalar>::IsComplex>());
break;
case Path::ComplexSchur: {
ComplexSchur<ComplexMatrix> schur;
for (std::size_t k = 0; k < d; ++k) {
schur.compute(leaves[k].template cast<ComplexScalar>());
if (schur.info() != Success) m_info = NoConvergence;
m_unitary.push_back(schur.matrixU());
m_triangular.push_back(schur.matrixT());
m_triangularAdjoint.emplace_back(m_triangular.back().adjoint());
}
break;
}
}
return *this;
}
Index rows() const noexcept { return m_size; }
Index cols() const noexcept { return m_size; }
/** \returns \c Success, \c InvalidInput for a non-finite factor, or
* \c NoConvergence when a factor decomposition did not converge. */
ComputationInfo info() const {
eigen_assert(m_isInitialized && "BartelsStewart is not initialized.");
return m_info;
}
/** \returns whether every factor is exactly Hermitian, so that the solve runs
* the fast diagonalization method. */
bool isHermitian() const {
eigen_assert(m_isInitialized && "BartelsStewart is not initialized.");
return m_path == Path::Diagonal;
}
#ifdef EIGEN_PARSED_BY_DOXYGEN
/** \returns the solution \c x of \c op * x = \a b, as a lazily evaluated
* expression. Supports multiple right-hand sides.
* \pre \ref compute has been called. */
template <typename Rhs>
inline const Solve<BartelsStewart, Rhs> solve(const MatrixBase<Rhs>& b) const;
#endif
#ifndef EIGEN_PARSED_BY_DOXYGEN
template <typename RhsType, typename DstType>
void _solve_impl(const RhsType& rhs, DstType& dst) const {
solveImpl(rhs, dst, /*adjoint=*/false);
}
// M^T = conj(M^H): M^{-T} b = conj(M^{-H} conj(b)).
template <bool Conjugate, typename RhsType, typename DstType>
void _solve_impl_transposed(const RhsType& rhs, DstType& dst) const {
constexpr bool ConjugateRhs = !Conjugate && NumTraits<Scalar>::IsComplex;
solveImpl(rhs.template conjugateIf<ConjugateRhs>(), dst, /*adjoint=*/true);
if (ConjugateRhs) dst = dst.conjugate();
}
#endif
private:
/** \internal x = M^{-1} b, or M^{-H} b when \a adjoint; on the Hermitian path
* M^H = M. */
template <typename RhsType, typename DstType>
void solveImpl(const RhsType& rhs, DstType& dst, bool adjoint) const {
if (m_info != Success) {
// No usable decompositions; the nested solves never see info().
dst.setConstant(Scalar(NumTraits<RealScalar>::quiet_NaN()));
return;
}
switch (m_path) {
case Path::Diagonal: {
DenseMatrix W = rhs;
applyBasis(W, m_basis, /*adjoint=*/true);
W.array().colwise() /= m_spectrum.array();
applyBasis(W, m_basis, /*adjoint=*/false);
dst = W;
break;
}
case Path::RealSchur: {
DenseMatrix W = rhs;
solveRealSchur(W, adjoint, internal::bool_constant<!NumTraits<Scalar>::IsComplex>());
dst = W;
break;
}
case Path::ComplexSchur: {
ComplexMatrix W = rhs.template cast<ComplexScalar>();
applyBasis(W, m_unitary, /*adjoint=*/true);
for (Index j = 0; j < W.cols(); ++j) {
if (adjoint)
adjointTriangularSolve(0, ComplexScalar(0), W.col(j).data());
else
triangularSolve(0, ComplexScalar(0), W.col(j).data());
}
applyBasis(W, m_unitary, /*adjoint=*/false);
dst = internal::structured_scalar_part_impl<Scalar>::run(W);
break;
}
}
}
template <typename Factor>
static void collectLeaves(const Factor& f, std::vector<DenseMatrix>& leaves) {
collectLeaves(f, leaves, internal::kron_factor_is_kronecker_sum<Factor>());
}
template <typename Factor>
static void collectLeaves(const Factor& f, std::vector<DenseMatrix>& leaves, std::true_type) {
collectLeaves(f.lhs(), leaves);
collectLeaves(f.rhs(), leaves);
}
template <typename Factor>
static void collectLeaves(const Factor& f, std::vector<DenseMatrix>& leaves, std::false_type) {
leaves.push_back(DenseMatrix(internal::kron_factor_ops<Factor>::denseFactor(f)));
}
/** \internal W.col(j) <- (M_1 (x) ... (x) M_d) W.col(j) with M_k = Q_k^H when
* \a adjoint, Q_k otherwise, one factor at a time: along factor k with inner
* stride s = n_{k+1} ... n_d, every length n_k s block of the columns is an
* s x n_k matrix V and the factor acts as V <- V M_k^T, or for s = 1 on all
* blocks at once as U <- M_k U. */
template <typename Work, typename Basis>
void applyBasis(Work& W, const std::vector<Basis>& Q, bool adjoint) const {
using WorkMatrix = Matrix<typename Work::Scalar, Dynamic, Dynamic, ColMajor>;
WorkMatrix T;
for (std::size_t k = 0; k < Q.size(); ++k) {
const Index n = m_sizes[k], s = m_inner[k], blocks = W.size() / (n * s);
if (s == 1) {
Map<WorkMatrix> U(W.data(), n, blocks);
if (adjoint)
T.noalias() = Q[k].adjoint() * U;
else
T.noalias() = Q[k] * U;
U = T;
continue;
}
for (Index b = 0; b < blocks; ++b) {
Map<WorkMatrix> V(W.data() + b * n * s, s, n);
if (adjoint)
T.noalias() = V * Q[k].conjugate();
else
T.noalias() = V * Q[k].transpose();
V = T;
}
}
}
/** \internal Solves (sigma I + T_k (+) ... (+) T_d) y = y in place for the
* length n_k s_k segment \a y, viewed as the s_k x n_k matrix Y whose column i
* belongs to index i of factor k; see the recurrence in the class
* documentation. */
void triangularSolve(std::size_t k, const ComplexScalar& sigma, ComplexScalar* y) const {
const Index n = m_sizes[k], s = m_inner[k];
const TriangularMatrix& T = m_triangular[k];
if (k + 1 == m_sizes.size()) {
// Last factor, s = 1: back substitution on the shifted T_d, by rows.
Map<Matrix<ComplexScalar, 1, Dynamic>> Y(y, n);
for (Index i = n - 1; i >= 0; --i) {
const Index tail = n - 1 - i;
Y(i) = (Y(i) - Y.tail(tail).cwiseProduct(T.row(i).tail(tail)).sum()) / (sigma + T(i, i));
}
return;
}
Map<ComplexMatrix> Y(y, s, n);
for (Index i = n - 1; i >= 0; --i) {
const Index tail = n - 1 - i;
if (tail > 0) Y.col(i).noalias() -= Y.rightCols(tail) * T.row(i).tail(tail).transpose();
triangularSolve(k + 1, sigma + T(i, i), Y.col(i).data());
}
}
/** \internal The adjoint of triangularSolve: solves
* (sigma I + L_k (+) ... (+) L_d) y = y in place by forward substitution on the
* lower triangular L_k = T_k^H, read by rows as triangularSolve reads T_k. */
void adjointTriangularSolve(std::size_t k, const ComplexScalar& sigma, ComplexScalar* y) const {
const Index n = m_sizes[k], s = m_inner[k];
const TriangularMatrix& L = m_triangularAdjoint[k];
if (k + 1 == m_sizes.size()) {
// Last factor, s = 1: forward substitution on the shifted L_d, by rows.
Map<Matrix<ComplexScalar, 1, Dynamic>> Y(y, n);
for (Index i = 0; i < n; ++i) Y(i) = (Y(i) - Y.head(i).cwiseProduct(L.row(i).head(i)).sum()) / (sigma + L(i, i));
return;
}
Map<ComplexMatrix> Y(y, s, n);
for (Index i = 0; i < n; ++i) {
if (i > 0) Y.col(i).noalias() -= Y.leftCols(i) * L.row(i).head(i).transpose();
adjointTriangularSolve(k + 1, sigma + L(i, i), Y.col(i).data());
}
}
// The real path's coupled systems are at most 2^d wide, see the class documentation.
static constexpr int kMaxRealFactors = 4;
static constexpr int kMaxCoupling = 1 << kMaxRealFactors;
using CouplingMatrix = Matrix<RealScalar, Dynamic, Dynamic, ColMajor, kMaxCoupling, kMaxCoupling>;
using CouplingVector = Matrix<RealScalar, Dynamic, 1, ColMajor, kMaxCoupling, 1>;
using RealMatrix = Matrix<RealScalar, Dynamic, Dynamic, ColMajor>;
void computeRealSchur(const std::vector<DenseMatrix>& leaves, std::true_type) {
RealSchur<DenseMatrix> schur;
for (const DenseMatrix& leaf : leaves) {
schur.compute(leaf);
if (schur.info() != Success) m_info = NoConvergence;
m_basis.push_back(schur.matrixU());
m_quasiTriangular.push_back(schur.matrixT());
}
}
void computeRealSchur(const std::vector<DenseMatrix>&, std::false_type) {}
void solveRealSchur(DenseMatrix& W, bool adjoint, std::true_type) const {
applyBasis(W, m_basis, /*adjoint=*/true);
std::vector<RealMatrix> work(m_sizes.size());
const CouplingMatrix outer = CouplingMatrix::Zero(1, 1);
for (Index j = 0; j < W.cols(); ++j)
quasiTriangularSolve(0, outer, Map<RealMatrix>(W.col(j).data(), W.rows(), 1), work, adjoint);
applyBasis(W, m_basis, /*adjoint=*/false);
}
void solveRealSchur(DenseMatrix&, bool, std::false_type) const {}
/** \internal Solves (Sigma (+) T_k (+) ... (+) T_d) vec(X) = vec(X) in place,
* X of size n_k s_k x m with column t belonging to index t of the m x m
* coupling Sigma (the slowest index). Diagonal block I of T_k couples
* Sigma (+) T_k(I,I) for the next factor; \a work holds one gather buffer per
* factor. When \a adjoint, T_j is replaced by T_j^T for every j, and the block
* rows are solved forward, block I coupling Sigma (+) T_k(I,I)^T. */
void quasiTriangularSolve(std::size_t k, const CouplingMatrix& Sigma, Map<RealMatrix> X,
std::vector<RealMatrix>& work, bool adjoint) const {
const Index n = m_sizes[k], s = m_inner[k], m = Sigma.rows();
const QuasiTriangularMatrix& T = m_quasiTriangular[k];
const bool last = k + 1 == m_sizes.size();
for (Index done = 0; done < n;) {
// Block I = [i, i + p): the next one up from the bottom, or down from the top when adjoint.
Index i, p;
if (adjoint) {
i = done;
p = i + 1 < n && T(i + 1, i) != RealScalar(0) ? 2 : 1;
} else {
const Index end = n - done;
p = end > 1 && T(end - 1, end - 2) != RealScalar(0) ? 2 : 1;
i = end - p;
}
const Index tail = n - i - p;
// T(I,I), T(J,I) for J < I, and T(I,J) for J > I.
const auto TII = T.block(i, i, p, p);
const auto colHead = T.block(0, i, i, p);
const auto rowTail = T.block(i, i + p, p, tail);
const CouplingMatrix coupled = adjoint ? kroneckerSum(Sigma, TII.transpose()) : kroneckerSum(Sigma, TII);
if (last) {
// s = 1: row i of X belongs to index i of the last factor.
auto XI = X.middleRows(i, p);
if (adjoint) {
if (i > 0) XI.noalias() -= colHead.transpose().lazyProduct(X.topRows(i));
} else if (tail > 0) {
XI.noalias() -= rowTail.lazyProduct(X.bottomRows(tail));
}
solveCoupled(coupled, XI);
} else {
for (Index t = 0; t < m; ++t) {
auto Xt = X.col(t).reshaped(s, n);
auto XtI = Xt.middleCols(i, p);
if (adjoint) {
if (i > 0) XtI.noalias() -= Xt.leftCols(i) * colHead;
} else if (tail > 0) {
XtI.noalias() -= Xt.rightCols(tail) * rowTail.transpose();
}
}
if (m == 1) {
quasiTriangularSolve(k + 1, coupled, Map<RealMatrix>(X.col(0).data() + i * s, s, p), work, adjoint);
} else {
// Columns (t, a) of Z, a fastest: column a of block I for coupling index t.
RealMatrix& Z = work[k];
Z.resize(s, m * p);
Z.reshaped(p * s, m) = X.middleRows(i * s, p * s);
quasiTriangularSolve(k + 1, coupled, Map<RealMatrix>(Z.data(), s, m * p), work, adjoint);
X.middleRows(i * s, p * s) = Z.reshaped(p * s, m);
}
}
done += p;
}
}
/** \internal Sigma (+) S = Sigma (x) I_p + I_m (x) S. */
template <typename Block>
static CouplingMatrix kroneckerSum(const CouplingMatrix& Sigma, const Block& S) {
const Index m = Sigma.rows(), p = S.rows();
eigen_internal_assert(m * p <= kMaxCoupling);
CouplingMatrix R = CouplingMatrix::Zero(m * p, m * p);
for (Index t = 0; t < m; ++t) {
for (Index u = 0; u < m; ++u) R.block(t * p, u * p, p, p).diagonal().setConstant(Sigma(t, u));
R.block(t * p, t * p, p, p) += S;
}
return R;
}
/** \internal Solves coupled * vec(XI) = vec(XI), XI of size p x m. */
template <typename Block>
static void solveCoupled(const CouplingMatrix& coupled, Block& XI) {
if (coupled.rows() == 1) {
XI(0, 0) /= coupled(0, 0);
return;
}
CouplingVector z = XI.reshaped();
z = coupled.partialPivLu().solve(z);
XI.reshaped() = z;
}
std::vector<Index> m_sizes, m_inner;
Index m_size = 0;
std::vector<DenseMatrix> m_basis;
RealVector m_spectrum;
// Row-major: the back substitution reads T_k by rows, and the forward
// substitution of the adjoint solve reads T_k^H, kept as its own copy, the
// same way.
using TriangularMatrix = Matrix<ComplexScalar, Dynamic, Dynamic, RowMajor>;
using QuasiTriangularMatrix = Matrix<RealScalar, Dynamic, Dynamic, RowMajor>;
std::vector<ComplexMatrix> m_unitary;
std::vector<TriangularMatrix> m_triangular, m_triangularAdjoint;
std::vector<QuasiTriangularMatrix> m_quasiTriangular;
enum class Path { Diagonal, RealSchur, ComplexSchur };
Path m_path = Path::Diagonal;
bool m_isInitialized = false;
ComputationInfo m_info = InvalidInput;
};
namespace internal {
template <typename LhsMatrix, typename RhsMatrix, typename Rhs, int ProductTag>
struct generic_product_impl<KroneckerSum<LhsMatrix, RhsMatrix>, Rhs, StructuredShape, DenseShape, ProductTag>
: structured_product_impl<KroneckerSum<LhsMatrix, RhsMatrix>, Rhs> {};
} // namespace internal
#if EIGEN_COMP_CXXVER < 17
template <typename LhsMatrix, typename RhsMatrix>
constexpr int KroneckerSum<LhsMatrix, RhsMatrix>::RowsAtCompileTime;
template <typename LhsMatrix, typename RhsMatrix>
constexpr int KroneckerSum<LhsMatrix, RhsMatrix>::ColsAtCompileTime;
template <typename LhsMatrix, typename RhsMatrix>
constexpr int KroneckerSum<LhsMatrix, RhsMatrix>::MaxRowsAtCompileTime;
template <typename LhsMatrix, typename RhsMatrix>
constexpr int KroneckerSum<LhsMatrix, RhsMatrix>::MaxColsAtCompileTime;
template <typename LhsMatrix, typename RhsMatrix>
constexpr int KroneckerSum<LhsMatrix, RhsMatrix>::SizeAtCompileTime;
template <typename LhsMatrix, typename RhsMatrix>
constexpr int KroneckerSum<LhsMatrix, RhsMatrix>::MaxSizeAtCompileTime;
template <typename LhsMatrix, typename RhsMatrix>
constexpr bool KroneckerSum<LhsMatrix, RhsMatrix>::IsRowMajor;
#endif
} // namespace Eigen
#endif // EIGEN_STRUCTURED_KRONECKER_SUM_H