主题037:辐射换分的降阶模型

摘要

降阶模型(Reduced-Order Model, ROM)是应对辐射换热仿真计算成本高昂问题的有效手段。本主题系统介绍本征正交分解(POD)、动态模态分解(DMD)及其变体在辐射换热问题中的应用。通过提取高维系统的低维本质特征,降阶模型能够在保持精度的同时实现数量级的计算加速。本主题详细阐述降阶模型的理论基础、构造方法、在线求解策略以及误差控制技术,并通过多个工程案例展示其在瞬态辐射-导热耦合、辐射传热优化和实时控制中的实际应用效果。

关键词

降阶模型、本征正交分解、动态模态分解、伽辽金投影、本征模态、计算加速、模型降维


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

1. 引言

1.1 辐射换热仿真的计算挑战

辐射换热是高温系统中主导的传热方式,其数值模拟面临独特的计算挑战。与导热和对流不同,辐射传递具有全域耦合特性——每个微元体与系统中所有其他微元体都存在辐射交换,这导致辐射换热矩阵通常是稠密的。对于具有复杂几何形状和光谱相关性的实际工程问题,完整的辐射换热计算可能消耗90%以上的总计算时间。

以典型的工业炉窑模拟为例,一个包含10万个控制体的三维模型,每次辐射换热计算需要求解同等规模的稠密线性系统。在瞬态模拟中,这一计算需要在每个时间步重复进行;在优化设计中,可能需要数千次重复计算;在实时控制应用中,计算延迟直接限制了控制品质。因此,开发高效的辐射换热降阶模型具有重要的工程价值。

1.2 降阶模型的基本概念

降阶模型的核心思想是:尽管原始系统的状态空间维度很高,但系统的实际动态行为往往被限制在一个低维流形上。通过识别并提取这个低维子空间,可以用少得多的自由度来近似描述系统行为。

数学上,考虑一个高维离散系统:

Mdudt+Ku=f(u)\mathbf{M}\frac{d\mathbf{u}}{dt} + \mathbf{K}\mathbf{u} = \mathbf{f}(\mathbf{u})Mdtdu+Ku=f(u)

其中 u∈RN\mathbf{u} \in \mathbb{R}^NuRN 是状态向量,NNN 可能达到 10510^5105 甚至更高。降阶模型假设解可以表示为:

u(t)≈Φa(t)=∑i=1rϕiai(t)\mathbf{u}(t) \approx \mathbf{\Phi}\mathbf{a}(t) = \sum_{i=1}^{r} \phi_i a_i(t)u(t)Φa(t)=i=1rϕiai(t)

其中 Φ=[ϕ1,ϕ2,...,ϕr]∈RN×r\mathbf{\Phi} = [\phi_1, \phi_2, ..., \phi_r] \in \mathbb{R}^{N \times r}Φ=[ϕ1,ϕ2,...,ϕr]RN×r 是降阶基矩阵,a(t)∈Rr\mathbf{a}(t) \in \mathbb{R}^ra(t)Rr 是模态系数向量,r≪Nr \ll NrN

1.3 降阶模型的分类

根据构造方法的不同,辐射换热降阶模型可以分为以下几类:

基于快照的方法:利用高保真仿真的结果(快照)提取模态结构,包括POD、DMD及其变体。这类方法数据驱动,实现简单,但需要预先进行充分的高保真计算。

基于物理的方法:直接利用控制方程的结构信息构造降阶基,如平衡截断、Krylov子空间方法。这类方法具有更强的数学保证,但对问题结构有特定要求。

基于机器学习的方法:使用神经网络学习从参数空间到降阶系数的映射,如主题036讨论的神经网络代理模型。这类方法灵活性高,但需要大量训练数据。

本主题重点讨论基于快照的方法,特别是POD和DMD,因为它们在辐射换热应用中最为成熟和广泛使用。


2. 本征正交分解(POD)理论基础

2.1 POD的数学原理

本征正交分解(Proper Orthogonal Decomposition),也称为主成分分析(PCA)或Karhunen-Loève展开,是一种寻找最优低维表示的数据驱动方法。给定一组快照数据 {u1,u2,...,uM}\{\mathbf{u}_1, \mathbf{u}_2, ..., \mathbf{u}_M\}{u1,u2,...,uM},POD寻找一组正交基 {ϕi}\{\phi_i\}{ϕi},使得在这些基上的投影能够最优地重构原始数据。

"最优"的数学定义是:在所有可能的 rrr 维基展开中,POD基最小化重构误差:

min⁡{ϕi}∑k=1M∥uk−∑i=1r(ukTϕi)ϕi∥2\min_{\{\phi_i\}} \sum_{k=1}^{M} \left\| \mathbf{u}_k - \sum_{i=1}^{r} (\mathbf{u}_k^T\phi_i)\phi_i \right\|^2{ϕi}mink=1M uki=1r(ukTϕi)ϕi 2

受约束于 ϕiTϕj=δij\phi_i^T\phi_j = \delta_{ij}ϕiTϕj=δij

2.2 快照矩阵与奇异值分解

将快照数据排列成快照矩阵:

U=[u1,u2,...,uM]∈RN×M\mathbf{U} = [\mathbf{u}_1, \mathbf{u}_2, ..., \mathbf{u}_M] \in \mathbb{R}^{N \times M}U=[u1,u2,...,uM]RN×M

POD基可以通过对 U\mathbf{U}U 进行奇异值分解(SVD)获得:

U=ΦΣVT\mathbf{U} = \mathbf{\Phi}\mathbf{\Sigma}\mathbf{V}^TU=ΦΣVT

其中 Φ=[ϕ1,ϕ2,...,ϕN]\mathbf{\Phi} = [\phi_1, \phi_2, ..., \phi_N]Φ=[ϕ1,ϕ2,...,ϕN] 是左奇异向量矩阵,Σ=diag(σ1,σ2,...,σmin⁡(N,M))\mathbf{\Sigma} = \text{diag}(\sigma_1, \sigma_2, ..., \sigma_{\min(N,M)})Σ=diag(σ1,σ2,...,σmin(N,M)) 是奇异值矩阵,V\mathbf{V}V 是右奇异向量矩阵。

rrr 个左奇异向量 {ϕ1,...,ϕr}\{\phi_1, ..., \phi_r\}{ϕ1,...,ϕr} 构成了最优的 rrr 维POD基。奇异值 σi\sigma_iσi 的大小反映了对应模态在数据中的能量占比。

2.3 能量截断与模型阶数选择

确定降阶模型的阶数 rrr 是POD应用中的关键问题。常用的方法是基于累积能量(cumulative energy)准则:

E(r)=∑i=1rσi2∑i=1min⁡(N,M)σi2E(r) = \frac{\sum_{i=1}^{r} \sigma_i^2}{\sum_{i=1}^{\min(N,M)} \sigma_i^2}E(r)=i=1min(N,M)σi2i=1rσi2

通常选择最小的 rrr 使得 E(r)≥0.99E(r) \geq 0.99E(r)0.990.9990.9990.999,即保留99%或99.9%的能量。

另一种方法是观察奇异值的衰减曲线,在"拐点"处截断。快速衰减的奇异值谱表明系统具有很强的低维结构,适合使用降阶模型。

2.4 POD在辐射换热中的特殊考虑

辐射换热问题具有一些特殊性质,需要在应用POD时特别考虑:

非线性特性:辐射换热系数与温度的四次方相关,导致系统具有强非线性。标准POD是线性方法,对于强非线性系统可能需要更多模态或采用非线性降阶技术。

参数依赖性:辐射特性(发射率、吸收系数)可能随材料、波长、温度变化。需要构建参数化的POD模型,或使用插值技术处理参数变化。

瞬态行为:瞬态辐射-导热耦合问题中,不同时刻的快照可能呈现不同的空间结构。需要考虑时间窗口策略或采用多尺度POD方法。


3. POD-Galerkin降阶模型构造

3.1 Galerkin投影原理

获得POD基后,下一步是将其投影到控制方程上,得到降阶动力学方程。考虑一般的半离散系统:

dudt=f(u,t;μ)\frac{d\mathbf{u}}{dt} = \mathbf{f}(\mathbf{u}, t; \mu)dtdu=f(u,t;μ)

其中 μ\muμ 表示系统参数。将 u≈Φa\mathbf{u} \approx \mathbf{\Phi}\mathbf{a}uΦa 代入并在方程两边左乘 ΦT\mathbf{\Phi}^TΦT

