线性方程组求解

《Convex Optimization》

数值解这么走下去,却不好好弄弄关于线性方程组的求解,总感觉很别扭,既然《凸优化》也很详细地介绍了这一块东西,我就先跳过别的把这一块整一整吧。

容易求解的线性方程组

先讨论Ax=b很容易求解的情况,即A为满秩的方阵,方程有唯一的解。

对角矩阵

aiixi=bixi=bi/aiiaii0
其中aij为矩阵A的第i行,第j列元素,下同。

下三角矩阵

下三角矩阵,即aij=0,j>i

[a1100a21a220an1an2ann][x1x2xn]=[b1b2bn]

所以方程的解即为:

x1:=b1/a11x2:=(b2a21x1/a22xn:=(bnan1x1an2x2an,n1xn1/ann

上三角矩阵

下三角矩阵采用的是前向代入算法,而上三角矩阵采用的是后向代入或者称为回代算法。情况,或者说推导是类似的,这里不多赘述。

正交矩阵

正交矩阵ARn×n的条件是ATA=I,即A1=AT,所以方程的解是x=ATb。如果A具有特殊的结构,可以进一步简化运算。

排列矩阵

π=(π1,,πn)(1,2,,n)的一种排列。相应的排列矩阵ARn×n定义为:

Aij={1j=πi0

于是可以得到:

Ax=(xπ1,,xπn)

排列矩阵的逆矩阵就是AT,由此可知排列矩阵是正交矩阵。

因式分解求解方法

求解Ax=b的基本途径是将A表示为一系列非奇异矩阵的乘积:

A=A1A2Ak

因此:

x=A1b=Ak1Ak11A11b

我们可以从右往左一步一步地来求解。

求解多个右边项的方程组

假设我们需要求解方程组:

Ax1=b1,Ax2=b2,,Axm=bm

求解这m个问题等价于:

X=A1B

其中:

X=[x1,x2,,xm]Rn×mB=[b1,b2,,bm]Rn×m

LU,CholeskyLDLT因式分解

每一个非奇异分解ARn×n都可以因式分解为:

A=PLU

其中PRn×n是排列矩阵,LRn×n是单位下三角矩阵,而URn×n是非奇异上三角矩阵。

Gauss消元法

我们定义A0=A,A1,A2,,An1表示第r步消元后的系数矩阵。相应的,我们设计一个第r步消元的初等矩阵Nr,这个矩阵的除了第r列外,与单位矩阵无异,第r列为:

[0,0,,1,ar+1,rr1/ar,rr1,ar+2,rr1/ar,rr1,,an,rr1/ar,rr1]T

于是,Ar=NrAr1ajr=0,j>r,显然,如果顺利的话(因为可能出现arrr1=0的情况),进行n1步消元后,矩阵就化为上三角矩阵了。

Nn1N2N1A0=An1,Nn1N2N1b0=bn+1

于是:

A0=N11N21Nn11An1=NAn1

其中N为单位下三角矩阵(下三角矩阵的逆为下三角矩阵,下三角矩阵的乘积为下三角矩阵)。值得一提的是,如果这种分解存在,那么它是唯一的。另外,在《代数特征值问题》一书中,给出了LU各个元素的显示表达式。
接下来,我们再讨论一下如何应对arrr1=0的情况。我们有一个最初的假设,即A是满秩的,虽然这个条件并非必要的(如果没有这个条件,那么就需要在最后判断是否有解)。

Ar=[Ar,rAr,nrAnr,rAnr,nr]

经过r步消元后(假设顺利进行了),那么Anr,r0矩阵,Ar,r为上三角矩阵。现在,如果Anr,nr的首元素ar+1,r+1为0,而且t=argmax{|ai,r+1||i>r+1}。注意at,t+10,否则就与我们的满秩条件相矛盾了。当然,如果撇去假设,真的出现了这种情况,我们只需让Nr+1=I即可,即跳过这一次。最后,我们这一次选择的变换是Nr+1Ir+1,t。其中It+1,t是指第r+1行与t行交换的初等矩阵。
为了便于说明,我们以n=4为例:

A3=N3I3,3N2I2,2N1I1,1=N3I3,3N2(I3,3I3,3)I2,2N1(I2,2I3,3I3,3I2,2)I1,1A0=N3(I3,3N2I3,3)(I3,3I2,2N1I2,2I3,3)(I3,3I2,2I1,1A0)=N~3N~2N~1PTA0

于是

A0=PN~A3

这也是最开始的A=PLU的由来。N~是下三角矩阵的证明比较简单,这里便不给出证明了。另外值得说明的一点是,我们对于t的选择,这么选择的原因是出于数值的稳定性(保证Nr的元素的绝对值都小于1

Cholesky 因式分解

如果ARn×n是对称正定矩阵,那么它可以因式分解为:

A=LLT

其中L是下三角非奇异矩阵,对角元素均为正数。这种分解可以看成LU分解的一种特例,不多赘述。

稀疏矩阵的Cholesky因式分解

A是对称正定稀疏矩阵时,通常可以因式分解为:

A=PLLTPT

举个例子便于理解:

A=[1uTuD]=[10uL][1uT0LT]

其中D=LLT,如果D为正对角矩阵,那么L的对角线元素便直接可以获得了。

LDLT 因式分解

每个非奇异对称矩阵A都能因式分解为:

A=PLDLTPT

其中P是排列矩阵,L是对角均为正数的下三角矩阵,D是块对角矩阵,对角块为1×12×2的非奇异矩阵。
这地方就不做分析了,因为自己没怎么细看这部分过。

分块消元和Schur补

xRn分成俩块:

x=[x1x2]

其中x1Rn1,x2Rn2
那么线性方程组可以这样表示:

[A11A12A21A22][x1x2]=[b1b2]

其中A11Rn1×n1,A22Rn2×n2且假设A11可逆。
由第一个方程可以获得:

x1=A111(b1A12x2)

代入第二个方程可以得到:

(A22A21A111A12)x2=b2A21A111b1

注意到,S=A22A21A111A12A11的Schur补。
由上面的启发,我们可以先计算x2再计算x1,虽然这种方法对于稠密的无结构矩阵而言没有什么优点,但如果要消去的变量对于的子矩阵容易因式分解,这种方法会很有效。

逆矩阵引理

分块消元法的想法是先消去部分变量,然后求解包含这些变量的Schur补的小方程组。同样的想法可以反向应用:如果讲某个矩阵视为Schur补,就可以引入新变量,然后形成并求解一个大方程组。很多情况下这样做没有好处,因为我们最终要求解一个更大的方程组。但是,如果所形成的大方程组具有可以利用的特殊结构,引入新变量就可能导致更加有效的求解方法。最经常利用的是可以从大方程组中消去另一部分变量的情况。
假设有下面的线性方程组:

(A+BC)x=b

其中ARn×n非奇异,BRn×p,CRp×n。我们引入新变量y=Cx,并将方程组重新写成

Ax+By=b,y=Cx

即:

[ABCI][xy]=[b0]

注意到A+BC是大矩阵中I的Schur补。容易看出,当A,B,C相当稀疏,而A+BC稀疏性很差的时候,解大方程组或许比原来的更加有效。

代码

import numpy as np


class LinearEqu: # 要求矩阵A为满秩方阵

    def __init__(self, A, b):
        self.m, self.n = A.shape
        assert self.n == len(b), "the dimensions don't match"
        assert self.m == self.n, "full-rank and row-column equal matrix required"
        self.A = np.array(A, dtype=float)
        self.b = np.array(b, dtype=float)

    @property
    def rank(self):
        """返回矩阵的秩"""
        return np.linalg.matrix_rank(self.A)

    @property
    def extendrank(self):
        """返回[A, b]的秩"""
        b = self.b.reshape(-1, 1)
        return np.linalg.matrix_rank(np.hstack((self.A, b)))
    
    @property
    def diagonal(self):
        assert self.rank == self.extendrank, "No solution"
        assert self.rank == self.n, "A is not a full-rank matrix"
        """
        下面这部分是对矩阵A对角性质的考察,但是想到,万一我只是希望利用一下
        对角元素呢,所以这部分引掉。
        index = np.fromfunction(lambda i, j: i!=j, (self.n ,self.n))
        remain = self.A[index] == 0.
        if not np.all(remain):
            raise TypeError("matrix A is not diagonal...")
        """
        diag_A = np.diag(1 / np.diag(self.A))
        return diag_A @ b
    
    @property
    def low_triangle(self):
        """对下三角矩阵求解"""
        assert self.rank == self.extendrank, "No solution"
        assert self.rank == self.n, "A is not a full-rank matrix"
        index = np.fromfunction(lambda i,j: i < j, (self.n, self.n))
        remain = self.A[index] == 0.
        if not np.all(remain): #这部分我们直接给出了检查
            raise TypeError("matrix A is not low-triangle...")
        x = np.zeros(self.n, dtype=float)
        for i in range(self.n):
            if not i:
                x[i] = self.b[i] / self.A[i, i]
            else:
                residual = self.A[i, :i] @ x[:i]
                x[i] = (self.b[i] - residual) / self.A[i, i]
        return x
    
    @property
    def up_triangle(self):
        """对上三角形矩阵求解"""
        assert self.rank == self.extendrank, "No solution"
        assert self.rank == self.n, "A is not a full-rank matrix"
        index = np.fromfunction(lambda i,j: i > j, (self.n, self.n))
        remain = self.A[index] == 0.
        if not np.all(remain): #这部分我们直接给出了检查
            raise TypeError("matrix A is not up-triangle...")
        x = np.zeros(self.n, dtype=float)
        for i in range(self.n):
            if not i:
                x[self.n-1] = self.b[-1] / self.A[-1, -1]
            else:
                k = self.n - 1 - i
                residual = self.A[k, k+1:] @ x[k+1:]
                x[k] = (self.b[k] - residual) / self.A[k, k]
        return x
    
    @property
    def orthogonal(self):
        """正交矩阵"""
        assert self.rank == self.extendrank, "No solution"
        assert self.rank == self.n, "A is not a full-rank matrix"
        """
        我们的确可以给出检查,只需:
        if np.sum(np.abs(self.A @ self.A.T - np.diag(np.ones(self.n)))) > 1e-5:
            raise TypeError("A is not orthogonal matrix...")
        因为会存在浪费计算的问题,这里就引掉吧。 
        """
        return self.A.T @ self.b
        
	@property
    def gauss(self):
        """利用高斯消元法求解"""
        assert self.rank == self.extendrank, "No solution"
        assert self.rank == self.n, "A is not a full-rank matrix"
        def find_max(A, r):
            vector = A[r:, r]
            max_pos = np.argmax(np.abs(vector)) + r
            return max_pos
        A = np.array(self.A, dtype=float)
        b = np.array(self.b, dtype=float)
        for r in range(self.n - 1):
            max_pos = find_max(A, r)  #寻找最大的点
            vector = np.array(A[r]) #替换 这么做的原因是多维ndarray似乎不支持a,b=b,a
            A[r] = A[max_pos]
            A[max_pos] = vector
            b[r], b[max_pos] = b[max_pos], b[r]
            N_r = np.diag(np.ones(self.n, dtype=float))
            N_r[r:, r] = -np.array(A[r:, r]) / A[r, r]
            N_r[r, r]  = 1.
            A = N_r @ A #更新A
            b = N_r @ b #更新b
        temp = LinearEqu(A, b)
        return temp.up_triangle

posted @   馒头and花卷  阅读(1249)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示