from ..typing import arrf64
from .viv_model import VIVModel
from .generalized_model import GeneralizedModel
from dataclasses import dataclass
from numpy import pi, array
[docs]
@dataclass(kw_only=True, slots=True)
class HartlenCurrieModel(VIVModel):
"""
Hartlen & Currie (1970) wake-oscillator model for vortex-induced vibrations
on circular cylinders :cite:`hartlen1970`. By identifying the lift, 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 D^3}{8 \\pi^2 \\text{St}^2 m_\\text{lin}} \\omega_s^2 q,
and the equation of the wake is written as
.. math::
\\ddot{q} + \\left(\\frac{\\gamma}{\\omega_s} \\dot{q}^2 - \\alpha \\omega_s \\right) \\dot{q} + \\omega_s^2 q = b^* \\frac{\\omega_s}{D} \\dot{y},
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.
gamma : float, optional
Quadratic wake damping coefficient, :math:`\\gamma`. Default is 0.667.
alpha : float, optional
Constant wake damping coefficient, :math:`\\alpha`. Default is 0.02.
b_star: float, optional
Wake-coupling coefficient, :math:`b^* = b \\dfrac{\\omega_0}{\\omega_s}`,
where :math:`b` is the wake-coupling coefficient as defined by Hartlen & Currie
(1970) and :math:`\\omega_0` is the natural frequency of the structure.
Default is 0.4.
"""
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`."""
gamma : float = 0.667
"""Quadratic wake damping coefficient, :math:`\\gamma`."""
alpha : float = 0.02
"""Constant wake damping coefficient, :math:`\\alpha`."""
b_star: float = 0.4
"""
Structure-to-wake coefficient, :math:`b^* = b \\dfrac{\\omega_0}{\\omega_s}`,
where :math:`b` is the structure-to-wake coefficient as defined by Hartlen & Currie
(1970) and :math:`\\omega_0` is the natural frequency of the structure.
"""
@property
def a(self) -> float:
"""Wake-to-structure coupling coefficient as defined by Hartlen & Currie (1970)."""
return self.rho * self.diam**2 / (2.0 * self.St_star**2 * self.m_lin)
@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.c_m * y_dot + self.k_m * y
rhs = self.a * self.ws**2 * self.diam * q
dy_dot = rhs - lhs
dy = y_dot
lhs = (self.gamma/self.ws * q_dot**2 - self.alpha * self.ws) * q_dot + self.ws**2 * q
rhs = self.b_star * self.ws / self.diam * 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,
"B0" : self.a * self.ws**2 * self.diam,
"gamma": self.alpha * self.ws,
"b" : self.gamma/(self.alpha * self.ws**2),
"kappa": self.ws**2,
"A1" : self.b_star * self.ws / 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()