Python实战:用NumPy手写高斯消元法解线性方程组(附完整代码)

用高斯(Gauss)消元法求n阶线性方程组的解(python) 用编程语言实现用高斯(Gauss)消元法求n阶线性方程组的解、用列主元高斯(Gauss)消元法求n阶线性方程组的解、用库郎(Courant)列主直接分解法求n阶线性方程组的解的程序。通过设计、编制、调试2~3个求n阶线性方程组数值解的程序,加深对其数值计算方法及有关的基础理论知识的理解。Guass消去法结果。 阅读详情

从零构建:用NumPy实现工业级高斯消元法,攻克线性方程组实战难题

在数据科学和工程计算的日常工作中,线性方程组就像空气一样无处不在。从物理仿真、金融建模到机器学习算法的优化,我们总在和各种形式的 Ax = b 打交道。很多朋友一遇到这类问题,第一反应就是调用 numpy.linalg.solve 或者 scipy.linalg.solve,这当然没问题。但你是否想过,当库函数返回一个“奇异矩阵”错误时,背后到底发生了什么?或者当矩阵规模巨大、条件数很差时,如何确保解的稳定性和精度?

理解高斯消元法,就是理解这一切的基石。它不仅是线性代数教材里的经典算法,更是众多高级分解方法(如LU、Cholesky分解)的灵魂。今天,我们不满足于理论推导,而是要亲手用NumPy实现一个鲁棒、实用的高斯消元求解器。我会带你穿越从基础实现到处理边界情况的完整路径,让你不仅“知其然”,更能“知其所以然”,在未来的项目中拥有自主调试和优化的能力。

这篇文章面向的是已经熟悉Python和NumPy基础,希望深入算法底层、提升工程实践能力的数据科学家和工程师。我们将采用Jupyter Notebook风格的交互式代码演示,确保每一步都可执行、可验证。

1. 高斯消元法:核心思想与算法骨架

高斯消元法的目标非常直接:将一个普通的系数矩阵 A,通过一系列行变换,转化为上三角矩阵 U。一旦得到上三角矩阵,我们就可以通过“回代”这个简单的过程,从最后一个方程开始,逐个解出所有未知数。

这个过程的本质,是用矩阵的行初等变换,保持方程组的等价性。具体来说,我们只允许三种操作:

  1. 交换两行的位置(行交换)。
  2. 将某一行乘以一个非零常数(行缩放)。
  3. 将一行的倍数加到另一行上(行倍加)。

我们主要利用第三种操作。想象一下,对于一个 n x n 的矩阵,消元过程会进行 n-1 步。在第 k 步,我们的目标是利用第 k 行,将第 k 列中第 k+1 行到第 n 行的元素全部变为零。

算法的核心伪代码可以概括如下:

对于 k 从 0 到 n-2:
    对于 i 从 k+1 到 n-1:
        计算乘子 factor = A[i, k] / A[k, k]
        对于 j 从 k 到 n-1:
            A[i, j] = A[i, j] - factor * A[k, j]
        b[i] = b[i] - factor * b[k]

这个过程结束后,矩阵 A 就变成了上三角矩阵。紧接着是回代:

创建解向量 x,长度为 n
对于 i 从 n-1 到 0 (倒序):
    sum = b[i]
    对于 j 从 i+1 到 n-1:
        sum = sum - A[i, j] * x[j]
    x[i] = sum / A[i, i]

听起来很简单,对吧?但如果我们直接把上面的伪代码翻译成Python,会遇到几个致命的陷阱:

  1. 除零错误:当主对角线元素 A[k, k] 为零时,计算 factor 会导致程序崩溃。
  2. 数值不稳定:即使 A[k, k] 不为零,但如果它非常接近于零,作为除数会导致 factor 极大,放大舍入误差,最终得到完全错误的解。
  3. 效率问题:三重循环的朴素实现,在 n 较大时速度很慢。

我们先给出一个最基础的、不考虑任何问题的“教学版本”实现,感受一下流程。

import numpy as np

def gaussian_elimination_naive(A, b):
    """
    基础版高斯消元法(无主元选择,不稳定,仅用于演示)
    参数:
        A: numpy.ndarray, 形状为 (n, n) 的系数矩阵
        b: numpy.ndarray, 形状为 (n,) 的常数向量
    返回:
        x: numpy.ndarray, 解向量
    """
    # 确保是浮点数类型,避免整数除法问题
    A = A.astype(float)
    b = b.astype(float)
    n = len(A)

    # 消元过程
    for k in range(n-1):
        for i in range(k+1, n):
            factor = A[i, k] / A[k, k]
            A[i, k:] = A[i, k:] - factor * A[k, k:]
            b[i] = b[i] - factor * b[k]

    # 回代过程
    x = np.zeros(n)
    for i in range(n-1, -1, -1):
        x[i] = (b[i] - np.dot(A[i, i+1:], x[i+1:])) / A[i, i]
    return x

