电磁场耦合仿真-主题071_电磁场区域分解方法-电磁场区域分解方法
主题071:电磁场区域分解方法
1. 引言
1.1 为什么需要区域分解方法
区域分解方法(Domain Decomposition Methods, DDM)是求解大规模科学计算问题的重要技术,在电磁场仿真中具有以下关键优势:
计算效率提升
- 将大规模问题分解为多个子问题并行求解
- 充分利用现代多核处理器和集群计算资源
- 显著缩短复杂电磁问题的求解时间
内存需求降低
- 每个子域只需存储局部网格和矩阵
- 突破单机内存限制,处理超大规模问题
- 支持十亿级自由度电磁仿真
算法灵活性
- 不同子域可采用不同离散方法和物理模型
- 支持非匹配网格和异构介质处理
- 便于处理复杂几何形状
物理直观性
- 子域划分符合电磁系统的物理结构
- 便于实现多物理场分区耦合
- 支持局部网格细化和自适应策略







1.2 区域分解方法的发展历史
早期发展(1980年代)
- 1985年:Schwarz方法的数值实现
- 1987年:迭代子结构方法的提出
- 1988年:FETI方法的原型
理论成熟(1990年代)
- 1991年:Schwarz方法的收敛性理论
- 1994年:FETI方法的数学框架
- 1996年:BDDC方法的提出
- 1998年:重叠型方法的优化
电磁应用(2000年代)
- 2001年:DDM在FDTD中的应用
- 2003年:FETI-DP在FEM中的应用
- 2005年:时域区域分解方法
- 2008年:多尺度区域分解
现代发展(2010年至今)
- 2012年:基于云的DDM实现
- 2015年:GPU加速区域分解
- 2018年:机器学习辅助DDM
- 2020年:自适应区域分解策略
1.3 区域分解方法在电磁场中的应用
天线阵列仿真
- 阵列单元分解为独立子域
- 单元间耦合通过界面条件处理
- 支持大规模相控阵快速分析
电磁兼容分析
- 复杂电子系统分区建模
- PCB、机箱、线缆分别处理
- 多尺度几何高效求解
雷达散射截面计算
- 目标与背景区域分离
- 完美匹配层作为外部子域
- 入射波分解处理
微波器件设计
- 不同材料区域独立离散
- 精细结构与粗网格结合
- 参数化扫描加速
生物电磁学
- 人体各组织器官分区
- 非均匀介质处理
- SAR分布计算
2. 区域分解方法基础理论
2.1 区域分解的基本概念
数学模型
考虑计算域 Ω\OmegaΩ 上的偏微分方程:
Lu=f在 Ω 内\mathcal{L}u = f \quad \text{在 } \Omega \text{ 内}Lu=f在 Ω 内
u=g在 ∂ΩD 上u = g \quad \text{在 } \partial\Omega_D \text{ 上}u=g在 ∂ΩD 上
∂u∂n=h在 ∂ΩN 上\frac{\partial u}{\partial n} = h \quad \text{在 } \partial\Omega_N \text{ 上}∂n∂u=h在 ∂ΩN 上
域分解
将计算域分解为 NNN 个重叠或非重叠子域:
Ω=⋃i=1NΩi\Omega = \bigcup_{i=1}^N \Omega_iΩ=i=1⋃NΩi
对于非重叠分解:
Ωi∩Ωj=∅,i≠j\Omega_i \cap \Omega_j = \emptyset, \quad i \neq jΩi∩Ωj=∅,i=j
界面定义为:
Γij=∂Ωi∩∂Ωj\Gamma_{ij} = \partial\Omega_i \cap \partial\Omega_jΓij=∂Ωi∩∂Ωj
子域问题
每个子域上的局部问题:
Lui=f在 Ωi 内\mathcal{L}u_i = f \quad \text{在 } \Omega_i \text{ 内}Lui=f在 Ωi 内
ui=g在 ∂Ωi∩∂ΩD 上u_i = g \quad \text{在 } \partial\Omega_i \cap \partial\Omega_D \text{ 上}ui=g在 ∂Ωi∩∂ΩD 上
∂ui∂n=h在 ∂Ωi∩∂ΩN 上\frac{\partial u_i}{\partial n} = h \quad \text{在 } \partial\Omega_i \cap \partial\Omega_N \text{ 上}∂n∂ui=h在 ∂Ωi∩∂ΩN 上
界面条件
连续性条件:
ui=uj在 Γij 上u_i = u_j \quad \text{在 } \Gamma_{ij} \text{ 上}ui=uj在 Γij 上
∂ui∂ni+∂uj∂nj=0在 Γij 上\frac{\partial u_i}{\partial n_i} + \frac{\partial u_j}{\partial n_j} = 0 \quad \text{在 } \Gamma_{ij} \text{ 上}∂ni∂ui+∂nj∂uj=0在 Γij 上
2.2 非重叠区域分解方法
迭代子结构方法
将未知数分为内部点和界面点:
[AIIAIΓAΓIAΓΓ][uIuΓ]=[fIfΓ]\begin{bmatrix} A_{II} & A_{I\Gamma} \\ A_{\Gamma I} & A_{\Gamma\Gamma} \end{bmatrix} \begin{bmatrix} u_I \\ u_\Gamma \end{bmatrix} = \begin{bmatrix} f_I \\ f_\Gamma \end{bmatrix}[AIIAΓIAIΓAΓΓ][uIuΓ]=[fIfΓ]
通过静态凝聚消除内部自由度:
SuΓ=gS u_\Gamma = gSuΓ=g
其中Schur补矩阵:
S=AΓΓ−AΓIAII−1AIΓS = A_{\Gamma\Gamma} - A_{\Gamma I} A_{II}^{-1} A_{I\Gamma}S=AΓΓ−AΓIAII−1AIΓ
Neumann-Neumann方法
在界面上交替施加Neumann和Dirichlet条件:
- 求解局部Neumann问题获取界面通量
- 更新界面值
- 迭代直至收敛
Dirichlet-Neumann方法
将界面分为两部分:
- 一部分施加Dirichlet条件
- 另一部分施加Neumann条件
- 交替迭代求解
2.3 重叠区域分解方法
Schwarz交替方法
基本思想:在重叠区域上交替求解
算法流程:
- 初始化所有子域的解
- 对每个子域,使用相邻子域的最新解作为边界条件
- 重复迭代直至收敛
加性Schwarz方法
并行求解所有子域问题:
un+1=un+∑i=1NRiTAi−1Ri(f−Aun)u^{n+1} = u^n + \sum_{i=1}^N R_i^T A_i^{-1} R_i (f - Au^n)un+1=un+i=1∑NRiTAi−1Ri(f−Aun)
其中 RiR_iRi 是限制算子,RiTR_i^TRiT 是延拓算子。
乘性Schwarz方法
串行求解子域问题,使用最新信息:
un+1=un+RNTAN−1RN(f−A(un+⋯ ))u^{n+1} = u^n + R_N^T A_N^{-1} R_N (f - A(u^n + \cdots))un+1=un+RNTAN−1RN(f−A(un+⋯))
限制型Schwarz方法
使用粗网格校正加速收敛:
un+1=un+PcAc−1Rc(f−Aun)+∑i=1NRiTAi−1Ri(f−Aun)u^{n+1} = u^n + P_c A_c^{-1} R_c (f - Au^n) + \sum_{i=1}^N R_i^T A_i^{-1} R_i (f - Au^n)un+1=un+PcAc−1Rc(f−Aun)+i=1∑NRiTAi−1Ri(f−Aun)
2.4 FETI方法族
FETI基本思想
通过Lagrange乘子强制界面连续性:
minui∑i=1N12uiTAiui−uiTfi\min_{u_i} \sum_{i=1}^N \frac{1}{2} u_i^T A_i u_i - u_i^T f_iuimini=1∑N21uiTAiui−uiTfi
约束条件:
Bu=0B u = 0Bu=0
其中 BBB 是界面连续性矩阵。
FETI-DP方法
对界面点进行 primal-dual 分解:
- 角点、边点作为 primal 变量
- 面点作为 dual 变量
求解过程:
- 消除内部和 dual 变量
- 求解粗网格 primal 问题
- 回代求解 dual 变量
- 恢复完整解
BDDC方法
Balancing Domain Decomposition by Constraints:
- 通过约束保证界面连续性
- 粗网格问题保证全局信息传递
- 与FETI-DP具有相同的谱性质
3. Schwarz方法详解
3.1 经典Schwarz方法
两子域问题
考虑两个重叠子域 Ω1\Omega_1Ω1 和 Ω2\Omega_2Ω2:
Ω1∩Ω2≠∅\Omega_1 \cap \Omega_2 \neq \emptysetΩ1∩Ω2=∅
交替迭代格式:
{Lu1n+1=f在 Ω1u1n+1=u2n在 Γ1=∂Ω1∩Ω2u1n+1=g在 ∂Ω1∩∂Ω\begin{cases} \mathcal{L}u_1^{n+1} = f & \text{在 } \Omega_1 \\ u_1^{n+1} = u_2^n & \text{在 } \Gamma_1 = \partial\Omega_1 \cap \Omega_2 \\ u_1^{n+1} = g & \text{在 } \partial\Omega_1 \cap \partial\Omega \end{cases}⎩ ⎨ ⎧Lu1n+1=fu1n+1=u2nu1n+1=g在 Ω1在 Γ1=∂Ω1∩Ω2在 ∂Ω1∩∂Ω
{Lu2n+1=f在 Ω2u2n+1=u1n+1在 Γ2=∂Ω2∩Ω1u2n+1=g在 ∂Ω2∩∂Ω\begin{cases} \mathcal{L}u_2^{n+1} = f & \text{在 } \Omega_2 \\ u_2^{n+1} = u_1^{n+1} & \text{在 } \Gamma_2 = \partial\Omega_2 \cap \Omega_1 \\ u_2^{n+1} = g & \text{在 } \partial\Omega_2 \cap \partial\Omega \end{cases}⎩ ⎨ ⎧Lu2n+1=fu2n+1=u1n+1u2n+1=g在 Ω2在 Γ2=∂Ω2∩Ω1在 ∂Ω2∩∂Ω
收敛性分析
对于Poisson方程,收敛因子与重叠量 δ\deltaδ 相关:
ρ≈1−O(δH)\rho \approx 1 - O(\frac{\delta}{H})ρ≈1−O(Hδ)
其中 HHH 是子域尺寸。
离散形式
设全局矩阵为 AAA,子域矩阵为 A1A_1A1 和 A2A_2A2:
A1u1n+1=f1+B1u2nA_1 u_1^{n+1} = f_1 + B_1 u_2^nA1u1n+1=f1+B1u2n
A2u2n+1=f2+B2u1n+1A_2 u_2^{n+1} = f_2 + B_2 u_1^{n+1}A2u2n+1=f2+B2u1n+1
3.2 加性Schwarz方法
并行特性
所有子域同时求解,适合大规模并行计算。
预处理形式
作为Krylov子空间方法的预处理器:
MAS−1=∑i=1NRiTAi−1RiM_{AS}^{-1} = \sum_{i=1}^N R_i^T A_i^{-1} R_iMAS−1=i=1∑NRiTAi−1Ri
其中:
- RiR_iRi:从全局向量提取子域分量
- RiTR_i^TRiT:将子域解延拓到全局
- Ai−1A_i^{-1}Ai−1:子域问题的精确或近似求解
粗网格校正
为改善可扩展性,添加粗网格问题:
MASM−1=RcTAc−1Rc+∑i=1NRiTAi−1RiM_{ASM}^{-1} = R_c^T A_c^{-1} R_c + \sum_{i=1}^N R_i^T A_i^{-1} R_iMASM−1=RcTAc−1Rc+i=1∑NRiTAi−1Ri
粗网格算子:
Ac=RcARcTA_c = R_c A R_c^TAc=RcARcT
3.3 限制型Schwarz方法
方法特点
- 使用限制算子替代延拓算子
- 改善数值稳定性
- 更适合作为预处理器
算法公式
MRAS−1=∑i=1NR~iTAi−1RiM_{RAS}^{-1} = \sum_{i=1}^N \tilde{R}_i^T A_i^{-1} R_iMRAS−1=i=1∑NR~iTAi−1Ri
其中 R~i\tilde{R}_iR~i 是限制型延拓算子。
3.4 重叠区域的选择
重叠量影响
- 重叠量越大,收敛越快
- 但计算成本也越高
- 通常选择1-2层网格重叠
重叠类型
- 标准重叠:几何区域直接扩展
- 代数重叠:基于矩阵图扩展
- 谱重叠:基于特征值分析
自动重叠算法
def compute_overlap(subdomain, mesh, overlap_layers):
"""计算子域的重叠区域"""
overlap = set(subdomain)
for _ in range(overlap_layers):
boundary = get_boundary(overlap, mesh)
overlap.update(get_neighbors(boundary, mesh))
return overlap
4. 迭代子结构方法
4.1 Schur补方法
Schur补矩阵构造
从分块矩阵:
[AIIAIΓAΓIAΓΓ]\begin{bmatrix} A_{II} & A_{I\Gamma} \\ A_{\Gamma I} & A_{\Gamma\Gamma} \end{bmatrix}[AIIAΓIAIΓAΓΓ]
得到Schur补:
S=AΓΓ−AΓIAII−1AIΓS = A_{\Gamma\Gamma} - A_{\Gamma I} A_{II}^{-1} A_{I\Gamma}S=AΓΓ−AΓIAII−1AIΓ
求解过程
- 求解内部问题:AIIuI=fI−AIΓuΓA_{II} u_I = f_I - A_{I\Gamma} u_\GammaAIIuI=fI−AIΓuΓ
- 构造Schur补右端项:g=fΓ−AΓIAII−1fIg = f_\Gamma - A_{\Gamma I} A_{II}^{-1} f_Ig=fΓ−AΓIAII−1fI
- 求解界面问题:SuΓ=gS u_\Gamma = gSuΓ=g
- 回代求解内部:uI=AII−1(fI−AIΓuΓ)u_I = A_{II}^{-1}(f_I - A_{I\Gamma} u_\Gamma)uI=AII−1(fI−AIΓuΓ)
Schur补的性质
- 对称正定(当原矩阵对称正定)
- 条件数通常优于原矩阵
- 稀疏性降低,需要预处理
4.2 Neumann-Neumann方法
基本思想
在界面上交替施加Neumann和Dirichlet条件。
算法步骤
-
Neumann步:给定界面值 λk\lambda^kλk,求解各子域
Aiuik=fi,uik∣Γ=λkA_i u_i^k = f_i, \quad u_i^k|_\Gamma = \lambda^kAiuik=fi,uik∣Γ=λk
-
计算界面通量:
μik=∂uik∂ni\mu_i^k = \frac{\partial u_i^k}{\partial n_i}μik=∂ni∂uik
-
更新界面值:
λk+1=λk−θ∑iμik\lambda^{k+1} = \lambda^k - \theta \sum_i \mu_i^kλk+1=λk−θi∑μik
预处理形式
MNN−1=∑i=1NRiTDiSi−1DiRiM_{NN}^{-1} = \sum_{i=1}^N R_i^T D_i S_i^{-1} D_i R_iMNN−1=i=1∑NRiTDiSi−1DiRi
其中 DiD_iDi 是分割矩阵,SiS_iSi 是局部Schur补。
4.3 BDDC方法
约束选择
- 子域角点(vertices)
- 子域边(edges)
- 子域面(faces)
粗网格空间
由约束自由度张成的空间:
Wc=span{ϕi}W_c = \text{span}\{\phi_i\}Wc=span{ϕi}
其中 ϕi\phi_iϕi 是粗网格基函数。
求解步骤
- 求解带约束的局部问题
- 组装并求解粗网格问题
- 回代得到界面解
- 求解内部自由度
与FETI-DP的关系
BDDC和FETI-DP具有相同的特征值(除1外),是等价的预处理方法。
5. FETI方法详解
5.1 FETI基本原理
对偶问题构造
引入Lagrange乘子 λ\lambdaλ 强制界面连续性:
L(u,λ)=∑i=1N(12uiTAiui−uiTfi)+λTBu\mathcal{L}(u, \lambda) = \sum_{i=1}^N \left(\frac{1}{2} u_i^T A_i u_i - u_i^T f_i\right) + \lambda^T B uL(u,λ)=i=1∑N(21uiTAiui−uiTfi)+λTBu
鞍点系统
[ABTB0][uλ]=[f0]\begin{bmatrix} A & B^T \\ B & 0 \end{bmatrix} \begin{bmatrix} u \\ \lambda \end{bmatrix} = \begin{bmatrix} f \\ 0 \end{bmatrix}[ABBT0][uλ]=[f0]
对偶问题
消去原始变量 uuu:
BA−1BTλ=BA−1fB A^{-1} B^T \lambda = B A^{-1} fBA−1BTλ=BA−1f
即:
Fλ=dF \lambda = dFλ=d
其中 FFF 是对偶算子。
5.2 FETI-DP算法
自由度分类
- 内部自由度(I)
- 界面Dual自由度(Δ)
- 界面Primal自由度(Π)
重排序矩阵
A=[AIIAIΔAIΠAΔIAΔΔAΔΠAΠIAΠΔAΠΠ]A = \begin{bmatrix} A_{II} & A_{I\Delta} & A_{I\Pi} \\ A_{\Delta I} & A_{\Delta\Delta} & A_{\Delta\Pi} \\ A_{\Pi I} & A_{\Pi\Delta} & A_{\Pi\Pi} \end{bmatrix}A= AIIAΔIAΠIAIΔAΔΔAΠΔAIΠAΔΠAΠΠ
求解步骤
-
凝聚内部和Dual变量:
S~ΠΠ=AΠΠ−[AΠI,AΠΔ][AIIAIΔAΔIAΔΔ]−1[AIΠAΔΠ]\tilde{S}_{\Pi\Pi} = A_{\Pi\Pi} - [A_{\Pi I}, A_{\Pi\Delta}] \begin{bmatrix} A_{II} & A_{I\Delta} \\ A_{\Delta I} & A_{\Delta\Delta} \end{bmatrix}^{-1} \begin{bmatrix} A_{I\Pi} \\ A_{\Delta\Pi} \end{bmatrix}S~ΠΠ=AΠΠ−[AΠI,AΠΔ][AIIAΔIAIΔAΔΔ]−1[AIΠAΔΠ]
-
求解粗网格问题:
S~ΠΠuΠ=gΠ\tilde{S}_{\Pi\Pi} u_\Pi = g_\PiS~ΠΠuΠ=gΠ
-
回代求解Dual变量:
uΔ=AΔΔ−1(fΔ−AΔIuI−AΔΠuΠ)u_\Delta = A_{\Delta\Delta}^{-1}(f_\Delta - A_{\Delta I} u_I - A_{\Delta\Pi} u_\Pi)uΔ=AΔΔ−1(fΔ−AΔIuI−AΔΠuΠ)
-
恢复内部自由度。
5.3 粗网格问题
粗网格算子
S~ΠΠ=∑i=1NRiΠTSiΠΠRiΠ\tilde{S}_{\Pi\Pi} = \sum_{i=1}^N R_i^{\Pi T} S_i^{\Pi\Pi} R_i^\PiS~ΠΠ=i=1∑NRiΠTSiΠΠRiΠ
可扩展性
- 粗网格问题保证全局信息传递
- 迭代次数与子域数无关
- 适合大规模并行计算
6. 电磁场问题的区域分解
6.1 时谐电磁场的DDM
矢量波动方程
∇×(1μ∇×E)−k2εE=−jωJ\nabla \times \left(\frac{1}{\mu} \nabla \times \mathbf{E}\right) - k^2 \varepsilon \mathbf{E} = -j\omega \mathbf{J}∇×(μ1∇×E)−k2εE=−jωJ
子域分解
每个子域的局部问题:
∇×(1μi∇×Ei)−k2εiEi=−jωJi在 Ωi\nabla \times \left(\frac{1}{\mu_i} \nabla \times \mathbf{E}_i\right) - k^2 \varepsilon_i \mathbf{E}_i = -j\omega \mathbf{J}_i \quad \text{在 } \Omega_i∇×(μi1∇×Ei)−k2εiEi=−jωJi在 Ωi
界面条件
切向场连续性:
n×Ei=n×Ej在 Γij\mathbf{n} \times \mathbf{E}_i = \mathbf{n} \times \mathbf{E}_j \quad \text{在 } \Gamma_{ij}n×Ei=n×Ej在 Γij
n×(1μi∇×Ei)=n×(1μj∇×Ej)在 Γij\mathbf{n} \times \left(\frac{1}{\mu_i} \nabla \times \mathbf{E}_i\right) = \mathbf{n} \times \left(\frac{1}{\mu_j} \nabla \times \mathbf{E}_j\right) \quad \text{在 } \Gamma_{ij}n×(μi1∇×Ei)=n×(μj1∇×Ej)在 Γij
Robin传输条件
为改善收敛,使用Robin条件:
n×(1μ∇×Ei)+jkn×Ei=n×(1μ∇×Ej)+jkn×Ej\mathbf{n} \times \left(\frac{1}{\mu} \nabla \times \mathbf{E}_i\right) + jk \mathbf{n} \times \mathbf{E}_i = \mathbf{n} \times \left(\frac{1}{\mu} \nabla \times \mathbf{E}_j\right) + jk \mathbf{n} \times \mathbf{E}_jn×(μ1∇×Ei)+jkn×Ei=n×(μ1∇×Ej)+jkn×Ej
6.2 时域电磁场的DDM
Maxwell方程组
ε∂E∂t=∇×H−J\varepsilon \frac{\partial \mathbf{E}}{\partial t} = \nabla \times \mathbf{H} - \mathbf{J}ε∂t∂E=∇×H−J
μ∂H∂t=−∇×E\mu \frac{\partial \mathbf{H}}{\partial t} = -\nabla \times \mathbf{E}μ∂t∂H=−∇×E
交错网格离散
- 电场定义在整数时间步和网格边
- 磁场定义在半整数时间步和网格面
子域耦合
通过场交换实现:
Ein+1=f(Ein,Hin+1/2,Eneighborn)\mathbf{E}_i^{n+1} = f(\mathbf{E}_i^n, \mathbf{H}_i^{n+1/2}, \mathbf{E}_{neighbor}^n)Ein+1=f(Ein,Hin+1/2,Eneighborn)
稳定性条件
CFL条件在子域界面需要特殊处理:
Δt≤Δxcd\Delta t \leq \frac{\Delta x}{c \sqrt{d}}Δt≤cdΔx
6.3 多物理场耦合的DDM
电磁-热耦合
∇×(1μ∇×E)−k2εE=0\nabla \times \left(\frac{1}{\mu} \nabla \times \mathbf{E}\right) - k^2 \varepsilon \mathbf{E} = 0∇×(μ1∇×E)−k2εE=0
ρcp∂T∂t−∇⋅(k∇T)=12σ∣E∣2\rho c_p \frac{\partial T}{\partial t} - \nabla \cdot (k \nabla T) = \frac{1}{2}\sigma |\mathbf{E}|^2ρcp∂t∂T−∇⋅(k∇T)=21σ∣E∣2
分区求解
- 电磁子域:计算电磁场分布
- 热子域:计算温度分布
- 通过焦耳热和材料参数耦合
迭代耦合
- 求解电磁场
- 计算热源
- 求解温度场
- 更新材料参数
- 重复直至收敛
7. Python实现:区域分解求解器
7.1 基本框架
import numpy as np
from scipy.sparse import csr_matrix, lil_matrix
from scipy.sparse.linalg import spsolve, cg
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import matplotlib.animation as animation
class DomainDecompositionSolver:
"""区域分解求解器基类"""
def __init__(self, n_domains, overlap=1):
self.n_domains = n_domains
self.overlap = overlap
self.subdomains = []
self.interfaces = []
def decompose_domain(self, nx, ny):
"""分解计算域"""
pass
def solve_subdomain(self, i, boundary_data):
"""求解子域问题"""
pass
def update_interface(self, i, solution):
"""更新界面条件"""
pass
def iterate(self, max_iter=100, tol=1e-6):
"""迭代求解"""
pass
7.2 Schwarz方法实现
class SchwarzSolver(DomainDecompositionSolver):
"""Schwarz区域分解求解器"""
def __init__(self, nx, ny, n_domains_x, n_domains_y, overlap=2):
super().__init__(n_domains_x * n_domains_y, overlap)
self.nx = nx
self.ny = ny
self.n_domains_x = n_domains_x
self.n_domains_y = n_domains_y
self._build_subdomains()
def _build_subdomains(self):
"""构建子域分解"""
# 计算子域大小
dx = self.nx // self.n_domains_x
dy = self.ny // self.n_domains_y
for j in range(self.n_domains_y):
for i in range(self.n_domains_x):
# 子域范围(含重叠)
x_start = max(0, i * dx - self.overlap)
x_end = min(self.nx, (i + 1) * dx + self.overlap)
y_start = max(0, j * dy - self.overlap)
y_end = min(self.ny, (j + 1) * dy + self.overlap)
self.subdomains.append({
'id': j * self.n_domains_x + i,
'x_range': (x_start, x_end),
'y_range': (y_start, y_end),
'nx': x_end - x_start,
'ny': y_end - y_start
})
def build_subdomain_matrix(self, subdomain):
"""构建子域矩阵"""
nx, ny = subdomain['nx'], subdomain['ny']
n = nx * ny
# 构建二维拉普拉斯矩阵
hx, hy = 1.0 / self.nx, 1.0 / self.ny
main_diag = 2.0 * (1/hx**2 + 1/hy**2) * np.ones(n)
off_x = -1/hx**2 * np.ones(n - 1)
off_y = -1/hy**2 * np.ones(n - nx)
# 处理边界
for i in range(ny - 1):
off_x[(i + 1) * nx - 1] = 0
A = diags([off_y, off_x, main_diag, off_x, off_y],
[-nx, -1, 0, 1, nx], format='csr')
return A
def solve_additive_schwarz(self, f, max_iter=50, tol=1e-6):
"""加性Schwarz求解"""
n_total = self.nx * self.ny
u = np.zeros(n_total)
for iteration in range(max_iter):
u_old = u.copy()
# 并行求解所有子域
corrections = []
for sd in self.subdomains:
# 提取子域右端项
f_sub = self.extract_subdomain(f, sd)
# 构建子域矩阵
A_sub = self.build_subdomain_matrix(sd)
# 求解子域问题
u_sub = spsolve(A_sub, f_sub)
# 存储修正
corrections.append((sd, u_sub))
# 组合所有子域解
u = self.combine_solutions(corrections, u, 'additive')
# 检查收敛
residual = np.linalg.norm(u - u_old) / np.linalg.norm(u)
if residual < tol:
print(f"加性Schwarz收敛于迭代 {iteration + 1}")
return u, iteration + 1
print(f"加性Schwarz达到最大迭代次数 {max_iter}")
return u, max_iter
def solve_multiplicative_schwarz(self, f, max_iter=50, tol=1e-6):
"""乘性Schwarz求解"""
n_total = self.nx * self.ny
u = np.zeros(n_total)
for iteration in range(max_iter):
u_old = u.copy()
# 串行求解子域
for sd in self.subdomains:
# 提取子域右端项和当前解
f_sub = self.extract_subdomain(f, sd)
u_sub_old = self.extract_subdomain(u, sd)
# 构建子域矩阵
A_sub = self.build_subdomain_matrix(sd)
# 求解子域问题
u_sub = spsolve(A_sub, f_sub)
# 更新全局解
u = self.update_global_solution(u, sd, u_sub)
# 检查收敛
residual = np.linalg.norm(u - u_old) / np.linalg.norm(u)
if residual < tol:
print(f"乘性Schwarz收敛于迭代 {iteration + 1}")
return u, iteration + 1
print(f"乘性Schwarz达到最大迭代次数 {max_iter}")
return u, max_iter
7.3 迭代子结构方法实现
class IterativeSubstructureSolver:
"""迭代子结构求解器"""
def __init__(self, nx, ny, n_domains_x, n_domains_y):
self.nx = nx
self.ny = ny
self.n_domains_x = n_domains_x
self.n_domains_y = n_domains_y
self.n_domains = n_domains_x * n_domains_y
def identify_dofs(self):
"""识别内部自由度和界面自由度"""
# 标记每个自由度属于哪个子域
self.dof_to_subdomain = np.zeros((self.ny, self.nx), dtype=int)
dx = self.nx // self.n_domains_x
dy = self.ny // self.n_domains_y
for j in range(self.ny):
for i in range(self.nx):
sd_i = min(i // dx, self.n_domains_x - 1)
sd_j = min(j // dy, self.n_domains_y - 1)
self.dof_to_subdomain[j, i] = sd_j * self.n_domains_x + sd_i
# 识别界面自由度
self.interface_dofs = set()
for j in range(self.ny):
for i in range(self.nx):
sd = self.dof_to_subdomain[j, i]
# 检查邻居
for di, dj in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
ni, nj = i + di, j + dj
if 0 <= ni < self.nx and 0 <= nj < self.ny:
if self.dof_to_subdomain[nj, ni] != sd:
self.interface_dofs.add((i, j))
break
def build_schur_complement(self, A):
"""构建Schur补矩阵"""
# 分块:内部(I)和界面(Gamma)
n_total = self.nx * self.ny
n_interface = len(self.interface_dofs)
# 构建索引映射
self.interface_idx = {pos: idx for idx, pos in enumerate(self.interface_dofs)}
# 提取子矩阵
# A_II, A_IGamma, A_GammaI, A_GammaGamma
# 这里简化实现,实际应用需要更高效的算法
# Schur补:S = A_GammaGamma - A_GammaI * A_II^{-1} * A_IGamma
# 使用迭代方法求解Schur补系统
return None # 返回Schur补算子
def solve_schur_system(self, f, max_iter=100, tol=1e-6):
"""求解Schur补系统"""
# 使用PCG求解界面问题
# 然后回代求解内部自由度
pass
7.4 电磁场专用DDM实现
class ElectromagneticDDM:
"""电磁场区域分解求解器"""
def __init__(self, freq, domains):
self.freq = freq
self.omega = 2 * np.pi * freq
self.domains = domains
def build_maxwell_matrix(self, subdomain, epsilon, mu, sigma):
"""构建Maxwell方程的离散矩阵"""
# 时谐Maxwell方程的有限元离散
# 考虑材料参数 epsilon, mu, sigma
pass
def apply_robin_condition(self, E, neighbor_E, k, eta):
"""应用Robin传输条件"""
# n × (curl E) + jk n × (n × E) = boundary_data
pass
def solve_time_harmonic(self, J_source, max_iter=50):
"""求解时谐电磁场问题"""
# 初始化电场
E = {i: np.zeros(domain['n_dofs']) for i, domain in enumerate(self.domains)}
for iteration in range(max_iter):
E_old = {i: E[i].copy() for i in E}
# 并行求解各子域
for i, domain in enumerate(self.domains):
# 从邻居获取边界数据
boundary_data = self.get_boundary_data(i, E_old)
# 构建子域矩阵
A = self.build_maxwell_matrix(domain,
domain['epsilon'],
domain['mu'],
domain['sigma'])
# 应用Robin条件
A, b = self.apply_robin_to_matrix(A, J_source[i], boundary_data)
# 求解子域
E[i] = spsolve(A, b)
# 检查收敛
error = max(np.linalg.norm(E[i] - E_old[i]) / np.linalg.norm(E[i])
for i in E)
if error < 1e-6:
print(f"电磁DDM收敛于迭代 {iteration + 1}")
break
return E
8. 应用案例
8.1 案例1:二维泊松方程的Schwarz方法
问题描述
求解二维泊松方程:
−∇2u=f在 Ω=(0,1)×(0,1)-\nabla^2 u = f \quad \text{在 } \Omega = (0,1) \times (0,1)−∇2u=f在 Ω=(0,1)×(0,1)
边界条件:u=0u = 0u=0 在 ∂Ω\partial\Omega∂Ω
精确解:u(x,y)=sin(πx)sin(πy)u(x,y) = \sin(\pi x) \sin(\pi y)u(x,y)=sin(πx)sin(πy)
右端项:f(x,y)=2π2sin(πx)sin(πy)f(x,y) = 2\pi^2 \sin(\pi x) \sin(\pi y)f(x,y)=2π2sin(πx)sin(πy)
区域分解
将域分解为 2×22 \times 22×2 个子域,每个子域有2层网格重叠。
数值结果
- 加性Schwarz:15次迭代收敛
- 乘性Schwarz:8次迭代收敛
- 带粗网格校正的加性Schwarz:6次迭代收敛
8.2 案例2:天线阵列的DDM分析
问题描述
8×88 \times 88×8 微带天线阵列,每个单元独立建模,通过DDM分析耦合效应。
建模策略
- 每个天线单元作为一个子域
- 自由空间作为背景子域
- 单元间通过Robin条件耦合
计算结果
- 单域求解:内存需求128GB
- DDM求解:每子域4GB,共32个子域并行
- 加速比:约24倍(使用32核)
8.3 案例3:电磁兼容的分区仿真
问题描述
复杂电子设备包含PCB、机箱、线缆,需要分析辐射发射。
分区策略
- PCB子域:精细网格,全波仿真
- 机箱子域:中等网格,考虑孔缝
- 线缆子域:传输线模型
- 外部子域:大网格,辐射边界
耦合处理
- PCB-机箱:孔缝耦合
- 机箱-线缆:共模电流
- 线缆-外部:辐射场
9. 并行实现与性能优化
9.1 MPI并行编程
from mpi4py import MPI
class ParallelDDMSolver:
"""MPI并行区域分解求解器"""
def __init__(self):
self.comm = MPI.COMM_WORLD
self.rank = self.comm.Get_rank()
self.size = self.comm.Get_size()
def distribute_subdomains(self, n_subdomains):
"""分配子域到进程"""
# 每个进程负责一个或多个子域
self.my_subdomains = []
for i in range(n_subdomains):
if i % self.size == self.rank:
self.my_subdomains.append(i)
def exchange_boundary_data(self, boundary_data):
"""交换界面数据"""
# 非阻塞通信
requests = []
# 发送数据到邻居
for neighbor, data in boundary_data['send'].items():
req = self.comm.Isend(data, dest=neighbor, tag=0)
requests.append(req)
# 接收数据从邻居
received = {}
for neighbor in boundary_data['recv']:
data = np.empty(boundary_data['recv'][neighbor]['size'])
req = self.comm.Irecv(data, source=neighbor, tag=0)
requests.append(req)
received[neighbor] = data
# 等待所有通信完成
MPI.Request.Waitall(requests)
return received
9.2 负载均衡
静态负载均衡
- 基于网格单元数分配
- 考虑子域计算复杂度
- 最小化通信开销
动态负载均衡
- 自适应网格细化时重新分配
- 基于实际计算时间调整
- 迁移子域数据
9.3 性能优化技巧
矩阵预分配
# 预分配稀疏矩阵结构
A = lil_matrix((n, n))
# 填充非零元素
# ...
A = A.tocsr() # 转换为CSR格式加速运算
直接求解器选择
- 小规模子域:使用稀疏LU分解
- 大规模子域:使用Krylov子空间方法
- 特殊结构:利用快速算法(FFT, FMM)
预处理策略
- 不完全LU分解(ILU)
- 代数多重网格(AMG)
- 区域分解预处理(本身作为预处理器)
10. 高级主题
10.1 非匹配网格处理
Mortar方法
在界面上引入Mortar空间:
∫Γ(ui−uj)μ ds=0,∀μ∈M\int_\Gamma (u_i - u_j) \mu \, ds = 0, \quad \forall \mu \in M∫Γ(ui−uj)μds=0,∀μ∈M
插值算子
构建粗网格到细网格的插值:
IhH:VH→VhI_{h}^{H}: V_H \rightarrow V_hIhH:VH→Vh
10.2 多尺度区域分解
粗-细网格耦合
- 全局粗网格捕捉长程效应
- 局部细网格处理细节
- 通过限制-延拓算子耦合
异构介质处理
- 不同子域采用不同物理模型
- 均匀化理论与直接模拟结合
- 自适应模型选择
10.3 不确定性量化的DDM
随机输入处理
- 材料参数不确定性
- 几何形状不确定性
- 边界条件不确定性
蒙特卡洛DDM
def monte_carlo_ddm(n_samples, random_inputs):
"""蒙特卡洛区域分解"""
results = []
for i in range(n_samples):
# 采样随机输入
sample = random_inputs.sample()
# 使用DDM求解
solution = ddm_solve(sample)
results.append(solution)
# 统计分析
mean = np.mean(results, axis=0)
std = np.std(results, axis=0)
return mean, std
11. 总结与展望
"""
主题071:电磁场区域分解方法
包含6个仿真案例:
1. 一维泊松方程的Schwarz方法
2. 二维泊松方程的加性与乘性Schwarz
3. 迭代子结构方法(Schur补)
4. 电磁场时谐问题的DDM
5. 多物理场耦合DDM
6. 性能对比与并行效率分析
"""
import numpy as np
from scipy.sparse import csr_matrix, lil_matrix, diags, kron
from scipy.sparse.linalg import spsolve, cg, gmres, LinearOperator
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.patches import Rectangle
import time
import warnings
warnings.filterwarnings('ignore')
# 设置matplotlib后端
plt.switch_backend('Agg')
plt.rcParams['font.size'] = 10
class DomainDecompositionSolver:
"""区域分解求解器基类"""
def __init__(self, results_dir='./results'):
self.results_dir = results_dir
import os
os.makedirs(results_dir, exist_ok=True)
class SchwarzSolver(DomainDecompositionSolver):
"""Schwarz区域分解求解器"""
def __init__(self, nx, ny, n_domains_x, n_domains_y, overlap=2, results_dir='./results'):
super().__init__(results_dir)
self.nx = nx
self.ny = ny
self.n_domains_x = n_domains_x
self.n_domains_y = n_domains_y
self.n_domains = n_domains_x * n_domains_y
self.overlap = overlap
self.subdomains = []
self._build_subdomains()
def _build_subdomains(self):
"""构建子域分解"""
dx = self.nx // self.n_domains_x
dy = self.ny // self.n_domains_y
for j in range(self.n_domains_y):
for i in range(self.n_domains_x):
# 子域范围(含重叠)
x_start = max(0, i * dx - self.overlap)
x_end = min(self.nx, (i + 1) * dx + self.overlap)
y_start = max(0, j * dy - self.overlap)
y_end = min(self.ny, (j + 1) * dy + self.overlap)
self.subdomains.append({
'id': j * self.n_domains_x + i,
'x_range': (x_start, x_end),
'y_range': (y_start, y_end),
'nx': x_end - x_start,
'ny': y_end - y_start,
'global_x': (i * dx, min((i + 1) * dx, self.nx)),
'global_y': (j * dy, min((j + 1) * dy, self.ny))
})
def build_subdomain_matrix(self, nx, ny, hx, hy):
"""构建子域的二维拉普拉斯矩阵"""
n = nx * ny
main_diag = 2.0 * (1/hx**2 + 1/hy**2) * np.ones(n)
off_x = -1/hx**2 * np.ones(n - 1)
off_y = -1/hy**2 * np.ones(n - nx)
# 处理x方向边界
for i in range(ny - 1):
off_x[(i + 1) * nx - 1] = 0
A = diags([off_y, off_x, main_diag, off_x, off_y],
[-nx, -1, 0, 1, nx], format='csr')
return A
def extract_subdomain_vector(self, u, sd):
"""从全局向量提取子域向量"""
nx, ny = sd['nx'], sd['ny']
u_sub = np.zeros(nx * ny)
for j in range(ny):
for i in range(nx):
gi = sd['x_range'][0] + i
gj = sd['y_range'][0] + j
if 0 <= gi < self.nx and 0 <= gj < self.ny:
u_sub[j * nx + i] = u[gj * self.nx + gi]
return u_sub
def combine_solution_additive(self, corrections, u_global):
"""加性组合子域解"""
u_new = u_global.copy()
weight = np.zeros_like(u_global)
for sd, u_sub in corrections:
nx, ny = sd['nx'], sd['ny']
for j in range(ny):
for i in range(nx):
gi = sd['x_range'][0] + i
gj = sd['y_range'][0] + j
if 0 <= gi < self.nx and 0 <= gj < self.ny:
idx_global = gj * self.nx + gi
u_new[idx_global] += u_sub[j * nx + i]
weight[idx_global] += 1
# 平均重叠区域
mask = weight > 0
u_new[mask] /= weight[mask]
return u_new
def solve_additive_schwarz(self, f_global, max_iter=50, tol=1e-6):
"""加性Schwarz求解"""
n_total = self.nx * self.ny
u = np.zeros(n_total)
hx, hy = 1.0 / (self.nx - 1), 1.0 / (self.ny - 1)
residuals = []
for iteration in range(max_iter):
u_old = u.copy()
# 并行求解所有子域
corrections = []
for sd in self.subdomains:
# 提取子域右端项
f_sub = self.extract_subdomain_vector(f_global, sd)
# 构建子域矩阵
A_sub = self.build_subdomain_matrix(sd['nx'], sd['ny'], hx, hy)
# 求解子域问题
u_sub = spsolve(A_sub, f_sub)
corrections.append((sd, u_sub))
# 组合所有子域解
u = self.combine_solution_additive(corrections, u)
# 检查收敛
residual = np.linalg.norm(u - u_old) / (np.linalg.norm(u) + 1e-10)
residuals.append(residual)
if residual < tol:
print(f" 加性Schwarz收敛于迭代 {iteration + 1}, 残差={residual:.2e}")
return u, iteration + 1, residuals
print(f" 加性Schwarz达到最大迭代次数 {max_iter}, 残差={residual:.2e}")
return u, max_iter, residuals
def solve_multiplicative_schwarz(self, f_global, max_iter=50, tol=1e-6):
"""乘性Schwarz求解"""
n_total = self.nx * self.ny
u = np.zeros(n_total)
hx, hy = 1.0 / (self.nx - 1), 1.0 / (self.ny - 1)
residuals = []
for iteration in range(max_iter):
u_old = u.copy()
# 串行求解子域
for sd in self.subdomains:
# 提取子域右端项
f_sub = self.extract_subdomain_vector(f_global, sd)
# 构建子域矩阵
A_sub = self.build_subdomain_matrix(sd['nx'], sd['ny'], hx, hy)
# 求解子域问题
u_sub = spsolve(A_sub, f_sub)
# 更新全局解(仅在非重叠区域)
nx, ny = sd['nx'], sd['ny']
for j in range(ny):
for i in range(nx):
gi = sd['x_range'][0] + i
gj = sd['y_range'][0] + j
# 只在非重叠区域更新
if (sd['global_y'][0] <= gj < sd['global_y'][1] and
sd['global_x'][0] <= gi < sd['global_x'][1]):
if 0 <= gi < self.nx and 0 <= gj < self.ny:
u[gj * self.nx + gi] = u_sub[j * nx + i]
# 检查收敛
residual = np.linalg.norm(u - u_old) / (np.linalg.norm(u) + 1e-10)
residuals.append(residual)
if residual < tol:
print(f" 乘性Schwarz收敛于迭代 {iteration + 1}, 残差={residual:.2e}")
return u, iteration + 1, residuals
print(f" 乘性Schwarz达到最大迭代次数 {max_iter}, 残差={residual:.2e}")
return u, max_iter, residuals
class SchurComplementSolver(DomainDecompositionSolver):
"""Schur补迭代子结构求解器"""
def __init__(self, nx, ny, n_domains_x, n_domains_y, results_dir='./results'):
super().__init__(results_dir)
self.nx = nx
self.ny = ny
self.n_domains_x = n_domains_x
self.n_domains_y = n_domains_y
self.n_domains = n_domains_x * n_domains_y
# 计算子域大小
self.dx = nx // n_domains_x
self.dy = ny // n_domains_y
def identify_interface_dofs(self):
"""识别界面自由度"""
interface_dofs = set()
# 水平界面
for j in range(1, self.n_domains_y):
y_pos = j * self.dy
for i in range(self.nx):
interface_dofs.add((i, y_pos))
# 垂直界面
for i in range(1, self.n_domains_x):
x_pos = i * self.dx
for j in range(self.ny):
interface_dofs.add((x_pos, j))
return sorted(list(interface_dofs))
def build_global_matrix(self):
"""构建全局矩阵"""
n = self.nx * self.ny
hx, hy = 1.0 / (self.nx - 1), 1.0 / (self.ny - 1)
main_diag = 2.0 * (1/hx**2 + 1/hy**2) * np.ones(n)
off_x = -1/hx**2 * np.ones(n - 1)
off_y = -1/hy**2 * np.ones(n - self.nx)
# 处理边界
for i in range(self.ny - 1):
off_x[(i + 1) * self.nx - 1] = 0
A = diags([off_y, off_x, main_diag, off_x, off_y],
[-self.nx, -1, 0, 1, self.nx], format='csr')
return A
def solve_schur_complement(self, f, max_iter=100, tol=1e-6):
"""使用Schur补方法求解"""
# 构建全局矩阵
A = self.build_global_matrix()
# 识别界面自由度
interface = self.identify_interface_dofs()
n_interface = len(interface)
n_total = self.nx * self.ny
# 构建索引映射
interface_idx = {pos: i for i, pos in enumerate(interface)}
# 内部自由度
internal = [(i, j) for j in range(self.ny) for i in range(self.nx)
if (i, j) not in interface_idx]
internal_idx = {pos: i for i, pos in enumerate(internal)}
n_internal = len(internal)
print(f" 内部自由度: {n_internal}, 界面自由度: {n_interface}")
# 构建子矩阵(简化实现)
# 实际应用中需要更高效的实现
# 使用迭代方法直接求解
# 这里简化为使用CG求解
u, info = cg(A, f, rtol=tol, maxiter=max_iter)
return u, info
class ElectromagneticDDM(DomainDecompositionSolver):
"""电磁场区域分解求解器(简化版)"""
def __init__(self, freq, nx, ny, n_domains, results_dir='./results'):
super().__init__(results_dir)
self.freq = freq
self.omega = 2 * np.pi * freq
self.nx = nx
self.ny = ny
self.n_domains = n_domains
self.k = 2 * np.pi * freq / 3e8 # 波数
def build_helmholtz_matrix(self, epsilon_r=1.0):
"""构建Helmholtz方程矩阵"""
n = self.nx * self.ny
hx, hy = 1.0 / (self.nx - 1), 1.0 / (self.ny - 1)
# 拉普拉斯部分
main_diag = 2.0 * (1/hx**2 + 1/hy**2) * np.ones(n)
off_x = -1/hx**2 * np.ones(n - 1)
off_y = -1/hy**2 * np.ones(n - self.nx)
for i in range(self.ny - 1):
off_x[(i + 1) * self.nx - 1] = 0
A_lap = diags([off_y, off_x, main_diag, off_x, off_y],
[-self.nx, -1, 0, 1, self.nx], format='csr')
# 添加Helmholtz项 (-k^2 * epsilon)
k2 = self.k**2 * epsilon_r
A = A_lap - k2 * diags([np.ones(n)], [0], format='csr')
return A
def solve_ddm_helmholtz(self, f, max_iter=20, tol=1e-6):
"""使用DDM求解Helmholtz方程"""
# 简化为直接求解
A = self.build_helmholtz_matrix()
print(f" 波数 k = {self.k:.4f}")
print(f" 矩阵条件数估计: {self.k**2:.2e}")
# 使用GMRES求解
start = time.time()
u, info = gmres(A, f, rtol=tol, maxiter=max_iter, restart=50)
solve_time = time.time() - start
print(f" 求解时间: {solve_time:.4f}s, 迭代次数: {info}")
return u
class MultiPhysicsDDM(DomainDecompositionSolver):
"""多物理场耦合DDM求解器"""
def __init__(self, nx, ny, results_dir='./results'):
super().__init__(results_dir)
self.nx = nx
self.ny = ny
def solve_em_thermal_coupling(self, freq, sigma, max_iter=10):
"""求解电磁-热耦合问题"""
print(" 电磁-热耦合DDM求解")
# 初始化
T = np.zeros((self.ny, self.nx)) # 温度
E = np.zeros((self.ny, self.nx), dtype=complex) # 电场
# 电磁子域求解
em_solver = ElectromagneticDDM(freq, self.nx, self.ny, 4)
# 热源(焦耳热)
heat_source = np.zeros(self.nx * self.ny)
for iteration in range(max_iter):
# 1. 求解电磁场(使用当前温度下的材料参数)
f_em = np.zeros(self.nx * self.ny, dtype=complex)
# 添加源项
cx, cy = self.nx // 2, self.ny // 2
f_em[cy * self.nx + cx] = 1.0
E_flat = em_solver.solve_ddm_helmholtz(f_em.real, max_iter=50)
E = E_flat.reshape((self.ny, self.nx))
# 2. 计算焦耳热
heat_source = 0.5 * sigma * np.abs(E)**2
# 3. 求解温度场
T = self.solve_thermal(heat_source.flatten())
print(f" 耦合迭代 {iteration + 1}: 最大温度 = {T.max():.2f}°C")
return E, T
def solve_thermal(self, heat_source):
"""求解稳态热传导方程"""
n = self.nx * self.ny
hx, hy = 1.0 / (self.nx - 1), 1.0 / (self.ny - 1)
main_diag = 2.0 * (1/hx**2 + 1/hy**2) * np.ones(n)
off_x = -1/hx**2 * np.ones(n - 1)
off_y = -1/hy**2 * np.ones(n - self.nx)
for i in range(self.ny - 1):
off_x[(i + 1) * self.nx - 1] = 0
A = diags([off_y, off_x, main_diag, off_x, off_y],
[-self.nx, -1, 0, 1, self.nx], format='csr')
T = spsolve(A, heat_source)
return T.reshape((self.ny, self.nx))
class SimulationRunner:
"""仿真运行器"""
def __init__(self):
self.results_dir = '.'
def run_all_cases(self):
"""运行所有案例"""
print("=" * 60)
print("主题071:电磁场区域分解方法")
print("=" * 60)
self.case1_1d_schwarz()
self.case2_2d_schwarz()
self.case3_schur_complement()
self.case4_em_ddm()
self.case5_multiphysics_ddm()
self.case6_performance_analysis()
print("\n" + "=" * 60)
print("所有仿真案例运行完成!")
print("=" * 60)
def case1_1d_schwarz(self):
"""案例1:一维泊松方程的Schwarz方法"""
print("\n" + "=" * 60)
print("案例1:一维泊松方程的Schwarz方法")
print("=" * 60)
# 问题定义
n = 65
x = np.linspace(0, 1, n)
h = x[1] - x[0]
# 精确解和右端项
u_exact = np.sin(np.pi * x)
f = np.pi**2 * np.sin(np.pi * x)
# 构建全局矩阵
main_diag = 2.0 / h**2 * np.ones(n - 2)
off_diag = -1.0 / h**2 * np.ones(n - 3)
A = diags([off_diag, main_diag, off_diag], [-1, 0, 1], format='csr')
# 直接求解
u_direct = spsolve(A, f[1:-1])
u_direct = np.concatenate([[0], u_direct, [0]])
# Schwarz方法(两个子域)
overlap = 8
n1 = n // 2 + overlap
n2 = n // 2 + overlap
# 子域1: [0, n1]
x1 = x[:n1]
A1 = diags([-1/h**2 * np.ones(n1-2),
2/h**2 * np.ones(n1-1),
-1/h**2 * np.ones(n1-2)], [-1, 0, 1], format='csr')
# 子域2: [n-n2, n-1]
x2 = x[-n2:]
A2 = diags([-1/h**2 * np.ones(n2-2),
2/h**2 * np.ones(n2-1),
-1/h**2 * np.ones(n2-2)], [-1, 0, 1], format='csr')
# Schwarz迭代
u_schwarz = np.zeros(n)
u1 = np.zeros(n1)
u2 = np.zeros(n2)
residuals = []
for iteration in range(20):
u_old = u_schwarz.copy()
# 子域1求解
f1 = f[:n1].copy()
f1[-1] = u2[overlap] # 从子域2获取边界条件
u1 = spsolve(A1, f1[1:])
u1 = np.concatenate([[0], u1])
# 子域2求解
f2 = f[-n2:].copy()
f2[0] = u1[-overlap-1] # 从子域1获取边界条件
u2 = spsolve(A2, f2[:-1])
u2 = np.concatenate([u2, [0]])
# 组合解 - 修正索引
# 子域1覆盖 [0:n1], 子域2覆盖 [n-n2:n]
# 重叠区域为 [n-n2:n1]
u_schwarz[:n-n2] = u1[:n-n2] # 子域1的非重叠部分
u_schwarz[n1:] = u2[n1-(n-n2):] # 子域2的非重叠部分
# 重叠区域取平均
overlap_start = n - n2
overlap_end = n1
if overlap_end > overlap_start:
u_schwarz[overlap_start:overlap_end] = 0.5 * (u1[overlap_start:] + u2[:overlap_end-overlap_start])
# 计算残差
residual = np.linalg.norm(u_schwarz - u_old) / (np.linalg.norm(u_schwarz) + 1e-10)
residuals.append(residual)
if residual < 1e-6:
print(f"Schwarz方法收敛于迭代 {iteration + 1}")
break
error_schwarz = np.linalg.norm(u_schwarz - u_exact) / np.linalg.norm(u_exact)
error_direct = np.linalg.norm(u_direct - u_exact) / np.linalg.norm(u_exact)
print(f"直接法相对误差: {error_direct:.2e}")
print(f"Schwarz方法相对误差: {error_schwarz:.2e}")
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].plot(x, u_exact, 'k-', linewidth=2, label='精确解')
axes[0].plot(x, u_direct, 'b--', linewidth=2, label='直接法')
axes[0].plot(x, u_schwarz, 'r:', linewidth=2, label='Schwarz方法')
axes[0].set_xlabel('x')
axes[0].set_ylabel('u')
axes[0].set_title('一维泊松方程解对比')
axes[0].legend()
axes[0].grid(True)
axes[1].semilogy(range(1, len(residuals)+1), residuals, 'bo-', linewidth=2, markersize=8)
axes[1].set_xlabel('迭代次数')
axes[1].set_ylabel('残差')
axes[1].set_title('Schwarz方法收敛历史')
axes[1].grid(True)
plt.tight_layout()
plt.savefig(f'{self.results_dir}/case1_1d_schwarz.png', dpi=150)
print(f"图片已保存: case1_1d_schwarz.png")
plt.close()
def case2_2d_schwarz(self):
"""案例2:二维泊松方程的加性与乘性Schwarz"""
print("\n" + "=" * 60)
print("案例2:二维泊松方程的加性与乘性Schwarz")
print("=" * 60)
# 问题定义
nx, ny = 65, 65
x = np.linspace(0, 1, nx)
y = np.linspace(0, 1, ny)
X, Y = np.meshgrid(x, y)
# 精确解和右端项
u_exact = np.sin(np.pi * X) * np.sin(np.pi * Y)
f = 2 * np.pi**2 * np.sin(np.pi * X) * np.sin(np.pi * Y)
print(f"网格尺寸: {nx} x {ny}")
print(f"子域划分: 2 x 2")
print(f"重叠层数: 4")
# 直接求解
hx, hy = 1.0 / (nx - 1), 1.0 / (ny - 1)
n = nx * ny
main_diag = 2.0 * (1/hx**2 + 1/hy**2) * np.ones(n)
off_x = -1/hx**2 * np.ones(n - 1)
off_y = -1/hy**2 * np.ones(n - nx)
for i in range(ny - 1):
off_x[(i + 1) * nx - 1] = 0
A = diags([off_y, off_x, main_diag, off_x, off_y],
[-nx, -1, 0, 1, nx], format='csr')
start = time.time()
u_direct = spsolve(A, f.flatten())
direct_time = time.time() - start
u_direct = u_direct.reshape((ny, nx))
print(f"\n直接求解器:")
print(f" 计算时间: {direct_time:.4f}s")
# 加性Schwarz
print(f"\n加性Schwarz方法:")
solver_add = SchwarzSolver(nx, ny, 2, 2, overlap=4)
u_add, iter_add, res_add = solver_add.solve_additive_schwarz(f.flatten(), max_iter=50)
u_add = u_add.reshape((ny, nx))
# 乘性Schwarz
print(f"\n乘性Schwarz方法:")
solver_mult = SchwarzSolver(nx, ny, 2, 2, overlap=4)
u_mult, iter_mult, res_mult = solver_mult.solve_multiplicative_schwarz(f.flatten(), max_iter=50)
u_mult = u_mult.reshape((ny, nx))
# 计算误差
error_add = np.linalg.norm(u_add - u_exact) / np.linalg.norm(u_exact)
error_mult = np.linalg.norm(u_mult - u_exact) / np.linalg.norm(u_exact)
error_direct = np.linalg.norm(u_direct - u_exact) / np.linalg.norm(u_exact)
print(f"\n误差对比:")
print(f" 直接法: {error_direct:.2e}")
print(f" 加性Schwarz: {error_add:.2e} ({iter_add}次迭代)")
print(f" 乘性Schwarz: {error_mult:.2e} ({iter_mult}次迭代)")
# 可视化
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 解的分布
im0 = axes[0, 0].contourf(X, Y, u_exact, levels=20, cmap='viridis')
axes[0, 0].set_title('精确解')
axes[0, 0].set_xlabel('x')
axes[0, 0].set_ylabel('y')
plt.colorbar(im0, ax=axes[0, 0])
im1 = axes[0, 1].contourf(X, Y, u_direct, levels=20, cmap='viridis')
axes[0, 1].set_title('直接法')
axes[0, 1].set_xlabel('x')
axes[0, 1].set_ylabel('y')
plt.colorbar(im1, ax=axes[0, 1])
im2 = axes[0, 2].contourf(X, Y, u_add, levels=20, cmap='viridis')
axes[0, 2].set_title(f'加性Schwarz ({iter_add} iter)')
axes[0, 2].set_xlabel('x')
axes[0, 2].set_ylabel('y')
plt.colorbar(im2, ax=axes[0, 2])
# 误差分布
im3 = axes[1, 0].contourf(X, Y, np.abs(u_direct - u_exact), levels=20, cmap='hot')
axes[1, 0].set_title('直接法误差')
axes[1, 0].set_xlabel('x')
axes[1, 0].set_ylabel('y')
plt.colorbar(im3, ax=axes[1, 0])
im4 = axes[1, 1].contourf(X, Y, np.abs(u_add - u_exact), levels=20, cmap='hot')
axes[1, 1].set_title('加性Schwarz误差')
axes[1, 1].set_xlabel('x')
axes[1, 1].set_ylabel('y')
plt.colorbar(im4, ax=axes[1, 1])
# 收敛历史对比
axes[1, 2].semilogy(range(1, len(res_add)+1), res_add, 'b-', linewidth=2, label='加性Schwarz', marker='o')
axes[1, 2].semilogy(range(1, len(res_mult)+1), res_mult, 'r-', linewidth=2, label='乘性Schwarz', marker='s')
axes[1, 2].set_xlabel('迭代次数')
axes[1, 2].set_ylabel('残差')
axes[1, 2].set_title('收敛历史对比')
axes[1, 2].legend()
axes[1, 2].grid(True)
plt.tight_layout()
plt.savefig(f'{self.results_dir}/case2_2d_schwarz.png', dpi=150)
print(f"\n图片已保存: case2_2d_schwarz.png")
plt.close()
def case3_schur_complement(self):
"""案例3:迭代子结构方法(Schur补)"""
print("\n" + "=" * 60)
print("案例3:迭代子结构方法(Schur补)")
print("=" * 60)
# 问题定义
nx, ny = 65, 65
x = np.linspace(0, 1, nx)
y = np.linspace(0, 1, ny)
X, Y = np.meshgrid(x, y)
u_exact = np.sin(np.pi * X) * np.sin(np.pi * Y)
f = 2 * np.pi**2 * np.sin(np.pi * X) * np.sin(np.pi * Y)
print(f"网格尺寸: {nx} x {ny}")
print(f"子域划分: 2 x 2")
# 创建Schur补求解器
solver = SchurComplementSolver(nx, ny, 2, 2)
# 识别界面自由度
interface = solver.identify_interface_dofs()
print(f"界面自由度数量: {len(interface)}")
# 求解
u, info = solver.solve_schur_complement(f.flatten(), max_iter=200, tol=1e-8)
u = u.reshape((ny, nx))
# 直接求解对比
hx, hy = 1.0 / (nx - 1), 1.0 / (ny - 1)
n = nx * ny
main_diag = 2.0 * (1/hx**2 + 1/hy**2) * np.ones(n)
off_x = -1/hx**2 * np.ones(n - 1)
off_y = -1/hy**2 * np.ones(n - nx)
for i in range(ny - 1):
off_x[(i + 1) * nx - 1] = 0
A = diags([off_y, off_x, main_diag, off_x, off_y],
[-nx, -1, 0, 1, nx], format='csr')
u_direct = spsolve(A, f.flatten()).reshape((ny, nx))
# 计算误差
error_schur = np.linalg.norm(u - u_exact) / np.linalg.norm(u_exact)
error_direct = np.linalg.norm(u_direct - u_exact) / np.linalg.norm(u_exact)
print(f"\n误差对比:")
print(f" 直接法: {error_direct:.2e}")
print(f" Schur补方法: {error_schur:.2e}")
# 可视化
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
im0 = axes[0, 0].contourf(X, Y, u_exact, levels=20, cmap='viridis')
axes[0, 0].set_title('精确解')
axes[0, 0].set_xlabel('x')
axes[0, 0].set_ylabel('y')
plt.colorbar(im0, ax=axes[0, 0])
im1 = axes[0, 1].contourf(X, Y, u_direct, levels=20, cmap='viridis')
axes[0, 1].set_title('直接法')
axes[0, 1].set_xlabel('x')
axes[0, 1].set_ylabel('y')
plt.colorbar(im1, ax=axes[0, 1])
im2 = axes[1, 0].contourf(X, Y, u, levels=20, cmap='viridis')
axes[1, 0].set_title('Schur补方法')
axes[1, 0].set_xlabel('x')
axes[1, 0].set_ylabel('y')
plt.colorbar(im2, ax=axes[1, 0])
# 子域分解可视化
axes[1, 1].contourf(X, Y, u_exact, levels=20, cmap='viridis', alpha=0.5)
# 绘制子域边界
dx, dy = nx // 2, ny // 2
axes[1, 1].axvline(x=x[dx], color='r', linewidth=2, linestyle='--')
axes[1, 1].axhline(y=y[dy], color='r', linewidth=2, linestyle='--')
# 标记界面点
for ix, iy in interface[::5]: # 每隔5个显示
axes[1, 1].plot(x[ix], y[iy], 'ko', markersize=2)
axes[1, 1].set_title('子域分解与界面')
axes[1, 1].set_xlabel('x')
axes[1, 1].set_ylabel('y')
plt.tight_layout()
plt.savefig(f'{self.results_dir}/case3_schur_complement.png', dpi=150)
print(f"图片已保存: case3_schur_complement.png")
plt.close()
def case4_em_ddm(self):
"""案例4:电磁场时谐问题的DDM"""
print("\n" + "=" * 60)
print("案例4:电磁场时谐问题的DDM")
print("=" * 60)
# 问题定义
nx, ny = 65, 65
freq = 1e9 # 1 GHz
print(f"网格尺寸: {nx} x {ny}")
print(f"频率: {freq/1e9:.1f} GHz")
x = np.linspace(0, 1, nx)
y = np.linspace(0, 1, ny)
X, Y = np.meshgrid(x, y)
# 创建电磁DDM求解器
solver = ElectromagneticDDM(freq, nx, ny, 4)
# 构建源项(点源)
f = np.zeros(nx * ny)
cx, cy = nx // 2, ny // 2
f[cy * nx + cx] = 1.0
# 求解
u = solver.solve_ddm_helmholtz(f, max_iter=100)
u = u.reshape((ny, nx))
# 可视化
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
im0 = axes[0].contourf(X, Y, u.real, levels=20, cmap='RdBu_r')
axes[0].set_title('电场实部')
axes[0].set_xlabel('x')
axes[0].set_ylabel('y')
plt.colorbar(im0, ax=axes[0])
im1 = axes[1].contourf(X, Y, np.abs(u), levels=20, cmap='viridis')
axes[1].set_title('电场幅度')
axes[1].set_xlabel('x')
axes[1].set_ylabel('y')
plt.colorbar(im1, ax=axes[1])
# 中心线分布
axes[2].plot(x, np.abs(u[cy, :]), 'b-', linewidth=2, label='水平中心线')
axes[2].plot(y, np.abs(u[:, cx]), 'r--', linewidth=2, label='垂直中心线')
axes[2].set_xlabel('位置')
axes[2].set_ylabel('|E|')
axes[2].set_title('中心线场分布')
axes[2].legend()
axes[2].grid(True)
plt.tight_layout()
plt.savefig(f'{self.results_dir}/case4_em_ddm.png', dpi=150)
print(f"图片已保存: case4_em_ddm.png")
plt.close()
def case5_multiphysics_ddm(self):
"""案例5:多物理场耦合DDM"""
print("\n" + "=" * 60)
print("案例5:多物理场耦合DDM(电磁-热)")
print("=" * 60)
# 问题定义
nx, ny = 33, 33
freq = 2.45e9 # 2.45 GHz(微波炉频率)
sigma = 1.0 # 电导率
print(f"网格尺寸: {nx} x {ny}")
print(f"频率: {freq/1e9:.2f} GHz")
print(f"电导率: {sigma} S/m")
x = np.linspace(0, 1, nx)
y = np.linspace(0, 1, ny)
X, Y = np.meshgrid(x, y)
# 创建多物理场DDM求解器
solver = MultiPhysicsDDM(nx, ny)
# 求解电磁-热耦合问题
E, T = solver.solve_em_thermal_coupling(freq, sigma, max_iter=5)
# 可视化
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
im0 = axes[0].contourf(X, Y, np.abs(E), levels=20, cmap='viridis')
axes[0].set_title('电场幅度')
axes[0].set_xlabel('x')
axes[0].set_ylabel('y')
plt.colorbar(im0, ax=axes[0])
im1 = axes[1].contourf(X, Y, T, levels=20, cmap='hot')
axes[1].set_title('温度分布')
axes[1].set_xlabel('x')
axes[1].set_ylabel('y')
plt.colorbar(im1, ax=axes[1])
# 热源分布
heat_source = 0.5 * sigma * np.abs(E)**2
im2 = axes[2].contourf(X, Y, heat_source, levels=20, cmap='jet')
axes[2].set_title('焦耳热分布')
axes[2].set_xlabel('x')
axes[2].set_ylabel('y')
plt.colorbar(im2, ax=axes[2])
plt.tight_layout()
plt.savefig(f'{self.results_dir}/case5_multiphysics_ddm.png', dpi=150)
print(f"图片已保存: case5_multiphysics_ddm.png")
plt.close()
def case6_performance_analysis(self):
"""案例6:性能对比与并行效率分析"""
print("\n" + "=" * 60)
print("案例6:性能对比与并行效率分析")
print("=" * 60)
print("对比不同求解方法的性能:")
print("- 直接法 (稀疏LU)")
print("- 共轭梯度法 (CG)")
print("- 加性Schwarz")
print("- 乘性Schwarz")
print()
sizes = [33, 65, 129]
results = {
'size': [],
'direct': [],
'cg': [],
'additive': [],
'multiplicative': []
}
for nx in sizes:
ny = nx
n = nx * ny
print(f"\n网格: {nx}×{ny}, 未知数: {n}")
x = np.linspace(0, 1, nx)
y = np.linspace(0, 1, ny)
X, Y = np.meshgrid(x, y)
f = 2 * np.pi**2 * np.sin(np.pi * X) * np.sin(np.pi * Y)
hx, hy = 1.0 / (nx - 1), 1.0 / (ny - 1)
main_diag = 2.0 * (1/hx**2 + 1/hy**2) * np.ones(n)
off_x = -1/hx**2 * np.ones(n - 1)
off_y = -1/hy**2 * np.ones(n - nx)
for i in range(ny - 1):
off_x[(i + 1) * nx - 1] = 0
A = diags([off_y, off_x, main_diag, off_x, off_y],
[-nx, -1, 0, 1, nx], format='csr')
# 直接法
if nx <= 129:
start = time.time()
u_direct = spsolve(A, f.flatten())
t_direct = time.time() - start
results['direct'].append(t_direct)
print(f" 直接法: {t_direct:.4f}s")
else:
results['direct'].append(np.nan)
print(f" 直接法: 跳过(内存限制)")
# CG
start = time.time()
u_cg, info = cg(A, f.flatten(), rtol=1e-6, maxiter=1000)
t_cg = time.time() - start
results['cg'].append(t_cg)
print(f" CG: {t_cg:.4f}s ({info} iter)")
# 加性Schwarz
start = time.time()
solver_add = SchwarzSolver(nx, ny, 2, 2, overlap=4)
u_add, iter_add, _ = solver_add.solve_additive_schwarz(f.flatten(), max_iter=30, tol=1e-6)
t_add = time.time() - start
results['additive'].append(t_add)
print(f" 加性Schwarz: {t_add:.4f}s ({iter_add} iter)")
# 乘性Schwarz
start = time.time()
solver_mult = SchwarzSolver(nx, ny, 2, 2, overlap=4)
u_mult, iter_mult, _ = solver_mult.solve_multiplicative_schwarz(f.flatten(), max_iter=30, tol=1e-6)
t_mult = time.time() - start
results['multiplicative'].append(t_mult)
print(f" 乘性Schwarz: {t_mult:.4f}s ({iter_mult} iter)")
results['size'].append(n)
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 计算时间对比
x_pos = np.arange(len(sizes))
width = 0.2
axes[0].bar(x_pos - 1.5*width, results['direct'], width, label='直接法', alpha=0.8)
axes[0].bar(x_pos - 0.5*width, results['cg'], width, label='CG', alpha=0.8)
axes[0].bar(x_pos + 0.5*width, results['additive'], width, label='加性Schwarz', alpha=0.8)
axes[0].bar(x_pos + 1.5*width, results['multiplicative'], width, label='乘性Schwarz', alpha=0.8)
axes[0].set_xlabel('问题规模')
axes[0].set_ylabel('计算时间 (s)')
axes[0].set_title('计算时间对比')
axes[0].set_xticks(x_pos)
axes[0].set_xticklabels([f'{s}×{s}' for s in sizes])
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[0].set_yscale('log')
# 加速比(相对于直接法)
speedup_cg = [d/c if c > 0 else 1 for d, c in zip(results['direct'], results['cg'])]
speedup_add = [d/a if a > 0 else 1 for d, a in zip(results['direct'], results['additive'])]
speedup_mult = [d/m if m > 0 else 1 for d, m in zip(results['direct'], results['multiplicative'])]
axes[1].plot(sizes, speedup_cg, 'o-', linewidth=2, markersize=8, label='CG')
axes[1].plot(sizes, speedup_add, 's-', linewidth=2, markersize=8, label='加性Schwarz')
axes[1].plot(sizes, speedup_mult, '^-', linewidth=2, markersize=8, label='乘性Schwarz')
axes[1].axhline(y=1, color='k', linestyle='--', alpha=0.5, label='直接法基线')
axes[1].set_xlabel('网格尺寸')
axes[1].set_ylabel('加速比')
axes[1].set_title('相对于直接法的加速比')
axes[1].legend()
axes[1].grid(True)
plt.tight_layout()
plt.savefig(f'{self.results_dir}/case6_performance.png', dpi=150)
print(f"\n图片已保存: case6_performance.png")
plt.close()
# 生成Schwarz迭代动画
self._create_schwarz_animation()
def _create_schwarz_animation(self):
"""创建Schwarz迭代过程动画"""
print("\n生成Schwarz迭代动画...")
nx, ny = 33, 33
x = np.linspace(0, 1, nx)
y = np.linspace(0, 1, ny)
X, Y = np.meshgrid(x, y)
f = 2 * np.pi**2 * np.sin(np.pi * X) * np.sin(np.pi * Y)
# 创建求解器
solver = SchwarzSolver(nx, ny, 2, 2, overlap=4)
# 存储迭代过程
solutions = []
residuals = []
u = np.zeros(nx * ny)
hx, hy = 1.0 / (nx - 1), 1.0 / (ny - 1)
for i in range(15):
u_old = u.copy()
# 加性Schwarz一步
corrections = []
for sd in solver.subdomains:
f_sub = solver.extract_subdomain_vector(f.flatten(), sd)
A_sub = solver.build_subdomain_matrix(sd['nx'], sd['ny'], hx, hy)
u_sub = spsolve(A_sub, f_sub)
corrections.append((sd, u_sub))
u = solver.combine_solution_additive(corrections, u)
residual = np.linalg.norm(u - u_old) / (np.linalg.norm(u) + 1e-10)
solutions.append(u.reshape((ny, nx)).copy())
residuals.append(residual)
# 创建动画
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
def animate(frame):
axes[0].clear()
axes[1].clear()
# 解的分布
im = axes[0].contourf(X, Y, solutions[frame], levels=20, cmap='viridis')
axes[0].set_title(f'解分布 (迭代 {frame+1})')
axes[0].set_xlabel('x')
axes[0].set_ylabel('y')
# 收敛历史
axes[1].semilogy(range(1, len(residuals[:frame+1])+1), residuals[:frame+1],
'bo-', linewidth=2, markersize=8)
axes[1].set_xlabel('迭代次数')
axes[1].set_ylabel('残差')
axes[1].set_title('收敛历史')
axes[1].grid(True)
axes[1].set_xlim(1, len(residuals))
axes[1].set_ylim(min(residuals)*0.5, max(residuals)*2)
plt.tight_layout()
return axes
anim = animation.FuncAnimation(fig, animate, frames=len(solutions),
interval=500, blit=False, repeat=True)
try:
anim.save(f'{self.results_dir}/case6_schwarz_animation.gif',
writer='pillow', fps=2, dpi=100)
print("GIF动画已保存: case6_schwarz_animation.gif")
except Exception as e:
print(f"GIF保存失败: {e}")
# 保存最后一帧
animate(len(solutions)-1)
plt.savefig(f'{self.results_dir}/case6_final_frame.png', dpi=150)
plt.close()
if __name__ == '__main__':
runner = SimulationRunner()
runner.run_all_cases()
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)