| // 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 |
| |
| #include "main.h" |
| |
| #include <unsupported/Eigen/StructuredMatrices> |
| |
| using namespace Eigen; |
| |
| // Reference dense Vandermonde built entry-wise from the nodes. |
| template <typename Scalar> |
| Matrix<Scalar, Dynamic, Dynamic> reference_vandermonde(const Matrix<Scalar, Dynamic, 1>& x, Index n) { |
| const Index m = x.size(); |
| Matrix<Scalar, Dynamic, Dynamic> dense(m, n); |
| for (Index i = 0; i < m; ++i) { |
| Scalar p(1); |
| for (Index j = 0; j < n; ++j) { |
| dense(i, j) = p; |
| p *= x[i]; |
| } |
| } |
| return dense; |
| } |
| |
| template <typename Scalar> |
| void test_vandermonde_product(Index m, Index n) { |
| typedef Matrix<Scalar, Dynamic, 1> Vec; |
| typedef Matrix<Scalar, Dynamic, Dynamic> Mat; |
| |
| Vec x = Vec::Random(m); |
| Vandermonde<Scalar> V(x, n); |
| Mat dense = reference_vandermonde<Scalar>(x, n); |
| |
| Mat Vd = V; |
| VERIFY_IS_APPROX(Vd, dense); |
| for (Index t = 0; t < 5; ++t) { |
| Index i = internal::random<Index>(0, m - 1), j = internal::random<Index>(0, n - 1); |
| VERIFY_IS_APPROX(V.coeff(i, j), dense(i, j)); |
| } |
| |
| Vec a = Vec::Random(n); |
| VERIFY_IS_APPROX((V * a).eval(), (dense * a).eval()); |
| |
| Mat A = Mat::Random(n, 3); |
| VERIFY_IS_APPROX((V * A).eval(), (dense * A).eval()); |
| |
| // Accumulation form exercised by the iterative solvers. |
| Vec y = Vec::Random(m); |
| Vec y0 = y; |
| y.noalias() += V * a; |
| VERIFY_IS_APPROX(y, (y0 + dense * a).eval()); |
| } |
| |
| // Well-conditioned interpolation: Chebyshev-like nodes in [-1,1] keep the |
| // conditioning around (1+sqrt(2))^n, so for moderate n a modest tolerance wide |
| // of roundoff verifies the primal and dual solves against the exact solution. |
| template <typename Scalar> |
| void test_bjorck_pereyra(Index n) { |
| typedef typename NumTraits<Scalar>::Real RealScalar; |
| typedef Matrix<Scalar, Dynamic, 1> Vec; |
| typedef Matrix<Scalar, Dynamic, Dynamic> Mat; |
| |
| Vec x(n); |
| for (Index i = 0; i < n; ++i) |
| x[i] = Scalar(RealScalar(std::cos(double(EIGEN_PI) * double(2 * i + 1) / double(2 * n)))); // Chebyshev nodes |
| Vandermonde<Scalar> V(x); |
| Mat dense = reference_vandermonde<Scalar>(x, n); |
| |
| BjorckPereyra<Scalar> bp(V); |
| VERIFY(bp.info() == Success); |
| |
| // Forward-error bound for the (moderately conditioned) Chebyshev-node system. |
| const RealScalar tol = RealScalar(5e7) * NumTraits<RealScalar>::epsilon(); // ~1e-8 in double |
| |
| // Primal: interpolate values of a known polynomial and recover its coefficients. |
| Vec aTrue = Vec::Random(n); |
| Vec f = dense * aTrue; |
| Vec a = bp.solve(f); |
| VERIFY((a - aTrue).norm() <= tol * aTrue.norm()); |
| |
| // Dual (moment) system through the SolverBase transpose idiom. |
| Vec wTrue = Vec::Random(n); |
| Vec b = dense.transpose() * wTrue; |
| Vec w = bp.transpose().solve(b); |
| VERIFY((w - wTrue).norm() <= tol * wTrue.norm()); |
| |
| // Adjoint solve. |
| Vec c = dense.adjoint() * wTrue; |
| Vec u = bp.adjoint().solve(c); |
| VERIFY((u - wTrue).norm() <= tol * wTrue.norm()); |
| |
| // Multiple right-hand sides. |
| Mat F = Mat::Random(n, 3); |
| Mat A = bp.solve(F); |
| VERIFY_IS_APPROX((dense * A).eval(), F); |
| } |
| |
| // The n-th roots of unity make V/sqrt(n) exactly unitary. Verify against the |
| // analytic inverse a = V^H f / n to catch catastrophic Newton-basis growth. |
| template <typename RealScalar> |
| void test_bjorck_pereyra_roots_of_unity(Index n) { |
| typedef std::complex<RealScalar> Scalar; |
| typedef Matrix<Scalar, Dynamic, 1> Vec; |
| |
| Vec x(n); |
| for (Index i = 0; i < n; ++i) |
| x[i] = std::polar(RealScalar(1), RealScalar(2 * EIGEN_PI) * RealScalar(i) / RealScalar(n)); |
| Vandermonde<Scalar> V(x); |
| |
| BjorckPereyra<Scalar> bp(V); |
| VERIFY(bp.info() == Success); |
| |
| // The first cardinal polynomial has every coefficient equal to 1/n. This |
| // deterministic case exposes the severe Newton-basis growth of cyclic node |
| // order while remaining analytically checkable. |
| Vec f = Vec::Zero(n); |
| f[0] = Scalar(1); |
| Vec a = bp.solve(f); |
| Vec aRef = Vec::Constant(n, Scalar(RealScalar(1) / RealScalar(n))); |
| // The recurrence performs O(n^2) complex operations; the fixed factor also |
| // covers growth in its intermediate Newton coefficients. |
| const RealScalar tol = RealScalar(128) * RealScalar(n) * RealScalar(n) * NumTraits<RealScalar>::epsilon(); |
| VERIFY((a - aRef).norm() <= tol * aRef.norm()); |
| } |
| |
| // Eigen::half has explicit conversion from the wider type returned by the |
| // standard scaling functions. Instantiate both product and solver paths so the |
| // range-protection helpers retain support for narrow floating-point scalars. |
| void test_vandermonde_half() { |
| typedef Matrix<half, 2, 1> Vec; |
| Vec nodes, coefficients; |
| nodes << half(0), half(2); |
| coefficients << half(3), half(2); |
| |
| Vandermonde<half, 2, 2> V(nodes); |
| Vec values = V * coefficients; |
| VERIFY_IS_EQUAL(float(values[0]), 3.0f); |
| VERIFY_IS_EQUAL(float(values[1]), 7.0f); |
| |
| BjorckPereyra<half> bp(V); |
| Vec recovered = bp.solve(values); |
| VERIFY_IS_EQUAL(float(recovered[0]), 3.0f); |
| VERIFY_IS_EQUAL(float(recovered[1]), 2.0f); |
| } |
| |
| // Björck-Pereyra's celebrated accuracy property (Higham, ASNA ch. 22): for |
| // monotone nodes and an alternating-sign right-hand side the forward error is |
| // tiny even though the matrix conditioning is astronomical. Deterministic. |
| void test_bjorck_pereyra_higham() { |
| typedef Matrix<double, Dynamic, 1> Vec; |
| |
| const Index n = 20; |
| Vec x(n); |
| for (Index i = 0; i < n; ++i) x[i] = double(i + 1) / 32.0; // exactly represented, monotone in (0,1) |
| Vandermonde<double> V(x); |
| |
| // f_i = (-1)^i: alternating signs. |
| Vec f(n); |
| for (Index i = 0; i < n; ++i) f[i] = (i % 2 == 0) ? 1.0 : -1.0; |
| BjorckPereyra<double> bp(V); |
| Vec a = bp.solve(f); |
| |
| // Independent long-double coefficient reference. For equally spaced nodes |
| // with h=1/32, the Newton weights are Delta^k f_0 / (k! h^k) = (-64)^k/k!. |
| // Accumulate weight_k * product_{j<k}(z-x_j) directly in the monomial basis. |
| Matrix<long double, Dynamic, 1> aRef = Matrix<long double, Dynamic, 1>::Zero(n); |
| Matrix<long double, Dynamic, 1> basis = Matrix<long double, Dynamic, 1>::Zero(n); |
| basis[0] = 1.0L; |
| long double weight = 1.0L; |
| for (Index k = 0; k < n; ++k) { |
| aRef += weight * basis; |
| if (k + 1 == n) break; |
| const long double node = static_cast<long double>(x[k]); |
| basis[k + 1] = basis[k]; |
| for (Index j = k; j > 0; --j) basis[j] = basis[j - 1] - node * basis[j]; |
| basis[0] *= -node; |
| weight *= -64.0L / static_cast<long double>(k + 1); |
| } |
| |
| const Vec roundedRef = aRef.cast<double>(); |
| const double maxRelativeError = ((a - roundedRef).cwiseAbs().array() / roundedRef.cwiseAbs().array()).maxCoeff(); |
| const double coefficientTol = 8.0 * double(n) * double(n) * NumTraits<double>::epsilon(); |
| VERIFY(maxRelativeError <= coefficientTol); |
| } |
| |
| // The transpose and adjoint recurrences must be distinguished for genuinely |
| // complex nodes. A rotated root-of-unity grid keeps both systems well |
| // conditioned while avoiding the unrotated grid's extra symmetries. |
| void test_bjorck_pereyra_complex_transpose_adjoint(Index n) { |
| typedef std::complex<double> Scalar; |
| typedef Matrix<double, Dynamic, 1> RealVec; |
| typedef Matrix<Scalar, Dynamic, 1> Vec; |
| typedef Matrix<Scalar, Dynamic, Dynamic> Mat; |
| |
| Vec x(n); |
| for (Index i = 0; i < n; ++i) x[i] = std::polar(1.0, double(2 * EIGEN_PI) * (double(i) + 0.25) / double(n)); |
| Vandermonde<Scalar> V(x); |
| Mat dense = reference_vandermonde<Scalar>(x, n); |
| BjorckPereyra<Scalar> bp(V); |
| |
| Vec expected = Vec::Random(n); |
| Vec transposed = bp.transpose().solve(dense.transpose() * expected); |
| Vec adjoint = bp.adjoint().solve(dense.adjoint() * expected); |
| const double tol = 256.0 * double(n) * double(n) * NumTraits<double>::epsilon(); |
| const double scale = numext::maxi(1.0, expected.norm()); |
| VERIFY((transposed - expected).norm() <= tol * scale); |
| VERIFY((adjoint - expected).norm() <= tol * scale); |
| |
| // A real right-hand side is compatible with complex nodes and promotes the |
| // solve result to complex. The rotated Fourier matrix has V^H V = n I, so |
| // all three analytic inverse forms are available without another solver. |
| RealVec realRhs = RealVec::Random(n); |
| Vec primalMixed = bp.solve(realRhs); |
| Vec transposedMixed = bp.transpose().solve(realRhs); |
| Vec adjointMixed = bp.adjoint().solve(realRhs); |
| Vec primalRef = dense.adjoint() * realRhs / Scalar(double(n)); |
| Vec transposedRef = dense.conjugate() * realRhs / Scalar(double(n)); |
| Vec adjointRef = dense * realRhs / Scalar(double(n)); |
| const double mixedScale = numext::maxi(1.0, realRhs.norm()); |
| VERIFY((primalMixed - primalRef).norm() <= tol * mixedScale); |
| VERIFY((transposedMixed - transposedRef).norm() <= tol * mixedScale); |
| VERIFY((adjointMixed - adjointRef).norm() <= tol * mixedScale); |
| |
| // The reverse promotion direction remains valid as well: real nodes with a |
| // complex right-hand side produce complex work values and output. |
| RealVec realNodes(2); |
| realNodes << -1.0, 1.0; |
| BjorckPereyra<double> realBp{Vandermonde<double>(realNodes)}; |
| Vec complexRhs(2), complexExpected(2); |
| complexRhs << Scalar(1.0, 1.0), Scalar(3.0, -2.0); |
| complexExpected << Scalar(2.0, -0.5), Scalar(1.0, -1.5); |
| Vec realNodeMixed = realBp.solve(complexRhs); |
| VERIFY((realNodeMixed - complexExpected).norm() <= 4.0 * NumTraits<double>::epsilon() * complexExpected.norm()); |
| |
| // Materializing an aliased RHS must precede the internal Leja permutation. |
| Vec aliased = Vec::Random(n); |
| const Vec aliasInput = (aliased + Vec::Ones(n)).eval(); |
| const Vec aliasReference = bp.solve(aliasInput); |
| aliased = bp.solve(aliased + Vec::Ones(n)); |
| VERIFY((aliased - aliasReference).norm() <= tol * numext::maxi(1.0, aliasReference.norm())); |
| } |
| |
| // Repeated nodes make the matrix exactly singular, while non-finite nodes make |
| // the solver input invalid. Both must be reported before solve evaluation. |
| void test_bjorck_pereyra_singular() { |
| typedef Matrix<double, Dynamic, 1> Vec; |
| Vec x(5); |
| x << 0.1, 0.7, 0.3, 0.7, 0.9; |
| Vandermonde<double> V(x); |
| BjorckPereyra<double> bp(V); |
| VERIFY(bp.info() == NumericalIssue); |
| |
| x[3] = std::numeric_limits<double>::quiet_NaN(); |
| bp.compute(Vandermonde<double>(x)); |
| VERIFY(bp.info() == InvalidInput); |
| } |
| |
| // Aliased products: the products carry the default product tag, so assignment |
| // materializes a temporary exactly like a dense product and x = V * x, |
| // x += V * x must come out as if the right-hand side had been copied first. |
| template <typename Scalar> |
| void test_vandermonde_aliased_product(Index n) { |
| typedef Matrix<Scalar, Dynamic, 1> Vec; |
| typedef Matrix<Scalar, Dynamic, Dynamic> Mat; |
| |
| Vec x = Vec::Random(n); |
| Vandermonde<Scalar> V(x); // square, so x = V * x type-checks |
| Mat dense = reference_vandermonde<Scalar>(x, n); |
| |
| Vec b = Vec::Random(n); |
| Vec y = b; |
| y = V * y; |
| VERIFY_IS_APPROX(y, (dense * b).eval()); |
| |
| y = b; |
| y += V * y; |
| VERIFY_IS_APPROX(y, (b + dense * b).eval()); |
| |
| y = b; |
| y -= V * y; |
| VERIFY_IS_APPROX(y, (b - dense * b).eval()); |
| |
| Mat B = Mat::Random(n, 3); |
| Mat Y = B; |
| Y = V * Y; |
| VERIFY_IS_APPROX(Y, (dense * B).eval()); |
| } |
| |
| // Aliasing beyond the same-object case: the default-product temporary must also |
| // resolve right-hand-side expressions that reference the destination, overlapping |
| // views of one buffer, and rectangular self-assignments where the destination is |
| // resized by the assignment. |
| template <typename Scalar> |
| void test_vandermonde_aliased_expression(Index n) { |
| typedef Matrix<Scalar, Dynamic, 1> Vec; |
| typedef Matrix<Scalar, Dynamic, Dynamic> Mat; |
| |
| Vec nodes = Vec::Random(n); |
| Vandermonde<Scalar> V(nodes); // square |
| Mat dense = reference_vandermonde<Scalar>(nodes, n); |
| |
| // Right-hand-side expression referencing the destination. |
| Vec x = Vec::Random(n), x0 = x; |
| x = V * (x + Vec::Ones(n)); |
| VERIFY_IS_APPROX(x, (dense * (x0 + Vec::Ones(n))).eval()); |
| |
| // Overlapping (shifted) segments of one buffer. |
| Vec buf = Vec::Random(n + 1); |
| Vec expected = dense * buf.tail(n); |
| buf.head(n) = V * buf.tail(n); |
| VERIFY_IS_APPROX(buf.head(n).eval(), expected); |
| |
| // Rectangular self-assignment (the reviewer's 5x4 case): x = V * x resizes |
| // the destination from 4 to 5, so the product must be captured before the |
| // destination storage is touched. |
| Vec tallNodes = Vec::Random(5); |
| Vandermonde<Scalar> Vt(tallNodes, 4); |
| Mat denseT = reference_vandermonde<Scalar>(tallNodes, 4); |
| Vec z = Vec::Random(4), z0 = z; |
| z = Vt * z; |
| VERIFY_IS_EQUAL(z.size(), 5); |
| VERIFY_IS_APPROX(z, (denseT * z0).eval()); |
| } |
| |
| // A delayed-evaluated product expression must keep its structured factor alive: |
| // the makeVandermonde() factory returns an owning temporary, so Product has to |
| // nest the operator by value (no NestByRefBit) or `expr` dangles. |
| template <typename Scalar> |
| void test_vandermonde_delayed_product(Index m, Index n) { |
| typedef Matrix<Scalar, Dynamic, 1> Vec; |
| typedef Matrix<Scalar, Dynamic, Dynamic> Mat; |
| |
| STATIC_CHECK(!std::is_reference<typename internal::ref_selector<Vandermonde<Scalar>>::type>::value); |
| |
| Vec x = Vec::Random(m), a = Vec::Random(n); |
| Mat dense = reference_vandermonde<Scalar>(x, n); |
| |
| auto expr = makeVandermonde(x, n) * a; // the factory temporary dies with the full expression |
| Vec scribble = Vec::Random(2 * m); // reuses the temporary's freed heap storage |
| Vec y = expr; |
| VERIFY_IS_APPROX(y, (dense * a).eval()); |
| VERIFY_IS_EQUAL(scribble.size(), 2 * m); // keep the scribble alive across the evaluation |
| } |
| |
| // Mixed-scalar products: a real operator applied to a complex right-hand side |
| // (and a complex operator applied to a real one) promotes to the complex |
| // product scalar, so alpha and the Horner accumulation must run in the promoted |
| // type rather than the operator scalar. |
| template <typename RealScalar> |
| void test_vandermonde_mixed_scalar(Index m, Index n) { |
| typedef std::complex<RealScalar> Complex; |
| typedef Matrix<RealScalar, Dynamic, 1> RVec; |
| typedef Matrix<Complex, Dynamic, 1> CVec; |
| typedef Matrix<Complex, Dynamic, Dynamic> CMat; |
| |
| RVec x = RVec::Random(m); |
| Vandermonde<RealScalar> V(x, n); |
| CMat dense = reference_vandermonde<RealScalar>(x, n).template cast<Complex>(); |
| |
| CVec a = CVec::Random(n); |
| CVec y = V * a; |
| VERIFY_IS_APPROX(y, (dense * a).eval()); |
| |
| CVec y0 = CVec::Random(m); |
| y = y0; |
| y.noalias() += V * a; |
| VERIFY_IS_APPROX(y, (y0 + dense * a).eval()); |
| |
| CVec xc = CVec::Random(m); |
| Vandermonde<Complex> Vc(xc, n); |
| CMat denseC = reference_vandermonde<Complex>(xc, n); |
| RVec ar = RVec::Random(n); |
| CVec z = Vc * ar; |
| VERIFY_IS_APPROX(z, (denseC * ar).eval()); |
| } |
| |
| template <typename Scalar> |
| void test_vandermonde_determinant(Index n) { |
| typedef Matrix<Scalar, Dynamic, 1> Vec; |
| typedef Matrix<Scalar, Dynamic, Dynamic> Mat; |
| |
| Vec x = Vec::Random(n); |
| Vandermonde<Scalar> V(x); |
| Mat dense = reference_vandermonde<Scalar>(x, n); |
| VERIFY_IS_APPROX(V.determinant(), dense.determinant()); |
| } |
| |
| // Reference for the wide-dynamic-range determinant tests: the same factor sequence with explicit |
| // exponent tracking, halving any factor whose node difference overflows (exact, since a difference |
| // only overflows for huge normal operands). Power-of-two rescaling is exact, so this reproduces the |
| // exact product up to one rounding per multiplication, representable where naive partial products |
| // leave the double range. Finite nodes only. |
| double reference_vandermonde_det(const Matrix<double, Dynamic, 1>& x) { |
| double mantissa = 1.0; |
| Index exponent = 0; |
| for (Index j = 1; j < x.size(); ++j) |
| for (Index i = 0; i < j; ++i) { |
| double diff = x[j] - x[i]; |
| if (!(numext::isfinite)(diff)) { |
| diff = 0.5 * x[j] - 0.5 * x[i]; |
| ++exponent; |
| } |
| int e; |
| mantissa *= std::frexp(diff, &e); |
| exponent += e; |
| mantissa = std::frexp(mantissa, &e); |
| exponent += e; |
| } |
| return std::ldexp(mantissa, static_cast<int>(exponent)); |
| } |
| |
| // Balanced determinant accumulation (reviewer repro on MR 2691): the running |
| // product is kept as mantissa * 2^e with exact frexp/ldexp renormalization, so |
| // partial products that leave the representable range cannot destroy a |
| // representable determinant, while zeros and genuine overflow still propagate. |
| void test_vandermonde_determinant_scaled() { |
| typedef Matrix<double, Dynamic, 1> Vec; |
| typedef std::complex<double> Complex; |
| // The implementation accumulates the identical factor sequence as the |
| // reference above, so the two sides differ only by the exactness of the |
| // power-of-two renormalization. |
| const double kBalancedTol = 16 * NumTraits<double>::epsilon(); |
| // The analytic values below additionally absorb the representation error of |
| // the decimal node literals and of pow(10, 27.5), amplified by the 15-factor |
| // product. |
| const double kAnalyticTol = 100 * NumTraits<double>::epsilon(); |
| |
| const double a = 1e-110, M = std::pow(10.0, 27.5); |
| { |
| // Underflow side (the reviewer's exact node set): the a-spaced factors alone |
| // reach 2e-330, below the smallest subnormal, so the naive running product |
| // flushes to an exact zero -- yet the determinant is 864 * a^3 * M^12 ~ 864. |
| Vec x(6); |
| x << 0.0, a, 2 * a, M, 2 * M, 3 * M; |
| Vandermonde<double> V(x); |
| const double det = V.determinant(); |
| const double ref = reference_vandermonde_det(x); |
| VERIFY((numext::isfinite)(det)); |
| VERIFY(numext::abs(det / ref - 1.0) <= kBalancedTol); |
| VERIFY(numext::abs(det / 864.0 - 1.0) <= kAnalyticTol); |
| } |
| { |
| // Overflow-side analogue: with the large nodes first the naive running |
| // product tops out at ~4.3e327 (infinity) three factors before the end, yet |
| // the determinant is a representable -864 * B^12 * s^3 = -8.64e305. |
| const double B = 1e28, s = 1e-11; |
| Vec x(6); |
| x << B, 2 * B, 3 * B, 0.0, s, 2 * s; |
| Vandermonde<double> V(x); |
| const double det = V.determinant(); |
| const double ref = reference_vandermonde_det(x); |
| VERIFY((numext::isfinite)(det)); |
| VERIFY(numext::abs(det / ref - 1.0) <= kBalancedTol); |
| VERIFY(numext::abs(det / -8.64e305 - 1.0) <= kAnalyticTol); |
| } |
| { |
| // Genuinely overflowing determinant, ~3.5e424: must saturate to +infinity. |
| const double T = 1e28; |
| Vec x(6); |
| x << 0.0, T, 2 * T, 3 * T, 4 * T, 5 * T; |
| Vandermonde<double> V(x); |
| const double det = V.determinant(); |
| VERIFY((numext::isinf)(det) && det > 0.0); |
| } |
| { |
| // Genuinely underflowing determinant: subnormal-spaced nodes give 15 factors |
| // below 2^-1071, a product near 2^-16000, which must saturate to zero with |
| // the sign of the factored form: +0 for ascending nodes (all factors |
| // positive), -0 for the descending permutation (15 negative factors). |
| const double d = std::numeric_limits<double>::denorm_min(); |
| Vec x(6); |
| x << 0.0, d, 2 * d, 3 * d, 4 * d, 5 * d; |
| const double det = Vandermonde<double>(x).determinant(); |
| VERIFY(det == 0.0 && !std::signbit(det)); |
| const double detNeg = Vandermonde<double>(Vec(x.reverse())).determinant(); |
| VERIFY(detNeg == 0.0 && std::signbit(detNeg)); |
| } |
| { |
| // A repeated node makes the matrix exactly singular: the zero factor must |
| // propagate to an exact 0 even though the naive running product overflows |
| // beforehand (which would end in 0 * inf = NaN). |
| Vec x(5); |
| x << 0.0, 1e300, 1e-300, 1e300, 5.0; |
| Vandermonde<double> V(x); |
| VERIFY(V.determinant() == 0.0); |
| } |
| { |
| // Complex path of the balanced accumulation: purely imaginary nodes i*x |
| // turn every factor of the underflow-side case into i*(x_j - x_i), so |
| // det = i^15 * 864 = -864i. |
| Vec xr(6); |
| xr << 0.0, a, 2 * a, M, 2 * M, 3 * M; |
| Matrix<Complex, Dynamic, 1> x = Complex(0, 1) * xr.cast<Complex>(); |
| Vandermonde<Complex> V(x); |
| const Complex det = V.determinant(); |
| const Complex ref(0.0, -reference_vandermonde_det(xr)); |
| VERIFY((numext::isfinite)(numext::abs(det))); |
| VERIFY(numext::abs(det - ref) <= kBalancedTol * numext::abs(ref)); |
| } |
| } |
| |
| // Reviewer reproducer on MR 2691: a node difference can overflow while the determinant stays |
| // representable. For nodes [-DBL_MAX, DBL_MAX, d, 2d, ..., 5d] with d the smallest subnormal, |
| // DBL_MAX - (-DBL_MAX) = 2^1025 overflows, yet det = -576 DBL_MAX^11 d^10 ~ -3.1633e160. The |
| // overflowing difference must be halved (exact for these huge normal operands) and the factor of |
| // two carried in the running exponent. |
| void test_vandermonde_determinant_overflowing_differences() { |
| typedef Matrix<double, Dynamic, 1> Vec; |
| typedef std::complex<double> Complex; |
| const double M = (std::numeric_limits<double>::max)(); |
| const double d = std::numeric_limits<double>::denorm_min(); |
| const double kBalancedTol = 16 * NumTraits<double>::epsilon(); |
| const double kAnalyticTol = 100 * NumTraits<double>::epsilon(); |
| |
| Vec x(7); |
| x << -M, M, d, 2 * d, 3 * d, 4 * d, 5 * d; |
| const double det = Vandermonde<double>(x).determinant(); |
| VERIFY((numext::isfinite)(det)); |
| |
| // Scaled reference computation with the identical factor sequence. |
| const double ref = reference_vandermonde_det(x); |
| VERIFY(numext::abs(det / ref - 1.0) <= kBalancedTol); |
| |
| // Analytic factored form, accumulated with the same exponent bookkeeping: |
| // every huge factor rounds to +-DBL_MAX (and the halved leading factor is |
| // exactly DBL_MAX * 2), the d-spaced block contributes exactly 288 * 2^-10740, |
| // so det = -2 * 288 * DBL_MAX^11 * 2^-10740. |
| double mantissa = -576.0; |
| Index exponent = -10740; |
| for (int t = 0; t < 11; ++t) { |
| int e; |
| mantissa *= std::frexp(M, &e); |
| exponent += e; |
| mantissa = std::frexp(mantissa, &e); |
| exponent += e; |
| } |
| const double expected = std::ldexp(mantissa, static_cast<int>(exponent)); |
| VERIFY(numext::abs(det / expected - 1.0) <= kAnalyticTol); |
| VERIFY(numext::abs(det / -3.1633e160 - 1.0) <= 1e-3); // the reviewer's quoted value |
| |
| // Complex nodes i*x: every factor becomes i*(x_j - x_i) -- the overflow now |
| // sits in the imaginary components, which the component-wise finiteness check |
| // must catch -- and det picks up i^21 = i. |
| Matrix<Complex, Dynamic, 1> xc = Complex(0, 1) * x.cast<Complex>(); |
| const Complex detc = Vandermonde<Complex>(xc).determinant(); |
| const Complex refc = Complex(0, 1) * Complex(ref); |
| VERIFY((numext::isfinite)(detc)); |
| VERIFY(numext::abs(detc - refc) <= kBalancedTol * numext::abs(refc)); |
| } |
| |
| // Reviewer reproducer on MR 2691: Horner intermediates can overflow while the value stays |
| // representable. At node 1/2 with coefficients [0, DBL_MAX, DBL_MAX] the intermediate 1.5 DBL_MAX |
| // is Inf, but the value is exactly fl(0.75 DBL_MAX). The scaled path must return it bit-exactly, |
| // the plain path must match the naive loop for moderate data, and genuinely unrepresentable values |
| // must still saturate. |
| void test_vandermonde_scaled_horner() { |
| typedef Matrix<double, Dynamic, 1> Vec; |
| typedef std::complex<double> Complex; |
| typedef Matrix<Complex, Dynamic, 1> CVec; |
| const double M = (std::numeric_limits<double>::max)(); |
| |
| { |
| // The reviewer's exact reproducer, in each accumulation form. |
| Vec x(1); |
| x << 0.5; |
| Vandermonde<double> V(x, 3); |
| Vec a(3); |
| a << 0.0, M, M; |
| Vec y = V * a; |
| VERIFY_IS_EQUAL(y[0], 0.75 * M); |
| y.setZero(); |
| y.noalias() += V * a; |
| VERIFY_IS_EQUAL(y[0], 0.75 * M); |
| // Two identical columns exercise the per-column screening. |
| Matrix<double, Dynamic, Dynamic> A(3, 2); |
| A.col(0) = a; |
| A.col(1) = a; |
| Matrix<double, Dynamic, Dynamic> Y = V * A; |
| VERIFY_IS_EQUAL(Y(0, 0), 0.75 * M); |
| VERIFY_IS_EQUAL(Y(0, 1), 0.75 * M); |
| } |
| { |
| // Complex path: the same coefficients rotated by i keep the overflow in a |
| // single component; the value is i * fl(0.75 * DBL_MAX). |
| CVec xc(1); |
| xc << Complex(0.5, 0.0); |
| Vandermonde<Complex> Vc(xc, 3); |
| CVec ac(3); |
| ac << Complex(0), Complex(0, M), Complex(0, M); |
| CVec yc = Vc * ac; |
| VERIFY_IS_EQUAL(numext::real(yc[0]), 0.0); |
| VERIFY_IS_EQUAL(numext::imag(yc[0]), 0.75 * M); |
| } |
| { |
| // Genuine overflow must still saturate: value 3 * DBL_MAX at the node 2. |
| Vec x(1); |
| x << 2.0; |
| Vandermonde<double> V(x, 2); |
| Vec a(2); |
| a << M, M; |
| Vec y = V * a; |
| VERIFY((numext::isinf)(y[0]) && y[0] > 0.0); |
| a << M, -M; // value -DBL_MAX: representable again, sign preserved |
| y = V * a; |
| VERIFY_IS_EQUAL(y[0], -M); |
| } |
| { |
| // A vanished accumulator has no scale (reviewer repro): after the running |
| // value becomes exactly zero -- a zero node annihilating it, or an exact |
| // cancellation -- the frame exponent must reset, or the trailing small |
| // coefficient underflows in the stale huge frame and the value 0 is |
| // returned instead of the coefficient itself. |
| const double mn = (std::numeric_limits<double>::min)(); |
| const double dm = std::numeric_limits<double>::denorm_min(); |
| Vec x(1); |
| x << 0.0; // zero node: the value is exactly a[0] |
| Vandermonde<double> V0(x, 3); |
| Vec a(3); |
| a << mn, M, M; |
| Vec y = V0 * a; |
| VERIFY_IS_EQUAL(y[0], mn); |
| |
| x << 1.0; // exact cancellation: M - M + dm = dm |
| Vandermonde<double> V1(x, 3); |
| a << dm, -M, M; |
| y = V1 * a; |
| VERIFY_IS_EQUAL(y[0], dm); |
| } |
| { |
| // Complex variant: the real components cancel exactly while the tiny |
| // imaginary coefficient must survive in a fresh frame. |
| const double dm = std::numeric_limits<double>::denorm_min(); |
| CVec xc(1); |
| xc << Complex(1.0, 0.0); |
| Vandermonde<Complex> Vc(xc, 3); |
| CVec ac(3); |
| ac << Complex(0.0, dm), Complex(-M, 0.0), Complex(M, 0.0); |
| CVec yc = Vc * ac; |
| VERIFY_IS_EQUAL(numext::real(yc[0]), 0.0); |
| VERIFY_IS_EQUAL(numext::imag(yc[0]), dm); |
| } |
| { |
| // Near-cancellation: M - (M - 2^971) leaves the tiny nonzero intermediate |
| // 2^971 (mantissa 2^-53 in the huge frame), which the frexp renormalization |
| // must rebase; the trailing coefficient 1 then rounds away exactly as in |
| // real arithmetic: fl(2^971 + 1) = 2^971. |
| const double big = std::ldexp(1.0, 971); // ulp(DBL_MAX), and M - big is exact |
| Vec x(1); |
| x << 1.0; |
| Vandermonde<double> V(x, 3); |
| Vec a(3); |
| a << 1.0, -(M - big), M; |
| Vec y = V * a; |
| VERIFY_IS_EQUAL(y[0], big); |
| } |
| { |
| // Moderate data keeps the plain path, so this matches a naive Horner loop to Horner's own forward |
| // error bound (Higham, ASNA 2nd ed., section 5.1): |p(x) - fl(p(x))| <= gamma_{2n} sum_j |a_j| |x|^j |
| // <= 2n eps sum_j |a_j| for |x| <= 1. Not bit-identical, since the compiler may contract |
| // acc * x[i] + a[j] into an FMA in one of the two loops and not the other. |
| const Index m = 7, n = 6; |
| Vec x = Vec::Random(m), a = Vec::Random(n); |
| Vandermonde<double> V(x, n); |
| Vec y = V * a; |
| const double bound = double(2 * n) * NumTraits<double>::epsilon() * a.cwiseAbs().sum(); |
| for (Index i = 0; i < m; ++i) { |
| double acc = a[n - 1]; |
| for (Index j = n - 2; j >= 0; --j) acc = acc * x[i] + a[j]; |
| VERIFY(numext::abs(y[i] - acc) <= bound); |
| } |
| } |
| } |
| |
| template <typename Scalar, int M, int N> |
| void test_vandermonde_fixed() { |
| typedef Matrix<Scalar, M, 1> NodeVec; |
| typedef Matrix<Scalar, Dynamic, 1> Vec; |
| typedef Matrix<Scalar, M, N> MatMN; |
| |
| NodeVec x = NodeVec::Random(); |
| Vandermonde<Scalar, M, N> V(x, N); |
| STATIC_CHECK((Vandermonde<Scalar, M, N>::RowsAtCompileTime == M)); |
| STATIC_CHECK((Vandermonde<Scalar, M, N>::ColsAtCompileTime == N)); |
| STATIC_CHECK((internal::remove_all_t<decltype(makeVandermonde(x))>::ColsAtCompileTime == M)); |
| |
| MatMN dense = V; |
| VERIFY_IS_APPROX(dense, MatMN(reference_vandermonde<Scalar>(Vec(x), N))); |
| |
| Matrix<Scalar, N, 1> a = Matrix<Scalar, N, 1>::Random(); |
| Matrix<Scalar, M, 1> y = V * a; |
| VERIFY_IS_APPROX(y, (dense * a).eval()); |
| } |
| |
| // Core rewrites alpha * (V * a) as (alpha * V) * a. Keep the two scaled |
| // expression forms together because they share the same structured wrapper. |
| void test_vandermonde_expression_regressions() { |
| typedef Matrix<double, 1, 1> Vec1; |
| typedef Matrix<double, 3, 1> Vec3; |
| typedef Matrix<double, 1, 3> Row3; |
| typedef Matrix<double, 3, 3> Mat3; |
| typedef Matrix<double, 3, 2> Mat32; |
| |
| Vec1 x; |
| x << 0.5; |
| Vandermonde<double, 1, 3> V(x, 3); |
| Row3 dense = V; |
| Row3 scaledDense = 2.0 * V; |
| VERIFY_IS_APPROX(scaledDense, (2.0 * dense).eval()); |
| |
| Vec3 a; |
| a << 1.0, 2.0, 3.0; |
| Vec1 scaledProduct = 0.5 * (V * a); |
| VERIFY_IS_APPROX(scaledProduct, (0.5 * (dense * a)).eval()); |
| |
| // A product on the right must be evaluated before the structured Horner |
| // evaluator asks it for coefficients. |
| Mat3 B = Mat3::Random(); |
| Mat32 C = Mat32::Random(); |
| VERIFY_IS_APPROX((V * (B * C)).eval(), (dense * (B * C)).eval()); |
| } |
| |
| void test_vandermonde_dimension_checks() { |
| VectorXd x(2); |
| x << 0.0, 1.0; |
| VERIFY_RAISES_ASSERT((Vandermonde<double, Dynamic, 3>(x))); |
| |
| Vandermonde<double> rectangular(x, 3); |
| VERIFY_RAISES_ASSERT((BjorckPereyra<double>(rectangular))); |
| } |
| |
| // Fixed-size Vandermonde expressions own the operator by value so temporaries |
| // returned from factories remain alive. Their node storage must therefore avoid |
| // over-alignment: generic Product and cwise wrapper types do not supply aligned |
| // operator new under C++14. |
| void test_vandermonde_fixed_heap_expressions() { |
| typedef Matrix<double, 4, 1> Vec4; |
| typedef Matrix<double, 4, 4> Mat4; |
| |
| Vec4 x; |
| x << -1.0, -0.25, 0.5, 1.0; |
| Vec4 a; |
| a << 1.0, 2.0, 3.0, 4.0; |
| |
| typedef decltype(makeVandermonde(x) * a) ProductExpression; |
| typedef decltype(2.0 * makeVandermonde(x)) ScaledExpression; |
| STATIC_CHECK(alignof(ProductExpression) <= alignof(std::max_align_t)); |
| STATIC_CHECK(alignof(ScaledExpression) <= alignof(std::max_align_t)); |
| |
| ProductExpression* delayedProduct = new ProductExpression(makeVandermonde(x) * a); |
| ScaledExpression* delayedScaled = new ScaledExpression(2.0 * makeVandermonde(x)); |
| |
| const Mat4 dense = reference_vandermonde<double>(Matrix<double, Dynamic, 1>(x), 4); |
| Vec4 y = *delayedProduct; |
| Mat4 scaled = *delayedScaled; |
| VERIFY_IS_APPROX(y, (dense * a).eval()); |
| VERIFY_IS_APPROX(scaled, (2.0 * dense).eval()); |
| delete delayedProduct; |
| delete delayedScaled; |
| } |
| |
| EIGEN_DECLARE_TEST(structured_vandermonde) { |
| for (int i = 0; i < g_repeat; ++i) { |
| // Horner products, dense assignment, coefficient access. |
| CALL_SUBTEST_1((test_vandermonde_product<double>(1, 1))); |
| CALL_SUBTEST_1((test_vandermonde_product<double>(8, 8))); |
| CALL_SUBTEST_1((test_vandermonde_product<double>(20, 12))); // tall |
| CALL_SUBTEST_1((test_vandermonde_product<double>(12, 20))); // wide |
| CALL_SUBTEST_1((test_vandermonde_product<float>(10, 10))); |
| CALL_SUBTEST_1((test_vandermonde_product<std::complex<double>>(9, 7))); |
| CALL_SUBTEST_1((test_vandermonde_product<std::complex<float>>(7, 9))); |
| |
| // Björck-Pereyra primal/dual/adjoint solves. |
| CALL_SUBTEST_2((test_bjorck_pereyra<double>(1))); |
| CALL_SUBTEST_2((test_bjorck_pereyra<double>(2))); |
| CALL_SUBTEST_2((test_bjorck_pereyra<double>(10))); |
| CALL_SUBTEST_2((test_bjorck_pereyra<double>(14))); |
| CALL_SUBTEST_2((test_bjorck_pereyra<std::complex<double>>(10))); |
| CALL_SUBTEST_2((test_bjorck_pereyra_roots_of_unity<double>(64))); |
| CALL_SUBTEST_2((test_bjorck_pereyra_roots_of_unity<float>(32))); |
| CALL_SUBTEST_2(test_vandermonde_half()); |
| CALL_SUBTEST_2(test_bjorck_pereyra_higham()); |
| CALL_SUBTEST_2(test_bjorck_pereyra_complex_transpose_adjoint(12)); |
| CALL_SUBTEST_2(test_bjorck_pereyra_singular()); |
| |
| // Closed-form determinant and fixed sizes. |
| CALL_SUBTEST_3((test_vandermonde_determinant<double>(8))); |
| CALL_SUBTEST_3((test_vandermonde_determinant<std::complex<double>>(7))); |
| CALL_SUBTEST_3(test_vandermonde_determinant_scaled()); |
| CALL_SUBTEST_3(test_vandermonde_determinant_overflowing_differences()); |
| CALL_SUBTEST_3(test_vandermonde_scaled_horner()); |
| CALL_SUBTEST_3((test_vandermonde_fixed<double, 6, 4>())); |
| CALL_SUBTEST_3((test_vandermonde_fixed<double, 5, 5>())); |
| CALL_SUBTEST_3((test_vandermonde_fixed<std::complex<float>, 4, 4>())); |
| CALL_SUBTEST_3(test_vandermonde_expression_regressions()); |
| CALL_SUBTEST_3(test_vandermonde_fixed_heap_expressions()); |
| |
| // Product regressions: aliasing, operand lifetime, mixed scalars. |
| CALL_SUBTEST_4((test_vandermonde_aliased_product<double>(8))); |
| CALL_SUBTEST_4((test_vandermonde_aliased_product<double>(17))); |
| CALL_SUBTEST_4((test_vandermonde_aliased_product<std::complex<double>>(9))); |
| CALL_SUBTEST_4((test_vandermonde_aliased_expression<double>(8))); |
| CALL_SUBTEST_4((test_vandermonde_aliased_expression<std::complex<double>>(9))); |
| CALL_SUBTEST_4((test_vandermonde_delayed_product<double>(12, 9))); |
| CALL_SUBTEST_4((test_vandermonde_delayed_product<std::complex<double>>(8, 8))); |
| CALL_SUBTEST_4((test_vandermonde_mixed_scalar<double>(10, 8))); |
| CALL_SUBTEST_4((test_vandermonde_mixed_scalar<float>(7, 9))); |
| } |
| |
| CALL_SUBTEST_3(test_vandermonde_dimension_checks()); |
| } |