# 测试一个简单的例子
A_test = np.array([[2., 1., -1.],
                   [-3., -1., 2.],
                   [-2., 1., 2.]])
b_test = np.array([8., -11., -3.])

x_naive = gaussian_elimination_naive(A_test.copy(), b_test.copy())
print("基础版求解结果:", x_naive)
print("验证 Ax - b:", np.dot(A_test, x_naive) - b_test)

运行这段代码,对于这个精心挑选的例子,你会得到正确的解 [2., 3., -1.]。但请不要高兴太早,这个版本脆弱得不堪一击。我们马上就会看到它的问题。

2. 核心挑战与实战技巧:主元选择与精度控制

上面“教学版本”失败的核心在于它盲目地将每行的第一个非零元素(即当前列的对角线元素 A[k, k])作为除数。这引出了数值计算中一个至关重要的概念:主元(Pivot)。主元的选择直接决定了算法的稳定性和成败。

2.1 为什么需要选主元?

考虑下面这个例子:

A_bad = np.array([[1e-15, 1.],
                   [1., 1.]])
b_bad = np.array([1., 2.])

这个方程组的精确解大约是 x1 = 1.0, x2 = 1.0。但如果我们用基础版算法,第一步计算 factor = A[1,0] / A[0,0] = 1.0 / 1e-15 = 1e15。这个巨大的乘子会使得 A[1,1] 在计算 A[1,1] - factor * A[0,1] 时,发生严重的有效数字丢失(Catastrophic Cancellation),因为 1 - 1e15 * 1 在浮点数运算中,1 的信息完全被吞没了。结果会溢出或产生极大的误差。

注意:在浮点数运算中,当两个数量级相差巨大的数相加减时,较小数的精度会完全丢失。这是数值误差的主要来源之一。

解决方案就是选主元:在每一步消元之前,从当前列(第 k 列)的第 k 行到第 n 行中,选取绝对值最大的元素作为主元。然后,通过行交换,将这个最大元素放到 A[k, k] 的位置上。这种方法被称为部分选主元(Partial Pivoting)。

为什么选最大值?因为作为除数的绝对值越大,计算出的乘子 factor 的绝对值就越小(≤1),从而可以最大限度地减少后续计算中舍入误差的放大效应。

2.2 实现带部分选主元的高斯消元

我们需要在消元循环中增加选主元的逻辑。同时,为了后续可能需要的行交换信息(这在LU分解中很有用),我们引入一个记录行交换顺序的数组 pivot。

def gaussian_elimination_partial_pivoting(A, b):
    """
    带部分选主元的高斯消元法
    参数:
        A: numpy.ndarray, 形状为 (n, n) 的系数矩阵
        b: numpy.ndarray, 形状为 (n,) 的常数向量
    返回:
        x: numpy.ndarray, 解向量
        L: numpy.ndarray, 单位下三角矩阵(用于理解,非必需返回)
        U: numpy.ndarray, 上三角矩阵
        pivot: list, 行交换记录
    """
    A = A.astype(float).copy()
    b = b.astype(float).copy()
    n = len(A)
    pivot = list(range(n))  # 初始行顺序

    # 消元过程(含选主元)
    for k in range(n-1):
        # --- 部分选主元 ---
        # 找到第k列中,从第k行开始绝对值最大的行索引
        max_row_index = np.argmax(np.abs(A[k:, k])) + k
        if max_row_index != k:
            # 交换矩阵A的行
            A[[k, max_row_index], :] = A[[max_row_index, k], :]
            # 交换向量b的元素
            b[[k, max_row_index]] = b[[max_row_index], k]
            # 记录行交换
            pivot[k], pivot[max_row_index] = pivot[max_row_index], pivot[k]

        # 检查主元是否为零(选主元后仍可能为零,意味着矩阵奇异)
        if np.abs(A[k, k]) < 1e-15:  # 使用一个很小的容差
            raise ValueError(f"矩阵在消元第{k}步后主元接近零,矩阵可能奇异。")

        # --- 消元 ---
        for i in range(k+1, n):
            factor = A[i, k] / A[k, k]
            # 使用向量化操作提高效率,只计算k列之后的元素
            A[i, k+1:] = A[i, k+1:] - factor * A[k, k+1:]
            # 同时处理b向量
            b[i] = b[i] - factor * b[k]
            # 将乘子存储在已消元的位置,便于后续构建L矩阵(可选)
            A[i, k] = factor

    # 此时A已成为上三角矩阵U(对角线以下存储了L的乘子)
    U = np.triu(A)  # 取上三角部分,包括对角线
    # 构建单位下三角矩阵L(对角线为1,下三角为消元乘子)
    L = np.eye(n) + np.tril(A, -1)

    # 回代求解
    x = np.zeros(n)
    for i in range(n-1, -1, -1):
        x[i] = (b[i] - np.dot(U[i, i+1:], x[i+1:])) / U[i, i]

    return x, L, U, pivot

