API contract
This page states the guarantees VANE’s public entry points make, independent of their implementation. They hold for the documented public API; private helpers may change without notice.
General guarantees
- Determinism.
Given the same inputs, every analysis function returns identical results. There are no random restarts, hidden global state, or wall-clock dependence in the numerical results (only the provenance timestamp records the time of the run). Stochastic utilities are seeded explicitly: the benchmark noise sweep (
robustness_curve) defaults toseed=0.- Validation at the boundary.
Inputs are validated where they enter the library and rejected with a specific, typed exception and a clear message, rather than surfacing as an opaque lower-level error. Numerical results are never silently produced from malformed input.
- Typed and documented.
Every public function has complete type annotations and a NumPy-style docstring stating its parameters, returns, and the conditions under which it raises.
Entry-point guarantees
read_lin_fileReturns a
LinFileonly for a well-formed modern.linfile. RaisesLinFileFormatErrorfor an empty, truncated, legacy, or structurally invalid file; for a non-finite operating-point scalar or operating-point table value; for a missingAmatrix or a state count that disagrees with the header; or for anA/B/C/Dmatrix whose shape disagrees with the channel-table dimensions. Overflow markers in matrix data becomenan(with a logged warning). A missing file raises the usualFileNotFoundError.find_nonfinite_lin_valuescans a file (header, operating-point tables and matrices) and returns the first NaN or infinite value with the channel it belongs to, orNone.mbc3_transformRequires one coherent operating point: a non-empty set of azimuth files with consistent channel layout, distinct azimuths (including the wrap-around), and a rotor and wind speed that do not drift. The default relative limit is 1% for each; an explicitly looser, finite, nonnegative tolerance is required otherwise. Violations, a file without an
Amatrix, and a rotating channel of a blade numbered above three raiseValueError. Files without blade triplets are averaged without the transform. The result is the azimuth-averaged non-rotating model; withretain_per_azimuth=Trueit also keeps aligned per-azimuthA/B/C/Dmatrices and input/output operating-point vectors. This is an explicit memory-cost opt-in for Floquet analysis and azimuth-periodic state/input/output models.select_statesselect_states(model, *, keep, drop, keep_categories, drop_categories)accepts aLinFileor anMBCResultand returns a new object of the same type with a subset of its states. States are kept or removed by case-insensitive regular expressions matched anywhere in the state description, or byDofCategory; removal is applied afterkeep. The displacement and velocity of a second-order DOF are always selected together.Akeeps the selected rows and columns,Bthe selected rows andCthe selected columns;D, the inputs and the outputs are unchanged. For anMBCResultthe per-azimuth matrices,ndof2,ndof1and the MBC coordinate labels follow the selection; for aLinFilethe module Jacobians are not carried over. RaisesValueErrorwhen no selector is given, a pattern or category matches no state, a pattern is not a valid regular expression, a category is unknown, or every state would be removed, andTypeErrorfor another model type or a non-string pattern.read_wamit_diagonal_difference_qtf/newman_slow_drift_loadRead one real, single-heading WAMIT diagonal difference-frequency QTF and evaluate Newman’s mean- and slow-drift approximation on an equally sampled periodic wave record. The period/heading diagonal-selection tolerance must be finite and nonnegative; infinity cannot turn off the diagonal mask. The real-FFT construction doubles only positive-frequency bins with a distinct negative-frequency partner; the unique DC and even-record Nyquist bins retain their physical amplitudes. Incident-wave cutoffs must be valid ordered numbers; only the high cutoff may be non-finite, and then only as positive infinity. Direct QTF construction requires a finite heading and stores immutable copies of the frequency and load arrays. Period-to-frequency conversion, HydroDyn dimensionalization, FFT frequency construction and the reconstructed load must all remain finite; extreme finite inputs raise
ValueErrorrather than returning infinities orNaN. Directional QTF interpolation and cropped-record edge correction are outside this contract.compute_modes/modes_from_mbcRaise
ValueErrorfor a non-finite, non-square or mis-partitioned state matrix;modes_from_mbcalso raises it when the MBC result has no averagedA. Return conjugate-collapsed modes (positive-imaginary representatives) with frequencies, damping, phase-normalized shapes, condition numbers, degeneracy flags, and unstable/over-damped counts. Real eigenvalues and numerically split rigid-body pairs (belowrigid_body_frequency_hz) are not returned as modes.estimate_averaging_errorTakes the delivered azimuths (radians), the fixed-frame state matrices of shape
(n_azimuth, n, n)(for exampleMBCResult.per_azimuth_afrommbc3_transform(..., retain_per_azimuth=True)) and the rotor speed in rad/s, and returns anAveragingErrorEstimatewith oneBranchErrorEstimateper assessed branch: its sampling, quadrature and periodicity terms, its eigenvalueconditionnumber andwell_conditionedflag (max_condition, default1e3), andwithin_tolerance(default tolerances 0.1 % on frequency and 1 % on damping).AveragingErrorEstimate.within_toleranceholds when every branch is within tolerance and the azimuth assessment (azimuth_assessment, available when the rotor-azimuth states are given) is not"inconsistent-and-risky". A predicted grid whose first alias order \(\operatorname{lcm}(N, B)\) lies above the orders the delivered samples resolve givessampling_resolved=Falseand is not accepted. Inconsistent inputs or a non-positive tolerance raiseValueError. The estimate covers the averaging error of the supplied linearization, not the accuracy of the linearization itself (see Azimuth averaging).identify_modesRejects a
frequency_weight,mac_threshold, orambiguity_marginoutside[0, 1](NaN included) and inconsistent mode-shape layouts. Returns deterministic tracks, each with a confidence and an ambiguity flag. Track labels use the same participation vote aslabel_solution.label_solutionLabels each mode by its scale-invariant left–right participation summed per DOF category (displacement and velocity states of a DOF together). A mode is named after its largest classified category only when classified DOFs carry at least
min_known_participation(default0.5) of the participation; otherwise it is"Unidentified". Confidence is the named category’s share of the participation. RaisesValueErrorfor missing DOF descriptions or a threshold outside[0, 1]or non-finite, andTypeErrorfor a non-numeric threshold. A solution without usable left eigenvectors falls back to the raw-magnitude plurality vote.excitation_frequencies/find_resonancesReject non-positive, fractional, non-finite, or duplicate harmonics; an empty harmonic set is a valid no-op.
find_resonancesrequires a rotor-speed (rpm) Campbell diagram.ModalPipelineValidates its tuning weights and harmonics at construction, and rejects an empty operating-point set, an unknown parameter name, a
parameter_rtolthat is not a finite number in[0, 1), or non-finite/duplicate operating-parameter values atrun. Values closer thanparameter_rtol(default1e-4) times the largest-magnitude value on the axis count as duplicates (a plateau, such as the constant above-rated rotor speed); for a rejected rotor-speed axis the error names the wind-speed axis (parameter_name="wind_speed", CLI--parameter wind) and whether it is distinct for those points, but the axis is never switched automatically. Operating points are sorted by the operating parameter, so input order does not affect the result.PipelineResult.state_space/state_space_from_mbc/modal_state_spaceExport a
StateSpacewhose matrix shapes, name lengths, finiteAand sample time are checked at construction (ValueErrorotherwise).PipelineResult.state_spaceraisesIndexErrorfor an out-of-range operating point. A modal realization (modal_state_space,modal_state_space_from_solution) re-extracts to the same frequencies and damping it encodes (export fidelity).modes_table/campbell_table/write_tableReject length mismatches between a solution and its optional label/confidence/ spread inputs, a Campbell parameter name colliding with a reserved column, and an unsupported file suffix. A
.matexport writes string columns as cell arrays with missing values (None/NaN) as"", so an all-Nonemultibladecolumn (a sweep without blade DOFs) exports; a value MATLAB cannot store, or two columns mapping to the same MATLAB variable name, raisesValueError. An.xlsxexport without the optionalopenpyxlpackage also raisesValueError.discover_operating_pointsRecursively groups a directory’s
<case>.<n>.linfiles (the case name is the path relative to the directory) into namedDiscoveredOperatingPointobjects (one per<case>root, ordered by case); each behaves as a sequence of itsLinFileobjects. OpenFAST per-module (LinOutMod) files<case>.<n>.<Token>.linare always excluded, whatever the alphabetic module token (ED,SEA,SED,ExtLd,BD1, …). RaisesFileNotFoundErrorwhen the directory contains no full-system.linfiles. Files are parsed withread_lin_file, so a malformed file raisesLinFileFormatError. Accepts astrorPath.run_studyValidates
parameter_nameandparameter_rtoland rejects a plateaued sweep axis (the same check asModalPipeline.run) before any MBC work, then runs the pipeline and returns aStudyResultcarrying thePipelineResultand aProvenancerecord. Operating points are sorted deterministically by the operating parameter, and the recorded provenance is in that same sorted order (so it lines up with the Campbell result).Provenance/StudyResult.write_bundleThe provenance records the library version, a timestamp (the UTC time of the run by default; a caller-injected
timestampis recorded as provided), the tuning thresholds, the sweep-axis plateau toleranceparameter_rtol, and pipeline-normalized harmonics, anenvironmentblock (Python, platform, and scientific-dependency versions), and, per operating point, the case name, full azimuth list, averaged rotor/wind speed, operating-parameter value, and that point’s own SHA-256-hashed source files.write_bundle(output_dir, *, report=True)writesprovenance.json(the manifest),campbell.csvand, unlessreport=False, a self-containedreport.htmlaudit report to a directory it creates if needed, accepting astrorPath.
System identification (vane.sysid)
The vane.sysid layer turns identified modes into a reduced, sensor-aware, estimator-
ready model. The guarantees and failure modes for the principal entry points are below;
each raises ValueError on the malformed arguments named in its entry unless the
entry names another exception type.
State-space export.
modal_state_space/StateSpacemodal_state_spaceexports a dimension-checked LTIStateSpacewhose modal realization re-extracts to the frequencies and damping it encodes (export fidelity). TheStateSpacedataclass rejects a non-squareAand anyB/C/Dwhose dimensions disagree with it. (ModalPipeline.state_spaceis documented under the entry-point guarantees above.)static_gainComputes
D - C A^-1 Bfor a continuous system, treating an absentDas zero. A discrete system, missing B/C, or singular A is rejected because this continuous equilibrium gain is then undefined under the contract.
Reduction.
modal_reductionKeeps the modes that drive a chosen output (by residue), optionally within a frequency band.
rank_outputsforms an equal-weight joint objective after normalizing each output’s residue vector, so mixed units do not determine the ranking. Returns aModalReductionwhose reduced system reproduces the retained modes’ eigenvalues — except that with the defaultstabilize=Truea retained pole with a positive real part is reflected into the stable half-plane (the count is reported inModalReduction.n_stabilized); passstabilize=Falseto preserve such unstable poles exactly. Rejects a non-positive mode count, an unknown output, or conflicting single/joint ranking selections; a count larger than the number of available modes is capped, not rejected.periodic_state_space_from_mbcRequires an
MBCResultbuilt withretain_per_azimuth=Trueand aModalReductionof that result’s averaged system. Every per-azimuth member is projected through the reduction’s same left/right state basis, so interpolation never mixes independently phased modal coordinates. PeriodicB,CandDvariations are applied as deviations about the accepted reduced system; therefore a feedthrough correction introduced bymatch_dc_gain=Trueis retained, including when no per-azimuthDfamily is available. The deviations are mapped onto the reduction’s channels by input name and by output name plus MBC coordinate, so a reduction whose inputs or outputs are a reordering of the MBC result’s is remapped; a layout that is not such a permutation (different names or coordinates, ambiguous duplicates, or a different channel count) is rejected withValueError. When either side is unnamed the channels are matched by position after a count check. The returnedPeriodicStateSpacevalidates a finite, strictly increasing azimuth grid of at least two samples in[0, 360)with one member per azimuth, and members with identical matrix dimensions, state names, input names, output names, output MBC-coordinate labels and sample time. It interpolates cyclically across the 360/0-degree seam and can discretize the complete grid. This is a model-construction guarantee; load observability and estimator accuracy must still be validated independently.balanced_truncationReturns a
BalancedReductionwith an a-priori error bound (twice the tail Hankel-singular-value sum). Requires an asymptotically stable system; rejects a non-positive or oversized order, and an order that retains a Hankel singular value at rounding level (the system is not minimal at that order). Numerically indefinite Gramians of a lightly damped minimal system are factored through a clipped symmetric eigendecomposition rather than rejected.BandCare required and a negativetolis rejected.build_rom_familyReduces and mode-tracks a family of operating-point systems into a consistent mode order. Requires continuous-time systems (
modal_reductionrejects a discrete system) that share the same output layout (count, names, and MBC coordinates), input dimension (fromB, orDfor a feedthrough-only model) and names, anddt, and thatoperating_pointsbe a finite, strictly monotonic 1-D schedule. A point yielding fewer than the requested modes is rejected. These guards prevent silently interpolating incompatible models. Withmatch_dc_gain=True, the feedthrough correction is evaluated after the tracked subset is truncated, so each stored operating-point ROM exactly preserves the corresponding full model’s equilibrium gain rather than the gain of an all-candidate intermediate reduction. With the defaultstabilize=Truea retained unstable pole is reflected into the stable half-plane in the stored realization, butRomFamily.damping_ratiosalways reports the physical (pre-reflection) damping — negative for an unstable mode — and the booleanRomFamily.stabilizedmask marks every reflected(point, mode)entry; a warning is logged whenever any mode was reflected.stabilize=Falsestores the unstable poles unchanged. The builder also rejects an empty family, a length mismatch betweensystemsandoperating_points, a member withoutC,n_required_transfer_modesgreater thann_modes, and afrequency_weightoutside[0, 1](default0.5). Every stored modal block is phase-aligned to the previous point and scale-normalized (|b_k| = |c_k|), so members that differ only by a state-space similarity store identical realizations and blend without gain error.save_rom_family/load_rom_familyRevalidate the complete reusable family at both archive boundaries, including a finite, strictly increasing or strictly decreasing operating-point schedule. Nonmonotonic or repeated schedules are rejected before saving and after loading, rather than being passed to interpolation. Mode-tracking MAC values must remain in their physical interval from zero to one, preventing malformed archive confidence from suppressing low-confidence scheduling warnings. The
stabilizedreflection mask round-trips and must be a boolean array of the modal shape; an archive that has nostabilizedentry loads with an all-Falsemask.save_rom_familyraisesTypeErrorfor an object that is not aRomFamilyor has fields of the wrong type;load_rom_familyreports every invalid archive asValueError.save_lpv_family/load_lpv_family(also exported fromvane.export)Archive an
LPVFamilyas a pickle-free.npz: the scheduling points, the dropped-coupling norms and each member’s matrices (explicit indexed keys, absentB/C/Drecorded as such) are arrays; names, the scheduling variable and unit, the auto-added states and each member’s sample time are JSON metadata stamped with the format tagvane-lpv-family, the schema versionLPV_FAMILY_SCHEMA_VERSION(1) and the VANE version. The round trip is exact (values, data types and names). Both functions validate the family: an exactLPVFamilyof exactStateSpacemembers, at least one member, finite floating-point arrays, strictly ascending scheduling points, norms with one value per member or none, and one shared layout (matrix shapes, names, sample time).save_lpv_familyraisesTypeErrorfor a wrong type andValueErrorfor an invalid family;load_lpv_familyopens the file withallow_pickle=Falseand reports every invalid archive asValueError: a missing or wrong format tag, a missing, non-integer or different schema version (naming both versions and the writing VANE version), missing or unexpected entries, and invalid metadata.build_physical_lpv_family(also exported fromvane.export)Reads a directory of
.linfiles and assembles, per operating point, a state-space model whose states/inputs/outputs are the named physical DOFs the caller selects (e.g."ED PtfmPitch","GenTq") — the LPV plant a gain-scheduled controller schedules on. The matrices are azimuth-averaged in the physical frame (no MBC, so per-blade names are preserved — usebuild_rom_familyfor the multi-blade-coordinate representation). Names are matched against the linearization descriptions (units stripped): an exact match first, then a unique substring; a substring that matches only a DOF and its"First time derivative of <DOF>"twin selects the position DOF. A missing or ambiguous name, two names that resolve to the same channel, and a name that resolves to different channels across operating points are rejected; a missing or emptylin_dirraisesFileNotFoundError. The returnedLPVFamilyis sorted ascending by the scheduling variable with the same channel layout at every point; a non-finite or repeated scheduling value is rejected.scheduling_varis"WindSpeed"(default) or"RotorSpeed". The schedule unit follows the raw linearization:WindSpeedis in m/s andRotorSpeedis in rad/s (the unit OpenFAST writes), not rpm — in contrast to the modal/Campbell path, which reports rotor speed in rpm. A controller scheduled onRotorSpeedmust therefore index on rad/s. This is the single entry point a downstream controller needs — it never reads raw.linfiles itself.Selecting a state subset is a truncation, and two safeguards keep it honest. With
pair_derivatives=True(the default) every selected second-order DOF whose"First time derivative of <DOF>"twin (or, for a selected rate, whose position) exists but was not requested gets that twin added next to it — position immediately followed by velocity — so a request such as["...Platform pitch..."]yields the position and velocity pair rather than collapsing toA = [[0]]. The added names (unit-stripped descriptions) are logged at INFO and recorded inLPVFamily.auto_added_states;state_namesis the final, paired list. Then, at every operating point, the relative coupling the selection still discards is measured as \(\lVert A[s,\bar s]\rVert_F / \lVert A[s,:]\rVert_F\) over the selected states \(s\) and, when outputs are selected, \(\lVert C[o,\bar s]\rVert_F / \lVert C[o,:]\rVert_F\) over the selected outputs \(o\). Both are recorded per point (dropped_coupling_a/dropped_coupling_c; their element-wise maximum isdropped_coupling), and if the largest over the family exceedsmax_dropped_coupling(default0.05) aValueErrornames the worst point and the dropped states contributing most.max_dropped_coupling=Nonedisables the check (the norms are still recorded); a NaN, infinite, negative, boolean or non-numeric tolerance is rejected. The norms are taken in the linearization’s own units, so the measure is a screening check rather than a unit-invariant error bound. Selecting every state gives zero dropped coupling and leaves the result unchanged.
Observability and sensor placement.
observability_gramian/finite_horizon_observability_gramianThe infinite- or finite-horizon observability Gramian. With a
measurement_noisecovariance the source is the noise-weighted (Fisher) informationCᵀR⁻¹C;Rmust be finite, symmetric, and positive-definite under the shared, scale-invariant covariance rule (see Covariance validity below — a singular, numerically singular, indefinite, or non-symmetricRis rejected). The infinite-horizon form requires a stable system; the finite-horizon form is defined for marginal/unstable systems too.observability_scores/load_reconstructabilityobservability_scoresreports per-output and per-mode observability.load_reconstructabilityreports what fraction of a target load lies in the sensors’ observable subspace, and the a-priori reconstruction variance. The result (reconstructable or not) andnoise_amplificationare basis-independent; the value of a partialreconstructable_fraction(andobservability) is measured in the Euclidean metric of the state coordinates and changes under a similarity transform, so rank sensor sets bynoise_amplification. The finite-horizon continuous Gramian is split into sub-intervals (no overflow from fast stable modes) and a non-finite Gramian raisesValueError. The sensor set never includes the load output: the default excludes it, and an explicit set containing it is rejected (a load cannot be reconstructed by measuring itself). Any blind component (areconstructable_fractionbelow 1) gives an infinitenoise_amplification, so a smaller-is-better ranking never prefers a partly blind load.recommend_sensors/constrained_sensor_selectionGreedy D-optimal sensor placement, optionally noise-weighted (a full
R; a noisy channel ranks below a calibrated one) and over a finite horizon (admitting marginal/augmented models).constrained_sensor_selectionadditionally honours required/forbidden sensors, per-sensor costs and a budget, a count cap, and per-group caps, rejecting a request whose required sensors alone already violate a constraint.
Load mapping.
build_qoi_mapBuilds a quantity-of-interest (load) map, returning a
QoiMapwhoseQoiSensitivityentries carry each load’s state and input sensitivities, and — only when a modal solution is supplied — its per-mode modal residues (otherwise the residues areNone). The residue ranking is modal-residue screening of which modes drive a load, not a validated fatigue/design-load assessment.- Spectral propagation functions
frequency_response,welch_cross_spectral_densityandpropagate_cross_spectral_densityform the frequency-domain propagation path.frequency_responseevaluates selected named channels as \(H=C(sI-A)^{-1}B+D\) on a physical-frequency grid, using \(s=j2\pi f\) for a continuous model and \(s=\exp(j2\pi f\Delta t)\) for a discrete model. Named selectors must be unambiguous and a discrete request cannot exceed Nyquist.welch_cross_spectral_densityreturns the complete one-sided Hermitian input CSD, including cross terms, with the convention \(S_{ij}=E[X_iX_j^*]\). Segment and overlap lengths must be exact integers. Frequency-response and CSD objects copy and freeze their arrays; they require finite, dimensionally consistent values and unique nonempty channel names. A CSD must be Hermitian positive semidefinite at every frequency, and continuous-time frequency conversion must remain finite.propagate_cross_spectral_densityrequires an exact frequency/name match and evaluates \(S_y=H S_u H^{\mathsf H}\);integrated_spectral_variancethen integrates its diagonal over a declared band, interpolating the autospectra at band limits that fall between bins and clipping the band to the spectrum’s frequency range (a band that does not overlap it is rejected). This is a linear, model-side input-error-to-QoI assessment, not nonlinear load or fatigue validation.
Noise models, augmentation, and the estimator assessment.
Covariance validity. Every entry point that consumes a covariance —
build_kalman_model (Q, P0, R), steady_state_estimator
(process_noise, measurement_noise), discretize_process_noise,
augment_with_disturbance, and the noise-weighted observability and sensor-placement
tools — applies one shared rule, so the same matrix is accepted or
rejected identically everywhere. The rule is invariant to the unit of every channel
(to any positive per-channel rescaling S M S, and so to a global rescaling):
definiteness is judged on the correlation matrix K = D^-1/2 M D^-1/2 with
D = diag(M). R = diag(1e-12) (a strain gauge with one-microstrain noise) and a
mixed-unit R = diag(1e8, 1e-6) (a tension variance in N² next to an angle variance
in rad²) are as valid as R = diag(1), and an indefinite matrix is rejected however
small its entries.
Symmetric: every entry finite and
|M_ij - M_ji| <= 1e-9 · max(sqrt(|M_ii| |M_jj|), |M_ij|, |M_ji|); every term rescales with the pair’s own units, so no channel’s units affect another pair.Positive semidefinite (
Q,Qd,P0, process-noise densities, disturbance intensities): no variance below-1e-9 · max|M_ii|(smaller negatives are treated as round-off of a zero variance); every off-diagonal entry of a non-positive-variance channel satisfies|M_ij| <= sqrt(1e-9 · |M_ii| |M_jj|)(so an exactly zero variance requires an exactly zero row, whatever the other channels’ units); andλ_min(K) >= -1e-9on the positive-variance channels. The zero matrix is valid. Only the allowance for a tiny negative variance refers to the largest variance, so that one case is invariant under global rescaling only.Positive definite (
R): every variance strictly positive, the Cholesky factorization ofKsucceeds, andλ_min(K) > 1e-12. SinceKhas unit diagonal this bounds the unit-free condition number near1e12: only a genuinely near-dependent correlation structure (for example two perfectly correlated channels) is rejected as numerically singular, never a disparity of units.
measurement_noise_from_specs/discretize_process_noise/modal_process_noiseBuild the measurement- and process-noise covariances from instrument specifications and modal assumptions.
measurement_noise_from_specsreturnsR = diag(noise_std**2)and rejects a negative or non-finite standard deviation; a zero standard deviation gives a singularR, which the estimator builders then reject.initialize_covariances/covariances_from_confidenceBuild the estimator covariances
Q,R,P0.initialize_covariancesmakes uniform diagonal covariances from scalar noise/variance values.covariances_from_confidenceinstead inflates each mode’s initial variance by its identification confidence — a less-trusted mode starts with a larger variance — so the identification uncertainty propagates into the filter. Choose the confidence-driven one to keep that propagation; the uniform one discards it.augment_with_disturbance/harmonic_disturbanceharmonic_disturbancebuilds a narrowband disturbance model;augment_with_disturbanceappends it to a continuous-time model and returns the augmented system, the recovery map that reads the disturbance back out, and the augmented process-noise spectral density, for unknown-input estimation. Every prescribed oscillator frequency must be finite and positive, and its conversion to angular frequency must remain representable; invalid frequencies are rejected before a state matrix is allocated.steady_state_estimator/assess_loadsteady_state_estimatorsolves the discrete-algebraic-Riccati steady-state Kalman filter and returns the gain and the (Joseph-form, symmetric-PSD) error covariance; it raises aValueErrorwhen no stabilizing Riccati solution exists, naming both causes:(A, C)not detectable from the sensors, or(A, Q^1/2)not stabilizable (a marginal mode with no process noise).assess_loadreports each load’s steady-state filtered (a-posteriori, \(\sqrt{\mathbf L\mathbf P^+\mathbf L^{\mathsf T}}\)) error standard deviation and, only when a tolerance is supplied and met, setswithin_tolerance(a load checked without a tolerance is reported withwithin_tolerance = False). The bound is exact for the supplied model and is conditional on its assumptions (accurate linear model, correctQd/R, Gaussian/stationary noise, calibration, detectability); it is not a field-validated accuracy guarantee.build_kalman_model/reduced_estimatorbuild_kalman_modelassembles a Kalman-readyKalmanModel(matrices, covariances, and names).reduced_estimatorbuilds a discreteKalmanModelon a reduced modal system (frommodal_reduction) for the chosen sensor rows, inflating each mode’s initial covariance by its identification confidence (throughcovariances_from_confidence); itsprocess_noiseis a hand-set discreteQ, not a discretized density.
Running the estimator.
KalmanFilter/run_filter/SteadyStateKalmanFilterRun the assembled model.
KalmanFilterperforms the recursive predict/update on a discrete model (it rejects a continuous one), forming the gain by a Cholesky solve and the covariance by the Joseph form (symmetric-PSD even for ill-conditioned innovations). Each update accepts anavailablechannel mask (a dropped sensor updates only the reporting channels) and an optional chi-square NIS gate that rejects an outlier rather than corrupting the estimate: a fixedgatethreshold, orgate_probabilitypfor the per-step quantilechi2.ppf(p, n_used)that scales with the reporting channels (mutually exclusive). The NIS, an acceptance flag, and the innovation’s channel count (n_active, also for a gated-out step) are recorded.run_filterdrives a whole measurement sequence into aFilterResult(filtered and predicted means/covariances, per-step NIS, acceptance).SteadyStateKalmanFilterruns the same predict/correct step with the constant steady-state gain (fromsteady_state_estimator), without a channel mask, NIS check or covariance update, for an embedded twin.Control timing (zero-order hold). Row
kofcontrolsis the input logged withmeasurements[k]. MatchingStateSpace.discretized, the filter predictsx_k = A x_{k-1} + B u_{k-1}and forms the innovation againstC x_k + D u_k: the previous sample’s control drives the state, the current one only the feedthrough.initial_state/P0is the prior one step beforemeasurements[0]; the first prediction usesinitial_control(defaultcontrols[0], the input held at its first logged value; pass zeros for a plant at rest);initial_controlwithoutcontrolsis rejected.KalmanFilter.step,SteadyStateKalmanFilter.step/run, andrts_smoother(through the recorded predictions) follow this convention;KalmanFilter.predict(u)is the raw transition with the held inputu.filter_consistency/rts_smoother/reconstruct_loadAudit and refine a run.
filter_consistencychecks the NIS sequence against its chi-square expectation (a mis-tunedQ/Ris flagged, not silent); gated-out steps are included (their NIS is computed before the gate), so gating does not bias a correctly tuned filter towards “under-confident”.rts_smootherruns the Rauch-Tung-Striebel backward pass for an offline, reduced-variance estimate.reconstruct_loadreads a linear load functional off the (filtered or smoothed) state and returns its value with the propagated standard deviation — the virtual sensor and its error bar.schedule_model/run_scheduled_filterRun the estimator over an operating-point sweep (LPV scheduling) on a
RomFamily.schedule_modellinearly interpolates the family to one operating point (clamping and flagging an out-of-range point asextrapolated);run_scheduled_filterre-schedules the plant each step as the operating point drifts, carrying the state and covariance in the family’s mode-tracked, phase-aligned basis. Each step records the lowest tracked-mode tracking MAC (the interpolation’s confidence) and the extrapolation flag, and a low-confidence or out-of-range schedule logs a warning rather than silently blending unrelated modes. Control timing and gating followrun_filter(zero-order hold withinitial_control; fixedgateor per-stepgate_probability). Requires a continuous-time family and a positivedt; rejects a discrete family, non-finite operating point/threshold, or mismatched sequence lengths.save_result/load_resultArchive and reload an estimator result (
FilterResultorScheduledFilterResult) as a single.npz. The arrays are stored as plain NumPy arrays and every other field (names, flags, the nested consistency report) as a JSON metadata blob, stamped with the result type and the producing VANE version.load_resultreads withallow_pickle=False— a tampered file can carry no executable payload — and reconstructs the result exactly; a file that is not a VANE estimator-result archive, or names an unknown type, is rejected with aValueError.save_resultrejects any other type, including a subclass, with aTypeError. An estimator result is pure data, so loading one needs no trust boundary.
Driving OpenFAST (the linearization sweep)
- Automatic configuration (
run_operating_sweep(auto_configure=True), the default) run_operating_sweep(fst_path, output_dir, wind_speeds, performance_csv=csv)is a complete call. Everything else the linearization needs is read from the user’s own deck and curve (auto_configure_sweep); VANE computes no rotor performance. Each value is logged at INFO and recorded as anInferredValue(name,value,sourcenaming the file and keyword or the rule,detail) in anAutoConfiguration, which also carries the warnings raised while inferring. It is returned asOperatingSweepResult.auto_configuration, written tooutput_dir/auto_configuration.jsonand copied into the driver’sRunProvenance.auto_configuration. A value passed explicitly is never replaced: an explicittemplate_root,executableorsettingsis used as given, and only the turbine constants the settings leave unset (rated_wind_speedandrated_generator_torquewhenNone, andtrim_gain_pitchandtrim_tolleft at their class defaults2e-4and1e-3) are inferred. Withauto_configure=Falsenothing is inferred:settings=NonemeansLinearizationSettings.offshore(),executable=Nonemeans"openfast"onPATH, andtemplate_root=Nonemeans the deck’s own directory.template_root(when not given and the deck references files outside its own directory): the deepest directory holding the deck and every file reached from it through a relative reference (deck_files.infer_template_root). Absolute references, and the references inside an absolutely referenced file, stay in place and do not widen it. Anoutput_dirinside the inferred root raisesValueErrorbefore anything is copied.The OpenFAST executable (
executables.select_openfast_executable): the deck’s input-format generation is detected from keyword signatures (detect_deck_generation: v5 primary-fileModCoupling/ConvTol/… or ElastoDynPitchDOF; v4.2 ElastoDynHubIner_Teeter; v4.1 HydroDynNPropSetsCyl; v4.0 HydroDynNPropSets; v3 noCompSeaSt) as a release range, and each candidate’s release is read once fromopenfast -v(cached per file). Candidates, in order:executable, theVANE_OPENFAST_EXECUTABLESenvironment variable (os.pathsep-separated), the file named byVANE_OPENFAST_CONFIG(default~/.vane/openfast_executables.txt, one path per line) andopenfast/openfast_x64/OpenFASTonPATH. The first whose release the deck accepts is used, else the first of unknown version (warned). None compatible raisesOpenFastErrorbefore any case is copied, naming the release range, the keyword evidence and every executable considered. An explicit executable of known, incompatible release raises rather than being swapped.rated_wind_speed: the first curve wind at which the rotor speed is within 0.5 % of the curve maximum (held for at least two samples) and, when the CSV has acpcolumn,cp * V**3stays within 0.5 % of its final value over the constant-speed region from that wind to the end of the curve (at least two samples; a power still rising at the last sample has no plateau and is an error). With a controller input file the curve’s rated speed and power are compared withVS_RefSpd/GBRatioandVS_RtPwr(2 % and 5 %); a disagreement is a warning. A curve without a plateau leaves it unset, and a rotating sweep that needs it then raisesValueErrorbefore launch.rated_generator_torque:VS_RtTqof the controller input file (ServoDynDLL_InFile,value ! Keywordlayout) when ServoDyn runs a control channel through the library; else ServoDynVS_RtTqwithVSContrl = 1; elsecp * rho/2 * pi * R**2 * V**3 / Omega * GBoxEff / GBRatioat the curve’s rated plateau (rhothe deck’sAirDens,RElastoDynTipRad). Thecpcolumn is read bycurve_power_coefficientwith the units ofrotor_performance_from_csvbelow; a wrong or ambiguous unit raisesValueErrorbefore launch, and a column that is absent, incomplete, negative or not aligned with the curve is not used.trim_gain_pitchandtrim_tol(when left at their class defaults 2e-4 and 1e-3; an explicitly passed value, even an equal one, is kept):scaled_trim_gain_pitchandscaled_trim_tolof ElastoDynTipRad.Module settings (
settings=Noneonly):LinearizationSettings.offshore()adapted to the deck. With WAMIT flow (PotMod = 1) a radiation model whose<PotFile>.ssis missing keepsRdtnMod = 2whenpreview_radiation_state_spacefits the<PotFile>.1data within the acceptance limit:hydro_radiation_fit = Trueand anInferredValuehydro_radiation_fit(source the.1file, detail the state count and error) are recorded. A radiation model that cannot be fitted (no or malformed.1data, more than one body, no water density, a fit beyond the limit), an excitation model whose.ssexctnis missing, or any model withNAddDOF > 0becomes 0 with a warning that says why; the deck’s own 0, or a file without the line (an older HydroDyn has noExctnMod), is left unchanged; without WAMIT flow both are left unchanged. A modern AeroDyn deck’s linearizableUA_Mod(4-6) is kept. When BEMT (Wake_Mod = 1) withTipLosswould keep it on the tip node (UAEndRadabsent or>= 1),ua_end_radius = 0.99is written or, if the file has noUAEndRadline, quasi-steady aerodynamics (0) is used with a warning. OtherUA_Modvalues, and a legacy-format AeroDyn file, get the preset’s 0.sim_time(settle sweeps whose platform probe ran, when not set):FLOATING_TRIM_PERIODS(20) periods of the slowest platform mode, withinMIN_CALC_STEADY_TMAXand the retry policy’smax_sim_time.
Before any case is copied the template is checked with
validate_deckunder the effective settings (withvalidate=True), and, for a curve sweep withoutsettle=True, every rotating curve point’s trim configuration is checked against it (FastInputError), so a problem common to every case (no rated torque for an above-rated point, a missingUAEndRadline) raises once, naming the keyword and its remedy.run_operating_sweepDrives OpenFAST over a list of wind speeds and returns an
OperatingSweepResult(the derivedoperating_pointsand the per-pointSweepCaserecords). It refuses to guess the operating point: a rotor-performance source must be given — an operating curve (curve_wind/curve_rotor_speed/curve_pitch) or aperformance_csv(at most one of the two), and/orsettle=True— and supplying none, or both a curve and a CSV, raises aValueErrorbefore any case is copied or any simulation is run. A curve or CSV combined withsettle=Trueis used only as the settle runs’ initial conditions (ElastoDynRotSpeedandBlPitch(1..3)of each settle copy, at the curve value for that wind); the settled point, not the curve, is linearized, and every requested wind must lie in the curve’s range. After each seeded settle,compare_settled_to_curvecompares the settled rotor speed (relative) and pitch (absolute) with the curve undersettle_curve_tolerances(aTrimTolerances, default 1 % and 0.5 deg); theSettleCurveCheckis attached to the point assettle_curve_checkand recorded inCaseProvenanceassettle_curve_rotor_speed_rpm/settle_curve_pitch_deg/settle_curve_status. A"flag"is logged as a warning and never raises. Without a curve a settle starts from 0 rpm and 0 deg pitch. The platform offset is never derived from the curve;settle_initial_platform(onePlatformPositionor a wind-keyed mapping whose keys must be requested winds) optionally sets it, else the deck’s position is kept.settle_initial_platformwithoutsettle=True, orsettle_curve_toleranceswithout a seeded settle, raisesValueError. The settle copies keep the template deck’s ElastoDyn DOF flags (structural_dofsapplies to the linearization cases only); the template’s values of the flags the linearization sets are returned asOperatingSweepResult.settle_structural_dofsand a difference is logged. One settle point never aborts the sweep. Every wind is settled (withrequire_settled=Falsepassed tosolve_steady_state), and each outcome is then classified. A settle whose criterion was not met or could not be assessed is"not_settled". Underrequire_settled=True(the default) it is not linearized, except that a prescribed-rotor settle (no loadable controller library) of a rotatingCalcSteadypoint is linearized, since the trim finds the equilibrium itself; underFalseit is linearized at its last mean. A settle that raisedSteadyStateError(OpenFAST failed, unreadable output) is"failed"and is not linearized. Any other exception is"error": it is logged with its traceback, and once every other wind has been settled and linearized it is re-raised asSweepSettleError(aSteadyStateError) chained to the first such exception, with the completeOperatingSweepResultattached as.result. Every such point is listed, in wind order, inOperatingSweepResult.settle_failuresas aSettleFailurewithwind_speed,reason,message,linearized, the last-meanoperating_pointand theconvergenceevidence (final RMS, every attempt’s horizon and window, the criterion, and any two-stage confirmation).to_dict()gives a strict-JSON form. A summary warning is logged.operating_pointsandcaseshold only the linearized points.strict_settle=TrueraisesSweepSettleError(with.result) at the end when any point is insettle_failures. A settle sub-file that resolves outsidetemplate_rootis a configuration error shared by every wind, so it raises before any settle case is copied. It validates the request at the boundary (a missing template deck, an empty/non-finite/negative/duplicate wind-speed list, a partial or mismatched curve, atemplate_rootthat does not contain the deck, and a positive wind point with no usable InflowWind file each raise one clear error up front). The worker count defaults to the CPU count minusreserve_cpus(default 1), at least one (default_max_workers). Unlesssim_timeis set, aCalcSteadytrim runs withTMaxof at leastMIN_CALC_STEADY_TMAX(vane.run.lin_config, 600 s), since template decks often carry a placeholderTMaxshorter than the trim; OpenFAST stops once the trim converges. Deck keywords are matched case-insensitively, as in OpenFAST. A non-convergingCalcSteadytrim is retried on theLinearizationRetryPolicyladder, which classifies each failed run (classify_openfast_outputfor trim not converged, motoring, rotor overspeed, divergence, initialization error and unknown; the driver adds timeout, incomplete linearization, and a non-periodic sweep whose rotor speed varies across the azimuth files by more than MBC3 accepts) and picks the next settings from that class. A trim that ran out of time gets a longerTMax, a looserTrimTol(not beyond 1e-2 unless it already was) and a lower pitch gain. A non-periodic sweep getsTrimToltightened back to its base value and a longerTMax. An unstable trim (motoring, overspeed, divergence) gets lower gains; overspeed and divergence also reduce the trim damping, and any of the three removes it after a failure withinearly_failure_time(1 s). After an unstable failure, the last of themax_attempts(4) attempts trims an at- or above-rated point by generator torque at the curve pitch. An initialization error is never retried. A run that otherwise converged is still rejected when any full-system.linfile holds a NaN or infinity (non_finite_linearization, naming the first offending variable); that is a modelling degeneracy, so it is not retried either. Every attempt’s full OpenFAST output is written to<deck stem>.attempt<k>.logbeside the case’s primary deck, and its failure mode, settings and log path are recorded inSweepCase.attempt_historyand the case provenance (CaseProvenance.attempt_history/failure_mode/sim_time). A point that never converges is recordedconverged=Falsewith itsattempts/n_linrather than silently accepted. One linearization case never discards the others: an unexpected exception while a case is generated, run or collected (a file-system error, an unreadable sub-file) ends that point’s retry ladder and is recorded as a failedSweepCasewithconverged=False,failure_mode == "case_error"(OpenFastFailureMode.CASE_ERROR, never retried), return code-1andn_lin == 0. The finalLinearizationAttemptcarries"case_error: <type>: <message>"as itsreason(also in the provenance), its attempt log holds the traceback, and the earlier attempts’ records are kept.LinearizationDriver.run_sweep(strict=True)andrun_operating_sweep(strict_cases=True)instead raiseSweepCaseErroronce every other case has finished, chained to the first exception, with every case attached as.cases(and, fromrun_operating_sweep, the completeOperatingSweepResultas.result).run_casecalled directly still propagates the exception. Boundary errors are still raised: the argument checks before any case is copied, and aDeckValidationErrorfrom the pre-flight guard (an input error found before that case’s OpenFAST run). On Windows the case-file operations (the template copy and hard links, the deck edits’ atomic replace, the case-directory removal before a retry, the settle copy edits, stale-output removal and the JSON provenance write) retry an access-denied, sharing-violation or lock-violation error (winerror5, 32 or 33), with up to five tries in all (four retries) and a backoff doubling from 0.05 s (0.75 s of waiting in all), before raising; antivirus scans, the search indexer and a recently exited OpenFAST briefly hold such handles. Any other error, and every POSIX error, is raised at once (vane.utils.fs_retry). SettingLinearizationDriver(acceptance_policy=LinearizationAcceptancePolicy())(a driver field, not arun_operating_sweepargument) adds an independent physical check after that numerical criterion: VANE estimates the azimuth mean of the ElastoDyn platform translational/angular rate states and rejects a secular residual above the configured limits (defaults 1e-3 m/s and 1e-4 rad/s). Rotating points require 12 unique azimuths by default, at least four under any explicit override, no duplicate azimuth, and a layout whose harmonic-cancelling mean weights (the minimum-norm weights that cancel the 1P–3P harmonics at the actual, possibly non-uniform, azimuths) exist and inflate the variance of an equal-weight mean by at most a factor of two. A physical-residual rejection stops without entering the normal retry ladder, because that ladder loosensTrimToland would move in the wrong direction. The complete residual audit is retained in the case provenance.LinearizationDriver.run_sweepnames each case directory after its operating point with trailing dots dropped and rejects, before any case is copied or run, two points whose names coincide case-insensitively (the default Windows and macOS filesystems would give them one directory, and the second case would delete the first one’s results) as well as a reserved Windows device name (CON,NUL,COM1…). Every case (and every settle run) is also checked before it is configured: the primary deck and every sub-file the run will edit must resolve inside the case copy (require_inputs_within), because a sub-file referenced outside it (../ElastoDyn.dat) would be edited where it lives, shared by every case and the template. A linearization always edits ElastoDyn and InflowWind, and SeaState, HydroDyn, AeroDyn or ServoDyn only when the settings change them; a settle run edits ElastoDyn, InflowWind and SeaState, ServoDyn when a controller library is written into the settle copy (given or found automatically), and HydroDyn when a settle platform damping is added to itsAddBLin. Files that are only read may stay shared. Settemplate_rootto a directory that contains every edited input.configure_linearizationapplies the same check when called withconfine_to=. What a case receives fromtemplate_rootis selected bytemplate_copy(LinearizationDriverfield andrun_operating_sweepargument; settle cases follow it too). The default,"referenced", copies only the files the deck references (deck_file_closure): starting from the primary.fst, every referenced text input is read and every value token that looks like a path is resolved relative to the referencing file’s directory and to the deck’s directory, and kept when it names an existing file undertemplate_root; a root-name keyword (PotFile,WvKinFile, any...Root) brings every<root>.<ext>input beside it, and a.dll/.so/.dylibreference brings that library’s platform and bitness variants (_x64,_win64, …) but no other sidecar unless referenced. Files keep their paths relative totemplate_root. Unknown keywords,"unused","default", numbers and missing files are ignored rather than fatal. A relative reference that resolves outsidetemplate_rootor an unreadable input raisesDeckReferenceErrorinside the walk, andcopy_template_filesthen logs a warning and copies the whole tree, exactly astemplate_copy="tree"does; an absolute reference outside the root is left in place (it still resolves from the case) and listed inDeckFileClosure.external. Either way the edited-input confinement above still applies, inputs are hard-linked whenLinearizationDriver.link_inputsis set (the default; a byte copy where linking is not supported), and OpenFAST output files are byte-copied, never linked.operating_curve_from_csv/operating_curve_from_performanceParse a rotor-performance schedule into the
(curve_wind, curve_rotor_speed, curve_pitch)arrays the sweep consumes, sorted by ascending wind. The CSV requires a header row naming three required columns —wind_speed(m/s),rotor_speed_rpm(rpm),blade_pitch_deg(deg) — matched tolerantly (case/whitespace/separator insensitive); other columns are ignored, units included. A unit in[...]or(...)on a required column is parsed case/whitespace-insensitively: no unit or the canonical unit (among othersm/s/mps,rpm/rev/min,deg/degree(s)) is read as is;km/h(÷3.6),rad/s(×60/2π), andrad(×180/π) are converted and logged at INFO; any other unit — or a converting unit on a header whose name states the canonical unit, e.g.rotor_speed_rpm [rad/s]— raisesValueErrornaming the column, the unit, and the accepted units. Both paths reject a non-numeric/NaN/infinite value, a negative wind or rotor speed (a negative collective blade pitch is allowed) and duplicate wind speeds, and require at least two operating points (the schedule is interpolated);operating_curve_from_performancecalled without aperformance_tableraisesRuntimeError. Units are read from the header only; a units row raisesValueErroras a non-numeric value, with a hint. VANE computes no rotor performance itself and has no aerodynamic-performance dependency.rotor_performance_from_csv/curve_power_coefficientRead the optional power- and thrust-coefficient columns (
cp,power_coefficient,RtAeroCp,RtFldCp;ct,thrust_coefficient,RtAeroCt,RtFldCt) and return them as fractions. More than one column for a coefficient raisesValueError. The header bracket declares the unit: none,-,1orfractionis a fraction;%,percentorpcta percentage, divided by 100 and logged at INFO; any other unit raisesValueErrorlisting the accepted forms. A converted value above the physical bound (the Betz limit 16/27 forcp, 2 forct) raisesValueError(a percentage without its unit), as does a column declared in percent whose non-zero values are all at most 1 in magnitude. Blank or non-numeric cells areNaNinrotor_performance_from_csv;curve_power_coefficientreturnsNonewhen acpvalue is missing or negative, the rows do not match the curve winds, or the file has nocpcolumn.LinearizationSettings(structural_dofs,offshore)Self-validates at construction (rejecting non-physical counts/tolerances and module switches that do not select a linearizable model).
structural_dofsmaps ElastoDyn DOF flags; its default enables the blade and tower flexibility DOFs (FlapDOF1,FlapDOF2,EdgeDOF,TwFADOF1,TwFADOF2,TwSSDOF1,TwSSDOF2) and disablesYawDOFandDrTrDOF(dropping the spurious free nacelle-yaw rigid-body mode and, forCalcSteadytrim convergence, the drivetrain-torsion mode; pass{"DrTrDOF": True}to keep it), and a supplied mapping is merged on top of those defaults.auto_configure_sweeplogs a warning naming each flexibility DOF that the deck has off and the settings turn on, and records it inAutoConfiguration.warnings. A flag listed but absent from the ElastoDyn deck is an error (FastInputError); a default flag the deck lacks is skipped.full_jacobiansrequireslin_inputs=2andlin_outputs=2(ValueErrorat construction). Whenfull_jacobiansormodule_linearizationsis enabled, the primary deck must containLinOutJacorLinOutMod, respectively; otherwiseconfigure_linearizationraisesFastInputErrorbefore any file is written.unsteady_aero_mode/dynamic_wake_modealso configure a legacy-format AeroDyn deck (AFAeroMod/UAMod/WakeModkeywords): quasi-steady aero setsAFAeroMod = 1, and a frozen (-1) or no (0) dynamic wake turns a dynamic-BEMTWakeMod = 2into plain BEMT withFrozenWakeon or off.validate_deckchecks those legacy keywords and reports a legacy deck that would still linearize with unsteady airfoils, a dynamic or free-vortex wake, or a model the legacy format cannot express (unsteady_aero_mode4–6,dynamic_wake_mode=3).ua_end_radiuswrites AeroDyn’sUAEndRad(a number in(0, 1]); an enabled modern-format AeroDyn file without that line raisesFastInputErrorbefore any file is written, and a legacy-format file receives the value only when it already has the line.hydro_radiation_fit(a bool, only withhydro_radiation_mode = 2) makes theLinearizationDrivercallensure_radiation_state_space(deck, confine_to=case_dir)on every configured case copy, before it is validated and launched, so a missing<PotFile>.ssis fitted into the copy and never next to the template’s data. A fit whose maximum relative error exceeds 0.2 writes nothing, and the driver raisesRadiationFitError(aValueError), as it does for more than one WAMIT body, a missing or unfittable.1file, or aPotFileoutside the case copy;run_sweeprecords it as that case’sfailure_mode="case_error"andrun_caseraises it. The generated file’sRadiationFitRecordisSweepCase.radiation_fitand, asto_dict(),CaseProvenance.radiation_fit.validate_deckaccepts a missing.ssunder this setting when<PotFile>.1exists. Withcalc_steady=False,linearization_timesmay specify a complete set of absolute sampling times for an independently settled rotating point. The sequence must be finite, non-negative, strictly increasing, equal in length ton_lin_times, and no later thansim_time(or the deck’s ownTMaxwhensim_timeis unset). The driver accepts the run only when every requested full-system.linfile exists. VANE does not itself check that the explicit times are at equilibrium; setLinearizationDriver(acceptance_policy=...)to apply the physical residual audit.remap_hydrodyn_state_space_wave_inputApplies the known HydroDyn state-space wave-input label correction only when its complete matrix, operating-point and channel signature is present in both the averaged model and every retained azimuth-resolved B/D member.
zero_atolandnonzero_atolmust both be finite and non-negative;NaNor infinity cannot weaken the signature checks. A missing signature raisesValueError; on success only the two input descriptions and their MBC-coordinate labels are swapped, and no coefficient changes.apply_hydrodyn_wave_dof_signsApplies an independently established HydroDyn wave-DOF sign convention only when the averaged and every retained azimuth-resolved wave column excite the same contiguous, HydroDyn-only state block whose size matches the caller’s
states_per_dof(the.ssexctnheader counts, seeread_state_space_excitation_metadata); eachdof_signsentry must be +1 or -1, and any mismatch raisesValueError.solve_steady_stateRuns the fully-coupled controller at a constant wind, discards the transient, and returns the settled
OperatingPoint(rotor speed, pitch, and the floating platform equilibrium). It judges settling by the platform-velocity RMS over the steady window (the working copy’s ElastoDynOutListgains any missingPtfmTVxi..``PtfmRVzi`` velocity,PtfmSurge..``PtfmYaw`` position,RotSpeedandBldPitch1channels, appended as one quoted line before the firstOutListblock’sEND); withrequire_settled=Truean unsettled or unverifiable point is an error rather than a silently-acceptedTMaxguess. Every run, including each adaptive retry at a longerTMax, is judged over a trailing window of fixed lengthsettle_time - discard_timeending at that run’sTMax(TStartis set to the window start), and the returned rotor speed, pitch and platform position are the means over that same window (see the period alignment below), so a longer run discards the start-up transient rather than diluting it. The output stepDT_Outis the whole multiple ofDTnearest one second (one second whenDTis missing, non-finite or not positive), as OpenFAST requires. A NaN or infinite sample in the window, or an overflowed (*****) output field, means the run diverged and raisesSteadyStateError. The window bounds are recorded aswindow_start/window_endonSettleConvergenceand on eachSettleAttempt.SettleConvergence.attemptspreserves the ordered horizon and RMS result from every adaptive attempt; a rejected required settle exposes that same evidence asSteadyStateError.convergence. An optionalplatform_positionwarm-start writes only the supplied ElastoDyn initial DOFs; it does not alter the controller or physical-module settings. The settle run and the linearization share one inflow convention: both write the InflowWind power-law shear exponentPLExpfromwind_shear_exponent(default0.0, steady uniform inflow;Nonekeeps the deck’s value), andrun_operating_sweeppassesLinearizationSettings.wind_shear_exponentto its settle runs, so a settled point is never linearized in a different (sheared) wind. The value is recorded with the other settings in the run provenance. The settle run also shares the linearization’s still water: SeaStateWaveMod = 0plus the same neutralization of the options OpenFAST rejects in still water (WaveStMod2/3 reset to 0,MCFDreset to 0, aDEFAULTCurrSSDirreplaced byWaveDir,WaveDirModandConstWaveModreset to 0). Unlike the linearization, the settle run keeps the deck’s Bladed-style controller library active. When ServoDyn routes any control through it (PCMode,VSContrl,HSSBrMode,YCMode,AfCmodeorCCmode= 5),check_controller_libraryverifies before OpenFAST launches (and before any deck is edited) thatDLL_FileName, resolved relative to the ServoDyn file, exists and loads on this host (checked by loading it in a child process), and otherwise raisesSteadyStateError(“controller library not found: …; pass a controller library path (controller_library=…) built for this platform”, or “… cannot be loaded on this host …”). This is the usual case for a published deck naming a Linuxlibdiscon.soon Windows.controller_library=(onsolve_steady_stateandrun_operating_sweep) supplies a library built for the host; it must exist (FileNotFoundErrorotherwise) and load on this host (SteadyStateErrorotherwise). When ServoDyn routes control through the library it is written, as an absolute path, intoDLL_FileNameof the settle copy only, never the template; otherwise it is ignored with a warning. The settle confinement set isSETTLE_EDITED_SUBFILE_KEYS(EDFile,InflowFile,SeaStFile,ServoFile);ServoFileis dropped from it without an override, so with an override the ServoDyn file must resolve inside the settle case.run_operating_sweepresolves the library once, before any case is copied, throughplan_floating_settle(below), and rejectscontroller_librarywithoutsettle=True.prescribed_rotor=Truesettles without a controller: the settle copy getsCompServo = 0and ElastoDynGenDOF = DrTrDOF = False, the library is neither checked nor written, the rotor turns atrotor_speed_rpmwith the blades atblade_pitch_deg, the returned rotor speed and pitch are exactly those values, andSettleConvergence.prescribed_rotorisTrue; combining it withcontroller_libraryraisesValueError.criterionselects how a run is judged settled:"auto"(default),"velocity"(every platform-velocity RMS withinvelocity_tol/angular_velocity_tol) or"mean_drift"(the means of the platform position,RotSpeedandBldPitch1over the first and second halves of the analysis window differ by no more thanequilibrium_tolerances, anEquilibriumTolerances: 0.1 m, 0.05 deg, 0.5 % ofmax(rotor speed, 1 rpm), 0.1 deg by default)."mean_drift"accepts a stationary oscillation about a steady mean and returns that mean; a run it accepts while still above the velocity tolerances is logged as a warning naming the oscillation. EverySettleAttemptand theSettleConvergencerecord thecriterion, the half-windowmean_driftand theoscillation_period(dominant period of the most active platform-velocity channel,Nonewhen there is too little data or no motion), whichever criterion is used, and the oscillation’sresidual_damping_ratioover the analysis window."auto"judges each run by the velocity criterion and switches that run to the mean-drift criterion only when the velocity criterion fails, the run was assessed, at least one platform position channel was output, and the oscillation is sustained:|residual_damping_ratio| < equilibrium_tolerances.residual_damping_ratio(a decaying transient keeps the velocity criterion, a growing oscillation never switches a single run, and fewer than two whole cycles never switch). The recordedcriterion(convergence and per attempt) is always"velocity"or"mean_drift", the one that judged the run;requested_criterionholds the argument andcriterion_switchedwhether"auto"switched.platform_damping(aSettlePlatformDamping: non-negative, finite N/(m/s) and N m/(rad/s) per platform DOF, at least one positive;from_critical_fractionsizes it as4 pi zeta m / T) makes the settle two-stage. Before launch the deck must run HydroDyn (CompHydronot 0, an existingHydroFile) whoseAddBLinblock is6 NBodynumeric rows (6 NBodycolumns forNBodyMod = 1, else 6) starting at theAddBLinlabel and followed byAddBQuad; otherwiseValueError. Stage 1 adds the damping to the diagonal of body 1’s block (other tokens and labels verbatim, via an atomic replace that never writes through a hard link) and settles with the configured schedule, outputtingTTDspFA/TTDspSSas well. The deck’s original HydroDyn bytes are then restored, also when stage 1 fails. Stage 2 is one undamped run ofconfirm_timeseconds judged over[confirm_time / 4, confirm_time], started from the stage-1 means ofRotSpeed,BldPitch1, the platform DOFs and theTTDspFA/TTDspSS(and, when the deck outputs them,OoPDefl1/IPDefl1) deflections.confirm_timeNone(the default) isceil(8 T / 0.75)s – eight periodsTof the slowest platform mode in the analysis window – withTtheplatform_periodargument (positive and finite, elseValueError) or else the damped stage’soscillation_period, floored at 1200 s (also the length without any period) and capped atmax_settle_timewhen given, else 3600 s; an explicit value is used as given. Every run’s returned means (every settle run’s, not only stage 2’s) and both halves of the mean-drift comparison are averaged over the trailing whole number of that run’soscillation_period(aligned to the window end; the plain window mean when no period is detected or less than one fits). Itsresidual_damping_ratiois estimated from the platform-velocity channel with the largest de-meaned RMS relative to its tolerance: the slope of the log of the RMS in successive whole periods, counted back from the end, divided by-2 pi. It isNonewhen no period was detected or fewer than two whole cycles are available.stableholds when stage 2 met the criterion by itself, or, when stage 2 was judged by the velocity criterion, whenresidual_damping_ratiois at leastequilibrium_tolerances.residual_damping_ratio(default 0.005). When stage 1 converged and stage 2 is decaying in that sense but a mean moved beyondequilibrium_tolerances, stage 2 is rerun once at twice its length (capped atmax_settle_timewhen given; no rerun when the cap leaves it no longer) and the rerun is judged instead (extended). Withcriterion="auto", when stage 1 converged, a stage 2 that is notstablebut whose means all stay withinequilibrium_tolerancesof stage 1, whoseresidual_damping_ratiois known (sustained or growing: there is no lower bound), and whose measured platform-velocity RMS values (at least one measured) are all withinequilibrium_tolerances.oscillation_velocity_rms(default 0.2 m/s and deg/s) is accepted about the confirmed equilibrium:sustained_oscillationis set on the confirmation and the convergence, and a warning is logged. Linear damping is zero at zero platform velocity, so the stage-1 equilibrium is the undamped model’s, stable or not. When the accepted residual is below-equilibrium_tolerances.growing_damping_ratio(default 0.02, a classification threshold only), the read-onlySettleConfirmation.growing_oscillationandSettleConvergence.growing_oscillationare true and a distinct warning says the equilibrium is unstable under the deck’s controller and is linearized without it (the trim replaces the controller, so that closed-loop instability is not part of the linear model).SettleConvergence.to_dict()andSettleConfirmation.to_dict()addgrowing_oscillationto theasdictfields;SettleFailure.to_dict()and theCaseProvenancerecord carry it. Under the"velocity"and"mean_drift"criteria this acceptance does not apply: they never accept a non-decaying stage 2 that does not meet them.convergedis true only when stage 1 met the criterion and either stage 2 isstablewith no stage-2 mean moved from stage 1 beyondequilibrium_tolerances(a stage 2 that still oscillates, that is, one accepted by the mean-drift criterion rather than by velocity or decay, must also keep every platform-velocity RMS withinequilibrium_tolerances.oscillation_velocity_rms), or the"auto"acceptance above applies (sustained_oscillation). A converged point returns the stage-1 means; an unconverged one returns the (last) stage-2 means. The evidence is aSettleConfirmationonSettleConvergence.confirmation: the damping, the stage-1 result (damped_converged), RMS,TMaxand means, the stage-2 means, theshiftand the flagged quantities,confirm_converged,residual_damping_ratio,stable,confirmed, the judgedconfirm_timeand itsconfirm_time_source("user","platform_period","damped_stage"or"fallback"),extended,sustained_oscillationand the appliedtolerances.attemptslists every run in order, each taggedstage="damped"or"confirm". A failed confirmation is logged or, withrequire_settled, raised, with its reason: the damped stage did not settle, the equilibrium moved, or the undamped run is still moving and not decaying (described as growing when its residual is below-growing_damping_ratio), together with what blocked its acceptance: a criterion other than"auto", the moved equilibrium, the unsettled damped stage, fewer than two analysed cycles, or a velocity RMS aboveoscillation_velocity_rms. Growth alone is never the reason.PlatformProbe.slowest_periodis the largest positive finite probe period (Nonewithout one).run_operating_sweepexposessettle_criterion(default"auto"),settle_platform_damping(defaultNone: for a floating deck, sized from the standstill platform probe unlesssettle_auto_damping=False),settle_confirm_time(defaultNone, automatic) andsettle_equilibrium_tolerances, and passes a probe’sslowest_periodasplatform_period. All four are rejected withoutsettle=True(asettle_criterionother than"auto"counts). With damping, the template’s HydroDyn is checked before any case is copied, andHydroFilejoins the settle case’s confinement set. Linearization cases are copied from the template and never see the damping.CaseProvenancerecordssettle_criterionandsettle_confirmation, with an unmeasured RMS written asnull.run_openfastA supplied timeout must be positive and finite (a boolean raises
TypeError, any other invalid valueValueError); it is checked before any retained output is removed. Before launch, stale full-system and per-module (LinOutMod) linearization files and the primary deck’s text, binary and summary outputs are removed; every path in the returned result therefore belongs to the current process invocation. The executable runs in its own process group (Windows) or session (POSIX); on a timeout the whole process tree is killed beforeOpenFastTimeoutErroris raised, so a wrapper executable (a.bator shell script launching OpenFAST) cannot leave an orphaned solver running or block the call on its still-open output pipes. The console output is decoded with the locale encoding, an undecodable byte being replaced (U+FFFD) rather than failing the run.The working directory and executable are passed without the Windows extended-length prefix (
\\?\). OpenFAST does start from a prefixed working directory, but it still does not collapse..and the prefix’s four characters count against its path limit, so it only fails sooner; a path that needs the prefix raisesOpenFastErrorinstead. The deck is also checked withcheck_deck_path_lengthsbefore launch;check_path_lengths=None(the default) runs the check on Windows only, andTrue/Falseforces it on or off. OpenFAST joins each referencing file’s directory, as it spelled it, to the reference without collapsing..(<deck dir>\.\..\5MW_Baseline\<file>), and the Windows build cannot open a path longer thanOPENFAST_MAX_PATH_LENGTH(258) characters: it then aborts at initialization withThe input file ... was not foundalthough the file exists. The check raisesDeckPathLengthError(aDeckValidationError) naming the longest such path and its length; the remedy is a shorteroutput_dir.classify_openfast_outputmatches its signatures on the output with whitespace and line breaks collapsed, because OpenFAST wraps console messages at about 100 columns.- JSON run records
RunProvenance.writeand the sweep’s automatic-configuration record serialize strict finite JSON (noNaNor infinity constants) before touching the destination. They write a freshly created same-directory temporary file, remove it after a failure, and replace the destination atomically.- Floating settle set up from the deck (
vane.run.floating_auto) find_controller_library(fst_path, *, search_paths=(), environ=None)reads the deck, writes nothing, and returns aControllerLibraryResolution(status,library,deck_library,source,searched;availableisstatus != "missing"):"not_used"when ServoDyn is off or routes no control through a library;"deck"whenDLL_FileName(resolved relative to the ServoDyn file) exists, does not carry another platform’s suffix and loads in a child Python process (libraryis thenNone: nothing is rewritten);"found"with the first match of the deck library’s key (name with the suffix, one bitness tag_x64/_x86/_win64/_win32/_64/_32and alibprefix removed, case folded) among files with the host suffix (.dll/.so/.dylib) that also load in a child process, searching, in order and each location once, the deck library’s directory (whenDLL_FileNameresolves), the ServoDyn directory, the primary deck’s directory,search_paths, theVANE_CONTROLLER_LIBRARIESentries (os.pathsep-separated) and the lines ofcontroller_libraries.txtinVANE_CONFIG_DIR(else%APPDATA%/vaneon Windows,$XDG_CONFIG_HOME/vaneor~/.config/vane); an entry may be a file (used only when its key matches) or a directory (a plain or 64-bit build preferred to a 32-bit one); else"missing".is_floating_deckis true when an ElastoDyn platform DOF flag is true and eitherCompMooringis non-zero (a moored SubDyn substructure is a flexible floater) orCompHydrois non-zero withCompSub0 or absent.probe_platform(fst_path, work_dir, ...)copies the template twice intowork_dir/baseandwork_dir/damped(the template is never edited; a deck without an active HydroDyn file with anAddBLinmatrix raisesValueErrorfirst), configures a parked, zero-wind single linearization att = 0withLinearizationSettings.offshore(hydro_radiation_mode=0, hydro_excitation_mode=0), setsCompAero,CompServoandCompInflowto 0 where present andTMax = max(0.1, 2 DT)(DTtaken as 0.01 when absent), addsprobe_damping(default 1e6 N s/m, 1e9 N m s/rad) to the second copy’sAddBLin, runs both, and passes the two state matrices toplatform_probe_from_jacobians. That function takesM^-1[:, v_j] = -(A_damped - A_base)[:, v_j] / b_jfor each platform velocity statev_j, computes left and right eigenvectors ofA_base, and for each DOF picks the oscillatory mode with the largestRe((w^H M^-1[:, v_j]) u[v_j] / (w^H u)), summed over the mode’s cluster of repeated eigenvalues (within1e-6 |lambda|) as-Re trace((W^H U)^-1 W^H dA U)withdA = -M^-1[:, v_j] e_{v_j}^T(the per-mode value of a repeated root depends on the arbitrary eigenvector basis, the cluster sum does not). For that sensitivitys,effective_mass = 1 / (2 s),natural_period = 2 pi / |lambda|,damping_ratio = -Re lambda / |lambda|andmode_frequency = Im lambda / (2 pi)(Hz) are returned per DOF in aPlatformProbe(withprobe_damping,n_statesand theslowest_periodproperty; a DOF that no oscillatory mode responds to is omitted), whosesettle_damping(fraction)isSettlePlatformDamping.from_critical_fraction.PlatformProbeErrorreports a failed run or one that wrote no.linfile, a.linwithoutA, state matrices whose shape does not match the state descriptions, no free ElastoDyn platform DOF, or a probe damping that damped no oscillatory platform mode.plan_floating_settlecombines both into aFloatingSettlePlan(controller,floating,platform_damping,damping_source"user"/"probe"/"none",probe,damping_fraction,notes;prescribed_rotorisnot controller.available;to_dict()is JSON-ready): a givencontroller_libraryis checked bycheck_controller_library(status"user", or"not_used"when ServoDyn runs no controller library; a missing file raisesFileNotFoundErrorand one that does not loadSteadyStateError); a given damping is used as is; otherwise a floating deck withauto_dampingis probed and sized atdamping_fraction(default 0.5; a non-positive or non-finite value raisesValueError) of critical, and a probe that raisesPlatformProbeError,OpenFastError(a timed-out or unstartable run),ValueError,OSError,FastInputErrororLinAlgErrorleaves the damping off with a note.run_operating_sweepcalls it once forsettle=True(probe directoryoutput_dir/probe; optionssettle_auto_dampingandcontroller_search_paths), raisesSteadyStateErrorbefore any settle when the library is missing and no curve or CSV was given, passesprescribed_rotorto every settle (seeded from the curve), linearizes an unconverged prescribed-rotor settle of a rotating point whenCalcSteadyis enabled (it is still listed insettle_failureswithlinearized=True; a parked point must settle), and returns the plan asOperatingSweepResult.floating_plan.- State-space radiation fits (
vane.run.radiation_fit) read_wamit_radiation(path, *, water_density, length_scale=1.0)reads a single-body WAMIT.1file:PER = 0rows areA(inf),PER < 0rows (zero frequency) are ignored, other rows are atomega = 2 pi / PER; values are scaled byrho L^k(added mass) andrho omega L^k(damping),k= 3, 4, 5 for two translations, mixed, two rotations. It raisesRadiationFitError(aValueError) for a DOF index outside 1 to 6, noPER = 0rows, fewer than four finite frequencies, a malformed row, or a non-positive or non-finitewater_densityorlength_scale.fit_radiation_state_spacefits every diagonal entry ofK = B + j omega (A - A(inf))with a non-zero peak and every off-diagonal entry whose peak is at leastcoupling_threshold(0.05) of the geometric mean of the two diagonal peaks, by vector fitting (iterations= 20 pole relocations, unstable poles reflected) with the final residues constrained toK(0) = 0, raising the order two states at a time up tomax_states_per_entry(20) until the relative error||K_fit - K|| / ||K||is at mosttolerance(0.05), else keeping the best. TheRadiationStateSpacegroups each entry’s real modal realization by input DOF (states_per_dof) with outputC = -c(the radiation force).write_radiation_state_spacewrites HydroDyn’s.sslayout (header, six enabled DOFs, state count, states per DOF, rows ofA,B,C) through an atomic replace.ensure_radiation_state_space(fst_path, *, tolerance=0.05, overwrite=False, confine_to=None)returnsNonewhen HydroDyn is off,PotMod != 1, or<PotFile>.ssexists andoverwriteis false; otherwise it fits<PotFile>.1(density from a positiveWtrDensof the primary deck, HydroDyn or SeaState; length fromWAMITULEN), writes the file only when every entry is within 0.2 (RadiationFitRecord.accepted), logs a warning either way (the provenance of a written file, or the rejection of a fit above 0.2), and returns aRadiationFitRecord(to_dict()is JSON-ready). More than one WAMIT body (NBody != 1), no usablePotFile, a missing.1, no positiveWtrDens, or a.sstarget outsideconfine_to(checked before the existence check and the fit) raiseRadiationFitError.preview_radiation_state_space(fst_path, *, tolerance=0.05)performs the same fit without writing anything (an existing.ssis ignored;writtenisFalse). Wave-excitation (.ssexctn) fits are not generated.
Known invalid uses
VANE’s claims are deliberately narrow. Each entry point does exactly what is stated above and no more; the following are the misreadings to avoid, per scientific claim. The Limitations page covers the modal-extraction assumptions in more depth; this list focuses on the system-identification and estimator surface.
Modal extraction / Campbell / labels. VANE faithfully recovers the modal content of the linearization it is given; it does not validate whether that linearization is physically correct. A comparison against published natural frequencies (
compare_to_reference) is a plausibility check, not a pass/fail validation — published values come from other tools/model versions and answer a different question (see Validation).QoI residue map.
build_qoi_mapis modal-residue screening, not a validated fatigue or design-load ranking; a large residue flags a mode worth examining, not a confirmed load contribution.Observability and sensor placement. Observability is a structural property (rank / Gramian conditioning), not an accuracy guarantee: a fully observable load can still be poorly estimated under model error or noise. Greedy
recommend_sensorsis not globally optimal, andload_reconstructabilityreports the observable subspace fraction and an a-priori variance — not field accuracy.Estimator assessment / load reconstruction bound. The reconstruction-error standard deviation is model-conditional: it holds under an accurate linear model, the correct
Qd/R, Gaussian stationary noise, calibrated sensors, and detectability. It is a bound under those assumptions, not a guarantee on a real, nonlinear, drifting turbine.NIS consistency.
filter_consistencymeasures consistency against the assumed model and noise. A consistent run is necessary, not sufficient, for field validity, and a run on simulated measurements from the linear model is not a substitute for measured-data or nonlinear time-domain validation.LPV scheduling.
schedule_model/run_scheduled_filterinterpolation is valid only where the family’s modes are tracked and phase-aligned. Through a low-tracking-MAC crossing it warns but still blends the bracketing models — it does not fall back to a nearest member, and the blend may not correspond to a single physical mode. Only outside the operating-point range does it clamp to the nearest member (an extrapolation, flagged as such). Heed the per-step confidence and extrapolation flag rather than assuming a safe fallback.Reduced models / export. A
ModalReduction/ ROM reproduces the retained modes in their frequency band, not the full-order response;balanced_truncationrequires an asymptotically stable system (ValueErrorotherwise), and its a-priori error bound holds only for that system.
Exceptions
LinFileFormatError(vane.io, aValueErrorsubclass): malformed or legacy.lininput.FstFileErrorandFastInputError(vane.io,ValueErrorsubclasses): a malformed OpenFAST input file, or a keyword that cannot be found or edited.DeckValidationError(vane.run, aValueErrorsubclass, with the subclassDeckPathLengthError),DeckReferenceErrorandRadiationFitError(ValueErrorsubclasses): a deck that cannot be linearized as configured, a referenced file that cannot be followed, or radiation data that cannot be read or fitted.OpenFastError(vane.run, aRuntimeErrorsubclass, with the subclassOpenFastTimeoutError): the executable is missing, fails or times out.SteadyStateError(aRuntimeErrorsubclass, with the subclassSweepSettleError, which carries the complete sweep result),SweepCaseErrorandPlatformProbeError(RuntimeErrorsubclasses): settle, strict-sweep and platform-probe failures.ValueError: invalid arguments or incoherent inputs to an analysis function, including out-of-range mode, state or channel indices.TypeError: an argument of the wrong type, for example a non-numericmin_known_participation.FileNotFoundError: a missing input file, or a directory without full-system.linfiles passed todiscover_operating_points.IndexError:PipelineResult.state_spacewith an out-of-range operating-point index.
Stability
The public API documented in API reference is stable. VANE follows semantic versioning: a breaking change to the documented public API is made only with a new major version and is recorded in the Changelog. Names that start with an underscore are private.