Validation matrix

VANE’s algorithms are validated against analytical solutions, exact mathematical invariants, and reference models. The full, continuously-updated matrix is maintained alongside the source and is reproduced below.

Validation

VANE is validated against three classes of reference: closed-form analytical solutions, exact mathematical invariants of the multi-blade-coordinate (MBC) transform, and end-to-end consistency on standard OpenFAST linearization models. Every check below is encoded as an automated test so the guarantees are re-verified on every change.

1. Analytical solutions

1.1 Second-order modal properties

For a single second-order degree of freedom with undamped natural frequency w0 and damping ratio zeta, the state matrix

A = [[0, 1], [-w0**2, -2*zeta*w0]]

has eigenvalue lambda = -zeta*w0 + i*w0*sqrt(1 - zeta**2). The eigensolver reproduces the closed-form modal quantities exactly:

Quantity

Closed form

Natural frequency

w_n = abs(lambda) = w0

Damping ratio

zeta = -Re(lambda) / abs(lambda)

Damped frequency

w_d = Im(lambda) = w0*sqrt(1-z^2)

Test: test_eigensolver.py::test_single_oscillator_matches_analytics.

1.2 Coleman regressive/progressive split

For an isotropic three-bladed rotor whose blades are identical oscillators at w0, the MBC transform of the spinning system yields three multiblade modes: a collective mode at w0, and a regressive/progressive pair split by the rotor speed Omega, i.e. frequencies w0 - Omega, w0, w0 + Omega. VANE reproduces this split (e.g. at Omega = 1.5 rad/s, w0 = 2.0: damped frequencies of 0.5 / 2.0 / 3.5).

Test: test_mbc_transform.py::TestMbc3Mathematics::test_regressive_progressive_frequency_split.

1.4 Eigenvalue conditioning (numerical sensitivity)

Each mode’s eigenvalue condition number kappa = 1 / |y^H x| (unit-norm left eigenvector y, right eigenvector x) obeys kappa >= 1 by Cauchy-Schwarz; it is exactly 1 for a normal matrix (a skew-symmetric rotation block gives kappa = 1) and large for a strongly non-normal matrix (e.g. [[-0.1, 10], [-0.1, -0.1]] gives kappa ~ 5). Repeated eigenvalues are flagged degenerate, and unstable (positive-real-part) and over-damped (stable real) eigenvalues are counted.

Tests: test_eigensolver.py::TestRobustEigenCore.

1.5 Euler-Bernoulli cantilever beam

A uniform clamped-free beam has closed-form natural frequencies f_n = (beta_n L)^2 / (2 pi) * sqrt(EI / (rho A L^4)) with beta_n L = 1.875104, 4.694091, 7.854757, ... (roots of cos x cosh x + 1 = 0). A consistent-mass finite-element model of the beam is cast into the second-order state matrix [[0, I], [-M^-1 K, 0]] and solved by the eigen-core; the lowest three frequencies match the analytical values to within 1% (consistent-mass FEM converges from above), and the undamped beam yields zero damping ratios. This is the canonical structural-dynamics benchmark, on a physical continuous system rather than a prescribed synthetic one.

Tests: test_validation/test_cantilever_beam.py.

2. Exact mathematical invariants of the MBC transform

2.1 Azimuth invariance (spinning rotor)

The defining property of the Coleman/MBC transform is that, for an isotropic rotor, the transformed (non-rotating) state matrix is identical at every azimuth. VANE satisfies this to machine precision at every rotor speed:

Rotor speed (rad/s)

Max azimuth-to-azimuth spread of transformed A

0.0

~1e-15

0.5

~1e-15

1.5

~1e-15

This is the decisive correctness check for the spinning regime: a transform that dropped the velocity-coupling inverse block would not be azimuth-invariant at Omega != 0.

Test: test_mbc_transform.py::TestMbc3Mathematics::test_a_is_azimuth_invariant.