# 测试之前的“坏”矩阵
try:
    x_pp, L_pp, U_pp, p_pp = gaussian_elimination_partial_pivoting(A_bad.copy(), b_bad.copy())
    print("部分选主元求解结果:", x_pp)
    print("验证 Ax - b:", np.dot(A_bad, x_pp) - b_bad)
except ValueError as e:
    print(f"求解失败: {e}")

通过选主元,算法会先交换两行,用 1.0 作为主元,从而避免了巨大乘子的产生,得到了稳定的解。

2.3 精度控制与条件数

即使用了选主元,有些问题本质上是“病态”的。这由矩阵的条件数(Condition Number) 决定。条件数 cond(A) 衡量了输出值(解 x)对输入值(A 和 b)微小变化的敏感程度。条件数越大,问题越病态,数值解越不可靠。

我们可以用NumPy快速估算条件数:

cond_number = np.linalg.cond(A_test)
print(f"矩阵A的条件数: {cond_number:.2e}")

对于条件数很大的矩阵(比如大于 1e10),即使使用最稳定的算法,得到的解也可能与真实解相差甚远。在实际工程中,如果遇到条件数过大的情况,我们需要:

  1. 重新审视问题:检查物理或数学模型是否合理,量纲是否统一。
  2. 使用更高精度:使用 np.float128(如果系统支持)或 decimal 库。
  3. 采用正则化方法:例如在机器学习中,通过添加L2正则项(岭回归)来改善矩阵的条件数。

下表对比了不同版本高斯消元法的特性:

特性基础版 (无主元)部分选主元版完全选主元版
稳定性极差,易因小主元失败良好,适用于绝大多数情况最优,但计算开销大
计算复杂度O(n³)O(n³),增加选主元比较O(n³),增加行列搜索
实现难度简单中等复杂
适用场景仅教学演示通用科学计算、工程应用极端病态问题、符号计算

提示:完全选主元会在当前右下子矩阵中搜索绝对值最大的元素,并进行行和列的交换。这更稳定,但破坏了矩阵的稀疏结构,且需要跟踪列交换以调整解的顺序,实现复杂。部分选主元在实践中的表现通常足够好。

3. 从高斯消元到LU分解:算法的深度解析

细心的你可能已经注意到,在我们带部分选主元的实现中,消元后的矩阵 A 其实包含了两个信息:上三角部分 U 和下三角部分(对角线以下)的乘子。这正是LU分解的雏形。

高斯消元法本质上在不进行行交换的情况下,隐式地完成了 A = LU 的分解,其中 L 是单位下三角矩阵(对角线为1,下三角是消元乘子),U 是上三角矩阵。当引入行交换后,分解关系变为 PA = LU,其中 P 是一个排列矩阵,代表我们的行交换操作。

理解这种等价性有巨大的实用价值:

  1. 高效求解多组方程:当需要反复求解 Ax = b₁, Ax = b₂, ... 时,我们只需做一次 LU 分解(O(n³)),然后对每个不同的 b 执行两次三角矩阵的回代(O(n²)),成本远低于每次都做完整消元。
  2. 求逆矩阵:矩阵的逆可以通过求解 AX = I 得到,其中 X 的每一列就是逆矩阵的一列。利用 LU 分解可以高效完成。
  3. 计算行列式:det(A) = det(P⁻¹) * det(L) * det(U) = (±1) * 1 * (∏ U[i,i])。符号由行交换次数(排列矩阵的奇偶性)决定。

让我们修改之前的函数,使其显式返回 P, L, U,并演示如何用它们来高效求解。

def lu_decomposition_partial_pivoting(A):
    """
    对矩阵A进行带部分选主元的LU分解 (PA = LU)
    参数:
        A: numpy.ndarray, 形状为 (n, n) 的系数矩阵
    返回:
        P: numpy.ndarray, 排列矩阵(或交换索引)
        L: numpy.ndarray, 单位下三角矩阵
        U: numpy.ndarray, 上三角矩阵
    """
    A = A.astype(float).copy()
    n = len(A)
    L = np.eye(n)
    P = np.eye(n)  # 排列矩阵初始化
    pivot_history = list(range(n))

    for k in range(n-1):
        # 选主元
        max_idx = np.argmax(np.abs(A[k:, k])) + k
        if max_idx != k:
            # 交换A的行
            A[[k, max_idx], :] = A[[max_idx, k], :]
            # 交换P的行(记录累积的排列)
            P[[k, max_idx], :] = P[[max_idx, k], :]
            # 交换L中已计算的部分(关键!)
            if k > 0:
                L[[k, max_idx], :k] = L[[max_idx, k], :k]
            pivot_history[k], pivot_history[max_idx] = pivot_history[max_idx], pivot_history[k]

        if np.abs(A[k, k]) < 1e-15:
            raise ValueError("矩阵奇异或近似奇异,无法进行LU分解。")

        # 计算乘子并消元
        for i in range(k+1, n):
            L[i, k] = factor = A[i, k] / A[k, k]
            A[i, k:] = A[i, k:] - factor * A[k, k:]

    U = np.triu(A)
    return P, L, U

