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>
|
|
@ -1,12 +1,72 @@
|
||||||
build/
|
# Build / VS
|
||||||
|
build/
|
||||||
build1/
|
build1/
|
||||||
scat3D/
|
**/build/
|
||||||
eigen3D/
|
**/build1/
|
||||||
3D opticsfem-master/scat3D/
|
.vs/
|
||||||
3D opticsfem-master/eigen3D/
|
|
||||||
3D opticsfem-master/build/
|
|
||||||
3D opticsfem-master/build1/
|
|
||||||
*.exe
|
*.exe
|
||||||
*.obj
|
*.obj
|
||||||
*.pdb
|
*.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/
|
||||||
|
测试数据集/
|
||||||
|
求解器/
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
||||||
|
|
@ -145,6 +145,10 @@ set(PHY
|
||||||
"phy/Phy_Base.h"
|
"phy/Phy_Base.h"
|
||||||
"phy/Phy_Base.cpp"
|
"phy/Phy_Base.cpp"
|
||||||
"phy/Phy_Interface.cpp"
|
"phy/Phy_Interface.cpp"
|
||||||
|
"phy/PortModeData.h"
|
||||||
|
"phy/PortModeData.cpp"
|
||||||
|
"phy/PortModeSolver.h"
|
||||||
|
"phy/PortModeSolver.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOLVER
|
set(SOLVER
|
||||||
|
|
@ -176,6 +180,12 @@ set(KERNEL
|
||||||
"kernel/Assemble_kernel.cpp"
|
"kernel/Assemble_kernel.cpp"
|
||||||
"kernel/Assemble_Scatter_2D_Boundary.cpp"
|
"kernel/Assemble_Scatter_2D_Boundary.cpp"
|
||||||
"kernel/Assemble_Scatter_3D_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"
|
"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_DEBUG "${CMAKE_BINARY_DIR}/Debug/complex"
|
||||||
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release/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"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include"../phy/Phy_Base.h"
|
#include"../phy/Phy_Base.h"
|
||||||
|
#include"../phy/PortModeSolver.h"
|
||||||
#include"../material/Material_Base.h"
|
#include"../material/Material_Base.h"
|
||||||
#include"../mesh/Mesh_Base.h"
|
#include"../mesh/Mesh_Base.h"
|
||||||
#include"../kernel/Assemble_Base.h"
|
#include"../kernel/Assemble_Base.h"
|
||||||
|
|
@ -6,6 +7,8 @@
|
||||||
#include "../nlohmann/json.hpp"
|
#include "../nlohmann/json.hpp"
|
||||||
#include "FEM_Interface.h"
|
#include "FEM_Interface.h"
|
||||||
|
|
||||||
|
#include "../Eigen/Dense"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
@ -127,15 +130,47 @@ int OpticsFEM_API::OpticsFEM_All(OpticsFEMData data)
|
||||||
solver.GetSolver(str);
|
solver.GetSolver(str);
|
||||||
mesh3d.GetMesh(meshFile, 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.GetMaterial(&matLab);
|
||||||
fem.GetMesh(&mesh3d);
|
fem.GetMesh(&mesh3d);
|
||||||
fem.GetPhy(&phy);
|
fem.GetPhy(&phy);
|
||||||
fem.GetSolver(&solver);
|
fem.GetSolver(&solver);
|
||||||
fem.GetPost(&post);
|
fem.GetPost(&post);
|
||||||
|
|
||||||
fem.Assemble();
|
|
||||||
fem.Run();
|
|
||||||
string outFile = js.at("OutFile");
|
string outFile = js.at("OutFile");
|
||||||
|
fem.SetExportAsmDirectory(outFile + "_asm");
|
||||||
|
fem.Assemble();
|
||||||
|
fem.Test_OutputMatrix(outFile);
|
||||||
|
fem.Run();
|
||||||
fem.Post(outFile);
|
fem.Post(outFile);
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
@ -158,6 +193,8 @@ int OpticsFEM_API::OpticsFEM_All(OpticsFEMData data)
|
||||||
fem.Assemble();
|
fem.Assemble();
|
||||||
string outFile = js.at("OutFile");
|
string outFile = js.at("OutFile");
|
||||||
fem.Test_OutputMatrix(outFile);
|
fem.Test_OutputMatrix(outFile);
|
||||||
|
fem.Run();
|
||||||
|
fem.Post(outFile);
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -487,7 +487,7 @@ cd "3D opticsfem-master"
|
||||||
|
|
||||||
# FemType=4 散射
|
# FemType=4 散射
|
||||||
cmake -B scat3D -G "Visual Studio 18 2026" -A x64
|
cmake -B scat3D -G "Visual Studio 18 2026" -A x64
|
||||||
cmake --build scat3D --config Release
|
|
||||||
|
|
||||||
# FemType=5 本征频率
|
# FemType=5 本征频率
|
||||||
cmake -B eigen3D -G "Visual Studio 18 2026" -A x64
|
cmake -B eigen3D -G "Visual Studio 18 2026" -A x64
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,221 @@
|
||||||
|
# OpticsFEM 3D PPT 更新建议(阶段三 · PBC 周期边界)
|
||||||
|
|
||||||
|
> 对应原稿:`yh -3D OpticsFEM.pptx`(9 页,2026-06-09)
|
||||||
|
> 当前进度:阶段 2(PEC/ELE)+ 阶段 3(PBC 单/双周期)已完成
|
||||||
|
> 配套图片目录:`ppt_assets/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、现有 PPT 结构 vs 当前进度
|
||||||
|
|
||||||
|
| 页码 | 现有内容 | 与现状的差距 |
|
||||||
|
|------|----------|--------------|
|
||||||
|
| 1 | 封面(2026.6.9) | 日期、副标题需更新 |
|
||||||
|
| 2–3 | 背景、软件框架 | 基本可用,可小幅补充 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=819,PBC2=813,合并后 **1570 对**
|
||||||
|
|
||||||
|
**本征 FemType=5 + PBC**
|
||||||
|
|
||||||
|
- `Assemble_EigenFreq_Boundary.cpp` 中 `Assemble_PEC_PBC()` 已接入
|
||||||
|
|
||||||
|
### 4. 第 7 页 · 后续工作一(重点改写)
|
||||||
|
|
||||||
|
建议拆成两段:
|
||||||
|
|
||||||
|
**【阶段 2–3 已完成】**
|
||||||
|
|
||||||
|
- 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 页 · 工作总结
|
||||||
|
|
||||||
|
在「已完成」中补充:
|
||||||
|
|
||||||
|
```
|
||||||
|
⑥ 阶段2:3D 散射 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 接口,与第 10–11 页呼应
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*文档生成日期:2026-06-23*
|
||||||
|
|
@ -167296,9 +167296,10 @@ ConnOfTri
|
||||||
35832 3
|
35832 3
|
||||||
35829 3
|
35829 3
|
||||||
NormOfFace
|
NormOfFace
|
||||||
5
|
6
|
||||||
1 -1 0 0
|
1 -1 0 0
|
||||||
2 0 -1 0
|
2 0 -1 0
|
||||||
3 0 0 -1
|
3 0 0 -1
|
||||||
|
4 0 0 1
|
||||||
5 0 1 0
|
5 0 1 0
|
||||||
14 1 0 0
|
14 1 0 0
|
||||||
|
|
|
||||||
|
|
@ -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 + 入射组装基础上,扩展更多边界类型",
|
||||||
|
" · 对齐 MATLAB:assembly_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.dat;phy / 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()
|
||||||
|
|
@ -20,7 +20,7 @@ const int BF_NEDELEC = 21;
|
||||||
const int BF_CURL_NEDELEC = 23;
|
const int BF_CURL_NEDELEC = 23;
|
||||||
|
|
||||||
const int BF_MIMORDER = 1;
|
const int BF_MIMORDER = 1;
|
||||||
const int BF_MAXORDER = 1;
|
const int BF_MAXORDER = 2;
|
||||||
const int BF_LINEFUNC = 1;
|
const int BF_LINEFUNC = 1;
|
||||||
const int BF_QUADFUNC = 2;
|
const int BF_QUADFUNC = 2;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,62 @@
|
||||||
#include"BF.h"
|
#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)
|
void BF_Tetrahedron_Nedelec(BF_INPUT)
|
||||||
{
|
{
|
||||||
switch (order)
|
switch (order)
|
||||||
|
|
@ -7,16 +64,20 @@ void BF_Tetrahedron_Nedelec(BF_INPUT)
|
||||||
case 1:
|
case 1:
|
||||||
switch (numBF)
|
switch (numBF)
|
||||||
{
|
{
|
||||||
case 1: o(0) = -v; o(1) = u; o(2) = 0.; break; //12
|
case 1: o(0) = -v; o(1) = u; o(2) = 0.; break;
|
||||||
case 2: o(0) = -w; o(1) = 0.; o(2) = u; break; //13
|
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; //14
|
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; //23
|
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; //24
|
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; //34
|
case 6: o(0) = -w; o(1) = -w; o(2) = -1 + u + v; break;
|
||||||
default: break;
|
default: o.setZero(); break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 2:
|
||||||
|
BF_Tetrahedron_Nedelec_Order2(numBF, u, v, w, o);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
|
o.setZero();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -28,17 +89,20 @@ void BF_Tetrahedron_Curl_Nedelec(BF_INPUT)
|
||||||
case 1:
|
case 1:
|
||||||
switch (numBF)
|
switch (numBF)
|
||||||
{
|
{
|
||||||
case 1: o(0) = 0.; o(1) = 0.; o(2) = 2.; break; //12
|
case 1: o(0) = 0.; o(1) = 0.; o(2) = 2.; break;
|
||||||
case 2: o(0) = 0.; o(1) = -2.; o(2) = 0.; break; //13
|
case 2: o(0) = 0.; o(1) = -2.; o(2) = 0.; break;
|
||||||
case 3: o(0) = 0.; o(1) = 2.; o(2) = -2.; break; //14
|
case 3: o(0) = 0.; o(1) = 2.; o(2) = -2.; break;
|
||||||
case 4: o(0) = 2.; o(1) = 0.; o(2) = 0.; break; //23
|
case 4: o(0) = 2.; o(1) = 0.; o(2) = 0.; break;
|
||||||
case 5: o(0) = -2.; o(1) = 0.; o(2) = 2.; break; //24
|
case 5: o(0) = -2.; o(1) = 0.; o(2) = 2.; break;
|
||||||
case 6: o(0) = 2.; o(1) = -2.; o(2) = 0.; break; //34
|
case 6: o(0) = 2.; o(1) = -2.; o(2) = 0.; break;
|
||||||
default:
|
default: o.setZero(); break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 2:
|
||||||
|
BF_Tetrahedron_Curl_Nedelec_Order2(numBF, u, v, w, o);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
|
o.setZero();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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), [
|
||||||
|
"原有 OpticsFEM:2D 三角网格 + 一阶边元(FemType 0–3)",
|
||||||
|
"目标:扩展至 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 4(3D 散射)与 5(3D 本征);共用 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/Bv(COO,0-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",
|
||||||
|
"complexsolver:Eigen SparseLU / BiCGSTAB",
|
||||||
|
"路径:Release/complex/complexsolver.exe",
|
||||||
|
"本征 3D:当前 MATLAB eigs 验证",
|
||||||
|
"优势:无需 PETSc,CMake 一键编译",
|
||||||
|
], 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()
|
||||||
|
|
@ -140,4 +140,9 @@ void OpticsFEM_3D_Scatter::GetSolver(Solver_LdaDom* solver)
|
||||||
void OpticsFEM_3D_Scatter::GetPost(Post_3D_Scatter* post)
|
void OpticsFEM_3D_Scatter::GetPost(Post_3D_Scatter* post)
|
||||||
{
|
{
|
||||||
_mPost = post;
|
_mPost = post;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpticsFEM_3D_Scatter::SetExportAsmDirectory(const std::string& dir)
|
||||||
|
{
|
||||||
|
_mExportAsmDir = dir;
|
||||||
}
|
}
|
||||||
|
|
@ -164,6 +164,8 @@ public:
|
||||||
void GetPost(Post_3D_EigenFreq* post);
|
void GetPost(Post_3D_EigenFreq* post);
|
||||||
|
|
||||||
void Assemble();
|
void Assemble();
|
||||||
|
void Run();
|
||||||
|
void Post(std::string str);
|
||||||
|
|
||||||
void Test_OutputMatrix(const std::string& outDir = "./OutFile");
|
void Test_OutputMatrix(const std::string& outDir = "./OutFile");
|
||||||
|
|
||||||
|
|
@ -180,6 +182,9 @@ private:
|
||||||
bool _mIsReal;
|
bool _mIsReal;
|
||||||
int _mDof;
|
int _mDof;
|
||||||
|
|
||||||
|
Eigen::MatrixXcd _mX;
|
||||||
|
Eigen::VectorXcd _mLambda;
|
||||||
|
|
||||||
std::vector<Eigen::Triplet<double>> _mTripleA_real, _mTripleB_real;
|
std::vector<Eigen::Triplet<double>> _mTripleA_real, _mTripleB_real;
|
||||||
Eigen::SparseMatrix<double, Eigen::RowMajor> _mA_real;
|
Eigen::SparseMatrix<double, Eigen::RowMajor> _mA_real;
|
||||||
Eigen::SparseMatrix<double, Eigen::RowMajor> _mB_real;
|
Eigen::SparseMatrix<double, Eigen::RowMajor> _mB_real;
|
||||||
|
|
@ -203,11 +208,20 @@ public:
|
||||||
void Run();
|
void Run();
|
||||||
void Post(std::string str);
|
void Post(std::string str);
|
||||||
void Test_OutputMatrix(const std::string& outDir = ".");
|
void Test_OutputMatrix(const std::string& outDir = ".");
|
||||||
|
void SetExportAsmDirectory(const std::string& dir);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Assemble_WaveEquation();
|
void Assemble_WaveEquation();
|
||||||
|
void Assemble_WaveEquation_Order2();
|
||||||
void Assemble_SBC();
|
void Assemble_SBC();
|
||||||
|
void Assemble_BELE();
|
||||||
|
void Assemble_MAG();
|
||||||
|
void Assemble_SCD();
|
||||||
|
void Assemble_MPD();
|
||||||
|
void Assemble_EPD();
|
||||||
void Assemble_PEC_ELE();
|
void Assemble_PEC_ELE();
|
||||||
|
void Assemble_PBC();
|
||||||
|
void Assemble_Port();
|
||||||
|
|
||||||
MaterialLib* _mMatLib;
|
MaterialLib* _mMatLib;
|
||||||
Mesh_3D* _mMesh;
|
Mesh_3D* _mMesh;
|
||||||
|
|
@ -217,6 +231,8 @@ private:
|
||||||
|
|
||||||
bool _mIsReal;
|
bool _mIsReal;
|
||||||
int _mDof;
|
int _mDof;
|
||||||
|
int _mPortExtraDof = 0;
|
||||||
|
std::vector<int> _mFreeDofIndices; // full-system DOF indices kept after PEC elimination (port case)
|
||||||
|
|
||||||
Eigen::VectorXcd _mX;
|
Eigen::VectorXcd _mX;
|
||||||
|
|
||||||
|
|
@ -228,6 +244,7 @@ private:
|
||||||
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor> _mA_complex;
|
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor> _mA_complex;
|
||||||
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor> _mP_complex;
|
Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor> _mP_complex;
|
||||||
Eigen::VectorXcd _mB_complex;
|
Eigen::VectorXcd _mB_complex;
|
||||||
|
std::string _mExportAsmDir;
|
||||||
};
|
};
|
||||||
|
|
||||||
class OpticsFEM_3D_Scatter2
|
class OpticsFEM_3D_Scatter2
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
#include"Assemble_Base.h"
|
#include"Assemble_Base.h"
|
||||||
|
#include"PBC_Util.h"
|
||||||
|
#include"Nedelec3D_Util.h"
|
||||||
#include"../common/util.h"
|
#include"../common/util.h"
|
||||||
#include<iostream>
|
#include<iostream>
|
||||||
|
#include<vector>
|
||||||
|
#include<algorithm>
|
||||||
|
|
||||||
void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
|
void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
|
||||||
{
|
{
|
||||||
|
|
@ -24,11 +28,11 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
|
||||||
PBCData(i) = _mPhy->GetPBCPhi(i);
|
PBCData(i) = _mPhy->GetPBCPhi(i);
|
||||||
}
|
}
|
||||||
_mMesh->GetIndexOfPBC(PBC, PBCData, srcNodeIndex, dstNodeIndex, nodePhi, srcEdgeIndex, dstEdgeIndex, edgePhi);
|
_mMesh->GetIndexOfPBC(PBC, PBCData, srcNodeIndex, dstNodeIndex, nodePhi, srcEdgeIndex, dstEdgeIndex, edgePhi);
|
||||||
//行 1:DOF
|
//?? 1:DOF
|
||||||
//列 1:(DOF-NbrPEC-NbrDst)
|
//?? 1??(DOF-NbrPEC-NbrDst)
|
||||||
int NbrVertex = _mMesh->GetNbrVertex();
|
int NbrVertex = _mMesh->GetNbrVertex();
|
||||||
int NbrDstIndex = nodeIndexOfPEC.rows() + edgeIndexOfPEC.rows() + dstNodeIndex.rows() + dstEdgeIndex.rows();
|
int NbrDstIndex = nodeIndexOfPEC.rows() + edgeIndexOfPEC.rows() + dstNodeIndex.rows() + dstEdgeIndex.rows();
|
||||||
//电场类别去除数目
|
//??T?????????
|
||||||
int numEle = 0;
|
int numEle = 0;
|
||||||
if (_mElectricType == 0)
|
if (_mElectricType == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -42,7 +46,7 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
|
||||||
Eigen::VectorXi dstIndex = Eigen::VectorXi::Zero(NbrDstIndex);
|
Eigen::VectorXi dstIndex = Eigen::VectorXi::Zero(NbrDstIndex);
|
||||||
Eigen::VectorXi srcIndex = Eigen::VectorXi::Zero(NbrDstIndex);
|
Eigen::VectorXi srcIndex = Eigen::VectorXi::Zero(NbrDstIndex);
|
||||||
Eigen::VectorXcd IndexPhi = Eigen::VectorXcd::Zero(NbrDstIndex);
|
Eigen::VectorXcd IndexPhi = Eigen::VectorXcd::Zero(NbrDstIndex);
|
||||||
//电场类别去除索引添加
|
//??T??????????????
|
||||||
if (_mElectricType == 0)
|
if (_mElectricType == 0)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < numEle; i++)
|
for (int i = 0; i < numEle; i++)
|
||||||
|
|
@ -89,12 +93,12 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
|
||||||
srcIndex(i + num) = srcEdgeIndex(i) + NbrVertex;
|
srcIndex(i + num) = srcEdgeIndex(i) + NbrVertex;
|
||||||
IndexPhi(i + num) = edgePhi(i);
|
IndexPhi(i + num) = edgePhi(i);
|
||||||
}
|
}
|
||||||
//排序
|
//????
|
||||||
Eigen::VectorXi tempIndex = Eigen::VectorXi::Zero(NbrDstIndex);
|
Eigen::VectorXi tempIndex = Eigen::VectorXi::Zero(NbrDstIndex);
|
||||||
for (int i = 0; i < NbrDstIndex; i++)
|
for (int i = 0; i < NbrDstIndex; i++)
|
||||||
tempIndex(i) = i;
|
tempIndex(i) = i;
|
||||||
QuickSort(dstIndex, tempIndex, 0, NbrDstIndex - 1);
|
QuickSort(dstIndex, tempIndex, 0, NbrDstIndex - 1);
|
||||||
//去重
|
//???
|
||||||
Unique(dstIndex, tempIndex);
|
Unique(dstIndex, tempIndex);
|
||||||
Eigen::VectorXcd tempIndexPhi = Eigen::VectorXcd::Zero(dstIndex.rows());
|
Eigen::VectorXcd tempIndexPhi = Eigen::VectorXcd::Zero(dstIndex.rows());
|
||||||
Eigen::VectorXi tempSrcIndex = Eigen::VectorXi::Zero(dstIndex.rows());
|
Eigen::VectorXi tempSrcIndex = Eigen::VectorXi::Zero(dstIndex.rows());
|
||||||
|
|
@ -106,12 +110,12 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
|
||||||
IndexPhi = tempIndexPhi;
|
IndexPhi = tempIndexPhi;
|
||||||
srcIndex = tempSrcIndex;
|
srcIndex = tempSrcIndex;
|
||||||
|
|
||||||
//不同数目
|
//??????
|
||||||
int dof = _mMesh->GetNbrEdge() + _mMesh->GetNbrVertex();
|
int dof = _mMesh->GetNbrEdge() + _mMesh->GetNbrVertex();
|
||||||
int vdof = _mMesh->GetNbrEdge();
|
int vdof = _mMesh->GetNbrEdge();
|
||||||
int sdof = _mMesh->GetNbrVertex();
|
int sdof = _mMesh->GetNbrVertex();
|
||||||
|
|
||||||
//P矩阵
|
//P????
|
||||||
int num = 0;
|
int num = 0;
|
||||||
if (_mIsReal)
|
if (_mIsReal)
|
||||||
{
|
{
|
||||||
|
|
@ -119,7 +123,7 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
|
||||||
for (int i = 0; i < dof; i++) //
|
for (int i = 0; i < dof; i++) //
|
||||||
{
|
{
|
||||||
temp_P.push_back(Eigen::Triplet<double>(i, i, 1));
|
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))
|
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(),
|
std::sort(temp_P.begin(), temp_P.end(),
|
||||||
[](const Eigen::Triplet<double>& a, const Eigen::Triplet<double>& b)
|
[](const Eigen::Triplet<double>& a, const Eigen::Triplet<double>& b)
|
||||||
{
|
{
|
||||||
return a.col() < b.col();
|
return a.col() < b.col();
|
||||||
});
|
});
|
||||||
///*排序后打印*/
|
///*???????*/
|
||||||
//std::sort(temp_P.begin(), temp_P.end(),
|
//std::sort(temp_P.begin(), temp_P.end(),
|
||||||
// [](const Eigen::Triplet<double>& a, const Eigen::Triplet<double>& b) {
|
// [](const Eigen::Triplet<double>& a, const Eigen::Triplet<double>& b) {
|
||||||
// return a.col() < b.col();
|
// 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;
|
// std::cout << "(" << triplet.row() << ", " << triplet.col() << "): " << triplet.value() << std::endl;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/*删除dstIndex对应列*/
|
/*???dstIndex?????*/
|
||||||
std::vector<Eigen::Triplet<double>> tripleP_real;
|
std::vector<Eigen::Triplet<double>> tripleP_real;
|
||||||
num = 0;// 用于跟踪新矩阵的列索引
|
num = 0;// ???????????????????
|
||||||
int DeleteIndex = 0;
|
int DeleteIndex = 0;
|
||||||
for (int i = 0; i < dof; i++)
|
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 = Eigen::SparseMatrix<double, Eigen::RowMajor>(dof, dof - dstIndex.rows());
|
||||||
_mP_real.setFromTriplets(tripleP_real.begin(), tripleP_real.end());
|
_mP_real.setFromTriplets(tripleP_real.begin(), tripleP_real.end());
|
||||||
|
|
||||||
/*排序后打印*/
|
/*???????*/
|
||||||
//std::sort(tripleP_real.begin(), tripleP_real.end(),
|
//std::sort(tripleP_real.begin(), tripleP_real.end(),
|
||||||
// [](const Eigen::Triplet<double>& a, const Eigen::Triplet<double>& b) {
|
// [](const Eigen::Triplet<double>& a, const Eigen::Triplet<double>& b) {
|
||||||
// return a.col() < b.col();
|
// return a.col() < b.col();
|
||||||
|
|
@ -194,7 +198,7 @@ void OpticsFEM_2D_EigenFreq::Assemble_PEC_PBC()
|
||||||
for (int i = 0; i < dof; i++) //
|
for (int i = 0; i < dof; i++) //
|
||||||
{
|
{
|
||||||
temp_P_complex.push_back(Eigen::Triplet<complex<double>>(i, i, 1));
|
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))
|
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(),
|
std::sort(temp_P_complex.begin(), temp_P_complex.end(),
|
||||||
[](const Eigen::Triplet<complex<double>>& a, const Eigen::Triplet<complex<double>>& b)
|
[](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;
|
std::vector<Eigen::Triplet<complex<double>>> tripleP_complex;
|
||||||
num = 0;// 用于跟踪新矩阵的列索引
|
num = 0;// ???????????????????
|
||||||
int DeleteIndex = 0;
|
int DeleteIndex = 0;
|
||||||
for (int i = 0; i < dof; i++)
|
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 = Eigen::SparseMatrix<complex<double>, Eigen::RowMajor>(dof, dof - dstIndex.rows());
|
||||||
_mP_complex.setFromTriplets(tripleP_complex.begin(), tripleP_complex.end());;
|
_mP_complex.setFromTriplets(tripleP_complex.begin(), tripleP_complex.end());;
|
||||||
///*排序后打印*/
|
///*???????*/
|
||||||
//std::sort(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)
|
//[](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()
|
void OpticsFEM_3D_EigenFreq::Assemble_PEC_PBC()
|
||||||
{
|
{
|
||||||
//find PEC index
|
const int elementOrder = _mPhy->GetElementOrder();
|
||||||
Eigen::VectorXi PECDomain = Eigen::VectorXi::Zero(_mPhy->GetNbrPEC());
|
std::vector<int> allSrc;
|
||||||
for (int i = 0; i < _mPhy->GetNbrPEC(); i++)
|
std::vector<int> allDst;
|
||||||
PECDomain(i) = _mPhy->GetPECDomain(i);
|
std::vector<std::complex<double>> allPhi;
|
||||||
Eigen::VectorXi edgeIndexOfPEC;
|
|
||||||
_mMesh->GetTriIndexOfDomain(PECDomain, edgeIndexOfPEC);
|
|
||||||
|
|
||||||
//find PBC index
|
if (_mPhy->GetNbrPEC() > 0)
|
||||||
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++)
|
|
||||||
{
|
{
|
||||||
PBC(i, 0) = _mPhy->GetSrcDomain(i);
|
Eigen::VectorXi PECDomain = Eigen::VectorXi::Zero(_mPhy->GetNbrPEC());
|
||||||
PBC(i, 1) = _mPhy->GetDstDomain(i);
|
for (int i = 0; i < _mPhy->GetNbrPEC(); i++)
|
||||||
PBCData(i) = _mPhy->GetPBCPhi(i);
|
PECDomain(i) = _mPhy->GetPECDomain(i) + 1;
|
||||||
}
|
// Reuse scatter PEC DOF collector via local duplicate of edge+face zeroing.
|
||||||
_mMesh->GetIndexOfPBC(PBC, PBCData, srcEdgeIndex, dstEdgeIndex, edgePhi);
|
// Edge DOFs (order1) or edge×2 + face×2 (order2).
|
||||||
|
std::vector<int> pecEdges;
|
||||||
//行 1:DOF
|
for (int i = 0; i < PECDomain.rows(); i++)
|
||||||
//列 1:(DOF-NbrPEC-NbrDst)
|
{
|
||||||
int NbrDstIndex = edgeIndexOfPEC.rows() + dstEdgeIndex.rows();
|
Eigen::VectorXi triIndices;
|
||||||
Eigen::VectorXi dstIndex = Eigen::VectorXi::Zero(NbrDstIndex);
|
_mMesh->GetTriIndicesOfDomain(PECDomain(i), triIndices);
|
||||||
Eigen::VectorXi srcIndex = Eigen::VectorXi::Zero(NbrDstIndex);
|
for (int t = 0; t < triIndices.size(); t++)
|
||||||
Eigen::VectorXcd IndexPhi = Eigen::VectorXcd::Zero(NbrDstIndex);
|
{
|
||||||
for (int i = 0; i < edgeIndexOfPEC.rows(); i++)
|
Eigen::Vector3i conn;
|
||||||
{
|
_mMesh->GetCoonOfTri(triIndices(t), conn);
|
||||||
dstIndex(i) = edgeIndexOfPEC(i);
|
const int numTet = conn(0);
|
||||||
srcIndex(i) = edgeIndexOfPEC(i);
|
const int numFace = conn(1) + 1;
|
||||||
IndexPhi(i) = 0;
|
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); }
|
||||||
for (int i = 0; i < dstEdgeIndex.rows(); i++)
|
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); }
|
||||||
int num = edgeIndexOfPEC.rows();
|
else if (numFace == 4) { e[0] = _mMesh->GetEdgeOfTet(numTet, 3); e[1] = _mMesh->GetEdgeOfTet(numTet, 4); e[2] = _mMesh->GetEdgeOfTet(numTet, 5); }
|
||||||
dstIndex(i + num) = dstEdgeIndex(i);
|
for (int k = 0; k < 3; k++)
|
||||||
srcIndex(i + num) = srcEdgeIndex(i);
|
if (e[k] >= 0) pecEdges.push_back(e[k]);
|
||||||
IndexPhi(i + num) = edgePhi(i);
|
}
|
||||||
}
|
}
|
||||||
//排序
|
std::sort(pecEdges.begin(), pecEdges.end());
|
||||||
Eigen::VectorXi tempIndex = Eigen::VectorXi::Zero(NbrDstIndex);
|
pecEdges.erase(std::unique(pecEdges.begin(), pecEdges.end()), pecEdges.end());
|
||||||
for (int i = 0; i < NbrDstIndex; i++)
|
const int nE = _mMesh->GetNbrEdge();
|
||||||
tempIndex(i) = i;
|
const int nF = _mMesh->GetNbrFace();
|
||||||
QuickSort(dstIndex, tempIndex, 0, NbrDstIndex - 1);
|
for (int edgeId : pecEdges)
|
||||||
Eigen::VectorXcd tempIndexPhi = Eigen::VectorXcd::Zero(NbrDstIndex);
|
{
|
||||||
for (int i = 0; i < NbrDstIndex; i++)
|
allSrc.push_back(Nedelec3D::edgeGlobalDof(edgeId, 0, nE));
|
||||||
tempIndexPhi(i) = IndexPhi(tempIndex(i));
|
allDst.push_back(Nedelec3D::edgeGlobalDof(edgeId, 0, nE));
|
||||||
IndexPhi = tempIndexPhi;
|
allPhi.push_back(0.0);
|
||||||
Eigen::VectorXi tempSrcIndex = Eigen::VectorXi::Zero(NbrDstIndex);
|
if (elementOrder == 2)
|
||||||
for (int i = 0; i < NbrDstIndex; i++)
|
{
|
||||||
tempSrcIndex(i) = srcIndex(tempIndex(i));
|
allSrc.push_back(Nedelec3D::edgeGlobalDof(edgeId, 1, nE));
|
||||||
srcIndex = tempSrcIndex;
|
allDst.push_back(Nedelec3D::edgeGlobalDof(edgeId, 1, nE));
|
||||||
//去重
|
allPhi.push_back(0.0);
|
||||||
Unique(dstIndex, tempIndex);
|
}
|
||||||
if (NbrDstIndex > tempIndex.rows())
|
}
|
||||||
{
|
if (elementOrder == 2)
|
||||||
for (int i = 0; i < tempIndex.rows(); i++)
|
{
|
||||||
IndexPhi(i) = IndexPhi(tempIndex(i));
|
std::vector<int> pecFaces;
|
||||||
IndexPhi = IndexPhi.head(tempIndex.rows());
|
for (int i = 0; i < PECDomain.rows(); i++)
|
||||||
|
{
|
||||||
for (int i = 0; i < tempIndex.rows(); i++)
|
Eigen::VectorXi triIndices;
|
||||||
srcIndex(i) = srcIndex(tempIndex(i));
|
_mMesh->GetTriIndicesOfDomain(PECDomain(i), triIndices);
|
||||||
srcIndex = srcIndex.head(tempIndex.rows());
|
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矩阵
|
if (_mPhy->GetNbrPBCGroups() > 0)
|
||||||
int num = 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)
|
if (_mIsReal)
|
||||||
{
|
buildPeriodicProjectionReal(_mDof, dstIndex, srcIndex, indexPhi, _mP_real);
|
||||||
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++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tripleP_real.push_back(Eigen::Triplet<double>(i, i - num, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_mP_real.setFromTriplets(tripleP_real.begin(), tripleP_real.end());
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
buildPeriodicProjectionComplex(_mDof, dstIndex, srcIndex, indexPhi, _mP_complex);
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include"Assemble_Base.h"
|
#include"Assemble_Base.h"
|
||||||
|
#include"Nedelec3D_Util.h"
|
||||||
#include"../function/BF.h"
|
#include"../function/BF.h"
|
||||||
#include"../function/Gauss.h"
|
#include"../function/Gauss.h"
|
||||||
#include"../common/define.h"
|
#include"../common/define.h"
|
||||||
|
|
@ -228,11 +229,15 @@ void OpticsFEM_2D_EigenFreq::Assemble_WaveEquation()
|
||||||
|
|
||||||
void OpticsFEM_3D_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
|
//init of Gauss point
|
||||||
Gauss gauss;
|
Gauss gauss;
|
||||||
int NbrGuassPoints;
|
int NbrGuassPoints;
|
||||||
double* u, * v, * w, * wght;
|
double* u, * v, * w, * wght;
|
||||||
NbrGuassPoints = gauss.GetNbrGaussPoints(THREEDIM, TETRAHEDRON, BF_LINEFUNC * 2);
|
NbrGuassPoints = gauss.GetNbrGaussPoints(THREEDIM, TETRAHEDRON, gaussVol);
|
||||||
u = new double[NbrGuassPoints];
|
u = new double[NbrGuassPoints];
|
||||||
v = new double[NbrGuassPoints];
|
v = new double[NbrGuassPoints];
|
||||||
w = new double[NbrGuassPoints];
|
w = new double[NbrGuassPoints];
|
||||||
|
|
@ -247,8 +252,8 @@ void OpticsFEM_3D_EigenFreq::Assemble_WaveEquation()
|
||||||
BF BF_Nedelec, BF_Curl_Nedelec;
|
BF BF_Nedelec, BF_Curl_Nedelec;
|
||||||
int dof;
|
int dof;
|
||||||
Vector3d** E, ** curlE;
|
Vector3d** E, ** curlE;
|
||||||
dof = BF_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, BF_LINEFUNC);
|
dof = BF_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder);
|
||||||
BF_Curl_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_CURL_NEDELEC, BF_LINEFUNC);
|
BF_Curl_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_CURL_NEDELEC, bfOrder);
|
||||||
E = new Vector3d * [NbrGuassPoints];
|
E = new Vector3d * [NbrGuassPoints];
|
||||||
curlE = new Vector3d * [NbrGuassPoints];
|
curlE = new Vector3d * [NbrGuassPoints];
|
||||||
for (int i = 0; i < NbrGuassPoints; i++)
|
for (int i = 0; i < NbrGuassPoints; i++)
|
||||||
|
|
@ -292,8 +297,10 @@ void OpticsFEM_3D_EigenFreq::Assemble_WaveEquation()
|
||||||
|
|
||||||
//mapping
|
//mapping
|
||||||
VectorXi MappingIndex = VectorXi::Zero(dof);
|
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++)
|
for (int i = 0; i < dof; i++)
|
||||||
MappingIndex(i) = _mMesh->GetEdgeOfTet(n, i);
|
MappingIndex(i) = mapBuf[static_cast<size_t>(i)];
|
||||||
|
|
||||||
//submatrix
|
//submatrix
|
||||||
MatrixXcd Se, Te;
|
MatrixXcd Se, Te;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,35 @@
|
||||||
#include"Assemble_Base.h"
|
#include"Assemble_Base.h"
|
||||||
|
#include"Nedelec3D_Util.h"
|
||||||
|
#include"PBC_Util.h"
|
||||||
|
#include"SBC_NormalUtil.h"
|
||||||
#include"../function/BF.h"
|
#include"../function/BF.h"
|
||||||
#include"../function/Gauss.h"
|
#include"../function/Gauss.h"
|
||||||
#include"../common/define.h"
|
#include"../common/define.h"
|
||||||
|
#include"../common/util.h"
|
||||||
#include"../material/Material_Base.h"
|
#include"../material/Material_Base.h"
|
||||||
#include"../phy/Phy_Base.h"
|
#include"../phy/Phy_Base.h"
|
||||||
|
#include"../parser/mpParser.h"
|
||||||
|
|
||||||
#include<cmath>
|
#include<cmath>
|
||||||
#include<complex>
|
#include<complex>
|
||||||
#include<string>
|
#include<string>
|
||||||
#include<vector>
|
#include<vector>
|
||||||
|
#include<algorithm>
|
||||||
|
|
||||||
using namespace Eigen;
|
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(γ) 进 A;E_b 的 ABC 切向项 + IBP 面项 → Assemble_BELE() 进 b
|
||||||
|
// Robin q=0:SBC 上不组装 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 {
|
namespace {
|
||||||
|
|
||||||
|
|
@ -21,11 +38,236 @@ Vector3d crossNormal(const Vector3d& normal, const Vector3d& a)
|
||||||
return normal.cross(a.cross(normal));
|
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)
|
double getScalarEps(int domainOfTet, MaterialLib* matLib)
|
||||||
{
|
{
|
||||||
return matLib->GetEpsr(domainOfTet)(0, 0).real();
|
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(
|
void assembleSBCFace(
|
||||||
Mesh_3D* mesh,
|
Mesh_3D* mesh,
|
||||||
MaterialLib* matLib,
|
MaterialLib* matLib,
|
||||||
|
|
@ -36,8 +278,10 @@ void assembleSBCFace(
|
||||||
BF& bfN,
|
BF& bfN,
|
||||||
bool isInc,
|
bool isInc,
|
||||||
const Vector3cd& Einc,
|
const Vector3cd& Einc,
|
||||||
int triIdx)
|
int triIdx,
|
||||||
|
int elementOrder)
|
||||||
{
|
{
|
||||||
|
const int nBfFace = (elementOrder == 2) ? 8 : 3;
|
||||||
const int domain = mesh->GetDomainOfTri(triIdx);
|
const int domain = mesh->GetDomainOfTri(triIdx);
|
||||||
|
|
||||||
Vector3i conn;
|
Vector3i conn;
|
||||||
|
|
@ -55,10 +299,44 @@ void assembleSBCFace(
|
||||||
|
|
||||||
Vector3d x2, y2, z2;
|
Vector3d x2, y2, z2;
|
||||||
Vector3d x3, y3, z3;
|
Vector3d x3, y3, z3;
|
||||||
int bfIndex[3];
|
std::vector<int> bfIndex(nBfFace);
|
||||||
int mappingIndex[3];
|
std::vector<int> mappingIndex(nBfFace);
|
||||||
|
|
||||||
if (numFace == 1)
|
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;
|
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];
|
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;
|
if (heron < 0.0) heron = 0.0;
|
||||||
const double integCoe = 0.25 * std::sqrt(heron);
|
const double integCoe = 0.25 * std::sqrt(heron);
|
||||||
|
|
||||||
Vector3d normal;
|
Vector3d meshNorm;
|
||||||
mesh->GetNormOfFace(domain, normal);
|
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++)
|
for (int gp = 0; gp < nbrGP; gp++)
|
||||||
{
|
{
|
||||||
const double wgp = 1.0 - u[gp] - v[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 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 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;
|
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]);
|
bfN.GetValueBF(bfIndex[static_cast<size_t>(j)], u2, v2, w2, E[static_cast<size_t>(gp)][static_cast<size_t>(j)]);
|
||||||
E[gp][j] = InvJac * E[gp][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> nn = std::sqrt(std::complex<double>(eps, 0.0));
|
||||||
const std::complex<double> iUnit(0.0, 1.0);
|
const std::complex<double> iUnit(0.0, 1.0);
|
||||||
|
|
||||||
Matrix3cd Ae = Matrix3cd::Zero();
|
MatrixXcd Ae = MatrixXcd::Zero(nBfFace, nBfFace);
|
||||||
for (int i = 0; i < 3; i++)
|
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++)
|
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]
|
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)
|
if (isInc)
|
||||||
{
|
{
|
||||||
const Vector3d EincReal(Einc.real()(0), Einc.real()(1), Einc.real()(2));
|
const Vector3cd tEinc = crossNormal(normal, Einc);
|
||||||
const Vector3d tEinc = crossNormal(normal, EincReal);
|
for (int i = 0; i < nBfFace; i++)
|
||||||
for (int i = 0; i < 3; i++)
|
|
||||||
{
|
{
|
||||||
for (int gp = 0; gp < nbrGP; gp++)
|
for (int gp = 0; gp < nbrGP; gp++)
|
||||||
{
|
{
|
||||||
Be[i] -= iUnit * k0 * nn * 2.0 * integCoe * wght[gp]
|
const std::complex<double> dotE =
|
||||||
* E[gp][i].dot(tEinc) * 2.0;
|
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++)
|
for (int j = 0; j < nBfFace; j++)
|
||||||
tripleA.emplace_back(mappingIndex[i], mappingIndex[j], Ae(i, j));
|
tripleA.emplace_back(mappingIndex[static_cast<size_t>(i)], mappingIndex[static_cast<size_t>(j)], Ae(i, j));
|
||||||
if (isInc)
|
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,
|
bool isInc,
|
||||||
const Vector3cd& Einc)
|
const Vector3cd& Einc)
|
||||||
{
|
{
|
||||||
|
const int elementOrder = phy->GetElementOrder();
|
||||||
|
const int gaussTriOrder = Nedelec3D::gaussOrderTri(elementOrder);
|
||||||
|
const int bfOrder = Nedelec3D::bfOrderParam(elementOrder);
|
||||||
|
|
||||||
Gauss gauss;
|
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* u = new double[nbrGP];
|
||||||
double* v = new double[nbrGP];
|
double* v = new double[nbrGP];
|
||||||
double* w = new double[nbrGP];
|
double* w = new double[nbrGP];
|
||||||
|
|
@ -195,7 +484,7 @@ void assembleSBCByType(
|
||||||
gauss.GetGaussPoints(TWODIM, TRIANGLE, u, v, w, wght);
|
gauss.GetGaussPoints(TWODIM, TRIANGLE, u, v, w, wght);
|
||||||
|
|
||||||
BF bfN;
|
BF bfN;
|
||||||
bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, BF_LINEFUNC);
|
bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder);
|
||||||
const int nbrSBC = phy->GetNbrSBC();
|
const int nbrSBC = phy->GetNbrSBC();
|
||||||
for (int s = 0; s < nbrSBC; s++)
|
for (int s = 0; s < nbrSBC; s++)
|
||||||
{
|
{
|
||||||
|
|
@ -207,7 +496,7 @@ void assembleSBCByType(
|
||||||
for (int n = 0; n < triIndices.size(); n++)
|
for (int n = 0; n < triIndices.size(); n++)
|
||||||
{
|
{
|
||||||
assembleSBCFace(mesh, matLib, tripleA, B, k0, u, v, wght, nbrGP, bfN,
|
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;
|
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
|
} // namespace
|
||||||
|
|
||||||
void OpticsFEM_3D_Scatter::Assemble_SBC()
|
void OpticsFEM_3D_Scatter::Assemble_SBC()
|
||||||
{
|
{
|
||||||
const double k0 = 2.0 * Pi / _mSolver->GetLda0();
|
const double k0 = 2.0 * Pi / _mSolver->GetLda0();
|
||||||
|
|
||||||
|
// SBCType=0: γ=ik₀,q=0 → 只修正 A
|
||||||
assembleSBCByType(_mMesh, _mMatLib, _mPhy, _mTripleA_complex, _mB_complex,
|
assembleSBCByType(_mMesh, _mMatLib, _mPhy, _mTripleA_complex, _mB_complex,
|
||||||
k0, 0, false, Vector3cd::Zero());
|
k0, 0, false, Vector3cd::Zero());
|
||||||
|
|
||||||
|
|
@ -233,9 +630,13 @@ void OpticsFEM_3D_Scatter::Assemble_SBC()
|
||||||
continue;
|
continue;
|
||||||
std::string sx, sy, sz;
|
std::string sx, sy, sz;
|
||||||
_mPhy->GetEinc(s, sx, sy, sz);
|
_mPhy->GetEinc(s, sx, sy, sz);
|
||||||
Einc(0) = std::complex<double>(std::stod(sx), 0.0);
|
mup::ParserX parser(mup::pckALL_COMPLEX);
|
||||||
Einc(1) = std::complex<double>(std::stod(sy), 0.0);
|
parser.EnableAutoCreateVar(true);
|
||||||
Einc(2) = std::complex<double>(std::stod(sz), 0.0);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
assembleSBCByType(_mMesh, _mMatLib, _mPhy, _mTripleA_complex, _mB_complex,
|
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()
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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) dS(PML 面或 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;
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include"Assemble_Base.h"
|
#include"Assemble_Base.h"
|
||||||
|
#include"Nedelec3D_Util.h"
|
||||||
#include"../function/BF.h"
|
#include"../function/BF.h"
|
||||||
#include"../function/Gauss.h"
|
#include"../function/Gauss.h"
|
||||||
#include"../common/define.h"
|
#include"../common/define.h"
|
||||||
|
|
@ -7,6 +8,8 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <complex>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Eigen;
|
using namespace Eigen;
|
||||||
|
|
@ -285,12 +288,15 @@ void OpticsFEM_3D_Scatter::Assemble_WaveEquation()
|
||||||
{
|
{
|
||||||
//physic
|
//physic
|
||||||
double k0 = 2 * Pi / _mSolver->GetLda0();
|
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
|
//init of Gauss point
|
||||||
Gauss gauss;
|
Gauss gauss;
|
||||||
int NbrGuassPoints;
|
int NbrGuassPoints;
|
||||||
double* u, * v, * w, * wght;
|
double* u, * v, * w, * wght;
|
||||||
NbrGuassPoints = gauss.GetNbrGaussPoints(THREEDIM, TETRAHEDRON, BF_LINEFUNC * 2);
|
NbrGuassPoints = gauss.GetNbrGaussPoints(THREEDIM, TETRAHEDRON, gaussVol);
|
||||||
u = new double[NbrGuassPoints];
|
u = new double[NbrGuassPoints];
|
||||||
v = new double[NbrGuassPoints];
|
v = new double[NbrGuassPoints];
|
||||||
w = new double[NbrGuassPoints];
|
w = new double[NbrGuassPoints];
|
||||||
|
|
@ -305,8 +311,8 @@ void OpticsFEM_3D_Scatter::Assemble_WaveEquation()
|
||||||
BF BF_Nedelec, BF_Curl_Nedelec;
|
BF BF_Nedelec, BF_Curl_Nedelec;
|
||||||
int dof;
|
int dof;
|
||||||
Vector3d** E, ** curlE;
|
Vector3d** E, ** curlE;
|
||||||
dof = BF_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, BF_LINEFUNC);
|
dof = BF_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder);
|
||||||
BF_Curl_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_CURL_NEDELEC, BF_LINEFUNC);
|
BF_Curl_Nedelec.GetNbrBF(THREEDIM, TETRAHEDRON, BF_CURL_NEDELEC, bfOrder);
|
||||||
E = new Vector3d * [NbrGuassPoints];
|
E = new Vector3d * [NbrGuassPoints];
|
||||||
curlE = new Vector3d * [NbrGuassPoints];
|
curlE = new Vector3d * [NbrGuassPoints];
|
||||||
for (int i = 0; i < NbrGuassPoints; i++)
|
for (int i = 0; i < NbrGuassPoints; i++)
|
||||||
|
|
@ -331,6 +337,18 @@ void OpticsFEM_3D_Scatter::Assemble_WaveEquation()
|
||||||
InvJac = Jac.inverse();
|
InvJac = Jac.inverse();
|
||||||
TJac = Jac.transpose() / Jac.determinant();
|
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
|
//basis function
|
||||||
for (int i = 0; i < NbrGuassPoints; i++)
|
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]);
|
BF_Nedelec.GetValueBF(j + 1, u[i], v[i], w[i], E[i][j]);
|
||||||
E[i][j] = InvJac * 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]);
|
BF_Curl_Nedelec.GetValueBF(j + 1, u[i], v[i], w[i], curlE[i][j]);
|
||||||
curlE[i][j] = TJac * curlE[i][j];
|
curlE[i][j] = TJac * curlE[i][j];
|
||||||
|
if (scalePortEdges)
|
||||||
|
curlE[i][j] *= edgeLen[j];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -374,44 +396,41 @@ void OpticsFEM_3D_Scatter::Assemble_WaveEquation()
|
||||||
|
|
||||||
//mapping
|
//mapping
|
||||||
VectorXi MappingIndex = VectorXi::Zero(dof);
|
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++)
|
for (int i = 0; i < dof; i++)
|
||||||
MappingIndex(i) = _mMesh->GetEdgeOfTet(n, i);
|
MappingIndex(i) = mapBuf[static_cast<size_t>(i)];
|
||||||
|
|
||||||
//submatrix
|
//submatrix — long double accumulate (short cavity is ill-conditioned;
|
||||||
MatrixXcd Se, Te;
|
// tiny imag sign flips from double cancellation destroy the solution).
|
||||||
Se = MatrixXcd::Zero(dof, dof);
|
auto bilinearRealCplx = [](const Vector3d& a, const Vector3cd& b) {
|
||||||
Te = MatrixXcd::Zero(dof, dof);
|
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 i = 0; i < dof; i++)
|
||||||
|
{
|
||||||
for (int j = 0; j < dof; j++)
|
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++)
|
for (int k = 0; k < NbrGuassPoints; k++)
|
||||||
{
|
{
|
||||||
Se(i, j) = Se(i, j) + wght[k] * DetJac * curlE[k][i].dot(invMur * curlE[k][j]);
|
const Vector3cd murCurl = 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 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
|
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
|
const std::complex<long double> aLd = seLd - teLd;
|
||||||
|
const complex<double> aij(
|
||||||
|
static_cast<double>(aLd.real()),
|
||||||
|
static_cast<double>(aLd.imag()));
|
||||||
|
if (_mIsReal)
|
||||||
|
_mTripleA_real.push_back(Triplet<double>(MappingIndex(i), MappingIndex(j), aij.real()));
|
||||||
|
else
|
||||||
|
_mTripleA_complex.push_back(Triplet<complex<double>>(MappingIndex(i), MappingIndex(j), aij));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
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)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -426,6 +445,12 @@ void OpticsFEM_3D_Scatter::Assemble_WaveEquation()
|
||||||
delete[] E, curlE;
|
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()
|
void OpticsFEM_3D_Scatter2::Assemble_WaveEquation()
|
||||||
{
|
{
|
||||||
//physic
|
//physic
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include"../common/define.h"
|
#include"../common/define.h"
|
||||||
#include"Assemble_Base.h"
|
#include"Assemble_Base.h"
|
||||||
|
#include"Nedelec3D_Util.h"
|
||||||
|
|
||||||
#include "../Eigen/SparseLU"
|
#include "../Eigen/SparseLU"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
@ -68,7 +69,7 @@ void OpticsFEM_2D_EigenMode::Assemble()
|
||||||
_mB_complex.setFromTriplets(_mTripleB_complex.begin(), _mTripleB_complex.end());
|
_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();
|
this->Assemble_PEC_PBC();
|
||||||
|
|
||||||
|
|
@ -188,7 +189,7 @@ void OpticsFEM_2D_EigenFreq::Assemble()
|
||||||
|
|
||||||
//????????????????????????
|
//????????????????????????
|
||||||
_mElectricType = _mSolver->GetElectricType();
|
_mElectricType = _mSolver->GetElectricType();
|
||||||
if ((_mPhy->GetNbrPEC() + _mPhy->GetNbrPBC() > 0))
|
if ((_mPhy->GetNbrPEC() + _mPhy->GetNbrPBCGroups() > 0))
|
||||||
{
|
{
|
||||||
this->Assemble_PEC_PBC();
|
this->Assemble_PEC_PBC();
|
||||||
}
|
}
|
||||||
|
|
@ -403,7 +404,7 @@ void OpticsFEM_2D_Scatter::Assemble()
|
||||||
this->Assemble_PEC_ELE();
|
this->Assemble_PEC_ELE();
|
||||||
|
|
||||||
//PBC
|
//PBC
|
||||||
if (_mPhy->GetNbrPBC() > 0)
|
if (_mPhy->GetNbrPBCGroups() > 0)
|
||||||
{
|
{
|
||||||
this->Assemble_PBC();
|
this->Assemble_PBC();
|
||||||
}
|
}
|
||||||
|
|
@ -516,7 +517,8 @@ void OpticsFEM_2D_Scatter::Post(string file)
|
||||||
*/
|
*/
|
||||||
void OpticsFEM_3D_EigenFreq::Assemble()
|
void OpticsFEM_3D_EigenFreq::Assemble()
|
||||||
{
|
{
|
||||||
_mDof = _mMesh->GetNbrEdge();
|
const int elementOrder = _mPhy->GetElementOrder();
|
||||||
|
_mDof = Nedelec3D::globalDofCount(_mMesh, elementOrder);
|
||||||
_mTripleA_real.clear();
|
_mTripleA_real.clear();
|
||||||
_mTripleB_real.clear();
|
_mTripleB_real.clear();
|
||||||
_mTripleA_complex.clear();
|
_mTripleA_complex.clear();
|
||||||
|
|
@ -552,7 +554,7 @@ void OpticsFEM_3D_EigenFreq::Assemble()
|
||||||
_mB_complex.setFromTriplets(_mTripleB_complex.begin(), _mTripleB_complex.end());
|
_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();
|
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
|
std::cout << "[OpticsFEM_3D_EigenFreq] Assemble done: DOF=" << _mDof
|
||||||
<< " nnz(A)=" << (_mIsReal ? _mA_real.nonZeros() : _mA_complex.nonZeros())
|
<< " nnz(A)=" << (_mIsReal ? _mA_real.nonZeros() : _mA_complex.nonZeros())
|
||||||
<< " nnz(B)=" << (_mIsReal ? _mB_real.nonZeros() : _mB_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()
|
void OpticsFEM_3D_Scatter::Assemble()
|
||||||
{
|
{
|
||||||
|
_mFreeDofIndices.clear();
|
||||||
|
_mPortExtraDof = 0;
|
||||||
_mIsReal = _mMatLib->IsReal();
|
_mIsReal = _mMatLib->IsReal();
|
||||||
if (_mIsReal)
|
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();
|
this->Assemble_WaveEquation();
|
||||||
|
|
||||||
|
|
@ -616,6 +648,9 @@ void OpticsFEM_3D_Scatter::Assemble()
|
||||||
if (_mPhy->GetNbrSBC() > 0)
|
if (_mPhy->GetNbrSBC() > 0)
|
||||||
this->Assemble_SBC();
|
this->Assemble_SBC();
|
||||||
|
|
||||||
|
if (_mPhy->GetNbrBELE() > 0)
|
||||||
|
this->Assemble_BELE();
|
||||||
|
|
||||||
if (_mIsReal)
|
if (_mIsReal)
|
||||||
{
|
{
|
||||||
_mA_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(_mDof, _mDof);
|
_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 = Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>(_mDof, _mDof);
|
||||||
_mP_complex.setIdentity();
|
_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()
|
void OpticsFEM_3D_Scatter::Run()
|
||||||
|
|
@ -644,6 +715,119 @@ void OpticsFEM_3D_Scatter::Run()
|
||||||
_mSolver->GetRealFlag(_mIsReal);
|
_mSolver->GetRealFlag(_mIsReal);
|
||||||
_mSolver->SetParam(&_mA_complex, &_mB_complex, &_mP_complex);
|
_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);
|
_mSolver->Run(&_mX);
|
||||||
|
|
||||||
if (!_mIsReal && _mB_complex.size() > 0)
|
if (!_mIsReal && _mB_complex.size() > 0)
|
||||||
|
|
@ -663,8 +847,33 @@ void OpticsFEM_3D_Scatter::Run()
|
||||||
void OpticsFEM_3D_Scatter::Post(string file)
|
void OpticsFEM_3D_Scatter::Post(string file)
|
||||||
{
|
{
|
||||||
_mPost->GetMesh(_mMesh);
|
_mPost->GetMesh(_mMesh);
|
||||||
|
_mPost->GetPhy(_mPhy);
|
||||||
_mPost->GetResult(&_mX);
|
_mPost->GetResult(&_mX);
|
||||||
_mPost->GetElectric();
|
_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);
|
this->Test_OutputMatrix(file);
|
||||||
_mPost->OutputData(file);
|
_mPost->OutputData(file);
|
||||||
std::cout << "[OpticsFEM] post to " << file << std::endl;
|
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 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 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");
|
std::ofstream outX_real(prefix + "X_real.txt"), outX_imag(prefix + "X_imag.txt");
|
||||||
|
outAv << std::setprecision(12);
|
||||||
|
|
||||||
if (_mIsReal)
|
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;
|
for (Eigen::SparseMatrix<double, Eigen::RowMajor>::InnerIterator it(_mA_real, col); it; ++it)
|
||||||
outAj << _mTripleA_real[k].col() << std::endl;
|
{
|
||||||
outAv << _mTripleA_real[k].value() << std::endl;
|
outAi << it.row() << std::endl;
|
||||||
|
outAj << it.col() << std::endl;
|
||||||
|
outAv << it.value() << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < _mB_real.size(); i++)
|
for (int i = 0; i < _mB_real.size(); i++)
|
||||||
outBv_real << _mB_real(i) << std::endl;
|
outBv_real << _mB_real(i) << std::endl;
|
||||||
|
|
@ -693,11 +906,15 @@ void OpticsFEM_3D_Scatter::Test_OutputMatrix(const std::string& outDir)
|
||||||
}
|
}
|
||||||
else
|
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;
|
for (Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>::InnerIterator it(_mA_complex, col); it; ++it)
|
||||||
outAj << _mTripleA_complex[k].col() << std::endl;
|
{
|
||||||
outAv << _mTripleA_complex[k].value() << std::endl;
|
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++)
|
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
|
3D_Scatter2
|
||||||
prism
|
prism
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include "OpticsFEM_3D_SBC_Scatter.h"
|
#include "OpticsFEM_3D_SBC_Scatter.h"
|
||||||
|
#include "SBC_NormalUtil.h"
|
||||||
#include "../function/BF.h"
|
#include "../function/BF.h"
|
||||||
#include "../function/Gauss.h"
|
#include "../function/Gauss.h"
|
||||||
#include "../common/define.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;
|
if (heron < 0.0) heron = 0.0;
|
||||||
const double integCoe = 0.25 * std::sqrt(heron);
|
const double integCoe = 0.25 * std::sqrt(heron);
|
||||||
|
|
||||||
Vector3d normal;
|
double tetX[4], tetY[4];
|
||||||
_mMesh->GetNormOfFace(domain, normal);
|
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);
|
int tetDom = _mMesh->GetDomainOfTet(numTet);
|
||||||
const double eps = (tetDom > 0 && tetDom <= _mEps.size()) ? _mEps(tetDom - 1) : _mEps(0);
|
const double eps = (tetDom > 0 && tetDom <= _mEps.size()) ? _mEps(tetDom - 1) : _mEps(0);
|
||||||
|
|
|
||||||
|
|
@ -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 13–20) 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());
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -21,8 +21,9 @@ void MaterialLib::SetMat(int num, string name, int nbrDomain, int* domain, int m
|
||||||
{
|
{
|
||||||
_mMat[num].SetEpsr(epsr[0]);
|
_mMat[num].SetEpsr(epsr[0]);
|
||||||
_mMat[num].SetMur(mur[0]);
|
_mMat[num].SetMur(mur[0]);
|
||||||
_mMat[num].SetSimga(sigma[0]);
|
|
||||||
_mMat[num].SetNK(sqrt(epsr[0]).real(), sqrt(epsr[0]).imag());
|
_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].SetChihe(chihe[0], chihe[0], chihe[0]);
|
||||||
_mMat[num].SetChieh(chieh[0], chieh[0], chieh[0]);
|
_mMat[num].SetChieh(chieh[0], chieh[0], chieh[0]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,21 @@ int Mesh_3D::GetEdge(int numEdge, int numVertex)
|
||||||
return _mEdge(numEdge, 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()
|
int Mesh_3D::GetNbrTet()
|
||||||
{
|
{
|
||||||
return _mNbrTet;
|
return _mNbrTet;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include"../Eigen/Dense"
|
#include"../Eigen/Dense"
|
||||||
#include<string>
|
#include<string>
|
||||||
|
#include<vector>
|
||||||
|
|
||||||
class Mesh_2D
|
class Mesh_2D
|
||||||
{
|
{
|
||||||
|
|
@ -69,6 +70,10 @@ public:
|
||||||
int GetNbrEdge();
|
int GetNbrEdge();
|
||||||
int GetEdge(int numEdge, int numVertex);
|
int GetEdge(int numEdge, int numVertex);
|
||||||
|
|
||||||
|
int GetNbrFace();
|
||||||
|
int GetFace(int numFace, int numVertex);
|
||||||
|
int GetFaceOfTet(int numTet, int numFace);
|
||||||
|
|
||||||
int GetNbrTet();
|
int GetNbrTet();
|
||||||
int GetTet(int numTet, int numVertex);
|
int GetTet(int numTet, int numVertex);
|
||||||
int GetEdgeOfTet(int numTet, int numEdge);
|
int GetEdgeOfTet(int numTet, int numEdge);
|
||||||
|
|
@ -94,16 +99,33 @@ public:
|
||||||
Eigen::VectorXi& triIndex, Eigen::VectorXi& triNum);
|
Eigen::VectorXi& triIndex, Eigen::VectorXi& triNum);
|
||||||
void GetIndexOfPBC(Eigen::MatrixXi PBC, Eigen::VectorXcd PBCData,
|
void GetIndexOfPBC(Eigen::MatrixXi PBC, Eigen::VectorXcd PBCData,
|
||||||
Eigen::VectorXi& srcEdgeIndex, Eigen::VectorXi& dstEdgeIndex, Eigen::VectorXcd& edgePhi);
|
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 GetNormOfFace(int domain, Eigen::Vector3d& norm);
|
||||||
void GetTriIndicesOfDomain(int domain, Eigen::VectorXi& triIndices);
|
void GetTriIndicesOfDomain(int domain, Eigen::VectorXi& triIndices);
|
||||||
|
|
||||||
|
void BuildFaceTopology();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int _mNbrVertex = 0;
|
int _mNbrVertex = 0;
|
||||||
Eigen::MatrixXd _mVertex;//n*3
|
Eigen::MatrixXd _mVertex;//n*3
|
||||||
int _mNbrEdge = 0;
|
int _mNbrEdge = 0;
|
||||||
Eigen::MatrixXi _mEdge;//n*2
|
Eigen::MatrixXi _mEdge;//n*2
|
||||||
|
|
||||||
|
int _mNbrFace = 0;
|
||||||
|
Eigen::MatrixXi _mFace;//n*3
|
||||||
|
Eigen::MatrixXi _mFaceOfTet;//n*4
|
||||||
|
|
||||||
int _mNbrTet = 0;
|
int _mNbrTet = 0;
|
||||||
Eigen::MatrixXi _mTet;//n*4
|
Eigen::MatrixXi _mTet;//n*4
|
||||||
Eigen::MatrixXi _mEdgeOfTet;//n*4
|
Eigen::MatrixXi _mEdgeOfTet;//n*4
|
||||||
|
|
@ -126,6 +148,8 @@ private:
|
||||||
|
|
||||||
Eigen::MatrixXi _mCopyOfTri;//n*2
|
Eigen::MatrixXi _mCopyOfTri;//n*2
|
||||||
Eigen::MatrixXi _mCopyOfQuad;//n*2
|
Eigen::MatrixXi _mCopyOfQuad;//n*2
|
||||||
|
int _mNbrCopyOfTri = 0;
|
||||||
|
int _mNbrCopyOfQuad = 0;
|
||||||
|
|
||||||
Eigen::MatrixXd _mNormOfFace;// maxDomain+1 * 3, indexed by domain id
|
Eigen::MatrixXd _mNormOfFace;// maxDomain+1 * 3, indexed by domain id
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
#include"Mesh_Base.h"
|
#include"Mesh_Base.h"
|
||||||
|
#include"../common/util.h"
|
||||||
#include<algorithm>
|
#include<algorithm>
|
||||||
|
#include<array>
|
||||||
#include<complex>
|
#include<complex>
|
||||||
#include<iostream>
|
#include<iostream>
|
||||||
|
#include<cmath>
|
||||||
|
#include<vector>
|
||||||
|
|
||||||
//get edges with edge's flag of domain
|
//get edges with edge's flag of domain
|
||||||
void Mesh_2D::GetIndexOfDomain(Eigen::VectorXi domain, Eigen::VectorXi& nodeIndex, Eigen::VectorXi& edgeIndex)
|
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=|‖v1−v3‖−dl|, l2=|‖v2−v4‖−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_src−T(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,
|
void Mesh_2D::GetIndexOfPBC(Eigen::MatrixXi PBC, Eigen::VectorXcd PBCData,
|
||||||
Eigen::VectorXi& srcNodeIndex, Eigen::VectorXi& dstNodeIndex, Eigen::VectorXcd& nodePhi,
|
Eigen::VectorXi& srcNodeIndex, Eigen::VectorXi& dstNodeIndex, Eigen::VectorXcd& nodePhi,
|
||||||
Eigen::VectorXi& srcEdgeIndex, Eigen::VectorXi& dstEdgeIndex, Eigen::VectorXcd& edgePhi)
|
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] = GetEdge(Tage_numEdge, 0);
|
||||||
tempDstNodeIndex[i * 2 + 1] = GetEdge(Tage_numEdge, 1);
|
tempDstNodeIndex[i * 2 + 1] = GetEdge(Tage_numEdge, 1);
|
||||||
}
|
}
|
||||||
//根据方向处理 DstNodeIndex 和 edgePhi
|
//????????? DstNodeIndex ?? edgePhi
|
||||||
//未考虑rotational bloch bounadry
|
//??????rotational bloch bounadry
|
||||||
for (int i = 0; i < NbrEdges; i++)
|
for (int i = 0; i < NbrEdges; i++)
|
||||||
{
|
{
|
||||||
for (int j = 0; j < _mCopyOfVertex.rows(); j++)
|
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;
|
dstEdgeIndex(i) = Tage_numEdge;
|
||||||
edgePhi(i) = tempEdgePhi[i];
|
edgePhi(i) = tempEdgePhi[i];
|
||||||
}
|
}
|
||||||
//处理重叠
|
//???????
|
||||||
Eigen::VectorXi overlapIndex1, overlapIndex2;
|
Eigen::VectorXi overlapIndex1, overlapIndex2;
|
||||||
overlapIndex1 = Eigen::VectorXi::Zero(NbrEdges * 2);
|
overlapIndex1 = Eigen::VectorXi::Zero(NbrEdges * 2);
|
||||||
overlapIndex2 = 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,
|
void Mesh_3D::GetIndexOfPBC(Eigen::MatrixXi PBC, Eigen::VectorXcd PBCData,
|
||||||
Eigen::VectorXi& srcEdgeIndex, Eigen::VectorXi& dstEdgeIndex, Eigen::VectorXcd& edgePhi)
|
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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -331,6 +331,24 @@ void Mesh_3D::GetMesh(std::string str, std::string jstr)
|
||||||
setDefaultNorm(14, 1.0, 0.0, 0.0);
|
setDefaultNorm(14, 1.0, 0.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
fd.close();
|
||||||
|
|
||||||
|
BuildFaceTopology();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include"Phy_Base.h"
|
#include"Phy_Base.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
Phy_WaveOpticsModel::Phy_WaveOpticsModel()
|
Phy_WaveOpticsModel::Phy_WaveOpticsModel()
|
||||||
{
|
{
|
||||||
|
|
@ -7,8 +8,7 @@ Phy_WaveOpticsModel::Phy_WaveOpticsModel()
|
||||||
|
|
||||||
_mPEC = Eigen::VectorXi::Zero(0);
|
_mPEC = Eigen::VectorXi::Zero(0);
|
||||||
|
|
||||||
_mPBC = Eigen::MatrixXi::Zero(0, 2);
|
_mPBCGroups.clear();
|
||||||
_mPBCData = Eigen::VectorXcd(0);
|
|
||||||
|
|
||||||
_mPML = Eigen::VectorXi::Zero(0);
|
_mPML = Eigen::VectorXi::Zero(0);
|
||||||
_mPMLData = Eigen::MatrixXd::Zero(0, 6);
|
_mPMLData = Eigen::MatrixXd::Zero(0, 6);
|
||||||
|
|
@ -31,19 +31,113 @@ int Phy_WaveOpticsModel::GetPECDomain(int num)
|
||||||
|
|
||||||
int Phy_WaveOpticsModel::GetNbrPBC()
|
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)
|
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)
|
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)
|
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()
|
int Phy_WaveOpticsModel::GetNbrPML()
|
||||||
|
|
@ -165,6 +259,13 @@ void Phy_WaveOpticsModel::GetCurlEb(std::string& curlEbx, std::string& curlEby,
|
||||||
curlEbz = _mcurlEbz;
|
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)
|
void Phy_WaveOpticsModel::GetE0(std::string& E0x, std::string& E0y, std::string& E0z,int num)
|
||||||
{
|
{
|
||||||
E0x = _mE0x[num];
|
E0x = _mE0x[num];
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,25 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include"../Eigen/Dense"
|
#include"../Eigen/Dense"
|
||||||
|
#include"PortModeData.h"
|
||||||
|
|
||||||
#include<complex>
|
#include<complex>
|
||||||
#include<string>
|
#include<string>
|
||||||
|
#include<vector>
|
||||||
|
|
||||||
class Phy_WaveOpticsModel
|
class Phy_WaveOpticsModel
|
||||||
{
|
{
|
||||||
public:
|
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();
|
Phy_WaveOpticsModel();
|
||||||
|
|
||||||
void GetPhy();
|
void GetPhy();
|
||||||
|
|
@ -16,9 +28,20 @@ public:
|
||||||
int GetPECDomain(int num);
|
int GetPECDomain(int num);
|
||||||
|
|
||||||
int GetNbrPBC();
|
int GetNbrPBC();
|
||||||
|
int GetNbrPBCGroups() const;
|
||||||
|
int GetNbrPBCInGroup(int group) const;
|
||||||
int GetSrcDomain(int num);
|
int GetSrcDomain(int num);
|
||||||
int GetDstDomain(int num);
|
int GetDstDomain(int num);
|
||||||
std::complex<double> GetPBCPhi(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 GetNbrPML();
|
||||||
int GetPMLEdge(int num);
|
int GetPMLEdge(int num);
|
||||||
|
|
@ -37,6 +60,7 @@ public:
|
||||||
int GetBELEDomain(int num);
|
int GetBELEDomain(int num);
|
||||||
void GetEb(std::string& Ebx, std::string& Eby, std::string& Ebz);
|
void GetEb(std::string& Ebx, std::string& Eby, std::string& Ebz);
|
||||||
void GetCurlEb(std::string& curlEbx, std::string& curlEby, std::string& curlEbz);
|
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 GetNbrElE();
|
||||||
int GetELEDomain(int num);
|
int GetELEDomain(int num);
|
||||||
|
|
@ -67,53 +91,62 @@ public:
|
||||||
void GetPortEincCurl(std::string& EincCurlx, std::string& EincCurly, std::string& EincCurlz);
|
void GetPortEincCurl(std::string& EincCurlx, std::string& EincCurly, std::string& EincCurlz);
|
||||||
void GetPortEoutCurl(std::string& EoutCurlx, std::string& EoutCurly, std::string& EoutCurlz);
|
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();
|
int GetBeamState();
|
||||||
void GetBeamDir(double& kx,double& ky);
|
void GetBeamDir(double& kx,double& ky);
|
||||||
|
|
||||||
void Test_ReadData(std::string str);
|
void Test_ReadData(std::string str);
|
||||||
|
|
||||||
|
int GetElementOrder() const { return _mElementOrder; }
|
||||||
|
|
||||||
private:
|
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 周期边界条件
|
std::vector<PbcGroupConfig> _mPBCGroups;
|
||||||
Eigen::VectorXcd _mPBCData;//n*1
|
|
||||||
|
|
||||||
Eigen::VectorXi _mPML;//n*1 完美匹配层
|
Eigen::VectorXi _mPML;//n*1 ????????
|
||||||
Eigen::VectorXi _mPMLType;
|
Eigen::VectorXi _mPMLType;
|
||||||
Eigen::VectorXi _mPMLEdge;
|
Eigen::VectorXi _mPMLEdge;
|
||||||
double _mPMLLda;//典型波长
|
double _mPMLLda;//???????
|
||||||
Eigen::MatrixXd _mPMLData;//n*6 x0 1/dx y0 1/dy z0 1/dz 无d则1/d=0
|
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
|
Eigen::MatrixXd _mSBCDir; //n*3
|
||||||
std::string* _mEincx, * _mEincy, * _mEincz;
|
std::string* _mEincx, * _mEincy, * _mEincz;
|
||||||
|
|
||||||
Eigen::VectorXi _mELE;//n*1 强加电场
|
Eigen::VectorXi _mELE;//n*1 ????T
|
||||||
std::string* _mE0x, *_mE0y, *_mE0z;
|
std::string* _mE0x, *_mE0y, *_mE0z;
|
||||||
|
|
||||||
Eigen::VectorXi _mMAG;//n*1 强加磁场
|
Eigen::VectorXi _mMAG;//n*1 ?????
|
||||||
std::string* _mH0x, *_mH0y, *_mH0z;
|
std::string* _mH0x, *_mH0y, *_mH0z;
|
||||||
|
|
||||||
Eigen::VectorXi _mSCD;//n*1 表面电流密度
|
Eigen::VectorXi _mSCD;//n*1 ??????????
|
||||||
std::string* _mJ0x, *_mJ0y, *_mJ0z;
|
std::string* _mJ0x, *_mJ0y, *_mJ0z;
|
||||||
|
|
||||||
Eigen::VectorXi _mMPD; //n*3 磁偶极子
|
Eigen::VectorXi _mMPD; //n*3 ???????
|
||||||
Eigen::MatrixXd _mMPDData; //n*3
|
Eigen::MatrixXd _mMPDData; //n*3
|
||||||
|
|
||||||
Eigen::VectorXi _mEPD; //n*3 电偶极子
|
Eigen::VectorXi _mEPD; //n*3 ???????
|
||||||
Eigen::MatrixXd _mEPDData; //n*3
|
Eigen::MatrixXd _mEPDData; //n*3
|
||||||
|
|
||||||
Eigen::VectorXi _mBELE; //n*1 背景场
|
Eigen::VectorXi _mBELE; //n*1 ??????
|
||||||
std::string _mEbx, _mEby, _mEbz;
|
std::string _mEbx, _mEby, _mEbz;
|
||||||
std::string _mcurlEbx, _mcurlEby, _mcurlEbz;
|
std::string _mcurlEbx, _mcurlEby, _mcurlEbz;
|
||||||
|
std::string _mcurlcurlEbx, _mcurlcurlEby, _mcurlcurlEbz;
|
||||||
|
|
||||||
Eigen::VectorXi _mPortinc;//n*1 端口边界条件
|
Eigen::VectorXi _mPortinc;//n*1 ?????????
|
||||||
Eigen::VectorXi _mPortout;//n*1 端口边界条件
|
Eigen::VectorXi _mPortout;//n*1 ?????????
|
||||||
std::string* _mPortEinc, * _mPortEout, * _mPortEincCurl, * _mPortEoutCurl; //入射场及出射场定义
|
std::string* _mPortEinc, * _mPortEout, * _mPortEincCurl, * _mPortEoutCurl; //??????????????
|
||||||
|
PortModeLibrary _mPortModes;
|
||||||
|
|
||||||
int _mBeam = 0;//0为不启用波束 1为启用波束
|
int _mBeam = 0;//0??????????? 1?????????
|
||||||
double _mkx = 0.;//波束方向kx
|
double _mkx = 0.;//????????kx
|
||||||
double _mky = 0;//波束方向ky
|
double _mky = 0;//????????ky
|
||||||
|
int _mElementOrder = 1;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -73,6 +73,19 @@ void Post_3D_EigenFreq::GetMesh(Mesh_3D* mesh)
|
||||||
_mMesh = 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
|
3D_Scatter
|
||||||
*/
|
*/
|
||||||
|
|
@ -81,6 +94,11 @@ void Post_3D_Scatter::GetMesh(Mesh_3D* mesh)
|
||||||
_mMesh = mesh;
|
_mMesh = mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Post_3D_Scatter::GetPhy(Phy_WaveOpticsModel* phy)
|
||||||
|
{
|
||||||
|
_mPhy = phy;
|
||||||
|
}
|
||||||
|
|
||||||
void Post_3D_Scatter::GetResult(Eigen::VectorXcd* x)
|
void Post_3D_Scatter::GetResult(Eigen::VectorXcd* x)
|
||||||
{
|
{
|
||||||
_mX = x;
|
_mX = x;
|
||||||
|
|
|
||||||
|
|
@ -82,24 +82,40 @@ class Post_3D_EigenFreq
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void GetMesh(Mesh_3D* mesh);
|
void GetMesh(Mesh_3D* mesh);
|
||||||
|
void GetSolver(Solver_EigenFreq* solver);
|
||||||
private:
|
void GetResult(Eigen::MatrixXcd* x, Eigen::VectorXcd* lambda);
|
||||||
Mesh_3D* _mMesh;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Post_3D_Scatter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void GetMesh(Mesh_3D* mesh);
|
|
||||||
void GetResult(Eigen::VectorXcd* x);
|
|
||||||
|
|
||||||
void GetElectric();
|
void GetElectric();
|
||||||
void OutputData(std::string str);
|
void OutputData(std::string str);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Mesh_3D* _mMesh;
|
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* _mX;
|
||||||
|
|
||||||
Eigen::VectorXcd _mEx, _mEy, _mEz;
|
Eigen::VectorXcd _mEx, _mEy, _mEz;
|
||||||
Eigen::VectorXd _mNormE;
|
Eigen::VectorXd _mNormE;
|
||||||
|
Eigen::VectorXcd _mEbx, _mEby, _mEbz;
|
||||||
};
|
};
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include"Post_Base.h"
|
#include"Post_Base.h"
|
||||||
#include"../function/BF.h"
|
#include"../function/BF.h"
|
||||||
|
#include"../kernel/Nedelec3D_Util.h"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
@ -337,10 +338,15 @@ void Post_3D_Scatter::GetElectric()
|
||||||
_mNormE = Eigen::VectorXd::Zero(nbrVertex);
|
_mNormE = Eigen::VectorXd::Zero(nbrVertex);
|
||||||
Eigen::VectorXd nCnt = 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;
|
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::Vector3d vertex[4];
|
||||||
Eigen::Matrix3d Jac, InvJac;
|
Eigen::Matrix3d Jac, InvJac;
|
||||||
|
int map[20];
|
||||||
|
|
||||||
const double u0[4] = { 1.0, 0.0, 0.0, 0.0 };
|
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 v0[4] = { 0.0, 1.0, 0.0, 0.0 };
|
||||||
|
|
@ -359,16 +365,32 @@ void Post_3D_Scatter::GetElectric()
|
||||||
continue;
|
continue;
|
||||||
InvJac = Jac.inverse();
|
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++)
|
for (int j = 0; j < 4; j++)
|
||||||
{
|
{
|
||||||
Eigen::Vector3cd Esum = Eigen::Vector3cd::Zero();
|
Eigen::Vector3cd Esum = Eigen::Vector3cd::Zero();
|
||||||
for (int i = 0; i < 6; i++)
|
for (int i = 0; i < localDof; i++)
|
||||||
{
|
{
|
||||||
Eigen::Vector3d bf;
|
Eigen::Vector3d bf;
|
||||||
bfN.GetValueBF(i + 1, u0[j], v0[j], w0[j], bf);
|
bfN.GetValueBF(i + 1, u0[j], v0[j], w0[j], bf);
|
||||||
bf = InvJac * bf;
|
bf = InvJac * bf;
|
||||||
const int eid = _mMesh->GetEdgeOfTet(n, i);
|
if (_mPhy != nullptr && _mPhy->HasNumericPort() && elementOrder == 1)
|
||||||
const std::complex<double> xe = (*_mX)(eid);
|
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(0) += bf(0) * xe;
|
||||||
Esum(1) += bf(1) * xe;
|
Esum(1) += bf(1) * xe;
|
||||||
Esum(2) += bf(2) * xe;
|
Esum(2) += bf(2) * xe;
|
||||||
|
|
@ -393,4 +415,90 @@ void Post_3D_Scatter::GetElectric()
|
||||||
|
|
||||||
for (int i = 0; i < nbrVertex; i++)
|
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)));
|
_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)));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
#include "../parser/mpParser.h"
|
#include "../parser/mpParser.h"
|
||||||
#include"../Eigen/Sparse"
|
#include"../Eigen/Sparse"
|
||||||
#include"../Eigen/Dense"
|
#include"../Eigen/Dense"
|
||||||
|
|
@ -69,4 +70,54 @@ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,6 +105,37 @@ void Post_2D_Scatter::OutputData(std::string str)
|
||||||
outNormE.close();
|
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)
|
void Post_3D_Scatter::OutputData(std::string str)
|
||||||
{
|
{
|
||||||
EnsureOutputDir(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);
|
outEz.open(str + "/Ez", std::ios::out | std::ios::trunc);
|
||||||
outNormE.open(str + "/normE", std::ios::out | std::ios::trunc);
|
outNormE.open(str + "/normE", std::ios::out | std::ios::trunc);
|
||||||
|
|
||||||
outEx << "//";
|
outEx << "//" << std::endl;
|
||||||
outEy << "//";
|
outEy << "//" << std::endl;
|
||||||
outEz << "//";
|
outEz << "//" << std::endl;
|
||||||
outNormE << "//";
|
outNormE << "//" << std::endl;
|
||||||
for (int j = 0; j < _mEx.rows(); j++)
|
for (int j = 0; j < _mEx.rows(); j++)
|
||||||
{
|
{
|
||||||
outEx << _mEx(j).real() << " " << _mEx(j).imag() << std::endl;
|
outEx << _mEx(j).real() << " " << _mEx(j).imag() << std::endl;
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 261 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 281 KiB |
|
|
@ -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=5:Assemble → 导出 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=4(SBCmesh)", 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)
|
||||||
|
|
@ -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.m(PBC/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, "已完成(阶段 1–3)", 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, "待实施(阶段 4–6)", 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()
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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), "阶段1–3\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()
|
||||||
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 435 KiB |
|
After Width: | Height: | Size: 303 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
|
@ -3,32 +3,145 @@
|
||||||
"EletricType": 2,
|
"EletricType": 2,
|
||||||
"lambda": 0.8,
|
"lambda": 0.8,
|
||||||
"NbrBoundary": 14,
|
"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": {
|
"sbc": {
|
||||||
"Index": [1, 2, 3, 4, 5, 14],
|
"Index": [
|
||||||
"SBCType": [0, 0, 0, 1, 0, 0],
|
1,
|
||||||
"E0x": ["1", "1", "1", "1", "1", "1"],
|
2,
|
||||||
"E0y": ["0", "0", "0", "0", "0", "0"],
|
3,
|
||||||
"E0z": ["0", "0", "0", "0", "0", "0"],
|
4,
|
||||||
"kx": [0, 0, 0, 0, 0, 0],
|
5,
|
||||||
"ky": [0, 0, 0, 0, 0, 0],
|
14
|
||||||
"kz": [0, 0, 0, 0, 0, 0]
|
],
|
||||||
|
"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,
|
"NbrDomain": 2,
|
||||||
"domainType": [2, 2],
|
"domainType": [
|
||||||
"domainIndex": [0, 1],
|
2,
|
||||||
"matType": [0, 0],
|
2
|
||||||
"epsilonrR": [1.0, 1.5],
|
],
|
||||||
"epsilonrI": [0.0, 0.0],
|
"domainIndex": [
|
||||||
"murR": [1.0, 1.0],
|
0,
|
||||||
"murI": [0.0, 0.0],
|
1
|
||||||
"chiheR": [0.0, 0.0],
|
],
|
||||||
"chiehR": [0.0, 0.0],
|
"matType": [
|
||||||
"chiheI": [0.0, 0.0],
|
0,
|
||||||
"chiehI": [0.0, 0.0],
|
0
|
||||||
"sigma": [0.0, 0.0],
|
],
|
||||||
"n": [1.0, 1.0],
|
"epsilonrR": [
|
||||||
"k": [0.0, 0.0],
|
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",
|
"MeshFile": "SBCmesh.dat",
|
||||||
"OutFile": "./OutFile"
|
"OutFile": "./OutFile"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -15,10 +15,10 @@ using namespace std;
|
||||||
void Solver_EigenMode::GetSolver(std::string input)
|
void Solver_EigenMode::GetSolver(std::string input)
|
||||||
{
|
{
|
||||||
json temp = nlohmann::json::parse(input);
|
json temp = nlohmann::json::parse(input);
|
||||||
_mNbrMode = temp.at("NbrMode");//模式数目
|
_mNbrMode = temp.at("NbrMode");//ģʽ<EFBFBD><EFBFBD>Ŀ
|
||||||
_mSearchValue = temp.at("searchValue");//搜索值-有效折射率
|
_mSearchValue = temp.at("searchValue");//<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ-<2D><>Ч<EFBFBD><D0A7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
_mLda0 = temp.at("lambda0");//真空波长
|
_mLda0 = temp.at("lambda0");//<EFBFBD><EFBFBD>ղ<EFBFBD><EFBFBD><EFBFBD>
|
||||||
_mPreproType = 0;//重排序类型
|
_mPreproType = 0;//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
}
|
}
|
||||||
|
|
||||||
void Solver_EigenMode::GetRealFlag(bool flag)
|
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++)
|
for (int i = 0; i < _mNbrMode; i++)
|
||||||
{
|
{
|
||||||
lambda[0](i) = sqrt(-eigenValues[i]) / k0;
|
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++)
|
for (int j = 0; j < _mParam.A_n; j++)
|
||||||
tempX(j, i) = x_complex[i][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)
|
void Solver_EigenFreq::GetSolver(std::string input)
|
||||||
{
|
{
|
||||||
json temp = nlohmann::json::parse(input);
|
json temp = nlohmann::json::parse(input);
|
||||||
_mNbrMode = temp.at("NbrMode");//模式数目
|
_mNbrMode = temp.at("NbrMode");//ģʽ<EFBFBD><EFBFBD>Ŀ
|
||||||
_mSearchValue = temp.at("searchValue");//搜索值-本征频率
|
_mSearchValue = temp.at("searchValue");//<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ-<2D><><EFBFBD><EFBFBD>Ƶ<EFBFBD><C6B5>
|
||||||
_mPreproType = 0; //重排序类型
|
_mPreproType = 0; //<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
_mElectricType = temp.at("EletricType"); //求解电场类型 0-Et 1-Ez 2-E
|
_mElectricType = temp.at("EletricType"); //<EFBFBD><EFBFBD><EFBFBD>糡<EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0-Et 1-Ez 2-E
|
||||||
}
|
}
|
||||||
|
|
||||||
void Solver_EigenFreq::GetRealFlag(bool flag)
|
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)
|
void Solver_LdaDom::GetSolver(std::string input)
|
||||||
{
|
{
|
||||||
json temp = nlohmann::json::parse(input);
|
json temp = nlohmann::json::parse(input);
|
||||||
_mLda0 = temp.at("lambda");//真空波长
|
_mLda0 = temp.at("lambda");//<EFBFBD><EFBFBD>ղ<EFBFBD><EFBFBD><EFBFBD>
|
||||||
_mElectricType = temp.at("EletricType");//求解电场类型 0-Et 1-Ez 2-E
|
_mElectricType = temp.at("EletricType");//<EFBFBD><EFBFBD><EFBFBD>糡<EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0-Et 1-Ez 2-E
|
||||||
}
|
}
|
||||||
|
|
||||||
void Solver_LdaDom::GetSolver2(std::string input)
|
void Solver_LdaDom::GetSolver2(std::string input)
|
||||||
{
|
{
|
||||||
json temp = nlohmann::json::parse(input);
|
json temp = nlohmann::json::parse(input);
|
||||||
_mLda0 = 0.299792458/(double)temp.at("freq");//真空波长
|
_mLda0 = 0.299792458/(double)temp.at("freq");//<EFBFBD><EFBFBD>ղ<EFBFBD><EFBFBD><EFBFBD>
|
||||||
_mElectricType = temp.at("EletricType");//求解电场类型 0-Et 1-Ez 2-E
|
_mElectricType = temp.at("EletricType");//<EFBFBD><EFBFBD><EFBFBD>糡<EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0-Et 1-Ez 2-E
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,43 @@
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <fstream>
|
#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 rfilewrite(const char *name, int *data, int Norf);
|
||||||
static void cfilewrite(const char *name, int *data, int nzero);
|
static void cfilewrite(const char *name, int *data, int nzero);
|
||||||
static void datafilewrite(const char *name, double *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) {
|
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
|
// 将参数写入文件 canshu.txt
|
||||||
std::ofstream ofs;
|
std::ofstream ofs;
|
||||||
ofs.open("./complex_solver/canshu.txt", std::ios::out);
|
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_real;
|
||||||
delete[] B_imag;
|
delete[] B_imag;
|
||||||
|
|
||||||
system("cd ./complex_solver && ./complex");
|
RunComplexEigenExe();
|
||||||
|
|
||||||
// 读取特征值文件
|
// 读取特征值文件
|
||||||
double *eigenValues_real = new double[nums];
|
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) {
|
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
|
//将参数写入文件 canshu.txt
|
||||||
std::ofstream ofs;
|
std::ofstream ofs;
|
||||||
ofs.open("./real_solver/canshu.txt", std::ios::out);
|
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);
|
cfilewrite("./real_solver/B_Cindices.dat", B_Cindices, B_nzero);
|
||||||
datafilewrite("./real_solver/B_real.dat", d_B_real, B_nzero);
|
datafilewrite("./real_solver/B_real.dat", d_B_real, B_nzero);
|
||||||
|
|
||||||
system("cd ./real_solver && ./real");
|
RunRealEigenExe();
|
||||||
|
|
||||||
//读取特征值文件
|
//读取特征值文件
|
||||||
std::ifstream ifs_EigenValues;
|
std::ifstream ifs_EigenValues;
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,16 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#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())
|
if (!fd.is_open())
|
||||||
{
|
{
|
||||||
std::cout << "cannot open sbc3d.json" << std::endl;
|
std::cout << "cannot open " << jsonFile << std::endl;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
std::string str((std::istreambuf_iterator<char>(fd)), std::istreambuf_iterator<char>());
|
std::string str((std::istreambuf_iterator<char>(fd)), std::istreambuf_iterator<char>());
|
||||||
|
|
@ -18,7 +22,7 @@ int main()
|
||||||
OpticsFEMData data;
|
OpticsFEMData data;
|
||||||
data.test1 = 1;
|
data.test1 = 1;
|
||||||
data.test2 = 2;
|
data.test2 = 2;
|
||||||
data.data = (char*)str.data();
|
data.data = const_cast<char*>(str.c_str());
|
||||||
|
|
||||||
int ret = femAPI.OpticsFEM_All(data);
|
int ret = femAPI.OpticsFEM_All(data);
|
||||||
std::cout << "OpticsFEM_All returned " << ret << std::endl;
|
std::cout << "OpticsFEM_All returned " << ret << std::endl;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,92 @@
|
||||||
#include "../nlohmann/json.hpp"
|
#include "../nlohmann/json.hpp"
|
||||||
using json = nlohmann::json;
|
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 <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
|
@ -17,6 +103,10 @@ void Phy_WaveOpticsModel::Test_ReadData(string str)
|
||||||
{
|
{
|
||||||
json js = nlohmann::json::parse(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");
|
std::vector<int> BoundaryFlag = js.at("BoundaryFlag");
|
||||||
int nbrBoundary = js.at("NbrBoundary");
|
int nbrBoundary = js.at("NbrBoundary");
|
||||||
int nbrPEC, nbrPMC, nbrELE, nbrSBC, nbrPBC, nbrMAG, nbrSCD;
|
int nbrPEC, nbrPMC, nbrELE, nbrSBC, nbrPBC, nbrMAG, nbrSCD;
|
||||||
|
|
@ -54,8 +144,7 @@ void Phy_WaveOpticsModel::Test_ReadData(string str)
|
||||||
_mEincy = new std::string[nbrSBC];
|
_mEincy = new std::string[nbrSBC];
|
||||||
_mEincz = new std::string[nbrSBC];
|
_mEincz = new std::string[nbrSBC];
|
||||||
_mSBCDir = Eigen::MatrixXd(nbrSBC, 3);
|
_mSBCDir = Eigen::MatrixXd(nbrSBC, 3);
|
||||||
_mPBC = Eigen::MatrixXi(nbrPBC, 2);
|
_mPBCGroups.clear();
|
||||||
_mPBCData = Eigen::VectorXcd(nbrPBC);
|
|
||||||
_mMAG = Eigen::VectorXi(nbrMAG);
|
_mMAG = Eigen::VectorXi(nbrMAG);
|
||||||
_mSCD = Eigen::VectorXi(nbrSCD);
|
_mSCD = Eigen::VectorXi(nbrSCD);
|
||||||
_mPortEinc = new std::string[3];
|
_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> kx = sbc.at("kx");
|
||||||
std::vector<double> ky = sbc.at("ky");
|
std::vector<double> ky = sbc.at("ky");
|
||||||
std::vector<double> kz = sbc.at("kz");
|
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++)
|
for (int i = 0; i < nbrSBC; i++)
|
||||||
{
|
{
|
||||||
_mSBC(i, 0) = Index[i];
|
_mSBC(i, 0) = static_cast<int>(Index[i]);
|
||||||
_mSBC(i, 1) = SBCType[i];
|
_mSBC(i, 1) = static_cast<int>(SBCType[i]);
|
||||||
_mEincx[i] = E0x[i];
|
_mEincx[i] = E0x[i];
|
||||||
_mEincy[i] = E0y[i];
|
_mEincy[i] = E0y[i];
|
||||||
_mEincz[i] = E0z[i];
|
_mEincz[i] = E0z[i];
|
||||||
|
|
@ -169,21 +271,23 @@ void Phy_WaveOpticsModel::Test_ReadData(string str)
|
||||||
}
|
}
|
||||||
|
|
||||||
nbrPBC = nbrPBC / 2;
|
nbrPBC = nbrPBC / 2;
|
||||||
if (nbrPBC != 0)
|
if (js.contains("pbc"))
|
||||||
{
|
{
|
||||||
json pbc = js.at("pbc");
|
PbcGroupConfig group;
|
||||||
std::vector<int> srcIndex = pbc.at("srcIndex");
|
parsePbcBlock(js.at("pbc"), group);
|
||||||
std::vector<int> dstIndex = pbc.at("dstIndex");
|
if (group.pbc.rows() > 0)
|
||||||
std::vector<double> phiR = pbc.at("phiR");
|
_mPBCGroups.push_back(group);
|
||||||
std::vector<double> phiI = pbc.at("phiI");
|
}
|
||||||
std::vector<std::complex<double>> PBCData(nbrPBC);
|
if (js.contains("pbc2"))
|
||||||
for (int i = 0; i < nbrPBC; i++)
|
{
|
||||||
{
|
PbcGroupConfig group2;
|
||||||
_mPBC(i, 0) = srcIndex[i];
|
parsePbcBlock(js.at("pbc2"), group2);
|
||||||
_mPBC(i, 1) = dstIndex[i];
|
if (group2.pbc.rows() > 0)
|
||||||
PBCData[i] = std::complex<double>(phiR[i], phiI[i]);
|
_mPBCGroups.push_back(group2);
|
||||||
_mPBCData(i) = PBCData[i];
|
}
|
||||||
}
|
else if (nbrPBC != 0 && _mPBCGroups.empty())
|
||||||
|
{
|
||||||
|
// legacy: BoundaryFlag==4 pairs without explicit pbc block
|
||||||
}
|
}
|
||||||
|
|
||||||
if (js.contains("mpd"))
|
if (js.contains("mpd"))
|
||||||
|
|
@ -240,6 +344,12 @@ void Phy_WaveOpticsModel::Test_ReadData(string str)
|
||||||
_mcurlEbx = bele.at("curlEbx");
|
_mcurlEbx = bele.at("curlEbx");
|
||||||
_mcurlEby = bele.at("curlEby");
|
_mcurlEby = bele.at("curlEby");
|
||||||
_mcurlEbz = bele.at("curlEbz");
|
_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"))
|
if (js.contains("pml"))
|
||||||
{
|
{
|
||||||
|
|
@ -308,6 +418,12 @@ void Phy_WaveOpticsModel::Test_ReadData(string str)
|
||||||
{
|
{
|
||||||
_mPortEoutCurl[i] = EoutCurl[i];
|
_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"))
|
if (js.contains("beam"))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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}")
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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 (x–z 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()
|
||||||
|
|
@ -61,11 +61,27 @@ def load_scalar_field(path: Path) -> np.ndarray:
|
||||||
if not line:
|
if not line:
|
||||||
continue
|
continue
|
||||||
if line.startswith("//"):
|
if line.startswith("//"):
|
||||||
line = line[2:].strip()
|
continue
|
||||||
vals.append(float(line.split()[0]))
|
vals.append(float(line.split()[0]))
|
||||||
return np.array(vals)
|
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:
|
def axis_index(axis: str) -> int:
|
||||||
return {"x": 0, "y": 1, "z": 2}[axis.lower()]
|
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
|
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]
|
v = V[tet_idx]
|
||||||
f = field[tet_idx]
|
|
||||||
A = np.column_stack([v[1] - v[0], v[2] - v[0], v[3] - v[0]])
|
A = np.column_stack([v[1] - v[0], v[2] - v[0], v[3] - v[0]])
|
||||||
w123 = np.linalg.solve(A, p - v[0])
|
w123 = np.linalg.solve(A, p - v[0])
|
||||||
w0 = 1.0 - np.sum(w123)
|
w0 = 1.0 - np.sum(w123)
|
||||||
w = np.array([w0, w123[0], w123[1], w123[2]])
|
return np.array([w0, w123[0], w123[1], w123[2]], dtype=float)
|
||||||
return float(np.dot(w, f))
|
|
||||||
|
|
||||||
|
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):
|
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)
|
ai, bi = other_axes(axis)
|
||||||
ax = axis_index(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)
|
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):
|
def build_surface_tris(V: np.ndarray, Tri: np.ndarray, field: np.ndarray, axis: str, coord: float, tol: float):
|
||||||
ai, bi = other_axes(axis)
|
ai, bi = other_axes(axis)
|
||||||
ax = axis_index(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)
|
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
|
ai, bi = plane_axes
|
||||||
names = ["x", "y", "z"]
|
names = ["x", "y", "z"]
|
||||||
fig, ax = plt.subplots(figsize=(7.5, 6.5), dpi=150)
|
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_aspect("equal")
|
||||||
ax.set_xlabel(f"{names[ai]} (m)")
|
ax.set_xlabel(f"{names[ai]} (m)")
|
||||||
ax.set_ylabel(f"{names[bi]} (m)")
|
ax.set_ylabel(f"{names[bi]} (m)")
|
||||||
ax.set_title(title)
|
ax.set_title(title)
|
||||||
cbar = fig.colorbar(tpc, ax=ax, fraction=0.046, pad=0.04)
|
cbar = fig.colorbar(tpc, ax=ax, fraction=0.046, pad=0.04)
|
||||||
cbar.set_label("|E| (V/m)")
|
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()
|
fig.tight_layout()
|
||||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
fig.savefig(out_path, bbox_inches="tight")
|
fig.savefig(out_path, bbox_inches="tight")
|
||||||
|
|
@ -216,33 +295,50 @@ def main():
|
||||||
parser = argparse.ArgumentParser(description="Plot normE on a plane slice")
|
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("--mesh", type=Path, default=root / "build" / "Release" / "SBCmesh.dat")
|
||||||
parser.add_argument("--outdir", type=Path, default=root / "build" / "Release" / "OutFile")
|
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("--plane", choices=["x", "y", "z"], default="z")
|
||||||
parser.add_argument("--coord", type=float, default=0.5, help="plane coordinate (m)")
|
parser.add_argument("--coord", type=float, default=0.5, help="plane coordinate (m)")
|
||||||
parser.add_argument("--mode", choices=["slice", "surface"], default="slice",
|
parser.add_argument("--mode", choices=["slice", "surface"], default="slice",
|
||||||
help="slice=cut volume; surface=boundary tris on plane")
|
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("--tol", type=float, default=1e-3, help="tolerance for surface mode")
|
||||||
parser.add_argument("--output", type=Path, default=None)
|
parser.add_argument("--output", type=Path, default=None)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
V, T, Tri, _ = load_mesh(args.mesh)
|
V, T, Tri, _ = load_mesh(args.mesh)
|
||||||
normE = load_scalar_field(args.outdir / "normE")
|
norme_path = args.norme if args.norme is not None else args.outdir / "normE"
|
||||||
if normE.size != V.shape[0]:
|
|
||||||
raise SystemExit(f"normE size {normE.size} != vertex count {V.shape[0]}")
|
|
||||||
|
|
||||||
if args.output is None:
|
if args.output is None:
|
||||||
args.output = args.outdir / f"normE_{args.plane}{args.coord:.3f}.png"
|
args.output = args.outdir / f"normE_{args.plane}{args.coord:.3f}.png"
|
||||||
|
|
||||||
if args.mode == "surface":
|
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)
|
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)"
|
title = f"normE on {args.plane}={args.coord} m (surface)"
|
||||||
else:
|
else:
|
||||||
tri, zval, plane_axes = build_slice_tris(V, T, normE, args.plane, args.coord)
|
# Prefer Ex/Ey/Ez → |E| (correct). Fallback to nodal normE.
|
||||||
title = f"normE on {args.plane}={args.coord} m (slice)"
|
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:
|
if tri is None:
|
||||||
raise SystemExit(f"no data on plane {args.plane}={args.coord}")
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||