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```
8pip install -e ".[dev]" # numpy, scipy, jax (CPU), pyyaml, pytest, matplotlib
9pip install "jax[cuda12]" # optional: run the time loop on a GPU
10```
12Runs at the default resolution (6 mm elements, about 4700 nodes and 40 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`.
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
21```
23The same from Python:
25```python
26from cymsim import SimulationConfig, Hit, StickExciter, simulate
28cfg = SimulationConfig.from_yaml("examples/crash_mallet.yaml")
29cfg.hits.append(Hit(time=0.8, r=0.19, phi=2.0, velocity=7.0, exciter=StickExciter()))
30result = simulate(cfg)
31result.write_wav("crash")
32print(result.hits) # peak force, contact time and rebound velocity of each hit
33print(result.energy_log) # kinetic / membrane / bending / exciter energy and max|w|/h over time
34```
36A run is described by one `SimulationConfig` made of `Material`, `Profile`, `Imperfections`, `MeshConfig`, a list of `Hit`s (each with a `StickExciter` or `MalletExciter`), `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.
38## Layout
40| module | contents |
41|---|---|
42| [config.py](src/cymsim/config.py) | configuration dataclasses, YAML load/save, validation |
43| [geometry.py](src/cymsim/geometry.py) | mid-surface height z0(x, y) and thickness h(x, y), seeded imperfections |
44| [mesh.py](src/cymsim/mesh.py) | numpy disk mesher (concentric rings, angular stitching, smoothing), CST operators |
45| [elements.py](src/cymsim/elements.py) | DKT bending stiffness, Marguerre-von Karman membrane strains, element energies |
46| [shell.py](src/cymsim/shell.py) | assembled shell: lumped mass, rigid modes, internal force by autodiff, stability bound |
47| [exciter.py](src/cymsim/exciter.py) | contact patches, power-law contact law, schedule of hits |
48| [radiation.py](src/cymsim/radiation.py) | Rayleigh integral and the unbaffled (rim-diffraction) approximation |
49| [simulate.py](src/cymsim/simulate.py) | leapfrog time loop, block compilation, result container |
50| [diagnostics.py](src/cymsim/diagnostics.py) | band envelopes, onset times, spectral centroid, cascade plot |
51| [cli.py](src/cymsim/cli.py) | `cymsim simulate`, `cymsim check`, `cymsim example-config` |
53`prototype.py` is the original single-file prototype that this package reorganizes; it is kept for reference and is not used by the package.
55## Tests
57```
58pytest
59```
61The 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, and short coarse simulations with one and two hits.