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) and margin_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 the selected traitlet.

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 var go, and which requirement stops it”: re-runs problem (an OpenMDAO Problem, duck-typed: set_val/run_model/get_val) for each entry of values, plotting every margin output (>= 0 iff the constraint holds, per longeron.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. Give title as the finding the chart shows (“Payloads above 0.46 kg cannot fly”).

Return type:

Any

longeron.analysis.viz.mix_table(study, architectures=None, derived=None)[source]

Flat rows (selection + metrics + feasible) for plotting.

derived adds computed columns, e.g. {"thrustToWeight": lambda a: a.metrics["totalThrust"] / (a.metrics["totalMass"] * 9.81)}. Defaults to the full candidate space (all_architectures()).

Return type:

list[dict[str, Any]]

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 selected traitlet (widget.selected_indices()). Re-assigning table_json re-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 positions t per axis, display strings v per axis, a hover label, and the feasible flag. Categorical axes place categories in first-appearance order; constant numeric axes pin to the middle.

Return type:

dict[str, Any]

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.

architectures is every evaluated mix (feasible or not, e.g. from all_architectures()). The highlighted frontier is computed here, from the plotted axes themselves: the feasible mixes that are non-dominated under sense – 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 with sense=("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 via annotate instead.

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 sense so the staircase always bounds the attainable side (when it has more than one point); give title as a finding (“The $118 cruiser dominates the cost-endurance trade”), not a caption.

Return type:

Any