Changelog

The changelog follows Keep a Changelog and the project adheres to Semantic Versioning. It is maintained at the repository root and reproduced below.

Changelog

All notable changes to VANE will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

[0.1.0] - 2026-06-09

First public release: automated OpenFAST linearization post-processing with robust, uncertainty-aware mode identification, provenance-tracked study orchestration, and a digital-twin-ready state-space export.

Changed

  • Packaging hygiene: the source distribution now carries the full evidence base — tests/, docs/, examples/, and the license/changelog/community files — via a new MANIFEST.in, so the validation claims can be reproduced from the released artifact. Project metadata uses the SPDX license = "Apache-2.0" with license-files, dropping the deprecated license table and classifier (build is now deprecation-warning-free).

  • Resonance detection is now damping-aware: each ResonanceCrossing reports the resonating mode’s interpolated damping_ratio and a ResonanceSeverity (HIGH/MEDIUM/LOW, since a lightly damped crossing is more dangerous than a well-damped one), and find_resonances accepts an operating_range to restrict crossings to a rotor-speed operating window.

  • The identify_modes docstring now states the tracker’s optimality caveat precisely: each path is the globally optimal single continuity path, but the extraction is sequential, not a simultaneous globally optimal multi-track assignment (ambiguous hotspots are flagged).

  • The learned AI layer is now explicitly experimental: constructing a ModeClassifier (directly, or through train_default_classifier, which builds one) emits a vane.ai.ExperimentalWarning, because the default model is trained on synthetic priors rather than measured data. A model card documents its training data, intended use, and out-of-scope use; the rule-based label and deterministic blade taxonomy remain the primary, validated identification path.

  • Documentation now matches the implementation: the README, docs/index.md, and the vane.modal.mac docstring describe the deterministic global mode tracker (identify_modes, optimal continuity paths) instead of “Hungarian-algorithm mode tracking” (Hungarian remains a pairwise match_modes building block), and drop the deferred graph-/sequence-based classifiers. CI lint now installs from the package metadata (pip install -e ".[dev]") instead of a hand-picked package list.

  • mbc3_transform now validates that the azimuth files form one coherent operating point: duplicate azimuths (which bias the azimuth average) and a rotor speed that drifts across the sweep (which makes the averaged non-rotating model physically meaningless) are rejected with a clear ValueError.

  • Input validation is now enforced at the lower-level APIs, not only in ModalPipeline: identify_modes rejects a frequency_weight, mac_threshold, or ambiguity_margin outside [0, 1] (NaN included); excitation_frequencies and find_resonances reject non-positive, fractional, non-finite, or duplicate harmonics (an empty set remains a valid no-op); and ModalPipeline.run rejects non-finite or duplicate operating-parameter values, which would otherwise make the Campbell diagram and resonance interpolation ambiguous.

  • read_lin_file now validates each parsed file at its trust boundary: a non-finite operating-point scalar (rotor speed, azimuth, wind speed, simulation time) and a state-space matrix whose shape disagrees with the channel-table dimensions (A=(n_x,n_x), B=(n_x,n_u), C=(n_y,n_x), D=(n_y,n_u)) now fail loudly with a clear LinFileFormatError instead of surfacing as an opaque error deep in the analysis. (Overflow markers in matrix data remain tolerated as nan.)

