Source code for vivyd.models.tamura

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)