Source code for osaft.core.backgroundfields

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