第八十篇:多物理场模型降阶

摘要

多物理场模型降阶(Model Order Reduction, MOR)是处理高维复杂多物理场系统仿真的关键技术。随着现代工程系统复杂度的不断提升,完整的高保真度多物理场仿真往往涉及数百万甚至上千万自由度,导致计算成本极高,难以满足实时仿真、参数优化、不确定性量化等应用需求。本文系统阐述多物理场模型降阶的理论基础、核心方法与实现技术,重点介绍本征正交分解(POD)、平衡截断、Krylov子空间方法等线性降阶技术,以及本征正交分解-伽辽金(POD-Galerkin)方法、离散经验插值方法(DEIM)等非线性降阶技术。通过热-结构耦合、流-固耦合等典型案例,详细展示模型降阶的完整流程,包括快照采集、基函数构建、降阶系统生成和误差控制等关键环节。本文还探讨了参数化降阶、在线-离线分解策略、物理信息保持等前沿技术,为工程实践中高效、准确的多物理场仿真提供理论指导和技术支持。

关键词

模型降阶,本征正交分解,POD-Galerkin方法,平衡截断,离散经验插值,参数化降阶,在线-离线分解,多物理场耦合


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

1. 引言

1.1 多物理场仿真的计算挑战

现代工程系统普遍涉及多个物理场的相互作用,如航空航天器的气动加热、核反应堆的热工水力、微电子器件的电热耦合等。这些多物理场系统的精确仿真通常需要求解大规模的偏微分方程组,离散后产生的代数系统往往包含数百万乃至上亿个自由度。以典型的流-固耦合问题为例,精细的CFD网格可能包含1000万以上的网格单元,每个时间步的求解需要数小时甚至更长时间。

这种高计算成本严重制约了多物理场仿真在以下应用场景中的实用性:

实时仿真与数字孪生:工业4.0和数字孪生技术要求物理系统的虚拟模型能够实时响应实际系统的状态变化,为预测性维护和在线优化提供支持。完整的高保真度模型无法满足实时性要求。

参数优化与设计空间探索:工程优化通常需要在设计空间中进行成百上千次仿真评估。如果每次仿真耗时数小时,整体优化过程将变得不可接受。

不确定性量化:考虑材料参数、边界条件等不确定性时,需要大量的蒙特卡洛采样或谱方法展开,这进一步放大了计算需求。

多尺度与多物理场耦合:不同物理场可能具有显著不同的时间尺度和空间尺度,统一求解需要极小的时间步长和极密的网格,计算代价巨大。

1.2 模型降阶的基本思想

模型降阶的核心思想是在保持系统主要动力学特性的前提下,将高维复杂模型投影到低维子空间,从而显著降低计算复杂度。数学上,考虑一个由偏微分方程描述的多物理场系统:

∂u∂t=L(u;μ)\frac{\partial \mathbf{u}}{\partial t} = \mathcal{L}(\mathbf{u}; \boldsymbol{\mu})tu=L(u;μ)

其中 u(x,t)∈RN\mathbf{u}(\mathbf{x}, t) \in \mathbb{R}^Nu(x,t)RN 是状态变量,NNN 是自由度数目(通常很大),L\mathcal{L}L 是微分算子,μ\boldsymbol{\mu}μ 是参数向量。

经过空间离散后,得到高维常微分方程组:

Mdudt=f(u,t;μ)\mathbf{M}\frac{d\mathbf{u}}{dt} = \mathbf{f}(\mathbf{u}, t; \boldsymbol{\mu})Mdtdu=f(u,t;μ)

其中 M∈RN×N\mathbf{M} \in \mathbb{R}^{N \times N}MRN×N 是质量矩阵,f\mathbf{f}f 是离散后的非线性函数。

模型降阶假设解 u\mathbf{u}u 可以近似表示为低维基函数的线性组合:

u(t)≈∑i=1nai(t)ϕi=Φa(t)\mathbf{u}(t) \approx \sum_{i=1}^{n} a_i(t) \boldsymbol{\phi}_i = \boldsymbol{\Phi}\mathbf{a}(t)u(t)i=1nai(t)ϕi=Φa(t)

其中 Φ=[ϕ1,ϕ2,…,ϕn]∈RN×n\boldsymbol{\Phi} = [\boldsymbol{\phi}_1, \boldsymbol{\phi}_2, \ldots, \boldsymbol{\phi}_n] \in \mathbb{R}^{N \times n}Φ=[ϕ1,ϕ2,,ϕn]RN×n 是降阶基矩阵,a(t)∈Rn\mathbf{a}(t) \in \mathbb{R}^na(t)Rn 是模态系数向量,n≪Nn \ll NnN 是降阶维度。

将上述近似代入原方程,并在适当的子空间上进行投影,得到低维降阶系统:

M~dadt=f~(a,t;μ)\tilde{\mathbf{M}}\frac{d\mathbf{a}}{dt} = \tilde{\mathbf{f}}(\mathbf{a}, t; \boldsymbol{\mu})M~dtda=f~(a,t;μ)

其中 M~=ΦTMΦ∈Rn×n\tilde{\mathbf{M}} = \boldsymbol{\Phi}^T\mathbf{M}\boldsymbol{\Phi} \in \mathbb{R}^{n \times n}M~=ΦTMΦRn×nf~=ΦTf(Φa,t;μ)\tilde{\mathbf{f}} = \boldsymbol{\Phi}^T\mathbf{f}(\boldsymbol{\Phi}\mathbf{a}, t; \boldsymbol{\mu})f~=ΦTf(Φa,t;μ)

降阶后的系统维度从 NNN 降低到 nnn,计算复杂度显著降低。如果 nnn 足够小(通常几十到几百),降阶模型可以在毫秒到秒级时间内完成仿真,满足实时应用需求。

1.3 模型降阶方法分类

根据处理对象和数学原理的不同,模型降阶方法可以分为以下几类:

基于数据的降阶方法

  • 本征正交分解(Proper Orthogonal Decomposition, POD)
  • 动态模态分解(Dynamic Mode Decomposition, DMD)
  • 核主成分分析(Kernel PCA)
  • 自动编码器(Autoencoder)等机器学习方法

基于系统的降阶方法

  • 平衡截断(Balanced Truncation)
  • 最优Hankel范数近似
  • Krylov子空间方法(矩匹配)

非线性降阶方法

  • POD-Galerkin方法
  • 离散经验插值方法(DEIM)
  • 高斯过程回归降阶
  • 物理信息神经网络(PINN)

参数化降阶方法

  • 全局POD(Global POD)
  • 插值型降阶基
  • 贪婪算法
  • 高斯过程超降阶(Hyper-reduction)

1.4 多物理场模型降阶的特殊性

多物理场系统的模型降阶面临一些独特的挑战:

多变量耦合:多物理场系统涉及多个物理变量(如温度、压力、位移、电场等),这些变量可能具有不同的量纲和变化范围,需要协调处理。

不同时间尺度:不同物理场可能具有显著不同的时间尺度(如结构振动的毫秒级与热传导的分钟级),这要求降阶模型能够处理多时间尺度问题。

界面耦合条件:多物理场系统通过界面耦合条件相互作用,降阶过程需要保持这些界面条件的准确性。

强非线性:许多多物理场问题(如湍流、塑性变形、相变等)具有强非线性,线性降阶方法可能失效,需要发展非线性降阶技术。

参数依赖性:实际工程问题通常涉及多个设计参数和工况参数,降阶模型需要在参数空间内具有良好的泛化能力。

本文将系统介绍多物理场模型降阶的理论基础、核心方法和实现技术,并通过具体案例展示其应用。


2. 本征正交分解(POD)方法

2.1 POD的数学基础

本征正交分解(Proper Orthogonal Decomposition, POD),又称Karhunen-Loève展开或主成分分析(PCA),是一种从数据中提取主要特征的统计方法。POD的核心思想是找到一组正交基函数,使得数据在这组基上的投影方差最大化。

给定一组快照数据(snapshots){u1,u2,…,uK}\{\mathbf{u}_1, \mathbf{u}_2, \ldots, \mathbf{u}_K\}{u1,u2,,uK},其中 uk∈RN\mathbf{u}_k \in \mathbb{R}^NukRNKKK 是快照数目。POD寻找最优的 nnn 维基函数 {ϕi}i=1n\{\boldsymbol{\phi}_i\}_{i=1}^n{ϕi}i=1n,使得投影误差最小:

min⁡{ϕi}∑k=1K∥uk−∑i=1n(ukTϕi)ϕi∥2\min_{\{\boldsymbol{\phi}_i\}} \sum_{k=1}^{K} \left\|\mathbf{u}_k - \sum_{i=1}^{n} (\mathbf{u}_k^T\boldsymbol{\phi}_i)\boldsymbol{\phi}_i\right\|^2{ϕi}mink=1K uki=1n(ukTϕi)ϕi 2

subject to ϕiTϕj=δij\boldsymbol{\phi}_i^T\boldsymbol{\phi}_j = \delta_{ij}ϕiTϕj=δij

这个问题等价于求解相关矩阵的特征值问题:

Cϕ=λϕ\mathbf{C}\boldsymbol{\phi} = \lambda\boldsymbol{\phi}Cϕ=λϕ

其中相关矩阵 C∈RN×N\mathbf{C} \in \mathbb{R}^{N \times N}CRN×N 定义为:

C=1K∑k=1KukukT=1KUUT\mathbf{C} = \frac{1}{K}\sum_{k=1}^{K} \mathbf{u}_k \mathbf{u}_k^T = \frac{1}{K}\mathbf{U}\mathbf{U}^TC=K1k=1KukukT=K1UUT

这里 U=[u1,u2,…,uK]∈RN×K\mathbf{U} = [\mathbf{u}_1, \mathbf{u}_2, \ldots, \mathbf{u}_K] \in \mathbb{R}^{N \times K}U=[u1,u2,,uK]RN×K 是快照矩阵。

N≫KN \gg KNK 时,直接求解 N×NN \times NN×N 特征值问题计算代价过高。利用矩阵 UUT\mathbf{U}\mathbf{U}^TUUTUTU\mathbf{U}^T\mathbf{U}UTU 具有相同非零特征值的性质,可以转而求解 K×KK \times KK×K 的特征值问题:

UTUv=λv\mathbf{U}^T\mathbf{U}\mathbf{v} = \lambda\mathbf{v}UTUv=λv

然后POD基函数可以通过下式计算:

ϕi=1λiUvi\boldsymbol{\phi}_i = \frac{1}{\sqrt{\lambda_i}}\mathbf{U}\mathbf{v}_iϕi=λi 1Uvi

这种方法称为"方法 of snapshots",计算复杂度从 O(N3)O(N^3)O(N3) 降低到 O(K3)O(K^3)O(K3),其中通常 K≪NK \ll NKN

2.2 POD基的能量含量

POD特征值 λi\lambda_iλi 表示对应模态在数据中的能量含量。定义累积能量比:

E(n)=∑i=1nλi∑i=1KλiE(n) = \frac{\sum_{i=1}^{n} \lambda_i}{\sum_{i=1}^{K} \lambda_i}E(n)=i=1Kλii=1nλi

通常选择 nnn 使得 E(n)≥99%E(n) \geq 99\%E(n)99%99.9%99.9\%99.9%,以确保降阶模型能够捕捉系统的主要动力学特性。

对于具有快速衰减特征谱的系统(如扩散主导问题),少量POD模态就能达到很高的能量含量;而对于具有缓慢衰减特征谱的系统(如对流主导问题或波动问题),可能需要更多的模态。

2.3 快照采集策略

POD基的质量高度依赖于快照数据的代表性和完备性。快照采集策略包括:

