blob: 6d3f6724fba6ab84d411493d0f318dfb3a11eb6c [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 a
// circulant matrix by the DFT and the closed-form eigenstructure used by
// eigenvalues()/eigenvectors(); the SVD and pseudo-inverse below follow
// from it by taking moduli/phases of the eigenvalues.
// [2] R. M. Gray, "Toeplitz and Circulant Matrices: A Review", Foundations and
// Trends in Communications and Information Theory, 2(3), 2006.
// [3] G. H. Golub and C. F. Van Loan, "Matrix Computations", 4th ed., Johns
// Hopkins University Press, 2013, chapter 4.8 (circulant systems and
// FFT-based products) and 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 FFT products
// rely on.
#ifndef EIGEN_STRUCTURED_CIRCULANT_H
#define EIGEN_STRUCTURED_CIRCULANT_H
// IWYU pragma: private
#include "./InternalHeaderCheck.h"
namespace Eigen {
template <typename Scalar_, int Size_ = Dynamic>
class Circulant;
namespace internal {
template <typename Scalar_, int Size_>
struct traits<Circulant<Scalar_, Size_>> {
using Scalar = Scalar_;
using StorageKind = Dense;
using XprKind = MatrixXpr;
using StorageIndex = int;
static constexpr int RowsAtCompileTime = Size_;
static constexpr int ColsAtCompileTime = Size_;
static constexpr int MaxRowsAtCompileTime = Size_;
static constexpr int MaxColsAtCompileTime = Size_;
// 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 int Flags = 0;
};
template <typename Scalar_, int Size_>
struct evaluator_traits<Circulant<Scalar_, Size_>> {
using Kind = IndexBased;
using Shape = StructuredShape;
};
} // namespace internal
/** \ingroup StructuredMatrices_Module
* \class Circulant
* \brief An \c n x \c n circulant matrix represented by its first column.
*
* For first column \f$c\f$ and unitary DFT matrix \f$F\f$,
* \f[ C_{ij}=c_{(i-j)\bmod n}, \qquad C=F^*\operatorname{diag}(Fc)F. \f]
* Thus its eigenvalues -- the operator's \em symbol -- are \f$Fc\f$, computed
* once at construction and reused by every product. This yields an O(n log n)
* matrix-vector product (\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 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
* symbol instead of recomputing FFTs.
*
* The operator stores its own copy of the generating column and derives from
* \c EigenBase. Because \c operator* returns an Eigen product expression, a
* \c Circulant 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<Circulant<double>,Lower|Upper,IdentityPreconditioner>):
* the default preconditioners read individual coefficients through \c col() or
* \c InnerIterator, which the structured operators do not expose.
*
* \tparam Scalar_ the scalar type, real or complex.
* \tparam Size_ the dimension at compile time, or \c Dynamic (the default).
*
* \sa class Toeplitz, makeCirculant()
*/
template <typename Scalar_, int Size_>
class Circulant : public EigenBase<Circulant<Scalar_, Size_>> {
public:
using Scalar = Scalar_;
using RealScalar = typename NumTraits<Scalar>::Real;
using StorageIndex = int;
using Complex = std::complex<RealScalar>;
using GeneratorType = Matrix<Scalar, Size_, 1>;
using ComplexVector = Matrix<Complex, Dynamic, 1>;
using RealVector = Matrix<RealScalar, Size_, 1>;
using ComplexMatrix = Matrix<Complex, Size_, Size_>;
static constexpr int RowsAtCompileTime = Size_;
static constexpr int ColsAtCompileTime = Size_;
static constexpr int MaxRowsAtCompileTime = Size_;
static constexpr int MaxColsAtCompileTime = Size_;
static constexpr int SizeAtCompileTime = internal::size_at_compile_time(Size_, Size_);
static constexpr int MaxSizeAtCompileTime = SizeAtCompileTime;
static constexpr bool IsRowMajor = false;
// Deliberately no IsVectorAtCompileTime: Ref<const Circulant>'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 circulant matrix from its first column \a col.
*
* When the matrix is large enough for products to take the FFT path, the DFT of
* \a col -- the eigenvalues of the matrix -- is computed here, once, and reused
* by every subsequent product and solve. */
template <typename Derived>
explicit Circulant(const MatrixBase<Derived>& col) : m_col(col) {
EIGEN_STATIC_ASSERT_VECTOR_ONLY(Derived)
eigen_assert(m_col.size() > 0 && "Circulant generator must be non-empty");
if (m_col.size() > internal::structured_direct_threshold()) {
m_symbol = computeSymbol();
m_prodSymbol = computeProdSymbol(m_col);
}
m_fftUsable = computeFftUsable();
}
EIGEN_DEVICE_FUNC Index rows() const { return m_col.size(); }
EIGEN_DEVICE_FUNC Index cols() const { return m_col.size(); }
/** \returns the generating first column. */
const GeneratorType& column() const { return m_col; }
/** \returns the symbol of the operator, i.e. the DFT of the generating column.
* Its entries are the eigenvalues of the matrix. Cached when the operator is
* large enough for products to take the FFT path, computed on the fly for small
* operators. */
ComplexVector 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 {
Index k = row - col;
if (k < 0) k += rows();
return m_col.coeff(k);
}
/** \returns the transpose of \c *this, itself a \c Circulant operator: the one
* generated by the index-reversed column. The cached symbols, when present, are
* reused -- the symbols of the transpose are the index reversals of the symbols
* -- so no FFT is recomputed. */
Circulant transpose() const {
const Index n = rows();
GeneratorType col(n);
col[0] = m_col[0];
if (n > 1) col.tail(n - 1) = m_col.tail(n - 1).reverse();
return Circulant(col, internal::structured_reverse_symbol(m_symbol),
internal::structured_reverse_symbol(m_prodSymbol));
}
/** \returns the complex conjugate of \c *this, itself a \c Circulant operator.
* The cached symbols, when present, are reused: the symbols of the conjugate are
* the conjugated index reversals of the symbols. */
Circulant conjugate() const {
return Circulant(m_col.conjugate(), internal::structured_reverse_symbol(m_symbol).conjugate(),
internal::structured_reverse_symbol(m_prodSymbol).conjugate());
}
/** \returns the adjoint of \c *this, itself a \c Circulant 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
* Fourier eigenbasis stays fixed). */
Circulant adjoint() const {
const Index n = rows();
GeneratorType col(n);
col[0] = numext::conj(m_col[0]);
if (n > 1) col.tail(n - 1) = m_col.tail(n - 1).reverse().conjugate();
return Circulant(col, m_symbol.conjugate(), m_prodSymbol.conjugate());
}
/** \internal Writes the dense representation into \a dst; column \c j is the
* generator rotated downwards by \c j, so only contiguous segment copies are
* involved. Invoked through \c dense = circulant; */
template <typename Dest>
void evalTo(Dest& dst) const {
const Index n = rows();
for (Index j = 0; j < n; ++j) {
dst.col(j).head(j) = m_col.tail(j);
dst.col(j).tail(n - j) = m_col.head(n - j);
}
}
/** \internal Computes \c dst += (*this), see evalTo(). */
template <typename Dest>
void addTo(Dest& dst) const {
const Index n = rows();
for (Index j = 0; j < n; ++j) {
dst.col(j).head(j) += m_col.tail(j);
dst.col(j).tail(n - j) += m_col.head(n - j);
}
}
/** \internal Computes \c dst -= (*this), see evalTo(). */
template <typename Dest>
void subTo(Dest& dst) const {
const Index n = rows();
for (Index j = 0; j < n; ++j) {
dst.col(j).head(j) -= m_col.tail(j);
dst.col(j).tail(n - j) -= m_col.head(n - j);
}
}
/** \returns the product expression \c (*this) * \a x, evaluated through a fast
* 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<Circulant, 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<Circulant, Rhs>(*this, x.derived());
}
/** \returns the minimum-norm least-squares solution of \c (*this) * x = b,
* computed directly in the 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
* \c x = ifft( fft(b) ./ fft(c) ). Supports multiple right-hand sides. */
template <typename Rhs>
Matrix<Scalar, Size_, 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 ComplexVector s = symbol();
RealVector mods;
RealScalar tol;
int es = 0;
scaledModuli(s, mods, tol, &es);
// Strictly-below-threshold entries are zeroed, matching SVDBase::rank(), so a
// smallest-normal 1x1 operator stays invertible. NaN moduli fail the
// comparison and land in the inverted set, so a NaN input propagates to the
// output instead of being silently zeroed.
// Wrapped in a lambda: a call through the function's address cannot inline.
const auto reciprocal = [](const Complex& z) { return internal::structured_scaled_reciprocal(z); };
const ComplexVector sinv = (mods.array() < tol).select(Complex(0), s.unaryExpr(reciprocal));
Matrix<Scalar, Size_, Rhs::ColsAtCompileTime> x(n, b.cols());
x.setZero();
if (!b.allFinite()) {
// A non-finite right-hand side cannot go through the transforms (see
// addProduct): apply the pseudo-inverse -- itself a circulant matrix,
// generated by the inverse DFT of the thresholded reciprocal symbol --
// through the direct kernel so Inf/NaN propagate entrywise.
GeneratorType pcol(n);
if (n == 1) {
pcol[0] = internal::structured_scalar_part_impl<Scalar>::run_scalar(sinv.coeff(0));
} else {
auto&& fft = internal::structured_fft_engine<RealScalar>();
ComplexVector pt(n);
fft.inv(pt, sinv, n);
pcol = internal::structured_scalar_part_impl<Scalar>::run(pt);
}
Circulant(pcol, ComplexVector(), ComplexVector()).directProduct(x, b.derived(), Scalar(1));
return x;
}
// The reciprocal of the largest symbol entry is about 2^-es: subnormal, hence
// lost under flush-to-zero, only for a symbol at the top of the exponent range.
// Dividing by the symbol keeps that mode normal but costs a complex division
// per entry, so it is reserved for that band.
constexpr Index kSubnormalReciprocal = -(Index(std::numeric_limits<RealScalar>::min_exponent) - 1) - 2;
if (es <= kSubnormalReciprocal) {
internal::structured_fft_apply(x, sinv, n, b.derived(), Scalar(1));
return x;
}
// ldexp per component rather than a multiply by 2^-es: for a near-boundary
// symbol that constant is itself subnormal, and reads as zero under
// flush-to-zero. The apply folds es back into the output scaling.
const ComplexVector sb = s.unaryExpr([es](const Complex& z) { return internal::structured_ldexp_clamped(z, -es); });
// mods and tol are already in a balanced frame, so rebalancing the symbol
// leaves the threshold comparison unchanged.
internal::structured_symbol_divide<RealVector, RealScalar> divide{&mods, tol};
internal::structured_fft_apply(
x, sb, n, b.derived(), Scalar(1),
[](Index) { eigen_assert(false && "non-finite column requires a direct kernel"); }, divide, es);
return x;
}
/** \returns the numerical rank: the number of symbol entries whose modulus is
* no smaller than the threshold \c n * epsilon * max_k|symbol[k]|, both
* evaluated in an exactly rescaled frame so the moduli cannot overflow (see
* scaledModuli()). 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 ComplexVector s = symbol();
RealVector 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 Circulant operator: the one
* generated by \c ifft(1 ./ symbol), the first column of the inverse matrix.
* \warning The operator must be non-singular; use \ref solve for a
* pseudo-inverse solve of a rank-deficient operator. */
Circulant inverse() const {
const Index n = rows();
const ComplexVector sinv =
symbol().unaryExpr([](const Complex& z) { return internal::structured_scaled_reciprocal(z); });
GeneratorType col(n);
if (n == 1) {
col = internal::structured_scalar_part_impl<Scalar>::run(sinv);
} else {
auto&& fft = internal::structured_fft_engine<RealScalar>();
ComplexVector ct(n);
fft.inv(ct, sinv, n);
col = internal::structured_scalar_part_impl<Scalar>::run(ct);
}
return Circulant(col, n > internal::structured_direct_threshold() ? sinv : ComplexVector(),
n > internal::structured_direct_threshold() ? computeProdSymbol(col) : ComplexVector());
}
/** \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 ComplexVector s = symbol();
Complex det(1);
Index exponent = 0;
for (Index k = 0; k < s.size(); ++k)
det = internal::structured_balance(Complex(det * internal::structured_balance(s[k], exponent)), exponent);
return internal::structured_scalar_part_impl<Scalar>::run_scalar(internal::structured_ldexp_clamped(det, exponent));
}
/** \returns the eigenvalues: eigenvalue \c k is \c symbol()[k], and its
* (unit-norm) eigenvector is the Fourier vector \c f_k with
* \f$ (f_k)_j = e^{2\pi i j k / n} / \sqrt{n} \f$, see \ref eigenvectors.
* Every circulant matrix is diagonalized by this same Fourier basis. */
ComplexVector eigenvalues() const { return symbol(); }
/** \returns the unitary matrix of eigenvectors: column \c k is the Fourier
* vector \c f_k matching \c eigenvalues()[k].
* \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 n = rows();
ComplexMatrix F(n, n);
for (Index k = 0; k < n; ++k) fourierColumn(F, k, k);
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.
* A real operator has a conjugate-symmetric symbol, so the two modes of a
* conjugate pair carry exactly equal singular values. */
RealVector singularValues() const {
ComplexVector s;
RealVector mods;
const std::vector<Index> perm = svdOrdering(s, 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 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 n = rows();
ComplexVector s;
RealVector mods;
const std::vector<Index> perm = svdOrdering(s, mods);
ComplexMatrix U(n, n);
for (Index t = 0; t < n; ++t) {
fourierColumn(U, perm[t], 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
* 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 n = rows();
ComplexVector s;
RealVector mods;
const std::vector<Index> perm = svdOrdering(s, mods);
ComplexMatrix V(n, n);
for (Index t = 0; t < n; ++t) fourierColumn(V, perm[t], t);
return V;
}
/** \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
* generator or cached symbol (which can overflow even for a finite generator)
* 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 the operator size is not
// 5-smooth (see computeProdSymbol()); the exact-size transform of such a
// size runs through kissfft's quadratic generic butterfly.
const ComplexVector& s = m_prodSymbol.size() > 0 ? m_prodSymbol : m_symbol;
internal::structured_fft_apply(dst, s, n, rhs, alpha, [&](Index k) { directProductColumn(dst, rhs, k, alpha); });
}
private:
/** \internal Builds an operator from a generator and already-known symbols
* (empty for small operators; \a prodSymbol also empty for 5-smooth sizes,
* which need 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. */
Circulant(const GeneratorType& col, const ComplexVector& symbol, const ComplexVector& prodSymbol)
: m_col(col), m_symbol(symbol), m_prodSymbol(prodSymbol) {
m_fftUsable = computeFftUsable();
}
/** \internal Whether products may take the FFT path: the generator and the
* cached symbols must be finite. The symbols accumulate up to n (respectively
* p) 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_col.allFinite() && (m_symbol.size() == 0 || m_symbol.allFinite()) &&
(m_prodSymbol.size() == 0 || m_prodSymbol.allFinite());
}
/** \internal \returns the symbol products should use for a generator \a col of
* size n: empty when n is 5-smooth (the exact-size transform is already fast),
* otherwise the DFT of the circulant embedding of size
* \c p = fft_next_good_size(2n - 1) -- the length-n cyclic convolution
* evaluated as a padded linear convolution, exactly the Toeplitz embedding of
* the circulant. kissfft falls back to a quadratic generic butterfly for prime
* factors other than 2, 3 and 5, so the exact-size transform of e.g. a prime n
* costs orders of magnitude more than the padded one. The spectral operations
* (eigenvalues, solve, rank, determinant, SVD) keep the exact-size symbol,
* whose entries are the eigenvalues. */
static ComplexVector computeProdSymbol(const GeneratorType& col) {
const Index n = col.size();
if (internal::fft_next_good_size(n) == n) return ComplexVector();
const Index p = internal::fft_next_good_size(2 * n - 1);
ComplexVector embedding = ComplexVector::Zero(p);
embedding.head(n) = col.template cast<Complex>();
embedding.tail(n - 1) = col.tail(n - 1).template cast<Complex>();
ComplexVector symbol(p);
auto&& fft = internal::structured_fft_engine<RealScalar>();
fft.fwd(symbol, embedding, p);
return symbol;
}
/** \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.
* 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);
if (n <= internal::structured_scalar_threshold()) {
// Tiny sizes: a plain scalar loop beats the segment-based path below, whose
// per-segment setup dominates when segments hold only a few entries.
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);
}
return;
}
// Column j is c rotated downward by j; split it into two contiguous segments.
auto dstCol = dst.col(k);
for (Index j = 0; j < n; ++j) {
const ProductScalar xj = unitAlpha ? ProductScalar(rhs.coeff(j, k)) : ProductScalar(alpha * rhs.coeff(j, k));
dstCol.head(j) += xj * m_col.tail(j);
dstCol.tail(n - j) += xj * m_col.head(n - j);
}
}
/** \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 Computes the moduli of the symbol entries and the matching
* rank/pseudo-inversion threshold, both evaluated in an exactly rescaled frame:
* 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_k|s[k]| 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 ComplexVector& s, RealVector& mods, RealScalar& tol, int* frameExp = nullptr) {
const int e = numext::maxi(internal::structured_exponent_bound(s), 0);
if (frameExp) *frameExp = e;
// Two exact factors, as in structured_fft_apply(): 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 Computes the symbol \a s and the moduli \a mods that order the
* SVD, and \returns the permutation sorting those moduli in decreasing order --
* the single ordering singularValues(), matrixU() and matrixV() agree on.
*
* For a real generator the symbol is conjugate-symmetric, so modes \c k and
* \c n-k have equal moduli; taken through the complex transform they agree only
* to roundoff. Ranking such a pair by the computed moduli leaves its order
* decided by those last bits, and the three accessors each derive the ordering
* from their own symbol computation, which nothing requires to agree down to
* the last bit: one of them ranking a pair the other way pairs a left singular
* vector with the wrong right one and breaks the reconstruction by an O(1)
* amount. The moduli of a conjugate pair are therefore tied exactly, which the
* stable sort resolves by index in every caller. Sharing one value between the
* pair costs nothing: it cancels out of \c U * S, where matrixU() divides the
* mode's phase by it and singularValues() returns it.
*/
std::vector<Index> svdOrdering(ComplexVector& s, RealVector& mods) const {
const Index n = rows();
s = symbol();
mods = s.cwiseAbs();
if (!NumTraits<Scalar>::IsComplex)
for (Index k = 1; 2 * k < n; ++k) mods[n - k] = mods[k];
return internal::structured_svd_permutation(mods);
}
/** \internal Writes the unit-norm Fourier eigenvector \c f_k into column
* \a dstCol of \a F: (f_k)_j = exp(2 pi i j k / n) / sqrt(n). The index product
* j*k is accumulated incrementally modulo n, so the argument passed to polar()
* stays O(2 pi) -- keeping full accuracy for any n -- and no Index overflow can
* occur. */
void fourierColumn(ComplexMatrix& F, Index k, Index dstCol) const {
const Index n = rows();
const RealScalar scale = RealScalar(1) / numext::sqrt(RealScalar(n));
Index jk = 0; // j * k mod n
for (Index j = 0; j < n; ++j) {
F(j, dstCol) = std::polar(scale, RealScalar(2 * EIGEN_PI) * RealScalar(jk) / RealScalar(n));
jk += k;
if (jk >= n) jk -= n;
}
}
/** \internal \returns the DFT of the generating column. */
ComplexVector computeSymbol() const {
const Index n = m_col.size();
const ComplexVector cc = m_col.template cast<Complex>();
if (n == 1) return cc; // the DFT of a single sample is the identity
ComplexVector symbol(n);
auto&& fft = internal::structured_fft_engine<RealScalar>();
fft.fwd(symbol, cc, n);
return symbol;
}
GeneratorType m_col;
ComplexVector m_symbol;
// The padded embedding symbol products use when the size is not 5-smooth;
// empty otherwise. See computeProdSymbol().
ComplexVector m_prodSymbol;
bool m_fftUsable;
};
/** \ingroup StructuredMatrices_Module
* \returns a \ref Circulant operator with first column \a col. The compile-time
* size of the operator is deduced from \a col. */
template <typename Derived>
Circulant<typename Derived::Scalar, Derived::SizeAtCompileTime> makeCirculant(const MatrixBase<Derived>& col) {
return Circulant<typename Derived::Scalar, Derived::SizeAtCompileTime>(col);
}
namespace internal {
// StructuredShape avoids ambiguity with the stock dense product specializations.
template <typename Scalar_, int Size_, typename Rhs, int ProductTag>
struct generic_product_impl<Circulant<Scalar_, Size_>, Rhs, StructuredShape, DenseShape, ProductTag>
: structured_product_impl<Circulant<Scalar_, Size_>, Rhs> {};
} // namespace internal
} // namespace Eigen
#endif // EIGEN_STRUCTURED_CIRCULANT_H