/ proof-of-concept / plate-dynamics-lab
Sign in
proof-of-concept / plate-dynamics-lab
plate-dynamics-lab
Go to fileHistoryFork
.mochiPlate dynamics lab: plates in realizable mounts under gravity, explicit finite differences on WebGPU
appsSave the run as a movie: the scene, its caption and the sound
packagesSave the run as a movie: the scene, its caption and the sound
.gitignorePlate dynamics lab: plates in realizable mounts under gravity, explicit finite differences on WebGPU
.prettierrcPlate dynamics lab: plates in realizable mounts under gravity, explicit finite differences on WebGPU
package-lock.jsonPlate dynamics lab: plates in realizable mounts under gravity, explicit finite differences on WebGPU
package.jsonPlate dynamics lab: plates in realizable mounts under gravity, explicit finite differences on WebGPU
README.mdSave the run as a movie: the scene, its caption and the sound
tsconfig.base.jsonPlate dynamics lab: plates in realizable mounts under gravity, explicit finite differences on WebGPU
tsconfig.jsonPlate dynamics lab: plates in realizable mounts under gravity, explicit finite differences on WebGPU
vite.shared.tsPlate dynamics lab: plates in realizable mounts under gravity, explicit finite differences on WebGPU
vitest.config.tsPlate dynamics lab: plates in realizable mounts under gravity, explicit finite differences on WebGPU

Plate Dynamics Lab#

An interactive lab for the dynamics of a thin metal plate held the way a plate can actually be held on a bench: hung from a thread or two, resting on foam or felt pads, on a cymbal stand, gripped in a vise, or pinched between finger and thumb. Gravity is part of the model. A hung plate swings on its thread and rocks about its hole while it rings; a plate on pads sags into them and can be knocked off them; a cantilever droops and then vibrates about its droop. You choose the plate (a rectangle or an ellipse), the material, the mount and its orientation, and strike the plate or pull it aside and let go; the app shows the plate moving in its mount, the flexural displacement and surface stress fields, the rigid-body swing and the support forces over time, a probe time series and spectrum, and the radiated sound, which you can play or save. The scene can also be saved as a movie, at whatever orientation and colouring you have set, with that sound as its audio track.

The numerics run in numbl, a MATLAB-syntax environment executing in the browser. The MATLAB source shown on screen is the source that ran: edit any file and rerun. The time loop, which is hundreds of thousands of steps for a run of a few seconds, has a WebGPU port that is on by default; the setup, the statics, the stresses, the sound and the spectra run in numbl in double precision, and the double-precision loop in numbl remains the reference and the fallback. No physics is computed in JavaScript outside that port; the host draws arrays and plays samples.

This lab succeeds plate-mechanics-sim, keeping its shapes, materials, source view and result panels, and replacing its idealized edge conditions with mounts, its several methods with one explicit scheme, and its millisecond windows with seconds.

Layout#

packages/core        problem spec, .m generation, numbl runner, WebGPU time loop, result typing, field primitives (no UI)
packages/core/src/m  the MATLAB library: plate_fd.m, plate_model.m, plate_mount.m, plate_prestress.m, ...
packages/components  React components: code view, parameter panel, 2-D field view, 3-D scene, plots, equations, run status, audio
apps/lab             the app: one arrangement of the components

The model#

The plate is a damped Kirchhoff-Love plate in its own coordinates, with two extra terms that carry gravity:

mu w_tt + 2 mu sigma0 w_t - 2 mu sigma1 lap(w_t) + D lap(lap(w)) - div(N grad w) = f + mu g_n + f_mount

The normal component of gravity, mu g_n, loads the plate transversely and gives a horizontal plate its static sag. The in-plane component puts the plate under its own weight, and N is the resulting in-plane stress resultant, found once at setup by a plane-stress solve with the mount's constraints (the hole a thread passes through, the jaws of a vise, the pinched patch). The membrane term div(N grad w) is how gravity acts on a hanging plate's motion: a tilted element under tension is pulled back. For a rigid rotation of the plate about its hole the term integrates to the compound-pendulum stiffness m g d exactly, and the lab assembles it with the same difference operators as the plane-stress energy, so this identity holds to rounding rather than to truncation error (packages/core/test/fd.test.ts checks it). The swing of a hung plate and the small stiffening of its lowest flexural modes therefore come from one operator, and a plate hung from below its centre of mass is correctly unstable.

