第3.1章 高斯消去法#

注明#

该文档仅仅为测试高斯消去法,如果实际使用的时候,建议请直接使用封装好的库,例如numpy。

除非你的项目就是优化线性方程求解器。

import numpy as np
import time
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt
from numpy import round

高斯消去法的代码#

def gaussian_elimination(A, b, perform_pivot=False, ptwise=True, precision=16):
    
    # A是系数矩阵,b是常数项向量
    n = len(A)
    
    for i in range(n):
        if perform_pivot:
            # 选择列主元并标准化该行
            pivot = np.abs(A[i][i])
            pivot_row = i 
            for row in range(i+1, n):
                if np.abs(A[row,i]) > pivot:
                    pivot = np.abs(A[row,i])
                    pivot_row = row
            
            if pivot_row != i:
                pivot_values = np.copy(A[pivot_row,:])
                A[pivot_row,:] = np.copy(A[i,:])
                A[i,:] = np.copy(pivot_values)
                bi = b[i]
                b[i] = b[pivot_row]
                b[pivot_row] = bi
        
        # 消除下方元素
        for row in range(i+1, n):
            factor = round(A[row,i] / A[i,i], precision)
            if ptwise:
                for col in range(i,n):
                    A[row,col] = round(A[row,col] - factor*A[i,col],precision)
            else:
                A[row,:] = round(A[row,:] - factor * A[i,:], precision)
            b[row] = round(b[row] -  factor * b[i], precision)

    # 回代求解
    x = np.zeros(n)
    x[n-1] = round(b[n-1]/A[n-1,n-1], precision)
    for i in range(n-2, -1, -1):
        # 我们已经简化了这里的有效数字问题
        x[i] = round(round(b[i] - round(np.dot(A[i,(i+1):], x[(i+1):]),precision),precision) / A[i,i], precision)

    return x, A, b

测试讲义里的例子#

A = np.array([[1,1,1],[0,4,-1],[2,-2,1]])*1.0
b = np.array([6,5,1])*1.0

x_exact = np.linalg.solve(A,b)
print("numpy给的解:")
print(x_exact)
print()

x, U, _ = gaussian_elimination(A, b, perform_pivot=False)
print("上面代码的解:")
print(x)
print()

print("消去法得到的上三角矩阵:")
print(U)
numpy给的解:
[1. 2. 3.]

上面代码的解:
[1. 2. 3.]

消去法得到的上三角矩阵:
[[ 1.  1.  1.]
 [ 0.  4. -1.]
 [ 0.  0. -2.]]

测试运行时间和矩阵大小的关系#

# unit test for codes
np.random.seed(1)

n_min = 50
n_max = 100
n_step = 2

n_list = np.array(np.arange(n_min,n_max,n_step))
ε = 1.0e-7

for i in range(len(n_list)):
    n = n_list[i]
    A = np.random.randn(n,n)
    b = np.random.randn(n)
    # 高斯顺序消去法
    x1,_,_ = gaussian_elimination(A.copy(), b.copy(), perform_pivot=False, ptwise=True)
    # 高斯顺序消去法(代码版本2)
    x2,_,_ = gaussian_elimination(A.copy(), b.copy(), perform_pivot=False, ptwise=False)
    # 高斯列主元素消去法
    x3,_,_ = gaussian_elimination(A.copy(), b.copy(), perform_pivot=True, ptwise=False)
    # numpy的解
    x_numpy = np.linalg.solve(A,b)
    assert np.linalg.norm(x1 - x_numpy) < ε
    assert np.linalg.norm(x2 - x_numpy) < ε
    assert np.linalg.norm(x3 - x_numpy) < ε 
n_min = 50
n_max = 100
n_step = 10
repeat = 40
seed = 1

n_list = np.array(np.arange(n_min,n_max,n_step))
# 记录运行的时间
time_list = np.zeros(len(n_list))
time_list_2 = np.zeros(len(n_list)) 
time_numpy = np.zeros(len(n_list))

np.random.seed(seed)

for i in range(len(n_list)):
    n = n_list[i]
    A = np.random.rand(n,n)
    b = np.random.rand(n)
    t = time.time()
    for k in range(repeat):
        x,_,_ = gaussian_elimination(A.copy(), b.copy(), perform_pivot=False, ptwise=True, precision=16)
    time_list[i] = (time.time() - t)/repeat

np.random.seed(seed)

