structure-test.js
58 lines · in natura-src-1.0.0.zip as natura/structure-test.js · a test
// structure-test.js -- assertions for STRUCTURE (Euler buckling, load-sharing, cascade failure).
// Run: node structure-test.js
const N = require('./natura.js');
const { makeStructure } = require('./structure.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 at = (cfg) => World({ seed: 1, systems: [makeStructure(cfg)] }).fields().structure;
console.log('structure assertions:');
// 1. DETERMINISM
{
const a = at({ N: 10, spread: 0.3, load: 5 }), b = at({ N: 10, spread: 0.3, load: 5 });
ok(a.standing === b.standing && a.maxDeflect === b.maxDeflect, 'same config -> identical result (reproducible)');
}
// 2. THE BUCKLING BIFURCATION -- below the critical load a column is STRAIGHT; above it BOWS, deflection ~ sqrt(overshoot)
{
const m = { members: [{ L: 1, section: 1 }], stiffness: 1, failMargin: 3 }; // pcrit = 1
const straight = at(Object.assign({ load: 0.5 }, m)), bowed = at(Object.assign({ load: 1.5 }, m)), more = at(Object.assign({ load: 2.5 }, m));
ok(straight.maxDeflect === 0, 'below Pcrit the column stays STRAIGHT (deflect 0)');
ok(bowed.maxDeflect > 0, 'past Pcrit it BUCKLES (deflect ' + bowed.maxDeflect + ')');
ok(more.maxDeflect > bowed.maxDeflect, 'deflection grows with the overshoot (the pitchfork: ' + bowed.maxDeflect + ' -> ' + more.maxDeflect + ')');
}
// 3. Pcrit ~ 1/L^2 -- a LONGER member buckles first; the short one still stands after it sheds its load
{
const f = at({ members: [{ L: 1, section: 1 }, { L: 2, section: 1 }], stiffness: 1, failMargin: 1.0, load: 0.6 });
ok(f.standing === 1 && f.members[1].failed && !f.members[0].failed, 'the long slender member buckles first, the stout one holds (Pcrit ~ 1/L^2)');
}
// 4. BRACING buys capacity -- halving the effective length quadruples Pcrit
{
const braced = at({ members: [{ L: 1, section: 1 }], stiffness: 1, braceFactor: 0.5, failMargin: 1.6, load: 3 });
const bare = at({ members: [{ L: 1, section: 1 }], stiffness: 1, braceFactor: 1.0, failMargin: 1.6, load: 3 });
ok(!braced.collapsed && bare.collapsed, 'a BRACED member carries a load that buckles the unbraced one (4x Pcrit)');
}
// 5. CASCADE COLLAPSE -- a bundle stands under a light load, then a heavy load cascades it to total collapse
{
const light = at({ N: 10, spread: 0.3, stiffness: 1, load: 3 }); // ~0.3 each, all under Pcrit~1
const heavy = at({ N: 10, spread: 0.3, stiffness: 1, load: 14 }); // over capacity -> progressive collapse
ok(light.standing === 10, 'a light load: every member stands (' + light.standing + '/10)');
ok(heavy.collapsed, 'a heavy load CASCADES to total collapse as failures shed load onto the rest (' + heavy.standing + '/10 left)');
}
// 6. LOAD RAMP -- as the load grows over time the structure holds, then fails progressively (a threshold in time)
{
const w = World({ seed: 1, systems: [makeStructure({ N: 12, spread: 0.35, stiffness: 1, load: 2, loadRate: 0.5 })] });
const s0 = w.fields().structure.standing;
let collapsed = false; for (let i = 0; i < 120 && !collapsed; i++) { w.step(2 * DAY); collapsed = w.fields().structure.collapsed; }
ok(s0 === 12 && collapsed, 'under a growing load the structure holds, then fails progressively to collapse (12 -> 0)');
}
console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail ? 1 : 0);