2.2 Eigenvalue preservation at zero rotor speed

At zero rotor speed the transform reduces to a similarity transform, so the eigenvalues of the state matrix are preserved.

Test: test_mbc_transform.py::TestMbc3Synthetic::test_zero_speed_preserves_eigenvalues.

2.3 Analytic multiblade inverse

The 3x3 multiblade matrix inverse is computed in closed form and matches a numerical inverse to ~1e-12.

Test: test_mbc_transform.py::TestMbc3Mathematics::test_tt_inverse_matches_numerical_inverse.

2.4 Input/output transform invariance

For an isotropic rotor with rotating blade inputs, the transformed input matrix B is also azimuth-invariant, confirming the B/C/D transform path.

Test: test_mbc_transform.py::TestMbc3Mathematics::test_b_transform_is_azimuth_invariant.

3. Mode identification

3.1 DOF classification

Each state description is mapped to a physical DOF category from its embedded DOF_* index token, including disambiguation of prefix-overlapping tokens (e.g. DOF_R platform roll versus DOF_RFrl rotor furl) and extraction of the blade index from DOF_BF(i,k) / DOF_BE(i,k) / DOF_BP(i).

Test: test_dof_map.py::TestClassifyDof.

3.2 Mode labeling and confidence

A mode is labeled by the DOF category carrying the most participation, and the reported confidence equals that category’s share of participation (so a clearly tower-dominated mode has high confidence and a 50/50 mixture has confidence near 0.5).

Test: test_labeler.py.

3.3 Global tracking through frequency crossings, with confidence

Mode lines are extracted as the globally optimal continuity path through the whole sweep (deterministic dynamic programming over the MAC / frequency-continuity affinity), not a greedy adjacent match. When two lines cross in frequency, tracking by frequency alone would swap them; the MAC-based affinity follows each mode by its shape, so a tower-shaped mode rising from 1.0 to 1.5 Hz and a drivetrain-shaped mode falling from 2.0 to 1.4 Hz stay distinct. Each track reports a confidence (its weakest-link MAC, ~1 for clean modes) and flags ambiguity where a mode has two near-equally correlated continuations.

Tests: test_identifier.py::TestIdentifyModes::test_frequency_crossing_tracked_by_shape, TestGlobalTrackQuality.

3.4 Collective / regressive / progressive blade modes

Blade modes are sub-classified deterministically from the multi-blade-coordinate structure (each transformed triplet is exactly [collective, cosine, sine]) — not from text templates. Collective vs cyclic is decided by which coordinates carry the mode; for a cyclic mode the whirl direction is the sign of Im(q_s * conj(q_c)), which is invariant to the mode’s overall phase. For an isotropic three-bladed rotor this recovers the collective mode at w0 and the regressive/progressive pair at w0 -/+ Omega. A non-blade-dominated mode receives no multi-blade type.

Test: test_labeler.py::TestBladeMultiblade.

3.5 Azimuth-spread uncertainty

Each averaged mode’s azimuth uncertainty is its first-order eigenvalue perturbation y^H (A(psi) - avg_a) x / (y^H x) evaluated across the per-azimuth matrices (the frozen-time spectra are similar, so this perturbation of the averaged model — not the per-azimuth spectrum — is the meaningful quantity). For an isotropic rotor every A(psi) equals avg_a, so the spread is ~0 (machine precision); a mildly anisotropic rotor (blade frequencies 1.8/2.0/2.2) produces a small but non-zero spread. The unified confidence multiplies this azimuth-stability factor with the eigenvalue conditioning, degeneracy, and tracking confidence, and a low confidence inflates the Kalman initial covariance.

Tests: test_uncertainty.py.

3.6 End-to-end Coleman/Campbell split

