feat: 增加驱动力系统、Marker渲染模式、动画防闪退、案例文档

- 新增 driving_force 驱动力系统(driver.txt 定义,支持周期控制)
- 新增 use_marker 渲染开关(GPU实例化点精灵,提升大量原子性能)
- 修复动画闪退:独立控制台、错误日志、启动存活检测
- 重绘 draw.py 架构:双渲染模式 + 预分配键线缓冲区
- 修复 raw trajectory 采样时间变量遮蔽 bug
- 重构 case05: 60原子一维链 + 驱动力 + 完整案例文档
- 修复所有案例 Readme.md 编码(GBK → UTF-8)
- 所有 input.txt 新增 driver_file / driving_force / use_marker 参数
This commit is contained in:
2026-06-10 15:34:53 +08:00
parent 0f04630fc0
commit 854f00ae44
28 changed files with 1404 additions and 68 deletions
+43 -20
View File
@@ -12,6 +12,7 @@ dynamics.py
import os
import sys
import subprocess
import time
import argparse
from contextlib import contextmanager
from pathlib import Path
@@ -316,6 +317,8 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
"elastic_force": int(data.get("elastic_force", 1)),
"damping_force": int(data.get("damping_force", 0)),
"gravity_strength": float(data.get("gravity_strength", 1.0)),
"driving_force": int(config.get("driving_force", 0)),
"use_marker": int(config.get("use_marker", 0)),
}
save_display_txt(disp_data, str(runtime_base))
print(f"[run] 抽帧完成: {sample_end - sample_start} 步 -> {n_frames}")
@@ -331,7 +334,7 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'WenQuanYi Micro Hei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
time = np.arange(NT) * DT
time_arr = np.arange(NT) * DT
n_atoms = all_x.shape[1]
atom_ids_list = data.get("atom_ids", np.arange(n_atoms) + 1)
@@ -353,7 +356,7 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
for ax, data_arr, title in plot_configs:
for i in range(n_atoms):
atom_id = int(atom_ids_list[i])
ax.plot(time, data_arr[:, i], color=colors[i], linewidth=1.5, label=f"原子 {atom_id}")
ax.plot(time_arr, data_arr[:, i], color=colors[i], linewidth=1.5, label=f"原子 {atom_id}")
ax.set_title(title)
ax.set_xlabel("时间 (s)")
ax.grid(True, alpha=0.3)
@@ -423,7 +426,7 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
ax_e = axes[2, 0]
for i in range(n_atoms):
aid = int(atom_ids_list[i])
ax_e.plot(time, e_total[:, i], color=colors[i], linewidth=1.5, label=f"原子 {aid}")
ax_e.plot(time_arr, e_total[:, i], color=colors[i], linewidth=1.5, label=f"原子 {aid}")
ax_e.set_title("各原子总能量")
ax_e.set_xlabel("时间 (s)")
ax_e.set_ylabel("能量")
@@ -432,13 +435,13 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
# ── 第 3 行右:系统总能量 ──
ax_sys = axes[2, 1]
ax_sys.plot(time, ek_sys, 'b-', linewidth=1.5, label="系统动能")
ax_sys.plot(time, ug_sys, 'g-', linewidth=1.5, label="均匀重力势能")
ax_sys.plot(time_arr, ek_sys, 'b-', linewidth=1.5, label="系统动能")
ax_sys.plot(time_arr, ug_sys, 'g-', linewidth=1.5, label="均匀重力势能")
if elastic_force_enabled and bond_pairs is not None and len(bond_pairs) > 0:
ax_sys.plot(time, us_sys, color='orange', linewidth=1.5, label="系统弹性势能")
ax_sys.plot(time_arr, us_sys, color='orange', linewidth=1.5, label="系统弹性势能")
if gravity_interaction_enabled:
ax_sys.plot(time, ug_grav_sys, color='purple', linewidth=1.5, label="万有引力势能")
ax_sys.plot(time, e_sys, 'r--', linewidth=1.5, label="系统总能量")
ax_sys.plot(time_arr, ug_grav_sys, color='purple', linewidth=1.5, label="万有引力势能")
ax_sys.plot(time_arr, e_sys, 'r--', linewidth=1.5, label="系统总能量")
ax_sys.set_title("系统总能量")
ax_sys.set_xlabel("时间 (s)")
ax_sys.set_ylabel("能量")
@@ -461,19 +464,39 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
# 5. 自动播放动画(可选)
if config.get("step_animation", 0):
draw_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "draw.py")
if os.path.exists(draw_script):
try:
print("[run] 正在启动 VisPy 3D 动画窗口…")
subprocess.Popen(
[sys.executable, draw_script],
cwd=runtime_base,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except Exception as e:
print(f"[run] 启动动画失败: {e}")
else:
if not os.path.exists(draw_script):
print(f"[run] 未找到动画脚本: {draw_script}")
else:
# 检查 display.txt 是否存在(step_sample=0 时可能没有)
disp_path = os.path.join(output_dir_abs, "display.txt")
if not os.path.exists(disp_path):
print(f"[run] 错误: 找不到 {disp_path}")
print(f"[run] 启动动画需要先运行抽帧(step_sample: 1),或手动保留 output/display.txt")
else:
try:
print("[run] 正在启动 VisPy 3D 动画窗口…")
ansi_log = os.path.join(output_dir_abs, "animation.log")
if sys.platform == "win32":
# Windows 上给子进程独立控制台,避免父进程退出时连带关闭
creation_flags = subprocess.CREATE_NEW_CONSOLE
else:
creation_flags = 0
proc = subprocess.Popen(
[sys.executable, draw_script, output_dir_abs],
cwd=runtime_base,
stdout=subprocess.DEVNULL,
stderr=open(ansi_log, "w", encoding="utf-8"),
creationflags=creation_flags,
)
# 等待半秒检查子进程是否成功启动(未立即崩溃)
time.sleep(0.5)
if proc.poll() is not None:
print(f"[run] ⚠ 动画进程已退出,返回码={proc.returncode}")
print(f"[run] 请查看错误日志: {ansi_log}")
else:
print(f"[run] VisPy 动画窗口已启动(PID={proc.pid}")
except Exception as e:
print(f"[run] 启动动画失败: {e}")
else:
print("[run] 运行 python draw.py 查看动画。")