43、快速特征求解器:QR 与 Lanczos 方法解析

快速特征求解器:QR 与 Lanczos 方法解析

1. 快速特征求解器基础

在特征求解器的领域中,存在一些基础的操作和特性。例如,在代码实现中,有如下的代码片段:

ChebyshevPoints(npts,x);
for(j=0;j<npts;j++)
    A[j] = pow(x[row],j);
delete[] x;

这里的 ChebyshevPoints 函数可能用于生成切比雪夫点,后续通过循环将这些点的幂赋值给数组 A ,最后释放 x 数组的内存。

同时,对于 Householder 矩阵,由于其特殊形式,我们无需显式地构造它。利用这种特殊形式,矩阵乘法 A * H 可以在 $O(n^2)$ 操作内完成,而不是普通矩阵 - 矩阵乘法所需的 $O(n^3)$ 操作。另外,使用 MPI Alltoall 函数来转置中间阶段的 A * H ,方便再次进行前乘 H 的操作。不过, MPI Alltoall 函数是一种非常耗费通信资源的调用,因为它需要每个进程与所有其他进程交换信息,常被用于并行机器通信能力的基准测试。

2. 全局特征求解器概述

全局特征求解器与局部特征求解器不同,它不需要对特征值有良好的初始猜测,并且能够提供整个特征谱。这里主要讨论两种基于不同概念的方法:QR 方法和 Lanczos 方法。QR 方法是半直接且通用的,而 Lanczos 方法是迭代的,适用于对称稀疏特征系统。

2.1 QR 特征求解器

2.1.1 基本原理

QR 特征求解器的主要思想是构造一系列相似矩阵 $A_1, A_2, \cdots, A_k$,其中 $A_1 = A$,并且当 $k \to \infty$ 时,$A_k$ 趋近于一个上三角矩阵,形式如下:
[
A_k \to
\begin{bmatrix}
\lambda_1 & * & * & \cdots & * \
0 & \lambda_2 & * & \cdots & * \
0 & 0 & \lambda_3 & \cdots & * \
\vdots & \vdots & \vdots & \ddots & \vdots \
0 & 0 & 0 & \cdots & \lambda_n
\end{bmatrix}
]
根据 Schur 三角化定理,如果在相似变换中涉及正交矩阵 $Q$,则该三角矩阵存在。如果矩阵 $A$ 是对称的,所有特征值都是实数,对角线上的标量元素对应这些实特征值;如果矩阵是非对称的,可能会遇到复特征值,它们会以 $2 \times 2$ 子矩阵的形式出现在对角线上,有时被称为“凸起”。

2.1.2 基本 QR 迭代

基本 QR 迭代的初始化步骤为 $A_0 = A$ 且 $A_0 = Q_0R_0$,然后进入循环:
[
\begin{align }
A_1 &= R_0Q_0 \text{ 且 } A_1 = Q_1R_1 \
A_2 &= R_1Q_1 \text{ 且 } A_2 = Q_2R_2 \
&\cdots
\end{align
}
]
在这个算法中,关键是在 $Q_iR_i$ 分解和 $R_iQ_i$ 乘积之间交替进行。可以证明序列中的所有矩阵都是相似的,例如 $A_1 = R_0Q_0 = (Q_0^TA_0)Q_0$,由于 $Q_0^TQ_0 = I$,所以 $A_1$ 与 $A_0 = A$ 相似,对于迭代 $k$ 也有 $A_k = R_{k - 1}Q_{k - 1} = (Q_{k - 1}^TA_{k - 1})Q_{k - 1}$。

该方法在所有特征值具有不同大小时收敛,对角线元素的序列满足 $|\lambda_1| > |\lambda_2| > \cdots > |\lambda_n|$,收敛速率为 $\frac{|\lambda_{i + 1}|}{|\lambda_i|}$。

2.1.3 与 LR 方法的比较

可以使用 LU 分解代替 QR 分解来生成类似的序列,这就是 LR 方法。然而,由于不能进行主元选择,否则会破坏上三角结构并可能改变特征值,所以 LU 过程是不稳定的。而且,与 LU 分解不同,QR 分解可以应用于奇异矩阵。不过,当矩阵 $A$ 是带状且对角占优时,LU 分解可能比 QR 分解更适用,因为 LU 分解可以保持带宽,而 QR 方法在每次迭代中会填充初始为零的非对角元素。

2.2 Hessenberg QR 特征求解器

基本 QR 方法有两个主要缺点:一是每一步都需要进行 QR 分解,成本为 $O(\frac{4n^3}{3})$,是 LU 分解的两倍;二是如果两个特征值具有相同大小,方法会发散。

Hessenberg QR 特征求解器通过先将矩阵 $A$ 转换为上 Hessenberg 矩阵 $H$,然后对 $H$ 进行 QR 分解来解决这些问题。初始的 Hessenberg 约化成本为 $O(\frac{5}{3}n^3)$,但 Hessenberg 矩阵的 QR 分解成本为 $O(n^2)$,所以该方法比基本 QR 特征求解器更高效。

2.3 移位 QR 特征求解器

QR 特征求解器的收敛速率最多是线性的,为了提高收敛速率,我们可以采用移位的思想。假设矩阵只有实单特征值,我们对 $(H - \sigma I)$ 进行 QR 分解,而不是对 $H$ 进行分解,将整个特征谱移动 $\sigma$ 到 $(\lambda_i - \sigma)$。

