第15章:数值线性代数基础

Haiyue
32min

第15章:数值线性代数基础

学习目标
  • 理解数值计算中的误差问题
  • 掌握矩阵分解的数值方法
  • 学习迭代法求解线性方程组
  • 理解条件数和稳定性
  • 掌握特征值的数值计算方法

数值计算中的误差与稳定性

浮点数表示与舍入误差

计算机中的实数表示存在精度限制,导致舍入误差的累积。

条件数

矩阵的条件数衡量了线性系统对输入扰动的敏感性: κ(A)=AA1\kappa(A) = \|A\| \|A^{-1}\|

import numpy as np
import matplotlib.pyplot as plt
from scipy.linalg import norm, solve, lu, qr, svd
from scipy.sparse import diags
import time
import warnings
warnings.filterwarnings('ignore')

def numerical_stability_demo():
    """数值稳定性和误差分析演示"""

    print("数值线性代数:稳定性和误差分析")
    print("=" * 60)

    # 1. 机器精度演示
    print("1. 机器精度分析")
    print("-" * 30)

    machine_epsilon = np.finfo(float).eps
    print(f"机器精度 ε: {machine_epsilon:.2e}")

    # 演示浮点数精度问题
    a = 0.1 + 0.1 + 0.1
    b = 0.3
    print(f"0.1 + 0.1 + 0.1 = {a:.17f}")
    print(f"0.3 = {b:.17f}")
    print(f"相等? {a == b}")
    print(f"差值: {abs(a - b):.2e}")

    # 2. 病态矩阵示例
    print(f"\n2. 病态矩阵分析")
    print("-" * 30)

    # Hilbert矩阵(著名的病态矩阵)
    def hilbert_matrix(n):
        """生成n阶Hilbert矩阵"""
        H = np.zeros((n, n))
        for i in range(n):
            for j in range(n):
                H[i, j] = 1.0 / (i + j + 1)
        return H

    # 不同大小的Hilbert矩阵
    sizes = [3, 5, 8, 10]
    condition_numbers = []

    print("Hilbert矩阵条件数分析:")
    for n in sizes:
        H = hilbert_matrix(n)
        cond_num = np.linalg.cond(H)
        condition_numbers.append(cond_num)
        print(f"H_{n}: 条件数 = {cond_num:.2e}")

    # 3. 条件数对求解精度的影响
    print(f"\n3. 条件数对求解精度的影响")
    print("-" * 40)

    n = 6
    H = hilbert_matrix(n)
    x_exact = np.ones(n)  # 精确解
    b_exact = H @ x_exact  # 精确右端项

    # 添加小扰动
    perturbation_levels = [1e-12, 1e-10, 1e-8, 1e-6]
    results = []

    for eps in perturbation_levels:
        # 扰动右端项
        b_perturbed = b_exact + eps * np.random.randn(n)

        # 求解扰动系统
        try:
            x_computed = solve(H, b_perturbed)
            relative_error = norm(x_computed - x_exact) / norm(x_exact)
            results.append((eps, relative_error))

            print(f"扰动水平 {eps:.0e}: 相对误差 = {relative_error:.2e}")
        except:
            print(f"扰动水平 {eps:.0e}: 求解失败")

    # 可视化
    fig, axes = plt.subplots(2, 3, figsize=(18, 12))

    # 条件数增长
    ax1 = axes[0, 0]
    ax1.semilogy(sizes, condition_numbers, 'o-', linewidth=2, markersize=8)
    ax1.set_xlabel('矩阵大小')
    ax1.set_ylabel('条件数')
    ax1.set_title('Hilbert矩阵条件数增长')
    ax1.grid(True, alpha=0.3)

    # Hilbert矩阵可视化
    ax2 = axes[0, 1]
    H_display = hilbert_matrix(8)
    im = ax2.imshow(H_display, cmap='Blues')
    ax2.set_title('8×8 Hilbert矩阵')
    plt.colorbar(im, ax=ax2)

    # 误差放大效应
    ax3 = axes[0, 2]
    if results:
        perturbations, errors = zip(*results)
        ax3.loglog(perturbations, errors, 'ro-', linewidth=2, markersize=8, label='实际误差')

        # 理论误差界
        cond_H = np.linalg.cond(H)
        theoretical_errors = [cond_H * eps / norm(b_exact) for eps in perturbations]
        ax3.loglog(perturbations, theoretical_errors, 'b--', linewidth=2, label='理论误差界')

        ax3.set_xlabel('输入扰动水平')
        ax3.set_ylabel('输出相对误差')
        ax3.set_title('误差放大效应')
        ax3.legend()
        ax3.grid(True, alpha=0.3)

    # 不同矩阵类型的条件数比较
    ax4 = axes[1, 0]

    matrix_types = {
        '单位矩阵': np.eye(5),
        '对角优势': np.diag([10, 5, 3, 2, 1]) + 0.1*np.random.randn(5, 5),
        'Hilbert-5': hilbert_matrix(5),
        '随机矩阵': np.random.randn(5, 5)
    }

    names = []
    conds = []

    for name, A in matrix_types.items():
        if name != 'Hilbert-5':
            A = A @ A.T  # 确保正定
        try:
            cond_A = np.linalg.cond(A)
            names.append(name)
            conds.append(cond_A)
        except:
            pass

    ax4.bar(range(len(names)), conds, alpha=0.7)
    ax4.set_yscale('log')
    ax4.set_xlabel('矩阵类型')
    ax4.set_ylabel('条件数(对数尺度)')
    ax4.set_title('不同矩阵类型的条件数')
    ax4.set_xticks(range(len(names)))
    ax4.set_xticklabels(names, rotation=45)
    ax4.grid(True, alpha=0.3)

    # 舍入误差累积演示
    ax5 = axes[1, 1]

    def sum_series(n, method='forward'):
        """计算级数和的不同方法"""
        if method == 'forward':
            # 正向累加
            result = 0.0
            for i in range(1, n+1):
                result += 1.0 / i**2
        else:  # backward
            # 反向累加
            result = 0.0
            for i in range(n, 0, -1):
                result += 1.0 / i**2
        return result

    n_values = np.logspace(2, 6, 20).astype(int)
    forward_results = []
    backward_results = []

    for n in n_values:
        forward_results.append(sum_series(n, 'forward'))
        backward_results.append(sum_series(n, 'backward'))

    ax5.semilogx(n_values, forward_results, 'r-', label='正向累加')
    ax5.semilogx(n_values, backward_results, 'b-', label='反向累加')
    ax5.axhline(y=np.pi**2/6, color='black', linestyle='--', label='理论值 π²/6')

    ax5.set_xlabel('项数')
    ax5.set_ylabel('级数和')
    ax5.set_title('舍入误差累积:Σ(1/k²)')
    ax5.legend()
    ax5.grid(True, alpha=0.3)

    # 数值稳定性比较
    ax6 = axes[1, 2]

    # 比较不同算法的数值稳定性
    def unstable_algorithm(x):
        """数值不稳定算法示例"""
        return (x**2 - 1) / (x - 1)

    def stable_algorithm(x):
        """数值稳定算法"""
        return x + 1

    x_values = np.linspace(0.99, 1.01, 100)
    x_values = x_values[x_values != 1.0]  # 避免除零

    unstable_results = []
    stable_results = []

    for x in x_values:
        try:
            unstable_results.append(unstable_algorithm(x))
            stable_results.append(stable_algorithm(x))
        except:
            unstable_results.append(np.nan)
            stable_results.append(np.nan)

    ax6.plot(x_values, unstable_results, 'r-', label='不稳定: (x²-1)/(x-1)', linewidth=2)
    ax6.plot(x_values, stable_results, 'b-', label='稳定: x+1', linewidth=2)

    ax6.set_xlabel('x')
    ax6.set_ylabel('函数值')
    ax6.set_title('数值稳定性比较')
    ax6.legend()
    ax6.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.show()

    return condition_numbers

