Skip to content

Architecture overview

Generated at render time from the live module docstrings of pyflightstream 0.29.0.dev0; this page has no text of its own beyond the layer diagram, so it can never drift from the code.

Offline fallback: pyflightstream.overview() renders this same page from the installed package, no docs site needed.

Layer diagram

Dependencies flow strictly downward: every module imports only modules of the rows below its own, never upward.

post  qa                                engineering data | probe and regression evidence
   |
run  workspace                          headless execution | input library, run layout, manifest
   |
cases                                   simulation and campaign definitions
   |
script  results                         validating script builder | output parsers
   |
commands                                the evidence-backed per-version command database
   |
versions                                canonical version identifiers and ordering
   |
_atmosphere                             the standard atmosphere, importing only the base exception below it
   |
_errors                                 the package base exception, imported by every layer and importing none

side branches, same downward-only import rule:
fsi                                     structural executable of the aeroelastic coupling loop
probes, farfield                        probe lattices and far-field conservation ledgers
reference, overview                     presentation: command reference and this page
utils                                   maintainer tooling outside the pipeline: reading a vendor manual, and writing documented rows back from that reading
options, exceptions, extras, testing    cross-cutting support: declared knobs | exception catalog | optional-extra refusals | quantified assertions

The package in its own words

pyflightstream: version-aware, didactic Python driver for FlightStream.

The package automates the FlightStream panel-method solver through its ASCII scripting interface. The FlightStream version is an explicit input: every command emitted is validated against the per-version command database in pyflightstream.commands, where each entry cites either the manual page documenting its command or a committed probe report measuring that the solver accepts one no edition documents, and carries empirical evidence of its status once probed on a licensed machine. The script builder refuses at build time what the solver would reject (or silently ignore) at run time.

Pipeline layers, dependencies flowing strictly downward:

  • versions: canonical YY.XXX version identifiers and ordering.
  • commands: the evidence-backed per-version command database.
  • script: the validating ASCII script builder.
  • results: anchor-based parsers for solver output files.
  • cases: simulation and campaign definitions.
  • run and workspace: execution, run manifest, and the managed workspace (input-artifact library plus run layout). The old files name was a deprecation shim and was removed at v0.4.0.
  • post: results into engineering data (sweep assembly, exports).
  • qa: probe harness and physics regression tooling.

Side packages follow the same downward-only rule:

  • fsi: the structural executable of the aeroelastic coupling loop.
  • probes and farfield: probe lattices for far-field surveys and the conservation ledgers computed on them.
  • reference: the command reference renderer behind help().
  • utils: maintainer tooling outside the run pipeline entirely, imported by nothing a campaign executes (reading a vendor manual against the command database).

Cross-cutting support modules, importable from any layer:

  • options: the declared, validated machine and QA knobs (get_option/set_option also re-exported here at top level).
  • exceptions: the single catalog of every exception and warning.
  • testing: public assertions with quantified violation reports.

Where to start:

  • pyflightstream.help opens the offline HTML command reference, rendered from the installed command database.
  • pyflightstream.overview opens the offline HTML architecture overview, rendered from the live module docstrings.
  • The published docs site carries the same reference and overview plus the compatibility matrix and worked examples.

pyflightstream._errors

The package base exception and the shared refusals, below every layer.

Pipeline role: below every layer, imported by all of them. It imports nothing from this package, which is the whole reason it exists as a separate module: the public catalog (pyflightstream.exceptions) imports the exception classes from their home modules, so those home modules cannot import their base back out of the catalog without a cycle.

The module is private and the classes are not. Import them from the catalog, or from the public module that re-exports each one:

from pyflightstream.exceptions import PyflightstreamError

Almost every class stays defined in its home module, next to the physics or the version rule it explains, which is the didactic policy. TWO KINDS sit here instead, and the difference is the import direction rather than a taste for grouping:

  • the base, because every home module needs it and it can need none of them;
  • an exception type MORE THAN ONE LAYER names. An exception type is vocabulary rather than behaviour, so a layer that needs a shared name is not reaching upward for work, and making it reach anyway is what produced the call-time imports the layering guard now refuses (OPS-2007.02.01). InputArtifactError is the one such class today; it is re-exported by pyflightstream.workspace, which is where its docstring sends a user and where it has always been caught.

A THIRD KIND arrived with PyflightstreamWarning (OPS-2006.02.02), and its reason is neither of those two. A warning category is named on a command line, in -W error::module.Class, and that filter is parsed by IMPORTING module before anything else in the process runs. A category whose home imports half the package makes every such command pay for the import and fail on the FILTER rather than on a warning when the home cannot be imported at all. This module imports nothing from the package and only the standard library, which is exactly what a warnings filter wants to name.

A FOURTH KIND is not a class: warn, the one route of the package's own warnings (RPT-058). A campaign post collects the warnings of its own thread with collecting_warnings, a sink held in a ContextVar, where warnings.catch_warnings swapped filters that are process-wide, so two posts in two threads each logged the other's warnings and one post's silenced sweep table silenced the other's. It lives here because every layer warns and this module is below all of them.

Neither kind changes the name a user catches. Adding a class here is a deliberate decision about layering, never a convenience.

pyflightstream._atmosphere

The standard atmosphere, below every layer (PFS-2027.03).

Pipeline role: a FLOOR rather than a stage. It imports nothing from this package except the base exception, and several layers need it. Nothing flows THROUGH it, which is the distinction pyflightstream.overview draws between the two shapes; pyflightstream._errors is the other module of this kind and the precedent this one follows.

