"""Predefined designs: flat and bowed sheets, discs, tapered cymbals with and without a bell, an ellipse, a thunder sheet. Each is a function returning a ``Design``, so that it can be modified before evaluation:: d = presets.crash_16() d.thickness = 1.0e-3 plate = d.evaluate(0.008) """ from __future__ import annotations import math import numpy as np from .design import Design from .material import ALUMINUM, BRONZE, MILD_STEEL, STAINLESS_STEEL inch = 0.0254 mm = 1e-3 def ramp(a: float, b: float, x): """0 at x = a, 1 at x = b, clamped to that range outside.""" return np.clip((x - a) / (b - a), 0, 1) def bow(s, span: float, sag: float, offset: float = 0.0): """A parabola of the given sag across a span centered on 0; ``s`` runs along the span, and ``offset`` in -1..1 moves the crest toward one end.""" t = 2 * s / span return sag * (1 - t**2) * (1 + offset * t) def dome(d, radius: float, height: float): """A cosine dome of the given height and base radius, with zero slope both at its rim and at its apex; ``d`` is the distance from its center.""" return 0.5 * height * (1 + np.cos(math.pi * d / radius)) * (d < radius) def dents(x, y, r0: float, r1: float, n: int, rd: float, depth: float, random: bool = True, seed: int = 1): """``n`` hammer marks between the radii ``r0`` and ``r1``: Gaussian dents of 1/e radius ``rd``, placed on a sunflower spiral (equal area per mark, golden-angle spacing) so that the pattern has no symmetry, or at random with uniform density over the annulus, from a small congruential generator so that a seed always gives the same pattern.""" v = np.zeros_like(x) ga = math.pi * (3 - math.sqrt(5)) s = seed for k in range(1, n + 1): if random: s = (s * 1664525 + 1013904223) % 4294967296 u = s / 4294967296 s = (s * 1664525 + 1013904223) % 4294967296 w = s / 4294967296 rk = math.sqrt(r0**2 + (r1**2 - r0**2) * u) tk = 2 * math.pi * w else: rk = math.sqrt(r0**2 + (r1**2 - r0**2) * (k - 0.5) / n) tk = k * ga dx = x - rk * math.cos(tk) dy = y - rk * math.sin(tk) v = v - depth * np.exp(-(dx**2 + dy**2) / rd**2) return v def flat_rectangle() -> Design: """A flat 12 by 18 inch sheet of stainless steel, 0.79 mm thick, hanging freely.""" return Design("Flat steel rectangle", STAINLESS_STEEL, 12 * inch, 18 * inch, 0.79 * mm) def aluminum_square() -> Design: """A flat 12 inch square of 6061-T6 aluminum, 1.02 mm thick. Aluminum is a third the density of steel and a third as stiff, so the bending frequencies land near those of a steel plate of the same size and thickness; its high thermal expansion and conductivity give it the strongest thermoelastic damping.""" return Design("Aluminum 6061-T6 square", ALUMINUM, 12 * inch, 12 * inch, 1.02 * mm) def bowed_rectangle() -> Design: """The steel sheet with a 3 mm parabolic bow across its width, the kind of bow that rolling and handling leave in sheet metal. The bow makes the sheet a shallow shell: the linear modes couple bending to stretching, and the full model oil-cans and wobbles when struck.""" W = 12 * inch return Design("Bowed steel rectangle", STAINLESS_STEEL, W, 18 * inch, 0.79 * mm, profile=lambda x, y, r, t: bow(x, W, 3 * mm, 0.3)) def flat_disc() -> Design: """A flat 16 inch bronze disc, 1 mm thick.""" D = 16 * inch return Design("Flat bronze disc", BRONZE, D, D, 1.0 * mm, inside=lambda x, y, r, t: r <= D / 2) def crash_16() -> Design: """A 16 inch flat crash: bronze, lathed to a taper from 1.2 mm at the center to 0.7 mm at the edge. Flat, so the linear modes are pure bending modes.""" D = 16 * inch R = D / 2 tc, te = 1.2 * mm, 0.7 * mm return Design("16 inch flat crash", BRONZE, D, D, lambda x, y, r, t: tc + (te - tc) * ramp(40 * mm, R, r), inside=lambda x, y, r, t: r <= R) def ride_20() -> Design: """A 20 inch flat ride: thicker bronze, 2.2 mm at the center to 1.4 mm at the edge.""" D = 20 * inch R = D / 2 return Design("20 inch flat ride", BRONZE, D, D, lambda x, y, r, t: 2.2 * mm + (1.4 * mm - 2.2 * mm) * ramp(50 * mm, R, r), inside=lambda x, y, r, t: r <= R) def crash_bell_16() -> Design: """A 16 inch crash with a shallow bow and a cosine bell. The profile makes the cymbal a shallow shell (a three-field linear system, so the modes take longer), and the full model shows the asymmetric nonlinearity of a curved plate. The slopes are kept small; ``Plate.profile_stats`` reports the largest.""" D = 16 * inch R = D / 2 bell_r, bell_h, bow_h = 55 * mm, 8 * mm, 10 * mm tc, te = 1.2 * mm, 0.7 * mm return Design( "16 inch crash with bell", BRONZE, D, D, lambda x, y, r, t: tc + (te - tc) * ramp(bell_r, R, r), inside=lambda x, y, r, t: r <= R, profile=lambda x, y, r, t: bow_h * (1 - (r / R) ** 2) + dome(r, bell_r, bell_h), ) def crash_hammered_16(random_dents: bool = True, seed: int = 1, n_dents: int = 160, dent_depth: float = 0.4 * mm) -> Design: """The 16 inch crash with bell, hammered: shallow dents on the bow, at random places (``seed`` picks the pattern) or on a sunflower spiral. A hammered cymbal is no longer axisymmetric, so its degenerate mode pairs split and its modes mix. The dents are a change of the rest shape; the residual stresses of real hammering are not modeled.""" D = 16 * inch R = D / 2 bell_r, bell_h, bow_h = 55 * mm, 8 * mm, 10 * mm tc, te = 1.2 * mm, 0.7 * mm dent_r = 12 * mm return Design( "16 inch hammered crash", BRONZE, D, D, lambda x, y, r, t: tc + (te - tc) * ramp(bell_r, R, r), inside=lambda x, y, r, t: r <= R, profile=lambda x, y, r, t: bow_h * (1 - (r / R) ** 2) + dome(r, bell_r, bell_h) + dents(x, y, bell_r + dent_r, R - dent_r, n_dents, dent_r, dent_depth, random_dents, seed), ) def ellipse() -> Design: """A 14 by 10 inch elliptical steel plate, 1 mm thick. The degenerate mode pairs of a disc split on an ellipse.""" A, B = 14 * inch, 10 * inch return Design("Steel ellipse", STAINLESS_STEEL, A, B, 1.0 * mm, inside=lambda x, y, r, t: (x / (A / 2)) ** 2 + (y / (B / 2)) ** 2 <= 1) def thunder_sheet() -> Design: """A thunder sheet: a 24 by 36 inch steel sheet, 0.5 mm thick. Its modes are dense and low, and the full model turns a hard hit into a roar. Long at the default spacing; a coarser grid keeps it interactive.""" return Design("Thunder sheet", MILD_STEEL, 24 * inch, 36 * inch, 0.5 * mm) PRESETS = { "flat-rectangle": flat_rectangle, "aluminum-square": aluminum_square, "bowed-rectangle": bowed_rectangle, "flat-disc": flat_disc, "crash-16": crash_16, "ride-20": ride_20, "crash-bell-16": crash_bell_16, "crash-hammered-16": crash_hammered_16, "ellipse": ellipse, "thunder-sheet": thunder_sheet, } """The presets by name.""" def preset(name: str) -> Design: """A preset design by name (see ``PRESETS``).""" try: return PRESETS[name]() except KeyError: raise KeyError(f"unknown preset {name!r}; choose from {', '.join(PRESETS)}") from None