// ============================================================ // test_three_body_integrator.js - 穿越三体 · 三体积分器校验 // 从 public/tools/thought_lab/labs/three_body_journey/index.html 抽取 // THREEBODY_MATH_BEGIN/END 之间的纯物理代码执行校验(测试对象 = 上线代码)。 // 校验:RK4 积分动量守恒、能量漂移极小、加速度满足牛顿第三定律对称性。 // 运行:node dev_test_scripts/unit/test_three_body_integrator.js // ============================================================ 'use strict' const fs = require('fs') const path = require('path') const vm = require('vm') const HTML = path.resolve(__dirname, '..', '..', 'public', 'tools', 'thought_lab', 'labs', 'three_body_journey', 'index.html') const src = fs.readFileSync(HTML, 'utf8') const m = src.match(/\/\/ ==== THREEBODY_MATH_BEGIN ====\n([\s\S]*?)\/\/ ==== THREEBODY_MATH_END ====/) if (!m) { console.error('FAIL - 未找到 THREEBODY_MATH 标记块'); process.exit(1) } const api = vm.runInNewContext(m[1] + ';({ tbAccel, tbStep, tbEnergy, tbMomentum })') let pass = 0, fail = 0 const check = (name, cond) => { if (cond) { pass++; console.log('PASS -', name) } else { fail++; console.log('FAIL -', name) } } const masses = [1, 1, 1] const eps2 = 0.02 let pos = [1, 0, -0.5, Math.sqrt(3) / 2, -0.5, -Math.sqrt(3) / 2] let vel = [0, 0.42, -0.38, -0.2, 0.38, -0.22] // 牛顿第三定律:Σ mᵢaᵢ = 0(作用力反作用力,任意位形) { const ms = [1, 2, 0.5] const a = api.tbAccel([0, 0, 1, 0.3, -0.7, 1.2], ms, 0) let sx = 0, sy = 0 for (let i = 0; i < 3; i++) { sx += ms[i] * a[2 * i]; sy += ms[i] * a[2 * i + 1] } check('Σ mᵢaᵢ = 0(作用反作用)', Math.abs(sx) < 1e-12 && Math.abs(sy) < 1e-12) } // 动量守恒:跑 5000 步 const p0 = api.tbMomentum(vel, masses) const E0 = api.tbEnergy(pos, vel, masses, eps2) for (let i = 0; i < 5000; i++) [pos, vel] = api.tbStep(pos, vel, masses, 0.002, eps2) const p1 = api.tbMomentum(vel, masses) const E1 = api.tbEnergy(pos, vel, masses, eps2) check('动量守恒(5000 步,|Δp|<1e-9)', Math.abs(p1[0] - p0[0]) < 1e-9 && Math.abs(p1[1] - p0[1]) < 1e-9) check(`能量漂移小(|ΔE/E0|<1e-3,实际 ${Math.abs((E1 - E0) / E0).toExponential(2)})`, Math.abs((E1 - E0) / E0) < 1e-3) // 有限性:5000 步后位置/速度均为有限数(软化避免奇点崩溃) check('积分稳定(全部有限)', [...pos, ...vel].every(Number.isFinite)) console.log(fail === 0 ? `ALL PASS (${pass})` : `FAILED: ${fail} / ${pass + fail}`) process.exit(fail === 0 ? 0 : 1)