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 r is approximated by a coprime pair (m, n) with m, 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' uses Fraction.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:

GeometryDatageom_type='field_2d' with coordinates the (R, R) field and field_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 of J_n (m is 1-indexed). Outside the disk the field is set to NaN.

Parameters:
  • modes_radial (sequence of int) – Radial mode index (m, 1-indexed). Same length as modes_angular.

  • modes_angular (sequence of int) – Angular mode index (n, 0). Same length as modes_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:

GeometryDatageom_type='field_2d'. Values outside the disk are NaN.

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 (0 is 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²) to O(n³) in the interior cell count.

  • solver ({‘fdm’, ‘fem’}, default=’fdm’) – 'fem' requires the optional scikit-fem dependency and is not yet wired through; selecting it raises NotImplementedError.

Returns:

GeometryDatageom_type='field_2d'. Cells outside the polygon are NaN.

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:

GeometryDatageom_type='field_3d' with coordinates the (R, R, R) scalar field and field_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:

GeometryDatageom_type='curve_set_2d' — one (N_i, 2) array per contour, with coordinates in the same units as the input field_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:

GeometryDatageom_type='mesh_3d' with vertex coordinates in the same units as the input field_grid and triangle faces.

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, using input as the mode source.

The phases used in the field are shifted by · f_k · t for each component (with f_k taken from input.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) via ratios_to_modes(); amplitudes are taken from input.normalized_amplitudes(); phases default to the input’s phases (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)