WHY IT IS NOT IN utils. That was asked directly, and the answer is the package's own declared architecture rather than a preference. utils is a SIDE BRANCH in the layer table, "maintainer tooling outside the pipeline", and nothing in the pipeline imports it. Putting the atmosphere there would create the first pipeline-to-utils dependency, in the one module family whose position in the layer rule is undefined, and would answer that open question by accident in the direction nobody argued for.

WHY IT IS SEPARATE FROM THE RESOLUTION THAT USES IT. The split is the design and it pays for itself in testing. The physics here can be checked against published ISA tables with no case, no matrix and no script. The resolution of a flight condition -- deciding which unknown the given keys leave and refusing an under- or over-determined set -- is placed with its caller in the workspace layer, downstream of reference binding, so that no layer below can resolve a Reynolds constraint before a reference exists to measure it against. In one module neither half is testable on its own.

WHAT THIS MODULE DOES NOT DO. It does not emit. The script layer's helpers.atmosphere is the emitter and stays one: it writes AIR_ALTITUDE or the explicit fluid properties and computes nothing. It cannot serve this feature for two reasons, and they are not equally well evidenced, so they are stated separately.

THE FIRST IS A FACT ABOUT THE GRAMMAR. AIR_ALTITUDE has no argument for an ISA deviation. So any condition carrying dISA must be emitted as explicit fluid properties computed on this side, and so must the solved-density path, which is not an atmosphere point at all. That one needs no probe: it is the command's own signature.

THE SECOND IS WEAKER THAN AN EARLIER VERSION OF THIS DOCSTRING SAID, and the correction is worth keeping. AIR_ALTITUDE is recorded broken on three supported builds, 26.100, 26.101 and 26.120. That count is exact. What those three rows RECORD is only that the command ran and the expected density was not observed. Only 26.120 has a reading behind it: 1.056 against the 0.736 expected, where 1.056 is the 5000-FOOT standard state and 0.736 the 5000-METRE one, seen twice on build 7012026 (RPT-014). That reading is carried by CMP-26120_2026-07-21_full.yaml and RPT-014 rather than by the 2026-08-08 row the command database cites, which carries the same boilerplate as the others. On 26.100 and 26.101 the probe recorded absence and nothing more.

This docstring previously said all three read their metres argument as feet, and cited the compat evidence for it. The evidence does not support that, which is weaker than refusing it and is what is true: commands/boundary_conditions.yaml says in its own notes that the "reads ignored" sentence is the probe spec's STATIC effect_note rather than a per-run reading, and proves it by pointing out that the same sentence appears in 26.121's row under outcome verified. Whether those builds read every altitude in feet is an open question about three solvers and needs the density read back on 26.100 and 26.101 as well.

What survives is still enough to decide the design, which is why the conclusion did not move: a command that is broken on three of nine registered builds is refused by the emitter unless a caller passes allow_broken, and on 25.000 it takes a bare value read in FEET. So computing here and emitting explicit properties is the more robust route across the supported range. That argument rests on the recorded STATUS, which is committed and dated, and not on a mechanism nobody measured.

THE CONVENTION THAT IS WORTH WRITING DOWN. An ISA deviation moves temperature and leaves PRESSURE alone, so density follows from the offset temperature at the unchanged pressure altitude. That is the standard reading of "ISA+5". The other reading, shifting pressure too, is what a reader who has not met the convention will assume, which is why it is stated here and asserted in tests/tier1_offline/test_atmosphere.py rather than left to be inferred from the arithmetic.

delta_isa_c is a DELTA in Celsius, and a temperature difference in Celsius and in Kelvin is the same number. So no conversion exists here to get wrong, and its absence is deliberate rather than an omission.

SOURCE OF THE CONSTANTS, cited rather than recalled. The atmosphere is ISO 2533:1975, published identically as ICAO Doc 7488/3, whose defining constants are reproduced in ISA below. The viscosity law is Sutherland's, with the air coefficients of White, Viscous Fluid Flow, 3rd ed., eq. (1-36).

Every ATMOSPHERE constant is from ISO 2533 and every VISCOSITY constant from White. METRES_PER_FOOT is neither: it is the defining value of the international foot, exact by definition rather than by measurement. It is called out because an earlier version of this sentence said "every constant this module uses is in one of those two places", and its own module falsified that sixty lines below.

ONE CITATION IS NOT CHECKABLE IN THIS REPOSITORY, and saying so is the honest form. No copy of White is committed here, so SUTHERLAND_REFERENCE_TEMPERATURE_K and SUTHERLAND_CONSTANT_K rest on a page reference nobody in this tree can resolve. A V and V pass noted that the triplet as commonly printed with eq. (1-36) is 273 K and 111 K, while 110.4 K is the classical Sutherland constant that circulates widely. MEASURED, rather than estimated: against this module's own reference viscosity the two readings differ by about 0.0492 percent at sea level, 0.0536 percent at 300 K and 0.0164 percent at the tropopause, where the two forms come closest. That figure read "roughly 0.1 percent" until a release review recomputed it and found it about twice the sea-level spread and several times the tropopause one, which matters because the tropopause is where a cruise Reynolds number is actually formed. The difference propagates into every density solved on the REmi path. Which triplet belongs to that equation needs the book, and it is the domain-expert seat's call rather than this module's.

