3Explicit nonlinear finite-element simulation of struck cymbals. The cymbal is a Marguerre-von Karman shallow shell with a bell, a bow, a thickness taper and seeded imperfections, discretized on a triangulated disk with constant-strain membrane triangles and Discrete Kirchhoff bending triangles, integrated in time with an explicit leapfrog scheme, struck by one or more rigid exciters through power-law contact springs, and heard at a listener point through a Rayleigh-integral radiation model. The equations, the discretization and the time-stepping method are written out in [docs/model.md](docs/model.md).
5## Installation
7```
e30c940save .cymsim fileJeremy Magland 8pip install -e ".[dev]" # numpy, scipy, jax (CPU), pyyaml, h5py, pytest, matplotlib
71e94accymsim: nonlinear FEM cymbal simulation packageJeremy Magland 9pip install "jax[cuda12]" # optional: run the time loop on a GPU
10```
42c1bf8Time step from the global stability bound (power iteration), with numerics.stability_bound to keep the element boundJeremy Magland 12Runs at the default resolution (6 mm elements, about 4700 nodes and 38 sub-steps per audio sample) need a GPU to finish in minutes. On a CPU, use a coarser mesh and a shorter duration, as in `examples/quick_cpu.yaml`.
71e94accymsim: nonlinear FEM cymbal simulation packageJeremy Magland 13
14## Usage
16```
17cymsim example-config my_run.yaml --exciter mallet # write a default config
18cymsim check my_run.yaml # mesh, time step, element self-tests, no time stepping
19cymsim simulate my_run.yaml # run; writes <prefix>_rayleigh.wav, _unbaffled.wav, _result.npz, _cascade.png
20cymsim simulate my_run.yaml --linear --duration 0.5 # linear reference run
e30c940save .cymsim fileJeremy Magland 21cymsim run my_run.yaml -o run.cymsim --save-every 60 # also write a .cymsim file, updated every minute of wall time
22cymsim run my_run.yaml -o run.cymsim --record-acceleration # include the audio-rate normal acceleration of every node
6c740b5Continue a run from a .cymsim file (cymsim run --resume, simulate(cfg, resume=...)), identical to the uninterrupted run; keep the outputs past the duration in the state and store probe signals in the fileJeremy Magland 23cymsim run my_run.yaml --resume run.cymsim --duration 4 -o run4.cymsim # continue a saved run to 4 s
4931449Cache keys for runs: hash of the non-default config fields without duration, energy-report cadence and output naming, with MODEL_VERSION; cymsim cache-keyJeremy Magland 25cymsim cache-key my_run.yaml --show # the run's cache key (independent of the duration) and what it hashes
a68a682Run cache, read path: cymsim run --cache URL (CYMSIM_CACHE_URL), simulate(cfg, cache=URL); a covering entry is cut without building the model, a shorter one is continued; local copies revalidated by ETagJeremy Magland 26cymsim run my_run.yaml --cache https://artifact-cache.tempory.net/api/apps/cymsim # take the run from the run cache, or continue a shorter cached run
ca554abRun cache: upload the partial run every --upload-every seconds of wall time (default 600), so that a killed job continues from its last upload; Simulator.run(checkpoint=...)Jeremy Magland 29With `--cache URL` (or `CYMSIM_CACHE_URL` set), a run is looked up in a run cache under its key (`cymsim cache-key`), which leaves out the duration and the output naming. The cache holds the longest run of each key so far as a `.cymsim` file without the acceleration record: a run it covers is cut from it without building the model, and a longer run continues from it (`--resume`), identical to an uninterrupted run on the same device. Downloads are kept in `~/.cache/cymsim` (`CYMSIM_CACHE_DIR`) and revalidated by ETag. With the cache app's passcode in `CYMSIM_CACHE_KEY`, a run that was computed (from rest or continued) is uploaded, at the end and as a partial run every 10 minutes of wall time (`--upload-every`), so that a killed job continues from its last upload; nothing replaces an entry that meanwhile holds at least as much. Without the passcode the cache is read-only. The entry's metadata records the device, jax version, commit and host of each segment of integration. See [cache.py](src/cymsim/cache.py).
a68a682Run cache, read path: cymsim run --cache URL (CYMSIM_CACHE_URL), simulate(cfg, cache=URL); a covering entry is cut without building the model, a shorter one is continued; local copies revalidated by ETagJeremy Magland 30
e30c940save .cymsim fileJeremy Magland 31A `.cymsim` file is an HDF5 file holding the configuration, the mesh, the listener signals, displacement frames at a low rate, optionally the normal acceleration of every node at the audio rate, and the integrator state; the layout is specified in [docs/format.md](docs/format.md). It is meant for taking the shell's motion into other programs (other listeners, rooms, acoustic solvers) and is the file the web companion cymsimweb saves and loads. A partial file written during a run is a consistent prefix of the complete one.
35```python
36from cymsim import SimulationConfig, Hit, StickExciter, simulate
38cfg = SimulationConfig.from_yaml("examples/crash_mallet.yaml")
39cfg.hits.append(Hit(time=0.8, r=0.19, phi=2.0, velocity=7.0, exciter=StickExciter()))
e30c940save .cymsim fileJeremy Magland 40cfg.output.record_acceleration = True # off by default: about 0.4 GB per simulated second at the default mesh
42result.write_wav("crash")
71e94accymsim: nonlinear FEM cymbal simulation packageJeremy Magland 44print(result.hits) # peak force, contact time and rebound velocity of each hit
45print(result.energy_log) # kinetic / membrane / bending / exciter energy and max|w|/h over time
e30c940save .cymsim fileJeremy Magland 46a = result.acceleration.as_float(0, 4410) # normal acceleration [m/s^2], first 0.1 s, shape (4410, n_nodes)
47w = result.displacement.w # transverse displacement at 60 Hz, shape (n_frames, n_nodes)
49from cymsim import load_cymsim
50back = load_cymsim("crash.cymsim") # the same SimulationResult, from the file
6c740b5Continue a run from a .cymsim file (cymsim run --resume, simulate(cfg, resume=...)), identical to the uninterrupted run; keep the outputs past the duration in the state and store probe signals in the fileJeremy Magland 51cfg.numerics.duration = 4.0
52longer = simulate(cfg, resume=back) # continue it to 4 s; identical to a 4 s run on the same device
e19a540Add a prescribed-force exciter and point probes of the normal acceleration; accept Python 3.9 in the YAML loaderJeremy Magland 55A run is described by one `SimulationConfig` made of `Material`, `Profile`, `Imperfections`, `MeshConfig`, a list of `Hit`s (each with a `StickExciter`, a `MalletExciter`, or a `ForceExciter` for a prescribed force pulse), `Listener`, `Numerics` and `Output`. The YAML file mirrors the dataclass fields; omitted fields take the defaults, which reproduce an 18 inch medium-thin B20 crash. Units are SI throughout. See [examples/](examples/) for a mallet stroke, a stick crash, a train of four hits and a quick CPU run.
71e94accymsim: nonlinear FEM cymbal simulation packageJeremy Magland 56
57## Layout
59| module | contents |
60|---|---|
61| [config.py](src/cymsim/config.py) | configuration dataclasses, YAML load/save, validation |
62| [geometry.py](src/cymsim/geometry.py) | mid-surface height z0(x, y) and thickness h(x, y), seeded imperfections |
63| [mesh.py](src/cymsim/mesh.py) | numpy disk mesher (concentric rings, angular stitching, smoothing), CST operators |
64| [elements.py](src/cymsim/elements.py) | DKT bending stiffness, Marguerre-von Karman membrane strains, element energies |
65| [shell.py](src/cymsim/shell.py) | assembled shell: lumped mass, rigid modes, internal force by autodiff, stability bound |
66| [exciter.py](src/cymsim/exciter.py) | contact patches, power-law contact law, schedule of hits |
67| [radiation.py](src/cymsim/radiation.py) | Rayleigh integral and the unbaffled (rim-diffraction) approximation |
e30c940save .cymsim fileJeremy Magland 68| [decimation.py](src/cymsim/decimation.py) | the anti-aliasing kernel that takes accelerations and pressure from the sub-step rate to the audio rate |
69| [simulate.py](src/cymsim/simulate.py) | leapfrog time loop, block compilation, acceleration and displacement records, result container |
70| [cymsim_file.py](src/cymsim/cymsim_file.py) | the .cymsim HDF5 file: streaming writer, loader, summary |
a68a682Run cache, read path: cymsim run --cache URL (CYMSIM_CACHE_URL), simulate(cfg, cache=URL); a covering entry is cut without building the model, a shorter one is continued; local copies revalidated by ETagJeremy Magland 71| [cache.py](src/cymsim/cache.py) | run cache: keys (hash of the non-default config fields without duration and output naming, and `MODEL_VERSION`), download, cut or continue |
71e94accymsim: nonlinear FEM cymbal simulation packageJeremy Magland 72| [diagnostics.py](src/cymsim/diagnostics.py) | band envelopes, onset times, spectral centroid, cascade plot |
4931449Cache keys for runs: hash of the non-default config fields without duration, energy-report cadence and output naming, with MODEL_VERSION; cymsim cache-keyJeremy Magland 73| [cli.py](src/cymsim/cli.py) | `cymsim simulate` (`run`), `cymsim check`, `cymsim info`, `cymsim cache-key`, `cymsim example-config` |
71e94accymsim: nonlinear FEM cymbal simulation packageJeremy Magland 74
75`prototype.py` is the original single-file prototype that this package reorganizes; it is kept for reference and is not used by the package.
77## Tests
79```
80pytest
81```
e30c940save .cymsim fileJeremy Magland 83The tests check the mesher (manifold, orientation, quality), the DKT element (rigid modes, constant curvature, twist), the membrane strains, the assembled shell (mass, rigid-mode annihilation, force = minus energy gradient, dissipative damping), the configuration round trip, the decimation kernel and streaming decimator, short coarse simulations with one and two hits, and the .cymsim file (round trip, partial files, and that the baffled listener signal is recovered from the stored accelerations alone).