blob: 4be7a191e962537187029872636c475091cec39f [file] [edit]
// 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)));
}