"""Core ``WingCanonical`` orchestration and validation."""
from __future__ import annotations
from buffalo_core.typing import FloatScalar
from .wing_canonical_distribution import WingCanonicalDistributionMixin
from .wing_canonical_sections import WingCanonicalSectionMixin
from .wing_schema import PanelSpec, WingSpec
_MIN_STATION_COUNT = 2
[docs]
class WingCanonical(
WingCanonicalDistributionMixin,
WingCanonicalSectionMixin,
):
"""
Canonical evaluator for wing-section placement and sampling.
Parameters
----------
spec : WingSpec
Parsed wing schema used for panel evaluation and section sampling.
Raises
------
ValueError
If the configured reference-axis and twist-axis combination is not
supported by the current implementation.
"""
def __init__(self, spec: WingSpec) -> None:
"""
Store the parsed wing specification and validate axis choices.
Parameters
----------
spec : WingSpec
Parsed wing schema used for panel evaluation and section
sampling.
Raises
------
ValueError
If the configured reference-axis and twist-axis combination is
not supported by the current implementation.
"""
self._spec = spec
self._panel_map = {panel.id: panel for panel in spec.wing.panels}
self._validate_axes()
[docs]
@classmethod
def from_spec(cls, spec: WingSpec) -> WingCanonical:
"""
Build a canonical evaluator from a parsed ``WingSpec``.
Parameters
----------
spec : WingSpec
Parsed wing schema used for panel evaluation and section
sampling.
Returns
-------
WingCanonical
Canonical evaluator backed by ``spec``.
Raises
------
ValueError
If the configured reference-axis and twist-axis combination is
not supported by the current implementation.
"""
return cls(spec)
@property
def spec(self) -> WingSpec:
"""
Return the underlying wing specification.
Returns
-------
WingSpec
Wing schema used by the evaluator.
"""
return self._spec
def _validate_axes(self) -> None:
"""
Reject unsupported reference-axis and twist-axis combinations.
Returns
-------
None
This method validates the stored wing specification in place.
Raises
------
ValueError
If the configured axes are unsupported by the canonical
evaluator.
"""
reference_axis = self._spec.wing.reference_axis
twist_axis = self._spec.wing.twist_axis
if reference_axis == "elastic_axis" or twist_axis == "elastic_axis":
msg = (
"elastic_axis is not yet supported because the schema does "
"not define its chordwise location."
)
raise ValueError(msg)
if reference_axis != twist_axis:
msg = (
"WingCanonical currently requires reference_axis and "
"twist_axis to match."
)
raise ValueError(msg)
def _get_panel(self, panel_id: str) -> PanelSpec:
"""
Return the panel definition for ``panel_id``.
Parameters
----------
panel_id : str
Identifier of the panel to retrieve.
Returns
-------
PanelSpec
Panel definition matching ``panel_id``.
Raises
------
KeyError
If ``panel_id`` does not match a known panel.
"""
try:
return self._panel_map[panel_id]
except KeyError as exc:
msg = f"Unknown panel id: {panel_id}"
raise KeyError(msg) from exc
@staticmethod
def _validate_eta(panel: PanelSpec, eta: FloatScalar) -> None:
"""
Ensure ``eta`` lies within the panel spanwise interval.
Parameters
----------
panel : PanelSpec
Panel whose spanwise interval should contain ``eta``.
eta : FloatScalar
Spanwise parameter to validate.
Returns
-------
None
This method validates the inputs in place.
Raises
------
ValueError
If ``eta`` lies outside ``panel.eta_range``.
"""
eta0, eta1 = panel.eta_range
if eta < eta0 or eta > eta1:
msg = (
f"eta={eta} lies outside panel {panel.id!r} range "
f"[{eta0}, {eta1}]."
)
raise ValueError(msg)