def solve_via_lu(P, L, U, b):
    """
    利用LU分解结果PA=LU求解Ax=b
    步骤: 1. 解 L y = P b   (前向替换)
         2. 解 U x = y     (回代)
    """
    n = len(b)
    # 步骤1: 前向替换解 Ly = Pb
    y = np.zeros(n)
    Pb = np.dot(P, b)  # 应用排列矩阵

    for i in range(n):
        y[i] = Pb[i] - np.dot(L[i, :i], y[:i])
        # L是单位下三角,L[i,i]=1,所以不需要除法

    # 步骤2: 回代解 Ux = y
    x = np.zeros(n)
    for i in range(n-1, -1, -1):
        x[i] = (y[i] - np.dot(U[i, i+1:], x[i+1:])) / U[i, i]
    return x

# 演示LU分解的用法
A_lu = np.array([[3., 17., 10.],
                 [2., 4., -2.],
                 [6., 18., -12.]])
b_lu1 = np.array([1., 2., 3.])
b_lu2 = np.array([-1., 0., 1.])

P, L, U = lu_decomposition_partial_pivoting(A_lu.copy())
print("排列矩阵 P:\n", P)
print("下三角矩阵 L:\n", L)
print("上三角矩阵 U:\n", U)
print("验证 PA - LU (应接近零矩阵):\n", np.dot(P, A_lu) - np.dot(L, U))

# 求解第一个方程组
x1 = solve_via_lu(P, L, U, b_lu1)
print("\n解 x1:", x1)
print("验证 A*x1 - b1:", np.dot(A_lu, x1) - b_lu1)

# 求解第二个方程组,复用LU分解结果!
x2 = solve_via_lu(P, L, U, b_lu2)
print("\n解 x2:", x2)
print("验证 A*x2 - b2:", np.dot(A_lu, x2) - b_lu2)

可以看到,在得到 P, L, U 后,求解新的 b 向量成本极低。这是工业级线性系统求解库(如Intel MKL, OpenBLAS)背后的核心思想之一。

4. 工程实践:处理奇异矩阵、性能优化与完整代码

4.1 检测与处理奇异或病态矩阵

在实际应用中,我们遇到的矩阵不总是可逆的。如何优雅地处理奇异或接近奇异的矩阵?

  1. 秩亏检测:在消元过程中,如果选主元后找到的主元绝对值仍然小于一个设定的阈值(例如 1e-12),我们可以认为矩阵是数值奇异的。
  2. 条件数预警:计算矩阵的条件数(虽然成本较高),如果超过某个阈值(如 1e12),则给出警告,提示用户结果可能不可信。
  3. 提供替代方案:对于欠定方程组(无穷多解)或超定方程组(最小二乘解),可以提示用户使用 np.linalg.lstsq 或 np.linalg.pinv 来求伪逆解。

下面是一个增强版的求解器,包含了更健壮的异常处理:

def robust_linear_solver(A, b, tol=1e-12, warn_cond=1e10):
    """
    鲁棒的线性方程组求解器,集成了高斯消元/LU分解与异常处理。
    参数:
        A: 系数矩阵
        b: 常数向量
        tol: 判断主元为零的容差
        warn_cond: 条件数警告阈值
    返回:
        x: 解向量或最小二乘解
        info: 字典,包含状态、条件数等信息
    """
    A = np.array(A, dtype=float, copy=True)
    b = np.array(b, dtype=float, copy=True).flatten()
    n = A.shape[0]

    # 快速形状检查
    if A.shape[0] != A.shape[1]:
        raise ValueError(f"系数矩阵A必须是方阵,当前形状为 {A.shape}")
    if A.shape[0] != b.shape[0]:
        raise ValueError(f"A的行数({A.shape[0]})必须等于b的长度({b.shape[0]})")

    # 可选:检查条件数(对于大矩阵可能较慢)
    try:
        cond_A = np.linalg.cond(A)
        if cond_A > warn_cond:
            print(f"警告:矩阵条件数较大 ({cond_A:.2e}),解可能不准确。")
    except np.linalg.LinAlgError:
        cond_A = np.inf
        print("警告:无法计算矩阵条件数。")

    pivot = list(range(n))
    # 消元过程
    for k in range(n-1):
        # 部分选主元
        max_idx = k + np.argmax(np.abs(A[k:, k]))
        if max_idx != k:
            A[[k, max_idx], :] = A[[max_idx, k], :]
            b[[k, max_idx]] = b[[max_idx], k]
            pivot[k], pivot[max_idx] = pivot[max_idx], pivot[k]

        # 检查主元
        if np.abs(A[k, k]) < tol:
            # 主元过小,尝试寻找非零列?或者判定为秩亏
            # 这里简单抛出异常,更复杂的实现可以尝试继续消元或返回最小二乘解
            raise np.linalg.LinAlgError(f"在步骤 {k} 检测到数值奇异性(主元 < {tol})。矩阵可能秩亏。")

        # 消元
        for i in range(k+1, n):
            factor = A[i, k] / A[k, k]
            A[i, k+1:] = A[i, k+1:] - factor * A[k, k+1:]
            b[i] = b[i] - factor * b[k]
            A[i, k] = factor  # 存储乘子

    # 检查最后一个主元
    if np.abs(A[-1, -1]) < tol:
        raise np.linalg.LinAlgError("矩阵数值奇异,无法求解唯一解。")

    # 回代
    x = np.zeros(n)
    for i in range(n-1, -1, -1):
        x[i] = (b[i] - np.dot(A[i, i+1:], x[i+1:])) / A[i, i]

    info = {
        'success': True,
        'condition_number': cond_A,
        'pivot_history': pivot
    }
    return x, info

