/ proof-of-concept / plate-mechanics-sim
Sign in
proof-of-concept / plate-mechanics-sim
plate-mechanics-sim / packages / core / src / spec / validate.ts
66 lines · 3.4 KBBlameHistoryRaw
1import { onPlate, type ProblemSpec } from "./types";
3export interface ValidationIssue {
4 /** Dotted path into the spec, e.g. "plate.h" */
5 path: string;
6 message: string;
7 severity: "error" | "warning";
8}
10/**
11 * Structural checks a control panel can show before running anything. The
12 * `.m` code repeats the checks that matter for the numerics, so a spec that
13 * passes here can still be rejected by the solver with a legible message.
14 */
15export function validateSpec(spec: ProblemSpec): ValidationIssue[] {
16 const out: ValidationIssue[] = [];
17 const err = (path: string, message: string) => out.push({ path, message, severity: "error" });
18 const warn = (path: string, message: string) => out.push({ path, message, severity: "warning" });
20 const { plate, material, excitation, probe, output, fd, modal, damping, implicit } = spec;
21 if (!(plate.Lx > 0)) err("plate.Lx", "must be positive");
22 if (!(plate.Ly > 0)) err("plate.Ly", "must be positive");
23 if (!(plate.h > 0)) err("plate.h", "must be positive");
24 if (plate.h > 0.1 * Math.min(plate.Lx, plate.Ly))
25 warn("plate.h", "thickness above a tenth of the shorter side: thin-plate theory is unreliable");
26 if (!(material.E > 0)) err("material.E", "must be positive");
27 if (!(material.nu >= 0 && material.nu < 0.5)) err("material.nu", "must lie in [0, 0.5)");
28 if (!(material.rho > 0)) err("material.rho", "must be positive");
29 if (damping.sigma0 < 0 || damping.sigma1 < 0) err("damping", "loss rates cannot be negative");
31 const where = plate.shape === "ellipse" ? "outside the ellipse" : "outside the plate";
32 if (!onPlate(plate, excitation.x, excitation.y)) err("excitation", `strike point lies ${where}`);
33 if (!onPlate(plate, probe.x, probe.y)) err("probe", `probe lies ${where}`);
34 if (!(excitation.radius > 0)) err("excitation.radius", "footprint radius must be positive");
35 if (excitation.type === "impulse" && !(excitation.duration > 0))
36 err("excitation.duration", "contact time must be positive");
37 if (excitation.type === "harmonic" && !(excitation.frequency > 0))
38 err("excitation.frequency", "drive frequency must be positive");
40 if (!(output.fs > 0)) err("output.fs", "sample rate must be positive");
41 if (!(output.duration > 0)) err("output.duration", "duration must be positive");
42 if (output.nframes < 2) err("output.nframes", "need at least 2 frames");
43 if (modal.fmax > output.fs / 2)
44 warn("modal.fmax", "modes above half the sample rate alias in the probe series and audio");
45 if (fd.nx < 5 || fd.ny < 5) err("fd", "finite-difference grid needs at least 5 nodes per side");
46 if (fd.cfl > 1)
47 warn("fd.cfl", "above the stability limit: the finite-difference run will blow up");
48 if (implicit.nx < 5 || implicit.ny < 5)
49 err("implicit", "implicit grid needs at least 5 nodes per side");
50 if (!(implicit.steps_per_sample > 0)) err("implicit.steps_per_sample", "must be positive");
51 if (implicit.theta < 0.25)
52 warn("implicit.theta", "below 1/4 the implicit scheme is only conditionally stable");
53 if (implicit.steps_per_sample < 1)
54 warn(
55 "implicit.steps_per_sample",
56 "fewer than one step per sample detunes modes near half the sample rate by tens of percent",
57 );
58 const dxFd = plate.Lx / (fd.nx - 1);
59 const dyFd = plate.Ly / (fd.ny - 1);
60 if (excitation.radius < Math.max(dxFd, dyFd))
61 warn(
62 "excitation.radius",
63 "footprint smaller than a finite-difference cell: the load lands on one node",
64 );
65 return out;
↑↓ move↵openescclose