# Dynamics 项目优化方案综合报告

> 整合日期：2026-06-12  
> 来源文档：[`claude.md`](claude.md)（Claude Sonnet 4.6 分析）、[`workbuddy.md`](workbuddy.md)（WorkBuddy 分析）  
> 项目路径：`D:\Share\Data\aliyun-gitea\dynamics`

---

## 第一部分：两份分析报告的评价与对比

### 1.1 WorkBuddy 分析报告评价（`workbuddy.md`）

#### 优势

**① 架构视野宏观、分层清晰**  
WorkBuddy 从整体软件工程角度出发，明确指出 `compute.py` 的职责混乱问题（1618 行同时承担物理引擎、文件 I/O、参数加载、外部引擎管理五种职责），并给出了完整的模块拆分方案：

```
compute/
├── core.py        # 物理引擎
├── io.py          # 文件 I/O
├── params.py      # 参数加载
├── runner.py      # 运行管理
├── engine_helper.py  # 外部引擎
└── main.py        # 主入口
```

这是一个系统性重构方向，有助于长期维护。

**② 关注工程规范与测试**  
WorkBuddy 专门开辟了"测试与质量"一节，强调了缺少单元测试的风险，并给出了类型标注的示例代码——这两点是 claude.md 未涉及的。对于一个教学/研究用项目，有测试才能安全重构。

**③ 配置管理建议具体**  
提出了将 `ball_color_r/g/b` 三个键合并为 `ball_color: [r,g,b]` 数组的配置统一方案，以及6个案例 `input.txt` 格式不统一的问题（如 `save_trajectory`、`camera_*` 字段缺失），并给出了统一模板，实操性强。

**④ 引擎一致性梳理全面**  
清晰列出了 C/C++/Fortran 三引擎在 `save_trajectory`、`box_a` 默认值等参数上的差异表格，对用户切换引擎有直接指导价值。

**⑤ 区分"已完成"与"待做"**  
文档中标注了"✅ 已修复"、"✅ 已完成"条目，帮助读者快速定位当前状态，避免重复分析。

#### 劣势

**① 缺少具体 Bug 定位**  
WorkBuddy 的分析主要停留在架构和规范层面，对于已经导致运行崩溃的 Bug（如 `plot_wave.py` 格式不匹配、`run_simulation` 中 `config` 未定义）没有识别和标注。用户按此文档操作时，可能先花时间做重构，但基础功能仍然崩溃。

**② 性能优化建议较抽象**  
对 Python 引擎性能仅建议"使用 Numba JIT"或"numpy.vectorize"，缺少具体的向量化代码示例。文档注明"Python vs C 慢约 6-8 倍"，但未分析哪个函数是瓶颈（实际上是弹簧力的 Python for 循环）。

**③ 部分建议颗粒度不足**  
"消除全局变量"建议封装为 `SimulationState` 数据类，但未说明：封装后接口如何调整、外部引擎的 `param.json` 协议是否需要同步更新、draw.py 的全局变量是否属于同一重构范围。

**④ 优先级排列有待商榷**  
将"全局变量封装"列为 P0（与 Fortran 引擎修复同级），但封装全局变量是一项重构工作（3-4h），而 Fortran 引擎崩溃是已存在的功能阻断性问题，两者紧迫性不同。

---

### 1.2 Claude 分析报告评价（`claude.md`）

#### 优势

**① Bug 定位精准，附有复现条件**  
每个 Bug 都说明了触发条件（"只要使用 `engine: python`"、"`step_plot_wave: 1` 时"），以及为什么当前没有爆发（"当前所有案例恰好设置 `step_plot: 0`"）。这对于开发者复现和验证问题极有价值。

**② 代码级修复方案完整**  
每个 Bug 都给出了完整可运行的修复代码，包括需要修改的行号、修改前后的对比。特别是 B3（`plot_wave.py` 格式不匹配）不仅指出问题，还解释了为何修复较复杂（`atom_masses` 等物理量在新格式中缺失，需要同步扩展 header 字段），给出了分层的修复路径（快速修复 vs 彻底修复）。

**③ 弹簧力向量化方案具体可行**  
给出了完整的 NumPy 向量化代码，包括处理重复索引的 `np.add.at` 用法，以及链状体系可进一步优化为直接索引的提示。预期加速 5-15 倍的估算也有根据（119 键 × 100,000 步的量级分析）。

**④ 引擎差异的量化说明**  
指出了 `engines/cpp/param.json` 中 `save_trajectory` 默认值为 1（与 C 引擎的 0 不一致），并说明了实际影响（切换引擎时可能意外生成大文件）。

#### 劣势