for i in range(len(n_list)):
    n = n_list[i]
    A = np.random.rand(n,n)
    b = np.random.rand(n)
    t = time.time()
    for k in range(repeat):
        x,_,_ =gaussian_elimination(A.copy(), b.copy(), perform_pivot=False, ptwise=False, precision=16)
    time_list_2[i] = (time.time() - t)/repeat

np.random.seed(seed)

for i in range(len(n_list)):
    n = n_list[i]
    A = np.random.rand(n,n)
    b = np.random.rand(n)
    t = time.time()
    for k in range(repeat):
        x = np.linalg.solve(A.copy(), b.copy())
    time_numpy[i] = (time.time() - t)/repeat
# 创建模拟实验的数据集
X = np.log10(n_list).reshape(-1, 1) 
y1 = np.log10(time_list)
y2 = np.log10(time_list_2)
y3 = np.log10(time_numpy)

# 创建线性回归模型实例
model1 = LinearRegression()
model1.fit(X, y1)
model2 = LinearRegression()
model2.fit(X, y2)
model3 = LinearRegression()
model3.fit(X, y3)

# 打印模型的系数
print("高斯消去法(版本1:自己写loop)")
print("Coefficient: {:.3f}\n".format(model1.coef_[0]))

print("高斯消去法(版本2:使用向量运算)")
print("Coefficient: {:.3f}\n".format(model2.coef_[0]))

print("高斯消去法(numpy版本)")
print("Coefficient: {:.3f}\n".format(model3.coef_[0]))

# 进行预测
X_new = np.log10(np.array(np.arange(n_min-3,n_max+3,1))).reshape(-1, 1)
y1_predict = model1.predict(X_new)
y2_predict = model2.predict(X_new)
y3_predict = model3.predict(X_new)
高斯消去法(版本1:自己写loop)
Coefficient: 2.929

高斯消去法(版本2:使用向量运算)
Coefficient: 2.039

高斯消去法(numpy版本)
Coefficient: 1.981
plt.figure(figsize=(4,3))
plt.plot(np.log10(n_list), np.log10(time_list), 'x', label="data (v1)") 
plt.plot(X_new, y1_predict, '-.',label="fit (v1)")

plt.plot(np.log10(n_list), np.log10(time_list_2),'o', label="data (v2)") 
plt.plot(X_new, y2_predict, '-.',label="fit (v2)")

plt.plot(np.log10(n_list), np.log10(time_numpy),'o', label="data (numpy)") 
plt.plot(X_new, y3_predict, '-.',label="fit (numpy)")

plt.legend(loc=(1.05,0.25))
plt.xlabel('log10 (n)')
plt.ylabel('log10 (time)')
plt.show()
_images/23d9c9366dad8c957fbc1c1f97b50a1745512f533cbdef33721f1bbb319ccb8b.png

实验的结论:#

理论上时间和矩阵大小应该呈现 \(\text{运行时间} = \mathcal{O}(n^3)\)(见第一个版本的高斯消去法)

现实中,时间和矩阵大小的关系有可能在一定区间可以比这个好(例如我们优化了for loop)(见第二个版本的高斯消去法)

一般而言,对于一定的参数区间,封装好的库未必满足理论的数量阶,但实际效果会更好(见numpy的版本)

结论:理论和现实的实验存在一定的界限;理论的复杂度一般而言是在\(n\)比较大的时候,才更有可能能被验证成功。

列主元素消去法#

A0 = np.array([[ 0.001, 2.000, 3.000],
               [-1.000, 3.712, 4.623],
               [-2.000, 1.072, 5.643]])
b0 = np.array([1.0, 2.0, 3.0])
x_numpy = np.linalg.solve(A0.copy(),b0.copy())
print(round(x_numpy, 5))
[-0.4904  -0.05104  0.36752]
x2, U2, b2 = gaussian_elimination(A0.copy(), b0.copy(), perform_pivot=True, ptwise=False, precision=4)
print("使用列主元素消元的解为:")
print(x2)
print("误差={:.4f}".format(np.linalg.norm(x2 - x_numpy)))
使用列主元素消元的解为:
[-0.4902 -0.0511  0.3676]
误差=0.0002
x3, U3, b3 = gaussian_elimination(A0.copy(), b0.copy(), perform_pivot=False, ptwise=False, precision=4)
print("不使用行交换的解为:")
print(x3)
print("误差={:.4f}".format(np.linalg.norm(x3 - x_numpy)))
不使用行交换的解为:
[-0.4    -0.0503  0.367 ]
误差=0.0904