THE OPEN QUESTION HAS A MECHANICAL TRIGGER, which is worth stating so the arbiter knows the cost of each answer: tests/tier1_offline/test_atmosphere.py pins viscosity at three temperatures to rel=1e-4, and the 273/111 reading differs by 1.64e-4 at 216.65 K, 3.59e-4 at 255.65 K and 5.36e-4 at 300 K. So resolving the citation the other way turns this file RED at ALL THREE of its points rather than passing silently, which is the right behaviour and is not an accident.

This paragraph said "two of its three points" and quoted 5.8e-4 at 300 K until a round-two review pass caught it disagreeing with the sentence above it. Both were wrong in the direction that makes the open question look cheaper than it is: the tropopause point is the closest of the three and it still exceeds the tolerance by a factor of 1.6. Recomputed rather than re-remembered.

WHAT THIS DOES NOT CLAIM. The model is the standard atmosphere, not the weather: it says what ISA defines at a pressure altitude, and nothing about any particular day. Above the ceiling in ISA it refuses rather than extrapolating, because ISO 2533 continues with further layers this module does not implement and a silent extrapolation would be wrong in a way no caller could see.

pyflightstream.versions

Canonical FlightStream version identifiers and their ordering.

Pipeline role: the bottom of the pipeline proper, above only _errors, the base-exception module that imports nothing. Everything else asks this module which FlightStream versions exist and how they are ordered.

Canonical identifiers use the YY.XXX three-digit scheme (for example 26.120 for the vendor release named 26.12); the last digit indexes vendor builds within one minor release. It is an ORDERING position and not a claim of descent: 26.101 sits behind 26.100 and is an independent release, so whether a build inherits its base release's command evidence is stated per build (FsVersion.inherits_base) rather than read off the digit. Neither string nor float comparison orders vendor names correctly ("26.1" versus "26.12"), so the ordered list in commands/_meta.yaml is the only ordering authority.

pyflightstream.commands

The FlightStream command database and per-version registry.

Pipeline role: single source of truth for which ASCII commands exist in which FlightStream version, with typed arguments, script layout, emission phase, and one evidence citation per entry: the manual page documenting it (manual_ref), or a committed probe report measuring that the solver accepts a command no edition documents (probe_ref). The script builder validates every emission against this database.

Data lives in the YAML files next to this module, one file per manual chapter; _meta.yaml holds the ordered version list, which is the only ordering authority (CONTRIBUTING.md invariant 4). Version keys in the YAML files are quoted strings ("26.120"); an unquoted key would be parsed as a float and rejected by the loader.

Statuses follow the evidence rules of CONTRIBUTING.md invariant 3: documented cites the manual through manual_ref, or a committed report through probe_ref where no edition documents the command; verified and broken additionally cite a committed probe report; removed says which of three things happened, since an edition stating a withdrawal, an edition merely going quiet, and a probe measuring the solver refusing the name are not the same claim; the measured case cites its run, through report when the harness promoted it and through probe_ref for the removals recorded before the harness could; and a successor is recorded where one is known. The single home of that rule is docs/srs/data-model.md.

A command whose argument grammar differs between versions declares the grammar of the latest documented version in args and overrides it per version through versions.<v>.args; the per-version view resolves the override, so the script builder binds and renders the grammar of its target version (the four-versus-three argument forms of CREATE_BULK_SEPARATION, SRC-003 p.342 versus SRC-725 p.341, are the motivating case).

pyflightstream.script

The validating FlightStream script builder.

Pipeline role: turns typed Python calls into the ASCII script text the solver executes, validating every emission against the per-version command database before a single line reaches FlightStream. Errors happen at build time with manual citations, because solver-side failures are silent or cryptic.

A Script is an ordinary object bound to one FlightStream version; two scripts coexist safely, and there is no module-level state. Emission is checked in order: command exists in the version, command is not recorded broken there, argument binding and types, enum membership, count-versus-list consistency, phase ordering, and cross references. The raw() escape hatch bypasses validation and flags the script for the run manifest.

A command whose per-version record is broken is refused by default (FR-48), because a probe measured that it does not do what the manual says, so emitting it hands the run a wrong number rather than an error. AIR_ALTITUDE on 26.120 is the sharp case: the licensed sweeps observed the 5000 FOOT standard density where 5000 metres was asked for, so the METERS argument read as ignored and the altitude the script asked for would not be the altitude solved, with nothing in the run saying so because the script was fully validated. Refusing to emit it is the only place that fact can still reach the caller. Script.allow_broken is the recorded way through the refusal, and the QA probe layer is its first caller, because re-measuring a broken record is the run that can unrecord it.

Cross references (SAD Section 4.2): an EntityRegistry counts the local coordinate systems, actuators, and motions the script creates, and rejects a command citing an index that does not exist yet, because FlightStream expects auxiliary definitions before they are referenced and fails silently otherwise. Creation commands accept an optional label, and every entity-citing argument then takes the index or the label, so recipes can speak in configuration terms while the library maintains the label-to-index dictionary. Entities carried by an opened project file, including the mesh boundary inventory (by count or by a name-to-index mapping), are declared with Script.declare_existing; boundary citations are only range checked once the inventory was declared, because the boundary total lives in the geometry file and cannot be known statically.

The two gaps of the first cut are closed: the per-surface lines of INITIALIZE_SOLVER when SURFACES is not -1 (surface_toggles) and the PERIODIC symmetry copy count (symmetry_copies) are regular database arguments now, emitted comfortably through the curated helper layer in pyflightstream.script.helpers (SAD Section 4.3).