def matrix_factorizations_numerical():
    """矩阵分解的数值实现"""

    print(f"\n矩阵分解的数值方法")
    print("=" * 40)

    # 生成测试矩阵
    np.random.seed(42)
    n = 6
    A_random = np.random.randn(n, n)
    A_spd = A_random @ A_random.T  # 对称正定矩阵
    A_general = np.random.randn(n, n) + 0.1 * np.eye(n)  # 一般矩阵

    print("测试矩阵信息:")
    print(f"随机对称正定矩阵条件数: {np.linalg.cond(A_spd):.2e}")
    print(f"一般矩阵条件数: {np.linalg.cond(A_general):.2e}")

    # 1. LU分解
    print(f"\n1. LU分解")
    print("-" * 20)

    P, L, U = lu(A_general)

    print("LU分解验证:")
    print(f"||PA - LU||: {norm(P @ A_general - L @ U):.2e}")

    # 检查L和U的性质
    print(f"L是下三角: {np.allclose(L, np.tril(L))}")
    print(f"U是上三角: {np.allclose(U, np.triu(U))}")
    print(f"P是置换矩阵: {np.allclose(P @ P.T, np.eye(n))}")

    # 2. QR分解
    print(f"\n2. QR分解")
    print("-" * 20)

    Q, R = qr(A_general)

    print("QR分解验证:")
    print(f"||A - QR||: {norm(A_general - Q @ R):.2e}")
    print(f"Q是正交矩阵: {np.allclose(Q @ Q.T, np.eye(n))}")
    print(f"R是上三角: {np.allclose(R, np.triu(R))}")

    # 3. SVD分解
    print(f"\n3. 奇异值分解")
    print("-" * 20)

    U_svd, s, Vt = svd(A_general)

    print("SVD分解验证:")
    print(f"||A - UΣVᵀ||: {norm(A_general - U_svd @ np.diag(s) @ Vt):.2e}")
    print(f"奇异值: {s}")
    print(f"条件数 (σ_max/σ_min): {s[0]/s[-1]:.2e}")

    # 4. Cholesky分解(仅对正定矩阵)
    print(f"\n4. Cholesky分解")
    print("-" * 20)

    try:
        from scipy.linalg import cholesky
        L_chol = cholesky(A_spd, lower=True)

        print("Cholesky分解验证:")
        print(f"||A - LLᵀ||: {norm(A_spd - L_chol @ L_chol.T):.2e}")
        print(f"L是下三角: {np.allclose(L_chol, np.tril(L_chol))}")

        # Cholesky vs LU计算复杂度比较
        print(f"Cholesky分解节省约50%计算量(对正定矩阵)")

    except Exception as e:
        print(f"Cholesky分解失败: {e}")

    # 可视化矩阵分解
    fig, axes = plt.subplots(3, 4, figsize=(16, 12))

    # 原始矩阵
    ax = axes[0, 0]
    im = ax.imshow(A_general, cmap='RdBu')
    ax.set_title('原矩阵 A')
    plt.colorbar(im, ax=ax)

    # LU分解
    ax = axes[0, 1]
    im = ax.imshow(L, cmap='Blues')
    ax.set_title('L (下三角)')
    plt.colorbar(im, ax=ax)

    ax = axes[0, 2]
    im = ax.imshow(U, cmap='Reds')
    ax.set_title('U (上三角)')
    plt.colorbar(im, ax=ax)

    ax = axes[0, 3]
    im = ax.imshow(P, cmap='Greys')
    ax.set_title('P (置换)')
    plt.colorbar(im, ax=ax)

    # QR分解
    ax = axes[1, 0]
    im = ax.imshow(Q, cmap='RdBu')
    ax.set_title('Q (正交)')
    plt.colorbar(im, ax=ax)

    ax = axes[1, 1]
    im = ax.imshow(R, cmap='Reds')
    ax.set_title('R (上三角)')
    plt.colorbar(im, ax=ax)

    # SVD分解
    ax = axes[1, 2]
    im = ax.imshow(U_svd, cmap='Blues')
    ax.set_title('U (左奇异向量)')
    plt.colorbar(im, ax=ax)

    ax = axes[1, 3]
    ax.bar(range(len(s)), s)
    ax.set_title('奇异值')
    ax.set_xlabel('索引')
    ax.set_ylabel('值')
    ax.grid(True, alpha=0.3)

    # 分解精度比较
    ax = axes[2, 0]
    methods = ['LU', 'QR', 'SVD']
    errors = [
        norm(P @ A_general - L @ U),
        norm(A_general - Q @ R),
        norm(A_general - U_svd @ np.diag(s) @ Vt)
    ]

    bars = ax.bar(methods, errors)
    ax.set_yscale('log')
    ax.set_ylabel('重构误差')
    ax.set_title('分解精度比较')
    ax.grid(True, alpha=0.3)

    # 计算时间比较
    ax = axes[2, 1]

    def time_factorization(A, method, n_trials=10):
        """测试分解方法的计算时间"""
        times = []
        for _ in range(n_trials):
            start_time = time.time()
            if method == 'LU':
                lu(A)
            elif method == 'QR':
                qr(A)
            elif method == 'SVD':
                svd(A)
            end_time = time.time()
            times.append(end_time - start_time)
        return np.mean(times)

    time_results = {}
    for method in ['LU', 'QR', 'SVD']:
        time_results[method] = time_factorization(A_general, method)

    methods = list(time_results.keys())
    times = list(time_results.values())

    ax.bar(methods, times, alpha=0.7)
    ax.set_ylabel('平均时间 (秒)')
    ax.set_title('计算时间比较')
    ax.grid(True, alpha=0.3)

    # 条件数与分解稳定性
    ax = axes[2, 2]

    # 生成不同条件数的矩阵
    condition_numbers = [1e2, 1e4, 1e6, 1e8]
    lu_errors = []
    qr_errors = []

    for cond in condition_numbers:
        # 构造指定条件数的矩阵
        U_temp, _, Vt_temp = svd(np.random.randn(5, 5))
        singular_vals = np.logspace(0, -np.log10(cond), 5)
        A_temp = U_temp @ np.diag(singular_vals) @ Vt_temp

        # 测试分解精度
        P_temp, L_temp, U_temp = lu(A_temp)
        Q_temp, R_temp = qr(A_temp)

        lu_error = norm(P_temp @ A_temp - L_temp @ U_temp) / norm(A_temp)
        qr_error = norm(A_temp - Q_temp @ R_temp) / norm(A_temp)

        lu_errors.append(lu_error)
        qr_errors.append(qr_error)

    ax.loglog(condition_numbers, lu_errors, 'o-', label='LU分解', linewidth=2)
    ax.loglog(condition_numbers, qr_errors, 's-', label='QR分解', linewidth=2)

    ax.set_xlabel('条件数')
    ax.set_ylabel('相对误差')
    ax.set_title('分解稳定性 vs 条件数')
    ax.legend()
    ax.grid(True, alpha=0.3)

    # 空白子图用于说明
    ax = axes[2, 3]
    ax.text(0.1, 0.8, '分解方法特点:', fontsize=12, weight='bold', transform=ax.transAxes)
    ax.text(0.1, 0.7, '• LU: 一般矩阵,O(n³)', fontsize=10, transform=ax.transAxes)
    ax.text(0.1, 0.6, '• QR: 数值稳定,正交化', fontsize=10, transform=ax.transAxes)
    ax.text(0.1, 0.5, '• SVD: 最稳定,最昂贵', fontsize=10, transform=ax.transAxes)
    ax.text(0.1, 0.4, '• Cholesky: 正定矩阵专用', fontsize=10, transform=ax.transAxes)
    ax.text(0.1, 0.2, '选择原则:', fontsize=12, weight='bold', transform=ax.transAxes)
    ax.text(0.1, 0.1, '• 速度: LU > QR > SVD', fontsize=10, transform=ax.transAxes)
    ax.text(0.1, 0.0, '• 稳定性: SVD > QR > LU', fontsize=10, transform=ax.transAxes)
    ax.set_xlim(0, 1)
    ax.set_ylim(0, 1)
    ax.axis('off')

    plt.tight_layout()
    plt.show()

