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.runandworkspace: execution, run manifest, and the managed workspace (input-artifact library plus run layout). The oldfilesname 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.probesandfarfield: probe lattices for far-field surveys and the conservation ledgers computed on them.reference: the command reference renderer behindhelp().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_optionalso 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.helpopens the offline HTML command reference, rendered from the installed command database.pyflightstream.overviewopens 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).
InputArtifactErroris the one such class today; it is re-exported bypyflightstream.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 theexecutables.tomlbuild registry; artifacts are declarative TOML resolved by stable id.sims/sim_<sim_id>/: per-simulation folder withinputs/(staged copies with recorded sha256),scripts/(generated script text per point), andoutputs/(solver outputs as produced; the folder was calledraw/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 underpost/, so a workspace made by an earlier release may still carry an emptyparsed/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.writerswrites 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.unsteadyreads a per-timestep field export back as an ordered series and averages it over a blade passage. Re-exported here;pyflightstream.post.productswrites 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 throughpyflightstream.post.productsitself and are not re-exported here.pyflightstream.post.superfilewrites 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 underreports/. 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.seriestables the stamped per-step exports of a windowed unsteady point, one table per export kind under the matrix'sseries/(PFS-2031.18.01); itswrite_point_seriesis re-exported here. It is notwrite_seriesbelow, which writes the plots export's own history for the reductions;pyflightstream.post.reductionsis 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_tableprojects 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.axesis 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.equationsevaluates 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 generatedWRITING-EQUATIONS.mdfirst;pyflightstream.post.guideswrites the generated input guides beside a workspace's pproc artifacts:VARIABLES.mdandWRITING-EQUATIONS.md, and since 0.27.0 the input glossaryINPUTS.md, every key an input artifact may state (G08); since 0.28.0 also the input templateinput_template.mdat the root ofinputs/, a complete example of every kind of input file (G47). Re-exported here;pyflightstream.post._tablesis 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 resolveqa.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_optionreturns the text instead of printing it; useprint(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_equalreports 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_closereports 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.