1e29264cymsimweb: WebGPU companion of cymsim with YAML/form editor, live spectrogram and playbackJeremy Magland 1// Everything the time loop needs, built once from a configuration: profile, mesh, shell, time
2// step, hit schedule and radiation, plus a human-readable summary of the set-up.
3import type { SimulationConfig } from "../config/schema.ts";
4import { buildPatch, buildSchedule, freeContactTime, patchEnergy, type ContactPatch, type HitSchedule } from "./exciter.ts";
5import { CymbalProfile } from "./geometry.ts";
6import { diskMesh, type Mesh } from "./mesh.ts";
7import { buildRadiation, type Radiation } from "./radiation.ts";
8import { buildShell, type Shell } from "./shell.ts";
10export interface Setup {
11 config: SimulationConfig; profile: CymbalProfile; mesh: Mesh; shell: Shell;
12 fs: number; dt: number; substeps: number; nSamples: number; nSteps: number;
13 patches: ContactPatch[]; schedule: HitSchedule; radiation: Radiation;
14 summary: SetupSummary;
15}
17export interface SetupSummary {
18 nodes: number; elements: number; dofs: number; mass: number; height: number; rimWarp: number; dents: number;
19 hMin: number; hMax: number; maxSlope: number; slopeWarning: boolean; omegaMax: number; limitingR: number; limitingH: number;
c24896fTime step from the global stability bound, as in cymsim; Resume falls back to the element bound for older runs; accept output.probesJeremy Magland 20 omegaGlobal: number; omegaGlobalIterations: number; omegaGlobalConverged: boolean; boundUsed: "global" | "element";
1e29264cymsimweb: WebGPU companion of cymsim with YAML/form editor, live spectrogram and playbackJeremy Magland 21 dt: number; substeps: number; nSteps: number; setupSeconds: number;
22 hits: { time: number; kind: string; points: number; r: number; kTotal: number; alpha: number; mass: number; velocity: number;
23 energy: number; freeContact: number; directBelow: number; late: boolean }[];
24}
26/** Build the set-up; ``progress`` is awaited between phases so a UI can repaint. */
27export async function buildSetup(config: SimulationConfig, progress?: (phase: string) => Promise<void> | void): Promise<Setup> {
28 const t0 = performance.now();
29 const step = async (s: string) => { if (progress) await progress(s); };
30 await step("profile and mesh");
31 const profile = new CymbalProfile(config.profile, config.imperfections);
32 const mesh = diskMesh(config.profile.radius, config.mesh.element_size, config.mesh.smoothing);
33 await step(`shell assembly (${mesh.n} nodes, ${mesh.e} elements)`);
34 const shell = buildShell(mesh, profile, config.material, config.numerics);
35 const num = config.numerics, fs = num.fs;
36 const substeps = Math.ceil(1 / (fs * num.cfl_safety * shell.dtStable));
37 const dt = 1 / (fs * substeps);
38 const nSamples = Math.ceil(num.duration * fs);
39 const nSteps = nSamples * substeps;
40 await step("hits and radiation");
41 const patches = config.hits.map((h) => buildPatch(h, mesh, profile));
42 const schedule = buildSchedule(patches, dt);
43 const radiation = buildRadiation(mesh, profile, shell.z0Node, config.listener, fs, substeps);
44 let zMax = -Infinity, rimMin = Infinity, rimMax = -Infinity, hMin = Infinity, hMax = -Infinity;
45 for (const z of shell.z0Node) zMax = Math.max(zMax, z);
46 for (const b of mesh.boundary) { rimMin = Math.min(rimMin, shell.z0Node[b]); rimMax = Math.max(rimMax, shell.z0Node[b]); }
47 for (const h of shell.hE) { hMin = Math.min(hMin, h); hMax = Math.max(hMax, h); }
48 const le = shell.limitingElement;
49 const summary: SetupSummary = {
50 nodes: mesh.n, elements: mesh.e, dofs: 5 * mesh.n, mass: shell.mass, height: zMax, rimWarp: rimMax - rimMin,
51 dents: profile.dentX.length, hMin, hMax, maxSlope: shell.maxSlope, slopeWarning: shell.maxSlope > config.profile.max_slope,
c24896fTime step from the global stability bound, as in cymsim; Resume falls back to the element bound for older runs; accept output.probesJeremy Magland 52 omegaMax: shell.omegaMax, omegaGlobal: shell.omegaGlobal, omegaGlobalIterations: shell.omegaGlobalIterations, omegaGlobalConverged: shell.omegaGlobalConverged,
53 boundUsed: shell.omegaStep < shell.omegaMax ? "global" : "element", limitingR: Math.hypot(mesh.centroid[2 * le], mesh.centroid[2 * le + 1]), limitingH: shell.hE[le],
1e29264cymsimweb: WebGPU companion of cymsim with YAML/form editor, live spectrogram and playbackJeremy Magland 54 dt, substeps, nSteps, setupSeconds: 0,
55 hits: patches.map((p) => {
56 const tau = freeContactTime(p);
57 return { time: p.time, kind: p.hit.exciter.kind, points: p.x.length, r: p.hit.r, kTotal: p.kTotal, alpha: p.alpha, mass: p.mass,
58 velocity: p.velocity, energy: patchEnergy(p), freeContact: tau, directBelow: 0.5 / tau, late: p.time >= num.duration };
59 }),
60 };
61 summary.setupSeconds = (performance.now() - t0) / 1000;
62 return { config, profile, mesh, shell, fs, dt, substeps, nSamples, nSteps, patches, schedule, radiation, summary };
63}