Evolution#

Evolutionary optimization through the pymoo algorithms. It works on a population within bounds rather than from an initial guess, tolerates failed simulations and non-smooth objectives, and handles several objectives at once.

Because it evaluates a whole population per generation, parallel_limit matters: it caps how many nextnano simulations run concurrently. The algorithm is selected with algorithm; the default differs between single-objective and multi-objective problems, since the two need different machinery.

Each run writes a JSON log that nextnano_optimizers.plotting reads, both afterwards and — with live_progress=True — while the run is still going, by spawning a viewer in a separate process.

Evolution(nextnanoio, metric, bounds[, ...])

The evolution class to run the evolutionary optimization with pymoo algorithms.

Evolution.run([gen, interval, seed, ...])

Runs the evolution with the specified parameters.

Evolution.load_algorithm(algorithm)

Loads the algorithm for the evolution.

Evolution.set_size(size)

Sets the population size for the evolution.

Evolution.setup_plotting(**plotter_kwargs)

Configure plotting once, for both the live viewer and the post-run figures.

Evolution.plot_all([save_dir, show])

Visualize this run's results (progress, Pareto front, input space).

Evolution.import_from_json()

Loads the JSON logger data.

Usage#

A single-objective run with four simulations in flight at a time:

from nextnano_optimizers.optimizer import Evolution

evolution = Evolution(
    io,
    metric,
    bounds=([5.0], [20.0]),
    size=20,
    parallel_limit=4,
)
evolution.run(gen=30)

Afterwards, champion_x and champion_f hold the best solution and its fitness.

Results#

For a multi-objective run the interesting result is the front rather than the champion. Two are kept, and they answer different questions:

pareto_solutions / pareto_fits

Accumulated over all generations — every solution never dominated at the time it was found. Use this for the best trade-offs the run ever saw.

last_gen_pareto_solutions / last_gen_pareto_fits

Restricted to the final generation. Use this to judge where the population actually converged.

Plotting the outcome, either straight from the object or from the log:

evolution.plot_all(save_dir=".", show=True)

# or, later, from the JSON log
from nextnano_optimizers.plotting import EvolutionPlotter
EvolutionPlotter.from_json("Simulation_2025_09_17.json").plot_all()

setup_plotting configures the plots once and both the live viewer (during the run) and the saved figures (afterwards) read from it, so the same look is applied in both places without repeating it:

evolution.setup_plotting(
    progress_series=["mean", "best"],
    param_names=["temperature", "donor conc."],
    fitness_names=["potential", "-potential"],
)
evolution.run(gen=30, live_progress=True)   # live viewer uses the config
evolution.plot_all(save_dir=".", show=True)  # saved figures use the same

The keyword arguments are forwarded verbatim to EvolutionPlotter, so the accepted ones are exactly its constructor parameters – progress_series, param_names, fitness_names, exclude_failed and fitness_limits. See its reference for their meanings and defaults.

Each setup_plotting call replaces the stored configuration outright; it does not merge with a previous one. Any argument you leave out reverts to its default so repeat the ones you still want.

For a one-off variation, drive EvolutionPlotter directly (EvolutionPlotter.from_evolution(evolution, ...)), which exposes every option.

Worked examples: Optimization of Mid-IR Quantum Cascade Laser Gain and Maximizing Envelope Overlaps for a Given Transition Energy.

Reference#

class nextnano_optimizers.optimizer.Evolution(nextnanoio: IO, metric: Metric, bounds, algorithm='default', size=20, parallel_limit=1)#

Bases: object

The evolution class to run the evolutionary optimization with pymoo algorithms.

Parameters:
nextnanoioIO

input-output object defining input file variables and output datafiles

metricMetric

defines how to retrieve numerical (scalar or vector) metric to optimize from output files

boundstuple of list

A tuple containing two lists, the lower and upper bounds for the input variables. Each list should have the same length as the number of input variables.

algorithmstr, optional

The name of the algorithm to be used for the evolution. If “default”, the default algorithm is used. Default is different for single objective and multi-objective problems.

sizeint, optional

The size of the population for the evolution. Default is 20.

parallel_limit: int, optional

The maximum number of parallel simulations to run. Default is 1.

