# OpticsFEM 二维光学有限元程序 — 开发与使用手册 | 项目 | 说明 | |------|------| | 文档版本 | v1.0 | | 编制日期 | 2026-06-03 | | 适用代码 | `opticsfem-master/` | | 适用平台 | Windows x64,Visual Studio 2026(18)/ MSVC | | 关联文档 | `OpticsFEM_LASPCEM_接口规范.md` | --- ## 目录 1. [概述](#1-概述) 2. [从源代码到可执行文件](#2-从源代码到可执行文件) 3. [软件架构与模块划分](#3-软件架构与模块划分) 4. [程序执行流程](#4-程序执行流程) 5. [数据流与对象模型](#5-数据流与对象模型) 6. [对外 API 规范](#6-对外-api-规范) 7. [输入与输出文件规范](#7-输入与输出文件规范) 8. [构建与部署](#8-构建与部署) 9. [算例运行指南](#9-算例运行指南) 10. [代码阅读路径](#10-代码阅读路径) 11. [求解器依赖说明](#11-求解器依赖说明) 12. [常见问题与排查](#12-常见问题与排查) --- ## 1. 概述 ### 1.1 程序定位 OpticsFEM 是一套基于 C++ 实现的**二维矢量有限元(FEM)光学电磁仿真内核**,采用 Nedelec 边元离散麦克斯韦方程,支持以下三类问题: | FemType | 问题类型 | 核心类 | |---------|----------|--------| | 0 | 本征模式(EigenMode) | `OpticsFEM_2D_EigenMode` | | 1 | 本征频率(EigenFreq) | `OpticsFEM_2D_EigenFreq` | | 2 | 散射(Scatter,按波长) | `OpticsFEM_2D_Scatter` | | 3 | 散射(Scatter,按频率) | `OpticsFEM_2D_Scatter` | 程序通过 **JSON 字符串** 描述物理模型、材料参数、边界条件及求解配置;通过 **`.dat` 文本文件** 描述三角网格。计算结果以文本文件形式输出至指定目录。 ### 1.2 入口程序与可执行文件的关系 `test/Test_Main.cpp` 并非完整程序,而是**可执行文件的入口模块**。其职责限于: 1. 从本地文件系统读取 JSON 配置文件; 2. 构造 `OpticsFEMData` 结构体; 3. 调用统一 API `OpticsFEM_API::OpticsFEM_All()` 触发完整求解流程。 可执行文件 `OpticsFEM.exe` 由 CMake 将 `Test_Main.cpp` 与项目中全部业务源文件(约 70 余个 `.cpp` 单元)**编译并链接**为单一二进制产物。操作系统启动进程时,自 `main()` 函数开始执行;后续全部计算逻辑由 `FEM_Interface.cpp` 及其调用的各功能模块完成。 ### 1.3 设计原则 | 原则 | 说明 | |------|------| | 单一 API 入口 | 外部调用方仅需调用 `OpticsFEM_All()`,无需感知内部分支 | | JSON 为配置语言 | 物理、材料、边界、求解参数均通过 JSON 字段传递 | | 前后端解耦 | `Test_Main.cpp` 可替换为 GUI、脚本或 DLL 宿主,接口不变 | | 模块化组装 | 网格、物理、材料、组装、求解、后处理分层实现 | --- ## 2. 从源代码到可执行文件 ### 2.1 构建系统 项目采用 **CMake** 管理构建,配置文件为 `opticsfem-master/CMakeLists.txt`。目标产物定义如下: ```cmake add_executable(OpticsFEM "test/Test_Main.cpp" ${PARSER} ${COMMON} ${GAUSS} ${BF} ${MESH} ${MATERIAL} ${PHY} ${SOLVER} ${POST} ${KERNEL} ${TEST} ${INTERFACE} "post/Post_GetEb.cpp" ) target_compile_definitions(OpticsFEM PRIVATE EIGENSOLVER_STATIC) set(CMAKE_CXX_STANDARD 17) ``` ### 2.2 编译链接流程 ``` ┌──────────────────────────────────────────────────────────────────┐ │ 源文件层 │ │ Test_Main.cpp, FEM_Interface.cpp, Mesh_Interface.cpp, ... │ └────────────────────────────┬─────────────────────────────────────┘ │ cmake .. -G "Visual Studio 18 2026" -A x64 ▼ ┌──────────────────────────────────────────────────────────────────┐ │ 工程生成层 │ │ build/OpticsFEM.vcxproj, build/Project.slnx │ └────────────────────────────┬─────────────────────────────────────┘ │ cmake --build . --config Release ▼ ┌──────────────────────────────────────────────────────────────────┐ │ 编译层(Translation) │ │ 每个 .cpp → 对应 .obj 目标文件 │ └────────────────────────────┬─────────────────────────────────────┘ │ 链接(Linking) ▼ ┌──────────────────────────────────────────────────────────────────┐ │ 产物层 │ │ build/Release/OpticsFEM.exe │ └──────────────────────────────────────────────────────────────────┘ ``` **编译(Compile)**:将各翻译单元(`.cpp`)独立转换为目标文件(`.obj`)。`#include` 头文件仅提供声明,不参与链接。 **链接(Link)**:合并全部 `.obj`,解析跨文件符号引用,以 `Test_Main.cpp` 中的 `main()` 作为进程入口,生成最终 `.exe`。 ### 2.3 第三方依赖 | 依赖 | 位置 | 用途 | |------|------|------| | Eigen | `opticsfem-master/Eigen/` | 稠密/稀疏矩阵运算 | | nlohmann/json | `opticsfem-master/nlohmann/` | JSON 解析 | | muparser 派生库 | `opticsfem-master/parser/` | BELE 等边界表达式求值 | ### 2.4 关于 Test_Main.cpp 中未使用的变量 `Test_Main.cpp` 中声明的 `Phy_WaveOpticsModel`、`MaterialLib`、`Mesh_2D` 等对象在 `main()` 内**未被直接使用**。实际对象实例化与数据注入在 `FEM_Interface.cpp::OpticsFEM_All()` 中完成。上述声明为历史遗留或调试用途;注释块(第 39–58 行)保留了手动分步调用流程的旧版写法,现行标准路径为 `OpticsFEM_All()` 一站式调用。 --- ## 3. 软件架构与模块划分 ### 3.1 目录结构与职责 ``` opticsfem-master/ ├── test/ │ ├── Test_Main.cpp # 进程入口;读取 JSON 文件 │ ├── Test_ReadData.cpp # JSON → Phy / Material 解析实现 │ └── Test_OutputData.cpp # 调试输出 ├── Interface/ │ ├── FEM_Interface.h # 对外 API 声明 │ └── FEM_Interface.cpp # FemType 路由与全流程调度 ├── phy/ # 物理模型:边界条件、激励 ├── material/ # 材料库:ε、μ、σ、n、k ├── mesh/ # 网格:.dat 读取与拓扑查询 ├── kernel/ # FEM 组装:方程与边界积分 ├── solver/ # 数值求解:线性方程组 / 特征值 ├── post/ # 后处理:DOF → 顶点场 → 文件输出 ├── function/ # Nedelec 基函数、高斯积分 ├── parser/ # 数学表达式解析器 └── Eigen/ # 线性代数库(第三方) ``` ### 3.2 模块依赖关系 ```mermaid flowchart TB subgraph entry [入口层] TM[Test_Main.cpp] API[FEM_Interface.cpp] end subgraph io [数据读入层] PHY[phy] MAT[material] MSH[mesh] end subgraph core [计算核心层] KRN[kernel] SLV[solver] end subgraph output [输出层] PST[post] end TM --> API API --> PHY API --> MAT API --> MSH API --> KRN KRN --> SLV KRN --> PST SLV --> PST PHY --> KRN MAT --> KRN MSH --> KRN MSH --> PST PHY --> PST ``` ### 3.3 核心类层次(二维) | 类名 | 职责 | |------|------| | `Phy_WaveOpticsModel` | 存储边界类型、BELE/PML/EF/SBC 等边界数据 | | `MaterialLib` | 存储各物理域材料参数 | | `Mesh_2D` | 存储顶点、边、三角形及域编号 | | `OpticsFEM_2D_Scatter` | 散射问题组装、求解、后处理调度 | | `Solver_LdaDom` | 散射线性方程组求解接口 | | `Post_2D_Scatter` | 散射结果电场插值与输出 | --- ## 4. 程序执行流程 ### 4.1 顶层调用序列 以 `FemType = 2`(散射,bele 算例)为例,完整调用序列为: ``` main() └─ OpticsFEM_API::OpticsFEM_All(data) ├─ json::parse(data.data) ├─ Phy_WaveOpticsModel::Test_ReadData(str) ├─ MaterialLib::Test_ReadData(str) ├─ Mesh_2D::GetMesh(meshFile, str) └─ [FemType == 2 分支] ├─ Solver_LdaDom::GetSolver(str) ├─ OpticsFEM_2D_Scatter::GetMaterial / GetMesh / GetPhy / GetSolver / GetPost ├─ OpticsFEM_2D_Scatter::Assemble() ├─ OpticsFEM_2D_Scatter::Run() └─ OpticsFEM_2D_Scatter::Post(outFile) ``` ### 4.2 Assemble 阶段内部流程 `OpticsFEM_2D_Scatter::Assemble()`(`kernel/Assemble_kernel.cpp`)执行以下步骤: 1. **确定矩阵类型**:根据 PML、SBC、复材料等条件判定采用实数或复数稀疏矩阵; 2. **体积积分**:`Assemble_WaveEquation()` — 在各三角形单元上积分麦克斯韦弱形式; 3. **边界积分**(按 JSON 配置条件触发): - `Assemble_PEC_ELE()` — 理想导体 / 电边界 - `Assemble_PBC()` — 周期边界 - `Assemble_BELE()` — 边界等效源(bele 算例) - `Assemble_PML()` — 完美匹配层 - `Assemble_Port()` — 端口激励 - 等 4. **约束处理**:对 Dirichlet 型边界自由度施加罚函数或消元。 输出:稀疏矩阵 **A**、右端向量 **b**、预处理矩阵 **P**。 ### 4.3 Run 阶段 ```cpp void OpticsFEM_2D_Scatter::Run() { _mSolver->SetParam(&_mA_complex, &_mB_complex, &_mP_complex); _mSolver->Run(&_mX); } ``` `Solver_LdaDom::Run()` 调用 `solveComplexLinearEqu()`(`solver/interface.cpp`),将 CSR 格式矩阵写入临时文件,并启动外部求解器 `complex/complexsolver.exe` 完成迭代求解。解向量经预处理矩阵变换后存入 `_mX`。 ### 4.4 Post 阶段 ```cpp void OpticsFEM_2D_Scatter::Post(string file) { _mPost->GetMesh(_mMesh); _mPost->GetSolver(_mSolver); _mPost->GetPhy(_mPhy); _mPost->GetResult(&_mX); _mPost->GetElectric(); // 边 DOF → 顶点 Ex/Ey/Ez _mPost->OutputData(file); // 写入 OutFile/ } ``` --- ## 5. 数据流与对象模型 ### 5.1 端到端数据流 ```mermaid flowchart LR subgraph inputs [外部输入] J["bele.json\n(JSON 字符串)"] D["project_3.dat\n(网格文件)"] end subgraph parsed [内存对象] P["Phy_WaveOpticsModel\n边界/激励"] M["MaterialLib\n材料参数"] G["Mesh_2D\n网格拓扑"] end subgraph linear [离散方程] A["稀疏矩阵 A"] B["右端向量 b"] X["解向量 x"] end subgraph files [文件输出] O["OutFile/Ex, Ey, Ez, normE"] end J -->|Test_ReadData| P J -->|Test_ReadData| M J -->|MeshFile 字段| D D -->|GetMesh| G P --> A M --> A G --> A P --> B A --> X B --> X X --> O G --> O ``` ### 5.2 JSON 字段到内存对象的映射 | JSON 字段组 | 目标对象 | 解析函数 | |-------------|----------|----------| | `FemType`, `EletricType`, `BoundaryFlag`, `bele`, `ef`, `pml`, `sbc`, … | `Phy_WaveOpticsModel` | `Phy_WaveOpticsModel::Test_ReadData()` | | `NbrDomain`, `epsilonrR/I`, `murR/I`, `n`, `k`, `sigma`, … | `MaterialLib` | `MaterialLib::Test_ReadData()` | | `MeshFile` | 网格文件路径 | `Mesh_2D::GetMesh()` | | `lambda` / `freq`, `NbrMode`, `searchValue`, … | 求解器参数 | `Solver_*::GetSolver()` | ### 5.3 bele 算例配置摘要 | 配置项 | 值 | 含义 | |--------|-----|------| | `FemType` | 2 | 散射问题 | | `EletricType` | 2 | 求解完整电场 E | | `lambda` | 1 | 真空波长 | | `bele.index` | [4] | 边界域 4 施加 BELE | | `bele.Eby` | `sin(2*pi*x)` | 边界切向电场解析表达式 | | `ef` | 边界 1 电场激励 | 等效 Dirichlet 激励 | | `pml` | 4 个吸收层 | PML 参数 | | `MeshFile` | `project_3.dat` | 网格文件 | | `OutFile` | `./OutFile` | 输出目录 | --- ## 6. 对外 API 规范 ### 6.1 数据结构 ```cpp struct OpticsFEMData { double test1; // 保留字段 double test2; // 保留字段 char* data; // UTF-8 JSON 字符串(单行或紧凑格式) }; ``` ### 6.2 接口函数 ```cpp class OpticsFEM_API { public: // 完整求解流程:读配置 → 组装 → 求解 → 后处理 static int OpticsFEM_All(OpticsFEMData data); // 连通性测试:向 OutFile 写入 "test success" int OpticsFEM_Test(OpticsFEMData data); }; ``` ### 6.3 返回值 | 返回值 | 含义 | |--------|------| | 0 | `FemType` 无法识别 | | 1 | 本征模式完成 | | 2 | 本征频率完成 | | 3 | 散射完成 | ### 6.4 集成方式 **方式 A — 独立可执行文件(当前 `Test_Main.cpp` 模式)** 从文件读取 JSON,调用 API,适用于本地验证与回归测试。 **方式 B — 动态库宿主(注释代码预留)** `FEM_Interface.h` 底部预留 `extern "C"` 导出声明,可将内核编译为 DLL 供前端或其他语言调用。 --- ## 7. 输入与输出文件规范 ### 7.1 JSON 配置文件 - **格式**:单行 UTF-8 JSON 或紧凑 JSON; - **读取方式**:`Test_Main.cpp` 使用 `getline()` 读取整行; - **必填公共字段**:`FemType`, `NbrBoundary`, `BoundaryFlag`, `NbrDomain`, `MeshFile`, `OutFile`; - **边界扩展字段**(可选,按 `js.contains` 判定):`bele`, `ef`, `pml`, `sbc`, `mag`, `scd`, `pbc`, `mpd`, `epd`, `port`, `beam`。 详细字段定义参见 `OpticsFEM_LASPCEM_接口规范.md` 第 2.3 节。 ### 7.2 二维网格 `.dat` 文件 由 `Mesh_2D::GetMesh()`(`mesh/Mesh_Interface.cpp`)顺序解析,字段顺序为: ``` NbrVertex → Vertex → NbrEdge → Edge → NbrTri → Tri → EdgeOfTri → DomainOfTri → NbrEdges → CoonOfEdges → DomainOfEdges → normal → CopyOfEdges → ... ``` 顶点坐标为二维 `(x, y)`,第三分量在读取时置零。 ### 7.3 输出文件 散射问题(`Post_2D_Scatter::OutputData`)在 `OutFile` 目录下生成: | 文件名 | 内容 | |--------|------| | `Ex` | 各顶点 Ex 分量(实部 虚部,逐行) | | `Ey` | 各顶点 Ey 分量 | | `Ez` | 各顶点 Ez 分量 | | `normE` | 各顶点电场模 | 本征问题额外输出 `neff` 或 `freq` 文件。 --- ## 8. 构建与部署 ### 8.1 环境要求 | 组件 | 要求 | |------|------| | 操作系统 | Windows 10/11 x64 | | 编译器 | Visual Studio 2026(18)Community,工作负载「使用 C++ 的桌面开发」 | | 构建工具 | CMake ≥ 3.8(VS 自带或独立安装) | | C++ 标准 | C++17 | ### 8.2 构建命令 ```powershell # 配置 CMake 路径(若未加入系统 PATH) $env:Path += ";C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin" cd opticsfem-master mkdir build -Force cd build cmake .. -G "Visual Studio 18 2026" -A x64 cmake --build . --config Release ``` 产物路径:`build/Release/OpticsFEM.exe` ### 8.3 运行时目录结构 散射算例(bele)最小部署结构: ``` Release/ ├── OpticsFEM.exe ├── bele.json # 输入配置(文件名与 Test_Main.cpp 中 fd.open 一致) ├── project_3.dat # 网格(与 JSON 中 MeshFile 一致) └── complex/ └── complexsolver.exe # 外部线性求解器(运行时必需) ``` 本征算例另需: ``` real_solver/real # 或 real_solver 目录下对应可执行文件 complex_solver/complex ``` ### 8.4 关于 eigen_solver 静态链接 当前工程已将 `solver/eigen_solver.cpp` 编入可执行文件,并通过宏 `EIGENSOLVER_STATIC` 取消 DLL 导入/导出修饰,**不再依赖** `lib/eigensolver.lib`。该模块服务于本征模式/本征频率问题(`realEigenSolver` / `complexEigenSolver`),运行时仍依赖 `real_solver/` 与 `complex_solver/` 目录下的外部可执行文件。 --- ## 9. 算例运行指南 ### 9.1 标准运行步骤 **步骤 1 — 编译** 执行第 8.2 节构建命令,确认生成 `OpticsFEM.exe`。 **步骤 2 — 配置入口 JSON 文件名** 编辑 `test/Test_Main.cpp` 第 28 行: ```cpp fd.open("bele.json"); // 改为目标算例 JSON 文件名 ``` 修改后须重新编译。 **步骤 3 — 部署输入文件** 将 JSON 及 `MeshFile` 指定的 `.dat` 文件复制至 `build/Release/` 目录。 **步骤 4 — 部署外部求解器** 将 `complex/complexsolver.exe` 置于 `Release/complex/` 目录(路径相对于工作目录)。 **步骤 5 — 执行** ```powershell cd build/Release .\OpticsFEM.exe ``` **步骤 6 — 验证输出** 检查 `OutFile/` 目录是否生成 `Ex`、`Ey`、`Ez`、`normE`。 ### 9.2 切换算例 | 操作 | 说明 | |------|------| | 更换 JSON | 修改 `Test_Main.cpp` 中 `fd.open()` 文件名,或使用同名复制 | | 更换网格 | 确保 JSON 中 `MeshFile` 字段与实际 `.dat` 文件名一致 | | 更换问题类型 | 修改 JSON 中 `FemType`;程序自动路由至对应求解分支 | 测试数据集位于 `测试数据集/`, subdirectory 名称即边界/物理类型(如 `bele/`、`scd/`、`pml_xy/`)。 --- ## 10. 代码阅读路径 建议按以下顺序阅读,以建立自入口至输出的完整认知: | 序号 | 文件 | 阅读目标 | |------|------|----------| | 1 | `test/Test_Main.cpp` | 进程入口、JSON 文件读取 | | 2 | `Interface/FEM_Interface.cpp` | FemType 路由、三阶段调度 | | 3 | `test/Test_ReadData.cpp` | JSON 字段解析逻辑 | | 4 | `mesh/Mesh_Interface.cpp` | `.dat` 网格格式 | | 5 | `kernel/Assemble_kernel.cpp` | Assemble / Run / Post 总控 | | 6 | `kernel/Assemble_Scatter_2D_Boundary.cpp` | BELE / PML / EF 等边界组装 | | 7 | `kernel/Assemble_Scatter_Equation.cpp` | 体积方程组装 | | 8 | `solver/Solver_Interface.cpp` | 求解器参数与 Run 实现 | | 9 | `solver/interface.cpp` | 外部求解器调用封装 | | 10 | `post/Post_Output.cpp` | 结果文件格式 | ### 调试建议 - 在 `FEM_Interface.cpp` 各 `FemType` 分支入口设置断点,确认路由正确; - 在 `Assemble()` 返回后检查矩阵非零元数量(`Test_OutputMatrix()` 已在散射分支调用); - 在 `Post()` 返回后检查 `OutFile/` 目录。 --- ## 11. 求解器依赖说明 ### 11.1 求解器分层 | 层级 | 模块 | 适用问题 | 运行时依赖 | |------|------|----------|------------| | 特征值求解 | `eigen_solver.cpp` | FemType 0, 1 | `real_solver/real`, `complex_solver/complex` | | 线性方程组(实) | `interface.cpp::solveRealLinearEqu` | 实散射 | `real/realsolver.exe` | | 线性方程组(复) | `interface.cpp::solveComplexLinearEqu` | 复散射(bele) | `complex/complexsolver.exe` | ### 11.2 外部求解器调用机制 `interface.cpp` 采用**文件交换**方式与外部求解器通信: 1. 将 CSR 格式矩阵、右端项写入当前目录临时文件; 2. 通过 `system()` 调用外部 `.exe`; 3. 读取解向量临时文件; 4. 清理临时文件。 因此,外部求解器必须与 `OpticsFEM.exe` **工作目录相对路径**正确对应,且具备可执行权限。 --- ## 12. 常见问题与排查 | 现象 | 可能原因 | 处理措施 | |------|----------|----------| | `cmake` 无法识别 | CMake 未加入 PATH | 使用 VS 自带 CMake 完整路径,或配置环境变量 | | `LNK1181: eigensolver.lib` | 旧版 CMakeLists 仍链接外部库 | 确认已启用 `EIGENSOLVER_STATIC` 并编入 `eigen_solver.cpp` | | 运行无输出、无 OutFile | 缺少 `complexsolver.exe` 或路径错误 | 检查 `complex/` 目录及工作目录 | | 无法打开 `bele.json` | 工作目录不含 JSON | 在 `Release/` 目录下运行,或复制输入文件 | | 无法打开 `project_3.dat` | `MeshFile` 路径不匹配 | 确保 `.dat` 与 exe 同目录,或修正 JSON | | 编译警告 C4819 | Eigen 头文件编码 | 不影响功能,可忽略 | | `FemType` 返回 0 打印 err | JSON 缺少或错误 `FemType` 字段 | 检查 JSON 完整性 | --- ## 附录 A:FemType 路由对照表 | FemType | 求解器类 | 后处理类 | 关键 JSON 参数 | |---------|----------|----------|----------------| | 0 | `Solver_EigenMode` | `Post_2D_EigenMode` | `lambda0`, `NbrMode`, `searchValue` | | 1 | `Solver_EigenFreq` | `Post_2D_EigenFreq` | `lambda0`, `NbrMode`, `searchValue` | | 2 | `Solver_LdaDom` | `Post_2D_Scatter` | `lambda`, 边界字段 | | 3 | `Solver_LdaDom` | `Post_2D_Scatter` | `freq`, 边界字段 | --- ## 附录 B:文档修订记录 | 版本 | 日期 | 修订内容 | |------|------|----------| | v1.0 | 2026-06-03 | 初版:构建流程、架构、数据流、部署与算例指南 | --- *本文档基于 `opticsfem-master` 当前代码状态编制,若 CMakeLists 或 API 发生变更,请同步更新相应章节。*