nextnano_optimizers.plotting module#

Visualization of Evolution results. The module is a read-only consumer of the JSON log a run already writes, so it never re-runs a simulation and works equally well on a log from last week.

matplotlib is an optional dependency of the package. Importing this module without it raises a clear error and the rest of nextnano_optimizers keeps working. Install it with:

pip install nextnano_optimizers[plot]

EvolutionPlotter is the offline figure producer — construct it from a live Evolution, from a log path, or from raw arrays, then call one of the plot_* methods. Each returns the matplotlib Figure, so the caller can compose or restyle further. LogWatcher is the live counterpart: it owns a window and a timer, re-reads the log when it changes on disk, and redraws. It is what Evolution.run(live_progress=True) spawns in a separate process, so the window’s event loop is never starved by the running simulation.

EvolutionPlotter(data[, log_path, ...])

Visualizes the outcome of an Evolution run.

EvolutionPlotter.from_evolution(evolution, ...)

Build a plotter from a finished Evolution instance.

EvolutionPlotter.from_json(json_path, **kwargs)

Build a plotter from a JSON log file on disk (offline use).

EvolutionPlotter.from_arrays(generation, ...)

Build a plotter straight from in-memory per-evaluation arrays.

EvolutionPlotter.plot_progress([fig, series])

Per-generation progress of objectives and parameters.

EvolutionPlotter.plot_pareto()

Final population in objective space, Pareto front highlighted.

EvolutionPlotter.plot_inputs()

Final population in input (decision-variable) space, Pareto highlighted.

EvolutionPlotter.plot_all([save_dir, show, ...])

Produce all available figures (progress, objective space, input space).

LogWatcher(json_path[, interval])

Live viewer for a (possibly still-running) evolution JSON log.

watch_progress(json_path[, interval])

Open a live-updating progress window for an evolution JSON log (blocking).

Usage#

Straight after a run — no log path needed:

evolution.run(gen=30)
EvolutionPlotter.from_evolution(evolution).plot_all(save_dir=".", show=True)

Offline, from a log written earlier, with axis labels and outliers dropped:

plotter = EvolutionPlotter.from_json(
    "Simulation_2025_09_17.json",
    param_names=["well width", "barrier width"],
    fitness_names=["gain", "current"],
    exclude_failed=True,
)
fig = plotter.plot_pareto()

fitness_limits discards solutions outside per-objective bounds from every panel, so all panels keep showing the same population:

plotter = EvolutionPlotter.from_json(
    "Simulation_2025_09_17.json",
    fitness_limits={"fitness2": (None, 1e3)},
)

Command line#

The module also runs as a script. Without --watch it shows the offline figures for a log; with --watch it opens the live viewer that polls the log and redraws while a run is still writing it:

python -m nextnano_optimizers.plotting Simulation_2025_09_17.json
python -m nextnano_optimizers.plotting Simulation_2025_09_17.json --watch

Reference#

class nextnano_optimizers.plotting.EvolutionPlotter(data: dict, log_path: str | None = None, param_names: list | None = None, fitness_names: list | None = None, exclude_failed: bool = False, fitness_limits: list | dict | None = None, progress_series: list | str | None = None)#

Bases: object

Visualizes the outcome of an Evolution run.

Every plot_* method returns the matplotlib Figure (and accepts style kwargs), so results can be further composed or restyled by the caller.

Parameters:
datadict

Parsed JSON log, i.e. {"simulation": {"gen1": [...], ...}, "summary": {...}} as written by Evolution.run().

log_pathstr, optional

Path the data came from. Used only to name saved PNGs next to it.

param_names, fitness_nameslist of str, optional

Axis labels for the input variables / objectives. Default to param1..N / fitness1..M.

exclude_failedbool, optional

Drop evaluations where every fitness component equals summary["non_execution_penalty"] (failed simulations) before plotting. Default False: opt-in, because it is only a heuristic – a genuine solution whose fitness happens to equal the penalty would be dropped too. Enable it when penalty outliers flatten the axes.

fitness_limitslist or dict, optional

Per-objective (min, max) bounds; a solution whose fitness falls outside any given bound is discarded from every plot (objective, parameter, objective-space and input-space panels alike), so all panels keep showing the same population. Either a list with one entry per objective (None entries mean “no limit on this objective”) or a sparse dict keyed by objective name or index:

fitness_limits=[(0, 100), None]
fitness_limits={"fitness2": (None, 1e3)}

None inside a pair leaves that side unbounded. A NaN fitness in a limited objective is discarded too (NaN never satisfies a bound). Note the Pareto highlights come from the run summary and are not filtered, so a tight limit can leave a highlighted point outside the surviving scatter.

progress_seriessequence of str or str, optional

Default selection of per-panel curves for plot_progress(), any of PROGRESS_SERIES (pass "all" for every curve). None (default) draws DEFAULT_SERIES. A series argument to plot_progress() (or progress_series to plot_all()) overrides it per call.

Methods

from_arrays(generation, params, fits, *[, ...])

Build a plotter straight from in-memory per-evaluation arrays.

from_evolution(evolution, **kwargs)

Build a plotter from a finished Evolution instance.

from_json(json_path, **kwargs)

Build a plotter from a JSON log file on disk (offline use).

plot_all([save_dir, show, progress_series])

Produce all available figures (progress, objective space, input space).

plot_inputs()

Final population in input (decision-variable) space, Pareto highlighted.

plot_pareto()

Final population in objective space, Pareto front highlighted.

plot_progress([fig, series])

Per-generation progress of objectives and parameters.

validate_config(*[, expected_n_params, ...])

Validate plotting keyword arguments without building a plotter.

