LU-decomposition -- LU-分解法

Adam posted @ Mon, 05 Mar 2012 18:55:11 +0800 in Numerical Receipes with tags c++ NumericalAlgebra , 3745 readers

LU分解法:

最好的功能是在于它在解A*x = b,并且当有许多个A相同,b不同的式子时,效率较高。

算法思想(由VC++那本书上说,这个又叫杜利特尔Doolittle方法):

我们将A分解成为两个子矩阵L和U,令A = L * U。其中L是下三角矩阵,U是上三角矩阵。

大致可以得到如下(Tex公式):

[tex]\begin{pmatrix} L_{00} & 0 & 0 & 0 \\ L_{10} & L_{11} & 0 & 0 \\ L_{20} & L_{21} & L_{22} & 0 \\ L_{30} & L_{31} & L_{32} & L_{33} \end{pmatrix} \cdot \begin{pmatrix} U_{00} & U_{01} & U_{02} & U_{03} \\ 0 & U_{11} & U_{12} & U_{13} \\ 0 & 0 & U_{22} & U_{23} \\ 0 & 0 & 0 & U_{33} \end{pmatrix} = \begin{pmatrix} a_{00} & a_{01} & a_{02} & a_{03} \\ a_{10} & a_{11} & a_{12} & a_{13} \\ a_{20} & a_{21} & a_{22} & a_{23} \\ a_{30} & a_{31} & a_{32} & a_{33} \end{pmatrix}[/tex]

非Tex公式:

(L00    0    0    0)    (U00 U01 U02 U03)    (a00 a01 a02 a03)
(L10 L11    0    0)    (    0 U11 U12 U13)    (a10 a11 a12 a13)
(L20 L21 L22    0) * (    0    0 U22 U23) = (a20 a21 a22 a23)
(L30 L31 L32 L33)    (    0    0    0 U33)    (a30 a31 a32 a33)

于是我们可以将整个线性方程化为如下形式:

A * x = (L * U) * x = L * (U * x) = b

先解出:

L * y = b

然后再解出:

U * x = y

即可得到最终解。

注:我们在储存L和U的时候,可以将两个矩阵储存在同一矩阵之中。

算法过程:

(1)A分解为L和U:

我们先将L[i,i]令为1,其中i = 0,.., N-1

对于for(j=0;j <= N-1; ++j),

for(i=0;i <= j; ++i)

[tex]U_{ij} = a_{ij} - \sum_{k=0}^{i-1}(L_{ik}*U_{kj})[/tex]

非tex公式:U[i,j] = a[i,j] - Sum[ L[i,k]*U[k,j],{k,0,i-1} ]

for(i=j+1;i <= N-1; ++i)

[tex]L_{ij} = \frac {1}{U_{jj}}[L_{ij} - \sum_{k=0}^{j-1}(L_{ik}*U_{kj})][/tex]