pyflightstream.results

Anchor-based parsers for FlightStream output files.

Pipeline role: reads solver output text files into typed results. Values are located by their printed labels (labeled_value) and tables by their header rows (delimited_table), never by fixed line numbers, so cosmetic layout changes between FlightStream versions do not silently corrupt data (SAD Section 8, PP-4). Completeness is structural: a missing footer or table terminator raises IncompleteOutputError, never a silently shorter table (FR-17).

The FlightStream version printed in each output is cross-checked against the requested version (FR-18). The printed string is coarser than the canonical scheme: the 26.120 build reports itself as Flightstream version 26.1, build #7012026 (observed in the committed fixtures), so the check compares by alias prefix and records the reported string and build verbatim; the build number is the precise discriminator.

Number forms follow the solver's printing: .000 (no leading zero), 4380000. (trailing point), and 1.000E-05 all parse.

On top of the parsers, a pandas tabular layer turns the parsed results into DataFrames: to_table/to_csv for each parser, parse_run_loads for one run's coefficients, and run_table/sweep_table for one run or a whole sweep read from the manifest (the manifest, an execution-layer artifact, is imported lazily so the layer rule is not violated at module load).

Two vocabularies live here beside the parsers, both because every layer above needs them and none of them may own them.

DATA_ORIGIN_CODES and REDUCTION_CODES are the published answer to "did these numbers come off the run or out of a reduction" (PFS-2014.05). The tables carry the tokens as columns and the numeric-only writers carry the integers; the code sets are append only, because a file written last month is read with this table and cannot be asked what it meant.

EXPORT_CONVERSIONS classifies every phase: export command of the database as parsed, excluded, not-an-export or owed (PFS-2014.02), and the tier 1 suite compares its keys against the live census, so a new export command fails until somebody says which of the four it is. Ten of the eighteen are parsed here; one is owed, and its entry says why the solver rather than this package is what stands in the way.

THE COLUMN LAYOUTS OF THE FIVE NEWEST FORMATS ARE PINNED (FORCE_DISTRIBUTION_COLUMNS and its four siblings) rather than read from the file the way the probe export's are. A probe export's columns are chosen by the run; these are fixed by the solver, so a header that has moved means a build reordered the numbers, and a table read by position would publish one physical quantity under another one's label.

pyflightstream.cases

Simulation and campaign definitions.

Pipeline role: describes what to run. A SimCase (identified by sim_id) is one solver configuration with its sweep; a Campaign groups cases with the FlightStream version and the executable path, both required and explicit: nothing is read from environment variables or guessed (SAD Section 5). Native persistence is campaign.toml; the pipe-delimited matrix.fs run matrix is read unchanged, forever, by the matrix reader (pyflightstream.cases.matrix, FR-10).

Script recipes are explicitly imported functions satisfying the ScriptRecipe protocol: build(case, script) -> None. The campaign loop specializes the case per sweep point (filling SimCase.point) and the recipe translates it into script emissions, usually through the curated helpers. Recipe references are "package.module:function" strings, replacing the historical import-by-number system (PP-7, FR-12).

pyflightstream.run

Execution of FlightStream and the campaign loop.

Pipeline role: runs the solver headless on rendered scripts and lands every campaign point in the manifest with exactly one terminal status. run_campaign composes an Executor with the managed workspace of pyflightstream.workspace; there is no code path from "point started" to "loop continued" that does not write a status, so silent skips are structurally impossible (PP-5, FR-14). Failures accumulate into CampaignErrors, raised after the loop.

Before any execution, plan_campaign pre-flights the same campaign: it resolves every recipe, allocates the managed folders, verifies the geometry files exist, and builds every script in dry run (the builder validates phase, version, and entity references without a solver), returning one status per point and writing the plan summary into the campaign root. Re-running a campaign into the same root uses run_campaign(..., resume=True), which skips the points already recorded in the manifest; the manifest's append-only duplicate rejection is what makes the skip safe, and with resume=False a duplicate point raises before anything executes.

Every campaign that records a point also LEAVES ITS TABLE: at the end of the loop, and before any failure is raised, run_campaign writes the sweep csv into the workspace's post/ folder under SWEEP_TABLE_NAME, one line per point with the integrated forces and with each line stating whether it is a raw integration or a reduction and over what window (PFS-2014.03). Nobody has to ask for it, and a campaign whose points all failed still leaves the file, because the identity rows are the record of what was attempted.

The local mechanism is the documented command-line script execution: FlightStream.exe -script <file>, with the -hidden flag for windowless batch runs. ONE dash on the script argument, and the spelling is the module constant SCRIPT_ARGUMENT rather than a literal here: SRC-003 pp.279-280 documents the two-dash form, the 25 series does not accept it, and one dash is the spelling every registered build accepts (RPT-023). In hidden mode an abnormal termination writes FlightStreamLog.txt into the command execution directory, which is why the executor runs the solver inside the point's own datapoint folder and captures that file (SRC-003 p.280); a steady row of several points is one job and runs in the simulation folder. An HPC executor with the same interface is deferred (FR-15).

Judging solver quality (converged, iteration limited, diverged) needs the solver outputs, so run_campaign takes an OutcomeAssessor; the standard implementation is LoadsAssessor, built on the anchor-based parsers of pyflightstream.results.

