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.
|
The evolution class to run the evolutionary optimization with pymoo algorithms. |
|
Runs the evolution with the specified parameters. |
|
Loads the algorithm for the evolution. |
|
Sets the population size for the evolution. |
|
Configure plotting once, for both the live viewer and the post-run figures. |
|
Visualize this run's results (progress, Pareto front, input space). |
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_fitsAccumulated 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_fitsRestricted 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:
objectThe 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.
Loads the JSON logger data.
load_algorithm(algorithm)Loads the algorithm for the evolution.
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 optionalmatplotlibdependency (pip install nextnano_optimizers[plot]).The figures’ curves and labels come from
setup_plotting()(plotter kwargs such asprogress_series,param_names,fitness_names,fitness_limits). Call it beforeplot_allto configure them; without it the defaults are used. For per-call variation or offline use, drivenextnano_optimizers.plotting.EvolutionPlotterdirectly (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. IfNone(default), figures are not saved.- showbool, optional
Call
plt.show()at the end. DefaultFalse(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. Repeatedrun()calls in one script each open their own window. Requires the optionalmatplotlibdependency (pip install nextnano_optimizers[plot]). Default is False.The live figure’s curves and labels come from
setup_plotting()(plotter kwargs such asprogress_series,param_names,fitness_names,fitness_limits). Call it beforerunto 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 byplot_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.EvolutionPlotterdirectly 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
EvolutionPlotterkwarg.- ValueError
If
progress_seriesnames an unknown curve, orparam_names/fitness_namesdoes 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.