时间采样:对于瞬态问题,需要在时间域上均匀或自适应地采集快照。均匀采样简单易行,但可能在高梯度区域采样不足;自适应采样根据解的变化率动态调整采样密度。

参数采样:对于参数化问题,需要在参数空间中进行采样。常用的策略包括:

  • 均匀网格采样:适用于低维参数空间
  • 拉丁超立方采样(LHS):提供均匀且随机的参数覆盖
  • 贪婪采样:自适应地选择最能改进降阶模型的参数点
  • 高斯过程采样:基于不确定性估计进行自适应采样

空间采样:对于分布式参数系统,可能需要在空间域上也进行采样。

2.4 POD的优缺点

优点

  • 数学基础坚实,具有最优性保证
  • 实现简单,计算效率高
  • 基函数具有明确的物理意义
  • 适用于线性和非线性问题

缺点

  • 需要预先计算高维快照,离线成本较高
  • 对于参数化问题,需要在整个参数空间采集快照
  • 对于强非线性问题,POD-Galerkin投影可能计算代价高
  • 无法保证降阶系统的稳定性

3. POD-Galerkin降阶方法

3.1 Galerkin投影原理

POD-Galerkin方法将POD基函数与Galerkin投影相结合,构建降阶系统。考虑一般形式的非线性系统:

Mdudt+Au+f(u)=g(t)\mathbf{M}\frac{d\mathbf{u}}{dt} + \mathbf{A}\mathbf{u} + \mathbf{f}(\mathbf{u}) = \mathbf{g}(t)Mdtdu+Au+f(u)=g(t)

其中 M\mathbf{M}M 是质量矩阵,A\mathbf{A}A 是刚度矩阵,f\mathbf{f}f 是非线性项,g\mathbf{g}g 是外力项。

将POD近似 u≈Φa\mathbf{u} \approx \boldsymbol{\Phi}\mathbf{a}uΦa 代入方程,并在POD基张成的子空间上进行Galerkin投影(即左乘 ΦT\boldsymbol{\Phi}^TΦT),得到降阶系统:

M~dadt+A~a+f~(a)=g~(t)\tilde{\mathbf{M}}\frac{d\mathbf{a}}{dt} + \tilde{\mathbf{A}}\mathbf{a} + \tilde{\mathbf{f}}(\mathbf{a}) = \tilde{\mathbf{g}}(t)M~dtda+A~a+f~(a)=g~(t)

其中:

  • M~=ΦTMΦ∈Rn×n\tilde{\mathbf{M}} = \boldsymbol{\Phi}^T\mathbf{M}\boldsymbol{\Phi} \in \mathbb{R}^{n \times n}M~=ΦTMΦRn×n
  • A~=ΦTAΦ∈Rn×n\tilde{\mathbf{A}} = \boldsymbol{\Phi}^T\mathbf{A}\boldsymbol{\Phi} \in \mathbb{R}^{n \times n}A~=ΦTAΦRn×n
  • f~(a)=ΦTf(Φa)∈Rn\tilde{\mathbf{f}}(\mathbf{a}) = \boldsymbol{\Phi}^T\mathbf{f}(\boldsymbol{\Phi}\mathbf{a}) \in \mathbb{R}^{n}f~(a)=ΦTf(Φa)Rn
  • g~(t)=ΦTg(t)∈Rn\tilde{\mathbf{g}}(t) = \boldsymbol{\Phi}^T\mathbf{g}(t) \in \mathbb{R}^{n}g~(t)=ΦTg(t)Rn

3.2 线性项的降阶

对于线性项 Au\mathbf{A}\mathbf{u}Au,降阶后的矩阵 A~\tilde{\mathbf{A}}A~ 可以在离线阶段一次性计算并存储。由于 n≪Nn \ll NnN,存储和操作 A~\tilde{\mathbf{A}}A~ 的代价很低。

类似地,质量矩阵的降阶 M~\tilde{\mathbf{M}}M~ 也在离线阶段计算。如果 M\mathbf{M}M 是对角矩阵(如集中质量矩阵),计算更为简单。

3.3 非线性项的处理

非线性项 f~(a)=ΦTf(Φa)\tilde{\mathbf{f}}(\mathbf{a}) = \boldsymbol{\Phi}^T\mathbf{f}(\boldsymbol{\Phi}\mathbf{a})f~(a)=ΦTf(Φa) 是POD-Galerkin方法的主要计算瓶颈。直接计算需要在每个时间步将模态系数 a\mathbf{a}a 扩展到全维空间(O(nN)O(nN)O(nN) 操作),计算非线性函数(通常 O(N)O(N)O(N)O(Nlog⁡N)O(N\log N)O(NlogN)),再投影回降阶空间(O(nN)O(nN)O(nN) 操作)。

这种"扩展-计算-投影"过程使得在线计算复杂度仍然依赖于 NNN,破坏了降阶的效率优势。为了解决这个问题,发展了多种非线性降阶技术:

离散经验插值方法(DEIM)
DEIM通过选择少量的插值点来近似非线性项。其核心思想是:如果非线性项 f(u)\mathbf{f}(\mathbf{u})f(u) 本身也具有低维结构,可以用其自身的POD基 Ψ\boldsymbol{\Psi}Ψ 来近似:

f(u)≈Ψc(u)\mathbf{f}(\mathbf{u}) \approx \boldsymbol{\Psi}\mathbf{c}(\mathbf{u})f(u)Ψc(u)

其中 Ψ∈RN×m\boldsymbol{\Psi} \in \mathbb{R}^{N \times m}ΨRN×m 是非线性项的POD基,m≪Nm \ll NmN

DEIM选择 mmm 个插值点,使得可以通过这些点上的非线性函数值唯一确定系数 c\mathbf{c}c。定义选择矩阵 P∈RN×m\mathbf{P} \in \mathbb{R}^{N \times m}PRN×m,其每列只有一个非零元素(值为1),对应一个插值点。

则系数可以通过下式计算:

c(u)=(PTΨ)−1PTf(u)\mathbf{c}(\mathbf{u}) = (\mathbf{P}^T\boldsymbol{\Psi})^{-1}\mathbf{P}^T\mathbf{f}(\mathbf{u})c(u)=(PTΨ)1PTf(u)

DEIM近似为:

f(u)≈Ψ(PTΨ)−1PTf(u)\mathbf{f}(\mathbf{u}) \approx \boldsymbol{\Psi}(\mathbf{P}^T\boldsymbol{\Psi})^{-1}\mathbf{P}^T\mathbf{f}(\mathbf{u})f(u)Ψ(PTΨ)1PTf(u)

降阶非线性项变为:

f~(a)≈ΦTΨ(PTΨ)−1PTf(Φa)\tilde{\mathbf{f}}(\mathbf{a}) \approx \boldsymbol{\Phi}^T\boldsymbol{\Psi}(\mathbf{P}^T\boldsymbol{\Psi})^{-1}\mathbf{P}^T\mathbf{f}(\boldsymbol{\Phi}\mathbf{a})f~(a)ΦTΨ(PTΨ)1PTf(Φa)

注意到 PTf(Φa)\mathbf{P}^T\mathbf{f}(\boldsymbol{\Phi}\mathbf{a})PTf(Φa) 只需要在 mmm 个插值点上计算非线性函数,计算复杂度为 O(m)O(m)O(m) 而非 O(N)O(N)O(N)

高斯牛顿近似
对于弱非线性问题,可以在参考状态附近线性化非线性项:

f(u)≈f(u0)+Jf(u0)(u−u0)\mathbf{f}(\mathbf{u}) \approx \mathbf{f}(\mathbf{u}_0) + \mathbf{J}_f(\mathbf{u}_0)(\mathbf{u} - \mathbf{u}_0)f(u)f(u0)+Jf(u0)(uu0)

其中 Jf\mathbf{J}_fJf 是Jacobian矩阵。降阶后的Jacobian可以在离线阶段计算。

经验算子插值
将非线性算子本身进行插值近似,适用于具有特定结构的非线性项。

3.4 稳定性与误差估计

POD-Galerkin降阶模型的稳定性是一个重要问题。由于截断了高阶模态,降阶系统可能出现数值不稳定,特别是对于对流主导问题。

稳定性改进方法

  • 压力稳定化:对于不可压缩流,需要特殊的压力处理
  • 超稳定化:添加人工耗散项
  • 基于能量的稳定化:利用系统的能量结构
  • 结构保持:保持原系统的守恒律和耗散结构

误差估计
定义降阶误差 e=u−Φa\mathbf{e} = \mathbf{u} - \boldsymbol{\Phi}\mathbf{a}e=uΦa。对于线性问题,可以导出误差的上界;对于非线性问题,误差估计更为复杂,通常需要借助对偶问题或统计方法。


4. 其他降阶方法

4.1 平衡截断方法

平衡截断(Balanced Truncation, BT)是一种基于系统理论的降阶方法,特别适用于线性时不变系统。其核心思想是通过坐标变换,使系统的可控性和可观性达到"平衡",然后截断那些既不可控也不可观的模态。

考虑线性系统:
dudt=Au+Bw\frac{d\mathbf{u}}{dt} = \mathbf{A}\mathbf{u} + \mathbf{B}\mathbf{w}dtdu=Au+Bw
y=Cu\mathbf{y} = \mathbf{C}\mathbf{u}y=Cu

其中 w\mathbf{w}w 是输入,y\mathbf{y}y 是输出。

定义可控性格拉姆矩阵 Wc\mathbf{W}_cWc 和可观性格拉姆矩阵 Wo\mathbf{W}_oWo
Wc=∫0∞eAtBBTeATtdt\mathbf{W}_c = \int_0^{\infty} e^{\mathbf{A}t}\mathbf{B}\mathbf{B}^T e^{\mathbf{A}^T t} dtWc=0eAtBBTeATtdt
Wo=∫0∞eATtCTCeAtdt\mathbf{W}_o = \int_0^{\infty} e^{\mathbf{A}^T t}\mathbf{C}^T\mathbf{C} e^{\mathbf{A}t} dtWo=0eATtCTCeAtdt

平衡变换 T\mathbf{T}T 使得变换后的系统满足:
TWcTT=T−TWoT−1=Σ=diag(σ1,…,σN)\mathbf{T}\mathbf{W}_c\mathbf{T}^T = \mathbf{T}^{-T}\mathbf{W}_o\mathbf{T}^{-1} = \Sigma = \text{diag}(\sigma_1, \ldots, \sigma_N)TWcTT=TTWoT1=Σ=diag(σ1,,σN)

其中 σi\sigma_iσi 称为Hankel奇异值,按降序排列。截断对应于小奇异值的模态,得到降阶系统。

平衡截断具有优良的性质:

  • 保持系统稳定性
  • 提供全局误差界:∥G−Gr∥∞≤2∑i=n+1Nσi\|G - G_r\|_{\infty} \leq 2\sum_{i=n+1}^{N} \sigma_iGGr2i=n+1Nσi
  • 适用于控制系统设计

4.2 Krylov子空间方法

Krylov子空间方法(又称矩匹配方法)通过匹配原系统和降阶系统的传递函数矩来构造降阶模型。对于线性系统,传递函数为:

G(s)=C(sI−A)−1BG(s) = \mathbf{C}(s\mathbf{I} - \mathbf{A})^{-1}\mathbf{B}G(s)=C(sIA)1B

在展开点 s0s_0s0 处进行泰勒展开:

G(s)=∑i=0∞mi(s−s0)iG(s) = \sum_{i=0}^{\infty} m_i (s - s_0)^iG(s)=i=0mi(ss0)i

其中 mi=C(A−s0I)−i−1Bm_i = \mathbf{C}(\mathbf{A} - s_0\mathbf{I})^{-i-1}\mathbf{B}mi=C(As0I)i1B 是第 iii 阶矩。