# 测试奇异矩阵
A_singular = np.array([[1., 2., 3.],
                       [4., 5., 6.],
                       [7., 8., 9.]])  # 第三行是第一行和第二行的线性组合,行列式为0
b_singular = np.array([1., 2., 3.])

try:
    x_sol, info_sol = robust_linear_solver(A_singular, b_singular)
    print("奇异矩阵求解结果:", x_sol)
except np.linalg.LinAlgError as e:
    print(f"求解失败,原因: {e}")
    # 在实际应用中,这里可以fallback到求最小二乘解
    x_pseudo, residuals, rank, s = np.linalg.lstsq(A_singular, b_singular, rcond=None)
    print(f"退而求其次,最小二乘解为: {x_pseudo}")

4.2 性能优化与向量化

我们之前的循环实现清晰易懂,但在处理大规模矩阵时效率不高。NumPy的威力在于向量化操作。我们可以将内层循环替换为向量化操作,显著提升性能。

def gaussian_elimination_vectorized(A, b):
    """使用更多向量化操作的高斯消元法"""
    n = len(A)
    A = A.astype(float).copy()
    b = b.astype(float).copy().reshape(-1, 1)  # 确保b是列向量

    for k in range(n-1):
        # 选主元
        max_idx = k + np.argmax(np.abs(A[k:, k]))
        if max_idx != k:
            A[[k, max_idx], :] = A[[max_idx, k], :]
            b[[k, max_idx], :] = b[[max_idx, k], :]

        if np.abs(A[k, k]) < 1e-15:
            raise np.linalg.LinAlgError("矩阵奇异")

        # 向量化消元:一次操作多行
        i = k+1
        factors = A[i:, k:k+1] / A[k, k]  # 保持二维形状,便于广播
        A[i:, k+1:] = A[i:, k+1:] - factors * A[k, k+1:]
        b[i:, :] = b[i:, :] - factors * b[k, :]
        # 可选:存储乘子
        A[i:, k:k+1] = factors

    # 向量化回代 (从下往上)
    x = np.zeros((n, 1))
    for i in range(n-1, -1, -1):
        x[i] = (b[i] - np.dot(A[i, i+1:], x[i+1:])) / A[i, i]
    return x.flatten()

对于追求极致性能的场景,我们可以使用SciPy的scipy.linalg.lu_factor和scipy.linalg.lu_solve,它们底层由高度优化的Fortran或C库(如LAPACK)实现。但自己实现一遍的意义在于,当出现问题时,你知道该从哪里入手调试。

4.3 完整、可复用的工业级代码示例

最后,我将提供一个整合了所有最佳实践的完整函数:部分选主元、LU分解格式输出、基本的异常处理以及清晰的文档。你可以直接将它用于你的项目或作为进一步开发的起点。

import numpy as np

