Feature/plume validation study - #129
Merged
andytorrestb merged 14 commits intoJul 28, 2026
Merged
Conversation
print_jfh_1d_approach_n_fire accepted an n_firings argument and then ignored it: the emitted Jet Firing History held whatever number of entries the deceleration simulation happened to produce. - add validate_n_firings() to approach_maneuvers as the single definition of the rule (positive integer; zero, negative, fractional and non-numeric values are rejected rather than coerced); - give compute_1d_approach an optional n_firings that stops the simulation at exactly that many entries and raises when the approach completes in fewer firings than requested, instead of silently returning a shorter history; - validate the argument in print_jfh_1d_approach_n_fire, pass it through, and assert the written file's entry count. Omitting n_firings keeps the previous behavior exactly, so no existing caller changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A study configuration is a declarative layer ON TOP OF an existing PyRPOD case: the case's config.ini keeps owning the vehicle, thruster, plume-model and target assets, so every existing case and public API is untouched, while the YAML adds only what a trade study needs and an INI cannot express -- swept plate angles and source distances, the sweep decomposition mode, the exact firing count, explicitly prescribed firings, the moment reference point, coefficient normalization, output/plot settings and an optional external reference-data location. Validation is strict and specific: a missing case directory, a case without a config.ini, an unsupported plume model, an unknown sweep mode, an empty or non-positive sweep axis, non-orthogonal target axes, duplicate component names, a non-positive normalization value or a firing list whose length disagrees with the mode's implied total each raise StudyConfigError naming the offending key. The plume model is recorded explicitly and must be SimplifiedGasKinetics; no model registry is introduced. n_firings has ONE meaning in both sweep modes -- entries contributed by each pose -- and reuses the rule defined with the JFH-generation code so the prescribed and dynamics-driven paths cannot drift apart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Validation sweeps do not fly a trajectory: the firing poses are prescribed, so this module owns their generation and the Jet Firing History they are written to, leaving the dynamics-based approach-maneuver workflow untouched. The pose convention -- source on the arc of radius L about the target reference point, aimed at it, DCM first column carrying the thruster axis and second column the sweep plane's binormal -- reproduces the committed sweep JFH generators to file precision, for both the flat and inclined cases. Two builders: one pose's firings (exactly n_firings entries), and the whole sweep as one continuous sequence (exactly len(poses) * n_firings entries, firing times running through). Each firing records the pose it realizes, so a history spanning many poses stays keyed to the sweep grid. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Turns the strike pipeline's per-face pressure, shear and heat-flux arrays into the integrated quantities a validation study reports: pressure, shear and combined force vectors, the corresponding moments about a user-defined reference point, center of pressure, peak surface loads, affected area, component heat load and nondimensional coefficients. Per-face data are not reduced away here -- they keep flowing to the VTK writer unchanged. Conventions are stated explicitly and follow the pipeline's own face-selection rule: pressure acts along -n_hat, shear along the tangential projection of the radial flow direction. The center of pressure is defined as the point on the resultant's line of action closest to the moment reference, since a unique 3D center of pressure does not exist in general; the irreducible force-parallel couple is reported separately, and zero-load and near-cancellation cases return no center of pressure with a status naming the reason rather than a misleadingly large value. An always-defined pressure-weighted centroid is reported alongside. Coefficients are computed only for the normalizations whose inputs are supplied in full; nothing is defaulted or invented. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One record per case, component and firing, carrying everything needed to reproduce and later compare the calculation: identifiers, geometry and mesh information, coordinate system and units, the prescribed source pose, the swept parameters, the plume model and its parameters, the integrated loads, the surface-field peaks, the optional coefficients, the artifact paths and the code/configuration provenance. Two machine-readable artifacts, both in formats the repository already uses and with no new dependency: a flat CSV (vectors expanded to _x/_y/_z columns, one column per coefficient, so it is directly plottable) and a JSON metadata document carrying study-level provenance plus the nested per-case records -- the exchange format an externally generated dataset is transformed into for comparison. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Compares study results against independently generated data WITHOUT knowing its origin: a reference record is named quantities attached to matching keys, loadable from CSV, JSON or YAML, and whether the numbers came from a DSMC solver, an analytical solution, an experiment or another code changes nothing. No producer-specific importer exists here. Metrics are applied only where they are mathematically meaningful: absolute error, relative error (None when the reference is zero, never infinity), normalized RMSE, peak-value error, integrated-load error (compared as vectors, so a load of the right magnitude pointing the wrong way is an error) and center-of-pressure displacement. A quantity the reference supplies but the result does not is reported as missing, and a case with no matching record is listed as unmatched -- nothing is fabricated or defaulted. The CSV layout is exactly what StudyResults.write_csv emits, so an external producer can be transformed into it column-for-column. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Force, moment and heat flux versus plate angle and source distance, center-of-pressure travel, and a study-versus-reference error summary. Every figure is optional output: the module is imported lazily by the study engines and pins matplotlib's non-interactive Agg backend, so no run and no automated test needs graphical output. Series are keyed on the swept pose rather than on the case, so both study engines plot identically. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The plumbing every study engine needs, in one place: build the existing PyRPOD case objects (failing early on an unsupported plume model or an unknown thruster id), precompute the target geometry and component face selection once, read a generated JFH back through the normal JetFiringHistory parser, run one history through the plume-strike calculation, and turn a firing's per-face arrays into per-component result records. Strike execution has two paths: with VTK output enabled the full PlumeStrikeEstimationStudy.jfh_plume_strikes() pipeline runs with the target vehicle's output root redirected, so studies that execute several histories cannot overwrite one another's artifacts; with it disabled the pipeline's own per-firing core runs instead -- identical numbers, no files. Living here rather than on a base class lets the two study engines be siblings with no duplicated logic and no inheritance between them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PlumeValidationStudy runs a configured sweep with ONE Jet Firing History per angle-distance combination (sweep.mode: per_case, the default): every pose is an independent case with its own history, strike run and artifacts, so no pose's results can be polluted by another's. That is what a validation matrix compared pose-by-pose against reference data wants. Per case it builds the prescribed firings, writes and re-reads the case's JFH, runs the plume-strike calculation, integrates per-component loads, exports the per-face VTK fields, records structured results and optionally compares against external reference data. Known limitation, documented here and in the emitted provenance: face selection is the pipeline's existing behavior, so plume shadowing, occlusion and self-shadowing of concave targets are not modeled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ParameterSweepStudy runs the sweep as ONE case driven by ONE Jet Firing History (sweep.mode: single_jfh): every pose contributes its firings to a single history, the strike pipeline runs once over all of them, and the strike files form a single results/strikes/firing-<i>.vtu series -- the repository's existing sweep convention, scrubbable in ParaView as a time sequence. Because the history is shared, the pipeline's cumulative fields finally mean something: max_pressures / max_shears become the worst load any pose put on each face, cum_strikes a coverage map and cum_heat_flux_load an accumulated dose. Their per-component summary is exposed as `study.envelope` and recorded in the metadata; with the full pipeline disabled those arrays do not exist and the envelope is reported empty rather than reconstructed. A separate class rather than a mode flag inside the per-case engine: the two decompositions answer different questions and their outputs differ in kind, not just in layout. Everything they share lives in study_runtime, so per-firing numbers are identical between engines and only the decomposition differs. n_firings keeps its per-pose meaning, and the single history is required to hold exactly len(poses) * n_firings entries -- checked after writing and again after reading the file back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Repairs, in the legacy dynamics-driven sweep path -- each of which raised
before any physics ran:
- init_trade_study constructed PlumeStrikeEstimationStudy.RPOD(case_dir), a
class that does not exist, and passed a case directory where the study
takes a MissionEnvironment. It now builds the environment and the real
PlumeStrikeEstimationStudy;
- the sweeps called jfh_plume_strikes(trade_study=True), which that method's
(parallel, workers) signature rejects with TypeError. They now use the
per-firing arrays it returns;
- print_mission_report read impingement maxima off study attributes that are
never set. It now summarizes them from those returned arrays, keeping the
old attribute path for existing callers;
- interpret_mission_report read self.rpod.config, which does not exist; the
configuration lives on the study's MissionEnvironment.
Adds the package-level prescribed-validation API:
study = TradeStudy.from_config('case.yaml')
results = study.run()
with compare() and plot() alongside it, and an optional output_dir override
so a committed configuration can be run into a scratch location. The engine
is selected from sweep.mode -- per_case binds PlumeValidationStudy,
single_jfh binds ParameterSweepStudy -- so the API and the configuration file
are the same either way. The class stays a thin facade over the pyrpod.mdao
modules, and its legacy constructor keeps its original behavior.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four examples on existing case assets, all recording the plume model explicitly and taking moments about the target's own reference point: - flat_plate_baseline.yaml: one head-on firing at L/D = 4; - flat_plate_sweep.yaml: 19 angles x 5 distances as 95 independent cases; - flat_plate_sweep_single_jfh.yaml: the same sweep as one 95-firing history, differing from the previous file only in sweep.mode and the output names; - cylinder_baseline.yaml: the same machinery on a curved, closed target, to keep the architecture honest and to be where quantitative cylinder reference data plugs in later. The flat-plate files carry the Cai normalization derived from the case's own thruster definition file, so coefficients are available. The cylinder file supplies none, so its coefficients are correctly reported as unavailable rather than invented -- no cylinder reference data exists yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Unit level: - mdao_unit_test_03, the exact meaning of n_firings: invalid counts are rejected; one requested firing produces one JFH entry and N produce exactly N, read back through JetFiringHistory; an explicit firing list that disagrees is an error; a whole-sweep sequence holds exactly poses x n_firings pose-tagged entries with continuous firing times; the generated poses reproduce all 95 firings of the committed flat-plate sweep JFH to file precision; and the dynamics-driven approach honors an exact count, reporting an unreachable one rather than shortening the history. - mdao_unit_test_04, surface-load integration on meshes whose loading has a known closed-form resultant: force, moment about a user-defined point, center of pressure (recovered from a linear pressure field, consistent with the reported moment, and unavailable with a stated reason for zero-load and cancelling loads), coefficients present and absent, component selection. - mdao_unit_test_05, configuration parsing and validation, including that the case's own config.ini keeps parsing unchanged and that n_firings keeps its per-pose meaning in both sweep modes. - mdao_unit_test_06, the comparison metrics and CSV / JSON / YAML datasets (compared identically regardless of origin), plus the result schema's serialization. Integration level: - mdao_integration_test_02, the baseline flat-plate case end to end through TradeStudy.from_config: artifacts, provenance, head-on physics, available coefficients, and agreement of the integrated normal load with the INDEPENDENT Cai 2016 exact reference, not with PyRPOD's own output. - mdao_integration_test_03, a reduced multi-angle / multi-distance sweep: mirror symmetry in +/- angle, load decay with distance, center-of-pressure travel, per-case VTK isolation, optional plots, and the same machinery on the cylinder target with coefficients correctly unavailable. - mdao_integration_test_04, the single-history engine: engine dispatch from the configuration, one JFH of exactly poses x n_firings entries, one record per firing keyed to its pose, a single results/strikes series, and the sweep envelope (absent, not fabricated, without the full pipeline). Its strongest assertion is equivalence -- per-firing loads match the per-case engine pose by pose. Measured against the exact reference: integrated normal load within 1.5% mean / 2.2% max, component heat load ~15% -- the documented accuracy of the Maxwellian engineering chain, asserted only as a loose envelope so a convention regression fails loudly while the known gap passes. Studies write into temporary directories, so a test run leaves no artifacts behind. The manifest entries and the regenerated tests/README.md land here rather than in a follow-up commit: the tooling suite checks that the manifest and pytest's collection agree, so the two must move together. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs/plume_validation_study.md covers the repaired TradeStudy architecture and the repairs made to the legacy sweeps, the package-level API, the two sweep decompositions and when to use each, the YAML configuration schema, the exact meaning of n_firings and prescribed firing lists, the force / moment / center-of-pressure definitions (including why a unique 3D center of pressure needs a stated convention and how degenerate cases are reported), coefficient normalization requirements, the result schema, VTK outputs, optional plots, the external reference-data format and how DSMC results plug into it later, how to add another validation geometry, and the known limitations. The root README gains a short pointer with the worked flat-plate example. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andytorrestb
force-pushed
the
feature/plume-validation-study
branch
from
July 28, 2026 04:43
515085f to
7e4422f
Compare
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.