非线性方程组求解器算法
总结一下电路仿真器中的非线性方程组求解器算法,即 Newton-Krylov 方法
算法概述
-
外层循环:非线性泰勒展开(牛顿法)
-
内层计算:线性方程组求解(Krylov 迭代)
-
约束导向:弧长法加持(弧长约束)
- 动作:如果此时电路处于极端非线性区(比如增益压缩的拐点),直接按牛顿法的 Δx 跳过去可能会"脱轨"。此时弧长法介入,它在方程组里额外加了一个距离约束(源步进法)(Δx)2+(Δ(λb))2=(Δs)2
- 结果:它强迫牛顿法的每一次迭代都必须落在以旧解为圆心、弧长为半径的"圆弧"上。
-
最终归位:拉回解曲线
- 动作:通过 Krylov 算出的 Δx 虽然是在圆弧上的,但不一定在真实的物理解曲线上。
- 循环:牛顿法继续迭代,利用残差 F(x) 不断修正,最终将这个点垂直(或按约束方向)拉回到物理曲线上,找到真正的平衡解。
如果不考虑内层线性方程组是如何求解的,将求解一个大型 m×m 矩阵方程组看成一个已经实现了的工具黑箱,那么牛顿法已经解决了非线性电路求解的问题,前面的博文电路仿真器是如何工作的已经介绍地足够清晰了。
所以这里作为完整求解器算法的补充,主要讲如何求解这个大型矩阵方程组。如何实现在有限的内存下,更快求解出这个成千上万未知量的矩阵方程组。
Krylov 子空间
首先要引进一个概念,Krylov 子空间。为了理解这个子空间,我们可以从最简单的人类认知来讲解。拥有成千上万个未知量的方程组好比是一个解谜游戏,谜底是什么我们完全不知道,但我们可以不断猜测谜底的特征来逐渐接近它。完整的精确的解,也就是谜底,本需要与未知量相同数量的维度去描述。但描述越精确,需要的代价太大,就好比我们计算的内存和时间不够。所以有没有可能,我们将这些特征从粗略到精细排个序,先粗略猜答案的大致范围。例如我们假定谜底是个非生物,带入方程组后发现错了,因此结果告诉我们谜底是生物,这样就能确定解的大致范围。接着我们假定谜底是植物,带入后发现错了,因此缩小范围判断谜底是动物。如此反复猜测迭代,最终就能确定谜底。
同样的道理,精确的解在一个非常高维的空间中,想直接求解非常困难。于是我们想让这个解先降维下来,求解它在低维空间中的近似解,看看这个近似解是否足够精确,也就是收敛。如果在某个低维空间中,这个解已经足够精确收敛了,那我们也没必要去浪费资源硬将它的高维精确解完整求出。求解过程中的用到的低维空间,就是我们这里的 Krylov 子空间。
传统的线性方程组可以表示为
Ax=b
我们想求解 x,直接法例如高斯消元或者 LU 本质都在间接求 A−1,但对于一个拥有成千上万维度的矩阵,求逆太困难了。于是我们考虑将 A−1 表示为 ∑k=0m−1ckAk,也即是
x=A−1b⇒x(m)=k=0∑m−1ckAkb
看到这里是不是想起了泰勒级数?没错,这种思想就是矩阵形式的"泰勒级数"展开,理论上只要我展开的项足够多,近似解 x(m) 就足够精确!
因此,高维矩阵求解问题便转化为了求解有限低维 m 级次下的展开系数。Krylov 子空间可以表示为:
Km(A,b)=span{b,Ab,A2b,...,Am−1b}
假如能获取这个子空间的所有正交基 {v1,v2,...,vm},和精确解在这个子空间中各个基上的投影,即子空间坐标 {y1,y2,...,ym},我们就能得到近似解 x(m) 的表达式
x(m)=y1v1+y2v2+⋅⋅⋅+ymvm=Vmy
我们的目标明确了,需要求 Krylov 子空间的正交基 {v1,v2,...,vm} 和子空间坐标 {y1,y2,...,ym}。
GMRES 算法
GMRES 算法大家应该在各种仿真软件(例如 COMSOL)中见过,它的全称是 Generalized Minimal Residual method,即广义最小残量方法,属于 Krylov 子空间方法。这里就以它作为例子来说明近似解是如何一步一步求出来的。
我们以解这个方程组为例:
⎩⎨⎧x1+x2+x3=3x1−x2+x3=1x1+x2−x3=1
写成矩阵形式:
Ax=b
其中
A=1111−1111−1,b=311
0 阶残差初始化
按 Krylov 法,先给一个初值。取最常见的
x(0)=0
则初始残差
r0=b−Ax(0)=b=311
其范数为
β=∥r0∥=32+12+12=11
于是 Arnoldi 的第一个标准正交基向量 v1 为
v1=∥r0∥r0=111311
这里相当于得到了 Krylov 子空间 Km(A,b)=span{b,Ab,A2b,...,Am−1b} 中的归一化 b
Arnoldi 算法迭代第一步
继续算 Km(A,b) 中的 Ab
w=Av1=1111111−1111−1311=111533
对 v1 正交化,先算投影系数
h11=v1⊤Av1=v1⊤w=111[311]111533=1121
然后消掉 v1 方向的投影:
w←w−h11v1
代入:
w=111533−1121⋅111311=11111−81212
求第二个基向量,先算它的范数:
h21=v2⊤Av1=v2⊤v2=∥w∥=11111(−8)2+122+122=1142
于是
v2=h21w=221−233
Arnoldi 算法迭代第二步
再继续算 Km(A,b) 中的 A2b
w=A2v1=Av2=2211111−1111−1−233=2214−2−2
先对 v1 正交化
h12=v1⊤Av2=v1⊤w=111[311]2214−2−2=1142
再对 v2 正交化
h22=v2⊤Av2=v2⊤w=221[−233]4−2−2=−1110
更新:
w=Av2−h12v1−h22v2=v3⊤Av2=0
所以
h32=v3⊤Av2=∥w∥=0
这说明 Arnoldi 在第二步就停了。如果不为零,则会一直迭代到维度为 Krylov 重启长度。
这里 h 是 Hessenberg 矩阵 Hm 的矩阵元,它表示 Arnoldi 过程将一个大型的 n×n 的矩阵 A 投影到一个小型 m×m 的 Hm 矩阵上,因此有
Hm=Vm⊤AVm
其中 Vm 为正交基 vm 组成的变换矩阵,作用是将矩阵 A 降维到 m×m
Vm=∣v1∣∣v2∣...∣vm∣
假如这里更新 w=0,接下来要算 A3b,也就是 w=Av3=v4。为了得到它在 v4 上的坐标,要依次减去它在 v1,v2,v3 上的分量,即 h13,h23,h33。根据 h 的定义,再得到 h31,h43,再判断 v4 的坐标 h43 是否为零。
总结前面的 Arnoldi 算法,其正交化过程实际上就是将 Avj 拆成各低阶分量与 j+1 阶分量之和,然后减去所有低价项并除以模 hj+1,j,从而得到高维正交基 vj+1
Avj=i=1∑jhijvi+hj+1,jvj+1=[v1v2⋯vj+1]h1jh2j⋮hj+1,j=Vm+1h1jh2j⋮hj+1,j
把所有列并在一起
A[v1v2⋯vm]=AVm=Vm+1h11h21h31⋮hm+1,1h12h22h32⋮hm+1,2⋯⋯⋯⋯h1mh2mh3m⋮hm+1,m=Vm+1Hˉm
注意这里的 Hˉm 是 (m+1)×m 的矩阵,而 Hm 是 m×m 的矩阵
得到投影小矩阵
于是二维 Krylov 子空间上的 Hessenberg 小矩阵为
H2=[h11h21h12h22]=[112111421142−1110]
而
V2=∣v1∣∣v2∣
其中
v1=111311,v2=221−233
得到投影方程
H2[y1y2]=βe1=β[10]
现在来解释一下这个投影方程的数学/物理意义。我们知道 y=[y1,y2] 是精确解 x 投影在 Krylov 子空间 Km(A,b)=span{b,Ab} 上的解(这里没有 A2b 是因为这个维度正交基的模为0)。H2 是完整系数矩阵 A 投影在子空间上的系数小矩阵。β 是第一个标准正交基 v1 的模,同样也是 b 投影在子空间内第一个基上的模大小。
因此,在这个低维空间中,原 Ax=b 同样对应了一个小矩阵方程组 Hmy=βe1。我们只需求解这个小矩阵方程组,得到 y,再空间变换回 x=Vmy,就能得到低维近似解 x(m)。
由此可见,求解复杂大型矩阵方程组的核心思想,就是对其降维并求解低维下的投影方程组。如果观察到解收敛了,就认为求解成功。
最小化残差
前面提到求解降维后的投影方程组,这其实是 FOM(Full Orthogonalization Method),是 Krylov 最基础的版本。GMRES 是它的优化版,毕竟低维近似解也可能一直不收敛,导致求解的 FOM 矩阵方程组越来越大。
所以人们干脆不直接对投影方程组求解了,而是算
y=argymin∥βe1−Hˉmy∥
注意这里相当于是找到 m 阶残差的最小值
根据前面推导的 Arnoldi 公式
AVm=Vm+1Hˉm
且 b=βv1=Vm+1(βe1)
rm=b−Ax(m)=Vm+1(βe1)−AVmy=Vm+1(βe1)−Vm+1Hˉmy=Vm+1(βe1−Hˉmy)
因此,所有高阶残差项都投影到了 Vm+1 这个基里。只要我找到其坐标 ∥βe1−Hˉmy∥ 的最小值,就能保证残差最小。最终问题又被转化为了一个 (m+1)×m 的最小二乘问题:
找到 y 使得小矩阵方程组 Hmy=βe1 的残差的模 ∥βe1−Hˉmy∥ 最小
QR 分解
QR 分解是将原矩阵分解为一个正交矩阵 Q(满足 Q⊤Q=I)和一个上三角矩阵 R 的乘积(A=QR)。该方法常用于解线性最小二乘问题,核心在于利用正交矩阵的性质将复杂计算转化为简单计算。
所以进一步地,计算最小残差的时候,选择用 QR 分解将 Hˉm 变成正交矩阵 Q 与上三角矩阵 R 的乘积。
根据前面的推导,矩阵 Hˉm=[h1,h2,⋯,hm],且每个 hi 为一个 m+1 维度的向量。通过 Gram-Schmidt 施密特正交化将列向量转化为一组标准正交基 Q=[q1,q2,⋯,qm+1](就是 Arnold 算法的正交化过程)
因为在正交化的过程中,原向量 hi 可以表示为这组新基 qi 的线性组合,例如:
h1h2h3⋮hm=r11q1=r12q1+r22q2=r13q1+r23q2+r33q3=r1,mq1+⋯+rm,mqm⇒Hˉm=[h1h2h3⋯hm]=[q1q2q3⋯qm+1]r1100⋮0r12r220⋮0r13r23r33⋮0⋯⋯⋯rm,m0=QR
这里的系数 rij 恰好就是矩阵 R 的元素,刚好形成了上三角矩阵,于是得到
- Q=[q1q2q3⋯qm+1],(m+1)×(m+1) 维度的正交矩阵
- R=r1100⋮0r12r220⋮0r13r23r33⋮0⋯⋯⋯rm,m0,(m+1)×m 维度的上三角矩阵
为什么这里非要将 Hˉm 拆成 QR 的形式呢?因为带入残差式中,得到
∥rm∥=∥Q⊤rm∥=∥Q⊤βe1−QRy∥=∥Q⊤βe1−Ry∥
令 g=Q⊤βe1,我们要求残差最小值 min∥rm∥,也就是解最小二乘问题 min∥g−Ry∥,找到一组 y 使得 ∥g−Ry∥ 最小。
将之展开成平方
∥g−Ry∥2=i=1∑m+1(gi−(Ry)i)2
逐行展开 Ry
(Ry)1(Ry)2⋮(Ry)m(Ry)m+1=r11y1==0=0+r12y2r22y2+0+0+⋯+r1mym+⋯+r2mym+⋯+rmmym+⋯+0
也就是说
∥g−Ry∥2=i=1∑m+1(gi−(Ry)i)2=可以优化i=1∑m(gi−(Ry)i)2+无法改变gm+12
最后这一项就是与 y 无关的残差项,因此 min∥g−Ry∥ 问题就变成了求解方程组
gi−(Ry)i=0(y=1,2,⋯,m)
总结起来,最小二乘在这里的表现形式为求解 Ry=Q⊤βe1 方程组,最小二乘 = 能拟合的维度全部拟合,不能拟合的维度变成残差
回到 GMRES 算法中,最终残差就是 ∥rm∥=∥g−Ry∥=∣gm+1∣。因此,GMRES 的目标就是把 gm+1 压到最小,且 QR 分解能够让最后求解 Ry=Q⊤βe1 方程组时利用上三角矩阵 R 的优势快速回代求解。
QR 分解的算法实现
求解器实现 QR 分解的算法为旋转消元法,构造一个 (m+1)×(m+1) 的矩阵 Givens:
例如作用在第 j 和 j+1 行:
Gj=1⋱c−ssc⋱
可以发现是一个 2×2 的旋转矩阵嵌在大矩阵里(c 是 cos,s 是 sin)
假设你有一列 h(m):
h(m)=h1h2⋮hjhj+1hj+2⋮
应用 Givens:
h(m)←Gjh(m)
实际效果是:
⎩⎨⎧hj←chj+shj+1hj+1←−shj+chj+1其它行不变
根据 (hj,hj+1) 的角度设置旋转矩阵,利用这种方式将 hj+1 消元为0。由于每一步都构建了一个 Givens,导致每一步都在进行旋转消元
算法流程如下
1. r0=b−Ax(0),v1=r0/∥r0∥2. for j=1,…,m:(a) w=Avj(b) Arnoldi 正交化 → 得 hij(c) 形成新列 h(j)(d) 应用所有已有 Givens G1,…,Gj−1(e) 构造 Gj 消掉 hj+1,j(f) 同时更新 g=QT(βe1)3. 解上三角: Ry=g1:m4. x(m)=Vmy5. ∥rm∥=∣gm+1∣
- 通过 x(0)=0,得到 v1
- 循环 Arnoldi 算法,计算 vi 和 hij,并组合成 h(j)(m×1向量)
- 应用已有的 Givens,h(j)←Gj−1⋯G1h(j)
- 针对 [hj,jhj+1,j],构造 Gj,使 hj+1,j→0
- 更新 h(j) 完成三角化,h(j)←Gjh(j)
- 同步更新 g,g←Gjg,数学上 g=QT(βe1)=Gm⋯G1(βe1),但 g 是逐步更新
- 上三角矩阵迭代完毕,求解 Ry=g1:m
- 转换回原 x 空间,x(m)=Vmy
- 计算残差 ∥rm∥ 是否满足收敛要求
重启长度与最大迭代次数
定义解释
- 重启长度(m):这是单次构造子空间的最大维度。每当正交基达到 m 个,Arnold 算法就会停下来,计算一次近似解,然后清空这 m 个基,x(0)←x(m),r0=b−Ax(m),v1=r0/∥r0∥ 重新开始
- 最大迭代次数 (Max Iterations):这是指算法累计允许计算的正交基总数。
- 例如:设置重启长度 m=30,最大迭代次数为 300。这意味着算法最多可以进行 10 轮重启(30×10=300)
这里之所以要设置这两个常量,是因为
- 残差精度不够,需要继续迭代
我们只求出了 m 个基下的最优解 x(m),但如果此时残差 ∥b−Ax(m)∥ 还未达到想要的精度(收敛要求),就必须继续找更多的基,继续求解。
- 内存墙(强制重启):
理论上,如果不重启,一直增加基的个数(m 越来越大),解会越来越准。但由于每个正交基都要存放在内存里,且新基要与之前所有基做正交化:
- 如果 m 太大,例如 1000,内存会爆
- 计算量越来越大(O(m2)),越往后越慢
具体流程为(m=30):
- 先算 30 个基,求出一个当前最牛的近似解 x(30),Krylov 子空间 K30(A,r0)
- 重启:把这 30 个基扔掉(释放内存),以 x(30) 产生的残差作为新起点,Krylov 子空间 K30(A,新残差)
- 再算 30 个基,求出一个基于前面结果改进的 x(60)
- 如此反复,直到超过最大 Arnoldi 迭代次数
重启副作用
重启可能会严重降低收敛速度,因为每次 restart:
- 丢掉之前的 Krylov 信息
- 相当于"记忆被清空"
所以实际工程中策略为 GMRES(m) + 预条件(preconditioner)