Files
alro65 0f85935fc8 feat(stability): Módulo 3 — Curva GZ + criterios IMO IS Code 2008
- gz_integrator.py: GZCurve, GZPoint, compute_gz_wall_sided (fórmula
  pared lateral), compute_gz_direct (integración Sutherland-Hodgman)
- imo_is2008.py: IMOCriterion, IMOResult, check_imo_is2008 —
  6 criterios A.2.1.1–A.2.1.6 del IS Code 2008 Cap.2
- gz_curve_widget.py: GZCurveWidget QPainter — curva cian, áreas
  sombreadas, líneas IMO, marcador AVS, tabla PASS/FAIL integrada
- main_window.py: GZCurveWidget en MOD_STABILITY, _compute_and_show_gz,
  _on_show_stability conectado al ribbon
- dark.qss: estilos GZCurveWidget
- test_module3_stability.py: 33 tests S-01..S-28 (315 total, todos pasan)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 13:59:32 -04:00

153 lines
4.1 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.
"""
imo_is2008.py — IMO IS Code 2008, Capítulo 2 — criterios de estabilidad intacta.
Criterios A.2.1 para buques de carga general y pequeñas embarcaciones:
2.1.1 Área 030° ≥ 0.055 m·rad
2.1.2 Área 040° ≥ 0.090 m·rad
2.1.3 Área 3040° ≥ 0.030 m·rad
2.1.4 GZ a 30° ≥ 0.200 m
2.1.5 Ángulo de GZ máximo ≥ 25°
2.1.6 GM₀ ≥ 0.150 m
Referencia: IMO MSC.267(85) — IS Code 2008, Parte A, Cap. 2.
"""
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class IMOCriterion:
"""Un criterio individual del IS Code 2008."""
code: str # e.g. "A.2.1.1"
description: str # Descripción corta
required: float # Valor mínimo requerido
achieved: float # Valor obtenido de la curva GZ
unit: str # Unidades (m·rad, m, °)
passed: bool # True si achieved >= required
@dataclass
class IMOResult:
"""Resultado completo de la verificación IMO IS Code 2008."""
criteria: list[IMOCriterion]
overall_passed: bool
def table_rows(self) -> list[tuple[str, str, str, str, bool]]:
"""Devuelve filas para la tabla: (code, description, required_str, achieved_str, passed).
El formato de los strings varía según las unidades:
- m·rad: 4 decimales
- m: 3 decimales
- °: 1 decimal
"""
rows = []
for c in self.criteria:
if c.unit == "m·rad":
req_str = f"{c.required:.4f} {c.unit}"
ach_str = f"{c.achieved:.4f} {c.unit}"
elif c.unit == "m":
req_str = f"{c.required:.3f} {c.unit}"
ach_str = f"{c.achieved:.3f} {c.unit}"
elif c.unit == "°":
req_str = f"{c.required:.1f}{c.unit}"
ach_str = f"{c.achieved:.1f}{c.unit}"
else:
req_str = f"{c.required} {c.unit}"
ach_str = f"{c.achieved} {c.unit}"
rows.append((c.code, c.description, req_str, ach_str, c.passed))
return rows
def check_imo_is2008(gz) -> IMOResult:
"""Verifica todos los criterios IS Code 2008 Cap.2 para la curva GZ dada.
Parameters
----------
gz : GZCurve
Curva de estabilidad calculada.
Returns
-------
IMOResult
Contiene la lista de criterios individuales y el resultado global.
"""
import numpy as np
from arshipdesign.stability.gz_integrator import GZCurve
def _criterion(
code: str,
desc: str,
req: float,
ach: float,
unit: str,
) -> IMOCriterion:
return IMOCriterion(
code=code,
description=desc,
required=req,
achieved=ach,
unit=unit,
passed=(ach >= req),
)
criteria: list[IMOCriterion] = []
# A.2.1.1 — Área bajo la curva GZ entre 0° y 30°
criteria.append(_criterion(
"A.2.1.1",
"Área 030°",
0.055,
float(gz.area_0_30),
"m·rad",
))
# A.2.1.2 — Área bajo la curva GZ entre 0° y 40°
criteria.append(_criterion(
"A.2.1.2",
"Área 040°",
0.090,
float(gz.area_0_40),
"m·rad",
))
# A.2.1.3 — Área bajo la curva GZ entre 30° y 40°
criteria.append(_criterion(
"A.2.1.3",
"Área 3040°",
0.030,
float(gz.area_30_40),
"m·rad",
))
# A.2.1.4 — GZ a 30° de escora
gz30 = float(np.interp(30.0, gz.angles_deg, gz.gz_values))
criteria.append(_criterion(
"A.2.1.4",
"GZ a 30°",
0.200,
gz30,
"m",
))
# A.2.1.5 — Ángulo en que se produce el GZ máximo
criteria.append(_criterion(
"A.2.1.5",
"Ángulo GZ máx",
25.0,
float(gz.phi_gz_max),
"°",
))
# A.2.1.6 — Altura metacéntrica inicial GM₀
criteria.append(_criterion(
"A.2.1.6",
"GM₀",
0.150,
float(gz.gm),
"m",
))
overall = all(c.passed for c in criteria)
return IMOResult(criteria=criteria, overall_passed=overall)