# cymsim Explicit 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). ## Installation ``` pip install -e ".[dev]" # numpy, scipy, jax (CPU), pyyaml, pytest, matplotlib pip install "jax[cuda12]" # optional: run the time loop on a GPU ``` Runs 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`. ## Usage ``` cymsim example-config my_run.yaml --exciter mallet # write a default config cymsim check my_run.yaml # mesh, time step, element self-tests, no time stepping cymsim simulate my_run.yaml # run; writes _rayleigh.wav, _unbaffled.wav, _result.npz, _cascade.png cymsim simulate my_run.yaml --linear --duration 0.5 # linear reference run ``` The same from Python: ```python from cymsim import SimulationConfig, Hit, StickExciter, simulate cfg = SimulationConfig.from_yaml("examples/crash_mallet.yaml") cfg.hits.append(Hit(time=0.8, r=0.19, phi=2.0, velocity=7.0, exciter=StickExciter())) result = simulate(cfg) result.write_wav("crash") print(result.hits) # peak force, contact time and rebound velocity of each hit print(result.energy_log) # kinetic / membrane / bending / exciter energy and max|w|/h over time ``` A 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. ## Layout | module | contents | |---|---| | [config.py](src/cymsim/config.py) | configuration dataclasses, YAML load/save, validation | | [geometry.py](src/cymsim/geometry.py) | mid-surface height z0(x, y) and thickness h(x, y), seeded imperfections | | [mesh.py](src/cymsim/mesh.py) | numpy disk mesher (concentric rings, angular stitching, smoothing), CST operators | | [elements.py](src/cymsim/elements.py) | DKT bending stiffness, Marguerre-von Karman membrane strains, element energies | | [shell.py](src/cymsim/shell.py) | assembled shell: lumped mass, rigid modes, internal force by autodiff, stability bound | | [exciter.py](src/cymsim/exciter.py) | contact patches, power-law contact law, schedule of hits | | [radiation.py](src/cymsim/radiation.py) | Rayleigh integral and the unbaffled (rim-diffraction) approximation | | [simulate.py](src/cymsim/simulate.py) | leapfrog time loop, block compilation, result container | | [diagnostics.py](src/cymsim/diagnostics.py) | band envelopes, onset times, spectral centroid, cascade plot | | [cli.py](src/cymsim/cli.py) | `cymsim simulate`, `cymsim check`, `cymsim example-config` | `prototype.py` is the original single-file prototype that this package reorganizes; it is kept for reference and is not used by the package. ## Tests ``` pytest ``` The 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.