API reference
The reference is generated from the NumPy-style docstrings in the source. Every public module is listed once, grouped by subpackage. Modules and functions whose names start with an underscore are private and are not shown.
Each subpackage __init__ re-exports the principal public names of its modules, so
from vane.modal import compute_modes and
from vane.modal.eigensolver import compute_modes refer to the same object.
vane.run re-exports every public name of its modules. Elsewhere, a helper or
constant that a subpackage does not re-export is imported from its defining module
(for example from vane.modal.averaging import azimuth_mean). The entries below are
listed under the defining module.
Package root
VANE — Visualization and Automated Natural-frequency Extraction.
Automated OpenFAST linearization post-processing, physics-aware mode identification, and state-space export for digital twin development of fixed-bottom and floating offshore wind turbines.
The package root re-exports the most common entry points, so they can be imported
as from vane import ...: ModalPipeline,
PipelineResult, run_study(),
discover_operating_points(), StudyResult,
Provenance, and the extension protocols
Tracker and Labeler. Each is documented
in its own module below.
Pipeline
High-level modal-analysis pipeline.
ModalPipeline runs the full workflow end to end: for each operating point it
applies the MBC3 transform, solves for modes, then links the modes across
operating points, assembles the Campbell diagram, and flags resonance crossings.
The PipelineResult exposes every intermediate product and convenience
exports to a state-space system, so a complete analysis is a few lines of code.
- DEFAULT_PARAMETER_RTOL = 0.0001
Default relative tolerance below which two operating points’ sweep-axis values are treated as the same point (a plateau), scaled by the largest-magnitude value on the axis. It sits well above the round-off that separates nominally equal trimmed/settled values (
.linheaders carry a handful of significant digits, and azimuth averages of the same schedule differ only in the trailing digits) and well below any physically meaningful sweep step (typically 1e-2 or more of the axis magnitude).
- class ModalPipeline(*, frequency_weight=0.5, mac_threshold=0.5, harmonics=(1, 3, 6, 9), tracking_strategy='single_path', branch_review=False, resolve=False, tracker=None, heavily_damped_threshold=0.99)[source]
Bases:
objectEnd-to-end modal analysis from linearization files to a Campbell diagram.
- Parameters:
- run(operating_points, *, parameter_name='rotor_speed_rpm', parameter_rtol=0.0001)[source]
Run the full pipeline over a set of operating points.
- Parameters:
operating_points (Sequence[Sequence[LinFile]]) – One entry per operating point, each a sequence of linearization files spanning that point’s azimuth sweep.
parameter_name (str, optional) – The operating parameter to plot against:
"rotor_speed_rpm"(the default) or"wind_speed". Above rated the trimmed rotor speed is constant, so a sweep that extends past rated must use"wind_speed".parameter_rtol (float, optional) – Relative tolerance, scaled by the largest-magnitude parameter value, below which two operating points count as the same point on the sweep axis. Such a plateau is rejected because ordering by nearly equal values is arbitrary and would link non-adjacent operating points during tracking. Default
DEFAULT_PARAMETER_RTOL; must be a finite number in[0, 1).
- Returns:
PipelineResult – Every intermediate product of the analysis. Operating points are ordered by ascending operating parameter regardless of input order, so tracking and resonance detection see a monotonic sweep.
- Raises:
ValueError – If
operating_pointsis empty,parameter_nameis unknown,parameter_rtolis invalid, or the operating-parameter values are non-finite or not distinct (beyondparameter_rtol) across points.- Return type:
- class PipelineResult(mbc_results, solutions, identification, campbell, resonances, parameter_name)[source]
Bases:
objectOutputs of a full modal-analysis run.
- Parameters:
mbc_results (list[MBCResult]) – The MBC transform result for each operating point.
solutions (list[ModalSolution]) – The modal solution for each operating point.
identification (IdentificationResult) – The cross-operating-point mode tracks.
campbell (CampbellDiagram) – The assembled Campbell diagram.
resonances (list[ResonanceCrossing]) – Detected resonance crossings (empty for non-rotor-speed runs).
parameter_name (str) – Name of the operating parameter used.
- solutions: list[ModalSolution]
- identification: IdentificationResult
- campbell: CampbellDiagram
- resonances: list[ResonanceCrossing]
- state_space(operating_point=0)[source]
Export the state-space system at one operating point.
- Parameters:
operating_point (int, optional) – Index of the operating point to export (default the first).
- Returns:
StateSpace – The continuous-time system from that operating point’s MBC result.
- Raises:
IndexError – If
operating_pointis negative or out of range.- Return type:
Study and provenance
High-level study orchestration with reproducibility provenance.
A study discovers the linearization files of a campaign, runs the modal pipeline, and
records the provenance of the result — the exact source files (with content
hashes) grouped by operating point, each point’s case name, full azimuth list, and
operating-parameter value, the tuning thresholds, and the library and environment
versions — so a Campbell diagram can be tied back to precisely the inputs and
assumptions that produced it. write_bundle serializes a reproducibility package (a
JSON manifest plus the result tables).
- class DiscoveredOperatingPoint(name, files)[source]
Bases:
Sequence[LinFile]A discovered operating point: its case name and its azimuth files.
Behaves as a sequence of its
LinFileobjects, so it can be passed wherever a sequence of linearization files is expected, while also carrying the<case>grouping name that discovery would otherwise lose.- Parameters:
- class Environment(python_version, platform, dependencies)[source]
Bases:
objectThe software environment a study ran in.
- Parameters:
- class OperatingPointProvenance(name, n_azimuths, azimuths_deg, azimuth_min_deg, azimuth_max_deg, rotor_speed_rpm, wind_speed, parameter_value, source_files)[source]
Bases:
objectProvenance of one operating point.
- Parameters:
name (str) – The
<case>grouping name (empty if the point was not discovered by name).n_azimuths (int) – Number of azimuth samples in the operating point.
azimuths_deg (tuple[float, ...]) – Every sampled azimuth, in degrees.
azimuth_min_deg (float) – Smallest and largest sampled azimuth, in degrees.
azimuth_max_deg (float) – Smallest and largest sampled azimuth, in degrees.
rotor_speed_rpm (float) – Azimuth-averaged rotor speed, in rev/min.
wind_speed (float) – Azimuth-averaged hub-height wind speed, in m/s.
parameter_value (float) – The operating-parameter value used to order the sweep.
source_files (tuple[SourceFile, ...]) – The files of this operating point, each with its content hash.
- source_files: tuple[SourceFile, ...]
- class Provenance(vane_version, created_at, parameter_name, frequency_weight, mac_threshold, tracking_strategy, branch_review, resolve, harmonics, environment, operating_points, n_tracks, n_resonances, n_branch_groups, parameter_rtol=0.0001)[source]
Bases:
objectA reproducibility record for a study run.
- Parameters:
vane_version (str) – Version of the library that produced the result.
created_at (str) – ISO-8601 timestamp of when the provenance was recorded.
parameter_name (str) – Operating parameter the sweep was run against.
frequency_weight (float) – Tracking tuning thresholds used.
mac_threshold (float) – Tracking tuning thresholds used.
tracking_strategy (str) – Cross-operating-point tracking strategy used (
"single_path"or"simultaneous").branch_review (bool) – Whether the branch-review pass was run.
resolve (bool) – Whether the smooth (minimum-curvature) alternative was computed for eligible branch groups.
harmonics (tuple[int, ...]) – Excitation harmonics used for resonance detection.
environment (Environment) – The Python, platform, and dependency versions.
operating_points (tuple[OperatingPointProvenance, ...]) – Per-operating-point provenance, in the (sorted) order of the result.
n_tracks (int) – Number of identified mode tracks, detected resonance crossings, and contested branch groups (the last is
0unlessbranch_reviewwas run).n_resonances (int) – Number of identified mode tracks, detected resonance crossings, and contested branch groups (the last is
0unlessbranch_reviewwas run).n_branch_groups (int) – Number of identified mode tracks, detected resonance crossings, and contested branch groups (the last is
0unlessbranch_reviewwas run).parameter_rtol (float, optional) – Relative plateau tolerance the sweep axis was validated with, so the run can be reproduced with the same acceptance (default
vane.pipeline.DEFAULT_PARAMETER_RTOL).
- environment: Environment
- operating_points: tuple[OperatingPointProvenance, ...]
- property source_files: tuple[SourceFile, ...]
Return every source file across all operating points (flattened).
- class SourceFile(path, sha256)[source]
Bases:
objectA source linearization file and the hash of its contents.
- Parameters:
- class StudyResult(pipeline, provenance)[source]
Bases:
objectThe pipeline result of a study together with its provenance.
- Parameters:
pipeline (PipelineResult) – Every intermediate product of the analysis.
provenance (Provenance) – The reproducibility record.
- pipeline: PipelineResult
- provenance: Provenance
- write_bundle(output_dir, *, report=True)[source]
Write a reproducibility bundle to
output_dir.The bundle contains
provenance.json(the manifest of inputs and assumptions),campbell.csv(the tracked-mode result table), and, unless disabled, a self-containedreport.htmlaudit dashboard.- Parameters:
output_dir (str or pathlib.Path) – Destination directory; created if it does not exist.
report (bool, optional) – Also write the HTML audit report (
report.html). DefaultTrue.
- Return type:
None
- discover_operating_points(directory)[source]
Discover and group a directory’s
.linfiles into operating points.Files are named
<case>.<index>.linby OpenFAST; those sharing a<case>root are one operating point’s azimuth sweep, and the case name is retained on the returnedDiscoveredOperatingPoint. Per-module files written underLinOutMod = True(OpenFAST names them<case>.<index>.<Module>.lin, e.g.case.1.ED.lin,case.1.SEA.lin,case.1.BD1.lin) are always excluded, whatever the alphabetic module token. Files named<case>.<Module>.<index>.lin(e.g.case.ED.1.lin) are excluded when every index of the<case>.<Module>group has a full-system twin<case>.<index>.lin: they describe a single module’s sub-system, not the coupled turbine, so they must not be treated as operating points. A module-like group without a full set of twins is kept, since the case root then genuinely ends in that token (e.g. a separatecase.ED.fstrun). See_module_sidecar_files()for the residual ambiguity, excluded with a warning.- Parameters:
directory (str or pathlib.Path) – Directory containing
.linlinearization files.- Returns:
list[DiscoveredOperatingPoint] – One named operating point per
<case>root, ordered by case.- Raises:
FileNotFoundError – If the directory contains no full-system
.linfiles.- Return type:
- run_study(operating_points, *, parameter_name='rotor_speed_rpm', parameter_rtol=0.0001, frequency_weight=0.5, mac_threshold=0.5, harmonics=(1, 3, 6, 9), tracking_strategy='single_path', branch_review=False, resolve=False, timestamp=None)[source]
Run the modal pipeline and record the provenance of the result.
- Parameters:
operating_points (Sequence[Sequence[LinFile]]) – One azimuth sweep per operating point (e.g. from
discover_operating_points()).parameter_name (str, optional) – Operating parameter to run against (
"rotor_speed_rpm"or"wind_speed"). A sweep that extends past rated, where the trimmed rotor speed is constant, must use"wind_speed".parameter_rtol (float, optional) – Relative plateau tolerance of the sweep axis (see
vane.pipeline.ModalPipeline.run()). Defaultvane.pipeline.DEFAULT_PARAMETER_RTOL.frequency_weight (float, optional) – Tracking tuning thresholds.
mac_threshold (float, optional) – Tracking tuning thresholds.
harmonics (Sequence[int], optional) – Excitation harmonics for resonance detection.
tracking_strategy ({"single_path", "simultaneous"}, optional) – Cross-operating-point tracking strategy; recorded in the provenance. See
vane.modal.identifier.identify_modes().branch_review (bool, optional) – When
True, flag crossing/veering hotspots for audit; recorded in the provenance and rendered in the report. DefaultFalse.resolve (bool, optional) – When
True, also compute the smooth (minimum-curvature) alternative for eligible hotspots (impliesbranch_review). DefaultFalse.timestamp (datetime or None, optional) – Timestamp to record (defaults to the current UTC time); injectable for reproducible tests.
- Returns:
StudyResult – The pipeline result and its provenance.
- Raises:
ValueError – If
parameter_nameis not a known operating parameter,parameter_rtolis invalid, or the operating-parameter values are non-finite or not distinct beyondparameter_rtol(a plateau, such as the constant rotor speed above rated; use"wind_speed"there).- Return type:
Extension protocols
Extension protocols for VANE’s pluggable analysis stages.
VANE’s two model-dependent stages, cross-operating-point tracking and physical
labelling, are each defined here as a typing.Protocol, so an alternative
implementation can be used wherever the built-in is used, without subclassing
anything. These are static (type-checker) contracts; the consumers accept the
protocol type, so a static type checker verifies that a custom implementation fits.
Tracker and Labeler are callable-only protocols and are deliberately not
runtime_checkable: an isinstance check against a callable Protocol only confirms
that the object is callable (not its signature or return type), so it would accept a
non-tracker and fail later. Use static typing, or simply the injection points, for
them.
The built-ins satisfy these contracts structurally:
The consumers accept the protocol type, not the concrete class:
vane.ModalPipeline takes an optional tracker. An LTP/Floquet tracker or
an alternative rule-based labeller can therefore be used without changing the
pipeline.
- class Labeler(*args, **kwargs)[source]
Bases:
ProtocolAssign a physical label to every mode of a solution.
Any callable taking a
ModalSolutionand returning oneModeLabelper mode (in mode order) is a labeler.vane.modal.label_solution()is the built-in. A static (callable) contract: notruntime_checkable(see the module docstring).
- class Tracker(*args, **kwargs)[source]
Bases:
ProtocolLink modes across operating points into labeled tracks.
Any callable taking the per-operating-point solutions (with the optional sweep axis) and returning an
IdentificationResultis a tracker.vane.modal.identify_modes()is the built-in; a Floquet/LTP tracker conforms by presenting the same call. A static (callable) contract: notruntime_checkable(see the module docstring).
Command-line interface
The vane console command wraps the Python API. Run vane --help or
vane <command> --help for the options of each command.
Command-line interface for VANE.
Exposes the high-level analysis as a vane console command so a linearization
sweep can be post-processed without writing Python:
vane modes— the modal solution for a single operating point’s.linfiles;vane campbell— the tracked Campbell diagram across a directory of operating points;vane study— a full provenance-tracked study written as a reproducibility bundle (provenance.json+campbell.csv+ an HTML audit report).
Each command is a thin wrapper over the same public API used from Python
(ModalPipeline, run_study(),
discover_operating_points(), and vane.export), so the CLI adds
convenience without a second code path. Known input errors are reported as a concise
message on stderr with a non-zero exit code rather than a traceback.
- class Parameter(*values)[source]
-
Operating parameter to analyse a sweep against.
- rpm = 'rpm'
- wind = 'wind'
- class Tracking(*values)[source]
-
Cross-operating-point mode-tracking strategy.
- single_path = 'single_path'
- simultaneous = 'simultaneous'
- main(version=False)[source]
Automated OpenFAST linearization post-processing and mode identification.
- modes(lin_files, output=None)[source]
Compute the modal solution for one operating point’s linearization files.
- campbell(directory, output=None, parameter=Parameter.rpm)[source]
Build the tracked Campbell diagram across a directory of operating points.
- study(directory, output_dir, parameter=Parameter.rpm, tracking=Tracking.single_path, branch_review=False, resolve=False, report=True)[source]
Run a provenance-tracked study and write a reproducibility bundle.
- Parameters:
directory (Annotated[Path, <typer.models.ArgumentInfo object at 0x713d1ec6b440>])
output_dir (Annotated[Path, <typer.models.OptionInfo object at 0x713d1ec69700>])
parameter (Annotated[Parameter, <typer.models.OptionInfo object at 0x713d1ec69850>])
tracking (Annotated[Tracking, <typer.models.OptionInfo object at 0x713d1ec69a60>])
branch_review (Annotated[bool, <typer.models.OptionInfo object at 0x713d1ec69bb0>])
resolve (Annotated[bool, <typer.models.OptionInfo object at 0x713d1ec69ca0>])
report (Annotated[bool, <typer.models.OptionInfo object at 0x713d1ec69d60>])
- Return type:
None
I/O
I/O layer — OpenFAST linearization and model file parsers, MBC3 transform.
Reader for OpenFAST linearization (.lin) files.
This module parses the modern linearization output produced by the OpenFAST
ModGlue engine into structured, in-memory objects. The modern format is
identified by the presence of a Derivative Order column in the
operating-point tables; the legacy format (which lacks that column and instead
emits StateRotation / mass-matrix blocks) is intentionally not supported and
raises LinFileFormatError.
The on-disk layout consumed here is, in order:
A header block beginning at
Simulation information:holding scalar metadata (simulation time, rotor speed in rad/s, azimuth in rad, wind speed, and channel counts) and terminated byJacobians included in this file?.Operating-point tables (
Order of continuous states:and the analogous blocks for state derivatives, inputs, and outputs). Each row carries an index, an operating-point value, a rotating-frame flag, a derivative order, and a free-text description.Matrix blocks (
A,B,C,DunderLinearized state matrices:and, when Jacobians are written,dUdu/dUdy/dXdy/JunderJacobian matrices:).
- class LinFile(path, sim_time, rotor_speed, azimuth, wind_speed, jacobians_included, x, xdot, u, y, a, b, c, d, jacobians)[source]
Bases:
objectParsed contents of a single OpenFAST
.linlinearization file.- Parameters:
path (pathlib.Path) – Source file the data was read from.
sim_time (float) – Simulation time of the linearization, in seconds.
rotor_speed (float) – Rotor speed at the operating point, in rad/s (as written by OpenFAST).
azimuth (float) – Rotor azimuth at the operating point, in rad.
wind_speed (float) – Mean hub-height wind speed, in m/s. Zero when InflowWind is absent.
jacobians_included (bool) – Whether the file contains the
Jacobian matricesblock.x (OperatingPointTable) – Operating-point tables for the continuous states, continuous-state derivatives, inputs, and outputs, respectively.
xdot (OperatingPointTable) – Operating-point tables for the continuous states, continuous-state derivatives, inputs, and outputs, respectively.
u (OperatingPointTable) – Operating-point tables for the continuous states, continuous-state derivatives, inputs, and outputs, respectively.
y (OperatingPointTable) – Operating-point tables for the continuous states, continuous-state derivatives, inputs, and outputs, respectively.
a (npt.NDArray[np.float64] or None) – State-space matrices \(A\) (n_x, n_x), \(B\) (n_x, n_u), \(C\) (n_y, n_x), and \(D\) (n_y, n_u).
Nonewhen the corresponding block is absent (e.g.B/Dwith zero inputs).b (npt.NDArray[np.float64] or None) – State-space matrices \(A\) (n_x, n_x), \(B\) (n_x, n_u), \(C\) (n_y, n_x), and \(D\) (n_y, n_u).
Nonewhen the corresponding block is absent (e.g.B/Dwith zero inputs).c (npt.NDArray[np.float64] or None) – State-space matrices \(A\) (n_x, n_x), \(B\) (n_x, n_u), \(C\) (n_y, n_x), and \(D\) (n_y, n_u).
Nonewhen the corresponding block is absent (e.g.B/Dwith zero inputs).d (npt.NDArray[np.float64] or None) – State-space matrices \(A\) (n_x, n_x), \(B\) (n_x, n_u), \(C\) (n_y, n_x), and \(D\) (n_y, n_u).
Nonewhen the corresponding block is absent (e.g.B/Dwith zero inputs).jacobians (dict[str, npt.NDArray[np.float64]]) – Any additional matrices from the
Jacobian matricesblock, keyed by their label (e.g."dUdu","dUdy","dXdy").
- path: Path
- xdot: OperatingPointTable
- exception LinFileFormatError[source]
Bases:
ValueErrorRaised when a
.linfile cannot be parsed as the modern format.
- class NonFiniteLinValue(path, section, variable, token, line_number)[source]
Bases:
objectThe first non-finite number found in a
.linfile.- Parameters:
path (pathlib.Path) – The
.linfile scanned.section (str) – Where the value sits:
"header"for a header scalar,"x"/"xdot"/"u"/"y"for an operating-point table, or the matrix label ("A","B","C","D"or a Jacobian such as"dUdu").variable (str) – What the value belongs to: the header field (
"Rotor Speed"), the channel description of an operating-point row ("AD x3 blade 1, node 59, -"), or the matrix entry with its row and column channels ("A[247, 1] (row: ...; column: ...)", 1-based).token (str) – The value as written in the file (
"NaN","Infinity","*****", …).line_number (int) – The 1-based line of the file holding the value.
- class OperatingPointTable(values, rotating_frame, derivative_order, descriptions)[source]
Bases:
objectOperating-point metadata for one channel group of a
.linfile.A channel group is the set of continuous states, continuous-state derivatives, inputs, or outputs. All array attributes are parallel and share the channel ordering written in the file.
- Parameters:
values (npt.NDArray[np.float64]) – Operating-point value of each channel, shape
(n_channels,). For orientation channels that are written with multiple comma-separated components, only the first component is retained here.rotating_frame (npt.NDArray[np.bool]) – Per-channel flag, shape
(n_channels,).Truemarks a channel expressed in the blade-rotating frame (the hook used by the MBC3 transform).derivative_order (npt.NDArray[np.int]) – Per-channel derivative order (0, 1, or 2), shape
(n_channels,).descriptions (list[str]) – Per-channel free-text description, length
n_channels. Each begins with the originating module prefix (e.g."ED ...").
- values: npt.NDArray[np.float64]
- rotating_frame: npt.NDArray[np.bool_]
- derivative_order: npt.NDArray[np.int_]
- find_nonfinite_lin_value(path)[source]
Return the first non-finite number in a
.linfile, orNone.The scan covers the header scalars, the four operating-point tables (every component of an orientation channel) and every matrix block.
read_lin_file()rejects a non-finite header scalar or operating-point value but accepts a non-finite matrix entry asnan. This scan finds both, and names the channel the value belongs to.- Parameters:
path (str or os.PathLike) – The
.linfile to scan.- Returns:
NonFiniteLinValue or None – The first non-finite value in file order, or
Nonewhen every number in the file is finite.- Raises:
OSError – If the file cannot be read.
- Return type:
NonFiniteLinValue | None
Notes
The file is first searched as a whole for a token that spells a non-finite number (
NaN,Inf,Infinity, a Fortran*overflow run or an Intel/MSVC1.#QNAN-style marker), which is fast even for a file of many megabytes. Only a file with such a token is then walked line by line, so that a non-numeric word (in a channel description, say) is never reported. Lines that do not follow the modern layout are skipped rather than rejected: the scan reports non-finite numbers and does not validate the format.
- read_lin_file(path)[source]
Read and parse a modern OpenFAST
.linfile.- Parameters:
path (str or os.PathLike) – Path to the
.linfile to read.- Returns:
LinFile – The parsed linearization data.
- Raises:
LinFileFormatError – If the file is empty, truncated, in the unsupported legacy format, or otherwise does not conform to the modern ModGlue layout.
- Return type:
Notes
Numeric fields containing Fortran overflow markers (runs of
*) are parsed asnumpy.nanand a warning is logged; the parse does not fail. EmptyB/C/Dblocks (no inputs/outputs) are not written by OpenFAST, so the correspondingLinFileattribute isNone; theAmatrix, however, is required whenever the file declares continuous states.
Readers for OpenFAST model input decks (.fst and ElastoDyn).
These parsers extract only the information VANE needs to drive linearization
post-processing: the linearization configuration block from the primary .fst
file (so the expected set of .lin files is known), the active module
switches and referenced sub-file paths, and the rotor/tower geometry from
ElastoDyn (used to scale mode shapes).
OpenFAST input files use the convention <value(s)> <Keyword> - <description>;
the value(s) precede the keyword on each line. Quoted string values and
multi-value array lines (e.g. LinTimes) are both handled.
- class FastModel(path, comp, files, lin)[source]
Bases:
objectSubset of a primary
.fstmodel relevant to linearization.- Parameters:
path (pathlib.Path) – Path to the primary
.fstfile.comp (dict[str, int]) – Active-module switches keyed by name (e.g.
"CompHydro").files (dict[str, pathlib.Path]) – Referenced sub-file paths resolved relative to
path’s directory, keyed by their keyword (e.g."EDFile"). Entries marked"unused"in the deck are omitted.lin (LinearizationConfig) – Parsed linearization configuration.
- path: Path
- lin: LinearizationConfig
- property is_floating: bool
Return a heuristic for a floating platform (has mooring, no SubDyn).
Notes
This is an approximation based on module switches: floating platforms use a mooring system (
CompMooring >= 1) and no SubDyn substructure (CompSub == 0). Detailed system classification (spar/semi/TLP) is the responsibility of the configuration layer, not this reader.
- exception FstFileError[source]
Bases:
ValueErrorRaised when an OpenFAST input file is malformed or missing a field.
- class LinearizationConfig(linearize, calc_steady, trim_case, trim_tol, trim_gain, n_lin_times, lin_times, lin_inputs, lin_outputs, lin_out_jac, lin_out_mod)[source]
Bases:
objectLinearization settings parsed from a primary
.fstfile.- Parameters:
linearize (bool) – Master linearization switch (
Linearize).calc_steady (bool) – Whether a periodic steady-state trim precedes linearization (
CalcSteady); whenTrue,n_lin_timesazimuth snapshots are produced per operating point.trim_case (int) – Trim target
{1: yaw, 2: torque, 3: pitch}(TrimCase).trim_tol (float) – Rotor-speed convergence tolerance (
TrimTol).trim_gain (float) – Proportional trim gain (
TrimGain).n_lin_times (int) – Number of linearization snapshots (
NLinTimes); the number of.linfiles written per operating point.lin_times (list[float]) – Absolute linearization times in seconds (
LinTimes); meaningful only whencalc_steadyisFalse.lin_inputs (int) – Inputs included
{0: none, 1: standard, 2: all}(LinInputs).lin_outputs (int) – Outputs included
{0: none, 1: from OutList, 2: all}(LinOutputs).lin_out_jac (bool) – Whether full Jacobians are written (
LinOutJac).lin_out_mod (bool) – Whether per-module
.linfiles are written (LinOutMod).
- class TurbineGeometry(num_blades, tip_radius, hub_radius, tower_height, tower_base_height)[source]
Bases:
objectRotor and tower geometry parsed from an ElastoDyn input file.
- Parameters:
num_blades (int) – Number of blades (
NumBl).tip_radius (float) – Rotor apex-to-tip distance in metres (
TipRad).hub_radius (float) – Rotor apex-to-blade-root distance in metres (
HubRad).tower_height (float) – Tower-top height above its reference datum in metres (
TowerHt).tower_base_height (float) – Tower-base height above the same datum in metres (
TowerBsHt);0.0when the ElastoDyn file does not define it.
- fortran_float(value)[source]
Return a deck value read as a Fortran real.
The token is cut at its first comma (a list-directed separator, as in
1.21D+02, TipRad), quotes are stripped, and a double-precision exponent (1.21D+02,5d-3) is read likeE, as Fortran list-directed input does.- Parameters:
value (str) – The value token.
- Returns:
float – The number.
- Raises:
ValueError – If the token is not a number.
- Return type:
- read_elastodyn_geometry(path)[source]
Read rotor and tower geometry from an ElastoDyn input file.
- Parameters:
path (pathlib.Path) – Path to the ElastoDyn
.datfile.- Returns:
TurbineGeometry – The parsed rotor/tower geometry.
- Raises:
FstFileError – If the file cannot be read or a required field is missing.
- Return type:
Notes
NumBl,TipRad,HubRad, andTowerHtare required.TowerBsHtis optional and defaults to0.0(tower base at the datum) when absent, as in ElastoDyn files that omit it.
- read_fst_file(path)[source]
Read a primary OpenFAST
.fstfile.- Parameters:
path (pathlib.Path) – Path to the
.fstfile.- Returns:
FastModel – Parsed module switches, referenced file paths, and linearization config.
- Raises:
FstFileError – If the file cannot be read. Missing module switches and linearization settings are tolerated and fall back to their defaults.
- Return type:
Format-preserving editor for OpenFAST input files.
OpenFAST input decks use the convention <value(s)> <Keyword> - <description>,
where the value(s) precede the keyword on each line. To drive a linearization,
VANE must change a handful of these values — enable Linearize, set
NLinTimes, set the operating point’s RotSpeed and HWindSpeed, and so on
— without disturbing the rest of the deck. This module provides
FastInputFile, a small editor that reads a deck, looks up a keyword,
replaces its value while preserving the keyword spelling, the trailing comment, and
the column layout, and writes the deck back. It is the write-side counterpart of
vane.io.fst_reader.
The editor operates on a single file. A deck spans several files (the primary
.fst references ElastoDyn, InflowWind, ServoDyn, …); FastInputFile.
resolve() returns a referenced sub-file’s path so the caller can open and edit it
the same way.
- exception FastInputError[source]
Bases:
ValueErrorRaised when an OpenFAST input file keyword cannot be found or edited.
- class FastInputFile(path, lines)[source]
Bases:
objectAn editable OpenFAST input deck file.
The file is held as its original lines;
set()rewrites only the value of a single keyword’s line, leaving every other character — including the keyword, the- descriptioncomment, and the surrounding whitespace — intact.- Parameters:
path (pathlib.Path) – Path the deck was read from (used to resolve referenced sub-files and as the default write destination).
lines (list[str]) – The file’s lines, without trailing newlines.
- classmethod read(path)[source]
Read an OpenFAST input file.
- Parameters:
path (str or pathlib.Path) – Path to the input file.
- Returns:
FastInputFile – The editable deck.
- Raises:
FastInputError – If the file cannot be read.
- Return type:
Notes
The file is decoded as UTF-8; a byte that is not UTF-8 (a Windows-1252
°in a comment, say) is kept as is and written back unchanged bywrite(), as OpenFAST itself reads the file byte by byte.
- tokens(keyword)[source]
Return the value tokens preceding
keywordon its line.- Parameters:
keyword (str) – The exact keyword (field name) to locate.
- Returns:
list[str] – The whitespace-delimited value tokens before the keyword.
- Raises:
FastInputError – If the keyword is not present.
- Return type:
- get(keyword)[source]
Return the first value token of
keyword.- Parameters:
keyword (str) – The keyword to read.
- Returns:
str – The first value token (quotes preserved).
- Raises:
FastInputError – If the keyword is not present or has no value.
- Return type:
- set(keyword, value)[source]
Replace the value of
keywordin place.The keyword, its column, and the trailing
- descriptioncomment are preserved; only the leading value field is rewritten. Booleans render asTrue/False; ints and floats usestr; astris written verbatim (so quoted paths and format codes such as'"ES20.11E3"'are the caller’s responsibility).
- set_array(keyword, values)[source]
Replace
keywordwith a space-separated list of values.Used for array fields such as
LinTimes.- Parameters:
- Raises:
FastInputError – If the keyword is not present.
- Return type:
None
- resolve(keyword)[source]
Return the path of a referenced sub-file given its keyword.
The value is treated as a path (quoted with
"or', as Fortran accepts, or bare) relative to this deck’s directory.- Parameters:
keyword (str) – The keyword whose value is a sub-file path (e.g.
"EDFile").- Returns:
pathlib.Path – The resolved sub-file path.
- Raises:
FastInputError – If the keyword is absent or its value is empty/
"unused".- Return type:
- write(path=None)[source]
Write the deck to disk.
- Parameters:
path (str or pathlib.Path or None, optional) – Destination path; defaults to the path the deck was read from. Parent directories are created.
- Returns:
pathlib.Path – The path written to.
- Return type:
Notes
The write is atomic: the text is written to a freshly created, uniquely named temporary file in the same directory and then
Path.replace()-d into place. This avoids leaving a partially written deck on failure and — because the replace swaps the directory entry to a new inode — editing a hard-linked copy never modifies the shared (e.g. template) inode, which lets a sweep hard-link its heavy read-only inputs while still editing the small deck files safely. The temporary file is created exclusively (a unique name), so it never collides with or mutates a pre-existing.tmphard link, and an existing deck’s permission bits are preserved across the replace.On Windows the temporary-file creation and the replace can fail transiently with an access-denied or sharing-violation error while another process (an antivirus scan, the search indexer, or an OpenFAST process still releasing its handles) briefly holds the deck or its directory. The whole write is then retried a few times with a short backoff (see
vane.utils.fs_retry.retry_transient()) before the error is raised.
Multi-blade coordinate (MBC3 / Coleman) transformation.
OpenFAST writes linearized state matrices in a mixed frame in which the blade degrees of freedom rotate with the rotor, making the system matrices azimuth-dependent. The MBC3 transform projects the three per-blade rotating coordinates onto non-rotating multi-blade coordinates (collective, cosine, and sine), and averaging the transformed matrices over one revolution yields the azimuth-invariant linear time-invariant system used for modal analysis.
This module implements the transform for three-bladed rotors directly from the
parsed LinFile objects of a single operating point
(one file per azimuth). Continuous states are reordered into the canonical
[q2, q2dot, q1] layout (second-order displacements, their velocities, then
first-order states) and blade triplets are detected from the channel
descriptions. The transformed A, B, C, and D matrices are
averaged over azimuth.
Notes
At zero rotor speed the transform reduces to a similarity transform of the state matrix and therefore preserves its eigenvalues; this property is used to verify the implementation.
- class MBCResult(avg_a, avg_b, avg_c, avg_d, state_descriptions, ndof2, ndof1, n_blades, performed_transformation, rotor_speed_rpm, wind_speed, azimuths_deg, mbc_coordinates=<factory>, per_azimuth_a=None, output_descriptions=<factory>, input_descriptions=<factory>, output_mbc_coordinates=<factory>, input_mbc_coordinates=<factory>, output_operating_point=None, input_operating_point=None, per_azimuth_b=None, per_azimuth_c=None, per_azimuth_d=None, per_azimuth_output_operating_point=None, per_azimuth_input_operating_point=None)[source]
Bases:
objectResult of an MBC3 transform over one operating point’s azimuth sweep.
- Parameters:
avg_a (npt.NDArray[np.float64] or None) – Azimuth-averaged state-space matrices in the non-rotating frame. Each is
Nonewhen the corresponding input matrix was absent. When the transform is performed, rows/columns follow the reordered[q2, q2dot, q1]state layout; otherwise the original order is kept.avg_b (npt.NDArray[np.float64] or None) – Azimuth-averaged state-space matrices in the non-rotating frame. Each is
Nonewhen the corresponding input matrix was absent. When the transform is performed, rows/columns follow the reordered[q2, q2dot, q1]state layout; otherwise the original order is kept.avg_c (npt.NDArray[np.float64] or None) – Azimuth-averaged state-space matrices in the non-rotating frame. Each is
Nonewhen the corresponding input matrix was absent. When the transform is performed, rows/columns follow the reordered[q2, q2dot, q1]state layout; otherwise the original order is kept.avg_d (npt.NDArray[np.float64] or None) – Azimuth-averaged state-space matrices in the non-rotating frame. Each is
Nonewhen the corresponding input matrix was absent. When the transform is performed, rows/columns follow the reordered[q2, q2dot, q1]state layout; otherwise the original order is kept.state_descriptions (list[str]) – State descriptions in the same order as the rows/columns of
avg_a.ndof2 (int) – Number of second-order degrees of freedom.
ndof1 (int) – Number of first-order states.
n_blades (int) – Number of blades inferred from the rotating triplets (3, or 0 when no rotating triplets were found).
performed_transformation (bool) – Whether the MBC3 transform was applied.
Falsewhen no three-blade triplets were detected, in which case the matrices are plain azimuth averages in the original state order.rotor_speed_rpm (float) – Mean rotor speed over the azimuth sweep, in rev/min.
wind_speed (float) – Mean wind speed over the azimuth sweep, in m/s.
azimuths_deg (npt.NDArray[np.float64]) – Sorted rotor azimuths used, in degrees.
mbc_coordinates (list[str]) – Multi-blade coordinate of each state, aligned with
state_descriptions:"collective","cosine", or"sine"for a transformed blade-triplet state, and""for non-rotating states or when the transform was not performed.output_descriptions (list[str]) – Output channel descriptions aligned with the rows of
avg_c/avg_d(in the transformed output order). Empty when no output matrix is present.input_descriptions (list[str]) – Input channel descriptions aligned with the columns of
avg_b/avg_d(in the transformed input order). Empty when no input matrix is present.output_mbc_coordinates (list[str]) – Multi-blade coordinate of each output (
"collective"/"cosine"/"sine"for a transformed blade-triplet output, else""), aligned withoutput_descriptions. A transformed output row is a multi-blade combination, not the per-blade channel its description names.input_mbc_coordinates (list[str]) – Multi-blade coordinate of each input, aligned with
input_descriptions.output_operating_point (npt.NDArray[np.float64] or None) – Azimuth-averaged output operating point in the transformed output order. This is the baseline that must be subtracted from absolute measurements before a perturbation-state estimator is run.
input_operating_point (npt.NDArray[np.float64] or None) – Azimuth-averaged input operating point in the transformed input order.
per_azimuth_a (list of array or None) – The transformed (non-rotating) state-space matrices at each azimuth, in the same state/input/output order as their averaged counterparts. They are retained only when
retain_per_azimuth=Trueand the corresponding matrix exists. The complete ensemble supports azimuth-periodic input/output simulation; theAstack also underpins Floquet and azimuth-spread analyses.per_azimuth_b (list of array or None) – The transformed (non-rotating) state-space matrices at each azimuth, in the same state/input/output order as their averaged counterparts. They are retained only when
retain_per_azimuth=Trueand the corresponding matrix exists. The complete ensemble supports azimuth-periodic input/output simulation; theAstack also underpins Floquet and azimuth-spread analyses.per_azimuth_c (list of array or None) – The transformed (non-rotating) state-space matrices at each azimuth, in the same state/input/output order as their averaged counterparts. They are retained only when
retain_per_azimuth=Trueand the corresponding matrix exists. The complete ensemble supports azimuth-periodic input/output simulation; theAstack also underpins Floquet and azimuth-spread analyses.per_azimuth_d (list of array or None) – The transformed (non-rotating) state-space matrices at each azimuth, in the same state/input/output order as their averaged counterparts. They are retained only when
retain_per_azimuth=Trueand the corresponding matrix exists. The complete ensemble supports azimuth-periodic input/output simulation; theAstack also underpins Floquet and azimuth-spread analyses.per_azimuth_output_operating_point (list of array or None) – Transformed output operating-point vectors at each azimuth, aligned with the output descriptions. These preserve deterministic periodic loads that an azimuth average would otherwise erase.
per_azimuth_input_operating_point (list of array or None) – Transformed input operating-point vectors at each azimuth, aligned with the input descriptions. These preserve deterministic periodic controls.
- azimuths_deg: npt.NDArray[np.float64]
- find_blade_triplets(descriptions, rotating_frame)[source]
Group rotating channels into per-blade triplets by description.
For each rotating channel, every blade-index token is located (prose
blade 1, BeamDynBD_1, blade-coordinate/AeroDynb1/B1,PitchBearing1, or a fallback single digit). A sibling matcher is built in which all blade digits are tied to one captured value, so a triplet is confirmed only when the other rotating channels are identical except for a consistent blade number — node, mode, and span indices stay literal and are never mistaken for the blade index, even when a channel carries more than one blade token. The(internal DOF index = ...)parenthetical, which holds a second blade-numbered token, is removed before matching.- Parameters:
- Returns:
list[tuple[int, …]] – Triplets of indices into
descriptions, each ordered by blade number; channels that do not form a complete blade triplet are omitted.- Return type:
list[Triplet]
- mbc3_transform(lin_files, *, omega_dot=0.0, retain_per_azimuth=False, rotor_speed_drift_rtol=0.01, wind_speed_drift_rtol=0.01)[source]
Apply the MBC3 transform and azimuth-average a set of linearizations.
- Parameters:
lin_files (Sequence[LinFile]) – Linearization files for a single operating point, one per azimuth. They must share the same channel layout; they are sorted by azimuth here.
omega_dot (float, optional) – Rotor angular acceleration in rad/s^2, assumed zero (appropriate for a steady-state periodic operating point).
retain_per_azimuth (bool, optional) – Keep the transformed
A/B/C/Dmatrices at each azimuth for Floquet, azimuth-spread, and periodic input/output analyses. Off by default to save memory.rotor_speed_drift_rtol (float, optional) – Relative rotor-speed spread allowed across the azimuth sweep before the files are rejected as not one operating point. Defaults to a tight 1%; raise it to accept a looser definition of “the same operating point”.
wind_speed_drift_rtol (float, optional) – Relative wind-speed spread allowed across the azimuth sweep. Defaults to 1% so wind-dependent Jacobian changes cannot be interpreted as rotor-periodic content. Both drift tolerances must be finite and nonnegative.
- Warns:
For a *rotating* operating point with blade triplets, a single/too-few or
non-uniform azimuth sweep is logged as a warning (the MBC azimuth average only)
removes the rotor’s periodic content when it spans a full revolution
near-uniformly, so a sparse sweep yields a misleading averaged model. A rotating
state that forms no blade triplet (e.g. of a one- or two-bladed rotor) is logged
as a warning too, since it stays in the rotating frame.
- Returns:
MBCResult – The azimuth-averaged non-rotating matrices and associated metadata.
- Raises:
ValueError – If
lin_filesis empty, the files have inconsistent state counts, a file is missing theAmatrix, two files share an azimuth, a drift tolerance is invalid, rotor/wind speed drifts across the sweep (the files are not a single operating point), or a rotating state names a blade numbered above three (the transform is the three-blade one).- Return type:
Keep or remove linearization states by description pattern or DOF class.
select_states() returns a copy of a linearized model with a subset of its
continuous states: the matching rows and columns of A, the rows of B and the
columns of C follow the selection, and D is unchanged. It works on a raw
LinFile (before the MBC3 transform) and on an
MBCResult (after it).
A second-order degree of freedom has two states, its displacement and its velocity
(First time derivative of ...). The two are always selected together: a pattern or
class that matches either one keeps, or removes, both, so the [q2, q2dot, q1]
layout the MBC3 transform and the eigensolver rely on stays consistent.
- select_states(model: LinFile, *, keep: str | Sequence[str] | None = None, drop: str | Sequence[str] | None = None, keep_categories: Iterable[DofCategory | str] | None = None, drop_categories: Iterable[DofCategory | str] | None = None) LinFile[source]
- select_states(model: MBCResult, *, keep: str | Sequence[str] | None = None, drop: str | Sequence[str] | None = None, keep_categories: Iterable[DofCategory | str] | None = None, drop_categories: Iterable[DofCategory | str] | None = None) MBCResult
Return a copy of a linearized model with a subset of its states.
- Parameters:
model (LinFile or MBCResult) – A parsed
.linfile (before the MBC3 transform) or the result ofmbc3_transform()(after it).keep (str or Sequence[str] or None, optional) – Regular expressions (case-insensitive, matched anywhere in the state description). When
keeporkeep_categoriesis given, only the states that match one of them are retained.drop (str or Sequence[str] or None, optional) – Regular expressions of states to remove. Removal is applied after
keep.keep_categories (Iterable[DofCategory or str] or None, optional) – Degree-of-freedom classes (
DofCategorymembers or their string values, as returned byclassify_dof()) to retain or remove.drop_categories (Iterable[DofCategory or str] or None, optional) – Degree-of-freedom classes (
DofCategorymembers or their string values, as returned byclassify_dof()) to retain or remove.
- Returns:
LinFile or MBCResult – A new object of the same type.
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, whose dimensions involve the states, are not carried over.- Raises:
ValueError – If no selector is given, a pattern or category matches no state, a pattern is not a valid regular expression, a category is unknown, or the selection removes every state.
TypeError – If
modelis neither aLinFilenor anMBCResult, or a pattern is not a string.
- Return type:
Notes
The displacement and velocity of a second-order degree of freedom are selected together: if either description matches, both states are kept (or removed). Use the function before the MBC3 transform to remove a module’s states from every azimuth, or after it to reduce the averaged model; in both cases the result can be passed on unchanged (for example to
mbc3_transform()ormodes_from_mbc()).Examples
>>> reduced = select_states(lin, drop=r"\bHD\b") >>> tower = select_states( ... result, keep_categories=["tower_fore_aft_1", "tower_side_side_1"] ... )
Guarded compatibility corrections for known OpenFAST linearization defects.
The corrections in this module are deliberately signature based. They do not infer physics from arbitrary non-zero columns: a correction is applied only when the complete matrix/name pattern produced by the corresponding OpenFAST implementation is present.
- class HydroDynWaveDofSignCorrection(wave_input_index, states_per_dof, dof_signs, corrected_rows)[source]
Bases:
objectAudit record for an explicit
.ssexctnDOF convention correction.- Parameters:
- class HydroDynWaveInputRemap(source_wave_index, source_carrier_index, wave_b_l2_before, carrier_b_l2_before, carrier_nonzero_rows, state_space_signature)[source]
Bases:
objectAudit record for the OpenFAST HydroDyn state-space wave-input correction.
source_wave_indexis the zero column carrying the intended wave label;source_carrier_indexis the column into which OpenFAST placed the state-space excitationBvector. The corrected model swaps only these two descriptions, leaving every numeric matrix and operating-point value unchanged.- Parameters:
- class StateSpaceExcitationMetadata(format_header, wave_heading_deg, time_offset_s, state_count, states_per_dof)[source]
Bases:
objectHeader metadata required to drive a HydroDyn
.ssexctnrealization.HydroDyn evaluates the incident wave at
t + time_offset_s. The offset is part of the fitted realization and is therefore a required runtime input convention, not a tunable phase correction.- Parameters:
- apply_hydrodyn_wave_dof_signs(result, *, states_per_dof, dof_signs, nonzero_atol=0.0)[source]
Apply an explicit wave-excitation DOF sign convention to retained
Bdata.This operation is intentionally not automatic. Callers must derive
dof_signsfrom an independent hydrodynamic convention audit (for example the phase of the WAMIT.3coefficients versus the preview-corrected.ssexctntransfer). The function enforces that the averaged and every retained azimuth-resolved wave column have the same contiguous, HydroDyn-only state block matching the six-DOF state counts in the fitted file.
- read_state_space_excitation_metadata(path)[source]
Read and validate the five-line header of a HydroDyn
.ssexctnfile.- Parameters:
- Return type:
- remap_hydrodyn_state_space_wave_input(result, *, zero_atol=0.0, nonzero_atol=0.0)[source]
Return a copy with OpenFAST’s HydroDyn wave-input label defect corrected.
OpenFAST’s HydroDyn state-space excitation Jacobian places
SS_Exctn%Binm%Jac%Nu(the final HydroDyn extended-input column), while the module registers wave elevation before current speed, shear, and propagation direction. The result is a non-zero wave-excitation column labelledpropagation directionand a zero column labelledwave elevation.This function fails closed unless all four HydroDyn extended inputs occur exactly once, the wave/current/shear columns are zero, the direction-labelled column is non-zero only in HydroDyn states, both affected direct-feedthrough columns are zero, and both operating-point inputs are zero. On success it swaps the two descriptions and their MBC-coordinate labels; no matrix coefficient is changed.
- Parameters:
- Returns:
corrected, audit (tuple[MBCResult, HydroDynWaveInputRemap]) – Corrected copy and an immutable provenance record.
- Raises:
ValueError – If the complete known-defect signature is not present.
- Return type:
WAMIT diagonal-QTF parsing and Newman’s slow-drift reconstruction.
The implementation follows OpenFAST HydroDyn’s NewmanApp_InitCalc formulation:
the positive and negative parts of each real diagonal difference-frequency QTF are
square-root filtered separately, transformed into analytic time histories, squared,
and subtracted. It is intentionally limited to a single wave heading so unsupported
directional assumptions fail closed.
- class DiagonalDifferenceQTF(angular_frequency_rad_s, values_si, wave_heading_deg)[source]
Bases:
objectDimensional diagonal difference-frequency QTF for one wave heading.
- Parameters:
angular_frequency_rad_s (npt.NDArray[np.float64]) – Strictly increasing angular-frequency grid, shape
(n_frequency,).values_si (npt.NDArray[np.float64]) – Real diagonal QTF values, shape
(n_frequency, 6). Translational columns have units N/m² and rotational columns N-m/m².wave_heading_deg (float) – Common incident-wave heading of both QTF wave components.
- angular_frequency_rad_s: npt.NDArray[np.float64]
- values_si: npt.NDArray[np.float64]
- newman_slow_drift_load(wave_elevation_m, *, sample_interval_s, qtf, wave_low_cutoff_rad_s=0.0, wave_high_cutoff_rad_s=inf)[source]
Evaluate Newman’s mean- and slow-drift six-axis load on a periodic record.
- Parameters:
wave_elevation_m (array-like) – Equally sampled, periodic incident-wave elevation. The record should span the sea-state realization’s period; using a cropped non-periodic record causes spectral-edge error and must be disclosed by the caller.
sample_interval_s (float) – Wave-elevation sample interval in seconds.
qtf (DiagonalDifferenceQTF) – Dimensional real diagonal QTF.
wave_low_cutoff_rad_s (float, optional) – Incident-wave frequency cutoffs. These match SeaState’s
WvLowCOffandWvHiCOffrather than the resulting difference-frequency cutoffs.wave_high_cutoff_rad_s (float, optional) – Incident-wave frequency cutoffs. These match SeaState’s
WvLowCOffandWvHiCOffrather than the resulting difference-frequency cutoffs.
- Returns:
npt.NDArray[np.float64] – Six-axis force/moment history, shape
(n_time, 6).- Return type:
npt.NDArray[np.float64]
- read_wamit_diagonal_difference_qtf(path, *, water_density_kg_m3, gravity_m_s2, characteristic_length_m, wave_heading_deg=0.0, tolerance=1e-08, imaginary_tolerance=1e-05)[source]
Read and dimensionalize one heading’s diagonal from a WAMIT
.12dfile.WAMIT stores period pairs, direction pairs, a one-based load component, and the complex QTF. Newman’s approximation uses only rows where both periods and both headings are equal, and discards the negligible imaginary diagonal component. HydroDyn dimensionalizes forces by
rho*g*Land moments byrho*g*L**2; this function applies the same convention.tolerancecontrols equality checks on periods/headings. The separateimaginary_toleranceadmits only the small diagonal roundoff that HydroDyn explicitly discards before applying Newman’s approximation.- Raises:
ValueError – If physical scales are invalid, the requested diagonal is incomplete, or its nominally real values have a material imaginary component.
- Parameters:
- Return type:
Configuration
Configuration — DOF/output index mapping and system type definitions.
Classification of OpenFAST degree-of-freedom descriptions.
OpenFAST embeds an internal degree-of-freedom (DOF) token in each ElastoDyn state
description (e.g. DOF_TFA1 for the first tower fore-aft mode or DOF_BF(1,1)
for the first flapwise mode of blade 1). These tokens are stable across versions and
are used here to map a state description onto a physical DOF category, the
originating module, the blade index for rotating DOFs, and whether the state is a
velocity. This classification underpins physics-aware mode labeling.
BeamDyn states carry no such token: they are finite-element nodal displacements and
rotations, e.g. BD_1 finite element node 2 (number of elements = 1; element order =
5) translational displacement in X, m and its velocity twin (BD_1 First time
derivative of finite element node 2 ..., also spelled with the derivative marker
before the BD_<n> prefix). BD_<n> names the blade, and the node index runs
from the root (node 1, a constraint that is never a state) to the tip.
In the linearization the nodal states are perturbations expressed in the BeamDyn
blade reference frame frozen at the operating point: the blade-root orientation
that ElastoDyn passes to BeamDyn, i.e. ElastoDyn’s pitched blade coordinate system.
Z runs along the pitch axis toward the tip, X is normal to the rotor plane at
zero pitch (the flapwise direction) and Y lies in the rotor plane at zero pitch
(the edgewise direction). Because the frame follows the blade pitch, X/Y stay
flapwise/edgewise at any pitch angle; only the local structural twist (largest at the
root) mixes them, so the split is the pitch-axis flap/edge split ElastoDyn uses for
its own tip deflections. A translation along X and a rotation about Y are
therefore DofCategory.BLADE_FLAP, a translation along Y and a rotation
about X DofCategory.BLADE_EDGE, a rotation about Z
DofCategory.BLADE_TORSION, and a translation along Z
DofCategory.BLADE_AXIAL.
The root boundary condition of the linearized beam is the root-motion input measured
from the same frozen frame, so a nodal state is the total nodal perturbation: it
includes the rigid motion the blade root carries from the rest of the structure (a
rotor-azimuth perturbation appears as a rigid rotation of every node), and the nodal
velocity states are total velocities as well. These categories therefore say which
blade direction a state measures, not which bending mode it belongs to: the mode
order (1st/2nd/3rd) is resolved from the mode shape along the span by
vane.modal.labeler.label_solution(), which also separates near-rigid blade
motion from elastic bending.
- class DofCategory(*values)[source]
Bases:
EnumPhysical category of a degree of freedom (string-valued, 3.10 compatible).
- PLATFORM_SURGE = 'platform_surge'
- PLATFORM_SWAY = 'platform_sway'
- PLATFORM_HEAVE = 'platform_heave'
- PLATFORM_ROLL = 'platform_roll'
- PLATFORM_PITCH = 'platform_pitch'
- PLATFORM_YAW = 'platform_yaw'
- TOWER_FORE_AFT_1 = 'tower_fore_aft_1'
- TOWER_SIDE_SIDE_1 = 'tower_side_side_1'
- TOWER_FORE_AFT_2 = 'tower_fore_aft_2'
- TOWER_SIDE_SIDE_2 = 'tower_side_side_2'
- NACELLE_YAW = 'nacelle_yaw'
- GENERATOR_AZIMUTH = 'generator_azimuth'
- DRIVETRAIN_TORSION = 'drivetrain_torsion'
- ROTOR_FURL = 'rotor_furl'
- TAIL_FURL = 'tail_furl'
- TEETER = 'teeter'
- BLADE_FLAP_1 = 'blade_flap_1'
- BLADE_FLAP_2 = 'blade_flap_2'
- BLADE_EDGE_1 = 'blade_edge_1'
- BLADE_PITCH = 'blade_pitch'
- UNKNOWN = 'unknown'
- BLADE_FLAP = 'blade_flap'
- BLADE_EDGE = 'blade_edge'
- BLADE_TORSION = 'blade_torsion'
- BLADE_AXIAL = 'blade_axial'
- class DofInfo(category, module, blade, is_velocity, node=None, component=None)[source]
Bases:
objectClassification of a single state description.
- Parameters:
category (DofCategory) – The physical DOF category (
UNKNOWNif not recognized).module (str) – The originating module prefix (e.g.
"ED"or"BD_1"), or an empty string if absent.blade (int or None) – The 1-based blade index for rotating blade DOFs, otherwise
None.is_velocity (bool) – Whether the state is the time derivative (velocity) of a DOF.
node (int or None, optional) – For a BeamDyn nodal state, the 1-based finite-element node index (node 1 is the blade root, so state nodes start at 2); otherwise
None.component (str or None, optional) – For a BeamDyn nodal state, the measured component in the blade reference frame:
"TX","TY","TZ"for a translation alongX,Y,Zand"RX","RY","RZ"for a rotation about it; otherwiseNone.
- category: DofCategory
- classify_dof(description)[source]
Classify a state description into a
DofInfo.- Parameters:
description (str) – A state description from a linearization file (e.g.
"ED 1st tower fore-aft ... (internal DOF index = DOF_TFA1), m").- Returns:
DofInfo – The classification;
categoryisDofCategory.UNKNOWNwhen the description carries neither a known ElastoDynDOF_token nor a recognized BeamDyn nodal quantity. For a BeamDyn state,moduleandbladecome from itsBD_<n>prefix (bladeisNonefor a bareBDprefix) andnode/componentlocate the state on the blade.- Return type:
Classification of OpenFAST linearization output channels.
The output equation y = C x + D u of a linearization maps the system state and
inputs onto OpenFAST output channels. Many of those channels are loads — mooring
fairlead and anchor tensions, tower-base and tower-top (yaw-bearing) moments and
forces, blade-root moments, shaft torque — which are the quantities of interest for
structural load and health monitoring. This module classifies an output channel
description (e.g. "MAP T[1], [N]" or "ED YawBrMzp, (kN-m)") into a physical
OutputCategory, recording the originating module, the load type, the
component index (mooring line or blade number), the measured quantity, the axis, and
the unit. It is the output-side counterpart of vane.config.classify_dof().
The catalog covers the common MAP++/MoorDyn mooring, ElastoDyn, and BeamDyn load
channels; unrecognized channels are returned as OutputCategory.UNKNOWN and
are simply not treated as loads.
- LOAD_CATEGORIES: frozenset[OutputCategory] = frozenset({OutputCategory.BLADE_ROOT_FORCE, OutputCategory.BLADE_ROOT_MOMENT, OutputCategory.MOORING_ANCHOR_TENSION, OutputCategory.MOORING_FAIRLEAD_TENSION, OutputCategory.MOORING_LINE_TENSION, OutputCategory.SHAFT_TORQUE, OutputCategory.TOWER_BASE_FORCE, OutputCategory.TOWER_BASE_MOMENT, OutputCategory.TOWER_TOP_FORCE, OutputCategory.TOWER_TOP_MOMENT})
Categories that represent a structural load (a force, moment, or tension).
- class OutputCategory(*values)[source]
Bases:
EnumPhysical category of a linearization output channel (string-valued).
- MOORING_FAIRLEAD_TENSION = 'mooring_fairlead_tension'
- MOORING_ANCHOR_TENSION = 'mooring_anchor_tension'
- MOORING_LINE_TENSION = 'mooring_line_tension'
- TOWER_BASE_MOMENT = 'tower_base_moment'
- TOWER_BASE_FORCE = 'tower_base_force'
- TOWER_TOP_MOMENT = 'tower_top_moment'
- TOWER_TOP_FORCE = 'tower_top_force'
- BLADE_ROOT_MOMENT = 'blade_root_moment'
- BLADE_ROOT_FORCE = 'blade_root_force'
- SHAFT_TORQUE = 'shaft_torque'
- PLATFORM_MOTION = 'platform_motion'
- DEFLECTION = 'deflection'
- WAVE_ELEVATION = 'wave_elevation'
- UNKNOWN = 'unknown'
- class OutputInfo(category, module, is_load, component, quantity, axis, unit)[source]
Bases:
objectClassification of a single output channel description.
- Parameters:
category (OutputCategory) – The physical category (
UNKNOWNif not recognized).module (str) – The originating module prefix (e.g.
"ED","MAP","MD"), or an empty string if absent.is_load (bool) – Whether the channel is a structural load (a member of
LOAD_CATEGORIES).component (int or None) – The 1-based mooring-line or blade index for component-indexed channels, otherwise
None.quantity (str) – The measured quantity (
"tension","force","moment","displacement","rotation","elevation", or"").axis (str or None) – The axis label (
"x","y","z") for directional forces and moments, otherwiseNone.unit (str) – The unit token parsed from the trailing
(...)or[...]of the description (e.g."kN-m","N","m"), or an empty string.
- category: OutputCategory
- classify_output(description)[source]
Classify a linearization output channel description.
- Parameters:
description (str) – An output channel description from a
.linfile (e.g."MAP T[1], [N]"or"ED YawBrMzp, (kN-m)").- Returns:
OutputInfo – The classification;
categoryisOutputCategory.UNKNOWNwhen the channel name matches no known pattern.- Return type:
Reference geometry of a wind-turbine system for mode-shape visualisation.
A linearization file carries modal degrees of freedom — generalized coordinates such as platform surge, tower fore-aft bending, or a blade flap mode — not nodal positions. To draw a mode on the physical structure, a reference (undeformed) geometry is required: where the tower, nacelle, rotor, floating platform, and mooring lines sit in space. This module provides that geometry as a small, parametric, fully auditable model.
The geometry is a schematic assembled from a turbine’s principal dimensions
(tower height, blade length, hub overhang, platform draft and radius, mooring
fairlead and anchor positions). It is deliberately not a high-fidelity hull mesh:
every node position follows from the documented dimensions, so the drawing can be
audited line by line. The deformation that animates it (see
vane.viz.modeshape_fowt) is driven by the actual complex mode shape, so
the relative motion across the structure is physically meaningful even though the
hull outline is stylised.
Coordinate convention
A right-handed inertial frame with origin at the intersection of the undeflected
tower centreline and the still-water level (SWL), matching OpenFAST’s platform
reference point: +x downwind, +y to the left looking downwind, +z up.
Onshore systems place the origin at the tower base instead; the convention is
otherwise identical.
- class FowtGeometry(system_type, members, reference_point, tower_top, tower_length, n_blades, characteristic_length, metadata=<factory>)[source]
Bases:
objectReference geometry of a complete wind-turbine system.
- Parameters:
system_type (SystemType) – The support-structure class this geometry represents.
members (list[Member]) – All polyline components (tower, blades, nacelle, hub, platform members, mooring lines).
reference_point (npt.NDArray[np.float64]) – The platform reference point about which the rigid-body platform degrees of freedom (surge/sway/heave/roll/pitch/yaw) act, shape
(3,).tower_top (npt.NDArray[np.float64]) – Position of the tower top (yaw bearing), shape
(3,); the origin of the tower-top rotation that carries the rotor-nacelle assembly.tower_length (float) – Undeformed tower length, used to convert a bending slope into a tower-top rotation.
n_blades (int) – Number of rotor blades.
characteristic_length (float) – A representative length (typically the hub height) used to scale the animated deflection to a visible, dimensionless fraction.
Notes
The geometry is immutable; presets are produced by the builder functions in this module.
membersis stored as a tuple-backed list to keep the dataclass hashable-friendly while remaining ergonomic to iterate.- system_type: SystemType
- reference_point: NDArray[float64]
- tower_top: NDArray[float64]
- class Member(name, kind, nodes, span_fraction=None, blade_index=None, flap_dir=None, edge_dir=None, rides_tower_top=False)[source]
Bases:
objectOne polyline component of the reference structure.
A member is an ordered list of node positions forming a polyline (the tower centreline, one blade, a pontoon, a mooring line, …). The optional fields tell the deformation engine how the member responds to the modal degrees of freedom; members that only ride the platform rigid-body motion (a pontoon, a column) need none of them.
- Parameters:
name (str) – Human-readable label, shown in the figure legend.
kind (str) – Component class, one of
"tower","blade","platform","mooring","nacelle", or"hub". Governs the deformation rule.nodes (npt.NDArray[np.float64]) – Undeformed node positions, shape
(n_nodes, 3).span_fraction (npt.NDArray[np.float64] or None) – For
"tower"and"blade"members, the normalized arc length \(\xi \in [0, 1]\) of each node from the clamped end (tower base / blade root), shape(n_nodes,). The bending shape functions are evaluated at these values.blade_index (int or None) – For
"blade"members, the 1-based blade number; its reference azimuth is2*pi*(blade_index - 1)/n_blades, measured as a right-hand rotation about the downwind shaft axis from vertical (the OpenFAST rotor azimuth used by the MBC3 transform), so blade 2 of a three-bladed rotor points to-y.Noneotherwise.flap_dir (npt.NDArray[np.float64] or None) – For
"blade"members, the unit flapwise (out-of-rotor-plane) and edgewise (in-rotor-plane) directions in the inertial frame, shape(3,). Blade bending displaces nodes along these. They follow the ElastoDyn blade-frame signs: positive flap points nominally downwind and positive edge points against the direction of rotation (toward the trailing edge).edge_dir (npt.NDArray[np.float64] or None) – For
"blade"members, the unit flapwise (out-of-rotor-plane) and edgewise (in-rotor-plane) directions in the inertial frame, shape(3,). Blade bending displaces nodes along these. They follow the ElastoDyn blade-frame signs: positive flap points nominally downwind and positive edge points against the direction of rotation (toward the trailing edge).rides_tower_top (bool) – Whether the member is rigidly attached to the tower top (nacelle, hub, and blades) and therefore inherits the tower-top translation and bending rotation in addition to the platform rigid-body motion.
- nodes: NDArray[float64]
- class SystemType(*values)[source]
Bases:
EnumSupport structure of a wind-turbine system (string-valued, 3.10 safe).
- FIXED = 'fixed'
- SPAR = 'spar'
- SEMI = 'semi'
- TLP = 'tlp'
- LAND = 'land'
- MONOPILE = 'monopile'
- JACKET = 'jacket'
- default_geometry(system_type)[source]
Return a default reference geometry for a system type.
- Parameters:
system_type (SystemType) – The support-structure class.
TLPuses the tension-leg schematic oftlp_geometry().- Returns:
FowtGeometry – A representative geometry built with the preset’s default dimensions.
- Return type:
- jacket_geometry(*, hub_height=110.0, tower_base_z=20.0, blade_length=60.0, base_radius=15.0, top_radius=7.0, water_depth=50.0, embedment=0.0, n_legs=4, n_bays=4, n_blades=3, shaft_tilt_deg=-5.0, precone_deg=-2.5, overhang=7.0)[source]
Build a bottom-fixed lattice-jacket reference geometry.
n_legsbattered legs run from the mudline up to the transition piece, tied byn_bayslevels of X-braces; a transition-piece deck carries the tower. The legs taper frombase_radiusat the seabed totop_radiusat the transition piece.- Returns:
FowtGeometry – The assembled jacket geometry.
- Parameters:
- Return type:
- monopile_geometry(*, hub_height=90.0, tower_base_z=20.0, blade_length=60.0, monopile_radius=3.0, water_depth=20.0, embedment=25.0, mudline_z=0.0, n_blades=3, shaft_tilt_deg=-5.0, precone_deg=-2.5, overhang=7.0)[source]
Build a bottom-fixed monopile reference geometry (no mooring lines).
The monopile runs from below the mudline up to the tower base above the waterline. There are no mooring lines; the platform reference point sits at the mudline, so platform degrees of freedom (if any) act there.
- Parameters:
mudline_z (float, optional) – Seabed elevation. The default
0.0keeps the plain bottom-fixed convention (seabed at the reference plane, no water column). Offshore presets pass-water_depthso the still-water plane atz = 0sits above the seabed and the pile crosses the waterline.hub_height (float)
tower_base_z (float)
blade_length (float)
monopile_radius (float)
water_depth (float)
embedment (float)
n_blades (int)
shaft_tilt_deg (float)
precone_deg (float)
overhang (float)
- Returns:
FowtGeometry – The assembled monopile geometry.
- Return type:
- onshore_geometry(*, hub_height=90.0, blade_length=60.0, foundation_radius=8.0, n_blades=3, shaft_tilt_deg=-5.0, precone_deg=-2.5, overhang=7.0)[source]
Build a land-based (onshore) reference geometry: tower + rotor on the ground.
The tower rises from the ground plane (
z = 0) to the hub; there is no substructure, water, or mooring. A foundation ring marks the base.
- semisub_geometry(*, hub_height=90.0, tower_base_z=10.0, blade_length=60.0, column_radius=6.0, offset_radius=28.0, draft=20.0, freeboard=10.0, fairlead_radius=28.0, fairlead_depth=14.0, anchor_radius=420.0, water_depth=200.0, n_blades=3, shaft_tilt_deg=-5.0, precone_deg=-2.5, overhang=7.0)[source]
Build a three-column semi-submersible FOWT reference geometry.
A central column carries the tower; three outer columns on an offset radius are joined to it by pontoons and braces, and three catenary mooring lines run from the outer-column fairleads to seabed anchors. Defaults are representative of a 5-MW-class semi-submersible and may be overridden per turbine.
- Parameters:
hub_height (float, optional) – Hub height above SWL.
tower_base_z (float, optional) – Tower-base height above SWL (top of the central column / freeboard).
blade_length (float, optional) – Blade length from root to tip.
column_radius (float, optional) – Radius of each buoyancy column.
offset_radius (float, optional) – Radial distance of the outer columns from the centreline.
draft (float, optional) – Column draft below SWL.
freeboard (float, optional) – Column height above SWL.
fairlead_radius (float, optional) – Radius of the mooring fairleads.
fairlead_depth (float, optional) – Depth of the fairleads below SWL.
anchor_radius (float, optional) – Radius of the seabed anchors.
water_depth (float, optional) – Water depth (anchors sit on the seabed at
-water_depth).n_blades (int, optional) – Number of rotor blades.
shaft_tilt_deg (float, optional) – Shaft tilt angle in degrees, with the OpenFAST
ShftTiltsign (positive raises the downwind shaft end). The default-5tilts the upwind rotor nose-up, as for the NREL 5-MW turbine.precone_deg (float, optional) – Blade precone angle in degrees, with the OpenFAST
PreConesign (positive cones the blades downwind). The default-2.5cones the blades upwind, away from the tower.overhang (float, optional) – Upwind shaft overhang of the hub from the tower top.
- Returns:
FowtGeometry – The assembled semi-submersible geometry.
- Return type:
- spar_geometry(*, hub_height=90.0, tower_base_z=10.0, blade_length=60.0, spar_radius=4.7, draft=120.0, fairlead_radius=5.2, fairlead_depth=70.0, anchor_radius=850.0, water_depth=320.0, n_blades=3, shaft_tilt_deg=-5.0, precone_deg=-2.5, overhang=7.0)[source]
Build a single-column spar-buoy FOWT reference geometry (OC3-Hywind class).
A deep slender cylinder provides buoyancy and ballast stability; three catenary mooring lines spread from fairleads near the waterline. Parameters mirror
semisub_geometry().- Returns:
FowtGeometry – The assembled spar geometry.
- Parameters:
- Return type:
- tlp_geometry(*, hub_height=90.0, tower_base_z=10.0, blade_length=60.0, column_radius=6.0, pontoon_radius=20.0, draft=20.0, freeboard=10.0, water_depth=200.0, n_tendons=3, n_blades=3, shaft_tilt_deg=-5.0, precone_deg=-2.5, overhang=7.0)[source]
Build a tension-leg-platform (TLP) reference geometry.
A central buoyancy column carries the tower; spoke pontoons reach out to the tendon-attachment corners, and vertical taut tendons run straight down from each corner to a seabed anchor directly below it — the defining TLP feature, unlike a semi-submersible’s spread catenary mooring.
- Returns:
FowtGeometry – The assembled TLP geometry.
- Parameters:
- Return type:
Modal analysis
Modal analysis — eigensolver, MAC, participation factors, mode identification.
Eigenvalue analysis of the azimuth-averaged state matrix.
Given the non-rotating (MBC-averaged) state matrix \(A\) in the canonical
[q2, q2dot, q1] layout, an eigenvalue analysis yields the full-system modes:
their natural frequencies, damping ratios, and (complex) mode shapes.
Each under-damped mode appears as a complex-conjugate eigenvalue pair; only the representative with positive imaginary part is retained. For an eigenvalue \(\lambda\), the modal quantities are
so the natural and damped frequencies in hertz are
\(\omega_n / 2\pi\) and \(\omega_d / 2\pi\). The velocity (q2dot)
rows are dropped from the returned mode shapes, leaving the second-order
displacements and first-order states; the full eigenvectors are also retained.
Notes
OpenFAST exposes no full-system mass or stiffness matrix, so modal frequencies are obtained directly from \(A\) and vary with operating point.
Numerically split rigid-body pairs. A degree of freedom without restoring
stiffness – the free rotor-azimuth (generator) DOF of a variable-speed turbine –
contributes a zero eigenvalue (the azimuth integrator) next to a small real one (the
aerodynamic damping of the rotor speed). Where the damping is small, the
finite-difference noise in the azimuth column of \(A\) is enough to merge the two
into a complex pair of negligible frequency (periods of tens of minutes) instead of
two real eigenvalues. Such a pair is not an oscillation, and reporting it as one makes
a spurious mode appear at isolated operating points. A complex pair is therefore
classified as a split rigid-body pair – dropped from the oscillatory modes and
counted in n_rigid_body_modes – when both
its natural frequency is below
rigid_body_frequency_hz(defaultRIGID_BODY_FREQUENCY_HZ, a period of 333 s: well below every restoring-force mode of a wind turbine, including the 100-200 s surge and sway of moored floating platforms), andits imaginary part is not resolved by the data: by first-order perturbation theory a relative error \(\delta\) in \(A\) moves the eigenvalue by up to \(\kappa\,\delta\,\rho(A)\), so a pair with
\[|\operatorname{Im}\lambda| \le \delta\,\kappa(\lambda)\,\rho(A)\]is indistinguishable from two real eigenvalues. \(\kappa\) is evaluated on the diagonally balanced matrix (the problem the eigenvalue solver actually solves, and independent of the state units), \(\rho\) is the spectral radius and \(\delta\) is
split_pair_rtol(defaultSPLIT_PAIR_RTOL).
Both conditions are needed. The first alone would drop a well-resolved slow oscillation of an unusually soft model. The second alone is not enough because the spectral radius of a model with stiff first-order states (MoorDyn line nodes) is large, so well-resolved surge and sway modes of such a model can fall below the bound; the frequency floor keeps them.
- RIGID_BODY_FREQUENCY_HZ = 0.003
Natural frequency (Hz) below which a numerically split pair may be rigid-body.
A period of 333 s. Moored floating platforms have their slowest restoring-force modes (surge, sway) at periods of roughly 100-200 s (0.005-0.01 Hz), so no physical oscillation of a wind turbine is this slow.
- SPLIT_PAIR_RTOL = 0.0001
Relative accuracy \(\delta\) of the state matrix assumed for split pairs.
OpenFAST computes its Jacobians by finite differences with perturbations of order \(10^{-3}\), and the MBC transform averages them over azimuth, so the entries of \(A\) are not reliable to better than about this relative level. On the reference-turbine sweeps the split rigid-body pairs sit 10-200 times inside the resulting bound, and the platform modes of floating models without MoorDyn 2-50 times outside it.
- class ModalSolution(eigenvalues, natural_frequencies_hz, damping_ratios, damped_frequencies_hz, mode_shapes, full_eigenvectors, n_rigid_body_modes, dof_descriptions, condition_numbers=<factory>, is_degenerate=<factory>, n_unstable=0, n_overdamped=0, dof_mbc_coordinates=<factory>, full_left_eigenvectors=<factory>, state_descriptions=<factory>, n_split_rigid_body_pairs=0, rotor_speed_rpm=nan)[source]
Bases:
objectModal properties extracted from a state matrix.
All per-mode arrays share the same ordering (ascending natural frequency when the solver sorts). Only under-damped modes (positive imaginary eigenvalue) are represented.
- Parameters:
eigenvalues (npt.NDArray[np.complex128]) – Retained eigenvalues \(\lambda\), shape
(n_modes,).natural_frequencies_hz (npt.NDArray[np.float64]) – Natural frequencies \(|\lambda| / 2\pi\), shape
(n_modes,).damping_ratios (npt.NDArray[np.float64]) – Damping ratios \(-\operatorname{Re}\lambda / |\lambda|\).
damped_frequencies_hz (npt.NDArray[np.float64]) – Damped frequencies \(\operatorname{Im}\lambda / 2\pi\).
mode_shapes (npt.NDArray[np.complex128]) – Reduced complex mode shapes (second-order displacements then first-order states), shape
(ndof2 + ndof1, n_modes), phase-normalized so each mode’s dominant component is real and positive.full_eigenvectors (npt.NDArray[np.complex128]) – Full eigenvectors over all states, shape
(n_states, n_modes), sharing the same phase normalization.full_left_eigenvectors (npt.NDArray[np.complex128]) – Full left eigenvectors over all states, shape
(n_states, n_modes). Together withfull_eigenvectorsthese support scale-invariant biorthogonal participation and first-order eigenvalue sensitivity.n_rigid_body_modes (int) – Number of degrees of freedom without an under-damped representative (rigid-body or over-damped modes, including split rigid-body pairs).
dof_descriptions (list[str]) – Descriptions of the degrees of freedom that index the rows of
mode_shapes(the second-order displacements then first-order states). Empty when descriptions were not supplied to the solver.state_descriptions (list[str]) – Descriptions of every state indexing the full left and right eigenvectors. Empty when descriptions were not supplied to the solver.
dof_mbc_coordinates (list[str]) – Multi-blade coordinate (
"collective"/"cosine"/"sine"/"") of eachmode_shapesrow, aligned withdof_descriptions. Empty when not supplied.condition_numbers (npt.NDArray[np.float64]) – Per-mode eigenvalue condition number \(\kappa = 1 / |y^H x|\) (the reciprocal cosine of the angle between the left and right eigenvectors), shape
(n_modes,).1is perfectly conditioned; large values flag eigenvalues that are numerically sensitive (highly non-normal system).is_degenerate (npt.NDArray[np.bool]) – Per-mode flag marking modes whose eigenvalue is (near-)repeated by another retained mode, so the individual mode shape is defined only up to a rotation within the shared invariant subspace.
n_unstable (int) – Number of eigenvalues with positive real part (unstable). Non-zero is a red flag for the operating point. Split rigid-body pairs are not counted: the sign of their real part is not resolved by the data.
n_overdamped (int) – Number of non-oscillatory stable real eigenvalues (over-damped modes), which are not returned as oscillatory modes.
n_split_rigid_body_pairs (int) – Number of complex-conjugate pairs classified as numerically split rigid-body eigenvalues (see the module notes) and therefore left out of the oscillatory modes; each also counts in
n_rigid_body_modes.rotor_speed_rpm (float) – Rotor speed (rev/min) of the operating point the state matrix was linearized at, or
nanwhen unknown (e.g. a matrix given directly tocompute_modes()).modes_from_mbc()copies it from the MBC result; the labeller uses it to read a blade whirl mode’s damping in the rotating frame.
- eigenvalues: npt.NDArray[np.complex128]
- natural_frequencies_hz: npt.NDArray[np.float64]
- damping_ratios: npt.NDArray[np.float64]
- damped_frequencies_hz: npt.NDArray[np.float64]
- mode_shapes: npt.NDArray[np.complex128]
- full_eigenvectors: npt.NDArray[np.complex128]
- condition_numbers: npt.NDArray[np.float64]
- is_degenerate: npt.NDArray[np.bool_]
- full_left_eigenvectors: npt.NDArray[np.complex128]
- property natural_frequencies_rad_s: npt.NDArray[np.float64]
Return natural frequencies in rad/s.
- compute_modes(a, ndof2, ndof1, *, sort_by_frequency=True, descriptions=None, mbc_coordinates=None, rigid_body_frequency_hz=0.003, split_pair_rtol=0.0001)[source]
Compute modal properties from a state matrix.
- Parameters:
a (npt.NDArray[np.float64]) – Square state matrix in
[q2, q2dot, q1]order, shape(2 * ndof2 + ndof1, 2 * ndof2 + ndof1).ndof2 (int) – Number of second-order degrees of freedom (the
q2block size).ndof1 (int) – Number of first-order states (the
q1block size).sort_by_frequency (bool, optional) – Sort the returned modes by ascending natural frequency (the default).
descriptions (list[str] or None, optional) – Descriptions of all
2 * ndof2 + ndof1states, in the matrix order. When given, the subset that indexes the mode-shape rows is stored on the result asdof_descriptions. A list of only thendof2 + ndof1mode-shape rows (displacements then first-order states) is also accepted and stored as is;state_descriptionsis then left empty.mbc_coordinates (list[str] or None, optional) – Multi-blade coordinate of all states (or of the mode-shape rows), in the matrix order; the mode-shape subset is stored on the result as
dof_mbc_coordinates.rigid_body_frequency_hz (float, optional) – Natural frequency below which a complex pair whose imaginary part is not resolved by the data is classified as a split rigid-body pair (see the module notes). Default
RIGID_BODY_FREQUENCY_HZ;0.0keeps every complex pair as an oscillatory mode.split_pair_rtol (float, optional) – Assumed relative accuracy of
ain the split-pair test. DefaultSPLIT_PAIR_RTOL;0.0keeps every complex pair.
- Returns:
ModalSolution – The modal frequencies, damping ratios, and mode shapes.
- Raises:
ValueError – If
ais not square, ifndof2/ndof1are not non-negative integers, if its size does not match2 * ndof2 + ndof1, ifdescriptionsormbc_coordinateshas neither one entry per state nor one per mode-shape row, or ifrigid_body_frequency_hzorsplit_pair_rtolis negative or non-finite.- Return type:
- modes_from_mbc(result, *, rigid_body_frequency_hz=0.003, split_pair_rtol=0.0001)[source]
Compute modal properties from an MBC transform result.
- Parameters:
result (MBCResult) – Output of
vane.io.mbc_transform.mbc3_transform(); itsavg_amust be present.rigid_body_frequency_hz (float, optional) – Frequency floor of the split rigid-body pair test; see
compute_modes().split_pair_rtol (float, optional) – Relative accuracy of the split rigid-body pair test; see
compute_modes().
- Returns:
ModalSolution – The modal solution for the averaged state matrix.
- Raises:
ValueError – If
result.avg_aisNone.- Return type:
Modal Assurance Criterion and mode-to-mode matching.
The Modal Assurance Criterion (MAC) measures the correlation between two mode
shapes; a value of 1 indicates perfect correlation and 0 indicates orthogonality
[1]. For complex (state-space) modes the pole-weighted MACXP criterion [2] is
also provided. match_modes performs a one-to-one Hungarian assignment over a MAC
(optionally frequency-penalized) matrix between two operating points; this is a
pairwise building block. Cross-operating-point tracking over a whole sweep is done
separately by vane.modal.identify_modes(), which extracts globally optimal
continuity paths rather than chaining pairwise matches.
References
- compute_mac(phi_ref, phi_test)[source]
Compute the Modal Assurance Criterion (MAC) matrix.
- Parameters:
phi_ref (npt.NDArray[np.complex128]) – Reference mode-shape matrix, shape
(n_dof, n_modes_ref).phi_test (npt.NDArray[np.complex128]) – Test mode-shape matrix, shape
(n_dof, n_modes_test).
- Returns:
npt.NDArray[np.float64] – MAC matrix of shape
(n_modes_ref, n_modes_test)with values in[0, 1].- Raises:
ValueError – If the two mode-shape matrices have incompatible first dimensions.
- Return type:
npt.NDArray[np.float64]
Notes
The MAC between modes \(i\) and \(j\) is
\[\text{MAC}_{ij} = \frac{|\phi_i^H \phi_j|^2} {(\phi_i^H \phi_i)(\phi_j^H \phi_j)}.\]
- compute_macxp(phi_ref, lambda_ref, phi_test, lambda_test)[source]
Compute the pole-weighted MACXP matrix for complex modes.
MACXP augments the MAC with the transpose product and weights both by the modal poles, making it more discriminating than MAC for complex state-space modes.
- Parameters:
phi_ref (npt.NDArray[np.complex128]) – Reference and test mode-shape matrices, shape
(n_dof, n_modes).phi_test (npt.NDArray[np.complex128]) – Reference and test mode-shape matrices, shape
(n_dof, n_modes).lambda_ref (npt.NDArray[np.complex128]) – Corresponding modal poles (eigenvalues), shape
(n_modes,).lambda_test (npt.NDArray[np.complex128]) – Corresponding modal poles (eigenvalues), shape
(n_modes,).
- Returns:
npt.NDArray[np.float64] – MACXP matrix of shape
(n_modes_ref, n_modes_test); a mode correlated with itself yields 1.- Raises:
ValueError – If shapes are inconsistent between mode matrices and pole vectors.
- Return type:
npt.NDArray[np.float64]
Notes
The criterion is defined for damped modes (poles with a non-zero real part); an undamped pole makes its self-correlation term infinite, so the whole row (reference pole) or column (test pole) of every undamped pole is returned as NaN rather than raising – including the cross entries against damped modes, which would otherwise collapse to a misleading
0.0.
- match_modes(mac, ref_frequencies=None, test_frequencies=None, frequency_weight=0.0, *, forbidden=None)[source]
Match reference modes to test modes by maximum-weight assignment.
A Hungarian (optimal) assignment maximizes the total correlation between the two mode sets. When frequencies are supplied, the MAC of each pair is scaled by
1 - frequency_weight * |df| / spanso that frequency proximity breaks ties between similar shapes (the strategy used for Campbell-diagram tracking).- Parameters:
mac (npt.NDArray[np.float64]) – Correlation matrix, shape
(n_ref, n_test)(e.g. fromcompute_mac()).ref_frequencies (npt.NDArray[np.float64] or None, optional) – Per-mode natural frequencies for the reference and test sets. Both must be given to apply the frequency penalty.
test_frequencies (npt.NDArray[np.float64] or None, optional) – Per-mode natural frequencies for the reference and test sets. Both must be given to apply the frequency penalty.
frequency_weight (float, optional) – Strength of the frequency penalty in
[0, 1];0(default) uses MAC alone.forbidden (npt.NDArray[np.bool] or None, optional) – Pairs that must not be matched, shape
(n_ref, n_test). This is a hard constraint: whenever an assignment avoiding every forbidden pair exists, it is chosen.
- Returns:
list[tuple[int, int]] –
(ref_index, test_index)pairs, one per matched mode (min(n_ref, n_test)pairs), ordered by reference index.- Raises:
ValueError – If
forbiddendoes not match the shape ofmac, or no assignment avoids every forbidden pair.- Return type:
Per-degree-of-freedom participation in each mode.
OpenFAST exposes no full-system mass matrix, so a classical mass-normalized modal participation factor is unavailable. Instead, participation is quantified from the right eigenvector: the magnitude of each state’s component in a mode measures how strongly that degree of freedom participates. Each mode is normalized so its dominant component has magnitude one, and a sign is assigned from the phase of each component relative to the dominant one (components more than 90 degrees out of phase are negated), giving the signed contribution used for physical mode identification.
- class ParticipationResult(magnitude, signed_magnitude, phase_deg, dominant_state)[source]
Bases:
objectPer-state participation in each mode.
- Parameters:
magnitude (npt.NDArray[np.float64]) – Normalized participation magnitude, shape
(n_dof, n_modes); each column’s maximum is 1.signed_magnitude (npt.NDArray[np.float64]) –
magnitudewith the sign of components more than 90 degrees out of phase with the dominant component flipped negative.phase_deg (npt.NDArray[np.float64]) – Phase of each component in degrees, shape
(n_dof, n_modes).dominant_state (npt.NDArray[np.int]) – Index of the largest-magnitude state in each mode, shape
(n_modes,).
- magnitude: npt.NDArray[np.float64]
- signed_magnitude: npt.NDArray[np.float64]
- phase_deg: npt.NDArray[np.float64]
- dominant_state: npt.NDArray[np.int_]
- compute_participation(mode_shapes, scale_factors=None)[source]
Compute per-state participation from complex mode shapes.
- Parameters:
mode_shapes (npt.NDArray[np.complex128]) – Complex mode-shape matrix, shape
(n_dof, n_modes).scale_factors (npt.NDArray[np.float64] or None, optional) – Per-DOF multipliers applied before normalization (e.g.
1/blade_lengthfor translational blade DOFs) so heterogeneous DOFs are comparable. WhenNone, no scaling is applied.
- Returns:
ParticipationResult – Normalized magnitudes, signed magnitudes, phases, and dominant states.
- Raises:
ValueError – If
scale_factorslength does not match the number of DOFs.- Return type:
- participation_from_modes(solution, scale_factors=None)[source]
Compute participation from a
ModalSolution’s mode shapes.- Parameters:
solution (ModalSolution) – Modal solution whose
mode_shapesare analysed.scale_factors (npt.NDArray[np.float64] or None, optional) – Per-DOF multipliers, as in
compute_participation().
- Returns:
ParticipationResult – The participation result for the solution’s mode shapes.
- Return type:
- participation_scale_factors(descriptions, *, blade_length, tower_length)[source]
Derive per-DOF participation scale factors from the state descriptions.
A turbine’s states are a mix of units: blade and tower deflections are in metres while the other degrees of freedom are rotations (radians) or generalized coordinates. Comparing their raw eigenvector components over-weights whichever has the larger numeric units. Scaling each translational deflection by its characteristic length — blade flap/edge by the flexible blade length, tower fore-aft/side-side by the flexible tower length — turns it into a dimensionless tip-deflection ratio comparable to the rest. A BeamDyn blade’s nodal translations (along the blade-frame
X,YandZaxes, in metres) are scaled by the blade length too, so they become spanwise deflection ratios comparable with the nodal rotations, which are in radians. It builds thescale_factorsarray thatcompute_participation()accepts (replacing a hand-made one), by classifying each state withclassify_dof().Every other category — the platform rigid-body DOFs (surge/sway/heave and roll/pitch/yaw), blade pitch, drivetrain torsion, generator azimuth, furl/teeter, BeamDyn nodal rotations, and any unrecognized state — is left at
1.0(no length scaling). Velocity states scale by the same factor as their position DOF, since they share its classification.Only the raw-magnitude participation (
compute_participation()) depends on these factors. The physical labels ofvane.modal.label_solution()use the biorthogonal participation, which is invariant to any rescaling of the states and needs none.- Parameters:
descriptions (Sequence[str]) – The state descriptions, one per mode-shape row and in the same order (e.g. from the linearization file / MBC result), as classified by
classify_dof().blade_length (float) – Flexible blade length in metres (e.g.
TurbineGeometry.blade_lengthfromread_elastodyn_geometry(),TipRad - HubRad, which the ElastoDyn file defines for BeamDyn blades too). Must be finite and positive.tower_length (float) – Flexible tower length in metres (e.g.
TurbineGeometry.tower_length). Must be finite and positive.
- Returns:
npt.NDArray[np.float64] – The per-DOF scale factors, shape
(len(descriptions),):1/blade_lengthfor ElastoDyn blade flap/edge deflections and BeamDyn nodal translations,1/tower_lengthfor tower deflections,1.0otherwise. Pass it straight tocompute_participation()/participation_from_modes().- Raises:
ValueError – If
blade_lengthortower_lengthis not finite and positive.- Return type:
npt.NDArray[np.float64]
Scale-invariant left–right modal participation.
Raw right-eigenvector amplitudes depend on the units chosen for the states. A metre, a radian, and an internal aerodynamic or hydrodynamic state therefore cannot be compared by magnitude alone. For a simple eigenvalue, the element-wise left–right product
is invariant to any nonsingular diagonal rescaling of the states. Its complex components sum to one. This module reports those factors and a non-negative share formed by normalising their magnitudes, both per state and aggregated into physical subsystems.
Individual factors are not physically unique for a repeated eigenvalue because the
eigenvectors may rotate within its invariant subspace. The caller should therefore
use is_degenerate as an acceptance check
before interpreting an individual degenerate mode.
- class BiorthogonalParticipation(complex_factors, magnitude_fractions, closure_error, valid, subsystem_names, subsystem_fractions, state_subsystems)[source]
Bases:
objectLeft–right participation factors for every retained oscillatory mode.
- Parameters:
complex_factors (npt.NDArray[np.complex128]) – Complex state factors, shape
(n_states, n_modes). Each valid column sums to one.magnitude_fractions (npt.NDArray[np.float64]) – Absolute factors normalised to sum to one per valid mode.
closure_error (npt.NDArray[np.float64]) – Absolute difference between each complex column sum and one.
valid (npt.NDArray[np.bool]) – Whether the left–right inner product is numerically resolvable. Invalid columns contain
NaNand must not be interpreted.subsystem_names (tuple[str, ...]) – Ordered subsystem labels indexing
subsystem_fractions.subsystem_fractions (npt.NDArray[np.float64]) – Magnitude fractions summed by subsystem, shape
(n_subsystems, n_modes).state_subsystems (tuple[str, ...]) – Subsystem assigned to each state.
- complex_factors: npt.NDArray[np.complex128]
- magnitude_fractions: npt.NDArray[np.float64]
- closure_error: npt.NDArray[np.float64]
- valid: npt.NDArray[np.bool_]
- subsystem_fractions: npt.NDArray[np.float64]
- biorthogonal_participation(solution, *, subsystem_labels=None)[source]
Compute scale-invariant participation from a modal solution.
- Parameters:
solution (ModalSolution) – Modal solution retaining matching full left and right eigenvectors.
subsystem_labels (sequence of str or None, optional) – Explicit subsystem label for every state. When omitted, labels are inferred from
solution.state_descriptions.
- Returns:
BiorthogonalParticipation – State and subsystem participation factors.
- Raises:
ValueError – If left eigenvectors are unavailable, shapes disagree, or supplied labels do not match the state count.
- Return type:
- classify_state_subsystem(description)[source]
Classify an OpenFAST state description into an engineering subsystem.
Cross-operating-point mode identification (global tracking).
Modes computed independently at successive operating points (rotor speeds or wind speeds) are linked into tracks — the lines of a Campbell diagram. Both tracking strategies score a candidate link by the same affinity (the modal-assurance criterion tempered by a frequency-continuity penalty) and forbid links below the MAC threshold; they differ only in how that affinity is resolved into tracks:
"single_path" (the default) extracts each mode line as the globally optimal
continuity path through the whole sweep: it repeatedly finds the single
best-continuity path over the not-yet-assigned modes (a deterministic dynamic
program over the affinity) and removes it, until every mode is assigned. It reads
the strongest line off first.
"simultaneous" instead resolves each operating-point transition at once: all
modes on one side are matched to all modes on the next by a single maximum-weight
assignment (the Hungarian algorithm, with both sides free to stay unmatched). No
early high-affinity link can then claim a mode that a different pairing would use to
reach a higher overall total — the failure mode of a per-line extraction in dense
spectra or at near-degenerate crossings, where an earlier high-scoring path can
strand a node a later path would have preferred.
Both are deterministic — no random restarts or preset cluster counts — and each track carries a confidence (its weakest link’s correlation) and an ambiguity flag (raised where a mode had two near-equally correlated continuations, i.e. a crossing or veering hotspot). Every track is given a physical label.
- DEFAULT_RIGID_BODY_FREQUENCY_HZ = 0.001
Natural frequency (Hz) below which a mode is rigid-body and is not tracked.
A period of 1000 s is far longer than any restrained mode of a turbine: the softest moored floating-platform surge and sway modes sit near 0.005-0.01 Hz. Below it lie only modes without restoring stiffness, such as the free rotor azimuth.
- LABEL_MISMATCH_PENALTY = 0.5
Factor applied to the affinity of a link between two differently labelled modes.
When both ends of a candidate link carry an unambiguous physical label (see
vane.modal.labeler.ModeLabel.is_ambiguous) and the labels differ – another family, mode number or multi-blade type – the link’s affinity is multiplied by this factor. A confident label change between neighbouring operating points is strong evidence that the link joins two different physical modes; ambiguous (hybrid) modes, as at a veering, are not penalised.
- NEAR_COINCIDENT_RTOL = 0.01
Relative eigenvalue distance within which modes of one point are near-coincident.
Two modes whose eigenvalues satisfy \(|\lambda_i - \lambda_j| \le \mathrm{rtol} \max(|\lambda_i|, |\lambda_j|)\) are mixed by any coupling of comparable size, so their individual eigenvectors are an arbitrary basis of the pair’s subspace (e.g. the collective and regressive second flap of the IEA 22-MW turbine, 0.07 % apart at 10 m/s). Their MAC with a neighbouring point’s mode then measures the mixing, not the identity. For a link between two modes carrying the same unambiguous label, the subspace MAC of each end against the other end’s near-coincident cluster is used when it is higher; the labels (assigned one per multi-blade type by
vane.modal.label_solution()) then decide which cluster member continues which line.
- class IdentificationResult(tracks, n_operating_points, branch_groups=<factory>, heavily_damped_tracks=<factory>, heavily_damped_modes=<factory>)[source]
Bases:
objectResult of cross-operating-point mode identification.
- Parameters:
tracks (list[ModeTrack]) – The identified mode tracks, ordered by their first natural frequency.
n_operating_points (int) – Number of operating points processed.
branch_groups (list[BranchGroup]) – Crossing/veering hotspots flagged by the branch-review pass (empty unless
branch_reviewwas requested). Seevane.modal.branch.heavily_damped_tracks (list[ModeTrack]) – Tracks of the heavily damped (near-critical) modes, linked among themselves only, ordered by first natural frequency. Empty when the heavily damped tier is disabled or no mode qualifies.
heavily_damped_modes (list[list[int]]) – For each operating point, the indices of the modes classified heavily damped, so they are reported even where they form no track. Empty when the tier is disabled.
- branch_groups: list[BranchGroup]
- class ModeTrack(operating_points, mode_indices, natural_frequencies_hz, damping_ratios, label, confidence=1.0, is_ambiguous=False, is_heavily_damped=False, label_agreement=1.0, point_labels=<factory>)[source]
Bases:
objectA mode followed across operating points.
- Parameters:
operating_points (list[int]) – Indices of the operating points this track spans, in order.
mode_indices (list[int]) – The mode index within each corresponding operating point.
natural_frequencies_hz (list[float]) – Natural frequency at each operating point along the track.
damping_ratios (list[float]) – Damping ratio at each operating point along the track.
label (ModeLabel) – Representative physical label: the label carrying the largest summed confidence over the track’s operating points (its most confident instance), counting only unambiguous point labels when there are any.
confidence (float) – The weakest link correlation along the track (minimum MAC between consecutive points, or the subspace MAC across a near-coincident pair), in
[0, 1];1.0for a single-point track.is_ambiguous (bool) – Whether the track’s identity is in doubt: a link was a near-tie (a mode with two near-equally correlated continuations, i.e. a crossing or veering hotspot), an unambiguous point label disagrees with the track label, every point label is ambiguous, or the track is a single operating point of a multi-point sweep (no continuity confirms it).
is_heavily_damped (bool) – Whether the track links heavily damped (near-critical) modes, which are tracked in their own tier (see
identify_modes()).label_agreement (float) – Fraction of the track’s labelled operating points whose own label equals the track label, in
[0, 1].point_labels (list[ModeLabel]) – The label of the track’s mode at each of its operating points, aligned with
operating_points; empty when any of those solutions carries no DOF descriptions (the agreement and ambiguity still use the labelled ones).
- heavily_damped_modes(solution, *, threshold=0.99, rotor_speed_rpm=None)[source]
Return which modes of one operating point are heavily damped.
A mode is heavily damped (near-critical) when its damping ratio, measured in the frame in which the mode is defined (
rotor_frame_damping_ratios()), is at leastthreshold. Such a mode barely oscillates: its damped frequency is a small fraction of its natural frequency, which mostly measures the decay rate, and its shape correlates weakly with its neighbours across operating points. These are oscillatory (complex) modes; the eigensolver’sn_overdampedinstead counts the real, non-oscillatory eigenvalues, which are never returned as modes.- Parameters:
solution (ModalSolution) – The modal solution of one operating point.
threshold (float, optional) – Damping ratio in
(0, 1]at or above which a mode is heavily damped. DefaultDEFAULT_HEAVILY_DAMPED_THRESHOLD.rotor_speed_rpm (float or None, optional) – The rotor speed (rev/min) at the operating point.
None(the default) usessolution.rotor_speed_rpm, asvane.modal.label_solution()does. When neither is known, regressive and progressive blade whirl modes are never classified, since their fixed-frame damping ratio is shifted by the whirl.
- Returns:
npt.NDArray[np.bool_] – Shape
(n_modes,);Truefor each heavily damped mode.- Raises:
ValueError – If
thresholdis not in(0, 1]orrotor_speed_rpmis not finite.- Return type:
npt.NDArray[np.bool_]
- identify_modes(solutions, *, frequency_weight=0.5, mac_threshold=0.5, ambiguity_margin=0.2, strategy='single_path', branch_review=False, resolve=False, parameter_values=None, rigid_body_frequency_hz=0.001, heavily_damped_threshold=0.99, rotor_speeds_rpm=None, continuity_metric='natural_frequency')[source]
Link modes across operating points into labeled tracks, globally.
- Parameters:
solutions (Sequence[ModalSolution]) – Modal solutions, one per operating point, in operating-point order. All must share the same number of mode-shape degrees of freedom.
frequency_weight (float, optional) – Weight of the frequency-continuity penalty in the link affinity,
[0, 1].mac_threshold (float, optional) – Minimum modal assurance criterion for two modes to be linkable.
ambiguity_margin (float, optional) – A link is flagged ambiguous when the best continuation’s MAC exceeds the second best by less than this margin.
strategy ({"single_path", "simultaneous"}, optional) – How links are resolved into tracks.
"single_path"(the default) extracts the globally optimal continuity path one line at a time;"simultaneous"resolves each operating-point transition with one maximum-weight assignment, which is more reliable where lines cross or veer in a dense spectrum. The two agree on well-separated spectra and differ only at contested transitions; see the module docstring.branch_review (bool, optional) – When
True, run the branch-review pass and attach the detected crossing/veering hotspots to the result’sbranch_groups(the tracks themselves are unchanged). DefaultFalse. Seevane.modal.branch.resolve (bool, optional) – When
True, also attach the smooth (minimum-curvature) alternative to each eligible branch group (impliesbranch_review; the tracks are still not modified). DefaultFalse.parameter_values (Sequence[float] or None, optional) – The operating-parameter value at each operating point, used only by
resolveas the curvature abscissa so smoothness is measured against the real (possibly non-uniform) sweep spacing. Defaults to the operating-point index (uniform spacing).rigid_body_frequency_hz (float, optional) – Modes whose undamped natural frequency is below this value (Hz) are rigid-body modes with no restoring stiffness and are left out of every track. Default
DEFAULT_RIGID_BODY_FREQUENCY_HZ;0.0tracks every mode.heavily_damped_threshold (float or None, optional) – Damping ratio, in
(0, 1], at or above which a mode is heavily damped (seeheavily_damped_modes()). Heavily damped modes are kept out of the main tracks and linked among themselves only, intoheavily_damped_tracks, so they can neither take over nor split a lightly damped line; every one is listed per operating point inheavily_damped_modes. DefaultDEFAULT_HEAVILY_DAMPED_THRESHOLD.Nonedisables the tier and tracks every mode together.rotor_speeds_rpm (Sequence[float] or None, optional) – Rotor speed (rev/min) at each operating point. Needed to measure the damping of a regressive or progressive blade whirl mode in the rotating frame (see
rotor_frame_damping_ratios()).None(the default) uses each solution’s ownrotor_speed_rpm(set byvane.modal.eigensolver.modes_from_mbc()), asvane.modal.label_solution()does; at a point whose rotor speed is unknown (nan) such modes are never classified heavily damped, because their fixed-frame damping ratio is shifted by the whirl and is not a property of the mode.continuity_metric ({"natural_frequency", "eigenvalue"}, optional) – The distance the continuity penalty measures between two modes of the main tracks.
"natural_frequency"(the default) is the difference of undamped natural frequencies;"eigenvalue"is the distance \(|\lambda_i - \lambda_j| / 2\pi\) in the complex plane, which equals it for lightly damped modes and also weighs a change of decay rate. The heavily damped tier always uses"eigenvalue", since a near-critical mode’s natural frequency mostly measures its decay rate.
- Returns:
IdentificationResult – The identified tracks and the number of operating points.
- Raises:
ValueError – If
frequency_weight,mac_threshold, orambiguity_marginis not in[0, 1],rigid_body_frequency_hzis negative or non-finite,heavily_damped_thresholdis not in(0, 1],rotor_speeds_rpmhas the wrong length or non-finite values,strategyorcontinuity_metricis unknown, or the solutions have inconsistent mode-shape DOF layout.- Return type:
Notes
The free rotor-azimuth (generator) DOF has no stiffness: its azimuth and speed states give a zero and an aerodynamic-damping eigenvalue, which the eigensolver normally returns as real and drops. Where the two nearly coincide they can come back as a complex pair of negligible frequency (periods of tens of minutes), so the mode would appear at isolated operating points only. The
rigid_body_frequency_hzfloor removes it everywhere.A blade mode whose aerodynamic damping exceeds critical in the rotating frame has two real rotating-frame eigenvalues \(-\sigma_1, -\sigma_2\). The MBC transform maps each cyclic pair of them to a fixed-frame pair \(-\sigma_k \pm i\Omega\): an oscillation at the rotor frequency whose natural frequency \(\sqrt{\sigma_k^2 + \Omega^2}\) measures the decay rate rather than any vibration, while the collective pair is real and is dropped by the eigensolver. Tracking those images by natural frequency against the lightly damped whirl lines either side of the transition draws spurious lines; the heavily damped tier keeps them apart.
- rotor_frame_damping_ratios(solution, rotor_speed_rpm, multiblade)[source]
Return each mode’s damping ratio in the frame in which the mode is defined.
- Parameters:
solution (ModalSolution) – The modal solution of one operating point (in multi-blade coordinates).
rotor_speed_rpm (float) – The rotor speed (rev/min) at that operating point.
multiblade (Sequence[str or None]) – The multi-blade type of each mode (
"collective","regressive","progressive","cyclic"orNone), e.g. themultibladeofvane.modal.label_solution().
- Returns:
npt.NDArray[np.float64] – Shape
(n_modes,). For a regressive or progressive whirl mode, the damping ratio of the rotating-frame eigenvalue it is the image of; for every other mode (collective, tower, platform, drivetrain, unlabelled), the fixed-frame damping ratio. A"cyclic"mode of undetermined whirl takes the smaller of the two whirl readings.- Raises:
ValueError – If
multibladedoes not have one entry per mode orrotor_speed_rpmis not finite.- Return type:
npt.NDArray[np.float64]
Notes
The MBC transform shifts a cyclic blade mode’s rotating-frame eigenvalue \(\lambda_r = -\sigma + i\omega_r\) by the rotor frequency \(\Omega\): the progressive whirl appears at \(\omega_r + \Omega\) and the regressive at \(|\omega_r - \Omega|\), with the same decay rate \(\sigma\). The fixed-frame damping ratio \(\sigma / |\lambda|\) therefore differs between the two whirls of one blade mode and rises towards 1 on the regressive branch as \(\omega_r\) approaches \(\Omega\), without the blade mode itself being any more damped. Undoing the shift, \(\omega_r = |\omega \mp \Omega|\), gives the damping ratio \(\sigma / \sqrt{\sigma^2 + \omega_r^2}\) of the blade mode, which is the same for its regressive, collective and progressive members.
Physics-aware labeling of system modes.
Each mode is labeled by the physical degree-of-freedom category that dominates
it. Participation is aggregated per DofCategory (via
vane.config.dof_map.classify_dof()), the dominant category names the mode
(e.g. “1st Tower Fore-Aft”), and the fraction of participation it carries is
reported as a confidence — a low value flags an ambiguous, mixed mode.
label_solution() votes with the scale-invariant left–right (biorthogonal)
participation of vane.modal.biorthogonal, so the many unclassified module
states of a coupled model (mooring line nodes, hydrodynamic radiation states,
aerodynamic states) cannot out-vote the structural degrees of freedom merely by
having large raw eigenvector amplitudes in their own units.
BeamDyn blades have no modal DOFs: their nodal states vote as flapwise, edgewise,
torsional, or axial blade motion (see vane.config.dof_map), and
label_solution() numbers the resulting flap, edge, and torsion modes from the
spanwise shape of their section rotation and their frequency order.
Ambiguity is reported, never resolved silently. A mode whose runner-up category
carries at least LABEL_AMBIGUITY_RATIO of the winner’s weight (a hybrid, e.g.
a monopile’s second tower fore-aft mode mixed with the progressive second flap), or
whose multi-blade type is not decided by its collective/cyclic split and whirl, is
returned with is_ambiguous=True and the runner-up as alternative. Within one
operating point each blade mode family (e.g. the second flap) has exactly one
collective, one regressive and one progressive member; label_solution()
enforces this (see label_solution(), Notes).
- BEAMDYN_RIGID_RANGE = 0.1
Spanwise rotation spread below which a BeamDyn blade mode counts as rigid.
The linearized BeamDyn nodal states carry the rigid motion of the blade root. When the section rotation varies along the span by less than this fraction of its largest value, the blade turns as a rigid body and the mode is not an elastic bending mode; a rigid collective edgewise rotation (all blades turning together in the rotor plane) is the rotor-azimuth mode.
- BEAMDYN_SHAPE_TOLERANCE = 0.05
Relative size below which a spanwise rotation increment is ignored.
The order of a BeamDyn bending or torsion mode is one plus the number of sign changes of the increments of its nodal section rotation along the span; increments smaller than this fraction of the largest one are treated as numerical noise and skipped.
- DEFAULT_HEAVILY_DAMPED_THRESHOLD = 0.99
Damping ratio at or above which a mode is heavily damped (near-critical).
The ratio is measured in the frame in which the mode is defined: the rotating frame for a regressive or progressive blade whirl mode (see
vane.modal.identifier.rotor_frame_damping_ratios()), the fixed frame otherwise.0.99means within 1 % of critical damping, where the damped frequency is below 15 % of the natural frequency and the natural frequency \(|\lambda|\) is essentially a decay rate. It is set from the reference-turbine sweeps (NREL 5-MW land and OC3 monopile, IEA 3.4-MW, IEA 10-MW and IEA 15-MW monopile, CalcSteady with quasi-steady aero): every first-flap Campbell line that tracks cleanly stays at or below 0.95 in this frame (its fixed-frame value reaches 0.998 on the regressive branch), while the modes that broke the IEA 15-MW lines above rated sit at 1.000, i.e. past critical damping in the rotating frame. Lightly damped lines that the community still draws (first flap at 0.25-0.78 in published IEA Campbell diagrams) are far below it.
- DEFAULT_MIN_KNOWN_PARTICIPATION = 0.5
Default minimum share of participation that classified DOFs must carry.
A mode is named after a physical DOF category only when the classified (structural) degrees of freedom carry at least half of its total participation, i.e. when the structure – not unclassified module states – is the majority participant. Below this the mode is reported as
"Unidentified".
- DRIVETRAIN_ROOT_SHARE = 0.25
Minimum in-phase root rotation of a BeamDyn drivetrain-torsion mode.
With the drivetrain flexible (
DrTrDOF) and the generator locked (noGenDOF), the lowest collective edgewise BeamDyn mode is the drivetrain torsion: the whole rotor swings against the shaft spring, so the blade roots rotate in phase with the tips by a large fraction of the tip rotation (one half on the NREL 5-MW). In a collective edgewise bending mode the hub instead stays put or counter-rotates. A collective edgewise mode whose root section rotation is in phase with its peak rotation and at least this fraction of it is named"Drivetrain Torsion".
- LABEL_AMBIGUITY_RATIO = 0.75
Runner-up to winner weight ratio at or above which a label is ambiguous.
The category vote (and, for a blade mode, the collective-versus-cyclic split) names a mode after its largest share. When the runner-up carries at least this fraction of the winner’s weight – e.g. 0.40 against 0.53 – the mode is a hybrid of the two, and which name it gets is decided by a few percent of participation that move with the operating point. The label is then reported as ambiguous with the runner-up as its alternative.
0.75is set from the reference-turbine sweeps: the clean modes of the NREL 5-MW, IEA 3.4-, 10-, 15- and 22-MW turbines have runner-up ratios below 0.4, while the hybrids that flip labels between neighbouring operating points (monopile tower/flap, flap/edge veering, flap/rotor-speed, aero-state/flap) sit between 0.8 and 1.0.
- TOWER_SUPPORT_SHARE = 0.3333333333333333
Minimum share of a support plane’s participation that tower bending must carry.
A support plane groups the platform translation and tilt with the tower bending DOFs of one plane (fore-aft: surge, pitch and the tower fore-aft modes; side-side: sway, roll and the tower side-side modes). On a fixed-bottom support whose substructure flexibility is carried by the platform DOFs (e.g. a monopile), the global tower bending modes put a large share of their participation on platform translation, so a per-category vote would call them platform modes. When tower bending carries at least this share of the plane’s participation and the plane as a whole out-votes every other category, the mode is a tower bending mode of that plane. A rigid-body platform mode of a floating turbine has (near-)zero tower participation and is never regrouped.
- WHIRL_CIRCULARITY_THRESHOLD = 0.2
Circularity below which a cyclic blade mode’s whirl direction is undetermined.
The circularity \(2\operatorname{Im}(q_s \overline{q_c}) / (|q_c|^2 + |q_s|^2)\) is
+1/-1for a pure backward/forward whirl and0for a standing pattern; below this magnitude the mode is a standing ("cyclic") pattern, e.g. a mixture of the regressive and progressive members of a near-coincident pair.
- class ModeLabel(category, label, confidence, dominant_dofs, multiblade=None, is_ambiguous=False, alternative=None, alternative_category=None, alternative_confidence=0.0)[source]
Bases:
objectPhysical label for a single mode.
- Parameters:
category (DofCategory) – The dominant DOF category.
label (str) – Human-readable name of the dominant category.
confidence (float) – Fraction of total participation carried by the dominant category, in
[0, 1]; low values indicate a mixed or ambiguous mode. For an"Unidentified"mode it is the share carried by unclassified states.dominant_dofs (list[str]) – Descriptions of the highest-participation degrees of freedom (up to three).
multiblade (str or None) – For a blade mode after the MBC transform, the multi-blade type
"collective","regressive","progressive", or"cyclic"(a standing cyclic mode with no whirl direction);Nonefor non-blade modes or when no multi-blade information is available.is_ambiguous (bool, optional) – Whether the label is not decided by the mode’s participation: the runner-up category carries at least
LABEL_AMBIGUITY_RATIOof the winner’s weight, the multi-blade type is not decided (collective and cyclic shares within that ratio, or a standing cyclic pattern), or the per-point multi-blade assignment oflabel_solution()overrode the mode’s own type. DefaultFalse.alternative (str or None, optional) – The strongest competing label: the runner-up category of the vote, the competing multi-blade type of an undecided blade mode, or the label the mode had before a multi-blade reassignment;
Nonewhen nothing competes. It is given for unambiguous labels too (as the runner-up);is_ambiguoussays whether it is a close competitor.alternative_category (DofCategory or None, optional) – The category of
alternative.alternative_confidence (float, optional) – The share of the participation carried by
alternative(0.0when there is none).
- category: DofCategory
- alternative_category: DofCategory | None = None
- category_to_label(category)[source]
Return the human-readable label for a DOF category.
- Parameters:
category (DofCategory) – The category to name.
- Returns:
str – The display label (
"Unidentified"for unknown categories).- Return type:
- label_mode(magnitudes, descriptions, *, multiblade=None, min_known_fraction=None)[source]
Label one mode from its per-DOF participation weights.
- Parameters:
magnitudes (npt.NDArray[np.float64]) – Non-negative participation weight of each DOF, shape
(n_dof,): raw mode-shape magnitudes, or (preferably) the scale-invariant participation fractions used bylabel_solution().descriptions (Sequence[str]) – DOF descriptions parallel to
magnitudes.multiblade (str or None, optional) – Multi-blade type (
"collective"/"regressive"/"progressive") for a blade mode; appended to the label and stored on the result.min_known_fraction (float or None, optional) – When
None(the default), the plain plurality vote: the category with the largest summed weight wins,DofCategory.UNKNOWNincluded. When a number in[0, 1]is given, the dominant category is the largest classified (non-UNKNOWN) category, accepted only if the classified categories together carry at least this fraction of the total weight; otherwise the mode is"Unidentified". On this (participation) path a support plane – the platform translation and tilt plus the tower bending DOFs of one plane – votes as one group when tower bending carries at leastTOWER_SUPPORT_SHAREof it; if the group out-votes every other category the mode is named after its dominant tower DOF.
- Returns:
ModeLabel – The dominant category, its label, the confidence, and the top DOFs. The confidence is the dominant category’s share of the total weight (the whole support plane’s share for a regrouped tower mode); for an
"Unidentified"mode it is the share carried by unclassified states.- Raises:
ValueError – If
magnitudesanddescriptionshave different lengths, or ifmin_known_fractionis outside[0, 1]or non-finite.TypeError – If
min_known_fractionis not a real number.
- Return type:
- label_modes(participation, descriptions, *, min_known_fraction=None)[source]
Label every mode of a participation result.
- Parameters:
participation (ParticipationResult) – Per-DOF participation (its
magnitudecolumns are one mode each).descriptions (Sequence[str]) – DOF descriptions parallel to the participation rows.
min_known_fraction (float or None, optional) – Forwarded to
label_mode();None(the default) keeps the plain plurality vote.
- Returns:
list[ModeLabel] – One label per mode (column).
- Return type:
- label_solution(solution, *, min_known_participation=0.5, rotor_speed_rpm=None)[source]
Label every mode of a modal solution by its biorthogonal participation.
For mode \(k\) with right and left eigenvectors \(\mathbf{x}_k\) and \(\mathbf{y}_k\) of the full state matrix, the participation of state \(i\) is \(p_{ik} \propto |\overline{y}_{ik} x_{ik}|\), normalised so that \(\sum_i p_{ik} = 1\) (see
vane.modal.biorthogonal.biorthogonal_participation()). The displacement and velocity states of a second-order DOF are summed onto that DOF, and the shares are voted perDofCategorybylabel_mode()withmin_known_fraction=min_known_participation: the largest classified category names the mode if the classified DOFs carry at leastmin_known_participationof it, otherwise the mode is"Unidentified". The confidence is the dominant category’s share of the mode’s participation.Platform translation/tilt and tower bending of one plane vote together when tower bending carries at least
TOWER_SUPPORT_SHAREof that plane’s participation, so the global tower modes of a fixed-bottom support (whose substructure flexibility sits in the platform DOFs) are named tower modes, not platform modes. The tower modes of each plane are then numbered in order of frequency ("3rd Tower Fore-Aft"for the third), never below the number of their dominant tower DOF.A non-degenerate rotor mode carries its multi-blade type (collective, regressive, progressive, or a standing cyclic pattern), decided from the collective/cyclic split of its participation and the whirl of its dominant cosine/sine pair.
A BeamDyn blade mode (one voted
BLADE_FLAP,BLADE_EDGE, orBLADE_TORSION) is then numbered, e.g."2nd Blade Flap (Collective)". Its shape order is one plus the number of sign changes of the spanwise increments of its section rotation (aboutYfor flap,Xfor edge,Zfor torsion), which is blind to the rigid root rotation the nodal states carry. Within a family the modes of one multi-blade type are numbered in frequency order, each at least its shape order. A mode whose section rotation barely varies along the span (BEAMDYN_RIGID_RANGE) is rigid blade motion, not bending: a rigid collective edgewise mode is the rotor-azimuth mode ("Rotor/Generator"), and any other rigid mode keeps its unnumbered family label.- Parameters:
solution (ModalSolution) – A solution whose
dof_descriptionsare populated (i.e. produced with descriptions, as byvane.modal.eigensolver.modes_from_mbc()).min_known_participation (float, optional) – Minimum share of the participation, in
[0, 1], that classified DOFs must carry for a mode to receive a physical label (defaultDEFAULT_MIN_KNOWN_PARTICIPATION,0.5: a majority).rotor_speed_rpm (float or None, optional) – Rotor speed (rev/min) of the operating point, used to read a whirl mode’s damping in the rotating frame so heavily damped modes stay out of the per-point multi-blade assignment. Default
Noneusessolution.rotor_speed_rpm(set byvane.modal.eigensolver.modes_from_mbc()).
- Returns:
list[ModeLabel] – One label per mode.
- Raises:
ValueError – If the solution carries no DOF descriptions, or
min_known_participationis outside[0, 1]or non-finite.TypeError – If
min_known_participationis not a real number.
- Return type:
Notes
A solution without usable left eigenvectors (e.g. constructed by hand), or an individual mode whose left–right product is numerically unresolvable, falls back to the plurality vote over raw mode-shape magnitudes. The multi-blade type is decided from the mode’s MBC coordinates – the collective/cyclic split from its participation (or, without it, from the blade energy of the mode shape) and the whirl from the shape’s cosine/sine phase; on the participation path it is attached only to a blade or
"Unidentified"mode, never to a mode the vote assigns to the tower, drivetrain, or platform.Ambiguity. A label whose runner-up category carries at least
LABEL_AMBIGUITY_RATIOof the winner’s weight is returned withis_ambiguous=Trueand the runner-up asalternative; so is a blade mode whose collective and cyclic shares are within that ratio, or whose whirl is a standing pattern ("cyclic").One mode per multi-blade type. Each blade mode family (e.g. the second flap) of a three-bladed rotor has one collective, one regressive and one progressive member, the regressive below the progressive. Where near-coincident members mix and break this at one point, the family’s lightly damped members are assigned to the three types jointly (best summed fit subject to the whirl frequency order); a reassigned member is marked ambiguous, and a member left over is renamed after its non-blade alternative (e.g. a tower or platform mode it is mixed with) or keeps its unsuffixed family name, ambiguous either way. Heavily damped modes (
DEFAULT_HEAVILY_DAMPED_THRESHOLD, read in the rotating frame for a whirl mode) are left out of the assignment: the fixed-frame images of an overdamped blade mode whirl at the rotor speed and are not members of a whirl pair.BeamDyn drivetrain. With a flexible drivetrain, a BeamDyn model’s drivetrain-torsion mode votes for the generator azimuth (generator free) or for collective edgewise motion (generator locked); it is renamed
"Drivetrain Torsion"from its blade-root rotation (seeDRIVETRAIN_ROOT_SHARE).The tower modes of each plane are numbered after the multi-blade assignment, so a tower/blade hybrid that loses its blade type to a purer family member (and is renamed after its tower alternative) is counted among the tower modes.
Branch review — detect crossing and veering hotspots among Campbell tracks.
Cross-operating-point tracking (vane.modal.identifier) links modes into the
lines of a Campbell diagram. Where two lines cross or veer (approach, exchange
character, then repel), a per-transition tracker is least certain which mode
continues which line. This module detects those contested clusters and surfaces them
as auditable BranchGroup records — a branch-review view — rather than
quietly trusting the automatic assignment.
Detection is deterministic and conservative. A pair of tracks is flagged only where both their frequencies approach (within a scale-adaptive tolerance) and their mode shapes genuinely compete (an ambiguous link or elevated cross-correlation across a transition). Frequency proximity alone is not enough: two modes with orthogonal shapes that merely pass close in frequency are tracked unambiguously and are not a hazard, so they are not flagged.
The pass adds no risk to the tracks themselves — it only reports. Re-drawing contested
clusters is a separate, opt-in step (see the resolve option of
vane.modal.identify_modes()).
- class BranchGroup(track_indices, op_lo, op_hi, frequency_band_hz, kind, min_separation_hz, min_separation_op, sub_windows, is_dense, resolved=False, smooth_branches=None)[source]
Bases:
objectA cluster of tracks contested at a crossing or veering hotspot.
- Parameters:
track_indices (tuple[int, ...]) – Indices (ascending) into
IdentificationResult.tracksof the tracks in the cluster.op_lo (int) – Hull operating-point window spanning all of the cluster’s contested windows.
op_hi (int) – Hull operating-point window spanning all of the cluster’s contested windows.
frequency_band_hz (tuple[float, float]) – Minimum and maximum natural frequency of the member tracks within the hull window.
kind (BranchKind) – Group-level classification —
BranchKind.CROSSINGif any member pair crosses, otherwiseBranchKind.VEERING.min_separation_hz (float) – Closest frequency approach between any member pair within the window.
min_separation_op (int) – Operating-point index at which the closest approach occurs.
sub_windows (tuple[BranchSubWindow, ...]) – The distinct contested windows that make up the group, ordered canonically.
is_dense (bool) – Whether the group has more than two tracks. Dense groups are still surfaced for review; resolution handles them up to a small branch cap.
resolved (bool) – Whether a resolution pass computed a smooth alternative for this group (always
Falsefor detection-only runs).smooth_branches (tuple[tuple[float, ...], ...] or None) – The minimum-curvature (“smooth”, non-crossing) frequency alternative for the contested cluster, one tuple per branch aligned with the window operating points
op_lo..op_hi. Set by the resolution pass for an eligible group, elseNone. The actual tracks are never modified — this is review-only metadata contrasting the smooth convention with the shape-following base tracks.
- kind: BranchKind
- sub_windows: tuple[BranchSubWindow, ...]
- class BranchKind(*values)[source]
Bases:
EnumWhether a contested window is a frequency crossing or a veering.
- CROSSING = 'crossing'
- VEERING = 'veering'
- class BranchSubWindow(op_lo, op_hi, kind, truncated)[source]
Bases:
objectA contiguous operating-point window where two tracks are contested.
- Parameters:
op_lo (int) – First and last operating-point index of the window (
op_hi > op_lo).op_hi (int) – First and last operating-point index of the window (
op_hi > op_lo).kind (BranchKind) – Whether the two tracks cross (their frequency order swaps) or veer (approach and separate without swapping) over the window.
truncated (bool) – Whether the closest approach is at a window endpoint, so the hotspot may extend beyond the swept range.
- kind: BranchKind
- attach_branch_groups(result, solutions, *, resolve=False, parameter_values=None, **kwargs)[source]
Return a copy of
resultwith itsbranch_groupspopulated.Detection does not alter the tracks; the returned result shares the same track objects and only carries the additional branch groups.
- Parameters:
result (IdentificationResult) – The tracking result to review.
solutions (Sequence[ModalSolution]) – The per-operating-point modal solutions, in operating-point order.
resolve (bool, optional) – When
True, also compute the smooth (minimum-curvature) alternative for each eligible group (seeresolve_branch_groups()); the tracks are still not modified. DefaultFalse.parameter_values (Sequence[float] or None, optional) – The operating-parameter value at each operating point, forwarded to the resolution curvature (see
resolve_branch_groups()).**kwargs – Forwarded to
detect_branch_groups()(veering_tol_hz,mac_competition,ambiguity_margin).
- Returns:
IdentificationResult – A new result with the detected (and optionally resolved) branch groups.
- Return type:
- detect_branch_groups(result, solutions, *, veering_tol_hz=None, mac_competition=0.25, ambiguity_margin=0.2)[source]
Find crossing/veering hotspots among a result’s tracks.
- Parameters:
result (IdentificationResult) – The tracking result whose tracks are reviewed. Not modified.
solutions (Sequence[ModalSolution]) – The per-operating-point modal solutions the tracks were built from, in operating-point order (used for the mode-shape correlations).
veering_tol_hz (float or None, optional) – Frequency proximity below which two tracks may be contested.
None(the default) derives it deterministically as0.25times the median adjacent natural-frequency spacing across the sweep, so it scales with the spectrum.mac_competition (float, optional) – Cross-MAC at or above which two tracks are deemed to compete across a transition.
ambiguity_margin (float, optional) – Two non-negligible continuations whose MAC differs by less than this are a near-tie (an ambiguous link).
- Returns:
list[BranchGroup] – The contested clusters, ordered canonically by window then frequency. Empty when no tracks are contested.
- Raises:
ValueError – If
mac_competitionorambiguity_marginis not in[0, 1], orveering_tol_hzis negative.- Return type:
- resolve_branch_groups(groups, result, *, parameter_values=None)[source]
Annotate each eligible group with its smooth (minimum-curvature) alternative.
For a group of two to four tracks spanning at least three operating points where every track is present throughout the window, computes the assignment of the group’s modes to branches that minimizes the total squared frequency curvature — the smooth, non-crossing convention — and attaches it as
smooth_brancheswithresolved=True. The tracks are never modified; this is review-only metadata. Larger groups, short windows, and ragged membership are left unresolved.- Parameters:
groups (list[BranchGroup]) – The detected branch groups.
result (IdentificationResult) – The tracking result the groups index (for the member-track frequencies).
parameter_values (Sequence[float] or None, optional) – The operating-parameter value at each operating point, used as the abscissa for the curvature so smoothness is measured against the real (possibly non-uniform) sweep spacing.
None(the default) falls back to the operating-point index (uniform spacing). When given, it must have one value per operating point and be strictly increasing.
- Returns:
list[BranchGroup] – New groups, with the smooth alternative attached where eligible.
- Return type:
Uncertainty quantification for modal estimates.
Two physically grounded uncertainty sources are combined into one per-mode confidence:
Azimuth spread. The MBC transform produces one non-rotating state matrix per azimuth; their average is the linear-time-invariant model. For an isotropic rotor every per-azimuth matrix is identical (azimuth-invariant to machine precision), so the eigenvalue spread across azimuths is ~0; for an anisotropic rotor the spread grows, quantifying how well the averaged model represents the periodic system.
Numerical conditioning and degeneracy from the eigen-analysis.
The unified confidence multiplies independent reliability factors (each in [0, 1])
so that a mode is trusted only when it is numerically well posed, non-degenerate,
azimuth-stable, and (optionally) confidently tracked. It feeds the covariance
initialization of the state-space export.
- class AzimuthSpread(natural_frequency_std, damping_ratio_std, n_azimuths)[source]
Bases:
objectPer-mode variation of the modal estimates across azimuth.
- Parameters:
natural_frequency_std (npt.NDArray[np.float64]) – Standard deviation of each averaged mode’s natural frequency (Hz) across the per-azimuth perturbations of the averaged model, one entry per mode of
vane.modal.eigensolver.modes_from_mbc()of the same result, in its order.damping_ratio_std (npt.NDArray[np.float64]) – Standard deviation of each averaged mode’s damping ratio across azimuths.
n_azimuths (int) – Number of azimuth samples used.
- natural_frequency_std: npt.NDArray[np.float64]
- damping_ratio_std: npt.NDArray[np.float64]
- azimuth_spread(result)[source]
Compute the per-mode azimuth spread of a retained MBC result.
Rather than comparing per-azimuth spectra directly, this measures how each frozen-time matrix
A(psi)perturbs the averaged model: for an isotropic rotor everyA(psi) == avg_aand the spread is zero, while anisotropy makes the per-azimuth matrices (and their spectra) differ by an amount the spread captures. For each averaged mode the first-order eigenvalue perturbationdlambda = y^H (A(psi) - avg_a) x / (y^H x)(left eigenvectory, rightx) is evaluated at every azimuth, and the standard deviation of the resulting natural frequency and damping is returned. It is zero for an isotropic rotor (A(psi) == avg_a) and grows with anisotropy.- Parameters:
result (MBCResult) – An MBC result computed with
retain_per_azimuth=True.- Returns:
AzimuthSpread – The per-mode frequency and damping spread.
- Raises:
ValueError – If the per-azimuth matrices were not retained or
avg_ais absent.- Return type:
- unified_mode_confidence(solution, *, frequency_spread=None, track_confidence=None, spread_scale=0.05)[source]
Combine the uncertainty sources into one per-mode confidence in
[0, 1].The confidence is the product of independent reliability factors: numerical conditioning (
1 / kappa), a degeneracy penalty, an azimuth-stability factorexp(-relative_spread / spread_scale), and an optional tracking confidence.- Parameters:
solution (ModalSolution) – The modal solution (provides condition numbers and degeneracy flags).
frequency_spread (npt.NDArray[np.float64] or None, optional) – Per-mode natural-frequency standard deviation (e.g. from
azimuth_spread());nanentries are treated as zero spread.track_confidence (npt.NDArray[np.float64] or None, optional) – Per-mode tracking confidence in
[0, 1]from the cross-operating-point tracker.spread_scale (float, optional) – Relative-spread scale at which the azimuth-stability factor falls to
1/e.
- Returns:
npt.NDArray[np.float64] – The per-mode confidence.
- Return type:
npt.NDArray[np.float64]
Calibration of the per-mode confidence into a reliability metric.
vane.modal.unified_mode_confidence() returns a per-mode confidence in [0, 1]
built from physically grounded factors (numerical conditioning, degeneracy, azimuth
spread, tracking ambiguity). That confidence is only useful if it is calibrated —
among the modes assigned confidence ~c, about a fraction c should be correct.
A raw confidence that is systematically over- or under-confident misleads any downstream
decision (which modes to trust, how to weight the Kalman covariance).
This module measures that calibration against a ground-truth correctness label and, when the raw confidence is miscalibrated, learns a monotone recalibration map so the reported number can be read as a probability of being correct:
calibrate_confidence()bins(confidence, correct)pairs into a reliability diagram and reports the expected/maximum calibration error and the Brier score, plus a pool-adjacent-violators (isotonic) mapconfidence -> P(correct).recalibrate()applies that map to new confidences.confidence_threshold()reads off the smallest confidence at which the calibrated probability of being correct reaches a target — the evidence behind a claim like “confidence >= 0.85 implies >= 95 % correct”.
The correct label is supplied by the caller and is domain-specific: for mode
identification it is predicted_label == true_label against a synthetic ground truth;
for cross-operating-point tracking it is whether a track was assigned to the right mode.
The calibration itself is agnostic to how correctness was decided.
- class ConfidenceCalibration(bin_edges, bin_confidence, bin_accuracy, bin_count, expected_calibration_error, max_calibration_error, brier_score, map_confidence, map_accuracy, n_samples)[source]
Bases:
objectThe calibration of a confidence score against a correctness label.
- Parameters:
bin_edges (npt.NDArray[np.float64]) – The
n_bins + 1equal-width bin edges spanning[0, 1].bin_confidence (npt.NDArray[np.float64]) – Mean confidence within each bin, shape
(n_bins,);nanfor an empty bin.bin_accuracy (npt.NDArray[np.float64]) – Empirical fraction correct within each bin, shape
(n_bins,);nanfor an empty bin. The reliability diagram plotsbin_confidencevsbin_accuracy.bin_count (npt.NDArray[np.int]) – Number of samples in each bin, shape
(n_bins,).expected_calibration_error (float) – Sample-weighted mean absolute gap between confidence and accuracy across non-empty bins (ECE);
0is perfectly calibrated.max_calibration_error (float) – Largest absolute confidence-accuracy gap over non-empty bins (MCE).
brier_score (float) – Mean squared error of confidence against the correctness label; lower is better.
map_confidence (npt.NDArray[np.float64]) – Strictly increasing confidence values of the isotonic recalibration map.
map_accuracy (npt.NDArray[np.float64]) – Non-decreasing calibrated
P(correct)at eachmap_confidencepoint.n_samples (int) – Number of
(confidence, correct)pairs the calibration was built from.
- bin_edges: npt.NDArray[np.float64]
- bin_confidence: npt.NDArray[np.float64]
- bin_accuracy: npt.NDArray[np.float64]
- bin_count: npt.NDArray[np.int_]
- map_confidence: npt.NDArray[np.float64]
- map_accuracy: npt.NDArray[np.float64]
- calibrate_confidence(confidence, correct, *, n_bins=10)[source]
Calibrate a confidence score against a ground-truth correctness label.
Builds an equal-width reliability diagram over
[0, 1], the expected (ECE) and maximum (MCE) calibration errors, the Brier score, and an isotonic (monotone) recalibration mapconfidence -> P(correct)fitted by pool-adjacent-violators.- Parameters:
confidence (npt.ArrayLike) – Per-sample confidence in
[0, 1](e.g. fromvane.modal.unified_mode_confidence()).correct (npt.ArrayLike) – Per-sample correctness label, boolean or
0/1, aligned withconfidence.n_bins (int, optional) – Number of equal-width reliability bins over
[0, 1]. Default10.
- Returns:
ConfidenceCalibration – The reliability diagram, calibration-error metrics, Brier score, and isotonic recalibration map.
- Raises:
ValueError – If the inputs are not equal-length non-empty 1-D arrays,
confidenceis non-finite or outside[0, 1],correctis not boolean/0/1, orn_binsis not a positive integer.- Return type:
- confidence_threshold(calibration, target_accuracy)[source]
Return the smallest confidence whose calibrated
P(correct)reaches a target.Inverts the same piecewise-linear curve
recalibrate()evaluates, so the returned confidencecis the lowest value withrecalibrate(c) >= target_accuracy: when the target falls between two map points the crossing confidence is linearly interpolated, rather than rounded up to the next sampled point. The answer is reported on the observed confidence span; a target at or below the lowest mapped accuracy returns the lowest mapped confidence.- Parameters:
calibration (ConfidenceCalibration) – A calibration from
calibrate_confidence().target_accuracy (float) – The desired probability of being correct, in
(0, 1].
- Returns:
float – The smallest confidence whose calibrated probability meets the target.
- Raises:
ValueError – If
target_accuracyis not in(0, 1], or the calibrated probability never reaches it (the confidence is not discriminative enough for this target).- Return type:
- recalibrate(calibration, confidence)[source]
Map raw confidence to a calibrated probability of being correct.
Linearly interpolates
confidenceonto the isotonic recalibration map; values below or above the fitted range are clamped to the map’s end values.- Parameters:
calibration (ConfidenceCalibration) – A calibration from
calibrate_confidence().confidence (npt.ArrayLike) – Raw confidence value(s) in
[0, 1].
- Returns:
npt.NDArray[np.float64] – The calibrated probability of being correct, same shape as
confidence.- Raises:
ValueError – If
confidenceis non-finite or outside[0, 1].- Return type:
npt.NDArray[np.float64]
Azimuth-density calibration of the averaged-model modal frequencies.
The azimuth-averaged (Coleman/MBC) model is only as good as its azimuth sampling: too few azimuths and the average does not remove the rotor’s periodic content, so the Campbell frequencies are biased. VANE’s “thin azimuth coverage” warning is otherwise an empirical judgement; this module replaces it with a calibration curve — how the averaged natural frequencies converge as the azimuth count grows — measured against the densest available sampling as the reference.
Given a dense per-azimuth set, azimuth_density_sweep() sub-samples it to a series
of azimuth counts (1, 3, 6, 12, 36, …), re-averages, and reports the natural-frequency
error of each coarse model relative to the full-density reference.
recommend_azimuth_count() then reads off the smallest azimuth count that brings
that error under a tolerance — a data-grounded answer to “how many azimuths are enough”
for a given turbine, in place of a hard-coded rule.
- class AzimuthConvergence(densities, frequency_error_hz, natural_frequencies_hz, reference_frequencies_hz)[source]
Bases:
objectHow the averaged-model natural frequencies converge with azimuth count.
- Parameters:
densities (npt.NDArray[np.int]) – The azimuth counts evaluated, ascending.
frequency_error_hz (npt.NDArray[np.float64]) – For each density, the maximum natural-frequency difference (Hz) between that coarse averaged model and the full-density reference.
natural_frequencies_hz (npt.NDArray[np.float64]) – The sorted natural frequencies (Hz) at each density, shape
(n_density, n_frequencies).reference_frequencies_hz (npt.NDArray[np.float64]) – The reference (full-density) sorted natural frequencies (Hz).
- densities: npt.NDArray[np.int_]
- frequency_error_hz: npt.NDArray[np.float64]
- natural_frequencies_hz: npt.NDArray[np.float64]
- reference_frequencies_hz: npt.NDArray[np.float64]
- azimuth_density_sweep(per_azimuth_a, azimuths, *, densities=(1, 3, 6, 12, 36))[source]
Measure how the averaged model’s frequencies converge with azimuth count.
Sub-samples the (azimuth-sorted) per-azimuth matrices to each requested count, takes the mean as the averaged model, and compares its natural frequencies to those of the full-density average (the reference).
- Parameters:
per_azimuth_a (npt.ArrayLike) – The frozen-time state matrices, shape
(n_azimuth, n, n)(e.g.MBCResult.per_azimuth_a).azimuths (npt.ArrayLike) – The rotor azimuth of each matrix; used only to order the samples, so any consistent unit works (degrees or radians, e.g.
MBCResult.azimuths_deg).densities (Sequence[int], optional) – Azimuth counts to evaluate; counts above the available number are skipped. Default
(1, 3, 6, 12, 36).
- Returns:
AzimuthConvergence – The per-density frequencies and their error versus the full-density reference.
- Raises:
ValueError – If the matrices are not a consistent
(n_azimuth, n, n)stack, the matrices or azimuths are non-finite, the azimuths do not match, a requested density is non-finite or not a whole number, or no requested density is achievable.- Return type:
- recommend_azimuth_count(convergence, *, tolerance_hz)[source]
Return the smallest evaluated azimuth count whose frequency error is within tol.
Reads the convergence curve for the fewest azimuths at which the averaged model’s natural-frequency error (and that of every denser model evaluated) stays at or below
tolerance_hz— the data-grounded minimum azimuth coverage.- Parameters:
convergence (AzimuthConvergence) – A sweep from
azimuth_density_sweep().tolerance_hz (float) – The acceptable natural-frequency error (Hz).
- Returns:
int – The recommended azimuth count.
- Raises:
ValueError – If
tolerance_hzis not positive, or no evaluated density meets it (the sampling does not converge within tolerance — use more azimuths).- Return type:
Floquet / Hill modal analysis of a linear time-periodic (LTP) system.
A spinning rotor makes the linearized dynamics \(\dot{\mathbf x} = \mathbf A(\psi) \mathbf x\) periodic in the azimuth \(\psi = \Omega t\) with period \(T = 2\pi/\Omega\), not time-invariant. The multi-blade (Coleman) transform removes that periodicity only for an isotropic rotor; for an anisotropic system — a two-bladed rotor, or a floating turbine whose mooring/hydrodynamics break the rotational symmetry — the azimuth-averaged model is an approximation whose error this module quantifies and replaces.
Floquet theory writes each solution as \(\mathbf x(t) = e^{\lambda t}\mathbf p(t)\) with \(\mathbf p\) periodic; the Floquet exponents \(\lambda\) are the true modal eigenvalues of the periodic system and \(\mathbf p(t) = \sum_m \mathbf p_m e^{i m \Omega t}\) carries the harmonic mode-shape components. Two independent routes are provided:
Hill’s method (
floquet_modes()): expand \(\mathbf A(\psi) = \sum_n \mathbf A_n e^{i n\psi}\) and solve the eigenproblem of the Hill matrix with blocks \(\mathbf H_{kj} = \mathbf A_{k-j} - i k\Omega\,\delta_{kj}\mathbf I\); the principal exponents are the eigenvalues whose eigenvectors concentrate in the fundamental (\(m=0\)) harmonic, and the blocks give \(\mathbf p_m\).The monodromy matrix (
monodromy_exponents()): integrate the state-transition matrix over one period (piecewise-constant / matrix-exponential product); its eigenvalues are the Floquet multipliers \(\mu\), and \(\lambda = \ln\mu / T\). An independent cross-check of Hill.
For an isotropic rotor (every \(\mathbf A(\psi)\) equal) both reduce to the ordinary
eigenvalues of that constant matrix, so the Floquet result degrades gracefully to the
averaged-model answer; the gap between them is the anisotropy error
(averaging_error()).
- HILL_UNIFORM_RTOL = 0.001
Relative tolerance on the azimuth intervals for Hill’s method. The Fourier coefficients of A(psi) come from the equal-weight discrete transform, which is exact only on a uniform grid, so every interval must equal the mean interval to this fraction of it (plus a 1e-9 rad absolute floor).
- class FloquetSolution(exponents, natural_frequencies_hz, damping_ratios, harmonics, harmonic_mode_shapes, rotor_speed)[source]
Bases:
objectThe Floquet modes of a linear time-periodic system.
- Parameters:
exponents (npt.NDArray[np.complex128]) – The principal Floquet exponents \(\lambda\) (one per state), sorted by ascending natural frequency. Selected as the Hill eigenvalues that concentrate in the fundamental (\(m=0\)) harmonic, one per distinct Floquet solution (harmonic-shifted copies \(\lambda + ik\Omega\) of a kept exponent are skipped), so a near-time-invariant system recovers the ordinary eigenvalues unfolded (constant \(A\) gives
eig(A)exactly) — unlikemonodromy_exponents(), whose imaginary part is known only modulo \(\Omega\).natural_frequencies_hz (npt.NDArray[np.float64]) –
|lambda| / 2pifor each exponent.damping_ratios (npt.NDArray[np.float64]) –
-Re(lambda) / |lambda|for each exponent.harmonics (npt.NDArray[np.int]) – The harmonic indices
mof the mode-shape blocks, from-n_harmonicsto+n_harmonics.harmonic_mode_shapes (npt.NDArray[np.complex128]) – The periodic mode-shape components
p_m, shape(n_harmonics_total, n_dof, n_modes);[harmonics == 0]is the fundamental (mean) mode shape.rotor_speed (float) – The rotor speed \(\Omega\) (rad/s) used.
- exponents: npt.NDArray[np.complex128]
- natural_frequencies_hz: npt.NDArray[np.float64]
- damping_ratios: npt.NDArray[np.float64]
- harmonics: npt.NDArray[np.int_]
- harmonic_mode_shapes: npt.NDArray[np.complex128]
- averaging_error(per_azimuth_a, azimuths, rotor_speed, *, n_harmonics=4)[source]
Return the azimuth-averaging error: max frequency gap (Hz) Floquet vs averaged.
Compares the Floquet natural frequencies to those of the azimuth-averaged model (the eigenvalues of the mean of
per_azimuth_a, which is what the averaged-LTI / MBC path uses). Zero for an isotropic rotor; it grows with anisotropy — the quantity that says when the averaged Campbell diagram is untrustworthy.- Parameters:
per_azimuth_a (npt.ArrayLike) – As for
floquet_modes().azimuths (npt.ArrayLike) – As for
floquet_modes().rotor_speed (float) – As for
floquet_modes().n_harmonics (int) – As for
floquet_modes().
- Returns:
float – The maximum absolute difference (Hz) between matched Floquet and averaged natural frequencies.
- Raises:
ValueError – As
floquet_modes(); in particular the azimuths must be near-uniform (relativeHILL_UNIFORM_RTOL), because the Floquet frequencies come from Hill’s method.- Return type:
- floquet_modes(per_azimuth_a, azimuths, rotor_speed, *, n_harmonics=4)[source]
Compute the Floquet modes of an LTP system by Hill’s harmonic-balance method.
- Parameters:
per_azimuth_a (Sequence[npt.NDArray[np.float64]]) – The frozen-time state matrices \(\mathbf A(\psi_k)\), one per azimuth (e.g.
MBCResult.per_azimuth_awithretain_per_azimuth=True).azimuths (Sequence[float]) – The rotor azimuth (rad) of each matrix, spanning one revolution.
rotor_speed (float) – The rotor speed \(\Omega\) (rad/s); must be positive.
n_harmonics (int, optional) – Harmonic truncation order
M; the Hill blocks need Fourier coefficients up to \(2M\), so the near-uniform azimuth sweep (see Notes) must havelen(per_azimuth_a) >= 4*n_harmonics + 1samples so those coefficients are resolved without aliasing.
- Returns:
FloquetSolution – The principal Floquet exponents, their frequencies/damping, and the harmonic mode-shape components.
- Raises:
ValueError – If
rotor_speedis not positive, the matrices are not square and consistent, the azimuth sweep is not near-uniform (see Notes; the message names the worst interval and its relative deviation), or it has too few samples to resolve the Hill coefficients forn_harmonicswithout aliasing.- Warns:
RuntimeWarning – If the selected exponents do not reproduce the monodromy multipliers (\(e^{\lambda T}\), compared modulo \(i\Omega\)) to within \(10^{-2}\Omega\) — the harmonic truncation or azimuth sampling is too coarse for this system.
- Return type:
Notes
Limitation: near-uniform azimuths only. The Fourier coefficients of \(\mathbf A(\psi)\) are computed with the equal-weight discrete transform, which is exact only on a uniform grid. Every azimuth interval, including the one that closes the revolution, must equal the mean interval within a relative
HILL_UNIFORM_RTOL(1e-3); otherwiseValueErroris raised. OpenFAST linearizes at the time steps nearest the requested azimuths, so a delivered grid is uneven unless the time step is commensurate with the azimuth spacing (commensurate_time_step()) and the rotor runs at its trimmed speed. Analyse an uneven delivered grid withmonodromy_exponents(), which weights each matrix by its own interval and accepts any distinct azimuths (its frequencies are known only modulo \(\Omega\)).averaging_error()calls this function and has the same restriction.Every Floquet solution appears in the Hill spectrum as a family of copies \(\lambda + ik\Omega\). Exactly one representative per distinct solution is kept: candidates are walked in descending fundamental (\(m=0\)) energy and a candidate that is a harmonic-shifted copy of an already-kept exponent (same exponent modulo \(i\Omega\) and the same eigenvector shifted by \(k\) blocks) is skipped. Inside a parametric-resonance tongue (real negative multipliers, \(\operatorname{Im}\lambda \equiv \Omega/2\)) the conjugate of an exponent is such a copy, so both tongue families — including an unstable one — are kept; distinct solutions sharing a multiplier (an isotropic mode at exactly \(\Omega/2\)) are both kept.
- monodromy_exponents(per_azimuth_a, azimuths, rotor_speed)[source]
Return the Floquet exponents via the monodromy matrix (independent of Hill).
Integrates the state-transition matrix over one revolution as a product of matrix exponentials over the (piecewise-constant) azimuth intervals; the eigenvalues of that monodromy matrix are the Floquet multipliers \(\mu\), and the exponents are \(\ln\mu / T\).
Note
The multiplier \(\mu = e^{\lambda T}\) is invariant under \(\lambda \mapsto \lambda + i k\Omega\), so this matrix determines the exponent’s real part (damping/growth rate) exactly but its imaginary part (frequency) only modulo \(\Omega\): the principal branch of the logarithm folds it into the fundamental strip \((-\Omega/2, \Omega/2]\). A mode whose damped frequency exceeds \(\Omega/2\) is therefore reported aliased (e.g. a 0.8 Hz mode with a 1 Hz rotor comes back at 0.2 Hz). Use
floquet_modes()(Hill) for the unfolded frequencies; this helper is an exact cross-check of the damping and of frequencies within the strip.- Parameters:
- Returns:
npt.NDArray[np.complex128] – The Floquet exponents (real part exact; imaginary part folded into the principal strip \((-\Omega/2, \Omega/2]\)), sorted by natural frequency.
- Raises:
ValueError – If
rotor_speedis not positive and finite, the matrices are non-finite or inconsistently shaped, or the azimuths are non-finite or not distinct (after wrapping into[0, 2pi)).- Return type:
npt.NDArray[np.complex128]
Azimuth averaging
See Azimuth averaging for the workflow.
Azimuth averaging on delivered grids: quadrature, harmonics and the azimuth state.
The azimuth-averaged (Coleman/MBC) model of a rotating turbine is a weighted mean of the fixed-frame state matrices \(\mathbf A(\psi_\ell)\) sampled at the azimuths a linearization actually delivered. Those azimuths are rarely uniform: each snapshot is taken at the first time step at or past its target azimuth. This module provides the building blocks for treating that mean exactly and for auditing it.
Quadrature.
interval_weights()gives the weights of the periodic piecewise-linear interpolant of the samples (the exact mean of that interpolant), andweight_kernel()their discrete Fourier transform \(W(h)=\sum_\ell w_\ell e^{\,jh\psi_\ell}\), the aliasing kernel of the grid.Harmonics.
fit_harmonics()represents the samples by a harmonic model \(\mathbf A(\psi)=\sum_h \mathbf A_h e^{\,jh\psi}\). With the default interpolating trigonometric fit the error of any weighted mean of the samples is exactly \(\mathbf E=\sum_h[W_S(h)-W_{\mathrm{ref}}(h)]\mathbf A_h\) (grid_error_matrix()).The rotor-azimuth state.
locate_azimuth_state()finds the ElastoDyn generator-azimuth position (DOF_GeAz) and rotor-speed states from the state descriptions. For a linearization about a periodic orbit the time derivative of that orbit (the orbit tangent, whose azimuth component is one) solves the linearized equations, so one Floquet exponent is exactly zero.azimuth_consistency()tests this from the averaged model alone: the averaged azimuth eigenvalue plus its second-order sideband correction (sideband_correction()) must vanish.Azimuth treatments.
pin_azimuth()replaces each sample by \(\mathbf A(\psi)(\mathbf I-\boldsymbol\xi\mathbf e_{\mathrm{az}}^T)\), which makes the tangent \(\boldsymbol\xi\) an exact null vector of every sample and, when \(\boldsymbol\xi\) is the right eigenvector of the reference mean (azimuth_tangent()), leaves every other reference eigenvalue unchanged. The simplerzero_azimuth_column()anddelete_azimuth_state()are kept for comparison; they also remove the zero-eigenvalue artefact but bias the reference, because the mean of the azimuth column is physical (the rotation of the gravity-sagged rotor, cancelled in an exact linearization by the cyclic blade coupling).
The per-branch error budget built on these pieces is in
vane.modal.averaging_error_estimate. Everything here is pure NumPy/SciPy and
takes azimuths in radians.
- class AzimuthConsistency(eigenvalue, correction, exponent, index, azimuth_share, condition, stiffness, speed_damping, tolerance, consistent, pair_indices, pair_eigenvalues, pair_exponents, residual_exponent, residual_stiffness, absolute_consistent, scale_tolerance, branch_risks, assessment)[source]
Bases:
objectResult of the azimuth (periodic-orbit) consistency test.
The azimuth position and rotor-speed states form a pair of modes, condensed \(s^2-bs-\kappa=0\). Both modes are tested: their sideband-corrected exponents \(\gamma_{1,2}\) estimate the two Floquet exponents, one of which is exactly zero for a linearization about a periodic orbit.
- Parameters:
eigenvalue (complex) – The averaged eigenvalue \(\lambda\) of the growing mode of the pair, the one whose exponent has the larger real part (1/s).
correction (complex) – Its sideband correction \(\widehat{\Delta\lambda}\).
exponent (complex) – \(\gamma_1=\lambda+\widehat{\Delta\lambda}\), the growing exponent of the pair. In a saddle (\(\kappa>0\)) this is the unstable root, not the stable one, whatever their azimuth shares.
index (int) – Index of that mode in the eigenbasis of the reference mean.
azimuth_share (float) – Participation share of the azimuth position state in that mode.
condition (float) – Eigenvalue condition number of that mode (large for the near-Jordan pair).
stiffness (float) – \(\kappa\), the azimuth stiffness of the static condensation of the reference mean onto (azimuth, rotor speed);
nanwithout the speed state.speed_damping (float) – \(b\), the rotor-speed damping of that condensation (1/s);
nanwithout the speed state.tolerance (float) – Absolute tolerance on the residual exponent (1/s), kept for comparison.
consistent (bool) –
assessment == "consistent".pair_indices (tuple[int, int]) – Indices of the two modes of the pair, growing first.
pair_eigenvalues (tuple[complex, complex]) – Their averaged eigenvalues.
pair_exponents (tuple[complex, complex]) – Their sideband-corrected exponents \((\gamma_1,\gamma_2)\), \(\operatorname{Re}\gamma_1\ge\operatorname{Re}\gamma_2\).
residual_exponent (float) – \(r=\min(|\gamma_1|,|\gamma_2|)\), the distance of the pair from an exact zero exponent (1/s).
residual_stiffness (float) – \(\kappa_r=-\operatorname{Re}(\gamma_1\gamma_2)\), the azimuth stiffness implied by the exponents (1/s^2); zero for a consistent linearization.
absolute_consistent (bool) –
residual_exponent <= tolerance: a fixed absolute-tolerance test.scale_tolerance (float) – The dimensionless tolerance :math:` au` of the scaled test.
branch_risks (tuple[BranchAzimuthRisk, ...]) – The decay and coalescence ratios of every assessed branch.
assessment (str) –
"consistent","inconsistent-but-harmless"or"inconsistent-and-risky"(seeazimuth_consistency()).
- branch_risks: tuple[BranchAzimuthRisk, ...]
- property growth_rate: float
Return \(\operatorname{Re}\gamma_1\), the largest real part of the pair.
With an open-loop rotor whose aerodynamic torque rises with speed (\(b>0\)) this is the physical rotor-speed divergence, not a defect; the assessment therefore uses the residual, not the growth rate.
- property speed_damping_exponent: float
Return \(b_\gamma=\operatorname{Re}(\gamma_1+\gamma_2)\) (1/s).
- property pendulum_frequency: float
Return the azimuth pendulum frequency \(\sqrt{-\kappa-b^2/4}\) (rad/s).
A negative stiffness, for example created by aliasing on a coarse grid, makes the azimuth an oscillator that can hybridise with a platform branch near this frequency. Zero for a real pair or an unknown condensation.
- property residual_pendulum_frequency: float
Return \(|\operatorname{Im}\gamma_1|\), the residual pair frequency.
Non-zero only when the residual makes the azimuth pair oscillatory.
- property worst_branch: BranchAzimuthRisk | None
Return the assessed branch with the largest coalescence ratio.
- Returns:
BranchAzimuthRisk or None –
Nonewhen no branch was assessed.
- class AzimuthStates(position, speed=None)[source]
Bases:
objectPositions of the rotor-azimuth position and rotor-speed states.
- Parameters:
- class BranchAzimuthRisk(index, eigenvalue, decay_ratio, coalescence_ratio, risky)[source]
Bases:
objectHow strongly the azimuth residual can contaminate one branch.
- Parameters:
index (int) – Index of the branch in the eigenbasis of the reference mean.
eigenvalue (complex) – Its reference eigenvalue \(\lambda_j=-\sigma_j+j\omega_j\) (1/s).
decay_ratio (float) – \(\nu_j=r/\sigma_j\), the residual exponent over the branch decay rate (
inffor a branch that does not decay).coalescence_ratio (float) – \(\mu_j=|\kappa_r|/(\omega_j^2+b_\gamma^2/4)\), the residual azimuth stiffness over the coalescence threshold of the branch.
risky (bool) – Both ratios exceed the scale tolerance.
- class EigenBasis(values, left, right, condition)[source]
Bases:
objectBiorthonormal eigentriples of a state matrix.
- Parameters:
values (npt.NDArray[np.complex128]) – Every eigenvalue \(\lambda_k\) (conjugates included), in solver order.
left (npt.NDArray[np.complex128]) – Left eigenvectors as columns, scaled so that \(\mathbf u_k^H\mathbf v_k=1\).
right (npt.NDArray[np.complex128]) – Unit-norm right eigenvectors as columns.
condition (npt.NDArray[np.float64]) – Eigenvalue condition numbers \(1/|\hat{\mathbf u}_k^H\mathbf v_k|\) of the unit-norm pair.
- values: npt.NDArray[np.complex128]
- left: npt.NDArray[np.complex128]
- right: npt.NDArray[np.complex128]
- condition: npt.NDArray[np.float64]
- class HarmonicModel(azimuths, reference_weights, reference_mean, coefficients, method, interpolating, nyquist_order=None, blades=None)[source]
Bases:
objectHarmonic representation \(\mathbf A(\psi)=\sum_h\mathbf A_h e^{jh\psi}\).
- Parameters:
azimuths (npt.NDArray[np.float64]) – The delivered azimuths (rad, wrapped into
[0, 2pi)) the model was fitted to.reference_weights (npt.NDArray[np.float64]) – The interval weights of those azimuths (the reference quadrature rule).
reference_mean (npt.NDArray[np.float64]) – The reference mean \(\sum_\ell w_\ell^{\mathrm{ref}}\mathbf A(\psi_\ell)\) computed from the samples.
coefficients (dict[int, npt.NDArray[np.complex128]]) – \(\mathbf A_h\) for every order present, with \(\mathbf A_{-h}=\overline{\mathbf A_h}\).
method (str) –
"trigonometric"(least-squares trigonometric series) or"piecewise_linear"(exact Fourier coefficients of the periodic piecewise-linear interpolant).interpolating (bool) – Whether the model reproduces every sample exactly (a square trigonometric design). Only an interpolating model makes
grid_error_matrix()exact.nyquist_order (int or None) – The order represented by its cosine only (half the sample count of an interpolating fit to an even number of samples), else
None.blades (int or None) – When set, only orders that are multiples of
bladeswere fitted.
- azimuths: npt.NDArray[np.float64]
- reference_weights: npt.NDArray[np.float64]
- reference_mean: npt.NDArray[np.float64]
- property orders: npt.NDArray[np.int_]
Return every order present (negative and positive), ascending.
- property mean: npt.NDArray[np.float64]
Return the zeroth harmonic \(\mathbf A_0\) (real).
- evaluate(azimuths)[source]
Evaluate the model at the given azimuths.
- Parameters:
azimuths (npt.ArrayLike) – Azimuths in radians (any finite values).
- Returns:
npt.NDArray[np.float64] – The model matrices, shape
(n, *sample_shape).- Return type:
npt.NDArray[np.float64]
- class TowerPassageFit(orders, magnitudes, decay, amplitude, log_r2, condensed)[source]
Bases:
objectFit of the tower-passage law to the azimuth-column harmonics.
- Parameters:
orders (npt.NDArray[np.int]) – The fitted orders (resolved multiples of the blade count).
magnitudes (npt.NDArray[np.float64]) – \(|c_h|\) at those orders.
decay (float) – \(\rho\), the fitted decay per harmonic order.
amplitude (float) – \(A_B\), the fitted magnitude at the blade order.
log_r2 (float) – Coefficient of determination of the fit in \(\log|c_h|\).
condensed (bool) – Whether \(c_h\) is the condensed azimuth stiffness harmonic \(\boldsymbol\ell^T\mathbf A_h\boldsymbol\xi\) (rotor-speed state known) or the azimuth-column norm \(\|\mathbf A_h\mathbf e_{\mathrm{az}}\|\).
- orders: npt.NDArray[np.int_]
- magnitudes: npt.NDArray[np.float64]
- azimuth_consistency(model, rotor_speed, azimuth, *, tolerance=0.0001, basis=None, max_order=None, branches=None, scale_tolerance=0.01)[source]
Test whether the samples are a linearization about a periodic orbit.
- Parameters:
model (HarmonicModel) – Harmonic model of the samples (
"piecewise_linear"recommended).rotor_speed (float) – Rotor speed \(\Omega\) in rad/s.
azimuth (AzimuthStates or int) – The rotor-azimuth states (see
locate_azimuth_state()). The rotor-speed index is needed for the condensed stiffness and damping.tolerance (float, optional) – Absolute tolerance on the residual exponent in 1/s, reported as
absolute_consistent. Default1e-4. It decides the assessment only when no branch is assessed.basis (EigenBasis or None, optional) – Eigentriples of
model.reference_meanif already computed.max_order (int or None, optional) – Highest harmonic order in the sideband sum; default every order.
branches (Sequence[int] or None, optional) – Indices (into the eigenbasis of the reference mean) of the branches the residual is assessed against. Default every oscillatory mode (positive imaginary part) outside the pair.
scale_tolerance (float, optional) – Dimensionless tolerance \(\tau\) of the decay and coalescence ratios. Default 0.01.
estimate_averaging_error()uses its damping tolerance, as a fraction.
- Returns:
AzimuthConsistency – Both exponents of the azimuth/speed pair (the growing one first), the residual exponent and stiffness, the per-branch ratios and the graded assessment.
- Raises:
ValueError – If the model is not of square matrices,
rotor_speed,toleranceorscale_toleranceis invalid, or an index is out of range.- Return type:
Notes
The orbit tangent of an autonomous system linearized about a periodic orbit is a periodic solution of the linear time-periodic model, so one Floquet exponent is exactly zero. The averaged model then has an eigenvalue of second order in the harmonics whose sideband correction cancels it. Both modes of the azimuth/rotor-speed pair are corrected, giving \(\gamma_{1,2}\). In the condensation \(s^2-bs-\kappa=0\) their sum is the speed damping \(b_\gamma\) and their product is \(-\kappa_r\), so the defect is the residual stiffness \(\kappa_r=-\gamma_1\gamma_2\) (zero when one root vanishes) and the residual exponent \(r=\min|\gamma_{1,2}|\). A positive \(b\) with \(\kappa_r=0\) is the physical open-loop rotor-speed divergence and is not a defect.
The residual matters only for the branches it can reach. For a branch \(\lambda_j=-\sigma_j+j\omega_j\) two ratios are formed,
\[\nu_j=\frac{r}{\sigma_j},\qquad \mu_j=\frac{|\kappa_r|}{\omega_j^2+b_\gamma^2/4}.\]\(\nu_j\le\tau\) means the residual root is slower than the branch decay by \(1/\tau\): even fully mixed into the branch it would change the decay rate by at most \(\tau\sigma_j\). \(\mu_j\) is the coalescence condition of the two-state azimuth model: the azimuth pendulum, of decay rate \(|b|/2\), can be tuned to the branch frequency only when the azimuth stiffness reaches \(\omega_j^2+b^2/4\). \(\mu_j\) is the share of that threshold the residual consumes on its own, and \(\sqrt{\mu_j}\) is the natural frequency of the residual pair relative to it. The assessment is
"consistent"when \(\nu_j\le\tau\) for every branch;"inconsistent-and-risky"when some branch has both \(\nu_j>\tau\) and \(\mu_j>\tau\) (the residual is neither slow against the branch decay nor a decade below it in frequency, for \(\tau=0.01\));"inconsistent-but-harmless"otherwise. Without branches the absolute test decides between the first and the last. The test detects but does not locate the defect (for example a trim controller still acting during the linearization revolution).
- azimuth_mean(azimuths, matrices, *, rule='interval')[source]
Return the azimuth mean of a stack of sampled matrices.
- Parameters:
azimuths (npt.ArrayLike) – Delivered azimuths in radians, one per sample.
matrices (npt.ArrayLike) – Stack of samples, shape
(n_azimuth, ...)(state, input or output matrices).rule ({"interval", "arithmetic"}, optional) –
"interval"(default) usesinterval_weights();"arithmetic"the plain sample mean that the MBC transform applies.
- Returns:
npt.NDArray[np.float64] – The weighted mean, with the shape of one sample.
- Raises:
ValueError – If the inputs are inconsistent or the rule is unknown.
- Return type:
npt.NDArray[np.float64]
- azimuth_tangent(mean_matrix, azimuth, *, n_samples=None)[source]
Return the orbit-tangent estimate \(\boldsymbol\xi\) from an averaged model.
- Parameters:
mean_matrix (npt.ArrayLike) – The reference (azimuth-averaged) state matrix of a dense grid.
azimuth (AzimuthStates or int) – The rotor-azimuth position state.
n_samples (int or None, optional) – Number of azimuths that formed the mean. Below 18 a warning is issued: the neutral direction of a coarse mean is itself contaminated by aliasing.
- Returns:
npt.NDArray[np.float64] – The real right eigenvector of the mode with the largest azimuth-state share, normalised to \(\xi_{\mathrm{az}}=1\).
- Raises:
ValueError – If the matrix is invalid or the azimuth index is out of range.
- Warns:
RuntimeWarning – If
n_samplesis below 18, or the azimuth eigenvalue is complex (an oscillatory azimuth pair; the real part of its eigenvector is returned and pinning then preserves the reference only to the size of its imaginary part).- Return type:
npt.NDArray[np.float64]
- delete_azimuth_state(matrices, azimuth)[source]
Delete the rotor-azimuth position state (row and column).
- Parameters:
matrices (npt.ArrayLike) – One state matrix
(n, n)or a stack(n_azimuth, n, n).azimuth (AzimuthStates or int) – The rotor-azimuth position state.
- Returns:
npt.NDArray[np.float64] – Matrices of size
n - 1. The rotor-speed state is kept as a first-order state, so the[q2, q2dot, q1]pairing no longer holds.- Raises:
ValueError – If the matrices are not square or the index is out of range.
- Return type:
npt.NDArray[np.float64]
Notes
This is the conventional Campbell practice of dropping the zero-frequency generator mode. Its eigenvalues (and Floquet multipliers) are exactly those of
zero_azimuth_column()without the zero, because a zero column makes the matrix block-triangular. It therefore biases the reference in the same way; seezero_azimuth_column().
- eigen_basis(matrix)[source]
Return the biorthonormal eigentriples of a square state matrix.
- Parameters:
matrix (npt.ArrayLike) – Square, finite state matrix.
- Returns:
EigenBasis – Eigenvalues with left and right eigenvectors,
u_k^H v_k = 1.- Raises:
ValueError – If the matrix is not square and finite, or an eigenvalue is defective (its left and right eigenvectors are orthogonal to working precision).
- Return type:
- fit_harmonics(azimuths, matrices, *, method='trigonometric', max_order=None, blades=None)[source]
Fit a harmonic model \(\mathbf A(\psi)=\sum_h\mathbf A_h e^{jh\psi}\).
- Parameters:
azimuths (npt.ArrayLike) – Delivered azimuths in radians, one per sample (any order).
matrices (npt.ArrayLike) – Sample stack, shape
(n_azimuth, ...).method ({"trigonometric", "piecewise_linear"}, optional) –
"trigonometric"(default) solves the trigonometric design by least squares. With the defaultmax_orderit is square, orders0..(M-1)//2plus the cosine of orderM/2for an even sample countM, so the model interpolates every sample and the sampling-error identity ofgrid_error_matrix()holds exactly."piecewise_linear"returns the exact Fourier coefficients of the periodic piecewise-linear interpolant of the samples; its \(\mathbf A_0\) equals the interval-weighted mean, which makes it the natural model for the sideband (periodicity) estimate.max_order (int or None, optional) – Highest harmonic order. Default
(M-1)//2(with the Nyquist cosine for the trigonometric fit to an evenM). A trigonometric fit with2*max_order + 1 > Mis rejected.blades (int or None, optional) – Keep only orders that are multiples of
blades(the content an isotropicblades-bladed rotor leaves after MBC). The trigonometric fit is then a least-squares fit and does not interpolate.
- Returns:
HarmonicModel – The fitted model, with the reference (interval) weights and mean.
- Raises:
ValueError – If the inputs are inconsistent,
methodis unknown,max_orderorbladesis not a positive integer, the design has more unknowns than samples, or it is ill-conditioned.- Return type:
- grid_error_matrix(model, azimuths, weights=None)[source]
Return the error of a grid’s mean relative to the reference mean.
- Parameters:
model (HarmonicModel) – Harmonic model of the delivered samples.
azimuths (npt.ArrayLike) – The azimuths (rad) of the grid whose mean is assessed: a subset of the delivered azimuths (a measured sub-grid) or any hypothetical grid (a design).
weights (npt.ArrayLike or None, optional) – The grid’s quadrature weights; default its
interval_weights().
- Returns:
npt.NDArray[np.float64] – \(\mathbf E=\sum_h[W_{\mathrm{grid}}(h)-W_{\mathrm{ref}}(h)]\mathbf A_h\).
- Return type:
npt.NDArray[np.float64]
Notes
Any weighted mean of the samples is \(\sum_h W(h)\mathbf A_h\) exactly when the model interpolates the samples, so for a sub-grid of the delivered azimuths and an interpolating model this equals \(\sum_\ell(w_\ell^{S}-w_\ell^{\mathrm{ref}}) \mathbf A(\psi_\ell)\) to rounding. The zeroth harmonic cancels because \(W(0)=1\) for every consistent rule. On a uniform \(N\)-grid \(W(h)\) vanishes unless \(N\mid h\) (the familiar aliasing rule); on a non-uniform grid it leaks at every order, which this form keeps. For a grid that is not a subset of the samples the result is a model-based prediction that ignores orders above
HarmonicModel.max_order.
- interval_weights(azimuths)[source]
Return the interval (periodic trapezoidal) weights of an azimuth grid.
- Parameters:
azimuths (npt.ArrayLike) – Delivered azimuths in radians, any order, distinct modulo \(2\pi\).
- Returns:
npt.NDArray[np.float64] – One weight per azimuth, in the input order, summing to one. Applied to the samples they give the exact revolution mean of the periodic piecewise-linear interpolant.
- Raises:
ValueError – If the azimuths are empty, non-finite, duplicated after wrapping, or leave a gap larger than half a revolution (a single azimuth always does).
- Return type:
npt.NDArray[np.float64]
Notes
With the azimuths sorted, \(g_\ell=\psi_{\ell+1}-\psi_\ell\) the periodic gaps and \(g_0 \equiv g_M\),
\[w_\ell = \frac{g_{\ell-1}+g_\ell}{4\pi},\]which reduces to \(1/M\) on a uniform grid.
- locate_azimuth_state(state_descriptions, *, ndof2=None)[source]
Locate the rotor-azimuth position and rotor-speed states from descriptions.
- Parameters:
state_descriptions (Sequence[str]) – State descriptions in matrix order (e.g.
MBCResult.state_descriptions).ndof2 (int or None, optional) – Number of second-order DOFs of the
[q2, q2dot, q1]layout. Used to place the rotor-speed state atposition + ndof2when no velocity description of the generator DOF is present.
- Returns:
AzimuthStates – The position index and, when found, the speed index.
- Raises:
ValueError – If no state, or more than one non-velocity state, carries the generator azimuth token
DOF_GeAz, orndof2is inconsistent with the layout.- Return type:
- pin_azimuth(matrices, tangent, azimuth)[source]
Apply tangent-pinned averaging to one matrix or a stack of samples.
Each matrix is replaced by \(\mathbf A'=\mathbf A(\mathbf I-\boldsymbol\xi\mathbf e_{\mathrm{az}}^T)\).
- Parameters:
matrices (npt.ArrayLike) – One state matrix
(n, n)or a stack(n_azimuth, n, n).tangent (npt.ArrayLike) – Tangent \(\boldsymbol\xi\) of length
n; it is rescaled so that its azimuth component is one.azimuth (AzimuthStates or int) – The rotor-azimuth position state.
- Returns:
npt.NDArray[np.float64] – The pinned matrices, same shape as
matrices. Only the azimuth column changes, to \(\mathbf A\mathbf e_{\mathrm{az}}-\mathbf A\boldsymbol\xi\).- Raises:
ValueError – If shapes disagree, the inputs are non-finite, or the tangent’s azimuth component is zero.
- Return type:
npt.NDArray[np.float64]
Notes
\(\mathbf I-\boldsymbol\xi\mathbf e_{\mathrm{az}}^T\) is an oblique projector, so \(\mathbf A'(\psi)\boldsymbol\xi=\mathbf 0\) for every sample: every sub-grid mean has an exact zero eigenvalue and the periodic model an exact unit Floquet multiplier. If \(\boldsymbol\xi\) is a right eigenvector of the reference mean with eigenvalue \(\lambda_{\mathrm{az}}\), the pinned mean is \(\bar{\mathbf A}-\lambda_{\mathrm{az}}\boldsymbol\xi\mathbf e_{\mathrm{az}}^T\) and, by Brauer’s theorem, keeps every other reference eigenvalue unchanged.
- sideband_correction(model, basis, index, rotor_speed, *, max_order=None)[source]
Return the second-order sideband (periodicity) correction of one mode.
- Parameters:
model (HarmonicModel) – Harmonic model of the samples (the
"piecewise_linear"fit is consistent with the interval-weighted reference).basis (EigenBasis) – Eigentriples of the reference mean.
index (int) – Mode index into
basis.values.rotor_speed (float) – Rotor speed \(\Omega\) in rad/s.
max_order (int or None, optional) – Highest harmonic order included; default every order of the model.
- Returns:
complex – \(\widehat{\Delta\lambda}_j=\sum_{h\neq0}\sum_k \frac{(\mathbf u_j^H\mathbf A_{-h}\mathbf v_k) (\mathbf u_k^H\mathbf A_h\mathbf v_j)}{\lambda_j+jh\Omega-\lambda_k}\), the estimate of the Floquet exponent minus the averaged eigenvalue. Every mode is retained in the sum: truncating it to the modes nearest in detuning is not admissible.
- Raises:
ValueError – If
rotor_speedis not positive orindexis out of range.- Return type:
- subgrid_weights(azimuths, indices, *, rule='interval')[source]
Return full-length weights of a sub-grid of the delivered azimuths.
- Parameters:
azimuths (npt.ArrayLike) – All delivered azimuths in radians.
indices (Sequence[int] or npt.ArrayLike) – Positions (into
azimuths) of the samples that form the sub-grid.rule ({"interval", "arithmetic"}, optional) –
"interval"weights the sub-grid by its owninterval_weights();"arithmetic"gives each of its samples1/len(indices).
- Returns:
npt.NDArray[np.float64] – A weight vector aligned with
azimuths, zero off the sub-grid, summing to one.- Raises:
ValueError – If
indicesis empty, out of range or repeated, orruleis unknown, or the sub-grid is invalid forinterval_weights().- Return type:
npt.NDArray[np.float64]
- tower_passage_decay(model, azimuth, *, blades=3)[source]
Fit the tower-passage law to the harmonics of the azimuth column.
- Parameters:
model (HarmonicModel) – Harmonic model of the delivered samples (the azimuth column as delivered, not pinned or zeroed).
azimuth (AzimuthStates or int) – The rotor-azimuth states. With the rotor-speed state the condensed harmonics \(c_h=\boldsymbol\ell^T\mathbf A_h\boldsymbol\xi\) are fitted, otherwise the column norms \(\|\mathbf A_h\mathbf e_{\mathrm{az}}\|\).
blades (int, optional) – Blade count \(B\); only its multiples are fitted. Default 3.
- Returns:
TowerPassageFit – \(|c_h|=A_B\,(h/B)\,\rho^{\,h-B}\) fitted by least squares in \(\log\) over the resolved multiples of \(B\).
- Raises:
ValueError – If the model is not of square matrices,
bladesis not a positive integer, the condensation is singular, or fewer than two blade multiples are resolved with non-zero content.- Return type:
Notes
A tower passage of peak deficit \(\delta\) and harmonic decay \(\rho\) makes the frozen-azimuth derivative of the rotor loads, which is the azimuth column, carry \(|c_h|\propto h\rho^{h-1}\): the derivative of a localised bump whose Fourier coefficients fall as \(\rho^h\). Every other load harmonic of the rotor comes from the same localised inflow, so \(\rho\) sets the decay above the resolved orders that
azimuth_count_table()uses when a branch’s own sensitivity does not decay.
- uniform_azimuths(count, start=0.0)[source]
Return a uniform grid of
countazimuths (rad) starting atstart.- Parameters:
- Returns:
npt.NDArray[np.float64] –
start + 2 pi k / countfork = 0 .. count - 1, wrapped into[0, 2pi).- Raises:
ValueError – If
countis not a positive integer orstartis not finite.- Return type:
npt.NDArray[np.float64]
- weight_kernel(azimuths, weights, orders)[source]
Return the quadrature-weight DFT kernel of an azimuth grid.
The kernel is \(W(h)=\sum_\ell w_\ell e^{jh\psi_\ell}\), the aliasing kernel of the grid.
- Parameters:
azimuths (npt.ArrayLike) – Azimuths in radians.
weights (npt.ArrayLike) – One weight per azimuth.
orders (Sequence[int] or npt.ArrayLike) – Harmonic orders \(h\) at which to evaluate the kernel.
- Returns:
npt.NDArray[np.complex128] – \(W(h)\) for each requested order. \(W(0)=\sum_\ell w_\ell\). On a uniform \(N\)-grid starting at \(\psi_0\) with equal weights, \(W(h)=e^{jh\psi_0}\) when \(N\mid h\) and zero otherwise.
- Raises:
ValueError – If the weights do not match the azimuths or are non-finite.
- Return type:
npt.NDArray[np.complex128]
- zero_azimuth_column(matrices, azimuth)[source]
Zero the rotor-azimuth column (naive neutralisation, kept for comparison).
- Parameters:
matrices (npt.ArrayLike) – One state matrix
(n, n)or a stack(n_azimuth, n, n).azimuth (AzimuthStates or int) – The rotor-azimuth position state.
- Returns:
npt.NDArray[np.float64] – A copy with the azimuth column set to zero.
- Raises:
ValueError – If the matrices are not square or the index is out of range.
- Return type:
npt.NDArray[np.float64]
Notes
This is
pin_azimuth()with \(\boldsymbol\xi=\mathbf e_{\mathrm{az}}\). It restores an exact zero eigenvalue and the perturbative regime of the sampling error, but \(\mathbf e_{\mathrm{az}}\) is not an eigenvector of the mean, so it changes the reference spectrum: the mean of the azimuth column carries the rotation of the steady cyclic blade deflection, which is physical. On archived floating-turbine linearizations it moved platform damping by up to 7 % of its value. Preferpin_azimuth()withazimuth_tangent()of a dense grid.
Per-branch error estimates of the azimuth-averaged model.
The averaged (Coleman/MBC) analysis of a rotating turbine carries three errors that are usually only guessed at. Each is predicted here from the averaged model and the harmonic content of the samples, without a periodic (Floquet) solution:
Sampling. The error of a coarse or hypothetical azimuth grid. Its matrix error is exactly a quadrature-weight error times the harmonic content (
vane.modal.averaging.grid_error_matrix()), and its effect on each branch follows from eigenvalue perturbation of the averaged eigensolution: first or second order, or, when the rotor-azimuth state is present, a structured predictor that keeps the azimuth column as an exact rank-one term and the near-Jordan azimuth/rotor-speed modes exactly. The structured predictor also yields a predicted modal assurance against the reference branch, the hybridisation flag.Quadrature. The difference between the arithmetic sample mean (what the MBC transform applies) and the interval-weighted mean on the delivered grid.
Periodicity. The second-order sideband estimate of the Floquet exponent minus the averaged eigenvalue (
vane.modal.averaging.sideband_correction()).
All terms are reported in the same units: the relative change of the damped
frequency \(|\operatorname{Im}\lambda|\) and of the damping ratio
\(\zeta=-\operatorname{Re}\lambda/|\lambda|\), in percent of the reference value.
estimate_averaging_error() assembles them per branch, together with the azimuth
consistency test. azimuth_count_table() designs the azimuth count from the
\(\operatorname{lcm}(N, B)\) aliasing rule and the fitted harmonics, and
commensurate_time_step() suggests a time step that puts every target azimuth on
a whole number of steps.
- class AliasTail(model, own_decay, tower_decay, own_level, peak_level, anchor_order, peak_order)[source]
Bases:
objectModel of one branch’s harmonic sensitivity above the resolved orders.
- Parameters:
model (str) –
"own decay"(the branch’s own sensitivity peaks inside the resolved multiples of \(B\) and its fitted trend at least halves after),"tower law"(it does not, and the tower-passage decay of the azimuth column is used),"no content"(the sensitivity is rounding, the tail is zero) or"unresolved"(no tail can be justified from the samples).own_decay (float) – Fitted decay per order of the branch’s own sensitivity (
nanif unused).tower_decay (float) – Tower-passage decay per order (
nanif unused).own_level (float) – Level of the smallest own-decay envelope through every fitted point, at
anchor_order(1/s).peak_level (float) – Largest observed sensitivity \(\max_h s_j(h)\) (1/s), the anchor of the tower-law envelope.
anchor_order (int) – The highest resolved multiple of \(B\).
peak_order (int) – The resolved order of the largest observed sensitivity.
- class AveragingErrorEstimate(branches, azimuth, grid, reference_mean, rotor_speed, method, damping_tolerance_pct, frequency_tolerance_pct, max_condition=1000.0, sampling_status=None, sampling_first_alias=None, reference_resolved_order=None)[source]
Bases:
objectPer-branch error estimates of an azimuth-averaged model.
- Parameters:
branches (dict[str, BranchErrorEstimate]) – The assessed branches by label.
azimuth (AzimuthConsistency or None) – The azimuth consistency test, when the rotor-azimuth state was given.
grid (GridReport) – Quadrature properties of the delivered grid.
reference_mean (npt.NDArray[np.float64]) – The interval-weighted reference mean.
rotor_speed (float) – Rotor speed (rad/s).
method (str) – Branch predictor used for the sampling and quadrature terms.
damping_tolerance_pct (float) – Damping-ratio tolerance, percent of the reference value.
frequency_tolerance_pct (float) – Frequency tolerance, percent of the reference value.
max_condition (float, optional) – Eigenvalue condition number above which a branch is ill-conditioned.
sampling_status (str or None, optional) –
"measured"(a sub-grid of the delivered samples),"resolved"(a predicted grid whose first alias order \(\operatorname{lcm}(N,B)\) lies within the resolved orders of the reference),"unresolved: needs denser reference ..."otherwise,Nonewithout a grid.sampling_first_alias (int or None, optional) – \(\operatorname{lcm}(N,B)\) of a predicted grid of \(N\) azimuths.
reference_resolved_order (int or None, optional) – The highest harmonic order the reference samples resolve.
- branches: dict[str, BranchErrorEstimate]
- azimuth: AzimuthConsistency | None
- grid: GridReport
- reference_mean: npt.NDArray[np.float64]
- property azimuth_assessment: str | None
Return the graded azimuth assessment,
Nonewithout the azimuth state.- Returns:
str or None –
"consistent","inconsistent-but-harmless"or"inconsistent-and-risky".
- property within_tolerance: bool
Return whether every branch is within tolerance and the azimuth is not risky.
A harmless azimuth inconsistency (see
azimuth_consistency()) does not void the branch budget; a risky one does.
- class AzimuthCountRow(count, first_alias_order, multiple_of_blades, resolved_damping_error_pct, resolved_frequency_error_pct, unresolved_damping_bound_pct, total_damping_bound_pct, min_tracking_mac, hybridised, within_tolerance, resolved_damping_by_branch, unresolved_damping_by_branch, alias_resolved=True, resolved_within_tolerance=False)[source]
Bases:
objectPredicted averaging error of one uniform azimuth count.
- Parameters:
count (int) – Number of uniform azimuths \(N\).
first_alias_order (int) – \(\operatorname{lcm}(N, B)\).
multiple_of_blades (bool) – \(B \mid N\).
resolved_damping_error_pct (float) – Worst damping-ratio error over branches and start azimuths from the resolved harmonics (exact eigenvalues of the model’s uniform mean).
resolved_frequency_error_pct (float) – The same for the damped frequency.
unresolved_damping_bound_pct (float) – Worst first-order, worst-phase estimate of the aliases above the model’s highest order, from the documented tail model (
nanwhen a branch’s tail is unresolved or extrapolation is off). It is a model-based estimate, not a bound.total_damping_bound_pct (float) – Worst over branches of resolved plus tail estimate (
nanwhen any tail is unresolved).min_tracking_mac (float) – Lowest MAC with which a branch was tracked.
hybridised (bool) – Some branch was tracked with a MAC below the hybridisation threshold.
within_tolerance (bool) –
total_damping_bound_pctis finite and within the tolerance: the extrapolated decision.resolved_damping_by_branch (dict[str, float]) – Worst resolved damping-ratio error per branch.
unresolved_damping_by_branch (dict[str, float]) – Tail estimate per branch (
nanwhen unresolved).alias_resolved (bool, optional) – \(\operatorname{lcm}(N,B)\) lies within the orders the model resolves, so the resolved error contains the first alias of the count.
resolved_within_tolerance (bool, optional) –
alias_resolved, not hybridised and the resolved error within the tolerance: the decision supported by the samples.
- class AzimuthCountTable(rows, recommended, blades, damping_tolerance_pct, extrapolated, recommended_resolved=None, recommended_extrapolated=None, extrapolation_status='not requested', tails=<factory>, resolved_order=0)[source]
Bases:
objectAzimuth-count design table and its two recommendations.
- Parameters:
rows (tuple[AzimuthCountRow, ...]) – One row per evaluated count, ascending.
recommended (int or None) – The recommendation supported by the samples, equal to
recommended_resolved.blades (int) – Blade count used for the lcm rule.
damping_tolerance_pct (float) – The damping-ratio tolerance, percent.
extrapolated (bool) – Whether a tail estimate was formed for at least one branch.
recommended_resolved (int or None, optional) – The smallest count whose first alias the model resolves and whose resolved error meets the tolerance. The resolved error is exact for the content the samples resolve, so this is the recommended count.
recommended_extrapolated (int or None, optional) – The smallest count whose resolved error plus tail estimate meets the tolerance. A model-based estimate, not a bound;
Nonewhen a tail is unresolved.extrapolation_status (str, optional) –
"extrapolated","not requested"or"unresolved: needs denser linearization ..."naming the branches whose tail cannot be modelled.tails (dict[str, AliasTail], optional) – The tail model of each branch.
resolved_order (int, optional) – The highest harmonic order the model resolves.
- rows: tuple[AzimuthCountRow, ...]
- class BranchErrorEstimate(label, index, eigenvalue, frequency_hz, damping_ratio, sampling, sampling_dominant_order, quadrature, periodicity, hybridised, within_tolerance, condition=nan, well_conditioned=True, sampling_exact=None, mixed=False, sampling_resolved=True)[source]
Bases:
objectError budget of one branch of the averaged model.
- Parameters:
label (str) – Branch label.
index (int) – Index of the branch in the eigenbasis of the reference mean.
eigenvalue (complex) – Reference eigenvalue (1/s).
frequency_hz (float) – Reference damped frequency \(|\operatorname{Im}\lambda|/2\pi\).
damping_ratio (float) – Reference damping ratio.
sampling (ErrorEstimate or None) – Predicted error of the requested azimuth grid,
Noneif no grid was given.sampling_dominant_order (int or None) – Harmonic order contributing most to the first-order sampling error.
quadrature (ErrorEstimate) – Arithmetic mean versus the interval-weighted mean on the delivered grid.
periodicity (ErrorEstimate) – Second-order sideband (Floquet) estimate.
hybridised (bool) – The sampling term (exact when available) or the quadrature term has hybridised.
within_tolerance (bool) – The branch is well conditioned, its sampling term is resolved, every term is within the tolerances and none has hybridised or mixed.
condition (float) – Eigenvalue condition number \(\kappa_j=1/|\hat{\mathbf u}_j^H \hat{\mathbf v}_j|\) of the unit-norm left and right eigenvectors.
well_conditioned (bool) – \(\kappa_j\) does not exceed
max_condition. Above it the perturbative terms (sampling prediction, quadrature, periodicity) cannot be trusted, and the branch is reported as ill-conditioned.sampling_exact (ErrorEstimate or None) – The sampling error from the exact trial eigenproblem of the grid mean (the delivered samples for a measured sub-grid, the harmonic model otherwise),
Nonewhen not computed.mixed (bool) – The general mode-mixing check flagged the sampling term (exact when available) or the quadrature term.
sampling_resolved (bool) – The sampling term is a measurement (a sub-grid of the delivered samples) or a prediction whose first alias order the reference resolves.
Falsemeans the term is only the resolved part of the error and the branch cannot be accepted for that grid (seeAveragingErrorEstimate.sampling_status).
- sampling: ErrorEstimate | None
- quadrature: ErrorEstimate
- periodicity: ErrorEstimate
- sampling_exact: ErrorEstimate | None = None
- property sampling_term: ErrorEstimate | None
exact when available.
- Returns:
ErrorEstimate or None –
sampling_exactif computed, elsesampling.- Type:
Return the sampling term used for the assessment
- class CommensurateStep(time_step, steps_per_azimuth, requested_time_step, relative_change, azimuth_spacing_deg, azimuth_per_step_deg)[source]
Bases:
objectA time step that places every target azimuth on a whole number of steps.
- Parameters:
time_step (float) – Suggested time step (s), never larger than the requested one.
steps_per_azimuth (int) – Whole number of steps between consecutive target azimuths.
requested_time_step (float) – The requested time step (s).
relative_change (float) –
(requested - suggested) / requested.azimuth_spacing_deg (float) – Target azimuth spacing,
360 / countdegrees.azimuth_per_step_deg (float) – Rotor rotation per suggested step, degrees.
- class ErrorEstimate(shift, frequency_error_pct, damping_error_pct, mac, hybridised, method, mixing_mac=nan, mixed=False, shift_bound=nan)[source]
Bases:
objectOne predicted error term of a branch.
- Parameters:
shift (complex) – Predicted eigenvalue change (1/s) relative to the reference branch.
frequency_error_pct (float) – Relative change of the damped frequency, percent (
nanfor a real reference eigenvalue).damping_error_pct (float) – Relative change of the damping ratio, percent (
nanfor an undamped reference).mac (float) – Predicted modal assurance of the perturbed eigenvector with the reference one (
nanwhen the term predicts no eigenvector).hybridised (bool) –
macis below the hybridisation threshold.method (str) –
"first","second","structured","exact"or"sideband".mixing_mac (float, optional) – Modal assurance of the general mode-mixing check: the reference eigenvector against its counterpart in the perturbed model, from the exact trial eigenproblem or from an exact solve over the modes strongly coupled to the branch, independent of the azimuth subspace (
nanwhen not evaluated).mixed (bool, optional) –
mixing_macis below the hybridisation threshold: the branch has mixed with another mode, whatever that mode is.shift_bound (float, optional) – First-order conditioning bound \(\kappa_j\|\mathbf E\|_2\) on \(|\delta\lambda|\) (1/s),
nanwhen not evaluated.
- within(damping_tolerance_pct, frequency_tolerance_pct)[source]
Return whether both errors are within the tolerances and nothing mixed.
- Parameters:
- Returns:
bool –
Truewhen neither error exceeds its tolerance (an undefined, NaN error is not checked; an infinite one exceeds it) and the branch has neither hybridised nor mixed.- Return type:
- class GridReport(n_azimuths, max_gap_deviation_pct, kernel_orders, kernel_magnitude)[source]
Bases:
objectQuadrature properties of the delivered azimuth grid.
- Parameters:
n_azimuths (int) – Number of delivered azimuths.
max_gap_deviation_pct (float) – Largest deviation of a gap from the uniform spacing, percent.
kernel_orders (npt.NDArray[np.int]) – Orders
1 .. n_azimuths - 1.kernel_magnitude (npt.NDArray[np.float64]) – \(|W_{\mathrm{ref}}(h)|\) of the interval weights at those orders: the fraction of each harmonic that leaks into the reference mean (zero to rounding on a uniform grid).
- kernel_orders: npt.NDArray[np.int_]
- kernel_magnitude: npt.NDArray[np.float64]
- alias_free_azimuth_count(blades, highest_order)[source]
Return the smallest uniform azimuth count free of aliases up to an order.
- Parameters:
- Returns:
int – The smallest \(N\) with \(\operatorname{lcm}(N, B)\) greater than
highest_order.- Raises:
ValueError – If
bladesorhighest_orderis not a positive integer.- Return type:
- azimuth_count_table(model, branches=None, *, blades=3, counts=range(5, 37), starts=24, damping_tolerance_pct=1.0, extrapolate=True, max_alias_order=None, mac_threshold=0.9, tail_decay=None)[source]
Predict the averaging error of uniform azimuth counts and recommend one.
- Parameters:
model (HarmonicModel) – Harmonic model of a dense set of samples (the interpolating trigonometric fit of
fit_harmonics()). Branch errors are measured against its zeroth harmonic.branches (Mapping[str, int | complex] or None, optional) – Branches to assess (as for
estimate_averaging_error(), resolved against the eigenbasis ofmodel.mean). Default every oscillatory mode.blades (int, optional) – Blade count \(B\) for the \(\operatorname{lcm}(N, B)\) rule. Default 3.
counts (Iterable[int], optional) – Uniform azimuth counts to evaluate. Default 5 to 36.
starts (int, optional) – Start azimuths per count, spread over one grid spacing; the worst is kept.
damping_tolerance_pct (float, optional) – Damping-ratio tolerance, percent of the reference value.
extrapolate (bool, optional) – Form the tail estimate of the aliases above the model’s highest order.
max_alias_order (int or None, optional) – Highest alias order summed in the tail estimate; default
Nonesums the geometric envelope to convergence, so the estimate does not depend on it.mac_threshold (float, optional) – Tracking MAC below which a count is flagged hybridised.
tail_decay (float or TowerPassageFit or None, optional) – Tower-passage decay per order for branches whose own sensitivity does not decay within the resolved orders, either a number in \((0,1)\) or the fit of
tower_passage_decay()to the delivered samples (it is ignored when its \(R^2\) in log is below 0.5).Noneleaves such branches unresolved.
- Returns:
AzimuthCountTable – One row per count, the resolved and the extrapolated recommendation, the tail model of every branch and the extrapolation status.
- Raises:
ValueError – If an argument is invalid or the model is not of square matrices.
- Return type:
Notes
The resolved error is exact for the content the samples resolve: the exact eigenvalues of the uniform \(N\)-mean of the fitted model, worst over start azimuths. It contains the first alias of \(N\) only when \(\operatorname{lcm}(N,B)\) lies within the resolved orders (
alias_resolved); only such counts are accepted, andrecommended_resolvedis the smallest of them meeting the tolerance. A 36-azimuth reference therefore confirms 5, 6, 9, 12 and 15 but not 7, 8, 10 or 13, whose first aliases (21 to 39) it cannot observe: for those the resolved error is only a lower estimate, and a denser reference is needed to confirm them.The tail estimate is first order and worst phase, \(\sum_{m}\hat s_j(m\,\operatorname{lcm}(N,B))/\sigma_j\) over the aliases above the model’s highest order, with an envelope \(\hat s_j\) of the sensitivity \(s_j(h)=|\mathbf u_j^H\mathbf A_h\mathbf v_j|+|\mathbf u_j^H\mathbf A_{-h} \mathbf v_j|\):
own decay: when \(s_j\) peaks inside the resolved multiples of \(B\) and its log-linear fit from \(2B\) at least halves over the fitted orders, the smallest geometric envelope of that decay through every fitted point;
tower law: otherwise, \(\max_h s_j(h)\,(h/H)\,\rho^{h-H}\) from the highest resolved multiple \(H\), with the tower-passage decay \(\rho\) of the azimuth column (
tail_decay), the law of the frozen-azimuth derivative of a localised inflow deficit;neither: the branch is
"unresolved", its tail and every total arenan, and the table reports"unresolved: needs denser linearization"instead of a recommendation.
Both envelopes are summable, so the estimate is independent of
max_alias_order. It is a model-based extrapolation of orders the samples cannot observe, not a bound: on new OpenFAST linearizations of two floating turbines this estimate from 36 azimuths was exceeded at 13 azimuths (and, on the delivered column, at 18) and was up to 30 times conservative elsewhere.recommended_extrapolatedis therefore an estimate, andrecommendedis the resolved count. With the delivered azimuth column the first-order tail is not valid (the near-Jordan azimuth/speed pair); evaluate the table on a pinned or neutralised model.
- commensurate_time_step(rotor_speed, count, requested_time_step)[source]
Suggest a time step close to a requested one that is commensurate with the grid.
- Parameters:
- Returns:
CommensurateStep – \(\Delta t=T/(Nk)\) with \(T=2\pi/\Omega\) and \(k\) the smallest whole number of steps per azimuth interval for which \(\Delta t\le\)
requested_time_step.- Raises:
ValueError – If an argument is not positive and finite.
- Return type:
Notes
A linearization snapshot is taken at the first time step at or past its target azimuth, so on a time step that does not divide the azimuth interval the delivered grid is non-uniform by up to \(\Omega\Delta t\). With a commensurate step and the rotor at the trimmed speed every snapshot falls on its target and the interval and arithmetic means coincide.
- estimate_averaging_error(azimuths, matrices, rotor_speed, *, branches=None, azimuth=None, grid=None, method='auto', damping_tolerance_pct=1.0, frequency_tolerance_pct=0.1, periodicity_order=None, consistency_tolerance=0.0001, mac_threshold=0.9, subspace_share=0.05, exact=True, max_condition=1000.0, mixing_coupling=0.005, blades=3)[source]
Estimate the error of each branch of an azimuth-averaged model from the samples.
- Parameters:
azimuths (npt.ArrayLike) – Delivered azimuths in radians (e.g.
np.deg2rad(result.azimuths_deg)).matrices (npt.ArrayLike) – Fixed-frame state matrices, shape
(n_azimuth, n, n)(e.g.result.per_azimuth_aof an MBC result kept withretain_per_azimuth=True).rotor_speed (float) – Rotor speed \(\Omega\) in rad/s.
branches (Mapping[str, int | complex] or None, optional) – Branches to assess: label to mode index of the reference mean or to an approximate eigenvalue (matched to the nearest one, on the same side of the real axis). Default every oscillatory mode (
mode_00, … by frequency).azimuth (AzimuthStates or None, optional) – The rotor-azimuth states (
locate_azimuth_state()). Enables the consistency test and the structured predictor.grid (int or npt.ArrayLike or None, optional) – The azimuth grid whose sampling error is predicted: explicit azimuths (a sub-grid of the delivered ones is measured exactly; any other grid is predicted from the interpolating harmonic model) or a count
Nfor a hypothetical uniform grid starting at the first delivered azimuth. For the worst phase over start azimuths useazimuth_count_table().Noneskips the sampling term.method ({"auto", "first", "second", "structured"}, optional) – Branch predictor for the sampling and quadrature terms.
"auto"uses"structured"whenazimuthis given and"second"otherwise.damping_tolerance_pct (float, optional) – Tolerances (percent of the reference value) for
within_tolerance. The damping tolerance, as a fraction, is also the scale tolerance of the azimuth consistency test.frequency_tolerance_pct (float, optional) – Tolerances (percent of the reference value) for
within_tolerance. The damping tolerance, as a fraction, is also the scale tolerance of the azimuth consistency test.periodicity_order (int or None, optional) – Highest harmonic order of the sideband estimate; default
(M - 1) // 2.consistency_tolerance (float, optional) – Absolute tolerance on the azimuth residual exponent, 1/s, reported as
azimuth.absolute_consistent; the assessment uses the scaled test.mac_threshold (float, optional) – MAC below which a branch is flagged hybridised or mixed.
subspace_share (float, optional) – Azimuth/speed participation share retained exactly by the structured predictor.
exact (bool, optional) – Also solve the trial eigenproblem of the grid mean (one dense eigen-solution) for the exact sampling term and the trial form of the mode-mixing check. Default
True.max_condition (float, optional) – Eigenvalue condition number above which a branch is ill-conditioned. Default
1e3.mixing_coupling (float, optional) – Coupling-to-detuning ratio of the predicted mode-mixing check (see
predict_branch_shift()).blades (int, optional) – Blade count \(B\) of the \(\operatorname{lcm}(N,B)\) aliasing rule that decides whether a predicted grid is resolved. Default 3; use 1 for an anisotropic rotor.
- Returns:
AveragingErrorEstimate – The per-branch budget, the azimuth consistency test and the grid report.
- Raises:
ValueError – If the inputs are inconsistent or a tolerance is not positive.
- Return type:
Notes
The sampling term of the analyst’s own full grid cannot be measured from that grid; assess interleaved sub-grids (
grid=azimuths[::2]) or extrapolate the harmonic model withazimuth_count_table(). The quadrature and periodicity terms are fully a priori.A grid that is not a sub-grid of the delivered samples is predicted from the interpolating harmonic model, which holds the orders up to the Nyquist order of the reference and nothing above. An \(N\)-azimuth grid of a \(B\)-bladed rotor is first aliased at \(\operatorname{lcm}(N,B)\). When that order lies above the resolved orders of the reference, the predicted term is only the resolved part of the error: the branch is reported with
sampling_resolved=Falseand is not accepted, andsampling_statusasks for a denser reference. On OpenFAST linearizations of two floating turbines, predictions from a 36-azimuth reference were exact for 12 azimuths (first alias 12) but recovered only 7 to 133 % of the error of 8, 10, 13 and 18 azimuths (first aliases 24 to 39 and the order-18 sine), while the same estimate built from a 72-azimuth reference recovered them all.The predicted terms are perturbative and assume a well-conditioned branch: the first-order sensitivity of \(\lambda_j\) to a matrix error is \(\kappa_j\) (the pseudospectral bound \(|\delta\lambda_j|\le\kappa_j\|\mathbf E\|_2\), reported as
shift_bound). A branch with \(\kappa_j>\)max_conditionis reported as ill-conditioned; withexact=Trueits sampling term is still exact, but its quadrature and periodicity terms are not trusted. On the IEA 22 MW semi-submersible at 8 m/s the platform sway branch (\(\kappa\approx1.2\times 10^3\)) is under-predicted six- to eight-fold by every predictor on some sub-grids, which this check flags and the exact term measures.
- first_alias_order(count, blades)[source]
Return the lowest harmonic order that aliases into a uniform
count-mean.- Parameters:
- Returns:
int – \(\operatorname{lcm}(N, B)\). When the periodic content after MBC lies only at multiples of \(B\) (an isotropic rotor), the \(N\)-sample mean is aliased only by common multiples of \(N\) and \(B\), so an \(N\) that is not a multiple of \(B\) pushes the first alias from \(N\) to \(BN\).
- Raises:
ValueError – If either argument is not a positive integer.
- Return type:
- predict_branch_shift(basis, error, branches, *, method='second', azimuth=None, subspace_share=0.05, mac_threshold=0.9, mixing_coupling=0.005)[source]
Predict the change of reference branches caused by a matrix error.
- Parameters:
basis (EigenBasis) – Eigentriples of the reference mean \(\mathbf A_0\).
error (npt.ArrayLike) – Matrix error \(\mathbf E\) (the perturbed model is \(\mathbf A_0+\mathbf E\)).
branches (Sequence[int]) – Mode indices into
basis.values.method ({"first", "second", "structured"}, optional) –
"first": \(P_{jj}\);"second"(default): adds \(\sum_{k\neq j}P_{jk}P_{kj}/(\lambda_j-\lambda_k)\), with \(P=\mathbf U^H\mathbf E\mathbf V\);"structured": the azimuth column of \(\mathbf E\) is kept as an exact rank-one term and the modes with azimuth or rotor-speed participation abovesubspace_shareare coupled exactly; every other mode keeps its first-order shift. Required when the rotor-azimuth state is present with its delivered column, where the azimuth/speed pair is nearly defective and ordinary perturbation breaks down.azimuth (AzimuthStates or int or None, optional) – The rotor-azimuth states; required for
"structured".subspace_share (float, optional) – Participation share above which a mode is retained exactly. Default 0.05.
mac_threshold (float, optional) – Predicted MAC below which a branch is flagged hybridised or mixed. Default 0.9.
mixing_coupling (float, optional) – Coupling-to-detuning ratio above which a mode joins the branch’s cluster in the general mode-mixing check. Default 0.005.
- Returns:
list[ErrorEstimate] – One estimate per requested branch, in order. For the first- and second-order methods the MAC is that of the first-order eigenvector correction. Every estimate also carries the general mode-mixing MAC (
mixing_mac,mixed) and the first-order conditioning boundshift_bound.- Raises:
ValueError – If the error matrix does not match the basis, an index is out of range, the method is unknown,
mixing_couplingis not positive, or"structured"is requested withoutazimuth.- Return type:
Notes
The hybridisation flag of
"structured"sees only mixing through the azimuth subspace. The mixing check is independent of it: two modes are linked when their coupling \(\sqrt{|P_{ik}P_{ki}|}\) exceedsmixing_couplingtimes their detuning, and every mode connected to the branch by a chain of links is coupled exactly, so two branches that alias into each other (for example two heavily damped flap modes on a three-azimuth grid) are caught as well. On a coarse grid of a non-normal model the cluster can approach the whole model, and the cost that of the trial eigenproblem. The conditioning bound uses the eigenvalue condition number \(\kappa_j=\|\mathbf u_j\|\|\mathbf v_j\|/|\mathbf u_j^H\mathbf v_j|\): \(|\delta\lambda_j|\le\kappa_j\|\mathbf E\|_2\) to first order (the pseudospectral radius). It is rigorous to first order but on multi-scale models it is far from tight; the branch-level condition number is whatestimate_averaging_error()tests.
- trial_branch_shift(basis, trial, branches, *, mac_threshold=0.9)[source]
Solve a trial eigenproblem and track the reference branches in it.
- Parameters:
basis (EigenBasis) – Eigentriples of the reference mean \(\mathbf A_0\).
trial (npt.ArrayLike) – The trial (perturbed) state matrix \(\mathbf A_0+\mathbf E\), for example a coarse sub-grid mean.
branches (Sequence[int]) – Mode indices into
basis.values.mac_threshold (float, optional) – Tracking MAC below which a branch is flagged mixed (and hybridised). Default 0.9.
- Returns:
list[ErrorEstimate] – One
"exact"estimate per branch: the eigenvalue of the trial matrix whose eigenvector has the largest MAC with the reference eigenvector (on the same side of the real axis), with that MAC as bothmacandmixing_mac.- Raises:
ValueError – If the trial matrix does not match the basis or an index is out of range.
- Return type:
Notes
This is the trial-eigenproblem form of the mode-mixing check: the exact answer for the given matrix, at the cost of one dense eigen-solution, and the reference for the predicted terms.
Campbell diagram
Campbell diagram assembly and nP excitation / resonance flagging.
Assembly of Campbell-diagram data from tracked modes.
A Campbell diagram plots modal natural frequency (and damping) against an operating
parameter — rotor speed or wind speed — with one curve per tracked mode. This
module pairs the mode tracks from vane.modal.identify_modes() with the
operating-parameter value of each operating point, producing a structure ready for
plotting or resonance analysis.
Heavily damped (near-critical) modes, which vane.modal.identify_modes() tracks
in a tier of their own, are carried separately in
CampbellDiagram.heavily_damped_tracks: they are drawn distinctly (or left
out) and never enter the resonance analysis, since a mode damped near critically
cannot resonate.
- class CampbellDiagram(parameter_values, parameter_name, tracks, n_operating_points, heavily_damped_tracks=<factory>)[source]
Bases:
objectCampbell-diagram data: tracked modes against an operating parameter.
- Parameters:
parameter_values (npt.NDArray[np.float64]) – Operating-parameter value (e.g. rotor speed in rev/min) at each operating point, indexed by operating-point number, shape
(n_operating_points,).parameter_name (str) – Name of the operating parameter (e.g.
"rotor_speed_rpm").tracks (list[ModeTrack]) – The tracked modes, ordered by first natural frequency.
n_operating_points (int) – Number of operating points.
heavily_damped_tracks (list[ModeTrack]) – Tracks of heavily damped (near-critical) modes, kept apart from
tracks: plotted distinctly and left out of resonance detection. Empty when the identification had none or they were not included.
- parameter_values: npt.NDArray[np.float64]
- track_curve(track)[source]
Return the operating-parameter, frequency, and damping arrays of a track.
- Parameters:
track (ModeTrack) – A track belonging to this diagram.
- Returns:
tuple of npt.NDArray[np.float64] –
(parameter_values, natural_frequencies_hz, damping_ratios)along the track, each of length equal to the track’s span.- Return type:
tuple[npt.NDArray[np.float64], npt.NDArray[np.float64], npt.NDArray[np.float64]]
- build_campbell(result, parameter_values, *, parameter_name='rotor_speed_rpm', include_heavily_damped=True)[source]
Build a Campbell diagram from identified mode tracks.
- Parameters:
result (IdentificationResult) – The cross-operating-point mode tracks.
parameter_values (Sequence[float]) – Operating-parameter value at each operating point, in operating-point order; its length must equal
result.n_operating_points.parameter_name (str, optional) – Name of the operating parameter (default
"rotor_speed_rpm").include_heavily_damped (bool, optional) – Carry the identification’s heavily damped tracks into the diagram’s
heavily_damped_tracks(defaultTrue);Falseomits them.
- Returns:
CampbellDiagram – The assembled diagram.
- Raises:
ValueError – If
parameter_valueslength does not match the number of operating points, or the values are not a finite, one-dimensional, strictly increasing sweep axis (the excitation/resonance analysis interpolates against this axis, so duplicate, non-monotone, or non-finite values would give wrong crossings or raw errors).- Return type:
- campbell_from_solutions(solutions, parameter_values, *, parameter_name='rotor_speed_rpm', frequency_weight=0.5, mac_threshold=0.5, heavily_damped_threshold=0.99, rotor_speeds_rpm=None)[source]
Identify mode tracks from solutions and build a Campbell diagram.
- Parameters:
solutions (Sequence[ModalSolution]) – Modal solutions, one per operating point, in operating-point order.
parameter_values (Sequence[float]) – Operating-parameter value at each operating point.
parameter_name (str, optional) – Name of the operating parameter.
frequency_weight (float, optional) – Passed through to
vane.modal.identify_modes().mac_threshold (float, optional) – Passed through to
vane.modal.identify_modes().heavily_damped_threshold (float or None, optional) – Passed through to
vane.modal.identify_modes();Nonetracks every mode together.rotor_speeds_rpm (Sequence[float] or None, optional) – Rotor speed (rev/min) at each operating point, so blade whirl modes are classified by their rotating-frame damping. Defaults to
parameter_valueswhenparameter_nameis"rotor_speed_rpm".
- Returns:
CampbellDiagram – The assembled diagram.
- Return type:
Per-rev excitation lines and resonance detection.
A rotor turning at Omega excites the structure at integer multiples of the
rotational frequency: the nP lines, with frequency n * rpm / 60 Hz. A
potential resonance occurs where a mode’s natural-frequency curve crosses an nP
line. For a three-bladed rotor the dominant excitations are 1P (rotor imbalance)
and the blade-passing 3P, 6P, and 9P lines.
- class ResonanceCrossing(track_index, harmonic, rotor_speed_rpm, frequency_hz, damping_ratio, severity)[source]
Bases:
objectA crossing of a mode curve with an
nPexcitation line.- Parameters:
track_index (int) – Index of the crossing mode track within the diagram.
harmonic (int) – The excitation harmonic
n(thenPline).rotor_speed_rpm (float) – Rotor speed at the crossing, interpolated between operating points.
frequency_hz (float) – Frequency at the crossing, in hertz.
damping_ratio (float) – Damping ratio of the resonating mode at the crossing, interpolated between operating points.
severity (ResonanceSeverity) – Damping-aware severity of the crossing.
- severity: ResonanceSeverity
- class ResonanceSeverity(*values)[source]
Bases:
EnumDamping-aware severity of a resonance crossing.
A crossing’s danger depends on how lightly damped the resonating mode is: a near-undamped mode at a crossing can build large response, while a well-damped one is benign.
- HIGH = 'high'
- MEDIUM = 'medium'
- LOW = 'low'
- excitation_frequencies(rotor_speed_rpm, harmonics)[source]
Return the
nPexcitation-line frequencies at each rotor speed.- Parameters:
rotor_speed_rpm (npt.NDArray[np.float64]) – Rotor speed in rev/min, shape
(n_points,).harmonics (Sequence[int]) – The harmonics
nto evaluate.
- Returns:
dict[int, npt.NDArray[np.float64]] – Mapping of each harmonic
nto itsnPfrequency (Hz) at every rotor speed,n * rpm / 60.- Raises:
ValueError – If
harmonicscontains a non-positive, fractional, non-finite, or duplicate value.- Return type:
- find_resonances(diagram, harmonics=(1, 3, 6, 9), *, operating_range=None)[source]
Find where mode curves cross the
nPexcitation lines.A crossing is detected wherever
natural_frequency - n * rpm / 60changes sign (or is zero) between consecutive operating points along a track; the rotor speed, frequency, and damping ratio at the crossing are linearly interpolated, and each crossing is assigned a damping-awareResonanceSeverity.The rotor-speed samples need not be strictly increasing: each track’s curve is sorted by rotor speed and duplicate rotor speeds are collapsed internally before the crossings are found, so a non-monotone or tied sweep is handled gracefully rather than producing spurious or zero-width crossings.
- Parameters:
diagram (CampbellDiagram) – A Campbell diagram whose operating parameter is rotor speed in rev/min.
harmonics (Sequence[int], optional) – Excitation harmonics to test (default 1P, 3P, 6P, 9P). Any positive-integer family may be supplied.
operating_range (tuple[float, float] or None, optional) – If given, only crossings whose rotor speed falls within this
(min_rpm, max_rpm)operating window are returned.
- Returns:
list[ResonanceCrossing] – The detected crossings, in track-then-harmonic order.
- Raises:
ValueError – If the diagram’s operating parameter is not rotor speed in rev/min,
harmonicscontains a non-positive, fractional, non-finite, or duplicate value, oroperating_rangeis given withmin_rpm > max_rpm.- Return type:
Driving OpenFAST
Driving OpenFAST — execute linearizations and collect the .lin output.
This subpackage produces linearization data: it runs the OpenFAST executable on an
input deck and gathers the resulting .lin files, ready for the parsing and modal
pipeline.
The vane.run subpackage produces linearization data: it configures a deck for
each operating point, runs the OpenFAST executable, and collects the .lin output.
run_operating_sweep() runs a complete sweep from an
operating curve (see The OpenFAST linearization sweep); the lower-level
driver, operating-point, trim and steady-state functions are public as well. Every
public name of the modules below, including helpers such as
curve_power_coefficient() and
rotor_radius() and constants such as
FLOATING_TRIM_PERIODS, can also be imported from
vane.run directly (from vane.run import curve_power_coefficient).
Linearization sweep
Automated operating-point sweep from rotor-performance data or settle runs.
- FLOATING_TRIM_PERIODS = 20
Periods of the slowest platform mode a floating linearization trim runs for.
- class OperatingSweepResult(operating_points, cases, settle_structural_dofs=None, settle_failures=(), floating_plan=None, auto_configuration=None)[source]
Bases:
objectResult of a fully automated settle/linearize operating sweep.
- Parameters:
operating_points (list[OperatingPoint]) – The linearized operating points, in
wind_speedsorder. Settle-derived points carry theirsettle_convergenceand, when the settle runs were seeded from an operating curve, theirsettle_curve_check. A settle point that was not linearized is absent here and listed insettle_failures.cases (list[SweepCase]) – The linearization cases, one per operating point.
settle_structural_dofs (dict[str, bool] or None, optional) – For a settle sweep, the template ElastoDyn flags the settle runs ran with for every structural DOF the linearization sets (the settle copy keeps the deck’s flags; see
run_operating_sweep()).Nonewithout settle runs.settle_failures (tuple of SettleFailure, optional) – The settle points that did not reach a confirmed equilibrium, in
wind_speedsorder, with their diagnostics. Empty when every point settled.floating_plan (FloatingSettlePlan or None, optional) – For a settle sweep, how the settle runs were set up automatically: the controller library used (or that none was loadable, so the rotor was prescribed from the operating curve), the settle damping and the platform probe it was sized from (see
plan_floating_settle()).Nonewithout settle runs.auto_configuration (AutoConfiguration or None, optional) – Every value the sweep inferred from the deck and curve (template root, OpenFAST executable, rated wind speed and torque, pitch trim gain, module settings), each with its source, and the warnings raised while inferring them.
Nonewithauto_configure=False.
- operating_points: list[OperatingPoint]
- settle_failures: tuple[SettleFailure, ...] = ()
- floating_plan: FloatingSettlePlan | None = None
- auto_configuration: AutoConfiguration | None = None
- class SettleFailure(wind_speed, reason, message, linearized=False, operating_point=None, convergence=None)[source]
Bases:
objectA settle point of a sweep that did not reach a confirmed equilibrium.
A sweep never aborts because one wind does not settle: the point is recorded here with its diagnostics, the other winds carry on, and only the points that settled are linearized (unless
require_settled=False; seerun_operating_sweep()).- Parameters:
wind_speed (float) – The wind speed (m/s) of the point.
reason ({"not_settled", "failed", "error"}) –
"not_settled": the settle ran to completion but its criterion was not met (or could not be assessed)."failed": the settle could not complete (SteadyStateError, e.g. OpenFAST failed or its output could not be read)."error": an unexpected exception, whichrun_operating_sweep()re-raises once the sweep is complete.message (str) – A one-line description of what went wrong.
linearized (bool) – Whether the point was linearized anyway (
"not_settled"withrequire_settled=False, at the settle’s last mean).operating_point (OperatingPoint or None, optional) – The settle’s last mean operating point (with its
settle_convergence) when the settle ran to completion;Noneotherwise.convergence (SettleConvergence or None, optional) – The settle evidence: the last RMS, every attempt with its horizon and window, the criterion, and for a two-stage settle the confirmation.
Nonewhen the settle failed before any run was judged.
- reason: SettleFailureReason
- operating_point: OperatingPoint | None = None
- convergence: SettleConvergence | None = None
- SettleFailureReason
Why a settle point is listed in
OperatingSweepResult.settle_failures.alias of
Literal[‘not_settled’, ‘failed’, ‘error’]
- exception SweepSettleError(message, *, result)[source]
Bases:
SteadyStateErrorRaised at the end of a sweep whose settle stage did not fully succeed.
Raised only after every wind has been settled and every usable point linearized, so no completed work is lost: the full
OperatingSweepResultis attached asresult. Raised whenstrict_settle=Trueand any point did not settle, or whenever a settle worker hit an unexpected exception (the first is chained as__cause__).- Parameters:
message (str) – The error message.
result (OperatingSweepResult) – The completed sweep result, including its
settle_failures.
- Return type:
None
- floating_trim_time(period, max_sim_time)[source]
Return the CalcSteady run time of a floating linearization case.
The trim must outlast the slow platform motion the settled initial conditions leave, so the run spans
FLOATING_TRIM_PERIODSperiods of the slowest platform mode, within[MIN_CALC_STEADY_TMAX, max_sim_time].- Parameters:
- Returns:
float – The run time
TMax(s).- Raises:
ValueError – If
periodormax_sim_timeis not finite and positive.- Return type:
Notes
On the IEA 15-MW semi-submersible (slowest standstill period 125.5 s), settled cases linearized at the rotor-scaled
TrimTol(7.1e-3) trimmed at 6, 8 and 12 m/s within 2400 s (about 19 periods; rotor-speed spread 0.08–0.09 %), but only at 8 m/s within 1200 s and at none within 600 s. LooseningTrimTolto 0.021 instead let the 6 m/s trim stop with a 6.6 % rotor-speed spread.
- run_operating_sweep(fst_path, output_dir, wind_speeds, *, curve_wind=None, curve_rotor_speed=None, curve_pitch=None, performance_csv=None, settle=False, settle_time=1200.0, discard_time=600.0, max_settle_time=None, controller_library=None, settle_initial_platform=None, settle_curve_tolerances=None, settle_criterion='auto', settle_platform_damping=None, settle_confirm_time=None, settle_equilibrium_tolerances=None, settle_auto_damping=True, controller_search_paths=(), settings=None, structural_dofs=None, trim_tuning=None, retry_policy=None, executable=None, template_root=None, template_copy='referenced', timeout=None, max_workers=None, reserve_cpus=1, require_settled=True, strict_settle=False, strict_cases=False, validate=True, auto_configure=True)[source]
Derive operating points and run an OpenFAST linearization sweep.
The function refuses to guess the rotor speed and blade pitch. It needs a rotor-performance source: an operating curve (
curve_wind/curve_rotor_speed/curve_pitch) or a rotor-performance CSV (performance_csv) – not both – and/orsettle=Trueto run the fully coupled controller first and read the operating point from the settled time series. Without any source, or with both a curve and a CSV, the sweep fails before any OpenFAST process is launched.Everything else is configured automatically by default (
auto_configure):run_operating_sweep(fst_path, output_dir, wind_speeds, performance_csv=csv)is a complete call. The copy root, the OpenFAST executable, the module settings and the turbine constants (rated wind speed, rated generator torque, pitch trim gain) are read from the deck and the curve, each logged and recorded with its source; anything that still cannot be made linearizable fails before launch with the keyword and its remedy.The combinations mean:
curve or CSV alone – the curve, interpolated at each wind speed, is the linearization point.
settle=Truealone – each settle run starts from 0 rpm and 0 deg collective pitch (written to the settle copy’s ElastoDynRotSpeed/BlPitch) and the deck’s platform position; the settled point is linearized.settle=Truewith a curve or CSV – the curve is used only as the settle runs’ initial conditions (RotSpeedandBlPitch(1..3)at each wind speed), so the rotor does not have to spin up from rest and start-up transients are smaller. The controller still decides where the turbine settles and the settled point is linearized, never the curve value. After each settle the settled rotor speed and pitch are compared with the curve (settle_curve_tolerances); a deviation outside tolerance is logged as a warning and recorded on the point’ssettle_curve_check(and in the run provenance). It means the controller and the curve disagree and is not an error. Every requested wind speed must lie inside the curve’s range.
The platform offset is never estimated from the curve (VANE computes no thrust); it starts at the deck’s values unless
settle_initial_platformsupplies it.- Parameters:
controller_library (str or pathlib.Path or None, optional) – Bladed-style controller library (e.g. a
libdiscon.dllbuilt for this host) for thesettle=Trueruns. Each settle case’s ServoDynDLL_FileNameis rewritten to it in the case copy only; the template deck is never edited.None(the default) finds one automatically (seefind_controller_library()): the deck’s ownDLL_FileNamewhen it loads on this host, else a host build of the same library next to it or incontroller_search_paths,VANE_CONTROLLER_LIBRARIESor the configuration file. When none exists the sweep does not fail: each settle runs with the rotor prescribed at the operating curve’s speed and pitch (no controller;prescribed_rotorofsolve_steady_state()) and the point is linearized by CalcSteady from the platform equilibrium it finds – which requires an operating curve orperformance_csv. The decision is recorded inOperatingSweepResult.floating_plan. Only valid withsettle=True(the linearization replaces the controller by the trim and never loads the library).settle_initial_platform (PlatformPosition or Mapping or None, optional) – Initial platform position for the settle runs: one
PlatformPositionfor every wind speed, or a mapping from wind speed to position (winds absent from it keep the deck’s position). Only the DOFs given are written. It is an initial condition only: a rough estimate (e.g. the mean surge and pitch of an earlier run) shortens the platform’s drift to its offset without changing where it settles.None(the default) keeps the deck’s platform position. Only valid withsettle=True; every mapping key must be a requested wind speed.settle_curve_tolerances (TrimTolerances or None, optional) – Tolerances of the settled-versus-curve comparison (rotor speed relative, pitch absolute; see
compare_settled_to_curve()).Noneuses theTrimTolerancesdefaults (1 %, 0.5 deg). Only valid withsettle=Trueand a curve or CSV.settle_criterion ({"auto", "velocity", "mean_drift"}, optional) – How each settle run is judged settled (see
solve_steady_state()):"auto"(the default) starts with the velocity criterion and switches to the mean-drift criterion when the platform carries a sustained, non-decaying oscillation;"velocity"is platform-velocity RMS within tolerance;"mean_drift"is operating-point means that no longer drift between the halves of the analysis window. The criterion that judged each point is recorded in itssettle_convergenceand in the run provenance. Only valid withsettle=True.settle_platform_damping (SettlePlatformDamping or None, optional) – Settle accelerator for floating platforms.
None(the default) sizes it automatically for a floating deck from a standstill platform probe (seeplan_floating_settle();settle_auto_damping=Falseopts out). Each settle then runs in two stages, first with this temporary linear damping added to HydroDyn’sAddBLinin the settle copy, then undamped from the stage-1 means to confirm the equilibrium is unchanged (seesolve_steady_state()andSettlePlatformDamping). The damping is only ever written to the settle case; every linearization case is copied afresh from the template and keeps the deck’s ownAddBLin. The template must run HydroDyn with anAddBLinmatrix; this is checked before any case is copied. Only valid withsettle=True.settle_confirm_time (float or None, optional) –
TMax(s) of the undamped confirmation run of a two-stage settle.None(the default) sizes it automatically so its analysis window holds eight periods of the slowest platform mode – the platform probe’s longest period when the probe ran, else the damped stage’s dominant period – never shorter than 1200 s (the length without any period) and never longer thanmax_settle_timewhen given, else 3600 s (seeconfirm_timeofsolve_steady_state()). A decaying confirmation whose means have not yet returned to the damped equilibrium is rerun once twice as long. Used only with a settle damping.settle_equilibrium_tolerances (EquilibriumTolerances or None, optional) – Tolerances on a change of the settled means (damped versus undamped stage, and window halves for
"mean_drift").Noneuses theEquilibriumTolerancesdefaults. Only valid withsettle=True.settle_auto_damping (bool, optional) – For a floating deck without
settle_platform_damping, size the settle damping at half of critical from a standstill platform probe (two short linearizations inoutput_dir; seeprobe_platform()). DefaultTrue;Falsesettles undamped.controller_search_paths (Sequence of str or pathlib.Path, optional) – Extra controller-library files or directories searched when
controller_libraryis not given.performance_csv (str or pathlib.Path or None, optional) – Path to a rotor-performance CSV (see
operating_curve_from_csv()for the column spec). When given it is parsed into the operating curve; it is mutually exclusive with thecurve_*arrays. Withsettle=Trueit seeds the settle runs (see above).structural_dofs (dict[str, bool] or None, optional) – ElastoDyn structural-DOF flags to set in each case’s ED sub-file.
None(the default) keeps thesettingsobject’s own value, whose default disablesYawDOFandDrTrDOFfor a reliable trim (dropping the spurious free-yaw mode and the ~0.62 Hz drivetrain-torsion mode, the latter a deliberate trade-off for trim convergence). Pass a mapping to override — it is merged on top of the defaults, so{"DrTrDOF": True}re-enables the drivetrain-torsion mode while still disablingYawDOF. A flag listed here but absent from the ED deck is an error. These flags apply to the linearization cases only: the settle copies keep the template deck’s own ElastoDyn DOF flags, because a settle runs the deck’s real controller on the model it was tuned for (a controller’s filters assume the deck’s drivetrain; the yaw drive holds the nacelle through ServoDyn). The settled mean rotor speed, pitch and platform position – all that is carried to the linearization – do not depend on the drivetrain or yaw flexibility. A difference between the two sets is logged, and the settle flags are recorded inOperatingSweepResult.settle_structural_dofs.retry_policy (LinearizationRetryPolicy or None, optional) – The convergence retry ladder for the linearization cases (see
LinearizationRetryPolicy), e.g. a largermax_sim_timefor a slowly settling floating platform.None(the default) uses the default policy.trim_tuning (TrimTuningPolicy or None, optional) – When set, each operating point’s starting trim tolerance and damping are tuned for its wind-speed region before the convergence retry ladder runs (see
tune_trim_settings()): at/above-rated points start from the looser-tolerance, higher-damping floors those harder trims need, so they tend to converge on the first attempt rather than after a reactive retry.None(the default) starts every point fromsettingsunchanged. Tuning only raises the per-casetrim_tol/twr_kdmp/bld_kdmptoward the floors, so the sweep’s provenance is unaffected.settings (LinearizationSettings or None, optional) – The linearization settings.
None(the default) configures the deck automatically (withauto_configure): theoffshore()module set, adapted to the deck – a HydroDyn radiation model whose.ssfile is missing keepsRdtnMod2 when the file can be fitted from the WAMIT.1data (it is then generated in each case copy,hydro_radiation_fit), a radiation or excitation model whose state-space file cannot be had falls back toRdtnMod/ExctnMod0 with a recorded warning, a deck’s own linearizable unsteady-aerodynamics model (UA_Mod4-6) is kept withUAEndRad0.99 where it would reach the tip node under tip loss, and a module the deck does not use is left untouched. Settings passed explicitly are used as given, except that the turbine constants they leave unset are still inferred (seeauto_configure).executable (str or pathlib.Path or None, optional) – The OpenFAST executable.
None(the default) chooses one whose release reads the deck’s input-format generation, from theVANE_OPENFAST_EXECUTABLESenvironment variable, the file named byVANE_OPENFAST_CONFIG(default~/.vane/openfast_executables.txt) andPATH(seeselect_openfast_executable()); the sweep fails before any case is copied, naming the release the deck needs, when none can. An explicit executable is used as given, but is rejected up front when its version is known not to read the deck.template_root (str or pathlib.Path or None, optional) – Directory the deck’s files are copied from per case.
None(the default) uses the deck’s own directory, or – when the deck references files outside it (../5MW_Baseline/...) andauto_configureis on – the deepest directory that holds the deck and every file it references by a relative path (infer_template_root()). An explicit value always wins.template_copy ({"referenced", "tree"}, optional) –
"referenced"(the default) copies only the files the deck references into each case (settle cases included);"tree"copies the wholetemplate_root. SeeLinearizationDriver.require_settled (bool, optional) – What to do with a settle point that does not reach a confirmed equilibrium (its criterion not met, or not assessable).
True(the default): do not linearize it.False: linearize it at the settle’s last mean. Either way the sweep carries on with the other winds – one unsettled point never aborts it – and the point is listed inOperatingSweepResult.settle_failureswith its diagnostics (and, when linearized, its settle evidence is in the case provenance). A settle that cannot complete (OpenFAST fails, unreadable output) is likewise recorded and never linearized.strict_settle (bool, optional) – When
True, raiseSweepSettleErroronce the sweep is complete if any settle point is insettle_failures; the full result is attached as itsresult. DefaultFalse(the failures are logged and returned). An unexpected exception in a settle worker is always re-raised this way, after the other winds have been settled and linearized.strict_cases (bool, optional) – How a linearization case that raises an unexpected exception (a file-system error while its case is copied or configured, say) is reported. Such a case never aborts the sweep: it is recorded in
OperatingSweepResult.casesas failed (converged=False,failure_mode == "case_error") and the other points run to completion (seevane.run.LinearizationDriver.run_sweep()). DefaultFalse: the failure is logged and the result returned.True: raisevane.run.SweepCaseErroronce the sweep is complete, with the full result attached as itsresult.validate (bool, optional) – When
True(the default) the template deck is checked under the effective settings (validate_deck()) and every rotating curve point’s trim configuration is checked before any case is copied, and each configured case again before its OpenFAST run.auto_configure (bool, optional) – When
True(the default) the sweep completes its configuration from the user’s own deck and curve, logging every value and recording it with its source inOperatingSweepResult.auto_configuration, in the driver’s run provenance and inoutput_dir/auto_configuration.json(auto_configure_sweep()): the template root and executable (above), the module settings whensettingsisNone, and the turbine constants left unset –rated_wind_speed(where the curve’s rotor speed and power reach their rated plateau),rated_generator_torque(controllerDISCON.INVS_RtTq, else ServoDynVS_RtTqwithVSContrl = 1, else the curve’scpat rated) and atrim_gain_pitchleft at its class default (scaled as2e-4 * (63 m / TipRad)**3). VANE computes no rotor performance in doing so.Falserestores the explicit behaviour: nothing is inferred,settings=Nonemeansoffshore(), andexecutable=Nonemeans"openfast"onPATH.fst_path (str | Path)
output_dir (str | Path)
wind_speeds (Sequence[float])
curve_wind (Sequence[float] | None)
curve_rotor_speed (Sequence[float] | None)
curve_pitch (Sequence[float] | None)
settle (bool)
settle_time (float)
discard_time (float)
max_settle_time (float | None)
timeout (float | None)
max_workers (int | None)
reserve_cpus (int)
- Raises:
SweepSettleError – After the sweep completes, with
strict_settle=Trueand a settle failure, or when a settle worker raised an unexpected exception.SweepCaseError – After the sweep completes, with
strict_cases=Trueand a linearization case that raised an unexpected exception.ValueError – Before any launch, for a malformed request or a rotating sweep whose rated wind speed is neither given nor inferable.
vane.run.DeckValidationError – Before any launch, when
validateis on and the template cannot be linearized under the effective settings; every problem is listed with the keyword and its remedy.vane.io.FastInputError – Before any launch, when a rotating curve point’s trim cannot be configured (e.g. no rated generator torque for an above-rated point).
vane.run.OpenFastError – Before any launch, when no OpenFAST executable can read the deck.
- Return type:
Infer a linearization sweep’s turbine constants from the user’s deck and curve.
run_operating_sweep() needs a few turbine constants that a deck does
not state in one place: the rated wind speed that separates torque trim from pitch
trim, the rated generator torque the trim holds above rated and derives its torque
gain from, a pitch-trim gain suited to the rotor’s size, and a module configuration
OpenFAST can linearize. This module reads them from the user’s own inputs – the
operating curve they supplied and the deck’s ElastoDyn, ServoDyn, AeroDyn, HydroDyn
and controller input files – so a sweep runs with no other arguments. VANE still
computes no rotor performance: every value is a constant read from, or arithmetic on,
the user’s data, and each one is logged and recorded with its source
(InferredValue, collected in AutoConfiguration) so the run
provenance says where it came from. A value the user sets explicitly is never
replaced.
The rules (see infer_rated_wind_speed(), infer_rated_generator_torque()
and scaled_trim_gain_pitch() for the evidence behind each):
rated wind speed – the first curve wind at which the rotor speed has reached its plateau and, when the curve has a
cpcolumn, the aerodynamic powercp * V**3has reached its plateau too. Cross-checked against the controller’s rated speed and power (DISCON.IN) when present; a disagreement is logged.rated generator torque – the
DISCON.INVS_RtTq(the file named by ServoDynDLL_InFile) when the deck runs a controller library; else ServoDynVS_RtTqwhenVSContrl = 1; else the curve’s aerodynamic torque at rated from itscpcolumn, ElastoDynGBRatio/GBoxEffand the deck’s air density.pitch trim gain –
1e-3 * (63 m / R)**3withRthe ElastoDynTipRad.module configuration (only when no settings are passed) – the
offshore()preset adapted to the deck: a missing.ssradiation file is fitted from the WAMIT.1data in each case copy when the fit is accurate enough (hydro_radiation_fit), other missing state-space hydrodynamics files fall back toRdtnMod/ExctnMod0, a deck’s own linearizable unsteady-aerodynamics model is kept (withUAEndRad0.99 where the tip node would otherwise carry it), and modules the deck does not use are left untouched.
- DEFAULT_TRIM_GAIN_PITCH = 0.0002
The class-default pitch trim gain (rad/(rad/s)); a value left here is scaled.
- DEFAULT_TRIM_TOL = 0.001
The class-default trim tolerance; a value left here is scaled.
- MAX_SCALED_TRIM_TOL = 0.01
Upper bound of the scaled trim tolerance. The MBC3 transform accepts a rotor-speed spread of 1 % across the azimuth sweep, which a looser trim can exceed.
- REFERENCE_ROTOR_RADIUS = 63.0
Rotor radius (m) at which the default trim gain and tolerance apply (NREL 5-MW).
- class AutoConfiguration(inferred=(), warnings=())[source]
Bases:
objectEverything a sweep inferred or adjusted on its own, for the provenance.
- Parameters:
inferred (tuple[InferredValue, ...]) – The inferred values, in the order they were decided.
warnings (tuple[str, ...]) – Disagreements and fallbacks the user should know about (also logged).
- inferred: tuple[InferredValue, ...] = ()
- get(name)[source]
Return the last record named
name, orNone.- Parameters:
name (str) – The record name.
- Returns:
InferredValue or None – The record, when one was made.
- Return type:
InferredValue | None
- extended(inferred=(), warnings=())[source]
Return a copy with more records and warnings appended.
- Parameters:
inferred (Sequence[InferredValue], optional) – Records to append.
warnings (Sequence[str], optional) – Warnings to append.
- Returns:
AutoConfiguration – The extended configuration.
- Return type:
- class ControllerConstants(path, rated_power=None, rated_torque=None, rated_generator_speed=None, generator_efficiency=None, blade_radius=None)[source]
Bases:
objectRated constants read from a
DISCON.IN-style controller input file.- Parameters:
path (pathlib.Path) – The controller input file (ServoDyn
DLL_InFile).rated_power (float or None) –
VS_RtPwr(W, electrical).rated_torque (float or None) –
VS_RtTq(N·m, generator/HSS side).rated_generator_speed (float or None) –
VS_RefSpd(elsePC_RefSpd), rad/s on the HSS.generator_efficiency (float or None) –
VS_GenEffas a fraction (the file holds percent).blade_radius (float or None) –
WE_BladeRadius(m).
- class InferredValue(name, value, source, detail='')[source]
Bases:
objectOne value VANE inferred or adjusted, with where it came from.
- Parameters:
- auto_configure_sweep(fst_path, settings, *, curve_wind=None, curve_rotor_speed=None, curve_cp=None)[source]
Return sweep settings completed from the deck and curve, with the record.
With
settings=Nonethe module configuration is chosen for the deck (theoffshore()preset, adapted as described in the module docstring). In every case the turbine constants the settings leave unset are inferred:rated_wind_speedandrated_generator_torquewhenNone, andtrim_gain_pitchandtrim_tolwhen they were left at their class defaults (2e-4 and 1e-3, which apply to a 63 m rotor; seeis_size_scaled_default()). Values set explicitly are kept, even when they equal the defaults.- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck.settings (LinearizationSettings or None) – The user’s settings, or
Nonefor a fully automatic configuration.curve_wind (Sequence[float] or None, optional) – The operating curve (m/s, rpm), when one was supplied.
curve_rotor_speed (Sequence[float] or None, optional) – The operating curve (m/s, rpm), when one was supplied.
curve_cp (Sequence[float] or None, optional) – The curve’s power coefficients, when its CSV has a
cpcolumn.
- Returns:
tuple[LinearizationSettings, AutoConfiguration] – The completed settings and every inferred value with its source.
- Return type:
Notes
A constant that cannot be inferred is left unset; the sweep’s boundary checks then fail before launch with the keyword to supply.
- curve_power_coefficient(csv_path, curve_wind)[source]
Return a performance CSV’s
cpcolumn aligned withcurve_wind.- Parameters:
csv_path (str or pathlib.Path) – The rotor-performance CSV (as given to
run_operating_sweep()).curve_wind (Sequence[float]) – The curve’s wind speeds as
operating_curve_from_csv()returned them (sorted ascending).
- Returns:
list[float] or None – The power coefficient (a fraction) at each curve wind, or
Nonewhen the file cannot be read, has nocpcolumn, its rows do not matchcurve_wind, or anycpvalue is missing, non-numeric or negative.- Raises:
ValueError – If the
cpcolumn’s unit or scale is wrong or ambiguous, as forrotor_performance_from_csv(): the header accepts the same column names (cp,power_coefficient,RtAeroCp,RtFldCp, only one of them) and units (none,[-],[1]or[fraction]for a fraction;[%],[percent]or[pct]for a percentage, which is converted), a fraction above the Betz limit 16/27 is rejected as a percentage written without its unit, and a column declared in percent whose values are all at most 1 is rejected as fractions labelled as percent.- Return type:
- infer_rated_generator_torque(fst_path, *, curve_wind=None, curve_rotor_speed=None, curve_cp=None, controller=None)[source]
Return the rated generator torque (N·m, HSS side) from the deck or the curve.
Sources, in order:
the controller input file’s
VS_RtTq(DISCON.IN, the file named by ServoDynDLL_InFile) when the deck runs a controller library;ServoDyn
VS_RtTqwhenVSContrl = 1(the simple variable-speed law actually uses it; under a library it is a placeholder);the curve’s aerodynamic torque at rated,
Q = cp * rho/2 * pi * R**2 * V**3 / Omegaaveraged over the rated region, moved to the generator side with ElastoDynGBoxEffandGBRatio(Q * GBoxEff / GBRatio), when the curve has acpcolumn.
- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck.curve_wind (Sequence[float] or None, optional) – The operating curve and its power coefficients, for source 3.
curve_rotor_speed (Sequence[float] or None, optional) – The operating curve and its power coefficients, for source 3.
curve_cp (Sequence[float] or None, optional) – The operating curve and its power coefficients, for source 3.
controller (ControllerConstants or None, optional) – The deck’s controller constants, when already read (
read_controller_constants()is called otherwise).
- Returns:
InferredValue or None – The torque with its source, or
Nonewhen no source has it.- Return type:
InferredValue | None
Notes
The torque trim gain is derived from this value as a tenth of it (Jonkman’s rule of thumb). On the low-speed shaft that loop’s proportional gain is
1000 * TrimGain * GBRatio = 100 * Q_rated, so its time constantJ / (100 * Q_rated)(Jthe ElastoDyn rotor and drivetrain inertia) is 0.10 s for the NREL 5-MW, 0.14 s for the IEA 3.4-MW, 0.19 s for the IEA 10-MW, 0.18 s for the IEA 15-MW and 0.23 s for the IEA 22-MW: the rule scales with size by construction, since the rated torque grows with the rotor. Source 3 reproduces the published rated torques within 0.3 % (NREL 5-MW), 1.1 % (IEA 15-MW) and 3.9 % (IEA 22-MW).
- infer_rated_wind_speed(curve_wind, curve_rotor_speed, curve_cp=None)[source]
Return the rated wind speed of an operating curve, and how it was found.
Rated is the first curve wind at which the rotor speed has reached its plateau (within 0.5 % of the curve’s maximum, held for at least two samples) and, when
curve_cpis given, the aerodynamic powercp * V**3has also reached its plateau: from that wind on, every curve sample is within 0.5 % of the curve’s final power (at least two samples). A power still rising through the last samples has no plateau.- Parameters:
- Returns:
tuple[float, str] – The rated wind speed (m/s) and a description of the evidence.
- Raises:
ValueError – If the curve has no rotor-speed plateau (e.g. it stops below rated), or its sequences differ in length.
- Return type:
Notes
The rated wind speed selects the CalcSteady trim: below it the generator torque is trimmed at the curve’s pitch, at and above it the pitch is trimmed against a constant rated torque. The boundary that matters is therefore where the generator torque reaches rated, i.e. where the rotor speed is at its rated value and the power has stopped rising. The point where the pitch starts to rise is not used: a peak-shaving schedule pitches before rated (the published NREL 5-MW curve from 11.1 m/s, while its rotor speed and power level off at 12.12 m/s), and a fine-pitch schedule pitches below rated too. On the published reference curves the rule gives 12.12 m/s (NREL 5-MW published curve), 9.81 m/s (IEA 3.4-MW curve; published 9.8), 10.76 m/s (IEA 10-MW curve, whose rotor speed levels off at 10.27 m/s while the power still rises to 10.76 m/s), 10.66 m/s (IEA 15-MW curve; published 10.66) and 11.13 m/s (IEA 22-MW curve; published 11.13). On a coarse curve without
cp(an IEA 22-MW operating table on a 1 m/s grid) the first plateau sample (12 m/s) is returned, which errs to the safe side: a point between the true rated speed and it is torque-trimmed at the curve’s pitch, which converges like any below-rated point.
- read_controller_constants(fst_path)[source]
Return the rated constants of the deck’s controller input file, if any.
The file is ServoDyn’s
DLL_InFile(resolved relative to the ServoDyn file), read only when ServoDyn is enabled and runs a control channel through the Bladed-style library (PCMode,VSContrl,HSSBrModeorYCMode= 5). Thevalue ! Keyword - descriptionlayout ofDISCON.INis parsed;VS_RtPwr,VS_RtTq,VS_RefSpd(orPC_RefSpd),VS_GenEffandWE_BladeRadiusare read.- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck.- Returns:
ControllerConstants or None – The constants (each
Nonewhen absent or not positive), orNonewhen the deck has no readable controller input file or it holds none of them.- Return type:
ControllerConstants | None
- rotor_radius(fst_path)[source]
Return the rotor radius (ElastoDyn
TipRad, m) of a deck, if readable.- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck.- Returns:
float or None –
TipRad, orNonewhen the deck has no readable positive value.- Return type:
float | None
- scaled_trim_gain_pitch(radius)[source]
Return the default CalcSteady pitch trim gain for a rotor of
radius.The rule is
2e-4 * (63 m / R)**3rad/(rad/s).- Parameters:
radius (float) – Rotor radius
R(m), ElastoDynTipRad.- Returns:
float – The pitch trim gain (
TrimGainforTrimCase = 3).- Raises:
ValueError – If
radiusis not finite and positive.- Return type:
Notes
ServoDyn’s pitch trim is a PI law on the rotor-speed error
e(rad/s): each step addsTrimGain * eto an integrator and the pitch offset adds1000 * TrimGain * eon top, so the proportional gain is1000 * TrimGainand the integral gainTrimGain / DT. The aerodynamic torque a pitch change produces,dQ/dtheta, scales withrho * R**3 * V**2at the similar rated wind speeds of utility turbines, so holding the trim’s torque correction per unit speed error (1000 * TrimGain * dQ/dtheta) at its NREL 5-MW value givesTrimGainproportional toR**-3. It is anchored at the class default 2e-4 for R = 63 m. The ElastoDyn rotor inertia about the low-speed shaft (blades, hub and generator) grows nearly as fast, asR**3.4over the NREL 5-MW and IEA 3.4/10/15/22-MW turbines (4.4e7 to 7.1e8 kg·m²), so the rule also keepsTrimGaintimes that inertia within a factor of two of the NREL 5-MW value on all five.Single-attempt CalcSteady trims give the evidence. The anchor was 1e-3 until the reference-turbine campaign showed that the r-test NREL 5-MW land deck (63 m,
DT6.25 ms) oscillates or motors above rated with it: at 16 m/s 1e-3 keeps oscillating while 6.3e-4, 5e-4 and 2e-4 converge, and at 13 m/s, just above rated, everything above 2e-4 motors. With the 2e-4 anchor every NREL 5-MW point, the IEA 3.4-MW (64.9 m) from 9 to 25 m/s and every IEA 15-MW (121.0 m) and 22-MW (141.0 m) point above rated trim on the first attempt. Before that, the 1e-3 anchor diverged on the IEA 10-, 15- and 22-MW, and its scaled 2.6e-4 converged the IEA 10-MW (99.2 m) at 12 m/s.
- scaled_trim_tol(radius)[source]
Return the default CalcSteady trim tolerance for a rotor of
radius.The rule is
1e-3 * (R / 63 m)**3, kept within[1e-3, 1e-2].- Parameters:
radius (float) – Rotor radius
R(m), ElastoDynTipRad.- Returns:
float – The trim tolerance (
TrimTol).- Raises:
ValueError – If
radiusis not finite and positive.- Return type:
Notes
TrimTolbounds the change of the trimmed outputs from one rotor revolution to the next. Without trim damping, the lightly damped tower and blade motion of a large, flexible turbine decays too slowly for a tight tolerance, while the rotor speed of a small, light rotor needs one: the MBC3 transform accepts a rotor-speed spread of at most 1 % across the azimuth sweep.Single-attempt trims on the reference-turbine campaign bracket the rule. At 1e-3 the IEA 3.4-MW (64.9 m) and the NREL 5-MW (63 m) converge every point, but the IEA 15-MW (121.0 m) converges only 4 of 9 and the IEA 22-MW (141.0 m) 2 of 9. At 1e-2 the IEA 15-MW converges 8 of 9 and the IEA 22-MW 9 of 9, while the IEA 3.4-MW’s below-rated points stop with a rotor-speed spread of 1.02–2.57 % that the MBC3 check rejects. Against the points that converge at both tolerances, 1e-2 changes the frequencies below 3 Hz by at most 0.1 %, the damping ratios by at most 0.1 percentage point and the trimmed generator torque by at most 0.8 %. The rule gives 1e-3 to 1.1e-3 for the 63–65 m rotors, 3.9e-3 for the IEA 10-MW (99 m), 7.1e-3 for the IEA 15-MW and the 1e-2 bound for the IEA 22-MW. A trim that still does not converge is retried with a looser tolerance and a longer run.
Operating points for a linearization sweep.
A linearization is taken at a single operating point — a wind speed, a rotor speed,
and a blade-pitch angle. A Campbell diagram or a load/QoI sweep needs a series of
them across the operating envelope. This module defines OperatingPoint and
builds a sweep either from an explicit list or by interpolating a turbine’s
steady-state operating curve (the rotor speed and pitch a controller would hold at
each wind speed), so a user need only choose the wind speeds to cover.
- class OperatingPoint(wind_speed, rotor_speed_rpm, blade_pitch_deg, name='', platform=None, settle_convergence=None, settle_curve_check=None)[source]
Bases:
objectA single steady operating point for a linearization.
- Parameters:
wind_speed (float) – Hub-height wind speed in m/s.
0denotes a no-wind (standstill or still-air) point.rotor_speed_rpm (float) – Rotor speed in rev/min.
0denotes a parked/standstill rotor.blade_pitch_deg (float) – Collective blade-pitch angle in degrees.
name (str) – Optional label for the point (used to name a generated case directory).
platform (PlatformPosition or None) – Initial floating-platform position to set in ElastoDyn.
None(the default) leaves the platform degrees of freedom at the deck’s values — appropriate for a bottom-fixed turbine or when the deck is already at equilibrium.settle_convergence (SettleConvergence or None) – Audited platform-velocity convergence from
solve_steady_state(), when the point came from a settle run. Hand-authored curve points leave itNone.settle_curve_check (SettleCurveCheck or None) – For a settle run seeded from an operating curve, the settled rotor speed and pitch compared with the curve at this wind speed (see
compare_settled_to_curve());Noneotherwise.
- platform: PlatformPosition | None = None
- settle_convergence: SettleConvergence | None = None
- settle_curve_check: SettleCurveCheck | None = None
- class PlatformPosition(surge=None, sway=None, heave=None, roll=None, pitch=None, yaw=None)[source]
Bases:
objectRigid-body floating-platform position (ElastoDyn platform DOFs).
Up to six platform degrees of freedom used to start a floating linearization at its thrust-loaded equilibrium, so the steady-state trim does not have to drag the slow platform modes there from rest (which it cannot reliably do near peak thrust). Translations are in metres and rotations in degrees, matching ElastoDyn’s
PtfmSurge/PtfmSway/PtfmHeave/PtfmRoll/PtfmPitch/PtfmYaw.Each field defaults to
None, meaning leave that DOF at the deck’s value; only the DOFs given are written. This keeps a partially-known position (e.g. only the surge and pitch a deck happens to output) from overwriting the deck’s other platform DOFs with fabricated zeros.- Parameters:
surge (float or None) – Platform translational displacements in metres, or
Noneto leave unchanged.sway (float or None) – Platform translational displacements in metres, or
Noneto leave unchanged.heave (float or None) – Platform translational displacements in metres, or
Noneto leave unchanged.roll (float or None) – Platform rotational displacements in degrees, or
Noneto leave unchanged.pitch (float or None) – Platform rotational displacements in degrees, or
Noneto leave unchanged.yaw (float or None) – Platform rotational displacements in degrees, or
Noneto leave unchanged.
- operating_curve_from_csv(path)[source]
Parse a rotor-performance CSV into a steady operating curve.
Reads a header-row CSV of the turbine’s steady operating schedule and returns the
(curve_wind, curve_rotor_speed, curve_pitch)arraysrun_operating_sweep()andoperating_points_from_curve()consume, sorted by ascending wind speed.The CSV must have a header row naming its columns. Three columns are required (canonical names, with tolerated variants — header matching ignores case, whitespace, and separators):
wind_speed(m/s) – alsoWindSpeed,wind,V [m/s],U;rotor_speed_rpm(rpm) – alsoRotSpeed,Omega [rpm],rpm;blade_pitch_deg(deg) – alsoBldPitch,pitch [deg],theta.
A unit written in square brackets or parentheses after a required column’s name is parsed (case- and whitespace-insensitive) and the column converted to the canonical unit; a column without a unit is taken to be in the canonical unit:
wind speed accepts
m/s(alsomps,m s^-1) and convertskm/h(alsokmh,kph) by 1/3.6;rotor speed accepts
rpm(alsorev/min) and convertsrad/sby 60/(2*pi);blade pitch accepts
deg(alsodegree,degrees) and convertsradby 180/pi.
Any other unit on a required column raises
ValueErrorrather than being silently misread, as does a converting unit on a header whose name states the canonical unit (rotor_speed_rpm [rad/s]). Each conversion is logged at INFO. Units are read from the header only; a separate units row is not supported and fails as a non-numeric value. Any other columns (cp,ct,cq,gen_power, …) are ignored here, units included, so a standard rotor-performance export drops in unchanged;rotor_performance_from_csv()reads thecpandctcolumns with their units.- Parameters:
path (str or pathlib.Path) – Path to the rotor-performance CSV file.
- Returns:
tuple of three list[float] –
(curve_wind, curve_rotor_speed, curve_pitch), sorted by ascending wind speed.- Raises:
FileNotFoundError – If
pathdoes not exist.ValueError – If the file is unreadable or empty, a required column is missing or carries an unsupported (or name-contradicting) unit, a value is non-numeric, NaN, or infinite, a wind speed or rotor speed is negative (negative collective blade pitch is allowed), there are fewer than two data rows (at least two operating points are needed to interpolate), or the wind speeds are duplicated or not strictly increasing once sorted.
- Return type:
- operating_curve_from_performance(*, performance_table=None)[source]
Derive a steady operating curve from a tabulated rotor-performance schedule.
Returns the
(curve_wind, curve_rotor_speed, curve_pitch)arrays thatoperating_points_from_curve()andrun_operating_sweep()consume, from an explicitperformance_table– the turbine’s tabulated steady operating schedule (wind speed, rotor speed, collective pitch), validated and returned sorted by ascending wind speed. The rotor-performance data is always supplied by the caller (e.g. read from a CSV withoperating_curve_from_csv()); VANE does not compute it from blade geometry.- Parameters:
performance_table (tuple of three Sequence[float] or None, optional) –
(wind_speed, rotor_speed_rpm, blade_pitch_deg)arrays of the turbine’s steady operating schedule, of equal length (>= 2).- Returns:
tuple of three list[float] –
(curve_wind, curve_rotor_speed, curve_pitch), sorted by ascending wind speed, ready to pass tooperating_points_from_curve().- Raises:
ValueError – If
performance_tablearrays differ in length, have fewer than two samples, contain a non-finite value, have a negative wind speed or rotor speed (negative collective blade pitch is allowed), or have a non-increasing wind-speed axis.RuntimeError – If no
performance_tableis given, so there is no rotor-performance source to derive the curve from.
- Return type:
- operating_points_from_curve(wind_speeds, *, curve_wind, curve_rotor_speed, curve_pitch)[source]
Build operating points by interpolating a steady-state operating curve.
The turbine’s steady operating curve (rotor speed and collective pitch versus wind speed) is interpolated with a shape-preserving monotone cubic (PCHIP, which does not overshoot between samples) and evaluated at each requested wind speed.
- Parameters:
wind_speeds (Sequence[float]) – Wind speeds (m/s) to produce operating points at.
curve_wind (Sequence[float]) – Strictly increasing wind-speed samples of the operating curve.
curve_rotor_speed (Sequence[float]) – Rotor speed (rpm) at each
curve_windsample.curve_pitch (Sequence[float]) – Collective blade pitch (deg) at each
curve_windsample.
- Returns:
list[OperatingPoint] – One operating point per requested wind speed, named
"ws<speed>".- Raises:
ValueError – If the curve arrays differ in length or have fewer than two points, the curve wind speeds are not strictly increasing, any curve value is non-finite, or a requested wind speed is non-finite or falls outside the curve’s range (interpolation only — extrapolation is not performed).
- Return type:
End-to-end linearization driver: a turbine model and a sweep to .lin files.
The LinearizationDriver ties the pieces together. Given a template OpenFAST
case and a list of operating points, it copies the case to a fresh directory per
point, configures each copy for linearization at that point
(vane.run.configure_linearization()), runs OpenFAST
(vane.run.run_openfast()), and collects the resulting .lin files — which
feed straight into the parsing and modal pipeline. This closes the loop from a
turbine model to a Campbell diagram / load (QoI) sweep / mode shapes, all from VANE.
A case is copied per operating point so that shared sub-files (a common InflowWind or
ElastoDyn deck) are never mutated across points. By default the case’s own directory is
the copy root; pass template_root to a higher directory when the deck references
files outside it (e.g. ../5MW_Baseline/...). Only the files the deck references are
copied (vane.run.deck_files.copy_template_files()), at their paths relative to
template_root, so a broad root such as a whole regression-test suite costs only the
deck’s own inputs per case.
- class CaseProvenance(name, wind_speed, rotor_speed_rpm, blade_pitch_deg, case_dir, returncode, succeeded, n_lin, attempts=1, converged=True, trim_tol=0.0, twr_kdmp=0.0, bld_kdmp=0.0, trim_gain_torque=0.0, trim_gain_pitch=0.0, settle_assessed=None, settle_converged=None, settle_translational_velocity_rms=None, settle_angular_velocity_rms=None, settle_time=None, settle_iterations=None, settle_attempts=None, settle_criterion=None, settle_confirmation=None, settle_curve_rotor_speed_rpm=None, settle_curve_pitch_deg=None, settle_curve_status=None, operating_point_residual=None, sim_time=None, failure_mode=None, attempt_history=(), fallback_overrides=None, radiation_fit=None)[source]
Bases:
objectPer-operating-point record within a sweep’s provenance.
- Parameters:
name (str) – The case-directory name for the operating point.
wind_speed (float) – Hub-height wind speed (m/s).
rotor_speed_rpm (float) – Rotor speed (rpm).
blade_pitch_deg (float) – Collective blade pitch (deg).
case_dir (str) – The case directory the run was written to.
returncode (int) – OpenFAST exit status (0 on success).
succeeded (bool) – Whether OpenFAST exited successfully.
n_lin (int) – Number of full-system
.linfiles produced.attempts (int) – How many OpenFAST runs the retry ladder used for this point.
converged (bool) – Whether the final attempt converged to a trustworthy linearization.
trim_tol (float) – The as-run trim tolerance for this case (escalated above the campaign base when the case was retried).
twr_kdmp (float) – The as-run tower/blade trim damping for this case (escalated on retry).
bld_kdmp (float) – The as-run tower/blade trim damping for this case (escalated on retry).
trim_gain_torque (float) – The as-run torque/pitch trim gains for this case.
trim_gain_torqueis the effective torque gain (the escalated override on a retry, elserated_generator_torque / 10when derived, else the explicit value). Recorded per case because the gain is a per-case escalation ignored by the campaign-wide settings record.trim_gain_pitch (float) – The as-run torque/pitch trim gains for this case.
trim_gain_torqueis the effective torque gain (the escalated override on a retry, elserated_generator_torque / 10when derived, else the explicit value). Recorded per case because the gain is a per-case escalation ignored by the campaign-wide settings record.settle_* (optional) – Platform-velocity settle evidence carried by a point produced through
vane.run.solve_steady_state();Nonefor hand-authored curve points.settle_criterion (str or None) – The criterion that accepted (or rejected) the settle:
"velocity"or"mean_drift"(seevane.run.solve_steady_state()).settle_confirmation (SettleConfirmation or None) – For a two-stage settle with temporary platform damping, the damped stage’s evidence and the undamped confirmation’s outcome;
Noneotherwise.settle_curve_rotor_speed_rpm (optional) – For a settle seeded from an operating curve, the curve’s rotor speed (rpm) and pitch (deg) at this wind speed and the settled-versus-curve status (
"pass","flag"or"unavailable"; seevane.run.SettleCurveCheck);Noneotherwise.settle_curve_pitch_deg (optional) – For a settle seeded from an operating curve, the curve’s rotor speed (rpm) and pitch (deg) at this wind speed and the settled-versus-curve status (
"pass","flag"or"unavailable"; seevane.run.SettleCurveCheck);Noneotherwise.settle_curve_status (optional) – For a settle seeded from an operating curve, the curve’s rotor speed (rpm) and pitch (deg) at this wind speed and the settled-versus-curve status (
"pass","flag"or"unavailable"; seevane.run.SettleCurveCheck);Noneotherwise.operating_point_residual (OperatingPointResidualAudit or None) – The physical acceptance audit, when the check was enabled.
sim_time (float or None) – The as-run
sim_time(TMax) setting, extended above the campaign base when a trim that ran out of time was retried;Nonekeeps the deck’s value.failure_mode (str or None) – The final attempt’s classified failure;
Nonewhen it converged.attempt_history (tuple[LinearizationAttempt, ...]) – The per-attempt records of the retry ladder (see
LinearizationAttempt).fallback_overrides (dict[str, object] or None) – For a case whose final attempt ran the retry policy’s
final_fallback, the as-run values of itsfallback_fields;Noneotherwise.radiation_fit (dict[str, object] or None) – The state-space radiation file generated in the case copy (
vane.run.RadiationFitRecord.to_dict(): the.sspath, the WAMIT.1source, the state count and the per-entry fit errors);Nonewhen none was generated.settle_assessed (bool | None)
settle_converged (bool | None)
settle_translational_velocity_rms (float | None)
settle_angular_velocity_rms (float | None)
settle_time (float | None)
settle_iterations (int | None)
settle_attempts (tuple[SettleAttempt, ...] | None)
- settle_attempts: tuple[SettleAttempt, ...] | None = None
- settle_confirmation: SettleConfirmation | None = None
- operating_point_residual: OperatingPointResidualAudit | None = None
- attempt_history: tuple[LinearizationAttempt, ...] = ()
- class LinearizationAcceptancePolicy(max_mean_translational_speed=0.001, max_mean_angular_speed=0.0001, min_rotating_azimuths=12)[source]
Bases:
objectPhysical residual limits for a floating operating point.
- Parameters:
max_mean_translational_speed (float) – Maximum norm of the azimuth-mean platform translational velocity, in m/s.
max_mean_angular_speed (float) – Maximum norm of the azimuth-mean platform angular velocity, in rad/s.
min_rotating_azimuths (int) – Minimum number of distinct azimuth samples needed to distinguish periodic motion from secular drift for a rotating point. The default is 12 and values below four are prohibited so blade-passing motion cannot alias into the mean. A non-rotating point needs only one linearization.
- class LinearizationAttempt(attempt, failure_mode, reason, openfast_message, failure_time, returncode, n_lin, log_file, trim_tol, twr_kdmp, bld_kdmp, trim_gain_torque, trim_gain_pitch, sim_time, tmax, fallback=False)[source]
Bases:
objectThe record of one OpenFAST run of an operating point’s retry ladder.
- Parameters:
attempt (int) – The 1-based attempt number.
failure_mode (str or None) – The classified failure (an
OpenFastFailureModevalue, or"physical_residual_rejected"when the acceptance check rejected a completed run).Nonewhen the attempt converged and was accepted.reason (str or None) – A one-line description of the failure;
Noneon success.openfast_message (str) – The relevant excerpt of OpenFAST’s output (its fatal error message, or its last lines); empty when none applies.
failure_time (float or None) – The simulation time (s) of OpenFAST’s fatal error, when it reported one.
returncode (int) – The OpenFAST exit status (
124for a run killed by the timeout).n_lin (int) – The number of full-system
.linfiles the attempt wrote.log_file (str or None) – The file in the case directory holding the attempt’s full OpenFAST standard output and error,
<deck stem>.attempt<k>.log;Noneif it could not be written.trim_tol (float) – The attempt’s trim tolerance, trim damping and effective trim gains.
twr_kdmp (float) – The attempt’s trim tolerance, trim damping and effective trim gains.
bld_kdmp (float) – The attempt’s trim tolerance, trim damping and effective trim gains.
trim_gain_torque (float) – The attempt’s trim tolerance, trim damping and effective trim gains.
trim_gain_pitch (float) – The attempt’s trim tolerance, trim damping and effective trim gains.
sim_time (float or None) – The attempt’s
sim_timesetting (Nonekeeps the deck’sTMax).tmax (float or None) – The
TMaxthe attempt’s configured deck actually carried, when readable.fallback (bool) – Whether the attempt ran with the retry policy’s
final_fallbacksettings.
- class LinearizationDriver(fst_path, output_dir, settings=<factory>, executable='openfast', template_root=None, link_inputs=True, template_copy='referenced', validate=True, retry_policy=<factory>, trim_tuning=None, acceptance_policy=None, auto_configuration=None)[source]
Bases:
objectGenerate, run, and collect linearizations across operating points.
- Parameters:
fst_path (pathlib.Path) – The template case’s primary
.fstdeck.output_dir (pathlib.Path) – Directory under which per-operating-point case directories are created. Must not be inside
template_root(nor contain it), to avoid a recursive copy.settings (LinearizationSettings) – Linearization tuning applied to every case.
executable (str or pathlib.Path) – OpenFAST executable name or path.
template_root (pathlib.Path or None) – Directory the case is copied from per operating point; defaults to
fst_path’s parent. Set to a higher directory when the deck references files outside its own folder so those are copied too. File paths relative to it are preserved in each case directory.link_inputs (bool) – When
True(the default), each case is created by hard-linking the template files rather than copying their bytes, so a sweep’s heavy read-only inputs (e.g. WAMIT hydrodynamic data, airfoil tables) are shared at the inode level and a many-point sweep does not duplicate hundreds of megabytes per point. Each case still gets its own complete tree and its own outputs; the small deck files are edited viaFastInputFile.write()’s atomic replace, which breaks the link so the template is never modified. Falls back to a full byte copy automatically when hard-linking is unsupported (e.g. the output directory is on a different filesystem from the template). Set toFalseto always copy.template_copy ({"referenced", "tree"}) – What each case receives from
template_root."referenced"(the default) copies only the files the deck references, found recursively from the primary deck (deck_file_closure()); unrelated cases and stale outputs elsewhere under the root are left behind. When that walk cannot vouch for completeness (a relative reference outsidetemplate_root, an unreadable input) the whole tree is copied instead, with a logged warning."tree"always copies the whole template tree.validate (bool) – When
True(the default), each configured case is checked withvalidate_deck()before OpenFAST is launched, so a deck that cannot linearize as configured (a DLL controller, active waves, missing state-space files, …) is rejected up front instead of failing late inside a long settle/trim run. Set toFalseto skip the pre-flight check.trim_tuning (TrimTuningPolicy or None) – When set, each operating point’s starting trim settings are tuned for its wind-speed region (
tune_trim_settings()) before the retry ladder runs: an at/above-rated point starts from the looser-tolerance, higher-damping floors that such points need, so it is more likely to converge on the first attempt instead of after a reactive retry.None(the default) starts every point fromsettingsunchanged. Tuning only raises the per-case trim_tol/twr_kdmp/bld_kdmp toward the floors, so provenance is unaffected.acceptance_policy (LinearizationAcceptancePolicy or None) – Optional post-run physical check for a floating operating point. When set, VANE averages the ElastoDyn platform-rate states over the azimuth sweep and rejects a nominally successful trimmed or explicit-time result with excessive secular translational or angular drift. This rejection is not sent through the normal retry ladder because that ladder loosens
TrimToland would make a residual drift worse; the caller must tighten the base trim or improve the warm start.auto_configuration (dict[str, object] or None) – The record of values inferred from the deck and curve (set by
vane.run.run_operating_sweep()), copied intoprovenance().retry_policy (LinearizationRetryPolicy)
- Raises:
ValueError – If
fst_pathis not withintemplate_root, oroutput_dirandtemplate_rootare nested in one another.
- fst_path: Path
- output_dir: Path
- settings: LinearizationSettings
- template_copy: TemplateCopyMode = 'referenced'
- retry_policy: LinearizationRetryPolicy
- trim_tuning: TrimTuningPolicy | None = None
- acceptance_policy: LinearizationAcceptancePolicy | None = None
- case_dir(operating_point)[source]
Return the case directory for an operating point.
- Parameters:
operating_point (OperatingPoint)
- Return type:
Path
- validated_workspace_dir(name)[source]
Return
output_dir/name, rejecting one that overlaps the template tree.A workspace directory (a linearization case or an auxiliary settle case) that is, contains, or sits inside the template root would have the template deleted by the clean-copy step, which removes the destination first. This is possible when the template lives under the output workspace and a generated name collides with a template directory. Reject any such overlap up front with a clear error, so directory creation can never destroy the source tree.
- Parameters:
name (str) – The directory name to create under
output_dir.- Returns:
pathlib.Path – The resolved, validated destination directory.
- Raises:
ValueError – If the destination is, contains, or is inside the template root, or
nameis a device name Windows reserves (CON,NUL,COM1, …, with or without an extension).- Return type:
Path
- generate_case(operating_point)[source]
Copy the template case and configure it for an operating point.
- Parameters:
operating_point (OperatingPoint) – The operating point to set up.
- Returns:
pathlib.Path – The configured primary
.fstdeck inside the new case directory.- Return type:
Path
- run_case(operating_point, *, timeout=None)[source]
Generate, run, and collect one operating point.
- Parameters:
operating_point (OperatingPoint) – The operating point to run.
timeout (float or None, optional) – Per-run timeout in seconds.
- Returns:
SweepCase – The generated case and its run outcome.
- Raises:
vane.run.DeckValidationError – If
validateis set and the configured deck cannot be linearized (raised before OpenFAST is launched).- Return type:
Notes
Called directly, an unexpected exception (a file-system error while the case is copied or configured, say) propagates. Within
run_sweep()the same exception is instead recorded as a failed case (see there).
- run_sweep(operating_points, *, timeout=None, max_workers=None, strict=False)[source]
Run a whole sweep of operating points.
Each operating point is an independent OpenFAST run in its own case directory, so a sweep can run several points concurrently. OpenFAST itself is single-threaded here, so
max_workersprocesses usemax_workerscores; on a multi-core workstation the sweep wall-time approaches that of a single operating point rather than their sum.- Parameters:
operating_points (Sequence[OperatingPoint]) – The operating points to run, in order.
timeout (float or None, optional) – Per-run timeout in seconds.
max_workers (int or None, optional) – Number of operating points to run concurrently.
None(the default) usesdefault_max_workers(), reserving one CPU for the machine. Results are returned in the input order regardless.strict (bool, optional) – When
True, raiseSweepCaseErroronce every case has finished if any case raised an unexpected exception; all the cases are attached as itscases. DefaultFalse: such a case is only recorded and logged.
- Returns:
list[SweepCase] – One result per operating point, in the input order.
- Raises:
SweepCaseError – With
strict=True, after all the cases have finished, when a case raised an unexpected exception.ValueError – If two operating points map to the same case directory, a case directory would overlap the template root or use a reserved device name, or
max_workersis less than 1. Names are compared case-insensitively (casefold), because the default Windows and macOS filesystems are case insensitive and two such cases would otherwise share – and delete – one directory. All such checks run up front, before any case is launched.vane.run.DeckValidationError – If
validateis set and a configured deck cannot be linearized: an input error found before that case’s OpenFAST run is launched.
- Return type:
Notes
One operating point never discards the others. An OpenFAST failure is classified and retried (see
LinearizationRetryPolicy); an unexpected exception while a case is generated, run or collected – a file-system error that outlasted the transient-error retry, an unreadable sub-file – ends that point’s retry ladder and is recorded as a failed case:convergedisFalse,failure_modeis"case_error"(CASE_ERROR), the finalLinearizationAttemptcarries the exception type and message as itsreason(and so does the provenance record), its attempt log holds the traceback, and the result’s return code is-1. The earlier attempts’ records are kept. The argument checks listed under Raises are boundary errors and are still raised, theValueErrorones before any case is copied or launched.
- provenance(cases, *, timestamp=None)[source]
Build a reproducibility record for a completed sweep.
Records the
LinearizationSettingsevery case was configured with, the library version and executable, and each case’s outcome, so the conditions a set of.linfiles was produced under travel with them.The recorded settings and executable are taken from the cases’ own snapshots (frozen when each case ran), not from the driver’s current attributes, so the record stays faithful even if the driver was reconfigured after the sweep.
- Parameters:
cases (Sequence[SweepCase]) – The cases returned by
run_sweep()(orrun_case()).timestamp (datetime.datetime or None, optional) – The time to record; defaults to the current UTC time.
- Returns:
RunProvenance – The settings, environment, and per-case record for the sweep.
- Raises:
ValueError – If the cases differ on a campaign-wide setting (anything other than the per-case retry-escalated trim tolerance/damping) or on the executable, so no single run configuration describes them.
- Return type:
- class LinearizationRetryPolicy(max_attempts=4, trim_tol_factor=3.0, damping_factor=2.0, min_retry_damping=5000.0, trim_gain_factor=4.0, pitch_trim_gain_factor=0.2, tmax_factor=2.0, max_sim_time=3600.0, early_failure_time=1.0, damping_backoff_factor=0.5, adaptive=True, final_fallback=<function above_rated_torque_trim>, fallback_modes=frozenset({OpenFastFailureMode.DIVERGENCE, OpenFastFailureMode.MOTORING, OpenFastFailureMode.ROTOR_OVERSPEED}), fallback_fields=('above_rated_trim', ))[source]
Bases:
objectAdaptive retry ladder for OpenFAST linearization failures.
A case is retried when OpenFAST exits non-zero, when a rotating CalcSteady run returns fewer full-system
.linfiles thanNLinTimesor logs that it forced the linearization, or when the run times out. Each failed attempt is classified (classify_openfast_output()) and the next attempt’s settings are chosen from that failure, starting from the failed attempt’s own settings so that successive remedies compose (next_attempt_settings()):trim not converged (the forced single
.lin): the trim ran out of time.TMaxis extended tomin(TMax * tmax_factor, max_sim_time)andTrimTolis loosened bytrim_tol_factor, but not beyondPERIODIC_TRIM_TOL(1e-2) unless it already was: a looser trim stops on a rotor speed that is not periodic enough for MBC3. The pitch trim gain is lowered bypitch_trim_gain_factor: near rated, the IEA 15- and 22-MW pitch trims oscillate without settling at the default gain and converge at about 1e-4 rad/(rad/s). The torque gain and the trim damping are left alone: raising the gain makes a slow trim diverge, and the damping biases the trimmed point and, on every reference deck it was tried on (IEA 3.4-MW, IEA 15-MW semi), made the rotor motor within 0.3 s.non-periodic sweep: every
.linwas written but the rotor speed varies across the azimuth files by more than MBC3 accepts (a loosenedTrimTollet a drifting response through).TrimTolis tightened back to the base value andTMaxis extended as for a trim that ran out of time.motoring (
u%HSS_Spd is negative): the trim overshot. The pitch gain is multiplied bypitch_trim_gain_factorand the torque gain divided bytrim_gain_factor. When the failure happens withinearly_failure_timeof the start, the trim damping itself destabilised the (light) blades, so it is removed (twr_kdmp = bld_kdmp = 0); otherwise it is kept.rotor overspeed and divergence: the gains are backed off as for motoring and the trim damping is multiplied by
damping_backoff_factor(removed for an early failure).initialisation error: an input problem. The case is not retried.
timeout, an incomplete explicit-time sweep, or an unrecognised non-zero exit: the generic escalation, which loosens
TrimTolbytrim_tol_factor, raises the trim damping tomax(previous, min_retry_damping) * damping_factor, multiplies the torque trim gain bytrim_gain_factorand the pitch trim gain bypitch_trim_gain_factor.
A too-low torque gain is a common cause of a below-rated trim failure (per Jonkman), which is why the generic step raises it. The pitch trim is a PI law whose proportional gain is
1000 * TrimGain, so an above-rated failure is usually overshoot into a growing oscillation, and every remedy except the time extension lowers that gain. Exhaustion returns the final failed result so callers still get the captured diagnostics.- Parameters:
max_attempts (int) – Total OpenFAST runs per operating point, including the first (default
4). Most failed attempts are cheap: a divergence fails within seconds, and only a trim that runs out of time uses its wholeTMax.trim_tol_factor (float) – Factor that loosens
TrimTolon a trim-not-converged or generic retry.damping_factor (float) – Factor that raises the trim damping on a generic retry.
min_retry_damping (float) – Floor the trim damping is raised to before
damping_factorapplies on a generic retry.trim_gain_factor (float) – Factor that raises the torque trim gain on a generic retry, and divides it on a motoring, overspeed or divergence retry.
pitch_trim_gain_factor (float) – Factor (below one by default) that multiplies the pitch trim gain on every retry except a trim-not-converged one.
tmax_factor (float) – Factor (at least one) that extends
TMaxon a trim-not-converged retry. The extended value is written assim_time;1disables the extension.max_sim_time (float) – Cap (s) on the extended
TMax. A deck already longer than the cap is never shortened.early_failure_time (float) – A motoring, overspeed or divergence failure at or before this simulation time (s) is attributed to the trim damping, which the next attempt removes.
damping_backoff_factor (float) – Factor in
[0, 1]that lowers the trim damping after a later overspeed or divergence.adaptive (bool) –
True(the default) chooses each retry from the classified failure.Falseuses the fixed schedule ofsettings_for_attempt(), which applies the generic escalation on every retry whatever the failure. An initialisation error is never retried in either mode.final_fallback (callable or None) – A last resort for a trim that the gain and damping remedies cannot stabilise. The default,
above_rated_torque_trim(), switches an at/above-rated point from pitch to torque trim at the curve’s pitch;Nonedisables it. When set, the last attempt (max_attempts) that follows a failure infallback_modesruns withfinal_fallback(settings), wheresettingsis what the ladder would otherwise have chosen (so removed damping or an extendedTMaxcarry over). It must return aLinearizationSettingsthat differs from its argument only in retry-escalated fields andfallback_fields. The attempt is flaggedfallback=TrueinSweepCase.attempt_history.fallback_modes (frozenset[OpenFastFailureMode]) – The failures that trigger
final_fallback; by default the unstable-trim modes (motoring, rotor overspeed and divergence).fallback_fields (tuple[str, ...]) – The
LinearizationSettingsfieldsfinal_fallbackmay change beyond the retry-escalated ones. They are treated as per-case values, excluded from the campaign-wide provenance comparison and recorded per case inCaseProvenance.fallback_overrides.
- final_fallback()
Return
settingswith at/above-rated points trimmed by generator torque.The default
LinearizationRetryPolicy.final_fallback. A pitch trim that the gain and damping remedies cannot stabilise (large rotors near rated, where the trim’s PI loop oscillates until the rotor motors) is replaced by a torque trim at the operating point’s pitch, which converges like a below-rated point. A below-rated point already uses torque trim, so the switch leaves it unchanged.- Parameters:
settings (LinearizationSettings) – The settings the retry ladder chose for the final attempt.
- Returns:
LinearizationSettings – A copy with
above_rated_trim="torque".- Return type:
- fallback_modes: frozenset[OpenFastFailureMode] = frozenset({OpenFastFailureMode.DIVERGENCE, OpenFastFailureMode.MOTORING, OpenFastFailureMode.ROTOR_OVERSPEED})
- settings_for_attempt(base, attempt)[source]
Return the fixed (non-adaptive) settings for a 1-based attempt number.
Attempt
1isbaseunchanged. Attemptk > 1isk - 1generic escalation steps frombase:trim_tol = min(base * trim_tol_factor**(k-1), MAX_TRIM_TOL); the clamp is logged and the as-run value is recorded per case in the provenance;twr_kdmp/bld_kdmp = max(base, min_retry_damping) * damping_factor**(k-1), so every retry raises the damping above both the base and the floor;torque trim gain
= base * trim_gain_factor**(k-1);pitch trim gain
= base * pitch_trim_gain_factor**(k-1).
The driver follows this schedule when
adaptiveisFalse.- Parameters:
base (LinearizationSettings) – The operating point’s starting settings.
attempt (int) – The 1-based attempt number, at most
max_attempts.
- Returns:
LinearizationSettings – The settings for that attempt.
- Raises:
ValueError – If
attemptis not an integer in[1, max_attempts]or the escalation leaves the finite positive range.- Return type:
- next_attempt_settings(base, previous, diagnosis, *, attempt, previous_tmax=None)[source]
Return the settings for the retry that follows a classified failure.
- Parameters:
base (LinearizationSettings) – The operating point’s starting settings (used by the fixed schedule).
previous (LinearizationSettings) – The settings of the attempt that failed; adaptive remedies start here.
diagnosis (OpenFastDiagnosis) – The classified failure of that attempt.
attempt (int) – The 1-based number of the attempt being configured, in
[2, max_attempts].previous_tmax (float or None, optional) – The
TMaxthe failed attempt actually ran with (read from its deck). The extension starts from it whenprevious.sim_timeis unset, and fromMIN_CALC_STEADY_TMAXwhen neither is known.
- Returns:
LinearizationSettings or None – The next attempt’s settings, or
Nonewhen the failure is not retriable (an initialisation error or a non-finite linearization).- Raises:
ValueError – If
attemptis not an integer in[2, max_attempts].- Return type:
LinearizationSettings | None
- uses_fallback(diagnosis, *, attempt)[source]
Return whether
attemptruns with thefinal_fallbacksettings.- Parameters:
diagnosis (OpenFastDiagnosis) – The classified failure of the previous attempt.
attempt (int) – The 1-based number of the attempt being configured.
- Returns:
bool –
Truewhen a fallback is configured,attemptis the last one (max_attempts, at least 2) and the failure is retriable and one offallback_modes.- Return type:
- class RunProvenance(vane_version, created_at, executable, settings, cases, auto_configuration=None)[source]
Bases:
objectReproducibility record for a linearization sweep.
Captures the exact
LinearizationSettingsapplied to every case — so the settings a set of.linfiles was produced under are recorded alongside them, not just remembered — together with the library version, the executable, and the per-case outcome.- Parameters:
vane_version (str) – Version of the library that drove the sweep.
created_at (str) – ISO-8601 timestamp of when the record was made.
executable (str) – The OpenFAST executable name or path used.
settings (dict[str, object]) – Every
LinearizationSettingsfield and its value.cases (tuple[CaseProvenance, ...]) – Per-operating-point provenance, in run order.
auto_configuration (dict[str, object] or None, optional) – The values
vane.run.run_operating_sweep()inferred from the deck and curve, each with its source, and the warnings raised while inferring them (seevane.run.AutoConfiguration);Nonewhen nothing was inferred.
- cases: tuple[CaseProvenance, ...]
- class SweepCase(operating_point, case_dir, fst_path, result, settings=<factory>, base_settings=<factory>, executable='openfast', attempts=1, converged=True, operating_point_residual=None, attempt_history=(), fallback_fields=(), radiation_fit=None)[source]
Bases:
objectThe generated case and run outcome for one operating point.
- Parameters:
operating_point (OperatingPoint) – The operating point this case was generated for.
case_dir (pathlib.Path) – The per-operating-point directory the case was copied into.
fst_path (pathlib.Path) – The configured primary
.fstdeck insidecase_dir.result (OpenFastResult) – The OpenFAST run outcome (return code,
.linfiles, output).settings (LinearizationSettings) – A snapshot of the as-run settings this case was configured with (the final retry attempt’s, so a retried case carries its escalated trim/damping), frozen at run time so provenance reflects what actually produced the case even if the driver is later mutated.
base_settings (LinearizationSettings) – A snapshot of the campaign base settings this case escalated from (the driver’s settings at run time, before any per-case retry escalation). Frozen at run time so the campaign-wide configuration provenance records is independent of any later driver mutation. For an unretried case this equals
settings.executable (str) – The OpenFAST executable used for this case, captured at run time.
attempts (int) – How many OpenFAST runs the retry ladder used to produce this result (
1when the first run converged). The case carries the final attempt’s outcome.converged (bool) – Whether the final attempt converged: OpenFAST exited successfully, produced the requested number of full-system
.linfiles, did not force the linearization without finding the steady state, and every.linfile holds only finite numbers.Falsemeans the ladder was exhausted without a trustworthy linearization for this point.operating_point_residual (OperatingPointResidualAudit or None) – Optional independent six-DOF platform-drift audit.
Nonewhen the driver’s physical acceptance check was disabled.attempt_history (tuple[LinearizationAttempt, ...]) – One record per OpenFAST run, in order: the classified failure, the settings it ran with and the log file holding its full output. Empty for a case built by hand rather than by
LinearizationDriver.run_case().fallback_fields (tuple[str, ...]) – The retry policy’s
fallback_fieldswhen the case ran, snapshotted so provenance reads the case with the policy that produced it even if the driver’s policy is replaced or mutated afterwards.radiation_fit (RadiationFitRecord or None) – With
hydro_radiation_fit, the state-space radiation file the final attempt generated in the case copy (vane.run.ensure_radiation_state_space());Noneotherwise, or when the copy already had the file.
- operating_point: OperatingPoint
- case_dir: Path
- fst_path: Path
- result: OpenFastResult
- settings: LinearizationSettings
- base_settings: LinearizationSettings
- operating_point_residual: OperatingPointResidualAudit | None = None
- attempt_history: tuple[LinearizationAttempt, ...] = ()
- radiation_fit: RadiationFitRecord | None = None
- exception SweepCaseError(message, *, cases, result=None)[source]
Bases:
RuntimeErrorRaised by a strict sweep after a case raised an unexpected exception.
A linearization sweep records an unexpected exception in one operating point (a file-system error while its case is copied or configured, say) as a failed
SweepCasewhosefailure_modeis"case_error"and carries on with the other points. Withstrict=True(LinearizationDriver.run_sweep()) orstrict_cases=True(vane.run.run_operating_sweep()) it then raises this error – but only after every other case has finished, with all of them attached, so no completed work is lost. The first case’s exception is chained as__cause__.- Parameters:
message (str) – The error message.
cases (list[SweepCase]) – Every case of the sweep, in input order, the failed ones included.
result (OperatingSweepResult or None, optional) – The full sweep result, when raised by
vane.run.run_operating_sweep().
- Return type:
None
- result
As given.
- Type:
OperatingSweepResult or None
- default_max_workers(*, reserve_cpus=1)[source]
Return a CPU-aware default worker count for independent OpenFAST cases.
Configure an OpenFAST deck for a linearization at one operating point.
Given a primary .fst deck and an OperatingPoint,
this sets the linearization controls in the primary file and the operating point in
the ElastoDyn and InflowWind sub-files, following the OpenFAST recommended practice:
enable
Linearizeand write outputs/inputs at high precision (OutFmt);for a rotating point, trim to a periodic steady state (
CalcSteady = True) withNLinTimesazimuth snapshots, choosing torque trim below rated wind speed and pitch trim at/above it (TrimCase2 vs 3) — the standard region split;when explicit absolute times are supplied, disable
CalcSteadyand linearize at those times; otherwise a standstill / parked point takes one snapshot at time zero;set the rotor speed and blade pitch in ElastoDyn and the steady wind speed in InflowWind, with uniform inflow (power-law shear exponent
PLExp = 0) by default – the same steady uniform wind the settle run (solve_steady_state()) uses, so a settled operating point is linearized in the inflow it was settled in.
For a full floating (aero-hydro-servo-elastic) deck,
LinearizationSettings.offshore() additionally makes the offshore modules
linearization-compatible — still water in SeaState, state-space hydrodynamics in
HydroDyn with the 2nd-order forces off,
quasi-steady aero with no dynamic wake in AeroDyn, and a trim-compatible ServoDyn (the
Bladed-style DLL controller is not linearizable, so CalcSteady’s trim replaces it, with
region-aware generator torque — zero below rated, constant above). For a trimmed
rotating point the generator DOF (GenDOF) is also ensured on, since the trim
regulates rotor speed through it. These edits are skipped for any module a deck does not
have, so an onshore deck is unaffected.
The deck and its sub-files are edited in place with the format-preserving
FastInputFile. For a multi-point sweep, copy the case to a
per-operating-point directory first so that shared sub-files (e.g. a common
InflowWind deck) are not mutated across points — the linearization driver does this.
- class LinearizationSettings(n_lin_times=36, calc_steady=True, trim_tol=0.001, trim_gain_torque=100.0, trim_gain_pitch=0.0002, rated_wind_speed=None, lin_inputs=1, lin_outputs=1, module_linearizations=False, full_jacobians=False, out_fmt='ES20.11E3', twr_kdmp=0.0, bld_kdmp=0.0, sim_time=None, linearization_times=None, wave_mod=None, hydro_radiation_mode=None, hydro_excitation_mode=None, disable_second_order_hydro=False, unsteady_aero_mode=None, dynamic_wake_mode=None, trim_controller=False, wind_shear_exponent=0.0, structural_dofs=None, rated_generator_torque=None, trim_gain_torque_override=None, above_rated_trim='pitch', ua_end_radius=None, hydro_radiation_fit=False)[source]
Bases:
objectTunable settings for configuring a linearization deck.
- Parameters:
n_lin_times (int) – Number of linearizations per rotor revolution for a rotating point (
NLinTimes); 36 (every 10 degrees of azimuth) is the OpenFAST recommendation.calc_steady (bool) – Whether a rotating point trims to a periodic steady state before linearizing (
CalcSteady). A standstill/parked point always uses a single linearization regardless of this flag.trim_tol (float) – Rotor-speed convergence tolerance for the trim (
TrimTol). Must be positive and at mostMAX_TRIM_TOL(0.1): a larger tolerance no longer distinguishes a periodic steady state from a drifting transient. The default1e-3applies to a 63 m rotor; the automatic sweep configuration scales a value left at this default with the rotor radius (vane.run.auto_config.scaled_trim_tol()).trim_gain_torque (float) – Proportional trim gain used below rated wind speed (torque trim, Nm/(rad/s)). Used only when
rated_generator_torqueis not given; must be positive (as musttrim_gain_pitch). The default100.0is far too low for a utility-scale turbine — per Jonkman the torque trim gain should be ~a tenth of the rated generator torque (e.g. ~4000 for the NREL 5-MW), so supplyingrated_generator_torque(which derives the gain) is strongly preferred over relying on this default.trim_gain_pitch (float) – Trim gain used at/above rated wind speed (pitch trim, rad/(rad/s)). ServoDyn’s CalcSteady trim is a PI law: each time step adds
TrimGaintimes the rotor-speed error to an integrator, and the pitch offset adds 1000 times that error on top, so the effective proportional gain is1000 * TrimGainand the integral gainTrimGain / DT. The default2e-4applies to a 63 m rotor (the NREL 5-MW, which oscillates or motors above rated at1e-3); the automatic sweep configuration scales a value left at this default with the rotor radius (vane.run.auto_config.scaled_trim_gain_pitch()).above_rated_trim ({"pitch", "torque"}) – How CalcSteady trims an at/above-rated point.
"pitch"(default, the documented OpenFAST practice) holds the generator torque constant and trims blade pitch."torque"holds the blade pitch at the operating point’s value (the user’s rotor-performance curve) and trims generator torque with the torque gain, exactly as below rated. Torque trim converges where a pitch trim oscillates (large rotors near rated, e.g. the IEA 15- and 22-MW); the linearized plant is the same open-loop model at the curve’s pitch and rotor speed, and the trimmed torque’s departure from rated measures how far the curve and the deck disagree (seeverify_operating_points()). The retry ladder switches to"torque"as its final fallback. ServoDyn’s flattened law stays engaged: in its region 3 the torque is the constantVS_RtTqwith zero speed derivative, and the trim adds its offset on top, so the generator torque is one constant (VS_RtTq+ offset) exactly as below rated (0 + offset), the linearized plant is unchanged, and the trim starts from rated torque rather than from zero.rated_generator_torque (float or None) – Rated generator torque in N·m. When given, the torque trim gain is derived from it as
rated_generator_torque / 10(Jonkman’s rule of thumb), overridingtrim_gain_torque; this is the dependable way to get a torque trim that converges below rated. It also supplies the constant generator torque (VS_RtTq) the above-rated pitch trim needs whentrim_controlleris on.None(default) falls back totrim_gain_torquefor the gain and to the deck’sVS_RtTqfor the torque – but only when the deck’s originalVSContrlis1(the simple variable-speed law actually in use); a DLL or other controller deck carries a placeholderVS_RtTq, so an above-rated trimmed point on it requires this value. This value is preserved across retry attempts.trim_gain_torque_override (float or None) – An explicit, already-resolved torque trim gain (Nm/(rad/s)) that takes precedence over both
rated_generator_torqueandtrim_gain_torqueinresolved_trim_gain_torque(). Set by the retry ladder (LinearizationRetryPolicy) to pin an escalated gain without discardingrated_generator_torque(which still drivesVS_RtTq); normally leftNone.rated_wind_speed (float or None) – Wind speed (m/s) separating the below-rated (torque-trim) and above-rated (pitch-trim) control regions of the turbine being linearized (e.g. 11.4 for the NREL 5-MW, about 10.6 for the IEA 15-MW). There is no turbine-independent default: it is required for any rotating point whose trim or controller handling depends on the region (
calc_steadyortrim_controller), and a clear error is raised when it is missing.lin_inputs (int) –
LinInputsswitch (0=none, 1=standard, 2=all).lin_outputs (int) –
LinOutputsswitch (0=none, 1=from OutList, 2=all).1is required for the load/QoI channels to appear inC/D.module_linearizations (bool) – Set
LinOutModso OpenFAST writes per-module linearization files alongside the full-system files. This is required for module-specific physics models, including MoorDyn driven directly by measured fairlead kinematics.full_jacobians (bool) – Set
LinOutJacso OpenFAST also writes the unassembled module Jacobians. OpenFAST requireslin_inputs=lin_outputs=2when this debug export is enabled. It is useful for auditing the exact module coupling paths used by a subsystem digital twin.out_fmt (str) – Output number format (
OutFmt); a high precision such as"ES20.11E3"is important for accurate eigenvalues.twr_kdmp (float) – Tower and blade artificial damping used only during the steady-state trim.
bld_kdmp (float) – Tower and blade artificial damping used only during the steady-state trim.
sim_time (float or None) –
TMaxto set.Noneleaves the deck’s value unchanged, except that a CalcSteady trim is given at leastMIN_CALC_STEADY_TMAX(600 s): a template deck’s placeholder TMax would end the trim before it converges. OpenFAST stops as soon as the trim converges, so the floor costs nothing.linearization_times (tuple[float, ...] or None) – Absolute OpenFAST times in seconds at which to linearize when
calc_steady=False. The values must be finite, non-negative, strictly increasing, no later thansim_timewhen it is supplied, and their count must equaln_lin_times.Noneretains the single-snapshot untrimmed behaviour. This path is intended for a rotating plant that has already been demonstrated to be at periodic equilibrium by an independent physical check.wave_mod (int or None) – SeaState
WaveModto set (0= still water, required for linearization).Noneleaves the deck unchanged. Setting still water also neutralises the wave options it leaves meaningless but that OpenFAST would reject:WaveDirModandConstWaveModare set to 0 (required for any linearization), a wave-stretching model other than 0/1 (WaveStMod) is set to 0, MacCamy-Fuchs diffraction (MCFD) to 0, and aDEFAULTsub-surface current heading (CurrSSDir) is replaced by the deck’sWaveDirso the current keeps its direction.hydro_radiation_mode (int or None) – HydroDyn
RdtnMod/ExctnModto set. Linearization rejects the convolution/DFT models, so use0(none) or2(state-space, which needs the.ss/.ssexctnfiles).Noneleaves the deck unchanged. When either is set and HydroDyn then requiresRdtnDTto equal the glue-codeDT(WAMIT potential flow with a radiation or state-space excitation model) but the deck holds another numeric step,RdtnDTis set toDEFAULT.hydro_excitation_mode (int or None) – HydroDyn
RdtnMod/ExctnModto set. Linearization rejects the convolution/DFT models, so use0(none) or2(state-space, which needs the.ss/.ssexctnfiles).Noneleaves the deck unchanged. When either is set and HydroDyn then requiresRdtnDTto equal the glue-codeDT(WAMIT potential flow with a radiation or state-space excitation model) but the deck holds another numeric step,RdtnDTis set toDEFAULT.hydro_radiation_fit (bool) – Generate a missing
<PotFile>.ssradiation file in each linearization case copy from the WAMIT.1data (vane.run.ensure_radiation_state_space()) before the case is validated and run; the template is never written, and the fit is recorded per case in the run provenance. Requireshydro_radiation_mode = 2. DefaultFalse.run_operating_sweep()sets it when its automatic configuration finds the file missing but the WAMIT data fittable within the acceptance limit.disable_second_order_hydro (bool) – Zero HydroDyn’s 2nd-order force switches (
MnDrift/NewmanApp/DiffQTF/SumQTF) and switch off SeaState’s 2nd-order wave kinematics (WvDiffQTF/WvSumQTF), which linearization does not support.unsteady_aero_mode (int or None) – AeroDyn
UA_Modto set; linearization requires0(quasi-steady) or4/5/6.Noneleaves the deck unchanged. On a legacy-format AeroDyn deck (AFAeroMod/UAMod/WakeModkeywords),0setsAFAeroMod = 1(steady airfoil aerodynamics), the only value that format can linearize; a linearizable unsteady model (4–6) cannot be expressed there and is reported byvalidate_deck().dynamic_wake_mode (int or None) – AeroDyn
DBEMT_Modto set; linearization requires-1(frozen),0(none), or3.Noneleaves the deck unchanged. On a legacy-format AeroDyn deck,-1/0replace a dynamic-BEMT wake (WakeMod = 2, not linearizable in that format) with plain BEMT (WakeMod = 1) and set theFrozenWakeflag toTrue/Falserespectively;3cannot be expressed there and is reported byvalidate_deck().ua_end_radius (float or None) – AeroDyn
UAEndRadto set: the blade-radius fraction beyond which unsteady aerodynamics is switched off, in(0, 1].None(the default) leaves the deck unchanged. AeroDyn keeps unsteady aerodynamics on every node whose radius fraction is not greater thanUAEndRad, soUAEndRad = 1keeps it on at the blade-tip node. With the Prandtl tip loss model that node’s inflow is fixed (full axial induction), and its unsteady-aerodynamics states can turn NaN or infinite in the linearization. A value below 1 (e.g.0.99) ends unsteady aerodynamics inboard of the tip. The AeroDyn file must contain aUAEndRadline; a modern-format file without one is rejected before any file is written. Legacy-format files get the value only when they have the line.trim_controller (bool) – Make ServoDyn trim-compatible (
PCMode = 0,VSContrl = 1,HSSBrMode = 0,YCMode = 0,GenTiStr = GenTiStp = True, and aGenModelother than 1/2 reset to 1): the Bladed-style DLL controller is not linearizable, so CalcSteady’s own trim replaces it. For a trimmed rotating point the generator torque is then set up per region: below rated the generator is kept off (GenTiStr/TimGenOnbeyond TMax) so the torque trim owns it; at/above rated the simple variable-speed law is flattened to a constant torque (VS_RtGnSp/VS_Rgn2K/VS_SlPctiny,VS_RtTqset fromrated_generator_torquewhen given, else the deck’s existingVS_RtTq) so the pitch trim converges. Flattening requires a positive constant torque; an above-rated point with neitherrated_generator_torquenor a positive deckVS_RtTqis rejected (it would otherwise trim against zero torque). The deckVS_RtTqis trusted only when the deck’s originalVSContrlis1: a DLL-controlled deck (VSContrl = 5) or any other non-simple law carries a placeholder (e.g.9999.9), so an above-rated point on such a deck withoutrated_generator_torqueis rejected rather than trimmed at a wrong torque.wind_shear_exponent (float or None) – InflowWind power-law shear exponent (
PLExp) to write. The default0.0is steady uniform inflow, the convention of the settle run (solve_steady_state()), so a settled point is linearized in the same inflow it settled in;run_operating_sweep()passes this value to its settle runs too.Noneleaves the deck’sPLExpunchanged. Recorded with the other settings in the run provenance.structural_dofs (dict[str, bool] or None) – ElastoDyn structural-DOF flags to set in the ED sub-file (e.g.
{"YawDOF": False, "DrTrDOF": False}).None(the default) applies the linearization defaults: the blade and tower flexibility DOFs (FLEXIBILITY_DOFS:FlapDOF1,FlapDOF2,EdgeDOF,TwFADOF1,TwFADOF2,TwSSDOF1,TwSSDOF2) are enabled, so a deck shipped with rigid blades or tower still linearizes its blade and tower modes, andYawDOFandDrTrDOFare disabled. DisablingYawDOFremoves the spurious free nacelle-yaw rigid-body mode (there is no yaw controller in a linearization); disablingDrTrDOFdrops the drivetrain-torsion mode (~0.62 Hz) but improves CalcSteady trim convergence — a deliberate tradeoff. Supply a mapping to override: it is merged on top of the defaults, so{"DrTrDOF": True}re-enables the drivetrain-torsion mode while still disablingYawDOF, and{}keeps only the defaults. A flag the user explicitly lists but the ED deck lacks is an error; a default flag the deck lacks is silently skipped (the deck variant simply has no such DOF).
Notes
The module fields default to leave the deck unchanged (
None/False), so an onshore or already-prepared deck is unaffected — exceptstructural_dofs, whoseNonedefault disablesYawDOF/DrTrDOFfor a reliable linearization. For a full floating (aero-hydro-servo-elastic) deck, build the linearization-compatible set in one call withoffshore().- is_below_rated(wind_speed)[source]
Return whether
wind_speedlies in the below-rated (torque-trim) region.- Parameters:
wind_speed (float) – Operating-point wind speed (m/s).
- Returns:
bool –
Truebelowrated_wind_speed,Falseat or above it.- Raises:
ValueError – If
rated_wind_speedis not set.- Return type:
- resolved_trim_gain_torque()[source]
Return the torque trim gain (Nm/(rad/s)) to write into
TrimGain.An explicit
trim_gain_torque_override(pinned by the retry ladder) takes precedence. Otherwise, whenrated_generator_torqueis given, the gain is derived from it asrated_generator_torque * 0.1(Jonkman’s rule of thumb — a too-low torque trim gain is the most common cause of CalcSteady non-convergence below rated); otherwise the explicittrim_gain_torqueis used.- Returns:
float – The torque trim gain to apply below rated wind speed.
- Return type:
- resolved_structural_dofs()[source]
Return the ElastoDyn DOF flags to apply, defaults merged with overrides.
The linearization defaults (blade and tower flexibility enabled,
YawDOFandDrTrDOFdisabled) are the base; a user-suppliedstructural_dofsmapping is merged on top, so an override re-enables or adds flags while the un-overridden defaults remain.
- explicit_structural_dofs()[source]
Return the DOF-flag names the user explicitly requested (not just defaults).
A user-listed flag must exist in the ED deck (its absence is an error); a flag present only via the defaults is skipped when the deck lacks it.
- snapshot()[source]
Return a fully independent copy, deep-copying the mutable fields.
dataclasses.replaceshallow-copies, so a plainreplacesnapshot would share the samestructural_dofsdict object as the source. A later mutation of the source’s (or the originally-supplied) dict would then also mutate the snapshot, breaking snapshot independence. This copies the mutablestructural_dofsmapping so the returned settings are decoupled from any later mutation.- Returns:
LinearizationSettings – A copy whose mutable fields share no object with this instance.
- Return type:
- classmethod offshore(**overrides)[source]
Return settings prepared for a floating aero-hydro-servo-elastic deck.
Enables the full linearization-compatible module set — still water, state-space hydrodynamics, quasi-steady aero with a frozen wake (
DBEMT_Mod = -1, valid for linearization across OpenFAST versions), the 2nd-order forces off and the controller replaced by the trim — that lets a real FOWT deck linearize. No trim damping is set:Twr_Kdmp/Bld_Kdmpdo work on the periodic blade and tower motion and bias the trimmed operating point (the IEA 10-MW’s trimmed generator torque came out about 8 % low at 4 m/s with 5000 N/(m/s)), and on light blades they can destabilise the start of the trim. The retry ladder adds damping only when a trim keeps running out of time. Keywordoverridesreplace any of these fields (e.g.twr_kdmp=5000.0, bld_kdmp=5000.0to trim with damping from the first attempt).- Parameters:
**overrides (object) – Field values to override on the returned settings (e.g.
hydro_radiation_mode=0when no.ssfile is available).- Returns:
LinearizationSettings – Settings with the offshore module configuration enabled.
- Return type:
- configure_linearization(deck_path, operating_point, settings=None, *, confine_to=None)[source]
Configure an OpenFAST deck for linearization at an operating point.
Edits the primary
.fstand its sub-files in place.- Parameters:
deck_path (str or pathlib.Path) – Path to the primary
.fstdeck (its sub-files are edited too).operating_point (OperatingPoint) – The operating point to set up.
settings (LinearizationSettings or None, optional) – Linearization tuning; defaults are used when
None.confine_to (str or pathlib.Path or None, optional) – When given, every input these settings edit must resolve inside this directory (see
require_inputs_within()); the sweep driver passes each case copy so a shared sub-file is never modified, while files that are only read may stay shared.
- Raises:
vane.io.FastInputError – If a required keyword (e.g.
Linearize) is absent from the deck, the operating point requests a positive wind speed but the deck has no usable InflowWind file to set it in (which would silently linearize in still air), or an edited input lies outsideconfine_to.- Return type:
None
Notes
For a multi-point sweep, copy the case to a per-operating-point directory first so shared sub-files are not mutated across points. The template deck is assumed to already enable the structural/aero/control modules appropriate for the case (
CompElast/CompAero/CompServo); this sets the linearization controls, the operating point, and — for a non-zero wind point —CompInflowso the configured wind is actually used.
- is_size_scaled_default(value)[source]
Return whether a trim setting is still the class default (not user-given).
- Parameters:
value (float) –
LinearizationSettings.trim_tolortrim_gain_pitch.- Returns:
bool –
Truewhen the value is the untouched class default, which the automatic sweep configuration scales with the rotor radius;Falsefor any value passed explicitly, even one equal to the default.- Return type:
- needs_rated_wind_speed(settings, deck_path)[source]
Return whether a rotating point on this deck needs
rated_wind_speed.The region split selects the CalcSteady trim case, and the ServoDyn generator handling under
trim_controller. It is therefore needed whencalc_steadyis on, or whentrim_controlleris on and the deck’s ServoDyn is enabled; a disabled ServoDyn (CompServo = 0) is never configured, so it needs no region.- Parameters:
settings (LinearizationSettings) – The linearization settings.
deck_path (str or pathlib.Path) – The primary
.fstdeck.
- Returns:
bool –
Truewhen a rotating operating point requires the rated wind speed.- Return type:
- require_inputs_within(deck_path, root, *, keys=None)[source]
Reject a deck whose edited sub-files resolve outside
root.Linearization and settle runs edit the primary deck and some of its sub-files. A sub-file referenced outside the per-case copy (e.g.
../ElastoDyn.datwhen the copy root is the deck’s own directory) would be edited where it lives – a file shared by every case and the template – so such a deck is rejected before anything is written. A sub-file that is only read may stay shared.- Parameters:
deck_path (str or pathlib.Path) – The primary
.fstdeck.root (str or pathlib.Path) – Directory every edited input must resolve inside (the case copy).
keys (Sequence[str] or None, optional) – The sub-file keywords that will be edited.
Nonechecks every file a linearization may edit (ElastoDyn, InflowWind, AeroDyn, ServoDyn, HydroDyn, SeaState), except the file of a module the deck switches off (CompAero,CompServo,CompHydroorCompSeaSt= 0).
- Raises:
vane.io.FastInputError – If the deck or an edited sub-file resolves outside
root.- Return type:
None
Pre-flight validation that a deck will actually linearize, before running it.
An OpenFAST linearization fails late and obscurely when the deck holds a module
configuration linearization does not support — a Bladed-style DLL controller (which has
no Jacobian), wave excitation, convolution radiation, or a state-space hydrodynamics
model whose .ss / .ssexctn coefficient files are missing. A long settle and
trim run then aborts with a terse solver error.
validate_deck() checks the effective configuration — the deck as it will be
after the LinearizationSettings edits are applied — and reports
every blocking problem at once, before any OpenFAST process is launched. It is a guard,
not a fixer: it never edits the deck. Settings that neutralise a problem (e.g.
trim_controller replacing the DLL, or wave_mod=0 forcing still water) clear the
corresponding check, so a raw FOWT template paired with
LinearizationSettings.offshore() passes, while the same template run with default
settings is rejected with an actionable message.
The checks mirror the linearization restrictions OpenFAST enforces in its glue code and
modules (see the Linearization restrictions reference page). A module the primary
deck switches off (CompServo, CompSeaSt, CompHydro, CompAero,
CompMooring, CompElast, CompInflow = 0 or another module choice) leaves its
input file inert, so that file is not inspected.
- OPENFAST_MAX_PATH_LENGTH = 258
Longest full path, in characters, the Windows OpenFAST build opens a file by.
Windows’ classic
MAX_PATHis 260 characters including the terminating null. The Intel-Fortran OpenFAST build is not long-path aware and does not collapse..: on OpenFAST v5.0.0 an input opened as<working dir>\.\..\5MW_Baseline\<file>of 258 characters was read and one of 259 characters failed withThe input file ... was not found.
- exception DeckPathLengthError[source]
Bases:
DeckValidationErrorRaised when OpenFAST would open a deck file by a path that is too long.
- exception DeckValidationError[source]
Bases:
ValueErrorRaised when a deck cannot be linearized as configured.
The message lists every blocking problem found, one per line, so the deck can be fixed in a single pass rather than one error per run.
- check_deck_path_lengths(deck_path, *, max_length=258)[source]
Reject a deck whose sub-files OpenFAST would open by an over-long path.
OpenFAST builds every sub-file path by joining the referencing file’s directory, as it spelled it, to the reference – without collapsing
... Started from the deck’s directory on the deck’s bare name (asrun_openfast()does), a deck that keeps shared files next to it in../Shared/therefore opens them as<deck dir>\.\..\Shared\<file>, and a nested reference inherits the whole chain. The operating system receives that string appended to the working directory. When it exceeds the Windows path limit the file cannot be opened and OpenFAST aborts at initialisation withThe input file ... was not found, although the file exists.This check walks the deck’s references (
deck_file_closure()) and spells each one as OpenFAST will, before any process is launched.- Parameters:
deck_path (str or pathlib.Path) – The primary
.fstdeck, in the directory OpenFAST will be run from.max_length (int, optional) – Longest permitted full path in characters. Default
OPENFAST_MAX_PATH_LENGTH.
- Raises:
DeckPathLengthError – If any file would be opened by a path longer than
max_length; the message names the longest such path and its length.FileNotFoundError – If
deck_pathis not an existing file.
- Return type:
None
Notes
The limit applies to Windows builds that are not long-path aware, which is why
run_openfast()runs this check only on Windows. A reference the walk cannot follow (an unreadable input) is skipped with a debug log rather than failing the check; OpenFAST will report it itself.
- validate_deck(deck_path, settings=None)[source]
Reject a deck that will not linearize as configured, before running it.
Validates the configuration that will be in force once
settingsis applied: a problem the settings neutralise (the DLL controller replaced by the trim, waves forced to still water) does not fail the check, but a genuinely non-linearizable state that nothing fixes does. The checks cover the module choices OpenFAST cannot linearize (SED, AeroDisk, ExtLoads, FEAMooring, OrcaFlex, ice and soil modules), the CalcSteady azimuth step, InflowWind (lidar, upflow), ServoDyn (control modes, generator model, timed generator start/stop), SeaState (waves, spreading, constrained waves, 2nd-order kinematics, still-water consistency), HydroDyn (potential-flow model, radiation/excitation models and their files, 2nd-order forces, additional body DOFs, radiation time step), AeroDyn (wake, unsteady-aero, and dynamic-wake models, unsteady aerodynamics reaching the blade-tip node under tip loss, nacelle drag), BeamDyn (pitch actuator), and MoorDyn (VIV and viscoelastic lines).- Parameters:
deck_path (str or pathlib.Path) – Path to the primary
.fstdeck. Its sub-files are inspected when present and their module is enabled; an onshore deck without them is fine.settings (LinearizationSettings or None, optional) – The settings that will be applied before running. Defaults to
LinearizationSettings(all module fields “leave unchanged”), so a deck that is already linearization-ready passes and a raw one is judged as-is.
- Raises:
DeckValidationError – If one or more blocking problems are found (all are reported together).
vane.io.FastInputError – If
deck_pathcannot be read at all.
- Return type:
None
Proactive per-operating-point trim auto-tuning for the linearization sweep.
The CalcSteady trim that finds a turbine’s periodic steady state before linearizing
is hardest at and above rated wind speed: thrust is high and the platform/tower response
is lightly damped, so a tolerance and trim damping that converge an easy below-rated
point often stall a near-/above-rated one. LinearizationRetryPolicy
recovers from that reactively — it adapts the tolerance, TMax, trim gains and
damping to the classified failure after a run has already failed, costing one or more
wasted OpenFAST runs per hard point.
This module sets those same knobs proactively. Given an operating point and the
campaign-base LinearizationSettings, tune_trim_settings()
returns the settings to start that point from, applying the documented above-rated trim
floors — a looser TrimTol and a trim-damping floor on Twr_Kdmp/Bld_Kdmp — to
points at or above rated so they are likely to converge on the first attempt. It only
ever raises tolerance/damping toward those floors, never tightens below the campaign
base, and below-rated and standstill points are returned unchanged, so the tuner can
only help. The reactive retry ladder still escalates from the tuned starting point for
any residual hard case.
The tuner adjusts only the three fields the retry ladder already treats as per-case
(trim_tol, twr_kdmp, bld_kdmp), so a sweep’s run provenance — which records
those per case and compares the rest campaign-wide — is unaffected. The
below/above-rated boundary is the settings’ own rated_wind_speed, the same split
configure_linearization() uses to choose torque vs pitch trim.
- class TrimTuningPolicy(above_rated_trim_tol=0.01, above_rated_damping=5000.0)[source]
Bases:
objectFloors applied to the trim settings of an at/above-rated operating point.
- Parameters:
above_rated_trim_tol (float) – Minimum rotor-speed trim tolerance (
TrimTol) to use at or above rated wind speed. A point’s tolerance is raised to this floor (never lowered below the campaign base); below-rated points keep the base tolerance. The default1e-2is the loosened tolerance the above-rated trim needs (the base default is1e-3). It must not exceedMAX_TRIM_TOL(0.1), and the retry ladder that escalates from it clamps there too.above_rated_damping (float) – Minimum artificial trim damping (
Twr_KdmpandBld_Kdmp) to use at or above rated wind speed; the point’s damping is raised to this floor. The damping acts only during the steady-state trim and is stripped from the linearized model, but it can bias the trimmed operating point slightly, so use the smallest floor that converges. The default5000matches the offshore preset’s trim damping.
- tune_trim_settings(operating_point, base, policy)[source]
Return the trim settings to start one operating point from.
For a rotating point that trims (
CalcSteady) at or above the base settings’rated_wind_speed, the trim tolerance and tower/blade trim damping are raised to the policy’s floors (whichever is larger — the campaign base is never tightened). A below-rated point, or a standstill/parked point that takes a single linearization without a trim, is returned unchanged.- Parameters:
operating_point (OperatingPoint) – The operating point whose starting trim settings to compute.
base (LinearizationSettings) – The campaign-base settings.
policy (TrimTuningPolicy) – The above-rated trim floors to apply.
- Returns:
LinearizationSettings – A copy of
basewith the above-rated trim floors applied when they raise the knobs, or an unchanged copy otherwise.- Return type:
Verify a linearization’s trimmed operating point against rotor-performance data.
VANE does not compute rotor performance: the user supplies the steady operating
schedule (wind speed, rotor speed, blade pitch and, optionally, the power and thrust
coefficients) and OpenFAST’s CalcSteady trims the turbine about it. Below rated it
trims the generator torque at the prescribed pitch and rotor speed; above rated it
trims the blade pitch at constant generator torque. Whether the trimmed point agrees
with the supplied curve is a direct consistency check on the curve and the deck: a
large mismatch points to a wrong curve, rotor radius, air density, gearbox ratio or
drivetrain loss, or a deck/curve inconsistency.
This module reads the trimmed operating point back out of the .lin files of each
linearization (inputs first, outputs as a fallback), derives the aerodynamic power
from the steady low-speed-shaft torque balance and compares rotor speed, pitch, power
coefficient and (when the outputs carry the rotor thrust) thrust coefficient with the
curve, each against a documented tolerance.
Torque balance
At a periodic steady state the azimuth-mean rotor acceleration vanishes, so the mean aerodynamic torque on the low-speed shaft equals the shaft torque transmitted to the gearbox. ElastoDyn relates the low-speed-shaft torque \(Q_{LSS}\) to the high-speed-shaft generator torque \(Q_g\) through the gearbox ratio \(N\) and efficiency \(\eta\),
so that losses reduce the generator torque when power flows rotor to generator and increase the required shaft torque when the generator motors the rotor. The aerodynamic power and power coefficient are then
The generator (electrical) efficiency does not enter: Q_g is the mechanical torque
on the high-speed shaft. Bearing friction and any other drivetrain loss not modelled by
GBoxEff are neglected.
The balance also assumes nothing else acts on the rotor. CalcSteady’s artificial
trim damping (Twr_Kdmp/Bld_Kdmp) does: on the IEA 10-MW sweep, trimmed with
5000 N/(m/s), it absorbed 8 % of the rotor torque at 4 m/s (OpenFAST v4.0.5: AeroDyn
power 436.6 kW against 396.7 kW from the balance; 1.3 % apart when the same point is
trimmed without damping, AeroDyn’s power unchanged). When the deck beside the .lin
files has non-zero trim damping, the aerodynamic power is therefore AeroDyn’s own
rotor power (RtFldPwr/RtAeroPwr) when the outputs carry it.
- class OperatingPointCheck(label, trimmed, aero_power, rotor_speed, pitch, cp, ct, issues=(), power_source='torque balance')[source]
Bases:
objectThe verification of one trimmed operating point.
- Parameters:
label (str) – Identifies the operating point (a sweep case name or the set index).
trimmed (TrimmedOperatingPoint) – The trimmed point read from the
.linfiles.aero_power (float) – Aerodynamic rotor power, in W, from the low-speed-shaft torque balance, or AeroDyn’s own rotor power when trim damping makes the balance invalid (see
power_source).rotor_speed (QuantityCheck) – Rotor speed (rpm) against the curve.
pitch (QuantityCheck) – Blade pitch (deg) against the curve.
cp (QuantityCheck) – Power coefficient against the curve.
ct (QuantityCheck) – Thrust coefficient against the curve (unavailable without a rotor-thrust output or a curve
ct).issues (tuple[str, ...]) – Notes on missing data, including the trimmed point’s own issues.
power_source (str) – Where
aero_powercomes from:"torque balance"or the AeroDyn output channel ("output: AD RtFldPwr").
- trimmed: TrimmedOperatingPoint
- rotor_speed: QuantityCheck
- pitch: QuantityCheck
- cp: QuantityCheck
- ct: QuantityCheck
- property checks: dict[str, QuantityCheck]
Return the four checks keyed by quantity name.
- property status: Literal['pass', 'flag', 'incomplete']
Return
"flag"if any check fails,"incomplete"if data is missing.A point is
"incomplete"when no check fails but its rotor speed or pitch could not be compared, or the trimmed data carries an issue. A curve that simply lackscp/ctdoes not make a point incomplete.
- class OperatingPointVerification(points, constants, tolerances, skipped=<factory>)[source]
Bases:
objectThe trimmed-versus-curve verification of a set of linearizations.
- Parameters:
points (tuple[OperatingPointCheck, ...]) – One check per verified operating point, in input order.
constants (TurbineConstants) – The turbine constants used.
tolerances (TrimTolerances) – The tolerances applied.
skipped (tuple[str, ...], optional) – Operating points that could not be verified (e.g. a non-converged sweep case), each with its reason.
- points: tuple[OperatingPointCheck, ...]
- constants: TurbineConstants
- tolerances: TrimTolerances
- property flagged: tuple[OperatingPointCheck, ...]
Return the points with at least one quantity outside tolerance.
- to_records()[source]
Return one flat record per point, e.g. for a
pandas.DataFrame.- Returns:
list[dict[str, float or str or None]] – Keys:
label,wind_speed,generator_torque,aero_power,power_source,aerodyn_power,trim_damping,rotor_thrust,status, and for each ofrotor_speed,pitch,cpandctthe<q>,<q>_curve,<q>_deviationand<q>_statusentries.- Return type:
- class QuantityCheck(trimmed, curve, tolerance, relative)[source]
Bases:
objectOne trimmed quantity compared with the curve.
- Parameters:
- class RotorPerformanceCurve(wind_speed, rotor_speed_rpm, blade_pitch_deg, cp=None, ct=None)[source]
Bases:
objectA user-supplied steady rotor-performance schedule.
- Parameters:
wind_speed (tuple[float, ...]) – Strictly increasing hub-height wind speeds, in m/s.
rotor_speed_rpm (tuple[float, ...]) – Rotor speed at each wind speed, in rpm.
blade_pitch_deg (tuple[float, ...]) – Collective blade pitch at each wind speed, in degrees.
cp (tuple[float, ...] or None, optional) – Aerodynamic power coefficient at each wind speed (
NaNwhere unknown), orNonewhen the curve carries no power coefficient.ct (tuple[float, ...] or None, optional) – Rotor thrust coefficient at each wind speed (
NaNwhere unknown), orNonewhen the curve carries no thrust coefficient.
- Raises:
ValueError – If the arrays differ in length, have fewer than two samples, contain a non-finite wind, rotor speed or pitch, or the wind speeds are not strictly increasing.
- classmethod from_arrays(wind_speed, rotor_speed_rpm, blade_pitch_deg, *, cp=None, ct=None)[source]
Build a curve from array-likes, e.g.
operating_curve_from_csvoutput.- Parameters:
wind_speed (Sequence[float]) – The required schedule, in m/s, rpm and degrees.
rotor_speed_rpm (Sequence[float]) – The required schedule, in m/s, rpm and degrees.
blade_pitch_deg (Sequence[float]) – The required schedule, in m/s, rpm and degrees.
cp (Sequence[float] or None, optional) – Optional power and thrust coefficients.
ct (Sequence[float] or None, optional) – Optional power and thrust coefficients.
- Returns:
RotorPerformanceCurve – The validated curve.
- Return type:
- class TrimTolerances(rotor_speed_rel=0.01, pitch_deg=0.5, cp_rel=0.05, ct_rel=0.05)[source]
Bases:
objectTolerances of the trimmed-versus-curve comparison.
The defaults reflect what a correct deck/curve pair achieves on the reference turbines (rotor speed exact, pitch within a few tenths of a degree, power coefficient within a few percent): a looser agreement is worth investigating.
- Parameters:
rotor_speed_rel (float, optional) – Relative rotor-speed tolerance (default 0.01, i.e. 1 %).
pitch_deg (float, optional) – Absolute blade-pitch tolerance, in degrees (default 0.5).
cp_rel (float, optional) – Relative power-coefficient tolerance (default 0.05).
ct_rel (float, optional) – Relative thrust-coefficient tolerance (default 0.05).
- Raises:
ValueError – If a tolerance is negative or non-finite.
- class TrimmedOperatingPoint(wind_speed, rotor_speed_rpm, blade_pitch_deg, blade_pitch_spread_deg, generator_torque, n_lin, pitch_source=None, generator_torque_source=None, rotor_thrust=None, rotor_thrust_source=None, aerodyn_power=None, issues=(), aerodyn_power_source=None, trim_damping=None)[source]
Bases:
objectThe operating point OpenFAST trimmed to, read from one linearization’s files.
Every quantity is the mean over the azimuth files of the set (and, for pitch, over the blades).
- Parameters:
wind_speed (float) – Hub-height wind speed, in m/s.
rotor_speed_rpm (float) – Rotor speed, in rpm.
blade_pitch_deg (float) – Blade pitch, mean over blades and azimuths, in degrees (
NaNif absent).blade_pitch_spread_deg (float) – Largest minus smallest pitch over blades and azimuths, in degrees (
NaNif absent). A trimmed collective pitch has zero spread.generator_torque (float) – High-speed-shaft generator torque, in N m (
NaNif absent).n_lin (int) – Number of
.linfiles in the set.pitch_source (str or None) – The channel the pitch was read from, or
None.generator_torque_source (str or None) – The channel the generator torque was read from, or
None.rotor_thrust (float or None) – AeroDyn rotor aerodynamic thrust along the shaft, in N, when the outputs carry it; else
None.rotor_thrust_source (str or None) – The output channel the thrust was read from.
aerodyn_power (float or None) – AeroDyn rotor aerodynamic power, in W, when the outputs carry it; an independent check on the torque-balance power.
issues (tuple[str, ...]) – Human-readable notes on anything missing or inconsistent.
aerodyn_power_source (str or None) – The output channel
aerodyn_powerwas read from.trim_damping (float or None) – The larger of the
Twr_KdmpandBld_Kdmptrim-damping factors, in N/(m/s), of the<root>.fstdeck beside the files (0.0whenCalcSteadyis off);Nonewhen that deck cannot be read. Non-zero trim damping absorbs rotor torque, so the torque balance no longer gives the aerodynamic power (seeverify_operating_points()).
- class TurbineConstants(gearbox_ratio, gearbox_efficiency, rotor_radius, air_density)[source]
Bases:
objectTurbine constants the torque balance and coefficients need.
- Parameters:
gearbox_ratio (float) – Gearbox ratio \(N\) (high-speed over low-speed shaft speed), positive.
gearbox_efficiency (float) – Gearbox efficiency \(\eta\) as a fraction in
(0, 1](ElastoDyn’sGBoxEffis in percent;from_deck()converts it).rotor_radius (float) – Rotor radius \(R\) used to normalize the coefficients, in m.
air_density (float) – Air density \(\rho\), in kg/m^3.
- Raises:
ValueError – If any constant is non-finite or non-positive, or the efficiency exceeds one.
- classmethod from_deck(fst_path, *, radius_convention='projected')[source]
Read the constants from an OpenFAST deck.
The gearbox ratio (
GBRatio, orGBoxRatiofor Simplified-ElastoDyn), gearbox efficiency (GBoxEff, percent; 100 % when absent), tip radius (TipRad) and precone (PreCone(i)orPreCone) are read from the structural (EDFile) input; this holds for BeamDyn decks too, whose rotor geometry still lives in ElastoDyn. The air density is AeroDyn’sAirDens, or the.fstAirDenswhen AeroDyn’s is"default"or absent.- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck.radius_convention ({"projected", "tip"}, optional) –
"projected"(default) normalizes with the rotor-plane radiusTipRad * cos(PreCone), the swept-area radius most blade-element tools use;"tip"usesTipRaditself. Pass explicit constants when a curve was normalized with some other radius.
- Returns:
TurbineConstants – The constants.
- Raises:
ValueError – If a required value is missing or non-numeric, or
radius_conventionis unknown.- Return type:
- aerodynamic_power(generator_torque, rotor_speed_rpm, constants)[source]
Return the aerodynamic rotor power from the steady torque balance.
- Parameters:
generator_torque (float) – High-speed-shaft generator torque, in N m.
rotor_speed_rpm (float) – Rotor speed, in rpm.
constants (TurbineConstants) – Supplies the gearbox ratio and efficiency.
- Returns:
float – \(N Q_g \Omega \eta^{-\operatorname{sgn}(Q_g\Omega)}\), in W: the gearbox loss is added back when power flows rotor to generator and subtracted when the generator motors the rotor.
- Return type:
- rotor_performance_from_csv(path)[source]
Read a rotor-performance CSV, including its optional coefficient columns.
The required
wind_speed,rotor_speed_rpmandblade_pitch_degcolumns are parsed, unit-converted and validated exactly as byoperating_curve_from_csv()(unit-annotated headers such aswind_speed [m/s]are accepted). The optional power-coefficient column (cp,power_coefficient,RtAeroCp,RtFldCp) and thrust-coefficient column (ct,thrust_coefficient,RtAeroCt,RtFldCt) are read as well; a blank or non-numeric coefficient cell becomesNaN(unknown) rather than an error.The coefficients are returned as fractions. A coefficient column’s unit is read from its header bracket, as for the required columns: no unit,
[-],[1]or[fraction]means a fraction, and[%],[percent]or[pct]a percentage, which is divided by 100. Any other unit is rejected. So is a scale that contradicts the declared unit: a fraction above the physical bound (the Betz limit 16/27 forcp, 2 forct), which is what a percentage written without its unit looks like, and a column declared in percent whose non-zero values are all at most 1 in magnitude, which reads as fractions labelled as percent. A separate units row is not supported.- Parameters:
path (str or pathlib.Path) – Path to the rotor-performance CSV.
- Returns:
RotorPerformanceCurve – The curve, sorted by ascending wind speed.
- Raises:
FileNotFoundError – If
pathdoes not exist.ValueError – As
operating_curve_from_csv(); if more than one column matches the power or thrust coefficient; or if a coefficient column has an unsupported unit or a scale that contradicts its unit (see above).
- Return type:
- trimmed_operating_point(lin_files)[source]
Read the trimmed operating point from one linearization’s
.linfiles.The blade pitch is read from the ElastoDyn pitch-command inputs (
ED Blade <i> pitch command, the same for ElastoDyn and BeamDyn blades), or the collective pitch-command input; the generator torque from theED Generator torqueinput. When the inputs are absent (LinInputs = 0) theED BldPitch<i>andSrvD GenTqoutputs are used instead. Wind and rotor speed come from each file’s header. Every quantity is averaged over the azimuth files.- Parameters:
lin_files (Sequence[LinFile or str or os.PathLike]) – The full-system
.linfiles of one linearization (one per azimuth), parsed or as paths.- Returns:
TrimmedOperatingPoint – The trimmed point; a quantity that cannot be found is
NaNand explained inTrimmedOperatingPoint.issues.- Raises:
ValueError – If
lin_filesis empty.- Return type:
- verify_operating_points(lin_sets, curve, *, deck=None, constants=None, radius_convention='projected', tolerances=None, labels=None)[source]
Verify the trimmed operating points of linearizations against a curve.
For each linearization (its set of azimuth
.linfiles) the trimmed wind, rotor speed, blade pitch and generator torque are read back (trimmed_operating_point()), the aerodynamic power is derived from the steady low-speed-shaft torque balance (aerodynamic_power()) and the power coefficient \(C_p = P / (\tfrac12\rho\pi R^2U^3)\) formed. The thrust coefficient is formed only when the outputs carry AeroDyn’s rotor thrust (RtFldFxhorRtAeroFxhin the AeroDynOutList). Each quantity is compared with the curve interpolated at the trimmed wind speed.Below rated,
CalcSteadyprescribes the rotor speed and pitch from the curve, so their agreement is by construction and \(C_p\) (from the trimmed torque) is the informative check; above rated the trimmed pitch is.When the deck beside a set’s files trimmed with artificial damping (non-zero
Twr_Kdmp/Bld_KdmpwithCalcSteady), the damping absorbs part of the rotor torque and the generator torque no longer balances the aerodynamic torque. The power is then AeroDyn’s rotor power output when present (OperatingPointCheck.power_sourcesays which was used); without it the balance is kept and a warning logged.- Parameters:
lin_sets (Sequence[Sequence[LinFile or str or os.PathLike]]) – One entry per operating point: the
.linfiles of that linearization.curve (RotorPerformanceCurve or str or pathlib.Path or tuple) – The user’s rotor-performance data. A path is read with
rotor_performance_from_csv()(so itscp/ctcolumns are used); a 3-tuple such asoperating_curve_from_csv()returns carries no coefficients, so only rotor speed and pitch are compared.deck (str or pathlib.Path or None, optional) – An
.fstdeck of the turbine, read for the constants (TurbineConstants.from_deck()). Required unlessconstantsis given.constants (TurbineConstants or None, optional) – Explicit constants; take precedence over
deck.radius_convention ({"projected", "tip"}, optional) – Radius used with
deck(seeTurbineConstants.from_deck()).tolerances (TrimTolerances or None, optional) – Comparison tolerances (default
TrimTolerances).labels (Sequence[str] or None, optional) – A label per set; defaults to
"ws<wind>".
- Returns:
OperatingPointVerification – The per-point checks and overall outcome.
- Raises:
ValueError – If neither
decknorconstantsis given,labelshas the wrong length, a set is empty, or the deck/curve cannot be read.- Return type:
- verify_sweep_operating_points(sweep, curve, *, constants=None, radius_convention='projected', tolerances=None)[source]
Verify the trimmed points of a
run_operating_sweep()result.Every converged case with
.linfiles is verified, labelled by its operating point’s name; a case that did not converge or produced no.linfiles is listed inOperatingPointVerification.skipped. Unlessconstantsis given, the turbine constants are read from the first verified case’s configured deck.- Parameters:
sweep (OperatingSweepResult) – The sweep result.
curve (RotorPerformanceCurve or str or pathlib.Path or tuple) – The rotor-performance data (see
verify_operating_points()); pass the same CSV the sweep used to get thecp/ctcomparison too.constants (TurbineConstants or None, optional) – Explicit constants.
radius_convention ({"projected", "tip"}, optional) – Radius used when reading the constants from the deck.
tolerances (TrimTolerances or None, optional) – Comparison tolerances.
- Returns:
OperatingPointVerification – The per-point checks.
- Raises:
ValueError – If no case of the sweep can be verified.
- Return type:
Post-run acceptance checks for a linearized operating point.
CalcSteady establishes periodicity according to OpenFAST’s normalized output
criterion. That criterion is necessary, but a deliberately loose TrimTol can
still stop while a floating platform carries a secular six-DOF drift. Such a point
is parseable and can produce a finite MBC model, yet it is a poor equilibrium about
which to build a small-perturbation model.
This module provides an independent physical check. It reads the ElastoDyn platform
rate states from every full-system .lin file and estimates the azimuth mean of each
rate component with quadrature weights that exactly annihilate the 1P, 2P and 3P
harmonics at the actual sample azimuths. Periodic motion therefore cancels; a secular
drift does not. The resulting translational and angular residuals can reject a
nominally successful trim without mistaking ordinary once-per-revolution motion for
non-convergence.
The weights are the minimum-norm solution of the harmonic moment conditions, which for
seven or more samples is exactly the constant term of a least-squares fit of a mean
plus 1P–3P harmonics. On an exactly uniform sweep they reduce to the equal-weight
average. They remain valid for the non-uniform azimuths CalcSteady actually
produces: OpenFAST takes each snapshot at the first time step at or past its target
azimuth, so real sweeps are uniform only to within RotSpeed * DT.
- class LinearizationAcceptancePolicy(max_mean_translational_speed=0.001, max_mean_angular_speed=0.0001, min_rotating_azimuths=12)[source]
Bases:
objectPhysical residual limits for a floating operating point.
- Parameters:
max_mean_translational_speed (float) – Maximum norm of the azimuth-mean platform translational velocity, in m/s.
max_mean_angular_speed (float) – Maximum norm of the azimuth-mean platform angular velocity, in rad/s.
min_rotating_azimuths (int) – Minimum number of distinct azimuth samples needed to distinguish periodic motion from secular drift for a rotating point. The default is 12 and values below four are prohibited so blade-passing motion cannot alias into the mean. A non-rotating point needs only one linearization.
- class OperatingPointResidualAudit(assessed, accepted, reason, file_count, distinct_azimuths, rotating, translational_channels, angular_channels, mean_translational_velocity, mean_angular_velocity, mean_translational_speed, mean_angular_speed, max_instantaneous_translational_speed, max_instantaneous_angular_speed, max_mean_translational_speed, max_mean_angular_speed)[source]
Bases:
objectPhysical residual evidence extracted from one linearization sweep.
- Parameters:
assessed (bool)
accepted (bool)
reason (str | None)
file_count (int)
distinct_azimuths (int)
rotating (bool)
mean_translational_speed (float | None)
mean_angular_speed (float | None)
max_instantaneous_translational_speed (float | None)
max_instantaneous_angular_speed (float | None)
max_mean_translational_speed (float)
max_mean_angular_speed (float)
- class PeriodicStateResidualAudit(assessed, reason, file_count, distinct_azimuths, rotating, min_rotating_azimuths, channels, excluded_rotating_channels, excluded_cyclic_channels, max_mean_to_rms_ratio)[source]
Bases:
objectState-derivative residuals for a periodic OpenFAST operating point.
Rotating-frame states are excluded because their native derivatives contain the intended rotor-periodic coordinate transport. The generator-azimuth coordinate is also excluded because its derivative is the intended rotor speed; its angular acceleration state remains in the audit. No heterogeneous state units are combined into one physical norm.
- Parameters:
- channels: tuple[StateDerivativeResidual, ...]
- class StateDerivativeResidual(state_description, derivative_description, module, unit, mean_derivative, rms_derivative, oscillatory_rms_derivative, mean_to_rms_ratio, active)[source]
Bases:
objectAzimuth statistics for one non-rotating continuous-state derivative.
mean_to_rms_ratiois dimensionless and invariant to a change of state units. It approaches zero for a resolved zero-mean periodic derivative and one for a constant secular derivative. It isNonewhen the derivative is inactive at floating-point resolution; absolutemean_derivativeandrms_derivativevalues are retained so this ratio is never mistaken for an engineering limit.- Parameters:
- audit_operating_point_residual(lin_files, policy=None)[source]
Audit secular platform drift across an OpenFAST azimuth sweep.
Paths are parsed with VANE’s strict
.linreader; already parsedLinFileobjects are accepted too. If the model has no ElastoDyn platform-rate states (for example a fixed-bottom turbine), the check is not applicable and returnsassessed=False, accepted=True. A rotating point with too few distinct azimuths is not accepted because an instantaneous periodic rate cannot be separated from secular drift.- Parameters:
lin_files (Sequence[path-like or LinFile]) – Full-system files from one operating point.
policy (LinearizationAcceptancePolicy or None, optional) – Residual limits; defaults to
LinearizationAcceptancePolicy.
- Returns:
OperatingPointResidualAudit – The component means, vector norms, thresholds, and acceptance decision.
- Raises:
ValueError – If no files are supplied or their state descriptions are inconsistent.
- Return type:
- audit_periodic_state_residual(lin_files, *, min_rotating_azimuths=12)[source]
Audit mean derivatives without mixing state units into a scalar norm.
The derivative operating point in each
.linfile is sampled over azimuth. For every non-rotating, non-cyclic continuous state, this function reports the azimuth mean, total RMS, zero-mean oscillatory RMS, and the scale-invariant ratioabs(mean) / RMS. A resolved periodic equilibrium has a small ratio, whereas a secular derivative survives the azimuth mean. Absolute residuals must still be interpreted in their stated engineering units.- Parameters:
lin_files (Sequence[path-like or LinFile]) – Full-system files from one operating point.
min_rotating_azimuths (int, optional) – Minimum number of distinct azimuths required before the periodic statistics are assessed. The default is 12; publication campaigns should normally use 36. The denser requirement prevents a 3P component from aliasing into a three-azimuth mean.
- Returns:
PeriodicStateResidualAudit – Per-state residual statistics and the explicit exclusions.
- Raises:
ValueError – If no files are supplied, the requested sample count is smaller than four, or the state/derivative channel layouts disagree.
- Return type:
Solve a floating turbine’s steady operating point by a fully-coupled settle run.
A CalcSteady linearization of a floating turbine needs good initial conditions — in particular the platform’s thrust-loaded equilibrium position — or the open-loop trim diverges near peak thrust (the rotor decelerates into a stalled, motoring state). The dependable way to obtain them is to run the fully-coupled model with its real controller at a steady wind until it settles, then read the operating point off the time series.
solve_steady_state() does exactly that: it configures the deck for a constant-wind
time-domain run (still water, uniform inflow with no shear by default – the same
inflow the linearization then uses – and the controller left active), runs OpenFAST,
discards the initial transient, and averages the steady window to return an
OperatingPoint carrying the settled rotor speed,
blade pitch, and PlatformPosition. Feeding that
point back into configure_linearization() (with
LinearizationSettings.offshore()) starts the linearization at the true
equilibrium.
The deck is edited in place, so pass a writable working copy (the linearization driver copies the case per operating point).
The settle run uses exactly the still water the linearization uses: SeaState is set to
WaveMod = 0 and the wave options OpenFAST rejects in still water are neutralised by
the same helper configure_linearization() applies.
Unlike the linearization, the settle run keeps the deck’s Bladed-style controller
library (ServoDyn DLL_FileName) active. Published decks often point it at a
library built for another platform (a Linux libdiscon.so path on Windows), so
the library is checked before OpenFAST launches and a missing one fails fast; pass
controller_library to point the settle copy at a library built for this host.
- ConfirmTimeSource
How the length of a two-stage settle’s confirmation run was chosen.
alias of
Literal[‘user’, ‘platform_period’, ‘damped_stage’, ‘fallback’]
- class EquilibriumTolerances(translation_m=0.1, rotation_deg=0.05, rotor_speed_rel=0.005, pitch_deg=0.1, residual_damping_ratio=0.005, growing_damping_ratio=0.02, oscillation_velocity_rms=0.2)[source]
Bases:
objectTolerances on a change of the settled operating-point means.
Used to judge whether the undamped confirmation of a two-stage settle reproduces the damped stage’s equilibrium, and – with
criterion="mean_drift"– whether a run’s means still drift between the halves of its analysis window.- Parameters:
translation_m (float, optional) – Platform surge/sway/heave, m. Default
0.1.rotation_deg (float, optional) – Platform roll/pitch/yaw, deg. Default
0.05.rotor_speed_rel (float, optional) – Rotor speed, relative to
max(|reference rotor speed|, 1 rpm). Default0.005.pitch_deg (float, optional) – Collective blade pitch, deg. Default
0.1.residual_damping_ratio (float, optional) – Smallest damping ratio at which the residual platform oscillation of an undamped confirmation counts as decaying (see
SettleConfirmation.residual_damping_ratio). Default0.005.growing_damping_ratio (float, optional) – Classification threshold: an undamped confirmation whose residual damping ratio is below
-growing_damping_ratiois clearly growing (its equilibrium is unstable under the deck’s controller), not merely sustained. Default0.02: the amplitude then grows byexp(2 pi 0.02) - 1, about 13 %, per cycle and by a factor of about 2.7 over the eight cycles an automatically sized confirmation analyses – unmistakable growth, whereas a limit cycle, a beating of two slow modes or a controller re-exciting the platform after the restart stays above it (the IEA 15-MW semi at 12-14 m/s gives -0.007 to -0.009). It does not reject anything: with the"auto"criterion a growing oscillation about a confirmed equilibrium is accepted like a sustained one, but flagged (SettleConfirmation.growing_oscillation) and logged with its own warning (seesolve_steady_state()).oscillation_velocity_rms (float, optional) – Largest platform-velocity RMS (m/s for the translational channels, deg/s for the angular ones) of a sustained or growing oscillation the
"auto"criterion accepts about a confirmed equilibrium. Default0.2, twenty times the default settle tolerance: at a 100 s surge period about +/-4.5 m of surge, at a 30 s pitch period about +/-1.4 deg of pitch – small enough for its whole-period means to be a well-defined measurement of the equilibrium. A larger one is reported, not accepted.
- Raises:
ValueError – If any tolerance is not positive and finite.
- exceeded(changes, reference_rotor_speed_rpm)[source]
Return the quantities whose change exceeds its tolerance.
- Parameters:
changes (Mapping[str, float]) – Change of each mean, keyed as
SettleAttempt.mean_drift.reference_rotor_speed_rpm (float) – The rotor speed the relative rotor-speed tolerance refers to.
- Returns:
tuple of str – The names (in the order of
changes) outside tolerance.- Return type:
- class SettleAttempt(translational_velocity_rms, angular_velocity_rms, assessed, converged, settle_time, iteration, window_start=None, window_end=None, stage='settle', mean_drift=None, oscillation_period=None, criterion=None, residual_damping_ratio=None)[source]
Bases:
objectConvergence evidence from one adaptive settle attempt.
Each entry preserves the horizon and measured platform-velocity RMS values before a later adaptive run overwrites the OpenFAST output file.
- Parameters:
translational_velocity_rms (float) – Largest RMS platform translational velocity (m/s) over the analysis window;
nanwhen no translational-velocity channel was output.angular_velocity_rms (float) – Largest RMS platform angular velocity (deg/s) over the analysis window;
nanwhen no angular-velocity channel was output.assessed (bool) – Whether any platform-velocity channel was available to judge settling.
converged (bool) – Whether every assessed velocity RMS was within tolerance.
settle_time (float) – The
TMax(s) of this attempt.iteration (int) – One-based attempt number.
window_start (float or None, optional) – Bounds (s) of the trailing analysis window this attempt was judged over,
[settle_time - (initial settle_time - discard_time), settle_time].Nonewhen not recorded.window_end (float or None, optional) – Bounds (s) of the trailing analysis window this attempt was judged over,
[settle_time - (initial settle_time - discard_time), settle_time].Nonewhen not recorded.stage ({"settle", "damped", "confirm"}, optional) – Which run this was: an ordinary settle run (
"settle"), a run of the temporarily damped first stage of a two-stage settle ("damped"), or its undamped confirmation run ("confirm"). Seesolve_steady_state().mean_drift (dict[str, float] or None, optional) – Change of each measured operating-point mean between the first and second halves of the analysis window (second minus first):
surge/sway/heave(m),roll/pitch/yaw(deg),rotor_speed_rpmandblade_pitch_deg.Nonewhen not computed.oscillation_period (float or None, optional) – Dominant period (s) of the platform-velocity channel with the largest RMS over the analysis window – the period of whatever is still moving.
Nonewhen it could not be estimated (too few samples, or no motion).criterion ({"velocity", "mean_drift"} or None, optional) – The criterion that judged this attempt (with
criterion="auto"it may differ between attempts).Nonewhen not recorded.residual_damping_ratio (float or None, optional) – Damping ratio of the platform oscillation over the analysis window, from the cycle-by-cycle decay of the most active platform-velocity channel (positive: decaying; near zero: sustained; negative: growing).
Nonewhen fewer than two whole cycles were output.
- class SettleConfirmation(damping, damped_converged, damped_translational_velocity_rms, damped_angular_velocity_rms, damped_settle_time, damped_means, confirm_means, shift, shift_flagged, confirm_converged, residual_damping_ratio, stable, confirmed, tolerances, confirm_time=None, confirm_time_source=None, extended=False, sustained_oscillation=False)[source]
Bases:
objectEvidence of a two-stage settle: damped stage, then undamped confirmation.
- Parameters:
damping (SettlePlatformDamping) – The temporary platform damping of the first stage.
damped_converged (bool) – Whether the damped stage met the settle criterion.
damped_translational_velocity_rms (float) – The damped stage’s final platform-velocity RMS (m/s, deg/s;
nanwhen not measured).damped_angular_velocity_rms (float) – The damped stage’s final platform-velocity RMS (m/s, deg/s;
nanwhen not measured).damped_settle_time (float) –
TMax(s) of the damped stage’s final run.damped_means (dict[str, float]) – The damped stage’s settled means (keys as
SettleAttempt.mean_drift); the undamped confirmation starts from them.confirm_means (dict[str, float]) – The undamped confirmation run’s means over its analysis window (averaged over whole periods of any residual oscillation).
shift (dict[str, float]) –
confirm_means - damped_meansfor every quantity measured in both.shift_flagged (tuple of str) – Quantities whose shift exceeds
tolerances.confirm_converged (bool) – Whether the undamped confirmation run met the settle criterion by itself.
residual_damping_ratio (float or None) – Damping ratio of the undamped run’s residual platform oscillation, from the decay of its cycle-by-cycle RMS: positive when it dies out, zero or negative when it is sustained or growing.
Nonewhen fewer than two whole cycles were output.stable (bool) – Whether the undamped run is stable about the equilibrium: it met the criterion by itself, or (velocity criterion) its residual oscillation decays with a damping ratio of at least
tolerances.residual_damping_ratio.confirmed (bool) –
Truewhen the undamped run reproduced the damped equilibrium withintolerancesand isstable, or –sustained_oscillation– when it reproduced it while carrying a measurable sustained or growing oscillation ("auto"). Restarting an undamped model, even at its equilibrium, rings its slow modes (the controller’s start-up transient alone does), and a lightly damped mode cannot ring down within a short confirmation; a decaying residual is accepted as confirmation of a stable equilibrium.Falsemeans the damping changed the answer, or the undamped model’s oscillation could not be accepted (too large to measure its mean, too few cycles, or a criterion other than"auto").tolerances (EquilibriumTolerances) – The tolerances applied.
confirm_time (float or None, optional) –
TMax(s) of the confirmation run that was judged (the extended one whenextended).Nonewhen not recorded.confirm_time_source ({"user", "platform_period", "damped_stage", "fallback"} or None, optional) – How the first confirmation’s length was chosen: given by the caller, or sized automatically from the slowest platform period (
platform_periodofsolve_steady_state(), e.g. the standstill platform probe’s), from the damped stage’s dominant period, or the fallback length when neither was known.Nonewhen not recorded.extended (bool, optional) –
Truewhen the confirmation was decaying but its means had not yet returned to the damped equilibrium, so it was rerun once twice as long and that longer run was judged.sustained_oscillation (bool, optional) –
Truewhen the point was accepted although the undamped run keeps a non-decaying oscillation: its whole-period means reproduce the damped equilibrium withintolerances, and either the mean-drift criterion accepted the run or –"auto"criterion only – the oscillation is measurable (residual damping ratio known, velocity RMS withintolerances.oscillation_velocity_rms), whether sustained or growing. Linear damping cannot move an equilibrium (it acts on the velocity, zero there), so the damped stage’s equilibrium is the undamped model’s, stable or not; it is the operating point. A growing oscillation is additionally flagged bygrowing_oscillation.
- damping: SettlePlatformDamping
- tolerances: EquilibriumTolerances
- property growing_oscillation: bool
Return whether the point was accepted with a growing oscillation.
Truewhensustained_oscillationholds and the residual damping ratio is below-tolerances.growing_damping_ratio: the equilibrium is unstable under the deck’s controller (the closed-loop oscillation grows without the added damping). Linear damping does not move an equilibrium, and the shift test reproduced it, so it is linearized; the linearization trims without the controller, so the controller-in-the-loop instability is not part of the linear model.- Returns:
bool – Whether the accepted confirmation’s oscillation is clearly growing.
- to_dict()[source]
Return the confirmation as a dictionary, with
growing_oscillation.- Returns:
dict[str, object] – Every field (as
dataclasses.asdict()) plusgrowing_oscillation.- Return type:
- class SettleConvergence(translational_velocity_rms, angular_velocity_rms, assessed, converged, settle_time, iterations, attempts=(), window_start=None, window_end=None, criterion='velocity', mean_drift=None, oscillation_period=None, confirmation=None, requested_criterion='velocity', criterion_switched=False, prescribed_rotor=False, residual_damping_ratio=None, sustained_oscillation=False)[source]
Bases:
objectWhether a settle run reached equilibrium, judged by platform-velocity RMS.
- Parameters:
translational_velocity_rms (float) – Largest root-mean-square platform translational velocity (m/s) over the trailing analysis window, across the output velocity channels;
nanwhen none were output.angular_velocity_rms (float) – Largest RMS platform angular velocity (deg/s) over the trailing analysis window;
nanwhen no angular-velocity channel was output.assessed (bool) – Whether any platform-velocity channel was available to judge settling. When
Falsethe run cannot be confirmed settled (the deck output no velocity).converged (bool) – Whether every assessed velocity RMS is within tolerance (
Truewhen not assessed — nothing measured cannot fail, butassessedrecords that).settle_time (float) – The
TMax(s) of the run this convergence describes.iterations (int) – How many settle runs were performed (more than one only when adaptive).
attempts (tuple of SettleAttempt) – Ordered convergence evidence for every attempted horizon. This makes an adaptive accept/reject decision reconstructible after intermediate OpenFAST output files have been overwritten.
window_start (float or None, optional) – Bounds (s) of the trailing analysis window of the final run, over which both the convergence outcome and the returned operating-point means were computed.
Nonewhen not recorded.window_end (float or None, optional) – Bounds (s) of the trailing analysis window of the final run, over which both the convergence outcome and the returned operating-point means were computed.
Nonewhen not recorded.criterion ({"velocity", "mean_drift"}, optional) – The settle criterion that judged the run: platform-velocity RMS within tolerance (
"velocity", the default) or operating-point means that no longer drift between the two halves of the analysis window ("mean_drift"). Withcriterion="auto"this is the criterion the automatic choice settled on. Seesolve_steady_state().mean_drift (dict[str, float] or None, optional) – The final run’s half-window mean drift (see
SettleAttempt.mean_drift).oscillation_period (float or None, optional) – The final run’s dominant platform-velocity period (s); see
SettleAttempt.oscillation_period.confirmation (SettleConfirmation or None, optional) – For a two-stage settle (
platform_dampinggiven), the damped stage’s evidence and the undamped confirmation’s outcome;Nonefor an ordinary settle.requested_criterion ({"velocity", "mean_drift", "auto"}, optional) – The criterion the caller asked for. Differs from
criteriononly for"auto".criterion_switched (bool, optional) –
Truewhencriterion="auto"switched from the velocity to the mean-drift criterion because the platform carried a sustained, non-decaying oscillation the velocity criterion can never accept.prescribed_rotor (bool, optional) –
Truewhen the settle ran without a controller, with the rotor speed and blade pitch held at their initial values (seeprescribed_rotorofsolve_steady_state()); the settled rotor speed and pitch are then the prescribed ones and only the platform equilibrium was found.residual_damping_ratio (float or None, optional) – The final run’s platform-oscillation damping ratio (see
SettleAttempt.residual_damping_ratio).sustained_oscillation (bool, optional) –
Truewhen a two-stage settle was accepted with a non-decaying (sustained or growing) oscillation about its confirmed equilibrium (seeSettleConfirmation.sustained_oscillation); a warning is logged.
- attempts: tuple[SettleAttempt, ...] = ()
- confirmation: SettleConfirmation | None = None
- property growing_oscillation: bool
Return whether the point was accepted with a growing oscillation.
- Returns:
bool –
SettleConfirmation.growing_oscillationof the confirmation;Falsefor a single-stage settle.
- to_dict()[source]
Return the record as a dictionary, with
growing_oscillationadded.- Returns:
dict[str, object] – Every field (as
dataclasses.asdict()), withconfirmationfromSettleConfirmation.to_dict(), plusgrowing_oscillation.- Return type:
- SettleCriterion
How a settle run is judged settled; see
solve_steady_state()."auto"starts with the velocity criterion and switches to the mean-drift criterion when a sustained, non-decaying oscillation is detected; a convergence record always holds the criterion that actually judged the run ("velocity"or"mean_drift").alias of
Literal[‘velocity’, ‘mean_drift’, ‘auto’]
- class SettleCurveCheck(rotor_speed, pitch, tolerances)[source]
Bases:
objectA settled operating point compared with the user’s operating curve.
When a settle run is seeded from a rotor-performance curve, the controller – not the curve – decides where the turbine settles, and that settled point is what is linearized. Comparing the two surfaces a disagreement between the controller and the curve (a mistuned controller, a curve for a different model or a different air density, a settle that has not reached its steady state). A deviation outside tolerance is reported, never treated as a failure.
- Parameters:
rotor_speed (QuantityCheck) – Settled rotor speed (rpm;
trimmedholds the settled value) against the curve, with a relative tolerance.pitch (QuantityCheck) – Settled collective blade pitch (deg) against the curve, with an absolute tolerance.
tolerances (TrimTolerances) – The tolerances applied (only
rotor_speed_relandpitch_degare used).
- rotor_speed: QuantityCheck
- pitch: QuantityCheck
- tolerances: TrimTolerances
- property status: CheckStatus
Return
"flag"if either quantity is outside tolerance.- Returns:
{“pass”, “flag”, “unavailable”} –
"flag"when the rotor speed or pitch deviates beyond tolerance,"unavailable"when either could not be compared, else"pass".
- class SettlePlatformDamping(surge=0.0, sway=0.0, heave=0.0, roll=0.0, pitch=0.0, yaw=0.0)[source]
Bases:
objectTemporary linear platform damping that speeds up a floating settle run.
The damping is added to the diagonal of HydroDyn’s
AddBLinmatrix (first body) in the settle copy only, for the first stage of a two-stage settle (seesolve_steady_state()); it is removed again before the undamped confirmation run and never reaches a linearization case.Why the settled mean is unchanged: the added load is
F = -B q_dot. At a static equilibrium the platform velocityq_dotis zero, so the force is zero and the equilibrium equations – rotor thrust against mooring, hydrostatic and gravity restoring loads – are exactly those of the undamped model: the fixed point does not move. Over a stationary periodic response the mean ofB q_dotisB (q(t+T) - q(t)) / T = 0as well. Quadratic drag (AddBQuad, Morison drag) also vanishes at zero velocity, and the settle runs in still water, so there is no wave mean-drift force to change. Only the transient – the slow, lightly damped surge/sway/yaw ringing – decays faster. Caveats the undamped confirmation stage exists for: a model with more than one equilibrium (e.g. a controller region boundary) may be steered to a different one, and an oscillation the damping suppresses may be self-excited, in which case the undamped model has no stable equilibrium and its oscillation reappears.- Parameters:
surge (float, optional) – Added linear damping for the translational DOFs, N/(m/s). Default
0.sway (float, optional) – Added linear damping for the translational DOFs, N/(m/s). Default
0.heave (float, optional) – Added linear damping for the translational DOFs, N/(m/s). Default
0.roll (float, optional) – Added linear damping for the rotational DOFs, N m/(rad/s). Default
0.pitch (float, optional) – Added linear damping for the rotational DOFs, N m/(rad/s). Default
0.yaw (float, optional) – Added linear damping for the rotational DOFs, N m/(rad/s). Default
0.
- Raises:
ValueError – If any value is negative or non-finite, or if all are zero.
See also
SettlePlatformDamping.from_critical_fractionsize it as a fraction of critical.
- classmethod from_critical_fraction(fraction, *, mass, natural_period)[source]
Size the damping as a fraction of critical damping per DOF.
For a single DOF with effective mass (or inertia)
mand stiffnessk, critical damping is2 sqrt(k m) = 2 m omega_n = 4 pi m / T_n, so the damping for a fractionzetais4 pi zeta m / T_n. The natural period stands in for the stiffness because it is what is usually known (published for reference platforms, or read off an earlier settle as itsSettleConvergence.oscillation_period); the mooring stiffness alone is not. Precision is not needed: any positive damping leaves the equilibrium unchanged, and a fraction of about 0.3–0.7 decays the transient in one to two cycles while staying clear of the slow creep of an overdamped DOF.- Parameters:
fraction (float or Mapping[str, float]) – Fraction of critical damping, the same for every DOF given in
massor one per DOF.mass (Mapping[str, float]) – Effective mass (kg) for
surge/sway/heaveor inertia (kg m^2) forroll/pitch/yaw, including the hydrodynamic added mass, for each DOF to damp. DOFs absent from it are not damped.natural_period (Mapping[str, float]) – Natural period (s) of each DOF in
mass.
- Returns:
SettlePlatformDamping – The absolute damping per DOF.
- Raises:
ValueError – If a DOF name is unknown, a DOF in
masshas no natural period (or fraction), or a mass, period or fraction is not positive and finite.- Return type:
Examples
IEA 15-MW on the VolturnUS-S semi-submersible: surge mass about
3.3e7kg (2.0e7 kg structure plus 1.3e7 kg added mass) with a surge period near 100 s under thrust; at half of critical damping>>> damping = SettlePlatformDamping.from_critical_fraction( ... 0.5, mass={"surge": 3.3e7}, natural_period={"surge": 100.0} ... ) >>> round(damping.surge / 1e6, 2) 2.07
- SettleStage
Which run of a (two-stage) settle an attempt belongs to.
alias of
Literal[‘settle’, ‘damped’, ‘confirm’]
- exception SteadyStateError(message, *, convergence=None)[source]
Bases:
RuntimeErrorRaised when a settle run fails, optionally carrying convergence evidence.
- Parameters:
message (str)
convergence (SettleConvergence | None)
- Return type:
None
- check_controller_library(fst_path, controller_library=None)[source]
Check that a settle run can load the deck’s Bladed-style controller library.
A settle run keeps the deck’s controller active, so when ServoDyn routes any control (
PCMode,VSContrl,HSSBrMode,YCMode,AfCmodeorCCmode= 5) through the controller library, OpenFAST loadsDLL_FileName(relative to the ServoDyn file) at initialization. Published decks often carry a path valid only on the machine that generated them (e.g. a Linuxlibdiscon.soon Windows); this check turns that into a clear error before OpenFAST is launched. Nothing is written.- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck.controller_library (str or pathlib.Path or None, optional) – A replacement controller library for the settle run. When given it must be an existing file; the deck’s own
DLL_FileNameis then not checked.
- Returns:
pathlib.Path or None – The resolved
controller_libraryto write into the settle copy’sDLL_FileName, orNonewhen nothing is to be rewritten (no override given, or ServoDyn does not run the controller library).- Raises:
FileNotFoundError – If
controller_libraryis given but does not exist.SteadyStateError – If no override is given, ServoDyn runs the controller library, and its
DLL_FileNamedoes not name an existing file; or if the library to be used (the override, or the deck’s own) exists but cannot be loaded on this host (wrong architecture, corrupt, or missing a dependency), checked by loading it in a child process.
- Return type:
Path | None
- compare_settled_to_curve(settled, curve_point, tolerances=None)[source]
Compare a settled operating point with the curve value at its wind speed.
- Parameters:
settled (OperatingPoint) – The operating point a settle run returned (the controller’s steady state).
curve_point (OperatingPoint) – The operating curve evaluated at the same wind speed (e.g. the seed the settle run started from).
tolerances (TrimTolerances or None, optional) – Rotor-speed (relative) and pitch (absolute) tolerances;
Noneuses theTrimTolerancesdefaults (1 % and 0.5 deg).
- Returns:
SettleCurveCheck – The rotor-speed and pitch comparisons and their combined status.
- Raises:
ValueError – If the two points are at different wind speeds.
- Return type:
- solve_steady_state(fst_path, wind_speed, *, settle_time=1200.0, discard_time=600.0, rotor_speed_rpm=0.0, blade_pitch_deg=0.0, platform_position=None, executable='openfast', timeout=None, name='', velocity_tol=0.01, angular_velocity_tol=0.01, adaptive=False, max_settle_time=None, settle_growth=1.5, require_settled=False, wind_shear_exponent=0.0, controller_library=None, criterion='auto', platform_damping=None, confirm_time=None, equilibrium_tolerances=None, prescribed_rotor=False, platform_period=None)[source]
Run the fully-coupled model to steady state and read the operating point.
Rather than trust a fixed
settle_time, the run is checked for genuine equilibrium: the platform-velocity channels’ RMS over the steady window must fall within tolerance (at equilibrium the platform is no longer moving). Withadaptive,TMaxis grown and the run repeated until that criterion is met ormax_settle_timeis reached. The channels this needs (the platform velocities, plusRotSpeedandBldPitch1) are appended to ElastoDyn’sOutListin the working copy when the deck does not already output them.Every run is judged over a trailing window of fixed length
settle_time - discard_timeending at that run’sTMax, i.e.[TMax - (settle_time - discard_time), TMax](the first run’s window is[discard_time, settle_time]). A longer adaptive run therefore discards the early transient rather than diluting it, and the returned rotor speed, blade pitch and platform position are the means over that same trailing window.TStartis set to the window start, so only the window is written to the.outfile.- Parameters:
fst_path (str or pathlib.Path) – Primary
.fstdeck (a writable working copy; edited in place).wind_speed (float) – Steady hub-height wind speed (m/s) to settle at.
settle_time (float, optional) – Initial
TMaxfor the settle run (s); should be long enough for the slow platform modes to settle pastdiscard_time.discard_time (float, optional) – Initial transient to discard (s) in the first run. Together with
settle_timeit fixes the analysis-window lengthsettle_time - discard_timeused by every (adaptive) run; output is written from the window start (TStart) and the window is averaged.rotor_speed_rpm (float, optional) – Initial rotor speed (rpm) and collective blade pitch (deg) for the run; the controller drives them to their steady values, so these only need to be reasonable starting guesses.
blade_pitch_deg (float, optional) – Initial rotor speed (rpm) and collective blade pitch (deg) for the run; the controller drives them to their steady values, so these only need to be reasonable starting guesses.
platform_position (PlatformPosition or None, optional) – Initial six-DOF floating-platform position written to ElastoDyn. Fields left
Noneretain the deck’s values. This changes only initial conditions; the active controller and physical modules remain untouched.executable (str or pathlib.Path, optional) – OpenFAST executable name or path.
timeout (float or None, optional) – Run timeout in seconds (per settle run).
name (str, optional) – Label for the returned operating point.
velocity_tol (float, optional) – Tolerance on the RMS platform translational velocity (m/s) for declaring the platform settled. Default
1e-2.angular_velocity_tol (float, optional) – Tolerance on the RMS platform angular velocity (deg/s). Default
1e-2.adaptive (bool, optional) – When
True, growTMaxbysettle_growthand re-run until the platform settles within tolerance ormax_settle_timeis reached. Requiresmax_settle_time. DefaultFalse(a single run atsettle_time).max_settle_time (float or None, optional) – The largest
TMax(s) the adaptive search may use; required whenadaptiveisTrue.settle_growth (float, optional) – Factor by which
TMaxgrows between adaptive iterations (> 1). Default1.5.require_settled (bool, optional) – When
True, raiseSteadyStateErrorif the platform is not confirmed settled (either the velocity RMS exceeds tolerance, or no platform-velocity channel was output to judge it). DefaultFalse(warn only).wind_shear_exponent (float or None, optional) – InflowWind power-law shear exponent (
PLExp) for the settle run. The default0.0is steady uniform inflow, the same defaultLinearizationSettingswrites for the linearization, so the settled point is linearized in the inflow it settled in.Noneleaves the deck’sPLExpunchanged. Keep it equal to the linearization’swind_shear_exponent;run_operating_sweep()does.controller_library (str or pathlib.Path or None, optional) – Bladed-style controller library (e.g. a
libdiscon.dllbuilt for this host) to use for the settle run. When set, ServoDyn’sDLL_FileNamein the deck being settled (a working copy) is rewritten to its absolute path.None(the default) keeps the deck’s ownDLL_FileName, which must then exist. Seecheck_controller_library().criterion ({"auto", "velocity", "mean_drift"}, optional) – How a run is judged settled.
"auto"(the default) applies the velocity criterion and switches to the mean-drift criterion only when the velocity criterion fails and the platform carries a sustained oscillation: one whose cycle-by-cycle RMS neither decays nor grows (its damping ratio, from the decay of the most active platform-velocity channel over the analysis window, lies withinequilibrium_tolerances.residual_damping_ratioof zero). A still-decaying transient keeps the velocity criterion (an adaptive settle then runs longer), and a growing oscillation never switches it (a two-stage confirmation is judged as described underplatform_damping). The criterion that judged the run is recorded inSettleConvergence.criterion(and per attempt), and a switch inSettleConvergence.criterion_switched."velocity": every platform-velocity RMS over the analysis window is withinvelocity_tol/angular_velocity_tol– the platform has stopped moving."mean_drift": the means of the platform position, rotor speed and blade pitch over the first and second halves of the analysis window agree withinequilibrium_tolerances– the mean no longer moves, even if a stationary oscillation about it persists. Use it for a point that carries a sustained (e.g. controller-platform) oscillation the velocity criterion can never accept; the operating point is then the mean about which it oscillates, and the oscillation is still reported (its velocity RMS andSettleConvergence.oscillation_period). The criterion used is recorded inSettleConvergence.criterion. Each half, and the returned mean, is averaged over its trailing whole number of oscillation periods so the phase of a partial cycle does not bias it; make each half of the window span at least a couple of periods.platform_damping (SettlePlatformDamping or None, optional) – Opt-in settle accelerator for floating platforms. When given, the settle runs in two stages. Stage 1 adds this linear damping to HydroDyn’s
AddBLinin the working copy and settles (adaptively, as configured) until the criterion is met; the slow, lightly damped platform modes then decay in a cycle or two instead of ringing for many minutes. Stage 2 restores the deck’s ownAddBLinbyte for byte, restarts from the stage-1 means (rotor speed, blade pitch, platform position, and the tower-top and, when output, blade-tip deflections), and runs an undamped confirmation ofconfirm_timeseconds, judged over its last three quarters. Every mean of the confirmation – and of any settle run – is averaged over the trailing whole number of the run’s dominant oscillation period (aligned to the window end; the plain window mean when no period is detected or less than one fits), so a partial cycle does not bias it. The point is accepted when the undamped run’s means reproduce the stage-1 means withinequilibrium_tolerances(the shift test) and the undamped run is stable about them: it meets the criterion by itself or, for the velocity criterion, its residual oscillation decays with at leastequilibrium_tolerances.residual_damping_ratio. Restarting even at the equilibrium rings the slow modes (the controller’s start-up transient does), and a lightly damped mode cannot ring down within a short run, so a decaying residual is accepted. A residual that decays but whose means have not yet returned (the shift test fails while the restart transient still biases them) gets one more confirmation run, twice as long (capped atmax_settle_timewhen given), and that run is judged instead (SettleConfirmation.extended). Withcriterion="auto"a confirmation that keeps a non-decaying oscillation – sustained or growing – while passing the shift test is accepted at the stage-1 equilibrium when the oscillation is measurable: its residual damping ratio is known and its velocity RMS is withinequilibrium_tolerances.oscillation_velocity_rms. A load linear in the platform velocity is zero at an equilibrium, so the damped stage’s equilibrium is the undamped model’s, stable or not, and the shift test has just reproduced it.SettleConfirmation.sustained_oscillationis set and a warning logged. When the residual is below-equilibrium_tolerances.growing_damping_ratiothe point is also flaggedSettleConfirmation.growing_oscillationwith a distinct warning: the equilibrium is unstable under the deck’s controller, but the linearization trims without the controller, so it is linearized and the controller-in-the-loop instability is not part of the linear model. A failed shift test, a too large oscillation, too few cycles to measure it, or a non-decaying oscillation under the"velocity"or"mean_drift"criterion is reported as not converged with its reason (and raises underrequire_settled), never silently accepted. A single-stage settle never accepts a growing oscillation. A confirmed point is the stage-1 equilibrium; an unconfirmed one reports the undamped run’s means. Both stages are recorded inSettleConvergence.confirmationandattempts. SeeSettlePlatformDampingfor why a load proportional to velocity cannot move the equilibrium. Requires an active HydroDyn with anAddBLinmatrix.confirm_time (float or None, optional) –
TMax(s) of the undamped confirmation run of a two-stage settle; its analysis window is the last three quarters.None(the default) sizes it automatically so the analysis window holds eight periods of the slowest platform mode:8 T / 0.75rounded up to a whole second, withTtheplatform_periodwhen given, else the damped stage’s dominant period. The length is never shorter than 1200 s (also used when no period is known) and never longer thanmax_settle_timewhen that is given, else 3600 s. The length and how it was chosen are recorded inSettleConfirmation.confirm_timeandSettleConfirmation.confirm_time_source. An explicit value is used as given.platform_period (float or None, optional) – The slowest platform natural period (s), e.g. the longest period of the standstill platform probe (
slowest_period;run_operating_sweep()passes it), used to size the automaticconfirm_time.None(the default) uses the damped stage’s dominant period instead. Ignored withoutplatform_dampingor with an explicitconfirm_time.equilibrium_tolerances (EquilibriumTolerances or None, optional) – Tolerances on a change of the settled means: between the damped and the undamped stage of a two-stage settle, and between window halves for
criterion="mean_drift".Noneuses theEquilibriumTolerancesdefaults.prescribed_rotor (bool, optional) – Settle without a controller. ServoDyn is switched off in the working copy (
CompServo = 0, so no controller library is loaded and none is checked), and ElastoDyn’s generator and drivetrain DOFs are locked (GenDOF = DrTrDOF = False), so the rotor turns at exactlyrotor_speed_rpmwith the blades held atblade_pitch_degwhile the platform finds its equilibrium under the resulting aerodynamic load. This is the fallback for a floating deck whose controller library is not available on this host: with a rotor-performance curve supplying the rotor speed and pitch, it yields the thrust-loaded platform position a CalcSteady linearization at that curve point starts from. The returned rotor speed and pitch are the prescribed ones; the record carriesSettleConvergence.prescribed_rotor.controller_librarymust not be given with it. DefaultFalse.
- Returns:
OperatingPoint – The settled operating point: the averaged rotor speed and blade pitch, and a
PlatformPositionwhen the deck outputs the platform channels (otherwiseplatformisNone).- Raises:
SteadyStateError – If the settle run fails, its output file cannot be read, or
require_settledis set and equilibrium could not be confirmed; or, before OpenFAST launches, if ServoDyn runs the controller library, nocontroller_libraryis given, and the deck’sDLL_FileNamedoes not exist.FileNotFoundError – If
controller_libraryis given but is not an existing file.ValueError – If
adaptiveis set withoutmax_settle_timeorsettle_growth <= 1; if any deck-written numeric input (wind_speed,rotor_speed_rpm,blade_pitch_deg,settle_time,discard_time, andmax_settle_timewhen adaptive, andwind_shear_exponentwhen set) is non-finite; ifwind_speedorrotor_speed_rpmis negative; ifcriterionis unknown; ifprescribed_rotoris combined withcontroller_library; ifplatform_periodis given but not positive and finite; or, withplatform_damping, ifconfirm_timeis given but not positive and finite or the deck has no active HydroDyn file with anAddBLinmatrix (checked before OpenFAST launches).
- Return type:
- validate_settle_schedule(settle_time, discard_time, *, adaptive=False, max_settle_time=None, settle_growth=1.5, criterion='auto', confirm_time=None)[source]
Check a settle’s schedule and criterion before anything is launched.
solve_steady_state()runs these checks itself; a sweep calls them once up front so a bad schedule fails before its platform probe launches OpenFAST.- Parameters:
settle_time (float) – The settle run length and the discarded start-up (s).
discard_time (float) – The settle run length and the discarded start-up (s).
adaptive (bool, optional) – Whether the run is grown until it settles.
max_settle_time (float or None, optional) – The adaptive ceiling (s); required when
adaptive.settle_growth (float, optional) – The adaptive growth factor.
criterion (str, optional) –
"auto","velocity"or"mean_drift".confirm_time (float or None, optional) – The confirmation length (s) of a two-stage settle;
Noneis automatic.
- Raises:
ValueError – If any value is out of range.
- Return type:
None
Set up a floating-turbine settle automatically: controller library and damping.
A floating operating point is found by settling the fully coupled model and then
linearizing at the settled mean (see solve_steady_state()). Two
inputs of that settle used to come from the user; this module derives both from the
deck itself.
The controller library. Published decks name the controller library of the
machine that generated them (often a Linux libdiscon.so).
find_controller_library() looks for a library this host can load: the deck’s
own DLL_FileName; the same library with another platform or bitness suffix
(.dll, _x64.dll, .so, …) next to it, next to the ServoDyn file and next
to the primary deck; then in the directories or files listed by the caller, by the
VANE_CONTROLLER_LIBRARIES environment variable (os.pathsep-separated) and by
the configuration file controller_libraries.txt (one entry per line, #
comments) in VANE’s configuration directory (VANE_CONFIG_DIR, else
%APPDATA%\vane on Windows and $XDG_CONFIG_HOME/vane or ~/.config/vane
elsewhere). Candidates are matched by the library’s name with the lib prefix,
the bitness tag and the suffix ignored, so libdiscon.so matches libdiscon.dll,
discon_x64.dll or DISCON.dll. When none is found the settle is not
abandoned: plan_floating_settle() falls back to a prescribed-rotor settle
(no controller; the rotor turns at the operating curve’s speed with the blades at
its pitch while the platform finds its equilibrium), and the point is then
linearized by CalcSteady from that equilibrium.
The settle damping. The two-stage settle accelerator
(SettlePlatformDamping) needs, per platform DOF, the effective
mass and the natural period. probe_platform() measures both from the model:
it runs two single-snapshot standstill linearizations of the platform (still air and
still water, rotor parked, aerodynamics and controller off, radiation memory off so
no state-space hydrodynamic fit is needed), the second with a known damping added to
HydroDyn’s AddBLin. The difference of the two state matrices is
-M^-1 Delta_B – the platform columns of the exact inverse mass matrix, including
the hydrodynamic added mass – and first-order eigenvalue perturbation of the
undamped state matrix gives, for every mode, how fast a damper on each platform DOF
damps it. For each DOF the mode it damps the most is its platform mode; that mode’s
natural period, and the mass m = 1 / (2 dsigma/dB) for which a single-DOF
oscillator would be damped at the same rate, size the damping as a fraction of
critical (PlatformProbe.settle_damping()). The probe costs two OpenFAST runs of
a fraction of a second of simulated time and needs no user data.
- CONTROLLER_LIBRARIES_ENV = 'VANE_CONTROLLER_LIBRARIES'
Environment variable listing controller libraries or directories to search.
- class ControllerLibraryResolution(status, library=None, deck_library='', source='', searched=())[source]
Bases:
objectWhere a settle run’s controller library comes from.
- Parameters:
status ({"not_used", "deck", "found", "user", "missing"}) –
"not_used": ServoDyn runs no controller library."deck": the deck’s ownDLL_FileNameis loadable here."found": a host build was found (library)."user": the caller supplied it."missing": none could be found; a controller settle is impossible on this host.library (pathlib.Path or None) – The library to write into the settle copy’s
DLL_FileName;Noneunlessstatusis"found"or"user".deck_library (str) – The deck’s
DLL_FileNameas written (empty when not used).source (str) – Where
librarywas found (e.g."next to the deck's library","VANE_CONTROLLER_LIBRARIES"); empty when not searched.searched (tuple of pathlib.Path) – The directories and files that were searched, in order.
- ControllerLibraryStatus
How
find_controller_library()resolved the settle’s controller library.alias of
Literal[‘not_used’, ‘deck’, ‘found’, ‘user’, ‘missing’]
- class FloatingSettlePlan(controller, floating, platform_damping=None, damping_source='none', probe=None, damping_fraction=0.5, notes=<factory>)[source]
Bases:
objectHow a sweep’s settle runs are set up, decided from the deck and this host.
- Parameters:
controller (ControllerLibraryResolution) – How the controller library was resolved.
floating (bool) – Whether the deck models a floating platform (
is_floating_deck()).platform_damping (SettlePlatformDamping or None) – The two-stage settle damping to use (
None: a single-stage settle).damping_source ({"user", "probe", "none"}) – Whether the damping was given, sized from the platform probe, or is off.
probe (PlatformProbe or None) – The platform probe, when one was run.
damping_fraction (float) – Fraction of critical damping the probe-sized damping uses.
notes (tuple of str) – Human-readable notes on every automatic decision (also logged).
- controller: ControllerLibraryResolution
- platform_damping: SettlePlatformDamping | None = None
- probe: PlatformProbe | None = None
- property controller_library: Path | None
keep).
- Type:
Return the library to write into the settle copies (
None
- class PlatformProbe(effective_mass, natural_period, damping_ratio, mode_frequency, probe_damping, n_states)[source]
Bases:
objectPlatform dynamics measured by two standstill linearizations.
- Parameters:
effective_mass (dict[str, float]) – Per platform DOF, the mass (kg; kg m^2 for rotations) of a single-DOF oscillator that a damper on that DOF would damp at the same rate as it damps the DOF’s platform mode:
1 / (2 dsigma/dB). It includes the hydrodynamic added mass and the coupling to the other DOFs.natural_period (dict[str, float]) – Per DOF, the undamped natural period (s) of its platform mode,
2 pi / |lambda|.damping_ratio (dict[str, float]) – Per DOF, the damping ratio the platform mode already has at standstill.
mode_frequency (dict[str, float]) – Per DOF, the damped frequency (Hz) of its platform mode.
probe_damping (SettlePlatformDamping) – The damping added in the second linearization.
n_states (int) – Number of continuous states of the standstill linearization.
- probe_damping: SettlePlatformDamping
- property slowest_period: float | None
Return the longest finite natural period (s) of the measured modes.
It sizes the undamped confirmation of a two-stage settle (
platform_periodofsolve_steady_state()): the confirmation must span several periods of the slowest platform mode. Standstill periods are the right scale; under thrust the mooring stiffens and the periods shorten slightly (about 125 s at standstill against about 106 s at rated for the IEA 15-MW semi-submersible’s surge), so sizing from them errs long.- Returns:
float or None – The largest positive, finite period;
Nonewhen there is none.
- settle_damping(fraction=0.5)[source]
Return the settle damping as a fraction of critical per measured DOF.
- Parameters:
fraction (float, optional) – Fraction of critical damping to add to every measured DOF (on top of the damping the mode already has). Default
0.5.- Returns:
SettlePlatformDamping –
4 pi fraction m / Tper DOF (seeSettlePlatformDamping.from_critical_fraction()).- Return type:
- exception PlatformProbeError[source]
Bases:
RuntimeErrorRaised when the standstill platform probe cannot measure the platform.
- controller_library_search_paths(environ=None)[source]
Return the configured controller-library locations and where each is from.
- Parameters:
environ (Mapping[str, str] or None, optional) – The environment to read;
Noneusesos.environ.- Returns:
tuple of (pathlib.Path, str) – Each configured file or directory with its origin: the
VANE_CONTROLLER_LIBRARIESentries (os.pathsep-separated) first, then the lines ofcontroller_libraries.txtin VANE’s configuration directory (blank lines and#comments ignored; relative entries are relative to that file).- Return type:
- find_controller_library(fst_path, *, search_paths=(), environ=None)[source]
Find a controller library this host can load for a settle run.
Nothing is written; the result says what the settle should use.
- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck.search_paths (Sequence of str or pathlib.Path, optional) – Extra library files or directories to search, after the deck’s own locations and before the configured ones.
environ (Mapping[str, str] or None, optional) – The environment holding
VANE_CONTROLLER_LIBRARIES/VANE_CONFIG_DIR;Noneusesos.environ.
- Returns:
ControllerLibraryResolution –
"not_used"when ServoDyn runs no controller library,"deck"when the deck’s own library loads here,"found"with the host build to use, or"missing".- Return type:
Notes
The search order is: the deck’s
DLL_FileName; then, for the same library name (thelibprefix, a bitness tag and the suffix ignored, case folded), the directory of the deck’s library, the ServoDyn file’s directory and the primary deck’s directory; thensearch_paths; thenVANE_CONTROLLER_LIBRARIESand the configuration file (seecontroller_library_search_paths()). Only libraries with this host’s suffix (.dllon Windows,.soon Linux,.dylibon macOS) are accepted; within a directory a plain or 64-bit build is preferred to a 32-bit one. A found library must still be compatible with the deck’s controller input file (DLL_InFile); VANE cannot check that.
- is_floating_deck(fst_path)[source]
Return whether a deck models a floating platform.
A deck is floating when ElastoDyn frees at least one platform DOF and the platform is moored, or is carried by HydroDyn without a substructure module. A substructure (
CompSub != 0) uses the platform DOFs too: without mooring it is a fixed-bottom monopile or jacket, with mooring a flexible floater modelled in SubDyn.- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck.- Returns:
bool – Whether the platform floats.
- Return type:
- plan_floating_settle(fst_path, work_dir, *, controller_library=None, platform_damping=None, auto_damping=True, damping_fraction=0.5, controller_search_paths=(), template_root=None, template_copy='referenced', executable='openfast', timeout=None)[source]
Decide, without user input, how a deck’s settle runs are set up.
Controller library. A given
controller_libraryis checked and used. Otherwisefind_controller_library()looks for a host build; when none exists the plan’sprescribed_rotorisTrue: the settle runs without a controller at the operating curve’s rotor speed and pitch (seeprescribed_rotorofsolve_steady_state()), and the point is then linearized by CalcSteady from the platform equilibrium it finds. That needs an operating curve; the caller enforces it.Settle damping. A given
platform_dampingis used as is. Otherwise, for a floating deck withauto_damping,probe_platform()measures the platform and the damping isdamping_fractionof critical on every measured DOF. A probe that fails is logged and the settle runs undamped.
- Parameters:
fst_path (str or pathlib.Path) – The template primary
.fstdeck.work_dir (str or pathlib.Path) – Directory for the platform probe cases.
controller_library (str or pathlib.Path or None, optional) – A host controller library to use instead of searching.
platform_damping (SettlePlatformDamping or None, optional) – A settle damping to use instead of probing.
auto_damping (bool, optional) – Size the damping from the platform probe for a floating deck. Default
True.damping_fraction (float, optional) – Fraction of critical damping for the probe-sized damping (
0 < f). Default0.5.controller_search_paths (Sequence of str or pathlib.Path, optional) – Extra library locations (see
find_controller_library()).template_root (str or pathlib.Path or None, optional) – The template copy root (see
probe_platform()).template_copy ({"referenced", "tree"}, optional) – How the template is copied for the probe.
executable (str or pathlib.Path, optional) – OpenFAST executable for the probe.
timeout (float or None, optional) – Per-run timeout (s) of the probe.
- Returns:
FloatingSettlePlan – The resolved controller library, damping and the evidence behind them.
- Raises:
FileNotFoundError – If
controller_libraryis given but does not exist.ValueError – If
damping_fractionis not positive and finite.
- Return type:
- platform_probe_from_jacobians(a_base, a_damped, descriptions, probe_damping=SettlePlatformDamping(surge=1000000.0, sway=1000000.0, heave=1000000.0, roll=1000000000.0, pitch=1000000000.0, yaw=1000000000.0))[source]
Measure the platform from two state matrices differing by a platform damping.
- Parameters:
a_base (npt.NDArray[np.float64]) – Full-system state matrix
Aof the standstill linearization.a_damped (npt.NDArray[np.float64]) – The same with
probe_dampingadded to HydroDyn’sAddBLin.descriptions (Sequence[str]) – The continuous-state descriptions (
.lin“Order of continuous states”).probe_damping (SettlePlatformDamping, optional) – The damping that was added.
- Returns:
PlatformProbe – The per-DOF effective mass, natural period and damping ratio of each platform DOF that is free in the model and has an oscillatory mode.
- Raises:
PlatformProbeError – If the matrices disagree in shape with the descriptions, no ElastoDyn platform DOF is free, or the probe damping did not change
A.- Return type:
Notes
Adding a damping
b_jon platform DOFjchanges only the columns of the platform velocities:Delta A[:, v_j] = -M^{-1}[:, v_j] b_j. For each modekwith right and left eigenvectorsu_kandw_kofa_base, the first-order sensitivity of its eigenvalue tob_jis\[\frac{\partial \lambda_k}{\partial b_j} = -\frac{(w_k^H M^{-1}_{:,v_j}) \, u_{k, v_j}}{w_k^H u_k}\]The platform mode of DOF
jis the oscillatory mode whose decay rate-Re lambdathe damper raises the most; a single-DOF oscillator of massmhasd(-Re lambda)/db = 1/(2m), which defines the effective mass.
- probe_platform(fst_path, work_dir, *, template_root=None, template_copy='referenced', executable='openfast', timeout=None, probe_damping=SettlePlatformDamping(surge=1000000.0, sway=1000000.0, heave=1000000.0, roll=1000000000.0, pitch=1000000000.0, yaw=1000000000.0))[source]
Measure a floating platform’s effective masses and natural periods.
Two single-snapshot standstill linearizations of a copy of the deck are run in
work_dir(see the module notes): still air and water, rotor parked, AeroDyn and ServoDyn off, HydroDyn radiation memory and wave excitation off; the second withprobe_dampingadded to HydroDyn’sAddBLin. The template deck is never edited.- Parameters:
fst_path (str or pathlib.Path) – The template primary
.fstdeck.work_dir (str or pathlib.Path) – Directory for the two probe cases (
base/damped; existing ones are replaced).template_root (str or pathlib.Path or None, optional) – Directory the deck’s files are copied from (default: the deck’s directory).
template_copy ({"referenced", "tree"}, optional) – How the template is copied (see
copy_template_files()).executable (str or pathlib.Path, optional) – OpenFAST executable.
timeout (float or None, optional) – Per-run timeout (s).
probe_damping (SettlePlatformDamping, optional) – The known damping added in the second run.
- Returns:
PlatformProbe – The measured platform dynamics.
- Raises:
PlatformProbeError – If a probe run fails, writes no
.linfile, or cannot measure the platform.ValueError – If the deck has no active HydroDyn with an
AddBLinmatrix.
- Return type:
State-space radiation fits generated from WAMIT first-order radiation data.
A linearization of a floating platform needs HydroDyn’s state-space radiation model
(RdtnMod = 2): the convolution model is not linearizable. That model reads a
<PotFile>.ss file, which some published decks do not ship (e.g. the IEA 22-MW
semi-submersible ships only the WAMIT .1/.3/.hst data). Without it the
radiation memory must be switched off, leaving only the infinite-frequency added mass
and no wave-radiation damping. This module generates the missing file from the
WAMIT .1 data itself.
The fit. The radiation impulse-response function K_ij(t) relates the
radiation force on DOF i to the velocity of DOF j; its transfer function is
with A and B the frequency-dependent added mass and radiation damping. Each
significant entry (every diagonal entry, and an off-diagonal one when its peak
exceeds coupling_threshold of the geometric mean of the two diagonal peaks) is
fitted in the frequency domain by a strictly proper rational function with stable
poles, by vector fitting [Gustavsen1999]: the poles are relocated iteratively
(unstable ones reflected into the left half-plane), and the residues of the final fit
satisfy K_ij(0) = 0 exactly (the radiation force has no static part) by a
constrained least-squares solve. The order grows until the relative error over the
WAMIT frequencies, ||K_fit - K|| / ||K||, falls below tolerance. Each entry is
realised in real modal form, the blocks grouped by input DOF as HydroDyn expects, and
the output sign is that of the radiation force (F = -K * v).
Validation. The IEA 15-MW VolturnUS-S ships both WAMIT data and an .ss file.
The generated fit matches the WAMIT data within 3.5–5 % per entry (the shipped fit
within 10–21 %), its impulse responses deviate from the WAMIT cosine transform of
the damping by 2–9 % of the peak (the shipped fit by 9–25 %), and a standstill
linearization with it reproduces the platform frequencies of one with the shipped
file within 1 % and their damping ratios within 0.003.
Wave-excitation fits (.ssexctn) are not generated. A linearization runs in
still water, where the excitation states are driven by nothing and leave the
platform dynamics unchanged, so ExctnMod = 0 loses only the wave-elevation input
channel of the linear model.
References
Gustavsen, B., & Semlyen, A. (1999). Rational approximation of frequency domain responses by vector fitting. IEEE Transactions on Power Delivery, 14(3), 1052-1061.
- exception RadiationFitError[source]
Bases:
ValueErrorRaised when radiation data cannot be read or fitted.
- class RadiationFitRecord(path, source, n_states, max_relative_error, entry_errors, written)[source]
Bases:
objectProvenance of a generated
.ssradiation file.- Parameters:
path (pathlib.Path) – The
.ssfile (written only whenwritten).source (pathlib.Path) – The WAMIT
.1file it was fitted to.n_states (int) – Radiation states of the model.
max_relative_error (float) – Largest per-entry relative fit error.
entry_errors (dict[str, float]) – Relative fit error per fitted entry (
"K11","K15", …).written (bool) – Whether the file was written (the fit met the acceptance limit).
- class RadiationPairFit(row, column, poles, coefficients, n_states, relative_error)[source]
Bases:
objectThe rational fit of one radiation-kernel entry
K_ij.- Parameters:
row (int) – The 0-based force DOF
iand velocity DOFj.column (int) – The 0-based force DOF
iand velocity DOFj.poles (tuple of complex) – The stable poles (rad/s); one of each complex-conjugate pair.
coefficients (tuple of float) – Real partial-fraction coefficients: one per real pole, two (real and imaginary part of the residue) per complex pair.
n_states (int) – The order of the fit (states in its realisation).
relative_error (float) –
||K_fit - K|| / ||K||over the WAMIT frequencies.
- class RadiationStateSpace(a, b, c, states_per_dof, pairs=())[source]
Bases:
objectA HydroDyn state-space radiation model (the content of a
.ssfile).The model maps the six platform velocities to the six radiation forces:
x' = A x + B v,F = C x.- Parameters:
a (npt.NDArray[np.float64]) – State matrix,
(n, n).b (npt.NDArray[np.float64]) – Input matrix,
(n, 6).c (npt.NDArray[np.float64]) – Output matrix,
(6, n).states_per_dof (tuple of int) – States belonging to each input DOF (the blocks are grouped by input DOF).
pairs (tuple of RadiationPairFit, optional) – The per-entry fits (empty for a model read from a file).
- a: npt.NDArray[np.float64]
- b: npt.NDArray[np.float64]
- c: npt.NDArray[np.float64]
- pairs: tuple[RadiationPairFit, ...] = ()
- property max_relative_error: float
Return the largest per-entry relative fit error (
nanif unknown).
- force_response(frequencies)[source]
Return the velocity-to-radiation-force transfer
C (j omega I - A)^-1 B.For an exact model this equals
-K(j omega).- Parameters:
frequencies (npt.NDArray[np.float64]) – Angular frequencies (rad/s).
- Returns:
npt.NDArray[np.complex128] – Shape
(n_frequencies, 6, 6).- Return type:
npt.NDArray[np.complex128]
- class WamitRadiation(frequencies, added_mass, damping, added_mass_infinite)[source]
Bases:
objectDimensional frequency-dependent radiation coefficients of one body.
- Parameters:
frequencies (npt.NDArray[np.float64]) – Angular frequencies (rad/s), positive and ascending, shape
(n,).added_mass (npt.NDArray[np.float64]) – Added mass
A(omega), shape(n, 6, 6)(kg, kg m, kg m^2).damping (npt.NDArray[np.float64]) – Radiation damping
B(omega), shape(n, 6, 6).added_mass_infinite (npt.NDArray[np.float64]) – Infinite-frequency added mass
A(inf), shape(6, 6).
- frequencies: npt.NDArray[np.float64]
- added_mass: npt.NDArray[np.float64]
- damping: npt.NDArray[np.float64]
- added_mass_infinite: npt.NDArray[np.float64]
- ensure_radiation_state_space(fst_path, *, tolerance=0.05, overwrite=False, confine_to=None)[source]
Generate the
<PotFile>.ssradiation file a deck lacks, from its.1.Call it on a working copy before a linearization with
RdtnMod = 2. Nothing is done when HydroDyn is off, uses no WAMIT potential flow (PotMod != 1), or the.ssfile already exists (unlessoverwrite). The file is written only when every fitted entry is within0.2relative error; otherwise the record says so, nothing is written, and the caller should linearize with the radiation memory off (hydro_radiation_mode=0).- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck (a working copy; the file is written next to the WAMIT data it references).tolerance (float, optional) – Per-entry fit tolerance (see
fit_radiation_state_space()).overwrite (bool, optional) – Refit and replace an existing
.ssfile. DefaultFalse.confine_to (str or pathlib.Path or None, optional) – A directory the
.ssfile must lie in (a case copy): aPotFilethat resolves outside it, e.g. into a shared template, is rejected before anything is fitted or written.None(the default) does not check.
- Returns:
RadiationFitRecord or None – The fit’s provenance, or
Nonewhen nothing needed to be generated.- Raises:
RadiationFitError – If the deck uses more than one WAMIT body, the
.1file is missing or cannot be fitted, or the.ssfile would lie outsideconfine_to.- Return type:
RadiationFitRecord | None
- fit_radiation_state_space(radiation, *, tolerance=0.05, max_states_per_entry=20, coupling_threshold=0.05, iterations=20)[source]
Fit a state-space radiation model to WAMIT radiation coefficients.
See the module notes for the method.
- Parameters:
radiation (WamitRadiation) – The dimensional radiation coefficients.
tolerance (float, optional) – Target relative error of each fitted entry over the WAMIT frequencies; the order grows (two states at a time) until it is met. Default
0.05.max_states_per_entry (int, optional) – Largest order tried per entry. Default
20.coupling_threshold (float, optional) – An off-diagonal entry is fitted when its peak magnitude is at least this fraction of the geometric mean of the two diagonal peaks. Default
0.05.iterations (int, optional) – Pole-relocation iterations per order. Default
20.
- Returns:
RadiationStateSpace – The assembled model, its blocks grouped by input DOF, with the per-entry fits.
- Raises:
RadiationFitError – If an argument is out of range or an entry cannot be fitted.
- Return type:
- preview_radiation_state_space(fst_path, *, tolerance=0.05)[source]
Fit a deck’s WAMIT
.1radiation data without writing anything.The fit
ensure_radiation_state_space()would write, for deciding before any case is copied whether the.ssfile can be generated (RadiationFitRecord.accepted). An existing.ssfile is ignored.- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck (only read).tolerance (float, optional) – Per-entry fit tolerance (see
fit_radiation_state_space()).
- Returns:
RadiationFitRecord or None – The fit’s record (
writtenisFalse), orNonewhen HydroDyn is off or uses no WAMIT potential flow.- Raises:
- Return type:
RadiationFitRecord | None
- read_radiation_state_space(path)[source]
Read a HydroDyn
.ssradiation file.- Parameters:
path (str or pathlib.Path) – The
.ssfile.- Returns:
RadiationStateSpace – The model (with no per-entry fits).
- Raises:
RadiationFitError – If the file is malformed.
- Return type:
- read_wamit_radiation(path, *, water_density, length_scale=1.0)[source]
Read a WAMIT
.1file (single body) into dimensional coefficients.Each row is
PER I J Abar [Bbar].PER = 0is the infinite-frequency (zero-period) added mass andPER < 0the zero-frequency one; other rows are atomega = 2 pi / PER. The non-dimensional values are scaled byrho L^k(added mass) andrho omega L^k(damping),k = 3for two translations,4for mixed and5for two rotations.- Parameters:
path (str or pathlib.Path) – The
.1file.water_density (float) – Water density
rho(kg/m^3).length_scale (float, optional) – WAMIT’s characteristic length
L(HydroDynWAMITULEN). Default1.
- Returns:
WamitRadiation – The coefficients at the finite positive frequencies, and
A(inf).- Raises:
RadiationFitError – If the file has DOF indices beyond 6 (more than one body), no infinite-frequency rows, fewer than four finite frequencies, or a malformed row; or if
water_density/length_scaleis not positive and finite.- Return type:
- write_radiation_state_space(model, path, *, header='')[source]
Write a model in HydroDyn’s
.ssformat.The file holds a header line, the six enabled-DOF flags, the state count, the states per DOF, then the rows of
A,BandC. It is written through an atomic replace, so a hard-linked copy never writes through to a template.- Parameters:
model (RadiationStateSpace) – The model to write.
path (str or pathlib.Path) – Destination
.ssfile.header (str, optional) – Text of the first line.
- Returns:
pathlib.Path – The path written.
- Return type:
Run the OpenFAST executable on an input deck and collect its .lin files.
OpenFAST is invoked as openfast <primary>.fst from the deck’s directory (so the
relative sub-file references resolve), and on success writes one full-system
linearization file per linearization time, named <RootName>.<i>.lin for
i = 1 .. NLinTimes. This module wraps that invocation: it locates the
executable, runs it (capturing output and the return code), and discovers the
produced .lin files — which feed straight into vane.io.read_lin_file()
and the modal pipeline, closing the loop from model deck to identified modes.
- class OpenFastDiagnosis(mode, detail, openfast_message='', failure_time=None)[source]
Bases:
objectA classified OpenFAST failure.
- Parameters:
mode (OpenFastFailureMode) – The failure class.
detail (str) – A one-line description of the failure.
openfast_message (str) – The relevant excerpt of OpenFAST’s own output (its fatal error message, or the last lines it printed), empty when none applies.
failure_time (float or None) – The simulation time (s) at which OpenFAST stopped with a fatal error, parsed from
encountered an error at simulation time <t> of <TMax> seconds.0.0for a failure during initialisation;Nonewhen OpenFAST did not report it.
- mode: OpenFastFailureMode
- exception OpenFastError[source]
Bases:
RuntimeErrorRaised when the OpenFAST executable cannot be found or fails to run.
- class OpenFastFailureMode(*values)[source]
-
Why an OpenFAST linearization run failed, as read from its output.
The value of each member is the stable string recorded in provenance.
- TRIM_NOT_CONVERGED
The CalcSteady trim did not reach a periodic steady state before
TMax: OpenFAST logsSteady state not found before end of simulationand forces a single linearization, or fewer.linfiles thanNLinTimesare written.
- MOTORING
The generator speed went negative (
u%HSS_Spd is negative), which the simple variable-speed controller rejects. Usually an overshooting trim, or trim damping destabilising light blades when it happens neart = 0.
- ROTOR_OVERSPEED
CalcSteady saw the rotor advance more than one azimuth step in a time step (
The rotor is spinning too fast); in practice a divergence.
- DIVERGENCE
The run ended on a fatal error after signs of a diverging response (small-angle violations, NaNs, a structural solver that stopped converging).
- INITIALIZATION_ERROR
OpenFAST failed while initialising its modules: an input problem that no trim setting fixes.
- TIMEOUT
The run exceeded the caller’s wall-clock timeout and was killed.
- INCOMPLETE_LINEARIZATION
An untrimmed explicit-time sweep exited normally but wrote fewer
.linfiles than requested.
- NON_PERIODIC_SWEEP
A CalcSteady run wrote every requested
.linfile, but the rotor speed in their headers varies across the azimuth sweep by more than MBC3 accepts (vane.io.mbc_transform.ROTOR_SPEED_DRIFT_RTOL): the trim stopped on a response that is not periodic, typically becauseTrimTolwas loosened. The linearization driver detects it from the files;classify_openfast_output()never returns it.
- NON_FINITE_LINEARIZATION
The run exited normally and wrote every requested
.linfile, but a file holds a NaN or infinite value (an operating-point value or a matrix entry). This is a modelling degeneracy, such as AeroDyn unsteady aerodynamics left on at the blade-tip node, that other trim settings cannot cure. The linearization driver detects it by scanning the files;classify_openfast_output()never returns it.
- CASE_ERROR
Not an OpenFAST failure: the linearization driver raised an unexpected exception while it generated, ran or collected the case (a file-system error while the case was copied or configured, say). A sweep records it per case rather than aborting (see
vane.run.LinearizationDriver.run_sweep()); it is never retried, andclassify_openfast_output()never returns it.
- UNKNOWN
A non-zero exit with no recognised signature.
- class OpenFastResult(fst_path, returncode, lin_files=<factory>, output_file=None, summary_file=None, stdout='', stderr='')[source]
Bases:
objectOutcome of an OpenFAST run.
- Parameters:
fst_path (pathlib.Path) – The primary
.fstdeck that was run.returncode (int) – The executable’s exit status (0 on success).
lin_files (list[pathlib.Path]) – Full-system
.linfiles produced, ordered by linearization index.output_file (pathlib.Path or None) – The time-series output file (
.out/.outb) if one was written.summary_file (pathlib.Path or None) – The
.sumsummary file if one was written.stdout (str) – Captured standard output of the run.
stderr (str) – Captured standard error of the run (OpenFAST may write diagnostics here).
- property diagnosis: OpenFastDiagnosis | None
Return the classified failure of this run, or
Nonewhen none is seen.A shorthand for
classify_openfast_output()on the captured output and return code. It does not check the.lincount against the requestedNLinTimes; the linearization driver does that.
- exception OpenFastTimeoutError[source]
Bases:
OpenFastErrorRaised when an OpenFAST run exceeds its timeout.
A distinct subclass so callers can treat a timeout (a slow but retriable hard operating point) differently from a configuration failure (a missing deck or executable), which retrying would not fix.
- classify_openfast_output(output, returncode)[source]
Classify an OpenFAST run’s failure from its console output.
- Parameters:
- Returns:
OpenFastDiagnosis or None – The classified failure, or
Nonefor a successful exit without the forced-linearization warning. A successful exit with that warning isOpenFastFailureMode.TRIM_NOT_CONVERGED.- Return type:
OpenFastDiagnosis | None
Notes
A non-zero exit is classified in this order: an error during module initialisation, generator motoring, rotor overspeed, a divergence signature, the forced-linearization warning, and otherwise
OpenFastFailureMode.UNKNOWN. Initialisation comes first because a deck that cannot be read fails there whatever else is printed.OpenFAST wraps its console messages at about 100 columns, so a signature can be split across two lines (
u%HSS_Spd is/negative.). Matching is therefore done on the output with every run of whitespace, line breaks included, collapsed to a single space, and the quoted message joins the lines it spans.
- find_lin_files(fst_path)[source]
Return the full-system
.linfiles for a deck, ordered by index.- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck (its stem and directory locate the outputs).- Returns:
list[pathlib.Path] – The
<root>.<i>.linfiles sorted byi; per-module files (<root>.<Module>.<i>.lin) are excluded.- Return type:
- find_openfast(executable='openfast')[source]
Locate the OpenFAST executable.
- Parameters:
executable (str or pathlib.Path, optional) – Executable name to look up on
PATH, or an explicit path to it.- Returns:
pathlib.Path – The resolved executable path.
- Raises:
OpenFastError – If the executable is neither an existing file nor found on
PATH.- Return type:
- run_openfast(fst_path, *, executable='openfast', timeout=None, extra_args=(), check_path_lengths=None)[source]
Run OpenFAST on a deck and collect the linearization output.
The executable is invoked from the deck’s directory so its relative sub-file references resolve. Pre-existing generated
.lin, time-series, and summary files for this deck are removed first so the returned paths reflect only this run.- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck to run. Should already be configured for linearization (Linearize = True).executable (str or pathlib.Path, optional) – OpenFAST executable name or path (default
"openfast"onPATH).timeout (float or None, optional) – Maximum seconds to allow the run;
Nonewaits indefinitely.extra_args (tuple[str, ...], optional) – Additional command-line arguments inserted before the deck path.
check_path_lengths (bool or None, optional) – Run
check_deck_path_lengths()before launching.None(the default) checks on Windows only, where the path limit applies.
- Returns:
OpenFastResult – The return code, captured output, and the produced
.linfiles.- Raises:
OpenFastError – If the executable cannot be found, the deck does not exist, or the deck’s directory or the executable can be named only with the Windows
\\?\extended-length prefix (longer than the Windows path limit). A shorter path given with the prefix is passed without it.vane.run.DeckPathLengthError – If OpenFAST would open one of the deck’s files by a path longer than the Windows limit (checked before the process is launched).
OpenFastTimeoutError – If the run exceeds
timeout(a subclass ofOpenFastError).ValueError – If
timeoutis not positive and finite.
- Return type:
Notes
The executable is started in its own process group (Windows) or session (POSIX). On a timeout the whole process tree is killed, not just the direct child, so a wrapper executable (a
.bator shell script that launches OpenFAST) cannot leave an orphaned solver running and holding the output pipes open.
Match an OpenFAST deck to an OpenFAST executable that can read it.
OpenFAST changes its input-file format between releases, and a deck written for one
release is rejected by another: a v4 primary file fails on v5 at ConvTol (v5 added
the tight-coupling lines), and a v4.0 deck fails on v4.2 (no ElastoDyn
HubIner_Teeter) and on v4.1 if it has HydroDyn members (the old NPropSets
table). This module
recognises a deck’s input-format generation from the keywords its files carry (
detect_deck_generation());reads an executable’s release from its version banner (
openfast -v,openfast_version()), cached per executable file;chooses a compatible executable from the explicit argument, the
VANE_OPENFAST_EXECUTABLESenvironment variable, a user configuration file andPATH(select_openfast_executable()), and fails before any case is copied when none can read the deck, naming the release the deck needs.
The keyword signatures follow the OpenFAST API-change notes for each release.
- OPENFAST_CONFIG_ENV = 'VANE_OPENFAST_CONFIG'
Environment variable naming a configuration file of OpenFAST executables.
- OPENFAST_EXECUTABLES_ENV = 'VANE_OPENFAST_EXECUTABLES'
Environment variable listing OpenFAST executables, separated by
os.pathsep.
- class DeckGeneration(label, min_version, max_version, evidence=())[source]
Bases:
objectThe OpenFAST release range whose input format a deck is written in.
- Parameters:
label (str) – A short name of the format generation, e.g.
"v5","v4.2"or"v4.0-v4.1".min_version (tuple[int, int, int]) – The oldest release that reads the deck (inclusive).
max_version (tuple[int, int, int] or None) – The first release that no longer reads it (exclusive);
Nonewhen no upper bound is known.evidence (tuple[str, ...]) – The keyword findings the range was inferred from, for the log and the run provenance.
- class OpenFastSelection(executable, version, generation, source, considered=<factory>)[source]
Bases:
objectThe OpenFAST executable chosen for a deck, with what was considered.
- Parameters:
executable (pathlib.Path) – The executable to run.
version (tuple[int, int, int] or None) – Its release, or
Nonewhen its banner could not be read.generation (DeckGeneration) – The deck’s input-format generation.
source (str) – Where the executable came from:
"argument", the environment variable, the configuration file, or"PATH".considered (tuple[tuple[str, str], ...]) – Every candidate examined, as
(path, version or "unknown")pairs.
- generation: DeckGeneration
- candidate_executables(executable=None)[source]
Return the OpenFAST executables to consider, in order of preference.
The order is: the explicit
executableargument; the entries of theVANE_OPENFAST_EXECUTABLESenvironment variable (separated byos.pathsep); the lines of the configuration file named byVANE_OPENFAST_CONFIG(default~/.vane/openfast_executables.txt; one path per line,#comments); andopenfast,openfast_x64andOpenFASTonPATH. A leading~in a listed path (or inVANE_OPENFAST_CONFIG) is the user’s home directory. Duplicates (the same resolved file) are dropped; entries that do not exist are kept, so the caller can report them.
- detect_deck_generation(fst_path)[source]
Return the OpenFAST release range whose input format a deck uses.
The decision reads keyword signatures the releases introduced or removed:
v5 (
>=5.0): the primary file has the tight-coupling or multi-rotor lines (ModCoupling,RhoInf,ConvTol,MaxConvIter,NRotors,MirrorRotor), or ElastoDyn hasPitchDOF/BlPIner(1)/PBrIner(1).unknown (any release): the primary file has no
CompHydroline or no readable ElastoDyn file, so it carries no format signal.v3 (
<4.0): the primary file hasCompHydrobut noCompSeaSt, which v4.0 added.v4.2 (
>=4.2, <5.0): ElastoDyn hasHubIner_Teeter.v4.1 (
>=4.1, <4.2): HydroDyn has the cylindrical/rectangular member tables (NPropSetsCyl) but ElastoDyn lacksHubIner_Teeter.v4.0 (
>=4.0, <4.1): HydroDyn has the single member table (NPropSets).otherwise v4.0-v4.1 (
>=4.0, <4.2): a v4 deck without HydroDyn members and withoutHubIner_Teeter.
- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck.- Returns:
DeckGeneration – The release range and the keyword evidence for it.
- Raises:
vane.io.FastInputError – If the primary deck cannot be read.
- Return type:
- openfast_version(executable)[source]
Return an OpenFAST executable’s release from its version banner.
Runs
<executable> -vonce and parsesOpenFAST-v<major>.<minor>.<patch>. The result is cached per executable file (path, modification time and size), so repeated selections cost nothing.
- select_openfast_executable(fst_path, executable=None)[source]
Choose an OpenFAST executable that can read a deck’s input format.
The deck’s generation is detected (
detect_deck_generation()) and each candidate (candidate_executables()) is probed for its release (openfast_version()).An explicit
executableis always the one used when it can read the deck or its version cannot be read (logged); if its version is known and outside the deck’s range, the selection fails rather than silently running another one.Otherwise the first candidate whose release the deck accepts is used; failing that, the first candidate whose version could not be read (with a warning).
- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck.executable (str or pathlib.Path or None, optional) – An explicitly requested executable.
None(the default) chooses among the configured ones.
- Returns:
OpenFastSelection – The executable, its version, and the evidence of the choice.
- Raises:
vane.run.OpenFastError – If no candidate can read the deck: the message names the release range the deck needs, the keyword evidence, and every executable considered with its version. Raised before any case is copied or launched.
- Return type:
Find and copy only the files an OpenFAST deck references.
A sweep copies the template case once per operating point so that each case can be
edited independently. When the template’s sub-files live outside the deck’s own folder
(../5MW_Baseline/...), template_root must be a higher directory – for the
OpenFAST regression-test suite, a directory holding dozens of unrelated cases and their
outputs. Copying that whole tree per point costs hundreds of megabytes per case and
carries unrelated .lin files into the case directory.
deck_file_closure() instead walks the deck’s references: starting from the
primary .fst it reads every referenced text input (ElastoDyn, its blade and tower
files, AeroDyn and its airfoil polars and @-prefixed coordinate files, ServoDyn and
its controller DLL and controller input file, the controller’s own performance file,
InflowWind wind files, SeaState, HydroDyn and its potential-flow data, SubDyn,
MoorDyn, structural-control files, …) and follows them recursively. The walk does not
rely on a keyword list: every value token that looks like a path is resolved, relative
to the referencing file’s directory (the OpenFAST convention) and to the deck’s
directory (OpenFAST’s working directory), and kept only if it names an existing file
under template_root. Tokens that are numbers, "unused", "default" or do not
name a file are ignored, so an unknown keyword never stops the walk. Two kinds of
reference name more than one file and are expanded:
a root name (HydroDyn
PotFile, SeaStateWvKinFile, InflowWindFilenameRootand any other keyword ending inRoot) brings every file<root>.<ext>in its directory (.1,.3,.hst,.ss,.ssexctn,.12d,.12s,.Elev, …), except OpenFAST output files;a shared library (
.dll,.so,.dylib) brings its platform and bitness variants of the same stem (DISCON.dll,DISCON.so,DISCON_x64.dll, …). Other files next to the library are copied only when they are referenced.
copy_template_files() copies the closure into a case directory, preserving each
file’s path relative to template_root so every relative reference still resolves.
When the walk cannot vouch for completeness – a relative reference that escapes
template_root or an input that cannot be read – it logs a warning and copies the
whole tree instead, exactly as before; mode="tree" selects that conservative copy
outright. Correctness takes precedence over size. A reference given as an absolute path
outside template_root keeps working from the case copy and is left in place (an
edited input outside the case is still rejected by
require_inputs_within()).
- class DeckFileClosure(root, files, external=(), open_paths=())[source]
Bases:
objectThe files a deck references, found by
deck_file_closure().- Parameters:
root (pathlib.Path) – The template root every file in
fileslies under.files (tuple[pathlib.Path, ...]) – Absolute paths of the referenced files under
root, the primary deck included, sorted.external (tuple[pathlib.Path, ...]) – Absolute references to existing files outside
root. They are not copied; an absolute path keeps resolving from the case copy.open_paths (tuple[tuple[pathlib.Path, str], ...]) – For each referenced file (the deck included), the path string OpenFAST opens it by when run from the deck’s directory on the deck’s bare file name: the referencing file’s directory as OpenFAST spelled it, joined to the reference but not normalized (
<deck dir>\.\..\Shared\Blade.dat). Relative strings are prefixed with the deck’s directory, so each is the full path the operating system receives. When a file is reached by several references, the longest spelling is kept.
- exception DeckReferenceError[source]
Bases:
ValueErrorRaised when the referenced-file closure of a deck cannot be trusted.
The walk raises it for a relative reference that resolves outside
template_root(the case copy could not reproduce it) or for an input file that cannot be read.copy_template_files()catches it and falls back to copying the whole template tree.
- TemplateCopyMode
its referenced files, or the whole tree.
- Type:
How a template is copied into a case
alias of
Literal[‘referenced’, ‘tree’]
- copy_template_files(fst_path, template_root, destination, *, mode='referenced', link_inputs=True)[source]
Copy a template case into
destination.- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck insidetemplate_root.template_root (str or pathlib.Path) – The directory copied from; file paths relative to it are preserved.
destination (str or pathlib.Path) – The case directory to create. It must not exist yet.
mode ({"referenced", "tree"}, optional) –
"referenced"(the default) copies only the deck’s referenced files (deck_file_closure());"tree"copies the whole template tree.link_inputs (bool, optional) – When
True(the default), input files are hard-linked rather than copied, so heavy read-only inputs share an inode with the template; OpenFAST output files (.out,.lin, …) are always copied. Linking falls back to a byte copy where it is unsupported (e.g. across filesystems).
- Returns:
{“referenced”, “tree”} – The copy that was made:
"tree"when requested, or when the referenced-file walk could not vouch for completeness and fell back (logged as a warning).- Raises:
ValueError – If
modeis not one of the supported copy modes.FileExistsError – If
destinationalready exists.
- Return type:
Literal[‘referenced’, ‘tree’]
- deck_file_closure(fst_path, template_root)[source]
Return every file under
template_rootthat a deck references, recursively.- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck.template_root (str or pathlib.Path) – The directory the referenced files are collected from; must contain the deck.
- Returns:
DeckFileClosure – The referenced files (the deck included) and any absolute references outside the root.
- Raises:
FileNotFoundError – If
fst_pathis not an existing file.ValueError – If
fst_pathis not insidetemplate_root.DeckReferenceError – If a relative reference resolves to an existing file outside
template_root, or a referenced input cannot be read.
- Return type:
Notes
A value token is treated as a path when it contains
.,/or\and is not a number; root-name keywords are expanded whatever their spelling. A token is kept only if it names an existing file, so prose in a description or a stale keyword cannot add a missing file or stop the walk. Including an unneeded file is harmless; the walk errs on that side.
- infer_template_root(fst_path)[source]
Return the smallest directory holding every file a deck references relatively.
A deck that references shared inputs outside its own folder (
../5MW_Baseline/...,../IEA-15-240-RWT/...) must be copied per case from a directory that contains them, so that every relative reference still resolves in the copy and every edited sub-file lies inside it. This walks the deck’s references (asdeck_file_closure()does, without a root boundary) and returns the deepest common ancestor of the deck’s directory and every file reached through a relative reference. References given as absolute paths keep working from any copy and do not widen the root.- Parameters:
fst_path (str or pathlib.Path) – The primary
.fstdeck.- Returns:
pathlib.Path – The inferred template root; the deck’s own directory when every relative reference lies under it.
- Raises:
FileNotFoundError – If
fst_pathis not an existing file.- Return type:
Notes
A referenced input that cannot be read stops the walk; the files found up to that point still widen the root, and the reason is logged.
System identification
System identification — state-space export and Kalman-ready matrices.
State space and reduced-order models
Clean linear-time-invariant state-space representation.
A StateSpace holds the validated A, B, C, D matrices of a
continuous- or discrete-time system together with the state, input, and output
names. The averaged matrices of an MBC transform are exported directly into this
form, and a zero-order-hold discretization is provided for use in a digital twin.
- class StateSpace(a, b=None, c=None, d=None, state_names=<factory>, input_names=<factory>, output_names=<factory>, dt=None, output_mbc_coordinates=<factory>)[source]
Bases:
objectA linear time-invariant state-space system.
- Parameters:
a (npt.NDArray[np.float64]) – State matrix, shape
(n_states, n_states).b (npt.NDArray[np.float64] or None) – Input, output, and feedthrough matrices, shapes
(n_states, n_inputs),(n_outputs, n_states),(n_outputs, n_inputs); any may beNone.c (npt.NDArray[np.float64] or None) – Input, output, and feedthrough matrices, shapes
(n_states, n_inputs),(n_outputs, n_states),(n_outputs, n_inputs); any may beNone.d (npt.NDArray[np.float64] or None) – Input, output, and feedthrough matrices, shapes
(n_states, n_inputs),(n_outputs, n_states),(n_outputs, n_inputs); any may beNone.state_names (list[str]) – Names of the states, inputs, and outputs; each is either empty or matches the corresponding dimension.
input_names (list[str]) – Names of the states, inputs, and outputs; each is either empty or matches the corresponding dimension.
output_names (list[str]) – Names of the states, inputs, and outputs; each is either empty or matches the corresponding dimension.
dt (float or None) – Sample time;
Nonefor a continuous-time system, otherwise the discrete sample time in seconds.output_mbc_coordinates (list[str]) – Multi-blade coordinate of each output (
"collective"/"cosine"/"sine"for a transformed blade-triplet output, else""), aligned withoutput_names. Empty when unknown. Carried from an MBC transform so a load map can tell a transformed multi-blade output apart from a literal per-blade channel.
- Raises:
ValueError – If the matrix shapes or name lengths are inconsistent.
- a: npt.NDArray[np.float64]
- property n_inputs: int
Return the number of inputs (from B, else D, else 0).
A feedthrough-only system (
B is NonebutDpresent) still hasD.shape[1]inputs; the feedthrough columns define them.
- property n_outputs: int
Return the number of outputs (from C, else D, else 0).
A feedthrough-only system (
C is NonebutDpresent) still hasD.shape[0]outputs; the feedthrough rows define them.
- is_stable()[source]
Return whether the system is asymptotically stable.
- Returns:
bool – For a continuous system, whether every eigenvalue of
Ahas negative real part; for a discrete system, whether every eigenvalue lies strictly inside the unit circle.- Return type:
- discretized(dt)[source]
Return a zero-order-hold discretization at sample time
dt.- Parameters:
dt (float) – Sample time in seconds; must be positive.
- Returns:
StateSpace – The discrete-time system;
CandDare unchanged.- Raises:
ValueError – If
dtis not a positive, finite number, or the system is already discrete.- Return type:
- state_space_from_mbc(result)[source]
Export the averaged MBC matrices as a continuous-time state-space system.
- Parameters:
result (MBCResult) – An MBC transform result whose
avg_ais present.- Returns:
StateSpace – The continuous-time system with state names from the result.
- Raises:
ValueError – If
result.avg_aisNone.- Return type:
- static_gain(system)[source]
Return the zero-frequency input-output gain
D - C A^-1 B.This quantity is especially important for module-level OpenFAST models: a very large direct stiffness can be cancelled by the equilibrium response of internal states. Preserving
Dalone during reduction then gives the wrong static physics even when the retained modes are otherwise accurate.- Raises:
ValueError – If the system is discrete, lacks
BorC, orAis singular.- Parameters:
system (StateSpace)
- Return type:
npt.NDArray[np.float64]
Real modal state-space realization from modal parameters.
A set of under-damped modes is realized as a real, block-diagonal state-space
system. Each mode with eigenvalue \(\lambda = \sigma + j\omega\) contributes a
2x2 block
whose eigenvalues are \(\sigma \pm j\omega\), acting on the real and imaginary parts of the modal coordinate. When mode shapes are supplied, the output matrix maps the modal states to physical responses via \([\,2\operatorname{Re}\phi, \,-2\operatorname{Im}\phi\,]\). The result is a compact reduced-order model of the identified modes.
- modal_state_space(eigenvalues, mode_shapes=None, *, output_names=None)[source]
Build a real modal state-space realization.
- Parameters:
eigenvalues (npt.NDArray[np.complex128]) – Modal eigenvalues, shape
(n_modes,).mode_shapes (npt.NDArray[np.complex128] or None, optional) – Complex mode shapes, shape
(n_outputs, n_modes); when given, the output matrix maps modal states to physical responses.output_names (list[str] or None, optional) – Names of the outputs (the rows of
mode_shapes).
- Returns:
StateSpace – A continuous-time system with
2 * n_modesstates.- Raises:
ValueError – If
mode_shapescolumn count does not match the number of eigenvalues.- Return type:
- modal_state_space_from_solution(solution)[source]
Build a modal realization from a
ModalSolution.- Parameters:
solution (ModalSolution) – The modal solution whose eigenvalues and mode shapes are realized.
- Returns:
StateSpace – The real modal state-space system, with outputs named by the solution’s DOF descriptions when available.
- Return type:
Reduced-order modal models from a linearized state space.
A full OpenFAST linearization carries hundreds of states, many of them fast, weakly observable, or numerically marginal aero/actuator modes that a downstream estimator neither needs nor can stabilise. This module reduces the system to the few structural modes that actually drive a quantity of interest, in a real, block-diagonal modal realization that preserves the inputs and outputs.
Each retained underdamped mode \(\lambda = \sigma + j\omega\) (with right eigenvector \(\mathbf v\) and left eigenvector \(\mathbf w\), \(\mathbf w^{\mathsf H}\mathbf v = 1\)) becomes a real \(2\times2\) block
with the feed-through \(\mathbf D\) unchanged. The block’s eigenvalues are exactly \(\sigma\pm j\omega\), so the retained modal frequencies and damping are preserved to machine precision. Modes are ranked by their residue in a chosen output (the load-observability ranking): the residue of mode \(k\) in output \(i\) is \(|\mathbf C_i\mathbf v_k|\), i.e. how strongly that mode appears in that load.
This is the reduction engine the digital-twin / reduced-estimator path depends on. It is the modal counterpart to balanced truncation; unlike balanced truncation it tolerates the unstable aero poles a turbine linearization contains (they are simply not selected, and any retained pole can optionally be reflected into the stable half-plane).
- class ModalReduction(system, frequencies_hz, damping_ratios, residues, energy_retained, n_full_states, n_stabilized, state_projection=None, state_reconstruction=None, stabilized=None)[source]
Bases:
objectA modally reduced state space and the modes it retained.
- Parameters:
system (StateSpace) – The reduced, real, block-diagonal continuous state space (two states per retained mode), with the original inputs and outputs.
frequencies_hz (npt.NDArray[np.float64]) – Natural frequency of each retained mode (Hz), in the reduced state order.
damping_ratios (npt.NDArray[np.float64]) – Damping ratio of each retained mode, consistent with the realized reduced
Ablock: a mode reflected into the stable half-plane (seen_stabilized) reports its post-stabilisation (positive) damping, not the original value.residues (npt.NDArray[np.float64]) – The ranking residue of each retained mode (its magnitude in the ranked output, or its observability norm when no output was specified).
energy_retained (float) – Fraction of the candidate modes’ total squared residue captured by the retained modes, in
[0, 1]— a cheap measure of how much of the ranked output’s modal content the reduction keeps.n_full_states (int) – Number of states in the original system.
n_stabilized (int) – How many retained modes had an unstable eigenvalue reflected into the stable half-plane (
0unless the system had unstable poles among those kept).state_projection (npt.NDArray[np.float64] or None) – Real map from a full physical state into the retained modal coordinates.
state_reconstruction (npt.NDArray[np.float64] or None) – Real map from retained modal coordinates back to the selected full-state subspace. Together these maps permit a consistently based periodic/LPV projection without independently reducing and re-phasing every azimuth.
stabilized (npt.NDArray[np.bool] or None) – Per retained mode,
Truewhere an unstable eigenvalue was reflected into the stable half-plane (n_stabilizedis its count). The physical (pre-reflection) damping of a reflected mode is-damping_ratios[k]; its natural frequency is unchanged.Nonefor a hand-built reduction that does not record it.
- system: StateSpace
- frequencies_hz: npt.NDArray[np.float64]
- damping_ratios: npt.NDArray[np.float64]
- residues: npt.NDArray[np.float64]
- modal_reduction(system, *, n_modes=None, frequency_band_hz=None, rank_output=None, rank_outputs=None, required_transfer_pairs=None, n_required_transfer_modes=0, stabilize=True, match_dc_gain=False)[source]
Reduce a state space to a real modal realization of its dominant modes.
Keeps the underdamped modes (optionally within a frequency band) with the largest residue, ranked in a chosen output (
rank_output, the load-observability ranking) by an equal-weight, unit-normalized joint residue over several outputs (rank_outputs), or by the overall observability norm, as a real block-diagonal model with the inputs and outputs preserved. The retained modal frequencies and damping are exact; the full model’s many marginal/unstable aero modes are simply dropped.- Parameters:
system (StateSpace) – A continuous-time system (
dtisNone) with aCmatrix.n_modes (int or None, optional) – Number of modes to retain (the highest-residue ones).
Nonekeeps every candidate mode (every underdamped mode in the band).frequency_band_hz (tuple[float, float] or None, optional) – Restrict candidates to modes whose natural frequency is in
[low, high](Hz), e.g. the structural band.Noneimposes no band.rank_output (int or str or None, optional) – Rank modes by their residue in this output (index or name) — keep the modes that drive this load.
Noneranks by the observability norm over all outputs unlessrank_outputsis given.rank_outputs (Sequence[int or str] or None, optional) – Rank modes jointly for these output rows. Each row with numerically resolved candidate-mode content has its modal-residue vector normalized to unit Euclidean norm before the rows are combined, so quantities with different physical units (for example platform motion and line tension) receive equal weight. Mutually exclusive with
rank_output.required_transfer_pairs (sequence of tuple(output, input) or None, optional) – Input-output pairs whose strongest transfer-function modes must be retained before the remaining modal budget is allocated by the output ranking. Each pair is ranked by the true modal transfer residue \(|(C_i v_k)(w_k^H B_j)|\), normalized so pairs in different units receive equal weight. This is intended for reduced observers that must preserve a measured response to an estimated unknown input.
n_required_transfer_modes (int, optional) – Number of modes reserved for
required_transfer_pairs. Must be zero when no pairs are supplied, positive when they are supplied, and no greater thann_modeswhen that budget is finite. Default0.stabilize (bool, optional) – Reflect any retained unstable eigenvalue into the stable half-plane (
sigma -> -|sigma|), so the reduced model is a stable estimator basis. DefaultTrue.match_dc_gain (bool, optional) – Correct the reduced feedthrough so its zero-frequency gain exactly matches the full model. This preserves equilibrium stiffness when a large direct term is cancelled by omitted internal-state response. Both models must have
Band nonsingularAmatrices. DefaultFalse.
- Returns:
ModalReduction – The reduced system and the retained-mode metadata.
- Raises:
ValueError – If the system is discrete, has no
Cmatrix,n_modesis not positive,frequency_band_hzis invalid, the ranking selections conflict or are empty, no modes remain after filtering, or a retained mode belongs to a defective (non-diagonalizable) repeated-eigenvalue cluster — a Jordan chain a block-diagonal modal realization cannot represent.- Return type:
Notes
The realization is exact for diagonalizable systems, including genuinely repeated (but non-defective) eigenvalues. A defective repeated mode (a Jordan chain) cannot be written as independent modal blocks and is rejected rather than silently reduced to a model with a different transfer function.
- output_mode_residues(system, output)[source]
Rank the underdamped modes by their residue in one output (load).
The residue of mode \(k\) in the output is \(|\\mathbf C_i\\mathbf v_k|\) — how strongly the mode appears in that load. This is the load-observability ranking that drives
modal_reduction()and is the basis for choosing which modes a load estimator must retain.- Parameters:
system (StateSpace) – A continuous system with a
Cmatrix.output (int or str) – The output row index, or an output name (exact match, else substring).
- Returns:
tuple of npt.NDArray[np.float64] –
(frequencies_hz, damping_ratios, residues)for the underdamped modes, sorted by descending residue.- Raises:
ValueError – If the system is discrete, has no
Cmatrix, or the output cannot be resolved.- Return type:
tuple[npt.NDArray[np.float64], npt.NDArray[np.float64], npt.NDArray[np.float64]]
Balanced truncation — gramian-based reduction with an a-priori error bound.
modal_reduction() keeps whole modes chosen by their residue in a load.
Balanced truncation is the complementary, input-output-optimal reduction: it balances
the controllability and observability Gramians so that each state is equally hard to
reach and to observe, then drops the states with the smallest Hankel singular values
— the ones that contribute least to the transfer function. Unlike modal reduction it
comes with a guarantee: the truncated model’s worst-case frequency-response error is
bounded a priori by twice the sum of the discarded Hankel singular values.
For a stable system the Gramians solve the Lyapunov equations
\(\mathbf A\mathbf W_c+\mathbf W_c\mathbf A^{\mathsf T}+\mathbf B\mathbf B^{\mathsf
T}=\mathbf 0\) and \(\mathbf A^{\mathsf T}\mathbf W_o+\mathbf W_o\mathbf A+
\mathbf C^{\mathsf T}\mathbf C=\mathbf 0\) (discrete analogues for a sampled system). The
Hankel singular values are \(\sigma_i=\sqrt{\lambda_i(\mathbf W_c\mathbf W_o)}\),
and in the balanced realization both Gramians equal
\(\operatorname{diag}(\sigma)\). This reduction requires asymptotic stability (the
Gramians exist) and that every retained Hankel singular value be resolved above
rounding level (the retained order is controllable and observable). The Gramians are
factored from their symmetric eigendecomposition with rounding-level negative
eigenvalues clipped, so the numerically indefinite Gramians of a lightly damped minimal
system do not cause a spurious rejection. It suits the structural ROM, while the
unstable aero poles of a raw turbine linearization are handled by
modal_reduction() instead.
- class BalancedReduction(system, hankel_singular_values, error_bound, n_full_states)[source]
Bases:
objectA balanced-truncation reduction and its Hankel-singular-value spectrum.
- Parameters:
system (StateSpace) – The reduced (or, when no order is given, balanced full-order) system, with the original inputs and outputs and the feed-through
Dunchanged.hankel_singular_values (npt.NDArray[np.float64]) – All Hankel singular values of the full system, in descending order. The retained states correspond to the largest ones; the tail is what the error bound sums.
error_bound (float) – The a-priori worst-case frequency-response error
2 * sum(discarded Hankel singular values)— an upper bound on the \(\mathcal H_\infty\) error \(\lVert\mathbf G-\mathbf G_r\rVert_\infty\). Zero when nothing is dropped.n_full_states (int) – Number of states in the original system.
- system: StateSpace
- hankel_singular_values: npt.NDArray[np.float64]
- balanced_truncation(system, *, n_states=None, tol=None)[source]
Reduce a system by balanced truncation, with an a-priori error bound.
Balances the controllability and observability Gramians, then keeps the states with the largest Hankel singular values. The order is set by
n_states(an explicit count) ortol(drop singular values at or belowtoltimes the largest); with neither, the system is balanced but not truncated.- Parameters:
system (StateSpace) – A stable system with both
BandCmatrices. May be continuous (dtisNone) or discrete.n_states (int or None, optional) – Number of states to retain (the largest Hankel singular values). Takes precedence over
tol. Must be in[1, n_states_full].tol (float or None, optional) – Relative threshold: retain the states whose Hankel singular value exceeds
tol * max(sigma). Must be non-negative.
- Returns:
BalancedReduction – The reduced system, the full Hankel-singular-value spectrum, and the bound.
- Raises:
ValueError – If the system lacks
BorC, is not asymptotically stable, the retained order includes a Hankel singular value at rounding level (the system is not minimal at that order), orn_states/tolis out of range.- Return type:
Operating-point ROM family with consistently-tracked modes (LPV packaging).
A turbine is linearized at many operating points (wind speed, rotor speed, …), and the digital twin wants one reduced model per point so it can schedule the estimator on the operating condition. Reducing each point independently is not enough: the reduction ranks modes by their residue, so the order — and the meaning of “mode 2” — changes from point to point as modes veer and cross. This module reduces every point and then tracks the modes across the family, so a given index is the same physical mode at every operating point: a proper linear-parameter-varying (LPV) packaging of the ROM.
Modes are matched between adjacent operating points by the Modal Assurance Criterion of their output residues (how each mode appears at the outputs — the same quantity the reduction ranks on), with a frequency-proximity tie-breaker, via an optimal (Hungarian) assignment. Each tracked mode carries the MAC value against the previous point, so a low value flags where the tracking is uncertain (a crossing or veering region) — the hook the operating-envelope-consistency analysis needs.
- class RomFamily(operating_points, systems, frequencies_hz, damping_ratios, tracking_mac, stabilized=<factory>)[source]
Bases:
objectA parameter-indexed family of reduced models with tracked modes.
- Parameters:
operating_points (npt.NDArray[np.float64]) – The scheduling-parameter value of each point, shape
(n_points,), in the order given.systems (list[StateSpace]) – The reduced model at each operating point, all with the same mode (and state) ordering: index
kis the same physical mode across the family.frequencies_hz (npt.NDArray[np.float64]) – Natural frequency of each tracked mode, shape
(n_points, n_modes).damping_ratios (npt.NDArray[np.float64]) – Physical damping ratio of each tracked mode, shape
(n_points, n_modes): the damping of the linearization’s own eigenvalue, negative for an unstable mode, even where the stored realization reflected that pole (seestabilized).tracking_mac (npt.NDArray[np.float64]) – The MAC of each tracked mode against the previous operating point, shape
(n_points, n_modes); the first row is all ones (the reference). A low value marks a mode whose tracking across that step is uncertain.stabilized (npt.NDArray[np.bool], optional) –
Truewhere the stored realization insystemsreflected that mode’s unstable pole into the stable half-plane, shape(n_points, n_modes). For such a mode the realizedAblock has damping-damping_ratiosrather than the physical value. Defaults to allFalse(for hand-built families).
- operating_points: npt.NDArray[np.float64]
- systems: list[StateSpace]
- frequencies_hz: npt.NDArray[np.float64]
- damping_ratios: npt.NDArray[np.float64]
- tracking_mac: npt.NDArray[np.float64]
- stabilized: npt.NDArray[np.bool_]
- build_rom_family(systems, operating_points, *, n_modes, frequency_band_hz=None, rank_output=None, rank_outputs=None, required_transfer_pairs=None, n_required_transfer_modes=0, frequency_weight=0.5, match_dc_gain=False, stabilize=True)[source]
Reduce a system at each operating point and track the modes across the family.
The reference point’s top-
n_modesmodes (by residue) define the tracked set. At each later point the system is reduced to all candidate modes and the tracked modes are matched against that full set by output-residue MAC (with a frequency tie-breaker), so a tracked mode is recovered even when it drops below the top-n_modesresidue rank at that point; the matched modes are then selected and reordered to keep mode identity consistent.- Parameters:
systems (Sequence[StateSpace]) – One continuous-time linearization per operating point, with a
Cmatrix and the same outputs throughout.operating_points (Sequence[float]) – The scheduling-parameter value of each system (same length as
systems).n_modes (int) – Number of modes to retain and track at every point. Each point must yield at least this many underdamped modes.
frequency_band_hz (tuple[float, float] or None, optional) – Restrict the retained modes to this natural-frequency band (Hz).
rank_output (int or str or None, optional) – Rank modes by their residue in this output when reducing (see
modal_reduction()).rank_outputs (Sequence[int or str] or None, optional) – Rank modes by the equal-weight, unit-normalized joint residue of these outputs (see
modal_reduction()). Mutually exclusive withrank_output.required_transfer_pairs (sequence of tuple(output, input) or None, optional) – Observer input-output pairs whose strongest transfer-residue modes are reserved in the reference modal set (see
modal_reduction()).n_required_transfer_modes (int, optional) – Number of the
n_modesslots reserved for those transfer pairs. The reserved set is fixed at the reference point and then tracked by mode identity (joint MAC/frequency matching over all candidates), not re-selected per point, so a reshuffled transfer ranking cannot swap physical modes; a reserved mode is only barred from matching a candidate with negligible transfer residue.frequency_weight (float, optional) – Strength of the frequency-proximity tie-breaker in
[0, 1]used when matching modes between points (default0.5).match_dc_gain (bool, optional) – After the tracked modal subset is selected, correct each reduced feedthrough so its zero-frequency gain exactly equals its corresponding full system. This is applied after truncation (not to the all-candidate intermediate model), thereby preserving equilibrium stiffness for the actual stored family. Default
False.stabilize (bool, optional) – Reflect a retained unstable pole into the stable half-plane in the stored realizations (see
modal_reduction()), so each member is a stable estimator basis. DefaultTrue. The reportedRomFamily.damping_ratiosare always the physical (pre-reflection) values,RomFamily.stabilizedmarks every reflected mode, and a warning is logged when any mode was reflected.Falsestores the unstable poles.
- Returns:
RomFamily – The reduced models in a consistent mode order, with per-point frequencies, physical damping ratios, the per-step tracking MAC, and the reflection mask.
- Raises:
ValueError – If
systemsis empty, its length differs fromoperating_points,n_modesis not positive, the systems do not share the same output layout, input dimension, and time domain,operating_pointsare not finite and strictly monotonic, or a point yields fewer thann_modesmodes.- Return type:
- combine_rom_families(base, enrichment, *, duplicate_frequency_tolerance_hz=1e-08, enrichment_outputs=None)[source]
Compose two disjoint tracked modal families without re-reducing either.
The base and enrichment must use the same operating-point schedule and I/O layout. Their modal state blocks are concatenated, their output contributions are summed through a horizontal
Cconcatenation, and the shared feedthrough is retained once. A frequency-separation check rejects a mode present in both families, which would otherwise double-count its transfer residue.- Parameters:
base (RomFamily) – Independently tracked, continuous- or discrete-time modal families with the same schedule, input/output names, MBC labels, feedthrough, and sample time.
enrichment (RomFamily) – Independently tracked, continuous- or discrete-time modal families with the same schedule, input/output names, MBC labels, feedthrough, and sample time.
duplicate_frequency_tolerance_hz (float, optional) – Maximum absolute per-point frequency difference considered a duplicate. Default
1e-8Hz.enrichment_outputs (sequence of int or str or None, optional) – Output rows to which the enrichment is allowed to contribute.
Nonekeeps every enrichment output. A declared subset zeros all other enrichmentCrows while leaving the base family untouched; this supports separated observer blocks, such as drivetrain modes used only by a rotor-speed measurement.
- Returns:
RomFamily – A block-composed family with base modes first and enrichment modes second.
- Raises:
ValueError – If the tolerance is invalid, schedules or I/O layouts differ, feedthroughs do not match exactly, or a duplicate modal branch is detected.
- Return type:
Physical-state LPV family export: named operating-point plants from .lin files.
build_rom_family() packages reduced, mode-tracked models for
modal/Campbell work. This module is the complementary export for control design: it
reads a directory of OpenFAST linearization files 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", "BldPitch1"), with a consistent
ordering across the operating-point sweep – the linear-parameter-varying plant a
gain-scheduled controller schedules on.
The matrices are azimuth-averaged in the physical (rotating) frame, preserving the
original per-blade channel names; no MBC/Coleman transform is applied (for the
multi-blade coordinate representation use build_rom_family() /
state_space_from_mbc()). The result is one LTI model per operating
point – the standard plant for LQR/LPV control synthesis. This is the single VANE entry
point a downstream controller needs: it never touches raw .lin files itself.
- class LPVFamily(scheduling_points, systems, state_names=<factory>, input_names=<factory>, output_names=<factory>, scheduling_variable='WindSpeed', scheduling_unit='m/s', auto_added_states=<factory>, dropped_coupling_a=<factory>, dropped_coupling_c=<factory>)[source]
Bases:
objectA parameter-indexed family of named physical-state plants for control design.
- Parameters:
scheduling_points (npt.NDArray[np.float64]) – The scheduling-variable value of each operating point, shape
(n_points,), sorted strictly ascending. In the raw linearization unit:WindSpeedin m/s,RotorSpeedin rad/s (not rpm).systems (list[StateSpace]) – The physical-state plant at each operating point, in the same order as
scheduling_points. Every system has the same selected states, inputs, and outputs, in the same order – the consistency a scheduled controller relies on.state_names (list[str]) – The selected physical state, input, and output names, shared by every system.
input_names (list[str]) – The selected physical state, input, and output names, shared by every system.
output_names (list[str]) – The selected physical state, input, and output names, shared by every system.
scheduling_variable (str) – The scheduling variable the family is indexed on (e.g.
"WindSpeed").scheduling_unit (str) – The physical unit of
scheduling_pointsin the raw linearization convention:"m/s"forWindSpeed,"rad/s"forRotorSpeed(not rpm, unlike the modal/Campbell path). Carried explicitly so a scheduled controller cannot mis-schedule by assuming the wrong unit.auto_added_states (list[str]) – The states the builder added to the request to complete a position/velocity pair (see
pair_derivativesinbuild_physical_lpv_family()), named by their unit-stripped linearization descriptions. Each also appears instate_names. Empty when nothing was added.dropped_coupling_a (npt.NDArray[np.float64]) – Per operating point, the relative state-matrix coupling the state selection discards,
||A[s, ~s]||_F / ||A[s, :]||_F(sthe selected states), shape(n_points,), in the order ofscheduling_points. Zero when every state is selected.dropped_coupling_c (npt.NDArray[np.float64]) – Per operating point, the relative output coupling the selection discards,
||C[o, ~s]||_F / ||C[o, :]||_F(othe selected outputs), shape(n_points,). Zero when no outputs are selected or every state is selected.
- scheduling_points: npt.NDArray[np.float64]
- systems: list[StateSpace]
- dropped_coupling_a: npt.NDArray[np.float64]
- dropped_coupling_c: npt.NDArray[np.float64]
- property dropped_coupling: npt.NDArray[np.float64]
Per-point relative dropped coupling, the larger of the
AandCterms.- Returns:
npt.NDArray[np.float64] –
max(dropped_coupling_a, dropped_coupling_c)element-wise, shape(n_points,). This is the quantitymax_dropped_couplingchecks.
- build_physical_lpv_family(lin_dir, *, states, inputs=(), outputs=(), scheduling_var='WindSpeed', pair_derivatives=True, max_dropped_coupling=0.05)[source]
Build a named physical-state LPV plant family from a directory of
.linfiles.Discovers the operating points in
lin_dir, averages each point’s azimuth files in the physical frame, and selects the namedstates/inputs/outputsinto a state-space model – one per operating point, ordered byscheduling_var, with the same channel layout everywhere. No MBC transform is applied, so per-blade channel names are preserved (usebuild_rom_family()for the multi-blade coordinate representation).Selecting a state subset truncates the plant: rows and columns of the unselected states are removed, discarding their coupling into the selected ones. Two safeguards keep that truncation honest. Second-order DOFs are completed with their position/velocity twin (
pair_derivatives), and the relative coupling the selection still drops is measured at every point and checked (max_dropped_coupling).- Parameters:
lin_dir (str or pathlib.Path) – Directory of OpenFAST
.linfiles, searched recursively, so a campaign laid out with one sub-directory per operating point is read as distinct points (grouped into operating points by case name).states (Sequence[str]) – Physical state names to select, e.g.
["ED PtfmPitch", "ED RotSpeed"]. Matched against the linearization’s state descriptions (units stripped): an exact match is preferred, else a unique substring. When a name matches both a DOF and its"First time derivative of <DOF>"twin, the position DOF is chosen (and, withpair_derivatives, its twin is then added); a name matching two genuinely different channels raises, listing them. The selected order is the state order, with any auto-added twin inserted next to its partner.inputs (Sequence[str], optional) – Input and output names to select (e.g.
["GenTq", "BldPitch1"]); empty selects none (noB/C).outputs (Sequence[str], optional) – Input and output names to select (e.g.
["GenTq", "BldPitch1"]); empty selects none (noB/C).scheduling_var (str, optional) – The scheduling variable:
"WindSpeed"(default, m/s) or"RotorSpeed".RotorSpeedis the raw linearization rotor speed in rad/s (the unit OpenFAST writes), not rpm — unlike the modal/Campbell path, which reports rotor speed in rpm. Schedule a controller built against rad/s accordingly.pair_derivatives (bool, optional) – If true (default), every selected second-order DOF whose twin – the
"First time derivative of <DOF>"velocity of a selected position, or the position of a selected velocity – exists in the linearization but was not requested gets that twin added, immediately after a position or immediately before a velocity, so each pair reads position then velocity. The added names (unit-stripped descriptions) are logged at INFO and recorded inLPVFamily.auto_added_states. A position without its velocity is a pure truncation artefact (d(position)/dtis exactly the dropped velocity), so disable this only to reproduce a deliberate truncation.max_dropped_coupling (float or None, optional) – Ceiling on the relative coupling the state selection discards, default
0.05. At each operating point VANE computes||A[s, ~s]||_F / ||A[s, :]||_Fover the selected statessand, when outputs are selected,||C[o, ~s]||_F / ||C[o, :]||_Fover the selected outputso; the per-point value is the larger of the two (LPVFamily.dropped_coupling). If the largest value over the family exceeds the ceiling,ValueErroris raised naming the worst point and the dropped states contributing most. The norms are taken in the linearization’s own units, so the measure is a screening check, not a unit-invariant error bound.Nonedisables the check (the norms are still recorded). Must be a finite number>= 0.
- Returns:
LPVFamily – The physical-state plant family, with
scheduling_pointssorted ascending and aStateSpaceper point sharing the selected channel layout. Itsstate_namesis the final (paired) state list;auto_added_states,dropped_coupling_aanddropped_coupling_crecord the pairing and the per-point truncation norms.- Raises:
FileNotFoundError – If
lin_dirdoes not exist or contains no.linfiles.ValueError – If
scheduling_varis unknown;max_dropped_couplingis notNoneor a finite number>= 0; a name selection is invalid (a bare string, an emptystates, a duplicate, or a non-string entry); a requested name is missing/ambiguous; a name resolves to a different channel (or different units) across operating points; an operating point’s azimuth files disagree on the channel layout or the operating condition (wind or rotor speed) or contain a duplicate azimuth; the schedule is non-finite or not increasing; or the state selection drops more coupling thanmax_dropped_couplingallows.
- Return type:
Safe, provenance-stamped save/reload for estimator results and model families.
An estimator’s outputs are evidence: a filtered trajectory or a scheduled run must be
reproducible and archivable, not recomputed from memory. This
module serializes the vane.sysid result dataclasses, the tracked
RomFamily and the physical-state
LPVFamily to a single .npz file each and loads
them back exactly.
The format is deliberately safe and transparent: the numeric arrays are stored as
plain NumPy arrays and every other field as a JSON metadata blob, so a result is loaded
with allow_pickle=False – it can carry no executable payload, unlike a pickle or
joblib artifact. Each file also records the result type and the VANE version that
produced it, so an archived result is traceable to the code that made it.
- LPV_FAMILY_SCHEMA_VERSION = 1
Schema version of the LPV-family archive written by
save_lpv_family().load_lpv_family()reads only this version and names both versions when an archive records another one.
- load_lpv_family(path)[source]
Load a physical-state LPV family saved by
save_lpv_family().The archive is opened with
allow_pickle=False. Its format tag and schema version are checked first, then every array and metadata field, and the rebuilt family is validated as on saving, so a file that loads is a family thatsave_lpv_family()could have written. The arrays, names, scheduling variable and unit, auto-added states and dropped-coupling norms come back exactly as saved.- Parameters:
path (str or os.PathLike) – The
.npzfile written bysave_lpv_family()(the suffix is added if absent).- Returns:
vane.sysid.physical_lpv.LPVFamily – The reconstructed family.
- Raises:
ValueError – If the file is not a VANE LPV-family archive, records no schema version or a schema other than
LPV_FAMILY_SCHEMA_VERSION(the message names both versions and the VANE version that wrote the file), or is incomplete, has unexpected entries or holds an invalid family.- Return type:
- load_result(path)[source]
Load an estimator result saved by
save_result().Reads the archive with
allow_pickle=False(so a tampered file can carry no executable payload), reconstructs the recorded result type, and returns it exactly as saved – arrays, names, flags, and the nested consistency report.- Parameters:
path (str or os.PathLike) – The
.npzfile written bysave_result().- Returns:
FilterResult or ScheduledFilterResult – The reconstructed result.
- Raises:
ValueError – If the file is not a VANE estimator-result archive or records an unknown type.
- Return type:
- load_rom_family(path)[source]
Load a
RomFamilywritten bysave_rom_family()safely.An archive without the
RomFamily.stabilizedmask loads with an all-Falseone.
- save_lpv_family(family, path)[source]
Save a physical-state
LPVFamilyto.npz.The archive holds the scheduling points, the per-point dropped-coupling norms and each system’s matrices as plain arrays under explicit indexed keys, and every other field (channel names, the scheduling variable and its unit, the auto-added states, each system’s names, sample time and matrix presence) as JSON metadata. It is stamped with the format tag,
LPV_FAMILY_SCHEMA_VERSIONand the producing VANE version. No object arrays are used, soload_lpv_family()opens it withallow_pickle=False.- Parameters:
family (vane.sysid.physical_lpv.LPVFamily) – The family, e.g. from
vane.export.build_physical_lpv_family().path (str or os.PathLike) – Destination file (a
.npzis written; the suffix is added if absent).
- Raises:
TypeError – If
familyis not exactly anLPVFamily, a system is not exactly aStateSpace, or a field has the wrong type.ValueError – If the family is empty, its arrays are non-finite or of the wrong length, the scheduling points are not strictly ascending, or the systems do not share one layout (matrix shapes, names and sample time).
- Return type:
None
- save_result(result, path)[source]
Save an estimator result to a
.npzfile.Writes the result’s NumPy arrays as plain arrays and the remaining fields (names, flags, the nested consistency report) as a JSON metadata blob, stamped with the result type and the producing VANE version. The file is self-describing and loads back with
load_result()without executing any embedded code.- Parameters:
result (FilterResult or ScheduledFilterResult) – The estimator result to serialize.
path (str or os.PathLike) – Destination file (a
.npzis written; the suffix is added if absent).
- Raises:
TypeError – If
resultis not a registered estimator-result type, or carries a field that cannot be serialized.- Return type:
None
- save_rom_family(family, path)[source]
Save a tracked
RomFamilyas a transparent pickle-free archive.Each state-space matrix is stored under an explicit indexed key; names, sample times, and optional-matrix presence are JSON metadata. No object arrays are used, so
load_rom_family()always opens the archive withallow_pickle=False. The per-mode reflection maskRomFamily.stabilizedis stored alongside the modal metadata.
Periodic systems
Rotor-azimuth-periodic ROMs in one fixed modal coordinate system.
Reducing every azimuth independently produces arbitrarily re-phased modal states that cannot be scheduled safely. This module instead takes one accepted averaged modal reduction and projects every retained per-azimuth MBC Jacobian through that same left/ right basis. The resulting matrices form a linear time-periodic (LTP) family whose state has a consistent meaning around the full revolution.
- class PeriodicStateSpace(azimuths_deg, systems)[source]
Bases:
objectA full-revolution matrix family in a fixed reduced-state basis.
- Parameters:
azimuths_deg (npt.NDArray[np.float64])
systems (tuple[StateSpace, ...])
- azimuths_deg: npt.NDArray[np.float64]
- systems: tuple[StateSpace, ...]
- at(azimuth_deg)[source]
Linearly interpolate the periodic family, including the 360/0 seam.
- Parameters:
azimuth_deg (float)
- Return type:
- periodic_state_space_from_mbc(result, reduction)[source]
Project retained per-azimuth MBC matrices through one modal basis.
The averaged reduced
Ais retained as the stable reference and projected per-azimuth deviations are added to it. Thus any intentionally reflected unstable poles remain reflected while the measured cyclic Jacobian variation is preserved.The
B/C/Ddeviations are mapped onto the reduction’s channels by input name and by output name plus MBC coordinate, so a reduction whose outputs or inputs are a reordering of the MBC result’s is remapped rather than corrupted. When either side is unnamed the channels are matched by position and only the counts are checked.- Parameters:
result (MBCResult) – An MBC result built with per-azimuth matrices retained.
reduction (ModalReduction) – A modal reduction of the result’s averaged system, with its projection maps.
- Returns:
PeriodicStateSpace – One reduced member per azimuth in the reduction’s fixed basis and channel order.
- Raises:
ValueError – If required averaged or per-azimuth matrices are missing, dimensions disagree, or the reduction’s input/output layout is not a permutation of the MBC result’s input/output descriptions (and output MBC coordinates).
- Return type:
Loads and frequency response
Quantity-of-interest (load) map from a linearized output equation.
A linearization’s output equation \(y = C x + D u\) already contains the sensitivity of every requested output — including structural loads such as mooring fairlead/anchor tensions, tower-base and yaw-bearing moments, and blade-root moments — to the system state and inputs. This module turns that into an explicit, auditable quantity-of-interest (QoI) map: for each load output it exposes
the state sensitivity (its row of \(C\)): how each state moves the load;
the input sensitivity (its row of \(D\)): the direct feed-through from the inputs (wind, wave, control) to the load, bypassing the dynamics; and
the modal residues \(C_i \Phi\): how each identified mode contributes to the load. Writing the state in modal coordinates \(x = \Phi q\) gives \(y_i = (C_i \Phi)\, q\), so the residue of mode \(k\) is the complex gain with which that mode appears in load \(i\). This is the bridge from a modal identification to load monitoring: it answers which modes drive this load.
The modal residues are taken over the retained (under-damped) modes of a
ModalSolution; over-damped/rigid-body modes and the
direct \(D u\) feed-through are not part of that modal sum and are reported
separately (the input sensitivity). The map is a static, deterministic property of
the linear model — the reduced-order foundation a downstream estimator consumes.
- class QoiMap(sensitivities, state_names, input_names, mode_frequencies_hz=None)[source]
Bases:
objectA map of output channels to their state/input/modal sensitivities.
- Parameters:
sensitivities (list[QoiSensitivity]) – One entry per included output channel.
state_names (list[str]) – State descriptions indexing
state_sensitivity.input_names (list[str]) – Input descriptions indexing
input_sensitivity.mode_frequencies_hz (npt.NDArray[np.float64] or None) – Natural frequencies of the modes indexing
modal_residues, orNonewhen no modal solution was supplied.
- sensitivities: list[QoiSensitivity]
- by_category(category)[source]
Return the sensitivities of a given output category.
- Parameters:
category (OutputCategory)
- Return type:
- get(name)[source]
Return the sensitivity whose channel name equals
name.
- modal_participation_table()[source]
Return a long-format table of modal contributions to each load.
Each row is one (load, mode) pair with the residue magnitude and phase and the per-load normalized magnitude (the fraction relative to that load’s strongest mode). Empty when no modal residues are available.
- Returns:
pandas.DataFrame – Columns
load,category,unit,mode,frequency_hz,residue_magnitude,residue_phase_deg,normalized.- Return type:
pd.DataFrame
- class QoiSensitivity(name, info, output_index, state_sensitivity, input_sensitivity, modal_residues, mbc_coordinate='')[source]
Bases:
objectSensitivity of one output channel to the states, inputs, and modes.
- Parameters:
name (str) – The output channel description.
info (OutputInfo) – Its classification (category, load flag, component, unit, …).
output_index (int) – Row index of this channel in
C/D.state_sensitivity (npt.NDArray[np.float64]) – The channel’s row of
C, shape(n_states,): the linear sensitivity of the output to each state.input_sensitivity (npt.NDArray[np.float64] or None) – The channel’s row of
D, shape(n_inputs,), orNonewhen the system has no feed-through matrix. The direct input-to-output gain.modal_residues (npt.NDArray[np.complex128] or None) – The complex modal residues \(C_i \Phi\), shape
(n_modes,), orNonewhen no modal solution was supplied. The contribution of each retained mode to this output.mbc_coordinate (str) – The multi-blade coordinate of the channel (
"collective","cosine", or"sine") when it is a transformed blade-triplet output, else"". A non-empty value means this row is a multi-blade combination rather than the literal per-blade channel its name suggests, soinfo.componentis cleared toNone.
- info: OutputInfo
- state_sensitivity: npt.NDArray[np.float64]
- dominant_modes(top_n=3)[source]
Return the indices of the modes that contribute most to this output.
- Parameters:
top_n (int) – Number of leading modes to return.
- Returns:
npt.NDArray[np.int_] – Mode indices ordered by descending residue magnitude (empty when no modal residues are available).
- Return type:
npt.NDArray[np.int_]
- build_qoi_map(system, solution=None, *, output_mbc_coordinates=None, loads_only=True)[source]
Build the quantity-of-interest map from a state-space system.
Note
This is a modal-residue screening map — which modes drive which load channels in the linearized model — not a validated load ranking. The residues are in the linearization’s output units (and an MBC-averaged frame for rotating loads); they are not fatigue/DEL, design-load, or extreme-load magnitudes and must not be used as a substitute for a load-analysis or assessment calculation. Use it to decide which modes a downstream estimator should retain, then validate the estimator against time-domain or measured loads.
- Parameters:
system (StateSpace) – A system carrying an output matrix
Cand named outputs. The optional feed-throughDsupplies the input sensitivities.solution (ModalSolution or None, optional) – A modal solution of the same system; when given, the modal residues \(C_i \Phi\) are computed from its full eigenvectors so each load’s modal composition is available.
output_mbc_coordinates (list[str] or None, optional) – The multi-blade coordinate of each output, aligned with the system outputs. When
None(the default) the system’s ownoutput_mbc_coordinatesare used, whichstate_space_from_mbc()populates — so an MBC-transformed system is handled correctly without any extra argument. A rotating blade-load output is a collective/cyclic combination, so when a channel’s coordinate is multi-blade its per-blade component index is cleared and the coordinate is recorded on the sensitivity. Pass an explicit list to override. Non-rotating loads (mooring, tower, platform) are unaffected.loads_only (bool, optional) – When
True(the default) include only channels classified as structural loads; whenFalseinclude every output channel.
- Returns:
QoiMap – The per-output sensitivities.
- Raises:
ValueError – If the system has no
Cmatrix or no output names, the output-name count disagrees withC,output_mbc_coordinateshas the wrong length, or the modal solution’s eigenvectors do not span the system states.- Return type:
- qoi_map_from_mbc(result, solution=None, *, loads_only=True)[source]
Build a QoI map directly from an MBC transform result.
A thin convenience over
build_qoi_map(state_space_from_mbc(result), ...). The exported state-space carries the output multi-blade coordinates, so a rotating blade-load output is correctly identified as a multi-blade coordinate rather than mislabeled as a literal per-blade channel.- Parameters:
result (MBCResult) – An MBC transform result with an averaged output matrix.
solution (ModalSolution or None, optional) – A modal solution of the same averaged system, for the modal residues.
loads_only (bool, optional) – Restrict to structural-load channels (the default).
- Returns:
QoiMap – The per-output sensitivities, with transformed blade outputs tagged by their multi-blade coordinate.
- Return type:
Frequency-domain transfer and stochastic input-to-QoI propagation.
For a linear state-space model, the complex transfer matrix is
with \(s=j2\pi f\) for a continuous model and \(s=\exp(j2\pi f\Delta t)\) for a discrete model. If the input cross-spectral density is \(S_u(f)\), the model-implied output spectrum is
This is the auditable bridge between an input-estimation error spectrum and the motion/load quantities affected by that error. It does not consult output truth and does not claim nonlinear or out-of-support validity; those remain validation tasks.
- class CrossSpectralDensity(frequencies_hz, density, channel_names)[source]
Bases:
objectNamed one-sided cross-spectral-density matrix.
densityhas shape(n_frequencies, n_channels, n_channels)and follows the covariance conventionS[i, j] = E[X_i conj(X_j)].- Parameters:
- frequencies_hz: npt.NDArray[np.float64]
- density: npt.NDArray[np.complex128]
- class FrequencyResponse(frequencies_hz, response, input_names, output_names)[source]
Bases:
objectNamed complex input-to-output response on a frequency grid.
responsehas shape(n_frequencies, n_outputs, n_inputs).- Parameters:
- frequencies_hz: npt.NDArray[np.float64]
- response: npt.NDArray[np.complex128]
- frequency_response(system, frequencies_hz, *, input_channels=None, output_channels=None)[source]
Evaluate selected complex state-space transfers.
- Parameters:
system (StateSpace) – Continuous or discrete state-space model with
BandCmatrices.frequencies_hz (npt.ArrayLike) – Strictly increasing nonnegative physical frequencies in Hz. A discrete model is restricted to its Nyquist interval.
input_channels (Sequence[int | str] | None) – Optional unique indices or unambiguous names. By default all channels are retained.
output_channels (Sequence[int | str] | None) – Optional unique indices or unambiguous names. By default all channels are retained.
- Return type:
- integrated_spectral_variance(spectrum, *, band_hz=None)[source]
Integrate each autospectrum over a declared frequency band.
Returns one nonnegative variance per channel. The trapezoidal integral runs exactly from
band_hz[0]toband_hz[1]: the autospectra are linearly interpolated at band limits that fall between spectrum bins, so a band edge is not truncated to the nearest interior bin. The band is clipped to the spectrum’s frequency range, because the density is undefined outside it, and the clipped band must retain a positive width; this avoids silently reporting a zero-width integral. Withoutband_hzthe whole spectrum grid is integrated.- Parameters:
spectrum (CrossSpectralDensity)
- Return type:
npt.NDArray[np.float64]
- propagate_cross_spectral_density(response, spectrum)[source]
Propagate
S_uthroughHasS_y = H S_u H^H.- Parameters:
response (FrequencyResponse)
spectrum (CrossSpectralDensity)
- Return type:
- welch_cross_spectral_density(signals, *, sample_interval_s, segment_samples=None, overlap_samples=None, channel_names=None)[source]
Estimate a one-sided cross-spectral-density matrix using Welch averaging.
Samples are rows and channels are columns. A one-dimensional input is treated as one channel. Cross terms are retained, so correlated torque/thrust errors are propagated without the independence assumption.
- Parameters:
- Return type:
Estimation
Ready-to-use Kalman-filter model assembly.
A KalmanModel bundles a state-space system with its process,
measurement, and initial covariances into the exact matrices a Kalman filter
consumes. The system can be discretized at a chosen sample time during assembly,
and the covariance dimensions are validated against the system so the resulting
model is internally consistent.
- class KalmanModel(a, b, c, d, q, r, p0, dt=None, state_names=<factory>, output_names=<factory>)[source]
Bases:
objectA state-space system with Kalman covariances, ready for filtering.
- Parameters:
a (npt.NDArray[np.float64]) – State matrix, shape
(n_states, n_states).b (npt.NDArray[np.float64] or None) – Input, output, and feedthrough matrices.
c (npt.NDArray[np.float64] or None) – Input, output, and feedthrough matrices.
d (npt.NDArray[np.float64] or None) – Input, output, and feedthrough matrices.
q (npt.NDArray[np.float64]) – Process-noise, measurement-noise, and initial state covariances.
r (npt.NDArray[np.float64]) – Process-noise, measurement-noise, and initial state covariances.
p0 (npt.NDArray[np.float64]) – Process-noise, measurement-noise, and initial state covariances.
dt (float or None) – Sample time;
Nonefor a continuous-time model.state_names (list[str]) – State and output names carried from the source system.
output_names (list[str]) – State and output names carried from the source system.
- a: npt.NDArray[np.float64]
- q: npt.NDArray[np.float64]
- r: npt.NDArray[np.float64]
- p0: npt.NDArray[np.float64]
- build_kalman_model(state_space, covariances, *, dt=None)[source]
Assemble a Kalman model from a state-space system and covariances.
- Parameters:
state_space (StateSpace) – The continuous- or discrete-time system.
covariances (CovarianceSet) – The process, measurement, and initial covariances.
Qis used exactly as given and must already be the discrete per-step covarianceQ_dfor the model’s sample time.dt (float or None, optional) – When given and the system is continuous, discretize
A/Bat this sample time before assembly.Qis not discretized: convert a continuous-timeQ_c(e.g. fromvane.sysid.modal_process_noise()) withvane.sysid.discretize_process_noise()at the samedtfirst.
- Returns:
KalmanModel – The assembled, dimension-checked model.
- Raises:
ValueError – If
dtis given for an already-discrete system, the covariance dimensions do not match the system, or a covariance is not a valid covariance matrix (Q/P0must be finite, symmetric, and positive semidefinite;Rmust be finite, symmetric, and positive definite).- Return type:
Runnable, auditable Kalman estimator over a KalmanModel.
The rest of vane.sysid builds and confirms an estimator-ready model; this
module runs it. Given the discrete model and its covariances, KalmanFilter
performs the recursive predict/update, with the properties a load-estimation digital
twin needs to be trusted in the field:
Numerically stable – the gain is formed by a Cholesky solve (never an explicit inverse) and the covariance by the Joseph form, so the error covariance stays symmetric positive-semidefinite even for ill-conditioned innovations.
Tolerant of real measurement streams – each step accepts an
availablemask, so a dropped or missing sensor updates on the channels that reported; an optional NIS gate rejects outliers (a measurement whose normalized innovation squared exceeds a chi-square threshold) and the step is flagged rather than corrupting the estimate.Zero-order-hold control timing – consistent with
discretized(), the prediction into stepkuses the previous sample’s control and the control logged with the measurement enters only the feedthrough: \(x_k = A x_{k-1} + B u_{k-1}\), \(y_k = C x_k + D u_k\).Self-diagnosing – every step records the innovation and its normalized innovation squared (NIS);
filter_consistency()checks the NIS sequence against its chi-square expectation so a mis-tunedQ/Ris visible, not silent.Auditable offline –
rts_smoother()refines a recorded run with the Rauch-Tung-Striebel backward pass, andreconstruct_load()reads a linear load functional off the filtered state with its propagated standard deviation.
SteadyStateKalmanFilter runs the same recursion with the constant steady-state
gain (from steady_state_estimator()), trading per-step Riccati updates
for a fixed-gain filter once the covariance has converged.
- class ConsistencyReport(mean_nis, expected_nis, lower, upper, consistent, n_accepted, n_gated=0)[source]
Bases:
objectWhether a run’s innovations are statistically consistent with the model.
- Parameters:
mean_nis (float) – Average normalized innovation squared over the tested steps – every step with a finite NIS, including steps the outlier gate rejected (their NIS is computed before the gate decision).
expected_nis (float) – The expected mean NIS under a correctly tuned filter – the average number of measurement channels per tested step (
dof / (n_accepted + n_gated)).lower (float) – The two-sided chi-square acceptance bounds for
mean_nisat the chosen level.upper (float) – The two-sided chi-square acceptance bounds for
mean_nisat the chosen level.consistent (bool) – Whether
mean_nislies within[lower, upper];Falseflags a mis-tunedQ/R(over- or under-confident filter) or a record with more outliers than the model’s noise explains.n_accepted (int) – Number of accepted (applied) steps included in the statistic.
n_gated (int, optional) – Number of gated-out (rejected) steps included in the statistic (default
0).
- class FilterResult(states, covariances, predicted_states, predicted_covariances, nis, accepted, n_active, state_names=<factory>)[source]
Bases:
objectThe recorded trajectory of a Kalman run, for diagnostics and smoothing.
- Parameters:
states (npt.NDArray[np.float64]) – Filtered (a-posteriori) state means, shape
(n_steps, n_states).covariances (npt.NDArray[np.float64]) – Filtered error covariances, shape
(n_steps, n_states, n_states).predicted_states (npt.NDArray[np.float64]) – A-priori (predicted) state means before each update, same shape as
states; retained so the RTS smoother can run without re-filtering.predicted_covariances (npt.NDArray[np.float64]) – A-priori error covariances, shape
(n_steps, n_states, n_states).nis (npt.NDArray[np.float64]) – Normalized innovation squared at each step (
nanwhere no measurement was available; a step rejected by the NIS gate keeps its finite value).accepted (npt.NDArray[np.bool]) – Whether each step’s measurement was applied (
Falsewhen fully masked or rejected by the NIS gate).n_active (npt.NDArray[np.int]) – Number of measurement channels in each step’s innovation (its NIS degrees of freedom), recorded for a gated-out step too;
0only where no channel was available. Equalsn_outputsunless a partialavailablemask reduced it.state_names (list[str]) – Names of the states, aligned with the last axis of
states.
- states: npt.NDArray[np.float64]
- covariances: npt.NDArray[np.float64]
- predicted_states: npt.NDArray[np.float64]
- predicted_covariances: npt.NDArray[np.float64]
- nis: npt.NDArray[np.float64]
- accepted: npt.NDArray[np.bool_]
- n_active: npt.NDArray[np.int_]
- class KalmanFilter(model, *, initial_state=None, initial_control=None)[source]
Bases:
objectA recursive discrete Kalman filter over a
KalmanModel.Holds the current state estimate
xand error covariancePand advances them withpredict()andupdate()(orstep()for both). The model must be discrete (dtset) and carry aCmatrix.Control timing (zero-order hold). The discrete model produced by
discretized()holds each input constant over the sample interval that follows it, so the sampled plant is\[\mathbf x_k = \mathbf A\mathbf x_{k-1} + \mathbf B\mathbf u_{k-1} + \mathbf w_{k-1}, \qquad \mathbf y_k = \mathbf C\mathbf x_k + \mathbf D\mathbf u_k + \mathbf v_k,\]with
u_kthe input logged alongside the measurementy_k.step()therefore predicts with the previous sample’s control and uses the current one only in the feedthroughD u_k. The initial(x0, P0)is the prior one step before the first measurement; the control driving that first prediction isinitial_controlwhen given, and otherwise the first control passed tostep()(the input is taken as held at its first logged value before the record, so a constant input is filtered exactly).- Parameters:
model (KalmanModel) – The discrete model and its
Q,R,P0covariances.initial_state (npt.NDArray[np.float64] or None, optional) – Initial state mean
x0(the prior one step before the first measurement); defaults to zeros.initial_control (npt.NDArray[np.float64] or None, optional) – The input
u_{-1}held over the interval before the first measurement, which drives the firststep()’s prediction.None(default) uses the first control passed tostep()(pass zeros for a plant at rest before the record).
- Raises:
ValueError – If the model is continuous (no
dt), has noCmatrix,initial_statedoes not match the state dimension, orinitial_controlis mis-sized, non-finite, or given for a model with neitherBnorD.
- x: npt.NDArray[np.float64]
- predict(control=None)[source]
Advance one step with the control held over this interval.
\(x \leftarrow A x (+ B u)\), \(P \leftarrow A P A^{\mathsf T} + Q\).
This is the raw transition:
controlis the input held (zero-order hold) over the interval being predicted – for the interval ending at measurementkthat is the previous sample’s inputu_{k-1}, notu_k.step()andrun_filter()apply this timing automatically.- Parameters:
control (npt.NDArray[np.float64] or None, optional) – The held input;
Nonefor no input over the interval.- Raises:
ValueError – If
controlis mis-sized, non-finite, or given for a model with neitherBnorD.- Return type:
None
- update(measurement, *, control=None, available=None, gate=None, gate_probability=None)[source]
Correct the state with a measurement; return its NIS, acceptance, and dof.
The predicted measurement is \(C x (+ D u_k)\), where
u_kis the control logged with this measurement (not the one that drove the prediction), so a model with a nonzero feedthroughDrequires it. Uses only the available output channels (a dropped sensor is simply omitted), a Cholesky solve for the gain, and the Joseph-form covariance update. With a gate set, a measurement whose normalized innovation squared exceeds the threshold is rejected (the state is left at its prediction) and reported as not accepted.- Parameters:
measurement (npt.NDArray[np.float64]) – The measured outputs, length
n_outputs; entries at masked-out channels are ignored.control (npt.NDArray[np.float64] or None, optional) – The control input logged with this measurement, used to subtract the feedthrough
D ufrom the predicted measurement. Required (with a nonzeroDmatrix) for a correct innovation; ignored if the model has noD.available (npt.NDArray[np.bool] or None, optional) – Boolean mask of reporting channels (length
n_outputs);Noneuses every channel.gate (float or None, optional) – A fixed chi-square NIS threshold for outlier rejection, applied as is whatever the number of reporting channels. Prefer
gate_probabilitywhen channels can drop out.gate_probability (float or None, optional) – Outlier-gate acceptance probability
pin(0, 1): the threshold ischi2.ppf(p, n_used)for then_usedchannels that reported, so a correctly tuned filter rejects a fraction1 - pof steps regardless of dropout. Mutually exclusive withgate; neither applies every update.
- Returns:
tuple[float, bool, int] – The normalized innovation squared (
nanif no channel was available), whether the update was applied, and the number of channels in the innovation – the NIS degrees of freedom, reported for a gated-out step too;0only when no channel was available.- Raises:
ValueError – If both gates are given,
gateis not a positive finite threshold,gate_probabilityis not in(0, 1),measurementis not lengthn_outputsor has non-finite available channels, or (with a nonzeroD)controlis missing, mis-sized, or non-finite.- Return type:
- step(measurement, control=None, *, available=None, gate=None, gate_probability=None)[source]
Predict then update; return the update’s NIS, acceptance, and dof.
controlis the inputu_klogged withmeasurement. Under the zero-order-hold convention (see the class docstring) the prediction is driven by the previous step’s controlu_{k-1}(on the first step,initial_controlor elseu_kitself), andu_kenters only the feedthroughD u_k; it is then held for the next step’s prediction.- Parameters:
measurement (npt.NDArray[np.float64]) – The measured outputs at this step.
control (npt.NDArray[np.float64] or None, optional) – The input logged with this measurement;
Nonefor no input.available (optional) – As for
update().gate (optional) – As for
update().gate_probability (optional) – As for
update().
- Returns:
tuple[float, bool, int] – As for
update().- Return type:
- class SteadyStateKalmanFilter(a, c, gain, *, b=None, d=None, initial_state=None, state_names=None, initial_control=None)[source]
Bases:
objectA constant-gain Kalman filter using the precomputed steady-state gain.
Once the error covariance has converged (the steady-state DARE solution from
steady_state_estimator()), the gain is constant, so each step is a matrix-vector predict/correct with no per-step Riccati update – cheaper and ideal for an embedded twin. The covariance is fixed at the steady-state value.It follows the same zero-order-hold control timing as
KalmanFilter: the prediction into stepkuses the previous step’s controlu_{k-1}(on the first step,initial_controlor else that step’s own control), and the control logged with the measurement enters only the feedthroughD u_k.- Parameters:
a (npt.NDArray[np.float64]) – Discrete state matrix.
c (npt.NDArray[np.float64]) – Output matrix.
gain (npt.NDArray[np.float64]) – The steady-state Kalman gain
K(EstimatorAssessment.gain).b (npt.NDArray[np.float64] or None, optional) – Input matrix, for control inputs into the state.
d (npt.NDArray[np.float64] or None, optional) – Feedthrough matrix; when given,
D uis subtracted from the measurement so a controlled model withy = C x + D uis filtered correctly.initial_state (npt.NDArray[np.float64] or None, optional) – Initial state mean (the prior one step before the first measurement); defaults to zeros.
state_names (list[str] or None, optional) – Names of the states.
initial_control (npt.NDArray[np.float64] or None, optional) – The input held over the interval before the first measurement (drives the first prediction);
None(default) uses the first step’s own control.
- Raises:
ValueError – If the gain shape is inconsistent with
a/c,initial_statedoes not match the state dimension, orinitial_controlis mis-sized, non-finite, or given for a model with neitherbnord.
- x: npt.NDArray[np.float64]
- step(measurement, control=None)[source]
Predict then correct with the constant gain; return the new state estimate.
\(x^- = A x (+ B u_{k-1})\), then \(x = x^- + K (z - C x^- (- D u_k))\), with
u_k = controlthe input logged withmeasurementandu_{k-1}the previous step’s control (the zero-order-hold timing ofKalmanFilter).- Parameters:
measurement (npt.NDArray[np.float64])
control (npt.NDArray[np.float64] | None)
- Return type:
npt.NDArray[np.float64]
- run(measurements, *, controls=None)[source]
Filter a measurement sequence; return state estimates
(n_steps, n).Row
kofcontrolsis the input logged withmeasurements[k]; the zero-order-hold timing ofstep()applies (continuing from any previousstep()calls on this filter).- Raises:
ValueError – If
measurementsis not 2-D withn_outputscolumns, orcontrolshas a different number of rows.- Parameters:
measurements (npt.NDArray[np.float64])
controls (npt.NDArray[np.float64] | None)
- Return type:
npt.NDArray[np.float64]
- filter_consistency(result, *, confidence=0.95)[source]
Check a run’s NIS sequence against its chi-square expectation (NIS test).
For a correctly tuned filter each step’s NIS, computed from the predicted covariance before any gate decision, is chi-square distributed with as many degrees of freedom as channels reported, so the sum over the tested steps is chi-square with
dof= the total number of channels in those innovations (result.n_activesummed) – a partialavailablemask, which lowers a step’s dof, is accounted for. Steps rejected by an outlier gate are included: keeping only the accepted ones would test a chi-square truncated at the gate, whose mean is lower, so a correctly tuned, gated filter would be reported as under-confident. A mean NIS far outside the acceptance band means the filter is over-confident (R/Qtoo small) or under-confident (too large) – a tuning fault that would otherwise be invisible – or that the record holds more outliers than the model’s noise explains.- Parameters:
result (FilterResult) – A trajectory from
run_filter().confidence (float, optional) – Two-sided confidence level for the acceptance band (default
0.95).
- Returns:
ConsistencyReport – The mean NIS, its expectation, the acceptance band, and the outcome.
- Raises:
ValueError – If no step has a finite NIS to test, or
confidenceis not in(0, 1).- Return type:
Notes
A step with a finite NIS but
n_active == 0(a gated step recorded without the degrees of freedom of its rejected innovation) carries no degrees of freedom and is left out.
- reconstruct_load(result, load_map, *, smoothed=None)[source]
Read a linear load off the estimated state with its propagated uncertainty.
For a load functional \(z = L x\), returns the reconstructed load \(\hat z_k = L \hat x_k\) and its standard deviation \(\sqrt{\operatorname{diag}(L P_k L^{\mathsf T})}\) at each step – the virtual sensor and its error bar.
- Parameters:
result (FilterResult) – A trajectory from
run_filter().load_map (npt.NDArray[np.float64]) – The load functionals
L, shape(n_loads, n_states).smoothed (tuple or None, optional) – A
(states, covariances)pair fromrts_smoother()to read the load from the smoothed estimate instead of the filtered one.
- Returns:
tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]] – The reconstructed loads
(n_steps, n_loads)and their standard deviations(n_steps, n_loads).- Raises:
ValueError – If
load_mapdoes not match the state dimension or is non-finite.- Return type:
tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]
- rts_smoother(model, result)[source]
Refine a filtered run with the Rauch-Tung-Striebel backward pass.
The smoother conditions each state on the whole record (not just the past), which an offline audit can afford: it lowers the variance and removes the filter’s lag. The control inputs enter only through the run’s recorded predictions (
result.predicted_states, formed with the zero-order-hold timing ofrun_filter()), so the backward pass needs no controls of its own.- Parameters:
model (KalmanModel) – The same discrete model the run used (for
A).result (FilterResult) – A trajectory from
run_filter().
- Returns:
tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]] – The smoothed state means
(n_steps, n_states)and covariances(n_steps, n_states, n_states).- Return type:
tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]
- run_filter(model, measurements, *, controls=None, available=None, gate=None, gate_probability=None, initial_state=None, initial_control=None)[source]
Run a Kalman filter over a measurement sequence and record the trajectory.
Control timing (zero-order hold). Row
kofcontrolsis the inputu_klogged alongsidemeasurements[k]. Consistent with the ZOH discretization, the recursion is\[\mathbf x_k^- = \mathbf A\mathbf x_{k-1}^+ + \mathbf B\mathbf u_{k-1}, \qquad \nu_k = \mathbf z_k - (\mathbf C\mathbf x_k^- + \mathbf D\mathbf u_k),\]i.e. the prediction into step
kuses the previous sample’s control and the current control enters only the feedthrough.initial_state(with the model’sP0) is the prior at step-1, one step beforemeasurements[0]; the first prediction is driven byinitial_control(u_{-1}), which defaults tocontrols[0]– the input held at its first logged value before the record, so a constant input gives exactly the same result as holding it throughout.- Parameters:
model (KalmanModel) – The discrete model and covariances.
measurements (npt.NDArray[np.float64]) – Measured outputs, shape
(n_steps, n_outputs).controls (npt.NDArray[np.float64] or None, optional) – Control inputs logged with the measurements, shape
(n_steps, n_inputs);Nonefor an autonomous model.available (npt.NDArray[np.bool] or None, optional) – Per-step channel mask, shape
(n_steps, n_outputs);Noneuses all channels at every step. Use this to encode sensor dropout.gate (float or None, optional) – A fixed chi-square NIS threshold for per-step outlier rejection (not scaled by the number of reporting channels).
gate_probability (float or None, optional) – Outlier-gate acceptance probability in
(0, 1); each step’s threshold is the chi-square quantile for its number of reporting channels. Mutually exclusive withgate.initial_state (npt.NDArray[np.float64] or None, optional) – Prior state mean one step before
measurements[0]; defaults to zeros.initial_control (npt.NDArray[np.float64] or None, optional) – The input
u_{-1}driving the first prediction; defaults tocontrols[0]. Pass zeros for a plant at rest (no input) before the record. Requirescontrols.
- Returns:
FilterResult – The filtered and predicted state means/covariances, per-step NIS, and acceptance flags.
- Raises:
ValueError – If
measurementsis not 2-D withn_outputscolumns,controls/availablelengths disagree with it,initial_controlis given withoutcontrols, or the gate arguments are invalid.- Return type:
Kalman covariance initialization, informed by mode-identification uncertainty.
A Kalman filter needs a process-noise covariance Q, a measurement-noise
covariance R, and an initial state covariance P0. When the modes were
classified with uncertainty quantification, that uncertainty is propagated into
P0: a mode identified with low confidence is given a larger initial variance,
so the filter trusts well-identified modes more than ambiguous ones.
- class CovarianceSet(q, r, p0)[source]
Bases:
objectKalman-filter covariance matrices.
- Parameters:
q (npt.NDArray[np.float64]) – Process-noise covariance, shape
(n_states, n_states).r (npt.NDArray[np.float64]) – Measurement-noise covariance, shape
(n_outputs, n_outputs).p0 (npt.NDArray[np.float64]) – Initial state covariance, shape
(n_states, n_states).
- q: npt.NDArray[np.float64]
- r: npt.NDArray[np.float64]
- p0: npt.NDArray[np.float64]
- covariances_from_confidence(confidences, n_outputs, *, process_noise=0.0001, measurement_noise=0.01, base_variance=1.0, uncertainty_scale=10.0)[source]
Build covariances whose
P0grows with mode-identification uncertainty.Each mode maps to two modal states (real and imaginary parts); the initial variance of those states is
base_variance * (1 + uncertainty_scale * (1 - confidence)), so a low-confidence mode starts with a larger covariance.- Parameters:
confidences (Sequence[float]) – Per-mode identification confidence in
[0, 1]; values are clipped to that range.n_outputs (int) – Number of measured outputs.
process_noise (float, optional) – Diagonal values for
QandR; must be non-negative.measurement_noise (float, optional) – Diagonal values for
QandR; must be non-negative.base_variance (float, optional) – Initial variance of a fully confident mode; must be non-negative.
uncertainty_scale (float, optional) – How strongly uncertainty inflates the initial variance; must be non-negative.
- Returns:
CovarianceSet – The covariance matrices, with
2 * len(confidences)states.- Raises:
ValueError – If a noise value,
base_variance,uncertainty_scale, orn_outputsis negative or non-finite, or a confidence is non-finite.- Return type:
- initialize_covariances(n_states, n_outputs, *, process_noise=0.0001, measurement_noise=0.01, initial_variance=1.0)[source]
Build uniform diagonal covariance matrices.
- Parameters:
n_states (int) – Number of states.
n_outputs (int) – Number of measured outputs.
process_noise (float, optional) – Diagonal values for
Q,R, andP0respectively; all must be non-negative.measurement_noise (float, optional) – Diagonal values for
Q,R, andP0respectively; all must be non-negative.initial_variance (float, optional) – Diagonal values for
Q,R, andP0respectively; all must be non-negative.
- Returns:
CovarianceSet – The diagonal covariance matrices.
- Raises:
ValueError – If a dimension is negative, or a noise value or
initial_varianceis negative or non-finite.- Return type:
Process- and measurement-noise covariances for the discrete estimator.
A Kalman filter on a discretized model needs a discrete process-noise covariance \(\mathbf Q_d\) consistent with the continuous model it came from, not a hand-picked diagonal. This module builds the covariances the reduced estimator consumes:
discretize_process_noise()converts a continuous process-noise spectral density \(\mathbf Q_c\) to the exact discrete covariance \(\mathbf Q_d=\int_0^{\Delta t}e^{\mathbf A s}\mathbf Q_c e^{\mathbf A^{\mathsf T} s}\,\mathrm ds\) over one step, by the Van Loan block-exponential identity — the discretization that matches the zero-order-holdA_dthe state space already uses.modal_process_noise()assembles a block-diagonal continuous \(\mathbf Q_c\) for the real modal realization (two states per mode), one intensity per mode, so the per-mode process noise can be set from each mode’s damping and identification uncertainty rather than a single global value.measurement_noise_from_specs()turns per-channel sensor noise standard deviations into the diagonal measurement-noise covariance \(\mathbf R\).
Together they replace the placeholder diagonal Q/R with covariances traceable to
the continuous model and the sensor specifications.
- discretize_process_noise(a, spectral_density, dt)[source]
Discretize a continuous process-noise spectral density over one step.
Computes the discrete process-noise covariance
\[\mathbf Q_d = \int_0^{\Delta t} e^{\mathbf A s}\,\mathbf Q_c\, e^{\mathbf A^{\mathsf T} s}\,\mathrm d s\]exactly, by the Van Loan block-exponential identity: for \(\mathbf F = \begin{bmatrix}-\mathbf A & \mathbf Q_c\\ \mathbf 0 & \mathbf A^{\mathsf T}\end{bmatrix}\), with \(e^{\mathbf F\Delta t}=\begin{bmatrix}\mathbf G_{11}&\mathbf G_{12}\\ \mathbf 0&\mathbf G_{22}\end{bmatrix}\), the discrete-time transition is \(\mathbf A_d=\mathbf G_{22}^{\mathsf T}\) (equal to the zero-order-hold \(e^{\mathbf A\Delta t}\)) and \(\mathbf Q_d=\mathbf A_d\mathbf G_{12}\). This is the process-noise companion to the ZOH discretization of
(A, B).The block exponentiates \(-\mathbf A\), which grows for stable modes; for a stiff system that intermediate overflows before the cancellation. To stay finite and well conditioned for stiff, near-marginal, and mixed systems alike, the step is split into \(2^k\) equal sub-steps small enough that the block is well behaved, and the sub-step covariance is recombined by doubling, \(\mathbf Q_d(2\tau)=\mathbf Q_d(\tau)+\boldsymbol\Phi(\tau)\mathbf Q_d(\tau) \boldsymbol\Phi(\tau)^{\mathsf T}\) with \(\boldsymbol\Phi=e^{\mathbf A\tau}\). No stability is required.
- Parameters:
a (npt.NDArray[np.float64]) – Continuous-time system matrix, shape
(n_states, n_states).spectral_density (npt.NDArray[np.float64]) – Continuous process-noise spectral density
Q_c, a symmetric positive semidefinite matrix of shape(n_states, n_states).dt (float) – Sample step
Δt(must be positive and finite).
- Returns:
npt.NDArray[np.float64] – The discrete process-noise covariance
Q_d, a symmetric(n_states, n_states)matrix.- Raises:
ValueError – If
ais not square,spectral_densitydoes not matcha, is not a valid (symmetric positive-semidefinite) covariance, ordtis not positive and finite.- Return type:
npt.NDArray[np.float64]
- measurement_noise_from_specs(noise_std)[source]
Build the measurement-noise covariance from per-channel noise specs.
Each sensor’s noise standard deviation maps to a variance on the diagonal of
R, so the measurement-noise covariance is traceable to the instrument specifications rather than a single global guess.- Parameters:
noise_std (Sequence[float]) – Per-channel noise standard deviation (one per sensor), all non-negative, in the sensor’s measurement units.
- Returns:
npt.NDArray[np.float64] – The diagonal measurement-noise covariance
R = diag(noise_std**2)of shape(n_sensors, n_sensors).- Raises:
ValueError – If any standard deviation is negative or non-finite.
- Return type:
npt.NDArray[np.float64]
- modal_process_noise(intensities)[source]
Build a block-diagonal continuous process-noise density over modal states.
The real modal realization carries two states per mode; this places one intensity on each mode’s two states, giving the continuous spectral density
Q_cfordiscretize_process_noise(). Setting a larger intensity on a less-trusted or more lightly damped mode lets the filter admit more drift there.- Parameters:
intensities (Sequence[float]) – Per-mode process-noise intensity (one value per mode), all non-negative.
- Returns:
npt.NDArray[np.float64] – The block-diagonal spectral density
Q_cof shape(2 * n_modes, 2 * n_modes), withintensities[k]on the two states of modek.- Raises:
ValueError – If any intensity is negative or non-finite.
- Return type:
npt.NDArray[np.float64]
Unknown-input (disturbance) augmentation for load virtual sensing.
A reduced model reconstructs states from sensors, but the quantity a digital twin ultimately wants is often an unknown input — the wave/wind load or mooring force that drives the structure and is never measured. The standard way to estimate it is to append a model of the disturbance to the state space and let the filter estimate the augmented state; the disturbance estimate is then read straight off it.
Given a physical continuous model
and a disturbance shaping model \(\dot{\mathbf z} = \mathbf A_d\mathbf z + \mathbf w\), \(\mathbf d = \mathbf C_d\mathbf z\) (white \(\mathbf w\)), the augmented system on \([\mathbf x; \mathbf z]\) is
with the disturbance recovered from the augmented state by \(\hat{\mathbf d} = \begin{bmatrix}\mathbf 0 & \mathbf C_d\end{bmatrix} [\mathbf x; \mathbf z]\).
random_walk_disturbance() models a slowly-varying (quasi-constant) unknown load;
harmonic_disturbance() models a narrowband load at a known frequency — the natural
choice for wave-frequency mooring and platform loads. augment_with_disturbance()
assembles the augmented state space, the disturbance-recovery map, and the augmented
process-noise spectral density (ready for discretize_process_noise()).
This construction is deterministic and auditable, so it lives in the model factory and
not ad hoc in the estimator runtime.
- class AugmentedModel(system, recovery, spectral_density, n_disturbance_states)[source]
Bases:
objectA state space augmented with a disturbance model and the recovery map.
- Parameters:
system (StateSpace) – The augmented continuous state space on
[x; z](physical states first, then disturbance states), with the physical inputs and outputs preserved.recovery (npt.NDArray[np.float64]) – The disturbance-recovery map, shape
(n_signals, n_aug_states); the disturbance estimate isd_hat = recovery @ x_aug.spectral_density (npt.NDArray[np.float64]) – The augmented continuous process-noise spectral density, shape
(n_aug_states, n_aug_states)— the physical process noise (or zero) on the physical block and the disturbanceintensityon the disturbance block.n_disturbance_states (int) – Number of appended disturbance states.
- system: StateSpace
- recovery: npt.NDArray[np.float64]
- spectral_density: npt.NDArray[np.float64]
- class DisturbanceModel(a, c, intensity, state_names)[source]
Bases:
objectA linear shaping model of an unknown disturbance/load.
The disturbance
dis the output of a small linear system driven by white noise,z' = a z + w,d = c z, withwa continuous white process of spectral densityintensity. Differenta/cgive different prior shapes (a random walk, a harmonic oscillator, …).- Parameters:
a (npt.NDArray[np.float64]) – Disturbance internal dynamics, shape
(n_dist_states, n_dist_states).c (npt.NDArray[np.float64]) – Disturbance output map, shape
(n_signals, n_dist_states);d = c zhasn_signalschannels.intensity (npt.NDArray[np.float64]) – Continuous process-noise spectral density of the disturbance states, a symmetric positive-semidefinite
(n_dist_states, n_dist_states)matrix.state_names (list[str]) – Names of the disturbance states (length
n_dist_states; may be empty).
- a: npt.NDArray[np.float64]
- c: npt.NDArray[np.float64]
- intensity: npt.NDArray[np.float64]
- augment_with_disturbance(system, disturbance, *, entry, output_entry=None, process_noise=None)[source]
Append a disturbance model to a state space for unknown-input estimation.
Builds the augmented continuous system on
[x; z]with the disturbance entering the physical dynamics throughentry(x' += entry @ d). An optionaloutput_entrypreserves direct disturbance-to-output feedthrough (y += output_entry @ d). The physical inputs and outputs are otherwise preserved, and the disturbance is recovered from the augmented state.- Parameters:
system (StateSpace) – A continuous-time physical model (
dtisNone).disturbance (DisturbanceModel) – The disturbance shaping model (e.g. from
random_walk_disturbance()orharmonic_disturbance()).entry (npt.NDArray[np.float64]) – Input map of the disturbance into the physical state derivative, shape
(n_states, n_signals)wheren_signalsisdisturbance.c’s row count. Often a column block ofB(the load enters where a known input does).output_entry (npt.NDArray[np.float64] or None, optional) – Direct disturbance-to-output map, shape
(n_outputs, n_signals). This is the disturbance column block removed from the physical system’sDmatrix when an originally known input is promoted to an augmented state.Nonemeans there is no direct disturbance feedthrough.process_noise (npt.NDArray[np.float64] or None, optional) – Continuous process-noise spectral density of the physical states, shape
(n_states, n_states).Noneuses zero physical process noise.
- Returns:
AugmentedModel – The augmented system, the disturbance-recovery map, the augmented process-noise spectral density, and the disturbance-state count.
- Raises:
ValueError – If
systemis discrete,entry/output_entrydo not match the physical system and disturbance signals, orprocess_noisehas the wrong shape.- Return type:
- combine_disturbance_models(disturbances)[source]
Combine independent disturbance models without changing their signals.
The returned shaping model is the block-diagonal union of the supplied dynamics and process-noise intensities. Its output matrix is block diagonal as well, so the physical disturbance signals remain ordered by model and are not summed. This is useful for mixed priors such as aerodynamic torque as a random walk and incident wave elevation as a narrowband oscillator.
- Parameters:
disturbances (Sequence[DisturbanceModel]) – Non-empty sequence of validated shaping models.
- Returns:
DisturbanceModel – Independent block-diagonal union of every state and signal channel.
- Return type:
- gauss_markov_disturbance(time_constants_s, intensity, *, n_signals=1, name='load')[source]
Model bounded load perturbations as independent first-order processes.
Each signal follows
z' = -z / tau + wandd = z. The operating-point baseline is supplied by the surrounding linear model, so the prior mean remains zero in perturbation coordinates whiletauprevents an unconstrained random walk from drifting indefinitely. This is suitable for scheduled controller-regulated loads such as above-rated aerodynamic torque.- Parameters:
time_constants_s (float or Sequence[float]) – Positive finite mean-reversion time constant per signal in seconds.
intensity (float or Sequence[float]) – Non-negative continuous process-noise intensity per signal.
n_signals (int, optional) – Number of independent signals; default one.
name (str, optional) – Base name for the disturbance states.
- Return type:
- harmonic_disturbance(frequencies_hz, intensity, *, name='wave')[source]
Model unknown loads as narrowband oscillators at known frequencies.
Each frequency contributes a two-state undamped oscillator
[[0, ω], [-ω, 0]](ω = 2π f) driven by white noise, with the disturbance read from its first state. This is the natural prior for a wave-frequency load whose frequency is known but whose amplitude and phase are not.- Parameters:
frequencies_hz (Sequence[float]) – Oscillator frequencies in Hz, all finite and positive. One disturbance channel (and two states) per frequency.
intensity (float or Sequence[float]) – Continuous process-noise intensity driving each oscillator (applied to both of its states); a scalar applies to all, or one value per frequency. Non-negative.
name (str, optional) – Base name for the disturbance states (
"{name}_{i}_{re,im}").
- Returns:
DisturbanceModel – A harmonic disturbance model with
2 * len(frequencies_hz)states and one output channel per frequency.- Raises:
ValueError – If no frequencies are given, any frequency is non-finite or not positive,
intensityhas the wrong length, or any intensity is negative.- Return type:
- random_walk_disturbance(intensity, *, n_signals=1, name='load')[source]
Model unknown loads as independent random walks (quasi-constant inputs).
Each disturbance channel is integrated white noise (
z' = w,d = z), the standard prior for a slowly-varying or constant unknown input. One disturbance state per channel.- Parameters:
intensity (float or Sequence[float]) – Continuous process-noise intensity of each random walk; a scalar applies to all channels, or one value per channel. All values must be non-negative.
n_signals (int, optional) – Number of independent disturbance channels (default 1).
name (str, optional) – Base name for the disturbance states (
"{name}_{i}").
- Returns:
DisturbanceModel – A random-walk disturbance model with
n_signalsstates.- Raises:
ValueError – If
n_signalsis not positive,intensityhas the wrong length, or any intensity is negative.- Return type:
- random_walk_harmonic_disturbance(frequencies_hz, random_walk_intensity, harmonic_intensity, *, name='load')[source]
Model one unknown load as a DC random walk plus scheduled harmonics.
Unlike
harmonic_disturbance(), whose frequencies are separate disturbance channels, this construction sums every component into one physical signal:d = z_dc + sum(z_h,re). It is intended for a rotor load containing a slowly varying component together with known 1P/3P orders. Amplitudes and phases remain estimated states; only the physics-derived frequencies are prescribed.- Parameters:
frequencies_hz (Sequence[float]) – Positive harmonic frequencies in hertz.
random_walk_intensity (float) – Continuous process-noise intensity of the DC random-walk state.
harmonic_intensity (float or Sequence[float]) – Continuous process-noise intensity per harmonic oscillator. A scalar broadcasts; each value is applied to both quadrature states.
name (str, optional) – Base name for the DC and harmonic states.
- Return type:
Observability analysis, sensor placement, and the UQ-initialized reduced estimator.
The reduced-order model (vane.sysid.rom) keeps the modes that drive a load; this
module answers the two questions a load estimator then poses: which sensors observe
those modes, and how to assemble the estimator with its uncertainty baked in.
observability_gramian()is the (infinite-horizon) observability Gramian \(\mathbf W_o\) of a stable system — the Lyapunov solution of \(\mathbf A^{\mathsf T}\mathbf W_o + \mathbf W_o\mathbf A + \mathbf C^{\mathsf T} \mathbf C = 0\) (continuous) — whose spectrum measures how strongly each state direction is seen at the outputs. With a measurement-noise covariance it becomes the noise-weighted (Fisher) information Gramian using \(\mathbf C^{\mathsf T}\mathbf R^{-1}\mathbf C\).finite_horizon_observability_gramian()is the observability Gramian accumulated over a finite sensing window \([0, T]\) — the relevant measure for short-window estimation and the only one defined when the system carries marginal or unstable modes (the infinite-horizon Gramian does not exist there). It is computed exactly: by the Van Loan block-matrix-exponential identity in continuous time, and by direct summation in discrete time.observability_scores()breaks the Gramian down into per-output scores (how much each candidate sensor sees) and per-mode scores (how strongly each identified mode is observed), tying the identified modal content to the sensor layout.load_reconstructability()answers the load-estimator question directly: given a named load output (not necessarily a sensor) and a sensor set, what fraction of that load lies in the observable subspace, and what is the a-priori (Cramér-Rao-type) variance of reconstructing it — the assessment a virtual-sensing twin needs before it runs a single time step.recommend_sensors()chooses, from a set of candidate output channels, the subset that maximises observability, by greedy D-optimal (log-determinant of \(\mathbf W_o\)) selection — the standard sensor-placement objective, which rewards covering all observable directions rather than piling sensors on the same one.reduced_estimator()assembles a Kalman-ready model on the reduced system whose initial covariance is inflated by each mode’s confidence — a less-trusted mode starts with larger variance — tying the reduction, the per-mode uncertainty, and the Kalman export into one auditable estimator.
- class ConstrainedPlacement(selected, names, scores, total_cost)[source]
Bases:
objectA sensor selection that honours deployment constraints, and its cost.
- Parameters:
selected (list[int]) – The chosen output (sensor) row indices: the required sensors first (in the order given), then the greedily added ones.
names (list[str]) – The output names of the chosen sensors (empty strings if unnamed).
scores (list[float]) – The observability metric (log-determinant of the Gramian) after each selection, aligned with
selected.total_cost (float) – The summed cost of the selected sensors.
- class GroupConstraint(members, max_count)[source]
Bases:
objectA cap on how many sensors may be chosen from one group of channels.
- Parameters:
- class LoadReconstructability(load_name, reconstructable_fraction, blind_fraction, observability, noise_amplification, n_observable_directions)[source]
Bases:
objectWhether and how well a target load can be reconstructed from a sensor set.
- Parameters:
load_name (str) – The name (or
"output[i]") of the target load output.reconstructable_fraction (float) – Fraction in
[0, 1]of the load output direction that lies in the observable subspace of the sensor set, measured in the Euclidean metric of the model’s state coordinates.1means the load is fully reconstructable; a value below1means a part of the load lives in the sensors’ blind subspace and cannot be recovered at any noise level. Only that outcome (1or not) is independent of the state basis: the value of a partial fraction depends on the state coordinates and units (a similarity transform or rescaling of the states changes it – a purely blind load can read close to1in skewed coordinates), because splitting the load between the observable and blind subspaces requires a state inner product. Do not rank or threshold on a partial value; usenoise_amplification(basis-independent) instead.blind_fraction (float) –
1 - reconstructable_fraction— the irrecoverable share of the load, with the same coordinate dependence.observability (float) – The observed energy per unit load,
c_load Wo c_load^T / (c_load c_load^T); larger means the load aligns with strongly observed directions. Like the fraction, it is normalised in the state coordinates and so is basis-dependent.noise_amplification (float) – The a-priori (Cramér-Rao-type) variance scale of reconstructing the load from the sensor information over the window,
c_load Wo^+ c_load^Tover the observable subspace. With a measurement-noise covariance supplied this is the best achievable static reconstruction variance; smaller is better. It isinfwheneverreconstructable_fraction < 1— any blind component is irrecoverable at any noise level, so the variance is undefined (reported as infinite so a smaller-is-better ranking never prefers a blind load). It is invariant under any invertible change of state coordinates (c_load Wo^+ c_load^Tis unchanged whenx = T x'), so it is the measure to compare sensor sets or models with.n_observable_directions (int) – The rank of the sensor observability Gramian (the dimension of the observable subspace).
- class ObservabilityScores(per_output, output_names, per_mode)[source]
Bases:
objectPer-output and per-mode breakdown of how strongly a system is observed.
- Parameters:
per_output (npt.NDArray[np.float64]) – For each output row, the observability energy that single sensor provides (the trace of the Gramian built from that row alone), in the output order. A near-zero entry marks a sensor that sees almost nothing.
output_names (list[str]) – The output names aligned with
per_output(empty strings if unnamed).per_mode (npt.NDArray[np.float64]) – For each mode group in
mode_states, the observability energy of that mode (the trace of the Gramian’s diagonal block over the mode’s states), in the group order. Empty when no mode grouping was supplied.
- per_output: npt.NDArray[np.float64]
- per_mode: npt.NDArray[np.float64]
- class SensorPlacement(selected, names, scores)[source]
Bases:
objectA recommended set of sensors and the observability gained at each step.
- Parameters:
selected (list[int]) – The chosen output (sensor) row indices, in the order they were selected.
names (list[str]) – The output names of the chosen sensors (empty strings if unnamed).
scores (list[float]) – The observability metric (log-determinant of the Gramian) after each selection — a non-decreasing record of the observability gained.
- constrained_sensor_selection(system, *, candidates=None, required=(), forbidden=(), costs=None, budget=None, max_sensors=None, groups=None, measurement_noise=None, horizon=None)[source]
Greedy D-optimal sensor selection under deployment constraints.
Extends
recommend_sensors()with the constraints a real instrumentation plan faces: sensors that must or must not be used, a per-sensor cost and a total budget, a cap on the total count, and caps on how many sensors may come from each group (e.g. one component, or a set of redundant channels). Required sensors are taken first; the rest are added greedily by the largest log-determinant gain among the channels that keep every constraint satisfied, until none remain feasible.- Parameters:
system (StateSpace) – A system with a
Cmatrix. Must be asymptotically stable unless a finitehorizonis given.candidates (Sequence[int] or None, optional) – Output row indices that may be instrumented.
Noneconsiders every output.required (Sequence[int], optional) – Output rows that must be selected (taken first, even if outside
candidates).forbidden (Sequence[int], optional) – Output rows that must never be selected.
costs (Sequence[float] or None, optional) – Per-output cost (length
n_outputs), all non-negative.Nonetreats every sensor as free (cost 0).budget (float or None, optional) – Maximum total cost of the selection.
Noneimposes no budget.max_sensors (int or None, optional) – Maximum number of sensors.
Noneimposes no count cap (the greedy loop then runs until no feasible candidate remains).groups (Sequence[GroupConstraint] or None, optional) – Per-group caps on the number of selected sensors.
measurement_noise (npt.NDArray[np.float64] or None, optional) – Full
(n_outputs, n_outputs)measurement-noise covarianceR; each selected sensor is weighted by its inverse noise so a noisy channel ranks below a calibrated one.Noneweights every channel equally.horizon (float or None, optional) – Finite sensing window
T.Noneuses the infinite-horizon Gramian (stable systems only); a finite horizon also admits marginal/augmented models.
- Returns:
ConstrainedPlacement – The selected sensors (required first, then greedily added), their names, the observability metric after each selection, and the total cost.
- Raises:
ValueError – If the system has no
Cmatrix, is unstable with no finitehorizon, an index is out of range,requiredandforbiddenoverlap, a cost/budget/cap is negative,measurement_noiseis not a symmetric positive-definite(n_outputs, n_outputs)matrix, or the required sensors alone already violate the budget, count cap, or a group cap.- Return type:
- finite_horizon_observability_gramian(system, horizon, *, measurement_noise=None)[source]
Return the observability Gramian accumulated over a finite window
[0, T].The finite-horizon observability Gramian is
\[\mathbf W_o(T) = \int_0^T e^{\mathbf A^{\mathsf T}\tau}\,\mathbf M\, e^{\mathbf A\tau}\,\mathrm d\tau \quad\text{(continuous)}, \qquad \mathbf W_o(N) = \sum_{k=0}^{N-1}(\mathbf A^{\mathsf T})^k\,\mathbf M\, \mathbf A^k \quad\text{(discrete)},\]with source \(\mathbf M = \mathbf C^{\mathsf T}\mathbf C\) (or its noise-weighted form \(\mathbf C^{\mathsf T}\mathbf R^{-1}\mathbf C\)). Unlike the infinite-horizon Gramian it requires no stability assumption — it is the right observability measure for a short sensing window and for systems that retain marginal or unstable (e.g. aero) modes. The continuous integral is evaluated exactly by the Van Loan block-matrix-exponential identity; the discrete sum, directly.
- Parameters:
system (StateSpace) – A system with a
Cmatrix (need not be stable).horizon (float) – The sensing window length
Tin seconds (must be positive and finite). For a discrete system (dtset) it is converted toN = round(T / dt)steps (at least 1).measurement_noise (npt.NDArray[np.float64] or None, optional) – Measurement-noise covariance
R; seeobservability_gramian().
- Returns:
npt.NDArray[np.float64] – The
(n_states, n_states)finite-horizon observability Gramian.- Raises:
ValueError – If the system has no
Cmatrix,horizonis not positive and finite,measurement_noisehas the wrong shape, or the Gramian is not finite (an unstable mode growing beyond floating-point range over the window).- Return type:
npt.NDArray[np.float64]
Notes
The continuous block
[[-A^T, M], [0, A]]exponentiates-A, which grows for every stable mode, so over a whole window it is the fastest mode (largest|Re(lambda)| T) that overflows the intermediate exponential, not the slowest. The window is therefore split into2^kequal sub-intervalstaushort enough thatmax |Re(lambda)| tau <= 1; the sub-interval Gramian is recombined by doubling,W(2 tau) = W(tau) + Phi(tau)^T W(tau) Phi(tau)withPhi = expm(A tau), which stays finite for any stable mode and any window. A genuinely unstable mode can still make the Gramian itself overflow over a long window; that raisesValueErrorrather than returninginf/nan. For a stable system over an effectively infinite horizon useobservability_gramian(), which the finite-horizon Gramian converges to.
- load_reconstructability(system, load_output, *, sensor_outputs=None, measurement_noise=None, horizon=None)[source]
Assess whether a target load is reconstructable from a sensor set.
A load estimator measures a few sensor channels and reports a different output — the load. That load is recoverable only if its output direction \(\mathbf c_{\text{load}}\) lies in the observable subspace of the sensors, the range of the sensor observability Gramian \(\mathbf W_o\) (the orthogonal complement of the unobservable subspace, \(\ker\mathbf W_o\)). This function reports that projection and the a-priori reconstruction variance — an assessment the downstream twin can check before running any filter.
Whether the load is reconstructable, and its
noise_amplification, are independent of the state basis. The size of a partial projection (reconstructable_fractionbelow1) andobservabilityare measured in the Euclidean metric of the state coordinates and change under a similarity transform or state rescaling; compare sensor sets bynoise_amplification.- Parameters:
system (StateSpace) – A system with a
Cmatrix whose rows include both the sensors and the load. Must be asymptotically stable unless a finitehorizonis given.load_output (int or str) – The target load output (row index, or a name matched exactly then by substring).
sensor_outputs (Sequence[int] or None, optional) – The output rows used as measurements.
Noneuses every output row except the load row (so the load is never measured to reconstruct itself). An explicit set must not containload_output.measurement_noise (npt.NDArray[np.float64] or None, optional) – Measurement-noise covariance
Rof the sensor rows (shape(n_sensors, n_sensors)). When given, the Gramian is the Fisher information andnoise_amplificationis the best achievable static reconstruction error.horizon (float or None, optional) – Finite sensing window
T;Noneuses the infinite-horizon Gramian, which requires a stable system.
- Returns:
LoadReconstructability – The reconstructable fraction, blind fraction, observed energy, a-priori variance scale, and the observable-subspace dimension.
- Raises:
ValueError – If the system has no
Cmatrix, the load row is zero, an index is out of range,sensor_outputsincludes the load output or leaves no sensor rows,horizonis non-positive, the system is unstable with no finite horizon, ormeasurement_noiseis the wrong shape.- Return type:
- observability_gramian(system, *, measurement_noise=None)[source]
Return the infinite-horizon observability Gramian \(\mathbf W_o\).
Solves the Lyapunov equation \(\mathbf A^{\mathsf T}\mathbf W_o + \mathbf W_o\mathbf A = -\mathbf M\) (continuous) or \(\mathbf A^{\mathsf T}\mathbf W_o\mathbf A - \mathbf W_o = -\mathbf M\) (discrete), where the source is \(\mathbf M = \mathbf C^{\mathsf T}\mathbf C\) or, when
measurement_noiseis given, the noise-weighted information \(\mathbf M = \mathbf C^{\mathsf T}\mathbf R^{-1}\mathbf C\). The Gramian is symmetric positive semidefinite; a near-singular Gramian signals weakly- or un-observable state directions.- Parameters:
system (StateSpace) – A stable system with a
Cmatrix.measurement_noise (npt.NDArray[np.float64] or None, optional) – Measurement-noise covariance
R(shape(n_outputs, n_outputs)). When given, each output is inverse-noise weighted, so the Gramian becomes the Fisher information available to an estimator.Noneuses unit weighting.
- Returns:
npt.NDArray[np.float64] – The
(n_states, n_states)observability Gramian.- Raises:
ValueError – If the system has no
Cmatrix, is not asymptotically stable, ormeasurement_noisehas the wrong shape.- Return type:
npt.NDArray[np.float64]
See also
finite_horizon_observability_gramianthe finite-window Gramian, defined even when the system has marginal or unstable modes.
- observability_scores(system, *, mode_states=None, measurement_noise=None, horizon=None)[source]
Break the observability Gramian down per output and per mode.
Two diagnostics drawn from one Gramian:
per output — for each output row \(\mathbf c_i\), the trace of the Gramian built from that row alone, i.e. the observability energy that single sensor provides. This ranks candidate sensors by how much they see.
per mode — for each state group in
mode_states, the trace of the Gramian’s diagonal block over those states. For the real modal realization ofmodal_reduction()(two states per mode, groups[[0, 1], [2, 3], ...]) this diagonal block equals that mode’s own single-mode Gramian exactly, so it measures how strongly each identified mode is observed — connecting the modal content to the sensor layout.
- Parameters:
system (StateSpace) – A system with a
Cmatrix. Must be asymptotically stable unless a finitehorizonis given.mode_states (Sequence[Sequence[int]] or None, optional) – State-index groups, one per mode.
Noneskips the per-mode breakdown.measurement_noise (npt.NDArray[np.float64] or None, optional) – Measurement-noise covariance
R; per-output scores use each channel’s own varianceR[i, i]; per-mode scores use the fullC^T R^-1 Cinformation.horizon (float or None, optional) – Finite sensing window
T;Noneuses the infinite-horizon Gramian (which requires a stable system). Seefinite_horizon_observability_gramian().
- Returns:
ObservabilityScores – The per-output and (optional) per-mode observability energies.
- Raises:
ValueError – If the system has no
Cmatrix,horizonis non-positive, the system is unstable with no finite horizon,measurement_noiseis the wrong shape, or a mode-state index is out of range.- Return type:
- recommend_sensors(system, *, n_sensors, candidates=None, measurement_noise=None, horizon=None)[source]
Recommend sensors that best observe a system, by greedy D-optimal selection.
Greedily adds, from the candidate output channels, the one that most increases the log-determinant of the observability Gramian built from the selected rows of
C. D-optimality rewards covering all observable directions, so it prefers complementary sensors over redundant ones. Intended for the reduced model (seemodal_reduction()), where the candidates are the instrumentable channels and the modes are those that drive the load of interest.- Parameters:
system (StateSpace) – A system with a
Cmatrix. Must be asymptotically stable unless a finitehorizonis given.n_sensors (int) – Number of sensors to recommend (capped at the number of candidates).
candidates (Sequence[int] or None, optional) – Output row indices that may be instrumented.
Noneconsiders every output.measurement_noise (npt.NDArray[np.float64] or None, optional) – Full
(n_outputs, n_outputs)measurement-noise covarianceR. When given, each selected sensor is weighted by its inverse noise (the marginal blockR[rows, rows]), so a noisy channel ranks below a calibrated one instead of scoring identically.Noneweights every channel equally.horizon (float or None, optional) – Finite sensing window
T.Noneuses the infinite-horizon Gramian (which requires a stable system); a finite horizon also admits marginal/augmented models whose infinite-horizon Gramian does not exist.
- Returns:
SensorPlacement – The selected sensors (in order), their names, and the observability metric after each selection.
- Raises:
ValueError – If the system has no
Cmatrix, is unstable with no finitehorizon,n_sensorsis not positive, a candidate index is out of range, ormeasurement_noiseis not a symmetric positive-definite(n_outputs, n_outputs)matrix.- Return type:
- reduced_estimator(reduction, *, sensor_outputs, dt, confidences=None, process_noise=0.0001, measurement_noise=0.01, base_variance=1.0, uncertainty_scale=10.0)[source]
Assemble a UQ-initialized Kalman model on a reduced modal system.
Builds a discrete Kalman model whose measurement matrix is the chosen sensor rows of the reduced system and whose initial covariance is inflated by each retained mode’s confidence (a less-trusted mode starts with larger variance, two states per mode). This is the reduced load estimator: reduction + per-mode uncertainty + Kalman export, in one object ready for a filter.
Note
process_noisehere is a hand-set discreteQdiagonal (a sensible starting placeholder), not a Van Loan discretization of a continuous spectral density: unlike the steady-state assessment path (discretize_process_noise()), it is used directly asQ_d, so it is not scaled todtor coupled throughA. For a model-consistentQ(a modal spectral density discretized againstA), build the model fromdiscretize_process_noise()instead and tune from there.- Parameters:
reduction (ModalReduction) – A reduced modal model from
modal_reduction().sensor_outputs (Sequence[int]) – Output row indices of the reduced system used as measurements (the sensors; e.g. from
recommend_sensors()).dt (float) – Sample time for the discrete estimator.
confidences (Sequence[float] or None, optional) – Per-mode confidence in
[0, 1](one per retained mode); a lower value inflates that mode’s initial covariance.Nonetrusts every mode (all ones).process_noise (float, optional) – Passed to
covariances_from_confidence().measurement_noise (float, optional) – Passed to
covariances_from_confidence().base_variance (float, optional) – Passed to
covariances_from_confidence().uncertainty_scale (float, optional) – Passed to
covariances_from_confidence().
- Returns:
KalmanModel – The dimension-checked, discretized Kalman model.
- Raises:
ValueError – If a sensor index is out of range, or
confidenceshas the wrong length.- Return type:
Online scheduled (LPV) estimator over a RomFamily.
build_rom_family() packages a turbine’s linearizations into one
mode-tracked, phase-aligned reduced model per operating point – the static half of a
linear-parameter-varying (LPV) estimator. This module is the runtime half: given a
scheduling parameter (wind speed, rotor speed, …) it selects and interpolates the
family into a single model at that operating point, and runs a Kalman filter over a
record whose operating point drifts, carrying the state and covariance across the sweep.
Two things make the interpolation legitimate. The family’s realizations are tracked into
a consistent state order (index k is the same physical mode everywhere),
phase-aligned and scale-normalized across points, so a model’s state vector
means the same thing at
adjacent operating points and the matrices may be blended entry-by-entry; the running
estimate’s state and covariance therefore stay valid as the schedule moves – no
discontinuous re-basing at a switch. And each tracked mode carries a tracking MAC
against its
neighbour, so where the tracking is uncertain (a crossing or veering region, or an
operating point outside the family’s range) the scheduler surfaces a low-confidence flag
rather than interpolating two unrelated modes silently.
This is the “run the estimator over a wind-speed sweep” product layer the ROM family was built for.
- class ScheduledFilterResult(states, covariances, predicted_states, predicted_covariances, nis, accepted, n_active, min_tracking_mac, extrapolated, state_names=<factory>)[source]
Bases:
objectThe trajectory of a scheduled (LPV) Kalman run, with per-step confidence.
Mirrors
FilterResultand adds the scheduling diagnostics.- Parameters:
states (npt.NDArray[np.float64]) – Filtered state means
(n_steps, n_states)and covariances(n_steps, n_states, n_states).covariances (npt.NDArray[np.float64]) – Filtered state means
(n_steps, n_states)and covariances(n_steps, n_states, n_states).predicted_states (npt.NDArray[np.float64]) – A-priori means/covariances before each update.
predicted_covariances (npt.NDArray[np.float64]) – A-priori means/covariances before each update.
nis (npt.NDArray[np.float64]) – Normalized innovation squared per step (
nanwhere no channel was available).accepted (npt.NDArray[np.bool]) – Whether each step’s measurement was applied.
n_active (npt.NDArray[np.int]) – Number of channels used at each accepted step.
min_tracking_mac (npt.NDArray[np.float64]) – The scheduled model’s lowest tracking MAC at each step (per-step confidence).
extrapolated (npt.NDArray[np.bool]) – Whether each step’s operating point fell outside the family range.
- states: npt.NDArray[np.float64]
- covariances: npt.NDArray[np.float64]
- predicted_states: npt.NDArray[np.float64]
- predicted_covariances: npt.NDArray[np.float64]
- nis: npt.NDArray[np.float64]
- accepted: npt.NDArray[np.bool_]
- n_active: npt.NDArray[np.int_]
- min_tracking_mac: npt.NDArray[np.float64]
- extrapolated: npt.NDArray[np.bool_]
- class ScheduledModel(system, operating_point, min_tracking_mac, extrapolated)[source]
Bases:
objectThe family interpolated to a single operating point, with a confidence flag.
- Parameters:
system (StateSpace) – The model at
operating_point– a linear blend of the bracketing family members (or the nearest member when the point is outside the family range). Same time domain (continuous/discrete) and I/O layout as the family.operating_point (float) – The scheduling-parameter value this model was interpolated to.
min_tracking_mac (float) – The lowest tracking MAC among the modes across the interpolated step – a measure of how trustworthy the blend is. A low value means a crossing/veering region where the modes may not correspond.
extrapolated (bool) –
Truewhenoperating_pointfell outside the family’s range and the model is the clamped nearest member (an extrapolation, not an interpolation).
- system: StateSpace
- run_scheduled_filter(family, covariances, measurements, operating_points, *, dt, controls=None, available=None, gate=None, gate_probability=None, initial_state=None, initial_control=None, mac_warn_threshold=0.8)[source]
Run a Kalman filter whose model is scheduled on a drifting operating point.
At each step the family is interpolated to that step’s
operating_pointsvalue (schedule_model()), discretized withdt, and used for one predict/update; the state and covariance carry across steps in the family’s consistent, phase-aligned basis, so a moving operating point needs no discontinuous re-basing. Control timing is zero-order hold, as inrun_filter(): the prediction into stepkis driven bycontrols[k-1](the first byinitial_control, defaulting tocontrols[0]), andcontrols[k]enters only the feedthrough of the measurement logged with it. The filter tuningcovariances(Q,R,P0) is held constant across the sweep – only the plant model is scheduled.- Parameters:
family (RomFamily) – A continuous-time mode-tracked family (members are discretized per step).
covariances (CovarianceSet) – The discrete process/measurement/initial covariances (
Q,R,P0).measurements (npt.NDArray[np.float64]) – Measured outputs, shape
(n_steps, n_outputs).operating_points (npt.NDArray[np.float64]) – The scheduling-parameter value at each step, shape
(n_steps,)– the time-varying operating condition (need not be monotonic).dt (float) – Discretization step for the scheduled model.
controls (npt.NDArray[np.float64] or None, optional) – Control inputs, shape
(n_steps, n_inputs); rowkis the input logged withmeasurements[k].Nonefor an autonomous model.available (npt.NDArray[np.bool] or None, optional) – Per-step channel mask (sensor dropout), shape
(n_steps, n_outputs).gate (float or None, optional) – Fixed chi-square NIS threshold for per-step outlier rejection.
gate_probability (float or None, optional) – Gate probability
p; each step is gated atchi2.ppf(p, n_used)for the channels that reported. Mutually exclusive withgate.initial_state (npt.NDArray[np.float64] or None, optional) – Initial state mean; defaults to zeros.
initial_control (npt.NDArray[np.float64] or None, optional) – The input held before
measurements[0](u_{-1}), which drives the first prediction; defaults tocontrols[0]. Requirescontrols.mac_warn_threshold (float, optional) – Tracking-MAC level below which a step is counted low-confidence (default 0.8).
- Returns:
ScheduledFilterResult – The filtered trajectory plus the per-step scheduling confidence.
- Raises:
ValueError – If the family is empty or discrete,
dtis not positive,measurementsis not(n_steps, n_outputs), oroperating_points/controls/availablelengths disagree with the step count,initial_controlis given withoutcontrols, or bothgateandgate_probabilityare set.- Return type:
- schedule_model(family, operating_point, *, mac_warn_threshold=0.8)[source]
Interpolate a ROM family to a single operating point.
Brackets
operating_pointbetween two family members and linearly blends theirA/B/C/Dmatrices (clamping to the nearest member outside the range). Because the family is mode-tracked and phase-aligned, the blended model is a valid state-space realization in the same state basis. The returnedScheduledModel.min_tracking_macreports the lowest tracking MAC across the interpolated step; a value belowmac_warn_threshold(or an extrapolation) logs a warning, because there the interpolation may mix modes that do not correspond.- Parameters:
- Returns:
ScheduledModel – The interpolated model and its confidence flags.
- Raises:
ValueError – If the family is empty, or
operating_point/mac_warn_thresholdis not finite.- Return type:
Steady-state estimator assessment: model-conditional reconstruction-error bounds.
Given the discrete model, the process-noise covariance and the measurement-noise
covariance, steady_state_estimator() solves the discrete
algebraic Riccati equation for the steady-state Kalman filter and returns the gain, the
predicted and filtered error covariances, and per-state and per-load error standard
deviations. assess_load() adds a pass or flag result
against a tolerance for each load.
Each reported standard deviation is exact for the supplied model and noise covariances. It is not a guarantee about the physical turbine: it holds only to the extent that the linearized model is accurate at the operating point, the supplied covariances are the true ones, the noises are zero-mean, white, Gaussian and mutually independent, and the sensors are calibrated, unbiased and make the pair detectable. Validate against nonlinear time-domain simulation or measured data before relying on it in operation.
- class EstimatorAssessment(gain, error_covariance, predicted_covariance, state_std)[source]
Bases:
objectThe steady-state Kalman filter and its predicted and filtered error covariances.
- Parameters:
gain (npt.NDArray[np.float64]) – The steady-state Kalman gain
K, shape(n_states, n_outputs).error_covariance (npt.NDArray[np.float64]) – The a-posteriori (filtered) steady-state estimation-error covariance
P+, shape(n_states, n_states)— the accuracy after each measurement update.predicted_covariance (npt.NDArray[np.float64]) – The a-priori (predicted) steady-state error covariance
P-(the DARE solution), shape(n_states, n_states).state_std (npt.NDArray[np.float64]) – Per-state a-posteriori error standard deviation,
sqrt(diag(P+)).
- gain: npt.NDArray[np.float64]
- error_covariance: npt.NDArray[np.float64]
- predicted_covariance: npt.NDArray[np.float64]
- state_std: npt.NDArray[np.float64]
- class LoadAssessment(load_name, error_std, tolerance, within_tolerance)[source]
Bases:
objectThe reconstruction-error bound for one target load.
- Parameters:
load_name (str) – The load’s name (or
"load[i]").error_std (float) – The steady-state filtered (a-posteriori) reconstruction-error standard deviation,
sqrt(L P+ L^T)— the load is reconstructable to±error_std.tolerance (float or None) – The acceptance tolerance the load was checked against (
Noneif only reported, not checked).within_tolerance (bool) – Whether the load met an explicit tolerance:
Trueonly when atolerancewas given anderror_std <= tolerance.Falsewhen the error exceeds the tolerance, and alsoFalsewhen no tolerance was given (the error is reported but not accepted). Usetolerance is Noneto distinguish “reported, unchecked” from “checked and failed”.
- steady_state_estimator(system, *, process_noise, measurement_noise)[source]
Solve the steady-state Kalman filter and its predicted/filtered covariances.
For the discrete model \(\mathbf x_{k+1}=\mathbf A_d\mathbf x_k+\mathbf w_k\), \(\mathbf y_k=\mathbf C\mathbf x_k+\mathbf v_k\) with \(\mathbf w\sim\mathcal N(0,\mathbf Q_d)\), \(\mathbf v\sim\mathcal N(0,\mathbf R)\), solves the discrete algebraic Riccati equation for the predicted error covariance \(\mathbf P^-=\mathbf A_d\mathbf P^+\mathbf A_d^{\mathsf T}+\mathbf Q_d\), then forms the gain \(\mathbf K=\mathbf P^-\mathbf C^{\mathsf T} (\mathbf C\mathbf P^-\mathbf C^{\mathsf T}+\mathbf R)^{-1}\) and the filtered covariance \(\mathbf P^+=(\mathbf I-\mathbf K\mathbf C)\mathbf P^-\).
- Parameters:
system (StateSpace) – A discrete-time system (
dtset) with aCmatrix.process_noise (npt.NDArray[np.float64]) – The discrete process-noise covariance
Q_d, shape(n_states, n_states), symmetric positive-semidefinite.measurement_noise (npt.NDArray[np.float64]) – The measurement-noise covariance
R, shape(n_outputs, n_outputs), symmetric positive-definite.
- Returns:
EstimatorAssessment – The steady Kalman gain and the steady-state error covariances.
- Raises:
ValueError – If the system is continuous, has no
Cmatrix, the covariances have the wrong shape or are not valid (Q_dPSD,RPD), or the filter has no steady-state solution ((A, C)not detectable from the sensors, or(A, Q^1/2)not stabilisable – a marginal mode with no process noise).- Return type:
- assess_load(assessment, load_map, *, tolerance=None, names=None)[source]
Bound the reconstruction error of each target load from the assessment.
For each load row \(\mathbf L_i\) (a linear functional of the state, e.g. a row of
Cfor a named output or the recovery map ofaugment_with_disturbance()), reports the steady-state filtered (a-posteriori) error standard deviation \(\sigma_i=\sqrt{\mathbf L_i\mathbf P^+\mathbf L_i^{\mathsf T}}\) and, if a tolerance is given, whether the load is reconstructable to within it.- Parameters:
assessment (EstimatorAssessment) – An assessment from
steady_state_estimator().load_map (npt.NDArray[np.float64]) – The load functionals
L, shape(n_loads, n_states);z = L x.tolerance (float or Sequence[float] or None, optional) – Acceptance tolerance on the error standard deviation; a scalar applies to every load, or one value per load.
Nonereports the error without checking.names (Sequence[str] or None, optional) – Per-load names (length
n_loads).Noneuses"load[i]".
- Returns:
list[LoadAssessment] – One assessment per load row.
- Raises:
ValueError – If
load_mapdoes not match the state dimension or is non-finite, ortolerance/nameshave the wrong length.- Return type:
Visualization
Visualization — Campbell plots, mode shape plots, damping plots.
Every figure follows one style, the plotting conventions of the Marine Structural
Mechanics and Integrity Lab (SMI Lab), Inha University: Matplotlib figures are drawn
inside plot_style(), Plotly figures with the registered "vane" template.
Static figures take a size= ("single", "double" or a width in cm), and
save_figure() writes them for publication.
Campbell-diagram plotting, interactive (Plotly) and static (Matplotlib).
Renders a CampbellDiagram: one line per tracked mode
(natural frequency versus the operating parameter), the nP excitation lines, and
markers at the detected resonance crossings. plot_campbell() returns an
interactive Plotly figure, plot_campbell_static() a Matplotlib figure for
publication, and plot_campbell_panels() a two-panel Matplotlib figure with the
frequencies and the damping ratios side by side.
The figures follow the lab’s Campbell conventions. Each track is coloured by its mode
family (blades green, tower black, platform red, drivetrain magenta, other modes cyan)
and the line style cycles within a family; the marker shows the multi-blade type. The
nP lines (“Blade Passing”) are thin and blue, tagged inline (3P) and kept out
of the legend. An optional operating window is shaded grey and marked with a labelled
double arrow. The static figures name each mode at its line end, in a column at the
right edge with a collision-free vertical placement, and keep the legend to a compact
key of the families and overlays. VANE adds resonance crossings (filled crosses
coloured by severity), confidence (reduced opacity), ambiguity (open diamonds) and
heavily damped tracks (dotted grey), each explained in the key. The interactive figure
has a legend grouped by family and a hover label on every point (mode, operating
point, frequency, damping, confidence).
- HEAVILY_DAMPED_COLOR = '#7f7f7f'
Line colour of the heavily damped (near-critical) tracks.
- SEVERITY_COLORS: dict[ResonanceSeverity, str] = {ResonanceSeverity.HIGH: '#d90000', ResonanceSeverity.LOW: '#8c8c8c', ResonanceSeverity.MEDIUM: '#ff8000'}
red, orange and grey.
- Type:
Marker colour for each resonance severity (more damped, less alarming)
- plot_campbell(diagram, *, harmonics=(1, 3, 6, 9), show_excitation=True, show_resonances=True, show_confidence=True, show_ambiguity=True, show_heavily_damped=True, operating_range=None, show_operating_range=True, figure=None, template=None, title=None)[source]
Plot a Campbell diagram as an interactive figure.
- Parameters:
diagram (CampbellDiagram) – The diagram to plot.
harmonics (Sequence[int], optional) – Excitation harmonics for the
nPlines and resonance markers (only used for rotor-speed diagrams).show_excitation (bool, optional) – Draw the
nPexcitation lines, tagged inline1P,3P, … (rotor-speed diagrams only). The legend has one “Blade Passing” entry for them.show_resonances (bool, optional) – Mark detected resonance crossings, coloured by severity (rotor-speed diagrams only).
show_confidence (bool, optional) – Scale each track’s line opacity by its identification confidence.
show_ambiguity (bool, optional) – Flag ambiguous tracks (open-diamond markers and an annotated legend name) and the ambiguous points of otherwise clear tracks (open-diamond markers at those points). The hover names each point’s own label, with its alternative when ambiguous.
show_heavily_damped (bool, optional) – Draw the diagram’s heavily damped tracks: dotted grey lines with open markers, named “(heavily damped)”, whose hover adds the damped frequency. Default
True;Falseleaves them out.operating_range (tuple[float, float] or None, optional) – The
(min, max)operating window of the parameter. Resonance detection is restricted to it, and it is shaded grey (seeshow_operating_range).show_operating_range (bool, optional) – Shade the operating window and name it (“Operating Speed Range” or “Operating Wind Range”). Default
True; it has no effect withoutoperating_range.figure (plotly.graph_objects.Figure or None, optional) – Draw into this figure instead of creating a new one (for composing subplots or overlays). The figure is returned.
template (str, plotly.graph_objects.layout.Template or None, optional) – Plotly layout template to apply.
None(the default) applies the VANE template"vane"; pass another name (for example"plotly_white") or a template object to override it.title (str or None, optional) – Figure title.
None(the default) draws none, leaving the caption to the document.
- Returns:
plotly.graph_objects.Figure – The Campbell-diagram figure, with the legend grouped by mode family.
- Return type:
go.Figure
- plot_campbell_panels(diagram, *, harmonics=(1, 3, 6, 9), layout='auto', as_percent=True, show_excitation=True, show_resonances=True, show_confidence=True, show_ambiguity=True, show_heavily_damped=True, show_stability_bands=True, caution_damping=0.01, operating_range=None, show_operating_range=True, style='vane', size=None, direct_labels=True)[source]
Plot frequencies and damping ratios of a Campbell diagram as two panels.
Panel (a) is the Campbell diagram (
plot_campbell_static()) and panel (b) the damping ratio (vane.viz.damping_plot.plot_damping_static()) against the same operating parameter. The panels share the operating-parameter axis and its label and carry(a)and(b)outside their top-left corners; one compact key in panel (a) explains the encoding of both. The mode names stand at the right edge: of panel (b) in the"row"layout and of both panels in the"column"layout.- Parameters:
diagram (CampbellDiagram) – The diagram to plot.
harmonics (Sequence[int], optional) – Excitation harmonics for panel (a).
layout ({"auto", "row", "column"}, optional) –
"row"places the panels side by side;"column"stacks them, frequency on top."auto"(the default) uses"row"for figures at least 12 cm wide and"column"for narrower ones.as_percent (bool, optional) – Plot damping in percent (the default) rather than as a fraction.
show_excitation (bool, optional) – As for
plot_campbell_static().show_resonances (bool, optional) – As for
plot_campbell_static().show_confidence (bool, optional) – As for
plot_campbell_static().show_ambiguity (bool, optional) – As for
plot_campbell_static().show_heavily_damped (bool, optional) – Draw the heavily damped tracks in panel (a).
show_stability_bands (bool, optional) – Shade the unstable and caution bands in panel (b).
caution_damping (float, optional) – Upper edge of the caution band as a damping fraction (default
0.01).operating_range (tuple[float, float] or None, optional) – The operating window: restricts resonance detection and is shaded in both panels (see
show_operating_range).show_operating_range (bool, optional) – Shade the operating window and mark it in panel (a). Default
True.style (str, dict, pathlib.Path or None, optional) – Matplotlib style, as for
plot_campbell_static().size (str, float, FigureSize or None, optional) – Figure size and fonts;
Noneuses"double"(17 cm wide), with a height suited to the layout.direct_labels (bool, optional) – Name the modes at their line ends (the default);
Falsedraws a per-track legend instead.
- Returns:
matplotlib.figure.Figure – The two-panel figure.
- Raises:
ValueError – If
layoutis not"auto","row"or"column", orcaution_dampingis negative.- Return type:
Figure
- plot_campbell_static(diagram, *, harmonics=(1, 3, 6, 9), show_excitation=True, show_resonances=True, show_confidence=True, show_ambiguity=True, show_heavily_damped=True, operating_range=None, show_operating_range=True, ax=None, style='vane', size=None, direct_labels=True, title=None)[source]
Plot a Campbell diagram as a static Matplotlib figure for publication.
The static counterpart of
plot_campbell(), with the same encoding. Each mode is named at its line end, in a column just right of the axes (inside the right edge when drawing into the caller’s axes), moved vertically only as far as needed to keep the names apart, with a thin leader back to the line. The frequency axis ends 30 % above the highest mode, so the steepnPlines run off the top; they are tagged inline at staggered heights. A compact key inside the axes explains the families, “Blade Passing” and any overlays used.- Parameters:
diagram (CampbellDiagram) – The diagram to plot.
harmonics (Sequence[int], optional) – Excitation harmonics for the
nPlines and resonance markers (only used for rotor-speed diagrams).show_excitation (bool, optional) – Draw the
nPexcitation lines (rotor-speed diagrams only).show_resonances (bool, optional) – Mark detected resonance crossings, coloured by severity (rotor-speed diagrams only).
show_confidence (bool, optional) – Scale each track’s line opacity by its identification confidence.
show_ambiguity (bool, optional) – Draw ambiguous tracks, and the ambiguous points of clear tracks, with open diamonds, and mark ambiguous tracks with
?in their labels.show_heavily_damped (bool, optional) – Draw the heavily damped tracks as dotted grey lines with open markers.
operating_range (tuple[float, float] or None, optional) – The
(min, max)operating window: restricts resonance detection and is shaded grey with a labelled double arrow (seeshow_operating_range).show_operating_range (bool, optional) – Shade and mark the operating window. Default
True.ax (matplotlib.axes.Axes or None, optional) – Draw into these axes instead of creating a new figure. The axes keep their own appearance (use
vane.viz.apply_style()to restyle them); the parent figure is returned.style (str, dict, pathlib.Path or None, optional) – Matplotlib style in effect while drawing.
"vane"(the default) is the VANE style (vane.viz.plot_style());Nonekeeps the currentrcParams(for example aplt.style.contextopened by the caller); any other value is passed tomatplotlib.style.context().size (str, float, FigureSize or None, optional) – Figure size and fonts (see
vane.viz.figure_size()):"single"(8.5 cm wide),"double"(17 cm) or a width in cm.Noneuses"double"for a new figure. In a figure narrower than 12 cm the direct labels use short mode names ("1st flap reg.").direct_labels (bool, optional) – Name the modes at their line ends with a compact key (the default).
Falsedraws a legend with one entry per track instead, grouped and titled by family, to the right of the axes.title (str or None, optional) – Axes title;
None(the default) draws none.
- Returns:
matplotlib.figure.Figure – The Campbell-diagram figure.
- Return type:
Figure
Damping-ratio plotting against the operating parameter, with stability signals.
Renders the damping ratio of each tracked mode as a function of the operating
parameter (rotor speed or wind speed), the companion to the Campbell diagram for
assessing stability margins across the operating envelope. plot_damping() returns
an interactive Plotly figure and plot_damping_static() a Matplotlib figure for
publication.
The plot makes stability legible at a glance: the negative-damping region is shaded as
unstable and a thin band above zero as a low-margin caution zone, and any point
that crosses into negative damping is flagged. Tracks use the same styling as the
Campbell diagram: colour, dash and marker by mode family
(vane.viz.mode_line_style()), line opacity scaled by identification confidence,
and open-diamond markers on ambiguous tracks and points. The static figure names each
track at its line end and explains the encoding in a compact key; the interactive one
has a legend grouped by family.
- plot_damping(diagram, *, as_percent=True, show_stability_bands=True, show_confidence=True, caution_damping=0.01, template=None, title=None)[source]
Plot damping ratio versus the operating parameter, with stability signals.
- Parameters:
diagram (CampbellDiagram) – The diagram whose tracks are plotted.
as_percent (bool, optional) – Plot damping as a percentage (the default) rather than a fraction.
show_stability_bands (bool, optional) – Shade the unstable (damping < 0) region and the low-margin caution band above it. Default
True.show_confidence (bool, optional) – Scale each track’s line opacity by its identification confidence and mark an ambiguous track (and the ambiguous points of a clear track) with open diamonds. Default
True.caution_damping (float, optional) – Upper edge of the caution band as a damping fraction (default
0.01, i.e. 1%); modes below it are flagged as lightly damped.template (str, plotly.graph_objects.layout.Template or None, optional) – Plotly layout template to apply.
None(the default) applies the VANE template"vane".title (str or None, optional) – Figure title.
None(the default) draws a title only as a warning, naming the modes that go unstable.
- Returns:
plotly.graph_objects.Figure – The damping figure: one line per tracked mode (styled by mode family, with the legend grouped by family), unstable points flagged, and, when enabled, the unstable and caution stability bands.
- Raises:
ValueError – If
caution_dampingis negative.- Return type:
go.Figure
- plot_damping_static(diagram, *, as_percent=True, show_stability_bands=True, show_confidence=True, caution_damping=0.01, ax=None, style='vane', size=None, direct_labels=True, title=None)[source]
Plot damping ratio versus the operating parameter as a Matplotlib figure.
The static counterpart of
plot_damping(): the same tracks, family styling, stability bands and instability flags (red filled crosses). Each mode is named at its line end; a compact key inside the axes explains the encoding.- Parameters:
diagram (CampbellDiagram) – The diagram whose tracks are plotted.
as_percent (bool, optional) – Plot damping as a percentage (the default) rather than a fraction.
show_stability_bands (bool, optional) – Shade the unstable (damping < 0) region and the low-margin caution band above it. Default
True.show_confidence (bool, optional) – Scale each track’s line opacity by its identification confidence and mark ambiguous tracks and points with open diamonds. Default
True.caution_damping (float, optional) – Upper edge of the caution band as a damping fraction (default
0.01).ax (matplotlib.axes.Axes or None, optional) – Draw into these axes instead of creating a new figure; the parent figure is returned.
style (str, dict, pathlib.Path or None, optional) – Matplotlib style in effect while drawing:
"vane"(the default),Nonefor the currentrcParams, or any value accepted bymatplotlib.style.context().size (str, float, FigureSize or None, optional) – Figure size and fonts (see
vane.viz.figure_size());Noneuses"double"for a new figure.direct_labels (bool, optional) – Name the tracks at their line ends with a compact key (the default);
Falsedraws a per-track legend grouped by family instead.title (str or None, optional) – Axes title;
None(the default) draws none.
- Returns:
matplotlib.figure.Figure – The damping figure.
- Raises:
ValueError – If
caution_dampingis negative.- Return type:
Figure
Static mode-shape (participation) plotting.
Since OpenFAST mode shapes are expressed over modal degrees of freedom rather than
spatial coordinates, a mode is visualized as a horizontal bar chart of the signed
participation of its most active degrees of freedom: bar length is the relative
contribution and the sign encodes the in-/out-of-phase relationship to the dominant
degree of freedom. The plot is therefore modal participation, not spatial geometry —
hence the primary name plot_mode_participation(). Following the lab’s mode-shape
conventions, the mode is named in the legend with its natural frequency, a thin grey
line marks zero, and short_names=True names the degrees of freedom briefly
("Tower FA1", "Flap 1 coll.").
- plot_mode_participation(solution, mode_index, *, top_n=10, ax=None, color='#0000d9', style='vane', size=None, short_names=False, title=None)[source]
Plot the signed participation of a mode’s most active degrees of freedom.
- Parameters:
solution (ModalSolution) – A solution whose
dof_descriptionsare populated.mode_index (int) – Index of the mode to plot.
top_n (int, optional) – Number of highest-participation degrees of freedom to show.
ax (matplotlib.axes.Axes or None, optional) – Draw into these axes instead of creating a new figure (for composing a multi-panel report). The axes keep their own appearance; the parent figure is returned.
color (str, optional) – Bar colour; blue, the third standard line colour, by default.
style (str, dict, pathlib.Path or None, optional) – Matplotlib style in effect while drawing.
"vane"(the default) is the VANE style (vane.viz.plot_style());Nonekeeps the currentrcParams; any other value is passed tomatplotlib.style.context().size (str, float, FigureSize or None, optional) – Figure size and fonts (see
vane.viz.figure_size());Noneuses"single"for a new figure.short_names (bool, optional) – Label the bars with short DOF names (
"Tower FA1","Flap 1 coll.") instead of the full state descriptions (the default). Short names suit a printed figure.title (str or None, optional) – Axes title;
None(the default) draws none. The legend names the mode and its natural frequency.
- Returns:
matplotlib.figure.Figure – The bar-chart figure.
- Raises:
ValueError – If the solution has no DOF descriptions,
mode_indexis out of range, ortop_nis not positive.- Return type:
Figure
- plot_mode_shape(solution, mode_index, *, top_n=10, ax=None, color='#0000d9', style='vane', size=None, short_names=False, title=None)[source]
Compatibility alias for
plot_mode_participation().The plot shows signed modal participation, not spatial mode-shape geometry;
plot_mode_participation()is the accurate name. This alias forwards all arguments unchanged.
Three-dimensional complex mode-shape plotting.
A complex mode shape carries both magnitude and phase per degree of freedom. This
view renders the most active degrees of freedom as phasors in three dimensions:
the horizontal axis orders the degrees of freedom by participation while the other
two axes are the real and imaginary parts of each component, so the spread out of
the real plane shows how non-proportional (complex) the mode is.
plot_mode_3d() is interactive (Plotly) and plot_mode_3d_static() static
(Matplotlib).
- plot_mode_3d(solution, mode_index, *, top_n=12, template=None, title=None)[source]
Plot a mode’s most active degrees of freedom as 3-D complex phasors.
- Parameters:
solution (ModalSolution) – A solution whose
dof_descriptionsare populated.mode_index (int) – Index of the mode to plot.
top_n (int, optional) – Number of highest-magnitude degrees of freedom to show.
template (str, plotly.graph_objects.layout.Template or None, optional) – Plotly layout template to apply.
None(the default) applies the VANE template"vane".title (str or None, optional) – Figure title.
None(the default) names the mode and its natural frequency;""draws none.
- Returns:
plotly.graph_objects.Figure – The 3-D phasor figure.
- Raises:
ValueError – If the solution has no DOF descriptions,
mode_indexis out of range, ortop_nis not positive.- Return type:
go.Figure
- plot_mode_3d_static(solution, mode_index, *, top_n=6, style='vane', size=None, title=None)[source]
Plot a mode’s most active degrees of freedom as static 3-D complex phasors.
Each degree of freedom is a stem from the axis of DOF ranks to its complex component (real and imaginary parts), in its own colour; the legend names the DOFs and its title the mode and its natural frequency.
- Parameters:
solution (ModalSolution) – A solution whose
dof_descriptionsare populated.mode_index (int) – Index of the mode to plot.
top_n (int, optional) – Number of highest-magnitude degrees of freedom to show (default 6).
style (str, dict, pathlib.Path or None, optional) – Matplotlib style in effect while drawing (
"vane"by default).size (str, float, FigureSize or None, optional) – Figure size and fonts;
Noneuses"single".title (str or None, optional) – Axes title;
None(the default) draws none.
- Returns:
matplotlib.figure.Figure – The 3-D figure.
- Raises:
ValueError – If the solution has no DOF descriptions,
mode_indexis out of range, ortop_nis not positive.- Return type:
Figure
Complex mode-shape composition views.
A mode shape is complex, and after the multi-blade-coordinate transform the blade degrees of freedom split into collective, cosine, and sine coordinates. These views expose that structure directly, without needing turbine geometry: a phasor wheel plots each dominant DOF as a vector (length = participation magnitude, angle = phase), its animated variant rotates the phasors through one modal period so the relative phasing reads as motion, and the blade-component bar chart shows how a mode’s participation divides among the collective/cosine/sine multi-blade coordinates. The phasor wheel and the bar chart also have static (Matplotlib) forms for publication.
- plot_blade_components(solution, mode_index, *, template=None, title=None)[source]
Plot how a mode’s participation divides among the multi-blade coordinates.
After the MBC transform each blade degree of freedom is a collective, cosine, or sine coordinate. This bar chart shows the participation magnitude of each such DOF, coloured by coordinate: a collective-dominated bar set marks a collective mode, while comparable cosine and sine sets mark a cyclic (whirling) mode.
- Parameters:
solution (ModalSolution) – A solution carrying
dof_mbc_coordinates(i.e. produced from an MBC- transformed model) anddof_descriptions.mode_index (int) – Index of the mode to plot.
template (str, plotly.graph_objects.layout.Template or None, optional) – Plotly layout template to apply.
None(the default) applies the VANE template"vane".title (str or None, optional) – Figure title.
None(the default) names the mode and its natural frequency;""draws none.
- Returns:
plotly.graph_objects.Figure – One bar per multi-blade DOF, grouped and coloured by coordinate.
- Raises:
ValueError – If the solution has no DOF descriptions,
mode_indexis out of range, or the solution carries no multi-blade coordinates.- Return type:
go.Figure
- plot_blade_components_static(solution, mode_index, *, style='vane', size=None, title=None)[source]
Plot how a mode’s participation divides among the multi-blade coordinates.
The static counterpart of
plot_blade_components(): one bar per multi-blade DOF, coloured by coordinate (collective black, cosine red, sine blue), with the legend titled by the mode and its natural frequency.- Parameters:
solution (ModalSolution) – A solution carrying
dof_mbc_coordinatesanddof_descriptions.mode_index (int) – Index of the mode to plot.
style (str, dict, pathlib.Path or None, optional) – Matplotlib style in effect while drawing (
"vane"by default).size (str, float, FigureSize or None, optional) – Figure size and fonts;
Noneuses"single".title (str or None, optional) – Axes title;
None(the default) draws none.
- Returns:
matplotlib.figure.Figure – The bar chart.
- Raises:
ValueError – If the solution has no DOF descriptions,
mode_indexis out of range, or the solution carries no multi-blade coordinates.- Return type:
Figure
- plot_mode_phasor(solution, mode_index, *, top_n=10, template=None, title=None)[source]
Plot a mode’s dominant DOFs as phasors on a polar wheel.
Each degree of freedom is drawn as a vector whose length is its participation magnitude (normalized to the most active DOF) and whose angle is its phase. Two DOFs that are in phase point the same way; a 180-degree spread is anti-phase; a spread of intermediate angles indicates a travelling (whirling) mode.
- Parameters:
solution (ModalSolution) – A solution whose
dof_descriptionsare populated.mode_index (int) – Index of the mode to plot.
top_n (int, optional) – Number of highest-participation degrees of freedom to show.
template (str, plotly.graph_objects.layout.Template or None, optional) – Plotly layout template to apply.
None(the default) applies the VANE template"vane".title (str or None, optional) – Figure title.
None(the default) names the mode and its natural frequency;""draws none.
- Returns:
plotly.graph_objects.Figure – The polar phasor figure.
- Raises:
ValueError – If the solution has no DOF descriptions,
mode_indexis out of range, ortop_nis not positive.- Return type:
go.Figure
- plot_mode_phasor_animation(solution, mode_index, *, top_n=10, n_frames=24, template=None, title=None)[source]
Animate a mode’s phasors through one modal period.
The phasors rotate rigidly (all DOFs share the modal frequency), so the animation makes the relative phasing visible as motion: an in-phase mode sweeps as a rigid fan, while a whirling mode shows a rotating spread.
- Parameters:
solution (ModalSolution) – A solution whose
dof_descriptionsare populated.mode_index (int) – Index of the mode to plot.
top_n (int, optional) – Number of highest-participation degrees of freedom to show.
n_frames (int, optional) – Number of animation frames over one period (must be positive).
template (str, plotly.graph_objects.layout.Template or None, optional) – Plotly layout template to apply.
None(the default) applies the VANE template"vane".title (str or None, optional) – Figure title.
None(the default) names the mode and its natural frequency;""draws none.
- Returns:
plotly.graph_objects.Figure – The animated polar phasor figure, with a play button.
- Raises:
ValueError – If the solution has no DOF descriptions,
mode_indexis out of range, ortop_norn_framesis not positive.- Return type:
go.Figure
- plot_mode_phasor_static(solution, mode_index, *, top_n=6, style='vane', size=None, title=None)[source]
Plot a mode’s dominant DOFs as phasors on a static polar wheel.
Each degree of freedom is an arrow from the centre, its length the normalized participation magnitude and its angle the phase relative to the dominant DOF (counter-clockwise, in degrees). Each arrow has its own colour (the standard line colours, in order of participation), and the legend names the DOFs with their magnitudes.
- Parameters:
solution (ModalSolution) – A solution whose
dof_descriptionsare populated.mode_index (int) – Index of the mode to plot.
top_n (int, optional) – Number of highest-participation degrees of freedom to show (default 6).
style (str, dict, pathlib.Path or None, optional) – Matplotlib style in effect while drawing (
"vane"by default).size (str, float, FigureSize or None, optional) – Figure size and fonts;
Noneuses"single".title (str or None, optional) – Axes title;
None(the default) draws none (the legend title names the mode and its frequency).
- Returns:
matplotlib.figure.Figure – The polar figure.
- Raises:
ValueError – If the solution has no DOF descriptions,
mode_indexis out of range, ortop_nis not positive.- Return type:
Figure
Physical full-system mode-shape visualization of a wind-turbine.
The other mode-shape views in vane.viz are deliberately abstract — phasor
wheels and participation bars — because an OpenFAST mode shape is expressed over
modal degrees of freedom, not spatial coordinates. This module closes the gap to
a picture an engineer can read at a glance: it draws the whole structure — floating
platform, mooring lines, tower, nacelle, and the three blades — and deforms it with
the actual complex mode shape, then animates it through one modal cycle.
How the modal coordinates become motion
The reference geometry (vane.config.system_config) supplies undeformed node
positions and, for the tower and blades, a normalized arc length per node. Each
modal degree of freedom is then mapped to a physical displacement field:
Platform surge/sway/heave/roll/pitch/yaw move the entire structure as a rigid body about the platform reference point.
Tower fore-aft and side-side bending modes add a lateral deflection along the tower via clamped-free (cantilever) bending shape functions; the resulting tower-top translation and slope carry the rotor-nacelle assembly.
Blade flap and edge modes deflect each blade along its flapwise and edgewise directions. After the multi-blade-coordinate transform the blade modes are collective/cosine/sine; the inverse transform recovers the per-blade amplitude at each blade’s azimuth.
Mooring lines are dragged kinematically by the fairlead motion, fading to the fixed anchor.
Honesty
The hull and mooring outlines are a schematic (see
vane.config.system_config); the bending shape functions are the standard
Euler-Bernoulli cantilever modes, not the turbine’s exact polynomial shapes. What
is faithful is the relative motion across the structure, which is set by the
computed complex mode shape. The deflection magnitude is exaggerated by a scale
factor for visibility (universal practice for mode-shape plots) — it is not a
physical amplitude.
- class FowtModeDeformation(geometry, displacements, scale, mode_index, frequency_hz, damping_ratio)[source]
Bases:
objectA mode’s scaled complex displacement field over a reference geometry.
- Parameters:
geometry (FowtGeometry) – The reference geometry that was deformed.
displacements (list[npt.NDArray[np.complex128]]) – Per-member complex displacement fields, aligned with
geometry.members; elementmhas shape(n_nodes_m, 3)and is already multiplied by the visualization scale. The instantaneous real displacement at modal phase \(\theta\) is(displacements[m] * exp(1j * theta)).real.scale (float) – The length multiplier applied so the peak nodal deflection equals the requested fraction of the geometry’s characteristic length.
mode_index (int) – The mode that was visualized.
frequency_hz (float) – Natural frequency of the mode, for labelling.
damping_ratio (float) – Damping ratio of the mode, for labelling.
- geometry: FowtGeometry
- peak_phase_rad()[source]
Return the modal phase of maximum total deflection.
A complex mode passes through different deformed configurations over a cycle; the instantaneous field at phase \(\theta\) is \(\operatorname{Re}(d\,e^{i\theta})\). This returns the \(\theta\) that maximizes the field’s total squared norm — the most legible single snapshot, and the displacement extreme for a real (in-phase) mode.
- Returns:
float – The peak-deflection phase in radians, in
[-pi/2, pi/2](a half-angle from the field’s double-angle norm, so it is defined modulopi).- Return type:
- deform_mode(solution, mode_index, geometry=None, *, deflection_ratio=0.12, scale=1.0)[source]
Map a mode shape onto a reference geometry as a complex displacement field.
- Parameters:
solution (ModalSolution) – A solution whose
dof_descriptionsare populated.mode_index (int) – Index of the mode to visualize.
geometry (FowtGeometry or None, optional) – The reference geometry. When
Nonea schematic is inferred from the solution’s degrees of freedom (seeinfer_geometry()).deflection_ratio (float, optional) – Target peak nodal deflection as a fraction of the geometry’s characteristic length. The displacement field is scaled so its largest node reaches this.
scale (float, optional) – Additional user exaggeration multiplier applied on top of
deflection_ratio.
- Returns:
FowtModeDeformation – The scaled per-member complex displacement field.
- Raises:
ValueError – If the solution has no DOF descriptions,
mode_indexis out of range, ordeflection_ratiois not positive.- Return type:
- infer_geometry(solution)[source]
Infer a schematic reference geometry from a solution’s degrees of freedom.
A floating (semi-submersible) schematic is chosen when platform degrees of freedom are present, otherwise a bottom-fixed monopile. The number of blades is taken from the highest blade index seen, defaulting to three. Principal dimensions use the preset defaults — pass an explicit
FowtGeometryto match a specific turbine.- Parameters:
solution (ModalSolution) – The solution whose
dof_descriptionsare inspected.- Returns:
FowtGeometry – A default geometry matching the detected support structure.
- Return type:
- plot_mode_fowt(solution, mode_index, geometry=None, *, deflection_ratio=0.12, scale=1.0, phase_deg=None, template=None, title=None)[source]
Plot a mode on the full wind-turbine structure at a single phase.
The undeformed structure is drawn as a faint grey outline; the deformed structure — platform, mooring lines, tower, nacelle, and blades — is drawn in colour at the requested modal phase.
- Parameters:
solution (ModalSolution) – A solution whose
dof_descriptionsare populated.mode_index (int) – Index of the mode to plot.
geometry (FowtGeometry or None, optional) – Reference geometry; inferred from the solution when
None.deflection_ratio (float, optional) – Peak deflection as a fraction of the characteristic length.
scale (float, optional) – Extra exaggeration multiplier.
phase_deg (float or None, optional) – Modal phase in degrees at which to freeze the deformation. When
None(the default) the phase of maximum total deflection is used, which is the displacement extreme for a real mode and the most legible snapshot for a complex one.template (str, plotly.graph_objects.layout.Template or None, optional) – Plotly layout template to apply.
None(the default) applies the VANE template"vane".title (str or None, optional) – Figure title.
None(the default) names the mode, its frequency and damping and the deflection scale;""draws none.
- Returns:
plotly.graph_objects.Figure – The 3-D mode-shape figure.
- Raises:
ValueError – Propagated from
deform_mode()for an empty, out-of-range, or non-positively-scaled request.- Return type:
go.Figure
- plot_mode_fowt_animation(solution, mode_index, geometry=None, *, motion='cycle', deflection_ratio=0.12, scale=1.0, n_frames=36, template=None, title=None)[source]
Animate a mode on the full wind-turbine structure (Play, scrub, swing/orbit).
The faint undeformed outline and the still-water plane stay fixed while the coloured structure oscillates, so the relative phasing across platform, tower, and blades reads as physical motion. The figure carries a Play/Pause control and a frame slider to scrub or replay any instant; the deformation magnification is set manually with
deflection_ratioxscale(an engineering deformation-scale factor, as in an FE post-processor).Two motions are offered.
"cycle"sweeps the full modal phase, so a whirling (complex) mode traces its true orbit."swing"oscillates the real peak shape symmetrically about the undeformed state (cosamplitude from+peakthrough0to-peak) — the standing-wave animation a modal analyst expects for a single mode.- Parameters:
solution (ModalSolution) – A solution whose
dof_descriptionsare populated.mode_index (int) – Index of the mode to animate.
geometry (FowtGeometry or None, optional) – Reference geometry; inferred from the solution when
None.motion ({"cycle", "swing"}, optional) –
"cycle"sweeps the full complex phase (orbit);"swing"oscillates the real peak shape about the undeformed state. Default"cycle".deflection_ratio (float, optional) – Peak deflection as a fraction of the characteristic length.
scale (float, optional) – Manual magnification multiplier on top of
deflection_ratio.n_frames (int, optional) – Number of frames over one period (must be positive).
template (str, plotly.graph_objects.layout.Template or None, optional) – Plotly layout template to apply.
None(the default) applies the VANE template"vane".title (str or None, optional) – Figure title.
None(the default) names the mode, its frequency and damping and the deflection scale;""draws none.
- Returns:
plotly.graph_objects.Figure – The animated 3-D mode-shape figure with a Play/Pause control and a scrub slider.
- Raises:
ValueError – If
n_framesis not positive, or as propagated fromdeform_mode().- Return type:
go.Figure
- plot_mode_fowt_static(solution, mode_index, geometry=None, *, deflection_ratio=0.12, scale=1.0, phase_deg=None, elevation_deg=15.0, azimuth_deg=-50.0, style='vane', size=None, title=None)[source]
Plot a mode on the full wind-turbine structure as a static 3-D figure.
The static counterpart of
plot_mode_fowt(): the undeformed structure as a light grey outline and the deformed one coloured by member (tower black, blades green, platform red, as the mode families of the Campbell diagram; nacelle, hub and mooring grey), on equal axes, with a translucent still-water plane for offshore systems. The legend title notes that the deflection is exaggerated.- Parameters:
solution (ModalSolution) – A solution whose
dof_descriptionsare populated.mode_index (int) – Index of the mode to plot.
geometry (FowtGeometry or None, optional) – Reference geometry; inferred from the solution when
None.deflection_ratio (float, optional) – Peak deflection as a fraction of the characteristic length.
scale (float, optional) – Extra exaggeration multiplier.
phase_deg (float or None, optional) – Modal phase in degrees;
Noneuses the phase of maximum total deflection.elevation_deg (float, optional) – Viewing angles of the 3-D axes, in degrees.
azimuth_deg (float, optional) – Viewing angles of the 3-D axes, in degrees.
style (str, dict, pathlib.Path or None, optional) – Matplotlib style in effect while drawing (
"vane"by default).size (str, float, FigureSize or None, optional) – Figure size and fonts;
Noneuses"single", nearly square.title (str or None, optional) – Axes title.
None(the default) draws none; the legend title names the mode and its natural frequency.
- Returns:
matplotlib.figure.Figure – The 3-D mode-shape figure.
- Raises:
ValueError – Propagated from
deform_mode()for an empty, out-of-range, or non-positively-scaled request.- Return type:
Figure
Visualization of the load quantity-of-interest map.
A QoiMap records, for each structural load, how strongly
each identified mode contributes to it (the modal residue \(C_i \Phi\)). This
view renders that as a load-versus-mode heatmap: rows are the load channels,
columns are the modes (labelled by natural frequency), and the colour is the
per-load normalized residue magnitude. It reads as a load-monitoring map — at a
glance, which modes drive which loads (e.g. the low-frequency platform modes that
dominate mooring tension versus the structural modes that dominate the yaw-bearing
moment). plot_load_mode_heatmap() is interactive (Plotly) and
plot_load_mode_heatmap_static() static (Matplotlib).
- plot_load_mode_heatmap(qoi_map, *, min_normalized=0.05, template=None, title=None)[source]
Plot a load-versus-mode modal-contribution heatmap.
- Parameters:
qoi_map (QoiMap) – A map built with a modal solution, so each load carries modal residues.
min_normalized (float, optional) – Keep only the modes whose per-load normalized contribution exceeds this threshold for at least one load, so dormant modes are dropped and the map stays legible. Set to
0to keep every mode.template (str, plotly.graph_objects.layout.Template or None, optional) – Plotly layout template to apply.
None(the default) applies the VANE template"vane".title (str or None, optional) – Figure title;
None(the default) draws none.
- Returns:
plotly.graph_objects.Figure – The heatmap (loads on the y-axis, contributing modes on the x-axis, labelled by natural frequency).
- Raises:
ValueError – If the map has no load channels or was built without a modal solution.
- Return type:
go.Figure
- plot_load_mode_heatmap_static(qoi_map, *, min_normalized=0.05, ax=None, style='vane', size=None, title=None)[source]
Plot a load-versus-mode modal-contribution heatmap as a static figure.
- Parameters:
qoi_map (QoiMap) – A map built with a modal solution, so each load carries modal residues.
min_normalized (float, optional) – Keep only the modes whose per-load normalized contribution exceeds this threshold for at least one load. Set to
0to keep every mode.ax (matplotlib.axes.Axes or None, optional) – Draw into these axes instead of creating a new figure.
style (str, dict, pathlib.Path or None, optional) – Matplotlib style in effect while drawing (
"vane"by default).size (str, float, FigureSize or None, optional) – Figure size and fonts;
Noneuses"single"for a new figure.title (str or None, optional) – Axes title;
None(the default) draws none.
- Returns:
matplotlib.figure.Figure – The heatmap with a colour bar; modes on the x-axis labelled by natural frequency, load channels on the y-axis.
- Raises:
ValueError – If the map has no load channels or was built without a modal solution.
- Return type:
Figure
Diagnostic plots for the identification and tracking quality.
These are the “show your work” visuals that turn the pipeline’s internal quality
signals into inspectable figures: the Modal Assurance Criterion (MAC) correlation
between adjacent operating points (the basis of mode tracking), the per-track
identification confidence, the zoomed review of contested crossings and veerings, and
the azimuth coverage behind each operating point’s averaged model. Each plot has an
interactive (Plotly) form and a static (Matplotlib) form for publication, the latter
named *_static. They complement the reference plots and feed the audit report.
- plot_azimuth_coverage(provenance, *, template=None, title=None)[source]
Plot the azimuth samples behind each operating point’s averaged model.
Sparse or clustered azimuths weaken the azimuth average; this shows, per operating point, exactly which rotor azimuths were sampled.
- Parameters:
provenance (Provenance) – A study’s provenance, carrying each operating point’s azimuth list.
template (str, plotly.graph_objects.layout.Template or None, optional) – Plotly layout template to apply.
None(the default) applies the VANE template"vane".title (str or None, optional) – Figure title;
None(the default) draws none.
- Returns:
plotly.graph_objects.Figure – Azimuth (degrees) versus operating-parameter value, one marker per sample.
- Return type:
go.Figure
- plot_azimuth_coverage_static(provenance, *, ax=None, style='vane', size=None, title=None)[source]
Plot the sampled azimuths of each operating point as a static figure.
- Parameters:
provenance (Provenance) – A study’s provenance, carrying each operating point’s azimuth list.
ax (matplotlib.axes.Axes or None, optional) – Draw into these axes instead of creating a new figure.
style (str, dict, pathlib.Path or None, optional) – Matplotlib style in effect while drawing (
"vane"by default).size (str, float, FigureSize or None, optional) – Figure size and fonts;
Noneuses"single"for a new figure.title (str or None, optional) – Axes title;
None(the default) draws none.
- Returns:
matplotlib.figure.Figure – Azimuth (0 to 360 degrees, ticks every 90 degrees) versus the operating parameter, one black marker per sample.
- Return type:
Figure
- plot_branch_review(diagram, branch_groups, *, template=None, title=None)[source]
Plot each contested branch group as a zoomed Campbell panel.
One small-multiple panel per crossing/veering hotspot shows the competing tracks over the group’s operating-point window, with a guide at the closest approach, so an analyst can review where the automatic tracking is least certain.
- Parameters:
diagram (CampbellDiagram) – The diagram the branch groups index (its tracks and parameter axis).
branch_groups (Sequence[BranchGroup]) – The contested groups to render (e.g.
result.identification.branch_groups).template (str, plotly.graph_objects.layout.Template or None, optional) – Plotly layout template to apply.
None(the default) applies the VANE template"vane".title (str or None, optional) – Figure title.
None(the default) draws one only to say how many groups were left out when there are more than nine.
- Returns:
plotly.graph_objects.Figure – One panel per group (capped at nine, with the remainder noted), or a single “none detected” panel when
branch_groupsis empty.- Return type:
go.Figure
- plot_branch_review_static(diagram, branch_groups, *, style='vane', size=None)[source]
Plot each contested branch group as a zoomed static Campbell panel.
The static counterpart of
plot_branch_review(): up to nine panels, three per row, labelled (a), (b), … when there are several, each with a legend above it that names the kind of hotspot, the closest approach and the competing tracks (drawn in their family colours and line styles), the smooth alternative dashed when resolution computed it, and a dotted guide at the closest approach.- Parameters:
diagram (CampbellDiagram) – The diagram the branch groups index.
branch_groups (Sequence[BranchGroup]) – The contested groups to render.
style (str, dict, pathlib.Path or None, optional) – Matplotlib style in effect while drawing (
"vane"by default).size (str, float, FigureSize or None, optional) – Figure size and fonts;
Noneuses"double".
- Returns:
matplotlib.figure.Figure – The panels, or a single panel saying that no contested branches were found.
- Return type:
Figure
- plot_mac_matrix(reference, test, *, template=None, title=None)[source]
Plot the MAC matrix between two operating points as a heatmap.
The MAC between mode i of
referenceand mode j oftestis the squared correlation of their shapes; a near-diagonal pattern means the modes track cleanly, while off-diagonal energy flags crossings or veering.- Parameters:
reference (ModalSolution) – The two solutions to correlate (e.g. adjacent operating points). Their mode shapes must share the same degrees of freedom.
test (ModalSolution) – The two solutions to correlate (e.g. adjacent operating points). Their mode shapes must share the same degrees of freedom.
template (str, plotly.graph_objects.layout.Template or None, optional) – Plotly layout template to apply.
None(the default) applies the VANE template"vane".title (str or None, optional) – Figure title;
None(the default) draws none.
- Returns:
plotly.graph_objects.Figure – The MAC heatmap, with values in
[0, 1]; the axes are labelled with the natural frequencies of the modes.- Return type:
go.Figure
- plot_mac_matrix_static(reference, test, *, ax=None, style='vane', size=None, annotate=None, title=None)[source]
Plot the MAC matrix between two operating points as a static heatmap.
- Parameters:
reference (ModalSolution) – The two solutions to correlate; their mode shapes must share the same degrees of freedom.
test (ModalSolution) – The two solutions to correlate; their mode shapes must share the same degrees of freedom.
ax (matplotlib.axes.Axes or None, optional) – Draw into these axes instead of creating a new figure.
style (str, dict, pathlib.Path or None, optional) – Matplotlib style in effect while drawing (
"vane"by default;Nonekeeps the currentrcParams).size (str, float, FigureSize or None, optional) – Figure size and fonts (see
vane.viz.figure_size());Noneuses"single"for a new figure, square.annotate (bool or None, optional) – Write the MAC value in each cell.
None(the default) does so for matrices of up to 10 by 10 modes.title (str or None, optional) – Axes title;
None(the default) draws none.
- Returns:
matplotlib.figure.Figure – The heatmap on a grey scale from white (0) to black (1), with a colour bar; the axes are labelled with the natural frequencies of the modes.
- Return type:
Figure
- plot_tracking_confidence(diagram, *, template=None, title=None)[source]
Plot each tracked mode’s identification confidence as a bar chart.
Ambiguous tracks are coloured distinctly, and a reference line marks the level below which the audit report flags a track for review.
- Parameters:
diagram (CampbellDiagram) – The diagram whose tracks are shown.
template (str, plotly.graph_objects.layout.Template or None, optional) – Plotly layout template to apply.
None(the default) applies the VANE template"vane".title (str or None, optional) – Figure title;
None(the default) draws none.
- Returns:
plotly.graph_objects.Figure – One bar per track, confidence on
[0, 1].- Return type:
go.Figure
- plot_tracking_confidence_static(diagram, *, ax=None, style='vane', size=None, title=None)[source]
Plot each tracked mode’s identification confidence as a static bar chart.
- Parameters:
diagram (CampbellDiagram) – The diagram whose tracks are shown.
ax (matplotlib.axes.Axes or None, optional) – Draw into these axes instead of creating a new figure.
style (str, dict, pathlib.Path or None, optional) – Matplotlib style in effect while drawing (
"vane"by default).size (str, float, FigureSize or None, optional) – Figure size and fonts;
Noneuses"single"for a new figure.title (str or None, optional) – Axes title;
None(the default) draws none.
- Returns:
matplotlib.figure.Figure – Horizontal bars, one per track in diagram order (top to bottom), blue for a clear track and orange for an ambiguous one, with the review threshold dashed.
- Return type:
Figure
Self-contained HTML audit report for a study result.
Turns a StudyResult into a single, shareable HTML file that pairs
the analysis with its provenance: the environment and tuning the run used, the
operating points and their hashed source files, the interactive Campbell diagram, and
tables of the tracked modes (with identification confidence) and detected resonance
crossings (with damping-aware severity). It is the auditable artifact a digital-twin or
assessment workflow can attach to a result.
- study_report_html(result, *, title='VANE Study Report', include_plotlyjs=True)[source]
Render a study result as a self-contained HTML report.
- Parameters:
result (StudyResult) – The analysis and its provenance.
title (str, optional) – Report heading.
include_plotlyjs (bool or str, optional) – How to embed Plotly.js, passed to
Figure.to_html. The defaultTrueinlines the library so the report is fully self-contained and works offline (the intended audit artifact); pass"cdn"for a much smaller file that instead loads Plotly.js from a CDN at view time.
- Returns:
str – A complete HTML document.
- Return type:
- write_report(result, output_path, *, title='VANE Study Report', include_plotlyjs=True)[source]
Write a study report to
output_pathas HTML.- Parameters:
result (StudyResult) – The analysis and its provenance.
output_path (str or pathlib.Path) – Destination
.htmlfile; parent directories are created.title (str, optional) – Report heading.
include_plotlyjs (bool or str, optional) – Passed through to
study_report_html(); defaults toTrue(inline, so the written file is fully self-contained).
- Return type:
None
- write_report_pdf(result, output_path, *, title='VANE Study Report')[source]
Write the study report to
output_pathas PDF (requires WeasyPrint).Renders the report’s auditable content — the heading and run metadata, the pass/review audit findings, the provenance (environment, operating points and their hashed source files), and the tracked-mode and resonance tables — to a paginated PDF via the optional WeasyPrint engine. This is the print-ready, archivable companion to the interactive HTML report; because WeasyPrint renders HTML/CSS and does not execute JavaScript, the interactive Campbell figure is available in the HTML report (
write_report()), not the PDF.- Parameters:
result (StudyResult) – The analysis and its provenance.
output_path (str or pathlib.Path) – Destination
.pdffile; parent directories are created.title (str, optional) – Report heading.
- Returns:
pathlib.Path – The PDF file written.
- Raises:
ValueError – If WeasyPrint is not installed (
pip install "vane[pdf]").- Return type:
Path
Export a mode shape as a deformed mesh in the legacy VTK format.
A mode shape is most legible in 3-D over the real structure. This writer turns a mode’s
nodal displacements and the undeformed node coordinates into a legacy ASCII VTK
POLYDATA file — the deformed node positions, the displacement vector field, and an
optional line connectivity — that opens directly in a VTK viewer and can be compared
against OpenFAST’s own -VTKLin linearization output.
write_mode_animation_vtk() extends this to a VTK-native animation: it
sweeps a (real or complex) mode shape through one oscillation period into one VTK file
per frame plus a .pvd collection, so a VTK viewer plays the mode’s motion directly
rather than the deformed shape being a single still frame. Both write the format by
hand, so they need no vtk package.
- write_mode_animation_vtk(points, displacement, path, *, n_frames=24, scale=1.0, lines=None, title='VANE mode shape')[source]
Write a mode’s oscillation as a VTK animation (frames +
.pvd).A mode oscillates: the physical motion at phase
thetais the real part of the complex mode shape rotated bytheta,Re(displacement) * cos(theta) - Im(displacement) * sin(theta). This sweepsthetaover one full period inn_framesequal steps, writing each frame’s deformed mesh as a legacy VTKPOLYDATAfile (via the same serializer aswrite_mode_shape_vtk()) and a a.pvdcollection atpaththat ties the frames into one time series. Opening the.pvdin a VTK viewer plays the mode’s motion directly — the VTK-native counterpart of the interactive Plotly animation.A real
displacementanimates as a purecos(theta)standing-wave swing; a complex one animates its travelling/orbiting motion (non-zero imaginary part).- Parameters:
points (npt.ArrayLike) – Undeformed node coordinates, shape
(n_nodes, 3).displacement (npt.ArrayLike) – Nodal mode-shape displacement vectors, shape
(n_nodes, 3); real or complex.path (str or pathlib.Path) – Destination
.pvdcollection path. One VTK file per frame is written next to it, named<stem>.<frame>.vtk.n_frames (int, optional) – Number of frames over one oscillation period. Default
24.scale (float, optional) – Visual exaggeration applied to the displacement. Default
1.0.lines (Sequence[Sequence[int]] or None, optional) – Polyline connectivity (e.g. one polyline per beam), written into every frame.
title (str, optional) – The VTK header title line written into every frame, sanitized to a single line of at most 255 characters as in
write_mode_shape_vtk().
- Returns:
list[pathlib.Path] – The per-frame VTK files in time order. The
.pvdcollection is written atpath(open that in a VTK viewer).- Raises:
ValueError – If
points/displacementare not(n_nodes, 3)and equal in shape, non-finite,scaleis non-finite,n_framesis not a whole number >= 1, or a line index is out of range.- Return type:
list[Path]
- write_mode_shape_vtk(points, displacement, path, *, scale=1.0, lines=None, title='VANE mode shape')[source]
Write a mode’s deformed mesh to a legacy ASCII VTK
POLYDATAfile.- Parameters:
points (npt.ArrayLike) – Undeformed node coordinates, shape
(n_nodes, 3).displacement (npt.ArrayLike) – Real nodal displacement vectors of the mode, shape
(n_nodes, 3). A complex array is accepted only when its imaginary part is round-off (below1e-9of the largest displacement magnitude); a genuinely complex mode is rejected rather than silently reduced to its real part. To write the motion at phasephase, passnp.real(displacement * np.exp(1j * phase)); to show the whole oscillation, usewrite_mode_animation_vtk().path (str or pathlib.Path) – Destination
.vtkfile path.scale (float, optional) – Factor applied to
displacementbefore adding it topointsto form the deformed geometry (visual exaggeration). Must be finite. Default1.0.lines (Sequence[Sequence[int]] or None, optional) – Polyline connectivity as sequences of node indices (e.g. one polyline per beam). Written as the VTK
LINEScells;Nonewrites points only.title (str, optional) – The VTK header title line. Line breaks and other non-printable characters are replaced by spaces and the title is truncated to 255 characters, so the header always stays a single valid line.
- Returns:
pathlib.Path – The file written: deformed
POINTS, adisplacementpoint-vector field, and theLINEScells whenlinesis given.- Raises:
ValueError – If
points/displacementare not(n_nodes, 3)and equal in shape, non-finite,displacementhas a non-negligible imaginary part,scaleis non-finite, or a line index is out of range.- Return type:
Path
Colour palettes for the figures. VANE’s figures use the standard line colours defined
in vane.viz (black, red, blue, green, magenta, orange, cyan; see
Visualization); this module re-exports them and also provides, as
an option for colour-vision deficiency, the Okabe-Ito qualitative palette (Okabe and
Ito, 2008) and a helper that applies it to a figure.
- vane.viz.palette.OKABE_ITO: tuple[str, ...]
The eight Okabe-Ito colours as hex strings, with black last so that the chromatic hues cycle first.
- apply_colorblind_palette(figure)[source]
Recolour a figure with the colourblind-safe Okabe-Ito palette.
The figure’s
layout.colorwaybecomesOKABE_ITO, and every explicit line or marker colour that is one ofSTANDARD_COLORS(such as the mode-family colours of the Campbell diagram) is replaced by the Okabe-Ito colour of the nearest hue. Other explicit colours (greys, colour scales) are left unchanged.- Parameters:
figure (plotly.graph_objects.Figure) – The figure to recolour, in place.
- Returns:
plotly.graph_objects.Figure – The same figure, for chaining.
- Return type:
go.Figure
- apply_standard_palette(figure)[source]
Set a figure’s trace colour cycle to the standard line colours.
- Parameters:
figure (plotly.graph_objects.Figure) – The figure; its
layout.colorwayis set so that traces added without an explicit colour cycle throughSTANDARD_COLORS.- Returns:
plotly.graph_objects.Figure – The same figure, for chaining.
- Return type:
go.Figure
Export
Tabular and reduced-order-model export of the analysis products.
The analysis products — a per-operating-point modal solution, its mode shapes and
participation factors, a Campbell diagram of tracked modes, and detected resonance
crossings — are flattened into pandas.DataFrame tables and written to CSV,
JSON, Excel, or .mat for downstream use (spreadsheets, reports, notebooks and
numerical environments that read .mat files). The tables optionally carry the
physical labels, per-mode confidence, and azimuth-spread uncertainty produced by the
rest of the library.
write_rom() packages a reduced-order model (the reduced state space plus its
modal metadata) as a self-contained, auditable bundle ready for a digital-twin /
state-estimation workflow. build_physical_lpv_family() exports the named
physical-state operating-point plant family a gain-scheduled controller schedules on;
save_lpv_family() and
load_lpv_family() archive it.
- class LPVFamily(scheduling_points, systems, state_names=<factory>, input_names=<factory>, output_names=<factory>, scheduling_variable='WindSpeed', scheduling_unit='m/s', auto_added_states=<factory>, dropped_coupling_a=<factory>, dropped_coupling_c=<factory>)[source]
Bases:
objectA parameter-indexed family of named physical-state plants for control design.
- Parameters:
scheduling_points (npt.NDArray[np.float64]) – The scheduling-variable value of each operating point, shape
(n_points,), sorted strictly ascending. In the raw linearization unit:WindSpeedin m/s,RotorSpeedin rad/s (not rpm).systems (list[StateSpace]) – The physical-state plant at each operating point, in the same order as
scheduling_points. Every system has the same selected states, inputs, and outputs, in the same order – the consistency a scheduled controller relies on.state_names (list[str]) – The selected physical state, input, and output names, shared by every system.
input_names (list[str]) – The selected physical state, input, and output names, shared by every system.
output_names (list[str]) – The selected physical state, input, and output names, shared by every system.
scheduling_variable (str) – The scheduling variable the family is indexed on (e.g.
"WindSpeed").scheduling_unit (str) – The physical unit of
scheduling_pointsin the raw linearization convention:"m/s"forWindSpeed,"rad/s"forRotorSpeed(not rpm, unlike the modal/Campbell path). Carried explicitly so a scheduled controller cannot mis-schedule by assuming the wrong unit.auto_added_states (list[str]) – The states the builder added to the request to complete a position/velocity pair (see
pair_derivativesinbuild_physical_lpv_family()), named by their unit-stripped linearization descriptions. Each also appears instate_names. Empty when nothing was added.dropped_coupling_a (npt.NDArray[np.float64]) – Per operating point, the relative state-matrix coupling the state selection discards,
||A[s, ~s]||_F / ||A[s, :]||_F(sthe selected states), shape(n_points,), in the order ofscheduling_points. Zero when every state is selected.dropped_coupling_c (npt.NDArray[np.float64]) – Per operating point, the relative output coupling the selection discards,
||C[o, ~s]||_F / ||C[o, :]||_F(othe selected outputs), shape(n_points,). Zero when no outputs are selected or every state is selected.
- scheduling_points: npt.NDArray[np.float64]
- systems: list[StateSpace]
- dropped_coupling_a: npt.NDArray[np.float64]
- dropped_coupling_c: npt.NDArray[np.float64]
- property dropped_coupling: npt.NDArray[np.float64]
Per-point relative dropped coupling, the larger of the
AandCterms.- Returns:
npt.NDArray[np.float64] –
max(dropped_coupling_a, dropped_coupling_c)element-wise, shape(n_points,). This is the quantitymax_dropped_couplingchecks.
- build_physical_lpv_family(lin_dir, *, states, inputs=(), outputs=(), scheduling_var='WindSpeed', pair_derivatives=True, max_dropped_coupling=0.05)[source]
Build a named physical-state LPV plant family from a directory of
.linfiles.Discovers the operating points in
lin_dir, averages each point’s azimuth files in the physical frame, and selects the namedstates/inputs/outputsinto a state-space model – one per operating point, ordered byscheduling_var, with the same channel layout everywhere. No MBC transform is applied, so per-blade channel names are preserved (usebuild_rom_family()for the multi-blade coordinate representation).Selecting a state subset truncates the plant: rows and columns of the unselected states are removed, discarding their coupling into the selected ones. Two safeguards keep that truncation honest. Second-order DOFs are completed with their position/velocity twin (
pair_derivatives), and the relative coupling the selection still drops is measured at every point and checked (max_dropped_coupling).- Parameters:
lin_dir (str or pathlib.Path) – Directory of OpenFAST
.linfiles, searched recursively, so a campaign laid out with one sub-directory per operating point is read as distinct points (grouped into operating points by case name).states (Sequence[str]) – Physical state names to select, e.g.
["ED PtfmPitch", "ED RotSpeed"]. Matched against the linearization’s state descriptions (units stripped): an exact match is preferred, else a unique substring. When a name matches both a DOF and its"First time derivative of <DOF>"twin, the position DOF is chosen (and, withpair_derivatives, its twin is then added); a name matching two genuinely different channels raises, listing them. The selected order is the state order, with any auto-added twin inserted next to its partner.inputs (Sequence[str], optional) – Input and output names to select (e.g.
["GenTq", "BldPitch1"]); empty selects none (noB/C).outputs (Sequence[str], optional) – Input and output names to select (e.g.
["GenTq", "BldPitch1"]); empty selects none (noB/C).scheduling_var (str, optional) – The scheduling variable:
"WindSpeed"(default, m/s) or"RotorSpeed".RotorSpeedis the raw linearization rotor speed in rad/s (the unit OpenFAST writes), not rpm — unlike the modal/Campbell path, which reports rotor speed in rpm. Schedule a controller built against rad/s accordingly.pair_derivatives (bool, optional) – If true (default), every selected second-order DOF whose twin – the
"First time derivative of <DOF>"velocity of a selected position, or the position of a selected velocity – exists in the linearization but was not requested gets that twin added, immediately after a position or immediately before a velocity, so each pair reads position then velocity. The added names (unit-stripped descriptions) are logged at INFO and recorded inLPVFamily.auto_added_states. A position without its velocity is a pure truncation artefact (d(position)/dtis exactly the dropped velocity), so disable this only to reproduce a deliberate truncation.max_dropped_coupling (float or None, optional) – Ceiling on the relative coupling the state selection discards, default
0.05. At each operating point VANE computes||A[s, ~s]||_F / ||A[s, :]||_Fover the selected statessand, when outputs are selected,||C[o, ~s]||_F / ||C[o, :]||_Fover the selected outputso; the per-point value is the larger of the two (LPVFamily.dropped_coupling). If the largest value over the family exceeds the ceiling,ValueErroris raised naming the worst point and the dropped states contributing most. The norms are taken in the linearization’s own units, so the measure is a screening check, not a unit-invariant error bound.Nonedisables the check (the norms are still recorded). Must be a finite number>= 0.
- Returns:
LPVFamily – The physical-state plant family, with
scheduling_pointssorted ascending and aStateSpaceper point sharing the selected channel layout. Itsstate_namesis the final (paired) state list;auto_added_states,dropped_coupling_aanddropped_coupling_crecord the pairing and the per-point truncation norms.- Raises:
FileNotFoundError – If
lin_dirdoes not exist or contains no.linfiles.ValueError – If
scheduling_varis unknown;max_dropped_couplingis notNoneor a finite number>= 0; a name selection is invalid (a bare string, an emptystates, a duplicate, or a non-string entry); a requested name is missing/ambiguous; a name resolves to a different channel (or different units) across operating points; an operating point’s azimuth files disagree on the channel layout or the operating condition (wind or rotor speed) or contain a duplicate azimuth; the schedule is non-finite or not increasing; or the state selection drops more coupling thanmax_dropped_couplingallows.
- Return type:
- campbell_table(diagram)[source]
Flatten a Campbell diagram into a long-format table of tracked modes.
- Parameters:
diagram (CampbellDiagram) – The Campbell diagram to export.
- Returns:
pandas.DataFrame – One row per (track, operating point): the track index and label, the operating-parameter value, natural frequency, damping ratio, track confidence, and ambiguity flag. The operating-parameter column is named after
diagram.parameter_name.- Raises:
ValueError – If
diagram.parameter_namecollides with a fixed export column.- Return type:
pd.DataFrame
- load_lpv_family(path)[source]
Load a physical-state LPV family saved by
save_lpv_family().The archive is opened with
allow_pickle=False. Its format tag and schema version are checked first, then every array and metadata field, and the rebuilt family is validated as on saving, so a file that loads is a family thatsave_lpv_family()could have written. The arrays, names, scheduling variable and unit, auto-added states and dropped-coupling norms come back exactly as saved.- Parameters:
path (str or os.PathLike) – The
.npzfile written bysave_lpv_family()(the suffix is added if absent).- Returns:
vane.sysid.physical_lpv.LPVFamily – The reconstructed family.
- Raises:
ValueError – If the file is not a VANE LPV-family archive, records no schema version or a schema other than
LPV_FAMILY_SCHEMA_VERSION(the message names both versions and the VANE version that wrote the file), or is incomplete, has unexpected entries or holds an invalid family.- Return type:
- mode_shapes_table(solution)[source]
Flatten a solution’s complex mode shapes into a long-format table.
- Parameters:
solution (ModalSolution) – The modal solution whose
mode_shapes(one column per mode) are exported.- Returns:
pandas.DataFrame – One row per (mode, degree of freedom): the mode index, DOF index and description, and the mode-shape component’s real part, imaginary part, magnitude, and phase (degrees).
- Return type:
pd.DataFrame
- modes_table(solution, *, labels=None, confidence=None, spread=None)[source]
Flatten a modal solution into a per-mode table.
- Parameters:
solution (ModalSolution) – The modal solution to export.
labels (Sequence[ModeLabel] or None, optional) – Per-mode physical labels; their name, category, and multi-blade type are added as columns.
confidence (npt.NDArray[np.float64] or Sequence[float] or None, optional) – Per-mode unified confidence, added as a column.
spread (AzimuthSpread or None, optional) – Per-mode azimuth spread; its frequency and damping standard deviations are added as columns.
- Returns:
pandas.DataFrame – One row per mode, ordered as in
solution.- Raises:
ValueError – If any optional argument’s length does not match the number of modes.
- Return type:
pd.DataFrame
- participation_table(participation, *, descriptions=None)[source]
Flatten participation factors into a long-format per-(mode, DOF) table.
- Parameters:
participation (ParticipationResult) – The per-DOF participation to export (
magnitudehas one column per mode).descriptions (Sequence[str] or None, optional) – DOF descriptions parallel to the participation rows; generic
dof_inames are used when omitted or mismatched in length.
- Returns:
pandas.DataFrame – One row per (mode, DOF): the magnitude, signed magnitude, phase (degrees), and whether the DOF is the mode’s dominant participant.
- Return type:
pd.DataFrame
- resonance_table(resonances)[source]
Flatten detected resonance crossings into a table.
- Parameters:
resonances (Sequence[ResonanceCrossing]) – The crossings to export (e.g.
PipelineResult.resonances).- Returns:
pandas.DataFrame – One row per crossing: the track index, excitation harmonic, rotor speed (rpm), crossing frequency (Hz), damping ratio, and severity. Empty (with the columns present) when there are no crossings.
- Return type:
pd.DataFrame
- save_lpv_family(family, path)[source]
Save a physical-state
LPVFamilyto.npz.The archive holds the scheduling points, the per-point dropped-coupling norms and each system’s matrices as plain arrays under explicit indexed keys, and every other field (channel names, the scheduling variable and its unit, the auto-added states, each system’s names, sample time and matrix presence) as JSON metadata. It is stamped with the format tag,
LPV_FAMILY_SCHEMA_VERSIONand the producing VANE version. No object arrays are used, soload_lpv_family()opens it withallow_pickle=False.- Parameters:
family (vane.sysid.physical_lpv.LPVFamily) – The family, e.g. from
vane.export.build_physical_lpv_family().path (str or os.PathLike) – Destination file (a
.npzis written; the suffix is added if absent).
- Raises:
TypeError – If
familyis not exactly anLPVFamily, a system is not exactly aStateSpace, or a field has the wrong type.ValueError – If the family is empty, its arrays are non-finite or of the wrong length, the scheduling points are not strictly ascending, or the systems do not share one layout (matrix shapes, names and sample time).
- Return type:
None
- write_rom(reduction, output_dir, *, matrix_format='mat')[source]
Write a reduced-order model as a self-contained, auditable bundle.
The bundle is a directory holding the reduced state-space matrices, a per-mode modal table, and a JSON manifest, so the reduced model a digital-twin / state estimator consumes travels with the provenance of how it was reduced.
- Parameters:
reduction (ModalReduction) – A reduced-order model from
vane.sysid.modal_reduction().output_dir (str or pathlib.Path) – Destination directory; created if it does not exist.
matrix_format ({"mat", "npz"}, optional) – Format for the state-space matrices —
.mat(the default) or NumPy.npz.
- Returns:
pathlib.Path – The bundle directory written:
state_space.{mat,npz}(theA/B/C/Dmatrices,dt, the state/input/output names and each output’s multi-blade coordinate, empty when unknown – MATLAB cell arrays of strings in.mat, unicode arrays loadable withoutallow_picklein.npz; plusstate_projection(full state to modal coordinates) andstate_reconstruction(modal coordinates to full state) when the reduction carries them, listed understate_mapsin the manifest),modes.csv(per retained mode: natural frequency, damping ratio, ranking residue), andmanifest.json(the reduction metadata and library version).- Raises:
ValueError – If
matrix_formatis not"mat"or"npz".- Return type:
Path
- write_table(table, path)[source]
Write a table to CSV, JSON, Excel, or MATLAB, inferred from the file suffix.
- Parameters:
table (pandas.DataFrame) – The table to write.
path (str or pathlib.Path) – Destination path; the suffix selects the format (
.csv,.json,.xlsx, or.mat). A.matfile stores each column as a MATLAB variable of the same name.
- Raises:
ValueError – If the suffix is not a supported format, Excel is requested without the optional
openpyxldependency installed, or a.mattable holds a value MATLAB cannot store. Missing values (None/NaN) in object/string columns are written to.matas empty strings in a cell array.- Return type:
None
Validation and benchmarking
Plausibility comparison of extracted frequencies against published references.
VANE is a post-processor of an OpenFAST linearization: its correctness question is whether it faithfully recovers the modal content of the matrices OpenFAST produced, which is settled against version-unambiguous references (analytical solutions, exact MBC invariants, prescribed-mode systems, and a second independent implementation on the same matrices). Published reference-turbine frequencies are deliberately not used as a pass/fail check: they are typically produced by other tools (isolated-beam modal codes or design-stage aero-structural tools) at a particular model version, and they routinely disagree with a coupled OpenFAST linearization because they answer a different question and drift with model/tool version. Requiring agreement with them would test the model, not VANE.
This module offers the published comparison in its correct role instead — a
plausibility check and reporting feature. compare_to_reference() matches the
frequencies VANE extracted to a set of literature values, reports the per-mode
differences, and leaves the outcome to the analyst, so a discrepancy can be attributed
to its cause (isolated-component vs full-system coupled modes, design-stage vs as-built,
tool/version drift) rather than silently failing a tolerance. It raises no error on
large deltas: it quantifies them.
- class PlausibilityComparison(labels, reference_hz, matched_hz, absolute_delta_hz, relative_delta, source)[source]
Bases:
objectA non-binding comparison of extracted frequencies to published references.
Each published reference frequency is paired with the closest extracted frequency (a unique assignment), and the signed differences are recorded. No tolerance is applied; the arrays simply quantify how the extracted model sits relative to the literature so the analyst can attribute the deltas.
- Parameters:
labels (list[str]) – A label for each reference mode, parallel to
reference_hz.reference_hz (npt.NDArray[np.float64]) – The published reference natural frequencies (Hz), shape
(n_ref,).matched_hz (npt.NDArray[np.float64]) – The extracted natural frequency (Hz) paired with each reference, same shape.
absolute_delta_hz (npt.NDArray[np.float64]) – Signed difference
matched_hz - reference_hz(Hz), same shape.relative_delta (npt.NDArray[np.float64]) – Signed relative difference
(matched_hz - reference_hz) / reference_hz, same shape (a fraction, not a percentage).source (str or None) – An optional name for the reference set (e.g. the publication), for reports.
- reference_hz: npt.NDArray[np.float64]
- matched_hz: npt.NDArray[np.float64]
- absolute_delta_hz: npt.NDArray[np.float64]
- relative_delta: npt.NDArray[np.float64]
- compare_to_reference(computed_hz, reference_hz, *, labels=None, source=None)[source]
Compare extracted natural frequencies to published references (non-binding).
Each reference frequency is paired with the closest extracted frequency under a unique assignment that minimizes the total absolute frequency distance (so two references never claim the same extracted mode), and the signed per-mode differences are reported. This is a plausibility check, not a correctness check: no tolerance is applied and no error is raised on large differences — they are quantified so they can be attributed to their cause (isolated-component vs coupled model, design-stage vs as-built, tool/version drift).
- Parameters:
computed_hz (npt.ArrayLike) – The natural frequencies VANE extracted (Hz), shape
(n_computed,); must be at least as many as the reference frequencies so every reference can be paired.reference_hz (npt.ArrayLike) – The published reference natural frequencies (Hz), shape
(n_ref,).labels (Sequence[str] or None, optional) – A label for each reference mode, parallel to
reference_hz(e.g."1st tower fore-aft"). Defaults to"mode 1","mode 2", …source (str or None, optional) – An optional name for the reference set, carried into
report().
- Returns:
PlausibilityComparison – The per-mode reference/extracted frequencies and their differences.
- Raises:
ValueError – If either input is not a 1-D array of finite, strictly positive frequencies, if
labelsdoes not matchreference_hzin length, or if fewer frequencies were extracted than references (so a pairing is impossible).- Return type:
Ground-truth benchmarking of modal extraction accuracy and robustness.
A synthetic system is built from prescribed natural frequencies, damping ratios, and mode shapes, so the exact modal answer is known. Running the extraction on it and scoring the result against that ground truth measures accuracy directly; adding controlled noise to the system matrix and re-scoring measures robustness. This is the quantitative evidence that the extraction is accurate and degrades gracefully, which the reference tools do not provide.
A classically damped system x'' + C x' + K x = 0 with M = I is assembled
from a modal matrix Phi and modal parameters via K = Phi diag(wn^2) Phi^-1
and C = Phi diag(2 zeta wn) Phi^-1; its state matrix [[0, I], [-K, -C]] then
has exactly the prescribed eigenvalues and (displacement) mode shapes Phi.
- class GroundTruthSystem(a, natural_frequencies_hz, damping_ratios, mode_shapes, ndof2)[source]
Bases:
objectA synthetic system with known modal parameters.
- Parameters:
a (npt.NDArray[np.float64]) – State matrix of shape
(2 n, 2 n)in[displacement, velocity]order.natural_frequencies_hz (npt.NDArray[np.float64]) – The prescribed natural frequencies (Hz), shape
(n,).damping_ratios (npt.NDArray[np.float64]) – The prescribed damping ratios, shape
(n,).mode_shapes (npt.NDArray[np.float64]) – The prescribed displacement mode shapes as columns, shape
(n, n).ndof2 (int) – The number of second-order degrees of freedom
n.
- a: npt.NDArray[np.float64]
- natural_frequencies_hz: npt.NDArray[np.float64]
- damping_ratios: npt.NDArray[np.float64]
- mode_shapes: npt.NDArray[np.float64]
- class ModeRecoveryScore(max_frequency_error, max_damping_error, min_mac, mean_mac, n_matched)[source]
Bases:
objectAccuracy of a recovered modal solution against a ground truth.
- Parameters:
max_frequency_error (float) – Largest relative natural-frequency error over the matched modes.
max_damping_error (float) – Largest absolute damping-ratio error over the matched modes.
min_mac (float) – Smallest mode-shape MAC over the matched modes (1 = exact);
0when any ground-truth mode was not recovered.mean_mac (float) – Mean mode-shape MAC over all ground-truth modes (unrecovered modes count as
0).n_matched (int) – Number of ground-truth modes matched to a recovered mode (less than the prescribed count when modes are missing).
- robustness_curve(truth, noise_levels, *, seed=0)[source]
Score recovery as increasing noise is added to the system dynamics.
One fixed seeded perturbation direction is drawn and, at each level
sigma, scaled bysigma * ||dynamics||and added to the stiffness/damping block of the state matrix (the kinematic[0, I]block is left exact). Reusing a single direction makes the sweep a pure amplitude sweep, so the error grows monotonically withsigma(to first order) rather than varying with an arbitrary fresh direction at each level.- Parameters:
truth (GroundTruthSystem) – The ground-truth system.
noise_levels (Sequence[float]) – Relative noise amplitudes, each
>= 0.seed (int, optional) – Seed for the deterministic perturbation direction.
- Returns:
list[tuple[float, ModeRecoveryScore]] – Each noise level paired with its recovery score.
- Raises:
ValueError – If any noise level is negative or non-finite.
- Return type:
- score_recovery(solution, truth)[source]
Score a recovered solution against the ground truth.
Recovered modes are matched to ground-truth modes by maximum total MAC (Hungarian assignment), and the worst-case frequency and damping errors and the mode-shape correlations are reported over the matched pairs.
- Parameters:
solution (ModalSolution) – The extracted modal solution.
truth (GroundTruthSystem) – The ground-truth system.
- Returns:
ModeRecoveryScore – The accuracy metrics.
- Raises:
ValueError – If the solution has no modes to score.
- Return type:
- synthetic_system(natural_frequencies_hz, damping_ratios, *, mode_shapes=None)[source]
Build a classically damped system with the prescribed modal parameters.
- Parameters:
natural_frequencies_hz (Sequence[float]) – Natural frequencies (Hz), all strictly positive.
damping_ratios (Sequence[float]) – Damping ratios in
[0, 1), parallel tonatural_frequencies_hz.mode_shapes (npt.NDArray[np.float64] or None, optional) – Invertible modal matrix
Phi(columns are mode shapes), shape(n, n); the identity is used when omitted.
- Returns:
GroundTruthSystem – The state matrix and the exact modal parameters it realizes.
- Raises:
ValueError – If the inputs have mismatched lengths, non-positive frequencies, damping outside
[0, 1), or a non-square / singularmode_shapes.- Return type:
Utilities
Shared utility subpackage for cross-cutting helpers.
vane.utils.paths resolves Windows extended-length paths safely, and
vane.utils.fs_retry retries file operations that fail transiently on Windows.
Path resolution that is safe against Windows extended-length (\\?\) paths.
On Windows, pathlib.Path.resolve() can return a path with the extended-length
prefix \\?\ (CPython keeps it when a transient error, such as a directory another
process is creating or deleting, stops it confirming the plain form). Under that prefix
Windows does not collapse .. segments, so an OpenFAST deck that references a shared
sub-file as ../Shared/File.dat no longer resolves. These helpers drop the prefix
when the plain path fits within MAX_PATH and otherwise collapse .. explicitly.
- has_extended_prefix(path)[source]
Return whether
pathcarries the Windows extended-length\\?\prefix.- Parameters:
path (str or pathlib.Path) – The path to test.
- Returns:
bool –
Truewhen the path text starts with\\?\.- Return type:
- normalize_extended_path(path)[source]
Return
pathwithout a needless extended-length prefix.A path carrying the Windows
\\?\prefix has its..segments collapsed and loses the prefix when the plain form fits withinMAX_PATH; a longer path keeps the prefix (it is needed there) but with..collapsed. Any other path is returned unchanged, so the function is a no-op off Windows.- Parameters:
path (pathlib.Path) – The path to normalize.
- Returns:
pathlib.Path – The normalized path.
- Return type:
- plain_path_text(path)[source]
Return
pathas text without any extended-length\\?\prefix.Unlike
normalize_extended_path(), the prefix is dropped even when the plain form exceedsMAX_PATH: the result is the spelling a program that is not long-path aware (such as OpenFAST) would have to use, so its length can be checked against that program’s limit.- Parameters:
path (str or pathlib.Path) – The path to convert.
- Returns:
str – The path text, with
..collapsed and the prefix removed when it had one; unchanged otherwise.- Return type:
- resolve_path(path)[source]
Resolve
pathto an absolute path without a needless\\?\prefix.- Parameters:
path (str or pathlib.Path) – The path to resolve.
- Returns:
pathlib.Path –
Path(path).resolve()passed throughnormalize_extended_path().- Return type:
Bounded retry for transient Windows file-system errors.
On Windows, renaming, replacing, copying or deleting a file fails transiently with
ERROR_ACCESS_DENIED (5), ERROR_SHARING_VIOLATION (32) or
ERROR_LOCK_VIOLATION (33) while another process briefly holds a handle on the
file or its directory: an antivirus or search-indexer scan of a freshly written file,
or an OpenFAST process that has just exited and is still releasing its handles. Python
raises these as PermissionError carrying the code in winerror. A sweep that
writes hundreds of case files across many parallel workers meets them sooner or later,
and retrying the operation a moment later succeeds.
retry_transient() runs an operation and retries it, with a short exponential
backoff, only on such an error; any other exception – including a POSIX
PermissionError, which never carries winerror and is a real permission
problem – propagates at once, so on POSIX the helpers behave exactly like the
operations they wrap. The last error is re-raised once the tries are exhausted.
- TRANSIENT_WINERRORS: frozenset[int] = frozenset({5, 32, 33})
access denied, sharing violation and lock violation.
- Type:
Windows error codes treated as transient
- copy2_with_retry(source, destination)[source]
Copy a file with
shutil.copy2(), with retry.- Parameters:
source (str or pathlib.Path) – The file to copy and its destination.
destination (str or pathlib.Path) – The file to copy and its destination.
- Returns:
str – The destination, as
shutil.copy2()returns it (usable as ashutil.copytree()copy_function).- Return type:
- is_transient_file_error(error)[source]
Return whether
erroris a transient Windows file-access error.- Parameters:
error (BaseException) – The exception raised by a file-system operation.
- Returns:
bool –
Truefor aPermissionErrorwhosewinerroris one ofTRANSIENT_WINERRORS;Falseotherwise, and always on POSIX, where the attribute is absent.- Return type:
- link_with_retry(source, destination)[source]
Hard-link
destinationtosourcewithos.link(), with retry.- Parameters:
source (str or pathlib.Path) – The existing file and the new link.
destination (str or pathlib.Path) – The existing file and the new link.
- Return type:
None
- retry_transient(operation, *, description, tries=5, initial_delay=0.05)[source]
Run
operation, retrying it on a transient Windows file-access error.- Parameters:
operation (Callable[[], T]) – The operation to run. It is called again from the start on each retry, so it must be safe to repeat after a failed try (e.g. it cleans up its own temporary file before raising).
description (str) – What the operation does, for the log message (e.g.
"replace deck.fst").tries (int, optional) – The maximum number of calls (default 5).
initial_delay (float, optional) – The pause before the first retry in seconds (default 0.05), doubled before each further retry – about 0.75 s in all with the defaults.
- Returns:
T – The operation’s return value.
- Raises:
ValueError – If
triesis less than 1 orinitial_delayis negative.PermissionError – The last transient error, once
triescalls have failed.Exception – Any non-transient error raised by
operation, at once.
- Return type:
_T
- rmtree_with_retry(path)[source]
Delete a directory tree with
shutil.rmtree(), with retry.A retry resumes the deletion: whatever the failed try already removed stays removed, and the tree’s root, which is removed last, still exists.
- Parameters:
path (str or pathlib.Path) – The directory to delete.
- Return type:
None
- unlink_with_retry(path, *, missing_ok=False)[source]
Delete a file with
pathlib.Path.unlink(), with retry.- Parameters:
path (str or pathlib.Path) – The file to delete.
missing_ok (bool, optional) – Whether a missing file is ignored (default
False).
- Return type:
None