def iterative_methods():
    """迭代法求解线性方程组"""

    print(f"\n迭代法求解线性方程组")
    print("=" * 40)

    # 生成对角优势矩阵(保证收敛)
    n = 8
    A = np.random.randn(n, n)
    A = A + (n + 1) * np.eye(n)  # 使其对角占优

    x_true = np.random.randn(n)
    b = A @ x_true

    print(f"系统规模: {n} × {n}")
    print(f"矩阵条件数: {np.linalg.cond(A):.2e}")

    # 直接法求解(作为参考)
    x_direct = solve(A, b)
    print(f"直接法误差: {norm(x_direct - x_true):.2e}")

    def jacobi_method(A, b, x0, max_iter=100, tol=1e-10):
        """雅可比迭代法"""
        n = len(b)
        x = x0.copy()
        D = np.diag(np.diag(A))
        R = A - D

        residuals = []
        errors = []

        for k in range(max_iter):
            x_new = solve(D, b - R @ x)

            residual = norm(A @ x_new - b)
            error = norm(x_new - x_true)
            residuals.append(residual)
            errors.append(error)

            if residual < tol:
                return x_new, k+1, residuals, errors

            x = x_new

        return x, max_iter, residuals, errors

    def gauss_seidel_method(A, b, x0, max_iter=100, tol=1e-10):
        """高斯-赛德尔迭代法"""
        n = len(b)
        x = x0.copy()

        residuals = []
        errors = []

        for k in range(max_iter):
            x_new = x.copy()

            for i in range(n):
                sum1 = np.dot(A[i, :i], x_new[:i])
                sum2 = np.dot(A[i, i+1:], x[i+1:])
                x_new[i] = (b[i] - sum1 - sum2) / A[i, i]

            residual = norm(A @ x_new - b)
            error = norm(x_new - x_true)
            residuals.append(residual)
            errors.append(error)

            if residual < tol:
                return x_new, k+1, residuals, errors

            x = x_new

        return x, max_iter, residuals, errors

    def sor_method(A, b, x0, omega=1.2, max_iter=100, tol=1e-10):
        """超松弛迭代法"""
        n = len(b)
        x = x0.copy()

        residuals = []
        errors = []

        for k in range(max_iter):
            x_new = x.copy()

            for i in range(n):
                sum1 = np.dot(A[i, :i], x_new[:i])
                sum2 = np.dot(A[i, i+1:], x[i+1:])
                x_gs = (b[i] - sum1 - sum2) / A[i, i]
                x_new[i] = (1 - omega) * x[i] + omega * x_gs

            residual = norm(A @ x_new - b)
            error = norm(x_new - x_true)
            residuals.append(residual)
            errors.append(error)

            if residual < tol:
                return x_new, k+1, residuals, errors

            x = x_new

        return x, max_iter, residuals, errors

    # 运行各种迭代方法
    x0 = np.zeros(n)  # 初始猜测

    print(f"\n迭代方法比较:")
    print("-" * 30)

    methods = {
        'Jacobi': jacobi_method,
        'Gauss-Seidel': gauss_seidel_method,
        'SOR (ω=1.2)': lambda A, b, x0, **kwargs: sor_method(A, b, x0, omega=1.2, **kwargs)
    }

    results = {}

    for name, method in methods.items():
        x_sol, iterations, residuals, errors = method(A, b, x0)
        final_error = norm(x_sol - x_true)

        results[name] = {
            'solution': x_sol,
            'iterations': iterations,
            'residuals': residuals,
            'errors': errors,
            'final_error': final_error
        }

        print(f"{name:12}: {iterations:3d} 次迭代,最终误差 = {final_error:.2e}")

    # 可视化
    fig, axes = plt.subplots(2, 3, figsize=(18, 12))

    # 收敛历史
    ax1 = axes[0, 0]
    for name, result in results.items():
        iterations_list = range(1, len(result['residuals']) + 1)
        ax1.semilogy(iterations_list, result['residuals'], 'o-',
                    linewidth=2, markersize=4, label=name)

    ax1.set_xlabel('迭代次数')
    ax1.set_ylabel('残差 ||Ax - b||')
    ax1.set_title('残差收敛历史')
    ax1.legend()
    ax1.grid(True, alpha=0.3)

    # 误差收敛历史
    ax2 = axes[0, 1]
    for name, result in results.items():
        iterations_list = range(1, len(result['errors']) + 1)
        ax2.semilogy(iterations_list, result['errors'], 'o-',
                    linewidth=2, markersize=4, label=name)

    ax2.set_xlabel('迭代次数')
    ax2.set_ylabel('误差 ||x - x_true||')
    ax2.set_title('误差收敛历史')
    ax2.legend()
    ax2.grid(True, alpha=0.3)

    # 收敛速度比较
    ax3 = axes[0, 2]
    method_names = list(results.keys())
    iterations_count = [results[name]['iterations'] for name in method_names]
    final_errors = [results[name]['final_error'] for name in method_names]

    bars = ax3.bar(range(len(method_names)), iterations_count, alpha=0.7)
    ax3.set_xlabel('方法')
    ax3.set_ylabel('收敛所需迭代次数')
    ax3.set_title('收敛速度比较')
    ax3.set_xticks(range(len(method_names)))
    ax3.set_xticklabels(method_names, rotation=45)
    ax3.grid(True, alpha=0.3)

    # 添加数值标签
    for bar, count in zip(bars, iterations_count):
        ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
                f'{count}', ha='center', va='bottom')

    # SOR参数优化
    ax4 = axes[1, 0]
    omega_values = np.linspace(0.5, 1.95, 30)
    sor_iterations = []

    for omega in omega_values:
        try:
            _, iters, _, _ = sor_method(A, b, x0, omega=omega, max_iter=200)
            sor_iterations.append(iters)
        except:
            sor_iterations.append(200)  # 未收敛

    ax4.plot(omega_values, sor_iterations, 'b-', linewidth=2)
    optimal_idx = np.argmin(sor_iterations)
    optimal_omega = omega_values[optimal_idx]
    ax4.plot(optimal_omega, sor_iterations[optimal_idx], 'ro', markersize=10,
             label=f'最优 ω = {optimal_omega:.2f}')

    ax4.set_xlabel('松弛因子 ω')
    ax4.set_ylabel('收敛迭代次数')
    ax4.set_title('SOR方法参数优化')
    ax4.legend()
    ax4.grid(True, alpha=0.3)

    # 谱半径分析
    ax5 = axes[1, 1]

    # 计算迭代矩阵的谱半径
    D = np.diag(np.diag(A))
    L = np.tril(A, k=-1)
    U = np.triu(A, k=1)

    # Jacobi迭代矩阵
    T_jacobi = -solve(D, L + U)
    rho_jacobi = max(abs(np.linalg.eigvals(T_jacobi)))

    # Gauss-Seidel迭代矩阵
    T_gs = -solve(D + L, U)
    rho_gs = max(abs(np.linalg.eigvals(T_gs)))

    methods_spectral = ['Jacobi', 'Gauss-Seidel']
    spectral_radii = [rho_jacobi, rho_gs]

    bars = ax5.bar(methods_spectral, spectral_radii, alpha=0.7)
    ax5.axhline(y=1, color='red', linestyle='--', label='收敛界限')
    ax5.set_ylabel('谱半径')
    ax5.set_title('迭代矩阵谱半径')
    ax5.legend()
    ax5.grid(True, alpha=0.3)

    # 添加数值标签
    for bar, rho in zip(bars, spectral_radii):
        ax5.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01,
                f'{rho:.3f}', ha='center', va='bottom')

    # 解的收敛轨迹(选择前两个分量)
    ax6 = axes[1, 2]

    for name, result in results.items():
        if name == 'Jacobi':  # 只显示一种方法的轨迹,避免图形混乱
            # 重新运行以获取所有中间解
            x = x0.copy()
            trajectory_x = [x[0]]
            trajectory_y = [x[1]]

            D = np.diag(np.diag(A))
            R = A - D

            for k in range(min(50, result['iterations'])):
                x = solve(D, b - R @ x)
                trajectory_x.append(x[0])
                trajectory_y.append(x[1])

            ax6.plot(trajectory_x, trajectory_y, 'b-o', markersize=4,
                    alpha=0.7, label='Jacobi轨迹')

    ax6.plot(x_true[0], x_true[1], 'rs', markersize=10, label='真解')
    ax6.plot(x0[0], x0[1], 'go', markersize=8, label='初始值')

    ax6.set_xlabel('x₁')
    ax6.set_ylabel('x₂')
    ax6.set_title('解的收敛轨迹')
    ax6.legend()
    ax6.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.show()

    return results

