"""Materials: elastic constants, the loss factor of internal friction, and the thermal constants from which the thermoelastic damping follows.""" from __future__ import annotations import math from dataclasses import dataclass from .constants import SPEED_OF_SOUND @dataclass(frozen=True) class Thermal: """Thermal constants of a metal. All three are needed for Zener's thermoelastic damping.""" alpha: float """Linear thermal expansion [1/K].""" cp: float """Specific heat [J/(kg K)].""" k: float """Thermal conductivity [W/(m K)].""" def __post_init__(self) -> None: if not (self.alpha >= 0 and self.cp >= 1 and self.k >= 1e-3): raise ValueError("thermal constants must be positive (alpha >= 0, cp >= 1, k >= 1e-3)") @dataclass(frozen=True) class Material: """A homogeneous isotropic metal. The decay of the sound is not prescribed but derived from ``loss_factor`` (the hysteretic loss factor of the metal's internal friction, of order 1e-4 for bronze, steel and aluminum), from ``thermal`` (Zener's thermoelastic damping; ``None`` leaves it out) and from the radiation of the plate into the air, which the models compute from the geometry. """ E: float """Young's modulus [Pa].""" nu: float """Poisson ratio.""" rho: float """Density [kg/m^3].""" loss_factor: float = 0.0 """Loss factor eta of internal friction: a mode decays at the rate eta omega / 2.""" thermal: Thermal | None = None """Thermal constants for the thermoelastic damping, or None for none.""" def __post_init__(self) -> None: if not self.E >= 1e6: raise ValueError("E must be at least 1e6 Pa") if not 0 <= self.nu < 0.5: raise ValueError("nu must lie in [0, 0.5)") if not self.rho >= 1: raise ValueError("rho must be at least 1 kg/m^3") if not 0 <= self.loss_factor < 0.1: raise ValueError("loss_factor must be small (metals have 1e-4 to 1e-3)") def bending_stiffness(self, h): """D = E h^3 / (12 (1 - nu^2)) [N m]; ``h`` may be an array.""" return self.E * h**3 / (12 * (1 - self.nu**2)) def membrane_stiffness(self, h): """C = E h / (1 - nu^2) [N/m].""" return self.E * h / (1 - self.nu**2) def shear_stiffness(self, h): """G = E h / (2 (1 + nu)) [N/m].""" return self.E * h / (2 * (1 + self.nu)) @property def in_plane_speed(self) -> float: """Speed of in-plane waves, sqrt(E / (rho (1 - nu^2))) [m/s]; it sets the time step of the full model.""" return math.sqrt(self.E / (self.rho * (1 - self.nu**2))) def bending_constant(self, h): """kappa = sqrt(D / (rho h)) [m^2/s], so that omega = kappa k^2 for a bending wave.""" return (self.bending_stiffness(h) / (self.rho * h)) ** 0.5 def coincidence_frequency(self, h): """Frequency at which the bending wavelength equals the wavelength of sound, c^2 / (2 pi kappa) [Hz].""" return SPEED_OF_SOUND**2 / (2 * math.pi * self.bending_constant(h)) STAINLESS_STEEL = Material(E=170e9, nu=0.29, rho=7900, loss_factor=3e-4, thermal=Thermal(17e-6, 500, 16)) """Stainless steel sheet.""" MILD_STEEL = Material(E=200e9, nu=0.3, rho=7850, loss_factor=3e-4, thermal=Thermal(12e-6, 460, 50)) """Carbon steel sheet.""" BRONZE = Material(E=110e9, nu=0.34, rho=8800, loss_factor=2e-4, thermal=Thermal(18e-6, 370, 40)) """Bell bronze, as used for cymbals.""" ALUMINUM = Material(E=68.9e9, nu=0.33, rho=2700, loss_factor=1e-4, thermal=Thermal(23.6e-6, 896, 167)) """6061-T6 aluminum."""