Python - 用 LU 分解求解线性方程组的数值问题

Python - Numerical issues for solving systems of linear equations with LU decomposition

信息

OS: 曼扎罗 Linux

开发环境:Spyder4 (Python3.7)

库:Numpy

问题

嗨,

我写了几个函数来按照这三种方法求解线性方程组:

  1. LU分解
  2. 雅可比方法
  3. 高斯-赛德尔法

程序运行完美。但是,LU 分解的结果让我很困扰。 例如,如果我的矩阵 A 和向量 b 是

A = np.array([[10., -1., 2., 0.],
          [-1., 11., -1., 3.],
          [2., -1., 10., -1.],
          [0., 3., -1., 8.]])
b = np.array([6., 25., -11., 15.])

那么我的结果是

  1. LU:[ 0.95034091 1.67840909 -0.9125 1.875 ]
  2. 雅各比:[ 1. 2. -1. 1.]
  3. 高斯-赛德尔:[ 1. 2. -1. 1.]

如您所见,LU 给我的结果略有不同。这是舍入或截断错误的问题吗?任何建议或帮助将不胜感激。

干杯!


编辑 1:

我解决了这个问题,它只是 LU 分解期间代码中的错误。感谢大家的反馈。


下面的共享代码:

def lu_decomposition(matrix_in, b_in, n):

lower = [[0 for x in range(n)]
            for y in range(n)];
upper = [[0 for x in range(n)]
            for y in range(n)];

# Doolittle's Method
# Decomposing matrix into upper and lower matrices
for i in range(n):
    # Upper triangle
    for k in range(i, n):
        # Sigma from j to i for each row k of U
        sum = 0
        for j in range(i):
            sum += lower[i][j] * upper[j][k]
        # Evaluate U for row k
        upper[i][k] = matrix_in[i][k] - sum
        
    # Lower Triangle
    for k in range(i,n):
        if(i == k): # Entry of a diagonal element
            lower[i][i] = 1 
        else:
            # Sigma from j to i for each column k of L
            sum = 0
            for j in range(i):
                sum += (lower[k][j] * upper[j][i])
            # Evaluate L for column k
            lower[k][i] = int( (matrix_in[k][i] - sum)/ upper[i][i])
            
# Perform forward substitution Ly=b
y = [0 for x in range(n)]
lower_inv = np.linalg.inv(lower)
y = np.dot(lower_inv, b_in)

# Perform back substitution Ux=y
x_sol = [0 for x in range(n)]
upper_inv = np.linalg.inv(upper)
x_sol = np.dot(upper_inv, y)

# printing results
# setw is for displaying nicely 
print("Lower Triangular\t\tUpper Triangular"); 

# Displaying the result : 
for i in range(n): 
      
    # Lower 
    for j in range(n): 
        print(lower[i][j], end = "\t");  
    print("", end = "\t"); 

    # Upper 
    for j in range(n): 
        print(upper[i][j], end = "\t"); 
    print(""); 
            
print("Here's the solution for your matrix: ")
print(x_sol)

您的代码中有很多问题需要解决:

  • Python 不需要;要结束一行,python 知道行何时结束。 Python 只使用 ;将两行代码放在同一物理行上。喜欢"x=7;print(x)"。
  • 如果您不在列表推导中使用变量,那么习惯上使用 _。例如,[0 for _ in range(10)]。当然,python还有更好的办法“[0]*10”。
  • 我注意到您导入了 numpy,但没有使用 numpy 数组的操作。这将极大地改进您的代码(并使其更快)并使其更易于阅读。例如,您可以同时写入和读取整行或整列。 "matrix[0,:]"(第一行),"matrix[:,0]"(第一列)。
  • 无需在 python 中包含对象的长度,因为 python 会自动存储其对象的长度,始终可以使用内置的 len() 检索。
  • 对您的代码来说并不重要(因为您可能正在练习这样做),但您可能已经知道,lu 分解已经存在,例如 scipy.linalg.lu()。事实上,快速检查 linalg.lu(A,True) 就会发现你的 L 和 U 都有错误。
  • 看到您手动生成 L 和 U 矩阵然后继续在 L 和 U 上使用 np.linalg.inv() 真的很奇怪。如果您愿意使用 np.linalg.inv( ) 函数,那么你的答案是一行,"np.linalg.inv(A) @ b"。通常,人们更容易找到 L 和 U 来手动求解 X。要找到 L 和 U 然后使用 numpy 的反函数,有点违背了目的。
  • 虽然它有时会有帮助,但 python 并不要求您在创建对象之前在内存中划出 space。 Python 自动管理您的内存创建和删除(无需创建空的零列表)。
  • np.dot() 只是手动访问 python 中的“@”运算符的方法。

一些示例:

import numpy as np
from scipy import linalg

def lu_dec_solver_v1(A,b):
    return np.linalg.inv(A) @ b

def lu_dec_solver_v2(A,b):
    L, U  = linalg.lu(A,True)
    L_inv = np.linalg.inv(L)
    U_inv = np.linalg.inv(U)
    return U_inv @ (L_inv @ b)