Attributes:
_optimization_managerEvolutionManager

The optimization manager for the evolution.

problemNextnanoParallelProblem

The problem definition for the evolution.

_population_sizeint

The size of the population for the evolution.

algorithmstr

The name of the algorithm to be used for the evolution.

pareto_solutionslist

The list of Pareto solutions accumulated over all generations.

pareto_fitslist

The list of fitness values for the accumulated Pareto solutions.

pareto_indexeslist

The list of indexes for the accumulated Pareto solutions.

last_gen_pareto_solutionslist

The list of Pareto solutions restricted to the final generation.

last_gen_pareto_fitslist

The list of fitness values for the final-generation Pareto solutions.

last_gen_pareto_indexeslist

The list of indexes for the final-generation Pareto solutions.

champion_xnumpy.ndarray

The champion solution found during the evolution.

champion_fnumpy.ndarray

The fitness value of the champion solution.

champion_indextuple

The index of the champion solution in the population.

norm_functionCallable

The function used to calculate the criteria for a champion solution.

Methods

add_json_summary(gen, seed, algorithm_kwargs)

Adds a summary of the evolution to the JSON logger.

create_dat_file(filename[, output_dir])

Creates a .dat output file from the JSON logger data.

import_from_json()

Loads the JSON logger data.

load_algorithm(algorithm)

Loads the algorithm for the evolution.

load_default_algorithm()

Loads the default algorithm based on the problem type.

plot_all([save_dir, show])

Visualize this run's results (progress, Pareto front, input space).

run([gen, interval, seed, initial_guess, ...])

Runs the evolution with the specified parameters.

set_size(size)

Sets the population size for the evolution.

setup_plotting(**plotter_kwargs)

Configure plotting once, for both the live viewer and the post-run figures.

update_champion(solutions, fits, gen)

Updates the champion solution and fit if a better one is found.

update_last_gen_pareto_front(solutions, ...)

Computes the Pareto front restricted to a single generation's evaluated solutions and stores it separately from the accumulated Pareto front.

update_pareto_front(solutions, fits, gen)

Updates the Pareto front with new solutions and fits.

validate_initial_guess(initial_guess)

Validates the initial guess provided by the user.

add_json_summary(gen, seed, algorithm_kwargs)#

Adds a summary of the evolution to the JSON logger.

create_dat_file(filename, output_dir=None)#

Creates a .dat output file from the JSON logger data.

import_from_json()#

Loads the JSON logger data.

load_algorithm(algorithm: str) None#

Loads the algorithm for the evolution. Only accepts the algorithms defined in defaults. If “default” is passed, the default algorithm is loaded based on the problem type (single or multi-objective).

load_default_algorithm() None#

Loads the default algorithm based on the problem type.

plot_all(save_dir=None, show=False)#

Visualize this run’s results (progress, Pareto front, input space).

Thin sugar over nextnano_optimizers.plotting.EvolutionPlotter; reads this run’s JSON log directly, so no log path is needed. Requires the optional matplotlib dependency (pip install nextnano_optimizers[plot]).

The figures’ curves and labels come from setup_plotting() (plotter kwargs such as progress_series, param_names, fitness_names, fitness_limits). Call it before plot_all to configure them; without it the defaults are used. For per-call variation or offline use, drive nextnano_optimizers.plotting.EvolutionPlotter directly (EvolutionPlotter.from_evolution(self, ...)), which exposes every option.

Parameters:
save_dirstr, optional

Directory to save the figures into as PNGs. If the string "next_to_log", they are written next to this run’s JSON log. If None (default), figures are not saved.

showbool, optional

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

Returns:
list of matplotlib.figure.Figure
run(gen: int = 20, interval: int = 1, seed: int | None = None, initial_guess: list[tuple[Number]] | ndarray | None = None, algorithm_kwargs: dict | None = None, non_execution_penalty: Number | None = None, keep_output: str = 'all', cleanup_per_generation: bool = False, verbose: bool = True, live_progress: bool = False) Result#

Runs the evolution with the specified parameters.

Parameters:
genint, optional

The number of generations to run the evolution. Default is 20.

intervalint, optional

The generation interval at which the accumulated Pareto front (over all generations) is recomputed. The final generation is always included regardless of this interval. Default is 1.

