Base optimizer (evopt.base_optimizer)

Warning

This module is part of the internal implementation of evopt and is not intended for direct use by end users. Its API may change without notice.

black-box optimization base implementation.

This module provides an abstract base class for implementing black-box optimization algorithms. It handles common functionality such as parameter handling, parallel evaluation of solutions, logging, and statistics calculation.

The BaseOptimizer class is designed to be extended by specific black-box optimization algorithm implementations like CMA-ES, DE, PSO, etc. At the current stage only CMA-ES is implemented.

Example

Creating a custom optimizer by extending BaseOptimizer:

>>> class MyOptimizer(BaseOptimizer):
...     def setup_opt(self, epoch=None):
...         # Initialize optimization algorithm
...         pass
...
...     def optimize(self):
...         # Run optimization algorithm
...         while not self.check_termination():
...             solutions = self.generate_solutions()
...             errors = self.process_batch(solutions)
...             self.update_algorithm(solutions, errors)
...         return self.best_solution
...
...     def check_termination(self):
...         # Check if optimization should stop
...         return self.current_epoch >= self.n_epochs
class evopt.base_optimizer.BaseOptimizer(parameters: dict, evaluator, batch_size: int, directory_manager: DirectoryManager, sigma_threshold: float = 0.1, rand_seed: int = 1, start_epoch: int | None = None, verbose: bool = True, n_epochs: int | None = None, target_dict: dict | None = None, max_workers: int = 1, cores_per_worker: int = 1, **kwargs)[source]

Bases: ABC

Abstract base class for evolutionary optimization algorithms.

This class provides the foundation for implementing evolutionary optimization algorithms, handling common tasks such as parameter management, parallel solution evaluation, statistics tracking, and result logging. It abstracts away the infrastructure details, allowing subclasses to focus on algorithm-specific implementation.

The class maintains a history of optimization metrics across epochs, supports both serial and parallel evaluation of solutions, and provides detailed logging of optimization progress.

parameters

Parameter definitions with (min, max) bounds.

Type:

dict

target_dict

Target values to optimize towards.

Type:

dict, optional

evaluator

Function that evaluates parameter sets.

Type:

callable

n_epochs

Maximum number of epochs to run.

Type:

int, optional

batch_size

Number of solutions to evaluate per epoch.

Type:

int

dir_manager

Manages output directories and files.

Type:

DirectoryManager

sigma_threshold

Convergence threshold for normalized sigmas.

Type:

float

rand_seed

Random seed for reproducibility.

Type:

int

verbose

Whether to print detailed progress information.

Type:

bool

current_epoch

The current optimization epoch.

Type:

int

max_workers

Maximum number of parallel workers.

Type:

int

Note

This is an abstract class and cannot be instantiated directly. Subclasses must implement the setup_opt, optimize, and check_termination methods.

abstract check_termination() bool[source]

Check if optimization should terminate.

Determines whether the optimization process should stop based on convergence criteria or maximum epochs.

Returns:

True if optimization should stop, False otherwise.

Return type:

bool

Note

This is an abstract method that must be implemented by subclasses.

Example

>>> class MyOptimizer(BaseOptimizer):
...     def check_termination(self):
...         if self.current_epoch >= self.n_epochs:
...             return True
...         return all(ns < self.sigma_threshold
...                   for ns in self.norm_sigmas.values())
cleanup()[source]

Explicitly clean up resources used by the optimizer.

Releases all resources used by the optimizer, including any process pools. This method should be called when the optimizer is no longer needed.

Example

>>> # After optimization is complete
>>> optimizer.cleanup()
property get_init_params: list

Generate initial parameters within normalized bounds.

Creates random initial parameter values uniformly distributed within the normalized parameter bounds.

Returns:

List of initial normalized parameter values.

Return type:

list

Example

>>> np.random.seed(1)  # For reproducible example
>>> optimizer = MyOptimizer(parameters={'x': (0, 10), 'y': (-5, 5)}, ...)
>>> optimizer.get_init_params
[2.17, -0.45]  # Random values within normalized bounds
property get_init_sigmas: ndarray

Calculate initial standard deviations based on parameter bounds.

Computes initial sigma values as 1/4 of the range between min and max for each parameter, providing a reasonable starting point for exploration.

Returns:

Array of initial standard deviation values for each parameter.

Return type:

np.ndarray

Example

>>> optimizer = MyOptimizer(parameters={'x': (0, 10), 'y': (-5, 5)}, ...)
>>> optimizer.get_init_sigmas
array([2.5, 2.5])  # 1/4 of parameter ranges
property get_norm_bounds: list

Calculate normalized parameter bounds.

Normalizes the parameter bounds by dividing by the initial sigma values. This creates a unified scale for parameters with different ranges.

Returns:

List of (normalized_min, normalized_max) tuples for each parameter.

Return type:

list

Example

>>> optimizer = MyOptimizer(parameters={'x': (0, 10), 'y': (-5, 5)}, ...)
>>> optimizer.get_norm_bounds
[(0.0, 4.0), (-2.0, 2.0)]  # Bounds divided by sigmas
property mean_error: list

Get the historical mean error values.

Returns:

Copy of the mean error history list.

Return type:

list

Example

>>> optimizer.mean_error
[10.5, 8.2, 6.7, 4.3, 2.1]  # Error trajectory across epochs
property mean_params: dict

Get the historical mean parameter values.

Returns:

Dictionary with parameter names as keys and lists of historical

mean values as values. Each value is a copy of the internal list.

Return type:

