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

96 lines
2.7 KiB
Python

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