def solve_linear_system(A, b, return_lu=False, pivot_tol=1e-12):
    """
    使用带部分选主元的高斯消元法/LU分解求解线性方程组 Ax = b。

    参数
    ----------
    A : array_like, shape (n, n)
        系数矩阵。
    b : array_like, shape (n,) or (n, 1)
        常数向量。
    return_lu : bool, optional
        如果为True,则额外返回P, L, U矩阵。默认为False。
    pivot_tol : float, optional
        判断主元是否为0的容差。默认为1e-12。

    返回
    -------
    x : ndarray, shape (n,)
        方程组的解向量。
    (P, L, U) : tuple of ndarrays (仅在 return_lu=True 时返回)
        P: 排列矩阵,满足 P A = L U
        L: 单位下三角矩阵
        U: 上三角矩阵

    异常
    ------
    LinAlgError
        如果矩阵A是奇异的,或者形状不匹配。

    示例
    --------
    >>> A = [[2, 1, -1], [-3, -1, 2], [-2, 1, 2]]
    >>> b = [8, -11, -3]
    >>> x = solve_linear_system(A, b)
    >>> print(x)
    [ 2.  3. -1.]
    """
    A = np.asarray(A, dtype=np.float64)
    b = np.asarray(b, dtype=np.float64).flatten()

    n, m = A.shape
    if n != m:
        raise np.linalg.LinAlgError(f"矩阵A必须是方阵,当前形状为 ({n}, {m})")
    if n != b.size:
        raise np.linalg.LinAlgError(f"A的行数 ({n}) 必须等于b的长度 ({b.size})")

    # 初始化
    A_work = A.copy()
    b_work = b.copy()
    P = np.eye(n, dtype=np.float64)  # 排列矩阵
    pivot_idx = np.arange(n)         # 行交换记录

    # 高斯消元(LU分解)主循环
    for k in range(n - 1):
        # 1. 部分选主元
        max_row = k + np.argmax(np.abs(A_work[k:, k]))
        if max_row != k:
            # 交换行
            A_work[[k, max_row], :] = A_work[[max_row, k], :]
            b_work[[k, max_row]] = b_work[[max_row], k]
            P[[k, max_row], :] = P[[max_row, k], :]
            pivot_idx[[k, max_row]] = pivot_idx[[max_row, k]]

        # 2. 检查主元
        pivot = A_work[k, k]
        if np.abs(pivot) < pivot_tol:
            raise np.linalg.LinAlgError(
                f"在消元步 {k} 检测到数值奇异性。主元值为 {pivot:.2e},小于容差 {pivot_tol}。"
            )

        # 3. 计算乘子并消元(向量化)
        i = k + 1
        multipliers = A_work[i:, k] / pivot
        A_work[i:, k+1:] = A_work[i:, k+1:] - multipliers[:, None] * A_work[k, k+1:]
        b_work[i:] = b_work[i:] - multipliers * b_work[k]
        # 将乘子存储在下三角部分
        A_work[i:, k] = multipliers

    # 最终主元检查
    if np.abs(A_work[-1, -1]) < pivot_tol:
        raise np.linalg.LinAlgError("矩阵数值奇异,无法求解。")

    # 从处理后的A_work中提取L和U
    L = np.eye(n)
    L[np.tril_indices(n, -1)] = A_work[np.tril_indices(n, -1)]
    U = np.triu(A_work)

    # 回代求解
    x = np.zeros(n)
    for i in range(n - 1, -1, -1):
        x[i] = (b_work[i] - np.dot(U[i, i+1:], x[i+1:])) / U[i, i]

    if return_lu:
        return x, (P, L, U)
    else:
        return x

# =============== 演示与验证 ===============
if __name__ == "__main__":
    print("=== 测试1: 标准可逆矩阵 ===")
    A1 = np.array([[4, 3, 2],
                   [3, 2, 1],
                   [2, 1, 1]], dtype=float)
    b1 = np.array([10, 7, 5], dtype=float)
    x1 = solve_linear_system(A1, b1)
    print(f"解 x = {x1}")
    print(f"残差范数 ||Ax - b|| = {np.linalg.norm(A1 @ x1 - b1):.2e}")
    print()

    print("=== 测试2: 需要行交换的矩阵 ===")
    A2 = np.array([[0, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]], dtype=float)
    b2 = np.array([1, 2, 3], dtype=float)
    x2, (P2, L2, U2) = solve_linear_system(A2, b2, return_lu=True)
    print(f"解 x = {x2}")
    print(f"验证 PA - LU 范数: {np.linalg.norm(P2 @ A2 - L2 @ U2):.2e}")
    print()

    print("=== 测试3: 接近奇异的矩阵(应给出警告或失败)===")
    A3 = np.array([[1, 2],
                   [1, 2.00000001]], dtype=float)  # 两行几乎线性相关
    b3 = np.array([3, 3.00000001], dtype=float)
    try:
        x3 = solve_linear_system(A3, b3, pivot_tol=1e-8)
        print(f"解 x = {x3}")
        print(f"残差范数: {np.linalg.norm(A3 @ x3 - b3):.2e}")
        print("注意:由于矩阵接近奇异,解对误差极其敏感。")
    except np.linalg.LinAlgError as e:
        print(f"求解器正确捕获异常: {e}")
        # 使用最小二乘作为后备
        x3_lstsq, *_ = np.linalg.lstsq(A3, b3, rcond=None)
        print(f"最小二乘解: {x3_lstsq}")

