Compare commits

...

3 Commits

Author SHA1 Message Date
admin 974216332c docs: 更新 examples 文档,移动引擎源码结构
- 更新 examples/Readme.md 覆盖全部 10 个案例
- 新增 examples/Readme.html 案例总览页面
- 引擎源码移至 engines/src/ 目录
- 编译产物统一至 engines/release/(静态编译)
- compute.py/engine_dll.py 路径同步更新
2026-06-17 16:18:19 +08:00
admin a16d2239a1 refactor: 引擎源码移至 src/,编译产物统一至 release/
- 源码: engines/c/ → engines/src/c/
- 源码: engines/cpp/ → engines/src/cpp/
- 源码: engines/fortran/ → engines/src/fortran/
- 编译产物: engines/release/ (静态编译)
  - dynamics_c.exe   (516KB, C 引擎)
  - dynamics_cpp.exe (3.3MB, C++ 引擎)
  - dynamics_f90.exe (988KB, Fortran 引擎)
- compute.py: engine_map 指向 release/
- compute.py: param.json 和校准缓存移至 release/
- engine_dll.py: DLL 搜索路径指向 release/
- release/ 目录纳入 git 版本管理,用户克隆后直接可用
2026-06-17 15:48:30 +08:00
admin 0e636e275d docs: 更新 examples/Readme.md 并新增 Readme.html
- 覆盖全部 10 个案例(原 Readme 只到 case06)
- 新增案例选择指南表格
- Readme.html 为深色主题独立 HTML 页面
  (含卡片布局、标签分类、代码高亮、响应式设计)
