Compare commits
66 Commits
8261017879
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e415bf5ba5 | |||
| b50c55d0a3 | |||
| 75d8aae1f2 | |||
| dd6fbdfccf | |||
| eea62de479 | |||
| 1261398be2 | |||
| 369b840cf2 | |||
| 7fb2b730e9 | |||
| e371fa8db1 | |||
| 7468a2d458 | |||
| 76a26d888e | |||
| 8183cbc22e | |||
| 41c454eeab | |||
| 6c06c00f4b | |||
| c8d12e7f45 | |||
| 7078cbc744 | |||
| d4ce23a07e | |||
| 84aed813df | |||
| f6c468702f | |||
| 8b82d3c4e4 | |||
| 80b7467421 | |||
| 974216332c | |||
| a16d2239a1 | |||
| 0e636e275d | |||
| ea99f09f9b | |||
| b584c4489c | |||
| 2ab3436235 | |||
| 39ff650539 | |||
| 1cefe184d7 | |||
| d371b28acc | |||
| d489222eaf | |||
| e62e536cee | |||
| e1ade53fff | |||
| d930fb558c | |||
| 782422e800 | |||
| e40393d793 | |||
| dc7bc00616 | |||
| 41790a782a | |||
| 6298ed5b34 | |||
| b95a3579fc | |||
| b4fed4fbb8 | |||
| c454162d0b | |||
| e40f7a49e4 | |||
| 22b94011ee | |||
| f1afb7c479 | |||
| 757a891a43 | |||
| 466a301d34 | |||
| 54aa20d7c5 | |||
| c3e50d265d | |||
| 6b5e12da46 | |||
| 7417d47658 | |||
| a3fa8b90f6 | |||
| ab3a847483 | |||
| 0c332b7dfc | |||
| 9d1f84d2bf | |||
| c158c74609 | |||
| 52505e9aff | |||
| db50ac6d4d | |||
| 42c6776eff | |||
| 9d5997afec | |||
| e353e04133 | |||
| 1fd87cc33b | |||
| b783cbb981 | |||
| b9ec622808 | |||
| 2ce8ded482 | |||
| 586d7c29b2 |
+8
-6
@@ -1,7 +1,9 @@
|
||||
# 全部使用 LF 换行,仓库内外一致,不随系统自动转换
|
||||
* text eol=lf
|
||||
# 引擎 DLL 是二进制文件,禁止行尾转换
|
||||
engines/release/*.dll binary
|
||||
|
||||
# 二进制文件不转换
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.ico binary
|
||||
# Python 源码使用 LF
|
||||
*.py text eol=lf
|
||||
|
||||
# Makefile 使用 LF
|
||||
Makefile text eol=lf
|
||||
*.mk text eol=lf
|
||||
|
||||
+8
-6
@@ -18,10 +18,9 @@ pip-wheel-metadata/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# ── C / C++ 编译产物 ─────────────────────────────────────────
|
||||
# Makefile 构建输出(engines/c/build/)
|
||||
engines/c/build/
|
||||
engines/cpp/build/
|
||||
# ── C / C++ / Fortran 编译产物 ────────────────────────
|
||||
# 源码目录 engines/src/*/ 中可能产生的构建输出
|
||||
engines/src/*/build/
|
||||
|
||||
# CMake 构建目录(根目录或自定义 build 目录)
|
||||
CMakeCache.txt
|
||||
@@ -42,12 +41,11 @@ build_*/
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# 可执行文件(保留源码,排除编译出的二进制)
|
||||
# 注意:Windows 下 .exe 后缀的可执行文件
|
||||
*.exe
|
||||
# 但 engines/c/Makefile 里指定了 build/ 目录,已由上面覆盖
|
||||
# 可在 engines/src/*/ 中用 make dll 编译引擎 DLL
|
||||
|
||||
# 运行时生成的引擎参数文件(每次运行都会覆盖)
|
||||
engines/*/param.json
|
||||
@@ -99,3 +97,7 @@ desktop.ini
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.tar.bz2
|
||||
output_test/
|
||||
|
||||
optimization
|
||||
optimization/*
|
||||
+920
-117
File diff suppressed because it is too large
Load Diff
@@ -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/
|
||||
@@ -11,6 +10,7 @@
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from vispy import app, scene
|
||||
@@ -30,89 +30,348 @@ else:
|
||||
output_dir = compute.get_output_dir(script_dir)
|
||||
os.environ["DYNAMICS_OUTPUT_DIR"] = output_dir
|
||||
disp_path = os.path.join(output_dir, "display.txt")
|
||||
npz_path = os.path.join(output_dir, "display.npz")
|
||||
|
||||
if not os.path.exists(disp_path):
|
||||
if not os.path.exists(npz_path) and not os.path.exists(disp_path):
|
||||
raise FileNotFoundError(
|
||||
f"找不到 display.txt!\n"
|
||||
f"期望路径: {disp_path}\n"
|
||||
f"找不到 display.npz 或 display.txt!\n"
|
||||
f"期望路径: {output_dir}\n"
|
||||
f"请先运行 compute.py 计算轨迹,再运行 sample.py 生成显示数组。\n"
|
||||
f"用法: python draw.py [案例输出目录]"
|
||||
)
|
||||
|
||||
disp_data = compute.load_text_data(disp_path)
|
||||
# 优先读二进制 npz(加载速度约快 5-10x)
|
||||
if os.path.exists(npz_path):
|
||||
disp_data = compute.load_display_npz(npz_path)
|
||||
else:
|
||||
disp_data = compute.load_display_txt(disp_path)
|
||||
|
||||
# 单原子数据(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"]
|
||||
# ── 从 input.txt 读取参数(替代 display.npz 中的 meta)──
|
||||
try:
|
||||
import yaml
|
||||
_have_yaml = True
|
||||
except ImportError:
|
||||
_have_yaml = False
|
||||
|
||||
# 全原子数据(用于多球绘制)
|
||||
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"]
|
||||
input_dir = os.path.join(os.path.dirname(output_dir), "input")
|
||||
input_path = os.path.join(input_dir, "input.txt")
|
||||
|
||||
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"])
|
||||
if _have_yaml and os.path.exists(input_path):
|
||||
try:
|
||||
with open(input_path, "r", encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f)
|
||||
except Exception:
|
||||
config = {}
|
||||
else:
|
||||
config = {}
|
||||
# 兼容旧版:若 input.txt 不存在或解析失败,降级到 display.npz 的 meta
|
||||
if not config:
|
||||
config = disp_data.get("header_fields", {})
|
||||
|
||||
# ── 从 coord.txt 读取平衡位置 ─────────────────
|
||||
# 注意: config 中的 *_file 可能带 "input/" 前缀,但 input_dir 已指向 input/ 目录
|
||||
_coord_file_raw = config.get("coord_file", "coord.txt")
|
||||
coord_path = os.path.join(input_dir, os.path.basename(_coord_file_raw))
|
||||
if os.path.exists(coord_path):
|
||||
try:
|
||||
_ids, _masses, _radii, _pos, _vel, _fixed = compute.load_coord_file(coord_path)
|
||||
EQ_POS = _pos # (n_atoms, 3)
|
||||
ATOM_FIXED = _fixed # (n_atoms, 3)
|
||||
except Exception:
|
||||
EQ_POS = None
|
||||
ATOM_FIXED = None
|
||||
else:
|
||||
EQ_POS = None
|
||||
ATOM_FIXED = None
|
||||
|
||||
# ── 从 connection.txt 读取成键信息(若 meta 中没有)──
|
||||
BOND_PAIRS = disp_data.get("bond_pairs", [])
|
||||
if not BOND_PAIRS and 'bond_pairs' not in disp_data:
|
||||
conn_path = os.path.join(input_dir, os.path.basename(config.get("connection_file", "connection.txt")))
|
||||
if os.path.exists(conn_path):
|
||||
try:
|
||||
_pairs = []
|
||||
with open(conn_path, "r", encoding="utf-8") as _f:
|
||||
_f.readline() # skip header
|
||||
for _line in _f:
|
||||
_line = _line.strip()
|
||||
if not _line or _line.startswith("#"):
|
||||
continue
|
||||
_parts = _line.split()
|
||||
if len(_parts) >= 2:
|
||||
_pairs.append([int(_parts[0]) - 1, int(_parts[1]) - 1])
|
||||
BOND_PAIRS = np.array(_pairs, dtype=np.int32) if _pairs else []
|
||||
except Exception:
|
||||
BOND_PAIRS = []
|
||||
BOND_PAIRS = BOND_PAIRS.tolist() if hasattr(BOND_PAIRS, 'tolist') else BOND_PAIRS
|
||||
|
||||
# 全原子帧数据
|
||||
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_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]
|
||||
|
||||
N_FRAMES = DISP_ALL_X.shape[0]
|
||||
NT = int(disp_data["n_total_frames"])
|
||||
N_ATOMS = int(disp_data["n_total_particles"])
|
||||
DT = float(config.get("DT", 0.001))
|
||||
|
||||
# 视觉位移放大:display_amp: [ax, ay, az],对偏离平衡位置的位移乘以倍数
|
||||
_damp_raw = config.get("display_amp", "")
|
||||
if isinstance(_damp_raw, (list, tuple)):
|
||||
_damp = np.array(_damp_raw, dtype=np.float64)
|
||||
elif isinstance(_damp_raw, str) and _damp_raw.strip():
|
||||
import ast as _ast
|
||||
_damp = np.array(_ast.literal_eval(_damp_raw.strip()), dtype=np.float64)
|
||||
else:
|
||||
_damp = None
|
||||
if _damp is not None and _damp.shape == (3,) and not np.allclose(_damp, 1.0):
|
||||
if EQ_POS is not None:
|
||||
_eq_x = EQ_POS[None, :, 0] # coord.txt 平衡位置
|
||||
_eq_y = EQ_POS[None, :, 1]
|
||||
_eq_z = EQ_POS[None, :, 2]
|
||||
else:
|
||||
_eq_x = DISP_ALL_X[0:1, :] # 第0帧作为平衡位置参考
|
||||
_eq_y = DISP_ALL_Y[0:1, :]
|
||||
_eq_z = DISP_ALL_Z[0:1, :]
|
||||
DISP_ALL_X = _eq_x + (DISP_ALL_X - _eq_x) * _damp[0]
|
||||
DISP_ALL_Y = _eq_y + (DISP_ALL_Y - _eq_y) * _damp[1]
|
||||
DISP_ALL_Z = _eq_z + (DISP_ALL_Z - _eq_z) * _damp[2]
|
||||
|
||||
# ── 位移颜色映射 ──────────────────────────────
|
||||
# display_color: { mode: [enabled, [R,G,B]], ... }
|
||||
# mode: x, y, z, xy, yz, zx, xyz
|
||||
# enabled: 0=off, 1=on
|
||||
# [R,G,B]: 最大位移时的颜色(渐变起点为白色)
|
||||
# 例:xyz: [1, [1.0,1.0,1.0]] → 三方向合成位移 → 白色渐变
|
||||
FRAME_COLORS = None
|
||||
_DC_MODE = None
|
||||
_DC_COLOR = None
|
||||
|
||||
_raw_dc = config.get("display_color", "")
|
||||
if _raw_dc:
|
||||
try:
|
||||
dc = json.loads(_raw_dc) if isinstance(_raw_dc, str) else _raw_dc
|
||||
if isinstance(dc, dict):
|
||||
for mode, (enabled, color) in dc.items():
|
||||
if int(enabled) and mode in ("x","y","z","xy","yz","zx","xyz"):
|
||||
_DC_MODE = mode
|
||||
_DC_COLOR = np.array(color, dtype=np.float32)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if _DC_MODE is not None and _DC_COLOR is not None:
|
||||
if EQ_POS is not None:
|
||||
eq_x = EQ_POS[None, :, 0] # coord.txt 平衡位置
|
||||
eq_y = EQ_POS[None, :, 1]
|
||||
eq_z = EQ_POS[None, :, 2]
|
||||
else:
|
||||
eq_x = DISP_ALL_X[0:1, :] # 第0帧
|
||||
eq_y = DISP_ALL_Y[0:1, :]
|
||||
eq_z = DISP_ALL_Z[0:1, :]
|
||||
dx = DISP_ALL_X - eq_x
|
||||
dy = DISP_ALL_Y - eq_y
|
||||
dz = DISP_ALL_Z - eq_z
|
||||
|
||||
if _DC_MODE == "x":
|
||||
disp_mag = np.abs(dx)
|
||||
elif _DC_MODE == "y":
|
||||
disp_mag = np.abs(dy)
|
||||
elif _DC_MODE == "z":
|
||||
disp_mag = np.abs(dz)
|
||||
elif _DC_MODE == "xy":
|
||||
disp_mag = np.sqrt(dx**2 + dy**2)
|
||||
elif _DC_MODE == "yz":
|
||||
disp_mag = np.sqrt(dy**2 + dz**2)
|
||||
elif _DC_MODE == "zx":
|
||||
disp_mag = np.sqrt(dz**2 + dx**2)
|
||||
else: # xyz
|
||||
disp_mag = np.sqrt(dx**2 + dy**2 + dz**2)
|
||||
|
||||
# ── color_xrange: 限定归一化基准的原子范围 ───────────
|
||||
# 格式: [['min'|'mid'|'max'|数值, 'min'|'mid'|'max'|数值], ...] 对应 x,y,z
|
||||
# 在此范围内的原子计算最大位移 d_max,所有原子以此基准归一化着色
|
||||
_cxr = config.get("color_xrange", None)
|
||||
_d_max_source = disp_mag # 默认:所有原子
|
||||
_range_label = "全部原子"
|
||||
if _cxr and isinstance(_cxr, (list, tuple)) and len(_cxr) == 3:
|
||||
try:
|
||||
# 获取坐标极值
|
||||
if EQ_POS is not None:
|
||||
_eq_all = EQ_POS
|
||||
else:
|
||||
_eq_all = np.column_stack([
|
||||
DISP_ALL_X[0], DISP_ALL_Y[0], DISP_ALL_Z[0]])
|
||||
_cmin = _eq_all.min(axis=0)
|
||||
_cmax = _eq_all.max(axis=0)
|
||||
_cmid = (_cmin + _cmax) / 2
|
||||
|
||||
_key_map = {"min": _cmin, "mid": _cmid, "max": _cmax}
|
||||
_range_lo = np.zeros(3, dtype=np.float64)
|
||||
_range_hi = np.zeros(3, dtype=np.float64)
|
||||
|
||||
for _i in range(3):
|
||||
_lo = _cxr[_i][0]
|
||||
_hi = _cxr[_i][1]
|
||||
_range_lo[_i] = _key_map[_lo][_i] if _lo in _key_map else float(_lo)
|
||||
_range_hi[_i] = _key_map[_hi][_i] if _hi in _key_map else float(_hi)
|
||||
|
||||
_in_x = (_eq_all[:, 0] >= _range_lo[0]) & (_eq_all[:, 0] <= _range_hi[0])
|
||||
_in_y = (_eq_all[:, 1] >= _range_lo[1]) & (_eq_all[:, 1] <= _range_hi[1])
|
||||
_in_z = (_eq_all[:, 2] >= _range_lo[2]) & (_eq_all[:, 2] <= _range_hi[2])
|
||||
_color_mask = _in_x & _in_y & _in_z
|
||||
|
||||
_n_in_range = _color_mask.sum()
|
||||
if _n_in_range > 0:
|
||||
_d_max_source = disp_mag[:, _color_mask] # 仅在范围内找最大位移
|
||||
_range_label = (f"x[{_range_lo[0]:.1f},{_range_hi[0]:.1f}] "
|
||||
f"y[{_range_lo[1]:.1f},{_range_hi[1]:.1f}] "
|
||||
f"z[{_range_lo[2]:.1f},{_range_hi[2]:.1f}]")
|
||||
except Exception as _e:
|
||||
print(f"[draw] color_xrange 解析失败: {_e}")
|
||||
|
||||
# 用(范围限定的)最大位移归一化,所有原子统一着色
|
||||
d_max = _d_max_source.max()
|
||||
if d_max > 1e-12:
|
||||
disp_norm = disp_mag / d_max
|
||||
else:
|
||||
disp_norm = disp_mag
|
||||
|
||||
FRAME_COLORS = np.ones((N_FRAMES, N_ATOMS, 4), dtype=np.float32)
|
||||
t = disp_norm[..., None]
|
||||
FRAME_COLORS[..., :3] = 1.0 + (_DC_COLOR - 1.0) * t
|
||||
print(f"[draw] 位移颜色映射: mode={_DC_MODE}, color={_DC_COLOR.tolist()}, "
|
||||
f"d_max={d_max:.4f}, 范围: {_range_label}")
|
||||
|
||||
# ── color_driver: 驱动原子 → 统一颜色 ──
|
||||
_cd_raw = config.get("color_driver", None)
|
||||
if _cd_raw is not None:
|
||||
try:
|
||||
_cd = np.array(_cd_raw, dtype=np.float32)
|
||||
# 读取 driver.txt 获取驱动原子 ID
|
||||
_driver_file = config.get("driver_file", "driver.txt")
|
||||
_driver_path = os.path.join(input_dir, os.path.basename(_driver_file))
|
||||
if os.path.exists(_driver_path):
|
||||
_driver_ids = []
|
||||
with open(_driver_path, "r", encoding="utf-8") as _df:
|
||||
_df.readline() # skip header
|
||||
for _line in _df:
|
||||
_line = _line.strip()
|
||||
if not _line or _line.startswith("#"):
|
||||
continue
|
||||
_parts = _line.split()
|
||||
if _parts:
|
||||
_driver_ids.append(int(_parts[0]) - 1) # 1-based → 0-based
|
||||
if _driver_ids:
|
||||
FRAME_COLORS[:, _driver_ids, :3] = _cd
|
||||
print(f"[draw] color_driver: {len(_driver_ids)} 驱动原子 → RGB{_cd_raw}")
|
||||
except Exception as _e:
|
||||
print(f"[draw] color_driver 解析失败: {_e}")
|
||||
|
||||
# ── color_fix: 全固定原子 (fix_x=fix_y=fix_z=1) → 统一颜色 ──
|
||||
_cf_raw = config.get("color_fix", None)
|
||||
if _cf_raw is not None and ATOM_FIXED is not None:
|
||||
try:
|
||||
_cf = np.array(_cf_raw, dtype=np.float32)
|
||||
_full_fixed = (ATOM_FIXED[:, 0] == 1) & (ATOM_FIXED[:, 1] == 1) & (ATOM_FIXED[:, 2] == 1)
|
||||
_n_fix = _full_fixed.sum()
|
||||
if _n_fix > 0:
|
||||
FRAME_COLORS[:, _full_fixed, :3] = _cf
|
||||
print(f"[draw] color_fix: {_n_fix} 全固定原子 → RGB{_cf_raw}")
|
||||
except Exception as _e:
|
||||
print(f"[draw] color_fix 解析失败: {_e}")
|
||||
NSTEP = int(config.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"]
|
||||
# 优先使用 per-atom 半径,否则用统一的 ball_radius
|
||||
_raw_radii = config.get("atom_radii", "")
|
||||
if _raw_radii.strip():
|
||||
ATOM_RADII = np.array([float(x) for x in _raw_radii.split(",")])
|
||||
else:
|
||||
ATOM_RADII = np.full(N_ATOMS, float(config.get("ball_radius", 0.5)))
|
||||
PLOT_ATOM_ROW = 0
|
||||
PLOT_ATOM_ID = int(ATOM_IDS[0])
|
||||
# 成键信息已在上面从 connection.txt 加载
|
||||
|
||||
# 渲染方式:0=Sphere(网格球体), 1=Marker(GPU点精灵)
|
||||
USE_MARKER = int(disp_data.get("use_marker", 0))
|
||||
USE_MARKER = int(config.get("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]
|
||||
if len(alpha_list) != 6:
|
||||
raise ValueError(f"alpha 数组长度须为 6,实际为 {len(alpha_list)}")
|
||||
# 模拟边界(从 input.txt 的 box_a 计算)
|
||||
_box_a = float(config.get("box_a", 10.0))
|
||||
X_MIN = -_box_a; X_MAX = _box_a
|
||||
Y_MIN = -_box_a; Y_MAX = _box_a
|
||||
Z_MIN = -_box_a; Z_MAX = _box_a
|
||||
raw_alpha = config.get("alpha", "0.2")
|
||||
if isinstance(raw_alpha, (list, tuple)):
|
||||
alpha_list = [float(x) for x in raw_alpha]
|
||||
else:
|
||||
alpha_list = [float(raw_alpha)] * 6
|
||||
try:
|
||||
alpha_list = [float(x) for x in raw_alpha.split(",")]
|
||||
except (ValueError, AttributeError):
|
||||
alpha_list = [float(raw_alpha)] * 6
|
||||
if len(alpha_list) != 6:
|
||||
alpha_list = (alpha_list * 6)[: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(config.get("ball_radius", 0.5))
|
||||
ball_color_r = float(config.get("ball_color_r", 0.9))
|
||||
ball_color_g = float(config.get("ball_color_g", 0.2))
|
||||
ball_color_b = float(config.get("ball_color_b", 0.2))
|
||||
box_color_r = float(config.get("box_color_r", 0.8))
|
||||
box_color_g = float(config.get("box_color_g", 0.8))
|
||||
box_color_b = float(config.get("box_color_b", 0.85))
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 图形界面无关的几何参数(不参与物理计算,仅用于场景外观)
|
||||
# ===========================================================================
|
||||
|
||||
info_margin = 36
|
||||
info_margin = 8
|
||||
axis_length = 10.0
|
||||
|
||||
import math as _math_cam
|
||||
|
||||
_cx = float(config.get("camera_center_x", 0.0))
|
||||
_cy = float(config.get("camera_center_y", 0.0))
|
||||
_cz = float(config.get("camera_center_z", 0.0))
|
||||
|
||||
# 若 input.txt 指定了摄像机自身坐标,则由坐标反推 distance/elevation/azimuth
|
||||
if config.get("camera_pos_x") is not None:
|
||||
_px = float(config.get("camera_pos_x"))
|
||||
_py = float(config.get("camera_pos_y"))
|
||||
_pz = float(config.get("camera_pos_z"))
|
||||
_dx, _dy, _dz = _px - _cx, _py - _cy, _pz - _cz
|
||||
_dist = _math_cam.sqrt(_dx*_dx + _dy*_dy + _dz*_dz) or 1.0
|
||||
_elev = _math_cam.degrees(_math_cam.asin(max(-1.0, min(1.0, _dy / _dist))))
|
||||
_azim = _math_cam.degrees(_math_cam.atan2(_dx, _dz))
|
||||
else:
|
||||
_dist = float(config.get("camera_distance", 40.0))
|
||||
_elev = float(config.get("camera_elevation", 0))
|
||||
_azim = float(config.get("camera_azimuth", 0))
|
||||
|
||||
initial_camera = {
|
||||
"distance": 40.0,
|
||||
"elevation": 0,
|
||||
"azimuth": 0,
|
||||
"center": (0, 0, 0),
|
||||
"distance": _dist,
|
||||
"elevation": _elev,
|
||||
"azimuth": _azim,
|
||||
"center": (_cx, _cy, _cz),
|
||||
}
|
||||
|
||||
|
||||
@@ -167,11 +426,11 @@ axes_group.append(scene.visuals.Arrow(
|
||||
parent=view.scene,
|
||||
))
|
||||
|
||||
axes_group.append(scene.visuals.Text(text="x", color=(1.0, 0.2, 0.2, 1.0), font_size=18,
|
||||
axes_group.append(scene.visuals.Text(text="x", color=(1.0, 0.2, 0.2, 1.0), font_size=14,
|
||||
pos=(axis_length + 0.2, 0, 0), anchor_x="left", anchor_y="center", parent=view.scene))
|
||||
axes_group.append(scene.visuals.Text(text="y", color=(0.2, 1.0, 0.2, 1.0), font_size=18,
|
||||
axes_group.append(scene.visuals.Text(text="y", color=(0.2, 1.0, 0.2, 1.0), font_size=14,
|
||||
pos=(0, axis_length + 0.2, 0), anchor_x="left", anchor_y="bottom", parent=view.scene))
|
||||
axes_group.append(scene.visuals.Text(text="z", color=(0.3, 0.6, 1.0, 1.0), font_size=18,
|
||||
axes_group.append(scene.visuals.Text(text="z", color=(0.3, 0.6, 1.0, 1.0), font_size=14,
|
||||
pos=(0, 0, axis_length + 0.2), anchor_x="left", anchor_y="bottom", parent=view.scene))
|
||||
|
||||
# ── 原子渲染 ──────────────────────────────────
|
||||
@@ -190,11 +449,14 @@ TAB10_RGB = np.array([
|
||||
[0.7373, 0.7412, 0.1333], # 黄绿
|
||||
[0.0902, 0.7451, 0.8118], # 青
|
||||
])
|
||||
# 每个原子的颜色(循环使用 tab10 色板)
|
||||
# 每个原子的颜色(循环使用 tab10 色板,或按位移着色)
|
||||
atom_colors = np.zeros((N_ATOMS, 4), dtype=np.float32)
|
||||
for i in range(N_ATOMS):
|
||||
r, g, b = TAB10_RGB[i % len(TAB10_RGB)]
|
||||
atom_colors[i] = [r, g, b, 1.0]
|
||||
if FRAME_COLORS is not None:
|
||||
atom_colors[:] = FRAME_COLORS[0] # 初始帧颜色
|
||||
else:
|
||||
for i in range(N_ATOMS):
|
||||
r, g, b = TAB10_RGB[i % len(TAB10_RGB)]
|
||||
atom_colors[i] = [r, g, b, 1.0]
|
||||
|
||||
if USE_MARKER:
|
||||
# ── Marker 模式:GPU 实例化,一次 draw call ──
|
||||
@@ -250,12 +512,12 @@ for f_idx, (pos, direction) in enumerate(faces):
|
||||
|
||||
# 右上角:相机信息
|
||||
camera_info = scene.visuals.Text(
|
||||
text="", color="white", font_size=14,
|
||||
text="", color="white", font_size=12,
|
||||
pos=(0, 0), anchor_x="right", anchor_y="top", parent=canvas.scene)
|
||||
|
||||
# 左上角:小球信息
|
||||
ball_info = scene.visuals.Text(
|
||||
text="", color=(0.2, 1.0, 0.2, 1.0), font_size=28,
|
||||
text="", color=(0.2, 1.0, 0.2, 1.0), font_size=14,
|
||||
pos=(0, 0), anchor_x="left", anchor_y="top",
|
||||
face="黑体", bold=True, parent=canvas.scene)
|
||||
|
||||
@@ -267,7 +529,7 @@ reset_button = scene.visuals.Rectangle(
|
||||
radius=6, color=(0.18, 0.35, 0.65, 0.85),
|
||||
border_color="white", parent=canvas.scene)
|
||||
reset_button_label = scene.visuals.Text(
|
||||
text="reset", color="white", font_size=16,
|
||||
text="reset", color="white", font_size=13,
|
||||
pos=(reset_btn_size[0] / 2 + 8, reset_btn_size[1] / 2 + 8),
|
||||
anchor_x="center", anchor_y="center",
|
||||
bold=True, parent=canvas.scene)
|
||||
@@ -281,7 +543,7 @@ info_button = scene.visuals.Rectangle(
|
||||
radius=6, color=(0.9, 0.3, 0.3, 0.9),
|
||||
border_color="white", parent=canvas.scene)
|
||||
info_button_label = scene.visuals.Text(
|
||||
text="info", color="white", font_size=16,
|
||||
text="info", color="white", font_size=13,
|
||||
pos=(info_btn_size[0] / 2 + 8, info_btn_size[1] / 2 + 8),
|
||||
anchor_x="center", anchor_y="center",
|
||||
bold=True, parent=canvas.scene)
|
||||
@@ -301,7 +563,7 @@ axes_button = scene.visuals.Rectangle(
|
||||
radius=6, color=(0.3, 0.7, 0.3, 0.9),
|
||||
border_color="white", parent=canvas.scene)
|
||||
axes_button_label = scene.visuals.Text(
|
||||
text="axes", color="white", font_size=16,
|
||||
text="axes", color="white", font_size=13,
|
||||
pos=(axes_btn_size[0] / 2 + 8, axes_btn_size[1] / 2 + 8),
|
||||
anchor_x="center", anchor_y="center",
|
||||
bold=True, parent=canvas.scene)
|
||||
@@ -506,11 +768,15 @@ def handle_mouse_press(event):
|
||||
# ===========================================================================
|
||||
|
||||
def _update_atom_positions(f_idx):
|
||||
"""更新所有原子到第 f_idx 帧的位置。"""
|
||||
"""更新所有原子到第 f_idx 帧的位置,必要时更新颜色。"""
|
||||
if USE_MARKER:
|
||||
for i in range(N_ATOMS):
|
||||
marker_pos[i] = [DISP_ALL_X[f_idx, i], DISP_ALL_Y[f_idx, i], DISP_ALL_Z[f_idx, 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]
|
||||
if FRAME_COLORS is not None:
|
||||
balls.set_data(pos=marker_pos, face_color=FRAME_COLORS[f_idx])
|
||||
else:
|
||||
balls.set_data(pos=marker_pos)
|
||||
else:
|
||||
for i in range(N_ATOMS):
|
||||
balls[i].transform = STTransform(translate=(
|
||||
@@ -549,9 +815,80 @@ print(f"[draw] 渲染方式: {mode_str}")
|
||||
print(f"[draw] 绘图参数: ball_radius={ball_radius}, box_color=({box_color_r:.2f},{box_color_g:.2f},{box_color_b:.2f}), alpha={alpha_list}")
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 每帧回调:仅推进帧索引,从预存数组读取位置,零物理计算
|
||||
# ===========================================================================
|
||||
# 运动相机(速度段驱动):优先读取 move_camera.txt,其次用 display.txt header 缓存
|
||||
import re as _re
|
||||
|
||||
def _load_move_camera_txt():
|
||||
"""直接读取 input/move_camera.txt(与 output 同级的 input 目录)。"""
|
||||
input_dir = os.path.join(os.path.dirname(output_dir), "input")
|
||||
cam_path = os.path.join(input_dir, "move_camera.txt")
|
||||
if not os.path.exists(cam_path):
|
||||
return None
|
||||
segs = []
|
||||
with open(cam_path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
# 解析帧范围:支持 "all"(全程)或 "N-M"(区间)
|
||||
if line.lower().startswith("all") or _re.match(r'^\s*all\s', line, _re.IGNORECASE):
|
||||
start, end = 0, 10**9 # 用极大值表示全程
|
||||
else:
|
||||
m = _re.match(r'(\d+)\s*-\s*(\d+)', line)
|
||||
if not m:
|
||||
continue
|
||||
start, end = int(m.group(1)), int(m.group(2))
|
||||
v, r = [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]
|
||||
for i, axis in enumerate(['x', 'y', 'z']):
|
||||
m2 = _re.search(r'v' + axis + r'\s*=\s*([-\d.]+)', line)
|
||||
if m2: v[i] = float(m2.group(1))
|
||||
m2 = _re.search(r'r' + axis + r'\s*=\s*([-\d.]+)', line)
|
||||
if m2: r[i] = float(m2.group(1))
|
||||
if any(v) or any(r):
|
||||
segs.append({"start": start, "end": end, "v": v, "r": r})
|
||||
return segs if segs else None
|
||||
|
||||
# 先试 move_camera.txt 直读,没有则用 display.txt 缓存
|
||||
# header 中 camera_keyframes 为空字符串表示 move_camera=0(开关关闭),跳过文件加载
|
||||
_camera_motion_enabled = bool(config.get("camera_keyframes", ""))
|
||||
_CAM_MOTION = _load_move_camera_txt() if _camera_motion_enabled else None
|
||||
if not _CAM_MOTION:
|
||||
_CAM_MOTION = json.loads(config.get("camera_keyframes", "null")) if config.get("camera_keyframes") else None
|
||||
if _CAM_MOTION:
|
||||
_cam_center = [0.0, 0.0, 0.0]
|
||||
_cam_elev = initial_camera["elevation"]
|
||||
_cam_azim = initial_camera["azimuth"]
|
||||
_cam_dist = initial_camera["distance"]
|
||||
src = "move_camera.txt" if _load_move_camera_txt() else "display.txt header"
|
||||
print(f"[draw] 运动相机已启用(数据来源: {src},{len(_CAM_MOTION)} 段)")
|
||||
|
||||
|
||||
def _update_motion_camera(f_idx):
|
||||
"""速度段驱动:每帧累加平移/旋转。
|
||||
|
||||
时间交叠时所有段同时生效,按文件中出现的顺序依次作用。
|
||||
只有当前帧存在活动段时才覆写相机,否则保留用户键盘/鼠标操作的结果。
|
||||
"""
|
||||
if not _CAM_MOTION:
|
||||
return
|
||||
global _cam_center, _cam_elev, _cam_azim, _cam_dist
|
||||
active = False
|
||||
for seg in _CAM_MOTION:
|
||||
if seg["start"] <= f_idx < seg["end"]:
|
||||
_cam_center[0] += seg["v"][0]
|
||||
_cam_center[1] += seg["v"][1]
|
||||
_cam_center[2] += seg["v"][2]
|
||||
_cam_elev += seg["r"][0]
|
||||
_cam_azim += seg["r"][1]
|
||||
active = True
|
||||
|
||||
if active:
|
||||
view.camera.center = tuple(_cam_center)
|
||||
view.camera.distance = _cam_dist
|
||||
view.camera.elevation = _cam_elev
|
||||
view.camera.azimuth = _cam_azim
|
||||
|
||||
|
||||
def update(event):
|
||||
global frame_idx
|
||||
frame_idx = (frame_idx + 1) % N_FRAMES # 循环播放
|
||||
@@ -563,6 +900,9 @@ def update(event):
|
||||
if bond_lines is not None and len(BOND_PAIRS) > 0:
|
||||
_update_bond_positions(frame_idx)
|
||||
|
||||
# 运动相机:速度段驱动
|
||||
_update_motion_camera(frame_idx)
|
||||
|
||||
# 信息面板显示 plot_atom 的数据
|
||||
x = float(DISP_X[frame_idx])
|
||||
y = float(DISP_Y[frame_idx])
|
||||
|
||||
+167
-273
@@ -14,6 +14,7 @@ import sys
|
||||
import subprocess
|
||||
import time
|
||||
import argparse
|
||||
import json
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
|
||||
@@ -25,6 +26,57 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import compute
|
||||
|
||||
|
||||
def _fmt_alpha(v):
|
||||
"""将 alpha 值格式化为逗号分隔字符串,兼容 numpy 数组/list/标量。"""
|
||||
if isinstance(v, (list, tuple, np.ndarray)):
|
||||
return ",".join(str(float(x)) for x in 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
|
||||
if not int(config.get("move_camera", 0)):
|
||||
return ""
|
||||
cam_file = str(config.get("move_camera_file",
|
||||
os.path.join("input", "move_camera.txt")))
|
||||
cam_path = cam_file
|
||||
if not os.path.isabs(cam_file):
|
||||
cam_path = os.path.join(runtime_base, cam_file)
|
||||
if not os.path.exists(cam_path):
|
||||
return ""
|
||||
segments = []
|
||||
with open(cam_path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
# 解析帧范围:支持 "all"(全程)或 "N-M"(区间)
|
||||
if line.lower().startswith("all") or re.match(r'^\s*all\s', line, re.IGNORECASE):
|
||||
start, end = 0, 10**9
|
||||
else:
|
||||
m = re.match(r'(\d+)\s*-\s*(\d+)', line)
|
||||
if not m:
|
||||
continue
|
||||
start, end = int(m.group(1)), int(m.group(2))
|
||||
v, r = [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]
|
||||
for i, axis in enumerate(['x', 'y', 'z']):
|
||||
m2 = re.search(r'v' + axis + r'\s*=\s*([-\d.]+)', line)
|
||||
if m2: v[i] = float(m2.group(1))
|
||||
m2 = re.search(r'r' + axis + r'\s*=\s*([-\d.]+)', line)
|
||||
if m2: r[i] = float(m2.group(1))
|
||||
if any(v) or any(r):
|
||||
segments.append({"start": start, "end": end, "v": v, "r": r})
|
||||
return json.dumps(segments) if segments else ""
|
||||
|
||||
|
||||
def read_optional_index(data, key, default_value):
|
||||
"""Read an optional integer index from structured txt metadata."""
|
||||
if key not in data:
|
||||
@@ -94,16 +146,6 @@ def build_sample_indices(total_steps, sample_step, sample_start, sample_end):
|
||||
return indices
|
||||
|
||||
|
||||
def save_display_txt(data, out_dir=None):
|
||||
"""将抽帧数据保存到 output/display.txt(含所有参数元数据)。"""
|
||||
if out_dir is None:
|
||||
out_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
disp_path = os.path.join(compute.get_output_dir(out_dir), "display.txt")
|
||||
compute.save_text_data(disp_path, data)
|
||||
print(f"[sample] 显示数组已保存至: {disp_path}")
|
||||
return disp_path
|
||||
|
||||
|
||||
def run_case(config_path, runtime_base, input_dir="input", output_dir="output", no_plot=False):
|
||||
"""Run one case with explicit program path, input path, and output path."""
|
||||
runtime_base = Path(runtime_base).resolve()
|
||||
@@ -145,12 +187,11 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
disp_path = os.path.join(output_dir_abs, "display.txt")
|
||||
|
||||
# ── 自动缓存检测 ───────────────────────────────────────
|
||||
# force_calc=1: 强制重新计算,忽略缓存
|
||||
# force_calc=0: 尊重 step_simulate 设置,不自动覆盖
|
||||
# force_calc=1: 强制重新计算,忽略缓存(仅在 step_simulate=1 时生效)
|
||||
# force_calc=0: 尊重 step_simulate 设置
|
||||
force_calc = int(config.get("force_calc", 0))
|
||||
if force_calc:
|
||||
if force_calc and config.get("step_simulate", 1):
|
||||
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 → 按用户要求执行计算
|
||||
@@ -183,8 +224,12 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
print(f"[run] 没有可用的缓存输出,但 step_simulate=0,将跳过模拟")
|
||||
|
||||
# 2. 运行物理模拟 → output/trajectory.txt
|
||||
_engine_aliases = {"c++": "cpp", "f90": "fortran", "f": "fortran"}
|
||||
engine = _engine_aliases.get(
|
||||
str(config.get("engine", "python")).lower(),
|
||||
str(config.get("engine", "python")).lower()
|
||||
)
|
||||
if config.get("step_simulate", 1):
|
||||
engine = config.get("engine", "python")
|
||||
total_steps = config["NT"]
|
||||
record_steps = total_steps - (config.get("warmup_steps") or 0)
|
||||
print(f"[run] 开始计算 总步数={total_steps} 记录步数={record_steps} DT={config['DT']}")
|
||||
@@ -193,273 +238,110 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
_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))
|
||||
compute.save_trajectory_txt(traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz, str(runtime_base))
|
||||
compute.run_from_config(config, str(runtime_base))
|
||||
else:
|
||||
# 外部引擎:先加载配置到全局变量,再运行引擎,再用 save_trajectory_txt 补全 metadata
|
||||
# 外部引擎:先加载配置到全局变量,再运行引擎
|
||||
config["_skip_run"] = True
|
||||
compute.run_from_config(config, str(runtime_base))
|
||||
config.pop("_skip_run", None)
|
||||
input_dir_abs = str(input_dir_path.resolve())
|
||||
output_dir_abs = str(output_dir_path.resolve())
|
||||
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))
|
||||
|
||||
# ── DLL 路径(无文件 I/O,直接输出 display.npz)──
|
||||
from engines.engine_dll import is_dll_available
|
||||
if not is_dll_available(engine):
|
||||
raise FileNotFoundError(
|
||||
f"DLL 未找到(引擎 {engine})。"
|
||||
f"请先编译:cd engines/{engine} && make dll")
|
||||
compute.run_engine_dll(engine, output_dir_abs, config)
|
||||
|
||||
_elapsed = _time.time() - _t0
|
||||
print(f"[run] 引擎: {engine} 计算完成: {record_steps} 步 {_elapsed:.3f} s")
|
||||
else:
|
||||
print("[run] 步骤 [模拟] 已跳过,直接加载已有轨迹")
|
||||
print("[run] 步骤 [模拟] 已跳过")
|
||||
|
||||
# 3. 检查/生成 display.txt
|
||||
disp_path_new = os.path.join(output_dir_abs, "display.txt")
|
||||
save_traj = int(config.get("save_trajectory", 0))
|
||||
|
||||
if os.path.exists(disp_path_new):
|
||||
# Python 引擎或新版外部引擎(save_trajectory=0)已直接写入
|
||||
print(f"[run] 发现已有 display.txt(引擎直接抽帧)")
|
||||
elif engine != "python" and os.path.exists(os.path.join(output_dir_abs, "trajectory.txt")):
|
||||
# 旧版外部引擎:从 trajectory.txt 抽帧
|
||||
traj_path = os.path.join(output_dir_abs, "trajectory.txt")
|
||||
if not os.path.exists(traj_path):
|
||||
print(f"[run] 错误: trajectory.txt 不存在,无法跳过模拟")
|
||||
print(f"[run] 错误: 找不到 trajectory.txt 或 display.txt")
|
||||
sys.exit(1)
|
||||
data = compute.load_text_data(traj_path)
|
||||
NT = int(data["NT"]); DT = float(data["DT"]); NSTEP = int(data.get("NSTEP", 1))
|
||||
record_steps = NT - int(data.get("warmup_steps", 0))
|
||||
n_atoms = len(data["atom_ids"])
|
||||
sample_start = 0
|
||||
sample_end = NT
|
||||
indices = np.arange(0, record_steps, NSTEP, dtype=np.int64)
|
||||
if len(indices) == 0:
|
||||
indices = np.array([0])
|
||||
|
||||
traj_x = data["traj_x"]; traj_y = data["traj_y"]; traj_z = data["traj_z"]
|
||||
traj_vx = data["traj_vx"]; traj_vy = data["traj_vy"]; traj_vz = data["traj_vz"]
|
||||
|
||||
# 构建 header_fields
|
||||
hf = {"DT": str(DT), "NSTEP": str(NSTEP), "method": str(data.get("method", "")),
|
||||
"warmup_steps": str(data.get("warmup_steps", 0)),
|
||||
"dynamic_steps": str(record_steps),
|
||||
"T_total": str(NT * DT),
|
||||
"X_MAX": str(data.get("X_MAX", 10)), "X_MIN": str(data.get("X_MIN", -10)),
|
||||
"Y_MAX": str(data.get("Y_MAX", 10)), "Y_MIN": str(data.get("Y_MIN", -10)),
|
||||
"Z_MAX": str(data.get("Z_MAX", 10)), "Z_MIN": str(data.get("Z_MIN", -10)),
|
||||
"ball_radius": str(data.get("ball_radius", 0.5)),
|
||||
"ball_color_r": str(data.get("ball_color_r", 0.9)),
|
||||
"ball_color_g": str(data.get("ball_color_g", 0.2)),
|
||||
"ball_color_b": str(data.get("ball_color_b", 0.2)),
|
||||
"box_color_r": str(data.get("box_color_r", 0.8)),
|
||||
"box_color_g": str(data.get("box_color_g", 0.8)),
|
||||
"box_color_b": str(data.get("box_color_b", 0.85)),
|
||||
"gravity_field": str(data.get("gravity_field", 1)),
|
||||
"gravity_interaction": str(data.get("gravity_interaction", 0)),
|
||||
"elastic_force": str(data.get("elastic_force", 1)),
|
||||
"damping_force": str(data.get("damping_force", 0)),
|
||||
"gravity_strength": str(data.get("gravity_strength", 1.0)),
|
||||
"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)),
|
||||
"camera_azimuth": str(config.get("camera_azimuth", 0)),
|
||||
"camera_keyframes": _load_camera_kf(config, str(runtime_base))}
|
||||
|
||||
n_frames = len(indices)
|
||||
compute.save_display_txt(
|
||||
disp_path_new,
|
||||
traj_x[indices], traj_y[indices], traj_z[indices],
|
||||
traj_vx[indices], traj_vy[indices], traj_vz[indices],
|
||||
np.array(data["atom_ids"]), n_frames, n_atoms,
|
||||
header_fields=hf)
|
||||
print(f"[run] 从 trajectory.txt 抽帧生成 display.txt ({n_frames} 帧)")
|
||||
|
||||
# 3. 抽帧 → output/display.txt
|
||||
traj_path = os.path.join(output_dir_abs, "trajectory.txt")
|
||||
data = compute.load_text_data(traj_path)
|
||||
|
||||
NT = int(data["NT"]); DT = float(data["DT"]); NSTEP = int(data["NSTEP"])
|
||||
warmup_steps = int(data.get("warmup_steps", 0))
|
||||
plot_atom_row = int(data["plot_atom_row"]) if "plot_atom_row" in data else 0
|
||||
plot_atom_id = int(data["plot_atom_id"]) if "plot_atom_id" in data else int(data["atom_ids"][plot_atom_row])
|
||||
|
||||
# 抽帧范围控制
|
||||
sample_start = read_optional_index(data, "sample_start", 0)
|
||||
sample_end = read_optional_index(data, "sample_end", NT)
|
||||
|
||||
indices = build_sample_indices(NT, NSTEP, sample_start, sample_end)
|
||||
n_frames = len(indices)
|
||||
|
||||
print(f"[run] 抽帧范围: [{sample_start}, {sample_end}), 共 {n_frames} 帧")
|
||||
|
||||
traj_x = data["traj_x"]
|
||||
traj_y = data["traj_y"]
|
||||
traj_z = data["traj_z"]
|
||||
traj_vx = data["traj_vx"]
|
||||
traj_vy = data["traj_vy"]
|
||||
traj_vz = data["traj_vz"]
|
||||
|
||||
if traj_x.ndim == 1:
|
||||
selected_x = traj_x
|
||||
selected_y = traj_y
|
||||
selected_z = traj_z
|
||||
selected_vx = traj_vx
|
||||
selected_vy = traj_vy
|
||||
selected_vz = traj_vz
|
||||
all_x = traj_x[:, None]
|
||||
all_y = traj_y[:, None]
|
||||
all_z = traj_z[:, None]
|
||||
all_vx = traj_vx[:, None]
|
||||
all_vy = traj_vy[:, None]
|
||||
all_vz = traj_vz[:, None]
|
||||
else:
|
||||
selected_x = traj_x[:, plot_atom_row]
|
||||
selected_y = traj_y[:, plot_atom_row]
|
||||
selected_z = traj_z[:, plot_atom_row]
|
||||
selected_vx = traj_vx[:, plot_atom_row]
|
||||
selected_vy = traj_vy[:, plot_atom_row]
|
||||
selected_vz = traj_vz[:, plot_atom_row]
|
||||
all_x = traj_x
|
||||
all_y = traj_y
|
||||
all_z = traj_z
|
||||
all_vx = traj_vx
|
||||
all_vy = traj_vy
|
||||
all_vz = traj_vz
|
||||
|
||||
if config.get("step_sample", 1):
|
||||
disp_data = {
|
||||
"disp_x": selected_x[indices],
|
||||
"disp_y": selected_y[indices],
|
||||
"disp_z": selected_z[indices],
|
||||
"disp_vx": selected_vx[indices],
|
||||
"disp_vy": selected_vy[indices],
|
||||
"disp_vz": selected_vz[indices],
|
||||
"disp_all_x": all_x[indices],
|
||||
"disp_all_y": all_y[indices],
|
||||
"disp_all_z": all_z[indices],
|
||||
"disp_all_vx": all_vx[indices],
|
||||
"disp_all_vy": all_vy[indices],
|
||||
"disp_all_vz": all_vz[indices],
|
||||
"disp_t": indices * DT,
|
||||
"disp_step": indices,
|
||||
"n_frames": n_frames,
|
||||
"NT": NT, "DT": DT, "NSTEP": NSTEP,
|
||||
"plot_atom_id": plot_atom_id,
|
||||
"plot_atom_row": plot_atom_row,
|
||||
"method": str(data["method"]) if "method" in data else "explicit_euler",
|
||||
"coord_file": str(data["coord_file"]) if "coord_file" in data else os.path.join("input", "coord.txt"),
|
||||
"atom_ids": data["atom_ids"] if "atom_ids" in data else np.array([1]),
|
||||
"atom_masses": data["atom_masses"] if "atom_masses" in data else np.array([float(data["M"])]),
|
||||
"atom_radii": data["atom_radii"] if "atom_radii" in data else np.array([float(data["ball_radius"])]),
|
||||
"atom_positions": data["atom_positions"] if "atom_positions" in data else np.array([[float(data["X0"]), float(data["Y0"]), float(data["Z0"])]]),
|
||||
"atom_velocities": data["atom_velocities"] if "atom_velocities" in data else np.array([[float(data["VX0"]), float(data["VY0"]), float(data["VZ0"])]]),
|
||||
"atom_fixed": data["atom_fixed"] if "atom_fixed" in data else np.array([[0, 0, 0]]),
|
||||
"bond_pairs": data.get("bond_pairs", np.zeros((0, 2), dtype=np.int64)).tolist(),
|
||||
"bond_stiffness": data.get("bond_stiffness", np.zeros(0, dtype=np.float64)).tolist(),
|
||||
"bond_rest_lengths": data.get("bond_rest_lengths", np.zeros(0, dtype=np.float64)).tolist(),
|
||||
"warmup_steps": warmup_steps,
|
||||
"sample_start": sample_start,
|
||||
"sample_end": sample_end,
|
||||
"X_MIN": float(data["X_MIN"]), "X_MAX": float(data["X_MAX"]),
|
||||
"Y_MIN": float(data["Y_MIN"]), "Y_MAX": float(data["Y_MAX"]),
|
||||
"Z_MIN": float(data["Z_MIN"]), "Z_MAX": float(data["Z_MAX"]),
|
||||
"X0": float(data["X0"]), "Y0": float(data["Y0"]), "Z0": float(data["Z0"]),
|
||||
"VX0": float(data["VX0"]), "VY0": float(data["VY0"]), "VZ0": float(data["VZ0"]),
|
||||
"M": float(data["M"]) if "M" in data else 1.0,
|
||||
"alpha": data["alpha"],
|
||||
"ball_radius": float(data["ball_radius"]),
|
||||
"ball_color_r": float(data["ball_color_r"]),
|
||||
"ball_color_g": float(data["ball_color_g"]),
|
||||
"ball_color_b": float(data["ball_color_b"]),
|
||||
"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)),
|
||||
"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} 帧")
|
||||
else:
|
||||
print("[run] 步骤 [抽帧] 已跳过")
|
||||
# save_trajectory=0 时清理 trajectory.txt
|
||||
if not save_traj:
|
||||
try:
|
||||
os.remove(traj_path)
|
||||
print(f"[run] save_trajectory=0,已删除 {traj_path}")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# 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}")
|
||||
|
||||
@@ -469,11 +351,13 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
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")
|
||||
# 检查 display.npz 或 display.txt 是否存在
|
||||
disp_npz = os.path.join(output_dir_abs, "display.npz")
|
||||
disp_txt = os.path.join(output_dir_abs, "display.txt")
|
||||
disp_path = disp_npz if os.path.exists(disp_npz) else disp_txt
|
||||
if not os.path.exists(disp_path):
|
||||
print(f"[run] 错误: 找不到 {disp_path}")
|
||||
print(f"[run] 启动动画需要先运行抽帧(step_sample: 1),或手动保留 output/display.txt")
|
||||
print(f"[run] 错误: 找不到 display.npz 或 display.txt")
|
||||
print(f"[run] 启动动画需要先运行模拟(step_simulate: 1)")
|
||||
else:
|
||||
try:
|
||||
print("[run] 正在启动 VisPy 3D 动画窗口…")
|
||||
@@ -505,14 +389,24 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
# 6. 波形能量动画(可选)
|
||||
if config.get("step_plot_wave", 0):
|
||||
try:
|
||||
_save_gif = int(config.get("plot_wave_save_gif", 0))
|
||||
_save_mp4 = int(config.get("plot_wave_save_mp4", 0))
|
||||
_to_file = bool(_save_gif or _save_mp4)
|
||||
if _to_file:
|
||||
import matplotlib
|
||||
matplotlib.use("Agg") # 保存文件时用非交互式后端
|
||||
import plot_wave as pw
|
||||
print("[run] 正在绘制波形与能量图…")
|
||||
pw.plot_wave(
|
||||
gif = pw.plot_wave(
|
||||
str(output_dir_abs),
|
||||
save_gif=int(config.get("plot_wave_save_gif", 0)),
|
||||
save_mp4=int(config.get("plot_wave_save_mp4", 0)),
|
||||
save_gif=_save_gif,
|
||||
save_mp4=_save_mp4,
|
||||
show=not _to_file, # 不保存文件时弹出交互窗口
|
||||
)
|
||||
if gif:
|
||||
print(f"[run] 波形 GIF 已保存: {gif}")
|
||||
except Exception as e:
|
||||
import traceback; traceback.print_exc()
|
||||
print(f"[run] 绘制波形图失败: {e}")
|
||||
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
# engines/c/Makefile
|
||||
# 跨平台编译:make → 本地系统编译
|
||||
# make linux → Linux 交叉编译(需 x86_64-linux-gnu-gcc)
|
||||
# make windows → Windows 交叉编译(需 x86_64-w64-mingw32-gcc)
|
||||
# make macos → macOS 交叉编译(需 osxcross 工具链)
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -O3 -march=native -Wall -Wextra
|
||||
LDFLAGS = -lm
|
||||
SRCS = main.c
|
||||
|
||||
# 自动检测系统
|
||||
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
|
||||
|
||||
# 目标文件名:统一使用 .exe 后缀(方便 Python 跨平台调用)
|
||||
TARGET = build/dynamics_c.exe
|
||||
|
||||
# ── 本地编译 ─────────────────────────────────
|
||||
.PHONY: all clean linux windows macos
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(SRCS) | build
|
||||
$(CC) $(CFLAGS) -o $@ $(SRCS) $(LDFLAGS)
|
||||
@echo " === C engine built: $@ ==="
|
||||
|
||||
build:
|
||||
mkdir -p build
|
||||
|
||||
# ── 交叉编译 ─────────────────────────────────
|
||||
# Linux → Linux (x86_64)
|
||||
linux: CROSS_PREFIX = x86_64-linux-gnu-
|
||||
linux: CC = $(CROSS_PREFIX)gcc
|
||||
linux: CFLAGS = -O3 -march=x86-64 -Wall -Wextra
|
||||
linux: $(SRCS) | build
|
||||
$(CC) $(CFLAGS) -o build/dynamics_c_linux.exe $(SRCS) $(LDFLAGS)
|
||||
@echo " === Linux binary: build/dynamics_c_linux.exe ==="
|
||||
|
||||
# 任意平台 → Windows (x86_64)
|
||||
# 需要安装 MinGW 交叉编译器:
|
||||
# apt install mingw-w64 (Debian/Ubuntu)
|
||||
# brew install mingw-w64 (macOS)
|
||||
windows: CROSS_PREFIX = x86_64-w64-mingw32-
|
||||
windows: CC = $(CROSS_PREFIX)gcc
|
||||
windows: CFLAGS = -O3 -march=x86-64 -Wall -Wextra
|
||||
windows: $(SRCS) | build
|
||||
$(CC) $(CFLAGS) -o build/dynamics_c_win.exe $(SRCS) $(LDFLAGS)
|
||||
@echo " === Windows binary: build/dynamics_c_win.exe ==="
|
||||
|
||||
# 任意平台 → macOS (x86_64)
|
||||
# 需要安装 osxcross 工具链
|
||||
macos: CROSS_PREFIX = x86_64-apple-darwin-
|
||||
macos: CC = $(CROSS_PREFIX)gcc
|
||||
macos: CFLAGS = -O3 -march=x86-64 -Wall -Wextra
|
||||
macos: $(SRCS) | build
|
||||
$(CC) $(CFLAGS) -o build/dynamics_c_mac.exe $(SRCS) $(LDFLAGS)
|
||||
@echo " === macOS binary: build/dynamics_c_mac.exe ==="
|
||||
|
||||
# ── 编译所有平台 ──────────────────────────────
|
||||
all-platforms: linux windows macos
|
||||
|
||||
clean:
|
||||
rm -rf build *.o
|
||||
@@ -1,948 +0,0 @@
|
||||
/**
|
||||
* engines/c/main.c
|
||||
* -----------------
|
||||
* C 语言动力学模拟引擎。
|
||||
* 与 Python 版 (compute.py) 算法保持一致。
|
||||
*
|
||||
* 输入: param.json 数值参数(Python 从 YAML 转换得来)
|
||||
* <input_dir>/coord.txt
|
||||
* <input_dir>/connection.txt
|
||||
* <input_dir>/bond.txt
|
||||
* 输出: <output_dir>/trajectory.txt (JSON 格式,与 Python 版兼容)
|
||||
*
|
||||
* 编译: cmake --build build --target dynamics_c
|
||||
* 用法: ./build/dynamics_c <input_dir> <output_dir> <param_json_path>
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
|
||||
/* ========================================================================
|
||||
* 配置参数(从 param.json 读取)
|
||||
* ======================================================================== */
|
||||
typedef struct {
|
||||
double box_a; /* 盒子半边长 */
|
||||
int NT; /* 总步数 */
|
||||
double DT; /* 时间步长 */
|
||||
int NSTEP; /* 抽帧间隔 */
|
||||
int warmup_steps; /* 预热步数 */
|
||||
char method[32]; /* 算法名称 */
|
||||
double G[3]; /* 重力分量 */
|
||||
double B[3]; /* 阻尼分量 */
|
||||
int gravity_field; /* 均匀重力场开关 */
|
||||
int gravity_interaction; /* 原子间万有引力开关 */
|
||||
int elastic_force; /* 弹簧键力开关 */
|
||||
int damping_force; /* 阻尼开关 */
|
||||
double gravity_strength; /* 万有引力强度 */
|
||||
int driving_force; /* 驱动力开关 */
|
||||
} SimParams;
|
||||
|
||||
/* ========================================================================
|
||||
* 原子数据
|
||||
* ======================================================================== */
|
||||
typedef struct {
|
||||
int n_atoms;
|
||||
int *atom_ids;
|
||||
double *masses;
|
||||
double *radii;
|
||||
double *pos_0; /* 初始位置 (n_atoms*3) */
|
||||
double *vel_0; /* 初始速度 (n_atoms*3) */
|
||||
int *fixed; /* 固定约束 (n_atoms*3) */
|
||||
} AtomData;
|
||||
|
||||
/* ========================================================================
|
||||
* 成键数据
|
||||
* ======================================================================== */
|
||||
typedef struct {
|
||||
int n_bonds;
|
||||
int *pairs; /* (n_bonds*2) */
|
||||
double *stiffness;
|
||||
double *rest_lengths;
|
||||
} BondData;
|
||||
|
||||
/* 前向声明 */
|
||||
static void *xmalloc(size_t sz);
|
||||
|
||||
/* ========================================================================
|
||||
* 驱动力数据
|
||||
* ======================================================================== */
|
||||
typedef struct {
|
||||
int n_drivers;
|
||||
int *atom_idx;
|
||||
double *amp_x, *amp_y, *amp_z;
|
||||
double *freq_x, *freq_y, *freq_z;
|
||||
double *phi_x, *phi_y, *phi_z; /* radians */
|
||||
int *has_period; /* 0=all, 1=limited cycles */
|
||||
double *period_cycles; /* number of cycles */
|
||||
double *freeze_x, *freeze_y, *freeze_z;
|
||||
} DriverData;
|
||||
|
||||
/* 读取 driver.txt */
|
||||
static DriverData read_driver(const char *input_dir, const AtomData *atoms) {
|
||||
DriverData d;
|
||||
memset(&d, 0, sizeof(d));
|
||||
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "%s/driver.txt", input_dir);
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) return d;
|
||||
|
||||
char line[1024];
|
||||
if (!fgets(line, sizeof(line), f)) { fclose(f); return d; }
|
||||
|
||||
/* 第一遍:统计行数 */
|
||||
int n_lines = 0;
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
char trimmed[1024];
|
||||
int j = 0;
|
||||
for (int i = 0; line[i]; i++) {
|
||||
if (line[i] != ' ' && line[i] != '\t' && line[i] != '\n' && line[i] != '\r')
|
||||
trimmed[j++] = line[i];
|
||||
}
|
||||
trimmed[j] = '\0';
|
||||
if (strlen(trimmed) > 0 && trimmed[0] != '#') n_lines++;
|
||||
}
|
||||
|
||||
if (n_lines == 0) { fclose(f); return d; }
|
||||
|
||||
/* 分配内存 */
|
||||
d.n_drivers = n_lines;
|
||||
d.atom_idx = (int*)xmalloc(n_lines * sizeof(int));
|
||||
d.amp_x = (double*)xmalloc(n_lines * sizeof(double));
|
||||
d.amp_y = (double*)xmalloc(n_lines * sizeof(double));
|
||||
d.amp_z = (double*)xmalloc(n_lines * sizeof(double));
|
||||
d.freq_x = (double*)xmalloc(n_lines * sizeof(double));
|
||||
d.freq_y = (double*)xmalloc(n_lines * sizeof(double));
|
||||
d.freq_z = (double*)xmalloc(n_lines * sizeof(double));
|
||||
d.phi_x = (double*)xmalloc(n_lines * sizeof(double));
|
||||
d.phi_y = (double*)xmalloc(n_lines * sizeof(double));
|
||||
d.phi_z = (double*)xmalloc(n_lines * sizeof(double));
|
||||
d.has_period = (int*)xmalloc(n_lines * sizeof(int));
|
||||
d.period_cycles = (double*)xmalloc(n_lines * sizeof(double));
|
||||
d.freeze_x = (double*)xmalloc(n_lines * sizeof(double));
|
||||
d.freeze_y = (double*)xmalloc(n_lines * sizeof(double));
|
||||
d.freeze_z = (double*)xmalloc(n_lines * sizeof(double));
|
||||
|
||||
/* 初始化 freeze 数组 */
|
||||
for (int i = 0; i < n_lines; i++) {
|
||||
d.freeze_x[i] = d.freeze_y[i] = d.freeze_z[i] = 0.0;
|
||||
}
|
||||
|
||||
/* 第二遍:解析 */
|
||||
rewind(f);
|
||||
fgets(line, sizeof(line), f); /* 跳过表头 */
|
||||
|
||||
int idx = 0;
|
||||
while (idx < n_lines && fgets(line, sizeof(line), f)) {
|
||||
char trimmed[1024];
|
||||
int j = 0;
|
||||
for (int i = 0; line[i]; i++) {
|
||||
if (line[i] != ' ' && line[i] != '\t' && line[i] != '\n' && line[i] != '\r')
|
||||
trimmed[j++] = line[i];
|
||||
}
|
||||
trimmed[j] = '\0';
|
||||
if (strlen(trimmed) == 0 || trimmed[0] == '#') continue;
|
||||
|
||||
int atom_id;
|
||||
double amp_x, amp_y, amp_z;
|
||||
double freq_x, freq_y, freq_z;
|
||||
double phi_x, phi_y, phi_z;
|
||||
char period_str[256] = {0};
|
||||
|
||||
int n_parsed = sscanf(line,
|
||||
"%d %lf %lf %lf %lf %lf %lf %lf %lf %lf %255s",
|
||||
&atom_id,
|
||||
&_x, &_y, &_z,
|
||||
&freq_x, &freq_y, &freq_z,
|
||||
&phi_x, &phi_y, &phi_z,
|
||||
period_str);
|
||||
|
||||
if (n_parsed < 11) continue;
|
||||
|
||||
/* 通过原子 ID 匹配内部索引(线性搜索)*/
|
||||
int ii = -1;
|
||||
for (int k = 0; k < atoms->n_atoms; k++) {
|
||||
if (atoms->atom_ids[k] == atom_id) { ii = k; break; }
|
||||
}
|
||||
if (ii < 0) continue;
|
||||
|
||||
d.atom_idx[idx] = ii;
|
||||
d.amp_x[idx] = amp_x;
|
||||
d.amp_y[idx] = amp_y;
|
||||
d.amp_z[idx] = amp_z;
|
||||
d.freq_x[idx] = freq_x;
|
||||
d.freq_y[idx] = freq_y;
|
||||
d.freq_z[idx] = freq_z;
|
||||
/* 角度 → 弧度 */
|
||||
d.phi_x[idx] = phi_x * M_PI / 180.0;
|
||||
d.phi_y[idx] = phi_y * M_PI / 180.0;
|
||||
d.phi_z[idx] = phi_z * M_PI / 180.0;
|
||||
|
||||
if (strcmp(period_str, "all") == 0 || strcmp(period_str, "-1") == 0) {
|
||||
d.has_period[idx] = 0;
|
||||
d.period_cycles[idx] = -1.0;
|
||||
} else {
|
||||
d.has_period[idx] = 1;
|
||||
d.period_cycles[idx] = strtod(period_str, NULL);
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
d.n_drivers = idx;
|
||||
|
||||
fclose(f);
|
||||
return d;
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* 轨迹缓冲区
|
||||
* ======================================================================== */
|
||||
typedef struct {
|
||||
int n_steps;
|
||||
int n_atoms;
|
||||
double *x, *y, *z;
|
||||
double *vx, *vy, *vz;
|
||||
} Trajectory;
|
||||
|
||||
/* ========================================================================
|
||||
* 辅助函数
|
||||
* ======================================================================== */
|
||||
|
||||
static void die(const char *msg) {
|
||||
fprintf(stderr, "[C-engine] 错误: %s\n", msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void *xmalloc(size_t sz) {
|
||||
void *p = malloc(sz);
|
||||
if (!p) die("内存分配失败");
|
||||
return p;
|
||||
}
|
||||
|
||||
/* 从 JSON 中读取 double 值 */
|
||||
static double json_read_double(const char *json, const char *key) {
|
||||
char search[256];
|
||||
snprintf(search, sizeof(search), "\"%s\"", key);
|
||||
const char *p = strstr(json, search);
|
||||
if (!p) return 0.0;
|
||||
p = strchr(p, ':');
|
||||
if (!p) return 0.0;
|
||||
p++;
|
||||
while (*p == ' ' || *p == '\t' || *p == '\n') p++;
|
||||
return strtod(p, NULL);
|
||||
}
|
||||
|
||||
static int json_read_int(const char *json, const char *key) {
|
||||
return (int)json_read_double(json, key);
|
||||
}
|
||||
|
||||
/* 从 JSON 中读取字符串值(写入 dst,最多 dst_sz 字节) */
|
||||
static void json_read_string(const char *json, const char *key, char *dst, int dst_sz) {
|
||||
char search[256];
|
||||
snprintf(search, sizeof(search), "\"%s\"", key);
|
||||
const char *p = strstr(json, search);
|
||||
if (!p) { dst[0] = '\0'; return; }
|
||||
p = strchr(p, ':');
|
||||
if (!p) { dst[0] = '\0'; return; }
|
||||
p++;
|
||||
while (*p == ' ' || *p == '\t' || *p == '\n') p++;
|
||||
if (*p != '"') { dst[0] = '\0'; return; }
|
||||
p++;
|
||||
int i = 0;
|
||||
while (*p && *p != '"' && i < dst_sz - 1) { dst[i++] = *p++; }
|
||||
dst[i] = '\0';
|
||||
}
|
||||
|
||||
/* 读取 JSON 数组 (如 "G": [0, 0, -9.8]) 到 double[3] */
|
||||
static void json_read_double3(const char *json, const char *key, double out[3]) {
|
||||
char search[256];
|
||||
snprintf(search, sizeof(search), "\"%s\"", key);
|
||||
const char *p = strstr(json, search);
|
||||
if (!p) { out[0]=out[1]=out[2]=0; return; }
|
||||
p = strchr(p, '[');
|
||||
if (!p) { out[0]=out[1]=out[2]=0; return; }
|
||||
p++;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == ',') p++;
|
||||
out[i] = strtod(p, (char**)&p);
|
||||
}
|
||||
}
|
||||
|
||||
/* 读取 param.json */
|
||||
static int g_gravity_field = 1;
|
||||
static int g_gravity_interaction = 0;
|
||||
static int g_elastic_force = 1;
|
||||
static int g_damping_force = 0;
|
||||
static double g_gravity_strength = 1.0;
|
||||
|
||||
static SimParams read_params(const char *path) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) die("无法打开 param.json");
|
||||
fseek(f, 0, SEEK_END);
|
||||
long sz = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
char *buf = (char*)xmalloc(sz + 1);
|
||||
fread(buf, 1, sz, f);
|
||||
buf[sz] = '\0';
|
||||
fclose(f);
|
||||
|
||||
SimParams p;
|
||||
p.box_a = json_read_double(buf, "box_a");
|
||||
p.NT = json_read_int(buf, "NT");
|
||||
p.DT = json_read_double(buf, "DT");
|
||||
p.NSTEP = json_read_int(buf, "NSTEP");
|
||||
p.warmup_steps = json_read_int(buf, "warmup_steps");
|
||||
strcpy(p.method, "leapfrog"); /* 默认 */
|
||||
json_read_string(buf, "method", p.method, sizeof(p.method));
|
||||
json_read_double3(buf, "G", p.G);
|
||||
json_read_double3(buf, "B", p.B);
|
||||
p.gravity_field = json_read_int(buf, "gravity_field");
|
||||
p.gravity_interaction = json_read_int(buf, "gravity_interaction");
|
||||
p.elastic_force = json_read_int(buf, "elastic_force");
|
||||
p.damping_force = json_read_int(buf, "damping_force");
|
||||
p.gravity_strength = json_read_double(buf, "gravity_strength");
|
||||
p.driving_force = json_read_int(buf, "driving_force");
|
||||
g_gravity_field = p.gravity_field;
|
||||
g_gravity_interaction = p.gravity_interaction;
|
||||
g_elastic_force = p.elastic_force;
|
||||
g_damping_force = p.damping_force;
|
||||
g_gravity_strength = p.gravity_strength;
|
||||
|
||||
free(buf);
|
||||
return p;
|
||||
}
|
||||
|
||||
/* 读取 coord.txt */
|
||||
static AtomData read_coord(const char *input_dir) {
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "%s/coord.txt", input_dir);
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) die("无法打开 coord.txt");
|
||||
|
||||
/* 跳过第一行表头 */
|
||||
char line[1024];
|
||||
if (!fgets(line, sizeof(line), f)) die("coord.txt 为空");
|
||||
|
||||
int capacity = 16;
|
||||
AtomData a;
|
||||
a.n_atoms = 0;
|
||||
a.atom_ids = (int*)xmalloc(capacity * sizeof(int));
|
||||
a.masses = (double*)xmalloc(capacity * sizeof(double));
|
||||
a.radii = (double*)xmalloc(capacity * sizeof(double));
|
||||
a.pos_0 = (double*)xmalloc(capacity * 3 * sizeof(double));
|
||||
a.vel_0 = (double*)xmalloc(capacity * 3 * sizeof(double));
|
||||
a.fixed = (int*)xmalloc(capacity * 3 * sizeof(int));
|
||||
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
if (a.n_atoms >= capacity) {
|
||||
capacity *= 2;
|
||||
a.atom_ids = realloc(a.atom_ids, capacity * sizeof(int));
|
||||
a.masses = realloc(a.masses, capacity * sizeof(double));
|
||||
a.radii = realloc(a.radii, capacity * sizeof(double));
|
||||
a.pos_0 = realloc(a.pos_0, capacity * 3 * sizeof(double));
|
||||
a.vel_0 = realloc(a.vel_0, capacity * 3 * sizeof(double));
|
||||
a.fixed = realloc(a.fixed, capacity * 3 * sizeof(int));
|
||||
}
|
||||
int id, fx, fy, fz;
|
||||
double mass, rad, px, py, pz, vx, vy, vz;
|
||||
int n_parsed = sscanf(line, "%d %lf %lf %lf %lf %lf %lf %lf %lf %d %d %d",
|
||||
&id, &mass, &rad, &px, &py, &pz, &vx, &vy, &vz, &fx, &fy, &fz);
|
||||
if (n_parsed == 9) {
|
||||
fx = fy = fz = 0;
|
||||
} else if (n_parsed != 12) {
|
||||
continue;
|
||||
}
|
||||
int i = a.n_atoms;
|
||||
a.atom_ids[i] = id;
|
||||
a.masses[i] = mass;
|
||||
a.radii[i] = rad;
|
||||
a.pos_0[i*3+0] = px; a.pos_0[i*3+1] = py; a.pos_0[i*3+2] = pz;
|
||||
a.vel_0[i*3+0] = vx; a.vel_0[i*3+1] = vy; a.vel_0[i*3+2] = vz;
|
||||
a.fixed[i*3+0] = fx; a.fixed[i*3+1] = fy; a.fixed[i*3+2] = fz;
|
||||
a.n_atoms++;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
if (a.n_atoms <= 0) die("coord.txt 原子数无效");
|
||||
return a;
|
||||
}
|
||||
|
||||
/* 读取 connection.txt */
|
||||
static BondData read_bonds(const char *input_dir, const AtomData *atoms) {
|
||||
char path[512];
|
||||
BondData b;
|
||||
b.n_bonds = 0;
|
||||
b.pairs = NULL;
|
||||
b.stiffness = NULL;
|
||||
b.rest_lengths = NULL;
|
||||
|
||||
snprintf(path, sizeof(path), "%s/connection.txt", input_dir);
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) return b;
|
||||
|
||||
char line[256];
|
||||
if (!fgets(line, sizeof(line), f)) { fclose(f); return b; }
|
||||
|
||||
int n_lines = 0, tmp_a, tmp_b;
|
||||
char bond_name[256];
|
||||
while (fscanf(f, "%d %d %s", &tmp_a, &tmp_b, bond_name) == 3) n_lines++;
|
||||
rewind(f);
|
||||
|
||||
if (n_lines == 0) { fclose(f); return b; }
|
||||
|
||||
b.n_bonds = n_lines;
|
||||
b.pairs = (int*)xmalloc(n_lines * 2 * sizeof(int));
|
||||
b.stiffness = (double*)xmalloc(n_lines * sizeof(double));
|
||||
b.rest_lengths = (double*)xmalloc(n_lines * sizeof(double));
|
||||
|
||||
char bond_path[512];
|
||||
snprintf(bond_path, sizeof(bond_path), "%s/bond.txt", input_dir);
|
||||
FILE *fb = fopen(bond_path, "r");
|
||||
|
||||
for (int i = 0; i < n_lines; i++) {
|
||||
fscanf(f, "%d %d %s", &tmp_a, &tmp_b, bond_name);
|
||||
b.pairs[i*2+0] = tmp_a - 1;
|
||||
b.pairs[i*2+1] = tmp_b - 1;
|
||||
b.stiffness[i] = 1.0;
|
||||
b.rest_lengths[i] = 2.0;
|
||||
if (fb) {
|
||||
char name[256], header[256];
|
||||
double k, r0;
|
||||
rewind(fb);
|
||||
fgets(header, sizeof(header), fb); // 跳过表头行
|
||||
while (fscanf(fb, "%s %lf %lf", name, &k, &r0) == 3) {
|
||||
if (strcmp(name, bond_name) == 0) {
|
||||
b.stiffness[i] = k;
|
||||
b.rest_lengths[i] = r0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
if (fb) fclose(fb);
|
||||
return b;
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* 物理核心(与 Python compute.py 对应)
|
||||
* ======================================================================== */
|
||||
|
||||
/* 加速度计算(各力独立开关控制) */
|
||||
static void compute_acceleration(
|
||||
int n, const double *x, const double *y, const double *z,
|
||||
const double *vx, const double *vy, const double *vz,
|
||||
const double *m, const double G[3], const double B[3],
|
||||
const BondData *bonds,
|
||||
double *ax, double *ay, double *az)
|
||||
{
|
||||
/* 先清零 */
|
||||
for (int i = 0; i < n; i++) {
|
||||
ax[i] = 0.0; ay[i] = 0.0; az[i] = 0.0;
|
||||
}
|
||||
|
||||
/* 均匀重力场 */
|
||||
if (g_gravity_field) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
ax[i] += G[0];
|
||||
ay[i] += G[1];
|
||||
az[i] += G[2];
|
||||
}
|
||||
}
|
||||
|
||||
/* 阻尼 */
|
||||
if (g_damping_force) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
ax[i] -= B[0] * vx[i] / m[i];
|
||||
ay[i] -= B[1] * vy[i] / m[i];
|
||||
az[i] -= B[2] * vz[i] / m[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* 弹簧键力 */
|
||||
if (g_elastic_force) {
|
||||
for (int b = 0; b < bonds->n_bonds; b++) {
|
||||
int i = bonds->pairs[b*2+0];
|
||||
int j = bonds->pairs[b*2+1];
|
||||
double dx = x[j] - x[i];
|
||||
double dy = y[j] - y[i];
|
||||
double dz = z[j] - z[i];
|
||||
double dist = sqrt(dx*dx + dy*dy + dz*dz);
|
||||
if (dist < 1e-12) continue;
|
||||
double stretch = dist - bonds->rest_lengths[b];
|
||||
double fmag = bonds->stiffness[b] * stretch;
|
||||
double ux = dx / dist, uy = dy / dist, uz = dz / dist;
|
||||
double fx = fmag * ux, fy = fmag * uy, fz = fmag * uz;
|
||||
ax[i] += fx / m[i]; ay[i] += fy / m[i]; az[i] += fz / m[i];
|
||||
ax[j] -= fx / m[j]; ay[j] -= fy / m[j]; az[j] -= fz / m[j];
|
||||
}
|
||||
}
|
||||
|
||||
/* 万有引力(所有原子对之间) */
|
||||
if (g_gravity_interaction) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (int j = i + 1; j < n; j++) {
|
||||
double dx = x[j] - x[i];
|
||||
double dy = y[j] - y[i];
|
||||
double dz = z[j] - z[i];
|
||||
double r2 = dx*dx + dy*dy + dz*dz;
|
||||
if (r2 <= 1e-12) continue;
|
||||
double r = sqrt(r2);
|
||||
double f_mag = g_gravity_strength * m[i] * m[j] / r2;
|
||||
double fx_g = f_mag * dx / r;
|
||||
double fy_g = f_mag * dy / r;
|
||||
double fz_g = f_mag * dz / r;
|
||||
ax[i] += fx_g / m[i]; ay[i] += fy_g / m[i]; az[i] += fz_g / m[i];
|
||||
ax[j] -= fx_g / m[j]; ay[j] -= fy_g / m[j]; az[j] -= fz_g / m[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 边界条件:clamp 位置 + 速度反转 ——与 Python Limit_in_box 一致 */
|
||||
static void limit_in_box(double *pos, double *vel, double lo, double hi) {
|
||||
if (*pos > hi) { *pos = hi; *vel = -*vel; }
|
||||
if (*pos < lo) { *pos = lo; *vel = -*vel; }
|
||||
}
|
||||
|
||||
/* ── 显式欧拉法 ──────────── */
|
||||
static void explicit_euler_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const double G[3], const double B[3],
|
||||
const BondData *bonds, const int *fixed, double dt)
|
||||
{
|
||||
double *ax = (double*)alloca(n * sizeof(double));
|
||||
double *ay = (double*)alloca(n * sizeof(double));
|
||||
double *az = (double*)alloca(n * sizeof(double));
|
||||
compute_acceleration(n, x, y, z, vx, vy, vz, m, G, B, bonds, ax, ay, az);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
x[i] += vx[i] * dt;
|
||||
y[i] += vy[i] * dt;
|
||||
z[i] += vz[i] * dt;
|
||||
vx[i] += ax[i] * dt;
|
||||
vy[i] += ay[i] * dt;
|
||||
vz[i] += az[i] * dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 隐式欧拉法 ──────────── */
|
||||
static void implicit_euler_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const double G[3], const double B[3],
|
||||
const BondData *bonds, const int *fixed, double dt)
|
||||
{
|
||||
double *vxn = (double*)alloca(n * sizeof(double));
|
||||
double *vyn = (double*)alloca(n * sizeof(double));
|
||||
double *vzn = (double*)alloca(n * sizeof(double));
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) {
|
||||
vxn[i] = 0; vyn[i] = 0; vzn[i] = 0; continue;
|
||||
}
|
||||
double gx = B[0] / m[i], gy = B[1] / m[i], gz = B[2] / m[i];
|
||||
vxn[i] = (vx[i] + G[0] * dt) / (1.0 + gx * dt);
|
||||
vyn[i] = (vy[i] + G[1] * dt) / (1.0 + gy * dt);
|
||||
vzn[i] = (vz[i] + G[2] * dt) / (1.0 + gz * dt);
|
||||
}
|
||||
|
||||
double *ax = (double*)alloca(n * sizeof(double));
|
||||
double *ay = (double*)alloca(n * sizeof(double));
|
||||
double *az = (double*)alloca(n * sizeof(double));
|
||||
compute_acceleration(n, x, y, z, vxn, vyn, vzn, m, G, B, bonds, ax, ay, az);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += ax[i] * dt;
|
||||
vy[i] += ay[i] * dt;
|
||||
vz[i] += az[i] * dt;
|
||||
x[i] += vx[i] * dt;
|
||||
y[i] += vy[i] * dt;
|
||||
z[i] += vz[i] * dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 中点法 ──────────── */
|
||||
static void midpoint_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const double G[3], const double B[3],
|
||||
const BondData *bonds, const int *fixed, double dt)
|
||||
{
|
||||
double *ax = (double*)alloca(n * sizeof(double));
|
||||
double *ay = (double*)alloca(n * sizeof(double));
|
||||
double *az = (double*)alloca(n * sizeof(double));
|
||||
compute_acceleration(n, x, y, z, vx, vy, vz, m, G, B, bonds, ax, ay, az);
|
||||
|
||||
double *xm = (double*)alloca(n * sizeof(double));
|
||||
double *ym = (double*)alloca(n * sizeof(double));
|
||||
double *zm = (double*)alloca(n * sizeof(double));
|
||||
double *vxm = (double*)alloca(n * sizeof(double));
|
||||
double *vym = (double*)alloca(n * sizeof(double));
|
||||
double *vzm = (double*)alloca(n * sizeof(double));
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) {
|
||||
xm[i]=ym[i]=zm[i]=vxm[i]=vym[i]=vzm[i]=0; continue;
|
||||
}
|
||||
xm[i] = x[i] + 0.5 * vx[i] * dt;
|
||||
ym[i] = y[i] + 0.5 * vy[i] * dt;
|
||||
zm[i] = z[i] + 0.5 * vz[i] * dt;
|
||||
vxm[i] = vx[i] + 0.5 * ax[i] * dt;
|
||||
vym[i] = vy[i] + 0.5 * ay[i] * dt;
|
||||
vzm[i] = vz[i] + 0.5 * az[i] * dt;
|
||||
x[i] = x[i] + vxm[i] * dt;
|
||||
y[i] = y[i] + vym[i] * dt;
|
||||
z[i] = z[i] + vzm[i] * dt;
|
||||
}
|
||||
|
||||
compute_acceleration(n, xm, ym, zm, vxm, vym, vzm, m, G, B, bonds, ax, ay, az);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += ax[i] * dt;
|
||||
vy[i] += ay[i] * dt;
|
||||
vz[i] += az[i] * dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 蛙跳法(Velocity-Verlet)── */
|
||||
static void leapfrog_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const double G[3], const double B[3],
|
||||
const BondData *bonds, const int *fixed, double dt)
|
||||
{
|
||||
double *ax = (double*)alloca(n * sizeof(double));
|
||||
double *ay = (double*)alloca(n * sizeof(double));
|
||||
double *az = (double*)alloca(n * sizeof(double));
|
||||
compute_acceleration(n, x, y, z, vx, vy, vz, m, G, B, bonds, ax, ay, az);
|
||||
|
||||
/* 半推速度:v_half = v + 0.5*a*dt */
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += ax[i] * dt * 0.5;
|
||||
vy[i] += ay[i] * dt * 0.5;
|
||||
vz[i] += az[i] * dt * 0.5;
|
||||
}
|
||||
|
||||
/* 全推位置(不含边界)*/
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
x[i] += vx[i] * dt; /* vx 此时是 v_half */
|
||||
y[i] += vy[i] * dt;
|
||||
z[i] += vz[i] * dt;
|
||||
}
|
||||
|
||||
/* 显式预测器:v_pred = v_half + 0.5*a_old*dt,用第一次加速度外推半步
|
||||
包含重力+阻尼+弹簧的所有贡献(标准 Velocity-Verlet 预测步)*/
|
||||
double *pred_vx = (double*)alloca(n * sizeof(double));
|
||||
double *pred_vy = (double*)alloca(n * sizeof(double));
|
||||
double *pred_vz = (double*)alloca(n * sizeof(double));
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
pred_vx[i] = vx[i] + 0.5 * ax[i] * dt;
|
||||
pred_vy[i] = vy[i] + 0.5 * ay[i] * dt;
|
||||
pred_vz[i] = vz[i] + 0.5 * az[i] * dt;
|
||||
}
|
||||
|
||||
/* 用新位置 + 预测速度重算加速度 */
|
||||
compute_acceleration(n, x, y, z, pred_vx, pred_vy, pred_vz, m, G, B, bonds, ax, ay, az);
|
||||
|
||||
/* 速度后半步:v = v_half + 0.5*a_next*dt
|
||||
vx 仍为 v_half(未被覆盖)*/
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += ax[i] * dt * 0.5;
|
||||
vy[i] += ay[i] * dt * 0.5;
|
||||
vz[i] += az[i] * dt * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 驱动力(与 Python apply_driving_force 一致)──────────────── */
|
||||
static void apply_driving_force(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
double t, int step, double dt,
|
||||
const DriverData *drivers)
|
||||
{
|
||||
if (!drivers || drivers->n_drivers == 0) return;
|
||||
for (int d = 0; d < drivers->n_drivers; d++) {
|
||||
int idx = drivers->atom_idx[d];
|
||||
/* 检查周期限制 */
|
||||
if (drivers->has_period[d]) {
|
||||
double max_freq = fmax(fabs(drivers->freq_x[d]),
|
||||
fmax(fabs(drivers->freq_y[d]), fabs(drivers->freq_z[d])));
|
||||
int period_steps = 0;
|
||||
if (max_freq > 1e-12) {
|
||||
period_steps = (int)(drivers->period_cycles[d] / max_freq / dt);
|
||||
}
|
||||
if (step > period_steps) {
|
||||
/* 冻结 */
|
||||
if (drivers->freeze_x) {
|
||||
x[idx] = drivers->freeze_x[d];
|
||||
y[idx] = drivers->freeze_y[d];
|
||||
z[idx] = drivers->freeze_z[d];
|
||||
}
|
||||
vx[idx] = vy[idx] = vz[idx] = 0.0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
double px = drivers->amp_x[d] * cos(2*M_PI*drivers->freq_x[d]*t + drivers->phi_x[d]);
|
||||
double py = drivers->amp_y[d] * cos(2*M_PI*drivers->freq_y[d]*t + drivers->phi_y[d]);
|
||||
double pz = drivers->amp_z[d] * cos(2*M_PI*drivers->freq_z[d]*t + drivers->phi_z[d]);
|
||||
double vpx = -drivers->amp_x[d]*2*M_PI*drivers->freq_x[d]*sin(2*M_PI*drivers->freq_x[d]*t + drivers->phi_x[d]);
|
||||
double vpy = -drivers->amp_y[d]*2*M_PI*drivers->freq_y[d]*sin(2*M_PI*drivers->freq_y[d]*t + drivers->phi_y[d]);
|
||||
double vpz = -drivers->amp_z[d]*2*M_PI*drivers->freq_z[d]*sin(2*M_PI*drivers->freq_z[d]*t + drivers->phi_z[d]);
|
||||
|
||||
x[idx] = px; y[idx] = py; z[idx] = pz;
|
||||
vx[idx] = vpx; vy[idx] = vpy; vz[idx] = vpz;
|
||||
|
||||
/* 记录冻结位置(周期结束时) */
|
||||
if (drivers->has_period[d]) {
|
||||
double max_freq = fmax(fabs(drivers->freq_x[d]),
|
||||
fmax(fabs(drivers->freq_y[d]), fabs(drivers->freq_z[d])));
|
||||
int period_steps = 0;
|
||||
if (max_freq > 1e-12) {
|
||||
period_steps = (int)(drivers->period_cycles[d] / max_freq / dt);
|
||||
}
|
||||
if (step == period_steps) {
|
||||
drivers->freeze_x[d] = px;
|
||||
drivers->freeze_y[d] = py;
|
||||
drivers->freeze_z[d] = pz;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 分发器:调用对应积分方法 + 边界条件 + 自由度约束(与 Python 一致)── */
|
||||
static void apply_step(
|
||||
const char *method,
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const double G[3], const double B[3],
|
||||
const BondData *bonds, const int *fixed,
|
||||
const double *pos_0,
|
||||
double box_a, double dt)
|
||||
{
|
||||
if (strcmp(method, "explicit_euler") == 0) {
|
||||
explicit_euler_step(n, x, y, z, vx, vy, vz, m, G, B, bonds, fixed, dt);
|
||||
} else if (strcmp(method, "implicit_euler") == 0) {
|
||||
implicit_euler_step(n, x, y, z, vx, vy, vz, m, G, B, bonds, fixed, dt);
|
||||
} else if (strcmp(method, "midpoint") == 0) {
|
||||
midpoint_step(n, x, y, z, vx, vy, vz, m, G, B, bonds, fixed, dt);
|
||||
} else if (strcmp(method, "leapfrog") == 0) {
|
||||
leapfrog_step(n, x, y, z, vx, vy, vz, m, G, B, bonds, fixed, dt);
|
||||
} else {
|
||||
fprintf(stderr, "[C-engine] 未知算法: %s\n", method);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* 边界条件(与 Python Limit_in_box 一致) */
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
limit_in_box(&x[i], &vx[i], -box_a, box_a);
|
||||
limit_in_box(&y[i], &vy[i], -box_a, box_a);
|
||||
limit_in_box(&z[i], &vz[i], -box_a, box_a);
|
||||
}
|
||||
|
||||
/* 逐自由度固定约束(与 Python apply_fixed_constraints 一致) */
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0]) { x[i] = pos_0[i*3+0]; vx[i] = 0.0; }
|
||||
if (fixed[i*3+1]) { y[i] = pos_0[i*3+1]; vy[i] = 0.0; }
|
||||
if (fixed[i*3+2]) { z[i] = pos_0[i*3+2]; vz[i] = 0.0; }
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// JSON 输出
|
||||
// ========================================================================
|
||||
|
||||
static void write_trajectory_json(const char *path, const Trajectory *traj,
|
||||
const SimParams *params, const AtomData *atoms,
|
||||
const BondData *bonds)
|
||||
{
|
||||
FILE *f = fopen(path, "w");
|
||||
if (!f) die("无法写入 trajectory.txt");
|
||||
|
||||
fprintf(f, "{\n");
|
||||
|
||||
const char *names[] = {"traj_x","traj_y","traj_z","traj_vx","traj_vy","traj_vz"};
|
||||
double *arrs[] = {traj->x, traj->y, traj->z, traj->vx, traj->vy, traj->vz};
|
||||
|
||||
for (int a = 0; a < 6; a++) {
|
||||
fprintf(f, " \"%s\": [\n", names[a]);
|
||||
for (int t = 0; t < traj->n_steps; t++) {
|
||||
fprintf(f, " [");
|
||||
for (int i = 0; i < traj->n_atoms; i++) {
|
||||
fprintf(f, "%.15g", arrs[a][t * traj->n_atoms + i]);
|
||||
if (i < traj->n_atoms - 1) fputc(',', f);
|
||||
}
|
||||
fprintf(f, "]");
|
||||
if (t < traj->n_steps - 1) fputc(',', f);
|
||||
fputc('\n', f);
|
||||
}
|
||||
fprintf(f, " ]");
|
||||
fputc(',', f);
|
||||
fputc('\n', f);
|
||||
}
|
||||
|
||||
/* 标量参数 */
|
||||
fprintf(f, " \"NT\": %d,\n", params->NT);
|
||||
fprintf(f, " \"DT\": %.15g,\n", params->DT);
|
||||
fprintf(f, " \"NSTEP\": %d,\n", params->NSTEP);
|
||||
fprintf(f, " \"method\": \"%s\",\n", params->method);
|
||||
fprintf(f, " \"warmup_steps\": %d,\n", params->warmup_steps);
|
||||
fprintf(f, " \"G\": [%.15g, %.15g, %.15g],\n", params->G[0], params->G[1], params->G[2]);
|
||||
fprintf(f, " \"B\": [%.15g, %.15g, %.15g],\n", params->B[0], params->B[1], params->B[2]);
|
||||
|
||||
fprintf(f, " \"atom_ids\": [");
|
||||
for (int i = 0; i < atoms->n_atoms; i++) {
|
||||
if (i > 0) fputc(',', f);
|
||||
fprintf(f, "%d", atoms->atom_ids[i]);
|
||||
}
|
||||
fprintf(f, "],\n");
|
||||
|
||||
fprintf(f, " \"atom_masses\": [");
|
||||
for (int i = 0; i < atoms->n_atoms; i++) {
|
||||
if (i > 0) fputc(',', f);
|
||||
fprintf(f, "%.15g", atoms->masses[i]);
|
||||
}
|
||||
fprintf(f, "],\n");
|
||||
|
||||
fprintf(f, " \"bond_pairs\": [");
|
||||
for (int b = 0; b < bonds->n_bonds; b++) {
|
||||
if (b > 0) fputc(',', f);
|
||||
fprintf(f, "[%d, %d]", bonds->pairs[b*2], bonds->pairs[b*2+1]);
|
||||
}
|
||||
fprintf(f, "],\n");
|
||||
|
||||
fprintf(f, " \"bond_stiffness\": [");
|
||||
for (int b = 0; b < bonds->n_bonds; b++) {
|
||||
if (b > 0) fputc(',', f);
|
||||
fprintf(f, "%.15g", bonds->stiffness[b]);
|
||||
}
|
||||
fprintf(f, "],\n");
|
||||
|
||||
fprintf(f, " \"bond_rest_lengths\": [");
|
||||
for (int b = 0; b < bonds->n_bonds; b++) {
|
||||
if (b > 0) fputc(',', f);
|
||||
fprintf(f, "%.15g", bonds->rest_lengths[b]);
|
||||
}
|
||||
fprintf(f, "],\n");
|
||||
fprintf(f, " \"driving_force\": %d\n", params->driving_force);
|
||||
|
||||
fprintf(f, "}\n");
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// 主函数
|
||||
// ========================================================================
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 4) {
|
||||
fprintf(stderr, "用法: %s <input_dir> <output_dir> <param_json>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
const char *input_dir = argv[1];
|
||||
const char *output_dir = argv[2];
|
||||
const char *param_path = argv[3];
|
||||
|
||||
clock_t t0 = clock();
|
||||
|
||||
SimParams params = read_params(param_path);
|
||||
AtomData atoms = read_coord(input_dir);
|
||||
BondData bonds = read_bonds(input_dir, &atoms);
|
||||
|
||||
DriverData drivers;
|
||||
drivers.n_drivers = 0;
|
||||
if (params.driving_force) {
|
||||
drivers = read_driver(input_dir, &atoms);
|
||||
}
|
||||
|
||||
printf("[C-engine] 原子数=%d, 键数=%d, 驱动=%d, NT=%d, DT=%.6g, method=%s\n",
|
||||
atoms.n_atoms, bonds.n_bonds, drivers.n_drivers, params.NT, params.DT, params.method);
|
||||
|
||||
int n = atoms.n_atoms;
|
||||
double *x = (double*)xmalloc(n * sizeof(double));
|
||||
double *y = (double*)xmalloc(n * sizeof(double));
|
||||
double *z = (double*)xmalloc(n * sizeof(double));
|
||||
double *vx = (double*)xmalloc(n * sizeof(double));
|
||||
double *vy = (double*)xmalloc(n * sizeof(double));
|
||||
double *vz = (double*)xmalloc(n * sizeof(double));
|
||||
for (int i = 0; i < n; i++) {
|
||||
x[i] = atoms.pos_0[i*3+0];
|
||||
y[i] = atoms.pos_0[i*3+1];
|
||||
z[i] = atoms.pos_0[i*3+2];
|
||||
vx[i] = atoms.vel_0[i*3+0];
|
||||
vy[i] = atoms.vel_0[i*3+1];
|
||||
vz[i] = atoms.vel_0[i*3+2];
|
||||
}
|
||||
|
||||
/* 分配轨迹缓冲区:改用 record_steps */
|
||||
int record_steps = params.NT - params.warmup_steps;
|
||||
Trajectory traj;
|
||||
traj.n_steps = record_steps;
|
||||
traj.n_atoms = n;
|
||||
traj.x = (double*)xmalloc(record_steps * n * sizeof(double) * 6);
|
||||
traj.y = traj.x + record_steps * n;
|
||||
traj.z = traj.y + record_steps * n;
|
||||
traj.vx = traj.z + record_steps * n;
|
||||
traj.vy = traj.vx + record_steps * n;
|
||||
traj.vz = traj.vy + record_steps * n;
|
||||
|
||||
/* 预热 */
|
||||
for (int s = 0; s < params.warmup_steps; s++) {
|
||||
double tw = (s + 1) * params.DT;
|
||||
if (params.driving_force) apply_driving_force(n, x, y, z, vx, vy, vz, tw, s, params.DT, &drivers);
|
||||
apply_step(params.method, n, x, y, z, vx, vy, vz,
|
||||
atoms.masses, params.G, params.B, &bonds, atoms.fixed,
|
||||
atoms.pos_0,
|
||||
params.box_a, params.DT);
|
||||
}
|
||||
|
||||
/* 记录 */
|
||||
for (int s = 0; s < record_steps; s++) {
|
||||
double t = (s + params.warmup_steps) * params.DT;
|
||||
if (params.driving_force) apply_driving_force(n, x, y, z, vx, vy, vz, t, s, params.DT, &drivers);
|
||||
for (int i = 0; i < n; i++) {
|
||||
traj.x[ s * n + i] = x[i];
|
||||
traj.y[ s * n + i] = y[i];
|
||||
traj.z[ s * n + i] = z[i];
|
||||
traj.vx[s * n + i] = vx[i];
|
||||
traj.vy[s * n + i] = vy[i];
|
||||
traj.vz[s * n + i] = vz[i];
|
||||
}
|
||||
apply_step(params.method, n, x, y, z, vx, vy, vz,
|
||||
atoms.masses, params.G, params.B, &bonds, atoms.fixed,
|
||||
atoms.pos_0,
|
||||
params.box_a, params.DT);
|
||||
}
|
||||
|
||||
char out_path[512];
|
||||
snprintf(out_path, sizeof(out_path), "%s/trajectory.txt", output_dir);
|
||||
write_trajectory_json(out_path, &traj, ¶ms, &atoms, &bonds);
|
||||
|
||||
clock_t t1 = clock();
|
||||
double elapsed = (double)(t1 - t0) / CLOCKS_PER_SEC;
|
||||
printf("[C-engine] 计算完成: %d 步, %.3f s\n", record_steps, elapsed);
|
||||
|
||||
/* 清理 */
|
||||
free(traj.x);
|
||||
free(x); free(y); free(z);
|
||||
free(vx); free(vy); free(vz);
|
||||
free(atoms.atom_ids);
|
||||
free(atoms.masses); free(atoms.radii);
|
||||
free(atoms.pos_0); free(atoms.vel_0);
|
||||
free(atoms.fixed);
|
||||
if (bonds.pairs) { free(bonds.pairs); free(bonds.stiffness); free(bonds.rest_lengths); }
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,863 +0,0 @@
|
||||
/**
|
||||
* engines/cpp/main.cpp
|
||||
* --------------------
|
||||
* C++ 动力学模拟引擎。
|
||||
* 与 Python 版 (compute.py) 算法保持一致。
|
||||
*
|
||||
* 输入: param.json, <input_dir>/coord.txt, connection.txt, bond.txt
|
||||
* 输出: <output_dir>/trajectory.txt (JSON, 与 Python 版兼容)
|
||||
*
|
||||
* 编译:
|
||||
* g++ -O3 -march=native -std=c++17 -o build/dynamics_cpp main.cpp
|
||||
*
|
||||
* 用法:
|
||||
* ./build/dynamics_cpp <input_dir> <output_dir> <param_json>
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// ========================================================================
|
||||
// 配置参数(从 param.json 读取)
|
||||
// ========================================================================
|
||||
struct SimParams {
|
||||
double box_a = 10.0;
|
||||
int NT = 10000;
|
||||
double DT = 0.001;
|
||||
int NSTEP = 100;
|
||||
int warmup_steps = 0;
|
||||
std::string method = "leapfrog";
|
||||
double G[3] = {0, 0, -9.8};
|
||||
double B[3] = {0, 0, 0};
|
||||
int gravity_field = 1;
|
||||
int gravity_interaction = 0;
|
||||
int elastic_force = 1;
|
||||
int damping_force = 0;
|
||||
double gravity_strength = 1.0;
|
||||
int driving_force = 0;
|
||||
};
|
||||
|
||||
// ========================================================================
|
||||
// 原子数据
|
||||
// ========================================================================
|
||||
struct AtomData {
|
||||
std::vector<int> ids;
|
||||
std::vector<double> masses;
|
||||
std::vector<double> radii;
|
||||
std::vector<double> pos_0; // (n_atoms * 3)
|
||||
std::vector<double> vel_0; // (n_atoms * 3)
|
||||
std::vector<int> fixed; // (n_atoms * 3), 0/1 flags
|
||||
};
|
||||
|
||||
// ========================================================================
|
||||
// 成键数据
|
||||
// ========================================================================
|
||||
struct BondData {
|
||||
std::vector<int> pairs; // (n_bonds * 2)
|
||||
std::vector<double> stiffness;
|
||||
std::vector<double> rest_lengths;
|
||||
};
|
||||
|
||||
// ========================================================================
|
||||
// 驱动力数据
|
||||
// ========================================================================
|
||||
struct DriverData {
|
||||
int n_drivers = 0;
|
||||
std::vector<int> atom_idx; // internal atom indices
|
||||
std::vector<double> amp_x, amp_y, amp_z;
|
||||
std::vector<double> freq_x, freq_y, freq_z;
|
||||
std::vector<double> phi_x, phi_y, phi_z; // radians
|
||||
std::vector<int> has_period; // 0=all, 1=limited
|
||||
std::vector<double> period_cycles;
|
||||
std::vector<double> freeze_x, freeze_y, freeze_z;
|
||||
};
|
||||
|
||||
// ========================================================================
|
||||
// 辅助函数
|
||||
// ========================================================================
|
||||
|
||||
static void die(const std::string &msg) {
|
||||
std::cerr << "[C++-engine] 错误: " << msg << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* 读整个文件为字符串 */
|
||||
static std::string read_file(const std::string &path) {
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
if (!f) die("无法打开 " + path);
|
||||
std::ostringstream ss;
|
||||
ss << f.rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/* 从 JSON 中查找 key,返回冒号后的数值 */
|
||||
static double json_read_double(const std::string &json, const std::string &key) {
|
||||
auto pos = json.find("\"" + key + "\"");
|
||||
if (pos == std::string::npos) return 0.0;
|
||||
pos = json.find(':', pos);
|
||||
if (pos == std::string::npos) return 0.0;
|
||||
while (pos < json.size() && (json[pos] == ':' || json[pos] == ' ' || json[pos] == '\t' || json[pos] == '\n')) pos++;
|
||||
return std::stod(json.substr(pos));
|
||||
}
|
||||
|
||||
static int json_read_int(const std::string &json, const std::string &key) {
|
||||
return static_cast<int>(json_read_double(json, key));
|
||||
}
|
||||
|
||||
/* 从 JSON 中读取字符串值 */
|
||||
static std::string json_read_string(const std::string &json, const std::string &key) {
|
||||
auto pos = json.find("\"" + key + "\"");
|
||||
if (pos == std::string::npos) return "";
|
||||
pos = json.find(':', pos);
|
||||
if (pos == std::string::npos) return "";
|
||||
while (pos < json.size() && (json[pos] == ':' || json[pos] == ' ' || json[pos] == '\t' || json[pos] == '\n')) pos++;
|
||||
if (pos >= json.size()) return "";
|
||||
// 找到引号
|
||||
if (json[pos] != '"') return "";
|
||||
pos++;
|
||||
std::string result;
|
||||
while (pos < json.size() && json[pos] != '"') {
|
||||
result += json[pos];
|
||||
pos++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* 读取 JSON 数组 (如 "G": [0, 0, -9.8]) 到 double[3] */
|
||||
static void json_read_double3(const std::string &json, const std::string &key, double out[3]) {
|
||||
auto pos = json.find("\"" + key + "\"");
|
||||
if (pos == std::string::npos) { out[0] = out[1] = out[2] = 0; return; }
|
||||
pos = json.find('[', pos);
|
||||
if (pos == std::string::npos) { out[0] = out[1] = out[2] = 0; return; }
|
||||
pos++;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t' || json[pos] == '\n' || json[pos] == ',')) pos++;
|
||||
char *end;
|
||||
out[i] = std::strtod(json.c_str() + pos, &end);
|
||||
pos = end - json.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
/* 解析 param.json */
|
||||
static SimParams read_params(const std::string &path) {
|
||||
std::string buf = read_file(path);
|
||||
SimParams p;
|
||||
p.box_a = json_read_double(buf, "box_a");
|
||||
p.NT = json_read_int(buf, "NT");
|
||||
p.DT = json_read_double(buf, "DT");
|
||||
p.NSTEP = json_read_int(buf, "NSTEP");
|
||||
p.warmup_steps = json_read_int(buf, "warmup_steps");
|
||||
std::string m = json_read_string(buf, "method");
|
||||
if (!m.empty()) p.method = m;
|
||||
json_read_double3(buf, "G", p.G);
|
||||
json_read_double3(buf, "B", p.B);
|
||||
p.gravity_field = json_read_int(buf, "gravity_field");
|
||||
p.gravity_interaction = json_read_int(buf, "gravity_interaction");
|
||||
p.elastic_force = json_read_int(buf, "elastic_force");
|
||||
p.damping_force = json_read_int(buf, "damping_force");
|
||||
p.gravity_strength = json_read_double(buf, "gravity_strength");
|
||||
p.driving_force = json_read_int(buf, "driving_force");
|
||||
return p;
|
||||
}
|
||||
|
||||
/* 读取 coord.txt */
|
||||
static AtomData read_coord(const std::string &input_dir) {
|
||||
std::string path = input_dir + "/coord.txt";
|
||||
std::ifstream f(path);
|
||||
if (!f) die("无法打开 " + path);
|
||||
|
||||
std::string header;
|
||||
std::getline(f, header); // 跳过表头
|
||||
|
||||
AtomData a;
|
||||
int id, fx, fy, fz;
|
||||
double mass, rad, px, py, pz, vx, vy, vz;
|
||||
std::string line;
|
||||
|
||||
while (std::getline(f, line)) {
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
int n_parsed = std::sscanf(line.c_str(), "%d %lf %lf %lf %lf %lf %lf %lf %lf %d %d %d",
|
||||
&id, &mass, &rad, &px, &py, &pz, &vx, &vy, &vz, &fx, &fy, &fz);
|
||||
if (n_parsed == 9) {
|
||||
fx = fy = fz = 0;
|
||||
} else if (n_parsed != 12) {
|
||||
continue;
|
||||
}
|
||||
a.ids.push_back(id);
|
||||
a.masses.push_back(mass);
|
||||
a.radii.push_back(rad);
|
||||
a.pos_0.push_back(px); a.pos_0.push_back(py); a.pos_0.push_back(pz);
|
||||
a.vel_0.push_back(vx); a.vel_0.push_back(vy); a.vel_0.push_back(vz);
|
||||
a.fixed.push_back(fx); a.fixed.push_back(fy); a.fixed.push_back(fz);
|
||||
}
|
||||
|
||||
if (a.ids.empty()) die("coord.txt 中没有原子数据");
|
||||
return a;
|
||||
}
|
||||
|
||||
/* 读取 connection.txt 和 bond.txt */
|
||||
static BondData read_bonds(const std::string &input_dir) {
|
||||
BondData b;
|
||||
std::string conn_path = input_dir + "/connection.txt";
|
||||
std::ifstream f(conn_path);
|
||||
if (!f) return b; // 无成键
|
||||
|
||||
std::string header;
|
||||
std::getline(f, header); // 跳过表头
|
||||
|
||||
// 先读取 bond.txt 获得键参数映射
|
||||
std::string bond_path = input_dir + "/bond.txt";
|
||||
std::ifstream fb(bond_path);
|
||||
|
||||
int a1, a2;
|
||||
std::string bond_name;
|
||||
std::vector<std::tuple<int, int, std::string>> conn_lines;
|
||||
while (f >> a1 >> a2 >> bond_name) {
|
||||
conn_lines.emplace_back(a1 - 1, a2 - 1, bond_name);
|
||||
}
|
||||
|
||||
for (auto &[i, j, name] : conn_lines) {
|
||||
double k = 1.0, r0 = 2.0;
|
||||
if (fb) {
|
||||
fb.clear();
|
||||
fb.seekg(0);
|
||||
std::string bn, header;
|
||||
double bk, br;
|
||||
std::getline(fb, header); // 跳过表头行
|
||||
while (fb >> bn >> bk >> br) {
|
||||
if (bn == name) {
|
||||
k = bk;
|
||||
r0 = br;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
b.pairs.push_back(i);
|
||||
b.pairs.push_back(j);
|
||||
b.stiffness.push_back(k);
|
||||
b.rest_lengths.push_back(r0);
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
/* 读取 driver.txt */
|
||||
static DriverData read_driver(const std::string &input_dir, const AtomData &atoms) {
|
||||
DriverData d;
|
||||
std::string path = input_dir + "/driver.txt";
|
||||
std::ifstream f(path);
|
||||
if (!f) { std::cerr << "[C++-engine] 警告: 无法打开 " << path << std::endl; return d; }
|
||||
|
||||
std::string header;
|
||||
std::getline(f, header); // skip header
|
||||
|
||||
int n;
|
||||
double ax, ay, az, fx, fy, fz, px, py, pz;
|
||||
std::string period_str;
|
||||
|
||||
while (f >> n >> ax >> ay >> az >> fx >> fy >> fz >> px >> py >> pz >> period_str) {
|
||||
// Find atom index by id
|
||||
int idx = -1;
|
||||
for (size_t i = 0; i < atoms.ids.size(); i++) {
|
||||
if (atoms.ids[i] == n) { idx = i; break; }
|
||||
}
|
||||
if (idx < 0) {
|
||||
std::cerr << "[C++-engine] 警告: driver.txt 原子 " << n << " 不在 coord.txt 中" << std::endl;
|
||||
continue;
|
||||
}
|
||||
d.atom_idx.push_back(idx);
|
||||
d.amp_x.push_back(ax); d.amp_y.push_back(ay); d.amp_z.push_back(az);
|
||||
d.freq_x.push_back(fx); d.freq_y.push_back(fy); d.freq_z.push_back(fz);
|
||||
// Convert degrees to radians
|
||||
const double DEG2RAD = M_PI / 180.0;
|
||||
d.phi_x.push_back(px * DEG2RAD);
|
||||
d.phi_y.push_back(py * DEG2RAD);
|
||||
d.phi_z.push_back(pz * DEG2RAD);
|
||||
|
||||
if (period_str == "all") {
|
||||
d.has_period.push_back(0);
|
||||
d.period_cycles.push_back(-1.0);
|
||||
} else {
|
||||
d.has_period.push_back(1);
|
||||
d.period_cycles.push_back(std::stod(period_str));
|
||||
}
|
||||
d.freeze_x.push_back(0.0);
|
||||
d.freeze_y.push_back(0.0);
|
||||
d.freeze_z.push_back(0.0);
|
||||
d.n_drivers++;
|
||||
}
|
||||
|
||||
if (d.n_drivers > 0)
|
||||
std::cout << "[C++-engine] 已加载驱动力: " << d.n_drivers << " 条定义" << std::endl;
|
||||
return d;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// 物理核心
|
||||
// ========================================================================
|
||||
|
||||
/* 加速度计算(各力独立开关控制)——与 Python compute_acceleration 一致 */
|
||||
static void compute_acceleration(
|
||||
int n,
|
||||
const double *x, const double *y, const double *z,
|
||||
const double *vx, const double *vy, const double *vz,
|
||||
const double *m, const double G[3], const double B[3],
|
||||
const BondData &bonds,
|
||||
int gravity_field, int gravity_interaction,
|
||||
int elastic_force, int damping_force,
|
||||
double gravity_strength,
|
||||
double *ax, double *ay, double *az)
|
||||
{
|
||||
// 清零
|
||||
std::fill(ax, ax + n, 0.0);
|
||||
std::fill(ay, ay + n, 0.0);
|
||||
std::fill(az, az + n, 0.0);
|
||||
|
||||
// 均匀重力场
|
||||
if (gravity_field) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
ax[i] += G[0];
|
||||
ay[i] += G[1];
|
||||
az[i] += G[2];
|
||||
}
|
||||
}
|
||||
|
||||
// 阻尼
|
||||
if (damping_force) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
ax[i] -= B[0] * vx[i] / m[i];
|
||||
ay[i] -= B[1] * vy[i] / m[i];
|
||||
az[i] -= B[2] * vz[i] / m[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 弹簧力
|
||||
if (elastic_force) {
|
||||
int nb = static_cast<int>(bonds.stiffness.size());
|
||||
for (int b = 0; b < nb; b++) {
|
||||
int i = bonds.pairs[b * 2];
|
||||
int j = bonds.pairs[b * 2 + 1];
|
||||
double dx = x[j] - x[i];
|
||||
double dy = y[j] - y[i];
|
||||
double dz = z[j] - z[i];
|
||||
double dist = std::sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (dist < 1e-12) continue;
|
||||
double stretch = dist - bonds.rest_lengths[b];
|
||||
double fmag = bonds.stiffness[b] * stretch;
|
||||
double ux = dx / dist, uy = dy / dist, uz = dz / dist;
|
||||
double fx = fmag * ux, fy = fmag * uy, fz = fmag * uz;
|
||||
ax[i] += fx / m[i]; ay[i] += fy / m[i]; az[i] += fz / m[i];
|
||||
ax[j] -= fx / m[j]; ay[j] -= fy / m[j]; az[j] -= fz / m[j];
|
||||
}
|
||||
}
|
||||
|
||||
// 万有引力(所有原子对之间)
|
||||
if (gravity_interaction) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (int j = i + 1; j < n; j++) {
|
||||
double dx = x[j] - x[i];
|
||||
double dy = y[j] - y[i];
|
||||
double dz = z[j] - z[i];
|
||||
double r2 = dx * dx + dy * dy + dz * dz;
|
||||
if (r2 <= 1e-12) continue;
|
||||
double r = std::sqrt(r2);
|
||||
double f_mag = gravity_strength * m[i] * m[j] / r2;
|
||||
double fx_g = f_mag * dx / r;
|
||||
double fy_g = f_mag * dy / r;
|
||||
double fz_g = f_mag * dz / r;
|
||||
ax[i] += fx_g / m[i]; ay[i] += fy_g / m[i]; az[i] += fz_g / m[i];
|
||||
ax[j] -= fx_g / m[j]; ay[j] -= fy_g / m[j]; az[j] -= fz_g / m[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 边界条件:clamp 位置 + 速度反转 ——与 Python Limit_in_box 一致 */
|
||||
static void limit_in_box(double &pos, double &vel, double lo, double hi) {
|
||||
if (pos > hi) { pos = hi; vel = -vel; }
|
||||
if (pos < lo) { pos = lo; vel = -vel; }
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// 四种积分方法(只做位置/速度更新,不含边界条件)
|
||||
// 与 Python: Explicit_Euler_Method / Implicit_Euler_Method /
|
||||
// Midpoint_Method / Leapfrog_Method 保持一致
|
||||
// ========================================================================
|
||||
|
||||
/* ── 显式欧拉法 ──────────── */
|
||||
static void explicit_euler_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const double G[3], const double B[3],
|
||||
const BondData &bonds, const int *fixed, double dt,
|
||||
int gravity_field, int gravity_interaction,
|
||||
int elastic_force, int damping_force,
|
||||
double gravity_strength)
|
||||
{
|
||||
std::vector<double> ax(n), ay(n), az(n);
|
||||
compute_acceleration(n, x, y, z, vx, vy, vz, m, G, B, bonds,
|
||||
gravity_field, gravity_interaction,
|
||||
elastic_force, damping_force, gravity_strength,
|
||||
ax.data(), ay.data(), az.data());
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
x[i] += vx[i] * dt;
|
||||
y[i] += vy[i] * dt;
|
||||
z[i] += vz[i] * dt;
|
||||
vx[i] += ax[i] * dt;
|
||||
vy[i] += ay[i] * dt;
|
||||
vz[i] += az[i] * dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 隐式欧拉法 ──────────── */
|
||||
static void implicit_euler_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const double G[3], const double B[3],
|
||||
const BondData &bonds, const int *fixed, double dt,
|
||||
int gravity_field, int gravity_interaction,
|
||||
int elastic_force, int damping_force,
|
||||
double gravity_strength)
|
||||
{
|
||||
std::vector<double> ax(n), ay(n), az(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) {
|
||||
ax[i] = ay[i] = az[i] = 0;
|
||||
continue;
|
||||
}
|
||||
double gamma_x = B[0] / m[i];
|
||||
double gamma_y = B[1] / m[i];
|
||||
double gamma_z = B[2] / m[i];
|
||||
// 隐式更新速度(重力 + 阻尼)
|
||||
double vxn = (vx[i] + G[0] * dt) / (1.0 + gamma_x * dt);
|
||||
double vyn = (vy[i] + G[1] * dt) / (1.0 + gamma_y * dt);
|
||||
double vzn = (vz[i] + G[2] * dt) / (1.0 + gamma_z * dt);
|
||||
// 用隐式速度 + 当前位置算加速度(包含各力开关)
|
||||
// 注意:Python 中 compute_acceleration(x, y, z, vx_next, ...) 用新速度+旧位置
|
||||
double tpx = x[i], tpy = y[i], tpz = z[i];
|
||||
compute_acceleration(1, &tpx, &tpy, &tpz, &vxn, &vyn, &vzn,
|
||||
&m[i], G, B, bonds,
|
||||
gravity_field, gravity_interaction,
|
||||
elastic_force, damping_force, gravity_strength,
|
||||
&ax[i], &ay[i], &az[i]);
|
||||
vx[i] += ax[i] * dt;
|
||||
vy[i] += ay[i] * dt;
|
||||
vz[i] += az[i] * dt;
|
||||
x[i] += vx[i] * dt;
|
||||
y[i] += vy[i] * dt;
|
||||
z[i] += vz[i] * dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 中点法 ──────────── */
|
||||
static void midpoint_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const double G[3], const double B[3],
|
||||
const BondData &bonds, const int *fixed, double dt,
|
||||
int gravity_field, int gravity_interaction,
|
||||
int elastic_force, int damping_force,
|
||||
double gravity_strength)
|
||||
{
|
||||
std::vector<double> ax(n), ay(n), az(n);
|
||||
compute_acceleration(n, x, y, z, vx, vy, vz, m, G, B, bonds,
|
||||
gravity_field, gravity_interaction,
|
||||
elastic_force, damping_force, gravity_strength,
|
||||
ax.data(), ay.data(), az.data());
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
double x_mid = x[i] + 0.5 * vx[i] * dt;
|
||||
double y_mid = y[i] + 0.5 * vy[i] * dt;
|
||||
double z_mid = z[i] + 0.5 * vz[i] * dt;
|
||||
double vx_mid = vx[i] + 0.5 * ax[i] * dt;
|
||||
double vy_mid = vy[i] + 0.5 * ay[i] * dt;
|
||||
double vz_mid = vz[i] + 0.5 * az[i] * dt;
|
||||
x[i] += vx_mid * dt;
|
||||
y[i] += vy_mid * dt;
|
||||
z[i] += vz_mid * dt;
|
||||
double ax_mid, ay_mid, az_mid;
|
||||
compute_acceleration(1, &x_mid, &y_mid, &z_mid, &vx_mid, &vy_mid, &vz_mid,
|
||||
&m[i], G, B, bonds,
|
||||
gravity_field, gravity_interaction,
|
||||
elastic_force, damping_force, gravity_strength,
|
||||
&ax_mid, &ay_mid, &az_mid);
|
||||
vx[i] += ax_mid * dt;
|
||||
vy[i] += ay_mid * dt;
|
||||
vz[i] += az_mid * dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 蛙跳法(Velocity-Verlet)——与 Python Leapfrog_Method 一致 ── */
|
||||
static void leapfrog_full_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const double G[3], const double B[3],
|
||||
const BondData &bonds, const int *fixed, double dt,
|
||||
int gravity_field, int gravity_interaction,
|
||||
int elastic_force, int damping_force,
|
||||
double gravity_strength)
|
||||
{
|
||||
// 第一次加速度
|
||||
std::vector<double> ax(n), ay(n), az(n);
|
||||
compute_acceleration(n, x, y, z, vx, vy, vz, m, G, B, bonds,
|
||||
gravity_field, gravity_interaction,
|
||||
elastic_force, damping_force, gravity_strength,
|
||||
ax.data(), ay.data(), az.data());
|
||||
|
||||
// 半推速度:v_half = v + 0.5*a*dt (存入 vx, vy, vz)
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += ax[i] * dt * 0.5;
|
||||
vy[i] += ay[i] * dt * 0.5;
|
||||
vz[i] += az[i] * dt * 0.5;
|
||||
}
|
||||
|
||||
// 全推位置(不含边界,边界在外层统一处理)
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
x[i] += vx[i] * dt; // vx 此时是 v_half
|
||||
y[i] += vy[i] * dt;
|
||||
z[i] += vz[i] * dt;
|
||||
}
|
||||
|
||||
// 显式预测器:v_pred = v_half + 0.5*a_old*dt,用第一次加速度外推半步
|
||||
// 包含所有力的贡献(标准 Velocity-Verlet 预测步)
|
||||
std::vector<double> pred_vx(n), pred_vy(n), pred_vz(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
pred_vx[i] = vx[i] + 0.5 * ax[i] * dt;
|
||||
pred_vy[i] = vy[i] + 0.5 * ay[i] * dt;
|
||||
pred_vz[i] = vz[i] + 0.5 * az[i] * dt;
|
||||
}
|
||||
|
||||
// 用新位置 + 预测速度重算加速度
|
||||
compute_acceleration(n, x, y, z, pred_vx.data(), pred_vy.data(), pred_vz.data(),
|
||||
m, G, B, bonds,
|
||||
gravity_field, gravity_interaction,
|
||||
elastic_force, damping_force, gravity_strength,
|
||||
ax.data(), ay.data(), az.data());
|
||||
|
||||
// 速度后半步:v = v_half + 0.5*a_next*dt
|
||||
// vx 仍为 v_half(未被覆盖),直接加上 0.5*a_next*dt
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += ax[i] * dt * 0.5;
|
||||
vy[i] += ay[i] * dt * 0.5;
|
||||
vz[i] += az[i] * dt * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 分发器:调用对应积分方法 + 边界条件(与 Python apply_motion_update 一致)── */
|
||||
static void apply_step(
|
||||
const std::string &method,
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const double G[3], const double B[3],
|
||||
const BondData &bonds, const int *fixed,
|
||||
const double *pos_0,
|
||||
double box_a, double dt,
|
||||
int gravity_field, int gravity_interaction,
|
||||
int elastic_force, int damping_force,
|
||||
double gravity_strength)
|
||||
{
|
||||
// 积分
|
||||
if (method == "explicit_euler") {
|
||||
explicit_euler_step(n, x, y, z, vx, vy, vz, m, G, B, bonds, fixed, dt,
|
||||
gravity_field, gravity_interaction,
|
||||
elastic_force, damping_force, gravity_strength);
|
||||
} else if (method == "implicit_euler") {
|
||||
implicit_euler_step(n, x, y, z, vx, vy, vz, m, G, B, bonds, fixed, dt,
|
||||
gravity_field, gravity_interaction,
|
||||
elastic_force, damping_force, gravity_strength);
|
||||
} else if (method == "midpoint") {
|
||||
midpoint_step(n, x, y, z, vx, vy, vz, m, G, B, bonds, fixed, dt,
|
||||
gravity_field, gravity_interaction,
|
||||
elastic_force, damping_force, gravity_strength);
|
||||
} else if (method == "leapfrog") {
|
||||
leapfrog_full_step(n, x, y, z, vx, vy, vz, m, G, B, bonds, fixed, dt,
|
||||
gravity_field, gravity_interaction,
|
||||
elastic_force, damping_force, gravity_strength);
|
||||
} else {
|
||||
die("未知算法: " + method);
|
||||
}
|
||||
|
||||
// 边界条件(与 Python Limit_in_box 一致)
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
limit_in_box(x[i], vx[i], -box_a, box_a);
|
||||
limit_in_box(y[i], vy[i], -box_a, box_a);
|
||||
limit_in_box(z[i], vz[i], -box_a, box_a);
|
||||
}
|
||||
|
||||
// 逐自由度固定约束(与 Python apply_fixed_constraints 一致)
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0]) { x[i] = pos_0[i*3]; vx[i] = 0.0; }
|
||||
if (fixed[i*3+1]) { y[i] = pos_0[i*3+1]; vy[i] = 0.0; }
|
||||
if (fixed[i*3+2]) { z[i] = pos_0[i*3+2]; vz[i] = 0.0; }
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// 驱动力应用
|
||||
// ========================================================================
|
||||
|
||||
static void apply_driving_force(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
double t, int step, double dt,
|
||||
DriverData &drivers)
|
||||
{
|
||||
if (drivers.n_drivers == 0) return;
|
||||
for (int d = 0; d < drivers.n_drivers; d++) {
|
||||
int idx = drivers.atom_idx[d];
|
||||
|
||||
// Check period limits
|
||||
if (drivers.has_period[d]) {
|
||||
double max_freq = std::max({std::fabs(drivers.freq_x[d]),
|
||||
std::fabs(drivers.freq_y[d]),
|
||||
std::fabs(drivers.freq_z[d])});
|
||||
int period_steps = 0;
|
||||
if (max_freq > 1e-12) {
|
||||
period_steps = static_cast<int>(drivers.period_cycles[d] / max_freq / dt);
|
||||
}
|
||||
if (step > period_steps) {
|
||||
// Frozen: keep last position, zero velocity
|
||||
x[idx] = drivers.freeze_x[d];
|
||||
y[idx] = drivers.freeze_y[d];
|
||||
z[idx] = drivers.freeze_z[d];
|
||||
vx[idx] = vy[idx] = vz[idx] = 0.0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const double TWO_PI = 2.0 * M_PI;
|
||||
double px = drivers.amp_x[d] * std::cos(TWO_PI * drivers.freq_x[d] * t + drivers.phi_x[d]);
|
||||
double py = drivers.amp_y[d] * std::cos(TWO_PI * drivers.freq_y[d] * t + drivers.phi_y[d]);
|
||||
double pz = drivers.amp_z[d] * std::cos(TWO_PI * drivers.freq_z[d] * t + drivers.phi_z[d]);
|
||||
double vpx = -drivers.amp_x[d] * TWO_PI * drivers.freq_x[d] * std::sin(TWO_PI * drivers.freq_x[d] * t + drivers.phi_x[d]);
|
||||
double vpy = -drivers.amp_y[d] * TWO_PI * drivers.freq_y[d] * std::sin(TWO_PI * drivers.freq_y[d] * t + drivers.phi_y[d]);
|
||||
double vpz = -drivers.amp_z[d] * TWO_PI * drivers.freq_z[d] * std::sin(TWO_PI * drivers.freq_z[d] * t + drivers.phi_z[d]);
|
||||
|
||||
x[idx] = px; y[idx] = py; z[idx] = pz;
|
||||
vx[idx] = vpx; vy[idx] = vpy; vz[idx] = vpz;
|
||||
|
||||
// Record freeze position at the last driving step
|
||||
if (drivers.has_period[d]) {
|
||||
double max_freq = std::max({std::fabs(drivers.freq_x[d]),
|
||||
std::fabs(drivers.freq_y[d]),
|
||||
std::fabs(drivers.freq_z[d])});
|
||||
int period_steps = 0;
|
||||
if (max_freq > 1e-12) {
|
||||
period_steps = static_cast<int>(drivers.period_cycles[d] / max_freq / dt);
|
||||
}
|
||||
if (step == period_steps) {
|
||||
drivers.freeze_x[d] = px;
|
||||
drivers.freeze_y[d] = py;
|
||||
drivers.freeze_z[d] = pz;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// JSON 输出
|
||||
// ========================================================================
|
||||
|
||||
static void write_trajectory_json(
|
||||
const std::string &path,
|
||||
const std::vector<double> &x, const std::vector<double> &y,
|
||||
const std::vector<double> &z, const std::vector<double> &vx,
|
||||
const std::vector<double> &vy, const std::vector<double> &vz,
|
||||
int n_steps, int n_atoms,
|
||||
const SimParams ¶ms, const AtomData &atoms, const BondData &bonds)
|
||||
{
|
||||
std::ofstream f(path);
|
||||
if (!f) die("无法写入 " + path);
|
||||
f << std::setprecision(15);
|
||||
|
||||
f << "{\n";
|
||||
|
||||
// 轨迹数组
|
||||
const std::string names[] = {"traj_x","traj_y","traj_z","traj_vx","traj_vy","traj_vz"};
|
||||
const std::vector<double> *arrs[] = {&x, &y, &z, &vx, &vy, &vz};
|
||||
|
||||
for (int a = 0; a < 6; a++) {
|
||||
f << " \"" << names[a] << "\": [\n";
|
||||
const auto &data = *arrs[a];
|
||||
for (int t = 0; t < n_steps; t++) {
|
||||
f << " [";
|
||||
for (int i = 0; i < n_atoms; i++) {
|
||||
f << data[t * n_atoms + i];
|
||||
if (i < n_atoms - 1) f << ',';
|
||||
}
|
||||
f << ']';
|
||||
if (t < n_steps - 1) f << ',';
|
||||
f << '\n';
|
||||
}
|
||||
f << " ],\n";
|
||||
}
|
||||
|
||||
// 标量参数
|
||||
f << " \"NT\": " << params.NT << ",\n";
|
||||
f << " \"DT\": " << params.DT << ",\n";
|
||||
f << " \"NSTEP\": " << params.NSTEP << ",\n";
|
||||
f << " \"method\": \"" << params.method << "\",\n";
|
||||
f << " \"warmup_steps\": " << params.warmup_steps << ",\n";
|
||||
f << " \"G\": [" << params.G[0] << ", " << params.G[1] << ", " << params.G[2] << "],\n";
|
||||
f << " \"B\": [" << params.B[0] << ", " << params.B[1] << ", " << params.B[2] << "],\n";
|
||||
|
||||
// 原子信息
|
||||
f << " \"atom_ids\": [";
|
||||
for (size_t i = 0; i < atoms.ids.size(); i++) {
|
||||
if (i > 0) f << ',';
|
||||
f << atoms.ids[i];
|
||||
}
|
||||
f << "],\n";
|
||||
|
||||
f << " \"atom_masses\": [";
|
||||
for (size_t i = 0; i < atoms.masses.size(); i++) {
|
||||
if (i > 0) f << ',';
|
||||
f << atoms.masses[i];
|
||||
}
|
||||
f << "],\n";
|
||||
|
||||
// 成键
|
||||
f << " \"bond_pairs\": [";
|
||||
for (size_t b = 0; b < bonds.stiffness.size(); b++) {
|
||||
if (b > 0) f << ',';
|
||||
f << "[" << bonds.pairs[b * 2] << ", " << bonds.pairs[b * 2 + 1] << "]";
|
||||
}
|
||||
f << "],\n";
|
||||
|
||||
f << " \"bond_stiffness\": [";
|
||||
for (size_t b = 0; b < bonds.stiffness.size(); b++) {
|
||||
if (b > 0) f << ',';
|
||||
f << bonds.stiffness[b];
|
||||
}
|
||||
f << "],\n";
|
||||
|
||||
f << " \"bond_rest_lengths\": [";
|
||||
for (size_t b = 0; b < bonds.rest_lengths.size(); b++) {
|
||||
if (b > 0) f << ',';
|
||||
f << bonds.rest_lengths[b];
|
||||
}
|
||||
f << "],\n";
|
||||
|
||||
f << " \"driving_force\": " << params.driving_force << "\n";
|
||||
|
||||
f << "}\n";
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// 主函数
|
||||
// ========================================================================
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 4) {
|
||||
std::cerr << "用法: " << argv[0] << " <input_dir> <output_dir> <param_json>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string input_dir = argv[1];
|
||||
std::string output_dir = argv[2];
|
||||
std::string param_path = argv[3];
|
||||
|
||||
auto t0 = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// 读取参数和输入
|
||||
SimParams params = read_params(param_path);
|
||||
AtomData atoms = read_coord(input_dir);
|
||||
BondData bonds = read_bonds(input_dir);
|
||||
|
||||
DriverData drivers;
|
||||
if (params.driving_force) {
|
||||
drivers = read_driver(input_dir, atoms);
|
||||
}
|
||||
|
||||
std::cout << "[C++-engine] 原子数=" << atoms.ids.size()
|
||||
<< ", 键数=" << bonds.stiffness.size()
|
||||
<< ", NT=" << params.NT << ", DT=" << params.DT
|
||||
<< ", method=" << params.method << std::endl;
|
||||
|
||||
int n = static_cast<int>(atoms.ids.size());
|
||||
|
||||
// 初始化位置/速度
|
||||
std::vector<double> x(n), y(n), z(n), vx(n), vy(n), vz(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
x[i] = atoms.pos_0[i * 3];
|
||||
y[i] = atoms.pos_0[i * 3 + 1];
|
||||
z[i] = atoms.pos_0[i * 3 + 2];
|
||||
vx[i] = atoms.vel_0[i * 3];
|
||||
vy[i] = atoms.vel_0[i * 3 + 1];
|
||||
vz[i] = atoms.vel_0[i * 3 + 2];
|
||||
}
|
||||
|
||||
// 分配轨迹缓冲区
|
||||
int record_steps = params.NT - params.warmup_steps;
|
||||
std::vector<double> traj_x(record_steps * n);
|
||||
std::vector<double> traj_y(record_steps * n);
|
||||
std::vector<double> traj_z(record_steps * n);
|
||||
std::vector<double> traj_vx(record_steps * n);
|
||||
std::vector<double> traj_vy(record_steps * n);
|
||||
std::vector<double> traj_vz(record_steps * n);
|
||||
|
||||
// 预热
|
||||
for (int s = 0; s < params.warmup_steps; s++) {
|
||||
double tw = (s + 1) * params.DT;
|
||||
if (params.driving_force)
|
||||
apply_driving_force(n, x.data(), y.data(), z.data(), vx.data(), vy.data(), vz.data(), tw, s, params.DT, drivers);
|
||||
apply_step(params.method, n, x.data(), y.data(), z.data(),
|
||||
vx.data(), vy.data(), vz.data(),
|
||||
atoms.masses.data(), params.G, params.B,
|
||||
bonds, atoms.fixed.data(),
|
||||
atoms.pos_0.data(),
|
||||
params.box_a, params.DT,
|
||||
params.gravity_field, params.gravity_interaction,
|
||||
params.elastic_force, params.damping_force, params.gravity_strength);
|
||||
}
|
||||
|
||||
// 记录
|
||||
for (int s = 0; s < record_steps; s++) {
|
||||
double t = (s + params.warmup_steps) * params.DT;
|
||||
if (params.driving_force)
|
||||
apply_driving_force(n, x.data(), y.data(), z.data(), vx.data(), vy.data(), vz.data(), t, s, params.DT, drivers);
|
||||
// 保存当前帧
|
||||
for (int i = 0; i < n; i++) {
|
||||
traj_x[s * n + i] = x[i];
|
||||
traj_y[s * n + i] = y[i];
|
||||
traj_z[s * n + i] = z[i];
|
||||
traj_vx[s * n + i] = vx[i];
|
||||
traj_vy[s * n + i] = vy[i];
|
||||
traj_vz[s * n + i] = vz[i];
|
||||
}
|
||||
|
||||
apply_step(params.method, n, x.data(), y.data(), z.data(),
|
||||
vx.data(), vy.data(), vz.data(),
|
||||
atoms.masses.data(), params.G, params.B,
|
||||
bonds, atoms.fixed.data(),
|
||||
atoms.pos_0.data(),
|
||||
params.box_a, params.DT,
|
||||
params.gravity_field, params.gravity_interaction,
|
||||
params.elastic_force, params.damping_force, params.gravity_strength);
|
||||
}
|
||||
|
||||
// 输出轨迹
|
||||
std::string out_path = output_dir + "/trajectory.txt";
|
||||
write_trajectory_json(out_path, traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz,
|
||||
record_steps, n, params, atoms, bonds);
|
||||
|
||||
auto t1 = std::chrono::high_resolution_clock::now();
|
||||
double elapsed = std::chrono::duration<double>(t1 - t0).count();
|
||||
std::cout << "[C++-engine] 计算完成: " << record_steps << " 步, " << elapsed << " s" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
"""
|
||||
engines/engine_dll.py
|
||||
---------------------
|
||||
Python ctypes 包装器:加载 C/C++/Fortran 动态链接库并调用 run_dynamics()。
|
||||
|
||||
用法(由 compute.py 内部调用,不直接运行):
|
||||
|
||||
from engines.engine_dll import load_dll, run_dynamics_dll
|
||||
|
||||
dll = load_dll("c") # 自动查找 engines/c/build/dynamics_c.dll/.so/.dylib
|
||||
arrays = run_dynamics_dll(dll, config, atom_data, bond_data, driver_data)
|
||||
# arrays: dict with keys x, y, z, vx, vy, vz shape=(n_frames, n_atoms)
|
||||
|
||||
DLL 编译(C 版本):
|
||||
Windows: gcc -O3 -shared -o engines/release/dynamics_c.dll engines/src/c/dynamics_lib.c -lm
|
||||
Linux: gcc -O3 -shared -fPIC -o engines/release/dynamics_c.so engines/src/c/dynamics_lib.c -lm
|
||||
macOS: gcc -O3 -dynamiclib -o engines/release/dynamics_c.dylib engines/src/c/dynamics_lib.c -lm
|
||||
或用 make dll 一键编译:
|
||||
cd engines/src/c && make dll
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import os
|
||||
import platform
|
||||
import numpy as np
|
||||
|
||||
# ── DLL 文件名后缀 ─────────────────────────────────────────────
|
||||
_SUFFIX = {
|
||||
"windows": ".dll",
|
||||
"linux": ".so",
|
||||
"darwin": ".dylib",
|
||||
}
|
||||
|
||||
# ── method 字符串 → 整数 ID ────────────────────────────────────
|
||||
_METHOD_ID = {
|
||||
"explicit_euler": 0,
|
||||
"euler": 0,
|
||||
"implicit_euler": 1,
|
||||
"midpoint": 2,
|
||||
"leapfrog": 3,
|
||||
}
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
_DLL_NAME = {
|
||||
"c": "dynamics_c",
|
||||
"cpp": "dynamics_cpp",
|
||||
"c++": "dynamics_cpp",
|
||||
"fortran": "dynamics_f90",
|
||||
"f90": "dynamics_f90",
|
||||
# "python" 引擎通过直接 import 调用,不使用 DLL
|
||||
}
|
||||
|
||||
# 引擎名规范化:将别名统一为目录名
|
||||
_ENGINE_DIR = {
|
||||
"c": "c",
|
||||
"cpp": "cpp",
|
||||
"c++": "cpp",
|
||||
"fortran": "fortran",
|
||||
"f90": "fortran",
|
||||
"python": "python",
|
||||
}
|
||||
|
||||
|
||||
def _dll_candidates(engine: str) -> list[str]:
|
||||
"""返回 DLL 候选路径列表(按优先级)。"""
|
||||
sys = platform.system().lower()
|
||||
ext = _SUFFIX.get(sys, ".so")
|
||||
eng_dir = _ENGINE_DIR.get(engine, engine)
|
||||
name = _DLL_NAME.get(engine, f"dynamics_{engine}")
|
||||
base = os.path.join(_HERE, "release", name)
|
||||
return [
|
||||
base + ext,
|
||||
base + ".dll",
|
||||
base + ".so",
|
||||
base + ".dylib",
|
||||
]
|
||||
|
||||
|
||||
def load_dll(engine: str = "c"):
|
||||
"""加载指定引擎。
|
||||
|
||||
- C/C++/Fortran: 返回 ctypes.CDLL 对象
|
||||
- Python: 返回模块对象(直接 import,无需编译)
|
||||
|
||||
Args:
|
||||
engine: "c", "cpp", "fortran", 或 "python"
|
||||
Raises:
|
||||
FileNotFoundError: DLL/模块文件不存在
|
||||
"""
|
||||
if _ENGINE_DIR.get(engine, engine) == "python":
|
||||
import importlib.util, sys as _sys
|
||||
mod_path = os.path.join(_HERE, "python", "dynamics_lib.py")
|
||||
if not os.path.exists(mod_path):
|
||||
raise FileNotFoundError(f"Python 引擎未找到: {mod_path}")
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"engines.python.dynamics_lib", mod_path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod # 返回模块,不是 CDLL
|
||||
|
||||
for p in _dll_candidates(engine):
|
||||
if os.path.exists(p):
|
||||
lib = ctypes.CDLL(p)
|
||||
_setup_prototype(lib)
|
||||
return lib
|
||||
raise FileNotFoundError(
|
||||
f"DLL 未找到(引擎 {engine}),候选路径:\n" +
|
||||
"\n".join(f" {p}" for p in _dll_candidates(engine)) +
|
||||
f"\n请先编译:cd engines/{engine} && make dll"
|
||||
)
|
||||
|
||||
|
||||
def _setup_prototype(lib: ctypes.CDLL) -> None:
|
||||
"""配置 run_dynamics 的参数类型和返回类型。"""
|
||||
c_dbl_p = ctypes.POINTER(ctypes.c_double)
|
||||
c_int_p = ctypes.POINTER(ctypes.c_int)
|
||||
cb_type = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_int)
|
||||
|
||||
lib.run_dynamics.restype = ctypes.c_int
|
||||
lib.run_dynamics.argtypes = [
|
||||
ctypes.c_int, # n_atoms
|
||||
c_dbl_p, # pos_init [n_atoms*3]
|
||||
c_dbl_p, # vel_init [n_atoms*3]
|
||||
c_dbl_p, # masses [n_atoms]
|
||||
c_int_p, # fixed [n_atoms*3]
|
||||
ctypes.c_int, # n_bonds
|
||||
c_int_p, # bond_pairs [n_bonds*2]
|
||||
c_dbl_p, # bond_k [n_bonds]
|
||||
c_dbl_p, # bond_r0 [n_bonds]
|
||||
ctypes.c_double, # box_a
|
||||
ctypes.c_double, # dt
|
||||
ctypes.c_int, # NT
|
||||
ctypes.c_int, # NSTEP
|
||||
ctypes.c_int, # warmup_steps
|
||||
ctypes.c_int, # method_id
|
||||
ctypes.c_double, # Gx
|
||||
ctypes.c_double, # Gy
|
||||
ctypes.c_double, # Gz
|
||||
ctypes.c_double, # Bx
|
||||
ctypes.c_double, # By
|
||||
ctypes.c_double, # Bz
|
||||
ctypes.c_int, # gravity_field
|
||||
ctypes.c_int, # elastic_force
|
||||
ctypes.c_int, # damping_force
|
||||
ctypes.c_double, # gravity_strength
|
||||
ctypes.c_int, # n_drivers
|
||||
c_int_p, # drv_idx [n_drivers]
|
||||
c_dbl_p, # drv_amp [n_drivers*3]
|
||||
c_dbl_p, # drv_freq [n_drivers*3]
|
||||
c_dbl_p, # drv_phi [n_drivers*3]
|
||||
c_dbl_p, # drv_eq [n_drivers*3]
|
||||
c_dbl_p, # drv_ncycles [n_drivers]
|
||||
c_int_p, # drv_has_period [n_drivers]
|
||||
ctypes.c_int, # n_frames
|
||||
c_dbl_p, # out_x
|
||||
c_dbl_p, # out_y
|
||||
c_dbl_p, # out_z
|
||||
c_dbl_p, # out_vx
|
||||
c_dbl_p, # out_vy
|
||||
c_dbl_p, # out_vz
|
||||
cb_type, # progress_cb (可为 NULL)
|
||||
]
|
||||
|
||||
|
||||
def _c_dbl(arr: np.ndarray):
|
||||
"""返回 float64 C 连续数组的 ctypes 指针。"""
|
||||
a = np.ascontiguousarray(arr, dtype=np.float64)
|
||||
return a.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), a
|
||||
|
||||
|
||||
def _c_int(arr: np.ndarray):
|
||||
"""返回 int32 C 连续数组的 ctypes 指针。"""
|
||||
a = np.ascontiguousarray(arr, dtype=np.int32)
|
||||
return a.ctypes.data_as(ctypes.POINTER(ctypes.c_int)), a
|
||||
|
||||
|
||||
def _is_python_module(lib) -> bool:
|
||||
"""判断 lib 是否为 Python 引擎模块(而非 ctypes.CDLL)。"""
|
||||
return not isinstance(lib, ctypes.CDLL)
|
||||
|
||||
|
||||
def _run_dynamics_python(lib, config, atom_positions, atom_velocities, atom_masses,
|
||||
atom_fixed, bond_pairs, bond_stiffness, bond_rest_lengths,
|
||||
driver_data, atom_ids, progress_cb=None) -> dict:
|
||||
"""调用 Python 引擎的 run_dynamics(),参数/返回值格式与 ctypes 版相同。"""
|
||||
n = len(atom_masses)
|
||||
NT = int(config["NT"])
|
||||
NSTEP = int(config.get("NSTEP", 1))
|
||||
warmup = int(config.get("warmup_steps", 0))
|
||||
dt = float(config["DT"])
|
||||
box_a = float(config.get("box_a", 300.0))
|
||||
method_str = str(config.get("method", "leapfrog")).lower().replace(" ", "_")
|
||||
method_id = _METHOD_ID.get(method_str, 3)
|
||||
|
||||
G = config.get("G", [0.0, 0.0, 0.0])
|
||||
B = config.get("B", [0.0, 0.0, 0.0])
|
||||
if hasattr(G, "tolist"): G = G.tolist()
|
||||
if hasattr(B, "tolist"): B = B.tolist()
|
||||
|
||||
gravity_field = int(config.get("gravity_field", 0))
|
||||
elastic_force = int(config.get("elastic_force", 1))
|
||||
damping_force = int(config.get("damping_force", 0))
|
||||
gravity_strength = float(config.get("gravity_strength", 1.0))
|
||||
|
||||
record_steps = NT - warmup
|
||||
n_frames = max(1, record_steps // NSTEP)
|
||||
|
||||
nd = len(driver_data) if driver_data else 0
|
||||
if nd > 0:
|
||||
drv_idx = np.array([d["local_idx"] for d in driver_data], dtype=np.int64)
|
||||
drv_amp = np.array([d["amp"] for d in driver_data], dtype=np.float64)
|
||||
drv_freq = np.array([d["freq"] for d in driver_data], dtype=np.float64)
|
||||
drv_phi = np.array([d["phi"] for d in driver_data], dtype=np.float64)
|
||||
drv_eq = np.array([d["eq_pos"] for d in driver_data], dtype=np.float64)
|
||||
drv_nc = np.array([d["n_cycles"] for d in driver_data], dtype=np.float64)
|
||||
drv_hp = np.array([d["has_period"]for d in driver_data], dtype=np.int32)
|
||||
else:
|
||||
drv_idx = drv_amp = drv_freq = drv_phi = drv_eq = drv_nc = drv_hp = \
|
||||
np.zeros(0, dtype=np.int64)
|
||||
|
||||
out_x, out_y, out_z, out_vx, out_vy, out_vz = lib.run_dynamics(
|
||||
n_atoms=n,
|
||||
pos_init=atom_positions,
|
||||
vel_init=atom_velocities,
|
||||
masses=atom_masses,
|
||||
fixed=atom_fixed,
|
||||
n_bonds=len(bond_pairs),
|
||||
bond_pairs=bond_pairs,
|
||||
bond_k=bond_stiffness,
|
||||
bond_r0=bond_rest_lengths,
|
||||
box_a=box_a,
|
||||
dt=dt,
|
||||
NT=NT,
|
||||
NSTEP=NSTEP,
|
||||
warmup_steps=warmup,
|
||||
method_id=method_id,
|
||||
Gx=float(G[0]), Gy=float(G[1]), Gz=float(G[2]),
|
||||
Bx=float(B[0]), By=float(B[1]), Bz=float(B[2]),
|
||||
gravity_field=gravity_field,
|
||||
elastic_force=elastic_force,
|
||||
damping_force=damping_force,
|
||||
gravity_strength=gravity_strength,
|
||||
n_drivers=nd,
|
||||
drv_idx=drv_idx,
|
||||
drv_amp=drv_amp,
|
||||
drv_freq=drv_freq,
|
||||
drv_phi=drv_phi,
|
||||
drv_eq=drv_eq,
|
||||
drv_ncycles=drv_nc,
|
||||
drv_has_period=drv_hp,
|
||||
n_frames=n_frames,
|
||||
progress_cb=progress_cb,
|
||||
)
|
||||
|
||||
shape = (n_frames, n)
|
||||
t_arr = np.arange(n_frames) * NSTEP * dt + warmup * dt
|
||||
return {
|
||||
"x": out_x.reshape(shape), "y": out_y.reshape(shape),
|
||||
"z": out_z.reshape(shape), "vx": out_vx.reshape(shape),
|
||||
"vy": out_vy.reshape(shape), "vz": out_vz.reshape(shape),
|
||||
"t": t_arr,
|
||||
}
|
||||
|
||||
|
||||
def run_dynamics_dll(
|
||||
lib,
|
||||
config: dict,
|
||||
atom_positions: np.ndarray, # (n_atoms, 3)
|
||||
atom_velocities: np.ndarray, # (n_atoms, 3)
|
||||
atom_masses: np.ndarray, # (n_atoms,)
|
||||
atom_fixed: np.ndarray, # (n_atoms, 3) int, 1=固定
|
||||
bond_pairs: np.ndarray, # (n_bonds, 2) int 0-based 局部索引
|
||||
bond_stiffness: np.ndarray, # (n_bonds,)
|
||||
bond_rest_lengths: np.ndarray,# (n_bonds,)
|
||||
driver_data: list, # 驱动原子列表(见下文)
|
||||
atom_ids: np.ndarray, # (n_atoms,) 全局 atom id(用于驱动原子查找)
|
||||
progress_cb=None,
|
||||
) -> dict:
|
||||
"""调用 DLL 的 run_dynamics(),返回抽帧后的轨迹数组。
|
||||
|
||||
driver_data 格式(每个元素对应一个驱动原子):
|
||||
{
|
||||
"atom_id": int, # 全局 atom id
|
||||
"local_idx": int, # 在 atom_ids 数组中的位置(0-based)
|
||||
"amp": [ax, ay, az],
|
||||
"freq": [fx, fy, fz],
|
||||
"phi": [px, py, pz],
|
||||
"eq_pos": [ex, ey, ez],
|
||||
"n_cycles": float, # 0=不限
|
||||
"has_period": int, # 0/1
|
||||
}
|
||||
|
||||
返回:
|
||||
{
|
||||
"x": np.ndarray (n_frames, n_atoms),
|
||||
"y": ...,
|
||||
"z": ...,
|
||||
"vx": ..., "vy": ..., "vz": ...,
|
||||
"t": np.ndarray (n_frames,), # 时间轴
|
||||
}
|
||||
"""
|
||||
# Python 引擎:直接调用模块函数,不走 ctypes
|
||||
if _is_python_module(lib):
|
||||
return _run_dynamics_python(
|
||||
lib, config, atom_positions, atom_velocities, atom_masses,
|
||||
atom_fixed, bond_pairs, bond_stiffness, bond_rest_lengths,
|
||||
driver_data, atom_ids, progress_cb)
|
||||
|
||||
n = len(atom_masses)
|
||||
NT = int(config["NT"])
|
||||
NSTEP = int(config.get("NSTEP", 1))
|
||||
warmup = int(config.get("warmup_steps", 0))
|
||||
dt = float(config["DT"])
|
||||
box_a = float(config.get("box_a", 300.0))
|
||||
method_str = str(config.get("method", "leapfrog")).lower().replace(" ", "_")
|
||||
method_id = _METHOD_ID.get(method_str, 3)
|
||||
|
||||
G = config.get("G", [0.0, 0.0, 0.0])
|
||||
B = config.get("B", [0.0, 0.0, 0.0])
|
||||
if hasattr(G, "tolist"): G = G.tolist()
|
||||
if hasattr(B, "tolist"): B = B.tolist()
|
||||
|
||||
gravity_field = int(config.get("gravity_field", 0))
|
||||
elastic_force = int(config.get("elastic_force", 1))
|
||||
damping_force = int(config.get("damping_force", 0))
|
||||
gravity_strength = float(config.get("gravity_strength", 1.0))
|
||||
|
||||
# ── 计算帧数 ──────────────────────────────────────────────
|
||||
record_steps = NT - warmup
|
||||
n_frames = max(1, record_steps // NSTEP)
|
||||
|
||||
# ── 驱动原子数据 ──────────────────────────────────────────
|
||||
nd = len(driver_data) if driver_data else 0
|
||||
if nd > 0:
|
||||
drv_idx_arr = np.array([d["local_idx"] for d in driver_data], dtype=np.int32)
|
||||
drv_amp_arr = np.array([d["amp"] for d in driver_data], dtype=np.float64).ravel()
|
||||
drv_freq_arr = np.array([d["freq"] for d in driver_data], dtype=np.float64).ravel()
|
||||
drv_phi_arr = np.array([d["phi"] for d in driver_data], dtype=np.float64).ravel()
|
||||
drv_eq_arr = np.array([d["eq_pos"] for d in driver_data], dtype=np.float64).ravel()
|
||||
drv_nc_arr = np.array([d["n_cycles"] for d in driver_data], dtype=np.float64)
|
||||
drv_hp_arr = np.array([d["has_period"] for d in driver_data], dtype=np.int32)
|
||||
else:
|
||||
drv_idx_arr = np.zeros(1, dtype=np.int32)
|
||||
drv_amp_arr = np.zeros(3, dtype=np.float64)
|
||||
drv_freq_arr = np.zeros(3, dtype=np.float64)
|
||||
drv_phi_arr = np.zeros(3, dtype=np.float64)
|
||||
drv_eq_arr = np.zeros(3, dtype=np.float64)
|
||||
drv_nc_arr = np.zeros(1, dtype=np.float64)
|
||||
drv_hp_arr = np.zeros(1, dtype=np.int32)
|
||||
|
||||
# ── 输出缓冲区 ────────────────────────────────────────────
|
||||
out_x = np.zeros(n_frames * n, dtype=np.float64)
|
||||
out_y = np.zeros(n_frames * n, dtype=np.float64)
|
||||
out_z = np.zeros(n_frames * n, dtype=np.float64)
|
||||
out_vx = np.zeros(n_frames * n, dtype=np.float64)
|
||||
out_vy = np.zeros(n_frames * n, dtype=np.float64)
|
||||
out_vz = np.zeros(n_frames * n, dtype=np.float64)
|
||||
|
||||
# ── ctypes 指针(保留 arr 引用防止 GC) ──────────────────
|
||||
p_pos, _pos = _c_dbl(atom_positions.ravel())
|
||||
p_vel, _vel = _c_dbl(atom_velocities.ravel())
|
||||
p_mass, _mass = _c_dbl(atom_masses)
|
||||
p_fixed, _fixed = _c_int(atom_fixed.ravel())
|
||||
p_bp, _bp = _c_int(bond_pairs.ravel() if len(bond_pairs) else np.zeros(2, dtype=np.int32))
|
||||
p_bk, _bk = _c_dbl(bond_stiffness if len(bond_stiffness) else np.zeros(1))
|
||||
p_br0, _br0 = _c_dbl(bond_rest_lengths if len(bond_rest_lengths) else np.zeros(1))
|
||||
p_didx, _didx = _c_int(drv_idx_arr)
|
||||
p_damp, _damp = _c_dbl(drv_amp_arr)
|
||||
p_dfrq, _dfrq = _c_dbl(drv_freq_arr)
|
||||
p_dphi, _dphi = _c_dbl(drv_phi_arr)
|
||||
p_deq, _deq = _c_dbl(drv_eq_arr)
|
||||
p_dnc, _dnc = _c_dbl(drv_nc_arr)
|
||||
p_dhp, _dhp = _c_int(drv_hp_arr)
|
||||
|
||||
p_ox = out_x.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||
p_oy = out_y.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||
p_oz = out_z.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||
p_ovx = out_vx.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||
p_ovy = out_vy.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||
p_ovz = out_vz.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||
|
||||
# 进度回调
|
||||
cb_type = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_int)
|
||||
if progress_cb is not None:
|
||||
cb = cb_type(progress_cb)
|
||||
else:
|
||||
cb = ctypes.cast(None, cb_type)
|
||||
|
||||
ret = lib.run_dynamics(
|
||||
n,
|
||||
p_pos, p_vel, p_mass, p_fixed,
|
||||
len(bond_pairs), p_bp, p_bk, p_br0,
|
||||
box_a, dt, NT, NSTEP, warmup, method_id,
|
||||
float(G[0]), float(G[1]), float(G[2]),
|
||||
float(B[0]), float(B[1]), float(B[2]),
|
||||
gravity_field, elastic_force, damping_force, gravity_strength,
|
||||
nd, p_didx, p_damp, p_dfrq, p_dphi, p_deq, p_dnc, p_dhp,
|
||||
n_frames,
|
||||
p_ox, p_oy, p_oz, p_ovx, p_ovy, p_ovz,
|
||||
cb,
|
||||
)
|
||||
|
||||
if ret != 0:
|
||||
raise RuntimeError(f"run_dynamics() returned error code {ret}")
|
||||
|
||||
shape = (n_frames, n)
|
||||
t_arr = np.arange(n_frames) * NSTEP * dt + warmup * dt
|
||||
|
||||
return {
|
||||
"x": out_x.reshape(shape),
|
||||
"y": out_y.reshape(shape),
|
||||
"z": out_z.reshape(shape),
|
||||
"vx": out_vx.reshape(shape),
|
||||
"vy": out_vy.reshape(shape),
|
||||
"vz": out_vz.reshape(shape),
|
||||
"t": t_arr,
|
||||
}
|
||||
|
||||
|
||||
def is_dll_available(engine: str = "c") -> bool:
|
||||
"""检查指定引擎是否可用(DLL 已编译或 Python 模块存在)。"""
|
||||
if _ENGINE_DIR.get(engine, engine) == "python":
|
||||
return os.path.exists(os.path.join(_HERE, "python", "dynamics_lib.py"))
|
||||
return any(os.path.exists(p) for p in _dll_candidates(engine))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,404 @@
|
||||
"""
|
||||
engines/python/dynamics_lib.py
|
||||
-------------------------------
|
||||
纯 NumPy 计算引擎:无文件 I/O,所有数据以 NumPy 数组传入,
|
||||
结果作为 NumPy 数组返回。
|
||||
|
||||
接口与 C/C++/Fortran DLL 的 run_dynamics() 完全一致,
|
||||
算法与 compute.py 的 run_simulation() 保持一致。
|
||||
|
||||
用法(由 engine_dll.py 内部调用):
|
||||
from engines.python.dynamics_lib import run_dynamics
|
||||
out_x, out_y, out_z, out_vx, out_vy, out_vz = run_dynamics(...)
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
TWO_PI = 2.0 * np.pi
|
||||
|
||||
# ── method_id 映射 ──────────────────────────────────────────
|
||||
# 0=euler 1=implicit_euler 2=midpoint 3=leapfrog
|
||||
|
||||
|
||||
# ── 保守加速度(弹簧键 + 均匀重力场,不含阻尼)──────────────
|
||||
def _accel_conservative(x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
bond_pairs, bond_k, bond_r0):
|
||||
ax = np.full_like(x, Gx) if gravity_field else np.zeros_like(x)
|
||||
ay = np.full_like(y, Gy) if gravity_field else np.zeros_like(y)
|
||||
az = np.full_like(z, Gz) if gravity_field else np.zeros_like(z)
|
||||
|
||||
if elastic_force and len(bond_pairs) > 0:
|
||||
i1 = bond_pairs[:, 0]
|
||||
i2 = bond_pairs[:, 1]
|
||||
dx = x[i2] - x[i1]
|
||||
dy = y[i2] - y[i1]
|
||||
dz = z[i2] - z[i1]
|
||||
dist = np.sqrt(dx*dx + dy*dy + dz*dz)
|
||||
valid = dist > 1e-12
|
||||
fac = np.where(valid, bond_k * (dist - bond_r0) / dist, 0.0)
|
||||
fx = fac * dx
|
||||
fy = fac * dy
|
||||
fz_b = fac * dz
|
||||
np.add.at(ax, i1, fx / m[i1]); np.add.at(ax, i2, -fx / m[i2])
|
||||
np.add.at(ay, i1, fy / m[i1]); np.add.at(ay, i2, -fy / m[i2])
|
||||
np.add.at(az, i1, fz_b / m[i1]); np.add.at(az, i2, -fz_b / m[i2])
|
||||
|
||||
return ax, ay, az
|
||||
|
||||
|
||||
# ── 完整加速度(含阻尼)──────────────────────────────────────
|
||||
def _accel_full(x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0):
|
||||
ax, ay, az = _accel_conservative(x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
if damping_force:
|
||||
ax -= Bx * vx / m
|
||||
ay -= By * vy / m
|
||||
az -= Bz * vz / m
|
||||
return ax, ay, az
|
||||
|
||||
|
||||
# ── 蛙跳法(半隐式阻尼,与 compute.py leapfrog_staggered_step 一致)─
|
||||
def _leapfrog_step(x, y, z, vx, vy, vz, fixed, m,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt):
|
||||
ax, ay, az = _accel_conservative(x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
has_damp = damping_force and (Bx != 0.0 or By != 0.0 or Bz != 0.0)
|
||||
if has_damp:
|
||||
alpha_x = Bx * dt / (2.0 * m)
|
||||
alpha_y = By * dt / (2.0 * m)
|
||||
alpha_z = Bz * dt / (2.0 * m)
|
||||
vx_new = (vx * (1.0 - alpha_x) + ax * dt) / (1.0 + alpha_x)
|
||||
vy_new = (vy * (1.0 - alpha_y) + ay * dt) / (1.0 + alpha_y)
|
||||
vz_new = (vz * (1.0 - alpha_z) + az * dt) / (1.0 + alpha_z)
|
||||
else:
|
||||
vx_new = vx + ax * dt
|
||||
vy_new = vy + ay * dt
|
||||
vz_new = vz + az * dt
|
||||
# 全固定原子保持不变
|
||||
all_fixed = np.all(fixed, axis=1)
|
||||
vx_new = np.where(all_fixed, vx, vx_new)
|
||||
vy_new = np.where(all_fixed, vy, vy_new)
|
||||
vz_new = np.where(all_fixed, vz, vz_new)
|
||||
x_new = x + vx_new * dt
|
||||
y_new = y + vy_new * dt
|
||||
z_new = z + vz_new * dt
|
||||
return x_new, y_new, z_new, vx_new, vy_new, vz_new
|
||||
|
||||
|
||||
# ── 显式欧拉法 ───────────────────────────────────────────────
|
||||
def _euler_step(x, y, z, vx, vy, vz, fixed, m,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt):
|
||||
ax, ay, az = _accel_full(x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
all_fixed = np.all(fixed, axis=1)
|
||||
mask = ~all_fixed
|
||||
x_new = np.where(mask, x + vx * dt, x)
|
||||
y_new = np.where(mask, y + vy * dt, y)
|
||||
z_new = np.where(mask, z + vz * dt, z)
|
||||
vx_new = np.where(mask, vx + ax * dt, vx)
|
||||
vy_new = np.where(mask, vy + ay * dt, vy)
|
||||
vz_new = np.where(mask, vz + az * dt, vz)
|
||||
return x_new, y_new, z_new, vx_new, vy_new, vz_new
|
||||
|
||||
|
||||
# ── 隐式欧拉法(与 compute.py Implicit_Euler_Method 一致)──────
|
||||
def _implicit_euler_step(x, y, z, vx, vy, vz, fixed, m,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt):
|
||||
gamma_x = Bx / m
|
||||
gamma_y = By / m
|
||||
gamma_z = Bz / m
|
||||
vx_next = (vx + Gx * dt) / (1.0 + gamma_x * dt)
|
||||
vy_next = (vy + Gy * dt) / (1.0 + gamma_y * dt)
|
||||
vz_next = (vz + Gz * dt) / (1.0 + gamma_z * dt)
|
||||
ax, ay, az = _accel_full(x, y, z, vx_next, vy_next, vz_next, m,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
all_fixed = np.all(fixed, axis=1)
|
||||
mask = ~all_fixed
|
||||
vx_new = np.where(mask, vx + ax * dt, vx)
|
||||
vy_new = np.where(mask, vy + ay * dt, vy)
|
||||
vz_new = np.where(mask, vz + az * dt, vz)
|
||||
x_new = np.where(mask, x + vx_new * dt, x)
|
||||
y_new = np.where(mask, y + vy_new * dt, y)
|
||||
z_new = np.where(mask, z + vz_new * dt, z)
|
||||
return x_new, y_new, z_new, vx_new, vy_new, vz_new
|
||||
|
||||
|
||||
# ── 中点法(与 compute.py Midpoint_Method 一致)────────────────
|
||||
def _midpoint_step(x, y, z, vx, vy, vz, fixed, m,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt):
|
||||
ax, ay, az = _accel_full(x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
all_fixed = np.all(fixed, axis=1)
|
||||
mask = ~all_fixed
|
||||
xm = np.where(mask, x + 0.5*vx*dt, x)
|
||||
ym = np.where(mask, y + 0.5*vy*dt, y)
|
||||
zm = np.where(mask, z + 0.5*vz*dt, z)
|
||||
vxm = np.where(mask, vx + 0.5*ax*dt, 0.0)
|
||||
vym = np.where(mask, vy + 0.5*ay*dt, 0.0)
|
||||
vzm = np.where(mask, vz + 0.5*az*dt, 0.0)
|
||||
x_new = np.where(mask, x + vxm * dt, x)
|
||||
y_new = np.where(mask, y + vym * dt, y)
|
||||
z_new = np.where(mask, z + vzm * dt, z)
|
||||
axm, aym, azm = _accel_full(xm, ym, zm, vxm, vym, vzm, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
vx_new = np.where(mask, vx + axm * dt, vx)
|
||||
vy_new = np.where(mask, vy + aym * dt, vy)
|
||||
vz_new = np.where(mask, vz + azm * dt, vz)
|
||||
return x_new, y_new, z_new, vx_new, vy_new, vz_new
|
||||
|
||||
|
||||
# ── 边界:反弹 + 回绕 + 逐自由度固定约束 ───────────────────────
|
||||
def _apply_bc(x, y, z, vx, vy, vz, fixed, pos_init, box_a):
|
||||
lo, hi = -box_a, box_a
|
||||
|
||||
# 反弹(全固定原子跳过)
|
||||
all_fixed = np.all(fixed, axis=1)
|
||||
do_bc = ~all_fixed
|
||||
over_x = do_bc & (x > hi); under_x = do_bc & (x < lo)
|
||||
over_y = do_bc & (y > hi); under_y = do_bc & (y < lo)
|
||||
over_z = do_bc & (z > hi); under_z = do_bc & (z < lo)
|
||||
x = np.where(over_x, hi, np.where(under_x, lo, x))
|
||||
y = np.where(over_y, hi, np.where(under_y, lo, y))
|
||||
z = np.where(over_z, hi, np.where(under_z, lo, z))
|
||||
vx = np.where(over_x | under_x, -np.abs(vx)*np.sign(np.where(over_x, 1, -1)), vx)
|
||||
vy = np.where(over_y | under_y, -np.abs(vy)*np.sign(np.where(over_y, 1, -1)), vy)
|
||||
vz = np.where(over_z | under_z, -np.abs(vz)*np.sign(np.where(over_z, 1, -1)), vz)
|
||||
# 反弹速度简化:越界则取反绝对值(与 C 版 _limit1 一致)
|
||||
vx = np.where(over_x, -np.abs(vx), np.where(under_x, np.abs(vx), vx))
|
||||
vy = np.where(over_y, -np.abs(vy), np.where(under_y, np.abs(vy), vy))
|
||||
vz = np.where(over_z, -np.abs(vz), np.where(under_z, np.abs(vz), vz))
|
||||
|
||||
# 回绕
|
||||
x = np.where(x > hi, lo, np.where(x < lo, hi, x))
|
||||
y = np.where(y > hi, lo, np.where(y < lo, hi, y))
|
||||
z = np.where(z > hi, lo, np.where(z < lo, hi, z))
|
||||
|
||||
# 逐自由度固定约束
|
||||
fx = fixed[:, 0].astype(bool)
|
||||
fy = fixed[:, 1].astype(bool)
|
||||
fz = fixed[:, 2].astype(bool)
|
||||
x = np.where(fx, pos_init[:, 0], x); vx = np.where(fx, 0.0, vx)
|
||||
y = np.where(fy, pos_init[:, 1], y); vy = np.where(fy, 0.0, vy)
|
||||
z = np.where(fz, pos_init[:, 2], z); vz = np.where(fz, 0.0, vz)
|
||||
return x, y, z, vx, vy, vz
|
||||
|
||||
|
||||
# ── 驱动力(与 compute.py apply_driving_force 逻辑一致)─────────
|
||||
def _apply_driving(x, y, z, vx, vy, vz, t, step, dt,
|
||||
drv_idx, drv_amp, drv_freq, drv_phi, drv_eq,
|
||||
drv_ncycles, drv_has_period, freeze):
|
||||
"""freeze: (n_drivers, 3) mutable array for frozen positions."""
|
||||
nd = len(drv_idx)
|
||||
for d in range(nd):
|
||||
idx = drv_idx[d]
|
||||
fx_ = drv_freq[d, 0]; fy_ = drv_freq[d, 1]; fz_ = drv_freq[d, 2]
|
||||
|
||||
if drv_has_period[d]:
|
||||
mf = max(abs(fx_), abs(fy_), abs(fz_))
|
||||
ps = int(drv_ncycles[d] / mf / dt) if mf > 1e-12 else 0
|
||||
if step > ps:
|
||||
x[idx] = freeze[d, 0]; y[idx] = freeze[d, 1]; z[idx] = freeze[d, 2]
|
||||
vx[idx] = vy[idx] = vz[idx] = 0.0
|
||||
continue
|
||||
px = drv_eq[d,0] + drv_amp[d,0]*np.cos(TWO_PI*fx_*t + drv_phi[d,0])
|
||||
py = drv_eq[d,1] + drv_amp[d,1]*np.cos(TWO_PI*fy_*t + drv_phi[d,1])
|
||||
pz = drv_eq[d,2] + drv_amp[d,2]*np.cos(TWO_PI*fz_*t + drv_phi[d,2])
|
||||
if step == ps:
|
||||
freeze[d, 0] = px; freeze[d, 1] = py; freeze[d, 2] = pz
|
||||
|
||||
x[idx] = drv_eq[d,0] + drv_amp[d,0]*np.cos(TWO_PI*fx_*t + drv_phi[d,0])
|
||||
y[idx] = drv_eq[d,1] + drv_amp[d,1]*np.cos(TWO_PI*fy_*t + drv_phi[d,1])
|
||||
z[idx] = drv_eq[d,2] + drv_amp[d,2]*np.cos(TWO_PI*fz_*t + drv_phi[d,2])
|
||||
vx[idx] = -drv_amp[d,0]*TWO_PI*fx_*np.sin(TWO_PI*fx_*t + drv_phi[d,0])
|
||||
vy[idx] = -drv_amp[d,1]*TWO_PI*fy_*np.sin(TWO_PI*fy_*t + drv_phi[d,1])
|
||||
vz[idx] = -drv_amp[d,2]*TWO_PI*fz_*np.sin(TWO_PI*fz_*t + drv_phi[d,2])
|
||||
|
||||
|
||||
def _do_step(x, y, z, vx, vy, vz, fixed, masses, method_id,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt, pos_init, box_a):
|
||||
if method_id == 0:
|
||||
x, y, z, vx, vy, vz = _euler_step(
|
||||
x, y, z, vx, vy, vz, fixed, masses,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt)
|
||||
elif method_id == 1:
|
||||
x, y, z, vx, vy, vz = _implicit_euler_step(
|
||||
x, y, z, vx, vy, vz, fixed, masses,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt)
|
||||
elif method_id == 2:
|
||||
x, y, z, vx, vy, vz = _midpoint_step(
|
||||
x, y, z, vx, vy, vz, fixed, masses,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt)
|
||||
else:
|
||||
x, y, z, vx, vy, vz = _leapfrog_step(
|
||||
x, y, z, vx, vy, vz, fixed, masses,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt)
|
||||
x, y, z, vx, vy, vz = _apply_bc(x, y, z, vx, vy, vz, fixed, pos_init, box_a)
|
||||
return x, y, z, vx, vy, vz
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# 主函数:run_dynamics
|
||||
# 接口与 C/C++/Fortran DLL 的 run_dynamics() 对应,
|
||||
# 参数格式:numpy 数组(替代 ctypes 指针)。
|
||||
#
|
||||
# method_id: 0=euler 1=implicit_euler 2=midpoint 3=leapfrog
|
||||
# drv_amp/freq/phi/eq: (n_drivers, 3) float64
|
||||
# drv_ncycles: (n_drivers,) float64 0=不限
|
||||
# drv_has_period: (n_drivers,) int
|
||||
#
|
||||
# 返回:(out_x, out_y, out_z, out_vx, out_vy, out_vz)
|
||||
# 各 shape=(n_frames, n_atoms)
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
def run_dynamics(
|
||||
n_atoms, pos_init, vel_init, masses, fixed,
|
||||
n_bonds, bond_pairs, bond_k, bond_r0,
|
||||
box_a, dt,
|
||||
NT, NSTEP, warmup_steps, method_id,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force, gravity_strength,
|
||||
n_drivers, drv_idx, drv_amp, drv_freq, drv_phi, drv_eq,
|
||||
drv_ncycles, drv_has_period,
|
||||
n_frames,
|
||||
progress_cb=None,
|
||||
):
|
||||
"""运行动力学模拟,返回抽帧轨迹数组。
|
||||
|
||||
Args:
|
||||
pos_init: (n_atoms, 3) float64
|
||||
vel_init: (n_atoms, 3) float64
|
||||
masses: (n_atoms,) float64
|
||||
fixed: (n_atoms, 3) int — 1=固定
|
||||
bond_pairs: (n_bonds, 2) int — 0-based 局部索引
|
||||
bond_k: (n_bonds,) float64
|
||||
bond_r0: (n_bonds,) float64
|
||||
drv_idx: (n_drivers,) int — 0-based
|
||||
drv_amp/freq/phi/eq: (n_drivers, 3) float64
|
||||
drv_ncycles: (n_drivers,) float64
|
||||
drv_has_period: (n_drivers,) int
|
||||
n_frames: 预分配的输出帧数
|
||||
|
||||
Returns:
|
||||
out_x, out_y, out_z, out_vx, out_vy, out_vz — 各 (n_frames, n_atoms)
|
||||
"""
|
||||
pos_init = np.asarray(pos_init, dtype=np.float64)
|
||||
vel_init = np.asarray(vel_init, dtype=np.float64)
|
||||
masses = np.asarray(masses, dtype=np.float64)
|
||||
fixed = np.asarray(fixed, dtype=np.int32)
|
||||
bond_pairs = np.asarray(bond_pairs, dtype=np.int64).reshape(-1, 2) if n_bonds else np.zeros((0,2), dtype=np.int64)
|
||||
bond_k = np.asarray(bond_k, dtype=np.float64) if n_bonds else np.zeros(0)
|
||||
bond_r0 = np.asarray(bond_r0, dtype=np.float64) if n_bonds else np.zeros(0)
|
||||
|
||||
n = n_atoms
|
||||
x = pos_init[:, 0].copy()
|
||||
y = pos_init[:, 1].copy()
|
||||
z = pos_init[:, 2].copy()
|
||||
vx = vel_init[:, 0].copy()
|
||||
vy = vel_init[:, 1].copy()
|
||||
vz = vel_init[:, 2].copy()
|
||||
|
||||
# 驱动力数据(保证正确形状)
|
||||
nd = n_drivers
|
||||
if nd > 0:
|
||||
drv_idx = np.asarray(drv_idx, dtype=np.int64)
|
||||
drv_amp = np.asarray(drv_amp, dtype=np.float64).reshape(nd, 3)
|
||||
drv_freq = np.asarray(drv_freq, dtype=np.float64).reshape(nd, 3)
|
||||
drv_phi = np.asarray(drv_phi, dtype=np.float64).reshape(nd, 3)
|
||||
drv_eq = np.asarray(drv_eq, dtype=np.float64).reshape(nd, 3)
|
||||
drv_nc = np.asarray(drv_ncycles, dtype=np.float64)
|
||||
drv_hp = np.asarray(drv_has_period, dtype=np.int32)
|
||||
freeze = np.zeros((nd, 3), dtype=np.float64)
|
||||
else:
|
||||
drv_idx = drv_amp = drv_freq = drv_phi = drv_eq = drv_nc = drv_hp = freeze = None
|
||||
|
||||
def _drive(t_, step_):
|
||||
if nd > 0:
|
||||
_apply_driving(x, y, z, vx, vy, vz, t_, step_, dt,
|
||||
drv_idx, drv_amp, drv_freq, drv_phi, drv_eq,
|
||||
drv_nc, drv_hp, freeze)
|
||||
|
||||
# ── 蛙跳法:初始化 v(-dt/2) ─────────────────────────────
|
||||
if method_id == 3:
|
||||
ax0, ay0, az0 = _accel_conservative(x, y, z, masses, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
all_fixed = np.all(fixed, axis=1)
|
||||
vx = np.where(all_fixed, vx, vx - 0.5 * ax0 * dt)
|
||||
vy = np.where(all_fixed, vy, vy - 0.5 * ay0 * dt)
|
||||
vz = np.where(all_fixed, vz, vz - 0.5 * az0 * dt)
|
||||
|
||||
# ── 初始驱动 t=0 ─────────────────────────────────────────
|
||||
_drive(0.0, 0)
|
||||
|
||||
# ── 预热 ─────────────────────────────────────────────────
|
||||
for s in range(warmup_steps):
|
||||
tw = (s + 1) * dt
|
||||
_drive(tw, s)
|
||||
x, y, z, vx, vy, vz = _do_step(
|
||||
x, y, z, vx, vy, vz, fixed, masses, method_id,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt, pos_init, box_a)
|
||||
|
||||
# ── 记录循环 ─────────────────────────────────────────────
|
||||
record_steps = NT - warmup_steps
|
||||
prog_interval = max(1, record_steps // 100)
|
||||
|
||||
out_x = np.zeros((n_frames, n), dtype=np.float64)
|
||||
out_y = np.zeros((n_frames, n), dtype=np.float64)
|
||||
out_z = np.zeros((n_frames, n), dtype=np.float64)
|
||||
out_vx = np.zeros((n_frames, n), dtype=np.float64)
|
||||
out_vy = np.zeros((n_frames, n), dtype=np.float64)
|
||||
out_vz = np.zeros((n_frames, n), dtype=np.float64)
|
||||
frame_idx = 0
|
||||
|
||||
for s in range(record_steps):
|
||||
if progress_cb is not None and s % prog_interval == 0 and s > 0:
|
||||
progress_cb(s, record_steps)
|
||||
|
||||
t = (s + warmup_steps) * dt
|
||||
_drive(t, s)
|
||||
|
||||
if s % NSTEP == 0 and frame_idx < n_frames:
|
||||
out_x[frame_idx] = x
|
||||
out_y[frame_idx] = y
|
||||
out_z[frame_idx] = z
|
||||
out_vx[frame_idx] = vx
|
||||
out_vy[frame_idx] = vy
|
||||
out_vz[frame_idx] = vz
|
||||
frame_idx += 1
|
||||
|
||||
x, y, z, vx, vy, vz = _do_step(
|
||||
x, y, z, vx, vy, vz, fixed, masses, method_id,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt, pos_init, box_a)
|
||||
|
||||
return out_x, out_y, out_z, out_vx, out_vy, out_vz
|
||||
@@ -0,0 +1,287 @@
|
||||
"""
|
||||
engines/python/main.py
|
||||
-----------------------
|
||||
独立 Python 计算引擎。
|
||||
|
||||
与 main.c / main.cpp / main.f90 结构一致:
|
||||
输入: <input_dir>/coord.txt, connection.txt, bond.txt, [driver.txt]
|
||||
<param_json> (同 engines/c/param.json 格式)
|
||||
输出: <output_dir>/display.txt (+ display.npz)
|
||||
<output_dir>/trajectory.txt (若 save_trajectory=1)
|
||||
|
||||
用法:
|
||||
python main.py <input_dir> <output_dir> <param_json>
|
||||
|
||||
内部调用 dynamics_lib.run_dynamics(),算法与 compute.py 完全一致。
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import numpy as np
|
||||
|
||||
# 将父目录(engines/python 的上级 engines)加入 sys.path,
|
||||
# 以便在独立运行时也能找到 dynamics_lib
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, _HERE)
|
||||
|
||||
from dynamics_lib import run_dynamics
|
||||
|
||||
# 为读取 coord/bond/display,复用 compute.py 中的 I/O 函数
|
||||
_COMPUTE = os.path.join(_HERE, "..", "..")
|
||||
sys.path.insert(0, _COMPUTE)
|
||||
import compute as _c
|
||||
|
||||
_METHOD_ID = {
|
||||
"explicit_euler": 0,
|
||||
"euler": 0,
|
||||
"implicit_euler": 1,
|
||||
"midpoint": 2,
|
||||
"leapfrog": 3,
|
||||
}
|
||||
|
||||
|
||||
def _load_params(param_path):
|
||||
"""读取 param.json(与 C 引擎格式相同)."""
|
||||
with open(param_path, "r", encoding="utf-8") as f:
|
||||
p = json.load(f)
|
||||
return p
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4:
|
||||
print("用法: python main.py <input_dir> <output_dir> <param_json>")
|
||||
sys.exit(1)
|
||||
|
||||
input_dir = sys.argv[1]
|
||||
output_dir = sys.argv[2]
|
||||
param_path = sys.argv[3]
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# ── 读取参数 ─────────────────────────────────────────────
|
||||
p = _load_params(param_path)
|
||||
box_a = float(p.get("box_a", 10.0))
|
||||
NT = int(p.get("NT", 10000))
|
||||
dt = float(p.get("DT", 0.001))
|
||||
NSTEP = int(p.get("NSTEP", 100))
|
||||
warmup_steps = int(p.get("warmup_steps", 0))
|
||||
method_str = str(p.get("method", "leapfrog")).lower().replace(" ", "_")
|
||||
method_id = _METHOD_ID.get(method_str, 3)
|
||||
G = p.get("G", [0.0, 0.0, -9.8])
|
||||
B = p.get("B", [0.0, 0.0, 0.0])
|
||||
gravity_field = int(p.get("gravity_field", 1))
|
||||
elastic_force = int(p.get("elastic_force", 1))
|
||||
damping_force = int(p.get("damping_force", 0))
|
||||
gravity_strength = float(p.get("gravity_strength", 1.0))
|
||||
driving_force = int(p.get("driving_force", 0))
|
||||
save_traj = int(p.get("save_trajectory", 0))
|
||||
|
||||
# ── 读取原子数据 ──────────────────────────────────────────
|
||||
coord_path = os.path.join(input_dir, "coord.txt")
|
||||
atom_ids, masses, radii, positions, velocities, fixed = _c.load_coord_file(coord_path)
|
||||
|
||||
# ── 读取键数据 ────────────────────────────────────────────
|
||||
conn_path = os.path.join(input_dir, "connection.txt")
|
||||
bond_path = os.path.join(input_dir, "bond.txt")
|
||||
bond_map = _c.load_bond_parameters(bond_path)
|
||||
bond_pairs, bond_names, bond_stiffness, bond_rest_lengths = \
|
||||
_c.load_bond_connections(conn_path, atom_ids, positions, bond_map)
|
||||
n_bonds = len(bond_pairs)
|
||||
|
||||
# ── 读取驱动力 ────────────────────────────────────────────
|
||||
drv_list = []
|
||||
if driving_force:
|
||||
driver_path = os.path.join(input_dir, "driver.txt")
|
||||
raw_drivers = _c.load_driver_file(driver_path, atom_ids)
|
||||
if raw_drivers:
|
||||
atom_id_map = {int(aid): i for i, aid in enumerate(atom_ids)}
|
||||
for d in raw_drivers:
|
||||
aid = int(d["atom_id"])
|
||||
if aid not in atom_id_map:
|
||||
continue
|
||||
lidx = atom_id_map[aid]
|
||||
eq = positions[lidx].tolist()
|
||||
d["eq_pos"] = np.array(eq)
|
||||
pc = d.get("period_cycles")
|
||||
nc = float(pc) if pc is not None else 0.0
|
||||
hp = 1 if nc > 0 else 0
|
||||
drv_list.append({
|
||||
"local_idx": lidx,
|
||||
"amp": d["amp"].tolist(),
|
||||
"freq": d["freq"].tolist(),
|
||||
"phi": d["phi"].tolist(), # radians
|
||||
"eq": eq,
|
||||
"nc": nc,
|
||||
"hp": hp,
|
||||
})
|
||||
|
||||
nd = len(drv_list)
|
||||
if nd > 0:
|
||||
drv_idx = np.array([d["local_idx"] for d in drv_list], dtype=np.int64)
|
||||
drv_amp = np.array([d["amp"] for d in drv_list], dtype=np.float64)
|
||||
drv_freq = np.array([d["freq"] for d in drv_list], dtype=np.float64)
|
||||
drv_phi = np.array([d["phi"] for d in drv_list], dtype=np.float64)
|
||||
drv_eq = np.array([d["eq"] for d in drv_list], dtype=np.float64)
|
||||
drv_nc = np.array([d["nc"] for d in drv_list], dtype=np.float64)
|
||||
drv_hp = np.array([d["hp"] for d in drv_list], dtype=np.int32)
|
||||
else:
|
||||
drv_idx = drv_amp = drv_freq = drv_phi = drv_eq = drv_nc = drv_hp = \
|
||||
np.zeros(0, dtype=np.int64)
|
||||
|
||||
# ── 计算帧数 ──────────────────────────────────────────────
|
||||
record_steps = NT - warmup_steps
|
||||
n_frames = max(1, record_steps // NSTEP)
|
||||
|
||||
# ── 进度回调 ──────────────────────────────────────────────
|
||||
def _progress(step, total):
|
||||
pct = step * 100 // total
|
||||
print(f"[python-engine] progress: {step}/{total} ({pct}%)", flush=True)
|
||||
|
||||
# ── 运行计算 ──────────────────────────────────────────────
|
||||
t0 = time.time()
|
||||
print(f"[python-engine] NT={NT} NSTEP={NSTEP} method={method_str} "
|
||||
f"n_atoms={len(atom_ids)} n_bonds={n_bonds}")
|
||||
|
||||
out_x, out_y, out_z, out_vx, out_vy, out_vz = run_dynamics(
|
||||
n_atoms=len(atom_ids),
|
||||
pos_init=positions,
|
||||
vel_init=velocities,
|
||||
masses=masses,
|
||||
fixed=fixed,
|
||||
n_bonds=n_bonds,
|
||||
bond_pairs=bond_pairs,
|
||||
bond_k=bond_stiffness,
|
||||
bond_r0=bond_rest_lengths,
|
||||
box_a=box_a,
|
||||
dt=dt,
|
||||
NT=NT,
|
||||
NSTEP=NSTEP,
|
||||
warmup_steps=warmup_steps,
|
||||
method_id=method_id,
|
||||
Gx=float(G[0]), Gy=float(G[1]), Gz=float(G[2]),
|
||||
Bx=float(B[0]), By=float(B[1]), Bz=float(B[2]),
|
||||
gravity_field=gravity_field,
|
||||
elastic_force=elastic_force,
|
||||
damping_force=damping_force,
|
||||
gravity_strength=gravity_strength,
|
||||
n_drivers=nd,
|
||||
drv_idx=drv_idx,
|
||||
drv_amp=drv_amp,
|
||||
drv_freq=drv_freq,
|
||||
drv_phi=drv_phi,
|
||||
drv_eq=drv_eq,
|
||||
drv_ncycles=drv_nc,
|
||||
drv_has_period=drv_hp,
|
||||
n_frames=n_frames,
|
||||
progress_cb=_progress,
|
||||
)
|
||||
elapsed = time.time() - t0
|
||||
print(f"[python-engine] 完成: {n_frames} 帧 {elapsed:.3f} s")
|
||||
|
||||
# ── 构建 display header ───────────────────────────────────
|
||||
ball_radius = float(p.get("ball_radius", 0.5))
|
||||
ball_color = p.get("ball_color", [0.9, 0.2, 0.2])
|
||||
box_color = p.get("box_color", [0.8, 0.8, 0.85])
|
||||
use_marker = int(p.get("use_marker", 0))
|
||||
alpha_val = p.get("alpha", 0.2)
|
||||
cam_dist = float(p.get("camera_distance", 40.0))
|
||||
cam_elev = float(p.get("camera_elevation", 0.0))
|
||||
cam_azim = float(p.get("camera_azimuth", 0.0))
|
||||
cam_cx = float(p.get("camera_center_x", 0.0))
|
||||
cam_cy = float(p.get("camera_center_y", 0.0))
|
||||
cam_cz = float(p.get("camera_center_z", 0.0))
|
||||
|
||||
header = {
|
||||
"DT": str(dt),
|
||||
"NSTEP": str(NSTEP),
|
||||
"method": method_str,
|
||||
"NT": str(NT),
|
||||
"warmup_steps": str(warmup_steps),
|
||||
"dynamic_steps": str(record_steps),
|
||||
"T_total": str(NT * dt),
|
||||
"box_a": str(box_a),
|
||||
"gravity_field": str(gravity_field),
|
||||
"elastic_force": str(elastic_force),
|
||||
"damping_force": str(damping_force),
|
||||
"driving_force": str(driving_force),
|
||||
"gravity_strength": str(gravity_strength),
|
||||
"G": json.dumps([float(v) for v in G]),
|
||||
"B": json.dumps([float(v) for v in B]),
|
||||
"number_of_frames": str(n_frames),
|
||||
"number_of_particles": str(len(atom_ids)),
|
||||
"use_marker": str(use_marker),
|
||||
"display_color": json.dumps(p.get("display_color",
|
||||
{"x":[0,[1.0,0.0,0.0]],"y":[0,[0.0,1.0,0.0]],"z":[0,[0.0,0.0,1.0]],
|
||||
"xy":[0,[1.0,1.0,0.0]],"yz":[0,[0.0,1.0,1.0]],"zx":[0,[1.0,0.0,1.0]],
|
||||
"xyz":[1,[1.0,1.0,1.0]]})),
|
||||
"ball_radius": str(ball_radius),
|
||||
"ball_color_r": str(ball_color[0]),
|
||||
"ball_color_g": str(ball_color[1]),
|
||||
"ball_color_b": str(ball_color[2]),
|
||||
"box_color_r": str(box_color[0]),
|
||||
"box_color_g": str(box_color[1]),
|
||||
"box_color_b": str(box_color[2]),
|
||||
"alpha": str(alpha_val) if not isinstance(alpha_val, list)
|
||||
else ",".join(str(a) for a in alpha_val),
|
||||
"atom_radii": ",".join(str(r) for r in radii),
|
||||
"atom_masses": json.dumps([float(m) for m in masses]),
|
||||
"atom_positions": json.dumps(positions.tolist()),
|
||||
"bond_pairs": json.dumps(bond_pairs.tolist() if n_bonds else []),
|
||||
"bond_stiffness": json.dumps(bond_stiffness.tolist() if n_bonds else []),
|
||||
"bond_rest_lengths": json.dumps(bond_rest_lengths.tolist() if n_bonds else []),
|
||||
"X_MIN": str(-box_a), "X_MAX": str(box_a),
|
||||
"Y_MIN": str(-box_a), "Y_MAX": str(box_a),
|
||||
"Z_MIN": str(-box_a), "Z_MAX": str(box_a),
|
||||
"camera_distance": str(cam_dist),
|
||||
"camera_elevation": str(cam_elev),
|
||||
"camera_azimuth": str(cam_azim),
|
||||
"camera_center_x": str(cam_cx),
|
||||
"camera_center_y": str(cam_cy),
|
||||
"camera_center_z": str(cam_cz),
|
||||
"camera_keyframes": "",
|
||||
}
|
||||
|
||||
# ── 保存 display.txt + display.npz ───────────────────────
|
||||
disp_txt = os.path.join(output_dir, "display.txt")
|
||||
_c.save_display_txt(
|
||||
disp_txt,
|
||||
out_x, out_y, out_z, out_vx, out_vy, out_vz,
|
||||
atom_ids, record_steps, len(atom_ids),
|
||||
header_fields=header,
|
||||
)
|
||||
print(f"[python-engine] display.txt 已保存: {disp_txt}")
|
||||
|
||||
disp_npz = os.path.join(output_dir, "display.npz")
|
||||
_c.save_display_npz(
|
||||
disp_npz,
|
||||
out_x, out_y, out_z, out_vx, out_vy, out_vz,
|
||||
atom_ids, header_fields=header,
|
||||
)
|
||||
print(f"[python-engine] display.npz 已保存: {disp_npz}")
|
||||
|
||||
# ── 可选:保存 trajectory.txt ─────────────────────────────
|
||||
if save_traj:
|
||||
traj_payload = {
|
||||
"traj_x": out_x, "traj_y": out_y, "traj_z": out_z,
|
||||
"traj_vx": out_vx, "traj_vy": out_vy, "traj_vz": out_vz,
|
||||
"NT": record_steps, "DT": dt, "NSTEP": NSTEP,
|
||||
"method": method_str,
|
||||
"atom_ids": atom_ids,
|
||||
"atom_masses": masses,
|
||||
"atom_radii": radii,
|
||||
"atom_positions": positions,
|
||||
"bond_pairs": bond_pairs,
|
||||
"bond_stiffness": bond_stiffness,
|
||||
"bond_rest_lengths": bond_rest_lengths,
|
||||
"G": [float(v) for v in G],
|
||||
"B": [float(v) for v in B],
|
||||
}
|
||||
traj_path = os.path.join(output_dir, "trajectory.txt")
|
||||
_c.save_text_data(traj_path, traj_payload)
|
||||
print(f"[python-engine] trajectory.txt 已保存: {traj_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,51 @@
|
||||
# engines/src/c/Makefile
|
||||
# 编译 DLL 到 engines/release/(主程序通过 ctypes 直接调用)
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -O3 -march=native -Wall -Wextra
|
||||
LDFLAGS = -lm
|
||||
LIB_SRC = dynamics_lib.c
|
||||
|
||||
# 自动检测系统
|
||||
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
|
||||
|
||||
# Windows 检测:Msys2/MINGW 也视为 Windows
|
||||
IS_WINDOWS := $(findstring MINGW,$(UNAME_S))
|
||||
ifneq ($(IS_WINDOWS),)
|
||||
UNAME_S := Windows
|
||||
endif
|
||||
IS_WINDOWS := $(findstring MSYS,$(UNAME_S))
|
||||
ifneq ($(IS_WINDOWS),)
|
||||
UNAME_S := Windows
|
||||
endif
|
||||
|
||||
# DLL 输出到 engines/release/
|
||||
DLL_DIR = ../../release
|
||||
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_c.so
|
||||
DLL_FLAGS = -shared -fPIC
|
||||
else ifeq ($(UNAME_S),Darwin)
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_c.dylib
|
||||
DLL_FLAGS = -dynamiclib
|
||||
else
|
||||
# Windows: 静态链接运行时,避免依赖 libgcc_s_seh-1.dll
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_c.dll
|
||||
DLL_FLAGS = -shared -static
|
||||
endif
|
||||
|
||||
.PHONY: all dll clean
|
||||
|
||||
all: dll
|
||||
|
||||
dll: $(DLL_TARGET)
|
||||
|
||||
$(DLL_TARGET): $(LIB_SRC) | $(DLL_DIR)
|
||||
$(CC) $(CFLAGS) $(DLL_FLAGS) -o $@ $(LIB_SRC) $(LDFLAGS)
|
||||
@echo " === C DLL built: $@ ==="
|
||||
|
||||
$(DLL_DIR):
|
||||
mkdir -p $(DLL_DIR)
|
||||
|
||||
clean:
|
||||
rm -f $(DLL_TARGET)
|
||||
@@ -0,0 +1,554 @@
|
||||
/**
|
||||
* engines/c/dynamics_lib.c
|
||||
* -------------------------
|
||||
* 纯计算 DLL:无文件 I/O,所有数据由 Python 以 NumPy 数组传入,
|
||||
* 结果直接写入 Python 预分配的输出数组。
|
||||
* 算法与 main.c 和 compute.py 保持完全一致。
|
||||
*
|
||||
* 编译(Windows DLL):
|
||||
* gcc -O3 -march=native -shared -o build/dynamics_c.dll dynamics_lib.c -lm
|
||||
* 编译(Linux .so):
|
||||
* gcc -O3 -march=native -shared -fPIC -o build/dynamics_c.so dynamics_lib.c -lm
|
||||
* 编译(macOS .dylib):
|
||||
* gcc -O3 -march=native -dynamiclib -o build/dynamics_c.dylib dynamics_lib.c -lm
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
# define EXPORT __declspec(dllexport)
|
||||
#else
|
||||
# define EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* ── 驱动力结构体 ─────────────────────────────────────────── */
|
||||
typedef struct {
|
||||
int n_drivers;
|
||||
const int *idx; /* [n_drivers] 0-based local atom index */
|
||||
const double *amp; /* [n_drivers*3] (ax,ay,az) interleaved */
|
||||
const double *freq; /* [n_drivers*3] */
|
||||
const double *phi; /* [n_drivers*3] radians */
|
||||
const double *eq; /* [n_drivers*3] equilibrium positions */
|
||||
const double *ncycles; /* [n_drivers] 0=unlimited */
|
||||
const int *has_period; /* [n_drivers] */
|
||||
/* mutable freeze positions (allocated internally) */
|
||||
double *freeze; /* [n_drivers*3] */
|
||||
} Drivers;
|
||||
|
||||
/* ── 加速度:保守力(弹簧键 + 均匀重力场)────────────────── */
|
||||
static void accel_conservative(
|
||||
int n, const double *x, const double *y, const double *z,
|
||||
const double *m,
|
||||
double Gx, double Gy, double Gz,
|
||||
int gravity_field, int elastic_force,
|
||||
int n_bonds, const int *bond_pairs,
|
||||
const double *bond_k, const double *bond_r0,
|
||||
double *ax, double *ay, double *az)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
ax[i] = gravity_field ? Gx : 0.0;
|
||||
ay[i] = gravity_field ? Gy : 0.0;
|
||||
az[i] = gravity_field ? Gz : 0.0;
|
||||
}
|
||||
|
||||
if (!elastic_force || n_bonds == 0) return;
|
||||
|
||||
for (int b = 0; b < n_bonds; b++) {
|
||||
int ii = bond_pairs[b*2];
|
||||
int jj = bond_pairs[b*2+1];
|
||||
double dx = x[jj] - x[ii];
|
||||
double dy = y[jj] - y[ii];
|
||||
double dz = z[jj] - z[ii];
|
||||
double dist = sqrt(dx*dx + dy*dy + dz*dz);
|
||||
if (dist < 1e-12) continue;
|
||||
double k = bond_k[b];
|
||||
double r0 = bond_r0[b];
|
||||
double fac = k * (dist - r0) / dist;
|
||||
double fx = fac * dx, fy = fac * dy, fz_b = fac * dz;
|
||||
ax[ii] += fx / m[ii]; ay[ii] += fy / m[ii]; az[ii] += fz_b / m[ii];
|
||||
ax[jj] -= fx / m[jj]; ay[jj] -= fy / m[jj]; az[jj] -= fz_b / m[jj];
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 完整加速度(含阻尼)────────────────────────────────── */
|
||||
static void accel_full(
|
||||
int n, const double *x, const double *y, const double *z,
|
||||
const double *vx, const double *vy, const double *vz,
|
||||
const double *m,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bond_pairs,
|
||||
const double *bond_k, const double *bond_r0,
|
||||
double *ax, double *ay, double *az)
|
||||
{
|
||||
accel_conservative(n, x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
n_bonds, bond_pairs, bond_k, bond_r0,
|
||||
ax, ay, az);
|
||||
if (damping_force) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
ax[i] -= Bx * vx[i] / m[i];
|
||||
ay[i] -= By * vy[i] / m[i];
|
||||
az[i] -= Bz * vz[i] / m[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 边界:反弹(与 main.c limit_in_box 一致)────────────── */
|
||||
static inline void _limit1(double *p, double *v, double lo, double hi) {
|
||||
if (*p > hi) { *p = hi; *v = -fabs(*v); }
|
||||
if (*p < lo) { *p = lo; *v = fabs(*v); }
|
||||
}
|
||||
|
||||
/* ── 边界:回绕(与 main.c wrap_position 一致)──────────── */
|
||||
static inline void _wrap1(double *p, double lo, double hi) {
|
||||
if (*p > hi) *p = lo;
|
||||
if (*p < lo) *p = hi;
|
||||
}
|
||||
|
||||
/* ── 边界 + 固定约束(与 main.c apply_step 末尾一致)──────── */
|
||||
static void apply_boundary_and_constraints(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const int *fixed, const double *pos_init,
|
||||
double box_a)
|
||||
{
|
||||
double lo = -box_a, hi = box_a;
|
||||
|
||||
/* 反弹 */
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
_limit1(&x[i], &vx[i], lo, hi);
|
||||
_limit1(&y[i], &vy[i], lo, hi);
|
||||
_limit1(&z[i], &vz[i], lo, hi);
|
||||
}
|
||||
|
||||
/* 回绕 */
|
||||
for (int i = 0; i < n; i++) {
|
||||
_wrap1(&x[i], lo, hi);
|
||||
_wrap1(&y[i], lo, hi);
|
||||
_wrap1(&z[i], lo, hi);
|
||||
}
|
||||
|
||||
/* 逐自由度固定约束:与 main.c 和 Python apply_fixed_constraints 一致 */
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0]) { x[i] = pos_init[i*3+0]; vx[i] = 0.0; }
|
||||
if (fixed[i*3+1]) { y[i] = pos_init[i*3+1]; vy[i] = 0.0; }
|
||||
if (fixed[i*3+2]) { z[i] = pos_init[i*3+2]; vz[i] = 0.0; }
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 蛙跳法(与 main.c leapfrog_step 完全一致)
|
||||
* x(t), v(t-dt/2) → x(t+dt), v(t+dt/2)
|
||||
* 无阻尼:纯辛积分。有阻尼:半隐式处理 α = B·dt/(2m)
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void leapfrog_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0,
|
||||
double dt)
|
||||
{
|
||||
double *ax = (double*)alloca(n*sizeof(double)*3);
|
||||
double *ay = ax+n; double *az = ay+n;
|
||||
|
||||
accel_conservative(n, x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
|
||||
int has_damp = damping_force && (Bx != 0.0 || By != 0.0 || Bz != 0.0);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
if (has_damp) {
|
||||
double ax_ = Bx*dt/(2.0*m[i]);
|
||||
double ay_ = By*dt/(2.0*m[i]);
|
||||
double az_ = Bz*dt/(2.0*m[i]);
|
||||
vx[i] = (vx[i]*(1.0-ax_) + ax[i]*dt) / (1.0+ax_);
|
||||
vy[i] = (vy[i]*(1.0-ay_) + ay[i]*dt) / (1.0+ay_);
|
||||
vz[i] = (vz[i]*(1.0-az_) + az[i]*dt) / (1.0+az_);
|
||||
} else {
|
||||
vx[i] += ax[i]*dt;
|
||||
vy[i] += ay[i]*dt;
|
||||
vz[i] += az[i]*dt;
|
||||
}
|
||||
x[i] += vx[i]*dt;
|
||||
y[i] += vy[i]*dt;
|
||||
z[i] += vz[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 显式欧拉法(与 main.c explicit_euler_step 一致)
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void euler_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0,
|
||||
double dt)
|
||||
{
|
||||
double *ax = (double*)alloca(n*sizeof(double)*3);
|
||||
double *ay = ax+n; double *az = ay+n;
|
||||
accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
x[i] += vx[i]*dt; y[i] += vy[i]*dt; z[i] += vz[i]*dt;
|
||||
vx[i]+= ax[i]*dt; vy[i]+= ay[i]*dt; vz[i]+= az[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 隐式欧拉法(与 main.c implicit_euler_step 完全一致)
|
||||
*
|
||||
* main.c 逻辑:
|
||||
* 1. 用 v_next ≈ (v + G·dt)/(1 + γ·dt) 预测(只含重力+阻尼,不含弹簧)
|
||||
* 2. 用 (x, v_next) 计算完整加速度 a_next
|
||||
* 3. v += a_next·dt; x += v·dt
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void implicit_euler_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0,
|
||||
double dt)
|
||||
{
|
||||
double *vxn = (double*)alloca(n*sizeof(double)*3);
|
||||
double *vyn = vxn+n; double *vzn = vyn+n;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) {
|
||||
vxn[i] = vyn[i] = vzn[i] = 0.0; continue;
|
||||
}
|
||||
double gx = Bx / m[i], gy = By / m[i], gz = Bz / m[i];
|
||||
vxn[i] = (vx[i] + Gx*dt) / (1.0 + gx*dt);
|
||||
vyn[i] = (vy[i] + Gy*dt) / (1.0 + gy*dt);
|
||||
vzn[i] = (vz[i] + Gz*dt) / (1.0 + gz*dt);
|
||||
}
|
||||
|
||||
double *ax = (double*)alloca(n*sizeof(double)*3);
|
||||
double *ay = ax+n; double *az = ay+n;
|
||||
accel_full(n, x, y, z, vxn, vyn, vzn, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += ax[i]*dt;
|
||||
vy[i] += ay[i]*dt;
|
||||
vz[i] += az[i]*dt;
|
||||
x[i] += vx[i]*dt;
|
||||
y[i] += vy[i]*dt;
|
||||
z[i] += vz[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 中点法(与 main.c midpoint_step 完全一致)
|
||||
*
|
||||
* main.c 逻辑:
|
||||
* 1. a = accel(x, v)
|
||||
* 2. xm = x + 0.5·v·dt; vm = v + 0.5·a·dt
|
||||
* 3. x = x + vm·dt (位置更新用 vm,即中点速度)
|
||||
* 4. am = accel(xm, vm)
|
||||
* 5. v = v + am·dt
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void midpoint_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0,
|
||||
double dt)
|
||||
{
|
||||
/* Allocate in one block for cache locality */
|
||||
double *buf = (double*)alloca(n*sizeof(double)*9);
|
||||
double *ax = buf;
|
||||
double *ay = ax+n; double *az = ay+n;
|
||||
double *xm = az+n; double *ym = xm+n; double *zm = ym+n;
|
||||
double *vxm = zm+n; double *vym = vxm+n; double *vzm = vym+n;
|
||||
|
||||
accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) {
|
||||
xm[i]=x[i]; ym[i]=y[i]; zm[i]=z[i];
|
||||
vxm[i]=vym[i]=vzm[i]=0.0; continue;
|
||||
}
|
||||
xm[i] = x[i] + 0.5*vx[i]*dt;
|
||||
ym[i] = y[i] + 0.5*vy[i]*dt;
|
||||
zm[i] = z[i] + 0.5*vz[i]*dt;
|
||||
vxm[i] = vx[i] + 0.5*ax[i]*dt;
|
||||
vym[i] = vy[i] + 0.5*ay[i]*dt;
|
||||
vzm[i] = vz[i] + 0.5*az[i]*dt;
|
||||
/* position updated with midpoint velocity (same as main.c) */
|
||||
x[i] = x[i] + vxm[i]*dt;
|
||||
y[i] = y[i] + vym[i]*dt;
|
||||
z[i] = z[i] + vzm[i]*dt;
|
||||
}
|
||||
|
||||
double *axm = (double*)alloca(n*sizeof(double)*3);
|
||||
double *aym = axm+n; double *azm = aym+n;
|
||||
accel_full(n, xm, ym, zm, vxm, vym, vzm, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, axm, aym, azm);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += axm[i]*dt;
|
||||
vy[i] += aym[i]*dt;
|
||||
vz[i] += azm[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 驱动力(与 main.c apply_driving_force 一致)────────── */
|
||||
static void apply_driving(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
double t, int step, double dt, Drivers *drv)
|
||||
{
|
||||
(void)n;
|
||||
if (!drv || drv->n_drivers == 0) return;
|
||||
const double TWO_PI = 2.0 * 3.14159265358979323846;
|
||||
|
||||
for (int d = 0; d < drv->n_drivers; d++) {
|
||||
int idx = drv->idx[d];
|
||||
double fx = drv->freq[d*3+0];
|
||||
double fy = drv->freq[d*3+1];
|
||||
double fz = drv->freq[d*3+2];
|
||||
|
||||
if (drv->has_period[d]) {
|
||||
double mf = fabs(fx) > fabs(fy) ? fabs(fx) : fabs(fy);
|
||||
if (fabs(fz) > mf) mf = fabs(fz);
|
||||
int period_steps = 0;
|
||||
if (mf > 1e-12)
|
||||
period_steps = (int)(drv->ncycles[d] / mf / dt);
|
||||
if (step > period_steps) {
|
||||
x[idx] = drv->freeze[d*3+0];
|
||||
y[idx] = drv->freeze[d*3+1];
|
||||
z[idx] = drv->freeze[d*3+2];
|
||||
vx[idx] = vy[idx] = vz[idx] = 0.0;
|
||||
continue;
|
||||
}
|
||||
|
||||
double px = drv->eq[d*3+0] + drv->amp[d*3+0]*cos(TWO_PI*fx*t + drv->phi[d*3+0]);
|
||||
double py = drv->eq[d*3+1] + drv->amp[d*3+1]*cos(TWO_PI*fy*t + drv->phi[d*3+1]);
|
||||
double pz = drv->eq[d*3+2] + drv->amp[d*3+2]*cos(TWO_PI*fz*t + drv->phi[d*3+2]);
|
||||
if (step == period_steps) {
|
||||
drv->freeze[d*3+0] = px;
|
||||
drv->freeze[d*3+1] = py;
|
||||
drv->freeze[d*3+2] = pz;
|
||||
}
|
||||
}
|
||||
|
||||
x[idx] = drv->eq[d*3+0] + drv->amp[d*3+0]*cos(TWO_PI*fx*t + drv->phi[d*3+0]);
|
||||
y[idx] = drv->eq[d*3+1] + drv->amp[d*3+1]*cos(TWO_PI*fy*t + drv->phi[d*3+1]);
|
||||
z[idx] = drv->eq[d*3+2] + drv->amp[d*3+2]*cos(TWO_PI*fz*t + drv->phi[d*3+2]);
|
||||
vx[idx] = -drv->amp[d*3+0]*TWO_PI*fx*sin(TWO_PI*fx*t + drv->phi[d*3+0]);
|
||||
vy[idx] = -drv->amp[d*3+1]*TWO_PI*fy*sin(TWO_PI*fy*t + drv->phi[d*3+1]);
|
||||
vz[idx] = -drv->amp[d*3+2]*TWO_PI*fz*sin(TWO_PI*fz*t + drv->phi[d*3+2]);
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 导出函数:run_dynamics
|
||||
*
|
||||
* 与 main.c 的计算顺序完全一致:
|
||||
* 1. leapfrog 初始化 v(-dt/2)
|
||||
* 2. 初始驱动 t=0
|
||||
* 3. 预热循环(不记录)
|
||||
* 4. 记录循环:drive → record → step → boundary → constraints
|
||||
*
|
||||
* 参数说明(所有数组均为 C-contiguous 行优先 float64/int32):
|
||||
* n_atoms 原子数
|
||||
* pos_init 初始位置 [n_atoms*3] x0,y0,z0, x1,y1,z1, ...
|
||||
* vel_init 初始速度 [n_atoms*3]
|
||||
* masses 质量 [n_atoms]
|
||||
* fixed 自由度约束 [n_atoms*3] int32, 1=固定
|
||||
* n_bonds 键数
|
||||
* bond_pairs 键对 [n_bonds*2] int32, 0-based local index
|
||||
* bond_k 刚度 [n_bonds]
|
||||
* bond_r0 平衡键长 [n_bonds]
|
||||
* box_a 盒子半边长
|
||||
* dt 时间步长
|
||||
* NT 总步数(含预热)
|
||||
* NSTEP 抽帧间隔
|
||||
* warmup_steps 预热步数
|
||||
* method_id 0=euler 1=implicit 2=midpoint 3=leapfrog
|
||||
* Gx/Gy/Gz 均匀重力场加速度分量
|
||||
* Bx/By/Bz 阻尼系数分量
|
||||
* gravity_field / elastic_force / damping_force 力开关
|
||||
* gravity_strength 原子间引力强度(暂未实现,留接口)
|
||||
* n_drivers 驱动原子数
|
||||
* drv_idx 驱动原子局部索引 [n_drivers] int32
|
||||
* drv_amp 振幅 [n_drivers*3]
|
||||
* drv_freq 频率 [n_drivers*3]
|
||||
* drv_phi 初相(弧度)[n_drivers*3]
|
||||
* drv_eq 平衡位置 [n_drivers*3]
|
||||
* drv_ncycles 周期数 [n_drivers] 0=不限
|
||||
* drv_has_period [n_drivers] int32
|
||||
* n_frames 输出帧数(Python 预计算:(NT-warmup)/NSTEP 向上取整)
|
||||
* out_x/y/z/vx/vy/vz 输出数组 [n_frames*n_atoms] 由 Python 预分配
|
||||
* progress_cb 进度回调(可为 NULL)
|
||||
*
|
||||
* 返回:0=成功,负数=错误
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
EXPORT int run_dynamics(
|
||||
int n_atoms,
|
||||
const double *pos_init,
|
||||
const double *vel_init,
|
||||
const double *masses,
|
||||
const int *fixed,
|
||||
int n_bonds,
|
||||
const int *bond_pairs,
|
||||
const double *bond_k,
|
||||
const double *bond_r0,
|
||||
double box_a, double dt,
|
||||
int NT, int NSTEP, int warmup_steps, int method_id,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
double gravity_strength,
|
||||
int n_drivers,
|
||||
const int *drv_idx,
|
||||
const double *drv_amp,
|
||||
const double *drv_freq,
|
||||
const double *drv_phi,
|
||||
const double *drv_eq,
|
||||
const double *drv_ncycles,
|
||||
const int *drv_has_period,
|
||||
int n_frames,
|
||||
double *out_x, double *out_y, double *out_z,
|
||||
double *out_vx, double *out_vy, double *out_vz,
|
||||
void (*progress_cb)(int step, int total))
|
||||
{
|
||||
(void)gravity_strength; /* 原子间引力暂未实现 */
|
||||
|
||||
int n = n_atoms;
|
||||
|
||||
/* ── 工作数组 ── */
|
||||
double *x = (double*)malloc(n*sizeof(double));
|
||||
double *y = (double*)malloc(n*sizeof(double));
|
||||
double *z = (double*)malloc(n*sizeof(double));
|
||||
double *vx = (double*)malloc(n*sizeof(double));
|
||||
double *vy = (double*)malloc(n*sizeof(double));
|
||||
double *vz = (double*)malloc(n*sizeof(double));
|
||||
if (!x||!y||!z||!vx||!vy||!vz) return -1;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
x[i]=pos_init[i*3+0]; y[i]=pos_init[i*3+1]; z[i]=pos_init[i*3+2];
|
||||
vx[i]=vel_init[i*3+0]; vy[i]=vel_init[i*3+1]; vz[i]=vel_init[i*3+2];
|
||||
}
|
||||
|
||||
/* ── 驱动结构 ── */
|
||||
Drivers drv;
|
||||
drv.n_drivers = n_drivers;
|
||||
drv.idx = drv_idx;
|
||||
drv.amp = drv_amp;
|
||||
drv.freq = drv_freq;
|
||||
drv.phi = drv_phi;
|
||||
drv.eq = drv_eq;
|
||||
drv.ncycles = drv_ncycles;
|
||||
drv.has_period = drv_has_period;
|
||||
drv.freeze = NULL;
|
||||
if (n_drivers > 0) {
|
||||
drv.freeze = (double*)calloc(n_drivers*3, sizeof(double));
|
||||
if (!drv.freeze) { free(x);free(y);free(z);free(vx);free(vy);free(vz); return -2; }
|
||||
}
|
||||
|
||||
/* ── 内联步进宏 ── */
|
||||
#define DO_STEP() do { \
|
||||
switch (method_id) { \
|
||||
case 0: euler_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
case 1: implicit_euler_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
case 2: midpoint_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
default: leapfrog_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
} \
|
||||
apply_boundary_and_constraints(n,x,y,z,vx,vy,vz,fixed,pos_init,box_a); \
|
||||
} while(0)
|
||||
|
||||
/* ── 蛙跳法:初始化 v(-dt/2) = v(0) - 0.5·a_c(0)·dt ── */
|
||||
if (method_id == 3) {
|
||||
double *ax0 = (double*)alloca(n*sizeof(double)*3);
|
||||
double *ay0 = ax0+n; double *az0 = ay0+n;
|
||||
accel_conservative(n, x, y, z, masses, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
n_bonds, bond_pairs, bond_k, bond_r0,
|
||||
ax0, ay0, az0);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] -= 0.5*ax0[i]*dt;
|
||||
vy[i] -= 0.5*ay0[i]*dt;
|
||||
vz[i] -= 0.5*az0[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 初始驱动 t=0(与 main.c 一致:leapfrog init 之后施加)── */
|
||||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, 0.0, 0, dt, &drv);
|
||||
|
||||
/* ── 预热(不记录)── */
|
||||
for (int s = 0; s < warmup_steps; s++) {
|
||||
double tw = (s + 1) * dt;
|
||||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, tw, s, dt, &drv);
|
||||
DO_STEP();
|
||||
}
|
||||
|
||||
/* ── 记录循环 ── */
|
||||
int record_steps = NT - warmup_steps;
|
||||
int prog_interval = record_steps / 100;
|
||||
if (prog_interval < 1) prog_interval = 1;
|
||||
int frame_idx = 0;
|
||||
|
||||
for (int s = 0; s < record_steps; s++) {
|
||||
if (progress_cb && s % prog_interval == 0 && s > 0)
|
||||
progress_cb(s, record_steps);
|
||||
|
||||
double t = (s + warmup_steps) * dt;
|
||||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, t, s, dt, &drv);
|
||||
|
||||
/* 抽帧记录(drive 之后,step 之前,与 main.c 一致)*/
|
||||
if (s % NSTEP == 0 && frame_idx < n_frames) {
|
||||
int base = frame_idx * n;
|
||||
for (int i = 0; i < n; i++) {
|
||||
out_x [base+i] = x[i]; out_y [base+i] = y[i]; out_z [base+i] = z[i];
|
||||
out_vx[base+i] = vx[i]; out_vy[base+i] = vy[i]; out_vz[base+i] = vz[i];
|
||||
}
|
||||
frame_idx++;
|
||||
}
|
||||
DO_STEP();
|
||||
}
|
||||
|
||||
#undef DO_STEP
|
||||
|
||||
free(x); free(y); free(z);
|
||||
free(vx); free(vy); free(vz);
|
||||
if (drv.freeze) free(drv.freeze);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# engines/src/cpp/Makefile
|
||||
# 编译 DLL 到 engines/release/(主程序通过 ctypes 直接调用)
|
||||
|
||||
CXX = g++
|
||||
LIB_SRC = dynamics_lib.cpp
|
||||
|
||||
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
|
||||
|
||||
# Windows 检测:Msys2/MINGW 也视为 Windows
|
||||
IS_WINDOWS := $(findstring MINGW,$(UNAME_S))
|
||||
ifneq ($(IS_WINDOWS),)
|
||||
UNAME_S := Windows
|
||||
endif
|
||||
IS_WINDOWS := $(findstring MSYS,$(UNAME_S))
|
||||
ifneq ($(IS_WINDOWS),)
|
||||
UNAME_S := Windows
|
||||
endif
|
||||
|
||||
CXXFLAGS = -O3 -march=native -std=c++17 -Wall -Wextra -D_USE_MATH_DEFINES
|
||||
|
||||
# DLL 输出到 engines/release/
|
||||
DLL_DIR = ../../release
|
||||
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_cpp.so
|
||||
DLL_FLAGS = -shared -fPIC
|
||||
else ifeq ($(UNAME_S),Darwin)
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_cpp.dylib
|
||||
DLL_FLAGS = -dynamiclib
|
||||
else
|
||||
# Windows: 完全静态链接,避免依赖运行时 DLL
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_cpp.dll
|
||||
DLL_FLAGS = -shared -static
|
||||
endif
|
||||
|
||||
.PHONY: all dll clean
|
||||
|
||||
all: dll
|
||||
|
||||
dll: $(DLL_TARGET)
|
||||
|
||||
$(DLL_TARGET): $(LIB_SRC) | $(DLL_DIR)
|
||||
$(CXX) $(CXXFLAGS) $(DLL_FLAGS) -o $@ $(LIB_SRC)
|
||||
@echo " === C++ DLL built: $@ ==="
|
||||
|
||||
$(DLL_DIR):
|
||||
mkdir -p $(DLL_DIR)
|
||||
|
||||
clean:
|
||||
rm -f $(DLL_TARGET)
|
||||
@@ -0,0 +1,450 @@
|
||||
/**
|
||||
* engines/cpp/dynamics_lib.cpp
|
||||
* -----------------------------
|
||||
* 纯计算 DLL(C++ 版):无文件 I/O,所有数据由 Python 以 NumPy 数组传入。
|
||||
* 算法与 main.cpp / compute.py 保持完全一致。
|
||||
*
|
||||
* 编译(Windows):
|
||||
* g++ -O3 -march=native -std=c++17 -shared -o build/dynamics_cpp.dll dynamics_lib.cpp
|
||||
* 编译(Linux):
|
||||
* g++ -O3 -march=native -std=c++17 -shared -fPIC -o build/dynamics_cpp.so dynamics_lib.cpp
|
||||
* 编译(macOS):
|
||||
* g++ -O3 -march=native -std=c++17 -dynamiclib -o build/dynamics_cpp.dylib dynamics_lib.cpp
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
# define EXPORT extern "C" __declspec(dllexport)
|
||||
#else
|
||||
# define EXPORT extern "C" __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
|
||||
/* ── 驱动力结构体 ─────────────────────────────────────────── */
|
||||
struct Drivers {
|
||||
int n_drivers = 0;
|
||||
const int *idx = nullptr;
|
||||
const double *amp = nullptr;
|
||||
const double *freq = nullptr;
|
||||
const double *phi = nullptr;
|
||||
const double *eq = nullptr;
|
||||
const double *ncycles = nullptr;
|
||||
const int *has_period = nullptr;
|
||||
std::vector<double> freeze; /* [n_drivers*3] 冻结位置(period 结束时锁定)*/
|
||||
};
|
||||
|
||||
/* ── 加速度:保守力(弹簧键 + 均匀重力场)────────────────── */
|
||||
static void accel_conservative(
|
||||
int n, const double *x, const double *y, const double *z,
|
||||
const double *m,
|
||||
double Gx, double Gy, double Gz,
|
||||
int gravity_field, int elastic_force,
|
||||
int n_bonds, const int *bond_pairs,
|
||||
const double *bond_k, const double *bond_r0,
|
||||
double *ax, double *ay, double *az)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
ax[i] = gravity_field ? Gx : 0.0;
|
||||
ay[i] = gravity_field ? Gy : 0.0;
|
||||
az[i] = gravity_field ? Gz : 0.0;
|
||||
}
|
||||
if (!elastic_force || n_bonds == 0) return;
|
||||
for (int b = 0; b < n_bonds; b++) {
|
||||
int ii = bond_pairs[b*2];
|
||||
int jj = bond_pairs[b*2+1];
|
||||
double dx = x[jj]-x[ii], dy = y[jj]-y[ii], dz = z[jj]-z[ii];
|
||||
double dist = std::sqrt(dx*dx + dy*dy + dz*dz);
|
||||
if (dist < 1e-12) continue;
|
||||
double fac = bond_k[b] * (dist - bond_r0[b]) / dist;
|
||||
double fx = fac*dx, fy = fac*dy, fz_b = fac*dz;
|
||||
ax[ii] += fx/m[ii]; ay[ii] += fy/m[ii]; az[ii] += fz_b/m[ii];
|
||||
ax[jj] -= fx/m[jj]; ay[jj] -= fy/m[jj]; az[jj] -= fz_b/m[jj];
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 完整加速度(含阻尼)────────────────────────────────── */
|
||||
static void accel_full(
|
||||
int n, const double *x, const double *y, const double *z,
|
||||
const double *vx, const double *vy, const double *vz,
|
||||
const double *m,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bond_pairs,
|
||||
const double *bond_k, const double *bond_r0,
|
||||
double *ax, double *ay, double *az)
|
||||
{
|
||||
accel_conservative(n, x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
n_bonds, bond_pairs, bond_k, bond_r0,
|
||||
ax, ay, az);
|
||||
if (damping_force) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
ax[i] -= Bx * vx[i] / m[i];
|
||||
ay[i] -= By * vy[i] / m[i];
|
||||
az[i] -= Bz * vz[i] / m[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 边界:反弹 ──────────────────────────────────────────── */
|
||||
static inline void _limit1(double &p, double &v, double lo, double hi) {
|
||||
if (p > hi) { p = hi; v = -std::fabs(v); }
|
||||
if (p < lo) { p = lo; v = std::fabs(v); }
|
||||
}
|
||||
|
||||
/* ── 边界:回绕 ──────────────────────────────────────────── */
|
||||
static inline void _wrap1(double &p, double lo, double hi) {
|
||||
if (p > hi) p = lo;
|
||||
if (p < lo) p = hi;
|
||||
}
|
||||
|
||||
/* ── 边界 + 固定约束 ────────────────────────────────────── */
|
||||
static void apply_boundary_and_constraints(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const int *fixed, const double *pos_init, double box_a)
|
||||
{
|
||||
double lo = -box_a, hi = box_a;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
_limit1(x[i], vx[i], lo, hi);
|
||||
_limit1(y[i], vy[i], lo, hi);
|
||||
_limit1(z[i], vz[i], lo, hi);
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
_wrap1(x[i], lo, hi);
|
||||
_wrap1(y[i], lo, hi);
|
||||
_wrap1(z[i], lo, hi);
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0]) { x[i] = pos_init[i*3+0]; vx[i] = 0.0; }
|
||||
if (fixed[i*3+1]) { y[i] = pos_init[i*3+1]; vy[i] = 0.0; }
|
||||
if (fixed[i*3+2]) { z[i] = pos_init[i*3+2]; vz[i] = 0.0; }
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 蛙跳法(与 main.cpp leapfrog_step 完全一致)
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void leapfrog_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0, double dt)
|
||||
{
|
||||
std::vector<double> buf(n * 3);
|
||||
double *ax = buf.data(), *ay = ax+n, *az = ay+n;
|
||||
accel_conservative(n, x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
bool has_damp = damping_force && (Bx != 0.0 || By != 0.0 || Bz != 0.0);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
if (has_damp) {
|
||||
double ax_ = Bx*dt/(2.0*m[i]);
|
||||
double ay_ = By*dt/(2.0*m[i]);
|
||||
double az_ = Bz*dt/(2.0*m[i]);
|
||||
vx[i] = (vx[i]*(1.0-ax_) + ax[i]*dt) / (1.0+ax_);
|
||||
vy[i] = (vy[i]*(1.0-ay_) + ay[i]*dt) / (1.0+ay_);
|
||||
vz[i] = (vz[i]*(1.0-az_) + az[i]*dt) / (1.0+az_);
|
||||
} else {
|
||||
vx[i] += ax[i]*dt;
|
||||
vy[i] += ay[i]*dt;
|
||||
vz[i] += az[i]*dt;
|
||||
}
|
||||
x[i] += vx[i]*dt;
|
||||
y[i] += vy[i]*dt;
|
||||
z[i] += vz[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 显式欧拉法
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void euler_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0, double dt)
|
||||
{
|
||||
std::vector<double> buf(n * 3);
|
||||
double *ax = buf.data(), *ay = ax+n, *az = ay+n;
|
||||
accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
x[i] += vx[i]*dt; y[i] += vy[i]*dt; z[i] += vz[i]*dt;
|
||||
vx[i]+= ax[i]*dt; vy[i]+= ay[i]*dt; vz[i]+= az[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 隐式欧拉法(与 main.cpp implicit_euler_step 完全一致)
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void implicit_euler_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0, double dt)
|
||||
{
|
||||
std::vector<double> vbuf(n * 3), abuf(n * 3);
|
||||
double *vxn = vbuf.data(), *vyn = vxn+n, *vzn = vyn+n;
|
||||
double *ax = abuf.data(), *ay = ax+n, *az = ay+n;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) {
|
||||
vxn[i] = vyn[i] = vzn[i] = 0.0; continue;
|
||||
}
|
||||
double gx = Bx/m[i], gy = By/m[i], gz = Bz/m[i];
|
||||
vxn[i] = (vx[i] + Gx*dt) / (1.0 + gx*dt);
|
||||
vyn[i] = (vy[i] + Gy*dt) / (1.0 + gy*dt);
|
||||
vzn[i] = (vz[i] + Gz*dt) / (1.0 + gz*dt);
|
||||
}
|
||||
accel_full(n, x, y, z, vxn, vyn, vzn, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += ax[i]*dt; vy[i] += ay[i]*dt; vz[i] += az[i]*dt;
|
||||
x[i] += vx[i]*dt; y[i] += vy[i]*dt; z[i] += vz[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 中点法(与 main.cpp midpoint_step 完全一致)
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void midpoint_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0, double dt)
|
||||
{
|
||||
std::vector<double> buf(n * 9);
|
||||
double *ax = buf.data();
|
||||
double *ay = ax+n; double *az = ay+n;
|
||||
double *xm = az+n; double *ym = xm+n; double *zm = ym+n;
|
||||
double *vxm = zm+n; double *vym = vxm+n; double *vzm = vym+n;
|
||||
|
||||
accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) {
|
||||
xm[i]=x[i]; ym[i]=y[i]; zm[i]=z[i];
|
||||
vxm[i]=vym[i]=vzm[i]=0.0; continue;
|
||||
}
|
||||
xm[i] = x[i] + 0.5*vx[i]*dt;
|
||||
ym[i] = y[i] + 0.5*vy[i]*dt;
|
||||
zm[i] = z[i] + 0.5*vz[i]*dt;
|
||||
vxm[i] = vx[i] + 0.5*ax[i]*dt;
|
||||
vym[i] = vy[i] + 0.5*ay[i]*dt;
|
||||
vzm[i] = vz[i] + 0.5*az[i]*dt;
|
||||
x[i] = x[i] + vxm[i]*dt;
|
||||
y[i] = y[i] + vym[i]*dt;
|
||||
z[i] = z[i] + vzm[i]*dt;
|
||||
}
|
||||
|
||||
std::vector<double> abuf(n * 3);
|
||||
double *axm = abuf.data(), *aym = axm+n, *azm = aym+n;
|
||||
accel_full(n, xm, ym, zm, vxm, vym, vzm, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, axm, aym, azm);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += axm[i]*dt;
|
||||
vy[i] += aym[i]*dt;
|
||||
vz[i] += azm[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 驱动力 ─────────────────────────────────────────────── */
|
||||
static void apply_driving(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
double t, int step, double dt, Drivers &drv)
|
||||
{
|
||||
(void)n;
|
||||
if (drv.n_drivers == 0) return;
|
||||
constexpr double TWO_PI = 2.0 * 3.14159265358979323846;
|
||||
|
||||
for (int d = 0; d < drv.n_drivers; d++) {
|
||||
int idx = drv.idx[d];
|
||||
double fx = drv.freq[d*3+0];
|
||||
double fy = drv.freq[d*3+1];
|
||||
double fz = drv.freq[d*3+2];
|
||||
|
||||
if (drv.has_period[d]) {
|
||||
double mf = std::fabs(fx) > std::fabs(fy) ? std::fabs(fx) : std::fabs(fy);
|
||||
if (std::fabs(fz) > mf) mf = std::fabs(fz);
|
||||
int period_steps = 0;
|
||||
if (mf > 1e-12)
|
||||
period_steps = (int)(drv.ncycles[d] / mf / dt);
|
||||
if (step > period_steps) {
|
||||
x[idx] = drv.freeze[d*3+0];
|
||||
y[idx] = drv.freeze[d*3+1];
|
||||
z[idx] = drv.freeze[d*3+2];
|
||||
vx[idx] = vy[idx] = vz[idx] = 0.0;
|
||||
continue;
|
||||
}
|
||||
double px = drv.eq[d*3+0] + drv.amp[d*3+0]*std::cos(TWO_PI*fx*t + drv.phi[d*3+0]);
|
||||
double py = drv.eq[d*3+1] + drv.amp[d*3+1]*std::cos(TWO_PI*fy*t + drv.phi[d*3+1]);
|
||||
double pz = drv.eq[d*3+2] + drv.amp[d*3+2]*std::cos(TWO_PI*fz*t + drv.phi[d*3+2]);
|
||||
if (step == period_steps) {
|
||||
drv.freeze[d*3+0] = px;
|
||||
drv.freeze[d*3+1] = py;
|
||||
drv.freeze[d*3+2] = pz;
|
||||
}
|
||||
}
|
||||
x[idx] = drv.eq[d*3+0] + drv.amp[d*3+0]*std::cos(TWO_PI*fx*t + drv.phi[d*3+0]);
|
||||
y[idx] = drv.eq[d*3+1] + drv.amp[d*3+1]*std::cos(TWO_PI*fy*t + drv.phi[d*3+1]);
|
||||
z[idx] = drv.eq[d*3+2] + drv.amp[d*3+2]*std::cos(TWO_PI*fz*t + drv.phi[d*3+2]);
|
||||
vx[idx] = -drv.amp[d*3+0]*TWO_PI*fx*std::sin(TWO_PI*fx*t + drv.phi[d*3+0]);
|
||||
vy[idx] = -drv.amp[d*3+1]*TWO_PI*fy*std::sin(TWO_PI*fy*t + drv.phi[d*3+1]);
|
||||
vz[idx] = -drv.amp[d*3+2]*TWO_PI*fz*std::sin(TWO_PI*fz*t + drv.phi[d*3+2]);
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 导出函数:run_dynamics(接口与 C 版完全相同)
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
EXPORT int run_dynamics(
|
||||
int n_atoms,
|
||||
const double *pos_init,
|
||||
const double *vel_init,
|
||||
const double *masses,
|
||||
const int *fixed,
|
||||
int n_bonds,
|
||||
const int *bond_pairs,
|
||||
const double *bond_k,
|
||||
const double *bond_r0,
|
||||
double box_a, double dt,
|
||||
int NT, int NSTEP, int warmup_steps, int method_id,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
double gravity_strength,
|
||||
int n_drivers,
|
||||
const int *drv_idx,
|
||||
const double *drv_amp,
|
||||
const double *drv_freq,
|
||||
const double *drv_phi,
|
||||
const double *drv_eq,
|
||||
const double *drv_ncycles,
|
||||
const int *drv_has_period,
|
||||
int n_frames,
|
||||
double *out_x, double *out_y, double *out_z,
|
||||
double *out_vx, double *out_vy, double *out_vz,
|
||||
void (*progress_cb)(int step, int total))
|
||||
{
|
||||
(void)gravity_strength;
|
||||
int n = n_atoms;
|
||||
|
||||
std::vector<double> xv(n), yv(n), zv(n);
|
||||
std::vector<double> vxv(n), vyv(n), vzv(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
xv[i]=pos_init[i*3+0]; yv[i]=pos_init[i*3+1]; zv[i]=pos_init[i*3+2];
|
||||
vxv[i]=vel_init[i*3+0]; vyv[i]=vel_init[i*3+1]; vzv[i]=vel_init[i*3+2];
|
||||
}
|
||||
double *x=xv.data(), *y=yv.data(), *z=zv.data();
|
||||
double *vx=vxv.data(), *vy=vyv.data(), *vz=vzv.data();
|
||||
|
||||
Drivers drv;
|
||||
drv.n_drivers = n_drivers;
|
||||
drv.idx = drv_idx;
|
||||
drv.amp = drv_amp;
|
||||
drv.freq = drv_freq;
|
||||
drv.phi = drv_phi;
|
||||
drv.eq = drv_eq;
|
||||
drv.ncycles = drv_ncycles;
|
||||
drv.has_period = drv_has_period;
|
||||
if (n_drivers > 0)
|
||||
drv.freeze.assign(n_drivers * 3, 0.0);
|
||||
|
||||
#define DO_STEP() do { \
|
||||
switch (method_id) { \
|
||||
case 0: euler_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
case 1: implicit_euler_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
case 2: midpoint_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
default: leapfrog_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
} \
|
||||
apply_boundary_and_constraints(n,x,y,z,vx,vy,vz,fixed,pos_init,box_a); \
|
||||
} while(0)
|
||||
|
||||
/* 蛙跳法:初始化 v(-dt/2) */
|
||||
if (method_id == 3) {
|
||||
std::vector<double> ibuf(n * 3);
|
||||
double *ax0=ibuf.data(), *ay0=ax0+n, *az0=ay0+n;
|
||||
accel_conservative(n, x, y, z, masses, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
n_bonds, bond_pairs, bond_k, bond_r0,
|
||||
ax0, ay0, az0);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] -= 0.5*ax0[i]*dt;
|
||||
vy[i] -= 0.5*ay0[i]*dt;
|
||||
vz[i] -= 0.5*az0[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* 初始驱动 t=0 */
|
||||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, 0.0, 0, dt, drv);
|
||||
|
||||
/* 预热 */
|
||||
for (int s = 0; s < warmup_steps; s++) {
|
||||
double tw = (s + 1) * dt;
|
||||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, tw, s, dt, drv);
|
||||
DO_STEP();
|
||||
}
|
||||
|
||||
/* 记录循环 */
|
||||
int record_steps = NT - warmup_steps;
|
||||
int prog_interval = std::max(1, record_steps / 100);
|
||||
int frame_idx = 0;
|
||||
|
||||
for (int s = 0; s < record_steps; s++) {
|
||||
if (progress_cb && s % prog_interval == 0 && s > 0)
|
||||
progress_cb(s, record_steps);
|
||||
|
||||
double t = (s + warmup_steps) * dt;
|
||||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, t, s, dt, drv);
|
||||
|
||||
if (s % NSTEP == 0 && frame_idx < n_frames) {
|
||||
int base = frame_idx * n;
|
||||
for (int i = 0; i < n; i++) {
|
||||
out_x [base+i] = x[i]; out_y [base+i] = y[i]; out_z [base+i] = z[i];
|
||||
out_vx[base+i] = vx[i]; out_vy[base+i] = vy[i]; out_vz[base+i] = vz[i];
|
||||
}
|
||||
frame_idx++;
|
||||
}
|
||||
DO_STEP();
|
||||
}
|
||||
|
||||
#undef DO_STEP
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# engines/src/fortran/Makefile
|
||||
# 编译 DLL 到 engines/release/(主程序通过 ctypes 直接调用)
|
||||
|
||||
FC = gfortran
|
||||
FFLAGS = -O3 -march=native -Wall -Wextra
|
||||
LIB_SRC = dynamics_lib.f90
|
||||
|
||||
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
|
||||
|
||||
# Windows 检测:Msys2/MINGW 也视为 Windows
|
||||
IS_WINDOWS := $(findstring MINGW,$(UNAME_S))
|
||||
ifneq ($(IS_WINDOWS),)
|
||||
UNAME_S := Windows
|
||||
endif
|
||||
IS_WINDOWS := $(findstring MSYS,$(UNAME_S))
|
||||
ifneq ($(IS_WINDOWS),)
|
||||
UNAME_S := Windows
|
||||
endif
|
||||
|
||||
# DLL 输出到 engines/release/
|
||||
DLL_DIR = ../../release
|
||||
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_f90.so
|
||||
DLL_FLAGS = -shared -fPIC
|
||||
else ifeq ($(UNAME_S),Darwin)
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_f90.dylib
|
||||
DLL_FLAGS = -dynamiclib
|
||||
else
|
||||
# Windows: 完全静态链接,避免依赖 libgfortran-5.dll
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_f90.dll
|
||||
DLL_FLAGS = -shared -fPIC -static
|
||||
endif
|
||||
|
||||
.PHONY: all dll clean
|
||||
|
||||
all: dll
|
||||
|
||||
dll: $(DLL_TARGET)
|
||||
|
||||
$(DLL_TARGET): $(LIB_SRC) | $(DLL_DIR)
|
||||
$(FC) $(FFLAGS) $(DLL_FLAGS) -o $@ $(LIB_SRC)
|
||||
@echo " === Fortran DLL built: $@ ==="
|
||||
|
||||
$(DLL_DIR):
|
||||
mkdir -p $(DLL_DIR)
|
||||
|
||||
clean:
|
||||
rm -f $(DLL_TARGET)
|
||||
@@ -0,0 +1,483 @@
|
||||
! engines/fortran/dynamics_lib.f90
|
||||
! ---------------------------------
|
||||
! 纯计算 DLL(Fortran 版):无文件 I/O,由 Python ctypes 调用。
|
||||
! 算法与 main.f90 / compute.py 完全一致。
|
||||
! 使用 iso_c_binding 导出 C 兼容接口。
|
||||
!
|
||||
! 编译(Windows):
|
||||
! gfortran -O3 -march=native -shared -fPIC -o build/dynamics_f90.dll dynamics_lib.f90
|
||||
! 编译(Linux):
|
||||
! gfortran -O3 -march=native -shared -fPIC -o build/dynamics_f90.so dynamics_lib.f90
|
||||
! 编译(macOS):
|
||||
! gfortran -O3 -march=native -dynamiclib -o build/dynamics_f90.dylib dynamics_lib.f90
|
||||
|
||||
module dynamics_dll
|
||||
use iso_c_binding, only: c_int, c_double, c_funptr, c_f_procpointer, c_associated
|
||||
implicit none
|
||||
private
|
||||
|
||||
real(c_double), parameter :: TWO_PI = 2.0d0 * 3.14159265358979323846d0
|
||||
|
||||
public :: run_dynamics
|
||||
|
||||
contains
|
||||
|
||||
! ── 保守加速度 ───────────────────────────────────────────────
|
||||
subroutine accel_conservative(n, x, y, z, m, Gx, Gy, Gz, &
|
||||
gravity_field, elastic_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, &
|
||||
ax, ay, az)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, n_bonds
|
||||
real(c_double), intent(in) :: x(n), y(n), z(n), m(n)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz
|
||||
integer, intent(in) :: bond_pairs(2, n_bonds)
|
||||
real(c_double), intent(in) :: bond_k(n_bonds), bond_r0(n_bonds)
|
||||
real(c_double), intent(out) :: ax(n), ay(n), az(n)
|
||||
|
||||
integer :: b, ii, jj
|
||||
real(c_double) :: dx, dy, dz, dist, fac, fx, fy, fz_b
|
||||
|
||||
if (gravity_field /= 0) then
|
||||
ax = Gx; ay = Gy; az = Gz
|
||||
else
|
||||
ax = 0.0d0; ay = 0.0d0; az = 0.0d0
|
||||
end if
|
||||
|
||||
if (elastic_force == 0 .or. n_bonds == 0) return
|
||||
|
||||
do b = 1, n_bonds
|
||||
ii = bond_pairs(1, b) + 1 ! 0-based → 1-based
|
||||
jj = bond_pairs(2, b) + 1
|
||||
dx = x(jj)-x(ii); dy = y(jj)-y(ii); dz = z(jj)-z(ii)
|
||||
dist = sqrt(dx*dx + dy*dy + dz*dz)
|
||||
if (dist < 1.0d-12) cycle
|
||||
fac = bond_k(b) * (dist - bond_r0(b)) / dist
|
||||
fx = fac*dx; fy = fac*dy; fz_b = fac*dz
|
||||
ax(ii) = ax(ii) + fx/m(ii); ay(ii) = ay(ii) + fy/m(ii); az(ii) = az(ii) + fz_b/m(ii)
|
||||
ax(jj) = ax(jj) - fx/m(jj); ay(jj) = ay(jj) - fy/m(jj); az(jj) = az(jj) - fz_b/m(jj)
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ── 完整加速度(含阻尼)──────────────────────────────────────
|
||||
subroutine accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax, ay, az)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, damping_force, n_bonds
|
||||
real(c_double), intent(in) :: x(n), y(n), z(n), vx(n), vy(n), vz(n), m(n)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz, Bx, By, Bz
|
||||
integer, intent(in) :: bond_pairs(2, n_bonds)
|
||||
real(c_double), intent(in) :: bond_k(n_bonds), bond_r0(n_bonds)
|
||||
real(c_double), intent(out) :: ax(n), ay(n), az(n)
|
||||
|
||||
integer :: i
|
||||
|
||||
call accel_conservative(n, x, y, z, m, Gx, Gy, Gz, &
|
||||
gravity_field, elastic_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax, ay, az)
|
||||
if (damping_force /= 0) then
|
||||
do i = 1, n
|
||||
ax(i) = ax(i) - Bx*vx(i)/m(i)
|
||||
ay(i) = ay(i) - By*vy(i)/m(i)
|
||||
az(i) = az(i) - Bz*vz(i)/m(i)
|
||||
end do
|
||||
end if
|
||||
end subroutine
|
||||
|
||||
! ── 边界 + 固定约束 ──────────────────────────────────────────
|
||||
subroutine apply_bc(n, x, y, z, vx, vy, vz, fixed, pos_init, box_a)
|
||||
integer, intent(in) :: n
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
integer, intent(in) :: fixed(3, n)
|
||||
real(c_double), intent(in) :: pos_init(3, n), box_a
|
||||
|
||||
integer :: i
|
||||
real(c_double) :: lo, hi
|
||||
|
||||
lo = -box_a; hi = box_a
|
||||
|
||||
! 反弹
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) cycle
|
||||
if (x(i)>hi) then; x(i)=hi; vx(i)=-abs(vx(i)); end if
|
||||
if (x(i)<lo) then; x(i)=lo; vx(i)= abs(vx(i)); end if
|
||||
if (y(i)>hi) then; y(i)=hi; vy(i)=-abs(vy(i)); end if
|
||||
if (y(i)<lo) then; y(i)=lo; vy(i)= abs(vy(i)); end if
|
||||
if (z(i)>hi) then; z(i)=hi; vz(i)=-abs(vz(i)); end if
|
||||
if (z(i)<lo) then; z(i)=lo; vz(i)= abs(vz(i)); end if
|
||||
end do
|
||||
! 回绕
|
||||
do i = 1, n
|
||||
if (x(i)>hi) x(i)=lo; if (x(i)<lo) x(i)=hi
|
||||
if (y(i)>hi) y(i)=lo; if (y(i)<lo) y(i)=hi
|
||||
if (z(i)>hi) z(i)=lo; if (z(i)<lo) z(i)=hi
|
||||
end do
|
||||
! 逐自由度固定约束
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0) then; x(i)=pos_init(1,i); vx(i)=0.0d0; end if
|
||||
if (fixed(2,i)/=0) then; y(i)=pos_init(2,i); vy(i)=0.0d0; end if
|
||||
if (fixed(3,i)/=0) then; z(i)=pos_init(3,i); vz(i)=0.0d0; end if
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ── 蛙跳法 ───────────────────────────────────────────────────
|
||||
subroutine leapfrog_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, damping_force, n_bonds
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
real(c_double), intent(in) :: m(n), bond_k(n_bonds), bond_r0(n_bonds)
|
||||
integer, intent(in) :: fixed(3,n), bond_pairs(2,n_bonds)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz, Bx, By, Bz, dt
|
||||
|
||||
real(c_double) :: ax(n), ay(n), az(n), ax_, ay_, az_
|
||||
logical :: has_damp
|
||||
integer :: i
|
||||
|
||||
call accel_conservative(n, x, y, z, m, Gx, Gy, Gz, &
|
||||
gravity_field, elastic_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax, ay, az)
|
||||
has_damp = (damping_force/=0) .and. (abs(Bx)+abs(By)+abs(Bz) > 0.0d0)
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) cycle
|
||||
if (has_damp) then
|
||||
ax_ = Bx*dt/(2.0d0*m(i)); ay_ = By*dt/(2.0d0*m(i)); az_ = Bz*dt/(2.0d0*m(i))
|
||||
vx(i) = (vx(i)*(1.0d0-ax_) + ax(i)*dt)/(1.0d0+ax_)
|
||||
vy(i) = (vy(i)*(1.0d0-ay_) + ay(i)*dt)/(1.0d0+ay_)
|
||||
vz(i) = (vz(i)*(1.0d0-az_) + az(i)*dt)/(1.0d0+az_)
|
||||
else
|
||||
vx(i) = vx(i)+ax(i)*dt; vy(i) = vy(i)+ay(i)*dt; vz(i) = vz(i)+az(i)*dt
|
||||
end if
|
||||
x(i) = x(i)+vx(i)*dt; y(i) = y(i)+vy(i)*dt; z(i) = z(i)+vz(i)*dt
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ── 显式欧拉法 ───────────────────────────────────────────────
|
||||
subroutine euler_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, damping_force, n_bonds
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
real(c_double), intent(in) :: m(n), bond_k(n_bonds), bond_r0(n_bonds)
|
||||
integer, intent(in) :: fixed(3,n), bond_pairs(2,n_bonds)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz, Bx, By, Bz, dt
|
||||
|
||||
real(c_double) :: ax(n), ay(n), az(n)
|
||||
integer :: i
|
||||
|
||||
call accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax, ay, az)
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) cycle
|
||||
x(i) = x(i)+vx(i)*dt; y(i) = y(i)+vy(i)*dt; z(i) = z(i)+vz(i)*dt
|
||||
vx(i)= vx(i)+ax(i)*dt; vy(i)= vy(i)+ay(i)*dt; vz(i)= vz(i)+az(i)*dt
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ── 隐式欧拉法 ───────────────────────────────────────────────
|
||||
subroutine implicit_euler_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, damping_force, n_bonds
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
real(c_double), intent(in) :: m(n), bond_k(n_bonds), bond_r0(n_bonds)
|
||||
integer, intent(in) :: fixed(3,n), bond_pairs(2,n_bonds)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz, Bx, By, Bz, dt
|
||||
|
||||
real(c_double) :: vxn(n), vyn(n), vzn(n), ax(n), ay(n), az(n)
|
||||
real(c_double) :: gamma_x, gamma_y, gamma_z
|
||||
integer :: i
|
||||
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) then
|
||||
vxn(i)=0.0d0; vyn(i)=0.0d0; vzn(i)=0.0d0; cycle
|
||||
end if
|
||||
gamma_x = Bx/m(i); gamma_y = By/m(i); gamma_z = Bz/m(i)
|
||||
vxn(i) = (vx(i)+Gx*dt)/(1.0d0+gamma_x*dt)
|
||||
vyn(i) = (vy(i)+Gy*dt)/(1.0d0+gamma_y*dt)
|
||||
vzn(i) = (vz(i)+Gz*dt)/(1.0d0+gamma_z*dt)
|
||||
end do
|
||||
call accel_full(n, x, y, z, vxn, vyn, vzn, m, Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax, ay, az)
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) cycle
|
||||
vx(i)=vx(i)+ax(i)*dt; vy(i)=vy(i)+ay(i)*dt; vz(i)=vz(i)+az(i)*dt
|
||||
x(i) =x(i) +vx(i)*dt; y(i) =y(i) +vy(i)*dt; z(i) =z(i) +vz(i)*dt
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ── 中点法 ───────────────────────────────────────────────────
|
||||
subroutine midpoint_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, damping_force, n_bonds
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
real(c_double), intent(in) :: m(n), bond_k(n_bonds), bond_r0(n_bonds)
|
||||
integer, intent(in) :: fixed(3,n), bond_pairs(2,n_bonds)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz, Bx, By, Bz, dt
|
||||
|
||||
real(c_double) :: ax(n), ay(n), az(n)
|
||||
real(c_double) :: xm(n), ym(n), zm(n), vxm(n), vym(n), vzm(n)
|
||||
real(c_double) :: axm(n), aym(n), azm(n)
|
||||
integer :: i
|
||||
|
||||
call accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax, ay, az)
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) then
|
||||
xm(i)=x(i); ym(i)=y(i); zm(i)=z(i)
|
||||
vxm(i)=0.0d0; vym(i)=0.0d0; vzm(i)=0.0d0; cycle
|
||||
end if
|
||||
xm(i) = x(i) +0.5d0*vx(i)*dt; ym(i) = y(i) +0.5d0*vy(i)*dt; zm(i) = z(i) +0.5d0*vz(i)*dt
|
||||
vxm(i) = vx(i)+0.5d0*ax(i)*dt; vym(i) = vy(i)+0.5d0*ay(i)*dt; vzm(i) = vz(i)+0.5d0*az(i)*dt
|
||||
x(i) = x(i) +vxm(i)*dt; y(i) = y(i) +vym(i)*dt; z(i) = z(i) +vzm(i)*dt
|
||||
end do
|
||||
call accel_full(n, xm, ym, zm, vxm, vym, vzm, m, Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, axm, aym, azm)
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) cycle
|
||||
vx(i)=vx(i)+axm(i)*dt; vy(i)=vy(i)+aym(i)*dt; vz(i)=vz(i)+azm(i)*dt
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ── 驱动力施加 ───────────────────────────────────────────────
|
||||
subroutine apply_drive(n, x, y, z, vx, vy, vz, t, step, dt, &
|
||||
nd, drv_idx, drv_amp, drv_freq, drv_phi, &
|
||||
drv_eq, drv_ncycles, drv_has_period, freeze)
|
||||
integer, intent(in) :: n, nd, step
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
real(c_double), intent(in) :: t, dt
|
||||
integer, intent(in) :: drv_idx(nd), drv_has_period(nd)
|
||||
real(c_double), intent(in) :: drv_amp(3,nd), drv_freq(3,nd)
|
||||
real(c_double), intent(in) :: drv_phi(3,nd), drv_eq(3,nd)
|
||||
real(c_double), intent(in) :: drv_ncycles(nd)
|
||||
real(c_double), intent(inout) :: freeze(3,nd)
|
||||
|
||||
integer :: d, idx, ps
|
||||
real(c_double) :: fx, fy, fz, mf, px, py, pz
|
||||
|
||||
do d = 1, nd
|
||||
idx = drv_idx(d) + 1 ! 0-based → 1-based
|
||||
fx = drv_freq(1,d); fy = drv_freq(2,d); fz = drv_freq(3,d)
|
||||
|
||||
if (drv_has_period(d) /= 0) then
|
||||
mf = max(abs(fx), max(abs(fy), abs(fz)))
|
||||
ps = 0
|
||||
if (mf > 1.0d-12) ps = int(drv_ncycles(d)/mf/dt)
|
||||
if (step > ps) then
|
||||
x(idx)=freeze(1,d); y(idx)=freeze(2,d); z(idx)=freeze(3,d)
|
||||
vx(idx)=0.0d0; vy(idx)=0.0d0; vz(idx)=0.0d0
|
||||
cycle
|
||||
end if
|
||||
px = drv_eq(1,d)+drv_amp(1,d)*cos(TWO_PI*fx*t+drv_phi(1,d))
|
||||
py = drv_eq(2,d)+drv_amp(2,d)*cos(TWO_PI*fy*t+drv_phi(2,d))
|
||||
pz = drv_eq(3,d)+drv_amp(3,d)*cos(TWO_PI*fz*t+drv_phi(3,d))
|
||||
if (step == ps) then
|
||||
freeze(1,d)=px; freeze(2,d)=py; freeze(3,d)=pz
|
||||
end if
|
||||
end if
|
||||
x(idx) = drv_eq(1,d)+drv_amp(1,d)*cos(TWO_PI*fx*t+drv_phi(1,d))
|
||||
y(idx) = drv_eq(2,d)+drv_amp(2,d)*cos(TWO_PI*fy*t+drv_phi(2,d))
|
||||
z(idx) = drv_eq(3,d)+drv_amp(3,d)*cos(TWO_PI*fz*t+drv_phi(3,d))
|
||||
vx(idx) = -drv_amp(1,d)*TWO_PI*fx*sin(TWO_PI*fx*t+drv_phi(1,d))
|
||||
vy(idx) = -drv_amp(2,d)*TWO_PI*fy*sin(TWO_PI*fy*t+drv_phi(2,d))
|
||||
vz(idx) = -drv_amp(3,d)*TWO_PI*fz*sin(TWO_PI*fz*t+drv_phi(3,d))
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ══════════════════════════════════════════════════════════════
|
||||
! 导出函数:run_dynamics(C 兼容接口,bind(C))
|
||||
! 接口与 C/C++ DLL 完全相同(扁平 C-contiguous 数组)。
|
||||
! ══════════════════════════════════════════════════════════════
|
||||
integer(c_int) function run_dynamics( &
|
||||
n_atoms, pos_init, vel_init, masses, fixed, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, &
|
||||
box_a, dt, NT, NSTEP, warmup_steps, method_id, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, gravity_strength, &
|
||||
n_drivers, drv_idx, drv_amp, drv_freq, drv_phi, drv_eq, &
|
||||
drv_ncycles, drv_has_period, &
|
||||
n_frames, out_x, out_y, out_z, out_vx, out_vy, out_vz, &
|
||||
progress_cb) &
|
||||
bind(C, name="run_dynamics")
|
||||
|
||||
integer(c_int), value, intent(in) :: n_atoms, n_bonds, NT, NSTEP
|
||||
integer(c_int), value, intent(in) :: warmup_steps, method_id
|
||||
integer(c_int), value, intent(in) :: gravity_field, elastic_force, damping_force
|
||||
integer(c_int), value, intent(in) :: n_drivers, n_frames
|
||||
real(c_double), value, intent(in) :: box_a, dt
|
||||
real(c_double), value, intent(in) :: Gx, Gy, Gz, Bx, By, Bz
|
||||
real(c_double), value, intent(in) :: gravity_strength
|
||||
|
||||
! 扁平数组:Python 传入 C-contiguous int32/float64
|
||||
! Fortran 以列优先解释,维度反转:(3,n) 对应 C 的 n×3
|
||||
real(c_double), intent(in) :: pos_init(3, n_atoms)
|
||||
real(c_double), intent(in) :: vel_init(3, n_atoms)
|
||||
real(c_double), intent(in) :: masses(n_atoms)
|
||||
integer(c_int), intent(in) :: fixed(3, n_atoms)
|
||||
integer(c_int), intent(in) :: bond_pairs(2, n_bonds)
|
||||
real(c_double), intent(in) :: bond_k(n_bonds), bond_r0(n_bonds)
|
||||
integer(c_int), intent(in) :: drv_idx(n_drivers)
|
||||
real(c_double), intent(in) :: drv_amp(3, n_drivers)
|
||||
real(c_double), intent(in) :: drv_freq(3, n_drivers)
|
||||
real(c_double), intent(in) :: drv_phi(3, n_drivers)
|
||||
real(c_double), intent(in) :: drv_eq(3, n_drivers)
|
||||
real(c_double), intent(in) :: drv_ncycles(n_drivers)
|
||||
integer(c_int), intent(in) :: drv_has_period(n_drivers)
|
||||
|
||||
real(c_double), intent(out) :: out_x(n_atoms, n_frames)
|
||||
real(c_double), intent(out) :: out_y(n_atoms, n_frames)
|
||||
real(c_double), intent(out) :: out_z(n_atoms, n_frames)
|
||||
real(c_double), intent(out) :: out_vx(n_atoms, n_frames)
|
||||
real(c_double), intent(out) :: out_vy(n_atoms, n_frames)
|
||||
real(c_double), intent(out) :: out_vz(n_atoms, n_frames)
|
||||
|
||||
type(c_funptr), value, intent(in) :: progress_cb
|
||||
|
||||
! 进度回调接口
|
||||
abstract interface
|
||||
subroutine cb_iface(step, total) bind(C)
|
||||
use iso_c_binding
|
||||
integer(c_int), value :: step, total
|
||||
end subroutine
|
||||
end interface
|
||||
procedure(cb_iface), pointer :: cb_ptr
|
||||
|
||||
integer :: n, s, frame_idx, record_steps, prog_interval, nd
|
||||
real(c_double) :: t, tw
|
||||
real(c_double), allocatable :: x(:), y(:), z(:), vx(:), vy(:), vz(:)
|
||||
real(c_double), allocatable :: ax0(:), ay0(:), az0(:)
|
||||
real(c_double), allocatable :: freeze(:,:)
|
||||
logical :: has_cb
|
||||
|
||||
n = n_atoms
|
||||
nd = n_drivers
|
||||
|
||||
allocate(x(n), y(n), z(n), vx(n), vy(n), vz(n))
|
||||
do s = 1, n
|
||||
x(s) = pos_init(1,s); y(s) = pos_init(2,s); z(s) = pos_init(3,s)
|
||||
vx(s) = vel_init(1,s); vy(s) = vel_init(2,s); vz(s) = vel_init(3,s)
|
||||
end do
|
||||
|
||||
allocate(freeze(3, max(nd,1)))
|
||||
freeze = 0.0d0
|
||||
|
||||
has_cb = c_associated(progress_cb)
|
||||
if (has_cb) call c_f_procpointer(progress_cb, cb_ptr)
|
||||
|
||||
! ── 蛙跳法:初始化 v(-dt/2) ─────────────────────────────
|
||||
if (method_id == 3) then
|
||||
allocate(ax0(n), ay0(n), az0(n))
|
||||
call accel_conservative(n, x, y, z, masses, Gx, Gy, Gz, &
|
||||
gravity_field, elastic_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax0, ay0, az0)
|
||||
do s = 1, n
|
||||
if (fixed(1,s)/=0 .and. fixed(2,s)/=0 .and. fixed(3,s)/=0) cycle
|
||||
vx(s)=vx(s)-0.5d0*ax0(s)*dt
|
||||
vy(s)=vy(s)-0.5d0*ay0(s)*dt
|
||||
vz(s)=vz(s)-0.5d0*az0(s)*dt
|
||||
end do
|
||||
deallocate(ax0, ay0, az0)
|
||||
end if
|
||||
|
||||
! ── 初始驱动 t=0 ─────────────────────────────────────────
|
||||
if (nd > 0) call apply_drive(n, x, y, z, vx, vy, vz, 0.0d0, 0, dt, &
|
||||
nd, drv_idx, drv_amp, drv_freq, drv_phi, &
|
||||
drv_eq, drv_ncycles, drv_has_period, freeze)
|
||||
|
||||
! ── 预热 ─────────────────────────────────────────────────
|
||||
do s = 0, warmup_steps-1
|
||||
tw = (s+1)*dt
|
||||
if (nd>0) call apply_drive(n, x, y, z, vx, vy, vz, tw, s, dt, &
|
||||
nd, drv_idx, drv_amp, drv_freq, drv_phi, &
|
||||
drv_eq, drv_ncycles, drv_has_period, freeze)
|
||||
call do_step(n, x, y, z, vx, vy, vz, masses, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt, method_id, &
|
||||
pos_init, box_a)
|
||||
end do
|
||||
|
||||
! ── 记录循环 ─────────────────────────────────────────────
|
||||
record_steps = NT - warmup_steps
|
||||
prog_interval = max(1, record_steps/100)
|
||||
frame_idx = 0
|
||||
|
||||
do s = 0, record_steps-1
|
||||
if (has_cb .and. mod(s, prog_interval)==0 .and. s>0) call cb_ptr(s, record_steps)
|
||||
|
||||
t = (s+warmup_steps)*dt
|
||||
if (nd>0) call apply_drive(n, x, y, z, vx, vy, vz, t, s, dt, &
|
||||
nd, drv_idx, drv_amp, drv_freq, drv_phi, &
|
||||
drv_eq, drv_ncycles, drv_has_period, freeze)
|
||||
|
||||
if (mod(s, NSTEP)==0 .and. frame_idx<n_frames) then
|
||||
frame_idx = frame_idx+1
|
||||
out_x(:, frame_idx) = x
|
||||
out_y(:, frame_idx) = y
|
||||
out_z(:, frame_idx) = z
|
||||
out_vx(:, frame_idx) = vx
|
||||
out_vy(:, frame_idx) = vy
|
||||
out_vz(:, frame_idx) = vz
|
||||
end if
|
||||
|
||||
call do_step(n, x, y, z, vx, vy, vz, masses, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt, method_id, &
|
||||
pos_init, box_a)
|
||||
end do
|
||||
|
||||
deallocate(x, y, z, vx, vy, vz, freeze)
|
||||
run_dynamics = 0
|
||||
|
||||
contains
|
||||
|
||||
subroutine do_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt, method_id, &
|
||||
pos_init, box_a)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, damping_force
|
||||
integer, intent(in) :: n_bonds, method_id
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
real(c_double), intent(in) :: m(n), bond_k(n_bonds), bond_r0(n_bonds)
|
||||
integer, intent(in) :: fixed(3,n), bond_pairs(2,n_bonds)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz, Bx, By, Bz, dt, box_a
|
||||
real(c_double), intent(in) :: pos_init(3, n)
|
||||
|
||||
select case (method_id)
|
||||
case (0)
|
||||
call euler_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
case (1)
|
||||
call implicit_euler_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
case (2)
|
||||
call midpoint_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
case default
|
||||
call leapfrog_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
end select
|
||||
call apply_bc(n, x, y, z, vx, vy, vz, fixed, pos_init, box_a)
|
||||
end subroutine do_step
|
||||
|
||||
end function run_dynamics
|
||||
|
||||
end module dynamics_dll
|
||||
@@ -0,0 +1,449 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dynamics 示例案例总览</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0d1117;
|
||||
--surface: #161b22;
|
||||
--border: #30363d;
|
||||
--text: #c9d1d9;
|
||||
--text-dim: #8b949e;
|
||||
--accent: #58a6ff;
|
||||
--green: #3fb950;
|
||||
--orange: #d29922;
|
||||
--red: #f85149;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", Helvetica, Arial, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
padding: 40px 24px;
|
||||
}
|
||||
.container { max-width: 960px; margin: 0 auto; }
|
||||
|
||||
h1 { font-size: 2rem; margin-bottom: 8px; color: #f0f6fc; }
|
||||
h1 small { font-size: 1rem; color: var(--text-dim); font-weight: 400; }
|
||||
.subtitle { color: var(--text-dim); margin-bottom: 32px; }
|
||||
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: #f0f6fc;
|
||||
}
|
||||
|
||||
/* 案例总览表格 */
|
||||
.case-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.case-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
transition: border-color 0.2s, transform 0.2s;
|
||||
}
|
||||
.case-card:hover {
|
||||
border-color: var(--accent);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.case-card .num {
|
||||
display: inline-block;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
background: var(--accent);
|
||||
color: #0d1117;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.case-card h3 {
|
||||
font-size: 1.05rem;
|
||||
margin-bottom: 6px;
|
||||
color: #f0f6fc;
|
||||
}
|
||||
.case-card p {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-dim);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.case-card .meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.tag-python { background: #3572A533; color: #3572A5; }
|
||||
.tag-c { background: #55555533; color: #aaa; }
|
||||
.tag-fortran { background: #73422233; color: #e9954a; }
|
||||
.tag-gravity { background: #d2992233; color: var(--orange); }
|
||||
.tag-spring { background: #3fb95033; color: var(--green); }
|
||||
.tag-drive { background: #58a6ff33; color: var(--accent); }
|
||||
.tag-warning { background: #f8514933; color: var(--red); }
|
||||
|
||||
/* 详情区域 */
|
||||
.detail-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.detail-card h3 {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 8px;
|
||||
color: #f0f6fc;
|
||||
}
|
||||
.detail-card h3 a { color: var(--accent); text-decoration: none; }
|
||||
.detail-card h3 a:hover { text-decoration: underline; }
|
||||
.detail-card p { color: var(--text-dim); margin-bottom: 8px; }
|
||||
.detail-card ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.detail-card li { font-size: 0.8rem; }
|
||||
.detail-card .highlight {
|
||||
background: #1f242e;
|
||||
border-left: 3px solid var(--accent);
|
||||
padding: 8px 12px;
|
||||
margin-top: 8px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* 使用指南 */
|
||||
.guide {
|
||||
background: #1f242e;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.guide h3 { color: #f0f6fc; margin-bottom: 12px; }
|
||||
.guide code {
|
||||
display: block;
|
||||
background: #0d1117;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
font-family: "SF Mono", "Fira Code", monospace;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 12px;
|
||||
color: var(--green);
|
||||
}
|
||||
.guide table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
|
||||
.guide th, .guide td {
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.guide th { color: var(--text-dim); font-weight: 600; }
|
||||
.guide td:first-child { color: var(--accent); font-weight: 500; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
body { padding: 16px; }
|
||||
.case-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<h1>Dynamics 示例案例 <small>v2.1</small></h1>
|
||||
<p class="subtitle">10 个从简单到复杂的物理模拟案例,展示分子动力学模拟框架的多种应用场景</p>
|
||||
|
||||
<h2>📋 案例总览</h2>
|
||||
<div class="case-grid">
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">01</span>
|
||||
<h3>双粒子弹簧系统</h3>
|
||||
<p>两个原子由弹簧连接,在重力场中运动</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-python">Python</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
<span class="tag tag-gravity">重力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">02</span>
|
||||
<h3>行星运动</h3>
|
||||
<p>地球绕太阳椭圆公转(万有引力)</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-python">Python</span>
|
||||
<span class="tag tag-gravity">万有引力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">03</span>
|
||||
<h3>日地月系统(失稳)</h3>
|
||||
<p>三体系统参数不当导致轨道发散</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-python">Python</span>
|
||||
<span class="tag tag-gravity">万有引力</span>
|
||||
<span class="tag tag-warning">失败案例</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">04</span>
|
||||
<h3>日地月系统(稳定)</h3>
|
||||
<p>三体系统稳定轨道,经希尔半径检验</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-python">Python</span>
|
||||
<span class="tag tag-gravity">万有引力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">05</span>
|
||||
<h3>一维原子链纵波</h3>
|
||||
<p>驱动原子 1 沿 x 振动,产生纵波传播</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-python">Python</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
<span class="tag tag-drive">驱动力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">06</span>
|
||||
<h3>一维原子链横波</h3>
|
||||
<p>带阻尼的横波传播(FPU 非线性)</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-c">C 引擎</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
<span class="tag tag-drive">驱动力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">07</span>
|
||||
<h3>一维链横波·双端驱动</h3>
|
||||
<p>原子 1 + 原子 120 同时驱动,波相遇干涉</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-c">C 引擎</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
<span class="tag tag-drive">驱动力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">08</span>
|
||||
<h3>双原子弹簧·C 引擎测试</h3>
|
||||
<p>2 原子快速验证 C 引擎正确性</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-c">C 引擎</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">09</span>
|
||||
<h3>一维链纵波·Fortran 引擎</h3>
|
||||
<p>Fortran 引擎驱动的纵波,GPU 实例化渲染</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-fortran">Fortran</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
<span class="tag tag-drive">驱动力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">10</span>
|
||||
<h3>一维链纵波·能量分析</h3>
|
||||
<p>纵波传播 + 轨迹/能量图绘制</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-c">C 引擎</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
<span class="tag tag-drive">驱动力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<h2>📖 各案例详情</h2>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case01/">case01 — 双粒子弹簧系统</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-python">Python 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
<li class="tag tag-gravity">重力场</li>
|
||||
</ul>
|
||||
<p>两个原子通过弹簧连接,在均匀重力场(G=[0,0,-9.8])中自由运动。展示重力作用下的耦合振动与落体运动的复合。</p>
|
||||
<div class="highlight">🔬 教学案例:算法 leapfrog,渲染 Sphere 模式,建议作为入门第一个案例</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case02/">case02 — 行星运动</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-python">Python 引擎</li>
|
||||
<li class="tag tag-gravity">万有引力</li>
|
||||
</ul>
|
||||
<p>模拟地球绕太阳的椭圆轨道运动。大质量中心体固定,小质量体绕行。</p>
|
||||
<div class="highlight">🌍 万有引力强度 gravity_strength=100.0,leapfrog 算法确保能量守恒</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case03/">case03 — 日地月系统(失败案例)</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-python">Python 引擎</li>
|
||||
<li class="tag tag-gravity">万有引力</li>
|
||||
<li class="tag tag-warning">失稳</li>
|
||||
</ul>
|
||||
<p>三体系统(太阳-地球-月球)。初始条件或参数设置不当,轨道不稳定。展示数值模拟中参数选择的重要性。</p>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case04/">case04 — 日地月系统(成功案例)</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-python">Python 引擎</li>
|
||||
<li class="tag tag-gravity">万有引力</li>
|
||||
</ul>
|
||||
<p>与 case03 相同的三体系统,但采用恰当的初始条件:地球置于近日点($r=10$),$v_z=520$ 接近圆轨道;月球缩至希尔半径($r_H\approx1.0$)以内的 $r=0.5$,相对速度 $v_{\text{rel}}\approx127$,确保月球被地球稳定束缚。</p>
|
||||
<div class="highlight">✅ 与 case03 对比学习:初始条件对数值稳定性的影响。地月距 0.5 在地球希尔半径以内,满足稳定性条件</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case05/">case05 — 一维原子链纵波</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-python">Python 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
<li class="tag tag-drive">驱动力</li>
|
||||
</ul>
|
||||
<p>60 原子沿 x 轴排列。原子 1 受 x 方向驱动力,产生沿链传播的纵波(压缩波)。原子 x 自由,y/z 锁定。</p>
|
||||
<div class="highlight">📈 纵波波速快(x 方向弹簧力线性),T_total=10, NSTEP=50</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case06/">case06 — 一维原子链横波(带阻尼)</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-c">C 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
<li class="tag tag-drive">驱动力</li>
|
||||
</ul>
|
||||
<p>120 原子沿 x 轴排列,带横向阻尼。驱动沿 z 方向,原子 z 自由,x/y 锁定。横波传播具有 FPU 型非线性。</p>
|
||||
<div class="highlight">⚡ C 引擎高性能计算,T_total=1000, NSTEP=500,支持运动相机</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case07/">case07 — 一维链横波·双端驱动</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-c">C 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
<li class="tag tag-drive">驱动力</li>
|
||||
</ul>
|
||||
<p>120 原子,原子 1 和原子 120 同时受 z 方向驱动(同频率、相位差 90°),两端向中间传播的横波相遇。</p>
|
||||
<div class="highlight">🌊 波干涉演示,视觉放大 display_amp=[1,1,10] 便于观察小幅度振动</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case08/">case08 — 双原子弹簧(C 引擎快速测试)</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-c">C 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
</ul>
|
||||
<p>2 原子弹簧系统,T_total=100 的短时模拟。用于快速验证 C 引擎的正确性和性能。</p>
|
||||
<div class="highlight">🧪 引擎快速验证用例,无动画输出</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case09/">case09 — 一维链纵波·Fortran 引擎</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-fortran">Fortran 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
<li class="tag tag-drive">驱动力</li>
|
||||
</ul>
|
||||
<p>40 原子沿 x 轴排列,Fortran 引擎驱动的纵波传播测试。使用 <code>use_marker: 1</code>(GPU 实例化 Marker 模式)加速渲染,验证 Fortran 引擎与其他引擎的输出兼容性。</p>
|
||||
<div class="highlight">🔧 Fortran 引擎兼容性验证,T_total=200, NSTEP=100。Marker 模式下 40 原子以 GPU 点精灵渲染,帧率高</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case10/">case10 — 一维链纵波·能量分析</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-c">C 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
<li class="tag tag-drive">驱动力</li>
|
||||
</ul>
|
||||
<p>40 原子纵波传播,支持轨迹/能量图绘制(step_plot=1)。原子 x/y/z 全部自由。</p>
|
||||
<div class="highlight">📊 能量分析演示,T_total=10, NSTEP=20</div>
|
||||
</div>
|
||||
|
||||
<h2>🚀 使用方法</h2>
|
||||
<div class="guide">
|
||||
<h3>命令行</h3>
|
||||
<code># 进入案例目录并运行<br>cd examples/case05<br>python run_dynamics.py<br><br># 仅运行模拟,跳过动画<br>python run_dynamics.py --no-plot<br><br># 手动启动 3D 动画<br>python ../../draw.py output/</code>
|
||||
|
||||
<h3>案例选择指南</h3>
|
||||
<table>
|
||||
<tr><th>目标</th><th>推荐案例</th></tr>
|
||||
<tr><td>快速上手框架</td><td>case01</td></tr>
|
||||
<tr><td>天体力学/万有引力</td><td>case02 / case04</td></tr>
|
||||
<tr><td>波动物理(纵波)</td><td>case05</td></tr>
|
||||
<tr><td>波动物理(横波/非线性/阻尼)</td><td>case06</td></tr>
|
||||
<tr><td>双端驱动/波干涉</td><td>case07</td></tr>
|
||||
<tr><td>引擎性能对比</td><td>case08 (C) / case09 (Fortran)</td></tr>
|
||||
<tr><td>能量分析</td><td>case10</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>配置</h3>
|
||||
<p style="color:var(--text-dim); font-size:0.875rem;">
|
||||
每个案例的 <code>input/input.txt</code> 可配置物理参数、力开关、算法、引擎、渲染方式等。
|
||||
</p>
|
||||
|
||||
<h3>引擎架构</h3>
|
||||
<p style="color:var(--text-dim); font-size:0.875rem;">
|
||||
外部引擎(C / C++ / Fortran)以 <strong>DLL 方式</strong> 运行,主程序通过 ctypes 在进程内直接调用,不启动子进程。DLL 预编译在 <code>engines/release/</code> 中,源码位于 <code>engines/src/{c,cpp,fortran}/</code>。重新编译:
|
||||
<code>cd engines/src/c && make dll</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>📁 框架结构</h2>
|
||||
<div class="detail-card">
|
||||
<pre style="font-size:0.825rem; color:var(--text-dim); line-height:1.5;">
|
||||
dynamics/
|
||||
├── dynamics.py # 统一运行入口
|
||||
├── compute.py # 物理引擎 + 显示数据生成
|
||||
├── draw.py # VisPy 3D 动画
|
||||
├── plot_wave.py # 波形能量图
|
||||
├── .gitattributes # DLL/二进制文件保护
|
||||
├── engines/
|
||||
│ ├── engine_dll.py # DLL 加载器(ctypes)
|
||||
│ ├── python/ # Python 引擎(dynamics_lib.py)
|
||||
│ ├── release/ # 预编译 DLL(C / C++ / Fortran)
|
||||
│ └── src/ # 引擎源码
|
||||
│ ├── c/ # C 源码 + Makefile → dynamics_c.dll
|
||||
│ ├── cpp/ # C++ 源码 + Makefile → dynamics_cpp.dll
|
||||
│ └── fortran/ # Fortran 源码 + Makefile → dynamics_f90.dll
|
||||
├── examples/ # 案例目录
|
||||
│ ├── case01/ ~ case10/
|
||||
└── output/ # 默认输出目录
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+73
-16
@@ -1,19 +1,23 @@
|
||||
# Dynamics 示例案例
|
||||
|
||||
本目录包含 6 个从简单到复杂的物理模拟案例,均基于 `../dynamics.py` 框架运行。
|
||||
本目录包含 10 个从简单到复杂的物理模拟案例,均基于 `../dynamics.py` 框架运行。
|
||||
|
||||
---
|
||||
|
||||
## 案例一览
|
||||
|
||||
| 案例 | 标题 | 简介 | 原子数 | 力类型 |
|
||||
|---|---|---|---|---|
|
||||
| [case01](./case01/) | **双粒子弹簧系统** | 两个原子由弹簧连接,在重力场中运动 | 2 | 重力 + 弹簧 |
|
||||
| [case02](./case02/) | **行星运动** | 地球绕太阳椭圆公转(万有引力) | 2 | 万有引力 |
|
||||
| [case03](./case03/) | **日地月系统(失败)** | 地球绕太阳、月球绕地球,参数不当导致失稳 | 3 | 万有引力 |
|
||||
| [case04](./case04/) | **日地月系统(成功)** | 地球绕太阳、月球绕地球,稳定轨道 | 3 | 万有引力 |
|
||||
| [case05](./case05/) | **一维原子链纵波** | 驱动原子 1 沿 x 轴振动,产生纵波传播 | 60 | 弹簧 + 驱动力 |
|
||||
| [case06](./case06/) | **一维原子链横波** | 驱动原子 1 沿 z 轴振动,产生横波传播 | 60 | 弹簧 + 驱动力 |
|
||||
| 案例 | 标题 | 简介 | 原子数 | 引擎 | 力类型 |
|
||||
|------|------|------|--------|------|--------|
|
||||
| [case01](./case01/) | **双粒子弹簧系统** | 两个原子由弹簧连接,在重力场中运动 | 2 | Python | 重力 + 弹簧 |
|
||||
| [case02](./case02/) | **行星运动** | 地球绕太阳椭圆公转(万有引力) | 2 | Python | 万有引力 |
|
||||
| [case03](./case03/) | **日地月系统(失稳)** | 地球绕太阳、月球绕地球,参数不当导致失稳 | 3 | Python | 万有引力 |
|
||||
| [case04](./case04/) | **日地月系统(稳定)** | 地球绕太阳、月球绕地球,稳定轨道 | 3 | Python | 万有引力 |
|
||||
| [case05](./case05/) | **一维原子链纵波** | 驱动原子 1 沿 x 轴振动,产生纵波传播 | 60 | Python | 弹簧 + 驱动力 |
|
||||
| [case06](./case06/) | **一维原子链横波(阻尼)** | 驱动原子 1 沿 z 轴振动,带阻尼的横波传播 | 120 | C | 弹簧 + 阻尼 + 驱动力 |
|
||||
| [case07](./case07/) | **一维原子链横波(双端驱动)** | 原子 1 和原子 120 同时受 z 方向驱动 | 120 | C | 弹簧 + 阻尼 + 驱动力 |
|
||||
| [case08](./case08/) | **双原子弹簧(C 引擎测试)** | 两个原子弹簧系统,C 引擎快速验证 | 2 | C | 弹簧 + 阻尼 + 驱动力 |
|
||||
| [case09](./case09/) | **一维链纵波(Fortran 引擎)** | Fortran 引擎驱动的纵波传播测试 | 40 | Fortran | 弹簧 + 驱动力 |
|
||||
| [case10](./case10/) | **一维链纵波(能量分析)** | 纵波传播 + 轨迹/能量图绘制 | 40 | C | 弹簧 + 驱动力 |
|
||||
|
||||
---
|
||||
|
||||
@@ -26,14 +30,16 @@
|
||||
- **力开关**:重力场开,弹簧键力开
|
||||
- **算法**:leapfrog(蛙跳法)
|
||||
- **物理**:重力 m·g + 弹簧胡克力
|
||||
- **渲染**:Sphere 模式(精细网格球体)
|
||||
|
||||
### case02 — 行星运动
|
||||
|
||||
模拟地球绕太阳的椭圆轨道运动(一个固定大质量中心体 + 一个绕行小质量体)。采用万有引力相互作用。
|
||||
|
||||
- **力开关**:万有引力开(含强度参数)
|
||||
- **力开关**:万有引力开(含强度参数 `gravity_strength: 100.0`)
|
||||
- **算法**:leapfrog(蛙跳法)
|
||||
- **物理**:牛顿万有引力 F = G·m₁·m₂/r²
|
||||
- **渲染**:Sphere 模式
|
||||
|
||||
### case03 — 日地月系统(失败案例)
|
||||
|
||||
@@ -60,14 +66,53 @@
|
||||
- **波速**:快(x 方向弹簧力为线性)
|
||||
- **渲染**:Marker 模式(GPU 实例化,60 原子)
|
||||
|
||||
### case06 — 一维原子链横波
|
||||
### case06 — 一维原子链横波(带阻尼)
|
||||
|
||||
与 case05 相同的原子链,但驱动力沿 z 方向 `z(t)=0.5·cos(2π·0.1·t+90°)`,原子 z 方向自由(fix_z=0),x/y 锁定。振动在横向传播,形成**横波**。
|
||||
120 个原子沿 x 轴等间距排列(间距 1),相邻原子用弹簧(k=1.0, L₀=1.0)连接,带横向阻尼(B=[0.01, 0, 0.01])。驱动在 z 方向 `z(t)=0.5·cos(2π·0.1·t+90°)`,原子 z 方向自由(fix_z=0),x/y 锁定。
|
||||
|
||||
- **力开关**:弹簧键力开,驱动力开
|
||||
- **力开关**:弹簧键力开,驱动力开,阻尼开
|
||||
- **算法**:leapfrog(蛙跳法)
|
||||
- **引擎**:C(高性能)
|
||||
- **参数**:T_total=1000, NSTEP=500
|
||||
- **波速**:慢(z 方向弹簧力呈几何非线性,类似 FPU 系统)
|
||||
- **渲染**:Marker 模式(GPU 实例化,60 原子)
|
||||
|
||||
### case07 — 一维原子链横波(双端驱动)
|
||||
|
||||
120 个原子沿 x 轴排列,原子 1 **和**原子 120 同时受 z 方向驱动力驱动(频率相同,初相位错开 90°),产生两端向中间传播的横波相遇。
|
||||
|
||||
- **力开关**:弹簧键力开,驱动力开,阻尼开
|
||||
- **引擎**:C
|
||||
- **参数**:T_total=1000, NSTEP=500
|
||||
- **视觉放大**:z 方向位移放大 10 倍(`display_amp: [1, 1, 10]`),便于观察小幅度横波
|
||||
|
||||
### case08 — 双原子弹簧(C 引擎快速测试)
|
||||
|
||||
2 个原子的简单弹簧系统,用于快速验证 C 引擎的正确性和性能。T_total 仅为 100s。
|
||||
|
||||
- **力开关**:弹簧键力开,驱动力开,阻尼开
|
||||
- **引擎**:C
|
||||
- **用途**:引擎验证 / 调试
|
||||
- **视觉放大**:z 方向位移放大 10 倍
|
||||
- **动画**:关闭(step_animation=0),仅输出波形图
|
||||
|
||||
### case09 — 一维链纵波(Fortran 引擎)
|
||||
|
||||
40 个原子沿 x 轴排列,使用 **Fortran 引擎**驱动的纵波传播模拟。验证 Fortran 引擎的输出兼容性和性能。
|
||||
|
||||
- **力开关**:弹簧键力开,驱动力开,阻尼关
|
||||
- **引擎**:Fortran
|
||||
- **参数**:T_total=200, NSTEP=100
|
||||
- **视觉放大**:z 方向位移放大 10 倍
|
||||
|
||||
### case10 — 一维链纵波(能量分析)
|
||||
|
||||
40 个原子沿 x 轴排列,纵波传播。与 case05 相比,x/y/z 全部自由(fix_x/y/z 均为 0),物理行为更复杂。支持轨迹/能量图绘制(step_plot=1)。
|
||||
|
||||
- **力开关**:弹簧键力开,驱动力开,阻尼关
|
||||
- **引擎**:C
|
||||
- **参数**:T_total=10, NSTEP=20
|
||||
- **视觉放大**:z 方向位移放大 10 倍
|
||||
- **动画**:关闭(step_animation=0),仅输出轨迹/能量图
|
||||
|
||||
---
|
||||
|
||||
@@ -77,7 +122,7 @@
|
||||
# 进入任意案例目录
|
||||
cd examples/case05
|
||||
|
||||
# 完整运行(模拟 + 采样 + 3D 动画)
|
||||
# 完整运行(模拟 + 3D 动画)
|
||||
python run_dynamics.py
|
||||
|
||||
# 仅运行模拟,跳过 3D 动画
|
||||
@@ -89,6 +134,18 @@ python ../../draw.py output/
|
||||
|
||||
每个案例的 `input/input.txt` 中可配置所有物理参数、力开关、算法、渲染方式等。
|
||||
|
||||
## 案例选择指南
|
||||
|
||||
| 你想做什么 | 推荐案例 |
|
||||
|-----------|---------|
|
||||
| 快速上手、理解基本框架 | case01 |
|
||||
| 天体力学 / 万有引力 | case02 / case04 |
|
||||
| 波动物理(纵波) | case05 |
|
||||
| 波动物理(横波、非线性、阻尼) | case06 |
|
||||
| 双端驱动/波干涉 | case07 |
|
||||
| 对比不同引擎性能 | case08 (C) / case09 (Fortran) |
|
||||
| 能量分析 | case10 |
|
||||
|
||||
## 框架结构
|
||||
|
||||
```
|
||||
@@ -101,6 +158,6 @@ dynamics/
|
||||
├── examples/ # 案例(本目录)
|
||||
│ ├── case01/
|
||||
│ ├── ...
|
||||
│ └── case06/
|
||||
│ └── case10/
|
||||
└── output/ # 默认输出目录
|
||||
```
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
为指定案例添加次紧邻键 (k2, k=100, r0=1.41421356)。
|
||||
|
||||
用法: python add_k2.py case16
|
||||
python add_k2.py case16 case17 case18
|
||||
python add_k2.py --all
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
def add_k2(case_dir):
|
||||
coord_path = os.path.join(case_dir, "input", "coord.txt")
|
||||
conn_path = os.path.join(case_dir, "input", "connection.txt")
|
||||
bond_path = os.path.join(case_dir, "input", "bond.txt")
|
||||
|
||||
if not os.path.exists(coord_path):
|
||||
print(f" [跳过] {case_dir}: 找不到 coord.txt")
|
||||
return False
|
||||
|
||||
# 读取 coord.txt 获取网格尺寸
|
||||
with open(coord_path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
n_atoms = len(lines) - 1 # 去掉表头
|
||||
N = int(n_atoms ** 0.5)
|
||||
if N * N != n_atoms:
|
||||
print(f" [跳过] {case_dir}: 非正方形网格 (n_atoms={n_atoms})")
|
||||
return False
|
||||
|
||||
print(f" {case_dir}: {N}x{N} 网格")
|
||||
|
||||
# 读取现有 connection.txt,检查是否已有 k2
|
||||
has_k2 = False
|
||||
if os.path.exists(conn_path):
|
||||
with open(conn_path, "r") as f:
|
||||
for line in f:
|
||||
if "k2" in line:
|
||||
has_k2 = True
|
||||
break
|
||||
|
||||
if has_k2:
|
||||
print(f" k2 已存在,跳过 connection.txt")
|
||||
else:
|
||||
# 追加 k2 键到 connection.txt
|
||||
with open(conn_path, "a", encoding="utf-8") as f:
|
||||
cnt = 0
|
||||
for row in range(N):
|
||||
for col in range(N):
|
||||
id1 = row * N + col + 1
|
||||
if col + 1 < N and row + 1 < N:
|
||||
f.write(f"{id1} {(row + 1) * N + (col + 1) + 1} k2\n")
|
||||
cnt += 1
|
||||
if col - 1 >= 0 and row + 1 < N:
|
||||
f.write(f"{id1} {(row + 1) * N + (col - 1) + 1} k2\n")
|
||||
cnt += 1
|
||||
print(f" connection.txt: 追加 {cnt} 条 k2 键")
|
||||
|
||||
# 检查 bond.txt 是否有 k2
|
||||
has_bond = False
|
||||
if os.path.exists(bond_path):
|
||||
with open(bond_path, "r") as f:
|
||||
for line in f:
|
||||
if line.startswith("k2"):
|
||||
has_bond = True
|
||||
break
|
||||
|
||||
if has_bond:
|
||||
print(f" bond.txt: k2 已存在")
|
||||
else:
|
||||
with open(bond_path, "a", encoding="utf-8") as f:
|
||||
f.write("k2 100.0 1.41421356\n")
|
||||
print(f" bond.txt: 追加 k2 定义")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
targets = []
|
||||
if "--all" in sys.argv:
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
for d in sorted(os.listdir(base)):
|
||||
if d.startswith("case") and os.path.isdir(os.path.join(base, d)):
|
||||
targets.append(os.path.join(base, d))
|
||||
else:
|
||||
for arg in sys.argv[1:]:
|
||||
if arg.startswith("--"):
|
||||
continue
|
||||
p = arg if os.path.isabs(arg) else os.path.join(os.path.dirname(os.path.abspath(__file__)), arg)
|
||||
targets.append(p)
|
||||
|
||||
for t in targets:
|
||||
add_k2(t)
|
||||
@@ -6,7 +6,7 @@
|
||||
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||
step_simulate: 1 # 运行物理模拟 → output/trajectory.txt
|
||||
step_sample: 1 # 抽帧 → output/display.txt
|
||||
step_sample: 0 # 抽帧 → output/display.txt
|
||||
step_plot: 1 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||
step_plot_wave: 0 # 绘制波形能量动画
|
||||
plot_wave_save_gif: 0 # 输出波形 GIF(需 step_plot_wave=1)
|
||||
|
||||
@@ -0,0 +1,829 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case04 — 日地月三体系统</title>
|
||||
<script>
|
||||
MathJax = {
|
||||
tex: { inlineMath: [['$','$'], ['\\(','\\)']], displayMath: [['$$','$$'], ['\\[','\\]']] },
|
||||
svg: { fontCache: 'global' }
|
||||
};
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js" async></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0d1117;
|
||||
--surface: #161b22;
|
||||
--border: #30363d;
|
||||
--text: #c9d1d9;
|
||||
--text-dim: #8b949e;
|
||||
--accent: #58a6ff;
|
||||
--green: #3fb950;
|
||||
--orange: #d29922;
|
||||
--red: #f85149;
|
||||
--teal: #56d4dd;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", Helvetica, Arial, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.8;
|
||||
padding: 40px 24px;
|
||||
}
|
||||
.container { max-width: 860px; margin: 0 auto; }
|
||||
|
||||
h1 { font-size: 1.8rem; margin-bottom: 6px; color: #f0f6fc; }
|
||||
h1 small { font-size: 1rem; color: var(--text-dim); font-weight: 400; }
|
||||
.subtitle { color: var(--text-dim); margin-bottom: 32px; }
|
||||
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
margin-top: 36px;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: #f0f6fc;
|
||||
}
|
||||
h3 { font-size: 1.1rem; margin-top: 24px; margin-bottom: 10px; color: #f0f6fc; }
|
||||
|
||||
p { margin-bottom: 12px; color: var(--text); }
|
||||
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 12px 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
th, td {
|
||||
text-align: left;
|
||||
padding: 8px 14px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { color: var(--text-dim); font-weight: 600; background: #1c2128; }
|
||||
|
||||
.highlight {
|
||||
background: #1f242e;
|
||||
border-left: 3px solid var(--accent);
|
||||
padding: 10px 16px;
|
||||
margin: 12px 0;
|
||||
border-radius: 0 6px 6px 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.highlight-eq {
|
||||
background: #1f242e;
|
||||
border-left: 3px solid var(--teal);
|
||||
padding: 14px 18px;
|
||||
margin: 14px 0;
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
.highlight-eq p { margin: 6px 0; }
|
||||
|
||||
code {
|
||||
background: #1c2128;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: "SF Mono", "Fira Code", monospace;
|
||||
font-size: 0.85em;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
margin: 2px;
|
||||
}
|
||||
.tag-python { background: #3572A533; color: #3572A5; }
|
||||
.tag-gravity { background: #d2992233; color: var(--orange); }
|
||||
|
||||
ul, ol { margin: 8px 0 12px 24px; }
|
||||
li { margin-bottom: 4px; }
|
||||
|
||||
.formula-block {
|
||||
overflow-x: auto;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.diagram-wrap {
|
||||
background: #0d1117;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 16px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.diagram-wrap canvas {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.diagram-controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.diagram-controls label {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
.diagram-controls input[type="range"] {
|
||||
width: 200px;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
.diagram-controls .val {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: var(--teal);
|
||||
min-width: 48px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
.diagram-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-top: 10px;
|
||||
font-size: 0.82rem;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
.diagram-info span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.diagram-info .dot {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
body { padding: 16px; }
|
||||
.diagram-controls { flex-direction: column; gap: 8px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<h1>case04 — 日地月三体系统 <small>稳定轨道版本</small></h1>
|
||||
<p class="subtitle">太阳、地球、月球三体系统,采用真实比例的质量和万有引力模拟,地球和月球维持稳定椭圆轨道。</p>
|
||||
|
||||
<div class="card" style="display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-bottom:20px;">
|
||||
<span class="tag tag-python">Python 引擎</span>
|
||||
<span class="tag tag-gravity">万有引力</span>
|
||||
<span style="color:var(--text-dim); font-size:0.85rem; margin-left:8px;">3 粒子 | leapfrog 算法 | 稳定轨道</span>
|
||||
</div>
|
||||
|
||||
<h2>一、物理系统概览</h2>
|
||||
|
||||
<div class="card">
|
||||
<p>本案例模拟真实的日-地-月三体系统:</p>
|
||||
<ul>
|
||||
<li><strong>太阳</strong>:位于原点,质量为 $M_\odot$,固定不动(fix_x=fix_y=fix_z=1)</li>
|
||||
<li><strong>地球</strong>:绕太阳公转,质量为 $M_\oplus$,近日点出发</li>
|
||||
<li><strong>月球</strong>:绕地球公转(同时跟随地球绕太阳),质量为 $M_\text{moon}$</li>
|
||||
</ul>
|
||||
<p>原子间万有引力由 <code>gravity_strength</code> 缩放控制,$F = G \dfrac{m_1 m_2}{r^2}$。</p>
|
||||
</div>
|
||||
|
||||
<h2>二、天体真实参数</h2>
|
||||
|
||||
<h3>2.1 质量</h3>
|
||||
|
||||
<table>
|
||||
<tr><th>天体</th><th>质量 / kg</th><th>模拟质量(月球=1)</th></tr>
|
||||
<tr><td>太阳</td><td>$1.989 \times 10^{30}$</td><td>$27\,000$</td></tr>
|
||||
<tr><td>地球</td><td>$5.972 \times 10^{24}$</td><td>$81$</td></tr>
|
||||
<tr><td>月球</td><td>$7.35 \times 10^{22}$</td><td>$1$</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>质量比</strong>:$M_\odot : M_\oplus : M_\text{moon} \approx 27\,100\,000 : 81.3 : 1$<br>
|
||||
模拟中采用缩放比例 $27\,000 : 81 : 1$(太阳质量缩至 $1/1000$ 以保持数值稳定)。
|
||||
</div>
|
||||
|
||||
<h3>2.2 轨道参数</h3>
|
||||
|
||||
<table>
|
||||
<tr><th>轨道</th><th>半长轴 $a$</th><th>偏心率 $e$</th><th>半短轴 $b$</th><th>周期</th></tr>
|
||||
<tr><td>地球绕太阳</td><td>$1.4960 \times 10^8$ km (1 AU)</td><td>$0.0167$</td><td>$1.4958 \times 10^8$ km</td><td>365.25 天</td></tr>
|
||||
<tr><td>月球绕地球</td><td>$3.844 \times 10^5$ km</td><td>$0.0549$</td><td>$3.838 \times 10^5$ km</td><td>27.32 天</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>比例</strong>:$R_{\text{日地}} : R_{\text{地月}} \approx 389 : 1$<br>
|
||||
地月距离约为日地距离的 $1/389$。
|
||||
</div>
|
||||
|
||||
<h2>三、轨道力学</h2>
|
||||
|
||||
<h3>3.1 交互式轨道示意图</h3>
|
||||
|
||||
<p>拖动下方滑块改变偏心率 $e$,观察轨道形状的变化和参数标注:</p>
|
||||
|
||||
<div class="diagram-wrap">
|
||||
<canvas id="orbitCanvas" width="760" height="400"></canvas>
|
||||
<div class="diagram-controls">
|
||||
<label>偏心率 e = <span class="val" id="eDisplay">0.0170</span>(地球真实值 0.0167)</label>
|
||||
<input type="range" id="eSlider" min="0" max="850" value="17">
|
||||
</div>
|
||||
<div class="diagram-info" id="diagramInfo"></div>
|
||||
</div>
|
||||
|
||||
<h3>3.2 月球绕地球轨道</h3>
|
||||
|
||||
<p>拖动下方滑块改变月球轨道偏心率 $e_\text{moon}$(真实值 0.0549):</p>
|
||||
|
||||
<div class="diagram-wrap">
|
||||
<canvas id="moonCanvas" width="760" height="400"></canvas>
|
||||
<div class="diagram-controls">
|
||||
<label>偏心率 e = <span class="val" id="moonEDisplay">0.0549</span>(月球真实值 0.0549)</label>
|
||||
<input type="range" id="moonESlider" min="0" max="850" value="55">
|
||||
</div>
|
||||
<div class="diagram-info" id="moonInfo"></div>
|
||||
</div>
|
||||
|
||||
<h3>3.3 偏心率定义</h3>
|
||||
|
||||
<div class="highlight-eq">
|
||||
<p>偏心率 $e$ 描述椭圆轨道偏离正圆的程度:</p>
|
||||
<div class="formula-block">
|
||||
$$e = \frac{c}{a}$$
|
||||
</div>
|
||||
<p>其中 $c = ea$ 为偏心距(焦点到椭圆中心的距离),$a$ 为半长轴。</p>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr><th>$e$ 值</th><th>轨道形状</th><th>说明</th></tr>
|
||||
<tr><td>$e = 0$</td><td>正圆形</td><td>速度恒定,距离恒定</td></tr>
|
||||
<tr><td>$0 < e < 1$</td><td>椭圆</td><td>近日点/近地点速度最大,远日点/远地点速度最小</td></tr>
|
||||
<tr><td>$e = 1$</td><td>抛物线</td><td>逃逸轨道,速度恰好达到逃逸速度</td></tr>
|
||||
<tr><td>$e > 1$</td><td>双曲线</td><td>飞越轨道,速度超过逃逸速度</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>3.4 半长轴与半短轴的关系</h3>
|
||||
|
||||
<div class="highlight-eq">
|
||||
<div class="formula-block">
|
||||
$$b = a\sqrt{1 - e^2}$$
|
||||
</div>
|
||||
<p>当 $e \ll 1$ 时,$b \approx a\left(1 - \dfrac{e^2}{2}\right)$,椭圆度非常微小。</p>
|
||||
</div>
|
||||
|
||||
<h3>3.5 近日点与远日点</h3>
|
||||
|
||||
<div class="highlight-eq">
|
||||
<p>近日点(距太阳最近)和远日点(距太阳最远)的距离分别为:</p>
|
||||
<div class="formula-block">
|
||||
$$r_{\text{peri}} = a(1 - e), \qquad r_{\text{ap}} = a(1 + e)$$
|
||||
</div>
|
||||
<p>在近日点轨道速度最大,在远日点轨道速度最小:</p>
|
||||
<div class="formula-block">
|
||||
$$v_{\text{peri}} = \sqrt{\frac{GM(1+e)}{a(1-e)}}, \qquad
|
||||
v_{\text{ap}} = \sqrt{\frac{GM(1-e)}{a(1+e)}}$$
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>3.6 开普勒三大定律</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>椭圆定律</strong>:行星轨道是椭圆,太阳位于椭圆的一个焦点上。</li>
|
||||
<li><strong>面积定律</strong>:行星与太阳的连线在相等时间内扫过相等的面积。</li>
|
||||
<li><strong>周期定律</strong>:公转周期的平方与半长轴的立方成正比:$T^2 \propto a^3$。</li>
|
||||
</ol>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>验证</strong>:在本模拟中,你可以通过轨迹图观察面积定律是否成立——地球在近日点移动更快,远日点移动更慢。
|
||||
</div>
|
||||
|
||||
<h2>四、模拟参数</h2>
|
||||
|
||||
<h3>4.1 缩放说明</h3>
|
||||
|
||||
<p>真实尺度无法直接用于模拟(日地距离 1.5 亿 km),因此采用缩放参数。当前 case04 的配置为教学演示而简化:</p>
|
||||
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td><code>gravity_strength</code></td><td>100.0</td><td>万有引力强度</td></tr>
|
||||
<tr><td><code>box_a</code></td><td>30.0</td><td>盒子半边长</td></tr>
|
||||
<tr><td><code>method</code></td><td>leapfrog</td><td>蛙跳法(辛积分器,能量守恒)</td></tr>
|
||||
<tr><td><code>T_total</code></td><td>10.0 s</td><td>总模拟时间</td></tr>
|
||||
<tr><td><code>NSTEP</code></td><td>2</td><td>抽帧间隔(密采帧)</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>4.2 初始构型</h3>
|
||||
|
||||
<table>
|
||||
<tr><th>天体</th><th>质量</th><th>位置 $(x,y,z)$</th><th>速度 $(v_x,v_y,v_z)$</th><th>固定约束</th></tr>
|
||||
<tr><td>太阳</td><td>$27\,000$</td><td>$(0,0,0)$</td><td>$(0,0,0)$</td><td>全部固定</td></tr>
|
||||
<tr><td>地球</td><td>$81$</td><td>$(10,0,0)$</td><td>$(0,0,520)$</td><td>无</td></tr>
|
||||
<tr><td>月球</td><td>$1$</td><td>$(10.5,0,0)$</td><td>$(0,0,647)$</td><td>无</td></tr>
|
||||
</table>
|
||||
|
||||
<p>地球在 $z$ 方向获得初速度 $v=520$,产生绕太阳的轨道运动(接近圆轨道);月球在地球基础上附加 $v\approx127$ 的绕地速度,形成绕地球的轨道。速度由圆形轨道公式 $v = \sqrt{G_{\text{eff}} M / r}$ 计算,其中 $G_{\text{eff}} = \text{gravity\_strength} = 100$。</p>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>验证</strong>:地球速度 $v_\oplus = \sqrt{100 \times 27\,000 / 10} \approx 519.6$,设 520 正确。月球相对速度 $v_{\text{rel}} = \sqrt{100 \times 81 / 0.5} \approx 127.3$,设 127 正确。地月距离 0.5 在地球希尔半径 $r_H \approx 10 \times (81 / 81\,000)^{1/3} \approx 1.0$ 之内,可确保轨道稳定。
|
||||
</div>
|
||||
|
||||
<h2>五、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<code style="display:block; padding:14px 18px; margin-bottom:10px;">
|
||||
# 进入 case04 目录并运行<br>
|
||||
cd examples/case04<br>
|
||||
python run_dynamics.py<br><br>
|
||||
# 仅输出轨迹图,跳过动画<br>
|
||||
python run_dynamics.py --no-plot
|
||||
</code>
|
||||
<p>配置文件:<code>input/input.txt</code>(物理参数)、<code>input/coord.txt</code>(初始位置/速度)。</p>
|
||||
</div>
|
||||
|
||||
<h2>六、与 case03 的对比</h2>
|
||||
|
||||
<table>
|
||||
<tr><th></th><th>case03(失败案例)</th><th>case04(成功案例)</th></tr>
|
||||
<tr><td>轨道状态</td><td>轨道发散或碰撞</td><td>稳定椭圆轨道</td></tr>
|
||||
<tr><td>关键差异</td><td>初始速度或质量比不恰当</td><td>合理的初值和参数</td></tr>
|
||||
<tr><td>教学意义</td><td>展示参数选择的重要性</td><td>展示正确的三体运动</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>教学建议</strong>:先运行 case03 观察失稳,再运行 case04 对比稳定轨道,理解初始条件对数值模拟的关键影响。
|
||||
</div>
|
||||
|
||||
<h2>七、已知局限</h2>
|
||||
|
||||
<ul>
|
||||
<li>太阳真实质量为月球 $27\,100\,000$ 倍,模拟中缩至 $27\,000$ 倍以保持数值稳定($1/1000$)</li>
|
||||
<li>轨道半径未按真实比例缩放(真实日地距是地月距的 389 倍,模拟中约为 20 倍,受希尔半径约束,月球已尽可能放远)</li>
|
||||
<li>未考虑月球轨道倾角(真实地月轨道有约 5° 的倾角)</li>
|
||||
<li>leapfrog 算法为能量守恒的辛积分器,长期稳定,但步长过大时仍可能偏离真实轨道</li>
|
||||
<li>太阳固定不动可作为教学简化,但严格三体模拟中应让所有天体在质心系中自由运动</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var canvas = document.getElementById('orbitCanvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
var slider = document.getElementById('eSlider');
|
||||
var eDisplay = document.getElementById('eDisplay');
|
||||
var infoDiv = document.getElementById('diagramInfo');
|
||||
|
||||
var W = 760, H = 400;
|
||||
var cx = 380, cy = 200;
|
||||
|
||||
// 实际绘图区域半径
|
||||
var A = 140;
|
||||
|
||||
function draw(e) {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
|
||||
var b = A * Math.sqrt(1 - e * e);
|
||||
var f = e * A;
|
||||
|
||||
// 长轴辅助线
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx - A - 20, cy);
|
||||
ctx.lineTo(cx + A + 20, cy);
|
||||
ctx.strokeStyle = '#30363d';
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.setLineDash([4, 3]);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// 椭圆轨道(中心在 cx+f,左焦点=cx 为太阳)
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(cx + f, cy, A, b, 0, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = '#58a6ff';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
|
||||
// 半长轴 a(标注线,从椭圆中心到右顶点)
|
||||
var aY = cy + 28;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f, aY);
|
||||
ctx.lineTo(cx + f + A, aY);
|
||||
ctx.strokeStyle = '#c9d1d9';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
// 箭头(左端:椭圆中心 cx+f)
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f + 6, aY - 4);
|
||||
ctx.lineTo(cx + f, aY);
|
||||
ctx.lineTo(cx + f + 6, aY + 4);
|
||||
ctx.strokeStyle = '#c9d1d9';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
// 箭头(右端:右顶点 cx+f+A)
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f + A - 6, aY - 4);
|
||||
ctx.lineTo(cx + f + A, aY);
|
||||
ctx.lineTo(cx + f + A - 6, aY + 4);
|
||||
ctx.strokeStyle = '#c9d1d9';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#c9d1d9';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('a = ' + A.toFixed(0), cx + f + A/2, aY + 6);
|
||||
|
||||
// 偏心距 c = ea(从左焦点到椭圆中心)
|
||||
var cY = cy - b - 20;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx, cY);
|
||||
ctx.lineTo(cx + f, cY);
|
||||
ctx.strokeStyle = '#d29922';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
// 箭头(左端:左焦点=cx 太阳)
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + 5, cY - 4);
|
||||
ctx.lineTo(cx, cY);
|
||||
ctx.lineTo(cx + 5, cY + 4);
|
||||
ctx.strokeStyle = '#d29922';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
// 箭头(右端:椭圆中心 cx+f)
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f - 5, cY - 4);
|
||||
ctx.lineTo(cx + f, cY);
|
||||
ctx.lineTo(cx + f - 5, cY + 4);
|
||||
ctx.strokeStyle = '#d29922';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#d29922';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('c = ea = ' + f.toFixed(1), cx + f/2, cY - 4);
|
||||
|
||||
// 半短轴 b 标注(从椭圆中心到上顶点)
|
||||
var bX = cx + f + A + 18;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bX, cy);
|
||||
ctx.lineTo(bX, cy - b);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bX - 4, cy - 6);
|
||||
ctx.lineTo(bX, cy);
|
||||
ctx.lineTo(bX + 4, cy - 6);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bX - 4, cy - b + 6);
|
||||
ctx.lineTo(bX, cy - b);
|
||||
ctx.lineTo(bX + 4, cy - b + 6);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#3fb950';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText('b = ' + b.toFixed(1), bX + 8, cy - b/2);
|
||||
|
||||
// 太阳(焦点)
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, 7, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#d29922';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#f0883e';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#f0f6fc';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('太阳(左焦点)', cx, cy - 12);
|
||||
|
||||
// 近日点(左顶点:距左焦点最近)
|
||||
var periX = cx + f - A;
|
||||
ctx.fillStyle = '#58a6ff';
|
||||
ctx.beginPath();
|
||||
ctx.arc(periX, cy, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#8b949e';
|
||||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('近日点 r = a(1-e) = ' + (A * (1 - e)).toFixed(1), periX, cy + 12);
|
||||
|
||||
// 远日点标注(右顶点:距左焦点最远)
|
||||
var apX = cx + f + A;
|
||||
ctx.fillStyle = '#58a6ff';
|
||||
ctx.beginPath();
|
||||
ctx.arc(apX, cy, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#8b949e';
|
||||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('远日点 r = a(1+e) = ' + (A * (1 + e)).toFixed(1), apX, cy + 12);
|
||||
|
||||
// 地球(轨道上的点)
|
||||
var angle = 0.4;
|
||||
var ex = cx + f + A * Math.cos(angle);
|
||||
var ey = cy - b * Math.sin(angle);
|
||||
ctx.beginPath();
|
||||
ctx.arc(ex, ey, 5, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#58a6ff';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#1f6feb';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#58a6ff';
|
||||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('地球', ex + 8, ey);
|
||||
|
||||
// 速度矢量
|
||||
var vlen = 32;
|
||||
var vx = -A * Math.sin(angle) * 0.7;
|
||||
var vy = b * Math.cos(angle) * 0.7;
|
||||
var vl = Math.sqrt(vx*vx + vy*vy);
|
||||
if (vl > 0) {
|
||||
vx = vx / vl * vlen;
|
||||
vy = vy / vl * vlen;
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(ex, ey);
|
||||
ctx.lineTo(ex + vx, ey + vy);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
var tipX = ex + vx, tipY = ey + vy;
|
||||
var a1 = 0.3;
|
||||
ctx.moveTo(tipX, tipY);
|
||||
ctx.lineTo(tipX - vx * a1 + vy * a1 * 0.5, tipY - vy * a1 - vx * a1 * 0.5);
|
||||
ctx.lineTo(tipX - vx * a1 - vy * a1 * 0.5, tipY - vy * a1 + vx * a1 * 0.5);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = '#3fb950';
|
||||
ctx.fill();
|
||||
|
||||
// 信息面板
|
||||
var roundE = Math.round(e * 10000) / 10000;
|
||||
infoDiv.innerHTML =
|
||||
'<span><span class="dot" style="background:#58a6ff"></span>椭圆轨道 e = ' + roundE.toFixed(4) + '</span>' +
|
||||
'<span><span class="dot" style="background:#d29922"></span>偏心距 c = ' + f.toFixed(1) + '</span>' +
|
||||
'<span><span class="dot" style="background:#3fb950"></span>b/a = ' + (b / A).toFixed(4) + '</span>' +
|
||||
'<span>近日点 ' + (A * (1 - e)).toFixed(1) + ' / 远日点 ' + (A * (1 + e)).toFixed(1) + '</span>';
|
||||
}
|
||||
|
||||
function update() {
|
||||
var val = parseInt(slider.value);
|
||||
var e = val / 1000;
|
||||
eDisplay.textContent = e.toFixed(4);
|
||||
draw(e);
|
||||
}
|
||||
|
||||
slider.addEventListener('input', update);
|
||||
update();
|
||||
})();
|
||||
|
||||
// ── 月球绕地球轨道图 ──
|
||||
(function() {
|
||||
var canvas = document.getElementById('moonCanvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
var slider = document.getElementById('moonESlider');
|
||||
var eDisplay = document.getElementById('moonEDisplay');
|
||||
var infoDiv = document.getElementById('moonInfo');
|
||||
|
||||
var W = 760, H = 400;
|
||||
var cx = 380, cy = 200;
|
||||
var A = 140;
|
||||
|
||||
function draw(e) {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
|
||||
var b = A * Math.sqrt(1 - e * e);
|
||||
var f = e * A;
|
||||
|
||||
// 长轴辅助线
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx - A - 20, cy);
|
||||
ctx.lineTo(cx + A + 20, cy);
|
||||
ctx.strokeStyle = '#30363d';
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.setLineDash([4, 3]);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// 椭圆轨道(中心在 cx+f,左焦点=cx 为地球)
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(cx + f, cy, A, b, 0, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = '#7ee787';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
|
||||
// 半长轴 a(标注线,从椭圆中心到右顶点)
|
||||
var aY = cy + 28;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f, aY);
|
||||
ctx.lineTo(cx + f + A, aY);
|
||||
ctx.strokeStyle = '#c9d1d9';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f + 6, aY - 4);
|
||||
ctx.lineTo(cx + f, aY);
|
||||
ctx.lineTo(cx + f + 6, aY + 4);
|
||||
ctx.strokeStyle = '#c9d1d9';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f + A - 6, aY - 4);
|
||||
ctx.lineTo(cx + f + A, aY);
|
||||
ctx.lineTo(cx + f + A - 6, aY + 4);
|
||||
ctx.strokeStyle = '#c9d1d9';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#c9d1d9';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('a = ' + A.toFixed(0), cx + f + A/2, aY + 6);
|
||||
|
||||
// 偏心距 c = ea(从左焦点=地球 到椭圆中心)
|
||||
var cY = cy - b - 20;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx, cY);
|
||||
ctx.lineTo(cx + f, cY);
|
||||
ctx.strokeStyle = '#d29922';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + 5, cY - 4);
|
||||
ctx.lineTo(cx, cY);
|
||||
ctx.lineTo(cx + 5, cY + 4);
|
||||
ctx.strokeStyle = '#d29922';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f - 5, cY - 4);
|
||||
ctx.lineTo(cx + f, cY);
|
||||
ctx.lineTo(cx + f - 5, cY + 4);
|
||||
ctx.strokeStyle = '#d29922';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#d29922';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('c = ea = ' + f.toFixed(1), cx + f/2, cY - 4);
|
||||
|
||||
// 半短轴 b 标注(从椭圆中心到上顶点)
|
||||
var bX = cx + f + A + 18;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bX, cy);
|
||||
ctx.lineTo(bX, cy - b);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bX - 4, cy - 6);
|
||||
ctx.lineTo(bX, cy);
|
||||
ctx.lineTo(bX + 4, cy - 6);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bX - 4, cy - b + 6);
|
||||
ctx.lineTo(bX, cy - b);
|
||||
ctx.lineTo(bX + 4, cy - b + 6);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#3fb950';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText('b = ' + b.toFixed(1), bX + 8, cy - b/2);
|
||||
|
||||
// 地球(左焦点)
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, 8, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#58a6ff';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#1f6feb';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#f0f6fc';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('地球(左焦点)', cx, cy - 14);
|
||||
|
||||
// 近地点(左顶点:距地球最近)
|
||||
var periX = cx + f - A;
|
||||
ctx.fillStyle = '#7ee787';
|
||||
ctx.beginPath();
|
||||
ctx.arc(periX, cy, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#8b949e';
|
||||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('近地点 r = a(1-e) = ' + (A * (1 - e)).toFixed(1), periX, cy + 12);
|
||||
|
||||
// 远地点(右顶点:距地球最远)
|
||||
var apX = cx + f + A;
|
||||
ctx.fillStyle = '#7ee787';
|
||||
ctx.beginPath();
|
||||
ctx.arc(apX, cy, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#8b949e';
|
||||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('远地点 r = a(1+e) = ' + (A * (1 + e)).toFixed(1), apX, cy + 12);
|
||||
|
||||
// 月球(轨道上的点)
|
||||
var angle = 0.6;
|
||||
var mx = cx + f + A * Math.cos(angle);
|
||||
var my = cy - b * Math.sin(angle);
|
||||
ctx.beginPath();
|
||||
ctx.arc(mx, my, 4, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#e2e8f0';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#8b949e';
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#e2e8f0';
|
||||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('月球', mx + 8, my);
|
||||
|
||||
// 速度矢量
|
||||
var vlen = 32;
|
||||
var vx = -A * Math.sin(angle) * 0.7;
|
||||
var vy = b * Math.cos(angle) * 0.7;
|
||||
var vl = Math.sqrt(vx*vx + vy*vy);
|
||||
if (vl > 0) { vx = vx/vl*vlen; vy = vy/vl*vlen; }
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(mx, my);
|
||||
ctx.lineTo(mx + vx, my + vy);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
var tipX = mx + vx, tipY = my + vy;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(tipX, tipY);
|
||||
ctx.lineTo(tipX - vx*0.3 + vy*0.15, tipY - vy*0.3 - vx*0.15);
|
||||
ctx.lineTo(tipX - vx*0.3 - vy*0.15, tipY - vy*0.3 + vx*0.15);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = '#3fb950';
|
||||
ctx.fill();
|
||||
|
||||
// 信息面板
|
||||
var roundE = Math.round(e * 10000) / 10000;
|
||||
infoDiv.innerHTML =
|
||||
'<span><span class="dot" style="background:#7ee787"></span>椭圆轨道 e = ' + roundE.toFixed(4) + '</span>' +
|
||||
'<span><span class="dot" style="background:#d29922"></span>偏心距 c = ' + f.toFixed(1) + '</span>' +
|
||||
'<span><span class="dot" style="background:#3fb950"></span>b/a = ' + (b / A).toFixed(4) + '</span>' +
|
||||
'<span>近地点 ' + (A * (1 - e)).toFixed(1) + ' / 远地点 ' + (A * (1 + e)).toFixed(1) + '</span>';
|
||||
}
|
||||
|
||||
function update() {
|
||||
var val = parseInt(slider.value);
|
||||
var e = val / 1000;
|
||||
eDisplay.textContent = e.toFixed(4);
|
||||
draw(e);
|
||||
}
|
||||
|
||||
slider.addEventListener('input', update);
|
||||
update();
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +1,4 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.28 0 0 0 0 0 0 1 1 1
|
||||
2 1 0.28 4 0 0 0 0 4 0 0 0
|
||||
3 0.1 0.18 5 0 0 0 0 6 0 0 0
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 27000 1.0 0 0 0 0 0 0 1 1 1
|
||||
2 81 0.2 10 0 0 0 0 520 0 0 0
|
||||
3 1 0.1 10.5 0 0 0 0 647 0 0 0
|
||||
@@ -23,7 +23,7 @@ force_calc: 0 # 强制重新计算:1=跳过缓存强算,0=自动使用
|
||||
engine: python # 默认使用 Python 引擎
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 20.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
box_a: 30.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
# 坐标文件格式:
|
||||
@@ -74,7 +74,7 @@ T_total: 10.0
|
||||
NSTEP: 2
|
||||
|
||||
# ── 时间步长 ──────────────────────────────────
|
||||
DT: 0.001 # 时间步长 (s)
|
||||
DT: 0.0001 # 时间步长 (s)
|
||||
|
||||
# 抽帧范围:只保存 [sample_start, sample_end) 区间内的帧
|
||||
sample_start: null # null 表示从头开始(帧索引从 0 起)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.1 0 0 1 0 0 0 0 1 1
|
||||
1 1 0.1 0 0 0 0 0 0 0 1 1
|
||||
2 1 0.1 1 0 0 0 0 0 0 1 1
|
||||
3 1 0.1 2 0 0 0 0 0 0 1 1
|
||||
4 1 0.1 3 0 0 0 0 0 0 1 1
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
step_simulate: 0 # 运行物理模拟 → output/trajectory.txt
|
||||
step_sample: 0 # 抽帧 → output/display.txt
|
||||
step_plot: 0 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||
step_animation: 0 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||
step_animation: 1 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||
step_plot_wave: 1 # 绘制波形能量动画
|
||||
plot_wave_save_gif: 0 # 输出波形 GIF(需 step_plot_wave=1)
|
||||
plot_wave_save_mp4: 0 # 输出波形 MP4(需 step_plot_wave=1)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 60 |
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">60 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>60 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
@@ -415,13 +415,13 @@ step_animation: 1 # 播放动画</pre>
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(60 个原子)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(10000 步 × 60 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(200 帧 × 60 原子)
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
bond_name k rest_length
|
||||
k1 1.0 1.0
|
||||
bond_name k rest_length
|
||||
k1 10.0 1.0
|
||||
|
||||
@@ -58,3 +58,63 @@ n1 n2 bond_name
|
||||
57 58 k1
|
||||
58 59 k1
|
||||
59 60 k1
|
||||
60 61 k1
|
||||
61 62 k1
|
||||
62 63 k1
|
||||
63 64 k1
|
||||
64 65 k1
|
||||
65 66 k1
|
||||
66 67 k1
|
||||
67 68 k1
|
||||
68 69 k1
|
||||
69 70 k1
|
||||
70 71 k1
|
||||
71 72 k1
|
||||
72 73 k1
|
||||
73 74 k1
|
||||
74 75 k1
|
||||
75 76 k1
|
||||
76 77 k1
|
||||
77 78 k1
|
||||
78 79 k1
|
||||
79 80 k1
|
||||
80 81 k1
|
||||
81 82 k1
|
||||
82 83 k1
|
||||
83 84 k1
|
||||
84 85 k1
|
||||
85 86 k1
|
||||
86 87 k1
|
||||
87 88 k1
|
||||
88 89 k1
|
||||
89 90 k1
|
||||
90 91 k1
|
||||
91 92 k1
|
||||
92 93 k1
|
||||
93 94 k1
|
||||
94 95 k1
|
||||
95 96 k1
|
||||
96 97 k1
|
||||
97 98 k1
|
||||
98 99 k1
|
||||
99 100 k1
|
||||
100 101 k1
|
||||
101 102 k1
|
||||
102 103 k1
|
||||
103 104 k1
|
||||
104 105 k1
|
||||
105 106 k1
|
||||
106 107 k1
|
||||
107 108 k1
|
||||
108 109 k1
|
||||
109 110 k1
|
||||
110 111 k1
|
||||
111 112 k1
|
||||
112 113 k1
|
||||
113 114 k1
|
||||
114 115 k1
|
||||
115 116 k1
|
||||
116 117 k1
|
||||
117 118 k1
|
||||
118 119 k1
|
||||
119 120 k1
|
||||
|
||||
+120
-60
@@ -1,61 +1,121 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.1 0 0 0 0 0 0 1 1 0
|
||||
2 1 0.1 1 0 0 0 0 0 1 1 0
|
||||
3 1 0.1 2 0 0 0 0 0 1 1 0
|
||||
4 1 0.1 3 0 0 0 0 0 1 1 0
|
||||
5 1 0.1 4 0 0 0 0 0 1 1 0
|
||||
6 1 0.1 5 0 0 0 0 0 1 1 0
|
||||
7 1 0.1 6 0 0 0 0 0 1 1 0
|
||||
8 1 0.1 7 0 0 0 0 0 1 1 0
|
||||
9 1 0.1 8 0 0 0 0 0 1 1 0
|
||||
10 1 0.1 9 0 0 0 0 0 1 1 0
|
||||
11 1 0.1 10 0 0 0 0 0 1 1 0
|
||||
12 1 0.1 11 0 0 0 0 0 1 1 0
|
||||
13 1 0.1 12 0 0 0 0 0 1 1 0
|
||||
14 1 0.1 13 0 0 0 0 0 1 1 0
|
||||
15 1 0.1 14 0 0 0 0 0 1 1 0
|
||||
16 1 0.1 15 0 0 0 0 0 1 1 0
|
||||
17 1 0.1 16 0 0 0 0 0 1 1 0
|
||||
18 1 0.1 17 0 0 0 0 0 1 1 0
|
||||
19 1 0.1 18 0 0 0 0 0 1 1 0
|
||||
20 1 0.1 19 0 0 0 0 0 1 1 0
|
||||
21 1 0.1 20 0 0 0 0 0 1 1 0
|
||||
22 1 0.1 21 0 0 0 0 0 1 1 0
|
||||
23 1 0.1 22 0 0 0 0 0 1 1 0
|
||||
24 1 0.1 23 0 0 0 0 0 1 1 0
|
||||
25 1 0.1 24 0 0 0 0 0 1 1 0
|
||||
26 1 0.1 25 0 0 0 0 0 1 1 0
|
||||
27 1 0.1 26 0 0 0 0 0 1 1 0
|
||||
28 1 0.1 27 0 0 0 0 0 1 1 0
|
||||
29 1 0.1 28 0 0 0 0 0 1 1 0
|
||||
30 1 0.1 29 0 0 0 0 0 1 1 0
|
||||
31 1 0.1 30 0 0 0 0 0 1 1 0
|
||||
32 1 0.1 31 0 0 0 0 0 1 1 0
|
||||
33 1 0.1 32 0 0 0 0 0 1 1 0
|
||||
34 1 0.1 33 0 0 0 0 0 1 1 0
|
||||
35 1 0.1 34 0 0 0 0 0 1 1 0
|
||||
36 1 0.1 35 0 0 0 0 0 1 1 0
|
||||
37 1 0.1 36 0 0 0 0 0 1 1 0
|
||||
38 1 0.1 37 0 0 0 0 0 1 1 0
|
||||
39 1 0.1 38 0 0 0 0 0 1 1 0
|
||||
40 1 0.1 39 0 0 0 0 0 1 1 0
|
||||
41 1 0.1 40 0 0 0 0 0 1 1 0
|
||||
42 1 0.1 41 0 0 0 0 0 1 1 0
|
||||
43 1 0.1 42 0 0 0 0 0 1 1 0
|
||||
44 1 0.1 43 0 0 0 0 0 1 1 0
|
||||
45 1 0.1 44 0 0 0 0 0 1 1 0
|
||||
46 1 0.1 45 0 0 0 0 0 1 1 0
|
||||
47 1 0.1 46 0 0 0 0 0 1 1 0
|
||||
48 1 0.1 47 0 0 0 0 0 1 1 0
|
||||
49 1 0.1 48 0 0 0 0 0 1 1 0
|
||||
50 1 0.1 49 0 0 0 0 0 1 1 0
|
||||
51 1 0.1 50 0 0 0 0 0 1 1 0
|
||||
52 1 0.1 51 0 0 0 0 0 1 1 0
|
||||
53 1 0.1 52 0 0 0 0 0 1 1 0
|
||||
54 1 0.1 53 0 0 0 0 0 1 1 0
|
||||
55 1 0.1 54 0 0 0 0 0 1 1 0
|
||||
56 1 0.1 55 0 0 0 0 0 1 1 0
|
||||
57 1 0.1 56 0 0 0 0 0 1 1 0
|
||||
58 1 0.1 57 0 0 0 0 0 1 1 0
|
||||
59 1 0.1 58 0 0 0 0 0 1 1 0
|
||||
60 1 0.1 59 0 0 0 0 0 1 1 0
|
||||
1 1 0.1 0 0 0 0 0 0 0 1 0
|
||||
2 1 0.1 1 0 0 0 0 0 0 1 0
|
||||
3 1 0.1 2 0 0 0 0 0 0 1 0
|
||||
4 1 0.1 3 0 0 0 0 0 0 1 0
|
||||
5 1 0.1 4 0 0 0 0 0 0 1 0
|
||||
6 1 0.1 5 0 0 0 0 0 0 1 0
|
||||
7 1 0.1 6 0 0 0 0 0 0 1 0
|
||||
8 1 0.1 7 0 0 0 0 0 0 1 0
|
||||
9 1 0.1 8 0 0 0 0 0 0 1 0
|
||||
10 1 0.1 9 0 0 0 0 0 0 1 0
|
||||
11 1 0.1 10 0 0 0 0 0 0 1 0
|
||||
12 1 0.1 11 0 0 0 0 0 0 1 0
|
||||
13 1 0.1 12 0 0 0 0 0 0 1 0
|
||||
14 1 0.1 13 0 0 0 0 0 0 1 0
|
||||
15 1 0.1 14 0 0 0 0 0 0 1 0
|
||||
16 1 0.1 15 0 0 0 0 0 0 1 0
|
||||
17 1 0.1 16 0 0 0 0 0 0 1 0
|
||||
18 1 0.1 17 0 0 0 0 0 0 1 0
|
||||
19 1 0.1 18 0 0 0 0 0 0 1 0
|
||||
20 1 0.1 19 0 0 0 0 0 0 1 0
|
||||
21 1 0.1 20 0 0 0 0 0 0 1 0
|
||||
22 1 0.1 21 0 0 0 0 0 0 1 0
|
||||
23 1 0.1 22 0 0 0 0 0 0 1 0
|
||||
24 1 0.1 23 0 0 0 0 0 0 1 0
|
||||
25 1 0.1 24 0 0 0 0 0 0 1 0
|
||||
26 1 0.1 25 0 0 0 0 0 0 1 0
|
||||
27 1 0.1 26 0 0 0 0 0 0 1 0
|
||||
28 1 0.1 27 0 0 0 0 0 0 1 0
|
||||
29 1 0.1 28 0 0 0 0 0 0 1 0
|
||||
30 1 0.1 29 0 0 0 0 0 0 1 0
|
||||
31 1 0.1 30 0 0 0 0 0 0 1 0
|
||||
32 1 0.1 31 0 0 0 0 0 0 1 0
|
||||
33 1 0.1 32 0 0 0 0 0 0 1 0
|
||||
34 1 0.1 33 0 0 0 0 0 0 1 0
|
||||
35 1 0.1 34 0 0 0 0 0 0 1 0
|
||||
36 1 0.1 35 0 0 0 0 0 0 1 0
|
||||
37 1 0.1 36 0 0 0 0 0 0 1 0
|
||||
38 1 0.1 37 0 0 0 0 0 0 1 0
|
||||
39 1 0.1 38 0 0 0 0 0 0 1 0
|
||||
40 1 0.1 39 0 0 0 0 0 0 1 0
|
||||
41 1 0.1 40 0 0 0 0 0 0 1 0
|
||||
42 1 0.1 41 0 0 0 0 0 0 1 0
|
||||
43 1 0.1 42 0 0 0 0 0 0 1 0
|
||||
44 1 0.1 43 0 0 0 0 0 0 1 0
|
||||
45 1 0.1 44 0 0 0 0 0 0 1 0
|
||||
46 1 0.1 45 0 0 0 0 0 0 1 0
|
||||
47 1 0.1 46 0 0 0 0 0 0 1 0
|
||||
48 1 0.1 47 0 0 0 0 0 0 1 0
|
||||
49 1 0.1 48 0 0 0 0 0 0 1 0
|
||||
50 1 0.1 49 0 0 0 0 0 0 1 0
|
||||
51 1 0.1 50 0 0 0 0 0 0 1 0
|
||||
52 1 0.1 51 0 0 0 0 0 0 1 0
|
||||
53 1 0.1 52 0 0 0 0 0 0 1 0
|
||||
54 1 0.1 53 0 0 0 0 0 0 1 0
|
||||
55 1 0.1 54 0 0 0 0 0 0 1 0
|
||||
56 1 0.1 55 0 0 0 0 0 0 1 0
|
||||
57 1 0.1 56 0 0 0 0 0 0 1 0
|
||||
58 1 0.1 57 0 0 0 0 0 0 1 0
|
||||
59 1 0.1 58 0 0 0 0 0 0 1 0
|
||||
60 1 0.1 59 0 0 0 0 0 0 1 0
|
||||
61 1 0.1 60 0 0 0 0 0 0 1 0
|
||||
62 1 0.1 61 0 0 0 0 0 0 1 0
|
||||
63 1 0.1 62 0 0 0 0 0 0 1 0
|
||||
64 1 0.1 63 0 0 0 0 0 0 1 0
|
||||
65 1 0.1 64 0 0 0 0 0 0 1 0
|
||||
66 1 0.1 65 0 0 0 0 0 0 1 0
|
||||
67 1 0.1 66 0 0 0 0 0 0 1 0
|
||||
68 1 0.1 67 0 0 0 0 0 0 1 0
|
||||
69 1 0.1 68 0 0 0 0 0 0 1 0
|
||||
70 1 0.1 69 0 0 0 0 0 0 1 0
|
||||
71 1 0.1 70 0 0 0 0 0 0 1 0
|
||||
72 1 0.1 71 0 0 0 0 0 0 1 0
|
||||
73 1 0.1 72 0 0 0 0 0 0 1 0
|
||||
74 1 0.1 73 0 0 0 0 0 0 1 0
|
||||
75 1 0.1 74 0 0 0 0 0 0 1 0
|
||||
76 1 0.1 75 0 0 0 0 0 0 1 0
|
||||
77 1 0.1 76 0 0 0 0 0 0 1 0
|
||||
78 1 0.1 77 0 0 0 0 0 0 1 0
|
||||
79 1 0.1 78 0 0 0 0 0 0 1 0
|
||||
80 1 0.1 79 0 0 0 0 0 0 1 0
|
||||
81 1 0.1 80 0 0 0 0 0 0 1 0
|
||||
82 1 0.1 81 0 0 0 0 0 0 1 0
|
||||
83 1 0.1 82 0 0 0 0 0 0 1 0
|
||||
84 1 0.1 83 0 0 0 0 0 0 1 0
|
||||
85 1 0.1 84 0 0 0 0 0 0 1 0
|
||||
86 1 0.1 85 0 0 0 0 0 0 1 0
|
||||
87 1 0.1 86 0 0 0 0 0 0 1 0
|
||||
88 1 0.1 87 0 0 0 0 0 0 1 0
|
||||
89 1 0.1 88 0 0 0 0 0 0 1 0
|
||||
90 1 0.1 89 0 0 0 0 0 0 1 0
|
||||
91 1 0.1 90 0 0 0 0 0 0 1 0
|
||||
92 1 0.1 91 0 0 0 0 0 0 1 0
|
||||
93 1 0.1 92 0 0 0 0 0 0 1 0
|
||||
94 1 0.1 93 0 0 0 0 0 0 1 0
|
||||
95 1 0.1 94 0 0 0 0 0 0 1 0
|
||||
96 1 0.1 95 0 0 0 0 0 0 1 0
|
||||
97 1 0.1 96 0 0 0 0 0 0 1 0
|
||||
98 1 0.1 97 0 0 0 0 0 0 1 0
|
||||
99 1 0.1 98 0 0 0 0 0 0 1 0
|
||||
100 1 0.1 99 0 0 0 0 0 0 1 0
|
||||
101 1 0.1 100 0 0 0 0 0 0 1 0
|
||||
102 1 0.1 101 0 0 0 0 0 0 1 0
|
||||
103 1 0.1 102 0 0 0 0 0 0 1 0
|
||||
104 1 0.1 103 0 0 0 0 0 0 1 0
|
||||
105 1 0.1 104 0 0 0 0 0 0 1 0
|
||||
106 1 0.1 105 0 0 0 0 0 0 1 0
|
||||
107 1 0.1 106 0 0 0 0 0 0 1 0
|
||||
108 1 0.1 107 0 0 0 0 0 0 1 0
|
||||
109 1 0.1 108 0 0 0 0 0 0 1 0
|
||||
110 1 0.1 109 0 0 0 0 0 0 1 0
|
||||
111 1 0.1 110 0 0 0 0 0 0 1 0
|
||||
112 1 0.1 111 0 0 0 0 0 0 1 0
|
||||
113 1 0.1 112 0 0 0 0 0 0 1 0
|
||||
114 1 0.1 113 0 0 0 0 0 0 1 0
|
||||
115 1 0.1 114 0 0 0 0 0 0 1 0
|
||||
116 1 0.1 115 0 0 0 0 0 0 1 0
|
||||
117 1 0.1 116 0 0 0 0 0 0 1 0
|
||||
118 1 0.1 117 0 0 0 0 0 0 1 0
|
||||
119 1 0.1 118 0 0 0 0 0 0 1 0
|
||||
120 1 0.1 119 0 0 0 0 0 1 1 1
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 0.5 0 0 0.1 0 0 90 all
|
||||
1 0 0 2.0 0 0 0.05 0 0 90 all
|
||||
|
||||
@@ -5,21 +5,24 @@
|
||||
# ── 流程控制 ──────────────────────────────────
|
||||
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||
step_simulate: 1 # 运行物理模拟 → output/trajectory.txt
|
||||
step_sample: 1 # 抽帧 → output/display.txt
|
||||
step_simulate: 1 # 运行物理模拟 → output/display.txt(引擎直接抽帧)
|
||||
step_sample: 0 # (旧版)从 trajectory.txt 重新抽帧,默认0=不执行
|
||||
step_plot: 0 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||
step_plot_wave: 0 # 绘制波形能量动画
|
||||
plot_wave_save_gif: 0 # 输出波形 GIF(需 step_plot_wave=1)
|
||||
plot_wave_save_mp4: 0 # 输出波形 MP4(需 step_plot_wave=1)
|
||||
step_animation: 1 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||
force_calc: 0 # 强制重新计算:1=跳过缓存强算,0=自动使用已有输出
|
||||
step_plot_wave: 0 # 绘制波形能量动画
|
||||
force_calc: 1 # 强制重新计算:1=跳过缓存强算,0=自动使用已有输出
|
||||
plot_wave_save_gif: 1 # 输出波形 GIF(需 step_plot_wave=1)
|
||||
plot_wave_save_mp4: 1 # 输出波形 MP4(需 step_plot_wave=1)
|
||||
|
||||
# ── 文件保存 ──────────────────────────────────
|
||||
save_trajectory: 0 # 0=不保留完整轨迹文件, 1=保留 trajectory.txt(用于后续单独抽帧)
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
# 可选: python, c, cpp, fortran, java
|
||||
engine: python # 默认使用 python 引擎
|
||||
engine: c # 默认使用 python 引擎
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 80.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
box_a: 300.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
# 坐标文件格式:
|
||||
@@ -35,14 +38,14 @@ plot_atom: 1
|
||||
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
# 三个方向分量分别对应 x, y, z
|
||||
G: [0.0, 0.0, 0.0] # 重力场分量 (m/s²)
|
||||
B: [0.0, 0.0, 0.0] # 阻尼分量
|
||||
G: [0.00, 0.00, 0.00] # 重力场分量 (m/s²)
|
||||
B: [0.01, 0.00, 0.01] # 阻尼分量
|
||||
|
||||
# ── 力开关(0=关闭, 1=开启)──────────────────
|
||||
gravity_field: 0 # 均匀重力场 (G)
|
||||
gravity_interaction: 0 # 原子间万有引力
|
||||
elastic_force: 1 # 弹簧键力
|
||||
damping_force: 0 # 阻尼 (B)
|
||||
damping_force: 1 # 阻尼 (B)
|
||||
driving_force: 1 # 驱动力(需 driver_file 定义)
|
||||
#
|
||||
gravity_strength: 1.0 # 万有引力强度(仅 gravity_interaction=1 时有效)
|
||||
@@ -63,10 +66,10 @@ warmup_steps: 0 # 默认 0(立即开始记录)
|
||||
|
||||
# 总模拟时间(秒),程序自动计算 NT = T_total / DT
|
||||
# 如果同时指定了 NT,以 NT 为准
|
||||
T_total: 50.0
|
||||
T_total: 1000.0
|
||||
|
||||
# 抽帧间隔(每 NSTEP 步取一帧用于动画)
|
||||
NSTEP: 100
|
||||
NSTEP: 500
|
||||
|
||||
# ── 时间步长 ──────────────────────────────────
|
||||
DT: 0.001 # 时间步长 (s)
|
||||
@@ -85,7 +88,7 @@ use_marker: 1
|
||||
|
||||
# ── 显示参数 ──────────────────────────────────
|
||||
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.5]
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
# 小球颜色
|
||||
# 小球半径从 coord_file 的 radius 列读取
|
||||
@@ -97,3 +100,12 @@ ball_color_b: 0.90 # B 分量
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机初始位置 ────────────────────────────
|
||||
camera_distance: 120.0 # 摄像机到场景中心的距离
|
||||
camera_elevation: 0.0 # 俯仰角(度),负值=俯视
|
||||
camera_azimuth: 0.0 # 方位角(度)
|
||||
camera_center_x: 60.0 # 摄像机注视点 x
|
||||
camera_center_y: 0.0 # 摄像机注视点 y
|
||||
camera_center_z: 0.0 # 摄像机注视点 z
|
||||
move_camera: 0 # 0=固定视角, 1=按 move_camera.txt 运动
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# move_camera.txt — 摄像机速度段驱动
|
||||
# 格式: start-end vx=f vy=f vz=f rx=d ry=d rz=d
|
||||
# vx/vy/vz: 平移速度(每帧移动单位)
|
||||
# rx/ry/rz: 旋转速度(每帧度数)
|
||||
# rx → elevation(俯仰), ry → azimuth(方位), rz → (预留)
|
||||
#
|
||||
# 示例:前60帧向右平移+绕x旋转,30-90帧向上平移+绕y绕z旋转
|
||||
all vx=0.02
|
||||
# 30-90 vy=0.02 ry=1 rz=1
|
||||
@@ -31,7 +31,7 @@ def load_dynamics_module(module_path: Path):
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case01")
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case06")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
k1 500.0 1.0
|
||||
@@ -0,0 +1,120 @@
|
||||
n1 n2 bond_name
|
||||
1 2 k1
|
||||
2 3 k1
|
||||
3 4 k1
|
||||
4 5 k1
|
||||
5 6 k1
|
||||
6 7 k1
|
||||
7 8 k1
|
||||
8 9 k1
|
||||
9 10 k1
|
||||
10 11 k1
|
||||
11 12 k1
|
||||
12 13 k1
|
||||
13 14 k1
|
||||
14 15 k1
|
||||
15 16 k1
|
||||
16 17 k1
|
||||
17 18 k1
|
||||
18 19 k1
|
||||
19 20 k1
|
||||
20 21 k1
|
||||
21 22 k1
|
||||
22 23 k1
|
||||
23 24 k1
|
||||
24 25 k1
|
||||
25 26 k1
|
||||
26 27 k1
|
||||
27 28 k1
|
||||
28 29 k1
|
||||
29 30 k1
|
||||
30 31 k1
|
||||
31 32 k1
|
||||
32 33 k1
|
||||
33 34 k1
|
||||
34 35 k1
|
||||
35 36 k1
|
||||
36 37 k1
|
||||
37 38 k1
|
||||
38 39 k1
|
||||
39 40 k1
|
||||
40 41 k1
|
||||
41 42 k1
|
||||
42 43 k1
|
||||
43 44 k1
|
||||
44 45 k1
|
||||
45 46 k1
|
||||
46 47 k1
|
||||
47 48 k1
|
||||
48 49 k1
|
||||
49 50 k1
|
||||
50 51 k1
|
||||
51 52 k1
|
||||
52 53 k1
|
||||
53 54 k1
|
||||
54 55 k1
|
||||
55 56 k1
|
||||
56 57 k1
|
||||
57 58 k1
|
||||
58 59 k1
|
||||
59 60 k1
|
||||
60 61 k1
|
||||
61 62 k1
|
||||
62 63 k1
|
||||
63 64 k1
|
||||
64 65 k1
|
||||
65 66 k1
|
||||
66 67 k1
|
||||
67 68 k1
|
||||
68 69 k1
|
||||
69 70 k1
|
||||
70 71 k1
|
||||
71 72 k1
|
||||
72 73 k1
|
||||
73 74 k1
|
||||
74 75 k1
|
||||
75 76 k1
|
||||
76 77 k1
|
||||
77 78 k1
|
||||
78 79 k1
|
||||
79 80 k1
|
||||
80 81 k1
|
||||
81 82 k1
|
||||
82 83 k1
|
||||
83 84 k1
|
||||
84 85 k1
|
||||
85 86 k1
|
||||
86 87 k1
|
||||
87 88 k1
|
||||
88 89 k1
|
||||
89 90 k1
|
||||
90 91 k1
|
||||
91 92 k1
|
||||
92 93 k1
|
||||
93 94 k1
|
||||
94 95 k1
|
||||
95 96 k1
|
||||
96 97 k1
|
||||
97 98 k1
|
||||
98 99 k1
|
||||
99 100 k1
|
||||
100 101 k1
|
||||
101 102 k1
|
||||
102 103 k1
|
||||
103 104 k1
|
||||
104 105 k1
|
||||
105 106 k1
|
||||
106 107 k1
|
||||
107 108 k1
|
||||
108 109 k1
|
||||
109 110 k1
|
||||
110 111 k1
|
||||
111 112 k1
|
||||
112 113 k1
|
||||
113 114 k1
|
||||
114 115 k1
|
||||
115 116 k1
|
||||
116 117 k1
|
||||
117 118 k1
|
||||
118 119 k1
|
||||
119 120 k1
|
||||
@@ -0,0 +1,121 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.1 0 0 0 0 0 0 0 1 0
|
||||
2 1 0.1 1 0 0 0 0 0 0 1 0
|
||||
3 1 0.1 2 0 0 0 0 0 0 1 0
|
||||
4 1 0.1 3 0 0 0 0 0 0 1 0
|
||||
5 1 0.1 4 0 0 0 0 0 0 1 0
|
||||
6 1 0.1 5 0 0 0 0 0 0 1 0
|
||||
7 1 0.1 6 0 0 0 0 0 0 1 0
|
||||
8 1 0.1 7 0 0 0 0 0 0 1 0
|
||||
9 1 0.1 8 0 0 0 0 0 0 1 0
|
||||
10 1 0.1 9 0 0 0 0 0 0 1 0
|
||||
11 1 0.1 10 0 0 0 0 0 0 1 0
|
||||
12 1 0.1 11 0 0 0 0 0 0 1 0
|
||||
13 1 0.1 12 0 0 0 0 0 0 1 0
|
||||
14 1 0.1 13 0 0 0 0 0 0 1 0
|
||||
15 1 0.1 14 0 0 0 0 0 0 1 0
|
||||
16 1 0.1 15 0 0 0 0 0 0 1 0
|
||||
17 1 0.1 16 0 0 0 0 0 0 1 0
|
||||
18 1 0.1 17 0 0 0 0 0 0 1 0
|
||||
19 1 0.1 18 0 0 0 0 0 0 1 0
|
||||
20 1 0.1 19 0 0 0 0 0 0 1 0
|
||||
21 1 0.1 20 0 0 0 0 0 0 1 0
|
||||
22 1 0.1 21 0 0 0 0 0 0 1 0
|
||||
23 1 0.1 22 0 0 0 0 0 0 1 0
|
||||
24 1 0.1 23 0 0 0 0 0 0 1 0
|
||||
25 1 0.1 24 0 0 0 0 0 0 1 0
|
||||
26 1 0.1 25 0 0 0 0 0 0 1 0
|
||||
27 1 0.1 26 0 0 0 0 0 0 1 0
|
||||
28 1 0.1 27 0 0 0 0 0 0 1 0
|
||||
29 1 0.1 28 0 0 0 0 0 0 1 0
|
||||
30 1 0.1 29 0 0 0 0 0 0 1 0
|
||||
31 1 0.1 30 0 0 0 0 0 0 1 0
|
||||
32 1 0.1 31 0 0 0 0 0 0 1 0
|
||||
33 1 0.1 32 0 0 0 0 0 0 1 0
|
||||
34 1 0.1 33 0 0 0 0 0 0 1 0
|
||||
35 1 0.1 34 0 0 0 0 0 0 1 0
|
||||
36 1 0.1 35 0 0 0 0 0 0 1 0
|
||||
37 1 0.1 36 0 0 0 0 0 0 1 0
|
||||
38 1 0.1 37 0 0 0 0 0 0 1 0
|
||||
39 1 0.1 38 0 0 0 0 0 0 1 0
|
||||
40 1 0.1 39 0 0 0 0 0 0 1 0
|
||||
41 1 0.1 40 0 0 0 0 0 0 1 0
|
||||
42 1 0.1 41 0 0 0 0 0 0 1 0
|
||||
43 1 0.1 42 0 0 0 0 0 0 1 0
|
||||
44 1 0.1 43 0 0 0 0 0 0 1 0
|
||||
45 1 0.1 44 0 0 0 0 0 0 1 0
|
||||
46 1 0.1 45 0 0 0 0 0 0 1 0
|
||||
47 1 0.1 46 0 0 0 0 0 0 1 0
|
||||
48 1 0.1 47 0 0 0 0 0 0 1 0
|
||||
49 1 0.1 48 0 0 0 0 0 0 1 0
|
||||
50 1 0.1 49 0 0 0 0 0 0 1 0
|
||||
51 1 0.1 50 0 0 0 0 0 0 1 0
|
||||
52 1 0.1 51 0 0 0 0 0 0 1 0
|
||||
53 1 0.1 52 0 0 0 0 0 0 1 0
|
||||
54 1 0.1 53 0 0 0 0 0 0 1 0
|
||||
55 1 0.1 54 0 0 0 0 0 0 1 0
|
||||
56 1 0.1 55 0 0 0 0 0 0 1 0
|
||||
57 1 0.1 56 0 0 0 0 0 0 1 0
|
||||
58 1 0.1 57 0 0 0 0 0 0 1 0
|
||||
59 1 0.1 58 0 0 0 0 0 0 1 0
|
||||
60 1 0.1 59 0 0 0 0 0 0 1 0
|
||||
61 1 0.1 60 0 0 0 0 0 0 1 0
|
||||
62 1 0.1 61 0 0 0 0 0 0 1 0
|
||||
63 1 0.1 62 0 0 0 0 0 0 1 0
|
||||
64 1 0.1 63 0 0 0 0 0 0 1 0
|
||||
65 1 0.1 64 0 0 0 0 0 0 1 0
|
||||
66 1 0.1 65 0 0 0 0 0 0 1 0
|
||||
67 1 0.1 66 0 0 0 0 0 0 1 0
|
||||
68 1 0.1 67 0 0 0 0 0 0 1 0
|
||||
69 1 0.1 68 0 0 0 0 0 0 1 0
|
||||
70 1 0.1 69 0 0 0 0 0 0 1 0
|
||||
71 1 0.1 70 0 0 0 0 0 0 1 0
|
||||
72 1 0.1 71 0 0 0 0 0 0 1 0
|
||||
73 1 0.1 72 0 0 0 0 0 0 1 0
|
||||
74 1 0.1 73 0 0 0 0 0 0 1 0
|
||||
75 1 0.1 74 0 0 0 0 0 0 1 0
|
||||
76 1 0.1 75 0 0 0 0 0 0 1 0
|
||||
77 1 0.1 76 0 0 0 0 0 0 1 0
|
||||
78 1 0.1 77 0 0 0 0 0 0 1 0
|
||||
79 1 0.1 78 0 0 0 0 0 0 1 0
|
||||
80 1 0.1 79 0 0 0 0 0 0 1 0
|
||||
81 1 0.1 80 0 0 0 0 0 0 1 0
|
||||
82 1 0.1 81 0 0 0 0 0 0 1 0
|
||||
83 1 0.1 82 0 0 0 0 0 0 1 0
|
||||
84 1 0.1 83 0 0 0 0 0 0 1 0
|
||||
85 1 0.1 84 0 0 0 0 0 0 1 0
|
||||
86 1 0.1 85 0 0 0 0 0 0 1 0
|
||||
87 1 0.1 86 0 0 0 0 0 0 1 0
|
||||
88 1 0.1 87 0 0 0 0 0 0 1 0
|
||||
89 1 0.1 88 0 0 0 0 0 0 1 0
|
||||
90 1 0.1 89 0 0 0 0 0 0 1 0
|
||||
91 1 0.1 90 0 0 0 0 0 0 1 0
|
||||
92 1 0.1 91 0 0 0 0 0 0 1 0
|
||||
93 1 0.1 92 0 0 0 0 0 0 1 0
|
||||
94 1 0.1 93 0 0 0 0 0 0 1 0
|
||||
95 1 0.1 94 0 0 0 0 0 0 1 0
|
||||
96 1 0.1 95 0 0 0 0 0 0 1 0
|
||||
97 1 0.1 96 0 0 0 0 0 0 1 0
|
||||
98 1 0.1 97 0 0 0 0 0 0 1 0
|
||||
99 1 0.1 98 0 0 0 0 0 0 1 0
|
||||
100 1 0.1 99 0 0 0 0 0 0 1 0
|
||||
101 1 0.1 100 0 0 0 0 0 0 1 0
|
||||
102 1 0.1 101 0 0 0 0 0 0 1 0
|
||||
103 1 0.1 102 0 0 0 0 0 0 1 0
|
||||
104 1 0.1 103 0 0 0 0 0 0 1 0
|
||||
105 1 0.1 104 0 0 0 0 0 0 1 0
|
||||
106 1 0.1 105 0 0 0 0 0 0 1 0
|
||||
107 1 0.1 106 0 0 0 0 0 0 1 0
|
||||
108 1 0.1 107 0 0 0 0 0 0 1 0
|
||||
109 1 0.1 108 0 0 0 0 0 0 1 0
|
||||
110 1 0.1 109 0 0 0 0 0 0 1 0
|
||||
111 1 0.1 110 0 0 0 0 0 0 1 0
|
||||
112 1 0.1 111 0 0 0 0 0 0 1 0
|
||||
113 1 0.1 112 0 0 0 0 0 0 1 0
|
||||
114 1 0.1 113 0 0 0 0 0 0 1 0
|
||||
115 1 0.1 114 0 0 0 0 0 0 1 0
|
||||
116 1 0.1 115 0 0 0 0 0 0 1 0
|
||||
117 1 0.1 116 0 0 0 0 0 0 1 0
|
||||
118 1 0.1 117 0 0 0 0 0 0 1 0
|
||||
119 1 0.1 118 0 0 0 0 0 0 1 0
|
||||
120 1 0.1 119 0 0 0 0 0 1 1 1
|
||||
@@ -0,0 +1,3 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 0.1 0 0 0.01333 0 0 90 all
|
||||
120 0 0 0.1 0 0 0.01333 0 0 90 all
|
||||
@@ -0,0 +1,114 @@
|
||||
# 物理模拟参数配置
|
||||
# 格式:YAML
|
||||
# 用法:python run_dynamics.py
|
||||
|
||||
# ── 流程控制 ──────────────────────────────────
|
||||
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||
step_simulate: 1 # 运行物理模拟 → output/display.txt(引擎直接抽帧)
|
||||
step_sample: 0 # (旧版)从 trajectory.txt 重新抽帧,默认0=不执行
|
||||
step_plot: 0 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||
step_animation: 0 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||
step_plot_wave: 1 # 绘制波形能量动画
|
||||
force_calc: 1 # 强制重新计算:1=跳过缓存强算,0=自动使用已有输出
|
||||
plot_wave_save_gif: 0 # 输出波形 GIF(需 step_plot_wave=1)
|
||||
plot_wave_save_mp4: 0 # 输出波形 MP4(需 step_plot_wave=1)
|
||||
|
||||
# ── 文件保存 ──────────────────────────────────
|
||||
save_trajectory: 0 # 0=不保留完整轨迹文件, 1=保留 trajectory.txt(用于后续单独抽帧)
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
# 可选: python, c, cpp, fortran, java
|
||||
engine: c # 默认使用 python 引擎
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 300.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
# 坐标文件格式:
|
||||
# 第一行:n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
# 后续行:原子序号 质量 半径 x y z vx vy vz fix_x fix_y fix_z
|
||||
coord_file: input/coord.txt
|
||||
connection_file: input/connection.txt
|
||||
bond_file: input/bond.txt
|
||||
driver_file: input/driver.txt # 驱动力定义文件(driving_force=1 时生效)
|
||||
|
||||
# 绘图/动画展示的原子序号(对应 coord_file 第一列 n)
|
||||
plot_atom: 1
|
||||
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
# 三个方向分量分别对应 x, y, z
|
||||
G: [0.000, 0.000, 0.000] # 重力场分量 (m/s²)
|
||||
B: [0.005, 0.000, 0.005] # 阻尼分量
|
||||
|
||||
# ── 力开关(0=关闭, 1=开启)──────────────────
|
||||
gravity_field: 0 # 均匀重力场 (G)
|
||||
gravity_interaction: 0 # 原子间万有引力
|
||||
elastic_force: 1 # 弹簧键力
|
||||
damping_force: 1 # 阻尼 (B)
|
||||
driving_force: 1 # 驱动力(需 driver_file 定义)
|
||||
#
|
||||
gravity_strength: 1.0 # 万有引力强度(仅 gravity_interaction=1 时有效)
|
||||
|
||||
# ── 数值算法 ──────────────────────────────────
|
||||
# 可选:
|
||||
# explicit_euler 显式欧拉法
|
||||
# implicit_euler 隐式欧拉法
|
||||
# midpoint 中点法
|
||||
# leapfrog 蛙跳法
|
||||
method: leapfrog
|
||||
|
||||
# ── 步骤控制 ──────────────────────────────────
|
||||
# 以下参数控制哪些步骤被执行和保存
|
||||
|
||||
# 预热步数:模拟开始时跳过不保存的步数(用于稳定初始状态)
|
||||
warmup_steps: 0 # 默认 0(立即开始记录)
|
||||
|
||||
# 总模拟时间(秒),程序自动计算 NT = T_total / DT
|
||||
# 如果同时指定了 NT,以 NT 为准
|
||||
T_total: 1000.0
|
||||
|
||||
# 抽帧间隔(每 NSTEP 步取一帧用于动画)
|
||||
NSTEP: 500
|
||||
|
||||
# ── 时间步长 ──────────────────────────────────
|
||||
DT: 0.001 # 时间步长 (s)
|
||||
|
||||
# 抽帧范围:只保存 [sample_start, sample_end) 区间内的帧
|
||||
sample_start: null # null 表示从头开始(帧索引从 0 起)
|
||||
sample_end: null # null 表示到末尾
|
||||
|
||||
|
||||
|
||||
# ── 渲染方式 ──────────────────────────────────
|
||||
# 3D 动画中原子渲染方式:
|
||||
# 0 = Sphere (网格球体,效果精细,原子数少时推荐)
|
||||
# 1 = Marker (GPU 实例化点,原子数多时性能更佳)
|
||||
use_marker: 1
|
||||
|
||||
# ── 显示参数 ──────────────────────────────────
|
||||
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
# 小球颜色
|
||||
# 小球半径从 coord_file 的 radius 列读取
|
||||
ball_color_r: 0.20 # R 分量 (0~1)
|
||||
ball_color_g: 0.60 # G 分量
|
||||
ball_color_b: 0.90 # B 分量
|
||||
|
||||
# 盒子面颜色
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机初始位置 ────────────────────────────
|
||||
camera_distance: 120.0 # 摄像机到场景中心的距离
|
||||
camera_elevation: 0.0 # 俯仰角(度),负值=俯视
|
||||
camera_azimuth: 0.0 # 方位角(度)
|
||||
camera_center_x: 60.0 # 摄像机注视点 x
|
||||
camera_center_y: 0.0 # 摄像机注视点 y
|
||||
camera_center_z: 0.0 # 摄像机注视点 z
|
||||
move_camera: 0 # 0=固定视角, 1=按 move_camera.txt 运动
|
||||
|
||||
# ── 视觉放大 ──────────────────────────────────
|
||||
display_amp: [1.0, 1.0, 10.0] # x/y/z 方向视觉位移放大倍数(不影响物理)
|
||||
@@ -0,0 +1,9 @@
|
||||
# move_camera.txt — 摄像机速度段驱动
|
||||
# 格式: start-end vx=f vy=f vz=f rx=d ry=d rz=d
|
||||
# vx/vy/vz: 平移速度(每帧移动单位)
|
||||
# rx/ry/rz: 旋转速度(每帧度数)
|
||||
# rx → elevation(俯仰), ry → azimuth(方位), rz → (预留)
|
||||
#
|
||||
# 示例:前60帧向右平移+绕x旋转,30-90帧向上平移+绕y绕z旋转
|
||||
all vx=0.02
|
||||
# 30-90 vy=0.02 ry=1 rz=1
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Case runner for Dynamics case06 — 1D atomic chain (transverse wave).
|
||||
|
||||
This script keeps program and data separated:
|
||||
- program: ../../dynamics.py
|
||||
- input: ./input
|
||||
- output: ./output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CASE_DIR = Path(__file__).resolve().parent
|
||||
DYNAMICS_PATH = Path("..") / ".." / "dynamics.py"
|
||||
INPUT_DIR = Path("input")
|
||||
OUTPUT_DIR = Path("output")
|
||||
CONFIG_FILE = INPUT_DIR / "input.txt"
|
||||
|
||||
|
||||
def load_dynamics_module(module_path: Path):
|
||||
spec = importlib.util.spec_from_file_location("dynamics_module", module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"无法加载 dynamics.py: {module_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case06")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
dynamics_path = (CASE_DIR / DYNAMICS_PATH).resolve()
|
||||
input_dir = (CASE_DIR / INPUT_DIR).resolve()
|
||||
output_dir = (CASE_DIR / OUTPUT_DIR).resolve()
|
||||
config_path = (CASE_DIR / CONFIG_FILE).resolve()
|
||||
|
||||
module = load_dynamics_module(dynamics_path)
|
||||
module.run_case(
|
||||
config_path=config_path,
|
||||
runtime_base=CASE_DIR,
|
||||
input_dir=input_dir,
|
||||
output_dir=output_dir,
|
||||
no_plot=args.no_plot,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
k1 0.1 1.0
|
||||
@@ -0,0 +1,2 @@
|
||||
n1 n2 bond_name
|
||||
1 2 k1
|
||||
@@ -0,0 +1,3 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.1 0 0 0 0 0 0 0 1 0
|
||||
2 1 0.1 1 0 0 0 0 0 0 1 0
|
||||
@@ -0,0 +1,2 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0.1 0 0.0 0.02 0 0 90 0 0 all
|
||||
@@ -0,0 +1,114 @@
|
||||
# 物理模拟参数配置
|
||||
# 格式:YAML
|
||||
# 用法:python run_dynamics.py
|
||||
|
||||
# ── 流程控制 ──────────────────────────────────
|
||||
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||
step_simulate: 1 # 运行物理模拟 → output/display.txt(引擎直接抽帧)
|
||||
step_sample: 0 # (旧版)从 trajectory.txt 重新抽帧,默认0=不执行
|
||||
step_plot: 0 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||
step_animation: 0 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||
step_plot_wave: 1 # 绘制波形能量动画
|
||||
force_calc: 1 # 强制重新计算:1=跳过缓存强算,0=自动使用已有输出
|
||||
plot_wave_save_gif: 0 # 输出波形 GIF(需 step_plot_wave=1)
|
||||
plot_wave_save_mp4: 0 # 输出波形 MP4(需 step_plot_wave=1)
|
||||
|
||||
# ── 文件保存 ──────────────────────────────────
|
||||
save_trajectory: 0 # 0=不保留完整轨迹文件, 1=保留 trajectory.txt(用于后续单独抽帧)
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
# 可选: python, c, cpp, fortran, java
|
||||
engine: c # 默认使用 python 引擎
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 300.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
# 坐标文件格式:
|
||||
# 第一行:n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
# 后续行:原子序号 质量 半径 x y z vx vy vz fix_x fix_y fix_z
|
||||
coord_file: input/coord.txt
|
||||
connection_file: input/connection.txt
|
||||
bond_file: input/bond.txt
|
||||
driver_file: input/driver.txt # 驱动力定义文件(driving_force=1 时生效)
|
||||
|
||||
# 绘图/动画展示的原子序号(对应 coord_file 第一列 n)
|
||||
plot_atom: 1
|
||||
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
# 三个方向分量分别对应 x, y, z
|
||||
G: [0.000, 0.000, 0.000] # 重力场分量 (m/s²)
|
||||
B: [0.005, 0.000, 0.005] # 阻尼分量
|
||||
|
||||
# ── 力开关(0=关闭, 1=开启)──────────────────
|
||||
gravity_field: 0 # 均匀重力场 (G)
|
||||
gravity_interaction: 0 # 原子间万有引力
|
||||
elastic_force: 1 # 弹簧键力
|
||||
damping_force: 1 # 阻尼 (B)
|
||||
driving_force: 1 # 驱动力(需 driver_file 定义)
|
||||
#
|
||||
gravity_strength: 1.0 # 万有引力强度(仅 gravity_interaction=1 时有效)
|
||||
|
||||
# ── 数值算法 ──────────────────────────────────
|
||||
# 可选:
|
||||
# explicit_euler 显式欧拉法
|
||||
# implicit_euler 隐式欧拉法
|
||||
# midpoint 中点法
|
||||
# leapfrog 蛙跳法
|
||||
method: leapfrog
|
||||
|
||||
# ── 步骤控制 ──────────────────────────────────
|
||||
# 以下参数控制哪些步骤被执行和保存
|
||||
|
||||
# 预热步数:模拟开始时跳过不保存的步数(用于稳定初始状态)
|
||||
warmup_steps: 0 # 默认 0(立即开始记录)
|
||||
|
||||
# 总模拟时间(秒),程序自动计算 NT = T_total / DT
|
||||
# 如果同时指定了 NT,以 NT 为准
|
||||
T_total: 100.0
|
||||
|
||||
# 抽帧间隔(每 NSTEP 步取一帧用于动画)
|
||||
NSTEP: 500
|
||||
|
||||
# ── 时间步长 ──────────────────────────────────
|
||||
DT: 0.001 # 时间步长 (s)
|
||||
|
||||
# 抽帧范围:只保存 [sample_start, sample_end) 区间内的帧
|
||||
sample_start: null # null 表示从头开始(帧索引从 0 起)
|
||||
sample_end: null # null 表示到末尾
|
||||
|
||||
|
||||
|
||||
# ── 渲染方式 ──────────────────────────────────
|
||||
# 3D 动画中原子渲染方式:
|
||||
# 0 = Sphere (网格球体,效果精细,原子数少时推荐)
|
||||
# 1 = Marker (GPU 实例化点,原子数多时性能更佳)
|
||||
use_marker: 1
|
||||
|
||||
# ── 显示参数 ──────────────────────────────────
|
||||
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
# 小球颜色
|
||||
# 小球半径从 coord_file 的 radius 列读取
|
||||
ball_color_r: 0.20 # R 分量 (0~1)
|
||||
ball_color_g: 0.60 # G 分量
|
||||
ball_color_b: 0.90 # B 分量
|
||||
|
||||
# 盒子面颜色
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机初始位置 ────────────────────────────
|
||||
camera_distance: 10.0 # 摄像机到场景中心的距离
|
||||
camera_elevation: 0.0 # 俯仰角(度),负值=俯视
|
||||
camera_azimuth: 0.0 # 方位角(度)
|
||||
camera_center_x: 0.0 # 摄像机注视点 x
|
||||
camera_center_y: 0.0 # 摄像机注视点 y
|
||||
camera_center_z: 0.0 # 摄像机注视点 z
|
||||
move_camera: 0 # 0=固定视角, 1=按 move_camera.txt 运动
|
||||
|
||||
# ── 视觉放大 ──────────────────────────────────
|
||||
display_amp: [1.0, 1.0, 10.0] # x/y/z 方向视觉位移放大倍数(不影响物理)
|
||||
@@ -0,0 +1,9 @@
|
||||
# move_camera.txt — 摄像机速度段驱动
|
||||
# 格式: start-end vx=f vy=f vz=f rx=d ry=d rz=d
|
||||
# vx/vy/vz: 平移速度(每帧移动单位)
|
||||
# rx/ry/rz: 旋转速度(每帧度数)
|
||||
# rx → elevation(俯仰), ry → azimuth(方位), rz → (预留)
|
||||
#
|
||||
# 示例:前60帧向右平移+绕x旋转,30-90帧向上平移+绕y绕z旋转
|
||||
all vx=0.02
|
||||
# 30-90 vy=0.02 ry=1 rz=1
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Case runner for Dynamics case06 — 1D atomic chain (transverse wave).
|
||||
|
||||
This script keeps program and data separated:
|
||||
- program: ../../dynamics.py
|
||||
- input: ./input
|
||||
- output: ./output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CASE_DIR = Path(__file__).resolve().parent
|
||||
DYNAMICS_PATH = Path("..") / ".." / "dynamics.py"
|
||||
INPUT_DIR = Path("input")
|
||||
OUTPUT_DIR = Path("output")
|
||||
CONFIG_FILE = INPUT_DIR / "input.txt"
|
||||
|
||||
|
||||
def load_dynamics_module(module_path: Path):
|
||||
spec = importlib.util.spec_from_file_location("dynamics_module", module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"无法加载 dynamics.py: {module_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case06")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
dynamics_path = (CASE_DIR / DYNAMICS_PATH).resolve()
|
||||
input_dir = (CASE_DIR / INPUT_DIR).resolve()
|
||||
output_dir = (CASE_DIR / OUTPUT_DIR).resolve()
|
||||
config_path = (CASE_DIR / CONFIG_FILE).resolve()
|
||||
|
||||
module = load_dynamics_module(dynamics_path)
|
||||
module.run_case(
|
||||
config_path=config_path,
|
||||
runtime_base=CASE_DIR,
|
||||
input_dir=input_dir,
|
||||
output_dir=output_dir,
|
||||
no_plot=args.no_plot,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
k1 300.0 1.0
|
||||
@@ -0,0 +1,40 @@
|
||||
n1 n2 bond_name
|
||||
1 2 k1
|
||||
2 3 k1
|
||||
3 4 k1
|
||||
4 5 k1
|
||||
5 6 k1
|
||||
6 7 k1
|
||||
7 8 k1
|
||||
8 9 k1
|
||||
9 10 k1
|
||||
10 11 k1
|
||||
11 12 k1
|
||||
12 13 k1
|
||||
13 14 k1
|
||||
14 15 k1
|
||||
15 16 k1
|
||||
16 17 k1
|
||||
17 18 k1
|
||||
18 19 k1
|
||||
19 20 k1
|
||||
20 21 k1
|
||||
21 22 k1
|
||||
22 23 k1
|
||||
23 24 k1
|
||||
24 25 k1
|
||||
25 26 k1
|
||||
26 27 k1
|
||||
27 28 k1
|
||||
28 29 k1
|
||||
29 30 k1
|
||||
30 31 k1
|
||||
31 32 k1
|
||||
32 33 k1
|
||||
33 34 k1
|
||||
34 35 k1
|
||||
35 36 k1
|
||||
36 37 k1
|
||||
37 38 k1
|
||||
38 39 k1
|
||||
39 40 k1
|
||||
@@ -0,0 +1,41 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.1 0 0 0 0 0 0 0 1 0
|
||||
2 1 0.1 1 0 0 0 0 0 0 1 0
|
||||
3 1 0.1 2 0 0 0 0 0 0 1 0
|
||||
4 1 0.1 3 0 0 0 0 0 0 1 0
|
||||
5 1 0.1 4 0 0 0 0 0 0 1 0
|
||||
6 1 0.1 5 0 0 0 0 0 0 1 0
|
||||
7 1 0.1 6 0 0 0 0 0 0 1 0
|
||||
8 1 0.1 7 0 0 0 0 0 0 1 0
|
||||
9 1 0.1 8 0 0 0 0 0 0 1 0
|
||||
10 1 0.1 9 0 0 0 0 0 0 1 0
|
||||
11 1 0.1 10 0 0 0 0 0 0 1 0
|
||||
12 1 0.1 11 0 0 0 0 0 0 1 0
|
||||
13 1 0.1 12 0 0 0 0 0 0 1 0
|
||||
14 1 0.1 13 0 0 0 0 0 0 1 0
|
||||
15 1 0.1 14 0 0 0 0 0 0 1 0
|
||||
16 1 0.1 15 0 0 0 0 0 0 1 0
|
||||
17 1 0.1 16 0 0 0 0 0 0 1 0
|
||||
18 1 0.1 17 0 0 0 0 0 0 1 0
|
||||
19 1 0.1 18 0 0 0 0 0 0 1 0
|
||||
20 1 0.1 19 0 0 0 0 0 0 1 0
|
||||
21 1 0.1 20 0 0 0 0 0 0 1 0
|
||||
22 1 0.1 21 0 0 0 0 0 0 1 0
|
||||
23 1 0.1 22 0 0 0 0 0 0 1 0
|
||||
24 1 0.1 23 0 0 0 0 0 0 1 0
|
||||
25 1 0.1 24 0 0 0 0 0 0 1 0
|
||||
26 1 0.1 25 0 0 0 0 0 0 1 0
|
||||
27 1 0.1 26 0 0 0 0 0 0 1 0
|
||||
28 1 0.1 27 0 0 0 0 0 0 1 0
|
||||
29 1 0.1 28 0 0 0 0 0 0 1 0
|
||||
30 1 0.1 29 0 0 0 0 0 0 1 0
|
||||
31 1 0.1 30 0 0 0 0 0 0 1 0
|
||||
32 1 0.1 31 0 0 0 0 0 0 1 0
|
||||
33 1 0.1 32 0 0 0 0 0 0 1 0
|
||||
34 1 0.1 33 0 0 0 0 0 0 1 0
|
||||
35 1 0.1 34 0 0 0 0 0 0 1 0
|
||||
36 1 0.1 35 0 0 0 0 0 0 1 0
|
||||
37 1 0.1 36 0 0 0 0 0 0 1 0
|
||||
38 1 0.1 37 0 0 0 0 0 0 1 0
|
||||
39 1 0.1 38 0 0 0 0 0 0 1 0
|
||||
40 1 0.1 39 0 0 0 0 0 1 1 1
|
||||
@@ -0,0 +1,2 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 0.1 0 0 0.04 0 0 90 all
|
||||
@@ -0,0 +1,114 @@
|
||||
# 物理模拟参数配置
|
||||
# 格式:YAML
|
||||
# 用法:python run_dynamics.py
|
||||
|
||||
# ── 流程控制 ──────────────────────────────────
|
||||
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||
step_simulate: 1 # 运行物理模拟 → output/display.txt(引擎直接抽帧)
|
||||
step_sample: 0 # (旧版)从 trajectory.txt 重新抽帧,默认0=不执行
|
||||
step_plot: 0 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||
step_animation: 1 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||
step_plot_wave: 1 # 绘制波形能量动画
|
||||
force_calc: 1 # 强制重新计算:1=跳过缓存强算,0=自动使用已有输出
|
||||
plot_wave_save_gif: 0 # 输出波形 GIF(需 step_plot_wave=1)
|
||||
plot_wave_save_mp4: 0 # 输出波形 MP4(需 step_plot_wave=1)
|
||||
|
||||
# ── 文件保存 ──────────────────────────────────
|
||||
save_trajectory: 0 # 0=不保留完整轨迹文件, 1=保留 trajectory.txt(用于后续单独抽帧)
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
# 可选: python, c, cpp, fortran, java
|
||||
engine: fortran # 默认使用 python 引擎
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 300.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
# 坐标文件格式:
|
||||
# 第一行:n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
# 后续行:原子序号 质量 半径 x y z vx vy vz fix_x fix_y fix_z
|
||||
coord_file: input/coord.txt
|
||||
connection_file: input/connection.txt
|
||||
bond_file: input/bond.txt
|
||||
driver_file: input/driver.txt # 驱动力定义文件(driving_force=1 时生效)
|
||||
|
||||
# 绘图/动画展示的原子序号(对应 coord_file 第一列 n)
|
||||
plot_atom: 1
|
||||
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
# 三个方向分量分别对应 x, y, z
|
||||
G: [0.000, 0.000, 0.000] # 重力场分量 (m/s²)
|
||||
B: [0.005, 0.000, 0.005] # 阻尼分量
|
||||
|
||||
# ── 力开关(0=关闭, 1=开启)──────────────────
|
||||
gravity_field: 0 # 均匀重力场 (G)
|
||||
gravity_interaction: 0 # 原子间万有引力
|
||||
elastic_force: 1 # 弹簧键力
|
||||
damping_force: 0 # 阻尼 (B)
|
||||
driving_force: 1 # 驱动力(需 driver_file 定义)
|
||||
#
|
||||
gravity_strength: 1.0 # 万有引力强度(仅 gravity_interaction=1 时有效)
|
||||
|
||||
# ── 数值算法 ──────────────────────────────────
|
||||
# 可选:
|
||||
# explicit_euler 显式欧拉法
|
||||
# implicit_euler 隐式欧拉法
|
||||
# midpoint 中点法
|
||||
# leapfrog 蛙跳法
|
||||
method: leapfrog
|
||||
|
||||
# ── 步骤控制 ──────────────────────────────────
|
||||
# 以下参数控制哪些步骤被执行和保存
|
||||
|
||||
# 预热步数:模拟开始时跳过不保存的步数(用于稳定初始状态)
|
||||
warmup_steps: 0 # 默认 0(立即开始记录)
|
||||
|
||||
# 总模拟时间(秒),程序自动计算 NT = T_total / DT
|
||||
# 如果同时指定了 NT,以 NT 为准
|
||||
T_total: 200.0
|
||||
|
||||
# 抽帧间隔(每 NSTEP 步取一帧用于动画)
|
||||
NSTEP: 100
|
||||
|
||||
# ── 时间步长 ──────────────────────────────────
|
||||
DT: 0.001 # 时间步长 (s)
|
||||
|
||||
# 抽帧范围:只保存 [sample_start, sample_end) 区间内的帧
|
||||
sample_start: null # null 表示从头开始(帧索引从 0 起)
|
||||
sample_end: null # null 表示到末尾
|
||||
|
||||
|
||||
|
||||
# ── 渲染方式 ──────────────────────────────────
|
||||
# 3D 动画中原子渲染方式:
|
||||
# 0 = Sphere (网格球体,效果精细,原子数少时推荐)
|
||||
# 1 = Marker (GPU 实例化点,原子数多时性能更佳)
|
||||
use_marker: 1
|
||||
|
||||
# ── 显示参数 ──────────────────────────────────
|
||||
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
# 小球颜色
|
||||
# 小球半径从 coord_file 的 radius 列读取
|
||||
ball_color_r: 0.20 # R 分量 (0~1)
|
||||
ball_color_g: 0.60 # G 分量
|
||||
ball_color_b: 0.90 # B 分量
|
||||
|
||||
# 盒子面颜色
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机初始位置 ────────────────────────────
|
||||
camera_distance: 60.0 # 摄像机到场景中心的距离
|
||||
camera_elevation: 0.0 # 俯仰角(度),负值=俯视
|
||||
camera_azimuth: 0.0 # 方位角(度)
|
||||
camera_center_x: 30.0 # 摄像机注视点 x
|
||||
camera_center_y: 0.0 # 摄像机注视点 y
|
||||
camera_center_z: 0.0 # 摄像机注视点 z
|
||||
move_camera: 0 # 0=固定视角, 1=按 move_camera.txt 运动
|
||||
|
||||
# ── 视觉放大 ──────────────────────────────────
|
||||
display_amp: [1.0, 1.0, 10.0] # x/y/z 方向视觉位移放大倍数(不影响物理)
|
||||
@@ -0,0 +1,9 @@
|
||||
# move_camera.txt — 摄像机速度段驱动
|
||||
# 格式: start-end vx=f vy=f vz=f rx=d ry=d rz=d
|
||||
# vx/vy/vz: 平移速度(每帧移动单位)
|
||||
# rx/ry/rz: 旋转速度(每帧度数)
|
||||
# rx → elevation(俯仰), ry → azimuth(方位), rz → (预留)
|
||||
#
|
||||
# 示例:前60帧向右平移+绕x旋转,30-90帧向上平移+绕y绕z旋转
|
||||
all vx=0.02
|
||||
# 30-90 vy=0.02 ry=1 rz=1
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Case runner for Dynamics case06 — 1D atomic chain (transverse wave).
|
||||
|
||||
This script keeps program and data separated:
|
||||
- program: ../../dynamics.py
|
||||
- input: ./input
|
||||
- output: ./output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CASE_DIR = Path(__file__).resolve().parent
|
||||
DYNAMICS_PATH = Path("..") / ".." / "dynamics.py"
|
||||
INPUT_DIR = Path("input")
|
||||
OUTPUT_DIR = Path("output")
|
||||
CONFIG_FILE = INPUT_DIR / "input.txt"
|
||||
|
||||
|
||||
def load_dynamics_module(module_path: Path):
|
||||
spec = importlib.util.spec_from_file_location("dynamics_module", module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"无法加载 dynamics.py: {module_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case06")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
dynamics_path = (CASE_DIR / DYNAMICS_PATH).resolve()
|
||||
input_dir = (CASE_DIR / INPUT_DIR).resolve()
|
||||
output_dir = (CASE_DIR / OUTPUT_DIR).resolve()
|
||||
config_path = (CASE_DIR / CONFIG_FILE).resolve()
|
||||
|
||||
module = load_dynamics_module(dynamics_path)
|
||||
module.run_case(
|
||||
config_path=config_path,
|
||||
runtime_base=CASE_DIR,
|
||||
input_dir=input_dir,
|
||||
output_dir=output_dir,
|
||||
no_plot=args.no_plot,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
k1 300.0 1.0
|
||||
@@ -0,0 +1,40 @@
|
||||
n1 n2 bond_name
|
||||
1 2 k1
|
||||
2 3 k1
|
||||
3 4 k1
|
||||
4 5 k1
|
||||
5 6 k1
|
||||
6 7 k1
|
||||
7 8 k1
|
||||
8 9 k1
|
||||
9 10 k1
|
||||
10 11 k1
|
||||
11 12 k1
|
||||
12 13 k1
|
||||
13 14 k1
|
||||
14 15 k1
|
||||
15 16 k1
|
||||
16 17 k1
|
||||
17 18 k1
|
||||
18 19 k1
|
||||
19 20 k1
|
||||
20 21 k1
|
||||
21 22 k1
|
||||
22 23 k1
|
||||
23 24 k1
|
||||
24 25 k1
|
||||
25 26 k1
|
||||
26 27 k1
|
||||
27 28 k1
|
||||
28 29 k1
|
||||
29 30 k1
|
||||
30 31 k1
|
||||
31 32 k1
|
||||
32 33 k1
|
||||
33 34 k1
|
||||
34 35 k1
|
||||
35 36 k1
|
||||
36 37 k1
|
||||
37 38 k1
|
||||
38 39 k1
|
||||
39 40 k1
|
||||
@@ -0,0 +1,41 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.1 0 0 0 0 0 0 0 1 1
|
||||
2 1 0.1 1 0 0 0 0 0 0 1 1
|
||||
3 1 0.1 2 0 0 0 0 0 0 1 1
|
||||
4 1 0.1 3 0 0 0 0 0 0 1 1
|
||||
5 1 0.1 4 0 0 0 0 0 0 1 1
|
||||
6 1 0.1 5 0 0 0 0 0 0 1 1
|
||||
7 1 0.1 6 0 0 0 0 0 0 1 1
|
||||
8 1 0.1 7 0 0 0 0 0 0 1 1
|
||||
9 1 0.1 8 0 0 0 0 0 0 1 1
|
||||
10 1 0.1 9 0 0 0 0 0 0 1 1
|
||||
11 1 0.1 10 0 0 0 0 0 0 1 1
|
||||
12 1 0.1 11 0 0 0 0 0 0 1 1
|
||||
13 1 0.1 12 0 0 0 0 0 0 1 1
|
||||
14 1 0.1 13 0 0 0 0 0 0 1 1
|
||||
15 1 0.1 14 0 0 0 0 0 0 1 1
|
||||
16 1 0.1 15 0 0 0 0 0 0 1 1
|
||||
17 1 0.1 16 0 0 0 0 0 0 1 1
|
||||
18 1 0.1 17 0 0 0 0 0 0 1 1
|
||||
19 1 0.1 18 0 0 0 0 0 0 1 1
|
||||
20 1 0.1 19 0 0 0 0 0 0 1 1
|
||||
21 1 0.1 20 0 0 0 0 0 0 1 1
|
||||
22 1 0.1 21 0 0 0 0 0 0 1 1
|
||||
23 1 0.1 22 0 0 0 0 0 0 1 1
|
||||
24 1 0.1 23 0 0 0 0 0 0 1 1
|
||||
25 1 0.1 24 0 0 0 0 0 0 1 1
|
||||
26 1 0.1 25 0 0 0 0 0 0 1 1
|
||||
27 1 0.1 26 0 0 0 0 0 0 1 1
|
||||
28 1 0.1 27 0 0 0 0 0 0 1 1
|
||||
29 1 0.1 28 0 0 0 0 0 0 1 1
|
||||
30 1 0.1 29 0 0 0 0 0 0 1 1
|
||||
31 1 0.1 30 0 0 0 0 0 0 1 1
|
||||
32 1 0.1 31 0 0 0 0 0 0 1 1
|
||||
33 1 0.1 32 0 0 0 0 0 0 1 1
|
||||
34 1 0.1 33 0 0 0 0 0 0 1 1
|
||||
35 1 0.1 34 0 0 0 0 0 0 1 1
|
||||
36 1 0.1 35 0 0 0 0 0 0 1 1
|
||||
37 1 0.1 36 0 0 0 0 0 0 1 1
|
||||
38 1 0.1 37 0 0 0 0 0 0 1 1
|
||||
39 1 0.1 38 0 0 0 0 0 0 1 1
|
||||
40 1 0.1 39 0 0 0 0 0 1 1 1
|
||||
@@ -0,0 +1,2 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0.1 0 0 0.04 0 0 0 0 90 all
|
||||
@@ -0,0 +1,114 @@
|
||||
# 物理模拟参数配置
|
||||
# 格式:YAML
|
||||
# 用法:python run_dynamics.py
|
||||
|
||||
# ── 流程控制 ──────────────────────────────────
|
||||
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||
step_simulate: 1 # 运行物理模拟 → output/display.txt(引擎直接抽帧)
|
||||
step_sample: 0 # (旧版)从 trajectory.txt 重新抽帧,默认0=不执行
|
||||
step_plot: 1 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||
step_animation: 0 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||
step_plot_wave: 1 # 绘制波形能量动画
|
||||
force_calc: 1 # 强制重新计算:1=跳过缓存强算,0=自动使用已有输出
|
||||
plot_wave_save_gif: 0 # 输出波形 GIF(需 step_plot_wave=1)
|
||||
plot_wave_save_mp4: 0 # 输出波形 MP4(需 step_plot_wave=1)
|
||||
|
||||
# ── 文件保存 ──────────────────────────────────
|
||||
save_trajectory: 0 # 0=不保留完整轨迹文件, 1=保留 trajectory.txt(用于后续单独抽帧)
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
# 可选: python, c, cpp, fortran, java
|
||||
engine: c # 默认使用 python 引擎
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 300.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
# 坐标文件格式:
|
||||
# 第一行:n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
# 后续行:原子序号 质量 半径 x y z vx vy vz fix_x fix_y fix_z
|
||||
coord_file: input/coord.txt
|
||||
connection_file: input/connection.txt
|
||||
bond_file: input/bond.txt
|
||||
driver_file: input/driver.txt # 驱动力定义文件(driving_force=1 时生效)
|
||||
|
||||
# 绘图/动画展示的原子序号(对应 coord_file 第一列 n)
|
||||
plot_atom: 1
|
||||
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
# 三个方向分量分别对应 x, y, z
|
||||
G: [0.000, 0.000, 0.000] # 重力场分量 (m/s²)
|
||||
B: [0.005, 0.000, 0.005] # 阻尼分量
|
||||
|
||||
# ── 力开关(0=关闭, 1=开启)──────────────────
|
||||
gravity_field: 0 # 均匀重力场 (G)
|
||||
gravity_interaction: 0 # 原子间万有引力
|
||||
elastic_force: 1 # 弹簧键力
|
||||
damping_force: 0 # 阻尼 (B)
|
||||
driving_force: 1 # 驱动力(需 driver_file 定义)
|
||||
#
|
||||
gravity_strength: 1.0 # 万有引力强度(仅 gravity_interaction=1 时有效)
|
||||
|
||||
# ── 数值算法 ──────────────────────────────────
|
||||
# 可选:
|
||||
# explicit_euler 显式欧拉法
|
||||
# implicit_euler 隐式欧拉法
|
||||
# midpoint 中点法
|
||||
# leapfrog 蛙跳法
|
||||
method: leapfrog
|
||||
|
||||
# ── 步骤控制 ──────────────────────────────────
|
||||
# 以下参数控制哪些步骤被执行和保存
|
||||
|
||||
# 预热步数:模拟开始时跳过不保存的步数(用于稳定初始状态)
|
||||
warmup_steps: 0 # 默认 0(立即开始记录)
|
||||
|
||||
# 总模拟时间(秒),程序自动计算 NT = T_total / DT
|
||||
# 如果同时指定了 NT,以 NT 为准
|
||||
T_total: 10.0
|
||||
|
||||
# 抽帧间隔(每 NSTEP 步取一帧用于动画)
|
||||
NSTEP: 20
|
||||
|
||||
# ── 时间步长 ──────────────────────────────────
|
||||
DT: 0.001 # 时间步长 (s)
|
||||
|
||||
# 抽帧范围:只保存 [sample_start, sample_end) 区间内的帧
|
||||
sample_start: null # null 表示从头开始(帧索引从 0 起)
|
||||
sample_end: null # null 表示到末尾
|
||||
|
||||
|
||||
|
||||
# ── 渲染方式 ──────────────────────────────────
|
||||
# 3D 动画中原子渲染方式:
|
||||
# 0 = Sphere (网格球体,效果精细,原子数少时推荐)
|
||||
# 1 = Marker (GPU 实例化点,原子数多时性能更佳)
|
||||
use_marker: 1
|
||||
|
||||
# ── 显示参数 ──────────────────────────────────
|
||||
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
# 小球颜色
|
||||
# 小球半径从 coord_file 的 radius 列读取
|
||||
ball_color_r: 0.20 # R 分量 (0~1)
|
||||
ball_color_g: 0.60 # G 分量
|
||||
ball_color_b: 0.90 # B 分量
|
||||
|
||||
# 盒子面颜色
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机初始位置 ────────────────────────────
|
||||
camera_distance: 120.0 # 摄像机到场景中心的距离
|
||||
camera_elevation: 0.0 # 俯仰角(度),负值=俯视
|
||||
camera_azimuth: 0.0 # 方位角(度)
|
||||
camera_center_x: 60.0 # 摄像机注视点 x
|
||||
camera_center_y: 0.0 # 摄像机注视点 y
|
||||
camera_center_z: 0.0 # 摄像机注视点 z
|
||||
move_camera: 0 # 0=固定视角, 1=按 move_camera.txt 运动
|
||||
|
||||
# ── 视觉放大 ──────────────────────────────────
|
||||
display_amp: [1.0, 1.0, 10.0] # x/y/z 方向视觉位移放大倍数(不影响物理)
|
||||
@@ -0,0 +1,9 @@
|
||||
# move_camera.txt — 摄像机速度段驱动
|
||||
# 格式: start-end vx=f vy=f vz=f rx=d ry=d rz=d
|
||||
# vx/vy/vz: 平移速度(每帧移动单位)
|
||||
# rx/ry/rz: 旋转速度(每帧度数)
|
||||
# rx → elevation(俯仰), ry → azimuth(方位), rz → (预留)
|
||||
#
|
||||
# 示例:前60帧向右平移+绕x旋转,30-90帧向上平移+绕y绕z旋转
|
||||
all vx=0.02
|
||||
# 30-90 vy=0.02 ry=1 rz=1
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Case runner for Dynamics case06 — 1D atomic chain (transverse wave).
|
||||
|
||||
This script keeps program and data separated:
|
||||
- program: ../../dynamics.py
|
||||
- input: ./input
|
||||
- output: ./output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CASE_DIR = Path(__file__).resolve().parent
|
||||
DYNAMICS_PATH = Path("..") / ".." / "dynamics.py"
|
||||
INPUT_DIR = Path("input")
|
||||
OUTPUT_DIR = Path("output")
|
||||
CONFIG_FILE = INPUT_DIR / "input.txt"
|
||||
|
||||
|
||||
def load_dynamics_module(module_path: Path):
|
||||
spec = importlib.util.spec_from_file_location("dynamics_module", module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"无法加载 dynamics.py: {module_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case06")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
dynamics_path = (CASE_DIR / DYNAMICS_PATH).resolve()
|
||||
input_dir = (CASE_DIR / INPUT_DIR).resolve()
|
||||
output_dir = (CASE_DIR / OUTPUT_DIR).resolve()
|
||||
config_path = (CASE_DIR / CONFIG_FILE).resolve()
|
||||
|
||||
module = load_dynamics_module(dynamics_path)
|
||||
module.run_case(
|
||||
config_path=config_path,
|
||||
runtime_base=CASE_DIR,
|
||||
input_dir=input_dir,
|
||||
output_dir=output_dir,
|
||||
no_plot=args.no_plot,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
h 100.0 1.0
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
5101 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
@@ -0,0 +1,89 @@
|
||||
# 物理模拟参数配置
|
||||
# 格式:YAML
|
||||
# 用法:python run_dynamics.py
|
||||
|
||||
# ── 流程控制 ──────────────────────────────────
|
||||
step_simulate: 1 # 运行物理模拟
|
||||
step_sample: 0 # 重新抽帧,默认0=不执行
|
||||
step_plot: 0 # 绘制轨迹/能量图
|
||||
step_animation: 1 # 自动播放 VisPy 3D 动画窗口
|
||||
step_plot_wave: 0 # 绘制波形能量动画
|
||||
force_calc: 1 # 强制重新计算
|
||||
|
||||
# ── 文件保存 ──────────────────────────────────
|
||||
save_trajectory: 0 # 0=不保留完整轨迹文件
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
engine: c
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 120.0
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
coord_file: input/coord.txt
|
||||
connection_file: input/connection.txt
|
||||
bond_file: input/bond.txt
|
||||
driver_file: input/driver.txt
|
||||
|
||||
# 绘图/动画展示的原子序号
|
||||
plot_atom: 5101 # 中心原子 (0,0)
|
||||
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
G: [0.000, 0.000, 0.000]
|
||||
B: [0.000, 0.000, 0.000]
|
||||
|
||||
# ── 力开关 ────────────────────────────────────
|
||||
gravity_field: 0
|
||||
gravity_interaction: 0
|
||||
elastic_force: 1
|
||||
damping_force: 0
|
||||
driving_force: 1
|
||||
gravity_strength: 1.0
|
||||
|
||||
# ── 数值算法 ──────────────────────────────────
|
||||
method: leapfrog
|
||||
|
||||
# ── 步骤控制 ──────────────────────────────────
|
||||
warmup_steps: 0 # 受迫波动,无需预热
|
||||
T_total: 100.0
|
||||
NSTEP: 10
|
||||
DT: 0.01
|
||||
|
||||
sample_start: null
|
||||
sample_end: null
|
||||
|
||||
# ── 渲染方式 ──────────────────────────────────
|
||||
use_marker: 1
|
||||
|
||||
# ── 位移着色 ──────────────────────────────────
|
||||
display_color: {
|
||||
x : [0, [255, 0, 0]],
|
||||
y : [0, [ 0, 255, 0]],
|
||||
z : [1, [ 0, 0, 255]],
|
||||
xy : [0, [255, 255, 0]],
|
||||
yz : [0, [ 0, 255, 255]],
|
||||
zx : [0, [255, 0, 255]],
|
||||
xyz : [0, [ 0, 0, 0]],
|
||||
}
|
||||
|
||||
# ── 显示参数 ──────────────────────────────────
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
ball_color_r: 0.20
|
||||
ball_color_g: 0.60
|
||||
ball_color_b: 0.90
|
||||
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机 ────────────────────────────────────
|
||||
camera_distance: 120.0
|
||||
camera_elevation: 60.0
|
||||
camera_azimuth: -45.0
|
||||
camera_center_x: 0.0
|
||||
camera_center_y: 0.0
|
||||
camera_center_z: 0.0
|
||||
move_camera: 0
|
||||
|
||||
display_amp: [1.0, 1.0, 1.0]
|
||||
@@ -0,0 +1,2 @@
|
||||
0 0 50
|
||||
0 0 80
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Case runner for Dynamics case11 — 2D grid (61x61 atomic mesh).
|
||||
|
||||
This script keeps program and data separated:
|
||||
- program: ../../dynamics.py
|
||||
- input: ./input
|
||||
- output: ./output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CASE_DIR = Path(__file__).resolve().parent
|
||||
DYNAMICS_PATH = Path("..") / ".." / "dynamics.py"
|
||||
INPUT_DIR = Path("input")
|
||||
OUTPUT_DIR = Path("output")
|
||||
CONFIG_FILE = INPUT_DIR / "input.txt"
|
||||
|
||||
|
||||
def load_dynamics_module(module_path: Path):
|
||||
spec = importlib.util.spec_from_file_location("dynamics_module", module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"无法加载 dynamics.py: {module_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case11")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
dynamics_path = (CASE_DIR / DYNAMICS_PATH).resolve()
|
||||
input_dir = (CASE_DIR / INPUT_DIR).resolve()
|
||||
output_dir = (CASE_DIR / OUTPUT_DIR).resolve()
|
||||
config_path = (CASE_DIR / CONFIG_FILE).resolve()
|
||||
|
||||
module = load_dynamics_module(dynamics_path)
|
||||
module.run_case(
|
||||
config_path=config_path,
|
||||
runtime_base=CASE_DIR,
|
||||
input_dir=input_dir,
|
||||
output_dir=output_dir,
|
||||
no_plot=args.no_plot,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
h 100.0 1.0
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
3081 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
7121 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
@@ -0,0 +1,83 @@
|
||||
# 物理模拟参数配置
|
||||
# case12 — 二维网格两点干涉(双点源 z 方向驱动)
|
||||
# 驱动点: (0,-10) 和 (0,10),波从两点向外传播,在中心区域干涉
|
||||
|
||||
# ── 流程控制 ──────────────────────────────────
|
||||
step_simulate: 1 # 运行物理模拟
|
||||
step_sample: 0 # 重新抽帧,默认0=不执行
|
||||
step_plot: 0 # 绘制轨迹/能量图
|
||||
step_animation: 1 # 自动播放 VisPy 3D 动画窗口
|
||||
step_plot_wave: 0 # 绘制波形能量动画
|
||||
force_calc: 1 # 强制重新计算
|
||||
|
||||
# ── 文件保存 ──────────────────────────────────
|
||||
save_trajectory: 0
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
engine: c
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 120.0
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
coord_file: input/coord.txt
|
||||
connection_file: input/connection.txt
|
||||
bond_file: input/bond.txt
|
||||
driver_file: input/driver.txt
|
||||
plot_atom: 5101 # 中心区域用于信息显示
|
||||
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
G: [0.000, 0.000, 0.000]
|
||||
B: [0.000, 0.000, 0.000]
|
||||
|
||||
gravity_field: 0
|
||||
gravity_interaction: 0
|
||||
elastic_force: 1
|
||||
damping_force: 0
|
||||
driving_force: 1
|
||||
gravity_strength: 1.0
|
||||
|
||||
method: leapfrog
|
||||
|
||||
# ── 步骤控制 ──────────────────────────────────
|
||||
warmup_steps: 0
|
||||
T_total: 100.0
|
||||
NSTEP: 500
|
||||
DT: 0.001
|
||||
|
||||
sample_start: null
|
||||
sample_end: null
|
||||
|
||||
# ── 渲染/着色 ─────────────────────────────────
|
||||
use_marker: 1
|
||||
display_color: {
|
||||
x : [0, [255, 0, 0]],
|
||||
y : [0, [ 0, 255, 0]],
|
||||
z : [0, [ 0, 0, 255]],
|
||||
xy : [0, [255, 255, 0]],
|
||||
yz : [0, [ 0, 255, 255]],
|
||||
zx : [0, [255, 0, 255]],
|
||||
xyz : [1, [255, 255, 255]],
|
||||
}
|
||||
|
||||
# ── 显示参数 ──────────────────────────────────
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
ball_color_r: 0.20
|
||||
ball_color_g: 0.60
|
||||
ball_color_b: 0.90
|
||||
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机 ────────────────────────────────────
|
||||
camera_distance: 120.0
|
||||
camera_elevation: 60.0
|
||||
camera_azimuth: -45.0
|
||||
camera_center_x: 0.0
|
||||
camera_center_y: 0.0
|
||||
camera_center_z: 0.0
|
||||
move_camera: 0
|
||||
|
||||
display_amp: [1.0, 1.0, 1.0]
|
||||
@@ -0,0 +1,2 @@
|
||||
0 0 50
|
||||
0 0 80
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Case runner for Dynamics case12 — 2D grid dual source interference.
|
||||
|
||||
This script keeps program and data separated:
|
||||
- program: ../../dynamics.py
|
||||
- input: ./input
|
||||
- output: ./output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CASE_DIR = Path(__file__).resolve().parent
|
||||
DYNAMICS_PATH = Path("..") / ".." / "dynamics.py"
|
||||
INPUT_DIR = Path("input")
|
||||
OUTPUT_DIR = Path("output")
|
||||
CONFIG_FILE = INPUT_DIR / "input.txt"
|
||||
|
||||
|
||||
def load_dynamics_module(module_path: Path):
|
||||
spec = importlib.util.spec_from_file_location("dynamics_module", module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"无法加载 dynamics.py: {module_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case11")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
dynamics_path = (CASE_DIR / DYNAMICS_PATH).resolve()
|
||||
input_dir = (CASE_DIR / INPUT_DIR).resolve()
|
||||
output_dir = (CASE_DIR / OUTPUT_DIR).resolve()
|
||||
config_path = (CASE_DIR / CONFIG_FILE).resolve()
|
||||
|
||||
module = load_dynamics_module(dynamics_path)
|
||||
module.run_case(
|
||||
config_path=config_path,
|
||||
runtime_base=CASE_DIR,
|
||||
input_dir=input_dir,
|
||||
output_dir=output_dir,
|
||||
no_plot=args.no_plot,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
h 100.0 1.0
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user