"""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."""