Feature/iss panel plume study - #130
Open
andytorrestb wants to merge 10 commits into
Open
Conversation
The strike pipeline hard-coded SimplifiedGasKinetics, so a study could
name a model but never select one. CollisionlessGasKinetics already
subclasses it with an identical constructor and overrides only the field
getters, so dispatch is a class lookup rather than a plugin framework.
pyrpod/plume/gas_kinetics_models.py adds that lookup plus the two pieces
of model-independent physics the pipeline needs:
* local_field_state() reduces any model to one common LocalFieldState
(number density, mass density, axial/radial velocity, velocity
magnitude, temperature, local speed ratio), calling every field getter
through the instance so an overriding model is honoured;
* maxwellian_surface_loads() applies the Shen gas-surface interaction to
that state, so pressure/shear/heat-transfer logic is written once.
The model is selected by the case's existing [pm] kinetics key
(Simplified, Collisionless, or None to disable surface loads), so both the
vectorized core and the scalar reference pick it up without any change to
PlumeStrikeEstimationStudy. An unknown key is an error, never a silent
fall back. Omitting it keeps the historical behavior, and the Simplified
path is preserved bit-for-bit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
study_config no longer hard-rejects every model but SimplifiedGasKinetics;
plume_model.name is validated against the registry and now SELECTS the
model that computes the plume field. An omitted block keeps the historical
default, and an unknown name is still a clear StudyConfigError.
Three schema additions, all optional and all defaulted so existing YAML
parses unchanged:
* sweep.source_offsets_u / source_offsets_v translate the plume source
parallel to the target surface, on the surface-local basis
TargetSpec.local_basis() derives from the existing normal/tangent keys
(u = tangent, v = n x u, u x v = n). Both default to [0.0].
* sweep.source_axis_mode picks the pose convention: aim_at_reference
(existing arc, aimed at the reference point) or parallel_to_normal
(translated source, axis fixed anti-parallel to the normal). The two
agree exactly at zero offset, so the new mode extends rather than
redefines the old one. Incompatible combinations -- offsets while
aiming, an approach angle with a fixed axis -- are rejected outright
rather than silently combined.
* a knudsen block records DERIVED metadata only: the mean free path must
be given (never inferred from gas properties), exactly one of the two
reference-length modes must be chosen, and nothing in the pipeline
reads Kn back. The models stay collisionless.
SweepSpec.sweep_poses enumerates the full parameterization distance-major,
then u, then v, then angle, which collapses to the historical order at the
default offsets; SweepSpec.poses keeps its (angle, distance) projection for
existing callers. firing_plan gains translated_pose_for() and dispatches on
the axis mode in one place, so the per-case and single-history engines
cannot drift apart.
The obsolete assertion that CollisionlessGasKinetics must be REJECTED is
replaced by tests that both supported models are accepted, that an unknown
name is refused, and that omitting the block keeps the default.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CaseResult gained the fields a panel study reports, all optional and
defaulted so older result files and the reference-comparison API keep
working unchanged:
* source_offset_u / source_offset_v / source_axis_mode -- the pose that
produced the record;
* normal_force, local_force_u/v, local_moment_u/v/n and
center_of_pressure_u/v -- the SAME integrated force and moment vectors
re-expressed on the target's surface-local basis, adding no physics;
* knudsen_number, mean_free_path, knudsen_reference_length and
knudsen_definition -- empty columns, not fabricated values, when the
study configures no knudsen block;
* model_variant (a derived short label) and surface_distribution_path.
surface_loads gained project_to_panel_frame() and panel_local_coordinates()
with the sign conventions written down and worked through in the module
docstring: normal_force = -F.n is POSITIVE for a load pressing into the
panel, and a source displaced toward +u gives a positive local_moment_v.
CSV columns stay flat and directly plottable; quantity() exposes the new
comparable scalars; no per-face array is embedded in a CSV row.
load_case_assets no longer refuses every model but Simplified. It applies
the study's plume_model by setting the environment's in-memory [pm]
kinetics key -- the one input both strike paths read -- so the selection
drives the calculation rather than only the metadata. The case's
config.ini on disk is untouched, and a study naming the model its case
already configures changes nothing. Kinetics disabled is still an error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds an opt-in second view of the per-face fields the VTK files already carry: one CSV per case and component with face_index, global centroid, panel-local (u, v), area, pressure, shear stress, heat flux and strike count. The VTK export stays the primary full-resolution output and is untouched; this is for plotting scripts, spreadsheets and a later comparison workflow that should not need a VTK reader. Values are copied through unchanged -- no interpolation, no resampling, no structured-grid projection, and no common-grid projection onto any external mesh. Every row is one native mesh face, traceable back to the mesh and the VTK file by its preserved face_index. A sidecar JSON records the column units, the panel basis, the pose, the plume model and any derived Knudsen metadata, so a distribution file is self-describing. Enabled with output.surface_distribution.enabled (default false); the path lands in CaseResult.surface_distribution_path. Both engines export it, into the per-case directory for per_case mode and the study root for single_jfh. case_id_for() now names what actually varies: aim_at_reference keeps the historical case000_alpha0p0_d4 form byte-for-byte, while parallel_to_normal uses case000_modelCollisionless_L4_u0_v0, since the approach angle is fixed in that mode. The per-case engine iterates the full SweepPose grid. A firing with no swept distance (an explicitly prescribed pose in single_jfh mode) records the mean free path and definition but leaves the distance-referenced Kn unset rather than inventing one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
study_plots covers the angle sweep; panel_plots covers the offset sweep,
where the independent variable is the panel-local source offset:
* plot_panel_pressure() draws one case's panel-local pressure field from
its exported distribution CSV, with the panel edges and the plume
centerline marked. A flat plate is an unstructured triangle mesh, so no
structured grid is fabricated -- it is drawn as a Delaunay
triangulation of the face centroids, falling back to a face-coloured
scatter when the face count is too small for contours to be honest.
* plot_offset_sweep_trends() writes normal force, moment about v, peak
pressure and center-of-pressure u against the u offset, plus normal
force against stand-off distance. Every series is grouped by distance
AND plume model, so merged Simplified and Collisionless results stay
separate rather than being averaged. Transverse figures appear only
when v is actually swept.
study_runtime.study_plots_for() decides which families a given study wants
and is called by both engines, so the choice is not duplicated and does not
leak into the TradeStudy facade. Both plotting modules are imported lazily,
so a study that asks for no plots never imports matplotlib; the Agg backend
and the existing palette are reused, and no new dependency is added.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An idealized 22 m x 12 m flat panel standing in for one ISS solar-array wing, centered at the origin with its normal along +Z and struck by the verified Cai 2016 argon round jet (D = 1 m, S0 = 2.0, T0 = 200 K, Tw = 300 K, fully diffuse), with the source TRANSLATED parallel to the panel rather than re-aimed at its center. config.ini case assets and gating geometry stl/generate_panel.py parameterized plate mesher stl/iss_panel.stl committed 44 x 24 quads, 2112 faces tcd/tcf_1_argon.txt, tcd/tdf.csv single head-on argon thruster study/iss_panel_baseline_simplified.yaml study/iss_panel_baseline_full_cai.yaml study/iss_panel_offset_distance_sweep.yaml run.py runner for one study or all README.md conventions, commands, artifacts The two baselines differ in exactly one line -- plume_model.name -- so the pair isolates the far-field simplification: 2.824 N vs 2.802 N centered normal force, 0.3317 Pa vs 0.3136 Pa peak pressure. That the numbers differ at all is the evidence that model selection reaches the calculation. The sweep is 3 distances x 5 u offsets = 15 per_case cases, with distribution export, plots and the Kn metadata block enabled; it runs in about 20 s. Verified symmetric: zero moment and zero CoP offset at u = 0, equal-and-opposite moments at +/-u, and normal force falling at the edges as the footprint spills off the panel. README.md documents expanding the arrays and running one study per Kn label of 100, 10, 1, 0.1, 0.01. The mesher uses surfmesh (github.com/plume-kit/surfmesh) for the quad grid and splits each quad into two triangles. Since the STL is committed, that is a build-time dependency of the script alone -- neither PyRPOD nor the tests import it. Also moves the panel-pressure figure title to the figure so a drawn-to-scale wide panel does not run its title under the colorbar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
cai2016_reference.py evaluates the exact Cai 2016 surface solution (CaiImpingement2016.py, Eqs. 9-14) at the same panel face centroids a study used and exports the same panel-local distribution schema, so the two can be diffed column for column. It is a REFERENCE GENERATOR, not a plume-model backend: it lives in the case directory, PyRPOD never imports it, PlumeStrikeCalculator cannot reach it, and it takes no part in any study run. CaiImpingement2016 stays independent of the production strike pipeline exactly as before. It implements normal incidence only (Cai alpha_0 = 90 deg), which is precisely the parallel_to_normal pose, and refuses any other geometry rather than silently mapping it. The frame mapping between the panel basis and Cai's nozzle frame is written out in the docstring. Value of the check: PyRPOD reaches the wall through a chain (plume field -> LocalFieldState -> Shen formulas) while this reference integrates the incident and re-emitted wall fluxes directly. At L = 4 m on the committed mesh the peaks agree to 2.8% in pressure, 3.4% in shear and 7.8% in heat flux -- the same documented Maxwellian-chain gap mdao_integration_test_02 already checks for integrated loads, now available per face. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two new files, both registered in tests/test_manifest.yaml (README
regenerated with scripts/generate_test_dashboard.py). No existing assertion
was weakened or removed.
mdao_unit_test_07.py (63 tests) covers the units:
* both Cai variants selectable by name, unknown names rejected rather
than defaulted, kinetics keys round-tripping;
* both models reduced to the same populated LocalFieldState, agreeing
bit-for-bit on the centerline (shared closed forms) and differing off
it (which is what makes the selection meaningful), with one shared
Maxwellian implementation consuming either;
* parallel_to_normal poses: centered position and DCM, a proper
orthonormal rotation at every offset, translation without tilting the
axis, and exact agreement with aim_at_reference at zero offset;
* offsets defaulting to zero, case count = distances x u x v, the pose
order, and refusal of incompatible pose definitions;
* Kn for both reference-length modes, the five documented Kn labels,
absent when unconfigured, and every invalid form rejected;
* panel-local transforms and hand-computed normal-force, moment and
center-of-pressure projections including the sign conventions;
* the distribution schema and the new result columns.
mdao_integration_test_05.py (41 tests) runs the committed case end to end.
The key one: running the two baselines, which differ only in
plume_model.name, and asserting the LOADS differ -- if selection were
metadata only, they would be identical. Also re-integrates the exported
per-face pressures and checks the result against CaseResult.pressure_force,
checks centered-source symmetry and offset moment signs, the sweep's count,
order and identifiers, headless plotting, and that the existing flat-plate
configurations, case identifiers and baseline numbers are unchanged.
Sweep tests build their own 64-face panel rather than running the
committed 15-case sweep on 2112 faces; the baseline runs on the committed
mesh unchanged.
Full suite: 427 passed, 12 skipped, 1 failed --
rpod_unit_test_01::test_stl_to_vtk, which fails identically on master and
is unrelated (convert_stl_to_vtk does not write its output file).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ions
docs/plume_validation_study.md gains four sections and updates several:
* Plume models -- the two selectable collisionless Cai variants, the
mapping to [pm] kinetics, and the fact that the name SELECTS the model
that computes the field rather than recording metadata. Notes that
CaiImpingement2016 stays independent of the strike pipeline.
* Source axis modes -- aim_at_reference vs parallel_to_normal written out
as formulas, why they are different experiments, and that they coincide
exactly at zero offset.
* Panel-local u/v offsets -- the basis derived from the existing
normal/tangent keys, the enumeration order, the case count and the case
identifier forms.
* Derived Knudsen metadata -- both reference-length modes, every
validation rule, and repeated statements that Kn never enters the
solution.
* Panel-local surface distributions -- the column schema, why it exists
alongside (never instead of) the VTK files, and the explicit absence of
interpolation and common-grid projection.
* ISS-panel example -- exact commands for all three studies and the
independent Cai 2016 cross-check.
Sign conventions now have their own subsection with a table and a worked
moment derivation: normal_force = -F.n is positive INTO the panel, and a
source displaced toward +u gives a positive local_moment_v.
The result-schema and YAML sections list every addition with its default and
state that all of them are optional or safely defaulted. The DSMC subsection
and Known limitations now say plainly that no DSMC handling exists -- no
OpenFOAM execution, dictionaries, field import, mesh interpolation, job
management or comparison report -- and that both models are collisionless
with no collisional, wake or shadowing correction.
Also drops the stale "the only plume model this workflow supports" comment
from the committed flat-plate baseline YAML.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two narrow typing slips in the new code, both annotation-only:
* KnudsenSpec.from_mapping's XOR check guarantees the explicit reference
length is not None on that branch, but mypy cannot see it -- narrowed
with a targeted ignore and a comment naming the invariant;
* plot_panel_pressure's mappable is a TriContourSet on one branch and a
PathCollection on the other, so it is annotated as their common
ScalarMappable base.
`python -m mypy pyrpod/mdao pyrpod/plume` is clean again (18 files), as it
was on master (15 files).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.