0e636e275d
- 覆盖全部 10 个案例(原 Readme 只到 case06) - 新增案例选择指南表格 - Readme.html 为深色主题独立 HTML 页面 (含卡片布局、标签分类、代码高亮、响应式设计) - 各案例详情对齐最新配置参数
451 lines
19 KiB
C++
451 lines
19 KiB
C++
/**
|
||
* engines/cpp/dynamics_lib.cpp
|
||
* -----------------------------
|
||
* 纯计算 DLL(C++ 版):无文件 I/O,所有数据由 Python 以 NumPy 数组传入。
|
||
* 算法与 main.cpp / compute.py 保持完全一致。
|
||
*
|
||
* 编译(Windows):
|
||
* g++ -O3 -march=native -std=c++17 -shared -o build/dynamics_cpp.dll dynamics_lib.cpp
|
||
* 编译(Linux):
|
||
* g++ -O3 -march=native -std=c++17 -shared -fPIC -o build/dynamics_cpp.so dynamics_lib.cpp
|
||
* 编译(macOS):
|
||
* g++ -O3 -march=native -std=c++17 -dynamiclib -o build/dynamics_cpp.dylib dynamics_lib.cpp
|
||
*/
|
||
|
||
#ifdef _WIN32
|
||
# define EXPORT extern "C" __declspec(dllexport)
|
||
#else
|
||
# define EXPORT extern "C" __attribute__((visibility("default")))
|
||
#endif
|
||
|
||
#include <cmath>
|
||
#include <cstring>
|
||
#include <cstdlib>
|
||
#include <vector>
|
||
|
||
/* ── 驱动力结构体 ─────────────────────────────────────────── */
|
||
struct Drivers {
|
||
int n_drivers = 0;
|
||
const int *idx = nullptr;
|
||
const double *amp = nullptr;
|
||
const double *freq = nullptr;
|
||
const double *phi = nullptr;
|
||
const double *eq = nullptr;
|
||
const double *ncycles = nullptr;
|
||
const int *has_period = nullptr;
|
||
std::vector<double> freeze; /* [n_drivers*3] 冻结位置(period 结束时锁定)*/
|
||
};
|
||
|
||
/* ── 加速度:保守力(弹簧键 + 均匀重力场)────────────────── */
|
||
static void accel_conservative(
|
||
int n, const double *x, const double *y, const double *z,
|
||
const double *m,
|
||
double Gx, double Gy, double Gz,
|
||
int gravity_field, int elastic_force,
|
||
int n_bonds, const int *bond_pairs,
|
||
const double *bond_k, const double *bond_r0,
|
||
double *ax, double *ay, double *az)
|
||
{
|
||
for (int i = 0; i < n; i++) {
|
||
ax[i] = gravity_field ? Gx : 0.0;
|
||
ay[i] = gravity_field ? Gy : 0.0;
|
||
az[i] = gravity_field ? Gz : 0.0;
|
||
}
|
||
if (!elastic_force || n_bonds == 0) return;
|
||
for (int b = 0; b < n_bonds; b++) {
|
||
int ii = bond_pairs[b*2];
|
||
int jj = bond_pairs[b*2+1];
|
||
double dx = x[jj]-x[ii], dy = y[jj]-y[ii], dz = z[jj]-z[ii];
|
||
double dist = std::sqrt(dx*dx + dy*dy + dz*dz);
|
||
if (dist < 1e-12) continue;
|
||
double fac = bond_k[b] * (dist - bond_r0[b]) / dist;
|
||
double fx = fac*dx, fy = fac*dy, fz_b = fac*dz;
|
||
ax[ii] += fx/m[ii]; ay[ii] += fy/m[ii]; az[ii] += fz_b/m[ii];
|
||
ax[jj] -= fx/m[jj]; ay[jj] -= fy/m[jj]; az[jj] -= fz_b/m[jj];
|
||
}
|
||
}
|
||
|
||
/* ── 完整加速度(含阻尼)────────────────────────────────── */
|
||
static void accel_full(
|
||
int n, const double *x, const double *y, const double *z,
|
||
const double *vx, const double *vy, const double *vz,
|
||
const double *m,
|
||
double Gx, double Gy, double Gz,
|
||
double Bx, double By, double Bz,
|
||
int gravity_field, int elastic_force, int damping_force,
|
||
int n_bonds, const int *bond_pairs,
|
||
const double *bond_k, const double *bond_r0,
|
||
double *ax, double *ay, double *az)
|
||
{
|
||
accel_conservative(n, x, y, z, m, Gx, Gy, Gz,
|
||
gravity_field, elastic_force,
|
||
n_bonds, bond_pairs, bond_k, bond_r0,
|
||
ax, ay, az);
|
||
if (damping_force) {
|
||
for (int i = 0; i < n; i++) {
|
||
ax[i] -= Bx * vx[i] / m[i];
|
||
ay[i] -= By * vy[i] / m[i];
|
||
az[i] -= Bz * vz[i] / m[i];
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ── 边界:反弹 ──────────────────────────────────────────── */
|
||
static inline void _limit1(double &p, double &v, double lo, double hi) {
|
||
if (p > hi) { p = hi; v = -std::fabs(v); }
|
||
if (p < lo) { p = lo; v = std::fabs(v); }
|
||
}
|
||
|
||
/* ── 边界:回绕 ──────────────────────────────────────────── */
|
||
static inline void _wrap1(double &p, double lo, double hi) {
|
||
if (p > hi) p = lo;
|
||
if (p < lo) p = hi;
|
||
}
|
||
|
||
/* ── 边界 + 固定约束 ────────────────────────────────────── */
|
||
static void apply_boundary_and_constraints(
|
||
int n, double *x, double *y, double *z,
|
||
double *vx, double *vy, double *vz,
|
||
const int *fixed, const double *pos_init, double box_a)
|
||
{
|
||
double lo = -box_a, hi = box_a;
|
||
for (int i = 0; i < n; i++) {
|
||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||
_limit1(x[i], vx[i], lo, hi);
|
||
_limit1(y[i], vy[i], lo, hi);
|
||
_limit1(z[i], vz[i], lo, hi);
|
||
}
|
||
for (int i = 0; i < n; i++) {
|
||
_wrap1(x[i], lo, hi);
|
||
_wrap1(y[i], lo, hi);
|
||
_wrap1(z[i], lo, hi);
|
||
}
|
||
for (int i = 0; i < n; i++) {
|
||
if (fixed[i*3+0]) { x[i] = pos_init[i*3+0]; vx[i] = 0.0; }
|
||
if (fixed[i*3+1]) { y[i] = pos_init[i*3+1]; vy[i] = 0.0; }
|
||
if (fixed[i*3+2]) { z[i] = pos_init[i*3+2]; vz[i] = 0.0; }
|
||
}
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════
|
||
* 蛙跳法(与 main.cpp leapfrog_step 完全一致)
|
||
* ══════════════════════════════════════════════════════════ */
|
||
static void leapfrog_step(
|
||
int n, double *x, double *y, double *z,
|
||
double *vx, double *vy, double *vz,
|
||
const double *m, const int *fixed,
|
||
double Gx, double Gy, double Gz,
|
||
double Bx, double By, double Bz,
|
||
int gravity_field, int elastic_force, int damping_force,
|
||
int n_bonds, const int *bp, const double *bk, const double *br0, double dt)
|
||
{
|
||
std::vector<double> buf(n * 3);
|
||
double *ax = buf.data(), *ay = ax+n, *az = ay+n;
|
||
accel_conservative(n, x, y, z, m, Gx, Gy, Gz,
|
||
gravity_field, elastic_force,
|
||
n_bonds, bp, bk, br0, ax, ay, az);
|
||
bool has_damp = damping_force && (Bx != 0.0 || By != 0.0 || Bz != 0.0);
|
||
for (int i = 0; i < n; i++) {
|
||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||
if (has_damp) {
|
||
double ax_ = Bx*dt/(2.0*m[i]);
|
||
double ay_ = By*dt/(2.0*m[i]);
|
||
double az_ = Bz*dt/(2.0*m[i]);
|
||
vx[i] = (vx[i]*(1.0-ax_) + ax[i]*dt) / (1.0+ax_);
|
||
vy[i] = (vy[i]*(1.0-ay_) + ay[i]*dt) / (1.0+ay_);
|
||
vz[i] = (vz[i]*(1.0-az_) + az[i]*dt) / (1.0+az_);
|
||
} else {
|
||
vx[i] += ax[i]*dt;
|
||
vy[i] += ay[i]*dt;
|
||
vz[i] += az[i]*dt;
|
||
}
|
||
x[i] += vx[i]*dt;
|
||
y[i] += vy[i]*dt;
|
||
z[i] += vz[i]*dt;
|
||
}
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════
|
||
* 显式欧拉法
|
||
* ══════════════════════════════════════════════════════════ */
|
||
static void euler_step(
|
||
int n, double *x, double *y, double *z,
|
||
double *vx, double *vy, double *vz,
|
||
const double *m, const int *fixed,
|
||
double Gx, double Gy, double Gz,
|
||
double Bx, double By, double Bz,
|
||
int gravity_field, int elastic_force, int damping_force,
|
||
int n_bonds, const int *bp, const double *bk, const double *br0, double dt)
|
||
{
|
||
std::vector<double> buf(n * 3);
|
||
double *ax = buf.data(), *ay = ax+n, *az = ay+n;
|
||
accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||
gravity_field, elastic_force, damping_force,
|
||
n_bonds, bp, bk, br0, ax, ay, az);
|
||
for (int i = 0; i < n; i++) {
|
||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||
x[i] += vx[i]*dt; y[i] += vy[i]*dt; z[i] += vz[i]*dt;
|
||
vx[i]+= ax[i]*dt; vy[i]+= ay[i]*dt; vz[i]+= az[i]*dt;
|
||
}
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════
|
||
* 隐式欧拉法(与 main.cpp implicit_euler_step 完全一致)
|
||
* ══════════════════════════════════════════════════════════ */
|
||
static void implicit_euler_step(
|
||
int n, double *x, double *y, double *z,
|
||
double *vx, double *vy, double *vz,
|
||
const double *m, const int *fixed,
|
||
double Gx, double Gy, double Gz,
|
||
double Bx, double By, double Bz,
|
||
int gravity_field, int elastic_force, int damping_force,
|
||
int n_bonds, const int *bp, const double *bk, const double *br0, double dt)
|
||
{
|
||
std::vector<double> vbuf(n * 3), abuf(n * 3);
|
||
double *vxn = vbuf.data(), *vyn = vxn+n, *vzn = vyn+n;
|
||
double *ax = abuf.data(), *ay = ax+n, *az = ay+n;
|
||
|
||
for (int i = 0; i < n; i++) {
|
||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) {
|
||
vxn[i] = vyn[i] = vzn[i] = 0.0; continue;
|
||
}
|
||
double gx = Bx/m[i], gy = By/m[i], gz = Bz/m[i];
|
||
vxn[i] = (vx[i] + Gx*dt) / (1.0 + gx*dt);
|
||
vyn[i] = (vy[i] + Gy*dt) / (1.0 + gy*dt);
|
||
vzn[i] = (vz[i] + Gz*dt) / (1.0 + gz*dt);
|
||
}
|
||
accel_full(n, x, y, z, vxn, vyn, vzn, m, Gx, Gy, Gz, Bx, By, Bz,
|
||
gravity_field, elastic_force, damping_force,
|
||
n_bonds, bp, bk, br0, ax, ay, az);
|
||
for (int i = 0; i < n; i++) {
|
||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||
vx[i] += ax[i]*dt; vy[i] += ay[i]*dt; vz[i] += az[i]*dt;
|
||
x[i] += vx[i]*dt; y[i] += vy[i]*dt; z[i] += vz[i]*dt;
|
||
}
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════
|
||
* 中点法(与 main.cpp midpoint_step 完全一致)
|
||
* ══════════════════════════════════════════════════════════ */
|
||
static void midpoint_step(
|
||
int n, double *x, double *y, double *z,
|
||
double *vx, double *vy, double *vz,
|
||
const double *m, const int *fixed,
|
||
double Gx, double Gy, double Gz,
|
||
double Bx, double By, double Bz,
|
||
int gravity_field, int elastic_force, int damping_force,
|
||
int n_bonds, const int *bp, const double *bk, const double *br0, double dt)
|
||
{
|
||
std::vector<double> buf(n * 9);
|
||
double *ax = buf.data();
|
||
double *ay = ax+n; double *az = ay+n;
|
||
double *xm = az+n; double *ym = xm+n; double *zm = ym+n;
|
||
double *vxm = zm+n; double *vym = vxm+n; double *vzm = vym+n;
|
||
|
||
accel_full(n, x, y, z, vx, vy, vz, m, Gx, Gy, Gz, Bx, By, Bz,
|
||
gravity_field, elastic_force, damping_force,
|
||
n_bonds, bp, bk, br0, ax, ay, az);
|
||
|
||
for (int i = 0; i < n; i++) {
|
||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) {
|
||
xm[i]=x[i]; ym[i]=y[i]; zm[i]=z[i];
|
||
vxm[i]=vym[i]=vzm[i]=0.0; continue;
|
||
}
|
||
xm[i] = x[i] + 0.5*vx[i]*dt;
|
||
ym[i] = y[i] + 0.5*vy[i]*dt;
|
||
zm[i] = z[i] + 0.5*vz[i]*dt;
|
||
vxm[i] = vx[i] + 0.5*ax[i]*dt;
|
||
vym[i] = vy[i] + 0.5*ay[i]*dt;
|
||
vzm[i] = vz[i] + 0.5*az[i]*dt;
|
||
x[i] = x[i] + vxm[i]*dt;
|
||
y[i] = y[i] + vym[i]*dt;
|
||
z[i] = z[i] + vzm[i]*dt;
|
||
}
|
||
|
||
std::vector<double> abuf(n * 3);
|
||
double *axm = abuf.data(), *aym = axm+n, *azm = aym+n;
|
||
accel_full(n, xm, ym, zm, vxm, vym, vzm, m, Gx, Gy, Gz, Bx, By, Bz,
|
||
gravity_field, elastic_force, damping_force,
|
||
n_bonds, bp, bk, br0, axm, aym, azm);
|
||
for (int i = 0; i < n; i++) {
|
||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||
vx[i] += axm[i]*dt;
|
||
vy[i] += aym[i]*dt;
|
||
vz[i] += azm[i]*dt;
|
||
}
|
||
}
|
||
|
||
/* ── 驱动力 ─────────────────────────────────────────────── */
|
||
static void apply_driving(
|
||
int n, double *x, double *y, double *z,
|
||
double *vx, double *vy, double *vz,
|
||
double t, int step, double dt, Drivers &drv)
|
||
{
|
||
(void)n;
|
||
if (drv.n_drivers == 0) return;
|
||
constexpr double TWO_PI = 2.0 * 3.14159265358979323846;
|
||
|
||
for (int d = 0; d < drv.n_drivers; d++) {
|
||
int idx = drv.idx[d];
|
||
double fx = drv.freq[d*3+0];
|
||
double fy = drv.freq[d*3+1];
|
||
double fz = drv.freq[d*3+2];
|
||
|
||
if (drv.has_period[d]) {
|
||
double mf = std::fabs(fx) > std::fabs(fy) ? std::fabs(fx) : std::fabs(fy);
|
||
if (std::fabs(fz) > mf) mf = std::fabs(fz);
|
||
int period_steps = 0;
|
||
if (mf > 1e-12)
|
||
period_steps = (int)(drv.ncycles[d] / mf / dt);
|
||
if (step > period_steps) {
|
||
x[idx] = drv.freeze[d*3+0];
|
||
y[idx] = drv.freeze[d*3+1];
|
||
z[idx] = drv.freeze[d*3+2];
|
||
vx[idx] = vy[idx] = vz[idx] = 0.0;
|
||
continue;
|
||
}
|
||
double px = drv.eq[d*3+0] + drv.amp[d*3+0]*std::cos(TWO_PI*fx*t + drv.phi[d*3+0]);
|
||
double py = drv.eq[d*3+1] + drv.amp[d*3+1]*std::cos(TWO_PI*fy*t + drv.phi[d*3+1]);
|
||
double pz = drv.eq[d*3+2] + drv.amp[d*3+2]*std::cos(TWO_PI*fz*t + drv.phi[d*3+2]);
|
||
if (step == period_steps) {
|
||
drv.freeze[d*3+0] = px;
|
||
drv.freeze[d*3+1] = py;
|
||
drv.freeze[d*3+2] = pz;
|
||
}
|
||
}
|
||
x[idx] = drv.eq[d*3+0] + drv.amp[d*3+0]*std::cos(TWO_PI*fx*t + drv.phi[d*3+0]);
|
||
y[idx] = drv.eq[d*3+1] + drv.amp[d*3+1]*std::cos(TWO_PI*fy*t + drv.phi[d*3+1]);
|
||
z[idx] = drv.eq[d*3+2] + drv.amp[d*3+2]*std::cos(TWO_PI*fz*t + drv.phi[d*3+2]);
|
||
vx[idx] = -drv.amp[d*3+0]*TWO_PI*fx*std::sin(TWO_PI*fx*t + drv.phi[d*3+0]);
|
||
vy[idx] = -drv.amp[d*3+1]*TWO_PI*fy*std::sin(TWO_PI*fy*t + drv.phi[d*3+1]);
|
||
vz[idx] = -drv.amp[d*3+2]*TWO_PI*fz*std::sin(TWO_PI*fz*t + drv.phi[d*3+2]);
|
||
}
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════
|
||
* 导出函数:run_dynamics(接口与 C 版完全相同)
|
||
* ══════════════════════════════════════════════════════════ */
|
||
EXPORT int run_dynamics(
|
||
int n_atoms,
|
||
const double *pos_init,
|
||
const double *vel_init,
|
||
const double *masses,
|
||
const int *fixed,
|
||
int n_bonds,
|
||
const int *bond_pairs,
|
||
const double *bond_k,
|
||
const double *bond_r0,
|
||
double box_a, double dt,
|
||
int NT, int NSTEP, int warmup_steps, int method_id,
|
||
double Gx, double Gy, double Gz,
|
||
double Bx, double By, double Bz,
|
||
int gravity_field, int elastic_force, int damping_force,
|
||
double gravity_strength,
|
||
int n_drivers,
|
||
const int *drv_idx,
|
||
const double *drv_amp,
|
||
const double *drv_freq,
|
||
const double *drv_phi,
|
||
const double *drv_eq,
|
||
const double *drv_ncycles,
|
||
const int *drv_has_period,
|
||
int n_frames,
|
||
double *out_x, double *out_y, double *out_z,
|
||
double *out_vx, double *out_vy, double *out_vz,
|
||
void (*progress_cb)(int step, int total))
|
||
{
|
||
(void)gravity_strength;
|
||
int n = n_atoms;
|
||
|
||
std::vector<double> xv(n), yv(n), zv(n);
|
||
std::vector<double> vxv(n), vyv(n), vzv(n);
|
||
for (int i = 0; i < n; i++) {
|
||
xv[i]=pos_init[i*3+0]; yv[i]=pos_init[i*3+1]; zv[i]=pos_init[i*3+2];
|
||
vxv[i]=vel_init[i*3+0]; vyv[i]=vel_init[i*3+1]; vzv[i]=vel_init[i*3+2];
|
||
}
|
||
double *x=xv.data(), *y=yv.data(), *z=zv.data();
|
||
double *vx=vxv.data(), *vy=vyv.data(), *vz=vzv.data();
|
||
|
||
Drivers drv;
|
||
drv.n_drivers = n_drivers;
|
||
drv.idx = drv_idx;
|
||
drv.amp = drv_amp;
|
||
drv.freq = drv_freq;
|
||
drv.phi = drv_phi;
|
||
drv.eq = drv_eq;
|
||
drv.ncycles = drv_ncycles;
|
||
drv.has_period = drv_has_period;
|
||
if (n_drivers > 0)
|
||
drv.freeze.assign(n_drivers * 3, 0.0);
|
||
|
||
#define DO_STEP() do { \
|
||
switch (method_id) { \
|
||
case 0: euler_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||
gravity_field,elastic_force,damping_force, \
|
||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||
case 1: implicit_euler_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||
gravity_field,elastic_force,damping_force, \
|
||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||
case 2: midpoint_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||
gravity_field,elastic_force,damping_force, \
|
||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||
default: leapfrog_step(n,x,y,z,vx,vy,vz,masses,fixed,Gx,Gy,Gz,Bx,By,Bz, \
|
||
gravity_field,elastic_force,damping_force, \
|
||
n_bonds,bond_pairs,bond_k,bond_r0,dt); break; \
|
||
} \
|
||
apply_boundary_and_constraints(n,x,y,z,vx,vy,vz,fixed,pos_init,box_a); \
|
||
} while(0)
|
||
|
||
/* 蛙跳法:初始化 v(-dt/2) */
|
||
if (method_id == 3) {
|
||
std::vector<double> ibuf(n * 3);
|
||
double *ax0=ibuf.data(), *ay0=ax0+n, *az0=ay0+n;
|
||
accel_conservative(n, x, y, z, masses, Gx, Gy, Gz,
|
||
gravity_field, elastic_force,
|
||
n_bonds, bond_pairs, bond_k, bond_r0,
|
||
ax0, ay0, az0);
|
||
for (int i = 0; i < n; i++) {
|
||
if (fixed[i*3] && fixed[i*3+1] && fixed[i*3+2]) continue;
|
||
vx[i] -= 0.5*ax0[i]*dt;
|
||
vy[i] -= 0.5*ay0[i]*dt;
|
||
vz[i] -= 0.5*az0[i]*dt;
|
||
}
|
||
}
|
||
|
||
/* 初始驱动 t=0 */
|
||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, 0.0, 0, dt, drv);
|
||
|
||
/* 预热 */
|
||
for (int s = 0; s < warmup_steps; s++) {
|
||
double tw = (s + 1) * dt;
|
||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, tw, s, dt, drv);
|
||
DO_STEP();
|
||
}
|
||
|
||
/* 记录循环 */
|
||
int record_steps = NT - warmup_steps;
|
||
int prog_interval = std::max(1, record_steps / 100);
|
||
int frame_idx = 0;
|
||
|
||
for (int s = 0; s < record_steps; s++) {
|
||
if (progress_cb && s % prog_interval == 0 && s > 0)
|
||
progress_cb(s, record_steps);
|
||
|
||
double t = (s + warmup_steps) * dt;
|
||
if (n_drivers > 0) apply_driving(n, x, y, z, vx, vy, vz, t, s, dt, drv);
|
||
|
||
if (s % NSTEP == 0 && frame_idx < n_frames) {
|
||
int base = frame_idx * n;
|
||
for (int i = 0; i < n; i++) {
|
||
out_x [base+i] = x[i]; out_y [base+i] = y[i]; out_z [base+i] = z[i];
|
||
out_vx[base+i] = vx[i]; out_vy[base+i] = vy[i]; out_vz[base+i] = vz[i];
|
||
}
|
||
frame_idx++;
|
||
}
|
||
DO_STEP();
|
||
}
|
||
|
||
#undef DO_STEP
|
||
return 0;
|
||
}
|