1// Set-up summary, energy and hit tables, and a short log.
2import type { SetupSummary } from "../model/setup.ts";
3import type { Diagnostics } from "../model/simulator.ts";
5const f = (x: number, d = 3) => (Number.isFinite(x) ? x.toFixed(d) : "-");
6const e = (x: number) => (Number.isFinite(x) ? x.toExponential(2) : "-");
8export function formatSummary(s: SetupSummary): string {
9 const lines = [
10 `mesh: ${s.nodes} nodes, ${s.elements} triangles, ${s.dofs} DOFs`,
11 `profile: height ${f(s.height * 1e3, 1)} mm, rim warp p-p ${f(s.rimWarp * 1e3, 2)} mm, ${s.dents} dents, h in [${f(s.hMin * 1e3, 2)}, ${f(s.hMax * 1e3, 2)}] mm, max |grad z0| = ${f(s.maxSlope)}${s.slopeWarning ? " (above the shallow-shell bound)" : ""}, mass ${f(s.mass)} kg`,
12 `omega_max bound ${e(s.omegaMax)} rad/s (limiting element at r = ${f(s.limitingR)} m, h = ${f(s.limitingH * 1e3, 2)} mm)`,
13 `dt = ${e(s.dt)} s, ${s.substeps} sub-steps per sample, ${s.nSteps} steps`,
14 ];
15 s.hits.forEach((h, k) => lines.push(
16 `hit ${k} at t = ${f(h.time)} s: ${h.kind}, ${h.points}-point patch at r = ${f(h.r)} m, k = ${e(h.kTotal)} N/m^${f(h.alpha, 2)}, m = ${f(h.mass)} kg, v = ${f(h.velocity, 1)} m/s, energy ${f(h.energy, 2)} J, free contact ~ ${f(h.freeContact * 1e3, 2)} ms (direct excitation below ~${f(h.directBelow, 0)} Hz)${h.late ? " (after the end of the run)" : ""}`));
17 lines.push(`set-up took ${f(s.setupSeconds, 1)} s`);
18 return lines.join("\n");
19}
21export class Readouts {
22 private readonly root: HTMLElement;
23 private readonly energies: HTMLTableElement; private readonly hits: HTMLTableElement; private readonly logEl: HTMLDivElement;
24 private lines: string[] = [];
26 constructor(root: HTMLElement) {
27 this.root = root;
28 this.energies = document.createElement("table"); this.hits = document.createElement("table");
29 this.logEl = document.createElement("div"); this.logEl.className = "log";
30 root.append(this.energies, this.hits, this.logEl);
31 this.energies.innerHTML = "<tr><th>t [s]</th><th>kinetic [J]</th><th>membrane [J]</th><th>bending [J]</th><th>exciters [J]</th><th>total [J]</th><th>max|w|/h</th></tr><tr><td colspan=7 class='muted'>no run yet</td></tr>";
32 }
34 setDiagnostics(d: Diagnostics, kinds: string[]) {
35 const tot = d.kinetic + d.membrane + d.bending + d.exciters;
36 this.energies.innerHTML = `<tr><th>t [s]</th><th>kinetic [J]</th><th>membrane [J]</th><th>bending [J]</th><th>exciters [J]</th><th>total [J]</th><th>max|w|/h</th></tr>` +
37 `<tr><td>${f(d.time)}</td><td>${e(d.kinetic)}</td><td>${e(d.membrane)}</td><td>${e(d.bending)}</td><td>${e(d.exciters)}</td><td>${e(tot)}</td><td>${f(d.maxWoverH, 2)}</td></tr>`;
38 this.hits.innerHTML = `<tr><th>hit</th><th>peak force [N]</th><th>contact [ms]</th><th>exciter velocity [m/s]</th></tr>` +
39 d.hits.map((h, k) => `<tr><td>${k} (${kinds[k]})</td><td>${f(h.peakForce, 1)}</td><td>${f(h.contactTime * 1e3, 2)}</td><td>${f(h.velocity, 2)}</td></tr>`).join("");
40 }
42 log(line: string) {
43 this.lines.push(line);
44 if (this.lines.length > 8) this.lines.shift();
45 this.logEl.textContent = this.lines.join("\n");
46 this.root.scrollTop = this.root.scrollHeight;
47 }
49 clearLog() { this.lines = []; this.logEl.textContent = ""; }
50}