Source code for vane.config.dof_map

"""Classification of OpenFAST degree-of-freedom descriptions.

OpenFAST embeds an internal degree-of-freedom (DOF) token in each ElastoDyn and
BeamDyn state description (e.g. ``DOF_TFA1`` for the first tower fore-aft mode or
``DOF_BF(1,1)`` for the first flapwise mode of blade 1). These tokens are stable
across versions and are used here to map a state description onto a physical DOF
category, the originating module, the blade index for rotating DOFs, and whether
the state is a velocity. This classification underpins physics-aware mode
labeling.
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from enum import Enum

__all__ = ["DofCategory", "DofInfo", "classify_dof"]


[docs] class DofCategory(Enum): """Physical category of a degree of freedom (string-valued, 3.10 compatible).""" PLATFORM_SURGE = "platform_surge" PLATFORM_SWAY = "platform_sway" PLATFORM_HEAVE = "platform_heave" PLATFORM_ROLL = "platform_roll" PLATFORM_PITCH = "platform_pitch" PLATFORM_YAW = "platform_yaw" TOWER_FORE_AFT_1 = "tower_fore_aft_1" TOWER_SIDE_SIDE_1 = "tower_side_side_1" TOWER_FORE_AFT_2 = "tower_fore_aft_2" TOWER_SIDE_SIDE_2 = "tower_side_side_2" NACELLE_YAW = "nacelle_yaw" GENERATOR_AZIMUTH = "generator_azimuth" DRIVETRAIN_TORSION = "drivetrain_torsion" ROTOR_FURL = "rotor_furl" TAIL_FURL = "tail_furl" TEETER = "teeter" BLADE_FLAP_1 = "blade_flap_1" BLADE_FLAP_2 = "blade_flap_2" BLADE_EDGE_1 = "blade_edge_1" BLADE_PITCH = "blade_pitch" UNKNOWN = "unknown"
[docs] @dataclass(frozen=True) class DofInfo: """Classification of a single state description. Parameters ---------- category : DofCategory The physical DOF category (``UNKNOWN`` if not recognized). module : str The originating module prefix (e.g. ``"ED"`` or ``"BD_1"``), or an empty string if absent. blade : int or None The 1-based blade index for rotating blade DOFs, otherwise ``None``. is_velocity : bool Whether the state is the time derivative (velocity) of a DOF. """ category: DofCategory module: str blade: int | None is_velocity: bool
_FIXED_TOKENS: dict[str, DofCategory] = { "DOF_Sg": DofCategory.PLATFORM_SURGE, "DOF_Sw": DofCategory.PLATFORM_SWAY, "DOF_Hv": DofCategory.PLATFORM_HEAVE, "DOF_R": DofCategory.PLATFORM_ROLL, "DOF_P": DofCategory.PLATFORM_PITCH, "DOF_Y": DofCategory.PLATFORM_YAW, "DOF_TFA1": DofCategory.TOWER_FORE_AFT_1, "DOF_TSS1": DofCategory.TOWER_SIDE_SIDE_1, "DOF_TFA2": DofCategory.TOWER_FORE_AFT_2, "DOF_TSS2": DofCategory.TOWER_SIDE_SIDE_2, "DOF_Yaw": DofCategory.NACELLE_YAW, "DOF_GeAz": DofCategory.GENERATOR_AZIMUTH, "DOF_DrTr": DofCategory.DRIVETRAIN_TORSION, "DOF_RFrl": DofCategory.ROTOR_FURL, "DOF_TFrl": DofCategory.TAIL_FURL, "DOF_Teet": DofCategory.TEETER, } # Blade DOF tokens: DOF_BF(blade, mode), DOF_BE(blade, mode), DOF_BP(blade). _BLADE_FLAP_RE = re.compile(r"DOF_BF\((\d+),(\d+)\)") _BLADE_EDGE_RE = re.compile(r"DOF_BE\((\d+),(\d+)\)") _BLADE_PITCH_RE = re.compile(r"DOF_BP\((\d+)\)") # A fixed token is ``DOF_`` followed by word characters, stopping before any '('. _FIXED_TOKEN_RE = re.compile(r"DOF_(\w+)") _MODULE_RE = re.compile(r"^([A-Za-z]+(?:_\d+)?)\s") _DERIV_MARKER = "First time derivative of" _FLAP_MODE = {1: DofCategory.BLADE_FLAP_1, 2: DofCategory.BLADE_FLAP_2}
[docs] def classify_dof(description: str) -> DofInfo: """Classify a state description into a :class:`DofInfo`. Parameters ---------- description : str A state description from a linearization file (e.g. ``"ED 1st tower fore-aft ... (internal DOF index = DOF_TFA1), m"``). Returns ------- DofInfo The classification; ``category`` is :attr:`DofCategory.UNKNOWN` when no known DOF token is present. """ module_match = _MODULE_RE.match(description) module = module_match.group(1) if module_match is not None else "" is_velocity = _DERIV_MARKER in description category, blade = _classify_token(description) return DofInfo( category=category, module=module, blade=blade, is_velocity=is_velocity )
def _classify_token(description: str) -> tuple[DofCategory, int | None]: """Return the DOF category and blade index encoded in ``description``.""" flap = _BLADE_FLAP_RE.search(description) if flap is not None: blade, mode = int(flap.group(1)), int(flap.group(2)) return _FLAP_MODE.get(mode, DofCategory.UNKNOWN), blade edge = _BLADE_EDGE_RE.search(description) if edge is not None: return DofCategory.BLADE_EDGE_1, int(edge.group(1)) pitch = _BLADE_PITCH_RE.search(description) if pitch is not None: return DofCategory.BLADE_PITCH, int(pitch.group(1)) fixed = _FIXED_TOKEN_RE.search(description) if fixed is not None: category = _FIXED_TOKENS.get(f"DOF_{fixed.group(1)}") if category is not None: return category, None return DofCategory.UNKNOWN, None