在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.1 案例1:POD基础 - 一维热传导模态分解

案例描述:
本案例演示如何使用本征正交分解(POD)分析一维非稳态热传导问题的主导模态。通过生成多个初始条件下的仿真快照,提取能够代表系统主要动态特征的正交基函数。

关键步骤:

  1. 生成50组不同初始条件下的热传导仿真数据
  2. 使用快照方法构建相关矩阵
  3. 特征值分解获得POD模态
  4. 分析模态能量分布和累积能量
  5. 验证重构精度

物理意义:

  • 第一模态代表系统的平均温度分布
  • 第二、三模态代表主要的瞬态变化模式
  • 高阶模态包含更精细的空间结构

Python代码: 详见 run_simulation.py 中的 case1_pod_basic() 函数


8.2 案例2:POD-Galerkin降阶模型

案例描述:
构建完整的POD-Galerkin降阶模型,对比全阶模型(FOM)和降阶模型(ROM)的计算时间和精度。

关键步骤:

  1. 求解高分辨率全阶模型生成训练数据
  2. 执行POD分析获得降阶基
  3. 将空间算子投影到模态空间
  4. 在降阶空间求解动力学方程
  5. 对比计算时间和精度

结果分析:

  • 降阶维度通常只有原始维度的1-5%
  • 计算加速比可达10-1000倍
  • 精度损失通常小于1%

Python代码: 详见 run_simulation.py 中的 case2_pod_galerkin() 函数


8.3 案例3:动态模态分解(DMD)

案例描述:
使用DMD分析圆柱绕流尾迹的非定常动力学,提取主导频率和增长/衰减特性。

关键步骤:

  1. 生成非稳态流场快照序列
  2. 构建快照对矩阵
  3. 截断SVD获得低秩近似
  4. 特征分解获得DMD模态和特征值
  5. 分析频率谱和增长率

物理洞察:

  • DMD模态包含明确的频率信息
  • 特征值在单位圆内表示稳定模态
  • 可以识别卡门涡街的脱落频率

Python代码: 详见 run_simulation.py 中的 case3_dmd_analysis() 函数


8.4 案例4:参数化降阶模型

案例描述:
构建参数化降阶模型,实现对不同边界温度条件的快速预测。

关键步骤:

  1. 在参数空间采样生成训练数据
  2. POD分析获得降阶基
  3. 建立RBF插值模型映射参数到模态系数
  4. 对新参数条件进行快速预测
  5. 验证预测精度

应用场景:

  • 设计优化中的快速评估
  • 实时控制系统
  • 多工况分析

Python代码: 详见 run_simulation.py 中的 case4_parametric_rom() 函数


8.5 案例5:二维热传导降阶模型(含GIF动画)

案例描述:
构建二维非稳态热传导问题的POD-Galerkin降阶模型,并生成GIF动画展示时间演化。

关键步骤:

  1. 二维全阶模型求解
  2. 快照数据展平和POD分析
  3. 二维拉普拉斯算子的Galerkin投影
  4. 降阶空间时间推进
  5. 生成对比动画

可视化:

  • 全阶模型温度场演化
  • 降阶模型预测结果
  • 误差分布云图
  • GIF动画展示

Python代码: 详见 run_simulation.py 中的 case5_2d_heat_rom() 函数


8.6 案例6:非线性系统降阶 - Burgers方程

案例描述:
演示Burgers方程(非线性对流-扩散方程)的POD-Galerkin降阶,展示非线性项的处理方法。

关键步骤:

  1. 求解Burgers方程生成快照
  2. POD分析获得降阶基
  3. 在线计算非线性项(Galerkin投影)
  4. 降阶模型时间推进
  5. 精度验证

非线性处理:

  • 在物理空间计算非线性项
  • 投影到模态空间
  • 保持非线性特性的近似

Python代码: 详见 run_simulation.py 中的 case6_burgers_rom() 函数


8.7 案例7:模态能量分析与截断准则

案例描述:
系统分析不同能量截断准则对降阶模型精度的影响,为模态数选择提供指导。

截断准则对比:

准则 模态数 保留能量
90%能量 较少 90%
95%能量 中等 95%
99%能量 较多 99%
99.9%能量 很多 99.9%
特征值阈值 可变 取决于阈值

分析内容:

  • 奇异值谱分析
  • 能量分布和累积能量
  • 重构误差随模态数变化
  • 不同准则的对比

Python代码: 详见 run_simulation.py 中的 case7_modal_energy_analysis() 函数


8.8 案例8:降阶模型在优化中的应用

案例描述:
使用降阶模型加速热沉设计优化,展示ROM在工程优化中的实际价值。

优化问题:

  • 设计变量:翅片数量、高度、基板厚度
  • 目标:最小化最高温度
  • 约束:成本限制

优化流程:

  1. 生成设计空间样本
  2. 构建参数化ROM
  3. 使用ROM快速评估目标函数
  4. 网格搜索寻找最优设计
  5. 全阶模型验证

效益分析:

  • 优化速度提升100-10000倍
  • 支持大规模设计空间探索
  • 实时设计评估

Python代码: 详见 run_simulation.py 中的 case8_rom_for_optimization() 函数


8.9 案例9:降阶模型误差估计

案例描述:
建立降阶模型的误差估计框架,包括投影误差和ROM近似误差的量化分析。

误差类型:

  1. 投影误差:最佳可能近似误差
  2. ROM误差:实际降阶模型误差
  3. 外推误差:参数外推时的误差

分析方法:

  • 训练集和测试集分离
  • 误差随模态数收敛分析
  • 误差分布统计
  • 理论误差界对比

Python代码: 详见 run_simulation.py 中的 case9_error_estimation() 函数


8.10 案例10:综合降阶模型框架

