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 # 原子间万有引力
|
||||
ELASTIC_FORCE = 1 # 弹簧键力
|
||||
DAMPING_FORCE = 0 # 阻尼
|
||||
DRIVING_FORCE = 0 # 驱动力
|
||||
GRAVITY_STRENGTH = 1.0
|
||||
|
||||
# 驱动力数据
|
||||
DRIVER_DATA = None # 加载自 driver.txt
|
||||
|
||||
# 派生边界(根据 box_a 计算)
|
||||
X_MIN = 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):
|
||||
"""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 warmup_steps, sample_start, sample_end
|
||||
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))
|
||||
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 DRIVING_FORCE, DRIVER_DATA
|
||||
GRAVITY_FIELD = int(config.get("gravity_field", 1))
|
||||
GRAVITY_INTERACTION = int(config.get("gravity_interaction", 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))
|
||||
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] 已加载成键信息: {len(BOND_PAIRS)} 条键")
|
||||
@@ -1062,11 +1207,14 @@ def run_simulation():
|
||||
vy = ATOM_VELOCITIES[:, 1].copy()
|
||||
vz = ATOM_VELOCITIES[:, 2].copy()
|
||||
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:
|
||||
print(f"[compute] 预热阶段: 前 {warmup_steps} 步不记录")
|
||||
for step in trange(warmup_steps, desc="[compute] 预热"):
|
||||
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 = wrap_position(x, y, z)
|
||||
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)
|
||||
|
||||
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_y[step] = y
|
||||
traj_z[step] = z
|
||||
|
||||
Reference in New Issue
Block a user