from ..core import is_taichi_used, ti_or_fallback as ti
from ..typing import arrf64
from .viv_model import VIVModel
from .collection import TaichiCompatibleInCollection
from numpy import array, zeros_like
from typing import Callable
[docs]
@ti.data_oriented
class GeneralizedModel(VIVModel, TaichiCompatibleInCollection):
"""
Generalized wake-oscillator model for vortex-induced vibrations on cylinders.
In fact, the studied models herein can be written in the following form in
a unified way. The structure's equation of motion is written as
.. math::
\\ddot{y} + \\frac{c}{m} \\dot{y} + \\frac{k}{m} y = B_0 q,
and the equation of the wake is written as
.. math::
\\ddot{q} + \\gamma \\left(a q^2 + b \\dot{q}^2 - 1 \\right) \\dot{q} + \\kappa q = A_2 \\ddot{y} + A_1 \\dot{y} + A_0 y.
Parameters
----------
c_m : float, optional
Specific mechanical damping, :math:`c/m`. Default is 0.0.
k_m : float, optional
Specific mechanical stiffness, :math:`k/m`. Default is 0.0.
ca_m : float, optional
Specific aerodynamic damping, :math:`c_a/m`. Default is 0.0.
B0 : float, optional
Wake-to-structure coupling parameter, :math:`B_0`. Default is 0.0.
gamma : float, optional
Wake nonlinear damping coefficient, :math:`\\gamma`. Default is 0.0.
a : float, optional
Van der Pol wake nonlinearity parameter, :math:`a`. Default is 0.0.
b : float, optional
Rayleigh wake nonlinearity parameter, :math:`b`. Default is 0.0.
kappa : float, optional
Wake stiffness parameter, :math:`\\kappa`. Default is 0.0.
A0 : float, optional
Positional wake-to-structure coupling parameter, :math:`A_0`. Default is 0.0.
A1 : float, optional
Velocity wake-to-structure coupling parameter, :math:`A_1`. Default is 0.0.
A2 : float, optional
Acceleration wake-to-structure coupling parameter, :math:`A_2`. Default is 0.0.
"""
state_size = 4
n_params = 11
def __init__(
self,
c_m : float = 0.0,
k_m : float = 0.0,
ca_m : float = 0.0,
B0 : float = 0.0,
gamma: float = 0.0,
a : float = 0.0,
b : float = 0.0,
kappa: float = 0.0,
A0 : float = 0.0,
A1 : float = 0.0,
A2 : float = 0.0
):
self.c_m = c_m
self.k_m = k_m
self.ca_m = ca_m
self.B0 = B0
self.gamma = gamma
self.a = a
self.b = b
self.kappa = kappa
self.A0 = A0
self.A1 = A1
self.A2 = A2
@property
def params(self) -> list[float]:
"""Return the model parameters as a list, in the order they are defined in the constructor."""
return [
self.c_m,
self.k_m,
self.ca_m,
self.B0,
self.gamma,
self.a,
self.b,
self.kappa,
self.A0,
self.A1,
self.A2
]
[docs]
def rhs(self, t: float, state: arrf64, handle: Callable = (lambda *args, **kwargs: None)) -> 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].
"""
_call = self._call_taichi if is_taichi_used() else self._call_python
return _call(t, self.validate_state(state), handle)
@staticmethod
@ti.func
def _call_taichi_func_param(
t : ti.f64, # type: ignore
state : ti.template(), # type: ignore
handle: ti.template(), # type: ignore
c_m : ti.f64, # type: ignore
k_m : ti.f64, # type: ignore
ca_m : ti.f64, # type: ignore
B0 : ti.f64, # type: ignore
gamma : ti.f64, # type: ignore
a : ti.f64, # type: ignore
b : ti.f64, # type: ignore
kappa : ti.f64, # type: ignore
A0 : ti.f64, # type: ignore
A1 : ti.f64, # type: ignore
A2 : ti.f64 # type: ignore
) -> ti.types.ndarray(dtype=ti.f64, ndim=1): # type: ignore
y_dot = state[0]
y = state[1]
q_dot = state[2]
q = state[3]
lhs = (c_m + ca_m) * y_dot + k_m * y
rhs = B0 * q
dy_dot = rhs - lhs
dy = y_dot
lhs = gamma * (a * q**2 + b * q_dot**2 - 1.0) * q_dot + kappa * q
rhs = A0 * y + A1 * y_dot + A2 * dy_dot
dq_dot = rhs - lhs
dq = q_dot
return ti.Vector([dy_dot, dy, dq_dot, dq], dt=ti.f64)
@ti.func
def _call_taichi_func(
self,
t : ti.f64, # type: ignore
state : ti.template(), # type: ignore
handle: ti.template() # type: ignore
) -> ti.types.ndarray(dtype=ti.f64, ndim=1): # type: ignore
return self._call_taichi_func_param(t, state, handle, *self.params)
@ti.kernel
def _call_taichi_kernel(
self,
t : ti.f64, # type: ignore
state : ti.types.ndarray(dtype=ti.f64, ndim=1), # type: ignore
handle: ti.template(), # type: ignore
out : ti.types.ndarray(dtype=ti.f64, ndim=1), # type: ignore
):
rhs = self._call_taichi_func(t, state, handle)
for i in range(ti.static(self.state_size)):
out[i] = rhs[i]
def _call_taichi(self, t: float, state: arrf64, handle: Callable) -> arrf64:
out = zeros_like(state, dtype=float)
self._call_taichi_kernel(t, state, handle, out)
return out
def _call_python(self, t: float, state: arrf64, handle: Callable) -> arrf64:
y_dot, y, q_dot, q = state
lhs = (self.c_m + self.ca_m) * y_dot + self.k_m * y
rhs = self.B0 * q
dy_dot = rhs - lhs
dy = y_dot
lhs = self.gamma * (self.a * q**2 + self.b * q_dot**2 - 1.0) * q_dot + self.kappa * q
rhs = self.A0 * y + self.A1 * y_dot + self.A2 * dy_dot
dq_dot = rhs - lhs
dq = q_dot
return array([dy_dot, dy, dq_dot, dq])