blob: 9d10bb12baeba1e57f6e09bfd97dd97b085e1a18 [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] 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.
// [2] 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_CAUCHY_H
#define EIGEN_STRUCTURED_CAUCHY_H
// IWYU pragma: private
#include "./InternalHeaderCheck.h"
namespace Eigen {
template <typename Scalar_, int Rows_ = Dynamic, int Cols_ = Dynamic>
class Cauchy;
template <typename Scalar_>
class CauchyLU;
namespace internal {
template <typename Scalar_, int Rows_, int Cols_>
struct traits<Cauchy<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(m+n), negligible against the O(mn) product evaluation.
static constexpr unsigned int Flags = 0;
};
template <typename Scalar_, int Rows_, int Cols_>
struct evaluator_traits<Cauchy<Scalar_, Rows_, Cols_>> {
using Kind = IndexBased;
using Shape = StructuredShape;
};
template <typename Scalar_>
struct traits<CauchyLU<Scalar_>> : traits<Matrix<Scalar_, Dynamic, Dynamic>> {
using XprKind = MatrixXpr;
using StorageKind = SolverStorage;
using StorageIndex = int;
using BaseTraits = traits<Matrix<Scalar_, Dynamic, Dynamic>>;
static constexpr unsigned int Flags = BaseTraits::Flags & RowMajorBit;
static constexpr int CoeffReadCost = Dynamic;
};
} // namespace internal
/** \ingroup StructuredMatrices_Module
* \class Cauchy
* \brief An \c m x \c n Cauchy matrix represented by its two node vectors.
*
* A Cauchy matrix has entry \c (i,j) equal to \f$ 1/(x_i - y_j) \f$; the class
* stores only the \c m + \c n nodes. Products are evaluated directly at O(mn)
* operations -- the same cost as a dense product, but with O(m+n) storage and
* without ever forming the matrix. (Fast approximate products via multipole
* expansions are out of scope.)
*
* The class is closed under transposition: \f$ C(x,y)^T = C(-y,-x) \f$, and the
* determinant of a square Cauchy matrix has the classical closed form
* \f$ \prod_{i<j}(x_j-x_i)(y_i-y_j) \big/ \prod_{i,j}(x_i-y_j) \f$.
*
* Because \c operator* returns an Eigen product expression, a \c Cauchy 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 GMRES<Cauchy<double>,IdentityPreconditioner>): the default preconditioners
* read individual coefficients through \c col() or \c InnerIterator, which the
* structured operators do not expose.
*
* Square systems are solved in O(n^2) by \ref CauchyLU, a partially pivoted LU
* factorization computed through the displacement structure
* (Gohberg-Kailath-Olshevsky): unlike the Toeplitz/Levinson world, Cauchy
* structure survives row permutations, which is what makes fast \em pivoted
* factorization possible. The Hilbert matrix is the Cauchy matrix with
* \c x_i = i+1, \c y_j = -j.
*
* All nodes \c x_i must differ from all nodes \c y_j (entries would be infinite
* otherwise); this is not checked.
*
* \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 CauchyLU, makeCauchy()
*/
template <typename Scalar_, int Rows_, int Cols_>
class Cauchy : public EigenBase<Cauchy<Scalar_, Rows_, Cols_>> {
public:
using Scalar = Scalar_;
using RealScalar = typename NumTraits<Scalar>::Real;
using StorageIndex = int;
using RowNodeVector = Matrix<Scalar, Rows_, 1>;
using ColNodeVector = Matrix<Scalar, Cols_, 1>;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW_IF(bool(RowNodeVector::NeedsToAlign || ColNodeVector::NeedsToAlign))
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 Cauchy>'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 Cauchy matrix from the row nodes \a x and the column nodes \a y:
* entry \c (i,j) is \c 1/(x[i] - y[j]). */
template <typename XDerived, typename YDerived>
Cauchy(const MatrixBase<XDerived>& x, const MatrixBase<YDerived>& y) : m_x(x), m_y(y) {
EIGEN_STATIC_ASSERT_VECTOR_ONLY(XDerived)
EIGEN_STATIC_ASSERT_VECTOR_ONLY(YDerived)
eigen_assert(m_x.size() > 0 && m_y.size() > 0 && "Cauchy node vectors must be non-empty");
}
EIGEN_DEVICE_FUNC Index rows() const { return m_x.size(); }
EIGEN_DEVICE_FUNC Index cols() const { return m_y.size(); }
/** \returns the row node vector \c x. */
const RowNodeVector& rowNodes() const { return m_x; }
/** \returns the column node vector \c y. */
const ColNodeVector& colNodes() const { return m_y; }
/** \returns the coefficient at row \a row and column \a col. */
Scalar coeff(Index row, Index col) const { return Scalar(1) / (m_x.coeff(row) - m_y.coeff(col)); }
/** \returns the transpose of \c *this, itself a Cauchy operator:
* \f$ C(x,y)^T = C(-y,-x) \f$. */
Cauchy<Scalar, Cols_, Rows_> transpose() const { return Cauchy<Scalar, Cols_, Rows_>(-m_y, -m_x); }
/** \returns the complex conjugate of \c *this, itself a Cauchy operator (the
* one with conjugated nodes). */
Cauchy conjugate() const { return Cauchy(m_x.conjugate(), m_y.conjugate()); }
/** \returns the adjoint of \c *this, itself a Cauchy operator:
* \f$ C(x,y)^H = C(-\bar y, -\bar x) \f$. */
Cauchy<Scalar, Cols_, Rows_> adjoint() const {
return Cauchy<Scalar, Cols_, Rows_>(-m_y.conjugate(), -m_x.conjugate());
}
/** \returns the determinant of a \b square Cauchy matrix through the classical
* closed form \f$ \prod_{i<j}(x_j-x_i)(y_i-y_j) \big/ \prod_{i,j}(x_i-y_j) \f$,
* in O(n^2) operations. All factors enter a single accumulation kept in the
* balanced form \c m * 2^e (the split fraction/exponent determinant
* convention of LINPACK's xGEDI [1]) -- every factor and the running value
* are renormalized to unit magnitude with the power of two tracked separately
* (exact frexp/ldexp rescaling [2]), numerator factors multiplied in and
* denominator factors divided out -- so no partial product can overflow or
* underflow when the determinant itself is representable. Zero factors
* (coincident \c x or \c y nodes) and non-finite factors propagate exactly;
* a node difference that overflows makes its factor infinite, which turns the
* determinant into NaN when it meets a zero or another infinite factor. */
Scalar determinant() const {
eigen_assert(rows() == cols() && "Cauchy::determinant requires a square matrix");
const Index n = rows();
Scalar det(1);
Index exponent = 0;
for (Index j = 1; j < n; ++j)
for (Index i = 0; i < j; ++i) {
const Scalar xDiff = internal::structured_balance(Scalar(m_x.coeff(j) - m_x.coeff(i)), exponent);
det = internal::structured_balance(Scalar(det * xDiff), exponent);
const Scalar yDiff = internal::structured_balance(Scalar(m_y.coeff(i) - m_y.coeff(j)), exponent);
det = internal::structured_balance(Scalar(det * yDiff), exponent);
}
for (Index j = 0; j < n; ++j)
for (Index i = 0; i < n; ++i) {
Index denomExponent = 0;
const Scalar d = internal::structured_balance(Scalar(m_x.coeff(i) - m_y.coeff(j)), denomExponent);
exponent -= denomExponent;
det = internal::structured_balance(Scalar(det / d), exponent);
}
return internal::structured_ldexp_clamped(det, exponent);
}
/** \internal Writes the dense representation into \a dst, one vectorized
* column at a time. Invoked through \c dense = cauchy; */
template <typename Dest>
void evalTo(Dest& dst) const {
applyAssignment(dst, internal::assign_op<typename Dest::Scalar, Scalar>());
}
/** \internal Computes \c dst += (*this), see evalTo(). */
template <typename Dest>
void addTo(Dest& dst) const {
applyAssignment(dst, internal::add_assign_op<typename Dest::Scalar, Scalar>());
}
/** \internal Computes \c dst -= (*this), see evalTo(). */
template <typename Dest>
void subTo(Dest& dst) const {
applyAssignment(dst, internal::sub_assign_op<typename Dest::Scalar, Scalar>());
}
/** \returns the product expression \c (*this) * \a v, evaluated directly at
* O(mn) operations per right-hand side and at most O(m) extra storage. The
* expression carries the default product tag, so assigning it behaves like any
* dense product: a temporary resolves aliasing between the destination and
* \a v, and \c .noalias() skips it. */
template <typename Rhs>
Product<Cauchy, Rhs> operator*(const MatrixBase<Rhs>& v) const {
EIGEN_STATIC_ASSERT(ColsAtCompileTime == Dynamic || Rhs::RowsAtCompileTime == Dynamic ||
int(ColsAtCompileTime) == int(Rhs::RowsAtCompileTime),
INVALID_MATRIX_PRODUCT)
eigen_assert(v.rows() == cols() && "invalid product: dimensions do not match");
return Product<Cauchy, Rhs>(*this, v.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. Each
* column of the operator is formed on the fly, vectorized over the rows; for
* several right-hand sides of the operator's own scalar type it is formed once
* per block of rows and reused across them, with identical results. */
template <typename Dest, typename Rhs, typename ProductScalar>
void addProduct(Dest& dst, const Rhs& rhs, const ProductScalar& alpha) const {
const Index n = cols();
eigen_assert(rhs.rows() == n && "invalid product: dimensions do not match");
// 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);
const auto weight = [&](Index j, Index k) {
return unitAlpha ? ProductScalar(rhs.coeff(j, k)) : ProductScalar(alpha * rhs.coeff(j, k));
};
// Reuse saves (r-1)/r of the divisions. A real operator applied to a complex
// right-hand side gains nothing: the complex updates dominate its real divisions.
if (rhs.cols() > 1 && std::is_same<ProductScalar, Scalar>::value) {
// Row blocks keep the r destination columns of a block in cache across all
// n reciprocal columns; the floor keeps the per-column updates long.
constexpr Index kBlockBytes = Index(1) << 19;
constexpr Index kMinBlockRows = 1024;
const Index m = rows(), r = rhs.cols();
const Index blockRows = numext::mini(m, numext::maxi(kMinBlockRows, kBlockBytes / (r * Index(sizeof(Scalar)))));
Matrix<Scalar, Dynamic, 1, ColMajor, Rows_> reciprocals(blockRows);
for (Index i = 0; i < m; i += blockRows) {
const Index b = numext::mini(blockRows, m - i);
for (Index j = 0; j < n; ++j) {
reciprocals.head(b) = (m_x.segment(i, b).array() - m_y.coeff(j)).inverse();
for (Index k = 0; k < r; ++k) dst.col(k).segment(i, b) += weight(j, k) * reciprocals.head(b);
}
}
return;
}
for (Index k = 0; k < rhs.cols(); ++k)
for (Index j = 0; j < n; ++j) dst.col(k) += weight(j, k) * (m_x.array() - m_y.coeff(j)).inverse().matrix();
}
private:
template <typename Dest, typename Assignment>
void applyAssignment(Dest& dst, const Assignment& assignment) const {
for (Index j = 0; j < cols(); ++j) {
auto dstColumn = dst.col(j);
internal::call_assignment_no_alias(dstColumn, (m_x.array() - m_y.coeff(j)).inverse().matrix(), assignment);
}
}
RowNodeVector m_x;
ColNodeVector m_y;
};
/** \ingroup StructuredMatrices_Module
* \returns a \ref Cauchy operator with row nodes \a x and column nodes \a y. The
* compile-time dimensions of the operator are deduced from the node vectors. */
template <typename XDerived, typename YDerived>
Cauchy<typename XDerived::Scalar, XDerived::SizeAtCompileTime, YDerived::SizeAtCompileTime> makeCauchy(
const MatrixBase<XDerived>& x, const MatrixBase<YDerived>& y) {
return Cauchy<typename XDerived::Scalar, XDerived::SizeAtCompileTime, YDerived::SizeAtCompileTime>(x, y);
}
/** \ingroup StructuredMatrices_Module
* \class CauchyLU
* \brief Partially pivoted O(n^2) LU solver for square Cauchy systems
* (Gohberg-Kailath-Olshevsky).
*
* A Cauchy matrix satisfies the displacement equation
* \f$ D_x C - C D_y = \mathbf{1}\mathbf{1}^T \f$, and -- crucially -- this
* structure survives row permutations. The GKO algorithm exploits it to compute
* the row-pivoted factorization \c P*C = L*U in O(n^2) operations: at each step
* the current column of the Schur complement is generated from O(n) data, the
* largest entry is chosen as pivot, and the Schur complement stays Cauchy-like
* with updated generators. This gives Cauchy systems what the Levinson world
* lacks: genuine partial pivoting at fast-algorithm cost.
*
* Usage follows the usual decomposition style, including the transposed and
* adjoint solves of \c SolverBase (from the same factorization):
* \code
* CauchyLU<double> lu(C); // or lu.compute(C);
* VectorXd u = lu.solve(b); // solve C * u = b
* VectorXd v = lu.transpose().solve(b); // solve C^T * v = b
* VectorXd w = lu.adjoint().solve(b); // solve C^H * w = b
* \endcode
*
* The factors are stored densely (O(n^2) memory, like the dense LU
* decompositions).
*
* References: I. Gohberg, T. Kailath, V. Olshevsky, "Fast Gaussian elimination
* with partial pivoting for matrices with displacement structure," Math. Comp.
* 64 (1995).
*
* \tparam Scalar_ the scalar type, real or complex.
*
* \sa class Cauchy
*/
template <typename Scalar_>
class CauchyLU : public SolverBase<CauchyLU<Scalar_>> {
public:
using Base = SolverBase<CauchyLU>;
friend class SolverBase<CauchyLU>;
EIGEN_GENERIC_PUBLIC_INTERFACE(CauchyLU)
using DenseMatrix = Matrix<Scalar, Dynamic, Dynamic>;
using DenseVector = Matrix<Scalar, Dynamic, 1>;
/** Default constructor; call \ref compute before \ref solve. */
CauchyLU() : m_isInitialized(false), m_info(InvalidInput) {}
/** Constructs and factorizes from the square Cauchy matrix \a C. */
template <int Rows_, int Cols_>
explicit CauchyLU(const Cauchy<Scalar, Rows_, Cols_>& C) : m_isInitialized(false), m_info(InvalidInput) {
compute(C);
}
/** Factorizes the square Cauchy matrix \a C as \c P*C = L*U by the GKO
* recursion with partial pivoting. The Schur complement retains the form
* \f[ S_{ij}=\frac{a_i b_j}{x_i-y_j}, \qquad
* a_i\leftarrow a_i\frac{x_i-x_k}{x_i-y_k},\quad
* b_j\leftarrow b_j\frac{y_j-y_k}{y_j-x_k}. \f]
* This follows from the rank-one displacement
* \f$D_x C-C D_y=\mathbf{1}\mathbf{1}^T\f$. \sa solve */
template <int Rows_, int Cols_>
CauchyLU& compute(const Cauchy<Scalar, Rows_, Cols_>& C) {
eigen_assert(C.rows() == C.cols() && "CauchyLU requires a square Cauchy matrix");
const Index n = C.rows();
DenseVector x = C.rowNodes();
const DenseVector y = C.colNodes();
DenseVector a = DenseVector::Ones(n);
DenseVector b = DenseVector::Ones(n);
m_lu.resize(n, n);
m_perm.resize(static_cast<std::size_t>(n));
m_info = Success;
for (Index k = 0; k < n; ++k) {
Index piv = k;
RealScalar best(-1);
for (Index i = k; i < n; ++i) {
m_lu(i, k) = a[i] * b[k] / (x[i] - y[k]);
const RealScalar mag = numext::abs(m_lu(i, k));
if ((numext::isnan)(mag) || mag > best) {
best = mag;
piv = i;
}
}
if (piv != k) {
m_lu.row(piv).head(k + 1).swap(m_lu.row(k).head(k + 1));
std::swap(x[piv], x[k]);
std::swap(a[piv], a[k]);
}
m_perm[static_cast<std::size_t>(k)] = piv;
const Scalar pivot = m_lu(k, k);
if (pivot == Scalar(0) || !(numext::isfinite)(pivot)) {
m_info = NumericalIssue;
m_lu.row(k).tail(n - k - 1).setZero();
m_lu.col(k).tail(n - k - 1).setZero();
continue;
}
m_lu.col(k).tail(n - k - 1) /= pivot;
for (Index j = k + 1; j < n; ++j) {
m_lu(k, j) = a[k] * b[j] / (x[k] - y[j]);
if (!(numext::isfinite)(m_lu(k, j))) m_info = NumericalIssue;
}
for (Index i = k + 1; i < n; ++i) a[i] *= (x[i] - x[k]) / (x[i] - y[k]);
for (Index j = k + 1; j < n; ++j) b[j] *= (y[j] - y[k]) / (y[j] - x[k]);
}
m_isInitialized = true;
return *this;
}
Index rows() const noexcept { return m_lu.rows(); }
Index cols() const noexcept { return m_lu.cols(); }
/** \returns \c Success, or \c NumericalIssue when the factorization encounters
* a zero or non-finite entry that prevents a usable factorization. */
ComputationInfo info() const {
eigen_assert(m_isInitialized && "CauchyLU is not initialized.");
return m_info;
}
#ifdef EIGEN_PARSED_BY_DOXYGEN
/** \returns the solution \c u of \c C*u = \a b, as a lazily evaluated
* expression. Supports multiple right-hand sides. The transposed and adjoint
* systems reuse the same factorization through \c transpose().solve(b) and
* \c adjoint().solve(b). \pre \ref compute has been called. */
template <typename Rhs>
inline const Solve<CauchyLU, Rhs> solve(const MatrixBase<Rhs>& b) const;
#endif
#ifndef EIGEN_PARSED_BY_DOXYGEN
/** \internal P*C = L*U, so C*u = b becomes L*U*u = P*b. */
template <typename RhsType, typename DstType>
void _solve_impl(const RhsType& rhs, DstType& dst) const {
dst = rhs;
for (Index k = 0; k < rows(); ++k) {
const Index piv = m_perm[static_cast<std::size_t>(k)];
if (piv != k) dst.row(k).swap(dst.row(piv));
}
m_lu.template triangularView<UnitLower>().solveInPlace(dst);
m_lu.template triangularView<Upper>().solveInPlace(dst);
}
/** \internal C^T = U^T L^T P, so C^T*w = b becomes U^T (L^T (P w)) = b: solve
* the transposed triangles, then undo the transpositions in reverse order;
* conjugated on the way in and out for the adjoint. */
template <bool Conjugate, typename RhsType, typename DstType>
void _solve_impl_transposed(const RhsType& rhs, DstType& dst) const {
dst = rhs.template conjugateIf<Conjugate>();
m_lu.template triangularView<Upper>().transpose().solveInPlace(dst);
m_lu.template triangularView<UnitLower>().transpose().solveInPlace(dst);
for (Index k = rows() - 1; k >= 0; --k) {
const Index piv = m_perm[static_cast<std::size_t>(k)];
if (piv != k) dst.row(k).swap(dst.row(piv));
}
if (Conjugate) dst = dst.conjugate().eval();
}
#endif
private:
DenseMatrix m_lu;
std::vector<Index> m_perm; // transposition applied at each elimination step
bool m_isInitialized;
ComputationInfo m_info;
};
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<Cauchy<Scalar_, Rows_, Cols_>, Rhs, StructuredShape, DenseShape, ProductTag>
: structured_product_impl<Cauchy<Scalar_, Rows_, Cols_>, Rhs> {};
} // namespace internal
} // namespace Eigen
#endif // EIGEN_STRUCTURED_CAUCHY_H