Chladni#
Chladni nodal fields and surfaces.
This module produces scalar displacement fields for vibrating plates and volumes, plus utilities for extracting their nodal sets (the curves / surfaces where the displacement is zero — the lines along which sand collects in a Chladni experiment).
Plate kinds supported#
Rectangular (free-edge superposition): closed-form Neumann modes.
Circular (clamped membrane): Bessel × angular modes.
Polygon (clamped membrane): numerical eigenmode solver via finite differences on a rasterized polygon mask.
3-D box (Dirichlet): closed-form standing-wave modes in a volume.
Nodal extraction#
2-D: marching squares via
skimage.measure.find_contours().3-D: marching cubes via
skimage.measure.marching_cubes().
Both extraction functions lazy-import scikit-image. If the dependency
is missing they raise ImportError with installation instructions.
References
- ratios_to_modes(ratios: Iterable[Fraction | int | float | Tuple[int, int]], strategy: str = 'stern_brocot', max_mode: int = 20) List[Tuple[int, int]][source]#
Map a list of ratios to Chladni mode pairs
(m, n).Each ratio
ris approximated by a coprime pair(m, n)withm, n ≤ max_mode(when feasible).- Parameters:
ratios (iterable of Fraction, int, float, or (int, int))
strategy ({‘stern_brocot’, ‘continued_fraction’, ‘rounded’, ‘best_simple’}) – Algorithm used to pick
(m, n).'stern_brocot'usesFraction.limit_denominator()(Stern-Brocot mediant search);'continued_fraction'walks the continued-fraction convergents and stops at the last one with both terms≤ max_mode;'rounded'returns(round(r), 1)for each ratio (cheap and coarse);'best_simple'brute-forces the closest(m, n)pair in[1, max_mode]^2.max_mode (int, default=20)
- Returns:
list of (int, int)
- chladni_field_rectangular(modes: Sequence[Tuple[int, int]], amps: Sequence[float] | None = None, phases: Sequence[float] | None = None, Lx: float = 1.0, Ly: float = 1.0, resolution: int = 256) GeometryData[source]#
Free-edge rectangular plate displacement.
u(x, y) = Σ_k A_k · cos(m_k π x / Lx) · cos(n_k π y / Ly) · cos(φ_k)- Parameters:
modes (sequence of (int, int)) – Mode pairs
(m, n).amps (sequence of float, optional) – Amplitudes per mode. Defaults to uniform
1 / n_modes.phases (sequence of float, optional) – Phase per mode in radians. Defaults to zeros.
Lx, Ly (float, default=1.0) – Plate dimensions.
resolution (int, default=256) – Grid resolution along each axis. The output field is
(resolution, resolution).
- Returns:
GeometryData –
geom_type='field_2d'withcoordinatesthe(R, R)field andfield_grid=(X, Y)meshgrid arrays.
- chladni_field_circular(modes_radial: Sequence[int], modes_angular: Sequence[int], amps: Sequence[float] | None = None, phases: Sequence[float] | None = None, R: float = 1.0, resolution: int = 256) GeometryData[source]#
Clamped circular membrane displacement.
For each mode
k:u_k(r, θ) = J_{n_k}(α_{n_k, m_k} · r / R) · cos(n_k · θ), whereα_{n, m}is the m-th positive zero ofJ_n(m is 1-indexed). Outside the disk the field is set toNaN.- Parameters:
modes_radial (sequence of int) – Radial mode index (
m, 1-indexed). Same length asmodes_angular.modes_angular (sequence of int) – Angular mode index (
n,≥ 0). Same length asmodes_radial.amps, phases (sequences of float, optional)
R (float, default=1.0) – Disk radius.
resolution (int, default=256) – Square-grid resolution covering
[-R, R]^2.
- Returns:
GeometryData –
geom_type='field_2d'. Values outside the disk areNaN.
- chladni_field_polygon(modes: Sequence[int], n_sides: int, amps: Sequence[float] | None = None, phases: Sequence[float] | None = None, radius: float = 1.0, resolution: int = 128, solver: str = 'fdm') GeometryData[source]#
Clamped regular polygon membrane displacement.
Solves the Dirichlet eigenproblem
-∇²ψ = λψnumerically on a rasterized polygon mask, then sums a chosen subset of eigenmodes.- Parameters:
modes (sequence of int) – 0-indexed eigenmode indices (
0is the fundamental).n_sides (int) – Polygon side count,
>= 3.amps, phases (sequences of float, optional)
radius (float, default=1.0) – Circumradius of the polygon.
resolution (int, default=128) – Bounding-square grid resolution. Lower than the rectangular default because the FDM eigenproblem is
O(n²)toO(n³)in the interior cell count.solver ({‘fdm’, ‘fem’}, default=’fdm’) –
'fem'requires the optionalscikit-femdependency and is not yet wired through; selecting it raisesNotImplementedError.
- Returns:
GeometryData –
geom_type='field_2d'. Cells outside the polygon areNaN.
- chladni_field_3d_box(modes_3d: Sequence[Tuple[int, int, int]], amps: Sequence[float] | None = None, phases: Sequence[float] | None = None, dimensions: Tuple[float, float, float] = (1.0, 1.0, 1.0), resolution: int = 48) GeometryData[source]#
Standing-wave displacement field in a 3-D box (Dirichlet BC).
u(x, y, z) = Σ_k A_k · sin(l_k π x / Lx) · sin(m_k π y / Ly) · sin(n_k π z / Lz) · cos(φ_k)- Parameters:
modes_3d (sequence of (int, int, int)) – Mode triples
(l, m, n)with all entries>= 1.amps, phases (sequences of float, optional)
dimensions ((float, float, float), default=(1, 1, 1)) –
(Lx, Ly, Lz)box dimensions.resolution (int, default=48) – Grid resolution per axis. Total memory scales as
O(R³); the default 48³ ≈ 110k cells is a balance for a quick eigen-sum.
- Returns:
GeometryData –
geom_type='field_3d'withcoordinatesthe(R, R, R)scalar field andfield_grid=(X, Y, Z).
- chladni_nodal_lines(field_data: GeometryData, threshold: float = 0.001) GeometryData[source]#
Extract nodal lines from a 2-D Chladni field via marching squares.
- Parameters:
field_data (GeometryData) – Must have
geom_type='field_2d'.threshold (float, default=1e-3) – Iso-value at which to extract contours. Cells where the field is
NaN(e.g. outside a circular plate) are treated as the boundary and skipped during extraction.
- Returns:
GeometryData –
geom_type='curve_set_2d'— one(N_i, 2)array per contour, with coordinates in the same units as the inputfield_grid.
- chladni_nodal_surfaces(field_3d: GeometryData, threshold: float = 0.001) GeometryData[source]#
Extract a 2-D nodal surface mesh from a 3-D Chladni field.
- Parameters:
field_3d (GeometryData) – Must have
geom_type='field_3d'.threshold (float, default=1e-3) – Iso-value of the marching-cubes extraction.
- Returns:
GeometryData –
geom_type='mesh_3d'with vertex coordinates in the same units as the inputfield_gridand trianglefaces.
- chladni_temporal(input: HarmonicInput, t: float, plate: str = 'rectangular', mode_strategy: str = 'stern_brocot', max_mode: int = 20, **plate_kwargs: Any) GeometryData[source]#
Chladni field at a specific time
t, usinginputas the mode source.The phases used in the field are shifted by
2π · f_k · tfor each component (withf_ktaken frominput.peaks), implementing the standing-wave time evolution. Suitable for assembling a time sequence of frames.- Parameters:
input (HarmonicInput)
t (float) – Time in seconds.
plate ({‘rectangular’, ‘circular’, ‘polygon’, ‘box_3d’}, default=’rectangular’)
mode_strategy (str, default=’stern_brocot’) – Forwarded to
ratios_to_modes().max_mode (int, default=20)
**plate_kwargs – Forwarded to the underlying field builder.
- Returns:
GeometryData
- chladni_from_input(input: HarmonicInput, plate: str = 'rectangular', plate_kwargs: dict | None = None, mode_strategy: str = 'stern_brocot', max_mode: int = 20) GeometryData[source]#
Build a Chladni field from a
HarmonicInput.The input’s ratios are mapped to mode pairs (or triples, for
box_3d) viaratios_to_modes(); amplitudes are taken frominput.normalized_amplitudes(); phases default to the input’sphases(or zero).- Parameters:
input (HarmonicInput)
plate ({‘rectangular’, ‘circular’, ‘polygon’, ‘box_3d’}, default=’rectangular’)
plate_kwargs (dict, optional) – Extra keyword arguments forwarded to the underlying field builder (e.g.
Lx,Ly,resolution,n_sides).mode_strategy (str, default=’stern_brocot’)
max_mode (int, default=20)