# 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 发生变更，请同步更新相应章节。*
