Sync 3D OpticsFEM PBC/port advances and MATLAB reference codes.

Ignore large build outputs and binary datasets so the repo stays source-focused.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
hpnickk-ai 2026-07-21 20:14:54 +08:00
parent db3ea489fe
commit 4bf7086a78
497 changed files with 6626700 additions and 357 deletions

76
.gitignore vendored
View File

@ -1,12 +1,72 @@
build/
# Build / VS
build/
build1/
scat3D/
eigen3D/
3D opticsfem-master/scat3D/
3D opticsfem-master/eigen3D/
3D opticsfem-master/build/
3D opticsfem-master/build1/
**/build/
**/build1/
.vs/
*.exe
*.obj
*.pdb
.vs/
*.dll
*.lib
*.ilk
*.exp
CMakeCache.txt
CMakeFiles/
*.dir/
x64/
Release/
Debug/
# Case / output dirs under 3D opticsfem-master
3D opticsfem-master/scat3D/
3D opticsfem-master/eigen3D/
3D opticsfem-master/scat3D-test/
3D opticsfem-master/scat3D_SBC/
3D opticsfem-master/scat3D_order1/
3D opticsfem-master/scat3D_order2/
3D opticsfem-master/PBC_double/
3D opticsfem-master/PBC_double_order2/
3D opticsfem-master/PBC_single/
3D opticsfem-master/MAG/
3D opticsfem-master/MAG_order2/
3D opticsfem-master/SCD/
3D opticsfem-master/SCD_order2/
3D opticsfem-master/bele/
3D opticsfem-master/bele_order2/
3D opticsfem-master/port/
3D opticsfem-master/port_fem4/
3D opticsfem-master/doublePBC/
3D opticsfem-master/real_solver/
3D opticsfem-master/third_party/
# Runtime outputs
OutFile/
OutFile_asm/
**/OutFile*/
**/OutFile_*/
B_imag.dat
B_real.dat
Roffsets.dat
# Python cache
__pycache__/
*.pyc
*.pyo
# Large archives / models / binaries (not source)
*.zip
*.mph
*.caj
*.pptx
# Root example / dataset trees (too large for git)
FEM001_RecHorn/
FEM009_Patch_MetalSurface/
FEM013_MountPatch/
FEM027_HalfAmygdala_FEIE/
FEM049_Anisotropic/
FEM3D001-scat/
laspcem-case/
测试数据集/
求解器/

View File

@ -0,0 +1,117 @@
# OpticsFEM 3D 光学仿真扩展实施计划
> 目标:参照 2D `opticsfem-master` 完整能力,在 `3D opticsfem-master` 中实现全部三维光学问题仿真。
> 重点:**边界条件** + **本征频率/本征模式**
> 验证基准:`三维matlab代码/` 各算例的 JSON + `.dat` + 矩阵/场输出。
---
## 现状摘要2026-06
| FemType | 问题 | 2D | 3D C++ |
|---------|------|----|--------|
| 0 | 本征模式 (neff) | ✅ | ❌ 无类 |
| 1 | 本征频率 | ✅ | ⚠️ FemType=5 仅组装+矩阵导出 |
| 2/3 | 散射 | ✅ 全边界 | ⚠️ FemType=4 仅 SBC |
| — | 二阶 Nedelec | — | ❌ MATLAB 有参考 |
3D 散射 DOF**纯边元**`NbrEdge`2D 散射 DOF`NbrVertex + NbrEdge`(混合)。
---
## 分阶段计划
### 阶段 0 — 工程基础(并行)
| 序号 | 任务 | 参照 2D | 验证 |
|------|------|---------|------|
| 0.1 | `Test_Main` 支持命令行/环境变量指定 JSON | — | 切换算例无需重编译 |
| 0.2 | 完善 `export_phy_to_json.m`PBC/PEC 块) | `Test_ReadData.cpp` | JSON 与 C++ 解析一致 |
| 0.3 | 统一 `EnsureOutputDir`、错误码返回 | — | 运行日志可读 |
### 阶段 1 — 本征频率闭环 ⭐ 当前执行
| 序号 | 任务 | 参照 2D 文件 | 验证 |
|------|------|--------------|------|
| 1.1 | `OpticsFEM_3D_EigenFreq::Assemble` 补 P 矩阵投影 `P^T A P` | `Assemble_kernel.cpp` 2D EigenFreq | 与 MATLAB `Av/Bv` 一致(已有) |
| 1.2 | 新增 `Run()` / `Post()` | 2D EigenFreq | `eigen3d.json` + `SBCmesh.dat` |
| 1.3 | `Post_3D_EigenFreq::GetElectric/OutputData` | `Post_3D_Scatter` + 2D EigenFreq | 输出 `freq/Ex/Ey/Ez/normE` |
| 1.4 | `FEM_Interface` FemType=5 完整链路 | FemType=1 | 返回码 5 |
### 阶段 2 — 散射基础边界 ✅ 已完成
| 序号 | 任务 | 参照 2D 文件 | 验证 |
|------|------|--------------|------|
| 2.1 | `Assemble_PEC_ELE()` 3D 实现(边 DOF 消元) | `Assemble_Scatter_2D_Boundary.cpp` | 含 PEC 的散射算例 |
| 2.2 | 接入 `OpticsFEM_3D_Scatter::Assemble()` | 2D Scatter Assemble | — |
| 2.3 | ELE 激励Dirichlet 边条件) | 2D `Assemble_PEC_ELE` | `ef` JSON 块 |
### 阶段 3 — 周期边界 PBC
| 序号 | 任务 | 参照 | 验证 |
|------|------|------|------|
| 3.1 | `Mesh_3D::GetIndexOfPBC()` 实现 | 2D `Mesh_Find.cpp` | `PBCmesh.dat` |
| 3.2 | 3D 散射 `Assemble_PBC()` | 2D + MATLAB `main_singlePBC.m` | 单周期散射 ✅ |
| 3.3 | 3D 双周期 PBC散射+本征) | MATLAB `assembly_pbc_double.m` | `doublePBC_scatter.json` ✅ |
| 3.4 | JSON 导出 PBC 块 | `export_phy_to_json.m` | `PBCmesh.json` |
### 阶段 4 — 源项与背景场边界
| 序号 | 任务 | 参照 2D | 验证 |
|------|------|---------|------|
| 4.1 | `Assemble_BELE()` 3D 体积分+ PML 面积分 | `Assemble_Scatter_2D_Boundary.cpp` | bele 类算例 |
| 4.2 | `Assemble_MAG()` | 同上 | mag.json |
| 4.3 | `Assemble_SCD()` | 同上 | scd.json |
| 4.4 | `Assemble_MPD()` / `Assemble_EPD()` | 同上 | mpd/epd.json |
| 4.5 | `Post_GetEb()` 3D 背景场BELE 后处理) | `Post_GetEb.cpp` | — |
### 阶段 5 — 吸收层与端口
| 序号 | 任务 | 说明 | 验证 |
|------|------|------|------|
| 5.1 | 3D PML 体积分/边界 | 2D PML 拉伸坐标推广到四面体 | pml 算例 |
| 5.2 | 3D Port 散射 | 2D Port 480 行逻辑3D 面模态 | port 算例 |
| 5.3 | Beam 高斯光束 | 2D beam JSON | — |
### 阶段 6 — 本征模式 & 高阶(扩展)
| 序号 | 任务 | 说明 |
|------|------|------|
| 6.1 | `OpticsFEM_3D_EigenMode` 新类 | 3D 波导 neff参照 2D EigenMode |
| 6.2 | 二阶 Nedelec 基函数 | 参照 MATLAB `3D二阶基+散射边界条件` |
| 6.3 | 棱柱/四边形边界网格读取 | `Mesh_Interface.cpp` 补全 |
---
## FemType 路由规划(目标态)
| FemType | 问题 | 网格 |
|---------|------|------|
| 0 | 2D 本征模式 | Mesh_2D |
| 1 | 2D 本征频率 | Mesh_2D |
| 2/3 | 2D 散射 | Mesh_2D |
| **4** | **3D 散射** | Mesh_3D |
| **5** | **3D 本征频率** | Mesh_3D |
| **6** | **3D 本征模式**(规划) | Mesh_3D |
---
## 执行顺序与依赖
```
阶段1 本征频率闭环
阶段2 PEC/ELE ──→ 阶段3 PBC ──→ 阶段4 BELE/源项
阶段5 PML/Port
阶段6 本征模式/二阶
```
---
## 文档修订
| 版本 | 日期 | 内容 |
|------|------|------|
| v1.0 | 2026-06-18 | 初版计划启动阶段1 |

View File

@ -145,6 +145,10 @@ set(PHY
"phy/Phy_Base.h"
"phy/Phy_Base.cpp"
"phy/Phy_Interface.cpp"
"phy/PortModeData.h"
"phy/PortModeData.cpp"
"phy/PortModeSolver.h"
"phy/PortModeSolver.cpp"
)
set(SOLVER
@ -176,6 +180,12 @@ set(KERNEL
"kernel/Assemble_kernel.cpp"
"kernel/Assemble_Scatter_2D_Boundary.cpp"
"kernel/Assemble_Scatter_3D_Boundary.cpp"
"kernel/Assemble_Scatter_3D_Source.cpp"
"kernel/Assemble_Scatter_3D_Port.cpp"
"kernel/PBC_Util.h"
"kernel/PBC_Util.cpp"
"kernel/Nedelec3D_Util.h"
"kernel/Nedelec3D_Util.cpp"
"kernel/Assemble_Scatter_Equation.cpp"
@ -209,3 +219,16 @@ set_target_properties(complexsolver PROPERTIES
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/Debug/complex"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release/complex"
)
# Real symmetric generalized eigenvalue solver for FemType=5 (A*x = lambda*B*x)
add_executable(real_eigs real_solver/realeigs.cpp)
target_include_directories(real_eigs PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/third_party/spectra/include
)
set_target_properties(real_eigs PROPERTIES
OUTPUT_NAME "real"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Release/real_solver"
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/Debug/real_solver"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release/real_solver"
)

View File

@ -1,4 +1,5 @@
#include"../phy/Phy_Base.h"
#include"../phy/PortModeSolver.h"
#include"../material/Material_Base.h"
#include"../mesh/Mesh_Base.h"
#include"../kernel/Assemble_Base.h"
@ -6,6 +7,8 @@
#include "../nlohmann/json.hpp"
#include "FEM_Interface.h"
#include "../Eigen/Dense"
#include <string>
#include <fstream>
#include <iostream>
@ -127,15 +130,47 @@ int OpticsFEM_API::OpticsFEM_All(OpticsFEMData data)
solver.GetSolver(str);
mesh3d.GetMesh(meshFile, str);
// Optional: compute numeric port modes in-process (MATLAB BoundaryEigenMode)
if (js.contains("port") && js.at("port").value("computeModes", false))
{
const json& port = js.at("port");
Eigen::VectorXi din(phy.GetNbrPortinc());
Eigen::VectorXi dout(phy.GetNbrPortout());
for (int i = 0; i < phy.GetNbrPortinc(); ++i)
din(i) = phy.GetPortincDomain(i);
for (int i = 0; i < phy.GetNbrPortout(); ++i)
dout(i) = phy.GetPortoutDomain(i);
const double lam0 = js.at("lambda").get<double>();
const double targetNeff = port.value("targetNeff", 2.6);
const int modeNum = port.value("modeNum", 1);
const bool normIn = port.value("normalizeInput", true);
std::cout << "[OpticsFEM] computing port modes (targetNeff=" << targetNeff
<< ", modeNum=" << modeNum << ")" << std::endl;
if (!ComputePortModesFromMesh(&mesh3d, &matLab, din, dout, lam0, targetNeff,
modeNum, phy.MutablePortModes(), normIn))
{
std::cerr << "[OpticsFEM] ComputePortModesFromMesh failed" << std::endl;
return -4;
}
if (port.contains("modeFileOut"))
{
const std::string outMode = port.at("modeFileOut").get<std::string>();
if (SavePortModesToFile(phy.MutablePortModes(), outMode))
std::cout << "[OpticsFEM] wrote computed modes -> " << outMode << std::endl;
}
}
fem.GetMaterial(&matLab);
fem.GetMesh(&mesh3d);
fem.GetPhy(&phy);
fem.GetSolver(&solver);
fem.GetPost(&post);
fem.Assemble();
fem.Run();
string outFile = js.at("OutFile");
fem.SetExportAsmDirectory(outFile + "_asm");
fem.Assemble();
fem.Test_OutputMatrix(outFile);
fem.Run();
fem.Post(outFile);
return 4;
}
@ -158,6 +193,8 @@ int OpticsFEM_API::OpticsFEM_All(OpticsFEMData data)
fem.Assemble();
string outFile = js.at("OutFile");
fem.Test_OutputMatrix(outFile);
fem.Run();
fem.Post(outFile);
return 5;
}
else

View File

@ -487,7 +487,7 @@ cd "3D opticsfem-master"
# FemType=4 散射
cmake -B scat3D -G "Visual Studio 18 2026" -A x64
cmake --build scat3D --config Release
# FemType=5 本征频率
cmake -B eigen3D -G "Visual Studio 18 2026" -A x64

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,221 @@
# OpticsFEM 3D PPT 更新建议(阶段三 · PBC 周期边界)
> 对应原稿:`yh -3D OpticsFEM.pptx`9 页2026-06-09
> 当前进度:阶段 2PEC/ELE+ 阶段 3PBC 单/双周期)已完成
> 配套图片目录:`ppt_assets/`
---
## 一、现有 PPT 结构 vs 当前进度
| 页码 | 现有内容 | 与现状的差距 |
|------|----------|--------------|
| 1 | 封面2026.6.9 | 日期、副标题需更新 |
| 23 | 背景、软件框架 | 基本可用,可小幅补充 PBC 模块 |
| 4 | 三维拓展总览表(图片) | **需标绿阶段 2/3 已完成项** |
| 5 | SBC 散射 + 本征 FemType 4/5 | **缺 PBC 算例说明** |
| 6 | 散射 \|E\| 结果SBC 立方体) | 仅有 SBC**缺 PBC 结果图** |
| 7 | 后续工作一PBC 仍列在待做) | **应改为「已完成 + 新待做」** |
| 8 | 后续工作二(.em 接口) | 可保留,基本不变 |
| 9 | 工作总结 | **未体现 PEC/ELE、PBC** |
`3D光学仿真扩展实施计划.md`,当前进度:
```
阶段0 工程基础 ──→ 阶段1 本征频率 ──→ 阶段2 PEC/ELE ✅ ──→ 阶段3 PBC ✅
阶段4 BELE/源项(下一步)
```
---
## 二、建议「就地修改」的页面
### 1. 第 1 页 · 封面
- 时间改为 **2026 年 6 月 23 日**(或汇报当天)
- 副标题可改为:
**「一阶 Nedelec · SBC/PEC/PBC 边界 · 散射与本征矩阵组装」**
### 2. 第 4 页 · 三维拓展总览表
建议在表格或模块图中标注:
| 模块 | 状态 | 说明 |
|------|------|------|
| 体积分 + SBC | ✅ 已有 | 原内容 |
| **PEC/ELE阶段 2** | ✅ 新增 | `Assemble_PEC_ELE()`,边 DOF 消元 |
| **PBC 单/双周期(阶段 3** | ✅ 新增 | `GetIndexOfPBC` + `Assemble_PBC` + `PBC_Util` |
| BELE/MAG/SCD 等 | ⏳ 阶段 4 | 仍待做 |
| .em 接口 | ⏳ 接口层 | 第 8 页内容 |
### 3. 第 5 页 · 三维算例验证
在现有 FemType 4/5 下方,**增加 PBC 算例块**
**单周期 PBC 散射**
- 配置:`singlePBC_scatter.json` + `singlePBC_mesh.dat`
- 方程A·x = b约束矩阵 **P^T A P**Bloch 相位 φ)
- 求解n ≈ 22751\|r\| ≈ **3.9×10⁻¹³**
**双周期 PBC 散射**
- 配置:`doublePBC_scatter.json`pbc + pbc2 两组)
- 难点:角点 DOF 重叠合并(`mergeDoublePbcPairs`
- 约束对数PBC1=819PBC2=813合并后 **1570 对**
**本征 FemType=5 + PBC**
- `Assemble_EigenFreq_Boundary.cpp``Assemble_PEC_PBC()` 已接入
### 4. 第 7 页 · 后续工作一(重点改写)
建议拆成两段:
**【阶段 23 已完成】**
- PEC/ELE 边 DOF 约束组装
- 单周期 / 双周期 PBC 边配对与 Bloch 相位
- `Mesh_3D::GetIndexOfPBC()`、`PBC_Util.cpp`
- 与 MATLAB `main_singlePBC.m`、`assembly_pbc_double.m` 对齐
**【阶段 4 及以后 — 仍待做】**
- BELE / MAG / SCD / MPD / EPD 源项
- PML 吸收层、Port 端口
- 二阶 Nedelec、3D 本征模式FemType=6
- 各向异性材料
原来写「PBC 待接入」的表述应全部删除或改为「已完成」。
### 5. 第 9 页 · 工作总结
在「已完成」中补充:
```
⑥ 阶段23D 散射 PEC/ELE 边界组装Assemble_PEC_ELE
⑦ 阶段3单/双周期 PBC 完整实现C++ vs MATLAB normE 相对误差 ~1.1×10⁻⁶相关系数 1.0
```
「后续方向」改为:**阶段 4 源项边界 → 阶段 5 PML/Port → .em 接口**。
---
## 三、建议「新增」的页面(插在 6 页之后)
### 新页 A · 「阶段三:周期边界 PBC 原理与实现」
建议内容:
- **物理**Bloch 定理 E(r+R) = E(r)·e^(jk·R)
- **实现流程**
```
PBC 面域 → GetIndexOfPBC 边配对 → 构建约束矩阵 P → A_proj = P^H A P
```
- **关键文件**`Mesh_Find.cpp`、`PBC_Util.cpp`、`Assemble_Scatter_3D_Boundary.cpp`
- **JSON 配置示例**`pbc` / `pbc2`srcIndex、dstIndex、phiR/I、pbcWaveVec
### 新页 B · 「PBC 数值验证C++ vs MATLAB
可直接用的数据(`tools/compare_double_pbc.py` 输出):
| 指标 | 数值 |
|------|------|
| 双 PBC 矩阵 max\|diff\| / rel | ~500 / **1.1×10⁻⁶** |
| normE L2 相对误差 | **1.1×10⁻⁶** |
| normE 相关系数 | **1.0** |
| 单 PBC 求解残差 | **3.9×10⁻¹³** |
**推荐配图**`ppt_assets/pbc_faces_domain2_domain5.png`domain 2 + domain 5 周期面 \|E\| 对比)
### 新页 C可选· 「分阶段路线图与进度」
**推荐配图**`ppt_assets/roadmap_stage_progress.png`
| 阶段 | 内容 | 状态 |
|------|------|------|
| 0 | Test_Main、JSON 导出 | ✅ |
| 1 | 本征频率闭环 | ⚠️ 矩阵已对齐Run/Post 待接 |
| 2 | PEC/ELE | ✅ |
| 3 | PBC 单/双周期 | ✅ |
| 4 | BELE/源项 | ⏳ |
| 5 | PML/Port | ⏳ |
| 6 | 本征模式/二阶 | ⏳ |
---
## 四、第 6 页散射结果的微调
不必删原 SBC 结果,建议改为 **三算例并列**
1. **SBC 立方体**原有z=0.5 切面)
2. **单周期 PBC**`OutFile/normE` 或 `OutFile_single_on_double`
3. **双周期 PBC**`OutFile_double/normE`
每图下方一行标注算例名、DOF 数、残差或相对误差。
---
## 五、更新后的推荐页序(约 12 页)
```
1. 封面(更新日期/副标题)
2. 项目背景与目标
3. 软件整体框架(+ PBC 模块)
4. 三维拓展总览(标绿阶段 2/3
5. 算例验证总览SBC + 本征 + PBC ← 改
6. SBC 散射结果展示 ← 保留
7. 【新】阶段三 PBC 原理与实现
8. 【新】PBC 数值验证C++ vs MATLAB ← 用 pbc_faces_domain2_domain5.png
9. 【新/可选】分阶段路线图 ← 用 roadmap_stage_progress.png
10. 后续工作(阶段 4+,删 PBC 待做) ← 原第 7 页改
11. 后续工作二:.em 接口 ← 原第 8 页
12. 工作总结 + 谢谢 ← 原第 9 页改
```
---
## 六、配套图片清单
| 文件名 | 用途 | 来源 |
|--------|------|------|
| `ppt_assets/pbc_faces_domain2_domain5.png` | PBC 面 2+5 **合并**在同一坐标系C++/MATLAB 对比) | `plot_domain_faces.py --combined` |
| `ppt_assets/face_domain2.png` | domain 2 单独(可选) | `--domains 2` |
| `ppt_assets/face_domain5.png` | domain 5 单独(可选) | `--domains 5` |
| `ppt_assets/roadmap_stage_progress.png` | 分阶段进度路线图 | `make_ppt_assets.py` |
| `ppt_assets/pbc_validation_metrics.png` | PBC 验证指标柱状图 | `make_ppt_assets.py` |
| `ppt_assets/module_status_stage3.png` | 模块完成状态表 | `make_ppt_assets.py` |
生成命令:
```powershell
cd "3D opticsfem-master"
# 合并 domain 2+5同一坐标系类似 COMSOL 切面)
python tools/plot_domain_faces.py --mesh build/Release/PBCmesh.dat `
--outdir build/Release/OutFile `
--mat-outdir "..\三维matlab代码\matlab 3D一阶基+散射边界条件+单周期边界\OutFile" `
--domains 2 5 --combined `
--output ppt_assets/pbc_faces_domain2_domain5.png
# 或一键生成全部 PPT 素材
python ppt_assets/make_ppt_assets.py
```
> 若 C++ `OutFile/normE` 顶点数与 `PBCmesh.dat` 不一致,请先在 `build/Release` 下运行:
> `OpticsFEM.exe pbc3d_sbc.json`
---
## 七、汇报叙事建议
1. **先讲已有成果**2D→3D 框架、SBC 散射闭环、本征矩阵对齐
2. **重点讲阶段三**PBC 是光学周期结构仿真的关键,双周期角点合并是技术难点
3. **用数字收尾**normE 相关系数 1.0、相对误差 ~10⁻⁶说明实现可靠
4. **下一步一笔带过**:阶段 4 源项、.em 接口,与第 1011 页呼应
---
*文档生成日期2026-06-23*

View File

@ -167296,9 +167296,10 @@ ConnOfTri
35832 3
35829 3
NormOfFace
5
6
1 -1 0 0
2 0 -1 0
3 0 0 -1
4 0 0 1
5 0 1 0
14 1 0 0

View File

@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
"""Add 后续工作 & 总结 slides to yh -3D_OpticsFEM.pptx仅矩阵组装范围"""
from pathlib import Path
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN
SRC = Path(r"e:\组会\yh -3D_OpticsFEM.pptx")
OUT = Path(r"e:\组会\yh -3D_OpticsFEM_updated.pptx")
LAYOUT = 11 # 5_标题和内容
# 保留前 6 页(封面~散射结果展示),其后全部替换
KEEP_SLIDES = 6
def delete_slide(prs, index):
sld_id = prs.slides._sldIdLst[index]
r_id = sld_id.rId
prs.part.drop_rel(r_id)
del prs.slides._sldIdLst[index]
def trim_after_keep(prs):
while len(prs.slides) > KEEP_SLIDES:
delete_slide(prs, KEEP_SLIDES)
def set_title(slide, text):
if slide.shapes.title:
slide.shapes.title.text = text
def add_bullets(slide, left, top, width, height, items, size=16):
box = slide.shapes.add_textbox(left, top, width, height)
tf = box.text_frame
tf.word_wrap = True
for i, item in enumerate(items):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.text = item
p.font.size = Pt(size)
p.space_after = Pt(8)
if item.startswith(""):
p.font.bold = True
def add_table_slide(prs, title, headers, rows):
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT])
set_title(slide, title)
nrows, ncols = len(rows) + 1, len(headers)
tbl = slide.shapes.add_table(
nrows, ncols, Inches(0.45), Inches(1.45), Inches(12.2), Inches(0.42 * nrows)
).table
for j, h in enumerate(headers):
c = tbl.cell(0, j)
c.text = h
for p in c.text_frame.paragraphs:
p.font.bold = True
p.font.size = Pt(12)
for i, row in enumerate(rows):
for j, val in enumerate(row):
c = tbl.cell(i + 1, j)
c.text = val
for p in c.text_frame.paragraphs:
p.font.size = Pt(11)
def build():
prs = Presentation(str(SRC))
trim_after_keep(prs)
# --- 后续工作一:矩阵组装扩展(不含求解/后处理)---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT])
set_title(slide, "后续工作一:完善三维矩阵组装")
add_bullets(slide, Inches(0.4), Inches(1.35), Inches(12), Inches(5.8), [
"工作范围kernel 矩阵组装与导出;求解器、后处理不在本阶段任务内。",
"",
"【散射 FemType=4】",
" · 在现有体积分 + SBC + 入射组装基础上,扩展更多边界类型",
" · 对齐 MATLABassembly_equ / assembly_out / assembly_inc 其余算例变体",
"",
"【本征 FemType=5】",
" · 完善 A/B 组装更多边界PEC/PBC 等)与算例配置",
" · 稳定导出 Ai/Aj/Av、Bi/Bj/Bv便于与参考代码对比",
"",
"【单元与算例扩展】",
" · 周期边界 PBC、二阶 Nedelec、各向异性材料等按光学算例需求逐项接入组装",
" · 覆盖课题组典型 3D 光学算例的「可组装、可导出矩阵」",
"",
"【验证方式】",
" · 与 MATLAB / 参考矩阵逐元或 COO 格式对比,不要求 C++ 端求解场",
], size=15)
# --- 后续工作二:.em 接口(待做)---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT])
set_title(slide, "后续工作二:改造接口适配 Design.em")
add_bullets(slide, Inches(0.4), Inches(1.35), Inches(12), Inches(5.8), [
"目标LASPCEM/HOFEM 使用 Design.em + Design.em.mesh内核仍用 JSON。",
"",
"【待开发】",
" · 适配层:解析 .em → 生成内部 JSON + 网格输入",
" · Mesh_3D 读入 Design.em.mesh与现有 SBCmesh.dat 路径并列)",
" · 材料、边界、频率、激励字段映射到 phy / material",
" · 仍通过 OpticsFEM_All 入口,组装完成后导出矩阵",
"",
"【说明】",
" · 本项为后续接口工作,当前阶段尚未完成",
" · 组装验证仍以 sbc3d.json / eigen3d.json + SBCmesh.dat 为主",
], size=15)
# --- 路线图 ---
add_table_slide(
prs,
"后续工作路线图",
["方向", "内容", "说明"],
[
["矩阵组装", "FemType 4/5 及更多 3D 光学算例", "本阶段核心"],
["边界扩展", "PBC、二阶基、各向异性等", "仅组装层"],
[".em 适配", "Design.em → JSON + mesh", "接口层,待开发"],
["验证", "与 MATLAB 矩阵对比", "不依赖 C++ 求解"],
],
)
# --- 总结 ---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT])
set_title(slide, "工作总结")
add_bullets(slide, Inches(0.5), Inches(1.35), Inches(12), Inches(4.0), [
"【已完成 — 矩阵组装】",
" ① 保持 JSON 配置与模块化架构,将 2D 一阶边元扩展为 3D 四面体矢量有限元。",
" ② Mesh_3D读入 SBCmesh.datphy / material 从 JSON 加载。",
" ③ 散射FemType=4体积分 + SBC 出射 + 入射激励,完成稀疏矩阵 A 与右端 b 组装。",
" ④ 本征FemType=5刚度 A、质量 B 组装COO 导出Ai/Aj/Av、Bi/Bj/Bv",
" ⑤ 与 MATLAB 参考代码矩阵对比一致(如 Av.txt 等)。",
"",
"【工作边界】",
" · 本阶段止于矩阵组装与导出;求解器、后处理由其他模块或 MATLAB 完成。",
], size=14)
add_bullets(slide, Inches(0.5), Inches(5.2), Inches(12), Inches(2.0), [
"【后续方向】",
" · 完善 3D 组装代码,适配更多光学算例类型",
" · 开发 .em 文件适配接口(尚未完成)",
], size=15)
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT])
set_title(slide, "谢谢!")
box = slide.shapes.add_textbox(Inches(2), Inches(2.8), Inches(9), Inches(1.5))
tf = box.text_frame
tf.text = "欢迎批评指正"
p = tf.paragraphs[0]
p.font.size = Pt(32)
p.alignment = PP_ALIGN.CENTER
prs.save(str(OUT))
print(f"Saved {len(prs.slides)} slides -> {OUT}")
if __name__ == "__main__":
build()

View File

@ -20,7 +20,7 @@ const int BF_NEDELEC = 21;
const int BF_CURL_NEDELEC = 23;
const int BF_MIMORDER = 1;
const int BF_MAXORDER = 1;
const int BF_MAXORDER = 2;
const int BF_LINEFUNC = 1;
const int BF_QUADFUNC = 2;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,312 @@
{
"FemType": 4,
"EletricType": 2,
"ElementOrder": 1,
"lambda": 4e-06,
"NbrBoundary": 58,
"BoundaryFlag": [
4,
4,
2,
4,
4,
0,
4,
4,
0,
4,
4,
0,
4,
4,
0,
2,
4,
4,
4,
4,
4,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
4,
4,
4,
4,
4
],
"sbc": {
"Index": [
3,
16
],
"SBCType": [
0,
1
],
"E0x": [
"1",
"1"
],
"E0y": [
"0",
"0"
],
"E0z": [
"0",
"0"
],
"kx": [
0,
0
],
"ky": [
0,
0
],
"kz": [
0,
0
]
},
"pbc": {
"srcIndex": [
1,
4,
7,
10,
13
],
"dstIndex": [
54,
55,
56,
57,
58
],
"phiR": [
-1.0
],
"phiI": [
0.0
],
"pbcWaveVec": [
2e-06,
0,
0
]
},
"pbc2": {
"srcIndex": [
2,
5,
8,
11,
14
],
"dstIndex": [
17,
18,
19,
20,
21
],
"phiR": [
1.0
],
"phiI": [
0.0
],
"pbcWaveVec": [
0,
2e-06,
0
]
},
"NbrDomain": 9,
"domainType": [
2,
2,
2,
2,
2,
2,
2,
2,
2
],
"domainIndex": [
0,
1,
2,
3,
4,
5,
6,
7,
8
],
"matType": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"epsilonrR": [
1,
1,
5.943844,
1.9321,
1,
5.943844,
5.943844,
5.943844,
5.943844
],
"epsilonrI": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"murR": [
1,
1,
1,
1,
1,
1,
1,
1,
1
],
"murI": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"chiheR": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"chiehR": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"chiheI": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"chiehI": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"sigma": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"n": [
1,
1,
1,
1,
1,
1,
1,
1,
1
],
"k": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"MeshFile": "doublePBC_mesh.dat",
"OutFile": "./OutFile_double"
}

View File

@ -0,0 +1,312 @@
{
"FemType": 4,
"EletricType": 2,
"ElementOrder": 2,
"lambda": 4e-06,
"NbrBoundary": 58,
"BoundaryFlag": [
4,
4,
2,
4,
4,
0,
4,
4,
0,
4,
4,
0,
4,
4,
0,
2,
4,
4,
4,
4,
4,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
4,
4,
4,
4,
4
],
"sbc": {
"Index": [
3,
16
],
"SBCType": [
0,
1
],
"E0x": [
"1",
"1"
],
"E0y": [
"0",
"0"
],
"E0z": [
"0",
"0"
],
"kx": [
0,
0
],
"ky": [
0,
0
],
"kz": [
0,
0
]
},
"pbc": {
"srcIndex": [
1,
4,
7,
10,
13
],
"dstIndex": [
54,
55,
56,
57,
58
],
"phiR": [
-1.0
],
"phiI": [
0.0
],
"pbcWaveVec": [
2e-06,
0,
0
]
},
"pbc2": {
"srcIndex": [
2,
5,
8,
11,
14
],
"dstIndex": [
17,
18,
19,
20,
21
],
"phiR": [
1.0
],
"phiI": [
0.0
],
"pbcWaveVec": [
0,
2e-06,
0
]
},
"NbrDomain": 9,
"domainType": [
2,
2,
2,
2,
2,
2,
2,
2,
2
],
"domainIndex": [
0,
1,
2,
3,
4,
5,
6,
7,
8
],
"matType": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"epsilonrR": [
1,
1,
5.943844,
1.9321,
1,
5.943844,
5.943844,
5.943844,
5.943844
],
"epsilonrI": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"murR": [
1,
1,
1,
1,
1,
1,
1,
1,
1
],
"murI": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"chiheR": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"chiehR": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"chiheI": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"chiehI": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"sigma": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"n": [
1,
1,
1,
1,
1,
1,
1,
1,
1
],
"k": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"MeshFile": "doublePBC_mesh.dat",
"OutFile": "./OutFile_double"
}

View File

@ -0,0 +1,34 @@
{
"FemType": 5,
"EletricType": 2,
"ElementOrder": 2,
"NbrBoundary": 14,
"BoundaryFlag": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"NbrDomain": 2,
"domainType": [2, 2],
"domainIndex": [0, 1],
"matType": [0, 0],
"epsilonrR": [1.0, 1.5],
"epsilonrI": [0.0, 0.0],
"murR": [1.0, 1.0],
"murI": [0.0, 0.0],
"chiheR": [0.0, 0.0],
"chiehR": [0.0, 0.0],
"chiheI": [0.0, 0.0],
"chiehI": [0.0, 0.0],
"sigma": [0.0, 0.0],
"n": [1.0, 1.0],
"k": [0.0, 0.0],
"MeshFile": "SBCmesh.dat",
"OutFile": "./OutFile",
"NbrMode": 6,
"searchType": 0,
"searchValue": 374740572.5,
"solverType": 0,
"maxIterNum": 40,
"tol": 1.0e-6,
"tolType": 0,
"lambda0": 0.8,
"lambda": 0.0,
"freq": 0.0
}

View File

@ -0,0 +1,153 @@
{
"FemType": 4,
"EletricType": 2,
"lambda": 2.0,
"NbrBoundary": 14,
"BoundaryFlag": [
2,
2,
2,
3,
2,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
"ef": {
"index": [
4
],
"E0x": [
"1"
],
"E0y": [
"0"
],
"E0z": [
"0"
]
},
"sbc": {
"Index": [
1,
2,
3,
5,
14
],
"SBCType": [
0,
0,
0,
0,
0
],
"E0x": [
"1",
"1",
"1",
"1",
"1"
],
"E0y": [
"0",
"0",
"0",
"0",
"0"
],
"E0z": [
"0",
"0",
"0",
"0",
"0"
],
"kx": [
0,
0,
0,
0,
0
],
"ky": [
0,
0,
0,
0,
0
],
"kz": [
0,
0,
0,
0,
0
]
},
"NbrDomain": 2,
"domainType": [
2,
2
],
"domainIndex": [
0,
1
],
"matType": [
0,
0
],
"epsilonrR": [
1.0,
1.5
],
"epsilonrI": [
0.0,
0.0
],
"murR": [
1.0,
1.0
],
"murI": [
0.0,
0.0
],
"chiheR": [
0.0,
0.0
],
"chiehR": [
0.0,
0.0
],
"chiheI": [
0.0,
0.0
],
"chiehI": [
0.0,
0.0
],
"sigma": [
0.0,
0.0
],
"n": [
1.0,
1.0
],
"k": [
0.0,
0.0
],
"MeshFile": "SBCmesh.dat",
"OutFile": "./OutFile"
}

View File

@ -0,0 +1,154 @@
{
"FemType": 4,
"EletricType": 2,
"ElementOrder": 2,
"lambda": 2.0,
"NbrBoundary": 14,
"BoundaryFlag": [
2,
2,
2,
3,
2,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
"ef": {
"index": [
4
],
"E0x": [
"1"
],
"E0y": [
"0"
],
"E0z": [
"0"
]
},
"sbc": {
"Index": [
1,
2,
3,
5,
14
],
"SBCType": [
0,
0,
0,
0,
0
],
"E0x": [
"1",
"1",
"1",
"1",
"1"
],
"E0y": [
"0",
"0",
"0",
"0",
"0"
],
"E0z": [
"0",
"0",
"0",
"0",
"0"
],
"kx": [
0,
0,
0,
0,
0
],
"ky": [
0,
0,
0,
0,
0
],
"kz": [
0,
0,
0,
0,
0
]
},
"NbrDomain": 2,
"domainType": [
2,
2
],
"domainIndex": [
0,
1
],
"matType": [
0,
0
],
"epsilonrR": [
1.0,
1.5
],
"epsilonrI": [
0.0,
0.0
],
"murR": [
1.0,
1.0
],
"murI": [
0.0,
0.0
],
"chiheR": [
0.0,
0.0
],
"chiehR": [
0.0,
0.0
],
"chiheI": [
0.0,
0.0
],
"chiehI": [
0.0,
0.0
],
"sigma": [
0.0,
0.0
],
"n": [
1.0,
1.0
],
"k": [
0.0,
0.0
],
"MeshFile": "SBCmesh.dat",
"OutFile": "./OutFile"
}

View File

@ -0,0 +1,153 @@
{
"FemType": 4,
"EletricType": 2,
"lambda": 2.0,
"NbrBoundary": 14,
"BoundaryFlag": [
2,
2,
2,
3,
2,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
"ef": {
"index": [
4
],
"E0x": [
"0"
],
"E0y": [
"sin(2*pi*x)"
],
"E0z": [
"0"
]
},
"sbc": {
"Index": [
1,
2,
3,
5,
14
],
"SBCType": [
0,
0,
0,
0,
0
],
"E0x": [
"1",
"1",
"1",
"1",
"1"
],
"E0y": [
"0",
"0",
"0",
"0",
"0"
],
"E0z": [
"0",
"0",
"0",
"0",
"0"
],
"kx": [
0,
0,
0,
0,
0
],
"ky": [
0,
0,
0,
0,
0
],
"kz": [
0,
0,
0,
0,
0
]
},
"NbrDomain": 2,
"domainType": [
2,
2
],
"domainIndex": [
0,
1
],
"matType": [
0,
0
],
"epsilonrR": [
1.0,
1.5
],
"epsilonrI": [
0.0,
0.0
],
"murR": [
1.0,
1.0
],
"murI": [
0.0,
0.0
],
"chiheR": [
0.0,
0.0
],
"chiehR": [
0.0,
0.0
],
"chiheI": [
0.0,
0.0
],
"chiehI": [
0.0,
0.0
],
"sigma": [
0.0,
0.0
],
"n": [
1.0,
1.0
],
"k": [
0.0,
0.0
],
"MeshFile": "SBCmesh.dat",
"OutFile": "./OutFile"
}

View File

@ -1,5 +1,62 @@
#include"BF.h"
static void BF_Tetrahedron_Nedelec_Order2(int numBF, double u, double v, double w, Eigen::Vector3d& o)
{
switch (numBF)
{
case 1: o(0) = v - 3 * u * v; o(1) = -u + 3 * u * u; o(2) = 0.; break;
case 2: o(0) = v - 3 * v * v; o(1) = -u + 3 * u * v; o(2) = 0.; break;
case 3: o(0) = w - 3 * u * w; o(1) = 0.; o(2) = -u + 3 * u * u; break;
case 4: o(0) = w - 3 * w * w; o(1) = 0.; o(2) = -u + 3 * u * w; break;
case 5: o(0) = 1 - 3 * u - v + 3 * u * v - w + 3 * u * w; o(1) = u - 3 * u * u; o(2) = u - 3 * u * u; break;
case 6: o(0) = -2 + 3 * u + 5 * v - 3 * u * v - 3 * v * v + 5 * w - 3 * u * w - 6 * v * w - 3 * w * w; o(1) = -2 * u + 3 * u * u + 3 * u * v + 3 * u * w; o(2) = -2 * u + 3 * u * u + 3 * u * v + 3 * u * w; break;
case 7: o(0) = 0.; o(1) = w - 3 * v * w; o(2) = -v + 3 * v * v; break;
case 8: o(0) = 0.; o(1) = w - 3 * w * w; o(2) = -v + 3 * v * w; break;
case 9: o(0) = v - 3 * v * v; o(1) = 1 - u - 3 * v + 3 * u * v - w + 3 * v * w; o(2) = v - 3 * v * v; break;
case 10: o(0) = -2 * v + 3 * u * v + 3 * v * v + 3 * v * w; o(1) = -2 + 5 * u - 3 * u * u + 3 * v - 3 * u * v + 5 * w - 6 * u * w - 3 * v * w - 3 * w * w; o(2) = -2 * v + 3 * u * v + 3 * v * v + 3 * v * w; break;
case 11: o(0) = w - 3 * w * w; o(1) = w - 3 * w * w; o(2) = 1 - u - v - 3 * w + 3 * u * w + 3 * v * w; break;
case 12: o(0) = -2 * w + 3 * u * w + 3 * v * w + 3 * w * w; o(1) = -2 * w + 3 * u * w + 3 * v * w + 3 * w * w; o(2) = -2 + 5 * u - 3 * u * u + 5 * v - 6 * u * v - 3 * v * v + 3 * w - 3 * u * w - 3 * v * w; break;
case 13: o(0) = -3 * v * w; o(1) = 3 * u * w; o(2) = 0.; break;
case 14: o(0) = -3 * v * w; o(1) = 0.; o(2) = 3 * u * v; break;
case 15: o(0) = -3 * v + 3 * u * v + 3 * v * v + 3 * v * w; o(1) = 3 * u - 3 * u * u - 3 * u * v - 3 * u * w; o(2) = 0.; break;
case 16: o(0) = -3 * v + 3 * v * v + 3 * v * w; o(1) = -3 * u * v; o(2) = -3 * u * v; break;
case 17: o(0) = -3 * w + 3 * u * w + 3 * v * w + 3 * w * w; o(1) = 0.; o(2) = 3 * u - 3 * u * u - 3 * u * v - 3 * u * w; break;
case 18: o(0) = -3 * w + 3 * v * w + 3 * w * w; o(1) = -3 * u * w; o(2) = -3 * u * w; break;
case 19: o(0) = 0.; o(1) = -3 * w + 3 * u * w + 3 * v * w + 3 * w * w; o(2) = 3 * v - 3 * u * v - 3 * v * v - 3 * v * w; break;
case 20: o(0) = -3 * v * w; o(1) = -3 * w + 3 * u * w + 3 * w * w; o(2) = -3 * v * w; break;
default: o.setZero(); break;
}
}
static void BF_Tetrahedron_Curl_Nedelec_Order2(int numBF, double u, double v, double w, Eigen::Vector3d& o)
{
(void)u; (void)v; (void)w;
switch (numBF)
{
case 1: o(0) = 0.; o(1) = 0.; o(2) = -2 + 9 * u; break;
case 2: o(0) = 0.; o(1) = 0.; o(2) = -2 + 9 * v; break;
case 3: o(0) = 0.; o(1) = 2 - 9 * u; o(2) = 0.; break;
case 4: o(0) = 0.; o(1) = 2 - 9 * w; o(2) = 0.; break;
case 5: o(0) = 0.; o(1) = -2 + 9 * u; o(2) = 2 - 9 * u; break;
case 6: o(0) = 0.; o(1) = 7 - 9 * u - 9 * v - 9 * w; o(2) = -7 + 9 * u + 9 * v + 9 * w; break;
case 7: o(0) = -2 + 9 * v; o(1) = 0.; o(2) = 0.; break;
case 8: o(0) = -2 + 9 * w; o(1) = 0.; o(2) = 0.; break;
case 9: o(0) = 2 - 9 * v; o(1) = 0.; o(2) = -2 + 9 * v; break;
case 10: o(0) = -7 + 9 * u + 9 * v + 9 * w; o(1) = 0.; o(2) = 7 - 9 * u - 9 * v - 9 * w; break;
case 11: o(0) = -2 + 9 * w; o(1) = 2 - 9 * w; o(2) = 0.; break;
case 12: o(0) = 7 - 9 * u - 9 * v - 9 * w; o(1) = -7 + 9 * u + 9 * v + 9 * w; o(2) = 0.; break;
case 13: o(0) = -3 * u; o(1) = -3 * v; o(2) = 6 * w; break;
case 14: o(0) = 3 * u; o(1) = -6 * v; o(2) = 3 * w; break;
case 15: o(0) = 3 * u; o(1) = 3 * v; o(2) = 6 - 9 * u - 9 * v - 6 * w; break;
case 16: o(0) = -3 * u; o(1) = 6 * v; o(2) = 3 - 9 * v - 3 * w; break;
case 17: o(0) = -3 * u; o(1) = -6 + 9 * u + 6 * v + 9 * w; o(2) = -3 * w; break;
case 18: o(0) = 3 * u; o(1) = -3 + 3 * v + 9 * w; o(2) = -6 * w; break;
case 19: o(0) = 6 - 6 * u - 9 * v - 9 * w; o(1) = 3 * v; o(2) = 3 * w; break;
case 20: o(0) = 3 - 3 * u - 9 * w; o(1) = -3 * v; o(2) = 6 * w; break;
default: o.setZero(); break;
}
}
void BF_Tetrahedron_Nedelec(BF_INPUT)
{
switch (order)
@ -7,16 +64,20 @@ void BF_Tetrahedron_Nedelec(BF_INPUT)
case 1:
switch (numBF)
{
case 1: o(0) = -v; o(1) = u; o(2) = 0.; break; //12
case 2: o(0) = -w; o(1) = 0.; o(2) = u; break; //13
case 3: o(0) = -1+v+w; o(1) = -u; o(2) = -u; break; //14
case 4: o(0) = 0.; o(1) = -w; o(2) = v; break; //23
case 5: o(0) = -v; o(1) = -1+u+w; o(2) = -v; break; //24
case 6: o(0) = -w; o(1) = -w; o(2) = -1+u+v; break; //34
default: break;
case 1: o(0) = -v; o(1) = u; o(2) = 0.; break;
case 2: o(0) = -w; o(1) = 0.; o(2) = u; break;
case 3: o(0) = -1 + v + w; o(1) = -u; o(2) = -u; break;
case 4: o(0) = 0.; o(1) = -w; o(2) = v; break;
case 5: o(0) = -v; o(1) = -1 + u + w; o(2) = -v; break;
case 6: o(0) = -w; o(1) = -w; o(2) = -1 + u + v; break;
default: o.setZero(); break;
}
break;
case 2:
BF_Tetrahedron_Nedelec_Order2(numBF, u, v, w, o);
break;
default:
o.setZero();
break;
}
}
@ -28,17 +89,20 @@ void BF_Tetrahedron_Curl_Nedelec(BF_INPUT)
case 1:
switch (numBF)
{
case 1: o(0) = 0.; o(1) = 0.; o(2) = 2.; break; //12
case 2: o(0) = 0.; o(1) = -2.; o(2) = 0.; break; //13
case 3: o(0) = 0.; o(1) = 2.; o(2) = -2.; break; //14
case 4: o(0) = 2.; o(1) = 0.; o(2) = 0.; break; //23
case 5: o(0) = -2.; o(1) = 0.; o(2) = 2.; break; //24
case 6: o(0) = 2.; o(1) = -2.; o(2) = 0.; break; //34
default:
break;
case 1: o(0) = 0.; o(1) = 0.; o(2) = 2.; break;
case 2: o(0) = 0.; o(1) = -2.; o(2) = 0.; break;
case 3: o(0) = 0.; o(1) = 2.; o(2) = -2.; break;
case 4: o(0) = 2.; o(1) = 0.; o(2) = 0.; break;
case 5: o(0) = -2.; o(1) = 0.; o(2) = 2.; break;
case 6: o(0) = 2.; o(1) = -2.; o(2) = 0.; break;
default: o.setZero(); break;
}
break;
case 2:
BF_Tetrahedron_Curl_Nedelec_Order2(numBF, u, v, w, o);
break;
default:
o.setZero();
break;
}
}

View File

@ -0,0 +1,261 @@
# -*- coding: utf-8 -*-
"""
Generate work-summary PPT styled like 组会/yh -散射边界条件.pptx
Run: python generate_summary_ppt.py
"""
from pathlib import Path
import subprocess
import sys
from pptx import Presentation
from pptx.util import Pt
from pptx.enum.text import PP_ALIGN
ROOT = Path(__file__).resolve().parent
ASSETS = ROOT / "ppt_assets"
REF_PPT = Path(r"e:\组会\yh -散射边界条件.pptx")
REF_IMGS = Path(r"c:\Users\Administrator\Desktop\ppt_assets_ref")
OUT_PATH = ROOT / "OpticsFEM_2D_to_3D_Summary.pptx"
LAYOUT_TITLE_CONTENT = 11 # 5_标题和内容 — same as reference deck
def delete_all_slides(prs):
while len(prs.slides) > 0:
sld_id = prs.slides._sldIdLst[0]
r_id = sld_id.rId
prs.part.drop_rel(r_id)
del prs.slides._sldIdLst[0]
def set_title(slide, text):
if slide.shapes.title:
slide.shapes.title.text = text
def add_textbox(slide, left, top, width, height, text, size=14, bold=False):
box = slide.shapes.add_textbox(left, top, width, height)
tf = box.text_frame
tf.word_wrap = True
p = tf.paragraphs[0]
p.text = text
p.font.size = Pt(size)
p.font.bold = bold
return box
def add_bullets(slide, left, top, width, height, items, size=13):
box = slide.shapes.add_textbox(left, top, width, height)
tf = box.text_frame
tf.word_wrap = True
for i, item in enumerate(items):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.text = item
p.font.size = Pt(size)
p.space_after = Pt(6)
def add_picture(slide, path, left, top, width=None, height=None):
path = Path(path)
if not path.exists():
print(f" [warn] missing image: {path}")
return None
if width and height:
return slide.shapes.add_picture(str(path), left, top, width=width, height=height)
if width:
return slide.shapes.add_picture(str(path), left, top, width=width)
if height:
return slide.shapes.add_picture(str(path), left, top, height=height)
return slide.shapes.add_picture(str(path), left, top)
def ensure_assets():
ASSETS.mkdir(parents=True, exist_ok=True)
# charts
subprocess.run([sys.executable, str(ASSETS / "make_charts.py")], check=True, cwd=ROOT)
# normE slices if not present
mesh = ROOT / "scat3D/Release/SBCmesh.dat"
outdir = ROOT / "scat3D/Release/OutFile"
plot_script = ROOT / "tools/plot_normE_slice.py"
if mesh.exists() and (outdir / "normE").exists() and plot_script.exists():
for plane, coord, name in [("z", 0.5, "normE_z050.png"), ("z", 0, "normE_z000.png"), ("y", 0.5, "normE_y050.png")]:
out = ASSETS / name
if not out.exists():
subprocess.run([
sys.executable, str(plot_script),
"--mesh", str(mesh), "--outdir", str(outdir),
"--plane", plane, "--coord", str(coord),
"--output", str(out),
], cwd=ROOT, check=False)
def build(prs):
delete_all_slides(prs)
# emu helpers — reference deck is 13.33" wide
from pptx.util import Inches
W = prs.slide_width
# --- 1 封面仿参考第1页布局---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT])
set_title(slide, "组会汇报")
add_textbox(
slide, Inches(0), Inches(2.9), W, Inches(1.6),
"OpticsFEM 二维→三维扩展工作总结\n"
"一阶 Nedelec 边元 · 散射边界条件与本征频率问题\n"
"汇报人: 时间2026年6月",
size=22, bold=False,
)
add_picture(slide, REF_IMGS / "image5.png", Inches(7.2), Inches(1.5), width=Inches(5.5))
# --- 2 背景与目标 ---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT])
set_title(slide, "项目背景与目标")
add_bullets(slide, Inches(0.35), Inches(1.35), Inches(5.8), Inches(5.5), [
"原有 OpticsFEM2D 三角网格 + 一阶边元FemType 03",
"目标:扩展至 3D 四面体网格,对标 MATLAB 参考代码",
"算例SBC 立方体模型εr=1.0/1.5,λ₀=0.8 m",
"散射assembly_equ + out + inc → Ax=b",
"本征assembly_equ → A·x=λ·B·x",
"交付C++ 散射全流程 + 本征矩阵与 MATLAB 对齐",
])
add_picture(slide, REF_IMGS / "image6.png", Inches(6.3), Inches(1.6), width=Inches(6.5))
# --- 3 SBC 物理(仿参考“散射边界条件模型”)---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT])
set_title(slide, "散射边界条件SBC物理模型")
add_textbox(slide, Inches(0.35), Inches(1.8), Inches(5.5), Inches(4.5),
"散射边界条件Scattering Boundary Condition\n\n"
"• 出射边界:模拟只有沿外法向传播的出射波\n"
"• 入射激励:平面波 Einc=(1,0,0)\n"
"• 体积分curl-curl k₀²ε\n"
"• C++ 实现Assemble_SBC + assembly_inc/out\n"
"• 与 MATLAB main.m 流程一致", size=14)
add_picture(slide, REF_IMGS / "image8.png", Inches(1.5), Inches(4.8), width=Inches(3.5))
add_picture(slide, REF_IMGS / "image17.png", Inches(6.0), Inches(1.5), width=Inches(6.8))
# --- 4 2D vs 3D ---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT])
set_title(slide, "二维与三维扩展对照")
add_picture(slide, ASSETS / "status_table.png", Inches(0.4), Inches(1.4), width=Inches(12.0))
add_textbox(slide, Inches(0.5), Inches(5.0), Inches(12), Inches(1.2),
"新增 FemType 43D 散射)与 53D 本征);共用 Mesh_3D + SBCmesh.dat",
size=13)
# --- 5 软件架构 ---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT])
set_title(slide, "软件整体架构")
add_picture(slide, ASSETS / "workflow.png", Inches(0.5), Inches(1.35), width=Inches(12.2))
add_bullets(slide, Inches(0.5), Inches(5.2), Inches(12), Inches(1.5), [
"模块mesh · material · phy · kernel · solver · post · Interface",
"构建scat3D/(散射)与 eigen3D/(本征)独立 CMake 目录",
], size=12)
# --- 6 散射方程与流程(仿参考“方程”页)---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT])
set_title(slide, "三维散射问题FemType = 4")
add_picture(slide, REF_IMGS / "image3.png", Inches(0.4), Inches(1.5), width=Inches(5.5))
add_picture(slide, REF_IMGS / "image4.png", Inches(6.2), Inches(1.5), width=Inches(6.5))
add_textbox(slide, Inches(0.5), Inches(5.5), Inches(12), Inches(1.2),
"方程 A·x=b | 配置 sbc3d.json | 返回码 4 | complexsolver SparseLU",
size=13, bold=True)
# --- 7 散射数值结果 ---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT])
set_title(slide, "散射仿真结果 |E| 分布")
add_picture(slide, ASSETS / "normE_z050.png", Inches(0.4), Inches(1.35), width=Inches(6.0))
add_picture(slide, ASSETS / "metrics.png", Inches(6.6), Inches(1.35), width=Inches(6.2))
add_textbox(slide, Inches(0.5), Inches(5.6), Inches(12), Inches(0.8),
"z=0.5 m 切面 | |E| min=0.72 max=1.36 V/m | 残差 |r|≈6.2×10⁻¹³",
size=13)
# --- 8 多切面对比 ---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT])
set_title(slide, "散射场多切面 |E|C++ Post 输出)")
add_picture(slide, ASSETS / "normE_z000.png", Inches(0.3), Inches(1.4), width=Inches(4.0))
add_picture(slide, ASSETS / "normE_z050.png", Inches(4.5), Inches(1.4), width=Inches(4.0))
add_picture(slide, ASSETS / "normE_y050.png", Inches(8.7), Inches(1.4), width=Inches(4.0))
add_textbox(slide, Inches(0.3), Inches(1.2), Inches(4), Inches(0.3), "z = 0", size=11, bold=True)
add_textbox(slide, Inches(4.5), Inches(1.2), Inches(4), Inches(0.3), "z = 0.5 m", size=11, bold=True)
add_textbox(slide, Inches(8.7), Inches(1.2), Inches(4), Inches(0.3), "y = 0.5 m", size=11, bold=True)
add_textbox(slide, Inches(0.5), Inches(5.8), Inches(12), Inches(0.6),
"可与 COMSOL 切面对比compare/compare_normE_zslice.m", size=12)
# --- 9 本征问题 ---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT])
set_title(slide, "三维本征频率问题FemType = 5")
add_bullets(slide, Inches(0.35), Inches(1.4), Inches(5.5), Inches(5.5), [
"广义本征A·x = λ·B·x",
"A = curl-curl刚度B = ε 质量矩阵",
"边界:全 PMC与 MATLAB 3D一阶本征问题2 一致)",
"C++Assemble + Test_OutputMatrix",
"导出Ai/Aj/Av、Bi/Bj/BvCOO0-based",
"求解MATLAB eigs(A,B) + get_ele 绘图",
"待接C++ Run/Post",
])
add_picture(slide, REF_IMGS / "image27.png", Inches(6.0), Inches(1.5), width=Inches(6.8))
# --- 10 本征矩阵验证 ---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT])
set_title(slide, "本征矩阵组装验证C++ vs MATLAB")
add_picture(slide, ASSETS / "metrics.png", Inches(0.4), Inches(1.4), width=Inches(6.5))
add_bullets(slide, Inches(6.8), Inches(1.5), Inches(6), Inches(5), [
"Av.txt 约 1.29×10⁶ 行非零元",
"C++ OutFile 与 MATLAB OutFile 逐行一致",
"Bi/Bj/Bv 约 43632 非零(质量矩阵)",
"FemType=5 返回码5",
"下一步:接入 Solver_EigenFreq",
], size=14)
# --- 11 求解器 ---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT])
set_title(slide, "求解器体系")
add_picture(slide, REF_IMGS / "image12.png", Inches(0.4), Inches(1.5), width=Inches(5.5))
add_bullets(slide, Inches(6.2), Inches(1.5), Inches(6.5), Inches(5), [
"solver/:接口层(编进 OpticsFEM.exe",
"散射interface.cpp → complexsolver.exe",
"complexsolverEigen SparseLU / BiCGSTAB",
"路径Release/complex/complexsolver.exe",
"本征 3D当前 MATLAB eigs 验证",
"优势:无需 PETScCMake 一键编译",
], size=13)
# --- 12 总结 ---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT])
set_title(slide, "总结与下一步")
add_bullets(slide, Inches(0.4), Inches(1.4), Inches(6), Inches(5.5), [
"【已完成】2D→3D 一阶 Nedelec 框架扩展",
"【已完成】3D SBC 散射端到端可运行、数值可靠",
"【已完成】3D 本征 A/B 组装与 MATLAB 严格对齐",
"【待完成】本征 C++ 求解与后处理接入",
"【待完成】PBC、二阶基等扩展MATLAB 已有)",
"【待完成】Test_Main 支持命令行指定 JSON",
], size=14)
add_picture(slide, ASSETS / "normE_z050.png", Inches(6.5), Inches(1.5), width=Inches(6.0))
# --- 13 致谢 ---
slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT])
set_title(slide, "下一步工作")
add_textbox(
slide, Inches(1.5), Inches(2.5), Inches(10), Inches(2.5),
"1. COMSOL 数值对比出图compare 脚本)\n"
"2. FemType=5 接入 Run/Post完成 C++ 本征闭环\n"
"3. 扩展 PBC / 二阶基边界条件\n"
"4. 算例自动化与文档完善\n\n"
"谢谢!",
size=22, bold=False,
)
def main():
print("Preparing assets...")
ensure_assets()
if not REF_PPT.exists():
raise FileNotFoundError(f"Reference PPT not found: {REF_PPT}")
print(f"Loading template: {REF_PPT}")
prs = Presentation(str(REF_PPT))
build(prs)
prs.save(str(OUT_PATH))
print(f"Saved: {OUT_PATH}")
if __name__ == "__main__":
main()

View File

@ -141,3 +141,8 @@ void OpticsFEM_3D_Scatter::GetPost(Post_3D_Scatter* post)
{
_mPost = post;
}
void OpticsFEM_3D_Scatter::SetExportAsmDirectory(const std::string& dir)
{
_mExportAsmDir = dir;
}

View File

@ -164,6 +164,8 @@ public:
void GetPost(Post_3D_EigenFreq* post);
void Assemble();
void Run();
void Post(std::string str);
void Test_OutputMatrix(const std::string& outDir = "./OutFile");
@ -180,6 +182,9 @@ private:
bool _mIsReal;
int _mDof;
Eigen::MatrixXcd _mX;
Eigen::VectorXcd _mLambda;
std::vector<Eigen::Triplet<double>> _mTripleA_real, _mTripleB_real;
Eigen::SparseMatrix<double, Eigen::RowMajor> _mA_real;
Eigen::SparseMatrix<double, Eigen::RowMajor> _mB_real;
@ -203,11 +208,20 @@ public:
void Run();
void Post(std::string str);
void Test_OutputMatrix(const std::string& outDir = ".");
void SetExportAsmDirectory(const std::string& dir);
private:
void Assemble_WaveEquation();
void Assemble_WaveEquation_Order2();
void Assemble_SBC();
void Assemble_BELE();
void Assemble_MAG();
void Assemble_SCD();
void Assemble_MPD();
void Assemble_EPD();
void Assemble_PEC_ELE();
void Assemble_PBC();
void Assemble_Port();
MaterialLib* _mMatLib;
Mesh_3D* _mMesh;
@ -217,6 +231,8 @@ private:
bool _mIsReal;
int _mDof;
int _mPortExtraDof = 0;
std::vector<int> _mFreeDofIndices; // full-system DOF indices kept after PEC elimination (port case)
Eigen::VectorXcd _mX;
@ -228,6 +244,7 @@ private:
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor> _mA_complex;
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor> _mP_complex;
Eigen::VectorXcd _mB_complex;
std::string _mExportAsmDir;
};
class OpticsFEM_3D_Scatter2

View File

@ -1,6 +1,10 @@
#include"Assemble_Base.h"
#include"PBC_Util.h"
#include"Nedelec3D_Util.h"
#include"../common/util.h"
#include<iostream>
#include<vector>
#include<algorithm>
void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
{
@ -24,11 +28,11 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
PBCData(i) = _mPhy->GetPBCPhi(i);
}
_mMesh->GetIndexOfPBC(PBC, PBCData, srcNodeIndex, dstNodeIndex, nodePhi, srcEdgeIndex, dstEdgeIndex, edgePhi);
// 1:DOF
//列 1(DOF-NbrPEC-NbrDst)
//?? 1:DOF
//?? 1??(DOF-NbrPEC-NbrDst)
int NbrVertex = _mMesh->GetNbrVertex();
int NbrDstIndex = nodeIndexOfPEC.rows() + edgeIndexOfPEC.rows() + dstNodeIndex.rows() + dstEdgeIndex.rows();
//电场类别去除数目
//??T?????????
int numEle = 0;
if (_mElectricType == 0)
{
@ -42,7 +46,7 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
Eigen::VectorXi dstIndex = Eigen::VectorXi::Zero(NbrDstIndex);
Eigen::VectorXi srcIndex = Eigen::VectorXi::Zero(NbrDstIndex);
Eigen::VectorXcd IndexPhi = Eigen::VectorXcd::Zero(NbrDstIndex);
//电场类别去除索引添加
//??T??????????????
if (_mElectricType == 0)
{
for (int i = 0; i < numEle; i++)
@ -89,12 +93,12 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
srcIndex(i + num) = srcEdgeIndex(i) + NbrVertex;
IndexPhi(i + num) = edgePhi(i);
}
//排序
//????
Eigen::VectorXi tempIndex = Eigen::VectorXi::Zero(NbrDstIndex);
for (int i = 0; i < NbrDstIndex; i++)
tempIndex(i) = i;
QuickSort(dstIndex, tempIndex, 0, NbrDstIndex - 1);
//去重
//???
Unique(dstIndex, tempIndex);
Eigen::VectorXcd tempIndexPhi = Eigen::VectorXcd::Zero(dstIndex.rows());
Eigen::VectorXi tempSrcIndex = Eigen::VectorXi::Zero(dstIndex.rows());
@ -106,12 +110,12 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
IndexPhi = tempIndexPhi;
srcIndex = tempSrcIndex;
//不同数目
//??????
int dof = _mMesh->GetNbrEdge() + _mMesh->GetNbrVertex();
int vdof = _mMesh->GetNbrEdge();
int sdof = _mMesh->GetNbrVertex();
//P矩阵
//P????
int num = 0;
if (_mIsReal)
{
@ -119,7 +123,7 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
for (int i = 0; i < dof; i++) //
{
temp_P.push_back(Eigen::Triplet<double>(i, i, 1));
if (num < dstIndex.size()) //处理PEC或PBC
if (num < dstIndex.size()) //????PEC??PBC
{
if (i == dstIndex(num))
{
@ -128,13 +132,13 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
}
}
}
/*临时P矩阵排序*/
/*???P????????*/
std::sort(temp_P.begin(), temp_P.end(),
[](const Eigen::Triplet<double>& a, const Eigen::Triplet<double>& b)
{
return a.col() < b.col();
});
///*排序后打印*/
///*???????*/
//std::sort(temp_P.begin(), temp_P.end(),
// [](const Eigen::Triplet<double>& a, const Eigen::Triplet<double>& b) {
// return a.col() < b.col();
@ -143,9 +147,9 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
// std::cout << "(" << triplet.row() << ", " << triplet.col() << "): " << triplet.value() << std::endl;
// }
/*删除dstIndex对应列*/
/*???dstIndex?????*/
std::vector<Eigen::Triplet<double>> tripleP_real;
num = 0;// 用于跟踪新矩阵的列索引
num = 0;// ???????????????????
int DeleteIndex = 0;
for (int i = 0; i < dof; i++)
{
@ -179,7 +183,7 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
_mP_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(dof, dof - dstIndex.rows());
_mP_real.setFromTriplets(tripleP_real.begin(), tripleP_real.end());
/*排序后打印*/
/*???????*/
//std::sort(tripleP_real.begin(), tripleP_real.end(),
// [](const Eigen::Triplet<double>& a, const Eigen::Triplet<double>& b) {
// return a.col() < b.col();
@ -194,7 +198,7 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
for (int i = 0; i < dof; i++) //
{
temp_P_complex.push_back(Eigen::Triplet<complex<double>>(i, i, 1));
if (num < dstIndex.size()) //处理PEC或PBC
if (num < dstIndex.size()) //????PEC??PBC
{
if (i == dstIndex(num))
{
@ -203,7 +207,7 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
}
}
}
/*临时P矩阵排序*/
/*???P????????*/
std::sort(temp_P_complex.begin(), temp_P_complex.end(),
[](const Eigen::Triplet<complex<double>>& a, const Eigen::Triplet<complex<double>>& b)
{
@ -211,9 +215,9 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
});
/*删除dstIndex对应列*/
/*???dstIndex?????*/
std::vector<Eigen::Triplet<complex<double>>> tripleP_complex;
num = 0;// 用于跟踪新矩阵的列索引
num = 0;// ???????????????????
int DeleteIndex = 0;
for (int i = 0; i < dof; i++)
{
@ -246,7 +250,7 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
}
_mP_complex = Eigen::SparseMatrix<complex<double>, Eigen::RowMajor>(dof, dof - dstIndex.rows());
_mP_complex.setFromTriplets(tripleP_complex.begin(), tripleP_complex.end());;
///*排序后打印*/
///*???????*/
//std::sort(tripleP_complex.begin(), tripleP_complex.end(),
//[](const Eigen::Triplet<complex<double>>& a, const Eigen::Triplet<complex<double>>& b)
// {
@ -261,111 +265,114 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
void OpticsFEM_3D_EigenFreq::Assemble_PEC_PBC()
{
//find PEC index
const int elementOrder = _mPhy->GetElementOrder();
std::vector<int> allSrc;
std::vector<int> allDst;
std::vector<std::complex<double>> allPhi;
if (_mPhy->GetNbrPEC() > 0)
{
Eigen::VectorXi PECDomain = Eigen::VectorXi::Zero(_mPhy->GetNbrPEC());
for (int i = 0; i < _mPhy->GetNbrPEC(); i++)
PECDomain(i) = _mPhy->GetPECDomain(i);
Eigen::VectorXi edgeIndexOfPEC;
_mMesh->GetTriIndexOfDomain(PECDomain, edgeIndexOfPEC);
//find PBC index
Eigen::VectorXi srcEdgeIndex, dstEdgeIndex;
Eigen::VectorXcd edgePhi, PBCData;
Eigen::MatrixXi PBC;
PBC = Eigen::MatrixXi::Zero(_mPhy->GetNbrPBC(), 2);
PBCData = Eigen::VectorXcd::Zero(_mPhy->GetNbrPBC());
for (int i = 0; i < _mPhy->GetNbrPBC(); i++)
PECDomain(i) = _mPhy->GetPECDomain(i) + 1;
// Reuse scatter PEC DOF collector via local duplicate of edge+face zeroing.
// Edge DOFs (order1) or edge×2 + face×2 (order2).
std::vector<int> pecEdges;
for (int i = 0; i < PECDomain.rows(); i++)
{
PBC(i, 0) = _mPhy->GetSrcDomain(i);
PBC(i, 1) = _mPhy->GetDstDomain(i);
PBCData(i) = _mPhy->GetPBCPhi(i);
Eigen::VectorXi triIndices;
_mMesh->GetTriIndicesOfDomain(PECDomain(i), triIndices);
for (int t = 0; t < triIndices.size(); t++)
{
Eigen::Vector3i conn;
_mMesh->GetCoonOfTri(triIndices(t), conn);
const int numTet = conn(0);
const int numFace = conn(1) + 1;
int e[3] = { -1, -1, -1 };
if (numFace == 1) { e[0] = _mMesh->GetEdgeOfTet(numTet, 0); e[1] = _mMesh->GetEdgeOfTet(numTet, 1); e[2] = _mMesh->GetEdgeOfTet(numTet, 3); }
else if (numFace == 2) { e[0] = _mMesh->GetEdgeOfTet(numTet, 0); e[1] = _mMesh->GetEdgeOfTet(numTet, 2); e[2] = _mMesh->GetEdgeOfTet(numTet, 4); }
else if (numFace == 3) { e[0] = _mMesh->GetEdgeOfTet(numTet, 1); e[1] = _mMesh->GetEdgeOfTet(numTet, 2); e[2] = _mMesh->GetEdgeOfTet(numTet, 5); }
else if (numFace == 4) { e[0] = _mMesh->GetEdgeOfTet(numTet, 3); e[1] = _mMesh->GetEdgeOfTet(numTet, 4); e[2] = _mMesh->GetEdgeOfTet(numTet, 5); }
for (int k = 0; k < 3; k++)
if (e[k] >= 0) pecEdges.push_back(e[k]);
}
_mMesh->GetIndexOfPBC(PBC, PBCData, srcEdgeIndex, dstEdgeIndex, edgePhi);
//行 1:DOF
//列 1(DOF-NbrPEC-NbrDst)
int NbrDstIndex = edgeIndexOfPEC.rows() + dstEdgeIndex.rows();
Eigen::VectorXi dstIndex = Eigen::VectorXi::Zero(NbrDstIndex);
Eigen::VectorXi srcIndex = Eigen::VectorXi::Zero(NbrDstIndex);
Eigen::VectorXcd IndexPhi = Eigen::VectorXcd::Zero(NbrDstIndex);
for (int i = 0; i < edgeIndexOfPEC.rows(); i++)
{
dstIndex(i) = edgeIndexOfPEC(i);
srcIndex(i) = edgeIndexOfPEC(i);
IndexPhi(i) = 0;
}
for (int i = 0; i < dstEdgeIndex.rows(); i++)
std::sort(pecEdges.begin(), pecEdges.end());
pecEdges.erase(std::unique(pecEdges.begin(), pecEdges.end()), pecEdges.end());
const int nE = _mMesh->GetNbrEdge();
const int nF = _mMesh->GetNbrFace();
for (int edgeId : pecEdges)
{
int num = edgeIndexOfPEC.rows();
dstIndex(i + num) = dstEdgeIndex(i);
srcIndex(i + num) = srcEdgeIndex(i);
IndexPhi(i + num) = edgePhi(i);
allSrc.push_back(Nedelec3D::edgeGlobalDof(edgeId, 0, nE));
allDst.push_back(Nedelec3D::edgeGlobalDof(edgeId, 0, nE));
allPhi.push_back(0.0);
if (elementOrder == 2)
{
allSrc.push_back(Nedelec3D::edgeGlobalDof(edgeId, 1, nE));
allDst.push_back(Nedelec3D::edgeGlobalDof(edgeId, 1, nE));
allPhi.push_back(0.0);
}
//排序
Eigen::VectorXi tempIndex = Eigen::VectorXi::Zero(NbrDstIndex);
for (int i = 0; i < NbrDstIndex; i++)
tempIndex(i) = i;
QuickSort(dstIndex, tempIndex, 0, NbrDstIndex - 1);
Eigen::VectorXcd tempIndexPhi = Eigen::VectorXcd::Zero(NbrDstIndex);
for (int i = 0; i < NbrDstIndex; i++)
tempIndexPhi(i) = IndexPhi(tempIndex(i));
IndexPhi = tempIndexPhi;
Eigen::VectorXi tempSrcIndex = Eigen::VectorXi::Zero(NbrDstIndex);
for (int i = 0; i < NbrDstIndex; i++)
tempSrcIndex(i) = srcIndex(tempIndex(i));
srcIndex = tempSrcIndex;
//去重
Unique(dstIndex, tempIndex);
if (NbrDstIndex > tempIndex.rows())
}
if (elementOrder == 2)
{
for (int i = 0; i < tempIndex.rows(); i++)
IndexPhi(i) = IndexPhi(tempIndex(i));
IndexPhi = IndexPhi.head(tempIndex.rows());
for (int i = 0; i < tempIndex.rows(); i++)
srcIndex(i) = srcIndex(tempIndex(i));
srcIndex = srcIndex.head(tempIndex.rows());
std::vector<int> pecFaces;
for (int i = 0; i < PECDomain.rows(); i++)
{
Eigen::VectorXi triIndices;
_mMesh->GetTriIndicesOfDomain(PECDomain(i), triIndices);
for (int t = 0; t < triIndices.size(); t++)
{
Eigen::Vector3i conn;
_mMesh->GetCoonOfTri(triIndices(t), conn);
pecFaces.push_back(_mMesh->GetFaceOfTet(conn(0), conn(1)));
}
}
std::sort(pecFaces.begin(), pecFaces.end());
pecFaces.erase(std::unique(pecFaces.begin(), pecFaces.end()), pecFaces.end());
for (int faceId : pecFaces)
{
allSrc.push_back(Nedelec3D::faceGlobalDof(faceId, 0, nE, nF));
allDst.push_back(Nedelec3D::faceGlobalDof(faceId, 0, nE, nF));
allPhi.push_back(0.0);
allSrc.push_back(Nedelec3D::faceGlobalDof(faceId, 1, nE, nF));
allDst.push_back(Nedelec3D::faceGlobalDof(faceId, 1, nE, nF));
allPhi.push_back(0.0);
}
}
}
//P矩阵
int num = 0;
if (_mPhy->GetNbrPBCGroups() > 0)
{
Eigen::VectorXi srcEdgeIndex;
Eigen::VectorXi dstEdgeIndex;
Eigen::VectorXcd edgePhi;
if (collectMergedPbcConstraints(_mMesh, _mPhy, elementOrder,
srcEdgeIndex, dstEdgeIndex, edgePhi))
{
for (int i = 0; i < srcEdgeIndex.size(); i++)
{
allSrc.push_back(srcEdgeIndex(i));
allDst.push_back(dstEdgeIndex(i));
allPhi.push_back(edgePhi(i));
}
}
}
const int nbr = static_cast<int>(allSrc.size());
Eigen::VectorXi srcIndex(nbr);
Eigen::VectorXi dstIndex(nbr);
Eigen::VectorXcd indexPhi(nbr);
for (int i = 0; i < nbr; i++)
{
srcIndex(i) = allSrc[i];
dstIndex(i) = allDst[i];
indexPhi(i) = allPhi[i];
}
sortUniquePbcPairs(srcIndex, dstIndex, indexPhi);
if (_mIsReal)
{
std::vector<Eigen::Triplet<double>> tripleP_real;
for (int i = 0; i < _mDof; i++) // 1 4 5
{
if (i == dstIndex(num))
{
tripleP_real.push_back(Eigen::Triplet<double>(dstIndex(num), srcIndex(num) - num, IndexPhi(num).real()));
num++;
}
buildPeriodicProjectionReal(_mDof, dstIndex, srcIndex, indexPhi, _mP_real);
else
{
tripleP_real.push_back(Eigen::Triplet<double>(i, i - num, 1));
buildPeriodicProjectionComplex(_mDof, dstIndex, srcIndex, indexPhi, _mP_complex);
}
}
_mP_real.setFromTriplets(tripleP_real.begin(), tripleP_real.end());
}
else
{
std::vector<Eigen::Triplet<complex<double>>> tripleP_complex;
for (int i = 0; i < _mDof; i++) // 1 4 5
{
if (i == dstIndex(num))
{
tripleP_complex.push_back(Eigen::Triplet<complex<double>>(dstIndex(num), srcIndex(num) - num, IndexPhi(num)));
num++;
}
else
{
tripleP_complex.push_back(Eigen::Triplet<complex<double>>(i, i - num, 1));
}
}
_mP_complex.setFromTriplets(tripleP_complex.begin(), tripleP_complex.end());
}
}

View File

@ -1,4 +1,5 @@
#include"Assemble_Base.h"
#include"Nedelec3D_Util.h"
#include"../function/BF.h"
#include"../function/Gauss.h"
#include"../common/define.h"
@ -228,11 +229,15 @@ void OpticsFEM_2D_EigenFreq::Assemble_WaveEquation()
void OpticsFEM_3D_EigenFreq::Assemble_WaveEquation()
{
const int elementOrder = _mPhy->GetElementOrder();
const int bfOrder = Nedelec3D::bfOrderParam(elementOrder);
const int gaussVol = Nedelec3D::gaussOrderVol(elementOrder);
//init of Gauss point
Gauss gauss;
int NbrGuassPoints;
double* u, * v, * w, * wght;
NbrGuassPoints = gauss.GetNbrGaussPoints(THREEDIM, TETRAHEDRON, BF_LINEFUNC * 2);
NbrGuassPoints = gauss.GetNbrGaussPoints(THREEDIM, TETRAHEDRON, gaussVol);
u = new double[NbrGuassPoints];
v = new double[NbrGuassPoints];
w = new double[NbrGuassPoints];
@ -247,8 +252,8 @@ void OpticsFEM_3D_EigenFreq::Assemble_WaveEquation()
BF BF_Nedelec, BF_Curl_Nedelec;
int dof;
Vector3d** E, ** curlE;
dof = BF_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, BF_LINEFUNC);
BF_Curl_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_CURL_NEDELEC, BF_LINEFUNC);
dof = BF_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder);
BF_Curl_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_CURL_NEDELEC, bfOrder);
E = new Vector3d * [NbrGuassPoints];
curlE = new Vector3d * [NbrGuassPoints];
for (int i = 0; i < NbrGuassPoints; i++)
@ -292,8 +297,10 @@ void OpticsFEM_3D_EigenFreq::Assemble_WaveEquation()
//mapping
VectorXi MappingIndex = VectorXi::Zero(dof);
std::vector<int> mapBuf(static_cast<size_t>(dof), 0);
Nedelec3D::buildTetDofMap(_mMesh, n, elementOrder, mapBuf.data(), dof);
for (int i = 0; i < dof; i++)
MappingIndex(i) = _mMesh->GetEdgeOfTet(n, i);
MappingIndex(i) = mapBuf[static_cast<size_t>(i)];
//submatrix
MatrixXcd Se, Te;

View File

@ -1,18 +1,35 @@
#include"Assemble_Base.h"
#include"Nedelec3D_Util.h"
#include"PBC_Util.h"
#include"SBC_NormalUtil.h"
#include"../function/BF.h"
#include"../function/Gauss.h"
#include"../common/define.h"
#include"../common/util.h"
#include"../material/Material_Base.h"
#include"../phy/Phy_Base.h"
#include"../parser/mpParser.h"
#include<cmath>
#include<complex>
#include<string>
#include<vector>
#include<algorithm>
using namespace Eigen;
// Port of MATLAB assembly_out.m and assembly_inc.m
// SBC 边界组装:按第三类边界条件 Robin 形式 K += γb += q
//
// 散射场 SBC教材: dE_sc/dn + ik₀ E_sc = 0 → γ = ik₀q = 0
// BELE 算例 (SBCType=0): E_s → Ae(γ) 进 AE_b 的 ABC 切向项 + IBP 面项 → Assemble_BELE() 进 b
// Robin q=0SBC 上不组装 Be(E_inc)
//
// Nedelec 边元上面上的 γ 不能写成单个 K_nn+=γ,需面积分。
// 代码中 Ae(i,j) 来自 assembly_out.mγ=ik₀√ε 在 Nedelec 基上的离散:
// A_ij += ik₀√ε ∫ N_i·(n×(n×N_j)) dS
// 这与标量 K+=γ 是同一 Robin 项在矢量一阶基下的写法,不是体域 Se/Te。
//
// Be 仅用于 SBCType=1 显式入射(已知 q = Einc不用于 BELE。
namespace {
@ -21,11 +38,236 @@ Vector3d crossNormal(const Vector3d& normal, const Vector3d& a)
return normal.cross(a.cross(normal));
}
Vector3cd crossNormal(const Vector3d& normal, const Vector3cd& a)
{
return normal.cross(a.cross(normal));
}
double getScalarEps(int domainOfTet, MaterialLib* matLib)
{
return matLib->GetEpsr(domainOfTet)(0, 0).real();
}
bool getFaceEdgeIndices(Mesh_3D* mesh, int triIdx, int outEdges[3])
{
Vector3i conn;
mesh->GetCoonOfTri(triIdx, conn);
const int numTet = conn(0);
const int numFace = conn(1) + 1;
if (numFace == 1)
{
outEdges[0] = mesh->GetEdgeOfTet(numTet, 0);
outEdges[1] = mesh->GetEdgeOfTet(numTet, 1);
outEdges[2] = mesh->GetEdgeOfTet(numTet, 3);
}
else if (numFace == 2)
{
outEdges[0] = mesh->GetEdgeOfTet(numTet, 0);
outEdges[1] = mesh->GetEdgeOfTet(numTet, 2);
outEdges[2] = mesh->GetEdgeOfTet(numTet, 4);
}
else if (numFace == 3)
{
outEdges[0] = mesh->GetEdgeOfTet(numTet, 1);
outEdges[1] = mesh->GetEdgeOfTet(numTet, 2);
outEdges[2] = mesh->GetEdgeOfTet(numTet, 5);
}
else if (numFace == 4)
{
outEdges[0] = mesh->GetEdgeOfTet(numTet, 3);
outEdges[1] = mesh->GetEdgeOfTet(numTet, 4);
outEdges[2] = mesh->GetEdgeOfTet(numTet, 5);
}
else
return false;
return true;
}
void getEdgeGeometry(Mesh_3D* mesh, int edgeId, Vector3d& midpoint, Vector3d& edgeVec)
{
const int v0 = mesh->GetEdge(edgeId, 0);
const int v1 = mesh->GetEdge(edgeId, 1);
Vector3d p0, p1;
mesh->GetVertex(v0, p0);
mesh->GetVertex(v1, p1);
edgeVec = p1 - p0;
midpoint = (p0 + p1) * 0.5;
}
Vector3cd evalE0AtPoint(
mup::ParserX& parser,
mup::Value& xx, mup::Value& yy, mup::Value& zz,
const std::string& E0x, const std::string& E0y, const std::string& E0z,
double x, double y, double z)
{
xx = x;
yy = y;
zz = z;
parser.SetExpr(_T(E0x.c_str()));
mup::Value vx = parser.Eval();
parser.SetExpr(_T(E0y.c_str()));
mup::Value vy = parser.Eval();
parser.SetExpr(_T(E0z.c_str()));
mup::Value vz = parser.Eval();
return Vector3cd(
std::complex<double>(vx.GetFloat(), vx.GetImag()),
std::complex<double>(vy.GetFloat(), vy.GetImag()),
std::complex<double>(vz.GetFloat(), vz.GetImag()));
}
Vector3cd parseEincConstant(
mup::ParserX& parser,
mup::Value& xx, mup::Value& yy, mup::Value& zz,
const std::string& Eincx, const std::string& Eincy, const std::string& Eincz)
{
return evalE0AtPoint(parser, xx, yy, zz, Eincx, Eincy, Eincz, 0.0, 0.0, 0.0);
}
void applyDirichletBCReal(
Eigen::SparseMatrix<double, Eigen::RowMajor>& A,
Eigen::VectorXd& B,
const Eigen::VectorXi& BCIndex,
const Eigen::VectorXcd& BCValue)
{
for (int i = 0; i < BCValue.rows(); i++)
{
Eigen::VectorXd tempValue = Eigen::VectorXd::Zero(A.rows());
tempValue(BCIndex(i)) = 1.0;
B = B - A * tempValue * BCValue(i).real();
}
Eigen::SparseMatrix<double, Eigen::RowMajor> P(A.rows(), A.rows());
std::vector<Eigen::Triplet<double>> tempTriple;
int num = 0;
for (int i = 0; i < A.rows(); i++)
{
if (num < BCIndex.rows() && i == BCIndex(num))
num++;
else
tempTriple.emplace_back(i, i, 1.0);
}
P.setFromTriplets(tempTriple.begin(), tempTriple.end());
A = P * A * P;
tempTriple.clear();
for (int i = 0; i < BCIndex.rows(); i++)
{
tempTriple.emplace_back(BCIndex(i), BCIndex(i), 1.0);
B(BCIndex(i)) = BCValue(i).real();
}
P.setZero();
P.setFromTriplets(tempTriple.begin(), tempTriple.end());
A = A + P;
A.makeCompressed();
}
void applyDirichletBCComplex(
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>& A,
Eigen::VectorXcd& B,
const Eigen::VectorXi& BCIndex,
const Eigen::VectorXcd& BCValue)
{
for (int i = 0; i < BCValue.rows(); i++)
{
Eigen::VectorXcd tempValue = Eigen::VectorXcd::Zero(A.rows());
tempValue(BCIndex(i)) = 1.0;
B = B - A * tempValue * BCValue(i);
}
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor> P(A.rows(), A.rows());
std::vector<Eigen::Triplet<std::complex<double>>> tempTriple;
int num = 0;
for (int i = 0; i < A.rows(); i++)
{
if (num < BCIndex.rows() && i == BCIndex(num))
num++;
else
tempTriple.emplace_back(i, i, 1.0);
}
P.setFromTriplets(tempTriple.begin(), tempTriple.end());
A = P * A * P;
tempTriple.clear();
for (int i = 0; i < BCIndex.rows(); i++)
{
tempTriple.emplace_back(BCIndex(i), BCIndex(i), 1.0);
B(BCIndex(i)) = BCValue(i);
}
P.setZero();
P.setFromTriplets(tempTriple.begin(), tempTriple.end());
A = A + P;
A.makeCompressed();
}
void eliminateDirichletDofsComplex(
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>& A,
Eigen::VectorXcd& B,
const Eigen::VectorXi& elimIndices,
std::vector<int>& freeIndicesOut)
{
const int n = static_cast<int>(A.rows());
std::vector<char> elim(static_cast<size_t>(n), 0);
for (int i = 0; i < elimIndices.rows(); i++)
{
const int idx = elimIndices(i);
if (idx >= 0 && idx < n)
elim[static_cast<size_t>(idx)] = 1;
}
freeIndicesOut.clear();
freeIndicesOut.reserve(static_cast<size_t>(n));
for (int i = 0; i < n; i++)
if (!elim[static_cast<size_t>(i)])
freeIndicesOut.push_back(i);
const int nf = static_cast<int>(freeIndicesOut.size());
if (nf == n)
return;
std::vector<int> oldToNew(static_cast<size_t>(n), -1);
for (int i = 0; i < nf; i++)
oldToNew[static_cast<size_t>(freeIndicesOut[static_cast<size_t>(i)])] = i;
std::vector<Eigen::Triplet<std::complex<double>>> trips;
trips.reserve(static_cast<size_t>(A.nonZeros()));
for (int col = 0; col < A.outerSize(); col++)
{
for (Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>::InnerIterator it(A, col); it; ++it)
{
const int nr = oldToNew[static_cast<size_t>(it.row())];
const int nc = oldToNew[static_cast<size_t>(it.col())];
if (nr >= 0 && nc >= 0)
trips.emplace_back(nr, nc, it.value());
}
}
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor> Anew(nf, nf);
Anew.setFromTriplets(trips.begin(), trips.end());
A = Anew;
Eigen::VectorXcd Bnew(nf);
for (int i = 0; i < nf; i++)
Bnew(i) = B(freeIndicesOut[static_cast<size_t>(i)]);
B = Bnew;
A.makeCompressed();
}
void physicalPointOnFace(
double u, double v,
const Vector3d& x2, const Vector3d& y2, const Vector3d& z2,
const Vector3d& x3, const Vector3d& y3, const Vector3d& z3,
double& px, double& py, double& pz)
{
const double w = 1.0 - u - v;
const double u2 = x2(0) * u + x2(1) * v + x2(2) * w;
const double v2 = y2(0) * u + y2(1) * v + y2(2) * w;
const double w2 = z2(0) * u + z2(1) * v + z2(2) * w;
px = x3(0) * u2 + x3(1) * v2 + x3(2) * w2;
py = y3(0) * u2 + y3(1) * v2 + y3(2) * w2;
pz = z3(0) * u2 + z3(1) * v2 + z3(2) * w2;
}
void assembleSBCFace(
Mesh_3D* mesh,
MaterialLib* matLib,
@ -36,8 +278,10 @@ void assembleSBCFace(
BF& bfN,
bool isInc,
const Vector3cd& Einc,
int triIdx)
int triIdx,
int elementOrder)
{
const int nBfFace = (elementOrder == 2) ? 8 : 3;
const int domain = mesh->GetDomainOfTri(triIdx);
Vector3i conn;
@ -55,10 +299,44 @@ void assembleSBCFace(
Vector3d x2, y2, z2;
Vector3d x3, y3, z3;
int bfIndex[3];
int mappingIndex[3];
std::vector<int> bfIndex(nBfFace);
std::vector<int> mappingIndex(nBfFace);
if (elementOrder == 2)
{
if (numFace == 1)
{
x2 << 1, 0, 0; y2 << 0, 1, 0; z2 << 0, 0, 1;
x3 << xv[0], xv[1], xv[2]; y3 << yv[0], yv[1], yv[2]; z3 << zv[0], zv[1], zv[2];
}
else if (numFace == 2)
{
x2 << 1, 0, 0; y2 << 0, 1, 0; z2 << 0, 0, 0;
x3 << xv[0], xv[1], xv[3]; y3 << yv[0], yv[1], yv[3]; z3 << zv[0], zv[1], zv[3];
}
else if (numFace == 3)
{
x2 << 1, 0, 0; y2 << 0, 0, 0; z2 << 0, 1, 0;
x3 << xv[0], xv[2], xv[3]; y3 << yv[0], yv[2], yv[3]; z3 << zv[0], zv[2], zv[3];
}
else if (numFace == 4)
{
x2 << 0, 0, 0; y2 << 1, 0, 0; z2 << 0, 1, 0;
x3 << xv[1], xv[2], xv[3]; y3 << yv[1], yv[2], yv[3]; z3 << zv[1], zv[2], zv[3];
}
else
return;
int bfIdx[8], mapIdx[8];
Nedelec3D::sbcFaceSecondOrderBfIndex(numFace, bfIdx);
Nedelec3D::buildSbcSecondOrderDofMap(mesh, numTet, numFace, mapIdx);
for (int i = 0; i < 8; i++)
{
bfIndex[static_cast<size_t>(i)] = bfIdx[i];
mappingIndex[static_cast<size_t>(i)] = mapIdx[i];
}
}
else if (numFace == 1)
{
x2 << 1, 0, 0; y2 << 0, 1, 0; z2 << 0, 0, 1;
x3 << xv[0], xv[1], xv[2]; y3 << yv[0], yv[1], yv[2]; z3 << zv[0], zv[1], zv[2];
@ -115,20 +393,25 @@ void assembleSBCFace(
if (heron < 0.0) heron = 0.0;
const double integCoe = 0.25 * std::sqrt(heron);
Vector3d normal;
mesh->GetNormOfFace(domain, normal);
Vector3d meshNorm;
mesh->GetNormOfFace(domain, meshNorm);
const Vector3d faceP0(x3(0), y3(0), z3(0));
const Vector3d faceP1(x3(1), y3(1), z3(1));
const Vector3d faceP2(x3(2), y3(2), z3(2));
const Vector3d normal = OpticsFEM::computeScatterSBCNormal(
isInc, domain, xv, yv, faceP0, faceP1, faceP2, &meshNorm);
Vector3d E[4][3];
std::vector<std::vector<Vector3d>> E(static_cast<size_t>(nbrGP), std::vector<Vector3d>(nBfFace));
for (int gp = 0; gp < nbrGP; gp++)
{
const double wgp = 1.0 - u[gp] - v[gp];
const double u2 = x2(0) * u[gp] + x2(1) * v[gp] + x2(2) * wgp;
const double v2 = y2(0) * u[gp] + y2(1) * v[gp] + y2(2) * wgp;
const double w2 = z2(0) * u[gp] + z2(1) * v[gp] + z2(2) * wgp;
for (int j = 0; j < 3; j++)
for (int j = 0; j < nBfFace; j++)
{
bfN.GetValueBF(bfIndex[j], u2, v2, w2, E[gp][j]);
E[gp][j] = InvJac * E[gp][j];
bfN.GetValueBF(bfIndex[static_cast<size_t>(j)], u2, v2, w2, E[static_cast<size_t>(gp)][static_cast<size_t>(j)]);
E[static_cast<size_t>(gp)][static_cast<size_t>(j)] = InvJac * E[static_cast<size_t>(gp)][static_cast<size_t>(j)];
}
}
@ -137,41 +420,43 @@ void assembleSBCFace(
const std::complex<double> nn = std::sqrt(std::complex<double>(eps, 0.0));
const std::complex<double> iUnit(0.0, 1.0);
Matrix3cd Ae = Matrix3cd::Zero();
for (int i = 0; i < 3; i++)
MatrixXcd Ae = MatrixXcd::Zero(nBfFace, nBfFace);
for (int i = 0; i < nBfFace; i++)
{
for (int j = 0; j < 3; j++)
for (int j = 0; j < nBfFace; j++)
{
for (int gp = 0; gp < nbrGP; gp++)
{
const Vector3d tEj = crossNormal(normal, E[gp][j]);
const Vector3d tEj = crossNormal(normal, E[static_cast<size_t>(gp)][static_cast<size_t>(j)]);
Ae(i, j) += iUnit * k0 * nn * integCoe * wght[gp]
* E[gp][i].dot(tEj) * 2.0;
* E[static_cast<size_t>(gp)][static_cast<size_t>(i)].dot(tEj) * 2.0;
}
}
}
std::complex<double> Be[3] = { 0.0, 0.0, 0.0 };
std::vector<std::complex<double>> Be(static_cast<size_t>(nBfFace), 0.0);
if (isInc)
{
const Vector3d EincReal(Einc.real()(0), Einc.real()(1), Einc.real()(2));
const Vector3d tEinc = crossNormal(normal, EincReal);
for (int i = 0; i < 3; i++)
const Vector3cd tEinc = crossNormal(normal, Einc);
for (int i = 0; i < nBfFace; i++)
{
for (int gp = 0; gp < nbrGP; gp++)
{
Be[i] -= iUnit * k0 * nn * 2.0 * integCoe * wght[gp]
* E[gp][i].dot(tEinc) * 2.0;
const std::complex<double> dotE =
E[static_cast<size_t>(gp)][static_cast<size_t>(i)](0) * tEinc(0)
+ E[static_cast<size_t>(gp)][static_cast<size_t>(i)](1) * tEinc(1)
+ E[static_cast<size_t>(gp)][static_cast<size_t>(i)](2) * tEinc(2);
Be[static_cast<size_t>(i)] -= iUnit * k0 * nn * 2.0 * integCoe * wght[gp] * dotE * 2.0;
}
}
}
for (int i = 0; i < 3; i++)
for (int i = 0; i < nBfFace; i++)
{
for (int j = 0; j < 3; j++)
tripleA.emplace_back(mappingIndex[i], mappingIndex[j], Ae(i, j));
for (int j = 0; j < nBfFace; j++)
tripleA.emplace_back(mappingIndex[static_cast<size_t>(i)], mappingIndex[static_cast<size_t>(j)], Ae(i, j));
if (isInc)
B(mappingIndex[i]) += Be[i];
B(mappingIndex[static_cast<size_t>(i)]) += Be[static_cast<size_t>(i)];
}
}
@ -186,8 +471,12 @@ void assembleSBCByType(
bool isInc,
const Vector3cd& Einc)
{
const int elementOrder = phy->GetElementOrder();
const int gaussTriOrder = Nedelec3D::gaussOrderTri(elementOrder);
const int bfOrder = Nedelec3D::bfOrderParam(elementOrder);
Gauss gauss;
const int nbrGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, BF_LINEFUNC * 2);
const int nbrGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, gaussTriOrder);
double* u = new double[nbrGP];
double* v = new double[nbrGP];
double* w = new double[nbrGP];
@ -195,7 +484,7 @@ void assembleSBCByType(
gauss.GetGaussPoints(TWODIM, TRIANGLE, u, v, w, wght);
BF bfN;
bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, BF_LINEFUNC);
bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder);
const int nbrSBC = phy->GetNbrSBC();
for (int s = 0; s < nbrSBC; s++)
{
@ -207,7 +496,7 @@ void assembleSBCByType(
for (int n = 0; n < triIndices.size(); n++)
{
assembleSBCFace(mesh, matLib, tripleA, B, k0, u, v, wght, nbrGP, bfN,
isInc, Einc, triIndices(n));
isInc, Einc, triIndices(n), elementOrder);
}
}
@ -217,12 +506,120 @@ void assembleSBCByType(
delete[] wght;
}
void assembleRobinOnDomains(
Mesh_3D* mesh,
MaterialLib* matLib,
std::vector<Triplet<std::complex<double>>>& tripleA,
VectorXcd& B,
double k0,
const Eigen::VectorXi& domains)
{
Gauss gauss;
const int nbrGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, BF_LINEFUNC * 2);
double* u = new double[nbrGP];
double* v = new double[nbrGP];
double* w = new double[nbrGP];
double* wght = new double[nbrGP];
gauss.GetGaussPoints(TWODIM, TRIANGLE, u, v, w, wght);
BF bfN;
bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, BF_LINEFUNC);
for (int d = 0; d < domains.rows(); d++)
{
VectorXi triIndices;
mesh->GetTriIndicesOfDomain(domains(d), triIndices);
for (int n = 0; n < triIndices.size(); n++)
{
assembleSBCFace(mesh, matLib, tripleA, B, k0, u, v, wght, nbrGP, bfN,
false, Vector3cd::Zero(), triIndices(n), 1);
}
}
delete[] u;
delete[] v;
delete[] w;
delete[] wght;
}
void collectBoundaryEdgesOnDomains(
Mesh_3D* mesh,
const Eigen::VectorXi& domains,
std::vector<int>& edgeIds)
{
for (int i = 0; i < domains.rows(); i++)
{
Eigen::VectorXi triIndices;
mesh->GetTriIndicesOfDomain(domains(i), triIndices);
for (int t = 0; t < triIndices.size(); t++)
{
int faceEdges[3];
if (!getFaceEdgeIndices(mesh, triIndices(t), faceEdges))
continue;
for (int n = 0; n < 3; n++)
edgeIds.push_back(faceEdges[n]);
}
}
std::sort(edgeIds.begin(), edgeIds.end());
edgeIds.erase(std::unique(edgeIds.begin(), edgeIds.end()), edgeIds.end());
}
void collectBoundaryFacesOnDomains(
Mesh_3D* mesh,
const Eigen::VectorXi& domains,
std::vector<int>& faceIds)
{
for (int i = 0; i < domains.rows(); i++)
{
Eigen::VectorXi triIndices;
mesh->GetTriIndicesOfDomain(domains(i), triIndices);
for (int t = 0; t < triIndices.size(); t++)
{
Eigen::Vector3i conn;
mesh->GetCoonOfTri(triIndices(t), conn);
faceIds.push_back(mesh->GetFaceOfTet(conn(0), conn(1)));
}
}
std::sort(faceIds.begin(), faceIds.end());
faceIds.erase(std::unique(faceIds.begin(), faceIds.end()), faceIds.end());
}
void collectPecDofIndices(
Mesh_3D* mesh,
const Eigen::VectorXi& pecDomains,
int elementOrder,
std::vector<int>& dofIndices)
{
std::vector<int> pecEdges;
collectBoundaryEdgesOnDomains(mesh, pecDomains, pecEdges);
const int nE = mesh->GetNbrEdge();
const int nF = mesh->GetNbrFace();
for (int edgeId : pecEdges)
{
dofIndices.push_back(Nedelec3D::edgeGlobalDof(edgeId, 0, nE));
if (elementOrder == 2)
dofIndices.push_back(Nedelec3D::edgeGlobalDof(edgeId, 1, nE));
}
if (elementOrder == 2)
{
std::vector<int> pecFaces;
collectBoundaryFacesOnDomains(mesh, pecDomains, pecFaces);
for (int faceId : pecFaces)
{
dofIndices.push_back(Nedelec3D::faceGlobalDof(faceId, 0, nE, nF));
dofIndices.push_back(Nedelec3D::faceGlobalDof(faceId, 1, nE, nF));
}
}
std::sort(dofIndices.begin(), dofIndices.end());
dofIndices.erase(std::unique(dofIndices.begin(), dofIndices.end()), dofIndices.end());
}
} // namespace
void OpticsFEM_3D_Scatter::Assemble_SBC()
{
const double k0 = 2.0 * Pi / _mSolver->GetLda0();
// SBCType=0: γ=ik₀q=0 → 只修正 A
assembleSBCByType(_mMesh, _mMatLib, _mPhy, _mTripleA_complex, _mB_complex,
k0, 0, false, Vector3cd::Zero());
@ -233,9 +630,13 @@ void OpticsFEM_3D_Scatter::Assemble_SBC()
continue;
std::string sx, sy, sz;
_mPhy->GetEinc(s, sx, sy, sz);
Einc(0) = std::complex<double>(std::stod(sx), 0.0);
Einc(1) = std::complex<double>(std::stod(sy), 0.0);
Einc(2) = std::complex<double>(std::stod(sz), 0.0);
mup::ParserX parser(mup::pckALL_COMPLEX);
parser.EnableAutoCreateVar(true);
mup::Value xx(0.0), yy(0.0), zz(0.0);
parser.DefineVar(_T("x"), mup::Variable(&xx));
parser.DefineVar(_T("y"), mup::Variable(&yy));
parser.DefineVar(_T("z"), mup::Variable(&zz));
Einc = parseEincConstant(parser, xx, yy, zz, sx, sy, sz);
break;
}
assembleSBCByType(_mMesh, _mMatLib, _mPhy, _mTripleA_complex, _mB_complex,
@ -244,4 +645,158 @@ void OpticsFEM_3D_Scatter::Assemble_SBC()
void OpticsFEM_3D_Scatter::Assemble_PEC_ELE()
{
const int elementOrder = _mPhy->GetElementOrder();
const int nbrPEC = _mPhy->GetNbrPEC();
const int nbrELE = _mPhy->GetNbrElE();
if (nbrPEC == 0 && nbrELE == 0)
return;
if (nbrELE > 0 && elementOrder == 2)
{
std::cerr << "[OpticsFEM] ElementOrder=2: ELE uses edge DOFs (×2) + zero face DOFs "
<< "(midpoint tangential projection)." << std::endl;
}
std::vector<int> pecDofs;
if (nbrPEC > 0)
{
Eigen::VectorXi PECDomain = Eigen::VectorXi::Zero(nbrPEC);
for (int i = 0; i < nbrPEC; i++)
PECDomain(i) = _mPhy->GetPECDomain(i) + 1;
collectPecDofIndices(_mMesh, PECDomain, elementOrder, pecDofs);
}
Eigen::VectorXi triIndexOfELE, triNumOfELE;
if (nbrELE > 0)
{
Eigen::VectorXi ELEDomain = Eigen::VectorXi::Zero(nbrELE);
Eigen::VectorXi indexNum = Eigen::VectorXi::Zero(nbrELE);
for (int i = 0; i < nbrELE; i++)
{
ELEDomain(i) = _mPhy->GetELEDomain(i);
indexNum(i) = i;
}
_mMesh->GetTriIndexOfDomain2(ELEDomain, indexNum, triIndexOfELE, triNumOfELE);
}
const int dofsPerEleFace = (elementOrder == 2) ? 8 : 3;
const int nbrELEEdges = (nbrELE > 0) ? static_cast<int>(triIndexOfELE.size()) * dofsPerEleFace : 0;
const int nbrPECDofs = (nbrPEC > 0) ? static_cast<int>(pecDofs.size()) : 0;
const int nbrBC = nbrPECDofs + nbrELEEdges;
if (nbrBC == 0)
return;
Eigen::VectorXi BCIndex = Eigen::VectorXi::Zero(nbrBC);
Eigen::VectorXcd BCValue = Eigen::VectorXcd::Zero(nbrBC);
mup::ParserX parser(mup::pckALL_COMPLEX);
parser.EnableAutoCreateVar(true);
mup::Value xx, yy, zz;
parser.DefineVar(_T("x"), mup::Variable(&xx));
parser.DefineVar(_T("y"), mup::Variable(&yy));
parser.DefineVar(_T("z"), mup::Variable(&zz));
int bcOffset = 0;
for (int m = 0; m < triIndexOfELE.size(); m++)
{
int faceEdges[3];
if (!getFaceEdgeIndices(_mMesh, triIndexOfELE(m), faceEdges))
continue;
std::string E0x, E0y, E0z;
_mPhy->GetE0(E0x, E0y, E0z, triNumOfELE(m));
std::string ExFunc = E0x, EyFunc = E0y, EzFunc = E0z;
if (_mPhy->GetNbrBELE() > 0)
{
std::string Ebx, Eby, Ebz;
_mPhy->GetEb(Ebx, Eby, Ebz);
ExFunc = E0x + "-" + Ebx;
EyFunc = E0y + "-" + Eby;
EzFunc = E0z + "-" + Ebz;
}
const int nE = _mMesh->GetNbrEdge();
if (elementOrder == 2)
{
Eigen::Vector3i conn;
_mMesh->GetCoonOfTri(triIndexOfELE(m), conn);
int mapIdx[8];
Nedelec3D::buildSbcSecondOrderDofMap(_mMesh, conn(0), conn(1) + 1, mapIdx);
for (int n = 0; n < 3; n++)
{
Vector3d midpoint, edgeVec;
getEdgeGeometry(_mMesh, faceEdges[n], midpoint, edgeVec);
xx = midpoint(0);
yy = midpoint(1);
zz = midpoint(2);
const Vector3cd E0 = evalE0AtPoint(parser, xx, yy, zz, ExFunc, EyFunc, EzFunc,
midpoint(0), midpoint(1), midpoint(2));
const std::complex<double> edgeVal = E0.dot(edgeVec);
BCIndex(bcOffset + n) = mapIdx[n];
BCValue(bcOffset + n) = edgeVal;
BCIndex(bcOffset + 3 + n) = mapIdx[3 + n];
BCValue(bcOffset + 3 + n) = edgeVal;
}
BCIndex(bcOffset + 6) = mapIdx[6];
BCValue(bcOffset + 6) = 0.0;
BCIndex(bcOffset + 7) = mapIdx[7];
BCValue(bcOffset + 7) = 0.0;
bcOffset += 8;
(void)nE;
}
else
{
for (int n = 0; n < 3; n++)
{
Vector3d midpoint, edgeVec;
getEdgeGeometry(_mMesh, faceEdges[n], midpoint, edgeVec);
xx = midpoint(0);
yy = midpoint(1);
zz = midpoint(2);
const Vector3cd E0 = evalE0AtPoint(parser, xx, yy, zz, ExFunc, EyFunc, EzFunc,
midpoint(0), midpoint(1), midpoint(2));
BCIndex(bcOffset + n) = faceEdges[n];
BCValue(bcOffset + n) = E0.dot(edgeVec);
}
bcOffset += 3;
}
}
for (int i = 0; i < nbrPECDofs; i++)
{
BCIndex(bcOffset + i) = pecDofs[static_cast<size_t>(i)];
BCValue(bcOffset + i) = 0.0;
}
Eigen::VectorXi tempIndex = Eigen::VectorXi::LinSpaced(nbrBC, 0, nbrBC - 1);
QuickSort(BCIndex, tempIndex, 0, nbrBC - 1);
Unique(BCIndex, tempIndex);
Eigen::VectorXcd tempValue = Eigen::VectorXcd::Zero(BCIndex.rows());
for (int i = 0; i < BCIndex.rows(); i++)
tempValue(i) = BCValue(tempIndex(i));
BCValue = tempValue;
// Port + PEC-only: match MATLAB FemMatrixAssembly (eliminate PEC DOFs before solve).
// Do NOT use applyDirichletBC here — it breaks the augmented port system.
if (!_mIsReal && _mPhy->HasNumericPort() && nbrELE == 0 && nbrPECDofs > 0)
{
eliminateDirichletDofsComplex(_mA_complex, _mB_complex, BCIndex, _mFreeDofIndices);
return;
}
if (_mIsReal)
applyDirichletBCReal(_mA_real, _mB_real, BCIndex, BCValue);
else
applyDirichletBCComplex(_mA_complex, _mB_complex, BCIndex, BCValue);
}
void OpticsFEM_3D_Scatter::Assemble_PBC()
{
if (_mPhy->GetNbrPBCGroups() <= 0)
return;
const int elementOrder = _mPhy->GetElementOrder();
const int dof = Nedelec3D::globalDofCount(_mMesh, elementOrder);
assemblePbcProjectionMatrix(_mMesh, _mPhy, elementOrder, dof, _mIsReal, _mP_real, _mP_complex);
}

View File

@ -0,0 +1,311 @@
#include "Assemble_Base.h"
#include "../function/BF.h"
#include "../function/Gauss.h"
#include "../common/define.h"
#include "../material/Material_Base.h"
#include "../phy/PortModeData.h"
#include <cmath>
#include <complex>
#include <iostream>
#include <vector>
using namespace Eigen;
namespace {
Vector3cd crossMuN(const Vector3d& n, const Vector3cd& a)
{
return n.cross(a);
}
// MATLAB PortBCMatrixAssembly: sum(Alphaj .* temp) — no conjugate on either factor.
std::complex<double> matlabSumProduct(const Vector3d& aReal, const Vector3cd& b)
{
return std::complex<double>(aReal(0), 0.0) * b(0)
+ std::complex<double>(aReal(1), 0.0) * b(1)
+ std::complex<double>(aReal(2), 0.0) * b(2);
}
void getTetFaceData(
Mesh_3D* mesh, int numEle, int numFace,
int faceNode[3], int faceBF[3],
double x[4], double y[4], double z[4], double l[6])
{
for (int i = 0; i < 4; i++)
{
Vector3d v;
mesh->GetVertex(mesh->GetTet(numEle, i), v);
x[i] = v(0); y[i] = v(1); z[i] = v(2);
}
auto edgeLen = [](double x1, double y1, double z1, double x2, double y2, double z2) {
return std::sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2));
};
l[0] = edgeLen(x[0], y[0], z[0], x[1], y[1], z[1]);
l[1] = edgeLen(x[0], y[0], z[0], x[2], y[2], z[2]);
l[2] = edgeLen(x[0], y[0], z[0], x[3], y[3], z[3]);
l[3] = edgeLen(x[1], y[1], z[1], x[2], y[2], z[2]);
l[4] = edgeLen(x[1], y[1], z[1], x[3], y[3], z[3]);
l[5] = edgeLen(x[2], y[2], z[2], x[3], y[3], z[3]);
if (numFace == 1) { faceNode[0] = 0; faceNode[1] = 1; faceNode[2] = 2; faceBF[0] = 0; faceBF[1] = 1; faceBF[2] = 3; }
else if (numFace == 2) { faceNode[0] = 0; faceNode[1] = 1; faceNode[2] = 3; faceBF[0] = 0; faceBF[1] = 2; faceBF[2] = 4; }
else if (numFace == 3) { faceNode[0] = 0; faceNode[1] = 2; faceNode[2] = 3; faceBF[0] = 1; faceBF[1] = 2; faceBF[2] = 5; }
else { faceNode[0] = 1; faceNode[1] = 2; faceNode[2] = 3; faceBF[0] = 3; faceBF[1] = 4; faceBF[2] = 5; }
}
void assembleOnePortFace(
Mesh_3D* mesh, MaterialLib* matLib,
const PortNumericMode& mode, int faceIdx,
VectorXcd& G, VectorXcd& S_or_T, VectorXcd& bEdge,
std::complex<double>& P, std::complex<double>& b1,
bool isInput)
{
const int numEle = mode.facesConn(faceIdx, 0) - 1;
const int numFace = mode.facesConn(faceIdx, 1);
int faceNode[3], faceBF[3];
double x[4], y[4], z[4], l[6];
getTetFaceData(mesh, numEle, numFace, faceNode, faceBF, x, y, z, l);
const int domain = mesh->GetDomainOfTet(numEle);
const std::complex<double> mu = 1.0 / matLib->GetMur(domain)(0, 0);
Matrix3d Jac;
Jac(0, 0) = x[0] - x[3]; Jac(0, 1) = y[0] - y[3]; Jac(0, 2) = z[0] - z[3];
Jac(1, 0) = x[1] - x[3]; Jac(1, 1) = y[1] - y[3]; Jac(1, 2) = z[1] - z[3];
Jac(2, 0) = x[2] - x[3]; Jac(2, 1) = y[2] - y[3]; Jac(2, 2) = z[2] - z[3];
const Matrix3d InvJac = Jac.inverse();
double xx[3], yy[3], zz[3];
for (int i = 0; i < 3; i++)
{
xx[i] = x[faceNode[i]];
yy[i] = y[faceNode[i]];
zz[i] = z[faceNode[i]];
}
Matrix3d fJac = Matrix3d::Zero();
fJac(0, 0) = -xx[0] + xx[1]; fJac(0, 1) = -yy[0] + yy[1];
fJac(1, 0) = -xx[0] + xx[2]; fJac(1, 1) = -yy[0] + yy[2]; fJac(2, 2) = 1.0;
const Matrix3d InvfJac = fJac.inverse();
Matrix3d fJacS = Matrix3d::Zero();
fJacS(0, 0) = InvfJac(1, 1); fJacS(0, 1) = -InvfJac(1, 0);
fJacS(1, 0) = -InvfJac(0, 1); fJacS(1, 1) = InvfJac(0, 0); fJacS(2, 2) = 1.0;
const Matrix3d fTJac = fJac.transpose() / fJac.determinant();
const double fDetJac = std::abs(fJac.determinant());
Gauss gauss;
BF bfEt, bfEz, bfEdge, bfCurlEt, bfCurlEz;
bfEt.GetNbrBF(TWODIM, TRIANGLE, BF_NEDELEC, BF_LINEFUNC);
bfEz.GetNbrBF(TWODIM, TRIANGLE, BF_LAGRANGE, BF_LINEFUNC);
bfEdge.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, BF_LINEFUNC);
bfCurlEt.GetNbrBF(TWODIM, TRIANGLE, BF_CURL_NEDELEC, BF_LINEFUNC);
bfCurlEz.GetNbrBF(TWODIM, TRIANGLE, BF_CURL_LAGRANGE, BF_LINEFUNC);
const int nGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, BF_LINEFUNC * 2);
std::vector<double> u(nGP), v(nGP), w(nGP), wght(nGP);
gauss.GetGaussPoints(TWODIM, TRIANGLE, u.data(), v.data(), w.data(), wght.data());
Matrix2d fJac2;
fJac2(0, 0) = -xx[0] + xx[1]; fJac2(0, 1) = -yy[0] + yy[1];
fJac2(1, 0) = -xx[0] + xx[2]; fJac2(1, 1) = -yy[0] + yy[2];
const Matrix3d vJac = Jac.inverse();
for (int gp = 0; gp < nGP; gp++)
{
Vector3cd fE = Vector3cd::Zero();
Vector3cd fcurlE = Vector3cd::Zero();
for (int i = 0; i < 3; i++)
{
Vector3d tempEt, tempGradEz, tempCurlEt, tempEz;
bfEt.GetValueBF(i + 1, u[gp], v[gp], 0.0, tempEt);
Vector3d et = InvfJac * tempEt * l[faceBF[i]];
bfEz.GetValueBF(i + 1, u[gp], v[gp], 0.0, tempEz);
Vector3d ezVec(0, 0, tempEz(2));
bfCurlEt.GetValueBF(i + 1, u[gp], v[gp], 0.0, tempCurlEt);
Vector3d curlEtVec = fTJac * Vector3d(0, 0, tempCurlEt(2)) * l[faceBF[i]];
bfCurlEz.GetValueBF(i + 1, u[gp], v[gp], 0.0, tempGradEz);
Vector3d curlEzVec = fJacS * Vector3d(tempGradEz(0), tempGradEz(1), 0.0);
const int mapEt = mode.portEdgeOfFace(faceIdx, i);
const int mapEz = mode.portNewFaces(faceIdx, i);
fE += et.cast<std::complex<double>>() * mode.Et(mapEt)
+ ezVec.cast<std::complex<double>>() * mode.Ez(mapEz);
fcurlE += curlEtVec.cast<std::complex<double>>() * mode.Et(mapEt)
+ curlEzVec.cast<std::complex<double>>() * mode.Ez(mapEz);
}
Vector3cd fN0, fcurlN0, fN1, fcurlN1, fN2, fcurlN2, fW;
if (isInput)
{
fN0 = fE * mode.powerCoef;
fcurlN0 = (fcurlE + mode.gamma * Vector3cd(fE(1), -fE(0), 0.0)) * mode.powerCoef;
fN1 = fE;
fcurlN1 = fcurlE - mode.gamma * Vector3cd(fE(1), -fE(0), 0.0);
}
else
{
fN2 = fE;
fcurlN2 = fcurlE + mode.gamma * Vector3cd(fE(1), -fE(0), 0.0);
}
fW = fE;
Vector3d alphaPhys[3];
for (int i = 0; i < 3; i++)
{
// Match MATLAB: temp = [u,v]*fJac2 + [xx(1),yy(1),zz(1)]
Vector3d refPt(
fJac2(0, 0) * u[gp] + fJac2(1, 0) * v[gp] + xx[0],
fJac2(0, 1) * u[gp] + fJac2(1, 1) * v[gp] + yy[0],
zz[0]);
// Match MATLAB: temp = [u,v]*fJac2 + xyz; ref3 = temp*vJac - x4*vJac (row-vector multiply)
Vector3d ref3 = vJac.transpose() * refPt - vJac.transpose() * Vector3d(x[3], y[3], z[3]);
Vector3d bf;
bfEdge.GetValueBF(faceBF[i] + 1, ref3(0), ref3(1), ref3(2), bf);
alphaPhys[i] = InvJac * bf * l[faceBF[i]];
}
const int mappingEdge[3] = {
mesh->GetEdgeOfTet(numEle, faceBF[0]),
mesh->GetEdgeOfTet(numEle, faceBF[1]),
mesh->GetEdgeOfTet(numEle, faceBF[2]) };
if (isInput)
{
for (int i = 0; i < 3; i++)
{
G(mappingEdge[i]) += wght[gp] * fDetJac * (fW(0) * alphaPhys[i](0) + fW(1) * alphaPhys[i](1));
const Vector3cd temp = mu * crossMuN(mode.normal, fcurlN1);
S_or_T(mappingEdge[i]) += wght[gp] * fDetJac * matlabSumProduct(alphaPhys[i], temp);
const Vector3cd temp0 = mu * crossMuN(mode.normal, fcurlN0.conjugate());
bEdge(mappingEdge[i]) += wght[gp] * fDetJac * matlabSumProduct(alphaPhys[i], temp0);
}
P += wght[gp] * fDetJac * (fW(0) * fN1(0) + fW(1) * fN1(1));
b1 += wght[gp] * fDetJac * (fW(0) * fN0(0) + fW(1) * fN0(1));
}
else
{
for (int i = 0; i < 3; i++)
{
G(mappingEdge[i]) += wght[gp] * fDetJac * (fW(0) * alphaPhys[i](0) + fW(1) * alphaPhys[i](1));
const Vector3cd temp = mu * crossMuN(mode.normal, fcurlN2);
S_or_T(mappingEdge[i]) += wght[gp] * fDetJac * matlabSumProduct(alphaPhys[i], temp);
}
P += wght[gp] * fDetJac * (fW(0) * fN2(0) + fW(1) * fN2(1));
}
}
}
void blockAppendInputPort(
SparseMatrix<std::complex<double>, RowMajor>& A,
VectorXcd& b,
int edgeDof,
const VectorXcd& S, const VectorXcd& G,
std::complex<double> P, std::complex<double> b1)
{
const int n = static_cast<int>(b.size());
SparseMatrix<std::complex<double>, RowMajor> Aold = A;
A = SparseMatrix<std::complex<double>, RowMajor>(n + 1, n + 1);
std::vector<Triplet<std::complex<double>>> trips;
for (int k = 0; k < Aold.outerSize(); k++)
for (SparseMatrix<std::complex<double>, RowMajor>::InnerIterator it(Aold, k); it; ++it)
trips.emplace_back(it.row(), it.col(), it.value());
for (int i = 0; i < edgeDof; i++)
if (std::abs(S(i)) > 0)
// Port column must match MATLAB PortBC (not Hermitian transpose).
trips.emplace_back(i, n, std::conj(S(i)));
for (int i = 0; i < edgeDof; i++)
if (std::abs(G(i)) > 0)
trips.emplace_back(n, i, G(i));
trips.emplace_back(n, n, -P);
A.setFromTriplets(trips.begin(), trips.end());
VectorXcd bnew(n + 1);
bnew.head(n) = -b;
bnew(n) = b1;
b = bnew;
}
void blockAppendOutputPort(
SparseMatrix<std::complex<double>, RowMajor>& A,
VectorXcd& b,
int edgeDof,
const VectorXcd& T, const VectorXcd& G,
std::complex<double> P)
{
const int n = static_cast<int>(b.size());
SparseMatrix<std::complex<double>, RowMajor> Aold = A;
A = SparseMatrix<std::complex<double>, RowMajor>(n + 1, n + 1);
std::vector<Triplet<std::complex<double>>> trips;
for (int k = 0; k < Aold.outerSize(); k++)
for (SparseMatrix<std::complex<double>, RowMajor>::InnerIterator it(Aold, k); it; ++it)
trips.emplace_back(it.row(), it.col(), it.value());
for (int i = 0; i < edgeDof; i++)
if (std::abs(T(i)) > 0)
trips.emplace_back(i, n, std::conj(T(i)));
for (int i = 0; i < edgeDof; i++)
if (std::abs(G(i)) > 0)
trips.emplace_back(n, i, G(i));
trips.emplace_back(n, n, -P);
A.setFromTriplets(trips.begin(), trips.end());
VectorXcd bnew(n + 1);
bnew.head(n) = b;
bnew(n) = 0.0;
b = bnew;
}
} // namespace
void OpticsFEM_3D_Scatter::Assemble_Port()
{
if (!_mPhy->HasNumericPort())
return;
_mIsReal = false;
const int dof = _mDof;
for (int p = 0; p < _mPhy->GetNbrNumericPort(); p++)
{
const PortNumericMode& mode = _mPhy->GetNumericPort(p);
const bool isInput = (mode.type == 1);
const int nf = mode.facesConn.rows();
VectorXcd G = VectorXcd::Zero(dof);
VectorXcd S_or_T = VectorXcd::Zero(dof);
VectorXcd bEdge = VectorXcd::Zero(dof);
std::complex<double> P(0.0, 0.0);
std::complex<double> b1(0.0, 0.0);
for (int f = 0; f < nf; f++)
assembleOnePortFace(_mMesh, _mMatLib, mode, f, G, S_or_T, bEdge, P, b1, isInput);
if (isInput)
{
_mB_complex += bEdge;
std::cout << "[OpticsFEM_3D_Scatter] Port input"
<< " |S|=" << S_or_T.norm()
<< " |G|=" << G.norm()
<< " |bEdge|=" << bEdge.norm()
<< " |P|=" << std::abs(P)
<< " b1=" << b1 << std::endl;
blockAppendInputPort(_mA_complex, _mB_complex, dof, S_or_T, G, P, b1);
}
else
{
std::cout << "[OpticsFEM_3D_Scatter] Port output"
<< " |T|=" << S_or_T.norm()
<< " |G|=" << G.norm()
<< " |P|=" << std::abs(P) << std::endl;
blockAppendOutputPort(_mA_complex, _mB_complex, dof, S_or_T, G, P);
}
}
_mPortExtraDof = static_cast<int>(_mB_complex.size()) - dof;
const int sysSize = static_cast<int>(_mB_complex.size());
if (_mP_complex.rows() < sysSize)
{
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor> Pnew(sysSize, sysSize);
Pnew.setIdentity();
_mP_complex = Pnew;
}
}

View File

@ -0,0 +1,808 @@
#include"Assemble_Base.h"
#include"Nedelec3D_Util.h"
#include"../function/BF.h"
#include"../function/Gauss.h"
#include"../common/define.h"
#include"../material/Material_Base.h"
#include"../phy/Phy_Base.h"
#include"../parser/mpParser.h"
#include"SBC_NormalUtil.h"
#include<algorithm>
#include<cmath>
#include<complex>
#include<string>
#include<vector>
using namespace Eigen;
namespace {
Vector3cd crossNormal(const Vector3d& normal, const Vector3cd& a)
{
return normal.cross(a.cross(normal));
}
Vector3cd evalVec3At(
mup::ParserX& parser,
mup::Value& xx, mup::Value& yy, mup::Value& zz,
const std::string& sx, const std::string& sy, const std::string& sz,
double x, double y, double z)
{
xx = x;
yy = y;
zz = z;
// Use string_type explicitly; _T(c_str) is not reliable for dynamic strings.
parser.SetExpr(mup::string_type(sx.begin(), sx.end()));
mup::Value vx = parser.Eval();
parser.SetExpr(mup::string_type(sy.begin(), sy.end()));
mup::Value vy = parser.Eval();
parser.SetExpr(mup::string_type(sz.begin(), sz.end()));
mup::Value vz = parser.Eval();
return Vector3cd(
std::complex<double>(vx.GetFloat(), vx.GetImag()),
std::complex<double>(vy.GetFloat(), vy.GetImag()),
std::complex<double>(vz.GetFloat(), vz.GetImag()));
}
bool setupFaceIntegration(
Mesh_3D* mesh,
int triIdx,
Matrix3d& invJac,
double& integCoe,
Vector3d& normal,
int mappingIndex[3],
int bfIndex[3],
Vector3d& x2, Vector3d& y2, Vector3d& z2,
Vector3d& x3, Vector3d& y3, Vector3d& z3,
double xv[4], double yv[4], double zv[4])
{
Vector3i conn;
mesh->GetCoonOfTri(triIdx, conn);
const int numTet = conn(0);
const int numFace = conn(1) + 1;
for (int i = 0; i < 4; i++)
{
Vector3d vtx;
mesh->GetVertex(mesh->GetTet(numTet, i), vtx);
xv[i] = vtx(0); yv[i] = vtx(1); zv[i] = vtx(2);
}
if (numFace == 1)
{
x2 << 1, 0, 0; y2 << 0, 1, 0; z2 << 0, 0, 1;
x3 << xv[0], xv[1], xv[2]; y3 << yv[0], yv[1], yv[2]; z3 << zv[0], zv[1], zv[2];
bfIndex[0] = 1; bfIndex[1] = 2; bfIndex[2] = 4;
mappingIndex[0] = mesh->GetEdgeOfTet(numTet, 0);
mappingIndex[1] = mesh->GetEdgeOfTet(numTet, 1);
mappingIndex[2] = mesh->GetEdgeOfTet(numTet, 3);
}
else if (numFace == 2)
{
x2 << 1, 0, 0; y2 << 0, 1, 0; z2 << 0, 0, 0;
x3 << xv[0], xv[1], xv[3]; y3 << yv[0], yv[1], yv[3]; z3 << zv[0], zv[1], zv[3];
bfIndex[0] = 1; bfIndex[1] = 3; bfIndex[2] = 5;
mappingIndex[0] = mesh->GetEdgeOfTet(numTet, 0);
mappingIndex[1] = mesh->GetEdgeOfTet(numTet, 2);
mappingIndex[2] = mesh->GetEdgeOfTet(numTet, 4);
}
else if (numFace == 3)
{
x2 << 1, 0, 0; y2 << 0, 0, 0; z2 << 0, 1, 0;
x3 << xv[0], xv[2], xv[3]; y3 << yv[0], yv[2], yv[3]; z3 << zv[0], zv[2], zv[3];
bfIndex[0] = 2; bfIndex[1] = 3; bfIndex[2] = 6;
mappingIndex[0] = mesh->GetEdgeOfTet(numTet, 1);
mappingIndex[1] = mesh->GetEdgeOfTet(numTet, 2);
mappingIndex[2] = mesh->GetEdgeOfTet(numTet, 5);
}
else if (numFace == 4)
{
x2 << 0, 0, 0; y2 << 1, 0, 0; z2 << 0, 1, 0;
x3 << xv[1], xv[2], xv[3]; y3 << yv[1], yv[2], yv[3]; z3 << zv[1], zv[2], zv[3];
bfIndex[0] = 4; bfIndex[1] = 5; bfIndex[2] = 6;
mappingIndex[0] = mesh->GetEdgeOfTet(numTet, 3);
mappingIndex[1] = mesh->GetEdgeOfTet(numTet, 4);
mappingIndex[2] = mesh->GetEdgeOfTet(numTet, 5);
}
else
{
return false;
}
Matrix3d Jac;
Jac(0, 0) = xv[0] - xv[3]; Jac(0, 1) = yv[0] - yv[3]; Jac(0, 2) = zv[0] - zv[3];
Jac(1, 0) = xv[1] - xv[3]; Jac(1, 1) = yv[1] - yv[3]; Jac(1, 2) = zv[1] - zv[3];
Jac(2, 0) = xv[2] - xv[3]; Jac(2, 1) = yv[2] - yv[3]; Jac(2, 2) = zv[2] - zv[3];
const double detJ = Jac.determinant();
if (std::abs(detJ) < 1e-30)
return false;
invJac = Jac.inverse();
const double a = std::sqrt((x3(0) - x3(1)) * (x3(0) - x3(1)) + (y3(0) - y3(1)) * (y3(0) - y3(1)) + (z3(0) - z3(1)) * (z3(0) - z3(1)));
const double b = std::sqrt((x3(0) - x3(2)) * (x3(0) - x3(2)) + (y3(0) - y3(2)) * (y3(0) - y3(2)) + (z3(0) - z3(2)) * (z3(0) - z3(2)));
const double c = std::sqrt((x3(1) - x3(2)) * (x3(1) - x3(2)) + (y3(1) - y3(2)) * (y3(1) - y3(2)) + (z3(1) - z3(2)) * (z3(1) - z3(2)));
double heron = (a + b + c) * (a + b - c) * (a - b + c) * (b + c - a);
if (heron < 0.0) heron = 0.0;
integCoe = 0.25 * std::sqrt(heron);
mesh->GetNormOfFace(mesh->GetDomainOfTri(triIdx), normal);
if (normal.norm() < 1e-30)
{
Vector3d e1 = Vector3d(x3(1) - x3(0), y3(1) - y3(0), z3(1) - z3(0));
Vector3d e2 = Vector3d(x3(2) - x3(0), y3(2) - y3(0), z3(2) - z3(0));
normal = e1.cross(e2);
if (normal.norm() > 1e-30)
normal.normalize();
}
return true;
}
void physicalPointOnFace(
double u, double v,
const Vector3d& x2, const Vector3d& y2, const Vector3d& z2,
const Vector3d& x3, const Vector3d& y3, const Vector3d& z3,
double& px, double& py, double& pz)
{
// Triangle barycentric (u, v, 1-u-v) on the three face vertices stored in x3/y3/z3.
// Do NOT route through tet ref coords (x2,y2,z2): that only matches face 1 and
// yields wrong z (and breaks z-dependent sources like SCD J0) on faces 2/3/4.
(void)x2; (void)y2; (void)z2;
const double w = 1.0 - u - v;
px = x3(0) * u + x3(1) * v + x3(2) * w;
py = y3(0) * u + y3(1) * v + y3(2) * w;
pz = z3(0) * u + z3(1) * v + z3(2) * w;
}
bool tetContainsVertex(Mesh_3D* mesh, int tetIdx, int vertexId)
{
for (int j = 0; j < 4; j++)
{
if (mesh->GetTet(tetIdx, j) == vertexId)
return true;
}
return false;
}
// IBP 边界闭合b -= ∮ N·(n×curl E_b) dSPML 面或 SBC 外表面,不是 Robin 的 q
void accumulateBeleFaceIbp(
Mesh_3D* mesh,
VectorXcd& B,
mup::ParserX& parser,
mup::Value& xx, mup::Value& yy, mup::Value& zz,
const std::string& curlEbx, const std::string& curlEby, const std::string& curlEbz,
int triIdx,
const double* uFace, const double* vFace, const double* wghtFace, int nbrGPFace,
BF& bfN,
bool useScatterSbcNormal,
int elementOrder)
{
Matrix3d invJac;
double integCoe = 0.0;
Vector3d normal = Vector3d::Zero();
int mappingIndex3[3], bfIndex3[3];
Vector3d x2, y2, z2, x3, y3, z3;
double xv[4], yv[4], zv[4];
if (!setupFaceIntegration(mesh, triIdx, invJac, integCoe, normal,
mappingIndex3, bfIndex3, x2, y2, z2, x3, y3, z3, xv, yv, zv))
return;
int mappingIndex[8], bfIndex[8];
int nLocal = 3;
if (elementOrder == 2)
{
Eigen::Vector3i conn;
mesh->GetCoonOfTri(triIdx, conn);
Nedelec3D::buildSbcSecondOrderDofMap(mesh, conn(0), conn(1) + 1, mappingIndex);
Nedelec3D::sbcFaceSecondOrderBfIndex(conn(1) + 1, bfIndex);
nLocal = 8;
}
else
{
for (int i = 0; i < 3; i++)
{
mappingIndex[i] = mappingIndex3[i];
bfIndex[i] = bfIndex3[i];
}
}
if (useScatterSbcNormal)
{
const int domain = mesh->GetDomainOfTri(triIdx);
Vector3d meshNorm;
mesh->GetNormOfFace(domain, meshNorm);
const Vector3d faceP0(x3(0), y3(0), z3(0));
const Vector3d faceP1(x3(1), y3(1), z3(1));
const Vector3d faceP2(x3(2), y3(2), z3(2));
normal = OpticsFEM::computeScatterSBCNormal(
false, domain, xv, yv, faceP0, faceP1, faceP2, &meshNorm);
}
std::vector<std::vector<Vector3d>> Egp(static_cast<size_t>(nbrGPFace),
std::vector<Vector3d>(static_cast<size_t>(nLocal)));
for (int gp = 0; gp < nbrGPFace; gp++)
{
const double wgp = 1.0 - uFace[gp] - vFace[gp];
const double u2 = x2(0) * uFace[gp] + x2(1) * vFace[gp] + x2(2) * wgp;
const double v2 = y2(0) * uFace[gp] + y2(1) * vFace[gp] + y2(2) * wgp;
const double w2 = z2(0) * uFace[gp] + z2(1) * vFace[gp] + z2(2) * wgp;
for (int j = 0; j < nLocal; j++)
{
bfN.GetValueBF(bfIndex[j], u2, v2, w2, Egp[static_cast<size_t>(gp)][static_cast<size_t>(j)]);
Egp[static_cast<size_t>(gp)][static_cast<size_t>(j)] =
invJac * Egp[static_cast<size_t>(gp)][static_cast<size_t>(j)];
}
}
std::vector<Vector3cd> bcurlE(static_cast<size_t>(nbrGPFace));
for (int gp = 0; gp < nbrGPFace; gp++)
{
double px, py, pz;
physicalPointOnFace(uFace[gp], vFace[gp], x2, y2, z2, x3, y3, z3, px, py, pz);
bcurlE[static_cast<size_t>(gp)] = evalVec3At(parser, xx, yy, zz, curlEbx, curlEby, curlEbz, px, py, pz);
}
VectorXcd bt = VectorXcd::Zero(nLocal);
for (int i = 0; i < nLocal; i++)
{
for (int gp = 0; gp < nbrGPFace; gp++)
{
const Vector3d& Ei = Egp[static_cast<size_t>(gp)][static_cast<size_t>(i)];
const Vector3cd& curlEb = bcurlE[static_cast<size_t>(gp)];
// 2D BELE PML legacy: b -= ∮ N·(n×curl E_b)
const std::complex<double> curlContrib = wghtFace[gp] * integCoe
* (std::complex<double>(Ei(0), 0.0) * (normal(1) * curlEb(2) - normal(2) * curlEb(1))
+ std::complex<double>(Ei(1), 0.0) * (normal(2) * curlEb(0) - normal(0) * curlEb(2))
+ std::complex<double>(Ei(2), 0.0) * (normal(0) * curlEb(1) - normal(1) * curlEb(0)));
bt(i) -= curlContrib;
}
}
for (int i = 0; i < nLocal; i++)
B(mappingIndex[i]) += bt(i);
}
} // namespace
void OpticsFEM_3D_Scatter::Assemble_BELE()
{
const double k0 = 2.0 * Pi / _mSolver->GetLda0();
const int elementOrder = _mPhy->GetElementOrder();
const int bfOrder = Nedelec3D::bfOrderParam(elementOrder);
const int gaussVol = Nedelec3D::gaussOrderVol(elementOrder);
const int gaussTri = Nedelec3D::gaussOrderTri(elementOrder);
Gauss gauss;
const int nbrGPVol = gauss.GetNbrGaussPoints(THREEDIM, TETRAHEDRON, gaussVol);
double* uVol = new double[nbrGPVol];
double* vVol = new double[nbrGPVol];
double* wVol = new double[nbrGPVol];
double* wghtVol = new double[nbrGPVol];
gauss.GetGaussPoints(THREEDIM, TETRAHEDRON, uVol, vVol, wVol, wghtVol);
BF bfN;
const int vdof = bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder);
Vector3d** E = new Vector3d*[nbrGPVol];
for (int i = 0; i < nbrGPVol; i++)
E[i] = new Vector3d[vdof];
mup::ParserX parser(mup::pckALL_COMPLEX);
parser.EnableAutoCreateVar(true);
mup::Value xx, yy, zz;
parser.DefineVar(_T("x"), mup::Variable(&xx));
parser.DefineVar(_T("y"), mup::Variable(&yy));
parser.DefineVar(_T("z"), mup::Variable(&zz));
std::string Ebx, Eby, Ebz, curlcurlEbx, curlcurlEby, curlcurlEbz;
_mPhy->GetEb(Ebx, Eby, Ebz);
_mPhy->GetCurlCurlEb(curlcurlEbx, curlcurlEby, curlcurlEbz);
Eigen::VectorXi beleDomains;
_mPhy->GetBELE(beleDomains);
Vector3d vertex[4];
for (int n = 0; n < _mMesh->GetNbrTet(); n++)
{
const int domain = _mMesh->GetDomainOfTet(n);
if (std::find(beleDomains.begin(), beleDomains.end(), domain) == beleDomains.end())
continue;
for (int i = 0; i < 4; i++)
_mMesh->GetVertex(_mMesh->GetTet(n, i), vertex[i]);
Matrix3d Jac, InvJac;
Jac(0, 0) = vertex[0](0) - vertex[3](0); Jac(0, 1) = vertex[0](1) - vertex[3](1); Jac(0, 2) = vertex[0](2) - vertex[3](2);
Jac(1, 0) = vertex[1](0) - vertex[3](0); Jac(1, 1) = vertex[1](1) - vertex[3](1); Jac(1, 2) = vertex[1](2) - vertex[3](2);
Jac(2, 0) = vertex[2](0) - vertex[3](0); Jac(2, 1) = vertex[2](1) - vertex[3](1); Jac(2, 2) = vertex[2](2) - vertex[3](2);
const double detJac = std::abs(Jac.determinant());
if (detJac < 1e-30)
continue;
InvJac = Jac.inverse();
for (int gp = 0; gp < nbrGPVol; gp++)
{
for (int j = 0; j < vdof; j++)
{
bfN.GetValueBF(j + 1, uVol[gp], vVol[gp], wVol[gp], E[gp][j]);
E[gp][j] = InvJac * E[gp][j];
}
}
Vector3cd* bE = new Vector3cd[nbrGPVol];
Vector3cd* bCurlCurlE = new Vector3cd[nbrGPVol];
for (int gp = 0; gp < nbrGPVol; gp++)
{
const double px = vertex[3](0) + Jac(0, 0) * uVol[gp] + Jac(1, 0) * vVol[gp] + Jac(2, 0) * wVol[gp];
const double py = vertex[3](1) + Jac(0, 1) * uVol[gp] + Jac(1, 1) * vVol[gp] + Jac(2, 1) * wVol[gp];
const double pz = vertex[3](2) + Jac(0, 2) * uVol[gp] + Jac(1, 2) * vVol[gp] + Jac(2, 2) * wVol[gp];
bE[gp] = evalVec3At(parser, xx, yy, zz, Ebx, Eby, Ebz, px, py, pz);
bCurlCurlE[gp] = evalVec3At(parser, xx, yy, zz, curlcurlEbx, curlcurlEby, curlcurlEbz, px, py, pz);
}
Matrix3cd epsr = _mMatLib->GetEpsr(domain);
const double k0sq = k0 * k0;
std::vector<int> mapBuf(static_cast<size_t>(vdof), 0);
Nedelec3D::buildTetDofMap(_mMesh, n, elementOrder, mapBuf.data(), vdof);
for (int i = 0; i < vdof; i++)
{
std::complex<double> contrib = 0.0;
for (int gp = 0; gp < nbrGPVol; gp++)
{
const Vector3cd Jb = bCurlCurlE[gp] - k0sq * (epsr * bE[gp]);
contrib += wghtVol[gp] * detJac * E[gp][i].dot(Jb);
}
_mB_complex(mapBuf[static_cast<size_t>(i)]) -= contrib;
}
delete[] bE;
delete[] bCurlCurlE;
}
const int nbrGPFace = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, gaussTri);
double* uFace = new double[nbrGPFace];
double* vFace = new double[nbrGPFace];
double* wFace = new double[nbrGPFace];
double* wghtFace = new double[nbrGPFace];
gauss.GetGaussPoints(TWODIM, TRIANGLE, uFace, vFace, wFace, wghtFace);
BF bfNFace;
bfNFace.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder);
std::string curlEbx, curlEby, curlEbz;
_mPhy->GetCurlEb(curlEbx, curlEby, curlEbz);
const int nbrPML = _mPhy->GetNbrPML();
for (int p = 0; p < nbrPML; p++)
{
VectorXi triIndices;
_mMesh->GetTriIndicesOfDomain(_mPhy->GetPMLEdge(p) + 1, triIndices);
for (int t = 0; t < triIndices.size(); t++)
{
accumulateBeleFaceIbp(_mMesh, _mB_complex, parser, xx, yy, zz,
curlEbx, curlEby, curlEbz, triIndices(t),
uFace, vFace, wghtFace, nbrGPFace, bfNFace, false, elementOrder);
}
}
delete[] uVol; delete[] vVol; delete[] wVol; delete[] wghtVol;
delete[] uFace; delete[] vFace; delete[] wFace; delete[] wghtFace;
for (int i = 0; i < nbrGPVol; i++)
delete[] E[i];
delete[] E;
}
void OpticsFEM_3D_Scatter::Assemble_MAG()
{
const double k0 = 2.0 * Pi / _mSolver->GetLda0();
const std::complex<double> iUnit(0.0, 1.0);
const double zFactor = std::sqrt(mu0 / epsilon0);
const int elementOrder = _mPhy->GetElementOrder();
const int bfOrder = Nedelec3D::bfOrderParam(elementOrder);
const int gaussTri = Nedelec3D::gaussOrderTri(elementOrder);
const int nBfFace = (elementOrder == 2) ? 8 : 3;
Gauss gauss;
const int nbrGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, gaussTri);
double* u = new double[nbrGP];
double* v = new double[nbrGP];
double* w = new double[nbrGP];
double* wght = new double[nbrGP];
gauss.GetGaussPoints(TWODIM, TRIANGLE, u, v, w, wght);
BF bfN;
bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder);
mup::ParserX parser(mup::pckALL_COMPLEX);
parser.EnableAutoCreateVar(true);
mup::Value xx, yy, zz;
parser.DefineVar(_T("x"), mup::Variable(&xx));
parser.DefineVar(_T("y"), mup::Variable(&yy));
parser.DefineVar(_T("z"), mup::Variable(&zz));
for (int n = 0; n < _mPhy->GetNbrMAG(); n++)
{
std::string H0x, H0y, H0z;
_mPhy->GetH0(H0x, H0y, H0z, n);
VectorXi triIndices;
_mMesh->GetTriIndicesOfDomain(_mPhy->GetMAGDomain(n), triIndices);
for (int t = 0; t < triIndices.size(); t++)
{
Matrix3d invJac;
double integCoe = 0.0;
Vector3d normal = Vector3d::Zero();
int mappingIndex3[3], bfIndex3[3];
Vector3d x2, y2, z2, x3, y3, z3;
double xv[4], yv[4], zv[4];
if (!setupFaceIntegration(_mMesh, triIndices(t), invJac, integCoe, normal,
mappingIndex3, bfIndex3, x2, y2, z2, x3, y3, z3, xv, yv, zv))
continue;
int mappingIndex[8], bfIndex[8];
int nLocal = 3;
if (elementOrder == 2)
{
Eigen::Vector3i conn;
_mMesh->GetCoonOfTri(triIndices(t), conn);
Nedelec3D::buildSbcSecondOrderDofMap(_mMesh, conn(0), conn(1) + 1, mappingIndex);
Nedelec3D::sbcFaceSecondOrderBfIndex(conn(1) + 1, bfIndex);
nLocal = 8;
}
else
{
for (int i = 0; i < 3; i++)
{
mappingIndex[i] = mappingIndex3[i];
bfIndex[i] = bfIndex3[i];
}
}
const int triDomain = _mMesh->GetDomainOfTri(triIndices(t));
Vector3i conn;
_mMesh->GetCoonOfTri(triIndices(t), conn);
const int tetDomain = _mMesh->GetDomainOfTet(conn(0));
const double eps = _mMatLib->GetEpsr(tetDomain)(0, 0).real();
const std::complex<double> nn = std::sqrt(std::complex<double>(eps, 0.0));
Vector3d meshNorm;
_mMesh->GetNormOfFace(triDomain, meshNorm);
const Vector3d faceP0(x3(0), y3(0), z3(0));
const Vector3d faceP1(x3(1), y3(1), z3(1));
const Vector3d faceP2(x3(2), y3(2), z3(2));
normal = OpticsFEM::computeScatterSBCNormal(
false, triDomain, xv, yv, faceP0, faceP1, faceP2, &meshNorm);
std::vector<std::vector<Vector3d>> Egp(static_cast<size_t>(nbrGP),
std::vector<Vector3d>(static_cast<size_t>(nLocal)));
for (int gp = 0; gp < nbrGP; gp++)
{
const double wgp = 1.0 - u[gp] - v[gp];
const double u2 = x2(0) * u[gp] + x2(1) * v[gp] + x2(2) * wgp;
const double v2 = y2(0) * u[gp] + y2(1) * v[gp] + y2(2) * wgp;
const double w2 = z2(0) * u[gp] + z2(1) * v[gp] + z2(2) * wgp;
for (int j = 0; j < nLocal; j++)
{
bfN.GetValueBF(bfIndex[j], u2, v2, w2, Egp[static_cast<size_t>(gp)][static_cast<size_t>(j)]);
Egp[static_cast<size_t>(gp)][static_cast<size_t>(j)] =
invJac * Egp[static_cast<size_t>(gp)][static_cast<size_t>(j)];
}
}
VectorXcd bt = VectorXcd::Zero(nLocal);
for (int i = 0; i < nLocal; i++)
{
for (int gp = 0; gp < nbrGP; gp++)
{
double px, py, pz;
physicalPointOnFace(u[gp], v[gp], x2, y2, z2, x3, y3, z3, px, py, pz);
const Vector3cd H0 = evalVec3At(parser, xx, yy, zz, H0x, H0y, H0z, px, py, pz);
const Vector3cd Ms = normal.cross(H0);
bt(i) -= iUnit * zFactor * k0 * nn * integCoe * wght[gp]
* Egp[static_cast<size_t>(gp)][static_cast<size_t>(i)].dot(Ms) * 2.0;
}
}
for (int i = 0; i < nLocal; i++)
_mB_complex(mappingIndex[i]) += bt(i);
}
}
delete[] u; delete[] v; delete[] w; delete[] wght;
(void)nBfFace;
}
void OpticsFEM_3D_Scatter::Assemble_SCD()
{
const double k0 = 2.0 * Pi / _mSolver->GetLda0();
const std::complex<double> iUnit(0.0, 1.0);
const double zFactor = std::sqrt(mu0 / epsilon0);
const int elementOrder = _mPhy->GetElementOrder();
const int bfOrder = Nedelec3D::bfOrderParam(elementOrder);
const int gaussTri = Nedelec3D::gaussOrderTri(elementOrder);
const int nBfFace = (elementOrder == 2) ? 8 : 3;
Gauss gauss;
const int nbrGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, gaussTri);
double* u = new double[nbrGP];
double* v = new double[nbrGP];
double* w = new double[nbrGP];
double* wght = new double[nbrGP];
gauss.GetGaussPoints(TWODIM, TRIANGLE, u, v, w, wght);
BF bfN;
bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder);
mup::ParserX parser(mup::pckALL_COMPLEX);
parser.EnableAutoCreateVar(true);
mup::Value xx, yy, zz;
parser.DefineVar(_T("x"), mup::Variable(&xx));
parser.DefineVar(_T("y"), mup::Variable(&yy));
parser.DefineVar(_T("z"), mup::Variable(&zz));
for (int n = 0; n < _mPhy->GetNbrSCD(); n++)
{
std::string J0x, J0y, J0z;
_mPhy->GetJ0(J0x, J0y, J0z, n);
// faceFactor / srcFactor:
// Order1: empirical COMSOL SurfaceCurrent match: 2*k0/(π*max(ε_r)).
// Order2: same *2.0 as MAG/SBC face sources (no extra k0). Reusing the
// order1 faceFactor over-scales |E| on the SCD plane (~4.4 vs COMSOL ~2.1).
double srcFactor = 2.0;
if (elementOrder != 2)
{
double epsScdNorm = 1.0;
for (int t = 0; t < _mMesh->GetNbrTet(); ++t)
{
const int dom = _mMesh->GetDomainOfTet(t);
epsScdNorm = std::max(epsScdNorm, _mMatLib->GetEpsr(dom)(0, 0).real());
}
srcFactor = 2.0 * k0 / Pi / epsScdNorm;
}
VectorXi triIndices;
_mMesh->GetTriIndicesOfDomain(_mPhy->GetSCDDomain(n), triIndices);
for (int t = 0; t < triIndices.size(); t++)
{
Matrix3d invJac;
double integCoe = 0.0;
Vector3d normal = Vector3d::Zero();
int mappingIndex3[3], bfIndex3[3];
Vector3d x2, y2, z2, x3, y3, z3;
double xv[4], yv[4], zv[4];
if (!setupFaceIntegration(_mMesh, triIndices(t), invJac, integCoe, normal,
mappingIndex3, bfIndex3, x2, y2, z2, x3, y3, z3, xv, yv, zv))
continue;
int mappingIndex[8], bfIndex[8];
int nLocal = 3;
if (elementOrder == 2)
{
Eigen::Vector3i conn;
_mMesh->GetCoonOfTri(triIndices(t), conn);
Nedelec3D::buildSbcSecondOrderDofMap(_mMesh, conn(0), conn(1) + 1, mappingIndex);
Nedelec3D::sbcFaceSecondOrderBfIndex(conn(1) + 1, bfIndex);
nLocal = 8;
}
else
{
for (int i = 0; i < 3; i++)
{
mappingIndex[i] = mappingIndex3[i];
bfIndex[i] = bfIndex3[i];
}
}
const int triDomain = _mMesh->GetDomainOfTri(triIndices(t));
Vector3i conn;
_mMesh->GetCoonOfTri(triIndices(t), conn);
Vector3d meshNorm;
_mMesh->GetNormOfFace(triDomain, meshNorm);
const Vector3d faceP0(x3(0), y3(0), z3(0));
const Vector3d faceP1(x3(1), y3(1), z3(1));
const Vector3d faceP2(x3(2), y3(2), z3(2));
normal = OpticsFEM::computeScatterSBCNormal(
false, triDomain, xv, yv, faceP0, faceP1, faceP2, &meshNorm);
const int tetDomain = _mMesh->GetDomainOfTet(conn(0));
const double eps = _mMatLib->GetEpsr(tetDomain)(0, 0).real();
const std::complex<double> nn = std::sqrt(std::complex<double>(eps, 0.0));
std::vector<std::vector<Vector3d>> Egp(static_cast<size_t>(nbrGP),
std::vector<Vector3d>(static_cast<size_t>(nLocal)));
for (int gp = 0; gp < nbrGP; gp++)
{
const double wgp = 1.0 - u[gp] - v[gp];
const double u2 = x2(0) * u[gp] + x2(1) * v[gp] + x2(2) * wgp;
const double v2 = y2(0) * u[gp] + y2(1) * v[gp] + y2(2) * wgp;
const double w2 = z2(0) * u[gp] + z2(1) * v[gp] + z2(2) * wgp;
for (int j = 0; j < nLocal; j++)
{
bfN.GetValueBF(bfIndex[j], u2, v2, w2, Egp[static_cast<size_t>(gp)][static_cast<size_t>(j)]);
Egp[static_cast<size_t>(gp)][static_cast<size_t>(j)] =
invJac * Egp[static_cast<size_t>(gp)][static_cast<size_t>(j)];
}
}
VectorXcd bt = VectorXcd::Zero(nLocal);
for (int i = 0; i < nLocal; i++)
{
for (int gp = 0; gp < nbrGP; gp++)
{
double px, py, pz;
physicalPointOnFace(u[gp], v[gp], x2, y2, z2, x3, y3, z3, px, py, pz);
const Vector3cd J0 = evalVec3At(parser, xx, yy, zz, J0x, J0y, J0z, px, py, pz);
const Vector3cd Jt = crossNormal(normal, J0);
bt(i) -= iUnit * zFactor * k0 * nn * integCoe * wght[gp]
* Egp[static_cast<size_t>(gp)][static_cast<size_t>(i)].dot(Jt) * srcFactor;
}
}
for (int i = 0; i < nLocal; i++)
_mB_complex(mappingIndex[i]) += bt(i);
}
}
delete[] u; delete[] v; delete[] w; delete[] wght;
(void)nBfFace;
}
void OpticsFEM_3D_Scatter::Assemble_MPD()
{
const double k0 = 2.0 * Pi / _mSolver->GetLda0();
const double zFactor = std::sqrt(mu0 / epsilon0);
const int elementOrder = _mPhy->GetElementOrder();
const int bfOrder = Nedelec3D::bfOrderParam(elementOrder);
const int gaussVol = Nedelec3D::gaussOrderVol(elementOrder);
Eigen::VectorXi mpd;
Eigen::MatrixXd m;
_mPhy->GetMPD(mpd);
_mPhy->GetMPDData(m);
Gauss gauss;
const int nbrGP = gauss.GetNbrGaussPoints(THREEDIM, TETRAHEDRON, gaussVol);
double* u = new double[nbrGP];
double* v = new double[nbrGP];
double* w = new double[nbrGP];
double* wght = new double[nbrGP];
gauss.GetGaussPoints(THREEDIM, TETRAHEDRON, u, v, w, wght);
BF bfCurlN;
const int vdof = bfCurlN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_CURL_NEDELEC, bfOrder);
Vector3d** curlE = new Vector3d*[nbrGP];
for (int i = 0; i < nbrGP; i++)
curlE[i] = new Vector3d[vdof];
Vector3d vertex[4];
for (int dip = 0; dip < mpd.size(); dip++)
{
const Vector3d mCurrent = m.row(dip);
for (int n = 0; n < _mMesh->GetNbrTet(); n++)
{
if (!tetContainsVertex(_mMesh, n, mpd(dip)))
continue;
for (int i = 0; i < 4; i++)
_mMesh->GetVertex(_mMesh->GetTet(n, i), vertex[i]);
Matrix3d Jac, TJac;
Jac(0, 0) = vertex[0](0) - vertex[3](0); Jac(0, 1) = vertex[0](1) - vertex[3](1); Jac(0, 2) = vertex[0](2) - vertex[3](2);
Jac(1, 0) = vertex[1](0) - vertex[3](0); Jac(1, 1) = vertex[1](1) - vertex[3](1); Jac(1, 2) = vertex[1](2) - vertex[3](2);
Jac(2, 0) = vertex[2](0) - vertex[3](0); Jac(2, 1) = vertex[2](1) - vertex[3](1); Jac(2, 2) = vertex[2](2) - vertex[3](2);
const double detJac = std::abs(Jac.determinant());
if (detJac < 1e-30)
continue;
TJac = Jac.transpose() / Jac.determinant();
for (int gp = 0; gp < nbrGP; gp++)
{
for (int j = 0; j < vdof; j++)
{
bfCurlN.GetValueBF(j + 1, u[gp], v[gp], w[gp], curlE[gp][j]);
curlE[gp][j] = TJac * curlE[gp][j];
}
}
VectorXcd Tt = VectorXcd::Zero(vdof);
for (int i = 0; i < vdof; i++)
{
for (int gp = 0; gp < nbrGP; gp++)
{
Tt(i) += std::complex<double>(0.0, -1.0) * zFactor * k0 * wght[gp] * detJac * curlE[gp][i].dot(mCurrent);
}
}
std::vector<int> mapBuf(static_cast<size_t>(vdof), 0);
Nedelec3D::buildTetDofMap(_mMesh, n, elementOrder, mapBuf.data(), vdof);
for (int i = 0; i < vdof; i++)
_mB_complex(mapBuf[static_cast<size_t>(i)]) += Tt(i);
}
}
delete[] u; delete[] v; delete[] w; delete[] wght;
for (int i = 0; i < nbrGP; i++)
delete[] curlE[i];
delete[] curlE;
}
void OpticsFEM_3D_Scatter::Assemble_EPD()
{
const double k0 = 2.0 * Pi / _mSolver->GetLda0();
const double zFactor = std::sqrt(mu0 / epsilon0);
const int elementOrder = _mPhy->GetElementOrder();
const int bfOrder = Nedelec3D::bfOrderParam(elementOrder);
const int gaussVol = Nedelec3D::gaussOrderVol(elementOrder);
Eigen::VectorXi epd;
Eigen::MatrixXd p;
_mPhy->GetEPD(epd);
_mPhy->GetEPDData(p);
Gauss gauss;
const int nbrGP = gauss.GetNbrGaussPoints(THREEDIM, TETRAHEDRON, gaussVol);
double* u = new double[nbrGP];
double* v = new double[nbrGP];
double* w = new double[nbrGP];
double* wght = new double[nbrGP];
gauss.GetGaussPoints(THREEDIM, TETRAHEDRON, u, v, w, wght);
BF bfN;
const int vdof = bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder);
Vector3d** E = new Vector3d*[nbrGP];
for (int i = 0; i < nbrGP; i++)
E[i] = new Vector3d[vdof];
Vector3d vertex[4];
for (int dip = 0; dip < epd.size(); dip++)
{
const Vector3d pCurrent = p.row(dip);
for (int n = 0; n < _mMesh->GetNbrTet(); n++)
{
if (!tetContainsVertex(_mMesh, n, epd(dip)))
continue;
for (int i = 0; i < 4; i++)
_mMesh->GetVertex(_mMesh->GetTet(n, i), vertex[i]);
Matrix3d Jac, InvJac;
Jac(0, 0) = vertex[0](0) - vertex[3](0); Jac(0, 1) = vertex[0](1) - vertex[3](1); Jac(0, 2) = vertex[0](2) - vertex[3](2);
Jac(1, 0) = vertex[1](0) - vertex[3](0); Jac(1, 1) = vertex[1](1) - vertex[3](1); Jac(1, 2) = vertex[1](2) - vertex[3](2);
Jac(2, 0) = vertex[2](0) - vertex[3](0); Jac(2, 1) = vertex[2](1) - vertex[3](1); Jac(2, 2) = vertex[2](2) - vertex[3](2);
const double detJac = std::abs(Jac.determinant());
if (detJac < 1e-30)
continue;
InvJac = Jac.inverse();
for (int gp = 0; gp < nbrGP; gp++)
{
for (int j = 0; j < vdof; j++)
{
bfN.GetValueBF(j + 1, u[gp], v[gp], w[gp], E[gp][j]);
E[gp][j] = InvJac * E[gp][j];
}
}
VectorXcd Tt = VectorXcd::Zero(vdof);
for (int i = 0; i < vdof; i++)
{
for (int gp = 0; gp < nbrGP; gp++)
{
Tt(i) += std::complex<double>(0.0, -1.0) * zFactor * k0 * wght[gp] * detJac * E[gp][i].dot(pCurrent);
}
}
std::vector<int> mapBuf(static_cast<size_t>(vdof), 0);
Nedelec3D::buildTetDofMap(_mMesh, n, elementOrder, mapBuf.data(), vdof);
for (int i = 0; i < vdof; i++)
_mB_complex(mapBuf[static_cast<size_t>(i)]) += Tt(i);
}
}
delete[] u; delete[] v; delete[] w; delete[] wght;
for (int i = 0; i < nbrGP; i++)
delete[] E[i];
delete[] E;
}

View File

@ -1,4 +1,5 @@
#include"Assemble_Base.h"
#include"Nedelec3D_Util.h"
#include"../function/BF.h"
#include"../function/Gauss.h"
#include"../common/define.h"
@ -7,6 +8,8 @@
#include <vector>
#include <algorithm>
#include <string>
#include <complex>
#include <cmath>
using namespace std;
using namespace Eigen;
@ -285,12 +288,15 @@ void OpticsFEM_3D_Scatter::Assemble_WaveEquation()
{
//physic
double k0 = 2 * Pi / _mSolver->GetLda0();
const int elementOrder = _mPhy->GetElementOrder();
const int bfOrder = Nedelec3D::bfOrderParam(elementOrder);
const int gaussVol = Nedelec3D::gaussOrderVol(elementOrder);
//init of Gauss point
Gauss gauss;
int NbrGuassPoints;
double* u, * v, * w, * wght;
NbrGuassPoints = gauss.GetNbrGaussPoints(THREEDIM, TETRAHEDRON, BF_LINEFUNC * 2);
NbrGuassPoints = gauss.GetNbrGaussPoints(THREEDIM, TETRAHEDRON, gaussVol);
u = new double[NbrGuassPoints];
v = new double[NbrGuassPoints];
w = new double[NbrGuassPoints];
@ -305,8 +311,8 @@ void OpticsFEM_3D_Scatter::Assemble_WaveEquation()
BF BF_Nedelec, BF_Curl_Nedelec;
int dof;
Vector3d** E, ** curlE;
dof = BF_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, BF_LINEFUNC);
BF_Curl_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_CURL_NEDELEC, BF_LINEFUNC);
dof = BF_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder);
BF_Curl_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_CURL_NEDELEC, bfOrder);
E = new Vector3d * [NbrGuassPoints];
curlE = new Vector3d * [NbrGuassPoints];
for (int i = 0; i < NbrGuassPoints; i++)
@ -331,6 +337,18 @@ void OpticsFEM_3D_Scatter::Assemble_WaveEquation()
InvJac = Jac.inverse();
TJac = Jac.transpose() / Jac.determinant();
double edgeLen[6] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
const bool scalePortEdges = _mPhy->HasNumericPort() && elementOrder == 1;
if (scalePortEdges)
{
edgeLen[0] = (vertex[0] - vertex[1]).norm();
edgeLen[1] = (vertex[0] - vertex[2]).norm();
edgeLen[2] = (vertex[0] - vertex[3]).norm();
edgeLen[3] = (vertex[1] - vertex[2]).norm();
edgeLen[4] = (vertex[1] - vertex[3]).norm();
edgeLen[5] = (vertex[2] - vertex[3]).norm();
}
//basis function
for (int i = 0; i < NbrGuassPoints; i++)
{
@ -338,8 +356,12 @@ void OpticsFEM_3D_Scatter::Assemble_WaveEquation()
{
BF_Nedelec.GetValueBF(j + 1, u[i], v[i], w[i], E[i][j]);
E[i][j] = InvJac * E[i][j];
if (scalePortEdges)
E[i][j] *= edgeLen[j];
BF_Curl_Nedelec.GetValueBF(j + 1, u[i], v[i], w[i], curlE[i][j]);
curlE[i][j] = TJac * curlE[i][j];
if (scalePortEdges)
curlE[i][j] *= edgeLen[j];
}
}
@ -374,45 +396,42 @@ void OpticsFEM_3D_Scatter::Assemble_WaveEquation()
//mapping
VectorXi MappingIndex = VectorXi::Zero(dof);
std::vector<int> mapBuf(static_cast<size_t>(dof), 0);
Nedelec3D::buildTetDofMap(_mMesh, n, elementOrder, mapBuf.data(), dof);
for (int i = 0; i < dof; i++)
MappingIndex(i) = _mMesh->GetEdgeOfTet(n, i);
MappingIndex(i) = mapBuf[static_cast<size_t>(i)];
//submatrix
MatrixXcd Se, Te;
Se = MatrixXcd::Zero(dof, dof);
Te = MatrixXcd::Zero(dof, dof);
//submatrix — long double accumulate (short cavity is ill-conditioned;
// tiny imag sign flips from double cancellation destroy the solution).
auto bilinearRealCplx = [](const Vector3d& a, const Vector3cd& b) {
return std::complex<long double>(a(0), 0) * std::complex<long double>(b(0).real(), b(0).imag())
+ std::complex<long double>(a(1), 0) * std::complex<long double>(b(1).real(), b(1).imag())
+ std::complex<long double>(a(2), 0) * std::complex<long double>(b(2).real(), b(2).imag());
};
for (int i = 0; i < dof; i++)
{
for (int j = 0; j < dof; j++)
{
std::complex<long double> seLd = 0.0L;
std::complex<long double> teLd = 0.0L;
for (int k = 0; k < NbrGuassPoints; k++)
{
Se(i, j) = Se(i, j) + wght[k] * DetJac * curlE[k][i].dot(invMur * curlE[k][j]);
Te(i, j) = Te(i, j) + wght[k] * DetJac * k0 * k0 * E[k][i].dot(epsr * E[k][j]);
const Vector3cd murCurl = invMur * curlE[k][j];
const Vector3cd epsE = epsr * E[k][j];
const long double wDet = static_cast<long double>(wght[k] * DetJac);
seLd += wDet * bilinearRealCplx(curlE[k][i], murCurl);
teLd += wDet * static_cast<long double>(k0 * k0) * bilinearRealCplx(E[k][i], epsE);
}
//store in triplet
const std::complex<long double> aLd = seLd - teLd;
const complex<double> aij(
static_cast<double>(aLd.real()),
static_cast<double>(aLd.imag()));
if (_mIsReal)
{
for (int i = 0; i < dof; i++)
{
for (int j = 0; j < dof; j++)
{
_mTripleA_real.push_back(Triplet<double>(MappingIndex(i), MappingIndex(j), Se(i, j).real() - Te(i, j).real()));
}
}
}
_mTripleA_real.push_back(Triplet<double>(MappingIndex(i), MappingIndex(j), aij.real()));
else
{
for (int i = 0; i < dof; i++)
{
for (int j = 0; j < dof; j++)
{
_mTripleA_complex.push_back(Triplet<complex<double>>(MappingIndex(i), MappingIndex(j), Se(i, j) - Te(i, j)));
_mTripleA_complex.push_back(Triplet<complex<double>>(MappingIndex(i), MappingIndex(j), aij));
}
}
}
}
delete[] u, v, w, wght;
@ -426,6 +445,12 @@ void OpticsFEM_3D_Scatter::Assemble_WaveEquation()
delete[] E, curlE;
}
void OpticsFEM_3D_Scatter::Assemble_WaveEquation_Order2()
{
// Kept for ABI; Order2 is handled by Assemble_WaveEquation via ElementOrder.
Assemble_WaveEquation();
}
void OpticsFEM_3D_Scatter2::Assemble_WaveEquation()
{
//physic

View File

@ -1,5 +1,6 @@
#include"../common/define.h"
#include"Assemble_Base.h"
#include"Nedelec3D_Util.h"
#include "../Eigen/SparseLU"
#include <cmath>
@ -68,7 +69,7 @@ void OpticsFEM_2D_EigenMode::Assemble()
_mB_complex.setFromTriplets(_mTripleB_complex.begin(), _mTripleB_complex.end());
}
//????????????????????????
if ((_mPhy->GetNbrPEC() + _mPhy->GetNbrPBC() > 0))
if ((_mPhy->GetNbrPEC() + _mPhy->GetNbrPBCGroups() > 0))
{
this->Assemble_PEC_PBC();
@ -188,7 +189,7 @@ void OpticsFEM_2D_EigenFreq::Assemble()
//????????????????????????
_mElectricType = _mSolver->GetElectricType();
if ((_mPhy->GetNbrPEC() + _mPhy->GetNbrPBC() > 0))
if ((_mPhy->GetNbrPEC() + _mPhy->GetNbrPBCGroups() > 0))
{
this->Assemble_PEC_PBC();
}
@ -403,7 +404,7 @@ void OpticsFEM_2D_Scatter::Assemble()
this->Assemble_PEC_ELE();
//PBC
if (_mPhy->GetNbrPBC() > 0)
if (_mPhy->GetNbrPBCGroups() > 0)
{
this->Assemble_PBC();
}
@ -516,7 +517,8 @@ void OpticsFEM_2D_Scatter::Post(string file)
*/
void OpticsFEM_3D_EigenFreq::Assemble()
{
_mDof = _mMesh->GetNbrEdge();
const int elementOrder = _mPhy->GetElementOrder();
_mDof = Nedelec3D::globalDofCount(_mMesh, elementOrder);
_mTripleA_real.clear();
_mTripleB_real.clear();
_mTripleA_complex.clear();
@ -552,7 +554,7 @@ void OpticsFEM_3D_EigenFreq::Assemble()
_mB_complex.setFromTriplets(_mTripleB_complex.begin(), _mTripleB_complex.end());
}
if ((_mPhy->GetNbrPEC() + _mPhy->GetNbrPBC() > 0))
if ((_mPhy->GetNbrPEC() + _mPhy->GetNbrPBCGroups() > 0))
{
this->Assemble_PEC_PBC();
}
@ -570,6 +572,17 @@ void OpticsFEM_3D_EigenFreq::Assemble()
}
}
if (_mIsReal)
{
_mA_real = (_mP_real.transpose() * _mA_real) * _mP_real;
_mB_real = (_mP_real.transpose() * _mB_real) * _mP_real;
}
else
{
_mA_complex = (_mP_complex.adjoint() * _mA_complex) * _mP_complex;
_mB_complex = (_mP_complex.adjoint() * _mB_complex) * _mP_complex;
}
std::cout << "[OpticsFEM_3D_EigenFreq] Assemble done: DOF=" << _mDof
<< " nnz(A)=" << (_mIsReal ? _mA_real.nonZeros() : _mA_complex.nonZeros())
<< " nnz(B)=" << (_mIsReal ? _mB_real.nonZeros() : _mB_complex.nonZeros())
@ -581,6 +594,8 @@ void OpticsFEM_3D_EigenFreq::Assemble()
*/
void OpticsFEM_3D_Scatter::Assemble()
{
_mFreeDofIndices.clear();
_mPortExtraDof = 0;
_mIsReal = _mMatLib->IsReal();
if (_mIsReal)
{
@ -603,8 +618,25 @@ void OpticsFEM_3D_Scatter::Assemble()
}
}
}
if (_mIsReal && _mPhy->GetNbrElE() > 0)
_mIsReal = false;
if (_mIsReal && (_mPhy->GetNbrBELE() > 0 || _mPhy->GetNbrMAG() > 0
|| _mPhy->GetNbrSCD() > 0 || _mPhy->GetNbrMPD() > 0 || _mPhy->GetNbrEPD() > 0))
_mIsReal = false;
if (_mIsReal && _mPhy->HasNumericPort())
_mIsReal = false;
_mDof = _mMesh->GetNbrEdge();
const int elementOrder = _mPhy->GetElementOrder();
if (elementOrder == 2)
{
if (_mPhy->HasNumericPort())
{
std::cerr << "[OpticsFEM] ElementOrder=2: Port not supported yet; skipped."
<< std::endl;
}
}
_mDof = Nedelec3D::globalDofCount(_mMesh, elementOrder);
this->Assemble_WaveEquation();
@ -616,6 +648,9 @@ void OpticsFEM_3D_Scatter::Assemble()
if (_mPhy->GetNbrSBC() > 0)
this->Assemble_SBC();
if (_mPhy->GetNbrBELE() > 0)
this->Assemble_BELE();
if (_mIsReal)
{
_mA_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(_mDof, _mDof);
@ -630,6 +665,42 @@ void OpticsFEM_3D_Scatter::Assemble()
_mP_complex = Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>(_mDof, _mDof);
_mP_complex.setIdentity();
}
if (_mExportAsmDir.size() > 0)
this->Test_OutputMatrix(_mExportAsmDir);
if (elementOrder != 2 && _mPhy->HasNumericPort())
this->Assemble_Port();
if (_mPhy->GetNbrMAG() > 0)
this->Assemble_MAG();
if (_mPhy->GetNbrSCD() > 0)
this->Assemble_SCD();
if (_mPhy->GetNbrMPD() > 0)
this->Assemble_MPD();
if (_mPhy->GetNbrEPD() > 0)
this->Assemble_EPD();
if (_mPhy->GetNbrPEC() > 0 || _mPhy->GetNbrElE() > 0)
this->Assemble_PEC_ELE();
if (_mPhy->GetNbrPBCGroups() > 0)
this->Assemble_PBC();
if (_mIsReal)
{
_mA_real = (_mP_real.transpose() * _mA_real) * _mP_real;
_mB_real = _mP_real.transpose() * _mB_real;
}
else
{
// After port PEC elimination A/B are already reduced; skip P projection.
if (_mFreeDofIndices.empty())
{
_mA_complex = (_mP_complex.adjoint() * _mA_complex) * _mP_complex;
_mB_complex = _mP_complex.adjoint() * _mB_complex;
}
}
}
void OpticsFEM_3D_Scatter::Run()
@ -644,6 +715,119 @@ void OpticsFEM_3D_Scatter::Run()
_mSolver->GetRealFlag(_mIsReal);
_mSolver->SetParam(&_mA_complex, &_mB_complex, &_mP_complex);
}
// PEC/ELE: after Dirichlet BC, external complexsolver CSR export fails; use SparseLU here only.
const bool useBuiltInLu = (_mPhy->GetNbrPEC() > 0 || _mPhy->GetNbrElE() > 0 || _mPhy->HasNumericPort());
if (!_mIsReal && useBuiltInLu)
{
_mA_complex.makeCompressed();
Eigen::VectorXcd xReduced;
if (_mPhy->HasNumericPort())
{
std::cout << "[OpticsFEM_3D_Scatter] Reduced system"
<< " size=" << _mA_complex.rows()
<< " nnz=" << _mA_complex.nonZeros() << std::endl;
if (_mFreeDofIndices.size() >= 2)
{
const size_t n = _mFreeDofIndices.size();
std::cout << "[OpticsFEM_3D_Scatter] Port free DOFs:"
<< " e1_idx=" << _mFreeDofIndices[n - 2]
<< " e2_idx=" << _mFreeDofIndices[n - 1] << std::endl;
}
}
bool solved = false;
{
// MATLAB FemMatrixAssembly uses equilibrate(A) before '\'.
// Ruiz infinity-norm scaling + iterative refinement stabilizes short-cavity ports.
const int nA = static_cast<int>(_mA_complex.rows());
Eigen::VectorXd Rsc = Eigen::VectorXd::Ones(nA);
Eigen::VectorXd Csc = Eigen::VectorXd::Ones(nA);
Eigen::SparseMatrix<std::complex<double>> Aeq = _mA_complex;
for (int itEq = 0; itEq < 5; ++itEq)
{
Eigen::VectorXd rowInf = Eigen::VectorXd::Zero(nA);
for (int k = 0; k < Aeq.outerSize(); ++k)
for (Eigen::SparseMatrix<std::complex<double>>::InnerIterator it(Aeq, k); it; ++it)
rowInf(it.row()) = std::max(rowInf(it.row()), std::abs(it.value()));
for (int i = 0; i < nA; ++i)
{
const double s = (rowInf(i) > 0.0) ? (1.0 / std::sqrt(rowInf(i))) : 1.0;
Rsc(i) *= s;
}
std::vector<Eigen::Triplet<std::complex<double>>> trips;
trips.reserve(static_cast<size_t>(Aeq.nonZeros()));
for (int k = 0; k < Aeq.outerSize(); ++k)
for (Eigen::SparseMatrix<std::complex<double>>::InnerIterator it(Aeq, k); it; ++it)
trips.emplace_back(it.row(), it.col(), it.value() * (rowInf(it.row()) > 0.0 ? (1.0 / std::sqrt(rowInf(it.row()))) : 1.0));
Aeq.resize(nA, nA);
Aeq.setFromTriplets(trips.begin(), trips.end());
Eigen::VectorXd colInf = Eigen::VectorXd::Zero(nA);
for (int k = 0; k < Aeq.outerSize(); ++k)
for (Eigen::SparseMatrix<std::complex<double>>::InnerIterator it(Aeq, k); it; ++it)
colInf(it.col()) = std::max(colInf(it.col()), std::abs(it.value()));
for (int i = 0; i < nA; ++i)
{
const double s = (colInf(i) > 0.0) ? (1.0 / std::sqrt(colInf(i))) : 1.0;
Csc(i) *= s;
}
trips.clear();
for (int k = 0; k < Aeq.outerSize(); ++k)
for (Eigen::SparseMatrix<std::complex<double>>::InnerIterator it(Aeq, k); it; ++it)
trips.emplace_back(it.row(), it.col(), it.value() * (colInf(it.col()) > 0.0 ? (1.0 / std::sqrt(colInf(it.col()))) : 1.0));
Aeq.resize(nA, nA);
Aeq.setFromTriplets(trips.begin(), trips.end());
}
const Eigen::VectorXcd beq = Rsc.asDiagonal() * _mB_complex;
Eigen::SparseLU<Eigen::SparseMatrix<std::complex<double>>> lu;
lu.compute(Aeq);
if (lu.info() == Eigen::Success)
{
xReduced = Csc.asDiagonal() * lu.solve(beq);
// Iterative refinement on the original (unscaled) system.
for (int ir = 0; ir < 3; ++ir)
{
const Eigen::VectorXcd r = _mB_complex - _mA_complex * xReduced;
Eigen::VectorXcd dr = Csc.asDiagonal() * lu.solve(Rsc.asDiagonal() * r);
xReduced += dr;
if (r.norm() < 1e-12 * (_mB_complex.norm() + 1e-30))
break;
}
solved = true;
}
}
if (solved)
{
if (_mPhy->HasNumericPort())
{
if (!_mFreeDofIndices.empty())
{
_mX = Eigen::VectorXcd::Zero(_mDof + _mPortExtraDof);
for (size_t i = 0; i < _mFreeDofIndices.size(); i++)
_mX(_mFreeDofIndices[i]) = xReduced(static_cast<int>(i));
}
else
_mX = xReduced;
}
else
{
_mX = Eigen::VectorXcd::Zero(_mP_complex.rows());
_mX = _mP_complex * xReduced;
}
const Eigen::VectorXcd r = _mA_complex * xReduced - _mB_complex;
std::cout << "[OpticsFEM_3D_Scatter] SparseLU (PEC/ELE/Port)"
<< " |x|=" << _mX.norm()
<< " |r|=" << r.norm()
<< " |b|=" << _mB_complex.norm() << std::endl;
}
else
{
std::cout << "[OpticsFEM_3D_Scatter] SparseLU factorization failed" << std::endl;
}
return;
}
_mSolver->Run(&_mX);
if (!_mIsReal && _mB_complex.size() > 0)
@ -663,8 +847,33 @@ void OpticsFEM_3D_Scatter::Run()
void OpticsFEM_3D_Scatter::Post(string file)
{
_mPost->GetMesh(_mMesh);
_mPost->GetPhy(_mPhy);
_mPost->GetResult(&_mX);
_mPost->GetElectric();
if (_mPortExtraDof > 0 && _mX.size() >= _mDof + _mPortExtraDof)
{
std::cout << "[OpticsFEM_3D_Scatter] Port amplitudes:";
for (int i = 0; i < _mPortExtraDof; i++)
std::cout << " e" << (i + 1) << "=" << _mX(_mDof + i);
std::cout << std::endl;
std::ofstream sOut(file + "/S_params.txt");
if (sOut.is_open())
{
sOut << std::setprecision(12);
const double pcoef = std::abs(_mPhy->GetNumericPort(0).powerCoef);
for (int i = 0; i < _mPortExtraDof; i++)
{
const std::complex<double> e = _mX(_mDof + i);
sOut << "e" << (i + 1) << " " << e.real() << " " << e.imag();
if (i == 0 && pcoef > 0.0)
sOut << " S11=" << (e.real() / pcoef) << " " << (e.imag() / pcoef);
else if (i == 1 && pcoef > 0.0)
sOut << " S21=" << (e.real() / pcoef) << " " << (e.imag() / pcoef);
sOut << std::endl;
}
}
}
this->Test_OutputMatrix(file);
_mPost->OutputData(file);
std::cout << "[OpticsFEM] post to " << file << std::endl;
@ -677,14 +886,18 @@ void OpticsFEM_3D_Scatter::Test_OutputMatrix(const std::string& outDir)
std::ofstream outAi(prefix + "Ai.txt"), outAj(prefix + "Aj.txt"), outAv(prefix + "Av.txt");
std::ofstream outBv_real(prefix + "Bv_real.txt"), outBv_imag(prefix + "Bv_imag.txt");
std::ofstream outX_real(prefix + "X_real.txt"), outX_imag(prefix + "X_imag.txt");
outAv << std::setprecision(12);
if (_mIsReal)
{
for (int k = 0; k < _mTripleA_real.size(); k++)
for (int col = 0; col < _mA_real.outerSize(); col++)
{
outAi << _mTripleA_real[k].row() << std::endl;
outAj << _mTripleA_real[k].col() << std::endl;
outAv << _mTripleA_real[k].value() << std::endl;
for (Eigen::SparseMatrix<double, Eigen::RowMajor>::InnerIterator it(_mA_real, col); it; ++it)
{
outAi << it.row() << std::endl;
outAj << it.col() << std::endl;
outAv << it.value() << std::endl;
}
}
for (int i = 0; i < _mB_real.size(); i++)
outBv_real << _mB_real(i) << std::endl;
@ -693,11 +906,15 @@ void OpticsFEM_3D_Scatter::Test_OutputMatrix(const std::string& outDir)
}
else
{
for (int k = 0; k < _mTripleA_complex.size(); k++)
// Export reduced system after PBC/PEC projection (matches MATLAB main_export OutFile).
for (int col = 0; col < _mA_complex.outerSize(); col++)
{
outAi << _mTripleA_complex[k].row() << std::endl;
outAj << _mTripleA_complex[k].col() << std::endl;
outAv << _mTripleA_complex[k].value() << std::endl;
for (Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>::InnerIterator it(_mA_complex, col); it; ++it)
{
outAi << it.row() << std::endl;
outAj << it.col() << std::endl;
outAv << "(" << it.value().real() << "," << it.value().imag() << ")" << std::endl;
}
}
for (int i = 0; i < _mB_complex.size(); i++)
{
@ -759,6 +976,33 @@ void OpticsFEM_3D_EigenFreq::Test_OutputMatrix(const std::string& outDir)
}
}
void OpticsFEM_3D_EigenFreq::Run()
{
double k0 = 2. * Pi / (c_const / _mSolver->GetSearchValue());
double search = k0 * k0;
if (_mIsReal)
{
_mSolver->GetRealFlag(_mIsReal);
_mSolver->SetParam(&_mA_real, &_mB_real, &_mP_real, search);
}
else
{
_mSolver->GetRealFlag(_mIsReal);
_mSolver->SetParam(&_mA_complex, &_mB_complex, &_mP_complex, search);
}
_mSolver->Run(&_mX, &_mLambda);
}
void OpticsFEM_3D_EigenFreq::Post(string file)
{
_mPost->GetMesh(_mMesh);
_mPost->GetSolver(_mSolver);
_mPost->GetResult(&_mX, &_mLambda);
_mPost->GetElectric();
_mPost->OutputData(file);
std::cout << "[OpticsFEM_3D_EigenFreq] post to " << file << std::endl;
}
/*
3D_Scatter2
prism

View File

@ -0,0 +1,68 @@
#include "Nedelec3D_Util.h"
namespace Nedelec3D
{
void buildTetDofMap(Mesh_3D* mesh, int numTet, int elementOrder, int* map, int mapSize)
{
if (elementOrder != 2)
{
if (mapSize < 6)
return;
for (int i = 0; i < 6; i++)
map[i] = mesh->GetEdgeOfTet(numTet, i);
return;
}
if (mapSize < 20)
return;
const int nE = mesh->GetNbrEdge();
const int nF = mesh->GetNbrFace();
for (int i = 0; i < 6; i++)
{
const int e = mesh->GetEdgeOfTet(numTet, i);
map[i * 2] = e;
map[i * 2 + 1] = e + nE;
}
for (int i = 0; i < 4; i++)
{
const int f = mesh->GetFaceOfTet(numTet, i);
map[12 + i * 2] = 2 * nE + f;
map[12 + i * 2 + 1] = 2 * nE + f + nF;
}
}
void sbcFaceSecondOrderBfIndex(int numFace, int bfIndex[8])
{
static const int tbl[4][8] = {
{1, 3, 7, 2, 4, 8, 13, 14},
{1, 5, 9, 2, 6, 10, 15, 16},
{3, 5, 11, 4, 6, 12, 17, 18},
{7, 9, 11, 8, 10, 12, 19, 20}
};
const int fi = numFace - 1;
if (fi < 0 || fi > 3)
return;
for (int i = 0; i < 8; i++)
bfIndex[i] = tbl[fi][i];
}
void buildSbcSecondOrderDofMap(Mesh_3D* mesh, int numTet, int numFace, int mapIdx[8])
{
static const int edgeSlots[4][3] = { {0, 1, 3}, {0, 2, 4}, {1, 2, 5}, {3, 4, 5} };
const int nE = mesh->GetNbrEdge();
const int nF = mesh->GetNbrFace();
const int fi = numFace - 1;
if (fi < 0 || fi > 3)
return;
const int faceId = mesh->GetFaceOfTet(numTet, fi);
for (int j = 0; j < 3; j++)
{
const int e = mesh->GetEdgeOfTet(numTet, edgeSlots[fi][j]);
mapIdx[j] = e;
mapIdx[j + 3] = e + nE;
}
mapIdx[6] = 2 * nE + faceId;
mapIdx[7] = 2 * nE + faceId + nF;
}
}

View File

@ -0,0 +1,50 @@
#pragma once
#include "../mesh/Mesh_Base.h"
#include "../common/define.h"
namespace Nedelec3D
{
inline int bfOrderParam(int elementOrder)
{
return (elementOrder == 2) ? BF_QUADFUNC : BF_LINEFUNC;
}
inline int gaussOrderVol(int elementOrder)
{
return (elementOrder == 2) ? 4 : (BF_LINEFUNC * 2);
}
inline int gaussOrderTri(int elementOrder)
{
return (elementOrder == 2) ? 6 : (BF_LINEFUNC * 2);
}
inline int localDofPerTet(int elementOrder)
{
return (elementOrder == 2) ? 20 : 6;
}
inline int globalDofCount(Mesh_3D* mesh, int elementOrder)
{
if (elementOrder == 2)
return 2 * mesh->GetNbrEdge() + 2 * mesh->GetNbrFace();
return mesh->GetNbrEdge();
}
void buildTetDofMap(Mesh_3D* mesh, int numTet, int elementOrder, int* map, int mapSize);
void sbcFaceSecondOrderBfIndex(int numFace, int bfIndex[8]);
void buildSbcSecondOrderDofMap(Mesh_3D* mesh, int numTet, int numFace, int mapIdx[8]);
inline int edgeGlobalDof(int edgeId, int slot, int nbrEdge)
{
return edgeId + slot * nbrEdge;
}
inline int faceGlobalDof(int faceId, int slot, int nbrEdge, int nbrFace)
{
return 2 * nbrEdge + faceId + slot * nbrFace;
}
}

View File

@ -1,4 +1,5 @@
#include "OpticsFEM_3D_SBC_Scatter.h"
#include "SBC_NormalUtil.h"
#include "../function/BF.h"
#include "../function/Gauss.h"
#include "../common/define.h"
@ -169,8 +170,19 @@ void OpticsFEM_3D_SBC_Scatter::Assemble_SBC_Face(bool isInc)
if (heron < 0.0) heron = 0.0;
const double integCoe = 0.25 * std::sqrt(heron);
Vector3d normal;
_mMesh->GetNormOfFace(domain, normal);
double tetX[4], tetY[4];
for (int i = 0; i < 4; i++)
{
tetX[i] = vertex[i](0);
tetY[i] = vertex[i](1);
}
Vector3d meshNorm;
_mMesh->GetNormOfFace(domain, meshNorm);
const Vector3d faceP0(x3(0), y3(0), z3(0));
const Vector3d faceP1(x3(1), y3(1), z3(1));
const Vector3d faceP2(x3(2), y3(2), z3(2));
const Vector3d normal = OpticsFEM::computeScatterSBCNormal(
isInc, domain, tetX, tetY, faceP0, faceP1, faceP2, &meshNorm);
int tetDom = _mMesh->GetDomainOfTet(numTet);
const double eps = (tetDom > 0 && tetDom <= _mEps.size()) ? _mEps(tetDom - 1) : _mEps(0);

View File

@ -0,0 +1,514 @@
#include "PBC_Util.h"
#include "Nedelec3D_Util.h"
#include <algorithm>
#include <unordered_map>
namespace {
int findMemberIndex(const std::vector<int>& values, int key)
{
for (size_t i = 0; i < values.size(); i++)
{
if (values[i] == key)
return static_cast<int>(i);
}
return -1;
}
std::vector<int> findDuplicateValues(const std::vector<int>& values)
{
std::unordered_map<int, int> counts;
for (int v : values)
counts[v]++;
std::vector<int> duplicates;
for (const auto& entry : counts)
{
if (entry.second > 1)
duplicates.push_back(entry.first);
}
std::sort(duplicates.begin(), duplicates.end());
return duplicates;
}
} // namespace
PbcEdgePairs collectPbcEdgePairs(
Mesh_3D* mesh,
const std::vector<Eigen::VectorXi>& srcDomains,
const std::vector<Eigen::VectorXi>& dstDomains,
const Eigen::VectorXd& pbcAngles,
const Eigen::MatrixXd& pbcWaveVecs,
bool useFaceStyleEdgeTol)
{
const int nbrGroups = static_cast<int>(srcDomains.size());
Eigen::VectorXcd unitPhi = Eigen::VectorXcd::Ones(nbrGroups);
Eigen::VectorXi srcEdgeIndex;
Eigen::VectorXi dstEdgeIndex;
Eigen::VectorXcd edgePhi;
mesh->GetIndexOfPBC(srcDomains, dstDomains, unitPhi, pbcAngles, pbcWaveVecs,
srcEdgeIndex, dstEdgeIndex, edgePhi, useFaceStyleEdgeTol);
sortUniquePbcPairs(srcEdgeIndex, dstEdgeIndex, edgePhi);
PbcEdgePairs pairs;
const int n = static_cast<int>(srcEdgeIndex.size());
pairs.src.resize(n);
pairs.dst.resize(n);
pairs.sign.resize(n);
for (int i = 0; i < n; i++)
{
pairs.src[i] = srcEdgeIndex(i);
pairs.dst[i] = dstEdgeIndex(i);
pairs.sign[i] = edgePhi(i);
}
return pairs;
}
PbcEdgePairs collectPbcFacePairs(
Mesh_3D* mesh,
const std::vector<Eigen::VectorXi>& srcDomains,
const std::vector<Eigen::VectorXi>& dstDomains,
const Eigen::VectorXd& pbcAngles,
const Eigen::MatrixXd& pbcWaveVecs)
{
Eigen::VectorXi srcFaceIndex;
Eigen::VectorXi dstFaceIndex;
Eigen::VectorXcd facePhi;
mesh->GetIndexOfPBCFaces(srcDomains, dstDomains, pbcAngles, pbcWaveVecs,
srcFaceIndex, dstFaceIndex, facePhi);
PbcEdgePairs pairs;
const int n = static_cast<int>(srcFaceIndex.size());
pairs.src.resize(n);
pairs.dst.resize(n);
pairs.sign.resize(n);
for (int i = 0; i < n; i++)
{
pairs.src[i] = srcFaceIndex(i);
pairs.dst[i] = dstFaceIndex(i);
pairs.sign[i] = facePhi(i);
}
return pairs;
}
void mergeDoublePbcPairs(
const PbcEdgePairs& pairs1,
std::complex<double> blochPhi1,
const PbcEdgePairs& pairs2,
std::complex<double> blochPhi2,
PbcEdgePairs& merged,
std::vector<std::complex<double>>& mergedPhi)
{
const int nbr2 = static_cast<int>(pairs1.src.size());
const int nbr3 = static_cast<int>(pairs2.src.size());
std::vector<int> allDst;
allDst.reserve(nbr2 + nbr3);
allDst.insert(allDst.end(), pairs1.dst.begin(), pairs1.dst.end());
allDst.insert(allDst.end(), pairs2.dst.begin(), pairs2.dst.end());
const std::vector<int> ovDstIndex = findDuplicateValues(allDst);
const int nbr1 = static_cast<int>(ovDstIndex.size());
merged.src.clear();
merged.dst.clear();
merged.sign.clear();
mergedPhi.clear();
merged.src.reserve(nbr2 + nbr3 - nbr1);
merged.dst.reserve(nbr2 + nbr3 - nbr1);
merged.sign.reserve(nbr2 + nbr3 - nbr1);
mergedPhi.reserve(nbr2 + nbr3 - nbr1);
std::vector<char> remove1(nbr2, 0);
std::vector<char> remove2(nbr3, 0);
for (int ovDst : ovDstIndex)
{
const int index1 = findMemberIndex(pairs2.dst, ovDst);
if (index1 < 0)
continue;
const int src2 = pairs2.src[index1];
const int index3 = findMemberIndex(pairs1.dst, src2);
if (index3 < 0)
continue;
const int src1 = pairs1.src[index3];
const std::complex<double> sign =
pairs2.sign[index1] * pairs1.sign[index3];
merged.src.push_back(src1);
merged.dst.push_back(ovDst);
merged.sign.push_back(sign);
mergedPhi.push_back(blochPhi1 * blochPhi2 * sign);
remove1[index3] = 1;
remove2[index1] = 1;
for (int i = 0; i < nbr2; i++)
{
if (pairs1.dst[i] == ovDst)
remove1[i] = 1;
}
for (int i = 0; i < nbr3; i++)
{
if (pairs2.dst[i] == ovDst)
remove2[i] = 1;
}
}
for (int i = 0; i < nbr2; i++)
{
if (remove1[i])
continue;
merged.src.push_back(pairs1.src[i]);
merged.dst.push_back(pairs1.dst[i]);
merged.sign.push_back(pairs1.sign[i]);
mergedPhi.push_back(blochPhi1 * pairs1.sign[i]);
}
for (int i = 0; i < nbr3; i++)
{
if (remove2[i])
continue;
merged.src.push_back(pairs2.src[i]);
merged.dst.push_back(pairs2.dst[i]);
merged.sign.push_back(pairs2.sign[i]);
mergedPhi.push_back(blochPhi2 * pairs2.sign[i]);
}
}
void sortUniquePbcPairs(
Eigen::VectorXi& srcEdgeIndex,
Eigen::VectorXi& dstEdgeIndex,
Eigen::VectorXcd& edgePhi)
{
if (dstEdgeIndex.size() == 0)
return;
Eigen::VectorXi tempIndex = Eigen::VectorXi::LinSpaced(dstEdgeIndex.size(), 0, dstEdgeIndex.size() - 1);
QuickSort(dstEdgeIndex, tempIndex, 0, static_cast<int>(dstEdgeIndex.size()) - 1);
Unique(dstEdgeIndex, tempIndex);
Eigen::VectorXcd sortedPhi = Eigen::VectorXcd::Zero(dstEdgeIndex.size());
Eigen::VectorXi sortedSrc = Eigen::VectorXi::Zero(dstEdgeIndex.size());
for (int i = 0; i < dstEdgeIndex.size(); i++)
{
sortedPhi(i) = edgePhi(tempIndex(i));
sortedSrc(i) = srcEdgeIndex(tempIndex(i));
}
edgePhi = sortedPhi;
srcEdgeIndex = sortedSrc;
}
void fillPbcGroupFromPhy(
const Phy_WaveOpticsModel* phy,
int group,
std::vector<Eigen::VectorXi>& srcDomains,
std::vector<Eigen::VectorXi>& dstDomains,
Eigen::VectorXd& pbcAngles,
Eigen::MatrixXd& pbcWaveVecs)
{
const int nbrPBC = phy->GetNbrPBCInGroup(group);
srcDomains.resize(nbrPBC);
dstDomains.resize(nbrPBC);
pbcAngles = Eigen::VectorXd(nbrPBC);
pbcWaveVecs = Eigen::MatrixXd::Zero(3, nbrPBC);
for (int i = 0; i < nbrPBC; i++)
{
phy->GetSrcDomainList(group, i, srcDomains[i]);
phy->GetDstDomainList(group, i, dstDomains[i]);
pbcAngles(i) = phy->GetPBCAngle(group, i);
Eigen::Vector3d waveVec;
phy->GetPBCWaveVec(group, i, waveVec);
pbcWaveVecs.col(i) = waveVec;
}
}
namespace {
void expandOrder2PbcPairs(
Mesh_3D* mesh,
const PbcEdgePairs& edgePairs,
const std::vector<std::complex<double>>& edgePhi,
const PbcEdgePairs& facePairs,
const std::vector<std::complex<double>>& facePhi,
Eigen::VectorXi& srcDofIndex,
Eigen::VectorXi& dstDofIndex,
Eigen::VectorXcd& dofPhi)
{
// Order-2 tangential PBC = order-1 edge pairing/signs, with each edge enriched
// to two DOFs (e, e+nE) sharing the same Bloch×orient φ.
//
// Face DOFs are intentionally NOT constrained here:
// - getBF face modes (BF 1320) have vanishing tangential trace on their face,
// so Et continuity on PBC walls is carried entirely by edge DOFs;
// - MATLAB-style identity face expand (f→f, f+nF→f+nF) is wrong on this
// translation mesh (Face vertex order usually does not match under +dis)
// and actively breaks L/R symmetry (A/B: with faces L/R~0.67, without ~0.98).
(void)facePairs;
(void)facePhi;
const int nE = mesh->GetNbrEdge();
std::vector<int> srcAll, dstAll;
std::vector<std::complex<double>> phiAll;
srcAll.reserve(edgePairs.src.size() * 2);
dstAll.reserve(edgePairs.dst.size() * 2);
phiAll.reserve(srcAll.capacity());
for (size_t i = 0; i < edgePairs.src.size(); i++)
{
const std::complex<double> phi = edgePhi[i];
srcAll.push_back(Nedelec3D::edgeGlobalDof(edgePairs.src[i], 0, nE));
dstAll.push_back(Nedelec3D::edgeGlobalDof(edgePairs.dst[i], 0, nE));
phiAll.push_back(phi);
srcAll.push_back(Nedelec3D::edgeGlobalDof(edgePairs.src[i], 1, nE));
dstAll.push_back(Nedelec3D::edgeGlobalDof(edgePairs.dst[i], 1, nE));
phiAll.push_back(phi);
}
std::vector<int> filteredSrc, filteredDst;
std::vector<std::complex<double>> filteredPhi;
for (size_t i = 0; i < srcAll.size(); i++)
{
if (srcAll[i] == dstAll[i])
continue;
filteredSrc.push_back(srcAll[i]);
filteredDst.push_back(dstAll[i]);
filteredPhi.push_back(phiAll[i]);
}
const int n = static_cast<int>(filteredDst.size());
srcDofIndex = Eigen::VectorXi(n);
dstDofIndex = Eigen::VectorXi(n);
dofPhi = Eigen::VectorXcd(n);
for (int i = 0; i < n; i++)
{
srcDofIndex(i) = filteredSrc[static_cast<size_t>(i)];
dstDofIndex(i) = filteredDst[static_cast<size_t>(i)];
dofPhi(i) = filteredPhi[static_cast<size_t>(i)];
}
sortUniquePbcPairs(srcDofIndex, dstDofIndex, dofPhi);
}
bool collectMergedPbcPairGroups(
Mesh_3D* mesh,
const Phy_WaveOpticsModel* phy,
int elementOrder,
PbcEdgePairs& mergedEdges,
std::vector<std::complex<double>>& mergedEdgePhi,
PbcEdgePairs& mergedFaces,
std::vector<std::complex<double>>& mergedFacePhi)
{
const int nbrGroups = phy->GetNbrPBCGroups();
if (nbrGroups <= 0)
return false;
// Do NOT loosen edge tol for order-2: it wrecked the validated top (z=+5e-6) face.
const bool looseEdgeTol = false;
std::vector<Eigen::VectorXi> srcDomains;
std::vector<Eigen::VectorXi> dstDomains;
Eigen::VectorXd pbcAngles;
Eigen::MatrixXd pbcWaveVecs;
fillPbcGroupFromPhy(phy, 0, srcDomains, dstDomains, pbcAngles, pbcWaveVecs);
const PbcEdgePairs edgePairs1 = collectPbcEdgePairs(mesh, srcDomains, dstDomains, pbcAngles, pbcWaveVecs, looseEdgeTol);
const PbcEdgePairs facePairs1 = collectPbcFacePairs(mesh, srcDomains, dstDomains, pbcAngles, pbcWaveVecs);
if (nbrGroups >= 2)
{
std::vector<Eigen::VectorXi> srcDomains2;
std::vector<Eigen::VectorXi> dstDomains2;
Eigen::VectorXd pbcAngles2;
Eigen::MatrixXd pbcWaveVecs2;
fillPbcGroupFromPhy(phy, 1, srcDomains2, dstDomains2, pbcAngles2, pbcWaveVecs2);
const PbcEdgePairs edgePairs2 = collectPbcEdgePairs(mesh, srcDomains2, dstDomains2, pbcAngles2, pbcWaveVecs2, looseEdgeTol);
const PbcEdgePairs facePairs2 = collectPbcFacePairs(mesh, srcDomains2, dstDomains2, pbcAngles2, pbcWaveVecs2);
const std::complex<double> phi1 = phy->GetPBCPhi(0, 0);
const std::complex<double> phi2 = phy->GetPBCPhi(1, 0);
mergeDoublePbcPairs(edgePairs1, phi1, edgePairs2, phi2, mergedEdges, mergedEdgePhi);
mergeDoublePbcPairs(facePairs1, phi1, facePairs2, phi2, mergedFaces, mergedFacePhi);
}
else
{
const std::complex<double> phi1 = phy->GetPBCPhi(0, 0);
mergedEdges = edgePairs1;
mergedFaces = facePairs1;
mergedEdgePhi.resize(edgePairs1.src.size());
mergedFacePhi.resize(facePairs1.src.size());
for (size_t i = 0; i < edgePairs1.src.size(); i++)
mergedEdgePhi[i] = phi1 * edgePairs1.sign[i];
for (size_t i = 0; i < facePairs1.src.size(); i++)
mergedFacePhi[i] = phi1 * facePairs1.sign[i];
}
return mergedEdges.dst.size() > 0 || mergedFaces.dst.size() > 0;
}
} // namespace
bool collectMergedPbcConstraints(
Mesh_3D* mesh,
const Phy_WaveOpticsModel* phy,
int elementOrder,
Eigen::VectorXi& srcDofIndex,
Eigen::VectorXi& dstDofIndex,
Eigen::VectorXcd& dofPhi)
{
PbcEdgePairs mergedEdges;
std::vector<std::complex<double>> mergedEdgePhi;
PbcEdgePairs mergedFaces;
std::vector<std::complex<double>> mergedFacePhi;
if (!collectMergedPbcPairGroups(mesh, phy, elementOrder, mergedEdges, mergedEdgePhi, mergedFaces, mergedFacePhi))
return false;
if (elementOrder == 2)
{
expandOrder2PbcPairs(mesh, mergedEdges, mergedEdgePhi, mergedFaces, mergedFacePhi,
srcDofIndex, dstDofIndex, dofPhi);
return dstDofIndex.size() > 0;
}
const int n = static_cast<int>(mergedEdges.src.size());
srcDofIndex = Eigen::VectorXi(n);
dstDofIndex = Eigen::VectorXi(n);
dofPhi = Eigen::VectorXcd(n);
for (int i = 0; i < n; i++)
{
srcDofIndex(i) = mergedEdges.src[static_cast<size_t>(i)];
dstDofIndex(i) = mergedEdges.dst[static_cast<size_t>(i)];
dofPhi(i) = mergedEdgePhi[static_cast<size_t>(i)];
}
sortUniquePbcPairs(srcDofIndex, dstDofIndex, dofPhi);
return dstDofIndex.size() > 0;
}
bool assemblePbcProjectionMatrix(
Mesh_3D* mesh,
const Phy_WaveOpticsModel* phy,
int elementOrder,
int dof,
bool isReal,
Eigen::SparseMatrix<double, Eigen::RowMajor>& P_real,
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>& P_complex)
{
Eigen::VectorXi srcDofIndex;
Eigen::VectorXi dstDofIndex;
Eigen::VectorXcd dofPhi;
if (!collectMergedPbcConstraints(mesh, phy, elementOrder, srcDofIndex, dstDofIndex, dofPhi))
return false;
if (isReal)
buildPeriodicProjectionReal(dof, dstDofIndex, srcDofIndex, dofPhi, P_real);
else
buildPeriodicProjectionComplex(dof, dstDofIndex, srcDofIndex, dofPhi, P_complex);
return true;
}
void buildPeriodicProjectionReal(
int dof,
const Eigen::VectorXi& dstIndex,
const Eigen::VectorXi& srcIndex,
const Eigen::VectorXcd& indexPhi,
Eigen::SparseMatrix<double, Eigen::RowMajor>& P)
{
std::vector<Eigen::Triplet<double>> tempP;
int num = 0;
for (int i = 0; i < dof; i++)
{
tempP.emplace_back(i, i, 1.0);
if (num < dstIndex.size() && i == dstIndex(num))
{
tempP.emplace_back(dstIndex(num), srcIndex(num), indexPhi(num).real());
num++;
}
}
std::sort(tempP.begin(), tempP.end(),
[](const Eigen::Triplet<double>& a, const Eigen::Triplet<double>& b)
{
return a.col() < b.col();
});
std::vector<Eigen::Triplet<double>> tripleP;
num = 0;
for (int i = 0; i < dof; i++)
{
bool deleteCol = false;
for (int j = 0; j < dstIndex.size(); j++)
{
if (i == dstIndex(j))
{
deleteCol = true;
break;
}
}
if (!deleteCol)
{
for (const auto& triplet : tempP)
{
if (triplet.col() == i)
tripleP.emplace_back(triplet.row(), triplet.col() - num, triplet.value());
}
}
else
{
num++;
}
}
P = Eigen::SparseMatrix<double, Eigen::RowMajor>(dof, dof - dstIndex.size());
P.setFromTriplets(tripleP.begin(), tripleP.end());
}
void buildPeriodicProjectionComplex(
int dof,
const Eigen::VectorXi& dstIndex,
const Eigen::VectorXi& srcIndex,
const Eigen::VectorXcd& indexPhi,
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>& P)
{
std::vector<Eigen::Triplet<std::complex<double>>> tempP;
int num = 0;
for (int i = 0; i < dof; i++)
{
tempP.emplace_back(i, i, 1.0);
if (num < dstIndex.size() && i == dstIndex(num))
{
tempP.emplace_back(dstIndex(num), srcIndex(num), indexPhi(num));
num++;
}
}
std::sort(tempP.begin(), tempP.end(),
[](const Eigen::Triplet<std::complex<double>>& a, const Eigen::Triplet<std::complex<double>>& b)
{
return a.col() < b.col();
});
std::vector<Eigen::Triplet<std::complex<double>>> tripleP;
num = 0;
for (int i = 0; i < dof; i++)
{
bool deleteCol = false;
for (int j = 0; j < dstIndex.size(); j++)
{
if (i == dstIndex(j))
{
deleteCol = true;
break;
}
}
if (!deleteCol)
{
for (const auto& triplet : tempP)
{
if (triplet.col() == i)
tripleP.emplace_back(triplet.row(), triplet.col() - num, triplet.value());
}
}
else
{
num++;
}
}
P = Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>(dof, dof - dstIndex.size());
P.setFromTriplets(tripleP.begin(), tripleP.end());
}

View File

@ -0,0 +1,77 @@
#pragma once
#include "../Eigen/Dense"
#include "../Eigen/Sparse"
#include "../mesh/Mesh_Base.h"
#include "../phy/Phy_Base.h"
#include "../common/util.h"
#include <complex>
#include <vector>
struct PbcEdgePairs
{
std::vector<int> src;
std::vector<int> dst;
std::vector<std::complex<double>> sign;
};
PbcEdgePairs collectPbcEdgePairs(
Mesh_3D* mesh,
const std::vector<Eigen::VectorXi>& srcDomains,
const std::vector<Eigen::VectorXi>& dstDomains,
const Eigen::VectorXd& pbcAngles,
const Eigen::MatrixXd& pbcWaveVecs,
bool useFaceStyleEdgeTol = false);
void mergeDoublePbcPairs(
const PbcEdgePairs& pairs1,
std::complex<double> blochPhi1,
const PbcEdgePairs& pairs2,
std::complex<double> blochPhi2,
PbcEdgePairs& merged,
std::vector<std::complex<double>>& mergedPhi);
void sortUniquePbcPairs(
Eigen::VectorXi& srcEdgeIndex,
Eigen::VectorXi& dstEdgeIndex,
Eigen::VectorXcd& edgePhi);
void fillPbcGroupFromPhy(
const Phy_WaveOpticsModel* phy,
int group,
std::vector<Eigen::VectorXi>& srcDomains,
std::vector<Eigen::VectorXi>& dstDomains,
Eigen::VectorXd& pbcAngles,
Eigen::MatrixXd& pbcWaveVecs);
bool collectMergedPbcConstraints(
Mesh_3D* mesh,
const Phy_WaveOpticsModel* phy,
int elementOrder,
Eigen::VectorXi& srcDofIndex,
Eigen::VectorXi& dstDofIndex,
Eigen::VectorXcd& dofPhi);
bool assemblePbcProjectionMatrix(
Mesh_3D* mesh,
const Phy_WaveOpticsModel* phy,
int elementOrder,
int dof,
bool isReal,
Eigen::SparseMatrix<double, Eigen::RowMajor>& P_real,
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>& P_complex);
void buildPeriodicProjectionReal(
int dof,
const Eigen::VectorXi& dstIndex,
const Eigen::VectorXi& srcIndex,
const Eigen::VectorXcd& indexPhi,
Eigen::SparseMatrix<double, Eigen::RowMajor>& P);
void buildPeriodicProjectionComplex(
int dof,
const Eigen::VectorXi& dstIndex,
const Eigen::VectorXi& srcIndex,
const Eigen::VectorXcd& indexPhi,
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>& P);

View File

@ -0,0 +1,53 @@
#pragma once
#include "../Eigen/Dense"
#include <cmath>
namespace OpticsFEM {
// SBC face normal for 3D first-order Nedelec scatter (FemType=4).
// Matches MATLAB assembly_inc.m / assembly_out.m:
// inc -> [0,0,1]
// out, domain==3 -> [0,0,-1]
// other out -> normalize([sum(x)/3, sum(y)/3, 0]) from tet vertices
// Fallback when the reference formula is degenerate:
// 1) mesh NormOfFace(domain) if non-zero
// 2) geometric normal from the boundary triangle
inline Eigen::Vector3d computeScatterSBCNormal(
bool isInc,
int boundaryDomain,
const double tetX[4],
const double tetY[4],
const Eigen::Vector3d& faceP0,
const Eigen::Vector3d& faceP1,
const Eigen::Vector3d& faceP2,
const Eigen::Vector3d* meshNormFallback = nullptr)
{
using Eigen::Vector3d;
if (isInc)
return Vector3d(0.0, 0.0, 1.0);
// Prefer mesh NormOfFace (matches MATLAB assembly_out / assembly_inc).
if (meshNormFallback != nullptr && meshNormFallback->norm() >= 1e-30)
return (*meshNormFallback) / meshNormFallback->norm();
if (boundaryDomain == 3)
return Vector3d(0.0, 0.0, -1.0);
Vector3d n(
(tetX[0] + tetX[1] + tetX[2] + tetX[3]) / 3.0,
(tetY[0] + tetY[1] + tetY[2] + tetY[3]) / 3.0,
0.0);
const double len = n.norm();
if (len >= 1e-30)
return n / len;
const Vector3d geo = (faceP1 - faceP0).cross(faceP2 - faceP0);
if (geo.norm() >= 1e-30)
return geo.normalized();
return Vector3d(0.0, 0.0, -1.0);
}
} // namespace OpticsFEM

View File

@ -0,0 +1,40 @@
{
"FemType": 4,
"EletricType": 2,
"lambda": 2.0,
"NbrBoundary": 14,
"BoundaryFlag": [2, 2, 2, 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2],
"sbc": {
"Index": [1, 2, 3, 5, 14],
"SBCType": [0, 0, 0, 0, 0],
"E0x": ["0", "0", "0", "0", "0"],
"E0y": ["0", "0", "0", "0", "0"],
"E0z": ["0", "0", "0", "0", "0"],
"kx": [0, 0, 0, 0, 0],
"ky": [0, 0, 0, 0, 0],
"kz": [0, 0, 0, 0, 0]
},
"mag": {
"index": [4],
"H0x": ["0"],
"H0y": ["2.654e-3"],
"H0z": ["0"]
},
"NbrDomain": 2,
"domainType": [2, 2],
"domainIndex": [0, 1],
"matType": [0, 0],
"epsilonrR": [1.0, 1.5],
"epsilonrI": [0.0, 0.0],
"murR": [1.0, 1.0],
"murI": [0.0, 0.0],
"chiheR": [0.0, 0.0],
"chiehR": [0.0, 0.0],
"chiheI": [0.0, 0.0],
"chiehI": [0.0, 0.0],
"sigma": [0.0, 0.0],
"n": [1.0, 1.0],
"k": [0.0, 0.0],
"MeshFile": "SBCmesh.dat",
"OutFile": "./OutFile"
}

View File

@ -0,0 +1,41 @@
{
"FemType": 4,
"EletricType": 2,
"ElementOrder": 2,
"lambda": 2.0,
"NbrBoundary": 14,
"BoundaryFlag": [2, 2, 2, 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2],
"sbc": {
"Index": [1, 2, 3, 5, 14],
"SBCType": [0, 0, 0, 0, 0],
"E0x": ["0", "0", "0", "0", "0"],
"E0y": ["0", "0", "0", "0", "0"],
"E0z": ["0", "0", "0", "0", "0"],
"kx": [0, 0, 0, 0, 0],
"ky": [0, 0, 0, 0, 0],
"kz": [0, 0, 0, 0, 0]
},
"mag": {
"index": [4],
"H0x": ["0"],
"H0y": ["2.654e-3"],
"H0z": ["0"]
},
"NbrDomain": 2,
"domainType": [2, 2],
"domainIndex": [0, 1],
"matType": [0, 0],
"epsilonrR": [1.0, 1.5],
"epsilonrI": [0.0, 0.0],
"murR": [1.0, 1.0],
"murI": [0.0, 0.0],
"chiheR": [0.0, 0.0],
"chiehR": [0.0, 0.0],
"chiheI": [0.0, 0.0],
"chiehI": [0.0, 0.0],
"sigma": [0.0, 0.0],
"n": [1.0, 1.0],
"k": [0.0, 0.0],
"MeshFile": "SBCmesh.dat",
"OutFile": "./OutFile"
}

View File

@ -21,8 +21,9 @@ void MaterialLib::SetMat(int num, string name, int nbrDomain, int* domain, int m
{
_mMat[num].SetEpsr(epsr[0]);
_mMat[num].SetMur(mur[0]);
_mMat[num].SetSimga(sigma[0]);
_mMat[num].SetNK(sqrt(epsr[0]).real(), sqrt(epsr[0]).imag());
// SetNK resets sigma; apply conductivity after n,k setup (MATLAB: epsr - i*sigma*temp).
_mMat[num].SetSimga(sigma[0]);
_mMat[num].SetChihe(chihe[0], chihe[0], chihe[0]);
_mMat[num].SetChieh(chieh[0], chieh[0], chieh[0]);
}

View File

@ -77,6 +77,21 @@ int Mesh_3D::GetEdge(int numEdge, int numVertex)
return _mEdge(numEdge, numVertex);
}
int Mesh_3D::GetNbrFace()
{
return _mNbrFace;
}
int Mesh_3D::GetFace(int numFace, int numVertex)
{
return _mFace(numFace, numVertex);
}
int Mesh_3D::GetFaceOfTet(int numTet, int numFace)
{
return _mFaceOfTet(numTet, numFace);
}
int Mesh_3D::GetNbrTet()
{
return _mNbrTet;

View File

@ -2,6 +2,7 @@
#include"../Eigen/Dense"
#include<string>
#include<vector>
class Mesh_2D
{
@ -69,6 +70,10 @@ public:
int GetNbrEdge();
int GetEdge(int numEdge, int numVertex);
int GetNbrFace();
int GetFace(int numFace, int numVertex);
int GetFaceOfTet(int numTet, int numFace);
int GetNbrTet();
int GetTet(int numTet, int numVertex);
int GetEdgeOfTet(int numTet, int numEdge);
@ -94,16 +99,33 @@ public:
Eigen::VectorXi& triIndex, Eigen::VectorXi& triNum);
void GetIndexOfPBC(Eigen::MatrixXi PBC, Eigen::VectorXcd PBCData,
Eigen::VectorXi& srcEdgeIndex, Eigen::VectorXi& dstEdgeIndex, Eigen::VectorXcd& edgePhi);
void GetIndexOfPBC(const std::vector<Eigen::VectorXi>& srcDomains,
const std::vector<Eigen::VectorXi>& dstDomains,
const Eigen::VectorXcd& PBCData, const Eigen::VectorXd& pbcAngles,
const Eigen::MatrixXd& pbcWaveVecs,
Eigen::VectorXi& srcEdgeIndex, Eigen::VectorXi& dstEdgeIndex, Eigen::VectorXcd& edgePhi,
bool useFaceStyleEdgeTol = false);
void GetIndexOfPBCFaces(const std::vector<Eigen::VectorXi>& srcDomains,
const std::vector<Eigen::VectorXi>& dstDomains,
const Eigen::VectorXd& pbcAngles,
const Eigen::MatrixXd& pbcWaveVecs,
Eigen::VectorXi& srcFaceIndex, Eigen::VectorXi& dstFaceIndex, Eigen::VectorXcd& facePhi);
void GetNormOfFace(int domain, Eigen::Vector3d& norm);
void GetTriIndicesOfDomain(int domain, Eigen::VectorXi& triIndices);
void BuildFaceTopology();
private:
int _mNbrVertex = 0;
Eigen::MatrixXd _mVertex;//n*3
int _mNbrEdge = 0;
Eigen::MatrixXi _mEdge;//n*2
int _mNbrFace = 0;
Eigen::MatrixXi _mFace;//n*3
Eigen::MatrixXi _mFaceOfTet;//n*4
int _mNbrTet = 0;
Eigen::MatrixXi _mTet;//n*4
Eigen::MatrixXi _mEdgeOfTet;//n*4
@ -126,6 +148,8 @@ private:
Eigen::MatrixXi _mCopyOfTri;//n*2
Eigen::MatrixXi _mCopyOfQuad;//n*2
int _mNbrCopyOfTri = 0;
int _mNbrCopyOfQuad = 0;
Eigen::MatrixXd _mNormOfFace;// maxDomain+1 * 3, indexed by domain id
};

View File

@ -1,7 +1,11 @@
#include"Mesh_Base.h"
#include"../common/util.h"
#include<algorithm>
#include<array>
#include<complex>
#include<iostream>
#include<cmath>
#include<vector>
//get edges with edge's flag of domain
void Mesh_2D::GetIndexOfDomain(Eigen::VectorXi domain, Eigen::VectorXi& nodeIndex, Eigen::VectorXi& edgeIndex)
@ -95,6 +99,439 @@ void Mesh_2D::GetIndexOfDomain2(Eigen::VectorXi domain, Eigen::VectorXi& edgeInd
}
namespace {
void appendFaceEdgesOfTri(Mesh_3D* mesh, int triIdx, std::vector<int>& edges)
{
Eigen::Vector3i conn;
mesh->GetCoonOfTri(triIdx, conn);
const int numTet = conn(0);
const int numFace = conn(1) + 1;
int e0 = 0, e1 = 0, e2 = 0;
if (numFace == 1)
{
e0 = mesh->GetEdgeOfTet(numTet, 0);
e1 = mesh->GetEdgeOfTet(numTet, 1);
e2 = mesh->GetEdgeOfTet(numTet, 3);
}
else if (numFace == 2)
{
e0 = mesh->GetEdgeOfTet(numTet, 0);
e1 = mesh->GetEdgeOfTet(numTet, 2);
e2 = mesh->GetEdgeOfTet(numTet, 4);
}
else if (numFace == 3)
{
e0 = mesh->GetEdgeOfTet(numTet, 1);
e1 = mesh->GetEdgeOfTet(numTet, 2);
e2 = mesh->GetEdgeOfTet(numTet, 5);
}
else if (numFace == 4)
{
e0 = mesh->GetEdgeOfTet(numTet, 3);
e1 = mesh->GetEdgeOfTet(numTet, 4);
e2 = mesh->GetEdgeOfTet(numTet, 5);
}
else
{
return;
}
edges.push_back(e0);
edges.push_back(e1);
edges.push_back(e2);
}
void collectEdgesOnDomains(Mesh_3D* mesh, const Eigen::VectorXi& domains, std::vector<int>& edges)
{
// Mirror MATLAB findTri + edge collect: gather/sort tris, then unique edges.
std::vector<int> triIndex;
for (int d = 0; d < domains.size(); d++)
{
const int domainId = domains(d);
for (int j = 0; j < mesh->GetNbrTri(); j++)
{
if (mesh->GetDomainOfTri(j) == domainId)
triIndex.push_back(j);
}
}
std::sort(triIndex.begin(), triIndex.end());
for (int j : triIndex)
appendFaceEdgesOfTri(mesh, j, edges);
std::sort(edges.begin(), edges.end());
edges.erase(std::unique(edges.begin(), edges.end()), edges.end());
}
void getEdgeVertices(Mesh_3D* mesh, int edgeId, Eigen::Vector3d& p0, Eigen::Vector3d& p1)
{
mesh->GetVertex(mesh->GetEdge(edgeId, 0), p0);
mesh->GetVertex(mesh->GetEdge(edgeId, 1), p1);
}
bool matchEdgePair(Mesh_3D* mesh, int srcEdge, int dstEdge, double theta, double tol, int& signOut)
{
Eigen::Vector3d v1, v2, v3, v4;
getEdgeVertices(mesh, srcEdge, v1, v2);
getEdgeVertices(mesh, dstEdge, v3, v4);
Eigen::Matrix3d traMat;
traMat << std::cos(theta), -std::sin(theta), 0.0,
std::sin(theta), std::cos(theta), 0.0,
0.0, 0.0, 1.0;
v3 = traMat * v3;
v4 = traMat * v4;
const double l1 = (v1 - v3).norm();
const double l2 = (v2 - v4).norm();
const double l3 = (v1 - v4).norm();
const double l4 = (v2 - v3).norm();
if (l1 + l2 < tol)
{
signOut = 1;
return true;
}
if (l3 + l4 < tol)
{
signOut = -1;
return true;
}
return false;
}
// ---------------------------------------------------------------------------
// PBC pairing — mirrors MATLAB:
// order1 translation: matlab 3D一阶基+散射边界条件+周期边界/findPBCIndex.m
// order2 faces: matlab 3D二阶基+...+单周期边界/findPBCIndex.m (face
// branch), with translation instead of rotation.
// ---------------------------------------------------------------------------
static double matlabPbcTol(const Eigen::Vector3d& dis)
{
// order1: err = norm(dis)*5e-5 (keep bit-identical for validated order1)
const double dl = dis.norm();
return (dl > 0.0) ? dl * 0.00005 : 0.01 * 0.00005;
}
static double matlabFacePbcTol(const Eigen::Vector3d& dis)
{
// order2 face branch hardcodes dl=0.01 → err=5e-7. For µm translation
// periods, norm(dis)*5e-5 is ~1e-10 and rejects true pairs; keep the
// order2 floor while still scaling with larger periods.
const double dl = dis.norm();
const double rel = (dl > 0.0) ? dl * 0.00005 : 0.0;
return std::max(rel, 0.01 * 0.00005);
}
bool matchEdgePairTranslation(Mesh_3D* mesh, int srcEdge, int dstEdge,
const Eigen::Vector3d& dis, double tol, int& signOut)
{
// MATLAB: l1=|‖v1v3‖dl|, l2=|‖v2v4‖dl|; flip uses (v1,v4)/(v2,v3).
Eigen::Vector3d v1, v2, v3, v4;
getEdgeVertices(mesh, srcEdge, v1, v2);
getEdgeVertices(mesh, dstEdge, v3, v4);
const double dl = dis.norm();
const double l1 = std::abs((v1 - v3).norm() - dl);
const double l2 = std::abs((v2 - v4).norm() - dl);
const double l3 = std::abs((v1 - v4).norm() - dl);
const double l4 = std::abs((v2 - v3).norm() - dl);
if ((l1 + l2) < tol)
{
signOut = 1;
return true;
}
if ((l3 + l4) < tol)
{
signOut = -1;
return true;
}
return false;
}
void pairEdgesByTranslation(Mesh_3D* mesh,
const std::vector<int>& srcEdges,
const std::vector<int>& dstEdges,
const Eigen::Vector3d& dis,
std::vector<int>& pairedSrc,
std::vector<int>& pairedDst,
std::vector<int>& pairedSign,
bool useFaceStyleTolFloor = false)
{
const double tol = useFaceStyleTolFloor ? matlabFacePbcTol(dis) : matlabPbcTol(dis);
if (!useFaceStyleTolFloor)
{
// Order-1: MATLAB first-match, no destination locking (bit-identical).
for (int srcEdge : srcEdges)
{
bool matched = false;
for (int dstEdge : dstEdges)
{
int sign = 1;
if (matchEdgePairTranslation(mesh, srcEdge, dstEdge, dis, tol, sign))
{
pairedSrc.push_back(srcEdge);
pairedDst.push_back(dstEdge);
pairedSign.push_back(sign);
matched = true;
break;
}
}
if (!matched)
{
pairedSrc.push_back(srcEdge);
pairedDst.push_back(srcEdge);
pairedSign.push_back(1);
}
}
return;
}
// Order-2: first-match with loose tol, then drop duplicate dst (keep first).
// Pure first-match without dedup breaks projection (duplicate dst).
std::vector<int> srcU = srcEdges;
std::vector<int> dstU = dstEdges;
std::sort(srcU.begin(), srcU.end());
srcU.erase(std::unique(srcU.begin(), srcU.end()), srcU.end());
std::sort(dstU.begin(), dstU.end());
dstU.erase(std::unique(dstU.begin(), dstU.end()), dstU.end());
std::vector<char> dstUsed(static_cast<size_t>(dstU.size()), 0);
for (int srcEdge : srcU)
{
bool matched = false;
for (size_t dj = 0; dj < dstU.size(); dj++)
{
if (dstUsed[dj])
continue;
int sign = 1;
if (!matchEdgePairTranslation(mesh, srcEdge, dstU[dj], dis, tol, sign))
continue;
pairedSrc.push_back(srcEdge);
pairedDst.push_back(dstU[dj]);
pairedSign.push_back(sign);
dstUsed[dj] = 1;
matched = true;
break;
}
if (!matched)
{
pairedSrc.push_back(srcEdge);
pairedDst.push_back(srcEdge);
pairedSign.push_back(1);
}
}
return;
}
void pairEdgesByGeometry(Mesh_3D* mesh,
const std::vector<int>& srcEdges,
const std::vector<int>& dstEdges,
double theta,
std::vector<int>& pairedSrc,
std::vector<int>& pairedDst,
std::vector<int>& pairedSign)
{
// MATLAB rotation PBC: err = 0.01*5e-5, first match, no dst lock.
const double tol = 0.01 * 0.00005;
for (int srcEdge : srcEdges)
{
bool matched = false;
for (int dstEdge : dstEdges)
{
int sign = 1;
if (matchEdgePair(mesh, srcEdge, dstEdge, theta, tol, sign))
{
pairedSrc.push_back(srcEdge);
pairedDst.push_back(dstEdge);
pairedSign.push_back(sign);
matched = true;
break;
}
}
if (!matched)
{
pairedSrc.push_back(srcEdge);
pairedDst.push_back(srcEdge);
pairedSign.push_back(1);
}
}
}
void getFaceCentroid(Mesh_3D* mesh, int faceId, Eigen::Vector3d& centroid)
{
Eigen::Vector3d v0, v1, v2;
mesh->GetVertex(mesh->GetFace(faceId, 0), v0);
mesh->GetVertex(mesh->GetFace(faceId, 1), v1);
mesh->GetVertex(mesh->GetFace(faceId, 2), v2);
centroid = (v0 + v1 + v2) / 3.0;
}
void collectFacesOnDomains(Mesh_3D* mesh, const Eigen::VectorXi& domains, std::vector<int>& faces)
{
// Mirror MATLAB findTri: gather tris on all domains, sort, then FaceOfTet.
std::vector<int> triIndex;
for (int d = 0; d < domains.size(); d++)
{
const int domainId = domains(d);
for (int j = 0; j < mesh->GetNbrTri(); j++)
{
if (mesh->GetDomainOfTri(j) == domainId)
triIndex.push_back(j);
}
}
std::sort(triIndex.begin(), triIndex.end());
faces.clear();
faces.reserve(triIndex.size());
for (int j : triIndex)
{
Eigen::Vector3i conn;
mesh->GetCoonOfTri(j, conn);
faces.push_back(mesh->GetFaceOfTet(conn(0), conn(1)));
}
}
bool matchFacePair(Mesh_3D* mesh, int srcFace, int dstFace, double theta, double tol)
{
// MATLAB order2 face branch (rotation): ‖c_src R·c_dst‖ < err, sign = +1.
Eigen::Vector3d c1, c2;
getFaceCentroid(mesh, srcFace, c1);
getFaceCentroid(mesh, dstFace, c2);
Eigen::Matrix3d traMat;
traMat << std::cos(theta), -std::sin(theta), 0.0,
std::sin(theta), std::cos(theta), 0.0,
0.0, 0.0, 1.0;
c2 = traMat * c2;
return (c1 - c2).norm() < tol;
}
bool matchFacePairTranslation(Mesh_3D* mesh, int srcFace, int dstFace,
const Eigen::Vector3d& dis, double tol)
{
// Translation port of MATLAB face branch: transform dst by dis (src+dis=dst).
Eigen::Vector3d c1, c2;
getFaceCentroid(mesh, srcFace, c1);
getFaceCentroid(mesh, dstFace, c2);
return (c1 - (c2 - dis)).norm() < tol;
}
void pairFacesByTranslation(Mesh_3D* mesh,
const std::vector<int>& srcFaces,
const std::vector<int>& dstFaces,
const Eigen::Vector3d& dis,
std::vector<int>& pairedSrc,
std::vector<int>& pairedDst,
std::vector<int>& pairedSign)
{
// Face branch of MATLAB findPBCIndex (translation form of ‖c_srcT(c_dst)‖).
// MATLAB uses first-match without dst-lock; that yields duplicate dst rows and
// breaks buildPeriodicProjection* (assumes unique sorted dst). We unique face
// ids, then assign globally best residual first with exclusive dst — same
// geometric criterion, projection-safe.
std::vector<int> srcU = srcFaces;
std::vector<int> dstU = dstFaces;
std::sort(srcU.begin(), srcU.end());
srcU.erase(std::unique(srcU.begin(), srcU.end()), srcU.end());
std::sort(dstU.begin(), dstU.end());
dstU.erase(std::unique(dstU.begin(), dstU.end()), dstU.end());
const double tol = matlabFacePbcTol(dis);
const int nSrc = static_cast<int>(srcU.size());
const int nDst = static_cast<int>(dstU.size());
struct Cand { double err; int si; int di; };
std::vector<Cand> cands;
cands.reserve(static_cast<size_t>(nSrc));
for (int si = 0; si < nSrc; si++)
{
Eigen::Vector3d c1;
getFaceCentroid(mesh, srcU[static_cast<size_t>(si)], c1);
for (int di = 0; di < nDst; di++)
{
Eigen::Vector3d c2;
getFaceCentroid(mesh, dstU[static_cast<size_t>(di)], c2);
const double err = (c1 - (c2 - dis)).norm();
if (err < tol)
cands.push_back({ err, si, di });
}
}
std::sort(cands.begin(), cands.end(),
[](const Cand& a, const Cand& b) { return a.err < b.err; });
std::vector<int> matchDst(static_cast<size_t>(nSrc), -1);
std::vector<char> srcUsed(static_cast<size_t>(nSrc), 0);
std::vector<char> dstUsed(static_cast<size_t>(nDst), 0);
for (const Cand& c : cands)
{
if (srcUsed[static_cast<size_t>(c.si)] || dstUsed[static_cast<size_t>(c.di)])
continue;
srcUsed[static_cast<size_t>(c.si)] = 1;
dstUsed[static_cast<size_t>(c.di)] = 1;
matchDst[static_cast<size_t>(c.si)] = c.di;
}
for (int si = 0; si < nSrc; si++)
{
pairedSrc.push_back(srcU[static_cast<size_t>(si)]);
if (matchDst[static_cast<size_t>(si)] >= 0)
pairedDst.push_back(dstU[static_cast<size_t>(matchDst[static_cast<size_t>(si)])]);
else
pairedDst.push_back(srcU[static_cast<size_t>(si)]);
pairedSign.push_back(1);
}
}
void pairFacesByGeometry(Mesh_3D* mesh,
const std::vector<int>& srcFaces,
const std::vector<int>& dstFaces,
double theta,
std::vector<int>& pairedSrc,
std::vector<int>& pairedDst,
std::vector<int>& pairedSign)
{
const double tol = 0.01 * 0.00005;
for (int srcFace : srcFaces)
{
bool matched = false;
for (int dstFace : dstFaces)
{
if (matchFacePair(mesh, srcFace, dstFace, theta, tol))
{
pairedSrc.push_back(srcFace);
pairedDst.push_back(dstFace);
pairedSign.push_back(1);
matched = true;
break;
}
}
if (!matched)
{
pairedSrc.push_back(srcFace);
pairedDst.push_back(srcFace);
pairedSign.push_back(1);
}
}
}
void removeSelfPairs(std::vector<int>& srcEdges,
std::vector<int>& dstEdges,
std::vector<std::complex<double>>& phis)
{
std::vector<int> newSrc, newDst;
std::vector<std::complex<double>> newPhi;
for (size_t i = 0; i < srcEdges.size(); i++)
{
if (srcEdges[i] == dstEdges[i])
continue;
newSrc.push_back(srcEdges[i]);
newDst.push_back(dstEdges[i]);
newPhi.push_back(phis[i]);
}
srcEdges.swap(newSrc);
dstEdges.swap(newDst);
phis.swap(newPhi);
}
} // namespace
void Mesh_2D::GetIndexOfPBC(Eigen::MatrixXi PBC, Eigen::VectorXcd PBCData,
Eigen::VectorXi& srcNodeIndex, Eigen::VectorXi& dstNodeIndex, Eigen::VectorXcd& nodePhi,
Eigen::VectorXi& srcEdgeIndex, Eigen::VectorXi& dstEdgeIndex, Eigen::VectorXcd& edgePhi)
@ -164,8 +601,8 @@ void Mesh_2D::GetIndexOfPBC(Eigen::MatrixXi PBC, Eigen::VectorXcd PBCData,
tempDstNodeIndex[i * 2] = GetEdge(Tage_numEdge, 0);
tempDstNodeIndex[i * 2 + 1] = GetEdge(Tage_numEdge, 1);
}
//根据方向处理 DstNodeIndex 和 edgePhi
//未考虑rotational bloch bounadry
//????????? DstNodeIndex ?? edgePhi
//??????rotational bloch bounadry
for (int i = 0; i < NbrEdges; i++)
{
for (int j = 0; j < _mCopyOfVertex.rows(); j++)
@ -192,7 +629,7 @@ void Mesh_2D::GetIndexOfPBC(Eigen::MatrixXi PBC, Eigen::VectorXcd PBCData,
dstEdgeIndex(i) = Tage_numEdge;
edgePhi(i) = tempEdgePhi[i];
}
//处理重叠
//???????
Eigen::VectorXi overlapIndex1, overlapIndex2;
overlapIndex1 = Eigen::VectorXi::Zero(NbrEdges * 2);
overlapIndex2 = Eigen::VectorXi::Zero(NbrEdges * 2);
@ -398,5 +835,212 @@ void Mesh_3D::GetTriIndexOfDomain2(Eigen::VectorXi domain, Eigen::VectorXi index
void Mesh_3D::GetIndexOfPBC(Eigen::MatrixXi PBC, Eigen::VectorXcd PBCData,
Eigen::VectorXi& srcEdgeIndex, Eigen::VectorXi& dstEdgeIndex, Eigen::VectorXcd& edgePhi)
{
//未写
std::vector<Eigen::VectorXi> srcDomains(PBC.rows());
std::vector<Eigen::VectorXi> dstDomains(PBC.rows());
Eigen::VectorXd angles = Eigen::VectorXd::Zero(PBC.rows());
Eigen::MatrixXd waveVecs = Eigen::MatrixXd::Zero(3, PBC.rows());
for (int i = 0; i < PBC.rows(); i++)
{
srcDomains[i] = Eigen::VectorXi(1);
dstDomains[i] = Eigen::VectorXi(1);
srcDomains[i](0) = PBC(i, 0);
dstDomains[i](0) = PBC(i, 1);
}
GetIndexOfPBC(srcDomains, dstDomains, PBCData, angles, waveVecs, srcEdgeIndex, dstEdgeIndex, edgePhi);
}
void Mesh_3D::GetIndexOfPBC(const std::vector<Eigen::VectorXi>& srcDomains,
const std::vector<Eigen::VectorXi>& dstDomains,
const Eigen::VectorXcd& PBCData, const Eigen::VectorXd& pbcAngles,
const Eigen::MatrixXd& pbcWaveVecs,
Eigen::VectorXi& srcEdgeIndex, Eigen::VectorXi& dstEdgeIndex, Eigen::VectorXcd& edgePhi,
bool useFaceStyleEdgeTol)
{
std::vector<int> allSrc, allDst;
std::vector<std::complex<double>> allPhi;
for (int pair = 0; pair < static_cast<int>(srcDomains.size()); pair++)
{
std::vector<int> srcEdges, dstEdges;
collectEdgesOnDomains(this, srcDomains[pair], srcEdges);
collectEdgesOnDomains(this, dstDomains[pair], dstEdges);
const double theta = (pair < pbcAngles.size()) ? pbcAngles(pair) : 0.0;
const std::complex<double> blochPhi = (pair < PBCData.size()) ? PBCData(pair) : 1.0;
Eigen::Vector3d waveVec = Eigen::Vector3d::Zero();
if (pair < pbcWaveVecs.cols())
waveVec = pbcWaveVecs.col(pair);
if (_mNbrCopyOfTri > 0)
{
for (int i = 0; i < _mNbrCopyOfTri; i++)
{
allSrc.push_back(_mCopyOfTri(i, 0));
allDst.push_back(_mCopyOfTri(i, 1));
allPhi.push_back(blochPhi);
}
continue;
}
std::vector<int> pairedSrc, pairedDst, pairedSign;
if (waveVec.norm() >= 1e-30)
pairEdgesByTranslation(this, srcEdges, dstEdges, waveVec, pairedSrc, pairedDst, pairedSign,
useFaceStyleEdgeTol);
else
pairEdgesByGeometry(this, srcEdges, dstEdges, theta, pairedSrc, pairedDst, pairedSign);
for (size_t i = 0; i < pairedSrc.size(); i++)
{
allSrc.push_back(pairedSrc[i]);
allDst.push_back(pairedDst[i]);
allPhi.push_back(blochPhi * static_cast<double>(pairedSign[i]));
}
}
removeSelfPairs(allSrc, allDst, allPhi);
const int n = static_cast<int>(allSrc.size());
srcEdgeIndex = Eigen::VectorXi(n);
dstEdgeIndex = Eigen::VectorXi(n);
edgePhi = Eigen::VectorXcd(n);
for (int i = 0; i < n; i++)
{
srcEdgeIndex(i) = allSrc[i];
dstEdgeIndex(i) = allDst[i];
edgePhi(i) = allPhi[i];
}
if (n == 0)
return;
Eigen::VectorXi tempIndex = Eigen::VectorXi::LinSpaced(n, 0, n - 1);
QuickSort(dstEdgeIndex, tempIndex, 0, n - 1);
Unique(dstEdgeIndex, tempIndex);
Eigen::VectorXcd tempPhi = Eigen::VectorXcd::Zero(dstEdgeIndex.size());
Eigen::VectorXi tempSrc = Eigen::VectorXi::Zero(dstEdgeIndex.size());
for (int i = 0; i < dstEdgeIndex.size(); i++)
{
tempPhi(i) = edgePhi(tempIndex(i));
tempSrc(i) = srcEdgeIndex(tempIndex(i));
}
edgePhi = tempPhi;
srcEdgeIndex = tempSrc;
}
void Mesh_3D::GetIndexOfPBCFaces(const std::vector<Eigen::VectorXi>& srcDomains,
const std::vector<Eigen::VectorXi>& dstDomains,
const Eigen::VectorXd& pbcAngles,
const Eigen::MatrixXd& pbcWaveVecs,
Eigen::VectorXi& srcFaceIndex, Eigen::VectorXi& dstFaceIndex, Eigen::VectorXcd& facePhi)
{
std::vector<int> allSrc, allDst;
std::vector<std::complex<double>> allPhi;
for (int pair = 0; pair < static_cast<int>(srcDomains.size()); pair++)
{
// MATLAB findPBCIndex(src,dst,...): all src domains together, all dst together.
std::vector<int> srcFaces, dstFaces;
collectFacesOnDomains(this, srcDomains[pair], srcFaces);
collectFacesOnDomains(this, dstDomains[pair], dstFaces);
const double theta = (pair < pbcAngles.size()) ? pbcAngles(pair) : 0.0;
Eigen::Vector3d waveVec = Eigen::Vector3d::Zero();
if (pair < pbcWaveVecs.cols())
waveVec = pbcWaveVecs.col(pair);
std::vector<int> pairedSrc, pairedDst, pairedSign;
if (waveVec.norm() >= 1e-30)
pairFacesByTranslation(this, srcFaces, dstFaces, waveVec, pairedSrc, pairedDst, pairedSign);
else
pairFacesByGeometry(this, srcFaces, dstFaces, theta, pairedSrc, pairedDst, pairedSign);
for (size_t i = 0; i < pairedSrc.size(); i++)
{
allSrc.push_back(pairedSrc[i]);
allDst.push_back(pairedDst[i]);
allPhi.push_back(static_cast<double>(pairedSign[i]));
}
}
removeSelfPairs(allSrc, allDst, allPhi);
const int n = static_cast<int>(allSrc.size());
srcFaceIndex = Eigen::VectorXi(n);
dstFaceIndex = Eigen::VectorXi(n);
facePhi = Eigen::VectorXcd(n);
for (int i = 0; i < n; i++)
{
srcFaceIndex(i) = allSrc[i];
dstFaceIndex(i) = allDst[i];
facePhi(i) = allPhi[i];
}
if (n == 0)
return;
Eigen::VectorXi tempIndex = Eigen::VectorXi::LinSpaced(n, 0, n - 1);
QuickSort(dstFaceIndex, tempIndex, 0, n - 1);
Unique(dstFaceIndex, tempIndex);
Eigen::VectorXcd tempPhi = Eigen::VectorXcd::Zero(dstFaceIndex.size());
Eigen::VectorXi tempSrc = Eigen::VectorXi::Zero(dstFaceIndex.size());
for (int i = 0; i < dstFaceIndex.size(); i++)
{
tempPhi(i) = facePhi(tempIndex(i));
tempSrc(i) = srcFaceIndex(tempIndex(i));
}
facePhi = tempPhi;
srcFaceIndex = tempSrc;
}
// Match MATLAB getMesh.m: unique face rows in column-major order (face slot, then tet).
void Mesh_3D::BuildFaceTopology()
{
if (_mNbrTet <= 0)
{
_mNbrFace = 0;
return;
}
static const int kFaceVert[4][3] = { {0, 1, 2}, {0, 1, 3}, {0, 2, 3}, {1, 2, 3} };
std::vector<std::array<int, 3>> faces;
faces.reserve(static_cast<size_t>(_mNbrTet) * 4);
_mFaceOfTet = Eigen::MatrixXi::Zero(_mNbrTet, 4);
auto findFace = [&](const std::array<int, 3>& key) -> int {
for (int i = 0; i < static_cast<int>(faces.size()); i++)
{
if (faces[static_cast<size_t>(i)] == key)
return i;
}
return -1;
};
for (int f = 0; f < 4; f++)
{
for (int t = 0; t < _mNbrTet; t++)
{
std::array<int, 3> key = {
_mTet(t, kFaceVert[f][0]),
_mTet(t, kFaceVert[f][1]),
_mTet(t, kFaceVert[f][2])
};
int fid = findFace(key);
if (fid < 0)
{
fid = static_cast<int>(faces.size());
faces.push_back(key);
}
_mFaceOfTet(t, f) = fid;
}
}
_mNbrFace = static_cast<int>(faces.size());
_mFace = Eigen::MatrixXi(_mNbrFace, 3);
for (int i = 0; i < _mNbrFace; i++)
{
_mFace(i, 0) = faces[static_cast<size_t>(i)][0];
_mFace(i, 1) = faces[static_cast<size_t>(i)][1];
_mFace(i, 2) = faces[static_cast<size_t>(i)][2];
}
}

View File

@ -331,6 +331,24 @@ void Mesh_3D::GetMesh(std::string str, std::string jstr)
setDefaultNorm(14, 1.0, 0.0, 0.0);
}
fd.close();
if (fd >> data && data == "NbrCopyOfTri")
{
fd >> _mNbrCopyOfTri;
if (fd >> data && data == "CopyOfTri")
{
_mCopyOfTri = Eigen::MatrixXi(_mNbrCopyOfTri, 2);
for (int i = 0; i < _mNbrCopyOfTri; i++)
{
int a = 0, b = 0;
fd >> a >> b;
_mCopyOfTri(i, 0) = a - 1;
_mCopyOfTri(i, 1) = b - 1;
}
}
}
fd.close();
BuildFaceTopology();
}

View File

@ -0,0 +1,41 @@
{
"FemType": 4,
"EletricType": 2,
"lambda": 0.049965,
"NbrBoundary": 9,
"BoundaryFlag": [4, 4, 2, 4, 4, 0, 0, 2, 2],
"sbc": {
"Index": [3, 9, 8],
"SBCType": [0, 0, 1],
"E0x": ["0.5", "0.5", "0.5"],
"E0y": ["0-0.5i", "0-0.5i", "0-0.5i"],
"E0z": ["0", "0", "0"],
"kx": [0, 0, 0],
"ky": [0, 0, 0],
"kz": [0, 0, 0]
},
"pbc": {
"srcIndex": [1, 4],
"dstIndex": [2, 5],
"phiR": [0.5],
"phiI": [0.8660254037844386],
"pbcAngle": 1.0471975511965976
},
"NbrDomain": 2,
"domainType": [2, 2],
"domainIndex": [0, 1],
"matType": [0, 0],
"epsilonrR": [1, 1.2],
"epsilonrI": [0, 0],
"murR": [1, 1],
"murI": [0, 0],
"chiheR": [0, 0],
"chiehR": [0, 0],
"chiheI": [0, 0],
"chiehI": [0, 0],
"sigma": [0, 0],
"n": [1, 1],
"k": [0, 0],
"MeshFile": "PBCmesh.dat",
"OutFile": "./OutFile"
}

View File

@ -1,5 +1,6 @@
#include"Phy_Base.h"
#include <iostream>
#include <vector>
Phy_WaveOpticsModel::Phy_WaveOpticsModel()
{
@ -7,8 +8,7 @@ Phy_WaveOpticsModel::Phy_WaveOpticsModel()
_mPEC = Eigen::VectorXi::Zero(0);
_mPBC = Eigen::MatrixXi::Zero(0, 2);
_mPBCData = Eigen::VectorXcd(0);
_mPBCGroups.clear();
_mPML = Eigen::VectorXi::Zero(0);
_mPMLData = Eigen::MatrixXd::Zero(0, 6);
@ -31,19 +31,113 @@ int Phy_WaveOpticsModel::GetPECDomain(int num)
int Phy_WaveOpticsModel::GetNbrPBC()
{
return _mPBC.rows();
return GetNbrPBCInGroup(0);
}
int Phy_WaveOpticsModel::GetNbrPBCGroups() const
{
return static_cast<int>(_mPBCGroups.size());
}
int Phy_WaveOpticsModel::GetNbrPBCInGroup(int group) const
{
if (group < 0 || group >= static_cast<int>(_mPBCGroups.size()))
return 0;
return _mPBCGroups[group].pbc.rows();
}
int Phy_WaveOpticsModel::GetSrcDomain(int num)
{
return _mPBC(num, 0);
if (_mPBCGroups.empty())
return 0;
return _mPBCGroups[0].pbc(num, 0);
}
int Phy_WaveOpticsModel::GetDstDomain(int num)
{
return _mPBC(num, 1);
if (_mPBCGroups.empty())
return 0;
return _mPBCGroups[0].pbc(num, 1);
}
std::complex<double> Phy_WaveOpticsModel::GetPBCPhi(int num)
{
return _mPBCData(num);
return GetPBCPhi(0, num);
}
std::complex<double> Phy_WaveOpticsModel::GetPBCPhi(int group, int num) const
{
if (group < 0 || group >= static_cast<int>(_mPBCGroups.size()))
return 1.0;
return _mPBCGroups[group].data(num);
}
void Phy_WaveOpticsModel::GetSrcDomainList(int num, Eigen::VectorXi& domains)
{
GetSrcDomainList(0, num, domains);
}
void Phy_WaveOpticsModel::GetSrcDomainList(int group, int num, Eigen::VectorXi& domains) const
{
if (group < 0 || group >= static_cast<int>(_mPBCGroups.size()))
{
domains = Eigen::VectorXi(0);
return;
}
if (num >= 0 && num < static_cast<int>(_mPBCGroups[group].srcDomains.size()))
domains = _mPBCGroups[group].srcDomains[num];
else if (num >= 0 && num < _mPBCGroups[group].pbc.rows())
{
domains = Eigen::VectorXi(1);
domains(0) = _mPBCGroups[group].pbc(num, 0);
}
}
void Phy_WaveOpticsModel::GetDstDomainList(int num, Eigen::VectorXi& domains)
{
GetDstDomainList(0, num, domains);
}
void Phy_WaveOpticsModel::GetDstDomainList(int group, int num, Eigen::VectorXi& domains) const
{
if (group < 0 || group >= static_cast<int>(_mPBCGroups.size()))
{
domains = Eigen::VectorXi(0);
return;
}
if (num >= 0 && num < static_cast<int>(_mPBCGroups[group].dstDomains.size()))
domains = _mPBCGroups[group].dstDomains[num];
else if (num >= 0 && num < _mPBCGroups[group].pbc.rows())
{
domains = Eigen::VectorXi(1);
domains(0) = _mPBCGroups[group].pbc(num, 1);
}
}
double Phy_WaveOpticsModel::GetPBCAngle(int num)
{
return GetPBCAngle(0, num);
}
double Phy_WaveOpticsModel::GetPBCAngle(int group, int num) const
{
if (group < 0 || group >= static_cast<int>(_mPBCGroups.size()))
return 0.0;
if (num >= 0 && num < _mPBCGroups[group].angle.size())
return _mPBCGroups[group].angle(num);
return 0.0;
}
void Phy_WaveOpticsModel::GetPBCWaveVec(int num, Eigen::Vector3d& waveVec)
{
GetPBCWaveVec(0, num, waveVec);
}
void Phy_WaveOpticsModel::GetPBCWaveVec(int group, int num, Eigen::Vector3d& waveVec) const
{
waveVec = Eigen::Vector3d::Zero();
if (group < 0 || group >= static_cast<int>(_mPBCGroups.size()))
return;
if (num >= 0 && num < _mPBCGroups[group].waveVec.cols())
waveVec = _mPBCGroups[group].waveVec.col(num);
}
int Phy_WaveOpticsModel::GetNbrPML()
@ -165,6 +259,13 @@ void Phy_WaveOpticsModel::GetCurlEb(std::string& curlEbx, std::string& curlEby,
curlEbz = _mcurlEbz;
}
void Phy_WaveOpticsModel::GetCurlCurlEb(std::string& curlcurlEbx, std::string& curlcurlEby, std::string& curlcurlEbz)
{
curlcurlEbx = _mcurlcurlEbx;
curlcurlEby = _mcurlcurlEby;
curlcurlEbz = _mcurlcurlEbz;
}
void Phy_WaveOpticsModel::GetE0(std::string& E0x, std::string& E0y, std::string& E0z,int num)
{
E0x = _mE0x[num];

View File

@ -1,13 +1,25 @@
#pragma once
#include"../Eigen/Dense"
#include"PortModeData.h"
#include<complex>
#include<string>
#include<vector>
class Phy_WaveOpticsModel
{
public:
struct PbcGroupConfig
{
Eigen::MatrixXi pbc;
Eigen::VectorXcd data;
std::vector<Eigen::VectorXi> srcDomains;
std::vector<Eigen::VectorXi> dstDomains;
Eigen::VectorXd angle;
Eigen::MatrixXd waveVec;
};
Phy_WaveOpticsModel();
void GetPhy();
@ -16,9 +28,20 @@ public:
int GetPECDomain(int num);
int GetNbrPBC();
int GetNbrPBCGroups() const;
int GetNbrPBCInGroup(int group) const;
int GetSrcDomain(int num);
int GetDstDomain(int num);
std::complex<double> GetPBCPhi(int num);
std::complex<double> GetPBCPhi(int group, int num) const;
void GetSrcDomainList(int num, Eigen::VectorXi& domains);
void GetSrcDomainList(int group, int num, Eigen::VectorXi& domains) const;
void GetDstDomainList(int num, Eigen::VectorXi& domains);
void GetDstDomainList(int group, int num, Eigen::VectorXi& domains) const;
double GetPBCAngle(int num);
double GetPBCAngle(int group, int num) const;
void GetPBCWaveVec(int num, Eigen::Vector3d& waveVec);
void GetPBCWaveVec(int group, int num, Eigen::Vector3d& waveVec) const;
int GetNbrPML();
int GetPMLEdge(int num);
@ -37,6 +60,7 @@ public:
int GetBELEDomain(int num);
void GetEb(std::string& Ebx, std::string& Eby, std::string& Ebz);
void GetCurlEb(std::string& curlEbx, std::string& curlEby, std::string& curlEbz);
void GetCurlCurlEb(std::string& curlcurlEbx, std::string& curlcurlEby, std::string& curlcurlEbz);
int GetNbrElE();
int GetELEDomain(int num);
@ -67,53 +91,62 @@ public:
void GetPortEincCurl(std::string& EincCurlx, std::string& EincCurly, std::string& EincCurlz);
void GetPortEoutCurl(std::string& EoutCurlx, std::string& EoutCurly, std::string& EoutCurlz);
bool HasNumericPort() const { return _mPortModes.HasModes(); }
int GetNbrNumericPort() const { return _mPortModes.GetNbrModes(); }
const PortNumericMode& GetNumericPort(int i) const { return _mPortModes.GetMode(i); }
PortModeLibrary& MutablePortModes() { return _mPortModes; }
int GetBeamState();
void GetBeamDir(double& kx,double& ky);
void Test_ReadData(std::string str);
int GetElementOrder() const { return _mElementOrder; }
private:
Eigen::VectorXi _mPMC;//n*1 完美磁导体
Eigen::VectorXi _mPMC;//n*1 ?????????
Eigen::VectorXi _mPEC;//n*1 完美电导体
Eigen::VectorXi _mPEC;//n*1 ?????????
Eigen::MatrixXi _mPBC;//n*2 周期边界条件
Eigen::VectorXcd _mPBCData;//n*1
std::vector<PbcGroupConfig> _mPBCGroups;
Eigen::VectorXi _mPML;//n*1 完美匹配层
Eigen::VectorXi _mPML;//n*1 ????????
Eigen::VectorXi _mPMLType;
Eigen::VectorXi _mPMLEdge;
double _mPMLLda;//典型波长
Eigen::MatrixXd _mPMLData;//n*6 x0 1/dx y0 1/dy z0 1/dz 无d则1/d=0
double _mPMLLda;//???????
Eigen::MatrixXd _mPMLData;//n*6 x0 1/dx y0 1/dy z0 1/dz ??d??1/d=0
Eigen::MatrixXi _mSBC;//n*2 散射边界条件 0为无入射 1为有入射
Eigen::MatrixXi _mSBC;//n*2 ????????? 0??????? 1???????
Eigen::MatrixXd _mSBCDir; //n*3
std::string* _mEincx, * _mEincy, * _mEincz;
Eigen::VectorXi _mELE;//n*1 强加电场
Eigen::VectorXi _mELE;//n*1 ????T
std::string* _mE0x, *_mE0y, *_mE0z;
Eigen::VectorXi _mMAG;//n*1 强加磁场
Eigen::VectorXi _mMAG;//n*1 ?????
std::string* _mH0x, *_mH0y, *_mH0z;
Eigen::VectorXi _mSCD;//n*1 表面电流密度
Eigen::VectorXi _mSCD;//n*1 ??????????
std::string* _mJ0x, *_mJ0y, *_mJ0z;
Eigen::VectorXi _mMPD; //n*3 磁偶极子
Eigen::VectorXi _mMPD; //n*3 ???????
Eigen::MatrixXd _mMPDData; //n*3
Eigen::VectorXi _mEPD; //n*3 电偶极子
Eigen::VectorXi _mEPD; //n*3 ???????
Eigen::MatrixXd _mEPDData; //n*3
Eigen::VectorXi _mBELE; //n*1 背景场
Eigen::VectorXi _mBELE; //n*1 ??????
std::string _mEbx, _mEby, _mEbz;
std::string _mcurlEbx, _mcurlEby, _mcurlEbz;
std::string _mcurlcurlEbx, _mcurlcurlEby, _mcurlcurlEbz;
Eigen::VectorXi _mPortinc;//n*1 端口边界条件
Eigen::VectorXi _mPortout;//n*1 端口边界条件
std::string* _mPortEinc, * _mPortEout, * _mPortEincCurl, * _mPortEoutCurl; //入射场及出射场定义
Eigen::VectorXi _mPortinc;//n*1 ?????????
Eigen::VectorXi _mPortout;//n*1 ?????????
std::string* _mPortEinc, * _mPortEout, * _mPortEincCurl, * _mPortEoutCurl; //??????????????
PortModeLibrary _mPortModes;
int _mBeam = 0;//0为不启用波束 1为启用波束
double _mkx = 0.;//波束方向kx
double _mky = 0;//波束方向ky
int _mBeam = 0;//0??????????? 1?????????
double _mkx = 0.;//????????kx
double _mky = 0;//????????ky
int _mElementOrder = 1;
};

View File

@ -0,0 +1,93 @@
#include "PortModeData.h"
#include "../nlohmann/json.hpp"
#include <fstream>
#include <iostream>
using json = nlohmann::json;
static Eigen::VectorXcd readComplexArrays(const json& reArr, const json& imArr)
{
const size_t n = reArr.size();
Eigen::VectorXcd v(static_cast<int>(n));
for (size_t i = 0; i < n; i++)
v(static_cast<int>(i)) = std::complex<double>(reArr[i].get<double>(), imArr[i].get<double>());
return v;
}
static PortNumericMode parseMode(const json& j)
{
PortNumericMode m;
m.type = j.at("type").get<int>();
const auto& g = j.at("gamma");
m.gamma = std::complex<double>(g[0].get<double>(), g[1].get<double>());
m.powerCoef = j.at("powerCoef").get<double>();
const auto& n = j.at("normal");
m.normal << n[0].get<double>(), n[1].get<double>(), n[2].get<double>();
const auto& fc = j.at("facesConn");
m.facesConn = Eigen::MatrixXi::Zero(static_cast<int>(fc.size()), 2);
for (size_t i = 0; i < fc.size(); i++)
{
m.facesConn(static_cast<int>(i), 0) = fc[i][0].get<int>();
m.facesConn(static_cast<int>(i), 1) = fc[i][1].get<int>();
}
const auto& pf = j.at("portNewFaces");
m.portNewFaces = Eigen::MatrixXi::Zero(static_cast<int>(pf.size()), 3);
for (size_t i = 0; i < pf.size(); i++)
for (int k = 0; k < 3; k++)
m.portNewFaces(static_cast<int>(i), k) = pf[i][k].get<int>();
const auto& pe = j.at("portEdgeOfFace");
m.portEdgeOfFace = Eigen::MatrixXi::Zero(static_cast<int>(pe.size()), 3);
for (size_t i = 0; i < pe.size(); i++)
for (int k = 0; k < 3; k++)
m.portEdgeOfFace(static_cast<int>(i), k) = pe[i][k].get<int>();
const auto& nodes = j.at("portNodes");
m.portNodes = Eigen::MatrixXd::Zero(static_cast<int>(nodes.size()), 3);
for (size_t i = 0; i < nodes.size(); i++)
for (int k = 0; k < 3; k++)
m.portNodes(static_cast<int>(i), k) = nodes[i][k].get<double>();
m.Ez = readComplexArrays(j.at("Ez_re"), j.at("Ez_im"));
m.Et = readComplexArrays(j.at("Et_re"), j.at("Et_im"));
return m;
}
void PortModeLibrary::Clear()
{
_mModes.clear();
}
void PortModeLibrary::AddMode(const PortNumericMode& m)
{
_mModes.push_back(m);
}
bool PortModeLibrary::LoadFromFile(const std::string& path)
{
_mModes.clear();
std::ifstream ifs(path);
if (!ifs.is_open())
{
std::cerr << "[PortModeLibrary] cannot open " << path << std::endl;
return false;
}
json js;
try
{
ifs >> js;
}
catch (const std::exception& e)
{
std::cerr << "[PortModeLibrary] JSON parse error: " << e.what() << std::endl;
return false;
}
if (js.contains("input"))
_mModes.push_back(parseMode(js.at("input")));
if (js.contains("output"))
_mModes.push_back(parseMode(js.at("output")));
return !_mModes.empty();
}

View File

@ -0,0 +1,36 @@
#pragma once
#include "../Eigen/Dense"
#include <complex>
#include <string>
#include <vector>
struct PortNumericMode
{
int type = 0; // 1 = input, 2 = output
std::complex<double> gamma = 0.0;
double powerCoef = 1.0;
Eigen::Vector3d normal = Eigen::Vector3d::Zero();
Eigen::MatrixXi facesConn; // nf x 2 (1-based tet index, 1-based local face)
Eigen::MatrixXi portNewFaces; // nf x 3 (0-based port node indices)
Eigen::MatrixXi portEdgeOfFace; // nf x 3 (0-based port edge DOF index)
Eigen::MatrixXd portNodes;
Eigen::VectorXcd Ez;
Eigen::VectorXcd Et;
};
class PortModeLibrary
{
public:
bool LoadFromFile(const std::string& path);
void Clear();
void AddMode(const PortNumericMode& m);
bool HasModes() const { return !_mModes.empty(); }
int GetNbrModes() const { return static_cast<int>(_mModes.size()); }
const PortNumericMode& GetMode(int i) const { return _mModes[static_cast<size_t>(i)]; }
private:
std::vector<PortNumericMode> _mModes;
};

View File

@ -0,0 +1,595 @@
#include "PortModeSolver.h"
#include "../mesh/Mesh_Base.h"
#include "../material/Material_Base.h"
#include "../function/BF.h"
#include "../function/Gauss.h"
#include "../common/define.h"
#include "../nlohmann/json.hpp"
#include "../Eigen/Sparse"
#include "../Eigen/SparseLU"
#include "../Eigen/Eigenvalues"
#include <algorithm>
#include <array>
#include <cmath>
#include <complex>
#include <fstream>
#include <iostream>
#include <map>
#include <set>
#include <vector>
using namespace Eigen;
using json = nlohmann::json;
namespace {
struct PortBoundaryMesh
{
MatrixXd nodes; // nNodes x 3
MatrixXi newFaces; // nf x 3 (0-based local node)
MatrixXi facesConn; // nf x 2 (1-based tet, 1-based local face)
VectorXi volDomain; // nf (1-based volume domain for material)
MatrixXi edgeOfFace; // nf x 3 (0-based edge DOF)
MatrixXi edgeNodes; // nEdges x 2 (0-based local nodes)
int nNodes = 0;
int nEdges = 0;
int nFaces = 0;
};
bool domainInList(int domain, const VectorXi& list)
{
for (int i = 0; i < list.size(); ++i)
if (list(i) == domain)
return true;
return false;
}
bool extractPortMesh(Mesh_3D* mesh, const VectorXi& domains, PortBoundaryMesh& out)
{
std::vector<int> triIds;
const int nTri = mesh->GetNbrTri();
for (int t = 0; t < nTri; ++t)
{
if (domainInList(mesh->GetDomainOfTri(t), domains))
triIds.push_back(t);
}
if (triIds.empty())
{
std::cerr << "[PortModeSolver] no port triangles for domains" << std::endl;
return false;
}
const int nf = static_cast<int>(triIds.size());
MatrixXi oldFaces(nf, 3);
out.facesConn = MatrixXi::Zero(nf, 2);
out.volDomain = VectorXi::Zero(nf);
std::vector<int> globalNodes;
globalNodes.reserve(nf * 3);
for (int i = 0; i < nf; ++i)
{
const int tri = triIds[i];
for (int k = 0; k < 3; ++k)
{
oldFaces(i, k) = mesh->GetTri(tri, k);
globalNodes.push_back(oldFaces(i, k));
}
Vector3i conn;
mesh->GetCoonOfTri(tri, conn);
out.facesConn(i, 0) = conn(0) + 1;
out.facesConn(i, 1) = conn(1) + 1;
out.volDomain(i) = mesh->GetDomainOfTet(conn(0));
}
std::sort(globalNodes.begin(), globalNodes.end());
globalNodes.erase(std::unique(globalNodes.begin(), globalNodes.end()), globalNodes.end());
out.nNodes = static_cast<int>(globalNodes.size());
out.nodes = MatrixXd::Zero(out.nNodes, 3);
std::map<int, int> g2l;
for (int i = 0; i < out.nNodes; ++i)
{
g2l[globalNodes[i]] = i;
Vector3d v;
mesh->GetVertex(globalNodes[i], v);
out.nodes.row(i) = v.transpose();
}
out.newFaces = MatrixXi::Zero(nf, 3);
for (int i = 0; i < nf; ++i)
for (int k = 0; k < 3; ++k)
out.newFaces(i, k) = g2l[oldFaces(i, k)];
// Local edges like MATLAB: (n1,n2),(n1,n3),(n2,n3) without sorting pair; unique by lexicographic rows.
std::vector<std::array<int, 2>> edgeRows;
edgeRows.reserve(static_cast<size_t>(nf) * 3);
for (int i = 0; i < nf; ++i)
{
const int a = out.newFaces(i, 0);
const int b = out.newFaces(i, 1);
const int c = out.newFaces(i, 2);
edgeRows.push_back({ a, b });
edgeRows.push_back({ a, c });
edgeRows.push_back({ b, c });
}
std::vector<std::array<int, 2>> uniqueEdges = edgeRows;
std::sort(uniqueEdges.begin(), uniqueEdges.end());
uniqueEdges.erase(std::unique(uniqueEdges.begin(), uniqueEdges.end()), uniqueEdges.end());
out.nEdges = static_cast<int>(uniqueEdges.size());
std::map<std::array<int, 2>, int> e2i;
for (int i = 0; i < out.nEdges; ++i)
e2i[uniqueEdges[i]] = i;
out.edgeOfFace = MatrixXi::Zero(nf, 3);
out.nFaces = nf;
for (int i = 0; i < nf; ++i)
{
for (int k = 0; k < 3; ++k)
out.edgeOfFace(i, k) = e2i[edgeRows[static_cast<size_t>(i) * 3 + k]];
}
out.edgeNodes = MatrixXi::Zero(out.nEdges, 2);
for (int i = 0; i < out.nEdges; ++i)
{
out.edgeNodes(i, 0) = uniqueEdges[static_cast<size_t>(i)][0];
out.edgeNodes(i, 1) = uniqueEdges[static_cast<size_t>(i)][1];
}
return true;
}
void assemblePortEigen(
const PortBoundaryMesh& pm,
MaterialLib* matLib,
double lam0,
SparseMatrix<std::complex<double>, RowMajor>& Ared,
SparseMatrix<std::complex<double>, RowMajor>& Bred,
SparseMatrix<std::complex<double>, RowMajor>& P,
int& dofFull)
{
const double k0 = 2.0 * Pi / lam0;
const int nNodes = pm.nNodes;
const int nEdges = pm.nEdges;
dofFull = nNodes + nEdges;
Gauss gauss;
const int nGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, BF_LINEFUNC * 2);
std::vector<double> u(nGP), v(nGP), w(nGP), wght(nGP);
gauss.GetGaussPoints(TWODIM, TRIANGLE, u.data(), v.data(), w.data(), wght.data());
BF bfEt, bfCurlEt, bfEz, bfGradEz;
bfEt.GetNbrBF(TWODIM, TRIANGLE, BF_NEDELEC, BF_LINEFUNC);
bfCurlEt.GetNbrBF(TWODIM, TRIANGLE, BF_CURL_NEDELEC, BF_LINEFUNC);
bfEz.GetNbrBF(TWODIM, TRIANGLE, BF_LAGRANGE, BF_LINEFUNC);
bfGradEz.GetNbrBF(TWODIM, TRIANGLE, BF_GRAD_LAGRANGE, BF_LINEFUNC);
using Trip = Triplet<std::complex<double>>;
std::vector<Trip> tripsA, tripsB;
tripsA.reserve(static_cast<size_t>(pm.nFaces) * 9);
tripsB.reserve(static_cast<size_t>(pm.nFaces) * 36);
std::vector<int> edgeCount(static_cast<size_t>(nEdges), 0);
for (int f = 0; f < pm.nFaces; ++f)
for (int k = 0; k < 3; ++k)
edgeCount[static_cast<size_t>(pm.edgeOfFace(f, k))]++;
for (int n = 0; n < pm.nFaces; ++n)
{
double x[3], y[3], l[3];
for (int i = 0; i < 3; ++i)
{
x[i] = pm.nodes(pm.newFaces(n, i), 0);
y[i] = pm.nodes(pm.newFaces(n, i), 1);
}
l[0] = std::sqrt((x[0] - x[1]) * (x[0] - x[1]) + (y[0] - y[1]) * (y[0] - y[1]));
l[1] = std::sqrt((x[0] - x[2]) * (x[0] - x[2]) + (y[0] - y[2]) * (y[0] - y[2]));
l[2] = std::sqrt((x[2] - x[1]) * (x[2] - x[1]) + (y[2] - y[1]) * (y[2] - y[1]));
Matrix3d Jac = Matrix3d::Zero();
Jac(0, 0) = x[1] - x[0]; Jac(0, 1) = y[1] - y[0];
Jac(1, 0) = x[2] - x[0]; Jac(1, 1) = y[2] - y[0];
Jac(2, 2) = 1.0;
const Matrix3d InvJac = Jac.inverse();
const double DetJac = std::abs(Jac.determinant());
const Matrix3d TJac = Jac.transpose() / Jac.determinant();
std::vector<Vector3d> Et(3 * nGP), curlEt(3 * nGP), Ez(3 * nGP), gradEz(3 * nGP);
auto at = [nGP](std::vector<Vector3d>& a, int j, int gp) -> Vector3d& {
return a[static_cast<size_t>(j * nGP + gp)];
};
for (int gp = 0; gp < nGP; ++gp)
{
for (int j = 0; j < 3; ++j)
{
Vector3d temp;
bfEt.GetValueBF(j + 1, u[gp], v[gp], 0.0, temp);
at(Et, j, gp) = InvJac * temp * l[j];
bfCurlEt.GetValueBF(j + 1, u[gp], v[gp], 0.0, temp);
at(curlEt, j, gp) = TJac * temp * l[j];
bfEz.GetValueBF(j + 1, u[gp], v[gp], 0.0, temp);
at(Ez, j, gp) = temp;
bfGradEz.GetValueBF(j + 1, u[gp], v[gp], 0.0, temp);
at(gradEz, j, gp) = InvJac * temp;
}
}
const int domain = pm.volDomain(n);
Matrix3cd epsr = matLib->GetEpsr(domain);
const Matrix3d sigma = matLib->GetSigma(domain);
epsr = epsr - sigma * std::complex<double>(0.0, 1.0 / k0 * 120.0 * Pi);
const Matrix3cd mur = matLib->GetMur(domain);
const std::complex<double> mu = mur(0, 0);
const std::complex<double> epsilon = epsr(0, 0);
Matrix3cd St = Matrix3cd::Zero(), Tte = Matrix3cd::Zero();
Matrix3cd Sz = Matrix3cd::Zero(), Tz = Matrix3cd::Zero();
Matrix3cd G = Matrix3cd::Zero(), Ttu = Matrix3cd::Zero();
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 3; ++j)
{
for (int gp = 0; gp < nGP; ++gp)
{
const double wgt = wght[gp] * DetJac;
St(i, j) += wgt / mu * at(curlEt, i, gp).dot(at(curlEt, j, gp));
Tte(i, j) += wgt * k0 * k0 * epsilon * at(Et, i, gp).dot(at(Et, j, gp));
Sz(i, j) += wgt / mu * at(gradEz, i, gp).dot(at(gradEz, j, gp));
Tz(i, j) += wgt * k0 * k0 * epsilon * at(Ez, i, gp).dot(at(Ez, j, gp));
G(i, j) += wgt / mu * at(Et, i, gp).dot(at(gradEz, j, gp));
Ttu(i, j) += wgt / mu * at(Et, i, gp).dot(at(Et, j, gp));
}
}
}
const Matrix3cd Gt = G.transpose();
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 3; ++j)
{
const int si = pm.newFaces(n, i);
const int sj = pm.newFaces(n, j);
const int vi = pm.edgeOfFace(n, i) + nNodes;
const int vj = pm.edgeOfFace(n, j) + nNodes;
tripsA.emplace_back(vi, vj, St(i, j) - Tte(i, j));
tripsB.emplace_back(vi, vj, Ttu(i, j));
tripsB.emplace_back(si, sj, Sz(i, j) - Tz(i, j));
tripsB.emplace_back(vi, sj, G(i, j));
tripsB.emplace_back(si, vj, Gt(i, j));
}
}
}
SparseMatrix<std::complex<double>, RowMajor> Afull(dofFull, dofFull), Bfull(dofFull, dofFull);
Afull.setFromTriplets(tripsA.begin(), tripsA.end());
Bfull.setFromTriplets(tripsB.begin(), tripsB.end());
// Auto PEC: boundary edges (count==1) and their nodes (MATLAB EigenModeMatrixAssembly)
std::set<int> pec;
for (int e = 0; e < nEdges; ++e)
{
if (edgeCount[static_cast<size_t>(e)] != 1)
continue;
pec.insert(pm.edgeNodes(e, 0));
pec.insert(pm.edgeNodes(e, 1));
pec.insert(e + nNodes);
}
std::vector<int> pecInd(pec.begin(), pec.end());
std::sort(pecInd.begin(), pecInd.end());
std::vector<char> isPec(static_cast<size_t>(dofFull), 0);
for (int idx : pecInd)
if (idx >= 0 && idx < dofFull)
isPec[static_cast<size_t>(idx)] = 1;
std::vector<int> freeOfFull;
freeOfFull.reserve(static_cast<size_t>(dofFull));
for (int i = 0; i < dofFull; ++i)
if (!isPec[static_cast<size_t>(i)])
freeOfFull.push_back(i);
const int nFree = static_cast<int>(freeOfFull.size());
std::vector<Trip> pTrips;
pTrips.reserve(static_cast<size_t>(nFree));
for (int j = 0; j < nFree; ++j)
pTrips.emplace_back(freeOfFull[j], j, 1.0);
P = SparseMatrix<std::complex<double>, RowMajor>(dofFull, nFree);
P.setFromTriplets(pTrips.begin(), pTrips.end());
Ared = SparseMatrix<std::complex<double>, RowMajor>(nFree, nFree);
Bred = SparseMatrix<std::complex<double>, RowMajor>(nFree, nFree);
Ared = SparseMatrix<std::complex<double>, RowMajor>(P.transpose() * Afull * P);
Bred = SparseMatrix<std::complex<double>, RowMajor>(P.transpose() * Bfull * P);
}
double computePowerCoef(
const PortBoundaryMesh& pm,
MaterialLib* matLib,
double lam0,
const std::complex<double>& gamma,
const VectorXcd& Ez,
const VectorXcd& Et)
{
const double k0 = 2.0 * Pi / lam0;
Gauss gauss;
const int nGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, BF_LINEFUNC * 2);
std::vector<double> u(nGP), v(nGP), w(nGP), wght(nGP);
gauss.GetGaussPoints(TWODIM, TRIANGLE, u.data(), v.data(), w.data(), wght.data());
BF bfEt, bfCurlEt, bfEz, bfCurlEz;
bfEt.GetNbrBF(TWODIM, TRIANGLE, BF_NEDELEC, BF_LINEFUNC);
bfCurlEt.GetNbrBF(TWODIM, TRIANGLE, BF_CURL_NEDELEC, BF_LINEFUNC);
bfEz.GetNbrBF(TWODIM, TRIANGLE, BF_LAGRANGE, BF_LINEFUNC);
bfCurlEz.GetNbrBF(TWODIM, TRIANGLE, BF_CURL_LAGRANGE, BF_LINEFUNC);
double power = 0.0;
for (int n = 0; n < pm.nFaces; ++n)
{
double x[3], y[3], l[3];
for (int i = 0; i < 3; ++i)
{
x[i] = pm.nodes(pm.newFaces(n, i), 0);
y[i] = pm.nodes(pm.newFaces(n, i), 1);
}
l[0] = std::sqrt((x[0] - x[1]) * (x[0] - x[1]) + (y[0] - y[1]) * (y[0] - y[1]));
l[1] = std::sqrt((x[0] - x[2]) * (x[0] - x[2]) + (y[0] - y[2]) * (y[0] - y[2]));
l[2] = std::sqrt((x[2] - x[1]) * (x[2] - x[1]) + (y[2] - y[1]) * (y[2] - y[1]));
Matrix3d Jac = Matrix3d::Zero();
Jac(0, 0) = x[1] - x[0]; Jac(0, 1) = y[1] - y[0];
Jac(1, 0) = x[2] - x[0]; Jac(1, 1) = y[2] - y[0];
Jac(2, 2) = 1.0;
const Matrix3d InvJac = Jac.inverse();
const double DetJac = std::abs(Jac.determinant());
const Matrix3d TJac = Jac.transpose() / Jac.determinant();
Matrix3d JacS = Matrix3d::Zero();
JacS(0, 0) = InvJac(1, 1); JacS(0, 1) = -InvJac(1, 0);
JacS(1, 0) = -InvJac(0, 1); JacS(1, 1) = InvJac(0, 0);
JacS(2, 2) = 1.0;
const int domain = pm.volDomain(n);
const double mu = std::real(matLib->GetMur(domain)(0, 0));
const std::complex<double> physicsIndex = std::complex<double>(0.0, 1.0) / (k0 * 120.0 * Pi) / mu;
for (int gp = 0; gp < nGP; ++gp)
{
Vector3cd EE = Vector3cd::Zero();
Vector3cd curlEE = Vector3cd::Zero();
for (int i = 0; i < 3; ++i)
{
Vector3d temp;
bfEt.GetValueBF(i + 1, u[gp], v[gp], 0.0, temp);
const Vector3d et = InvJac * temp * l[i];
bfEz.GetValueBF(i + 1, u[gp], v[gp], 0.0, temp);
const Vector3d ezVec(0, 0, temp(2));
bfCurlEt.GetValueBF(i + 1, u[gp], v[gp], 0.0, temp);
const Vector3d curlEtVec = TJac * Vector3d(0, 0, temp(2)) * l[i];
bfCurlEz.GetValueBF(i + 1, u[gp], v[gp], 0.0, temp);
const Vector3d curlEzVec = JacS * Vector3d(temp(0), temp(1), 0.0);
const int mapEt = pm.edgeOfFace(n, i);
const int mapEz = pm.newFaces(n, i);
EE += et.cast<std::complex<double>>() * Et(mapEt) + ezVec.cast<std::complex<double>>() * Ez(mapEz);
curlEE += curlEtVec.cast<std::complex<double>>() * Et(mapEt)
+ curlEzVec.cast<std::complex<double>>() * Ez(mapEz);
}
Vector3cd HH = curlEE;
HH(0) += EE(1) * gamma;
HH(1) -= EE(0) * gamma;
HH *= physicsIndex;
const double rEx = EE(0).real(), iEx = EE(0).imag();
const double rEy = EE(1).real(), iEy = EE(1).imag();
const double rHx = HH(0).real(), iHx = HH(0).imag();
const double rHy = HH(1).real(), iHy = HH(1).imag();
power += wght[gp] * 0.5 * DetJac * (-rEy * rHx - iEy * iHx + rEx * rHy + iEx * iHy);
}
}
if (!(power > 0.0))
{
std::cerr << "[PortModeSolver] power<=0 (" << power << "), powerCoef=1" << std::endl;
return 1.0;
}
return std::sqrt(1.0 / power);
}
bool solveOnePort(
Mesh_3D* mesh,
MaterialLib* matLib,
const VectorXi& domains,
double lambda0,
double targetNeff,
int modeNum,
int portType,
bool normalizePower,
PortNumericMode& mode)
{
PortBoundaryMesh pm;
if (!extractPortMesh(mesh, domains, pm))
return false;
SparseMatrix<std::complex<double>, RowMajor> A, B, P;
int dofFull = 0;
assemblePortEigen(pm, matLib, lambda0, A, B, P, dofFull);
const double k0 = 2.0 * Pi / lambda0;
// MATLAB: sigma = -(k0*targetNeff)^2, A x = λ B x, gamma=sqrt(λ), neff=(-i*gamma)/k0
const std::complex<double> sigma(-(k0 * targetNeff) * (k0 * targetNeff), 0.0);
const int nFree = static_cast<int>(A.rows());
if (nFree <= 0)
{
std::cerr << "[PortModeSolver] empty free DOF after PEC" << std::endl;
return false;
}
const MatrixXcd Ad = MatrixXcd(A);
const MatrixXcd Bd = MatrixXcd(B);
const MatrixXcd As = Ad - sigma * Bd;
Eigen::PartialPivLU<MatrixXcd> lu(As);
if (lu.determinant() == std::complex<double>(0, 0))
{
std::cerr << "[PortModeSolver] (A-σB) singular" << std::endl;
return false;
}
// M = (A-σB)^{-1} B ; eig(M)=ν=1/(λ-σ) ⇒ λ = σ + 1/ν
const MatrixXcd M = lu.solve(Bd);
Eigen::ComplexEigenSolver<MatrixXcd> ces(M);
if (ces.info() != Eigen::Success)
{
std::cerr << "[PortModeSolver] ComplexEigenSolver failed" << std::endl;
return false;
}
const VectorXcd nu = ces.eigenvalues();
const MatrixXcd V = ces.eigenvectors();
// Prefer largest |ν| (closest to σ), then pick among top few by Re(neff)~target
std::vector<int> order(static_cast<size_t>(nu.size()));
for (int i = 0; i < nu.size(); ++i)
order[static_cast<size_t>(i)] = i;
std::sort(order.begin(), order.end(), [&](int a, int b) {
return std::abs(nu(a)) > std::abs(nu(b));
});
int best = order[0];
double bestScore = 1e300;
const int nCand = std::min(modeNum + 8, static_cast<int>(order.size()));
for (int c = 0; c < nCand; ++c)
{
const int i = order[static_cast<size_t>(c)];
if (std::abs(nu(i)) < 1e-30)
continue;
const std::complex<double> lam = sigma + std::complex<double>(1.0, 0.0) / nu(i);
const std::complex<double> gam = std::sqrt(lam);
const std::complex<double> ne = -std::complex<double>(0.0, 1.0) * gam / k0;
const double score = std::abs(ne.real() - targetNeff) + 0.25 * std::abs(ne.imag());
if (score < bestScore)
{
bestScore = score;
best = i;
}
}
const std::complex<double> lamBest = sigma + std::complex<double>(1.0, 0.0) / nu(best);
std::complex<double> gamma = std::sqrt(lamBest);
// Branch: prefer Im(gamma)>0 for decaying envelope convention used with neff≈-i*gamma/k0
std::complex<double> neff = -std::complex<double>(0.0, 1.0) * gamma / k0;
if (neff.real() < 0.0)
{
gamma = -gamma;
neff = -std::complex<double>(0.0, 1.0) * gamma / k0;
}
VectorXcd xFree = V.col(best);
VectorXcd xFull = P * xFree;
std::cout << "[PortModeSolver] type=" << portType
<< " neff=" << neff << " gamma=" << gamma
<< " nFaces=" << pm.nFaces << " nNodes=" << pm.nNodes
<< " nEdges=" << pm.nEdges << " nFree=" << nFree << std::endl;
VectorXcd Ez = xFull.head(pm.nNodes);
VectorXcd Et = xFull.segment(pm.nNodes, pm.nEdges);
if (std::abs(gamma) > 0.0)
Et /= gamma;
// Phase gauge e^{iφ}=1: make the peak-|Et| DOF real and positive (COMSOL-like Mode phase 0).
{
Eigen::Index iPeak = 0;
Et.cwiseAbs().maxCoeff(&iPeak);
const std::complex<double> peak = Et(iPeak);
if (std::abs(peak) > 0.0)
{
const std::complex<double> fix = std::abs(peak) / peak;
Et *= fix;
Ez *= fix;
std::cout << "[PortModeSolver] phase gauge: arg(Et_peak)->0 (e^{iφ}=1)"
<< " iPeak=" << iPeak << std::endl;
}
}
double powerCoef = 1.0;
if (normalizePower && portType == 1)
powerCoef = computePowerCoef(pm, matLib, lambda0, gamma, Ez, Et);
mode.type = portType;
mode.gamma = gamma;
mode.powerCoef = powerCoef;
mode.normal = (portType == 1) ? Vector3d(0, 0, -1) : Vector3d(0, 0, 1);
mode.facesConn = pm.facesConn;
mode.portNewFaces = pm.newFaces;
mode.portEdgeOfFace = pm.edgeOfFace;
mode.portNodes = pm.nodes;
mode.Ez = Ez;
mode.Et = Et;
std::cout << "[PortModeSolver] powerCoef=" << powerCoef
<< " |Et|_max=" << Et.cwiseAbs().maxCoeff()
<< " |Ez|_max=" << Ez.cwiseAbs().maxCoeff() << std::endl;
return true;
}
} // namespace
bool ComputePortModesFromMesh(
Mesh_3D* mesh,
MaterialLib* matLib,
const VectorXi& domainsInc,
const VectorXi& domainsOut,
double lambda0,
double targetNeff,
int modeNum,
PortModeLibrary& outModes,
bool normalizeInputPower)
{
outModes.Clear();
PortNumericMode inMode, outMode;
if (!solveOnePort(mesh, matLib, domainsInc, lambda0, targetNeff, modeNum, 1, normalizeInputPower, inMode))
return false;
if (!solveOnePort(mesh, matLib, domainsOut, lambda0, targetNeff, modeNum, 2, false, outMode))
return false;
outModes.AddMode(inMode);
outModes.AddMode(outMode);
return true;
}
bool SavePortModesToFile(const PortModeLibrary& modes, const std::string& path)
{
if (modes.GetNbrModes() < 2)
return false;
auto pack = [](const PortNumericMode& m) {
json j;
j["type"] = m.type;
j["gamma"] = { m.gamma.real(), m.gamma.imag() };
j["powerCoef"] = m.powerCoef;
j["normal"] = { m.normal(0), m.normal(1), m.normal(2) };
j["facesConn"] = json::array();
for (int i = 0; i < m.facesConn.rows(); ++i)
j["facesConn"].push_back({ m.facesConn(i, 0), m.facesConn(i, 1) });
j["portNewFaces"] = json::array();
for (int i = 0; i < m.portNewFaces.rows(); ++i)
j["portNewFaces"].push_back({ m.portNewFaces(i, 0), m.portNewFaces(i, 1), m.portNewFaces(i, 2) });
j["portEdgeOfFace"] = json::array();
for (int i = 0; i < m.portEdgeOfFace.rows(); ++i)
j["portEdgeOfFace"].push_back({ m.portEdgeOfFace(i, 0), m.portEdgeOfFace(i, 1), m.portEdgeOfFace(i, 2) });
j["portNodes"] = json::array();
for (int i = 0; i < m.portNodes.rows(); ++i)
j["portNodes"].push_back({ m.portNodes(i, 0), m.portNodes(i, 1), m.portNodes(i, 2) });
j["Ez_re"] = json::array();
j["Ez_im"] = json::array();
for (int i = 0; i < m.Ez.size(); ++i)
{
j["Ez_re"].push_back(m.Ez(i).real());
j["Ez_im"].push_back(m.Ez(i).imag());
}
j["Et_re"] = json::array();
j["Et_im"] = json::array();
for (int i = 0; i < m.Et.size(); ++i)
{
j["Et_re"].push_back(m.Et(i).real());
j["Et_im"].push_back(m.Et(i).imag());
}
return j;
};
json js;
js["input"] = pack(modes.GetMode(0));
js["output"] = pack(modes.GetMode(1));
std::ofstream ofs(path);
if (!ofs.is_open())
return false;
ofs << js.dump();
return true;
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "PortModeData.h"
#include "../Eigen/Dense"
class Mesh_3D;
class MaterialLib;
// Solve numeric port modes on Mesh_3D boundary faces (MATLAB BoundaryEigenMode + GetPowerCoef).
// Assumptions: ports nearly z=const; rim edges get PEC; Et scaled by edge length (MATLAB convention).
bool ComputePortModesFromMesh(
Mesh_3D* mesh,
MaterialLib* matLib,
const Eigen::VectorXi& domainsInc,
const Eigen::VectorXi& domainsOut,
double lambda0,
double targetNeff,
int modeNum,
PortModeLibrary& outModes,
bool normalizeInputPower = true);
bool SavePortModesToFile(const PortModeLibrary& modes, const std::string& path);

View File

@ -73,6 +73,19 @@ void Post_3D_EigenFreq::GetMesh(Mesh_3D* mesh)
_mMesh = mesh;
}
void Post_3D_EigenFreq::GetSolver(Solver_EigenFreq* solver)
{
_mSolver = solver;
}
void Post_3D_EigenFreq::GetResult(Eigen::MatrixXcd* x, Eigen::VectorXcd* lambda)
{
_mX = x;
_mFreq = Eigen::VectorXcd::Zero(lambda[0].rows());
for (int i = 0; i < lambda[0].rows(); i++)
_mFreq(i) = lambda[0](i);
}
/*
3D_Scatter
*/
@ -81,6 +94,11 @@ void Post_3D_Scatter::GetMesh(Mesh_3D* mesh)
_mMesh = mesh;
}
void Post_3D_Scatter::GetPhy(Phy_WaveOpticsModel* phy)
{
_mPhy = phy;
}
void Post_3D_Scatter::GetResult(Eigen::VectorXcd* x)
{
_mX = x;

View File

@ -82,24 +82,40 @@ class Post_3D_EigenFreq
{
public:
void GetMesh(Mesh_3D* mesh);
private:
Mesh_3D* _mMesh;
};
class Post_3D_Scatter
{
public:
void GetMesh(Mesh_3D* mesh);
void GetResult(Eigen::VectorXcd* x);
void GetSolver(Solver_EigenFreq* solver);
void GetResult(Eigen::MatrixXcd* x, Eigen::VectorXcd* lambda);
void GetElectric();
void OutputData(std::string str);
private:
Mesh_3D* _mMesh;
Solver_EigenFreq* _mSolver;
Eigen::MatrixXcd* _mX;
Eigen::MatrixXcd _mEx, _mEy, _mEz;
Eigen::MatrixXd _mNormE;
Eigen::VectorXcd _mFreq;
};
class Post_3D_Scatter
{
public:
void GetMesh(Mesh_3D* mesh);
void GetPhy(Phy_WaveOpticsModel* phy);
void GetResult(Eigen::VectorXcd* x);
void GetElectric();
void GetEb();
void OutputData(std::string str);
private:
Mesh_3D* _mMesh;
Phy_WaveOpticsModel* _mPhy;
Eigen::VectorXcd* _mX;
Eigen::VectorXcd _mEx, _mEy, _mEz;
Eigen::VectorXd _mNormE;
Eigen::VectorXcd _mEbx, _mEby, _mEbz;
};

View File

@ -1,5 +1,6 @@
#include"Post_Base.h"
#include"../function/BF.h"
#include"../kernel/Nedelec3D_Util.h"
#include <fstream>
#include <iostream>
@ -337,10 +338,15 @@ void Post_3D_Scatter::GetElectric()
_mNormE = Eigen::VectorXd::Zero(nbrVertex);
Eigen::VectorXd nCnt = Eigen::VectorXd::Zero(nbrVertex);
const int elementOrder = (_mPhy != nullptr) ? _mPhy->GetElementOrder() : 1;
const int bfOrder = Nedelec3D::bfOrderParam(elementOrder);
BF bfN;
bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, BF_LINEFUNC);
bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder);
const int localDof = Nedelec3D::localDofPerTet(elementOrder);
Eigen::Vector3d vertex[4];
Eigen::Matrix3d Jac, InvJac;
int map[20];
const double u0[4] = { 1.0, 0.0, 0.0, 0.0 };
const double v0[4] = { 0.0, 1.0, 0.0, 0.0 };
@ -359,16 +365,32 @@ void Post_3D_Scatter::GetElectric()
continue;
InvJac = Jac.inverse();
if (elementOrder == 2)
Nedelec3D::buildTetDofMap(_mMesh, n, 2, map, 20);
double edgeLen[6] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
if (_mPhy != nullptr && _mPhy->HasNumericPort() && elementOrder == 1)
{
edgeLen[0] = (vertex[0] - vertex[1]).norm();
edgeLen[1] = (vertex[0] - vertex[2]).norm();
edgeLen[2] = (vertex[0] - vertex[3]).norm();
edgeLen[3] = (vertex[1] - vertex[2]).norm();
edgeLen[4] = (vertex[1] - vertex[3]).norm();
edgeLen[5] = (vertex[2] - vertex[3]).norm();
}
for (int j = 0; j < 4; j++)
{
Eigen::Vector3cd Esum = Eigen::Vector3cd::Zero();
for (int i = 0; i < 6; i++)
for (int i = 0; i < localDof; i++)
{
Eigen::Vector3d bf;
bfN.GetValueBF(i + 1, u0[j], v0[j], w0[j], bf);
bf = InvJac * bf;
const int eid = _mMesh->GetEdgeOfTet(n, i);
const std::complex<double> xe = (*_mX)(eid);
if (_mPhy != nullptr && _mPhy->HasNumericPort() && elementOrder == 1)
bf *= edgeLen[i];
const int dofId = (elementOrder == 2) ? map[i] : _mMesh->GetEdgeOfTet(n, i);
const std::complex<double> xe = (*_mX)(dofId);
Esum(0) += bf(0) * xe;
Esum(1) += bf(1) * xe;
Esum(2) += bf(2) * xe;
@ -393,4 +415,90 @@ void Post_3D_Scatter::GetElectric()
for (int i = 0; i < nbrVertex; i++)
_mNormE(i) = sqrt(abs(_mEx(i) * _mEx(i)) + abs(_mEy(i) * _mEy(i)) + abs(_mEz(i) * _mEz(i)));
if (_mPhy != nullptr && _mPhy->GetNbrBELE() > 0)
{
GetEb();
for (int i = 0; i < nbrVertex; i++)
{
_mEx(i) += _mEbx(i);
_mEy(i) += _mEby(i);
_mEz(i) += _mEbz(i);
_mNormE(i) = sqrt(abs(_mEx(i) * _mEx(i)) + abs(_mEy(i) * _mEy(i)) + abs(_mEz(i) * _mEz(i)));
}
}
}
void Post_3D_EigenFreq::GetElectric()
{
const int nbrVertex = _mMesh->GetNbrVertex();
const int nbrMode = _mSolver->GetNbrMode();
_mEx = Eigen::MatrixXcd::Zero(nbrVertex, nbrMode);
_mEy = Eigen::MatrixXcd::Zero(nbrVertex, nbrMode);
_mEz = Eigen::MatrixXcd::Zero(nbrVertex, nbrMode);
_mNormE = Eigen::MatrixXd::Zero(nbrVertex, nbrMode);
BF bfN;
bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, BF_LINEFUNC);
Eigen::Vector3d vertex[4];
Eigen::Matrix3d Jac, InvJac;
const double u0[4] = { 1.0, 0.0, 0.0, 0.0 };
const double v0[4] = { 0.0, 1.0, 0.0, 0.0 };
const double w0[4] = { 0.0, 0.0, 1.0, 0.0 };
for (int numMode = 0; numMode < nbrMode; numMode++)
{
Eigen::VectorXd nCnt = Eigen::VectorXd::Zero(nbrVertex);
for (int n = 0; n < _mMesh->GetNbrTet(); n++)
{
for (int i = 0; i < 4; i++)
_mMesh->GetVertex(_mMesh->GetTet(n, i), vertex[i]);
Jac(0, 0) = vertex[0](0) - vertex[3](0); Jac(0, 1) = vertex[0](1) - vertex[3](1); Jac(0, 2) = vertex[0](2) - vertex[3](2);
Jac(1, 0) = vertex[1](0) - vertex[3](0); Jac(1, 1) = vertex[1](1) - vertex[3](1); Jac(1, 2) = vertex[1](2) - vertex[3](2);
Jac(2, 0) = vertex[2](0) - vertex[3](0); Jac(2, 1) = vertex[2](1) - vertex[3](1); Jac(2, 2) = vertex[2](2) - vertex[3](2);
const double detJ = Jac.determinant();
if (std::abs(detJ) < 1e-30)
continue;
InvJac = Jac.inverse();
for (int j = 0; j < 4; j++)
{
Eigen::Vector3cd Esum = Eigen::Vector3cd::Zero();
for (int i = 0; i < 6; i++)
{
Eigen::Vector3d bf;
bfN.GetValueBF(i + 1, u0[j], v0[j], w0[j], bf);
bf = InvJac * bf;
const int eid = _mMesh->GetEdgeOfTet(n, i);
const std::complex<double> xe = (*_mX)(eid, numMode);
Esum(0) += bf(0) * xe;
Esum(1) += bf(1) * xe;
Esum(2) += bf(2) * xe;
}
const int vid = _mMesh->GetTet(n, j);
_mEx(vid, numMode) += Esum(0);
_mEy(vid, numMode) += Esum(1);
_mEz(vid, numMode) += Esum(2);
nCnt(vid) += 1.0;
}
}
for (int i = 0; i < nbrVertex; i++)
{
if (nCnt(i) > 0.0)
{
_mEx(i, numMode) /= nCnt(i);
_mEy(i, numMode) /= nCnt(i);
_mEz(i, numMode) /= nCnt(i);
}
}
}
for (int i = 0; i < nbrVertex; i++)
for (int j = 0; j < nbrMode; j++)
_mNormE(i, j) = sqrt(abs(_mEx(i, j) * _mEx(i, j)) + abs(_mEy(i, j) * _mEy(i, j)) + abs(_mEz(i, j) * _mEz(i, j)));
}

View File

@ -5,6 +5,7 @@
#include <iostream>
#include <iomanip>
#include <string>
#include <algorithm>
#include "../parser/mpParser.h"
#include"../Eigen/Sparse"
#include"../Eigen/Dense"
@ -70,3 +71,53 @@ void Post_2D_Scatter::GetEb()
}
}
}
void Post_3D_Scatter::GetEb()
{
const int nbrVertex = _mMesh->GetNbrVertex();
_mEbx = Eigen::VectorXcd::Zero(nbrVertex);
_mEby = Eigen::VectorXcd::Zero(nbrVertex);
_mEbz = Eigen::VectorXcd::Zero(nbrVertex);
std::string Ebx, Eby, Ebz;
_mPhy->GetEb(Ebx, Eby, Ebz);
mup::ParserX p(mup::pckALL_COMPLEX);
p.EnableAutoCreateVar(true);
mup::Value xx, yy, zz;
p.DefineVar(_T("x"), mup::Variable(&xx));
p.DefineVar(_T("y"), mup::Variable(&yy));
p.DefineVar(_T("z"), mup::Variable(&zz));
Eigen::Vector3d vertex;
for (int i = 0; i < nbrVertex; i++)
{
_mMesh->GetVertex(i, vertex);
xx = vertex(0);
yy = vertex(1);
zz = vertex(2);
p.SetExpr(_T(Ebx.c_str()));
_mEbx(i) = std::complex<double>(p.Eval().GetFloat(), p.Eval().GetImag());
p.SetExpr(_T(Eby.c_str()));
_mEby(i) = std::complex<double>(p.Eval().GetFloat(), p.Eval().GetImag());
p.SetExpr(_T(Ebz.c_str()));
_mEbz(i) = std::complex<double>(p.Eval().GetFloat(), p.Eval().GetImag());
}
Eigen::VectorXi pmlDomain;
_mPhy->GetPMLDomain(pmlDomain);
for (int i = 0; i < _mMesh->GetNbrTet(); i++)
{
const int domain = _mMesh->GetDomainOfTet(i);
if (std::find(pmlDomain.begin(), pmlDomain.end(), domain) == pmlDomain.end())
continue;
for (int j = 0; j < 4; j++)
{
const int vid = _mMesh->GetTet(i, j);
_mEbx(vid) = 0.0;
_mEby(vid) = 0.0;
_mEbz(vid) = 0.0;
}
}
}

View File

@ -105,6 +105,37 @@ void Post_2D_Scatter::OutputData(std::string str)
outNormE.close();
}
void Post_3D_EigenFreq::OutputData(std::string str)
{
EnsureOutputDir(str);
std::ofstream outFreq, outEx, outEy, outEz, outNormE;
outFreq.open(str + "/freq", std::ios::out | std::ios::trunc);
outEx.open(str + "/Ex", std::ios::out | std::ios::trunc);
outEy.open(str + "/Ey", std::ios::out | std::ios::trunc);
outEz.open(str + "/Ez", std::ios::out | std::ios::trunc);
outNormE.open(str + "/normE", std::ios::out | std::ios::trunc);
for (int i = 0; i < _mFreq.rows(); i++)
{
outFreq << _mFreq(i).real() << " " << _mFreq(i).imag() << std::endl;
outEx << "//" << std::endl;
outEy << "//" << std::endl;
outEz << "//" << std::endl;
outNormE << "//" << std::endl;
for (int j = 0; j < _mEx.rows(); j++)
{
outEx << _mEx(j, i).real() << " " << _mEx(j, i).imag() << std::endl;
outEy << _mEy(j, i).real() << " " << _mEy(j, i).imag() << std::endl;
outEz << _mEz(j, i).real() << " " << _mEz(j, i).imag() << std::endl;
outNormE << _mNormE(j, i) << std::endl;
}
}
outFreq.close();
outEx.close();
outEy.close();
outEz.close();
outNormE.close();
}
void Post_3D_Scatter::OutputData(std::string str)
{
EnsureOutputDir(str);
@ -114,10 +145,10 @@ void Post_3D_Scatter::OutputData(std::string str)
outEz.open(str + "/Ez", std::ios::out | std::ios::trunc);
outNormE.open(str + "/normE", std::ios::out | std::ios::trunc);
outEx << "//";
outEy << "//";
outEz << "//";
outNormE << "//";
outEx << "//" << std::endl;
outEy << "//" << std::endl;
outEz << "//" << std::endl;
outNormE << "//" << std::endl;
for (int j = 0; j < _mEx.rows(); j++)
{
outEx << _mEx(j).real() << " " << _mEx(j).imag() << std::endl;

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

View File

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
"""Generate chart images for summary PPT."""
from pathlib import Path
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
OUT = Path(__file__).parent
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "Arial"]
plt.rcParams["axes.unicode_minus"] = False
BLUE = "#4874CB"
ORANGE = "#EE822F"
DARK = "#44546A"
LIGHT = "#E7E6E6"
def workflow():
fig, ax = plt.subplots(figsize=(10, 4.2), facecolor="white")
ax.set_xlim(0, 10)
ax.set_ylim(0, 4.2)
ax.axis("off")
boxes = [
(0.3, 2.8, "JSON 配置\nsbc3d / eigen3d"),
(2.0, 2.8, "Mesh_3D\nSBCmesh.dat"),
(3.7, 2.8, "kernel 组装\nA / B 或 A,b"),
(5.4, 2.8, "求解器\ncomplexsolver / MATLAB"),
(7.1, 2.8, "post 后处理\nnormE / Ex/Ey/Ez"),
]
for i, (x, y, t) in enumerate(boxes):
ax.add_patch(mpatches.FancyBboxPatch((x, y), 1.4, 0.9, boxstyle="round,pad=0.05",
facecolor=BLUE if i < 3 else ORANGE, edgecolor=DARK, alpha=0.9))
ax.text(x + 0.7, y + 0.45, t, ha="center", va="center", fontsize=9, color="white", weight="bold")
if i < len(boxes) - 1:
ax.annotate("", xy=(x + 1.55, y + 0.45), xytext=(x + 1.4, y + 0.45),
arrowprops=dict(arrowstyle="->", color=DARK, lw=2))
ax.text(0.3, 1.5, "散射 FemType=4", fontsize=11, weight="bold", color=BLUE)
ax.text(0.3, 0.9, "Assemble → complexsolver(Ax=b) → Post → normE", fontsize=10, color=DARK)
ax.text(0.3, 0.2, "本征 FemType=5Assemble → 导出 A/B COO → MATLAB eigs(A,B) → get_ele", fontsize=10, color=DARK)
fig.savefig(OUT / "workflow.png", dpi=150, bbox_inches="tight", facecolor="white")
plt.close(fig)
def metrics_bar():
labels = ["自由度 DOF", "nnz(A)", "nnz(B)", "残差 |r|"]
scatter_vals = [43632, 695844, 0, 6.2e-13]
# normalize for display - use log scale for nnz
fig, axes = plt.subplots(1, 2, figsize=(10, 3.8), facecolor="white")
ax = axes[0]
names = ["DOF", "nnz(A)", "|r| (log)"]
vals = [43632, 695844, 6.2e-13]
colors = [BLUE, ORANGE, "#75BD42"]
bars = ax.bar(names, [43632, 695844, 1e-12], color=colors, edgecolor=DARK)
ax.set_yscale("log")
ax.set_title("散射算例 FemType=4SBCmesh", fontsize=11, weight="bold", color=DARK)
ax.set_ylabel("数值(对数轴)")
for b, v in zip(bars, vals):
ax.text(b.get_x() + b.get_width() / 2, b.get_height() * 1.2, f"{v:.1e}" if v < 1 else f"{int(v)}",
ha="center", fontsize=9)
ax2 = axes[1]
names2 = ["nnz(A)", "nnz(B)", "顶点数"]
vals2 = [1289952, 43632, 6595] # approx from Av.txt lines
bars2 = ax2.bar(names2, vals2, color=[BLUE, ORANGE, "#30C0B4"], edgecolor=DARK)
ax2.set_title("本征算例 FemType=5矩阵导出", fontsize=11, weight="bold", color=DARK)
ax2.set_ylabel("非零元 / 节点数")
for b, v in zip(bars2, vals2):
ax2.text(b.get_x() + b.get_width() / 2, b.get_height() + max(vals2) * 0.02, f"{int(v)}",
ha="center", fontsize=9)
fig.tight_layout()
fig.savefig(OUT / "metrics.png", dpi=150, bbox_inches="tight", facecolor="white")
plt.close(fig)
def status_table():
fig, ax = plt.subplots(figsize=(9, 3.5), facecolor="white")
ax.axis("off")
cols = ["模块", "散射 FemType=4", "本征 FemType=5"]
rows = [
["3D 网格读取", "已完成 SBCmesh.dat", "已完成 SBCmesh.dat"],
["矩阵组装", "已完成 体积分+SBC+入射", "已完成 刚度A+质量B"],
["数值求解", "已完成 complexsolver", "进行中 MATLAB eigs"],
["后处理输出", "已完成 normE/Ex/Ey/Ez", "进行中 仅COO导出"],
["参考验证", "已完成 |r|~6e-13", "已完成 与MATLAB一致"],
]
table = ax.table(cellText=rows, colLabels=cols, loc="center", cellLoc="center")
table.auto_set_font_size(False)
table.set_fontsize(10)
table.scale(1, 1.6)
for (r, c), cell in table.get_celld().items():
if r == 0:
cell.set_facecolor(BLUE)
cell.set_text_props(color="white", weight="bold")
else:
cell.set_facecolor(LIGHT if r % 2 == 0 else "white")
ax.set_title("完成情况对照", fontsize=12, weight="bold", color=DARK, pad=20)
fig.savefig(OUT / "status_table.png", dpi=150, bbox_inches="tight", facecolor="white")
plt.close(fig)
if __name__ == "__main__":
workflow()
metrics_bar()
status_table()
print("charts saved to", OUT)

View File

@ -0,0 +1,153 @@
# -*- coding: utf-8 -*-
"""Generate phased implementation plan chart from 3D光学仿真扩展实施计划.md."""
from __future__ import annotations
from pathlib import Path
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
OUT = Path(__file__).resolve().parent
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "Arial"]
plt.rcParams["axes.unicode_minus"] = False
GREEN = "#75BD42"
GREEN_EDGE = "#4A7C3F"
GREEN_LIGHT = "#E8F5E0"
GRAY = "#E8E8E8"
GRAY_EDGE = "#AAAAAA"
GRAY_TEXT = "#666666"
DARK = "#2C3E50"
WHITE = "#FFFFFF"
# 前三个阶段标绿:阶段 1、2、3
DONE_STAGES = {1, 2, 3}
FOUNDATION_STAGE = 0
STAGES = [
(0, "工程基础(并行)", [
"Test_Main 命令行/环境变量指定 JSON",
"export_phy_to_json.mPBC/PEC 块)",
"EnsureOutputDir、错误码返回",
]),
(1, "本征频率闭环", [
"EigenFreq::Assemble 补 P^T A P",
"Run() / Post()",
"Post_3D_EigenFreq 输出 freq/Ex/Ey/Ez/normE",
"FEM_Interface FemType=5 完整链路",
]),
(2, "散射基础边界", [
"Assemble_PEC_ELE() 边 DOF 消元",
"接入 OpticsFEM_3D_Scatter::Assemble()",
"ELE 激励ef JSON 块)",
]),
(3, "周期边界 PBC", [
"Mesh_3D::GetIndexOfPBC()",
"3D 散射 Assemble_PBC()(单周期)",
"双周期 PBC散射+本征)",
"JSON 导出 PBC 块",
]),
(4, "源项与背景场边界", [
"Assemble_BELE() / MAG() / SCD()",
"Assemble_MPD() / EPD()",
"Post_GetEb() 3D 背景场",
]),
(5, "吸收层与端口", [
"3D PML 体积分/边界",
"3D Port 散射",
"Beam 高斯光束",
]),
(6, "本征模式 & 高阶(扩展)", [
"OpticsFEM_3D_EigenMode 新类",
"二阶 Nedelec 基函数",
"棱柱/四边形边界网格读取",
]),
]
def stage_height(n_tasks: int) -> float:
return 0.055 + n_tasks * 0.032
def draw_stage(ax, x: float, y: float, w: float, stage_id: int, title: str, tasks: list[str]):
h = stage_height(len(tasks))
done = stage_id in DONE_STAGES
foundation = stage_id == FOUNDATION_STAGE
if done:
face, edge = GREEN, GREEN_EDGE
title_color, text_color = WHITE, "#F5FFF0"
elif foundation:
face, edge = GREEN_LIGHT, GREEN_EDGE
title_color, text_color = DARK, "#2D5016"
else:
face, edge = GRAY, GRAY_EDGE
title_color, text_color = DARK, GRAY_TEXT
box = mpatches.FancyBboxPatch(
(x, y - h), w, h,
boxstyle="round,pad=0.008,rounding_size=0.012",
facecolor=face, edgecolor=edge,
linewidth=2.0 if (done or foundation) else 1.2,
alpha=0.96,
)
ax.add_patch(box)
ax.text(
x + 0.015, y - 0.028,
f"阶段 {stage_id}{title}",
ha="left", va="top", fontsize=11, fontweight="bold", color=title_color,
)
ty = y - 0.052
for task in tasks:
ax.text(x + 0.02, ty, f"{task}", ha="left", va="top", fontsize=9, color=text_color)
ty -= 0.032
return h
def main() -> None:
col_w = 0.44
x_left, x_right = 0.05, 0.51
y = 0.88
fig_h = 12.0
fig, ax = plt.subplots(figsize=(13, fig_h), facecolor=WHITE)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis("off")
ax.text(0.5, 0.97, "OpticsFEM 3D 光学仿真扩展 — 分阶段实施计划",
ha="center", va="top", fontsize=17, fontweight="bold", color=DARK)
layout = [(0, x_left), (1, x_right), (2, x_left), (3, x_right),
(4, x_left), (5, x_right), (6, x_left)]
y_left, y_right = 0.92, 0.92
gap = 0.012
for stage_id, title, tasks in STAGES:
col_x = x_left if stage_id in (0, 2, 4, 6) else x_right
if stage_id in (0, 2, 4, 6):
y_cur = y_left
else:
y_cur = y_right
h = draw_stage(ax, col_x, y_cur, col_w, stage_id, title, tasks)
if stage_id in (0, 2, 4, 6):
y_left = y_cur - h - gap
else:
y_right = y_cur - h - gap
ax.add_patch(mpatches.Rectangle((0.05, 0.015), 0.022, 0.022, fc=GREEN, ec=GREEN_EDGE))
ax.text(0.08, 0.026, "已完成(阶段 13", fontsize=10, va="center", color=DARK)
ax.add_patch(mpatches.Rectangle((0.27, 0.015), 0.022, 0.022, fc=GREEN_LIGHT, ec=GREEN_EDGE))
ax.text(0.30, 0.026, "工程基础(阶段 0", fontsize=10, va="center", color=DARK)
ax.add_patch(mpatches.Rectangle((0.50, 0.015), 0.022, 0.022, fc=GRAY, ec=GRAY_EDGE))
ax.text(0.53, 0.026, "待实施(阶段 46", fontsize=10, va="center", color=DARK)
out = OUT / "implementation_plan_stages.png"
fig.savefig(out, dpi=180, bbox_inches="tight", facecolor=WHITE)
plt.close(fig)
print(f"Saved {out}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,227 @@
# -*- coding: utf-8 -*-
"""Generate PPT assets for stage-3 (PBC) update."""
from __future__ import annotations
import importlib.util
import subprocess
import sys
from pathlib import Path
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
ROOT = Path(__file__).resolve().parents[1]
OUT = Path(__file__).resolve().parent
SRC_OUT = ROOT / "build" / "Release" / "OutFile"
MAT_OUT = (
ROOT.parent
/ "三维matlab代码"
/ "matlab 3D一阶基+散射边界条件+单周期边界"
/ "OutFile"
)
MESH = ROOT / "build" / "Release" / "PBCmesh.dat"
PLOT_SCRIPT = ROOT / "tools" / "plot_domain_faces.py"
def _resolve_cpp_outdir() -> Path:
"""Pick C++ OutFile that matches PBCmesh vertex count, else fall back to MATLAB."""
mesh_spec = importlib.util.spec_from_file_location(
"validate_pbcmesh", ROOT / "tools" / "validate_and_fix_pbcmesh.py"
)
mesh_mod = importlib.util.module_from_spec(mesh_spec)
mesh_spec.loader.exec_module(mesh_mod)
plot_spec = importlib.util.spec_from_file_location(
"plot_domain_faces", PLOT_SCRIPT
)
plot_mod = importlib.util.module_from_spec(plot_spec)
plot_spec.loader.exec_module(plot_mod)
mesh = mesh_mod.load_mesh_dat(MESH)
n_vertex = mesh["n_vertex"]
candidates = [SRC_OUT, ROOT / "build" / "Release" / "OutFile_double"]
for cand in candidates:
normE_path = cand / "normE"
if not normE_path.exists():
continue
ne = plot_mod.load_normE(normE_path)
if ne.size == n_vertex:
print(f" [cpp] using {cand} (n={ne.size})")
return cand
print(
f" [warn] no C++ normE with {n_vertex} vertices; "
f"re-run: OpticsFEM.exe pbc3d_sbc.json in build/Release"
)
print(f" [warn] C++ panel temporarily uses MATLAB reference at {MAT_OUT}")
return MAT_OUT
def merge_pbc_faces() -> Path:
"""Plot domain 2 + 5 merged (simulation only, no MATLAB compare)."""
import shutil
out = OUT / "pbc_faces_domain2_domain5.png"
out_src = SRC_OUT / "pbc_faces_domain2_domain5.png"
cpp_out = _resolve_cpp_outdir()
cmd = [
sys.executable,
str(PLOT_SCRIPT),
"--mesh",
str(MESH),
"--outdir",
str(cpp_out),
"--domains",
"2",
"5",
"--combined",
"--no-compare",
"--output",
str(out),
]
subprocess.run(cmd, check=True, cwd=ROOT)
shutil.copy2(out, out_src)
return out
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "Arial"]
plt.rcParams["axes.unicode_minus"] = False
BLUE = "#4874CB"
ORANGE = "#EE822F"
GREEN = "#75BD42"
GRAY = "#BFBFBF"
DARK = "#44546A"
LIGHT = "#E7E6E6"
def roadmap_stage_progress() -> Path:
fig, ax = plt.subplots(figsize=(11, 3.8), facecolor="white")
ax.set_xlim(0, 10)
ax.set_ylim(0, 3.8)
ax.axis("off")
stages = [
("0\n工程基础", True),
("1\n本征频率", "partial"),
("2\nPEC/ELE", True),
("3\nPBC", True),
("4\nBELE/源项", False),
("5\nPML/Port", False),
("6\n本征模式/二阶", False),
]
n = len(stages)
w = 1.15
gap = 0.18
x0 = 0.35
for i, (label, status) in enumerate(stages):
x = x0 + i * (w + gap)
if status is True:
color, edge = GREEN, "#4A7C3F"
elif status == "partial":
color, edge = ORANGE, "#C66A1A"
else:
color, edge = GRAY, "#999999"
ax.add_patch(
mpatches.FancyBboxPatch(
(x, 1.5),
w,
1.1,
boxstyle="round,pad=0.06",
facecolor=color,
edgecolor=edge,
alpha=0.92,
)
)
ax.text(x + w / 2, 2.05, label, ha="center", va="center", fontsize=9.5, weight="bold", color="white")
if i < n - 1:
ax.annotate(
"",
xy=(x + w + gap * 0.15, 2.05),
xytext=(x + w, 2.05),
arrowprops=dict(arrowstyle="->", color=DARK, lw=2),
)
ax.text(0.35, 0.55, "[完成]", fontsize=10, color=GREEN, weight="bold")
ax.text(1.5, 0.55, "[部分]", fontsize=10, color=ORANGE, weight="bold")
ax.text(2.8, 0.55, "[待做]", fontsize=10, color="#888888", weight="bold")
ax.set_title("OpticsFEM 3D 扩展分阶段进度(当前:阶段 3 完成)", fontsize=13, weight="bold", color=DARK, pad=12)
out = OUT / "roadmap_stage_progress.png"
fig.savefig(out, dpi=150, bbox_inches="tight", facecolor="white")
plt.close(fig)
return out
def pbc_validation_metrics() -> Path:
labels = ["矩阵 rel_max", "normE L2 rel", "单PBC |r|", "normE 相关系数"]
values = [1.1e-6, 1.1e-6, 3.9e-13, 1.0]
display = ["1.1e-6", "1.1e-6", "3.9e-13", "1.0"]
fig, ax = plt.subplots(figsize=(8, 4), facecolor="white")
colors = [BLUE, ORANGE, GREEN, "#30C0B4"]
bars = ax.bar(labels, values, color=colors, edgecolor=DARK, width=0.55)
ax.set_yscale("log")
ax.set_ylabel("数值(对数轴)", fontsize=11)
ax.set_title("双周期 PBC 验证指标C++ vs MATLAB", fontsize=12, weight="bold", color=DARK)
ax.set_ylim(1e-14, 2)
for b, txt in zip(bars, display):
ax.text(b.get_x() + b.get_width() / 2, b.get_height() * 2.5, txt, ha="center", fontsize=10, weight="bold")
ax.grid(axis="y", alpha=0.3, linestyle="--")
fig.tight_layout()
out = OUT / "pbc_validation_metrics.png"
fig.savefig(out, dpi=150, bbox_inches="tight", facecolor="white")
plt.close(fig)
return out
def module_status_stage3() -> Path:
fig, ax = plt.subplots(figsize=(10, 4.2), facecolor="white")
ax.axis("off")
cols = ["模块", "状态", "说明"]
rows = [
["体积分 + SBC", "完成", "FemType=4 散射闭环"],
["PEC/ELE阶段2", "完成", "Assemble_PEC_ELE边 DOF 消元"],
["PBC 单周期阶段3", "完成", "singlePBC_scatter.json"],
["PBC 双周期阶段3", "完成", "doublePBC_scatter.json角点合并"],
["本征 A/B 矩阵", "完成", "与 MATLAB COO 逐行一致"],
["BELE/MAG/SCD", "待做", "阶段 4"],
[".em 接口", "待做", "Design.em 适配"],
]
table = ax.table(cellText=rows, colLabels=cols, loc="center", cellLoc="center")
table.auto_set_font_size(False)
table.set_fontsize(10)
table.scale(1, 1.55)
for (r, c), cell in table.get_celld().items():
if r == 0:
cell.set_facecolor(BLUE)
cell.set_text_props(color="white", weight="bold")
else:
cell.set_facecolor(LIGHT if r % 2 == 0 else "white")
if c == 1 and rows[r - 1][1] == "完成":
cell.set_text_props(color=GREEN, weight="bold")
ax.set_title("三维拓展模块完成状态(阶段三更新)", fontsize=12, weight="bold", color=DARK, pad=18)
out = OUT / "module_status_stage3.png"
fig.savefig(out, dpi=150, bbox_inches="tight", facecolor="white")
plt.close(fig)
return out
def main() -> None:
OUT.mkdir(parents=True, exist_ok=True)
outputs = [
merge_pbc_faces(),
roadmap_stage_progress(),
pbc_validation_metrics(),
module_status_stage3(),
]
print("Generated PPT assets:")
for p in outputs:
print(f" {p}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
"""Gantt-style timeline for 3D OpticsFEM (target: late July 2026)."""
from __future__ import annotations
from datetime import date, timedelta
from pathlib import Path
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.patches as mpatches
OUT = Path(__file__).resolve().parent
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "Arial"]
plt.rcParams["axes.unicode_minus"] = False
GREEN = "#75BD42"
GREEN_D = "#4A7C3F"
BLUE = "#4874CB"
ORANGE = "#EE822F"
GRAY = "#CCCCCC"
DARK = "#2C3E50"
WHITE = "#FFFFFF"
# (label, start, end, color, status)
# status: done | doing | plan | stretch
T0 = date(2026, 6, 1)
ITEMS = [
("阶段0 工程基础", date(2026, 6, 1), date(2026, 6, 15), GREEN, "done"),
("阶段2 PEC/ELE", date(2026, 6, 10), date(2026, 6, 20), GREEN, "done"),
("阶段3 PBC 单/双周期", date(2026, 6, 15), date(2026, 6, 25), GREEN, "done"),
("阶段1 本征频率 Run/Post", date(2026, 6, 23), date(2026, 7, 5), ORANGE, "doing"),
("阶段4 BELE/源项边界", date(2026, 6, 28), date(2026, 7, 12), BLUE, "plan"),
("算例验证 & 文档整理", date(2026, 7, 8), date(2026, 7, 18), BLUE, "plan"),
(".em 接口适配(基础)", date(2026, 7, 10), date(2026, 7, 25), BLUE, "plan"),
("阶段5 PML/Port可选", date(2026, 7, 20), date(2026, 8, 10), GRAY, "stretch"),
("阶段6 本征模式/二阶(可选)", date(2026, 8, 1), date(2026, 8, 31), GRAY, "stretch"),
]
MILESTONES = [
(date(2026, 6, 25), "阶段13\n验收"),
(date(2026, 7, 18), "MVP\n内测"),
(date(2026, 7, 31), "7月下旬\n交付"),
]
def main() -> None:
fig, ax = plt.subplots(figsize=(14, 6.2), facecolor=WHITE)
y = len(ITEMS)
for i, (label, start, end, color, status) in enumerate(ITEMS):
yy = y - i
width = (end - start).days + 1
hatch = "" if status != "stretch" else "///"
alpha = 1.0 if status != "stretch" else 0.55
ax.barh(
yy, width, left=mdates.date2num(start), height=0.55,
color=color, edgecolor=DARK, linewidth=0.8,
hatch=hatch, alpha=alpha,
)
mid = start + timedelta(days=width // 2)
ax.text(
mdates.date2num(mid), yy, label,
ha="center", va="center", fontsize=9.5,
color=WHITE if status in ("done", "doing") else DARK,
fontweight="bold" if status == "doing" else "normal",
)
for ms_date, ms_text in MILESTONES:
x = mdates.date2num(ms_date)
ax.axvline(x, color="#C0392B", ls="--", lw=1.2, alpha=0.75, zorder=0)
ax.text(x, y + 0.55, ms_text, ha="center", va="bottom", fontsize=8.5,
color="#C0392B", fontweight="bold")
ax.set_yticks([])
ax.set_ylim(0.3, y + 1.2)
ax.set_xlim(mdates.date2num(date(2026, 5, 28)), mdates.date2num(date(2026, 9, 5)))
ax.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.MO, interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%m/%d"))
ax.xaxis.set_minor_locator(mdates.DayLocator(interval=1))
ax.grid(axis="x", alpha=0.25, ls="--")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["left"].set_visible(False)
ax.set_title(
"OpticsFEM 3D 扩展 — 时间节点规划目标2026年7月下旬交付",
fontsize=14, fontweight="bold", color=DARK, pad=14,
)
legend_items = [
mpatches.Patch(facecolor=GREEN, edgecolor=DARK, label="已完成"),
mpatches.Patch(facecolor=ORANGE, edgecolor=DARK, label="进行中"),
mpatches.Patch(facecolor=BLUE, edgecolor=DARK, label="7月计划"),
mpatches.Patch(facecolor=GRAY, edgecolor=DARK, hatch="///", alpha=0.55, label="8月及以后扩展"),
]
ax.legend(handles=legend_items, loc="lower right", fontsize=9, framealpha=0.95)
out = OUT / "timeline_july2026.png"
fig.savefig(out, dpi=180, bbox_inches="tight", facecolor=WHITE)
plt.close(fig)
print(f"Saved {out}")
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -3,32 +3,145 @@
"EletricType": 2,
"lambda": 0.8,
"NbrBoundary": 14,
"BoundaryFlag": [0,2,2,2,2,2,0,0,0,0,0,0,0,2],
"BoundaryFlag": [
2,
2,
2,
2,
2,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
"sbc": {
"Index": [1, 2, 3, 4, 5, 14],
"SBCType": [0, 0, 0, 1, 0, 0],
"E0x": ["1", "1", "1", "1", "1", "1"],
"E0y": ["0", "0", "0", "0", "0", "0"],
"E0z": ["0", "0", "0", "0", "0", "0"],
"kx": [0, 0, 0, 0, 0, 0],
"ky": [0, 0, 0, 0, 0, 0],
"kz": [0, 0, 0, 0, 0, 0]
"Index": [
1,
2,
3,
4,
5,
14
],
"SBCType": [
0,
0,
0,
1,
0,
0
],
"E0x": [
"1",
"1",
"1",
"1",
"1",
"1"
],
"E0y": [
"0",
"0",
"0",
"0",
"0",
"0"
],
"E0z": [
"0",
"0",
"0",
"0",
"0",
"0"
],
"kx": [
0,
0,
0,
0,
0,
0
],
"ky": [
0,
0,
0,
0,
0,
0
],
"kz": [
0,
0,
0,
0,
0,
0
]
},
"NbrDomain": 2,
"domainType": [2, 2],
"domainIndex": [0, 1],
"matType": [0, 0],
"epsilonrR": [1.0, 1.5],
"epsilonrI": [0.0, 0.0],
"murR": [1.0, 1.0],
"murI": [0.0, 0.0],
"chiheR": [0.0, 0.0],
"chiehR": [0.0, 0.0],
"chiheI": [0.0, 0.0],
"chiehI": [0.0, 0.0],
"sigma": [0.0, 0.0],
"n": [1.0, 1.0],
"k": [0.0, 0.0],
"domainType": [
2,
2
],
"domainIndex": [
0,
1
],
"matType": [
0,
0
],
"epsilonrR": [
1.0,
1.5
],
"epsilonrI": [
0.0,
0.0
],
"murR": [
1.0,
1.0
],
"murI": [
0.0,
0.0
],
"chiheR": [
0.0,
0.0
],
"chiehR": [
0.0,
0.0
],
"chiheI": [
0.0,
0.0
],
"chiehI": [
0.0,
0.0
],
"sigma": [
0.0,
0.0
],
"n": [
1.0,
1.0
],
"k": [
0.0,
0.0
],
"MeshFile": "SBCmesh.dat",
"OutFile": "./OutFile"
}

View File

@ -0,0 +1,148 @@
{
"FemType": 4,
"EletricType": 2,
"ElementOrder": 2,
"lambda": 2.0,
"NbrBoundary": 14,
"BoundaryFlag": [
0,
2,
2,
2,
2,
2,
0,
0,
0,
0,
0,
0,
0,
2
],
"sbc": {
"Index": [
1,
2,
3,
5,
14,
4
],
"SBCType": [
0,
0,
0,
0,
0,
1
],
"E0x": [
"1",
"1",
"1",
"1",
"1",
"1"
],
"E0y": [
"0",
"0",
"0",
"0",
"0",
"0"
],
"E0z": [
"0",
"0",
"0",
"0",
"0",
"0"
],
"kx": [
0,
0,
0,
0,
0,
0
],
"ky": [
0,
0,
0,
0,
0,
0
],
"kz": [
0,
0,
0,
0,
0,
0
]
},
"NbrDomain": 2,
"domainType": [
2,
2
],
"domainIndex": [
0,
1
],
"matType": [
0,
0
],
"epsilonrR": [
1.0,
1.5
],
"epsilonrI": [
0.0,
0.0
],
"murR": [
1.0,
1.0
],
"murI": [
0.0,
0.0
],
"chiheR": [
0.0,
0.0
],
"chiehR": [
0.0,
0.0
],
"chiheI": [
0.0,
0.0
],
"chiehI": [
0.0,
0.0
],
"sigma": [
0.0,
0.0
],
"n": [
1.0,
1.0
],
"k": [
0.0,
0.0
],
"MeshFile": "SBCmesh.dat",
"OutFile": "./OutFile_order2"
}

View File

@ -0,0 +1,147 @@
{
"FemType": 4,
"EletricType": 2,
"lambda": 2.0,
"NbrBoundary": 14,
"BoundaryFlag": [
2,
2,
2,
2,
2,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
"sbc": {
"Index": [
1,
2,
3,
4,
5,
14
],
"SBCType": [
0,
0,
0,
1,
0,
0
],
"E0x": [
"1",
"1",
"1",
"1",
"1",
"1"
],
"E0y": [
"0",
"0",
"0",
"0",
"0",
"0"
],
"E0z": [
"0",
"0",
"0",
"0",
"0",
"0"
],
"kx": [
0,
0,
0,
0,
0,
0
],
"ky": [
0,
0,
0,
0,
0,
0
],
"kz": [
0,
0,
0,
0,
0,
0
]
},
"NbrDomain": 2,
"domainType": [
2,
2
],
"domainIndex": [
0,
1
],
"matType": [
0,
0
],
"epsilonrR": [
1.0,
1.5
],
"epsilonrI": [
0.0,
0.0
],
"murR": [
1.0,
1.0
],
"murI": [
0.0,
0.0
],
"chiheR": [
0.0,
0.0
],
"chiehR": [
0.0,
0.0
],
"chiheI": [
0.0,
0.0
],
"chiehI": [
0.0,
0.0
],
"sigma": [
0.0,
0.0
],
"n": [
1.0,
1.0
],
"k": [
0.0,
0.0
],
"MeshFile": "SBCmesh.dat",
"OutFile": "./OutFile"
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,284 @@
{
"FemType": 4,
"EletricType": 2,
"lambda": 4e-06,
"NbrBoundary": 58,
"BoundaryFlag": [
4,
0,
2,
4,
0,
0,
4,
0,
0,
4,
0,
0,
4,
0,
0,
2,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
4,
4,
4,
4,
4
],
"sbc": {
"Index": [
3,
16
],
"SBCType": [
0,
1
],
"E0x": [
"1",
"1"
],
"E0y": [
"i",
"i"
],
"E0z": [
"0",
"0"
],
"kx": [
0,
0
],
"ky": [
0,
0
],
"kz": [
0,
0
]
},
"pbc": {
"srcIndex": [
1,
4,
7,
10,
13
],
"dstIndex": [
54,
55,
56,
57,
58
],
"phiR": [
1.0
],
"phiI": [
0.0
],
"pbcWaveVec": [
2e-06,
0,
0
]
},
"NbrDomain": 9,
"domainType": [
2,
2,
2,
2,
2,
2,
2,
2,
2
],
"domainIndex": [
0,
1,
2,
3,
4,
5,
6,
7,
8
],
"matType": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"epsilonrR": [
1,
1,
-607.609056,
1.9320998775,
1,
1,
-607.609056,
-607.609056,
-607.609056
],
"epsilonrI": [
0,
0,
-120.77852,
-0.000973,
0,
0,
-120.77852,
-120.77852,
-120.77852
],
"murR": [
1,
1,
1,
1,
1,
1,
1,
1,
1
],
"murI": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"chiheR": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"chiehR": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"chiheI": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"chiehI": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"sigma": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"n": [
1,
1,
1,
1,
1,
1,
1,
1,
1
],
"k": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"MeshFile": "singlePBC_mesh.dat",
"OutFile": "./OutFile"
}

View File

@ -15,10 +15,10 @@ using namespace std;
void Solver_EigenMode::GetSolver(std::string input)
{
json temp = nlohmann::json::parse(input);
_mNbrMode = temp.at("NbrMode");//模式数目
_mSearchValue = temp.at("searchValue");//搜索值-有效折射率
_mLda0 = temp.at("lambda0");//真空波长
_mPreproType = 0;//重排序类型
_mNbrMode = temp.at("NbrMode");//ģʽ<EFBFBD><EFBFBD>Ŀ
_mSearchValue = temp.at("searchValue");//<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ-<2D><>Ч<EFBFBD><D0A7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
_mLda0 = temp.at("lambda0");//<EFBFBD><EFBFBD>ղ<EFBFBD><EFBFBD><EFBFBD>
_mPreproType = 0;//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
}
void Solver_EigenMode::GetRealFlag(bool flag)
@ -144,7 +144,7 @@ void Solver_EigenMode::Run(Eigen::MatrixXcd* x, Eigen::VectorXcd* lambda)
for (int i = 0; i < _mNbrMode; i++)
{
lambda[0](i) = sqrt(-eigenValues[i]) / k0;
cout << lambda[0](i) << endl;//测试用
cout << lambda[0](i) << endl;//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
for (int j = 0; j < _mParam.A_n; j++)
tempX(j, i) = x_complex[i][j];
}
@ -168,10 +168,10 @@ void Solver_EigenMode::Run(Eigen::MatrixXcd* x, Eigen::VectorXcd* lambda)
void Solver_EigenFreq::GetSolver(std::string input)
{
json temp = nlohmann::json::parse(input);
_mNbrMode = temp.at("NbrMode");//模式数目
_mSearchValue = temp.at("searchValue");//搜索值-本征频率
_mPreproType = 0; //重排序类型
_mElectricType = temp.at("EletricType"); //求解电场类型 0-Et 1-Ez 2-E
_mNbrMode = temp.at("NbrMode");//ģʽ<EFBFBD><EFBFBD>Ŀ
_mSearchValue = temp.at("searchValue");//<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ-<2D><><EFBFBD><EFBFBD>Ƶ<EFBFBD><C6B5>
_mPreproType = 0; //<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
_mElectricType = temp.at("EletricType"); //<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0-Et 1-Ez 2-E
}
void Solver_EigenFreq::GetRealFlag(bool flag)
@ -319,15 +319,15 @@ void Solver_EigenFreq::Run(Eigen::MatrixXcd* x, Eigen::VectorXcd* lambda)
void Solver_LdaDom::GetSolver(std::string input)
{
json temp = nlohmann::json::parse(input);
_mLda0 = temp.at("lambda");//真空波长
_mElectricType = temp.at("EletricType");//求解电场类型 0-Et 1-Ez 2-E
_mLda0 = temp.at("lambda");//<EFBFBD><EFBFBD>ղ<EFBFBD><EFBFBD><EFBFBD>
_mElectricType = temp.at("EletricType");//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0-Et 1-Ez 2-E
}
void Solver_LdaDom::GetSolver2(std::string input)
{
json temp = nlohmann::json::parse(input);
_mLda0 = 0.299792458/(double)temp.at("freq");//真空波长
_mElectricType = temp.at("EletricType");//求解电场类型 0-Et 1-Ez 2-E
_mLda0 = 0.299792458/(double)temp.at("freq");//<EFBFBD><EFBFBD>ղ<EFBFBD><EFBFBD><EFBFBD>
_mElectricType = temp.at("EletricType");//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0-Et 1-Ez 2-E
}

View File

@ -4,12 +4,43 @@
#include <cstdio>
#include <fstream>
#ifdef _WIN32
#include <direct.h>
static void EnsureEigenSolverDir(const char* dir)
{
_mkdir(dir);
}
static int RunRealEigenExe()
{
return system("cd real_solver && real.exe");
}
static int RunComplexEigenExe()
{
return system("cd complex_solver && complex.exe");
}
#else
#include <sys/stat.h>
static void EnsureEigenSolverDir(const char* dir)
{
mkdir(dir, 0755);
}
static int RunRealEigenExe()
{
return system("cd ./real_solver && ./real");
}
static int RunComplexEigenExe()
{
return system("cd ./complex_solver && ./complex");
}
#endif
static void rfilewrite(const char *name, int *data, int Norf);
static void cfilewrite(const char *name, int *data, int nzero);
static void datafilewrite(const char *name, double *data, int nzero);
EIGENSOLVER_API void complexEigenSolver(int *A_Roffsets, int *A_Cindices, std::complex<double> *A_complex, int *B_Roffsets, int *B_Cindices, std::complex<double> *B_complex, int A_n, int B_n, int A_nzero, int B_nzero, double target_real, double target_imag, int option, int nums, std::complex<double> *eigenValues, std::complex<double> **eigenVectors) {
EnsureEigenSolverDir("complex_solver");
// 将参数写入文件 canshu.txt
std::ofstream ofs;
ofs.open("./complex_solver/canshu.txt", std::ios::out);
@ -54,7 +85,7 @@ EIGENSOLVER_API void complexEigenSolver(int *A_Roffsets, int *A_Cindices, std::c
delete[] B_real;
delete[] B_imag;
system("cd ./complex_solver && ./complex");
RunComplexEigenExe();
// 读取特征值文件
double *eigenValues_real = new double[nums];
@ -119,6 +150,7 @@ EIGENSOLVER_API void complexEigenSolver(int *A_Roffsets, int *A_Cindices, std::c
}
EIGENSOLVER_API void realEigenSolver(int *A_Roffsets, int *A_Cindices, double *A_real, int *B_Roffsets, int *B_Cindices, double *B_real, int A_n, int B_n, int A_nzero, int B_nzero, double target, int option, int nums, double *eigenValues, double **eigenVectors) {
EnsureEigenSolverDir("real_solver");
//将参数写入文件 canshu.txt
std::ofstream ofs;
ofs.open("./real_solver/canshu.txt", std::ios::out);
@ -143,7 +175,7 @@ EIGENSOLVER_API void realEigenSolver(int *A_Roffsets, int *A_Cindices, double *A
cfilewrite("./real_solver/B_Cindices.dat", B_Cindices, B_nzero);
datafilewrite("./real_solver/B_real.dat", d_B_real, B_nzero);
system("cd ./real_solver && ./real");
RunRealEigenExe();
//读取特征值文件
std::ifstream ifs_EigenValues;

View File

@ -4,12 +4,16 @@
#include <iostream>
#include <string>
int main()
int main(int argc, char* argv[])
{
std::ifstream fd("sbc3d.json"); // FemType=4 散射;本征请改为 eigen3d.json 并编译 eigen3D 目录
std::string jsonFile = "sbc3d_sbc.json";
if (argc > 1)
jsonFile = argv[1];
std::ifstream fd(jsonFile);
if (!fd.is_open())
{
std::cout << "cannot open sbc3d.json" << std::endl;
std::cout << "cannot open " << jsonFile << std::endl;
return -1;
}
std::string str((std::istreambuf_iterator<char>(fd)), std::istreambuf_iterator<char>());
@ -18,7 +22,7 @@ int main()
OpticsFEMData data;
data.test1 = 1;
data.test2 = 2;
data.data = (char*)str.data();
data.data = const_cast<char*>(str.c_str());
int ret = femAPI.OpticsFEM_All(data);
std::cout << "OpticsFEM_All returned " << ret << std::endl;

View File

@ -8,6 +8,92 @@
#include "../nlohmann/json.hpp"
using json = nlohmann::json;
namespace {
void parsePbcBlock(const json& pbc, Phy_WaveOpticsModel::PbcGroupConfig& group)
{
std::vector<int> srcIndex = pbc.at("srcIndex");
std::vector<int> dstIndex = pbc.at("dstIndex");
std::vector<double> phiR = pbc.at("phiR");
std::vector<double> phiI = pbc.at("phiI");
const int nbrPbcPairs = static_cast<int>(phiR.size());
if (nbrPbcPairs <= 0)
return;
group.pbc = Eigen::MatrixXi(nbrPbcPairs, 2);
group.data = Eigen::VectorXcd(nbrPbcPairs);
group.angle = Eigen::VectorXd(nbrPbcPairs);
group.waveVec = Eigen::MatrixXd::Zero(3, nbrPbcPairs);
group.srcDomains.clear();
group.dstDomains.clear();
group.srcDomains.resize(nbrPbcPairs);
group.dstDomains.resize(nbrPbcPairs);
const bool multiDomainSinglePair =
(nbrPbcPairs == 1 && srcIndex.size() > 1 && srcIndex.size() == dstIndex.size());
for (int i = 0; i < nbrPbcPairs; i++)
{
group.data(i) = std::complex<double>(phiR[i], phiI[i]);
group.angle(i) = 0.0;
if (pbc.contains("pbcAngle"))
{
if (pbc.at("pbcAngle").is_array())
{
std::vector<double> angles = pbc.at("pbcAngle");
if (i < static_cast<int>(angles.size()))
group.angle(i) = angles[i];
}
else
{
group.angle(i) = pbc.at("pbcAngle");
}
}
if (pbc.contains("pbcWaveVec"))
{
const auto& wv = pbc.at("pbcWaveVec");
if (wv.is_array() && !wv.empty())
{
if (wv[0].is_array())
{
for (int k = 0; k < 3 && k < static_cast<int>(wv[i].size()); k++)
group.waveVec(k, i) = wv[i][k];
}
else if (wv.size() >= 3)
{
for (int k = 0; k < 3; k++)
group.waveVec(k, i) = wv[k];
}
}
}
if (multiDomainSinglePair)
{
group.srcDomains[i] = Eigen::VectorXi(static_cast<int>(srcIndex.size()));
group.dstDomains[i] = Eigen::VectorXi(static_cast<int>(dstIndex.size()));
for (int k = 0; k < static_cast<int>(srcIndex.size()); k++)
{
group.srcDomains[i](k) = srcIndex[k];
group.dstDomains[i](k) = dstIndex[k];
}
group.pbc(i, 0) = srcIndex[0];
group.pbc(i, 1) = dstIndex[0];
}
else
{
group.pbc(i, 0) = srcIndex[i];
group.pbc(i, 1) = dstIndex[i];
group.srcDomains[i] = Eigen::VectorXi(1);
group.dstDomains[i] = Eigen::VectorXi(1);
group.srcDomains[i](0) = srcIndex[i];
group.dstDomains[i](0) = dstIndex[i];
}
}
}
} // namespace
#include <fstream>
#include <iostream>
#include <iomanip>
@ -17,6 +103,10 @@ void Phy_WaveOpticsModel::Test_ReadData(string str)
{
json js = nlohmann::json::parse(str);
_mElementOrder = js.value("ElementOrder", 1);
if (_mElementOrder != 1 && _mElementOrder != 2)
_mElementOrder = 1;
std::vector<int> BoundaryFlag = js.at("BoundaryFlag");
int nbrBoundary = js.at("NbrBoundary");
int nbrPEC, nbrPMC, nbrELE, nbrSBC, nbrPBC, nbrMAG, nbrSCD;
@ -54,8 +144,7 @@ void Phy_WaveOpticsModel::Test_ReadData(string str)
_mEincy = new std::string[nbrSBC];
_mEincz = new std::string[nbrSBC];
_mSBCDir = Eigen::MatrixXd(nbrSBC, 3);
_mPBC = Eigen::MatrixXi(nbrPBC, 2);
_mPBCData = Eigen::VectorXcd(nbrPBC);
_mPBCGroups.clear();
_mMAG = Eigen::VectorXi(nbrMAG);
_mSCD = Eigen::VectorXi(nbrSCD);
_mPortEinc = new std::string[3];
@ -97,10 +186,23 @@ void Phy_WaveOpticsModel::Test_ReadData(string str)
std::vector<double> kx = sbc.at("kx");
std::vector<double> ky = sbc.at("ky");
std::vector<double> kz = sbc.at("kz");
const int nSbcExplicit = static_cast<int>(Index.size());
if (nSbcExplicit != nbrSBC)
{
nbrSBC = nSbcExplicit;
_mSBC = Eigen::MatrixXi(nbrSBC, 2);
delete[] _mEincx;
delete[] _mEincy;
delete[] _mEincz;
_mEincx = new std::string[nbrSBC];
_mEincy = new std::string[nbrSBC];
_mEincz = new std::string[nbrSBC];
_mSBCDir = Eigen::MatrixXd(nbrSBC, 3);
}
for (int i = 0; i < nbrSBC; i++)
{
_mSBC(i, 0) = Index[i];
_mSBC(i, 1) = SBCType[i];
_mSBC(i, 0) = static_cast<int>(Index[i]);
_mSBC(i, 1) = static_cast<int>(SBCType[i]);
_mEincx[i] = E0x[i];
_mEincy[i] = E0y[i];
_mEincz[i] = E0z[i];
@ -169,21 +271,23 @@ void Phy_WaveOpticsModel::Test_ReadData(string str)
}
nbrPBC = nbrPBC / 2;
if (nbrPBC != 0)
if (js.contains("pbc"))
{
json pbc = js.at("pbc");
std::vector<int> srcIndex = pbc.at("srcIndex");
std::vector<int> dstIndex = pbc.at("dstIndex");
std::vector<double> phiR = pbc.at("phiR");
std::vector<double> phiI = pbc.at("phiI");
std::vector<std::complex<double>> PBCData(nbrPBC);
for (int i = 0; i < nbrPBC; i++)
{
_mPBC(i, 0) = srcIndex[i];
_mPBC(i, 1) = dstIndex[i];
PBCData[i] = std::complex<double>(phiR[i], phiI[i]);
_mPBCData(i) = PBCData[i];
PbcGroupConfig group;
parsePbcBlock(js.at("pbc"), group);
if (group.pbc.rows() > 0)
_mPBCGroups.push_back(group);
}
if (js.contains("pbc2"))
{
PbcGroupConfig group2;
parsePbcBlock(js.at("pbc2"), group2);
if (group2.pbc.rows() > 0)
_mPBCGroups.push_back(group2);
}
else if (nbrPBC != 0 && _mPBCGroups.empty())
{
// legacy: BoundaryFlag==4 pairs without explicit pbc block
}
if (js.contains("mpd"))
@ -240,6 +344,12 @@ void Phy_WaveOpticsModel::Test_ReadData(string str)
_mcurlEbx = bele.at("curlEbx");
_mcurlEby = bele.at("curlEby");
_mcurlEbz = bele.at("curlEbz");
if (bele.contains("curlcurlEbx"))
_mcurlcurlEbx = bele.at("curlcurlEbx");
if (bele.contains("curlcurlEby"))
_mcurlcurlEby = bele.at("curlcurlEby");
if (bele.contains("curlcurlEbz"))
_mcurlcurlEbz = bele.at("curlcurlEbz");
}
if (js.contains("pml"))
{
@ -308,6 +418,12 @@ void Phy_WaveOpticsModel::Test_ReadData(string str)
{
_mPortEoutCurl[i] = EoutCurl[i];
}
if (port.contains("modeFile"))
{
const std::string modeFile = port.at("modeFile").get<std::string>();
if (!_mPortModes.LoadFromFile(modeFile))
std::cerr << "[Phy] failed to load port mode file: " << modeFile << std::endl;
}
}
if (js.contains("beam"))
{

View File

@ -0,0 +1,175 @@
"""Build SBCmesh.mat / SBCmesh.dat from COMSOL SBC.mph (same logic as getMesh.m)."""
from __future__ import annotations
import argparse
from pathlib import Path
import mph
import numpy as np
import scipy.io as sio
def _build_mesh_struct(mph_path: Path) -> dict:
client = mph.start()
model = client.load(str(mph_path))
m = model.java.component("comp1").mesh("mesh1")
vertex = np.array(m.getVertex(), dtype=float).T
nbr_vertex = vertex.shape[0]
tet_raw = np.array(m.getElem("tet"), dtype=int).T
nbr_tet = tet_raw.shape[0]
domain_of_tet = np.array(m.getElemEntity("tet"), dtype=int).reshape(-1, 1)
tet = np.sort(tet_raw, axis=1) + 1
el2no = tet.T
rows_n1 = [0, 0, 0, 1, 1, 2]
rows_n2 = [1, 2, 3, 2, 3, 3]
n1 = el2no[rows_n1, :].reshape(-1, order="F")
n2 = el2no[rows_n2, :].reshape(-1, order="F")
el_ed2no_array = np.column_stack([n1, n2])
edge, edge_of_tet_flat = np.unique(el_ed2no_array, axis=0, return_inverse=True)
nbr_edge = edge.shape[0]
edge_of_tet = edge_of_tet_flat.reshape(6, nbr_tet, order="F").T
rows_f1 = [0, 0, 0, 1]
rows_f2 = [1, 1, 2, 2]
rows_f3 = [2, 3, 3, 3]
f1 = el2no[rows_f1, :].reshape(-1, order="F")
f2 = el2no[rows_f2, :].reshape(-1, order="F")
f3 = el2no[rows_f3, :].reshape(-1, order="F")
el_face_array = np.column_stack([f1, f2, f3])
tri_raw = np.array(m.getElem("tri"), dtype=int).T
tri = np.sort(tri_raw, axis=1) + 1
domain_of_tri = np.array(m.getElemEntity("tri"), dtype=int).reshape(-1, 1)
nbr_tri = tri.shape[0]
conn_of_tri = np.zeros((nbr_tri, 2), dtype=int)
for i in range(nbr_tri):
matches = np.where(np.all(el_face_array == tri[i], axis=1))[0]
if matches.size == 0:
raise RuntimeError(f"boundary tri {i + 1} not found in tet faces")
index = int(matches[0]) + 1
conn_of_tri[i, 0] = (index - 1) // 4 + 1
conn_of_tri[i, 1] = index - (conn_of_tri[i, 0] - 1) * 4
norm_of_face = np.zeros((14, 3), dtype=float)
norm_of_face[0] = [-1, 0, 0]
norm_of_face[1] = [0, -1, 0]
norm_of_face[2] = [0, 0, -1]
norm_of_face[3] = [0, 0, 1]
norm_of_face[4] = [0, 1, 0]
norm_of_face[13] = [1, 0, 0]
return {
"NbrVertex": np.int32(nbr_vertex),
"Vertex": vertex,
"NbrTet": np.int32(nbr_tet),
"DomainOfTet": domain_of_tet,
"Tet": tet,
"NbrEdge": np.int32(nbr_edge),
"Edge": edge.astype(np.int32),
"EdgeOfTet": edge_of_tet.astype(np.int32),
"NbrTri": np.int32(nbr_tri),
"Tri": tri.astype(np.int32),
"DomainOfTri": domain_of_tri,
"ConnOfTri": conn_of_tri.astype(np.int32),
"NormOfFace": norm_of_face,
}
def _write_dat(mesh: dict, dat_path: Path) -> None:
lines: list[str] = []
lines.append("NbrVertex")
lines.append(str(int(mesh["NbrVertex"])))
lines.append("Vertex")
for row in mesh["Vertex"]:
lines.append(f"{row[0]:.16g} {row[1]:.16g} {row[2]:.16g}")
lines.append("NbrTet")
lines.append(str(int(mesh["NbrTet"])))
lines.append("Tet")
for row in mesh["Tet"]:
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])} {int(row[3])}")
lines.append("DomainOfTet")
for v in mesh["DomainOfTet"].reshape(-1):
lines.append(str(int(v)))
lines.append("NbrEdge")
lines.append(str(int(mesh["NbrEdge"])))
lines.append("Edge")
for row in mesh["Edge"]:
lines.append(f"{int(row[0])} {int(row[1])}")
lines.append("EdgeOfTet")
for row in mesh["EdgeOfTet"]:
lines.append(" ".join(str(int(x) + 1) for x in row))
lines.append("NbrTri")
lines.append(str(int(mesh["NbrTri"])))
lines.append("Tri")
for row in mesh["Tri"]:
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])}")
lines.append("DomainOfTri")
for v in mesh["DomainOfTri"].reshape(-1):
lines.append(str(int(v)))
lines.append("ConnOfTri")
for row in mesh["ConnOfTri"]:
lines.append(f"{int(row[0])} {int(row[1])}")
norm_rows = [1, 2, 3, 4, 5, 14]
lines.append("NormOfFace")
lines.append(str(len(norm_rows)))
for d in norm_rows:
n = mesh["NormOfFace"][d - 1]
lines.append(f"{d} {n[0]:.16g} {n[1]:.16g} {n[2]:.16g}")
dat_path.parent.mkdir(parents=True, exist_ok=True)
dat_path.write_text("\n".join(lines) + "\n", encoding="ascii")
def build_sbcmesh(mph_path: Path, out_dir: Path, deploy: Path | None = None) -> dict:
mesh = _build_mesh_struct(mph_path)
out_dir.mkdir(parents=True, exist_ok=True)
mat_path = out_dir / "SBCmesh.mat"
dat_path = out_dir / "SBCmesh.dat"
sio.savemat(str(mat_path), {"mesh": mesh}, do_compression=True)
_write_dat(mesh, dat_path)
if deploy is not None:
deploy.mkdir(parents=True, exist_ok=True)
import shutil
shutil.copy2(mat_path, deploy / "SBCmesh.mat")
shutil.copy2(dat_path, deploy / "SBCmesh.dat")
print(f"Wrote {mat_path}")
print(f"Wrote {dat_path}")
print(
f" NbrVertex={mesh['NbrVertex']}, NbrTet={mesh['NbrTet']}, "
f"NbrEdge={mesh['NbrEdge']}, NbrTri={mesh['NbrTri']}"
)
print(f" A COO estimate (NbrTet*36) = {int(mesh['NbrTet']) * 36}")
if deploy is not None:
print(f"Deployed to {deploy}")
return mesh
def main() -> None:
parser = argparse.ArgumentParser(description="Build SBCmesh from COMSOL mph")
parser.add_argument("mph_file", type=Path)
parser.add_argument("-o", "--output-dir", type=Path, default=None)
parser.add_argument("--deploy", type=Path, default=None)
args = parser.parse_args()
out_dir = args.output_dir or args.mph_file.parent
build_sbcmesh(args.mph_file, out_dir, deploy=args.deploy)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,189 @@
#!/usr/bin/env python3
"""Export MeshData2x.mat (Fem4) or MeshData2.mat (Fem2) to OpticsFEM Portmesh_*.dat."""
from __future__ import annotations
import argparse
from pathlib import Path
import numpy as np
try:
import scipy.io as sio
except ImportError:
sio = None
def _load_mat_mesh(mat_path: Path) -> object:
"""Load Mesh struct from .mat (v7 or v7.3)."""
if sio is not None:
try:
data = sio.loadmat(str(mat_path), squeeze_me=True, struct_as_record=False)
return data["Mesh"]
except NotImplementedError:
pass
import h5py
class MeshView:
pass
mesh = MeshView()
with h5py.File(str(mat_path), "r") as f:
m = f["Mesh"]
mesh.Nodes = np.asarray(m["Nodes"]).T
mesh.Elements = np.asarray(m["Elements"]).T
mesh.Domains = np.asarray(m["Domains"]).reshape(-1)
mesh.Faces = np.asarray(m["Faces"]).T
mesh.FacesIndex = np.asarray(m["FacesIndex"]).reshape(-1)
return mesh
def build_mesh_from_mat(mat_path: Path) -> dict:
mesh = _load_mat_mesh(mat_path)
vertex = np.asarray(mesh.Nodes, dtype=float)
tet_raw = np.asarray(mesh.Elements, dtype=int)
domain_of_tet = np.asarray(mesh.Domains, dtype=int).reshape(-1, 1)
tri_raw = np.asarray(mesh.Faces, dtype=int)
domain_of_tri = np.asarray(mesh.FacesIndex, dtype=int).reshape(-1, 1)
nbr_vertex = vertex.shape[0]
nbr_tet = tet_raw.shape[0]
# Keep MATLAB Elements vertex order — sorting breaks Nedelec local edge BF mapping.
tet = np.asarray(tet_raw, dtype=np.int32)
# Match MATLAB PhysicMatrixAssembly: unique(...,'rows') sorts edge pairs
# lexicographically; EdgesOfElements maps to that sorted order (not first-seen).
el2no = tet.T
n1 = el2no[[0, 0, 0, 1, 1, 2], :]
n2 = el2no[[1, 2, 3, 2, 3, 3], :]
el_ed2no = np.column_stack([n1.reshape(-1, order="F"), n2.reshape(-1, order="F")])
edge, ic = np.unique(el_ed2no, axis=0, return_inverse=True)
edge = edge.astype(np.int32)
edge_of_tet = (ic.reshape(6, -1, order="F").T + 1).astype(np.int32)
tri = np.asarray(tri_raw, dtype=np.int32)
el2no = tet.T
rows_f1 = [0, 0, 0, 1]
rows_f2 = [1, 1, 2, 2]
rows_f3 = [2, 3, 3, 3]
f1 = el2no[rows_f1, :].reshape(-1, order="F")
f2 = el2no[rows_f2, :].reshape(-1, order="F")
f3 = el2no[rows_f3, :].reshape(-1, order="F")
el_face_array = np.column_stack([f1, f2, f3])
conn_of_tri = np.zeros((tri.shape[0], 2), dtype=np.int32)
for i in range(tri.shape[0]):
tri_sorted = np.sort(tri[i])
matches = np.where(np.all(np.sort(el_face_array, axis=1) == tri_sorted, axis=1))[0]
if matches.size == 0:
raise RuntimeError(f"boundary tri {i + 1} not found in tet faces")
index = int(matches[0]) + 1
conn_of_tri[i, 0] = (index - 1) // 4 + 1
conn_of_tri[i, 1] = index - (conn_of_tri[i, 0] - 1) * 4
norm_of_face = np.zeros((24, 3), dtype=float)
norm_of_face[0] = [-1, 0, 0]
norm_of_face[1] = [0, -1, 0]
norm_of_face[2] = [0, 0, -1]
norm_of_face[3] = [0, 0, 1]
norm_of_face[4] = [0, 1, 0]
norm_of_face[23] = [1, 0, 0]
return {
"NbrVertex": np.int32(nbr_vertex),
"Vertex": vertex,
"NbrTet": np.int32(nbr_tet),
"DomainOfTet": domain_of_tet,
"Tet": tet.astype(np.int32),
"NbrEdge": np.int32(edge.shape[0]),
"Edge": edge,
"EdgeOfTet": edge_of_tet,
"NbrTri": np.int32(tri.shape[0]),
"Tri": tri.astype(np.int32),
"DomainOfTri": domain_of_tri,
"ConnOfTri": conn_of_tri,
"NormOfFace": norm_of_face,
}
def write_dat(mesh: dict, dat_path: Path) -> None:
lines: list[str] = []
lines.append("NbrVertex")
lines.append(str(int(mesh["NbrVertex"])))
lines.append("Vertex")
for row in mesh["Vertex"]:
lines.append(f"{row[0]:.16g} {row[1]:.16g} {row[2]:.16g}")
lines.append("NbrTet")
lines.append(str(int(mesh["NbrTet"])))
lines.append("Tet")
for row in mesh["Tet"]:
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])} {int(row[3])}")
lines.append("DomainOfTet")
for v in mesh["DomainOfTet"].reshape(-1):
lines.append(str(int(v)))
lines.append("NbrEdge")
lines.append(str(int(mesh["NbrEdge"])))
lines.append("Edge")
for row in mesh["Edge"]:
lines.append(f"{int(row[0])} {int(row[1])}")
lines.append("EdgeOfTet")
for row in mesh["EdgeOfTet"]:
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])} {int(row[3])} {int(row[4])} {int(row[5])}")
lines.append("NbrTri")
lines.append(str(int(mesh["NbrTri"])))
lines.append("Tri")
for row in mesh["Tri"]:
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])}")
lines.append("DomainOfTri")
for v in mesh["DomainOfTri"].reshape(-1):
lines.append(str(int(v)))
lines.append("ConnOfTri")
for row in mesh["ConnOfTri"]:
lines.append(f"{int(row[0])} {int(row[1])}")
lines.append("NormOfFace")
active_norms = [
(idx + 1, row)
for idx, row in enumerate(mesh["NormOfFace"])
if np.linalg.norm(row) > 0.0
]
lines.append(str(len(active_norms)))
for domain_id, row in active_norms:
lines.append(f"{int(domain_id)} {row[0]:.16g} {row[1]:.16g} {row[2]:.16g}")
dat_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--mat",
type=Path,
default=Path(__file__).resolve().parents[2]
/ "三维matlab代码/2023-2-端口激励问题(四面体网格)/MeshData2x.mat",
)
parser.add_argument(
"--out",
type=Path,
default=Path(__file__).resolve().parents[1] / "port/Release/Portmesh_fem4.dat",
)
args = parser.parse_args()
# If out still defaults to fem4 but mat is MeshData2, suggest fem2 name
if args.out.name == "Portmesh_fem4.dat" and "MeshData2.mat" in args.mat.name and "MeshData2x" not in args.mat.name:
args.out = args.out.with_name("Portmesh_fem2.dat")
mesh = build_mesh_from_mat(args.mat)
args.out.parent.mkdir(parents=True, exist_ok=True)
write_dat(mesh, args.out)
print(f"Wrote {args.out} (V={mesh['NbrVertex']}, T={mesh['NbrTet']}, E={mesh['NbrEdge']})")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""Compare two OpticsFEM COO exports (Ai/Aj/Av, Bv_real/Bv_imag)."""
from __future__ import annotations
import argparse
from pathlib import Path
import numpy as np
from scipy import sparse
def load_coo(prefix: Path) -> tuple[sparse.csr_matrix, np.ndarray]:
ai = np.loadtxt(prefix / "Ai.txt", dtype=np.int64)
aj = np.loadtxt(prefix / "Aj.txt", dtype=np.int64)
av: list[complex] = []
for line in (prefix / "Av.txt").read_text(encoding="utf-8", errors="ignore").splitlines():
line = line.strip()
if not line:
continue
if line.startswith("(") and line.endswith(")"):
re_s, im_s = line[1:-1].split(",")
av.append(complex(float(re_s), float(im_s)))
else:
av.append(complex(float(line)))
a = sparse.coo_matrix((np.asarray(av), (ai, aj))).tocsr()
br = np.loadtxt(prefix / "Bv_real.txt")
bi = np.loadtxt(prefix / "Bv_imag.txt")
b = br + 1j * bi
return a, b
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("ref", type=Path, help="reference directory (e.g. MATLAB OutFile_fem4_ab)")
ap.add_argument("other", type=Path, help="other directory (e.g. C++ OutFile)")
args = ap.parse_args()
a_ref, b_ref = load_coo(args.ref)
a_other, b_other = load_coo(args.other)
print(f"ref: n={a_ref.shape[0]} nnz={a_ref.nnz} |b|={np.linalg.norm(b_ref):.6g}")
print(f"other: n={a_other.shape[0]} nnz={a_other.nnz} |b|={np.linalg.norm(b_other):.6g}")
if a_ref.shape != a_other.shape:
print("[FAIL] matrix shape mismatch")
return 1
if b_ref.size != b_other.size:
print("[FAIL] b length mismatch")
return 1
d = a_ref - a_other
max_abs = abs(d).max()
nnz_diff = d.nnz
rel = max_abs / max(abs(a_ref).max(), 1e-30)
print(f"A max|diff|={max_abs:.6g} rel={rel:.6g} nnz(diff)={nnz_diff}")
b_diff = np.max(np.abs(b_ref - b_other))
print(f"b max|diff|={b_diff:.6g}")
if max_abs < 1e-9 and b_diff < 1e-9:
print("[OK] matrices match within 1e-9")
return 0
if max_abs < 1e-6 and b_diff < 1e-6:
print("[WARN] close but not identical")
return 0
print("[FAIL] significant difference")
return 2
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -0,0 +1,229 @@
#!/usr/bin/env python3
"""Diagnose double-PBC A matrix diff: MATLAB vs C++ projection."""
from __future__ import annotations
import math
import re
from collections import Counter, defaultdict
from pathlib import Path
import numpy as np
import scipy.io as sio
from scipy import sparse
ROOT = Path(__file__).resolve().parents[1]
MAT_ROOT = ROOT.parent / "三维matlab代码" / "matlab 3D一阶基+散射边界条件+周期边界"
CPP_OUT = ROOT / "build" / "Release" / "OutFile_double"
MAT_OUT = MAT_ROOT / "OutFile_double"
MAT_ASM = MAT_ROOT / "OutFile_double_asm"
MESH_MAT = MAT_ROOT / "doublePBC_mesh.mat"
def load_coo(prefix: Path) -> sparse.csr_matrix:
ai = np.loadtxt(prefix / "Ai.txt", dtype=np.int64)
aj = np.loadtxt(prefix / "Aj.txt", dtype=np.int64)
av = []
with open(prefix / "Av.txt", encoding="utf-8", errors="ignore") as f:
for line in f:
line = line.strip()
if not line:
continue
m = re.match(r"\(([-+0-9.eE]+),([-+0-9.eE]+)\)", line)
av.append(complex(float(m.group(1)), float(m.group(2))))
n = int(max(ai.max(), aj.max()) + 1)
return sparse.csr_matrix((av, (ai, aj)), shape=(n, n))
def find_tri(domains, mesh) -> np.ndarray:
domains = np.atleast_1d(domains)
dom_tri = np.asarray(mesh.DomainOfTri).flatten()
conn = np.asarray(mesh.ConnOfTri, dtype=int)
out = []
for j, d in enumerate(dom_tri):
if d in domains:
out.append(j)
return np.array(out, dtype=int)
def face_edges(mesh, tri_idx: int) -> list[int]:
num_tet, num_face = np.asarray(mesh.ConnOfTri[tri_idx], dtype=int) - 1
e = np.asarray(mesh.EdgeOfTet[num_tet], dtype=int).flatten() - 1
face = num_face + 1
if face == 1:
return [e[0], e[1], e[3]]
if face == 2:
return [e[0], e[2], e[4]]
if face == 3:
return [e[1], e[2], e[5]]
if face == 4:
return [e[3], e[4], e[5]]
raise ValueError(face)
def find_pbc_index(src, dst, dis, mesh) -> np.ndarray:
"""MATLAB findPBCIndex (1-based edge ids in output)."""
src_tris = find_tri(src, mesh)
dst_tris = find_tri(dst, mesh)
src_edges, dst_edges = [], []
for tri in src_tris:
src_edges.extend(face_edges(mesh, tri))
for tri in dst_tris:
dst_edges.extend(face_edges(mesh, tri))
src_edges = np.unique(src_edges)
dst_edges = np.unique(dst_edges)
dis = np.asarray(dis, dtype=float).reshape(3)
dl = np.linalg.norm(dis)
err = dl * 0.00005
vertex = np.asarray(mesh.Vertex, dtype=float)
edge = np.asarray(mesh.Edge, dtype=int) - 1
pairs = []
for si in src_edges:
v1 = vertex[edge[si, 0]]
v2 = vertex[edge[si, 1]]
matched = False
for dj in dst_edges:
v3 = vertex[edge[dj, 0]]
v4 = vertex[edge[dj, 1]]
l1 = abs(np.linalg.norm(v1 - v3) - dl)
l2 = abs(np.linalg.norm(v2 - v4) - dl)
l3 = abs(np.linalg.norm(v1 - v4) - dl)
l4 = abs(np.linalg.norm(v2 - v3) - dl)
if l1 + l2 < err:
pairs.append((si + 1, dj + 1, 1))
matched = True
break
if l3 + l4 < err:
pairs.append((si + 1, dj + 1, -1))
matched = True
break
if not matched:
pairs.append((si + 1, si + 1, 1))
return np.array(pairs, dtype=int)
def tabulate_duplicate_rows(values: np.ndarray) -> np.ndarray:
"""Return row indices where tabulate count > 1 (MATLAB assembly_pbc_double bug)."""
vals = np.sort(values)
uniq, counts = np.unique(vals, return_counts=True)
rows = np.where(counts > 1)[0] + 1 # 1-based row index in tabulate table
return rows
def duplicate_values(values: np.ndarray) -> np.ndarray:
c = Counter(values.tolist())
return np.array(sorted([v for v, n in c.items() if n > 1]), dtype=int)
def merge_pbc_double_matlab_fixed(pbc1: np.ndarray, pbc2: np.ndarray, phi1: float, phi2: float):
"""Intended MATLAB merge: ovDstIndex = tbl2(find(tbl2(:,2)>1), 1)."""
return merge_pbc_double_correct(pbc1, pbc2, phi1, phi2)
def merge_pbc_double_correct(pbc1: np.ndarray, pbc2: np.ndarray, phi1: float, phi2: float):
"""C++ mergeDoublePbcPairs logic (duplicate dst values)."""
ov = duplicate_values(np.concatenate([pbc1[:, 1], pbc2[:, 1]]))
remove1 = np.zeros(len(pbc1), dtype=bool)
remove2 = np.zeros(len(pbc2), dtype=bool)
merged = []
dst1 = pbc1[:, 1]
src1 = pbc1[:, 0]
dst2 = pbc2[:, 1]
src2 = pbc2[:, 0]
for ov_dst in ov:
i1 = np.where(dst2 == ov_dst)[0]
if len(i1) == 0:
continue
i1 = i1[0]
s2 = src2[i1]
i3 = np.where(dst1 == s2)[0]
if len(i3) == 0:
continue
i3 = i3[0]
s1 = src1[i3]
sign = pbc2[i1, 2] * pbc1[i3, 2]
merged.append((s1, ov_dst, sign, phi1 * phi2 * sign))
remove1[i3] = True
remove2[i1] = True
remove1[dst1 == ov_dst] = True
remove2[dst2 == ov_dst] = True
for i in range(len(pbc1)):
if not remove1[i]:
merged.append((pbc1[i, 0], pbc1[i, 1], pbc1[i, 2], phi1 * pbc1[i, 2]))
for i in range(len(pbc2)):
if not remove2[i]:
merged.append((pbc2[i, 0], pbc2[i, 1], pbc2[i, 2], phi2 * pbc2[i, 2]))
arr = np.array([(m[0], m[1], m[2]) for m in merged], dtype=int)
phi = np.array([m[3] for m in merged], dtype=float)
return arr, phi
def build_p(dof: int, pbc: np.ndarray, phi: np.ndarray) -> sparse.csr_matrix:
rows, cols, data = [], [], []
for i in range(dof):
rows.append(i)
cols.append(i)
data.append(1.0)
for k in range(len(pbc)):
src = int(pbc[k, 0]) - 1
dst = int(pbc[k, 1]) - 1
rows.append(dst)
cols.append(src)
data.append(phi[k])
p_full = sparse.csr_matrix((data, (rows, cols)), shape=(dof, dof))
dst_cols = sorted({int(pbc[k, 1]) - 1 for k in range(len(pbc))})
keep = np.ones(dof, dtype=bool)
keep[dst_cols] = False
return p_full[:, keep]
def diff_stats(a: sparse.csr_matrix, b: sparse.csr_matrix, label: str) -> None:
if a.shape != b.shape:
print(f"{label}: shape mismatch {a.shape} vs {b.shape}")
return
d = a - b
mx = float(np.max(np.abs(d.data))) if d.nnz else 0.0
rel = mx / max(float(np.max(np.abs(b.data))), 1e-30)
print(f"{label}: max|diff|={mx:.6g}, rel_max={rel:.6g}, diff_nnz={d.nnz}")
def main() -> None:
mesh = sio.loadmat(MESH_MAT, squeeze_me=True, struct_as_record=False)["mesh"]
dof = int(mesh.NbrEdge)
pbc1 = find_pbc_index([1, 4, 7, 10, 13], [54, 55, 56, 57, 58], [2e-6, 0, 0], mesh)
pbc2 = find_pbc_index([2, 5, 8, 11, 14], [17, 18, 19, 20, 21], [0, 2e-6, 0], mesh)
print(f"PBC1 pairs: {len(pbc1)}, PBC2 pairs: {len(pbc2)}")
ov_vals = duplicate_values(np.concatenate([pbc1[:, 1], pbc2[:, 1]]))
print(f"Corner overlap dst count (correct): {len(ov_vals)}")
pbc_ok, phi_ok = merge_pbc_double_correct(pbc1, pbc2, -1.0, 1.0)
pbc_fix, phi_fix = merge_pbc_double_matlab_fixed(pbc1, pbc2, -1.0, 1.0)
print(f"Merged constraints: correct={len(pbc_ok)}")
a_asm = load_coo(MAT_ASM)
a_mat = load_coo(MAT_OUT)
a_cpp = load_coo(CPP_OUT)
p_ok = build_p(dof, pbc_ok, phi_ok)
a_proj_ok = p_ok.conj().T @ a_asm @ p_ok
diff_stats(a_mat, a_cpp, "MAT out vs C++ out")
diff_stats(a_mat, a_proj_ok, "MAT out vs asm+correct P")
diff_stats(a_cpp, a_proj_ok, "C++ out vs asm+correct P")
# normE quick
ne_cpp = np.loadtxt(CPP_OUT / "normE", comments="//")
ne_mat = np.loadtxt(MAT_OUT / "normE", comments="//")
rel = np.linalg.norm(ne_cpp - ne_mat) / np.linalg.norm(ne_mat)
print(f"normE L2 rel diff: {rel:.6g}, corr={np.corrcoef(ne_cpp, ne_mat)[0,1]:.6f}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,249 @@
#!/usr/bin/env python3
"""Compare PBC-projected A matrix: MATLAB vs C++, and verify PBC projection pipeline."""
import re
import math
from pathlib import Path
import numpy as np
from scipy import sparse
ROOT = Path(__file__).resolve().parents[1]
MATLAB_DIR = ROOT.parent / "三维matlab代码" / "matlab 3D一阶基+散射边界条件+单周期边界"
CPP_OUT = ROOT / "build" / "Release" / "OutFile"
MAT_OUT = MATLAB_DIR / "OutFile"
MAT_ASM = MATLAB_DIR / "OutFile_asm"
MESH_FILE = MATLAB_DIR / "PBCmesh.dat"
PBC_ANGLE = math.pi / 3
PBC_PHI = complex(0.5, 0.8660254037844386)
def load_coo(prefix: Path) -> sparse.csr_matrix:
def read_col(p):
with open(p, encoding="utf-8", errors="ignore") as f:
return np.array([int(x.strip()) for x in f if x.strip()], dtype=np.int64)
def read_val(p):
vals = []
with open(p, encoding="utf-8", errors="ignore") as f:
for line in f:
line = line.strip()
if not line:
continue
m = re.match(r"\(([-+0-9.eE]+),([-+0-9.eE]+)\)", line)
if m:
vals.append(complex(float(m.group(1)), float(m.group(2))))
else:
vals.append(complex(float(line), 0.0))
return np.array(vals, dtype=np.complex128)
ai = read_col(prefix / "Ai.txt")
aj = read_col(prefix / "Aj.txt")
av = read_val(prefix / "Av.txt")
n = int(max(ai.max(), aj.max()) + 1)
return sparse.csr_matrix((av, (ai, aj)), shape=(n, n))
def read_mesh_dat(path: Path):
"""Minimal PBCmesh.dat reader (0-based indices)."""
lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
i = 0
def read_block(name):
nonlocal i
while i < len(lines) and lines[i].strip() != name:
i += 1
if i >= len(lines):
raise KeyError(name)
i += 1
return i
def read_int():
nonlocal i
v = int(lines[i].strip())
i += 1
return v
def read_floats(n):
nonlocal i
vals = list(map(float, lines[i].split()))
i += 1
return np.array(vals[:n])
read_block("NbrVertex")
n_vertex = read_int()
read_block("Vertex")
vertex = np.zeros((n_vertex, 3))
for r in range(n_vertex):
vertex[r] = read_floats(3)
read_block("NbrEdge")
n_edge = read_int()
read_block("Edge")
edge = np.zeros((n_edge, 2), dtype=np.int64)
for r in range(n_edge):
a, b = map(int, lines[i].split())
edge[r] = (a - 1, b - 1)
i += 1
read_block("NbrTet")
n_tet = read_int()
read_block("Tet")
tet = np.zeros((n_tet, 4), dtype=np.int64)
for r in range(n_tet):
tet[r] = np.array(list(map(int, lines[i].split()))) - 1
i += 1
read_block("EdgeOfTet")
edge_of_tet = np.zeros((n_tet, 6), dtype=np.int64)
for r in range(n_tet):
edge_of_tet[r] = np.array(list(map(int, lines[i].split()))) - 1
i += 1
read_block("NbrTri")
n_tri = read_int()
read_block("ConnOfTri")
conn_of_tri = np.zeros((n_tri, 2), dtype=np.int64)
for r in range(n_tri):
a, b = map(int, lines[i].split())
conn_of_tri[r] = (a - 1, b - 1)
i += 1
read_block("DomainOfTri")
domain_of_tri = np.zeros(n_tri, dtype=np.int64)
for r in range(n_tri):
domain_of_tri[r] = int(lines[i].strip()) # keep 1-based like MATLAB mesh
i += 1
return {
"vertex": vertex,
"edge": edge,
"tet": tet,
"edge_of_tet": edge_of_tet,
"conn_of_tri": conn_of_tri,
"domain_of_tri": domain_of_tri,
"n_edge": n_edge,
}
def find_tri(domains, mesh):
domains = np.atleast_1d(domains)
out = []
for j in range(mesh["conn_of_tri"].shape[0]):
num_tet, _ = mesh["conn_of_tri"][j]
dom = mesh["domain_of_tri"][j]
# DomainOfTri in dat is 1-based after +1 in C++ loader; MATLAB c6 may differ.
# PBCmesh.dat stores 0-based domain ids before C++ +1.
if dom in domains or (dom + 1) in domains:
out.append(j)
return np.array(out, dtype=np.int64)
def face_edges(mesh, tri_idx):
num_tet, num_face = mesh["conn_of_tri"][tri_idx]
e = mesh["edge_of_tet"][num_tet]
face = num_face + 1
if face == 1:
return [e[0], e[1], e[3]]
if face == 2:
return [e[0], e[2], e[4]]
if face == 3:
return [e[1], e[2], e[5]]
if face == 4:
return [e[3], e[4], e[5]]
raise ValueError(face)
def find_pbc_index(src, dst, theta, mesh):
src_tris = find_tri(src, mesh)
dst_tris = find_tri(dst, mesh)
src_edges, dst_edges = [], []
for tri in src_tris:
src_edges.extend(face_edges(mesh, tri))
for tri in dst_tris:
dst_edges.extend(face_edges(mesh, tri))
src_edges = np.unique(src_edges)
dst_edges = np.unique(dst_edges)
tra = np.array(
[[math.cos(theta), -math.sin(theta), 0.0],
[math.sin(theta), math.cos(theta), 0.0],
[0.0, 0.0, 1.0]]
)
tol = 0.01 * 0.00005
pairs = []
for si in src_edges:
v1 = mesh["vertex"][mesh["edge"][si, 0]]
v2 = mesh["vertex"][mesh["edge"][si, 1]]
matched = False
for dj, di in enumerate(dst_edges):
v3 = tra @ mesh["vertex"][mesh["edge"][di, 0]]
v4 = tra @ mesh["vertex"][mesh["edge"][di, 1]]
l1 = np.linalg.norm(v1 - v3)
l2 = np.linalg.norm(v2 - v4)
l3 = np.linalg.norm(v1 - v4)
l4 = np.linalg.norm(v2 - v3)
if l1 + l2 < tol:
pairs.append((si, di, 1))
matched = True
break
if l3 + l4 < tol:
pairs.append((si, di, -1))
matched = True
break
if not matched:
pairs.append((si, -1, 0))
pairs = [(s, d, sg) for s, d, sg in pairs if s != d and d >= 0]
return pairs
def build_P(dof, pairs):
P = sparse.lil_matrix((dof, dof), dtype=np.complex128)
for i in range(dof):
P[i, i] = 1.0
dst_cols = []
for src, dst, sign in pairs:
P[dst, src] = sign * PBC_PHI
dst_cols.append(dst)
keep = [c for c in range(dof) if c not in dst_cols]
return P[:, keep].tocsr()
def diff_stats(A, B, label):
D = A - B
print(f"\n=== {label} ===")
print(f" max |diff| = {np.max(np.abs(D.data)) if D.nnz else 0:.6g}")
print(f" max |real diff| = {np.max(np.abs(D.real.data)) if D.nnz else 0:.6g}")
print(f" max |imag diff| = {np.max(np.abs(D.imag.data)) if D.nnz else 0:.6g}")
if D.nnz and A.shape[0] == B.shape[0]:
di = np.abs(A.diagonal().imag - B.diagonal().imag)
print(f" diag imag max diff = {di.max():.6g}, >10 count = {(di > 10).sum()}")
def main():
print("Loading matrices...")
A_cpp = load_coo(CPP_OUT)
A_mat = load_coo(MAT_OUT)
A_asm = load_coo(MAT_ASM)
diff_stats(A_cpp, A_mat, "C++ OutFile vs MATLAB OutFile (after PBC)")
print("\nLoading mesh and building PBC pairs...")
mesh = read_mesh_dat(MESH_FILE)
# domains in case_config: src=[1,4], dst=[2,5] (1-based in MATLAB)
pairs = find_pbc_index(np.array([1, 4]), np.array([2, 5]), PBC_ANGLE, mesh)
print(f" PBC pairs: {len(pairs)}")
signs = [p[2] for p in pairs]
print(f" sign +1: {signs.count(1)}, sign -1: {signs.count(-1)}")
P = build_P(mesh["n_edge"], pairs)
A_proj = P.conj().T @ A_asm @ P
diff_stats(A_proj, A_mat, "MATLAB asm projected with Python P vs MATLAB OutFile")
diff_stats(A_proj, A_cpp, "MATLAB asm projected with Python P vs C++ OutFile")
# Test sign-flip hypothesis on all -1 pairs
pairs_flip = [(s, d, -sg if sg < 0 else sg) for s, d, sg in pairs]
Pf = build_P(mesh["n_edge"], pairs_flip)
Af = Pf.conj().T @ A_asm @ Pf
diff_stats(Af, A_cpp, "MATLAB asm with flipped -1 signs vs C++ OutFile")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,239 @@
#!/usr/bin/env python3
"""Compare MATLAB vs C++ PBC edge pairing on PBCmesh.dat."""
from __future__ import annotations
import math
from pathlib import Path
import numpy as np
def read_tag(lines: list[str], i: int, expected: str) -> int:
while i < len(lines) and not lines[i].strip():
i += 1
if i >= len(lines) or lines[i].strip() != expected:
got = lines[i].strip() if i < len(lines) else "EOF"
raise ValueError(f"Expected '{expected}' at line {i + 1}, got '{got}'")
return i + 1
def load_mesh(path: Path) -> dict:
lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
i = 0
i = read_tag(lines, i, "NbrVertex")
n_vertex = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "Vertex")
vertex = np.zeros((n_vertex, 3))
for r in range(n_vertex):
vertex[r] = np.array(list(map(float, lines[i].split())))
i += 1
i = read_tag(lines, i, "NbrTet")
n_tet = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "Tet")
tet = np.zeros((n_tet, 4), dtype=np.int64)
for r in range(n_tet):
tet[r] = np.array(list(map(int, lines[i].split())), dtype=np.int64)
i += 1
i = read_tag(lines, i, "DomainOfTet")
i += n_tet
i = read_tag(lines, i, "NbrEdge")
n_edge = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "Edge")
edge = np.zeros((n_edge, 2), dtype=np.int64)
for r in range(n_edge):
edge[r] = np.array(list(map(int, lines[i].split())), dtype=np.int64)
i += 1
i = read_tag(lines, i, "EdgeOfTet")
edge_of_tet = np.zeros((n_tet, 6), dtype=np.int64)
for r in range(n_tet):
edge_of_tet[r] = np.array(list(map(int, lines[i].split())), dtype=np.int64)
i += 1
i = read_tag(lines, i, "NbrTri")
n_tri = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "Tri")
i += n_tri
i = read_tag(lines, i, "DomainOfTri")
domain_of_tri = np.zeros(n_tri, dtype=np.int64)
for r in range(n_tri):
domain_of_tri[r] = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "ConnOfTri")
conn_of_tri = np.zeros((n_tri, 2), dtype=np.int64)
for r in range(n_tri):
conn_of_tri[r] = np.array(list(map(int, lines[i].split())), dtype=np.int64)
i += 1
return {
"vertex": vertex,
"edge": edge,
"tet": tet,
"edge_of_tet": edge_of_tet,
"domain_of_tri": domain_of_tri,
"conn_of_tri": conn_of_tri,
"n_edge": n_edge,
}
def tri_edges(mesh: dict, tri_idx: int) -> list[int]:
num_tet, num_face = mesh["conn_of_tri"][tri_idx]
num_tet -= 1
num_face += 1
eot = mesh["edge_of_tet"][num_tet]
if num_face == 1:
return [eot[0], eot[1], eot[3]]
if num_face == 2:
return [eot[0], eot[2], eot[4]]
if num_face == 3:
return [eot[1], eot[2], eot[5]]
if num_face == 4:
return [eot[3], eot[4], eot[5]]
return []
def collect_edges(mesh: dict, domains: list[int]) -> list[int]:
edges: list[int] = []
for tri_idx, dom in enumerate(mesh["domain_of_tri"]):
if dom in domains:
edges.extend(tri_edges(mesh, tri_idx))
return sorted(set(edges))
def edge_vertices(mesh: dict, edge_idx: int) -> tuple[np.ndarray, np.ndarray]:
e = mesh["edge"][edge_idx - 1]
v = mesh["vertex"]
return v[e[0] - 1], v[e[1] - 1]
def match_pair(mesh: dict, src_e: int, dst_e: int, theta: float, tol: float) -> int | None:
tra = np.array(
[
[math.cos(theta), -math.sin(theta), 0.0],
[math.sin(theta), math.cos(theta), 0.0],
[0.0, 0.0, 1.0],
]
)
v1, v2 = edge_vertices(mesh, src_e)
v3, v4 = edge_vertices(mesh, dst_e)
v3 = tra @ v3
v4 = tra @ v4
l1 = np.linalg.norm(v1 - v3)
l2 = np.linalg.norm(v2 - v4)
l3 = np.linalg.norm(v1 - v4)
l4 = np.linalg.norm(v2 - v3)
if l1 + l2 < tol:
return 1
if l3 + l4 < tol:
return -1
return None
def pair_matlab(mesh: dict, src_edges: list[int], dst_edges: list[int], theta: float) -> list[tuple[int, int, int]]:
tol = 0.01 * 0.00005
pairs: list[tuple[int, int, int]] = []
for src_e in src_edges:
matched = False
for dst_e in dst_edges:
sign = match_pair(mesh, src_e, dst_e, theta, tol)
if sign is not None:
pairs.append((src_e, dst_e, sign))
matched = True
break
if not matched:
pairs.append((src_e, src_e, 1))
return pairs
def pair_cpp(mesh: dict, src_edges: list[int], dst_edges: list[int], theta: float) -> list[tuple[int, int, int]]:
tol = 0.01 * 0.00005
dst_used = [False] * len(dst_edges)
pairs: list[tuple[int, int, int]] = []
for src_e in src_edges:
matched = False
for j, dst_e in enumerate(dst_edges):
if dst_used[j]:
continue
sign = match_pair(mesh, src_e, dst_e, theta, tol)
if sign is not None:
pairs.append((src_e, dst_e, sign))
dst_used[j] = True
matched = True
break
if not matched:
pairs.append((src_e, src_e, 1))
return pairs
def remove_self(pairs: list[tuple[int, int, int]]) -> list[tuple[int, int, int]]:
return [(s, d, sg) for s, d, sg in pairs if s != d]
def main() -> None:
mesh_path = Path(__file__).resolve().parents[1] / "build" / "Release" / "PBCmesh.dat"
mesh = load_mesh(mesh_path)
theta = math.pi / 3
phi = complex(0.5, math.sqrt(3) / 2)
src_domains = [1, 4]
dst_domains = [2, 5]
src_edges = collect_edges(mesh, src_domains)
dst_edges = collect_edges(mesh, dst_domains)
mat_pairs = remove_self(pair_matlab(mesh, src_edges, dst_edges, theta))
cpp_pairs = remove_self(pair_cpp(mesh, src_edges, dst_edges, theta))
mat_map = {(d, s): sg for s, d, sg in mat_pairs}
cpp_map = {(d, s): sg for s, d, sg in cpp_pairs}
print(f"src edges: {len(src_edges)}, dst edges: {len(dst_edges)}")
print(f"MATLAB pairs (no self): {len(mat_pairs)}")
print(f"C++ pairs (no self): {len(cpp_pairs)}")
sign_diff = []
dst_only_mat = []
dst_only_cpp = []
for key, sg_mat in mat_map.items():
if key not in cpp_map:
dst_only_mat.append(key)
elif cpp_map[key] != sg_mat:
sign_diff.append((key, sg_mat, cpp_map[key]))
for key in cpp_map:
if key not in mat_map:
dst_only_cpp.append(key)
print(f"\nSign mismatches: {len(sign_diff)}")
for key, a, b in sign_diff[:15]:
print(f" dst={key[0]} src={key[1]}: mat={a} cpp={b}")
print(f"Only in MATLAB map: {len(dst_only_mat)}")
print(f"Only in C++ map: {len(dst_only_cpp)}")
if dst_only_mat[:5]:
print(" mat examples:", dst_only_mat[:5])
if dst_only_cpp[:5]:
print(" cpp examples:", dst_only_cpp[:5])
# Check DOF 20552 (1-based edge index in mesh)
dof = 20552
for label, pairs in [("MATLAB", mat_pairs), ("C++", cpp_pairs)]:
roles = []
for s, d, sg in pairs:
if s == dof:
roles.append(f"src->{d} sign={sg} phi={sg*phi}")
if d == dof:
roles.append(f"dst<-{s} sign={sg} phi={sg*phi}")
print(f"\nEdge {dof} in {label} PBC: {roles or 'not in pairs'}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,275 @@
#!/usr/bin/env python3
"""Replicate MATLAB FemMatrixAssembly (wave+port+PEC) and compare with reference."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import h5py
import numpy as np
from scipy import sparse
from scipy.sparse.linalg import spsolve
from compare_wave_asm import assemble_wave_matlab, bf_edge, build_edges
from export_ab_coo import export_ab_coo
def gauss_tri_order2() -> tuple[np.ndarray, np.ndarray, np.ndarray]:
xft = np.array([1.0 / 3, 0.6, 0.2, 0.2])
yft = np.array([1.0 / 3, 0.2, 0.6, 0.2])
pft = np.array([-0.28125, 25.0 / 96, 25.0 / 96, 25.0 / 96])
return xft, yft, pft
def bf_et(i: int, u: float, v: float) -> np.ndarray:
if i == 1:
return np.array([1.0 - v, u, 0.0])
if i == 2:
return np.array([v, 1.0 - u, 0.0])
if i == 3:
return np.array([-v, u, 0.0])
raise ValueError(i)
def bf_ez(i: int, u: float, v: float) -> np.ndarray:
return np.array([0.0, 0.0, [1 - u - v, u, v][i - 1]])
def bf_curl_et(i: int, u: float, v: float) -> np.ndarray:
return np.array([0.0, 0.0, [2.0, -2.0, 2.0][i - 1]])
def bf_curl_ez(i: int, u: float, v: float) -> np.ndarray:
if i == 1:
return np.array([-1.0, 1.0, 0.0])
if i == 2:
return np.array([0.0, -1.0, 0.0])
if i == 3:
return np.array([1.0, 0.0, 0.0])
raise ValueError(i)
def get_tet_face(num_face: int) -> tuple[list[int], list[int]]:
mapping = {
1: ([0, 1, 2], [0, 1, 3]),
2: ([0, 1, 3], [0, 2, 4]),
3: ([0, 2, 3], [1, 2, 5]),
4: ([1, 2, 3], [3, 4, 5]),
}
return mapping[num_face]
def assemble_port_face(
nodes: np.ndarray,
elements: np.ndarray,
eoe: np.ndarray,
mode: dict,
face_idx: int,
is_input: bool,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, complex, complex]:
n_edges = int(eoe.max()) + 1
fc = mode["facesConn"][face_idx]
num_ele = int(fc[0]) - 1
num_face = int(fc[1])
face_node, face_bf = get_tet_face(num_face)
idx = elements[num_ele] - 1
x, y, z = nodes[idx, 0], nodes[idx, 1], nodes[idx, 2]
pairs = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
l = np.array([np.linalg.norm(nodes[idx[a]] - nodes[idx[b]]) for a, b in pairs])
mu = 1.0
gamma = complex(mode["gamma"][0], mode["gamma"][1])
power = mode["powerCoef"]
normal = np.array(mode["normal"], dtype=float)
et_coef = np.array(mode["Et_re"]) + 1j * np.array(mode["Et_im"])
ez_coef = np.array(mode["Ez_re"]) + 1j * np.array(mode["Ez_im"])
port_edge = np.array(mode["portEdgeOfFace"][face_idx], dtype=int)
port_ez = np.array(mode["portNewFaces"][face_idx], dtype=int)
jac = np.column_stack([x[:3] - x[3], y[:3] - y[3], z[:3] - z[3]])
inv_jac = np.linalg.inv(jac)
xx, yy, zz = x[face_node], y[face_node], z[face_node]
f_jac = np.zeros((3, 3))
f_jac[0, 0] = -xx[0] + xx[1]
f_jac[0, 1] = -yy[0] + yy[1]
f_jac[1, 0] = -xx[0] + xx[2]
f_jac[1, 1] = -yy[0] + yy[2]
f_jac[2, 2] = 1.0
inv_f = np.linalg.inv(f_jac)
f_jac_s = np.zeros((3, 3))
inv22 = np.linalg.inv(f_jac[:2, :2])
f_jac_s[0, 0] = inv22[1, 1]
f_jac_s[0, 1] = -inv22[1, 0]
f_jac_s[1, 0] = -inv22[0, 1]
f_jac_s[1, 1] = inv22[0, 0]
f_jac_s[2, 2] = 1.0
f_t_jac = f_jac.T / np.linalg.det(f_jac)
f_det = abs(np.linalg.det(f_jac))
f_jac2 = np.zeros((2, 3))
f_jac2[0, 0] = -xx[0] + xx[1]
f_jac2[0, 1] = -yy[0] + yy[1]
f_jac2[1, 0] = -xx[0] + xx[2]
f_jac2[1, 1] = -yy[0] + yy[2]
v_jac = np.linalg.inv(jac)
xft, yft, pft = gauss_tri_order2()
g = np.zeros(n_edges, dtype=complex)
st = np.zeros(n_edges, dtype=complex)
b_edge = np.zeros(n_edges, dtype=complex)
p = 0.0 + 0.0j
b1 = 0.0 + 0.0j
for gp in range(len(pft)):
f_e = np.zeros(3, dtype=complex)
f_curl_e = np.zeros(3, dtype=complex)
for i in range(3):
et = inv_f @ bf_et(i + 1, xft[gp], yft[gp]) * l[face_bf[i]]
ez = bf_ez(i + 1, xft[gp], yft[gp])
curl_et = f_t_jac @ bf_curl_et(i + 1, xft[gp], yft[gp]) * l[face_bf[i]]
curl_ez = f_jac_s @ bf_curl_ez(i + 1, xft[gp], yft[gp])
f_e += et * et_coef[port_edge[i]] + ez * ez_coef[port_ez[i]]
f_curl_e += curl_et * et_coef[port_edge[i]] + curl_ez * ez_coef[port_ez[i]]
rot = np.array([f_e[1], -f_e[0], 0.0])
if is_input:
f_n0 = f_e * power
f_curl_n0 = (f_curl_e + gamma * rot) * power
f_n1 = f_e
f_curl_n1 = f_curl_e - gamma * rot
else:
f_n2 = f_e
f_curl_n2 = f_curl_e + gamma * rot
f_w = f_e
ref = np.array([xft[gp], yft[gp]]) @ f_jac2 + np.array([xx[0], yy[0], zz[0]])
ref3 = ref @ v_jac - np.array([x[3], y[3], z[3]]) @ v_jac
if is_input:
for i in range(3):
bf = inv_jac @ bf_edge(face_bf[i] + 1, ref3[0], ref3[1], ref3[2]) * l[face_bf[i]]
edge_id = int(eoe[num_ele, face_bf[i]])
g[edge_id] += pft[gp] * f_det * (f_w[0] * bf[0] + f_w[1] * bf[1])
temp = mu * np.cross(normal, f_curl_n1)
st[edge_id] += pft[gp] * f_det * np.vdot(bf, temp)
temp0 = mu * np.cross(normal, f_curl_n0)
b_edge[edge_id] += pft[gp] * f_det * np.vdot(bf, temp0)
p += pft[gp] * f_det * (f_w[0] * f_n1[0] + f_w[1] * f_n1[1])
b1 += pft[gp] * f_det * (f_w[0] * f_n0[0] + f_w[1] * f_n0[1])
else:
for i in range(3):
bf = inv_jac @ bf_edge(face_bf[i] + 1, ref3[0], ref3[1], ref3[2]) * l[face_bf[i]]
edge_id = int(eoe[num_ele, face_bf[i]])
g[edge_id] += pft[gp] * f_det * (f_w[0] * bf[0] + f_w[1] * bf[1])
temp = mu * np.cross(normal, f_curl_n2)
st[edge_id] += pft[gp] * f_det * np.vdot(bf, temp)
p += pft[gp] * f_det * (f_w[0] * f_n2[0] + f_w[1] * f_n2[1])
return st, g, b_edge, p, b1
from scipy.sparse import bmat
def block_append_input(a: sparse.csr_matrix, b: np.ndarray, s: np.ndarray, g: np.ndarray, p: complex, b1: complex):
n = b.size
s_col = sparse.csr_matrix(s.reshape(-1, 1))
g_row = sparse.csr_matrix(g.reshape(1, -1))
p_corner = sparse.csr_matrix(np.array([[-p]]))
a2 = bmat([[a, s_col], [g_row, p_corner]], format="csr")
b2 = np.concatenate([-b, [b1]])
return a2, b2
def block_append_output(a: sparse.csr_matrix, b: np.ndarray, t: np.ndarray, g: np.ndarray, p: complex):
n = b.size
t_col = sparse.csr_matrix(t.reshape(-1, 1))
g_row = sparse.csr_matrix(g.reshape(1, -1))
p_corner = sparse.csr_matrix(np.array([[-p]]))
a2 = bmat([[a, t_col], [g_row, p_corner]], format="csr")
b2 = np.concatenate([b, [0.0]])
return a2, b2
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument(
"--export",
type=Path,
default=None,
help="export reduced A/b (COO) to this directory",
)
args = ap.parse_args()
root = Path(__file__).resolve().parents[2]
rel = root / "3D opticsfem-master/port/Release"
with h5py.File(next(root.rglob("MeshData2x.mat")), "r") as f:
nodes = np.asarray(f["Mesh/Nodes"]).T
elements = np.asarray(f["Mesh/Elements"]).T.astype(int)
faces = np.asarray(f["Mesh/Faces"]).T.astype(int)
faces_index = np.asarray(f["Mesh/FacesIndex"]).reshape(-1).astype(int)
domains = np.asarray(f["Mesh/Domains"]).reshape(-1).astype(int)
modes = json.loads(rel.joinpath("port_modes_fem4.json").read_text())
_, eoe = build_edges(elements)
a = assemble_wave_matlab(nodes, elements, domains)
b = np.zeros(a.shape[0], dtype=complex)
n_edge = int(eoe.max()) + 1
for mode, is_in in [(modes["input"], True), (modes["output"], False)]:
st = np.zeros(a.shape[0], dtype=complex)
g = np.zeros(a.shape[0], dtype=complex)
bedge = np.zeros(n_edge, dtype=complex)
p = 0.0 + 0.0j
b1 = 0.0 + 0.0j
for fi in range(len(mode["facesConn"])):
s1, g1, be1, p1, b1e = assemble_port_face(nodes, elements, eoe, mode, fi, is_in)
st[:n_edge] += s1
g[:n_edge] += g1
if is_in:
bedge += be1
b1 += b1e
p += p1
if is_in:
b[:n_edge] += bedge
a, b = block_append_input(a, b, st[: a.shape[0]], g[: a.shape[0]], p, b1)
print(f"after input |b|={np.linalg.norm(b):.6g} b1={b1:.6g}")
else:
a, b = block_append_output(a, b, st[: a.shape[0]], g[: a.shape[0]], p)
print(f"after output |b|={np.linalg.norm(b):.6g}")
pec_faces = faces[np.isin(faces_index, [1, 2, 5, 24])]
pec_pairs = set()
for tri in pec_faces:
for a0, b0 in [(tri[0], tri[1]), (tri[1], tri[2]), (tri[2], tri[0])]:
pec_pairs.add(tuple(sorted((int(a0), int(b0)))))
edge_u, _ = build_edges(elements)
pec_idx = {i for i, e in enumerate(edge_u) if tuple(e) in pec_pairs}
free = [i for i in range(a.shape[0]) if i not in pec_idx]
a = a.tocsr()[free, :][:, free]
b = b[free]
export_dir = args.export
if export_dir is None:
export_dir = root / "三维matlab代码/2023-2-端口激励问题(四面体网格)/OutFile_fem4_ab"
export_ab_coo(a, b, export_dir)
x = spsolve(a, b)
pcoef = modes["input"]["powerCoef"]
e1, e2 = x[-2], x[-1]
print(f"Python S11={e1/pcoef} S21={e2/pcoef}")
ref = next(root.rglob("OutFile_fem4/S_params.txt"))
vals = ref.read_text().strip().splitlines()[1].split()
print(f"MATLAB S11={complex(float(vals[0]), float(vals[1]))} S21={complex(float(vals[2]), float(vals[3]))}")
cpp = rel / "OutFile_fem4" / "S_params.txt"
print("C++", cpp.read_text().strip())
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -0,0 +1,176 @@
#!/usr/bin/env python3
"""Compare C++ wave-only A with Python replication of MATLAB PhysicMatrixAssembly."""
from __future__ import annotations
import re
from pathlib import Path
import h5py
import numpy as np
from scipy import sparse
PI = np.pi
def bf_edge(num: int, u: float, v: float, w: float) -> np.ndarray:
out = np.zeros(3)
if num == 1:
out[:] = [-v, u, 0.0]
elif num == 2:
out[:] = [-w, 0.0, u]
elif num == 3:
out[:] = [-1 + v + w, -u, -u]
elif num == 4:
out[:] = [0.0, -w, v]
elif num == 5:
out[:] = [-v, -1 + u + w, -v]
elif num == 6:
out[:] = [-w, -w, -1 + u + v]
else:
raise ValueError(num)
return out
def bf_curl_edge(num: int, u: float, v: float, w: float) -> np.ndarray:
out = np.zeros(3)
if num == 1:
out[2] = 2.0
elif num == 2:
out[1] = -2.0
elif num == 3:
out[1] = 2.0
out[2] = -2.0
elif num == 4:
out[0] = 2.0
elif num == 5:
out[0] = -2.0
out[2] = 2.0
elif num == 6:
out[0] = 2.0
out[1] = -2.0
else:
raise ValueError(num)
return out
def gauss_tet_order2() -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
xt = np.array([0.25, 0.166666666667, 0.166666666667, 0.166666666667, 0.5])
yt = np.array([0.25, 0.166666666667, 0.166666666667, 0.5, 0.166666666667])
zt = np.array([0.25, 0.166666666667, 0.5, 0.166666666667, 0.166666666667])
pt = np.array([-0.133333333333, 0.075, 0.075, 0.075, 0.075])
return xt, yt, zt, pt
def build_edges(elements: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
el2no = elements.T
n1 = el2no[[0, 0, 0, 1, 1, 2], :]
n2 = el2no[[1, 2, 3, 2, 3, 3], :]
el_ed = np.column_stack([n1.reshape(-1, order="F"), n2.reshape(-1, order="F")])
edge, ic = np.unique(el_ed, axis=0, return_inverse=True)
eoe = ic.reshape(6, -1, order="F").T
return edge, eoe
def assemble_wave_matlab(
nodes: np.ndarray, elements: np.ndarray, domains: np.ndarray
) -> sparse.csr_matrix:
lam0 = 1.55e-6
k0 = 2 * PI / lam0
epsilonr = np.array([4.0, 11.9, 11.9, 11.9], dtype=complex)
sigma = np.array([0.0, 0.0, 5000.0, 0.0])
mur = np.ones(4)
temp = 1 / k0 * 120 * PI
epsilon = epsilonr - 1j * sigma * temp
_, eoe = build_edges(elements)
xt, yt, zt, pt = gauss_tet_order2()
trips: list[tuple[int, int, complex]] = []
for n in range(elements.shape[0]):
idx = elements[n] - 1
x = nodes[idx, 0]
y = nodes[idx, 1]
z = nodes[idx, 2]
l = np.ones(6)
pairs = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
for i, (a, b) in enumerate(pairs):
l[i] = np.linalg.norm(nodes[idx[a]] - nodes[idx[b]])
jac = np.column_stack([x[:3] - x[3], y[:3] - y[3], z[:3] - z[3]])
inv_jac = np.linalg.inv(jac)
t_jac = jac.T / np.linalg.det(jac)
det_jac = abs(np.linalg.det(jac))
domain = int(domains[n]) - 1
mu = 1.0 / mur[domain]
eps = epsilon[domain]
ne = np.zeros((3, 6, len(xt)), dtype=complex)
curl_ne = np.zeros((3, 6, len(xt)), dtype=complex)
for i in range(6):
for k in range(len(xt)):
temp_bf = bf_edge(i + 1, xt[k], yt[k], zt[k])
ne[:, i, k] = inv_jac @ temp_bf * l[i]
temp_c = bf_curl_edge(i + 1, xt[k], yt[k], zt[k])
curl_ne[:, i, k] = t_jac @ temp_c * l[i]
ae = np.zeros((6, 6), dtype=complex)
for i in range(6):
for j in range(6):
for k in range(len(pt)):
ae[i, j] += pt[k] * det_jac * (
mu * np.vdot(curl_ne[:, i, k], curl_ne[:, j, k])
- k0 * k0 * eps * np.vdot(ne[:, i, k], ne[:, j, k])
)
for i in range(6):
for j in range(6):
ii = int(eoe[n, i])
jj = int(eoe[n, j])
trips.append((ii, jj, ae[i, j]))
ai = np.array([t[0] for t in trips], dtype=np.int64)
aj = np.array([t[1] for t in trips], dtype=np.int64)
av = np.array([t[2] for t in trips])
n = int(max(ai.max(), aj.max()) + 1)
return sparse.coo_matrix((av, (ai, aj)), shape=(n, n)).tocsr()
def load_cpp_wave(cpp_asm: Path) -> sparse.csr_matrix:
ai = np.loadtxt(cpp_asm / "Ai.txt", dtype=np.int64)
aj = np.loadtxt(cpp_asm / "Aj.txt", dtype=np.int64)
av = []
for line in (cpp_asm / "Av.txt").read_text().splitlines():
line = line.strip()
if not line or line.startswith("//"):
continue
m = re.match(r"\(([^,]+),([^)]+)\)", line)
av.append(complex(float(m.group(1)), float(m.group(2))))
av = np.array(av)
n = int(max(ai.max(), aj.max()) + 1)
return sparse.coo_matrix((av, (ai, aj)), shape=(n, n)).tocsr()
def main() -> int:
root = Path(__file__).resolve().parents[2]
mat_path = next(root.rglob("MeshData2x.mat"))
cpp_asm = root / "3D opticsfem-master/port/Release/OutFile_asm"
with h5py.File(mat_path, "r") as f:
nodes = np.asarray(f["Mesh/Nodes"]).T
elements = np.asarray(f["Mesh/Elements"]).T.astype(int)
domains = np.asarray(f["Mesh/Domains"]).reshape(-1).astype(int)
print("Assembling MATLAB-style wave matrix ...")
a_py = assemble_wave_matlab(nodes, elements, domains)
a_cpp = load_cpp_wave(cpp_asm)
print(f"Python nnz={a_py.nnz} C++ nnz={a_cpp.nnz}")
diff = a_py - a_cpp
print(f"max |A_py - A_cpp| = {max(abs(diff.data).max() if diff.nnz else 0.0, 0.0):.6e}")
rel = diff.data / (a_cpp.data + 1e-30)
if diff.nnz:
print(f"median rel diff on overlapping nnz = {np.median(np.abs(rel)):.6e}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""Diagnose why ref A + cpp b works but cpp A + cpp b fails despite tiny A diff."""
from __future__ import annotations
from pathlib import Path
import numpy as np
from scipy import sparse
from scipy.sparse.linalg import spsolve
ROOT = Path(__file__).resolve().parents[2]
REF = ROOT / "三维matlab代码/2023-2-端口激励问题(四面体网格)/OutFile_fem4_ab"
CPP = ROOT / "3D opticsfem-master/port/Release/OutFile_fem4"
PCOEF = 2.69467866496258e15
def parse_av_line(s: str) -> complex:
s = s.strip()
if s.startswith("("):
body = s[1:-1]
a, b = body.split(",", 1)
return complex(float(a), float(b))
return complex(float(s))
def load_coo(prefix: Path) -> sparse.csr_matrix:
ai = np.loadtxt(prefix / "Ai.txt", dtype=np.int64)
aj = np.loadtxt(prefix / "Aj.txt", dtype=np.int64)
av = [parse_av_line(line) for line in (prefix / "Av.txt").read_text(encoding="utf-8", errors="ignore").splitlines() if line.strip()]
return sparse.coo_matrix((av, (ai, aj)), shape=(79616, 79616)).tocsr()
def load_b(prefix: Path) -> np.ndarray:
return np.loadtxt(prefix / "Bv_real.txt") + 1j * np.loadtxt(prefix / "Bv_imag.txt")
def main() -> int:
print("=== Load matrices ===")
A_ref = load_coo(REF)
A_cpp = load_coo(CPP)
b_ref = load_b(REF)
b_cpp = load_b(CPP)
print(f"A_ref nnz={A_ref.nnz}, A_cpp nnz={A_cpp.nnz}")
print(f"indices equal: {np.array_equal(A_ref.indices, A_cpp.indices) and np.array_equal(A_ref.indptr, A_cpp.indptr)}")
ddata = np.abs(A_ref.data - A_cpp.data)
print(f"A.data max|diff|={ddata.max():.6g}, mean|diff|={ddata.mean():.6g}")
print(f"A.data rel max|diff|={ddata.max()/max(np.abs(A_ref.data).max(),1e-30):.6g}")
print(f"b max|diff|={np.abs(b_ref-b_cpp).max():.6g}, count>1e-6={(np.abs(b_ref-b_cpp)>1e-6).sum()}")
# duplicate (i,j) in raw COO before CSR merge?
ai = np.loadtxt(REF / "Ai.txt", dtype=np.int64)
aj = np.loadtxt(REF / "Aj.txt", dtype=np.int64)
keys = np.column_stack([ai, aj])
uniq, counts = np.unique(keys, axis=0, return_counts=True)
ndup = int((counts > 1).sum())
print(f"raw COO duplicate (i,j) groups: {ndup}")
print("\n=== CRITICAL: same b_cpp, two A ===")
x_refA = spsolve(A_ref, b_cpp)
x_cppA = spsolve(A_cpp, b_cpp)
rel = np.linalg.norm(x_refA - x_cppA) / np.linalg.norm(x_refA)
print(f"||x_refA - x_cppA|| / ||x_refA|| = {rel:.6g}")
print(f"x_refA S11={x_refA[-2]/PCOEF} S21={x_refA[-1]/PCOEF}")
print(f"x_cppA S11={x_cppA[-2]/PCOEF} S21={x_cppA[-1]/PCOEF}")
print("\n=== Same A_ref, two b ===")
x1 = spsolve(A_ref, b_ref)
x2 = spsolve(A_ref, b_cpp)
print(f"||x(b_ref)-x(b_cpp)||/||x(b_ref)|| = {np.linalg.norm(x1-x2)/np.linalg.norm(x1):.6g}")
print(f"x(b_ref) S11={x1[-2]/PCOEF}")
print(f"x(b_cpp) S11={x2[-2]/PCOEF}")
print("\n=== C++ exported X ===")
x_exp = np.loadtxt(CPP / "X_real.txt") + 1j * np.loadtxt(CPP / "X_imag.txt")
free_len = 79616
x_red = x_exp # reduced? export is full 83350
# map: C++ exports full _mX; reduced solve is 79616
print(f"X size={x_exp.size}, e1={x_exp[83348]/PCOEF}, e2={x_exp[83349]/PCOEF}")
print(f"||x_cppA - x_exp[?]|| : reduced x vs full X needs free map")
# Where do A values differ most (relative)?
mask = ddata > 0
if mask.any():
rel_d = ddata[mask] / np.maximum(np.abs(A_ref.data[mask]), 1e-30)
top = np.argsort(-rel_d)[:10]
print("\nTop relative A diffs (CSR data index):")
for i in top:
print(f" idx={i} rel={rel_d[i]:.3g} abs={ddata[i]:.3g} ref={A_ref.data[i]:.6g} cpp={A_cpp.data[i]:.6g}")
# Port columns 79614,79615
for col in [79614, 79615]:
dcol = (A_ref[:, col] - A_cpp[:, col]).toarray().ravel()
nz = np.abs(dcol) > 0
print(f"\nPort col {col}: nnz diff={nz.sum()}, max|diff|={np.abs(dcol).max():.3g}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -0,0 +1,114 @@
"""Export SBCmesh.mat (MATLAB getMesh.m output) to OpticsFEM SBCmesh.dat format."""
from __future__ import annotations
import argparse
from pathlib import Path
import scipy.io as sio
def _as_rows(arr, ncol: int) -> list[list[int]]:
if arr.ndim == 1:
return [list(arr)]
if arr.shape[0] == ncol and arr.shape[1] != ncol:
arr = arr.T
return [list(row) for row in arr]
def export_sbcmesh_mat_to_dat(mat_path: Path, dat_path: Path) -> None:
data = sio.loadmat(str(mat_path), squeeze_me=True, struct_as_record=False)
mesh = data["mesh"]
lines: list[str] = []
lines.append("NbrVertex")
lines.append(str(int(mesh.NbrVertex)))
lines.append("Vertex")
for row in _as_rows(mesh.Vertex, 3):
lines.append(f"{row[0]:.16g} {row[1]:.16g} {row[2]:.16g}")
lines.append("NbrTet")
lines.append(str(int(mesh.NbrTet)))
lines.append("Tet")
for row in _as_rows(mesh.Tet, 4):
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])} {int(row[3])}")
lines.append("DomainOfTet")
dom_tet = mesh.DomainOfTet.flatten()
for v in dom_tet:
lines.append(str(int(v)))
lines.append("NbrEdge")
lines.append(str(int(mesh.NbrEdge)))
lines.append("Edge")
for row in _as_rows(mesh.Edge, 2):
lines.append(f"{int(row[0])} {int(row[1])}")
lines.append("EdgeOfTet")
eot_rows = _as_rows(mesh.EdgeOfTet, 6)
zero_based = any(int(v) == 0 for row in eot_rows for v in row)
for row in eot_rows:
vals = [int(x) + (1 if zero_based else 0) for x in row]
lines.append(" ".join(str(x) for x in vals))
lines.append("NbrTri")
lines.append(str(int(mesh.NbrTri)))
lines.append("Tri")
for row in _as_rows(mesh.Tri, 3):
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])}")
lines.append("DomainOfTri")
dom_tri = mesh.DomainOfTri.flatten()
for v in dom_tri:
lines.append(str(int(v)))
lines.append("ConnOfTri")
for row in _as_rows(mesh.ConnOfTri, 2):
lines.append(f"{int(row[0])} {int(row[1])}")
norm_rows = [1, 2, 3, 4, 5, 14]
norm_face = mesh.NormOfFace
lines.append("NormOfFace")
lines.append(str(len(norm_rows)))
for d in norm_rows:
n = norm_face[d - 1] if norm_face.shape[0] >= d else norm_face[d]
if hasattr(n, "__len__") and len(n) >= 3:
nx, ny, nz = float(n[0]), float(n[1]), float(n[2])
else:
defaults = {
1: (-1, 0, 0),
2: (0, -1, 0),
3: (0, 0, -1),
4: (0, 0, 1),
5: (0, 1, 0),
14: (1, 0, 0),
}
nx, ny, nz = defaults[d]
lines.append(f"{d} {nx:.16g} {ny:.16g} {nz:.16g}")
dat_path.parent.mkdir(parents=True, exist_ok=True)
dat_path.write_text("\n".join(lines) + "\n", encoding="ascii")
print(f"Wrote {dat_path} ({len(lines)} lines)")
print(
f" NbrVertex={mesh.NbrVertex}, NbrTet={mesh.NbrTet}, "
f"NbrEdge={mesh.NbrEdge}, NbrTri={mesh.NbrTri}"
)
def main() -> None:
parser = argparse.ArgumentParser(description="Export SBCmesh.mat to SBCmesh.dat")
parser.add_argument("mat_file", type=Path, help="Input SBCmesh.mat path")
parser.add_argument(
"-o",
"--output",
type=Path,
default=None,
help="Output SBCmesh.dat path (default: same dir as mat)",
)
args = parser.parse_args()
out = args.output or args.mat_file.with_name("SBCmesh.dat")
export_sbcmesh_mat_to_dat(args.mat_file, out)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""Export sparse A/b in OpticsFEM COO text format (matches C++ Test_OutputMatrix)."""
from __future__ import annotations
from pathlib import Path
import numpy as np
from scipy import sparse
def export_ab_coo(a: sparse.spmatrix, b: np.ndarray, out_dir: Path) -> None:
out_dir.mkdir(parents=True, exist_ok=True)
a = a.tocoo()
order = np.lexsort((a.col, a.row))
ai = a.row[order].astype(np.int64)
aj = a.col[order].astype(np.int64)
av = a.data[order]
np.savetxt(out_dir / "Ai.txt", ai, fmt="%d")
np.savetxt(out_dir / "Aj.txt", aj, fmt="%d")
with open(out_dir / "Av.txt", "w", encoding="utf-8") as f:
for v in av:
f.write(f"({v.real:.12g},{v.imag:.12g})\n")
b = np.asarray(b).reshape(-1)
np.savetxt(out_dir / "Bv_real.txt", b.real, fmt="%.12g")
np.savetxt(out_dir / "Bv_imag.txt", b.imag, fmt="%.12g")
meta = out_dir / "meta.txt"
meta.write_text(
"\n".join(
[
"source=export_ab_coo.py",
f"n={a.shape[0]}",
f"nnz={ai.size}",
f"|b|={np.linalg.norm(b):.15g}",
]
)
+ "\n",
encoding="utf-8",
)
print(f"Exported A/b to {out_dir}")
print(f" n={a.shape[0]}, nnz={ai.size}, |b|={np.linalg.norm(b):.6g}")

View File

@ -0,0 +1,63 @@
"""Generate sbc3d_order2.json from MATLAB case_config (2nd-order SBC scatter)."""
from __future__ import annotations
import json
from pathlib import Path
# Mirrors: 三维matlab代码/matlab 3D二阶基+散射边界条件/case_config.m
OUT_FACES = [1, 2, 3, 5, 14]
INC_FACES = [4]
SBC_INDEX = OUT_FACES + [f for f in INC_FACES if f not in OUT_FACES]
SBC_TYPE = [0 if f in OUT_FACES else 1 for f in SBC_INDEX]
DOC = {
"FemType": 4,
"EletricType": 2,
"ElementOrder": 2,
"lambda": 2.0,
"NbrBoundary": 14,
"BoundaryFlag": [0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2],
"sbc": {
"Index": SBC_INDEX,
"SBCType": SBC_TYPE,
"E0x": ["1"] * len(SBC_INDEX),
"E0y": ["0"] * len(SBC_INDEX),
"E0z": ["0"] * len(SBC_INDEX),
"kx": [0] * len(SBC_INDEX),
"ky": [0] * len(SBC_INDEX),
"kz": [0] * len(SBC_INDEX),
},
"NbrDomain": 2,
"domainType": [2, 2],
"domainIndex": [0, 1],
"matType": [0, 0],
"epsilonrR": [1.0, 1.5],
"epsilonrI": [0.0, 0.0],
"murR": [1.0, 1.0],
"murI": [0.0, 0.0],
"chiheR": [0.0, 0.0],
"chiehR": [0.0, 0.0],
"chiheI": [0.0, 0.0],
"chiehI": [0.0, 0.0],
"sigma": [0.0, 0.0],
"n": [1.0, 1.0],
"k": [0.0, 0.0],
"MeshFile": "SBCmesh.dat",
"OutFile": "./OutFile",
}
def main() -> None:
root = Path(__file__).resolve().parents[1]
release = root / "scat3D_order2" / "Release"
release.mkdir(parents=True, exist_ok=True)
out_path = release / "sbc3d_order2.json"
out_path.write_text(json.dumps(DOC, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
print(f"Wrote {out_path}")
print(f" sbc Index={SBC_INDEX}, SBCType={SBC_TYPE}")
print(f" lambda={DOC['lambda']}, ElementOrder={DOC['ElementOrder']}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Export singlePBC_mesh.mat / doublePBC_mesh.mat to OpticsFEM .dat (all NormOfFace rows)."""
from __future__ import annotations
import argparse
from pathlib import Path
import numpy as np
import scipy.io as sio
def _as_rows(arr, ncol: int) -> list[list[int]]:
if arr.ndim == 1:
return [list(arr)]
if arr.shape[0] == ncol and arr.shape[1] != ncol:
arr = arr.T
return [list(row) for row in arr]
def export_mat_to_dat(mat_path: Path, dat_path: Path) -> None:
data = sio.loadmat(str(mat_path), squeeze_me=True, struct_as_record=False)
mesh = data["mesh"]
lines: list[str] = []
lines.append("NbrVertex")
lines.append(str(int(mesh.NbrVertex)))
lines.append("Vertex")
for row in _as_rows(mesh.Vertex, 3):
lines.append(f"{row[0]:.16g} {row[1]:.16g} {row[2]:.16g}")
lines.append("NbrTet")
lines.append(str(int(mesh.NbrTet)))
lines.append("Tet")
for row in _as_rows(mesh.Tet, 4):
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])} {int(row[3])}")
lines.append("DomainOfTet")
for v in mesh.DomainOfTet.flatten():
lines.append(str(int(v)))
lines.append("NbrEdge")
lines.append(str(int(mesh.NbrEdge)))
lines.append("Edge")
for row in _as_rows(mesh.Edge, 2):
lines.append(f"{int(row[0])} {int(row[1])}")
lines.append("EdgeOfTet")
eot_rows = _as_rows(mesh.EdgeOfTet, 6)
zero_based = any(int(v) == 0 for row in eot_rows for v in row)
for row in eot_rows:
vals = [int(x) + (1 if zero_based else 0) for x in row]
lines.append(" ".join(str(x) for x in vals))
lines.append("NbrTri")
lines.append(str(int(mesh.NbrTri)))
lines.append("Tri")
for row in _as_rows(mesh.Tri, 3):
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])}")
lines.append("DomainOfTri")
for v in mesh.DomainOfTri.flatten():
lines.append(str(int(v)))
lines.append("ConnOfTri")
for row in _as_rows(mesh.ConnOfTri, 2):
lines.append(f"{int(row[0])} {int(row[1])}")
norm_face = np.asarray(mesh.NormOfFace, dtype=float)
n_norm = norm_face.shape[0]
lines.append("NormOfFace")
lines.append(str(n_norm))
for d in range(1, n_norm + 1):
n = norm_face[d - 1]
nx, ny, nz = float(n[0]), float(n[1]), float(n[2])
if abs(nx) + abs(ny) + abs(nz) < 1e-30:
nx, ny, nz = 0.0, 0.0, 1.0
lines.append(f"{d} {nx:.16g} {ny:.16g} {nz:.16g}")
dat_path.parent.mkdir(parents=True, exist_ok=True)
dat_path.write_text("\n".join(lines) + "\n", encoding="ascii")
print(f"Wrote {dat_path} ({len(lines)} lines)")
print(
f" NbrVertex={mesh.NbrVertex}, NbrEdge={mesh.NbrEdge}, "
f"NbrTri={mesh.NbrTri}, NormOfFace={n_norm}"
)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("mat_file", type=Path)
parser.add_argument("-o", "--output", type=Path, default=None)
args = parser.parse_args()
out = args.output or args.mat_file.with_suffix(".dat")
export_mat_to_dat(args.mat_file, out)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,436 @@
#!/usr/bin/env python3
"""Plot normE on boundary face domains (e.g. PBC dst faces 2 and 5)."""
from __future__ import annotations
import argparse
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import PolyCollection
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "Arial"]
plt.rcParams["axes.unicode_minus"] = False
# Reuse mesh loader
import importlib.util
_ROOT = Path(__file__).resolve().parents[1]
_spec = importlib.util.spec_from_file_location(
"validate_pbcmesh", _ROOT / "tools" / "validate_and_fix_pbcmesh.py"
)
_v = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_v)
load_mesh_dat = _v.load_mesh_dat
face_vertices = _v.face_vertices
def face_vertex_ids(tet: np.ndarray, num_tet: int, num_face: int) -> np.ndarray:
"""Return 3 mesh vertex ids (0-based) for a boundary face."""
tv = tet[num_tet - 1] - 1
if num_face == 1:
local = [0, 1, 2]
elif num_face == 2:
local = [0, 1, 3]
elif num_face == 3:
local = [0, 2, 3]
elif num_face == 4:
local = [1, 2, 3]
else:
raise ValueError(f"bad face id {num_face}")
return tv[np.array(local)]
def load_normE(path: Path) -> np.ndarray:
vals: list[float] = []
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
line = line.strip()
if not line:
continue
if line.startswith("//"):
line = line[2:].strip()
if not line:
continue
vals.append(float(line.split()[0]))
return np.array(vals)
def collect_face_tris(
mesh: dict, domain: int, normE: np.ndarray
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Return (points Nx2 in xz, triangles Mx3, face_values M)."""
V = mesh["vertex"]
T = mesh["tet"]
dom = mesh["domain_of_tri"]
conn = mesh["conn_of_tri"]
pts_list: list[np.ndarray] = []
tri_list: list[tuple[int, int, int]] = []
vals_list: list[float] = []
pt_index: dict[tuple[float, float], int] = {}
def get_pt(x: float, z: float) -> int:
key = (round(x, 10), round(z, 10))
if key not in pt_index:
pt_index[key] = len(pts_list)
pts_list.append([x, z])
return pt_index[key]
for tri_idx in np.where(dom == domain)[0]:
num_tet, num_face = int(conn[tri_idx, 0]), int(conn[tri_idx, 1])
vids = face_vertex_ids(T, num_tet, num_face)
xyz = V[vids]
i0 = get_pt(xyz[0, 0], xyz[0, 2])
i1 = get_pt(xyz[1, 0], xyz[1, 2])
i2 = get_pt(xyz[2, 0], xyz[2, 2])
tri_list.append((i0, i1, i2))
vals_list.append(float(np.mean(normE[vids])))
if not pts_list:
return np.zeros((0, 2)), np.zeros((0, 3), dtype=int), np.zeros(0)
return np.array(pts_list), np.array(tri_list, dtype=int), np.array(vals_list)
def plot_domain_ax(ax, pts: np.ndarray, tris: np.ndarray, fvals: np.ndarray, title: str):
if len(pts) == 0:
ax.set_title(title + " (empty)")
return
# Per-triangle constant color
polys = pts[tris]
coll = PolyCollection(
polys,
array=fvals,
cmap="jet",
edgecolors="k",
linewidths=0.15,
alpha=0.95,
)
ax.add_collection(coll)
ax.set_aspect("equal")
ax.set_xlabel("x (m)")
ax.set_ylabel("z (m)")
ax.set_title(title)
ax.autoscale()
mappable = coll
return mappable
def _tri_centroids(pts: np.ndarray, tris: np.ndarray) -> np.ndarray:
return np.array([pts[t].mean(axis=0) for t in tris])
def _centroids_inside_patch(
centroids: np.ndarray, patch_pts: np.ndarray, patch_tris: np.ndarray
) -> np.ndarray:
"""True where centroid lies inside the xz footprint of a patch (bbox test)."""
xmin, xmax = patch_pts[:, 0].min(), patch_pts[:, 0].max()
zmin, zmax = patch_pts[:, 1].min(), patch_pts[:, 1].max()
return (
(centroids[:, 0] >= xmin)
& (centroids[:, 0] <= xmax)
& (centroids[:, 1] >= zmin)
& (centroids[:, 1] <= zmax)
)
def _append_tris_unified(
pts_acc: list[np.ndarray],
tris_acc: list[tuple[int, int, int]],
vals_acc: list[float],
pt_index: dict[tuple[float, float], int],
patch_pts: np.ndarray,
patch_tris: np.ndarray,
patch_vals: np.ndarray,
) -> None:
def get_pt(x: float, z: float) -> int:
key = (round(x, 10), round(z, 10))
if key not in pt_index:
pt_index[key] = len(pts_acc)
pts_acc.append([x, z])
return pt_index[key]
for tri, val in zip(patch_tris, patch_vals):
p0, p1, p2 = patch_pts[tri]
i0 = get_pt(p0[0], p0[1])
i1 = get_pt(p1[0], p1[1])
i2 = get_pt(p2[0], p2[1])
tris_acc.append((i0, i1, i2))
vals_acc.append(float(val))
def collect_domains_combined(
mesh: dict,
domains: list[int],
normE: np.ndarray,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Merge face domains on one x-z plane with shared vertex indexing.
Adjacent patches (e.g. domain 2 + 5) share boundary vertices; duplicate
points at the same (x, z) are welded so triangles tile without z-fighting.
"""
if not domains:
return np.zeros((0, 2)), np.zeros((0, 3), dtype=int), np.zeros(0)
raw = [(d, *collect_face_tris(mesh, d, normE)) for d in domains]
raw = [(d, p, t, f) for d, p, t, f in raw if len(t) > 0]
if not raw:
return np.zeros((0, 2)), np.zeros((0, 3), dtype=int), np.zeros(0)
pts_acc: list[list[float]] = []
tris_acc: list[tuple[int, int, int]] = []
vals_acc: list[float] = []
pt_index: dict[tuple[float, float], int] = {}
for _, pts, tris, fvals in raw:
_append_tris_unified(
pts_acc, tris_acc, vals_acc, pt_index, pts, tris, fvals,
)
if not pts_acc:
return np.zeros((0, 2)), np.zeros((0, 3), dtype=int), np.zeros(0)
return (
np.array(pts_acc),
np.array(tris_acc, dtype=int),
np.array(vals_acc),
)
def plot_combined_faces(
mesh: dict,
domains: list[int],
normE_cpp: np.ndarray,
normE_mat: np.ndarray | None,
output: Path,
title: str | None = None,
cpp_outdir: Path | None = None,
mat_outdir: Path | None = None,
layout: str = "merge",
) -> None:
"""Plot PBC face domains on one axes.
layout:
merge weld shared vertices and tile all domains (default)
side one panel per domain, horizontal row
"""
has_mat = normE_mat is not None
plot_domains = list(domains)
use_side = layout == "side" and len(plot_domains) > 1
ncols = (len(plot_domains) if use_side else 1) * (2 if has_mat else 1)
fig_w = 5.5 * ncols if use_side else (6.8 * ncols if has_mat else 6.0)
fig, axes = plt.subplots(
1, ncols if ncols else 1,
figsize=(fig_w, 5.5),
squeeze=False,
facecolor="white",
)
cpp_is_mat = (
has_mat
and cpp_outdir is not None
and mat_outdir is not None
and cpp_outdir.resolve() == mat_outdir.resolve()
)
mappables = []
labels = ["C++ OpticsFEM", "MATLAB 参考"] if has_mat else ["OpticsFEM |E|"]
if cpp_is_mat:
labels[0] = "MATLAB 参考 (C++ OutFile 待 pbc3d_sbc.json 重跑)"
datasets = [normE_cpp]
if has_mat:
datasets.append(normE_mat)
domain_label = " + ".join(f"domain {d}" for d in plot_domains)
if use_side:
mappables = []
for i, dom in enumerate(plot_domains):
col = i * (2 if has_mat else 1)
for j, (normE, suffix) in enumerate(
[(normE_cpp, "C++"), (normE_mat, "MATLAB")] if has_mat else [(normE_cpp, "")]
):
if normE is None:
continue
pts, tris, fvals = collect_face_tris(mesh, dom, normE)
ax = axes[0, col + j]
label = f"{suffix} domain {dom}" if suffix else f"domain {dom}"
m = plot_domain_ax(ax, pts, tris, fvals, f"{label}\n(n_tri={len(fvals)})")
if m is not None:
mappables.append(m)
else:
for col, (normE, label) in enumerate(zip(datasets, labels)):
pts, tris, fvals = collect_domains_combined(mesh, plot_domains, normE)
n_tri = len(fvals)
m = plot_domain_ax(
axes[0, col],
pts,
tris,
fvals,
f"{label}\n(domain {' + '.join(map(str, plot_domains))}, n_tri={n_tri})",
)
if m is not None:
mappables.append(m)
if mappables:
vmin = min(m.get_array().min() for m in mappables if m.get_array() is not None)
vmax = max(m.get_array().max() for m in mappables if m.get_array() is not None)
for m in mappables:
m.set_clim(vmin, vmax)
if has_mat:
fig.colorbar(
mappables[0],
ax=axes.ravel().tolist(),
shrink=0.82,
pad=0.02,
label="|E| (V/m)",
)
else:
fig.subplots_adjust(right=0.88)
cbar = fig.colorbar(
mappables[0],
ax=axes[0, 0],
fraction=0.046,
pad=0.04,
label="|E| (V/m)",
)
cbar.ax.tick_params(labelsize=9)
fig.subplots_adjust(top=0.92, bottom=0.12, left=0.10, right=0.88 if not has_mat else 0.95)
output.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(output, dpi=160, bbox_inches="tight", facecolor="white")
plt.close(fig)
def main() -> None:
parser = argparse.ArgumentParser(description="Plot normE on mesh face domains")
parser.add_argument(
"--mesh",
type=Path,
default=_ROOT / "build" / "Release" / "PBCmesh.dat",
)
parser.add_argument(
"--outdir",
type=Path,
default=_ROOT / "build" / "Release" / "OutFile",
help="OpticsFEM OutFile with normE",
)
parser.add_argument(
"--mat-outdir",
type=Path,
default=_ROOT.parent
/ "三维matlab代码"
/ "matlab 3D一阶基+散射边界条件+单周期边界"
/ "OutFile",
help="MATLAB OutFile for comparison (set empty to skip)",
)
parser.add_argument(
"--domains",
type=int,
nargs="+",
default=[2, 5],
help="DomainOfTri ids to plot",
)
parser.add_argument(
"--output",
type=Path,
default=_ROOT / "build" / "Release" / "OutFile" / "faces_2_5.png",
)
parser.add_argument(
"--combined",
action="store_true",
help="Plot all --domains on the same axes (connected geometry)",
)
parser.add_argument(
"--no-compare",
action="store_true",
help="Skip MATLAB comparison panel (simulation result only)",
)
parser.add_argument(
"--layout",
choices=["merge", "side"],
default="merge",
help="merge: weld vertices and tile domains; side: horizontal panels",
)
args = parser.parse_args()
mesh = load_mesh_dat(args.mesh)
normE_cpp = load_normE(args.outdir / "normE")
if normE_cpp.size != mesh["n_vertex"]:
raise SystemExit(
f"normE size {normE_cpp.size} != NbrVertex {mesh['n_vertex']}"
)
has_mat = (
not args.no_compare
and args.mat_outdir is not None
and (args.mat_outdir / "normE").exists()
)
normE_mat = load_normE(args.mat_outdir / "normE") if has_mat else None
if args.combined:
plot_combined_faces(
mesh,
args.domains,
normE_cpp,
normE_mat,
args.output,
cpp_outdir=args.outdir,
mat_outdir=args.mat_outdir if has_mat else None,
layout=args.layout,
)
print(f"Saved combined {args.output}")
return
ncols = 2 if has_mat else 1
nrows = len(args.domains)
fig, axes = plt.subplots(
nrows, ncols, figsize=(6.5 * ncols, 5 * nrows), squeeze=False, facecolor="white"
)
mappables = []
for row, dom in enumerate(args.domains):
pts, tris, fvals = collect_face_tris(mesh, dom, normE_cpp)
m = plot_domain_ax(
axes[row, 0],
pts,
tris,
fvals,
f"C++ domain {dom} (n_tri={len(fvals)})",
)
if m is not None:
mappables.append(m)
if has_mat:
pts_m, tris_m, fvals_m = collect_face_tris(mesh, dom, normE_mat)
m2 = plot_domain_ax(
axes[row, 1],
pts_m,
tris_m,
fvals_m,
f"MATLAB domain {dom}",
)
if m2 is not None:
mappables.append(m2)
if mappables:
vmin = min(m.get_array().min() for m in mappables if m.get_array() is not None)
vmax = max(m.get_array().max() for m in mappables if m.get_array() is not None)
for m in mappables:
m.set_clim(vmin, vmax)
fig.colorbar(mappables[0], ax=axes.ravel().tolist(), shrink=0.6, label="|E| (V/m)")
fig.suptitle(
"normE on PBC faces (xz projection, y≈0)\n"
+ ", ".join(f"domain {d}" for d in args.domains),
fontsize=13,
fontweight="bold",
)
fig.tight_layout()
args.output.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(args.output, dpi=160, bbox_inches="tight")
print(f"Saved {args.output}")
plt.close(fig)
if __name__ == "__main__":
main()

View File

@ -61,11 +61,27 @@ def load_scalar_field(path: Path) -> np.ndarray:
if not line:
continue
if line.startswith("//"):
line = line[2:].strip()
continue
vals.append(float(line.split()[0]))
return np.array(vals)
def load_complex_field(path: Path) -> np.ndarray:
"""Load vertex complex field (Ex/Ey/Ez). Accepts 'a b' or '(a,b)'."""
vals = []
for line in path.read_text().splitlines():
line = line.strip()
if not line or line.startswith("//"):
continue
s = line.replace("(", "").replace(")", "").replace(",", " ")
parts = s.split()
if len(parts) >= 2:
vals.append(complex(float(parts[0]), float(parts[1])))
else:
vals.append(complex(float(parts[0]), 0.0))
return np.array(vals, dtype=np.complex128)
def axis_index(axis: str) -> int:
return {"x": 0, "y": 1, "z": 2}[axis.lower()]
@ -128,17 +144,26 @@ def tet_plane_poly(verts: np.ndarray, axis: int, coord: float, tol: float = 1e-9
return uniq
def interp_scalar(p: np.ndarray, tet_idx: np.ndarray, V: np.ndarray, field: np.ndarray) -> float:
def _barycentric_weights(p: np.ndarray, tet_idx: np.ndarray, V: np.ndarray) -> np.ndarray:
v = V[tet_idx]
f = field[tet_idx]
A = np.column_stack([v[1] - v[0], v[2] - v[0], v[3] - v[0]])
w123 = np.linalg.solve(A, p - v[0])
w0 = 1.0 - np.sum(w123)
w = np.array([w0, w123[0], w123[1], w123[2]])
return float(np.dot(w, f))
return np.array([w0, w123[0], w123[1], w123[2]], dtype=float)
def interp_scalar(p: np.ndarray, tet_idx: np.ndarray, V: np.ndarray, field: np.ndarray) -> float:
w = _barycentric_weights(p, tet_idx, V)
return float(np.dot(w, field[tet_idx]))
def interp_complex(p: np.ndarray, tet_idx: np.ndarray, V: np.ndarray, field: np.ndarray) -> complex:
w = _barycentric_weights(p, tet_idx, V)
return complex(np.dot(w, field[tet_idx]))
def build_slice_tris(V: np.ndarray, T: np.ndarray, field: np.ndarray, axis: str, coord: float):
"""Slice by interpolating a nodal scalar (e.g. normE). Prefer build_slice_tris_from_E."""
ai, bi = other_axes(axis)
ax = axis_index(axis)
@ -167,6 +192,58 @@ def build_slice_tris(V: np.ndarray, T: np.ndarray, field: np.ndarray, axis: str,
return tri, zval, (ai, bi)
def build_slice_tris_from_E(
V: np.ndarray,
T: np.ndarray,
Ex: np.ndarray,
Ey: np.ndarray,
Ez: np.ndarray,
axis: str,
coord: float,
):
"""Slice |E| by interpolating Ex,Ey,Ez then taking modulus.
Interpolating nodal |E| through long tets is wrong (|E| is not linear) and
invents fake midplane peaks (seen on BELE z=0). Always prefer this path.
"""
ai, bi = other_axes(axis)
ax = axis_index(axis)
polys = []
values = []
for tet in T:
verts = V[tet]
poly = tet_plane_poly(verts, ax, coord)
if poly is None:
continue
poly_arr = np.array(poly)
local_vals = []
for p in poly_arr:
e = np.array(
[
interp_complex(p, tet, V, Ex),
interp_complex(p, tet, V, Ey),
interp_complex(p, tet, V, Ez),
],
dtype=np.complex128,
)
local_vals.append(float(np.sqrt(np.sum(np.abs(e) ** 2))))
tris = triangulate_polygon(poly)
for t0, t1, t2 in tris:
polys.append(poly_arr[[t0, t1, t2]])
values.append([local_vals[t0], local_vals[t1], local_vals[t2]])
if not polys:
return None, None, None
polys = np.array(polys)
values = np.array(values)
xy = polys.reshape(-1, 3)[:, [ai, bi]]
zval = values.reshape(-1)
tri = Triangulation(xy[:, 0], xy[:, 1], np.arange(len(zval)).reshape(-1, 3))
return tri, zval, (ai, bi)
def build_surface_tris(V: np.ndarray, Tri: np.ndarray, field: np.ndarray, axis: str, coord: float, tol: float):
ai, bi = other_axes(axis)
ax = axis_index(axis)
@ -191,18 +268,20 @@ def build_surface_tris(V: np.ndarray, Tri: np.ndarray, field: np.ndarray, axis:
return tri, np.array(zs), (ai, bi)
def plot_slice(tri, zval, plane_axes, out_path: Path, coord: float, axis: str, title: str):
def plot_slice(tri, zval, plane_axes, out_path: Path, coord: float, axis: str, title: str, flat: bool = False):
ai, bi = plane_axes
names = ["x", "y", "z"]
fig, ax = plt.subplots(figsize=(7.5, 6.5), dpi=150)
tpc = ax.tripcolor(tri, zval, shading="gouraud", cmap="jet", edgecolors="k", linewidth=0.15)
shading = "flat" if flat else "gouraud"
lw = 0.35 if flat else 0.15
tpc = ax.tripcolor(tri, zval, shading=shading, cmap="jet", edgecolors="k", linewidth=lw)
ax.set_aspect("equal")
ax.set_xlabel(f"{names[ai]} (m)")
ax.set_ylabel(f"{names[bi]} (m)")
ax.set_title(title)
cbar = fig.colorbar(tpc, ax=ax, fraction=0.046, pad=0.04)
cbar.set_label("|E| (V/m)")
fig.text(0.02, 0.98, f"lambda0 = 0.8 m\n{axis} = {coord} m", va="top", fontsize=9)
fig.text(0.02, 0.98, f"{axis} = {coord:g} m", va="top", fontsize=9)
fig.tight_layout()
out_path.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(out_path, bbox_inches="tight")
@ -216,33 +295,50 @@ def main():
parser = argparse.ArgumentParser(description="Plot normE on a plane slice")
parser.add_argument("--mesh", type=Path, default=root / "build" / "Release" / "SBCmesh.dat")
parser.add_argument("--outdir", type=Path, default=root / "build" / "Release" / "OutFile")
parser.add_argument("--norme", type=Path, default=None, help="normE file (default: outdir/normE)")
parser.add_argument("--plane", choices=["x", "y", "z"], default="z")
parser.add_argument("--coord", type=float, default=0.5, help="plane coordinate (m)")
parser.add_argument("--mode", choices=["slice", "surface"], default="slice",
help="slice=cut volume; surface=boundary tris on plane")
parser.add_argument("--flat", action="store_true", help="flat shading (show mesh patches)")
parser.add_argument("--tol", type=float, default=1e-3, help="tolerance for surface mode")
parser.add_argument("--output", type=Path, default=None)
args = parser.parse_args()
V, T, Tri, _ = load_mesh(args.mesh)
normE = load_scalar_field(args.outdir / "normE")
if normE.size != V.shape[0]:
raise SystemExit(f"normE size {normE.size} != vertex count {V.shape[0]}")
norme_path = args.norme if args.norme is not None else args.outdir / "normE"
if args.output is None:
args.output = args.outdir / f"normE_{args.plane}{args.coord:.3f}.png"
if args.mode == "surface":
normE = load_scalar_field(norme_path)
if normE.size != V.shape[0]:
raise SystemExit(f"normE size {normE.size} != vertex count {V.shape[0]}")
tri, zval, plane_axes = build_surface_tris(V, Tri, normE, args.plane, args.coord, args.tol)
title = f"normE on {args.plane}={args.coord} m (surface)"
else:
# Prefer Ex/Ey/Ez → |E| (correct). Fallback to nodal normE.
ex_p, ey_p, ez_p = args.outdir / "Ex", args.outdir / "Ey", args.outdir / "Ez"
if ex_p.is_file() and ey_p.is_file() and ez_p.is_file():
Ex = load_complex_field(ex_p)
Ey = load_complex_field(ey_p)
Ez = load_complex_field(ez_p)
if Ex.size != V.shape[0]:
raise SystemExit(f"Ex size {Ex.size} != vertex count {V.shape[0]}")
tri, zval, plane_axes = build_slice_tris_from_E(V, T, Ex, Ey, Ez, args.plane, args.coord)
title = f"|E| on {args.plane}={args.coord} m (from ExEyEz)"
else:
normE = load_scalar_field(norme_path)
if normE.size != V.shape[0]:
raise SystemExit(f"normE size {normE.size} != vertex count {V.shape[0]}")
tri, zval, plane_axes = build_slice_tris(V, T, normE, args.plane, args.coord)
title = f"normE on {args.plane}={args.coord} m (slice)"
if tri is None:
raise SystemExit(f"no data on plane {args.plane}={args.coord}")
plot_slice(tri, zval, plane_axes, args.output, args.coord, args.plane, title)
plot_slice(tri, zval, plane_axes, args.output, args.coord, args.plane, title, flat=args.flat)
if __name__ == "__main__":

View File

@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Project MATLAB OutFile_asm with PBC and compare to OutFile / C++."""
from __future__ import annotations
import math
import re
from pathlib import Path
import numpy as np
from scipy import sparse
from compare_pbc_pairs import collect_edges, load_mesh, pair_matlab, remove_self
def load_coo(prefix: Path) -> sparse.csr_matrix:
def read_col(p: Path) -> np.ndarray:
with open(p, encoding="utf-8", errors="ignore") as f:
return np.array([int(x.strip()) for x in f if x.strip()])
def read_val(p: Path) -> np.ndarray:
vals: list[complex] = []
with open(p, encoding="utf-8", errors="ignore") as f:
for line in f:
line = line.strip()
if not line:
continue
m = re.match(r"\(([-+0-9.eE]+),([-+0-9.eE]+)\)", line)
if m:
vals.append(complex(float(m.group(1)), float(m.group(2))))
else:
vals.append(complex(float(line), 0.0))
return np.array(vals)
ai = read_col(prefix / "Ai.txt")
aj = read_col(prefix / "Aj.txt")
av = read_val(prefix / "Av.txt")
n = int(max(ai.max(), aj.max()) + 1)
return sparse.csr_matrix((av, (ai, aj)), shape=(n, n))
def build_p_matrix(dof: int, pairs: list[tuple[int, int, int]], phi: complex) -> sparse.csr_matrix:
# 1-based edge indices in mesh -> use as matrix indices directly (MATLAB convention)
rows: list[int] = []
cols: list[int] = []
data: list[complex] = []
for i in range(1, dof + 1):
rows.append(i)
cols.append(i)
data.append(1.0)
for src, dst, sign in pairs:
rows.append(dst)
cols.append(src)
data.append(sign * phi)
p_full = sparse.csr_matrix((data, (np.array(rows) - 1, np.array(cols) - 1)), shape=(dof, dof))
dst_cols = sorted({dst - 1 for _, dst, _ in pairs})
keep = np.ones(dof, dtype=bool)
keep[dst_cols] = False
return p_full[:, keep]
def main() -> None:
root = Path(__file__).resolve().parents[2]
mat_dir = root / "三维matlab代码" / "matlab 3D一阶基+散射边界条件+单周期边界"
cpp_dir = root / "3D opticsfem-master" / "build" / "Release" / "OutFile"
mesh = load_mesh(root / "3D opticsfem-master" / "build" / "Release" / "PBCmesh.dat")
phi = complex(0.5, math.sqrt(3) / 2)
theta = math.pi / 3
src_edges = collect_edges(mesh, [1, 4])
dst_edges = collect_edges(mesh, [2, 5])
pairs = remove_self(pair_matlab(mesh, src_edges, dst_edges, theta))
a_asm = load_coo(mat_dir / "OutFile_asm")
a_out = load_coo(mat_dir / "OutFile")
a_cpp = load_coo(cpp_dir)
dof = a_asm.shape[0]
p = build_p_matrix(dof, pairs, phi)
a_proj = p.conj().T @ a_asm @ p
d_self = a_proj - a_out
d_cpp = a_cpp - a_out
print("Projected asm vs mat OutFile:")
print(" max |diff|", np.max(np.abs(d_self.data)))
print(" max |real diff|", np.max(np.abs(d_self.real.data)))
print(" max |imag diff|", np.max(np.abs(d_self.imag.data)))
print("\nC++ vs mat OutFile:")
print(" max |imag diff|", np.max(np.abs(d_cpp.imag.data)))
i = 20552 - 1 # 0-based for scipy
print(f"\nDOF 20552:")
print(" mat out ", a_out[i, i])
print(" proj asm", a_proj[i, i])
print(" cpp out ", a_cpp[i, i])
print(" asm pre ", a_asm[i, i])
# If projected asm matches mat out, pre-PBC asm from C++ is wrong
# Compare asm sizes / check if cpp pre-projection differs
r, c, v = sparse.find(d_self)
order = np.argsort(-np.abs(v.imag))
print("\nTop projected-asm vs mat-out imag diffs:")
for k in order[:5]:
ri, ci = int(r[k]), int(c[k])
print(f" ({ri+1},{ci+1}) proj={a_proj[ri,ci]:.6g} mat={a_out[ri,ci]:.6g}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""Align BoundaryFlag with sbc/ef/mag/scd/pbc index blocks in OpticsFEM JSON files."""
from __future__ import annotations
import json
import sys
from pathlib import Path
PMC, PEC, SBC, ELE, PBC, MAG, SCD = 0, 1, 2, 3, 4, 8, 9
ROOT = Path(__file__).resolve().parents[2]
OPTICS_JSON_GLOBS = [
ROOT / "3D opticsfem-master" / "*.json",
ROOT / "3D opticsfem-master" / "scat3D" / "Release" / "*.json",
ROOT / "3D opticsfem-master" / "scat3D_SBC" / "Release" / "*.json",
ROOT / "三维matlab代码" / "matlab 3D一阶散射问题" / "*.json",
ROOT / "三维matlab代码" / "matlab 3D一阶基+散射边界条件" / "*.json",
ROOT / "三维matlab代码" / "matlab 3D一阶基 + bele" / "*.json",
ROOT / "三维matlab代码" / "matlab 3D一阶基+散射边界条件+单周期边界" / "PBCmesh.json",
]
def collect_pbc_domains(doc: dict) -> set[int]:
domains: set[int] = set()
for key in ("pbc", "pbc2"):
if key not in doc:
continue
block = doc[key]
for field in ("srcIndex", "dstIndex"):
if field in block:
domains.update(int(x) for x in block[field])
return domains
def build_boundary_flag(doc: dict) -> list[int] | None:
if "NbrBoundary" not in doc or "BoundaryFlag" not in doc:
return None
n = int(doc["NbrBoundary"])
flags = [PMC] * n
for d in collect_pbc_domains(doc):
if 1 <= d <= n:
flags[d - 1] = PBC
if "sbc" in doc:
for d in doc["sbc"]["Index"]:
d = int(d)
if 1 <= d <= n:
flags[d - 1] = SBC
if "ef" in doc:
for d in doc["ef"]["index"]:
d = int(d)
if 1 <= d <= n:
flags[d - 1] = ELE
if "mag" in doc:
for d in doc["mag"]["index"]:
d = int(d)
if 1 <= d <= n:
flags[d - 1] = MAG
if "scd" in doc:
for d in doc["scd"]["index"]:
d = int(d)
if 1 <= d <= n:
flags[d - 1] = SCD
# Legacy 14-face cube: domain 6 is material interface, not an outer SBC face.
if n == 14 and "sbc" in doc:
sbc_set = {int(x) for x in doc["sbc"]["Index"]}
if 6 not in sbc_set and flags[5] == SBC:
flags[5] = PMC
return flags
def validate(doc: dict, path: Path) -> list[str]:
issues: list[str] = []
if "BoundaryFlag" not in doc:
return issues
flags = doc["BoundaryFlag"]
n = len(flags)
if "sbc" in doc:
for d in doc["sbc"]["Index"]:
d = int(d)
if not (1 <= d <= n):
issues.append(f"sbc.Index {d} out of range")
elif flags[d - 1] != SBC:
issues.append(f"sbc.Index {d} but BoundaryFlag[{d}]={flags[d - 1]} (expected {SBC})")
if "ef" in doc:
for d in doc["ef"]["index"]:
d = int(d)
if flags[d - 1] != ELE:
issues.append(f"ef.index {d} but BoundaryFlag[{d}]={flags[d - 1]} (expected {ELE})")
if "mag" in doc:
for d in doc["mag"]["index"]:
d = int(d)
if flags[d - 1] != MAG:
issues.append(f"mag.index {d} flag={flags[d - 1]} (expected {MAG})")
for d in collect_pbc_domains(doc):
if 1 <= d <= n and flags[d - 1] != PBC:
issues.append(f"pbc domain {d} but BoundaryFlag[{d}]={flags[d - 1]} (expected {PBC})")
return issues
def main() -> int:
write = "--write" in sys.argv
paths: list[Path] = []
for pattern in OPTICS_JSON_GLOBS:
if pattern.parent.exists():
paths.extend(sorted(pattern.parent.glob(pattern.name)))
changed = 0
for path in paths:
if path.name in ("CMakeSettings.json",):
continue
try:
doc = json.loads(path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
continue
if "FemType" not in doc:
continue
new_flags = build_boundary_flag(doc)
if new_flags is None:
continue
old_flags = doc.get("BoundaryFlag")
issues_before = validate(doc, path) if old_flags else ["missing BoundaryFlag"]
if old_flags == new_flags and not issues_before:
print(f"OK {path.relative_to(ROOT)}")
continue
print(f"FIX {path.relative_to(ROOT)}")
if old_flags != new_flags:
print(f" old: {old_flags}")
print(f" new: {new_flags}")
for msg in issues_before:
print(f" was: {msg}")
if write:
doc["BoundaryFlag"] = new_flags
path.write_text(json.dumps(doc, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
changed += 1
if write:
print(f"\nUpdated {changed} file(s).")
else:
print("\nDry run. Re-run with --write to apply.")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -0,0 +1,304 @@
#!/usr/bin/env python3
"""Validate PBCmesh.dat and repair NormOfFace for domains 1-9 (PBC model)."""
from __future__ import annotations
import argparse
import math
import sys
from pathlib import Path
import numpy as np
# PBC single-period case: DomainOfTri 1..9
ALL_FACE_DOMAINS = list(range(1, 10))
SBC_OUT = {3, 9}
SBC_INC = {8}
def read_tag(lines: list[str], i: int, expected: str) -> int:
while i < len(lines) and not lines[i].strip():
i += 1
if i >= len(lines) or lines[i].strip() != expected:
got = lines[i].strip() if i < len(lines) else "EOF"
raise ValueError(f"Expected tag '{expected}' at line {i + 1}, got '{got}'")
return i + 1
def face_vertices(V: np.ndarray, T: np.ndarray, num_tet: int, num_face: int) -> np.ndarray:
"""Return 3x3 array of face vertex coordinates (reference tet local face)."""
x = V[T[num_tet] - 1, 0]
y = V[T[num_tet] - 1, 1]
z = V[T[num_tet] - 1, 2]
if num_face == 1:
idx = [0, 1, 2]
elif num_face == 2:
idx = [0, 1, 3]
elif num_face == 3:
idx = [0, 2, 3]
elif num_face == 4:
idx = [1, 2, 3]
else:
raise ValueError(f"bad face {num_face}")
pts = np.column_stack([x[idx], y[idx], z[idx]])
return pts
def geometric_face_normal(pts: np.ndarray) -> np.ndarray:
n = np.cross(pts[1] - pts[0], pts[2] - pts[0])
norm = np.linalg.norm(n)
if norm < 1e-30:
raise ValueError("degenerate face normal")
return n / norm
def matlab_out_normal(domain: int, tet_vertex_xyz: np.ndarray) -> np.ndarray:
"""assembly_out.m normal for one SBC triangle."""
if domain == 3:
return np.array([0.0, 0.0, -1.0])
x = tet_vertex_xyz[:, 0]
y = tet_vertex_xyz[:, 1]
n = np.array([x.sum() / 3.0, y.sum() / 3.0, 0.0])
norm = np.linalg.norm(n)
if norm < 1e-30:
raise ValueError(f"Degenerate assembly_out normal domain {domain}")
return n / norm
def matlab_inc_normal() -> np.ndarray:
return np.array([0.0, 0.0, 1.0])
def load_mesh_dat(path: Path) -> dict:
lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
i = 0
i = read_tag(lines, i, "NbrVertex")
n_vertex = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "Vertex")
vertex = np.zeros((n_vertex, 3))
for r in range(n_vertex):
vertex[r] = np.array(list(map(float, lines[i].split())))
i += 1
i = read_tag(lines, i, "NbrTet")
n_tet = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "Tet")
tet = np.zeros((n_tet, 4), dtype=np.int64)
for r in range(n_tet):
tet[r] = np.array(list(map(int, lines[i].split())), dtype=np.int64)
i += 1
i = read_tag(lines, i, "DomainOfTet")
i += n_tet
i = read_tag(lines, i, "NbrEdge")
n_edge = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "Edge")
i += n_edge
i = read_tag(lines, i, "EdgeOfTet")
i += n_tet
i = read_tag(lines, i, "NbrTri")
n_tri = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "Tri")
tri = np.zeros((n_tri, 3), dtype=np.int64)
for r in range(n_tri):
tri[r] = np.array(list(map(int, lines[i].split())), dtype=np.int64)
i += 1
i = read_tag(lines, i, "DomainOfTri")
domain_of_tri = np.zeros(n_tri, dtype=np.int64)
for r in range(n_tri):
domain_of_tri[r] = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "ConnOfTri")
conn_of_tri = np.zeros((n_tri, 2), dtype=np.int64)
for r in range(n_tri):
conn_of_tri[r] = np.array(list(map(int, lines[i].split())), dtype=np.int64)
i += 1
norm_of_face: dict[int, np.ndarray] = {}
norm_line = i
norm_truncated = False
if i < len(lines) and lines[i].strip() == "NormOfFace":
i += 1
if i >= len(lines):
norm_truncated = True
else:
nbr_norm = int(lines[i].strip())
i += 1
for _ in range(nbr_norm):
if i >= len(lines):
norm_truncated = True
break
parts = list(map(float, lines[i].split()))
if len(parts) < 4:
raise ValueError(f"Bad NormOfFace at line {i + 1}: {lines[i]!r}")
norm_of_face[int(parts[0])] = np.array(parts[1:4])
i += 1
return {
"lines": lines,
"norm_start": norm_line,
"content_end": i,
"norm_truncated": norm_truncated,
"vertex": vertex,
"tet": tet,
"tri": tri,
"domain_of_tri": domain_of_tri,
"conn_of_tri": conn_of_tri,
"norm_of_face": norm_of_face,
"n_vertex": n_vertex,
"n_tet": n_tet,
"n_edge": n_edge,
"n_tri": n_tri,
}
def compute_normals(mesh: dict) -> dict[int, np.ndarray]:
"""Compute NormOfFace for domains 1-9."""
V, T = mesh["vertex"], mesh["tet"]
dom_tri, conn = mesh["domain_of_tri"], mesh["conn_of_tri"]
result: dict[int, np.ndarray] = {}
for domain in ALL_FACE_DOMAINS:
tri_idx = np.where(dom_tri == domain)[0]
if tri_idx.size == 0:
raise ValueError(f"No triangles on domain {domain}")
if domain == 8:
result[domain] = matlab_inc_normal()
continue
if domain in SBC_OUT:
normals = []
for tri in tri_idx:
num_tet = conn[tri, 0] - 1
verts = V[T[num_tet] - 1]
normals.append(matlab_out_normal(domain, verts))
arr = np.vstack(normals)
n = arr.mean(axis=0)
n /= np.linalg.norm(n)
result[domain] = n
continue
# PBC (1,2,4,5) and PMC (6,7): geometric outward normal
geo = []
for tri in tri_idx:
num_tet = conn[tri, 0] - 1
num_face = conn[tri, 1]
pts = face_vertices(V, T, num_tet, num_face)
geo.append(geometric_face_normal(pts))
arr = np.vstack(geo)
n = arr.mean(axis=0)
n /= np.linalg.norm(n)
result[domain] = n
return result
def validate_mesh(mesh: dict, norms: dict[int, np.ndarray]) -> list[str]:
issues: list[str] = []
dom_set = set(int(d) for d in np.unique(mesh["domain_of_tri"]))
if mesh.get("norm_truncated"):
issues.append("CRITICAL: NormOfFace truncated")
if dom_set != set(ALL_FACE_DOMAINS):
issues.append(f"WARNING: DomainOfTri ids {sorted(dom_set)} (expected 1..9)")
extra = set(mesh["norm_of_face"]) - set(ALL_FACE_DOMAINS)
if extra:
issues.append(f"WARNING: obsolete NormOfFace domain ids {sorted(extra)} (e.g. 14 from SBC template)")
missing = set(ALL_FACE_DOMAINS) - set(mesh["norm_of_face"])
if missing:
issues.append(f"WARNING: missing NormOfFace entries for domains {sorted(missing)}")
if 9 in mesh["norm_of_face"] and np.allclose(mesh["norm_of_face"][9], [1, 0, 0], atol=1e-12):
issues.append("CRITICAL: domain 9 was [1,0,0] (SBC template), not assembly_out normal")
if mesh["tet"].min() < 1 or mesh["tet"].max() > mesh["n_vertex"]:
issues.append("CRITICAL: Tet indices out of range")
return issues
def build_norm_section(norms: dict[int, np.ndarray]) -> list[str]:
lines = ["NormOfFace", str(len(ALL_FACE_DOMAINS))]
for d in ALL_FACE_DOMAINS:
n = norms[d]
lines.append(f"{d} {n[0]:.16g} {n[1]:.16g} {n[2]:.16g}")
return lines
def fix_mesh_file(src: Path, dst: Path | None = None) -> int:
dst = dst or src
mesh = load_mesh_dat(src)
norms = compute_normals(mesh)
issues = validate_mesh(mesh, norms)
print(f"=== Validate: {src} ===")
print(f" NbrVertex={mesh['n_vertex']}, NbrTet={mesh['n_tet']}, "
f"NbrEdge={mesh['n_edge']}, NbrTri={mesh['n_tri']}")
print(f" DomainOfTri: {sorted(set(mesh['domain_of_tri'].tolist()))}")
for msg in issues:
print(f" {msg}")
print("\n=== NormOfFace (domains 1-9) ===")
for d in ALL_FACE_DOMAINS:
tag = ""
if d in SBC_OUT:
tag = " [SBC out, assembly_out.m]"
elif d in SBC_INC:
tag = " [SBC inc, assembly_inc.m]"
elif d in {1, 2, 4, 5}:
tag = " [PBC, geometric]"
else:
tag = " [PMC, geometric]"
n = norms[d]
print(f" {d}: ({n[0]:.6g}, {n[1]:.6g}, {n[2]:.6g}){tag}")
out_lines = mesh["lines"][: mesh["norm_start"]]
out_lines.extend(build_norm_section(norms))
dst.write_text("\n".join(out_lines) + "\n", encoding="ascii")
print(f"\nWrote fixed mesh: {dst}")
return 0 if not any("CRITICAL" in x for x in issues) else 1
def main() -> int:
parser = argparse.ArgumentParser(description="Validate and fix PBCmesh.dat NormOfFace (domains 1-9)")
parser.add_argument(
"mesh",
nargs="?",
type=Path,
default=Path(__file__).resolve().parents[2]
/ "三维matlab代码"
/ "matlab 3D一阶基+散射边界条件+单周期边界"
/ "PBCmesh.dat",
)
parser.add_argument("-o", "--output", type=Path, default=None)
parser.add_argument("--copy-to", type=Path, action="append", default=[])
args = parser.parse_args()
if not args.mesh.is_file():
print(f"Mesh not found: {args.mesh}", file=sys.stderr)
return 2
rc = fix_mesh_file(args.mesh, args.output or args.mesh)
out = args.output or args.mesh
for cp in args.copy_to:
cp.parent.mkdir(parents=True, exist_ok=True)
cp.write_text(out.read_text(encoding="ascii"), encoding="ascii")
print(f"Copied to {cp}")
return rc
if __name__ == "__main__":
raise SystemExit(main())

Some files were not shown because too many files have changed in this diff Show More