docs: 更新 examples/Readme.md 并新增 Readme.html
- 覆盖全部 10 个案例(原 Readme 只到 case06) - 新增案例选择指南表格 - Readme.html 为深色主题独立 HTML 页面 (含卡片布局、标签分类、代码高亮、响应式设计) - 各案例详情对齐最新配置参数
This commit is contained in:
+209
-22
@@ -947,6 +947,179 @@ def run_from_config(config, out_dir=None):
|
||||
return traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz
|
||||
|
||||
|
||||
def run_engine_dll(engine, output_dir, config):
|
||||
"""通过 DLL(ctypes)调用计算引擎,不经过文件 I/O,直接返回轨迹数组。
|
||||
|
||||
Args:
|
||||
engine: 引擎名称 "c", "cpp", 或 "fortran"
|
||||
output_dir: 输出目录(用于保存 display.npz)
|
||||
config: YAML 配置字典
|
||||
Returns:
|
||||
None(结果直接写入 output_dir/display.npz)
|
||||
Raises:
|
||||
FileNotFoundError: DLL 尚未编译
|
||||
RuntimeError: DLL 运算出错
|
||||
"""
|
||||
import sys as _sys
|
||||
import datetime as _datetime
|
||||
_eng_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "engines")
|
||||
if _eng_dir not in _sys.path:
|
||||
_sys.path.insert(0, _eng_dir)
|
||||
from engine_dll import load_dll, run_dynamics_dll, is_dll_available
|
||||
|
||||
if not is_dll_available(engine):
|
||||
raise FileNotFoundError(
|
||||
f"DLL 未找到(引擎 {engine})。"
|
||||
f"请先编译:cd engines/{engine} && make dll")
|
||||
|
||||
lib = load_dll(engine)
|
||||
|
||||
# ── 构造原子数据 ──────────────────────────────────────────
|
||||
if ATOM_POSITIONS is None:
|
||||
raise RuntimeError("run_engine_dll: 请先调用 load_parameters() 加载配置")
|
||||
|
||||
pos = np.asarray(ATOM_POSITIONS, dtype=np.float64) # (n, 3)
|
||||
vel = np.asarray(ATOM_VELOCITIES, dtype=np.float64)
|
||||
mass = np.asarray(ATOM_MASSES, dtype=np.float64)
|
||||
fixed= np.asarray(ATOM_FIXED, dtype=np.int32) # (n, 3)
|
||||
|
||||
# ── 键数据 ────────────────────────────────────────────────
|
||||
n_bonds = len(BOND_PAIRS) if BOND_PAIRS is not None else 0
|
||||
bp = np.asarray(BOND_PAIRS, dtype=np.int32) if n_bonds else np.zeros((0,2), dtype=np.int32)
|
||||
bk = np.asarray(BOND_STIFFNESS, dtype=np.float64) if n_bonds else np.zeros(0)
|
||||
br0 = np.asarray(BOND_REST_LENGTHS,dtype=np.float64) if n_bonds else np.zeros(0)
|
||||
|
||||
# ── 驱动数据 ──────────────────────────────────────────────
|
||||
drv_list = []
|
||||
if int(config.get("driving_force", 0)) and DRIVER_DATA:
|
||||
atom_id_to_local = {int(aid): i for i, aid in enumerate(ATOM_IDS)}
|
||||
for d in DRIVER_DATA:
|
||||
aid = int(d.get("atom_id", -1))
|
||||
if aid not in atom_id_to_local:
|
||||
continue
|
||||
local_idx = atom_id_to_local[aid]
|
||||
# d["amp"], d["freq"], d["phi"] are numpy arrays; d["phi"] is already in radians
|
||||
amp = [float(v) for v in d["amp"]]
|
||||
freq = [float(v) for v in d["freq"]]
|
||||
phi = [float(v) for v in d["phi"]] # radians
|
||||
# eq_pos is set by run_from_config; fall back to initial position
|
||||
eq_pos = d.get("eq_pos")
|
||||
eq = ([float(v) for v in eq_pos] if eq_pos is not None
|
||||
else [float(pos[local_idx, 0]), float(pos[local_idx, 1]), float(pos[local_idx, 2])])
|
||||
pc = d.get("period_cycles") # None → unlimited, float → finite
|
||||
nc = float(pc) if pc is not None else 0.0
|
||||
hp = 1 if nc > 0 else 0
|
||||
drv_list.append({"local_idx": local_idx, "amp": amp, "freq": freq,
|
||||
"phi": phi, "eq_pos": eq, "n_cycles": nc, "has_period": hp})
|
||||
|
||||
# ── 进度回调 ──────────────────────────────────────────────
|
||||
total_steps = int(config["NT"]) - int(config.get("warmup_steps", 0))
|
||||
try:
|
||||
from tqdm import tqdm as _tqdm
|
||||
_pbar = _tqdm(total=total_steps, desc=f"[compute] DLL {engine}",
|
||||
unit="步", bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]')
|
||||
def _cb(step, total):
|
||||
_pbar.n = step
|
||||
_pbar.refresh()
|
||||
except ImportError:
|
||||
_pbar = None
|
||||
_cb = None
|
||||
|
||||
_t0 = time.time()
|
||||
try:
|
||||
result = run_dynamics_dll(
|
||||
lib, config,
|
||||
pos, vel, mass, fixed,
|
||||
bp, bk, br0,
|
||||
drv_list, np.asarray(ATOM_IDS),
|
||||
progress_cb=_cb,
|
||||
)
|
||||
finally:
|
||||
if _pbar is not None:
|
||||
_pbar.n = total_steps
|
||||
_pbar.close()
|
||||
elapsed = time.time() - _t0
|
||||
|
||||
n_frames, n_atoms = result["x"].shape
|
||||
print(f"[compute] DLL 完成: {n_frames} 帧 {n_atoms} 原子 {elapsed:.3f} s")
|
||||
|
||||
# ── 构建 header 并保存 display.npz ────────────────────────
|
||||
# 与 run_simulation 写入的 header 保持字段完全一致,
|
||||
# 确保 draw.py / plot_wave.py 读到所有必要参数。
|
||||
G_vec = parse_gravity_vector(config.get("G", [0, 0, 0]))
|
||||
B_vec = parse_damping_vector(config.get("B", [0, 0, 0]))
|
||||
record_steps_hdr = int(config["NT"]) - int(config.get("warmup_steps", 0))
|
||||
header = {
|
||||
"DT": str(config["DT"]),
|
||||
"NSTEP": str(config.get("NSTEP", 1)),
|
||||
"method": str(config.get("method", "leapfrog")),
|
||||
"NT": str(config["NT"]),
|
||||
"warmup_steps": str(config.get("warmup_steps", 0)),
|
||||
"dynamic_steps": str(record_steps_hdr),
|
||||
"T_total": str(int(config["NT"]) * float(config["DT"])),
|
||||
"box_a": str(config.get("box_a", 300.0)),
|
||||
"gravity_field": str(config.get("gravity_field", 0)),
|
||||
"gravity_interaction": str(config.get("gravity_interaction", 0)),
|
||||
"elastic_force": str(config.get("elastic_force", 1)),
|
||||
"damping_force": str(config.get("damping_force", 0)),
|
||||
"driving_force": str(config.get("driving_force", 0)),
|
||||
"gravity_strength": str(config.get("gravity_strength", 1.0)),
|
||||
"G": json.dumps(G_vec.tolist()),
|
||||
"B": json.dumps(B_vec.tolist()),
|
||||
"number_of_frames": str(n_frames),
|
||||
"number_of_particles": str(n_atoms),
|
||||
# draw.py 需要的渲染参数
|
||||
"use_marker": str(use_marker),
|
||||
"ball_radius": str(config.get("ball_radius", float(ATOM_RADII[0]) if ATOM_RADII is not None else 0.5)),
|
||||
"ball_color_r": str(config.get("ball_color_r", 0.9)),
|
||||
"ball_color_g": str(config.get("ball_color_g", 0.2)),
|
||||
"ball_color_b": str(config.get("ball_color_b", 0.2)),
|
||||
"box_color_r": str(config.get("box_color_r", 0.8)),
|
||||
"box_color_g": str(config.get("box_color_g", 0.8)),
|
||||
"box_color_b": str(config.get("box_color_b", 0.85)),
|
||||
"alpha": ",".join(str(a) for a in (alpha if isinstance(alpha, list) else [alpha])),
|
||||
# draw.py / plot_wave.py 需要的原子、键数据
|
||||
"atom_radii": ",".join(str(r) for r in ATOM_RADII),
|
||||
"atom_masses": json.dumps([float(v) for v in ATOM_MASSES]),
|
||||
"atom_positions": json.dumps(ATOM_POSITIONS.tolist()),
|
||||
"bond_pairs": json.dumps(BOND_PAIRS.tolist() if BOND_PAIRS is not None else []),
|
||||
"bond_stiffness": json.dumps(BOND_STIFFNESS.tolist() if BOND_STIFFNESS is not None else []),
|
||||
"bond_rest_lengths": json.dumps(BOND_REST_LENGTHS.tolist() if BOND_REST_LENGTHS is not None else []),
|
||||
# 边界(draw.py 用于场景缩放)
|
||||
"X_MIN": str(-float(config.get("box_a", 300.0))),
|
||||
"X_MAX": str( float(config.get("box_a", 300.0))),
|
||||
"Y_MIN": str(-float(config.get("box_a", 300.0))),
|
||||
"Y_MAX": str( float(config.get("box_a", 300.0))),
|
||||
"Z_MIN": str(-float(config.get("box_a", 300.0))),
|
||||
"Z_MAX": str( float(config.get("box_a", 300.0))),
|
||||
# 相机参数
|
||||
"camera_distance": str(camera_distance),
|
||||
"camera_elevation": str(camera_elevation),
|
||||
"camera_azimuth": str(camera_azimuth),
|
||||
"camera_center_x": str(camera_center_x),
|
||||
"camera_center_y": str(camera_center_y),
|
||||
"camera_center_z": str(camera_center_z),
|
||||
"camera_keyframes": str(camera_keyframes_raw),
|
||||
}
|
||||
if display_amp_str:
|
||||
header["display_amp"] = display_amp_str
|
||||
if camera_pos_x is not None:
|
||||
header["camera_pos_x"] = str(camera_pos_x)
|
||||
header["camera_pos_y"] = str(camera_pos_y)
|
||||
header["camera_pos_z"] = str(camera_pos_z)
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
npz_path = os.path.join(output_dir, "display.npz")
|
||||
save_display_npz(
|
||||
npz_path,
|
||||
result["x"], result["y"], result["z"],
|
||||
result["vx"], result["vy"], result["vz"],
|
||||
np.asarray(ATOM_IDS),
|
||||
header_fields=header,
|
||||
)
|
||||
print(f"[compute] display.npz 已生成: {npz_path}")
|
||||
|
||||
|
||||
def run_engine(engine, input_dir, output_dir, config):
|
||||
"""调用外部计算引擎(C/C++/Fortran),生成 trajectory.txt。
|
||||
|
||||
@@ -961,32 +1134,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",
|
||||
"c": "engines/c/build/dynamics_c",
|
||||
"cpp": "engines/cpp/build/dynamics_cpp",
|
||||
"c++": "engines/cpp/build/dynamics_cpp",
|
||||
"fortran": "engines/fortran/build/dynamics_f90",
|
||||
"f90": "engines/fortran/build/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]))
|
||||
@@ -1089,8 +1273,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 = []
|
||||
|
||||
Reference in New Issue
Block a user