Afterwards, reconstruct reads one manifest record back into the invocation that produced it: the command line, the working directory, the effective timeout and the script text, with a per-artifact verdict on whether each file still hashes to what the record says. That is the collectable half of NFR-07's promise, and package_vcs_state supplies the other end of it, recording which commit of this package ran.

pyflightstream.workspace

Managed campaign workspace: inputs, run files, and the manifest.

Pipeline role: owns where campaign files live, inputs and outputs alike. Folder layout, the reusable input-artifact library, staging of solver inputs, collection of outputs, and archiving are managed by the package, not by the user: folder identity mistakes were a recurring failure mode in the predecessor toolchain. Run identity lives in the manifest (runs.json), never in folder or file names; names are generated, English, and stable, and are never parsed for meaning (SAD Section 6). Human-readable names come from the output-only pyflightstream.workspace.naming.NamingTemplate.

The managed layout under a user-chosen campaign root, created by CampaignWorkspace.init (or pyfs-workspace init):

  • runs.json: the authoritative manifest, one record per executed point.
  • inputs/: the reusable input-artifact library (pyflightstream.workspace.inputs): geometries/, references/, setups/, pproc/ (groups/ until 0.11.0), profiles/, plus the executables.toml build registry; artifacts are declarative TOML resolved by stable id.
  • sims/sim_<sim_id>/: per-simulation folder with inputs/ (staged copies with recorded sha256), scripts/ (generated script text per point), and outputs/ (solver outputs as produced; the folder was called raw/ until 0.16.0, FR-84, and a workspace that holds one is still read). Until 0.13.0 a fourth folder, parsed/, was created here and written by nothing (PFS-2032.01): the typed extracts it was named for are built at campaign level under post/, so a workspace made by an earlier release may still carry an empty parsed/ per simulation, which is left where it is and refused by nothing.
  • post/: post-processing products (sweep tables and exports built by reading the manifest).
  • archive/: zipped completed simulations, manifest-driven.

Archiving and cleaning refuse to act when the manifest is missing or does not record the target simulation, so file management can never destroy an unrecorded run.

This package was renamed from pyflightstream.files in v0.3.0. The old module name re-exported everything with a DeprecationWarning for one minor release and was REMOVED at v0.4.0, on the horizon its own deprecation entry recorded; importing it now raises ImportError.

pyflightstream.post

Results into engineering data.

Pipeline role: the top of the pipeline, where parsed solver output becomes something a report can carry. TEN public inhabitants and one private one, and the list is what EXISTS rather than what is planned. Each says whether it is reached through this package or through its own module, which is stated rather than left to be discovered:

  • pyflightstream.post.writers writes flow-visualization exports (VTK legacy ASCII and Tecplot ASCII), each beside a settings record that lets the file be read alone. Re-exported here;
  • pyflightstream.post.unsteady reads a per-timestep field export back as an ordered series and averages it over a blade passage. Re-exported here;
  • pyflightstream.post.products writes the campaign's CSV products, the polar table per group, the sections table and the plots table per point, from the collected exports and the manifest (PFS-2029.15); the custom polar format beside the polar table when asked (PFS-2014.01.01), its writer and reader re-exported here; and a PROV-JSON provenance document per recorded run (PFS-2012.08.01). Its other writers (the polar row, the rotor table, the per-blade table, the unsteady polar) and the header of an unsteady polar's axes are reached through pyflightstream.post.products itself and are not re-exported here.
  • pyflightstream.post.superfile writes the SUPERFILE of each polar and group beside the polar table (FR-89), one row per converged point whose column set is a superset of everything the workspace knows about that simulation, and the measurement of what it wrote under reports/. Reached through its own module rather than re-exported here: everything it offers is called by the products stage, and a reader wanting it wants its page;
  • pyflightstream.post.series tables the stamped per-step exports of a windowed unsteady point, one table per export kind under the matrix's series/ (PFS-2031.18.01); its write_point_series is re-exported here. It is not write_series below, which writes the plots export's own history for the reductions;
  • pyflightstream.post.reductions is the writing seam that keeps a reduction from overwriting the file it came from. Re-exported here, and it was the one this list omitted while naming the module below, which this package does NOT re-export;
  • pyflightstream.post.settings_table projects a solver-flag snapshot into an all-numeric table, for tools that cannot read strings. Imported from its own module, because the projection is optional and lossy and a reader should meet its page first;
  • pyflightstream.post.axes is the ONE home of the frame conventions: the export's frame, the body, stability and wind axes with sideslip, the eighteen axis coefficients of a polar row, and where a blade is at a step. Reached through its own module, because a reader asking which frame a published column is in wants that page and nothing else;
  • pyflightstream.post.equations evaluates a pproc's [equations] over the averaged columns of an unsteady polar. Reached through its own module: the products stage is its caller, and a user writing an equation reads the generated WRITING-EQUATIONS.md first;
  • pyflightstream.post.guides writes the generated input guides beside a workspace's pproc artifacts: VARIABLES.md and WRITING-EQUATIONS.md, and since 0.27.0 the input glossary INPUTS.md, every key an input artifact may state (G08); since 0.28.0 also the input template input_template.md at the root of inputs/, a complete example of every kind of input file (G47). Re-exported here;
  • pyflightstream.post._tables is PRIVATE: the table primitives (the condition block, the CSV writer, the column renaming) the product modules share, so that no two of them import each other.