ΦTΦdadt=ΦTf(Φa,t;μ)\mathbf{\Phi}^T\mathbf{\Phi}\frac{d\mathbf{a}}{dt} = \mathbf{\Phi}^T\mathbf{f}(\mathbf{\Phi}\mathbf{a}, t; \mu)ΦTΦdtda=ΦTf(Φa,t;μ)

由于POD基的正交性 ΦTΦ=I\mathbf{\Phi}^T\mathbf{\Phi} = \mathbf{I}ΦTΦ=I,得到降阶方程:

dadt=ΦTf(Φa,t;μ)=f~(a,t;μ)\frac{d\mathbf{a}}{dt} = \mathbf{\Phi}^T\mathbf{f}(\mathbf{\Phi}\mathbf{a}, t; \mu) = \tilde{\mathbf{f}}(\mathbf{a}, t; \mu)dtda=ΦTf(Φa,t;μ)=f~(a,t;μ)

这是一个 rrr 维系统,远小于原始 NNN 维系统。

3.2 辐射换热的POD-Galerkin实现

对于辐射-导热耦合问题,控制方程可以写为:

ρcp∂T∂t=∇⋅(k∇T)−∇⋅qr\rho c_p \frac{\partial T}{\partial t} = \nabla \cdot (k\nabla T) - \nabla \cdot \mathbf{q}_rρcptT=(kT)qr

其中辐射热流 qr\mathbf{q}_rqr 通过求解辐射传递方程获得。离散化后得到:

MdTdt+KT=Qr(T)\mathbf{M}\frac{d\mathbf{T}}{dt} + \mathbf{K}\mathbf{T} = \mathbf{Q}_r(\mathbf{T})MdtdT+KT=Qr(T)

应用POD-Galerkin投影:

M~dadt+K~a=Q~r(a)\tilde{\mathbf{M}}\frac{d\mathbf{a}}{dt} + \tilde{\mathbf{K}}\mathbf{a} = \tilde{\mathbf{Q}}_r(\mathbf{a})M~dtda+K~a=Q~r(a)

其中:

  • M~=ΦTMΦ∈Rr×r\tilde{\mathbf{M}} = \mathbf{\Phi}^T\mathbf{M}\mathbf{\Phi} \in \mathbb{R}^{r \times r}M~=ΦTRr×r
  • K~=ΦTKΦ∈Rr×r\tilde{\mathbf{K}} = \mathbf{\Phi}^T\mathbf{K}\mathbf{\Phi} \in \mathbb{R}^{r \times r}K~=ΦTRr×r
  • Q~r(a)=ΦTQr(Φa)∈Rr\tilde{\mathbf{Q}}_r(\mathbf{a}) = \mathbf{\Phi}^T\mathbf{Q}_r(\mathbf{\Phi}\mathbf{a}) \in \mathbb{R}^rQ~r(a)=ΦTQr(Φa)Rr

3.3 非线性项的高效计算

辐射热源项 Qr\mathbf{Q}_rQr 是温度的强非线性函数,直接计算 Qr(Φa)\mathbf{Q}_r(\mathbf{\Phi}\mathbf{a})Qr(Φa) 需要在高维空间进行,破坏了降阶模型的计算效率。解决这个问题的方法包括:

离散经验插值(DEIM):通过选择少量的"魔法点",将非线性项的插值问题转化为低维问题。设 Qr\mathbf{Q}_rQr 本身也可以用POD基近似:

Qr(T)≈Ψc(T)\mathbf{Q}_r(\mathbf{T}) \approx \mathbf{\Psi}\mathbf{c}(\mathbf{T})Qr(T)Ψc(T)

其中 Ψ\mathbf{\Psi}Ψ 是非线性项的POD基。DEIM选择索引矩阵 P\mathbf{P}P,使得:

Qr(T)≈Ψ(PTΨ)−1PTQr(T)\mathbf{Q}_r(\mathbf{T}) \approx \mathbf{\Psi}(\mathbf{P}^T\mathbf{\Psi})^{-1}\mathbf{P}^T\mathbf{Q}_r(\mathbf{T})Qr(T)Ψ(PTΨ)1PTQr(T)

这样只需要在选定的魔法点计算 Qr\mathbf{Q}_rQr,大大降低了计算成本。

超缩减技术:包括GNAT(Gauss-Newton with Approximated Tensors)等方法,通过采样策略进一步降低计算量。

预计算查表:对于参数变化范围有限的问题,可以预先计算并存储 Q~r\tilde{\mathbf{Q}}_rQ~r 在降阶系数空间中的值,在线求解时通过插值获得。

3.4 误差分析与收敛性

POD-Galerkin降阶模型的误差来源于两个方面:

截断误差:由于只保留了前 rrr 个模态,忽略了高阶模态的贡献。这部分误差可以通过增加模态数来减小,其衰减速率与奇异值的衰减速率相同。

投影误差:Galerkin投影本身引入的近似,特别是对于非线性项的处理。这部分误差与具体的问题和降阶方法有关。

对于线性系统,POD-Galerkin模型具有最优的收敛性质。对于非线性系统,收敛性分析更为复杂,但数值经验表明,只要模态数足够,降阶模型可以达到很高的精度。


4. 动态模态分解(DMD)方法

4.1 DMD的基本思想

动态模态分解(Dynamic Mode Decomposition)是一种从时间序列数据中提取动态特征的数据驱动方法。与POD关注空间结构不同,DMD关注系统的时序演化规律。

给定一对快照序列 {u1,u2,...,uM}\{\mathbf{u}_1, \mathbf{u}_2, ..., \mathbf{u}_M\}{u1,u2,...,uM}{u2,u3,...,uM+1}\{\mathbf{u}_2, \mathbf{u}_3, ..., \mathbf{u}_{M+1}\}{u2,u3,...,uM+1},DMD寻找最佳拟合的线性动力学:

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

其中 A\mathbf{A}A 是近似的状态转移矩阵。DMD的特征值和特征向量(称为DMD模态和特征值)揭示了系统的固有频率、增长率和空间结构。

4.2 DMD算法实现

标准的DMD算法步骤如下:

步骤1:构造快照矩阵

X=[u1,u2,...,uM],Y=[u2,u3,...,uM+1]\mathbf{X} = [\mathbf{u}_1, \mathbf{u}_2, ..., \mathbf{u}_M], \quad \mathbf{Y} = [\mathbf{u}_2, \mathbf{u}_3, ..., \mathbf{u}_{M+1}]X=[u1,u2,...,uM],Y=[u2,u3,...,uM+1]

步骤2:对 X\mathbf{X}X 进行SVD分解

X=UΣVT\mathbf{X} = \mathbf{U}\mathbf{\Sigma}\mathbf{V}^TX=VT

步骤3:计算投影后的状态转移矩阵

A~=UTYVΣ−1\tilde{\mathbf{A}} = \mathbf{U}^T\mathbf{Y}\mathbf{V}\mathbf{\Sigma}^{-1}A~=UTYVΣ1

这是一个 r×rr \times rr×r 的小矩阵,其中 rrr 是截断的秩。

步骤4:对 A~\tilde{\mathbf{A}}A~ 进行特征值分解

A~W=WΛ\tilde{\mathbf{A}}\mathbf{W} = \mathbf{W}\mathbf{\Lambda}A~W=

步骤5:重构DMD模态

Φ=YVΣ−1W\mathbf{\Phi} = \mathbf{Y}\mathbf{V}\mathbf{\Sigma}^{-1}\mathbf{W}Φ=YVΣ1W

4.3 DMD特征值的物理意义

DMD特征值 λ\lambdaλ 通常表示为复数形式。设 λ=α+iβ\lambda = \alpha + i\betaλ=α+iβ,则:

增长率Re(ln⁡(λ)/Δt)\text{Re}(\ln(\lambda)/\Delta t)Re(ln(λ)t),正值表示不稳定模态,负值表示衰减模态。

频率Im(ln⁡(λ)/Δt)/(2π)\text{Im}(\ln(\lambda)/\Delta t)/(2\pi)Im(ln(λ)t)/(2π),表示模态的振荡频率。

周期1/frequency1/\text{frequency}1/frequency,表示完成一个振荡周期所需的时间。

在辐射换热问题中,DMD可以识别系统的热惯性、响应时间尺度以及不同区域之间的热耦合模式。

4.4 DMD变体与扩展

压缩DMD(Compressed DMD):对于超大规模问题,通过随机投影将数据压缩到低维空间,在压缩空间进行DMD,大幅降低计算成本。