def lu_dec_solver_v3(A,b):
    U = A.copy()
    L = np.identity(len(A))
    for n in range(0,len(A)-1):
        for m in range(n+1,len(A)):
            L[m,n]  = U[m,n]/U[n,n]
            U[m,:] += -L[m,n]*U[n,:]
    L_inv = np.linalg.inv(L)
    U_inv = np.linalg.inv(U)
    return U_inv @ (L_inv @ b)

如果您想自己实现 LU 分解,this 是一种不需要任何外部库的代码。我把它附加在这里,以防你只是想复制它

def Abs(x):
    '''
    Input x  and you'll get the abs.
    '''
    #Not the best way, but it is an easy way to not use numpy
    return (x**2)**0.5


def ind_max(row,N):
    '''
    Find the index of the maximum of a list (row) of lentgth N.
    '''
    _in=0
    _max=row[0]
    i=0
    while i<N:#the end of the row should be included (convension in how I use LUP..)
        if row[i]>_max:
            _max=row[i]
            _in=i
        i+=1
            
    return _in


def Sum(List,N):
    '''
    Calculates the sum of a List of size N
    '''
    s=0
    for i in range(N):
        s+=List[i]
    return s


def index_swap(A,index_1,index_2):
    '''
        index_swap takes an array and interchanges 
         A[index_1] with A[index_2].
         
         Example:
             A=[0,1,2,3]
             index_swap(A,0,2)
             A
             >>[2, 1, 0, 3]
    '''
    
    tmp=A[index_1]
    A[index_1]=A[index_2]
    A[index_2]=tmp
    
    
def apply_permutations_vector(A,P,N):
    '''
    Applies the permutations given by P from LUP
    to a list A of length N, and returns the result.
    Example:
    A=[1,2,5,8,3]
    P=[2,4,0,3,1]

    apply_permutations_vector(A,P,5)
    >>[5, 3, 1, 8, 2]
    '''
    #that is, you make a list like this (P basically gives you the indices of A):)
    
    Ap=[A[ P[i] ] for i in range(N)]

    return Ap
    
def apply_permutations_matrix(M,P,N):
    '''
    Applies the permutations given by P from LUP
    to a N*N array M of length N, and returns the result.
    
    M=[
    [ 0,  2,  2 , 3 , 5],
    [-3, -1,  1 , 5 , 9],
    [ 1, -1,  1 , 4 , 7],
    [ 1, -1,  1 , 0 , 2],
    [ 1, -1,  1 , 0 , 3]
    ]

    P=[2,0,1,4,3]

    apply_permutations_matrix(M,P,5)
    >>[
      [ 1, -1, 1, 4, 7],
      [ 0,  2, 2, 3, 5],
      [-3, -1, 1, 5, 9],
      [ 1, -1, 1, 0, 3],
      [ 1, -1, 1, 0, 2]
      ]
    '''
    Mp=[ [M[ P[i] ][j]for j in range(N)] for i in range(N) ]
    

    return Mp




def LUP(M,N,_tiny=1e-20):
    U=[  [ M[i][j] for j in range(N)] for i in range(N) ]
    L=[  [ 0 if i!=j else 1 for j in range(N)] for i in range(N) ]
    #this is the "permutation vector". if it is e.g. [2 1 0 3] it means you make 0<->2
    P=[  i for i in range(N) ]
    
    for k in range(1,N):
        for i in range(k,N):
            #find the index of the maximum in column
            _col=[Abs(U[_r][k-1]) for _r in range(k-1,N)]
            
            #find the index of the maximum of _col
            # notice that the length of _col is N-(k-1)
            len_col=N-(k-1)
            pivot=ind_max( _col ,len_col) + k - 1 #convert the index of _col (it has a length of len_col) to the index of  a row of U   
            
            ##################################################
            #if you remove it, then you get a lot of infinities
            #it has to do with the fact that if U[pivot][k-1] <_tiny , then U[k-1][k-1] will be a zero, 
            #L[i][k-1] explodes. 
            #You are allowed to skip this i, then, because if U[pivot][k-1] <_tiny , then all U[i][k-1] are small!
            #Check that this is true by  uncommenting print(_col)
            if Abs(U[pivot][k-1]) < _tiny  :         
                #print(_col)
                break
            ###################################################
            #if the maximum is not at k-1, swap!
            if pivot != k-1 : 
                # Permute rows k-1 and pivot in U
                
                index_swap(P,k-1,pivot)
                
                tmpU=[U[k-1][_r] for _r in range(k-1,N)]
                
                #print(U)
                for _r in range(k-1,N):
                    U[k-1][_r]=U[pivot][_r]
                #print(U)
                for _r in range(k-1,N):
                    U[pivot][_r]=tmpU[_r-(k-1)]#again we have to convert the index of tmpU
                #print(U)
                #print("=========================")
                tmpL=[L[k-1][_r] for _r in range(k-1)]
                #print(L)
                for _r in range(k-1):
                    L[k-1][_r]=L[pivot][_r]
                #print(L)
                for _r in range(k-1):
                    L[pivot][_r]=tmpL[_r]
                    
                #print(L)
                #print("========================")
                
            L[i][k-1]=U[i][k-1]/U[k-1][k-1]
           
        
            for j in range(k-1,N):
                U[i][j]=U[i][j]-L[i][k-1]*U[k-1][j]

                
    return L,U,P