from ..typing import arrf64
from .viv_model import VIVModel
from .generalized_model import GeneralizedModel
from dataclasses import dataclass
from numpy import array, pi
[docs]
@dataclass(kw_only=True, slots=True)
class TamuraModel(VIVModel):
"""
Tamura & Matsui (1980) wake-oscillator model for vortex-induced vibrations
on circular cylinders :cite:`tamura1980`. By identifying the lift and
transverse drag forces, the mechanical equation of motion of the cylinder
is written as
.. math::
\\ddot{y} + \\frac{c}{m} \\dot{y} + \\frac{k}{m} y = - \\frac{\\rho U_\\infty^2 D}{2 m_\\text{lin}} \\left(f_m \\left(q + \\frac{\\dot{y}}{U_\\infty} \\right) + C_D \\frac{\\dot{y}}{U_\\infty} \\right),
and the equation of the wake is written as
.. math::
\\ddot{q} + 2 \\zeta \\omega_s \\left(4 \\frac{f_m^2}{C_{L 0}^2} q^2 - 1 \\right) \\dot{q} + \\omega_s^2 \\left(q + \\frac{\\dot{y}}{U_\\infty} \\right) = - \\frac{\\ddot{y}}{D \\left(\\displaystyle \\frac{1}{2} + l^* \\right)},
with
.. math::
\\omega_s = 2 \\pi \\text{St} \\frac{U_\\infty}{D}.
Parameters
----------
diam : float, optional
Diameter of the cylinder, :math:`D`. Default is 0.18 m.
St : float, optional
Strouhal number, :math:`\\text{St}`. Default is 0.18.
c_m : float, optional
Specific mechanical damping, :math:`c/m`. Default is 0.05.
k_m : float, optional
Specific mechanical stiffness, :math:`k/m`. Default is :math:`4 \\pi^2`.
m_lin : float, optional
Mass per unit length of the cylinder, :math:`m_\\text{lin}`. Default is 25.0 kg/m.
rho : float, optional
Density of the fluid, :math:`\\rho`. Default is 1.25 kg/m³.
u_inf : float, optional
Free-stream velocity, :math:`U_\\infty`. Default is 1.0 m/s.
fm : float, optional
Magnus force coefficient, :math:`f_m`. Default is 1.16.
Cd : float, optional
Drag coefficient, :math:`C_D`. Default is 1.2.
zeta : float, optional
Damping ratio, :math:`\\zeta`. Default is 0.038.
Cl0 : float, optional
Lift coefficient at zero angle of attack, :math:`C_{L 0}`. Default is 0.4.
l_star : float, optional
Dimensionless length of the wake lamina, :math:`l^* = l / D`. Default is 1.1.
"""
state_size = 4
diam : float = 0.18
"""Diameter of the cylinder, :math:`D`."""
St : float = 0.18
"""Strouhal number, :math:`\\text{St}`."""
c_m : float = 0.05
"""Specific mechanical damping, :math:`c/m`."""
k_m : float = 4.0 * pi**2
"""Specific mechanical stiffness, :math:`k/m`."""
m_lin : float = 25.0
"""Mass per unit length of the cylinder, :math:`m_\\text{lin}`."""
rho : float = 1.25
"""Density of the fluid, :math:`\\rho`."""
u_inf : float = 1.0
"""Free-stream velocity, :math:`U_\\infty`."""
fm : float = 1.16
"""Magnus force coefficient, :math:`f_m`."""
Cd : float = 1.2
"""Drag coefficient, :math:`C_D`."""
zeta : float = 0.038
"""Damping ratio, :math:`\\zeta`."""
Cl0 : float = 0.4
"""Lift coefficient at zero angle of attack, :math:`C_{L 0}`."""
l_star: float = 1.1
"""Dimensionless length of the wake lamina, :math:`l^* = l / D`."""
@property
def n(self) -> float:
"""
Dimensionless mass ratio, :math:`n = \\dfrac{\\rho D^2}{2 m_\\text{lin}}`.
.. list-table::
:header-rows: 1
* - Reference values
-
* - Ferguson (1965) :cite:`ferguson1965`
- :math:`n = 0.00330`
* - Feng (1968) :cite:`feng1968`
- :math:`n = 0.00257`
* - Yamaguchi (1970) :cite:`yamaguchi1970`
- :math:`n = 0.00178`
"""
return self.rho * self.diam**2 / (2 * self.m_lin)
@property
def m_star(self) -> float:
"""Coefficient :math:`m^* = \\dfrac{1}{0.5 + l^*}`."""
return 1.0 / (0.5 + self.l_star)
@property
def St_star(self) -> float:
"""Angular Strouhal number, :math:`\\text{St}^* = 2 \\pi \\text{St}`."""
return 2.0 * pi * self.St
@property
def ws(self) -> float:
"""Strouhal's vortex shedding pulsation, :math:`\\omega_s = 2 \\pi \\text{St} \\dfrac{U_\\infty}{D}`."""
return self.St_star * self.u_inf / self.diam
[docs]
def rhs(self, t: float, state: arrf64, **kwargs) -> arrf64:
"""
Compute the right-hand side of the system of ODEs.
Args:
t: Time.
state: State vector indexed [y_dot, y, q_dot, q].
Returns:
arrf64: Derivatives of the state vector indexed [dy_dot, dy, dq_dot, dq].
"""
self.validate_state(state)
y_dot, y, q_dot, q = state
lhs = (self.n*(self.fm+self.Cd) * self.u_inf / self.diam + self.c_m) * y_dot + self.k_m * y
rhs = - self.n*self.fm * self.u_inf**2/self.diam * q
dy_dot = rhs - lhs
dy = y_dot
lhs = 2.0 * self.zeta * self.ws * (4.0*self.fm**2/self.Cl0**2 * q**2 - 1.0) * q_dot + self.ws**2 * q
rhs = - self.m_star/self.diam * dy_dot - self.ws**2/self.u_inf * y_dot
dq_dot = rhs - lhs
dq = q_dot
return array([dy_dot, dy, dq_dot, dq])
@property
def generalized_params(self) -> dict[str, float]:
"""
A dictionary containing the parameters of the model in a format
compatible with the constructor of :class:`GeneralizedModel`.
"""
return {
"c_m" : self.c_m,
"k_m" : self.k_m,
"ca_m" : self.n*(self.fm+self.Cd) * self.u_inf / self.diam,
"B0" : - self.n*self.fm * self.u_inf**2/self.diam,
"gamma": 2.0 * self.zeta * self.ws,
"a" : 4.0*self.fm**2/self.Cl0**2,
"kappa": self.ws**2,
"A1" : - self.ws**2/self.u_inf,
"A2" : - self.m_star/self.diam
}
[docs]
def to_generalized(self) -> GeneralizedModel:
"""
Returns
-------
GeneralizedModel
A generalized model instance equivalent to the present model.
"""
return GeneralizedModel(**self.generalized_params)
[docs]
def to_taichi(self) -> GeneralizedModel:
"""
Returns
-------
GeneralizedModel
A Taichi-compatible generalized model instance equivalent to the
present model.
"""
return self.to_generalized()
[docs]
def get_lift(self, state: arrf64) -> float:
"""
Compute the lift force associated with a given state.
Args:
state: State vector indexed [y_dot, y, q_dot, q].
Returns:
float: Lift force.
"""
y_dot, _, _, q = state
return - self.n * self.fm * self.u_inf**2 / self.diam * (q + y_dot / self.u_inf)