Note: the accumulated Pareto front is always kept. In addition, the Pareto front restricted to the final generation is always computed and stored separately (see last_gen_pareto_solutions / last_gen_pareto_fits, the log, and the JSON summary).

seedint, optional

The seed for the algorithm.

initial_guesslist of tuples or numpy arrays, optional

A list of initial guesses for the population. Each initial guess will be added to the initial population.

algorithm_kwargsdict, optional

A dictionary of keyword arguments to be passed to the algorithm.

non_execution_penaltynumbers.Number, optional

A penalty to be applied for simulations which are failed to execute.

keep_outputstr, optional

Specifies which output to keep after the evolution is finished. Options are “all”, “pareto”, or “champion”. Default is “all”.

verbosebool, optional

If True, the pymoo algorithm prints progress output during the run. Default is True.

live_progressbool, optional

If True, open the progress figure (the same plot as EvolutionPlotter.plot_progress) in a separate viewer process that watches the run’s JSON log and refreshes it about once per second. Because it is its own process, the window stays responsive while a generation is computing (it no longer freezes / ghosts as “Not Responding”). The window keeps showing the final state after the run finishes and is closed by the user – run() neither waits on it nor tears it down. Repeated run() calls in one script each open their own window. Requires the optional matplotlib dependency (pip install nextnano_optimizers[plot]). Default is False.

The live figure’s curves and labels come from setup_plotting() (plotter kwargs such as progress_series, param_names, fitness_names, fitness_limits). Call it before run to configure them; without it the viewer uses the defaults.

Returns:
pymoo.core.result.Result:

The result of the optimization

Raises:
ValueError

If keep_output is not one of “all”, “pareto”, or “champion”. If gen is not >= 1.

set_size(size: int) None#

Sets the population size for the evolution.

Raises:
ValueError

If size is not an int >= 1.

setup_plotting(**plotter_kwargs) None#

Configure plotting once, for both the live viewer and the post-run figures.

Stores the configuration on the evolution. The live viewer opened by run(live_progress=True) and the figures produced by plot_all() both draw with it, so neither takes plotting arguments of its own.

Each call replaces the stored configuration outright; it does not merge with a previous call. Any argument you omit reverts to its default, so repeat the ones you still want. For a one-off variation, drive nextnano_optimizers.plotting.EvolutionPlotter directly instead.

The kwargs are validated here, against nextnano_optimizers.plotting.EvolutionPlotter.validate_config() and this run’s dimensions, so a typo or a wrong-length name list fails at this call rather than later when the figures are drawn (or silently, in the detached live viewer).

Parameters:
**plotter_kwargs

Forwarded to nextnano_optimizers.plotting.EvolutionPlotter – e.g. progress_series (which per-panel curves the progress figure draws; pass "all" for every curve), param_names, fitness_names, exclude_failed, fitness_limits.

Raises:
TypeError

If a keyword argument is not an EvolutionPlotter kwarg.

ValueError

If progress_series names an unknown curve, or param_names / fitness_names does not match the number of input variables / objectives.

update_champion(solutions, fits, gen) None#

Updates the champion solution and fit if a better one is found. Champion is the solution with the lowest norm of the fit.

update_last_gen_pareto_front(solutions, fits, gen) None#

Computes the Pareto front restricted to a single generation’s evaluated solutions and stores it separately from the accumulated Pareto front. Used for the final generation so its non-dominated set is available independently of the run-wide front.

update_pareto_front(solutions, fits, gen) None#

Updates the Pareto front with new solutions and fits. Re-evaluated candidates (identical solution AND identical fit) are dropped, keeping the first occurrence, so repeated individuals do not accumulate across generations. Distinct solutions that happen to share the same fit are all kept: they are different designs with equal performance.

validate_initial_guess(initial_guess) int#

Validates the initial guess provided by the user.

Returns:
int

The number of valid initial guess solutions. Returns 0 if no initial guess is provided or if the initial guess is None.

Raises:
TypeError

If the initial guess or its solutions are not sized iterables, or if a solution component is not a number.

ValueError

If the length of the initial guess is not between 1 and population size, if a solution does not match the number of input variables, or if a solution component is out of bounds.