非tex公式:L[i,j] = 1/U[j,j]*(L[i,j] - Sum[ L[i,k]*U[k,j], {k,0,j-1} ]

(2)对于L * y = b解y得到:

[tex]y_0 = \frac {b_0}{L_{00}}[/tex]
[tex]y_i = \frac {1}{L_{ii}}[b_i-\sum_{j=0}^{i-1}(L_{ij}*y_j)] , i = 1, 2, ..., N-1[/tex]

非tex公式:y[0] = b[0]/L[0,0]; y[i] = 1/L[i,i]*(b[i] - Sum[ L[i,j] * y[j], {j,0,i-1} ]), i = 1, 2, ..., N-1

对于U * x = y解x得到:

[tex]x_{N-1} = \frac {y_{N-1}}{U_{N-1,N-1}}[/tex]
[tex]x_i = \frac {1}{U_{ii}}[y_i-\sum_{j=i+1}^{N-1}(U_{ij}*x_j)] , i = N-2, N-3, ..., 0[/tex]

非tex公式:x[N-1] = y[N-1]/U[N-1,N-1]; x[i] = 1/U[i,i]*(y[i] - Sum[ U[i,j] * x[j], {j,i+1,N-1} ], i = N-2, N-3, ..., 0

使用概述:

如果需要重复使用A矩阵来解不同b的线性方程时,我们在使用完第一次solve(b,x)之后,重新刷新b的值,再度调用solve(b,x)即可在x中获得新的解。如有不明白之处请查看本文中LUdecomposition_test1.cpp

Version 1.0

/**
*@file LUdecomposition.h
*/
/**
*LU Decomposition Introduction:
*Solves "A * x = b"
*(1) Let "L * U = A", shown as follow:
*		(	L00		0		0		0	)		(	U00	U01	U02	U03	)		(	a00	a01	a02	a03	)
*		(	L10	L11		0		0	)		(		0	U11	U12	U13	)		(	a10	a11	a12	a13	)
*		(	L20	L21	L22		0	)	*	(		0		0	U22	U23	)	=	(	a20	a21	a22	a23	)
*		(	L30	L31	L32	L33	)		(		0		0		0	U33	)		(	a30	a31	a32	a33	)
*(2)	Solve "A * x = b" using a decomposition as:
*			A * x = (L * U) * x = L * (U * x) = b
*		then
*			L * y = b
*		then solve
*			U * x = y
*(3) solve for L and U as follow:
*			L[i,0] * U[0,j] + ... = a[i,j]
*		set "a[i,i] = 1, i = 0, ..., N-1"
*		for i = 0, 1, ..., j, solve for U[i,j] as follow
*			U[i,j] = a[i,j] - Sum[L[i,k] * U[k,j], {k, 0, j-1}]
*		for i = j+1, j+2, ..., N-1 solve for L[i,j] as follow
*			L[i,j] = 1/U[j,j] * (a[i,j] - Sum[L[i,k] * U[k,j], {k, 0, j-1}])
*(4) forward substitution as follow:
*			y[0] = b[0]/L[0,0];
*			y[i] = 1/L[i,i]*(b[i] - Sum[L[i,j] * y[j], {j, 0, i-1}]), i = 1, 2, ..., N-1
*		backsubstitution as follow:
*			x[N-1] = y[N-1]/U[N-1,N-1]
*			x[i] = 1/U[i,i]*(y[i] - Sum[U[i,j] * x[j], {j, i+1, N-1}]), i = N-2, N-3, ..., 0
*(5) save the L and U in a matrix as follow:
*			(	U00	U01	U02	U03	)
*			(	L10	U11	U12	U13	)
*			(	L20	L21	U22	U23	)
*			(	L30	L31	L32	U33	)
*		 Matrix--2.3.14
*/

#ifndef LUDECOMPOSITION_H_INCLUDED
#define LUDECOMPOSITION_H_INCLUDED

/**
*@brief Object for solving linear equations A*x = b using LU decomposition, and related functions.
*/
struct LUdecomposition{
	Int n;
	MatDoub lu; ///Stores the decomposition.
	VecInt indx; ///Stores the permutation.
	Doub det_d; ///Used by det.
	LUdecomposition(MatDoub_I &a); ///Constructor. Argument is the matrix A.
	void solve(VecDoub_I &b, VecDoub_O &x); ///Solve for a single right-hand side.
	void solve(MatDoub_I &b, MatDoub_O &x); ///Solve for multiple right-hand sides.
	void inverse(MatDoub_O &a_inverse); ///Calculate matrix inverse A^(-1).
	Doub det(); ///Return determinant of A.
	void mprove(VecDoub_I &b, VecDoub_IO &x);
	MatDoub_I &aref; ///Used only by mprove.
};

/**
*@brief Constructor
*@param  a[0..n-1][0..n-1]  Input as Matrix A in A * x = b, and Output a as after LU decomposition.
*Input:
*	Given a matrix a[0..n-1][0..n-1], this routine replaces it by the LU decomposition of a
*		rowwise permutation of itself. "a" is input.
*Output:
*	"a" is arranged as in "Matrix--2.3.14" above;
*	indx[0..n-1] is an output vector that records the row permutation effected by
*		the partial pivoting
*	d is output as 1 or -1 depending on whether the number of row interchanges
*		was even or odd, respectively.
*This routine is used in combination with solve to solve linear equations or invert a matrix.
*/
LUdecomposition :: LUdecomposition(MatDoub_I &a) : n(a.nrows()), lu(a), aref(a), indx(n) {
	const Doub TINY = 1.0e-40; //A small number;
	Int i, imax, j, k;
	Doub big, temp;
	VecDoub vv(n); //vv stores the implicit scaling of each row.
	det_d = 1.0; //No row interchages yet.
	for(i = 0; i < n; ++i){
		//Loop over rows to get the implicit scaling information
		big = 0.0;
		for(j = 0; j < n; ++j){
			if((temp = abs(lu[i][j])) > big)
				big = temp;
		}//for
		if(big == 0.0) //No nonzero largest element.
			NRthrow("Singular matrix in LUdecomposition");
		vv[i] = 1.0/big;
	}//for

	for(k = 0; k < n; ++k){
		//This is the outermost k[j,j] loop.
		big = 0.0; //Initialize for the search for largest pivot element.
		for(i = k; i < n; ++i){
			temp = vv[i] * abs(lu[i][k]);
			if(temp > big){
				//Is the figure of merit for the pivot better than the best so far?
				big = temp;
				imax = i;
			}//if
		}//for
		if(k != imax){
			//Interchange rows
			for(j = 0; j < n; ++j){
				temp = lu[imax][j]; lu[imax][j] = lu[k][j]; lu[k][j] = temp;
			}//for
			det_d = -det_d; //change the parity of d
			vv[imax] = vv[k]; //interchange the scale factor.
		}//if
		indx[k] = imax;
		if(lu[k][k] == 0.0)
			lu[k][k] = TINY;
			/*
			*If the pivot element is zero, the matrix is singular (at least to the precision of
			*the algorithm). For some applications on singular matrices, it is desirable to
			*substitute TINY for zero.
			*/
			for(i = k+1; i < n; ++i){
				temp = lu[i][k] /= lu[k][k]; //Divide by the pivot element.
				for(j = k+1; j < n; ++j){
					//Innermost loop: reduce remaining submatrix.
					lu[i][j] -= temp * lu[k][j];
				}//for
			}//for
	}//for
}//LUdecomposition(MatDoub_I &a)

/**
*@brief LU Decomposition solve A * x = b for a single right-hand side.
*Solves the set of n linear equations A * x = b using the stored LU decomposition of A.
*b[0..n-1] is input as the right-hand side vector b, while x returns the solution vector x;
*b and x may reference the same vector, in which case the solution overwrites the input.
*This routine takes into account the possibility that b will begin with many zero elements,
*so it is efficient for use in matrix inversion.
*
*Input:
*@param  b[0..n-1]  Input as right-hand side vector
*Output:
*@param  x[0..n-1]  Output as solution vector x of A^(-1) * b
*/
void LUdecomposition :: solve(VecDoub_I &b, VecDoub_O &x){
	Int i, ii = 0, ip, j;
	Doub sum;
	if(b.size() != n || x.size() != n)
		NRthrow("LUdecomposition : solve bad sizes. The right-hand vector should be the same as"
				"the solution vector.");
	for(i = 0; i < n; ++i)
		x[i] = b[i];
	for(i = 0; i < n; ++i){
		/*
		*When ii is set to a positive value, it will become the index of the first
		*nonvanishing element of b. We now do the forward substitution in Information Step(4).
		*The only new small problem is to get the permutation as we go.
		*(
		*	If you read the "next_permutation" function of STL in "stl_algo.h", you will find a similar
		*	algorithm.
		*)
		*/
		ip = indx[i];
		sum = x[ip];
		x[ip] = x[i];
		if(ii != 0){
			for(j = ii-1; j < i; ++j)
				sum -= lu[i][j] * x[j];
		}//if
		else if(sum != 0.0){
			//A nonzero element was encountered, so from now we will have to do the sums in the loop above.
			ii = i+1;
		}//else if
		x[i] = sum;
	}//for
	for(i = n-1; i >= 0; --i){
		//Now we do the backsubstitution in Information Step(4).
		sum = x[i];
		for(j = i+1; j < n; ++j)
			sum -= lu[i][j] * x[j];
		x[i] = sum/lu[i][i]; //Store a component of the solution vector X.
	}//for
}//LUdecomposition :: solve(VecDoub_I &b, VecDoub_O &x)

/**
*@brief LU Decomposition solve for multiple right-hand sides.
*Solves m sets of n linear equations A * X = B using the stored LU decomposition of A.
*The matrix b[0..n-1][0..m-1] inputs the right-hand sides, while x[0..n-1][0..m-1] returns
*the solution A^(-1) * B.
*@attention b and x may reference the same matrix, in which case the solution overwrites the input.
*
*Input:
*@param  b[0..n-1][0..m-1]  Input as right-hand side matrix
*Output:
*@param  x[0..n-1][0..m-1]  Output as solution matrix x of A^(-1) * B
*/
void LUdecomposition :: solve(MatDoub_I &b, MatDoub_O &x){
	Int i, j, m = b.ncols();
	if(b.nrows() != n || x.nrows() != n || b.ncols() != x.ncols())
		NRthrow("LUdecomposition :: solve bad sizes. The right-hand matrix should be the same as"
				"the solution matrix.");
	VecDoub xx(n);
	for(j = 0; j < m; ++j){
		//Copy and solve each column in turn.
		for(i = 0; i < n; ++i)
			xx[i] = b[i][j];
		solve(xx,xx);
		for(i = 0; i < n; ++i)
			x[i][j] = xx[i];
	}//for
}//LUdecomposition :: solve(MatDoub_I &b, MatDoub_O &x)

/**
*@brief Using the stored LU decomposition, return in a_inverse the matrix inverse, A^(-1).
*@attention the matrix inverse, A^(-1) is in a_inverse.
*/
void LUdecomposition :: inverse(MatDoub_O &a_inverse){
	Int i,j;
	a_inverse.resize(n,n);
	for(i = 0;i < n; ++i){
		for(j = 0; j < n; ++j)
			a_inverse[i][j] = 0.;
		a_inverse[i][i] = 1.;
	}
	solve(a_inverse, a_inverse);
}

/**
*@brief Using the stored LU decomposition, return the determinant of the matrix A.
*@return the determinant of the matrix A
*/
Doub LUdecomposition :: det(){
	Doub dd = det_d;
	for(Int i = 0; i < n; ++i)
		dd *= lu[i][i];
	return dd;
}


#endif // LUDECOMPOSITION_H_INCLUDED

 

以下为对LUdecomposition.h头文件的测试文件,仅供参照:

/**
*@file LUdecomposition_test.cpp
*/
#include <iostream>
#include "nr3.h"
#include "LUdecomposition.h"
using namespace std;

int main(){
	MatDoub_IO a,b,x;
	int rows, cols;
	//INPUT DATA:
	cout << "Please input rows, cols and the matrix data for A: " << endl;
	cin >> rows >> cols;
	a.resize(rows, cols);
	readMatrix_console(a);

	//Do the Linear Solve; a and b will not be altered.
	{//Start a temporary scope.
		LUdecomposition lu(a);
		//Read matrix B1:
		cout << "Please input rows, cols and the matrix data for B: " << endl;
		cin >> rows >> cols;
		b.resize(rows, cols);
		x.resize(rows, cols);
		readMatrix_console(b);
		lu.solve(b,x);
		//PRINT:
		cout << "Gives the solution matrix: " << endl;
		printMatrix_console(x);
		//Read matrix B2:
		cout << "Please input rows, cols and the matrix data for B: " << endl;
		cin >> rows >> cols;
		b.resize(rows, cols);
		x.resize(rows, cols);
		readMatrix_console(b);
		//PRINT:
		cout << "Gives the solution matrix: " << endl;
		printMatrix_console(x);
	}//destroy alu.

	return 0;
}

 

Version 2.0

mprove方法的实现:

功能:

改进已知近似解的精度,既可用于求一般方程组的高精度解,也可以用于病态方程组的求解,复杂度O(n^2)。

算法原理:

对于A * x = b(记为①式),其中x表示精确解。你使用上述LU-decomposition的solve方法得到一个近似解x+△x,于是如果你将x+△x与矩阵A相乘,得到的将是一个近似的右端项b+△b,如:“A * (x+△x) = (b+△b)”,记为②式。

我们将②式减去①式,得到“A * △x = △b”记为③式。

我们将②式带入③式,替换△b,则得到:A * △x = A * (x+△x) - b(记为④式),代码中我们将A * (x+△x) - b记为sdp。

我们对④式再进行LUdecomposition分解,即代码中的solve(r, r),得到△x。

最后将△x从x中减去即可。

测试方法:

对于由上lu.solve(b,x)解出的值x解向量,加上一个扰动值(可以由随机数生成)。

然后执行lu.mprove(b,x);进行解的优化,最后再输出,看是否得到正确的解。

提醒:

由于mprove的复杂度只有O(n^2),相比于solve的复杂度O(n^3)要小很多,建议在每次执行solve之后都执行1~2次的mprove,可根据精度要求增加次数。

/**
*@brief	Improve a solution vector x[0..n-1] of the linear set of equations A * x = b.
*@param	b	the right-hand side vector as input
*@param	x Input as the original solution, Output as the improved solution.
*/
void LUdecomposition::mprove(VecDoub_I &b, VecDoub_IO &x){
	Int i, j;
	VecDoub r(n);
	for(i=0; i < n; ++i){
		//Calculate the right-hand side, accumulating the residual in higher precision.
		Ldoub sdp = -b[i];
		for(j=0; j < n; ++j)
			sdp += (Ldoub)aref[i][j] * (Ldoub)x[j];
		r[i] = sdp;
	}//for
	solve(r,r); //Solve for the error term.
	for(i = 0; i < n; ++i) //Subtract it from the old solution.
		x[i] -= r[i];
} //mprove(VecDoub_I &b, VecDoub_IO &x)

 


Login *


loading captcha image...
(type the code from the image)
or Ctrl+Enter