sync-test.js
62 lines · in natura-src-1.0.0.zip as natura/sync-test.js · a test
// sync-test.js -- assertions for SYNCHRONIZATION (the Kuramoto model). Run: node sync-test.js
const N = require('./natura.js');
const { makeSync } = require('./sync.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: 6, systems: [makeSync(Object.assign({ N: 200, seed: 6, meanOmega: 6, freqSpread: 1.0 }, cfg))] });
const run = (w, days) => { for (let i = 0; i < days / 10; i++) w.step(10 * DAY); };
console.log('sync assertions:');
// 1. DETERMINISM
{
const a = build({ K: 3 }), b = build({ K: 3 }); run(a, 300); run(b, 300);
const fa = a.fields().sync.phase, fb = b.fields().sync.phase; let same = true;
for (let k = 0; k < 200; k++) if (fa[k] !== fb[k]) { same = false; break; }
ok(same, 'same seed -> identical phases (reproducible)');
}
// 2. THE SYNCHRONIZATION TRANSITION -- weak coupling stays NOISE, strong coupling LOCKS into one beat
{
const weak = build({ K: 0.2 }), strong = build({ K: 6 }); run(weak, 400); run(strong, 400);
const rw = weak.fields().sync.R, rs = strong.fields().sync.R;
ok(rw < 0.4, 'below the critical coupling the crowd stays INCOHERENT (R ' + rw + ')');
ok(rs > 0.9, 'above it the crowd spontaneously LOCKS into one rhythm (R ' + rs + ')');
ok(rs > rw + 0.5, 'coupling drives the order parameter up (R ' + rw + ' -> ' + rs + ')');
}
// 3. IT IS A THRESHOLD -- sweep K; R stays low then climbs (a phase transition, not linear)
{
let prev = 0, monotone = true, jumped = false;
for (const K of [0.2, 0.8, 1.6, 2.4, 4, 6]) { const w = build({ K }); run(w, 350); const R = w.fields().sync.R;
if (R < prev - 0.08) monotone = false; if (R - prev > 0.2) jumped = true; prev = R; }
ok(monotone && jumped, 'R rises through a critical band as K grows (a genuine synchronization threshold)');
}
// 4. IDENTICAL OSCILLATORS lock even at faint coupling; a WIDE spread of tempos resists locking
{
const same = build({ K: 1.0, freqSpread: 0 }); run(same, 400);
ok(same.fields().sync.R > 0.9, 'identical tempos synchronize even at faint coupling (R ' + same.fields().sync.R + ')');
const wide = build({ K: 1.0, freqSpread: 4.0 }); run(wide, 400);
ok(wide.fields().sync.R < 0.6, 'a wide spread of natural tempos resists the SAME coupling (R ' + wide.fields().sync.R + ')');
}
// 5. the readout states: a strong crowd reads LOCKED, a weak one INCOHERENT
{
const strong = build({ K: 6 }); run(strong, 400);
ok(strong.fields().sync.R > 0.9, 'a strongly-coupled crowd reads LOCKED (R>0.9)');
const weak = build({ K: 0.2 }); run(weak, 400);
ok(weak.fields().sync.R < 0.25, 'a weakly-coupled crowd reads INCOHERENT (R<0.25)');
}
// 6. CHIMERA -- on a ring with a phase lag near pi/2, order and chaos COEXIST (a coherent domain beside an incoherent one)
{
const w = build({ N: 256, mode: 'ring', neighbours: 40, phaseLag: 1.45, freqSpread: 0, K: 6 }); run(w, 500);
const f = w.fields().sync;
ok(f.localRmax > 0.8 && f.localRmin < 0.5, 'a CHIMERA forms: a synchronized domain (localR ' + f.localRmax + ') beside an incoherent one (localR ' + f.localRmin + ') -- from identical oscillators');
}
console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail ? 1 : 0);