0f85935fc8
- 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>
153 lines
4.1 KiB
Python
153 lines
4.1 KiB
Python
"""
|
||
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 0–30° ≥ 0.055 m·rad
|
||
2.1.2 Área 0–40° ≥ 0.090 m·rad
|
||
2.1.3 Área 30–40° ≥ 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 0–30°",
|
||
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 0–40°",
|
||
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 30–40°",
|
||
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)
|