Compare commits
24 Commits
ea99f09f9b
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e415bf5ba5 | |||
| b50c55d0a3 | |||
| 75d8aae1f2 | |||
| dd6fbdfccf | |||
| eea62de479 | |||
| 1261398be2 | |||
| 369b840cf2 | |||
| 7fb2b730e9 | |||
| e371fa8db1 | |||
| 7468a2d458 | |||
| 76a26d888e | |||
| 8183cbc22e | |||
| 41c454eeab | |||
| 6c06c00f4b | |||
| c8d12e7f45 | |||
| 7078cbc744 | |||
| d4ce23a07e | |||
| 84aed813df | |||
| f6c468702f | |||
| 8b82d3c4e4 | |||
| 80b7467421 | |||
| 974216332c | |||
| a16d2239a1 | |||
| 0e636e275d |
+8
-6
@@ -1,7 +1,9 @@
|
||||
# 全部使用 LF 换行,仓库内外一致,不随系统自动转换
|
||||
* text eol=lf
|
||||
# 引擎 DLL 是二进制文件,禁止行尾转换
|
||||
engines/release/*.dll binary
|
||||
|
||||
# 二进制文件不转换
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.ico binary
|
||||
# Python 源码使用 LF
|
||||
*.py text eol=lf
|
||||
|
||||
# Makefile 使用 LF
|
||||
Makefile text eol=lf
|
||||
*.mk text eol=lf
|
||||
|
||||
+4
-6
@@ -18,10 +18,9 @@ pip-wheel-metadata/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# ── C / C++ 编译产物 ─────────────────────────────────────────
|
||||
# Makefile 构建输出(engines/c/build/)
|
||||
engines/c/build/
|
||||
engines/cpp/build/
|
||||
# ── C / C++ / Fortran 编译产物 ────────────────────────
|
||||
# 源码目录 engines/src/*/ 中可能产生的构建输出
|
||||
engines/src/*/build/
|
||||
|
||||
# CMake 构建目录(根目录或自定义 build 目录)
|
||||
CMakeCache.txt
|
||||
@@ -42,12 +41,11 @@ build_*/
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# 可执行文件(保留源码,排除编译出的二进制)
|
||||
# 注意:Windows 下 .exe 后缀的可执行文件
|
||||
*.exe
|
||||
# 但 engines/c/Makefile 里指定了 build/ 目录,已由上面覆盖
|
||||
# 可在 engines/src/*/ 中用 make dll 编译引擎 DLL
|
||||
|
||||
# 运行时生成的引擎参数文件(每次运行都会覆盖)
|
||||
engines/*/param.json
|
||||
|
||||
+238
-37
@@ -668,12 +668,20 @@ def load_driver_file(driver_path, atom_ids):
|
||||
return None
|
||||
|
||||
print(f"[compute] 已加载驱动力: {len(drivers)} 条定义")
|
||||
for d in drivers:
|
||||
print(f" 原子 {d['atom_id']}: "
|
||||
f"A=({d['amp'][0]},{d['amp'][1]},{d['amp'][2]}), "
|
||||
f"f=({d['freq'][0]},{d['freq'][1]},{d['freq'][2]}), "
|
||||
f"φ=({phi_deg[d['amp'].tolist().index(max(d['amp']))]}° 等), "
|
||||
f"period={d['period_str']}")
|
||||
if len(drivers) <= 20:
|
||||
for d in drivers:
|
||||
print(f" 原子 {d['atom_id']}: "
|
||||
f"A=({d['amp'][0]},{d['amp'][1]},{d['amp'][2]}), "
|
||||
f"f=({d['freq'][0]},{d['freq'][1]},{d['freq'][2]}), "
|
||||
f"φ=({phi_deg[d['amp'].tolist().index(max(d['amp']))]}° 等), "
|
||||
f"period={d['period_str']}")
|
||||
else:
|
||||
_first = drivers[0]
|
||||
_last = drivers[-1]
|
||||
print(f" 原子 {_first['atom_id']}~{_last['atom_id']}: "
|
||||
f"A=({_first['amp'][0]},{_first['amp'][1]},{_first['amp'][2]}), "
|
||||
f"f=({_first['freq'][0]},{_first['freq'][1]},{_first['freq'][2]}), "
|
||||
f"period={_first['period_str']} 等 {len(drivers)} 条")
|
||||
return drivers
|
||||
|
||||
|
||||
@@ -947,6 +955,183 @@ def run_from_config(config, out_dir=None):
|
||||
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),
|
||||
"display_color": json.dumps(config.get("display_color",
|
||||
{"x":[0,[1.0,0.0,0.0]],"y":[0,[0.0,1.0,0.0]],"z":[0,[0.0,0.0,1.0]],
|
||||
"xy":[0,[1.0,1.0,0.0]],"yz":[0,[0.0,1.0,1.0]],"zx":[0,[1.0,0.0,1.0]],
|
||||
"xyz":[1,[1.0,1.0,1.0]]})),
|
||||
"ball_radius": str(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):
|
||||
"""调用外部计算引擎(C/C++/Fortran),生成 trajectory.txt。
|
||||
|
||||
@@ -961,32 +1146,43 @@ def run_engine(engine, input_dir, output_dir, config):
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
system = platform.system().lower()
|
||||
engine_map = {
|
||||
"c": "engines/c/build/dynamics_c",
|
||||
"cpp": "engines/cpp/build/dynamics_cpp",
|
||||
"fortran": "engines/fortran/build/dynamics_f90",
|
||||
"c": "engines/release/dynamics_c",
|
||||
"cpp": "engines/release/dynamics_cpp",
|
||||
"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:
|
||||
raise ValueError(f"不支持的引擎: {engine},可选: {list(engine_map.keys())}")
|
||||
raise ValueError(f"不支持的引擎: {engine},可选: c, cpp, fortran, python")
|
||||
|
||||
engine_rel = engine_map[engine]
|
||||
engine_path = os.path.join(script_dir, engine_rel)
|
||||
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_path = os.path.join(script_dir, engine_rel)
|
||||
|
||||
# 自动检测可执行文件后缀和平台专用版本
|
||||
candidates = [
|
||||
engine_path, # 无后缀
|
||||
engine_path + ".exe", # Windows .exe
|
||||
engine_path + f"_{system}.exe", # 平台专用 (c_linux.exe, c_darwin.exe)
|
||||
]
|
||||
found = None
|
||||
for p in candidates:
|
||||
if os.path.exists(p):
|
||||
found = p
|
||||
break
|
||||
if found is None:
|
||||
raise FileNotFoundError(
|
||||
f"引擎可执行文件不存在: 尝试了 {candidates}\n"
|
||||
f"请先编译: cd engines/{engine} && make\n"
|
||||
f"或安装交叉编译器后: cd engines/{engine} && make {system}")
|
||||
# 自动检测可执行文件后缀和平台专用版本
|
||||
candidates = [
|
||||
engine_path,
|
||||
engine_path + ".exe",
|
||||
engine_path + f"_{system}.exe",
|
||||
]
|
||||
found = None
|
||||
for p in candidates:
|
||||
if os.path.exists(p):
|
||||
found = p
|
||||
break
|
||||
if found is None:
|
||||
raise FileNotFoundError(
|
||||
f"引擎可执行文件不存在: 尝试了 {candidates}\n"
|
||||
f"请先编译: cd engines/{engine} && make\n"
|
||||
f"或安装交叉编译器后: cd engines/{engine} && make {system}")
|
||||
|
||||
# 构造 param.json(数值参数)
|
||||
G = parse_gravity_vector(config.get("G", [0, 0, -9.8]))
|
||||
@@ -1024,7 +1220,7 @@ def run_engine(engine, input_dir, output_dir, config):
|
||||
"camera_center_y": float(config.get("camera_center_y", 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)
|
||||
with open(param_path, "w", encoding="utf-8") as f:
|
||||
json.dump(param_json, f, indent=2)
|
||||
@@ -1042,7 +1238,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 相同且 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
|
||||
try:
|
||||
with open(_cache_path, encoding="utf-8") as _cf:
|
||||
@@ -1057,10 +1253,10 @@ def run_engine(engine, input_dir, output_dir, config):
|
||||
if _step_time is None:
|
||||
_calib_param = dict(param_json)
|
||||
_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:
|
||||
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)
|
||||
_ct0 = time.time()
|
||||
subprocess.run(
|
||||
@@ -1089,8 +1285,11 @@ def run_engine(engine, input_dir, output_dir, config):
|
||||
t_start = time.time()
|
||||
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(
|
||||
[engine_path, os.path.abspath(input_dir), os.path.abspath(output_dir), param_path],
|
||||
_cmd,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
text=True, encoding='utf-8', errors='replace')
|
||||
_engine_lines = []
|
||||
@@ -1611,10 +1810,6 @@ def apply_motion_update(x, y, z, vx, vy, vz, dt, m, g, b):
|
||||
x, y, z, vx, vy, vz = Leapfrog_Method(x, y, z, vx, vy, vz, dt, m, g, b)
|
||||
else:
|
||||
raise ValueError(f"未知算法: {METHOD}")
|
||||
|
||||
x, vx = Limit_in_box(x, X_MIN, X_MAX, vx)
|
||||
y, vy = Limit_in_box(y, Y_MIN, Y_MAX, vy)
|
||||
z, vz = Limit_in_box(z, Z_MIN, Z_MAX, vz)
|
||||
return x, y, z, vx, vy, vz
|
||||
|
||||
|
||||
@@ -1683,6 +1878,9 @@ def run_simulation(save_trajectory=0):
|
||||
x, y, z, vx, vy, vz, DT, ATOM_MASSES, G, B)
|
||||
else:
|
||||
x, y, z, vx, vy, vz = apply_motion_update(x, y, z, vx, vy, vz, DT, ATOM_MASSES, G, B)
|
||||
x, vx = Limit_in_box(x, X_MIN, X_MAX, vx)
|
||||
y, vy = Limit_in_box(y, Y_MIN, Y_MAX, vy)
|
||||
z, vz = Limit_in_box(z, Z_MIN, Z_MAX, vz)
|
||||
x, y, z = wrap_position(x, y, z)
|
||||
x, y, z, vx, vy, vz = apply_fixed_constraints(x, y, z, vx, vy, vz)
|
||||
print(
|
||||
@@ -1737,6 +1935,9 @@ def run_simulation(save_trajectory=0):
|
||||
x, y, z, vx, vy, vz, DT, ATOM_MASSES, G, B)
|
||||
else:
|
||||
x, y, z, vx, vy, vz = apply_motion_update(x, y, z, vx, vy, vz, DT, ATOM_MASSES, G, B)
|
||||
x, vx = Limit_in_box(x, X_MIN, X_MAX, vx)
|
||||
y, vy = Limit_in_box(y, Y_MIN, Y_MAX, vy)
|
||||
z, vz = Limit_in_box(z, Z_MIN, Z_MAX, vz)
|
||||
x, y, z = wrap_position(x, y, z)
|
||||
x, y, z, vx, vy, vz = apply_fixed_constraints(x, y, z, vx, vy, vz)
|
||||
|
||||
|
||||
@@ -45,7 +45,65 @@ if os.path.exists(npz_path):
|
||||
disp_data = compute.load_display_npz(npz_path)
|
||||
else:
|
||||
disp_data = compute.load_display_txt(disp_path)
|
||||
h = disp_data["header_fields"]
|
||||
|
||||
# ── 从 input.txt 读取参数(替代 display.npz 中的 meta)──
|
||||
try:
|
||||
import yaml
|
||||
_have_yaml = True
|
||||
except ImportError:
|
||||
_have_yaml = False
|
||||
|
||||
input_dir = os.path.join(os.path.dirname(output_dir), "input")
|
||||
input_path = os.path.join(input_dir, "input.txt")
|
||||
|
||||
if _have_yaml and os.path.exists(input_path):
|
||||
try:
|
||||
with open(input_path, "r", encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f)
|
||||
except Exception:
|
||||
config = {}
|
||||
else:
|
||||
config = {}
|
||||
# 兼容旧版:若 input.txt 不存在或解析失败,降级到 display.npz 的 meta
|
||||
if not config:
|
||||
config = disp_data.get("header_fields", {})
|
||||
|
||||
# ── 从 coord.txt 读取平衡位置 ─────────────────
|
||||
# 注意: config 中的 *_file 可能带 "input/" 前缀,但 input_dir 已指向 input/ 目录
|
||||
_coord_file_raw = config.get("coord_file", "coord.txt")
|
||||
coord_path = os.path.join(input_dir, os.path.basename(_coord_file_raw))
|
||||
if os.path.exists(coord_path):
|
||||
try:
|
||||
_ids, _masses, _radii, _pos, _vel, _fixed = compute.load_coord_file(coord_path)
|
||||
EQ_POS = _pos # (n_atoms, 3)
|
||||
ATOM_FIXED = _fixed # (n_atoms, 3)
|
||||
except Exception:
|
||||
EQ_POS = None
|
||||
ATOM_FIXED = None
|
||||
else:
|
||||
EQ_POS = None
|
||||
ATOM_FIXED = None
|
||||
|
||||
# ── 从 connection.txt 读取成键信息(若 meta 中没有)──
|
||||
BOND_PAIRS = disp_data.get("bond_pairs", [])
|
||||
if not BOND_PAIRS and 'bond_pairs' not in disp_data:
|
||||
conn_path = os.path.join(input_dir, os.path.basename(config.get("connection_file", "connection.txt")))
|
||||
if os.path.exists(conn_path):
|
||||
try:
|
||||
_pairs = []
|
||||
with open(conn_path, "r", encoding="utf-8") as _f:
|
||||
_f.readline() # skip header
|
||||
for _line in _f:
|
||||
_line = _line.strip()
|
||||
if not _line or _line.startswith("#"):
|
||||
continue
|
||||
_parts = _line.split()
|
||||
if len(_parts) >= 2:
|
||||
_pairs.append([int(_parts[0]) - 1, int(_parts[1]) - 1])
|
||||
BOND_PAIRS = np.array(_pairs, dtype=np.int32) if _pairs else []
|
||||
except Exception:
|
||||
BOND_PAIRS = []
|
||||
BOND_PAIRS = BOND_PAIRS.tolist() if hasattr(BOND_PAIRS, 'tolist') else BOND_PAIRS
|
||||
|
||||
# 全原子帧数据
|
||||
DISP_ALL_X = disp_data["frames_x"] # (n_frames, n_atoms)
|
||||
@@ -66,64 +124,220 @@ DISP_VZ = DISP_ALL_VZ[:, 0]
|
||||
N_FRAMES = DISP_ALL_X.shape[0]
|
||||
NT = int(disp_data["n_total_frames"])
|
||||
N_ATOMS = int(disp_data["n_total_particles"])
|
||||
DT = float(h.get("DT", 0.001))
|
||||
DT = float(config.get("DT", 0.001))
|
||||
|
||||
# 视觉位移放大:display_amp: [ax, ay, az],对偏离第0帧的位移乘以倍数
|
||||
_damp_raw = h.get("display_amp", "")
|
||||
if _damp_raw.strip():
|
||||
# 视觉位移放大:display_amp: [ax, ay, az],对偏离平衡位置的位移乘以倍数
|
||||
_damp_raw = config.get("display_amp", "")
|
||||
if isinstance(_damp_raw, (list, tuple)):
|
||||
_damp = np.array(_damp_raw, dtype=np.float64)
|
||||
elif isinstance(_damp_raw, str) and _damp_raw.strip():
|
||||
import ast as _ast
|
||||
_damp_vals = _ast.literal_eval(_damp_raw.strip())
|
||||
_damp = np.array(_damp_vals, dtype=np.float64)
|
||||
if _damp.shape == (3,) and not np.allclose(_damp, 1.0):
|
||||
_eq_x = DISP_ALL_X[0:1, :] # 第0帧作为平衡位置参考
|
||||
_eq_y = DISP_ALL_Y[0:1, :]
|
||||
_eq_z = DISP_ALL_Z[0:1, :]
|
||||
_damp = np.array(_ast.literal_eval(_damp_raw.strip()), dtype=np.float64)
|
||||
else:
|
||||
_damp = None
|
||||
if _damp is not None and _damp.shape == (3,) and not np.allclose(_damp, 1.0):
|
||||
if EQ_POS is not None:
|
||||
_eq_x = EQ_POS[None, :, 0] # coord.txt 平衡位置
|
||||
_eq_y = EQ_POS[None, :, 1]
|
||||
_eq_z = EQ_POS[None, :, 2]
|
||||
else:
|
||||
_eq_x = DISP_ALL_X[0:1, :] # 第0帧作为平衡位置参考
|
||||
_eq_y = DISP_ALL_Y[0:1, :]
|
||||
_eq_z = DISP_ALL_Z[0:1, :]
|
||||
DISP_ALL_X = _eq_x + (DISP_ALL_X - _eq_x) * _damp[0]
|
||||
DISP_ALL_Y = _eq_y + (DISP_ALL_Y - _eq_y) * _damp[1]
|
||||
DISP_ALL_Z = _eq_z + (DISP_ALL_Z - _eq_z) * _damp[2]
|
||||
NSTEP = int(h.get("NSTEP", 1))
|
||||
|
||||
# ── 位移颜色映射 ──────────────────────────────
|
||||
# display_color: { mode: [enabled, [R,G,B]], ... }
|
||||
# mode: x, y, z, xy, yz, zx, xyz
|
||||
# enabled: 0=off, 1=on
|
||||
# [R,G,B]: 最大位移时的颜色(渐变起点为白色)
|
||||
# 例:xyz: [1, [1.0,1.0,1.0]] → 三方向合成位移 → 白色渐变
|
||||
FRAME_COLORS = None
|
||||
_DC_MODE = None
|
||||
_DC_COLOR = None
|
||||
|
||||
_raw_dc = config.get("display_color", "")
|
||||
if _raw_dc:
|
||||
try:
|
||||
dc = json.loads(_raw_dc) if isinstance(_raw_dc, str) else _raw_dc
|
||||
if isinstance(dc, dict):
|
||||
for mode, (enabled, color) in dc.items():
|
||||
if int(enabled) and mode in ("x","y","z","xy","yz","zx","xyz"):
|
||||
_DC_MODE = mode
|
||||
_DC_COLOR = np.array(color, dtype=np.float32)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if _DC_MODE is not None and _DC_COLOR is not None:
|
||||
if EQ_POS is not None:
|
||||
eq_x = EQ_POS[None, :, 0] # coord.txt 平衡位置
|
||||
eq_y = EQ_POS[None, :, 1]
|
||||
eq_z = EQ_POS[None, :, 2]
|
||||
else:
|
||||
eq_x = DISP_ALL_X[0:1, :] # 第0帧
|
||||
eq_y = DISP_ALL_Y[0:1, :]
|
||||
eq_z = DISP_ALL_Z[0:1, :]
|
||||
dx = DISP_ALL_X - eq_x
|
||||
dy = DISP_ALL_Y - eq_y
|
||||
dz = DISP_ALL_Z - eq_z
|
||||
|
||||
if _DC_MODE == "x":
|
||||
disp_mag = np.abs(dx)
|
||||
elif _DC_MODE == "y":
|
||||
disp_mag = np.abs(dy)
|
||||
elif _DC_MODE == "z":
|
||||
disp_mag = np.abs(dz)
|
||||
elif _DC_MODE == "xy":
|
||||
disp_mag = np.sqrt(dx**2 + dy**2)
|
||||
elif _DC_MODE == "yz":
|
||||
disp_mag = np.sqrt(dy**2 + dz**2)
|
||||
elif _DC_MODE == "zx":
|
||||
disp_mag = np.sqrt(dz**2 + dx**2)
|
||||
else: # xyz
|
||||
disp_mag = np.sqrt(dx**2 + dy**2 + dz**2)
|
||||
|
||||
# ── color_xrange: 限定归一化基准的原子范围 ───────────
|
||||
# 格式: [['min'|'mid'|'max'|数值, 'min'|'mid'|'max'|数值], ...] 对应 x,y,z
|
||||
# 在此范围内的原子计算最大位移 d_max,所有原子以此基准归一化着色
|
||||
_cxr = config.get("color_xrange", None)
|
||||
_d_max_source = disp_mag # 默认:所有原子
|
||||
_range_label = "全部原子"
|
||||
if _cxr and isinstance(_cxr, (list, tuple)) and len(_cxr) == 3:
|
||||
try:
|
||||
# 获取坐标极值
|
||||
if EQ_POS is not None:
|
||||
_eq_all = EQ_POS
|
||||
else:
|
||||
_eq_all = np.column_stack([
|
||||
DISP_ALL_X[0], DISP_ALL_Y[0], DISP_ALL_Z[0]])
|
||||
_cmin = _eq_all.min(axis=0)
|
||||
_cmax = _eq_all.max(axis=0)
|
||||
_cmid = (_cmin + _cmax) / 2
|
||||
|
||||
_key_map = {"min": _cmin, "mid": _cmid, "max": _cmax}
|
||||
_range_lo = np.zeros(3, dtype=np.float64)
|
||||
_range_hi = np.zeros(3, dtype=np.float64)
|
||||
|
||||
for _i in range(3):
|
||||
_lo = _cxr[_i][0]
|
||||
_hi = _cxr[_i][1]
|
||||
_range_lo[_i] = _key_map[_lo][_i] if _lo in _key_map else float(_lo)
|
||||
_range_hi[_i] = _key_map[_hi][_i] if _hi in _key_map else float(_hi)
|
||||
|
||||
_in_x = (_eq_all[:, 0] >= _range_lo[0]) & (_eq_all[:, 0] <= _range_hi[0])
|
||||
_in_y = (_eq_all[:, 1] >= _range_lo[1]) & (_eq_all[:, 1] <= _range_hi[1])
|
||||
_in_z = (_eq_all[:, 2] >= _range_lo[2]) & (_eq_all[:, 2] <= _range_hi[2])
|
||||
_color_mask = _in_x & _in_y & _in_z
|
||||
|
||||
_n_in_range = _color_mask.sum()
|
||||
if _n_in_range > 0:
|
||||
_d_max_source = disp_mag[:, _color_mask] # 仅在范围内找最大位移
|
||||
_range_label = (f"x[{_range_lo[0]:.1f},{_range_hi[0]:.1f}] "
|
||||
f"y[{_range_lo[1]:.1f},{_range_hi[1]:.1f}] "
|
||||
f"z[{_range_lo[2]:.1f},{_range_hi[2]:.1f}]")
|
||||
except Exception as _e:
|
||||
print(f"[draw] color_xrange 解析失败: {_e}")
|
||||
|
||||
# 用(范围限定的)最大位移归一化,所有原子统一着色
|
||||
d_max = _d_max_source.max()
|
||||
if d_max > 1e-12:
|
||||
disp_norm = disp_mag / d_max
|
||||
else:
|
||||
disp_norm = disp_mag
|
||||
|
||||
FRAME_COLORS = np.ones((N_FRAMES, N_ATOMS, 4), dtype=np.float32)
|
||||
t = disp_norm[..., None]
|
||||
FRAME_COLORS[..., :3] = 1.0 + (_DC_COLOR - 1.0) * t
|
||||
print(f"[draw] 位移颜色映射: mode={_DC_MODE}, color={_DC_COLOR.tolist()}, "
|
||||
f"d_max={d_max:.4f}, 范围: {_range_label}")
|
||||
|
||||
# ── color_driver: 驱动原子 → 统一颜色 ──
|
||||
_cd_raw = config.get("color_driver", None)
|
||||
if _cd_raw is not None:
|
||||
try:
|
||||
_cd = np.array(_cd_raw, dtype=np.float32)
|
||||
# 读取 driver.txt 获取驱动原子 ID
|
||||
_driver_file = config.get("driver_file", "driver.txt")
|
||||
_driver_path = os.path.join(input_dir, os.path.basename(_driver_file))
|
||||
if os.path.exists(_driver_path):
|
||||
_driver_ids = []
|
||||
with open(_driver_path, "r", encoding="utf-8") as _df:
|
||||
_df.readline() # skip header
|
||||
for _line in _df:
|
||||
_line = _line.strip()
|
||||
if not _line or _line.startswith("#"):
|
||||
continue
|
||||
_parts = _line.split()
|
||||
if _parts:
|
||||
_driver_ids.append(int(_parts[0]) - 1) # 1-based → 0-based
|
||||
if _driver_ids:
|
||||
FRAME_COLORS[:, _driver_ids, :3] = _cd
|
||||
print(f"[draw] color_driver: {len(_driver_ids)} 驱动原子 → RGB{_cd_raw}")
|
||||
except Exception as _e:
|
||||
print(f"[draw] color_driver 解析失败: {_e}")
|
||||
|
||||
# ── color_fix: 全固定原子 (fix_x=fix_y=fix_z=1) → 统一颜色 ──
|
||||
_cf_raw = config.get("color_fix", None)
|
||||
if _cf_raw is not None and ATOM_FIXED is not None:
|
||||
try:
|
||||
_cf = np.array(_cf_raw, dtype=np.float32)
|
||||
_full_fixed = (ATOM_FIXED[:, 0] == 1) & (ATOM_FIXED[:, 1] == 1) & (ATOM_FIXED[:, 2] == 1)
|
||||
_n_fix = _full_fixed.sum()
|
||||
if _n_fix > 0:
|
||||
FRAME_COLORS[:, _full_fixed, :3] = _cf
|
||||
print(f"[draw] color_fix: {_n_fix} 全固定原子 → RGB{_cf_raw}")
|
||||
except Exception as _e:
|
||||
print(f"[draw] color_fix 解析失败: {_e}")
|
||||
NSTEP = int(config.get("NSTEP", 1))
|
||||
DISP_STEP = np.arange(N_FRAMES) * NSTEP
|
||||
DISP_T = DISP_STEP * DT
|
||||
|
||||
# 原子信息
|
||||
ATOM_IDS = disp_data["atom_ids"]
|
||||
# 优先使用 per-atom 半径,否则用统一的 ball_radius
|
||||
_raw_radii = h.get("atom_radii", "")
|
||||
_raw_radii = config.get("atom_radii", "")
|
||||
if _raw_radii.strip():
|
||||
ATOM_RADII = np.array([float(x) for x in _raw_radii.split(",")])
|
||||
else:
|
||||
ATOM_RADII = np.full(N_ATOMS, float(h.get("ball_radius", 0.5)))
|
||||
ATOM_RADII = np.full(N_ATOMS, float(config.get("ball_radius", 0.5)))
|
||||
PLOT_ATOM_ROW = 0
|
||||
PLOT_ATOM_ID = int(ATOM_IDS[0])
|
||||
BOND_PAIRS = [] # display 格式不含成键信息,从原始数据加载
|
||||
# 成键信息已在上面从 connection.txt 加载
|
||||
|
||||
# 渲染方式:0=Sphere(网格球体), 1=Marker(GPU点精灵)
|
||||
USE_MARKER = int(h.get("use_marker", 0))
|
||||
USE_MARKER = int(config.get("use_marker", 0))
|
||||
|
||||
if N_FRAMES <= 0:
|
||||
raise ValueError(
|
||||
"output/display.txt 中没有可播放的帧,请检查 sample_start/sample_end/NSTEP 配置。")
|
||||
|
||||
# 保留模拟边界常量(用于场景缩放、相机等),从 output/display.txt 中读取
|
||||
X_MIN = float(h.get("X_MIN", -10)); X_MAX = float(h.get("X_MAX", 10))
|
||||
Y_MIN = float(h.get("Y_MIN", -10)); Y_MAX = float(h.get("Y_MAX", 10))
|
||||
Z_MIN = float(h.get("Z_MIN", -10)); Z_MAX = float(h.get("Z_MAX", 10))
|
||||
raw_alpha = h.get("alpha", "0.2")
|
||||
try:
|
||||
alpha_list = [float(x) for x in raw_alpha.split(",")]
|
||||
if len(alpha_list) != 6:
|
||||
alpha_list = alpha_list * 6
|
||||
except (ValueError, AttributeError):
|
||||
alpha_list = [float(raw_alpha)] * 6
|
||||
# 模拟边界(从 input.txt 的 box_a 计算)
|
||||
_box_a = float(config.get("box_a", 10.0))
|
||||
X_MIN = -_box_a; X_MAX = _box_a
|
||||
Y_MIN = -_box_a; Y_MAX = _box_a
|
||||
Z_MIN = -_box_a; Z_MAX = _box_a
|
||||
raw_alpha = config.get("alpha", "0.2")
|
||||
if isinstance(raw_alpha, (list, tuple)):
|
||||
alpha_list = [float(x) for x in raw_alpha]
|
||||
else:
|
||||
try:
|
||||
alpha_list = [float(x) for x in raw_alpha.split(",")]
|
||||
except (ValueError, AttributeError):
|
||||
alpha_list = [float(raw_alpha)] * 6
|
||||
if len(alpha_list) != 6:
|
||||
alpha_list = (alpha_list * 6)[:6]
|
||||
|
||||
# 绘图参数
|
||||
ball_radius = float(h.get("ball_radius", 0.5))
|
||||
ball_color_r = float(h.get("ball_color_r", 0.9))
|
||||
ball_color_g = float(h.get("ball_color_g", 0.2))
|
||||
ball_color_b = float(h.get("ball_color_b", 0.2))
|
||||
box_color_r = float(h.get("box_color_r", 0.8))
|
||||
box_color_g = float(h.get("box_color_g", 0.8))
|
||||
box_color_b = float(h.get("box_color_b", 0.85))
|
||||
ball_radius = float(config.get("ball_radius", 0.5))
|
||||
ball_color_r = float(config.get("ball_color_r", 0.9))
|
||||
ball_color_g = float(config.get("ball_color_g", 0.2))
|
||||
ball_color_b = float(config.get("ball_color_b", 0.2))
|
||||
box_color_r = float(config.get("box_color_r", 0.8))
|
||||
box_color_g = float(config.get("box_color_g", 0.8))
|
||||
box_color_b = float(config.get("box_color_b", 0.85))
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
@@ -135,23 +349,23 @@ axis_length = 10.0
|
||||
|
||||
import math as _math_cam
|
||||
|
||||
_cx = float(h.get("camera_center_x", 0.0))
|
||||
_cy = float(h.get("camera_center_y", 0.0))
|
||||
_cz = float(h.get("camera_center_z", 0.0))
|
||||
_cx = float(config.get("camera_center_x", 0.0))
|
||||
_cy = float(config.get("camera_center_y", 0.0))
|
||||
_cz = float(config.get("camera_center_z", 0.0))
|
||||
|
||||
# 若 input.txt 指定了摄像机自身坐标,则由坐标反推 distance/elevation/azimuth
|
||||
if h.get("camera_pos_x") is not None:
|
||||
_px = float(h["camera_pos_x"])
|
||||
_py = float(h["camera_pos_y"])
|
||||
_pz = float(h["camera_pos_z"])
|
||||
if config.get("camera_pos_x") is not None:
|
||||
_px = float(config.get("camera_pos_x"))
|
||||
_py = float(config.get("camera_pos_y"))
|
||||
_pz = float(config.get("camera_pos_z"))
|
||||
_dx, _dy, _dz = _px - _cx, _py - _cy, _pz - _cz
|
||||
_dist = _math_cam.sqrt(_dx*_dx + _dy*_dy + _dz*_dz) or 1.0
|
||||
_elev = _math_cam.degrees(_math_cam.asin(max(-1.0, min(1.0, _dy / _dist))))
|
||||
_azim = _math_cam.degrees(_math_cam.atan2(_dx, _dz))
|
||||
else:
|
||||
_dist = float(h.get("camera_distance", 40.0))
|
||||
_elev = float(h.get("camera_elevation", 0))
|
||||
_azim = float(h.get("camera_azimuth", 0))
|
||||
_dist = float(config.get("camera_distance", 40.0))
|
||||
_elev = float(config.get("camera_elevation", 0))
|
||||
_azim = float(config.get("camera_azimuth", 0))
|
||||
|
||||
initial_camera = {
|
||||
"distance": _dist,
|
||||
@@ -212,11 +426,11 @@ axes_group.append(scene.visuals.Arrow(
|
||||
parent=view.scene,
|
||||
))
|
||||
|
||||
axes_group.append(scene.visuals.Text(text="x", color=(1.0, 0.2, 0.2, 1.0), font_size=18,
|
||||
axes_group.append(scene.visuals.Text(text="x", color=(1.0, 0.2, 0.2, 1.0), font_size=14,
|
||||
pos=(axis_length + 0.2, 0, 0), anchor_x="left", anchor_y="center", parent=view.scene))
|
||||
axes_group.append(scene.visuals.Text(text="y", color=(0.2, 1.0, 0.2, 1.0), font_size=18,
|
||||
axes_group.append(scene.visuals.Text(text="y", color=(0.2, 1.0, 0.2, 1.0), font_size=14,
|
||||
pos=(0, axis_length + 0.2, 0), anchor_x="left", anchor_y="bottom", parent=view.scene))
|
||||
axes_group.append(scene.visuals.Text(text="z", color=(0.3, 0.6, 1.0, 1.0), font_size=18,
|
||||
axes_group.append(scene.visuals.Text(text="z", color=(0.3, 0.6, 1.0, 1.0), font_size=14,
|
||||
pos=(0, 0, axis_length + 0.2), anchor_x="left", anchor_y="bottom", parent=view.scene))
|
||||
|
||||
# ── 原子渲染 ──────────────────────────────────
|
||||
@@ -235,11 +449,14 @@ TAB10_RGB = np.array([
|
||||
[0.7373, 0.7412, 0.1333], # 黄绿
|
||||
[0.0902, 0.7451, 0.8118], # 青
|
||||
])
|
||||
# 每个原子的颜色(循环使用 tab10 色板)
|
||||
# 每个原子的颜色(循环使用 tab10 色板,或按位移着色)
|
||||
atom_colors = np.zeros((N_ATOMS, 4), dtype=np.float32)
|
||||
for i in range(N_ATOMS):
|
||||
r, g, b = TAB10_RGB[i % len(TAB10_RGB)]
|
||||
atom_colors[i] = [r, g, b, 1.0]
|
||||
if FRAME_COLORS is not None:
|
||||
atom_colors[:] = FRAME_COLORS[0] # 初始帧颜色
|
||||
else:
|
||||
for i in range(N_ATOMS):
|
||||
r, g, b = TAB10_RGB[i % len(TAB10_RGB)]
|
||||
atom_colors[i] = [r, g, b, 1.0]
|
||||
|
||||
if USE_MARKER:
|
||||
# ── Marker 模式:GPU 实例化,一次 draw call ──
|
||||
@@ -295,12 +512,12 @@ for f_idx, (pos, direction) in enumerate(faces):
|
||||
|
||||
# 右上角:相机信息
|
||||
camera_info = scene.visuals.Text(
|
||||
text="", color="white", font_size=14,
|
||||
text="", color="white", font_size=12,
|
||||
pos=(0, 0), anchor_x="right", anchor_y="top", parent=canvas.scene)
|
||||
|
||||
# 左上角:小球信息
|
||||
ball_info = scene.visuals.Text(
|
||||
text="", color=(0.2, 1.0, 0.2, 1.0), font_size=18,
|
||||
text="", color=(0.2, 1.0, 0.2, 1.0), font_size=14,
|
||||
pos=(0, 0), anchor_x="left", anchor_y="top",
|
||||
face="黑体", bold=True, parent=canvas.scene)
|
||||
|
||||
@@ -312,7 +529,7 @@ reset_button = scene.visuals.Rectangle(
|
||||
radius=6, color=(0.18, 0.35, 0.65, 0.85),
|
||||
border_color="white", parent=canvas.scene)
|
||||
reset_button_label = scene.visuals.Text(
|
||||
text="reset", color="white", font_size=16,
|
||||
text="reset", color="white", font_size=13,
|
||||
pos=(reset_btn_size[0] / 2 + 8, reset_btn_size[1] / 2 + 8),
|
||||
anchor_x="center", anchor_y="center",
|
||||
bold=True, parent=canvas.scene)
|
||||
@@ -326,7 +543,7 @@ info_button = scene.visuals.Rectangle(
|
||||
radius=6, color=(0.9, 0.3, 0.3, 0.9),
|
||||
border_color="white", parent=canvas.scene)
|
||||
info_button_label = scene.visuals.Text(
|
||||
text="info", color="white", font_size=16,
|
||||
text="info", color="white", font_size=13,
|
||||
pos=(info_btn_size[0] / 2 + 8, info_btn_size[1] / 2 + 8),
|
||||
anchor_x="center", anchor_y="center",
|
||||
bold=True, parent=canvas.scene)
|
||||
@@ -346,7 +563,7 @@ axes_button = scene.visuals.Rectangle(
|
||||
radius=6, color=(0.3, 0.7, 0.3, 0.9),
|
||||
border_color="white", parent=canvas.scene)
|
||||
axes_button_label = scene.visuals.Text(
|
||||
text="axes", color="white", font_size=16,
|
||||
text="axes", color="white", font_size=13,
|
||||
pos=(axes_btn_size[0] / 2 + 8, axes_btn_size[1] / 2 + 8),
|
||||
anchor_x="center", anchor_y="center",
|
||||
bold=True, parent=canvas.scene)
|
||||
@@ -551,12 +768,15 @@ def handle_mouse_press(event):
|
||||
# ===========================================================================
|
||||
|
||||
def _update_atom_positions(f_idx):
|
||||
"""更新所有原子到第 f_idx 帧的位置。"""
|
||||
"""更新所有原子到第 f_idx 帧的位置,必要时更新颜色。"""
|
||||
if USE_MARKER:
|
||||
marker_pos[:, 0] = DISP_ALL_X[f_idx]
|
||||
marker_pos[:, 1] = DISP_ALL_Y[f_idx]
|
||||
marker_pos[:, 2] = DISP_ALL_Z[f_idx]
|
||||
balls.set_data(pos=marker_pos)
|
||||
if FRAME_COLORS is not None:
|
||||
balls.set_data(pos=marker_pos, face_color=FRAME_COLORS[f_idx])
|
||||
else:
|
||||
balls.set_data(pos=marker_pos)
|
||||
else:
|
||||
for i in range(N_ATOMS):
|
||||
balls[i].transform = STTransform(translate=(
|
||||
@@ -630,10 +850,10 @@ def _load_move_camera_txt():
|
||||
|
||||
# 先试 move_camera.txt 直读,没有则用 display.txt 缓存
|
||||
# header 中 camera_keyframes 为空字符串表示 move_camera=0(开关关闭),跳过文件加载
|
||||
_camera_motion_enabled = bool(h.get("camera_keyframes", ""))
|
||||
_camera_motion_enabled = bool(config.get("camera_keyframes", ""))
|
||||
_CAM_MOTION = _load_move_camera_txt() if _camera_motion_enabled else None
|
||||
if not _CAM_MOTION:
|
||||
_CAM_MOTION = json.loads(h.get("camera_keyframes", "null")) if h.get("camera_keyframes") else None
|
||||
_CAM_MOTION = json.loads(config.get("camera_keyframes", "null")) if config.get("camera_keyframes") else None
|
||||
if _CAM_MOTION:
|
||||
_cam_center = [0.0, 0.0, 0.0]
|
||||
_cam_elev = initial_camera["elevation"]
|
||||
|
||||
+22
-14
@@ -187,12 +187,11 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
disp_path = os.path.join(output_dir_abs, "display.txt")
|
||||
|
||||
# ── 自动缓存检测 ───────────────────────────────────────
|
||||
# force_calc=1: 强制重新计算,忽略缓存
|
||||
# force_calc=0: 尊重 step_simulate 设置,不自动覆盖
|
||||
# force_calc=1: 强制重新计算,忽略缓存(仅在 step_simulate=1 时生效)
|
||||
# force_calc=0: 尊重 step_simulate 设置
|
||||
force_calc = int(config.get("force_calc", 0))
|
||||
if force_calc:
|
||||
if force_calc and config.get("step_simulate", 1):
|
||||
print(f"[run] force_calc=1,跳过缓存,强制重新计算")
|
||||
config["step_simulate"] = 1
|
||||
config["step_sample"] = 1
|
||||
elif config.get("step_simulate", 1):
|
||||
# step_simulate=1 且 force_calc=0 → 按用户要求执行计算
|
||||
@@ -225,8 +224,12 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
print(f"[run] 没有可用的缓存输出,但 step_simulate=0,将跳过模拟")
|
||||
|
||||
# 2. 运行物理模拟 → output/trajectory.txt
|
||||
_engine_aliases = {"c++": "cpp", "f90": "fortran", "f": "fortran"}
|
||||
engine = _engine_aliases.get(
|
||||
str(config.get("engine", "python")).lower(),
|
||||
str(config.get("engine", "python")).lower()
|
||||
)
|
||||
if config.get("step_simulate", 1):
|
||||
engine = config.get("engine", "python")
|
||||
total_steps = config["NT"]
|
||||
record_steps = total_steps - (config.get("warmup_steps") or 0)
|
||||
print(f"[run] 开始计算 总步数={total_steps} 记录步数={record_steps} DT={config['DT']}")
|
||||
@@ -243,11 +246,14 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
config.pop("_skip_run", None)
|
||||
input_dir_abs = str(input_dir_path.resolve())
|
||||
output_dir_abs = str(output_dir_path.resolve())
|
||||
# 外部引擎写完整 trajectory.txt,后续抽帧
|
||||
traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz = compute.run_engine(
|
||||
engine, input_dir_abs, output_dir_abs, config)
|
||||
if int(config.get("save_trajectory", 0)):
|
||||
compute.save_trajectory_txt(traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz, str(runtime_base))
|
||||
|
||||
# ── DLL 路径(无文件 I/O,直接输出 display.npz)──
|
||||
from engines.engine_dll import is_dll_available
|
||||
if not is_dll_available(engine):
|
||||
raise FileNotFoundError(
|
||||
f"DLL 未找到(引擎 {engine})。"
|
||||
f"请先编译:cd engines/{engine} && make dll")
|
||||
compute.run_engine_dll(engine, output_dir_abs, config)
|
||||
|
||||
_elapsed = _time.time() - _t0
|
||||
print(f"[run] 引擎: {engine} 计算完成: {record_steps} 步 {_elapsed:.3f} s")
|
||||
@@ -345,11 +351,13 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
||||
if not os.path.exists(draw_script):
|
||||
print(f"[run] 未找到动画脚本: {draw_script}")
|
||||
else:
|
||||
# 检查 display.txt 是否存在(step_sample=0 时可能没有)
|
||||
disp_path = os.path.join(output_dir_abs, "display.txt")
|
||||
# 检查 display.npz 或 display.txt 是否存在
|
||||
disp_npz = os.path.join(output_dir_abs, "display.npz")
|
||||
disp_txt = os.path.join(output_dir_abs, "display.txt")
|
||||
disp_path = disp_npz if os.path.exists(disp_npz) else disp_txt
|
||||
if not os.path.exists(disp_path):
|
||||
print(f"[run] 错误: 找不到 {disp_path}")
|
||||
print(f"[run] 启动动画需要先运行抽帧(step_sample: 1),或手动保留 output/display.txt")
|
||||
print(f"[run] 错误: 找不到 display.npz 或 display.txt")
|
||||
print(f"[run] 启动动画需要先运行模拟(step_simulate: 1)")
|
||||
else:
|
||||
try:
|
||||
print("[run] 正在启动 VisPy 3D 动画窗口…")
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
# engines/c/Makefile
|
||||
# 跨平台编译:make → 本地系统编译
|
||||
# make linux → Linux 交叉编译(需 x86_64-linux-gnu-gcc)
|
||||
# make windows → Windows 交叉编译(需 x86_64-w64-mingw32-gcc)
|
||||
# make macos → macOS 交叉编译(需 osxcross 工具链)
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -O3 -march=native -Wall -Wextra
|
||||
LDFLAGS = -lm
|
||||
SRCS = main.c
|
||||
|
||||
# 自动检测系统
|
||||
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
|
||||
|
||||
# 目标文件名:统一使用 .exe 后缀(方便 Python 跨平台调用)
|
||||
TARGET = build/dynamics_c.exe
|
||||
|
||||
# ── 本地编译 ─────────────────────────────────
|
||||
.PHONY: all clean linux windows macos
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(SRCS) | build
|
||||
$(CC) $(CFLAGS) -o $@ $(SRCS) $(LDFLAGS)
|
||||
@echo " === C engine built: $@ ==="
|
||||
|
||||
build:
|
||||
mkdir -p build
|
||||
|
||||
# ── 交叉编译 ─────────────────────────────────
|
||||
# Linux → Linux (x86_64)
|
||||
linux: CROSS_PREFIX = x86_64-linux-gnu-
|
||||
linux: CC = $(CROSS_PREFIX)gcc
|
||||
linux: CFLAGS = -O3 -march=x86-64 -Wall -Wextra
|
||||
linux: $(SRCS) | build
|
||||
$(CC) $(CFLAGS) -o build/dynamics_c_linux.exe $(SRCS) $(LDFLAGS)
|
||||
@echo " === Linux binary: build/dynamics_c_linux.exe ==="
|
||||
|
||||
# 任意平台 → Windows (x86_64)
|
||||
# 需要安装 MinGW 交叉编译器:
|
||||
# apt install mingw-w64 (Debian/Ubuntu)
|
||||
# brew install mingw-w64 (macOS)
|
||||
windows: CROSS_PREFIX = x86_64-w64-mingw32-
|
||||
windows: CC = $(CROSS_PREFIX)gcc
|
||||
windows: CFLAGS = -O3 -march=x86-64 -Wall -Wextra
|
||||
windows: $(SRCS) | build
|
||||
$(CC) $(CFLAGS) -o build/dynamics_c_win.exe $(SRCS) $(LDFLAGS)
|
||||
@echo " === Windows binary: build/dynamics_c_win.exe ==="
|
||||
|
||||
# 任意平台 → macOS (x86_64)
|
||||
# 需要安装 osxcross 工具链
|
||||
macos: CROSS_PREFIX = x86_64-apple-darwin-
|
||||
macos: CC = $(CROSS_PREFIX)gcc
|
||||
macos: CFLAGS = -O3 -march=x86-64 -Wall -Wextra
|
||||
macos: $(SRCS) | build
|
||||
$(CC) $(CFLAGS) -o build/dynamics_c_mac.exe $(SRCS) $(LDFLAGS)
|
||||
@echo " === macOS binary: build/dynamics_c_mac.exe ==="
|
||||
|
||||
# ── 编译所有平台 ──────────────────────────────
|
||||
all-platforms: linux windows macos
|
||||
|
||||
clean:
|
||||
rm -rf build *.o
|
||||
-1114
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,426 @@
|
||||
"""
|
||||
engines/engine_dll.py
|
||||
---------------------
|
||||
Python ctypes 包装器:加载 C/C++/Fortran 动态链接库并调用 run_dynamics()。
|
||||
|
||||
用法(由 compute.py 内部调用,不直接运行):
|
||||
|
||||
from engines.engine_dll import load_dll, run_dynamics_dll
|
||||
|
||||
dll = load_dll("c") # 自动查找 engines/c/build/dynamics_c.dll/.so/.dylib
|
||||
arrays = run_dynamics_dll(dll, config, atom_data, bond_data, driver_data)
|
||||
# arrays: dict with keys x, y, z, vx, vy, vz shape=(n_frames, n_atoms)
|
||||
|
||||
DLL 编译(C 版本):
|
||||
Windows: gcc -O3 -shared -o engines/release/dynamics_c.dll engines/src/c/dynamics_lib.c -lm
|
||||
Linux: gcc -O3 -shared -fPIC -o engines/release/dynamics_c.so engines/src/c/dynamics_lib.c -lm
|
||||
macOS: gcc -O3 -dynamiclib -o engines/release/dynamics_c.dylib engines/src/c/dynamics_lib.c -lm
|
||||
或用 make dll 一键编译:
|
||||
cd engines/src/c && make dll
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import os
|
||||
import platform
|
||||
import numpy as np
|
||||
|
||||
# ── DLL 文件名后缀 ─────────────────────────────────────────────
|
||||
_SUFFIX = {
|
||||
"windows": ".dll",
|
||||
"linux": ".so",
|
||||
"darwin": ".dylib",
|
||||
}
|
||||
|
||||
# ── method 字符串 → 整数 ID ────────────────────────────────────
|
||||
_METHOD_ID = {
|
||||
"explicit_euler": 0,
|
||||
"euler": 0,
|
||||
"implicit_euler": 1,
|
||||
"midpoint": 2,
|
||||
"leapfrog": 3,
|
||||
}
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
_DLL_NAME = {
|
||||
"c": "dynamics_c",
|
||||
"cpp": "dynamics_cpp",
|
||||
"c++": "dynamics_cpp",
|
||||
"fortran": "dynamics_f90",
|
||||
"f90": "dynamics_f90",
|
||||
# "python" 引擎通过直接 import 调用,不使用 DLL
|
||||
}
|
||||
|
||||
# 引擎名规范化:将别名统一为目录名
|
||||
_ENGINE_DIR = {
|
||||
"c": "c",
|
||||
"cpp": "cpp",
|
||||
"c++": "cpp",
|
||||
"fortran": "fortran",
|
||||
"f90": "fortran",
|
||||
"python": "python",
|
||||
}
|
||||
|
||||
|
||||
def _dll_candidates(engine: str) -> list[str]:
|
||||
"""返回 DLL 候选路径列表(按优先级)。"""
|
||||
sys = platform.system().lower()
|
||||
ext = _SUFFIX.get(sys, ".so")
|
||||
eng_dir = _ENGINE_DIR.get(engine, engine)
|
||||
name = _DLL_NAME.get(engine, f"dynamics_{engine}")
|
||||
base = os.path.join(_HERE, "release", name)
|
||||
return [
|
||||
base + ext,
|
||||
base + ".dll",
|
||||
base + ".so",
|
||||
base + ".dylib",
|
||||
]
|
||||
|
||||
|
||||
def load_dll(engine: str = "c"):
|
||||
"""加载指定引擎。
|
||||
|
||||
- C/C++/Fortran: 返回 ctypes.CDLL 对象
|
||||
- Python: 返回模块对象(直接 import,无需编译)
|
||||
|
||||
Args:
|
||||
engine: "c", "cpp", "fortran", 或 "python"
|
||||
Raises:
|
||||
FileNotFoundError: DLL/模块文件不存在
|
||||
"""
|
||||
if _ENGINE_DIR.get(engine, engine) == "python":
|
||||
import importlib.util, sys as _sys
|
||||
mod_path = os.path.join(_HERE, "python", "dynamics_lib.py")
|
||||
if not os.path.exists(mod_path):
|
||||
raise FileNotFoundError(f"Python 引擎未找到: {mod_path}")
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"engines.python.dynamics_lib", mod_path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod # 返回模块,不是 CDLL
|
||||
|
||||
for p in _dll_candidates(engine):
|
||||
if os.path.exists(p):
|
||||
lib = ctypes.CDLL(p)
|
||||
_setup_prototype(lib)
|
||||
return lib
|
||||
raise FileNotFoundError(
|
||||
f"DLL 未找到(引擎 {engine}),候选路径:\n" +
|
||||
"\n".join(f" {p}" for p in _dll_candidates(engine)) +
|
||||
f"\n请先编译:cd engines/{engine} && make dll"
|
||||
)
|
||||
|
||||
|
||||
def _setup_prototype(lib: ctypes.CDLL) -> None:
|
||||
"""配置 run_dynamics 的参数类型和返回类型。"""
|
||||
c_dbl_p = ctypes.POINTER(ctypes.c_double)
|
||||
c_int_p = ctypes.POINTER(ctypes.c_int)
|
||||
cb_type = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_int)
|
||||
|
||||
lib.run_dynamics.restype = ctypes.c_int
|
||||
lib.run_dynamics.argtypes = [
|
||||
ctypes.c_int, # n_atoms
|
||||
c_dbl_p, # pos_init [n_atoms*3]
|
||||
c_dbl_p, # vel_init [n_atoms*3]
|
||||
c_dbl_p, # masses [n_atoms]
|
||||
c_int_p, # fixed [n_atoms*3]
|
||||
ctypes.c_int, # n_bonds
|
||||
c_int_p, # bond_pairs [n_bonds*2]
|
||||
c_dbl_p, # bond_k [n_bonds]
|
||||
c_dbl_p, # bond_r0 [n_bonds]
|
||||
ctypes.c_double, # box_a
|
||||
ctypes.c_double, # dt
|
||||
ctypes.c_int, # NT
|
||||
ctypes.c_int, # NSTEP
|
||||
ctypes.c_int, # warmup_steps
|
||||
ctypes.c_int, # method_id
|
||||
ctypes.c_double, # Gx
|
||||
ctypes.c_double, # Gy
|
||||
ctypes.c_double, # Gz
|
||||
ctypes.c_double, # Bx
|
||||
ctypes.c_double, # By
|
||||
ctypes.c_double, # Bz
|
||||
ctypes.c_int, # gravity_field
|
||||
ctypes.c_int, # elastic_force
|
||||
ctypes.c_int, # damping_force
|
||||
ctypes.c_double, # gravity_strength
|
||||
ctypes.c_int, # n_drivers
|
||||
c_int_p, # drv_idx [n_drivers]
|
||||
c_dbl_p, # drv_amp [n_drivers*3]
|
||||
c_dbl_p, # drv_freq [n_drivers*3]
|
||||
c_dbl_p, # drv_phi [n_drivers*3]
|
||||
c_dbl_p, # drv_eq [n_drivers*3]
|
||||
c_dbl_p, # drv_ncycles [n_drivers]
|
||||
c_int_p, # drv_has_period [n_drivers]
|
||||
ctypes.c_int, # n_frames
|
||||
c_dbl_p, # out_x
|
||||
c_dbl_p, # out_y
|
||||
c_dbl_p, # out_z
|
||||
c_dbl_p, # out_vx
|
||||
c_dbl_p, # out_vy
|
||||
c_dbl_p, # out_vz
|
||||
cb_type, # progress_cb (可为 NULL)
|
||||
]
|
||||
|
||||
|
||||
def _c_dbl(arr: np.ndarray):
|
||||
"""返回 float64 C 连续数组的 ctypes 指针。"""
|
||||
a = np.ascontiguousarray(arr, dtype=np.float64)
|
||||
return a.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), a
|
||||
|
||||
|
||||
def _c_int(arr: np.ndarray):
|
||||
"""返回 int32 C 连续数组的 ctypes 指针。"""
|
||||
a = np.ascontiguousarray(arr, dtype=np.int32)
|
||||
return a.ctypes.data_as(ctypes.POINTER(ctypes.c_int)), a
|
||||
|
||||
|
||||
def _is_python_module(lib) -> bool:
|
||||
"""判断 lib 是否为 Python 引擎模块(而非 ctypes.CDLL)。"""
|
||||
return not isinstance(lib, ctypes.CDLL)
|
||||
|
||||
|
||||
def _run_dynamics_python(lib, config, atom_positions, atom_velocities, atom_masses,
|
||||
atom_fixed, bond_pairs, bond_stiffness, bond_rest_lengths,
|
||||
driver_data, atom_ids, progress_cb=None) -> dict:
|
||||
"""调用 Python 引擎的 run_dynamics(),参数/返回值格式与 ctypes 版相同。"""
|
||||
n = len(atom_masses)
|
||||
NT = int(config["NT"])
|
||||
NSTEP = int(config.get("NSTEP", 1))
|
||||
warmup = int(config.get("warmup_steps", 0))
|
||||
dt = float(config["DT"])
|
||||
box_a = float(config.get("box_a", 300.0))
|
||||
method_str = str(config.get("method", "leapfrog")).lower().replace(" ", "_")
|
||||
method_id = _METHOD_ID.get(method_str, 3)
|
||||
|
||||
G = config.get("G", [0.0, 0.0, 0.0])
|
||||
B = config.get("B", [0.0, 0.0, 0.0])
|
||||
if hasattr(G, "tolist"): G = G.tolist()
|
||||
if hasattr(B, "tolist"): B = B.tolist()
|
||||
|
||||
gravity_field = int(config.get("gravity_field", 0))
|
||||
elastic_force = int(config.get("elastic_force", 1))
|
||||
damping_force = int(config.get("damping_force", 0))
|
||||
gravity_strength = float(config.get("gravity_strength", 1.0))
|
||||
|
||||
record_steps = NT - warmup
|
||||
n_frames = max(1, record_steps // NSTEP)
|
||||
|
||||
nd = len(driver_data) if driver_data else 0
|
||||
if nd > 0:
|
||||
drv_idx = np.array([d["local_idx"] for d in driver_data], dtype=np.int64)
|
||||
drv_amp = np.array([d["amp"] for d in driver_data], dtype=np.float64)
|
||||
drv_freq = np.array([d["freq"] for d in driver_data], dtype=np.float64)
|
||||
drv_phi = np.array([d["phi"] for d in driver_data], dtype=np.float64)
|
||||
drv_eq = np.array([d["eq_pos"] for d in driver_data], dtype=np.float64)
|
||||
drv_nc = np.array([d["n_cycles"] for d in driver_data], dtype=np.float64)
|
||||
drv_hp = np.array([d["has_period"]for d in driver_data], dtype=np.int32)
|
||||
else:
|
||||
drv_idx = drv_amp = drv_freq = drv_phi = drv_eq = drv_nc = drv_hp = \
|
||||
np.zeros(0, dtype=np.int64)
|
||||
|
||||
out_x, out_y, out_z, out_vx, out_vy, out_vz = lib.run_dynamics(
|
||||
n_atoms=n,
|
||||
pos_init=atom_positions,
|
||||
vel_init=atom_velocities,
|
||||
masses=atom_masses,
|
||||
fixed=atom_fixed,
|
||||
n_bonds=len(bond_pairs),
|
||||
bond_pairs=bond_pairs,
|
||||
bond_k=bond_stiffness,
|
||||
bond_r0=bond_rest_lengths,
|
||||
box_a=box_a,
|
||||
dt=dt,
|
||||
NT=NT,
|
||||
NSTEP=NSTEP,
|
||||
warmup_steps=warmup,
|
||||
method_id=method_id,
|
||||
Gx=float(G[0]), Gy=float(G[1]), Gz=float(G[2]),
|
||||
Bx=float(B[0]), By=float(B[1]), Bz=float(B[2]),
|
||||
gravity_field=gravity_field,
|
||||
elastic_force=elastic_force,
|
||||
damping_force=damping_force,
|
||||
gravity_strength=gravity_strength,
|
||||
n_drivers=nd,
|
||||
drv_idx=drv_idx,
|
||||
drv_amp=drv_amp,
|
||||
drv_freq=drv_freq,
|
||||
drv_phi=drv_phi,
|
||||
drv_eq=drv_eq,
|
||||
drv_ncycles=drv_nc,
|
||||
drv_has_period=drv_hp,
|
||||
n_frames=n_frames,
|
||||
progress_cb=progress_cb,
|
||||
)
|
||||
|
||||
shape = (n_frames, n)
|
||||
t_arr = np.arange(n_frames) * NSTEP * dt + warmup * dt
|
||||
return {
|
||||
"x": out_x.reshape(shape), "y": out_y.reshape(shape),
|
||||
"z": out_z.reshape(shape), "vx": out_vx.reshape(shape),
|
||||
"vy": out_vy.reshape(shape), "vz": out_vz.reshape(shape),
|
||||
"t": t_arr,
|
||||
}
|
||||
|
||||
|
||||
def run_dynamics_dll(
|
||||
lib,
|
||||
config: dict,
|
||||
atom_positions: np.ndarray, # (n_atoms, 3)
|
||||
atom_velocities: np.ndarray, # (n_atoms, 3)
|
||||
atom_masses: np.ndarray, # (n_atoms,)
|
||||
atom_fixed: np.ndarray, # (n_atoms, 3) int, 1=固定
|
||||
bond_pairs: np.ndarray, # (n_bonds, 2) int 0-based 局部索引
|
||||
bond_stiffness: np.ndarray, # (n_bonds,)
|
||||
bond_rest_lengths: np.ndarray,# (n_bonds,)
|
||||
driver_data: list, # 驱动原子列表(见下文)
|
||||
atom_ids: np.ndarray, # (n_atoms,) 全局 atom id(用于驱动原子查找)
|
||||
progress_cb=None,
|
||||
) -> dict:
|
||||
"""调用 DLL 的 run_dynamics(),返回抽帧后的轨迹数组。
|
||||
|
||||
driver_data 格式(每个元素对应一个驱动原子):
|
||||
{
|
||||
"atom_id": int, # 全局 atom id
|
||||
"local_idx": int, # 在 atom_ids 数组中的位置(0-based)
|
||||
"amp": [ax, ay, az],
|
||||
"freq": [fx, fy, fz],
|
||||
"phi": [px, py, pz],
|
||||
"eq_pos": [ex, ey, ez],
|
||||
"n_cycles": float, # 0=不限
|
||||
"has_period": int, # 0/1
|
||||
}
|
||||
|
||||
返回:
|
||||
{
|
||||
"x": np.ndarray (n_frames, n_atoms),
|
||||
"y": ...,
|
||||
"z": ...,
|
||||
"vx": ..., "vy": ..., "vz": ...,
|
||||
"t": np.ndarray (n_frames,), # 时间轴
|
||||
}
|
||||
"""
|
||||
# Python 引擎:直接调用模块函数,不走 ctypes
|
||||
if _is_python_module(lib):
|
||||
return _run_dynamics_python(
|
||||
lib, config, atom_positions, atom_velocities, atom_masses,
|
||||
atom_fixed, bond_pairs, bond_stiffness, bond_rest_lengths,
|
||||
driver_data, atom_ids, progress_cb)
|
||||
|
||||
n = len(atom_masses)
|
||||
NT = int(config["NT"])
|
||||
NSTEP = int(config.get("NSTEP", 1))
|
||||
warmup = int(config.get("warmup_steps", 0))
|
||||
dt = float(config["DT"])
|
||||
box_a = float(config.get("box_a", 300.0))
|
||||
method_str = str(config.get("method", "leapfrog")).lower().replace(" ", "_")
|
||||
method_id = _METHOD_ID.get(method_str, 3)
|
||||
|
||||
G = config.get("G", [0.0, 0.0, 0.0])
|
||||
B = config.get("B", [0.0, 0.0, 0.0])
|
||||
if hasattr(G, "tolist"): G = G.tolist()
|
||||
if hasattr(B, "tolist"): B = B.tolist()
|
||||
|
||||
gravity_field = int(config.get("gravity_field", 0))
|
||||
elastic_force = int(config.get("elastic_force", 1))
|
||||
damping_force = int(config.get("damping_force", 0))
|
||||
gravity_strength = float(config.get("gravity_strength", 1.0))
|
||||
|
||||
# ── 计算帧数 ──────────────────────────────────────────────
|
||||
record_steps = NT - warmup
|
||||
n_frames = max(1, record_steps // NSTEP)
|
||||
|
||||
# ── 驱动原子数据 ──────────────────────────────────────────
|
||||
nd = len(driver_data) if driver_data else 0
|
||||
if nd > 0:
|
||||
drv_idx_arr = np.array([d["local_idx"] for d in driver_data], dtype=np.int32)
|
||||
drv_amp_arr = np.array([d["amp"] for d in driver_data], dtype=np.float64).ravel()
|
||||
drv_freq_arr = np.array([d["freq"] for d in driver_data], dtype=np.float64).ravel()
|
||||
drv_phi_arr = np.array([d["phi"] for d in driver_data], dtype=np.float64).ravel()
|
||||
drv_eq_arr = np.array([d["eq_pos"] for d in driver_data], dtype=np.float64).ravel()
|
||||
drv_nc_arr = np.array([d["n_cycles"] for d in driver_data], dtype=np.float64)
|
||||
drv_hp_arr = np.array([d["has_period"] for d in driver_data], dtype=np.int32)
|
||||
else:
|
||||
drv_idx_arr = np.zeros(1, dtype=np.int32)
|
||||
drv_amp_arr = np.zeros(3, dtype=np.float64)
|
||||
drv_freq_arr = np.zeros(3, dtype=np.float64)
|
||||
drv_phi_arr = np.zeros(3, dtype=np.float64)
|
||||
drv_eq_arr = np.zeros(3, dtype=np.float64)
|
||||
drv_nc_arr = np.zeros(1, dtype=np.float64)
|
||||
drv_hp_arr = np.zeros(1, dtype=np.int32)
|
||||
|
||||
# ── 输出缓冲区 ────────────────────────────────────────────
|
||||
out_x = np.zeros(n_frames * n, dtype=np.float64)
|
||||
out_y = np.zeros(n_frames * n, dtype=np.float64)
|
||||
out_z = np.zeros(n_frames * n, dtype=np.float64)
|
||||
out_vx = np.zeros(n_frames * n, dtype=np.float64)
|
||||
out_vy = np.zeros(n_frames * n, dtype=np.float64)
|
||||
out_vz = np.zeros(n_frames * n, dtype=np.float64)
|
||||
|
||||
# ── ctypes 指针(保留 arr 引用防止 GC) ──────────────────
|
||||
p_pos, _pos = _c_dbl(atom_positions.ravel())
|
||||
p_vel, _vel = _c_dbl(atom_velocities.ravel())
|
||||
p_mass, _mass = _c_dbl(atom_masses)
|
||||
p_fixed, _fixed = _c_int(atom_fixed.ravel())
|
||||
p_bp, _bp = _c_int(bond_pairs.ravel() if len(bond_pairs) else np.zeros(2, dtype=np.int32))
|
||||
p_bk, _bk = _c_dbl(bond_stiffness if len(bond_stiffness) else np.zeros(1))
|
||||
p_br0, _br0 = _c_dbl(bond_rest_lengths if len(bond_rest_lengths) else np.zeros(1))
|
||||
p_didx, _didx = _c_int(drv_idx_arr)
|
||||
p_damp, _damp = _c_dbl(drv_amp_arr)
|
||||
p_dfrq, _dfrq = _c_dbl(drv_freq_arr)
|
||||
p_dphi, _dphi = _c_dbl(drv_phi_arr)
|
||||
p_deq, _deq = _c_dbl(drv_eq_arr)
|
||||
p_dnc, _dnc = _c_dbl(drv_nc_arr)
|
||||
p_dhp, _dhp = _c_int(drv_hp_arr)
|
||||
|
||||
p_ox = out_x.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||
p_oy = out_y.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||
p_oz = out_z.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||
p_ovx = out_vx.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||
p_ovy = out_vy.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||
p_ovz = out_vz.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||
|
||||
# 进度回调
|
||||
cb_type = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_int)
|
||||
if progress_cb is not None:
|
||||
cb = cb_type(progress_cb)
|
||||
else:
|
||||
cb = ctypes.cast(None, cb_type)
|
||||
|
||||
ret = lib.run_dynamics(
|
||||
n,
|
||||
p_pos, p_vel, p_mass, p_fixed,
|
||||
len(bond_pairs), p_bp, p_bk, p_br0,
|
||||
box_a, dt, NT, NSTEP, warmup, method_id,
|
||||
float(G[0]), float(G[1]), float(G[2]),
|
||||
float(B[0]), float(B[1]), float(B[2]),
|
||||
gravity_field, elastic_force, damping_force, gravity_strength,
|
||||
nd, p_didx, p_damp, p_dfrq, p_dphi, p_deq, p_dnc, p_dhp,
|
||||
n_frames,
|
||||
p_ox, p_oy, p_oz, p_ovx, p_ovy, p_ovz,
|
||||
cb,
|
||||
)
|
||||
|
||||
if ret != 0:
|
||||
raise RuntimeError(f"run_dynamics() returned error code {ret}")
|
||||
|
||||
shape = (n_frames, n)
|
||||
t_arr = np.arange(n_frames) * NSTEP * dt + warmup * dt
|
||||
|
||||
return {
|
||||
"x": out_x.reshape(shape),
|
||||
"y": out_y.reshape(shape),
|
||||
"z": out_z.reshape(shape),
|
||||
"vx": out_vx.reshape(shape),
|
||||
"vy": out_vy.reshape(shape),
|
||||
"vz": out_vz.reshape(shape),
|
||||
"t": t_arr,
|
||||
}
|
||||
|
||||
|
||||
def is_dll_available(engine: str = "c") -> bool:
|
||||
"""检查指定引擎是否可用(DLL 已编译或 Python 模块存在)。"""
|
||||
if _ENGINE_DIR.get(engine, engine) == "python":
|
||||
return os.path.exists(os.path.join(_HERE, "python", "dynamics_lib.py"))
|
||||
return any(os.path.exists(p) for p in _dll_candidates(engine))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,404 @@
|
||||
"""
|
||||
engines/python/dynamics_lib.py
|
||||
-------------------------------
|
||||
纯 NumPy 计算引擎:无文件 I/O,所有数据以 NumPy 数组传入,
|
||||
结果作为 NumPy 数组返回。
|
||||
|
||||
接口与 C/C++/Fortran DLL 的 run_dynamics() 完全一致,
|
||||
算法与 compute.py 的 run_simulation() 保持一致。
|
||||
|
||||
用法(由 engine_dll.py 内部调用):
|
||||
from engines.python.dynamics_lib import run_dynamics
|
||||
out_x, out_y, out_z, out_vx, out_vy, out_vz = run_dynamics(...)
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
TWO_PI = 2.0 * np.pi
|
||||
|
||||
# ── method_id 映射 ──────────────────────────────────────────
|
||||
# 0=euler 1=implicit_euler 2=midpoint 3=leapfrog
|
||||
|
||||
|
||||
# ── 保守加速度(弹簧键 + 均匀重力场,不含阻尼)──────────────
|
||||
def _accel_conservative(x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
bond_pairs, bond_k, bond_r0):
|
||||
ax = np.full_like(x, Gx) if gravity_field else np.zeros_like(x)
|
||||
ay = np.full_like(y, Gy) if gravity_field else np.zeros_like(y)
|
||||
az = np.full_like(z, Gz) if gravity_field else np.zeros_like(z)
|
||||
|
||||
if elastic_force and len(bond_pairs) > 0:
|
||||
i1 = bond_pairs[:, 0]
|
||||
i2 = bond_pairs[:, 1]
|
||||
dx = x[i2] - x[i1]
|
||||
dy = y[i2] - y[i1]
|
||||
dz = z[i2] - z[i1]
|
||||
dist = np.sqrt(dx*dx + dy*dy + dz*dz)
|
||||
valid = dist > 1e-12
|
||||
fac = np.where(valid, bond_k * (dist - bond_r0) / dist, 0.0)
|
||||
fx = fac * dx
|
||||
fy = fac * dy
|
||||
fz_b = fac * dz
|
||||
np.add.at(ax, i1, fx / m[i1]); np.add.at(ax, i2, -fx / m[i2])
|
||||
np.add.at(ay, i1, fy / m[i1]); np.add.at(ay, i2, -fy / m[i2])
|
||||
np.add.at(az, i1, fz_b / m[i1]); np.add.at(az, i2, -fz_b / m[i2])
|
||||
|
||||
return ax, ay, az
|
||||
|
||||
|
||||
# ── 完整加速度(含阻尼)──────────────────────────────────────
|
||||
def _accel_full(x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0):
|
||||
ax, ay, az = _accel_conservative(x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
if damping_force:
|
||||
ax -= Bx * vx / m
|
||||
ay -= By * vy / m
|
||||
az -= Bz * vz / m
|
||||
return ax, ay, az
|
||||
|
||||
|
||||
# ── 蛙跳法(半隐式阻尼,与 compute.py leapfrog_staggered_step 一致)─
|
||||
def _leapfrog_step(x, y, z, vx, vy, vz, fixed, m,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt):
|
||||
ax, ay, az = _accel_conservative(x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
has_damp = damping_force and (Bx != 0.0 or By != 0.0 or Bz != 0.0)
|
||||
if has_damp:
|
||||
alpha_x = Bx * dt / (2.0 * m)
|
||||
alpha_y = By * dt / (2.0 * m)
|
||||
alpha_z = Bz * dt / (2.0 * m)
|
||||
vx_new = (vx * (1.0 - alpha_x) + ax * dt) / (1.0 + alpha_x)
|
||||
vy_new = (vy * (1.0 - alpha_y) + ay * dt) / (1.0 + alpha_y)
|
||||
vz_new = (vz * (1.0 - alpha_z) + az * dt) / (1.0 + alpha_z)
|
||||
else:
|
||||
vx_new = vx + ax * dt
|
||||
vy_new = vy + ay * dt
|
||||
vz_new = vz + az * dt
|
||||
# 全固定原子保持不变
|
||||
all_fixed = np.all(fixed, axis=1)
|
||||
vx_new = np.where(all_fixed, vx, vx_new)
|
||||
vy_new = np.where(all_fixed, vy, vy_new)
|
||||
vz_new = np.where(all_fixed, vz, vz_new)
|
||||
x_new = x + vx_new * dt
|
||||
y_new = y + vy_new * dt
|
||||
z_new = z + vz_new * dt
|
||||
return x_new, y_new, z_new, vx_new, vy_new, vz_new
|
||||
|
||||
|
||||
# ── 显式欧拉法 ───────────────────────────────────────────────
|
||||
def _euler_step(x, y, z, vx, vy, vz, fixed, m,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt):
|
||||
ax, ay, az = _accel_full(x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
all_fixed = np.all(fixed, axis=1)
|
||||
mask = ~all_fixed
|
||||
x_new = np.where(mask, x + vx * dt, x)
|
||||
y_new = np.where(mask, y + vy * dt, y)
|
||||
z_new = np.where(mask, z + vz * dt, z)
|
||||
vx_new = np.where(mask, vx + ax * dt, vx)
|
||||
vy_new = np.where(mask, vy + ay * dt, vy)
|
||||
vz_new = np.where(mask, vz + az * dt, vz)
|
||||
return x_new, y_new, z_new, vx_new, vy_new, vz_new
|
||||
|
||||
|
||||
# ── 隐式欧拉法(与 compute.py Implicit_Euler_Method 一致)──────
|
||||
def _implicit_euler_step(x, y, z, vx, vy, vz, fixed, m,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt):
|
||||
gamma_x = Bx / m
|
||||
gamma_y = By / m
|
||||
gamma_z = Bz / m
|
||||
vx_next = (vx + Gx * dt) / (1.0 + gamma_x * dt)
|
||||
vy_next = (vy + Gy * dt) / (1.0 + gamma_y * dt)
|
||||
vz_next = (vz + Gz * dt) / (1.0 + gamma_z * dt)
|
||||
ax, ay, az = _accel_full(x, y, z, vx_next, vy_next, vz_next, m,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
all_fixed = np.all(fixed, axis=1)
|
||||
mask = ~all_fixed
|
||||
vx_new = np.where(mask, vx + ax * dt, vx)
|
||||
vy_new = np.where(mask, vy + ay * dt, vy)
|
||||
vz_new = np.where(mask, vz + az * dt, vz)
|
||||
x_new = np.where(mask, x + vx_new * dt, x)
|
||||
y_new = np.where(mask, y + vy_new * dt, y)
|
||||
z_new = np.where(mask, z + vz_new * dt, z)
|
||||
return x_new, y_new, z_new, vx_new, vy_new, vz_new
|
||||
|
||||
|
||||
# ── 中点法(与 compute.py Midpoint_Method 一致)────────────────
|
||||
def _midpoint_step(x, y, z, vx, vy, vz, fixed, m,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt):
|
||||
ax, ay, az = _accel_full(x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
all_fixed = np.all(fixed, axis=1)
|
||||
mask = ~all_fixed
|
||||
xm = np.where(mask, x + 0.5*vx*dt, x)
|
||||
ym = np.where(mask, y + 0.5*vy*dt, y)
|
||||
zm = np.where(mask, z + 0.5*vz*dt, z)
|
||||
vxm = np.where(mask, vx + 0.5*ax*dt, 0.0)
|
||||
vym = np.where(mask, vy + 0.5*ay*dt, 0.0)
|
||||
vzm = np.where(mask, vz + 0.5*az*dt, 0.0)
|
||||
x_new = np.where(mask, x + vxm * dt, x)
|
||||
y_new = np.where(mask, y + vym * dt, y)
|
||||
z_new = np.where(mask, z + vzm * dt, z)
|
||||
axm, aym, azm = _accel_full(xm, ym, zm, vxm, vym, vzm, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
vx_new = np.where(mask, vx + axm * dt, vx)
|
||||
vy_new = np.where(mask, vy + aym * dt, vy)
|
||||
vz_new = np.where(mask, vz + azm * dt, vz)
|
||||
return x_new, y_new, z_new, vx_new, vy_new, vz_new
|
||||
|
||||
|
||||
# ── 边界:反弹 + 回绕 + 逐自由度固定约束 ───────────────────────
|
||||
def _apply_bc(x, y, z, vx, vy, vz, fixed, pos_init, box_a):
|
||||
lo, hi = -box_a, box_a
|
||||
|
||||
# 反弹(全固定原子跳过)
|
||||
all_fixed = np.all(fixed, axis=1)
|
||||
do_bc = ~all_fixed
|
||||
over_x = do_bc & (x > hi); under_x = do_bc & (x < lo)
|
||||
over_y = do_bc & (y > hi); under_y = do_bc & (y < lo)
|
||||
over_z = do_bc & (z > hi); under_z = do_bc & (z < lo)
|
||||
x = np.where(over_x, hi, np.where(under_x, lo, x))
|
||||
y = np.where(over_y, hi, np.where(under_y, lo, y))
|
||||
z = np.where(over_z, hi, np.where(under_z, lo, z))
|
||||
vx = np.where(over_x | under_x, -np.abs(vx)*np.sign(np.where(over_x, 1, -1)), vx)
|
||||
vy = np.where(over_y | under_y, -np.abs(vy)*np.sign(np.where(over_y, 1, -1)), vy)
|
||||
vz = np.where(over_z | under_z, -np.abs(vz)*np.sign(np.where(over_z, 1, -1)), vz)
|
||||
# 反弹速度简化:越界则取反绝对值(与 C 版 _limit1 一致)
|
||||
vx = np.where(over_x, -np.abs(vx), np.where(under_x, np.abs(vx), vx))
|
||||
vy = np.where(over_y, -np.abs(vy), np.where(under_y, np.abs(vy), vy))
|
||||
vz = np.where(over_z, -np.abs(vz), np.where(under_z, np.abs(vz), vz))
|
||||
|
||||
# 回绕
|
||||
x = np.where(x > hi, lo, np.where(x < lo, hi, x))
|
||||
y = np.where(y > hi, lo, np.where(y < lo, hi, y))
|
||||
z = np.where(z > hi, lo, np.where(z < lo, hi, z))
|
||||
|
||||
# 逐自由度固定约束
|
||||
fx = fixed[:, 0].astype(bool)
|
||||
fy = fixed[:, 1].astype(bool)
|
||||
fz = fixed[:, 2].astype(bool)
|
||||
x = np.where(fx, pos_init[:, 0], x); vx = np.where(fx, 0.0, vx)
|
||||
y = np.where(fy, pos_init[:, 1], y); vy = np.where(fy, 0.0, vy)
|
||||
z = np.where(fz, pos_init[:, 2], z); vz = np.where(fz, 0.0, vz)
|
||||
return x, y, z, vx, vy, vz
|
||||
|
||||
|
||||
# ── 驱动力(与 compute.py apply_driving_force 逻辑一致)─────────
|
||||
def _apply_driving(x, y, z, vx, vy, vz, t, step, dt,
|
||||
drv_idx, drv_amp, drv_freq, drv_phi, drv_eq,
|
||||
drv_ncycles, drv_has_period, freeze):
|
||||
"""freeze: (n_drivers, 3) mutable array for frozen positions."""
|
||||
nd = len(drv_idx)
|
||||
for d in range(nd):
|
||||
idx = drv_idx[d]
|
||||
fx_ = drv_freq[d, 0]; fy_ = drv_freq[d, 1]; fz_ = drv_freq[d, 2]
|
||||
|
||||
if drv_has_period[d]:
|
||||
mf = max(abs(fx_), abs(fy_), abs(fz_))
|
||||
ps = int(drv_ncycles[d] / mf / dt) if mf > 1e-12 else 0
|
||||
if step > ps:
|
||||
x[idx] = freeze[d, 0]; y[idx] = freeze[d, 1]; z[idx] = freeze[d, 2]
|
||||
vx[idx] = vy[idx] = vz[idx] = 0.0
|
||||
continue
|
||||
px = drv_eq[d,0] + drv_amp[d,0]*np.cos(TWO_PI*fx_*t + drv_phi[d,0])
|
||||
py = drv_eq[d,1] + drv_amp[d,1]*np.cos(TWO_PI*fy_*t + drv_phi[d,1])
|
||||
pz = drv_eq[d,2] + drv_amp[d,2]*np.cos(TWO_PI*fz_*t + drv_phi[d,2])
|
||||
if step == ps:
|
||||
freeze[d, 0] = px; freeze[d, 1] = py; freeze[d, 2] = pz
|
||||
|
||||
x[idx] = drv_eq[d,0] + drv_amp[d,0]*np.cos(TWO_PI*fx_*t + drv_phi[d,0])
|
||||
y[idx] = drv_eq[d,1] + drv_amp[d,1]*np.cos(TWO_PI*fy_*t + drv_phi[d,1])
|
||||
z[idx] = drv_eq[d,2] + drv_amp[d,2]*np.cos(TWO_PI*fz_*t + drv_phi[d,2])
|
||||
vx[idx] = -drv_amp[d,0]*TWO_PI*fx_*np.sin(TWO_PI*fx_*t + drv_phi[d,0])
|
||||
vy[idx] = -drv_amp[d,1]*TWO_PI*fy_*np.sin(TWO_PI*fy_*t + drv_phi[d,1])
|
||||
vz[idx] = -drv_amp[d,2]*TWO_PI*fz_*np.sin(TWO_PI*fz_*t + drv_phi[d,2])
|
||||
|
||||
|
||||
def _do_step(x, y, z, vx, vy, vz, fixed, masses, method_id,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt, pos_init, box_a):
|
||||
if method_id == 0:
|
||||
x, y, z, vx, vy, vz = _euler_step(
|
||||
x, y, z, vx, vy, vz, fixed, masses,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt)
|
||||
elif method_id == 1:
|
||||
x, y, z, vx, vy, vz = _implicit_euler_step(
|
||||
x, y, z, vx, vy, vz, fixed, masses,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt)
|
||||
elif method_id == 2:
|
||||
x, y, z, vx, vy, vz = _midpoint_step(
|
||||
x, y, z, vx, vy, vz, fixed, masses,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt)
|
||||
else:
|
||||
x, y, z, vx, vy, vz = _leapfrog_step(
|
||||
x, y, z, vx, vy, vz, fixed, masses,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt)
|
||||
x, y, z, vx, vy, vz = _apply_bc(x, y, z, vx, vy, vz, fixed, pos_init, box_a)
|
||||
return x, y, z, vx, vy, vz
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# 主函数:run_dynamics
|
||||
# 接口与 C/C++/Fortran DLL 的 run_dynamics() 对应,
|
||||
# 参数格式:numpy 数组(替代 ctypes 指针)。
|
||||
#
|
||||
# method_id: 0=euler 1=implicit_euler 2=midpoint 3=leapfrog
|
||||
# drv_amp/freq/phi/eq: (n_drivers, 3) float64
|
||||
# drv_ncycles: (n_drivers,) float64 0=不限
|
||||
# drv_has_period: (n_drivers,) int
|
||||
#
|
||||
# 返回:(out_x, out_y, out_z, out_vx, out_vy, out_vz)
|
||||
# 各 shape=(n_frames, n_atoms)
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
def run_dynamics(
|
||||
n_atoms, pos_init, vel_init, masses, fixed,
|
||||
n_bonds, bond_pairs, bond_k, bond_r0,
|
||||
box_a, dt,
|
||||
NT, NSTEP, warmup_steps, method_id,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force, gravity_strength,
|
||||
n_drivers, drv_idx, drv_amp, drv_freq, drv_phi, drv_eq,
|
||||
drv_ncycles, drv_has_period,
|
||||
n_frames,
|
||||
progress_cb=None,
|
||||
):
|
||||
"""运行动力学模拟,返回抽帧轨迹数组。
|
||||
|
||||
Args:
|
||||
pos_init: (n_atoms, 3) float64
|
||||
vel_init: (n_atoms, 3) float64
|
||||
masses: (n_atoms,) float64
|
||||
fixed: (n_atoms, 3) int — 1=固定
|
||||
bond_pairs: (n_bonds, 2) int — 0-based 局部索引
|
||||
bond_k: (n_bonds,) float64
|
||||
bond_r0: (n_bonds,) float64
|
||||
drv_idx: (n_drivers,) int — 0-based
|
||||
drv_amp/freq/phi/eq: (n_drivers, 3) float64
|
||||
drv_ncycles: (n_drivers,) float64
|
||||
drv_has_period: (n_drivers,) int
|
||||
n_frames: 预分配的输出帧数
|
||||
|
||||
Returns:
|
||||
out_x, out_y, out_z, out_vx, out_vy, out_vz — 各 (n_frames, n_atoms)
|
||||
"""
|
||||
pos_init = np.asarray(pos_init, dtype=np.float64)
|
||||
vel_init = np.asarray(vel_init, dtype=np.float64)
|
||||
masses = np.asarray(masses, dtype=np.float64)
|
||||
fixed = np.asarray(fixed, dtype=np.int32)
|
||||
bond_pairs = np.asarray(bond_pairs, dtype=np.int64).reshape(-1, 2) if n_bonds else np.zeros((0,2), dtype=np.int64)
|
||||
bond_k = np.asarray(bond_k, dtype=np.float64) if n_bonds else np.zeros(0)
|
||||
bond_r0 = np.asarray(bond_r0, dtype=np.float64) if n_bonds else np.zeros(0)
|
||||
|
||||
n = n_atoms
|
||||
x = pos_init[:, 0].copy()
|
||||
y = pos_init[:, 1].copy()
|
||||
z = pos_init[:, 2].copy()
|
||||
vx = vel_init[:, 0].copy()
|
||||
vy = vel_init[:, 1].copy()
|
||||
vz = vel_init[:, 2].copy()
|
||||
|
||||
# 驱动力数据(保证正确形状)
|
||||
nd = n_drivers
|
||||
if nd > 0:
|
||||
drv_idx = np.asarray(drv_idx, dtype=np.int64)
|
||||
drv_amp = np.asarray(drv_amp, dtype=np.float64).reshape(nd, 3)
|
||||
drv_freq = np.asarray(drv_freq, dtype=np.float64).reshape(nd, 3)
|
||||
drv_phi = np.asarray(drv_phi, dtype=np.float64).reshape(nd, 3)
|
||||
drv_eq = np.asarray(drv_eq, dtype=np.float64).reshape(nd, 3)
|
||||
drv_nc = np.asarray(drv_ncycles, dtype=np.float64)
|
||||
drv_hp = np.asarray(drv_has_period, dtype=np.int32)
|
||||
freeze = np.zeros((nd, 3), dtype=np.float64)
|
||||
else:
|
||||
drv_idx = drv_amp = drv_freq = drv_phi = drv_eq = drv_nc = drv_hp = freeze = None
|
||||
|
||||
def _drive(t_, step_):
|
||||
if nd > 0:
|
||||
_apply_driving(x, y, z, vx, vy, vz, t_, step_, dt,
|
||||
drv_idx, drv_amp, drv_freq, drv_phi, drv_eq,
|
||||
drv_nc, drv_hp, freeze)
|
||||
|
||||
# ── 蛙跳法:初始化 v(-dt/2) ─────────────────────────────
|
||||
if method_id == 3:
|
||||
ax0, ay0, az0 = _accel_conservative(x, y, z, masses, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
bond_pairs, bond_k, bond_r0)
|
||||
all_fixed = np.all(fixed, axis=1)
|
||||
vx = np.where(all_fixed, vx, vx - 0.5 * ax0 * dt)
|
||||
vy = np.where(all_fixed, vy, vy - 0.5 * ay0 * dt)
|
||||
vz = np.where(all_fixed, vz, vz - 0.5 * az0 * dt)
|
||||
|
||||
# ── 初始驱动 t=0 ─────────────────────────────────────────
|
||||
_drive(0.0, 0)
|
||||
|
||||
# ── 预热 ─────────────────────────────────────────────────
|
||||
for s in range(warmup_steps):
|
||||
tw = (s + 1) * dt
|
||||
_drive(tw, s)
|
||||
x, y, z, vx, vy, vz = _do_step(
|
||||
x, y, z, vx, vy, vz, fixed, masses, method_id,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt, pos_init, box_a)
|
||||
|
||||
# ── 记录循环 ─────────────────────────────────────────────
|
||||
record_steps = NT - warmup_steps
|
||||
prog_interval = max(1, record_steps // 100)
|
||||
|
||||
out_x = np.zeros((n_frames, n), dtype=np.float64)
|
||||
out_y = np.zeros((n_frames, n), dtype=np.float64)
|
||||
out_z = np.zeros((n_frames, n), dtype=np.float64)
|
||||
out_vx = np.zeros((n_frames, n), dtype=np.float64)
|
||||
out_vy = np.zeros((n_frames, n), dtype=np.float64)
|
||||
out_vz = np.zeros((n_frames, n), dtype=np.float64)
|
||||
frame_idx = 0
|
||||
|
||||
for s in range(record_steps):
|
||||
if progress_cb is not None and s % prog_interval == 0 and s > 0:
|
||||
progress_cb(s, record_steps)
|
||||
|
||||
t = (s + warmup_steps) * dt
|
||||
_drive(t, s)
|
||||
|
||||
if s % NSTEP == 0 and frame_idx < n_frames:
|
||||
out_x[frame_idx] = x
|
||||
out_y[frame_idx] = y
|
||||
out_z[frame_idx] = z
|
||||
out_vx[frame_idx] = vx
|
||||
out_vy[frame_idx] = vy
|
||||
out_vz[frame_idx] = vz
|
||||
frame_idx += 1
|
||||
|
||||
x, y, z, vx, vy, vz = _do_step(
|
||||
x, y, z, vx, vy, vz, fixed, masses, method_id,
|
||||
Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
bond_pairs, bond_k, bond_r0, dt, pos_init, box_a)
|
||||
|
||||
return out_x, out_y, out_z, out_vx, out_vy, out_vz
|
||||
@@ -0,0 +1,287 @@
|
||||
"""
|
||||
engines/python/main.py
|
||||
-----------------------
|
||||
独立 Python 计算引擎。
|
||||
|
||||
与 main.c / main.cpp / main.f90 结构一致:
|
||||
输入: <input_dir>/coord.txt, connection.txt, bond.txt, [driver.txt]
|
||||
<param_json> (同 engines/c/param.json 格式)
|
||||
输出: <output_dir>/display.txt (+ display.npz)
|
||||
<output_dir>/trajectory.txt (若 save_trajectory=1)
|
||||
|
||||
用法:
|
||||
python main.py <input_dir> <output_dir> <param_json>
|
||||
|
||||
内部调用 dynamics_lib.run_dynamics(),算法与 compute.py 完全一致。
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import numpy as np
|
||||
|
||||
# 将父目录(engines/python 的上级 engines)加入 sys.path,
|
||||
# 以便在独立运行时也能找到 dynamics_lib
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, _HERE)
|
||||
|
||||
from dynamics_lib import run_dynamics
|
||||
|
||||
# 为读取 coord/bond/display,复用 compute.py 中的 I/O 函数
|
||||
_COMPUTE = os.path.join(_HERE, "..", "..")
|
||||
sys.path.insert(0, _COMPUTE)
|
||||
import compute as _c
|
||||
|
||||
_METHOD_ID = {
|
||||
"explicit_euler": 0,
|
||||
"euler": 0,
|
||||
"implicit_euler": 1,
|
||||
"midpoint": 2,
|
||||
"leapfrog": 3,
|
||||
}
|
||||
|
||||
|
||||
def _load_params(param_path):
|
||||
"""读取 param.json(与 C 引擎格式相同)."""
|
||||
with open(param_path, "r", encoding="utf-8") as f:
|
||||
p = json.load(f)
|
||||
return p
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4:
|
||||
print("用法: python main.py <input_dir> <output_dir> <param_json>")
|
||||
sys.exit(1)
|
||||
|
||||
input_dir = sys.argv[1]
|
||||
output_dir = sys.argv[2]
|
||||
param_path = sys.argv[3]
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# ── 读取参数 ─────────────────────────────────────────────
|
||||
p = _load_params(param_path)
|
||||
box_a = float(p.get("box_a", 10.0))
|
||||
NT = int(p.get("NT", 10000))
|
||||
dt = float(p.get("DT", 0.001))
|
||||
NSTEP = int(p.get("NSTEP", 100))
|
||||
warmup_steps = int(p.get("warmup_steps", 0))
|
||||
method_str = str(p.get("method", "leapfrog")).lower().replace(" ", "_")
|
||||
method_id = _METHOD_ID.get(method_str, 3)
|
||||
G = p.get("G", [0.0, 0.0, -9.8])
|
||||
B = p.get("B", [0.0, 0.0, 0.0])
|
||||
gravity_field = int(p.get("gravity_field", 1))
|
||||
elastic_force = int(p.get("elastic_force", 1))
|
||||
damping_force = int(p.get("damping_force", 0))
|
||||
gravity_strength = float(p.get("gravity_strength", 1.0))
|
||||
driving_force = int(p.get("driving_force", 0))
|
||||
save_traj = int(p.get("save_trajectory", 0))
|
||||
|
||||
# ── 读取原子数据 ──────────────────────────────────────────
|
||||
coord_path = os.path.join(input_dir, "coord.txt")
|
||||
atom_ids, masses, radii, positions, velocities, fixed = _c.load_coord_file(coord_path)
|
||||
|
||||
# ── 读取键数据 ────────────────────────────────────────────
|
||||
conn_path = os.path.join(input_dir, "connection.txt")
|
||||
bond_path = os.path.join(input_dir, "bond.txt")
|
||||
bond_map = _c.load_bond_parameters(bond_path)
|
||||
bond_pairs, bond_names, bond_stiffness, bond_rest_lengths = \
|
||||
_c.load_bond_connections(conn_path, atom_ids, positions, bond_map)
|
||||
n_bonds = len(bond_pairs)
|
||||
|
||||
# ── 读取驱动力 ────────────────────────────────────────────
|
||||
drv_list = []
|
||||
if driving_force:
|
||||
driver_path = os.path.join(input_dir, "driver.txt")
|
||||
raw_drivers = _c.load_driver_file(driver_path, atom_ids)
|
||||
if raw_drivers:
|
||||
atom_id_map = {int(aid): i for i, aid in enumerate(atom_ids)}
|
||||
for d in raw_drivers:
|
||||
aid = int(d["atom_id"])
|
||||
if aid not in atom_id_map:
|
||||
continue
|
||||
lidx = atom_id_map[aid]
|
||||
eq = positions[lidx].tolist()
|
||||
d["eq_pos"] = np.array(eq)
|
||||
pc = d.get("period_cycles")
|
||||
nc = float(pc) if pc is not None else 0.0
|
||||
hp = 1 if nc > 0 else 0
|
||||
drv_list.append({
|
||||
"local_idx": lidx,
|
||||
"amp": d["amp"].tolist(),
|
||||
"freq": d["freq"].tolist(),
|
||||
"phi": d["phi"].tolist(), # radians
|
||||
"eq": eq,
|
||||
"nc": nc,
|
||||
"hp": hp,
|
||||
})
|
||||
|
||||
nd = len(drv_list)
|
||||
if nd > 0:
|
||||
drv_idx = np.array([d["local_idx"] for d in drv_list], dtype=np.int64)
|
||||
drv_amp = np.array([d["amp"] for d in drv_list], dtype=np.float64)
|
||||
drv_freq = np.array([d["freq"] for d in drv_list], dtype=np.float64)
|
||||
drv_phi = np.array([d["phi"] for d in drv_list], dtype=np.float64)
|
||||
drv_eq = np.array([d["eq"] for d in drv_list], dtype=np.float64)
|
||||
drv_nc = np.array([d["nc"] for d in drv_list], dtype=np.float64)
|
||||
drv_hp = np.array([d["hp"] for d in drv_list], dtype=np.int32)
|
||||
else:
|
||||
drv_idx = drv_amp = drv_freq = drv_phi = drv_eq = drv_nc = drv_hp = \
|
||||
np.zeros(0, dtype=np.int64)
|
||||
|
||||
# ── 计算帧数 ──────────────────────────────────────────────
|
||||
record_steps = NT - warmup_steps
|
||||
n_frames = max(1, record_steps // NSTEP)
|
||||
|
||||
# ── 进度回调 ──────────────────────────────────────────────
|
||||
def _progress(step, total):
|
||||
pct = step * 100 // total
|
||||
print(f"[python-engine] progress: {step}/{total} ({pct}%)", flush=True)
|
||||
|
||||
# ── 运行计算 ──────────────────────────────────────────────
|
||||
t0 = time.time()
|
||||
print(f"[python-engine] NT={NT} NSTEP={NSTEP} method={method_str} "
|
||||
f"n_atoms={len(atom_ids)} n_bonds={n_bonds}")
|
||||
|
||||
out_x, out_y, out_z, out_vx, out_vy, out_vz = run_dynamics(
|
||||
n_atoms=len(atom_ids),
|
||||
pos_init=positions,
|
||||
vel_init=velocities,
|
||||
masses=masses,
|
||||
fixed=fixed,
|
||||
n_bonds=n_bonds,
|
||||
bond_pairs=bond_pairs,
|
||||
bond_k=bond_stiffness,
|
||||
bond_r0=bond_rest_lengths,
|
||||
box_a=box_a,
|
||||
dt=dt,
|
||||
NT=NT,
|
||||
NSTEP=NSTEP,
|
||||
warmup_steps=warmup_steps,
|
||||
method_id=method_id,
|
||||
Gx=float(G[0]), Gy=float(G[1]), Gz=float(G[2]),
|
||||
Bx=float(B[0]), By=float(B[1]), Bz=float(B[2]),
|
||||
gravity_field=gravity_field,
|
||||
elastic_force=elastic_force,
|
||||
damping_force=damping_force,
|
||||
gravity_strength=gravity_strength,
|
||||
n_drivers=nd,
|
||||
drv_idx=drv_idx,
|
||||
drv_amp=drv_amp,
|
||||
drv_freq=drv_freq,
|
||||
drv_phi=drv_phi,
|
||||
drv_eq=drv_eq,
|
||||
drv_ncycles=drv_nc,
|
||||
drv_has_period=drv_hp,
|
||||
n_frames=n_frames,
|
||||
progress_cb=_progress,
|
||||
)
|
||||
elapsed = time.time() - t0
|
||||
print(f"[python-engine] 完成: {n_frames} 帧 {elapsed:.3f} s")
|
||||
|
||||
# ── 构建 display header ───────────────────────────────────
|
||||
ball_radius = float(p.get("ball_radius", 0.5))
|
||||
ball_color = p.get("ball_color", [0.9, 0.2, 0.2])
|
||||
box_color = p.get("box_color", [0.8, 0.8, 0.85])
|
||||
use_marker = int(p.get("use_marker", 0))
|
||||
alpha_val = p.get("alpha", 0.2)
|
||||
cam_dist = float(p.get("camera_distance", 40.0))
|
||||
cam_elev = float(p.get("camera_elevation", 0.0))
|
||||
cam_azim = float(p.get("camera_azimuth", 0.0))
|
||||
cam_cx = float(p.get("camera_center_x", 0.0))
|
||||
cam_cy = float(p.get("camera_center_y", 0.0))
|
||||
cam_cz = float(p.get("camera_center_z", 0.0))
|
||||
|
||||
header = {
|
||||
"DT": str(dt),
|
||||
"NSTEP": str(NSTEP),
|
||||
"method": method_str,
|
||||
"NT": str(NT),
|
||||
"warmup_steps": str(warmup_steps),
|
||||
"dynamic_steps": str(record_steps),
|
||||
"T_total": str(NT * dt),
|
||||
"box_a": str(box_a),
|
||||
"gravity_field": str(gravity_field),
|
||||
"elastic_force": str(elastic_force),
|
||||
"damping_force": str(damping_force),
|
||||
"driving_force": str(driving_force),
|
||||
"gravity_strength": str(gravity_strength),
|
||||
"G": json.dumps([float(v) for v in G]),
|
||||
"B": json.dumps([float(v) for v in B]),
|
||||
"number_of_frames": str(n_frames),
|
||||
"number_of_particles": str(len(atom_ids)),
|
||||
"use_marker": str(use_marker),
|
||||
"display_color": json.dumps(p.get("display_color",
|
||||
{"x":[0,[1.0,0.0,0.0]],"y":[0,[0.0,1.0,0.0]],"z":[0,[0.0,0.0,1.0]],
|
||||
"xy":[0,[1.0,1.0,0.0]],"yz":[0,[0.0,1.0,1.0]],"zx":[0,[1.0,0.0,1.0]],
|
||||
"xyz":[1,[1.0,1.0,1.0]]})),
|
||||
"ball_radius": str(ball_radius),
|
||||
"ball_color_r": str(ball_color[0]),
|
||||
"ball_color_g": str(ball_color[1]),
|
||||
"ball_color_b": str(ball_color[2]),
|
||||
"box_color_r": str(box_color[0]),
|
||||
"box_color_g": str(box_color[1]),
|
||||
"box_color_b": str(box_color[2]),
|
||||
"alpha": str(alpha_val) if not isinstance(alpha_val, list)
|
||||
else ",".join(str(a) for a in alpha_val),
|
||||
"atom_radii": ",".join(str(r) for r in radii),
|
||||
"atom_masses": json.dumps([float(m) for m in masses]),
|
||||
"atom_positions": json.dumps(positions.tolist()),
|
||||
"bond_pairs": json.dumps(bond_pairs.tolist() if n_bonds else []),
|
||||
"bond_stiffness": json.dumps(bond_stiffness.tolist() if n_bonds else []),
|
||||
"bond_rest_lengths": json.dumps(bond_rest_lengths.tolist() if n_bonds else []),
|
||||
"X_MIN": str(-box_a), "X_MAX": str(box_a),
|
||||
"Y_MIN": str(-box_a), "Y_MAX": str(box_a),
|
||||
"Z_MIN": str(-box_a), "Z_MAX": str(box_a),
|
||||
"camera_distance": str(cam_dist),
|
||||
"camera_elevation": str(cam_elev),
|
||||
"camera_azimuth": str(cam_azim),
|
||||
"camera_center_x": str(cam_cx),
|
||||
"camera_center_y": str(cam_cy),
|
||||
"camera_center_z": str(cam_cz),
|
||||
"camera_keyframes": "",
|
||||
}
|
||||
|
||||
# ── 保存 display.txt + display.npz ───────────────────────
|
||||
disp_txt = os.path.join(output_dir, "display.txt")
|
||||
_c.save_display_txt(
|
||||
disp_txt,
|
||||
out_x, out_y, out_z, out_vx, out_vy, out_vz,
|
||||
atom_ids, record_steps, len(atom_ids),
|
||||
header_fields=header,
|
||||
)
|
||||
print(f"[python-engine] display.txt 已保存: {disp_txt}")
|
||||
|
||||
disp_npz = os.path.join(output_dir, "display.npz")
|
||||
_c.save_display_npz(
|
||||
disp_npz,
|
||||
out_x, out_y, out_z, out_vx, out_vy, out_vz,
|
||||
atom_ids, header_fields=header,
|
||||
)
|
||||
print(f"[python-engine] display.npz 已保存: {disp_npz}")
|
||||
|
||||
# ── 可选:保存 trajectory.txt ─────────────────────────────
|
||||
if save_traj:
|
||||
traj_payload = {
|
||||
"traj_x": out_x, "traj_y": out_y, "traj_z": out_z,
|
||||
"traj_vx": out_vx, "traj_vy": out_vy, "traj_vz": out_vz,
|
||||
"NT": record_steps, "DT": dt, "NSTEP": NSTEP,
|
||||
"method": method_str,
|
||||
"atom_ids": atom_ids,
|
||||
"atom_masses": masses,
|
||||
"atom_radii": radii,
|
||||
"atom_positions": positions,
|
||||
"bond_pairs": bond_pairs,
|
||||
"bond_stiffness": bond_stiffness,
|
||||
"bond_rest_lengths": bond_rest_lengths,
|
||||
"G": [float(v) for v in G],
|
||||
"B": [float(v) for v in B],
|
||||
}
|
||||
traj_path = os.path.join(output_dir, "trajectory.txt")
|
||||
_c.save_text_data(traj_path, traj_payload)
|
||||
print(f"[python-engine] trajectory.txt 已保存: {traj_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,51 @@
|
||||
# engines/src/c/Makefile
|
||||
# 编译 DLL 到 engines/release/(主程序通过 ctypes 直接调用)
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -O3 -march=native -Wall -Wextra
|
||||
LDFLAGS = -lm
|
||||
LIB_SRC = dynamics_lib.c
|
||||
|
||||
# 自动检测系统
|
||||
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
|
||||
|
||||
# Windows 检测:Msys2/MINGW 也视为 Windows
|
||||
IS_WINDOWS := $(findstring MINGW,$(UNAME_S))
|
||||
ifneq ($(IS_WINDOWS),)
|
||||
UNAME_S := Windows
|
||||
endif
|
||||
IS_WINDOWS := $(findstring MSYS,$(UNAME_S))
|
||||
ifneq ($(IS_WINDOWS),)
|
||||
UNAME_S := Windows
|
||||
endif
|
||||
|
||||
# DLL 输出到 engines/release/
|
||||
DLL_DIR = ../../release
|
||||
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_c.so
|
||||
DLL_FLAGS = -shared -fPIC
|
||||
else ifeq ($(UNAME_S),Darwin)
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_c.dylib
|
||||
DLL_FLAGS = -dynamiclib
|
||||
else
|
||||
# Windows: 静态链接运行时,避免依赖 libgcc_s_seh-1.dll
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_c.dll
|
||||
DLL_FLAGS = -shared -static
|
||||
endif
|
||||
|
||||
.PHONY: all dll clean
|
||||
|
||||
all: dll
|
||||
|
||||
dll: $(DLL_TARGET)
|
||||
|
||||
$(DLL_TARGET): $(LIB_SRC) | $(DLL_DIR)
|
||||
$(CC) $(CFLAGS) $(DLL_FLAGS) -o $@ $(LIB_SRC) $(LDFLAGS)
|
||||
@echo " === C DLL built: $@ ==="
|
||||
|
||||
$(DLL_DIR):
|
||||
mkdir -p $(DLL_DIR)
|
||||
|
||||
clean:
|
||||
rm -f $(DLL_TARGET)
|
||||
@@ -0,0 +1,554 @@
|
||||
/**
|
||||
* engines/c/dynamics_lib.c
|
||||
* -------------------------
|
||||
* 纯计算 DLL:无文件 I/O,所有数据由 Python 以 NumPy 数组传入,
|
||||
* 结果直接写入 Python 预分配的输出数组。
|
||||
* 算法与 main.c 和 compute.py 保持完全一致。
|
||||
*
|
||||
* 编译(Windows DLL):
|
||||
* gcc -O3 -march=native -shared -o build/dynamics_c.dll dynamics_lib.c -lm
|
||||
* 编译(Linux .so):
|
||||
* gcc -O3 -march=native -shared -fPIC -o build/dynamics_c.so dynamics_lib.c -lm
|
||||
* 编译(macOS .dylib):
|
||||
* gcc -O3 -march=native -dynamiclib -o build/dynamics_c.dylib dynamics_lib.c -lm
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
# define EXPORT __declspec(dllexport)
|
||||
#else
|
||||
# define EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* ── 驱动力结构体 ─────────────────────────────────────────── */
|
||||
typedef struct {
|
||||
int n_drivers;
|
||||
const int *idx; /* [n_drivers] 0-based local atom index */
|
||||
const double *amp; /* [n_drivers*3] (ax,ay,az) interleaved */
|
||||
const double *freq; /* [n_drivers*3] */
|
||||
const double *phi; /* [n_drivers*3] radians */
|
||||
const double *eq; /* [n_drivers*3] equilibrium positions */
|
||||
const double *ncycles; /* [n_drivers] 0=unlimited */
|
||||
const int *has_period; /* [n_drivers] */
|
||||
/* mutable freeze positions (allocated internally) */
|
||||
double *freeze; /* [n_drivers*3] */
|
||||
} Drivers;
|
||||
|
||||
/* ── 加速度:保守力(弹簧键 + 均匀重力场)────────────────── */
|
||||
static void accel_conservative(
|
||||
int n, const double *x, const double *y, const double *z,
|
||||
const double *m,
|
||||
double Gx, double Gy, double Gz,
|
||||
int gravity_field, int elastic_force,
|
||||
int n_bonds, const int *bond_pairs,
|
||||
const double *bond_k, const double *bond_r0,
|
||||
double *ax, double *ay, double *az)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
ax[i] = gravity_field ? Gx : 0.0;
|
||||
ay[i] = gravity_field ? Gy : 0.0;
|
||||
az[i] = gravity_field ? Gz : 0.0;
|
||||
}
|
||||
|
||||
if (!elastic_force || n_bonds == 0) return;
|
||||
|
||||
for (int b = 0; b < n_bonds; b++) {
|
||||
int ii = bond_pairs[b*2];
|
||||
int jj = bond_pairs[b*2+1];
|
||||
double dx = x[jj] - x[ii];
|
||||
double dy = y[jj] - y[ii];
|
||||
double dz = z[jj] - z[ii];
|
||||
double dist = sqrt(dx*dx + dy*dy + dz*dz);
|
||||
if (dist < 1e-12) continue;
|
||||
double k = bond_k[b];
|
||||
double r0 = bond_r0[b];
|
||||
double fac = k * (dist - r0) / dist;
|
||||
double fx = fac * dx, fy = fac * dy, fz_b = fac * dz;
|
||||
ax[ii] += fx / m[ii]; ay[ii] += fy / m[ii]; az[ii] += fz_b / m[ii];
|
||||
ax[jj] -= fx / m[jj]; ay[jj] -= fy / m[jj]; az[jj] -= fz_b / m[jj];
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 完整加速度(含阻尼)────────────────────────────────── */
|
||||
static void accel_full(
|
||||
int n, const double *x, const double *y, const double *z,
|
||||
const double *vx, const double *vy, const double *vz,
|
||||
const double *m,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bond_pairs,
|
||||
const double *bond_k, const double *bond_r0,
|
||||
double *ax, double *ay, double *az)
|
||||
{
|
||||
accel_conservative(n, x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
n_bonds, bond_pairs, bond_k, bond_r0,
|
||||
ax, ay, az);
|
||||
if (damping_force) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
ax[i] -= Bx * vx[i] / m[i];
|
||||
ay[i] -= By * vy[i] / m[i];
|
||||
az[i] -= Bz * vz[i] / m[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 边界:反弹(与 main.c limit_in_box 一致)────────────── */
|
||||
static inline void _limit1(double *p, double *v, double lo, double hi) {
|
||||
if (*p > hi) { *p = hi; *v = -fabs(*v); }
|
||||
if (*p < lo) { *p = lo; *v = fabs(*v); }
|
||||
}
|
||||
|
||||
/* ── 边界:回绕(与 main.c wrap_position 一致)──────────── */
|
||||
static inline void _wrap1(double *p, double lo, double hi) {
|
||||
if (*p > hi) *p = lo;
|
||||
if (*p < lo) *p = hi;
|
||||
}
|
||||
|
||||
/* ── 边界 + 固定约束(与 main.c apply_step 末尾一致)──────── */
|
||||
static void apply_boundary_and_constraints(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const int *fixed, const double *pos_init,
|
||||
double box_a)
|
||||
{
|
||||
double lo = -box_a, hi = box_a;
|
||||
|
||||
/* 反弹 */
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
_limit1(&x[i], &vx[i], lo, hi);
|
||||
_limit1(&y[i], &vy[i], lo, hi);
|
||||
_limit1(&z[i], &vz[i], lo, hi);
|
||||
}
|
||||
|
||||
/* 回绕 */
|
||||
for (int i = 0; i < n; i++) {
|
||||
_wrap1(&x[i], lo, hi);
|
||||
_wrap1(&y[i], lo, hi);
|
||||
_wrap1(&z[i], lo, hi);
|
||||
}
|
||||
|
||||
/* 逐自由度固定约束:与 main.c 和 Python apply_fixed_constraints 一致 */
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0]) { x[i] = pos_init[i*3+0]; vx[i] = 0.0; }
|
||||
if (fixed[i*3+1]) { y[i] = pos_init[i*3+1]; vy[i] = 0.0; }
|
||||
if (fixed[i*3+2]) { z[i] = pos_init[i*3+2]; vz[i] = 0.0; }
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 蛙跳法(与 main.c leapfrog_step 完全一致)
|
||||
* x(t), v(t-dt/2) → x(t+dt), v(t+dt/2)
|
||||
* 无阻尼:纯辛积分。有阻尼:半隐式处理 α = B·dt/(2m)
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void leapfrog_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0,
|
||||
double dt)
|
||||
{
|
||||
double *ax = (double*)alloca(n*sizeof(double)*3);
|
||||
double *ay = ax+n; double *az = ay+n;
|
||||
|
||||
accel_conservative(n, x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
|
||||
int has_damp = damping_force && (Bx != 0.0 || By != 0.0 || Bz != 0.0);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
if (has_damp) {
|
||||
double ax_ = Bx*dt/(2.0*m[i]);
|
||||
double ay_ = By*dt/(2.0*m[i]);
|
||||
double az_ = Bz*dt/(2.0*m[i]);
|
||||
vx[i] = (vx[i]*(1.0-ax_) + ax[i]*dt) / (1.0+ax_);
|
||||
vy[i] = (vy[i]*(1.0-ay_) + ay[i]*dt) / (1.0+ay_);
|
||||
vz[i] = (vz[i]*(1.0-az_) + az[i]*dt) / (1.0+az_);
|
||||
} else {
|
||||
vx[i] += ax[i]*dt;
|
||||
vy[i] += ay[i]*dt;
|
||||
vz[i] += az[i]*dt;
|
||||
}
|
||||
x[i] += vx[i]*dt;
|
||||
y[i] += vy[i]*dt;
|
||||
z[i] += vz[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 显式欧拉法(与 main.c explicit_euler_step 一致)
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void euler_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0,
|
||||
double dt)
|
||||
{
|
||||
double *ax = (double*)alloca(n*sizeof(double)*3);
|
||||
double *ay = ax+n; double *az = ay+n;
|
||||
accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
x[i] += vx[i]*dt; y[i] += vy[i]*dt; z[i] += vz[i]*dt;
|
||||
vx[i]+= ax[i]*dt; vy[i]+= ay[i]*dt; vz[i]+= az[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 隐式欧拉法(与 main.c implicit_euler_step 完全一致)
|
||||
*
|
||||
* main.c 逻辑:
|
||||
* 1. 用 v_next ≈ (v + G·dt)/(1 + γ·dt) 预测(只含重力+阻尼,不含弹簧)
|
||||
* 2. 用 (x, v_next) 计算完整加速度 a_next
|
||||
* 3. v += a_next·dt; x += v·dt
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void implicit_euler_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0,
|
||||
double dt)
|
||||
{
|
||||
double *vxn = (double*)alloca(n*sizeof(double)*3);
|
||||
double *vyn = vxn+n; double *vzn = vyn+n;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) {
|
||||
vxn[i] = vyn[i] = vzn[i] = 0.0; continue;
|
||||
}
|
||||
double gx = Bx / m[i], gy = By / m[i], gz = Bz / m[i];
|
||||
vxn[i] = (vx[i] + Gx*dt) / (1.0 + gx*dt);
|
||||
vyn[i] = (vy[i] + Gy*dt) / (1.0 + gy*dt);
|
||||
vzn[i] = (vz[i] + Gz*dt) / (1.0 + gz*dt);
|
||||
}
|
||||
|
||||
double *ax = (double*)alloca(n*sizeof(double)*3);
|
||||
double *ay = ax+n; double *az = ay+n;
|
||||
accel_full(n, x, y, z, vxn, vyn, vzn, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += ax[i]*dt;
|
||||
vy[i] += ay[i]*dt;
|
||||
vz[i] += az[i]*dt;
|
||||
x[i] += vx[i]*dt;
|
||||
y[i] += vy[i]*dt;
|
||||
z[i] += vz[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 中点法(与 main.c midpoint_step 完全一致)
|
||||
*
|
||||
* main.c 逻辑:
|
||||
* 1. a = accel(x, v)
|
||||
* 2. xm = x + 0.5·v·dt; vm = v + 0.5·a·dt
|
||||
* 3. x = x + vm·dt (位置更新用 vm,即中点速度)
|
||||
* 4. am = accel(xm, vm)
|
||||
* 5. v = v + am·dt
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void midpoint_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0,
|
||||
double dt)
|
||||
{
|
||||
/* Allocate in one block for cache locality */
|
||||
double *buf = (double*)alloca(n*sizeof(double)*9);
|
||||
double *ax = buf;
|
||||
double *ay = ax+n; double *az = ay+n;
|
||||
double *xm = az+n; double *ym = xm+n; double *zm = ym+n;
|
||||
double *vxm = zm+n; double *vym = vxm+n; double *vzm = vym+n;
|
||||
|
||||
accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) {
|
||||
xm[i]=x[i]; ym[i]=y[i]; zm[i]=z[i];
|
||||
vxm[i]=vym[i]=vzm[i]=0.0; continue;
|
||||
}
|
||||
xm[i] = x[i] + 0.5*vx[i]*dt;
|
||||
ym[i] = y[i] + 0.5*vy[i]*dt;
|
||||
zm[i] = z[i] + 0.5*vz[i]*dt;
|
||||
vxm[i] = vx[i] + 0.5*ax[i]*dt;
|
||||
vym[i] = vy[i] + 0.5*ay[i]*dt;
|
||||
vzm[i] = vz[i] + 0.5*az[i]*dt;
|
||||
/* position updated with midpoint velocity (same as main.c) */
|
||||
x[i] = x[i] + vxm[i]*dt;
|
||||
y[i] = y[i] + vym[i]*dt;
|
||||
z[i] = z[i] + vzm[i]*dt;
|
||||
}
|
||||
|
||||
double *axm = (double*)alloca(n*sizeof(double)*3);
|
||||
double *aym = axm+n; double *azm = aym+n;
|
||||
accel_full(n, xm, ym, zm, vxm, vym, vzm, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, axm, aym, azm);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += axm[i]*dt;
|
||||
vy[i] += aym[i]*dt;
|
||||
vz[i] += azm[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 驱动力(与 main.c apply_driving_force 一致)────────── */
|
||||
static void apply_driving(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
double t, int step, double dt, Drivers *drv)
|
||||
{
|
||||
(void)n;
|
||||
if (!drv || drv->n_drivers == 0) return;
|
||||
const double TWO_PI = 2.0 * 3.14159265358979323846;
|
||||
|
||||
for (int d = 0; d < drv->n_drivers; d++) {
|
||||
int idx = drv->idx[d];
|
||||
double fx = drv->freq[d*3+0];
|
||||
double fy = drv->freq[d*3+1];
|
||||
double fz = drv->freq[d*3+2];
|
||||
|
||||
if (drv->has_period[d]) {
|
||||
double mf = fabs(fx) > fabs(fy) ? fabs(fx) : fabs(fy);
|
||||
if (fabs(fz) > mf) mf = fabs(fz);
|
||||
int period_steps = 0;
|
||||
if (mf > 1e-12)
|
||||
period_steps = (int)(drv->ncycles[d] / mf / dt);
|
||||
if (step > period_steps) {
|
||||
x[idx] = drv->freeze[d*3+0];
|
||||
y[idx] = drv->freeze[d*3+1];
|
||||
z[idx] = drv->freeze[d*3+2];
|
||||
vx[idx] = vy[idx] = vz[idx] = 0.0;
|
||||
continue;
|
||||
}
|
||||
|
||||
double px = drv->eq[d*3+0] + drv->amp[d*3+0]*cos(TWO_PI*fx*t + drv->phi[d*3+0]);
|
||||
double py = drv->eq[d*3+1] + drv->amp[d*3+1]*cos(TWO_PI*fy*t + drv->phi[d*3+1]);
|
||||
double pz = drv->eq[d*3+2] + drv->amp[d*3+2]*cos(TWO_PI*fz*t + drv->phi[d*3+2]);
|
||||
if (step == period_steps) {
|
||||
drv->freeze[d*3+0] = px;
|
||||
drv->freeze[d*3+1] = py;
|
||||
drv->freeze[d*3+2] = pz;
|
||||
}
|
||||
}
|
||||
|
||||
x[idx] = drv->eq[d*3+0] + drv->amp[d*3+0]*cos(TWO_PI*fx*t + drv->phi[d*3+0]);
|
||||
y[idx] = drv->eq[d*3+1] + drv->amp[d*3+1]*cos(TWO_PI*fy*t + drv->phi[d*3+1]);
|
||||
z[idx] = drv->eq[d*3+2] + drv->amp[d*3+2]*cos(TWO_PI*fz*t + drv->phi[d*3+2]);
|
||||
vx[idx] = -drv->amp[d*3+0]*TWO_PI*fx*sin(TWO_PI*fx*t + drv->phi[d*3+0]);
|
||||
vy[idx] = -drv->amp[d*3+1]*TWO_PI*fy*sin(TWO_PI*fy*t + drv->phi[d*3+1]);
|
||||
vz[idx] = -drv->amp[d*3+2]*TWO_PI*fz*sin(TWO_PI*fz*t + drv->phi[d*3+2]);
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 导出函数:run_dynamics
|
||||
*
|
||||
* 与 main.c 的计算顺序完全一致:
|
||||
* 1. leapfrog 初始化 v(-dt/2)
|
||||
* 2. 初始驱动 t=0
|
||||
* 3. 预热循环(不记录)
|
||||
* 4. 记录循环:drive → record → step → boundary → constraints
|
||||
*
|
||||
* 参数说明(所有数组均为 C-contiguous 行优先 float64/int32):
|
||||
* n_atoms 原子数
|
||||
* pos_init 初始位置 [n_atoms*3] x0,y0,z0, x1,y1,z1, ...
|
||||
* vel_init 初始速度 [n_atoms*3]
|
||||
* masses 质量 [n_atoms]
|
||||
* fixed 自由度约束 [n_atoms*3] int32, 1=固定
|
||||
* n_bonds 键数
|
||||
* bond_pairs 键对 [n_bonds*2] int32, 0-based local index
|
||||
* bond_k 刚度 [n_bonds]
|
||||
* bond_r0 平衡键长 [n_bonds]
|
||||
* box_a 盒子半边长
|
||||
* dt 时间步长
|
||||
* NT 总步数(含预热)
|
||||
* NSTEP 抽帧间隔
|
||||
* warmup_steps 预热步数
|
||||
* method_id 0=euler 1=implicit 2=midpoint 3=leapfrog
|
||||
* Gx/Gy/Gz 均匀重力场加速度分量
|
||||
* Bx/By/Bz 阻尼系数分量
|
||||
* gravity_field / elastic_force / damping_force 力开关
|
||||
* gravity_strength 原子间引力强度(暂未实现,留接口)
|
||||
* n_drivers 驱动原子数
|
||||
* drv_idx 驱动原子局部索引 [n_drivers] int32
|
||||
* drv_amp 振幅 [n_drivers*3]
|
||||
* drv_freq 频率 [n_drivers*3]
|
||||
* drv_phi 初相(弧度)[n_drivers*3]
|
||||
* drv_eq 平衡位置 [n_drivers*3]
|
||||
* drv_ncycles 周期数 [n_drivers] 0=不限
|
||||
* drv_has_period [n_drivers] int32
|
||||
* n_frames 输出帧数(Python 预计算:(NT-warmup)/NSTEP 向上取整)
|
||||
* out_x/y/z/vx/vy/vz 输出数组 [n_frames*n_atoms] 由 Python 预分配
|
||||
* progress_cb 进度回调(可为 NULL)
|
||||
*
|
||||
* 返回:0=成功,负数=错误
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
EXPORT int run_dynamics(
|
||||
int n_atoms,
|
||||
const double *pos_init,
|
||||
const double *vel_init,
|
||||
const double *masses,
|
||||
const int *fixed,
|
||||
int n_bonds,
|
||||
const int *bond_pairs,
|
||||
const double *bond_k,
|
||||
const double *bond_r0,
|
||||
double box_a, double dt,
|
||||
int NT, int NSTEP, int warmup_steps, int method_id,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
double gravity_strength,
|
||||
int n_drivers,
|
||||
const int *drv_idx,
|
||||
const double *drv_amp,
|
||||
const double *drv_freq,
|
||||
const double *drv_phi,
|
||||
const double *drv_eq,
|
||||
const double *drv_ncycles,
|
||||
const int *drv_has_period,
|
||||
int n_frames,
|
||||
double *out_x, double *out_y, double *out_z,
|
||||
double *out_vx, double *out_vy, double *out_vz,
|
||||
void (*progress_cb)(int step, int total))
|
||||
{
|
||||
(void)gravity_strength; /* 原子间引力暂未实现 */
|
||||
|
||||
int n = n_atoms;
|
||||
|
||||
/* ── 工作数组 ── */
|
||||
double *x = (double*)malloc(n*sizeof(double));
|
||||
double *y = (double*)malloc(n*sizeof(double));
|
||||
double *z = (double*)malloc(n*sizeof(double));
|
||||
double *vx = (double*)malloc(n*sizeof(double));
|
||||
double *vy = (double*)malloc(n*sizeof(double));
|
||||
double *vz = (double*)malloc(n*sizeof(double));
|
||||
if (!x||!y||!z||!vx||!vy||!vz) return -1;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
x[i]=pos_init[i*3+0]; y[i]=pos_init[i*3+1]; z[i]=pos_init[i*3+2];
|
||||
vx[i]=vel_init[i*3+0]; vy[i]=vel_init[i*3+1]; vz[i]=vel_init[i*3+2];
|
||||
}
|
||||
|
||||
/* ── 驱动结构 ── */
|
||||
Drivers drv;
|
||||
drv.n_drivers = n_drivers;
|
||||
drv.idx = drv_idx;
|
||||
drv.amp = drv_amp;
|
||||
drv.freq = drv_freq;
|
||||
drv.phi = drv_phi;
|
||||
drv.eq = drv_eq;
|
||||
drv.ncycles = drv_ncycles;
|
||||
drv.has_period = drv_has_period;
|
||||
drv.freeze = NULL;
|
||||
if (n_drivers > 0) {
|
||||
drv.freeze = (double*)calloc(n_drivers*3, sizeof(double));
|
||||
if (!drv.freeze) { free(x);free(y);free(z);free(vx);free(vy);free(vz); return -2; }
|
||||
}
|
||||
|
||||
/* ── 内联步进宏 ── */
|
||||
#define DO_STEP() do { \
|
||||
switch (method_id) { \
|
||||
case 0: euler_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
case 1: implicit_euler_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
case 2: midpoint_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
default: leapfrog_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
} \
|
||||
apply_boundary_and_constraints(n,x,y,z,vx,vy,vz,fixed,pos_init,box_a); \
|
||||
} while(0)
|
||||
|
||||
/* ── 蛙跳法:初始化 v(-dt/2) = v(0) - 0.5·a_c(0)·dt ── */
|
||||
if (method_id == 3) {
|
||||
double *ax0 = (double*)alloca(n*sizeof(double)*3);
|
||||
double *ay0 = ax0+n; double *az0 = ay0+n;
|
||||
accel_conservative(n, x, y, z, masses, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
n_bonds, bond_pairs, bond_k, bond_r0,
|
||||
ax0, ay0, az0);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] -= 0.5*ax0[i]*dt;
|
||||
vy[i] -= 0.5*ay0[i]*dt;
|
||||
vz[i] -= 0.5*az0[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 初始驱动 t=0(与 main.c 一致:leapfrog init 之后施加)── */
|
||||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, 0.0, 0, dt, &drv);
|
||||
|
||||
/* ── 预热(不记录)── */
|
||||
for (int s = 0; s < warmup_steps; s++) {
|
||||
double tw = (s + 1) * dt;
|
||||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, tw, s, dt, &drv);
|
||||
DO_STEP();
|
||||
}
|
||||
|
||||
/* ── 记录循环 ── */
|
||||
int record_steps = NT - warmup_steps;
|
||||
int prog_interval = record_steps / 100;
|
||||
if (prog_interval < 1) prog_interval = 1;
|
||||
int frame_idx = 0;
|
||||
|
||||
for (int s = 0; s < record_steps; s++) {
|
||||
if (progress_cb && s % prog_interval == 0 && s > 0)
|
||||
progress_cb(s, record_steps);
|
||||
|
||||
double t = (s + warmup_steps) * dt;
|
||||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, t, s, dt, &drv);
|
||||
|
||||
/* 抽帧记录(drive 之后,step 之前,与 main.c 一致)*/
|
||||
if (s % NSTEP == 0 && frame_idx < n_frames) {
|
||||
int base = frame_idx * n;
|
||||
for (int i = 0; i < n; i++) {
|
||||
out_x [base+i] = x[i]; out_y [base+i] = y[i]; out_z [base+i] = z[i];
|
||||
out_vx[base+i] = vx[i]; out_vy[base+i] = vy[i]; out_vz[base+i] = vz[i];
|
||||
}
|
||||
frame_idx++;
|
||||
}
|
||||
DO_STEP();
|
||||
}
|
||||
|
||||
#undef DO_STEP
|
||||
|
||||
free(x); free(y); free(z);
|
||||
free(vx); free(vy); free(vz);
|
||||
if (drv.freeze) free(drv.freeze);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# engines/src/cpp/Makefile
|
||||
# 编译 DLL 到 engines/release/(主程序通过 ctypes 直接调用)
|
||||
|
||||
CXX = g++
|
||||
LIB_SRC = dynamics_lib.cpp
|
||||
|
||||
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
|
||||
|
||||
# Windows 检测:Msys2/MINGW 也视为 Windows
|
||||
IS_WINDOWS := $(findstring MINGW,$(UNAME_S))
|
||||
ifneq ($(IS_WINDOWS),)
|
||||
UNAME_S := Windows
|
||||
endif
|
||||
IS_WINDOWS := $(findstring MSYS,$(UNAME_S))
|
||||
ifneq ($(IS_WINDOWS),)
|
||||
UNAME_S := Windows
|
||||
endif
|
||||
|
||||
CXXFLAGS = -O3 -march=native -std=c++17 -Wall -Wextra -D_USE_MATH_DEFINES
|
||||
|
||||
# DLL 输出到 engines/release/
|
||||
DLL_DIR = ../../release
|
||||
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_cpp.so
|
||||
DLL_FLAGS = -shared -fPIC
|
||||
else ifeq ($(UNAME_S),Darwin)
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_cpp.dylib
|
||||
DLL_FLAGS = -dynamiclib
|
||||
else
|
||||
# Windows: 完全静态链接,避免依赖运行时 DLL
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_cpp.dll
|
||||
DLL_FLAGS = -shared -static
|
||||
endif
|
||||
|
||||
.PHONY: all dll clean
|
||||
|
||||
all: dll
|
||||
|
||||
dll: $(DLL_TARGET)
|
||||
|
||||
$(DLL_TARGET): $(LIB_SRC) | $(DLL_DIR)
|
||||
$(CXX) $(CXXFLAGS) $(DLL_FLAGS) -o $@ $(LIB_SRC)
|
||||
@echo " === C++ DLL built: $@ ==="
|
||||
|
||||
$(DLL_DIR):
|
||||
mkdir -p $(DLL_DIR)
|
||||
|
||||
clean:
|
||||
rm -f $(DLL_TARGET)
|
||||
@@ -0,0 +1,450 @@
|
||||
/**
|
||||
* engines/cpp/dynamics_lib.cpp
|
||||
* -----------------------------
|
||||
* 纯计算 DLL(C++ 版):无文件 I/O,所有数据由 Python 以 NumPy 数组传入。
|
||||
* 算法与 main.cpp / compute.py 保持完全一致。
|
||||
*
|
||||
* 编译(Windows):
|
||||
* g++ -O3 -march=native -std=c++17 -shared -o build/dynamics_cpp.dll dynamics_lib.cpp
|
||||
* 编译(Linux):
|
||||
* g++ -O3 -march=native -std=c++17 -shared -fPIC -o build/dynamics_cpp.so dynamics_lib.cpp
|
||||
* 编译(macOS):
|
||||
* g++ -O3 -march=native -std=c++17 -dynamiclib -o build/dynamics_cpp.dylib dynamics_lib.cpp
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
# define EXPORT extern "C" __declspec(dllexport)
|
||||
#else
|
||||
# define EXPORT extern "C" __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
|
||||
/* ── 驱动力结构体 ─────────────────────────────────────────── */
|
||||
struct Drivers {
|
||||
int n_drivers = 0;
|
||||
const int *idx = nullptr;
|
||||
const double *amp = nullptr;
|
||||
const double *freq = nullptr;
|
||||
const double *phi = nullptr;
|
||||
const double *eq = nullptr;
|
||||
const double *ncycles = nullptr;
|
||||
const int *has_period = nullptr;
|
||||
std::vector<double> freeze; /* [n_drivers*3] 冻结位置(period 结束时锁定)*/
|
||||
};
|
||||
|
||||
/* ── 加速度:保守力(弹簧键 + 均匀重力场)────────────────── */
|
||||
static void accel_conservative(
|
||||
int n, const double *x, const double *y, const double *z,
|
||||
const double *m,
|
||||
double Gx, double Gy, double Gz,
|
||||
int gravity_field, int elastic_force,
|
||||
int n_bonds, const int *bond_pairs,
|
||||
const double *bond_k, const double *bond_r0,
|
||||
double *ax, double *ay, double *az)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
ax[i] = gravity_field ? Gx : 0.0;
|
||||
ay[i] = gravity_field ? Gy : 0.0;
|
||||
az[i] = gravity_field ? Gz : 0.0;
|
||||
}
|
||||
if (!elastic_force || n_bonds == 0) return;
|
||||
for (int b = 0; b < n_bonds; b++) {
|
||||
int ii = bond_pairs[b*2];
|
||||
int jj = bond_pairs[b*2+1];
|
||||
double dx = x[jj]-x[ii], dy = y[jj]-y[ii], dz = z[jj]-z[ii];
|
||||
double dist = std::sqrt(dx*dx + dy*dy + dz*dz);
|
||||
if (dist < 1e-12) continue;
|
||||
double fac = bond_k[b] * (dist - bond_r0[b]) / dist;
|
||||
double fx = fac*dx, fy = fac*dy, fz_b = fac*dz;
|
||||
ax[ii] += fx/m[ii]; ay[ii] += fy/m[ii]; az[ii] += fz_b/m[ii];
|
||||
ax[jj] -= fx/m[jj]; ay[jj] -= fy/m[jj]; az[jj] -= fz_b/m[jj];
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 完整加速度(含阻尼)────────────────────────────────── */
|
||||
static void accel_full(
|
||||
int n, const double *x, const double *y, const double *z,
|
||||
const double *vx, const double *vy, const double *vz,
|
||||
const double *m,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bond_pairs,
|
||||
const double *bond_k, const double *bond_r0,
|
||||
double *ax, double *ay, double *az)
|
||||
{
|
||||
accel_conservative(n, x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
n_bonds, bond_pairs, bond_k, bond_r0,
|
||||
ax, ay, az);
|
||||
if (damping_force) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
ax[i] -= Bx * vx[i] / m[i];
|
||||
ay[i] -= By * vy[i] / m[i];
|
||||
az[i] -= Bz * vz[i] / m[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 边界:反弹 ──────────────────────────────────────────── */
|
||||
static inline void _limit1(double &p, double &v, double lo, double hi) {
|
||||
if (p > hi) { p = hi; v = -std::fabs(v); }
|
||||
if (p < lo) { p = lo; v = std::fabs(v); }
|
||||
}
|
||||
|
||||
/* ── 边界:回绕 ──────────────────────────────────────────── */
|
||||
static inline void _wrap1(double &p, double lo, double hi) {
|
||||
if (p > hi) p = lo;
|
||||
if (p < lo) p = hi;
|
||||
}
|
||||
|
||||
/* ── 边界 + 固定约束 ────────────────────────────────────── */
|
||||
static void apply_boundary_and_constraints(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const int *fixed, const double *pos_init, double box_a)
|
||||
{
|
||||
double lo = -box_a, hi = box_a;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
_limit1(x[i], vx[i], lo, hi);
|
||||
_limit1(y[i], vy[i], lo, hi);
|
||||
_limit1(z[i], vz[i], lo, hi);
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
_wrap1(x[i], lo, hi);
|
||||
_wrap1(y[i], lo, hi);
|
||||
_wrap1(z[i], lo, hi);
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3+0]) { x[i] = pos_init[i*3+0]; vx[i] = 0.0; }
|
||||
if (fixed[i*3+1]) { y[i] = pos_init[i*3+1]; vy[i] = 0.0; }
|
||||
if (fixed[i*3+2]) { z[i] = pos_init[i*3+2]; vz[i] = 0.0; }
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 蛙跳法(与 main.cpp leapfrog_step 完全一致)
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void leapfrog_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0, double dt)
|
||||
{
|
||||
std::vector<double> buf(n * 3);
|
||||
double *ax = buf.data(), *ay = ax+n, *az = ay+n;
|
||||
accel_conservative(n, x, y, z, m, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
bool has_damp = damping_force && (Bx != 0.0 || By != 0.0 || Bz != 0.0);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
if (has_damp) {
|
||||
double ax_ = Bx*dt/(2.0*m[i]);
|
||||
double ay_ = By*dt/(2.0*m[i]);
|
||||
double az_ = Bz*dt/(2.0*m[i]);
|
||||
vx[i] = (vx[i]*(1.0-ax_) + ax[i]*dt) / (1.0+ax_);
|
||||
vy[i] = (vy[i]*(1.0-ay_) + ay[i]*dt) / (1.0+ay_);
|
||||
vz[i] = (vz[i]*(1.0-az_) + az[i]*dt) / (1.0+az_);
|
||||
} else {
|
||||
vx[i] += ax[i]*dt;
|
||||
vy[i] += ay[i]*dt;
|
||||
vz[i] += az[i]*dt;
|
||||
}
|
||||
x[i] += vx[i]*dt;
|
||||
y[i] += vy[i]*dt;
|
||||
z[i] += vz[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 显式欧拉法
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void euler_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0, double dt)
|
||||
{
|
||||
std::vector<double> buf(n * 3);
|
||||
double *ax = buf.data(), *ay = ax+n, *az = ay+n;
|
||||
accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
x[i] += vx[i]*dt; y[i] += vy[i]*dt; z[i] += vz[i]*dt;
|
||||
vx[i]+= ax[i]*dt; vy[i]+= ay[i]*dt; vz[i]+= az[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 隐式欧拉法(与 main.cpp implicit_euler_step 完全一致)
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void implicit_euler_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0, double dt)
|
||||
{
|
||||
std::vector<double> vbuf(n * 3), abuf(n * 3);
|
||||
double *vxn = vbuf.data(), *vyn = vxn+n, *vzn = vyn+n;
|
||||
double *ax = abuf.data(), *ay = ax+n, *az = ay+n;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) {
|
||||
vxn[i] = vyn[i] = vzn[i] = 0.0; continue;
|
||||
}
|
||||
double gx = Bx/m[i], gy = By/m[i], gz = Bz/m[i];
|
||||
vxn[i] = (vx[i] + Gx*dt) / (1.0 + gx*dt);
|
||||
vyn[i] = (vy[i] + Gy*dt) / (1.0 + gy*dt);
|
||||
vzn[i] = (vz[i] + Gz*dt) / (1.0 + gz*dt);
|
||||
}
|
||||
accel_full(n, x, y, z, vxn, vyn, vzn, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += ax[i]*dt; vy[i] += ay[i]*dt; vz[i] += az[i]*dt;
|
||||
x[i] += vx[i]*dt; y[i] += vy[i]*dt; z[i] += vz[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 中点法(与 main.cpp midpoint_step 完全一致)
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
static void midpoint_step(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
const double *m, const int *fixed,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
int n_bonds, const int *bp, const double *bk, const double *br0, double dt)
|
||||
{
|
||||
std::vector<double> buf(n * 9);
|
||||
double *ax = buf.data();
|
||||
double *ay = ax+n; double *az = ay+n;
|
||||
double *xm = az+n; double *ym = xm+n; double *zm = ym+n;
|
||||
double *vxm = zm+n; double *vym = vxm+n; double *vzm = vym+n;
|
||||
|
||||
accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, ax, ay, az);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) {
|
||||
xm[i]=x[i]; ym[i]=y[i]; zm[i]=z[i];
|
||||
vxm[i]=vym[i]=vzm[i]=0.0; continue;
|
||||
}
|
||||
xm[i] = x[i] + 0.5*vx[i]*dt;
|
||||
ym[i] = y[i] + 0.5*vy[i]*dt;
|
||||
zm[i] = z[i] + 0.5*vz[i]*dt;
|
||||
vxm[i] = vx[i] + 0.5*ax[i]*dt;
|
||||
vym[i] = vy[i] + 0.5*ay[i]*dt;
|
||||
vzm[i] = vz[i] + 0.5*az[i]*dt;
|
||||
x[i] = x[i] + vxm[i]*dt;
|
||||
y[i] = y[i] + vym[i]*dt;
|
||||
z[i] = z[i] + vzm[i]*dt;
|
||||
}
|
||||
|
||||
std::vector<double> abuf(n * 3);
|
||||
double *axm = abuf.data(), *aym = axm+n, *azm = aym+n;
|
||||
accel_full(n, xm, ym, zm, vxm, vym, vzm, m, Gx, Gy, Gz, Bx, By, Bz,
|
||||
gravity_field, elastic_force, damping_force,
|
||||
n_bonds, bp, bk, br0, axm, aym, azm);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] += axm[i]*dt;
|
||||
vy[i] += aym[i]*dt;
|
||||
vz[i] += azm[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 驱动力 ─────────────────────────────────────────────── */
|
||||
static void apply_driving(
|
||||
int n, double *x, double *y, double *z,
|
||||
double *vx, double *vy, double *vz,
|
||||
double t, int step, double dt, Drivers &drv)
|
||||
{
|
||||
(void)n;
|
||||
if (drv.n_drivers == 0) return;
|
||||
constexpr double TWO_PI = 2.0 * 3.14159265358979323846;
|
||||
|
||||
for (int d = 0; d < drv.n_drivers; d++) {
|
||||
int idx = drv.idx[d];
|
||||
double fx = drv.freq[d*3+0];
|
||||
double fy = drv.freq[d*3+1];
|
||||
double fz = drv.freq[d*3+2];
|
||||
|
||||
if (drv.has_period[d]) {
|
||||
double mf = std::fabs(fx) > std::fabs(fy) ? std::fabs(fx) : std::fabs(fy);
|
||||
if (std::fabs(fz) > mf) mf = std::fabs(fz);
|
||||
int period_steps = 0;
|
||||
if (mf > 1e-12)
|
||||
period_steps = (int)(drv.ncycles[d] / mf / dt);
|
||||
if (step > period_steps) {
|
||||
x[idx] = drv.freeze[d*3+0];
|
||||
y[idx] = drv.freeze[d*3+1];
|
||||
z[idx] = drv.freeze[d*3+2];
|
||||
vx[idx] = vy[idx] = vz[idx] = 0.0;
|
||||
continue;
|
||||
}
|
||||
double px = drv.eq[d*3+0] + drv.amp[d*3+0]*std::cos(TWO_PI*fx*t + drv.phi[d*3+0]);
|
||||
double py = drv.eq[d*3+1] + drv.amp[d*3+1]*std::cos(TWO_PI*fy*t + drv.phi[d*3+1]);
|
||||
double pz = drv.eq[d*3+2] + drv.amp[d*3+2]*std::cos(TWO_PI*fz*t + drv.phi[d*3+2]);
|
||||
if (step == period_steps) {
|
||||
drv.freeze[d*3+0] = px;
|
||||
drv.freeze[d*3+1] = py;
|
||||
drv.freeze[d*3+2] = pz;
|
||||
}
|
||||
}
|
||||
x[idx] = drv.eq[d*3+0] + drv.amp[d*3+0]*std::cos(TWO_PI*fx*t + drv.phi[d*3+0]);
|
||||
y[idx] = drv.eq[d*3+1] + drv.amp[d*3+1]*std::cos(TWO_PI*fy*t + drv.phi[d*3+1]);
|
||||
z[idx] = drv.eq[d*3+2] + drv.amp[d*3+2]*std::cos(TWO_PI*fz*t + drv.phi[d*3+2]);
|
||||
vx[idx] = -drv.amp[d*3+0]*TWO_PI*fx*std::sin(TWO_PI*fx*t + drv.phi[d*3+0]);
|
||||
vy[idx] = -drv.amp[d*3+1]*TWO_PI*fy*std::sin(TWO_PI*fy*t + drv.phi[d*3+1]);
|
||||
vz[idx] = -drv.amp[d*3+2]*TWO_PI*fz*std::sin(TWO_PI*fz*t + drv.phi[d*3+2]);
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
* 导出函数:run_dynamics(接口与 C 版完全相同)
|
||||
* ══════════════════════════════════════════════════════════ */
|
||||
EXPORT int run_dynamics(
|
||||
int n_atoms,
|
||||
const double *pos_init,
|
||||
const double *vel_init,
|
||||
const double *masses,
|
||||
const int *fixed,
|
||||
int n_bonds,
|
||||
const int *bond_pairs,
|
||||
const double *bond_k,
|
||||
const double *bond_r0,
|
||||
double box_a, double dt,
|
||||
int NT, int NSTEP, int warmup_steps, int method_id,
|
||||
double Gx, double Gy, double Gz,
|
||||
double Bx, double By, double Bz,
|
||||
int gravity_field, int elastic_force, int damping_force,
|
||||
double gravity_strength,
|
||||
int n_drivers,
|
||||
const int *drv_idx,
|
||||
const double *drv_amp,
|
||||
const double *drv_freq,
|
||||
const double *drv_phi,
|
||||
const double *drv_eq,
|
||||
const double *drv_ncycles,
|
||||
const int *drv_has_period,
|
||||
int n_frames,
|
||||
double *out_x, double *out_y, double *out_z,
|
||||
double *out_vx, double *out_vy, double *out_vz,
|
||||
void (*progress_cb)(int step, int total))
|
||||
{
|
||||
(void)gravity_strength;
|
||||
int n = n_atoms;
|
||||
|
||||
std::vector<double> xv(n), yv(n), zv(n);
|
||||
std::vector<double> vxv(n), vyv(n), vzv(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
xv[i]=pos_init[i*3+0]; yv[i]=pos_init[i*3+1]; zv[i]=pos_init[i*3+2];
|
||||
vxv[i]=vel_init[i*3+0]; vyv[i]=vel_init[i*3+1]; vzv[i]=vel_init[i*3+2];
|
||||
}
|
||||
double *x=xv.data(), *y=yv.data(), *z=zv.data();
|
||||
double *vx=vxv.data(), *vy=vyv.data(), *vz=vzv.data();
|
||||
|
||||
Drivers drv;
|
||||
drv.n_drivers = n_drivers;
|
||||
drv.idx = drv_idx;
|
||||
drv.amp = drv_amp;
|
||||
drv.freq = drv_freq;
|
||||
drv.phi = drv_phi;
|
||||
drv.eq = drv_eq;
|
||||
drv.ncycles = drv_ncycles;
|
||||
drv.has_period = drv_has_period;
|
||||
if (n_drivers > 0)
|
||||
drv.freeze.assign(n_drivers * 3, 0.0);
|
||||
|
||||
#define DO_STEP() do { \
|
||||
switch (method_id) { \
|
||||
case 0: euler_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
case 1: implicit_euler_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
case 2: midpoint_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
default: leapfrog_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||||
gravity_field,elastic_force,damping_force, \
|
||||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||||
} \
|
||||
apply_boundary_and_constraints(n,x,y,z,vx,vy,vz,fixed,pos_init,box_a); \
|
||||
} while(0)
|
||||
|
||||
/* 蛙跳法:初始化 v(-dt/2) */
|
||||
if (method_id == 3) {
|
||||
std::vector<double> ibuf(n * 3);
|
||||
double *ax0=ibuf.data(), *ay0=ax0+n, *az0=ay0+n;
|
||||
accel_conservative(n, x, y, z, masses, Gx, Gy, Gz,
|
||||
gravity_field, elastic_force,
|
||||
n_bonds, bond_pairs, bond_k, bond_r0,
|
||||
ax0, ay0, az0);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||||
vx[i] -= 0.5*ax0[i]*dt;
|
||||
vy[i] -= 0.5*ay0[i]*dt;
|
||||
vz[i] -= 0.5*az0[i]*dt;
|
||||
}
|
||||
}
|
||||
|
||||
/* 初始驱动 t=0 */
|
||||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, 0.0, 0, dt, drv);
|
||||
|
||||
/* 预热 */
|
||||
for (int s = 0; s < warmup_steps; s++) {
|
||||
double tw = (s + 1) * dt;
|
||||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, tw, s, dt, drv);
|
||||
DO_STEP();
|
||||
}
|
||||
|
||||
/* 记录循环 */
|
||||
int record_steps = NT - warmup_steps;
|
||||
int prog_interval = std::max(1, record_steps / 100);
|
||||
int frame_idx = 0;
|
||||
|
||||
for (int s = 0; s < record_steps; s++) {
|
||||
if (progress_cb && s % prog_interval == 0 && s > 0)
|
||||
progress_cb(s, record_steps);
|
||||
|
||||
double t = (s + warmup_steps) * dt;
|
||||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, t, s, dt, drv);
|
||||
|
||||
if (s % NSTEP == 0 && frame_idx < n_frames) {
|
||||
int base = frame_idx * n;
|
||||
for (int i = 0; i < n; i++) {
|
||||
out_x [base+i] = x[i]; out_y [base+i] = y[i]; out_z [base+i] = z[i];
|
||||
out_vx[base+i] = vx[i]; out_vy[base+i] = vy[i]; out_vz[base+i] = vz[i];
|
||||
}
|
||||
frame_idx++;
|
||||
}
|
||||
DO_STEP();
|
||||
}
|
||||
|
||||
#undef DO_STEP
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# engines/src/fortran/Makefile
|
||||
# 编译 DLL 到 engines/release/(主程序通过 ctypes 直接调用)
|
||||
|
||||
FC = gfortran
|
||||
FFLAGS = -O3 -march=native -Wall -Wextra
|
||||
LIB_SRC = dynamics_lib.f90
|
||||
|
||||
UNAME_S := $(shell uname -s 2>/dev/null || echo Windows)
|
||||
|
||||
# Windows 检测:Msys2/MINGW 也视为 Windows
|
||||
IS_WINDOWS := $(findstring MINGW,$(UNAME_S))
|
||||
ifneq ($(IS_WINDOWS),)
|
||||
UNAME_S := Windows
|
||||
endif
|
||||
IS_WINDOWS := $(findstring MSYS,$(UNAME_S))
|
||||
ifneq ($(IS_WINDOWS),)
|
||||
UNAME_S := Windows
|
||||
endif
|
||||
|
||||
# DLL 输出到 engines/release/
|
||||
DLL_DIR = ../../release
|
||||
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_f90.so
|
||||
DLL_FLAGS = -shared -fPIC
|
||||
else ifeq ($(UNAME_S),Darwin)
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_f90.dylib
|
||||
DLL_FLAGS = -dynamiclib
|
||||
else
|
||||
# Windows: 完全静态链接,避免依赖 libgfortran-5.dll
|
||||
DLL_TARGET = $(DLL_DIR)/dynamics_f90.dll
|
||||
DLL_FLAGS = -shared -fPIC -static
|
||||
endif
|
||||
|
||||
.PHONY: all dll clean
|
||||
|
||||
all: dll
|
||||
|
||||
dll: $(DLL_TARGET)
|
||||
|
||||
$(DLL_TARGET): $(LIB_SRC) | $(DLL_DIR)
|
||||
$(FC) $(FFLAGS) $(DLL_FLAGS) -o $@ $(LIB_SRC)
|
||||
@echo " === Fortran DLL built: $@ ==="
|
||||
|
||||
$(DLL_DIR):
|
||||
mkdir -p $(DLL_DIR)
|
||||
|
||||
clean:
|
||||
rm -f $(DLL_TARGET)
|
||||
@@ -0,0 +1,483 @@
|
||||
! engines/fortran/dynamics_lib.f90
|
||||
! ---------------------------------
|
||||
! 纯计算 DLL(Fortran 版):无文件 I/O,由 Python ctypes 调用。
|
||||
! 算法与 main.f90 / compute.py 完全一致。
|
||||
! 使用 iso_c_binding 导出 C 兼容接口。
|
||||
!
|
||||
! 编译(Windows):
|
||||
! gfortran -O3 -march=native -shared -fPIC -o build/dynamics_f90.dll dynamics_lib.f90
|
||||
! 编译(Linux):
|
||||
! gfortran -O3 -march=native -shared -fPIC -o build/dynamics_f90.so dynamics_lib.f90
|
||||
! 编译(macOS):
|
||||
! gfortran -O3 -march=native -dynamiclib -o build/dynamics_f90.dylib dynamics_lib.f90
|
||||
|
||||
module dynamics_dll
|
||||
use iso_c_binding, only: c_int, c_double, c_funptr, c_f_procpointer, c_associated
|
||||
implicit none
|
||||
private
|
||||
|
||||
real(c_double), parameter :: TWO_PI = 2.0d0 * 3.14159265358979323846d0
|
||||
|
||||
public :: run_dynamics
|
||||
|
||||
contains
|
||||
|
||||
! ── 保守加速度 ───────────────────────────────────────────────
|
||||
subroutine accel_conservative(n, x, y, z, m, Gx, Gy, Gz, &
|
||||
gravity_field, elastic_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, &
|
||||
ax, ay, az)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, n_bonds
|
||||
real(c_double), intent(in) :: x(n), y(n), z(n), m(n)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz
|
||||
integer, intent(in) :: bond_pairs(2, n_bonds)
|
||||
real(c_double), intent(in) :: bond_k(n_bonds), bond_r0(n_bonds)
|
||||
real(c_double), intent(out) :: ax(n), ay(n), az(n)
|
||||
|
||||
integer :: b, ii, jj
|
||||
real(c_double) :: dx, dy, dz, dist, fac, fx, fy, fz_b
|
||||
|
||||
if (gravity_field /= 0) then
|
||||
ax = Gx; ay = Gy; az = Gz
|
||||
else
|
||||
ax = 0.0d0; ay = 0.0d0; az = 0.0d0
|
||||
end if
|
||||
|
||||
if (elastic_force == 0 .or. n_bonds == 0) return
|
||||
|
||||
do b = 1, n_bonds
|
||||
ii = bond_pairs(1, b) + 1 ! 0-based → 1-based
|
||||
jj = bond_pairs(2, b) + 1
|
||||
dx = x(jj)-x(ii); dy = y(jj)-y(ii); dz = z(jj)-z(ii)
|
||||
dist = sqrt(dx*dx + dy*dy + dz*dz)
|
||||
if (dist < 1.0d-12) cycle
|
||||
fac = bond_k(b) * (dist - bond_r0(b)) / dist
|
||||
fx = fac*dx; fy = fac*dy; fz_b = fac*dz
|
||||
ax(ii) = ax(ii) + fx/m(ii); ay(ii) = ay(ii) + fy/m(ii); az(ii) = az(ii) + fz_b/m(ii)
|
||||
ax(jj) = ax(jj) - fx/m(jj); ay(jj) = ay(jj) - fy/m(jj); az(jj) = az(jj) - fz_b/m(jj)
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ── 完整加速度(含阻尼)──────────────────────────────────────
|
||||
subroutine accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax, ay, az)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, damping_force, n_bonds
|
||||
real(c_double), intent(in) :: x(n), y(n), z(n), vx(n), vy(n), vz(n), m(n)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz, Bx, By, Bz
|
||||
integer, intent(in) :: bond_pairs(2, n_bonds)
|
||||
real(c_double), intent(in) :: bond_k(n_bonds), bond_r0(n_bonds)
|
||||
real(c_double), intent(out) :: ax(n), ay(n), az(n)
|
||||
|
||||
integer :: i
|
||||
|
||||
call accel_conservative(n, x, y, z, m, Gx, Gy, Gz, &
|
||||
gravity_field, elastic_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax, ay, az)
|
||||
if (damping_force /= 0) then
|
||||
do i = 1, n
|
||||
ax(i) = ax(i) - Bx*vx(i)/m(i)
|
||||
ay(i) = ay(i) - By*vy(i)/m(i)
|
||||
az(i) = az(i) - Bz*vz(i)/m(i)
|
||||
end do
|
||||
end if
|
||||
end subroutine
|
||||
|
||||
! ── 边界 + 固定约束 ──────────────────────────────────────────
|
||||
subroutine apply_bc(n, x, y, z, vx, vy, vz, fixed, pos_init, box_a)
|
||||
integer, intent(in) :: n
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
integer, intent(in) :: fixed(3, n)
|
||||
real(c_double), intent(in) :: pos_init(3, n), box_a
|
||||
|
||||
integer :: i
|
||||
real(c_double) :: lo, hi
|
||||
|
||||
lo = -box_a; hi = box_a
|
||||
|
||||
! 反弹
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) cycle
|
||||
if (x(i)>hi) then; x(i)=hi; vx(i)=-abs(vx(i)); end if
|
||||
if (x(i)<lo) then; x(i)=lo; vx(i)= abs(vx(i)); end if
|
||||
if (y(i)>hi) then; y(i)=hi; vy(i)=-abs(vy(i)); end if
|
||||
if (y(i)<lo) then; y(i)=lo; vy(i)= abs(vy(i)); end if
|
||||
if (z(i)>hi) then; z(i)=hi; vz(i)=-abs(vz(i)); end if
|
||||
if (z(i)<lo) then; z(i)=lo; vz(i)= abs(vz(i)); end if
|
||||
end do
|
||||
! 回绕
|
||||
do i = 1, n
|
||||
if (x(i)>hi) x(i)=lo; if (x(i)<lo) x(i)=hi
|
||||
if (y(i)>hi) y(i)=lo; if (y(i)<lo) y(i)=hi
|
||||
if (z(i)>hi) z(i)=lo; if (z(i)<lo) z(i)=hi
|
||||
end do
|
||||
! 逐自由度固定约束
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0) then; x(i)=pos_init(1,i); vx(i)=0.0d0; end if
|
||||
if (fixed(2,i)/=0) then; y(i)=pos_init(2,i); vy(i)=0.0d0; end if
|
||||
if (fixed(3,i)/=0) then; z(i)=pos_init(3,i); vz(i)=0.0d0; end if
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ── 蛙跳法 ───────────────────────────────────────────────────
|
||||
subroutine leapfrog_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, damping_force, n_bonds
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
real(c_double), intent(in) :: m(n), bond_k(n_bonds), bond_r0(n_bonds)
|
||||
integer, intent(in) :: fixed(3,n), bond_pairs(2,n_bonds)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz, Bx, By, Bz, dt
|
||||
|
||||
real(c_double) :: ax(n), ay(n), az(n), ax_, ay_, az_
|
||||
logical :: has_damp
|
||||
integer :: i
|
||||
|
||||
call accel_conservative(n, x, y, z, m, Gx, Gy, Gz, &
|
||||
gravity_field, elastic_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax, ay, az)
|
||||
has_damp = (damping_force/=0) .and. (abs(Bx)+abs(By)+abs(Bz) > 0.0d0)
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) cycle
|
||||
if (has_damp) then
|
||||
ax_ = Bx*dt/(2.0d0*m(i)); ay_ = By*dt/(2.0d0*m(i)); az_ = Bz*dt/(2.0d0*m(i))
|
||||
vx(i) = (vx(i)*(1.0d0-ax_) + ax(i)*dt)/(1.0d0+ax_)
|
||||
vy(i) = (vy(i)*(1.0d0-ay_) + ay(i)*dt)/(1.0d0+ay_)
|
||||
vz(i) = (vz(i)*(1.0d0-az_) + az(i)*dt)/(1.0d0+az_)
|
||||
else
|
||||
vx(i) = vx(i)+ax(i)*dt; vy(i) = vy(i)+ay(i)*dt; vz(i) = vz(i)+az(i)*dt
|
||||
end if
|
||||
x(i) = x(i)+vx(i)*dt; y(i) = y(i)+vy(i)*dt; z(i) = z(i)+vz(i)*dt
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ── 显式欧拉法 ───────────────────────────────────────────────
|
||||
subroutine euler_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, damping_force, n_bonds
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
real(c_double), intent(in) :: m(n), bond_k(n_bonds), bond_r0(n_bonds)
|
||||
integer, intent(in) :: fixed(3,n), bond_pairs(2,n_bonds)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz, Bx, By, Bz, dt
|
||||
|
||||
real(c_double) :: ax(n), ay(n), az(n)
|
||||
integer :: i
|
||||
|
||||
call accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax, ay, az)
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) cycle
|
||||
x(i) = x(i)+vx(i)*dt; y(i) = y(i)+vy(i)*dt; z(i) = z(i)+vz(i)*dt
|
||||
vx(i)= vx(i)+ax(i)*dt; vy(i)= vy(i)+ay(i)*dt; vz(i)= vz(i)+az(i)*dt
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ── 隐式欧拉法 ───────────────────────────────────────────────
|
||||
subroutine implicit_euler_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, damping_force, n_bonds
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
real(c_double), intent(in) :: m(n), bond_k(n_bonds), bond_r0(n_bonds)
|
||||
integer, intent(in) :: fixed(3,n), bond_pairs(2,n_bonds)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz, Bx, By, Bz, dt
|
||||
|
||||
real(c_double) :: vxn(n), vyn(n), vzn(n), ax(n), ay(n), az(n)
|
||||
real(c_double) :: gamma_x, gamma_y, gamma_z
|
||||
integer :: i
|
||||
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) then
|
||||
vxn(i)=0.0d0; vyn(i)=0.0d0; vzn(i)=0.0d0; cycle
|
||||
end if
|
||||
gamma_x = Bx/m(i); gamma_y = By/m(i); gamma_z = Bz/m(i)
|
||||
vxn(i) = (vx(i)+Gx*dt)/(1.0d0+gamma_x*dt)
|
||||
vyn(i) = (vy(i)+Gy*dt)/(1.0d0+gamma_y*dt)
|
||||
vzn(i) = (vz(i)+Gz*dt)/(1.0d0+gamma_z*dt)
|
||||
end do
|
||||
call accel_full(n, x, y, z, vxn, vyn, vzn, m, Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax, ay, az)
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) cycle
|
||||
vx(i)=vx(i)+ax(i)*dt; vy(i)=vy(i)+ay(i)*dt; vz(i)=vz(i)+az(i)*dt
|
||||
x(i) =x(i) +vx(i)*dt; y(i) =y(i) +vy(i)*dt; z(i) =z(i) +vz(i)*dt
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ── 中点法 ───────────────────────────────────────────────────
|
||||
subroutine midpoint_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, damping_force, n_bonds
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
real(c_double), intent(in) :: m(n), bond_k(n_bonds), bond_r0(n_bonds)
|
||||
integer, intent(in) :: fixed(3,n), bond_pairs(2,n_bonds)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz, Bx, By, Bz, dt
|
||||
|
||||
real(c_double) :: ax(n), ay(n), az(n)
|
||||
real(c_double) :: xm(n), ym(n), zm(n), vxm(n), vym(n), vzm(n)
|
||||
real(c_double) :: axm(n), aym(n), azm(n)
|
||||
integer :: i
|
||||
|
||||
call accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax, ay, az)
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) then
|
||||
xm(i)=x(i); ym(i)=y(i); zm(i)=z(i)
|
||||
vxm(i)=0.0d0; vym(i)=0.0d0; vzm(i)=0.0d0; cycle
|
||||
end if
|
||||
xm(i) = x(i) +0.5d0*vx(i)*dt; ym(i) = y(i) +0.5d0*vy(i)*dt; zm(i) = z(i) +0.5d0*vz(i)*dt
|
||||
vxm(i) = vx(i)+0.5d0*ax(i)*dt; vym(i) = vy(i)+0.5d0*ay(i)*dt; vzm(i) = vz(i)+0.5d0*az(i)*dt
|
||||
x(i) = x(i) +vxm(i)*dt; y(i) = y(i) +vym(i)*dt; z(i) = z(i) +vzm(i)*dt
|
||||
end do
|
||||
call accel_full(n, xm, ym, zm, vxm, vym, vzm, m, Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, axm, aym, azm)
|
||||
do i = 1, n
|
||||
if (fixed(1,i)/=0 .and. fixed(2,i)/=0 .and. fixed(3,i)/=0) cycle
|
||||
vx(i)=vx(i)+axm(i)*dt; vy(i)=vy(i)+aym(i)*dt; vz(i)=vz(i)+azm(i)*dt
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ── 驱动力施加 ───────────────────────────────────────────────
|
||||
subroutine apply_drive(n, x, y, z, vx, vy, vz, t, step, dt, &
|
||||
nd, drv_idx, drv_amp, drv_freq, drv_phi, &
|
||||
drv_eq, drv_ncycles, drv_has_period, freeze)
|
||||
integer, intent(in) :: n, nd, step
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
real(c_double), intent(in) :: t, dt
|
||||
integer, intent(in) :: drv_idx(nd), drv_has_period(nd)
|
||||
real(c_double), intent(in) :: drv_amp(3,nd), drv_freq(3,nd)
|
||||
real(c_double), intent(in) :: drv_phi(3,nd), drv_eq(3,nd)
|
||||
real(c_double), intent(in) :: drv_ncycles(nd)
|
||||
real(c_double), intent(inout) :: freeze(3,nd)
|
||||
|
||||
integer :: d, idx, ps
|
||||
real(c_double) :: fx, fy, fz, mf, px, py, pz
|
||||
|
||||
do d = 1, nd
|
||||
idx = drv_idx(d) + 1 ! 0-based → 1-based
|
||||
fx = drv_freq(1,d); fy = drv_freq(2,d); fz = drv_freq(3,d)
|
||||
|
||||
if (drv_has_period(d) /= 0) then
|
||||
mf = max(abs(fx), max(abs(fy), abs(fz)))
|
||||
ps = 0
|
||||
if (mf > 1.0d-12) ps = int(drv_ncycles(d)/mf/dt)
|
||||
if (step > ps) then
|
||||
x(idx)=freeze(1,d); y(idx)=freeze(2,d); z(idx)=freeze(3,d)
|
||||
vx(idx)=0.0d0; vy(idx)=0.0d0; vz(idx)=0.0d0
|
||||
cycle
|
||||
end if
|
||||
px = drv_eq(1,d)+drv_amp(1,d)*cos(TWO_PI*fx*t+drv_phi(1,d))
|
||||
py = drv_eq(2,d)+drv_amp(2,d)*cos(TWO_PI*fy*t+drv_phi(2,d))
|
||||
pz = drv_eq(3,d)+drv_amp(3,d)*cos(TWO_PI*fz*t+drv_phi(3,d))
|
||||
if (step == ps) then
|
||||
freeze(1,d)=px; freeze(2,d)=py; freeze(3,d)=pz
|
||||
end if
|
||||
end if
|
||||
x(idx) = drv_eq(1,d)+drv_amp(1,d)*cos(TWO_PI*fx*t+drv_phi(1,d))
|
||||
y(idx) = drv_eq(2,d)+drv_amp(2,d)*cos(TWO_PI*fy*t+drv_phi(2,d))
|
||||
z(idx) = drv_eq(3,d)+drv_amp(3,d)*cos(TWO_PI*fz*t+drv_phi(3,d))
|
||||
vx(idx) = -drv_amp(1,d)*TWO_PI*fx*sin(TWO_PI*fx*t+drv_phi(1,d))
|
||||
vy(idx) = -drv_amp(2,d)*TWO_PI*fy*sin(TWO_PI*fy*t+drv_phi(2,d))
|
||||
vz(idx) = -drv_amp(3,d)*TWO_PI*fz*sin(TWO_PI*fz*t+drv_phi(3,d))
|
||||
end do
|
||||
end subroutine
|
||||
|
||||
! ══════════════════════════════════════════════════════════════
|
||||
! 导出函数:run_dynamics(C 兼容接口,bind(C))
|
||||
! 接口与 C/C++ DLL 完全相同(扁平 C-contiguous 数组)。
|
||||
! ══════════════════════════════════════════════════════════════
|
||||
integer(c_int) function run_dynamics( &
|
||||
n_atoms, pos_init, vel_init, masses, fixed, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, &
|
||||
box_a, dt, NT, NSTEP, warmup_steps, method_id, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, gravity_strength, &
|
||||
n_drivers, drv_idx, drv_amp, drv_freq, drv_phi, drv_eq, &
|
||||
drv_ncycles, drv_has_period, &
|
||||
n_frames, out_x, out_y, out_z, out_vx, out_vy, out_vz, &
|
||||
progress_cb) &
|
||||
bind(C, name="run_dynamics")
|
||||
|
||||
integer(c_int), value, intent(in) :: n_atoms, n_bonds, NT, NSTEP
|
||||
integer(c_int), value, intent(in) :: warmup_steps, method_id
|
||||
integer(c_int), value, intent(in) :: gravity_field, elastic_force, damping_force
|
||||
integer(c_int), value, intent(in) :: n_drivers, n_frames
|
||||
real(c_double), value, intent(in) :: box_a, dt
|
||||
real(c_double), value, intent(in) :: Gx, Gy, Gz, Bx, By, Bz
|
||||
real(c_double), value, intent(in) :: gravity_strength
|
||||
|
||||
! 扁平数组:Python 传入 C-contiguous int32/float64
|
||||
! Fortran 以列优先解释,维度反转:(3,n) 对应 C 的 n×3
|
||||
real(c_double), intent(in) :: pos_init(3, n_atoms)
|
||||
real(c_double), intent(in) :: vel_init(3, n_atoms)
|
||||
real(c_double), intent(in) :: masses(n_atoms)
|
||||
integer(c_int), intent(in) :: fixed(3, n_atoms)
|
||||
integer(c_int), intent(in) :: bond_pairs(2, n_bonds)
|
||||
real(c_double), intent(in) :: bond_k(n_bonds), bond_r0(n_bonds)
|
||||
integer(c_int), intent(in) :: drv_idx(n_drivers)
|
||||
real(c_double), intent(in) :: drv_amp(3, n_drivers)
|
||||
real(c_double), intent(in) :: drv_freq(3, n_drivers)
|
||||
real(c_double), intent(in) :: drv_phi(3, n_drivers)
|
||||
real(c_double), intent(in) :: drv_eq(3, n_drivers)
|
||||
real(c_double), intent(in) :: drv_ncycles(n_drivers)
|
||||
integer(c_int), intent(in) :: drv_has_period(n_drivers)
|
||||
|
||||
real(c_double), intent(out) :: out_x(n_atoms, n_frames)
|
||||
real(c_double), intent(out) :: out_y(n_atoms, n_frames)
|
||||
real(c_double), intent(out) :: out_z(n_atoms, n_frames)
|
||||
real(c_double), intent(out) :: out_vx(n_atoms, n_frames)
|
||||
real(c_double), intent(out) :: out_vy(n_atoms, n_frames)
|
||||
real(c_double), intent(out) :: out_vz(n_atoms, n_frames)
|
||||
|
||||
type(c_funptr), value, intent(in) :: progress_cb
|
||||
|
||||
! 进度回调接口
|
||||
abstract interface
|
||||
subroutine cb_iface(step, total) bind(C)
|
||||
use iso_c_binding
|
||||
integer(c_int), value :: step, total
|
||||
end subroutine
|
||||
end interface
|
||||
procedure(cb_iface), pointer :: cb_ptr
|
||||
|
||||
integer :: n, s, frame_idx, record_steps, prog_interval, nd
|
||||
real(c_double) :: t, tw
|
||||
real(c_double), allocatable :: x(:), y(:), z(:), vx(:), vy(:), vz(:)
|
||||
real(c_double), allocatable :: ax0(:), ay0(:), az0(:)
|
||||
real(c_double), allocatable :: freeze(:,:)
|
||||
logical :: has_cb
|
||||
|
||||
n = n_atoms
|
||||
nd = n_drivers
|
||||
|
||||
allocate(x(n), y(n), z(n), vx(n), vy(n), vz(n))
|
||||
do s = 1, n
|
||||
x(s) = pos_init(1,s); y(s) = pos_init(2,s); z(s) = pos_init(3,s)
|
||||
vx(s) = vel_init(1,s); vy(s) = vel_init(2,s); vz(s) = vel_init(3,s)
|
||||
end do
|
||||
|
||||
allocate(freeze(3, max(nd,1)))
|
||||
freeze = 0.0d0
|
||||
|
||||
has_cb = c_associated(progress_cb)
|
||||
if (has_cb) call c_f_procpointer(progress_cb, cb_ptr)
|
||||
|
||||
! ── 蛙跳法:初始化 v(-dt/2) ─────────────────────────────
|
||||
if (method_id == 3) then
|
||||
allocate(ax0(n), ay0(n), az0(n))
|
||||
call accel_conservative(n, x, y, z, masses, Gx, Gy, Gz, &
|
||||
gravity_field, elastic_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, ax0, ay0, az0)
|
||||
do s = 1, n
|
||||
if (fixed(1,s)/=0 .and. fixed(2,s)/=0 .and. fixed(3,s)/=0) cycle
|
||||
vx(s)=vx(s)-0.5d0*ax0(s)*dt
|
||||
vy(s)=vy(s)-0.5d0*ay0(s)*dt
|
||||
vz(s)=vz(s)-0.5d0*az0(s)*dt
|
||||
end do
|
||||
deallocate(ax0, ay0, az0)
|
||||
end if
|
||||
|
||||
! ── 初始驱动 t=0 ─────────────────────────────────────────
|
||||
if (nd > 0) call apply_drive(n, x, y, z, vx, vy, vz, 0.0d0, 0, dt, &
|
||||
nd, drv_idx, drv_amp, drv_freq, drv_phi, &
|
||||
drv_eq, drv_ncycles, drv_has_period, freeze)
|
||||
|
||||
! ── 预热 ─────────────────────────────────────────────────
|
||||
do s = 0, warmup_steps-1
|
||||
tw = (s+1)*dt
|
||||
if (nd>0) call apply_drive(n, x, y, z, vx, vy, vz, tw, s, dt, &
|
||||
nd, drv_idx, drv_amp, drv_freq, drv_phi, &
|
||||
drv_eq, drv_ncycles, drv_has_period, freeze)
|
||||
call do_step(n, x, y, z, vx, vy, vz, masses, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt, method_id, &
|
||||
pos_init, box_a)
|
||||
end do
|
||||
|
||||
! ── 记录循环 ─────────────────────────────────────────────
|
||||
record_steps = NT - warmup_steps
|
||||
prog_interval = max(1, record_steps/100)
|
||||
frame_idx = 0
|
||||
|
||||
do s = 0, record_steps-1
|
||||
if (has_cb .and. mod(s, prog_interval)==0 .and. s>0) call cb_ptr(s, record_steps)
|
||||
|
||||
t = (s+warmup_steps)*dt
|
||||
if (nd>0) call apply_drive(n, x, y, z, vx, vy, vz, t, s, dt, &
|
||||
nd, drv_idx, drv_amp, drv_freq, drv_phi, &
|
||||
drv_eq, drv_ncycles, drv_has_period, freeze)
|
||||
|
||||
if (mod(s, NSTEP)==0 .and. frame_idx<n_frames) then
|
||||
frame_idx = frame_idx+1
|
||||
out_x(:, frame_idx) = x
|
||||
out_y(:, frame_idx) = y
|
||||
out_z(:, frame_idx) = z
|
||||
out_vx(:, frame_idx) = vx
|
||||
out_vy(:, frame_idx) = vy
|
||||
out_vz(:, frame_idx) = vz
|
||||
end if
|
||||
|
||||
call do_step(n, x, y, z, vx, vy, vz, masses, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt, method_id, &
|
||||
pos_init, box_a)
|
||||
end do
|
||||
|
||||
deallocate(x, y, z, vx, vy, vz, freeze)
|
||||
run_dynamics = 0
|
||||
|
||||
contains
|
||||
|
||||
subroutine do_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt, method_id, &
|
||||
pos_init, box_a)
|
||||
integer, intent(in) :: n, gravity_field, elastic_force, damping_force
|
||||
integer, intent(in) :: n_bonds, method_id
|
||||
real(c_double), intent(inout) :: x(n), y(n), z(n), vx(n), vy(n), vz(n)
|
||||
real(c_double), intent(in) :: m(n), bond_k(n_bonds), bond_r0(n_bonds)
|
||||
integer, intent(in) :: fixed(3,n), bond_pairs(2,n_bonds)
|
||||
real(c_double), intent(in) :: Gx, Gy, Gz, Bx, By, Bz, dt, box_a
|
||||
real(c_double), intent(in) :: pos_init(3, n)
|
||||
|
||||
select case (method_id)
|
||||
case (0)
|
||||
call euler_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
case (1)
|
||||
call implicit_euler_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
case (2)
|
||||
call midpoint_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
case default
|
||||
call leapfrog_step(n, x, y, z, vx, vy, vz, m, fixed, &
|
||||
Gx, Gy, Gz, Bx, By, Bz, &
|
||||
gravity_field, elastic_force, damping_force, &
|
||||
n_bonds, bond_pairs, bond_k, bond_r0, dt)
|
||||
end select
|
||||
call apply_bc(n, x, y, z, vx, vy, vz, fixed, pos_init, box_a)
|
||||
end subroutine do_step
|
||||
|
||||
end function run_dynamics
|
||||
|
||||
end module dynamics_dll
|
||||
@@ -0,0 +1,449 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dynamics 示例案例总览</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0d1117;
|
||||
--surface: #161b22;
|
||||
--border: #30363d;
|
||||
--text: #c9d1d9;
|
||||
--text-dim: #8b949e;
|
||||
--accent: #58a6ff;
|
||||
--green: #3fb950;
|
||||
--orange: #d29922;
|
||||
--red: #f85149;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", Helvetica, Arial, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
padding: 40px 24px;
|
||||
}
|
||||
.container { max-width: 960px; margin: 0 auto; }
|
||||
|
||||
h1 { font-size: 2rem; margin-bottom: 8px; color: #f0f6fc; }
|
||||
h1 small { font-size: 1rem; color: var(--text-dim); font-weight: 400; }
|
||||
.subtitle { color: var(--text-dim); margin-bottom: 32px; }
|
||||
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: #f0f6fc;
|
||||
}
|
||||
|
||||
/* 案例总览表格 */
|
||||
.case-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.case-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
transition: border-color 0.2s, transform 0.2s;
|
||||
}
|
||||
.case-card:hover {
|
||||
border-color: var(--accent);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.case-card .num {
|
||||
display: inline-block;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
background: var(--accent);
|
||||
color: #0d1117;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.case-card h3 {
|
||||
font-size: 1.05rem;
|
||||
margin-bottom: 6px;
|
||||
color: #f0f6fc;
|
||||
}
|
||||
.case-card p {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-dim);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.case-card .meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.tag-python { background: #3572A533; color: #3572A5; }
|
||||
.tag-c { background: #55555533; color: #aaa; }
|
||||
.tag-fortran { background: #73422233; color: #e9954a; }
|
||||
.tag-gravity { background: #d2992233; color: var(--orange); }
|
||||
.tag-spring { background: #3fb95033; color: var(--green); }
|
||||
.tag-drive { background: #58a6ff33; color: var(--accent); }
|
||||
.tag-warning { background: #f8514933; color: var(--red); }
|
||||
|
||||
/* 详情区域 */
|
||||
.detail-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.detail-card h3 {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 8px;
|
||||
color: #f0f6fc;
|
||||
}
|
||||
.detail-card h3 a { color: var(--accent); text-decoration: none; }
|
||||
.detail-card h3 a:hover { text-decoration: underline; }
|
||||
.detail-card p { color: var(--text-dim); margin-bottom: 8px; }
|
||||
.detail-card ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.detail-card li { font-size: 0.8rem; }
|
||||
.detail-card .highlight {
|
||||
background: #1f242e;
|
||||
border-left: 3px solid var(--accent);
|
||||
padding: 8px 12px;
|
||||
margin-top: 8px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* 使用指南 */
|
||||
.guide {
|
||||
background: #1f242e;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.guide h3 { color: #f0f6fc; margin-bottom: 12px; }
|
||||
.guide code {
|
||||
display: block;
|
||||
background: #0d1117;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
font-family: "SF Mono", "Fira Code", monospace;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 12px;
|
||||
color: var(--green);
|
||||
}
|
||||
.guide table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
|
||||
.guide th, .guide td {
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.guide th { color: var(--text-dim); font-weight: 600; }
|
||||
.guide td:first-child { color: var(--accent); font-weight: 500; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
body { padding: 16px; }
|
||||
.case-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<h1>Dynamics 示例案例 <small>v2.1</small></h1>
|
||||
<p class="subtitle">10 个从简单到复杂的物理模拟案例,展示分子动力学模拟框架的多种应用场景</p>
|
||||
|
||||
<h2>📋 案例总览</h2>
|
||||
<div class="case-grid">
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">01</span>
|
||||
<h3>双粒子弹簧系统</h3>
|
||||
<p>两个原子由弹簧连接,在重力场中运动</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-python">Python</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
<span class="tag tag-gravity">重力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">02</span>
|
||||
<h3>行星运动</h3>
|
||||
<p>地球绕太阳椭圆公转(万有引力)</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-python">Python</span>
|
||||
<span class="tag tag-gravity">万有引力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">03</span>
|
||||
<h3>日地月系统(失稳)</h3>
|
||||
<p>三体系统参数不当导致轨道发散</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-python">Python</span>
|
||||
<span class="tag tag-gravity">万有引力</span>
|
||||
<span class="tag tag-warning">失败案例</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">04</span>
|
||||
<h3>日地月系统(稳定)</h3>
|
||||
<p>三体系统稳定轨道,经希尔半径检验</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-python">Python</span>
|
||||
<span class="tag tag-gravity">万有引力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">05</span>
|
||||
<h3>一维原子链纵波</h3>
|
||||
<p>驱动原子 1 沿 x 振动,产生纵波传播</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-python">Python</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
<span class="tag tag-drive">驱动力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">06</span>
|
||||
<h3>一维原子链横波</h3>
|
||||
<p>带阻尼的横波传播(FPU 非线性)</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-c">C 引擎</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
<span class="tag tag-drive">驱动力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">07</span>
|
||||
<h3>一维链横波·双端驱动</h3>
|
||||
<p>原子 1 + 原子 120 同时驱动,波相遇干涉</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-c">C 引擎</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
<span class="tag tag-drive">驱动力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">08</span>
|
||||
<h3>双原子弹簧·C 引擎测试</h3>
|
||||
<p>2 原子快速验证 C 引擎正确性</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-c">C 引擎</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">09</span>
|
||||
<h3>一维链纵波·Fortran 引擎</h3>
|
||||
<p>Fortran 引擎驱动的纵波,GPU 实例化渲染</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-fortran">Fortran</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
<span class="tag tag-drive">驱动力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case-card">
|
||||
<span class="num">10</span>
|
||||
<h3>一维链纵波·能量分析</h3>
|
||||
<p>纵波传播 + 轨迹/能量图绘制</p>
|
||||
<div class="meta">
|
||||
<span class="tag tag-c">C 引擎</span>
|
||||
<span class="tag tag-spring">弹簧</span>
|
||||
<span class="tag tag-drive">驱动力</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<h2>📖 各案例详情</h2>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case01/">case01 — 双粒子弹簧系统</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-python">Python 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
<li class="tag tag-gravity">重力场</li>
|
||||
</ul>
|
||||
<p>两个原子通过弹簧连接,在均匀重力场(G=[0,0,-9.8])中自由运动。展示重力作用下的耦合振动与落体运动的复合。</p>
|
||||
<div class="highlight">🔬 教学案例:算法 leapfrog,渲染 Sphere 模式,建议作为入门第一个案例</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case02/">case02 — 行星运动</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-python">Python 引擎</li>
|
||||
<li class="tag tag-gravity">万有引力</li>
|
||||
</ul>
|
||||
<p>模拟地球绕太阳的椭圆轨道运动。大质量中心体固定,小质量体绕行。</p>
|
||||
<div class="highlight">🌍 万有引力强度 gravity_strength=100.0,leapfrog 算法确保能量守恒</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case03/">case03 — 日地月系统(失败案例)</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-python">Python 引擎</li>
|
||||
<li class="tag tag-gravity">万有引力</li>
|
||||
<li class="tag tag-warning">失稳</li>
|
||||
</ul>
|
||||
<p>三体系统(太阳-地球-月球)。初始条件或参数设置不当,轨道不稳定。展示数值模拟中参数选择的重要性。</p>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case04/">case04 — 日地月系统(成功案例)</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-python">Python 引擎</li>
|
||||
<li class="tag tag-gravity">万有引力</li>
|
||||
</ul>
|
||||
<p>与 case03 相同的三体系统,但采用恰当的初始条件:地球置于近日点($r=10$),$v_z=520$ 接近圆轨道;月球缩至希尔半径($r_H\approx1.0$)以内的 $r=0.5$,相对速度 $v_{\text{rel}}\approx127$,确保月球被地球稳定束缚。</p>
|
||||
<div class="highlight">✅ 与 case03 对比学习:初始条件对数值稳定性的影响。地月距 0.5 在地球希尔半径以内,满足稳定性条件</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case05/">case05 — 一维原子链纵波</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-python">Python 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
<li class="tag tag-drive">驱动力</li>
|
||||
</ul>
|
||||
<p>60 原子沿 x 轴排列。原子 1 受 x 方向驱动力,产生沿链传播的纵波(压缩波)。原子 x 自由,y/z 锁定。</p>
|
||||
<div class="highlight">📈 纵波波速快(x 方向弹簧力线性),T_total=10, NSTEP=50</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case06/">case06 — 一维原子链横波(带阻尼)</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-c">C 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
<li class="tag tag-drive">驱动力</li>
|
||||
</ul>
|
||||
<p>120 原子沿 x 轴排列,带横向阻尼。驱动沿 z 方向,原子 z 自由,x/y 锁定。横波传播具有 FPU 型非线性。</p>
|
||||
<div class="highlight">⚡ C 引擎高性能计算,T_total=1000, NSTEP=500,支持运动相机</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case07/">case07 — 一维链横波·双端驱动</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-c">C 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
<li class="tag tag-drive">驱动力</li>
|
||||
</ul>
|
||||
<p>120 原子,原子 1 和原子 120 同时受 z 方向驱动(同频率、相位差 90°),两端向中间传播的横波相遇。</p>
|
||||
<div class="highlight">🌊 波干涉演示,视觉放大 display_amp=[1,1,10] 便于观察小幅度振动</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case08/">case08 — 双原子弹簧(C 引擎快速测试)</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-c">C 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
</ul>
|
||||
<p>2 原子弹簧系统,T_total=100 的短时模拟。用于快速验证 C 引擎的正确性和性能。</p>
|
||||
<div class="highlight">🧪 引擎快速验证用例,无动画输出</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case09/">case09 — 一维链纵波·Fortran 引擎</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-fortran">Fortran 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
<li class="tag tag-drive">驱动力</li>
|
||||
</ul>
|
||||
<p>40 原子沿 x 轴排列,Fortran 引擎驱动的纵波传播测试。使用 <code>use_marker: 1</code>(GPU 实例化 Marker 模式)加速渲染,验证 Fortran 引擎与其他引擎的输出兼容性。</p>
|
||||
<div class="highlight">🔧 Fortran 引擎兼容性验证,T_total=200, NSTEP=100。Marker 模式下 40 原子以 GPU 点精灵渲染,帧率高</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3><a href="./case10/">case10 — 一维链纵波·能量分析</a></h3>
|
||||
<ul>
|
||||
<li class="tag tag-c">C 引擎</li>
|
||||
<li class="tag tag-spring">弹簧键力</li>
|
||||
<li class="tag tag-drive">驱动力</li>
|
||||
</ul>
|
||||
<p>40 原子纵波传播,支持轨迹/能量图绘制(step_plot=1)。原子 x/y/z 全部自由。</p>
|
||||
<div class="highlight">📊 能量分析演示,T_total=10, NSTEP=20</div>
|
||||
</div>
|
||||
|
||||
<h2>🚀 使用方法</h2>
|
||||
<div class="guide">
|
||||
<h3>命令行</h3>
|
||||
<code># 进入案例目录并运行<br>cd examples/case05<br>python run_dynamics.py<br><br># 仅运行模拟,跳过动画<br>python run_dynamics.py --no-plot<br><br># 手动启动 3D 动画<br>python ../../draw.py output/</code>
|
||||
|
||||
<h3>案例选择指南</h3>
|
||||
<table>
|
||||
<tr><th>目标</th><th>推荐案例</th></tr>
|
||||
<tr><td>快速上手框架</td><td>case01</td></tr>
|
||||
<tr><td>天体力学/万有引力</td><td>case02 / case04</td></tr>
|
||||
<tr><td>波动物理(纵波)</td><td>case05</td></tr>
|
||||
<tr><td>波动物理(横波/非线性/阻尼)</td><td>case06</td></tr>
|
||||
<tr><td>双端驱动/波干涉</td><td>case07</td></tr>
|
||||
<tr><td>引擎性能对比</td><td>case08 (C) / case09 (Fortran)</td></tr>
|
||||
<tr><td>能量分析</td><td>case10</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>配置</h3>
|
||||
<p style="color:var(--text-dim); font-size:0.875rem;">
|
||||
每个案例的 <code>input/input.txt</code> 可配置物理参数、力开关、算法、引擎、渲染方式等。
|
||||
</p>
|
||||
|
||||
<h3>引擎架构</h3>
|
||||
<p style="color:var(--text-dim); font-size:0.875rem;">
|
||||
外部引擎(C / C++ / Fortran)以 <strong>DLL 方式</strong> 运行,主程序通过 ctypes 在进程内直接调用,不启动子进程。DLL 预编译在 <code>engines/release/</code> 中,源码位于 <code>engines/src/{c,cpp,fortran}/</code>。重新编译:
|
||||
<code>cd engines/src/c && make dll</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>📁 框架结构</h2>
|
||||
<div class="detail-card">
|
||||
<pre style="font-size:0.825rem; color:var(--text-dim); line-height:1.5;">
|
||||
dynamics/
|
||||
├── dynamics.py # 统一运行入口
|
||||
├── compute.py # 物理引擎 + 显示数据生成
|
||||
├── draw.py # VisPy 3D 动画
|
||||
├── plot_wave.py # 波形能量图
|
||||
├── .gitattributes # DLL/二进制文件保护
|
||||
├── engines/
|
||||
│ ├── engine_dll.py # DLL 加载器(ctypes)
|
||||
│ ├── python/ # Python 引擎(dynamics_lib.py)
|
||||
│ ├── release/ # 预编译 DLL(C / C++ / Fortran)
|
||||
│ └── src/ # 引擎源码
|
||||
│ ├── c/ # C 源码 + Makefile → dynamics_c.dll
|
||||
│ ├── cpp/ # C++ 源码 + Makefile → dynamics_cpp.dll
|
||||
│ └── fortran/ # Fortran 源码 + Makefile → dynamics_f90.dll
|
||||
├── examples/ # 案例目录
|
||||
│ ├── case01/ ~ case10/
|
||||
└── output/ # 默认输出目录
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+73
-16
@@ -1,19 +1,23 @@
|
||||
# Dynamics 示例案例
|
||||
|
||||
本目录包含 6 个从简单到复杂的物理模拟案例,均基于 `../dynamics.py` 框架运行。
|
||||
本目录包含 10 个从简单到复杂的物理模拟案例,均基于 `../dynamics.py` 框架运行。
|
||||
|
||||
---
|
||||
|
||||
## 案例一览
|
||||
|
||||
| 案例 | 标题 | 简介 | 原子数 | 力类型 |
|
||||
|---|---|---|---|---|
|
||||
| [case01](./case01/) | **双粒子弹簧系统** | 两个原子由弹簧连接,在重力场中运动 | 2 | 重力 + 弹簧 |
|
||||
| [case02](./case02/) | **行星运动** | 地球绕太阳椭圆公转(万有引力) | 2 | 万有引力 |
|
||||
| [case03](./case03/) | **日地月系统(失败)** | 地球绕太阳、月球绕地球,参数不当导致失稳 | 3 | 万有引力 |
|
||||
| [case04](./case04/) | **日地月系统(成功)** | 地球绕太阳、月球绕地球,稳定轨道 | 3 | 万有引力 |
|
||||
| [case05](./case05/) | **一维原子链纵波** | 驱动原子 1 沿 x 轴振动,产生纵波传播 | 60 | 弹簧 + 驱动力 |
|
||||
| [case06](./case06/) | **一维原子链横波** | 驱动原子 1 沿 z 轴振动,产生横波传播 | 120 | 弹簧 + 驱动力 |
|
||||
| 案例 | 标题 | 简介 | 原子数 | 引擎 | 力类型 |
|
||||
|------|------|------|--------|------|--------|
|
||||
| [case01](./case01/) | **双粒子弹簧系统** | 两个原子由弹簧连接,在重力场中运动 | 2 | Python | 重力 + 弹簧 |
|
||||
| [case02](./case02/) | **行星运动** | 地球绕太阳椭圆公转(万有引力) | 2 | Python | 万有引力 |
|
||||
| [case03](./case03/) | **日地月系统(失稳)** | 地球绕太阳、月球绕地球,参数不当导致失稳 | 3 | Python | 万有引力 |
|
||||
| [case04](./case04/) | **日地月系统(稳定)** | 地球绕太阳、月球绕地球,稳定轨道 | 3 | Python | 万有引力 |
|
||||
| [case05](./case05/) | **一维原子链纵波** | 驱动原子 1 沿 x 轴振动,产生纵波传播 | 60 | Python | 弹簧 + 驱动力 |
|
||||
| [case06](./case06/) | **一维原子链横波(阻尼)** | 驱动原子 1 沿 z 轴振动,带阻尼的横波传播 | 120 | C | 弹簧 + 阻尼 + 驱动力 |
|
||||
| [case07](./case07/) | **一维原子链横波(双端驱动)** | 原子 1 和原子 120 同时受 z 方向驱动 | 120 | C | 弹簧 + 阻尼 + 驱动力 |
|
||||
| [case08](./case08/) | **双原子弹簧(C 引擎测试)** | 两个原子弹簧系统,C 引擎快速验证 | 2 | C | 弹簧 + 阻尼 + 驱动力 |
|
||||
| [case09](./case09/) | **一维链纵波(Fortran 引擎)** | Fortran 引擎驱动的纵波传播测试 | 40 | Fortran | 弹簧 + 驱动力 |
|
||||
| [case10](./case10/) | **一维链纵波(能量分析)** | 纵波传播 + 轨迹/能量图绘制 | 40 | C | 弹簧 + 驱动力 |
|
||||
|
||||
---
|
||||
|
||||
@@ -26,14 +30,16 @@
|
||||
- **力开关**:重力场开,弹簧键力开
|
||||
- **算法**:leapfrog(蛙跳法)
|
||||
- **物理**:重力 m·g + 弹簧胡克力
|
||||
- **渲染**:Sphere 模式(精细网格球体)
|
||||
|
||||
### case02 — 行星运动
|
||||
|
||||
模拟地球绕太阳的椭圆轨道运动(一个固定大质量中心体 + 一个绕行小质量体)。采用万有引力相互作用。
|
||||
|
||||
- **力开关**:万有引力开(含强度参数)
|
||||
- **力开关**:万有引力开(含强度参数 `gravity_strength: 100.0`)
|
||||
- **算法**:leapfrog(蛙跳法)
|
||||
- **物理**:牛顿万有引力 F = G·m₁·m₂/r²
|
||||
- **渲染**:Sphere 模式
|
||||
|
||||
### case03 — 日地月系统(失败案例)
|
||||
|
||||
@@ -60,14 +66,53 @@
|
||||
- **波速**:快(x 方向弹簧力为线性)
|
||||
- **渲染**:Marker 模式(GPU 实例化,60 原子)
|
||||
|
||||
### case06 — 一维原子链横波
|
||||
### case06 — 一维原子链横波(带阻尼)
|
||||
|
||||
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(蛙跳法)
|
||||
- **引擎**:C(高性能)
|
||||
- **参数**:T_total=1000, NSTEP=500
|
||||
- **波速**:慢(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
|
||||
|
||||
# 完整运行(模拟 + 采样 + 3D 动画)
|
||||
# 完整运行(模拟 + 3D 动画)
|
||||
python run_dynamics.py
|
||||
|
||||
# 仅运行模拟,跳过 3D 动画
|
||||
@@ -89,6 +134,18 @@ python ../../draw.py output/
|
||||
|
||||
每个案例的 `input/input.txt` 中可配置所有物理参数、力开关、算法、渲染方式等。
|
||||
|
||||
## 案例选择指南
|
||||
|
||||
| 你想做什么 | 推荐案例 |
|
||||
|-----------|---------|
|
||||
| 快速上手、理解基本框架 | case01 |
|
||||
| 天体力学 / 万有引力 | case02 / case04 |
|
||||
| 波动物理(纵波) | case05 |
|
||||
| 波动物理(横波、非线性、阻尼) | case06 |
|
||||
| 双端驱动/波干涉 | case07 |
|
||||
| 对比不同引擎性能 | case08 (C) / case09 (Fortran) |
|
||||
| 能量分析 | case10 |
|
||||
|
||||
## 框架结构
|
||||
|
||||
```
|
||||
@@ -101,6 +158,6 @@ dynamics/
|
||||
├── examples/ # 案例(本目录)
|
||||
│ ├── case01/
|
||||
│ ├── ...
|
||||
│ └── case06/
|
||||
│ └── case10/
|
||||
└── output/ # 默认输出目录
|
||||
```
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
为指定案例添加次紧邻键 (k2, k=100, r0=1.41421356)。
|
||||
|
||||
用法: python add_k2.py case16
|
||||
python add_k2.py case16 case17 case18
|
||||
python add_k2.py --all
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
def add_k2(case_dir):
|
||||
coord_path = os.path.join(case_dir, "input", "coord.txt")
|
||||
conn_path = os.path.join(case_dir, "input", "connection.txt")
|
||||
bond_path = os.path.join(case_dir, "input", "bond.txt")
|
||||
|
||||
if not os.path.exists(coord_path):
|
||||
print(f" [跳过] {case_dir}: 找不到 coord.txt")
|
||||
return False
|
||||
|
||||
# 读取 coord.txt 获取网格尺寸
|
||||
with open(coord_path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
n_atoms = len(lines) - 1 # 去掉表头
|
||||
N = int(n_atoms ** 0.5)
|
||||
if N * N != n_atoms:
|
||||
print(f" [跳过] {case_dir}: 非正方形网格 (n_atoms={n_atoms})")
|
||||
return False
|
||||
|
||||
print(f" {case_dir}: {N}x{N} 网格")
|
||||
|
||||
# 读取现有 connection.txt,检查是否已有 k2
|
||||
has_k2 = False
|
||||
if os.path.exists(conn_path):
|
||||
with open(conn_path, "r") as f:
|
||||
for line in f:
|
||||
if "k2" in line:
|
||||
has_k2 = True
|
||||
break
|
||||
|
||||
if has_k2:
|
||||
print(f" k2 已存在,跳过 connection.txt")
|
||||
else:
|
||||
# 追加 k2 键到 connection.txt
|
||||
with open(conn_path, "a", encoding="utf-8") as f:
|
||||
cnt = 0
|
||||
for row in range(N):
|
||||
for col in range(N):
|
||||
id1 = row * N + col + 1
|
||||
if col + 1 < N and row + 1 < N:
|
||||
f.write(f"{id1} {(row + 1) * N + (col + 1) + 1} k2\n")
|
||||
cnt += 1
|
||||
if col - 1 >= 0 and row + 1 < N:
|
||||
f.write(f"{id1} {(row + 1) * N + (col - 1) + 1} k2\n")
|
||||
cnt += 1
|
||||
print(f" connection.txt: 追加 {cnt} 条 k2 键")
|
||||
|
||||
# 检查 bond.txt 是否有 k2
|
||||
has_bond = False
|
||||
if os.path.exists(bond_path):
|
||||
with open(bond_path, "r") as f:
|
||||
for line in f:
|
||||
if line.startswith("k2"):
|
||||
has_bond = True
|
||||
break
|
||||
|
||||
if has_bond:
|
||||
print(f" bond.txt: k2 已存在")
|
||||
else:
|
||||
with open(bond_path, "a", encoding="utf-8") as f:
|
||||
f.write("k2 100.0 1.41421356\n")
|
||||
print(f" bond.txt: 追加 k2 定义")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
targets = []
|
||||
if "--all" in sys.argv:
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
for d in sorted(os.listdir(base)):
|
||||
if d.startswith("case") and os.path.isdir(os.path.join(base, d)):
|
||||
targets.append(os.path.join(base, d))
|
||||
else:
|
||||
for arg in sys.argv[1:]:
|
||||
if arg.startswith("--"):
|
||||
continue
|
||||
p = arg if os.path.isabs(arg) else os.path.join(os.path.dirname(os.path.abspath(__file__)), arg)
|
||||
targets.append(p)
|
||||
|
||||
for t in targets:
|
||||
add_k2(t)
|
||||
@@ -6,7 +6,7 @@
|
||||
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||
step_simulate: 1 # 运行物理模拟 → output/trajectory.txt
|
||||
step_sample: 1 # 抽帧 → output/display.txt
|
||||
step_sample: 0 # 抽帧 → output/display.txt
|
||||
step_plot: 1 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||
step_plot_wave: 0 # 绘制波形能量动画
|
||||
plot_wave_save_gif: 0 # 输出波形 GIF(需 step_plot_wave=1)
|
||||
|
||||
@@ -0,0 +1,829 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case04 — 日地月三体系统</title>
|
||||
<script>
|
||||
MathJax = {
|
||||
tex: { inlineMath: [['$','$'], ['\\(','\\)']], displayMath: [['$$','$$'], ['\\[','\\]']] },
|
||||
svg: { fontCache: 'global' }
|
||||
};
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js" async></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0d1117;
|
||||
--surface: #161b22;
|
||||
--border: #30363d;
|
||||
--text: #c9d1d9;
|
||||
--text-dim: #8b949e;
|
||||
--accent: #58a6ff;
|
||||
--green: #3fb950;
|
||||
--orange: #d29922;
|
||||
--red: #f85149;
|
||||
--teal: #56d4dd;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", Helvetica, Arial, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.8;
|
||||
padding: 40px 24px;
|
||||
}
|
||||
.container { max-width: 860px; margin: 0 auto; }
|
||||
|
||||
h1 { font-size: 1.8rem; margin-bottom: 6px; color: #f0f6fc; }
|
||||
h1 small { font-size: 1rem; color: var(--text-dim); font-weight: 400; }
|
||||
.subtitle { color: var(--text-dim); margin-bottom: 32px; }
|
||||
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
margin-top: 36px;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: #f0f6fc;
|
||||
}
|
||||
h3 { font-size: 1.1rem; margin-top: 24px; margin-bottom: 10px; color: #f0f6fc; }
|
||||
|
||||
p { margin-bottom: 12px; color: var(--text); }
|
||||
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 12px 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
th, td {
|
||||
text-align: left;
|
||||
padding: 8px 14px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { color: var(--text-dim); font-weight: 600; background: #1c2128; }
|
||||
|
||||
.highlight {
|
||||
background: #1f242e;
|
||||
border-left: 3px solid var(--accent);
|
||||
padding: 10px 16px;
|
||||
margin: 12px 0;
|
||||
border-radius: 0 6px 6px 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.highlight-eq {
|
||||
background: #1f242e;
|
||||
border-left: 3px solid var(--teal);
|
||||
padding: 14px 18px;
|
||||
margin: 14px 0;
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
.highlight-eq p { margin: 6px 0; }
|
||||
|
||||
code {
|
||||
background: #1c2128;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: "SF Mono", "Fira Code", monospace;
|
||||
font-size: 0.85em;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
margin: 2px;
|
||||
}
|
||||
.tag-python { background: #3572A533; color: #3572A5; }
|
||||
.tag-gravity { background: #d2992233; color: var(--orange); }
|
||||
|
||||
ul, ol { margin: 8px 0 12px 24px; }
|
||||
li { margin-bottom: 4px; }
|
||||
|
||||
.formula-block {
|
||||
overflow-x: auto;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.diagram-wrap {
|
||||
background: #0d1117;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 16px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.diagram-wrap canvas {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.diagram-controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.diagram-controls label {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
.diagram-controls input[type="range"] {
|
||||
width: 200px;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
.diagram-controls .val {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: var(--teal);
|
||||
min-width: 48px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
.diagram-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-top: 10px;
|
||||
font-size: 0.82rem;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
.diagram-info span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.diagram-info .dot {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
body { padding: 16px; }
|
||||
.diagram-controls { flex-direction: column; gap: 8px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<h1>case04 — 日地月三体系统 <small>稳定轨道版本</small></h1>
|
||||
<p class="subtitle">太阳、地球、月球三体系统,采用真实比例的质量和万有引力模拟,地球和月球维持稳定椭圆轨道。</p>
|
||||
|
||||
<div class="card" style="display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-bottom:20px;">
|
||||
<span class="tag tag-python">Python 引擎</span>
|
||||
<span class="tag tag-gravity">万有引力</span>
|
||||
<span style="color:var(--text-dim); font-size:0.85rem; margin-left:8px;">3 粒子 | leapfrog 算法 | 稳定轨道</span>
|
||||
</div>
|
||||
|
||||
<h2>一、物理系统概览</h2>
|
||||
|
||||
<div class="card">
|
||||
<p>本案例模拟真实的日-地-月三体系统:</p>
|
||||
<ul>
|
||||
<li><strong>太阳</strong>:位于原点,质量为 $M_\odot$,固定不动(fix_x=fix_y=fix_z=1)</li>
|
||||
<li><strong>地球</strong>:绕太阳公转,质量为 $M_\oplus$,近日点出发</li>
|
||||
<li><strong>月球</strong>:绕地球公转(同时跟随地球绕太阳),质量为 $M_\text{moon}$</li>
|
||||
</ul>
|
||||
<p>原子间万有引力由 <code>gravity_strength</code> 缩放控制,$F = G \dfrac{m_1 m_2}{r^2}$。</p>
|
||||
</div>
|
||||
|
||||
<h2>二、天体真实参数</h2>
|
||||
|
||||
<h3>2.1 质量</h3>
|
||||
|
||||
<table>
|
||||
<tr><th>天体</th><th>质量 / kg</th><th>模拟质量(月球=1)</th></tr>
|
||||
<tr><td>太阳</td><td>$1.989 \times 10^{30}$</td><td>$27\,000$</td></tr>
|
||||
<tr><td>地球</td><td>$5.972 \times 10^{24}$</td><td>$81$</td></tr>
|
||||
<tr><td>月球</td><td>$7.35 \times 10^{22}$</td><td>$1$</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>质量比</strong>:$M_\odot : M_\oplus : M_\text{moon} \approx 27\,100\,000 : 81.3 : 1$<br>
|
||||
模拟中采用缩放比例 $27\,000 : 81 : 1$(太阳质量缩至 $1/1000$ 以保持数值稳定)。
|
||||
</div>
|
||||
|
||||
<h3>2.2 轨道参数</h3>
|
||||
|
||||
<table>
|
||||
<tr><th>轨道</th><th>半长轴 $a$</th><th>偏心率 $e$</th><th>半短轴 $b$</th><th>周期</th></tr>
|
||||
<tr><td>地球绕太阳</td><td>$1.4960 \times 10^8$ km (1 AU)</td><td>$0.0167$</td><td>$1.4958 \times 10^8$ km</td><td>365.25 天</td></tr>
|
||||
<tr><td>月球绕地球</td><td>$3.844 \times 10^5$ km</td><td>$0.0549$</td><td>$3.838 \times 10^5$ km</td><td>27.32 天</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>比例</strong>:$R_{\text{日地}} : R_{\text{地月}} \approx 389 : 1$<br>
|
||||
地月距离约为日地距离的 $1/389$。
|
||||
</div>
|
||||
|
||||
<h2>三、轨道力学</h2>
|
||||
|
||||
<h3>3.1 交互式轨道示意图</h3>
|
||||
|
||||
<p>拖动下方滑块改变偏心率 $e$,观察轨道形状的变化和参数标注:</p>
|
||||
|
||||
<div class="diagram-wrap">
|
||||
<canvas id="orbitCanvas" width="760" height="400"></canvas>
|
||||
<div class="diagram-controls">
|
||||
<label>偏心率 e = <span class="val" id="eDisplay">0.0170</span>(地球真实值 0.0167)</label>
|
||||
<input type="range" id="eSlider" min="0" max="850" value="17">
|
||||
</div>
|
||||
<div class="diagram-info" id="diagramInfo"></div>
|
||||
</div>
|
||||
|
||||
<h3>3.2 月球绕地球轨道</h3>
|
||||
|
||||
<p>拖动下方滑块改变月球轨道偏心率 $e_\text{moon}$(真实值 0.0549):</p>
|
||||
|
||||
<div class="diagram-wrap">
|
||||
<canvas id="moonCanvas" width="760" height="400"></canvas>
|
||||
<div class="diagram-controls">
|
||||
<label>偏心率 e = <span class="val" id="moonEDisplay">0.0549</span>(月球真实值 0.0549)</label>
|
||||
<input type="range" id="moonESlider" min="0" max="850" value="55">
|
||||
</div>
|
||||
<div class="diagram-info" id="moonInfo"></div>
|
||||
</div>
|
||||
|
||||
<h3>3.3 偏心率定义</h3>
|
||||
|
||||
<div class="highlight-eq">
|
||||
<p>偏心率 $e$ 描述椭圆轨道偏离正圆的程度:</p>
|
||||
<div class="formula-block">
|
||||
$$e = \frac{c}{a}$$
|
||||
</div>
|
||||
<p>其中 $c = ea$ 为偏心距(焦点到椭圆中心的距离),$a$ 为半长轴。</p>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr><th>$e$ 值</th><th>轨道形状</th><th>说明</th></tr>
|
||||
<tr><td>$e = 0$</td><td>正圆形</td><td>速度恒定,距离恒定</td></tr>
|
||||
<tr><td>$0 < e < 1$</td><td>椭圆</td><td>近日点/近地点速度最大,远日点/远地点速度最小</td></tr>
|
||||
<tr><td>$e = 1$</td><td>抛物线</td><td>逃逸轨道,速度恰好达到逃逸速度</td></tr>
|
||||
<tr><td>$e > 1$</td><td>双曲线</td><td>飞越轨道,速度超过逃逸速度</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>3.4 半长轴与半短轴的关系</h3>
|
||||
|
||||
<div class="highlight-eq">
|
||||
<div class="formula-block">
|
||||
$$b = a\sqrt{1 - e^2}$$
|
||||
</div>
|
||||
<p>当 $e \ll 1$ 时,$b \approx a\left(1 - \dfrac{e^2}{2}\right)$,椭圆度非常微小。</p>
|
||||
</div>
|
||||
|
||||
<h3>3.5 近日点与远日点</h3>
|
||||
|
||||
<div class="highlight-eq">
|
||||
<p>近日点(距太阳最近)和远日点(距太阳最远)的距离分别为:</p>
|
||||
<div class="formula-block">
|
||||
$$r_{\text{peri}} = a(1 - e), \qquad r_{\text{ap}} = a(1 + e)$$
|
||||
</div>
|
||||
<p>在近日点轨道速度最大,在远日点轨道速度最小:</p>
|
||||
<div class="formula-block">
|
||||
$$v_{\text{peri}} = \sqrt{\frac{GM(1+e)}{a(1-e)}}, \qquad
|
||||
v_{\text{ap}} = \sqrt{\frac{GM(1-e)}{a(1+e)}}$$
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>3.6 开普勒三大定律</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>椭圆定律</strong>:行星轨道是椭圆,太阳位于椭圆的一个焦点上。</li>
|
||||
<li><strong>面积定律</strong>:行星与太阳的连线在相等时间内扫过相等的面积。</li>
|
||||
<li><strong>周期定律</strong>:公转周期的平方与半长轴的立方成正比:$T^2 \propto a^3$。</li>
|
||||
</ol>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>验证</strong>:在本模拟中,你可以通过轨迹图观察面积定律是否成立——地球在近日点移动更快,远日点移动更慢。
|
||||
</div>
|
||||
|
||||
<h2>四、模拟参数</h2>
|
||||
|
||||
<h3>4.1 缩放说明</h3>
|
||||
|
||||
<p>真实尺度无法直接用于模拟(日地距离 1.5 亿 km),因此采用缩放参数。当前 case04 的配置为教学演示而简化:</p>
|
||||
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td><code>gravity_strength</code></td><td>100.0</td><td>万有引力强度</td></tr>
|
||||
<tr><td><code>box_a</code></td><td>30.0</td><td>盒子半边长</td></tr>
|
||||
<tr><td><code>method</code></td><td>leapfrog</td><td>蛙跳法(辛积分器,能量守恒)</td></tr>
|
||||
<tr><td><code>T_total</code></td><td>10.0 s</td><td>总模拟时间</td></tr>
|
||||
<tr><td><code>NSTEP</code></td><td>2</td><td>抽帧间隔(密采帧)</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>4.2 初始构型</h3>
|
||||
|
||||
<table>
|
||||
<tr><th>天体</th><th>质量</th><th>位置 $(x,y,z)$</th><th>速度 $(v_x,v_y,v_z)$</th><th>固定约束</th></tr>
|
||||
<tr><td>太阳</td><td>$27\,000$</td><td>$(0,0,0)$</td><td>$(0,0,0)$</td><td>全部固定</td></tr>
|
||||
<tr><td>地球</td><td>$81$</td><td>$(10,0,0)$</td><td>$(0,0,520)$</td><td>无</td></tr>
|
||||
<tr><td>月球</td><td>$1$</td><td>$(10.5,0,0)$</td><td>$(0,0,647)$</td><td>无</td></tr>
|
||||
</table>
|
||||
|
||||
<p>地球在 $z$ 方向获得初速度 $v=520$,产生绕太阳的轨道运动(接近圆轨道);月球在地球基础上附加 $v\approx127$ 的绕地速度,形成绕地球的轨道。速度由圆形轨道公式 $v = \sqrt{G_{\text{eff}} M / r}$ 计算,其中 $G_{\text{eff}} = \text{gravity\_strength} = 100$。</p>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>验证</strong>:地球速度 $v_\oplus = \sqrt{100 \times 27\,000 / 10} \approx 519.6$,设 520 正确。月球相对速度 $v_{\text{rel}} = \sqrt{100 \times 81 / 0.5} \approx 127.3$,设 127 正确。地月距离 0.5 在地球希尔半径 $r_H \approx 10 \times (81 / 81\,000)^{1/3} \approx 1.0$ 之内,可确保轨道稳定。
|
||||
</div>
|
||||
|
||||
<h2>五、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<code style="display:block; padding:14px 18px; margin-bottom:10px;">
|
||||
# 进入 case04 目录并运行<br>
|
||||
cd examples/case04<br>
|
||||
python run_dynamics.py<br><br>
|
||||
# 仅输出轨迹图,跳过动画<br>
|
||||
python run_dynamics.py --no-plot
|
||||
</code>
|
||||
<p>配置文件:<code>input/input.txt</code>(物理参数)、<code>input/coord.txt</code>(初始位置/速度)。</p>
|
||||
</div>
|
||||
|
||||
<h2>六、与 case03 的对比</h2>
|
||||
|
||||
<table>
|
||||
<tr><th></th><th>case03(失败案例)</th><th>case04(成功案例)</th></tr>
|
||||
<tr><td>轨道状态</td><td>轨道发散或碰撞</td><td>稳定椭圆轨道</td></tr>
|
||||
<tr><td>关键差异</td><td>初始速度或质量比不恰当</td><td>合理的初值和参数</td></tr>
|
||||
<tr><td>教学意义</td><td>展示参数选择的重要性</td><td>展示正确的三体运动</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>教学建议</strong>:先运行 case03 观察失稳,再运行 case04 对比稳定轨道,理解初始条件对数值模拟的关键影响。
|
||||
</div>
|
||||
|
||||
<h2>七、已知局限</h2>
|
||||
|
||||
<ul>
|
||||
<li>太阳真实质量为月球 $27\,100\,000$ 倍,模拟中缩至 $27\,000$ 倍以保持数值稳定($1/1000$)</li>
|
||||
<li>轨道半径未按真实比例缩放(真实日地距是地月距的 389 倍,模拟中约为 20 倍,受希尔半径约束,月球已尽可能放远)</li>
|
||||
<li>未考虑月球轨道倾角(真实地月轨道有约 5° 的倾角)</li>
|
||||
<li>leapfrog 算法为能量守恒的辛积分器,长期稳定,但步长过大时仍可能偏离真实轨道</li>
|
||||
<li>太阳固定不动可作为教学简化,但严格三体模拟中应让所有天体在质心系中自由运动</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var canvas = document.getElementById('orbitCanvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
var slider = document.getElementById('eSlider');
|
||||
var eDisplay = document.getElementById('eDisplay');
|
||||
var infoDiv = document.getElementById('diagramInfo');
|
||||
|
||||
var W = 760, H = 400;
|
||||
var cx = 380, cy = 200;
|
||||
|
||||
// 实际绘图区域半径
|
||||
var A = 140;
|
||||
|
||||
function draw(e) {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
|
||||
var b = A * Math.sqrt(1 - e * e);
|
||||
var f = e * A;
|
||||
|
||||
// 长轴辅助线
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx - A - 20, cy);
|
||||
ctx.lineTo(cx + A + 20, cy);
|
||||
ctx.strokeStyle = '#30363d';
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.setLineDash([4, 3]);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// 椭圆轨道(中心在 cx+f,左焦点=cx 为太阳)
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(cx + f, cy, A, b, 0, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = '#58a6ff';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
|
||||
// 半长轴 a(标注线,从椭圆中心到右顶点)
|
||||
var aY = cy + 28;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f, aY);
|
||||
ctx.lineTo(cx + f + A, aY);
|
||||
ctx.strokeStyle = '#c9d1d9';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
// 箭头(左端:椭圆中心 cx+f)
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f + 6, aY - 4);
|
||||
ctx.lineTo(cx + f, aY);
|
||||
ctx.lineTo(cx + f + 6, aY + 4);
|
||||
ctx.strokeStyle = '#c9d1d9';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
// 箭头(右端:右顶点 cx+f+A)
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f + A - 6, aY - 4);
|
||||
ctx.lineTo(cx + f + A, aY);
|
||||
ctx.lineTo(cx + f + A - 6, aY + 4);
|
||||
ctx.strokeStyle = '#c9d1d9';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#c9d1d9';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('a = ' + A.toFixed(0), cx + f + A/2, aY + 6);
|
||||
|
||||
// 偏心距 c = ea(从左焦点到椭圆中心)
|
||||
var cY = cy - b - 20;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx, cY);
|
||||
ctx.lineTo(cx + f, cY);
|
||||
ctx.strokeStyle = '#d29922';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
// 箭头(左端:左焦点=cx 太阳)
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + 5, cY - 4);
|
||||
ctx.lineTo(cx, cY);
|
||||
ctx.lineTo(cx + 5, cY + 4);
|
||||
ctx.strokeStyle = '#d29922';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
// 箭头(右端:椭圆中心 cx+f)
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f - 5, cY - 4);
|
||||
ctx.lineTo(cx + f, cY);
|
||||
ctx.lineTo(cx + f - 5, cY + 4);
|
||||
ctx.strokeStyle = '#d29922';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#d29922';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('c = ea = ' + f.toFixed(1), cx + f/2, cY - 4);
|
||||
|
||||
// 半短轴 b 标注(从椭圆中心到上顶点)
|
||||
var bX = cx + f + A + 18;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bX, cy);
|
||||
ctx.lineTo(bX, cy - b);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bX - 4, cy - 6);
|
||||
ctx.lineTo(bX, cy);
|
||||
ctx.lineTo(bX + 4, cy - 6);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bX - 4, cy - b + 6);
|
||||
ctx.lineTo(bX, cy - b);
|
||||
ctx.lineTo(bX + 4, cy - b + 6);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#3fb950';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText('b = ' + b.toFixed(1), bX + 8, cy - b/2);
|
||||
|
||||
// 太阳(焦点)
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, 7, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#d29922';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#f0883e';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#f0f6fc';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('太阳(左焦点)', cx, cy - 12);
|
||||
|
||||
// 近日点(左顶点:距左焦点最近)
|
||||
var periX = cx + f - A;
|
||||
ctx.fillStyle = '#58a6ff';
|
||||
ctx.beginPath();
|
||||
ctx.arc(periX, cy, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#8b949e';
|
||||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('近日点 r = a(1-e) = ' + (A * (1 - e)).toFixed(1), periX, cy + 12);
|
||||
|
||||
// 远日点标注(右顶点:距左焦点最远)
|
||||
var apX = cx + f + A;
|
||||
ctx.fillStyle = '#58a6ff';
|
||||
ctx.beginPath();
|
||||
ctx.arc(apX, cy, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#8b949e';
|
||||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('远日点 r = a(1+e) = ' + (A * (1 + e)).toFixed(1), apX, cy + 12);
|
||||
|
||||
// 地球(轨道上的点)
|
||||
var angle = 0.4;
|
||||
var ex = cx + f + A * Math.cos(angle);
|
||||
var ey = cy - b * Math.sin(angle);
|
||||
ctx.beginPath();
|
||||
ctx.arc(ex, ey, 5, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#58a6ff';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#1f6feb';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#58a6ff';
|
||||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('地球', ex + 8, ey);
|
||||
|
||||
// 速度矢量
|
||||
var vlen = 32;
|
||||
var vx = -A * Math.sin(angle) * 0.7;
|
||||
var vy = b * Math.cos(angle) * 0.7;
|
||||
var vl = Math.sqrt(vx*vx + vy*vy);
|
||||
if (vl > 0) {
|
||||
vx = vx / vl * vlen;
|
||||
vy = vy / vl * vlen;
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(ex, ey);
|
||||
ctx.lineTo(ex + vx, ey + vy);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
var tipX = ex + vx, tipY = ey + vy;
|
||||
var a1 = 0.3;
|
||||
ctx.moveTo(tipX, tipY);
|
||||
ctx.lineTo(tipX - vx * a1 + vy * a1 * 0.5, tipY - vy * a1 - vx * a1 * 0.5);
|
||||
ctx.lineTo(tipX - vx * a1 - vy * a1 * 0.5, tipY - vy * a1 + vx * a1 * 0.5);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = '#3fb950';
|
||||
ctx.fill();
|
||||
|
||||
// 信息面板
|
||||
var roundE = Math.round(e * 10000) / 10000;
|
||||
infoDiv.innerHTML =
|
||||
'<span><span class="dot" style="background:#58a6ff"></span>椭圆轨道 e = ' + roundE.toFixed(4) + '</span>' +
|
||||
'<span><span class="dot" style="background:#d29922"></span>偏心距 c = ' + f.toFixed(1) + '</span>' +
|
||||
'<span><span class="dot" style="background:#3fb950"></span>b/a = ' + (b / A).toFixed(4) + '</span>' +
|
||||
'<span>近日点 ' + (A * (1 - e)).toFixed(1) + ' / 远日点 ' + (A * (1 + e)).toFixed(1) + '</span>';
|
||||
}
|
||||
|
||||
function update() {
|
||||
var val = parseInt(slider.value);
|
||||
var e = val / 1000;
|
||||
eDisplay.textContent = e.toFixed(4);
|
||||
draw(e);
|
||||
}
|
||||
|
||||
slider.addEventListener('input', update);
|
||||
update();
|
||||
})();
|
||||
|
||||
// ── 月球绕地球轨道图 ──
|
||||
(function() {
|
||||
var canvas = document.getElementById('moonCanvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
var slider = document.getElementById('moonESlider');
|
||||
var eDisplay = document.getElementById('moonEDisplay');
|
||||
var infoDiv = document.getElementById('moonInfo');
|
||||
|
||||
var W = 760, H = 400;
|
||||
var cx = 380, cy = 200;
|
||||
var A = 140;
|
||||
|
||||
function draw(e) {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
|
||||
var b = A * Math.sqrt(1 - e * e);
|
||||
var f = e * A;
|
||||
|
||||
// 长轴辅助线
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx - A - 20, cy);
|
||||
ctx.lineTo(cx + A + 20, cy);
|
||||
ctx.strokeStyle = '#30363d';
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.setLineDash([4, 3]);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// 椭圆轨道(中心在 cx+f,左焦点=cx 为地球)
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(cx + f, cy, A, b, 0, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = '#7ee787';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
|
||||
// 半长轴 a(标注线,从椭圆中心到右顶点)
|
||||
var aY = cy + 28;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f, aY);
|
||||
ctx.lineTo(cx + f + A, aY);
|
||||
ctx.strokeStyle = '#c9d1d9';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f + 6, aY - 4);
|
||||
ctx.lineTo(cx + f, aY);
|
||||
ctx.lineTo(cx + f + 6, aY + 4);
|
||||
ctx.strokeStyle = '#c9d1d9';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f + A - 6, aY - 4);
|
||||
ctx.lineTo(cx + f + A, aY);
|
||||
ctx.lineTo(cx + f + A - 6, aY + 4);
|
||||
ctx.strokeStyle = '#c9d1d9';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#c9d1d9';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('a = ' + A.toFixed(0), cx + f + A/2, aY + 6);
|
||||
|
||||
// 偏心距 c = ea(从左焦点=地球 到椭圆中心)
|
||||
var cY = cy - b - 20;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx, cY);
|
||||
ctx.lineTo(cx + f, cY);
|
||||
ctx.strokeStyle = '#d29922';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + 5, cY - 4);
|
||||
ctx.lineTo(cx, cY);
|
||||
ctx.lineTo(cx + 5, cY + 4);
|
||||
ctx.strokeStyle = '#d29922';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + f - 5, cY - 4);
|
||||
ctx.lineTo(cx + f, cY);
|
||||
ctx.lineTo(cx + f - 5, cY + 4);
|
||||
ctx.strokeStyle = '#d29922';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#d29922';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('c = ea = ' + f.toFixed(1), cx + f/2, cY - 4);
|
||||
|
||||
// 半短轴 b 标注(从椭圆中心到上顶点)
|
||||
var bX = cx + f + A + 18;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bX, cy);
|
||||
ctx.lineTo(bX, cy - b);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bX - 4, cy - 6);
|
||||
ctx.lineTo(bX, cy);
|
||||
ctx.lineTo(bX + 4, cy - 6);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bX - 4, cy - b + 6);
|
||||
ctx.lineTo(bX, cy - b);
|
||||
ctx.lineTo(bX + 4, cy - b + 6);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#3fb950';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText('b = ' + b.toFixed(1), bX + 8, cy - b/2);
|
||||
|
||||
// 地球(左焦点)
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, 8, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#58a6ff';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#1f6feb';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#f0f6fc';
|
||||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('地球(左焦点)', cx, cy - 14);
|
||||
|
||||
// 近地点(左顶点:距地球最近)
|
||||
var periX = cx + f - A;
|
||||
ctx.fillStyle = '#7ee787';
|
||||
ctx.beginPath();
|
||||
ctx.arc(periX, cy, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#8b949e';
|
||||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('近地点 r = a(1-e) = ' + (A * (1 - e)).toFixed(1), periX, cy + 12);
|
||||
|
||||
// 远地点(右顶点:距地球最远)
|
||||
var apX = cx + f + A;
|
||||
ctx.fillStyle = '#7ee787';
|
||||
ctx.beginPath();
|
||||
ctx.arc(apX, cy, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#8b949e';
|
||||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('远地点 r = a(1+e) = ' + (A * (1 + e)).toFixed(1), apX, cy + 12);
|
||||
|
||||
// 月球(轨道上的点)
|
||||
var angle = 0.6;
|
||||
var mx = cx + f + A * Math.cos(angle);
|
||||
var my = cy - b * Math.sin(angle);
|
||||
ctx.beginPath();
|
||||
ctx.arc(mx, my, 4, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#e2e8f0';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#8b949e';
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#e2e8f0';
|
||||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('月球', mx + 8, my);
|
||||
|
||||
// 速度矢量
|
||||
var vlen = 32;
|
||||
var vx = -A * Math.sin(angle) * 0.7;
|
||||
var vy = b * Math.cos(angle) * 0.7;
|
||||
var vl = Math.sqrt(vx*vx + vy*vy);
|
||||
if (vl > 0) { vx = vx/vl*vlen; vy = vy/vl*vlen; }
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(mx, my);
|
||||
ctx.lineTo(mx + vx, my + vy);
|
||||
ctx.strokeStyle = '#3fb950';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
var tipX = mx + vx, tipY = my + vy;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(tipX, tipY);
|
||||
ctx.lineTo(tipX - vx*0.3 + vy*0.15, tipY - vy*0.3 - vx*0.15);
|
||||
ctx.lineTo(tipX - vx*0.3 - vy*0.15, tipY - vy*0.3 + vx*0.15);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = '#3fb950';
|
||||
ctx.fill();
|
||||
|
||||
// 信息面板
|
||||
var roundE = Math.round(e * 10000) / 10000;
|
||||
infoDiv.innerHTML =
|
||||
'<span><span class="dot" style="background:#7ee787"></span>椭圆轨道 e = ' + roundE.toFixed(4) + '</span>' +
|
||||
'<span><span class="dot" style="background:#d29922"></span>偏心距 c = ' + f.toFixed(1) + '</span>' +
|
||||
'<span><span class="dot" style="background:#3fb950"></span>b/a = ' + (b / A).toFixed(4) + '</span>' +
|
||||
'<span>近地点 ' + (A * (1 - e)).toFixed(1) + ' / 远地点 ' + (A * (1 + e)).toFixed(1) + '</span>';
|
||||
}
|
||||
|
||||
function update() {
|
||||
var val = parseInt(slider.value);
|
||||
var e = val / 1000;
|
||||
eDisplay.textContent = e.toFixed(4);
|
||||
draw(e);
|
||||
}
|
||||
|
||||
slider.addEventListener('input', update);
|
||||
update();
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +1,4 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.28 0 0 0 0 0 0 1 1 1
|
||||
2 1 0.28 4 0 0 0 0 4 0 0 0
|
||||
3 0.1 0.18 5 0 0 0 0 6 0 0 0
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 27000 1.0 0 0 0 0 0 0 1 1 1
|
||||
2 81 0.2 10 0 0 0 0 520 0 0 0
|
||||
3 1 0.1 10.5 0 0 0 0 647 0 0 0
|
||||
@@ -23,7 +23,7 @@ force_calc: 0 # 强制重新计算:1=跳过缓存强算,0=自动使用
|
||||
engine: python # 默认使用 Python 引擎
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 20.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
box_a: 30.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
# 坐标文件格式:
|
||||
@@ -74,7 +74,7 @@ T_total: 10.0
|
||||
NSTEP: 2
|
||||
|
||||
# ── 时间步长 ──────────────────────────────────
|
||||
DT: 0.001 # 时间步长 (s)
|
||||
DT: 0.0001 # 时间步长 (s)
|
||||
|
||||
# 抽帧范围:只保存 [sample_start, sample_end) 区间内的帧
|
||||
sample_start: null # null 表示从头开始(帧索引从 0 起)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.1 0 0 1 0 0 0 0 1 1
|
||||
1 1 0.1 0 0 0 0 0 0 0 1 1
|
||||
2 1 0.1 1 0 0 0 0 0 0 1 1
|
||||
3 1 0.1 2 0 0 0 0 0 0 1 1
|
||||
4 1 0.1 3 0 0 0 0 0 0 1 1
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
bond_name k rest_length
|
||||
k1 50.0 1.0
|
||||
bond_name k rest_length
|
||||
k1 10.0 1.0
|
||||
|
||||
+120
-120
@@ -1,121 +1,121 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.1 0 0 0 0 0 0 1 1 0
|
||||
2 1 0.1 1 0 0 0 0 0 1 1 0
|
||||
3 1 0.1 2 0 0 0 0 0 1 1 0
|
||||
4 1 0.1 3 0 0 0 0 0 1 1 0
|
||||
5 1 0.1 4 0 0 0 0 0 1 1 0
|
||||
6 1 0.1 5 0 0 0 0 0 1 1 0
|
||||
7 1 0.1 6 0 0 0 0 0 1 1 0
|
||||
8 1 0.1 7 0 0 0 0 0 1 1 0
|
||||
9 1 0.1 8 0 0 0 0 0 1 1 0
|
||||
10 1 0.1 9 0 0 0 0 0 1 1 0
|
||||
11 1 0.1 10 0 0 0 0 0 1 1 0
|
||||
12 1 0.1 11 0 0 0 0 0 1 1 0
|
||||
13 1 0.1 12 0 0 0 0 0 1 1 0
|
||||
14 1 0.1 13 0 0 0 0 0 1 1 0
|
||||
15 1 0.1 14 0 0 0 0 0 1 1 0
|
||||
16 1 0.1 15 0 0 0 0 0 1 1 0
|
||||
17 1 0.1 16 0 0 0 0 0 1 1 0
|
||||
18 1 0.1 17 0 0 0 0 0 1 1 0
|
||||
19 1 0.1 18 0 0 0 0 0 1 1 0
|
||||
20 1 0.1 19 0 0 0 0 0 1 1 0
|
||||
21 1 0.1 20 0 0 0 0 0 1 1 0
|
||||
22 1 0.1 21 0 0 0 0 0 1 1 0
|
||||
23 1 0.1 22 0 0 0 0 0 1 1 0
|
||||
24 1 0.1 23 0 0 0 0 0 1 1 0
|
||||
25 1 0.1 24 0 0 0 0 0 1 1 0
|
||||
26 1 0.1 25 0 0 0 0 0 1 1 0
|
||||
27 1 0.1 26 0 0 0 0 0 1 1 0
|
||||
28 1 0.1 27 0 0 0 0 0 1 1 0
|
||||
29 1 0.1 28 0 0 0 0 0 1 1 0
|
||||
30 1 0.1 29 0 0 0 0 0 1 1 0
|
||||
31 1 0.1 30 0 0 0 0 0 1 1 0
|
||||
32 1 0.1 31 0 0 0 0 0 1 1 0
|
||||
33 1 0.1 32 0 0 0 0 0 1 1 0
|
||||
34 1 0.1 33 0 0 0 0 0 1 1 0
|
||||
35 1 0.1 34 0 0 0 0 0 1 1 0
|
||||
36 1 0.1 35 0 0 0 0 0 1 1 0
|
||||
37 1 0.1 36 0 0 0 0 0 1 1 0
|
||||
38 1 0.1 37 0 0 0 0 0 1 1 0
|
||||
39 1 0.1 38 0 0 0 0 0 1 1 0
|
||||
40 1 0.1 39 0 0 0 0 0 1 1 0
|
||||
41 1 0.1 40 0 0 0 0 0 1 1 0
|
||||
42 1 0.1 41 0 0 0 0 0 1 1 0
|
||||
43 1 0.1 42 0 0 0 0 0 1 1 0
|
||||
44 1 0.1 43 0 0 0 0 0 1 1 0
|
||||
45 1 0.1 44 0 0 0 0 0 1 1 0
|
||||
46 1 0.1 45 0 0 0 0 0 1 1 0
|
||||
47 1 0.1 46 0 0 0 0 0 1 1 0
|
||||
48 1 0.1 47 0 0 0 0 0 1 1 0
|
||||
49 1 0.1 48 0 0 0 0 0 1 1 0
|
||||
50 1 0.1 49 0 0 0 0 0 1 1 0
|
||||
51 1 0.1 50 0 0 0 0 0 1 1 0
|
||||
52 1 0.1 51 0 0 0 0 0 1 1 0
|
||||
53 1 0.1 52 0 0 0 0 0 1 1 0
|
||||
54 1 0.1 53 0 0 0 0 0 1 1 0
|
||||
55 1 0.1 54 0 0 0 0 0 1 1 0
|
||||
56 1 0.1 55 0 0 0 0 0 1 1 0
|
||||
57 1 0.1 56 0 0 0 0 0 1 1 0
|
||||
58 1 0.1 57 0 0 0 0 0 1 1 0
|
||||
59 1 0.1 58 0 0 0 0 0 1 1 0
|
||||
60 1 0.1 59 0 0 0 0 0 1 1 0
|
||||
61 1 0.1 60 0 0 0 0 0 1 1 0
|
||||
62 1 0.1 61 0 0 0 0 0 1 1 0
|
||||
63 1 0.1 62 0 0 0 0 0 1 1 0
|
||||
64 1 0.1 63 0 0 0 0 0 1 1 0
|
||||
65 1 0.1 64 0 0 0 0 0 1 1 0
|
||||
66 1 0.1 65 0 0 0 0 0 1 1 0
|
||||
67 1 0.1 66 0 0 0 0 0 1 1 0
|
||||
68 1 0.1 67 0 0 0 0 0 1 1 0
|
||||
69 1 0.1 68 0 0 0 0 0 1 1 0
|
||||
70 1 0.1 69 0 0 0 0 0 1 1 0
|
||||
71 1 0.1 70 0 0 0 0 0 1 1 0
|
||||
72 1 0.1 71 0 0 0 0 0 1 1 0
|
||||
73 1 0.1 72 0 0 0 0 0 1 1 0
|
||||
74 1 0.1 73 0 0 0 0 0 1 1 0
|
||||
75 1 0.1 74 0 0 0 0 0 1 1 0
|
||||
76 1 0.1 75 0 0 0 0 0 1 1 0
|
||||
77 1 0.1 76 0 0 0 0 0 1 1 0
|
||||
78 1 0.1 77 0 0 0 0 0 1 1 0
|
||||
79 1 0.1 78 0 0 0 0 0 1 1 0
|
||||
80 1 0.1 79 0 0 0 0 0 1 1 0
|
||||
81 1 0.1 80 0 0 0 0 0 1 1 0
|
||||
82 1 0.1 81 0 0 0 0 0 1 1 0
|
||||
83 1 0.1 82 0 0 0 0 0 1 1 0
|
||||
84 1 0.1 83 0 0 0 0 0 1 1 0
|
||||
85 1 0.1 84 0 0 0 0 0 1 1 0
|
||||
86 1 0.1 85 0 0 0 0 0 1 1 0
|
||||
87 1 0.1 86 0 0 0 0 0 1 1 0
|
||||
88 1 0.1 87 0 0 0 0 0 1 1 0
|
||||
89 1 0.1 88 0 0 0 0 0 1 1 0
|
||||
90 1 0.1 89 0 0 0 0 0 1 1 0
|
||||
91 1 0.1 90 0 0 0 0 0 1 1 0
|
||||
92 1 0.1 91 0 0 0 0 0 1 1 0
|
||||
93 1 0.1 92 0 0 0 0 0 1 1 0
|
||||
94 1 0.1 93 0 0 0 0 0 1 1 0
|
||||
95 1 0.1 94 0 0 0 0 0 1 1 0
|
||||
96 1 0.1 95 0 0 0 0 0 1 1 0
|
||||
97 1 0.1 96 0 0 0 0 0 1 1 0
|
||||
98 1 0.1 97 0 0 0 0 0 1 1 0
|
||||
99 1 0.1 98 0 0 0 0 0 1 1 0
|
||||
100 1 0.1 99 0 0 0 0 0 1 1 0
|
||||
101 1 0.1 100 0 0 0 0 0 1 1 0
|
||||
102 1 0.1 101 0 0 0 0 0 1 1 0
|
||||
103 1 0.1 102 0 0 0 0 0 1 1 0
|
||||
104 1 0.1 103 0 0 0 0 0 1 1 0
|
||||
105 1 0.1 104 0 0 0 0 0 1 1 0
|
||||
106 1 0.1 105 0 0 0 0 0 1 1 0
|
||||
107 1 0.1 106 0 0 0 0 0 1 1 0
|
||||
108 1 0.1 107 0 0 0 0 0 1 1 0
|
||||
109 1 0.1 108 0 0 0 0 0 1 1 0
|
||||
110 1 0.1 109 0 0 0 0 0 1 1 0
|
||||
111 1 0.1 110 0 0 0 0 0 1 1 0
|
||||
112 1 0.1 111 0 0 0 0 0 1 1 0
|
||||
113 1 0.1 112 0 0 0 0 0 1 1 0
|
||||
114 1 0.1 113 0 0 0 0 0 1 1 0
|
||||
115 1 0.1 114 0 0 0 0 0 1 1 0
|
||||
116 1 0.1 115 0 0 0 0 0 1 1 0
|
||||
117 1 0.1 116 0 0 0 0 0 1 1 0
|
||||
118 1 0.1 117 0 0 0 0 0 1 1 0
|
||||
119 1 0.1 118 0 0 0 0 0 1 1 0
|
||||
120 1 0.1 119 0 0 0 0 0 1 1 1
|
||||
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
|
||||
|
||||
@@ -19,10 +19,10 @@ save_trajectory: 0 # 0=不保留完整轨迹文件, 1=保留 trajectory.txt(
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
# 可选: python, c, cpp, fortran, java
|
||||
engine: python # 默认使用 python 引擎
|
||||
engine: c # 默认使用 python 引擎
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 80.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
box_a: 300.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
# 坐标文件格式:
|
||||
@@ -39,7 +39,7 @@ plot_atom: 1
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
# 三个方向分量分别对应 x, y, z
|
||||
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=开启)──────────────────
|
||||
gravity_field: 0 # 均匀重力场 (G)
|
||||
@@ -66,13 +66,13 @@ warmup_steps: 0 # 默认 0(立即开始记录)
|
||||
|
||||
# 总模拟时间(秒),程序自动计算 NT = T_total / DT
|
||||
# 如果同时指定了 NT,以 NT 为准
|
||||
T_total: 100.0
|
||||
T_total: 1000.0
|
||||
|
||||
# 抽帧间隔(每 NSTEP 步取一帧用于动画)
|
||||
NSTEP: 10
|
||||
NSTEP: 500
|
||||
|
||||
# ── 时间步长 ──────────────────────────────────
|
||||
DT: 0.01 # 时间步长 (s)
|
||||
DT: 0.001 # 时间步长 (s)
|
||||
|
||||
# 抽帧范围:只保存 [sample_start, sample_end) 区间内的帧
|
||||
sample_start: null # null 表示从头开始(帧索引从 0 起)
|
||||
@@ -102,7 +102,10 @@ box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机初始位置 ────────────────────────────
|
||||
camera_distance: 40.0 # 摄像机到场景中心的距离
|
||||
camera_elevation: 0 # 俯仰角(度),负值=俯视
|
||||
camera_azimuth: 0 # 方位角(度)
|
||||
move_camera: 1 # 0=固定视角, 1=按 move_camera.txt 运动
|
||||
camera_distance: 120.0 # 摄像机到场景中心的距离
|
||||
camera_elevation: 0.0 # 俯仰角(度),负值=俯视
|
||||
camera_azimuth: 0.0 # 方位角(度)
|
||||
camera_center_x: 60.0 # 摄像机注视点 x
|
||||
camera_center_y: 0.0 # 摄像机注视点 y
|
||||
camera_center_z: 0.0 # 摄像机注视点 z
|
||||
move_camera: 0 # 0=固定视角, 1=按 move_camera.txt 运动
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
k1 500.0 1.0
|
||||
@@ -0,0 +1,120 @@
|
||||
n1 n2 bond_name
|
||||
1 2 k1
|
||||
2 3 k1
|
||||
3 4 k1
|
||||
4 5 k1
|
||||
5 6 k1
|
||||
6 7 k1
|
||||
7 8 k1
|
||||
8 9 k1
|
||||
9 10 k1
|
||||
10 11 k1
|
||||
11 12 k1
|
||||
12 13 k1
|
||||
13 14 k1
|
||||
14 15 k1
|
||||
15 16 k1
|
||||
16 17 k1
|
||||
17 18 k1
|
||||
18 19 k1
|
||||
19 20 k1
|
||||
20 21 k1
|
||||
21 22 k1
|
||||
22 23 k1
|
||||
23 24 k1
|
||||
24 25 k1
|
||||
25 26 k1
|
||||
26 27 k1
|
||||
27 28 k1
|
||||
28 29 k1
|
||||
29 30 k1
|
||||
30 31 k1
|
||||
31 32 k1
|
||||
32 33 k1
|
||||
33 34 k1
|
||||
34 35 k1
|
||||
35 36 k1
|
||||
36 37 k1
|
||||
37 38 k1
|
||||
38 39 k1
|
||||
39 40 k1
|
||||
40 41 k1
|
||||
41 42 k1
|
||||
42 43 k1
|
||||
43 44 k1
|
||||
44 45 k1
|
||||
45 46 k1
|
||||
46 47 k1
|
||||
47 48 k1
|
||||
48 49 k1
|
||||
49 50 k1
|
||||
50 51 k1
|
||||
51 52 k1
|
||||
52 53 k1
|
||||
53 54 k1
|
||||
54 55 k1
|
||||
55 56 k1
|
||||
56 57 k1
|
||||
57 58 k1
|
||||
58 59 k1
|
||||
59 60 k1
|
||||
60 61 k1
|
||||
61 62 k1
|
||||
62 63 k1
|
||||
63 64 k1
|
||||
64 65 k1
|
||||
65 66 k1
|
||||
66 67 k1
|
||||
67 68 k1
|
||||
68 69 k1
|
||||
69 70 k1
|
||||
70 71 k1
|
||||
71 72 k1
|
||||
72 73 k1
|
||||
73 74 k1
|
||||
74 75 k1
|
||||
75 76 k1
|
||||
76 77 k1
|
||||
77 78 k1
|
||||
78 79 k1
|
||||
79 80 k1
|
||||
80 81 k1
|
||||
81 82 k1
|
||||
82 83 k1
|
||||
83 84 k1
|
||||
84 85 k1
|
||||
85 86 k1
|
||||
86 87 k1
|
||||
87 88 k1
|
||||
88 89 k1
|
||||
89 90 k1
|
||||
90 91 k1
|
||||
91 92 k1
|
||||
92 93 k1
|
||||
93 94 k1
|
||||
94 95 k1
|
||||
95 96 k1
|
||||
96 97 k1
|
||||
97 98 k1
|
||||
98 99 k1
|
||||
99 100 k1
|
||||
100 101 k1
|
||||
101 102 k1
|
||||
102 103 k1
|
||||
103 104 k1
|
||||
104 105 k1
|
||||
105 106 k1
|
||||
106 107 k1
|
||||
107 108 k1
|
||||
108 109 k1
|
||||
109 110 k1
|
||||
110 111 k1
|
||||
111 112 k1
|
||||
112 113 k1
|
||||
113 114 k1
|
||||
114 115 k1
|
||||
115 116 k1
|
||||
116 117 k1
|
||||
117 118 k1
|
||||
118 119 k1
|
||||
119 120 k1
|
||||
@@ -0,0 +1,121 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.1 0 0 0 0 0 0 0 1 0
|
||||
2 1 0.1 1 0 0 0 0 0 0 1 0
|
||||
3 1 0.1 2 0 0 0 0 0 0 1 0
|
||||
4 1 0.1 3 0 0 0 0 0 0 1 0
|
||||
5 1 0.1 4 0 0 0 0 0 0 1 0
|
||||
6 1 0.1 5 0 0 0 0 0 0 1 0
|
||||
7 1 0.1 6 0 0 0 0 0 0 1 0
|
||||
8 1 0.1 7 0 0 0 0 0 0 1 0
|
||||
9 1 0.1 8 0 0 0 0 0 0 1 0
|
||||
10 1 0.1 9 0 0 0 0 0 0 1 0
|
||||
11 1 0.1 10 0 0 0 0 0 0 1 0
|
||||
12 1 0.1 11 0 0 0 0 0 0 1 0
|
||||
13 1 0.1 12 0 0 0 0 0 0 1 0
|
||||
14 1 0.1 13 0 0 0 0 0 0 1 0
|
||||
15 1 0.1 14 0 0 0 0 0 0 1 0
|
||||
16 1 0.1 15 0 0 0 0 0 0 1 0
|
||||
17 1 0.1 16 0 0 0 0 0 0 1 0
|
||||
18 1 0.1 17 0 0 0 0 0 0 1 0
|
||||
19 1 0.1 18 0 0 0 0 0 0 1 0
|
||||
20 1 0.1 19 0 0 0 0 0 0 1 0
|
||||
21 1 0.1 20 0 0 0 0 0 0 1 0
|
||||
22 1 0.1 21 0 0 0 0 0 0 1 0
|
||||
23 1 0.1 22 0 0 0 0 0 0 1 0
|
||||
24 1 0.1 23 0 0 0 0 0 0 1 0
|
||||
25 1 0.1 24 0 0 0 0 0 0 1 0
|
||||
26 1 0.1 25 0 0 0 0 0 0 1 0
|
||||
27 1 0.1 26 0 0 0 0 0 0 1 0
|
||||
28 1 0.1 27 0 0 0 0 0 0 1 0
|
||||
29 1 0.1 28 0 0 0 0 0 0 1 0
|
||||
30 1 0.1 29 0 0 0 0 0 0 1 0
|
||||
31 1 0.1 30 0 0 0 0 0 0 1 0
|
||||
32 1 0.1 31 0 0 0 0 0 0 1 0
|
||||
33 1 0.1 32 0 0 0 0 0 0 1 0
|
||||
34 1 0.1 33 0 0 0 0 0 0 1 0
|
||||
35 1 0.1 34 0 0 0 0 0 0 1 0
|
||||
36 1 0.1 35 0 0 0 0 0 0 1 0
|
||||
37 1 0.1 36 0 0 0 0 0 0 1 0
|
||||
38 1 0.1 37 0 0 0 0 0 0 1 0
|
||||
39 1 0.1 38 0 0 0 0 0 0 1 0
|
||||
40 1 0.1 39 0 0 0 0 0 0 1 0
|
||||
41 1 0.1 40 0 0 0 0 0 0 1 0
|
||||
42 1 0.1 41 0 0 0 0 0 0 1 0
|
||||
43 1 0.1 42 0 0 0 0 0 0 1 0
|
||||
44 1 0.1 43 0 0 0 0 0 0 1 0
|
||||
45 1 0.1 44 0 0 0 0 0 0 1 0
|
||||
46 1 0.1 45 0 0 0 0 0 0 1 0
|
||||
47 1 0.1 46 0 0 0 0 0 0 1 0
|
||||
48 1 0.1 47 0 0 0 0 0 0 1 0
|
||||
49 1 0.1 48 0 0 0 0 0 0 1 0
|
||||
50 1 0.1 49 0 0 0 0 0 0 1 0
|
||||
51 1 0.1 50 0 0 0 0 0 0 1 0
|
||||
52 1 0.1 51 0 0 0 0 0 0 1 0
|
||||
53 1 0.1 52 0 0 0 0 0 0 1 0
|
||||
54 1 0.1 53 0 0 0 0 0 0 1 0
|
||||
55 1 0.1 54 0 0 0 0 0 0 1 0
|
||||
56 1 0.1 55 0 0 0 0 0 0 1 0
|
||||
57 1 0.1 56 0 0 0 0 0 0 1 0
|
||||
58 1 0.1 57 0 0 0 0 0 0 1 0
|
||||
59 1 0.1 58 0 0 0 0 0 0 1 0
|
||||
60 1 0.1 59 0 0 0 0 0 0 1 0
|
||||
61 1 0.1 60 0 0 0 0 0 0 1 0
|
||||
62 1 0.1 61 0 0 0 0 0 0 1 0
|
||||
63 1 0.1 62 0 0 0 0 0 0 1 0
|
||||
64 1 0.1 63 0 0 0 0 0 0 1 0
|
||||
65 1 0.1 64 0 0 0 0 0 0 1 0
|
||||
66 1 0.1 65 0 0 0 0 0 0 1 0
|
||||
67 1 0.1 66 0 0 0 0 0 0 1 0
|
||||
68 1 0.1 67 0 0 0 0 0 0 1 0
|
||||
69 1 0.1 68 0 0 0 0 0 0 1 0
|
||||
70 1 0.1 69 0 0 0 0 0 0 1 0
|
||||
71 1 0.1 70 0 0 0 0 0 0 1 0
|
||||
72 1 0.1 71 0 0 0 0 0 0 1 0
|
||||
73 1 0.1 72 0 0 0 0 0 0 1 0
|
||||
74 1 0.1 73 0 0 0 0 0 0 1 0
|
||||
75 1 0.1 74 0 0 0 0 0 0 1 0
|
||||
76 1 0.1 75 0 0 0 0 0 0 1 0
|
||||
77 1 0.1 76 0 0 0 0 0 0 1 0
|
||||
78 1 0.1 77 0 0 0 0 0 0 1 0
|
||||
79 1 0.1 78 0 0 0 0 0 0 1 0
|
||||
80 1 0.1 79 0 0 0 0 0 0 1 0
|
||||
81 1 0.1 80 0 0 0 0 0 0 1 0
|
||||
82 1 0.1 81 0 0 0 0 0 0 1 0
|
||||
83 1 0.1 82 0 0 0 0 0 0 1 0
|
||||
84 1 0.1 83 0 0 0 0 0 0 1 0
|
||||
85 1 0.1 84 0 0 0 0 0 0 1 0
|
||||
86 1 0.1 85 0 0 0 0 0 0 1 0
|
||||
87 1 0.1 86 0 0 0 0 0 0 1 0
|
||||
88 1 0.1 87 0 0 0 0 0 0 1 0
|
||||
89 1 0.1 88 0 0 0 0 0 0 1 0
|
||||
90 1 0.1 89 0 0 0 0 0 0 1 0
|
||||
91 1 0.1 90 0 0 0 0 0 0 1 0
|
||||
92 1 0.1 91 0 0 0 0 0 0 1 0
|
||||
93 1 0.1 92 0 0 0 0 0 0 1 0
|
||||
94 1 0.1 93 0 0 0 0 0 0 1 0
|
||||
95 1 0.1 94 0 0 0 0 0 0 1 0
|
||||
96 1 0.1 95 0 0 0 0 0 0 1 0
|
||||
97 1 0.1 96 0 0 0 0 0 0 1 0
|
||||
98 1 0.1 97 0 0 0 0 0 0 1 0
|
||||
99 1 0.1 98 0 0 0 0 0 0 1 0
|
||||
100 1 0.1 99 0 0 0 0 0 0 1 0
|
||||
101 1 0.1 100 0 0 0 0 0 0 1 0
|
||||
102 1 0.1 101 0 0 0 0 0 0 1 0
|
||||
103 1 0.1 102 0 0 0 0 0 0 1 0
|
||||
104 1 0.1 103 0 0 0 0 0 0 1 0
|
||||
105 1 0.1 104 0 0 0 0 0 0 1 0
|
||||
106 1 0.1 105 0 0 0 0 0 0 1 0
|
||||
107 1 0.1 106 0 0 0 0 0 0 1 0
|
||||
108 1 0.1 107 0 0 0 0 0 0 1 0
|
||||
109 1 0.1 108 0 0 0 0 0 0 1 0
|
||||
110 1 0.1 109 0 0 0 0 0 0 1 0
|
||||
111 1 0.1 110 0 0 0 0 0 0 1 0
|
||||
112 1 0.1 111 0 0 0 0 0 0 1 0
|
||||
113 1 0.1 112 0 0 0 0 0 0 1 0
|
||||
114 1 0.1 113 0 0 0 0 0 0 1 0
|
||||
115 1 0.1 114 0 0 0 0 0 0 1 0
|
||||
116 1 0.1 115 0 0 0 0 0 0 1 0
|
||||
117 1 0.1 116 0 0 0 0 0 0 1 0
|
||||
118 1 0.1 117 0 0 0 0 0 0 1 0
|
||||
119 1 0.1 118 0 0 0 0 0 0 1 0
|
||||
120 1 0.1 119 0 0 0 0 0 1 1 1
|
||||
@@ -0,0 +1,3 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 0.1 0 0 0.01333 0 0 90 all
|
||||
120 0 0 0.1 0 0 0.01333 0 0 90 all
|
||||
@@ -0,0 +1,114 @@
|
||||
# 物理模拟参数配置
|
||||
# 格式:YAML
|
||||
# 用法:python run_dynamics.py
|
||||
|
||||
# ── 流程控制 ──────────────────────────────────
|
||||
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||
step_simulate: 1 # 运行物理模拟 → output/display.txt(引擎直接抽帧)
|
||||
step_sample: 0 # (旧版)从 trajectory.txt 重新抽帧,默认0=不执行
|
||||
step_plot: 0 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||
step_animation: 0 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||
step_plot_wave: 1 # 绘制波形能量动画
|
||||
force_calc: 1 # 强制重新计算:1=跳过缓存强算,0=自动使用已有输出
|
||||
plot_wave_save_gif: 0 # 输出波形 GIF(需 step_plot_wave=1)
|
||||
plot_wave_save_mp4: 0 # 输出波形 MP4(需 step_plot_wave=1)
|
||||
|
||||
# ── 文件保存 ──────────────────────────────────
|
||||
save_trajectory: 0 # 0=不保留完整轨迹文件, 1=保留 trajectory.txt(用于后续单独抽帧)
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
# 可选: python, c, cpp, fortran, java
|
||||
engine: c # 默认使用 python 引擎
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 300.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
# 坐标文件格式:
|
||||
# 第一行:n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
# 后续行:原子序号 质量 半径 x y z vx vy vz fix_x fix_y fix_z
|
||||
coord_file: input/coord.txt
|
||||
connection_file: input/connection.txt
|
||||
bond_file: input/bond.txt
|
||||
driver_file: input/driver.txt # 驱动力定义文件(driving_force=1 时生效)
|
||||
|
||||
# 绘图/动画展示的原子序号(对应 coord_file 第一列 n)
|
||||
plot_atom: 1
|
||||
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
# 三个方向分量分别对应 x, y, z
|
||||
G: [0.000, 0.000, 0.000] # 重力场分量 (m/s²)
|
||||
B: [0.005, 0.000, 0.005] # 阻尼分量
|
||||
|
||||
# ── 力开关(0=关闭, 1=开启)──────────────────
|
||||
gravity_field: 0 # 均匀重力场 (G)
|
||||
gravity_interaction: 0 # 原子间万有引力
|
||||
elastic_force: 1 # 弹簧键力
|
||||
damping_force: 1 # 阻尼 (B)
|
||||
driving_force: 1 # 驱动力(需 driver_file 定义)
|
||||
#
|
||||
gravity_strength: 1.0 # 万有引力强度(仅 gravity_interaction=1 时有效)
|
||||
|
||||
# ── 数值算法 ──────────────────────────────────
|
||||
# 可选:
|
||||
# explicit_euler 显式欧拉法
|
||||
# implicit_euler 隐式欧拉法
|
||||
# midpoint 中点法
|
||||
# leapfrog 蛙跳法
|
||||
method: leapfrog
|
||||
|
||||
# ── 步骤控制 ──────────────────────────────────
|
||||
# 以下参数控制哪些步骤被执行和保存
|
||||
|
||||
# 预热步数:模拟开始时跳过不保存的步数(用于稳定初始状态)
|
||||
warmup_steps: 0 # 默认 0(立即开始记录)
|
||||
|
||||
# 总模拟时间(秒),程序自动计算 NT = T_total / DT
|
||||
# 如果同时指定了 NT,以 NT 为准
|
||||
T_total: 1000.0
|
||||
|
||||
# 抽帧间隔(每 NSTEP 步取一帧用于动画)
|
||||
NSTEP: 500
|
||||
|
||||
# ── 时间步长 ──────────────────────────────────
|
||||
DT: 0.001 # 时间步长 (s)
|
||||
|
||||
# 抽帧范围:只保存 [sample_start, sample_end) 区间内的帧
|
||||
sample_start: null # null 表示从头开始(帧索引从 0 起)
|
||||
sample_end: null # null 表示到末尾
|
||||
|
||||
|
||||
|
||||
# ── 渲染方式 ──────────────────────────────────
|
||||
# 3D 动画中原子渲染方式:
|
||||
# 0 = Sphere (网格球体,效果精细,原子数少时推荐)
|
||||
# 1 = Marker (GPU 实例化点,原子数多时性能更佳)
|
||||
use_marker: 1
|
||||
|
||||
# ── 显示参数 ──────────────────────────────────
|
||||
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
# 小球颜色
|
||||
# 小球半径从 coord_file 的 radius 列读取
|
||||
ball_color_r: 0.20 # R 分量 (0~1)
|
||||
ball_color_g: 0.60 # G 分量
|
||||
ball_color_b: 0.90 # B 分量
|
||||
|
||||
# 盒子面颜色
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机初始位置 ────────────────────────────
|
||||
camera_distance: 120.0 # 摄像机到场景中心的距离
|
||||
camera_elevation: 0.0 # 俯仰角(度),负值=俯视
|
||||
camera_azimuth: 0.0 # 方位角(度)
|
||||
camera_center_x: 60.0 # 摄像机注视点 x
|
||||
camera_center_y: 0.0 # 摄像机注视点 y
|
||||
camera_center_z: 0.0 # 摄像机注视点 z
|
||||
move_camera: 0 # 0=固定视角, 1=按 move_camera.txt 运动
|
||||
|
||||
# ── 视觉放大 ──────────────────────────────────
|
||||
display_amp: [1.0, 1.0, 10.0] # x/y/z 方向视觉位移放大倍数(不影响物理)
|
||||
@@ -0,0 +1,9 @@
|
||||
# move_camera.txt — 摄像机速度段驱动
|
||||
# 格式: start-end vx=f vy=f vz=f rx=d ry=d rz=d
|
||||
# vx/vy/vz: 平移速度(每帧移动单位)
|
||||
# rx/ry/rz: 旋转速度(每帧度数)
|
||||
# rx → elevation(俯仰), ry → azimuth(方位), rz → (预留)
|
||||
#
|
||||
# 示例:前60帧向右平移+绕x旋转,30-90帧向上平移+绕y绕z旋转
|
||||
all vx=0.02
|
||||
# 30-90 vy=0.02 ry=1 rz=1
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Case runner for Dynamics case06 — 1D atomic chain (transverse wave).
|
||||
|
||||
This script keeps program and data separated:
|
||||
- program: ../../dynamics.py
|
||||
- input: ./input
|
||||
- output: ./output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CASE_DIR = Path(__file__).resolve().parent
|
||||
DYNAMICS_PATH = Path("..") / ".." / "dynamics.py"
|
||||
INPUT_DIR = Path("input")
|
||||
OUTPUT_DIR = Path("output")
|
||||
CONFIG_FILE = INPUT_DIR / "input.txt"
|
||||
|
||||
|
||||
def load_dynamics_module(module_path: Path):
|
||||
spec = importlib.util.spec_from_file_location("dynamics_module", module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"无法加载 dynamics.py: {module_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case06")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
dynamics_path = (CASE_DIR / DYNAMICS_PATH).resolve()
|
||||
input_dir = (CASE_DIR / INPUT_DIR).resolve()
|
||||
output_dir = (CASE_DIR / OUTPUT_DIR).resolve()
|
||||
config_path = (CASE_DIR / CONFIG_FILE).resolve()
|
||||
|
||||
module = load_dynamics_module(dynamics_path)
|
||||
module.run_case(
|
||||
config_path=config_path,
|
||||
runtime_base=CASE_DIR,
|
||||
input_dir=input_dir,
|
||||
output_dir=output_dir,
|
||||
no_plot=args.no_plot,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
k1 0.1 1.0
|
||||
@@ -0,0 +1,2 @@
|
||||
n1 n2 bond_name
|
||||
1 2 k1
|
||||
@@ -0,0 +1,3 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.1 0 0 0 0 0 0 0 1 0
|
||||
2 1 0.1 1 0 0 0 0 0 0 1 0
|
||||
@@ -0,0 +1,2 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0.1 0 0.0 0.02 0 0 90 0 0 all
|
||||
@@ -0,0 +1,114 @@
|
||||
# 物理模拟参数配置
|
||||
# 格式:YAML
|
||||
# 用法:python run_dynamics.py
|
||||
|
||||
# ── 流程控制 ──────────────────────────────────
|
||||
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||
step_simulate: 1 # 运行物理模拟 → output/display.txt(引擎直接抽帧)
|
||||
step_sample: 0 # (旧版)从 trajectory.txt 重新抽帧,默认0=不执行
|
||||
step_plot: 0 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||
step_animation: 0 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||
step_plot_wave: 1 # 绘制波形能量动画
|
||||
force_calc: 1 # 强制重新计算:1=跳过缓存强算,0=自动使用已有输出
|
||||
plot_wave_save_gif: 0 # 输出波形 GIF(需 step_plot_wave=1)
|
||||
plot_wave_save_mp4: 0 # 输出波形 MP4(需 step_plot_wave=1)
|
||||
|
||||
# ── 文件保存 ──────────────────────────────────
|
||||
save_trajectory: 0 # 0=不保留完整轨迹文件, 1=保留 trajectory.txt(用于后续单独抽帧)
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
# 可选: python, c, cpp, fortran, java
|
||||
engine: c # 默认使用 python 引擎
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 300.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
# 坐标文件格式:
|
||||
# 第一行:n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
# 后续行:原子序号 质量 半径 x y z vx vy vz fix_x fix_y fix_z
|
||||
coord_file: input/coord.txt
|
||||
connection_file: input/connection.txt
|
||||
bond_file: input/bond.txt
|
||||
driver_file: input/driver.txt # 驱动力定义文件(driving_force=1 时生效)
|
||||
|
||||
# 绘图/动画展示的原子序号(对应 coord_file 第一列 n)
|
||||
plot_atom: 1
|
||||
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
# 三个方向分量分别对应 x, y, z
|
||||
G: [0.000, 0.000, 0.000] # 重力场分量 (m/s²)
|
||||
B: [0.005, 0.000, 0.005] # 阻尼分量
|
||||
|
||||
# ── 力开关(0=关闭, 1=开启)──────────────────
|
||||
gravity_field: 0 # 均匀重力场 (G)
|
||||
gravity_interaction: 0 # 原子间万有引力
|
||||
elastic_force: 1 # 弹簧键力
|
||||
damping_force: 1 # 阻尼 (B)
|
||||
driving_force: 1 # 驱动力(需 driver_file 定义)
|
||||
#
|
||||
gravity_strength: 1.0 # 万有引力强度(仅 gravity_interaction=1 时有效)
|
||||
|
||||
# ── 数值算法 ──────────────────────────────────
|
||||
# 可选:
|
||||
# explicit_euler 显式欧拉法
|
||||
# implicit_euler 隐式欧拉法
|
||||
# midpoint 中点法
|
||||
# leapfrog 蛙跳法
|
||||
method: leapfrog
|
||||
|
||||
# ── 步骤控制 ──────────────────────────────────
|
||||
# 以下参数控制哪些步骤被执行和保存
|
||||
|
||||
# 预热步数:模拟开始时跳过不保存的步数(用于稳定初始状态)
|
||||
warmup_steps: 0 # 默认 0(立即开始记录)
|
||||
|
||||
# 总模拟时间(秒),程序自动计算 NT = T_total / DT
|
||||
# 如果同时指定了 NT,以 NT 为准
|
||||
T_total: 100.0
|
||||
|
||||
# 抽帧间隔(每 NSTEP 步取一帧用于动画)
|
||||
NSTEP: 500
|
||||
|
||||
# ── 时间步长 ──────────────────────────────────
|
||||
DT: 0.001 # 时间步长 (s)
|
||||
|
||||
# 抽帧范围:只保存 [sample_start, sample_end) 区间内的帧
|
||||
sample_start: null # null 表示从头开始(帧索引从 0 起)
|
||||
sample_end: null # null 表示到末尾
|
||||
|
||||
|
||||
|
||||
# ── 渲染方式 ──────────────────────────────────
|
||||
# 3D 动画中原子渲染方式:
|
||||
# 0 = Sphere (网格球体,效果精细,原子数少时推荐)
|
||||
# 1 = Marker (GPU 实例化点,原子数多时性能更佳)
|
||||
use_marker: 1
|
||||
|
||||
# ── 显示参数 ──────────────────────────────────
|
||||
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
# 小球颜色
|
||||
# 小球半径从 coord_file 的 radius 列读取
|
||||
ball_color_r: 0.20 # R 分量 (0~1)
|
||||
ball_color_g: 0.60 # G 分量
|
||||
ball_color_b: 0.90 # B 分量
|
||||
|
||||
# 盒子面颜色
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机初始位置 ────────────────────────────
|
||||
camera_distance: 10.0 # 摄像机到场景中心的距离
|
||||
camera_elevation: 0.0 # 俯仰角(度),负值=俯视
|
||||
camera_azimuth: 0.0 # 方位角(度)
|
||||
camera_center_x: 0.0 # 摄像机注视点 x
|
||||
camera_center_y: 0.0 # 摄像机注视点 y
|
||||
camera_center_z: 0.0 # 摄像机注视点 z
|
||||
move_camera: 0 # 0=固定视角, 1=按 move_camera.txt 运动
|
||||
|
||||
# ── 视觉放大 ──────────────────────────────────
|
||||
display_amp: [1.0, 1.0, 10.0] # x/y/z 方向视觉位移放大倍数(不影响物理)
|
||||
@@ -0,0 +1,9 @@
|
||||
# move_camera.txt — 摄像机速度段驱动
|
||||
# 格式: start-end vx=f vy=f vz=f rx=d ry=d rz=d
|
||||
# vx/vy/vz: 平移速度(每帧移动单位)
|
||||
# rx/ry/rz: 旋转速度(每帧度数)
|
||||
# rx → elevation(俯仰), ry → azimuth(方位), rz → (预留)
|
||||
#
|
||||
# 示例:前60帧向右平移+绕x旋转,30-90帧向上平移+绕y绕z旋转
|
||||
all vx=0.02
|
||||
# 30-90 vy=0.02 ry=1 rz=1
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Case runner for Dynamics case06 — 1D atomic chain (transverse wave).
|
||||
|
||||
This script keeps program and data separated:
|
||||
- program: ../../dynamics.py
|
||||
- input: ./input
|
||||
- output: ./output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CASE_DIR = Path(__file__).resolve().parent
|
||||
DYNAMICS_PATH = Path("..") / ".." / "dynamics.py"
|
||||
INPUT_DIR = Path("input")
|
||||
OUTPUT_DIR = Path("output")
|
||||
CONFIG_FILE = INPUT_DIR / "input.txt"
|
||||
|
||||
|
||||
def load_dynamics_module(module_path: Path):
|
||||
spec = importlib.util.spec_from_file_location("dynamics_module", module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"无法加载 dynamics.py: {module_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case06")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
dynamics_path = (CASE_DIR / DYNAMICS_PATH).resolve()
|
||||
input_dir = (CASE_DIR / INPUT_DIR).resolve()
|
||||
output_dir = (CASE_DIR / OUTPUT_DIR).resolve()
|
||||
config_path = (CASE_DIR / CONFIG_FILE).resolve()
|
||||
|
||||
module = load_dynamics_module(dynamics_path)
|
||||
module.run_case(
|
||||
config_path=config_path,
|
||||
runtime_base=CASE_DIR,
|
||||
input_dir=input_dir,
|
||||
output_dir=output_dir,
|
||||
no_plot=args.no_plot,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
k1 300.0 1.0
|
||||
@@ -0,0 +1,40 @@
|
||||
n1 n2 bond_name
|
||||
1 2 k1
|
||||
2 3 k1
|
||||
3 4 k1
|
||||
4 5 k1
|
||||
5 6 k1
|
||||
6 7 k1
|
||||
7 8 k1
|
||||
8 9 k1
|
||||
9 10 k1
|
||||
10 11 k1
|
||||
11 12 k1
|
||||
12 13 k1
|
||||
13 14 k1
|
||||
14 15 k1
|
||||
15 16 k1
|
||||
16 17 k1
|
||||
17 18 k1
|
||||
18 19 k1
|
||||
19 20 k1
|
||||
20 21 k1
|
||||
21 22 k1
|
||||
22 23 k1
|
||||
23 24 k1
|
||||
24 25 k1
|
||||
25 26 k1
|
||||
26 27 k1
|
||||
27 28 k1
|
||||
28 29 k1
|
||||
29 30 k1
|
||||
30 31 k1
|
||||
31 32 k1
|
||||
32 33 k1
|
||||
33 34 k1
|
||||
34 35 k1
|
||||
35 36 k1
|
||||
36 37 k1
|
||||
37 38 k1
|
||||
38 39 k1
|
||||
39 40 k1
|
||||
@@ -0,0 +1,41 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.1 0 0 0 0 0 0 0 1 0
|
||||
2 1 0.1 1 0 0 0 0 0 0 1 0
|
||||
3 1 0.1 2 0 0 0 0 0 0 1 0
|
||||
4 1 0.1 3 0 0 0 0 0 0 1 0
|
||||
5 1 0.1 4 0 0 0 0 0 0 1 0
|
||||
6 1 0.1 5 0 0 0 0 0 0 1 0
|
||||
7 1 0.1 6 0 0 0 0 0 0 1 0
|
||||
8 1 0.1 7 0 0 0 0 0 0 1 0
|
||||
9 1 0.1 8 0 0 0 0 0 0 1 0
|
||||
10 1 0.1 9 0 0 0 0 0 0 1 0
|
||||
11 1 0.1 10 0 0 0 0 0 0 1 0
|
||||
12 1 0.1 11 0 0 0 0 0 0 1 0
|
||||
13 1 0.1 12 0 0 0 0 0 0 1 0
|
||||
14 1 0.1 13 0 0 0 0 0 0 1 0
|
||||
15 1 0.1 14 0 0 0 0 0 0 1 0
|
||||
16 1 0.1 15 0 0 0 0 0 0 1 0
|
||||
17 1 0.1 16 0 0 0 0 0 0 1 0
|
||||
18 1 0.1 17 0 0 0 0 0 0 1 0
|
||||
19 1 0.1 18 0 0 0 0 0 0 1 0
|
||||
20 1 0.1 19 0 0 0 0 0 0 1 0
|
||||
21 1 0.1 20 0 0 0 0 0 0 1 0
|
||||
22 1 0.1 21 0 0 0 0 0 0 1 0
|
||||
23 1 0.1 22 0 0 0 0 0 0 1 0
|
||||
24 1 0.1 23 0 0 0 0 0 0 1 0
|
||||
25 1 0.1 24 0 0 0 0 0 0 1 0
|
||||
26 1 0.1 25 0 0 0 0 0 0 1 0
|
||||
27 1 0.1 26 0 0 0 0 0 0 1 0
|
||||
28 1 0.1 27 0 0 0 0 0 0 1 0
|
||||
29 1 0.1 28 0 0 0 0 0 0 1 0
|
||||
30 1 0.1 29 0 0 0 0 0 0 1 0
|
||||
31 1 0.1 30 0 0 0 0 0 0 1 0
|
||||
32 1 0.1 31 0 0 0 0 0 0 1 0
|
||||
33 1 0.1 32 0 0 0 0 0 0 1 0
|
||||
34 1 0.1 33 0 0 0 0 0 0 1 0
|
||||
35 1 0.1 34 0 0 0 0 0 0 1 0
|
||||
36 1 0.1 35 0 0 0 0 0 0 1 0
|
||||
37 1 0.1 36 0 0 0 0 0 0 1 0
|
||||
38 1 0.1 37 0 0 0 0 0 0 1 0
|
||||
39 1 0.1 38 0 0 0 0 0 0 1 0
|
||||
40 1 0.1 39 0 0 0 0 0 1 1 1
|
||||
@@ -0,0 +1,2 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 0.1 0 0 0.04 0 0 90 all
|
||||
@@ -0,0 +1,114 @@
|
||||
# 物理模拟参数配置
|
||||
# 格式:YAML
|
||||
# 用法:python run_dynamics.py
|
||||
|
||||
# ── 流程控制 ──────────────────────────────────
|
||||
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||
step_simulate: 1 # 运行物理模拟 → output/display.txt(引擎直接抽帧)
|
||||
step_sample: 0 # (旧版)从 trajectory.txt 重新抽帧,默认0=不执行
|
||||
step_plot: 0 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||
step_animation: 1 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||
step_plot_wave: 1 # 绘制波形能量动画
|
||||
force_calc: 1 # 强制重新计算:1=跳过缓存强算,0=自动使用已有输出
|
||||
plot_wave_save_gif: 0 # 输出波形 GIF(需 step_plot_wave=1)
|
||||
plot_wave_save_mp4: 0 # 输出波形 MP4(需 step_plot_wave=1)
|
||||
|
||||
# ── 文件保存 ──────────────────────────────────
|
||||
save_trajectory: 0 # 0=不保留完整轨迹文件, 1=保留 trajectory.txt(用于后续单独抽帧)
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
# 可选: python, c, cpp, fortran, java
|
||||
engine: fortran # 默认使用 python 引擎
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 300.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
# 坐标文件格式:
|
||||
# 第一行:n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
# 后续行:原子序号 质量 半径 x y z vx vy vz fix_x fix_y fix_z
|
||||
coord_file: input/coord.txt
|
||||
connection_file: input/connection.txt
|
||||
bond_file: input/bond.txt
|
||||
driver_file: input/driver.txt # 驱动力定义文件(driving_force=1 时生效)
|
||||
|
||||
# 绘图/动画展示的原子序号(对应 coord_file 第一列 n)
|
||||
plot_atom: 1
|
||||
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
# 三个方向分量分别对应 x, y, z
|
||||
G: [0.000, 0.000, 0.000] # 重力场分量 (m/s²)
|
||||
B: [0.005, 0.000, 0.005] # 阻尼分量
|
||||
|
||||
# ── 力开关(0=关闭, 1=开启)──────────────────
|
||||
gravity_field: 0 # 均匀重力场 (G)
|
||||
gravity_interaction: 0 # 原子间万有引力
|
||||
elastic_force: 1 # 弹簧键力
|
||||
damping_force: 0 # 阻尼 (B)
|
||||
driving_force: 1 # 驱动力(需 driver_file 定义)
|
||||
#
|
||||
gravity_strength: 1.0 # 万有引力强度(仅 gravity_interaction=1 时有效)
|
||||
|
||||
# ── 数值算法 ──────────────────────────────────
|
||||
# 可选:
|
||||
# explicit_euler 显式欧拉法
|
||||
# implicit_euler 隐式欧拉法
|
||||
# midpoint 中点法
|
||||
# leapfrog 蛙跳法
|
||||
method: leapfrog
|
||||
|
||||
# ── 步骤控制 ──────────────────────────────────
|
||||
# 以下参数控制哪些步骤被执行和保存
|
||||
|
||||
# 预热步数:模拟开始时跳过不保存的步数(用于稳定初始状态)
|
||||
warmup_steps: 0 # 默认 0(立即开始记录)
|
||||
|
||||
# 总模拟时间(秒),程序自动计算 NT = T_total / DT
|
||||
# 如果同时指定了 NT,以 NT 为准
|
||||
T_total: 200.0
|
||||
|
||||
# 抽帧间隔(每 NSTEP 步取一帧用于动画)
|
||||
NSTEP: 100
|
||||
|
||||
# ── 时间步长 ──────────────────────────────────
|
||||
DT: 0.001 # 时间步长 (s)
|
||||
|
||||
# 抽帧范围:只保存 [sample_start, sample_end) 区间内的帧
|
||||
sample_start: null # null 表示从头开始(帧索引从 0 起)
|
||||
sample_end: null # null 表示到末尾
|
||||
|
||||
|
||||
|
||||
# ── 渲染方式 ──────────────────────────────────
|
||||
# 3D 动画中原子渲染方式:
|
||||
# 0 = Sphere (网格球体,效果精细,原子数少时推荐)
|
||||
# 1 = Marker (GPU 实例化点,原子数多时性能更佳)
|
||||
use_marker: 1
|
||||
|
||||
# ── 显示参数 ──────────────────────────────────
|
||||
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
# 小球颜色
|
||||
# 小球半径从 coord_file 的 radius 列读取
|
||||
ball_color_r: 0.20 # R 分量 (0~1)
|
||||
ball_color_g: 0.60 # G 分量
|
||||
ball_color_b: 0.90 # B 分量
|
||||
|
||||
# 盒子面颜色
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机初始位置 ────────────────────────────
|
||||
camera_distance: 60.0 # 摄像机到场景中心的距离
|
||||
camera_elevation: 0.0 # 俯仰角(度),负值=俯视
|
||||
camera_azimuth: 0.0 # 方位角(度)
|
||||
camera_center_x: 30.0 # 摄像机注视点 x
|
||||
camera_center_y: 0.0 # 摄像机注视点 y
|
||||
camera_center_z: 0.0 # 摄像机注视点 z
|
||||
move_camera: 0 # 0=固定视角, 1=按 move_camera.txt 运动
|
||||
|
||||
# ── 视觉放大 ──────────────────────────────────
|
||||
display_amp: [1.0, 1.0, 10.0] # x/y/z 方向视觉位移放大倍数(不影响物理)
|
||||
@@ -0,0 +1,9 @@
|
||||
# move_camera.txt — 摄像机速度段驱动
|
||||
# 格式: start-end vx=f vy=f vz=f rx=d ry=d rz=d
|
||||
# vx/vy/vz: 平移速度(每帧移动单位)
|
||||
# rx/ry/rz: 旋转速度(每帧度数)
|
||||
# rx → elevation(俯仰), ry → azimuth(方位), rz → (预留)
|
||||
#
|
||||
# 示例:前60帧向右平移+绕x旋转,30-90帧向上平移+绕y绕z旋转
|
||||
all vx=0.02
|
||||
# 30-90 vy=0.02 ry=1 rz=1
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Case runner for Dynamics case06 — 1D atomic chain (transverse wave).
|
||||
|
||||
This script keeps program and data separated:
|
||||
- program: ../../dynamics.py
|
||||
- input: ./input
|
||||
- output: ./output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CASE_DIR = Path(__file__).resolve().parent
|
||||
DYNAMICS_PATH = Path("..") / ".." / "dynamics.py"
|
||||
INPUT_DIR = Path("input")
|
||||
OUTPUT_DIR = Path("output")
|
||||
CONFIG_FILE = INPUT_DIR / "input.txt"
|
||||
|
||||
|
||||
def load_dynamics_module(module_path: Path):
|
||||
spec = importlib.util.spec_from_file_location("dynamics_module", module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"无法加载 dynamics.py: {module_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case06")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
dynamics_path = (CASE_DIR / DYNAMICS_PATH).resolve()
|
||||
input_dir = (CASE_DIR / INPUT_DIR).resolve()
|
||||
output_dir = (CASE_DIR / OUTPUT_DIR).resolve()
|
||||
config_path = (CASE_DIR / CONFIG_FILE).resolve()
|
||||
|
||||
module = load_dynamics_module(dynamics_path)
|
||||
module.run_case(
|
||||
config_path=config_path,
|
||||
runtime_base=CASE_DIR,
|
||||
input_dir=input_dir,
|
||||
output_dir=output_dir,
|
||||
no_plot=args.no_plot,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
k1 300.0 1.0
|
||||
@@ -0,0 +1,40 @@
|
||||
n1 n2 bond_name
|
||||
1 2 k1
|
||||
2 3 k1
|
||||
3 4 k1
|
||||
4 5 k1
|
||||
5 6 k1
|
||||
6 7 k1
|
||||
7 8 k1
|
||||
8 9 k1
|
||||
9 10 k1
|
||||
10 11 k1
|
||||
11 12 k1
|
||||
12 13 k1
|
||||
13 14 k1
|
||||
14 15 k1
|
||||
15 16 k1
|
||||
16 17 k1
|
||||
17 18 k1
|
||||
18 19 k1
|
||||
19 20 k1
|
||||
20 21 k1
|
||||
21 22 k1
|
||||
22 23 k1
|
||||
23 24 k1
|
||||
24 25 k1
|
||||
25 26 k1
|
||||
26 27 k1
|
||||
27 28 k1
|
||||
28 29 k1
|
||||
29 30 k1
|
||||
30 31 k1
|
||||
31 32 k1
|
||||
32 33 k1
|
||||
33 34 k1
|
||||
34 35 k1
|
||||
35 36 k1
|
||||
36 37 k1
|
||||
37 38 k1
|
||||
38 39 k1
|
||||
39 40 k1
|
||||
@@ -0,0 +1,41 @@
|
||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
1 1 0.1 0 0 0 0 0 0 0 1 1
|
||||
2 1 0.1 1 0 0 0 0 0 0 1 1
|
||||
3 1 0.1 2 0 0 0 0 0 0 1 1
|
||||
4 1 0.1 3 0 0 0 0 0 0 1 1
|
||||
5 1 0.1 4 0 0 0 0 0 0 1 1
|
||||
6 1 0.1 5 0 0 0 0 0 0 1 1
|
||||
7 1 0.1 6 0 0 0 0 0 0 1 1
|
||||
8 1 0.1 7 0 0 0 0 0 0 1 1
|
||||
9 1 0.1 8 0 0 0 0 0 0 1 1
|
||||
10 1 0.1 9 0 0 0 0 0 0 1 1
|
||||
11 1 0.1 10 0 0 0 0 0 0 1 1
|
||||
12 1 0.1 11 0 0 0 0 0 0 1 1
|
||||
13 1 0.1 12 0 0 0 0 0 0 1 1
|
||||
14 1 0.1 13 0 0 0 0 0 0 1 1
|
||||
15 1 0.1 14 0 0 0 0 0 0 1 1
|
||||
16 1 0.1 15 0 0 0 0 0 0 1 1
|
||||
17 1 0.1 16 0 0 0 0 0 0 1 1
|
||||
18 1 0.1 17 0 0 0 0 0 0 1 1
|
||||
19 1 0.1 18 0 0 0 0 0 0 1 1
|
||||
20 1 0.1 19 0 0 0 0 0 0 1 1
|
||||
21 1 0.1 20 0 0 0 0 0 0 1 1
|
||||
22 1 0.1 21 0 0 0 0 0 0 1 1
|
||||
23 1 0.1 22 0 0 0 0 0 0 1 1
|
||||
24 1 0.1 23 0 0 0 0 0 0 1 1
|
||||
25 1 0.1 24 0 0 0 0 0 0 1 1
|
||||
26 1 0.1 25 0 0 0 0 0 0 1 1
|
||||
27 1 0.1 26 0 0 0 0 0 0 1 1
|
||||
28 1 0.1 27 0 0 0 0 0 0 1 1
|
||||
29 1 0.1 28 0 0 0 0 0 0 1 1
|
||||
30 1 0.1 29 0 0 0 0 0 0 1 1
|
||||
31 1 0.1 30 0 0 0 0 0 0 1 1
|
||||
32 1 0.1 31 0 0 0 0 0 0 1 1
|
||||
33 1 0.1 32 0 0 0 0 0 0 1 1
|
||||
34 1 0.1 33 0 0 0 0 0 0 1 1
|
||||
35 1 0.1 34 0 0 0 0 0 0 1 1
|
||||
36 1 0.1 35 0 0 0 0 0 0 1 1
|
||||
37 1 0.1 36 0 0 0 0 0 0 1 1
|
||||
38 1 0.1 37 0 0 0 0 0 0 1 1
|
||||
39 1 0.1 38 0 0 0 0 0 0 1 1
|
||||
40 1 0.1 39 0 0 0 0 0 1 1 1
|
||||
@@ -0,0 +1,2 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0.1 0 0 0.04 0 0 0 0 90 all
|
||||
@@ -0,0 +1,114 @@
|
||||
# 物理模拟参数配置
|
||||
# 格式:YAML
|
||||
# 用法:python run_dynamics.py
|
||||
|
||||
# ── 流程控制 ──────────────────────────────────
|
||||
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||
step_simulate: 1 # 运行物理模拟 → output/display.txt(引擎直接抽帧)
|
||||
step_sample: 0 # (旧版)从 trajectory.txt 重新抽帧,默认0=不执行
|
||||
step_plot: 1 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||
step_animation: 0 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||
step_plot_wave: 1 # 绘制波形能量动画
|
||||
force_calc: 1 # 强制重新计算:1=跳过缓存强算,0=自动使用已有输出
|
||||
plot_wave_save_gif: 0 # 输出波形 GIF(需 step_plot_wave=1)
|
||||
plot_wave_save_mp4: 0 # 输出波形 MP4(需 step_plot_wave=1)
|
||||
|
||||
# ── 文件保存 ──────────────────────────────────
|
||||
save_trajectory: 0 # 0=不保留完整轨迹文件, 1=保留 trajectory.txt(用于后续单独抽帧)
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
# 可选: python, c, cpp, fortran, java
|
||||
engine: c # 默认使用 python 引擎
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 300.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
# 坐标文件格式:
|
||||
# 第一行:n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||
# 后续行:原子序号 质量 半径 x y z vx vy vz fix_x fix_y fix_z
|
||||
coord_file: input/coord.txt
|
||||
connection_file: input/connection.txt
|
||||
bond_file: input/bond.txt
|
||||
driver_file: input/driver.txt # 驱动力定义文件(driving_force=1 时生效)
|
||||
|
||||
# 绘图/动画展示的原子序号(对应 coord_file 第一列 n)
|
||||
plot_atom: 1
|
||||
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
# 三个方向分量分别对应 x, y, z
|
||||
G: [0.000, 0.000, 0.000] # 重力场分量 (m/s²)
|
||||
B: [0.005, 0.000, 0.005] # 阻尼分量
|
||||
|
||||
# ── 力开关(0=关闭, 1=开启)──────────────────
|
||||
gravity_field: 0 # 均匀重力场 (G)
|
||||
gravity_interaction: 0 # 原子间万有引力
|
||||
elastic_force: 1 # 弹簧键力
|
||||
damping_force: 0 # 阻尼 (B)
|
||||
driving_force: 1 # 驱动力(需 driver_file 定义)
|
||||
#
|
||||
gravity_strength: 1.0 # 万有引力强度(仅 gravity_interaction=1 时有效)
|
||||
|
||||
# ── 数值算法 ──────────────────────────────────
|
||||
# 可选:
|
||||
# explicit_euler 显式欧拉法
|
||||
# implicit_euler 隐式欧拉法
|
||||
# midpoint 中点法
|
||||
# leapfrog 蛙跳法
|
||||
method: leapfrog
|
||||
|
||||
# ── 步骤控制 ──────────────────────────────────
|
||||
# 以下参数控制哪些步骤被执行和保存
|
||||
|
||||
# 预热步数:模拟开始时跳过不保存的步数(用于稳定初始状态)
|
||||
warmup_steps: 0 # 默认 0(立即开始记录)
|
||||
|
||||
# 总模拟时间(秒),程序自动计算 NT = T_total / DT
|
||||
# 如果同时指定了 NT,以 NT 为准
|
||||
T_total: 10.0
|
||||
|
||||
# 抽帧间隔(每 NSTEP 步取一帧用于动画)
|
||||
NSTEP: 20
|
||||
|
||||
# ── 时间步长 ──────────────────────────────────
|
||||
DT: 0.001 # 时间步长 (s)
|
||||
|
||||
# 抽帧范围:只保存 [sample_start, sample_end) 区间内的帧
|
||||
sample_start: null # null 表示从头开始(帧索引从 0 起)
|
||||
sample_end: null # null 表示到末尾
|
||||
|
||||
|
||||
|
||||
# ── 渲染方式 ──────────────────────────────────
|
||||
# 3D 动画中原子渲染方式:
|
||||
# 0 = Sphere (网格球体,效果精细,原子数少时推荐)
|
||||
# 1 = Marker (GPU 实例化点,原子数多时性能更佳)
|
||||
use_marker: 1
|
||||
|
||||
# ── 显示参数 ──────────────────────────────────
|
||||
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
# 小球颜色
|
||||
# 小球半径从 coord_file 的 radius 列读取
|
||||
ball_color_r: 0.20 # R 分量 (0~1)
|
||||
ball_color_g: 0.60 # G 分量
|
||||
ball_color_b: 0.90 # B 分量
|
||||
|
||||
# 盒子面颜色
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机初始位置 ────────────────────────────
|
||||
camera_distance: 120.0 # 摄像机到场景中心的距离
|
||||
camera_elevation: 0.0 # 俯仰角(度),负值=俯视
|
||||
camera_azimuth: 0.0 # 方位角(度)
|
||||
camera_center_x: 60.0 # 摄像机注视点 x
|
||||
camera_center_y: 0.0 # 摄像机注视点 y
|
||||
camera_center_z: 0.0 # 摄像机注视点 z
|
||||
move_camera: 0 # 0=固定视角, 1=按 move_camera.txt 运动
|
||||
|
||||
# ── 视觉放大 ──────────────────────────────────
|
||||
display_amp: [1.0, 1.0, 10.0] # x/y/z 方向视觉位移放大倍数(不影响物理)
|
||||
@@ -0,0 +1,9 @@
|
||||
# move_camera.txt — 摄像机速度段驱动
|
||||
# 格式: start-end vx=f vy=f vz=f rx=d ry=d rz=d
|
||||
# vx/vy/vz: 平移速度(每帧移动单位)
|
||||
# rx/ry/rz: 旋转速度(每帧度数)
|
||||
# rx → elevation(俯仰), ry → azimuth(方位), rz → (预留)
|
||||
#
|
||||
# 示例:前60帧向右平移+绕x旋转,30-90帧向上平移+绕y绕z旋转
|
||||
all vx=0.02
|
||||
# 30-90 vy=0.02 ry=1 rz=1
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Case runner for Dynamics case06 — 1D atomic chain (transverse wave).
|
||||
|
||||
This script keeps program and data separated:
|
||||
- program: ../../dynamics.py
|
||||
- input: ./input
|
||||
- output: ./output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CASE_DIR = Path(__file__).resolve().parent
|
||||
DYNAMICS_PATH = Path("..") / ".." / "dynamics.py"
|
||||
INPUT_DIR = Path("input")
|
||||
OUTPUT_DIR = Path("output")
|
||||
CONFIG_FILE = INPUT_DIR / "input.txt"
|
||||
|
||||
|
||||
def load_dynamics_module(module_path: Path):
|
||||
spec = importlib.util.spec_from_file_location("dynamics_module", module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"无法加载 dynamics.py: {module_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case06")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
dynamics_path = (CASE_DIR / DYNAMICS_PATH).resolve()
|
||||
input_dir = (CASE_DIR / INPUT_DIR).resolve()
|
||||
output_dir = (CASE_DIR / OUTPUT_DIR).resolve()
|
||||
config_path = (CASE_DIR / CONFIG_FILE).resolve()
|
||||
|
||||
module = load_dynamics_module(dynamics_path)
|
||||
module.run_case(
|
||||
config_path=config_path,
|
||||
runtime_base=CASE_DIR,
|
||||
input_dir=input_dir,
|
||||
output_dir=output_dir,
|
||||
no_plot=args.no_plot,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
h 100.0 1.0
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
5101 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
@@ -0,0 +1,89 @@
|
||||
# 物理模拟参数配置
|
||||
# 格式:YAML
|
||||
# 用法:python run_dynamics.py
|
||||
|
||||
# ── 流程控制 ──────────────────────────────────
|
||||
step_simulate: 1 # 运行物理模拟
|
||||
step_sample: 0 # 重新抽帧,默认0=不执行
|
||||
step_plot: 0 # 绘制轨迹/能量图
|
||||
step_animation: 1 # 自动播放 VisPy 3D 动画窗口
|
||||
step_plot_wave: 0 # 绘制波形能量动画
|
||||
force_calc: 1 # 强制重新计算
|
||||
|
||||
# ── 文件保存 ──────────────────────────────────
|
||||
save_trajectory: 0 # 0=不保留完整轨迹文件
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
engine: c
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 120.0
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
coord_file: input/coord.txt
|
||||
connection_file: input/connection.txt
|
||||
bond_file: input/bond.txt
|
||||
driver_file: input/driver.txt
|
||||
|
||||
# 绘图/动画展示的原子序号
|
||||
plot_atom: 5101 # 中心原子 (0,0)
|
||||
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
G: [0.000, 0.000, 0.000]
|
||||
B: [0.000, 0.000, 0.000]
|
||||
|
||||
# ── 力开关 ────────────────────────────────────
|
||||
gravity_field: 0
|
||||
gravity_interaction: 0
|
||||
elastic_force: 1
|
||||
damping_force: 0
|
||||
driving_force: 1
|
||||
gravity_strength: 1.0
|
||||
|
||||
# ── 数值算法 ──────────────────────────────────
|
||||
method: leapfrog
|
||||
|
||||
# ── 步骤控制 ──────────────────────────────────
|
||||
warmup_steps: 0 # 受迫波动,无需预热
|
||||
T_total: 100.0
|
||||
NSTEP: 10
|
||||
DT: 0.01
|
||||
|
||||
sample_start: null
|
||||
sample_end: null
|
||||
|
||||
# ── 渲染方式 ──────────────────────────────────
|
||||
use_marker: 1
|
||||
|
||||
# ── 位移着色 ──────────────────────────────────
|
||||
display_color: {
|
||||
x : [0, [255, 0, 0]],
|
||||
y : [0, [ 0, 255, 0]],
|
||||
z : [1, [ 0, 0, 255]],
|
||||
xy : [0, [255, 255, 0]],
|
||||
yz : [0, [ 0, 255, 255]],
|
||||
zx : [0, [255, 0, 255]],
|
||||
xyz : [0, [ 0, 0, 0]],
|
||||
}
|
||||
|
||||
# ── 显示参数 ──────────────────────────────────
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
ball_color_r: 0.20
|
||||
ball_color_g: 0.60
|
||||
ball_color_b: 0.90
|
||||
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机 ────────────────────────────────────
|
||||
camera_distance: 120.0
|
||||
camera_elevation: 60.0
|
||||
camera_azimuth: -45.0
|
||||
camera_center_x: 0.0
|
||||
camera_center_y: 0.0
|
||||
camera_center_z: 0.0
|
||||
move_camera: 0
|
||||
|
||||
display_amp: [1.0, 1.0, 1.0]
|
||||
@@ -0,0 +1,2 @@
|
||||
0 0 50
|
||||
0 0 80
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Case runner for Dynamics case11 — 2D grid (61x61 atomic mesh).
|
||||
|
||||
This script keeps program and data separated:
|
||||
- program: ../../dynamics.py
|
||||
- input: ./input
|
||||
- output: ./output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CASE_DIR = Path(__file__).resolve().parent
|
||||
DYNAMICS_PATH = Path("..") / ".." / "dynamics.py"
|
||||
INPUT_DIR = Path("input")
|
||||
OUTPUT_DIR = Path("output")
|
||||
CONFIG_FILE = INPUT_DIR / "input.txt"
|
||||
|
||||
|
||||
def load_dynamics_module(module_path: Path):
|
||||
spec = importlib.util.spec_from_file_location("dynamics_module", module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"无法加载 dynamics.py: {module_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case11")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
dynamics_path = (CASE_DIR / DYNAMICS_PATH).resolve()
|
||||
input_dir = (CASE_DIR / INPUT_DIR).resolve()
|
||||
output_dir = (CASE_DIR / OUTPUT_DIR).resolve()
|
||||
config_path = (CASE_DIR / CONFIG_FILE).resolve()
|
||||
|
||||
module = load_dynamics_module(dynamics_path)
|
||||
module.run_case(
|
||||
config_path=config_path,
|
||||
runtime_base=CASE_DIR,
|
||||
input_dir=input_dir,
|
||||
output_dir=output_dir,
|
||||
no_plot=args.no_plot,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
h 100.0 1.0
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
3081 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
7121 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
@@ -0,0 +1,83 @@
|
||||
# 物理模拟参数配置
|
||||
# case12 — 二维网格两点干涉(双点源 z 方向驱动)
|
||||
# 驱动点: (0,-10) 和 (0,10),波从两点向外传播,在中心区域干涉
|
||||
|
||||
# ── 流程控制 ──────────────────────────────────
|
||||
step_simulate: 1 # 运行物理模拟
|
||||
step_sample: 0 # 重新抽帧,默认0=不执行
|
||||
step_plot: 0 # 绘制轨迹/能量图
|
||||
step_animation: 1 # 自动播放 VisPy 3D 动画窗口
|
||||
step_plot_wave: 0 # 绘制波形能量动画
|
||||
force_calc: 1 # 强制重新计算
|
||||
|
||||
# ── 文件保存 ──────────────────────────────────
|
||||
save_trajectory: 0
|
||||
|
||||
# ── 计算引擎 ──────────────────────────────────
|
||||
engine: c
|
||||
|
||||
# ── 盒子 ──────────────────────────────────────
|
||||
box_a: 120.0
|
||||
|
||||
# ── 初始构型 ──────────────────────────────────
|
||||
coord_file: input/coord.txt
|
||||
connection_file: input/connection.txt
|
||||
bond_file: input/bond.txt
|
||||
driver_file: input/driver.txt
|
||||
plot_atom: 5101 # 中心区域用于信息显示
|
||||
|
||||
# ── 物理参数 ──────────────────────────────────
|
||||
G: [0.000, 0.000, 0.000]
|
||||
B: [0.000, 0.000, 0.000]
|
||||
|
||||
gravity_field: 0
|
||||
gravity_interaction: 0
|
||||
elastic_force: 1
|
||||
damping_force: 0
|
||||
driving_force: 1
|
||||
gravity_strength: 1.0
|
||||
|
||||
method: leapfrog
|
||||
|
||||
# ── 步骤控制 ──────────────────────────────────
|
||||
warmup_steps: 0
|
||||
T_total: 100.0
|
||||
NSTEP: 500
|
||||
DT: 0.001
|
||||
|
||||
sample_start: null
|
||||
sample_end: null
|
||||
|
||||
# ── 渲染/着色 ─────────────────────────────────
|
||||
use_marker: 1
|
||||
display_color: {
|
||||
x : [0, [255, 0, 0]],
|
||||
y : [0, [ 0, 255, 0]],
|
||||
z : [0, [ 0, 0, 255]],
|
||||
xy : [0, [255, 255, 0]],
|
||||
yz : [0, [ 0, 255, 255]],
|
||||
zx : [0, [255, 0, 255]],
|
||||
xyz : [1, [255, 255, 255]],
|
||||
}
|
||||
|
||||
# ── 显示参数 ──────────────────────────────────
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
ball_color_r: 0.20
|
||||
ball_color_g: 0.60
|
||||
ball_color_b: 0.90
|
||||
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
# ── 摄像机 ────────────────────────────────────
|
||||
camera_distance: 120.0
|
||||
camera_elevation: 60.0
|
||||
camera_azimuth: -45.0
|
||||
camera_center_x: 0.0
|
||||
camera_center_y: 0.0
|
||||
camera_center_z: 0.0
|
||||
move_camera: 0
|
||||
|
||||
display_amp: [1.0, 1.0, 1.0]
|
||||
@@ -0,0 +1,2 @@
|
||||
0 0 50
|
||||
0 0 80
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Case runner for Dynamics case12 — 2D grid dual source interference.
|
||||
|
||||
This script keeps program and data separated:
|
||||
- program: ../../dynamics.py
|
||||
- input: ./input
|
||||
- output: ./output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CASE_DIR = Path(__file__).resolve().parent
|
||||
DYNAMICS_PATH = Path("..") / ".." / "dynamics.py"
|
||||
INPUT_DIR = Path("input")
|
||||
OUTPUT_DIR = Path("output")
|
||||
CONFIG_FILE = INPUT_DIR / "input.txt"
|
||||
|
||||
|
||||
def load_dynamics_module(module_path: Path):
|
||||
spec = importlib.util.spec_from_file_location("dynamics_module", module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"无法加载 dynamics.py: {module_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case11")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
dynamics_path = (CASE_DIR / DYNAMICS_PATH).resolve()
|
||||
input_dir = (CASE_DIR / INPUT_DIR).resolve()
|
||||
output_dir = (CASE_DIR / OUTPUT_DIR).resolve()
|
||||
config_path = (CASE_DIR / CONFIG_FILE).resolve()
|
||||
|
||||
module = load_dynamics_module(dynamics_path)
|
||||
module.run_case(
|
||||
config_path=config_path,
|
||||
runtime_base=CASE_DIR,
|
||||
input_dir=input_dir,
|
||||
output_dir=output_dir,
|
||||
no_plot=args.no_plot,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
h 100.0 1.0
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,102 @@
|
||||
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
102 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
203 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
304 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
405 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
506 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
607 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
708 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
809 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
910 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
1011 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
1112 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
1213 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
1314 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
1415 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
1516 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
1617 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
1718 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
1819 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
1920 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
2021 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
2122 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
2223 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
2324 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
2425 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
2526 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
2627 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
2728 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
2829 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
2930 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
3031 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
3132 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
3233 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
3334 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
3435 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
3536 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
3637 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
3738 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
3839 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
3940 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
4041 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
4142 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
4243 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
4344 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
4445 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
4546 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
4647 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
4748 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
4849 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
4950 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
5051 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
5152 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
5253 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
5354 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
5455 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
5556 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
5657 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
5758 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
5859 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
5960 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
6061 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
6162 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
6263 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
6364 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
6465 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
6566 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
6667 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
6768 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
6869 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
6970 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
7071 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
7172 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
7273 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
7374 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
7475 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
7576 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
7677 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
7778 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
7879 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
7980 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
8081 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
8182 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
8283 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
8384 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
8485 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
8586 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
8687 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
8788 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
8889 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
8990 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
9091 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
9192 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
9293 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
9394 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
9495 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
9596 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
9697 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
9798 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
9899 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
10000 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
10101 0 0 2.0 0 0 0.05 90 90 90 all
|
||||
@@ -0,0 +1,73 @@
|
||||
# 物理模拟参数配置
|
||||
# case13 — 二维网格平面波(左边界驱动,右边界吸收)
|
||||
# 左边界全部 101 原子齐振驱动 → 波从左向右传播 → 右边界全固定
|
||||
|
||||
step_simulate: 1
|
||||
step_sample: 0
|
||||
step_plot: 0
|
||||
step_animation: 1
|
||||
step_plot_wave: 0
|
||||
force_calc: 1
|
||||
|
||||
save_trajectory: 0
|
||||
|
||||
engine: c
|
||||
box_a: 120.0
|
||||
|
||||
coord_file: input/coord.txt
|
||||
connection_file: input/connection.txt
|
||||
bond_file: input/bond.txt
|
||||
driver_file: input/driver.txt
|
||||
plot_atom: 51 # 左边界中间原子用于信息显示
|
||||
|
||||
G: [0.000, 0.000, 0.000]
|
||||
B: [0.000, 0.000, 0.000]
|
||||
|
||||
gravity_field: 0
|
||||
gravity_interaction: 0
|
||||
elastic_force: 1
|
||||
damping_force: 0
|
||||
driving_force: 1
|
||||
gravity_strength: 1.0
|
||||
|
||||
method: leapfrog
|
||||
|
||||
warmup_steps: 0
|
||||
T_total: 200.0
|
||||
NSTEP: 100
|
||||
DT: 0.01
|
||||
|
||||
sample_start: null
|
||||
sample_end: null
|
||||
|
||||
# ── 渲染/着色 ─────────────────────────────────
|
||||
use_marker: 1
|
||||
display_color: {
|
||||
x : [1, [255, 0, 0]],
|
||||
y : [1, [ 0, 255, 0]],
|
||||
z : [1, [ 0, 0, 255]],
|
||||
xy : [0, [255, 255, 0]],
|
||||
yz : [0, [ 0, 255, 255]],
|
||||
zx : [0, [255, 0, 255]],
|
||||
xyz : [0, [255, 255, 255]],
|
||||
}
|
||||
|
||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
ball_color_r: 0.20
|
||||
ball_color_g: 0.60
|
||||
ball_color_b: 0.90
|
||||
|
||||
box_color_r: 0.80
|
||||
box_color_g: 0.80
|
||||
box_color_b: 0.85
|
||||
|
||||
camera_distance: 120.0
|
||||
camera_elevation: 60.0
|
||||
camera_azimuth: -45.0
|
||||
camera_center_x: 0.0
|
||||
camera_center_y: 0.0
|
||||
camera_center_z: 0.0
|
||||
move_camera: 0
|
||||
|
||||
display_amp: [1.0, 1.0, 1.0]
|
||||
@@ -0,0 +1,2 @@
|
||||
0 0 50
|
||||
0 0 80
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Case runner for Dynamics case13 — 2D grid (61x61 atomic mesh).
|
||||
|
||||
This script keeps program and data separated:
|
||||
- program: ../../dynamics.py
|
||||
- input: ./input
|
||||
- output: ./output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CASE_DIR = Path(__file__).resolve().parent
|
||||
DYNAMICS_PATH = Path("..") / ".." / "dynamics.py"
|
||||
INPUT_DIR = Path("input")
|
||||
OUTPUT_DIR = Path("output")
|
||||
CONFIG_FILE = INPUT_DIR / "input.txt"
|
||||
|
||||
|
||||
def load_dynamics_module(module_path: Path):
|
||||
spec = importlib.util.spec_from_file_location("dynamics_module", module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"无法加载 dynamics.py: {module_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="运行 Dynamics 示例案例 case13")
|
||||
parser.add_argument("--no-plot", action="store_true", help="跳过 matplotlib 绘图")
|
||||
args = parser.parse_args()
|
||||
|
||||
dynamics_path = (CASE_DIR / DYNAMICS_PATH).resolve()
|
||||
input_dir = (CASE_DIR / INPUT_DIR).resolve()
|
||||
output_dir = (CASE_DIR / OUTPUT_DIR).resolve()
|
||||
config_path = (CASE_DIR / CONFIG_FILE).resolve()
|
||||
|
||||
module = load_dynamics_module(dynamics_path)
|
||||
module.run_case(
|
||||
config_path=config_path,
|
||||
runtime_base=CASE_DIR,
|
||||
input_dir=input_dir,
|
||||
output_dir=output_dir,
|
||||
no_plot=args.no_plot,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# case06: 一维原子链横波模拟
|
||||
|
||||
60 个原子沿 x 轴排列,相邻原子用弹簧连接。原子 1 受 z 方向驱动力作用,产生沿链传播的横波。
|
||||
|
||||
## 物理设定
|
||||
|
||||
| 参数 | 值 |
|
||||
|---|---|
|
||||
| 原子数 | 120 |
|
||||
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||
| 约束 | 原子**沿 z 方向自由振动**(fix_x=1, fix_y=1, fix_z=0),x, y 锁定 |
|
||||
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||
| 重力 | 无 |
|
||||
| 万有引力 | 无 |
|
||||
| 阻尼 | 无 |
|
||||
| 驱动力 | 原子 1(z 方向驱动) |
|
||||
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||
|
||||
## 驱动力
|
||||
|
||||
原子 1 的位置由 `input/driver.txt` 中的驱动力公式决定:
|
||||
|
||||
```math
|
||||
z(t) = A_z \cdot \cos(2\pi f_z t + \phi_z)
|
||||
```
|
||||
|
||||
当前参数:A_z = 0.5, f_z = 0.1 Hz, φ_z = 90°, period = all(全程驱动)。
|
||||
|
||||
## 动力学行为
|
||||
|
||||
原子 1 沿 z 方向的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的**横波**。由于 z 方向的振动是横向的,弹簧大部分张力在 x 方向,z 方向的有效刚度是非线性的——等效于一个三次方恢复力(FPU 型非线性),因此波速较慢。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
cd examples/case06
|
||||
python run_dynamics.py
|
||||
```
|
||||
|
||||
配置参数详见 `input/input.txt`,驱动力定义见 `input/driver.txt`,完整文档见 `doc/index.html`。
|
||||
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>case06 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f8f9fa;
|
||||
--card: #fff;
|
||||
--text: #1a1a2e;
|
||||
--accent: #2563eb;
|
||||
--accent-light: #dbeafe;
|
||||
--code-bg: #1e293b;
|
||||
--code-text: #e2e8f0;
|
||||
--border: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
padding: 56px 24px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hero .subtitle {
|
||||
margin-top: 10px;
|
||||
font-size: 1.05rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── Layout ── */
|
||||
.container { max-width: 820px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
section { margin-bottom: 44px; }
|
||||
h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
p, li { margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
strong { color: var(--accent); }
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* ── Formula / Code blocks ── */
|
||||
.formula {
|
||||
background: var(--card);
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 14px 20px;
|
||||
margin: 14px 0;
|
||||
font-family: "Times New Roman", "STIX", serif;
|
||||
font-size: 1.05rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
code {
|
||||
background: var(--accent-light);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 16px 20px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: 14px 0;
|
||||
}
|
||||
pre .cm { color: #94a3b8; font-style: italic; } /* comment */
|
||||
|
||||
/* ── Table ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { background: var(--accent-light); font-weight: 600; }
|
||||
|
||||
/* ── TOC ── */
|
||||
.toc { counter-reset: toc; }
|
||||
.toc li { counter-increment: toc; list-style: none; margin-bottom: 6px; }
|
||||
.toc li::before { content: counter(toc) ". "; font-weight: 600; color: var(--accent); }
|
||||
.toc a { color: var(--accent); text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Flow diagram ── */
|
||||
.flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center; margin: 16px 0; }
|
||||
.flow-step {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.flow-arrow { color: var(--muted); font-size: 1.2rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.hero h1 { font-size: 1.5rem; }
|
||||
.flow { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================ -->
|
||||
<header class="hero">
|
||||
<h1>一维原子链驱动力学模拟</h1>
|
||||
<p class="subtitle">120 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||
<span class="badge">case06 · examples/case06</span>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TOC -->
|
||||
<!-- ============================================================ -->
|
||||
<section>
|
||||
<h2>目录</h2>
|
||||
<ol class="toc">
|
||||
<li><a href="#physics">物理原理</a></li>
|
||||
<li><a href="#algorithm">数值算法</a></li>
|
||||
<li><a href="#driver">驱动力模型</a></li>
|
||||
<li><a href="#usage">使用方法</a></li>
|
||||
<li><a href="#params">参数参考</a></li>
|
||||
<li><a href="#files">文件结构</a></li>
|
||||
<li><a href="#troubleshoot">常见问题</a></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 1. Physics -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="physics">
|
||||
<h2>一、物理原理</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.1 一维原子链</h3>
|
||||
<p>120 个原子沿 <strong>x 轴</strong> 等间距排列,原子间距为 1。相邻原子之间用 <strong>理想弹簧</strong> 连接,弹簧的劲度系数 <em>k</em> = 1.0,原长 <em>L</em>₀ = 1.0(与原子间距一致,初始状态弹簧无拉伸)。</p>
|
||||
<p>每个原子被限制在 <strong>z 方向</strong> 自由振动,x 和 y 方向锁定(<code>fix_x=1, fix_y=1, fix_z=0</code>)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.2 弹簧力(胡克定律)</h3>
|
||||
<p>当原子 <em>i</em> 和 <em>j</em> 之间有弹簧连接时,原子 <em>i</em> 受到的弹簧力为:</p>
|
||||
<div class="formula">
|
||||
<strong>F</strong> = −<em>k</em> · (<em>d</em> − <em>L</em>₀) · <strong>u</strong><sub><em>ij</em></sub>
|
||||
</div>
|
||||
<p>其中 <em>d</em> = |<strong>r</strong><sub><em>j</em></sub> − <strong>r</strong><sub><em>i</em></sub>| 为两原子间距离,<strong>u</strong><sub><em>ij</em></sub> 为从 <em>i</em> 指向 <em>j</em> 的单位向量。由于原子只在 z 方向振动,弹簧在 z 方向的分量是 <strong>几何非线性</strong> 的——对于小振幅近似,z 方向等效于一个三次方恢复力(FPU 型非线性)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.3 运动方程</h3>
|
||||
<p>对于第 <em>i</em> 个自由原子(非受驱),牛顿第二定律给出:</p>
|
||||
<div class="formula">
|
||||
<em>m</em> · <strong>a</strong><sub><em>i</em></sub> = <strong>F</strong><sub><em>i</em></sub><sup>spring</sup> + <strong>F</strong><sub><em>i</em></sub><sup>driving</sup>
|
||||
</div>
|
||||
<p>本案例中 <strong>唯一的外力</strong> 来自驱动力(仅施加于原子 1)。无重力、无万有引力、无阻尼,系统总能量守恒。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>1.4 波传播</h3>
|
||||
<p>原子 1 的受迫振动通过弹簧逐次传递给相邻原子,形成沿链传播的 <strong>横波</strong>。由于横向振动的几何非线性(弹簧大部分张力在 x 方向,z 方向的有效刚度远小于 1),波的传播速度较慢,且高阶频率成分会在链中产生复杂的非线性动力学行为(类似 FPU 回波现象)。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 2. Algorithm -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="algorithm">
|
||||
<h2>二、数值算法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.1 蛙跳法(Leapfrog / Velocity-Verlet)</h3>
|
||||
<p>采用能量守恒特性优异的 <strong>蛙跳法</strong>(二阶辛积分器),更新公式为:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) = <strong>v</strong>(<em>t</em>) + ½ <strong>a</strong>(<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>r</strong>(<em>t</em> + Δ<em>t</em>) = <strong>r</strong>(<em>t</em>) + <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) · Δ<em>t</em><br>
|
||||
<strong>a</strong>(<em>t</em> + Δ<em>t</em>) = <strong>F</strong>(<strong>r</strong>(<em>t</em> + Δ<em>t</em>), <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>)) / <em>m</em><br>
|
||||
<strong>v</strong>(<em>t</em> + Δ<em>t</em>) = <strong>v</strong>(<em>t</em> + ½Δ<em>t</em>) + ½ <strong>a</strong>(<em>t</em> + Δ<em>t</em>) · Δ<em>t</em>
|
||||
</div>
|
||||
<p>蛙跳法在长时间模拟中能量漂移极小(本案例验证 <strong>< 0.004%</strong>),适合无阻尼的保守系统。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.2 时间步长与采样</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||||
<tr><td>DT</td><td>0.01 s</td><td>积分步长(远小于 1/ω ≈ 0.16 s,满足稳定性条件)</td></tr>
|
||||
<tr><td>T_total</td><td>100 s</td><td>总模拟时间 → NT = 10000 步</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>每 NSTEP 步取一帧用于动画 → 200 帧</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>蛙跳法(Velocity-Verlet)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>2.3 计算流程</h3>
|
||||
<div class="flow">
|
||||
<span class="flow-step">读入 coord.txt<br>connection.txt<br>bond.txt</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">施加驱动力<br>(驱动原子 1)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">记录轨迹</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">蛙跳法<br>更新位置/速度</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">固定约束<br>(x, y 锁定)</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step" style="background:#fef3c7;border-color:#f59e0b;">循环<br>NT 次</span>
|
||||
</div>
|
||||
<p style="margin-top:12px;">注意:驱动力在 <strong>每次积分前</strong> 施加,确保受驱原子的位置正确传递给弹簧力计算。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 3. Driving Force -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="driver">
|
||||
<h2>三、驱动力模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.1 定义文件</h3>
|
||||
<p>驱动力由 <code>input/driver.txt</code> 定义,格式如下:</p>
|
||||
<pre>n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||
1 0 0 5 0 0 1 0 0 90 all</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.2 数学公式</h3>
|
||||
<p>受驱原子的位置由下式决定(<strong>完全替换</strong> coord.txt 中的初始坐标和固定约束):</p>
|
||||
<div class="formula">
|
||||
<strong>r</strong>(<em>t</em>) = <strong>A</strong> · cos(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>速度由解析导数给出:</p>
|
||||
<div class="formula">
|
||||
<strong>v</strong>(<em>t</em>) = −<strong>A</strong> · 2π<em>f</em> · sin(2π<em>f</em> · <em>t</em> + <strong>φ</strong>)
|
||||
</div>
|
||||
<p>其中 <strong>A</strong> = (amp_x, amp_y, amp_z),<strong>f</strong> = (freq_x, freq_y, freq_z) 为不同方向的驱动频率,<strong>φ</strong> = (phi_x, phi_y, phi_z) 为相位(<strong>角度制</strong>,代码自动转换为弧度)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.3 本案例驱动参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>值</th><th>含义</th></tr>
|
||||
<tr><td>amp_z</td><td>5.0</td><td>z 方向驱动振幅</td></tr>
|
||||
<tr><td>freq_z</td><td>1.0 Hz</td><td>驱动频率(周期 1 s)</td></tr>
|
||||
<tr><td>phi_z</td><td>90°</td><td>驱动相位 → z(0) = 5·cos(90°) = 0</td></tr>
|
||||
<tr><td>period</td><td>all</td><td>全程驱动,永不停止</td></tr>
|
||||
</table>
|
||||
<div class="formula">
|
||||
<em>z</em>(<em>t</em>) = 5.0 · cos(2π · 1.0 · <em>t</em> + 90°)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.4 有限周期驱动</h3>
|
||||
<p><code>period</code> 参数支持三种模式:</p>
|
||||
<ul>
|
||||
<li><strong>all</strong> — 全程驱动</li>
|
||||
<li><strong>数值</strong> — 驱动指定周期数后 <strong>静止</strong>(冻结在最终位置,速度归零)。例如 <code>period: 1</code> 表示驱动 1 个完整周期后停止。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>3.5 驱动与固定约束的关系</h3>
|
||||
<p>对于受驱原子(<code>driver.txt</code> 中 <code>n</code> 指定的原子),其在 <code>coord.txt</code> 中的初始坐标和 <code>fix_x/fix_y/fix_z</code> 约束被 <strong>完全忽略</strong>。原子的位置和速度完全由驱动力公式决定。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 4. Usage -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="usage">
|
||||
<h2>四、使用方法</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.1 完整运行(模拟 + 动画)</h3>
|
||||
<pre>cd examples/case06
|
||||
python run_dynamics.py</pre>
|
||||
<p>这步会依次执行:物理模拟 → 抽帧 → 打开 VisPy 3D 动画窗口。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.2 仅查看已有结果</h3>
|
||||
<p>如果已经跑完模拟且生成了 <code>output/display.txt</code>,可以通过修改 <code>input.txt</code> 跳过计算,只开动画:</p>
|
||||
<pre>step_simulate: 0 # 跳过模拟
|
||||
step_sample: 0 # 跳过抽帧
|
||||
step_animation: 1 # 播放动画</pre>
|
||||
<p>然后运行:<code>python run_dynamics.py</code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.3 手动 3D 动画</h3>
|
||||
<p>也可以单独启动 VisPy 窗口:</p>
|
||||
<pre>python ../../draw.py output/</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.4 强制重新计算</h3>
|
||||
<p>修改参数后需要重新运行模拟时,设置:</p>
|
||||
<pre>force_calc: 1 # 忽略缓存,强制重新计算</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>4.5 动画交互</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>鼠标拖动</td><td>旋转视角</td></tr>
|
||||
<tr><td>滚轮</td><td>缩放</td></tr>
|
||||
<tr><td>W / S 键</td><td>相机沿 Z 轴向前 / 向后移动(靠近/远离场景)</td></tr>
|
||||
<tr><td>A / D 键</td><td>视角向右 / 向左平移</td></tr>
|
||||
<tr><td>E / Q 键</td><td>视角上升 / 下降(屏幕方向)</td></tr>
|
||||
<tr><td>C / X 键</td><td>增大 / 减小步长</td></tr>
|
||||
<tr><td>V 键</td><td>切换透视 / 正交投影</td></tr>
|
||||
<tr><td>左上角 <strong>reset</strong> 按钮</td><td>复位视角到初始位置</td></tr>
|
||||
<tr><td>左上角 <strong>info</strong> 按钮</td><td>切换信息面板显示/隐藏</td></tr>
|
||||
<tr><td>左上角 <strong>axes</strong> 按钮</td><td>切换坐标轴显示/隐藏</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 5. Parameters -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="params">
|
||||
<h2>五、参数参考</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.1 input.txt 关键参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>gravity_field</td><td>0</td><td>均匀重力场(已关闭)</td></tr>
|
||||
<tr><td>gravity_interaction</td><td>0</td><td>原子间万有引力(已关闭)</td></tr>
|
||||
<tr><td>elastic_force</td><td>1</td><td>弹簧键力(已开启)</td></tr>
|
||||
<tr><td>damping_force</td><td>0</td><td>阻尼(已关闭)</td></tr>
|
||||
<tr><td><strong>driving_force</strong></td><td><strong>1</strong></td><td>驱动力开关(1=开启,需 driver.txt)</td></tr>
|
||||
<tr><td>method</td><td>leapfrog</td><td>数值积分方法</td></tr>
|
||||
<tr><td>DT</td><td>0.01</td><td>积分步长 (s)</td></tr>
|
||||
<tr><td>T_total</td><td>100.0</td><td>总模拟时间 (s)</td></tr>
|
||||
<tr><td>NSTEP</td><td>50</td><td>抽帧步数间隔</td></tr>
|
||||
<tr><td>engine</td><td>python</td><td>计算引擎(python / c / cpp / fortran)</td></tr>
|
||||
<tr><td>use_marker</td><td>1</td><td>渲染模式(0=Sphere 网格, 1=Marker GPU 实例化)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>5.2 流程控制参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>0</th><th>1</th></tr>
|
||||
<tr><td>step_simulate</td><td>跳过模拟(加载已有轨迹)</td><td>运行物理模拟</td></tr>
|
||||
<tr><td>step_sample</td><td>跳过抽帧</td><td>从轨迹抽取显示帧</td></tr>
|
||||
<tr><td>step_plot</td><td>不生成图表</td><td>生成轨迹/能量图</td></tr>
|
||||
<tr><td><strong>step_plot_wave</strong></td><td>不生成波形图</td><td>生成波形能量动画 GIF</td></tr>
|
||||
<tr><td>step_animation</td><td>不启动动画</td><td>自动打开 VisPy 3D 窗口</td></tr>
|
||||
<tr><td>force_calc</td><td>自动检测缓存</td><td>强制重新计算</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 6. File Structure -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="files">
|
||||
<h2>六、文件结构</h2>
|
||||
|
||||
<pre>case06/
|
||||
├── input/
|
||||
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||
│ ├── coord.txt # 原子坐标(120 个原子)
|
||||
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||
├── output/
|
||||
│ ├── trajectory.txt # 全量轨迹数据(50000 步 × 120 原子)
|
||||
│ ├── display.txt # 抽帧后的动画数据(500 帧 × 120 原子)
|
||||
│ ├── dynamics.log # 计算日志
|
||||
│ ├── animation.log # 动画启动日志(闪退时排查用)
|
||||
│ └── wave_animation.gif # 波形能量动画(step_plot_wave=1 时生成)
|
||||
├── doc/
|
||||
│ └── index.html # <span class="cm">本文档</span>
|
||||
├── Readme.md # 案例简介
|
||||
└── run_dynamics.py # 案例运行入口</pre>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 7. Troubleshooting -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="troubleshoot">
|
||||
<h2>七、常见问题</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.1 动画窗口闪退</h3>
|
||||
<p>如果 VisPy 窗口一闪就消失,请检查:</p>
|
||||
<ul>
|
||||
<li><code>output/animation.log</code> 中是否有错误信息</li>
|
||||
<li><code>output/display.txt</code> 是否存在(需先跑 <code>step_sample: 1</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.2 原子不振动</h3>
|
||||
<p>可能原因:</p>
|
||||
<ul>
|
||||
<li><strong>NSTEP 过大</strong>:抽帧间隔大于驱动周期的一半时,动画会丢失振动细节。建议 NSTEP ≤ 1/(freq · DT · 10)</li>
|
||||
<li><strong>相位 φ 使采样点落在零值</strong>:试试 <code>phi_z: 0</code> 让原子在 t=0 处于振幅峰值</li>
|
||||
<li>确认 <code>driving_force: 1</code> 且 <code>driver.txt</code> 中 amp_z 不为 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>7.3 渲染性能慢</h3>
|
||||
<p>原子数多时动画卡顿:</p>
|
||||
<ul>
|
||||
<li>设置 <code>use_marker: 1</code>(使用 GPU 实例化渲染替代独立网格球体)</li>
|
||||
<li>增大 <code>NSTEP</code> 减少动画帧数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="border:none;border-top:1px solid var(--border);margin:40px 0;">
|
||||
|
||||
<footer style="text-align:center;color:var(--muted);font-size:0.85rem;margin-bottom:40px;">
|
||||
Dynamics Simulation Framework · 生成于 2026-06-10
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
bond_name k rest_length
|
||||
h 100.0 1.0
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user