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 |
|
Damping ratio |
|
Damped frequency |
|
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.3 Modal Assurance Criterion identities¶
A mode set correlated with itself yields a unit-diagonal MAC matrix.
Orthogonal mode shapes yield a MAC of zero.
MAC is invariant to per-mode complex scaling.
The pole-weighted MACXP self-correlation is exactly 1.
Tests: test_mac.py::TestComputeMac, TestComputeMacxp.
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.