1import type { ProblemSpec } from "../spec/types";
2import { formatNumber as n, formatString as s, formatVector as vec } from "./format";
4/**
5 * Render the specification as the MATLAB `params` struct. The controls edit the
6 * spec; this block is what the solver actually reads, so the two cannot drift.
7 */
8export function generateParamsBlock(spec: ProblemSpec): string {
9 const e = spec.excitation;
10 const excitation =
11 e.type === "impulse"
12 ? `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]`
13 : `struct('type', 'harmonic', 'x', ${n(e.x)}, 'y', ${n(e.y)}, 'force', ${n(e.force)}, 'frequency', ${n(e.frequency)}, 'radius', ${n(e.radius)}); % sinusoidal drive: point [m], amplitude [N], frequency [Hz], footprint radius [m]`;
14 const lines = [
15 "% Problem specification, generated from the controls. SI units: m, s, N, Pa, kg/m^3.",
16 "% Edit anything here or in the library files; the controls regenerate only this block.",
17 "params = struct();",
18 `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]`,
19 `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]`,
20 `params.bc = ${s(spec.bc)}; % 'simply-supported', 'clamped', 'free' or 'pinned-center' (modal: simply-supported only)`,
21 `params.damping = struct('sigma0', ${n(spec.damping.sigma0)}, 'sigma1', ${n(spec.damping.sigma1)}); % loss rates: frequency-independent [1/s], frequency-dependent [m^2/s]`,
22 `params.excitation = ${excitation}`,
23 `params.probe = struct('x', ${n(spec.probe.x)}, 'y', ${n(spec.probe.y)}); % where the time series are recorded [m]`,
24 `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)}, 'patches', ${vec(spec.listener.patches)}); % microphone position [m], sound speed [m/s], air density [kg/m^3], Rayleigh patch grid`,
25 `params.output = struct('fs', ${n(spec.output.fs)}, 'duration', ${n(spec.output.duration)}, 'nframes', ${n(spec.output.nframes)}, 'grid', ${vec(spec.output.grid)}); % sample rate [Hz], simulated time [s], animation frames, display grid [nx ny]`,
26 `params.modal = struct('fmax', ${n(spec.modal.fmax)}, 'max_modes', ${n(spec.modal.max_modes)}); % keep modes below fmax [Hz], at most max_modes`,
27 `params.fd = struct('nx', ${n(spec.fd.nx)}, 'ny', ${n(spec.fd.ny)}, 'cfl', ${n(spec.fd.cfl)}, 'duration', ${n(spec.fd.duration)}); % explicit: grid nodes, fraction of the stable time step (> 1 blows up), simulated time [s]`,
28 `params.implicit = struct('nx', ${n(spec.implicit.nx)}, 'ny', ${n(spec.implicit.ny)}, 'duration', ${n(spec.implicit.duration)}, 'steps_per_sample', ${n(spec.implicit.steps_per_sample)}, 'theta', ${n(spec.implicit.theta)}); % implicit: grid nodes, simulated time [s], steps per output sample, theta (1/4 = trapezoidal)`,
29 ];
30 return lines.join("\n");
31}