pyflightstream 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyflightstream-0.2.0/LICENSE +21 -0
- pyflightstream-0.2.0/PKG-INFO +88 -0
- pyflightstream-0.2.0/README.md +55 -0
- pyflightstream-0.2.0/pyproject.toml +80 -0
- pyflightstream-0.2.0/setup.cfg +4 -0
- pyflightstream-0.2.0/src/pyflightstream/__init__.py +27 -0
- pyflightstream-0.2.0/src/pyflightstream/cases/__init__.py +332 -0
- pyflightstream-0.2.0/src/pyflightstream/cases/matrix_legacy.py +337 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/__init__.py +544 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/_meta.yaml +18 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/actuators.yaml +167 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/advanced_settings.yaml +136 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/aeroelastic_coupling.yaml +172 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/boundary_conditions.yaml +153 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/ccs_wing_mesh.yaml +74 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/coordinate_systems.yaml +123 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/file_io.yaml +82 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/mesh_import_export.yaml +72 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/motion_definitions.yaml +185 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/probe_points.yaml +116 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/runtime_settings.yaml +160 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/scenes.yaml +14 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/script_controls.yaml +39 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/simulation_controls.yaml +30 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/solver_analysis.yaml +118 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/solver_export.yaml +138 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/solver_initialization.yaml +95 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/solver_settings.yaml +120 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/streamlines.yaml +63 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/surface_sections.yaml +145 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/sweeper.yaml +115 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/unsteady_solver.yaml +68 -0
- pyflightstream-0.2.0/src/pyflightstream/commands/volume_sections.yaml +148 -0
- pyflightstream-0.2.0/src/pyflightstream/farfield/__init__.py +613 -0
- pyflightstream-0.2.0/src/pyflightstream/files/__init__.py +375 -0
- pyflightstream-0.2.0/src/pyflightstream/fsi/__init__.py +57 -0
- pyflightstream-0.2.0/src/pyflightstream/fsi/beam.py +417 -0
- pyflightstream-0.2.0/src/pyflightstream/fsi/centrifugal.py +418 -0
- pyflightstream-0.2.0/src/pyflightstream/fsi/cli.py +206 -0
- pyflightstream-0.2.0/src/pyflightstream/fsi/config.py +316 -0
- pyflightstream-0.2.0/src/pyflightstream/fsi/driver.py +501 -0
- pyflightstream-0.2.0/src/pyflightstream/fsi/kinematics.py +153 -0
- pyflightstream-0.2.0/src/pyflightstream/fsi/loads.py +740 -0
- pyflightstream-0.2.0/src/pyflightstream/fsi/nodes.py +463 -0
- pyflightstream-0.2.0/src/pyflightstream/fsi/state.py +181 -0
- pyflightstream-0.2.0/src/pyflightstream/post/__init__.py +18 -0
- pyflightstream-0.2.0/src/pyflightstream/post/writers.py +195 -0
- pyflightstream-0.2.0/src/pyflightstream/probes/__init__.py +453 -0
- pyflightstream-0.2.0/src/pyflightstream/probes/geometry.py +281 -0
- pyflightstream-0.2.0/src/pyflightstream/probes/planar.py +477 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/__init__.py +59 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/cli.py +364 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/compat.py +285 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/drift.py +406 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/geometry.py +421 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/physics.py +1744 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/probes.py +1023 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/references/PHY-01.yaml +38 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/references/PHY-02.yaml +28 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/references/PHY-05.yaml +29 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/references/PHY-06.yaml +90 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/references/SMI-01.yaml +28 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/references/SMI-02.yaml +28 -0
- pyflightstream-0.2.0/src/pyflightstream/qa/specs.py +1405 -0
- pyflightstream-0.2.0/src/pyflightstream/reference.py +465 -0
- pyflightstream-0.2.0/src/pyflightstream/results/__init__.py +547 -0
- pyflightstream-0.2.0/src/pyflightstream/run/__init__.py +677 -0
- pyflightstream-0.2.0/src/pyflightstream/script/__init__.py +474 -0
- pyflightstream-0.2.0/src/pyflightstream/script/helpers.py +844 -0
- pyflightstream-0.2.0/src/pyflightstream/versions.py +191 -0
- pyflightstream-0.2.0/src/pyflightstream.egg-info/PKG-INFO +88 -0
- pyflightstream-0.2.0/src/pyflightstream.egg-info/SOURCES.txt +107 -0
- pyflightstream-0.2.0/src/pyflightstream.egg-info/dependency_links.txt +1 -0
- pyflightstream-0.2.0/src/pyflightstream.egg-info/entry_points.txt +3 -0
- pyflightstream-0.2.0/src/pyflightstream.egg-info/requires.txt +25 -0
- pyflightstream-0.2.0/src/pyflightstream.egg-info/top_level.txt +1 -0
- pyflightstream-0.2.0/tests/test_cases.py +104 -0
- pyflightstream-0.2.0/tests/test_command_db.py +264 -0
- pyflightstream-0.2.0/tests/test_farfield.py +181 -0
- pyflightstream-0.2.0/tests/test_files.py +144 -0
- pyflightstream-0.2.0/tests/test_fsi_beam.py +117 -0
- pyflightstream-0.2.0/tests/test_fsi_centrifugal.py +108 -0
- pyflightstream-0.2.0/tests/test_fsi_cli.py +80 -0
- pyflightstream-0.2.0/tests/test_fsi_config.py +85 -0
- pyflightstream-0.2.0/tests/test_fsi_driver.py +260 -0
- pyflightstream-0.2.0/tests/test_fsi_kinematics.py +211 -0
- pyflightstream-0.2.0/tests/test_fsi_loads.py +234 -0
- pyflightstream-0.2.0/tests/test_fsi_sources.py +102 -0
- pyflightstream-0.2.0/tests/test_fsi_wing.py +123 -0
- pyflightstream-0.2.0/tests/test_house_style.py +54 -0
- pyflightstream-0.2.0/tests/test_matrix_legacy.py +106 -0
- pyflightstream-0.2.0/tests/test_package_imports.py +36 -0
- pyflightstream-0.2.0/tests/test_post_writers.py +57 -0
- pyflightstream-0.2.0/tests/test_probes.py +124 -0
- pyflightstream-0.2.0/tests/test_probes_geometry.py +154 -0
- pyflightstream-0.2.0/tests/test_probes_planar.py +145 -0
- pyflightstream-0.2.0/tests/test_qa_compat.py +169 -0
- pyflightstream-0.2.0/tests/test_qa_drift.py +83 -0
- pyflightstream-0.2.0/tests/test_qa_geometry.py +126 -0
- pyflightstream-0.2.0/tests/test_qa_physics.py +329 -0
- pyflightstream-0.2.0/tests/test_qa_probes.py +309 -0
- pyflightstream-0.2.0/tests/test_reference.py +93 -0
- pyflightstream-0.2.0/tests/test_results.py +139 -0
- pyflightstream-0.2.0/tests/test_run.py +78 -0
- pyflightstream-0.2.0/tests/test_run_campaign.py +256 -0
- pyflightstream-0.2.0/tests/test_run_preproc.py +70 -0
- pyflightstream-0.2.0/tests/test_script.py +294 -0
- pyflightstream-0.2.0/tests/test_script_helpers.py +251 -0
- pyflightstream-0.2.0/tests/test_versions.py +58 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Geovana Neves
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyflightstream
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Version-aware, didactic Python driver for the FlightStream panel-method solver
|
|
5
|
+
Author: Geovana Neves
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/nevesgeovana/pyflightstream
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: pandas
|
|
13
|
+
Requires-Dist: pydantic
|
|
14
|
+
Requires-Dist: pyyaml
|
|
15
|
+
Requires-Dist: xarray
|
|
16
|
+
Provides-Extra: plot
|
|
17
|
+
Requires-Dist: matplotlib; extra == "plot"
|
|
18
|
+
Provides-Extra: geom
|
|
19
|
+
Requires-Dist: trimesh; extra == "geom"
|
|
20
|
+
Requires-Dist: rtree; extra == "geom"
|
|
21
|
+
Requires-Dist: scipy; extra == "geom"
|
|
22
|
+
Provides-Extra: fsi
|
|
23
|
+
Requires-Dist: PyNiteFEA>=3.0; extra == "fsi"
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff; extra == "dev"
|
|
27
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
28
|
+
Requires-Dist: mkdocs-material; extra == "dev"
|
|
29
|
+
Requires-Dist: mkdocstrings[python]; extra == "dev"
|
|
30
|
+
Requires-Dist: mkdocs-gen-files; extra == "dev"
|
|
31
|
+
Requires-Dist: mkdocs-literate-nav; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# pyflightstream
|
|
35
|
+
|
|
36
|
+
Version-aware, didactic Python driver for the FlightStream panel-method solver.
|
|
37
|
+
Successor of the author's legacy research scripts. MIT licensed.
|
|
38
|
+
|
|
39
|
+
Status: pre-alpha, milestone M0 (repository skeleton). The package installs and
|
|
40
|
+
imports, but no functionality ships yet. See the milestone plan below.
|
|
41
|
+
|
|
42
|
+
## Why this package
|
|
43
|
+
|
|
44
|
+
FlightStream is scripted through an ASCII command file whose commands change
|
|
45
|
+
between solver versions, and not every change reaches the changelog. This
|
|
46
|
+
package makes the FlightStream version an explicit input: every command it
|
|
47
|
+
emits is validated against a per-version command database, and old versions
|
|
48
|
+
are only ever added, never dropped.
|
|
49
|
+
|
|
50
|
+
## What is each folder?
|
|
51
|
+
|
|
52
|
+
| Folder | Purpose in plain language |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `src/pyflightstream/` | The package itself, one subpackage per pipeline stage |
|
|
55
|
+
| `src/pyflightstream/commands/` | The command database: what exists in which FlightStream version, with manual page citations |
|
|
56
|
+
| `tests/` | Tier 1 tests, runnable anywhere, no FlightStream needed |
|
|
57
|
+
| `reports/` | Committed evidence from licensed machines: which commands actually work (compat) and physics regression results |
|
|
58
|
+
| `docs/` | Documentation source (mkdocs) |
|
|
59
|
+
| `examples/` | Runnable example scripts in percent format |
|
|
60
|
+
| `.claude/skills/` | Maintenance procedures (version updates, command additions, QA runs, releases) |
|
|
61
|
+
| `_private/` | Local only, never committed: FlightStream manuals and research geometry |
|
|
62
|
+
|
|
63
|
+
## Supported FlightStream versions
|
|
64
|
+
|
|
65
|
+
Planned at launch: 26.000, 26.100, 26.120 (canonical 26.XXX scheme; the last
|
|
66
|
+
digit indexes vendor hotfix builds). The ordered list in
|
|
67
|
+
`src/pyflightstream/commands/_meta.yaml` is the only ordering authority.
|
|
68
|
+
|
|
69
|
+
## Development setup
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
pip install -e .[dev]
|
|
73
|
+
pre-commit install
|
|
74
|
+
pytest
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Tier 2 (command validity probes) and Tier 3 (physics regression) require a
|
|
78
|
+
local FlightStream license and are documented in CONTRIBUTING.md.
|
|
79
|
+
|
|
80
|
+
## Milestones
|
|
81
|
+
|
|
82
|
+
M0 skeleton (this) > M1 command database > M2 builder, runner, parser >
|
|
83
|
+
M3 compat report > M4 physics cases > M5 docs and example > v0.1.0 (private).
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT. Contributions must be original or MIT-compatible; code derived from the
|
|
88
|
+
AGPL pyFlightscript package is not accepted. See CONTRIBUTING.md.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# pyflightstream
|
|
2
|
+
|
|
3
|
+
Version-aware, didactic Python driver for the FlightStream panel-method solver.
|
|
4
|
+
Successor of the author's legacy research scripts. MIT licensed.
|
|
5
|
+
|
|
6
|
+
Status: pre-alpha, milestone M0 (repository skeleton). The package installs and
|
|
7
|
+
imports, but no functionality ships yet. See the milestone plan below.
|
|
8
|
+
|
|
9
|
+
## Why this package
|
|
10
|
+
|
|
11
|
+
FlightStream is scripted through an ASCII command file whose commands change
|
|
12
|
+
between solver versions, and not every change reaches the changelog. This
|
|
13
|
+
package makes the FlightStream version an explicit input: every command it
|
|
14
|
+
emits is validated against a per-version command database, and old versions
|
|
15
|
+
are only ever added, never dropped.
|
|
16
|
+
|
|
17
|
+
## What is each folder?
|
|
18
|
+
|
|
19
|
+
| Folder | Purpose in plain language |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `src/pyflightstream/` | The package itself, one subpackage per pipeline stage |
|
|
22
|
+
| `src/pyflightstream/commands/` | The command database: what exists in which FlightStream version, with manual page citations |
|
|
23
|
+
| `tests/` | Tier 1 tests, runnable anywhere, no FlightStream needed |
|
|
24
|
+
| `reports/` | Committed evidence from licensed machines: which commands actually work (compat) and physics regression results |
|
|
25
|
+
| `docs/` | Documentation source (mkdocs) |
|
|
26
|
+
| `examples/` | Runnable example scripts in percent format |
|
|
27
|
+
| `.claude/skills/` | Maintenance procedures (version updates, command additions, QA runs, releases) |
|
|
28
|
+
| `_private/` | Local only, never committed: FlightStream manuals and research geometry |
|
|
29
|
+
|
|
30
|
+
## Supported FlightStream versions
|
|
31
|
+
|
|
32
|
+
Planned at launch: 26.000, 26.100, 26.120 (canonical 26.XXX scheme; the last
|
|
33
|
+
digit indexes vendor hotfix builds). The ordered list in
|
|
34
|
+
`src/pyflightstream/commands/_meta.yaml` is the only ordering authority.
|
|
35
|
+
|
|
36
|
+
## Development setup
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
pip install -e .[dev]
|
|
40
|
+
pre-commit install
|
|
41
|
+
pytest
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Tier 2 (command validity probes) and Tier 3 (physics regression) require a
|
|
45
|
+
local FlightStream license and are documented in CONTRIBUTING.md.
|
|
46
|
+
|
|
47
|
+
## Milestones
|
|
48
|
+
|
|
49
|
+
M0 skeleton (this) > M1 command database > M2 builder, runner, parser >
|
|
50
|
+
M3 compat report > M4 physics cases > M5 docs and example > v0.1.0 (private).
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
MIT. Contributions must be original or MIT-compatible; code derived from the
|
|
55
|
+
AGPL pyFlightscript package is not accepted. See CONTRIBUTING.md.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyflightstream"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Version-aware, didactic Python driver for the FlightStream panel-method solver"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
authors = [{name = "Geovana Neves"}]
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
# xarray confirmed as a runtime dependency by the author's far-field
|
|
14
|
+
# extraction instruction of 2026-07-21 (PLN-006 closed): the farfield
|
|
15
|
+
# ledgers live on labeled (station, r, psi) arrays.
|
|
16
|
+
dependencies = [
|
|
17
|
+
"numpy",
|
|
18
|
+
"pandas",
|
|
19
|
+
"pydantic",
|
|
20
|
+
"pyyaml",
|
|
21
|
+
"xarray",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
plot = ["matplotlib"]
|
|
26
|
+
# Geometry gate of the probe planner: containment culling and
|
|
27
|
+
# distance-to-surface queries (license evidence reports/RPT-003).
|
|
28
|
+
geom = ["trimesh", "rtree", "scipy"]
|
|
29
|
+
# FSI structural coupling (M6, DLV-007). PyNiteFEA is MIT; license
|
|
30
|
+
# evidence: reports/RPT-002_pynitefea-license_2026-07-21.md (NFR-02).
|
|
31
|
+
fsi = ["PyNiteFEA>=3.0"]
|
|
32
|
+
dev = [
|
|
33
|
+
"pytest",
|
|
34
|
+
"ruff",
|
|
35
|
+
"pre-commit",
|
|
36
|
+
"mkdocs-material",
|
|
37
|
+
"mkdocstrings[python]",
|
|
38
|
+
"mkdocs-gen-files",
|
|
39
|
+
"mkdocs-literate-nav",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
pyfs-qa = "pyflightstream.qa.cli:main"
|
|
44
|
+
pyfs-fsi = "pyflightstream.fsi.cli:main"
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Repository = "https://github.com/nevesgeovana/pyflightstream"
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.packages.find]
|
|
50
|
+
where = ["src"]
|
|
51
|
+
|
|
52
|
+
[tool.setuptools.package-data]
|
|
53
|
+
"pyflightstream.commands" = ["*.yaml"]
|
|
54
|
+
"pyflightstream.qa" = ["references/*.yaml"]
|
|
55
|
+
|
|
56
|
+
[tool.pytest.ini_options]
|
|
57
|
+
testpaths = ["tests"]
|
|
58
|
+
markers = [
|
|
59
|
+
"needs_flightstream: requires a local FlightStream installation and license",
|
|
60
|
+
"validity: tier 2 per-command probe tests",
|
|
61
|
+
"physics: tier 3 physics regression matrix (slow)",
|
|
62
|
+
]
|
|
63
|
+
addopts = "-m 'not needs_flightstream'"
|
|
64
|
+
|
|
65
|
+
[tool.ruff]
|
|
66
|
+
line-length = 100
|
|
67
|
+
target-version = "py311"
|
|
68
|
+
src = ["src"]
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint]
|
|
71
|
+
select = ["E", "F", "W", "I", "UP", "B", "N", "D"]
|
|
72
|
+
# N803/N806 exempted so aerodynamic symbols (CL, CDi, J, alpha_deg) keep
|
|
73
|
+
# their standard names (SAD Section 12).
|
|
74
|
+
ignore = ["N803", "N806"]
|
|
75
|
+
|
|
76
|
+
[tool.ruff.lint.pydocstyle]
|
|
77
|
+
convention = "numpy"
|
|
78
|
+
|
|
79
|
+
[tool.ruff.lint.per-file-ignores]
|
|
80
|
+
"tests/*" = ["D"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""pyflightstream: version-aware, didactic Python driver for FlightStream.
|
|
2
|
+
|
|
3
|
+
The package automates the FlightStream panel-method solver through its ASCII
|
|
4
|
+
scripting interface. The FlightStream version is an explicit input: every
|
|
5
|
+
command emitted is validated against the per-version command database in
|
|
6
|
+
``pyflightstream.commands``.
|
|
7
|
+
|
|
8
|
+
Pipeline layers, dependencies flowing strictly downward:
|
|
9
|
+
|
|
10
|
+
- ``versions``: canonical 26.XXX version identifiers and ordering.
|
|
11
|
+
- ``commands``: the command database and per-version registry.
|
|
12
|
+
- ``script``: the validating ASCII script builder.
|
|
13
|
+
- ``results``: anchor-based parsers for solver output files.
|
|
14
|
+
- ``cases``: simulation and campaign definitions.
|
|
15
|
+
- ``run`` and ``files``: execution, run manifest, managed file layout.
|
|
16
|
+
- ``post``: results into engineering data (sweep assembly, PLTET, exports).
|
|
17
|
+
- ``qa``: probe harness and physics regression tooling.
|
|
18
|
+
- ``fsi``: seam for the future fluid-structure interaction coupling.
|
|
19
|
+
|
|
20
|
+
Milestone M0: skeleton only; functionality arrives from milestone M1 on.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__version__ = "0.0.1.dev0"
|
|
24
|
+
|
|
25
|
+
from pyflightstream.reference import help # noqa: E402
|
|
26
|
+
|
|
27
|
+
__all__ = ["__version__", "help"]
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"""Simulation and campaign definitions.
|
|
2
|
+
|
|
3
|
+
Pipeline role: describes what to run. A :class:`SimCase` (identified
|
|
4
|
+
by ``sim_id``) is one solver configuration with its sweep; a
|
|
5
|
+
:class:`Campaign` groups cases with the FlightStream version and the
|
|
6
|
+
executable path, both required and explicit: nothing is read from
|
|
7
|
+
environment variables or guessed (SAD Section 5). Native persistence
|
|
8
|
+
is ``campaign.toml``; the legacy pipe-delimited ``matriz.fs`` matrix
|
|
9
|
+
of the predecessor scripts will be read unchanged, forever, by the
|
|
10
|
+
legacy reader (FR-10, next step of milestone M2).
|
|
11
|
+
|
|
12
|
+
Script recipes are explicitly imported functions satisfying the
|
|
13
|
+
:class:`ScriptRecipe` protocol: ``build(case, script) -> None``. The
|
|
14
|
+
campaign loop specializes the case per sweep point (filling
|
|
15
|
+
:attr:`SimCase.point`) and the recipe translates it into script
|
|
16
|
+
emissions, usually through the curated helpers. Recipe references are
|
|
17
|
+
``"package.module:function"`` strings, replacing the historical
|
|
18
|
+
import-by-number system (PP-7, FR-12).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import tomllib
|
|
24
|
+
from collections.abc import Callable, Iterator
|
|
25
|
+
from importlib import import_module
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Literal, Protocol, runtime_checkable
|
|
28
|
+
|
|
29
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
30
|
+
|
|
31
|
+
from pyflightstream.script import Script
|
|
32
|
+
from pyflightstream.versions import resolve
|
|
33
|
+
|
|
34
|
+
_TAG_PREFIXES = (("alpha", "a"), ("beta", "b"), ("advance_ratio", "j"))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@runtime_checkable
|
|
38
|
+
class ScriptRecipe(Protocol):
|
|
39
|
+
"""A function that turns one case point into script emissions.
|
|
40
|
+
|
|
41
|
+
Implementations receive the per-point specialized case (the
|
|
42
|
+
campaign loop fills :attr:`SimCase.point` and stages the geometry)
|
|
43
|
+
and an empty :class:`~pyflightstream.script.Script` bound to the
|
|
44
|
+
campaign's FlightStream version; they emit the whole script,
|
|
45
|
+
usually through the curated helpers. Output files must use paths
|
|
46
|
+
relative to the execution directory, so the collected evidence
|
|
47
|
+
stays inside the managed simulation folder.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __call__(self, case: SimCase, script: Script) -> None:
|
|
51
|
+
"""Emit the complete script for one case point."""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class SweepAxis(BaseModel):
|
|
56
|
+
"""The sweep of one case: which axis varies and its values.
|
|
57
|
+
|
|
58
|
+
Attributes
|
|
59
|
+
----------
|
|
60
|
+
type : str
|
|
61
|
+
``alpha`` (angle of attack, deg), ``beta`` (side slip, deg),
|
|
62
|
+
``alpha_beta`` (paired values), or ``advance_ratio``
|
|
63
|
+
(propeller advance ratio J, dimensionless).
|
|
64
|
+
values : list
|
|
65
|
+
Axis values; for ``alpha_beta`` each entry is an
|
|
66
|
+
``[alpha, beta]`` pair in deg.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
model_config = ConfigDict(extra="forbid")
|
|
70
|
+
|
|
71
|
+
type: Literal["alpha", "beta", "alpha_beta", "advance_ratio"]
|
|
72
|
+
values: list[float] | list[tuple[float, float]]
|
|
73
|
+
|
|
74
|
+
@model_validator(mode="after")
|
|
75
|
+
def _values_match_the_axis_type(self) -> SweepAxis:
|
|
76
|
+
pairs = self.type == "alpha_beta"
|
|
77
|
+
for value in self.values:
|
|
78
|
+
if pairs != isinstance(value, tuple):
|
|
79
|
+
expected = "[alpha, beta] pairs" if pairs else "scalar values"
|
|
80
|
+
raise ValueError(f"a {self.type} sweep takes {expected}, got {value!r}")
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
def points(self) -> Iterator[dict[str, float]]:
|
|
84
|
+
"""Iterate the sweep as named point coordinates.
|
|
85
|
+
|
|
86
|
+
Yields
|
|
87
|
+
------
|
|
88
|
+
dict of str to float
|
|
89
|
+
One mapping per point, keyed ``alpha``, ``beta``, or
|
|
90
|
+
``advance_ratio`` (both keys for ``alpha_beta``).
|
|
91
|
+
"""
|
|
92
|
+
for value in self.values:
|
|
93
|
+
if self.type == "alpha_beta":
|
|
94
|
+
alpha, beta = value
|
|
95
|
+
yield {"alpha": alpha, "beta": beta}
|
|
96
|
+
else:
|
|
97
|
+
yield {self.type: value}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def point_tag(point: dict[str, float]) -> str:
|
|
101
|
+
"""Return the stable file-name tag of one sweep point.
|
|
102
|
+
|
|
103
|
+
The tag encodes the point coordinates in a fixed axis order with
|
|
104
|
+
signed fixed-width values, for example ``a+02.0_b+00.0``; it names
|
|
105
|
+
the generated script and ends the ``run_id``.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
point : dict of str to float
|
|
110
|
+
Point coordinates as produced by :meth:`SweepAxis.points`.
|
|
111
|
+
"""
|
|
112
|
+
parts = [f"{prefix}{point[axis]:+05.1f}" for axis, prefix in _TAG_PREFIXES if axis in point]
|
|
113
|
+
if not parts:
|
|
114
|
+
raise ValueError(f"point {point!r} has no known axis (alpha, beta, advance_ratio)")
|
|
115
|
+
return "_".join(parts)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class ReferenceData(BaseModel):
|
|
119
|
+
"""Reference quantities for coefficient normalization.
|
|
120
|
+
|
|
121
|
+
Attributes
|
|
122
|
+
----------
|
|
123
|
+
area : float
|
|
124
|
+
Reference area S_ref in simulation length units squared.
|
|
125
|
+
length : float
|
|
126
|
+
Reference length L_ref in simulation length units.
|
|
127
|
+
velocity : float, optional
|
|
128
|
+
Reference velocity in m/s; None lets the recipe default it to
|
|
129
|
+
the free-stream velocity (steady runs) or a characteristic
|
|
130
|
+
velocity such as the rotor tip speed (SRC-003 p.201).
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
model_config = ConfigDict(extra="forbid")
|
|
134
|
+
|
|
135
|
+
area: float
|
|
136
|
+
length: float
|
|
137
|
+
velocity: float | None = None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class SolverSettings(BaseModel):
|
|
141
|
+
"""Solver runtime settings of one case.
|
|
142
|
+
|
|
143
|
+
Field names match the keyword arguments of
|
|
144
|
+
:func:`pyflightstream.script.helpers.solver_settings`, so recipes
|
|
145
|
+
can forward them directly.
|
|
146
|
+
|
|
147
|
+
Attributes
|
|
148
|
+
----------
|
|
149
|
+
iterations : int
|
|
150
|
+
Solver iteration limit.
|
|
151
|
+
convergence : float
|
|
152
|
+
Residual threshold declaring convergence (SRC-003 p.200).
|
|
153
|
+
forced_iterations : bool, optional
|
|
154
|
+
Run the full iteration count regardless of convergence.
|
|
155
|
+
boundary_layer : str, optional
|
|
156
|
+
``LAMINAR``, ``TRANSITIONAL``, or ``TURBULENT``.
|
|
157
|
+
viscous_coupling : bool, optional
|
|
158
|
+
Couple the boundary layer model to the potential solution.
|
|
159
|
+
max_threads : int, optional
|
|
160
|
+
Parallel core count.
|
|
161
|
+
timeout_s : float, optional
|
|
162
|
+
Wall-clock limit for one point's solver process; enforced by
|
|
163
|
+
the executor, not by FlightStream.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
model_config = ConfigDict(extra="forbid")
|
|
167
|
+
|
|
168
|
+
iterations: int = 500
|
|
169
|
+
convergence: float = 1e-5
|
|
170
|
+
forced_iterations: bool | None = None
|
|
171
|
+
boundary_layer: str | None = None
|
|
172
|
+
viscous_coupling: bool | None = None
|
|
173
|
+
max_threads: int | None = None
|
|
174
|
+
timeout_s: float | None = None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class SimCase(BaseModel):
|
|
178
|
+
"""One solver configuration with its sweep (SAD Section 5).
|
|
179
|
+
|
|
180
|
+
Attributes
|
|
181
|
+
----------
|
|
182
|
+
sim_id : str
|
|
183
|
+
Case identity; also names the managed folder
|
|
184
|
+
``sims/sim_<sim_id>``.
|
|
185
|
+
aircraft : str
|
|
186
|
+
Aircraft or configuration name.
|
|
187
|
+
description : str
|
|
188
|
+
Free-text description.
|
|
189
|
+
reynolds : float, optional
|
|
190
|
+
Chord Reynolds number of the condition.
|
|
191
|
+
mach : float, optional
|
|
192
|
+
Free-stream Mach number.
|
|
193
|
+
velocity : float, optional
|
|
194
|
+
Free-stream velocity in m/s.
|
|
195
|
+
geometry : str, optional
|
|
196
|
+
Path of the simulation (.fsm) file; the campaign loop stages
|
|
197
|
+
it into ``inputs/`` and rewrites this field to the staged
|
|
198
|
+
copy, so recipes OPEN exactly what the manifest hashed.
|
|
199
|
+
sweep : SweepAxis
|
|
200
|
+
The sweep of this case.
|
|
201
|
+
reference : ReferenceData, optional
|
|
202
|
+
Coefficient normalization references.
|
|
203
|
+
solver : SolverSettings
|
|
204
|
+
Runtime settings; defaults apply when omitted.
|
|
205
|
+
recipe : str
|
|
206
|
+
Script recipe reference, ``"package.module:function"``, or a
|
|
207
|
+
name registered with the campaign loop.
|
|
208
|
+
variables : dict
|
|
209
|
+
Free per-case variables for the recipe (strings, numbers, or
|
|
210
|
+
booleans), for example a symmetry declaration.
|
|
211
|
+
outputs : list of str
|
|
212
|
+
Output files the recipe's script exports, relative to the
|
|
213
|
+
execution directory; the loop collects them into ``raw/`` and
|
|
214
|
+
a missing one marks the point FAILED_INCOMPLETE_OUTPUT.
|
|
215
|
+
point : dict of str to float
|
|
216
|
+
The current sweep point; filled by the campaign loop before
|
|
217
|
+
the recipe builds, empty on the authored case.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
model_config = ConfigDict(extra="forbid")
|
|
221
|
+
|
|
222
|
+
sim_id: str
|
|
223
|
+
aircraft: str
|
|
224
|
+
description: str = ""
|
|
225
|
+
reynolds: float | None = None
|
|
226
|
+
mach: float | None = None
|
|
227
|
+
velocity: float | None = None
|
|
228
|
+
geometry: str | None = None
|
|
229
|
+
sweep: SweepAxis
|
|
230
|
+
reference: ReferenceData | None = None
|
|
231
|
+
solver: SolverSettings = Field(default_factory=SolverSettings)
|
|
232
|
+
recipe: str
|
|
233
|
+
variables: dict[str, str | float | int | bool] = Field(default_factory=dict)
|
|
234
|
+
outputs: list[str] = Field(default_factory=list)
|
|
235
|
+
point: dict[str, float] = Field(default_factory=dict)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class Campaign(BaseModel):
|
|
239
|
+
"""A named group of cases bound to one FlightStream installation.
|
|
240
|
+
|
|
241
|
+
Attributes
|
|
242
|
+
----------
|
|
243
|
+
name : str
|
|
244
|
+
Campaign name; prefixes every ``run_id``.
|
|
245
|
+
fs_version : str
|
|
246
|
+
FlightStream version, canonical or alias; validated against
|
|
247
|
+
the registered versions at load time, resolved to canonical in
|
|
248
|
+
the manifest.
|
|
249
|
+
fs_exe : str
|
|
250
|
+
Explicit path of the FlightStream executable; existence is
|
|
251
|
+
checked by the executor at construction, not here, so a
|
|
252
|
+
campaign file can be authored away from the licensed machine.
|
|
253
|
+
sims : list of SimCase
|
|
254
|
+
The cases of the campaign.
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
model_config = ConfigDict(extra="forbid")
|
|
258
|
+
|
|
259
|
+
name: str
|
|
260
|
+
fs_version: str
|
|
261
|
+
fs_exe: str
|
|
262
|
+
sims: list[SimCase]
|
|
263
|
+
|
|
264
|
+
@field_validator("fs_version")
|
|
265
|
+
@classmethod
|
|
266
|
+
def _version_is_registered(cls, value: str) -> str:
|
|
267
|
+
resolve(value)
|
|
268
|
+
return value
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def load_campaign(path: str | Path) -> Campaign:
|
|
272
|
+
"""Load and validate a ``campaign.toml`` file.
|
|
273
|
+
|
|
274
|
+
The file holds one ``[campaign]`` table (name, fs_version,
|
|
275
|
+
fs_exe) and one ``[[sim]]`` array entry per case, as in SAD
|
|
276
|
+
Section 5.
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
path : str or Path
|
|
281
|
+
Location of the TOML file.
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
Campaign
|
|
286
|
+
Validated campaign; version aliases are checked against the
|
|
287
|
+
registered versions immediately, so a typo fails at load
|
|
288
|
+
time, not at the first point.
|
|
289
|
+
"""
|
|
290
|
+
with open(path, "rb") as handle:
|
|
291
|
+
data = tomllib.load(handle)
|
|
292
|
+
if "campaign" not in data:
|
|
293
|
+
raise ValueError(
|
|
294
|
+
f"{path} has no [campaign] table; campaign.toml needs [campaign] with "
|
|
295
|
+
"name, fs_version, and fs_exe, plus one [[sim]] entry per case"
|
|
296
|
+
)
|
|
297
|
+
return Campaign(**data["campaign"], sims=data.get("sim", []))
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def resolve_recipe(reference: str) -> Callable[[SimCase, Script], None]:
|
|
301
|
+
"""Import the recipe function a reference string names.
|
|
302
|
+
|
|
303
|
+
Parameters
|
|
304
|
+
----------
|
|
305
|
+
reference : str
|
|
306
|
+
``"package.module:function"``; the module must be importable
|
|
307
|
+
and the attribute callable. Explicit references replace the
|
|
308
|
+
historical import-by-number system (PP-7, FR-12).
|
|
309
|
+
|
|
310
|
+
Returns
|
|
311
|
+
-------
|
|
312
|
+
callable
|
|
313
|
+
The recipe function, satisfying :class:`ScriptRecipe`.
|
|
314
|
+
"""
|
|
315
|
+
module_name, separator, function_name = reference.partition(":")
|
|
316
|
+
if not separator or not module_name or not function_name:
|
|
317
|
+
raise ValueError(
|
|
318
|
+
f"recipe reference {reference!r} is not of the form 'package.module:function'"
|
|
319
|
+
)
|
|
320
|
+
try:
|
|
321
|
+
module = import_module(module_name)
|
|
322
|
+
except ImportError as error:
|
|
323
|
+
raise ValueError(
|
|
324
|
+
f"recipe module {module_name!r} cannot be imported: {error}. Recipes are "
|
|
325
|
+
"explicitly imported functions; check the module path and the environment."
|
|
326
|
+
) from error
|
|
327
|
+
recipe = getattr(module, function_name, None)
|
|
328
|
+
if not callable(recipe):
|
|
329
|
+
raise ValueError(
|
|
330
|
+
f"recipe {reference!r} does not name a callable in {module_name!r}; found {recipe!r}"
|
|
331
|
+
)
|
|
332
|
+
return recipe
|