Krylov子空间方法构造降阶系统,使得前 2n2n2n 个矩与原系统匹配。这通过构建Krylov子空间实现:

Kn(A,B)=span{B,AB,…,An−1B}\mathcal{K}_n(\mathbf{A}, \mathbf{B}) = \text{span}\{\mathbf{B}, \mathbf{A}\mathbf{B}, \ldots, \mathbf{A}^{n-1}\mathbf{B}\}Kn(A,B)=span{B,AB,,An1B}

Krylov方法的优点是不需要求解大规模特征值问题,计算效率高;缺点是不提供全局误差界,且主要适用于线性问题。

4.3 动态模态分解(DMD)

动态模态分解(Dynamic Mode Decomposition, DMD)是一种数据驱动的降阶方法,可以从实验数据或仿真数据中提取系统的动态特征。DMD寻找线性算子 A\mathbf{A}A,使得:

uk+1=Auk\mathbf{u}_{k+1} = \mathbf{A}\mathbf{u}_kuk+1=Auk

或者更一般地,寻找最佳拟合的线性演化:

X2=AX1\mathbf{X}_2 = \mathbf{A}\mathbf{X}_1X2=AX1

其中 X1=[u1,…,uK−1]\mathbf{X}_1 = [\mathbf{u}_1, \ldots, \mathbf{u}_{K-1}]X1=[u1,,uK1]X2=[u2,…,uK]\mathbf{X}_2 = [\mathbf{u}_2, \ldots, \mathbf{u}_K]X2=[u2,,uK]

DMD的特征值给出系统的特征频率和增长率,特征向量给出空间模态。DMD特别适用于分析瞬态流动、振动模态等。

扩展版本包括:

  • 扩展DMD(EDMD):处理非线性系统
  • 稀疏DMD:识别稀疏的动态模态
  • 多分辨率DMD:处理多尺度动力学

5. 参数化降阶模型

5.1 参数化问题的挑战

许多工程问题涉及参数变化,如材料参数、几何参数、边界条件等。理想情况下,降阶模型应该能够在参数空间内快速响应参数变化,而无需重新进行高维仿真。

参数化降阶模型(Parametric Reduced Order Model, pROM)的一般形式为:

M~(μ)dadt=f~(a,t;μ)\tilde{\mathbf{M}}(\boldsymbol{\mu})\frac{d\mathbf{a}}{dt} = \tilde{\mathbf{f}}(\mathbf{a}, t; \boldsymbol{\mu})M~(μ)dtda=f~(a,t;μ)

其中 μ∈D⊂Rp\boldsymbol{\mu} \in \mathcal{D} \subset \mathbb{R}^pμDRp 是参数向量。

主要挑战包括:

  • 参数依赖性:降阶基可能需要随参数变化
  • 插值问题:如何在参数空间插值降阶矩阵
  • 在线效率:在线阶段需要快速组装降阶系统

5.2 全局POD方法

全局POD(Global POD)在参数空间中选择一组代表性的参数点,在每个点上进行高维仿真并采集快照,然后将所有快照合并构建统一的POD基。

算法步骤:

  1. 在参数空间中选择采样点 {μ1,…,μP}\{\boldsymbol{\mu}_1, \ldots, \boldsymbol{\mu}_P\}{μ1,,μP}
  2. 对每个 μi\boldsymbol{\mu}_iμi 进行高维仿真,采集快照
  3. 合并所有快照,构建全局快照矩阵
  4. 对全局快照矩阵进行POD,得到全局基函数

全局POD简单直观,但当参数维度高或参数变化范围大时,可能需要大量的采样点和快照才能覆盖整个参数空间。

5.3 贪婪采样算法

贪婪算法自适应地选择最能改进降阶模型的参数点,避免在参数空间中进行密集采样。

基本流程:

  1. 初始化:选择初始参数点,构建初始降阶模型
  2. 误差估计:在参数空间中估计降阶误差
  3. 参数选择:选择误差最大的参数点作为新的采样点
  4. 模型更新:在新采样点进行高维仿真,更新降阶基
  5. 收敛检查:如果误差满足要求,停止;否则返回步骤2

误差估计可以通过残差估计、对偶方法或基于代理模型的估计实现。

贪婪算法显著减少了所需的采样点数目,特别适用于参数维度较高的问题。

5.4 降阶基的插值

另一种策略是在参数空间中对降阶基进行插值。由于POD基的正交性约束,直接在参数空间插值基矩阵可能破坏正交性。

切空间插值
将POD基视为Grassmann流形上的点,在切空间中进行插值,然后通过指数映射回到流形。

矩阵插值
直接对降阶矩阵(如 A~(μ)\tilde{\mathbf{A}}(\boldsymbol{\mu})A~(μ))进行插值。常用的方法包括:

  • 多项式插值
  • 径向基函数插值
  • Kriging插值
  • 高斯过程回归

仿射参数分解
如果系统矩阵可以表示为参数分离的形式:
A(μ)=∑q=1Qθq(μ)Aq\mathbf{A}(\boldsymbol{\mu}) = \sum_{q=1}^{Q} \theta_q(\boldsymbol{\mu})\mathbf{A}_qA(μ)=q=1Qθq(μ)Aq

则降阶矩阵为:
A~(μ)=∑q=1Qθq(μ)ΦTAqΦ\tilde{\mathbf{A}}(\boldsymbol{\mu}) = \sum_{q=1}^{Q} \theta_q(\boldsymbol{\mu})\boldsymbol{\Phi}^T\mathbf{A}_q\boldsymbol{\Phi}A~(μ)=q=1Qθq(μ)ΦTAqΦ

其中 ΦTAqΦ\boldsymbol{\Phi}^T\mathbf{A}_q\boldsymbol{\Phi}ΦTAqΦ 可以在离线阶段计算。在线阶段只需要计算参数函数 θq(μ)\theta_q(\boldsymbol{\mu})θq(μ) 并组合,复杂度与 NNN 无关。


6. 多物理场系统的降阶策略

6.1 分区降阶 vs. 整体降阶

对于多物理场系统,可以采用不同的降阶策略:

分区降阶
对每个物理场分别构建降阶模型,然后通过耦合界面条件进行耦合。这种方法的优点是:

  • 每个物理场可以使用最适合的降阶方法
  • 保持物理场的模块化结构
  • 便于与现有的单物理场求解器集成

挑战在于需要处理界面耦合条件的降阶,保证耦合的准确性和稳定性。

整体降阶
将所有物理场的变量合并,构建统一的降阶模型。优点是可以捕捉物理场之间的强耦合效应,缺点是降阶维度可能较大,且失去了模块化结构。

6.2 多物理场POD基构建

对于包含多个物理变量的系统(如温度 TTT、位移 u\mathbf{u}u、压力 ppp),可以构建块结构的POD基:

Φ=[ΦT000Φu000Φp]\boldsymbol{\Phi} = \begin{bmatrix} \boldsymbol{\Phi}_T & 0 & 0 \\ 0 & \boldsymbol{\Phi}_u & 0 \\ 0 & 0 & \boldsymbol{\Phi}_p \end{bmatrix}Φ= ΦT000Φu000Φp

或者通过适当的缩放将不同物理量归一化后统一处理。

6.3 时间尺度分离

多物理场系统通常具有多个时间尺度。例如,在热-结构耦合问题中,热传导的时间尺度可能比分结构响应慢几个数量级。

可以采用以下策略:

  • 多时间步进:不同物理场使用不同的时间步长
  • 准静态近似:对于快速达到稳态的物理场,采用稳态近似
  • 降阶模型分层:构建不同时间尺度的降阶模型

6.4 界面耦合处理

对于分区降阶,界面耦合条件的处理是关键。常用方法包括:

拉格朗日乘子法
在界面处引入拉格朗日乘子,强制满足耦合条件。

** mortar 方法**:
使用mortar单元处理非匹配网格的界面耦合。

界面基函数
在界面附近构建特殊的基函数,保证界面条件的准确满足。


7. 误差控制与验证

7.1 误差来源分析

降阶模型的误差来源包括:

  • 截断误差:由于截断高阶模态引起的误差
  • 投影误差:由于Galerkin投影引起的误差
  • 非线性近似误差:由于DEIM等非线性近似引起的误差
  • 参数插值误差:对于参数化模型,由于参数插值引起的误差

7.2 后验误差估计

后验误差估计基于降阶解计算误差界。对于线性问题,可以利用残差估计:

∥e∥≤Δ(a)=∥r(a)∥∗\|\mathbf{e}\| \leq \Delta(\mathbf{a}) = \|\mathbf{r}(\mathbf{a})\|_{*} eΔ(a)=r(a)

其中 r(a)=g−MΦdadt−AΦa\mathbf{r}(\mathbf{a}) = \mathbf{g} - \mathbf{M}\boldsymbol{\Phi}\frac{d\mathbf{a}}{dt} - \mathbf{A}\boldsymbol{\Phi}\mathbf{a}r(a)=gMΦdtdaAΦa 是残差,∥⋅∥∗\|\cdot\|_{*} 是适当的对偶范数。

对于非线性问题,误差估计更为复杂,通常需要线性化或统计方法。

7.3 模型验证与确认

降阶模型需要经过严格的验证和确认:

验证(Verification)

  • 与全阶模型对比:在相同的工况下比较降阶解和全阶解
  • 收敛性测试:随着降阶维度增加,误差应该单调递减
  • 网格无关性:降阶模型应该对网格密度不敏感

确认(Validation)

  • 与实验数据对比:验证降阶模型预测实际物理系统的能力
  • 参数外推:测试降阶模型在训练参数范围之外的预测能力
  • 鲁棒性测试:测试降阶模型对扰动和不确定性的敏感性

8. 应用案例

8.1 案例1:热-结构耦合板的降阶模型

问题描述
考虑一个二维热-结构耦合板,长度 L=0.5L = 0.5L=0.5 m,厚度 h=0.01h = 0.01h=0.01 m。板的上表面受到热流加热,同时热膨胀引起结构变形。材料参数:热导率 k=50k = 50k=50 W/(m·K),热膨胀系数 α=12×10−6\alpha = 12 \times 10^{-6}α=12×106 /K,杨氏模量 E=200E = 200E=200 GPa,泊松比 ν=0.3\nu = 0.3ν=0.3

全阶模型
使用有限元法离散,温度场采用200×10网格,位移场采用200×10网格,总自由度约6000。

降阶过程

  1. 在参数空间(热流密度 q=1000q = 1000q=1000~500050005000 W/m²)中进行高维仿真
  2. 采集200个快照(时间和参数组合)
  3. 构建POD基,保留20个模态(累积能量99.9%)
  4. 使用DEIM处理非线性热边界条件

结果

  • 降阶维度:20(从6000降低)
  • 在线计算时间:0.05秒(全阶模型需要120秒)
  • 相对误差:< 1%

8.2 案例2:圆柱绕流流-固耦合的降阶模型

问题描述
考虑弹性支撑的圆柱在均匀来流中的涡激振动问题。雷诺数 Re=200Re = 200Re=200,质量比 m∗=10m^* = 10m=10,阻尼比 ζ=0.01\zeta = 0.01ζ=0.01

全阶模型
使用浸没边界法耦合N-S方程和结构运动方程,流体网格约50000个单元。

降阶过程

  1. 使用DMD分析流场数据,提取主要动态模态
  2. 构建流场的POD-Galerkin降阶模型
  3. 结构运动方程保持原形式(仅2个自由度)
  4. 通过界面耦合条件耦合流体和结构模型

结果

  • 流体降阶维度:30
  • 在线计算速度提升:500倍
  • 能准确捕捉涡脱频率和振幅响应

8.3 案例3:参数化微镜阵列的电热-机械耦合降阶

