Módulo 1 fixes + Módulo 2 motor hidrostático (Tasks 13–13b)
Fixes Module 1 UI: - wizard_cruiser/sailing/planing: perfiles sin^n calibrados por Cm, V-bottom con ángulo de astilla, corrección zona sobre chine planeador - viewer_3d: buffer hull pendiente para eliminar race condition 500ms - viewer_lines: reescritura completa — waterlines visibles, control points interactivos (drag DelftShip-style), señal offsets_edited - main_window: conecta offsets_edited → slot _on_offsets_edited_from_viewer que propaga cambios a todos los visores, editor, 3D y barra hidrostática Módulo 2 — motor HydrostaticCurves (Task 13): - integrator.py: integrate() (Simpson+trapz), waterplane_strips(), section_areas() - upright.py: UprightHydrostatics (19 campos), compute_upright() single-pass - curves_of_form.py: HydrostaticCurves.compute(), at_draft(), to_csv_lines(), to_dict() - tests/test_module2_hydrostatics.py: 83 tests — Wigley V&V, monotonicidad, CSV export, IACS Rec.34 §4.3–4.5; todos los 224 tests pasan Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1,30 @@
|
||||
# arshipdesign/hydrostatics
|
||||
"""
|
||||
arshipdesign.hydrostatics — motor de hidrostáticos navales.
|
||||
|
||||
Módulos
|
||||
-------
|
||||
integrator Primitivas numéricas (Simpson/trapecios).
|
||||
upright UprightHydrostatics (calado único) + compute_upright().
|
||||
curves_of_form HydrostaticCurves (barrido de calados).
|
||||
|
||||
Uso rápido
|
||||
----------
|
||||
>>> from arshipdesign.hydrostatics import HydrostaticCurves
|
||||
>>> curves = HydrostaticCurves.compute(hull, n_points=20)
|
||||
>>> print(curves.displacements)
|
||||
"""
|
||||
from arshipdesign.hydrostatics.upright import (
|
||||
UprightHydrostatics,
|
||||
compute_upright,
|
||||
)
|
||||
from arshipdesign.hydrostatics.curves_of_form import (
|
||||
HydrostaticCurves,
|
||||
CSV_HEADERS,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"UprightHydrostatics",
|
||||
"compute_upright",
|
||||
"HydrostaticCurves",
|
||||
"CSV_HEADERS",
|
||||
]
|
||||
|
||||
@@ -1,2 +1,342 @@
|
||||
"""Curvas hidrostáticas. Stub — Sprint 2."""
|
||||
raise NotImplementedError("curves_of_form — Sprint 2")
|
||||
"""
|
||||
HydrostaticCurves — curvas de formas hidrostáticas.
|
||||
|
||||
Evalúa UprightHydrostatics en *n_points* calados equidistantes entre
|
||||
el 10 % del calado de diseño y el propio calado de diseño, produciendo
|
||||
las curvas clásicas usadas en arquitectura naval.
|
||||
|
||||
Conforme con IACS Rec.34 §4 — verificación y §6 — trazabilidad.
|
||||
|
||||
Autor: Álvaro Romero
|
||||
Módulo 2 — AR-ShipDesign
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Iterator
|
||||
|
||||
import numpy as np
|
||||
|
||||
from arshipdesign.hydrostatics.upright import UprightHydrostatics, compute_upright
|
||||
|
||||
|
||||
# Encabezados de exportación CSV (orden canónico)
|
||||
CSV_HEADERS: tuple[str, ...] = (
|
||||
"T[m]", "V[m3]", "Delta[t]", "Awp[m2]",
|
||||
"LCB[m]", "LCF[m]", "KB[m]",
|
||||
"IT[m4]", "IL[m4]",
|
||||
"BMT[m]", "BML[m]", "KMT[m]", "KML[m]",
|
||||
"TPC[t/cm]", "MCT[tm/cm]",
|
||||
"Cb", "Cw", "Cm", "Cp",
|
||||
)
|
||||
|
||||
# Mapa campo → atributo de UprightHydrostatics (mismo orden que CSV_HEADERS)
|
||||
_FIELD_ATTRS: tuple[str, ...] = (
|
||||
"draft", "volume", "displacement", "awp",
|
||||
"lcb", "lcf", "kb",
|
||||
"it", "il",
|
||||
"bmt", "bml", "kmt", "kml",
|
||||
"tpc", "mct",
|
||||
"cb", "cw", "cm", "cp",
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dataclass principal
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@dataclass
|
||||
class HydrostaticCurves:
|
||||
"""Curvas de formas hidrostáticas de un casco naval.
|
||||
|
||||
Se construye con el método de clase :meth:`compute`. Una vez creado,
|
||||
los puntos individuales se acceden con índice (``curves[i]``) o
|
||||
iteración, y los arrays vectorizados con propiedades nombradas
|
||||
(``curves.drafts``, ``curves.displacements``, etc.).
|
||||
|
||||
Atributos
|
||||
---------
|
||||
hull_name : str
|
||||
Nombre del casco.
|
||||
lpp : float
|
||||
Eslora entre perpendiculares [m].
|
||||
beam : float
|
||||
Manga máxima [m].
|
||||
design_draft : float
|
||||
Calado de diseño [m].
|
||||
rho : float
|
||||
Densidad del agua usada [kg/m³].
|
||||
points : list[UprightHydrostatics]
|
||||
Puntos de la curva, ordenados por calado creciente.
|
||||
"""
|
||||
|
||||
hull_name: str
|
||||
lpp: float
|
||||
beam: float
|
||||
design_draft: float
|
||||
rho: float
|
||||
points: list[UprightHydrostatics] = field(default_factory=list)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Constructor principal
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@classmethod
|
||||
def compute(
|
||||
cls,
|
||||
hull,
|
||||
n_points: int = 20,
|
||||
rho: float = 1025.0,
|
||||
kg: float | None = None,
|
||||
t_min_fraction: float = 0.10,
|
||||
) -> "HydrostaticCurves":
|
||||
"""Calcula las curvas hidrostáticas completas para *hull*.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
hull : Hull
|
||||
Casco de referencia (``arshipdesign.core.hull.Hull``).
|
||||
n_points : int
|
||||
Número de calados a evaluar. Mínimo 5.
|
||||
rho : float
|
||||
Densidad del agua [kg/m³]. Default 1025 (agua salada).
|
||||
kg : float | None
|
||||
KG estimado [m]. Si None se usa ``hull.depth × 0.55``.
|
||||
t_min_fraction : float
|
||||
Fracción del calado de diseño para el calado mínimo de cálculo.
|
||||
Default 0.10 (10 %). Se aplica un piso de 1 mm.
|
||||
|
||||
Returns
|
||||
-------
|
||||
HydrostaticCurves
|
||||
"""
|
||||
n_points = max(5, int(n_points))
|
||||
T_min = max(hull.draft * float(t_min_fraction), 1e-3)
|
||||
T_max = float(hull.draft)
|
||||
drafts = np.linspace(T_min, T_max, n_points)
|
||||
|
||||
points = [
|
||||
compute_upright(hull, float(T), rho=rho, kg=kg)
|
||||
for T in drafts
|
||||
]
|
||||
|
||||
return cls(
|
||||
hull_name = str(hull.name),
|
||||
lpp = float(hull.lpp),
|
||||
beam = float(hull.beam),
|
||||
design_draft = T_max,
|
||||
rho = float(rho),
|
||||
points = points,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Acceso individual y por iteración
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.points)
|
||||
|
||||
def __getitem__(self, idx: int) -> UprightHydrostatics:
|
||||
return self.points[idx]
|
||||
|
||||
def __iter__(self) -> Iterator[UprightHydrostatics]:
|
||||
return iter(self.points)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
n = len(self.points)
|
||||
if n:
|
||||
T_range = f"{self.points[0].draft:.3f}–{self.points[-1].draft:.3f} m"
|
||||
else:
|
||||
T_range = "vacío"
|
||||
return f"HydrostaticCurves({self.hull_name!r}, n={n}, T={T_range})"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Arrays vectorizados (numpy)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _col(self, attr: str) -> np.ndarray:
|
||||
"""Extrae un atributo de todos los puntos como array numpy."""
|
||||
return np.array([getattr(p, attr) for p in self.points])
|
||||
|
||||
@property
|
||||
def drafts(self) -> np.ndarray:
|
||||
"""Calados T [m]."""
|
||||
return self._col("draft")
|
||||
|
||||
@property
|
||||
def volumes(self) -> np.ndarray:
|
||||
"""Volúmenes de desplazamiento V [m³]."""
|
||||
return self._col("volume")
|
||||
|
||||
@property
|
||||
def displacements(self) -> np.ndarray:
|
||||
"""Desplazamientos Δ [t]."""
|
||||
return self._col("displacement")
|
||||
|
||||
@property
|
||||
def awp_values(self) -> np.ndarray:
|
||||
"""Áreas de flotación Awp [m²]."""
|
||||
return self._col("awp")
|
||||
|
||||
@property
|
||||
def lcb_values(self) -> np.ndarray:
|
||||
"""LCB desde AP [m]."""
|
||||
return self._col("lcb")
|
||||
|
||||
@property
|
||||
def lcf_values(self) -> np.ndarray:
|
||||
"""LCF desde AP [m]."""
|
||||
return self._col("lcf")
|
||||
|
||||
@property
|
||||
def kb_values(self) -> np.ndarray:
|
||||
"""KB sobre la quilla [m]."""
|
||||
return self._col("kb")
|
||||
|
||||
@property
|
||||
def bmt_values(self) -> np.ndarray:
|
||||
"""Radios metacéntricos transversales BM_T [m]."""
|
||||
return self._col("bmt")
|
||||
|
||||
@property
|
||||
def bml_values(self) -> np.ndarray:
|
||||
"""Radios metacéntricos longitudinales BM_L [m]."""
|
||||
return self._col("bml")
|
||||
|
||||
@property
|
||||
def kmt_values(self) -> np.ndarray:
|
||||
"""Alturas metacéntricas transversales KM_T [m]."""
|
||||
return self._col("kmt")
|
||||
|
||||
@property
|
||||
def kml_values(self) -> np.ndarray:
|
||||
"""Alturas metacéntricas longitudinales KM_L [m]."""
|
||||
return self._col("kml")
|
||||
|
||||
@property
|
||||
def tpc_values(self) -> np.ndarray:
|
||||
"""TPC [t/cm]."""
|
||||
return self._col("tpc")
|
||||
|
||||
@property
|
||||
def mct_values(self) -> np.ndarray:
|
||||
"""MCT [t·m/cm]."""
|
||||
return self._col("mct")
|
||||
|
||||
@property
|
||||
def cb_values(self) -> np.ndarray:
|
||||
"""Coeficientes de bloque Cb [-]."""
|
||||
return self._col("cb")
|
||||
|
||||
@property
|
||||
def cw_values(self) -> np.ndarray:
|
||||
"""Coeficientes de flotación Cw [-]."""
|
||||
return self._col("cw")
|
||||
|
||||
@property
|
||||
def cm_values(self) -> np.ndarray:
|
||||
"""Coeficientes de cuaderna maestra Cm [-]."""
|
||||
return self._col("cm")
|
||||
|
||||
@property
|
||||
def cp_values(self) -> np.ndarray:
|
||||
"""Coeficientes prismáticos Cp [-]."""
|
||||
return self._col("cp")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Interpolación a calado arbitrario
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def at_draft(self, T: float) -> UprightHydrostatics:
|
||||
"""Interpola linealmente todos los hidrostáticos al calado *T*.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
T : float
|
||||
Calado de consulta [m]. Se clampea al rango [T_min, T_design].
|
||||
|
||||
Returns
|
||||
-------
|
||||
UprightHydrostatics
|
||||
Valores interpolados en *T*.
|
||||
"""
|
||||
drafts_arr = self.drafts
|
||||
T_clamped = float(np.clip(T, drafts_arr[0], drafts_arr[-1]))
|
||||
|
||||
kwargs: dict[str, float] = {"draft": T_clamped}
|
||||
for attr in _FIELD_ATTRS:
|
||||
if attr == "draft":
|
||||
continue
|
||||
col = self._col(attr)
|
||||
kwargs[attr] = float(np.interp(T_clamped, drafts_arr, col))
|
||||
|
||||
return UprightHydrostatics(**kwargs) # type: ignore[arg-type]
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Exportación
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def to_csv_lines(self, sep: str = ",", decimal: str = ".") -> list[str]:
|
||||
"""Genera las líneas CSV (encabezado + datos).
|
||||
|
||||
No requiere pandas.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sep : str
|
||||
Separador de campos. Default ``','``.
|
||||
decimal : str
|
||||
Separador decimal. Default ``'.'``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[str]
|
||||
Lista de cadenas, cada una es una línea CSV sin ``\\n`` final.
|
||||
"""
|
||||
lines = [sep.join(CSV_HEADERS)]
|
||||
for p in self.points:
|
||||
row = [
|
||||
f"{p.draft:.4f}", f"{p.volume:.4f}",
|
||||
f"{p.displacement:.4f}", f"{p.awp:.4f}",
|
||||
f"{p.lcb:.4f}", f"{p.lcf:.4f}",
|
||||
f"{p.kb:.4f}", f"{p.it:.4f}",
|
||||
f"{p.il:.4f}", f"{p.bmt:.4f}",
|
||||
f"{p.bml:.4f}", f"{p.kmt:.4f}",
|
||||
f"{p.kml:.4f}", f"{p.tpc:.4f}",
|
||||
f"{p.mct:.4f}", f"{p.cb:.4f}",
|
||||
f"{p.cw:.4f}", f"{p.cm:.4f}",
|
||||
f"{p.cp:.4f}",
|
||||
]
|
||||
if decimal != ".":
|
||||
row = [v.replace(".", decimal) for v in row]
|
||||
lines.append(sep.join(row))
|
||||
return lines
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Serializa a dict JSON-serializable.
|
||||
|
||||
Útil para almacenar resultados en el archivo ``.arsd``.
|
||||
"""
|
||||
return {
|
||||
"hull_name": self.hull_name,
|
||||
"lpp": self.lpp,
|
||||
"beam": self.beam,
|
||||
"design_draft": self.design_draft,
|
||||
"rho": self.rho,
|
||||
"headers": list(CSV_HEADERS),
|
||||
"points": [
|
||||
{
|
||||
"T": p.draft, "V": p.volume,
|
||||
"Delta": p.displacement, "Awp": p.awp,
|
||||
"LCB": p.lcb, "LCF": p.lcf,
|
||||
"KB": p.kb, "IT": p.it,
|
||||
"IL": p.il, "BMT": p.bmt,
|
||||
"BML": p.bml, "KMT": p.kmt,
|
||||
"KML": p.kml, "TPC": p.tpc,
|
||||
"MCT": p.mct, "Cb": p.cb,
|
||||
"Cw": p.cw, "Cm": p.cm,
|
||||
"Cp": p.cp,
|
||||
}
|
||||
for p in self.points
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,2 +1,95 @@
|
||||
"""Integradores Simpson. Stub — Sprint 2."""
|
||||
raise NotImplementedError("integrator — Sprint 2")
|
||||
"""
|
||||
Integradores numéricos para hidrostáticos navales.
|
||||
|
||||
Regla de Simpson con fallback a trapecios cuando hay menos de 3 puntos.
|
||||
Conforme con IACS Rec.34 §4.2 — métodos de integración numérica.
|
||||
|
||||
Autor: Álvaro Romero
|
||||
Módulo 2 — AR-ShipDesign
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
from scipy.integrate import simpson as _scipy_simpson
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Integración 1D
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def integrate(y: np.ndarray, x: np.ndarray) -> float:
|
||||
"""Integra y(x) usando la regla de Simpson (fallback a trapecios ≤ 2 pts).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
y : array_like, shape (n,)
|
||||
Ordenadas.
|
||||
x : array_like, shape (n,)
|
||||
Abscisas, monótonamente crecientes.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
∫ y dx
|
||||
"""
|
||||
y = np.asarray(y, dtype=float)
|
||||
x = np.asarray(x, dtype=float)
|
||||
n = len(x)
|
||||
if n < 2:
|
||||
return 0.0
|
||||
if n >= 3:
|
||||
return float(_scipy_simpson(y, x=x))
|
||||
return float(np.trapz(y, x))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Primitivas para plano de flotación y secciones
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def waterplane_strips(offsets_table, draft: float) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Devuelve (x_stations, y_half_breadths) en el calado *draft*.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
offsets_table : OffsetsTable
|
||||
Tabla de offsets del casco.
|
||||
draft : float
|
||||
Calado al que se evalúa el plano de flotación [m].
|
||||
|
||||
Returns
|
||||
-------
|
||||
x : np.ndarray, shape (n_sta,)
|
||||
Posiciones longitudinales de las estaciones [m].
|
||||
y : np.ndarray, shape (n_sta,)
|
||||
Semi-mangas en el plano draft [m].
|
||||
"""
|
||||
x = offsets_table.x_stations
|
||||
y = np.array([offsets_table.half_breadth(xi, float(draft)) for xi in x])
|
||||
return x, y
|
||||
|
||||
|
||||
def section_areas_and_centroids(
|
||||
sections: list, draft: float
|
||||
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
||||
"""Devuelve (x, areas, centroides_z) para todas las secciones al calado *draft*.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sections : list[Section]
|
||||
Lista de secciones del casco.
|
||||
draft : float
|
||||
Calado de cálculo [m].
|
||||
|
||||
Returns
|
||||
-------
|
||||
x : np.ndarray, shape (n_sec,)
|
||||
Posiciones longitudinales [m].
|
||||
areas : np.ndarray, shape (n_sec,)
|
||||
Áreas sumergidas [m²].
|
||||
cz : np.ndarray, shape (n_sec,)
|
||||
Centroides verticales de cada sección [m desde quilla].
|
||||
"""
|
||||
x = np.array([s.x for s in sections])
|
||||
a = np.array([s.area(draft=draft) for s in sections])
|
||||
cz = np.array([s.centroid_z(draft=draft) for s in sections])
|
||||
return x, a, cz
|
||||
|
||||
@@ -1,2 +1,227 @@
|
||||
"""Hidrostáticos vertical. Stub — Sprint 2."""
|
||||
raise NotImplementedError("upright — Sprint 2")
|
||||
"""
|
||||
UprightHydrostatics — hidrostáticos en condición vertical (quilla recta).
|
||||
|
||||
Calcula el conjunto completo de variables hidrostáticas para un calado
|
||||
dado: volumen, desplazamiento, plano de flotación, metacentros, TPC, MCT
|
||||
y los cuatro coeficientes de forma.
|
||||
|
||||
Conforme con IACS Rec.34 §4.3 — verificación analítica y §6 — trazabilidad.
|
||||
|
||||
Autor: Álvaro Romero
|
||||
Módulo 2 — AR-ShipDesign
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
|
||||
from arshipdesign.hydrostatics.integrator import (
|
||||
integrate,
|
||||
waterplane_strips,
|
||||
section_areas_and_centroids,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dataclass de resultado
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@dataclass
|
||||
class UprightHydrostatics:
|
||||
"""Conjunto completo de hidrostáticos upright a un calado dado.
|
||||
|
||||
Todos los valores referidos a:
|
||||
- Origen longitudinal: AP (x = 0)
|
||||
- Origen vertical: quilla (z = 0)
|
||||
- Plano de crujía: eje de simetría
|
||||
|
||||
Atributos
|
||||
---------
|
||||
draft : float
|
||||
Calado T [m].
|
||||
volume : float
|
||||
Volumen de desplazamiento V [m³].
|
||||
displacement : float
|
||||
Desplazamiento Δ [t] (agua salada ρ = 1025 kg/m³ por defecto).
|
||||
awp : float
|
||||
Área del plano de flotación Awp [m²].
|
||||
lcb : float
|
||||
Centro longitudinal de carena desde AP LCB [m].
|
||||
lcf : float
|
||||
Centro longitudinal de flotación desde AP LCF [m].
|
||||
kb : float
|
||||
Altura del centro de carena sobre la quilla KB [m].
|
||||
it : float
|
||||
Segundo momento transversal del plano de flotación IT [m⁴].
|
||||
il : float
|
||||
Segundo momento longitudinal del plano de flotación IL [m⁴].
|
||||
bmt : float
|
||||
Radio metacéntrico transversal BM_T = IT / V [m].
|
||||
bml : float
|
||||
Radio metacéntrico longitudinal BM_L = IL / V [m].
|
||||
kmt : float
|
||||
Altura del metacentro transversal KM_T = KB + BM_T [m].
|
||||
kml : float
|
||||
Altura del metacentro longitudinal KM_L = KB + BM_L [m].
|
||||
tpc : float
|
||||
Toneladas por centímetro de inmersión TPC [t/cm].
|
||||
mct : float
|
||||
Momento para cambiar asiento 1 cm MCT [t·m/cm].
|
||||
cb : float
|
||||
Coeficiente de bloque Cb [-].
|
||||
cw : float
|
||||
Coeficiente de plano de flotación Cw [-].
|
||||
cm : float
|
||||
Coeficiente de cuaderna maestra Cm [-].
|
||||
cp : float
|
||||
Coeficiente prismático Cp [-].
|
||||
"""
|
||||
|
||||
draft: float
|
||||
volume: float
|
||||
displacement: float
|
||||
awp: float
|
||||
lcb: float
|
||||
lcf: float
|
||||
kb: float
|
||||
it: float
|
||||
il: float
|
||||
bmt: float
|
||||
bml: float
|
||||
kmt: float
|
||||
kml: float
|
||||
tpc: float
|
||||
mct: float
|
||||
cb: float
|
||||
cw: float
|
||||
cm: float
|
||||
cp: float
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Función de cálculo
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def compute_upright(
|
||||
hull,
|
||||
draft: float,
|
||||
rho: float = 1025.0,
|
||||
kg: float | None = None,
|
||||
) -> UprightHydrostatics:
|
||||
"""Calcula todos los hidrostáticos upright para *hull* al calado *draft*.
|
||||
|
||||
El cálculo se realiza en una sola pasada sobre las secciones, reutilizando
|
||||
los arrays intermedios para evitar redundancia.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
hull : Hull
|
||||
Casco de referencia (objeto ``arshipdesign.core.hull.Hull``).
|
||||
draft : float
|
||||
Calado de cálculo T [m]. Si T ≤ 0, retorna ceros.
|
||||
rho : float
|
||||
Densidad del agua [kg/m³]. Default 1025 (agua salada).
|
||||
kg : float | None
|
||||
Altura del centro de gravedad KG [m]. Si None se estima
|
||||
como ``hull.depth × 0.55`` (buque en rosca, conservador).
|
||||
|
||||
Returns
|
||||
-------
|
||||
UprightHydrostatics
|
||||
Todos los hidrostáticos al calado *draft*.
|
||||
"""
|
||||
T = float(draft)
|
||||
|
||||
# Caso degenerado (calado nulo o negativo)
|
||||
if T <= 1e-6:
|
||||
return _zero_hydrostatics(T)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 1. Secciones transversales → volumen, LCB, KB
|
||||
# ----------------------------------------------------------------
|
||||
sections = hull.offsets.to_sections()
|
||||
x_s, areas, cz = section_areas_and_centroids(sections, T)
|
||||
|
||||
vol = abs(integrate(areas, x_s))
|
||||
delta = vol * rho / 1000.0
|
||||
|
||||
if vol > 1e-12:
|
||||
lcb = integrate(areas * x_s, x_s) / vol
|
||||
kb = integrate(areas * cz, x_s) / vol
|
||||
else:
|
||||
lcb = hull.lpp / 2.0
|
||||
kb = T / 2.0
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 2. Plano de flotación → Awp, LCF, IT, IL
|
||||
# ----------------------------------------------------------------
|
||||
x_wl, y_wl = waterplane_strips(hull.offsets, T)
|
||||
strip = 2.0 * y_wl # ancho total a cada x
|
||||
|
||||
awp = abs(integrate(strip, x_wl))
|
||||
|
||||
if awp > 1e-12:
|
||||
lcf = integrate(strip * x_wl, x_wl) / awp
|
||||
else:
|
||||
lcf = hull.lpp / 2.0
|
||||
|
||||
# IT = (2/3) · ∫ y³ dx (Rawson & Tupper §3.2)
|
||||
it = abs(integrate((2.0 / 3.0) * y_wl ** 3, x_wl))
|
||||
|
||||
# IL = ∫ 2y · (x − LCF)² dx
|
||||
il = abs(integrate(strip * (x_wl - lcf) ** 2, x_wl))
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 3. Radios metacéntricos
|
||||
# ----------------------------------------------------------------
|
||||
bmt = it / vol if vol > 1e-12 else 0.0
|
||||
bml = il / vol if vol > 1e-12 else 0.0
|
||||
kmt = kb + bmt
|
||||
kml = kb + bml
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 4. TPC y MCT
|
||||
# ----------------------------------------------------------------
|
||||
tpc = awp * rho / 100_000.0
|
||||
|
||||
kg_val = hull.depth * 0.55 if kg is None else float(kg)
|
||||
gml = max(kb + bml - kg_val, 0.0)
|
||||
mct = delta * gml / (100.0 * hull.lpp) if hull.lpp > 1e-12 else 0.0
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 5. Coeficientes de forma
|
||||
# ----------------------------------------------------------------
|
||||
cb = vol / (hull.lpp * hull.beam * T) if (hull.lpp * hull.beam * T) > 1e-12 else 0.0
|
||||
cw = awp / (hull.lpp * hull.beam) if (hull.lpp * hull.beam) > 1e-12 else 0.0
|
||||
|
||||
# Área de cuaderna maestra: interpolar en x_mid
|
||||
x_mid = hull.lpp / 2.0
|
||||
am = float(np.interp(x_mid, x_s, areas))
|
||||
cm = am / (hull.beam * T) if (hull.beam * T) > 1e-12 else 0.0
|
||||
cp = vol / (am * hull.lpp) if (am * hull.lpp) > 1e-12 else 0.0
|
||||
|
||||
return UprightHydrostatics(
|
||||
draft=T, volume=vol, displacement=delta,
|
||||
awp=awp, lcb=lcb, lcf=lcf, kb=kb,
|
||||
it=it, il=il,
|
||||
bmt=bmt, bml=bml, kmt=kmt, kml=kml,
|
||||
tpc=tpc, mct=mct,
|
||||
cb=cb, cw=cw, cm=cm, cp=cp,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Auxiliar privado
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _zero_hydrostatics(draft: float) -> UprightHydrostatics:
|
||||
"""Devuelve un UprightHydrostatics con todos los valores en cero."""
|
||||
return UprightHydrostatics(
|
||||
draft=draft, volume=0.0, displacement=0.0,
|
||||
awp=0.0, lcb=0.0, lcf=0.0, kb=0.0,
|
||||
it=0.0, il=0.0,
|
||||
bmt=0.0, bml=0.0, kmt=0.0, kml=0.0,
|
||||
tpc=0.0, mct=0.0,
|
||||
cb=0.0, cw=0.0, cm=0.0, cp=0.0,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user