具体来说,选择 $\sigma$ 使得位置 $(n, n - 1)$ 的元素尽可能快地收敛到零。收敛后,位置 $(n, n)$ 的元素(减去移位 $\sigma$ 后)就是所需的特征值。修改后的算法如下:
1. 进行 QR 分解:$H_k - h_{nn}^{(k)}I = Q_kR_k$
2. 获得新的迭代:$H_{k + 1} = R_kQ_k + h_{nn}^{(k)}I$

这种移位方法的收敛通常是二次的,如果原始矩阵 $A$ 是对称的,则收敛速率会增加到三次。对于双特征值或复特征值,移位策略需要改变,以有效处理矩阵 $A_k$ 对角线上的“凸起”。

2.4 对称 QR 特征求解器:Wilkinson 移位

对称矩阵具有特殊性质,其特征值都是实数,并且 Hessenberg 变换可以将矩阵约化为对称三对角矩阵。QR 迭代会保持这个性质。对称三对角矩阵的 QR 分解成本仅为 $O(n)$,而不是 Hessenberg 矩阵的 $O(n^2)$。

Wilkinson 建议使用 $2 \times 2$ 矩阵
[
\begin{bmatrix}
h_{n - 1, n - 1} & h_{n, n - 1} \
h_{n, n - 1} & h_{nn}
\end{bmatrix}
]
更接近 $h_{nn}$ 的特征值作为移位 $\sigma$ 来加速收敛。对称 QR 特征求解器的收敛是三次的,操作计数为 $O(n^2)$,但将 $A$ 约化为三对角矩阵有 $O(\frac{2}{3}n^3)$ 的成本。

2.5 并行 QR 特征求解器:分治法

Dongarra 和 Sorensen 开发了一种用于对称特征值问题的全并行算法,基于 QR 迭代和分治法。首先将矩阵 $A$ 约化为 Hessenberg 形式,然后将其转换为三对角矩阵 $T$,接着将 $T$ 分成两个半大小的对称三对角矩阵 $T_1$ 和 $T_2$。

通过适当的正交矩阵 $Q_1$ 和 $Q_2$ 对 $T_1$ 和 $T_2$ 进行对角化,将原问题转化为求解矩阵 $G = D + b_k\xi\xi^T$ 的特征值问题,其中 $D$ 是对角矩阵,$\xi$ 是向量。特征值 $\lambda_i$ 由方程
[
1 + b_k\sum_{i = 1}^{n}\frac{\xi_i^2}{d_i - \lambda} = 0
]
给出,这就是所谓的久期方程。

下面是一个简单的分治过程示例:

graph TD;
    A[矩阵 A] --> B[约化为 Hessenberg 矩阵 H];
    B --> C[转换为三对角矩阵 T];
    C --> D[分成 T1 和 T2];
    D --> E[对角化 T1 和 T2];
    E --> F[求解久期方程得到特征值];
    F --> G[计算特征向量];

2.6 并行 QR 特征求解器的代码实现

以下是一个使用 C++/MPI 实现的并行 QR 特征求解器的代码示例:

