import test from "node:test"; import assert from "node:assert/strict"; import { CymbalProfile } from "../src/model/geometry.ts"; import { diskMesh } from "../src/model/mesh.ts"; import { buildShell } from "../src/model/shell.ts"; import { shellChecks } from "../src/model/forces.ts"; import { buildPatch, freeContactTime, maxCompression } from "../src/model/exciter.ts"; import { fixture, maxAbs, maxAbsDiff } from "./helpers.ts"; const cfg = fixture.config; const profile = new CymbalProfile(cfg.profile, cfg.imperfections); const mesh = diskMesh(cfg.profile.radius, cfg.mesh.element_size, cfg.mesh.smoothing); const shell = buildShell(mesh, profile, cfg.material, cfg.numerics); test("dent-free shell matches the Python assembly", () => { const f = fixture.shell; const rel = (a: number, b: number) => Math.abs(a - b) / Math.abs(b); assert.ok(rel(shell.mass, f.mass) < 1e-10); assert.ok(rel(shell.omegaMax, f.omega_max) < 1e-8, `omega ${shell.omegaMax} vs ${f.omega_max}`); assert.equal(shell.limitingElement, f.limiting_element); assert.ok(rel(shell.maxSlope, f.max_slope) < 1e-10); assert.ok(rel(Math.min(...shell.hE), f.h_e_min) < 1e-10 && rel(Math.max(...shell.hE), f.h_e_max) < 1e-10); assert.ok(rel(Math.max(...shell.z0Node), f.z0_max) < 1e-10); assert.ok(maxAbsDiff(shell.z0Node.subarray(0, 5), f.z0_node_first5) < 1e-14); assert.ok(maxAbsDiff(shell.Ke.subarray(0, 81), f.Ke0) < 1e-8 * maxAbs(f.Ke0)); assert.ok(rel(shell.De[0], f.D0) < 1e-12 && maxAbsDiff(shell.Cm.subarray(0, 3), f.Cm0) < 1e-6); assert.ok(maxAbsDiff(shell.M.subarray(0, 15), f.M_first3.flat()) < 1e-12); const N = mesh.n; assert.ok(maxAbsDiff(shell.Rm.subarray(4 * 5 * N, 4 * 5 * N + 15), f.Rm4_first3.flat()) < 1e-8); assert.deepEqual(shell.zeroModes, [6, 6]); }); test("element and rigid-mode checks", () => { const c = shellChecks(shell); assert.ok(c.rigidTiltResidual < 1e-12); assert.ok(c.constantCurvatureError < 1e-8); assert.ok(c.rigidModeRatio < 1e-8, `rigid ratio ${c.rigidModeRatio}`); }); test("contact patch matches Python", () => { const p = buildPatch(cfg.hits[0], mesh, profile); const f = fixture.patch; assert.deepEqual(Array.from(p.nodes), f.nodes.flat()); assert.ok(maxAbsDiff(p.weights, f.weights.flat()) < 1e-10); assert.ok(maxAbsDiff(p.offsets, f.offsets) < 1e-12); assert.ok(Math.abs(p.kTotal / f.k_total - 1) < 1e-12); assert.ok(Math.abs(maxCompression(p) / f.max_compression - 1) < 1e-10); assert.ok(Math.abs(freeContactTime(p) / f.free_contact_time - 1) < 1e-6); });