Skip to content

Commit 6690b5d

Browse files
committed
harden: reject non-physical (degree-valued) cone and uptilt angles
WindIO stores angles in radians (SI): the IEA references carry cone_angle 0.0698 (4 deg), uptilt 0.1047 (6 deg) and root twist 0.2094 (12 deg), and the auto-RNA reads them directly, which the IEA-22 deck cross-check confirms to <0.5 % on CM. A hand-authored file that leaves an angle in degrees (cone_angle: 4 meaning 4 deg) would otherwise be evaluated at 4 rad = 229 deg and silently corrupt the coned lever and axial offset. Guard cone_angle and uptilt against a physical +/-45 deg bound so that mistake fails loudly with a message naming the radians convention.
1 parent bf23bbe commit 6690b5d

2 files changed

Lines changed: 47 additions & 2 deletions

File tree

src/pybmodes/io/windio.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,27 @@ def _finite_float(value: Any, what: str) -> float:
732732
return f
733733

734734

735+
def _finite_angle(value: Any, what: str, max_abs: float = float(np.pi / 4)) -> float:
736+
"""Coerce ``value`` to a finite angle in radians within a physical bound.
737+
738+
WindIO stores angles in radians (SI). A rotor precone or shaft tilt beyond
739+
~45 deg is non-physical for a horizontal-axis turbine and almost always
740+
means degrees were supplied by mistake (e.g. ``cone_angle: 4`` read as
741+
4 rad = 229 deg instead of 4 deg), which would silently corrupt the coned
742+
lever and axial offset. Reject it with a clear message rather than
743+
evaluate the trig at the wrong angle.
744+
"""
745+
ang = _finite_float(value, what)
746+
if abs(ang) > max_abs:
747+
raise ValueError(
748+
f"{what} = {ang:.4f} rad ({np.degrees(ang):.1f} deg) is outside the "
749+
f"physical range +/-{np.degrees(max_abs):.0f} deg. WindIO angles are "
750+
f"in radians; a value near a small integer usually means degrees "
751+
f"were supplied."
752+
)
753+
return ang
754+
755+
735756
def _positive_mass(value: Any, what: str) -> float:
736757
"""Coerce ``value`` to a positive, finite mass (kg), else raise."""
737758
m = _finite_float(value, what)
@@ -1055,7 +1076,7 @@ def _nac_geom(key: str) -> float:
10551076
return _finite_float(val, f"nacelle.drivetrain.{key}")
10561077

10571078
overhang = _nac_geom("overhang")
1058-
uptilt = _nac_geom("uptilt")
1079+
uptilt = _finite_angle(_nac_geom("uptilt"), "nacelle.drivetrain.uptilt")
10591080
dist_tt_hub = _nac_geom("distance_tt_hub")
10601081

10611082
# --- Hub: components.<hub>.elastic_properties_mb ---
@@ -1082,7 +1103,7 @@ def _nac_geom(key: str) -> float:
10821103
)
10831104
if hub_r < 0.0:
10841105
raise ValueError(f"hub diameter must be non-negative; got {hub_diam!r}.")
1085-
cone_ang = _finite_float(hub.get("cone_angle", 0.0), "hub cone_angle")
1106+
cone_ang = _finite_angle(hub.get("cone_angle", 0.0), "hub cone_angle")
10861107
cone_cos = float(np.cos(cone_ang))
10871108
cone_sin = float(np.sin(cone_ang))
10881109

tests/test_windio_rna.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,30 @@ def _mk(cone: float):
200200
assert coned.mass == pytest.approx(flat.mass)
201201

202202

203+
def test_rna_rejects_degree_valued_cone_angle(tmp_path: pathlib.Path) -> None:
204+
"""WindIO angles are radians; a degrees-valued cone_angle (e.g. 4 read as
205+
4 rad = 229 deg) is non-physical and rejected rather than silently
206+
evaluated at the wrong angle (Codex review on #130)."""
207+
pytest.importorskip("yaml")
208+
from pybmodes.io.windio import read_windio_rna
209+
210+
o = _base_ontology()
211+
o["components"]["hub"]["cone_angle"] = 4.0 # 4 deg mistakenly left un-converted
212+
with pytest.raises(ValueError, match="radians"):
213+
read_windio_rna(_write(o, tmp_path, "cone_deg.yaml"))
214+
215+
216+
def test_rna_rejects_degree_valued_uptilt(tmp_path: pathlib.Path) -> None:
217+
"""A degrees-valued uptilt is likewise rejected as out of physical range."""
218+
pytest.importorskip("yaml")
219+
from pybmodes.io.windio import read_windio_rna
220+
221+
o = _base_ontology()
222+
o["components"]["nacelle"]["drivetrain"]["uptilt"] = 6.0 # 6 deg un-converted
223+
with pytest.raises(ValueError, match="radians"):
224+
read_windio_rna(_write(o, tmp_path, "uptilt_deg.yaml"))
225+
226+
203227
@pytest.mark.parametrize("n_bl", [1, 2])
204228
def test_rna_rejects_one_or_two_bladed_rotor(
205229
tmp_path: pathlib.Path, n_bl: int

0 commit comments

Comments
 (0)