前向-后向DMD(Forward-Backward DMD):利用前向和后向时间演化的信息,提高对噪声的鲁棒性。

多分辨率DMD(Multi-Resolution DMD):处理具有多个时间尺度的系统,在不同时间窗口分别进行DMD分析。

参数化DMD(Parametric DMD):处理参数变化的情况,通过插值或回归建立参数与DMD特征的关系。

4.5 DMD在辐射换热中的应用场景

DMD特别适用于以下辐射换热问题:

瞬态响应分析:识别系统在扰动下的响应模式,如炉窑升温过程中的温度演化规律。

稳定性分析:通过DMD特征值的实部判断系统的稳定性,识别可能导致温度失控的模态。

控制设计:DMD模态可以作为控制设计的基函数,实现基于模态的控制策略。

数据压缩:DMD提供了一种紧凑的时序数据表示,可用于长期仿真的数据存储和快速回放。


5. 高级降阶技术

5.1 参数化降阶模型

实际工程问题中,辐射换热系统往往涉及多个参数,如几何尺寸、材料属性、边界条件等。参数化降阶模型(pROM)能够在参数空间中快速预测系统响应。

插值方法:在不同参数点分别构建降阶模型,通过插值获得新参数点的模型。插值可以在矩阵级别(插值降阶矩阵)或模态级别(插值模态形状)进行。

贪婪采样:通过自适应采样策略,在参数空间中选择最具"信息量"的点进行高保真计算。常用的准则是基于后验误差估计或残差范数。

仿射分解:将参数依赖的算子分解为参数无关部分和参数相关系数的乘积:

A(μ)=∑q=1Qθq(μ)Aq\mathbf{A}(\mu) = \sum_{q=1}^{Q} \theta_q(\mu)\mathbf{A}_qA(μ)=q=1Qθq(μ)Aq

这样降阶矩阵可以预先计算:

A~(μ)=∑q=1Qθq(μ)ΦTAqΦ\tilde{\mathbf{A}}(\mu) = \sum_{q=1}^{Q} \theta_q(\mu)\mathbf{\Phi}^T\mathbf{A}_q\mathbf{\Phi}A~(μ)=q=1Qθq(μ)ΦTAqΦ

在线计算时只需要组合这些预计算的矩阵。

5.2 非线性降阶方法

对于强非线性辐射换热问题,线性POD-Galerkin可能需要大量模态。非线性降阶方法包括:

二次流形:假设解位于一个二次流形上,而非线性子空间。这可以通过对POD系数施加二次约束来实现。

局部POD:将状态空间划分为多个区域,每个区域使用专门的POD基。通过分类器或聚类确定当前状态所属的区域。

神经网络增强:使用神经网络学习从降阶系数到非线性项的映射,替代昂贵的投影计算。

5.3 多物理场耦合降阶

辐射换热通常与导热、对流、燃烧等其他物理过程耦合。多物理场降阶需要协调不同物理量的降阶基:

分区降阶:每个物理场使用独立的降阶基,通过界面条件耦合。

统一降阶:将所有物理量组合成一个大向量,进行联合POD分析。这种方法可以捕捉物理场之间的耦合关系,但可能导致模态物理意义不明确。

迭代耦合:在降阶空间中交替求解各个物理场,直到收敛。

5.4 不确定性量化与降阶模型

降阶模型本身引入了额外的近似误差,在进行不确定性量化时需要考虑:

双层级方法:外层采样不确定性参数,内层使用降阶模型进行快速求解。

误差估计:构建降阶模型的后验误差估计器,用于自适应地决定是否需要高保真修正。

统计修正:通过少量高保真样本建立降阶模型误差的统计模型,对降阶预测进行修正。


6. 工程应用案例

6.1 案例一:航天器热控系统瞬态分析

问题描述:航天器在轨道运行中经历周期性的日照和阴影,需要分析其温度响应。完整的三维辐射-导热耦合模型包含数十万个自由度,单次轨道周期仿真需要数小时。

降阶模型构建

  1. 收集不同轨道位置、不同初始条件下的温度快照
  2. 进行POD分析,提取前20个模态(保留99.5%能量)
  3. 使用DEIM处理辐射非线性项,选择200个魔法点
  4. 构建POD-Galerkin降阶模型

结果:降阶模型将单次轨道周期仿真时间从3小时降低到2分钟,加速比约90倍,温度预测误差小于1K。

关键发现

  • 前3个POD模态对应于航天器的整体加热/冷却趋势
  • 第4-10个模态描述了不同舱段之间的温差演化
  • 高频模态主要对应于局部热斑的快速响应

6.2 案例二:工业炉窑燃烧优化

问题描述:优化工业炉窑的燃烧器布置和功率分配,以实现炉膛内温度均匀分布。优化过程需要评估数千种不同的工况配置。

降阶模型构建

  1. 使用拉丁超立方采样生成200个训练工况
  2. 对每个工况进行稳态辐射-燃烧耦合计算
  3. 构建参数化POD模型,参数包括燃烧器位置和功率
  4. 使用RBF插值处理参数变化

结果:降阶模型将单次工况评估时间从45分钟降低到3秒,整个优化过程从数月缩短到数天。

关键发现

  • 炉膛温度场可以用15个POD模态很好地近似
  • 参数化模型在训练参数范围内具有良好的外推能力
  • 最优解与经验设计相比,温度均匀性提高了35%

6.3 案例三:太阳能集热器阵列实时控制

问题描述:大型太阳能集热器阵列需要根据太阳位置和云层遮挡实时调整各集热器的角度,最大化集热效率。控制算法需要在秒级时间内完成优化计算。

降阶模型构建

  1. 使用DMD分析集热器阵列在不同太阳角度下的瞬态响应
  2. 识别出主导的热惯性模态和阴影传播模态
  3. 构建基于DMD的状态空间模型用于预测控制
  4. 在线更新DMD模型以适应天气变化

结果:基于降阶模型的预测控制将集热效率提高了12%,计算延迟从分钟级降低到秒级。

关键发现

  • DMD识别出的主导模态对应于集热器管内的流体热惯性
  • 阴影传播模态揭示了云层移动对下游集热器的影响规律
  • 在线更新策略有效应对了天气条件的变化

7. 降阶模型的验证与误差控制

7.1 验证框架

降阶模型的验证需要回答三个层次的问题:

代码验证:降阶模型实现是否正确?通过与解析解或已验证的高保真代码对比来确认。

解验证:降阶模型是否收敛到正确解?通过网格/模态细化研究,确认解的收敛性。

模型确认:降阶模型是否适用于目标应用?通过与实验数据对比,确认模型的预测能力。

7.2 误差估计方法

后验误差估计:基于残差范数估计当前解的误差:

η=∥R(uROM)∥\eta = \|\mathbf{R}(\mathbf{u}_{ROM})\|η=R(uROM)

其中 R\mathbf{R}R 是控制方程的残差。如果误差超过阈值,可以触发高保真计算进行修正。

先验误差估计:基于奇异值衰减估计截断误差的上界。

统计误差估计:通过交叉验证或自助法估计降阶模型的预测不确定性。

7.3 自适应降阶策略

自适应基更新:当系统状态发生显著变化时(如相变、几何变形),原有的POD基可能不再适用。自适应策略监测误差指标,在必要时重新计算POD基。

局部基切换:预先计算多个工况下的POD基,在线求解时根据当前参数选择最合适的基。

分层降阶:使用不同精度的降阶模型层次,根据精度要求动态切换。


# -*- coding: utf-8 -*-
"""
主题037:辐射换分的降阶模型
演示POD和DMD在辐射换热问题中的应用
"""

import matplotlib
matplotlib.use('Agg')
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from scipy.integrate import odeint
from scipy.sparse import diags
from scipy.sparse.linalg import spsolve
from scipy.linalg import svd, eig
import warnings
warnings.filterwarnings('ignore')
import os

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

# 物理常数
sigma = 5.67e-8  # Stefan-Boltzmann常数

print("=" * 70)
print("主题037:辐射换分的降阶模型")
print("=" * 70)

# 案例1:POD降阶模型 - 一维瞬态辐射导热耦合
print("\n" + "=" * 70)
print("案例1:POD降阶模型 - 一维瞬态辐射导热耦合")
print("=" * 70)

