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 FacchinettiDelangreBiolleyModel(VIVModel):
"""
Facchinetti, Delangre and Biolley (2004) wake-oscillator model for vortex-induced vibrations
on circular cylinders :cite:`facchinetti2004`. By identifying the lift and
transverse drag forces, the mechanical equation of motion of the cylinder
is written as
.. math::
\\ddot{y} + \\left(\\frac{c}{m} + \\frac{\\gamma}{\\mu} \\omega_s \\right) \\dot{y} + \\frac{k}{m} y = M \\omega_s^2 q,
and the equation of the wake is written as
.. math::
\\ddot{q} + \\varepsilon \\omega_s (q^2 - 1) \\dot{q} + \\omega_s^2 q = \\frac{A}{D} \\ddot{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.
Cm : float, optional
Added mass coefficient, :math:`C_M`. Default is 1.0.
Cd : float, optional
Drag coefficient, :math:`C_D`. Default is 1.2.
Cl0 : float, optional
Lift coefficient at zero angle of attack, :math:`C_{L 0}`. Default is 0.4.
eps : float, optional
Wake nonlinearity parameter, :math:`\\varepsilon`. Default is 0.3.
A : float, optional
Structure-to-wake coupling parameter, :math:`A`. Default is 12.0.
"""
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`."""
Cm : float = 1.0
"""Added mass coefficient, :math:`C_M`."""
Cd : float = 1.2
"""Drag coefficient, :math:`C_D`."""
Cl0 : float = 0.4
"""Lift coefficient at zero angle of attack, :math:`C_{L 0}`."""
eps : float = 0.3
"""Wake nonlinearity parameter, :math:`\\varepsilon`."""
A : float = 12.0
"""Structure-to-wake coupling parameter, :math:`A`."""
@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
@property
def gamma(self) -> float:
"""Drag-induced wake damping coefficient, :math:`\\gamma = C_D / (2 \\text{St}^*)`."""
return self.Cd / (2.0 * self.St_star)
@property
def m_fluid(self) -> float:
"""Mass of the fluid displaced by the cylinder, :math:`m_\\text{fluid} = C_M \\dfrac{\\rho \\pi D^2}{4}`."""
return self.Cm * self.rho * pi * self.diam**2 / 4.0
@property
def mu(self) -> float:
"""Mass ratio, :math:`\\mu = \\dfrac{m_\\text{lin} + m_\\text{fluid}}{\\rho D^2}`."""
return (self.m_lin + self.m_fluid) / (self.rho * self.diam**2)
@property
def M(self) -> float:
"""Structure-wake coupling coefficient, :math:`M = \\dfrac{C_{L 0}}{4 \\text{St}^{*2} \\mu}`."""
return self.Cl0 / (4.0 * self.St_star**2 * self.mu)
[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 + self.gamma/self.mu * self.ws) * y_dot + self.k_m * y
rhs = self.M * self.ws**2 * self.diam * q
dy_dot = rhs - lhs
dy = y_dot
lhs = self.eps * self.ws * (q**2 - 1.0) * q_dot + self.ws**2 * q
rhs = self.A / self.diam * dy_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.gamma/self.mu * self.ws,
"B0" : self.M * self.ws**2 * self.diam,
"gamma": self.eps * self.ws,
"a" : 1.0,
"kappa": self.ws**2,
"A2" : self.A / 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()