import numpy as np
import cmath
from treams import misc
[docs]
class AcousticMaterial:
r"""Material definition.
The material properties are defined in the frequency domain through scalar values
for density :math:`\rho`, the speed of longitudinal elastic waves :math:`c`, and
the speed of transverse elastic waves :math:`c`. Materials are, thus, assumed to be linear,
time-invariant, homogeneous, isotropic, and local. Moreover, it is assumed that they have no gain.
Args:
rho (optional, complex): Mass density. Defaults to 1.3 [kg/m^3].
c (optional, complex): Longitudinal speed of sound. Defaults to 343 [m/s].
ct (optional, complex): Transverse speed of sound. Defaults to 0 [m/s].
"""
def __init__(self, rho=1.3, c=343., ct=0.):
"""Initialization."""
if isinstance(rho, AcousticMaterial):
rho, c, ct = rho()
elif isinstance(rho, (tuple, list, np.ndarray)):
if len(rho) == 0:
rho = 1.3
elif len(rho) == 1:
rho = rho[0]
elif len(rho) == 2:
rho, c = rho
elif len(rho) == 3:
rho, c, ct = rho
else:
raise ValueError("invalid material definition")
self._rho = rho
self._c = c
self._ct = ct
@property
def rho(self):
"""Mass density.
Returns:
float or complex
"""
return self._rho
@property
def c(self):
"""Speed of longitudinal elastic waves.
Returns:
float or complex
"""
return self._c
@property
def ct(self):
"""Speed of transverse elastic waves.
Returns:
float or complex
"""
return self._ct
def __iter__(self):
"""Iterator for a tuple containing the material parameters.
Useful for unpacking the material parameters into a function that takes these
parameters separately, e.g. ``foo(*material)``.
"""
return iter((self.rho, self.c, self.ct))
[docs]
@classmethod
def from_params(cls, rho=1.3, params=(151767.21, 0.)):
r"""Create acoustic material from Lamé parameters.
This function calculates the longitudinal and transverse speeds of sound with
:math:`c = \sqrt{\frac{\lambda+2\mu}{\rho}}` and :math:`c_t = \sqrt{\frac{\mu}{\rho}}`.
Args:
rho (complex, optional): Mass density. Defaults to 1.3.
params (complex, optional): Lamé parameters. Defaults to (151767.21, 0.).
Returns:
AcousticMaterial
"""
c = cmath.sqrt((params[0] + 2 * params[1]) / rho)
ct = cmath.sqrt(params[1] / rho)
return cls(rho, c, ct)
[docs]
@classmethod
def from_pratio(cls, rho=1.3, c=343, pratio=0.5):
r"""Create acoustic material from Poisson's ratio.
This function calculates the transverse speed of sound with
:math:`c_t = c \sqrt{\frac{1 - 2\sigma}{2 - 2\sigma}}`.
Args:
rho (complex, optional): Mass density. Defaults to 1.3.
c (complex, optional): Longitudinal speed of sound. Defaults to 343.
pratio (complex, optional): Poisson's ratio. Defaults to 0.5.
Returns:
AcousticMaterial
"""
ct = c * cmath.sqrt((1 - 2 * pratio) / (2 - 2 * pratio))
return cls(rho, c, ct)
@property
def impedance(self):
r"""Relative impedance for longitudinal waves.
The relative impedance is defined by :math:`Z = \rho c`.
Returns:
complex
"""
return self.rho * self.c
@property
def impedancet(self):
r"""Relative impedance for transverse waves..
The relative impedance is defined by :math:`Z = \rho c_t`.
Returns:
complex
"""
return self.rho * self.ct
[docs]
def __call__(self):
"""Return a tuple containing all material parameters.
Returns:
tuple
"""
return self.rho, self.c, self.ct
[docs]
def __eq__(self, other):
"""Compare material parameters.
Materials are considered equal, when all material parameters are equal. Also,
compares with objects that contain at most three values.
Returns:
bool
"""
if other is None:
return False
if not isinstance(other, AcousticMaterial):
other = AcousticMaterial(*other)
return (
self.rho == other.rho
and self.c == other.c
and self.ct == other.ct
)
@property
def isreal(self):
"""Test if the material has purely real parameters.
Returns:
bool
"""
return all(i.imag == 0 for i in self)
@property
def isshear(self):
"""Test if the material supports transverse (shear) elastic waves.
Returns:
bool
"""
return self.ct != 0
[docs]
def __str__(self):
"""All three material parameters.
Returns:
str
"""
return "(" + ", ".join([str(i) for i in self()]) + ")"
[docs]
def __repr__(self):
"""Representation that allows recreating the object.
Returns:
str
"""
return self.__class__.__name__ + str(self)
[docs]
def ks(self, k0):
"""Return the wave number of longitudinal waves in the medium.
Args:
k0 (float): Wave number in air.
Returns:
tuple
"""
if isinstance(self.c, (tuple, list, np.ndarray)):
return tuple(k0 * AcousticMaterial().c / c for c in self.c)
else:
return k0 * AcousticMaterial().c / self.c
[docs]
def kst(self, k0):
"""Return the wave number of transverse waves in the medium.
Args:
k0 (float): Wave number in air.
Returns:
tuple
"""
if isinstance(self.ct, (tuple, list, np.ndarray)):
return tuple(k0 * AcousticMaterial().c / ct for ct in self.ct)
else:
return k0 * AcousticMaterial().c / self.ct
[docs]
def krhos(self, k0, kz):
r"""The (cylindrically) radial part of the wave vector for longitudinal waves .
The cylindrically radial part is defined by :math:`k_\rho = \sqrt(k^2 - k_z^2)`.
The returned values have non-negative imaginary parts.
Args:
k0 (float): Wave number in air.
kz (float, array-like): z-component of the wave vector
Returns:
complex, array-like
"""
ks = self.ks(k0)
return misc.wave_vec_z(kz, 0, ks)
[docs]
def kzs(self, k0, kx, ky):
r"""The z-component of the wave vector for longitudinal waves.
The z-component of the wave vector is defined by
:math:`k_z = \sqrt(k^2 - k_x^2 - k_y^2)`. The returned values have
non-negative imaginary parts.
Args:
k0 (float): Wave number in air.
kx (float, array-like): x-component of the wave vector
ky (float, array-like): y-component of the wave vector
Returns:
complex, array-like
"""
ks = self.ks(k0)
return misc.wave_vec_z(kx, ky, ks)
[docs]
def krhost(self, k0, kz):
r"""The (cylindrically) radial part of the wave vector for transverse waves.
The cylindrically radial part is defined by :math:`k_\rho = \sqrt(k^2 - k_z^2)`.
The returned values have non-negative imaginary parts.
Args:
k0 (float): Wave number in air.
kz (float, array-like): z-component of the wave vector
Returns:
complex, array-like
"""
ks = self.kst(k0)
return misc.wave_vec_z(kz, 0, ks)
[docs]
def kzst(self, k0, kx, ky):
r"""The z-component of the wave vector for transverse waves.
The z-component of the wave vector is defined by
:math:`k_z = \sqrt(k^2 - k_x^2 - k_y^2)`. The returned values have
non-negative imaginary parts.
Args:
k0 (float): Wave number in air.
kx (float, array-like): x-component of the wave vector
ky (float, array-like): y-component of the wave vector
Returns:
complex, array-like
"""
ks = self.kst(k0)
return misc.wave_vec_z(kx, ky, ks)