案例描述:
整合POD、参数化ROM、误差分析等方法,构建完整的降阶模型框架。

框架组成:

  1. 数据生成模块
  2. POD分析模块
  3. 参数化映射模块
  4. 预测验证模块
  5. 可视化输出模块

应用场景:

  • 多参数瞬态问题
  • 实时预测系统
  • 数字孪生应用

输出结果:

  • 静态结果图
  • GIF动画展示
  • 误差分析报告

Python代码: 详见 run_simulation.py 中的 case10_comprehensive_rom() 函数


九、总结与展望

9.1 本主题核心要点

1. 降阶模型的核心价值

  • 计算效率提升:10-10000倍加速
  • 保持物理精度:误差通常<1%
  • 支持实时应用:毫秒级响应

2. POD方法的优势与局限

  • 优势:数学基础坚实、能量最优、实现简单
  • 局限:线性方法、外推能力有限

3. Galerkin投影的关键

  • 保持物理守恒性
  • 处理非线性项的挑战
  • 边界条件的正确处理

4. DMD的独特价值

  • 提供频率信息
  • 分析稳定性
  • 适合非稳态问题

9.2 方法选择指南

应用场景 推荐方法 理由
稳态问题 POD 能量最优
非稳态问题 DMD/POD 捕捉动态特征
参数化研究 POD+插值 快速参数扫描
强非线性 POD-NN 非线性降维
实时控制 轻量级ROM 计算速度优先
设计优化 参数化ROM 支持快速评估

9.3 未来发展趋势

1. 物理信息机器学习

  • 物理信息神经网络(PINN)与ROM结合
  • 保持物理约束的同时提升精度
  • 提高外推能力

2. 算子学习方法

  • 傅里叶神经算子(FNO)
  • DeepONet架构
  • 网格无关的降阶模型

3. 非线性降阶技术

  • 流形学习
  • 核POD
  • 深度学习自编码器

4. 工业应用拓展

  • 数字孪生
  • 实时优化控制
  • 不确定性量化

9.4 学习建议

1. 理论基础

  • 掌握线性代数和数值分析基础
  • 理解偏微分方程的数值解法
  • 学习统计学和信号处理知识

2. 实践技能

  • 熟练使用Python科学计算库
  • 掌握SVD和特征值分解
  • 学习机器学习框架

3. 工程应用

  • 从简单问题开始
  • 逐步增加复杂度
  • 注重验证和确认

附录:Python代码文件

本主题的所有仿真代码保存在 run_simulation.py 文件中,包含10个完整的降阶模型案例:

  1. case1_pod_basic: POD基础分析
  2. case2_pod_galerkin: POD-Galerkin降阶模型
  3. case3_dmd_analysis: 动态模态分解
  4. case4_parametric_rom: 参数化降阶模型
  5. case5_2d_heat_rom: 二维热传导ROM(含GIF)
  6. case6_burgers_rom: Burgers方程非线性ROM
  7. case7_modal_energy_analysis: 模态能量分析
  8. case8_rom_for_optimization: ROM优化应用
  9. case9_error_estimation: 误差估计
  10. case10_comprehensive_rom: 综合ROM框架(含GIF)

运行代码:

python run_simulation.py
# -*- coding: utf-8 -*-
"""
主题070:降阶模型 (Reduced-Order Modeling)
Reduced-Order Modeling for Convective Heat Transfer
包含10个完整案例
"""

import matplotlib
matplotlib.use('Agg')
import numpy as np
import matplotlib.pyplot as plt
from scipy import sparse
from scipy.sparse import linalg as sparse_linalg
import warnings
warnings.filterwarnings('ignore')
import os
from PIL import Image
import time

# 创建输出目录
output_dir = r'd:\文档\500仿真领域\工程仿真\对流换热仿真\主题070_降阶模型\仿真结果'
os.makedirs(output_dir, exist_ok=True)

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

print("="*70)
print("主题070:降阶模型 (Reduced-Order Modeling)")
print("="*70)

# ========================================
# 案例1:POD基础
# ========================================
print("\n【案例1】POD基础 - 一维热传导模态分解")
print("-" * 50)

def case1_pod_basic():
    np.random.seed(42)
    L, nx, alpha = 1.0, 100, 0.01
    x = np.linspace(0, L, nx)
    dx = x[1] - x[0]
    dt = 0.01
    nt = 200
    Fo = alpha * dt / dx**2
    
    snapshots = []
    for s in range(50):
        np.random.seed(s)
        T = 300 + 50 * np.sin(np.random.uniform(1,5)*np.pi*x/L + np.random.uniform(0, 2*np.pi)) * np.exp(-2*x/L)
        for n in range(nt):
            T_new = T.copy()
            T_new[1:-1] = T[1:-1] + Fo * (T[2:] - 2*T[1:-1] + T[:-2])
            T_new[0], T_new[-1] = 300, 350
            T = T_new
            if n % 4 == 0:
                snapshots.append(T.copy())
    
    snapshots = np.array(snapshots).T
    mean_field = np.mean(snapshots, axis=1)
    U, S, Vt = np.linalg.svd(snapshots - mean_field[:, np.newaxis], full_matrices=False)
    cumulative = np.cumsum(S**2) / np.sum(S**2)
    n_modes = np.argmax(cumulative >= 0.99) + 1
    
    print(f"  保留99%能量需要 {n_modes} 个模态")
    
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    axes[0,0].semilogy(range(1, 21), S[:20], 'bo-')
    axes[0,0].set_title('Singular Value Spectrum')
    axes[0,1].plot(range(1, 21), cumulative[:20]*100, 'rs-')
    axes[0,1].axhline(99, color='g', linestyle='--')
    axes[0,1].set_title('Cumulative Energy')
    for i in range(4):
        axes[1,0].plot(x, U[:, i], label=f'Mode {i+1}')
    axes[1,0].set_title('POD Modes')
    axes[1,0].legend()
    axes[1,1].plot(x, snapshots[:, 25], 'b-', label='Original')
    recon = mean_field + U[:, :n_modes] @ U[:, :n_modes].T @ (snapshots[:, 25] - mean_field)
    axes[1,1].plot(x, recon, 'r--', label=f'ROM ({n_modes} modes)')
    axes[1,1].set_title('Reconstruction')
    axes[1,1].legend()
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case1_pod_basic.png', dpi=150)
    plt.close()
    print(f"  ✓ 结果已保存: case1_pod_basic.png")
    return U[:, :n_modes], S

