| // 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 |