def case1_pod_rom():
    """
    案例1:使用POD构建一维瞬态辐射导热耦合问题的降阶模型
    """
    np.random.seed(42)
    
    # 问题设置
    L = 1.0  # 域长度 (m)
    N = 100  # 高保真模型网格数
    dx = L / (N - 1)
    x = np.linspace(0, L, N)
    
    # 材料参数
    rho = 1000  # 密度 (kg/m^3)
    cp = 1000   # 比热容 (J/kg·K)
    k = 1.0     # 导热系数 (W/m·K)
    eps = 0.8   # 表面发射率
    
    # 辐射参数(简化模型)
    kappa = 1.0  # 吸收系数 (1/m)
    
    # 高保真模型:瞬态辐射导热耦合
    def high_fidelity_model(T, t, q_in):
        """
        高保真模型:求解瞬态辐射导热耦合
        dT/dt = (k/rho/cp) * d²T/dx² - (1/rho/cp) * dqr/dx
        """
        T = np.maximum(T, 300)  # 防止温度过低
        
        # 导热项(中心差分)
        d2T_dx2 = np.zeros(N)
        d2T_dx2[1:-1] = (T[2:] - 2*T[1:-1] + T[:-2]) / dx**2
        
        # 边界条件(Neumann)
        d2T_dx2[0] = (T[1] - T[0]) / dx**2
        d2T_dx2[-1] = (T[-2] - T[-1]) / dx**2
        
        conduction = (k / rho / cp) * d2T_dx2
        
        # 辐射热流(简化模型:两通量近似)
        # qr = - (4*sigma/3/kappa) * d(T^4)/dx
        T4 = T**4
        dT4_dx = np.zeros(N)
        dT4_dx[1:-1] = (T4[2:] - T4[:-2]) / (2*dx)
        dT4_dx[0] = (T4[1] - T4[0]) / dx
        dT4_dx[-1] = (T4[-1] - T4[-2]) / dx
        
        qr = - (4 * sigma / (3 * kappa)) * dT4_dx
        
        # 辐射散度
        div_qr = np.zeros(N)
        div_qr[1:-1] = (qr[2:] - qr[:-2]) / (2*dx)
        div_qr[0] = (qr[1] - qr[0]) / dx
        div_qr[-1] = (qr[-1] - qr[-2]) / dx
        
        radiation = - div_qr / (rho * cp)
        
        # 边界热流
        boundary = np.zeros(N)
        boundary[0] = q_in / (rho * cp * dx)  # 左边界热流输入
        boundary[-1] = -eps * sigma * (T[-1]**4 - 300**4) / (rho * cp * dx)  # 右边界辐射散热
        
        dTdt = conduction + radiation + boundary
        return dTdt
    
    # 生成训练快照(不同边界条件)
    print("生成高保真训练数据...")
    n_snapshots = 50
    n_time_steps = 100
    t_span = np.linspace(0, 1000, n_time_steps)  # 时间范围 (s)
    
    snapshots = []
    q_values = np.linspace(1000, 10000, n_snapshots)  # 不同的边界热流
    
    for i, q_in in enumerate(q_values):
        T0 = np.ones(N) * 300  # 初始温度
        sol = odeint(high_fidelity_model, T0, t_span, args=(q_in,))
        # 保存每个时间步的解
        for j in range(0, n_time_steps, 5):  # 每隔5步保存
            snapshots.append(sol[j])
        if (i + 1) % 10 == 0:
            print(f"  完成 {(i+1)/n_snapshots*100:.0f}%")
    
    snapshots = np.array(snapshots).T  # 形状: (N, n_snapshots_total)
    print(f"  生成 {snapshots.shape[1]} 个快照")
    
    # POD分析
    print("\n执行POD分析...")
    U, S, Vt = svd(snapshots, full_matrices=False)
    
    # 计算累积能量
    cumulative_energy = np.cumsum(S**2) / np.sum(S**2)
    
    # 选择模态数(保留99.9%能量)
    r = np.argmax(cumulative_energy >= 0.999) + 1
    print(f"  保留 {r} 个POD模态(保留 {cumulative_energy[r-1]*100:.2f}% 能量)")
    
    # 提取POD基
    Phi = U[:, :r]  # POD基矩阵
    
    # 构建降阶模型
    print("\n构建POD-Galerkin降阶模型...")
    
    # 投影质量矩阵和刚度矩阵(简化处理)
    M_tilde = np.eye(r)  # 假设质量矩阵是单位阵
    
    # 降阶模型求解函数
    def rom_model(a, t, q_in, Phi, N, dx):
        """
        降阶模型:求解模态系数
        """
        # 重构高维温度场
        T = Phi @ a + 300  # 加上基准温度
        T = np.maximum(T, 300)
        
        # 计算高保真残差
        dTdt_full = high_fidelity_model(T, t, q_in)
        
        # 投影到降阶空间
        dadt = Phi.T @ dTdt_full
        return dadt
    
    # 测试降阶模型
    print("\n测试降阶模型...")
    q_test = 5000  # 测试边界热流
    T0 = np.ones(N) * 300
    a0 = np.zeros(r)  # 初始模态系数
    
    # 高保真解
    t_test = np.linspace(0, 1000, 200)
    print("  计算高保真解...")
    T_hf = odeint(high_fidelity_model, T0, t_test, args=(q_test,))
    
    # 降阶解
    print("  计算降阶解...")
    a_rom = odeint(lambda a, t: rom_model(a, t, q_test, Phi, N, dx), a0, t_test)
    T_rom = Phi @ a_rom.T + 300
    T_rom = T_rom.T
    
    # 计算误差
    error = np.abs(T_hf - T_rom)
    max_error = np.max(error)
    mean_error = np.mean(error)
    rmse = np.sqrt(np.mean((T_hf - T_rom)**2))
    
    print(f"\n降阶模型误差:")
    print(f"  最大误差: {max_error:.2f} K")
    print(f"  平均误差: {mean_error:.2f} K")
    print(f"  RMSE: {rmse:.2f} K")
    
    # 计算加速比(模拟)
    time_hf = 10.0  # 高保真模型计算时间(归一化)
    time_rom = 0.1  # 降阶模型计算时间
    speedup = time_hf / time_rom
    print(f"  加速比: {speedup:.0f}x")
    
    # 可视化
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    
    # 1. 奇异值衰减
    ax1 = axes[0, 0]
    ax1.semilogy(S[:20], 'bo-', linewidth=2, markersize=8)
    ax1.axvline(x=r-1, color='r', linestyle='--', label=f'Selected r={r}')
    ax1.set_xlabel('Mode Index')
    ax1.set_ylabel('Singular Value')
    ax1.set_title('Singular Value Decay')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # 2. 累积能量
    ax2 = axes[0, 1]
    ax2.plot(cumulative_energy[:20], 'g-', linewidth=2)
    ax2.axhline(y=0.999, color='r', linestyle='--', label='99.9% Energy')
    ax2.axvline(x=r-1, color='r', linestyle='--')
    ax2.set_xlabel('Number of Modes')
    ax2.set_ylabel('Cumulative Energy')
    ax2.set_title('Cumulative Energy Content')
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    
    # 3. 前4个POD模态
    ax3 = axes[0, 2]
    for i in range(min(4, r)):
        ax3.plot(x, Phi[:, i], linewidth=2, label=f'Mode {i+1}')
    ax3.set_xlabel('Position x (m)')
    ax3.set_ylabel('Mode Amplitude')
    ax3.set_title('First 4 POD Modes')
    ax3.legend()
    ax3.grid(True, alpha=0.3)
    
    # 4. 温度分布对比(最后时刻)
    ax4 = axes[1, 0]
    ax4.plot(x, T_hf[-1], 'b-', linewidth=2, label='High-Fidelity')
    ax4.plot(x, T_rom[-1], 'r--', linewidth=2, label='ROM')
    ax4.set_xlabel('Position x (m)')
    ax4.set_ylabel('Temperature (K)')
    ax4.set_title(f'Temperature Distribution (t={t_test[-1]:.0f}s)')
    ax4.legend()
    ax4.grid(True, alpha=0.3)
    
    # 5. 时间演化对比(中心点)
    ax5 = axes[1, 1]
    center_idx = N // 2
    ax5.plot(t_test, T_hf[:, center_idx], 'b-', linewidth=2, label='High-Fidelity')
    ax5.plot(t_test, T_rom[:, center_idx], 'r--', linewidth=2, label='ROM')
    ax5.set_xlabel('Time (s)')
    ax5.set_ylabel('Temperature (K)')
    ax5.set_title(f'Temperature at x={x[center_idx]:.2f}m')
    ax5.legend()
    ax5.grid(True, alpha=0.3)
    
    # 6. 误差分布
    ax6 = axes[1, 2]
    im = ax6.contourf(x, t_test, error, levels=20, cmap='hot')
    ax6.set_xlabel('Position x (m)')
    ax6.set_ylabel('Time (s)')
    ax6.set_title('Error Distribution (K)')
    plt.colorbar(im, ax=ax6)
    
    plt.tight_layout()
    plt.savefig('case1_pod_rom.png', dpi=150, bbox_inches='tight')
    print("\n案例1图表已保存: case1_pod_rom.png")
    plt.close()
    
    return Phi, S, r, speedup

