在 Python 中实现 Gelfand 的光谱半径公式 - 缺乏收敛性

计算科学 Python 特征值 收敛
2021-12-09 02:39:54

对于上下文: Gelfand 的光谱半径公式是其中是任何定义明确的算子范数。limk|Ak|1/k||

我天真地编写了一个函数来计算个术语k

from numpy import linalg as la

def kth_term(matrix, k):
        matrix = la.matrix_power(matrix, k)
        f_norm = la.norm(matrix, 'fro')
        a = f_norm**(1.0/k)

        return a

它似乎收敛到一个点,然后偏离轨道。

例如,使用矩阵

[912284118]

出现在关于光谱半径的维基百科文章中

k=1: 15.362291495737216
k=2: 12.328294348193777
k=3: 11.532450663575863
k=4: 11.151002985846981
k=5: 10.921242234560514
k=6: 10.766714723560009
k=7: 10.655756642574673
k=8: 10.572406231885966
k=9: 10.507628501663131
k=10: 10.455910429510872
k=11: 10.41370221340334
k=12: 10.378620929581876
k=13: 10.349011593187207
k=14: 10.323691295053795
k=15: 10.301793374505857
k=16: 10.282669031598717
k=17: 10.265823265946006
k=18: 10.250872030293372
k=19: 10.237512882117132
k=20: 8.999022729694703
k=21: 8.267420074010055
k=22: 7.497283519619893
k=23: 6.853431593167068
k=24: 5.895962973131352
k=25: 5.867018843533908

一切都很好,此时它停止收敛到正确答案(即)。这可能是什么原因?只有三个函数调用,所以很明显,当我计算矩阵幂、Frobenius 范数或其根(或这些东西的某种组合)时,错误正在蔓延。k=2010

2个回答

Frobenius 范数不是算子范数,它是线性算子/矩阵向量空间的范数,这不是一回事。只需将其更改为任何其他预设规范,它应该可以工作。

您计算矩阵幂的方法也不稳定。Numpy 中使用的算法是基本的重复平方,它没有归一化或正则化步骤,并且对于较大的可能会爆炸。计算高矩阵幂的一种更稳定的方法是使用特征分解,但您可以直接从中计算光谱半径,因此看起来没有实际意义。你能更准确地描述你的目标和局限性吗?你这样做是为了学校吗?你的矩阵是不是太大了?等等。k

事实证明,这个问题非常简单(与 numpy 的矩阵幂函数本身的限制无关)。我最初认为传播了一些数值浮点错误 - 但我正在测试的示例矩阵仅包含整数。问题在于,在时,矩阵条目的值超过了 numpy 的最大可能 int64 值——它们在那里变成了的数量级。k=201020

如果我改变

a = np.array([[9, -1, 2], [-2, 8, 4], [1, 1, 8]])

a = np.array([[9.0, -1, 2], [-2, 8, 4], [1, 1, 8]])

在我的代码中,当我们增加的值时,Gelfand 的公式会按预期收敛。k

一些情节来证明这个问题。第一个(不正确的)对应于上面的整数类型的 numpy 数组,而第二个(正确的)对应于浮点类型的数组。

使用不正确的 int 类型绘图

使用正确的浮点类型绘图

这个解决方案并不令人惊讶,但也许在这里发布它会帮助遇到类似问题的其他人。随着的增加,您仍然会遇到数值问题(我开始处获取inf),因此我认为如果您想做得更好,则需要更复杂的方法。kk=154

代码:

import matplotlib.pyplot as plt
import numpy as np
from numpy import linalg as la

def kth_term(matrix, k=20):
    """Gelfand's formula"""
    matrix = la.matrix_power(matrix, k)
    f_norm = la.norm(matrix, 'fro')
    term = f_norm**(1.0/k)

    return term


def test_convergence_of_gelfands_formula():
    a = np.array([[9.0, -1, 2], [-2, 8, 4], [1, 1, 8]])

    for k in range(1, 100):
        term = kth_term(a, k)
        print("k={}: {}".format(k, term))
        plt.plot(k, term, 'rx')

    plt.plot(k, term, 'rx', label="kth term")
    plt.title("Convergence of Gelfand's formula for the spectral radius")
    plt.xlabel("k")
    plt.axhline(y=10, color='b', linestyle='-', label='Spectral radius')
    plt.legend()
    plt.show()


def main():
    test_convergence_of_gelfands_formula()


if __name__ == '__main__':
    main()