我有一个由Python代码计算的NxN对称和三对角矩阵,我想对它进行对角化。
在特定情况下,我处理N = 6000
,但矩阵可能会变大。由于它是稀疏的,我认为对角化它的最好方法是使用算法scipy.sparse.linalg.eigsh()
,这与其他稀疏和对称矩阵(不是三对角矩阵)表现得非常好,但我使用过。特别是,因为我只需要频谱的低位部分,所以我在函数中指定k=2
和which='SM'
。
但是,在这种情况下,此算法似乎不起作用,因为在大约20分钟的计算后,我得到以下错误:
ArpackNoConvergence:ARPACK错误-1:无收敛(60001次迭代,0/2特征向量收敛)
为什么会这样?这是与三对角矩阵的某些属性有关的问题吗?我可以使用哪种Python(以及请,只有Python!)例程来有效地对齐矩阵?
这里是要求重现错误的最小代码:
import scipy.sparse.linalg as sl
import numpy as np
dim = 6000
a = np.empty( dim - 1 )
a.fill( 1. )
diag_up = np.diag( a, 1 )
diag_bot = np.diag( a, -1 )
b = np.empty( dim )
b.fill( 1. )
mat = np.diag( b ) + diag_up + diag_bot
v, w = sl.eigsh(mat, 2, which = 'SM')
在我的电脑上,矩阵的构造需要364ms,而对角化给出报告的错误。
答案 0 :(得分:2)
ARPACK擅长于找到大幅度的特征值,但很难找到小的特征值。幸运的是,通过使用eigsh
中内置的shift-invert选项,您可以非常轻松地解决这个问题。例如,请参阅here。
import scipy.sparse.linalg as sl
import scipy.sparse as spr
import numpy as np
dim = 6000
diag = np.empty( dim )
diag.fill( 1. )
# construct the matrix in sparse format and cast to CSC which is preferred by the shift-invert algorithm
M = spr.dia_matrix((np.array([diag, diag, diag]), [0,-1, 1]), shape=(dim,dim)).tocsc()
# Set sigma=0 to find eigenvalues closest to zero, i.e. those with smallest magnitude.
# Note: under shift-invert the small magnitued eigenvalues in the original problem become the large magnitue eigenvalue
# so 'which' parameter needs to be 'LM'
v, w = sl.eigsh(M, 2, sigma=0, which='LM')
print(v)
对于这个特定的示例问题,您可以验证上面是否找到了正确的特征值,因为特征值碰巧有explicit formula:
from math import sqrt, cos, pi
eigs = [abs(1-2*cos(i*pi/(1+dim))) for i in range(1, dim+1)]
print(sorted(eigs)[0:2])
答案 1 :(得分:1)
如果您想要多个特征值,那么就性能而言,使用eigsh
可能不是一个好主意。而是考虑使用eigh_tridiagonal
,它使用适当的LAPACK例程(而不是ARPACK)。请注意,使用eigh_tridiagonal
,我认为您无法计算出最小的特征值。您可以计算所有特征值,对应于特定索引的特征值或特定范围内的特征值(请参见documentation page)。我认为,最后一种选择可能对您目前的目的有用。
下面是一些示例代码:
import numpy as np
import scipy.linalg as la
from timeit import default_timer as timer
dim = 6000
diag = np.ones(dim)
offdiag = np.ones(dim - 1)
eig_range = (-0.005, 0.005) # center it on zero for the comparison with eigsh to work properly!
lapack_time = timer()
evals_lapack, evecs_lapack = la.eigh_tridiagonal(diag, offdiag, select = 'v', select_range = eig_range)
lapack_time = timer() - lapack_time
然后您可以与稀疏ARPACK方法进行比较:
import scipy.sparse as spa
import scipy.sparse.linalg as sla
n_eigvals = np.size(evals_lapack)
evals_true = 1.0 - 2.0 * np.cos(np.arange(1, dim + 1) * np.pi / (dim + 1))
sm_idxs = np.argpartition(np.abs(evals_true), (0, n_eigvals))[:n_eigvals]
evals_true_sm = np.sort(evals_true[sm_idxs])
arpack_time = timer()
sparse_csc_matrix = spa.dia_matrix((np.array([diag, diag, diag]), [0, -1, 1]), shape = (dim, dim)).tocsc()
evals_arpack, evecs_arpack = sla.eigsh(sparse_csc_matrix, n_eigvals, sigma = 0, which = 'LM')
arpack_time = timer() - arpack_time
lapack_accuracy = np.sqrt(np.mean(np.square((evals_lapack - evals_true_sm) / evals_true_sm)))
arpack_accuracy = np.sqrt(np.mean(np.square((evals_arpack - evals_true_sm) / evals_true_sm)))
print("for", n_eigvals, "eigenvalues...")
print("are results the same?", np.allclose(evals_lapack, evals_arpack))
print("lapack time: ", lapack_time)
print("arpack time: ", arpack_time)
print("lapack error:", lapack_accuracy)
print("arpack error:", arpack_accuracy)
一些结果:
for 11 eigenvalues...
are results the same? True
lapack time: 0.027329199999996945
arpack time: 0.1122953999999936
lapack error: 1.0656446384601465e-13
arpack error: 3.0175238399729594e-13
for 1137 eigenvalues...
are results the same? True
lapack time: 10.724007
arpack time: 62.3984447
lapack error: 4.6350742410143475e-14
arpack error: 3.0063051257502015e-14