blob: 84c73f9cc4ede06f05a6e39600d026840084a769 [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] P. J. Davis, "Circulant Matrices", Wiley, 1979. Diagonalization of
// circulant and block circulant matrices by the DFT; the closed-form
// eigenstructure used by eigenvalues()/eigenvectors(), and the SVD and
// pseudo-inverse below, follow from it by taking moduli/phases of the
// eigenvalues.
// [2] R. H. Chan and X.-Q. Jin, "An Introduction to Iterative Toeplitz
// Solvers", SIAM, 2007. BCCB matrices are diagonalized by the 2-D DFT
// F_{n1} (x) F_{n2}; the FFT-based products and solves below, and the use
// of BCCB operators as preconditioners for two-level Toeplitz (BTTB)
// systems, follow this reference.
// [3] G. H. Golub and C. F. Van Loan, "Matrix Computations", 4th ed., Johns
// Hopkins University Press, 2013, chapter 5.4 (numerical rank conventions).
// [4] J. J. Dongarra, J. R. Bunch, C. B. Moler and G. W. Stewart, "LINPACK
// Users' Guide", SIAM, 1979. determinant()'s balanced accumulation follows
// the convention of its xGEDI routines, which return determinants as a
// (fraction, exponent) pair to avoid spurious overflow/underflow.
// [5] P. H. Sterbenz, "Floating-Point Computation", Prentice-Hall, 1974.
// Scaling by a power of two is exact, the property the balanced
// accumulation, the rescaled rank threshold and the scaled 2-D FFT
// products rely on.
#ifndef EIGEN_STRUCTURED_BCCB_H
#define EIGEN_STRUCTURED_BCCB_H
// IWYU pragma: private
#include "./InternalHeaderCheck.h"
namespace Eigen {
template <typename Scalar_, int BlockSize_ = Dynamic, int NumBlocks_ = Dynamic>
class Bccb;
namespace internal {
/** \internal Compile-time product of the two circulant levels, Dynamic-aware. */
constexpr int bccb_dim(int a, int b) { return (a == Dynamic || b == Dynamic) ? Dynamic : a * b; }
template <typename Scalar_, int BlockSize_, int NumBlocks_>
struct traits<Bccb<Scalar_, BlockSize_, NumBlocks_>> {
using Scalar = Scalar_;
using StorageKind = Dense;
using XprKind = MatrixXpr;
using StorageIndex = int;
static constexpr int RowsAtCompileTime = bccb_dim(BlockSize_, NumBlocks_);
static constexpr int ColsAtCompileTime = RowsAtCompileTime;
static constexpr int MaxRowsAtCompileTime = RowsAtCompileTime;
static constexpr int MaxColsAtCompileTime = RowsAtCompileTime;
// Deliberately no NestByRefBit: transpose(), conjugate() and adjoint() return
// owning temporaries, so Product must nest the operator by value for a
// delayed-evaluated product expression to keep its left factor alive. The copy
// is O(N), negligible against the O(N log N) product evaluation.
static constexpr unsigned int Flags = 0;
};
template <typename Scalar_, int BlockSize_, int NumBlocks_>
struct evaluator_traits<Bccb<Scalar_, BlockSize_, NumBlocks_>> {
using Kind = IndexBased;
using Shape = StructuredShape;
};
} // namespace internal
/** \ingroup StructuredMatrices_Module
* \class Bccb
* \brief A block circulant matrix with circulant blocks (BCCB), the matrix of a
* two-dimensional circular convolution, represented by its n2 x n1 generating
* array.
*
* A BCCB matrix is the \c N x \c N matrix, \c N = n1*n2, that is circulant at two
* levels: it is an n1 x n1 block circulant whose n2 x n2 blocks are themselves
* circulant. With the generating array \c G (column \c k holds the first column
* of the k-th block), entry \c (i,j) with \c i = b1*n2 + i2, \c j = c1*n2 + j2
* equals
* \f[ C_{i,j}=G_{(i_2-j_2)\bmod n_2,\,(b_1-c_1)\bmod n_1}. \f]
* On a column-major reshaped vector, \f$C\,\operatorname{vec}(X)
* =\operatorname{vec}(G\mathbin{\circledast}X)\f$, where \f$\circledast\f$
* denotes 2-D circular convolution.
*
* BCCB matrices are diagonalized by the 2-D discrete Fourier transform
* \f$ F_{n_1} \otimes F_{n_2} \f$ ([1], [2]): the operator's \em symbol -- the 2-D
* DFT of \c G -- holds the eigenvalues. Products reuse that symbol or, for an
* awkward transform size, an equivalent cached padded-embedding symbol. This
* yields O(N log N) products (\c operator*), an O(N log N) direct
* (pseudo-inverse) solve (\ref solve), and closed-form factorizations: the
* eigendecomposition (\ref eigenvalues, \ref eigenvectors) and the SVD
* (\ref singularValues, \ref matrixU, \ref matrixV) in the 2-D Fourier basis,
* plus \ref rank, \ref inverse and \ref determinant. The class is closed under
* \ref transpose, \ref conjugate and \ref adjoint, which reuse the cached symbols.
* BCCB operators are the workhorse of image deblurring with periodic boundary
* conditions, and the natural preconditioners for two-level Toeplitz (BTTB)
* systems [2].
*
* The operator stores its own copy of the generating array and derives from
* \c EigenBase. Because \c operator* returns an Eigen product expression, a
* \c Bccb also drops into the matrix-free iterative solvers, and it can be
* assigned to a dense matrix when an explicit representation is needed. As with
* any matrix-free operator, the iterative solvers must be instantiated with
* \c IdentityPreconditioner (e.g.
* \c ConjugateGradient<Bccb<double>,Lower|Upper,IdentityPreconditioner>):
* the default preconditioners read individual coefficients through \c col() or
* \c InnerIterator, which the structured operators do not expose.
*
* Spectral operations use FFTs of the exact sizes \c n1 and \c n2. Products use
* those sizes when both are 5-smooth; otherwise an equivalent per-axis
* circulant embedding pads each awkward dimension to a 5-smooth size, avoiding
* the default kissfft backend's slow generic butterfly for large prime factors.
*
* \tparam Scalar_ the scalar type, real or complex.
* \tparam BlockSize_ the circulant block dimension \c n2 at compile time, or
* \c Dynamic (the default).
* \tparam NumBlocks_ the number of blocks \c n1 at compile time, or \c Dynamic
* (the default).
*
* \sa class Circulant, makeBccb()
*/
template <typename Scalar_, int BlockSize_, int NumBlocks_>
class Bccb : public EigenBase<Bccb<Scalar_, BlockSize_, NumBlocks_>> {
enum DenseAssignment { SetAssignment, AddAssignment, SubAssignment };
public:
using Scalar = Scalar_;
using RealScalar = typename NumTraits<Scalar>::Real;
using StorageIndex = int;
using Complex = std::complex<RealScalar>;
// The two-level structure keys on the *column-major* flattening of the n2 x n1
// generating array and symbol (entry (k2, k1) is flat index k1*n2 + k2), so
// every internal 2-D type is pinned to ColMajor explicitly: the semantics must
// not change under EIGEN_DEFAULT_TO_ROW_MAJOR. The single-row special case
// only satisfies Eigen's rule that 1 x n matrices be row-major; with one row
// the two orders coincide.
using GeneratorType =
Matrix<Scalar, BlockSize_, NumBlocks_, (BlockSize_ == 1 && NumBlocks_ != 1) ? int(RowMajor) : int(ColMajor)>;
using ComplexArray = Matrix<Complex, Dynamic, Dynamic, ColMajor>;
using ComplexVector = Matrix<Complex, Dynamic, 1>;
using RealVector = Matrix<RealScalar, Dynamic, 1>;
using RealArray = Matrix<RealScalar, Dynamic, Dynamic, ColMajor>;
using ComplexMatrix = Matrix<Complex, Dynamic, Dynamic, ColMajor>;
static constexpr int RowsAtCompileTime = internal::bccb_dim(BlockSize_, NumBlocks_);
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: Ref<const Bccb>'s default StrideType
// argument reads it, so its absence makes internal::is_ref_compatible SFINAE to
// false and keeps the iterative solvers on their matrix-free path.
/** Builds a BCCB matrix from its generating array \a generator: column \c k is
* the first column of the k-th circulant block.
*
* When the matrix is large enough for products to take the FFT path, the 2-D
* DFT of the array -- the eigenvalues of the matrix -- and any padded product
* symbol are computed here. Subsequent products and solves reuse the applicable
* cached transform. */
template <typename Derived>
explicit Bccb(const MatrixBase<Derived>& generator) : m_g(generator) {
eigen_assert(m_g.rows() > 0 && m_g.cols() > 0 && "Bccb generator must be non-empty");
if (m_g.size() > internal::structured_direct_threshold()) {
m_symbol = computeSymbol();
m_prodSymbol = computeProdSymbol(m_g);
}
m_fftUsable = computeFftUsable();
}
EIGEN_DEVICE_FUNC Index rows() const { return m_g.size(); }
EIGEN_DEVICE_FUNC Index cols() const { return m_g.size(); }
/** \returns the circulant block dimension \c n2. */
Index blockSize() const { return m_g.rows(); }
/** \returns the number of blocks \c n1 in each block row. */
Index numBlocks() const { return m_g.cols(); }
/** \returns the generating array. */
const GeneratorType& generator() const { return m_g; }
/** \returns the symbol of the operator: the 2-D DFT of the generating array,
* an n2 x n1 complex array whose entries are the eigenvalues of the matrix
* (see \ref eigenvalues for the ordering). Cached when the operator is large
* enough for products to take the FFT path, computed on the fly for small
* operators. */
ComplexArray symbol() const { return m_symbol.size() > 0 ? m_symbol : computeSymbol(); }
/** \returns the coefficient at row \a row and column \a col. */
Scalar coeff(Index row, Index col) const {
const Index n2 = blockSize();
Index k2 = row % n2 - col % n2;
if (k2 < 0) k2 += n2;
Index k1 = row / n2 - col / n2;
if (k1 < 0) k1 += numBlocks();
return m_g.coeff(k2, k1);
}
/** \returns the transpose of \c *this, itself a \c Bccb operator: the one
* generated by the array index-reversed in both dimensions. The cached
* symbols, when present, are reused -- the symbols of the transpose are their
* two-dimensional index reversals (embedding a generator commutes with
* index-reversing it, per axis, so the padded product symbol follows the same
* rule at its own grid) -- so no FFT is recomputed. */
Bccb transpose() const { return Bccb(reverse2(m_g), reverse2(m_symbol), reverse2(m_prodSymbol)); }
/** \returns the complex conjugate of \c *this, itself a \c Bccb operator. The
* cached symbols, when present, are reused: the symbols of the conjugate are
* the conjugated two-dimensional index reversals of the symbols. */
Bccb conjugate() const {
return Bccb(m_g.conjugate(), reverse2(m_symbol).conjugate(), reverse2(m_prodSymbol).conjugate());
}
/** \returns the adjoint of \c *this, itself a \c Bccb operator. The cached
* symbols, when present, are reused: the symbols of the adjoint are the
* elementwise conjugates of the symbols (the eigenvalues conjugate while the
* 2-D Fourier eigenbasis stays fixed). */
Bccb adjoint() const { return Bccb(reverse2(m_g).conjugate(), m_symbol.conjugate(), m_prodSymbol.conjugate()); }
/** \returns the minimum-norm least-squares solution of \c (*this) * x = b,
* computed directly in the 2-D Fourier domain. Symbol entries whose modulus
* reaches the rank threshold (see \ref rank) are inverted; the remaining ones
* are treated as exact zeros, so the result is the pseudo-inverse applied to
* \a b. For a non-singular operator this is the exact solution. Supports
* multiple right-hand sides. */
template <typename Rhs>
Matrix<Scalar, RowsAtCompileTime, Rhs::ColsAtCompileTime> solve(const MatrixBase<Rhs>& b) const {
EIGEN_STATIC_ASSERT(RowsAtCompileTime == Dynamic || Rhs::RowsAtCompileTime == Dynamic ||
int(RowsAtCompileTime) == int(Rhs::RowsAtCompileTime),
YOU_MIXED_MATRICES_OF_DIFFERENT_SIZES)
const Index N = rows();
eigen_assert(b.rows() == N && "right-hand side has the wrong number of rows");
const ComplexArray s = symbol();
RealArray mods;
RealScalar tol;
scaledModuli(s, mods, tol);
ComplexArray sinv(s.rows(), s.cols());
// Strictly-below-threshold entries are zeroed, matching SVDBase::rank(), so a
// smallest-normal 1x1 operator stays invertible. A comparison with NaN is
// false, so NaN symbol entries stay in the inverted set and propagate to the
// output instead of being silently zeroed.
// Keep the reciprocal behind a scalar branch: select() evaluates both arms,
// so it would divide by thresholded zeros and potentially raise floating-point
// exceptions even though those coefficients are discarded.
for (Index k = 0; k < s.size(); ++k) sinv(k) = mods(k) < tol ? Complex(0) : Complex(1) / s(k);
Matrix<Scalar, RowsAtCompileTime, Rhs::ColsAtCompileTime> x(N, b.cols());
if (!b.allFinite()) {
// A non-finite right-hand side cannot go through the transforms (see
// addProduct): apply the pseudo-inverse -- itself a BCCB operator,
// generated by the 2-D inverse DFT of the thresholded reciprocal symbol --
// through the direct kernel so Inf/NaN propagate entrywise.
const GeneratorType pg = generatorFromSymbol(sinv);
x.setZero();
Bccb(pg, ComplexArray(), ComplexArray()).directProduct(x, b.derived(), Scalar(1));
return x;
}
// The right-hand side is finite here (checked above), so no per-column
// direct kernel can be needed.
applySymbol(x, sinv, b.derived(), Scalar(1), /*accumulate=*/false,
[](Index) { eigen_assert(false && "non-finite column requires a direct kernel"); });
return x;
}
/** \returns the numerical rank: the number of symbol entries whose modulus is
* no smaller than the threshold \c N * epsilon * max|symbol|, both evaluated
* in an exactly rescaled frame so the moduli cannot overflow (see
* scaledModuli()) and clamped from below like \c SVDBase::rank(). This is the
* same threshold \ref solve uses to decide which Fourier components to invert,
* and the comparison is strict like SVDBase's, so an entry sitting exactly on
* the threshold still counts as non-zero. */
Index rank() const {
const ComplexArray s = symbol();
RealArray mods;
RealScalar tol;
scaledModuli(s, mods, tol);
return (!(mods.array() < tol)).count(); // negated so NaN entries count as non-zero
}
/** \returns the inverse of \c *this, itself a \c Bccb operator: the one
* generated by the 2-D inverse DFT of the entrywise-inverted symbol.
* \warning The operator must be non-singular; use \ref solve for a
* pseudo-inverse solve of a rank-deficient operator. */
Bccb inverse() const {
ComplexArray sinv = symbol().cwiseInverse();
const GeneratorType ginv = generatorFromSymbol(sinv);
const bool cache = rows() > internal::structured_direct_threshold();
return Bccb(ginv, cache ? sinv : ComplexArray(), cache ? computeProdSymbol(ginv) : ComplexArray());
}
/** \returns the determinant, i.e. the product of the eigenvalues (the symbol
* entries). The product is accumulated in the balanced form \c m * 2^e (the
* split fraction/exponent determinant convention of LINPACK's xGEDI [4]) --
* every factor and the running product are renormalized to unit magnitude with
* the power of two tracked separately -- so the partial products can neither
* overflow nor underflow when the determinant itself is representable, whatever
* the ordering of large and small eigenvalues. For a real operator the product
* is real up to roundoff, and its real part is returned. */
Scalar determinant() const {
const ComplexArray s = symbol();
Complex det(1);
Index exponent = 0;
for (Index k1 = 0; k1 < s.cols(); ++k1)
for (Index k2 = 0; k2 < s.rows(); ++k2)
det = internal::structured_balance(Complex(det * internal::structured_balance(s(k2, k1), exponent)), exponent);
return toScalar(internal::structured_ldexp_clamped(det, exponent), std::is_same<RealScalar, Scalar>());
}
/** \returns the eigenvalues as the column-major flattening of the symbol:
* eigenvalue \c f1*n2 + f2 is \c symbol()(f2, f1), and its (unit-norm)
* eigenvector is the Kronecker product of the 1-D Fourier vectors of
* frequencies \c f1 and \c f2, i.e. column \c f1*n2 + f2 of \ref eigenvectors.
* Every BCCB matrix is diagonalized by this same 2-D Fourier basis. */
ComplexVector eigenvalues() const { return symbol().reshaped(); }
/** \returns the unitary matrix of eigenvectors: column \c f1*n2 + f2 is the
* 2-D Fourier vector matching \c eigenvalues()[f1*n2 + f2].
* \note The eigenvector matrix is materialized as a dense \c N x \c N matrix;
* unlike the other methods of this class this costs O(N^2) storage. */
ComplexMatrix eigenvectors() const {
const Index n2 = blockSize(), n1 = numBlocks(), N = rows();
ComplexMatrix F(N, N);
for (Index f1 = 0; f1 < n1; ++f1)
for (Index f2 = 0; f2 < n2; ++f2) fourierColumn(F, f1, f2, f1 * n2 + f2);
return F;
}
/** \returns the singular values, sorted in decreasing order: the moduli of the
* symbol entries. The ordering is shared with \ref matrixU and \ref matrixV,
* so together they form the SVD \c *this = U * singularValues().asDiagonal() * V^H. */
RealVector singularValues() const {
const ComplexVector s = eigenvalues();
const RealVector mods = s.cwiseAbs();
const std::vector<Index> perm = internal::structured_svd_permutation(mods);
RealVector sv(s.size());
for (Index t = 0; t < s.size(); ++t) sv[t] = mods[perm[t]];
return sv;
}
/** \returns the matrix of left singular vectors \c U: column \c t is the 2-D
* Fourier vector of the t-th largest symbol entry, scaled by its phase (phase 1
* for a zero entry). Dense \c N x \c N, see the note in \ref eigenvectors. */
ComplexMatrix matrixU() const {
const Index n2 = blockSize(), N = rows();
const ComplexVector s = eigenvalues();
const RealVector mods = s.cwiseAbs();
const std::vector<Index> perm = internal::structured_svd_permutation(mods);
ComplexMatrix U(N, N);
for (Index t = 0; t < N; ++t) {
fourierColumn(U, perm[t] / n2, perm[t] % n2, t);
const RealScalar a = mods[perm[t]];
if (a > RealScalar(0)) U.col(t) *= s[perm[t]] / a;
}
return U;
}
/** \returns the matrix of right singular vectors \c V: column \c t is the 2-D
* Fourier vector of the t-th largest symbol entry. Dense \c N x \c N, see the
* note in \ref eigenvectors. */
ComplexMatrix matrixV() const {
const Index n2 = blockSize(), N = rows();
const ComplexVector s = eigenvalues();
const std::vector<Index> perm = internal::structured_svd_permutation(RealVector(s.cwiseAbs()));
ComplexMatrix V(N, N);
for (Index t = 0; t < N; ++t) fourierColumn(V, perm[t] / n2, perm[t] % n2, t);
return V;
}
/** \internal Writes the dense representation into \a dst: the (b1,c1) block is
* the generator column (b1-c1) mod n1 rotated downwards by the within-block
* column index, so only contiguous segment copies are involved. Invoked
* through \c dense = bccb; */
template <typename Dest>
void evalTo(Dest& dst) const {
assignTo<SetAssignment>(dst);
}
/** \internal Computes \c dst += (*this), see evalTo(). */
template <typename Dest>
void addTo(Dest& dst) const {
assignTo<AddAssignment>(dst);
}
/** \internal Computes \c dst -= (*this), see evalTo(). */
template <typename Dest>
void subTo(Dest& dst) const {
assignTo<SubAssignment>(dst);
}
/** \returns the product expression \c (*this) * \a x, evaluated through a fast
* 2-D-FFT-based matrix-vector product. The expression carries the default
* product tag, so assigning it behaves like any dense product: a temporary
* resolves aliasing between the destination and \a x, and \c .noalias() skips
* it. */
template <typename Rhs>
Product<Bccb, 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<Bccb, Rhs>(*this, x.derived());
}
/** \internal Computes \c dst += alpha * (*this) * rhs. \c ProductScalar is the
* promoted scalar of the product (complex when a real operator is applied to a
* complex right-hand side); the accumulation runs in the promoted type.
*
* Non-finite data takes the direct O(N^2) kernel: the transforms would smear a
* single Inf/NaN into NaNs across the whole output, where the dense product
* only propagates it through the dot products that touch it. A non-finite
* generating array or cached symbol (which can overflow even for a finite
* generator: the 2-D DFT accumulates up to N addends) routes the whole
* product; a non-finite right-hand-side column is detected inside the FFT
* loop -- in the same pass that derives its scaling exponent, so finite data
* pays no extra scan -- and falls back per column. */
template <typename Dest, typename Rhs, typename ProductScalar>
void addProduct(Dest& dst, const Rhs& rhs, const ProductScalar& alpha) const {
const Index N = rows();
eigen_assert(rhs.rows() == N && "invalid product: dimensions do not match");
if (N <= internal::structured_direct_threshold() || !m_fftUsable) {
directProduct(dst, rhs, alpha);
return;
}
// Products use the padded embedding symbol when a block dimension is not
// 5-smooth (see computeProdSymbol()); the exact-size transform of such a
// dimension runs through kissfft's quadratic generic butterfly.
const ComplexArray& s = m_prodSymbol.size() > 0 ? m_prodSymbol : m_symbol;
applySymbol(dst, s, rhs, alpha, /*accumulate=*/true, [&](Index k) { directProductColumn(dst, rhs, k, alpha); });
}
private:
/** \internal Materializes the dense BCCB representation with the selected
* assignment mode; shared by evalTo(), addTo() and subTo(). */
template <DenseAssignment Assignment, typename Dest>
void assignTo(Dest& dst) const {
const Index n2 = blockSize(), n1 = numBlocks();
for (Index c1 = 0; c1 < n1; ++c1)
for (Index j2 = 0; j2 < n2; ++j2) {
auto col = dst.col(c1 * n2 + j2);
for (Index b1 = 0; b1 < n1; ++b1) {
Index k1 = b1 - c1;
if (k1 < 0) k1 += n1;
EIGEN_IF_CONSTEXPR (Assignment == SetAssignment) {
col.segment(b1 * n2, j2) = m_g.col(k1).tail(j2);
col.segment(b1 * n2 + j2, n2 - j2) = m_g.col(k1).head(n2 - j2);
} else EIGEN_IF_CONSTEXPR (Assignment == AddAssignment) {
col.segment(b1 * n2, j2) += m_g.col(k1).tail(j2);
col.segment(b1 * n2 + j2, n2 - j2) += m_g.col(k1).head(n2 - j2);
} else {
col.segment(b1 * n2, j2) -= m_g.col(k1).tail(j2);
col.segment(b1 * n2 + j2, n2 - j2) -= m_g.col(k1).head(n2 - j2);
}
}
}
}
/** \internal Builds an operator from a generating array and already-known
* symbols (empty for small operators; \a prodSymbol also empty when both block
* dimensions are 5-smooth, which needs no padded product embedding), skipping
* the FFTs of the public constructor. Used by transpose(), conjugate(),
* adjoint() and inverse(), whose symbols are cheap transformations of the
* existing ones. */
Bccb(const GeneratorType& g, const ComplexArray& symbol, const ComplexArray& prodSymbol)
: m_g(g), m_symbol(symbol), m_prodSymbol(prodSymbol) {
m_fftUsable = computeFftUsable();
}
/** \internal Whether products may take the FFT path: the generating array and
* the cached symbols must be finite. The symbols accumulate up to N
* (respectively p2*p1) addends, so they can overflow to Inf even for a finite
* generator; such operators fall back to the direct kernel, which stays
* exact. */
bool computeFftUsable() const {
return m_g.allFinite() && (m_symbol.size() == 0 || m_symbol.allFinite()) &&
(m_prodSymbol.size() == 0 || m_prodSymbol.allFinite());
}
/** \internal \returns the symbol products should use for a generating array
* \a g: empty when both block dimensions are 5-smooth (the exact-size
* transforms are already fast), otherwise the 2-D DFT of the per-axis
* circulant embedding of \a g. kissfft falls back to a quadratic generic
* butterfly for prime factors other than 2, 3 and 5, and the row-column
* algorithm pays that cost in every transform of the awkward axis, so each
* such axis is padded to \c p_i = fft_next_good_size(2*n_i - 1) and the cyclic
* convolution along it is evaluated as a padded linear convolution -- exactly
* the Toeplitz embedding of a circulant, applied per dimension. Along a
* padded axis the generator is laid out as [g; 0...0; g(1..)] (the wrapped
* band g(1..) sits at the top indices), so that for reachable offsets
* \c d = i - j in [-(n-1), n-1] the embedded array satisfies
* \c Ge(d mod p) = g(d mod n); offsets outside that range only touch the zero
* band. The two axes decouple, so the identity holds per entry of the 2-D
* grid and the leading (n2, n1) block of the padded cyclic convolution equals
* the exact one. A 5-smooth axis is kept at its exact size with no embedding.
* The spectral operations (eigenvalues, solve, rank, determinant, SVD) keep
* the exact-size symbol, whose entries are the eigenvalues. */
ComplexArray computeProdSymbol(const GeneratorType& g) const {
const Index n2 = g.rows(), n1 = g.cols();
const Index p2 = internal::fft_next_good_size(n2) == n2 ? n2 : internal::fft_next_good_size(2 * n2 - 1);
const Index p1 = internal::fft_next_good_size(n1) == n1 ? n1 : internal::fft_next_good_size(2 * n1 - 1);
if (p2 == n2 && p1 == n1) return ComplexArray();
ComplexArray Ge = ComplexArray::Zero(p2, p1);
Ge.topLeftCorner(n2, n1) = g.template cast<Complex>();
if (p2 != n2) Ge.bottomLeftCorner(n2 - 1, n1) = g.bottomRows(n2 - 1).template cast<Complex>();
if (p1 != n1) Ge.topRightCorner(n2, n1 - 1) = g.rightCols(n1 - 1).template cast<Complex>();
if (p2 != n2 && p1 != n1)
Ge.bottomRightCorner(n2 - 1, n1 - 1) = g.bottomRightCorner(n2 - 1, n1 - 1).template cast<Complex>();
fft2(Ge);
return Ge;
}
/** \internal Direct O(N^2) kernel for column \a k of the right-hand side:
* computes \c dst.col(k) += alpha * (*this) * rhs.col(k) without transforms,
* as a plain scalar loop (the two-level segment-based middle tier of the 1-D
* operators does not pay off here: the within-block segments are too short at
* these sizes). Serves operators below the FFT threshold and any column
* involving non-finite data, whose entrywise IEEE semantics the transforms
* cannot preserve. */
template <typename Dest, typename Rhs, typename ProductScalar>
void directProductColumn(Dest& dst, const Rhs& rhs, Index k, const ProductScalar& alpha) const {
const Index N = rows();
// A unit alpha must not multiply: even the identity complex scalar (1,0)
// pollutes an (Inf,0) value with NaN through the 0*Inf cross term.
const bool unitAlpha = alpha == ProductScalar(1);
for (Index i = 0; i < N; ++i) {
ProductScalar acc(0);
for (Index j = 0; j < N; ++j) acc += coeff(i, j) * rhs.coeff(j, k);
dst.coeffRef(i, k) += unitAlpha ? acc : ProductScalar(alpha * acc);
}
}
/** \internal Direct O(N^2) product kernel over every column, see
* directProductColumn(). */
template <typename Dest, typename Rhs, typename ProductScalar>
void directProduct(Dest& dst, const Rhs& rhs, const ProductScalar& alpha) const {
for (Index k = 0; k < rhs.cols(); ++k) directProductColumn(dst, rhs, k, alpha);
}
/** \internal Applies the operator whose 2-D symbol is \a s to every column of
* \a rhs: reshape to n2 x n1 (column-major), 2-D FFT, multiply by \a s,
* back-transform, take the \c ProductScalar part. Adds into \a dst when
* \a accumulate, overwrites otherwise. The transforms run on the grid of
* \a s itself: for the padded product symbol (see computeProdSymbol()) the
* column is zero-padded into the (p2, p1) grid and the leading (n2, n1) block
* of the back-transform is read out; for an exact-size symbol the grid is
* (n2, n1) and no padding is involved.
*
* The transforms sum up to \c N inputs, so intermediates can overflow even
* when every entry of the true result is representable. To keep the whole
* pipeline overflow-free for finite inputs anywhere in the floating-point
* range, the symbol and each right-hand-side column are rescaled by an exact
* power of two that brings their maximum modulus below one -- huge generators
* make huge symbols, hence both sides -- and the removed exponent is folded
* back into the output through per-entry ldexp, which saturates cleanly to
* zero or infinity if the true result itself leaves the representable range.
* The exponents come from the component-wise magnitudes: the modulus of a
* finite complex value near the overflow threshold is not representable,
* which would silently disable the scaling exactly where it is needed.
*
* A single plain (fast-max) reduction pass per column yields the finiteness
* routing, the zero-column shortcut and the scaling exponent (see
* internal::structured_exponent_bound_finite() for why missing a NaN in the
* fast max cannot change a routing decision). Non-finite columns are handed
* to \a directColumn, the caller's per-column direct kernel, so the remaining
* columns keep the fast path; a non-finite symbol is the caller's
* responsibility (addProduct routes it to the direct kernel up front, and
* solve() takes its dedicated pseudo-inverse fallback for non-finite
* right-hand sides). Zero columns short-circuit to zero, but only under a
* finite symbol (Inf*0 and NaN*0 are NaN entrywise) and after an exact
* recheck: the fast max can miss a NaN hiding among zeros (an Inf always
* surfaces), and such a column must fall back, not shortcut. */
template <typename Dest, typename Rhs, typename ProductScalar, typename DirectColumn>
void applySymbol(Dest& dst, const ComplexArray& s, const Rhs& rhs, const ProductScalar& alpha, bool accumulate,
DirectColumn&& directColumn) const {
const Index n2 = blockSize(), n1 = numBlocks(), N = rows();
const Index p2 = s.rows(), p1 = s.cols(); // the symbol's transform grid: (n2, n1) or the padded product grid
const bool padded = p2 != n2 || p1 != n1;
const bool sFinite = s.allFinite();
// max|s| < 2^es; component bounds avoid overflow in complex moduli.
const int es = internal::structured_exponent_bound(s);
ComplexArray sScaled;
if (es != 0) {
sScaled = s;
ldexpInPlace(sScaled, -es);
}
const ComplexArray& sUse = es != 0 ? sScaled : s;
Matrix<ProductScalar, Dynamic, 1> xc(N);
ComplexArray X(p2, p1), Xn; // Xn: the leading-block extraction of a padded grid
for (Index k = 0; k < rhs.cols(); ++k) {
xc = rhs.col(k).template cast<ProductScalar>();
const RealScalar m = xc.realView().cwiseAbs().maxCoeff();
if (!(numext::isfinite)(m)) {
directColumn(k);
continue;
}
if (m == RealScalar(0)) {
// The fast max cannot hide an Inf (those comparisons are ordered), but
// it can miss a NaN among zeros: recheck exactly before shortcutting.
if ((xc.array() == ProductScalar(0)).all()) {
if (sFinite) {
// An exactly zero column maps to an exactly zero column -- unless
// the symbol holds Inf or NaN, whose products with zero are NaN;
// falling through to the transforms produces exactly that.
if (!accumulate) dst.col(k).setZero();
continue;
}
} else {
directColumn(k);
continue;
}
}
int ex = 0; // stays 0 for an all-zero column: no scaling
if (m > RealScalar(0)) {
EIGEN_USING_STD(frexp);
frexp(m, &ex);
EIGEN_IF_CONSTEXPR (NumTraits<ProductScalar>::IsComplex) ++ex;
}
// reshaped() defaults to column-major traversal, the flattening the
// two-level structure keys on, regardless of EIGEN_DEFAULT_TO_ROW_MAJOR.
if (padded) {
X.setZero();
X.topLeftCorner(n2, n1) = xc.reshaped(n2, n1).template cast<Complex>();
} else {
X = xc.reshaped(n2, n1).template cast<Complex>();
}
ldexpInPlace(X, -ex);
fft2(X);
X.array() *= sUse.array();
ifft2(X);
if (padded) Xn = X.topLeftCorner(n2, n1);
ComplexArray& out = padded ? Xn : X;
ldexpInPlace(out, ex + es);
if (accumulate)
dst.col(k) += alpha * internal::structured_scalar_part_impl<ProductScalar>::run(out.reshaped());
else
dst.col(k) = alpha * internal::structured_scalar_part_impl<ProductScalar>::run(out.reshaped());
}
}
/** \internal Multiplies every entry of \a X by 2^e, exactly. The per-entry
* ldexp saturates to zero / infinity component-wise without ever forming the
* (possibly unrepresentable) scale factor 2^e itself. */
static void ldexpInPlace(ComplexArray& X, int e) {
if (e == 0) return;
X.realView() = X.realView().array().ldexp(e).matrix();
}
/** \internal In-place forward or inverse 2-D FFT by the row-column algorithm:
* transform every column (length n2), then every row (length n1). Length-1
* transforms are the identity (and unsupported by the kissfft backend), hence
* the guards. */
template <bool Inverse>
void transform2(ComplexArray& X) const {
auto&& fft = internal::structured_fft_engine<RealScalar>();
const Index n2 = X.rows(), n1 = X.cols();
// Both passes share one output buffer (resized by the engine) and one packed
// input buffer, allocated at most twice for the whole transform. A column of
// the column-major grid is already packed and goes to the engine as is; a row
// is strided, and the engine would pack it into a fresh temporary per call.
ComplexVector tmp, rowv;
if (n2 > 1) {
for (Index k1 = 0; k1 < n1; ++k1) {
EIGEN_IF_CONSTEXPR (Inverse) {
fft.inv(tmp, X.col(k1), n2);
} else {
fft.fwd(tmp, X.col(k1), n2);
}
X.col(k1) = tmp;
}
}
if (n1 > 1) {
for (Index k2 = 0; k2 < n2; ++k2) {
rowv = X.row(k2).transpose();
EIGEN_IF_CONSTEXPR (Inverse) {
fft.inv(tmp, rowv, n1);
} else {
fft.fwd(tmp, rowv, n1);
}
X.row(k2) = tmp.transpose();
}
}
}
/** \internal In-place 2-D forward FFT. */
void fft2(ComplexArray& X) const { transform2<false>(X); }
/** \internal In-place 2-D inverse FFT. */
void ifft2(ComplexArray& X) const { transform2<true>(X); }
/** \internal Reconstructs the generating array from an exact-size symbol. */
GeneratorType generatorFromSymbol(ComplexArray symbol) const {
ifft2(symbol);
GeneratorType generator = internal::structured_scalar_part_impl<Scalar>::run(symbol);
return generator;
}
/** \internal \returns the 2-D DFT of the generating array. */
ComplexArray computeSymbol() const {
ComplexArray s = m_g.template cast<Complex>();
fft2(s);
return s;
}
/** \internal \returns \a M index-reversed in both dimensions:
* result(k2, k1) = M((-k2) mod n2, (-k1) mod n1). Row 0 and column 0 stay in
* place; the rest is a two-dimensional reversal. Empty input stays empty. */
template <typename MatType>
static MatType reverse2(const MatType& M) {
const Index r = M.rows(), c = M.cols();
MatType R(r, c);
if (M.size() == 0) return R;
R(0, 0) = M(0, 0);
if (c > 1) R.row(0).tail(c - 1) = M.row(0).tail(c - 1).reverse();
if (r > 1) R.col(0).tail(r - 1) = M.col(0).tail(r - 1).reverse();
if (r > 1 && c > 1) R.bottomRightCorner(r - 1, c - 1) = M.bottomRightCorner(r - 1, c - 1).reverse();
return R;
}
/** \internal Computes the moduli of the symbol entries and the matching
* rank/pseudo-inversion threshold, both evaluated in an exactly rescaled frame
* [5]: the entries are pre-scaled by a power of two chosen so no modulus can
* overflow. A finite complex entry near the overflow threshold has a
* non-representable modulus, which would otherwise turn the threshold into
* infinity and misclassify every other entry (rank under-reported, solve()
* zeroing valid Fourier modes). The rescaling is exact, so comparing scaled
* moduli against the scaled threshold is equivalent to the unscaled
* comparison. The threshold keeps the N * epsilon * max|s| convention of [3],
* chapter 5.4, and the smallest-normal clamp -- carried into the scaled frame,
* where its underflowing to zero for a huge frame is correct: no entry of such
* a symbol can sit below the smallest normal number. Entries at or above the
* threshold, in particular a smallest-normal entry of a moderate symbol, are
* inverted (their reciprocals are finite). */
static void scaledModuli(const ComplexArray& s, RealArray& mods, RealScalar& tol) {
const int e = numext::maxi(internal::structured_exponent_bound(s), 0);
// Two exact factors, as in Circulant::scaledModuli(): a single 2^-e is itself
// subnormal once the frame exceeds the exponent range, and reads as zero under
// flush-to-zero -- collapsing every scaled modulus, and the threshold with
// them. Neither factor exceeds one, so no intermediate underflows on its own.
const RealScalar down1 = numext::ldexp(RealScalar(1), -(e / 2)), down2 = numext::ldexp(RealScalar(1), -(e - e / 2));
mods = ((s * down1) * down2).cwiseAbs();
tol = numext::maxi(RealScalar(s.size()) * NumTraits<RealScalar>::epsilon() * mods.maxCoeff(),
((std::numeric_limits<RealScalar>::min)() * down1) * down2);
}
/** \internal Writes the unit-norm 2-D Fourier eigenvector of frequencies
* \c (f1, f2) into column \a dstCol of \a F: entry \c b1*n2 + i2 is
* exp(2 pi i (b1 f1 / n1 + i2 f2 / n2)) / sqrt(N). All frequency products are
* accumulated incrementally modulo their length, so the angles stay O(2 pi) at
* full accuracy and no Index overflow can occur. */
void fourierColumn(ComplexMatrix& F, Index f1, Index f2, Index dstCol) const {
const Index n2 = blockSize(), n1 = numBlocks();
const RealScalar scale = RealScalar(1) / numext::sqrt(RealScalar(rows()));
ComplexVector w2(n2);
Index jf = 0; // i2 * f2 mod n2
for (Index i2 = 0; i2 < n2; ++i2) {
w2[i2] = std::polar(scale, RealScalar(2 * EIGEN_PI) * RealScalar(jf) / RealScalar(n2));
jf += f2;
if (jf >= n2) jf -= n2;
}
Index bf = 0; // b1 * f1 mod n1
for (Index b1 = 0; b1 < n1; ++b1) {
const Complex w1 = std::polar(RealScalar(1), RealScalar(2 * EIGEN_PI) * RealScalar(bf) / RealScalar(n1));
F.col(dstCol).segment(b1 * n2, n2) = w1 * w2;
bf += f1;
if (bf >= n1) bf -= n1;
}
}
/** \internal Projects the complex determinant onto \c Scalar. */
static Scalar toScalar(const Complex& z, std::true_type /*scalar_is_real*/) { return numext::real(z); }
static Scalar toScalar(const Complex& z, std::false_type /*scalar_is_real*/) { return z; }
GeneratorType m_g;
ComplexArray m_symbol;
// The padded embedding symbol products use when a block dimension is not
// 5-smooth; empty otherwise. See computeProdSymbol().
ComplexArray m_prodSymbol;
bool m_fftUsable;
};
/** \ingroup StructuredMatrices_Module
* \returns a \ref Bccb operator with generating array \a generator. The
* compile-time dimensions of the operator are deduced from the array. */
template <typename Derived>
Bccb<typename Derived::Scalar, Derived::RowsAtCompileTime, Derived::ColsAtCompileTime> makeBccb(
const MatrixBase<Derived>& generator) {
return Bccb<typename Derived::Scalar, Derived::RowsAtCompileTime, Derived::ColsAtCompileTime>(generator);
}
namespace internal {
template <typename Scalar_, int BlockSize_, int NumBlocks_, typename Rhs, int ProductTag>
struct generic_product_impl<Bccb<Scalar_, BlockSize_, NumBlocks_>, Rhs, StructuredShape, DenseShape, ProductTag>
: structured_product_impl<Bccb<Scalar_, BlockSize_, NumBlocks_>, Rhs> {};
} // namespace internal
} // namespace Eigen
#endif // EIGEN_STRUCTURED_BCCB_H