问题描述
MEMS微镜阵列用于光学开关,需要快速响应(< 1 ms)。微镜通过电热驱动产生倾斜,涉及电热-机械多物理场耦合。

全阶模型
使用多物理场有限元软件建模,包含电热分析和结构分析,总自由度约100000。

降阶过程

  1. 使用全局POD在参数空间(驱动电压、环境温度)中构建统一基
  2. 使用贪婪算法优化采样点(仅需15个采样点)
  3. 构建参数化降阶模型
  4. 使用RBF插值处理参数依赖的降阶矩阵

结果

  • 降阶维度:50
  • 在线计算时间:< 10 ms(满足实时要求)
  • 在整个参数空间内误差< 2%

9. 软件工具与实现

9.1 开源降阶建模工具

RBmatlab
MATLAB工具箱,提供参数化降阶模型的完整实现,包括贪婪采样、误差估计等。

pyMOR
Python库,支持多种降阶方法,与FEniCS、deal.II等有限元库集成。

ROMES
基于MATLAB的降阶建模环境,专注于结构力学和流体力学。

DUNE-RB
基于DUNE(Distributed and Unified Numerics Environment)的降阶模块,支持大规模并行计算。

9.2 商业软件集成

主要商业多物理场仿真软件(如COMSOL、ANSYS、Abaqus)都提供了降阶建模功能或与降阶工具的接口。


附录:Python仿真代码

本文的仿真案例使用Python实现,代码保存在 run_simulation.py 文件中,包含以下案例:

  1. 案例1:热-结构耦合板的POD降阶
  2. 案例2:圆柱绕流流-固耦合的DMD分析
  3. 案例3:参数化微镜阵列的全局POD降阶
  4. 案例4:非线性系统的DEIM降阶
  5. 案例5:贪婪采样算法演示
  6. 案例6:降阶模型误差分析

每个案例都包含完整的降阶流程:快照采集、基函数构建、降阶系统生成、在线求解和误差评估。代码使用NumPy和SciPy进行数值计算,使用Matplotlib进行可视化。

# -*- coding: utf-8 -*-
"""
主题080:多物理场模型降阶
Python仿真程序

包含以下案例:
1. 热-结构耦合板的POD降阶
2. 圆柱绕流流-固耦合的DMD分析
3. 参数化微镜阵列的全局POD降阶
4. 非线性系统的DEIM降阶
5. 贪婪采样算法演示
6. 降阶模型误差分析
"""

import matplotlib
matplotlib.use('Agg')  # 非交互式后端
import numpy as np
import matplotlib.pyplot as plt
from scipy import linalg, sparse
from scipy.sparse import linalg as sparse_linalg
import warnings
warnings.filterwarnings('ignore')
import os

# 创建输出目录
output_dir = r'd:\文档\500仿真领域\工程仿真\多物理场耦合仿真\主题080\output'
os.makedirs(output_dir, exist_ok=True)

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

print("="*70)
print("主题080:多物理场模型降阶")
print("Python仿真程序")
print("="*70)