#include <iostream.h>
#include <iomanip.h>
#include "SCmathlib.h"
#include "SCchapter10.h"
#include <mpi.h>
// Global variable to set size of the system
const int size = 50;
int main(int argc, char *argv[]){
    int i,j,k,ll,m,isum,ioffset,cnt,N1,N2;
    int mynode, totalnodes;
    MPI-Status status;
    double bn,*tmpd,**tmpdd;
    MPI-Init(&argc,&argv);
    MPI-Comm-size(MPI-COMM-WORLD, &totalnodes);
    MPI-Comm-rank(MPI-COMM-WORLD, &mynode);
    // Set up storage on each process
    double * a = new double[size];
    double * b = new double[size];
    double * lambda = new double[size];
    double ** Q = CreateMatrix(size,size);
    double ** Q1 = CreateMatrix(size,size);
    double ** Q2 = CreateMatrix(size,size);
    double * xi = new double[size];
    double * d = new double[size];
    int ** index = ICreateMatrix(totalnodes,totalnodes);
    double ** adjust = CreateMatrix(totalnodes,totalnodes);
    //Form the Matrix A
    for(i=0;i<size;i++){
        a[i] = i+1.5;
        b[i] = 0.3;
    }
    //Set up recursive partitioning of the matrix.
    //Each process will solve for some subset of the problem
    index[0][0] = size;
    for(i=0;i<log2(totalnodes);i++){
        isum = 0;
        for(j=0;j<pow(2,i);j++){
            index[i+1][2*j] = index[i][j]/2;
            index[i+1][2*j+1] = index[i][j] - index[i+1][2*j];
            isum += index[i+1][2*j];
            adjust[i][j] = b[isum-1];
            a[isum-1] = a[isum-1] - b[isum-1];
            a[isum] = a[isum] - b[isum-1];
            isum += index[i+1][2*j+1];
        }
    }
    // Each process solves recursively for its subpart of
    // the problem.
    ioffset = (int) log2(totalnodes);
    isum = 0;
    for(k=0;k<mynode;k++)
        isum += index[ioffset][k];
    TDQREigensolver(index[ioffset][mynode],&a[isum],
                    &b[isum],d,Q1);
    // Fan-in algorithm to finish solving system
    for(i=0;i<log2(totalnodes);i++){
        isum = 0; cnt = 0;
        for(j=0;j<totalnodes;j+=(int)pow(2.0,i)){
            if(mynode == j){
                if(isum%2==0){
                    MPI-Recv(d+index[ioffset][isum],index[ioffset]
                               [isum+1],MPI-DOUBLE, j+(int)pow(2.0,i),1,
                               MPI-COMM-WORLD,&status);
                    for(k=0;k<index[ioffset][isum+1];k++)
                        MPI-Recv(Q2[k],index[ioffset][isum+1],MPI-DOUBLE,
                                 j+(int)pow(2.0,i),1,MPI-COMM-WORLD,
                                 &status);
                    N1 = index[ioffset][isum];
                    N2 = index[ioffset][isum+1];
                    bn = adjust[ioffset-1][cnt++];
                }
                else{
                    MPI-Send(d,index[ioffset][isum],MPI-DOUBLE,
                             j-(int)pow(2.0,i),1,MPI-COMM-WORLD);
                    for(k=0;k<index[ioffset][isum];k++)
                        MPI-Send(Q1[k],index[ioffset][isum],MPI-DOUBLE,
                                 j-(int)pow(2.0,i),1,MPI-COMM-WORLD);
                }
            }
            isum++;
        }
        for(j=0;j<totalnodes;j+=(int)pow(2.0,i+1)){
            if(mynode == j){
                cnt = 0;
                for(k=0;k<N1;k++)
                    xi[cnt++] = Q1[N1-1][k];
                for(k=0;k<N2;k++)
                    xi[cnt++] = Q2[0][k];
                // Solve for the secular equation to
                // obtain eigenvalues
                SolveSecularEq(bn,N1+N2,d,xi,lambda);
                // Form the Q matrix from Q1 and Q2
                for(k=0;k<N1;k++){
                    for(ll=0;ll<N1+N2;ll++){
                        Q[k][ll] = 0.0;
                        for(m=0;m<N1;m++)
                            Q[k][ll] += Q1[k][m]*xi[m]/(d[m]-lambda[ll]);
                    }
                }
                for(k=0;k<N2;k++){
                    for(ll=0;ll<N1+N2;ll++){
                        Q[N1+k][ll] = 0.0;
                        for(m=0;m<N2;m++)
                            Q[k+N1][ll] += Q2[k][m]*xi[N1+m]/
                                (d[N1+m]-lambda[ll]);
                    }
                }
                // Normalize the Q matrix so that each eigenvector
                // has length one
                double sum;
                for(k=0;k<N1+N2;k++){
                    sum = 0.0;
                    for(ll=0;ll<N1+N2;ll++)
                        sum+= Q[ll][k]*Q[ll][k];
                    sum = sqrt(sum);
                    for(ll=0;ll<N1+N2;ll++)
                        Q[ll][k] = Q[ll][k]/sum;
                }
                // Swap d and lambda arrays for use in the
                // next part of the fan-in algorithm
                tmpd = d;
                d = lambda;
                lambda = tmpd;
                // Swap Q and Q1 for use in the
                // next part of the fan-in algorithm
                tmpdd = Q1;
                Q1 = Q;
                Q = tmpdd;
            }
        }
        ioffset = ioffset - 1;
    }
    if(mynode==0){
        cout << "The eigenvalues are: " << endl;
        for(k=0;k<size;k++)
            cout << d[k] << endl;
    }
    MPI-Barrier(MPI-COMM-WORLD);
    delete[] a;
    delete[] b;
    delete[] lambda;
    delete[] xi;
    delete[] d;
    DestroyMatrix(Q,size,size);
    DestroyMatrix(Q1,size,size);
    DestroyMatrix(Q2,size,size);
    DestroyMatrix(adjust,totalnodes,totalnodes);
    IDestroyMatrix(index,totalnodes,totalnodes);
    MPI-Finalize();
}

代码中的一些注意事项:
- 每个进程最初使用递归例程 TDQREigensolver 解决其分配的子问题。
- 使用 SolveSecularEq 例程来求解久期方程,该例程实现了一个简单的二分算法来寻找久期方程的根。
- 求解特征向量的方程 $y_i = (D - \lambda_iI)^{-1}\xi$ 并不总是数值稳定的,在某些文献中提出了更稳定的替代方法。
- 在分治过程中,空闲进程可以被调用回来解决久期方程或完成所需的矩阵乘法部分,以提高并行效率。

2.7 Lanczos 特征求解器

Lanczos 算法在 20 世纪 50 年代由 Lanczos 开发,特别适用于大型对称稀疏矩阵。QR 方法也可用于此类系统,但在每次迭代中不能保持新矩阵的稀疏性,因此效率较低。

2.7.1 基本原理

Lanczos 方法的关键思想是利用幂法迭代中产生的所有先前迭代向量 $v, Av, A^2v, \cdots, A^{k - 1}v$,这些 $k$ 个向量形成了所谓的 Krylov 空间 $K_k(A, v) = span{v, Av, A^2v, \cdots, A^{k - 1}}$。

为了使这些向量在计算上更友好,我们需要对它们进行正交化。这里使用了魔法三项递推公式来进行快速正交化。假设已经从前面的序列中产生了正交向量 $q_1, q_2, \cdots, q_k$,我们可以构造正交矩阵 $Q$,并通过相似变换 $Q^TAQ = T$ 将 $A$ 转换为其相似矩阵 $T$,其中 $T$ 是一个三对角矩阵,形式如下:
[
T_k =
\begin{bmatrix}
\alpha_1 & \beta_1 & 0 & \cdots & 0 \
\beta_1 & \alpha_2 & \beta_2 & \cdots & 0 \
0 & \beta_2 & \alpha_3 & \cdots & 0 \
\vdots & \vdots & \vdots & \ddots & \vdots \
0 & 0 & 0 & \cdots & \alpha_k
\end{bmatrix}
]
通过正交性条件,我们可以计算出系数 $\alpha_i$ 和 $\beta_i$。