# 案例2:DMD分析 - 辐射系统动态特性
print("\n" + "=" * 70)
print("案例2:DMD分析 - 辐射系统动态特性")
print("=" * 70)

def case2_dmd_analysis():
    """
    案例2:使用DMD分析辐射换热系统的动态模态
    """
    np.random.seed(42)
    
    # 问题设置:二维辐射-导热问题
    Lx, Ly = 1.0, 1.0  # 域尺寸
    Nx, Ny = 30, 30    # 网格数
    dx, dy = Lx/(Nx-1), Ly/(Ny-1)
    
    x = np.linspace(0, Lx, Nx)
    y = np.linspace(0, Ly, Ny)
    X, Y = np.meshgrid(x, y)
    
    # 生成时间序列数据(模拟瞬态辐射换热)
    print("生成瞬态数据...")
    n_steps = 200
    dt = 10.0  # 时间步长
    t = np.arange(n_steps) * dt
    
    # 模拟多个频率分量的衰减过程
    data = []
    for n in range(n_steps):
        # 基础温度场
        T_base = 500 + 200 * np.sin(np.pi * X / Lx) * np.sin(np.pi * Y / Ly)
        
        # 添加衰减模态
        mode1 = 100 * np.exp(-0.01 * n) * np.sin(2 * np.pi * X / Lx) * np.sin(np.pi * Y / Ly)
        mode2 = 50 * np.exp(-0.05 * n) * np.sin(np.pi * X / Lx) * np.sin(2 * np.pi * Y / Ly)
        mode3 = 30 * np.exp(-0.1 * n) * np.cos(3 * np.pi * X / Lx) * np.cos(2 * np.pi * Y / Ly)
        
        # 添加振荡模态
        mode4 = 20 * np.sin(0.05 * n) * np.exp(-0.005 * n) * np.sin(4 * np.pi * X / Lx)
        
        T = T_base + mode1 + mode2 + mode3 + mode4
        data.append(T.flatten())
    
    data = np.array(data).T  # 形状: (Nx*Ny, n_steps)
    print(f"  数据矩阵形状: {data.shape}")
    
    # DMD分析
    print("\n执行DMD分析...")
    
    # 构造快照矩阵
    X1 = data[:, :-1]  # 前n-1个快照
    X2 = data[:, 1:]   # 后n-1个快照
    
    # SVD分解
    U, S, Vh = svd(X1, full_matrices=False)
    
    # 截断秩
    r = 10  # 保留前10个模态
    Ur = U[:, :r]
    Sr = np.diag(S[:r])
    Vr = Vh[:r, :].T
    
    # 计算投影后的A矩阵
    A_tilde = Ur.T @ X2 @ Vr @ np.linalg.inv(Sr)
    
    # 特征值分解
    eigenvalues, eigenvectors = eig(A_tilde)
    
    # 计算DMD模态
    Phi_dmd = X2 @ Vr @ np.linalg.inv(Sr) @ eigenvectors
    
    # 计算频率和增长率
    omega = np.log(eigenvalues) / dt
    frequencies = np.imag(omega) / (2 * np.pi)
    growth_rates = np.real(omega)
    
    print(f"  提取了 {r} 个DMD模态")
    print(f"  频率范围: [{frequencies.min():.3f}, {frequencies.max():.3f}] Hz")
    print(f"  增长率范围: [{growth_rates.min():.3f}, {growth_rates.max():.3f}]")
    
    # DMD重构
    print("\n重构DMD解...")
    b = np.linalg.lstsq(Phi_dmd, data[:, 0], rcond=None)[0]  # 初始振幅
    
    data_dmd = np.zeros_like(data, dtype=complex)
    for n in range(n_steps):
        data_dmd[:, n] = Phi_dmd @ (b * (eigenvalues ** n))
    
    data_dmd = np.real(data_dmd)
    
    # 计算重构误差
    reconstruction_error = np.linalg.norm(data - data_dmd) / np.linalg.norm(data)
    print(f"  DMD重构相对误差: {reconstruction_error:.4f}")
    
    # 可视化
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    
    # 1. DMD特征值(复平面)
    ax1 = axes[0, 0]
    ax1.scatter(np.real(eigenvalues), np.imag(eigenvalues), c=growth_rates, 
                cmap='RdYlGn_r', s=100, edgecolors='black')
    theta = np.linspace(0, 2*np.pi, 100)
    ax1.plot(np.cos(theta), np.sin(theta), 'k--', linewidth=1, label='Unit Circle')
    ax1.axvline(x=0, color='k', linewidth=0.5)
    ax1.axhline(y=0, color='k', linewidth=0.5)
    ax1.set_xlabel('Real Part')
    ax1.set_ylabel('Imaginary Part')
    ax1.set_title('DMD Eigenvalues')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    ax1.axis('equal')
    
    # 2. 频率-增长率图
    ax2 = axes[0, 1]
    colors = plt.cm.viridis(np.linspace(0, 1, r))
    for i in range(r):
        ax2.scatter(frequencies[i], growth_rates[i], c=[colors[i]], s=100, 
                   edgecolors='black', linewidth=1.5)
        ax2.annotate(f'{i+1}', (frequencies[i], growth_rates[i]), 
                    xytext=(5, 5), textcoords='offset points', fontsize=9)
    ax2.axhline(y=0, color='r', linestyle='--', linewidth=1)
    ax2.set_xlabel('Frequency (Hz)')
    ax2.set_ylabel('Growth Rate (1/s)')
    ax2.set_title('Frequency vs Growth Rate')
    ax2.grid(True, alpha=0.3)
    
    # 3. 前4个DMD模态的空间分布
    for idx, ax in enumerate([axes[0, 2], axes[1, 0], axes[1, 1], axes[1, 2]]):
        if idx < r:
            mode = np.real(Phi_dmd[:, idx]).reshape(Ny, Nx)
            im = ax.contourf(X, Y, mode, levels=20, cmap='RdBu_r')
            ax.set_title(f'DMD Mode {idx+1}\n(f={frequencies[idx]:.4f} Hz, g={growth_rates[idx]:.4f})')
            ax.set_xlabel('x (m)')
            ax.set_ylabel('y (m)')
            plt.colorbar(im, ax=ax)
    
    plt.tight_layout()
    plt.savefig('case2_dmd_analysis.png', dpi=150, bbox_inches='tight')
    print("\n案例2图表已保存: case2_dmd_analysis.png")
    plt.close()
    
    return Phi_dmd, eigenvalues, frequencies, growth_rates

# 案例3:DEIM非线性降阶
print("\n" + "=" * 70)
print("案例3:DEIM非线性降阶")
print("=" * 70)

