| // This file is part of Eigen, a lightweight C++ template library |
| // for linear algebra. |
| // |
| // Copyright (C) 2010,2012 Jitse Niesen <jitse@maths.leeds.ac.uk> |
| // |
| // 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-License-Identifier: MPL-2.0 |
| |
| #include "main.h" |
| #include "fp_control.h" |
| #include <limits> |
| #include <Eigen/Eigenvalues> |
| |
| template <typename MatrixType> |
| void schur(int size = MatrixType::ColsAtCompileTime) { |
| typedef typename ComplexSchur<MatrixType>::ComplexScalar ComplexScalar; |
| typedef typename ComplexSchur<MatrixType>::ComplexMatrixType ComplexMatrixType; |
| |
| // Test basic functionality: T is triangular and A = U T U* |
| for (int counter = 0; counter < g_repeat; ++counter) { |
| MatrixType A = MatrixType::Random(size, size); |
| ComplexSchur<MatrixType> schurOfA(A); |
| VERIFY_IS_EQUAL(schurOfA.info(), Success); |
| ComplexMatrixType U = schurOfA.matrixU(); |
| ComplexMatrixType T = schurOfA.matrixT(); |
| for (int row = 1; row < size; ++row) { |
| for (int col = 0; col < row; ++col) { |
| VERIFY(T(row, col) == (typename MatrixType::Scalar)0); |
| } |
| } |
| VERIFY_IS_APPROX(A.template cast<ComplexScalar>(), U * T * U.adjoint()); |
| } |
| |
| // Test asserts when not initialized |
| ComplexSchur<MatrixType> csUninitialized; |
| VERIFY_RAISES_ASSERT(csUninitialized.matrixT()); |
| VERIFY_RAISES_ASSERT(csUninitialized.matrixU()); |
| VERIFY_RAISES_ASSERT(csUninitialized.info()); |
| |
| // Test whether compute() and constructor returns same result |
| MatrixType A = MatrixType::Random(size, size); |
| ComplexSchur<MatrixType> cs1; |
| cs1.compute(A); |
| ComplexSchur<MatrixType> cs2(A); |
| VERIFY_IS_EQUAL(cs1.info(), Success); |
| VERIFY_IS_EQUAL(cs2.info(), Success); |
| VERIFY_IS_EQUAL(cs1.matrixT(), cs2.matrixT()); |
| VERIFY_IS_EQUAL(cs1.matrixU(), cs2.matrixU()); |
| |
| // Test maximum number of iterations |
| ComplexSchur<MatrixType> cs3; |
| cs3.setMaxIterations(ComplexSchur<MatrixType>::m_maxIterationsPerRow * size).compute(A); |
| VERIFY_IS_EQUAL(cs3.info(), Success); |
| VERIFY_IS_EQUAL(cs3.matrixT(), cs1.matrixT()); |
| VERIFY_IS_EQUAL(cs3.matrixU(), cs1.matrixU()); |
| cs3.setMaxIterations(1).compute(A); |
| // The schur decomposition does often converge with a single iteration. |
| // VERIFY_IS_EQUAL(cs3.info(), size > 1 ? NoConvergence : Success); |
| VERIFY_IS_EQUAL(cs3.getMaxIterations(), 1); |
| |
| MatrixType Atriangular = A; |
| Atriangular.template triangularView<StrictlyLower>().setZero(); |
| cs3.setMaxIterations(1).compute(Atriangular); // triangular matrices do not need any iterations |
| VERIFY_IS_EQUAL(cs3.info(), Success); |
| VERIFY_IS_EQUAL(cs3.matrixT(), Atriangular.template cast<ComplexScalar>()); |
| VERIFY_IS_EQUAL(cs3.matrixU(), ComplexMatrixType::Identity(size, size)); |
| |
| // Test computation of only T, not U |
| ComplexSchur<MatrixType> csOnlyT(A, false); |
| VERIFY_IS_EQUAL(csOnlyT.info(), Success); |
| VERIFY_IS_EQUAL(cs1.matrixT(), csOnlyT.matrixT()); |
| VERIFY_RAISES_ASSERT(csOnlyT.matrixU()); |
| |
| if (size > 1 && size < 20) { |
| // Test matrix with NaN |
| A(0, 0) = std::numeric_limits<typename MatrixType::RealScalar>::quiet_NaN(); |
| ComplexSchur<MatrixType> csNaN(A); |
| VERIFY_IS_EQUAL(csNaN.info(), NoConvergence); |
| } |
| } |
| |
| // The Hessenberg reduction treats a subdiagonal tail whose squared norm underflows as already zero. ComplexSchur used |
| // to reduce the caller's matrix directly, so at this scale it returned the diagonal of the input as the Schur form. |
| template <typename MatrixType> |
| void schur_underflow_scale(Index size) { |
| typedef typename MatrixType::Scalar Scalar; |
| typedef typename MatrixType::RealScalar RealScalar; |
| typedef typename ComplexSchur<MatrixType>::ComplexScalar ComplexScalar; |
| typedef typename ComplexSchur<MatrixType>::ComplexMatrixType ComplexMatrixType; |
| |
| // Representable, but every product of two coefficients underflows. |
| const RealScalar scale = numext::sqrt((std::numeric_limits<RealScalar>::min)()) / RealScalar(1024); |
| const MatrixType A = MatrixType::Random(size, size) * Scalar(scale); |
| |
| ComplexSchur<MatrixType> schurOfA(A); |
| VERIFY_IS_EQUAL(schurOfA.info(), Success); |
| const ComplexMatrixType& U = schurOfA.matrixU(); |
| const ComplexMatrixType& T = schurOfA.matrixT(); |
| |
| // isApprox squares its operands, and every square here is subnormal: under flush-to-zero both sides of the |
| // reconstruction collapse to zero and any T passes. scale is an exact power of two, so multiplying by its |
| // reciprocal is lossless and puts the comparison back in the normal range. |
| const ComplexScalar invScale = ComplexScalar(RealScalar(1) / scale); |
| VERIFY_IS_APPROX(ComplexMatrixType(A.template cast<ComplexScalar>() * invScale), |
| ComplexMatrixType(U * T * U.adjoint() * invScale)); |
| VERIFY_IS_APPROX(U * U.adjoint(), ComplexMatrixType::Identity(size, size)); |
| for (Index row = 1; row < size; ++row) |
| for (Index col = 0; col < row; ++col) VERIFY(numext::is_exactly_zero(T(row, col))); |
| } |
| |
| // A matrix whose coefficients are all subnormal is still a matrix with a Schur decomposition, not the zero matrix. |
| // Scaling by a power of two moves it into the normal range exactly, so the factorization stays accurate to the |
| // quantization of the subnormal input. |
| template <typename MatrixType> |
| void schur_subnormal_scale(Index size) { |
| typedef typename MatrixType::Scalar Scalar; |
| typedef typename MatrixType::RealScalar RealScalar; |
| typedef typename ComplexSchur<MatrixType>::ComplexScalar ComplexScalar; |
| typedef typename ComplexSchur<MatrixType>::ComplexMatrixType ComplexMatrixType; |
| |
| const RealScalar denormalMin = std::numeric_limits<RealScalar>::denorm_min(); |
| const RealScalar normalMin = (std::numeric_limits<RealScalar>::min)(); |
| if (!(denormalMin < normalMin)) return; // Target has no subnormals, or flushes them to zero. |
| |
| // Every coefficient lands in the top of the subnormal range: none of them is normal, yet each still carries most of |
| // its mantissa, so an accurate decomposition remains possible. |
| const MatrixType A = MatrixType::Random(size, size) * Scalar(normalMin / RealScalar(2)); |
| if (!(A.cwiseAbs().maxCoeff() > RealScalar(0))) return; |
| VERIFY(A.cwiseAbs().maxCoeff() < normalMin); |
| |
| ComplexSchur<MatrixType> schurOfA(A); |
| VERIFY_IS_EQUAL(schurOfA.info(), Success); |
| const ComplexMatrixType& U = schurOfA.matrixU(); |
| const ComplexMatrixType& T = schurOfA.matrixT(); |
| |
| // Unitary invariance gives ||T||_F = ||A||_F, so the largest coefficient of T cannot be more than a factor of the |
| // dimension below the largest coefficient of A. Returning the zero matrix, as the unscaled reduction did, fails |
| // this; isZero() would not, because every coefficient here is much smaller than one. |
| VERIFY(T.cwiseAbs().maxCoeff() >= A.cwiseAbs().maxCoeff() / RealScalar(size)); |
| VERIFY_IS_APPROX(U * U.adjoint(), ComplexMatrixType::Identity(size, size)); |
| // The reconstruction rounds back into the subnormal range, where the spacing is denorm_min rather than eps times |
| // the coefficient, so bound the residual by that spacing instead of by a relative tolerance. |
| const RealScalar residual = (A.template cast<ComplexScalar>() - U * T * U.adjoint()).cwiseAbs().maxCoeff(); |
| VERIFY(residual <= RealScalar(4 * size * size) * denormalMin); |
| } |
| |
| // Scaling divides by a power of two, so coefficients more than the exponent range below the largest one flush to zero. |
| // That perturbation is bounded by the smallest subnormal relative to the largest coefficient, far below eps, and the |
| // unscaled reduction squares such a matrix into overflow anyway. |
| template <typename MatrixType> |
| void schur_dynamic_range(Index size) { |
| typedef typename MatrixType::Scalar Scalar; |
| typedef typename MatrixType::RealScalar RealScalar; |
| typedef typename ComplexSchur<MatrixType>::ComplexScalar ComplexScalar; |
| typedef typename ComplexSchur<MatrixType>::ComplexMatrixType ComplexMatrixType; |
| |
| const RealScalar huge = numext::sqrt((std::numeric_limits<RealScalar>::max)()); |
| MatrixType A = MatrixType::Random(size, size) * Scalar(huge); |
| A(size - 1, 0) = Scalar(std::numeric_limits<RealScalar>::denorm_min()); |
| |
| ComplexSchur<MatrixType> schurOfA(A); |
| VERIFY_IS_EQUAL(schurOfA.info(), Success); |
| const ComplexMatrixType& U = schurOfA.matrixU(); |
| const ComplexMatrixType& T = schurOfA.matrixT(); |
| |
| VERIFY(U.allFinite() && T.allFinite()); |
| VERIFY_IS_APPROX(U * U.adjoint(), ComplexMatrixType::Identity(size, size)); |
| // Both Frobenius norms overflow at this scale, so isApprox would compare infinity against infinity and any T would |
| // pass. Compare at unit scale instead; huge is not a power of two, so this costs one rounding per coefficient. |
| const ComplexScalar invHuge = ComplexScalar(RealScalar(1) / huge); |
| VERIFY_IS_APPROX(ComplexMatrixType(A.template cast<ComplexScalar>() * invHuge), |
| ComplexMatrixType(U * T * U.adjoint() * invHuge)); |
| } |
| |
| // A NaN must not be scaled away. maxCoeff has to propagate it, or a matrix that is zero apart from a NaN looks like |
| // the zero matrix and reports a successful decomposition. Which coefficient carries the NaN matters, because the |
| // default max reduction keeps whichever operand it visits first. |
| template <typename MatrixType> |
| void schur_nan_only(Index size) { |
| typedef typename MatrixType::Scalar Scalar; |
| typedef typename MatrixType::RealScalar RealScalar; |
| |
| for (Index k = 0; k < size; ++k) { |
| MatrixType A = MatrixType::Zero(size, size); |
| A(k, k) = Scalar(std::numeric_limits<RealScalar>::quiet_NaN()); |
| |
| ComplexSchur<MatrixType> schurOfA(A); |
| VERIFY_IS_NOT_EQUAL(schurOfA.info(), Success); |
| } |
| } |
| |
| // The zero matrix has no exponent to normalize by; it must still decompose trivially. |
| template <typename MatrixType> |
| void schur_zero(Index size) { |
| typedef typename ComplexSchur<MatrixType>::ComplexMatrixType ComplexMatrixType; |
| |
| const MatrixType A = MatrixType::Zero(size, size); |
| ComplexSchur<MatrixType> schurOfA(A); |
| VERIFY_IS_EQUAL(schurOfA.info(), Success); |
| VERIFY(schurOfA.matrixT().isZero()); |
| VERIFY_IS_EQUAL(schurOfA.matrixU(), ComplexMatrixType::Identity(size, size)); |
| } |
| |
| EIGEN_DECLARE_TEST(schur_complex) { |
| CALL_SUBTEST_1((schur<Matrix4cd>())); |
| CALL_SUBTEST_2((schur<MatrixXcf>(internal::random<int>(1, EIGEN_TEST_MAX_SIZE / 4)))); |
| CALL_SUBTEST_3((schur<Matrix<std::complex<float>, 1, 1> >())); |
| CALL_SUBTEST_4((schur<Matrix<float, 3, 3, Eigen::RowMajor> >())); |
| |
| // Test problem size constructors |
| CALL_SUBTEST_5(ComplexSchur<MatrixXf>(10)); |
| |
| CALL_SUBTEST_6((schur_underflow_scale<Matrix4cd>(4))); |
| CALL_SUBTEST_6((schur_underflow_scale<MatrixXcf>(8))); |
| CALL_SUBTEST_6((schur_underflow_scale<MatrixXf>(8))); |
| { |
| // Every square this reconstruction compares is subnormal, so run it again with subnormals flushed to zero: that |
| // is the configuration in which the unscaled reduction it guards against also passes. |
| Eigen::ScopedFlushToZero flush_to_zero; |
| if (flush_to_zero.isSupported()) { |
| CALL_SUBTEST_6((schur_underflow_scale<Matrix4cd>(4))); |
| CALL_SUBTEST_6((schur_underflow_scale<MatrixXcf>(8))); |
| CALL_SUBTEST_6((schur_underflow_scale<MatrixXf>(8))); |
| } |
| } |
| |
| CALL_SUBTEST_7((schur_subnormal_scale<Matrix4cd>(4))); |
| CALL_SUBTEST_7((schur_subnormal_scale<MatrixXcf>(8))); |
| CALL_SUBTEST_7((schur_subnormal_scale<MatrixXf>(8))); |
| |
| CALL_SUBTEST_8((schur_dynamic_range<Matrix4cd>(4))); |
| CALL_SUBTEST_8((schur_dynamic_range<MatrixXcf>(8))); |
| CALL_SUBTEST_8((schur_dynamic_range<MatrixXf>(8))); |
| |
| CALL_SUBTEST_9((schur_nan_only<Matrix4cd>(4))); |
| CALL_SUBTEST_9((schur_nan_only<MatrixXcf>(8))); |
| CALL_SUBTEST_9((schur_nan_only<MatrixXf>(8))); |
| |
| CALL_SUBTEST_10((schur_zero<Matrix4cd>(4))); |
| CALL_SUBTEST_10((schur_zero<MatrixXcf>(8))); |
| CALL_SUBTEST_10((schur_zero<MatrixXf>(8))); |
| } |