import { describe, expect, it } from "vitest"; import { solve, testSpec, maxAbs } from "./helpers"; describe("modal superposition", () => { it("reproduces the closed-form fundamental frequency of the simply supported rectangle", async () => { const spec = testSpec(); const { result } = await solve(spec, "modal"); const { Lx: a, Ly: b, h } = spec.plate; const { E, nu, rho } = spec.material; const D = (E * h ** 3) / (12 * (1 - nu ** 2)); const kappa = Math.sqrt(D / (rho * h)); const f11 = (kappa / (2 * Math.PI)) * ((Math.PI / a) ** 2 + (Math.PI / b) ** 2); expect(result.modes).toBeDefined(); expect(result.modes!.f_hz[0]).toBeCloseTo(f11, 6); expect(result.modes!.m[0]).toBe(1); expect(result.modes!.n[0]).toBe(1); // Modes are sorted by frequency and all below fmax const f = result.modes!.f_hz; for (let k = 1; k < f.length; k++) expect(f[k]).toBeGreaterThanOrEqual(f[k - 1]); expect(f[f.length - 1]).toBeLessThanOrEqual(spec.modal.fmax); }); it("produces a finite, decaying response with a plausible amplitude", async () => { const spec = testSpec(); const { result, run } = await solve(spec, "modal"); expect(result.method).toBe("modal"); expect(result.info.stable).toBe(true); expect(result.t.length).toBe(Math.round(spec.output.fs * spec.output.duration) + 1); const w = result.probe.w; expect(w.every(Number.isFinite)).toBe(true); // 50 N hammer on a 2 mm steel sheet: deflection of order 0.1 mm, stress of order 10 MPa const peak = maxAbs(w); expect(peak).toBeGreaterThan(1e-5); expect(peak).toBeLessThan(1e-3); const vmPeak = maxAbs(result.probe.vm); expect(vmPeak).toBeGreaterThan(1e6); expect(vmPeak).toBeLessThan(1e8); // damping: the last tenth of the record is quieter than the first tenth const n = w.length; expect(maxAbs(w.subarray(Math.floor(0.9 * n)))).toBeLessThan(maxAbs(w.subarray(0, Math.floor(0.1 * n)))); // frames and audio have the promised shapes expect(result.frames.w.nx).toBe(spec.output.grid[0]); expect(result.frames.w.ny).toBe(spec.output.grid[1]); expect(result.frames.w.nf).toBe(result.frames.t.length); expect(result.audio.p.length).toBe(result.t.length); expect(result.audio.fs).toBe(spec.output.fs); expect(run.backend.engine).toBe("numbl"); expect(run.wallMs).toBeGreaterThan(0); }); it("refuses clamped edges with a legible message", async () => { const spec = testSpec(); spec.bc = "clamped"; await expect(solve(spec, "modal")).rejects.toThrow(/simply supported/); }); it("handles harmonic forcing", async () => { const spec = testSpec(); spec.excitation = { type: "harmonic", x: 0.1, y: 0.08, force: 5, frequency: 500, radius: 0.012 }; const { result } = await solve(spec, "modal"); expect(result.probe.w.every(Number.isFinite)).toBe(true); expect(maxAbs(result.probe.w)).toBeGreaterThan(0); }); });