def case3_deim_nonlinear():
    """
    案例3:使用DEIM处理辐射非线性项的降阶
    """
    np.random.seed(42)
    
    # 问题设置
    N = 50  # 高保真维度
    x = np.linspace(0, 1, N)
    kappa = 1.0  # 吸收系数
    
    # 生成非线性项的快照(模拟辐射源项)
    print("生成非线性项快照...")
    n_snapshots = 100
    Q_snapshots = []
    
    for i in range(n_snapshots):
        # 模拟不同温度分布下的辐射源项
        T = 300 + 500 * np.random.rand() * (1 + np.sin(2 * np.pi * x * np.random.rand()))
        T4 = T**4
        
        # 简化的辐射源项模型
        Q = -kappa * (T4 - np.mean(T4))
        Q_snapshots.append(Q)
    
    Q_snapshots = np.array(Q_snapshots).T
    
    # 对非线性项进行POD
    U_q, S_q, _ = svd(Q_snapshots, full_matrices=False)
    
    # 选择DEIM基的数量
    m_deim = 8
    Phi_q = U_q[:, :m_deim]
    
    print(f"  DEIM基数量: {m_deim}")
    
    # DEIM算法:选择魔法点
    def deim_algorithm(Phi):
        """
        DEIM算法选择魔法点索引
        """
        n, m = Phi.shape
        indices = []
        
        # 第一个索引:第一个基向量的最大绝对值位置
        idx = np.argmax(np.abs(Phi[:, 0]))
        indices.append(idx)
        
        for i in range(1, m):
            # 解插值问题
            c = np.linalg.solve(Phi[indices, :i], Phi[indices, i])
            residual = Phi[:, i] - Phi[:, :i] @ c
            idx = np.argmax(np.abs(residual))
            indices.append(idx)
        
        return np.array(indices)
    
    magic_points = deim_algorithm(Phi_q)
    print(f"  魔法点索引: {magic_points}")
    print(f"  魔法点位置: {x[magic_points]}")
    
    # 构建DEIM插值矩阵
    P = np.zeros((N, m_deim))
    for i, idx in enumerate(magic_points):
        P[idx, i] = 1
    
    # DEIM近似: Q ≈ Phi_q * (P^T * Phi_q)^(-1) * P^T * Q
    PT_Phi_q = P.T @ Phi_q
    DEIM_matrix = Phi_q @ np.linalg.inv(PT_Phi_q)
    
    # 测试DEIM近似
    print("\n测试DEIM近似精度...")
    n_test = 20
    errors = []
    
    for i in range(n_test):
        T = 300 + 500 * np.random.rand() * (1 + np.sin(2 * np.pi * x * np.random.rand()))
        T4 = T**4
        Q_exact = -kappa * (T4 - np.mean(T4))
        
        # DEIM近似(只在魔法点计算)
        Q_at_magic = Q_exact[magic_points]
        Q_approx = DEIM_matrix @ Q_at_magic
        
        error = np.linalg.norm(Q_exact - Q_approx) / np.linalg.norm(Q_exact)
        errors.append(error)
    
    mean_error = np.mean(errors)
    max_error = np.max(errors)
    print(f"  平均相对误差: {mean_error:.4f}")
    print(f"  最大相对误差: {max_error:.4f}")
    
    # 计算加速比
    speedup_deim = N / m_deim
    print(f"  理论加速比: {speedup_deim:.1f}x")
    
    # 可视化
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))
    
    # 1. 非线性项POD奇异值
    ax1 = axes[0, 0]
    ax1.semilogy(S_q[:15], 'bo-', linewidth=2, markersize=8)
    ax1.axvline(x=m_deim-1, color='r', linestyle='--', label=f'Selected m={m_deim}')
    ax1.set_xlabel('Mode Index')
    ax1.set_ylabel('Singular Value')
    ax1.set_title('Nonlinear Term POD Singular Values')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # 2. DEIM基函数
    ax2 = axes[0, 1]
    for i in range(min(4, m_deim)):
        ax2.plot(x, Phi_q[:, i], linewidth=2, label=f'Basis {i+1}')
    ax2.scatter(x[magic_points], np.zeros(m_deim), c='red', s=100, 
               marker='x', linewidth=3, label='Magic Points', zorder=5)
    ax2.set_xlabel('Position x')
    ax2.set_ylabel('Basis Amplitude')
    ax2.set_title('DEIM Basis Functions & Magic Points')
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    
    # 3. 精确解与DEIM近似对比
    ax3 = axes[1, 0]
    T_test = 300 + 500 * (1 + np.sin(2 * np.pi * x))
    T4_test = T_test**4
    Q_exact_test = -kappa * (T4_test - np.mean(T4_test))
    Q_approx_test = DEIM_matrix @ Q_exact_test[magic_points]
    
    ax3.plot(x, Q_exact_test, 'b-', linewidth=2, label='Exact')
    ax3.plot(x, Q_approx_test, 'r--', linewidth=2, label='DEIM Approximation')
    ax3.scatter(x[magic_points], Q_exact_test[magic_points], c='green', 
               s=100, marker='o', label='Magic Points', zorder=5)
    ax3.set_xlabel('Position x')
    ax3.set_ylabel('Nonlinear Term Q')
    ax3.set_title('Exact vs DEIM Approximation')
    ax3.legend()
    ax3.grid(True, alpha=0.3)
    
    # 4. 误差分布
    ax4 = axes[1, 1]
    error_dist = np.abs(Q_exact_test - Q_approx_test)
    ax4.plot(x, error_dist, 'm-', linewidth=2)
    ax4.fill_between(x, error_dist, alpha=0.3)
    ax4.set_xlabel('Position x')
    ax4.set_ylabel('Absolute Error')
    ax4.set_title(f'Approximation Error (Max: {np.max(error_dist):.2e})')
    ax4.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('case3_deim_nonlinear.png', dpi=150, bbox_inches='tight')
    print("\n案例3图表已保存: case3_deim_nonlinear.png")
    plt.close()
    
    return Phi_q, magic_points, DEIM_matrix

# 案例4:参数化降阶模型
print("\n" + "=" * 70)
print("案例4:参数化降阶模型")
print("=" * 70)

def case4_parametric_rom():
    """
    案例4:构建参数化降阶模型
    """
    np.random.seed(42)
    
    # 问题设置:一维辐射板,参数为发射率和边界热流
    N = 80
    x = np.linspace(0, 1, N)
    
    # 参数空间
    n_eps = 5  # 发射率样本数
    n_q = 5    # 热流样本数
    eps_values = np.linspace(0.3, 0.9, n_eps)
    q_values = np.linspace(1000, 8000, n_q)
    
    print(f"参数空间: epsilon ∈ [{eps_values[0]:.1f}, {eps_values[-1]:.1f}]")
    print(f"           q ∈ [{q_values[0]:.0f}, {q_values[-1]:.0f}]")
    
    # 生成快照(遍历参数空间)
    print("\n生成参数化快照...")
    all_snapshots = []
    params = []
    
    for eps in eps_values:
        for q in q_values:
            # 稳态温度分布(简化模型)
            T = 300 + (q / (eps * sigma * (1 + x))) ** 0.25
            all_snapshots.append(T)
            params.append([eps, q])
    
    all_snapshots = np.array(all_snapshots).T
    params = np.array(params)
    
    print(f"  生成 {all_snapshots.shape[1]} 个快照")
    
    # POD分析
    U, S, _ = svd(all_snapshots, full_matrices=False)
    r = 6  # 保留6个模态
    Phi = U[:, :r]
    
    print(f"  保留 {r} 个POD模态")
    
    # 计算每个快照的模态系数
    coefficients = Phi.T @ all_snapshots
    
    # 使用RBF插值建立参数到系数的映射
    from scipy.interpolate import Rbf
    
    # 为每个模态系数构建RBF插值器
    rbf_interpolators = []
    for i in range(r):
        rbf = Rbf(params[:, 0], params[:, 1], coefficients[i], 
                  function='multiquadric')
        rbf_interpolators.append(rbf)
    
    print("  构建RBF插值器完成")
    
    # 测试参数化ROM
    print("\n测试参数化ROM...")
    
    # 测试点(不在训练网格上)
    eps_test = 0.6
    q_test = 4500
    
    # 高保真解
    T_hf = 300 + (q_test / (eps_test * sigma * (1 + x))) ** 0.25
    
    # ROM预测
    a_pred = np.array([rbf(eps_test, q_test) for rbf in rbf_interpolators])
    T_rom = Phi @ a_pred
    
    # 计算误差
    error = np.linalg.norm(T_hf - T_rom) / np.linalg.norm(T_hf)
    print(f"  测试参数: epsilon={eps_test}, q={q_test}")
    print(f"  相对误差: {error:.4f}")
    
    # 可视化
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))
    
    # 1. 参数空间采样
    ax1 = axes[0, 0]
    ax1.scatter(params[:, 0], params[:, 1], c='blue', s=100, 
               marker='s', label='Training Points', edgecolors='black')
    ax1.scatter([eps_test], [q_test], c='red', s=200, 
               marker='*', label='Test Point', edgecolors='black', linewidth=2)
    ax1.set_xlabel('Emissivity epsilon')
    ax1.set_ylabel('Heat Flux q (W/m^2)')
    ax1.set_title('Parameter Space Sampling')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # 2. POD模态
    ax2 = axes[0, 1]
    for i in range(min(4, r)):
        ax2.plot(x, Phi[:, i], linewidth=2, label=f'Mode {i+1}')
    ax2.set_xlabel('Position x')
    ax2.set_ylabel('Mode Amplitude')
    ax2.set_title('POD Modes')
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    
    # 3. 温度分布对比
    ax3 = axes[1, 0]
    ax3.plot(x, T_hf, 'b-', linewidth=2, label='High-Fidelity')
    ax3.plot(x, T_rom, 'r--', linewidth=2, label='Parametric ROM')
    ax3.set_xlabel('Position x')
    ax3.set_ylabel('Temperature (K)')
    ax3.set_title(f'Temperature Distribution (eps={eps_test}, q={q_test})')
    ax3.legend()
    ax3.grid(True, alpha=0.3)
    
    # 4. 误差随参数变化
    ax4 = axes[1, 1]
    
    # 在参数空间上采样测试
    eps_test_grid = np.linspace(0.35, 0.85, 10)
    q_test_grid = np.linspace(1500, 7500, 10)
    EPS, Q = np.meshgrid(eps_test_grid, q_test_grid)
    ERROR = np.zeros_like(EPS)
    
    for i in range(len(eps_test_grid)):
        for j in range(len(q_test_grid)):
            eps_t = eps_test_grid[i]
            q_t = q_test_grid[j]
            
            T_hf_t = 300 + (q_t / (eps_t * sigma * (1 + x))) ** 0.25
            a_t = np.array([rbf(eps_t, q_t) for rbf in rbf_interpolators])
            T_rom_t = Phi @ a_t
            
            ERROR[j, i] = np.linalg.norm(T_hf_t - T_rom_t) / np.linalg.norm(T_hf_t)
    
    im = ax4.contourf(EPS, Q, ERROR, levels=20, cmap='hot')
    ax4.scatter(params[:, 0], params[:, 1], c='white', s=50, marker='s')
    ax4.set_xlabel('Emissivity epsilon')
    ax4.set_ylabel('Heat Flux q (W/m^2)')
    ax4.set_title('Relative Error in Parameter Space')
    plt.colorbar(im, ax=ax4, label='Relative Error')
    
    plt.tight_layout()
    plt.savefig('case4_parametric_rom.png', dpi=150, bbox_inches='tight')
    print("\n案例4图表已保存: case4_parametric_rom.png")
    plt.close()
    
    return Phi, rbf_interpolators, params