The whole pipeline — MBC transform, eigen-analysis, cross-operating-point tracking, and Campbell assembly — is run on an isotropic three-bladed rotor of identical blade-flap oscillators (natural frequency w0) swept over rotor speed Omega, and checked against the closed-form Coleman prediction: a collective line at w0 and a regressive/progressive pair at w0 -/+ Omega (rad/s). At every rotor speed the three tracked frequencies match {w0 - Omega, w0, w0 + Omega} to better than 2%, the regressive line falls while the progressive line rises and the collective line stays flat, and the modes are labeled collective/regressive/progressive. This validates the integrated pipeline against theory, not just internal consistency.

Tests: test_validation/test_coleman_campbell.py.

4. Learned mode classification

4.1 Scale-independent features

Each mode reduces to its natural frequency, damping ratio, and the fraction of participation in each physical DOF category. The fractions sum to one and are independent of mode-shape scale and DOF count, so one classifier applies across turbine models.

Test: test_features.py.

4.2 Gaussian-process classification with uncertainty

The classifier recovers the correct category on separable data and reports a calibrated per-mode confidence in [0, 1] (the maximum class probability), which quantifies classification uncertainty. Because no labeled corpus exists yet, it is bootstrapped on synthetic samples and retrained unchanged when measured data arrives.

Tests: test_gp_classifier.py, test_trainer.py.

4.3 Physics/learned label fusion

The ensemble fuses the rule-based and learned labels: agreement reinforces confidence (probabilistic OR), while disagreement yields the more confident category at a discounted confidence, so genuine conflicts surface as low-confidence labels and categories outside the classifier’s training set are still caught by the rule-based view.

Test: test_ensemble.py.

5. Campbell diagram and resonance detection

5.1 Per-rev excitation lines

The nP excitation-line frequency is n * rpm / 60 Hz, so the 1P line at 60 rpm is 1 Hz and the 3P line is 3 Hz.

Test: test_excitation.py::TestExcitationFrequencies.

5.2 Resonance crossing by interpolation

A mode at a constant 1.5 Hz crosses the 3P line exactly where 3 * rpm / 60 = 1.5, i.e. at 30 rpm; the detector recovers that crossing (30 rpm, 1.5 Hz) by linear interpolation between operating points.

Test: test_excitation.py::TestFindResonances::test_detects_interpolated_crossing.

6. State-space export and Kalman initialization

6.1 Zero-order-hold discretization

A continuous system discretized at sample time dt has discrete eigenvalues equal to the exponential of the continuous eigenvalues, exp(lambda * dt); stability is preserved.

Test: test_state_space.py::TestDiscretization::test_discrete_eigenvalues_are_exponentials.

6.2 Real modal realization and export fidelity

The real, block-diagonal modal realization built from a set of modes has exactly those modes and their complex conjugates as its eigenvalues, reproducing the original spectrum. End to end, prescribed natural frequencies and damping ratios realized into a state-space model re-extract to the same frequencies (to 1e-9) and damping (to 1e-6) when the exported state matrix is solved again, and a solution round-trips through the export unchanged — so the exported digital-twin model is faithful to the identified modes.

Tests: test_modal_to_ss.py::TestModalStateSpace::test_block_eigenvalues_match_modes, TestModalFromSolution::test_from_solution_round_trips_eigenvalues, test_validation/test_state_space_export.py.

6.3 Uncertainty-driven covariance

Classification uncertainty propagates into the initial state covariance: a mode identified with low confidence receives a larger initial variance than a confidently identified one, so the filter trusts well-identified modes more.

Test: test_covariance_init.py::TestCovariancesFromConfidence::test_low_confidence_inflates_initial_variance.

7. Visualization

Plots have no analytical reference, so they are verified structurally: the Campbell plot contains one line per tracked mode plus the requested nP lines and a marker at each detected resonance crossing; the damping plot has one line per track; the mode-shape plots return the correct figure type; and all mode-shape plotters reject an out-of-range mode index, a non-positive top_n, or a solution without DOF descriptions.

Tests: test_plots.py.

8. Pipeline orchestration