Each mount is data on the grid (plate_mount.m):

  • One thread. A plate hung from one point turns in its plane until its centre of mass is straight below the hole, which fixes the direction of gravity. For small out-of-plane motion the thread is a transverse spring of stiffness T/L at the hole (the linearized pendulum), plus a torsional spring G J / L about the vertical, which for any real thread is so weak that the spin has a period of minutes.
  • Two threads of one length from hooks at one height: the line of the holes hangs horizontal, statics shares the weight, and the lab refuses a geometry in which a thread would go slack.
  • Pads. Rectangular patches of a stiffness and damping per unit area, one-sided: a pad pushes while compressed and lets go when the plate lifts off. Which pads carry the plate at rest is found by an active-set iteration.
  • Cymbal stand. A felt washer below and one above the centre, each preloaded by the wing nut; the rod, the hole and the cymbal's bow are not modelled.
  • Vise. The nodes within the jaw depth are held at zero; the clamped condition follows from the bending energy (a second difference centred on a held node penalizes the slope at the clamp). Any tilt from hanging down (0°) through horizontal (90°) to standing up (180°, self-weight in compression).
  • Fingers. A two-sided spring and dashpot over a patch, also fixed in the plane; hanging (0°) to flat (90°).

Every quantity of the model has an equation in the app's Equations tab, and every idealization a line in its validity notes.

The scheme#

The spatial operators are the sparse energy-method matrices of plate_bending.m (the 13-point biharmonic stencil inside, the free-edge conditions as natural conditions on the edges, held nodes dropped) and plate_prestress.m. The time stepping is the second-order explicit scheme of the old lab, with two changes.