**① 覆盖面刻意与 workbuddy.md 错开，造成视野盲区**  
claude.md 开篇声明"不与 workbuddy.md 重复"，因此主动跳过了架构、测试、配置管理等方向。但这导致文档读者如果只看 claude.md，会误以为架构是没问题的。两份文档需要结合阅读，割裂感较强。

**② 部分建议依赖全局变量方案**  
claude.md 提出的 Bug B1 修复方案（将 `camera_distance` 等加入全局变量）是在全局变量模式下的补丁式修复，与 workbuddy.md 建议的"封装全局变量为类"方向相反。若未来执行 workbuddy.md 的重构，B1 的修复需要再次调整。

**③ 某些结论尚未验证**  
文档末尾注明"基于代码静态分析，未实际运行测试验证"。例如 B3（`plot_wave.py` 格式不匹配）的结论基于函数调用链分析，如果 `load_text_data` 内部有容错逻辑，实际情况可能不同。

**④ 缺乏对 draw.py 的深入分析**  
draw.py 是用户交互最频繁的模块（3D 动画播放），但 claude.md 仅指出了一处裸 `except`（B5），未分析动画帧率优化、大原子数渲染性能等用户体验层面的问题。

---

### 1.3 两份报告对比总结

| 维度 | WorkBuddy | Claude |
|------|-----------|--------|
| **Bug 识别** | ❌ 未发现运行时 Bug | ✅ 识别 5 个确认 Bug，附触发条件 |
| **性能优化** | 🟡 方向正确但缺代码 | ✅ 完整向量化代码示例 |
| **架构设计** | ✅ 完整模块拆分方案 | ❌ 刻意回避，未覆盖 |
| **测试建议** | ✅ pytest 方案具体 | ❌ 未涉及 |
| **配置管理** | ✅ 6 案例统一模板 | ❌ 未涉及 |
| **引擎一致性** | ✅ 参数差异表格 | ✅ 补充了势能归属约定 |
| **修复代码** | 🟡 仅有架构示意 | ✅ 逐条给出可运行代码 |
| **优先级合理性** | 🟡 P0 安排有误 | ✅ 按紧迫性分层清晰 |
| **可独立阅读** | ✅ 自成体系 | ❌ 依赖读者已读 workbuddy.md |
| **当前状态标注** | ✅ 已完成项有标注 | ❌ 未区分 |

**结论**：WorkBuddy 擅长系统性架构分析和工程规范，Claude 擅长 Bug 精确定位和具体修复代码。两份报告互补，单独使用任何一份都存在明显盲区。

---

## 第二部分：综合优化方案

### 原则

基于以上分析，综合方案遵循以下原则：
1. **先止血再手术**：运行时 Bug 优先于架构重构，不能让教学演示功能处于崩溃状态
2. **快速收益优先**：同等重要性时，工作量小的先做
3. **向量化先于重构**：Python 引擎提速不依赖架构改动，可独立进行
4. **渐进式重构**：全局变量封装等重构分阶段进行，避免一次性大改引入新 Bug

---

### 阶段一：紧急修复（总工作量约 4 小时）

> 目标：恢复所有已有功能到正常可用状态

#### 1. 修复 `plot_wave.py` 格式不匹配（B3）— 1.5h

**根本原因**：`display.txt` 格式从 JSON 迁移到新文本格式时，`plot_wave.py` 未同步更新。

**修复步骤**：

① 修改加载函数（`plot_wave.py:22-27`）：
```python
def load_disp_data(output_dir):
    disp_path = os.path.join(output_dir, "display.txt")
    if not os.path.exists(disp_path):
        raise FileNotFoundError(f"找不到 {disp_path}")
    return compute.load_display_txt(disp_path)   # 改为新格式加载
```

② 在 `save_display_txt`（`compute.py:217`）的 header 中补充 `atom_masses` 字段：
```python
# 在 header_fields 中添加
"atom_masses": ",".join(str(m) for m in ATOM_MASSES),
```

③ 修改 `plot_wave.py` 中的字段访问：
```python
# 旧 → 新
data["n_frames"]       → disp_data["frames_x"].shape[0]
data["disp_all_x"]     → disp_data["frames_x"]
data["atom_masses"]    → np.array([float(x) for x in h.get("atom_masses","").split(",") if x])
data["atom_ids"]       → disp_data["atom_ids"]
data["bond_pairs"]     → []   # display.txt 不含成键信息，能量计算中弹性势能项跳过
```

**验证**：运行 case05（`step_plot_wave: 1`），确认生成 `wave_animation.gif`。

---

#### 2. 修复 `run_simulation()` 中的 `config` 未定义（B1）— 15min

