| // 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 relies 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_INTERNAL_ASSERT_NONEMPTY_MSG(m_g, "Bccb generator must be non-empty"); | |
| if (m_g.size() > internal::structured_direct_threshold()) { | |
| m_symbol = computeSymbol(); | |
| m_prodSymbol = computeProdSymbol(m_g); | |
| } | |
| } | |
| 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_INTERNAL_ASSERT_SOLVER_RHS_MSG(*this, b, false, "right-hand side has the wrong number of rows"); | |
| const Index N = rows(); | |
| const ComplexArray s = symbol(); | |
| const RealArray mods = s.cwiseAbs(); | |
| const ComplexArray sinv = internal::structured_pinv_symbol(s, mods, internal::structured_rank_threshold(s, mods)); | |
| Matrix<Scalar, RowsAtCompileTime, Rhs::ColsAtCompileTime> x(N, b.cols()); | |
| applySymbol(x, sinv, b.derived(), Scalar(1), /*accumulate=*/false); | |
| return x; | |
| } | |
| /** \returns the numerical rank: the number of symbol entries whose modulus is | |
| * no smaller than the threshold \c N * epsilon * max|symbol|, clamped from | |
| * below by the smallest normal number 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(); | |
| const RealArray mods = s.cwiseAbs(); | |
| return (!(mods.array() < internal::structured_rank_threshold(s, mods))).count(); // 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 { | |
| 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. */ | |
| template <typename Dest, typename Rhs, typename ProductScalar> | |
| void addProduct(Dest& dst, const Rhs& rhs, const ProductScalar& alpha) const { | |
| const Index N = rows(); | |
| EIGEN_INTERNAL_ASSERT_PRODUCT_SHAPE_MSG(*this, rhs, "invalid product: dimensions do not match"); | |
| if (N <= internal::structured_direct_threshold()) { | |
| 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); | |
| } | |
| 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) {} | |
| /** \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), for operators below the FFT threshold. */ | |
| 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. As in internal::structured_fft_apply(), | |
| * the transforms run in plain floating-point arithmetic. */ | |
| template <typename Dest, typename Rhs, typename ProductScalar> | |
| void applySymbol(Dest& dst, const ComplexArray& s, const Rhs& rhs, const ProductScalar& alpha, | |
| bool accumulate) const { | |
| const Index n2 = blockSize(), n1 = numBlocks(); | |
| 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; | |
| ComplexVector xc(rows()); | |
| ComplexArray X(p2, p1), Xn; // Xn: the leading-block extraction of a padded grid | |
| for (Index k = 0; k < rhs.cols(); ++k) { | |
| // reshaped() defaults to column-major traversal, the flattening the | |
| // two-level structure keys on, regardless of EIGEN_DEFAULT_TO_ROW_MAJOR. | |
| // Plain objects keep both reshapes linear. | |
| xc = rhs.col(k).template cast<Complex>(); | |
| if (padded) { | |
| X.setZero(); | |
| X.topLeftCorner(n2, n1) = xc.reshaped(n2, n1); | |
| } else { | |
| X = xc.reshaped(n2, n1); | |
| } | |
| fft2(X); | |
| X.array() *= s.array(); | |
| ifft2(X); | |
| if (padded) Xn = X.topLeftCorner(n2, n1); | |
| const auto out = internal::structured_scalar_part_impl<ProductScalar>::run((padded ? Xn : X).reshaped()); | |
| if (accumulate) | |
| dst.col(k) += alpha * out; | |
| else | |
| dst.col(k) = alpha * out; | |
| } | |
| } | |
| /** \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 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; | |
| }; | |
| /** \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 | |
| #if EIGEN_COMP_CXXVER < 17 | |
| template <typename Scalar_, int BlockSize_, int NumBlocks_> | |
| constexpr int Bccb<Scalar_, BlockSize_, NumBlocks_>::RowsAtCompileTime; | |
| template <typename Scalar_, int BlockSize_, int NumBlocks_> | |
| constexpr int Bccb<Scalar_, BlockSize_, NumBlocks_>::ColsAtCompileTime; | |
| template <typename Scalar_, int BlockSize_, int NumBlocks_> | |
| constexpr int Bccb<Scalar_, BlockSize_, NumBlocks_>::MaxRowsAtCompileTime; | |
| template <typename Scalar_, int BlockSize_, int NumBlocks_> | |
| constexpr int Bccb<Scalar_, BlockSize_, NumBlocks_>::MaxColsAtCompileTime; | |
| template <typename Scalar_, int BlockSize_, int NumBlocks_> | |
| constexpr int Bccb<Scalar_, BlockSize_, NumBlocks_>::SizeAtCompileTime; | |
| template <typename Scalar_, int BlockSize_, int NumBlocks_> | |
| constexpr int Bccb<Scalar_, BlockSize_, NumBlocks_>::MaxSizeAtCompileTime; | |
| template <typename Scalar_, int BlockSize_, int NumBlocks_> | |
| constexpr bool Bccb<Scalar_, BlockSize_, NumBlocks_>::IsRowMajor; | |
| #endif | |
| } // namespace Eigen | |
| #endif // EIGEN_STRUCTURED_BCCB_H |