# 案例5:降阶模型误差分析
print("\n" + "=" * 70)
print("案例5:降阶模型误差分析")
print("=" * 70)

def case5_error_analysis():
    """
    案例5:分析降阶模型的误差随模态数的变化
    """
    np.random.seed(42)
    
    # 生成高保真数据
    N = 100
    n_snapshots = 200
    
    print("生成快照数据...")
    snapshots = []
    for i in range(n_snapshots):
        # 复杂的温度场
        x = np.linspace(0, 1, N)
        T = (300 + 200 * np.sin(np.pi * x) * (1 + 0.5 * np.sin(5 * np.pi * x)) +
             100 * np.random.randn() * np.exp(-5 * (x - 0.5)**2))
        snapshots.append(T)
    
    snapshots = np.array(snapshots).T
    
    # POD分析
    U, S, Vt = svd(snapshots, full_matrices=False)
    
    # 测试不同模态数的重构误差
    print("\n分析不同模态数的重构误差...")
    max_modes = 20
    reconstruction_errors = []
    projection_errors = []
    
    # 测试快照(不在训练集中)
    n_test = 50
    test_snapshots = []
    for i in range(n_test):
        x = np.linspace(0, 1, N)
        T = (300 + 200 * np.sin(np.pi * x) * (1 + 0.5 * np.sin(5 * np.pi * x)) +
             100 * np.random.randn() * np.exp(-5 * (x - 0.5)**2))
        test_snapshots.append(T)
    test_snapshots = np.array(test_snapshots).T
    
    for r in range(1, max_modes + 1):
        Phi_r = U[:, :r]
        
        # 重构训练集
        coeffs_train = Phi_r.T @ snapshots
        recon_train = Phi_r @ coeffs_train
        error_train = np.mean(np.linalg.norm(snapshots - recon_train, axis=0) / 
                              np.linalg.norm(snapshots, axis=0))
        
        # 重构测试集
        coeffs_test = Phi_r.T @ test_snapshots
        recon_test = Phi_r @ coeffs_test
        error_test = np.mean(np.linalg.norm(test_snapshots - recon_test, axis=0) / 
                             np.linalg.norm(test_snapshots, axis=0))
        
        reconstruction_errors.append(error_train)
        projection_errors.append(error_test)
    
    # 计算累积能量
    cumulative_energy = np.cumsum(S**2) / np.sum(S**2)
    
    print(f"  使用 {max_modes} 个模态时:")
    print(f"    训练集误差: {reconstruction_errors[-1]:.6f}")
    print(f"    测试集误差: {projection_errors[-1]:.6f}")
    print(f"    累积能量: {cumulative_energy[max_modes-1]*100:.2f}%")
    
    # 可视化
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))
    
    # 1. 奇异值衰减
    ax1 = axes[0, 0]
    ax1.semilogy(S[:max_modes], 'bo-', linewidth=2, markersize=8)
    ax1.set_xlabel('Mode Number')
    ax1.set_ylabel('Singular Value')
    ax1.set_title('Singular Value Decay')
    ax1.grid(True, alpha=0.3)
    
    # 2. 累积能量
    ax2 = axes[0, 1]
    ax2.plot(range(1, max_modes+1), cumulative_energy[:max_modes]*100, 
             'g-', linewidth=2)
    ax2.axhline(y=99, color='r', linestyle='--', label='99% Energy')
    ax2.axhline(y=99.9, color='orange', linestyle='--', label='99.9% Energy')
    ax2.set_xlabel('Number of Modes')
    ax2.set_ylabel('Cumulative Energy (%)')
    ax2.set_title('Cumulative Energy vs Number of Modes')
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    
    # 3. 重构误差
    ax3 = axes[1, 0]
    ax3.semilogy(range(1, max_modes+1), reconstruction_errors, 'b-', 
                 linewidth=2, label='Training Error')
    ax3.semilogy(range(1, max_modes+1), projection_errors, 'r--', 
                 linewidth=2, label='Test Error')
    ax3.set_xlabel('Number of Modes')
    ax3.set_ylabel('Relative Reconstruction Error')
    ax3.set_title('Reconstruction Error vs Number of Modes')
    ax3.legend()
    ax3.grid(True, alpha=0.3)
    
    # 4. 前6个模态的可视化
    ax4 = axes[1, 1]
    x = np.linspace(0, 1, N)
    for i in range(min(6, max_modes)):
        ax4.plot(x, U[:, i], linewidth=2, label=f'Mode {i+1}')
    ax4.set_xlabel('Position x')
    ax4.set_ylabel('Mode Amplitude')
    ax4.set_title('First 6 POD Modes')
    ax4.legend()
    ax4.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('case5_error_analysis.png', dpi=150, bbox_inches='tight')
    print("\n案例5图表已保存: case5_error_analysis.png")
    plt.close()
    
    return S, reconstruction_errors, projection_errors

# GIF动画1:POD模态时间演化
print("\n" + "=" * 70)
print("生成动画1:POD模态时间演化")
print("=" * 70)

def create_gif1_pod_modes_evolution():
    """
    动画1:展示POD模态随时间的演化
    """
    print("生成POD模态时间演化动画...")
    np.random.seed(42)
    
    N = 50
    x = np.linspace(0, 1, N)
    n_frames = 50
    
    # 生成时变数据
    data = []
    for n in range(n_frames):
        t = n / n_frames * 2 * np.pi
        T = (300 + 100 * np.sin(np.pi * x) * np.cos(t) +
             50 * np.sin(2 * np.pi * x) * np.cos(2 * t) +
             30 * np.sin(3 * np.pi * x) * np.cos(3 * t))
        data.append(T)
    data = np.array(data).T
    
    # POD分析
    U, S, _ = svd(data, full_matrices=False)
    r = 3
    Phi = U[:, :r]
    coeffs = Phi.T @ data
    
    # 创建动画
    fig, axes = plt.subplots(1, 2, figsize=(14, 5))
    
    def update(frame):
        for ax in axes:
            ax.clear()
        
        # 左图:温度场演化
        axes[0].plot(x, data[:, frame], 'b-', linewidth=2, label='Temperature Field')
        axes[0].set_xlim(0, 1)
        axes[0].set_ylim(200, 450)
        axes[0].set_xlabel('Position x')
        axes[0].set_ylabel('Temperature (K)')
        axes[0].set_title(f'Temperature Evolution (Frame {frame+1}/{n_frames})')
        axes[0].legend()
        axes[0].grid(True, alpha=0.3)
        
        # 右图:模态系数演化
        colors = ['red', 'green', 'purple']
        for i in range(r):
            axes[1].plot(range(frame+1), coeffs[i, :frame+1], 
                        color=colors[i], linewidth=2, label=f'Mode {i+1}')
        axes[1].set_xlim(0, n_frames)
        axes[1].set_ylim(-150, 150)
        axes[1].set_xlabel('Time Step')
        axes[1].set_ylabel('Modal Coefficient')
        axes[1].set_title('POD Modal Coefficients Evolution')
        axes[1].legend()
        axes[1].grid(True, alpha=0.3)
        
        return axes
    
    anim = animation.FuncAnimation(fig, update, frames=n_frames, 
                                  interval=150, blit=False)
    anim.save('gif1_pod_modes_evolution.gif', writer='pillow', fps=6, dpi=100)
    print("GIF动画1完成:已保存 gif1_pod_modes_evolution.gif")
    plt.close()