dict

Example

>>> optimizer.mean_params
{'x': [5.2, 5.1, 5.05, 5.01], 'y': [0.5, 0.3, 0.1, 0.05]}
property mean_targets: dict

Get the historical mean target values.

Returns:

Dictionary with target names as keys and lists of historical

mean observed values as values. Each value is a copy of the internal list.

Return type:

dict

Example

>>> optimizer.mean_targets
{'stress': [250, 240, 230, 225], 'weight': [120, 118, 117, 116.5]}
property norm_sigmas: dict

Get the historical normalized sigma values.

Normalized sigmas represent the standard deviation divided by initial sigma, serving as a measure of convergence.

Returns:

Dictionary with parameter names as keys and lists of historical

normalized sigma values as values. Each value is a copy of the internal list.

Return type:

dict

Example

>>> optimizer.norm_sigmas
{'x': [1.0, 0.7, 0.5, 0.2], 'y': [1.0, 0.8, 0.5, 0.3]}
abstract optimize()[source]

Run the optimization process.

Executes the optimization algorithm until termination criteria are met.

Returns:

A representation of the best solution found.

Note

This is an abstract method that must be implemented by subclasses.

Example

>>> class MyCmaesOptimizer(BaseOptimizer):
...     def optimize(self):
...         self.setup_opt(epoch=self.start_epoch)
...         while not self.check_termination():
...             solutions = self.es.ask()
...             errors = self.process_batch(solutions)
...             self.es.tell(solutions, errors)
...             self.current_epoch += 1
...         return {p: v[-1] for p, v in self.mean_params.items()}
print_epoch(mean_error: float, sigma_error: float, mean_params: ndarray, sigma_params: ndarray, norm_sigmas: ndarray) None[source]

Print statistics for the current epoch.

Displays aggregated statistics for the current epoch, including mean error, error standard deviation, and parameter statistics. Only prints if verbose mode is enabled.

Parameters:
  • mean_error – Mean error across all solutions in the epoch.

  • sigma_error – Standard deviation of errors in the epoch.

  • mean_params – Array of mean parameter values.

  • sigma_params – Array of parameter standard deviations.

  • norm_sigmas – Array of normalized sigma values.

print_solution(sol_id: int, params: ndarray, error: float) None[source]

Print information about a single evaluated solution.

Displays details of a solution evaluation, including the solution ID, parameter values, and error. Only prints if verbose mode is enabled.

Parameters:
  • sol_id – Solution identifier within the current epoch.

  • params – Array of parameter values used for this solution.

  • error – Error value for the solution (lower is better).

process_batch(solutions: list) list[source]

Process a batch of solutions in parallel or serial mode.

This method handles the evaluation of multiple parameter sets (solutions) using the provided evaluator function. It supports both serial and parallel processing based on max_workers setting.

Parameters:

solutions – List of parameter value arrays to evaluate.

Returns:

List of error values for each solution.

Return type:

list

Raises:

Exception – If solution evaluation fails.

Example

>>> # Generate solutions with some algorithm
>>> solutions = [[1.2, 3.4], [2.3, 1.4], [3.1, 2.8]]
>>> errors = optimizer.process_batch(solutions)
>>> print(errors)
[12.5, 8.7, 15.2]  # Error value for each solution
rescale_params(params: ndarray) ndarray[source]

Rescale normalized parameters to their original scale.

Converts normalized parameter values (used internally by optimization algorithms) back to their original scale by multiplying by the initial sigma values.

Parameters:

params – Normalized parameter values.

Returns:

Parameter values in their original scale.

Return type:

np.ndarray

Example

>>> normalized_params = np.array([1.0, -0.5])
>>> optimizer.rescale_params(normalized_params)
array([2.5, -1.25])  # Assuming init_sigmas = [2.5, 2.5]
abstract setup_opt(epoch: int | None = None)[source]

Set up the optimization algorithm.

Initializes or reinitializes the optimization algorithm, potentially starting from a specific epoch (for resuming interrupted optimizations).

Parameters:

epoch – Starting epoch number. If None, starts from 0.

Note

This is an abstract method that must be implemented by subclasses.

Example

>>> class MyCmaesOptimizer(BaseOptimizer):
...     def setup_opt(self, epoch=None):
...         self.es = cma.CMAEvolutionStrategy(
...             self.init_params,
...             0.5,
...             {'seed': self.rand_seed}
...         )
...         if epoch is not None:
...             # Load state from checkpoint if available
...             self.es = self.dir_manager.load_checkpoint(epoch)
...             self.current_epoch = epoch
property sigma_error: list

Get the historical error standard deviations.

Returns:

Copy of the error standard deviation history list.

Return type:

list

Example

>>> optimizer.sigma_error
[5.2, 4.3, 3.1, 2.5, 1.2]  # Error variance trajectory
property sigma_params: dict

Get the historical parameter standard deviations.

Returns:

Dictionary with parameter names as keys and lists of historical

standard deviation values as values. Each value is a copy of the internal list.

Return type:

dict

Example

>>> optimizer.sigma_params
{'x': [2.0, 1.5, 1.0, 0.5], 'y': [1.0, 0.7, 0.4, 0.2]}
property sigma_targets: dict

Get the historical target standard deviations.

Returns:

Dictionary with target names as keys and lists of historical

standard deviation values as values. Each value is a copy of the internal list.

Return type:

dict

Example

>>> optimizer.sigma_targets
{'stress': [25, 20, 15, 10], 'weight': [8, 6, 4, 3]}