2.7.2 算法步骤

Lanczos 算法的正交化版本如下:

LANCZOS ALGORITHM
Initialize:
Choose q0 = 0; arbitrary
r ≠ 0; and β0 = ∥r∥2
Begin Loop: for j = 1, ...
    qj = r / βj−1
    r = Aqj
    αj = qj^T r
    r = r - qj−1βj−1
    r = r - qjαj
    Orthogonalize if necessary
    βj = ∥r∥2
    Compute approximate eigenvalues of Tj
    Test for convergence
endfor
End Loop
2.7.3 算法特点
  • 该算法每次迭代可以使用单个矩阵 - 向量乘积和 BLAS1 例程(如 ddot 和 daxpy )实现,在任何给定时间只需要存储三个向量 $r, q_j, q_{j - 1}$。
  • 如果进行 $n$ 次迭代,我们可以获得矩阵 $A$ 的整个特征谱。也可以在任何迭代 $j < n$ 时停止,得到矩阵 $A$ 的前 $j$ 个特征值的近似值。
  • 如果矩阵 $A$ 的特征值分离不好,可以使用移位并采用矩阵 $(A - \sigma I)^{-1}$ 来生成适当的 Krylov 子空间。
  • Lanczos 算法可以扩展到非对称系统。

2.8 总结

本文介绍了多种特征求解器,包括 QR 特征求解器及其各种改进版本,如 Hessenberg QR 特征求解器、移位 QR 特征求解器和对称 QR 特征求解器,以及 Lanczos 特征求解器。不同的方法适用于不同类型的矩阵,例如 QR 方法适用于一般矩阵,而 Lanczos 方法适用于大型对称稀疏矩阵。在实际应用中,需要根据矩阵的特点和问题的需求选择合适的方法。同时,通过并行计算可以提高特征求解的效率,如并行 QR 特征求解器的分治法实现。

2.9 展望

未来的研究可以集中在进一步提高这些特征求解器的效率和稳定性上。例如,对于 Lanczos 算法,可以研究更有效的正交化方法来解决正交性丢失的问题。对于并行特征求解器,可以探索更高效的并行算法和通信策略,以充分利用多核和分布式计算资源。此外,还可以研究如何将这些特征求解器应用于更广泛的领域,如机器学习、数据分析和科学计算等。

2.10 算法对比

为了更清晰地了解不同特征求解器的特点,下面对几种主要的特征求解器进行对比,具体内容如下表所示:
| 特征求解器 | 适用矩阵类型 | 计算复杂度 | 收敛情况 | 特点 |
| — | — | — | — | — |
| 基本 QR 特征求解器 | 一般矩阵 | 每步 QR 分解成本 $O(\frac{4n^3}{3})$ | 所有特征值不同大小时收敛,收敛速率 $\frac{|\lambda_{i + 1}|}{|\lambda_i|}$ | 半直接且通用,但成本高,两特征值同大小时发散 |
| Hessenberg QR 特征求解器 | 一般矩阵 | 初始 Hessenberg 约化 $O(\frac{5}{3}n^3)$,后续 QR 分解 $O(n^2)$ | 改善了基本 QR 方法的收敛问题 | 先将矩阵转换为 Hessenberg 矩阵再分解,更高效 |
| 移位 QR 特征求解器 | 一般矩阵 | - | 通常二次收敛,对称矩阵三次收敛 | 通过移位提高收敛速率 |
| 对称 QR 特征求解器(Wilkinson 移位) | 对称矩阵 | 约化到三对角矩阵 $O(\frac{2}{3}n^3)$,后续操作 $O(n^2)$ | 三次收敛 | 利用对称矩阵特性,采用 Wilkinson 移位加速 |
| 并行 QR 特征求解器(分治法) | 对称矩阵 | - | - | 基于分治法,适合并行计算,提高效率 |
| Lanczos 特征求解器 | 大型对称稀疏矩阵 | - | - | 利用 Krylov 空间,保持稀疏性,可局部或全局求解特征值 |

2.11 实际应用建议

在实际应用中,选择合适的特征求解器至关重要。以下是一些根据矩阵特点的选择建议:
- 一般矩阵 :如果矩阵规模较小且特征值差异较大,基本 QR 特征求解器可以作为一个选择。但如果对计算效率有较高要求,Hessenberg QR 特征求解器可能更合适。
- 对称矩阵 :对于对称矩阵,对称 QR 特征求解器(Wilkinson 移位)通常能提供较好的收敛速度和计算效率。如果矩阵规模较大且具备并行计算条件,并行 QR 特征求解器(分治法)是一个不错的选择。
- 大型对称稀疏矩阵 :Lanczos 特征求解器是处理这类矩阵的首选,它能够充分利用矩阵的稀疏性,减少不必要的计算。

2.12 代码优化思路

对于前面给出的并行 QR 特征求解器的代码,我们可以从以下几个方面进行优化:
- 通信优化 :在代码中, MPI Alltoall 函数调用是一个通信瓶颈。可以考虑采用更高效的通信模式,例如使用非阻塞通信来重叠计算和通信,减少等待时间。
- 内存管理 :代码中使用了大量的动态内存分配,如 new 操作。在使用完后及时释放内存,避免内存泄漏。同时,可以考虑使用内存池技术来减少内存分配和释放的开销。
- 算法优化 :对于 SolveSecularEq 例程,可以采用更高效的根查找算法,如修改后的 Newton - Raphson 方法,以提高求解久期方程的效率。

2.13 优化后的代码示例(部分)

以下是对通信部分进行优化的代码示例,使用非阻塞通信:

#include <iostream.h>
#include <iomanip.h>
#include "SCmathlib.h"
#include "SCchapter10.h"
#include <mpi.h>
// Global variable to set size of the system
const int size = 50;
int main(int argc, char *argv[]){
    int i,j,k,ll,m,isum,ioffset,cnt,N1,N2;
    int mynode, totalnodes;
    MPI_Request request[2];
    MPI_Status status[2];
    double bn,*tmpd,**tmpdd;
    MPI_Init(&argc,&argv);
    MPI_Comm_size(MPI_COMM_WORLD, &totalnodes);
    MPI_Comm_rank(MPI_COMM_WORLD, &mynode);
    // Set up storage on each process
    double * a = new double[size];
    double * b = new double[size];
    double * lambda = new double[size];
    double ** Q = CreateMatrix(size,size);
    double ** Q1 = CreateMatrix(size,size);
    double ** Q2 = CreateMatrix(size,size);
    double * xi = new double[size];
    double * d = new double[size];
    int ** index = ICreateMatrix(totalnodes,totalnodes);
    double ** adjust = CreateMatrix(totalnodes,totalnodes);
    //Form the Matrix A
    for(i=0;i<size;i++){
        a[i] = i+1.5;
        b[i] = 0.3;
    }
    //Set up recursive partitioning of the matrix.
    //Each process will solve for some subset of the problem
    index[0][0] = size;
    for(i=0;i<log2(totalnodes);i++){
        isum = 0;
        for(j=0;j<pow(2,i);j++){
            index[i+1][2*j] = index[i][j]/2;
            index[i+1][2*j+1] = index[i][j] - index[i+1][2*j];
            isum += index[i+1][2*j];
            adjust[i][j] = b[isum-1];
            a[isum-1] = a[isum-1] - b[isum-1];
            a[isum] = a[isum] - b[isum-1];
            isum += index[i+1][2*j+1];
        }
    }
    // Each process solves recursively for its subpart of
    // the problem.
    ioffset = (int) log2(totalnodes);
    isum = 0;
    for(k=0;k<mynode;k++)
        isum += index[ioffset][k];
    TDQREigensolver(index[ioffset][mynode],&a[isum],
                    &b[isum],d,Q1);
    // Fan-in algorithm to finish solving system
    for(i=0;i<log2(totalnodes);i++){
        isum = 0; cnt = 0;
        for(j=0;j<totalnodes;j+=(int)pow(2.0,i)){
            if(mynode == j){
                if(isum%2==0){
                    MPI_Irecv(d+index[ioffset][isum],index[ioffset]
                               [isum+1],MPI_DOUBLE, j+(int)pow(2.0,i),1,
                               MPI_COMM_WORLD,&request[0]);
                    for(k=0;k<index[ioffset][isum+1];k++)
                        MPI_Irecv(Q2[k],index[ioffset][isum+1],MPI_DOUBLE,
                                 j+(int)pow(2.0,i),1,MPI_COMM_WORLD,
                                 &request[1]);
                    // 可以在这里插入一些计算代码,与通信重叠
                    MPI_Waitall(2, request, status);
                    N1 = index[ioffset][isum];
                    N2 = index[ioffset][isum+1];
                    bn = adjust[ioffset-1][cnt++];
                }
                else{
                    MPI_Isend(d,index[ioffset][isum],MPI_DOUBLE,
                             j-(int)pow(2.0,i),1,MPI_COMM_WORLD,&request[0]);
                    for(k=0;k<index[ioffset][isum];k++)
                        MPI_Isend(Q1[k],index[ioffset][isum],MPI_DOUBLE,
                                 j-(int)pow(2.0,i),1,MPI_COMM_WORLD,&request[1]);
                    // 可以在这里插入一些计算代码,与通信重叠
                    MPI_Waitall(2, request, status);
                }
            }
            isum++;
        }
        for(j=0;j<totalnodes;j+=(int)pow(2.0,i+1)){
            if(mynode == j){
                cnt = 0;
                for(k=0;k<N1;k++)
                    xi[cnt++] = Q1[N1-1][k];
                for(k=0;k<N2;k++)
                    xi[cnt++] = Q2[0][k];
                // Solve for the secular equation to
                // obtain eigenvalues
                SolveSecularEq(bn,N1+N2,d,xi,lambda);
                // Form the Q matrix from Q1 and Q2
                for(k=0;k<N1;k++){
                    for(ll=0;ll<N1+N2;ll++){
                        Q[k][ll] = 0.0;
                        for(m=0;m<N1;m++)
                            Q[k][ll] += Q1[k][m]*xi[m]/(d[m]-lambda[ll]);
                    }
                }
                for(k=0;k<N2;k++){
                    for(ll=0;ll<N1+N2;ll++){
                        Q[N1+k][ll] = 0.0;
                        for(m=0;m<N2;m++)
                            Q[k+N1][ll] += Q2[k][m]*xi[N1+m]/
                                (d[N1+m]-lambda[ll]);
                    }
                }
                // Normalize the Q matrix so that each eigenvector
                // has length one
                double sum;
                for(k=0;k<N1+N2;k++){
                    sum = 0.0;
                    for(ll=0;ll<N1+N2;ll++)
                        sum+= Q[ll][k]*Q[ll][k];
                    sum = sqrt(sum);
                    for(ll=0;ll<N1+N2;ll++)
                        Q[ll][k] = Q[ll][k]/sum;
                }
                // Swap d and lambda arrays for use in the
                // next part of the fan-in algorithm
                tmpd = d;
                d = lambda;
                lambda = tmpd;
                // Swap Q and Q1 for use in the
                // next part of the fan-in algorithm
                tmpdd = Q1;
                Q1 = Q;
                Q = tmpdd;
            }
        }
        ioffset = ioffset - 1;
    }
    if(mynode==0){
        cout << "The eigenvalues are: " << endl;
        for(k=0;k<size;k++)
            cout << d[k] << endl;
    }
    MPI_Barrier(MPI_COMM_WORLD);
    delete[] a;
    delete[] b;
    delete[] lambda;
    delete[] xi;
    delete[] d;
    DestroyMatrix(Q,size,size);
    DestroyMatrix(Q1,size,size);
    DestroyMatrix(Q2,size,size);
    DestroyMatrix(adjust,totalnodes,totalnodes);
    IDestroyMatrix(index,totalnodes,totalnodes);
    MPI_Finalize();
}

