我已经从提供的用于就地LU矩阵分解的C的Numerical Recipes的源代码中复制并粘贴,问题是它不起作用。
我确信我做的事情很愚蠢,但我很高兴有人能指出我正确的方向;我整天都在努力工作,看不出我做错了什么。
POST-ANSWER UPDATE:项目是finished and working。感谢大家的指导。
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#define MAT1 3
#define TINY 1e-20
int h_NR_LU_decomp(float *a, int *indx){
//Taken from Numerical Recipies for C
int i,imax,j,k;
float big,dum,sum,temp;
int n=MAT1;
float vv[MAT1];
int d=1.0;
//Loop over rows to get implicit scaling info
for (i=0;i<n;i++) {
big=0.0;
for (j=0;j<n;j++)
if ((temp=fabs(a[i*MAT1+j])) > big)
big=temp;
if (big == 0.0) return -1; //Singular Matrix
vv[i]=1.0/big;
}
//Outer kij loop
for (j=0;j<n;j++) {
for (i=0;i<j;i++) {
sum=a[i*MAT1+j];
for (k=0;k<i;k++)
sum -= a[i*MAT1+k]*a[k*MAT1+j];
a[i*MAT1+j]=sum;
}
big=0.0;
//search for largest pivot
for (i=j;i<n;i++) {
sum=a[i*MAT1+j];
for (k=0;k<j;k++) sum -= a[i*MAT1+k]*a[k*MAT1+j];
a[i*MAT1+j]=sum;
if ((dum=vv[i]*fabs(sum)) >= big) {
big=dum;
imax=i;
}
}
//Do we need to swap any rows?
if (j != imax) {
for (k=0;k<n;k++) {
dum=a[imax*MAT1+k];
a[imax*MAT1+k]=a[j*MAT1+k];
a[j*MAT1+k]=dum;
}
d = -d;
vv[imax]=vv[j];
}
indx[j]=imax;
if (a[j*MAT1+j] == 0.0) a[j*MAT1+j]=TINY;
for (k=j+1;k<n;k++) {
dum=1.0/(a[j*MAT1+j]);
for (i=j+1;i<n;i++) a[i*MAT1+j] *= dum;
}
}
return 0;
}
void main(){
//3x3 Matrix
float exampleA[]={1,3,-2,3,5,6,2,4,3};
//pivot array (not used currently)
int* h_pivot = (int *)malloc(sizeof(int)*MAT1);
int retval = h_NR_LU_decomp(&exampleA[0],h_pivot);
for (unsigned int i=0; i<3; i++){
printf("\n%d:",h_pivot[i]);
for (unsigned int j=0;j<3; j++){
printf("%.1lf,",exampleA[i*3+j]);
}
}
}
WolframAlpha说答案应该是
1,3,-2
2,-2,7
3,2,-2
我得到了:
2,4,3
0.2,2,-2.8
0.8,1,6.5
到目前为止,我已经找到了至少3种不同版本的“相同”算法,所以我完全糊涂了。
PS是的我知道至少有十几个不同的库可以做到这一点,但我更感兴趣的是理解我做错了什么而不是正确的答案。
PPS因为在LU分解中,下面的结果矩阵是单位的,并且使用Crouts算法(我认为)实现,阵列索引访问仍然是安全的,L和U都可以就地叠加在一起;因此,单个结果矩阵。
答案 0 :(得分:9)
我认为您的指数本身存在一些错误。它们有时会有不寻常的开始值和结束值,而j
而不是i
的外部循环会让我产生怀疑。
在你要求任何人检查你的代码之前,这里有一些建议:
sum
a(i,j)
代替a[i*MAT1+j]
以下是遵循这些建议的版本:
#define MAT1 3
#define a(i,j) a[(i)*MAT1+(j)]
int h_NR_LU_decomp(float *a, int *indx)
{
int i, j, k;
int n = MAT1;
for (i = 0; i < n; i++) {
// compute R
for (j = i; j < n; j++)
for (k = 0; k < i-2; k++)
a(i,j) -= a(i,k) * a(k,j);
// compute L
for (j = i+1; j < n; j++)
for (k = 0; k < i-2; k++)
a(j,i) -= a(j,k) * a(k,i);
}
return 0;
}
它的主要优点是:
我的建议是:不要在不理解的情况下复制别人的代码。
大多数程序员都是程序员。
答案 1 :(得分:9)
对于所有圣洁的爱,不要将数字接收代码用于任何事情,除了作为教学目的的玩具实现,文本中描述的算法 - 实际上,文本并不那么好。并且,正如您正在学习的那样,代码也不是。
当然不会在您自己的代码中放置任何数字Recipies例程 - 许可证具有极大的限制性,特别是考虑到代码质量。如果你有NR内容,你将无法分发自己的代码。
查看您的系统是否已安装LAPACK库。它是计算科学和工程中线性代数例程的标准接口,虽然它并不完美,但您可以为任何移动代码的机器找到lapack库,并且可以编译,链接和运行。如果它尚未安装在您的系统上,您的软件包管理器(rpm,apt-get,fink,port等)可能知道lapack并可以为您安装。如果没有,只要您的系统上有Fortran编译器,就可以从here下载并编译它,并且可以在同一页面的下方找到标准的C绑定。
为线性代数例程提供标准API非常方便的原因是它们非常常见,但它们的性能与系统有关。例如,Goto BLAS 是线性代数所需的低级操作的x86系统的极其快速的实现;一旦你有LAPACK工作,你可以安装该库以尽可能快地完成任务。
一旦安装了任何类型的LAPACK,对一般矩阵进行LU分解的例程是浮点数的SGETRF,或者双数的DGETRF。如果您对矩阵的结构有所了解,那么还有其他更快的例程 - 它是对称的正定,比如(SBPTRF),或者它是三对角(STDTRF)。这是一个很大的图书馆,但是一旦你学会了它,你就会在你的数字工具箱中拥有一个非常强大的装备。
答案 2 :(得分:3)
对我来说最可疑的是标有“寻找最大支点”的部分。这不仅会搜索,还会更改矩阵A.我发现很难相信这是正确的。
不同版本的LU算法在旋转方面有所不同,因此请确保您理解这一点。您无法比较不同算法的结果。更好的检查是查看L乘以U是否等于原始矩阵,或者如果算法进行旋转,则检查其是否排列。话虽如此,你的结果是错误的,因为行列式是错误的(旋转不会改变行列式,除了符号)。
除了@Philip有很好的建议。如果你想理解代码,首先要理解LU分解而不需要转动。
答案 3 :(得分:3)
严厉地解释阿尔伯特·爱因斯坦:
......一个有手表的男人总是知道 确切的时间,但有两个人是 永远不会确定....
您的代码肯定不会产生正确的结果,但即使是这样,旋转的结果也不会直接对应于没有旋转的结果。在一个透视解决方案的背景下,Alpha真正给你的是这个等价:
1 0 0 1 0 0 1 3 -2
P= 0 1 0 L= 2 1 0 U = 0 -2 7
0 0 1 3 2 1 0 0 -2
然后满足条件A = P.L.U(其中。表示矩阵乘积)。如果我以另一种方式计算(理论上)相同的分解操作(在这种情况下使用LAPACK例程dgetrf通过numpy):
In [27]: A
Out[27]:
array([[ 1, 3, -2],
[ 3, 5, 6],
[ 2, 4, 3]])
In [28]: import scipy.linalg as la
In [29]: LU,ipivot = la.lu_factor(A)
In [30]: print LU
[[ 3. 5. 6. ]
[ 0.33333333 1.33333333 -4. ]
[ 0.66666667 0.5 1. ]]
In [31]: print ipivot
[1 1 2]
在用ipivot做了一点黑魔法之后我们得到了
0 1 0 1 0 0 3 5 6
P = 0 0 1 L = 0.33333 1 0 U = 0 1.3333 -4
1 0 0 0.66667 0.5 1 0 0 1
也满足A = P.L.U.这两种因素都是正确的,但它们是不同的,它们与NR代码的正确功能版本不对应。
因此,在你决定是否有“正确”的答案之前,你真的应该花一些时间来理解你复制的代码所实现的实际算法。