The high-level ModalPipeline is verified to run the whole flow on synthetic linearization files — per operating point MBC3 transform, eigen-solution, cross-operating-point tracking, Campbell assembly, and resonance detection — and to expose the operating-parameter values, tracks, and a state-space export. It also rejects empty input and unknown operating parameters, and selects rotor speed or wind speed as the operating axis.

Test: test_pipeline.py.

9. End-to-end consistency on reference models

The full pipeline — parse linearization files, apply MBC3, average over azimuth, and solve for modes — is exercised on standard OpenFAST linearization models, including a land-based 5 MW turbine sampled at three azimuths over a revolution and an offshore semi-submersible floating case. These integration checks confirm that parsed dimensions match the model, the transform runs on full-size systems, and the resulting modes are real, finite, and physically plausible (non-negative natural frequencies, finite damping). Because these models are large binary/text reference outputs, the tests run only when the reference data is available locally and are skipped otherwise; they are never committed to the repository.

Tests: test_lin_reader.py::TestReadLinFileRTest, test_mbc_transform.py::TestMbc3RTest, test_eigensolver.py::TestEigensolverRTest.

10. Ground-truth accuracy and robustness benchmark

vane.benchmark assembles a classically damped system [[0, I], [-K, -C]] from prescribed natural frequencies, damping ratios, and mode shapes (K = Phi diag(wn^2) Phi^-1, C = Phi diag(2 zeta wn) Phi^-1), so the exact modal answer is known. The extraction recovers it to machine precision: relative frequency and absolute damping errors below 1e-9 and mode-shape MAC above 1 - 1e-9, both for diagonal and for coupled (non-identity Phi) mode shapes. robustness_curve then adds increasing Gaussian noise to the stiffness/damping block and re-scores against the noise-free truth: the error is zero without noise, grows monotonically with the noise level, and stays bounded for moderate noise — the quantitative evidence of accuracy and graceful degradation. The harness is fully deterministic (seeded perturbations).

Tests: test_benchmark/test_benchmark.py.

11. Tabular export

vane.export flattens a modal solution and a Campbell diagram into pandas tables and writes them to CSV, JSON, or Excel by file suffix. The tables preserve one row per mode (or per track and operating point), the operating-parameter column is named after the diagram’s parameter, and a CSV/JSON write round-trips back to the same frequencies and row count. Length mismatches between the solution and any optional label/confidence/spread inputs, and unsupported file suffixes, are rejected.

Tests: test_export/test_export.py.

12. Reference-model baseline

A canonical land-turbine model — a tower fore-aft mode (0.32 Hz), a drivetrain mode (1.70 Hz), and an isotropic three-bladed flap triplet (0.69 Hz) — is generated across a rotor-speed sweep and run end to end through the provenance-tracked study API. The recovered Campbell diagram reproduces the documented baselines to within 2%: the flat tower and drivetrain lines, and the blade collective line at 0.69 Hz with the regressive/progressive pair split by the rotor speed. The blade triplet is labelled collective/regressive/progressive, and the study records full provenance (hashed source files, per-operating-point coverage, thresholds). This is a realistic, reproducible end-to-end check; the model is generated programmatically, so no proprietary turbine data is committed.

Tests: test_validation/test_reference_models.py.

Reproducing

pytest                      # full suite, including the analytical and invariant checks
pytest -k Mathematics       # MBC transform invariants only
pytest -k RTest             # reference-model integration (requires local reference data)

References

  • Bir, G. (2008). Multi-blade coordinate transformation and its application to wind turbine analysis. AIAA/ASME Wind Energy Symposium.

  • Allemang, R. J. (2003). The modal assurance criterion — twenty years of use and abuse. Sound and Vibration, 37(8), 14-23.

  • Vacher, P., Jacquier, B., & Bucharles, A. (2010). Extensions of the MAC criterion to complex modes. Proceedings of ISMA 2010.