2.14 总结与展望

本文全面介绍了多种快速特征求解器,包括 QR 特征求解器及其改进版本和 Lanczos 特征求解器。通过对比不同求解器的特点和适用场景,我们可以根据具体的矩阵类型和问题需求做出合理的选择。同时,给出了代码优化的思路和示例,以提高特征求解的效率。

未来,随着计算能力的不断提升和应用场景的不断拓展,特征求解器的研究将朝着更高效率、更高稳定性和更广泛适用性的方向发展。例如,结合深度学习技术,开发自适应的特征求解算法,根据矩阵的特征自动选择最优的求解方法。此外,对于大规模分布式系统中的特征求解问题,还需要进一步研究高效的并行算法和通信策略。

以下是一个整体的流程图,展示了特征求解器的选择和处理流程:

graph LR;
    A[矩阵类型] --> B{是否为对称矩阵};
    B -- 是 --> C{是否为稀疏矩阵};
    C -- 是 --> D[Lanczos 特征求解器];
    C -- 否 --> E[对称 QR 特征求解器(Wilkinson 移位)];
    B -- 否 --> F{矩阵规模和特征值情况};
    F -- 规模小且特征值差异大 --> G[基本 QR 特征求解器];
    F -- 其他 --> H[Hessenberg QR 特征求解器];
    D --> I[求解特征值和特征向量];
    E --> I;
    G --> I;
    H --> I;
    I --> J{是否需要并行计算};
    J -- 是 --> K[并行 QR 特征求解器(分治法)];
    J -- 否 --> L[串行求解结果];

通过以上的介绍和分析,希望读者能够对快速特征求解器有更深入的理解,并在实际应用中做出正确的决策。

相关推荐

38、快速线性求解器:Cholesky分解、QR分解与共轭梯度法

本文深入探讨了几种高效的线性系统求解方法,包括Cholesky分解、QR分解和预条件共轭梯度法(PCGM)。首先介绍了Cholesky分解的原理、算法实现及其优缺点,适用于对称正定矩阵。随后详细讲解了基于Householder变换的QR分解过程,包括其稳定性和计算复杂度,并通过示例演示了具体实现步骤。接着重点介绍了预条件共轭梯度法,分析了其收敛速率、预条件矩阵的作用与选择策略,并提供了Python实现示例。最后对各种方法的适用场景、优劣进行了比较总结,为不同矩阵类型和问题需求提供了求解器选择建议。

uber9的博客 240

【信息科学与工程学】【数据科学】第十八篇 数值分析 系列二 数值算法设计01

编号类型领域问题问题的数学分析及数值分析算法逐步推理思考的数学表达式参数列表及参数的边界范围及条件及常数关联知识1数值积分数值逼近计算定积分 I=∫ab​f(x)dx的近似值。数学分析:将积分区间等分,利用插值多项式近似被积函数,构造机械求积公式。数值分析:Newton-Cotes公式是等距节点的插值型求积公式。高阶公式(n≥8)因Cotes系数有正负,数值不稳定。偶阶公式(如Simpson)具有n+1次代数精度。为提高精度,常采用复化求积法(将区间细分后应用低阶公式)。

weixin_49199313的博客 156

【信息科学与工程学】【数据科学】第五十一篇 数据仓库00 基础设计01

数据仓库设计的系统性解决方案,涵盖架构模式、分层设计、百万级访问支撑、海量数据配置及与数据库/数据湖的对接策略: ​​ODS(操作数据层)​​:原始数据镜像,保留历史快照 ​​DWD(明细数据层)​​:清洗、标准化、维度退化 ​​DWS(汇总数据层)​​:主题宽表、轻度聚合 ​​ADS(应用数据层)​​:高度聚合,面向报表/API ​​二、百万用户访问架构设计​​ ​​1. 核心架构组件​​ ​​2. 关键配置参数(以Snowflake为例)​​ ​​3. 性能优化措

weixin_49199313的博客 1201

【信息科学与工程学】【物理/化学和工程技术】第六十八篇 密度/介质/刚度/强度/散射/投影矩阵01