def eigenvalue_algorithms():
    """特征值计算的数值方法"""

    print(f"\n特征值计算的数值方法")
    print("=" * 40)

    # 生成测试矩阵
    np.random.seed(42)
    n = 6
    A_random = np.random.randn(n, n)
    A_symmetric = A_random + A_random.T  # 对称矩阵
    A_general = A_random + 0.1 * np.eye(n)  # 一般矩阵

    print("测试矩阵:")
    print(f"对称矩阵 A_sym 的条件数: {np.linalg.cond(A_symmetric):.2e}")
    print(f"一般矩阵 A_gen 的条件数: {np.linalg.cond(A_general):.2e}")

    # 真实特征值(用作参考)
    true_eigenvals_sym = np.linalg.eigvals(A_symmetric)
    true_eigenvals_gen = np.linalg.eigvals(A_general)

    # 按模长排序
    true_eigenvals_sym = true_eigenvals_sym[np.argsort(np.abs(true_eigenvals_sym))[::-1]]
    true_eigenvals_gen = true_eigenvals_gen[np.argsort(np.abs(true_eigenvals_gen))[::-1]]

    print(f"\n对称矩阵真实特征值: {true_eigenvals_sym}")
    print(f"一般矩阵真实特征值: {true_eigenvals_gen}")

    def power_method(A, max_iter=100, tol=1e-10):
        """幂法求主特征值"""
        n = A.shape[0]
        x = np.random.randn(n)
        x = x / norm(x)

        eigenval_history = []
        eigenvec_history = []

        for k in range(max_iter):
            y = A @ x
            eigenval = x.T @ y
            x_new = y / norm(y)

            eigenval_history.append(eigenval)
            eigenvec_history.append(x_new.copy())

            if k > 0 and abs(eigenval_history[-1] - eigenval_history[-2]) < tol:
                return eigenval, x_new, k+1, eigenval_history

            x = x_new

        return eigenval_history[-1], x, max_iter, eigenval_history

    def inverse_power_method(A, sigma, max_iter=100, tol=1e-10):
        """反幂法求接近σ的特征值"""
        n = A.shape[0]
        x = np.random.randn(n)
        x = x / norm(x)

        A_shifted = A - sigma * np.eye(n)

        eigenval_history = []

        for k in range(max_iter):
            try:
                y = solve(A_shifted, x)
                mu = x.T @ y
                x_new = y / norm(y)

                eigenval = 1/mu + sigma  # 逆变换回原特征值

                eigenval_history.append(eigenval)

                if k > 0 and abs(eigenval_history[-1] - eigenval_history[-2]) < tol:
                    return eigenval, x_new, k+1, eigenval_history

                x = x_new
            except:
                break

        return eigenval_history[-1] if eigenval_history else sigma, x, k+1, eigenval_history

    def qr_algorithm_basic(A, max_iter=100, tol=1e-10):
        """基础QR算法"""
        A_k = A.copy()
        eigenval_history = []

        for k in range(max_iter):
            Q, R = qr(A_k)
            A_k = R @ Q

            # 记录对角元素(特征值的近似)
            eigenvals = np.diag(A_k)
            eigenval_history.append(eigenvals.copy())

            # 检查收敛(下三角部分接近零)
            if k > 5:
                lower_triangular_norm = norm(np.tril(A_k, k=-1))
                if lower_triangular_norm < tol:
                    break

        return np.diag(A_k), A_k, k+1, eigenval_history

    # 运行各种方法
    print(f"\n幂法求主特征值:")
    print("-" * 25)

    # 对称矩阵
    eigenval_pm_sym, eigenvec_pm_sym, iter_pm_sym, hist_pm_sym = power_method(A_symmetric)
    error_pm_sym = abs(eigenval_pm_sym - true_eigenvals_sym[0])

    print(f"对称矩阵:")
    print(f"  幂法结果: {eigenval_pm_sym:.6f} ({iter_pm_sym} 次迭代)")
    print(f"  真实值: {true_eigenvals_sym[0]:.6f}")
    print(f"  误差: {error_pm_sym:.2e}")

    # 一般矩阵
    eigenval_pm_gen, eigenvec_pm_gen, iter_pm_gen, hist_pm_gen = power_method(A_general)
    error_pm_gen = abs(eigenval_pm_gen - true_eigenvals_gen[0])

    print(f"一般矩阵:")
    print(f"  幂法结果: {eigenval_pm_gen:.6f} ({iter_pm_gen} 次迭代)")
    print(f"  真实值: {true_eigenvals_gen[0]:.6f}")
    print(f"  误差: {error_pm_gen:.2e}")

    # 反幂法
    print(f"\n反幂法求特定特征值:")
    print("-" * 30)

    # 选择一个目标值
    target = 0.5
    eigenval_ipm, eigenvec_ipm, iter_ipm, hist_ipm = inverse_power_method(A_symmetric, target)

    # 找到最接近的真实特征值
    closest_true = true_eigenvals_sym[np.argmin(np.abs(true_eigenvals_sym - target))]
    error_ipm = abs(eigenval_ipm - closest_true)

    print(f"目标值: {target}")
    print(f"反幂法结果: {eigenval_ipm:.6f} ({iter_imp} 次迭代)" if 'iter_ipm' in locals() else f"反幂法结果: {eigenval_ipm:.6f}")
    print(f"最接近的真实特征值: {closest_true:.6f}")
    print(f"误差: {error_ipm:.2e}")

    # QR算法
    print(f"\nQR算法求所有特征值:")
    print("-" * 30)

    eigenvals_qr_sym, A_final_sym, iter_qr_sym, hist_qr_sym = qr_algorithm_basic(A_symmetric)
    eigenvals_qr_gen, A_final_gen, iter_qr_gen, hist_qr_gen = qr_algorithm_basic(A_general)

    print(f"对称矩阵 ({iter_qr_sym} 次迭代):")
    print(f"  QR结果: {np.sort(eigenvals_qr_sym)[::-1]}")
    print(f"  真实值: {np.sort(true_eigenvals_sym)[::-1]}")
    print(f"  误差: {norm(np.sort(eigenvals_qr_sym) - np.sort(true_eigenvals_sym)):.2e}")

    print(f"一般矩阵 ({iter_qr_gen} 次迭代):")
    print(f"  QR结果: {np.sort(np.real(eigenvals_qr_gen))[::-1]}")
    print(f"  真实值: {np.sort(np.real(true_eigenvals_gen))[::-1]}")

    # 可视化
    fig, axes = plt.subplots(3, 3, figsize=(18, 18))

    # 幂法收敛历史
    ax1 = axes[0, 0]
    ax1.semilogy(range(1, len(hist_pm_sym)+1),
                [abs(val - true_eigenvals_sym[0]) for val in hist_pm_sym],
                'b-o', linewidth=2, markersize=4, label='对称矩阵')

    ax1.semilogy(range(1, len(hist_pm_gen)+1),
                [abs(val - true_eigenvals_gen[0]) for val in hist_pm_gen],
                'r-s', linewidth=2, markersize=4, label='一般矩阵')

    ax1.set_xlabel('迭代次数')
    ax1.set_ylabel('误差')
    ax1.set_title('幂法收敛历史')
    ax1.legend()
    ax1.grid(True, alpha=0.3)

    # 反幂法收敛历史
    ax2 = axes[0, 1]
    if hist_ipm:
        ax2.semilogy(range(1, len(hist_ipm)+1),
                    [abs(val - closest_true) for val in hist_ipm],
                    'g-o', linewidth=2, markersize=4)

        ax2.set_xlabel('迭代次数')
        ax2.set_ylabel('误差')
        ax2.set_title(f'反幂法收敛历史 (目标={target})')
        ax2.grid(True, alpha=0.3)

    # QR算法收敛历史(显示最大非对角元素)
    ax3 = axes[0, 2]

    def off_diagonal_norm(eigenval_matrix):
        """计算矩阵的非对角元素范数"""
        return norm(eigenval_matrix - np.diag(np.diag(eigenval_matrix)))

    if len(hist_qr_sym) > 1:
        # 这里需要重新运行QR算法来获取矩阵历史
        # 简化显示:只显示特征值的变化
        max_changes_sym = []
        for i in range(1, len(hist_qr_sym)):
            change = max(abs(hist_qr_sym[i] - hist_qr_sym[i-1]))
            max_changes_sym.append(change)

        if max_changes_sym:
            ax3.semilogy(range(1, len(max_changes_sym)+1), max_changes_sym,
                        'b-o', linewidth=2, markersize=4, label='对称矩阵')

    ax3.set_xlabel('迭代次数')
    ax3.set_ylabel('最大特征值变化')
    ax3.set_title('QR算法收敛历史')
    ax3.legend()
    ax3.grid(True, alpha=0.3)

    # 特征值比较
    ax4 = axes[1, 0]
    x_pos = range(len(true_eigenvals_sym))
    width = 0.35

    ax4.bar([x - width/2 for x in x_pos], np.sort(true_eigenvals_sym)[::-1], width,
           label='真实值', alpha=0.7)
    ax4.bar([x + width/2 for x in x_pos], np.sort(eigenvals_qr_sym)[::-1], width,
           label='QR算法', alpha=0.7)

    ax4.set_xlabel('特征值索引')
    ax4.set_ylabel('特征值')
    ax4.set_title('对称矩阵特征值比较')
    ax4.legend()
    ax4.grid(True, alpha=0.3)

    # 计算时间比较
    ax5 = axes[1, 1]

    # 简化的时间测试
    methods = ['Power Method', 'QR Algorithm', 'NumPy (eig)']
    times = []

    # 幂法时间(估算)
    times.append(iter_pm_sym * 1e-4)  # 估算每次迭代时间

    # QR算法时间(估算)
    times.append(iter_qr_sym * 1e-3)  # QR每次迭代更昂贵

    # NumPy时间
    start_time = time.time()
    for _ in range(10):
        np.linalg.eig(A_symmetric)
    numpy_time = (time.time() - start_time) / 10
    times.append(numpy_time)

    bars = ax5.bar(methods, times, alpha=0.7)
    ax5.set_ylabel('时间 (秒)')
    ax5.set_title('计算时间比较 (估算)')
    ax5.set_xticklabels(methods, rotation=45)

    for bar, time_val in zip(bars, times):
        ax5.text(bar.get_x() + bar.get_width()/2, bar.get_height(),
                f'{time_val:.2e}', ha='center', va='bottom')

    # 特征向量可视化(前两个分量)
    ax6 = axes[1, 2]

    # 比较主特征向量
    true_eigenvec = np.linalg.eig(A_symmetric)[1][:, 0]

    # 确保符号一致
    if np.dot(eigenvec_pm_sym, true_eigenvec) < 0:
        eigenvec_pm_sym = -eigenvec_pm_sym

    ax6.arrow(0, 0, true_eigenvec[0], true_eigenvec[1],
             head_width=0.05, head_length=0.05, fc='blue', ec='blue',
             linewidth=3, label='真实特征向量')
    ax6.arrow(0, 0, eigenvec_pm_sym[0], eigenvec_pm_sym[1],
             head_width=0.05, head_length=0.05, fc='red', ec='red',
             linewidth=3, alpha=0.7, label='幂法结果')

    ax6.set_xlim(-1.2, 1.2)
    ax6.set_ylim(-1.2, 1.2)
    ax6.set_xlabel('v₁')
    ax6.set_ylabel('v₂')
    ax6.set_title('主特征向量比较')
    ax6.legend()
    ax6.grid(True, alpha=0.3)
    ax6.set_aspect('equal')

    # 算法复杂度比较
    ax7 = axes[2, 0]

    matrix_sizes = [10, 20, 50, 100]
    complexities = {
        'Power Method': [n for n in matrix_sizes],  # O(n²) per iteration
        'QR Algorithm': [n**3 for n in matrix_sizes],  # O(n³)
        'Direct (eig)': [n**3 for n in matrix_sizes]   # O(n³)
    }

    for method, complexity in complexities.items():
        ax7.loglog(matrix_sizes, complexity, 'o-', linewidth=2, label=method)

    ax7.set_xlabel('矩阵大小 n')
    ax7.set_ylabel('计算复杂度')
    ax7.set_title('算法复杂度比较')
    ax7.legend()
    ax7.grid(True, alpha=0.3)

    # 收敛速度比较
    ax8 = axes[2, 1]

    # 模拟不同特征值间隙对幂法收敛的影响
    gaps = [0.1, 0.3, 0.5, 0.7, 0.9]  # λ₂/λ₁ 比值
    convergence_rates = []

    for gap in gaps:
        # 理论收敛率 ≈ |λ₂/λ₁|
        rate = gap
        convergence_rates.append(rate)

    ax8.plot(gaps, convergence_rates, 'bo-', linewidth=2, markersize=8)
    ax8.set_xlabel('特征值间隙 (λ₂/λ₁)')
    ax8.set_ylabel('收敛率')
    ax8.set_title('幂法收敛率 vs 特征值间隙')
    ax8.grid(True, alpha=0.3)

    # 方法选择指南
    ax9 = axes[2, 2]
    ax9.text(0.1, 0.9, '特征值算法选择指南', fontsize=14, weight='bold', transform=ax9.transAxes)

    guidelines = [
        '单个主特征值:',
        '• 幂法 - 简单、内存效率高',
        '• 适用于大型稀疏矩阵',
        '',
        '特定特征值:',
        '• 反幂法 - 收敛快',
        '• 需要近似值',
        '',
        '所有特征值:',
        '• QR算法 - 数值稳定',
        '• 对称矩阵优化版本',
        '',
        '大规模问题:',
        '• Lanczos/Arnoldi方法',
        '• Krylov子空间方法'
    ]

    for i, text in enumerate(guidelines):
        y_pos = 0.8 - i * 0.05
        if text.startswith('•'):
            ax9.text(0.15, y_pos, text, fontsize=10, transform=ax9.transAxes)
        elif text == '':
            continue
        else:
            ax9.text(0.1, y_pos, text, fontsize=11, weight='bold', transform=ax9.transAxes)

    ax9.set_xlim(0, 1)
    ax9.set_ylim(0, 1)
    ax9.axis('off')

    plt.tight_layout()
    plt.show()

