Evolution#

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

Bases: PlottingFacadeMixin

Optimize the device design using evolutionary algorithms.

Uses algorithms from the pymoo library to optimize objectives defined by metric, evaluating candidates through nextnano simulations. Supports both single-objective and multi-objective optimization.

Parameters:
nextnanoioIO

Defines the input file path, input variables under optimization and output files of the simulations.

metricMetric

Defines how the objective value is extracted from the simulation outputs.

boundstuple of list

Lower and upper bounds for the optimization variables, respectively. Each list must contain one value per optimization variable, in the same order as variable_names in nextnanoio.

algorithmstr, default=”default”

Evolutionary algorithm to use. If "default", the algorithm is selected automatically based on the number of objectives.

sizeint, default=20

Size of the population for the evolution.

parallel_limitint, default=1

Maximum number of parallel simulations to run.

output_dirstr, default=None

Directory where optimization results are saved. If None, defaults to the outputdirectory specified in nextnanopy config.

Attributes:
algorithmtype[pymoo.core.algorithm.Algorithm]

Pymoo algorithm class used for the evolution.

pareto_solutionslist

Input variable values of the nondominated candidates accumulated over all generations.

pareto_fitslist

Objective values corresponding to pareto_solutions.

pareto_indexeslist

(generation, index) pairs identifying the candidates in pareto_solutions.

last_gen_pareto_solutionslist

Input variable values of the nondominated candidates in the final generation.

last_gen_pareto_fitslist

Objective values corresponding to last_gen_pareto_solutions.

last_gen_pareto_indexeslist

(generation, index) pairs identifying the candidates in last_gen_pareto_solutions.

champion_xnumpy.ndarray

Input variable values of the best candidate evaluated during optimization.

champion_fnumpy.ndarray

Objective values corresponding to champion_x.

champion_indextuple

(generation, index) pair identifying the champion candidate.

norm_functionCallable

Function that converts objective values into a scalar score used to select the champion candidate. Default is numpy.linalg.norm.

Raises:
ValueError

If bounds is invalid. If size is not an int >= 1. If algorithm is a str naming an algorithm that is not accepted.

NotImplementedError

If algorithm is not a str.

See also

IO

Configure simulation inputs and outputs.

Metric

Define how objective values are calculated.

Examples

Optimize the quantum well width to match the target transition energy using a single-objective evolutionary algorithm.

import numpy as np
from nextnano_optimizers.io import IO
from nextnano_optimizers.metric import Metric
from nextnano_optimizers.optimizer import Evolution

io = IO(
    input_file_path="device.nnp",
    variable_names=["well_width"],
    target_output_paths=[("bias_00000", "Quantum", "energy_spectrum.dat")],
)

def extract_target(datafiles):
    energies = datafiles[0].variables["Energy"].value
    transition_energy = energies[1] - energies[0]
    return np.array([(transition_energy - 0.25) ** 2]) # assume the target of 0.25 eV

metric = Metric(
    input_shape=(1,),
    output_shape=(1,),
    extraction_function=extract_target,
)

optimizer = Evolution(
    nextnanoio=io,
    metric=metric,
    bounds=([2.0], [12.0]), # optimize between 2 and 12 nm
)
optimizer.run(gen=20)

print(optimizer.champion_x)  # Optimized well width
print(optimizer.champion_f)  # Objective value

For multi-objective optimization, consider minimizing the first transition energy while maximizing the second, by adjusting the well and barrier widths.

io = IO(
    input_file_path="device.nnp",
    variable_names=["well_width", "barrier_width"],
    target_output_paths=[("bias_00000", "Quantum", "energy_spectrum.dat")],
)

def extract_targets(datafiles):
    energies = datafiles[0].variables["Energy"].value
    first_transition = energies[1] - energies[0]
    second_transition = energies[2] - energies[1]

    return np.array([first_transition, -second_transition]) # minus for second_transition to maximize it

metric = Metric(
    input_shape=(1,),
    output_shape=(2,),
    extraction_function=extract_targets,
)

optimizer = Evolution(
    nextnanoio=io,
    metric=metric,
    bounds=([2.0, 1.0], [12.0, 5.0]),
    algorithm="nsga2",
)
optimizer.run(gen=20)

print(optimizer.pareto_solutions)
print(optimizer.pareto_fits)
plot_all(save_dir=None, show=False, fig_format='png')#

Plot the optimization results.

Generates figures showing the optimization progress and available visualizations of the optimization results. The figures produced depend on the number of optimization variables and objectives:

Requires the optional matplotlib dependency (pip install nextnano_optimizers[plot]).

  • Optimization progress: Objective and parameter values as a function of optimization iterations.

  • Parameter space: Evaluated candidates in the design space. Plotted only for problems with multiple parameters.

  • Pareto front: The Pareto-set (the optimal set in the input space) and the Pareto front (the optimal set in the objective space). Plotted only for multi-objective problems.

Parameters:
save_dirstr or bool, default=None

Path to a directory where figures are saved. If None, figures are saved in the figures subdirectory of the optimization output directory. If False, figures are not saved. A directory is created if specified path it does not exist.

showbool, default=False

If True, display the figures.

fig_formatstr, default=”png”

File format for saving figures, such as "png", "pdf", or "svg". Ignored if save_dir=False.

Returns:
list of matplotlib.figure.Figure

Generated figures.

Raises:
RuntimeError

If called before run().