在 `compute.py` 模块顶部（约第 68 行，紧随 `camera_keyframes_raw`）添加全局变量：

```python
camera_keyframes_raw = ""
camera_distance  = 40.0    # 新增
camera_elevation = 0       # 新增
camera_azimuth   = 0       # 新增
```

在 `run_from_config`（约第 756 行，`use_marker` 赋值附近）添加：
```python
use_marker = int(config.get("use_marker", 0))
camera_distance  = float(config.get("camera_distance", 40.0))   # 新增
camera_elevation = float(config.get("camera_elevation", 0))     # 新增
camera_azimuth   = float(config.get("camera_azimuth", 0))       # 新增
```

在 `run_from_config` 的 `global` 声明行追加这三个变量：
```python
global use_marker, camera_keyframes_raw, camera_distance, camera_elevation, camera_azimuth
```

在 `run_simulation`（第 1548-1550 行）改为读全局变量：
```python
"camera_distance": str(camera_distance),
"camera_elevation": str(camera_elevation),
"camera_azimuth": str(camera_azimuth),
```

**验证**：用 `engine: python` 运行 case01，确认生成正确的 `display.txt`。

---

#### 3. 修复 `dynamics.py` 绘图块死代码（B2）— 30min

`step_plot` 功能依赖完整轨迹数据，但当前架构中 `run_from_config` 不再在主流程中保留这些数据。
快速修复：添加 "功能暂不可用" 保护，避免用户误开后崩溃：

```python
# dynamics.py 约 322 行
if not no_plot and config.get("step_plot", 1):
    print("[run] 注意：step_plot 绘图功能需要 save_trajectory=1 时的完整轨迹，")
    print("[run]       当前版本暂未支持，已跳过。如需绘图请联系开发者。")
```

**长期修复**（阶段三再做）：从 `display.txt` 读取抽帧数据重建绘图逻辑。

---

#### 4. 修复 Fortran 引擎输出格式（E3）— 2h

Fortran 引擎总是输出 JSON 格式 `trajectory.txt`，与 Python 侧期待的 `display.txt` 不兼容，
使用 `engine: fortran` 时必然崩溃。

参照 C 引擎的 `write_display_txt` 函数（`engines/c/main.c`）在 Fortran 中实现：
1. 读取 `param.json` 中的 `save_trajectory` 参数
2. 新增 `write_display_txt_f90` 子程序，按 NSTEP 抽帧写文本格式
3. 根据 `save_trajectory` 决定是否额外写 `trajectory.txt`

**同时**：将 `engines/cpp/param.json` 中的 `"save_trajectory": 1` 改为 `0`，与 C 保持一致。

---

#### 5. 其他 1-5 分钟的小修复

```python
# B4: examples/case06/run_dynamics.py:34
description="运行 Dynamics 示例案例 case06"   # 改 case01 → case06

# B5: draw.py:97
except (ValueError, AttributeError):           # 改裸 except

# Q1: dynamics.py 顶部
from compute import _load_camera_motion as _load_camera_kf   # 删除重复实现

# E2: engines/cpp/param.json
"save_trajectory": 0                            # 统一默认值
```

---

### 阶段二：性能优化（总工作量约 3 小时）

> 目标：Python 引擎提速 5-10 倍，外部引擎减少启动开销

#### 6. 弹簧力向量化（P1）— 1h

将 `compute.py:1253-1277` 的 Python for 循环替换为 NumPy 向量化版本（见 claude.md §P1 完整代码）。

**链状体系（case01-06）的进一步优化**：  
由于链状体系每根键的两端没有重复（原子 i 和 j 各只出现一次），可以用更快的直接索引替代 `np.add.at`：

```python
# 比 np.add.at 快约 3 倍（无冲突索引时）
fx[i_arr] += fx_bond
fx[j_arr] -= fx_bond
fy[i_arr] += fy_bond
fy[j_arr] -= fy_bond
fz[i_arr] += fz_bond
fz[j_arr] -= fz_bond
```

对于有分叉键的复杂体系仍需用 `np.add.at`，可根据 `BOND_PAIRS` 检测是否有重复端点自动选择路径。

---

#### 7. `apply_fixed_constraints` 预计算掩码（P2）— 30min

将 `compute.py:1408-1418` 的 `column_stack` 方案替换为预计算 bool 掩码，见 claude.md §P2 完整代码。  
对 case06（120 原子，x/y 全固定）每步节省 2 次 `(120, 3)` 数组分配。

---

#### 8. 外部引擎校准缓存（E4）— 1h

在 `compute.py:924` 校准逻辑前添加缓存命中检查：

