Plate: remove membrane tension (did not sound like a drum; drums later); clamped edges stay
5 changed files+23−49
src/index.htmlmodified+1−1View file
@@ -34,7 +34,7 @@
3434 <li><a href="./double-pendulum/"><strong>Double pendulum</strong><div class="muted">Strike it, watch and hear the motion turn regular as the energy decays; Lyapunov exponent and Poincaré section.</div></a></li>
3535 <li><a href="./fput-chain/"><strong>FPUT chain</strong><div class="muted">A struck chain of N masses with cubic springs; energy spreading up the modes, Lyapunov exponent and Poincaré sections for N = 2 and 3.</div></a></li>
3636 <li><a href="./beam/"><strong>Beam</strong><div class="muted">A struck beam of N masses with bending stiffness and stretching: clamped, pinned or cantilever; inharmonic, cymbal-like overtones.</div></a></li>
37- <li><a href="./plate/"><strong>Plate</strong><div class="muted">A struck square plate of N × N masses with bending stiffness and von Kármán stretching, simply supported, clamped or free, optional membrane tension (drum-like); modes (m, n) at m² + n², pitch glides when struck hard.</div></a></li>
37+ <li><a href="./plate/"><strong>Plate</strong><div class="muted">A struck square plate of N × N masses with bending stiffness and von Kármán stretching, simply supported, clamped or free; modes (m, n) at m² + n², pitch glides when struck hard.</div></a></li>
3838 </ul>
3939 </main>
4040 </body>
src/plate/README.mdmodified+4−4View file
@@ -1,6 +1,6 @@
11 # plate
22
3-Struck square plate, step 4 of "Listening for chaos" (after double-pendulum, fput-chain and beam), the first two-dimensional system on the way to cymbal and gong. Work in progress: step 1 (2026-10-09), a linear, simply supported square plate with mallet strike, animation and sped-up note with spectrogram; step 2 (2026-10-09), von Kármán stretching; step 3 (2026-10-09), free edges; step 4 (2026-10-09), stretching with free edges (and the free layout moved to the grid vertices); step 5 (2026-10-09), clamped edges and membrane tension.
3+Struck square plate, step 4 of "Listening for chaos" (after double-pendulum, fput-chain and beam), the first two-dimensional system on the way to cymbal and gong. Work in progress: step 1 (2026-10-09), a linear, simply supported square plate with mallet strike, animation and sped-up note with spectrogram; step 2 (2026-10-09), von Kármán stretching; step 3 (2026-10-09), free edges; step 4 (2026-10-09), stretching with free edges (and the free layout moved to the grid vertices); step 5 (2026-10-09), clamped edges.
44
55 Current state: N × N masses (N per side, 2 to 32, default 8) on vertical rails at the nodes of a square grid, spacing a, moving up and down only. Bending κ on the five-point Laplacian at each node, energy κ (L u)² / 2, force −κ L(L u). The edge nodes are supports at u = 0 with no bending term (mirror ghost), which makes the edge simply supported. Stretching (von Kármán), below, set by the thickness h (default 5 cm; 0 = linear). Damping as in the beam: drag (τ, all modes alike) and a frequency-dependent loss (dashpots alongside the grid springs, force −c L v, set by τ top at the top of the band). Parameters are given for the 8 × 8 plate (m 1 kg, κ 20 N/m, h 0.05 m, a 0.5 m, so the side is 4.5 m; τ 540 s, τ top 200 s); Masses per side refines the same plate (`refine`: m with the cell area, κ inversely, the loss coefficient c unchanged). Strike: a kick (pyramid velocity bump of half-width 0.5 m) or the shared Hertz mallet (`../common/mallet.ts`, M 1 kg, K 1000) over the same bump, at (x, y) m from the lower left corner (default (1.3, 1.1)); clicking the plate moves the strike point. The card shows the plate from above as a height map (red up, blue down, scaled to the recent largest |u|), with the strike point (cross) and the listening point (circle). The sound panel is the beam's, reduced: velocity, displacement or acceleration at a listening point (default (3.1, 1.7), bilinear between masses), pitch of the lowest mode, the whole decay as a note through the shared renderer and anti-alias filter, spectrogram with dashed lines at the distinct mode pitches, energy chart, Replay and Save WAV. Not yet: Lyapunov exponent, ghost, mode bars and histories, cross-section.
66
@@ -20,8 +20,8 @@ Stretching with free edges (step 4, 2026-10-09): the edge is free in its own pla
2020
2121 Sound (2026-10-09, free, N = 8, h 5 cm, mallet at (1.3, 1.1), velocity at (3.1, 1.7), lowest mode at 100 Hz): at 0.01 J linear; at 1 J the energy already spreads between modes within the first second; at 10 J a broadband attack; at 100 J a wash for about the first second, the low modes emerging as it decays. Spectral centroid at 0.2 s: 880, 1210, 1270, 1290, 1500 Hz for 0.01, 1, 2.51, 10, 100 J.
2222
23-Clamped edges and tension (step 5, 2026-10-09). Edges select: simply supported, clamped, free. Clamped: the simply supported layout with the slope held at zero (even ghost beyond the supports); the support nodes then carry a bending term (L u = −2 u next to them, weight 1/2), so the energy is κ/2 (|L u|² + 2 Σ c u²), c the edges a node touches: the standard clamped 13-point stencil (21 next to an edge, 22 in a corner), the same operator as the free plate's stress function. Modes computed by the symmetry blocks (no (m, n)); stretching as simply supported (same in-plane edge). Check against Leissa's clamped square plate (ω L² √(ρh/D): 35.99, 73.39 (×2), 108.22, 131.58, 132.20, 165.15 (×2)): 33.62, 64.96, 94.51, 107.02 at N = 7; 35.33, 70.98, 104.10, 124.28 at N = 15; 35.82, 72.77, 107.12, 129.66 at N = 31 (lowest 6.6%, 1.8%, 0.47% low: second order). Time domain (N = 8, undamped kick): all 48 distinct frequencies within 3 cents (median 0.22), energy error 1.1e-3. Nonlinear scale (h 5 cm, N = 8): 0.44 J (simply supported 0.13 J).
23+Clamped edges (step 5, 2026-10-09). Edges select: simply supported, clamped, free. Clamped: the simply supported layout with the slope held at zero (even ghost beyond the supports); the support nodes then carry a bending term (L u = −2 u next to them, weight 1/2), so the energy is κ/2 (|L u|² + 2 Σ c u²), c the edges a node touches: the standard clamped 13-point stencil (21 next to an edge, 22 in a corner), the same operator as the free plate's stress function. Modes computed by the symmetry blocks (no (m, n)); stretching as simply supported (same in-plane edge). Check against Leissa's clamped square plate (ω L² √(ρh/D): 35.99, 73.39 (×2), 108.22, 131.58, 132.20, 165.15 (×2)): 33.62, 64.96, 94.51, 107.02 at N = 7; 35.33, 70.98, 104.10, 124.28 at N = 15; 35.82, 72.77, 107.12, 129.66 at N = 31 (lowest 6.6%, 1.8%, 0.47% low: second order). Time domain (N = 8, undamped kick): all 48 distinct frequencies within 3 cents (median 0.22), energy error 1.1e-3. Nonlinear scale (h 5 cm, N = 8): 0.44 J (simply supported 0.13 J).
2424
25-Tension T (N/m, Parameters; default 0; held edges only, disabled for free): a membrane tension, springs between neighbours and to the supports, energy T/2 uᵀLu, force −T L u, the same T at every N (T a²/a² per spring). Simply supported, the modes stay exact sines with ω² = (T l + κ l²)/m; the derived values compare the ratios with the continuum's, from ω² ∝ T k² + κ k⁴. With tension dominant (small κ) a held plate becomes a drumhead: ratios approach the square membrane's √(m² + n²) (clamped, κ ≈ 0: 1.580, 1.998, 2.230, 2.543 at N = 32 against 1.581, 2, 2.236, 2.550). Stretching then adds to the tension (tension modulation). A drum-like setting needs a thinner plate for its stretching to matter (the tension raises the linear stiffness): e.g. κ 2, T 200, h 1 mm gives a nonlinear scale near 1 J (simply supported, N = 8). Forces checked against central differences (1e-10, with tension, both held edges). A "drum" edge option was tried first and dropped for the general tension parameter.
25+A membrane tension T for held edges (drum-like: springs between neighbours and to the supports, energy T/2 uᵀLu, exact sine modes with ω² = (T l + κ l²)/m when simply supported) was added in step 5 and removed the same day: the suggested drum setting (κ 2, T 200, h 1 mm) did not sound like a drum. Drums to be revisited later.
2626
27-Known cost issue (2026-10-09): the 4x oversampled note forces at least 4 steps per audio sample, and the step stability needs is longer than that whenever the speed-up is small, that is when the lowest mode is close in frequency to the top of the band: clamped (lowest mode higher) and especially with tension (top/lowest only 7.5 at N = 8, so about 8x more steps than needed). Clamped default note: 12 s to render in the browser. Fix planned: read the signal between steps by interpolation (shared note renderer).
27+Known cost issue (2026-10-09): the 4x oversampled note forces at least 4 steps per audio sample, and the step stability needs is longer than that whenever the speed-up is small, that is when the lowest mode is close in frequency to the top of the band: clamped (lowest mode higher; a tensioned membrane, tried briefly, was far worse, about 8x more steps than needed). Clamped default note: 12 s to render in the browser. Fix planned: read the signal between steps by interpolation (shared note renderer).
src/plate/index.htmlmodified+3−4View file
@@ -56,7 +56,6 @@
5656 <div id="param-form">
5757 <label>m <input id="p-m" type="number" step="any" /> <span class="unit">kg</span></label>
5858 <label>κ <input id="p-kappa" type="number" step="any" /> <span class="unit">N/m (bending)</span></label>
59- <label>T <input id="p-tension" type="number" step="any" /> <span class="unit">N/m (membrane tension, held edges; 0 = none)</span></label>
6059 <label>h <input id="p-thick" type="number" step="any" /> <span class="unit">m (thickness, sets the stretching; 0 = linear)</span></label>
6160 <label>a <input id="p-a" type="number" step="any" /> <span class="unit">m (spacing)</span></label>
6261 <label>τ <input id="p-tau" type="number" step="any" /> <span class="unit">s (decay, 0 = off)</span></label>
@@ -104,15 +103,15 @@
104103
105104 <template id="help-plate">
106105 <h2>The plate</h2>
107- <p>A square plate as a grid of equal masses on vertical rails, moving up and down only, seen from above: red is up, blue down, white at rest (scaled to fit). Each mass resists bending (κ, on how far it sits off the mean of its four neighbours). The edges are simply supported (held at zero height but free to turn there), clamped (held and kept level), or free. A held plate can also carry a membrane tension T (Parameters), as a drumhead does. A free plate would also slide and tilt as a whole; that motion is removed (the plate is followed in its own frame), and the readout says how much of a strike's energy it took. A bent plate also stretches in its own plane (von Kármán), and the in-plane stress stiffens it: this is the nonlinearity. It matters once the plate moves by about its thickness h, so weak strikes ring as independent normal modes, while strong ones start sharp and glide down in pitch as they decay, and the strongest spread their energy over many modes.</p>
106+ <p>A square plate as a grid of equal masses on vertical rails, moving up and down only, seen from above: red is up, blue down, white at rest (scaled to fit). Each mass resists bending (κ, on how far it sits off the mean of its four neighbours). The edges are simply supported (held at zero height but free to turn there), clamped (held and kept level), or free. A free plate would also slide and tilt as a whole; that motion is removed (the plate is followed in its own frame), and the readout says how much of a strike's energy it took. A bent plate also stretches in its own plane (von Kármán), and the in-plane stress stiffens it: this is the nonlinearity. It matters once the plate moves by about its thickness h, so weak strikes ring as independent normal modes, while strong ones start sharp and glide down in pitch as they decay, and the strongest spread their energy over many modes.</p>
108107 <p>Strike gives the energy on the slider as a hammer blow (a short pyramid bump of velocity, one spacing of the 8 × 8 plate wide on each side) or with a mallet (mass M, contact stiffness K in Parameters) that presses into the plate over the same bump through a Hertz contact (force K δ^1.5) and lifts off. The point is in metres from the lower left corner; click the plate to move it there. The cross marks the strike point, the circle the listening point. The space bar pauses.</p>
109108 </template>
110109 <template id="help-params">
111110 <h2>Parameters</h2>
112- <p>Mass, bending κ, the membrane tension T (force per unit length, for simply supported or clamped edges; the same at every N), the thickness h (it sets the stretching, as the amplitude at which it matters; 0 for a linear plate), the spacing a, the decay time τ of the amplitude (drag on the masses, all modes alike; 0 for none), and τ top, the decay time a frequency-dependent loss (dashpots alongside the grid springs) gives the highest modes (at 8 × 8 masses), less for lower ones (0 for none). Apply restarts the plate at rest.</p>
111+ <p>Mass, bending κ, the thickness h (it sets the stretching, as the amplitude at which it matters; 0 for a linear plate), the spacing a, the decay time τ of the amplitude (drag on the masses, all modes alike; 0 for none), and τ top, the decay time a frequency-dependent loss (dashpots alongside the grid springs) gives the highest modes (at 8 × 8 masses), less for lower ones (0 for none). Apply restarts the plate at rest.</p>
113112 <p>The values are for the plate with 8 masses per side. Masses per side refines the same plate: same size, strike energies and decay, with each mass scaled with the area of its cell and κ inversely (the thickness stays), so the low modes stay put and more masses add higher ones.</p>
114113 <p>The stretching follows the von Kármán plate equations: the in-plane stress comes from a stress function, solved on the grid at every step. A simply supported edge may move outward but not along itself; a free edge is free in its own plane too. The nonlinear scale below is the energy of the lowest mode at which its stretching energy equals its bending energy.</p>
115- <p>With free edges the bending energy also includes the plate's twisting term (Poisson's ratio 0.3), which sets the edge conditions; the modes are computed, and their frequencies converge to plate theory (Leissa's tables) as the spacing squared (the lowest is 2.6% low at 9 × 9, 0.2% at 33 × 33). On a free plate the outer masses sit on the edge, carrying half a cell's mass (a quarter at the corners), so the free plate's side is one spacing less than the number of masses. Clamped edges hold the slope at zero too; the modes are computed (no (m, n)), and the plate is stiffer than a simply supported one, with overtones about as widely spread (plate theory: 1, 2.04, 3.01, 3.66, ...). With tension T and little bending (small κ), a held plate becomes a drumhead: its frequencies approach the ideal square membrane's, √(m² + n²), ratios 1, 1.58, 2, 2.24, 2.55, ..., also inharmonic but closer together than a plate's; stretching adds to the tension as it moves, so a hard strike starts sharp and settles, as on a tom-tom. For a simply supported plate, the normal modes are products of sines, numbered (m, n) by their half waves along x and y. Their frequencies go as m² + n² for the low ones (the continuum plate), so (1, 2) and (2, 1) share a pitch, and the ratios to the lowest, 2.5, 4, 5, 6.5, 8.5, ..., are not harmonic. The derived values list the lowest modes and how close the grid is to the continuum.</p>
114+ <p>With free edges the bending energy also includes the plate's twisting term (Poisson's ratio 0.3), which sets the edge conditions; the modes are computed, and their frequencies converge to plate theory (Leissa's tables) as the spacing squared (the lowest is 2.6% low at 9 × 9, 0.2% at 33 × 33). On a free plate the outer masses sit on the edge, carrying half a cell's mass (a quarter at the corners), so the free plate's side is one spacing less than the number of masses. Clamped edges hold the slope at zero too; the modes are computed (no (m, n)), and the plate is stiffer than a simply supported one, with overtones about as widely spread (plate theory: 1, 2.04, 3.01, 3.66, ...). For a simply supported plate, the normal modes are products of sines, numbered (m, n) by their half waves along x and y. Their frequencies go as m² + n² for the low ones (the continuum plate), so (1, 2) and (2, 1) share a pitch, and the ratios to the lowest, 2.5, 4, 5, 6.5, 8.5, ..., are not harmonic. The derived values list the lowest modes and how close the grid is to the continuum.</p>
116115 </template>
117116 <template id="help-sound">
118117 <h2>Sped up · listen</h2>
src/plate/main.tsmodified+7−15View file
@@ -36,10 +36,10 @@ const pauseButton = document.getElementById("pause") as HTMLButtonElement;
3636 // Parameters, as the beam's defaults: bending kappa = 20 on unit masses,
3737 // drag tau 540 s, the frequency-dependent loss tauTop 200 s; thickness 5 cm
3838 // for the stretching (2026-10-09: the default strike glides down by about
39-// half an octave, 100 J is a broadband wash); no tension. base: the page's
39+// half an octave, 100 J is a broadband wash). base: the page's
4040 // parameters, for the plate at REF_N masses per side; P: the same plate
4141 // refined to the chosen N (see refine).
42-let base: Params = { N: REF_N, edges: "simply", m: 1, kappa: 20, tension: 0, thick: 0.05, a: 0.5, tau: 540, tauTop: 200 };
42+let base: Params = { N: REF_N, edges: "simply", m: 1, kappa: 20, thick: 0.05, a: 0.5, tau: 540, tauTop: 200 };
4343 let P: Params = base;
4444 // The mallet: a physical object, not refined with N.
4545 let mallet: MalletParams = { M: 1, K: 1000 };
@@ -152,7 +152,6 @@ const relErr = () => (E0 > 0 ? (energy(P, s) + s.W - E0) / E0 : 0);
152152 const paramInputs = {
153153 m: document.getElementById("p-m") as HTMLInputElement,
154154 kappa: document.getElementById("p-kappa") as HTMLInputElement,
155- tension: document.getElementById("p-tension") as HTMLInputElement,
156155 thick: document.getElementById("p-thick") as HTMLInputElement,
157156 a: document.getElementById("p-a") as HTMLInputElement,
158157 tau: document.getElementById("p-tau") as HTMLInputElement,
@@ -181,23 +180,17 @@ function distinctModes(): { w: number; count: number; k: number }[] {
181180 return out;
182181 }
183182 // Values that follow from the parameters: the lowest modes, with, when
184-// simply supported, their (m, n) and the continuum's ratio (from omega^2 ~ T
185-// k^2 + kappa k^4 with k^2 ~ m^2 + n^2: (m^2 + n^2) / 2 without tension,
186-// sqrt((m^2 + n^2) / 2) with tension alone), and the decay times.
183+// simply supported, their (m, n) and the continuum's ratio (m^2 + n^2) / 2,
184+// and the decay times.
187185 function showDerived() {
188186 const w1 = modes.omega[0], hz = (w: number) => w / (2 * Math.PI);
189187 const free = P.edges !== "simply"; // no (m, n)
190- const kk = (Math.PI / (P.N + 1)) ** 2, T = P.edges === "free" ? 0 : P.tension;
191- const cont = (m: number, n: number) => {
192- const l = kk * (m * m + n * n);
193- return Math.sqrt(T * l + P.kappa * l * l);
194- };
195188 const rows = distinctModes().slice(0, 8).map(({ w, count, k }) => {
196189 const m = modes.m[k], n = modes.n[k];
197190 return (
198191 ` ${free ? "" : `(${m}, ${n})`}${count > 1 ? ` ×${count} ` : " "}${hz(w).toPrecision(3).padStart(7)} Hz ` +
199192 `ratio ${(w / w1).toFixed(3)}` +
200- (free ? "" : ` continuum ${(cont(m, n) / cont(1, 1)).toFixed(3)}`)
193+ (free ? "" : ` continuum ${((m * m + n * n) / 2).toFixed(1)}`)
201194 );
202195 });
203196 const g = (x: number) => (x === 0 ? "0" : x.toPrecision(4));
@@ -351,7 +344,6 @@ function setParams(next: Params, N: number) {
351344 base = next;
352345 P = refine(base, next.edges === "free" ? Math.max(3, N) : N); // free: at least 3 per side
353346 edgesSelect.value = P.edges;
354- paramInputs.tension.disabled = P.edges === "free";
355347 modes = new Modes(P);
356348 H = stepFor(P, modes, 0);
357349 s = newState(P.N);
@@ -385,10 +377,10 @@ edgesSelect.addEventListener("change", () => setParams({ ...base, edges: edgesSe
385377 document.getElementById("param-apply")!.addEventListener("click", () => {
386378 const v = Object.fromEntries(Object.entries(paramInputs).map(([key, el]) => [key, Number(el.value)]));
387379 const mv = { M: Number(malletInputs.M.value), K: Number(malletInputs.K.value) };
388- const ok = Object.entries(v).every(([key, x]) => Number.isFinite(x) && (x > 0 || (["tau", "tauTop", "thick", "tension"].includes(key) && x === 0))) &&
380+ const ok = Object.entries(v).every(([key, x]) => Number.isFinite(x) && (x > 0 || (["tau", "tauTop", "thick"].includes(key) && x === 0))) &&
389381 Object.values(mv).every((x) => Number.isFinite(x) && x > 0);
390382 if (!ok) {
391- paramStatus.textContent = "parameters must be positive (T, h, τ, τ top may be 0)";
383+ paramStatus.textContent = "parameters must be positive (h, τ, τ top may be 0)";
392384 return;
393385 }
394386 paramStatus.textContent = "";
src/plate/plate.tsmodified+8−25View file
@@ -50,16 +50,6 @@
5050 // supported. The modes are computed (by symmetry blocks, as for the free
5151 // plate); stretching as for the simply supported plate (same in-plane
5252 // edge).
53-// Tension: a membrane tension T (N/m, force per unit length) on a held
54-// plate (simply supported or clamped), as on a drumhead: springs between
55-// neighbours and to the supports, energy T / 2 (u^T L u) (from T / 2 |grad
56-// w|^2; T a^2 / a^2 per spring, so T needs no scaling with the spacing),
57-// force -T L u, on top of the bending. Simply supported, L is diagonal in
58-// the same sine modes: omega_mn^2 = (T l + kappa l^2) / m, l = l_m + l_n.
59-// With the tension dominant the frequencies go as sqrt(m^2 + n^2), a square
60-// membrane; the bending stiffens the upper modes, as a real drumhead's does.
61-// Stretching then adds to the tension as the plate moves (tension
62-// modulation: a hard strike starts sharp). A free plate has no tension.
6353 // Free edges: the bending energy then needs its full form, D / 2 ((Lap w)^2
6454 // - 2 (1 - nu)(w_xx w_yy - w_xy^2)), whose second (Gaussian) term only
6555 // matters at a free edge and sets its natural conditions (zero moment and
@@ -107,7 +97,6 @@ export interface Params {
10797 edges: Edges;
10898 m: number; // kg, each mass
10999 kappa: number; // N/m, bending
110- tension: number; // N/m, membrane tension (held edges only); 0 = none
111100 thick: number; // m, thickness, sets the stretching (von Karman); 0 = linear
112101 a: number; // m, rest spacing (for positions and drawing; it does not enter the equations)
113102 tau: number; // s, amplitude decay time from drag (all modes); 0 = no damping
@@ -139,8 +128,6 @@ export const isDamped = (P: Params) => P.tau > 0 || P.tauTop > 0;
139128 const lam1 = (N: number, m: number) => 4 * Math.sin((m * Math.PI) / (2 * (N + 1))) ** 2;
140129 const isFree = (P: Params) => P.edges === "free";
141130 const isClamped = (P: Params) => P.edges === "clamped";
142-// Membrane tension (N/m): 0 for a free plate.
143-export const tensionOf = (P: Params) => (isFree(P) ? 0 : P.tension);
144131 // Edges a node touches (0, 1 or 2), for the clamped term.
145132 const touched = (N: number, i: number, j: number) => (i === 0 ? 1 : 0) + (i === N - 1 ? 1 : 0) + (j === 0 ? 1 : 0) + (j === N - 1 ? 1 : 0);
146133 // Largest eigenvalue of L: 2 l_N; free, 8 (of m M^-1 L, exactly: see laplacian).
@@ -533,8 +520,7 @@ export function forces(P: Params, u: Float64Array, F: Float64Array, c: Float64Ar
533520 }
534521 laplacian(P.N, u, c);
535522 laplacian(P.N, c, F);
536- const T = tensionOf(P);
537- for (let k = 0; k < F.length; k++) F[k] = -P.kappa * F[k] - T * c[k];
523+ for (let k = 0; k < F.length; k++) F[k] *= -P.kappa;
538524 if (isClamped(P)) {
539525 const N = P.N;
540526 for (let j = 0, k = 0; j < N; j++) for (let i = 0; i < N; i++, k++) F[k] -= 2 * P.kappa * touched(N, i, j) * u[k];
@@ -548,18 +534,15 @@ export function kinetic(P: Params, s: State): number {
548534 for (let k = 0; k < s.p.length; k++) T += (s.p[k] * s.p[k]) / M[k];
549535 return T / 2;
550536 }
551-// Linear energy at u: bending, plus the tension.
537+// Linear energy at u: bending.
552538 export function linearEnergy(P: Params, u: Float64Array): number {
553539 if (isFree(P)) return freeBending(P, u);
554540 const N = P.N, c = new Float64Array(u.length);
555541 laplacian(N, u, c);
556- let V = 0, uLu = 0;
557- for (let k = 0; k < u.length; k++) {
558- V += c[k] * c[k];
559- uLu += u[k] * c[k];
560- }
542+ let V = 0;
543+ for (let k = 0; k < u.length; k++) V += c[k] * c[k];
561544 if (isClamped(P)) for (let j = 0, k = 0; j < N; j++) for (let i = 0; i < N; i++, k++) V += 2 * touched(N, i, j) * u[k] * u[k];
562- return (P.kappa * V + tensionOf(P) * uLu) / 2;
545+ return (P.kappa * V) / 2;
563546 }
564547 export const stretchEnergy = (P: Params, u: Float64Array) => stretching(P, u);
565548 export const potential = (P: Params, s: State) => linearEnergy(P, s.u) + stretching(P, s.u);
@@ -664,9 +647,9 @@ export class Modes {
664647 this.n = new Int32Array(this.count);
665648 return;
666649 }
667- const T = tensionOf(P), w = (l: number) => Math.sqrt((T * l + P.kappa * l * l) / P.m);
650+ const w0 = Math.sqrt(P.kappa / P.m);
668651 const all: [number, number, number][] = [];
669- for (let m = 1; m <= N; m++) for (let n = 1; n <= N; n++) all.push([w(lam1(N, m) + lam1(N, n)), m, n]);
652+ for (let m = 1; m <= N; m++) for (let n = 1; n <= N; n++) all.push([w0 * (lam1(N, m) + lam1(N, n)), m, n]);
670653 all.sort((x, y) => x[0] - y[0] || x[1] - y[1] || x[2] - y[2]);
671654 this.count = all.length;
672655 this.omega = Float64Array.from(all, (x) => x[0]);
@@ -679,7 +662,7 @@ export class Modes {
679662 }
680663
681664 // Computed modes (free or clamped plate): eigenvalues lambda = omega^2 of M^-1/2
682-// K M^-1/2 (K the linear stiffness, M the node masses) and displacement
665+// K M^-1/2 (K the bending stiffness, M the node masses) and displacement
683666 // shapes, without a free plate's three rigid motions (lambda = 0). The square is symmetric under x ->
684667 // -x and y -> -y (masses included), so the matrix splits into four blocks,
685668 // even or odd in x and in y (built on mirror pairs of nodes), each a quarter