# GIF动画2:DMD模态动态
print("\n" + "=" * 70)
print("生成动画2:DMD模态动态")
print("=" * 70)

def create_gif2_dmd_modes_dynamic():
    """
    动画2:展示DMD模态的动态行为
    """
    print("生成DMD模态动态动画...")
    np.random.seed(42)
    
    N = 40
    x = np.linspace(0, 1, N)
    n_frames = 40
    
    # 构造具有不同增长率和频率的模态
    modes = []
    growth_rates = [-0.05, -0.02, 0.01]
    frequencies = [0.1, 0.3, 0.5]
    
    for n in range(n_frames):
        t = n * 2
        field = np.zeros(N)
        for i, (g, f) in enumerate(zip(growth_rates, frequencies)):
            amplitude = np.exp(g * t)
            phase = 2 * np.pi * f * t
            mode_shape = np.sin((i+1) * np.pi * x)
            field += amplitude * np.cos(phase) * mode_shape * 50
        
        field += 300  # 基准温度
        modes.append(field)
    
    modes = np.array(modes).T
    
    # 创建动画
    fig, axes = plt.subplots(1, 2, figsize=(14, 5))
    
    colors = ['blue', 'green', 'red']
    
    def update(frame):
        for ax in axes:
            ax.clear()
        
        # 左图:当前场分布
        axes[0].plot(x, modes[:, frame], 'b-', linewidth=2)
        axes[0].set_xlim(0, 1)
        axes[0].set_ylim(150, 450)
        axes[0].set_xlabel('Position x')
        axes[0].set_ylabel('Temperature (K)')
        axes[0].set_title(f'Field Distribution (t={frame*2:.0f}s)')
        axes[0].grid(True, alpha=0.3)
        
        # 右图:各模态振幅演化
        t_vals = np.arange(frame + 1) * 2
        for i, (g, f) in enumerate(zip(growth_rates, frequencies)):
            amplitude = np.exp(g * t_vals) * np.abs(np.cos(2 * np.pi * f * t_vals))
            axes[1].plot(t_vals, amplitude * 50, color=colors[i], 
                        linewidth=2, label=f'Mode {i+1} (g={g:.2f}, f={f:.2f})')
        
        axes[1].set_xlim(0, n_frames * 2)
        axes[1].set_ylim(0, 100)
        axes[1].set_xlabel('Time (s)')
        axes[1].set_ylabel('Amplitude')
        axes[1].set_title('DMD Mode Amplitudes')
        axes[1].legend()
        axes[1].grid(True, alpha=0.3)
        
        return axes
    
    anim = animation.FuncAnimation(fig, update, frames=n_frames, 
                                  interval=150, blit=False)
    anim.save('gif2_dmd_modes_dynamic.gif', writer='pillow', fps=6, dpi=100)
    print("GIF动画2完成:已保存 gif2_dmd_modes_dynamic.gif")
    plt.close()

# GIF动画3:降阶模型vs高保真模型对比
print("\n" + "=" * 70)
print("生成动画3:降阶模型vs高保真模型对比")
print("=" * 70)

def create_gif3_rom_vs_hf():
    """
    动画3:展示降阶模型和高保真模型的计算对比
    """
    print("生成ROM vs HF对比动画...")
    np.random.seed(42)
    
    N = 100
    x = np.linspace(0, 1, N)
    n_frames = 40
    
    # 生成高保真数据
    hf_data = []
    rom_data = []
    
    # POD基(预计算)
    snapshot_matrix = []
    for i in range(50):
        T = 300 + 200 * np.sin(np.pi * x) * (1 + 0.3 * np.sin(3 * np.pi * x + i*0.1))
        snapshot_matrix.append(T)
    snapshot_matrix = np.array(snapshot_matrix).T
    U, _, _ = svd(snapshot_matrix, full_matrices=False)
    Phi = U[:, :3]  # 使用前3个模态
    
    for n in range(n_frames):
        t = n / n_frames * 4 * np.pi
        
        # 高保真解
        T_hf = (300 + 200 * np.sin(np.pi * x) * np.cos(t) * 
                (1 + 0.3 * np.sin(3 * np.pi * x + t)))
        hf_data.append(T_hf)
        
        # ROM解(使用POD重构)
        coeffs = Phi.T @ (T_hf - 300)
        T_rom = Phi @ coeffs + 300
        rom_data.append(T_rom)
    
    hf_data = np.array(hf_data).T
    rom_data = np.array(rom_data).T
    
    # 创建动画
    fig, axes = plt.subplots(1, 3, figsize=(15, 5))
    
    def update(frame):
        for ax in axes:
            ax.clear()
        
        # 左图:高保真模型
        axes[0].plot(x, hf_data[:, frame], 'b-', linewidth=2)
        axes[0].set_xlim(0, 1)
        axes[0].set_ylim(100, 600)
        axes[0].set_xlabel('Position x')
        axes[0].set_ylabel('Temperature (K)')
        axes[0].set_title(f'High-Fidelity Model\n(100 DOFs)')
        axes[0].grid(True, alpha=0.3)
        
        # 中图:降阶模型
        axes[1].plot(x, rom_data[:, frame], 'r-', linewidth=2)
        axes[1].set_xlim(0, 1)
        axes[1].set_ylim(100, 600)
        axes[1].set_xlabel('Position x')
        axes[1].set_ylabel('Temperature (K)')
        axes[1].set_title(f'Reduced-Order Model\n(3 DOFs)')
        axes[1].grid(True, alpha=0.3)
        
        # 右图:误差
        error = np.abs(hf_data[:, frame] - rom_data[:, frame])
        axes[2].plot(x, error, 'g-', linewidth=2)
        axes[2].fill_between(x, error, alpha=0.3, color='green')
        axes[2].set_xlim(0, 1)
        axes[2].set_ylim(0, 50)
        axes[2].set_xlabel('Position x')
        axes[2].set_ylabel('Absolute Error (K)')
        axes[2].set_title(f'Error (Max: {np.max(error):.1f} K)')
        axes[2].grid(True, alpha=0.3)
        
        fig.suptitle(f'ROM vs High-Fidelity Comparison (Frame {frame+1}/{n_frames})', 
                    fontsize=14, fontweight='bold')
        
        return axes
    
    anim = animation.FuncAnimation(fig, update, frames=n_frames, 
                                  interval=150, blit=False)
    anim.save('gif3_rom_vs_hf.gif', writer='pillow', fps=6, dpi=100)
    print("GIF动画3完成:已保存 gif3_rom_vs_hf.gif")
    plt.close()

# 主函数
if __name__ == "__main__":
    # 运行所有案例
    Phi_pod, S_pod, r_pod, speedup1 = case1_pod_rom()
    Phi_dmd, eigenvalues, frequencies, growth_rates = case2_dmd_analysis()
    Phi_q, magic_points, DEIM_matrix = case3_deim_nonlinear()
    Phi_param, rbf_interp, params = case4_parametric_rom()
    S_err, train_err, test_err = case5_error_analysis()
    
    # 生成GIF动画
    create_gif1_pod_modes_evolution()
    create_gif2_dmd_modes_dynamic()
    create_gif3_rom_vs_hf()
    
    print("\n" + "=" * 70)
    print("所有仿真案例和GIF动画生成完成!")
    print("=" * 70)
    print("\n生成的文件列表:")
    print("  - case1_pod_rom.png")
    print("  - case2_dmd_analysis.png")
    print("  - case3_deim_nonlinear.png")
    print("  - case4_parametric_rom.png")
    print("  - case5_error_analysis.png")
    print("  - gif1_pod_modes_evolution.gif")
    print("  - gif2_dmd_modes_dynamic.gif")
    print("  - gif3_rom_vs_hf.gif")
    print("=" * 70)

Logo

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

更多推荐