Artifacts
The files a study leaves behind: the model diagram, the flown trajectory, the report.
A run that prints its numbers and exits leaves nothing to attach to a design review. This module writes the three artefacts a study is normally asked for, into one directory the caller names:
n2.htmlOpenMDAO’s N2 diagram of the built model. Every component, every connection, every solver – generated from the live problem, so it is the model that was actually run.
trajectory.pdfWhat was flown, plotted against range. The quantities and their units are the ones OpenConcept’s own
B738.pyplots inshow_outputs: altitude, equivalent airspeed, fuel used, throttle, vertical speed, Mach number and lift coefficient.report.txt,results.jsonThe same text the command line prints, and the same numbers as structured data.
Nothing here computes anything. The trajectory is read out of the converged box phase by phase, in the units the box is asked for them in, exactly as every other cdadt response is read – and the phases are discovered from what the box publishes rather than listed, so a different mission model plots correctly without being described here.
Matplotlib is an optional dependency (pip install -e ".[plot]"). Only :meth:`
StudyArtifacts.write_trajectory` needs it, and it says so by name if it is missing.
- exception cdadt.artifacts.ArtifactError[source]
Bases:
ExceptionRaised when an artefact cannot be produced from the box as it stands.
- class cdadt.artifacts.MissionTrajectory(box, mission_path='mission')[source]
Bases:
objectWhat was flown, read out of a converged black box.
The phases are discovered rather than listed, and ordered by where they happen rather than by what they are called.
Which subsystems are phases. A steady flight phase in an OpenConcept mission integrates its own state and therefore publishes
ode_integ_phase.range_final; the ground-roll phases –v0v1,v1vr,rotateand the rejected-takeoffv1v0– do not. That is the discriminator, and being structural rather than a list of names it lets a mission model with a second diversion, or without a loiter, plot correctly with nothing here changed. It also keeps the rejected takeoff out of the picture:v1v0retraces ground already covered, and drawn on a range axis it is a trajectory that doubles back.What order they are flown in. Alphabetical order is not flight order – it puts loiter before the reserve climb – and the box publishes its outputs sorted by name, so model order is not available to be read either. Phases are therefore ordered by the abscissa itself: where each one starts. That is a fact about the converged mission rather than a convention.
- Parameters:
box (OpenConceptSizingBox) – A converged box. Reading an unconverged one produces a plot of a state the solver was passing through, which is indistinguishable from a mission – and leaves the phase order resting on numbers that do not mean anything yet.
mission_path (str, optional) – Name of the mission subsystem inside the box. Default
"mission".
- Raises:
ArtifactError – If the box publishes no steady flight phase, which means either that it was never built or that
mission_pathnames the wrong subsystem.- Variables:
ABSCISSA (Trace) – What everything is plotted against: ground distance covered.
PHASE_MARKER (str) – The output whose presence makes a mission subsystem a steady flight phase.
TRACES (tuple of Trace) – The plotted quantities: the ones OpenConcept’s own
B738.pyplots, in its order and its units, with one difference in where the number comes from. It plotsfuel_used, which is a promoted input of the phase and therefore not unambiguously readable. What the box publishes as an output is the phase’s fuel integrator, and each phase’s carries on from the end of the one before rather than restarting – measured, not assumed: on the shipped case the last node reads 41000.07 lbm, which istotal_fuelto all printed digits. The panel is therefore cumulative mission fuel, asB738.pyplots it.
- PHASE_MARKER: ClassVar[str] = 'ode_integ_phase.range_final'
- TRACES: ClassVar[tuple[Trace, ...]] = (Trace('fltcond|h', 'ft'), Trace('fltcond|Ueas', 'kn'), Trace('fuel_burn_integ.fuel_burn', 'lbm'), Trace('throttle', None), Trace('fltcond|vs', 'ft/min'), Trace('fltcond|M', None), Trace('fltcond|CL', None))
- property phases: tuple[str, ...]
The phases flown, in flight order.
- property available: tuple[Trace, ...]
The traces every phase publishes, and which can therefore be plotted whole.
A trace one phase is missing is dropped rather than plotted with a gap, because a trajectory drawn across a hole reads as a trajectory, not as missing data.
- read(trace)[source]
Return one trace, concatenated across the phases in flight order.
- Raises:
ArtifactError – If a phase does not publish it, naming the phase.
- Parameters:
trace (Trace)
- Return type:
ndarray
- class cdadt.artifacts.StudyArtifacts(directory)[source]
Bases:
objectThe directory a study writes its files into, and the files it writes.
- Parameters:
directory (str or Path) – Where to write. Created if it does not exist, including parents.
Examples
>>> artifacts = StudyArtifacts("b738_out") >>> artifacts.write_text(report, "report.txt") PosixPath('b738_out/report.txt')
- property directory: Path
Where the files are written.
- write_text(text, name)[source]
Write one text file and return its path.
- Parameters:
text (str)
name (str)
- Return type:
Path
- write_json(payload, name)[source]
Write one JSON file and return its path.
- Parameters:
payload (Any)
name (str)
- Return type:
Path
- write_n2(box, name='n2.html')[source]
Write OpenMDAO’s N2 diagram of the built model.
- Raises:
ArtifactError – If the box has not been built, since there is no model to diagram.
- Parameters:
box (Any)
name (str)
- Return type:
Path
- write_trajectory(box, title, mission_path='mission', name='trajectory.pdf', columns=2)[source]
Plot the flown trajectory against range, one panel per quantity.
- Parameters:
box (OpenConceptSizingBox) – A converged box.
title (str) – Figure title, e.g. the case file’s name.
mission_path (str, optional) – Name of the mission subsystem. Default
"mission".name (str, optional) – File to write. The extension decides the format, as matplotlib reads it.
columns (int, optional) – Panels across. Default 2.
- Raises:
ArtifactError – If matplotlib is not installed, or if the box publishes no trajectory.
- Return type:
Path
- PROFILE_PANELS: ClassVar[tuple[tuple[str, tuple[Trace, ...]], ...]] = (('Altitude (ft)', (Trace('fltcond|h', 'ft'),)), ('Mach number', (Trace('fltcond|M', None),)), ('Vertical speed (ft/min)', (Trace('fltcond|vs', 'ft/min'),)), ('Weight (lb)', (Trace('weight', 'lb'),)), ('Longitudinal force (lb)', (Trace('drag', 'lbf'), Trace('thrust', 'lbf'))), ('Throttle (%)', (Trace('throttle', None),)))
The panels
B738_sizing.py’s ownplot_resultsdraws, in its order and its units. The fifth carries two series on one axis, which is why a panel is a tuple of traces.
- write_mission_profile(box, title, mission_path='mission', name='mission.pdf')[source]
Reproduce
B738_sizing.py’splot_results: the 2x3 mission profile.This is the figure belonging to the example cdadt actually drives, as distinct from
write_trajectory(), which reproduces the seven-panel figureB738.pydraws inshow_outputs. They plot different quantities, so cdadt writes both rather than choosing which example to be faithful to.Throttle is drawn as a percentage and the force panel carries drag and thrust together, both as
plot_resultsdoes it.- Raises:
ArtifactError – If matplotlib is not installed, or if the box publishes no trajectory.
- Parameters:
box (Any)
title (str)
mission_path (str)
name (str)
- Return type:
Path
- write_takeoff(box, title, mission_path='mission', name='takeoff.pdf')[source]
Plot the balanced field: the continued takeoff and the rejected one, against distance.
Neither reference example draws this. It is here because the field length is what the takeoff phases are for, and “balanced” is a claim about two paths ending at the same distance – which a number in a table states but does not show.
- Raises:
ArtifactError – If matplotlib is not installed, or if the box has no ground roll.
- Parameters:
box (Any)
title (str)
mission_path (str)
name (str)
- Return type:
Path
- class cdadt.artifacts.TakeoffTrajectory(box, mission_path='mission')[source]
Bases:
objectThe balanced field: what the aeroplane does before the mission starts.
Neither reference example plots this –
B738.pyandB738_sizing.pyboth list the same seven steady phases – but the takeoff is where the field length comes from, and a balanced field is a statement about two paths that a table of numbers cannot show.Two paths, not one. From brake release the aeroplane accelerates to V1 (
v0v1). At V1 it either continues, accelerating to rotation and lifting off (v1vrthenrotate), or it rejects and brakes to a stop (v1v0). Balanced means the two end at the same distance. Drawn together against ground distance, that is the picture; concatenated into one line, as the mission phases are, it would be a trajectory that doubles back on itself, which is exactly whyMissionTrajectoryleaves these out.Why these four are named rather than discovered. The mission phases are discovered because nothing distinguishes them but their structure. These four are different: which one is the continued takeoff and which is the rejected one is semantics, and no output the box publishes carries it. The names are OpenConcept’s own, from the takeoff group that
B738_sizing.pybuilds, and a box that does not have them raises rather than guessing.- Parameters:
box (OpenConceptSizingBox) – A converged box.
mission_path (str, optional) – Name of the mission subsystem. Default
"mission".
- Raises:
ArtifactError – If the box does not publish the ground-roll phases, naming the ones it is missing.
- CONTINUE: ClassVar[tuple[str, ...]] = ('v0v1', 'v1vr', 'rotate')
rotate and lift off.
- Type:
Accelerate to V1, then continue
- ABORT: ClassVar[tuple[str, ...]] = ('v0v1', 'v1v0')
Accelerate to V1, then reject and brake to a stop.
- TRACES: ClassVar[tuple[Trace, ...]] = (Trace('fltcond|Utrue', 'kn'), Trace('fltcond|h', 'ft'), Trace('throttle', None), Trace('weight', 'lb'))
- V1_PATH: ClassVar[str] = 'takeoff|v1'
Where the decision speed lives, relative to the mission.
- property decision_speed: float
where the two paths separate.
- Type:
V1, in knots
- class cdadt.artifacts.Trace(name, units, label)[source]
Bases:
objectOne quantity plotted along the mission, read from every phase and concatenated.
- Parameters:
name (str) – Name of the variable inside a phase, e.g.
"fltcond|h".units (str or None) – Units to read it in.
Nonefor a dimensionless quantity such as throttle.label (str) – Axis label, including the unit as a reader expects to see it.
Examples
>>> Trace("fltcond|h", "ft", "Altitude (ft)") Trace('fltcond|h', 'ft')
- property name: str
Name of the variable inside a phase.
- property units: str | None
Units the variable is read in.
- property label: str
Axis label.