feat: 增加驱动力系统、Marker渲染模式、动画防闪退、案例文档
- 新增 driving_force 驱动力系统(driver.txt 定义,支持周期控制) - 新增 use_marker 渲染开关(GPU实例化点精灵,提升大量原子性能) - 修复动画闪退:独立控制台、错误日志、启动存活检测 - 重绘 draw.py 架构:双渲染模式 + 预分配键线缓冲区 - 修复 raw trajectory 采样时间变量遮蔽 bug - 重构 case05: 60原子一维链 + 驱动力 + 完整案例文档 - 修复所有案例 Readme.md 编码(GBK → UTF-8) - 所有 input.txt 新增 driver_file / driving_force / use_marker 参数
This commit is contained in:
+152
@@ -69,8 +69,12 @@ GRAVITY_FIELD = 1 # 均匀重力场
|
|||||||
GRAVITY_INTERACTION = 0 # 原子间万有引力
|
GRAVITY_INTERACTION = 0 # 原子间万有引力
|
||||||
ELASTIC_FORCE = 1 # 弹簧键力
|
ELASTIC_FORCE = 1 # 弹簧键力
|
||||||
DAMPING_FORCE = 0 # 阻尼
|
DAMPING_FORCE = 0 # 阻尼
|
||||||
|
DRIVING_FORCE = 0 # 驱动力
|
||||||
GRAVITY_STRENGTH = 1.0
|
GRAVITY_STRENGTH = 1.0
|
||||||
|
|
||||||
|
# 驱动力数据
|
||||||
|
DRIVER_DATA = None # 加载自 driver.txt
|
||||||
|
|
||||||
# 派生边界(根据 box_a 计算)
|
# 派生边界(根据 box_a 计算)
|
||||||
X_MIN = None
|
X_MIN = None
|
||||||
X_MAX = None
|
X_MAX = None
|
||||||
@@ -328,6 +332,135 @@ def load_bond_connections(connection_path, atom_ids, atom_positions, bond_map):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 驱动力加载 & 应用
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
def load_driver_file(driver_path, atom_ids):
|
||||||
|
"""从 driver.txt 加载驱动力定义。
|
||||||
|
|
||||||
|
格式:
|
||||||
|
n amp_x amp_y amp_z freq_x freq_y freq_z phi_x phi_y phi_z period
|
||||||
|
数值 0 0 0 0 0 10 0 0 90 all
|
||||||
|
|
||||||
|
其中:
|
||||||
|
position = A * cos(2π f t + φ), φ 为角度制(自动转弧度)
|
||||||
|
period = all | 数值(周期数,结束后原子静止)
|
||||||
|
"""
|
||||||
|
if not os.path.exists(driver_path):
|
||||||
|
print(f"[compute] 警告: 驱动力文件不存在: {driver_path}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
atom_index = {int(aid): idx for idx, aid in enumerate(atom_ids)}
|
||||||
|
drivers = []
|
||||||
|
ncols = 11
|
||||||
|
|
||||||
|
with open(driver_path, "r", encoding="utf-8") as f:
|
||||||
|
header = f.readline().strip().split()
|
||||||
|
expected = ["n", "amp_x", "amp_y", "amp_z",
|
||||||
|
"freq_x", "freq_y", "freq_z",
|
||||||
|
"phi_x", "phi_y", "phi_z", "period"]
|
||||||
|
if header != expected:
|
||||||
|
raise ValueError(
|
||||||
|
f"driver.txt 表头应为: {' '.join(expected)},实际为: {' '.join(header)}")
|
||||||
|
|
||||||
|
for line_no, line in enumerate(f, start=2):
|
||||||
|
stripped = line.strip()
|
||||||
|
if not stripped or stripped.startswith("#"):
|
||||||
|
continue
|
||||||
|
parts = stripped.split()
|
||||||
|
if len(parts) != ncols:
|
||||||
|
raise ValueError(
|
||||||
|
f"{driver_path}:{line_no} 应有 {ncols} 列,实际为 {len(parts)} 列")
|
||||||
|
|
||||||
|
n = int(parts[0])
|
||||||
|
if n not in atom_index:
|
||||||
|
raise ValueError(f"{driver_path}:{line_no} 原子序号 {n} 不在 coord.txt 中")
|
||||||
|
|
||||||
|
amp = np.array([float(parts[1]), float(parts[2]), float(parts[3])],
|
||||||
|
dtype=np.float64)
|
||||||
|
freq = np.array([float(parts[4]), float(parts[5]), float(parts[6])],
|
||||||
|
dtype=np.float64)
|
||||||
|
phi_deg = np.array([float(parts[7]), float(parts[8]), float(parts[9])],
|
||||||
|
dtype=np.float64)
|
||||||
|
phi_rad = phi_deg * np.pi / 180.0
|
||||||
|
|
||||||
|
period_str = parts[10]
|
||||||
|
|
||||||
|
drivers.append({
|
||||||
|
"atom_index": atom_index[n],
|
||||||
|
"atom_id": n,
|
||||||
|
"amp": amp,
|
||||||
|
"freq": freq,
|
||||||
|
"phi": phi_rad,
|
||||||
|
"period_str": period_str,
|
||||||
|
"period_cycles": None if period_str == "all" else float(period_str),
|
||||||
|
# 在模拟中动态记录:冻结步数索引、冻结位置
|
||||||
|
"freeze_step": None,
|
||||||
|
"freeze_pos": None,
|
||||||
|
})
|
||||||
|
|
||||||
|
if not drivers:
|
||||||
|
print(f"[compute] 警告: driver.txt 中没有有效数据")
|
||||||
|
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']}")
|
||||||
|
return drivers
|
||||||
|
|
||||||
|
|
||||||
|
def apply_driving_force(x, y, z, vx, vy, vz, t, step, drivers, dt):
|
||||||
|
"""对受驱原子按驱动力函数覆盖位置/速度。
|
||||||
|
|
||||||
|
驱动力公式:pos = A * cos(2π f t + φ)
|
||||||
|
vel = -A * 2π f * sin(2π f t + φ)
|
||||||
|
"""
|
||||||
|
if drivers is None:
|
||||||
|
return x, y, z, vx, vy, vz
|
||||||
|
|
||||||
|
for d in drivers:
|
||||||
|
idx = d["atom_index"]
|
||||||
|
|
||||||
|
# 确定驱动力持续到哪一步
|
||||||
|
if d["period_cycles"] is not None:
|
||||||
|
max_freq = np.max(np.abs(d["freq"]))
|
||||||
|
if max_freq > 1e-12:
|
||||||
|
period_duration = d["period_cycles"] / max_freq
|
||||||
|
period_steps = int(period_duration / dt)
|
||||||
|
else:
|
||||||
|
period_steps = 0
|
||||||
|
|
||||||
|
if step > period_steps:
|
||||||
|
# 驱动力已结束:原子静止(保持最后位置,速度归零)
|
||||||
|
if d["freeze_pos"] is not None:
|
||||||
|
x[idx], y[idx], z[idx] = d["freeze_pos"]
|
||||||
|
vx[idx] = vy[idx] = vz[idx] = 0.0
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
period_steps = None # 全程驱动
|
||||||
|
|
||||||
|
# 当前驱动力下的位置 / 速度
|
||||||
|
t_vec = np.array([t, t, t], dtype=np.float64)
|
||||||
|
pos_drive = d["amp"] * np.cos(2.0 * np.pi * d["freq"] * t_vec + d["phi"])
|
||||||
|
vel_drive = -d["amp"] * 2.0 * np.pi * d["freq"] * np.sin(2.0 * np.pi * d["freq"] * t_vec + d["phi"])
|
||||||
|
|
||||||
|
x[idx] = pos_drive[0]
|
||||||
|
y[idx] = pos_drive[1]
|
||||||
|
z[idx] = pos_drive[2]
|
||||||
|
vx[idx] = vel_drive[0]
|
||||||
|
vy[idx] = vel_drive[1]
|
||||||
|
vz[idx] = vel_drive[2]
|
||||||
|
|
||||||
|
# 若周期有限,记录冻结位置(驱动力最后一帧的位置)
|
||||||
|
if period_steps is not None and step == period_steps:
|
||||||
|
d["freeze_pos"] = (float(pos_drive[0]), float(pos_drive[1]), float(pos_drive[2]))
|
||||||
|
|
||||||
|
return x, y, z, vx, vy, vz
|
||||||
def parse_gravity_vector(value):
|
def parse_gravity_vector(value):
|
||||||
"""Parse gravity into a 3D acceleration vector.
|
"""Parse gravity into a 3D acceleration vector.
|
||||||
|
|
||||||
@@ -379,6 +512,7 @@ def run_from_config(config, out_dir=None):
|
|||||||
global box_color_r, box_color_g, box_color_b
|
global box_color_r, box_color_g, box_color_b
|
||||||
global warmup_steps, sample_start, sample_end
|
global warmup_steps, sample_start, sample_end
|
||||||
global GRAVITY_FIELD, GRAVITY_INTERACTION, ELASTIC_FORCE, DAMPING_FORCE, GRAVITY_STRENGTH
|
global GRAVITY_FIELD, GRAVITY_INTERACTION, ELASTIC_FORCE, DAMPING_FORCE, GRAVITY_STRENGTH
|
||||||
|
global DRIVING_FORCE, DRIVER_DATA
|
||||||
|
|
||||||
box_a = float(config.get("box_a", 10.0))
|
box_a = float(config.get("box_a", 10.0))
|
||||||
raw_alpha = config.get("alpha", 0.2)
|
raw_alpha = config.get("alpha", 0.2)
|
||||||
@@ -439,11 +573,22 @@ def run_from_config(config, out_dir=None):
|
|||||||
|
|
||||||
# 力开关
|
# 力开关
|
||||||
global GRAVITY_FIELD, GRAVITY_INTERACTION, ELASTIC_FORCE, DAMPING_FORCE, GRAVITY_STRENGTH
|
global GRAVITY_FIELD, GRAVITY_INTERACTION, ELASTIC_FORCE, DAMPING_FORCE, GRAVITY_STRENGTH
|
||||||
|
global DRIVING_FORCE, DRIVER_DATA
|
||||||
GRAVITY_FIELD = int(config.get("gravity_field", 1))
|
GRAVITY_FIELD = int(config.get("gravity_field", 1))
|
||||||
GRAVITY_INTERACTION = int(config.get("gravity_interaction", 0))
|
GRAVITY_INTERACTION = int(config.get("gravity_interaction", 0))
|
||||||
ELASTIC_FORCE = int(config.get("elastic_force", 1))
|
ELASTIC_FORCE = int(config.get("elastic_force", 1))
|
||||||
DAMPING_FORCE = int(config.get("damping_force", 0))
|
DAMPING_FORCE = int(config.get("damping_force", 0))
|
||||||
GRAVITY_STRENGTH = float(config.get("gravity_strength", 1.0))
|
GRAVITY_STRENGTH = float(config.get("gravity_strength", 1.0))
|
||||||
|
DRIVING_FORCE = int(config.get("driving_force", 0))
|
||||||
|
|
||||||
|
# 加载驱动力定义
|
||||||
|
DRIVER_DATA = None
|
||||||
|
if DRIVING_FORCE:
|
||||||
|
driver_rel = str(config.get("driver_file", os.path.join("input", "driver.txt")))
|
||||||
|
driver_path = driver_rel
|
||||||
|
if out_dir is not None and not os.path.isabs(driver_rel):
|
||||||
|
driver_path = os.path.join(out_dir, driver_rel)
|
||||||
|
DRIVER_DATA = load_driver_file(driver_path, ATOM_IDS)
|
||||||
|
|
||||||
print(f"[compute] 使用算法: {METHOD}")
|
print(f"[compute] 使用算法: {METHOD}")
|
||||||
print(f"[compute] 已加载成键信息: {len(BOND_PAIRS)} 条键")
|
print(f"[compute] 已加载成键信息: {len(BOND_PAIRS)} 条键")
|
||||||
@@ -1062,11 +1207,14 @@ def run_simulation():
|
|||||||
vy = ATOM_VELOCITIES[:, 1].copy()
|
vy = ATOM_VELOCITIES[:, 1].copy()
|
||||||
vz = ATOM_VELOCITIES[:, 2].copy()
|
vz = ATOM_VELOCITIES[:, 2].copy()
|
||||||
x, y, z, vx, vy, vz = apply_fixed_constraints(x, y, z, vx, vy, vz)
|
x, y, z, vx, vy, vz = apply_fixed_constraints(x, y, z, vx, vy, vz)
|
||||||
|
# 初始时刻驱动力(t=0 时原子 1 的位置由驱动力决定而非 coord.txt)
|
||||||
|
x, y, z, vx, vy, vz = apply_driving_force(x, y, z, vx, vy, vz, 0.0, 0, DRIVER_DATA, DT)
|
||||||
|
|
||||||
if warmup_steps is not None and warmup_steps > 0:
|
if warmup_steps is not None and warmup_steps > 0:
|
||||||
print(f"[compute] 预热阶段: 前 {warmup_steps} 步不记录")
|
print(f"[compute] 预热阶段: 前 {warmup_steps} 步不记录")
|
||||||
for step in trange(warmup_steps, desc="[compute] 预热"):
|
for step in trange(warmup_steps, desc="[compute] 预热"):
|
||||||
t = (step + 1) * DT
|
t = (step + 1) * DT
|
||||||
|
x, y, z, vx, vy, vz = apply_driving_force(x, y, z, vx, vy, vz, t, step, DRIVER_DATA, DT)
|
||||||
x, y, z, vx, vy, vz = apply_motion_update(x, y, z, vx, vy, vz, DT, ATOM_MASSES, G, B)
|
x, y, z, vx, vy, vz = apply_motion_update(x, y, z, vx, vy, vz, DT, ATOM_MASSES, G, B)
|
||||||
x, y, z = wrap_position(x, y, z)
|
x, y, z = wrap_position(x, y, z)
|
||||||
x, y, z, vx, vy, vz = apply_fixed_constraints(x, y, z, vx, vy, vz)
|
x, y, z, vx, vy, vz = apply_fixed_constraints(x, y, z, vx, vy, vz)
|
||||||
@@ -1086,6 +1234,10 @@ def run_simulation():
|
|||||||
traj_vz = np.zeros((record_steps, n_atoms), dtype=np.float64)
|
traj_vz = np.zeros((record_steps, n_atoms), dtype=np.float64)
|
||||||
|
|
||||||
for step in trange(record_steps, desc="[compute] 计算中"):
|
for step in trange(record_steps, desc="[compute] 计算中"):
|
||||||
|
t = (step + (warmup_steps or 0)) * DT
|
||||||
|
# 先施加驱动力(受驱原子的位置覆盖初始/固定约束,为弹簧力提供正确参考)
|
||||||
|
x, y, z, vx, vy, vz = apply_driving_force(x, y, z, vx, vy, vz, t, step, DRIVER_DATA, DT)
|
||||||
|
|
||||||
traj_x[step] = x
|
traj_x[step] = x
|
||||||
traj_y[step] = y
|
traj_y[step] = y
|
||||||
traj_z[step] = z
|
traj_z[step] = z
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ PLOT_ATOM_ROW = int(disp_data.get("plot_atom_row", 0))
|
|||||||
PLOT_ATOM_ID = int(disp_data.get("plot_atom_id", ATOM_IDS[0]))
|
PLOT_ATOM_ID = int(disp_data.get("plot_atom_id", ATOM_IDS[0]))
|
||||||
BOND_PAIRS = disp_data.get("bond_pairs", [])
|
BOND_PAIRS = disp_data.get("bond_pairs", [])
|
||||||
|
|
||||||
|
# 渲染方式:0=Sphere(网格球体), 1=Marker(GPU点精灵)
|
||||||
|
USE_MARKER = int(disp_data.get("use_marker", 0))
|
||||||
|
|
||||||
if N_FRAMES <= 0:
|
if N_FRAMES <= 0:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"output/display.txt 中没有可播放的帧,请检查 sample_start/sample_end/NSTEP 配置。")
|
"output/display.txt 中没有可播放的帧,请检查 sample_start/sample_end/NSTEP 配置。")
|
||||||
@@ -171,7 +174,10 @@ axes_group.append(scene.visuals.Text(text="y", color=(0.2, 1.0, 0.2, 1.0), font_
|
|||||||
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=18,
|
||||||
pos=(0, 0, axis_length + 0.2), anchor_x="left", anchor_y="bottom", parent=view.scene))
|
pos=(0, 0, axis_length + 0.2), anchor_x="left", anchor_y="bottom", parent=view.scene))
|
||||||
|
|
||||||
# 所有小球(每个原子一个球,不同颜色)
|
# ── 原子渲染 ──────────────────────────────────
|
||||||
|
# 两种渲染模式:
|
||||||
|
# Sphere = 独立网格球体(精细,适合少量原子)
|
||||||
|
# Marker = GPU 实例化点精灵(高效,适合大量原子)
|
||||||
TAB10_RGB = np.array([
|
TAB10_RGB = np.array([
|
||||||
[0.1216, 0.4667, 0.7059], # 蓝
|
[0.1216, 0.4667, 0.7059], # 蓝
|
||||||
[0.8902, 0.4667, 0.1137], # 橙
|
[0.8902, 0.4667, 0.1137], # 橙
|
||||||
@@ -184,23 +190,41 @@ TAB10_RGB = np.array([
|
|||||||
[0.7373, 0.7412, 0.1333], # 黄绿
|
[0.7373, 0.7412, 0.1333], # 黄绿
|
||||||
[0.0902, 0.7451, 0.8118], # 青
|
[0.0902, 0.7451, 0.8118], # 青
|
||||||
])
|
])
|
||||||
balls = []
|
# 每个原子的颜色(循环使用 tab10 色板)
|
||||||
|
atom_colors = np.zeros((N_ATOMS, 4), dtype=np.float32)
|
||||||
for i in range(N_ATOMS):
|
for i in range(N_ATOMS):
|
||||||
r, g, b = TAB10_RGB[i % len(TAB10_RGB)]
|
r, g, b = TAB10_RGB[i % len(TAB10_RGB)]
|
||||||
s = scene.visuals.Sphere(
|
atom_colors[i] = [r, g, b, 1.0]
|
||||||
radius=float(ATOM_RADII[i]), method="latitude",
|
|
||||||
color=(r, g, b, 1.0), edge_color=None, parent=view.scene)
|
if USE_MARKER:
|
||||||
s.mesh.shading = "smooth"
|
# ── Marker 模式:GPU 实例化,一次 draw call ──
|
||||||
balls.append(s)
|
marker_pos = np.zeros((N_ATOMS, 3), dtype=np.float32)
|
||||||
|
marker_sizes = np.array([float(ATOM_RADII[i]) * 40 for i in range(N_ATOMS)], dtype=np.float32)
|
||||||
|
balls = scene.visuals.Markers(parent=view.scene)
|
||||||
|
balls.set_data(
|
||||||
|
pos=marker_pos, face_color=atom_colors,
|
||||||
|
size=marker_sizes, symbol='disc', edge_width=0,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# ── Sphere 模式:每个原子一个独立网格球体 ──
|
||||||
|
balls = []
|
||||||
|
for i in range(N_ATOMS):
|
||||||
|
r, g, b, _ = atom_colors[i]
|
||||||
|
s = scene.visuals.Sphere(
|
||||||
|
radius=float(ATOM_RADII[i]), method="latitude",
|
||||||
|
color=(r, g, b, 1.0), edge_color=None, parent=view.scene)
|
||||||
|
s.mesh.shading = "smooth"
|
||||||
|
balls.append(s)
|
||||||
|
|
||||||
# 成键线(原子之间的连接)
|
# 成键线(原子之间的连接)
|
||||||
if len(BOND_PAIRS) > 0:
|
if len(BOND_PAIRS) > 0:
|
||||||
n_bonds = len(BOND_PAIRS)
|
n_bonds = len(BOND_PAIRS)
|
||||||
bond_pos = np.zeros((n_bonds * 2, 3), dtype=np.float32)
|
bond_pos_buffer = np.zeros((n_bonds * 2, 3), dtype=np.float32) # 预分配,后续原地修改
|
||||||
bond_lines = scene.visuals.Line(
|
bond_lines = scene.visuals.Line(
|
||||||
pos=bond_pos, color=(0.7, 0.7, 0.7, 0.8), width=3,
|
pos=bond_pos_buffer, color=(0.7, 0.7, 0.7, 0.8), width=3,
|
||||||
connect="segments", parent=view.scene)
|
connect="segments", parent=view.scene)
|
||||||
else:
|
else:
|
||||||
|
bond_pos_buffer = None
|
||||||
bond_lines = None
|
bond_lines = None
|
||||||
|
|
||||||
# 六个面形成立方体边界(每个面独立透明度,alpha<=0 时隐藏该面)
|
# 六个面形成立方体边界(每个面独立透明度,alpha<=0 时隐藏该面)
|
||||||
@@ -361,19 +385,10 @@ def handle_key_press(event):
|
|||||||
def reset_camera_view():
|
def reset_camera_view():
|
||||||
global frame_idx
|
global frame_idx
|
||||||
frame_idx = 0
|
frame_idx = 0
|
||||||
# 立即复位所有小球到第 0 帧
|
# 立即复位所有原子到第 0 帧
|
||||||
for i in range(N_ATOMS):
|
_update_atom_positions(frame_idx)
|
||||||
balls[i].transform = STTransform(translate=(
|
|
||||||
float(DISP_ALL_X[frame_idx, i]),
|
|
||||||
float(DISP_ALL_Y[frame_idx, i]),
|
|
||||||
float(DISP_ALL_Z[frame_idx, i]),
|
|
||||||
))
|
|
||||||
if bond_lines is not None and len(BOND_PAIRS) > 0:
|
if bond_lines is not None and len(BOND_PAIRS) > 0:
|
||||||
pos = np.zeros((len(BOND_PAIRS) * 2, 3), dtype=np.float32)
|
_update_bond_positions(frame_idx)
|
||||||
for b_idx, (i, j) in enumerate(BOND_PAIRS):
|
|
||||||
pos[b_idx * 2] = [DISP_ALL_X[frame_idx, i], DISP_ALL_Y[frame_idx, i], DISP_ALL_Z[frame_idx, i]]
|
|
||||||
pos[b_idx * 2 + 1] = [DISP_ALL_X[frame_idx, j], DISP_ALL_Y[frame_idx, j], DISP_ALL_Z[frame_idx, j]]
|
|
||||||
bond_lines.set_data(pos=pos)
|
|
||||||
# 复位相机
|
# 复位相机
|
||||||
view.camera.distance = initial_camera["distance"]
|
view.camera.distance = initial_camera["distance"]
|
||||||
view.camera.elevation = initial_camera["elevation"]
|
view.camera.elevation = initial_camera["elevation"]
|
||||||
@@ -413,31 +428,51 @@ def handle_mouse_press(event):
|
|||||||
axis.visible = axes_visible
|
axis.visible = axes_visible
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 位置/键线更新辅助函数(两种渲染模式共用)
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
def _update_atom_positions(f_idx):
|
||||||
|
"""更新所有原子到第 f_idx 帧的位置。"""
|
||||||
|
if USE_MARKER:
|
||||||
|
for i in range(N_ATOMS):
|
||||||
|
marker_pos[i] = [DISP_ALL_X[f_idx, i], DISP_ALL_Y[f_idx, i], DISP_ALL_Z[f_idx, i]]
|
||||||
|
balls.set_data(pos=marker_pos)
|
||||||
|
else:
|
||||||
|
for i in range(N_ATOMS):
|
||||||
|
balls[i].transform = STTransform(translate=(
|
||||||
|
float(DISP_ALL_X[f_idx, i]),
|
||||||
|
float(DISP_ALL_Y[f_idx, i]),
|
||||||
|
float(DISP_ALL_Z[f_idx, i]),
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
def _update_bond_positions(f_idx):
|
||||||
|
"""原地更新键线顶点位置,避免重新分配内存。"""
|
||||||
|
for b_idx, (i, j) in enumerate(BOND_PAIRS):
|
||||||
|
b2 = b_idx * 2
|
||||||
|
bond_pos_buffer[b2] = [DISP_ALL_X[f_idx, i], DISP_ALL_Y[f_idx, i], DISP_ALL_Z[f_idx, i]]
|
||||||
|
bond_pos_buffer[b2 + 1] = [DISP_ALL_X[f_idx, j], DISP_ALL_Y[f_idx, j], DISP_ALL_Z[f_idx, j]]
|
||||||
|
bond_lines.set_data(pos=bond_pos_buffer)
|
||||||
|
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# 动画初始化
|
# 动画初始化
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
frame_idx = 0
|
frame_idx = 0
|
||||||
|
|
||||||
# 初始帧:摆放所有小球并刷新 UI
|
# 初始帧:摆放所有原子并刷新 UI
|
||||||
for i in range(N_ATOMS):
|
_update_atom_positions(frame_idx)
|
||||||
balls[i].transform = STTransform(translate=(
|
|
||||||
float(DISP_ALL_X[frame_idx, i]),
|
|
||||||
float(DISP_ALL_Y[frame_idx, i]),
|
|
||||||
float(DISP_ALL_Z[frame_idx, i]),
|
|
||||||
))
|
|
||||||
# 初始帧:更新成键线
|
|
||||||
if bond_lines is not None and len(BOND_PAIRS) > 0:
|
if bond_lines is not None and len(BOND_PAIRS) > 0:
|
||||||
pos = np.zeros((len(BOND_PAIRS) * 2, 3), dtype=np.float32)
|
_update_bond_positions(frame_idx)
|
||||||
for b_idx, (i, j) in enumerate(BOND_PAIRS):
|
|
||||||
pos[b_idx * 2] = [DISP_ALL_X[frame_idx, i], DISP_ALL_Y[frame_idx, i], DISP_ALL_Z[frame_idx, i]]
|
|
||||||
pos[b_idx * 2 + 1] = [DISP_ALL_X[frame_idx, j], DISP_ALL_Y[frame_idx, j], DISP_ALL_Z[frame_idx, j]]
|
|
||||||
bond_lines.set_data(pos=pos)
|
|
||||||
reposition_camera_info()
|
reposition_camera_info()
|
||||||
update_ball_info(frame_idx,
|
update_ball_info(frame_idx,
|
||||||
float(DISP_X[frame_idx]), float(DISP_Y[frame_idx]), float(DISP_Z[frame_idx]),
|
float(DISP_X[frame_idx]), float(DISP_Y[frame_idx]), float(DISP_Z[frame_idx]),
|
||||||
float(DISP_VX[frame_idx]), float(DISP_VY[frame_idx]), float(DISP_VZ[frame_idx]))
|
float(DISP_VX[frame_idx]), float(DISP_VY[frame_idx]), float(DISP_VZ[frame_idx]))
|
||||||
|
|
||||||
|
mode_str = "Marker (GPU 实例化)" if USE_MARKER else "Sphere (独立网格)"
|
||||||
print(f"[draw] 加载 output/display.txt: {N_FRAMES} 帧, {N_ATOMS} 个原子, NT={NT}, DT={DT}, NSTEP={NSTEP}")
|
print(f"[draw] 加载 output/display.txt: {N_FRAMES} 帧, {N_ATOMS} 个原子, NT={NT}, DT={DT}, NSTEP={NSTEP}")
|
||||||
|
print(f"[draw] 渲染方式: {mode_str}")
|
||||||
print(f"[draw] 绘图参数: ball_radius={ball_radius}, box_color=({box_color_r:.2f},{box_color_g:.2f},{box_color_b:.2f}), alpha={alpha_list}")
|
print(f"[draw] 绘图参数: ball_radius={ball_radius}, box_color=({box_color_r:.2f},{box_color_g:.2f},{box_color_b:.2f}), alpha={alpha_list}")
|
||||||
|
|
||||||
|
|
||||||
@@ -448,20 +483,12 @@ def update(event):
|
|||||||
global frame_idx
|
global frame_idx
|
||||||
frame_idx = (frame_idx + 1) % N_FRAMES # 循环播放
|
frame_idx = (frame_idx + 1) % N_FRAMES # 循环播放
|
||||||
|
|
||||||
# 更新所有小球位置
|
# 更新所有原子位置(两种模式共用逻辑)
|
||||||
for i in range(N_ATOMS):
|
_update_atom_positions(frame_idx)
|
||||||
x = float(DISP_ALL_X[frame_idx, i])
|
|
||||||
y = float(DISP_ALL_Y[frame_idx, i])
|
|
||||||
z = float(DISP_ALL_Z[frame_idx, i])
|
|
||||||
balls[i].transform = STTransform(translate=(x, y, z))
|
|
||||||
|
|
||||||
# 更新成键线
|
# 更新成键线(原地修改,无内存分配)
|
||||||
if bond_lines is not None and len(BOND_PAIRS) > 0:
|
if bond_lines is not None and len(BOND_PAIRS) > 0:
|
||||||
pos = np.zeros((len(BOND_PAIRS) * 2, 3), dtype=np.float32)
|
_update_bond_positions(frame_idx)
|
||||||
for b_idx, (i, j) in enumerate(BOND_PAIRS):
|
|
||||||
pos[b_idx * 2] = [DISP_ALL_X[frame_idx, i], DISP_ALL_Y[frame_idx, i], DISP_ALL_Z[frame_idx, i]]
|
|
||||||
pos[b_idx * 2 + 1] = [DISP_ALL_X[frame_idx, j], DISP_ALL_Y[frame_idx, j], DISP_ALL_Z[frame_idx, j]]
|
|
||||||
bond_lines.set_data(pos=pos)
|
|
||||||
|
|
||||||
# 信息面板显示 plot_atom 的数据
|
# 信息面板显示 plot_atom 的数据
|
||||||
x = float(DISP_X[frame_idx])
|
x = float(DISP_X[frame_idx])
|
||||||
|
|||||||
+43
-20
@@ -12,6 +12,7 @@ dynamics.py
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import time
|
||||||
import argparse
|
import argparse
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -316,6 +317,8 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
|||||||
"elastic_force": int(data.get("elastic_force", 1)),
|
"elastic_force": int(data.get("elastic_force", 1)),
|
||||||
"damping_force": int(data.get("damping_force", 0)),
|
"damping_force": int(data.get("damping_force", 0)),
|
||||||
"gravity_strength": float(data.get("gravity_strength", 1.0)),
|
"gravity_strength": float(data.get("gravity_strength", 1.0)),
|
||||||
|
"driving_force": int(config.get("driving_force", 0)),
|
||||||
|
"use_marker": int(config.get("use_marker", 0)),
|
||||||
}
|
}
|
||||||
save_display_txt(disp_data, str(runtime_base))
|
save_display_txt(disp_data, str(runtime_base))
|
||||||
print(f"[run] 抽帧完成: {sample_end - sample_start} 步 -> {n_frames} 帧")
|
print(f"[run] 抽帧完成: {sample_end - sample_start} 步 -> {n_frames} 帧")
|
||||||
@@ -331,7 +334,7 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
|||||||
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'WenQuanYi Micro Hei', 'DejaVu Sans']
|
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'WenQuanYi Micro Hei', 'DejaVu Sans']
|
||||||
plt.rcParams['axes.unicode_minus'] = False
|
plt.rcParams['axes.unicode_minus'] = False
|
||||||
|
|
||||||
time = np.arange(NT) * DT
|
time_arr = np.arange(NT) * DT
|
||||||
n_atoms = all_x.shape[1]
|
n_atoms = all_x.shape[1]
|
||||||
atom_ids_list = data.get("atom_ids", np.arange(n_atoms) + 1)
|
atom_ids_list = data.get("atom_ids", np.arange(n_atoms) + 1)
|
||||||
|
|
||||||
@@ -353,7 +356,7 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
|||||||
for ax, data_arr, title in plot_configs:
|
for ax, data_arr, title in plot_configs:
|
||||||
for i in range(n_atoms):
|
for i in range(n_atoms):
|
||||||
atom_id = int(atom_ids_list[i])
|
atom_id = int(atom_ids_list[i])
|
||||||
ax.plot(time, data_arr[:, i], color=colors[i], linewidth=1.5, label=f"原子 {atom_id}")
|
ax.plot(time_arr, data_arr[:, i], color=colors[i], linewidth=1.5, label=f"原子 {atom_id}")
|
||||||
ax.set_title(title)
|
ax.set_title(title)
|
||||||
ax.set_xlabel("时间 (s)")
|
ax.set_xlabel("时间 (s)")
|
||||||
ax.grid(True, alpha=0.3)
|
ax.grid(True, alpha=0.3)
|
||||||
@@ -423,7 +426,7 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
|||||||
ax_e = axes[2, 0]
|
ax_e = axes[2, 0]
|
||||||
for i in range(n_atoms):
|
for i in range(n_atoms):
|
||||||
aid = int(atom_ids_list[i])
|
aid = int(atom_ids_list[i])
|
||||||
ax_e.plot(time, e_total[:, i], color=colors[i], linewidth=1.5, label=f"原子 {aid}")
|
ax_e.plot(time_arr, e_total[:, i], color=colors[i], linewidth=1.5, label=f"原子 {aid}")
|
||||||
ax_e.set_title("各原子总能量")
|
ax_e.set_title("各原子总能量")
|
||||||
ax_e.set_xlabel("时间 (s)")
|
ax_e.set_xlabel("时间 (s)")
|
||||||
ax_e.set_ylabel("能量")
|
ax_e.set_ylabel("能量")
|
||||||
@@ -432,13 +435,13 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
|||||||
|
|
||||||
# ── 第 3 行右:系统总能量 ──
|
# ── 第 3 行右:系统总能量 ──
|
||||||
ax_sys = axes[2, 1]
|
ax_sys = axes[2, 1]
|
||||||
ax_sys.plot(time, ek_sys, 'b-', linewidth=1.5, label="系统动能")
|
ax_sys.plot(time_arr, ek_sys, 'b-', linewidth=1.5, label="系统动能")
|
||||||
ax_sys.plot(time, ug_sys, 'g-', linewidth=1.5, label="均匀重力势能")
|
ax_sys.plot(time_arr, ug_sys, 'g-', linewidth=1.5, label="均匀重力势能")
|
||||||
if elastic_force_enabled and bond_pairs is not None and len(bond_pairs) > 0:
|
if elastic_force_enabled and bond_pairs is not None and len(bond_pairs) > 0:
|
||||||
ax_sys.plot(time, us_sys, color='orange', linewidth=1.5, label="系统弹性势能")
|
ax_sys.plot(time_arr, us_sys, color='orange', linewidth=1.5, label="系统弹性势能")
|
||||||
if gravity_interaction_enabled:
|
if gravity_interaction_enabled:
|
||||||
ax_sys.plot(time, ug_grav_sys, color='purple', linewidth=1.5, label="万有引力势能")
|
ax_sys.plot(time_arr, ug_grav_sys, color='purple', linewidth=1.5, label="万有引力势能")
|
||||||
ax_sys.plot(time, e_sys, 'r--', linewidth=1.5, label="系统总能量")
|
ax_sys.plot(time_arr, e_sys, 'r--', linewidth=1.5, label="系统总能量")
|
||||||
ax_sys.set_title("系统总能量")
|
ax_sys.set_title("系统总能量")
|
||||||
ax_sys.set_xlabel("时间 (s)")
|
ax_sys.set_xlabel("时间 (s)")
|
||||||
ax_sys.set_ylabel("能量")
|
ax_sys.set_ylabel("能量")
|
||||||
@@ -461,19 +464,39 @@ def run_case(config_path, runtime_base, input_dir="input", output_dir="output",
|
|||||||
# 5. 自动播放动画(可选)
|
# 5. 自动播放动画(可选)
|
||||||
if config.get("step_animation", 0):
|
if config.get("step_animation", 0):
|
||||||
draw_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "draw.py")
|
draw_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "draw.py")
|
||||||
if os.path.exists(draw_script):
|
if not os.path.exists(draw_script):
|
||||||
try:
|
|
||||||
print("[run] 正在启动 VisPy 3D 动画窗口…")
|
|
||||||
subprocess.Popen(
|
|
||||||
[sys.executable, draw_script],
|
|
||||||
cwd=runtime_base,
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
stderr=subprocess.DEVNULL,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[run] 启动动画失败: {e}")
|
|
||||||
else:
|
|
||||||
print(f"[run] 未找到动画脚本: {draw_script}")
|
print(f"[run] 未找到动画脚本: {draw_script}")
|
||||||
|
else:
|
||||||
|
# 检查 display.txt 是否存在(step_sample=0 时可能没有)
|
||||||
|
disp_path = os.path.join(output_dir_abs, "display.txt")
|
||||||
|
if not os.path.exists(disp_path):
|
||||||
|
print(f"[run] 错误: 找不到 {disp_path}")
|
||||||
|
print(f"[run] 启动动画需要先运行抽帧(step_sample: 1),或手动保留 output/display.txt")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
print("[run] 正在启动 VisPy 3D 动画窗口…")
|
||||||
|
ansi_log = os.path.join(output_dir_abs, "animation.log")
|
||||||
|
if sys.platform == "win32":
|
||||||
|
# Windows 上给子进程独立控制台,避免父进程退出时连带关闭
|
||||||
|
creation_flags = subprocess.CREATE_NEW_CONSOLE
|
||||||
|
else:
|
||||||
|
creation_flags = 0
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[sys.executable, draw_script, output_dir_abs],
|
||||||
|
cwd=runtime_base,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=open(ansi_log, "w", encoding="utf-8"),
|
||||||
|
creationflags=creation_flags,
|
||||||
|
)
|
||||||
|
# 等待半秒检查子进程是否成功启动(未立即崩溃)
|
||||||
|
time.sleep(0.5)
|
||||||
|
if proc.poll() is not None:
|
||||||
|
print(f"[run] ⚠ 动画进程已退出,返回码={proc.returncode}")
|
||||||
|
print(f"[run] 请查看错误日志: {ansi_log}")
|
||||||
|
else:
|
||||||
|
print(f"[run] VisPy 动画窗口已启动(PID={proc.pid})")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[run] 启动动画失败: {e}")
|
||||||
else:
|
else:
|
||||||
print("[run] 运行 python draw.py 查看动画。")
|
print("[run] 运行 python draw.py 查看动画。")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
本案例描述两个粒子同一个键连接,受到重力作用下的动力学过程。
|
||||||
@@ -29,6 +29,7 @@ box_a: 20.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³
|
|||||||
coord_file: input/coord.txt
|
coord_file: input/coord.txt
|
||||||
connection_file: input/connection.txt
|
connection_file: input/connection.txt
|
||||||
bond_file: input/bond.txt
|
bond_file: input/bond.txt
|
||||||
|
driver_file: input/driver.txt # 驱动力定义文件(driving_force=1 时生效)
|
||||||
|
|
||||||
# 绘图/动画展示的原子序号(对应 coord_file 第一列 n)
|
# 绘图/动画展示的原子序号(对应 coord_file 第一列 n)
|
||||||
plot_atom: 1
|
plot_atom: 1
|
||||||
@@ -44,6 +45,7 @@ gravity_field: 1 # 均匀重力场 (G)
|
|||||||
gravity_interaction: 0 # 原子间万有引力
|
gravity_interaction: 0 # 原子间万有引力
|
||||||
elastic_force: 1 # 弹簧键力
|
elastic_force: 1 # 弹簧键力
|
||||||
damping_force: 0 # 阻尼 (B)
|
damping_force: 0 # 阻尼 (B)
|
||||||
|
driving_force: 0 # 驱动力(需 driver_file 定义)
|
||||||
#
|
#
|
||||||
gravity_strength: 1.0 # 万有引力强度(仅 gravity_interaction=1 时有效)
|
gravity_strength: 1.0 # 万有引力强度(仅 gravity_interaction=1 时有效)
|
||||||
|
|
||||||
@@ -77,6 +79,12 @@ sample_end: null # null 表示到末尾
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ── 渲染方式 ──────────────────────────────────
|
||||||
|
# 3D 动画中原子渲染方式:
|
||||||
|
# 0 = Sphere (网格球体,效果精细,原子数少时推荐)
|
||||||
|
# 1 = Marker (GPU 实例化点,原子数多时性能更佳)
|
||||||
|
use_marker: 0
|
||||||
|
|
||||||
# ── 显示参数 ──────────────────────────────────
|
# ── 显示参数 ──────────────────────────────────
|
||||||
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
||||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.5]
|
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.5]
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
本案例描述地球围绕太阳做椭圆运动
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
n mass radius x y z vx vy vz fix_x fix_y fix_z
|
||||||
1 1 0.28 0 0 0 0 0 0 1 1 1
|
1 1 0.28 0 0 0 0 0 0 1 1 1
|
||||||
2 1 0.28 0 0 4 0 4 0 0 0 0
|
2 1 0.28 4 0 0 0 0 4 0 0 0
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ box_a: 20.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³
|
|||||||
coord_file: input/coord.txt
|
coord_file: input/coord.txt
|
||||||
connection_file: input/connection.txt
|
connection_file: input/connection.txt
|
||||||
bond_file: input/bond.txt
|
bond_file: input/bond.txt
|
||||||
|
driver_file: input/driver.txt # 驱动力定义文件(driving_force=1 时生效)
|
||||||
|
|
||||||
# 绘图/动画展示的原子序号(对应 coord_file 第一列 n)
|
# 绘图/动画展示的原子序号(对应 coord_file 第一列 n)
|
||||||
plot_atom: 1
|
plot_atom: 1
|
||||||
@@ -44,6 +45,7 @@ gravity_field: 0 # 均匀重力场 (G)
|
|||||||
gravity_interaction: 1 # 原子间万有引力
|
gravity_interaction: 1 # 原子间万有引力
|
||||||
elastic_force: 0 # 弹簧键力
|
elastic_force: 0 # 弹簧键力
|
||||||
damping_force: 0 # 阻尼 (B)
|
damping_force: 0 # 阻尼 (B)
|
||||||
|
driving_force: 0 # 驱动力(需 driver_file 定义)
|
||||||
#
|
#
|
||||||
gravity_strength: 100.0 # 万有引力强度(仅 gravity_interaction=1 时有效)
|
gravity_strength: 100.0 # 万有引力强度(仅 gravity_interaction=1 时有效)
|
||||||
|
|
||||||
@@ -77,6 +79,12 @@ sample_end: null # null 表示到末尾
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ── 渲染方式 ──────────────────────────────────
|
||||||
|
# 3D 动画中原子渲染方式:
|
||||||
|
# 0 = Sphere (网格球体,效果精细,原子数少时推荐)
|
||||||
|
# 1 = Marker (GPU 实例化点,原子数多时性能更佳)
|
||||||
|
use_marker: 0
|
||||||
|
|
||||||
# ── 显示参数 ──────────────────────────────────
|
# ── 显示参数 ──────────────────────────────────
|
||||||
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
||||||
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.5]
|
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.5]
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
地球围绕太阳转,月球围绕地球转,失败案例
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
bond_name k rest_length
|
||||||
|
k1 100.0 2.0
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
n1 n2 bond_name
|
||||||
|
|
||||||
@@ -0,0 +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
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# 物理模拟参数配置
|
||||||
|
# 格式:YAML
|
||||||
|
# 用法:python run_dynamics.py
|
||||||
|
|
||||||
|
# ── 流程控制 ──────────────────────────────────
|
||||||
|
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||||
|
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||||
|
step_simulate: 1 # 运行物理模拟 → output/trajectory.txt
|
||||||
|
step_sample: 1 # 抽帧 → output/display.txt
|
||||||
|
step_plot: 0 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||||
|
step_animation: 1 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||||
|
force_calc: 0 # 强制重新计算:1=跳过缓存强算,0=自动使用已有输出
|
||||||
|
|
||||||
|
# ── 计算引擎 ──────────────────────────────────
|
||||||
|
# 可选: python, c, cpp, fortran, java
|
||||||
|
# python = Python 参考实现(compute.py)
|
||||||
|
# c = C 引擎 (engines/c/build/dynamics_c)
|
||||||
|
# cpp = C++ 引擎 (engines/cpp/build/dynamics_cpp)
|
||||||
|
# fortran = Fortran 引擎 (engines/fortran/build/dynamics_f90)
|
||||||
|
engine: python # 默认使用 Python 引擎
|
||||||
|
|
||||||
|
# ── 盒子 ──────────────────────────────────────
|
||||||
|
box_a: 20.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||||
|
|
||||||
|
# ── 初始构型 ──────────────────────────────────
|
||||||
|
# 坐标文件格式:
|
||||||
|
# 第一行:n mass radius x y z vx vy vz
|
||||||
|
# 后续行:原子序号 质量 半径 x y z vx vy vz
|
||||||
|
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.0, 0.0, -9.8] # 重力场分量 (m/s²)
|
||||||
|
# B: [0.5, 0.5, 0.5] # 阻尼分量
|
||||||
|
B: [0.0, 0.0, 0.0] # 阻尼分量
|
||||||
|
|
||||||
|
# ── 力开关(0=关闭, 1=开启)──────────────────
|
||||||
|
gravity_field: 0 # 均匀重力场 (G)
|
||||||
|
gravity_interaction: 1 # 原子间万有引力
|
||||||
|
elastic_force: 0 # 弹簧键力
|
||||||
|
damping_force: 0 # 阻尼 (B)
|
||||||
|
driving_force: 0 # 驱动力(需 driver_file 定义)
|
||||||
|
#
|
||||||
|
gravity_strength: 100.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: 2
|
||||||
|
|
||||||
|
# ── 时间步长 ──────────────────────────────────
|
||||||
|
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: 0
|
||||||
|
|
||||||
|
# ── 显示参数 ──────────────────────────────────
|
||||||
|
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
||||||
|
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.5]
|
||||||
|
|
||||||
|
# 小球颜色
|
||||||
|
# 小球半径从 coord_file 的 radius 列读取
|
||||||
|
ball_color_r: 0.90 # R 分量 (0~1)
|
||||||
|
ball_color_g: 0.20 # G 分量
|
||||||
|
ball_color_b: 0.20 # B 分量
|
||||||
|
|
||||||
|
# 盒子面颜色
|
||||||
|
box_color_r: 0.80
|
||||||
|
box_color_g: 0.80
|
||||||
|
box_color_b: 0.85
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
Case runner for Dynamics case01.
|
||||||
|
|
||||||
|
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 示例案例 case01")
|
||||||
|
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,15 @@
|
|||||||
|
地球围绕太阳转,月球围绕地球转,成功案例
|
||||||
|
| 项目 | 数值 | 比例 |
|
||||||
|
| --------- | --------------------: | ----------------: |
|
||||||
|
| 太阳—地球平均距离 | (1.496\times 10^8) km | 约为地月距离的 **389 倍** |
|
||||||
|
| 地球—月球平均距离 | (3.844\times 10^5) km | 1 |
|
||||||
|
|
||||||
|
R日地? : R地?月 ≈ 389
|
||||||
|
|
||||||
|
| 天体 | 质量/kg | 与地球质量之比 |
|
||||||
|
| -- | --------------------: | -------: |
|
||||||
|
| 太阳 | (1.989\times 10^{30}) | (333000) |
|
||||||
|
| 地球 | (5.972\times 10^{24}) | (1) |
|
||||||
|
| 月球 | (7.35\times 10^{22}) | (0.0123) |
|
||||||
|
|
||||||
|
M太阳?:M地球?:M月球?≈27100000:81.3:1
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
bond_name k rest_length
|
||||||
|
k1 100.0 2.0
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
n1 n2 bond_name
|
||||||
|
|
||||||
@@ -0,0 +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
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# 物理模拟参数配置
|
||||||
|
# 格式:YAML
|
||||||
|
# 用法:python run_dynamics.py
|
||||||
|
|
||||||
|
# ── 流程控制 ──────────────────────────────────
|
||||||
|
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||||
|
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||||
|
step_simulate: 1 # 运行物理模拟 → output/trajectory.txt
|
||||||
|
step_sample: 1 # 抽帧 → output/display.txt
|
||||||
|
step_plot: 0 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||||
|
step_animation: 1 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||||
|
force_calc: 0 # 强制重新计算:1=跳过缓存强算,0=自动使用已有输出
|
||||||
|
|
||||||
|
# ── 计算引擎 ──────────────────────────────────
|
||||||
|
# 可选: python, c, cpp, fortran, java
|
||||||
|
# python = Python 参考实现(compute.py)
|
||||||
|
# c = C 引擎 (engines/c/build/dynamics_c)
|
||||||
|
# cpp = C++ 引擎 (engines/cpp/build/dynamics_cpp)
|
||||||
|
# fortran = Fortran 引擎 (engines/fortran/build/dynamics_f90)
|
||||||
|
engine: python # 默认使用 Python 引擎
|
||||||
|
|
||||||
|
# ── 盒子 ──────────────────────────────────────
|
||||||
|
box_a: 20.0 # 立方体半边长,粒子被限制在 [-box_a, box_a]³ 内
|
||||||
|
|
||||||
|
# ── 初始构型 ──────────────────────────────────
|
||||||
|
# 坐标文件格式:
|
||||||
|
# 第一行:n mass radius x y z vx vy vz
|
||||||
|
# 后续行:原子序号 质量 半径 x y z vx vy vz
|
||||||
|
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.0, 0.0, -9.8] # 重力场分量 (m/s²)
|
||||||
|
# B: [0.5, 0.5, 0.5] # 阻尼分量
|
||||||
|
B: [0.0, 0.0, 0.0] # 阻尼分量
|
||||||
|
|
||||||
|
# ── 力开关(0=关闭, 1=开启)──────────────────
|
||||||
|
gravity_field: 0 # 均匀重力场 (G)
|
||||||
|
gravity_interaction: 1 # 原子间万有引力
|
||||||
|
elastic_force: 0 # 弹簧键力
|
||||||
|
damping_force: 0 # 阻尼 (B)
|
||||||
|
driving_force: 0 # 驱动力(需 driver_file 定义)
|
||||||
|
#
|
||||||
|
gravity_strength: 100.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: 2
|
||||||
|
|
||||||
|
# ── 时间步长 ──────────────────────────────────
|
||||||
|
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: 0
|
||||||
|
|
||||||
|
# ── 显示参数 ──────────────────────────────────
|
||||||
|
# 盒子透明度:单个数值(统一)或 6 个数的数组,按 [-x,+x,-y,+y,-z,+z] 顺序
|
||||||
|
alpha: [0.0, 0.0, 0.0, 0.0, 0.0, 0.5]
|
||||||
|
|
||||||
|
# 小球颜色
|
||||||
|
# 小球半径从 coord_file 的 radius 列读取
|
||||||
|
ball_color_r: 0.90 # R 分量 (0~1)
|
||||||
|
ball_color_g: 0.20 # G 分量
|
||||||
|
ball_color_b: 0.20 # B 分量
|
||||||
|
|
||||||
|
# 盒子面颜色
|
||||||
|
box_color_r: 0.80
|
||||||
|
box_color_g: 0.80
|
||||||
|
box_color_b: 0.85
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
Case runner for Dynamics case01.
|
||||||
|
|
||||||
|
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 示例案例 case01")
|
||||||
|
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,28 @@
|
|||||||
|
# case05: 一维原子链(60原子)
|
||||||
|
|
||||||
|
60个原子沿 x 轴排列,相邻原子用弹簧连接。
|
||||||
|
|
||||||
|
## 物理设定
|
||||||
|
|
||||||
|
| 参数 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| 原子数 | 60 |
|
||||||
|
| 排列 | 沿 x 轴等间距排列,间距为 1 |
|
||||||
|
| 约束 | 原子**只沿 z 方向**振动(fix_x=1, fix_y=1, fix_z=0) |
|
||||||
|
| 弹簧 | 劲度系数 k=1.0,原长 L₀=1.0 |
|
||||||
|
| 重力 | 无 |
|
||||||
|
| 万有引力 | 无 |
|
||||||
|
| 阻尼 | 无 |
|
||||||
|
| 算法 | leapfrog(蛙跳法,能量守恒) |
|
||||||
|
|
||||||
|
## 初始条件
|
||||||
|
|
||||||
|
- 所有原子初始速度为零
|
||||||
|
- **第1个原子**在 z 方向有初始位移,位移量 = 1
|
||||||
|
- 其余原子初始 z=0
|
||||||
|
|
||||||
|
## 动力学行为
|
||||||
|
|
||||||
|
初始时刻第1个原子的 z 位移拉伸了它和第2个原子之间的弹簧,产生一个沿 z 方向的扰动。该扰动将以波的形式沿一维原子链传播,在链的两端反射,形成驻波叠加。
|
||||||
|
|
||||||
|
由于无阻尼,系统总能量守恒。
|
||||||
@@ -0,0 +1,471 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>case05 — 一维原子链驱动力学模拟 | 物理原理 & 使用文档</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">60 个原子沿 x 轴排列 · 弹簧连接 · z 方向受迫振动</p>
|
||||||
|
<span class="badge">case05 · examples/case05</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>60 个原子沿 <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/case05
|
||||||
|
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>左上角 <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>
|
||||||
|
<tr><td>Q / E 键</td><td>画面绕视向旋转 -90° / +90°</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>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>case05/
|
||||||
|
├── input/
|
||||||
|
│ ├── input.txt # 主配置文件(YAML 格式)
|
||||||
|
│ ├── coord.txt # 原子坐标(60 个原子)
|
||||||
|
│ ├── connection.txt # 弹簧连接关系(59 条键)
|
||||||
|
│ ├── bond.txt # 弹簧参数(k=1.0, L₀=1.0)
|
||||||
|
│ └── <strong>driver.txt</strong> # <span class="cm">驱动力定义(本案例新增)</span>
|
||||||
|
├── output/
|
||||||
|
│ ├── trajectory.txt # 全量轨迹数据(10000 步 × 60 原子)
|
||||||
|
│ ├── display.txt # 抽帧后的动画数据(200 帧 × 60 原子)
|
||||||
|
│ ├── dynamics.log # 计算日志
|
||||||
|
│ └── animation.log # 动画启动日志(闪退时排查用)
|
||||||
|
├── 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 50.0 1.0
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
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 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
|
||||||
@@ -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 1 0 0 0.1 0 0 90 all
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# 物理模拟参数配置
|
||||||
|
# 格式:YAML
|
||||||
|
# 用法:python run_dynamics.py
|
||||||
|
|
||||||
|
# ── 流程控制 ──────────────────────────────────
|
||||||
|
# 每步用 0/1 单独开关,1=执行,0=跳过
|
||||||
|
# 依赖关系:抽帧依赖模拟结果,绘图依赖模拟+抽帧
|
||||||
|
step_simulate: 1 # 运行物理模拟 → output/trajectory.txt
|
||||||
|
step_sample: 1 # 抽帧 → output/display.txt
|
||||||
|
step_plot: 0 # 绘制轨迹/能量图 → output/trajectory_plots.png
|
||||||
|
step_animation: 1 # 自动播放 VisPy 3D 动画窗口(需安装 vispy)
|
||||||
|
force_calc: 0 # 强制重新计算:1=跳过缓存强算,0=自动使用已有输出
|
||||||
|
|
||||||
|
# ── 计算引擎 ──────────────────────────────────
|
||||||
|
# 可选: python, c, cpp, fortran, java
|
||||||
|
engine: python # 默认使用 Python 引擎
|
||||||
|
|
||||||
|
# ── 盒子 ──────────────────────────────────────
|
||||||
|
box_a: 80.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.0, 0.0, 0.0] # 重力场分量 (m/s²)
|
||||||
|
B: [0.0, 0.0, 0.0] # 阻尼分量
|
||||||
|
|
||||||
|
# ── 力开关(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: 100.0
|
||||||
|
|
||||||
|
# 抽帧间隔(每 NSTEP 步取一帧用于动画)
|
||||||
|
NSTEP: 50
|
||||||
|
|
||||||
|
# ── 时间步长 ──────────────────────────────────
|
||||||
|
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.5]
|
||||||
|
|
||||||
|
# 小球颜色
|
||||||
|
# 小球半径从 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
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
Case runner for Dynamics case05 — 1D atomic chain.
|
||||||
|
|
||||||
|
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 示例案例 case01")
|
||||||
|
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()
|
||||||
Reference in New Issue
Block a user