编码一级领域二级大类三级子类/具体行为说明/举例核心干系人示例1法律领域刑事违法暴力侵害逻辑非法侵害他人生命、健康、自由,破坏公共安全。犯罪嫌疑人、被害人、公检法机关2法律领域刑事违法财产侵占逻辑以非法占有为目的,侵害公私财产所有权。犯罪嫌疑人、财产权利人、公检法机关3法律领域刑事违法破坏秩序逻辑挑战国家法律确立的社会、经济管理秩序。犯罪嫌疑人、国家/社会、监管部门4法律领域刑事违法职务滥用逻辑滥用职权或玩忽职守,损害国家或公民权益。职务犯罪人、所在单位、监察司法机关5法律领域刑事违法具体行为:故意杀人​非

weixin_49199313的博客 1158

【信息科学与工程学】【物理/化学和工程技术】【数学分析】第一百九十九篇 矢量分析和场论及高温合金——低空经济01

br>∗∗散度∗∗:\nabla \cdot \mathbf{F} = \frac{1}{h_1 h_2 h_3} \left[ \frac{\partial}{\partial u_1}(h_2 h_3 F_1) + \frac{\partial}{\partial u_2}(h_3 h_1 F_2) + \frac{\partial}{\partial u_3}(h_1 h_2 F_3) \right]。计算\frac{\partial \rho}{\partial t} = \psi^

weixin_49199313的博客 253

线性方程组求解器

用此程序包可以求解大型稀疏矩阵方程组,还可以 并行,优化提高效率

zed(Rust)开源项目完整源码

「zed(Rust)开源项目完整源码」是开源项目(Rust)。项目简介:Code at the speed of thought – Zed is a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter.源码完整,下载解压即可查看使用,适合学习参考、课程设计与二次开发。

IEC 63281-3-1:2024 电动运输车 - 第3-1部分:考虑实际使用温度条件的电动滑板车总运行时间性能测试方法 - 完整英文电子版(30页)

IEC 63281-3-1:2024规定了电动滑板车单人运输总运行时间的测试方法,考虑了用户在不同温度下驾驶时的实际使用温度条件,用于公路或公共场所。 本文件不涵盖残障人士或老年人的电动滑板车。此外,本文件不包括货运电动滑板车。

YOLO26算法表面凹坑与条纹检测+训练好的模型+610张数据集+pyqt可视化界面.zip

附训练好的 YOLO 权重与 PyQt 可视化检测界面,页面底部含可视化效果预览,毕设/课设开箱即用。 【数据集概况】 · 检测类别(中文):[未获取()] 【训练曲线与评估图】 【模型训练配置】 参数 | 值 模型 | yolo26n 训练轮数 | 100 epochs 输入尺寸 | 640x640 批次大小 | 24 优化器 | auto 初始学习率 | 0.01 训练设备 【关键指标汇总】 训练了 100 个 epoch,最终轮指标: 指标 | 数值 mAP50 | **0.7184** mAP50-95 | 0.4372 Precision | 0.6793 Recall | 0.7024 train/box_loss | 1.1399 train/cls_loss | 0.7578 val/box_loss | 1.3453 val/cls_loss | 1.0488 【训练过程分析】 记得从开始第一轮训练起,那曲线就一直像坐滑梯一样,很快地就下滑到了低谷值。大概到第 10 轮时,模型就开始缓慢而持续地从 0.5 左右的 mAP50 走向平稳上升的通道。整个训练过程,除了初期的那股势如破竹的气势外,中间部分的曲线就变得起伏不定,有时还会在某个阶段停滞不前,似乎在寻找下一个前进的方向。而到了后期,曲线又慢慢开始平稳下来,从第 80 轮开始,mAP50 的提升逐渐减缓,这时候,我不得不面对一个问题:是继续等待模型自行收敛,还是尝试更多的调参策略。在经过一轮轮的观察和思考后,我觉得继续等待已经不太可能有显著的进步,于是决定在第 95 轮时停下了脚步,最终完成了总共 100 轮的训练任务。 在训练过程的后半程,我注意到 loss 曲线虽然总体下降趋势明显,但在某些阶段却出现了停滞,特别是在第 70 轮到... 使用场景:毕业设计、课程设计、深度学习入门、工业场景落地验证。

编程语言扩展与跨边界架构设计:五种组合方式、统一判定与框架开发(20章)

本资源是一份 20章系统化技术长文,围绕“宿主语言如何借外部力量获得自身不具备的能力”展开,建立 “脚本解释器核心—适配层—被组合组件”三层统一模型,系统拆解 进程级、函数级、协议级、语言级、数据级 五种边界,并给出可复用的 三问拆解法、跨边界四要素、适配层厚薄定律与横向判定规则。内容覆盖 Shell 管道、Python import、requests/psycopg2、setuptools entry_points、Make/CMake、CI/CD、Lua 嵌入式脚本、Airflow DAG 等八个典型实例,深入讲解进程通信、ABI/FFI、动态库加载、import 协议、插件发现与入口点、HTTP 与数据库线协议、序列化与 Schema、构建编排、任务调度与数据管道等机制。文档同时包含混合系统主导/从属方式判定、选型决策树、反模式批判、框架开发清单、插件 SDK 设计、隔离策略、性能预算、可观测治理、综合实例、术语表、原文核心内容收录与交付校验清单。本资源适合 架构师、框架/平台开发者、插件系统设计者、跨语言集成工程师、后端与数据平台工程师、技术负责人 阅读,可作为技术选型、架构评审、插件体系设计、跨边界排障与团队内训的参考手册。关键词: 编程语言扩展、跨边界架构、五种边界、三层模型、进程级组合、函数级组合、协议级组合、语言级组合、数据级组合、插件系统、FFI、ABI、import机制、HTTP、数据库线协议、CI/CD、Lua、Airflow、框架设计、技术选型。

