blob: 5238f58b6e48f41aacfacac50ff9bce365f6c6d7 [file]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
// Least-squares polynomial approximation in the monomial basis. The normal
// equations of the continuous problem, min \int_0^1 (f - sum_j c_j x^j)^2 dx,
// have the Hilbert matrix H_ij = 1/(i + j + 1), the Cauchy matrix with nodes
// x_i = i + 1 and y_j = -j, and the moments m_i = \int_0^1 x^i f dx on the right.
// CauchyLU solves them in O(n^2), and Cauchy::determinant evaluates det H in
// closed form where dense LU, at kappa(H_n) ~ e^{3.5 n}, loses digits. The
// coefficients inherit the conditioning, the fitted polynomial much less: the
// coefficient error d = H^{-1} r of a residual r = H c - m perturbs the fit by
// (d^T H d)^{1/2} = (r^T H^{-1} r)^{1/2} in L^2, so the fit loses at most about
// sqrt(kappa) eps where the coefficients can lose kappa eps. The discrete
// least-squares fit -- a dense QR of the rectangular Vandermonde matrix of 200
// sample points -- is the reference; Vandermonde products (Horner's rule)
// evaluate both fits.
#include <contrib/Eigen/StructuredMatrices>
#include <cmath>
#include <iomanip>
#include <iostream>
using namespace Eigen;
int main() {
const double e = std::exp(1.0);
const ArrayXd samples = ArrayXd::LinSpaced(200, 0, 1), fine = ArrayXd::LinSpaced(1001, 0, 1);
std::cout << " n det H: Cauchy dense LU exact fit error: Hilbert QR\n";
for (int n : {4, 6, 8, 10}) {
const auto H = makeCauchy(VectorXd::LinSpaced(n, 1, n), -VectorXd::LinSpaced(n, 0, n - 1));
// m_i = e - i m_{i-1}, run backwards from m_40 ~ e / 41: the forward recurrence amplifies errors by i!.
VectorXd moments(n);
double mi = e / 41;
for (int i = 40; i > 0; --i) {
mi = (e - mi) / i;
if (i <= n) moments[i - 1] = mi;
}
const VectorXd c = CauchyLU<double>(H).solve(moments);
const MatrixXd V = makeVandermonde(samples.matrix(), n); // 200 x n
const VectorXd cq = V.colPivHouseholderQr().solve(samples.exp().matrix());
// det H_n = c_n^4 / c_{2n} with c_n = prod_{i<n} i!
double cn = 1, c2n = 1, factorial = 1;
for (int i = 1; i < 2 * n; ++i) {
factorial *= i;
if (i < n) cn *= factorial;
c2n *= factorial;
}
const auto E = makeVandermonde(fine.matrix(), n);
std::cout << std::setw(2) << n << std::setprecision(8) << std::setw(16) << H.determinant() << std::setw(15)
<< MatrixXd(H).determinant() << std::setw(15) << std::pow(cn, 4) / c2n << std::setprecision(3)
<< std::setw(16) << (E * c - fine.exp().matrix()).cwiseAbs().maxCoeff() << std::setw(13)
<< (E * cq - fine.exp().matrix()).cwiseAbs().maxCoeff() << "\n";
}
}