blob: c3b095eb5bea85251c7f92c7382e227f2cf07b07 [file]
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
// References:
// [1] G. H. Golub and C. F. Van Loan, "Matrix Computations", 4th ed., Johns
// Hopkins University Press, 2013, chapter 4.7 (Toeplitz, Hankel and
// related systems) and chapter 4.8 (fast products via circulant embedding
// and the FFT).
// [2] R. H. Chan and X.-Q. Jin, "An Introduction to Iterative Toeplitz
// Solvers", SIAM, 2007 (a Hankel matrix is a column-reversed Toeplitz
// matrix, so its products and solves reduce to the Toeplitz machinery).
#ifndef EIGEN_STRUCTURED_HANKEL_H
#define EIGEN_STRUCTURED_HANKEL_H
// IWYU pragma: private
#include "./InternalHeaderCheck.h"
namespace Eigen {
template <typename Scalar_, int Rows_ = Dynamic, int Cols_ = Dynamic>
class Hankel;
namespace internal {
template <typename Scalar_, int Rows_, int Cols_>
struct traits<Hankel<Scalar_, Rows_, Cols_>> {
using Scalar = Scalar_;
using StorageKind = Dense;
using XprKind = MatrixXpr;
using StorageIndex = int;
static constexpr int RowsAtCompileTime = Rows_;
static constexpr int ColsAtCompileTime = Cols_;
static constexpr int MaxRowsAtCompileTime = Rows_;
static constexpr int MaxColsAtCompileTime = Cols_;
// 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 Rows_, int Cols_>
struct evaluator_traits<Hankel<Scalar_, Rows_, Cols_>> {
using Kind = IndexBased;
using Shape = StructuredShape;
};
} // namespace internal
/** \ingroup StructuredMatrices_Module
* \class Hankel
* \brief An \c m x \c n Hankel matrix represented by its first column and last row.
*
* A Hankel matrix has constant anti-diagonals: entry \c (i,j) equals \c h[i+j],
* where \c h is the length \c m+n-1 sequence formed by the first column followed
* by the tail of the last row. The overlap entry \c h[m-1] is taken from the
* column, so \c row[0] is ignored. Every column of the matrix is a contiguous
* slice of \c h. A real square Hankel matrix is symmetric.
*
* The matrix-vector product (\c operator*) is evaluated in O(n log n): a Hankel
* matrix is a column-reversed Toeplitz matrix, so the product is a circulant
* convolution of \c h with the reversed input, evaluated through a DFT symbol
* computed once at construction. (Small operators, and single-row or
* single-column ones -- whose products cost O(n) directly -- skip the FFT for a
* direct evaluation.) As with \ref Toeplitz, \c operator* returns an Eigen
* product expression, so a \c Hankel plugs into the matrix-free iterative
* solvers (including the least-squares solvers, through \ref adjoint), 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 LeastSquaresConjugateGradient<Hankel<double>,IdentityPreconditioner>):
* the default preconditioners read individual coefficients through \c col() or
* \c InnerIterator, which the structured operators do not expose.
*
* The class is closed under \ref transpose, \ref conjugate and \ref adjoint. The
* transpose is the Hankel matrix of the same sequence with the dimensions
* swapped, and its symbol is obtained from the cached one by a diagonal phase
* multiplication instead of a new FFT.
*
* Square linear systems are solved directly in O(n^2) through the column-reversed
* \ref Toeplitz equivalent (\ref toToeplitz) and the \ref LookAheadLevinson
* solver, see \ref solve.
*
* \tparam Scalar_ the scalar type, real or complex.
* \tparam Rows_ the number of rows at compile time, or \c Dynamic (the default).
* \tparam Cols_ the number of columns at compile time, or \c Dynamic (the default).
*
* \sa class Toeplitz, class Circulant, makeHankel()
*/
template <typename Scalar_, int Rows_, int Cols_>
class Hankel : public EigenBase<Hankel<Scalar_, Rows_, Cols_>> {
public:
using Scalar = Scalar_;
using RealScalar = typename NumTraits<Scalar>::Real;
using StorageIndex = int;
using Complex = std::complex<RealScalar>;
using ColGeneratorType = Matrix<Scalar, Rows_, 1>;
using RowGeneratorType = Matrix<Scalar, Cols_, 1>;
using ComplexVector = Matrix<Complex, Dynamic, 1>;
static constexpr int RowsAtCompileTime = Rows_;
static constexpr int ColsAtCompileTime = Cols_;
static constexpr int MaxRowsAtCompileTime = Rows_;
static constexpr int MaxColsAtCompileTime = Cols_;
static constexpr int SizeAtCompileTime = internal::size_at_compile_time(Rows_, Cols_);
static constexpr int MaxSizeAtCompileTime = SizeAtCompileTime;
static constexpr bool IsRowMajor = false;
// Deliberately no IsVectorAtCompileTime: Ref<const Hankel>'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.
/** \internal Compile-time length of the generating sequence h. */
static constexpr int GenSizeAtCompileTime = (Rows_ == Dynamic || Cols_ == Dynamic) ? Dynamic : (Rows_ + Cols_ - 1);
using GeneratorType = Matrix<Scalar, GenSizeAtCompileTime, 1>;
/** Builds a Hankel matrix from its first column \a col and last row \a row.
* The anti-diagonal overlap entry is taken from \a col, hence \c row[0] is
* ignored.
*
* Unless the matrix is small, or a single row or column (whose products are
* always evaluated directly, in linear time), the DFT symbol of the underlying
* circulant convolution is computed here, once, and reused by every subsequent
* product. */
template <typename ColDerived, typename RowDerived>
Hankel(const MatrixBase<ColDerived>& col, const MatrixBase<RowDerived>& row)
: m_rows(col.size()), m_cols(row.size()) {
EIGEN_STATIC_ASSERT_VECTOR_ONLY(ColDerived)
EIGEN_STATIC_ASSERT_VECTOR_ONLY(RowDerived)
const Index m = col.size(), n = row.size();
eigen_assert(m > 0 && n > 0 && "Hankel generators must be non-empty");
m_h.resize(m + n - 1);
m_h.head(m) = col;
if (n > 1) m_h.tail(n - 1) = row.tail(n - 1);
if (m > 1 && n > 1 && (m > internal::structured_direct_threshold() || n > internal::structured_direct_threshold()))
m_symbol = computeSymbol();
m_fftUsable = computeFftUsable();
}
EIGEN_DEVICE_FUNC Index rows() const { return m_rows.value(); }
EIGEN_DEVICE_FUNC Index cols() const { return m_cols.value(); }
/** \returns the generating sequence \c h of length \c m+n-1; entry \c (i,j) of
* the matrix is \c h[i+j]. */
const GeneratorType& generator() const { return m_h; }
/** \returns the first column, \c h[0..m-1]. */
ColGeneratorType column() const { return m_h.head(rows()); }
/** \returns the last row, \c h[m-1..m+n-2]. */
RowGeneratorType lastRow() const { return m_h.segment(rows() - 1, cols()); }
/** \returns the symbol of the underlying circulant convolution: the DFT of the
* generating sequence, zero-padded to a 5-smooth size p >= m+n-1 and rotated so
* the product appears in the leading \c m entries of the back-transform. Cached
* when the operator is large enough for products to take the FFT path, computed
* on the fly for small operators. */
ComplexVector symbol() const { return m_symbol.size() > 0 ? m_symbol : computeSymbol(); }
/** \returns the coefficient at row \a row and column \a col. */
Scalar coeff(Index row, Index col) const { return m_h.coeff(row + col); }
/** \returns the transpose of \c *this, itself a Hankel operator: the same
* generating sequence with the dimensions swapped. The cached symbol, when
* present, is reused: transposing changes the rotation of the embedding from
* \c n-1 to \c m-1, which multiplies DFT entry \c f by \c exp(2 pi i f (m-n)/p)
* -- a diagonal phase multiplication instead of a new FFT. */
Hankel<Scalar, Cols_, Rows_> transpose() const {
return Hankel<Scalar, Cols_, Rows_>(m_h, cols(), rows(), transposedSymbol());
}
/** \returns the complex conjugate of \c *this, itself a Hankel operator. The
* cached symbol, when present, is reused: the symbol of the conjugate is the
* conjugated index reversal of the symbol. */
Hankel conjugate() const {
return Hankel(m_h.conjugate(), rows(), cols(), internal::structured_reverse_symbol(m_symbol).conjugate());
}
/** \returns the adjoint of \c *this, itself a Hankel operator (the conjugated
* transpose); the cached symbol is reused through the composition of the
* \ref conjugate and \ref transpose rules. */
Hankel<Scalar, Cols_, Rows_> adjoint() const { return conjugate().transpose(); }
/** \returns the column-reversed \ref Toeplitz equivalent \c T = H*E (with \c E
* the exchange matrix), i.e. \c T(i,j) = h[i + n-1 - j]. Its symbol is computed
* afresh. Useful for solving Hankel systems with the Toeplitz machinery:
* \c H*x = b is \c T*(E*x) = b. */
Toeplitz<Scalar, Rows_, Cols_> toToeplitz() const {
ColGeneratorType c = m_h.segment(cols() - 1, rows());
RowGeneratorType r = m_h.head(cols()).reverse();
return Toeplitz<Scalar, Rows_, Cols_>(c, r);
}
/** \internal Compile-time row count of solve() results: the solution of a
* square system has cols() == rows() entries, so either fixed dimension
* determines it at compile time. */
static constexpr int SolveRowsAtCompileTime = Cols_ != Dynamic ? Cols_ : Rows_;
/** \returns the solution \c x of \c (*this) * x = b for a \b square Hankel
* matrix, computed in O(n^2) by solving the column-reversed Toeplitz system
* \c T*y = b with the \ref LookAheadLevinson solver and reversing the solution.
* Supports multiple right-hand sides. Each call factorizes anew; to reuse a
* factorization across calls, or to check for numerical breakdown through
* \c info(), use \c LookAheadLevinson on \ref toToeplitz directly and reverse
* the solution rows. */
template <typename Rhs>
Matrix<Scalar, SolveRowsAtCompileTime, 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)
eigen_assert(rows() == cols() && "Hankel::solve requires a square matrix");
eigen_assert(b.rows() == rows() && "right-hand side has the wrong number of rows");
LookAheadLevinson<Scalar> levinson(toToeplitz());
eigen_assert(levinson.info() == Success &&
"Hankel::solve: the Levinson recursion broke down; "
"use LookAheadLevinson on toToeplitz() for diagnostics");
return levinson.solve(b).colwise().reverse();
}
/** \internal Writes the dense representation into \a dst; column \c j is the
* contiguous slice \c h[j..j+m-1]. Invoked through \c dense = hankel; */
template <typename Dest>
void evalTo(Dest& dst) const {
for (Index j = 0; j < cols(); ++j) dst.col(j) = m_h.segment(j, rows());
}
/** \internal Computes \c dst += (*this), see evalTo(). */
template <typename Dest>
void addTo(Dest& dst) const {
for (Index j = 0; j < cols(); ++j) dst.col(j) += m_h.segment(j, rows());
}
/** \internal Computes \c dst -= (*this), see evalTo(). */
template <typename Dest>
void subTo(Dest& dst) const {
for (Index j = 0; j < cols(); ++j) dst.col(j) -= m_h.segment(j, rows());
}
/** \returns the product expression \c (*this) * \a x, evaluated through a fast
* FFT-based matrix-vector product. The expression carries the default product
* tag, so assigning it behaves like any dense product: a temporary resolves
* aliasing between the destination and \a x, and \c .noalias() skips it. */
template <typename Rhs>
Product<Hankel, 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<Hankel, 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.
*
* The FFT path evaluates the circular convolution
* \f[ (Hx)_i = \sum_{j=0}^{n-1} h_{i+j} x_j. \f]
* Reversing \f$x\f$ and rotating the padded symbol by \f$n-1\f$ places these
* entries at the start of the inverse transform.
*
* Non-finite data takes the direct kernels: 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 sequence or cached symbol (which can overflow even for a finite
* sequence) 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 m = rows(), n = cols();
eigen_assert(rhs.rows() == n && "invalid product: dimensions do not match");
const bool small = m == 1 || n == 1 ||
(m <= internal::structured_direct_threshold() && n <= internal::structured_direct_threshold());
if (small || !m_fftUsable) {
directProduct(dst, rhs, alpha);
return;
}
// Reversal preserves which right-hand-side columns need the direct fallback.
internal::structured_fft_apply(dst, m_symbol, m, rhs.colwise().reverse(), alpha,
[&](Index k) { directProductColumn(dst, rhs, k, alpha); });
}
private:
template <typename OtherScalar, int OtherRows, int OtherCols>
friend class Hankel;
/** \internal Builds an operator from the generating sequence and an
* already-known symbol (empty for small operators), skipping the FFT of the
* public constructor. Used by transpose(), conjugate() and adjoint(), whose
* symbols are cheap transformations of the existing one. */
Hankel(const GeneratorType& h, Index rows, Index cols, const ComplexVector& symbol)
: m_rows(rows), m_cols(cols), m_h(h), m_symbol(symbol), m_fftUsable(computeFftUsable()) {}
/** \internal Whether products may take the FFT path: the generating sequence
* and the cached symbol must be finite. The symbol accumulates up to p addends,
* so it can overflow to Inf even for a finite sequence; such operators fall
* back to the direct kernels, which stay exact. */
bool computeFftUsable() const { return m_h.allFinite() && (m_symbol.size() == 0 || m_symbol.allFinite()); }
/** \internal Direct kernel for column \a k of the right-hand side: computes
* \c dst.col(k) += alpha * (*this) * rhs.col(k) without transforms. Serves
* operators below the FFT threshold, single-row and single-column operators
* (whose products cost O(n) directly, however large the operator), 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 m = rows(), n = cols();
// A unit alpha must not multiply: even the identity complex scalar (1,0)
// pollutes an (Inf,0) value with NaN through the 0*Inf cross term.
const bool unitAlpha = alpha == ProductScalar(1);
if (n == 1) {
// The runtime-sized head keeps other fixed-size shapes well-formed.
const ProductScalar xj = unitAlpha ? ProductScalar(rhs.coeff(0, k)) : ProductScalar(alpha * rhs.coeff(0, k));
dst.col(k) += xj * m_h.head(m);
return;
}
if (m == 1) {
const ProductScalar acc = m_h.head(n).cwiseProduct(rhs.col(k)).sum();
dst.coeffRef(0, k) += unitAlpha ? acc : ProductScalar(alpha * acc);
return;
}
if (m <= internal::structured_scalar_threshold() && n <= internal::structured_scalar_threshold()) {
// Tiny sizes: a plain scalar loop beats the segment-based path below, whose
// per-segment setup dominates when segments hold only a few entries.
for (Index i = 0; i < m; ++i) {
ProductScalar acc(0);
for (Index j = 0; j < n; ++j) acc += coeff(i, j) * rhs.coeff(j, k);
dst.coeffRef(i, k) += unitAlpha ? acc : ProductScalar(alpha * acc);
}
return;
}
// Column j is the contiguous, vectorizable slice h[j..j+m-1].
auto dstCol = dst.col(k);
for (Index j = 0; j < n; ++j) {
const ProductScalar xj = unitAlpha ? ProductScalar(rhs.coeff(j, k)) : ProductScalar(alpha * rhs.coeff(j, k));
dstCol += xj * m_h.segment(j, m);
}
}
/** \internal Direct 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 \returns the DFT of the generating sequence, zero-padded to the
* 5-smooth size p >= m+n-1 and rotated left by n-1 so that the convolution
* against a reversed input lands the product in the leading m entries. */
ComplexVector computeSymbol() const {
const Index m = rows(), n = cols();
const Index p = internal::fft_next_good_size(m + n - 1);
ComplexVector embedding = ComplexVector::Zero(p);
embedding.head(m) = m_h.tail(m).template cast<Complex>(); // h[n-1 .. m+n-2]
embedding.tail(n - 1) = m_h.head(n - 1).template cast<Complex>();
if (p == 1) return embedding; // the DFT of a single sample is the identity
ComplexVector symbol(p);
auto&& fft = internal::structured_fft_engine<RealScalar>();
fft.fwd(symbol, embedding, p);
return symbol;
}
/** \internal \returns the symbol of the transposed operator: the embedding
* rotation changes from n-1 to m-1, which by the DFT shift theorem multiplies
* entry f by exp(2 pi i f (m-n) / p). The frequency-shift product f*(m-n) is
* accumulated incrementally modulo p, so the angle stays O(2 pi) at full
* accuracy and no Index overflow can occur. An empty symbol stays empty. */
ComplexVector transposedSymbol() const {
ComplexVector sym = m_symbol;
const Index p = sym.size();
if (p > 0 && rows() != cols()) {
Index s = (rows() - cols()) % p;
if (s < 0) s += p;
Index fs = 0; // f * s mod p
for (Index f = 0; f < p; ++f) {
sym[f] *= std::polar(RealScalar(1), RealScalar(2 * EIGEN_PI) * RealScalar(fs) / RealScalar(p));
fs += s;
if (fs >= p) fs -= p;
}
}
return sym;
}
// Empty (compile-time constant) when the corresponding dimension is fixed; the
// dimensions cannot be recovered from m_h alone, whose length is rows()+cols()-1.
internal::variable_if_dynamic<Index, RowsAtCompileTime> m_rows;
internal::variable_if_dynamic<Index, ColsAtCompileTime> m_cols;
GeneratorType m_h;
ComplexVector m_symbol;
bool m_fftUsable;
};
/** \ingroup StructuredMatrices_Module
* \returns a \ref Hankel operator with first column \a col and last row \a row.
* The compile-time dimensions of the operator are deduced from the generators. */
template <typename ColDerived, typename RowDerived>
Hankel<typename ColDerived::Scalar, ColDerived::SizeAtCompileTime, RowDerived::SizeAtCompileTime> makeHankel(
const MatrixBase<ColDerived>& col, const MatrixBase<RowDerived>& row) {
return Hankel<typename ColDerived::Scalar, ColDerived::SizeAtCompileTime, RowDerived::SizeAtCompileTime>(col, row);
}
namespace internal {
// Single product specialization covering every product tag; see the note in
// Circulant.h.
template <typename Scalar_, int Rows_, int Cols_, typename Rhs, int ProductTag>
struct generic_product_impl<Hankel<Scalar_, Rows_, Cols_>, Rhs, StructuredShape, DenseShape, ProductTag>
: structured_product_impl<Hankel<Scalar_, Rows_, Cols_>, Rhs> {};
} // namespace internal
} // namespace Eigen
#endif // EIGEN_STRUCTURED_HANKEL_H