植物大战僵尸神人版-手机版-arm64-2026.10.30-手机版.apk.1

植物大战僵尸神人版-手机版-arm64-2026.10.30-手机版.apk.1

不平衡电网下自抗扰顺序模型预测PWM整流器控制仿真模型(Simulink仿真实现)

不平衡电网下自抗扰顺序模型预测PWM整流器控制仿真模型(Simulink仿真实现)

随机森林的可解释性分析(含python代码)

随机森林的“黑箱”特性逐渐凸显,这使得我们难以理解和解释模型内部的工作机制。对于许多应用场景而言,仅仅获得高准确率的预测结果是不够的;了解预测背后的逻辑同样重要,特别是在医疗诊断、金融风险评估等领域。因此,对随机森林进行可解释性分析变得尤为重要。

m0_59257547的博客 3847

如何免费访问和使用Gemini API?

Gemini Ultra具有最先进的性能,在几个指标上超过了GPT-4的性能。它是第一个在大规模多任务语言理解基准测试中超越人类专家的模型,该基准测试57个不同学科的世界知识和解决问题的能力。Gemini是谷歌迄今为止开发的最先进、最庞大的AI模型。它为多模态而建,理解并处理不同类型的数据,比如文本、代码、音频、图像和视频。下面我们将加载Masood Aslami的图像,并用它来测试Gemini Pro Vision的多模态性。有了Gemini可以为查询提供图像、音频和文本,获得几乎完美的答案。

java_cjkl的博客 1万+

我用 Claude Code 给 Spring Boot 老项目做了一次代码 Review:查出 4 类隐藏问题

接手 Spring Boot 老项目时,真正麻烦的往往不是编译报错,而是分散在业务代码中的空指针、N+1 查询、异常吞没和并发隐患。本文记录一次使用 Claude Code 辅助代码 Review 的完整流程,并给出提示词、Java 示例、修改方案和人工复核方法。

海波的专栏 396

LTE随机接入实战:如何通过PRACH前导码优化小区覆盖半径(附计算公式)

本文深入探讨了LTE网络中PRACH前导码格式对小区覆盖半径的关键影响。通过解析不同前导码格式(如Format 0-3)的物理参数与覆盖能力,提供了核心计算公式与实战Python脚本,指导网络工程师根据覆盖需求、环境多径和容量等因素,科学选择前导码格式以优化边缘用户接入成功率,实现精准的网络规划与优化。

qq_27496129的博客 572

本地部署大模型:dgx-spark-ai项目硬件选型、部署与私有化AI构建指南

大语言模型(LLM)的本地部署是当前AI工程实践中的重要方向,其核心原理在于将模型权重与计算过程完全置于用户可控的硬件环境中。这项技术的核心价值在于实现了数据主权与隐私安全,避免了云端服务的延迟、持续订阅成本及数据外泄风险。从技术实现看,本地部署的关键在于硬件选型与软件环境适配,尤其是NVIDIA GPU的CUDA生态、显存容量与内存带宽的匹配。在应用场景上,本地化部署特别适合对数据敏感性要求高的领域,如企业内部代码助手、金融分析或医疗研究,能够支持私有化模型微调与定制化工作流构建。本文以dgx-spark

weixin_42531579的博客 502

STM32cubemx配置驱动DHT11模块

本篇文章将带大家学习使用DHT11湿温度模块,使用这个模块可以获取当前的温度和湿度数据。DHT11可以用于智能家居,蔬菜大棚等项目当中适用范围广。DHT11 是一种数字式温湿度传感器,由杜邦公司推出。它可以测量环境中的温度和湿度,并将测量结果以数字形式传输给微控制器或其他电子设备,适用于通过数字信号读取环境温湿度的应用场景。DHT11 传感器使用单总线数据交换协议,可以通过一个 I/O 引脚进行控制和数据传输。

m0_49476241的博客 5912

我终于停止写 JUnit 了!用 JavaParser + GPT-4 自动生成 90% 覆盖率的单元测试

本文介绍了一种基于JavaParser和GPT-4的智能单元测试生成方案。针对开发者在单元测试编写中面临的繁琐、无聊和硬指标等问题,提出通过AST语法树分析代码结构,提取关键信息后生成精准提示词,再调用GPT-4自动生成高质量的单元测试代码。文章详细阐述了技术原理、实现步骤(包括JavaParser解析、上下文感知提示词构建和GPT-4调用),并展示了该方法能自动识别依赖、Mock对象和覆盖边界条件,显著提升测试效率。同时指出了在复杂数据结构、私有方法测试等方面的局限性,最终强调这种技术组合可以解放开发者,

BUG猿的博客 1127

ReAct框架到多智能体协作:OpenClaw任务分解机制的演进与突破

AI的最终价值是落地,是帮用户解决实际问题,而不是停留在聊天界面里。从ReAct框架到多智能体协作,OpenClaw的任务分解体系走了一条非常务实的路线:所有的优化都是为了解决真实场景里的痛点,所有的设计都是为了让用户用起来更省心。很多人说现在的AI还是“人工智障”,那是因为他们没有用到真正能落地的AI产品。当你用过OpenClaw,体验过你说一句话它就能把一整套复杂任务全都做完的感觉,你就会明白:AI代替人做重复性工作的时代,已经真的来了。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 311

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

上一篇: 26、知识创建:本体与ABox的实践指南
下一篇: 20、前端应用测试:HTTP 请求与 WebSockets 测试全解析
生活碎片
博客等级 码龄2年 51粉丝 1780原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值