- 各案例详情对齐最新配置参数
2026-06-17 15:33:49 +08:00
70 changed files with 12234 additions and 423 deletions
+199 -12
View File
@@ -947,6 +947,179 @@ def run_from_config(config, out_dir=None):
return traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz return traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz
def run_engine_dll(engine, output_dir, config):
"""通过 DLL(ctypes)调用计算引擎,不经过文件 I/O,直接返回轨迹数组。
Args:
engine: 引擎名称 "c", "cpp", 或 "fortran"
output_dir: 输出目录(用于保存 display.npz
config: YAML 配置字典
Returns:
None(结果直接写入 output_dir/display.npz
Raises:
FileNotFoundError: DLL 尚未编译
RuntimeError: DLL 运算出错
"""
import sys as _sys
import datetime as _datetime
_eng_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "engines")
if _eng_dir not in _sys.path:
_sys.path.insert(0, _eng_dir)
from engine_dll import load_dll, run_dynamics_dll, is_dll_available
if not is_dll_available(engine):
raise FileNotFoundError(
f"DLL 未找到(引擎 {engine})。"
f"请先编译:cd engines/{engine} && make dll")
lib = load_dll(engine)
# ── 构造原子数据 ──────────────────────────────────────────
if ATOM_POSITIONS is None:
raise RuntimeError("run_engine_dll: 请先调用 load_parameters() 加载配置")
pos = np.asarray(ATOM_POSITIONS, dtype=np.float64) # (n, 3)
vel = np.asarray(ATOM_VELOCITIES, dtype=np.float64)
mass = np.asarray(ATOM_MASSES, dtype=np.float64)
fixed= np.asarray(ATOM_FIXED, dtype=np.int32) # (n, 3)
# ── 键数据 ────────────────────────────────────────────────
n_bonds = len(BOND_PAIRS) if BOND_PAIRS is not None else 0
bp = np.asarray(BOND_PAIRS, dtype=np.int32) if n_bonds else np.zeros((0,2), dtype=np.int32)
bk = np.asarray(BOND_STIFFNESS, dtype=np.float64) if n_bonds else np.zeros(0)
br0 = np.asarray(BOND_REST_LENGTHS,dtype=np.float64) if n_bonds else np.zeros(0)
# ── 驱动数据 ──────────────────────────────────────────────
drv_list = []
if int(config.get("driving_force", 0)) and DRIVER_DATA:
atom_id_to_local = {int(aid): i for i, aid in enumerate(ATOM_IDS)}
for d in DRIVER_DATA:
aid = int(d.get("atom_id", -1))
if aid not in atom_id_to_local:
continue
local_idx = atom_id_to_local[aid]
# d["amp"], d["freq"], d["phi"] are numpy arrays; d["phi"] is already in radians
amp = [float(v) for v in d["amp"]]
freq = [float(v) for v in d["freq"]]
phi = [float(v) for v in d["phi"]] # radians
# eq_pos is set by run_from_config; fall back to initial position
eq_pos = d.get("eq_pos")
eq = ([float(v) for v in eq_pos] if eq_pos is not None
else [float(pos[local_idx, 0]), float(pos[local_idx, 1]), float(pos[local_idx, 2])])
pc = d.get("period_cycles") # None → unlimited, float → finite
nc = float(pc) if pc is not None else 0.0
hp = 1 if nc > 0 else 0
drv_list.append({"local_idx": local_idx, "amp": amp, "freq": freq,
"phi": phi, "eq_pos": eq, "n_cycles": nc, "has_period": hp})
# ── 进度回调 ──────────────────────────────────────────────
total_steps = int(config["NT"]) - int(config.get("warmup_steps", 0))
try:
from tqdm import tqdm as _tqdm
_pbar = _tqdm(total=total_steps, desc=f"[compute] DLL {engine}",
unit="", bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]')
def _cb(step, total):
_pbar.n = step
_pbar.refresh()
except ImportError:
_pbar = None
_cb = None
_t0 = time.time()
try:
result = run_dynamics_dll(
lib, config,
pos, vel, mass, fixed,
bp, bk, br0,
drv_list, np.asarray(ATOM_IDS),
progress_cb=_cb,
)
finally:
if _pbar is not None:
_pbar.n = total_steps
_pbar.close()
elapsed = time.time() - _t0
n_frames, n_atoms = result["x"].shape
print(f"[compute] DLL 完成: {n_frames}{n_atoms} 原子 {elapsed:.3f} s")
# ── 构建 header 并保存 display.npz ────────────────────────
# 与 run_simulation 写入的 header 保持字段完全一致,
# 确保 draw.py / plot_wave.py 读到所有必要参数。
G_vec = parse_gravity_vector(config.get("G", [0, 0, 0]))
B_vec = parse_damping_vector(config.get("B", [0, 0, 0]))
record_steps_hdr = int(config["NT"]) - int(config.get("warmup_steps", 0))
header = {
"DT": str(config["DT"]),
"NSTEP": str(config.get("NSTEP", 1)),
"method": str(config.get("method", "leapfrog")),
"NT": str(config["NT"]),
"warmup_steps": str(config.get("warmup_steps", 0)),
"dynamic_steps": str(record_steps_hdr),
"T_total": str(int(config["NT"]) * float(config["DT"])),
"box_a": str(config.get("box_a", 300.0)),
"gravity_field": str(config.get("gravity_field", 0)),
"gravity_interaction": str(config.get("gravity_interaction", 0)),
"elastic_force": str(config.get("elastic_force", 1)),
"damping_force": str(config.get("damping_force", 0)),
"driving_force": str(config.get("driving_force", 0)),
"gravity_strength": str(config.get("gravity_strength", 1.0)),
"G": json.dumps(G_vec.tolist()),
"B": json.dumps(B_vec.tolist()),
"number_of_frames": str(n_frames),
"number_of_particles": str(n_atoms),
# draw.py 需要的渲染参数
"use_marker": str(use_marker),
"ball_radius": str(config.get("ball_radius", float(ATOM_RADII[0]) if ATOM_RADII is not None else 0.5)),
"ball_color_r": str(config.get("ball_color_r", 0.9)),
"ball_color_g": str(config.get("ball_color_g", 0.2)),
"ball_color_b": str(config.get("ball_color_b", 0.2)),
"box_color_r": str(config.get("box_color_r", 0.8)),
"box_color_g": str(config.get("box_color_g", 0.8)),
"box_color_b": str(config.get("box_color_b", 0.85)),
"alpha": ",".join(str(a) for a in (alpha if isinstance(alpha, list) else [alpha])),
# draw.py / plot_wave.py 需要的原子、键数据
"atom_radii": ",".join(str(r) for r in ATOM_RADII),
"atom_masses": json.dumps([float(v) for v in ATOM_MASSES]),
"atom_positions": json.dumps(ATOM_POSITIONS.tolist()),
"bond_pairs": json.dumps(BOND_PAIRS.tolist() if BOND_PAIRS is not None else []),
"bond_stiffness": json.dumps(BOND_STIFFNESS.tolist() if BOND_STIFFNESS is not None else []),
"bond_rest_lengths": json.dumps(BOND_REST_LENGTHS.tolist() if BOND_REST_LENGTHS is not None else []),
# 边界(draw.py 用于场景缩放)
"X_MIN": str(-float(config.get("box_a", 300.0))),
"X_MAX": str( float(config.get("box_a", 300.0))),
"Y_MIN": str(-float(config.get("box_a", 300.0))),
"Y_MAX": str( float(config.get("box_a", 300.0))),
"Z_MIN": str(-float(config.get("box_a", 300.0))),
"Z_MAX": str( float(config.get("box_a", 300.0))),
# 相机参数
"camera_distance": str(camera_distance),
"camera_elevation": str(camera_elevation),
"camera_azimuth": str(camera_azimuth),
"camera_center_x": str(camera_center_x),
"camera_center_y": str(camera_center_y),
"camera_center_z": str(camera_center_z),
"camera_keyframes": str(camera_keyframes_raw),
}
if display_amp_str:
header["display_amp"] = display_amp_str
if camera_pos_x is not None:
header["camera_pos_x"] = str(camera_pos_x)
header["camera_pos_y"] = str(camera_pos_y)
header["camera_pos_z"] = str(camera_pos_z)
os.makedirs(output_dir, exist_ok=True)
npz_path = os.path.join(output_dir, "display.npz")
save_display_npz(
npz_path,
result["x"], result["y"], result["z"],
result["vx"], result["vy"], result["vz"],
np.asarray(ATOM_IDS),
header_fields=header,
)
print(f"[compute] display.npz 已生成: {npz_path}")
def run_engine(engine, input_dir, output_dir, config): def run_engine(engine, input_dir, output_dir, config):
"""调用外部计算引擎(C/C++/Fortran),生成 trajectory.txt。 """调用外部计算引擎(C/C++/Fortran),生成 trajectory.txt。
@@ -961,21 +1134,32 @@ def run_engine(engine, input_dir, output_dir, config):
script_dir = os.path.dirname(os.path.abspath(__file__)) script_dir = os.path.dirname(os.path.abspath(__file__))
system = platform.system().lower() system = platform.system().lower()
engine_map = { engine_map = {
"c": "engines/c/build/dynamics_c", "c": "engines/release/dynamics_c",
"cpp": "engines/cpp/build/dynamics_cpp", "cpp": "engines/release/dynamics_cpp",
"fortran": "engines/fortran/build/dynamics_f90", "c++": "engines/release/dynamics_cpp",
"fortran": "engines/release/dynamics_f90",
"f90": "engines/release/dynamics_f90",
"python": None, # 特殊处理:用 sys.executable 调用 main.py
} }
if engine not in engine_map: if engine not in engine_map:
raise ValueError(f"不支持的引擎: {engine},可选: {list(engine_map.keys())}") raise ValueError(f"不支持的引擎: {engine},可选: c, cpp, fortran, python")
if engine == "python":
# Python 引擎:用当前解释器运行 engines/python/main.py
py_main = os.path.join(script_dir, "engines", "python", "main.py")
if not os.path.exists(py_main):
raise FileNotFoundError(f"Python 引擎脚本不存在: {py_main}")
found = py_main
engine_path = sys.executable
else:
engine_rel = engine_map[engine] engine_rel = engine_map[engine]
engine_path = os.path.join(script_dir, engine_rel) engine_path = os.path.join(script_dir, engine_rel)
# 自动检测可执行文件后缀和平台专用版本 # 自动检测可执行文件后缀和平台专用版本
candidates = [ candidates = [
engine_path, # 无后缀 engine_path,
engine_path + ".exe", # Windows .exe engine_path + ".exe",
engine_path + f"_{system}.exe", # 平台专用 (c_linux.exe, c_darwin.exe) engine_path + f"_{system}.exe",
] ]
found = None found = None
for p in candidates: for p in candidates:
@@ -1024,7 +1208,7 @@ def run_engine(engine, input_dir, output_dir, config):
"camera_center_y": float(config.get("camera_center_y", 0.0)), "camera_center_y": float(config.get("camera_center_y", 0.0)),
"camera_center_z": float(config.get("camera_center_z", 0.0)), "camera_center_z": float(config.get("camera_center_z", 0.0)),
} }
param_path = os.path.join(script_dir, "engines", engine, "param.json") param_path = os.path.join(script_dir, "engines", "release", f"{engine}.json")
os.makedirs(os.path.dirname(param_path), exist_ok=True) os.makedirs(os.path.dirname(param_path), exist_ok=True)
with open(param_path, "w", encoding="utf-8") as f: with open(param_path, "w", encoding="utf-8") as f:
json.dump(param_json, f, indent=2) json.dump(param_json, f, indent=2)
@@ -1042,7 +1226,7 @@ def run_engine(engine, input_dir, output_dir, config):
n_atoms_calib = len(ATOM_IDS) if ATOM_IDS is not None else 0 n_atoms_calib = len(ATOM_IDS) if ATOM_IDS is not None else 0
# 尝试读取缓存;当 n_atoms 相同且 NT 在 50% 范围内时视为有效 # 尝试读取缓存;当 n_atoms 相同且 NT 在 50% 范围内时视为有效
_cache_path = os.path.join(script_dir, "engines", engine, "_calib_cache.json") _cache_path = os.path.join(script_dir, "engines", "release", f"_calib_{engine}.json")
_step_time = None _step_time = None
try: try:
with open(_cache_path, encoding="utf-8") as _cf: with open(_cache_path, encoding="utf-8") as _cf:
@@ -1057,10 +1241,10 @@ def run_engine(engine, input_dir, output_dir, config):
if _step_time is None: if _step_time is None:
_calib_param = dict(param_json) _calib_param = dict(param_json)
_calib_param["NT"] = _calib_nt _calib_param["NT"] = _calib_nt
_calib_path = os.path.join(script_dir, "engines", engine, "_calib.json") _calib_path = os.path.join(script_dir, "engines", "release", f"_calib_{engine}.json")
with open(_calib_path, "w", encoding="utf-8") as _cf: with open(_calib_path, "w", encoding="utf-8") as _cf:
json.dump(_calib_param, _cf, indent=2) json.dump(_calib_param, _cf, indent=2)
_calib_outdir = os.path.join(script_dir, "engines", engine, "_calib_out") _calib_outdir = os.path.join(script_dir, "engines", "release", f"_calib_{engine}_out")
os.makedirs(_calib_outdir, exist_ok=True) os.makedirs(_calib_outdir, exist_ok=True)
_ct0 = time.time() _ct0 = time.time()
subprocess.run( subprocess.run(
@@ -1089,8 +1273,11 @@ def run_engine(engine, input_dir, output_dir, config):
t_start = time.time() t_start = time.time()
t_start_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") t_start_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Python 引擎:[python, main.py, args];其他引擎:[exe, args]
_cmd = ([engine_path, found] if engine == "python" else [engine_path]) + \
[os.path.abspath(input_dir), os.path.abspath(output_dir), param_path]
_p = subprocess.Popen( _p = subprocess.Popen(
[engine_path, os.path.abspath(input_dir), os.path.abspath(output_dir), param_path], _cmd,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True, encoding='utf-8', errors='replace') text=True, encoding='utf-8', errors='replace')
_engine_lines = [] _engine_lines = []
+25 -6
View File
@@ -226,7 +226,11 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
# 2. 运行物理模拟 → output/trajectory.txt # 2. 运行物理模拟 → output/trajectory.txt
if config.get("step_simulate", 1): if config.get("step_simulate", 1):
engine = config.get("engine", "python") _engine_aliases = {"c++": "cpp", "f90": "fortran", "f": "fortran"}
engine = _engine_aliases.get(
str(config.get("engine", "python")).lower(),
str(config.get("engine", "python")).lower()
)
total_steps = config["NT"] total_steps = config["NT"]
record_steps = total_steps - (config.get("warmup_steps") or 0) record_steps = total_steps - (config.get("warmup_steps") or 0)
print(f"[run] 开始计算 总步数={total_steps} 记录步数={record_steps} DT={config['DT']}") print(f"[run] 开始计算 总步数={total_steps} 记录步数={record_steps} DT={config['DT']}")
@@ -243,7 +247,20 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
config.pop("_skip_run", None) config.pop("_skip_run", None)
input_dir_abs = str(input_dir_path.resolve()) input_dir_abs = str(input_dir_path.resolve())
output_dir_abs = str(output_dir_path.resolve()) output_dir_abs = str(output_dir_path.resolve())
# 外部引擎写完整 trajectory.txt,后续抽帧
# ── 优先尝试 DLL 路径(无文件 I/O,直接输出 display.npz)──
_dll_used = False
try:
from engines.engine_dll import is_dll_available
if is_dll_available(engine):
compute.run_engine_dll(engine, output_dir_abs, config)
_dll_used = True
print(f"[run] DLL 路径成功")
except Exception as _dll_err:
print(f"[run] DLL 路径不可用 ({_dll_err}),回退到子进程模式")
if not _dll_used:
# 回退:子进程模式(读写 display.txt / trajectory.txt
traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz = compute.run_engine( traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz = compute.run_engine(
engine, input_dir_abs, output_dir_abs, config) engine, input_dir_abs, output_dir_abs, config)
if int(config.get("save_trajectory", 0)): if int(config.get("save_trajectory", 0)):
@@ -345,11 +362,13 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
if not os.path.exists(draw_script): if not os.path.exists(draw_script):
print(f"[run] 未找到动画脚本: {draw_script}") print(f"[run] 未找到动画脚本: {draw_script}")
else: else:
# 检查 display.txt 是否存在step_sample=0 时可能没有) # 检查 display.npz 或 display.txt 是否存在
disp_path = os.path.join(output_dir_abs, "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): if not os.path.exists(disp_path):
print(f"[run] 错误: 找不到 {disp_path}") print(f"[run] 错误: 找不到 display.npz 或 display.txt")
print(f"[run] 启动动画需要先运行抽帧step_sample: 1),或手动保留 output/display.txt") print(f"[run] 启动动画需要先运行模拟step_simulate: 1")
else: else:
try: try:
print("[run] 正在启动 VisPy 3D 动画窗口…") print("[run] 正在启动 VisPy 3D 动画窗口…")
View File
+20 -1
View File
@@ -8,6 +8,7 @@ CC = gcc
CFLAGS = -O3 -march=native -Wall -Wextra CFLAGS = -O3 -march=native -Wall -Wextra
LDFLAGS = -lm LDFLAGS = -lm
SRCS = main.c SRCS = main.c
LIB_SRC = dynamics_lib.c
# 自动检测系统 # 自动检测系统
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows) UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
@@ -15,15 +16,33 @@ UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
# 目标文件名:统一使用 .exe 后缀(方便 Python 跨平台调用) # 目标文件名:统一使用 .exe 后缀(方便 Python 跨平台调用)
TARGET = build/dynamics_c.exe TARGET = build/dynamics_c.exe
# DLL 目标(平台自动选择后缀)
ifeq ($(UNAME_S),Linux)
DLL_TARGET = build/dynamics_c.so
DLL_FLAGS = -shared -fPIC
else ifeq ($(UNAME_S),Darwin)
DLL_TARGET = build/dynamics_c.dylib
DLL_FLAGS = -dynamiclib
else
DLL_TARGET = build/dynamics_c.dll
DLL_FLAGS = -shared
endif
# ── 本地编译 ───────────────────────────────── # ── 本地编译 ─────────────────────────────────
.PHONY: all clean linux windows macos .PHONY: all dll clean linux windows macos
all: $(TARGET) all: $(TARGET)
dll: $(DLL_TARGET)
$(TARGET): $(SRCS) | build $(TARGET): $(SRCS) | build
$(CC) $(CFLAGS) -o $@ $(SRCS) $(LDFLAGS) $(CC) $(CFLAGS) -o $@ $(SRCS) $(LDFLAGS)
@echo " === C engine built: $@ ===" @echo " === C engine built: $@ ==="
$(DLL_TARGET): $(LIB_SRC) | build
$(CC) $(CFLAGS) $(DLL_FLAGS) -o $@ $(LIB_SRC) $(LDFLAGS)
@echo " === C DLL built: $@ ==="
build: build:
mkdir -p build mkdir -p build
+1
View File
@@ -0,0 +1 @@
{"n_atoms": 40, "nt": 200000, "step_time": 2.5352442264556887e-05}
+554
View File
@@ -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;
}
+49
View File
@@ -0,0 +1,49 @@
# engines/cpp/Makefile
CXX = g++
SRCS = main.cpp
LIB_SRC = dynamics_lib.cpp
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
CXXFLAGS = -O3 -march=native -std=c++17 -Wall -Wextra -D_USE_MATH_DEFINES
# Windows 下静态链接运行时,避免 libstdc++-6.dll / libgcc_s_seh-1.dll 版本冲突
ifeq ($(UNAME_S),Windows)
STATIC_FLAGS = -static-libgcc -static-libstdc++
else
STATIC_FLAGS =
endif
TARGET = build/dynamics_cpp.exe
ifeq ($(UNAME_S),Linux)
DLL_TARGET = build/dynamics_cpp.so
DLL_FLAGS = -shared -fPIC
else ifeq ($(UNAME_S),Darwin)
DLL_TARGET = build/dynamics_cpp.dylib
DLL_FLAGS = -dynamiclib
else
DLL_TARGET = build/dynamics_cpp.dll
DLL_FLAGS = -shared
endif
.PHONY: all dll clean
all: $(TARGET)
dll: $(DLL_TARGET)
$(TARGET): $(SRCS) | build
$(CXX) $(CXXFLAGS) $(STATIC_FLAGS) -o $@ $(SRCS)
@echo " === C++ engine built: $@ ==="
$(DLL_TARGET): $(LIB_SRC) | build
$(CXX) $(CXXFLAGS) $(STATIC_FLAGS) $(DLL_FLAGS) -o $@ $(LIB_SRC)
@echo " === C++ DLL built: $@ ==="
build:
mkdir -p build
clean:
rm -rf build *.o
+1
View File
@@ -0,0 +1 @@
{"n_atoms": 40, "nt": 200000, "step_time": 0.0022148028612136842}
+450
View File
@@ -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;
}
+424
View File
@@ -0,0 +1,424 @@
"""
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/c/build/dynamics_c.dll engines/c/dynamics_lib.c -lm
Linux: gcc -O3 -shared -fPIC -o engines/c/build/dynamics_c.so engines/c/dynamics_lib.c -lm
macOS: gcc -O3 -dynamiclib -o engines/c/build/dynamics_c.dylib engines/c/dynamics_lib.c -lm
"""
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))
+47
View File
@@ -0,0 +1,47 @@
# engines/fortran/Makefile
FC = gfortran
FFLAGS = -O3 -march=native -Wall -Wextra
SRCS = main.f90
LIB_SRC = dynamics_lib.f90
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
ifeq ($(UNAME_S),Windows)
STATIC_FLAGS = -static-libgcc -static-libgfortran -static-libquadmath
else
STATIC_FLAGS =
endif
TARGET = build/dynamics_f90.exe
ifeq ($(UNAME_S),Linux)
DLL_TARGET = build/dynamics_f90.so
DLL_FLAGS = -shared -fPIC
else ifeq ($(UNAME_S),Darwin)
DLL_TARGET = build/dynamics_f90.dylib
DLL_FLAGS = -dynamiclib
else
DLL_TARGET = build/dynamics_f90.dll
DLL_FLAGS = -shared -fPIC
endif
.PHONY: all dll clean
all: $(TARGET)
dll: $(DLL_TARGET)
$(TARGET): $(SRCS) | build
$(FC) $(FFLAGS) $(STATIC_FLAGS) -o $@ $(SRCS)
@echo " === Fortran engine built: $@ ==="
$(DLL_TARGET): $(LIB_SRC) | build
$(FC) $(FFLAGS) $(STATIC_FLAGS) $(DLL_FLAGS) -o $@ $(LIB_SRC)
@echo " === Fortran DLL built: $@ ==="
build:
mkdir -p build
clean:
rm -rf build *.o *.mod
+1
View File
@@ -0,0 +1 @@
{"n_atoms": 40, "nt": 200000, "step_time": 0.005991018545627594}
+483
View File
@@ -0,0 +1,483 @@
! engines/fortran/dynamics_lib.f90
! ---------------------------------
! 纯计算 DLLFortran 版):无文件 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_dynamicsC 兼容接口,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
+79 -171
View File
@@ -49,7 +49,7 @@ program dynamics_f90
double precision, allocatable :: vx(:), vy(:), vz(:) double precision, allocatable :: vx(:), vy(:), vz(:)
! 轨迹缓冲区 ! 轨迹缓冲区
integer :: record_steps integer :: record_steps, n_frames, frame_idx
double precision, allocatable :: traj_x(:, :), traj_y(:, :), traj_z(:, :) double precision, allocatable :: traj_x(:, :), traj_y(:, :), traj_z(:, :)
double precision, allocatable :: traj_vx(:, :), traj_vy(:, :), traj_vz(:, :) double precision, allocatable :: traj_vx(:, :), traj_vy(:, :), traj_vz(:, :)
@@ -104,10 +104,11 @@ program dynamics_f90
vx(i) = vel_0(i, 1); vy(i) = vel_0(i, 2); vz(i) = vel_0(i, 3) vx(i) = vel_0(i, 1); vy(i) = vel_0(i, 2); vz(i) = vel_0(i, 3)
end do end do
! 分配轨迹缓冲区 ! 分配轨迹缓冲区(只保存采样帧,不保存每一步)
record_steps = NT - warmup_steps record_steps = NT - warmup_steps
allocate(traj_x(record_steps, n), traj_y(record_steps, n), traj_z(record_steps, n)) n_frames = max(1, record_steps / max(1, NSTEP))
allocate(traj_vx(record_steps, n), traj_vy(record_steps, n), traj_vz(record_steps, n)) allocate(traj_x(n_frames, n), traj_y(n_frames, n), traj_z(n_frames, n))
allocate(traj_vx(n_frames, n), traj_vy(n_frames, n), traj_vz(n_frames, n))
! 真蛙跳初始化:v(0) 反推 v(-dt/2) = v(0) - 0.5*a_c(0)*dt ! 真蛙跳初始化:v(0) 反推 v(-dt/2) = v(0) - 0.5*a_c(0)*dt
if (trim(method) == 'leapfrog') then if (trim(method) == 'leapfrog') then
@@ -160,12 +161,19 @@ program dynamics_f90
pos_0) pos_0)
end do end do
! 记录 ! 记录(每 NSTEP 步采一帧)
prog_step = record_steps / 100 prog_step = max(1, record_steps / 100)
if (prog_step < 1) prog_step = 1 frame_idx = 0
do s = 1, record_steps do s = 1, record_steps
if (mod(s, prog_step) == 0 .and. s > 0) then if (mod(s, prog_step) == 0) then
write(*, '("[Fortran-engine] progress: ", i0, "/", i0)') s, record_steps write(*, '("[Fortran-engine] progress: ", i0, "/", i0)') s, record_steps
flush(6)
end if
! 采帧:在每个 NSTEP 区间的起始时刻记录
if (mod(s-1, max(1, NSTEP)) == 0 .and. frame_idx < n_frames) then
frame_idx = frame_idx + 1
traj_x(frame_idx, :) = x; traj_y(frame_idx, :) = y; traj_z(frame_idx, :) = z
traj_vx(frame_idx, :) = vx; traj_vy(frame_idx, :) = vy; traj_vz(frame_idx, :) = vz
end if end if
if (driving_force /= 0 .and. n_drivers > 0) then if (driving_force /= 0 .and. n_drivers > 0) then
tw = ((s-1 + warmup_steps) * 1.0d0) * DT tw = ((s-1 + warmup_steps) * 1.0d0) * DT
@@ -178,8 +186,6 @@ program dynamics_f90
drv_eq_x, drv_eq_y, drv_eq_z, & drv_eq_x, drv_eq_y, drv_eq_z, &
drv_freeze_x, drv_freeze_y, drv_freeze_z) drv_freeze_x, drv_freeze_y, drv_freeze_z)
end if end if
traj_x(s, :) = x; traj_y(s, :) = y; traj_z(s, :) = z
traj_vx(s, :) = vx; traj_vy(s, :) = vy; traj_vz(s, :) = vz
call apply_step(method, n, x, y, z, vx, vy, vz, masses, G, B, & call apply_step(method, n, x, y, z, vx, vy, vz, masses, G, B, &
n_bonds, bond_pairs, bond_stiffness, bond_rest_lengths, & n_bonds, bond_pairs, bond_stiffness, bond_rest_lengths, &
fixed, box_a, DT, & fixed, box_a, DT, &
@@ -188,13 +194,14 @@ program dynamics_f90
pos_0) pos_0)
end do end do
! 输出轨迹 ! 输出 display.txt
write(*, '("[Fortran-engine] 正在写入轨迹数据…")') write(*, '("[Fortran-engine] 正在写入 display.txt (", i0, " 帧)…")') n_frames
call write_json(output_dir, traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz, & flush(6)
record_steps, n_atoms, atom_ids, masses, & call write_display_txt(output_dir, n_frames, n_atoms, atom_ids, &
traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz, &
NT, DT, NSTEP, warmup_steps, method, G, B, & NT, DT, NSTEP, warmup_steps, method, G, B, &
n_bonds, bond_pairs, bond_stiffness, bond_rest_lengths, & n_bonds, gravity_field, elastic_force, damping_force, &
driving_force) driving_force, box_a, gravity_strength)
call cpu_time(t1) call cpu_time(t1)
elapsed = t1 - t0 elapsed = t1 - t0
@@ -985,179 +992,80 @@ subroutine apply_driving(n, x, y, z, vx, vy, vz, t, step, dt, &
end subroutine apply_driving end subroutine apply_driving
! ======================================================================== ! ========================================================================
! JSON 输出 ! display.txt 输出(与 compute.py save_display_txt 格式一致)
! ======================================================================== ! ========================================================================
subroutine write_json(outdir, tx, ty, tz, tvx, tvy, tvz, & subroutine write_display_txt(outdir, n_frames, nat, aid, &
nsteps, nat, aid, amass, & tx, ty, tz, tvx, tvy, tvz, &
NT, DT, NSTEP, warmup, method, G, B, & NT, DT, NSTEP, warmup, method, G, B, &
nb, bp, bk, br, driving_force) nb, gravity_field, elastic_force, damping_force, &
driving_force, box_a, gravity_strength)
character(len=*), intent(in) :: outdir, method character(len=*), intent(in) :: outdir, method
integer, intent(in) :: nsteps, nat, NT, NSTEP, warmup, nb, bp(nb, 2), aid(nat), driving_force integer, intent(in) :: n_frames, nat, NT, NSTEP, warmup, nb
double precision, intent(in) :: tx(nsteps, nat), ty(nsteps, nat), tz(nsteps, nat) integer, intent(in) :: gravity_field, elastic_force, damping_force, driving_force
double precision, intent(in) :: tvx(nsteps, nat), tvy(nsteps, nat), tvz(nsteps, nat) integer, intent(in) :: aid(nat)
double precision, intent(in) :: DT, G(3), B(3), bk(nb), br(nb), amass(nat) double precision, intent(in) :: tx(n_frames, nat), ty(n_frames, nat), tz(n_frames, nat)
double precision, intent(in) :: tvx(n_frames, nat), tvy(n_frames, nat), tvz(n_frames, nat)
double precision, intent(in) :: DT, G(3), B(3), box_a, gravity_strength
character(len=512) :: path, buf character(len=512) :: path, buf
integer :: u, s, i, ib, ios integer :: u, f, a, ios
integer :: dynamic_steps
double precision :: T_total
path = trim(outdir) // '/trajectory.txt' dynamic_steps = NT - warmup
T_total = NT * DT
path = trim(outdir) // '/display.txt'
open(newunit=u, file=trim(path), status='replace', action='write', iostat=ios) open(newunit=u, file=trim(path), status='replace', action='write', iostat=ios)
if (ios /= 0) then if (ios /= 0) then
write(*, '("[Fortran-engine] 错误: 无法写入 ", a)') trim(path) write(*, '("[Fortran-engine] 错误: 无法写入 ", a)') trim(path)
stop return
end if end if
write(u, '(a)') '{' ! ── header ────────────────────────────────────────────────────────────
write(u, '("number of frames: ", i0)') n_frames
! traj_x write(u, '("number of particles: ", i0)') nat
write(u, '(a)') ' "traj_x": [' write(u, '("DT: ", g0)') DT
do s = 1, nsteps write(u, '("NSTEP: ", i0)') NSTEP
call json_arr(u, tx(s, :), nat, s < nsteps, ' ') write(u, '("method: ", a)') trim(method)
end do write(u, '("NT: ", i0)') NT
write(u, '(a)') ' ],' write(u, '("warmup_steps: ", i0)') warmup
write(u, '("dynamic_steps: ", i0)') dynamic_steps
! traj_y write(u, '("T_total: ", g0)') T_total
write(u, '(a)') ' "traj_y": [' write(u, '("box_a: ", g0)') box_a
do s = 1, nsteps write(u, '("gravity_field: ", i0)') gravity_field
call json_arr(u, ty(s, :), nat, s < nsteps, ' ') write(u, '("elastic_force: ", i0)') elastic_force
end do write(u, '("damping_force: ", i0)') damping_force
write(u, '(a)') ' ],' write(u, '("driving_force: ", i0)') driving_force
write(u, '("gravity_strength: ", g0)') gravity_strength
! traj_z write(buf, '("G: [", g0, ", ", g0, ", ", g0, "]")') G(1), G(2), G(3)
write(u, '(a)') ' "traj_z": ['
do s = 1, nsteps
call json_arr(u, tz(s, :), nat, s < nsteps, ' ')
end do
write(u, '(a)') ' ],'
! traj_vx
write(u, '(a)') ' "traj_vx": ['
do s = 1, nsteps
call json_arr(u, tvx(s, :), nat, s < nsteps, ' ')
end do
write(u, '(a)') ' ],'
! traj_vy
write(u, '(a)') ' "traj_vy": ['
do s = 1, nsteps
call json_arr(u, tvy(s, :), nat, s < nsteps, ' ')
end do
write(u, '(a)') ' ],'
! traj_vz
write(u, '(a)') ' "traj_vz": ['
do s = 1, nsteps
call json_arr(u, tvz(s, :), nat, s < nsteps, ' ')
end do
write(u, '(a)') ' ],'
! 标量参数
write(buf, '(a, i0, a)') ' "NT": ', NT, ','
write(u, '(a)') trim(buf) write(u, '(a)') trim(buf)
write(buf, '(a, g0, a)') ' "DT": ', DT, ',' write(buf, '("B: [", g0, ", ", g0, ", ", g0, "]")') B(1), B(2), B(3)
write(u, '(a)') trim(buf)
write(buf, '(a, i0, a)') ' "NSTEP": ', NSTEP, ','
write(u, '(a)') trim(buf)
write(buf, '(a, a, a)') ' "method": "', trim(method), '",'
write(u, '(a)') trim(buf)
write(buf, '(a, i0, a)') ' "warmup_steps": ', warmup, ','
write(u, '(a)') trim(buf) write(u, '(a)') trim(buf)
write(u, '("number_of_frames: ", i0)') n_frames
write(u, '("number_of_particles: ", i0)') nat
write(buf, '("X_MIN: ", g0)') -box_a; write(u, '(a)') trim(buf)
write(buf, '("X_MAX: ", g0)') box_a; write(u, '(a)') trim(buf)
write(buf, '("Y_MIN: ", g0)') -box_a; write(u, '(a)') trim(buf)
write(buf, '("Y_MAX: ", g0)') box_a; write(u, '(a)') trim(buf)
write(buf, '("Z_MIN: ", g0)') -box_a; write(u, '(a)') trim(buf)
write(buf, '("Z_MAX: ", g0)') box_a; write(u, '(a)') trim(buf)
write(buf, '(a, g0, a, g0, a, g0, a)') & ! ── frame data ────────────────────────────────────────────────────────
' "G": [', G(1), ', ', G(2), ', ', G(3), '],' do f = 1, n_frames
write(u, '(a)') trim(buf) write(u, '()') ! 空行
write(buf, '(a, g0, a, g0, a, g0, a)') & write(u, '("frame: ", i0)') f
' "B": [', B(1), ', ', B(2), ', ', B(3), '],' write(u, '("n x y z vx vy vz")')
write(u, '(a)') trim(buf) do a = 1, nat
write(u, '(i0, 6(f13.6))') aid(a), &
! 原子信息 tx(f,a), ty(f,a), tz(f,a), tvx(f,a), tvy(f,a), tvz(f,a)
write(u, '(a)', advance='no') ' "atom_ids": ['
do i = 1, nat
if (i > 1) write(u, '(a)', advance='no') ','
write(u, '(i0)', advance='no') aid(i)
end do end do
write(u, '(a)') '],'
write(u, '(a)', advance='no') ' "atom_masses": ['
do i = 1, nat
if (i > 1) write(u, '(a)', advance='no') ','
write(u, '(g0)', advance='no') amass(i)
end do end do
write(u, '(a)') '],'
! 成键
if (nb > 0) then
call write_int2_arr(u, 'bond_pairs', bp, nb, .true.)
call write_dbl_arr(u, 'bond_stiffness', bk, nb, .true.)
call write_dbl_arr(u, 'bond_rest_lengths', br, nb, .true.)
else
write(u, '(a)') ' "bond_pairs": [],'
write(u, '(a)') ' "bond_stiffness": [],'
write(u, '(a)') ' "bond_rest_lengths": [],'
end if
write(buf, '(a, i0)') ' "driving_force": ', driving_force
write(u, '(a)') trim(buf)
write(u, '(a)') '}'
close(u) close(u)
end subroutine write_json write(*, '("[Fortran-engine] display.txt 已保存: ", a)') trim(path)
flush(6)
! 写出单行 JSON 数组 [v1, v2, ...] end subroutine write_display_txt
subroutine json_arr(u, vals, n, has_next, indent)
integer, intent(in) :: u, n
double precision, intent(in) :: vals(n)
logical, intent(in) :: has_next
character(len=*), intent(in) :: indent
integer :: i
write(u, '(a)', advance='no') indent // '['
do i = 1, n
if (i > 1) write(u, '(a)', advance='no') ','
write(u, '(g0.8)', advance='no') vals(i)
end do
if (has_next) then
write(u, '(a)') '],'
else
write(u, '(a)') ']'
end if
end subroutine json_arr
subroutine write_int2_arr(u, name, arr, n, has_next)
integer, intent(in) :: u, n, arr(n, 2)
character(len=*), intent(in) :: name
logical, intent(in) :: has_next
character(len=65536) :: buf
integer :: i, pos
write(u, '(a)', advance='no') ' "' // trim(name) // '": ['
do i = 1, n
if (i > 1) write(u, '(a)', advance='no') ','
write(buf, '(a, i0, a, i0, a)') '[', arr(i, 1), ',', arr(i, 2), ']'
write(u, '(a)', advance='no') trim(buf)
end do
if (has_next) then
write(u, '(a)') '],'
else
write(u, '(a)') ']'
end if
end subroutine write_int2_arr
subroutine write_dbl_arr(u, name, arr, n, has_next)
integer, intent(in) :: u, n
double precision, intent(in) :: arr(n)
character(len=*), intent(in) :: name
logical, intent(in) :: has_next
integer :: i
write(u, '(a)', advance='no') ' "' // trim(name) // '": ['
do i = 1, n
if (i > 1) write(u, '(a)', advance='no') ','
write(u, '(g0.8)', advance='no') arr(i)
end do
if (has_next) then
write(u, '(a)') '],'
else
write(u, '(a)') ']'
end if
end subroutine write_dbl_arr
end program dynamics_f90 end program dynamics_f90
View File
+404
View File
@@ -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
+283
View File
@@ -0,0 +1,283 @@
"""
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),
"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()
+1
View File
@@ -0,0 +1 @@
{"n_atoms": 40, "nt": 10000, "step_time": 0.00015027170181274412}
+51
View File
@@ -0,0 +1,51 @@
{
"box_a": 300.0,
"NT": 10000,
"DT": 0.001,
"NSTEP": 20,
"warmup_steps": 0,
"method": "leapfrog",
"G": [
0.0,
0.0,
0.0
],
"B": [
0.005,
0.0,
0.005
],
"gravity_field": 0,
"gravity_interaction": 0,
"elastic_force": 1,
"damping_force": 0,
"gravity_strength": 1.0,
"driving_force": 1,
"save_trajectory": 0,
"alpha": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"ball_radius": 0.5,
"ball_color": [
0.2,
0.6,
0.9
],
"box_color": [
0.8,
0.8,
0.85
],
"use_marker": 1,
"camera_distance": 120.0,
"camera_elevation": 0.0,
"camera_azimuth": 0.0,
"camera_center_x": 60.0,
"camera_center_y": 0.0,
"camera_center_z": 0.0
}
+82
View File
@@ -0,0 +1,82 @@
# 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
LIB_SRC = dynamics_lib.c
# 自动检测系统
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
# 目标文件名:统一使用 .exe 后缀(方便 Python 跨平台调用)
TARGET = build/dynamics_c.exe
# DLL 目标(平台自动选择后缀)
ifeq ($(UNAME_S),Linux)
DLL_TARGET = build/dynamics_c.so
DLL_FLAGS = -shared -fPIC
else ifeq ($(UNAME_S),Darwin)
DLL_TARGET = build/dynamics_c.dylib
DLL_FLAGS = -dynamiclib
else
DLL_TARGET = build/dynamics_c.dll
DLL_FLAGS = -shared
endif
# ── 本地编译 ─────────────────────────────────
.PHONY: all dll clean linux windows macos
all: $(TARGET)
dll: $(DLL_TARGET)
$(TARGET): $(SRCS) | build
$(CC) $(CFLAGS) -o $@ $(SRCS) $(LDFLAGS)
@echo " === C engine built: $@ ==="
$(DLL_TARGET): $(LIB_SRC) | build
$(CC) $(CFLAGS) $(DLL_FLAGS) -o $@ $(LIB_SRC) $(LDFLAGS)
@echo " === C DLL 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
+554
View File
@@ -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;
}
+1114
View File
File diff suppressed because it is too large Load Diff
+49
View File
@@ -0,0 +1,49 @@
# engines/cpp/Makefile
CXX = g++
SRCS = main.cpp
LIB_SRC = dynamics_lib.cpp
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
CXXFLAGS = -O3 -march=native -std=c++17 -Wall -Wextra -D_USE_MATH_DEFINES
# Windows 下静态链接运行时,避免 libstdc++-6.dll / libgcc_s_seh-1.dll 版本冲突
ifeq ($(UNAME_S),Windows)
STATIC_FLAGS = -static-libgcc -static-libstdc++
else
STATIC_FLAGS =
endif
TARGET = build/dynamics_cpp.exe
ifeq ($(UNAME_S),Linux)
DLL_TARGET = build/dynamics_cpp.so
DLL_FLAGS = -shared -fPIC
else ifeq ($(UNAME_S),Darwin)
DLL_TARGET = build/dynamics_cpp.dylib
DLL_FLAGS = -dynamiclib
else
DLL_TARGET = build/dynamics_cpp.dll
DLL_FLAGS = -shared
endif
.PHONY: all dll clean
all: $(TARGET)
dll: $(DLL_TARGET)
$(TARGET): $(SRCS) | build
$(CXX) $(CXXFLAGS) $(STATIC_FLAGS) -o $@ $(SRCS)
@echo " === C++ engine built: $@ ==="
$(DLL_TARGET): $(LIB_SRC) | build
$(CXX) $(CXXFLAGS) $(STATIC_FLAGS) $(DLL_FLAGS) -o $@ $(LIB_SRC)
@echo " === C++ DLL built: $@ ==="
build:
mkdir -p build
clean:
rm -rf build *.o
+450
View File
@@ -0,0 +1,450 @@
/**
* engines/cpp/dynamics_lib.cpp
* -----------------------------
* DLLC++ 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;
}
File diff suppressed because it is too large Load Diff
+47
View File
@@ -0,0 +1,47 @@
# engines/fortran/Makefile
FC = gfortran
FFLAGS = -O3 -march=native -Wall -Wextra
SRCS = main.f90
LIB_SRC = dynamics_lib.f90
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
ifeq ($(UNAME_S),Windows)
STATIC_FLAGS = -static-libgcc -static-libgfortran -static-libquadmath
else
STATIC_FLAGS =
endif
TARGET = build/dynamics_f90.exe
ifeq ($(UNAME_S),Linux)
DLL_TARGET = build/dynamics_f90.so
DLL_FLAGS = -shared -fPIC
else ifeq ($(UNAME_S),Darwin)
DLL_TARGET = build/dynamics_f90.dylib
DLL_FLAGS = -dynamiclib
else
DLL_TARGET = build/dynamics_f90.dll
DLL_FLAGS = -shared -fPIC
endif
.PHONY: all dll clean
all: $(TARGET)
dll: $(DLL_TARGET)
$(TARGET): $(SRCS) | build
$(FC) $(FFLAGS) $(STATIC_FLAGS) -o $@ $(SRCS)
@echo " === Fortran engine built: $@ ==="
$(DLL_TARGET): $(LIB_SRC) | build
$(FC) $(FFLAGS) $(STATIC_FLAGS) $(DLL_FLAGS) -o $@ $(LIB_SRC)
@echo " === Fortran DLL built: $@ ==="
build:
mkdir -p build
clean:
rm -rf build *.o *.mod
+483
View File
@@ -0,0 +1,483 @@
! engines/fortran/dynamics_lib.f90
! ---------------------------------
! DLLFortran 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_dynamicsC 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
File diff suppressed because it is too large Load Diff
+436
View File
@@ -0,0 +1,436 @@
<!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.0</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 引擎驱动的纵波传播测试</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.0leapfrog 算法确保能量守恒</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 相同的三体系统,但采用恰当的初始条件,地球和月球维持稳定的椭圆轨道运动。</p>
<div class="highlight">✅ 与 case03 对比学习:初始条件对数值稳定性的影响</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 引擎驱动的纵波传播测试。验证 Fortran 引擎的输出兼容性。</p>
<div class="highlight">🔧 Fortran 引擎兼容性验证,T_total=200, NSTEP=100</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> 可配置物理参数、力开关、算法、引擎、渲染方式等。
从 case06 起支持 <code>save_trajectory</code> 开关、摄像机初始位置、<code>display_amp</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 # Python 物理引擎
├── draw.py # VisPy 3D 动画
├── plot_wave.py # 波形能量图
├── engines/ # C / C++ / Fortran 引擎
├── examples/ # 案例目录
│ ├── case01/ ~ case10/
└── output/ # 默认输出目录
</pre>
</div>
</div>
</body>
</html>
+73 -16
View File
@@ -1,19 +1,23 @@
# Dynamics 示例案例 # Dynamics 示例案例
本目录包含 6 个从简单到复杂的物理模拟案例,均基于 `../dynamics.py` 框架运行。 本目录包含 10 个从简单到复杂的物理模拟案例,均基于 `../dynamics.py` 框架运行。
--- ---
## 案例一览 ## 案例一览
| 案例 | 标题 | 简介 | 原子数 | 力类型 | | 案例 | 标题 | 简介 | 原子数 | 引擎 | 力类型 |
|---|---|---|---|---| |------|------|------|--------|------|--------|
| [case01](./case01/) | **双粒子弹簧系统** | 两个原子由弹簧连接,在重力场中运动 | 2 | 重力 + 弹簧 | | [case01](./case01/) | **双粒子弹簧系统** | 两个原子由弹簧连接,在重力场中运动 | 2 | Python | 重力 + 弹簧 |
| [case02](./case02/) | **行星运动** | 地球绕太阳椭圆公转(万有引力) | 2 | 万有引力 | | [case02](./case02/) | **行星运动** | 地球绕太阳椭圆公转(万有引力) | 2 | Python | 万有引力 |
| [case03](./case03/) | **日地月系统(失** | 地球绕太阳、月球绕地球,参数不当导致失稳 | 3 | 万有引力 | | [case03](./case03/) | **日地月系统(失** | 地球绕太阳、月球绕地球,参数不当导致失稳 | 3 | Python | 万有引力 |
| [case04](./case04/) | **日地月系统(成功** | 地球绕太阳、月球绕地球,稳定轨道 | 3 | 万有引力 | | [case04](./case04/) | **日地月系统(稳定** | 地球绕太阳、月球绕地球,稳定轨道 | 3 | Python | 万有引力 |
| [case05](./case05/) | **一维原子链纵波** | 驱动原子 1 沿 x 轴振动,产生纵波传播 | 60 | 弹簧 + 驱动力 | | [case05](./case05/) | **一维原子链纵波** | 驱动原子 1 沿 x 轴振动,产生纵波传播 | 60 | Python | 弹簧 + 驱动力 |
| [case06](./case06/) | **一维原子链横波** | 驱动原子 1 沿 z 轴振动,产生横波传播 | 120 | 弹簧 + 驱动力 | | [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(蛙跳法) - **算法**leapfrog(蛙跳法)
- **物理**:重力 m·g + 弹簧胡克力 - **物理**:重力 m·g + 弹簧胡克力
- **渲染**Sphere 模式(精细网格球体)
### case02 — 行星运动 ### case02 — 行星运动
模拟地球绕太阳的椭圆轨道运动(一个固定大质量中心体 + 一个绕行小质量体)。采用万有引力相互作用。 模拟地球绕太阳的椭圆轨道运动(一个固定大质量中心体 + 一个绕行小质量体)。采用万有引力相互作用。
- **力开关**:万有引力开(含强度参数) - **力开关**:万有引力开(含强度参数 `gravity_strength: 100.0`
- **算法**leapfrog(蛙跳法) - **算法**leapfrog(蛙跳法)
- **物理**:牛顿万有引力 F = G·m₁·m₂/r² - **物理**:牛顿万有引力 F = G·m₁·m₂/r²
- **渲染**Sphere 模式
### case03 — 日地月系统(失败案例) ### case03 — 日地月系统(失败案例)
@@ -60,14 +66,53 @@
- **波速**:快(x 方向弹簧力为线性) - **波速**:快(x 方向弹簧力为线性)
- **渲染**Marker 模式(GPU 实例化,60 原子) - **渲染**Marker 模式(GPU 实例化,60 原子)
### case06 — 一维原子链横波 ### case06 — 一维原子链横波(带阻尼)
120 个原子沿 x 轴等间距排列(间距 1),相邻原子用弹簧(k=1.0, L₀=1.0)连接。驱动力沿 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(蛙跳法) - **算法**leapfrog(蛙跳法)
- **引擎**C(高性能)
- **参数**T_total=1000, NSTEP=500
- **波速**:慢(z 方向弹簧力呈几何非线性,类似 FPU 系统) - **波速**:慢(z 方向弹簧力呈几何非线性,类似 FPU 系统)
- **渲染**Marker 模式(GPU 实例化,120 原子)
### 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 cd examples/case05
# 完整运行(模拟 + 采样 + 3D 动画) # 完整运行(模拟 + 3D 动画)
python run_dynamics.py python run_dynamics.py
# 仅运行模拟,跳过 3D 动画 # 仅运行模拟,跳过 3D 动画
@@ -89,6 +134,18 @@ python ../../draw.py output/
每个案例的 `input/input.txt` 中可配置所有物理参数、力开关、算法、渲染方式等。 每个案例的 `input/input.txt` 中可配置所有物理参数、力开关、算法、渲染方式等。
## 案例选择指南
| 你想做什么 | 推荐案例 |
|-----------|---------|
| 快速上手、理解基本框架 | case01 |
| 天体力学 / 万有引力 | case02 / case04 |
| 波动物理(纵波) | case05 |
| 波动物理(横波、非线性、阻尼) | case06 |
| 双端驱动/波干涉 | case07 |
| 对比不同引擎性能 | case08 (C) / case09 (Fortran) |
| 能量分析 | case10 |
## 框架结构 ## 框架结构
``` ```
@@ -101,6 +158,6 @@ dynamics/
├── examples/ # 案例(本目录) ├── examples/ # 案例(本目录)
│ ├── case01/ │ ├── case01/
│ ├── ... │ ├── ...
│ └── case06/ │ └── case10/
└── output/ # 默认输出目录 └── output/ # 默认输出目录
``` ```
+1 -1
View File
@@ -1,2 +1,2 @@
bond_name k rest_length bond_name k rest_length
k1 50.0 1.0 k1 10.0 1.0
+119 -119
View File
@@ -1,121 +1,121 @@
n mass radius x y z vx vy vz fix_x fix_y fix_z 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 1 1 0.1 0 0 0 0 0 0 0 1 0
2 1 0.1 1 0 0 0 0 0 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 0 40 1 0.1 39 0 0 0 0 0 0 1 0
41 1 0.1 40 0 0 0 0 0 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 1 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 120 1 0.1 119 0 0 0 0 0 1 1 1
+13 -10
View File
@@ -19,10 +19,10 @@ save_trajectory: 0 # 0=不保留完整轨迹文件, 1=保留 trajectory.txt
# ── 计算引擎 ────────────────────────────────── # ── 计算引擎 ──────────────────────────────────
# 可选: python, c, cpp, fortran, java # 可选: 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]³ 内
# ── 初始构型 ────────────────────────────────── # ── 初始构型 ──────────────────────────────────
# 坐标文件格式: # 坐标文件格式:
@@ -39,7 +39,7 @@ plot_atom: 1
# ── 物理参数 ────────────────────────────────── # ── 物理参数 ──────────────────────────────────
# 三个方向分量分别对应 x, y, z # 三个方向分量分别对应 x, y, z
G: [0.00, 0.00, 0.00] # 重力场分量 (m/s²) G: [0.00, 0.00, 0.00] # 重力场分量 (m/s²)
B: [0.02, 0.00, 0.02] # 阻尼分量 B: [0.01, 0.00, 0.01] # 阻尼分量
# ── 力开关(0=关闭, 1=开启)────────────────── # ── 力开关(0=关闭, 1=开启)──────────────────
gravity_field: 0 # 均匀重力场 (G) gravity_field: 0 # 均匀重力场 (G)
@@ -66,13 +66,13 @@ warmup_steps: 0 # 默认 0(立即开始记录)
# 总模拟时间(秒),程序自动计算 NT = T_total / DT # 总模拟时间(秒),程序自动计算 NT = T_total / DT
# 如果同时指定了 NT,以 NT 为准 # 如果同时指定了 NT,以 NT 为准
T_total: 100.0 T_total: 1000.0
# 抽帧间隔(每 NSTEP 步取一帧用于动画) # 抽帧间隔(每 NSTEP 步取一帧用于动画)
NSTEP: 10 NSTEP: 500
# ── 时间步长 ────────────────────────────────── # ── 时间步长 ──────────────────────────────────
DT: 0.01 # 时间步长 (s) DT: 0.001 # 时间步长 (s)
# 抽帧范围:只保存 [sample_start, sample_end) 区间内的帧 # 抽帧范围:只保存 [sample_start, sample_end) 区间内的帧
sample_start: null # null 表示从头开始(帧索引从 0 起) sample_start: null # null 表示从头开始(帧索引从 0 起)
@@ -102,7 +102,10 @@ box_color_g: 0.80
box_color_b: 0.85 box_color_b: 0.85
# ── 摄像机初始位置 ──────────────────────────── # ── 摄像机初始位置 ────────────────────────────
camera_distance: 40.0 # 摄像机到场景中心的距离 camera_distance: 120.0 # 摄像机到场景中心的距离
camera_elevation: 0 # 俯仰角(度),负值=俯视 camera_elevation: 0.0 # 俯仰角(度),负值=俯视
camera_azimuth: 0 # 方位角(度) camera_azimuth: 0.0 # 方位角(度)
move_camera: 1 # 0=固定视角, 1=按 move_camera.txt 运动 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 运动
+40
View File
@@ -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`
+477
View File
@@ -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 — 一维原子链驱动力学模拟 | 物理原理 &amp; 使用文档</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>&lt; 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 &nbsp;·&nbsp; 生成于 2026-06-10
</footer>
</div>
</body>
</html>
+2
View File
@@ -0,0 +1,2 @@
bond_name k rest_length
k1 500.0 1.0
+120
View File
@@ -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
+121
View File
@@ -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
+3
View File
@@ -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
+114
View File
@@ -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 方向视觉位移放大倍数(不影响物理)
+9
View File
@@ -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
+54
View File
@@ -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()
+40
View File
@@ -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`
+477
View File
@@ -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 — 一维原子链驱动力学模拟 | 物理原理 &amp; 使用文档</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>&lt; 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 &nbsp;·&nbsp; 生成于 2026-06-10
</footer>
</div>
</body>
</html>
+2
View File
@@ -0,0 +1,2 @@
bond_name k rest_length
k1 0.1 1.0
+2
View File
@@ -0,0 +1,2 @@
n1 n2 bond_name
1 2 k1
+3
View File
@@ -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
+2
View File
@@ -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
+114
View File
@@ -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 方向视觉位移放大倍数(不影响物理)
+9
View File
@@ -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
+54
View File
@@ -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()
+40
View File
@@ -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`
+477
View File
@@ -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 — 一维原子链驱动力学模拟 | 物理原理 &amp; 使用文档</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>&lt; 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 &nbsp;·&nbsp; 生成于 2026-06-10
</footer>
</div>
</body>
</html>
+2
View File
@@ -0,0 +1,2 @@
bond_name k rest_length
k1 300.0 1.0
+40
View File
@@ -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
+41
View File
@@ -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
+2
View File
@@ -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
+114
View File
@@ -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 方向视觉位移放大倍数(不影响物理)
+9
View File
@@ -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
+54
View File
@@ -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()
+40
View File
@@ -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`
+477
View File
@@ -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 — 一维原子链驱动力学模拟 | 物理原理 &amp; 使用文档</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>&lt; 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 &nbsp;·&nbsp; 生成于 2026-06-10
</footer>
</div>
</body>
</html>
+2
View File
@@ -0,0 +1,2 @@
bond_name k rest_length
k1 300.0 1.0
+40
View File
@@ -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
+41
View File
@@ -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
+2
View File
@@ -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
+114
View File
@@ -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 方向视觉位移放大倍数(不影响物理)
+9
View File
@@ -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
+54
View File
@@ -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()
+418 -61
View File
@@ -108,9 +108,21 @@ def _load_wave_dataset(output_dir):
"gravity_strength": float(header.get("gravity_strength", 1.0)), "gravity_strength": float(header.get("gravity_strength", 1.0)),
"G": gravity_vec, "G": gravity_vec,
"driving_force": int(header.get("driving_force", 0)), "driving_force": int(header.get("driving_force", 0)),
"display_amp": _parse_display_amp(header.get("display_amp", "")),
} }
def _parse_display_amp(raw):
if not raw or not str(raw).strip():
return np.ones(3)
try:
import ast as _ast
v = np.array(_ast.literal_eval(str(raw).strip()), dtype=np.float64)
return v if v.shape == (3,) else np.ones(3)
except Exception:
return np.ones(3)
def compute_energy(x, y, z, vx, vy, vz, masses, mass_arr, def compute_energy(x, y, z, vx, vy, vz, masses, mass_arr,
bond_pairs, bond_stiffness, bond_rest_lengths, bond_pairs, bond_stiffness, bond_rest_lengths,
gravity_field, G, gravity_interaction, gravity_strength): gravity_field, G, gravity_interaction, gravity_strength):
@@ -320,6 +332,70 @@ def compute_energy_flux(x, y, z, vx, vy, vz,
return flux, bond_xpos return flux, bond_xpos
def compute_driver_work_power(x, y, z, vx, vy, vz,
bond_pairs, bond_stiffness, bond_rest_lengths,
atom_ids, driver_info):
"""计算每个驱动原子通过键对系统(非驱动原子)做功的功率。
对于驱动原子 d 与系统原子 j 之间的键
P_{dj} = F_{dj} · v_j
其中 F_{dj} 是键对系统原子 j 的弹簧力
Returns:
drv_powers: dict {atom_id: (n_frames,)} 每个驱动原子的瞬时功率
total_power: (n_frames,) 所有驱动原子功率之和
"""
if bond_pairs is None or len(bond_pairs) == 0:
n_frames = x.shape[0]
return {}, np.zeros(n_frames)
id_to_idx = {int(aid): i for i, aid in enumerate(atom_ids)}
driven_idx = {id_to_idx[aid] for aid in driver_info if aid in id_to_idx}
n_frames = x.shape[0]
drv_powers = {}
for b in range(len(bond_pairs)):
ii, jj = int(bond_pairs[b, 0]), int(bond_pairs[b, 1])
i_drv = ii in driven_idx
j_drv = jj in driven_idx
if i_drv == j_drv: # 两端同为驱动或同为自由,跳过
continue
drv_loc = ii if i_drv else jj # 驱动端 index
sys_loc = jj if i_drv else ii # 系统端 index
drv_aid = int(atom_ids[drv_loc])
dx_ = x[:, jj] - x[:, ii]
dy_ = y[:, jj] - y[:, ii]
dz_ = z[:, jj] - z[:, ii]
dist = np.sqrt(dx_**2 + dy_**2 + dz_**2)
dist = np.maximum(dist, 1e-12)
k = bond_stiffness[b]
r0 = bond_rest_lengths[b]
fac = k * (dist - r0) / dist # 标量弹力因子
# 作用于系统原子的弹簧力:指向驱动原子方向
if i_drv: # drv=i, sys=j: 力方向 j→i,即 -(dx_, dy_, dz_)
fx = -fac * dx_
fy = -fac * dy_
fz = -fac * dz_
else: # drv=j, sys=i: 力方向 i→j,即 +(dx_, dy_, dz_)
fx = fac * dx_
fy = fac * dy_
fz = fac * dz_
power = fx * vx[:, sys_loc] + fy * vy[:, sys_loc] + fz * vz[:, sys_loc]
if drv_aid not in drv_powers:
drv_powers[drv_aid] = np.zeros(n_frames)
drv_powers[drv_aid] += power
total_power = sum(drv_powers.values()) if drv_powers else np.zeros(n_frames)
return drv_powers, total_power
def plot_wave(output_dir, save_gif=False, save_mp4=False, show=True): def plot_wave(output_dir, save_gif=False, save_mp4=False, show=True):
"""主绘图函数:读取 display.txt 并生成波形+能量动画。 """主绘图函数:读取 display.txt 并生成波形+能量动画。
@@ -370,26 +446,106 @@ def plot_wave(output_dir, save_gif=False, save_mp4=False, show=True):
dy = y - pos_0[np.newaxis, :, 1] dy = y - pos_0[np.newaxis, :, 1]
dz = z - pos_0[np.newaxis, :, 2] dz = z - pos_0[np.newaxis, :, 2]
# ── 系统总能量(用于右下时间图)── # ── 每粒子能量(图2 与图3 共用同一套计算)──
ek_sys, us_sys, ug_sys, ugr_sys = compute_energy(
x, y, z, vx, vy, vz, masses, masses,
bond_pairs, bond_stiffness, bond_rest_lengths,
gravity_field, G, gravity_interaction, gravity_strength)
e_total = ek_sys + us_sys + ug_sys + ugr_sys
power = np.gradient(e_total, t)
# ── 每粒子能量 ──
ek_atom, pe_atom, et_atom = compute_per_atom_energy( ek_atom, pe_atom, et_atom = compute_per_atom_energy(
x, y, z, vx, vy, vz, masses, x, y, z, vx, vy, vz, masses,
bond_pairs, bond_stiffness, bond_rest_lengths, bond_pairs, bond_stiffness, bond_rest_lengths,
atom_ids, driver_info) atom_ids, driver_info)
# ── 系统总能量 = 各粒子求和(与图2 完全一致)──
ek_sys = np.sum(ek_atom, axis=1)
us_sys = np.sum(pe_atom, axis=1)
e_total = np.sum(et_atom, axis=1)
power = np.gradient(e_total, t)
# 重力势能:仍用原有函数提供(若启用重力场)
_, _, ug_sys, ugr_sys = compute_energy(
x, y, z, vx, vy, vz, masses, masses,
bond_pairs, bond_stiffness, bond_rest_lengths,
gravity_field, G, gravity_interaction, gravity_strength)
if gravity_field or gravity_interaction:
e_total = e_total + ug_sys + ugr_sys
power = np.gradient(e_total, t)
# ── 能流密度 ── # ── 能流密度 ──
flux, bond_xpos = compute_energy_flux( flux, bond_xpos = compute_energy_flux(
x, y, z, vx, vy, vz, x, y, z, vx, vy, vz,
bond_pairs, bond_stiffness, bond_rest_lengths) bond_pairs, bond_stiffness, bond_rest_lengths)
# ── y 轴范围 ── # ── 驱动做功功率 ──
drv_powers, total_drv_power = compute_driver_work_power(
x, y, z, vx, vy, vz,
bond_pairs, bond_stiffness, bond_rest_lengths,
atom_ids, driver_info)
# ── 原子可视化预计算 ──
display_amp = np.array(data.get("display_amp", [1.0, 1.0, 1.0]), dtype=np.float64)
eq_x_vis = pos_0[:, 0]
eq_z_vis = pos_0[:, 2]
# 视觉坐标 = 平衡位置 + 放大的位移
x_vis = eq_x_vis + (x - eq_x_vis) * display_amp[0] # (n_frames, n_atoms)
z_vis = eq_z_vis + (z - eq_z_vis) * display_amp[2]
# 找边界原子(与驱动原子成键的系统原子)及对应键
id_to_idx_vis = {int(aid): i for i, aid in enumerate(atom_ids)}
driven_set_vis = {id_to_idx_vis[aid] for aid in driver_info if aid in id_to_idx_vis}
bond_boundary_list = [] # (drv_idx, sys_idx, bond_b)
for _b in range(len(bond_pairs)):
_ii, _jj = int(bond_pairs[_b, 0]), int(bond_pairs[_b, 1])
if (_ii in driven_set_vis) ^ (_jj in driven_set_vis):
_drv = _ii if _ii in driven_set_vis else _jj
_sys = _jj if _ii in driven_set_vis else _ii
bond_boundary_list.append((_drv, _sys, _b))
# 唯一边界原子索引列表
_bnd_set = {}
for _drv, _sys, _b in bond_boundary_list:
if _sys not in _bnd_set:
_bnd_set[_sys] = len(_bnd_set)
boundary_atom_idx = np.array(list(_bnd_set.keys()), dtype=int)
n_boundary = len(boundary_atom_idx)
_lat = (eq_x_vis[-1] - eq_x_vis[0]) / max(n_atoms - 1, 1)
_z_all = z_vis.reshape(-1)
_z_min, _z_max = np.min(_z_all), np.max(_z_all)
_z_mg = max((_z_max - _z_min) * 0.2, _lat * 2)
_z_range = max((_z_max - _z_min) + 2 * _z_mg, _lat * 4)
_arrow_len = _z_range * 0.50 # 箭头最大显示长度 = 纵坐标范围的 50%
# 预计算边界原子受到的驱动力
# 方向:沿显示坐标下的键方向(消除坐标轴比例失真);大小:胡克力模 k|d-r0|
bnd_fx_scaled = np.zeros((n_frames, max(n_boundary, 1)))
bnd_fz_scaled = np.zeros((n_frames, max(n_boundary, 1)))
_f_mag_all = []
for _drv, _sys, _b in bond_boundary_list:
_bi = _bnd_set[_sys]
# 物理键长
_dx3 = x[:, _drv] - x[:, _sys]
_dy3 = y[:, _drv] - y[:, _sys]
_dz3 = z[:, _drv] - z[:, _sys]
_dist = np.maximum(np.sqrt(_dx3**2 + _dy3**2 + _dz3**2), 1e-12)
# 有符号力大小(正 = 拉向驱动原子,负 = 推离)
_f_signed = bond_stiffness[_b] * (_dist - bond_rest_lengths[_b])
# 显示坐标下的键方向(x-z 平面)
_dx_d = x_vis[:, _drv] - x_vis[:, _sys]
_dz_d = z_vis[:, _drv] - z_vis[:, _sys]
_disp_len = np.maximum(np.sqrt(_dx_d**2 + _dz_d**2), 1e-12)
bnd_fx_scaled[:, _bi] += _f_signed * _dx_d / _disp_len
bnd_fz_scaled[:, _bi] += _f_signed * _dz_d / _disp_len
_f_mag_all.append(np.abs(_f_signed))
_f_max = np.max(_f_mag_all) if _f_mag_all else 1.0
_f_max = _f_max if _f_max > 1e-20 else 1.0
bnd_fx_scaled = bnd_fx_scaled / _f_max * _arrow_len
bnd_fz_scaled = bnd_fz_scaled / _f_max * _arrow_len
# 边界原子速度(方向沿实际速度,大小归一化)
bnd_vx_raw = vx[:, boundary_atom_idx] if n_boundary > 0 else np.zeros((n_frames, 1))
bnd_vz_raw = vz[:, boundary_atom_idx] if n_boundary > 0 else np.zeros((n_frames, 1))
_v_max = np.max(np.sqrt(bnd_vx_raw**2 + bnd_vz_raw**2)) if n_boundary > 0 else 1.0
_v_max = _v_max if _v_max > 1e-20 else 1.0
bnd_vx_scaled = bnd_vx_raw / _v_max * _arrow_len
bnd_vz_scaled = bnd_vz_raw / _v_max * _arrow_len
# y 轴范围 ──
def get_ylim(arr): def get_ylim(arr):
vmax = np.max(np.abs(arr)) vmax = np.max(np.abs(arr))
if vmax < 1e-10: if vmax < 1e-10:
@@ -413,8 +569,9 @@ def plot_wave(output_dir, save_gif=False, save_mp4=False, show=True):
energy_vmax = energy_vmax if energy_vmax > 1e-12 else 1.0 energy_vmax = energy_vmax if energy_vmax > 1e-12 else 1.0
energy_ylim = (0.0, energy_vmax * 1.2) energy_ylim = (0.0, energy_vmax * 1.2)
e_max = max(np.max(e_total), 0.01) * 1.3 e_max = max(np.max(e_total), 1e-12)
p_max = max(np.max(np.abs(power)) * 1.3, 0.01) e_min = min(np.min(e_total), 0.0)
p_max = max(np.percentile(np.abs(power), 95) if len(power) > 0 else 0.0, 0.0)
# 能流 y 轴范围(对称,正负各半) # 能流 y 轴范围(对称,正负各半)
if flux.size > 0: if flux.size > 0:
@@ -426,100 +583,282 @@ def plot_wave(output_dir, save_gif=False, save_mp4=False, show=True):
atom_idx = np.arange(n_atoms) atom_idx = np.arange(n_atoms)
# ── 图形布局:4 行 × 1 列,纵向排列 ── # ── 驱动/非驱动粒子能量(右下图)──
id_to_idx = {int(aid): i for i, aid in enumerate(atom_ids)}
driven_idx = np.array([id_to_idx[aid] for aid in driver_info if aid in id_to_idx], dtype=int)
free_idx = np.setdiff1d(np.arange(n_atoms), driven_idx)
has_driver = len(driven_idx) > 0
if has_driver:
ek_drv = np.sum(ek_atom[:, driven_idx], axis=1)
ep_drv = np.sum(pe_atom[:, driven_idx], axis=1)
ek_free = np.sum(ek_atom[:, free_idx], axis=1)
ep_free = np.sum(pe_atom[:, free_idx], axis=1)
# ── 图形布局:左3行、右3行(subplot_mosaic)──
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'DejaVu Sans'] plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False plt.rcParams['axes.unicode_minus'] = False
fig, (ax_wave, ax_energy, ax_flux, ax_ep) = plt.subplots(4, 1, figsize=(12, 18)) from matplotlib.collections import LineCollection as _LC
fig.suptitle("波形与能量分析", fontsize=16)
fig.subplots_adjust(hspace=0.42, top=0.95)
# ── 图1:x/y/z 位移波形叠加 ── fig, axes = plt.subplot_mosaic(
[['atoms', 'ep'],
['wave', 'drv'],
['energy', 'pwr']],
figsize=(20, 15))
ax_atoms = axes['atoms']
ax_wave = axes['wave']
ax_ep = axes['ep']
ax_energy = axes['energy']
ax_drv = axes['drv']
ax_pwr = axes['pwr']
fig.subplots_adjust(hspace=0.45, wspace=0.32, top=0.97)
# ── 左上:原子位置 + 键 + 力/速度箭头 ──
_x_min, _x_max = eq_x_vis[0], eq_x_vis[-1]
ax_atoms.set_xlim(_x_min - _lat, _x_max + _lat)
ax_atoms.set_ylim(_z_min - _z_mg, _z_max + _z_mg)
ax_atoms.set_xlabel("位置 $x$")
ax_atoms.set_ylabel("位移 $z$(放大 {:.0f}×)".format(display_amp[2]))
ax_atoms.set_title("原子运动(红=驱动,箭头:红=驱动力,蓝=边界速度)")
ax_atoms.set_aspect('auto')
ax_atoms.grid(True, alpha=0.2)
# 键线段(LineCollection,初始帧)
def _make_bond_segs(frame_idx):
segs = []
for _b in range(len(bond_pairs)):
_ii, _jj = int(bond_pairs[_b, 0]), int(bond_pairs[_b, 1])
segs.append([(x_vis[frame_idx, _ii], z_vis[frame_idx, _ii]),
(x_vis[frame_idx, _jj], z_vis[frame_idx, _jj])])
return segs
_bond_lc = _LC(_make_bond_segs(0), colors='#888888', linewidths=0.8, zorder=1)
ax_atoms.add_collection(_bond_lc)
# 散点:自由原子(黑)
_free_mask = np.array([i not in driven_set_vis for i in range(n_atoms)])
_scat_free, = ax_atoms.plot(
x_vis[0, _free_mask], z_vis[0, _free_mask],
'o', color='black', ms=4, zorder=3)
# 散点:驱动原子(红)
_drv_mask = ~_free_mask
_scat_drv, = ax_atoms.plot(
x_vis[0, _drv_mask], z_vis[0, _drv_mask],
'o', color='red', ms=6, zorder=4)
# 力箭头(红,边界原子)
_q_force = ax_atoms.quiver(
x_vis[0, boundary_atom_idx] if n_boundary > 0 else [],
z_vis[0, boundary_atom_idx] if n_boundary > 0 else [],
bnd_fx_scaled[0] if n_boundary > 0 else [],
bnd_fz_scaled[0] if n_boundary > 0 else [],
color='red', angles='xy', scale_units='xy', scale=1,
width=0.007, headwidth=5, headlength=5, zorder=5)
# 速度箭头(蓝,边界原子)
_q_vel = ax_atoms.quiver(
x_vis[0, boundary_atom_idx] if n_boundary > 0 else [],
z_vis[0, boundary_atom_idx] if n_boundary > 0 else [],
bnd_vx_scaled[0] if n_boundary > 0 else [],
bnd_vz_scaled[0] if n_boundary > 0 else [],
color='blue', angles='xy', scale_units='xy', scale=1,
width=0.007, headwidth=5, headlength=5, zorder=5)
# ── 左上:x/y/z 位移波形 ──
ax_wave.set_xlim(0, n_atoms - 1) ax_wave.set_xlim(0, n_atoms - 1)
ax_wave.set_ylim(disp_ylim) ax_wave.set_ylim(disp_ylim)
ax_wave.set_xlabel("原子序号") ax_wave.set_xlabel("原子序号")
ax_wave.set_ylabel("位移") ax_wave.set_ylabel("位移 $u$")
ax_wave.set_title("粒子位移(x / y / z 方向)") ax_wave.set_title("粒子位移($x$ / $y$ / $z$ 方向)")
ax_wave.grid(True, alpha=0.3) ax_wave.grid(True, alpha=0.3)
wave_disps = [dx, dy, dz] wave_disps = [dx, dy, dz]
wave_labels = ["x 方向(纵波)", "y 方向(横波)", "z 方向(横波)"] wave_labels = ["$u_x$(纵波)", "$u_y$(横波)", "$u_z$(横波)"]
wave_colors = ["#2563eb", "#ea580c", "#16a34a"] wave_colors = ["#2563eb", "#ea580c", "#16a34a"]
wave_lines = [] wave_lines = []
for label, color in zip(wave_labels, wave_colors): for label, color in zip(wave_labels, wave_colors):
ln, = ax_wave.plot([], [], color=color, linewidth=1.5, label=label) ln, = ax_wave.plot([], [], color=color, linewidth=1.5, label=label)
wave_lines.append(ln) wave_lines.append(ln)
ax_wave.legend(loc="upper right", fontsize=9) ax_wave.legend(loc="upper right", fontsize=9)
time_text = ax_wave.text(0.02, 0.95, "", transform=ax_wave.transAxes, _dt_frame = (t[1] - t[0]) if len(t) > 1 else 0.0
fontsize=10, verticalalignment="top") _t_total_str = f"{t[-1] + _dt_frame:.2f} s"
_time_axes = [ax_atoms, ax_wave, ax_energy, ax_ep, ax_drv, ax_pwr]
time_texts = [
ax.text(0.02, 0.97, "", transform=ax.transAxes,
fontsize=9, verticalalignment="top",
bbox=dict(boxstyle="round,pad=0.2", fc="white", alpha=0.7))
for ax in _time_axes
]
time_text = time_texts[1] # 保留旧名兼容下面的代码
# ── 图2:每粒子动能、势能、总能叠加 ── # ── 左下:每粒子能量(左轴)+ 能流密度(右轴)──
ax_energy.set_xlim(0, n_atoms - 1) ax_energy.set_xlim(0, n_atoms - 1)
ax_energy.set_ylim(energy_ylim) ax_energy.set_ylim(energy_ylim)
ax_energy.set_xlabel("原子序号") ax_energy.set_xlabel("原子序号 / 键位置")
ax_energy.set_ylabel("能量") ax_energy.set_ylabel("能量 $E$")
ax_energy.set_title("每粒子能量(动能 / 势能 / 总能)") ax_energy.set_title(
r"每粒子能量($E_k$/$E_p$/$E_{tot}$)与能流密度 $J$"
)
ax_energy.grid(True, alpha=0.3) ax_energy.grid(True, alpha=0.3)
energy_arrays = [ek_atom, pe_atom, et_atom] energy_arrays = [ek_atom, pe_atom, et_atom]
energy_labels = ["动能", "势能", "总能"] energy_labels = ["$E_k$动能", "$E_p$势能", "$E_{tot}$总能"]
energy_colors = ["#1d4ed8", "#b45309", "#7c3aed"] energy_colors = ["#16a34a", "#b45309", "#7c3aed"]
energy_lines = [] energy_lines = []
for label, color in zip(energy_labels, energy_colors): for label, color in zip(energy_labels, energy_colors):
ln, = ax_energy.plot([], [], color=color, linewidth=1.5, label=label) ln, = ax_energy.plot([], [], color=color, linewidth=1.5, label=label)
energy_lines.append(ln) energy_lines.append(ln)
ax_energy.legend(loc="upper right", fontsize=9)
# ── 图3:能流密度 J(Hardy 公式)── ax_flux = ax_energy.twinx()
xmin_flux = bond_xpos[0] if len(bond_xpos) > 0 else 0
xmax_flux = bond_xpos[-1] if len(bond_xpos) > 0 else n_atoms - 1
ax_flux.set_xlim(xmin_flux, xmax_flux)
ax_flux.set_ylim(flux_ylim) ax_flux.set_ylim(flux_ylim)
ax_flux.set_ylabel("能流密度 $J$", color="#dc2626")
ax_flux.tick_params(axis='y', labelcolor="#dc2626")
ax_flux.axhline(0, color="gray", linewidth=0.8, linestyle="--") ax_flux.axhline(0, color="gray", linewidth=0.8, linestyle="--")
ax_flux.set_xlabel("位置(键中点 x 坐标)") flux_line, = ax_flux.plot([], [], color="#dc2626", linewidth=1.5,
ax_flux.set_ylabel("能流密度 J") label="$J$能流密度")
ax_flux.set_title("键能流密度 J = ½ F·(vᵢ+vⱼ) J>0 向右传播,J<0 向左传播)") handles_e, labels_e = ax_energy.get_legend_handles_labels()
ax_flux.grid(True, alpha=0.3) handles_f, labels_f = ax_flux.get_legend_handles_labels()
flux_line, = ax_flux.plot([], [], color="#dc2626", linewidth=1.5) ax_energy.legend(handles_e + handles_f, labels_e + labels_f,
loc="upper right", fontsize=9)
# ── 图4:系统总能量随时间 ── # ── 右上:系统总能量随时间 ──
ax_ep.set_xlim(t[0], t[-1]) ax_ep.set_xlim(t[0], t[-1])
ep_yhigh = max(e_max, p_max) ep_margin = (e_max - e_min) * 0.15 if e_max > e_min else e_max * 0.15
ep_ylow = min(-p_max * 0.1, 0.0) ax_ep.set_ylim(e_min - ep_margin, e_max + ep_margin)
ax_ep.set_ylim(ep_ylow, ep_yhigh) ax_ep.set_clip_on(True)
ax_ep.set_xlabel("时间 (s)") ax_ep.set_xlabel("时间 $t$ (s)")
ax_ep.set_ylabel("能量 / 功率") ax_ep.set_ylabel("能量 $E$ / 功率 $P$")
ax_ep.set_title("系统能量与输入功率") ax_ep.set_title("系统能量与输入功率")
ax_ep.grid(True, alpha=0.3) ax_ep.grid(True, alpha=0.3)
ln_ek, = ax_ep.plot([], [], "b-", lw=1.5, label="动能") ln_ek, = ax_ep.plot([], [], "b-", lw=1.5, label="$E_k$动能")
ln_us, = ax_ep.plot([], [], "orange", lw=1.5, label="弹性势能") ln_us, = ax_ep.plot([], [], "orange", lw=1.5, label="$E_s$弹性势能")
ln_et, = ax_ep.plot([], [], "r--", lw=1.5, label="总能量") ln_et, = ax_ep.plot([], [], "r--", lw=1.5, label="$E_{tot}$总能量")
ln_pw, = ax_ep.plot([], [], "g-", lw=1.5, alpha=0.7, label="输入功率 (dE/dt)") ln_pw, = ax_ep.plot([], [], "g-", lw=1.5, alpha=0.7, label=r"$P_{in}=dE/dt$")
ln_ug = None ln_ug = None
ln_ugr = None ln_ugr = None
if gravity_field: if gravity_field:
ln_ug, = ax_ep.plot([], [], "purple", lw=1.0, alpha=0.5, label="重力势能") ln_ug, = ax_ep.plot([], [], "purple", lw=1.0, alpha=0.5, label="$E_g$重力势能")
if gravity_interaction and n_atoms <= 200: if gravity_interaction and n_atoms <= 200:
ln_ugr, = ax_ep.plot([], [], "brown", lw=1.0, alpha=0.5, label="万有引力势能") ln_ugr, = ax_ep.plot([], [], "brown", lw=1.0, alpha=0.5, label="$E_{gr}$万有引力势能")
ax_ep.legend(loc="upper left", fontsize=9) ax_ep.legend(loc="upper right", fontsize=9)
# ── 右下:驱动/非驱动粒子能量随时间 ──
ax_drv.set_xlim(t[0], t[-1])
ax_drv.set_xlabel("时间 $t$ (s)")
ax_drv.set_ylabel("能量 $E$")
ax_drv.grid(True, alpha=0.3)
ln_ek_drv = ln_ep_drv = ln_ek_free = ln_ep_free = None
ln_et_drv = ln_et_free = None
if has_driver:
et_drv = ek_drv + ep_drv
et_free = ek_free + ep_free
drv_ids = sorted(driver_info.keys())
ax_drv.set_title(f"驱动粒子(序号 {drv_ids})向系统做功")
ln_ek_drv, = ax_drv.plot([], [], color="#dc2626", lw=1.2, linestyle="--",
label=r"$E_k^{drv}$(驱动动能)")
ln_ep_drv, = ax_drv.plot([], [], color="#f97316", lw=1.2, linestyle="--",
label=r"$E_p^{drv}$(驱动势能)")
ln_et_drv, = ax_drv.plot([], [], color="#7f1d1d", lw=2.0,
label=r"$E_{tot}^{drv}$(驱动总能)")
ln_ek_free, = ax_drv.plot([], [], color="#2563eb", lw=1.2, linestyle="--",
label=r"$E_k^{sys}$(系统动能)")
ln_ep_free, = ax_drv.plot([], [], color="#16a34a", lw=1.2, linestyle="--",
label=r"$E_p^{sys}$(系统势能)")
ln_et_free, = ax_drv.plot([], [], color="#1e3a5f", lw=2.0,
label=r"$E_{tot}^{sys}$(系统总能)")
ax_drv.legend(loc="upper right", fontsize=8)
# y 轴一次定好
_drv_all = np.concatenate([ek_drv, ep_drv, et_drv, ek_free, ep_free, et_free])
_dy_max = np.max(_drv_all)
_dy_min = np.min(_drv_all)
_dy_mg = (_dy_max - _dy_min) * 0.15 if _dy_max > _dy_min else abs(_dy_max) * 0.15 + 1e-12
ax_drv.set_ylim(_dy_min - _dy_mg, _dy_max + _dy_mg)
else:
ax_drv.set_title("驱动粒子能量(无驱动力)")
ax_drv.text(0.5, 0.5, "无驱动力", transform=ax_drv.transAxes,
ha="center", va="center", fontsize=12, color="gray")
# ── 右下:驱动做功功率 ──
ax_pwr.set_xlim(t[0], t[-1])
ax_pwr.set_xlabel("时间 $t$ (s)")
ax_pwr.set_ylabel("功率 $P$")
ax_pwr.set_title("驱动原子对系统做功的功率 $P = \\mathbf{F}_{bond}\\cdot\\mathbf{v}_{sys}$")
ax_pwr.axhline(0, color="gray", linewidth=0.8, linestyle="--")
ax_pwr.grid(True, alpha=0.3)
pwr_colors = ["#dc2626", "#2563eb", "#16a34a", "#f97316", "#7c3aed"]
ln_pwr_each = {} # aid -> Line2D
if drv_powers:
for idx_d, (aid, _) in enumerate(sorted(drv_powers.items())):
color = pwr_colors[idx_d % len(pwr_colors)]
ln, = ax_pwr.plot([], [], color=color, lw=1.2, linestyle="--",
label=f"$P_{{drv,{aid}}}$(原子 {aid}")
ln_pwr_each[aid] = ln
ln_pwr_total, = ax_pwr.plot([], [], color="black", lw=2.0,
label=r"$P_{total}$(总功率)")
ax_pwr.legend(loc="upper right", fontsize=9)
# y 轴一次定好
if drv_powers:
_pw_all = np.concatenate(list(drv_powers.values()) + [total_drv_power])
_pw_max = np.max(_pw_all)
_pw_min = np.min(_pw_all)
_pw_mg = (_pw_max - _pw_min) * 0.15 if _pw_max > _pw_min else abs(_pw_max) * 0.15 + 1e-12
ax_pwr.set_ylim(_pw_min - _pw_mg, _pw_max + _pw_mg)
# ── 动画更新 ── # ── 动画更新 ──
def update(frame): def update(frame):
# 图1:位移波形 # 每轮开始时清屏
# 左上:原子位置动画
_bond_lc.set_segments(_make_bond_segs(frame))
_scat_free.set_xdata(x_vis[frame, _free_mask])
_scat_free.set_ydata(z_vis[frame, _free_mask])
_scat_drv.set_xdata(x_vis[frame, _drv_mask])
_scat_drv.set_ydata(z_vis[frame, _drv_mask])
if n_boundary > 0:
_q_force.set_offsets(
np.column_stack([x_vis[frame, boundary_atom_idx],
z_vis[frame, boundary_atom_idx]]))
_q_force.set_UVC(bnd_fx_scaled[frame], bnd_fz_scaled[frame])
_q_vel.set_offsets(
np.column_stack([x_vis[frame, boundary_atom_idx],
z_vis[frame, boundary_atom_idx]]))
_q_vel.set_UVC(bnd_vx_scaled[frame], bnd_vz_scaled[frame])
if frame == 0:
all_clear = list(wave_lines) + list(energy_lines) + [flux_line]
all_clear += [ln for ln in [ln_ek, ln_us, ln_et, ln_pw, ln_ug, ln_ugr]
if ln is not None]
if has_driver:
all_clear += [ln for ln in [ln_ek_drv, ln_ep_drv, ln_et_drv,
ln_ek_free, ln_ep_free, ln_et_free]
if ln is not None]
all_clear += list(ln_pwr_each.values()) + [ln_pwr_total]
for ln in all_clear:
ln.set_data([], [])
_tstr0 = f"t = {t[0]:.2f} s / {_t_total_str} | 帧 1/{n_frames}"
for _tt in time_texts:
_tt.set_text(_tstr0)
return all_clear + time_texts + [_bond_lc, _scat_free, _scat_drv,
_q_force, _q_vel]
# 左中:位移波形
for i, ln in enumerate(wave_lines): for i, ln in enumerate(wave_lines):
ln.set_data(atom_idx, wave_disps[i][frame]) ln.set_data(atom_idx, wave_disps[i][frame])
time_text.set_text(f"t = {t[frame]:.2f} s | 帧 {frame+1}/{n_frames}") _tstr = f"t = {t[frame]:.2f} s / {_t_total_str} | 帧 {frame+1}/{n_frames}"
for _tt in time_texts:
_tt.set_text(_tstr)
# 图2:每粒子能量 # 左下:每粒子能量 + 能流密度
for i, ln in enumerate(energy_lines): for i, ln in enumerate(energy_lines):
ln.set_data(atom_idx, energy_arrays[i][frame]) ln.set_data(atom_idx, energy_arrays[i][frame])
# 图3:能流密度
if flux.shape[1] > 0: if flux.shape[1] > 0:
flux_line.set_data(bond_xpos, flux[frame]) flux_line.set_data(bond_xpos, flux[frame])
# 图4:系统能量(累计到当前帧 # 右上:系统能量(累计)
cur_t = t[:frame + 1] cur_t = t[:frame + 1]
ln_ek.set_data(cur_t, ek_sys[:frame + 1]) ln_ek.set_data(cur_t, ek_sys[:frame + 1])
ln_us.set_data(cur_t, us_sys[:frame + 1]) ln_us.set_data(cur_t, us_sys[:frame + 1])
@@ -527,15 +866,33 @@ def plot_wave(output_dir, save_gif=False, save_mp4=False, show=True):
ln_pw.set_data(cur_t, power[:frame + 1]) ln_pw.set_data(cur_t, power[:frame + 1])
if ln_ug: ln_ug.set_data(cur_t, ug_sys[:frame + 1]) if ln_ug: ln_ug.set_data(cur_t, ug_sys[:frame + 1])
if ln_ugr: ln_ugr.set_data(cur_t, ugr_sys[:frame + 1]) if ln_ugr: ln_ugr.set_data(cur_t, ugr_sys[:frame + 1])
ax_ep.set_xlim(t[0], max(t[frame] + max(t[-1] * 0.05, 1), t[-1]))
artists = wave_lines + [time_text] + energy_lines + \ # 右中:驱动/系统粒子能量(累计)
[flux_line, ln_ek, ln_us, ln_et, ln_pw] if has_driver:
ln_ek_drv.set_data( cur_t, ek_drv[:frame + 1])
ln_ep_drv.set_data( cur_t, ep_drv[:frame + 1])
ln_et_drv.set_data( cur_t, et_drv[:frame + 1])
ln_ek_free.set_data(cur_t, ek_free[:frame + 1])
ln_ep_free.set_data(cur_t, ep_free[:frame + 1])
ln_et_free.set_data(cur_t, et_free[:frame + 1])
# 右下:驱动做功功率(累计)
for aid, ln in ln_pwr_each.items():
ln.set_data(cur_t, drv_powers[aid][:frame + 1])
ln_pwr_total.set_data(cur_t, total_drv_power[:frame + 1])
artists = (wave_lines + time_texts + energy_lines +
[flux_line, ln_ek, ln_us, ln_et, ln_pw])
if ln_ug: artists.append(ln_ug) if ln_ug: artists.append(ln_ug)
if ln_ugr: artists.append(ln_ugr) if ln_ugr: artists.append(ln_ugr)
if has_driver:
artists += [ln_ek_drv, ln_ep_drv, ln_et_drv,
ln_ek_free, ln_ep_free, ln_et_free]
artists += list(ln_pwr_each.values()) + [ln_pwr_total]
artists += [_bond_lc, _scat_free, _scat_drv, _q_force, _q_vel]
return artists return artists
ani = FuncAnimation(fig, update, frames=n_frames, interval=50, blit=True) ani = FuncAnimation(fig, update, frames=n_frames, interval=50, blit=True, repeat=True)
# ── 输出文件 ── # ── 输出文件 ──
gif_path = None gif_path = None