这个 solve_linear_system 函数是一个实用的起点。它处理了形状检查、选主元、奇异性检测,并提供了返回LU分解结果的选项。在实际项目中,你可能还需要根据具体情况添加日志、更复杂的误差估计、或者与迭代法结合来处理稀疏矩阵。

自己动手实现核心算法,能让你在调用 np.linalg.solve 时更加自信,因为你知道黑盒里大概发生了什么。当遇到数值问题时,你也有了深入排查的工具和思路。高斯消元法这座桥梁,连接着线性代数的理论世界和工程计算的现实需求,掌握它,是迈向更高级数值计算领域坚实的一步。

高斯消元法python实现 高斯消元法的python实现 阅读详情

相关推荐

高斯消元法求解线性方程组(附python代码)

输入:a是m×n的系数矩阵,b是m×1的(列)向量。 输出:方程组的通解。用高斯消元法(行化简法)解线性方程组步骤 1.构造方程组的增广矩阵 2.从最左边列往右,使用行化简算法把增广矩阵化为阶梯形,确定矩阵是否有解: 若最后一列为主元列(最后一行非零行形如 [0 0 0 5]),无解,返回无解。 3.继续行化简,把主元上面的所有的元素都化为0,把主元位置变成1. 4.把每个主元列对应的变量表示

大学之道,在明明德 1万+

python高斯消元法

防止对角线元素为0的情况。

qq_33599874的博客 1017

线性方程组求解(高斯消元法、LU三角分解法)

一、实验内容二、代码(python)import numpy as np''' 列主元高斯消元法 A:系数增广矩阵 n:未知数个数 ''' def main_element_gauss(A,n): for i in range(0,n-1): if(np.max(A[i:,i])!=A[i,i]): #如果当前系数不是最大值,则列主元

Solitarily的博客 6581

利用numpy,math实现高斯消元法

从头开始索引,进行减去,如果遇见本行,则跳过本次循环。print('-'*10,f'{i}起{j}算')# todo(cxs)判断是否有解,无解直接退出,有解接着进行。#如果这一行全是0,则跳过本次循环。:param array_bool:bool数组。:param i:使用行,每列首元列索引。#通过bool索引,拿到第一个非0的。#首元不为0,就开始进行消元。:return: 全部进行析取,返回结果。:param j: 消元行。:param b: 常数项矩阵。:param a:系数矩阵。

m0_73016766的博客 685

Python解线性方程组的直接法(1)————高斯消元法

高斯消元法 import numpy as np def fgauss(A, b): n = A.shape[0] zengguan = np.hstack((A, b.T)) ra = np.linalg.matrix_rank(A) rz = np.linalg.matrix_rank(zengguan) temp1 = rz - ra if temp1 > 0: print("无一般意义下的解,系数矩阵与增广矩阵的秩不同")

leetteel 1805

用Python实现高斯消元法

高斯消元法是一种线性方程组求解的经典方法。在许多数学和工程应用中都有广泛的应用,比如矩阵求逆、矩阵行列式计算等。Python作为一门强大的编程语言,自然也能够用来实现高斯消元法。总之,高斯消元法是一个强大的线性方程组求解方法,它可以有效地解决许多实际问题。在Python中,通过使用NumPy库,我们可以轻松地实现高斯消元法。在Python中,我们可以使用NumPy库来进行矩阵操作。用Python实现高斯消元法。返回方程组的解向量。

TechSavant的博客 847

高斯消元法解线性方程组数学原理及Python手工实现

高斯消元法解线性方程组数学原理及Python手工实现 先逐次消去未知数,将方程化为同解的上三角形方程组,此过程称为消元。然后按方程相反顺序求解上三角形方程组,得到原方程组的解,此过程称为回代。这种方法称为高斯消元法。

杨老师的小跟班(*∩_∩*) 2931

Python解线性方程组的直接法(5)————平方根法求解线性方程组

基于吉文斯变换的QR分解

leetteel 2507

python实现高斯消元法求线性方程组的解

高斯消元法简介   数学上,高斯消元法(或译:高斯消去法),是线性代数规划中的一个算法,可用来为线性方程组求解。但其算法十分复杂,不常用于加减消元法,求出矩阵的秩,以及求出可逆方阵的逆矩阵。不过,如果有过百万条等式时,这个算法会十分省时。一些极大的方程组通常会用迭代法以及花式消元来解决。当用于一个矩阵时,高斯消元法会产生出一个“行梯阵式”。高斯消元法可以用在电脑中来解决数千条等式及未知数。亦有一些...

weixin_44410742的博客 9650

高斯消元法(python)解线性方程组

高斯消元法:1.消元:通过初等行变换一步步把系数矩阵化为一个上三角矩阵,2.回代:一个个解出xn···x2, x1 一般性解法: 设Ax=b,A \in R^{n\times n},若A的所有顺序主子式均不为零,则基本高斯消元无需换行进行到底,得到唯一解,其消元和回代的计算公式为: ...

