| // 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_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); |
| } |
| } |
| |
| 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(); |
| 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 { |
| 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. */ |
| 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()) { |
| 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 |
| |
| } // namespace Eigen |
| |
| #endif // EIGEN_STRUCTURED_BCCB_H |