Added

  • Study orchestration with reproducibility provenance (vane.study): discover_operating_points groups a directory’s .lin files into operating points, run_study runs the pipeline and records a Provenance manifest (source files with SHA-256 content hashes, per-operating-point azimuth coverage, tuning thresholds, harmonics, and the library version), and StudyResult.write_bundle serializes a reproducibility package (provenance.json + campbell.csv). The OpenFAST examples use the shared discovery helper instead of duplicating ad-hoc grouping logic.

  • Data-free quickstart example (examples/synthetic_quickstart.py): builds a system with prescribed modes, extracts and labels them, attaches a unified confidence, writes a CSV, and reports recovery accuracy and noise robustness — runnable without any OpenFAST data.

  • Tabular export layer (vane.export): modes_table flattens a modal solution (optionally with physical labels, unified confidence, and azimuth spread) into a per-mode pandas.DataFrame; campbell_table flattens a Campbell diagram into a long-format table of tracked modes versus the operating parameter; and write_table writes either to CSV, JSON, or Excel inferred from the file suffix (Excel via the optional openpyxl). Lets downstream tools consume the results directly.

  • Ground-truth accuracy and robustness benchmark (vane.benchmark): synthetic_system builds a classically damped system with prescribed natural frequencies, damping ratios, and mode shapes (exact known answer); score_recovery scores an extracted solution against it (frequency/damping error and MAC, matched by Hungarian assignment); and robustness_curve measures error growth under increasing additive noise. The extraction recovers clean systems to machine precision and degrades gracefully — quantitative evidence neither reference tool provides.

  • Unified mode uncertainty quantification (vane.modal.uncertainty): a novel azimuth-spread estimate (azimuth_spread, requiring mbc3_transform(retain_per_azimuth=True)) measures each averaged mode’s first-order eigenvalue perturbation across the per-azimuth matrices — ~0 for an isotropic rotor, growing with anisotropy — and unified_mode_confidence fuses it with the eigenvalue conditioning, degeneracy, and tracking confidence into one calibrated per-mode confidence that feeds the Kalman covariance export. No uncertainty quantification exists in the reference tools.

  • Global deterministic mode tracker (vane.modal.identify_modes): mode lines are now extracted as the globally optimal continuity path through the whole sweep (successive optimal-path dynamic programming over the MAC / frequency-continuity affinity) instead of a greedy adjacent match. Deterministic — no random restarts or preset cluster counts — and each ModeTrack carries a confidence (its weakest-link MAC) and an is_ambiguous flag (raised at crossing/veering hotspots where a mode had two near-equally correlated continuations).

  • Deterministic blade multi-blade taxonomy (vane.modal.labeler): blade modes are sub-classified as collective / regressive / progressive from the MBC multi-blade-coordinate structure (each transformed triplet is [collective, cosine, sine]) and the phase-invariant whirl sign Im(q_s * conj(q_c)) — template-free and general. The MBC transform now annotates each state’s multi-blade coordinate (MBCResult.mbc_coordinates), carried through to ModalSolution.dof_mbc_coordinates and surfaced as ModeLabel.multiblade.

  • Robust eigen-core (vane.modal.eigensolver): via a left/right-eigenvector analysis, each mode now reports its eigenvalue condition number (numerical sensitivity; 1 = perfectly conditioned, large = fragile/non-normal) and a near-degeneracy flag (shape defined only up to a subspace rotation), and the solution reports the unstable and over-damped eigenvalue counts. Numbers the rivals do not provide.

  • High-level pipeline (vane.ModalPipeline):

    • ModalPipeline.run executes the full workflow over a set of operating points (MBC3 → modes → cross-operating-point tracking → Campbell diagram → resonance detection), against rotor speed or wind speed.

    • PipelineResult exposes every intermediate product and a state_space export; ModalPipeline and PipelineResult are re-exported from the package root.

    • examples/ show end-to-end use for the NREL 5 MW land turbine and the OC3 spar and OC4 semi-submersible floating cases (including a UQ-informed Kalman model export).

  • Visualization layer (vane.viz):

    • plot_campbell renders an interactive Campbell diagram with nP excitation lines and resonance markers; plot_damping plots damping versus the operating parameter.

    • plot_mode_shape (Matplotlib) shows the signed participation of a mode’s most active DOFs; plot_mode_3d (Plotly) renders the mode’s complex components as 3-D phasors.

  • System-identification layer (vane.sysid):

    • StateSpace / state_space_from_mbc export a validated continuous-time LTI system from the averaged MBC matrices, with zero-order-hold discretization and a stability check.

    • modal_state_space realizes a real, block-diagonal modal state-space model from the identified modes.

    • covariances_from_confidence / initialize_covariances build the Kalman Q, R, P0 matrices, propagating classification uncertainty into P0.

    • build_kalman_model assembles a dimension-checked, optionally discretized Kalman-ready model.

  • Campbell layer (vane.campbell):

    • build_campbell / campbell_from_solutions assemble a CampbellDiagram of natural frequency and damping versus an operating parameter (rotor speed or wind speed) from the identified mode tracks.

    • excitation_frequencies and find_resonances compute the nP excitation lines and flag mode/nP resonance crossings by interpolation.

  • AI layer (vane.ai), torch-free core:

    • solution_features / mode_feature_vector reduce each mode to a scale-independent feature vector (frequency, damping, per-category participation fractions).

    • ModeClassifier is a Gaussian-process mode classifier (scikit-learn) that predicts a DOF category with a calibrated confidence for uncertainty quantification, with save/load.

    • train_default_classifier / synthetic_training_set bootstrap the classifier on synthetic labeled modes until measured data is available.

    • ensemble_solution / fuse_label fuse the rule-based and learned labels, reinforcing agreement and discounting conflicts.

  • Mode identification:

    • vane.config.classify_dof maps an OpenFAST state description onto a physical DOF category, module, and blade index using the embedded DOF_* index token.

    • vane.modal.label_mode / label_modes / label_solution assign a physical label to each mode from its dominant participation, with a confidence that flags ambiguous modes.

    • vane.modal.identify_modes links modes across operating points into labeled tracks (Campbell-diagram lines) via a MAC- and frequency-continuity-based Hungarian assignment, tracking through frequency crossings by mode shape.

  • Modal layer (vane.modal):

    • compute_modes / modes_from_mbc perform the eigenvalue analysis of the azimuth-averaged state matrix, returning natural frequencies, damping ratios, damped frequencies, and phase-normalized complex mode shapes, with conjugate-pair collapse and rigid-body-mode counting.

    • compute_mac, compute_macxp (pole-weighted), and match_modes (Hungarian assignment with an optional frequency penalty) provide mode correlation and cross-operating-point matching.

    • compute_participation quantifies per-degree-of-freedom participation from the mode shapes, with dominant-component normalization and a phase-based sign.

  • I/O layer (vane.io):

    • read_lin_file parses modern OpenFAST (ModGlue) .lin linearization files into a typed LinFile (state/input/output operating-point tables plus the A, B, C, D matrices), handling orientation rows, overflow markers, and absent matrix blocks; legacy-format files are rejected with a clear error.

    • read_fst_file and read_elastodyn_geometry extract the linearization configuration, active-module switches, referenced sub-files, and rotor/tower geometry from a primary .fst deck and its ElastoDyn input.

    • mbc3_transform applies the multi-blade-coordinate (MBC3) transform to the A, B, C, and D matrices and azimuth-averages them into the non-rotating frame, with automatic blade-triplet detection.

  • Test fixtures for synthetic modern .lin generation and optional integration against a local OpenFAST r-test clone.

  • Initial repository scaffolding: src-layout package skeleton, subpackage stubs for io, modal, ai, campbell, viz, sysid, config, and utils.

  • Build, lint, type-check, and test configuration: pyproject.toml, .ruff.toml, mypy --strict settings, and pytest defaults with coverage gate (≥ 85%).

  • GitHub Actions CI workflow covering lint, type check, multi-version tests (Python 3.10–3.12), and an automated PR review summary comment.

  • Pre-commit hooks for ruff, formatting, basic file hygiene, and mypy.

  • Pull request template and contributor checklist aligned with the project workflow.

  • Tests skeleton with conftest.py and a smoke test for package import and version exposure.