# 运行所有演示
condition_nums = numerical_stability_demo()
matrix_factorizations_numerical()
iter_results = iterative_methods()
eigenvalue_algorithms()

# 最终总结
def numerical_linear_algebra_summary():
    """数值线性代数总结"""

    print(f"\n数值线性代数总结")
    print("=" * 50)

    summary = {
        "误差分析": {
            "舍入误差": "浮点数精度限制",
            "条件数": "系统对输入扰动的敏感性",
            "数值稳定性": "算法对舍入误差的鲁棒性",
            "后向误差": "实际解对应的扰动问题"
        },

        "矩阵分解": {
            "LU分解": "A = PLU,高效求解",
            "QR分解": "A = QR,数值稳定",
            "SVD分解": "A = UΣVᵀ,最稳定最通用",
            "Cholesky": "A = LLᵀ,正定矩阵专用"
        },

        "线性方程组": {
            "直接法": "分解求解,精确但昂贵",
            "Jacobi": "简单并行,收敛慢",
            "Gauss-Seidel": "收敛更快,串行",
            "SOR": "加速收敛,需调参"
        },

        "特征值问题": {
            "幂法": "主特征值,简单高效",
            "反幂法": "特定特征值,快速收敛",
            "QR算法": "所有特征值,工业标准",
            "Krylov方法": "大规模稀疏矩阵"
        }
    }

    for category, methods in summary.items():
        print(f"\n{category}:")
        print("-" * 30)
        for method, description in methods.items():
            print(f"• {method:15}: {description}")

    print(f"\n实践建议:")
    print("-" * 20)
    recommendations = [
        "• 优先考虑数值稳定性而非速度",
        "• 利用矩阵的特殊结构(对称、稀疏等)",
        "• 监控条件数,警惕病态问题",
        "• 选择合适的分解方法",
        "• 使用成熟的数值库(LAPACK, BLAS)",
        "• 验证结果的数值精度"
    ]

    for rec in recommendations:
        print(rec)

numerical_linear_algebra_summary()

# 完成所有章节
print(f"\n" + "="*60)
print("线性代数完整课程学习笔记生成完成!")
print("="*60)
print(f"已生成章节:")
for i in range(8, 16):
    print(f"• 第{i}章:la-{i:03d}.md")

# 最后更新todo列表
from datetime import datetime
print(f"\n生成完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")