WHAT THIS LAYER DOES NOT HAVE, said plainly because this docstring advertised it for three releases and a reader has no other way to find out. There is no ResultArray facade: no interp_along, no reparametrize, no trim. FR-20 carries that promise and is pending; AD-06 sends the interpolation half to the sister library. Sweep assembly is not here either, it is pyflightstream.results.tables.

pyflightstream.qa

Probe harness and physics regression tooling.

Pipeline role: produces the evidence behind the command database. Tier 2 probes (pyflightstream.qa.probes) execute each database command in a minimal script on a licensed machine and classify it into four outcomes: verified, broken and removed are promotable evidence, and unprobed records why no judgment exists. A build whose solver refuses the name is removed rather than broken, because the two produce different refusals for a caller. A command that runs but does nothing is broken, not verified; pyflightstream.qa.compat writes the compat report under reports/compat/ and promotes database statuses from it. Tier 3 is here as well: the physics judge, reductions and references (pyflightstream.qa.physics), the version-comparison drift diff (pyflightstream.qa.drift), and since 0.13.0 the driver that reads both out of a campaign workspace (pyflightstream.qa.matrix): the cases are rows of the workspace's physics matrix, run through the run layer over the workspace's synthetic library, so no research geometry is needed and no case is stated in Python (PFS-2031.17). pyflightstream.qa.reports holds the report-naming and never-overwrite rule all three writers share, so a run is refused before a licensed seat is spent rather than after. The pyfs-qa CLI (pyflightstream.qa.cli) drives all three.

One member of this package spends no seat at all and reads no report: pyflightstream.qa.cost builds the wall-time cost view from campaign manifests alone, points down and solver builds across, so a build that got slower on the same points can be shown. FR-19 has recorded the field since the v0.3 line; this is what reads it.

pyflightstream.fsi

Fluid-structure interaction coupling for rotating blades.

Pipeline role: this subpackage implements the external structural executable of the FlightStream Aeroelastic Toolbox loop (M6, DLV-007; FR-23a). Per coupling call FlightStream exports sectional loads on user-defined surface sections; the executable reads them together with its configuration and persisted state, solves one beam per blade, converts the solution to nodal translations, and writes the displacement file back for the solver to deform the mesh. All exchange happens in the rotating blade frames; this package never handles azimuth or global-frame transforms (FSI-R02).

The structural backend is PyNite (PyPI distribution PyNiteFEA, import name Pynite), pulled in only by the optional [fsi] extra; importing pyflightstream.fsi itself stays dependency free so the core package works without the extra installed.

The evidence status of the structural model is recorded in DLV-007 Section 2: formulas live in small isolated functions with their source cited in the docstring, so a later primary-source correction stays a localized change.

pyflightstream.probes

Probe lattices for far-field extraction surveys.

Pipeline role: defines the serializable cylindrical probe lattice (stations, ring edges, uniform azimuths) that both the FlightStream probe export and the companion CFD extraction sample, and generates the version-validated script lines that create and export the probes (NEW_PROBE_POINT / PROBE_POINTS_IMPORT, UPDATE_PROBE_POINTS, EXPORT_PROBE_POINTS; SRC-003 pp.362-363). One lattice object is the single source of probe positions for every solver, so cross-solver data loading is transparent by construction (design note DLV-006 Sec. 2, requirement R1).

Coordinates: cylindrical (x, r, psi) aligned with the shaft axis, x positive downstream, origin at the disk center, lengths nondimensionalized by the tip radius. The Cartesian mapping is y = r sin(psi), z = r cos(psi): psi = 0 points along +z (up) and grows toward +y. A tier-1 test pins this convention (DLV-006 Sec. 3.1: fix it once, in code, with a test).

Azimuthal spacing is uniform by construction: the lattice stores only the azimuth count, so a nonuniform spacing is unrepresentable. Uniform periodic sampling is what makes the azimuthal rectangle rule spectrally accurate and the FFT direct (DLV-006 Sec. 2.2); this is a hard design constraint, not a default.

pyflightstream.farfield

Far-field conservation ledgers on the probe lattice.

Pipeline role: turns probe-sampled flow fields into the discrete conservation ledgers of the far-field extraction (design note DLV-006 Sec. 3): one annular quadrature, an azimuthal FFT harmonic layer, and the force, moment, and loss-channel ledgers, all on xarray structures with dims (station, r, psi).

Field names on the dataset: axial velocity u (m/s, +x downstream), transverse Cartesian v (+y) and w (+z), cylindrical v_r and v_theta (derived, see cylindrical_components), pressure perturbation p_prime = p - p_inf (Pa). Radii and stations are nondimensionalized by the lattice tip radius; integrals are returned in the field units times square meters (the tip radius rescales the quadrature).

Solver asymmetry, by design (DLV-006 Sec. 2.3): the FlightStream side runs the purely kinematic, reversible ledgers (momentum, angular momentum, crossflow kinetic energy) with the constant free-stream density. The rothalpy-based irreversible machinery (rothalpy, irreversible_deficit) runs on the Euler CFD side only, where total enthalpy and entropy exist; any cross-solver delta in a reversible channel is numerics, never physics.

The azimuthal rectangle rule is spectrally accurate only on the uniform azimuth spacing the lattice guarantees by construction; the harmonic layer re-checks the spacing and refuses anything else, because a nonuniform azimuth grid silently destroys both the quadrature accuracy and the FFT (DLV-006 Sec. 2.2).

pyflightstream.reference

Command reference and compatibility matrix rendered from the database.

