80c0b6dPlate dynamics lab: plates in realizable mounts under gravity, explicit finite differences on WebGPUJeremy Magland 1import type { Mount, ProblemSpec } from "../spec/types";
2import { formatNumber as n, formatString as s } from "./format";
4function mountLine(m: Mount): string {
5 switch (m.type) {
6 case "thread":
7 return `struct('type', 'thread', 'x', ${n(m.x)}, 'y', ${n(m.y)}, 'length', ${n(m.length)}, 'thread', struct('name', ${s(m.thread.name)}, 'diameter', ${n(m.thread.diameter)}, 'G', ${n(m.thread.G)})); % one thread: hole [m], length [m], thread diameter [m] and shear modulus [Pa] for its torsion`;
8 case "threads":
9 return `struct('type', 'threads', 'x1', ${n(m.x1)}, 'y1', ${n(m.y1)}, 'x2', ${n(m.x2)}, 'y2', ${n(m.y2)}, 'length', ${n(m.length)}); % two threads of one length from hooks at one height: the two holes [m]`;
10 case "pads": {
11 const pads = m.pads
12 .map((p) => `struct('x', ${n(p.x)}, 'y', ${n(p.y)}, 'rx', ${n(p.rx)}, 'ry', ${n(p.ry)})`)
13 .join(", ");
14 return `struct('type', 'pads', 'pads', [${pads}], 'k', ${n(m.k)}, 'c', ${n(m.c)}); % pads under a horizontal plate: centre and half-widths [m]; stiffness [N/m^3] and damping [N s/m^3] per area, one-sided`;
15 }
16 case "cymbal":
17 return `struct('type', 'cymbal', 'r_rod', ${n(m.r_rod)}, 'r_felt', ${n(m.r_felt)}, 'k', ${n(m.k)}, 'c', ${n(m.c)}, 'preload', ${n(m.preload)}); % stand: rod radius, felt radius [m], felt stiffness [N/m^3] and damping [N s/m^3], preload compression [m]`;
18 case "vise":
19 return `struct('type', 'vise', 'edge', ${s(m.edge)}, 'depth', ${n(m.depth)}, 'tilt', ${n(m.tilt)}); % one edge in a vise: which edge, jaw depth [m], tilt [deg] (0 hangs down, 90 horizontal, 180 stands up)`;
20 case "pinched":
21 return `struct('type', 'pinched', 'x', ${n(m.x)}, 'y', ${n(m.y)}, 'radius', ${n(m.radius)}, 'k', ${n(m.k)}, 'c', ${n(m.c)}, 'tilt', ${n(m.tilt)}); % between finger and thumb: patch centre and radius [m], grip stiffness [N/m] and damping [N s/m], tilt [deg]`;
22 }
23}
25/**
26 * Render the specification as the MATLAB `params` struct. The controls edit the
27 * spec; this block is what the solver actually reads, so the two cannot drift.
28 */
29export function generateParamsBlock(spec: ProblemSpec): string {
30 const e = spec.excitation;
31 const excitation =
32 e.type === "impulse"
33 ? `struct('type', 'impulse', 'x', ${n(e.x)}, 'y', ${n(e.y)}, 'force', ${n(e.force)}, 'duration', ${n(e.duration)}, 'radius', ${n(e.radius)}); % half-sine hammer pulse: strike point [m], peak force [N], contact time [s], footprint radius [m]`
34 : `struct('type', 'release', 'x', ${n(e.x)}, 'y', ${n(e.y)}, 'displacement', ${n(e.displacement)}, 'radius', ${n(e.radius)}); % pull aside and let go: pull point [m], displacement there at release [m], footprint radius [m]`;
35 const lines = [
36 "% Problem specification, generated from the controls. SI units: m, s, N, Pa, kg/m^3.",
37 "% Edit anything here or in the library files; the controls regenerate only this block.",
38 "params = struct();",
39 `params.plate = struct('shape', ${s(spec.plate.shape)}, 'Lx', ${n(spec.plate.Lx)}, 'Ly', ${n(spec.plate.Ly)}, 'h', ${n(spec.plate.h)}); % 'rectangle' or 'ellipse' in the box Lx x Ly, thickness h [m]`,
40 `params.material = struct('name', ${s(spec.material.name)}, 'E', ${n(spec.material.E)}, 'nu', ${n(spec.material.nu)}, 'rho', ${n(spec.material.rho)}); % Young's modulus [Pa], Poisson's ratio, density [kg/m^3]`,
41 `params.gravity = struct('g', ${n(spec.gravity.g)}); % [m/s^2]; 0 switches gravity off`,
42 `params.mount = ${mountLine(spec.mount)}`,
43 `params.damping = struct('sigma0', ${n(spec.damping.sigma0)}, 'sigma1', ${n(spec.damping.sigma1)}); % loss rates: frequency-independent [1/s], frequency-dependent [m^2/s]`,
44 `params.excitation = ${excitation}`,
45 `params.probe = struct('x', ${n(spec.probe.x)}, 'y', ${n(spec.probe.y)}); % where the time series are recorded [m]`,
46 `params.listener = struct('x', ${n(spec.listener.x)}, 'y', ${n(spec.listener.y)}, 'z', ${n(spec.listener.z)}, 'c', ${n(spec.listener.c)}, 'rho_air', ${n(spec.listener.rho_air)}, 'model', ${s(spec.listener.model)}); % microphone in plate coordinates [m], sound speed [m/s], air density [kg/m^3], 'baffled' or 'unbaffled'`,
47 `params.output = struct('fs', ${n(spec.output.fs)}, 'nframes', ${n(spec.output.nframes)}); % sample rate of the series and the audio [Hz], animation frames`,
48 `params.fd = struct('nx', ${n(spec.fd.nx)}, 'ny', ${n(spec.fd.ny)}, 'cfl', ${n(spec.fd.cfl)}, 'duration', ${n(spec.fd.duration)}); % grid nodes, fraction of the stable time step (> 1 blows up), simulated time [s]`,
49 ];
50 return lines.join("\n");
51}