稳定 3x3 实对称矩阵特征值计算

计算科学 C++ 矩阵 特征值 浮点
2021-12-13 21:54:49

我有许多 3x3 实对称矩阵,我需要为其确定特征值。Wikipedia为这种情况提供了一个很好的非迭代算法,我已将其翻译成 C++:

#include <iostream>
#include <vector>
#include <cmath>
#include <boost/math/constants/constants.hpp>

template<typename Real>
std::vector<Real> eigenvalues(const Real A[3][3])
{
    using boost::math::constants::third;
    using boost::math::constants::pi;
    using boost::math::constants::half;

    static_assert(std::numeric_limits<Real>::is_iec559,
                  "Template argument must be a floating point type.\n");

    std::vector<Real> eigs(3, std::numeric_limits<Real>::quiet_NaN());
    auto p1 = A[0][1]*A[0][1] + A[0][2]*A[0][2] + A[1][2]*A[1][2];
    auto diag_sq = A[0][0]*A[0][0] + A[1][1]*A[1][1] + A[2][2];
    if (p1 == 0 || 2*p1/(2*p1 + diag_sq) < std::numeric_limits<Real>::epsilon())
    {
        eigs[0] = A[0][0];
        eigs[1] = A[1][1];
        eigs[2] = A[2][2];
        return eigs;
    }

    auto q = third<Real>()*(A[0][0] + A[1][1] + A[2][2]);
    auto p2 = (A[0][0] - q)*(A[0][0] - q) + (A[1][1] - q)*(A[1][1] -q) + (A[2][2] -q)*(A[2][2] -q) + 2*p1;
    auto p = std::sqrt(p2/6);
    auto invp = 1/p;
    Real B[3][3];
    B[0][0] = A[0][0] - q;
    B[0][1] = A[0][1];
    B[0][2] = A[0][2];
    B[1][1] = A[1][1] - q;
    B[1][2] = A[1][2];
    B[2][2] = A[2][2] - q;
    auto detB = B[0][0]*(B[1][1]*B[2][2] - B[1][2]*B[1][2])
              - B[0][1]*(B[0][1]*B[2][2] - B[1][2]*B[0][2])
              + B[0][2]*(B[0][1]*B[1][2] - B[1][1]*B[0][1]);
    auto r = invp*invp*invp*half<Real>()*detB;
    if (r >= 1)
    {
        eigs[0] = q + 2*p;
        eigs[1] = q - p;
        eigs[2] = 3*q - eigs[1] - eigs[0];
        return eigs;
    }

    if (r <= -1)
    {
        eigs[0] = q + p;
        eigs[1] = q - 2*p;
        eigs[2] = 3*q - eigs[1] - eigs[0];
        return eigs;
    }

    auto phi = third<Real>()*std::acos(r);
    eigs[0] = q + 2*p*std::cos(phi);
    eigs[1] = q + 2*p*std::cos(phi + 2*third<Real>()*pi<Real>());
    eigs[2] = 3*q - eigs[0] - eigs[1];

    return eigs;
}

int main()
{
    float M[3][3];
    M[0][0] = 1.25e6;
    M[1][0] = 1.25e6;
    M[0][1] = 1.25e6;
    M[1][1] = 1.25e6;
    M[0][2] = 0.0;
    M[2][0] = 0.0;
    M[1][2] = 0.0;
    M[2][1] = 0.0;
    M[2][2] = 0.0;

    auto eigs = eigenvalues<float>(M);
    std::cout << "Eigenvalues: ";
    std::cout.precision(std::numeric_limits<float>::digits10 + 5);
    std::cout << std::fixed << std::scientific;
    for (auto eig : eigs)
    {
        std::cout << eig << ", ";
    }
    std::cout << '\n';
}

矩阵的特征值为2.5×106、0 和 0。但是,程序返回2.5×106,0.0625, 和0. 是的,第二个与第一个的比率大致是浮点 epsilon,并且qp几乎相等。但是有没有办法稳定这个算法,使精度损失不那么剧烈?

2个回答

这是试图通过计算特征多项式的根来计算特征值。在这种情况下,特征多项式是p(t)=t32t2x,x=1.25×106, 零是多重性二的根。

通常没有比计算多项式的双根精度更好的方法O(ϵ),因为如果多项式由其系数指定(如此处),则幅度系数中存在扰动O(ϵ)这导致双根分裂成两个根分开O(ϵ). 这同样适用于扰动O(ϵ)也在输入矩阵中。

通常的建议是,目标是拥有一个向后稳定的算法(计算受扰输入的精确结果的算法),如果您遇到的问题对输入中的微小扰动非常敏感,那么您可能问错了问题,尽管有一些例外。所以我想说这里的重要问题是为什么准确计算矩阵的双特征值对你很重要。

请检查这一行,A[2][2] 应该是平方的:auto diag_sq = A[0][0]*A[0][0] + A[1][1]*A[1][1 ] + A[2][2];