Pipeline role: presentation layer sitting above commands and versions; nothing imports it back. This module is the single rendering source for both delivery layers of the reference: pyflightstream.help() renders a self-contained HTML page and opens it in the default browser (layer 1, the offline fallback), and the markdown generators feed the docs site at build time (layer 2, the published docs). Both layers read the same database through the same extraction helpers, so they can never disagree.

pyflightstream.overview

Architecture overview rendered from the live module docstrings.

Pipeline role: presentation layer sitting above every subpackage; nothing imports it back. This module is the single rendering source for both delivery layers of the architecture overview: pyflightstream.overview() renders a self-contained HTML page and opens it in the default browser (layer 1, the offline fallback), and markdown_overview feeds the docs site at build time (layer 2, the published docs). Both layers read the same source: the actual top docstrings of the package and its subpackages, imported at call time, so the overview can never drift from the code it describes.

This is the LibraryHelp companion of the CommandHelp in pyflightstream.reference: help() answers "which commands exist and with what evidence", overview() answers "how the package is put together and where to start".

pyflightstream.options

Runtime options registry: the package's declared, validated knobs.

Pipeline role: cross-cutting support module (imports nothing from the pipeline; any layer may consume it). Machine-dependent and quality-assurance knobs (scratch roots, solver timeouts) live here as registered options instead of scattered literals, following the pandas register_option model: every option is declared once with a default, a docstring, and a validator, and every access goes through get_option / set_option.

Three deliberate differences from pandas:

  • Keys are exact, never pattern-matched: get_option("qa.probe") does not resolve qa.probe_timeout_s. Partial matching makes every new key a potential silent behavior change of existing code.
  • The registry refuses unknown keys with the full known-key list (openmdao message contract: the error names the option, the value, and what would have been accepted).
  • describe_option returns the text instead of printing it; use print(describe_option()) at the REPL.

The options serve programmatic drivers of the package (scripts, notebooks, campaign code calling the qa entry points); a terminal user of the CLIs tunes the same quantities per invocation through the command-line flags whose defaults read from here.

Examples

from pyflightstream import options options.get_option("qa.probe_timeout_s") 120.0 options.set_option("qa.probe_timeout_s", 60.0) with options.option_context("qa.scratch_root", "D:/scratch"): ... pass # runs launched here scratch under D:/scratch options.reset_option("qa.probe_timeout_s")

Options are process-wide state: they configure the machine the process runs on (where scratch goes, how long a solver may take), never the physics of a case. Anything that changes a result belongs in the case definition or the workspace, where it is recorded by the manifest.

pyflightstream.exceptions

Single public catalog of every pyflightstream exception and warning.

Pipeline role: cross-cutting support module, after the pandas pandas.errors model. Every exception or warning class the package can raise is importable from here under one roof, so user code catches without knowing which pipeline layer raises, and the completeness is test-asserted: a new exception class that does not join this catalog fails the suite. The catalog imports on a base install; only the modules an optional extra gates keep their classes in import-light homes.

Every exception here descends from PyflightstreamError, so one except clause catches every CATALOGUED exception (SRS FR-39), and each also keeps the standard-library base it had before that class existed, so except ValueError and except RuntimeError keep catching exactly what they used to. Catalogued is the operative word and the requirement says so in bold: a residual of bare standard-library raises survives outside this catalog. Every site the guard's walk REACHES is named in the ratchet in tests/tier1_offline/test_exceptions_catalog.py, which is the single home of that list; the walk's own reach is stated in SRS FR-39, and at least one site sits outside it. Until the residual is empty, the standard-library bases are what covers it, and the plural matters: the residual is mostly ValueError and also holds TypeError and RuntimeError sites, so being exhaustive today means catching PyflightstreamError and those bases together.

THE CATALOG HAS TWO ROOTS, not one root and one stray. Every exception descends from PyflightstreamError, and every WARNING descends from pyflightstream._errors.PyflightstreamWarning, which parents pyflightstream._errors.PyflightstreamDeprecationWarning and pyflightstream.results.VersionMismatchWarning. So a caller selects every warning this package raises with one category, which is what the examples build's filter rests on. This paragraph named ONE catalogued warning and called it the single member outside the hierarchy; that stopped being true when the warning base arrived, and a review pass on 2026-08-20 found it still saying so.

Examples

from pyflightstream.exceptions import MatrixError try: ... raise MatrixError("demo") ... except MatrixError as error: ... print(error) demo

from pyflightstream.exceptions import PyflightstreamError issubclass(MatrixError, PyflightstreamError), issubclass(MatrixError, ValueError) (True, True)

The classes stay defined in their home modules (the didactic policy wants the refusal next to the physics it explains); this module only re-exports. Structured refusals carry their facts as attributes where the message alone would force parsing: see pyflightstream.versions.UnknownVersionError (version, known), pyflightstream.versions.AmbiguousVersionAliasError (alias, candidates) and pyflightstream.workspace.InputArtifactError (kind, artifact_id, available).

pyflightstream.extras

Optional extras: what each one installs, and one refusal for all of them.

Pipeline role: cross-cutting support module, deliberately import-light. It imports nothing but the package base exception, so every gated module can raise from it without the refusal itself needing the thing that is missing.

An extra is a promise with two halves: the distribution set pyproject.toml installs under that name, and the message a user sees when they reach the gated code without it. The two halves used to live apart, and the second was three different exception types with three hand-written strings: ModuleNotFoundError in pyflightstream.fsi.beam, a bespoke class in pyflightstream.probes.geometry, and a bare ImportError in pyflightstream.results.tables. A caller who wanted to handle "an extra is missing" had to know all three, and nothing checked that the remedy each one printed was the remedy that works (review finding PYFS-025).

