Source code for berhoel.holtrop.ship

"""Hold ship dimensions."""

from __future__ import annotations

import ast
import sys
import math
from abc import ABC, abstractmethod
from enum import Enum, auto
from pathlib import Path

from astropy import units as u  # type: ignore
from astropy.units import imperial as ui  # type: ignore
from astropy.units.quantity import Quantity

from . import hydro

__date__ = "2024/08/01 21:22:19 hoel"
__author__ = "Berthold Höllmann"
__copyright__ = "Copyright © 2019 by Berthold Höllmann"
__credits__ = ["Berthold Höllmann"]
__maintainer__ = "Berthold Höllmann"
__email__ = "berhoel@gmail.com"


u.imperial.enable()


[docs] class WettedSurfaceEstimationMethod(Enum): """Estimation formulas for caculating wetted surfaces.""" Holtrop = auto() Schenzle = auto()
[docs] class WaterlineCoefficientMethod(Enum): r"""Calculation method for estimating C_WP 1. **Schneekluth_U**: For ships with U-shaped sections, with not sweeping stern lines: .. math:: C_{WP} = 0.95 C_P + 0.17 (1 - C_P)^{\frac{1}{3}} 2. **Schneekluth_medium**: For medium shape forms: .. math:: C_{WP} = \frac{1 + 2 C_B}{3} 3. **Schneekluth_V**: V-shaped sections, also for sweeping stern lines: .. math:: C_{WP} = \sqrt{C_B} - 0.025 4. **Schneekluth_V_alt_1**: For shapes as Schneekluth_V .. math:: C_{WP} = C_P^\frac 2 3 5. **Schneekluth_V_alt_2**: For shapes as Schneekluth_V .. math:: C_{WP} = \frac{1 + 2 \frac{C_B}{\sqrt{C_M}}} 3 All formulas according to :cite:`schneekluth1985entwerfen` """ Schneekluth_U = auto() Schneekluth_medium = auto() Schneekluth_V = auto() Schneekluth_V_alt_1 = auto() Schneekluth_V_alt_2 = auto()
[docs] def C_WP_Schneekluth_U(C_P: float): r"""Calculation method for estimating C_WP acc. to Schneekluth. Formula for ships with U-shaped sections, with not sweeping stern lines: .. math:: C_{WP} = 0.95 C_P + 0.17 (1 - C_P)^{\frac{1}{3}} Formula according to :cite:`schneekluth1985entwerfen`, p. 144 """ return 0.95 * C_P + 0.17 * math.pow((1.0 - C_P), (1.0 / 3.0))
[docs] def C_WP_Schneekluth_medium(C_B: float): r"""Calculation method for estimating C_WP acc. to Schneekluth. Formula for medium shape forms: .. math:: C_{WP} = \frac{1 + 2 C_B} 3 Formula according to :cite:`schneekluth1985entwerfen`, p. 144 """ return (1.0 + 2.0 * C_B) / 3.0
[docs] def C_WP_Schneekluth_V(C_B: float): r"""Calculation method for estimating C_WP acc. to Schneekluth. Formula for V-shaped sections, also for sweeping stern lines: .. math:: C_{WP} = \sqrt{C_B} - 0.025 Formula according to :cite:`schneekluth1985entwerfen`, p. 144 """ return math.sqrt(C_B) - 0.025
[docs] def C_WP_Schneekluth_V_alt_1(C_P: float): r"""Calculation method for estimating C_WP acc. to Schneekluth. Formula for V-shaped sections, also for sweeping stern lines, alternative 2: .. math:: C_{WP} = C_P^\frac 2 3 Formula according to :cite:`schneekluth1985entwerfen`, p. 144 """ return math.pow(C_P, (2.0 / 3.0))
[docs] def C_WP_Schneekluth_V_alt_2(C_B: float, C_M: float): r"""Calculation method for estimating C_WP C_WP acc. to Schneekluth. Formula for V-shaped sections, also for sweeping stern lines, alternative 2: .. math:: C_{WP} = \frac{1 + 2 \frac{C_B}{\sqrt{C_M}}} 3 Formula according to :cite:`schneekluth1985entwerfen`, p. 144 """ # For shapes as Schneekluth_V return (1.0 + 2.0 * (C_B / math.sqrt(C_M))) / 3.0
[docs] class BlockCoefficientMethod(Enum): r"""Select estimation formula for calculating block coefficient :math:`C_B`. Formulas are taken from :cite:`schneekluth1985entwerfen`, p. 134f. 1. **Schneekluth_1**: .. math:: C_B = \frac{0.14}{F_n} \cdot \frac{\frac L B + 20}{26} 2. **Schneekluth_2**: .. math:: C_B = \frac{0.23}{F_n^{\frac 2 3}} \cdot \frac{\frac L B + 20}{26} 3. **Ayre_1_06** Often used in more recent appliocations. :math:`C_\text{Ayre} = 1.06` 4. **Ayre_1_08** One screw ships, :math:`C_\text{Ayre} = 1.08` 5. **Ayre_1_09** Two screw ships, :math:`C_\text{Ayre} = 1.09` With .. math:: {C_B}_\text{Ayre} = C_\text{Ayre} - 1.68 F_n """ Schneekluth_1 = auto() Schneekluth_2 = auto() Ayre_1_06 = auto() Ayre_1_08 = auto() Ayre_1_09 = auto()
[docs] def C_B_Schneekluth_1(F_n: float, L: Quantity, B: Quantity): """Estimate :math:`C_B` acc. to Schneekluth. Variant 1 acc to :cite:`schneekluth1985entwerfen`, p. 134f. """ if F_n < 0.14 or F_n > 0.32: msg = f"Froude number not in allowed range 0.14 <= {F_n} <= 0.32." raise ValueError(msg) if F_n > 0.3: F_n = 0.3 C_B = (0.14 / F_n) * (((L / B) + 20) / 26) if C_B < 0.48: return 0.48 if C_B > 0.85: return 0.85 return C_B.value
[docs] def C_B_Schneekluth_2(F_n: float, L: Quantity, B: Quantity): """Estimate :math:`C_B` acc. to Schneekluth. Variant 2 acc to :cite:`schneekluth1985entwerfen`, p. 134f. """ if F_n < 0.14 or F_n > 0.32: msg = f"Froude number not in allowed range 0.14 <= {F_n} <= 0.32." raise ValueError(msg) if F_n > 0.3: F_n = 0.3 C_B = (0.23 / (F_n ** (2.0 / 3.0))) * (((L / B) + 20) / 26) if C_B < 0.48: return 0.48 if C_B > 0.85: return 0.85 return C_B.value
[docs] def C_B_Ayre(F_n: float, C_Ayre: float) -> float: """Estimate :math:`C_B` acc. to Ayre. Formula acc. to :cite:`schneekluth1985entwerfen`, p. 134f. """ return C_Ayre - 1.69 * F_n
[docs] def C_B_Ayre_1_06(F_n: float): """Often used in more recent appliocations. :math:`C_\\text{Ayre} = 1.06`""" return C_B_Ayre(F_n, 1.06)
[docs] def C_B_Ayre_1_08(F_n: float): """One screw ships, :math:`C_\\text{Ayre} = 1.08`""" return C_B_Ayre(F_n, 1.08)
[docs] def C_B_Ayre_1_09(F_n: float): """Two screw ships, :math:`C_\\text{Ayre} = 1.09`""" return C_B_Ayre(F_n, 1.09)
[docs] class Ship(ABC): """Class to describe a ship for resistance calculation.""" __known_methods = {"S", "C_WP", "C_B", "i_E", "C_P", "C_A", "Nab"} _S_method = WettedSurfaceEstimationMethod.Holtrop __C_WP_method = WaterlineCoefficientMethod.Schneekluth_U __C_B_method = BlockCoefficientMethod.Ayre_1_06 __default_speed = 14 * ui.kn __needed_units = { "L": u.m, # Quantity("1m").unit, "B": u.m, # Quantity("1m").unit, "T": u.m, # Quantity("1m").unit, "D": u.m, # Quantity("1m").unit, "T_F": u.m, # Quantity("1m").unit, "T_A": u.m, # Quantity("1m").unit, "S": u.m**2, # Quantity("1m**2").unit, "h_b": u.m, # Quantity("1m").unit, "Nab": u.m**3, # Quantity("1m**3").unit, "S_app": [u.m**2], # Quantity("1m**2").unit], "k_1": None, "k_2": [None], "App": [[u.m**2, None]], # Quantity("1m**2").unit, None]], "A_BT": u.m**2, # Quantity("1m**2").unit, "i_E": u.degree, # Quantity("1deg").unit, "lcb": None, "A_T": u.m**2, # Quantity("1m**2").unit, "A_WP": u.m**2, # Quantity("1m**2").unit, "L_WP": u.m, # Quantity("1m").unit, "B_WP": u.m, # Quantity("1m").unit, "C_Stern": None, "C_A": None, "C_M": None, "C_WP": None, "C_P": None, "C_B": None, "v_Probe": u.m / u.s, # Quantity("1m/s").unit, "R_F": 1000.0 * u.N, # Quantity("1kN").unit, "R_app": 1000.0 * u.N, # Quantity("1kN").unit, "R_W": 1000.0 * u.N, # Quantity("1kN").unit, "R_B": 1000.0 * u.N, # Quantity("1kN").unit, "R_TR": 1000.0 * u.N, # Quantity("1kN").unit, "R_A": 1000.0 * u.N, # Quantity("1kN").unit, "R": 1000.0 * u.N, # Quantity("1kN").unit, "eta_R": None, "w": None, "t": None, "A_E_0": None, "c_P_D": None, "eta_0": None, }
[docs] def __init__(self, **kw): self.__keys = {} self.__keys.update(kw) # copy.deepcopy(kw) # attach variables given on constructor line as attributes and methods # to class # for key in self.__keys.keys(): # self[key] = self.__keys[key] # attach those attributes and methods, we don't have given values for on # the contructor line, but can be calculated easily if "T" not in self.__keys and "T_F" in self.__keys and "T_A" in self.__keys: self.T = 0.5 * (self.T_F + self.T_A) if "T" in self.__keys and "T_F" not in self.__keys: self.T_F = self.T if "T" in self.__keys and "T_A" not in self.__keys: self.T_A = self.T if ( "C_WP" not in self.__keys and "A_WP" in self.__keys and "L_WP" in self.__keys and "B_WP" in self.__keys ): self.C_WP = (self.A_WP / (self.L_WP * self.B_WP)).value if "App" not in self.__keys: self.App = [] if "S_app" in self.__keys and "k_2" in self.__keys: if len(self.__keys["S_app"]) != len(self.__keys["k_2"]): raise ValueError self.App = [_ for _ in zip(self.__keys["S_app"], self.__keys["k_2"])]
[docs] @abstractmethod def R(self, speed: Quantity) -> Quantity: "Calculate total ship resistance."
@property def L(self): return self.__keys["L"] @L.setter def L(self, value): self.__keys["L"] = value @property def h_b(self): return self.__keys["h_b"] @h_b.setter def h_b(self, value): self.__keys["h_b"] = value @property def Nab(self): if self.__keys["Nab"] is None: return (self.L * self.B * self.T) * self.C_B return self.__keys["Nab"] @Nab.setter def Nab(self, value): self.__keys["Nab"] = value @property def T_F(self): return self.__keys["T_F"] @T_F.setter def T_F(self, value): self.__keys["T_F"] = value @property def T_A(self): return self.__keys["T_A"] @T_A.setter def T_A(self, value): self.__keys["T_A"] = value @property def T(self): return self.__keys["T"] @T.setter def T(self, value): self.__keys["T"] = value @property def B(self): return self.__keys["B"] @B.setter def B(self, value): self.__keys["B"] = value @property def S(self) -> float: """Wetted surface for ship. Calculates the wetted area of the hull if none is given. Known methods are: - Holtrop : Approximated wetted area of a ship hull, according to J. Holtrop and G. G. J. Mennen, An Approximate Power Predition Method, International shipbuilding progress. Vol. 29, 1982, p. 166 - Schenzle: Default method is Holtrop. """ if "S" not in self.__keys: if self._S_method == WettedSurfaceEstimationMethod.Holtrop: return ( self.L * (2.0 * self.T + self.B) * math.pow(self.C_M, 0.5) * ( 0.453 + 0.4425 * self.C_B - 0.2862 * self.C_M - 0.003467 * self.B / self.T + 0.3696 * self.C_WP ) + 2.38 * self.A_BT / self.C_B ) elif self._S_method == WettedSurfaceEstimationMethod.Schenzle: B = self.C_WP * self.B / self.T C = self.L / self.B / self.C_M A1 = (1.0 + B / 2.0 - math.pow(1.0 + B * B / 4.0, 0.5)) * 2.0 / B A2 = 1.0 + C - math.pow(1.0 + C * C, 0.5) CN1 = 0.8 + 0.2 * B CN2 = 1.15 + 0.2833 * C CPX = self.C_B / self.C_M CPZ = self.C_B / self.C_WP C1 = 1.0 - A1 * math.pow(1.0 - (2.0 * CPZ - 1.0) ** CN1, 0.5) C2 = 1.0 - A2 * math.pow(1.0 - (2.0 * CPX - 1.0) ** CN2, 0.5) return (2.0 + C1 * B + 2.0 * C2 / C) * self.L * self.T else: raise KeyError return self.__keys["S"] @S.setter def S(self, value): self.__keys["S"] = value @property def C_Stern(self): return self.__keys["C_Stern"] @C_Stern.setter def C_Stern(self, value): self.__keys["C_Stern"] = value @property def C_WP(self): """Return waterplane coefficient of the hull. Calculates the waterplane coefficient of the hull if none is given. """ if "C_WP" not in self.__keys: if self.__C_WP_method == WaterlineCoefficientMethod.Schneekluth_U: return C_WP_Schneekluth_U(self.C_P) elif self.__C_WP_method == WaterlineCoefficientMethod.Schneekluth_medium: return C_WP_Schneekluth_medium(self.C_B) elif self.__C_WP_method == WaterlineCoefficientMethod.Schneekluth_V: return C_WP_Schneekluth_V(self.C_B) elif self.__C_WP_method == WaterlineCoefficientMethod.Schneekluth_V_alt_1: return C_WP_Schneekluth_V_alt_1(self.C_P) elif self.__C_WP_method == WaterlineCoefficientMethod.Schneekluth_V_alt_2: return C_WP_Schneekluth_V_alt_2(self.C_B, self.C_M) else: raise KeyError raise ShipInternalError return self.C_WP_calc() return self.__keys["C_WP"] @C_WP.setter def C_WP(self, value): self.__keys["C_WP"] = value @property def C_B(self): """Estimate :math:`C_B`, the block coefficient based on the Froude No.""" if "C_B" not in self.__keys: speed = self.__default_speed F_n = hydro.F_n(speed, self.L) match self.__C_B_method: case BlockCoefficientMethod.Ayre_1_06: return C_B_Ayre_1_06(F_n) case BlockCoefficientMethod.Ayre_1_08: return C_B_Ayre_1_08(F_n) case BlockCoefficientMethod.Ayre_1_09: return C_B_Ayre_1_09(F_n) case BlockCoefficientMethod.Schneekluth_1: return C_B_Schneekluth_1(F_n, self.L, self.B) case BlockCoefficientMethod.Schneekluth_2: return C_B_Schneekluth_2(F_n, self.L, self.B) case _: raise KeyError("unknown method") return self.__keys["C_B"] @C_B.setter def C_B(self, value): self.__keys["C_B"] = value @property def i_E(self): if self.__keys["i_E"] is None: return self.i_E_calc() return self.__keys["i_E"] @i_E.setter def i_E(self, value): self.__keys["i_E"] = value @property def C_P(self): if self.__keys["C_P"] is None: return self.C_B / self.C_M return self.__keys["C_P"] @C_P.setter def C_P(self, value): self.__keys["C_P"] = value @property def C_A(self): return self.__keys.get("C_A") @C_A.setter def C_A(self, value): self.__keys["C_A"] = value @property def App(self): return self.__keys["App"] @App.setter def App(self, value): self.__keys["App"] = value @property def C_M(self): return self.__keys["C_M"] @C_M.setter def C_M(self, value): self.__keys["C_M"] = value @property def A_T(self): return self.__keys["A_T"] @A_T.setter def A_T(self, value): self.__keys["A_T"] = value @property def A_WP(self): return self.__keys["A_WP"] @A_WP.setter def A_WP(self, value): self.__keys["A_WP"] = value @property def L_WP(self): return self.__keys["L_WP"] @L_WP.setter def L_WP(self, value): self.__keys["L_WP"] = value @property def B_WP(self): return self.__keys["B_WP"] @B_WP.setter def B_WP(self, value): self.__keys["B_WP"] = value @property def A_BT(self) -> Quantity: return self.__keys["A_BT"] @A_BT.setter def A_BT(self, value: Quantity): self.__keys["A_BT"] = value
[docs] def R_F(self, speed: Quantity) -> Quantity: """Frictional resitance according to the ITTC-57 formula""" return hydro.C_F(speed, self.L) * 0.5 * hydro.rho * pow(speed, 2) * self.S
@property def lcb(self) -> Quantity: return self.__keys["lcb"] @lcb.setter def lcb(self, value) -> None: self.__keys["lcb"] = value
[docs] def __call__(self) -> dict: return self.__keys
[docs] def __repr__(self) -> str: out = self.__class__.__name__ + "(" sep = "" for i in self.__keys: out = out + sep + i + " = " + repr(self.__keys[i]) sep = ", " return out + ")"
[docs] def set_S_Method(self, method: WettedSurfaceEstimationMethod) -> None: """Set method for calculating the wette surface :math:`S`.""" self._S_method = method
[docs] def set_C_WP_Method(self, method: WaterlineCoefficientMethod) -> None: """Set method for calculating Waterline Coefficient :math:`C_{WP}`.""" self.__C_WP_method = method
[docs] def set_C_B_method(self, method: BlockCoefficientMethod) -> None: """Set method for calculating Block Coefficient :math:`C_B`.""" self.__C_B_method = method
[docs] def set_default_speed(self, speed: Quantity) -> None: """Set the default speed for calculations.""" self.__default_speed = speed
[docs] def load(self, file: Path | None = None) -> None | dict: """Load data from configuration file.""" if file is None: return None out = {} with file.open() as inp: for _line in inp: line = _line.strip().split("#")[0].strip() if len(line) > 0: res = line.split("=") key, value = res[0].strip(), res[1].strip() out[key] = ast.literal_eval(value) self.__keys.append(key) return out
[docs] def save(self, file: Path | None = None) -> None: """Save current data.""" raise NotImplementedError
[docs] def execute(self, entity: str) -> None: """Excute actions.""" sys.stdout.write(f"entity {entity}\n")
[docs] class ShipError(Exception): """Indicate error."""
[docs] class ShipTypeError(TypeError): """Indicate use of wrong type."""
[docs] class ShipInternalError(ShipError): """Indicate internal error."""