modified: CMakeLists.txt
modified: INSTALL.md modified: README.md modified: build_release_zip.py modified: compute.py new file: doc/index.html modified: dynamics.py modified: engines/c/main.c modified: engines/cpp/main.cpp modified: engines/fortran/main.f90 modified: examples/case01/input/coord.txt renamed: examples/case01/input/parameters.yaml -> examples/case01/input/input.txt modified: examples/case01/run_dynamics.py new file: examples/case02/input/bond.txt new file: examples/case02/input/connection.txt new file: examples/case02/input/coord.txt new file: examples/case02/input/input.txt new file: examples/case02/run_dynamics.py
This commit is contained in:
+95
-36
@@ -4,9 +4,9 @@ dynamics.py
|
||||
统一入口:读取 YAML 配置文件 → 运行模拟 → 抽帧 → 绘图(可选)
|
||||
|
||||
用法:
|
||||
python dynamics.py # 使用 input/parameters.yaml
|
||||
python dynamics.py input/parameters.yaml # 指定配置文件
|
||||
python dynamics.py --config input/parameters.yaml --no-plot
|
||||
python dynamics.py # 使用 input/input.txt
|
||||
python dynamics.py input/input.txt # 指定配置文件(YAML 格式)
|
||||
python dynamics.py --config input/input.txt --no-plot
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -115,6 +115,14 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
config = load_yaml_config(config_path)
|
||||
print(f"[run] 已加载配置: {config_path}")
|
||||
|
||||
# ── T_total → NT 自动转换 ──────────────────────
|
||||
if "T_total" in config and "NT" not in config:
|
||||
dt = float(config["DT"])
|
||||
config["NT"] = int(float(config["T_total"]) / dt)
|
||||
print(f"[run] T_total={config['T_total']} → NT={config['NT']} (DT={dt})")
|
||||
elif "T_total" in config and "NT" in config:
|
||||
print(f"[run] 同时指定了 T_total 和 NT,使用 NT={config['NT']}")
|
||||
|
||||
# 显示步骤控制信息
|
||||
steps_info = {k: config.get(k, 1) for k in ["step_simulate", "step_sample", "step_plot", "step_animation"]}
|
||||
step_flags = ", ".join(f"{k}={v}" for k, v in steps_info.items())
|
||||
@@ -136,27 +144,42 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
disp_path = os.path.join(output_dir_abs, "display.txt")
|
||||
|
||||
# ── 自动缓存检测 ───────────────────────────────────────
|
||||
# 若 output/ 中 trajectory.txt 和 display.txt 均已存在,
|
||||
# 自动跳过模拟和抽帧,直接使用已有结果。
|
||||
# 如需强制重新计算,删除 output/ 目录或设 step_simulate: 1 即可。
|
||||
output_exists = (
|
||||
os.path.isdir(output_dir_abs)
|
||||
and os.path.exists(traj_path)
|
||||
and os.path.exists(disp_path)
|
||||
)
|
||||
if output_exists:
|
||||
if config.get("step_simulate", 1):
|
||||
print(f"[run] 检测到已有输出({traj_path}),自动跳过模拟与抽帧,直接进入后续步骤")
|
||||
config["step_simulate"] = 0
|
||||
config["step_sample"] = 0
|
||||
else:
|
||||
print(f"[run] 已有输出,步骤已被跳过")
|
||||
# force_calc=1: 强制重新计算,忽略缓存
|
||||
# force_calc=0: 尊重 step_simulate 设置,不自动覆盖
|
||||
force_calc = int(config.get("force_calc", 0))
|
||||
if force_calc:
|
||||
print(f"[run] force_calc=1,跳过缓存,强制重新计算")
|
||||
config["step_simulate"] = 1
|
||||
config["step_sample"] = 1
|
||||
elif config.get("step_simulate", 1):
|
||||
# step_simulate=1 且 force_calc=0 → 按用户要求执行计算
|
||||
# 但检测一下参数是否已变更(NT),如果变了则自动更新 step_sample
|
||||
if os.path.isdir(output_dir_abs) and os.path.exists(traj_path) and os.path.exists(disp_path):
|
||||
cached_nt = None
|
||||
try:
|
||||
with open(traj_path, 'rb') as _f:
|
||||
_f.seek(-4096, 2)
|
||||
_tail = _f.read().decode('utf-8', errors='replace')
|
||||
import re as _re
|
||||
_m = _re.search(r'"NT":\s*(\d+)', _tail)
|
||||
if _m:
|
||||
cached_nt = int(_m.group(1))
|
||||
except Exception:
|
||||
pass
|
||||
config_nt = int(config.get("NT", 0))
|
||||
if cached_nt is not None and cached_nt != config_nt:
|
||||
print(f"[run] 参数已变更(缓存 NT={cached_nt},配置 NT={config_nt}),"
|
||||
f"将重新计算")
|
||||
config["step_sample"] = 1
|
||||
else:
|
||||
# 参数一致,按 step_simulate=1 执行,step_sample 由用户设置决定
|
||||
pass
|
||||
else:
|
||||
# 目录存在但文件不全 → 强制重新计算
|
||||
if os.path.isdir(output_dir_abs):
|
||||
print(f"[run] output/ 目录存在但文件不完整,将重新计算")
|
||||
# step_simulate=0 → 检测缓存是否存在并提示
|
||||
if os.path.isdir(output_dir_abs) and os.path.exists(traj_path) and os.path.exists(disp_path):
|
||||
print(f"[run] 已有输出,步骤已被跳过")
|
||||
else:
|
||||
print(f"[run] output/ 目录不存在,将执行完整流程")
|
||||
print(f"[run] 没有可用的缓存输出,但 step_simulate=0,将跳过模拟")
|
||||
|
||||
# 2. 运行物理模拟 → output/trajectory.txt
|
||||
if config.get("step_simulate", 1):
|
||||
@@ -164,9 +187,12 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
total_steps = config["NT"]
|
||||
record_steps = total_steps - (config.get("warmup_steps") or 0)
|
||||
print(f"[run] 开始计算 总步数={total_steps} 记录步数={record_steps} DT={config['DT']}")
|
||||
|
||||
import time as _time
|
||||
_t0 = _time.time()
|
||||
|
||||
if engine == "python":
|
||||
traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz = compute.run_from_config(config, str(runtime_base))
|
||||
print(f"[run] 计算完成,记录 {record_steps} 步")
|
||||
compute.save_trajectory_txt(traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz, str(runtime_base))
|
||||
else:
|
||||
# 外部引擎:先加载配置到全局变量,再运行引擎,再用 save_trajectory_txt 补全 metadata
|
||||
@@ -178,6 +204,9 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz = compute.run_engine(
|
||||
engine, input_dir_abs, output_dir_abs, config)
|
||||
compute.save_trajectory_txt(traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz, str(runtime_base))
|
||||
|
||||
_elapsed = _time.time() - _t0
|
||||
print(f"[run] 引擎: {engine} 计算完成: {record_steps} 步 {_elapsed:.3f} s")
|
||||
else:
|
||||
print("[run] 步骤 [模拟] 已跳过,直接加载已有轨迹")
|
||||
if not os.path.exists(traj_path):
|
||||
@@ -282,6 +311,11 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
"box_color_r": float(data["box_color_r"]),
|
||||
"box_color_g": float(data["box_color_g"]),
|
||||
"box_color_b": float(data["box_color_b"]),
|
||||
"gravity_field": int(data.get("gravity_field", 1)),
|
||||
"gravity_interaction": int(data.get("gravity_interaction", 0)),
|
||||
"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)),
|
||||
}
|
||||
save_display_txt(disp_data, str(runtime_base))
|
||||
print(f"[run] 抽帧完成: {sample_end - sample_start} 步 -> {n_frames} 帧")
|
||||
@@ -328,21 +362,28 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
# ── 能量计算 ─────────────────────────────────────
|
||||
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 = -masses[np.newaxis, :] * (
|
||||
G_vec[0] * all_x + G_vec[1] * all_y + G_vec[2] * all_z
|
||||
)
|
||||
# 均匀重力场势能 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 bond_pairs is not None and len(bond_pairs) > 0:
|
||||
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]
|
||||
@@ -350,17 +391,33 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
dz = all_z[:, j] - all_z[:, i]
|
||||
dist = np.sqrt(dx**2 + dy**2 + dz**2)
|
||||
stretch = dist - bond_rest_lengths[b_idx]
|
||||
us[:, i] += 0.5 * bond_stiffness[b_idx] * stretch**2
|
||||
us[:, j] += 0.5 * bond_stiffness[b_idx] * stretch**2
|
||||
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 # (NT, n_atoms)
|
||||
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)
|
||||
e_sys = ek_sys + ug_sys + us_sys
|
||||
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]
|
||||
@@ -376,9 +433,11 @@ 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="系统重力势能")
|
||||
if bond_pairs is not None and len(bond_pairs) > 0:
|
||||
ax_sys.plot(time, 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="系统弹性势能")
|
||||
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.set_title("系统总能量")
|
||||
ax_sys.set_xlabel("时间 (s)")
|
||||
@@ -421,8 +480,8 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="物理模拟统一入口")
|
||||
parser.add_argument("config_file", nargs="?", default=os.path.join("input", "parameters.yaml"),
|
||||
help="YAML 配置文件路径(默认: input/parameters.yaml)")
|
||||
parser.add_argument("config_file", nargs="?", default=os.path.join("input", "input.txt"),
|
||||
help="YAML 配置文件路径(默认: input/input.txt,虽然是 .txt 后缀但使用 YAML 格式)")
|
||||
parser.add_argument("--config", dest="config_override",
|
||||
help="YAML 配置文件路径(可选,优先于位置参数)")
|
||||
parser.add_argument("--input-dir", default="input",
|
||||
|
||||
Reference in New Issue
Block a user