MissingExtraError is that one type, and its remedy is BUILT from the extra's name rather than typed, so a message can no longer name an extra that does not exist or spell an install command that does not work.

pyflightstream.testing

Public testing assertions with quantified violation reports.

Pipeline role: cross-cutting support module, after the public testing namespaces of numpy and pandas. User campaigns and this repository's own suites compare two kinds of artifacts, and each gets the matching assertion under the project's golden philosophy split:

  • Solver scripts and other deterministic ASCII artifacts compare exactly: assert_scripts_equal reports the first differing line and the total count of differing lines, never a fuzzy match.
  • Numeric records (parsed coefficients, ledger values) compare within tolerances: assert_records_close reports how many values were compared, how many violate, and the worst offender, so a failure names the size of the disagreement instead of a bare mismatch.

Failure messages carry statistics because a lone "not equal" hides whether one digit moved in one value or the whole record diverged; the count and the worst offender make the difference visible at the first read.

pyflightstream.support

What "supported" means, per registered FlightStream version.

Pipeline role: cross-cutting reporting layer above the script builder. It answers one question the rest of the package could only answer by implication, and answers it in named values rather than in prose.

The package registers a version by adding it to the ordered list in commands/_meta.yaml, and from that moment every public surface called it supported. That word covered four very different states at once, and the widest gap between two of them was measured by the independent review, and it is quoted here as the measurement of 2026-08-02 that it was rather than as a present fact: 26.000 was registered, was accepted by Script(version="26.000"), and carried evidence for zero of the database's commands, so nothing whatsoever could be built for it. The README said as much in a sentence; nothing said it in a value a caller could read (finding PYFS-019, SRS FR-49). That build's own manual was read on 2026-08-10 and it now carries 274 emittable commands, which is the levels working: the value moved without anyone editing a claim.

The four levels, and every one of them is DERIVED. Nothing here is declared in a file, because a hand-set support level is exactly the kind of claim that outlives the fact behind it:

registered In the ordered list, and no command is available. The identifier exists so that campaigns naming it are refused by name rather than by accident, and so evidence can be backfilled later. documented Commands are available, drafted from the manual with page citations. No command has been measured against a running solver on this version. verified At least one command carries probe evidence this version can reach: a committed report either confirmed it or recorded it broken. Both count, because both are measurements. On a hotfix build the record may be the base release's, inherited: see pyflightstream.commands.CommandEntry.evidence_in and the compatibility matrix, which marks every inherited cell. operational Verified, and the minimal end-to-end workflow of minimal_workflow builds for it. This is the level that claims a user can actually get from geometry to a loads file.

The ladder is ordered and total: every registered version sits at exactly one level, and the levels ascend in the order above.

Why operational is not simply "verified plus some commands": the review asked for a level whose claim is checkable end to end, and a version can carry probe evidence for a scattering of commands while lacking one link of the chain that produces a result. So the level is defined by a workflow that builds, and a tier 1 test builds it for every version this module reports operational. The claim and its check are the same object.

pyflightstream.utils

Maintainer utilities that are not part of a run.

Pipeline role: none. Nothing in the run pipeline imports this subpackage, which is what keeps it outside the layer rule rather than at a position within it (CONTRIBUTING.md Layout, AD-01).

The two positions are stated separately, because one declaration for the subpackage was wrong for half of it. pyflightstream.utils.manual and pyflightstream.utils.errors import nothing from this package and sit at the bottom beside pyflightstream._errors, so they can be used from any layer. pyflightstream.utils.cli is an ENTRY POINT above pyflightstream.commands, which it reads to answer what the database already records, the same shape as pyflightstream.reference.

What belongs here is the work of KEEPING the package current rather than of using it: reading a new vendor manual, comparing it against the command database, reporting what a new release added. That work runs on the maintainer's machine against licensed material in _private/.

ITS OUTPUT WAS A DRAFT A PERSON REVIEWS AND NEVER A DATABASE WRITE, and that stopped being true on 2026-08-17. pyfs-manual register writes documented version rows for the commands a new edition describes exactly as its predecessor did, which is the one write that carries no judgement: it copies nothing and asserts nothing beyond "these two editions say the same thing", and anything they do not it reports for a person to read. Everything else here is still a draft.

The layer statement above is unchanged by it, and the split is why. pyflightstream.utils.manual.insert_version_row takes the text of a chapter file and returns the edited text; it opens nothing.

THE IO LIVES IN pyflightstream.utils.database, and this paragraph said cli until 2026-08-17, when the registration transaction was lifted out of the argument parser. That module reads and writes the chapter bytes, validates every edit against the command schema before any of them reaches the disk, and is the one importable home of a registration.

SO THIS SUBPACKAGE HAS THREE POSITIONS, not the two the statement above enumerates: manual and errors at the bottom, and cli and database above pyflightstream.commands. database is deliberately NOT re-exported from this module, and that is load bearing rather than an oversight: re-exporting it would make import pyflightstream.utils pull pyflightstream.commands in, and cost manual the property that it can be used from any layer. Import it by its own name.

What does NOT belong here is anything a user's run depends on. A helper that a script, a campaign or a parser needs is part of that layer and belongs in it; putting it here would make utils a bag that everything reaches into, which is the shape this subpackage is most at risk of becoming.