Last updated on Feb 20, 2026.

Source code for netsse.model.ship

# -*- coding: utf-8 -*-
"""
Closed-form expressions of the linear wave-to-motion transfer functions
for a box-shaped uniformly-loaded **monohull ship**.

.. dropdown:: Copyright (C) 2023-2026 Technical University of Denmark, R.E.G. Mounet
    :color: primary
    :icon: law

    *This code is part of the NetSSE software.*

    NetSSE is free software: you can redistribute it and/or modify it under
    the terms of the GNU General Public License as published by the Free
    Software Foundation, either version 3 of the License, or (at your
    option) any later version.

    NetSSE is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    for more details.

    You should have received a copy of the GNU General Public License along
    with this program.  If not, see https://www.gnu.org/licenses/.

    To credit the author, users are encouraged to use below reference:

    .. code-block:: text

        Mounet, R. E. G., & Nielsen, U. D. NetSSE: An open-source Python package
        for network-based sea state estimation from ships, buoys, and other
        observation platforms (version 2.2). Technical University of Denmark,
        GitLab. February 2026. https://doi.org/10.11583/DTU.26379811.

*Last updated on 20-02-2026 by R.E.G. Mounet*

"""

import numpy as np
# import warnings


[docs] def heaveCF(om0, beta_deg, U, L, B0, T, C_B=1): """Computes the heave transfer function amplitude, using the closed-form expressions presented in Jensen et al. (2004). Parameters ---------- om0 : array_like of shape (Nom0,) Vector of absolute wave frequencies [rad/s]. beta_deg : array_like of shape (Nbeta,) Vector of heading angles [deg]. U : float Vessel forward speed [m/s]. L : float Length of the ship [m]. B0 : float Breadth of the ship [m]. T : float Draft of the ship [m]. C_B : float, default 1 Block coefficient of the ship [-]. Returns ------- heave : numpy.ndarray of shape (Nom0,Nbeta) Coefficients of the heave transfer function amplitude [m/m]. See Also -------- pitchCF, rollCF : Computes the pitch/roll transfer function amplitude using closed-form expressions. References ---------- Jensen, J. J., Mansour, A. E., & Olsen, A. S. (2004). Estimation of ship motions using closed-form expressions. Ocean Engineering, 31(1), 61–85. https://doi.org/10.1016/S0029-8018(03)00108-2 Example ------- >>> heave = heaveCF(om0,beta_deg,U,L,B0,T,C_B=1) """ # warnings.filterwarnings("ignore", category=RuntimeWarning) # Physical quantities: g = 9.81 # Gravitational acceleration [m/s^2] beta = np.array(beta_deg * np.pi / 180).reshape((-1, 1)) Fn = U / np.sqrt(g * L) k = np.square(om0) / g k[np.where(k < 1e-9)] = 1e-9 B = B0 * C_B # correction of the breadth B0 for shape effect of the hull geometry heave = np.zeros((len(om0), len(beta))) for i, beta_i in enumerate(beta): alpha = 1 - Fn * np.sqrt(k * L) * np.cos(beta_i) ome = om0 - k * U * np.cos(beta_i) A = 2 * np.sin(np.square(ome) * B / (2 * g)) * np.exp(-np.square(ome) * T / g) ke = np.abs(k * np.cos(beta_i)) f = np.sqrt((1 - k * T) ** 2 + (A**2 / (k * B * alpha**3)) ** 2) kappa = np.exp(-k * T) F = kappa * f * 2 / (ke * L) * np.sin(ke * L / 2) eta = 1 / np.sqrt( (1 - 2 * k * T * alpha**2) ** 2 + (A**2 / (k * B * alpha**2)) ** 2 ) heave[:, i] = np.reshape(eta * np.abs(F), (-1,)) # warnings.filterwarnings("default", category=RuntimeWarning) return heave
[docs] def pitchCF(om0, beta_deg, U, L, B0, T, C_B=1): """Computes the pitch transfer function amplitude, using the closed-form expressions presented in Jensen et al. (2004). Parameters ---------- om0 : array_like of shape (Nom0,) Vector of absolute wave frequency [rad/s]. beta_deg : array_like of shape (Nbeta,) Vector of heading angle [deg]. U : float Vessel forward speed [m/s]. L : float Length of the ship [m]. B0 : float Breadth of the ship [m]. T : float Draft of the ship [m]. C_B : float, default 1 Block coefficient of the ship [-]. Returns ------- pitch : numpy.ndarray of shape (Nom0,Nbeta) Coefficients of the pitch transfer function amplitude [rad/m]. See Also -------- heaveCF, rollCF : Computes the heave/roll transfer function amplitude using closed-form expressions. References ---------- Jensen, J. J., Mansour, A. E., & Olsen, A. S. (2004). Estimation of ship motions using closed-form expressions. Ocean Engineering, 31(1), 61–85. https://doi.org/10.1016/S0029-8018(03)00108-2 Example ------- >>> pitch = pitchCF(om0,beta_deg,U,L,B0,T,C_B=1) """ # warnings.filterwarnings("ignore", category=RuntimeWarning) # Physical quantities: g = 9.81 # Gravitational acceleration [m/s^2] beta = np.array(beta_deg * np.pi / 180).reshape((-1, 1)) Fn = U / np.sqrt(g * L) k = np.square(om0) / g k[np.where(k < 1e-9)] = 1e-9 B = B0 * C_B # correction of the breadth B0 for shape effect of the hull geometry pitch = np.zeros((len(om0), len(beta))) for i, beta_i in enumerate(beta): # In case beta==90 deg or 270 deg, the amplitude of pitch is taken as # 10% of the amplitude at beta==100 deg (would be zero otherwise): # if (beta_i+np.pi/2)%np.pi == 0.: # beta_i += np.pi/180 # eta = 1.0 # else: # eta = 1.0 alpha = 1 - Fn * np.sqrt(k * L) * np.cos(beta_i) ome = om0 - k * U * np.cos(beta_i) A = 2 * np.sin(np.square(ome) * B / (2 * g)) * np.exp(-np.square(ome) * T / g) ke = np.abs(k * np.cos(beta_i)) f = np.sqrt((1 - k * T) ** 2 + (A**2 / (k * B * alpha**3)) ** 2) kappa = np.exp(-k * T) G = ( kappa * f * 24 / ((ke * L) ** 2 * L) * (np.sin(ke * L / 2) - ke * L / 2 * np.cos(ke * L / 2)) ) eta = 1 / np.sqrt( (1 - 2 * k * T * alpha**2) ** 2 + (A**2 / (k * B * alpha**2)) ** 2 ) pitch[:, i] = np.reshape(eta * np.abs(G), (-1,)) # warnings.filterwarnings("default", category=RuntimeWarning) return pitch
[docs] def rollCF(om0, beta_deg, U, L, B0, T, C_B, C_WP, GM_T, kappa, mu, T_N=0): """Computes the roll transfer function amplitude, using the closed-form expressions presented in Jensen et al. (2004). Parameters ---------- om0 : array_like of shape (Nom0,) Vector of absolute wave frequencies [rad/s]. beta_deg : array_like of shape (Nbeta,) Vector of heading angles [deg]. U : float Vessel forward speed [m/s]. L : float Length of the ship [m]. B0 : float Breadth of the ship [m]. T : float Draft of the ship [m]. C_B : float Block coefficient of the ship [-]. C_WP : float Waterplane area coefficient of the ship [-]. GM_T : float Transverse metacentric height [m]. kappa : float Custom parameter (chosen between 0 and ``C_WP``) representing the ratio between the length of the aft beam and the whole ship length in the simplified ship model for roll. mu : float, default 0 Ratio between added viscous damping and critical damping. T_N : float, optional Roll natural period [s]. .. note:: If not specified as input, ``T_N`` is calculated using empirical formulas from ADA147598. Returns ------- roll : numpy.ndarray of shape (Nom0,Nbeta) Coefficients of the roll transfer function amplitude [rad/m]. See Also -------- heaveCF, pitchCF : Computes the heave/pitch transfer function amplitude using closed-form expressions. References ---------- 1. Jensen, J. J., Mansour, A. E., & Olsen, A. S. (2004). Estimation of ship motions using closed-form expressions. Ocean Engineering, 31(1), 61–85. https://doi.org/10.1016/S0029-8018(03)00108-2 2. ADA147598, 1973. Code of Safety for Fishermen and Fishing Vessels. Part B. Safety and Health Requirements for the Construction and Equipment of Fishing Vessels. Inter-Governmental Maritime Consultative Organization, London, England. Example ------- >>> roll = rollCF(om0,beta_deg,U,L,B0,T,C_B,C_WP,GM_T,kappa,mu,T_N=0) """ # warnings.filterwarnings("ignore", category=RuntimeWarning) # Physical quantities: g = 9.81 # Gravitational acceleration [m/s^2] rho = 1025 # Density of seawater [kg/m^3] roll = np.zeros((len(om0), len(beta_deg))) if om0[0,] == 0.0: om0 = om0[1:,] start_om = int(1) else: start_om = int(0) beta = np.radians(beta_deg) k = om0**2 / g k[np.where(k < 1e-100)] = 1e-100 delta = np.amax((C_WP - kappa, 0)) gamma = np.amax((kappa / (1 - delta), 1 / 6)) B1 = gamma * B0 # The ship draught T should be less than B0 and B1, and should not be # lower than B0/6 and B1/6: T = np.amax((np.amin((T, B0, B1)), B0 / 6, B1 / 6)) Delta = L * B0 * T * C_B * rho # Displacement [kg] # Roll natural period [s]: if T_N == 0: # Select the factor for the rolling period (found in IMO A.685(17) # resolution and ADA147598): if L < 45: f = 0.4 else: f = 0.373 + 0.023 * (B0 / T) - 0.043 * (L / 100) T_N = 2 * f * B0 / np.sqrt(GM_T) C44 = g * Delta * GM_T # Restoring moment coefficient B44_crit = C44 * T_N / np.pi # Critical roll damping A0 = C_B * B0 * T / (delta + gamma * (1 - delta)) A1 = gamma * A0 if (B0 / T >= 3) & (B0 / T <= 6): aBT0 = 0.256 * B0 / T - 0.286 bBT0 = -0.11 * B0 / T - 2.55 dBT0 = 0.033 * B0 / T - 1.419 elif (B0 / T >= 0.99) & (B0 / T < 3): aBT0 = -3.94 * B0 / T + 13.69 bBT0 = -2.12 * B0 / T - 1.89 dBT0 = 1.16 * B0 / T - 7.97 if (B1 / T >= 3) & (B1 / T <= 6): aBT1 = 0.256 * B1 / T - 0.286 bBT1 = -0.11 * B1 / T - 2.55 dBT1 = 0.033 * B1 / T - 1.419 elif (B1 / T >= 0.99) & (B1 / T < 3): aBT1 = -3.94 * B1 / T + 13.69 bBT1 = -2.12 * B1 / T - 1.89 dBT1 = 1.16 * B1 / T - 7.97 for i, beta_i in enumerate(beta): ome = np.abs(om0 - k * U * np.cos(beta_i)) ome[np.where(ome < 1e-100)] = 1e-100 ke = np.abs(k * np.cos(beta_i)) ke[np.where(ke < 1e-100)] = 1e-100 b44_0 = ( rho * A0 * B0**2 * np.sqrt(2 * g / B0) * aBT0 * np.exp(bBT0 * ome ** (-1.3)) * ome**dBT0 ) b44_0[np.where(b44_0 < 1e-100)] = 1e-100 b44_1 = ( rho * A1 * B1**2 * np.sqrt(2 * g / B1) * aBT1 * np.exp(bBT1 * ome ** (-1.3)) * ome**dBT1 ) kappa = np.sqrt(1 + (b44_1 - b44_0) / b44_0) B44_invisc = ( L * b44_0 * (delta + kappa**2 * (1 - delta)) ) # Roll hydrodynamic damping B44_tot = B44_invisc + mu * B44_crit # Amplitude of the roll excitation moment: M_abs = ( np.abs(np.sin(beta_i)) * np.sqrt(rho * g**2 / ome) * 2 / ke * np.sqrt(b44_0) * np.sqrt( np.sin(0.5 * delta * L * ke) ** 2 + kappa**2 * np.sin(0.5 * (1 - delta) * L * ke) ** 2 + 2 * kappa * np.sin(0.5 * delta * L * ke) * np.sin(0.5 * (1 - delta) * L * ke) * np.cos(0.5 * L * ke) ) ) roll[start_om:, i] = M_abs / np.sqrt( (-(ome**2) * (T_N / (2 * np.pi)) ** 2 + 1) ** 2 * C44**2 + ome**2 * B44_tot**2 ) # warnings.filterwarnings("default", category=RuntimeWarning) return roll