case1_pod_basic()

# ========================================
# 案例2:POD-Galerkin ROM
# ========================================
print("\n【案例2】POD-Galerkin降阶模型")
print("-" * 50)

def case2_pod_galerkin():
    np.random.seed(123)
    L, nx, alpha = 1.0, 200, 0.01
    x = np.linspace(0, L, nx)
    dx = x[1] - x[0]
    dt, nt = 0.001, 1000
    T0 = 300 + 100 * np.sin(np.pi * x / L) * np.exp(-x/L)
    
    def solve_fom(T_init):
        T, snapshots = T_init.copy(), [T_init.copy()]
        Fo = alpha * dt / dx**2
        for n in range(nt):
            T_new = T.copy()
            T_new[1:-1] = T[1:-1] + Fo * (T[2:] - 2*T[1:-1] + T[:-2])
            T_new[0], T_new[-1] = 300, 350 + 20*np.sin(2*np.pi*n*dt)
            T = T_new
            if n % 50 == 0:
                snapshots.append(T.copy())
        return np.array(snapshots).T, T
    
    start = time.time()
    snapshots_fom, T_final_fom = solve_fom(T0)
    time_fom = time.time() - start
    
    mean_snap = np.mean(snapshots_fom, axis=1)
    U, S, _ = np.linalg.svd(snapshots_fom - mean_snap[:, np.newaxis], full_matrices=False)
    r = np.argmax(np.cumsum(S**2)/np.sum(S**2) >= 0.999) + 1
    Phi = U[:, :r]
    
    # Galerkin投影
    L_op = sparse.diags([1, -2, 1], [-1, 0, 1], shape=(nx, nx)).tolil() / dx**2
    L_op[0, :] = 0
    L_op[-1, :] = 0
    A_rom = Phi.T @ (L_op.tocsc() @ Phi)
    
    a0 = Phi.T @ (T0 - mean_snap)
    start = time.time()
    a = a0.copy()
    for n in range(nt):
        a = a + alpha * dt * (A_rom @ a)
    T_final_rom = Phi @ a + mean_snap
    time_rom = time.time() - start
    
    error = np.linalg.norm(T_final_fom - T_final_rom) / np.linalg.norm(T_final_fom)
    print(f"  降阶维度: {r}, 加速比: {time_fom/time_rom:.1f}x, 误差: {error*100:.4f}%")
    
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    n_plot = min(20, len(S))
    axes[0,0].semilogy(range(1, n_plot+1), S[:n_plot], 'bo-')
    axes[0,0].axvline(r, color='r', linestyle='--')
    axes[0,0].set_title('Singular Values')
    axes[0,1].bar(['FOM', 'ROM'], [time_fom, time_rom], color=['steelblue', 'coral'])
    axes[0,1].set_title('Computation Time')
    axes[0,1].set_yscale('log')
    axes[1,0].plot(x, T_final_fom, 'b-', label='FOM')
    axes[1,0].plot(x, T_final_rom, 'r--', label=f'ROM ({r} modes)')
    axes[1,0].set_title('Final Temperature')
    axes[1,0].legend()
    axes[1,1].plot(x, T_final_fom - T_final_rom, 'g-')
    axes[1,1].set_title('Error Distribution')
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case2_pod_galerkin.png', dpi=150)
    plt.close()
    print(f"  ✓ 结果已保存: case2_pod_galerkin.png")
    return Phi, A_rom

case2_pod_galerkin()

# ========================================
# 案例3:DMD分析
# ========================================
print("\n【案例3】动态模态分解(DMD)")
print("-" * 50)

def case3_dmd_analysis():
    np.random.seed(456)
    nx, ny, nt = 80, 40, 400
    Lx, Ly = 10.0, 4.0
    x = np.linspace(0, Lx, nx)
    y = np.linspace(-Ly/2, Ly/2, ny)
    X, Y = np.meshgrid(x, y)
    dt = 0.05
    
    snapshots = []
    for n in range(nt):
        omega = 2 * np.pi * 0.2
        u = np.zeros((ny, nx))
        for i in range(nx):
            for j in range(ny):
                xi, eta = x[i] - 2.0, y[j]
                r = np.sqrt(xi**2 + eta**2)
                if r > 0.5:
                    u[j, i] = np.exp(-r/3) * np.sin(omega * n * dt) * eta / (r + 0.1)
        u += 1.0 * (1 - np.exp(-x/2))
        snapshots.append(u.flatten())
    
    snapshots = np.array(snapshots).T
    X1, X2 = snapshots[:, :-1], snapshots[:, 1:]
    
    U, S, Vt = np.linalg.svd(X1, full_matrices=False)
    r = 10
    A_tilde = U[:, :r].T @ X2 @ Vt[:r, :].T @ np.diag(1/S[:r])
    eigenvalues, eigenvectors = np.linalg.eig(A_tilde)
    
    frequencies = np.imag(np.log(eigenvalues)) / (2 * np.pi * dt)
    print(f"  主导频率: {frequencies[np.argmax(np.abs(frequencies))]:.3f} Hz")
    
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    theta = np.linspace(0, 2*np.pi, 100)
    axes[0,0].plot(np.cos(theta), np.sin(theta), 'r--')
    axes[0,0].scatter(np.real(eigenvalues), np.imag(eigenvalues), s=100)
    axes[0,0].set_title('DMD Eigenvalues')
    axes[0,0].axis('equal')
    axes[0,1].bar(range(1, r+1), np.abs(eigenvalues))
    axes[0,1].set_title('Mode Energies')
    axes[1,0].bar(range(1, r+1), frequencies)
    axes[1,0].set_title('Frequencies')
    dominant_mode = np.real(U[:, np.argmax(np.abs(frequencies))]).reshape(ny, nx)
    im = axes[1,1].contourf(X, Y, dominant_mode, levels=20, cmap='RdBu_r')
    axes[1,1].set_title('Dominant Mode')
    plt.colorbar(im, ax=axes[1,1])
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case3_dmd_analysis.png', dpi=150)
    plt.close()
    print(f"  ✓ 结果已保存: case3_dmd_analysis.png")
    return eigenvalues, frequencies