```python
import hashlib, json as _json

def _calib_cache_key(engine, n_atoms, method, dt):
    s = f"{engine}:{n_atoms}:{method}:{dt}"
    return hashlib.md5(s.encode()).hexdigest()[:8]

_calib_cache_path = os.path.join(script_dir, "engines", engine, "_calib_cache.json")
_calib_key = _calib_cache_key(engine, len(ATOM_IDS), config.get("method","leapfrog"), float(config["DT"]))
_cached = {}
if os.path.exists(_calib_cache_path):
    with open(_calib_cache_path) as _cf:
        _cached = _json.load(_cf)

if _cached.get("key") == _calib_key and time.time() - _cached.get("ts", 0) < 86400:
    # 缓存命中（1天内有效），跳过校准
    _step_time = _cached["step_time"]
    _overhead  = _cached["overhead"]
else:
    # 执行校准（现有逻辑）...
    # 校准完成后写缓存
    with open(_calib_cache_path, "w") as _cf:
        _json.dump({"key": _calib_key, "ts": time.time(),
                    "step_time": _step_time, "overhead": _overhead}, _cf)
```

**预期收益**：case06 第二次运行节省约 10 秒（校准 10,000 步）。

---

#### 9. 其他微优化（P3/P4）— 30min

```python
# P3: run_simulation 中删除 frame_indices 列表
# 删除: frame_indices = []  和  frame_indices.append(step)
# 替换: n_frames_actual = record_steps // NSTEP  （直接计算）

# P4: apply_driving_force 中删除 t_vec 数组创建
t_vec = np.array([t, t, t], dtype=np.float64)    # 删除此行
pos_drive = d["amp"] * np.cos(2.0 * np.pi * d["freq"] * t + d["phi"])   # 直接用标量 t
```

---

### 阶段三：架构重构（总工作量约 15-20 小时）

> 目标：提升长期可维护性，支持并发模拟、单元测试

> **重要说明**：此阶段是 workbuddy.md 建议的核心，以下是对其建议的细化和补充。  
> 建议在阶段一、二完成并通过全案例验证后再开始。

#### 10. 全局变量封装为 `SimulationState`（workbuddy.md §3.1）— 3-4h

封装后，阶段一中 B1 的补丁修复（新增三个全局变量）可以一并纳入 `SimulationState`，不需要额外保留。

封装时注意：外部引擎路径（`run_engine`）通过 `param.json` 传参，不受全局变量影响，可独立进行。

#### 11. `compute.py` 模块拆分（workbuddy.md §1.1）— 4-6h

拆分时建议**先拆 io.py**（文件读写），因为它与物理算法完全解耦，风险最低。  
拆分顺序建议：`io.py` → `params.py` → `core.py` → `runner.py`。

**补充**：同步删除已废弃的 `load_parameters()` 函数（102 行）和已被 `dynamics.py` 取代的 `compute.main()`（25 行），见 claude.md §Q2。

#### 12. 添加单元测试（workbuddy.md §5.1）— 3-5h

优先覆盖以下三类：
1. 物理算法正确性：以简谐振子解析解验证 leapfrog、midpoint 方法（参照 `tools/NumericalMethods.py` 已有实现）
2. 文件 I/O 往返一致性：`save_display_txt → load_display_txt` 数值不变
3. 参数验证边界：质量为 0、键长为负、NT 为 0 等异常输入

#### 13. 案例 `input.txt` 格式统一（workbuddy.md §4.1）— 1-2h

将 `case01-05` 的 `input.txt` 补充缺失字段（`save_trajectory`、`camera_*`、`move_camera`），
统一 `step_sample: 0`（新版引擎已内置抽帧），清理旧格式注释。

#### 14. `dynamics.py` 绘图功能完整重建（B2 长期修复）— 2h

从 `display.txt` 的抽帧数据重建绘图逻辑（替代依赖完整轨迹的旧实现）：

```python
disp_data = compute.load_display_txt(disp_path)
frames_x = disp_data["frames_x"]   # (n_frames, n_atoms)
# 对每帧计算能量，绘制时序图
```

能量计算需要质量等物理量，依赖阶段一 B3 修复中在 header 补充的 `atom_masses` 字段。

---

### 三个引擎的综合评价

#### C 引擎（`engines/c/main.c`）

| 维度 | 评价 |
|------|------|
| **功能完整性** | ✅ 最完整，支持 `display.txt` 直接输出，`save_trajectory` 控制 |
| **性能** | ✅ 基准引擎，比 Python 快 6-8 倍 |
| **一致性** | ✅ `save_trajectory` 默认值为 0，与 Python 行为一致 |
| **代码质量** | 🟡 JSON 解析为自行实现（非 cJSON 库），维护成本高 |
| **建议** | 作为参考实现，其他引擎向 C 引擎看齐 |