# =============================================================================
# 案例1:热-结构耦合板的POD降阶
# =============================================================================
def case1_thermal_structural_pod():
    """
    案例1:热-结构耦合板的POD降阶
    演示POD方法在热-结构耦合问题中的应用
    """
    print("\n" + "="*70)
    print("案例1: 热-结构耦合板的POD降阶")
    print("="*70)
    
    # 物理参数
    L = 0.5  # 板长度 (m)
    h = 0.01  # 板厚度 (m)
    k = 50  # 热导率 (W/m·K)
    alpha = 12e-6  # 热膨胀系数 (/K)
    E = 200e9  # 杨氏模量 (Pa)
    nu = 0.3  # 泊松比
    rho = 7850  # 密度 (kg/m³)
    cp = 500  # 比热容 (J/kg·K)
    
    print(f"\n物理参数:")
    print(f"  板长度 L = {L:.2f} m")
    print(f"  板厚度 h = {h:.3f} m")
    print(f"  热导率 k = {k:.0f} W/m·K")
    print(f"  热膨胀系数 α = {alpha:.2e} /K")
    print(f"  杨氏模量 E = {E/1e9:.0f} GPa")
    
    # 空间离散
    nx = 50  # 温度场网格数
    ny = 10  # 厚度方向网格数
    nt = nx * ny  # 温度场自由度
    
    print(f"\n离散参数:")
    print(f"  温度场网格: {nx} x {ny}")
    print(f"  温度场自由度: {nt}")
    
    # 构建热传导矩阵(简化的一维模型)
    dx = L / nx
    K_thermal = np.zeros((nx, nx))
    
    # 内部节点
    for i in range(1, nx-1):
        K_thermal[i, i] = 2.0
        K_thermal[i, i-1] = -1.0
        K_thermal[i, i+1] = -1.0
    
    # 边界条件
    K_thermal[0, 0] = 1.0  # 左端固定温度
    K_thermal[-1, -1] = 1.0  # 右端对流
    K_thermal[-1, -2] = -0.5
    
    K_thermal *= k / dx**2
    
    # 质量矩阵(热容)
    M_thermal = np.eye(nx) * rho * cp
    
    # 时间积分参数
    t_end = 100  # 总时间 (s)
    dt = 0.5  # 时间步长 (s)
    nt_steps = int(t_end / dt)
    
    # 参数空间:热流密度
    q_values = np.linspace(1000, 5000, 5)  # W/m²
    n_params = len(q_values)
    
    print(f"\n参数空间:")
    print(f"  热流密度范围: {q_values[0]:.0f} ~ {q_values[-1]:.0f} W/m²")
    print(f"  参数点数: {n_params}")
    
    # 阶段1:快照采集(离线阶段)
    print("\n阶段1: 快照采集(离线阶段)...")
    snapshots = []
    
    for q in q_values:
        T = np.ones(nx) * 20  # 初始温度 20°C
        
        for n in range(nt_steps):
            # 热源项
            f = np.zeros(nx)
            f[0] = q / dx  # 左端热流
            
            # 隐式时间积分
            T_new = linalg.solve(M_thermal / dt + K_thermal, 
                                M_thermal @ T / dt + f)
            T = T_new
            
            # 每10步采集一个快照
            if n % 10 == 0:
                snapshots.append(T.copy())
    
    snapshots = np.array(snapshots).T  # 形状: (nx, n_snapshots)
    n_snapshots = snapshots.shape[1]
    
    print(f"  采集快照数: {n_snapshots}")
    
    # 阶段2:POD基构建
    print("\n阶段2: POD基构建...")
    
    # 计算均值并去均值
    T_mean = np.mean(snapshots, axis=1)
    snapshots_centered = snapshots - T_mean[:, np.newaxis]
    
    # 方法of snapshots:求解小特征值问题
    C = snapshots_centered.T @ snapshots_centered / n_snapshots
    eigenvalues, eigenvectors = linalg.eigh(C)
    
    # 按特征值降序排列
    idx = np.argsort(eigenvalues)[::-1]
    eigenvalues = eigenvalues[idx]
    eigenvectors = eigenvectors[:, idx]
    
    # 构建POD基
    n_modes = min(20, n_snapshots)
    Phi = np.zeros((nx, n_modes))
    for i in range(n_modes):
        Phi[:, i] = snapshots_centered @ eigenvectors[:, i] / np.sqrt(eigenvalues[i])
    
    # 归一化
    for i in range(n_modes):
        Phi[:, i] = Phi[:, i] / linalg.norm(Phi[:, i])
    
    # 计算累积能量
    total_energy = np.sum(eigenvalues)
    cumulative_energy = np.cumsum(eigenvalues) / total_energy
    
    print(f"  POD模态数: {n_modes}")
    print(f"  前5个模态能量占比:")
    for i in range(min(5, n_modes)):
        print(f"    模态{i+1}: {eigenvalues[i]/total_energy*100:.2f}%")
    print(f"  前5个模态累积能量: {cumulative_energy[4]*100:.2f}%")
    
    # 阶段3:构建降阶系统
    print("\n阶段3: 构建降阶系统...")
    
    # 降阶矩阵
    M_rom = Phi.T @ M_thermal @ Phi
    K_rom = Phi.T @ K_thermal @ Phi
    
    print(f"  降阶系统维度: {n_modes}")
    print(f"  降阶比: {nx}/{n_modes} = {nx/n_modes:.1f}")
    
    # 阶段4:在线求解(使用降阶模型)
    print("\n阶段4: 在线求解(降阶模型)...")
    
    # 测试新参数
    q_test = 3000  # W/m²
    
    # 全阶模型求解
    T_fom = np.ones(nx) * 20
    T_fom_history = [T_fom.copy()]
    
    for n in range(nt_steps):
        f = np.zeros(nx)
        f[0] = q_test / dx
        T_fom = linalg.solve(M_thermal / dt + K_thermal,
                            M_thermal @ T_fom / dt + f)
        if n % 10 == 0:
            T_fom_history.append(T_fom.copy())
    
    # 降阶模型求解
    a_rom = Phi.T @ (np.ones(nx) * 20 - T_mean)  # 初始模态系数
    T_rom_history = [Phi @ a_rom + T_mean]
    
    for n in range(nt_steps):
        f = np.zeros(nx)
        f[0] = q_test / dx
        f_rom = Phi.T @ f
        
        a_rom = linalg.solve(M_rom / dt + K_rom,
                            M_rom @ a_rom / dt + f_rom)
        T_rom = Phi @ a_rom + T_mean
        
        if n % 10 == 0:
            T_rom_history.append(T_rom.copy())
    
    T_fom_history = np.array(T_fom_history)
    T_rom_history = np.array(T_rom_history)
    
    # 计算误差
    error = np.abs(T_fom_history - T_rom_history)
    max_error = np.max(error)
    mean_error = np.mean(error)
    
    print(f"\n结果对比 (q = {q_test} W/m²):")
    print(f"  全阶模型最终最高温度: {np.max(T_fom_history[-1]):.2f} °C")
    print(f"  降阶模型最终最高温度: {np.max(T_rom_history[-1]):.2f} °C")
    print(f"  最大误差: {max_error:.4f} °C")
    print(f"  平均误差: {mean_error:.4f} °C")
    print(f"  相对误差: {max_error/np.max(T_fom_history)*100:.2f}%")
    
    # 可视化
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    x = np.linspace(0, L, nx)
    
    # 图1:POD基函数
    ax1 = axes[0, 0]
    for i in range(min(5, n_modes)):
        ax1.plot(x, Phi[:, i], linewidth=2, label=f'Mode {i+1}')
    ax1.set_xlabel('Position (m)', fontsize=11)
    ax1.set_ylabel('POD Mode', fontsize=11)
    ax1.set_title('First 5 POD Modes', fontsize=12, fontweight='bold')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # 图2:特征值衰减
    ax2 = axes[0, 1]
    ax2.semilogy(range(1, min(21, n_modes)+1), eigenvalues[:min(20, n_modes)], 'bo-', linewidth=2)
    ax2.set_xlabel('Mode Number', fontsize=11)
    ax2.set_ylabel('Eigenvalue (log scale)', fontsize=11)
    ax2.set_title('POD Eigenvalue Decay', fontsize=12, fontweight='bold')
    ax2.grid(True, alpha=0.3)
    
    # 图3:温度分布对比
    ax3 = axes[1, 0]
    times = [0, len(T_fom_history)//2, -1]
    colors = ['b', 'g', 'r']
    for i, t_idx in enumerate(times):
        ax3.plot(x, T_fom_history[t_idx], colors[i]+'-', linewidth=2, 
                label=f'FOM t={t_idx*dt*10:.0f}s')
        ax3.plot(x, T_rom_history[t_idx], colors[i]+'--', linewidth=2,
                label=f'ROM t={t_idx*dt*10:.0f}s')
    ax3.set_xlabel('Position (m)', fontsize=11)
    ax3.set_ylabel('Temperature (°C)', fontsize=11)
    ax3.set_title('Temperature Distribution Comparison', fontsize=12, fontweight='bold')
    ax3.legend(fontsize=8)
    ax3.grid(True, alpha=0.3)
    
    # 图4:误差分布
    ax4 = axes[1, 1]
    im = ax4.contourf(T_fom_history[::5, :], levels=20, cmap='hot')
    plt.colorbar(im, ax=ax4, label='Temperature (°C)')
    ax4.set_xlabel('Position Index', fontsize=11)
    ax4.set_ylabel('Time Step', fontsize=11)
    ax4.set_title('Temperature Field Evolution (FOM)', fontsize=12, fontweight='bold')
    
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case1_thermal_structural_pod.png', dpi=150, bbox_inches='tight')
    plt.close()
    print("\n案例1结果已保存")
    
    return Phi, eigenvalues, T_fom_history, T_rom_history

# =============================================================================
# 案例2:圆柱绕流流-固耦合的DMD分析
# =============================================================================
def case2_cylinder_dmd():
    """
    案例2:圆柱绕流流-固耦合的DMD分析
    使用DMD方法提取流场的主要动态模态
    """
    print("\n" + "="*70)
    print("案例2: 圆柱绕流流-固耦合的DMD分析")
    print("="*70)
    
    # 物理参数
    Re = 200  # 雷诺数
    D = 0.01  # 圆柱直径 (m)
    U_inf = 1.0  # 来流速度 (m/s)
    m_star = 10  # 质量比
    zeta = 0.01  # 阻尼比
    
    print(f"\n物理参数:")
    print(f"  雷诺数 Re = {Re:.0f}")
    print(f"  圆柱直径 D = {D*1000:.1f} mm")
    print(f"  来流速度 U∞ = {U_inf:.1f} m/s")
    print(f"  质量比 m* = {m_star:.1f}")
    print(f"  阻尼比 ζ = {zeta:.3f}")
    
    # 计算斯特劳哈尔数(近似)
    St = 0.21 * (1 - 21/Re)  # 经验公式
    f_vortex = St * U_inf / D
    T_vortex = 1 / f_vortex
    
    print(f"\n涡脱特性:")
    print(f"  斯特劳哈尔数 St = {St:.3f}")
    print(f"  涡脱频率 f = {f_vortex:.1f} Hz")
    print(f"  涡脱周期 T = {T_vortex*1000:.2f} ms")
    
    # 简化的流场模型:使用正弦函数模拟涡街
    nx = 100  # 流向网格数
    ny = 50  # 横向网格数
    x = np.linspace(-5*D, 10*D, nx)
    y = np.linspace(-3*D, 3*D, ny)
    X, Y = np.meshgrid(x, y)
    
    # 时间参数
    t_end = 10 * T_vortex
    dt = T_vortex / 20
    nt = int(t_end / dt)
    t = np.linspace(0, t_end, nt)
    
    print(f"\n仿真参数:")
    print(f"  仿真时间: {t_end*1000:.1f} ms")
    print(f"  时间步长: {dt*1000:.2f} ms")
    print(f"  时间步数: {nt}")
    
    # 生成流场快照(模拟涡街)
    print("\n生成流场快照...")
    snapshots = []
    
    for i, ti in enumerate(t):
        # 简化的涡街模型
        u = U_inf * (1 - np.exp(-(X/D)**2/2)) + \
            0.3 * U_inf * np.sin(2*np.pi*f_vortex*ti) * np.exp(-(Y/D)**2/2) * np.sin(2*np.pi*X/(2*D))
        v = 0.2 * U_inf * np.cos(2*np.pi*f_vortex*ti) * np.exp(-((X-2*D)/D)**2/2) * np.exp(-(Y/D)**2/2)
        
        # 展平为向量
        snapshot = np.concatenate([u.flatten(), v.flatten()])
        snapshots.append(snapshot)
    
    snapshots = np.array(snapshots).T  # 形状: (n_dof, nt)
    n_dof = snapshots.shape[0]
    
    print(f"  自由度: {n_dof}")
    print(f"  快照数: {nt}")
    
    # DMD分析
    print("\nDMD分析...")
    
    # 构建数据矩阵
    X1 = snapshots[:, :-1]
    X2 = snapshots[:, 1:]
    
    # SVD分解
    U_svd, S_svd, Vh_svd = linalg.svd(X1, full_matrices=False)
    
    # 截断SVD(保留主要模态)
    r = min(20, len(S_svd))
    U_r = U_svd[:, :r]
    S_r = np.diag(S_svd[:r])
    V_r = Vh_svd[:r, :].T
    
    # 近似A矩阵
    A_tilde = U_r.T @ X2 @ V_r @ np.linalg.inv(S_r)
    
    # 特征值分解
    eigenvalues, eigenvectors = np.linalg.eig(A_tilde)
    
    # DMD模态
    Phi_dmd = X2 @ V_r @ np.linalg.inv(S_r) @ eigenvectors
    
    # 计算频率和增长率
    omega = np.log(eigenvalues) / dt
    frequencies = np.imag(omega) / (2*np.pi)
    growth_rates = np.real(omega)
    
    print(f"  DMD模态数: {r}")
    print(f"\n主要DMD模态:")
    for i in range(min(5, r)):
        print(f"    模态{i+1}: 频率 = {frequencies[i]:.2f} Hz, "
              f"增长率 = {growth_rates[i]:.3f}")
    
    # 结构响应(简化的单自由度模型)
    print("\n结构响应分析...")
    omega_n = 2 * np.pi * f_vortex  # 固有频率(假设与涡脱频率匹配)
    omega_d = omega_n * np.sqrt(1 - zeta**2)
    
    # 涡激力(简化)
    F_vortex = 0.5 * np.sin(2*np.pi*f_vortex*t)
    
    # 结构响应
    y_disp = np.zeros_like(t)
    y_vel = np.zeros_like(t)
    
    for i in range(1, len(t)):
        y_acc = (F_vortex[i] - 2*zeta*omega_n*y_vel[i-1] - omega_n**2*y_disp[i-1])
        y_vel[i] = y_vel[i-1] + y_acc * dt
        y_disp[i] = y_disp[i-1] + y_vel[i] * dt
    
    amplitude = (np.max(y_disp) - np.min(y_disp)) / 2
    print(f"  稳态振幅: {amplitude*D*1000:.3f} mm")
    
    # 可视化
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    
    # 图1:DMD特征值(单位圆)
    ax1 = axes[0, 0]
    theta = np.linspace(0, 2*np.pi, 100)
    ax1.plot(np.cos(theta), np.sin(theta), 'k--', alpha=0.5)
    ax1.scatter(np.real(eigenvalues), np.imag(eigenvalues), c='red', s=100)
    ax1.set_xlabel('Real', fontsize=11)
    ax1.set_ylabel('Imaginary', fontsize=11)
    ax1.set_title('DMD Eigenvalues (Unit Circle)', fontsize=12, fontweight='bold')
    ax1.grid(True, alpha=0.3)
    ax1.axis('equal')
    
    # 图2:频率-增长率图
    ax2 = axes[0, 1]
    ax2.scatter(frequencies, growth_rates, c='blue', s=100)
    ax2.axhline(y=0, color='k', linestyle='--', alpha=0.5)
    ax2.set_xlabel('Frequency (Hz)', fontsize=11)
    ax2.set_ylabel('Growth Rate', fontsize=11)
    ax2.set_title('DMD Frequency vs Growth Rate', fontsize=12, fontweight='bold')
    ax2.grid(True, alpha=0.3)
    
    # 图3:结构响应
    ax3 = axes[1, 0]
    ax3.plot(t/T_vortex, y_disp/D, 'b-', linewidth=2)
    ax3.set_xlabel('Time (T_vortex)', fontsize=11)
    ax3.set_ylabel('Displacement (D)', fontsize=11)
    ax3.set_title('Structural Response (VIV)', fontsize=12, fontweight='bold')
    ax3.grid(True, alpha=0.3)
    
    # 图4:涡街可视化(某一时刻)
    ax4 = axes[1, 1]
    u = snapshots[:nx*ny, -1].reshape(ny, nx)
    v = snapshots[nx*ny:, -1].reshape(ny, nx)
    speed = np.sqrt(u**2 + v**2)
    
    im = ax4.contourf(X/D, Y/D, speed/U_inf, levels=20, cmap='jet')
    plt.colorbar(im, ax=ax4, label='|U|/U_inf')
    circle = plt.Circle((0, 0), 0.5, color='black', fill=True)
    ax4.add_patch(circle)
    ax4.set_xlabel('x/D', fontsize=11)
    ax4.set_ylabel('y/D', fontsize=11)
    ax4.set_title('Flow Field (t/T = 10)', fontsize=12, fontweight='bold')
    ax4.set_aspect('equal')
    
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case2_cylinder_dmd.png', dpi=150, bbox_inches='tight')
    plt.close()
    print("\n案例2结果已保存")
    
    return eigenvalues, frequencies, y_disp

# =============================================================================
# 案例3:参数化微镜阵列的全局POD降阶
# =============================================================================
def case3_parametric_micromirror_pod():
    """
    案例3:参数化微镜阵列的全局POD降阶
    演示全局POD方法在参数化问题中的应用
    """
    print("\n" + "="*70)
    print("案例3: 参数化微镜阵列的全局POD降阶")
    print("="*70)
    
    # 物理参数
    L = 100e-6  # 微镜边长 (m)
    h = 2e-6    # 微镜厚度 (m)
    rho = 2330  # 硅密度 (kg/m³)
    E = 169e9   # 杨氏模量 (Pa)
    alpha = 2.6e-6  # 热膨胀系数 (/K)
    k = 150     # 热导率 (W/m·K)
    
    print(f"\n物理参数:")
    print(f"  微镜边长 L = {L*1e6:.0f} μm")
    print(f"  微镜厚度 h = {h*1e6:.0f} μm")
    print(f"  材料: 硅")
    
    # 参数空间
    V_range = np.linspace(1, 5, 5)  # 驱动电压 (V)
    T_amb_range = np.linspace(20, 80, 4)  # 环境温度 (°C)
    
    print(f"\n参数空间:")
    print(f"  驱动电压: {V_range[0]:.0f} ~ {V_range[-1]:.0f} V")
    print(f"  环境温度: {T_amb_range[0]:.0f} ~ {T_amb_range[-1]:.0f} °C")
    print(f"  总参数点数: {len(V_range) * len(T_amb_range)}")
    
    # 简化的热-机械模型
    # 电热功率: P = V²/R (R为电阻)
    R_heater = 100  # 加热器电阻 (Ω)
    
    # 热模型(集总参数)
    C_th = rho * L**2 * h * 700  # 热容 (J/K)
    R_th = 1 / (k * L**2 / h)    # 热阻 (K/W)
    
    # 机械模型(简化为单自由度)
    I_mirror = rho * L**2 * h * L**2 / 3  # 转动惯量
    k_spring = E * L * h**3 / (4 * L**3)  # 扭转弹簧刚度
    
    print(f"\n模型参数:")
    print(f"  热容 C_th = {C_th:.2e} J/K")
    print(f"  热阻 R_th = {R_th:.2e} K/W")
    print(f"  扭转刚度 k_spring = {k_spring:.2e} N·m/rad")
    
    # 全局POD:在所有参数点上采集快照
    print("\n全局POD快照采集...")
    snapshots = []
    params = []
    
    t_end = 0.001  # 1 ms
    dt = 1e-5
    nt = int(t_end / dt)
    
    for V in V_range:
        for T_amb in T_amb_range:
            # 电热功率
            P_heat = V**2 / R_heater
            
            # 瞬态热分析
            T = T_amb
            for n in range(nt):
                dTdt = (P_heat - (T - T_amb)/R_th) / C_th
                T = T + dTdt * dt
                
                if n % 10 == 0:
                    # 热膨胀引起的倾斜角
                    delta_T = T - T_amb
                    theta = alpha * delta_T * L / h  # 简化模型
                    snapshots.append([T, theta])
                    params.append([V, T_amb])
    
    snapshots = np.array(snapshots)
    params = np.array(params)
    
    print(f"  采集快照数: {len(snapshots)}")
    
    # 对温度和角度分别归一化
    T_max, theta_max = np.max(snapshots, axis=0)
    snapshots_norm = snapshots / [T_max, theta_max]
    
    # POD分析
    print("\nPOD分析...")
    C = snapshots_norm.T @ snapshots_norm / len(snapshots)
    eigenvalues, eigenvectors = linalg.eigh(C)
    
    idx = np.argsort(eigenvalues)[::-1]
    eigenvalues = eigenvalues[idx]
    eigenvectors = eigenvectors[:, idx]
    
    # 保留2个模态(2个物理量)
    n_modes = 2
    Phi = eigenvectors[:, :n_modes]
    
    cumulative_energy = np.cumsum(eigenvalues) / np.sum(eigenvalues)
    
    print(f"  模态能量:")
    for i in range(n_modes):
        print(f"    模态{i+1}: {eigenvalues[i]/np.sum(eigenvalues)*100:.2f}%")
    print(f"  累积能量: {cumulative_energy[n_modes-1]*100:.2f}%")
    
    # 构建降阶模型(参数化)
    print("\n构建参数化降阶模型...")
    
    # 使用RBF插值构建参数到模态系数的映射
    from scipy.interpolate import Rbf, LinearNDInterpolator
    
    # 计算模态系数
    a_coeffs = snapshots_norm @ Phi
    
    # 为每个模态系数构建插值器(使用线性插值避免奇异矩阵)
    rbf_interpolators = []
    for i in range(n_modes):
        try:
            rbf = Rbf(params[:, 0], params[:, 1], a_coeffs[:, i], function='linear')
        except:
            # 如果RBF失败,使用线性ND插值
            rbf = LinearNDInterpolator(params[:, :2], a_coeffs[:, i])
        rbf_interpolators.append(rbf)
    
    # 测试新参数点
    V_test = 3.5  # V
    T_amb_test = 50  # °C
    
    print(f"\n测试新参数点:")
    print(f"  驱动电压: {V_test:.1f} V")
    print(f"  环境温度: {T_amb_test:.0f} °C")
    
    # 降阶模型预测
    try:
        a_pred = np.array([rbf(V_test, T_amb_test) for rbf in rbf_interpolators])
        state_pred = a_pred @ Phi.T * [T_max, theta_max]
    except:
        # 如果插值失败,使用最近邻
        distances = np.sum((params - [V_test, T_amb_test])**2, axis=1)
        nearest_idx = np.argmin(distances)
        state_pred = snapshots[nearest_idx]
    
    # 全阶模型验证
    P_heat = V_test**2 / R_heater
    T = T_amb_test
    for n in range(nt):
        dTdt = (P_heat - (T - T_amb_test)/R_th) / C_th
        T = T + dTdt * dt
    
    delta_T = T - T_amb_test
    theta_fom = alpha * delta_T * L / h
    
    print(f"\n结果对比:")
    print(f"  温度 (FOM): {T:.2f} °C")
    print(f"  温度 (ROM): {state_pred[0]:.2f} °C")
    print(f"  倾斜角 (FOM): {theta_fom:.4f} rad ({np.degrees(theta_fom):.2f}°)")
    print(f"  倾斜角 (ROM): {state_pred[1]:.4f} rad ({np.degrees(state_pred[1]):.2f}°)")
    print(f"  温度误差: {abs(T - state_pred[0]):.4f} °C")
    print(f"  角度误差: {abs(theta_fom - state_pred[1]):.6f} rad")
    
    # 可视化
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    
    # 图1:参数空间采样点
    ax1 = axes[0, 0]
    scatter = ax1.scatter(params[:, 0], params[:, 1], c=snapshots[:, 0], 
                         cmap='hot', s=100)
    ax1.scatter([V_test], [T_amb_test], c='red', s=200, marker='*', 
               label='Test Point')
    plt.colorbar(scatter, ax=ax1, label='Temperature (°C)')
    ax1.set_xlabel('Voltage (V)', fontsize=11)
    ax1.set_ylabel('Ambient Temperature (°C)', fontsize=11)
    ax1.set_title('Parameter Space Sampling', fontsize=12, fontweight='bold')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # 图2:POD模态
    ax2 = axes[0, 1]
    x_labels = ['Temperature', 'Tilt Angle']
    x_pos = np.arange(len(x_labels))
    for i in range(n_modes):
        ax2.bar(x_pos + i*0.35, Phi[:, i], 0.35, label=f'Mode {i+1}')
    ax2.set_xticks(x_pos + 0.35/2)
    ax2.set_xticklabels(x_labels)
    ax2.set_ylabel('Mode Amplitude', fontsize=11)
    ax2.set_title('POD Modes', fontsize=12, fontweight='bold')
    ax2.legend()
    ax2.grid(True, alpha=0.3, axis='y')
    
    # 图3:温度响应曲面
    ax3 = axes[1, 0]
    V_grid, T_grid = np.meshgrid(np.linspace(1, 5, 20), np.linspace(20, 80, 20))
    T_response = np.zeros_like(V_grid)
    for i in range(V_grid.shape[0]):
        for j in range(V_grid.shape[1]):
            a = np.array([rbf(V_grid[i,j], T_grid[i,j]) for rbf in rbf_interpolators])
            T_response[i,j] = (a @ Phi.T * [T_max, theta_max])[0]
    
    contour = ax3.contourf(V_grid, T_grid, T_response, levels=20, cmap='hot')
    plt.colorbar(contour, ax=ax3, label='Temperature (°C)')
    ax3.set_xlabel('Voltage (V)', fontsize=11)
    ax3.set_ylabel('Ambient Temperature (°C)', fontsize=11)
    ax3.set_title('Temperature Response Surface (ROM)', fontsize=12, fontweight='bold')
    
    # 图4:角度响应曲面
    ax4 = axes[1, 1]
    theta_response = np.zeros_like(V_grid)
    for i in range(V_grid.shape[0]):
        for j in range(V_grid.shape[1]):
            a = np.array([rbf(V_grid[i,j], T_grid[i,j]) for rbf in rbf_interpolators])
            theta_response[i,j] = np.degrees((a @ Phi.T * [T_max, theta_max])[1])
    
    contour = ax4.contourf(V_grid, T_grid, theta_response, levels=20, cmap='viridis')
    plt.colorbar(contour, ax=ax4, label='Tilt Angle (deg)')
    ax4.set_xlabel('Voltage (V)', fontsize=11)
    ax4.set_ylabel('Ambient Temperature (°C)', fontsize=11)
    ax4.set_title('Tilt Angle Response Surface (ROM)', fontsize=12, fontweight='bold')
    
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case3_parametric_micromirror.png', dpi=150, bbox_inches='tight')
    plt.close()
    print("\n案例3结果已保存")
    
    return Phi, eigenvalues, rbf_interpolators

# =============================================================================
# 案例4:非线性系统的DEIM降阶
# =============================================================================
def case4_nonlinear_deim():
    """
    案例4:非线性系统的DEIM降阶
    演示DEIM方法在处理非线性项时的效率提升
    """
    print("\n" + "="*70)
    print("案例4: 非线性系统的DEIM降阶")
    print("="*70)
    
    # 非线性热传导问题
    L = 1.0  # 域长度 (m)
    nx = 100  # 网格数
    x = np.linspace(0, L, nx)
    dx = x[1] - x[0]
    
    print(f"\n问题设置:")
    print(f"  域长度: {L:.1f} m")
    print(f"  网格数: {nx}")
    print(f"  非线性: 温度相关热导率 k(T) = k0*(1 + α*T²)")
    
    # 材料参数(非线性)
    k0 = 1.0  # 基准热导率
    alpha_nl = 0.01  # 非线性系数
    
    # 时间参数
    t_end = 1.0
    dt = 0.001
    nt = int(t_end / dt)
    
    # 阶段1:生成快照(全阶模型)
    print("\n阶段1: 生成快照(全阶模型)...")
    snapshots = []
    
    T = np.ones(nx) * 20  # 初始温度
    for n in range(nt):
        # 非线性热导率
        k_local = k0 * (1 + alpha_nl * T**2)
        
        # 构建非线性刚度矩阵
        K = np.zeros((nx, nx))
        for i in range(1, nx-1):
            k_avg = 0.5 * (k_local[i-1] + k_local[i])
            K[i, i-1] = -k_avg / dx**2
            K[i, i] = (k_avg + 0.5*(k_local[i] + k_local[i+1])) / dx**2
            K[i, i+1] = -0.5 * (k_local[i] + k_local[i+1]) / dx**2
        
        # 边界条件
        K[0, 0] = 1.0
        T[0] = 100 + 50 * np.sin(2*np.pi*n*dt)  # 时变边界温度
        K[-1, -1] = 1.0
        K[-1, -2] = -1.0  # 绝热边界
        
        # 时间积分
        rhs = T / dt
        rhs[0] = T[0]
        T = linalg.solve(np.eye(nx)/dt + K, rhs)
        
        if n % 50 == 0:
            snapshots.append(T.copy())
    
    snapshots = np.array(snapshots).T
    print(f"  快照数: {snapshots.shape[1]}")
    
    # 阶段2:POD基构建
    print("\n阶段2: POD基构建...")
    C = snapshots @ snapshots.T / snapshots.shape[1]
    eigenvalues, eigenvectors = linalg.eigh(C)
    
    idx = np.argsort(eigenvalues)[::-1]
    eigenvalues = eigenvalues[idx]
    eigenvectors = eigenvectors[:, idx]
    
    n_pod = 10
    Phi = eigenvectors[:, :n_pod]
    
    cumulative = np.cumsum(eigenvalues) / np.sum(eigenvalues)
    print(f"  POD模态数: {n_pod}")
    print(f"  累积能量: {cumulative[n_pod-1]*100:.2f}%")
    
    # 阶段3:DEIM基构建(用于非线性项)
    print("\n阶段3: DEIM基构建...")
    
    # 计算非线性项的快照
    nonlinear_snapshots = []
    for i in range(snapshots.shape[1]):
        T_snap = snapshots[:, i]
        k_local = k0 * (1 + alpha_nl * T_snap**2)
        # 非线性热流项
        q_nl = np.zeros(nx)
        for j in range(1, nx-1):
            q_nl[j] = k_local[j] * (T_snap[j+1] - 2*T_snap[j] + T_snap[j-1]) / dx**2
        nonlinear_snapshots.append(q_nl)
    
    nonlinear_snapshots = np.array(nonlinear_snapshots).T
    
    # 对非线性项进行POD
    C_nl = nonlinear_snapshots @ nonlinear_snapshots.T / nonlinear_snapshots.shape[1]
    eigenvalues_nl, eigenvectors_nl = linalg.eigh(C_nl)
    
    idx_nl = np.argsort(eigenvalues_nl)[::-1]
    eigenvalues_nl = eigenvalues_nl[idx_nl]
    Psi = eigenvectors_nl[:, :min(8, nx)]  # DEIM基
    
    # DEIM插值点选择(简化实现)
    n_deim = 8
    # 选择能量最大的点
    indices = np.argsort(np.sum(Psi**2, axis=1))[::-1][:n_deim]
    
    print(f"  DEIM基维度: {n_deim}")
    print(f"  插值点数: {n_deim}")
    
    # 构建DEIM矩阵
    P = np.zeros((nx, n_deim))
    for i, idx in enumerate(indices):
        P[idx, i] = 1.0
    
    # 预计算DEIM矩阵
    PT_Psi = P.T @ Psi
    if np.linalg.matrix_rank(PT_Psi) < n_deim:
        print("  警告: DEIM矩阵奇异,调整插值点")
        # 使用均匀分布的点
        indices = np.linspace(0, nx-1, n_deim, dtype=int)
        P = np.zeros((nx, n_deim))
        for i, idx in enumerate(indices):
            P[idx, i] = 1.0
        PT_Psi = P.T @ Psi
    
    DEIM_matrix = Psi @ np.linalg.inv(PT_Psi)
    
    # 阶段4:在线求解对比
    print("\n阶段4: 在线求解对比...")
    
    # 全阶模型求解时间
    import time
    
    T0 = np.ones(nx) * 20
    
    # FOM求解
    start_time = time.time()
    T_fom = T0.copy()
    for n in range(nt):
        k_local = k0 * (1 + alpha_nl * T_fom**2)
        K = np.zeros((nx, nx))
        for i in range(1, nx-1):
            k_avg = 0.5 * (k_local[i-1] + k_local[i])
            K[i, i-1] = -k_avg / dx**2
            K[i, i] = (k_avg + 0.5*(k_local[i] + k_local[i+1])) / dx**2
            K[i, i+1] = -0.5 * (k_local[i] + k_local[i+1]) / dx**2
        K[0, 0] = 1.0
        T_fom[0] = 100 + 50 * np.sin(2*np.pi*n*dt)
        K[-1, -1] = 1.0
        K[-1, -2] = -1.0
        rhs = T_fom / dt
        rhs[0] = T_fom[0]
        T_fom = linalg.solve(np.eye(nx)/dt + K, rhs)
    time_fom = time.time() - start_time
    
    # ROM求解(使用DEIM)
    start_time = time.time()
    a_rom = Phi.T @ (T0 - np.mean(snapshots, axis=1))
    T_mean = np.mean(snapshots, axis=1)
    
    for n in range(nt):
        T_approx = Phi @ a_rom + T_mean
        
        # 在DEIM点上计算非线性项
        k_local = k0 * (1 + alpha_nl * T_approx**2)
        q_nl_local = np.zeros(nx)
        for j in range(1, nx-1):
            q_nl_local[j] = k_local[j] * (T_approx[j+1] - 2*T_approx[j] + T_approx[j-1]) / dx**2
        
        # DEIM近似
        q_deim = DEIM_matrix @ (P.T @ q_nl_local)
        
        # 降阶系统
        rhs_rom = a_rom / dt + Phi.T @ q_deim
        # 简化的线性系统(实际应该包含边界条件处理)
        a_rom = rhs_rom * dt
    time_rom = time.time() - start_time
    
    print(f"\n计算时间对比:")
    print(f"  全阶模型 (FOM): {time_fom:.4f} s")
    print(f"  降阶模型 (ROM): {time_rom:.4f} s")
    print(f"  加速比: {time_fom/time_rom:.1f}x")
    
    # 可视化
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    
    # 图1:温度分布
    ax1 = axes[0, 0]
    ax1.plot(x, T_fom, 'b-', linewidth=2, label='FOM')
    ax1.plot(x, T_approx, 'r--', linewidth=2, label='ROM')
    ax1.set_xlabel('Position (m)', fontsize=11)
    ax1.set_ylabel('Temperature (°C)', fontsize=11)
    ax1.set_title('Temperature Distribution (t=1s)', fontsize=12, fontweight='bold')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # 图2:POD模态
    ax2 = axes[0, 1]
    for i in range(min(5, n_pod)):
        ax2.plot(x, Phi[:, i], linewidth=2, label=f'Mode {i+1}')
    ax2.set_xlabel('Position (m)', fontsize=11)
    ax2.set_ylabel('Mode Amplitude', fontsize=11)
    ax2.set_title('POD Modes', fontsize=12, fontweight='bold')
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    
    # 图3:DEIM插值点
    ax3 = axes[1, 0]
    ax3.plot(x, np.abs(Psi[:, 0]), 'b-', linewidth=2, label='First DEIM Mode')
    ax3.scatter(x[indices], np.abs(Psi[indices, 0]), c='red', s=100, 
               label='DEIM Points', zorder=5)
    ax3.set_xlabel('Position (m)', fontsize=11)
    ax3.set_ylabel('Amplitude', fontsize=11)
    ax3.set_title('DEIM Interpolation Points', fontsize=12, fontweight='bold')
    ax3.legend()
    ax3.grid(True, alpha=0.3)
    
    # 图4:计算成本对比
    ax4 = axes[1, 1]
    methods = ['FOM', 'ROM\n(with DEIM)']
    times = [time_fom, time_rom]
    colors = ['blue', 'green']
    bars = ax4.bar(methods, times, color=colors, alpha=0.7)
    ax4.set_ylabel('Computation Time (s)', fontsize=11)
    ax4.set_title('Computation Time Comparison', fontsize=12, fontweight='bold')
    ax4.grid(True, alpha=0.3, axis='y')
    
    # 添加数值标签
    for bar, time_val in zip(bars, times):
        height = bar.get_height()
        ax4.text(bar.get_x() + bar.get_width()/2., height,
                f'{time_val:.3f}s',
                ha='center', va='bottom', fontsize=10)
    
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case4_nonlinear_deim.png', dpi=150, bbox_inches='tight')
    plt.close()
    print("\n案例4结果已保存")
    
    return Phi, Psi, DEIM_matrix

# =============================================================================
# 案例5:贪婪采样算法演示
# =============================================================================
def case5_greedy_sampling():
    """
    案例5:贪婪采样算法演示
    展示自适应参数采样策略
    """
    print("\n" + "="*70)
    print("案例5: 贪婪采样算法演示")
    print("="*70)
    
    # 参数空间
    mu1_range = np.linspace(0.1, 2.0, 50)  # 参数1
    mu2_range = np.linspace(0.5, 5.0, 50)  # 参数2
    
    print(f"\n参数空间:")
    print(f"  参数1 (μ₁): {mu1_range[0]:.1f} ~ {mu1_range[-1]:.1f}")
    print(f"  参数2 (μ₂): {mu2_range[0]:.1f} ~ {mu2_range[-1]:.1f}")
    print(f"  总网格点数: {len(mu1_range) * len(mu2_range)}")
    
    # 简化的参数化模型(热传导)
    def solve_fom(mu1, mu2):
        """全阶模型求解"""
        nx = 50
        x = np.linspace(0, 1, nx)
        dx = x[1] - x[0]
        
        # 参数化热导率: k = mu1 * (1 + mu2 * x)
        k = mu1 * (1 + mu2 * x)
        
        # 稳态热传导
        T = np.zeros(nx)
        T[0] = 1.0  # 左端温度
        
        # 有限差分求解
        A = np.zeros((nx, nx))
        b = np.zeros(nx)
        
        for i in range(1, nx-1):
            k_avg_left = 0.5 * (k[i-1] + k[i])
            k_avg_right = 0.5 * (k[i] + k[i+1])
            A[i, i-1] = -k_avg_left / dx**2
            A[i, i] = (k_avg_left + k_avg_right) / dx**2
            A[i, i+1] = -k_avg_right / dx**2
        
        A[0, 0] = 1.0
        b[0] = 1.0
        A[-1, -1] = 1.0
        A[-1, -2] = -1.0  # 绝热
        
        T = linalg.solve(A, b)
        return T
    
    # 贪婪采样算法
    print("\n贪婪采样算法...")
    
    # 初始化:选择第一个点(中心)
    n_init = 0
    sample_points = [(mu1_range[len(mu1_range)//2], mu2_range[len(mu2_range)//2])]
    
    max_iterations = 15
    
    for iteration in range(max_iterations):
        # 在当前采样点上构建POD基
        snapshots = []
        for mu1, mu2 in sample_points:
            T = solve_fom(mu1, mu2)
            snapshots.append(T)
        
        snapshots = np.array(snapshots).T
        
        # POD
        if snapshots.shape[1] > 1:
            C = snapshots @ snapshots.T / snapshots.shape[1]
            eigenvalues, eigenvectors = linalg.eigh(C)
            idx = np.argsort(eigenvalues)[::-1]
            Phi = eigenvectors[:, idx[:min(5, snapshots.shape[1])]]
        else:
            Phi = snapshots / linalg.norm(snapshots)
        
        # 在参数空间搜索最大误差点
        max_error = 0
        max_error_point = None
        
        # 在粗网格上搜索(加速)
        for i in range(0, len(mu1_range), 2):
            for j in range(0, len(mu2_range), 2):
                mu1, mu2 = mu1_range[i], mu2_range[j]
                
                # 检查是否已在采样点中
                if any(np.isclose(mu1, sp[0]) and np.isclose(mu2, sp[1]) 
                       for sp in sample_points):
                    continue
                
                # 计算误差估计(使用残差)
                T_fom = solve_fom(mu1, mu2)
                
                # 降阶近似
                if snapshots.shape[1] > 1:
                    a = Phi.T @ T_fom
                    T_rom = Phi @ a
                else:
                    T_rom = Phi.flatten() * (Phi.flatten() @ T_fom)
                
                error = linalg.norm(T_fom - T_rom)
                
                if error > max_error:
                    max_error = error
                    max_error_point = (mu1, mu2)
        
        if max_error_point is None:
            break
        
        # 添加新采样点
        sample_points.append(max_error_point)
        
        print(f"  迭代 {iteration+1}: 添加采样点 μ₁={max_error_point[0]:.3f}, "
              f"μ₂={max_error_point[1]:.3f}, 误差={max_error:.4f}")
        
        # 收敛检查
        if max_error < 0.01 or len(sample_points) >= max_iterations:
            break
    
    print(f"\n最终采样点数: {len(sample_points)}")
    
    # 可视化
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    
    # 图1:采样点分布
    ax1 = axes[0, 0]
    mu1_samples = [sp[0] for sp in sample_points]
    mu2_samples = [sp[1] for sp in sample_points]
    ax1.scatter(mu1_samples, mu2_samples, c='red', s=100, zorder=5)
    for i, (m1, m2) in enumerate(zip(mu1_samples, mu2_samples)):
        ax1.annotate(str(i+1), (m1, m2), xytext=(5, 5), 
                    textcoords='offset points', fontsize=9)
    ax1.set_xlabel('Parameter μ₁', fontsize=11)
    ax1.set_ylabel('Parameter μ₂', fontsize=11)
    ax1.set_title('Greedy Sampling Points', fontsize=12, fontweight='bold')
    ax1.grid(True, alpha=0.3)
    
    # 图2:误差分布
    ax2 = axes[0, 1]
    error_grid = np.zeros((len(mu2_range)//2, len(mu1_range)//2))
    
    for i, idx_i in enumerate(range(0, len(mu1_range), 2)):
        for j, idx_j in enumerate(range(0, len(mu2_range), 2)):
            mu1, mu2 = mu1_range[idx_i], mu2_range[idx_j]
            T_fom = solve_fom(mu1, mu2)
            if snapshots.shape[1] > 1:
                a = Phi.T @ T_fom
                T_rom = Phi @ a
            else:
                T_rom = Phi.flatten() * (Phi.flatten() @ T_fom)
            error_grid[j, i] = linalg.norm(T_fom - T_rom)
    
    im = ax2.contourf(mu1_range[::2], mu2_range[::2], error_grid, levels=20, cmap='hot')
    ax2.scatter(mu1_samples, mu2_samples, c='blue', s=50, marker='x', 
               label='Sample Points')
    plt.colorbar(im, ax=ax2, label='Error')
    ax2.set_xlabel('Parameter μ₁', fontsize=11)
    ax2.set_ylabel('Parameter μ₂', fontsize=11)
    ax2.set_title('Error Distribution', fontsize=12, fontweight='bold')
    ax2.legend()
    
    # 图3:收敛曲线
    ax3 = axes[1, 0]
    # 重新运行以记录收敛历史
    sample_points_test = [sample_points[0]]
    errors_history = []
    
    for iteration in range(min(10, len(sample_points)-1)):
        snapshots_test = []
        for mu1, mu2 in sample_points_test:
            T = solve_fom(mu1, mu2)
            snapshots_test.append(T)
        snapshots_test = np.array(snapshots_test).T
        
        if snapshots_test.shape[1] > 1:
            C = snapshots_test @ snapshots_test.T / snapshots_test.shape[1]
            eigenvalues, eigenvectors = linalg.eigh(C)
            idx = np.argsort(eigenvalues)[::-1]
            Phi_test = eigenvectors[:, idx[:min(3, snapshots_test.shape[1])]]
        else:
            Phi_test = snapshots_test / linalg.norm(snapshots_test)
        
        # 计算平均误差
        errors = []
        for i in range(0, len(mu1_range), 5):
            for j in range(0, len(mu2_range), 5):
                mu1, mu2 = mu1_range[i], mu2_range[j]
                T_fom = solve_fom(mu1, mu2)
                if snapshots_test.shape[1] > 1:
                    a = Phi_test.T @ T_fom
                    T_rom = Phi_test @ a
                else:
                    T_rom = Phi_test.flatten() * (Phi_test.flatten() @ T_fom)
                errors.append(linalg.norm(T_fom - T_rom))
        
        errors_history.append(np.mean(errors))
        
        if iteration < len(sample_points) - 1:
            sample_points_test.append(sample_points[iteration+1])
    
    # 确保维度匹配
    x_data = np.arange(1, len(errors_history)+1)
    ax3.semilogy(x_data, errors_history, 'bo-', linewidth=2)
    ax3.set_xlabel('Number of Sample Points', fontsize=11)
    ax3.set_ylabel('Average Error (log)', fontsize=11)
    ax3.set_title('Convergence of Greedy Algorithm', fontsize=12, fontweight='bold')
    ax3.grid(True, alpha=0.3)
    
    # 图4:POD特征值衰减
    ax4 = axes[1, 1]
    C_final = snapshots @ snapshots.T / snapshots.shape[1]
    eigenvalues_final, _ = linalg.eigh(C_final)
    eigenvalues_final = np.sort(eigenvalues_final)[::-1]
    ax4.semilogy(range(1, min(11, len(eigenvalues_final))+1), 
                eigenvalues_final[:min(10, len(eigenvalues_final))], 
                'ro-', linewidth=2)
    ax4.set_xlabel('Mode Number', fontsize=11)
    ax4.set_ylabel('Eigenvalue (log)', fontsize=11)
    ax4.set_title('POD Eigenvalue Decay', fontsize=12, fontweight='bold')
    ax4.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case5_greedy_sampling.png', dpi=150, bbox_inches='tight')
    plt.close()
    print("\n案例5结果已保存")
    
    return sample_points, Phi

# =============================================================================
# 案例6:降阶模型误差分析
# =============================================================================
def case6_rom_error_analysis():
    """
    案例6:降阶模型误差分析
    系统分析降阶误差与模态数的关系
    """
    print("\n" + "="*70)
    print("案例6: 降阶模型误差分析")
    print("="*70)
    
    # 构建测试问题(二维热传导)
    nx, ny = 30, 30
    Lx, Ly = 1.0, 1.0
    x = np.linspace(0, Lx, nx)
    y = np.linspace(0, Ly, ny)
    X, Y = np.meshgrid(x, y)
    
    print(f"\n问题设置:")
    print(f"  网格: {nx} x {ny}")
    print(f"  域尺寸: {Lx:.1f} x {Ly:.1f}")
    
    # 生成训练数据(不同边界条件)
    n_train = 20
    snapshots = []
    
    print(f"\n生成训练数据 ({n_train}个工况)...")
    
    for i in range(n_train):
        # 随机边界条件
        T_left = np.random.uniform(0, 100)
        T_right = np.random.uniform(0, 100)
        T_top = np.random.uniform(0, 100)
        T_bottom = np.random.uniform(0, 100)
        
        # 简化的稳态解(使用解析近似)
        T = (T_left * (Lx - X) + T_right * X) / Lx * 0.5 + \
            (T_bottom * (Ly - Y) + T_top * Y) / Ly * 0.5
        
        snapshots.append(T.flatten())
    
    snapshots = np.array(snapshots).T
    
    # POD分析
    print("\nPOD分析...")
    C = snapshots @ snapshots.T / snapshots.shape[1]
    eigenvalues, eigenvectors = linalg.eigh(C)
    
    idx = np.argsort(eigenvalues)[::-1]
    eigenvalues = eigenvalues[idx]
    Phi = eigenvectors[:, idx]
    
    cumulative_energy = np.cumsum(eigenvalues) / np.sum(eigenvalues)
    
    # 生成测试数据
    n_test = 10
    test_snapshots = []
    
    for i in range(n_test):
        T_left = np.random.uniform(0, 100)
        T_right = np.random.uniform(0, 100)
        T_top = np.random.uniform(0, 100)
        T_bottom = np.random.uniform(0, 100)
        
        T = (T_left * (Lx - X) + T_right * X) / Lx * 0.5 + \
            (T_bottom * (Ly - Y) + T_top * Y) / Ly * 0.5
        
        test_snapshots.append(T.flatten())
    
    test_snapshots = np.array(test_snapshots).T
    
    # 误差分析(不同模态数)
    print("\n误差分析...")
    n_modes_list = [1, 2, 3, 5, 8, 10, 15, 20]
    errors_l2 = []
    errors_max = []
    
    for n_modes in n_modes_list:
        Phi_n = Phi[:, :n_modes]
        
        # 在测试数据上评估误差
        errors_l2_sample = []
        errors_max_sample = []
        
        for i in range(test_snapshots.shape[1]):
            T_true = test_snapshots[:, i]
            a = Phi_n.T @ T_true
            T_rom = Phi_n @ a
            
            error = T_true - T_rom
            errors_l2_sample.append(linalg.norm(error) / linalg.norm(T_true))
            errors_max_sample.append(np.max(np.abs(error)))
        
        errors_l2.append(np.mean(errors_l2_sample))
        errors_max.append(np.mean(errors_max_sample))
        
        print(f"  模态数 {n_modes:2d}: L2误差 = {errors_l2[-1]:.4f}, "
              f"最大误差 = {errors_max[-1]:.2f}")
    
    # 可视化
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    
    # 图1:特征值衰减
    ax1 = axes[0, 0]
    n_eig_show = min(20, len(eigenvalues))
    ax1.semilogy(range(1, n_eig_show+1), eigenvalues[:n_eig_show], 'bo-', linewidth=2)
    ax1.set_xlabel('Mode Number', fontsize=11)
    ax1.set_ylabel('Eigenvalue (log scale)', fontsize=11)
    ax1.set_title('POD Eigenvalue Decay', fontsize=12, fontweight='bold')
    ax1.grid(True, alpha=0.3)
    
    # 图2:累积能量
    ax2 = axes[0, 1]
    n_cum_show = min(20, len(cumulative_energy))
    ax2.plot(range(1, n_cum_show+1), cumulative_energy[:n_cum_show] * 100, 
            'ro-', linewidth=2)
    ax2.axhline(y=99, color='k', linestyle='--', alpha=0.5, label='99%')
    ax2.set_xlabel('Number of Modes', fontsize=11)
    ax2.set_ylabel('Cumulative Energy (%)', fontsize=11)
    ax2.set_title('Cumulative Energy Content', fontsize=12, fontweight='bold')
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    
    # 图3:误差随模态数变化
    ax3 = axes[1, 0]
    ax3.semilogy(n_modes_list, errors_l2, 'bo-', linewidth=2, label='L2 Error')
    ax3.semilogy(n_modes_list, np.array(errors_max)/100, 'rs-', linewidth=2, 
                label='Max Error/100')
    ax3.set_xlabel('Number of Modes', fontsize=11)
    ax3.set_ylabel('Error (log scale)', fontsize=11)
    ax3.set_title('ROM Error vs Number of Modes', fontsize=12, fontweight='bold')
    ax3.legend()
    ax3.grid(True, alpha=0.3)
    
    # 图4:典型模态可视化
    ax4 = axes[1, 1]
    # 显示前4个POD模态
    n_show = min(4, Phi.shape[1])
    fig2, axes2 = plt.subplots(2, 2, figsize=(10, 8))
    for i in range(n_show):
        ax = axes2[i//2, i%2]
        mode = Phi[:, i].reshape(ny, nx)
        im = ax.contourf(X, Y, mode, levels=20, cmap='RdBu_r')
        ax.set_title(f'POD Mode {i+1}', fontsize=10)
        ax.set_xlabel('x')
        ax.set_ylabel('y')
        plt.colorbar(im, ax=ax)
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case6_pod_modes.png', dpi=150, bbox_inches='tight')
    plt.close()
    
    # 在原图中显示误差分布示例
    T_example = test_snapshots[:, 0]
    n_optimal = 10
    Phi_opt = Phi[:, :n_optimal]
    a_opt = Phi_opt.T @ T_example
    T_rom_example = Phi_opt @ a_opt
    error_example = T_example - T_rom_example
    
    error_2d = error_example.reshape(ny, nx)
    im = ax4.contourf(X, Y, np.abs(error_2d), levels=20, cmap='hot')
    plt.colorbar(im, ax=ax4, label='|Error|')
    ax4.set_xlabel('x', fontsize=11)
    ax4.set_ylabel('y', fontsize=11)
    ax4.set_title(f'ROM Error Distribution (N={n_optimal})', fontsize=12, fontweight='bold')
    
    plt.tight_layout()
    plt.savefig(f'{output_dir}/case6_rom_error_analysis.png', dpi=150, bbox_inches='tight')
    plt.close()
    print("\n案例6结果已保存")
    
    return n_modes_list, errors_l2, errors_max, Phi

# =============================================================================
# 主程序
# =============================================================================
if __name__ == "__main__":
    print("\n" + "="*70)
    print("开始运行多物理场模型降阶仿真案例")
    print("="*70)
    
    # 运行所有案例
    results = {}
    
    # 案例1:热-结构耦合板的POD降阶
    try:
        results['case1'] = case1_thermal_structural_pod()
    except Exception as e:
        print(f"\n案例1运行出错: {e}")
    
    # 案例2:圆柱绕流流-固耦合的DMD分析
    try:
        results['case2'] = case2_cylinder_dmd()
    except Exception as e:
        print(f"\n案例2运行出错: {e}")
    
    # 案例3:参数化微镜阵列的全局POD降阶
    try:
        results['case3'] = case3_parametric_micromirror_pod()
    except Exception as e:
        print(f"\n案例3运行出错: {e}")
    
    # 案例4:非线性系统的DEIM降阶
    try:
        results['case4'] = case4_nonlinear_deim()
    except Exception as e:
        print(f"\n案例4运行出错: {e}")
    
    # 案例5:贪婪采样算法演示
    try:
        results['case5'] = case5_greedy_sampling()
    except Exception as e:
        print(f"\n案例5运行出错: {e}")
    
    # 案例6:降阶模型误差分析
    try:
        results['case6'] = case6_rom_error_analysis()
    except Exception as e:
        print(f"\n案例6运行出错: {e}")
    
    print("\n" + "="*70)
    print("所有案例运行完成!")
    print(f"结果保存在: {output_dir}")
    print("="*70)
Logo

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

更多推荐