from __future__ import annotations
from enum import Enum
from numpy import absolute
from osaft import log
from osaft.core.fluids import InviscidFluid
from osaft.core.functions import exp
from osaft.core.variable import (
ActiveListVariable,
ActiveVariable,
PassiveVariable,
)
class DocEnum(Enum):
def __new__(cls, value, doc=None):
self = object.__new__(cls)
self._value_ = value
self.__doc__ = doc
return self
[docs]class WaveType(DocEnum):
"""Supported Wavetypes for the acoustic background field"""
STANDING = 0, "Plane standing wave"
TRAVELLING = 1, "Plane travelling wave"
[docs]class WrongWaveTypeError(Exception):
"""Exception for using the wrong Wave type"""
def __init__(self, msg=None, *args, **kwargs):
if msg is None:
msg = (
"Wavetype needs to be of type:"
f" {[t.name for t in WaveType]}"
)
super().__init__(msg, *args, **kwargs)
[docs]class BackgroundField:
"""BackgroundField class which is a base class for all background field
classes
:param fluid: fluid for to be used for the background field
:param p_0: pressure amplitude in [Pa]
:param wave_type: wave type: either standing or traveling / travelling
:param position: position of the scatterer in the standing wave in [rad]
"""
def __init__(
self,
fluid: InviscidFluid,
p_0: float,
wave_type: WaveType = WaveType.STANDING,
position: None | float = None,
) -> None:
"""Constructor Method"""
self.fluid = fluid
# Check position variable
if position is None and wave_type == WaveType.STANDING:
raise ValueError(
"Argument position needs to be given for a " "standing wave.",
)
# Independent Variables
self._p_0 = PassiveVariable(p_0, "pressure amplitude p_0")
self._wave_type = PassiveVariable(wave_type, "wave type")
self._position = PassiveVariable(position, "scatterer position d")
# Dependent variables
self._abs_pos = ActiveVariable(
self._compute_abs_pos,
"absolute position in the standing wave",
)
self._A = ActiveVariable(
self._compute_A,
"velocity potential amplitude A",
)
self._abs_A_squared = ActiveVariable(
self._compute_abs_A_squared,
"squared absolute amplitude A",
)
self._A_in = ActiveListVariable(
self._compute_A_in,
"velocity potential amplitude A_n",
)
self._E = ActiveVariable(
self._compute_E,
"acoustic energy density E",
)
self._I = ActiveVariable(
self._compute_I,
"acoustic intensity I",
)
# Dependencies
self._abs_pos.is_computed_by(self.fluid._k_f, self._position)
self._A.is_computed_by(
self.fluid.frequency._omega,
self.fluid._rho_f,
self._p_0,
)
self._abs_A_squared.is_computed_by(self._A)
self._A_in.is_computed_by(
self._abs_pos,
self._A,
self._wave_type,
)
self._E.is_computed_by(
self._A,
self.fluid._rho_f,
self.fluid._k_f,
)
self._I.is_computed_by(
self._A,
self.fluid._rho_f,
self.fluid._k_f,
self.fluid._c_f,
)
log.debug(f"Creating {self}")
def __repr__(self) -> str:
return (
f"BackgroundField(fluid={self.fluid}, p_0={self.p_0} "
f"wave_type={self.wave_type}, position={self.position})"
)
# -------------------------------------------------------------------------
# Setters and Getters for Independent Variables
# -------------------------------------------------------------------------
@property
def p_0(self) -> float:
"""Background pressure amplitude of the acoustic field [Pa]
:getter: returns the value for the pressure amplitude
:setter: automatically invokes
:meth:`osaft.core.variable.BaseVariable.notify`
"""
return self._p_0.value
@p_0.setter
def p_0(self, value: float) -> None:
self._p_0.value = value
@property
def wave_type(self) -> WaveType:
"""Wave type of the acoustic background field
:getter: returns the wave type
:setter: automatically invokes
:meth:`osaft.core.variable.BaseVariable.notify`
"""
return self._wave_type.value
@wave_type.setter
def wave_type(self, value: WaveType) -> None:
self._wave_type.value = value
@property
def position(self) -> float:
"""Position of the particle in an acoustic standing wave field [rad]
:getter: returns the wave type
:setter: automatically invokes
:meth:`osaft.core.variable.BaseVariable.notify`
"""
return self._position.value
@position.setter
def position(self, value: float) -> None:
self._position.value = value
# -------------------------------------------------------------------------
# Getters for Dependent Variables
# -------------------------------------------------------------------------
@property
def abs_pos(self) -> float:
"""Absolute position of the scatterer in the standing wave in [m]"""
return self._abs_pos.value
@property
def A(self) -> complex:
"""Amplitude of the pressure wave [Pa]"""
return self._A.value
@property
def abs_A_squared(self) -> float:
""":math:`A\\,\\text{conj}(A) = |A|^2` [Pa^2]"""
return self._abs_A_squared.value
@property
def E_ac(self) -> float:
"""Mean acoustic energy density [J/m^3]"""
if not isinstance(self.wave_type, WaveType):
raise WrongWaveTypeError
return self._E.value
@property
def I_ac(self) -> float:
"""Acoustic intensity / energy flux density [W/m^2]"""
if self.wave_type != WaveType.TRAVELLING:
raise WrongWaveTypeError(
"The acoustic intensity is only "
"defined for: "
f"{WaveType.TRAVELLING}",
)
return self._I.value
# -------------------------------------------------------------------------
# Method for Dependent Variables
# -------------------------------------------------------------------------
def _compute_A(self) -> complex:
return self.p_0 / (1j * self.omega * self.rho_f)
def _compute_abs_A_squared(self) -> complex:
return absolute(self.A) ** 2
# -------------------------------------------------------------------------
# Wrapper Methods for Frequency
# -------------------------------------------------------------------------
@property
def f(self) -> float:
"""wrapper for :attr:`osaft.core.fluids.InviscidFluid.f`"""
return self.fluid.f
@f.setter
def f(self, value) -> None:
self.fluid.f = value
@property
def omega(self) -> float:
"""wrapper for :attr:`osaft.core.fluids.InviscidFluid.omega`"""
return self.fluid.omega
# -------------------------------------------------------------------------
# Wrapper Methods for Independent Fluid Attributes
# -------------------------------------------------------------------------
@property
def c_f(self) -> float:
"""wrapper for :attr:`osaft.core.fluids.InviscidFluid.c_f`"""
return self.fluid.c_f
@c_f.setter
def c_f(self, value) -> None:
self.fluid.c_f = value
@property
def rho_f(self) -> float:
"""wrapper for :attr:`osaft.core.fluids.InviscidFluid.rho_f`"""
return self.fluid.rho_f
@rho_f.setter
def rho_f(self, value) -> None:
self.fluid.rho_f = value
@property
def eta_f(self) -> float:
"""wrapper for :attr:`osaft.core.fluids.ViscousFluid.eta_f`"""
try:
return self.fluid.eta_f
except AttributeError:
log.warning(
"underlying fluid class of BackgroundField doesn't "
"have attribute eta_f",
)
return 0
@eta_f.setter
def eta_f(self, value) -> None:
if hasattr(self.fluid, "eta_f"):
self.fluid.eta_f = value
else:
log.warning(
"underlying fluid class of BackgroundField doesn't "
"have attribute eta_f",
)
@property
def zeta_f(self) -> float:
"""wrapper for :attr:`osaft.core.fluids.ViscousFluid.zeta_f`"""
try:
return self.fluid.zeta_f
except AttributeError:
log.warning(
"underlying fluid class of BackgroundField doesn't "
"have attribute zeta_f",
)
return 0
@zeta_f.setter
def zeta_f(self, value) -> None:
if hasattr(self.fluid, "zeta_f"):
self.fluid.zeta_f = value
else:
log.warning(
"underlying fluid class of BackgroundField doesn't "
"have attribute zeta_f",
)
@property
def eta_p(self) -> float:
"""wrapper for :attr:`osaft.core.fluids.ViscoelasticFluid.eta_p`"""
try:
return self.fluid.eta_p
except AttributeError:
log.warning(
"underlying fluid class of BackgroundField doesn't "
"have attribute eta_p",
)
return 0
@eta_p.setter
def eta_p(self, value) -> None:
if hasattr(self.fluid, "eta_p"):
self.fluid.eta_p = value
else:
log.warning(
"underlying fluid class of BackgroundField doesn't "
"have attribute eta_p",
)
@property
def zeta_p(self) -> float:
"""wrapper for :attr:`osaft.core.fluids.ViscoelasticFluid.zeta_p`"""
try:
return self.fluid.zeta_p
except AttributeError:
log.warning(
"underlying fluid class of BackgroundField doesn't "
"have attribute zeta_p",
)
return 0
@zeta_p.setter
def zeta_p(self, value) -> None:
if hasattr(self.fluid, "zeta_p"):
self.fluid.zeta_p = value
else:
log.warning(
"underlying fluid class of BackgroundField doesn't "
"have attribute zeta_p",
)
@property
def lambda_M(self) -> float:
"""wrapper for :attr:`osaft.core.fluids.ViscoelasticFluid.lambda_M`"""
try:
return self.fluid.lambda_M
except AttributeError:
log.warning(
"underlying fluid class of BackgroundField doesn't "
"have attribute lambda_M",
)
return 0
@lambda_M.setter
def lambda_M(self, value) -> None:
if hasattr(self.fluid, "lambda_M"):
self.fluid.lambda_M = value
else:
log.warning(
"underlying fluid class of BackgroundField doesn't "
"have attribute lambda_M",
)
# -------------------------------------------------------------------------
# Wrapper Methods for Dependent Fluid Attributes
# -------------------------------------------------------------------------
@property
def lambda_f(self) -> float:
"""wrapper for :attr:`osaft.core.fluids.InviscidFluid.lambda_f`"""
return self.fluid.lambda_f
@property
def k_f(self) -> float:
"""wrapper for :attr:`osaft.core.fluids.InviscidFluid.k_f`"""
return self.fluid.k_f
@property
def k_v(self) -> complex:
"""wrapper for :attr:`osaft.core.fluids.ViscousFluid.k_v`"""
return self.fluid.k_v
@property
def kappa_f(self) -> float:
"""wrapper for :attr:`osaft.core.fluids.ViscousFluid.kappa_f`"""
return self.fluid.kappa_f
# -------------------------------------------------------------------------
# Methods
# -------------------------------------------------------------------------
def _compute_abs_pos(self) -> float:
return self.position / self.k_f.real
def _compute_A_in(self, n: int) -> complex:
if not isinstance(self.wave_type, WaveType):
raise WrongWaveTypeError
if self.wave_type == WaveType.TRAVELLING:
return self._compute_A_in_travelling(n)
else:
return self._compute_A_in_standing(n)
def _compute_A_in_travelling(self, n: int) -> complex:
return self.A * (2 * n + 1) * 1j**n
def _compute_A_in_standing(self, n: int) -> complex:
out = exp(1j * self.abs_pos * self.k_f)
out += (-1) ** n * exp(-1j * self.abs_pos * self.k_f)
out *= self.A / 2 * (2 * n + 1) * 1j**n
return out
[docs] def A_in(self, n: int) -> complex:
"""Incident amplitude at order n [Pa]
:param n: order
"""
return self._A_in.item(n)
def _compute_E(self) -> float:
return (self.fluid.kappa_f * self.p_0**2) / 4
def _compute_I(self) -> float:
out = self.abs_A_squared * self.rho_f * self.fluid.c_f
out *= (self.fluid.k_f**2) / 2
return out
if __name__ == "__main__":
pass