import { describe, expect, it } from "vitest"; import { buildPlanBundle, getMethod, parseFdPlan } from "../src/index"; import { runner, solve, testSpec, maxAbs } from "./helpers"; describe("finite differences", () => { it("plans a time step at the stability bound", async () => { const spec = testSpec(); const run = await runner.run({ bundle: buildPlanBundle(spec, getMethod("fd"))! }); expect(run.ok).toBe(true); if (!run.ok) return; const plan = parseFdPlan(run.value); const { Lx, Ly, h } = spec.plate; const { E, nu, rho } = spec.material; const kappa = Math.sqrt((E * h ** 3) / (12 * (1 - nu ** 2)) / (rho * h)); const dx = Lx / (spec.fd.nx - 1); const dy = Ly / (spec.fd.ny - 1); const lamMax = 4 / dx ** 2 + 4 / dy ** 2; const s1 = spec.damping.sigma1; const dtMax = 2 / (lamMax * (s1 + Math.sqrt(kappa ** 2 + s1 ** 2))); expect(plan.dt_max).toBeCloseTo(dtMax, 12); expect(plan.dt).toBeCloseTo(spec.fd.cfl * dtMax, 12); expect(plan.cell_steps).toBe(plan.ncells * plan.nsteps); }); it("is stable just below the CFL limit and diverges just above it", async () => { const spec = testSpec(); spec.fd.cfl = 0.98; const stable = await solve(spec, "fd"); expect(stable.result.info.stable).toBe(true); expect(stable.result.probe.w.every(Number.isFinite)).toBe(true); spec.fd.cfl = 1.03; const blown = await solve(spec, "fd"); expect(blown.result.info.stable).toBe(false); expect(blown.result.info.diverged_step).toBeGreaterThan(0); expect(blown.result.info.diverged_step).toBeLessThanOrEqual(blown.result.info.nsteps); // everything returned is still finite, so the interface can show the blow-up expect(blown.result.probe.w.every(Number.isFinite)).toBe(true); expect(blown.result.frames.w.data.every(Number.isFinite)).toBe(true); expect(blown.result.info.notes.some((n) => /diverged/.test(n))).toBe(true); }); it("supports clamped edges and gives a stiffer response than simply supported", async () => { const ss = testSpec(); const cl = testSpec(); cl.bc = "clamped"; const a = await solve(ss, "fd"); const b = await solve(cl, "fd"); expect(b.result.info.stable).toBe(true); expect(maxAbs(b.result.probe.w)).toBeLessThan(maxAbs(a.result.probe.w)); }); it("reports its problem size and a short audio clip", async () => { const spec = testSpec(); const { result } = await solve(spec, "fd"); expect(result.info.ncells).toBe(spec.fd.nx * spec.fd.ny); expect(result.info.extra.cell_steps).toBe(result.info.ncells * result.info.nsteps); const expectedAudio = Math.ceil(result.t.length * result.info.dt * spec.output.fs); expect(Math.abs(result.audio.p.length - expectedAudio)).toBeLessThanOrEqual(1); expect(result.audio.p.every(Number.isFinite)).toBe(true); }); });