ValueError

If fig_format is not supported by matplotlib.

See also

setup_plotting

Customize the figures generated by this method. Should be called before plot_all.

Examples

Save the figures in the figures subdirectory of this run’s output directory:

>>> optimizer.run(x0=[10.0])
>>> figures = optimizer.plot_all()

Display the figures without saving them:

>>> optimizer.plot_all(save_dir=False, show=True)

Save them as PDFs in a custom directory:

>>> optimizer.plot_all(save_dir="my_figures", fig_format="pdf")

Label the axes before plotting:

>>> optimizer.setup_plotting(param_names=["well width (nm)"])
>>> optimizer.plot_all()
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#

Run the evolutionary optimization.

Parameters:
genint, default=20

Number of generations to run.

intervalint, default=1

Number of generations between updates of the accumulated Pareto solutions. The final generation is always included.

seedint, default=None

Random seed used to initialize the optimization.

initial_guesslist of tuple or numpy.ndarray, default=None

Initial candidates to include in the population. Each candidate contains one value per optimization variable, in the same order as variable_names in nextnanoio.

algorithm_kwargsdict, default=None

Additional keyword arguments passed to the selected pymoo algorithm.

non_execution_penaltynumbers.Number, default=None

Objective value assigned to candidates whose simulations fail.

keep_output{“all”, “pareto”, “champion”}, default=”all”

Simulation outputs to retain after optimization. Keep all outputs, only those corresponding to Pareto solutions, or only the champion’s output, respectively.

cleanup_per_generationbool, default=False

If True, remove unnecessary simulation outputs after each generation rather than at the end of optimization, according to keep_output.

verbosebool, default=True

If True, print optimization progress to the terminal.

live_progressbool, default=False

If True, display a live plot of the optimization progress. The plot updates during optimization and closes when the run finishes. Requires the optional matplotlib dependency. For the progress plot customization, see setup_plotting().

Returns:
pymoo.core.result.Result

Optimization result containing the solutions (X) and corresponding objective values (F).

Raises:
ValueError

If either of the following conditions is met:

  • gen is less than 1.

  • keep_output is not one of "all", "pareto",

    or "champion".

See also

pymoo.core.result.Result()

Result object returned by the optimization.

Examples

Run the optimization for 50 generations:

>>> optimizer.run(gen=50)

Set a random seed for reproducibility:

>>> optimizer.run(gen=50, seed=42)

Specify initial candidates for the population:

>>> optimizer.run(gen=50, initial_guess=[[5.0, 2.0], [8.0, 3.0]])

Run the optimization with live progress visualization:

>>> optimizer.run(gen=50, live_progress=True)

Limit the retained simulation outputs to Pareto solutions:

>>> optimizer.run(gen=50, keep_output="pareto")
setup_plotting(**plotter_kwargs) → None#

Configure the optimization plots.

The configuration applies to both the live progress plot displayed by run(live_progress=True) and the figures generated by plot_all().

Parameters:
**plotter_kwargs

Plotting options described below.

param_nameslist of str, optional

Labels for the optimization variables.

fitness_nameslist of str, optional

Labels for the objectives.

exclude_failedbool, optional

If True, exclude evaluations assigned the non_execution_penalty from the plots (i.e. failed evaluations).

fitness_limitslist or dict, optional

Plotting limits for each objective, specified as (min, max) pairs. If dictionary is used, the keys should match the fitness names, see examples below.

progress_seriesstr or sequence of str, optional

Curves to display in the optimization progress plot. Use "all" to display all available curves. The series applicable to all optimizations: (“best”, “candidates”). The series also applicable to evolution: (“min-max”, “min”, “max”, “median”, “mean”).

series_labelsdict, optional

Custom legend labels for progress_series curves, keyed by curve tag from the progress_series.

series_specsdict, optional

Custom plotting properties for individual curves, keyed by curve name. Each entry can specify kind and/or style. See examples below.

iteration_labelstr, optional

Label used for optimization iterations. Each optimizer defaults to its own word: "generation" for Evolution, "trial" for BayesianOptimization, and "evaluation" for LocalOptimizer.

progress_titlestr, optional

Title of the optimization progress plot.

population_labelstr, optional

Label used for candidates in the final iteration.

Raises:
TypeError

If an unrecognized plotting option is provided.

ValueError

In either of two cases:

  • progress_series contains an unknown curve.

  • The number of entries in param_names or fitness_names does not match the number of optimization variables or objectives.

See also

plot_all

Generate figures using the configured plotting options.

Examples

Customize the labels and title of the optimization plots:

optimizer.setup_plotting(
    param_names=["Well width (nm)", "Barrier width (nm)"],
    fitness_names=["Energy error (eV)"],
    progress_title="Quantum well optimization",
)

optimizer.plot_all()

Customize the progress curves and objective limits for an Evolution optimizer. Plot only the candidates with “Energy error” and “Efficiency” within specified ranges. Change the style of the “best” and “mean” lines.

optimizer.setup_plotting(
    fitness_names=["Energy error", "Efficiency"],
    fitness_limits={
        "Energy error": (0, 0.5),
        "Efficiency": (0, 1),
    },
    progress_series=["best", "mean", "min-max"],
    series_specs={
        "best": {"kind": "line", "style": {"linewidth": 2}},
        "mean": {"kind": "line", "style": {"linestyle": "--"}},
    },
)

optimizer.plot_all()