Source code for vivyd.models.generalized_model

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])