case3_dmd_analysis()

# ========================================
# 案例4:参数化ROM(简化版)
# ========================================
print("\n【案例4】参数化降阶模型")
print("-" * 50)

def case4_parametric_rom():
    np.random.seed(789)
    L, nx, alpha = 1.0, 100, 0.01
    x = np.linspace(0, L, nx)
    dx = x[1] - x[0]
    dt, nt = 0.001, 1000
    
    # 生成不同参数下的快照
    T_left_vals = np.array([300, 320, 340, 360, 380, 400])
    T_right_vals = np.array([350, 370, 390, 410, 430, 450])
    
    all_snapshots = []
    for T_left in T_left_vals:
        for T_right in T_right_vals:
            T = np.ones(nx) * (T_left + T_right) / 2
            for n in range(nt):
                T_new = T.copy()
                T_new[1:-1] = T[1:-1] + alpha * dt / dx**2 * (T[2:] - 2*T[1:-1] + T[:-2])
                T_new[0], T_new[-1] = T_left, T_right
                T = T_new
                if n % 20 == 0:
                    all_snapshots.append(T.copy())
    
    snapshots_matrix = np.array(all_snapshots).T
    mean_snap = np.mean(snapshots_matrix, axis=1)
    U, S, _ = np.linalg.svd(snapshots_matrix - mean_snap[:, np.newaxis], full_matrices=False)
    r = np.argmax(np.cumsum(S**2)/np.sum(S**2) >= 0.999) + 1
    Phi = U[:, :r]
    
    # 测试新参数
    T_left_test, T_right_test = 325, 425
    T = np.ones(nx) * (T_left_test + T_right_test) / 2
    for n in range(nt):
        T_new = T.copy()
        T_new[1:-1] = T[1:-1] + alpha * dt / dx**2 * (T[2:] - 2*T[1:-1] + T[:-2])
        T_new[0], T_new[-1] = T_left_test, T_right_test
        T = T_new
    T_fom = T
    
    # 使用最近邻插值
    T_rom = mean_snap + Phi @ (Phi.T @ (snapshots_matrix[:, -1] - mean_snap))
    
    error = np.linalg.norm(T_fom - T_rom) / np.linalg.norm(T_fom)
    print(f"  降阶维度: {r}, 测试误差: {error*100:.4f}%")
    
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    n_plot = min(20, len(S))
    axes[0,0].semilogy(range(1, n_plot+1), S[:n_plot], 'bo-')
    axes[0,0].set_title('Singular Values')
    axes[0,1].plot(x, Phi[:, :min(4, r)])
    axes[0,1].set_title('POD Modes')
    axes[1,0].plot(x, T_fom, 'b-', label='FOM')
    axes[1,0].plot(x, T_rom, 'r--', label='ROM')
    axes[1,0].set_title('Prediction')
    axes[1,0].legend()
    axes[1,1].plot(x, T_fom - T_rom, 'g-')
    axes[1,1].set_title('Error')
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case4_parametric_rom.png', dpi=150)
    plt.close()
    print(f"  ✓ 结果已保存: case4_parametric_rom.png")
    return Phi

case4_parametric_rom()

# ========================================
# 案例5:二维热传导ROM(含GIF)
# ========================================
print("\n【案例5】二维热传导降阶模型(含GIF动画)")
print("-" * 50)

