/ listening-for-chaos / chaos-apps
Sign in
listening-for-chaos / chaos-apps
Plate: Parameters back in the right column; true-scale cross-section under the height map showing the dome and the thickness
Jeremy Magland <jmagland@flatironinstitute.org> committed commit ecc198b66896 parent e7f0b54 Browse files
4 changed files+99−20
src/plate/README.mdmodified+2−0View file
@@ -29,3 +29,5 @@ Cost issue, fixed (2026-10-09, `../common/note-render.ts`): the 4x oversampled n
2929 Lyapunov exponent (step 6, 2026-10-09): as the beam's (two-trajectory method, `../common/lyapunov.ts`), with the plate's shadow in `lyapunov.ts`: a second plate D0 = 1e-8 away, distances with displacements in units of A/√(N²) (A the lowest mode's amplitude at which stretching equals bending, `nonlinearAmplitude`; 1 m for a linear plate) and momenta in m ω₁ times that; the initial offset is a fixed pseudo-random pattern (free: its rigid part removed). Layout now as the beam's: Parameters folded under the plate, a Real time card on the right with Lyapunov (λ 10 s and since the strike, first 5 s left out of the y range) and Energy error charts. Sound panel: Lyapunov chart and λ vs E scatter, both off at first (the shadow doubles the time to compute a note: default note 4.3 s without, about 12 s with, in the browser). Exponents (2026-10-09, N = 8, kick at (1.3, 1.1), undamped, 300 s, λ since the strike, 1/s): linear plate 0.007 at every energy (finite-time; the last 100 s, −0.001: zero); simply supported, h 5 cm: 0.008, 0.065, 0.26, 0.53, 1.01 at 0.001, 0.1, 1, 10, 100 J; free, h 5 cm: 0.006, 0.015, 0.16, 1.54, 8.4. The free plate's exponent grows much faster with energy.
3030
3131 Curvature (step 7, 2026-10-09; Parameters, rise, default 0): the plate rests, free of stress, in a shallow dome u0 of height H (held edges: H sin(πx/L) sin(πy/L), zero at the supports; free: the paraboloid H(1 − r²/c²) about the centre, a spherical cap), as the beam's curved rest shape. Marguerre shallow shell: bending unchanged (linear, from the rest shape), stretching from the rest shape, s = l(u0 + u, u0 + u) − l(u0, u0) = l(u, u) + 2 l(u0, u), the force its exact gradient (the second differences of the total shape in the scatter). The 2 l(u0, u) term adds linear stiffness, which lifts the low modes most (the cymbal effect), and a quadratic coupling. Acts only through the stretching (h > 0). With a rise the modes are computed (symmetry blocks; the rest shapes are mirror-symmetric), also simply supported, from the linearized forces (`forces(..., linear)` now includes the curvature stiffness; `linearEnergy` = bending + that quadratic part). Nonlinear scale and amplitude generalized: the lowest mode's amplitude at which the nonlinear stretching (cubic and quartic) equals the linear energy, by bisection (flat: unchanged, 0.131 J). Checks: force against central differences 2e-10 to 5e-10 (simply supported, clamped, free, H 0.1 m, N 6); linearized force against the force at 1e-7 amplitude 6e-8; free plate still invariant under rigid motion (2e-15); flat results unchanged. Continuum check (simply supported, H/h = 0.5, 1, 2, 4): the (1, 1) shape's Rayleigh quotient ω²/ω₀² is 1 + 0.836 (H/h)² in the continuum (single mode, exact stress solution, the same 0.209 coefficient as the flat check above, times 4); grid 1.2102, 1.8410, 4.3639, 14.456 at N = 32 against 1.2090, 1.8360, 4.3440, 14.376 (second order). Lowest modes, simply supported, N = 16, relative to the flat (1, 1): H = 0.05 m (= h): 1.35, 2.64, 2.64, 4.04; H = 0.1: 1.94, 3.03, 3.03, 4.24; H = 0.2: 2.90, 4.05, 4.05, 4.89 (flat: 1, 2.48, 2.48, 3.97). Energy error with a rise of 0.1 m at 100 J: 1e-3 (simply supported), 6e-4 (free).
32+
33+Layout and cross-section (2026-10-09): Parameters back in the right column (above Real time). Under the height map, a cross-section through the middle of the plate (y = side / 2, dashed line on the map) at true scale (the same metres per pixel across and up): the rest shape faint, the plate now (rest shape plus displacement) dark, each a band of the plate's thickness h, supports marked for held edges; the label gives the side, h, and the rise as multiples of h and fractions of the side.
src/plate/index.htmlmodified+22−19View file
@@ -11,6 +11,7 @@
1111 <div class="grid">
1212 <section class="card">
1313 <canvas id="plate"></canvas>
14+ <canvas id="section" title="Cross-section through the middle of the plate (dashed line above), at true scale"></canvas>
1415 <div id="controls">
1516 <div class="row">
1617 <button id="strike" class="primary">Strike</button>
@@ -50,25 +51,26 @@
5051 </select>
5152 </div>
5253 </div>
53- <details>
54- <summary>Parameters <button type="button" class="help" data-help="params" title="About the parameters" aria-label="About the parameters">?</button></summary>
55- <div id="param-form">
56- <label>m <input id="p-m" type="number" step="any" /> <span class="unit">kg</span></label>
57- <label>κ <input id="p-kappa" type="number" step="any" /> <span class="unit">N/m (bending)</span></label>
58- <label>h <input id="p-thick" type="number" step="any" /> <span class="unit">m (thickness, sets the stretching; 0 = linear)</span></label>
59- <label>rise <input id="p-rise" type="number" step="any" /> <span class="unit">m (height of a domed rest shape; 0 = flat)</span></label>
60- <label>a <input id="p-a" type="number" step="any" /> <span class="unit">m (spacing)</span></label>
61- <label>τ <input id="p-tau" type="number" step="any" /> <span class="unit">s (decay, 0 = off)</span></label>
62- <label>τ top <input id="p-tautop" type="number" step="any" /> <span class="unit">s (decay of the highest modes from the loss, 0 = off)</span></label>
63- <label>mallet M <input id="p-mallet-m" type="number" step="any" /> <span class="unit">kg</span></label>
64- <label>mallet K <input id="p-mallet-k" type="number" step="any" /> <span class="unit">N/m^1.5 (contact stiffness)</span></label>
65- </div>
66- <div id="param-actions" class="row">
67- <button id="param-apply">Apply</button>
68- <span id="param-status"></span>
69- </div>
70- <div id="params"></div>
71- </details>
54+ </section>
55+ <div class="column">
56+ <section class="card">
57+ <div class="card-head">Parameters <button type="button" class="help" data-help="params" title="About the parameters" aria-label="About the parameters">?</button></div>
58+ <div id="param-form">
59+ <label>m <input id="p-m" type="number" step="any" /> <span class="unit">kg</span></label>
60+ <label>κ <input id="p-kappa" type="number" step="any" /> <span class="unit">N/m (bending)</span></label>
61+ <label>h <input id="p-thick" type="number" step="any" /> <span class="unit">m (thickness, sets the stretching; 0 = linear)</span></label>
62+ <label>rise <input id="p-rise" type="number" step="any" /> <span class="unit">m (height of a domed rest shape; 0 = flat)</span></label>
63+ <label>a <input id="p-a" type="number" step="any" /> <span class="unit">m (spacing)</span></label>
64+ <label>τ <input id="p-tau" type="number" step="any" /> <span class="unit">s (decay, 0 = off)</span></label>
65+ <label>τ top <input id="p-tautop" type="number" step="any" /> <span class="unit">s (decay of the highest modes from the loss, 0 = off)</span></label>
66+ <label>mallet M <input id="p-mallet-m" type="number" step="any" /> <span class="unit">kg</span></label>
67+ <label>mallet K <input id="p-mallet-k" type="number" step="any" /> <span class="unit">N/m^1.5 (contact stiffness)</span></label>
68+ </div>
69+ <div id="param-actions" class="row">
70+ <button id="param-apply">Apply</button>
71+ <span id="param-status"></span>
72+ </div>
73+ <div id="params"></div>
7274 </section>
7375 <section class="card">
7476 <div class="card-head">Real time <button type="button" class="help" data-help="realtime" title="About the real-time charts" aria-label="About the real-time charts">?</button></div>
@@ -76,6 +78,7 @@
7678 <div id="traces"></div>
7779 </section>
7880 </div>
81+ </div>
7982 <details class="card panel" id="sound" open>
8083 <summary><span class="label">Sped up · listen</span> <span id="sound-clock" class="muted"></span> <button type="button" class="help" data-help="sound" title="About the sped-up run" aria-label="About the sped-up run">?</button></summary>
8184 <div class="row">
src/plate/main.tsmodified+72−1View file
@@ -2,7 +2,7 @@ import "../common/style.css";
22 import "./style.css";
33 import {
44 Modes, REF_N, Verlet, copyState, decayTimes, energy, forces, hammerShape, isDamped, newState, nodePosition, nonlinearScale, plateSide, refine,
5- masses, removeRigid, stepFor, strikeAt, valueAt, type Edges, type Params, type State,
5+ masses, removeRigid, restShape, stepFor, strikeAt, valueAt, type Edges, type Params, type State,
66 } from "./plate";
77 import { fit } from "../common/canvas";
88 import { newCursor } from "../common/cursor";
@@ -515,6 +515,15 @@ function drawPlate() {
515515 ctx.setLineDash(free ? [4, 4] : []);
516516 ctx.strokeRect(g.x0, g.y0, g.size, g.size);
517517 ctx.setLineDash([]);
518+ // Where the cross-section below is taken.
519+ ctx.strokeStyle = "rgba(0, 0, 0, 0.35)";
520+ ctx.lineWidth = 1;
521+ ctx.setLineDash([3, 4]);
522+ ctx.beginPath();
523+ ctx.moveTo(px(0), py(L / 2));
524+ ctx.lineTo(px(L), py(L / 2));
525+ ctx.stroke();
526+ ctx.setLineDash([]);
518527 // Strike point and listening point.
519528 const sx = px(coord(strikeXInput)), sy = py(coord(strikeYInput));
520529 ctx.strokeStyle = TEXT;
@@ -541,6 +550,67 @@ function drawPlate() {
541550 ctx.textAlign = "left";
542551 }
543552
553+// Cross-section through the middle of the plate (y = side / 2) at true
554+// scale, the same metres per pixel across and up, so the dome's rise can be
555+// seen against the plate's size and thickness: the rest shape (faint) and
556+// the plate now (rest shape plus displacement), each drawn as a band of the
557+// plate's thickness h (at least a hairline), the edge supports as small
558+// marks. Shapes taller than the strip are clipped.
559+const sectionCanvas = document.getElementById("section") as HTMLCanvasElement;
560+function drawSection() {
561+ const ctx = fit(sectionCanvas);
562+ const W = sectionCanvas.clientWidth, Hc = sectionCanvas.clientHeight;
563+ ctx.clearRect(0, 0, W, Hc);
564+ const g = geometry(), L = plateSide(P), sc = g.size / L; // px per m
565+ const x0 = g.x0, base = Hc - 26; // z = 0
566+ const u0 = restShape(P), y = L / 2, n = 240;
567+ const zRest = (x: number) => (u0 ? valueAt(P, (k) => u0[k], x, y) : 0);
568+ const band = (z: (x: number) => number, fill: string) => {
569+ const half = Math.max(0.5, (P.thick * sc) / 2);
570+ ctx.fillStyle = fill;
571+ ctx.beginPath();
572+ for (let i = 0; i <= n; i++) {
573+ const x = (L * i) / n;
574+ ctx.lineTo(x0 + x * sc, base - z(x) * sc - half);
575+ }
576+ for (let i = n; i >= 0; i--) {
577+ const x = (L * i) / n;
578+ ctx.lineTo(x0 + x * sc, base - z(x) * sc + half);
579+ }
580+ ctx.closePath();
581+ ctx.fill();
582+ };
583+ ctx.save();
584+ ctx.beginPath();
585+ ctx.rect(0, 0, W, Hc - 14);
586+ ctx.clip();
587+ band(zRest, "rgba(122, 125, 130, 0.35)");
588+ band((x) => zRest(x) + valueAt(P, (k) => s.u[k], x, y), TEXT);
589+ ctx.restore();
590+ // Supports of a held plate.
591+ if (P.edges !== "free") {
592+ ctx.fillStyle = MUTED;
593+ for (const xs of [x0, x0 + g.size]) {
594+ ctx.beginPath();
595+ ctx.moveTo(xs, base);
596+ ctx.lineTo(xs - 4, base + 7);
597+ ctx.lineTo(xs + 4, base + 7);
598+ ctx.closePath();
599+ ctx.fill();
600+ }
601+ }
602+ ctx.fillStyle = MUTED;
603+ ctx.font = "11px system-ui, sans-serif";
604+ ctx.textAlign = "left";
605+ const rel = (x: number) => (x >= 10 ? x.toFixed(0) : x.toPrecision(2));
606+ ctx.fillText(
607+ `true scale · side ${fmtLength(L)}` +
608+ (P.thick > 0 ? ` · h ${fmtLength(P.thick)}` : "") +
609+ (P.rise > 0 ? ` · rise ${fmtLength(P.rise)}${P.thick > 0 ? ` = ${rel(P.rise / P.thick)} h` : ""} = side / ${rel(L / P.rise)}` : " · flat"),
610+ x0, Hc - 2,
611+ );
612+}
613+
544614 // Animation: the simulation advances `speed` seconds per second of real time.
545615 let last = performance.now();
546616 let carry = 0;
@@ -567,6 +637,7 @@ function frame(now: number) {
567637 for (const u of s.u) umax = Math.max(umax, Math.abs(u));
568638 ampl = Math.max(umax, 0.998 * ampl);
569639 drawPlate();
640+ drawSection();
570641 traces.draw();
571642 if (note && soundPanel.open) {
572643 const at = player.position();
src/plate/style.cssmodified+3−0View file
@@ -5,6 +5,9 @@
55 canvas { display: block; width: 100%; }
66 [hidden] { display: none !important; }
77 #plate { aspect-ratio: 1; cursor: crosshair; }
8+#section { height: 84px; margin-top: 6px; }
9+.column { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
10+#param-form { margin-top: 0; }
811 .card-head { font-size: 12px; margin-bottom: 12px; display: flex; align-items: center; }
912 #traces canvas, #sound-traces canvas { height: auto; margin-top: 12px; }
1013 #toggles { display: contents; }
↑↓ move↵openescclose