MINIBLOG

Blog Note Tags Links About
Home Search
Mar 17, 2026
miniyuan

解线性方程组的直接法(LU 分解、Cholesky 分解)


LU 分解概述

定义

LU 分解是将一个方阵 AAA (不一定可逆)分解为一个下三角矩阵 LLL 和一个上三角矩阵 UUU 的乘积:

A=LUA = LUA=LU

其中:

  • LLL 为下三角矩阵(通常取为单位下三角矩阵)
  • UUU 为上三角矩阵

注:若不加限制,LU 分解不唯一。为保证唯一性,以下采用单位下三角矩阵形式,即:

[a11a12⋯a1na21a22⋯a2n⋮⋮⋱⋮an1an2⋯ann]=[10⋯0l211⋯0⋮⋮⋱⋮ln1ln2⋯1][u11u12⋯u1n0u22⋯u2n⋮⋮⋱⋮00⋯unn]\begin{bmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{n1} & a_{n2} & \cdots & a_{nn} \end{bmatrix} = \begin{bmatrix} 1 & 0 & \cdots & 0 \\ l_{21} & 1 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ l_{n1} & l_{n2} & \cdots & 1 \end{bmatrix} \begin{bmatrix} u_{11} & u_{12} & \cdots & u_{1n} \\ 0 & u_{22} & \cdots & u_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & u_{nn} \end{bmatrix}​a11​a21​⋮an1​​a12​a22​⋮an2​​⋯⋯⋱⋯​a1n​a2n​⋮ann​​​=​1l21​⋮ln1​​01⋮ln2​​⋯⋯⋱⋯​00⋮1​​​u11​0⋮0​u12​u22​⋮0​⋯⋯⋱⋯​u1n​u2n​⋮unn​​​

利用 LU 分解求解线性方程组

给定线性方程组 Ax=bAx=bAx=b,若已知 A=LUA=LUA=LU,则可转化为两步求解:

  1. 前向代入(Forward Substitution):求解 Ly=bLy = bLy=b

    yi=bi−∑j=1i−1lijyj,i=1,2,…,ny_i = b_i - \sum_{j=1}^{i-1} l_{ij}y_j, \quad i=1,2,\dots,nyi​=bi​−j=1∑i−1​lij​yj​,i=1,2,…,n
  2. 后向代入(Backward Substitution):求解 Ux=yUx = yUx=y

    xi=1uii(yi−∑j=i+1nuijxj),i=n,n−1,…,1x_i = \frac{1}{u_{ii}}\left(y_i - \sum_{j=i+1}^{n} u_{ij}x_j\right), \quad i=n,n-1,\dots,1xi​=uii​1​(yi​−j=i+1∑n​uij​xj​),i=n,n−1,…,1

从而我们只需给出 LU 分解即可完成线性方程组的求解。

注: 与 Gauss 消去法相比,LU 分解将消元过程与回代过程分离,便于多次求解同一系数矩阵的方程组(只需一次分解,多次代入)。并且消元是 O(n3)O(n^3)O(n3) 的,而回代则是 O(n2)O(n^2)O(n2) 的。

LU 分解

数学基础:LU 分解的存在唯一性

矩阵 A∈Rn×nA \in \mathbb{R}^{n \times n}A∈Rn×n 存在唯一的 LU 分解(其中 LLL 为单位下三角矩阵)的充要条件是:AAA 的所有 kkk 阶顺序主子式(k=1,2,…,n−1k=1,2,\dots,n-1k=1,2,…,n−1)均非零。

证明:

以下 AkA_kAk​、LkL_kLk​、UkU_kUk​ 分别表示 AAA、LLL、UUU 的 kkk 阶主子矩阵。n=1n=1n=1 时显然存在唯一。假设 n=1,2,…,k−1n=1, 2, \dots, k-1n=1,2,…,k−1 时成立,下证 n=kn=kn=k 时成立。 设

Ak=[Ak−1cTdakk]A_k = \begin{bmatrix} A_{k-1} & c^T \\ d & a_{kk} \\ \end{bmatrix}Ak​=[Ak−1​d​cTakk​​]

其 LU 分解可写成

Ak=[Ak−1cTdakk]=[Lk−10l1][Uk−1uT0ukk]A_k = \begin{bmatrix} A_{k-1} & c^T \\ d & a_{kk} \\ \end{bmatrix} = \begin{bmatrix} L_{k-1} & 0 \\ l & 1 \\ \end{bmatrix} \begin{bmatrix} U_{k-1} & u^T \\ 0 & u_{kk} \\ \end{bmatrix}Ak​=[Ak−1​d​cTakk​​]=[Lk−1​l​01​][Uk−1​0​uTukk​​]

也即

{Ak−1=Lk−1Uk−1cT=Lk−1uTd=lUk−1akk=luT+ukk(1)\begin{cases} A_{k-1} = L_{k-1} U_{k-1} \\ c^T = L_{k-1} u^T \\ d = l U_{k-1} \\ a_{kk} = l u^T + u_{kk} \end{cases} \tag{1}⎩⎨⎧​Ak−1​=Lk−1​Uk−1​cT=Lk−1​uTd=lUk−1​akk​=luT+ukk​​(1)

则 LU 分解存在且唯一等价于方程组 (1)(1)(1) 的解存在且唯一。

  • 充分性: 由条件和归纳知 Ak−1A_{k-1}Ak−1​ 的 LU 分解存在且唯一,也即 Lk−1L_{k-1}Lk−1​、Uk−1U_{k-1}Uk−1​ 存在且唯一。 又由 det⁡(Ak−1)≠0\det(A_{k-1}) \neq 0det(Ak−1​)=0 知 Lk−1L_{k-1}Lk−1​、Uk−1U_{k-1}Uk−1​ 可逆,故方程 (1)(1)(1) 的解存在且唯一,也即有 LU 分解:

    Ak=[Lk−10−lUk−1−11][Uk−1Lk−1−1uT0akk−lUk−1−1Lk−1−1uT]A_k = \begin{bmatrix} L_{k-1} & 0 \\ -l U_{k-1}^{-1} & 1 \\ \end{bmatrix} \begin{bmatrix} U_{k-1} & L_{k-1}^{-1} u^T \\ 0 & a_{kk} - l U_{k-1}^{-1} L_{k-1}^{-1} u^T \\ \end{bmatrix}Ak​=[Lk−1​−lUk−1−1​​01​][Uk−1​0​Lk−1−1​uTakk​−lUk−1−1​Lk−1−1​uT​]

    本质上就是简单的行列变换消元。

  • 必要性: 由条件知方程 (1)(1)(1) 的解存在且唯一。故必须 Lk−1L_{k-1}Lk−1​、Uk−1U_{k-1}Uk−1​ 可逆,也即 Ak−1A_{k-1}Ak−1​ 可逆,从而得证。

注:

  • 我们证明时不需要 AAA 是奇异的,其右下角元可以为 0
  • 若某顺序主子式为 0,但主元位置可通过行交换(列主元)避免为 0,则可进行PLU分解(PA=LUPA = LUPA=LU,其中 PPP 为置换矩阵)。

LU 分解计算公式

ukj=akj−∑m=1k−1lkmumj,j=k,k+1,…,nu_{kj} = a_{kj} - \sum_{m=1}^{k-1} l_{km}u_{mj}, \quad j=k,k+1,\dots,nukj​=akj​−m=1∑k−1​lkm​umj​,j=k,k+1,…,n lik=1ukk(aik−∑m=1k−1limumk),i=k+1,k+2,…,nl_{ik} = \frac{1}{u_{kk}}\left(a_{ik} - \sum_{m=1}^{k-1} l_{im}u_{mk}\right), \quad i=k+1,k+2,\dots,nlik​=ukk​1​(aik​−m=1∑k−1​lim​umk​),i=k+1,k+2,…,n

证明:

从 A=LUA = LUA=LU 出发,逐元素比较来推导即可。

矩阵乘法:

aij=(LU)ij=∑m=1nlimumj.a_{ij} = (LU)_{ij} = \sum_{m=1}^n l_{im} u_{mj}.aij​=(LU)ij​=m=1∑n​lim​umj​.

由于 LLL 下三角(lim=0l_{im} = 0lim​=0 当 m>im > im>i),且 UUU 上三角(umj=0u_{mj} = 0umj​=0 当 m>jm > jm>j),所以求和实际上只到 min⁡(i,j)\min(i, j)min(i,j):

aij=∑m=1min⁡(i,j)limumj.a_{ij} = \sum_{m=1}^{\min(i, j)} l_{im} u_{mj}.aij​=m=1∑min(i,j)​lim​umj​.
  • 情形 1:i≤ji \le ji≤j(图 1 中红黄蓝部分,得到 UUU 的元素) 当 i≤ji \le ji≤j 时,min⁡(i,j)=i\min(i, j) = imin(i,j)=i,所以

    aij=∑m=1ilimumj=∑m=1i−1limumj+liiuij.a_{ij} = \sum_{m=1}^{i} l_{im} u_{mj} = \sum_{m=1}^{i-1} l_{im} u_{mj} + l_{ii} u_{ij}.aij​=m=1∑i​lim​umj​=m=1∑i−1​lim​umj​+lii​uij​.

    因为 LLL 是单位下三角,lii=1l_{ii} = 1lii​=1,再将 iii 替换为 kkk 化简得:

    ukj=akj−∑m=1k−1lkmumj,j=k,k+1,…,n.u_{kj} = a_{kj} - \sum_{m=1}^{k-1} l_{km} u_{mj}, \quad j = k, k+1, \dots, n.ukj​=akj​−m=1∑k−1​lkm​umj​,j=k,k+1,…,n.
  • 情形 2:i>ji > ji>j(图 1 中橙绿部分,得到 LLL 的元素) 当 i>ji > ji>j 时,min⁡(i,j)=j\min(i, j) = jmin(i,j)=j,所以

    aij=∑m=1jlimumj=∑m=1j−1limumj+lijujj.a_{ij} = \sum_{m=1}^{j} l_{im} u_{mj} = \sum_{m=1}^{j-1} l_{im} u_{mj} + l_{ij} u_{jj}.aij​=m=1∑j​lim​umj​=m=1∑j−1​lim​umj​+lij​ujj​.

    用 kkk 替换 jjj,化简得:

    lik=1ukk(aik−∑m=1k−1limumk),i=k+1,k+2,…,n.l_{ik} = \frac{1}{u_{kk}} \left( a_{ik} - \sum_{m=1}^{k-1} l_{im} u_{mk} \right), \quad i = k+1, k+2, \dots, n.lik​=ukk​1​(aik​−m=1∑k−1​lim​umk​),i=k+1,k+2,…,n.

实际操作时应按照下图所示的红橙黄绿蓝顺序依次求解。也即先由上式求出 u1u^1u1,再由下式求出 l1l_1l1​,再由上式求出 u2u^2u2,以此类推。

图1:LU 分解求解顺序
图1:LU 分解求解顺序

LU 分解代码

标准 LU 分解

for k in range(n): # 枚举 U 的行,同时也是枚举 L 的列
   for j in range(k, n): # 枚举 U 的列
      U[k, j] = A[k, j]
      for m in range(k - 1):
         U[k, j] = U[k, j] - L[k, m] * U[m, j]
   for i in range(k + 1, n): # 枚举 L 的行
      L[i, k] = A[i, k]
      for m in range(k - 1):
         L[i, k] = L[i, k] - L[i, m] * U[m, k]
      L[i, k] = L[i, k] / U[k, k]

注:标准算法需要额外存储空间。实际中常采用原位存储优化,即将 LLL 的非对角元素和 UUU 存储在同一矩阵 AAA 中:

[a11a12⋯a1na21a22⋯a2n⋮⋮⋱⋮an1an2⋯ann]⇒[u11u12⋯u1nl21u22⋯u2n⋮⋮⋱⋮ln1ln2⋯unn]\begin{bmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{n1} & a_{n2} & \cdots & a_{nn} \end{bmatrix} \Rightarrow \begin{bmatrix} u_{11} & u_{12} & \cdots & u_{1n} \\ l_{21} & u_{22} & \cdots & u_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ l_{n1} & l_{n2} & \cdots & u_{nn} \end{bmatrix}​a11​a21​⋮an1​​a12​a22​⋮an2​​⋯⋯⋱⋯​a1n​a2n​⋮ann​​​⇒​u11​l21​⋮ln1​​u12​u22​⋮ln2​​⋯⋯⋱⋯​u1n​u2n​⋮unn​​​

原位存储 LU 分解

由此算法可以更清晰地看到求解顺序。

for k in range(n): # 枚举 U 的行,同时也是枚举 L 的列
   for j in range(k, n): # 枚举 U 的列
      for m in range(k - 1):
         A[k, j] = A[k, j] - A[k, m] * A[m, j]
   for i in range(k + 1, n): # 枚举 L 的行
      for m in range(k - 1):
         A[i, k] = A[i, k] - A[i, m] * A[m, k]
      A[i, k] = A[i, k] / A[k, k]

也即:

[a11a12⋯a1na21a22⋯a2n⋮⋮⋱⋮an1an2⋯ann]⇒[u11u12⋯u1na21a22⋯a2n⋮⋮⋱⋮an1an2⋯ann]⇒[u11u12⋯u1nl21a22⋯a2n⋮⋮⋱⋮ln1an2⋯ann]⇒[u11u12⋯u1nl21u22⋯u2n⋮⋮⋱⋮ln1an2⋯ann]\begin{bmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{n1} & a_{n2} & \cdots & a_{nn} \end{bmatrix} \Rightarrow \begin{bmatrix} u_{11} & u_{12} & \cdots & u_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{n1} & a_{n2} & \cdots & a_{nn} \end{bmatrix} \Rightarrow \begin{bmatrix} u_{11} & u_{12} & \cdots & u_{1n} \\ l_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ l_{n1} & a_{n2} & \cdots & a_{nn} \end{bmatrix} \Rightarrow \begin{bmatrix} u_{11} & u_{12} & \cdots & u_{1n} \\ l_{21} & u_{22} & \cdots & u_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ l_{n1} & a_{n2} & \cdots & a_{nn} \end{bmatrix}​a11​a21​⋮an1​​a12​a22​⋮an2​​⋯⋯⋱⋯​a1n​a2n​⋮ann​​​⇒​u11​a21​⋮an1​​u12​a22​⋮an2​​⋯⋯⋱⋯​u1n​a2n​⋮ann​​​⇒​u11​l21​⋮ln1​​u12​a22​⋮an2​​⋯⋯⋱⋯​u1n​a2n​⋮ann​​​⇒​u11​l21​⋮ln1​​u12​u22​⋮an2​​⋯⋯⋱⋯​u1n​u2n​⋮ann​​​

递归的 LU 分解

基于分块矩阵思想,将 AAA 分块为:

A=[a11uTlB]=[10l/a11In−1][100B−luT/a11][a11uT0In−1]A = \begin{bmatrix} a_{11} & \mathbf{u}^T \\ \mathbf{l} & B \end{bmatrix} = \begin{bmatrix} 1 & 0 \\ \mathbf{l}/a_{11} & I_{n-1} \end{bmatrix} \begin{bmatrix} 1 & 0 \\ 0 & B - \mathbf{l}\mathbf{u}^T/a_{11} \end{bmatrix} \begin{bmatrix} a_{11} & \mathbf{u}^T \\ 0 & I_{n-1} \end{bmatrix}A=[a11​l​uTB​]=[1l/a11​​0In−1​​][10​0B−luT/a11​​][a11​0​uTIn−1​​]

其中 B−luT/a11B - \mathbf{l}\mathbf{u}^T/a_{11}B−luT/a11​ 称为 a11a_{11}a11​ 的 Schur 补,其 LU 分解与原矩阵的 LU 分解递归相关。 这是因为若 Schur 补也能够 LU 分解,即若有 B−luT/a11=Ln−1Un−1B - \mathbf{l}\mathbf{u}^T/a_{11} = L_{n-1} U_{n-1}B−luT/a11​=Ln−1​Un−1​,则:

A=[a11uTlB]=[10l/a11Ln−1][a11uT0Un−1]A = \begin{bmatrix} a_{11} & \mathbf{u}^T \\ \mathbf{l} & B \end{bmatrix} = \begin{bmatrix} 1 & 0 \\ \mathbf{l}/a_{11} & L_{n-1} \end{bmatrix} \begin{bmatrix} a_{11} & \mathbf{u}^T \\ 0 & U_{n-1} \end{bmatrix}A=[a11​l​uTB​]=[1l/a11​​0Ln−1​​][a11​0​uTUn−1​​]

原位存储的递归算法:

实际上每一递归步都是按照红橙黄的顺序进行计算,其中红橙色区域计算得到的是最终值,而黄色区域计算得到的是中间值。

for k in range(n - 1): # 递归的第 k 步,最后一步不用计算
   # 红色区域不需计算
   for i in range(k + 1, n): # 计算橙色区域
      A[i, k] = A[i, k] / A[k, k]
   for i in range(k + 1, n): # 计算黄色区域
      for j in range(k + 1, n):
         A[i, j] = A[i, j] - A[i, k] * A[k, j]
图2:递归 LU 分解求解顺序
图2:递归 LU 分解求解顺序

向量化的递归算法:

针对按行或按列存储的数组分别进行优化,主要是黄色区域的更新可以考虑内存访问方式(还有 ICS 的事 OoO):

按行存储:

for k in range(n - 1): # 递归的第 k 步,最后一步不用计算
   # 红色区域不需计算
   A[k + 1:n, k] = A[k + 1:n, k] / A[k, k] # 计算橙色区域
   for i in range(k + 1, n): # 计算黄色区域
         A[i, k + 1:n] = A[i, k + 1:n] - A[i, k] * A[k, k + 1:n]

按列存储:

for k in range(n - 1): # 递归的第 k 步,最后一步不用计算
   # 红色区域不需计算
   A[k + 1:n, k] = A[k + 1:n, k] / A[k, k] # 计算橙色区域
   for j in range(k + 1, n): # 计算黄色区域
         A[k + 1:n, j] = A[k + 1:n, j] - A[k + 1:n, k] * A[k, j]

注: 向量化操作能充分利用 CPU 的向量指令集(如SSE、AVX),从而加速运算。

复杂度分析

针对原位存储 LU 分解而言:

  • 时间复杂度:乘除法次数为 ∑k=1n−12(n−k)k+n≈n3/3\sum_{k=1}^{n-1} 2(n-k)k + n \approx n^3/3∑k=1n−1​2(n−k)k+n≈n3/3,也即 O(n3)O(n^3)O(n3)
  • 空间复杂度:原位存储只需 O(n2)O(n^2)O(n2)

带状矩阵的 LU 分解

带状矩阵定义

  • 下带宽 ppp:若 i>j+pi > j + pi>j+p 时 aij=0a_{ij} = 0aij​=0
  • 上带宽 qqq:若 j>i+qj > i + qj>i+q 时 aij=0a_{ij} = 0aij​=0

常见类型:

矩阵结构下带宽上带宽
对角矩阵00
上三角矩阵0n−1n-1n−1
下三角矩阵n−1n-1n−10
三对角矩阵11

带状矩阵的压缩存储

普通 n×nn \times nn×n 矩阵需 n2n^2n2 存储空间,带状矩阵仅需 (p+q+1)×n(p+q+1) \times n(p+q+1)×n 空间:

aij⇒aˉi−j+q+1,j,其中 Aˉ∈R(p+q+1)×na_{ij} \Rightarrow \bar{a}_{i-j+q+1,j}, \quad \text{其中 } \bar{A} \in \mathbb{R}^{(p+q+1) \times n}aij​⇒aˉi−j+q+1,j​,其中 Aˉ∈R(p+q+1)×n
图3:下带宽 p=1、上带宽 q=2 的6×6矩阵
图3:下带宽 p=1、上带宽 q=2 的6×6矩阵

数学基础:带状矩阵 LU 分解的稀疏性

若 A=LUA = LUA=LU 且 AAA 的上带宽为 qqq、下带宽为 ppp,则 UUU 的上带宽为 qqq,LLL 的下带宽为 ppp。

注:并非所有稀疏矩阵的 LU 分解都保持稀疏性(称为”填充”现象),但对于带状矩阵,LU 分解能够保持稀疏性。

未压缩的算法

for k in range(n - 1): # 递归的第 k 步,最后一步不用计算
   # 红色区域不需计算
   for i in range(k + 1, min(k + p + 1, n)): # 计算橙色区域
      A[i, k] = A[i, k] / A[k, k]
   for i in range(k + 1, min(k + p + 1, n)): # 计算黄色区域
      for j in range(k + 1, min(k + q + 1, n)):
         A[i, j] = A[i, j] - A[i, k] * A[k, j]

复杂度分析:

当 n≫p,qn \gg p,qn≫p,q 时,运算量约为 npqnpqnpq 次乘除法和 npqnpqnpq 次加减法,也即时间复杂度 O(npq)O(npq)O(npq)

正定矩阵的 Cholesky 分解

数学基础:正定矩阵

定义:矩阵 AAA 称为对称正定矩阵(SPD),若 AT=AA^T = AAT=A 且对任意非零向量 x∈Rn\mathbf{x} \in \mathbb{R}^nx∈Rn,有 xTAx>0\mathbf{x}^TA\mathbf{x} > 0xTAx>0。

重要性质:

  1. AAA 的所有特征值均为正数
  2. AAA 的所有顺序主子式均为正数
  3. AAA 的所有主子阵也为正定阵,所有对角元素均为正数
  4. AAA 可逆,且 A−1A^{-1}A−1 也是对称正定矩阵

几何意义:SPD 矩阵对应的二次型 xTAx\mathbf{x}^TA\mathbf{x}xTAx 在 Rn\mathbb{R}^nRn 中表示一个开口向上的碗状曲面,其最小值在原点处取得。

数学基础:Cholesky 分解的存在唯一性

若 A∈Rn×nA \in \mathbb{R}^{n \times n}A∈Rn×n 是对称正定矩阵,则存在唯一的下三角矩阵 LLL(对角元为正),使得:

A=LLTA = LL^TA=LLT

证明:

记 AkA_kAk​ 为 AAA 的 kkk 阶顺序主子矩阵。由 AAA 正定知 AkA_kAk​ 可逆。当 n=1n=1n=1 时,A=[a11]A = [a_{11}]A=[a11​],a11>0a_{11} > 0a11​>0,显然存在唯一。

假设对 n≤k−1n \le k-1n≤k−1 结论成立,则对称正定矩阵 Ak−1A_{k-1}Ak−1​ 存在唯一的 Cholesky 分解
Ak−1=Lk−1Lk−1TA_{k-1} = L_{k-1} L_{k-1}^TAk−1​=Lk−1​Lk−1T​,其中 Lk−1L_{k-1}Lk−1​ 下三角且对角元为正。下证 n=kn = kn=k 时成立。

将 AkA_kAk​ 分块为

Ak=[Ak−1vvTakk],A_k = \begin{bmatrix} A_{k-1} & v \\ v^T & a_{kk} \end{bmatrix},Ak​=[Ak−1​vT​vakk​​],

其中 v∈Rk−1v \in \mathbb{R}^{k-1}v∈Rk−1,akk>0a_{kk} > 0akk​>0。

设待求的 LkL_kLk​ 分块为

Lk=[Lk−10wTlkk],L_k = \begin{bmatrix} L_{k-1} & 0 \\ w^T & l_{kk} \end{bmatrix},Lk​=[Lk−1​wT​0lkk​​],

其中 Lk−1L_{k-1}Lk−1​ 是归纳得到的下三角矩阵(对角元为正),w∈Rk−1w \in \mathbb{R}^{k-1}w∈Rk−1,lkk>0l_{kk} > 0lkk​>0。

计算

LkLkT=[Lk−10wTlkk][Lk−1Tw0lkk]=[Lk−1Lk−1TLk−1wwTLk−1TwTw+lkk2].L_k L_k^T = \begin{bmatrix} L_{k-1} & 0 \\ w^T & l_{kk} \end{bmatrix} \begin{bmatrix} L_{k-1}^T & w \\ 0 & l_{kk} \end{bmatrix} = \begin{bmatrix} L_{k-1} L_{k-1}^T & L_{k-1} w \\ w^T L_{k-1}^T & w^T w + l_{kk}^2 \end{bmatrix}.Lk​LkT​=[Lk−1​wT​0lkk​​][Lk−1T​0​wlkk​​]=[Lk−1​Lk−1T​wTLk−1T​​Lk−1​wwTw+lkk2​​].

令其等于 AkA_kAk​:

{Ak−1=Lk−1Lk−1T(由归纳假设已知)v=Lk−1w(1)akk=wTw+lkk2(2)(2)\begin{cases} A_{k-1} = L_{k-1} L_{k-1}^T & \text{(由归纳假设已知)}\\ v = L_{k-1} w & \text{(1)}\\ a_{kk} = w^T w + l_{kk}^2 & \text{(2)} \end{cases} \tag{2}⎩⎨⎧​Ak−1​=Lk−1​Lk−1T​v=Lk−1​wakk​=wTw+lkk2​​(由归纳假设已知)(1)(2)​(2)

则 Cholesky 分解存在且唯一等价于方程组 (2)(2)(2) 的解存在且唯一。显然 w=Lk−1−1vw = L_{k-1}^{-1} vw=Lk−1−1​v 存在且唯一,只需验证 akk−wTw>0a_{kk} - w^T w > 0akk​−wTw>0 即可!

由正定性,对任意非零向量 x∈Rkx \in \mathbb{R}^kx∈Rk 有 xTAkx>0x^T A_k x > 0xTAk​x>0。 取 x=(−Lk−1−Tw1)x = \begin{pmatrix} -L_{k-1}^{-T} w \\ 1 \end{pmatrix}x=(−Lk−1−T​w1​),注意这是一个分块向量,第一块长度 k−1k-1k−1,第二块是标量。 则

xTAkx=(−wTLk−1−11)[Ak−1vvTakk](−Lk−1−Tw1)=(−wTLk−1−11)[Lk−1Lk−1TLk−1wwTLk−1Takk](−Lk−1−Tw1)=akk−wTw>0.x^T A_k x = \begin{pmatrix} -w^T L_{k-1}^{-1} & 1 \end{pmatrix} \begin{bmatrix} A_{k-1} & v \\ v^T & a_{kk} \end{bmatrix} \begin{pmatrix} -L_{k-1}^{-T} w \\ 1 \end{pmatrix} \\ = \begin{pmatrix} -w^T L_{k-1}^{-1} & 1 \end{pmatrix} \begin{bmatrix} L_{k-1} L_{k-1}^T & L_{k-1} w \\ w^T L_{k-1}^T & a_{kk} \end{bmatrix} \begin{pmatrix} -L_{k-1}^{-T} w \\ 1 \end{pmatrix} \\ = a_{kk} - w^T w > 0.xTAk​x=(−wTLk−1−1​​1​)[Ak−1​vT​vakk​​](−Lk−1−T​w1​)=(−wTLk−1−1​​1​)[Lk−1​Lk−1T​wTLk−1T​​Lk−1​wakk​​](−Lk−1−T​w1​)=akk​−wTw>0.

于是存在唯一性得证。

Cholesky 分解计算公式

从 A=LLTA=L L^TA=LLT 出发,对比矩阵元素可得:

lkk=akk−∑j=1k−1lkj2,k=1,2,3,…,nl_{kk} = \sqrt{a_{kk} - \sum_{j=1}^{k-1} l_{kj}^2}, \quad k=1,2,3,\dots,nlkk​=akk​−j=1∑k−1​lkj2​​,k=1,2,3,…,n lik=1lkk(aik−∑j=1k−1lijlkj),i=k+1,k+2,…,nl_{ik} = \frac{1}{l_{kk}}\left(a_{ik} - \sum_{j=1}^{k-1} l_{ij}l_{kj}\right), \quad i=k+1,k+2,\dots,nlik​=lkk​1​(aik​−j=1∑k−1​lij​lkj​),i=k+1,k+2,…,n

证明:

矩阵乘法:

aij=(LLT)ij=∑k=1nlikljk=∑k=1min⁡(i,j)likljk.a_{ij} = (LL^T)_{ij} = \sum_{k=1}^n l_{ik} l_{jk} = \sum_{k=1}^{\min(i, j)} l_{ik} l_{jk}.aij​=(LLT)ij​=k=1∑n​lik​ljk​=k=1∑min(i,j)​lik​ljk​.

由于 LLL 下三角,lik=0l_{ik} = 0lik​=0 当 i<ki < ki<k,且 ljk=0l_{jk} = 0ljk​=0 当 j<kj < kj<k,所以求和实际上只到 min⁡(i,j)\min(i, j)min(i,j)。

  • 情形 1:对角元 (i=ji = ji=j)

    aii=∑k=1ilik2=∑k=1i−1lik2+lii2.a_{ii} = \sum_{k=1}^i l_{ik}^2 = \sum_{k=1}^{i-1} l_{ik}^2 + l_{ii}^2.aii​=k=1∑i​lik2​=k=1∑i−1​lik2​+lii2​.

    于是

    lii2=aii−∑k=1i−1lik2.l_{ii}^2 = a_{ii} - \sum_{k=1}^{i-1} l_{ik}^2.lii2​=aii​−k=1∑i−1​lik2​.

    将 iii 替换为 kkk,kkk 替换为 jjj,并开方得(可以开方的证明见前一节):

    lkk=akk−∑j=1k−1lkj2,k=1,2,…,n.l_{kk} = \sqrt{a_{kk} - \sum_{j=1}^{k-1} l_{kj}^2}, \quad k = 1, 2, \dots, n.lkk​=akk​−j=1∑k−1​lkj2​​,k=1,2,…,n.
  • 情形 2:非对角元 (i>ji > ji>j) 下三角部分 由对称性,我们只需考虑 i>ji > ji>j。此时

    aij=∑k=1jlikljk=∑k=1j−1likljk+lijljj.a_{ij} = \sum_{k=1}^j l_{ik} l_{jk} = \sum_{k=1}^{j-1} l_{ik} l_{jk} + l_{ij} l_{jj}.aij​=k=1∑j​lik​ljk​=k=1∑j−1​lik​ljk​+lij​ljj​.

    将 jjj、kkk 互换,解得

    lik=1lkk(aik−∑j=1k−1lijlkj),i=k+1,k+2,…,n.l_{ik} = \frac{1}{l_{kk}} \left( a_{ik} - \sum_{j=1}^{k-1} l_{ij} l_{kj} \right), \quad i=k+1,k+2,\dots,n.lik​=lkk​1​(aik​−j=1∑k−1​lij​lkj​),i=k+1,k+2,…,n.

实际操作时应按照下图所示的红橙黄绿顺序依次求解。也即先由上式求出 a11a_{11}a11​,再由下式求出 a1a_1a1​(a1a^1a1),再由上式求出 a22a_{22}a22​,以此类推。

图4:Cholesky 分解求解顺序
图4:Cholesky 分解求解顺序

Cholesky 分解代码

标准 Cholesky 分解

L = np.zeros(n, n)
for k in range(n):
   # 求解对角元
   L[k, k] = A[k, k]
   for j in range(k):
      L[k, k] = L[k, k] - L[k, j] * L[k, j]
   L[k, k] = np.sqrt(L[k, k])
   # 求解对角元下侧(右侧)的剩余元
   for i in range(k + 1, n):
      L[i, k] = A[i, k]
      for j in range(k):
         L[i, k] = L[i, k] - L[i, j] * L[k, j]
      L[i, k] = L[i, k] / L[k, k]

原位存储 Cholesky 分解

对于对称正定矩阵

A=[a11lTlB]A = \begin{bmatrix} a_{11} & \mathbf{l}^T \\ \mathbf{l} & B \end{bmatrix}A=[a11​l​lTB​]

其中 a11>0a_{11} > 0a11​>0,BBB 对称正定。

则:

A=[a110l/a11In−1][100B−llT/a11][a11lT/a110In−1]A = \begin{bmatrix} \sqrt{a_{11}} & 0 \\ \mathbf{l}/\sqrt{a_{11}} & I_{n-1} \end{bmatrix} \begin{bmatrix} 1 & 0 \\ 0 & B - \mathbf{l}\mathbf{l}^T / a_{11} \end{bmatrix} \begin{bmatrix} \sqrt{a_{11}} & \mathbf{l}^T/\sqrt{a_{11}} \\ 0 & I_{n-1} \end{bmatrix}A=[a11​​l/a11​​​0In−1​​][10​0B−llT/a11​​][a11​​0​lT/a11​​In−1​​]

其中 B−llT/a11B - \mathbf{l}\mathbf{l}^T/a_{11}B−llT/a11​ 为 Schur 补,其 Cholesky 分解与原矩阵的 Cholesky 分解递归相关。 这是因为若 Schur 补也能够 Cholesky 分解,即若有 B−llT/a11=Ln−1Ln−1TB - \mathbf{l}\mathbf{l}^T/a_{11} = L_{n-1} L_{n-1}^TB−llT/a11​=Ln−1​Ln−1T​,则:

A=[a110l/a11Ln−1][a11lT/a110Ln−1T]A = \begin{bmatrix} \sqrt{a_{11}} & 0 \\ \mathbf{l}/\sqrt{a_{11}} & L_{n-1} \end{bmatrix} \begin{bmatrix} \sqrt{a_{11}} & \mathbf{l}^T/\sqrt{a_{11}} \\ 0 & L_{n-1}^T \end{bmatrix}A=[a11​​l/a11​​​0Ln−1​​][a11​​0​lT/a11​​Ln−1T​​]

原位存储的 Cholesky 分解:

实际上每一递归步都是按照红橙黄的顺序进行计算,其中红橙色区域计算得到的是最终值,而黄色区域计算得到的是中间值。又由于对称性,所以我们只使用和计算下三角部分。

for k in range(n):
   # 计算红色区域
   A[k, k] = np.sqrt(A[k, k])
   # 计算橙色区域
   for i in range(k + 1, n):
      A[i, k] = A[i, k] / A[k, k]
   # 计算黄色区域
   for i in range(k + 1, n):
      for j in range(i, n):
         A[i, j] = A[i, j] - A[i, k] * A[j, k] # 前面已经除了,故不需再除
图5:递归 Cholesky 分解求解顺序
图5:递归 Cholesky 分解求解顺序

注:利用了对称性,只需计算下三角部分,存储空间和运算量都减半。

LDL^T 分解

当矩阵对称但不一定是正定时,可采用 LDLTLDL^TLDLT 分解。当然,对于对称正定矩阵也适用,并且比 Cholesky 分解少 nnn 次开方运算。

数学基础:LDL^T 分解的存在唯一性

定理: 设 A∈Rn×nA \in \mathbb{R}^{n \times n}A∈Rn×n 是对称矩阵,且其所有顺序主子式 det⁡(Ak)≠0\det(A_k) \neq 0det(Ak​)=0(其中 AkA_kAk​ 为 AAA 的前 kkk 阶顺序主子阵), 则存在唯一的单位下三角矩阵 LLL(对角元全为 111)和对角矩阵 DDD(对角元 dk≠0d_k \neq 0dk​=0),使得:

A=LDLTA = LDL^TA=LDLT

特别地,若 AAA 对称正定(SPD),则所有 dk>0d_k > 0dk​>0。

与 Cholesky 分解的关系:
若 A=LLTA = LL^TA=LLT 是标准 Cholesky 分解,则 LDLTLDL^TLDLT 分解可视为 Lchol=LldlDL_{\text{chol}} = L_{\text{ldl}} \sqrt{D}Lchol​=Lldl​D​,从而避免开方运算,数值上更稳定,尤其适用于半正定或接近奇异的矩阵。

证明:

采用归纳法。

n=1n=1n=1 时,显然存在且唯一。

假设对 n≤k−1n \le k-1n≤k−1 结论成立,下证对于 n=kn=kn=k 也成立。

将 AkA_kAk​ 分块为

Ak=[Ak−1vvTakk],A_k = \begin{bmatrix} A_{k-1} & v \\ v^T & a_{kk} \end{bmatrix},Ak​=[Ak−1​vT​vakk​​],

其中 v∈Rk−1v \in \mathbb{R}^{k-1}v∈Rk−1。

由归纳假设,Ak−1=Lk−1Dk−1Lk−1TA_{k-1} = L_{k-1} D_{k-1} L_{k-1}^TAk−1​=Lk−1​Dk−1​Lk−1T​。设待求的 LkL_kLk​ 和 DkD_kDk​ 分块为

Lk=[Lk−10wT1],Dk=[Dk−100dkk],L_k = \begin{bmatrix} L_{k-1} & 0 \\ w^T & 1 \end{bmatrix}, \quad D_k = \begin{bmatrix} D_{k-1} & 0 \\ 0 & d_{kk} \end{bmatrix},Lk​=[Lk−1​wT​01​],Dk​=[Dk−1​0​0dkk​​],

其中 w∈Rk−1w \in \mathbb{R}^{k-1}w∈Rk−1,dkk≠0d_{kk} \neq 0dkk​=0 待定。

计算 LkDkLkTL_k D_k L_k^TLk​Dk​LkT​:

LkDkLkT=[Lk−1Dk−1Lk−1TLk−1Dk−1wwTDk−1Lk−1TwTDk−1w+dkk].L_k D_k L_k^T = \begin{bmatrix} L_{k-1} D_{k-1} L_{k-1}^T & L_{k-1} D_{k-1} w \\ w^T D_{k-1} L_{k-1}^T & w^T D_{k-1} w + d_{kk} \end{bmatrix}.Lk​Dk​LkT​=[Lk−1​Dk−1​Lk−1T​wTDk−1​Lk−1T​​Lk−1​Dk−1​wwTDk−1​w+dkk​​].

令其等于 AkA_kAk​,得方程组:

{Ak−1=Lk−1Dk−1Lk−1T(由归纳假设满足)v=Lk−1Dk−1w(1)akk=wTDk−1w+dkk(2)\begin{cases} A_{k-1} = L_{k-1} D_{k-1} L_{k-1}^T & \text{(由归纳假设满足)}\\ v = L_{k-1} D_{k-1} w & \text{(1)}\\ a_{kk} = w^T D_{k-1} w + d_{kk} & \text{(2)} \end{cases}⎩⎨⎧​Ak−1​=Lk−1​Dk−1​Lk−1T​v=Lk−1​Dk−1​wakk​=wTDk−1​w+dkk​​(由归纳假设满足)(1)(2)​

由 (1) 解得

w=Dk−1−1Lk−1−1v.w = D_{k-1}^{-1} L_{k-1}^{-1} v.w=Dk−1−1​Lk−1−1​v.

由于 Lk−1L_{k-1}Lk−1​ 单位下三角可逆,Dk−1D_{k-1}Dk−1​ 对角元非零可逆,故 www 存在且唯一。

代入 (2) 得

dkk=akk−wTDk−1w.d_{kk} = a_{kk} - w^T D_{k-1} w.dkk​=akk​−wTDk−1​w.

dkkd_{kk}dkk​ 由给定数据唯一确定。至此 LkL_kLk​、DkD_kDk​ 被唯一确定。

需要验证 dkk≠0d_{kk} \neq 0dkk​=0:由条件 det⁡(Ak)≠0\det(A_k) \neq 0det(Ak​)=0,且

det⁡(Ak)=det⁡(LkDkLkT)=det⁡(Dk)=(∏i=1k−1di)⋅dkk.\det(A_k) = \det(L_k D_k L_k^T) = \det(D_k) = \left( \prod_{i=1}^{k-1} d_i \right) \cdot d_{kk}.det(Ak​)=det(Lk​Dk​LkT​)=det(Dk​)=(i=1∏k−1​di​)⋅dkk​.

由归纳假设 ∏i=1k−1di=det⁡(Ak−1)≠0\prod_{i=1}^{k-1} d_i = \det(A_{k-1}) \neq 0∏i=1k−1​di​=det(Ak−1​)=0,结合 det⁡(Ak)≠0\det(A_k) \neq 0det(Ak​)=0 得 dkk≠0d_{kk} \neq 0dkk​=0。证毕。

当正定时,显然可知所有 dk>0d_k \gt 0dk​>0

LDL^T 分解计算公式

从 A=LDLTA = LDL^TA=LDLT 出发,设 LLL 为单位下三角,D=diag⁡(d1,d2,…,dn)D = \operatorname{diag}(d_1, d_2, \dots, d_n)D=diag(d1​,d2​,…,dn​),对比矩阵元素可得:

dk=akk−∑j=1k−1lkj2dj,k=1,2,…,nd_k = a_{kk} - \sum_{j=1}^{k-1} l_{kj}^2 d_j, \quad k=1,2,\dots,ndk​=akk​−j=1∑k−1​lkj2​dj​,k=1,2,…,n lik=1dk(aik−∑j=1k−1lijlkjdj),i=k+1,…,nl_{ik} = \frac{1}{d_k} \left( a_{ik} - \sum_{j=1}^{k-1} l_{ij} l_{kj} d_j \right), \quad i=k+1,\dots,nlik​=dk​1​(aik​−j=1∑k−1​lij​lkj​dj​),i=k+1,…,n

证明:

由 A=LDLTA = LDL^TA=LDLT,且 LLL 下三角且对角元为 111,得

aij=∑k=1min⁡(i,j)likdkljk.a_{ij} = \sum_{k=1}^{\min(i,j)} l_{ik} d_k l_{jk}.aij​=k=1∑min(i,j)​lik​dk​ljk​.
  • 情形 1:对角元 (i=ji=ji=j)

    aii=∑k=1ilik2dk=∑k=1i−1lik2dk+di.a_{ii} = \sum_{k=1}^{i} l_{ik}^2 d_k = \sum_{k=1}^{i-1} l_{ik}^2 d_k + d_i.aii​=k=1∑i​lik2​dk​=k=1∑i−1​lik2​dk​+di​.

    将 iii 替换为 kkk,并化简得:

    dk=akk−∑j=1k−1lkj2dj.d_k = a_{kk} - \sum_{j=1}^{k-1} l_{kj}^2 d_j.dk​=akk​−j=1∑k−1​lkj2​dj​.
  • 情形 2:非对角元 (i>ji>ji>j)

    aij=∑k=1jlikdkljk=∑k=1j−1likdkljk+lijdj.a_{ij} = \sum_{k=1}^{j} l_{ik} d_k l_{jk} = \sum_{k=1}^{j-1} l_{ik} d_k l_{jk} + l_{ij} d_j.aij​=k=1∑j​lik​dk​ljk​=k=1∑j−1​lik​dk​ljk​+lij​dj​.

    将 jjj 换为 kkk,iii 换为 iii,kkk 换为 jjj,并化简得:

    lik=1dk(aik−∑j=1k−1lijlkjdj),i=k+1,…,n.l_{ik} = \frac{1}{d_k} \left( a_{ik} - \sum_{j=1}^{k-1} l_{ij} l_{kj} d_j \right), \quad i=k+1,\dots,n.lik​=dk​1​(aik​−j=1∑k−1​lij​lkj​dj​),i=k+1,…,n.

实际操作时计算顺序与 Cholesky 分解类似,不过此处对角元在 D 中计算。

LDL^T 分解代码

标准 LDL^T 分解

L = np.eye(n)
D = np.zeros(n)
for k in range(n):
   # 计算 D 中对角元 d_k
   D[k] = A[k, k]
   for j in range(k):
      D[k] = D[k] - L[k, j] * L[k, j] * D[j]
    
   # 计算 L 中第 k 列严格下三角部分
   for i in range(k + 1, n):
      L[i, k] = A[i, k]
      for j in range(k):
         L[i, k] = L[i, k] - L[i, j] * L[k, j] * D[j]
      L[i, k] = L[i, k] / D[k]

原位存储 LDL^T 分解

对于对称阵

A=[a11lTlB]A = \begin{bmatrix} a_{11} & \mathbf{l}^T \\ \mathbf{l} & B \end{bmatrix}A=[a11​l​lTB​]

其中 a11≠0a_{11} \ne 0a11​=0,BBB 对称。

则:

A=[10l/a11In−1][a1100B−llT/a11][1lT/a110In−1]A = \begin{bmatrix} 1 & 0 \\ \mathbf{l}/a_{11} & I_{n-1} \end{bmatrix} \begin{bmatrix} a_{11} & 0 \\ 0 & B - \mathbf{l}\mathbf{l}^T / a_{11} \end{bmatrix} \begin{bmatrix} 1 & \mathbf{l}^T/a_{11} \\ 0 & I_{n-1} \end{bmatrix}A=[1l/a11​​0In−1​​][a11​0​0B−llT/a11​​][10​lT/a11​In−1​​]

其中 B−llT/a11B - \mathbf{l}\mathbf{l}^T / a_{11}B−llT/a11​ 是 Schur 补,其 LDLTLDL^TLDLT 分解可以递归进行。也即若有 B−llT/a11=Ln−1Dn−1Ln−1TB - \mathbf{l}\mathbf{l}^T / a_{11} = L_{n-1}D_{n-1}L_{n-1}^TB−llT/a11​=Ln−1​Dn−1​Ln−1T​,则:

A=[10l/a11Ln−1][a1100Dn−1][1lT/a110Ln−1T]A = \begin{bmatrix} 1 & 0 \\ \mathbf{l}/a_{11} & L_{n-1} \end{bmatrix} \begin{bmatrix} a_{11} & 0 \\ 0 & D_{n-1} \end{bmatrix} \begin{bmatrix} 1 & \mathbf{l}^T/a_{11} \\ 0 & L_{n-1}^T \end{bmatrix}A=[1l/a11​​0Ln−1​​][a11​0​0Dn−1​​][10​lT/a11​Ln−1T​​]

原位存储时,我们只需存储下三角部分(包括对角元),最终 AAA 的下三角部分被 LLL 的严格下三角部分和 DDD 的对角元覆盖。

for k in range(n):
   # 计算 D 中对角元 d_k,暂存在 A[k, k]
   for i in range(k + 1, n):
      A[i, k] = A[i, k] / A[k, k]
   # 计算 L 中第 k 列严格下三角部分
   for i in range(k + 1, n):
      for j in range(i, n):
         A[i, j] = A[i, j] - A[i, k] * A[j, k] # 前面已经除了,故不需再除

注:
与 Cholesky 分解相比,LDLTLDL^TLDLT 避免了开方运算,数值稳定性更好,且可推广到对称不定矩阵(此时 DDD 允许有正负对角元)。

方法对比与总结

各种分解方法对比

方法适用条件存储需求运算量特点
LU 分解一般方阵(顺序主子式非零)n2n^2n223n3\frac{2}{3}n^332​n3通用性强,适合多次右端项求解
Cholesky 分解对称正定矩阵12n2\frac{1}{2}n^221​n213n3\frac{1}{3}n^331​n3运算量少,数值稳定性好
LDL^T 分解对称矩阵(顺序主子式非零)12n2\frac{1}{2}n^221​n213n3\frac{1}{3}n^331​n3无需开方,适用于对称不定矩阵
带状 LU 分解带状矩阵(p+q+1)n(p+q+1)n(p+q+1)nnpqnpqnpq利用稀疏性,效率高

数值稳定性对比

  • LU 分解:若主元很小,可能导致舍入误差放大,需列主元(PLU分解)提高稳定性。
  • Cholesky 分解:若主元 lkk=akk(k)l_{kk} = \sqrt{a_{kk}^{(k)}}lkk​=akk(k)​​ 很小,则会不稳定。
  • LDL^T 分解:对称矩阵中,若 DDD 中出现接近 0 的对角元,同样会有数值稳定性问题。
目录
  • LU 分解概述
    • 定义
    • 利用 LU 分解求解线性方程组
  • LU 分解
    • 数学基础:LU 分解的存在唯一性
    • LU 分解计算公式
    • LU 分解代码
      • 标准 LU 分解
      • 原位存储 LU 分解
      • 递归的 LU 分解
    • 复杂度分析
  • 带状矩阵的 LU 分解
    • 带状矩阵定义
    • 带状矩阵的压缩存储
    • 数学基础:带状矩阵 LU 分解的稀疏性
    • 未压缩的算法
  • 正定矩阵的 Cholesky 分解
    • 数学基础:正定矩阵
    • 数学基础:Cholesky 分解的存在唯一性
    • Cholesky 分解计算公式
    • Cholesky 分解代码
      • 标准 Cholesky 分解
      • 原位存储 Cholesky 分解
  • LDL^T 分解
    • 数学基础:LDL^T 分解的存在唯一性
    • LDL^T 分解计算公式
    • LDL^T 分解代码
      • 标准 LDL^T 分解
      • 原位存储 LDL^T 分解
  • 方法对比与总结
    • 各种分解方法对比
    • 数值稳定性对比
© 2026 miniyuan. All rights reserved.
Go to miniyuan's GitHub repo