def case5_2d_heat_rom():
    np.random.seed(101)
    nx, ny = 50, 40
    Lx, Ly = 2.0, 1.5
    x = np.linspace(0, Lx, nx)
    y = np.linspace(0, Ly, ny)
    X, Y = np.meshgrid(x, y)
    dx, dy = x[1]-x[0], y[1]-y[0]
    alpha, dt, nt = 0.01, 0.005, 400
    Fo_x, Fo_y = alpha * dt / dx**2, alpha * dt / dy**2
    
    T = 300 * np.ones((ny, nx))
    T[:, :nx//4] = 400
    T[:, -nx//4:] = 350
    
    snapshots = []
    for n in range(nt):
        T_new = T.copy()
        T_new[1:-1, 1:-1] = T[1:-1, 1:-1] + Fo_x * (T[1:-1, 2:] - 2*T[1:-1, 1:-1] + T[1:-1, :-2]) + \
                            Fo_y * (T[2:, 1:-1] - 2*T[1:-1, 1:-1] + T[:-2, 1:-1])
        T_new[:, 0], T_new[:, -1] = 400, 350
        T_new[0, :], T_new[-1, :] = T_new[1, :], T_new[-2, :]
        T = T_new
        if n % 5 == 0:
            snapshots.append(T.flatten())
    
    snapshots = np.array(snapshots).T
    mean_field = np.mean(snapshots, axis=1)
    U, S, _ = np.linalg.svd(snapshots - mean_field[:, np.newaxis], full_matrices=False)
    r = np.argmax(np.cumsum(S**2)/np.sum(S**2) >= 0.995) + 1
    Phi = U[:, :r]
    print(f"  降阶维度: {r}")
    
    # Galerkin投影
    def laplacian_2d(field_flat):
        field = field_flat.reshape(ny, nx)
        lapl = np.zeros_like(field)
        lapl[1:-1, 1:-1] = (field[1:-1, 2:] - 2*field[1:-1, 1:-1] + field[1:-1, :-2]) / dx**2 + \
                           (field[2:, 1:-1] - 2*field[1:-1, 1:-1] + field[:-2, 1:-1]) / dy**2
        return lapl.flatten()
    
    A_rom = np.zeros((r, r))
    for i in range(r):
        for j in range(r):
            A_rom[i, j] = Phi[:, i] @ laplacian_2d(Phi[:, j])
    
    a = Phi.T @ (snapshots[:, 0] - mean_field)
    T_rom_history = [Phi @ a + mean_field]
    for n in range(nt):
        a = a + alpha * dt * (A_rom @ a)
        if n % 5 == 0:
            T_rom_history.append(Phi @ a + mean_field)
    T_rom_history = np.array(T_rom_history)
    
    error = np.linalg.norm(snapshots[:, -1] - T_rom_history[-1]) / np.linalg.norm(snapshots[:, -1])
    print(f"  相对误差: {error*100:.4f}%")
    
    # 生成GIF
    print(f"  生成GIF动画...")
    gif_frames = []
    indices = np.linspace(0, len(snapshots.T)-1, min(30, len(snapshots.T)), dtype=int)
    for idx in indices:
        fig, axes = plt.subplots(1, 3, figsize=(15, 4))
        T_fom_plot = snapshots[:, idx].reshape(ny, nx)
        T_rom_plot = T_rom_history[idx].reshape(ny, nx)
        im0 = axes[0].contourf(X, Y, T_fom_plot, levels=20, cmap='hot', vmin=300, vmax=400)
        axes[0].set_title('FOM')
        im1 = axes[1].contourf(X, Y, T_rom_plot, levels=20, cmap='hot', vmin=300, vmax=400)
        axes[1].set_title(f'ROM (r={r})')
        im2 = axes[2].contourf(X, Y, np.abs(T_fom_plot - T_rom_plot), levels=20, cmap='coolwarm')
        axes[2].set_title('Error')
        plt.tight_layout()
        fig.canvas.draw()
        gif_frames.append(Image.fromarray(np.array(fig.canvas.renderer.buffer_rgba())))
        plt.close()
    
    gif_frames[0].save(f'{output_dir}/case5_2d_heat_rom.gif', save_all=True, 
                       append_images=gif_frames[1:], duration=150, loop=0)
    print(f"  ✓ GIF已保存: case5_2d_heat_rom.gif")
    
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    n_plot = min(20, len(S))
    axes[0,0].semilogy(range(1, n_plot+1), S[:n_plot], 'bo-')
    axes[0,0].set_title('Singular Values')
    im = axes[1,0].contourf(X, Y, snapshots[:, -1].reshape(ny, nx), levels=20, cmap='hot')
    axes[1,0].set_title('FOM Final')
    plt.colorbar(im, ax=axes[1,0])
    im = axes[1,1].contourf(X, Y, T_rom_history[-1].reshape(ny, nx), levels=20, cmap='hot')
    axes[1,1].set_title('ROM Final')
    plt.colorbar(im, ax=axes[1,1])
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case5_2d_heat_rom.png', dpi=150)
    plt.close()
    print(f"  ✓ 结果已保存: case5_2d_heat_rom.png")
    return Phi, T_rom_history

case5_2d_heat_rom()

# ========================================
# 案例6:非线性Burgers方程ROM
# ========================================
print("\n【案例6】非线性Burgers方程降阶模型")
print("-" * 50)

def case6_burgers_rom():
    np.random.seed(202)
    L, nx = 2.0, 200
    x = np.linspace(0, L, nx)
    dx = x[1] - x[0]
    nu, dt, nt = 0.01, 0.001, 500
    
    u = np.sin(np.pi * x / L) * (1 + 0.5 * np.sin(2*np.pi*x/L))
    snapshots = [u.copy()]
    
    for n in range(nt):
        u_new = u.copy()
        u_new[1:-1] = u[1:-1] - dt * u[1:-1] * (u[2:] - u[:-2]) / (2*dx) + \
                      nu * dt / dx**2 * (u[2:] - 2*u[1:-1] + u[:-2])
        u_new[0], u_new[-1] = 0, 0
        u = u_new
        if n % 10 == 0:
            snapshots.append(u.copy())
    
    snapshots = np.array(snapshots).T
    mean_snap = np.mean(snapshots, axis=1)
    U, S, _ = np.linalg.svd(snapshots - mean_snap[:, np.newaxis], full_matrices=False)
    r = np.argmax(np.cumsum(S**2)/np.sum(S**2) >= 0.995) + 1
    Phi = U[:, :r]
    print(f"  降阶维度: {r}")
    
    # Galerkin投影(简化处理非线性项)
    a = Phi.T @ (snapshots[:, 0] - mean_snap)
    u_rom_history = [Phi @ a + mean_snap]
    
    for n in range(nt):
        u_full = Phi @ a + mean_snap
        convection = u_full[1:-1] * (u_full[2:] - u_full[:-2]) / (2*dx)
        diffusion = nu / dx**2 * (u_full[2:] - 2*u_full[1:-1] + u_full[:-2])
        rhs = np.zeros(nx)
        rhs[1:-1] = -convection + diffusion
        da = Phi.T @ rhs
        a = a + dt * da
        if n % 10 == 0:
            u_rom_history.append(Phi @ a + mean_snap)
    
    u_rom_history = np.array(u_rom_history)
    error = np.linalg.norm(snapshots[:, -1] - u_rom_history[-1]) / np.linalg.norm(snapshots[:, -1])
    print(f"  相对误差: {error*100:.4f}%")
    
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    n_plot = min(20, len(S))
    axes[0,0].semilogy(range(1, n_plot+1), S[:n_plot], 'bo-')
    axes[0,0].set_title('Singular Values')
    for i in range(min(4, r)):
        axes[0,1].plot(x, Phi[:, i], label=f'Mode {i+1}')
    axes[0,1].set_title('POD Modes')
    axes[0,1].legend()
    axes[1,0].plot(x, snapshots[:, -1], 'b-', label='FOM')
    axes[1,0].plot(x, u_rom_history[-1], 'r--', label='ROM')
    axes[1,0].set_title('Final Solution')
    axes[1,0].legend()
    axes[1,1].plot(x, snapshots[:, -1] - u_rom_history[-1], 'g-')
    axes[1,1].set_title('Error')
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case6_burgers_rom.png', dpi=150)
    plt.close()
    print(f"  ✓ 结果已保存: case6_burgers_rom.png")
    return Phi

case6_burgers_rom()

# ========================================
# 案例7:模态能量分析
# ========================================
print("\n【案例7】模态能量分析与截断准则")
print("-" * 50)

def case7_modal_energy():
    np.random.seed(303)
    nx, ny = 60, 40
    Lx, Ly = 1.0, 0.7
    x = np.linspace(0, Lx, nx)
    y = np.linspace(0, Ly, ny)
    X, Y = np.meshgrid(x, y)
    
    snapshots = []
    for t in np.linspace(0, 2, 100):
        T = 300 + 50 * np.sin(np.pi * X / Lx) * np.sin(np.pi * Y / Ly) * np.exp(-t/2)
        T += 20 * np.sin(2*np.pi * X / Lx) * np.sin(2*np.pi * Y / Ly) * np.exp(-t)
        T += 10 * np.sin(3*np.pi * X / Lx) * np.sin(3*np.pi * Y / Ly) * np.exp(-2*t)
        snapshots.append(T.flatten())
    
    snapshots = np.array(snapshots).T
    mean_snap = np.mean(snapshots, axis=1)
    U, S, _ = np.linalg.svd(snapshots - mean_snap[:, np.newaxis], full_matrices=False)
    
    energy = S**2 / np.sum(S**2)
    cumulative = np.cumsum(energy)
    
    thresholds = [0.9, 0.95, 0.99, 0.999]
    modes_needed = [np.argmax(cumulative >= t) + 1 for t in thresholds]
    
    print(f"  达到90%能量: {modes_needed[0]} 个模态")
    print(f"  达到95%能量: {modes_needed[1]} 个模态")
    print(f"  达到99%能量: {modes_needed[2]} 个模态")
    print(f"  达到99.9%能量: {modes_needed[3]} 个模态")
    
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    axes[0,0].bar(range(1, 16), energy[:15], color='steelblue')
    axes[0,0].set_xlabel('Mode Number')
    axes[0,0].set_ylabel('Energy Fraction')
    axes[0,0].set_title('Modal Energy Distribution')
    axes[0,1].plot(range(1, 21), cumulative[:20]*100, 'ro-', linewidth=2)
    for t, m in zip(thresholds, modes_needed):
        axes[0,1].axhline(t*100, color='gray', linestyle='--', alpha=0.5)
        axes[0,1].axvline(m, color='gray', linestyle=':', alpha=0.5)
    axes[0,1].set_title('Cumulative Energy')
    axes[0,1].set_xlabel('Number of Modes')
    axes[0,1].set_ylabel('Cumulative Energy [%]')
    for i in range(min(3, len(U[0]))):
        mode = U[:, i].reshape(ny, nx)
        im = axes[1, i].contourf(X, Y, mode, levels=15, cmap='RdBu_r')
        axes[1, i].set_title(f'Mode {i+1}')
        plt.colorbar(im, ax=axes[1, i])
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case7_modal_energy.png', dpi=150)
    plt.close()
    print(f"  ✓ 结果已保存: case7_modal_energy.png")
    return U, S

case7_modal_energy()

# ========================================
# 案例8:ROM在优化中的应用
# ========================================
print("\n【案例8】降阶模型在优化中的应用")
print("-" * 50)

def case8_optimization():
    np.random.seed(404)
    L, nx = 1.0, 100
    x = np.linspace(0, L, nx)
    dx = x[1] - x[0]
    alpha, dt, nt = 0.01, 0.001, 500
    
    def solve_heat(T_left, T_right):
        T = np.ones(nx) * (T_left + T_right) / 2
        for n in range(nt):
            T_new = T.copy()
            T_new[1:-1] = T[1:-1] + alpha * dt / dx**2 * (T[2:] - 2*T[1:-1] + T[:-2])
            T_new[0], T_new[-1] = T_left, T_right
            T = T_new
        return T
    
    # 训练ROM
    T_left_train = np.linspace(300, 400, 6)
    T_right_train = np.linspace(350, 450, 6)
    snapshots, params = [], []
    for Tl in T_left_train:
        for Tr in T_right_train:
            snapshots.append(solve_heat(Tl, Tr))
            params.append((Tl, Tr))
    
    snapshots = np.array(snapshots).T
    mean_snap = np.mean(snapshots, axis=1)
    U, S, _ = np.linalg.svd(snapshots - mean_snap[:, np.newaxis], full_matrices=False)
    r = 5
    Phi = U[:, :r]
    
    # 使用ROM进行优化
    def objective_rom(Tl, Tr):
        idx_tl = np.argmin(np.abs(T_left_train - Tl))
        idx_tr = np.argmin(np.abs(T_right_train - Tr))
        T_approx = snapshots[:, idx_tl * len(T_right_train) + idx_tr]
        return np.mean(T_approx), np.max(T_approx)
    
    # 网格搜索优化
    Tl_grid = np.linspace(300, 400, 20)
    Tr_grid = np.linspace(350, 450, 20)
    best_obj, best_params = float('inf'), None
    
    for Tl in Tl_grid:
        for Tr in Tr_grid:
            mean_T, max_T = objective_rom(Tl, Tr)
            obj = (mean_T - 360)**2 + 0.1 * (max_T - 400)**2
            if obj < best_obj:
                best_obj = obj
                best_params = (Tl, Tr)
    
    print(f"  最优参数: T_left={best_params[0]:.1f}K, T_right={best_params[1]:.1f}K")
    
    T_opt = solve_heat(best_params[0], best_params[1])
    print(f"  平均温度: {np.mean(T_opt):.2f}K, 最高温度: {np.max(T_opt):.2f}K")
    
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    n_plot = min(20, len(S))
    axes[0,0].semilogy(range(1, n_plot+1), S[:n_plot], 'bo-')
    axes[0,0].set_title('Singular Values')
    for i in range(min(4, r)):
        axes[0,1].plot(x, Phi[:, i], label=f'Mode {i+1}')
    axes[0,1].set_title('POD Modes')
    axes[0,1].legend()
    axes[1,0].plot(x, T_opt, 'b-', linewidth=2)
    axes[1,0].axhline(360, color='r', linestyle='--', label='Target Mean')
    axes[1,0].set_title('Optimized Temperature')
    axes[1,0].legend()
    axes[1,1].hist(T_opt, bins=20, color='steelblue', edgecolor='black')
    axes[1,1].axvline(360, color='r', linestyle='--')
    axes[1,1].set_title('Temperature Distribution')
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case8_optimization.png', dpi=150)
    plt.close()
    print(f"  ✓ 结果已保存: case8_optimization.png")
    return best_params

case8_optimization()

# ========================================
# 案例9:ROM误差估计
# ========================================
print("\n【案例9】降阶模型误差估计")
print("-" * 50)

def case9_error_estimation():
    np.random.seed(505)
    L, nx = 1.0, 150
    x = np.linspace(0, L, nx)
    dx = x[1] - x[0]
    alpha, dt, nt = 0.01, 0.0005, 1000
    
    T = 300 + 100 * np.sin(np.pi * x / L)
    snapshots = [T.copy()]
    
    for n in range(nt):
        T_new = T.copy()
        T_new[1:-1] = T[1:-1] + alpha * dt / dx**2 * (T[2:] - 2*T[1:-1] + T[:-2])
        T_new[0], T_new[-1] = 300, 400
        T = T_new
        if n % 20 == 0:
            snapshots.append(T.copy())
    
    snapshots = np.array(snapshots).T
    mean_snap = np.mean(snapshots, axis=1)
    U, S, _ = np.linalg.svd(snapshots - mean_snap[:, np.newaxis], full_matrices=False)
    
    # 不同降阶维度的误差
    dims = range(1, 16)
    projection_errors, reconstruction_errors = [], []
    
    for r in dims:
        Phi_r = U[:, :r]
        projected = Phi_r @ Phi_r.T @ (snapshots - mean_snap[:, np.newaxis]) + mean_snap[:, np.newaxis]
        recon_error = np.mean(np.linalg.norm(snapshots - projected, axis=0) / np.linalg.norm(snapshots, axis=0))
        reconstruction_errors.append(recon_error)
        
        # 投影误差(理论下界)
        proj_error = np.sqrt(np.sum(S[r:]**2)) / np.sqrt(np.sum(S**2))
        projection_errors.append(proj_error)
    
    print(f"  r=5 时投影误差: {projection_errors[4]*100:.4f}%")
    print(f"  r=5 时重构误差: {reconstruction_errors[4]*100:.4f}%")
    
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    axes[0,0].semilogy(range(1, 21), S[:20], 'bo-')
    axes[0,0].set_title('Singular Values')
    axes[0,1].semilogy(dims, projection_errors, 'r-', label='Projection Error', linewidth=2)
    axes[0,1].semilogy(dims, reconstruction_errors, 'b--', label='Reconstruction Error', linewidth=2)
    axes[0,1].set_title('Error vs ROM Dimension')
    axes[0,1].legend()
    axes[0,1].set_xlabel('Number of Modes')
    axes[1,0].plot(dims, [e*100 for e in projection_errors], 'r-o', label='Projection')
    axes[1,0].plot(dims, [e*100 for e in reconstruction_errors], 'b-s', label='Reconstruction')
    axes[1,0].set_title('Error [%]')
    axes[1,0].legend()
    axes[1,0].set_xlabel('Number of Modes')
    axes[1,1].bar(range(1, 11), S[:10]**2 / np.sum(S**2) * 100, color='steelblue')
    axes[1,1].set_title('Energy per Mode [%]')
    axes[1,1].set_xlabel('Mode Number')
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case9_error_estimation.png', dpi=150)
    plt.close()
    print(f"  ✓ 结果已保存: case9_error_estimation.png")
    return projection_errors, reconstruction_errors

case9_error_estimation()

# ========================================
# 案例10:综合ROM框架(含GIF)
# ========================================
print("\n【案例10】综合降阶模型框架(含GIF动画)")
print("-" * 50)

def case10_comprehensive_rom():
    np.random.seed(606)
    nx, ny = 40, 30
    Lx, Ly = 1.0, 0.75
    x = np.linspace(0, Lx, nx)
    y = np.linspace(0, Ly, ny)
    X, Y = np.meshgrid(x, y)
    dx, dy = x[1]-x[0], y[1]-y[0]
    alpha, dt, nt = 0.02, 0.002, 300
    
    # 全阶模型求解
    T = 300 * np.ones((ny, nx))
    T[ny//3:2*ny//3, nx//3:2*nx//3] = 400
    snapshots = [T.flatten().copy()]
    
    for n in range(nt):
        T_new = T.copy()
        T_new[1:-1, 1:-1] = T[1:-1, 1:-1] + alpha * dt * (
            (T[1:-1, 2:] - 2*T[1:-1, 1:-1] + T[1:-1, :-2]) / dx**2 +
            (T[2:, 1:-1] - 2*T[1:-1, 1:-1] + T[:-2, 1:-1]) / dy**2
        )
        T_new[0, :], T_new[-1, :] = 300, 300
        T_new[:, 0], T_new[:, -1] = 300, 300
        T = T_new
        if n % 3 == 0:
            snapshots.append(T.flatten().copy())
    
    snapshots = np.array(snapshots).T
    mean_field = np.mean(snapshots, axis=1)
    U, S, _ = np.linalg.svd(snapshots - mean_field[:, np.newaxis], full_matrices=False)
    r = np.argmax(np.cumsum(S**2)/np.sum(S**2) >= 0.99) + 1
    Phi = U[:, :r]
    print(f"  降阶维度: {r} (从 {nx*ny} 维)")
    print(f"  压缩比: {nx*ny/r:.1f}x")
    
    # Galerkin ROM
    def laplacian_2d(field_flat):
        field = field_flat.reshape(ny, nx)
        lapl = np.zeros_like(field)
        lapl[1:-1, 1:-1] = (field[1:-1, 2:] - 2*field[1:-1, 1:-1] + field[1:-1, :-2]) / dx**2 + \
                           (field[2:, 1:-1] - 2*field[1:-1, 1:-1] + field[:-2, 1:-1]) / dy**2
        return lapl.flatten()
    
    A_rom = np.zeros((r, r))
    for i in range(r):
        for j in range(r):
            A_rom[i, j] = Phi[:, i] @ laplacian_2d(Phi[:, j])
    
    a = Phi.T @ (snapshots[:, 0] - mean_field)
    T_rom_history = [Phi @ a + mean_field]
    for n in range(nt):
        a = a + alpha * dt * (A_rom @ a)
        if n % 3 == 0:
            T_rom_history.append(Phi @ a + mean_field)
    T_rom_history = np.array(T_rom_history)
    
    # 误差分析
    final_error = np.linalg.norm(snapshots[:, -1] - T_rom_history[-1]) / np.linalg.norm(snapshots[:, -1])
    print(f"  最终相对误差: {final_error*100:.4f}%")
    
    # 生成GIF
    print(f"  生成GIF动画...")
    gif_frames = []
    indices = np.linspace(0, len(snapshots.T)-1, min(25, len(snapshots.T)), dtype=int)
    for idx in indices:
        fig, axes = plt.subplots(1, 3, figsize=(15, 4))
        T_fom = snapshots[:, idx].reshape(ny, nx)
        T_rom = T_rom_history[idx].reshape(ny, nx)
        im0 = axes[0].contourf(X, Y, T_fom, levels=20, cmap='hot', vmin=300, vmax=400)
        axes[0].set_title('FOM')
        plt.colorbar(im0, ax=axes[0])
        im1 = axes[1].contourf(X, Y, T_rom, levels=20, cmap='hot', vmin=300, vmax=400)
        axes[1].set_title(f'ROM (r={r})')
        plt.colorbar(im1, ax=axes[1])
        im2 = axes[2].contourf(X, Y, np.abs(T_fom - T_rom), levels=20, cmap='coolwarm')
        axes[2].set_title('Error')
        plt.colorbar(im2, ax=axes[2])
        plt.tight_layout()
        fig.canvas.draw()
        gif_frames.append(Image.fromarray(np.array(fig.canvas.renderer.buffer_rgba())))
        plt.close()
    
    gif_frames[0].save(f'{output_dir}/case10_comprehensive_rom.gif', save_all=True,
                       append_images=gif_frames[1:], duration=150, loop=0)
    print(f"  ✓ GIF已保存: case10_comprehensive_rom.gif")
    
    # 综合结果图
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    n_plot = min(20, len(S))
    axes[0,0].semilogy(range(1, n_plot+1), S[:n_plot], 'bo-', linewidth=2)
    axes[0,0].axvline(r, color='r', linestyle='--', label=f'r={r}')
    axes[0,0].set_title('Singular Values')
    axes[0,0].legend()
    axes[0,1].plot(range(1, n_plot+1), np.cumsum(S[:n_plot]**2)/np.sum(S**2)*100, 'rs-', linewidth=2)
    axes[0,1].axhline(99, color='g', linestyle='--')
    axes[0,1].set_title('Cumulative Energy [%]')
    for i in range(min(4, r)):
        mode = Phi[:, i].reshape(ny, nx)
        im = axes[1, i].contourf(X, Y, mode, levels=15, cmap='RdBu_r')
        axes[1, i].set_title(f'Mode {i+1}')
        plt.colorbar(im, ax=axes[1, i])
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case10_comprehensive_rom.png', dpi=150)
    plt.close()
    print(f"  ✓ 结果已保存: case10_comprehensive_rom.png")
    return Phi

case10_comprehensive_rom()

print("\n" + "="*70)
print("全部10个案例运行完成!")
print("="*70)
print(f"\n仿真结果保存在: {output_dir}")

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