98ff57ed08
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>
96 lines
2.7 KiB
Python
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
|