Files
alro65 98ff57ed08 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>
2026-05-27 09:11:58 -04:00

228 lines
7.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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,
)