#### C++ 引擎（`engines/cpp/main.cpp`）

| 维度 | 评价 |
|------|------|
| **功能完整性** | ✅ 功能与 C 引擎相当 |
| **性能** | 🟡 编译产物 3.3 MB（远大于 C 的 504 KB），有优化空间 |
| **一致性** | ⚠️ `save_trajectory` 默认值为 1（与 C 不一致），已在阶段一修复 |
| **代码质量** | 🟡 物理算法与 C 引擎重复，应提取到公共头文件 |
| **建议** | 统一 `param.json` 默认值；考虑与 C 引擎共用物理算法头文件 |

#### Fortran 引擎（`engines/fortran/main.f90`）

| 维度 | 评价 |
|------|------|
| **功能完整性** | ❌ 不支持 `display.txt` 直接输出，使用时必然崩溃 |
| **性能** | ✅ 编译性能与 C 相当，适合大规模计算 |
| **一致性** | ❌ 输出 JSON 格式 trajectory.txt，与其他引擎行为完全不同 |
| **代码质量** | 🟡 代码结构清晰，但与 C/C++ 引擎物理算法重复 |
| **建议** | 阶段一 E3 修复为最高优先级；修复后可成为高性能替代引擎 |

---

### 综合优先级总表

| 阶段 | 编号 | 问题 | 来源 | 优先级 | 预估工时 |
|------|------|------|------|--------|---------|
| 一 | B3 | `plot_wave.py` 格式不匹配（波形动画失效） | Claude | 🔴 P0 | 1.5h |
| 一 | E3 | Fortran 引擎不支持 display.txt | 两者 | 🔴 P0 | 2h |
| 一 | B1 | `run_simulation` 内 `config` 未定义 | Claude | 🔴 P0 | 15min |
| 一 | B2 | `dynamics.py` 绘图块死代码 | Claude | 🔴 P0 | 30min |
| 一 | B4/B5 | 小错误修复（case06 文字、裸 except 等） | Claude | 🟡 P1 | 15min |
| 一 | Q1/E2 | 重复函数去除、C++ 默认值统一 | Claude | 🟡 P1 | 20min |
| 二 | P1 | 弹簧力向量化（Python 引擎 5-15x） | Claude | 🟢 P1 | 1h |
| 二 | E4 | 外部引擎校准缓存 | 两者 | 🟢 P1 | 1h |
| 二 | P2-P4 | apply_fixed_constraints 等微优化 | Claude | 🟢 P2 | 1h |
| 三 | 架构 | 全局变量封装为 SimulationState | WorkBuddy | 🔵 P2 | 3-4h |
| 三 | 架构 | compute.py 模块拆分 | WorkBuddy | 🔵 P2 | 4-6h |
| 三 | 测试 | 添加 pytest 单元测试 | WorkBuddy | 🔵 P2 | 3-5h |
| 三 | 配置 | 6 案例 input.txt 格式统一 | WorkBuddy | 🔵 P3 | 1-2h |
| 三 | 绘图 | dynamics.py 绘图功能重建 | Claude | 🔵 P3 | 2h |
| 三 | 引擎 | C/C++/Fortran 共用物理算法头文件 | WorkBuddy | 🔵 P3 | 2-4h |

---

## 附录：快速执行清单

### 今天可以完成（总计约 30 分钟的小修复）

```
[ ] examples/case06/run_dynamics.py:34  "case01" → "case06"
[ ] draw.py:97  裸 except → except (ValueError, AttributeError):
[ ] dynamics.py  删除 _load_camera_kf()，改为从 compute 导入
[ ] engines/cpp/param.json  "save_trajectory": 1 → 0
[ ] compute.py:1548-1550  config.get → 全局变量（B1 修复）
```

### 本周可以完成（核心功能恢复）

```
[ ] plot_wave.py 格式适配（B3）
[ ] dynamics.py 绘图块保护（B2 临时修复）
[ ] Fortran 引擎 display.txt 支持（E3）
[ ] 弹簧力向量化（P1）
[ ] 引擎校准缓存（E4）
```

### 下阶段规划（架构改善）

```
[ ] 全局变量 → SimulationState 类
[ ] compute.py 拆分为子模块
[ ] pytest 单元测试套件
[ ] 案例 input.txt 格式统一
```

---

*本文档综合 Claude Sonnet 4.6（`claude.md`）与 WorkBuddy（`workbuddy.md`）的分析报告生成。  
所有代码改动建议均基于静态分析，实施前请先在独立分支验证。*