First, the state is the departure from the static equilibrium, split into three rigid-body coordinates (a translation along the normal at the mount's pivot, and the rotations about the horizontal and the vertical in-plane axes through it) and a flexural remainder that is mass-orthogonal to them (plate_rigid_basis.m). The rigid coordinates obey three scalar equations with the exact reduced stiffness R' A R (the pendulum, the thread springs, the thread's torsion) and are coupled to the flexural field through A R; the flexural field obeys the grid equation with the rigid acceleration projected out. The split is exact, and it is what keeps a millimetre swing and a micrometre vibration apart in single precision: bending annihilates the rigid basis, so the bending operator is never applied to the swing.

Second, the stable step is the von Neumann bound of the bending scheme extended by a Gershgorin bound on the extra stiffness of the prestress, the springs and the engaged contacts (plate_model.m), and the dashpots, treated explicitly, keep the step below half of their own limit. A run of a few seconds on the default 41 x 27 grid is about 650,000 steps.

Contacts are evaluated at the current step, one-sided, with the static part subtracted so that the forces are linear in the departure until a contact opens. A release starts from the static deflection under a force at the pull point, scaled to the asked displacement and split into rigid and flexural parts.

The GPU loop#

packages/core/src/gpu/fdLoop.ts is plate_fd_loop.m as compute shaders: five dispatches per step (strains, forces, the rigid-body update with the reductions it needs, the field update, and the audio and blow-up reductions). The setup (plate_fd_setup.m) and the output (plate_fd_output.m) run in numbl, unchanged and in double precision; the worker hands the setup struct to the GPU and the loop's record back. Editing plate_fd_loop.m sends the loop back to numbl, where the edit is live, and packages/core/test/gpu.test.ts compares the port with the numbl loop for every mount.

The port applies the operators in their factored form, a difference stencil, then weights, then the transposed stencil, rather than as the assembled sparse matrix. In single precision the assembled 13-point stencil loses the smooth modes: the biharmonic of a smooth field is a small residual of large cancelling terms, and the rounding of the field alone is a few percent of it at low wavenumbers, which is where a long run lives. In factored form each stencil is exact integers applied to nearby values, the scales sit in the weights where rounding is harmless, and the port agrees with the double-precision loop to a relative L2 error of about 1e-6 in the fields, the rigid coordinates and the support forces over the test windows.

Cost. On an integrated Intel GPU the default run (a 300 x 200 mm sheet on a 41 x 27 grid for 3 s, about 700,000 steps) takes between 15 and 50 s in the loop, the spread being the GPU's own load, plus about 1.5 s in numbl for the setup and the output; a 61 x 41 grid for 1 s takes about a minute. The same loop in numbl runs at roughly 0.8 us per cell-step, so the default run would take some ten minutes there; the estimate shown before a run is calibrated by the runs before it.

Scenarios#

The scenario picker sets everything at once; every field stays editable afterwards.

  • Steel sheet on a thread (the default): sways at about 0.8 Hz, rocks about the hole at about 2.9 Hz, spins slowly, and rings.
  • Gong on two cords: a bronze disk, struck at the centre.
  • Glockenspiel bar on nodal pads: felt strips under the nodes of the first mode.
  • Sheet on four foam blocks: a hard strike lifts it off the pads.
  • Cymbal on a stand: preloaded felts at the centre of a flat bronze disk.
  • Cantilever in a vise: a steel strip pulled down 10 mm and released.
  • Disk pinched in the hand: a soft, lossy grip near the rim.

Saving a run#

Two exports, both written by the browser itself with no server and no bundled encoder.

  • The sound, as a 16-bit mono WAV at the simulation's own sample rate, peak-normalized (the Rayleigh estimate is not a calibrated level, so the absolute scale would be misleading either way).
  • The movie, as WebM (VP9 and Opus) or MP4 where the browser records it, 960 pixels wide: the 3-D scene as it stands, at its present orientation, exaggeration and colouring, with a title bar carrying the simulated time and a caption bar carrying the field, its colour scale and the exaggeration factors, so the file explains itself. The sound is the audio track. packages/components/src/video/recordMovie.ts composites the scene and the caption onto a canvas, captures that canvas as a video track, plays the sound into an audio track, and lets MediaRecorder mux the two.

The recording is real time, which is what using nothing but the platform costs: a three-second movie takes three seconds to write, the tab has to stay in front, and a machine that cannot keep up drops frames rather than slowing down (the lab says how many of the run's frames it drew). Below real time the picture and the sound are slowed together, so the sound drops in pitch as a tape would. A run whose frames would need more than about sixty a second cannot have all of them drawn at real time, and the lab says so and offers the slower speeds.

Running#

npm install
npm run dev              # the lab, http://localhost:5173
npm test                 # vitest: the .m files in numbl under Node, plus the GPU loop against the numbl loop when a WebGPU device is available
npm run typecheck
npm run build

packages/core/test/gpu.test.ts runs the WebGPU loop from Node against desktop WebGPU through the optional webgpu package (prebuilt Google Dawn); npm i --no-save webgpu installs it. Without the package or a GPU adapter the suite skips itself.

The .m files also run unchanged from the numbl CLI, Octave or MATLAB:

npx numbl run --path packages/core/src/m my_script.m

Honest limits#

  • Kirchhoff-Love thin-plate theory: no shear deformation or rotary inertia, so thick plates and very high modes are wrong.
  • Small-angle theory throughout. The threads are linear springs, the pendulum motion is linearized, and the plate's in-plane motion (a swing in its own plane, a rotation about the normal) is not part of the model. A pull beyond a few degrees of swing leaves this regime; the spin about a thread, which has no gravitational stiffness, is exact at any angle and is drawn as a true rotation.
  • Mounts are idealizations with order-of-magnitude constants: a thread has one tension, a pad is a spring and dashpot per unit area, a vise is perfectly rigid, a hand is a guess.
  • Free edges and curved edges are first order at the boundary; the stress at a hole or a jaw is that of a point or line constraint on the grid and depends on the spacing.
  • Damping is the two-parameter loss model plus the mount's dashpots, chosen, not measured; the sigma1 loss acts on the flexural part only.
  • The probe series are averages over each output sample; stress fields are evaluated only at the saved frames.
  • Radiated sound is an estimate of the waveform, not a calibrated level: Rayleigh's integral in a baffle, or a rough front-back cancellation for an unbaffled plate.
  • The movie is recorded in real time from the browser's own encoder, so its length is the length of the run divided by the speed, its bitrate is the browser's choice, and frames are dropped when the machine falls behind. It is a recording of the animation on screen, not a re-render: the time resolution is the run's saved frames, not its time step.
  • The GPU loop is single precision. The rigid-flexural split and the factored operators keep it close to the double-precision loop, but a run of many seconds accumulates rounding that the tests over tens of milliseconds do not measure.
↑↓ move↵openescclose