vortex-test.js
57 lines · in natura-src-1.0.0.zip as natura/vortex-test.js · a test
// vortex-test.js -- assertions for the VORTEX-BLOB fluid (pair self-propulsion, mutual orbit, conserved
// circulation, rotational field). Run: node vortex-test.js
const N = require('./natura.js');
const { makeVortex } = require('./vortex.js');
const { World, UNITS } = N;
const DAY = UNITS.DAY;
let pass = 0, fail = 0;
const ok = (c, m) => { if (c) pass++; else { fail++; console.log(' FAIL: ' + m); } };
const build = (cfg) => World({ seed: 1, systems: [makeVortex(cfg)] });
const run = (w, days) => { for (let i = 0; i < days; i++) w.step(DAY); };
console.log('vortex assertions:');
// 1. DETERMINISM
{
const a = build({}), b = build({}); run(a, 30); run(b, 30);
const va = a.fields().vortex.vortices, vb = b.fields().vortex.vortices;
ok(Math.abs(va[0].x - vb[0].x) < 1e-12 && Math.abs(va[0].y - vb[0].y) < 1e-12, 'same config -> identical evolution (reproducible)');
}
// 2. A COUNTER-ROTATING PAIR SELF-PROPELS -- a dipole travels in a straight line (the smoke-ring cross-section)
{
const w = build({ vortices: [{ x: 0, y: 0.5, gamma: 1 }, { x: 0, y: -0.5, gamma: -1 }] });
const c0 = w.fields().vortex.centroid.slice(); run(w, 30); const c1 = w.fields().vortex.centroid;
const moved = Math.hypot(c1[0] - c0[0], c1[1] - c0[1]);
ok(moved > 0.3 && Math.abs(c1[1] - c0[1]) < 0.05, 'a counter-rotating pair SELF-PROPELS in a straight line (travelled ' + moved.toFixed(2) + ')');
}
// 3. LIKE-SIGNED VORTICES ORBIT each other about a fixed centroid
{
const w = build({ vortices: [{ x: 0.5, y: 0, gamma: 1 }, { x: -0.5, y: 0, gamma: 1 }] });
const a0 = Math.atan2(w.fields().vortex.vortices[0].y, w.fields().vortex.vortices[0].x);
const c0 = w.fields().vortex.centroid.slice();
run(w, 3); // ~0.95 rad -- mid-orbit (a full revolution is ~20 days, which would wrap back to ~0)
const v0 = w.fields().vortex.vortices[0], a1 = Math.atan2(v0.y, v0.x), c1 = w.fields().vortex.centroid;
const sep = Math.hypot(v0.x - w.fields().vortex.vortices[1].x, v0.y - w.fields().vortex.vortices[1].y);
ok(Math.abs(a1 - a0) > 0.4 && Math.hypot(c1[0] - c0[0], c1[1] - c0[1]) < 0.05 && Math.abs(sep - 1.0) < 0.15,
'like-signed vortices ORBIT a fixed centroid at ~constant separation (angle turned ' + Math.abs(a1 - a0).toFixed(2) + ' rad)');
}
// 4. CIRCULATION IS CONSERVED -- the total spin never changes (an invariant of the ideal fluid)
{
const w = build({ vortices: [{ x: 0, y: 0.5, gamma: 1.3 }, { x: 0, y: -0.5, gamma: -0.7 }] });
const g0 = w.fields().vortex.circulation; run(w, 40); const g1 = w.fields().vortex.circulation;
ok(Math.abs(g1 - g0) < 1e-6, 'total circulation is conserved (' + g0 + ' -> ' + g1 + ')');
}
// 5. THE INDUCED FIELD IS ROTATIONAL -- around a single vortex the flow runs TANGENTIALLY (perpendicular to the radius)
{
const w = build({ vortices: [{ x: 0, y: 0, gamma: 1 }] });
const f = w.fields().vortex, uv = f.sampleAt(1, 0); // sample due east of a CCW vortex -> velocity should point +y
const radial = uv[0] * 1 + uv[1] * 0, tang = uv[1]; // radial component (along +x) vs tangential (+y)
ok(Math.abs(radial) < 1e-9 && tang > 0, 'the induced flow is purely TANGENTIAL around a vortex (rotational, not radial)');
}
console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail ? 1 : 0);