【发布时间】:2019-01-12 05:50:29
【问题描述】:
R 内核中没有 LU 分解功能。尽管这种分解是solve 的一个步骤,但它并没有明确地作为独立函数提供。我们可以为此编写一个 R 函数吗?它需要模仿 LAPACK 例程dgetrf。 Matrix 包有一个 lu function 很好,但如果我们能写一个 trackable R 函数会更好,它可以
- 将矩阵分解到某一列/行并返回中间结果;
- 继续从中间结果分解到另一列/行或到末尾。
此功能对于教育和调试目的都很有用。教育的好处是显而易见的,因为我们可以逐列说明分解/高斯消除。对于调试使用,这里有两个例子。
在Inconsistent results between LU decomposition in R and Python 中,有人问为什么 R 和 Python 中的 LU 分解会给出不同的结果。我们可以清楚地看到,两个软件都返回相同的第一个枢轴和第二个枢轴,但不是第三个。所以当分解进行到第三行/列时,一定有一些有趣的事情。如果我们能检索到该临时结果进行调查,那就太好了。
在Can I stably invert a Vandermonde matrix with many small values in R? 中,这种类型的矩阵的 LU 分解是不稳定的。在我的回答中,给出了一个 3 x 3 矩阵作为示例。我希望solve 会产生一个抱怨U[3, 3] = 0 的错误,但是运行solve 几次我发现solve 有时会成功。因此,对于数值调查,我想知道当分解进行到第二列/行时会发生什么。
由于该函数是用纯 R 代码编写的,因此对于中等到大的矩阵,预计会很慢。但性能不是问题,因为教育和调试我们只使用一个小矩阵。
dgetrf 的一点介绍
LAPACK 的 dgetrf 使用行旋转计算 LU 分解:A = PLU。在分解退出时,
-
L是一个单位下三角矩阵,存放在A的下三角部分; -
U是一个上三角矩阵,存放在A的上三角部分; -
P是一个行置换矩阵,存储为一个单独的置换索引向量。
除非一个主元完全为零(没有达到一定的容差),否则应该继续分解。
我从什么开始
编写既没有行旋转也没有“暂停/继续”选项的 LU 分解并不具有挑战性:
LU <- function (A) {
## check dimension
n <- dim(A)
if (n[1] != n[2]) stop("'A' must be a square matrix")
n <- n[1]
## Gaussian elimination
for (j in 1:(n - 1)) {
ind <- (j + 1):n
## check if the pivot is EXACTLY 0
piv <- A[j, j]
if (piv == 0) stop(sprintf("system is exactly singular: U[%d, %d] = 0", j, j))
l <- A[ind, j] / piv
## update `L` factor
A[ind, j] <- l
## update `U` factor by Gaussian elimination
A[ind, ind] <- A[ind, ind] - tcrossprod(l, A[j, ind])
}
A
}
当不需要旋转时,这会给出正确的结果:
A <- structure(c(0.923065107548609, 0.922819485189393, 0.277002309216186,
0.532856695353985, 0.481061384081841, 0.0952619954477996,
0.261916425777599, 0.433514681644738, 0.677919807843864,
0.771985625848174, 0.705952850636095, 0.873727774480358,
0.28782021952793, 0.863347264472395, 0.627262107795104,
0.187472499441355), .Dim = c(4L, 4L))
oo <- LU(A)
oo
# [,1] [,2] [,3] [,4]
#[1,] 0.9230651 0.4810614 0.67791981 0.2878202
#[2,] 0.9997339 -0.3856714 0.09424621 0.5756036
#[3,] 0.3000897 -0.3048058 0.53124291 0.7163376
#[4,] 0.5772688 -0.4040044 0.97970570 -0.4479307
L <- diag(4)
low <- lower.tri(L)
L[low] <- oo[low]
L
# [,1] [,2] [,3] [,4]
#[1,] 1.0000000 0.0000000 0.0000000 0
#[2,] 0.9997339 1.0000000 0.0000000 0
#[3,] 0.3000897 -0.3048058 1.0000000 0
#[4,] 0.5772688 -0.4040044 0.9797057 1
U <- oo
U[low] <- 0
U
# [,1] [,2] [,3] [,4]
#[1,] 0.9230651 0.4810614 0.67791981 0.2878202
#[2,] 0.0000000 -0.3856714 0.09424621 0.5756036
#[3,] 0.0000000 0.0000000 0.53124291 0.7163376
#[4,] 0.0000000 0.0000000 0.00000000 -0.4479307
与lu 来自Matrix 包的比较:
library(Matrix)
rr <- expand(lu(A))
rr
#$L
#4 x 4 Matrix of class "dtrMatrix" (unitriangular)
# [,1] [,2] [,3] [,4]
#[1,] 1.0000000 . . .
#[2,] 0.9997339 1.0000000 . .
#[3,] 0.3000897 -0.3048058 1.0000000 .
#[4,] 0.5772688 -0.4040044 0.9797057 1.0000000
#
#$U
#4 x 4 Matrix of class "dtrMatrix"
# [,1] [,2] [,3] [,4]
#[1,] 0.92306511 0.48106138 0.67791981 0.28782022
#[2,] . -0.38567138 0.09424621 0.57560363
#[3,] . . 0.53124291 0.71633755
#[4,] . . . -0.44793070
#
#$P
#4 x 4 sparse Matrix of class "pMatrix"
#
#[1,] | . . .
#[2,] . | . .
#[3,] . . | .
#[4,] . . . |
现在考虑一个置换的A:
B <- A[c(4, 3, 1, 2), ]
LU(B)
# [,1] [,2] [,3] [,4]
#[1,] 0.5328567 0.43351468 0.8737278 0.1874725
#[2,] 0.5198439 0.03655646 0.2517508 0.5298057
#[3,] 1.7322952 -7.38348421 1.0231633 3.8748743
#[4,] 1.7318343 -17.93154011 3.6876940 -4.2504433
结果与LU(A) 不同。但是,由于Matrix::lu 执行行旋转,lu(B) 的结果仅与排列矩阵中的lu(A) 不同:
expand(lu(B))$P
#4 x 4 sparse Matrix of class "pMatrix"
#
#[1,] . . . |
#[2,] . . | .
#[3,] | . . .
#[4,] . | . .
【问题讨论】:
-
pracma包也有一个lu函数,但它不应用旋转。读者可以验证(使用矩阵B)它等同于我的问题中的函数LU。pracma::lu在 R 级别使用双循环嵌套编写得很糟糕。LU使用单个 R 级循环,因此是更好的实现。
标签: r function matrix matrix-factorization