feat: 真蛙跳法重构(Python/C/C++/Fortran 四引擎统一)
- 新增 compute_accel_conservative / accel_conservative: 保守力加速度(弹簧+重力+原子间引力),不含阻尼,供蛙跳专用 - 重写 leapfrog_step / leapfrog_full: - 无阻尼:纯辛积分器,每步 1 次力计算(原 Velocity-Verlet 需 2 次) - 有阻尼:半隐式处理 v(t+dt/2)=[v(t-dt/2)*(1-α)+a_c*dt]/(1+α),无条件稳定 - 主循环加初始化反向半步 v(-dt/2)=v(0)-0.5*a_c(0)*dt - 修复 C/C++ number of frames 字段写采样帧数而非总积分步数的 bug - Python 引擎:新增 display.npz 二进制格式,draw.py/plot_wave.py 优先读取 - 编译参数统一为 -O3 -march=native -ffast-math
This commit is contained in:
+16
-131
@@ -14,6 +14,7 @@ import sys
|
||||
import subprocess
|
||||
import time
|
||||
import argparse
|
||||
import json
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
|
||||
@@ -32,6 +33,13 @@ def _fmt_alpha(v):
|
||||
return str(float(v))
|
||||
|
||||
|
||||
def _json_field(value):
|
||||
"""Serialize arrays/lists for display header metadata."""
|
||||
if isinstance(value, np.ndarray):
|
||||
value = value.tolist()
|
||||
return json.dumps(value, ensure_ascii=False)
|
||||
|
||||
|
||||
def _load_camera_kf(config, runtime_base):
|
||||
"""加载 move_camera.txt(速度段格式)→ JSON 字符串。"""
|
||||
import re, json
|
||||
@@ -295,6 +303,12 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
"driving_force": str(data.get("driving_force", 0)),
|
||||
"use_marker": str(config.get("use_marker", 0)),
|
||||
"alpha": _fmt_alpha(data.get("alpha", 0.2)),
|
||||
"atom_masses": _json_field(data.get("atom_masses", [])),
|
||||
"atom_positions": _json_field(data.get("atom_positions", [])),
|
||||
"bond_pairs": _json_field(data.get("bond_pairs", [])),
|
||||
"bond_stiffness": _json_field(data.get("bond_stiffness", [])),
|
||||
"bond_rest_lengths": _json_field(data.get("bond_rest_lengths", [])),
|
||||
"G": _json_field(data.get("G", [0.0, 0.0, 0.0])),
|
||||
"atom_radii": _fmt_alpha(data.get("atom_radii", [])),
|
||||
"camera_distance": str(config.get("camera_distance", 40.0)),
|
||||
"camera_elevation": str(config.get("camera_elevation", 0)),
|
||||
@@ -320,137 +334,8 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
|
||||
# 4. 绘图(可选)
|
||||
if not no_plot and config.get("step_plot", 1):
|
||||
try:
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# 配置中文字体支持
|
||||
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'WenQuanYi Micro Hei', 'DejaVu Sans']
|
||||
plt.rcParams['axes.unicode_minus'] = False
|
||||
|
||||
time_arr = np.arange(NT) * DT
|
||||
n_atoms = all_x.shape[1]
|
||||
atom_ids_list = data.get("atom_ids", np.arange(n_atoms) + 1)
|
||||
|
||||
fig, axes = plt.subplots(3, 3, figsize=(15, 13))
|
||||
fig.suptitle("轨迹与能量分析", fontsize=16)
|
||||
|
||||
# ── 位置 / 速度 6 子图(前 2 行,每行 3 列) ──
|
||||
plot_configs = [
|
||||
(axes[0, 0], all_x, "x - 时间"),
|
||||
(axes[0, 1], all_y, "y - 时间"),
|
||||
(axes[0, 2], all_z, "z - 时间"),
|
||||
(axes[1, 0], all_vx, "vx - 时间"),
|
||||
(axes[1, 1], all_vy, "vy - 时间"),
|
||||
(axes[1, 2], all_vz, "vz - 时间"),
|
||||
]
|
||||
|
||||
colors = plt.cm.tab10(np.linspace(0, 1, n_atoms))
|
||||
|
||||
for ax, data_arr, title in plot_configs:
|
||||
for i in range(n_atoms):
|
||||
atom_id = int(atom_ids_list[i])
|
||||
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)
|
||||
ax.legend()
|
||||
|
||||
# ── 能量计算 ─────────────────────────────────────
|
||||
masses = np.array(data["atom_masses"]) # (n_atoms,)
|
||||
G_vec = np.array(data.get("G", [0.0, 0.0, -9.8])) # [gx, gy, gz]
|
||||
gravity_field_enabled = int(data.get("gravity_field", 1))
|
||||
gravity_interaction_enabled = int(data.get("gravity_interaction", 0))
|
||||
gravity_strength = float(data.get("gravity_strength", 1.0))
|
||||
elastic_force_enabled = int(data.get("elastic_force", 1))
|
||||
damping_force_enabled = int(data.get("damping_force", 0))
|
||||
|
||||
# 动能 Ek = ½ m v²
|
||||
ek = 0.5 * masses[np.newaxis, :] * (all_vx**2 + all_vy**2 + all_vz**2)
|
||||
|
||||
# 均匀重力场势能 Ug = -m G·r
|
||||
ug = np.zeros_like(ek)
|
||||
if gravity_field_enabled:
|
||||
ug = -masses[np.newaxis, :] * (
|
||||
G_vec[0] * all_x + G_vec[1] * all_y + G_vec[2] * all_z
|
||||
)
|
||||
|
||||
# 弹性势能 Us = ½ k (d - d₀)²
|
||||
us = np.zeros_like(ek)
|
||||
bond_pairs = data.get("bond_pairs")
|
||||
bond_stiffness = data.get("bond_stiffness")
|
||||
bond_rest_lengths = data.get("bond_rest_lengths")
|
||||
if elastic_force_enabled and bond_pairs is not None and len(bond_pairs) > 0:
|
||||
for b_idx in range(len(bond_pairs)):
|
||||
i, j = bond_pairs[b_idx]
|
||||
dx = all_x[:, j] - all_x[:, i]
|
||||
dy = all_y[:, j] - all_y[:, i]
|
||||
dz = all_z[:, j] - all_z[:, i]
|
||||
dist = np.sqrt(dx**2 + dy**2 + dz**2)
|
||||
stretch = dist - bond_rest_lengths[b_idx]
|
||||
us_each = 0.5 * bond_stiffness[b_idx] * stretch**2
|
||||
us[:, i] += us_each # 将整根键的势能记给 i
|
||||
|
||||
# 万有引力势能 Ug_grav = -G_grav * m_i * m_j / r
|
||||
ug_grav = np.zeros_like(ek)
|
||||
if gravity_interaction_enabled:
|
||||
n_atoms_en = len(masses)
|
||||
for i in range(n_atoms_en):
|
||||
for j in range(i + 1, n_atoms_en):
|
||||
dx = all_x[:, j] - all_x[:, i]
|
||||
dy = all_y[:, j] - all_y[:, i]
|
||||
dz = all_z[:, j] - all_z[:, i]
|
||||
dist = np.sqrt(dx**2 + dy**2 + dz**2)
|
||||
dist = np.maximum(dist, 1e-12)
|
||||
pair_pe = -gravity_strength * masses[i] * masses[j] / dist
|
||||
ug_grav[:, i] += 0.5 * pair_pe
|
||||
ug_grav[:, j] += 0.5 * pair_pe
|
||||
|
||||
# 各原子总能量
|
||||
e_total = ek + ug + us + ug_grav # (NT, n_atoms)
|
||||
|
||||
# 系统能量分量
|
||||
ek_sys = np.sum(ek, axis=1)
|
||||
ug_sys = np.sum(ug, axis=1)
|
||||
us_sys = np.sum(us, axis=1)
|
||||
ug_grav_sys = np.sum(ug_grav, axis=1)
|
||||
e_sys = ek_sys + ug_sys + us_sys + ug_grav_sys
|
||||
|
||||
# ── 第 3 行左:各原子总能量 ──
|
||||
ax_e = axes[2, 0]
|
||||
for i in range(n_atoms):
|
||||
aid = int(atom_ids_list[i])
|
||||
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("能量")
|
||||
ax_e.grid(True, alpha=0.3)
|
||||
ax_e.legend(loc="upper right")
|
||||
|
||||
# ── 第 3 行右:系统总能量 ──
|
||||
ax_sys = axes[2, 1]
|
||||
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_arr, us_sys, color='orange', linewidth=1.5, label="系统弹性势能")
|
||||
if gravity_interaction_enabled:
|
||||
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("能量")
|
||||
ax_sys.grid(True, alpha=0.3)
|
||||
ax_sys.legend(loc="upper right")
|
||||
|
||||
# 隐藏第 3 行第 3 列空子图
|
||||
axes[2, 2].set_visible(False)
|
||||
|
||||
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
|
||||
plot_path = os.path.join(output_dir_abs, "trajectory_plots.png")
|
||||
plt.savefig(plot_path, dpi=300, bbox_inches="tight")
|
||||
print(f"[run] 轨迹图表已保存至: {plot_path}")
|
||||
plt.show()
|
||||
except ImportError:
|
||||
print("[run] 警告: 未安装 matplotlib,跳过绘图")
|
||||
print("[run] 注意: 旧版 step_plot 绘图路径依赖完整轨迹局部变量,当前已暂时跳过。")
|
||||
print("[run] 如需波形/能量动画,请使用 step_plot_wave: 1。")
|
||||
|
||||
print(f"[run] 完成!输出目录: {output_dir_abs}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user