MaYuHuaw的博客 1460

Python解线性方程组的直接法(6)————求解三对角方程组的追赶法

基于豪斯霍尔德变换的QR分解

leetteel 2733

解锁线性代数之门:用Python秒解任何线性方程组

解锁线性代数之门:用Python秒解任何线性方程组

lalalamavis的博客 2421

python实现高斯列组元消元

python实现高斯列组元消元

python——高斯消元法解线性方程组

方程组如下: 求解上述方程,我们可以使用高斯消元法进行求解。 import numpy as np def f(x,n):#高斯消元法 jie=np.zeros(n*(n-1)).reshape(n-1,n);t=1;xj=np.ones(n-1)#定义参数 jie1=np.zeros(n*(n-1)).reshape(n-1,n);jie1[:,:]=x[:,:];#这一步很重要,要不然会出现超大的BUG,耗时巨久。 for i in range(n-1):#正迭..

Thattear的博客 3166

FoundationPose实战:从零部署到Demo运行全记录

本文详细记录了FoundationPose从零部署到Demo运行的全过程,包括Ubuntu环境配置、项目部署、依赖安装及常见问题解决方案。特别针对复现过程中的关键步骤如CUDA版本选择、Eigen3安装等提供了实用技巧,帮助开发者高效完成FoundationPose的部署与调试。

weixin_27102543的博客 269

python毕设 基于Web的中药材信息查询系统程序+论文

***(程序+源码+数据库+调试部署+开发环境)论文文档1,文末可获取,系统界面在最后面。****

m0_74825541的博客 1257

【算法 | Python】高斯消元法

程序来源:Gaussian Elimination Arithmetic Analysis原理说明源代码代码说明 原理说明 高斯消元法(Gauss Elimination)【超详解&模板】 高斯消元法-百度百科 源代码 """ Gaussian elimination method for solving a system of linear equations. Gaussian elimination - https://en.wikipedia.org/wiki/Gaussian_.

MCP的博客 2387

Visual Tracker Benchamark傻瓜式教程(二)

问题  1 1  在调用main.m跑TRE和SRE时,因为会生成相应的.mat数据文件,如果跑完以后发现自己算法生成的.mat文件有问题,那么要把该文件删除,待找到错误原因后,重新生成相应的.mat文件。因为程序中写的是如果已经存在了.mat文件,例如已经存在car4_STC.mat文件,即使该文件中的数据有错误,但是重新跑时会默认该文件中的数据写完,不会重新写入数据 问题  2  b

mingbujingren的专栏 9928

使用python求解线性方程组(numpy,linalg.solve)

求解线性方程组

m0_74045818的博客 2899

QGroundControl实现多机控制

前言 作为西安电子科技大学的本科生一名,目前在研究关于无人机导航规划与轨迹控制方面。 目前在跟导师做关于雷达无人机的一个项目,在这里记录一下关于QGC地面站实现多机控制的一些方法。 某些地方写的可能不是很完善,欢迎大家补充。 目前该文章还未写完,后期本人会继续编写 多机控制原理 多机地面站支持TCP,UDP和串口等三种连接方式,首先我们需要对这两种连接方式抽象出一个基类LinkInterface,提供两个虚函数分别对应于数据接受和发送处理,针对于不同的连接方式实现TCP,UDP和串口连接相对应的子类TCPL

Phillweston的博客 8432

TIA博途中S7-1200进行MODBUS TCP通信(作为服务器)的具体方法演示

TIA博途中S7-1200进行MODBUS TCP通信(作为服务器)的具体方法演示

Robot_PLC_HMI_Automation 8617

【python3】【计算方法】高斯消元法求解线性方程组 [a]{x} = b

使用高斯消元法求解线性方程组[a]{x} = b,其中[a]是一个 Vandermonde 矩阵,{x}是未知向量,b是常向量。

hmywillstronger的博客 692

2026年经典爬虫案例专栏|第17篇:学术论文爬虫实战——知网与万方数据采集

字段名说明数据类型title论文标题字符串authors作者列表列表abstract摘要字符串keywords关键词列表列表journal发表期刊字符串year发表年份整数volume卷号字符串issue期号字符串pages页码范围字符串citations引用次数整数downloads下载次数整数doiDOI编号字符串url论文链接字符串学术论文爬虫是科研工作的重要辅助工具,可以帮助研究人员高效地收集和管理论文数据。

2201_76125261的博客 1064
上一篇: SAP采购申请转订单全流程解析:从Z01凭证类型到Y01的字段控制技巧
下一篇: Spring Boot项目中Lombok与ECJ编译器的兼容性问题排查与解决方案
study
博客等级 码龄27年 67粉丝 1642原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值