refactor: 引擎直接抽帧 + 新 display.txt 纯文本格式 + save_trajectory 开关

核心变更:
1. compute.py: run_simulation 直接按 NSTEP 抽帧写 display.txt(新格式)
   - 新格式:纯文本,帧 1→n 分块,每行: n x y z vx vy vz
   - 新函数 save_display_txt() / load_display_txt()
   - save_trajectory 参数(默认0=不保留 trajectory.txt)
2. dynamics.py: 移除旧 JSON 采样逻辑,自动检测 display.txt
   - Python 引擎直接读取引擎输出的 display.txt
   - 外部引擎仍写 trajectory.txt,自动抽帧转 display.txt
3. draw.py: 适配 load_display_txt() 新格式
4. case06/input.txt: 添加 save_trajectory: 0, step_sample: 0

TODO: 外部引擎(C/C++/Fortran)内部抽帧写 display.txt
TODO: plot_wave.py 适配新格式
TODO: 其他案例 input.txt 更新默认值
This commit is contained in:
2026-06-12 06:36:50 +08:00
parent c158c74609
commit 9d1f84d2bf
4 changed files with 334 additions and 204 deletions
+47 -47
View File
@@ -1,9 +1,8 @@
"""VisPy 演示:加载预计算轨迹数据,驱动小球运动动画。
计算与显示完全分离:
1. 运行 compute.py → 生成 output/trajectory.txt(全量 NT 步轨迹
2. 再运行 sample.py → 从 output/trajectory.txt 抽帧生成 output/display.txt
3. 本文件加载 output/display.txt,按帧播放动画
1. 运行 run_dynamics.py → 生成 output/display.txt(新格式,直接抽帧
2. 本文件加载 output/display.txt,按帧播放动画
用法:
python draw.py # 使用 dynamics 根目录下的 output/
@@ -39,66 +38,67 @@ if not os.path.exists(disp_path):
f"用法: python draw.py [案例输出目录]"
)
disp_data = compute.load_text_data(disp_path)
disp_data = compute.load_display_txt(disp_path)
h = disp_data["header_fields"]
# 原子数据plot_atom:用于信息显示)
DISP_X = disp_data["disp_x"]
DISP_Y = disp_data["disp_y"]
DISP_Z = disp_data["disp_z"]
DISP_VX = disp_data["disp_vx"]
DISP_VY = disp_data["disp_vy"]
DISP_VZ = disp_data["disp_vz"]
# 原子数据
DISP_ALL_X = disp_data["frames_x"] # (n_frames, n_atoms)
DISP_ALL_Y = disp_data["frames_y"]
DISP_ALL_Z = disp_data["frames_z"]
DISP_ALL_VX = disp_data["frames_vx"]
DISP_ALL_VY = disp_data["frames_vy"]
DISP_ALL_VZ = disp_data["frames_vz"]
# 全原子数据(用于多球绘制
DISP_ALL_X = disp_data["disp_all_x"] # (n_frames, n_atoms)
DISP_ALL_Y = disp_data["disp_all_y"]
DISP_ALL_Z = disp_data["disp_all_z"]
DISP_ALL_VX = disp_data["disp_all_vx"]
DISP_ALL_VY = disp_data["disp_all_vy"]
DISP_ALL_VZ = disp_data["disp_all_vz"]
# 第一个原子的轨迹(用于信息显示
DISP_X = DISP_ALL_X[:, 0]
DISP_Y = DISP_ALL_Y[:, 0]
DISP_Z = DISP_ALL_Z[:, 0]
DISP_VX = DISP_ALL_VX[:, 0]
DISP_VY = DISP_ALL_VY[:, 0]
DISP_VZ = DISP_ALL_VZ[:, 0]
DISP_T = disp_data["disp_t"]
DISP_STEP = disp_data["disp_step"]
N_FRAMES = int(disp_data["n_frames"])
NT = int(disp_data["NT"])
DT = float(disp_data["DT"])
NSTEP = int(disp_data["NSTEP"])
N_FRAMES = DISP_ALL_X.shape[0]
NT = int(disp_data["n_total_frames"])
N_ATOMS = int(disp_data["n_total_particles"])
DT = float(h.get("DT", 0.001))
NSTEP = int(h.get("NSTEP", 1))
DISP_STEP = np.arange(N_FRAMES) * NSTEP
DISP_T = DISP_STEP * DT
# 原子信息
ATOM_IDS = disp_data.get("atom_ids", np.array([1]))
ATOM_RADII = disp_data.get("atom_radii", np.array([float(disp_data["ball_radius"])]))
N_ATOMS = len(ATOM_IDS)
PLOT_ATOM_ROW = int(disp_data.get("plot_atom_row", 0))
PLOT_ATOM_ID = int(disp_data.get("plot_atom_id", ATOM_IDS[0]))
BOND_PAIRS = disp_data.get("bond_pairs", [])
ATOM_IDS = disp_data["atom_ids"]
ATOM_RADII = np.full(N_ATOMS, float(h.get("ball_radius", 0.5)))
PLOT_ATOM_ROW = 0
PLOT_ATOM_ID = int(ATOM_IDS[0])
BOND_PAIRS = [] # display 格式不含成键信息,从原始数据加载
# 渲染方式:0=Sphere(网格球体), 1=Marker(GPU点精灵)
USE_MARKER = int(disp_data.get("use_marker", 0))
USE_MARKER = 0
if N_FRAMES <= 0:
raise ValueError(
"output/display.txt 中没有可播放的帧,请检查 sample_start/sample_end/NSTEP 配置。")
# 保留模拟边界常量(用于场景缩放、相机等),从 output/display.txt 中读取
X_MIN = float(disp_data["X_MIN"]); X_MAX = float(disp_data["X_MAX"])
Y_MIN = float(disp_data["Y_MIN"]); Y_MAX = float(disp_data["Y_MAX"])
Z_MIN = float(disp_data["Z_MIN"]); Z_MAX = float(disp_data["Z_MAX"])
X0 = float(disp_data["X0"]); Y0 = float(disp_data["Y0"]); Z0 = float(disp_data["Z0"])
raw_alpha = disp_data["alpha"]
if isinstance(raw_alpha, (list, tuple, np.ndarray)):
alpha_list = [float(a) for a in raw_alpha]
X_MIN = float(h.get("X_MIN", -10)); X_MAX = float(h.get("X_MAX", 10))
Y_MIN = float(h.get("Y_MIN", -10)); Y_MAX = float(h.get("Y_MAX", 10))
Z_MIN = float(h.get("Z_MIN", -10)); Z_MAX = float(h.get("Z_MAX", 10))
raw_alpha = h.get("alpha", "0.2")
try:
alpha_list = [float(x) for x in raw_alpha.split(",")]
if len(alpha_list) != 6:
raise ValueError(f"alpha 数组长度须为 6,实际为 {len(alpha_list)}")
else:
alpha_list = alpha_list * 6
except:
alpha_list = [float(raw_alpha)] * 6
# 绘图参数
ball_radius = float(disp_data["ball_radius"])
ball_color_r = float(disp_data["ball_color_r"])
ball_color_g = float(disp_data["ball_color_g"])
ball_color_b = float(disp_data["ball_color_b"])
box_color_r = float(disp_data["box_color_r"])
box_color_g = float(disp_data["box_color_g"])
box_color_b = float(disp_data["box_color_b"])
ball_radius = float(h.get("ball_radius", 0.5))
ball_color_r = float(h.get("ball_color_r", 0.9))
ball_color_g = float(h.get("ball_color_g", 0.2))
ball_color_b = float(h.get("ball_color_b", 0.2))
box_color_r = float(h.get("box_color_r", 0.8))
box_color_g = float(h.get("box_color_g", 0.8))
box_color_b = float(h.get("box_color_b", 0.85))
# ===========================================================================