modified: CMakeLists.txt
modified: INSTALL.md modified: README.md modified: build_release_zip.py modified: compute.py new file: doc/index.html modified: dynamics.py modified: engines/c/main.c modified: engines/cpp/main.cpp modified: engines/fortran/main.f90 modified: examples/case01/input/coord.txt renamed: examples/case01/input/parameters.yaml -> examples/case01/input/input.txt modified: examples/case01/run_dynamics.py new file: examples/case02/input/bond.txt new file: examples/case02/input/connection.txt new file: examples/case02/input/coord.txt new file: examples/case02/input/input.txt new file: examples/case02/run_dynamics.py
This commit is contained in:
+157
-35
@@ -64,6 +64,13 @@ box_color_r = None
|
||||
box_color_g = None
|
||||
box_color_b = None
|
||||
|
||||
# 力开关
|
||||
GRAVITY_FIELD = 1 # 均匀重力场
|
||||
GRAVITY_INTERACTION = 0 # 原子间万有引力
|
||||
ELASTIC_FORCE = 1 # 弹簧键力
|
||||
DAMPING_FORCE = 0 # 阻尼
|
||||
GRAVITY_STRENGTH = 1.0
|
||||
|
||||
# 派生边界(根据 box_a 计算)
|
||||
X_MIN = None
|
||||
X_MAX = None
|
||||
@@ -156,9 +163,11 @@ def load_coord_file(coord_path):
|
||||
header = f.readline().strip().split()
|
||||
expected = ["n", "mass", "radius", "x", "y", "z", "vx", "vy", "vz"]
|
||||
legacy = expected + ["fixed_x", "fixed_y", "fixed_z"]
|
||||
if header not in (expected, legacy):
|
||||
legacy_new = expected + ["fix_x", "fix_y", "fix_z"]
|
||||
if header not in (expected, legacy, legacy_new):
|
||||
raise ValueError(
|
||||
f"坐标文件表头应为: {' '.join(expected)},实际为: {' '.join(header)}")
|
||||
f"坐标文件表头应为: {' '.join(expected)} 或加三列 fix_x fix_y fix_z,"
|
||||
f"实际为: {' '.join(header)}")
|
||||
|
||||
for line_no, line in enumerate(f, start=2):
|
||||
stripped = line.strip()
|
||||
@@ -167,7 +176,7 @@ def load_coord_file(coord_path):
|
||||
parts = stripped.split()
|
||||
if header == expected and len(parts) != 9:
|
||||
raise ValueError(f"{coord_path}:{line_no} 应有 9 列,实际为 {len(parts)} 列")
|
||||
if header == legacy and len(parts) != 12:
|
||||
if header in (legacy, legacy_new) and len(parts) != 12:
|
||||
raise ValueError(f"{coord_path}:{line_no} 应有 12 列,实际为 {len(parts)} 列")
|
||||
rows.append(parts)
|
||||
|
||||
@@ -182,7 +191,7 @@ def load_coord_file(coord_path):
|
||||
velocities = np.array([[float(row[6]), float(row[7]), float(row[8])] for row in rows],
|
||||
dtype=np.float64)
|
||||
fixed = np.zeros((len(rows), 3), dtype=np.int64)
|
||||
if header == legacy:
|
||||
if header in (legacy, legacy_new):
|
||||
fixed = np.array([[int(row[9]), int(row[10]), int(row[11])] for row in rows],
|
||||
dtype=np.int64)
|
||||
|
||||
@@ -369,6 +378,7 @@ def run_from_config(config, out_dir=None):
|
||||
global ball_radius, ball_color_r, ball_color_g, ball_color_b
|
||||
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
|
||||
|
||||
box_a = float(config.get("box_a", 10.0))
|
||||
raw_alpha = config.get("alpha", 0.2)
|
||||
@@ -427,6 +437,14 @@ def run_from_config(config, out_dir=None):
|
||||
box_color_g = float(config.get("box_color_g", 0.8))
|
||||
box_color_b = float(config.get("box_color_b", 0.85))
|
||||
|
||||
# 力开关
|
||||
global GRAVITY_FIELD, GRAVITY_INTERACTION, ELASTIC_FORCE, DAMPING_FORCE, GRAVITY_STRENGTH
|
||||
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))
|
||||
|
||||
print(f"[compute] 使用算法: {METHOD}")
|
||||
print(f"[compute] 已加载成键信息: {len(BOND_PAIRS)} 条键")
|
||||
if config.get("_skip_run", False):
|
||||
@@ -470,7 +488,8 @@ def run_from_config(config, out_dir=None):
|
||||
f.write(f" mass: {M}\n")
|
||||
f.write("=" * 50 + "\n")
|
||||
print(f"[compute] 日志已保存至: {log_path}")
|
||||
print(f"[compute] 计算耗时: {elapsed:.3f} s")
|
||||
record = len(traj_x)
|
||||
print(f"[compute] 计算完成: {record} 步, {elapsed:.3f} s")
|
||||
|
||||
return traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz
|
||||
|
||||
@@ -525,8 +544,14 @@ def run_engine(engine, input_dir, output_dir, config):
|
||||
"DT": float(config["DT"]),
|
||||
"NSTEP": int(config.get("NSTEP", 1)),
|
||||
"warmup_steps": int(config.get("warmup_steps", 0)),
|
||||
"method": normalize_method_name(config.get("method", "leapfrog")),
|
||||
"G": [float(v) for v in G],
|
||||
"B": [float(v) for v in B],
|
||||
"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)),
|
||||
}
|
||||
param_path = os.path.join(script_dir, "engines", engine, "param.json")
|
||||
os.makedirs(os.path.dirname(param_path), exist_ok=True)
|
||||
@@ -540,24 +565,87 @@ def run_engine(engine, input_dir, output_dir, config):
|
||||
print(f"[compute] input: {input_dir}")
|
||||
print(f"[compute] output: {output_dir}")
|
||||
|
||||
# ── 预校准:跑 NT=1000 步测速 ────────────────────────────────
|
||||
total_steps = int(config["NT"]) - int(config.get("warmup_steps", 0))
|
||||
_calib_nt = min(1000, max(100, total_steps // 10))
|
||||
_calib_param = dict(param_json)
|
||||
_calib_param["NT"] = _calib_nt
|
||||
_calib_path = os.path.join(script_dir, "engines", engine, "_calib.json")
|
||||
with open(_calib_path, "w", encoding="utf-8") as _cf:
|
||||
json.dump(_calib_param, _cf, indent=2)
|
||||
_ct0 = time.time()
|
||||
subprocess.run(
|
||||
[engine_path, os.path.abspath(input_dir), os.devnull, _calib_path],
|
||||
capture_output=True, timeout=60)
|
||||
os.remove(_calib_path)
|
||||
_calib_elapsed = max(time.time() - _ct0, 0.001)
|
||||
_overhead = _calib_elapsed * 0.15
|
||||
_step_time = max(_calib_elapsed - _overhead, 0.0001) / _calib_nt
|
||||
_est_total = max(_calib_elapsed, _overhead + _step_time * total_steps)
|
||||
|
||||
t_start = time.time()
|
||||
t_start_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
result = subprocess.run(
|
||||
[engine_path, os.path.abspath(input_dir), os.path.abspath(output_dir), param_path],
|
||||
capture_output=True, text=True, timeout=600)
|
||||
t_end = time.time()
|
||||
elapsed = t_end - t_start
|
||||
t_end_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# 打印引擎输出
|
||||
if result.stdout:
|
||||
for line in result.stdout.strip().split("\n"):
|
||||
line = line.strip()
|
||||
if line:
|
||||
print(f" {line}")
|
||||
if result.returncode != 0:
|
||||
print(f"[compute] 引擎错误:\n{result.stderr}")
|
||||
raise RuntimeError(f"引擎 {engine} 返回错误码 {result.returncode}")
|
||||
_p = subprocess.Popen(
|
||||
[engine_path, os.path.abspath(input_dir), os.path.abspath(output_dir), param_path],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
text=True, encoding='utf-8', errors='replace')
|
||||
_engine_lines = []
|
||||
|
||||
try:
|
||||
from tqdm import tqdm as _tqdm
|
||||
_pbar = _tqdm(total=total_steps, desc=f"[compute] 引擎 {engine}",
|
||||
unit="步", bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]')
|
||||
except ImportError:
|
||||
_pbar = None
|
||||
|
||||
try:
|
||||
while True:
|
||||
_line = _p.stdout.readline() if _p.stdout else ''
|
||||
if _line:
|
||||
_line = _line.strip()
|
||||
if _line:
|
||||
_engine_lines.append(_line)
|
||||
if _p.poll() is not None:
|
||||
if _p.stdout:
|
||||
for _r in _p.stdout:
|
||||
_r = _r.strip()
|
||||
if _r:
|
||||
_engine_lines.append(_r)
|
||||
break
|
||||
if _pbar is not None and _est_total > 0:
|
||||
_pbar.n = int(min((time.time() - t_start) / _est_total, 0.99) * total_steps)
|
||||
_pbar.refresh()
|
||||
time.sleep(0.2)
|
||||
finally:
|
||||
if _pbar is not None:
|
||||
_pbar.n = total_steps
|
||||
_pbar.refresh()
|
||||
_pbar.close()
|
||||
if _p.poll() is None:
|
||||
_p.kill()
|
||||
_p.wait(timeout=5)
|
||||
|
||||
_rc = _p.returncode
|
||||
t_end = time.time()
|
||||
t_end_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
elapsed = t_end - t_start
|
||||
|
||||
if _rc != 0:
|
||||
_err = _p.stderr.read() if _p.stderr else ''
|
||||
if _err:
|
||||
print(f"[compute] 引擎错误:\n{_err}")
|
||||
raise RuntimeError(f"引擎 {engine} 返回错误码 {_rc}")
|
||||
|
||||
if _engine_lines:
|
||||
_log_path = os.path.join(output_dir, "dynamics.log")
|
||||
try:
|
||||
with open(_log_path, "a", encoding="utf-8") as _lf:
|
||||
_lf.write("\n--- 引擎输出 ---\n")
|
||||
for _ln in _engine_lines:
|
||||
_lf.write(_ln + "\n")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# 加载输出的 trajectory.txt
|
||||
traj_path = os.path.join(os.path.abspath(output_dir), "trajectory.txt")
|
||||
@@ -636,6 +724,11 @@ def save_trajectory_txt(traj_x, traj_y, traj_z, traj_vx, traj_vy, traj_vz, out_d
|
||||
"box_color_r": box_color_r,
|
||||
"box_color_g": box_color_g,
|
||||
"box_color_b": box_color_b,
|
||||
"gravity_field": GRAVITY_FIELD,
|
||||
"gravity_interaction": GRAVITY_INTERACTION,
|
||||
"elastic_force": ELASTIC_FORCE,
|
||||
"damping_force": DAMPING_FORCE,
|
||||
"gravity_strength": GRAVITY_STRENGTH,
|
||||
}
|
||||
save_text_data(out_path, payload)
|
||||
print(f"[compute] 轨迹数据已保存至: {out_path}")
|
||||
@@ -752,16 +845,27 @@ def normalize_method_name(method):
|
||||
def compute_force(x, y, z, vx, vy, vz, m, g, b):
|
||||
"""Compute total force from the current state.
|
||||
|
||||
Current model:
|
||||
- gravity: F = m * g_vec
|
||||
- linear drag: F_drag = -B_vec * v
|
||||
- spring bonds: Hooke force based on bonded pair distance
|
||||
Each force type is independently controlled by a global switch:
|
||||
- GRAVITY_FIELD: uniform gravity F = m * g_vec
|
||||
- DAMPING_FORCE: linear drag F_drag = -B_vec * v
|
||||
- ELASTIC_FORCE: spring bonds based on bonded pair distance
|
||||
- GRAVITY_INTERACTION: Newtonian gravity between atom pairs
|
||||
"""
|
||||
fx = m * g[0] - b[0] * vx
|
||||
fy = m * g[1] - b[1] * vy
|
||||
fz = m * g[2] - b[2] * vz
|
||||
fx = np.zeros_like(x)
|
||||
fy = np.zeros_like(y)
|
||||
fz = np.zeros_like(z)
|
||||
|
||||
if BOND_PAIRS is not None and len(BOND_PAIRS) > 0:
|
||||
if GRAVITY_FIELD:
|
||||
fx += m * g[0]
|
||||
fy += m * g[1]
|
||||
fz += m * g[2]
|
||||
|
||||
if DAMPING_FORCE:
|
||||
fx -= b[0] * vx
|
||||
fy -= b[1] * vy
|
||||
fz -= b[2] * vz
|
||||
|
||||
if ELASTIC_FORCE and BOND_PAIRS is not None and len(BOND_PAIRS) > 0:
|
||||
for bond_idx, (idx_1, idx_2) in enumerate(BOND_PAIRS):
|
||||
dx = x[idx_2] - x[idx_1]
|
||||
dy = y[idx_2] - y[idx_1]
|
||||
@@ -787,6 +891,25 @@ def compute_force(x, y, z, vx, vy, vz, m, g, b):
|
||||
fy[idx_2] -= fy_bond
|
||||
fz[idx_2] -= fz_bond
|
||||
|
||||
if GRAVITY_INTERACTION:
|
||||
n = len(m)
|
||||
for i in range(n):
|
||||
for j in range(i + 1, n):
|
||||
dx = x[j] - x[i]
|
||||
dy = y[j] - y[i]
|
||||
dz = z[j] - z[i]
|
||||
r2 = dx * dx + dy * dy + dz * dz
|
||||
if r2 <= 1e-12:
|
||||
continue
|
||||
mi = m[i]; mj = m[j]
|
||||
f_mag = GRAVITY_STRENGTH * mi * mj / r2
|
||||
r = np.sqrt(r2)
|
||||
fx_i = f_mag * dx / r
|
||||
fy_i = f_mag * dy / r
|
||||
fz_i = f_mag * dz / r
|
||||
fx[i] += fx_i; fy[i] += fy_i; fz[i] += fz_i
|
||||
fx[j] -= fx_i; fy[j] -= fy_i; fz[j] -= fz_i
|
||||
|
||||
return fx, fy, fz
|
||||
|
||||
|
||||
@@ -857,14 +980,13 @@ def Leapfrog_Method(x, y, z, vx, vy, vz, dt, m, g, b):
|
||||
y = y + vy_half * dt
|
||||
z = z + vz_half * dt
|
||||
|
||||
gamma_x = b[0] / m
|
||||
gamma_y = b[1] / m
|
||||
gamma_z = b[2] / m
|
||||
vx_next = (vx_half + 0.5 * g[0] * dt) / (1.0 + 0.5 * gamma_x * dt)
|
||||
vy_next = (vy_half + 0.5 * g[1] * dt) / (1.0 + 0.5 * gamma_y * dt)
|
||||
vz_next = (vz_half + 0.5 * g[2] * dt) / (1.0 + 0.5 * gamma_z * dt)
|
||||
# 显式预测器:用原始加速度外推半步(标准 Velocity-Verlet 预测步)
|
||||
# v_pred = v_half + 0.5 * a(t)*dt,包含重力+阻尼+弹簧的所有贡献
|
||||
vx_pred = vx_half + 0.5 * ax * dt
|
||||
vy_pred = vy_half + 0.5 * ay * dt
|
||||
vz_pred = vz_half + 0.5 * az * dt
|
||||
ax_next, ay_next, az_next = compute_acceleration(
|
||||
x, y, z, vx_next, vy_next, vz_next, m, g, b)
|
||||
x, y, z, vx_pred, vy_pred, vz_pred, m, g, b)
|
||||
vx = vx_half + 0.5 * ax_next * dt
|
||||
vy = vy_half + 0.5 * ay_next * dt
|
||||
vz = vz_half + 0.5 * az_next * dt
|
||||
|
||||
Reference in New Issue
Block a user