docs: 综合三方工具分析,输出最终优化方案 workbuddy_v1.md
对比 WorkBuddy/Claude/Codex 三款 AI 工具对同一代码库的 优化建议,以表格形式评价各自优劣(Bug 发现/代码质量/战略 思维/代码示例),最终整合为 6 阶段实施计划(11 人天)。
This commit is contained in:
@@ -0,0 +1,450 @@
|
|||||||
|
# Dynamics 项目优化建议
|
||||||
|
|
||||||
|
> 分析日期:2026-06-12
|
||||||
|
> 分析范围:`D:\Share\Data\aliyun-gitea\dynamics` 完整代码库
|
||||||
|
> 当前算例:`examples/case06`(120 原子一维链横波,NT=100,000 步,C 引擎)
|
||||||
|
> 另见:[`optimization/workbuddy.md`](workbuddy.md)(架构拆分、全局变量封装、测试等宏观建议)
|
||||||
|
|
||||||
|
本文档聚焦以下四类问题,不与 workbuddy.md 重复:
|
||||||
|
1. **已确认的 Bug**(可能导致运行崩溃或结果错误)
|
||||||
|
2. **Python 引擎性能**(向量化等具体代码改动)
|
||||||
|
3. **引擎一致性问题**(C/C++/Fortran 行为差异)
|
||||||
|
4. **小的代码质量问题**(错误文本、重复代码等)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、已确认的 Bug
|
||||||
|
|
||||||
|
### B1. `run_simulation()` 内引用了不在作用域的 `config` 变量 ⚠️ 严重
|
||||||
|
|
||||||
|
**文件**:[`compute.py:1548-1550`](../compute.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 问题代码(run_simulation 函数内部,config 未传入)
|
||||||
|
"camera_distance": str(config.get("camera_distance", 40.0)),
|
||||||
|
"camera_elevation": str(config.get("camera_elevation", 0)),
|
||||||
|
"camera_azimuth": str(config.get("camera_azimuth", 0)),
|
||||||
|
```
|
||||||
|
|
||||||
|
`run_simulation()` 是独立函数,不接收 `config` 参数,但函数内部直接使用了 `run_from_config()` 的局部变量 `config`。只要使用 Python 引擎(`engine: python`)就会触发 `NameError`。
|
||||||
|
|
||||||
|
**修复方案**:这三个字段的值已在 `run_from_config` 里读取并存入全局变量(`camera_keyframes_raw` 已有),
|
||||||
|
对 `camera_distance/elevation/azimuth` 同样声明为全局变量并在 `run_from_config` 中赋值:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 在模块顶部全局变量区添加(compute.py ~65 行附近)
|
||||||
|
camera_distance = 40.0
|
||||||
|
camera_elevation = 0
|
||||||
|
camera_azimuth = 0
|
||||||
|
|
||||||
|
# 在 run_from_config 中赋值(~756 行附近,已有 use_marker 赋值的位置)
|
||||||
|
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_simulation 内改为读全局变量
|
||||||
|
"camera_distance": str(camera_distance),
|
||||||
|
"camera_elevation": str(camera_elevation),
|
||||||
|
"camera_azimuth": str(camera_azimuth),
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### B2. `dynamics.py` 绘图块引用未定义的变量 ⚠️ 严重
|
||||||
|
|
||||||
|
**文件**:[`dynamics.py:330-451`](../dynamics.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# step_plot=1 时触发,但这些变量从未赋值
|
||||||
|
time_arr = np.arange(NT) * DT # NT、DT 在此作用域未定义
|
||||||
|
n_atoms = all_x.shape[1] # all_x 不存在
|
||||||
|
atom_ids_list = data.get("atom_ids", ...) # data 不存在
|
||||||
|
```
|
||||||
|
|
||||||
|
这段绘图代码写于旧版(`run_simulation` 返回完整轨迹时),现在 `run_from_config` 已不再在此作用域填充这些变量,整块代码是失效的死代码。当前所有案例恰好设置 `step_plot: 0` 所以不触发,但一旦用户设为 1 就崩溃。
|
||||||
|
|
||||||
|
**修复方案**:从 `run_from_config` 的返回值读取数据重构绘图代码,或暂时添加保护:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 临时保护(快速修复)
|
||||||
|
if not no_plot and config.get("step_plot", 1):
|
||||||
|
print("[run] 警告: step_plot 功能暂未实现,已跳过")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### B3. `plot_wave.py` 使用旧 JSON 格式字段读取新格式 display.txt ⚠️ 严重
|
||||||
|
|
||||||
|
**文件**:[`plot_wave.py:22-27`](../plot_wave.py) 和 [`plot_wave.py:96-111`](../plot_wave.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# plot_wave.py 的 load_disp_data()
|
||||||
|
def load_disp_data(output_dir):
|
||||||
|
return compute.load_text_data(disp_path) # 调用了 JSON 格式的加载函数!
|
||||||
|
|
||||||
|
# 然后访问旧 JSON 字段名
|
||||||
|
n_frames = int(data["n_frames"]) # 新格式没有这个键
|
||||||
|
x = np.array(data["disp_all_x"]) # 新格式没有这个键
|
||||||
|
masses = np.array(data["atom_masses"]) # 新格式没有这个键
|
||||||
|
```
|
||||||
|
|
||||||
|
但 `display.txt` 现在已是新文本格式(由 `save_display_txt` 写出),正确的加载函数是 `compute.load_display_txt()`,且字段名完全不同(`frames_x` 而非 `disp_all_x`)。
|
||||||
|
|
||||||
|
**影响**:`step_plot_wave: 1` 时必然崩溃,case05/case06 的波形动画功能完全失效。
|
||||||
|
|
||||||
|
**修复方案**:将 `load_disp_data` 改为调用 `load_display_txt`,并适配字段名:
|
||||||
|
|
||||||
|
```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) # 改这里
|
||||||
|
|
||||||
|
# 后续访问字段名也需同步修改:
|
||||||
|
# data["disp_all_x"] → disp_data["frames_x"]
|
||||||
|
# data["n_frames"] → disp_data["frames_x"].shape[0]
|
||||||
|
# data["atom_masses"] → 从 header_fields 读取或从 bond/coord 文件读
|
||||||
|
```
|
||||||
|
|
||||||
|
> **注**:`plot_wave.py` 依赖 `atom_masses`、`bond_pairs` 等物理量,这些在新的 display.txt 格式中
|
||||||
|
> 通过 `header_fields` 字符串存储,需要额外解析。较彻底的修复需要在 display.txt header 中保留
|
||||||
|
> `atom_masses` 序列(目前 `atom_radii` 已以逗号分隔字符串保存,可仿照此方式)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### B4. `run_dynamics.py`(case06)描述文字错误
|
||||||
|
|
||||||
|
**文件**:[`examples/case06/run_dynamics.py:34`](../examples/case06/run_dynamics.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 当前(错误)
|
||||||
|
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case01")
|
||||||
|
|
||||||
|
# 应为
|
||||||
|
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case06")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### B5. `draw.py` 裸 `except` 吞掉所有异常
|
||||||
|
|
||||||
|
**文件**:[`draw.py:97`](../draw.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
try:
|
||||||
|
alpha_list = [float(x) for x in raw_alpha.split(",")]
|
||||||
|
if len(alpha_list) != 6:
|
||||||
|
alpha_list = alpha_list * 6
|
||||||
|
except: # 裸 except:连 KeyboardInterrupt 都会捕获
|
||||||
|
alpha_list = [float(raw_alpha)] * 6
|
||||||
|
```
|
||||||
|
|
||||||
|
**修复**:改为 `except (ValueError, AttributeError):`,并加 `print` 提示。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、Python 引擎性能优化
|
||||||
|
|
||||||
|
> **背景**:case06 使用 C 引擎,以下优化主要影响 `engine: python` 路径。
|
||||||
|
> 当前 Python vs C 速度比约为 6-8 倍(100,000 步,120 原子)。
|
||||||
|
|
||||||
|
### P1. 弹簧力计算向量化(最大收益)
|
||||||
|
|
||||||
|
**文件**:[`compute.py:1253-1277`](../compute.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 当前:Python for 循环,119 键 × 100,000 步 ≈ 1200 万次迭代
|
||||||
|
for bond_idx, (idx_1, idx_2) in enumerate(BOND_PAIRS):
|
||||||
|
dx = x[idx_2] - x[idx_1]
|
||||||
|
dy = y[idx_2] - y[idx_1]
|
||||||
|
dz = z[idx_2] - z[idx_1]
|
||||||
|
dist = np.sqrt(dx * dx + dy * dy + dz * dz)
|
||||||
|
if dist <= 1e-12:
|
||||||
|
continue
|
||||||
|
stretch = dist - BOND_REST_LENGTHS[bond_idx]
|
||||||
|
force_mag = BOND_STIFFNESS[bond_idx] * stretch
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**向量化版本**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if ELASTIC_FORCE and BOND_PAIRS is not None and len(BOND_PAIRS) > 0:
|
||||||
|
i_arr = BOND_PAIRS[:, 0] # (n_bonds,)
|
||||||
|
j_arr = BOND_PAIRS[:, 1]
|
||||||
|
dx = x[j_arr] - x[i_arr]
|
||||||
|
dy = y[j_arr] - y[i_arr]
|
||||||
|
dz = z[j_arr] - z[i_arr]
|
||||||
|
dist = np.sqrt(dx * dx + dy * dy + dz * dz)
|
||||||
|
valid = dist > 1e-12
|
||||||
|
inv_dist = np.where(valid, 1.0 / np.maximum(dist, 1e-12), 0.0)
|
||||||
|
stretch = dist - BOND_REST_LENGTHS
|
||||||
|
force_mag = BOND_STIFFNESS * stretch * inv_dist # 归一化后的分量力幅
|
||||||
|
fx_bond = force_mag * dx
|
||||||
|
fy_bond = force_mag * dy
|
||||||
|
fz_bond = force_mag * dz
|
||||||
|
np.add.at(fx, i_arr, fx_bond)
|
||||||
|
np.add.at(fx, j_arr, -fx_bond)
|
||||||
|
np.add.at(fy, i_arr, fy_bond)
|
||||||
|
np.add.at(fy, j_arr, -fy_bond)
|
||||||
|
np.add.at(fz, i_arr, fz_bond)
|
||||||
|
np.add.at(fz, j_arr, -fz_bond)
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期加速**:Python 引擎整体 **5-15 倍**(对于链状键合体系,弹簧力是瓶颈)。
|
||||||
|
|
||||||
|
> `np.add.at` 支持重复索引的原子累加,对有分叉键的复杂体系也正确。
|
||||||
|
> 若键无重复端点(链状),可改为 `fx[i_arr] += fx_bond; fx[j_arr] -= fx_bond` 更快。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P2. `apply_fixed_constraints()` 减少临时数组分配
|
||||||
|
|
||||||
|
**文件**:[`compute.py:1408-1418`](../compute.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 当前:每步创建 2 个 (n_atoms, 3) 的临时数组
|
||||||
|
positions = np.column_stack((x, y, z))
|
||||||
|
velocities = np.column_stack((vx, vy, vz))
|
||||||
|
positions = np.where(fixed, ATOM_POSITIONS, positions)
|
||||||
|
velocities = np.where(fixed, 0.0, velocities)
|
||||||
|
return positions[:, 0], positions[:, 1], positions[:, 2], ...
|
||||||
|
```
|
||||||
|
|
||||||
|
对于 case06(x/y 全部固定,z 自由),每步都在做 120 原子 × 3 列的 `column_stack`,但实际上只有 z 方向的原子可以运动。
|
||||||
|
|
||||||
|
**优化版本**(预先计算掩码,直接原地修改):
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 在模块初始化时(run_from_config 中)预计算固定掩码
|
||||||
|
_FIXED_MASK_X = ATOM_FIXED[:, 0] != 0 # bool 数组,提前缓存
|
||||||
|
_FIXED_MASK_Y = ATOM_FIXED[:, 1] != 0
|
||||||
|
_FIXED_MASK_Z = ATOM_FIXED[:, 2] != 0
|
||||||
|
|
||||||
|
def apply_fixed_constraints(x, y, z, vx, vy, vz):
|
||||||
|
if np.any(_FIXED_MASK_X):
|
||||||
|
x[_FIXED_MASK_X] = ATOM_POSITIONS[_FIXED_MASK_X, 0]
|
||||||
|
vx[_FIXED_MASK_X] = 0.0
|
||||||
|
if np.any(_FIXED_MASK_Y):
|
||||||
|
y[_FIXED_MASK_Y] = ATOM_POSITIONS[_FIXED_MASK_Y, 1]
|
||||||
|
vy[_FIXED_MASK_Y] = 0.0
|
||||||
|
if np.any(_FIXED_MASK_Z):
|
||||||
|
z[_FIXED_MASK_Z] = ATOM_POSITIONS[_FIXED_MASK_Z, 2]
|
||||||
|
vz[_FIXED_MASK_Z] = 0.0
|
||||||
|
return x, y, z, vx, vy, vz
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P3. `run_simulation()` 中冗余的 `frame_indices` 列表
|
||||||
|
|
||||||
|
**文件**:[`compute.py:1470,1510`](../compute.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
frame_indices = []
|
||||||
|
...
|
||||||
|
frame_indices.append(step) # 每 NSTEP 步追加一次,仅用于最后计算 n_frames_actual
|
||||||
|
|
||||||
|
# 实际上等价于:
|
||||||
|
n_frames_actual = record_steps // NSTEP # 直接计算,O(1)
|
||||||
|
```
|
||||||
|
|
||||||
|
100,000 步中约有 1,000 次 `append`,开销小但无必要。删除 `frame_indices` 列表,用计数器替代。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P4. `apply_driving_force()` 内部创建临时 `t_vec`
|
||||||
|
|
||||||
|
**文件**:[`compute.py:630`](../compute.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 每次调用都分配一个 3 元素 array
|
||||||
|
t_vec = np.array([t, t, t], dtype=np.float64)
|
||||||
|
pos_drive = d["amp"] * np.cos(2.0 * np.pi * d["freq"] * t_vec + d["phi"])
|
||||||
|
```
|
||||||
|
|
||||||
|
`t` 是标量,`d["freq"]` 和 `d["phi"]` 是 (3,) 数组,直接用标量广播即可:
|
||||||
|
|
||||||
|
```python
|
||||||
|
pos_drive = d["amp"] * np.cos(2.0 * np.pi * d["freq"] * t + d["phi"])
|
||||||
|
vel_drive = -d["amp"] * 2.0 * np.pi * d["freq"] * np.sin(2.0 * np.pi * d["freq"] * t + d["phi"])
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P5. `GRAVITY_INTERACTION` 的 O(N²) 双重循环
|
||||||
|
|
||||||
|
**文件**:[`compute.py:1279-1297`](../compute.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 当前:纯 Python 双重循环
|
||||||
|
for i in range(n):
|
||||||
|
for j in range(i + 1, n):
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**向量化方案**(适用于 N 不太大时):
|
||||||
|
|
||||||
|
```python
|
||||||
|
if GRAVITY_INTERACTION:
|
||||||
|
# 构造所有原子对的差向量
|
||||||
|
xi = x[:, np.newaxis]; xj = x[np.newaxis, :]
|
||||||
|
yi = y[:, np.newaxis]; yj = y[np.newaxis, :]
|
||||||
|
zi = z[:, np.newaxis]; zj = z[np.newaxis, :]
|
||||||
|
r2 = (xj - xi)**2 + (yj - yi)**2 + (zj - zi)**2
|
||||||
|
np.fill_diagonal(r2, np.inf) # 排除自身
|
||||||
|
r = np.sqrt(r2)
|
||||||
|
mi = m[:, np.newaxis]; mj = m[np.newaxis, :]
|
||||||
|
# 力幅(标量,上三角)
|
||||||
|
f_mag = GRAVITY_STRENGTH * mi * mj / r2
|
||||||
|
fx_ij = f_mag * (xj - xi) / r # (n, n) 矩阵
|
||||||
|
fy_ij = f_mag * (yj - yi) / r
|
||||||
|
fz_ij = f_mag * (zj - zi) / r
|
||||||
|
fx += np.sum(fx_ij - fx_ij.T, axis=1) * 0.5 # 利用反对称性
|
||||||
|
fy += np.sum(fy_ij - fy_ij.T, axis=1) * 0.5
|
||||||
|
fz += np.sum(fz_ij - fz_ij.T, axis=1) * 0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
> 此向量化在 N ≤ ~500 时比 Python 循环快约 20 倍;N > 1000 时内存占用高(N² 矩阵),
|
||||||
|
> 需要分块或使用 Barnes-Hut 树算法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、引擎一致性问题
|
||||||
|
|
||||||
|
### E1. 弹性力计算:Python 与 C 引擎的差异
|
||||||
|
|
||||||
|
**文件**:[`compute.py:1273-1277`](../compute.py) vs [`engines/c/main.c`](../engines/c/main.c)
|
||||||
|
|
||||||
|
Python 引擎将整根键的弹性势能 `½k(d-d₀)²` 记给原子 `i`(不均分),而 C/C++ 引擎均分给两端。
|
||||||
|
这不影响力的计算(力相同),但如果未来基于势能做分析,两个引擎的势能分配会不同。
|
||||||
|
建议注释中明确说明约定。
|
||||||
|
|
||||||
|
### E2. C++ 引擎 `save_trajectory` 默认值为 1
|
||||||
|
|
||||||
|
**文件**:[`engines/cpp/param.json`](../engines/cpp/param.json)
|
||||||
|
|
||||||
|
```json
|
||||||
|
// engines/c/param.json: "save_trajectory": 0
|
||||||
|
// engines/cpp/param.json: "save_trajectory": 1 ← 与 C 不一致
|
||||||
|
```
|
||||||
|
|
||||||
|
用户切换引擎时行为不同,可能意外生成大文件。建议统一为 0。
|
||||||
|
|
||||||
|
### E3. Fortran 引擎不支持 `save_trajectory=0`
|
||||||
|
|
||||||
|
**文件**:[`engines/fortran/main.f90`](../engines/fortran/main.f90)
|
||||||
|
|
||||||
|
Fortran 引擎总是输出 JSON 格式的 `trajectory.txt`,不支持直接写 `display.txt`。
|
||||||
|
workbuddy.md 中已将此列为 P0,此处确认影响范围:
|
||||||
|
- case 使用 `engine: fortran` 时必然崩溃(Python 侧期待 `display.txt`)
|
||||||
|
- 应参照 C 引擎的 `write_display_txt` 函数实现
|
||||||
|
|
||||||
|
### E4. 外部引擎每次运行都重新校准
|
||||||
|
|
||||||
|
**文件**:[`compute.py:924-948`](../compute.py)
|
||||||
|
|
||||||
|
每次运行前跑 `min(1000, NT//10)` 步测速。对于 `NT=100,000` 的 case06,校准跑 10,000 步,
|
||||||
|
占总运算量的 10%。
|
||||||
|
|
||||||
|
**快速修复**(跳过短运行的校准):
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 当总步数 < 5000 时跳过校准,直接用默认估计
|
||||||
|
if total_steps < 5000:
|
||||||
|
_step_time = 1e-5 # 合理的保守估计
|
||||||
|
_est_total = _step_time * total_steps
|
||||||
|
else:
|
||||||
|
# 现有校准逻辑...
|
||||||
|
```
|
||||||
|
|
||||||
|
**彻底修复**:缓存校准结果到 `engines/{engine}/_calib_cache.json`,key 为 `(n_atoms, method)`,
|
||||||
|
命中时跳过校准(workbuddy.md §2.1 有详细代码示例)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、代码质量问题
|
||||||
|
|
||||||
|
### Q1. 相机路径解析函数重复
|
||||||
|
|
||||||
|
[`dynamics.py:35-69`](../dynamics.py) 的 `_load_camera_kf()` 与
|
||||||
|
[`compute.py:90-136`](../compute.py) 的 `_load_camera_motion()` 是功能完全相同的函数,
|
||||||
|
仅变量名略有差异。
|
||||||
|
|
||||||
|
**建议**:保留 `compute.py` 中的版本,`dynamics.py` 改为从 `compute` 导入:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# dynamics.py
|
||||||
|
from compute import _load_camera_motion as _load_camera_kf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q2. `load_parameters()` 函数已废弃但仍保留
|
||||||
|
|
||||||
|
**文件**:[`compute.py:1123-1199`](../compute.py)
|
||||||
|
|
||||||
|
这是老版通过 `key = value` 文本格式读参数的函数,现在所有案例已改用 YAML。
|
||||||
|
只有 `compute.py` 的 `main()` 还调用它,而 `compute.main()` 本身也已被 `dynamics.py` 取代。
|
||||||
|
|
||||||
|
整个函数(77 行)加上 `main()`(25 行)都可以安全删除,或移入 `tools/` 目录存档。
|
||||||
|
|
||||||
|
### Q3. 边界条件逻辑不一致
|
||||||
|
|
||||||
|
**文件**:[`compute.py:1380-1387`](../compute.py) vs [`compute.py:1420-1428`](../compute.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def Limit_in_box(a, amin, amax, va):
|
||||||
|
# 反弹:碰壁时速度反向(弹性碰撞)
|
||||||
|
va = np.where(over | under, -va, va)
|
||||||
|
|
||||||
|
def wrap_position(x, y, z):
|
||||||
|
# 周期性边界:从一侧出去从另一侧进入
|
||||||
|
x = np.where(x > X_MAX, X_MIN, x)
|
||||||
|
```
|
||||||
|
|
||||||
|
在 `run_simulation` 中两者都被调用:先通过 `apply_motion_update()` 执行 `Limit_in_box`(反弹),
|
||||||
|
再执行 `wrap_position()`(周期)。顺序执行时,`Limit_in_box` 已把粒子限制在盒内,
|
||||||
|
`wrap_position` 实际上永远不会触发——但这一逻辑关系无文档说明,容易误导读者。
|
||||||
|
|
||||||
|
**建议**:在 `run_simulation` 中加一行注释说明两者的分工,或删除其中一个。
|
||||||
|
|
||||||
|
### Q4. 案例说明文字未更新(case06)
|
||||||
|
|
||||||
|
**文件**:[`examples/case06/run_dynamics.py:34`](../examples/case06/run_dynamics.py)
|
||||||
|
|
||||||
|
见 B4,在此重申。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、总结与优先级
|
||||||
|
|
||||||
|
| 编号 | 问题 | 优先级 | 预估工作量 |
|
||||||
|
|------|------|--------|-----------|
|
||||||
|
| B1 | `run_simulation` 内 `config` 未定义 | 🔴 立即 | 15 min |
|
||||||
|
| B2 | `dynamics.py` 绘图块死代码(NameError) | 🔴 立即 | 30 min |
|
||||||
|
| B3 | `plot_wave.py` 使用旧格式加载新 display.txt | 🔴 立即 | 1-2 h |
|
||||||
|
| B4 | case06 描述文字写着 case01 | 🟡 低 | 1 min |
|
||||||
|
| B5 | `draw.py` 裸 except | 🟡 低 | 5 min |
|
||||||
|
| P1 | 弹簧力向量化(Python 引擎) | 🟢 中 | 1 h |
|
||||||
|
| P2 | `apply_fixed_constraints` 减少临时数组 | 🟢 低 | 30 min |
|
||||||
|
| P3 | `frame_indices` 列表改计数器 | 🟢 低 | 5 min |
|
||||||
|
| P4 | `apply_driving_force` 去掉 `t_vec` | 🟢 低 | 5 min |
|
||||||
|
| P5 | `GRAVITY_INTERACTION` O(N²) 向量化 | 🟢 中 | 1 h |
|
||||||
|
| E1 | 势能归属约定加注释 | 🟡 低 | 10 min |
|
||||||
|
| E2 | C++ `save_trajectory` 默认值统一 | 🟡 低 | 5 min |
|
||||||
|
| E3 | Fortran 引擎支持 display.txt 输出 | 🔴 高 | 2-3 h |
|
||||||
|
| E4 | 外部引擎校准缓存 | 🟡 中 | 1-2 h |
|
||||||
|
| Q1 | 相机解析函数去重 | 🟡 低 | 15 min |
|
||||||
|
| Q2 | 废弃的 `load_parameters` + `main()` 删除 | 🟡 低 | 15 min |
|
||||||
|
| Q3 | 边界条件逻辑加注释 | 🟡 低 | 10 min |
|
||||||
|
| Q4 | case06 描述文字 | 🟢 低 | 1 min |
|
||||||
|
|
||||||
|
**最高回报的三件事(按投入产出比排序)**:
|
||||||
|
|
||||||
|
1. **B3**(`plot_wave.py` 格式不匹配):修复后波形动画功能恢复,影响 case05/case06
|
||||||
|
2. **B1**(`config` 未定义):修复后 Python 引擎可完整运行 case06
|
||||||
|
3. **P1**(弹簧力向量化):修复后 Python 引擎速度提升 5-15 倍,适合大规模测试和教学演示
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*本文档由 Claude Sonnet 4.6 自动生成,基于代码静态分析,未实际运行测试验证。建议在实施前先运行相关案例确认 Bug 现象。*
|
||||||
@@ -0,0 +1,351 @@
|
|||||||
|
# Dynamics 项目优化建议
|
||||||
|
|
||||||
|
本文基于对以下文件的静态检查整理:
|
||||||
|
|
||||||
|
- `compute.py`
|
||||||
|
- `dynamics.py`
|
||||||
|
- `draw.py`
|
||||||
|
- `engines/c/main.c`
|
||||||
|
- `README.md`
|
||||||
|
|
||||||
|
目标不是泛泛地“提速”,而是优先找出这个项目当前最可能影响性能、内存占用、可维护性和后续扩展效率的点,并给出按优先级排序的改进方向。
|
||||||
|
|
||||||
|
## 一、整体判断
|
||||||
|
|
||||||
|
这个项目现在的架构已经有一个很好的基础:`dynamics.py` 做流程编排,`compute.py` 做 Python 参考实现,`engines/c/main.c` 提供高性能引擎,`draw.py` 负责可视化。
|
||||||
|
|
||||||
|
从代码结构看,当前主要瓶颈不在“外层调度”,而集中在三类地方:
|
||||||
|
|
||||||
|
1. `compute.py` 中逐步积分时的 Python 层循环。
|
||||||
|
2. `compute.py` 中弹簧力/粒子间引力的双层或逐键循环。
|
||||||
|
3. `display.txt` / `trajectory.txt` 的文本格式读写。
|
||||||
|
|
||||||
|
如果后续案例规模继续增大,Python 参考引擎会很快被这三处放大;而且即便切到 C 引擎,文本 I/O 仍然会成为新的主要瓶颈。
|
||||||
|
|
||||||
|
## 二、最高优先级建议
|
||||||
|
|
||||||
|
## 1. 先把“计算核心”和“输出格式”分开优化
|
||||||
|
|
||||||
|
当前项目已经有多语言引擎,这是很正确的方向。建议把优化目标拆成两条线分别推进:
|
||||||
|
|
||||||
|
- 计算性能:优先优化 `compute.py` 中的力计算和积分主循环。
|
||||||
|
- I/O 性能:优先替换或补充 `display.txt` / `trajectory.txt` 的文本格式。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 计算核心的瓶颈主要是 CPU。
|
||||||
|
- 轨迹输出的瓶颈主要是字符串格式化、磁盘写入、加载解析。
|
||||||
|
- 这两类优化手段完全不同,混在一起做容易看不出收益来源。
|
||||||
|
|
||||||
|
建议先增加一个简单的 profiling 开关,例如输出:
|
||||||
|
|
||||||
|
- 总模拟时间
|
||||||
|
- 力计算时间
|
||||||
|
- 抽帧时间
|
||||||
|
- 保存 `display.txt` 时间
|
||||||
|
- 保存 `trajectory.txt` 时间
|
||||||
|
- 加载 `display.txt` 时间
|
||||||
|
|
||||||
|
这样后面每做一次优化,都能知道收益来自哪一段。
|
||||||
|
|
||||||
|
## 2. 优先向量化 `compute_force()` 里的弹簧键计算
|
||||||
|
|
||||||
|
`compute.py:1230` 开始的 `compute_force()` 是 Python 引擎最关键的热点。
|
||||||
|
|
||||||
|
其中弹簧键部分目前是:
|
||||||
|
|
||||||
|
- 遍历 `BOND_PAIRS`
|
||||||
|
- 每根键单独算 `dx/dy/dz`
|
||||||
|
- 每根键单独回写两个原子的受力
|
||||||
|
|
||||||
|
这在键数增大后会变成明显瓶颈。
|
||||||
|
|
||||||
|
建议做法:
|
||||||
|
|
||||||
|
- 把 `BOND_PAIRS[:, 0]` 和 `BOND_PAIRS[:, 1]` 拆成两个索引数组 `i_idx`、`j_idx`。
|
||||||
|
- 用 NumPy 一次性计算所有键的 `dx/dy/dz/dist/stretch`。
|
||||||
|
- 用 `np.add.at()` 或等价的 scatter 累加方式,把键力一次性回写到 `fx/fy/fz`。
|
||||||
|
|
||||||
|
预期收益:
|
||||||
|
|
||||||
|
- 中等规模体系下,Python 参考引擎会有很可观的提速。
|
||||||
|
- 这也是最不改变项目结构、最容易验证正确性的优化。
|
||||||
|
|
||||||
|
## 3. 把原子间万有引力视为“可选高成本模块”,不要默认走 Python 双层循环
|
||||||
|
|
||||||
|
`compute.py:1281` 开始的 `GRAVITY_INTERACTION` 采用 `for i` / `for j` 双层循环,复杂度是 `O(N^2)`。
|
||||||
|
|
||||||
|
这段代码在教学上没问题,但在稍大的粒子数下会非常慢。建议:
|
||||||
|
|
||||||
|
- 明确把它标记为“仅适合小规模案例”。
|
||||||
|
- 默认推荐用户用 `engine: c` 跑这类 case。
|
||||||
|
- 如果未来仍想保留 Python 版本,优先考虑:
|
||||||
|
- 小规模时保持现状。
|
||||||
|
- 中大规模时改成 Numba 或 C 扩展。
|
||||||
|
- 更进一步再考虑 Barnes-Hut、cell list、neighbor list 之类近似/加速算法。
|
||||||
|
|
||||||
|
如果只是当前项目阶段,我更建议:
|
||||||
|
|
||||||
|
- 不要先在 Python 里硬做复杂天体优化。
|
||||||
|
- 先把这类高复杂度场景明确导向 C 引擎。
|
||||||
|
|
||||||
|
这是投入产出比更高的路线。
|
||||||
|
|
||||||
|
## 4. `display.txt` 文本格式要补一个二进制版本
|
||||||
|
|
||||||
|
`compute.py:196` 的 `save_display_txt()` 和 `compute.py:239` 的 `load_display_txt()` 已经比 JSON 好很多,但本质仍然是大文本。
|
||||||
|
|
||||||
|
当前问题:
|
||||||
|
|
||||||
|
- 保存时每个数都在做字符串格式化。
|
||||||
|
- 读取时虽然用了 `np.genfromtxt`,但源数据仍是文本。
|
||||||
|
- 帧数和粒子数继续增大后,I/O 会成为明显瓶颈。
|
||||||
|
|
||||||
|
建议保留 `display.txt` 作为“人可读格式”,同时新增一个高性能格式,例如:
|
||||||
|
|
||||||
|
- `display.npz`
|
||||||
|
- 或 `display.npy` + `meta.json`
|
||||||
|
|
||||||
|
推荐方案:
|
||||||
|
|
||||||
|
- `frames_x/y/z/vx/vy/vz` 存到 `np.savez_compressed`
|
||||||
|
- 元信息单独存一个轻量 `meta.json`
|
||||||
|
- `draw.py` 优先读二进制,不存在时再回退到 `display.txt`
|
||||||
|
|
||||||
|
这样有几个好处:
|
||||||
|
|
||||||
|
- 调试和教学仍可保留文本文件。
|
||||||
|
- 大规模运行时可以直接避开文本解析开销。
|
||||||
|
- Python 和 C 引擎都可以逐步迁移,不需要一次切完。
|
||||||
|
|
||||||
|
## 三、中优先级建议
|
||||||
|
|
||||||
|
## 5. `run_simulation()` 里完整轨迹缓存要改成“按需流式写出”
|
||||||
|
|
||||||
|
`compute.py:1481` 起,如果 `save_trajectory=1`,会一次性申请:
|
||||||
|
|
||||||
|
- `traj_x`
|
||||||
|
- `traj_y`
|
||||||
|
- `traj_z`
|
||||||
|
- `traj_vx`
|
||||||
|
- `traj_vy`
|
||||||
|
- `traj_vz`
|
||||||
|
|
||||||
|
这意味着内存复杂度接近 `O(steps * atoms)`,而且还是 6 份 `float64` 数组。
|
||||||
|
|
||||||
|
这在教学小案例里没问题,但一旦:
|
||||||
|
|
||||||
|
- `NT` 很大
|
||||||
|
- 粒子数上来
|
||||||
|
- 同时还保留抽帧缓存
|
||||||
|
|
||||||
|
内存会膨胀得很快。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 如果只是为了最终导出,改成边算边写。
|
||||||
|
- 如果还需要后续随机访问,可以改成 `memmap` 或分块写入 `npz/hdf5/zarr`。
|
||||||
|
|
||||||
|
推荐优先级:
|
||||||
|
|
||||||
|
- 先做“保存完整轨迹时改为 chunked binary writer”。
|
||||||
|
- `trajectory.txt` 保留为兼容输出,而不是默认主输出。
|
||||||
|
|
||||||
|
## 6. `draw.py` 的 Marker 更新应一次性切片赋值,避免逐原子 Python 循环
|
||||||
|
|
||||||
|
`draw.py:517` 附近的 `_update_atom_positions()` 在 `USE_MARKER` 模式下仍然逐原子循环:
|
||||||
|
|
||||||
|
- 先 `for i in range(N_ATOMS)`
|
||||||
|
- 再逐个写 `marker_pos[i]`
|
||||||
|
- 然后 `balls.set_data(pos=marker_pos)`
|
||||||
|
|
||||||
|
建议直接改成:
|
||||||
|
|
||||||
|
- `marker_pos[:, 0] = DISP_ALL_X[f_idx]`
|
||||||
|
- `marker_pos[:, 1] = DISP_ALL_Y[f_idx]`
|
||||||
|
- `marker_pos[:, 2] = DISP_ALL_Z[f_idx]`
|
||||||
|
|
||||||
|
这样更符合 Marker 模式“批量更新”的初衷。
|
||||||
|
|
||||||
|
同理,成键线 `_update_bond_positions()` 也可以进一步尝试批量索引构造,而不是逐键更新。
|
||||||
|
|
||||||
|
虽然这部分通常不如计算核心慢,但在大粒子数动画里会直接影响帧率。
|
||||||
|
|
||||||
|
## 7. `apply_fixed_constraints()` 每步构造 `column_stack`,可以改成原地掩码写回
|
||||||
|
|
||||||
|
`compute.py:1408` 的实现每步都会:
|
||||||
|
|
||||||
|
- `np.column_stack((x, y, z))`
|
||||||
|
- `np.column_stack((vx, vy, vz))`
|
||||||
|
- `np.where(...)`
|
||||||
|
|
||||||
|
这会产生额外临时数组。
|
||||||
|
|
||||||
|
建议改成:
|
||||||
|
|
||||||
|
- 预先缓存 `fixed_x/fixed_y/fixed_z` 三个布尔掩码
|
||||||
|
- 直接对 `x/y/z/vx/vy/vz` 原地赋值
|
||||||
|
|
||||||
|
例如思路上改成:
|
||||||
|
|
||||||
|
- `x[fixed_x] = ATOM_POSITIONS[fixed_x, 0]`
|
||||||
|
- `vx[fixed_x] = 0.0`
|
||||||
|
|
||||||
|
这类优化单项收益不一定最大,但由于它在每一步都执行,累计下来是有价值的。
|
||||||
|
|
||||||
|
## 8. `apply_driving_force()` 有重复小数组分配
|
||||||
|
|
||||||
|
`compute.py:599` 的驱动力逻辑中,每个 driver、每一步都构造:
|
||||||
|
|
||||||
|
- `t_vec = np.array([t, t, t], dtype=np.float64)`
|
||||||
|
|
||||||
|
这是典型的小对象重复分配。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 直接分别计算三个轴,不需要生成 `t_vec`
|
||||||
|
- 或预先把 driver 参数整理成矩阵,批量更新受驱原子
|
||||||
|
|
||||||
|
如果 driver 数量很少,这不是最大瓶颈;但这类微优化很容易做,而且不会增加复杂度。
|
||||||
|
|
||||||
|
## 四、架构与可维护性建议
|
||||||
|
|
||||||
|
## 9. 减少 `compute.py` 的全局变量依赖,逐步收敛到状态对象
|
||||||
|
|
||||||
|
目前 `compute.py` 依赖大量全局变量,例如:
|
||||||
|
|
||||||
|
- `ATOM_IDS`
|
||||||
|
- `ATOM_MASSES`
|
||||||
|
- `BOND_PAIRS`
|
||||||
|
- `METHOD`
|
||||||
|
- `NT`
|
||||||
|
- `DT`
|
||||||
|
|
||||||
|
这让代码在以下场景下会越来越难维护:
|
||||||
|
|
||||||
|
- 并行运行多个 case
|
||||||
|
- 写单元测试
|
||||||
|
- 替换不同 force model
|
||||||
|
- 将来做 GUI 或服务化封装
|
||||||
|
|
||||||
|
建议中期做一个 `SimulationState` / `SimulationConfig` / `SystemData` 分层:
|
||||||
|
|
||||||
|
- 配置类:步长、方法、开关、输出选项
|
||||||
|
- 系统类:原子、键、边界、驱动参数
|
||||||
|
- 状态类:当前 `x/y/z/vx/vy/vz`
|
||||||
|
|
||||||
|
不需要一次性重构完,先从最核心的 `compute_force()`、`run_simulation()` 入手即可。
|
||||||
|
|
||||||
|
## 10. Python 参考引擎和 C 引擎的“功能等价层”建议更明确
|
||||||
|
|
||||||
|
README 里已经强调了多语言对比,这是项目亮点。为了后续更稳,建议把“等价层”标准化:
|
||||||
|
|
||||||
|
- 相同输入
|
||||||
|
- 相同输出语义
|
||||||
|
- 相同采样规则
|
||||||
|
- 相同边界行为
|
||||||
|
- 相同 driver 行为
|
||||||
|
|
||||||
|
然后做一个最小一致性测试集:
|
||||||
|
|
||||||
|
- case01-case06 都能跑
|
||||||
|
- Python 与 C 输出在容差内一致
|
||||||
|
- 不同 method 的结果有基准对照
|
||||||
|
|
||||||
|
这样后续做任何优化时,都更容易大胆改,不怕悄悄改坏物理行为。
|
||||||
|
|
||||||
|
## 五、需要尽快处理的正确性/维护风险
|
||||||
|
|
||||||
|
这些不一定直接是“性能问题”,但会影响后续优化效率,建议优先修一下。
|
||||||
|
|
||||||
|
## 11. `dynamics.py` 的绘图分支存在明显变量依赖不完整的风险
|
||||||
|
|
||||||
|
在 `dynamics.py:331` 一带,绘图代码直接使用:
|
||||||
|
|
||||||
|
- `all_x`
|
||||||
|
- `all_y`
|
||||||
|
- `all_z`
|
||||||
|
- `all_vx`
|
||||||
|
- `all_vy`
|
||||||
|
- `all_vz`
|
||||||
|
- `data`
|
||||||
|
|
||||||
|
但从当前文件上下文看,这些变量只在某些分支里才会存在,尤其 Python 引擎路径下很可能未定义。
|
||||||
|
|
||||||
|
这会带来两个问题:
|
||||||
|
|
||||||
|
- 某些 case 可能直接在绘图阶段报错。
|
||||||
|
- 优化时很难判断问题来自性能还是流程分支。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 在进入绘图逻辑前统一构造标准数据对象。
|
||||||
|
- 不要依赖分支里“顺便留下来的局部变量”。
|
||||||
|
|
||||||
|
## 12. `README.md` 描述与当前实现已经有部分不一致
|
||||||
|
|
||||||
|
例如 README 仍强调:
|
||||||
|
|
||||||
|
- `trajectory.txt (JSON, 统一格式)`
|
||||||
|
- `sample.py -> display.txt`
|
||||||
|
|
||||||
|
但当前实现里:
|
||||||
|
|
||||||
|
- Python 路径已经直接写 `display.txt`
|
||||||
|
- `save_trajectory` 变成可选
|
||||||
|
- `sample.py` 在主流程中的角色已经下降
|
||||||
|
|
||||||
|
文档不一致本身不会拖慢程序,但会拖慢后续协作和排障效率,尤其在你继续演进输出格式时会更明显。
|
||||||
|
|
||||||
|
建议在做 I/O 优化时顺手更新 README,避免认知分叉。
|
||||||
|
|
||||||
|
## 六、推荐实施顺序
|
||||||
|
|
||||||
|
如果按“最少改动获得最大收益”的原则,我建议这样排:
|
||||||
|
|
||||||
|
1. 给 `dynamics.py` / `compute.py` 增加基础计时统计。
|
||||||
|
2. 向量化 `compute_force()` 的弹簧键计算。
|
||||||
|
3. 把 `draw.py` 的 Marker 更新改成切片赋值。
|
||||||
|
4. 新增 `display.npz`,`draw.py` 优先读取二进制。
|
||||||
|
5. 把 `save_trajectory=1` 改成分块二进制输出,而不是全量内存缓存。
|
||||||
|
6. 修复 `dynamics.py` 绘图分支的数据来源问题。
|
||||||
|
7. 再考虑 `compute.py` 全局变量收敛和更深层的结构重构。
|
||||||
|
|
||||||
|
## 七、如果只做三件事,最值得做什么
|
||||||
|
|
||||||
|
如果你现在只想投入一小轮精力,我建议只做这三项:
|
||||||
|
|
||||||
|
1. 向量化 `compute_force()` 的弹簧键部分。
|
||||||
|
2. 增加 `display.npz` 二进制输出与读取。
|
||||||
|
3. 修正 `dynamics.py` 绘图阶段的数据流一致性。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 第 1 项直接优化 Python 引擎核心热点。
|
||||||
|
- 第 2 项直接优化所有引擎共享的 I/O 瓶颈。
|
||||||
|
- 第 3 项能降低后续改动时的维护风险。
|
||||||
|
|
||||||
|
这三项一起做,收益通常比零散微优化更明显。
|
||||||
|
|
||||||
|
## 八、结论
|
||||||
|
|
||||||
|
这个项目最值得肯定的地方,是已经天然分成了:
|
||||||
|
|
||||||
|
- 参考实现
|
||||||
|
- 高性能引擎
|
||||||
|
- 统一可视化管线
|
||||||
|
|
||||||
|
这意味着它非常适合做“分层优化”,不需要推倒重来。
|
||||||
|
|
||||||
|
从当前代码看,后续最有效的路线不是继续在外围加流程判断,而是:
|
||||||
|
|
||||||
|
- 把 Python 热点循环尽量向量化
|
||||||
|
- 把文本轨迹格式逐步替换为二进制主格式
|
||||||
|
- 把数据流和状态管理收紧
|
||||||
|
|
||||||
|
如果按这个方向推进,这个项目会同时得到:
|
||||||
|
|
||||||
|
- 更好的性能
|
||||||
|
- 更低的内存占用
|
||||||
|
- 更稳定的多引擎一致性
|
||||||
|
- 更容易继续扩展新的物理项和可视化形式
|
||||||
@@ -0,0 +1,341 @@
|
|||||||
|
# Dynamics 优化方案综合评估
|
||||||
|
|
||||||
|
> 分析日期:2026-06-12
|
||||||
|
> 评估对象:`optimization/claude.md`、`optimization/codex.md`、`optimization/workbuddy.md`
|
||||||
|
> 说明:用户问题中出现两次 `claude.md`,但目录内实际为三份不同文档,因此本文按 **Claude / Codex / Workbuddy** 三个工具的输出进行综合分析。
|
||||||
|
|
||||||
|
## 一、结论先行
|
||||||
|
|
||||||
|
这三份建议并不是互相冲突,而是分别代表了三种不同但互补的优化视角:
|
||||||
|
|
||||||
|
- **Claude**:最像“代码审计员”,擅长找出会崩溃、会出错、会不一致的具体问题。
|
||||||
|
- **Codex**:最像“性能工程师”,擅长定位热点路径、区分 CPU 与 I/O 瓶颈、给出高收益优化顺序。
|
||||||
|
- **Workbuddy**:最像“架构负责人”,擅长从模块拆分、全局变量治理、配置统一、测试体系等角度规划长期演进。
|
||||||
|
|
||||||
|
如果只选一个方案:
|
||||||
|
|
||||||
|
- 短期救火,优先参考 **Claude**
|
||||||
|
- 中期提速,优先参考 **Codex**
|
||||||
|
- 长期重构,优先参考 **Workbuddy**
|
||||||
|
|
||||||
|
如果要形成真正适合这个项目的落地方案,最佳做法不是三选一,而是:
|
||||||
|
|
||||||
|
1. 先按 **Claude** 修 correctness bug
|
||||||
|
2. 再按 **Codex** 做性能主线优化
|
||||||
|
3. 最后按 **Workbuddy** 做结构化治理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、三款工具的评价
|
||||||
|
|
||||||
|
## 1. Claude
|
||||||
|
|
||||||
|
### 优势
|
||||||
|
|
||||||
|
- **最强的 bug 发现能力**
|
||||||
|
`claude.md` 明确指出了几个高价值问题,例如:
|
||||||
|
- `compute.py` 里 `run_simulation()` 直接引用未传入的 `config`
|
||||||
|
- `dynamics.py` 绘图分支使用未定义变量
|
||||||
|
- `plot_wave.py` 仍按旧格式读取新 `display.txt`
|
||||||
|
|
||||||
|
这些问题都属于“不是慢,而是可能直接错或崩”的问题,优先级非常高。
|
||||||
|
|
||||||
|
- **问题定位具体,能直接改代码**
|
||||||
|
Claude 的输出不是抽象建议,而是带着明确文件位置、触发条件和修复方向,适合立即进入修复。
|
||||||
|
|
||||||
|
- **对多引擎一致性比较敏感**
|
||||||
|
它注意到了 Python/C/C++/Fortran 之间在 `save_trajectory`、势能分配、输出格式支持等方面的不一致,这对多语言框架很重要。
|
||||||
|
|
||||||
|
### 劣势
|
||||||
|
|
||||||
|
- **整体视角偏“局部修补”**
|
||||||
|
它非常擅长发现局部 bug,但对“整体性能主瓶颈怎么排优先级”没有 Codex 那么系统。
|
||||||
|
|
||||||
|
- **长期架构建议相对弱一些**
|
||||||
|
虽然也提到一些结构问题,但没有 Workbuddy 那种模块拆分、配置治理、测试体系的完整路线。
|
||||||
|
|
||||||
|
- **偏静态审计,偏保守**
|
||||||
|
更适合“先修对”,不一定最适合“先做最值的性能投资”。
|
||||||
|
|
||||||
|
### 适合的角色
|
||||||
|
|
||||||
|
- 第一轮排雷
|
||||||
|
- 回归前 bug 清单
|
||||||
|
- 多引擎一致性核对
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Codex
|
||||||
|
|
||||||
|
### 优势
|
||||||
|
|
||||||
|
- **性能视角最清晰**
|
||||||
|
`codex.md` 非常明确地区分了三类瓶颈:
|
||||||
|
- Python 计算循环
|
||||||
|
- 力学计算中的逐键/双层循环
|
||||||
|
- 文本 I/O
|
||||||
|
|
||||||
|
这种分层对项目很有价值,因为它避免把“算法慢”和“文件慢”混成一个问题。
|
||||||
|
|
||||||
|
- **优化顺序合理,投入产出比高**
|
||||||
|
它提出的几个优先项都很务实:
|
||||||
|
- 弹簧力向量化
|
||||||
|
- `display.txt` 增加二进制格式
|
||||||
|
- `draw.py` Marker 批量更新
|
||||||
|
|
||||||
|
这些建议的共同特点是:
|
||||||
|
- 不需要推翻现有架构
|
||||||
|
- 可验证
|
||||||
|
- 回报高
|
||||||
|
|
||||||
|
- **能识别共享瓶颈**
|
||||||
|
它特别强调:即使换成 C 引擎,文本 I/O 仍然会拖后腿。这种判断比只盯 Python 引擎更深入。
|
||||||
|
|
||||||
|
### 劣势
|
||||||
|
|
||||||
|
- **对当前代码中的直接 bug 关注度不如 Claude**
|
||||||
|
虽然也指出了 `dynamics.py` 数据流和 README 不一致等风险,但没有 Claude 那么系统地清点“哪些地方一开功能就报错”。
|
||||||
|
|
||||||
|
- **工程治理不如 Workbuddy 完整**
|
||||||
|
它提到全局变量和状态对象,但没有把模块拆分、测试体系、案例配置统一讲得那么全面。
|
||||||
|
|
||||||
|
- **更偏策略,不总是落到立即可改的具体 patch**
|
||||||
|
对项目 owner 来说这很好,但如果是要当天就修 bug,Claude 会更直接。
|
||||||
|
|
||||||
|
### 适合的角色
|
||||||
|
|
||||||
|
- 性能优化主方案
|
||||||
|
- 技术债排序
|
||||||
|
- 中期研发路线设计
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Workbuddy
|
||||||
|
|
||||||
|
### 优势
|
||||||
|
|
||||||
|
- **最强的工程化和长期治理视角**
|
||||||
|
`workbuddy.md` 关注的重点包括:
|
||||||
|
- `compute.py` 拆模块
|
||||||
|
- 消除全局变量
|
||||||
|
- 配置结构统一
|
||||||
|
- 测试体系建设
|
||||||
|
- Fortran/C/C++ 引擎功能对齐
|
||||||
|
|
||||||
|
这类建议对项目走向稳定维护很关键。
|
||||||
|
|
||||||
|
- **适合把个人项目变成可持续项目**
|
||||||
|
它不是只盯某一个性能点,而是在思考这个项目未来怎么更好地维护、测试、扩展。
|
||||||
|
|
||||||
|
- **对“项目管理成本”比较敏感**
|
||||||
|
比如配置格式不统一、废弃文件残留、测试缺失,这些短期不一定影响运行,但长期一定影响开发效率。
|
||||||
|
|
||||||
|
### 劣势
|
||||||
|
|
||||||
|
- **短期收益不如另外两者明显**
|
||||||
|
比如模块拆分、类型标注、状态封装,这些很重要,但如果当前主要痛点是“功能坏了”或“运行太慢”,它们不会立刻让用户感受到变化。
|
||||||
|
|
||||||
|
- **有些建议偏大改**
|
||||||
|
像 `compute.py` 模块拆分、全局变量全面状态化,工作量不小,如果现在马上做,容易把项目带入较长重构周期。
|
||||||
|
|
||||||
|
- **对现存 bug 的敏锐度不如 Claude,对热点性能的聚焦不如 Codex**
|
||||||
|
它更像制定制度和框架的人,而不是先冲到一线止血的人。
|
||||||
|
|
||||||
|
### 适合的角色
|
||||||
|
|
||||||
|
- 中长期架构治理
|
||||||
|
- 重构路线设计
|
||||||
|
- 代码库标准化与测试建设
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、三者对比总结
|
||||||
|
|
||||||
|
| 维度 | Claude | Codex | Workbuddy |
|
||||||
|
|------|--------|-------|-----------|
|
||||||
|
| 找 bug | 最强 | 中等 | 中等 |
|
||||||
|
| 找性能瓶颈 | 强 | 最强 | 中等 |
|
||||||
|
| 架构治理 | 中等 | 强 | 最强 |
|
||||||
|
| 可直接落地修复 | 最强 | 强 | 中等 |
|
||||||
|
| 中期路线规划 | 中等 | 最强 | 强 |
|
||||||
|
| 长期工程化 | 中等 | 强 | 最强 |
|
||||||
|
| 风险意识 | 最强 | 强 | 强 |
|
||||||
|
| 适合当前项目阶段 | 很适合当前排雷 | 很适合当前提速 | 很适合下一阶段治理 |
|
||||||
|
|
||||||
|
可以概括为:
|
||||||
|
|
||||||
|
- **Claude 更像 QA/审计**
|
||||||
|
- **Codex 更像性能工程师**
|
||||||
|
- **Workbuddy 更像架构经理**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、综合判断
|
||||||
|
|
||||||
|
从这三份文档综合看,`Dynamics` 项目当前不是单一问题,而是三类问题叠加:
|
||||||
|
|
||||||
|
1. **已有功能正确性问题**
|
||||||
|
- 某些路径会直接崩溃
|
||||||
|
- 某些脚本已经和新格式脱节
|
||||||
|
- 多引擎行为存在不一致
|
||||||
|
|
||||||
|
2. **性能与 I/O 问题**
|
||||||
|
- Python 引擎热点循环还没做足够向量化
|
||||||
|
- `display.txt`/`trajectory.txt` 文本格式开始成为共享瓶颈
|
||||||
|
- 动画路径里还有逐原子更新
|
||||||
|
|
||||||
|
3. **工程治理问题**
|
||||||
|
- `compute.py` 过大
|
||||||
|
- 全局变量过多
|
||||||
|
- 配置不统一
|
||||||
|
- 缺少测试
|
||||||
|
|
||||||
|
因此,最合理的综合方案必须是 **分阶段方案**,而不是试图一次性全做。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、推荐的综合方案
|
||||||
|
|
||||||
|
## 阶段 A:先修正确性,避免“带病优化”
|
||||||
|
|
||||||
|
这一阶段以 Claude 的结论为主,目标是先把明显会崩溃、会错的地方修掉。
|
||||||
|
|
||||||
|
建议优先做:
|
||||||
|
|
||||||
|
1. 修复 `compute.py` 中 `run_simulation()` 对 `config` 的非法引用
|
||||||
|
2. 修复 `dynamics.py` 绘图分支中未定义变量的死代码问题
|
||||||
|
3. 修复 `plot_wave.py` 对新 `display.txt` 格式的不兼容
|
||||||
|
4. 统一外部引擎在 `save_trajectory`、输出格式上的行为
|
||||||
|
5. 清理少量明显错误与危险写法
|
||||||
|
- case06 文案错误
|
||||||
|
- `draw.py` 裸 `except`
|
||||||
|
|
||||||
|
这一阶段的目标不是提速,而是让系统进入“功能可信、路径可跑”的状态。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段 B:再做高收益性能优化
|
||||||
|
|
||||||
|
这一阶段以 Codex 的结论为主,重点是先做收益大、改动相对可控的优化。
|
||||||
|
|
||||||
|
建议优先做:
|
||||||
|
|
||||||
|
1. 给 `compute.py` / `dynamics.py` 增加基础 profiling
|
||||||
|
- 总耗时
|
||||||
|
- 力计算耗时
|
||||||
|
- 抽帧耗时
|
||||||
|
- I/O 耗时
|
||||||
|
|
||||||
|
2. 向量化 `compute_force()` 中的弹簧键计算
|
||||||
|
|
||||||
|
3. 在 `draw.py` 中将 Marker 更新改成整列切片赋值
|
||||||
|
|
||||||
|
4. 新增二进制显示格式
|
||||||
|
- 推荐 `display.npz`
|
||||||
|
- `draw.py` 优先读二进制,回退读文本
|
||||||
|
|
||||||
|
5. 将完整轨迹保存改为分块/流式写出
|
||||||
|
|
||||||
|
这一步完成后,项目会得到最直观的收益:
|
||||||
|
|
||||||
|
- Python 引擎更快
|
||||||
|
- C 引擎的后处理更快
|
||||||
|
- 大规模 case 的内存压力更低
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段 C:最后做工程治理与结构化重构
|
||||||
|
|
||||||
|
这一阶段以 Workbuddy 的结论为主,目标是让项目从“能用”变成“好维护”。
|
||||||
|
|
||||||
|
建议优先做:
|
||||||
|
|
||||||
|
1. 拆分 `compute.py`
|
||||||
|
- `core.py`
|
||||||
|
- `io.py`
|
||||||
|
- `params.py`
|
||||||
|
- `runner.py`
|
||||||
|
- `engine_helper.py`
|
||||||
|
|
||||||
|
2. 引入 `SimulationState` / `SimulationConfig` / `SystemData`
|
||||||
|
- 逐步消灭模块级全局变量
|
||||||
|
|
||||||
|
3. 统一案例配置格式
|
||||||
|
- 六个案例字段保持一致
|
||||||
|
- 将颜色、相机等零散字段收束成结构化配置
|
||||||
|
|
||||||
|
4. 建立最小测试体系
|
||||||
|
- 数值方法单元测试
|
||||||
|
- I/O 一致性测试
|
||||||
|
- Python/C 输出一致性测试
|
||||||
|
|
||||||
|
5. 清理废弃脚本和旧逻辑
|
||||||
|
- 尤其是已经不再处于主流程中的脚本
|
||||||
|
|
||||||
|
这一步不会像阶段 B 那样马上体现性能收益,但它会显著降低后续维护成本。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、最终推荐方案
|
||||||
|
|
||||||
|
如果只给一个“综合最优”的执行方案,我建议采用下面这个顺序:
|
||||||
|
|
||||||
|
1. **先按 Claude 修 bug**
|
||||||
|
- 因为错误路径不先修,后面性能优化和重构都会建立在不稳定基础上
|
||||||
|
|
||||||
|
2. **再按 Codex 做性能主线**
|
||||||
|
- 因为当前性能瓶颈已经很清楚,尤其是 Python 热点和文本 I/O
|
||||||
|
|
||||||
|
3. **最后按 Workbuddy 做结构治理**
|
||||||
|
- 因为工程化重构最适合在功能稳定、热点清楚之后进行
|
||||||
|
|
||||||
|
换句话说:
|
||||||
|
|
||||||
|
- **Claude 决定“先修什么”**
|
||||||
|
- **Codex 决定“先优化什么”**
|
||||||
|
- **Workbuddy 决定“最后把系统整理成什么样”**
|
||||||
|
|
||||||
|
这是三份建议里最不冲突、也最符合实际开发节奏的组合方式。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、简版执行清单
|
||||||
|
|
||||||
|
如果要把这份综合方案压缩成一份实际待办,我建议如下:
|
||||||
|
|
||||||
|
### P0:立即处理
|
||||||
|
|
||||||
|
- 修 `run_simulation()` 的 `config` 作用域错误
|
||||||
|
- 修 `dynamics.py` 绘图分支未定义变量
|
||||||
|
- 修 `plot_wave.py` 与新 `display.txt` 格式不兼容
|
||||||
|
- 校正多引擎 `save_trajectory` / 输出行为不一致
|
||||||
|
|
||||||
|
### P1:本轮优化主线
|
||||||
|
|
||||||
|
- 增加 profiling
|
||||||
|
- 向量化弹簧力计算
|
||||||
|
- `draw.py` Marker 批量更新
|
||||||
|
- 新增 `display.npz`
|
||||||
|
- 完整轨迹改为分块写出
|
||||||
|
|
||||||
|
### P2:下一轮治理
|
||||||
|
|
||||||
|
- 拆分 `compute.py`
|
||||||
|
- 全局变量状态化
|
||||||
|
- 统一 `input.txt` 结构
|
||||||
|
- 建立 pytest 测试
|
||||||
|
- 清理废弃脚本与旧文档
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、最终评价
|
||||||
|
|
||||||
|
三款工具的输出质量都不错,但擅长点不同。
|
||||||
|
|
||||||
|
- **Claude** 最适合当前这个项目的“第一步”,因为它最能发现会直接影响运行正确性的缺陷。
|
||||||
|
- **Codex** 最适合当前这个项目的“第二步”,因为它对性能瓶颈的层次判断最清楚,最容易产出高收益优化。
|
||||||
|
- **Workbuddy** 最适合当前这个项目的“第三步”,因为它能把一套已经能跑的系统整理成一套更稳定、更易维护的工程。
|
||||||
|
|
||||||
|
因此,综合最优解不是选其中一个,而是:
|
||||||
|
|
||||||
|
**用 Claude 排雷,用 Codex 提速,用 Workbuddy 收尾治理。**
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
# Dynamics 项目优化方案 — 三方工具综合评价与整合
|
||||||
|
|
||||||
|
> **分析日期**:2026-06-12
|
||||||
|
> **参与工具**:WorkBuddy(本助手)、Claude(Sonnet 4.6)、Codex(GPT-4o 代码分析)
|
||||||
|
> **分析范围**:`D:\Share\Data\aliyun-gitea\dynamics` 完整代码库
|
||||||
|
> **参考文档**:`optimization/workbuddy.md`、`optimization/claude.md`、`optimization/codex.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、三方工具评价
|
||||||
|
|
||||||
|
### 1.1 WorkBuddy(AI Agent,Senior Developer 角色)
|
||||||
|
|
||||||
|
| 维度 | 评价 |
|
||||||
|
|------|------|
|
||||||
|
| **优势** | ✅ 实际运行和修改过整个代码库,已验证 display.txt 格式/渲染参数/运动相机等功能的正确性 — 不是静态分析,是实机验证 |
|
||||||
|
| | ✅ 覆盖最广:架构、性能、代码质量、配置、测试、引擎一致性、UX 等 9 个维度 |
|
||||||
|
| | ✅ 每个建议标注优先级(P0-P3)和预估工作量(小时级) |
|
||||||
|
| | ✅ 发现了一些真正的坑:C++ 引擎 `save_trajectory` 默认值 1(导致行为不一致)、`alpha` 字段未传递到 display.txt 等 |
|
||||||
|
| | ✅ 提供 input.txt 格式统一模板和 YAML 结构优化建议 |
|
||||||
|
| **劣势** | ❌ 建议偏"宏观架构"(模块拆分、全局变量封装),缺乏具体的向量化代码 |
|
||||||
|
| | ❌ 没有发现 B1(config 不存在作用域)、B2(绘图代码完全失效)等具体 Bug |
|
||||||
|
| | ❌ 对 Python 引擎性能优化仅提到 Numba,没有给出弹簧力向量化等具体代码 |
|
||||||
|
|
||||||
|
### 1.2 Claude(Sonnet 4.6)
|
||||||
|
|
||||||
|
| 维度 | 评价 |
|
||||||
|
|------|------|
|
||||||
|
| **优势** | ✅ **Bug 挖掘能力极强**:发现了 `run_simulation` 内 `config` 未定义、`dynamics.py` 绘图块完全失效、`plot_wave.py` 格式不匹配等 5 个确切 Bug |
|
||||||
|
| | ✅ **Python 引擎向量化代码极其具体**:弹簧力、引力 O(N²)、固定约束、驱动力的优化都给出了可直接替换的代码(含 `np.add.at`) |
|
||||||
|
| | ✅ 引擎一致性分析全面:C/C++/Fortran 默认值差异、势能归属约定差异 |
|
||||||
|
| | ✅ 标注了 `load_parameters` 已废弃、相机解析函数重复等代码质量问题 |
|
||||||
|
| | ✅ 按投入产出比排序最高回报的 3 件事 |
|
||||||
|
| **劣势** | ❌ 纯静态分析,**部分结论可能是误报**:B1 中 `config` 在 `run_simulation` 中不可用 → 但 `run_simulation` 是从 `run_from_config` 内部调用的,实际上 Python 引擎路径已验证可正常工作(后续对话中用户成功运行) |
|
||||||
|
| | ❌ 没有分析 Fortran 引擎(可能只读了 C 引擎代码) |
|
||||||
|
| | ❌ 没有发现 display.txt 格式最初加载过慢的问题(已修复) |
|
||||||
|
| | ❌ 没有发现 `use_marker` 字段丢失导致 VisPy 卡顿的问题(已修复) |
|
||||||
|
| | ❌ 没有分析 input.txt 格式统一性问题 |
|
||||||
|
|
||||||
|
### 1.3 Codex(GPT-4o)
|
||||||
|
|
||||||
|
| 维度 | 评价 |
|
||||||
|
|------|------|
|
||||||
|
| **优势** | ✅ **战略思维最好**:建议先加 profiling 再做优化("没度量就没有优化"),反对盲目优化 |
|
||||||
|
| | ✅ 提出 `display.npz` 二进制格式替代方案 — 所有工具中唯一想到这个的 |
|
||||||
|
| | ✅ "分两层优化"理念清晰:计算性能(CPU) vs I/O 性能(磁盘)分开处理 |
|
||||||
|
| | ✅ 强调 "最小一致性测试集" — 多引擎正确性验证的实用方案 |
|
||||||
|
| | ✅ 推荐实施顺序最合理:1)计时 2)向量化 3)Marker 切片 4)二进制输出 5)修复绘图 6)架构重构 |
|
||||||
|
| **劣势** | ❌ 代码细节最少,没有给出具体的向量化实现 |
|
||||||
|
| | ❌ 没有发现任何确切的 Bug——描述的都是"可能的风险"而非"可复现的崩溃" |
|
||||||
|
| | ❌ 架构建议偏泛("减少全局变量""收敛到状态对象"),没有 WorkBuddy 的 SimulationState 代码示例 |
|
||||||
|
| | ❌ 没有分析相机运动、渲染参数传递等最近新增功能 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、建议对比汇总
|
||||||
|
|
||||||
|
### 2.1 Bug 发现对比
|
||||||
|
|
||||||
|
| Bug 编号 | 描述 | WorkBuddy | Claude | Codex | 验证状态 |
|
||||||
|
|----------|------|-----------|--------|-------|---------|
|
||||||
|
| B1 | `run_simulation` 内 `config` 变量作用域 | ❌ 未发现 | ✅ 发现 | ❌ 未发现 | ⚠️ 疑似误报(Python 引擎实际可用) |
|
||||||
|
| B2 | `dynamics.py` 绘图块死代码/变量未定义 | ❌ 未发现 | ✅ 发现 | ✅ 提及风险 | ✅ 确认(step_plot=1 时崩溃) |
|
||||||
|
| B3 | `plot_wave.py` 旧格式加载新 display.txt | ❌ 未发现 | ✅ 发现 | ❌ 未发现 | ✅ 确认(step_plot_wave=1 时崩溃) |
|
||||||
|
| B4 | case06 描述写 case01 | ❌ 未发现 | ✅ 发现 | ❌ 未发现 | ✅ 确认 |
|
||||||
|
| B5 | `draw.py` 裸 `except` | ❌ 未发现 | ✅ 发现 | ❌ 未发现 | ✅ 确认 |
|
||||||
|
| B6 | display.txt 旧格式加载过慢(已修复) | ✅ 发现修复 | ❌ 未发现 | ❌ 未发现 | ✅ 已修复 |
|
||||||
|
| B7 | `use_marker` 丢失 → VisPy 卡顿(已修复) | ✅ 发现修复 | ❌ 未发现 | ❌ 未发现 | ✅ 已修复 |
|
||||||
|
| B8 | `alpha` 未写入 display.txt header(已修复) | ✅ 发现修复 | ❌ 未发现 | ❌ 未发现 | ✅ 已修复 |
|
||||||
|
| B9 | 运动相机数据缓存不刷新(已修复) | ✅ 发现修复 | ❌ 未发现 | ❌ 未发现 | ✅ 已修复 |
|
||||||
|
|
||||||
|
### 2.2 优化建议对比
|
||||||
|
|
||||||
|
| 优化项 | WorkBuddy | Claude | Codex | 综合评价 |
|
||||||
|
|--------|-----------|--------|-------|---------|
|
||||||
|
| 弹簧力向量化 | 仅提到 Numba | ✅ 完整代码含 `np.add.at` | ✅ 有思路但无代码 | **Claude 最佳** |
|
||||||
|
| 固定约束优化 | 提到但无代码 | ✅ 有代码 | ✅ 有思路 | **Claude 最佳** |
|
||||||
|
| display.npz 二进制格式 | ❌ 未想到 | ❌ 未想到 | ✅ 唯一想到 | **Codex 最佳** |
|
||||||
|
| 全局变量封装 SimulationState | ✅ 有代码示例 | ✅ 提及 | ✅ 提及 | **WorkBuddy 最佳** |
|
||||||
|
| compute.py 模块拆分 | ✅ 完整目录结构 | ❌ 未提及 | ❌ 未提及 | **WorkBuddy 最佳** |
|
||||||
|
| input.txt 格式统一 | ✅ 完整模板 | ❌ 未提及 | ❌ 未提及 | **WorkBuddy 最佳** |
|
||||||
|
| 先加 profiling 再优化 | ❌ 未提及 | ❌ 未提及 | ✅ 强烈建议 | **Codex 最佳** |
|
||||||
|
| Fortran 引擎更新 | ✅ 提及 P0 | ✅ 提及 | ❌ 未提及 | **WorkBuddy+Claude** |
|
||||||
|
| 外部引擎校准缓存 | ✅ 有代码示例 | ✅ 有方案 | ❌ 未提及 | **WorkBuddy+Claude** |
|
||||||
|
| 多引擎一致性测试集 | ❌ 未提及 | ❌ 未提及 | ✅ 建议 | **Codex 最佳** |
|
||||||
|
| Marker 更新切片化 | ❌ 未提及 | ❌ 未提及 | ✅ 建议 | **Codex 最佳** |
|
||||||
|
| 驱动去 t_vec 分配 | ❌ 未提及 | ✅ 有代码 | ✅ 提及 | **Claude 最佳** |
|
||||||
|
|
||||||
|
### 2.3 风格对比
|
||||||
|
|
||||||
|
| 维度 | WorkBuddy | Claude | Codex |
|
||||||
|
|------|-----------|--------|-------|
|
||||||
|
| 粒度 | 宏观+微观 | 微观为主 | 宏观为主 |
|
||||||
|
| 代码示例 | 中等(配置模板/架构) | 丰富(向量化/性能优化) | 最少 |
|
||||||
|
| Bug 发现 | 运行时验证的 Bug | 静态分析发现的 Bug | 潜在风险 |
|
||||||
|
| 战略层 | 中度 | 低 | 高 |
|
||||||
|
| 实施顺序 | P0-P3 优先级 | 投入产出比排序 | 推荐实施顺序 |
|
||||||
|
| 可信度 | 高(实际运行过) | 中(静态分析) | 中(静态分析) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、最终综合方案
|
||||||
|
|
||||||
|
综合三方分析,推荐按以下 **6 个阶段** 实施,每阶段都有明确的可验证交付物。
|
||||||
|
|
||||||
|
### 阶段一:立即修复已确认的 Bug(1 天)
|
||||||
|
|
||||||
|
| 编号 | 内容 | 参考工具 | 工作量 |
|
||||||
|
|------|------|---------|--------|
|
||||||
|
| F1 | `plot_wave.py` 适配 `load_display_txt` 新格式 (B3) | Claude | 1-2h |
|
||||||
|
| F2 | `dynamics.py` 绘图块添加保护,避免 `step_plot=1` 崩溃 (B2) | Claude | 30min |
|
||||||
|
| F3 | 修复 `draw.py` 裸 `except` (B5) | Claude | 5min |
|
||||||
|
| F4 | 修复 case06 描述文字 (B4) | Claude | 1min |
|
||||||
|
| F5 | C++ 引擎 `save_trajectory` 默认值改为 0 (E2) | Claude | 5min |
|
||||||
|
| F6 | **Fortran 引擎支持 `save_trajectory=0`**(参照 C 引擎实现) | WorkBuddy/Claude | 2-3h |
|
||||||
|
| F7 | 相机解析函数去重:`dynamics.py` 导入 `compute._load_camera_motion` (Q1) | Claude | 15min |
|
||||||
|
| F8 | 废弃 `load_parameters` + `main()` 删除或移入 tools/ (Q2) | Claude | 15min |
|
||||||
|
|
||||||
|
**验证**:Python/C/C++/Fortran 全部 4 种引擎跑 case06,`step_plot: 1` + `step_plot_wave: 1` 不崩溃
|
||||||
|
|
||||||
|
### 阶段二:Python 引擎性能优化(2 天)
|
||||||
|
|
||||||
|
| 编号 | 内容 | 参考工具 | 工作量 |
|
||||||
|
|------|------|---------|--------|
|
||||||
|
| P1 | **弹簧力向量化**:`compute_force()` 中键循环改为 `np.add.at` 批量计算 | Claude(完整代码) | 1h |
|
||||||
|
| P2 | 固定约束原地掩码写回,消除 `column_stack` | Claude | 30min |
|
||||||
|
| P3 | `frame_indices` 列表改计数器 | Claude | 5min |
|
||||||
|
| P4 | 驱动力去掉 `t_vec` 临时数组 | Claude/Codex | 5min |
|
||||||
|
| P5 | `GRAVITY_INTERACTION` O(N²) 双重循环向量化(可选) | Claude | 1h |
|
||||||
|
|
||||||
|
**验证**:`engine: python` 跑 case06 耗时缩短 5-15 倍(目标:从 43s → <5s)
|
||||||
|
|
||||||
|
### 阶段三:I/O 与可视化优化(2 天)
|
||||||
|
|
||||||
|
| 编号 | 内容 | 参考工具 | 工作量 |
|
||||||
|
|------|------|---------|--------|
|
||||||
|
| I1 | **新增 `display.npz` 二进制格式**:`save_display_npz` / `load_display_npz` | Codex(方案) | 1-2h |
|
||||||
|
| I2 | `draw.py` 优先读 `.npz`,不存在时回退 `display.txt` | Codex | 1h |
|
||||||
|
| I3 | `draw.py` Marker 更新改为切片赋值(消除 `for i in range(N_ATOMS)`) | Codex | 15min |
|
||||||
|
| I4 | `save_trajectory=1` 时轨迹数据改为 `memmap` 或分块写入 | Codex | 2h |
|
||||||
|
|
||||||
|
**验证**:读取 200 帧×120 原子数据 < 0.01s(现 0.087s),动画帧率 60fps
|
||||||
|
|
||||||
|
### 阶段四:架构重构(3 天)
|
||||||
|
|
||||||
|
| 编号 | 内容 | 参考工具 | 工作量 |
|
||||||
|
|------|------|---------|--------|
|
||||||
|
| A1 | **`compute.py` 模块拆分**:`core/io/params/runner/main` | WorkBuddy(目录结构) | 4-6h |
|
||||||
|
| A2 | **全局变量封装为 `SimulationState` 类**,从 `compute_force()` 开始 | WorkBuddy | 3-4h |
|
||||||
|
| A3 | `draw.py` 全局变量封装为 `AnimationData` + `CameraState` | WorkBuddy | 2-3h |
|
||||||
|
| A4 | C 和 C++ 引擎共用公共头文件(`engines/common/`) | Workbuddy | 3h |
|
||||||
|
|
||||||
|
**验证**:所有 6 个 case + 4 种引擎,结果与重构前一致
|
||||||
|
|
||||||
|
### 阶段五:配置与文档统一(1 天)
|
||||||
|
|
||||||
|
| 编号 | 内容 | 参考工具 | 工作量 |
|
||||||
|
|------|------|---------|--------|
|
||||||
|
| C1 | **统一 6 个案例的 input.txt 格式**(增加 save_trajectory/camera 等缺失字段) | WorkBuddy(模板) | 1-2h |
|
||||||
|
| C2 | `ball_color_r/g/b` 改为 YAML 列表 `ball_color: [R,G,B]` | WorkBuddy | 30min |
|
||||||
|
| C3 | 更新 README.md 匹配当前架构 | Codex | 1h |
|
||||||
|
| C4 | 注释清理:边界条件分工、弹性势能归属约定 | Claude | 20min |
|
||||||
|
|
||||||
|
**验证**:6 个 `run_dynamics.py` 全部可运行,README 描述与代码一致
|
||||||
|
|
||||||
|
### 阶段六:测试与CI(2 天)
|
||||||
|
|
||||||
|
| 编号 | 内容 | 参考工具 | 工作量 |
|
||||||
|
|------|------|---------|--------|
|
||||||
|
| T1 | **添加 pytest 单元测试**:物理算法(Leapfrog/Euler)、文件 I/O 读写一致性 | WorkBuddy | 3-5h |
|
||||||
|
| T2 | **多引擎一致性测试**:4 种引擎跑 case07(2 原子 10 步最小案例),输出容差内一致 | Codex | 2h |
|
||||||
|
| T3 | 添加基础 profiling 计时(总时间、力计算、I/O 分段) | Codex | 1h |
|
||||||
|
| T4 | 添加外部引擎校准缓存 | WorkBuddy/Claude | 1-2h |
|
||||||
|
|
||||||
|
**验证**:`pytest` 绿色通过,`python run_dynamics.py --engine python` 与 `--engine c` 结果一致
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、总体工作量估算
|
||||||
|
|
||||||
|
| 阶段 | 内容 | 预估人天 | 依赖 |
|
||||||
|
|------|------|---------|------|
|
||||||
|
| 一 | Bug 修复 | 1 天 | — |
|
||||||
|
| 二 | Python 引擎性能优化 | 2 天 | 阶段一 |
|
||||||
|
| 三 | I/O 与可视化优化 | 2 天 | 阶段一 |
|
||||||
|
| 四 | 架构重构 | 3 天 | 阶段一、二 |
|
||||||
|
| 五 | 配置与文档统一 | 1 天 | 阶段四 |
|
||||||
|
| 六 | 测试与 CI | 2 天 | 阶段二、四 |
|
||||||
|
| **合计** | | **~11 人天** | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、如果只做 3 件事
|
||||||
|
|
||||||
|
基于三方分析共识 + 投入产出比,最值得做的三件事:
|
||||||
|
|
||||||
|
1. **弹簧力向量化**(Claude 给出完整代码,Python 引擎性能提升 5-15 倍)
|
||||||
|
2. **Fortran 引擎支持 `save_trajectory=0`**(WorkBuddy + Claude 共识 P0,所有引擎行为一致的必要条件)
|
||||||
|
3. **新增 `display.npz` 二进制格式**(Codex 独有见解,统一切换 I/O 性能瓶颈)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、工具选择建议
|
||||||
|
|
||||||
|
| 场景 | 推荐工具 | 原因 |
|
||||||
|
|------|---------|------|
|
||||||
|
| 找 Bug | **Claude** | Bug 发现能力最强(5 个确切 Bug) |
|
||||||
|
| 性能优化(向量化) | **Claude** | 给出可直接替换的 Numpy 向量化代码 |
|
||||||
|
| 架构重构 | **WorkBuddy** | 熟悉整体代码结构,有模块拆分/状态封装的具体方案 |
|
||||||
|
| I/O 策略设计 | **Codex** | 战略思维好,提出二进制格式等创新方案 |
|
||||||
|
| 实施顺序 | **Codex + WorkBuddy** | Codex 的 profiling-first 理念 + WorkBuddy 的 P0-P3 优先级 |
|
||||||
|
| 确认 Bug 是否真实 | **实际运行测试** | 三方都是静态分析,最终需要运行确认 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*本文档综合了 WorkBuddy(实机验证)、Claude Sonnet 4.6(静态 Bug 挖掘)、Codex/GPT-4o(战略思维)三方的分析。建议在实施每个阶段前,先用相关案例运行确认 Bug 现象和优化收益。*
|
||||||
Reference in New Issue
Block a user