blob: 2a844213ba7454201a4aa8f256faf973602fbc17 [file]
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2012 The Eigen Authors
//
// 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
#define EIGEN_RUNTIME_NO_MALLOC
#include "main.h"
#include <Eigen/Eigenvalues>
/* this test covers the following files:
ComplexQZ.h
*/
template <typename MatrixType>
void generate_random_matrix_pair(const Index dim, MatrixType& A, MatrixType& B) {
A.setRandom(dim, dim);
B.setRandom(dim, dim);
// Zero out each row of B to with a probability of 10%.
for (int i = 0; i < dim; i++) {
if (internal::random<int>(0, 10) == 0) B.row(i).setZero();
}
}
template <typename MatrixType>
void complex_qz(const MatrixType& A, const MatrixType& B) {
using std::abs;
const Index dim = A.rows();
ComplexQZ<MatrixType> qz(A, B);
VERIFY_IS_EQUAL(qz.info(), Success);
auto T = qz.matrixT(), S = qz.matrixS();
bool is_all_zero_T = true, is_all_zero_S = true;
using RealScalar = typename MatrixType::RealScalar;
RealScalar tol = dim * 10 * NumTraits<RealScalar>::epsilon();
for (Index j = 0; j < dim; j++) {
for (Index i = j + 1; i < dim; i++) {
if (std::abs(T(i, j)) > tol) {
std::cerr << std::abs(T(i, j)) << std::endl;
is_all_zero_T = false;
}
if (std::abs(S(i, j)) > tol) {
std::cerr << std::abs(S(i, j)) << std::endl;
is_all_zero_S = false;
}
}
}
VERIFY_IS_EQUAL(is_all_zero_T, true);
VERIFY_IS_EQUAL(is_all_zero_S, true);
VERIFY_IS_APPROX(qz.matrixQ() * qz.matrixS() * qz.matrixZ(), A);
VERIFY_IS_APPROX(qz.matrixQ() * qz.matrixT() * qz.matrixZ(), B);
VERIFY_IS_APPROX(qz.matrixQ() * qz.matrixQ().adjoint(), MatrixType::Identity(dim, dim));
VERIFY_IS_APPROX(qz.matrixZ() * qz.matrixZ().adjoint(), MatrixType::Identity(dim, dim));
}
template <typename MatrixType, typename QZType>
void verify_complex_qz_convergence(const MatrixType& a, const MatrixType& b, const QZType& qz) {
using RealScalar = typename MatrixType::RealScalar;
// Accumulated rounding in the unitary transformations is O(n * epsilon).
const RealScalar tolerance = RealScalar(128 * a.rows()) * NumTraits<RealScalar>::epsilon();
VERIFY_IS_EQUAL(qz.info(), Success);
VERIFY((a - qz.matrixQ() * qz.matrixS() * qz.matrixZ()).norm() <= tolerance * a.norm());
VERIFY((b - qz.matrixQ() * qz.matrixT() * qz.matrixZ()).norm() <= tolerance * b.norm());
VERIFY(qz.matrixQ().isUnitary(tolerance));
VERIFY(qz.matrixZ().isUnitary(tolerance));
const MatrixType lowerS = qz.matrixS().template triangularView<StrictlyLower>();
const MatrixType lowerT = qz.matrixT().template triangularView<StrictlyLower>();
VERIFY(lowerS.norm() <= tolerance * a.norm());
VERIFY(lowerT.norm() <= tolerance * b.norm());
}
template <typename MatrixType>
void complex_qz_exceptional_shift() {
using Scalar = typename MatrixType::Scalar;
using RealScalar = typename MatrixType::RealScalar;
for (Index dim : {3, 4, 5, 8}) {
for (Scalar phase : {Scalar(1), Scalar(0, 1)}) {
// With corner -1 and phase 1, the exceptional shift 2 is equidistant from the eigenvalues exp(+-i*pi/dim).
for (RealScalar corner : {RealScalar(1), RealScalar(-1)}) {
// The trailing 2x2 block has two zero shifts: ordinary double shifts cycle without deflation.
MatrixType a = MatrixType::Zero(dim, dim), b = MatrixType::Identity(dim, dim);
a.diagonal(-1).setConstant(phase);
a(0, dim - 1) = corner * phase;
for (Index j = 0; j < dim; ++j) {
b(j, j) = Scalar(1 << (j % 3));
a.col(j) *= b(j, j);
}
const RealScalar tolerance = RealScalar(128 * dim) * NumTraits<RealScalar>::epsilon();
ComplexQZ<MatrixType> qz(a, b);
verify_complex_qz_convergence(a, b, qz);
// a*b^-1 = phase*P with P^dim = corner*I, so lambda_k = phase*w_k with w_k^dim = corner. These are
// 2*sin(pi/dim) apart with unit condition number (y^* b x = 1 for unit left y and |x| <= 1), so each moves by
// at most the backward error, including the dropped strictly lower parts of S and T.
const Matrix<Scalar, Dynamic, 1> lambda = qz.matrixS().diagonal().cwiseQuotient(qz.matrixT().diagonal());
const RealScalar eigenvalueTolerance = RealScalar(2) * tolerance * (a.norm() + b.norm());
for (Index k = 0; k < dim; ++k) {
const RealScalar angle = (RealScalar(2 * k) + (corner < 0 ? RealScalar(1) : RealScalar(0))) *
RealScalar(EIGEN_PI) / RealScalar(dim);
const Scalar expected = phase * std::polar(RealScalar(1), angle);
VERIFY((lambda.array() - expected).abs().minCoeff() <= eigenvalueTolerance);
}
ComplexQZ<MatrixType> limited(a, b, true, 1);
VERIFY_IS_EQUAL(limited.info(), NoConvergence);
VERIFY_IS_EQUAL(limited.iterations(), 1);
MatrixType inplaceA = a, inplaceB = b;
ComplexQZ<Ref<MatrixType>> inplace(inplaceA, inplaceB);
verify_complex_qz_convergence(a, b, inplace);
const MatrixType s = qz.matrixS(), t = qz.matrixT();
qz.compute(a, b, false);
VERIFY_IS_EQUAL(qz.info(), Success);
VERIFY((qz.matrixS() - s).norm() <= tolerance * a.norm());
VERIFY((qz.matrixT() - t).norm() <= tolerance * b.norm());
qz.compute(a, b);
verify_complex_qz_convergence(a, b, qz);
}
}
}
}
// Block Householder operations still allocate at larger sizes (issue #3163).
template <typename MatrixType>
void complex_qz_no_malloc() {
const Index dim = 24;
MatrixType A = MatrixType::Random(dim, dim), B = MatrixType::Random(dim, dim);
ComplexQZ<MatrixType> qz(dim);
internal::set_is_malloc_allowed(false);
qz.compute(A, B, false);
internal::set_is_malloc_allowed(true);
VERIFY_IS_EQUAL(qz.info(), Success);
const MatrixType S = qz.matrixS(), T = qz.matrixT();
ComplexQZ<MatrixType> resized(1, false);
resized.compute(A, B, false);
internal::set_is_malloc_allowed(false);
resized.compute(A, B, false);
internal::set_is_malloc_allowed(true);
VERIFY_IS_EQUAL(resized.info(), Success);
VERIFY_IS_APPROX(resized.matrixS(), S);
VERIFY_IS_APPROX(resized.matrixT(), T);
for (int i = 0; i < 2; ++i) {
internal::set_is_malloc_allowed(false);
qz.compute(A, B, true);
internal::set_is_malloc_allowed(true);
VERIFY_IS_EQUAL(qz.info(), Success);
VERIFY_IS_APPROX(qz.matrixS(), S);
VERIFY_IS_APPROX(qz.matrixT(), T);
VERIFY_IS_APPROX(qz.matrixQ() * qz.matrixS() * qz.matrixZ(), A);
VERIFY_IS_APPROX(qz.matrixQ() * qz.matrixT() * qz.matrixZ(), B);
VERIFY_IS_APPROX(qz.matrixQ() * qz.matrixQ().adjoint(), MatrixType::Identity(dim, dim));
VERIFY_IS_APPROX(qz.matrixZ() * qz.matrixZ().adjoint(), MatrixType::Identity(dim, dim));
}
}
EIGEN_DECLARE_TEST(complex_qz) {
CALL_SUBTEST_3((complex_qz_no_malloc<MatrixXcf>()));
CALL_SUBTEST_4((complex_qz_no_malloc<MatrixXcd>()));
CALL_SUBTEST_5((complex_qz_no_malloc<Matrix<std::complex<float>, Dynamic, Dynamic, RowMajor>>()));
CALL_SUBTEST_6((complex_qz_no_malloc<Matrix<std::complex<double>, Dynamic, Dynamic, RowMajor>>()));
CALL_SUBTEST_7((complex_qz_exceptional_shift<MatrixXcf>()));
CALL_SUBTEST_8((complex_qz_exceptional_shift<MatrixXcd>()));
CALL_SUBTEST_9((complex_qz_exceptional_shift<Matrix<std::complex<float>, Dynamic, Dynamic, RowMajor>>()));
CALL_SUBTEST_10((complex_qz_exceptional_shift<Matrix<std::complex<double>, Dynamic, Dynamic, RowMajor>>()));
for (int i = 0; i < g_repeat; i++) {
// Check for very small, fixed-sized double- and float complex matrices
Eigen::Matrix2cd A_2x2, B_2x2;
A_2x2.setRandom();
B_2x2.setRandom();
B_2x2.row(1).setZero();
Eigen::Matrix3cf A_3x3, B_3x3;
A_3x3.setRandom();
B_3x3.setRandom();
B_3x3.col(i % 3).setRandom();
CALL_SUBTEST_1(complex_qz(A_2x2, B_2x2));
CALL_SUBTEST_2(complex_qz(A_3x3, B_3x3));
// Test for float complex matrices
const Index dim = internal::random<Index>(15, 80);
Eigen::MatrixXcf A_float, B_float;
generate_random_matrix_pair(dim, A_float, B_float);
CALL_SUBTEST_3(complex_qz(A_float, B_float));
CALL_SUBTEST_5((complex_qz(Matrix<std::complex<float>, Dynamic, Dynamic, RowMajor>(A_float),
Matrix<std::complex<float>, Dynamic, Dynamic, RowMajor>(B_float))));
// Test for double complex matrices
Eigen::MatrixXcd A_double, B_double;
generate_random_matrix_pair(dim, A_double, B_double);
CALL_SUBTEST_4(complex_qz(A_double, B_double));
CALL_SUBTEST_6((complex_qz(Matrix<std::complex<double>, Dynamic, Dynamic, RowMajor>(A_double),
Matrix<std::complex<double>, Dynamic, Dynamic, RowMajor>(B_double))));
}
}