classmethod from_arrays(generation, params, fits, *, summary=None, param_names=None, fitness_names=None, exclude_failed=False, fitness_limits=None, progress_series=None) EvolutionPlotter#

Build a plotter straight from in-memory per-evaluation arrays.

Bypasses the JSON log entirely – used for live plotting during a run, where the accumulated generation/params/fits are already in memory.

Parameters:
generationarray-like of int, shape (n,)

Generation index of each evaluated individual.

paramsarray-like, shape (n, n_params)

Input-variable values of each evaluated individual.

fitsarray-like, shape (n, n_obj)

Fitness values of each evaluated individual.

summarydict, optional

Summary dict; only non_execution_penalty matters for the progress plot (failed-evaluation masking).

fitness_limitslist or dict, optional

Per-objective (min, max) row filter; see the class docstring.

classmethod from_evolution(evolution, **kwargs) EvolutionPlotter#

Build a plotter from a finished Evolution instance.

Reads the run’s JSON log directly from the evolution’s manager, so no log path is needed.

classmethod from_json(json_path, **kwargs) EvolutionPlotter#

Build a plotter from a JSON log file on disk (offline use).

plot_all(save_dir=None, show=False, progress_series=None)#

Produce all available figures (progress, objective space, input space).

Parameters:
save_dirstr, optional

If given, save PNGs there as <logname>_progress.png / _pareto.png / _inputs.png. If save_dir is the string "next_to_log" and a log_path is known, they are written next to the log.

showbool, optional

Call plt.show() at the end. Default False (never blocks in library code).

progress_seriessequence of str or str, optional

Which per-panel curves the progress figure draws, forwarded to plot_progress() (see its series argument; pass "all" for every curve). None (default) falls back to the plotter’s progress_series.

plot_inputs()#

Final population in input (decision-variable) space, Pareto highlighted.

Input-space counterpart of plot_pareto. Returns None for a single parameter – an input-space view needs at least two parameters to form a pair, and parameter-vs-generation is already covered by plot_progress. For >= 2 parameters it draws a scatter for every pair of parameters: the final population’s values as points, with the final-generation Pareto solutions highlighted.

plot_pareto()#

Final population in objective space, Pareto front highlighted.

Returns None for a single objective – an objective-space view needs at least two objectives, and fitness-vs-generation is already covered by plot_progress. For >= 2 objectives it draws a scatter for every pair of objectives: the final population’s fitnesses as points, with the Pareto-front points highlighted; for exactly 2 objectives the Pareto points are connected into the front line.

plot_progress(fig=None, series=None)#

Per-generation progress of objectives and parameters.

Returns the Figure, laid out in two columns sharing the generation x-axis: objectives on the left, parameters on the right. Empty cells (when the objective and parameter counts differ) are hidden. When any evaluations are excluded (exclude_failed / fitness_limits), an extra panel directly below the objective panels charts the per-generation excluded counts, one line per reason (failed / out-of-limit).

Parameters:
seriessequence of str, optional

Which per-panel curves to draw, any of PROGRESS_SERIES (candidates, min-max, min, max, median, mean, best). None (default) falls back to the plotter’s progress_series, which itself defaults to DEFAULT_SERIES (candidates, min-max, mean, best); min, max and median are available but off by default. Pass an explicit list to override, e.g. series=["median", "min", "max"]. best (per-generation minimum) is only drawn on the objective panels.

If ``fig`` is given, it is cleared and reused instead of creating a new
figure – this lets a live view redraw into the same window (far cheaper
than tearing down and recreating the figure every generation).
classmethod validate_config(*, expected_n_params=None, expected_n_obj=None, **kwargs)#

Validate plotting keyword arguments without building a plotter.

Lets a caller that configures plotting ahead of time (notably nextnano_optimizers.optimizer.Evolution.setup_plotting()) reject a bad kwarg at the point it was passed, rather than much later when the figures are drawn – or silently, in the detached live-viewer process whose stderr is discarded. Needs no data and no matplotlib.

Parameters:
expected_n_params, expected_n_objint, optional

Problem dimensions to check the lengths of param_names / fitness_names against. Leave None (default) when unknown, and that length check is skipped.

**kwargs

The candidate plotting kwargs (the same ones __init__ accepts).

Raises:
TypeError

If kwargs holds a name that is not an EvolutionPlotter kwarg.

ValueError

If progress_series names an unknown curve, or param_names / fitness_names has a length other than the expected dimension.

class nextnano_optimizers.plotting.LogWatcher(json_path, interval=1.0, **plotter_kwargs)#

Bases: object

Live viewer for a (possibly still-running) evolution JSON log.

Owns one matplotlib figure and a GUI timer; each tick re-reads the log if it changed on disk and redraws the progress figure into the same window. Runs in its own process (see the module CLI), so the window’s event loop is never starved by the simulation.

Parameters:
json_pathstr

Path to the evolution JSON log to watch. It need not exist yet: the watcher waits for it to appear and for the first population to be logged.

intervalfloat, optional

Polling interval in seconds. Default 1.0.

seriessequence of str, optional

Which per-panel curves to draw; forwarded to EvolutionPlotter.plot_progress(). None uses the default set.

plotter_kwargsdict, optional

Forwarded to EvolutionPlotter (e.g. param_names, fitness_names, exclude_failed, fitness_limits).

Methods

start()

Open the window and block in the GUI event loop until it is closed.

start()#

Open the window and block in the GUI event loop until it is closed.

In the viewer process the GUI loop is the program, so this blocks. One immediate poll is done before starting the timer so a log that is already complete shows up without waiting a full interval.

nextnano_optimizers.plotting.watch_progress(json_path, interval=1.0, **plotter_kwargs)#

Open a live-updating progress window for an evolution JSON log (blocking).