0e636e275d
- 覆盖全部 10 个案例(原 Readme 只到 case06) - 新增案例选择指南表格 - Readme.html 为深色主题独立 HTML 页面 (含卡片布局、标签分类、代码高亮、响应式设计) - 各案例详情对齐最新配置参数
425 lines
16 KiB
Python
425 lines
16 KiB
Python
"""
|
||
engines/engine_dll.py
|
||
---------------------
|
||
Python ctypes 包装器:加载 C/C++/Fortran 动态链接库并调用 run_dynamics()。
|
||
|
||
用法(由 compute.py 内部调用,不直接运行):
|
||
|
||
from engines.engine_dll import load_dll, run_dynamics_dll
|
||
|
||
dll = load_dll("c") # 自动查找 engines/c/build/dynamics_c.dll/.so/.dylib
|
||
arrays = run_dynamics_dll(dll, config, atom_data, bond_data, driver_data)
|
||
# arrays: dict with keys x, y, z, vx, vy, vz shape=(n_frames, n_atoms)
|
||
|
||
DLL 编译(C 版本):
|
||
Windows: gcc -O3 -shared -o engines/c/build/dynamics_c.dll engines/c/dynamics_lib.c -lm
|
||
Linux: gcc -O3 -shared -fPIC -o engines/c/build/dynamics_c.so engines/c/dynamics_lib.c -lm
|
||
macOS: gcc -O3 -dynamiclib -o engines/c/build/dynamics_c.dylib engines/c/dynamics_lib.c -lm
|
||
"""
|
||
|
||
import ctypes
|
||
import os
|
||
import platform
|
||
import numpy as np
|
||
|
||
# ── DLL 文件名后缀 ─────────────────────────────────────────────
|
||
_SUFFIX = {
|
||
"windows": ".dll",
|
||
"linux": ".so",
|
||
"darwin": ".dylib",
|
||
}
|
||
|
||
# ── method 字符串 → 整数 ID ────────────────────────────────────
|
||
_METHOD_ID = {
|
||
"explicit_euler": 0,
|
||
"euler": 0,
|
||
"implicit_euler": 1,
|
||
"midpoint": 2,
|
||
"leapfrog": 3,
|
||
}
|
||
|
||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||
|
||
|
||
_DLL_NAME = {
|
||
"c": "dynamics_c",
|
||
"cpp": "dynamics_cpp",
|
||
"c++": "dynamics_cpp",
|
||
"fortran": "dynamics_f90",
|
||
"f90": "dynamics_f90",
|
||
# "python" 引擎通过直接 import 调用,不使用 DLL
|
||
}
|
||
|
||
# 引擎名规范化:将别名统一为目录名
|
||
_ENGINE_DIR = {
|
||
"c": "c",
|
||
"cpp": "cpp",
|
||
"c++": "cpp",
|
||
"fortran": "fortran",
|
||
"f90": "fortran",
|
||
"python": "python",
|
||
}
|
||
|
||
|
||
def _dll_candidates(engine: str) -> list[str]:
|
||
"""返回 DLL 候选路径列表(按优先级)。"""
|
||
sys = platform.system().lower()
|
||
ext = _SUFFIX.get(sys, ".so")
|
||
eng_dir = _ENGINE_DIR.get(engine, engine)
|
||
name = _DLL_NAME.get(engine, f"dynamics_{engine}")
|
||
base = os.path.join(_HERE, eng_dir, "build", 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))
|