Visualization¶
Requires the viz extra (pip install "longeron[viz]").
Views of trade-study results: figures and a parallel-coordinates widget.
Two kinds of output over the mix tables produced by
longeron.analysis.trades:
static, publication-styled matplotlib figures –
pareto_figure()(the two-objective frontier inside the full candidate space; the frontier is computed from the plotted axes so a many-objective front can never masquerade as a two-objective one) andmargin_sweep_figure()(requirement margins across a design-variable sweep of an OpenMDAO problem; every stretch where ANY margin goes negative is shaded warm and hatched and labeled with the constraint(s) binding there – feasible stretches stay unshaded, the absence of shading IS the feasible region);an interactive parallel-coordinates anywidget –
parcoords()– following the house widget pattern (longeron.widgets.replay): Python bakes the whole payload (axis specs, tick labels, normalized line positions) into one JSON-string traitlet, the inline vanilla-JS front-end only paints. Brush gestures live in a narrow zone around each axis (the brushes are movable/resizable intervals with end handles); polyline hover works everywhere else; the brushed subset syncs back through theselectedtraitlet.
Requires the viz extra: pip install "longeron[viz]" (matplotlib
for the figures, anywidget for the widget; both import lazily).
- longeron.analysis.viz.Sense¶
one plotted objective’s direction of improvement (there is deliberately no silent maximize default: see
pareto_figure())alias of
Literal[‘min’, ‘max’]
- longeron.analysis.viz.margin_sweep_figure(problem, var, values, margins, *, xlabel=None, title=None)[source]¶
Requirement margins across a design-variable sweep.
One chart answering “where can
vargo, and which requirement stops it”: re-runsproblem(an OpenMDAOProblem, duck-typed:set_val/run_model/get_val) for each entry ofvalues, plotting every margin output (>= 0 iff the constraint holds, perlongeron.analysis.mdao) with direct end labels. Only the INFEASIBLE stretches are shaded (_sweep_bands()): wherever ANY margin goes negative the band is tinted warm and hatched, labeled with every constraint binding there (stacked when several overlap), and its boundaries are marked – the union over all constraints, so a floor broken at the slow end, a ceiling broken at the fast end, and a requirement lost in the middle all show up with their own names. Feasible stretches stay unshaded: the clear axis IS the go region. Restores the original value afterwards. Givetitleas the finding the chart shows (“Payloads above 0.46 kg cannot fly”).- Return type:
- longeron.analysis.viz.mix_table(study, architectures=None, derived=None)[source]¶
Flat rows (selection + metrics +
feasible) for plotting.derivedadds computed columns, e.g.{"thrustToWeight": lambda a: a.metrics["totalThrust"] / (a.metrics["totalMass"] * 9.81)}. Defaults to the full candidate space (all_architectures()).
- longeron.analysis.viz.parcoords(rows, axes=None, *, width_px=920, height_px=380)[source]¶
A brushable parallel-coordinates widget over
mix_table()rows.Brush gestures live in a narrow zone around each axis (the cursor turns to a crosshair there); everywhere else the pointer belongs to the polylines (hover for the full mix). Drag along an axis to brush a range – lines outside any brush fade. A brush is editable after creation: drag its body to move the whole interval (grab cursor), drag an end handle to extend/contract that end (ns-resize cursor), and click the axis outside the brush – or double-click anywhere in the zone – to clear it. The indices of rows passing every brush sync back through the
selectedtraitlet (widget.selected_indices()). Re-assigningtable_jsonre-bakes the view in place (brushes survive by axis name), so a dashboard can re-score an axis live.- Return type:
AnyWidget
- longeron.analysis.viz.parcoords_payload(rows, axes=None)[source]¶
The baked parallel-coordinates payload (house pattern: Python owns the schema, JS only paints).
Per axis: a name and tick marks
{t, label}in normalized [0, 1] coordinates (1 = top). Per line: normalized positionstper axis, display stringsvper axis, a hover label, and the feasible flag. Categorical axes place categories in first-appearance order; constant numeric axes pin to the middle.
- longeron.analysis.viz.pareto_figure(architectures, *, x, y, sense=('min', 'min'), panel_y=None, xlabel=None, ylabel=None, panel_ylabel=None, annotate=None, title=None)[source]¶
The two-objective Pareto frontier inside the full candidate space.
architecturesis every evaluated mix (feasible or not, e.g. fromall_architectures()). The highlighted frontier is computed here, from the plotted axes themselves: the feasible mixes that are non-dominated undersense– an explicit(x_sense, y_sense)pair, each"min"or"max". The default is the conservative("min", "min"); a chart whose y metric is better large (station time, payload range, catchable target speed, …) must say so explicitly withsense=("min", "max")– there is deliberately no silent maximize default, because a front computed with the wrong sense hugs the wrong corner and leaves genuinely better mixes drawn as dominated dots outside the drawn staircase.A caller-supplied front is likewise not accepted – a front computed over more objectives than the two plotted axes is only a projection, and a projection puts points on the drawn “frontier” that are strictly worse on both plotted metrics (they earn their Pareto rank through an unplotted objective). Track such extra objectives with
panel_y– a small-multiple panel over the same x axis – and call-outs viaannotateinstead.Dominated mixes are muted dots, infeasible ones pale crosses – infeasible mixes can land outside the frontier (their metrics are what the mix would score if it could fly; the constraints it breaks are exactly why the front does not reach them). The frontier is the accent + a step line oriented by
senseso the staircase always bounds the attainable side (when it has more than one point); givetitleas a finding (“The $118 cruiser dominates the cost-endurance trade”), not a caption.- Return type: