← Natura·Docs/Modules/Source

granular-test.js

52 lines · in natura-src-1.0.0.zip as natura/granular-test.js · a test

// granular-test.js -- assertions for GRANULAR MEDIA (angle of repose, constant discharge / Beverloo, jamming).
// Run: node granular-test.js
const N = require('./natura.js');
const { makeGranular } = require('./granular.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: 3, systems: [makeGranular(Object.assign({ N: 41, H: 60, seed: 3 }, cfg))] });

console.log('granular assertions:');

// 1. DETERMINISM
{
  const a = build({ orifice: 5 }), b = build({ orifice: 5 }); a.step(DAY); b.step(DAY);
  ok(a.fields().granular.exited === b.fields().granular.exited, 'same seed -> identical flow (reproducible)');
}

// 2. CONSTANT DISCHARGE (Beverloo) -- the outflow rate holds steady as the pile drains, unlike a fluid (which
//    would slow as its head drops). Compare the rate early vs late in the drain.
{
  const w = build({ orifice: 5, fillFrac: 0.85 });
  for (let i = 0; i < 2; i++) w.step(DAY); const early = w.fields().granular.discharge;   // day 2, past the initial settle
  for (let i = 0; i < 3; i++) w.step(DAY); const late = w.fields().granular.discharge, held = w.fields().granular.count;   // day 5, bulk still draining
  ok(held > 800 && early > 0 && Math.abs(late - early) / early < 0.2, 'discharge stays ~CONSTANT through the bulk drain, though the head drops (day2 ' + early + ' -> day5 ' + late + ', still holding ' + held + ')');
}

// 3. BEVERLOO SCALING -- a WIDER orifice discharges FASTER
{
  const narrow = build({ orifice: 3 }); narrow.step(DAY);
  const wide = build({ orifice: 11 }); wide.step(DAY);
  ok(wide.fields().granular.discharge > narrow.fields().granular.discharge * 1.8, 'a wider orifice flows faster (narrow ' + narrow.fields().granular.discharge + ' vs wide ' + wide.fields().granular.discharge + ')');
}

// 4. JAMMING -- a one-cell orifice arches and chokes to a trickle (or stops) vs a wide one
{
  const jam = build({ orifice: 1 }); for (let i = 0; i < 4; i++) jam.step(DAY);
  const flow = build({ orifice: 11 }); flow.step(DAY);
  ok(jam.fields().granular.discharge < flow.fields().granular.discharge * 0.3, 'a too-narrow orifice jams to a trickle (' + jam.fields().granular.discharge + ' vs ' + flow.fields().granular.discharge + ')');
}

// 5. ANGLE OF REPOSE -- poured onto a point (no outlet), the grains form a PEAKED heap (tall at the centre,
//    sloping down to the sides), not a flat level like a fluid would
{
  const w = build({ orifice: 0, fillFrac: 0, feed: 2 }); for (let i = 0; i < 12; i++) w.step(DAY);
  const g = w.get('granular').g, GN = 41, GH = 60, colH = (x) => { let c = 0; for (let y = 0; y < GH; y++) if (g[y * GN + x]) c++; return c; };
  const centre = colH(20), left = colH(8), right = colH(32);
  ok(centre > left + 3 && centre > right + 3 && left > 0 && right > 0, 'a poured heap PEAKS at the centre and slopes to the sides -- its angle of repose (centre ' + centre + ' vs sides ' + left + '/' + right + '), not a fluid level');
}

console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail ? 1 : 0);