class documentation

class Sofomore(interfaces.OOOptimizer):

Constructor: Sofomore(list_of_solvers_instances, reference_point, opts)

View In Hierarchy

Sofomore framework for multiobjective optimization, with the ask-and-tell interface.

See: [Toure, Cheikh, et al. "Uncrowded Hypervolume Improvement: COMO-CMA-ES and the Sofomore framework." GECCO'19-Genetic and Evolutionary Computation Conference. 2019.].

Calling Sequences

  • moes = Sofomore(list_of_solvers_instances, reference_point, opts)
  • moes = Sofomore(list_of_solvers_instances, reference_point)
  • moes = Sofomore(list_of_solvers_instances, reference_point).optimize(objective_fcts)

Arguments

list_of_solvers_instances

list of instances of single-objective solvers. It's generally created via a factory function. Let's take the example of a factory function that returns cma-es instances, called get_cmas. Then: list_of_solvers_instances = get_cmas(11 * [x0], sigma0) creates a list of 11 cma-es instances of initial mean x0 and initial step-size sigma0. A single-objective solver instance must have the following attributes and methods:

  • incumbent: an attribute that gives an estimate of the single-objective solver.
  • objective_values: an attribute that stores the objective values of the incumbent.
  • stop: a method returning a dictionary representing the termination status.
  • ask: generates new candidate solutions.
  • tell: passes the objective values and updates the states of the single-objective solvers.
opts

opts, a dictionary with optional settings related to the Sofomore framework. It mainly contains the following keys:

  • 'archive': default value is True. If its value is True, tracks the non-dominated points that dominate the reference point, among the points evaluated so far during the optimization. The archive will not interfere with the optimization process.
  • 'indicator_front': default value is None. Used via self.indicator_front = IndicatorFront(self.opts['indicator_front']) within the __init__ method of Sofomore. See the class IndicatorFront for more details.
  • 'restart': used to define in __init__ the attribute restart via self.restart = self.opts['restart'].
  • 'update_order': default value is a function that takes a natural integer as input and returns a random number between 0 and 1. It is used as a key value in: sorted(..., key = ...), and guides the order in which the kernels will be updated during the optimization.
reference_point
reference point of the multiobjective optimization. Its default value is None but should be set by the user beforehand to guarantee an optimal p-distribution convergence of the Hypervolume indicator of p points towards the Pareto set/front. It can be changed dynamically by the user if needed.

Main interface / usage

The interface is inherited from the generic OOOptimizer class, which is the same interface used by the pycma module. An object instance is generated as following:

>>> import cma, comocma
>>> import numpy as np
>>> reference_point = [11, 11]
>>> num_kernels = 11 # the number of points we seek to have on the Pareto front
>>> dimension = 5 # the dimension of the search space
>>> x0 = dimension * [0] # an initial mean for cma
>>> sigma0 = 0.2 # initial step-size for cma
>>> list_of_solvers_instances = comocma.get_cmas(num_kernels * [x0], sigma0, {'verbose':-9})
>>> # `comocma.get_cmas` is a factory function that returns `num_kernels` cma-es instances
>>> moes = comocma.Sofomore(list_of_solvers_instances,
...                      reference_point) #instantiation of our MO optimizer

The least verbose interface is via the optimize method:

>>> fitness = comocma.FitFun(cma.ff.sphere, lambda x: cma.ff.sphere(x-1)) # a callable bi-objective function
>>> moes.optimize(fitness, iterations=23) # doctest:+ELLIPSIS
Iterat #Fevals   Hypervolume   axis ratios   sigmas   min&max stds***
>>> list_of_solvers_instances = comocma.get_cmas(num_kernels * [x0], sigma0, {'verbose':-9})
>>> reference_point = [11, 11, 11]
>>> moes = comocma.Sofomore(list_of_solvers_instances,
...                      reference_point) #instantiation of our MO optimizer
>>> fitness = comocma.FitFun(cma.ff.sphere, lambda x: cma.ff.sphere(x-1),
...                       lambda x: cma.ff.sphere(x+1)) # a callable 3-objective function
>>> moes.optimize(fitness, iterations=4) # doctest:+ELLIPSIS
Iterat #Fevals   Hypervolume   axis ratios   sigmas   min&max stds***

More verbosely, the optimization of the callable multiobjective function fitness is done via the ask-and-tell interface:

>>> list_of_solvers_instances = comocma.get_cmas(num_kernels * [x0], sigma0, {'verbose':-9})
>>> moes = comocma.Sofomore(list_of_solvers_instances, reference_point)
>>> while not moes.stop() and moes.countiter < 30:
...     solutions = moes.ask() # `ask` delivers new candidate solutions
...     objective_values = [fitness(x) for x in solutions]
...     moes.tell(solutions, objective_values)
...  # `tell` updates the MO instance by passing the respective function values.
...     # moes.logger.add()
...     moes.disp() # doctest:+ELLIPSIS
***

One iteration of the optimize interface is equivalent to one step in the loop of the ask-and-tell interface. But for the latter, the prototyper has more controls to analyse and guide the optimization, due to the access of the instance between the ask and the tell calls.

Attributes and Properties

  • archive: list of non-dominated points among all points evaluated so far, which dominate the reference point.
  • countevals: the number of function evaluations.
  • countiter: the number of iterations.
  • dimension: is the dimension of the search space.
  • indicator_front: the indicator used as a changing fitness inside an iteration of Sofomore. By default it is the UHVI of all the objective values of the kernels' incumbents, except the kernel being optimized. See the class IndicatorFront for more details.
  • isarchive: a boolean accessible via self.opts['archive']. If True, we keep track of the archive, otherwise we don't.
  • kernels: initialized with list_of_solvers_instances, and is the list of single-objective solvers.
  • key_sort_indices: default value is self.opts['update_order']. It is a function used as a key to sort some kernels' indices, in order to select the first indices during the call of the ask method.
  • logger: an attribute that accounts for the way we log the Sofomore data during an optimization. self.logger is an instance of the SofomoreDataLogger class.
  • NDA: it is the non-dominated archiving method used within Sofomore. By default the class BiobjectiveNondominatedSortedList is used in two objectives and the class (non tested yet) NonDominatedList is used for three or more objectives.
  • opts: passed options.
  • offspring: list of tuples of the index of a kernel with its corresponding candidate solutions, that we generally get with the cma's ask method.
  • pareto_front_cut: list of non-dominated points among the incumbents of self.kernels, which dominate the reference point.
  • pareto_set_cut: preimage of pareto_front_cut.
  • reference_point: the current reference point.
  • restart: accessible via self.opts['restart']. If not None, a callback function that adds kernels after a kernel of the running Sofomore instance stops. Its default value is None, meaning that we do not do any restart.
  • _active_indices: the indices of the kernels that have not stopped yet.
  • _last_stopped_kernel_id: the index of the last stopped kernel.
  • _told_indices: the kernels' indices for which we will evaluate the objective values of their incumbents, before the next call of the tell method. And before the first call of tell, they are the indices of all the initial kernels (i.e. range(len(self))). And before another call of tell, they are the indices of the kernels from which we have sampled new candidate solutions during the penultimate ask method. Note that we should call the ask method before any call of the tell method.
Method __getitem__ make self subscriptable.
Method __init__ Initialization:
Method __iter__ make self iterable.
Method __len__ return length of the Sofomore instance by calling len(.).
Method activate activate kernel when it was inactivated beforehand. Otherwise it remains quiet.
Method add add kernels of type list to self.kernels. Generally, kernels are created from a factory function. If kernels is of length 1, the brackets can be omitted.
Method ask get the kernels' incumbents to be evaluated and sample new candidate solutions from number_to_ask kernels. The sampling is done by calling the ask method of the cma.CMAEvolutionStrategy class. The indices of the considered kernels' incumbents are given by the ...
Method disp copy-pasted from cma.evolution_strategy. print current state variables in a single-line. copy-pasted from cma.evolution_strategy module
Method disp_annotation copy-pasted from cma.evolution_strategy. print annotation line for disp ()
Method inactivate inactivate kernel, assuming that it's an element of self.kernels, or an index in range(len(self)). When inactivated, kernel is no longer updated, it is ignored. However we do not remove it from self.kernels...
Method remove remove elements of the kernels (type list) that belong to self.kernels, and update the _active_indices attribute. If kernels is of length 1, the brackets can be omitted.
Method sorted return a reversed sorted list of kernels.
Method stop return a nonempty dictionary when all kernels stop, containing all the termination status. Therefore it's solely ... on the kernels' stop method, which also return dictionaries. Return ------ For example with 5 kernels, stop should return either None, or a ...
Method tell pass objective function values to update the state variables of some kernels, self._told_indices and eventually self.archive. Arguments ---------
Instance Variable archive Undocumented
Instance Variable best_hypervolume_pareto_front Undocumented
Instance Variable count_kernel_updates Undocumented
Instance Variable countevals Undocumented
Instance Variable countiter Undocumented
Instance Variable dimension Undocumented
Instance Variable epsilon_hypervolume_pareto_front Undocumented
Instance Variable has_been_called Undocumented
Instance Variable indicator_front Undocumented
Instance Variable isarchive Undocumented
Instance Variable kernels Undocumented
Instance Variable key_sort_indices Undocumented
Instance Variable logger Undocumented
Instance Variable NDA Undocumented
Instance Variable offspring Undocumented
Instance Variable opts Undocumented
Instance Variable reference_point Undocumented
Instance Variable restart Undocumented
Property max_max_stds No summary
Property median_stds No summary
Property pareto_front_cut return the non-dominated solutions dominating the reference point, among the kernels' objective values. It's the image of self.pareto_set_cut.
Property pareto_front_uncut provisorial, return _all_ non-dominated objective values irrespectively
Property pareto_set_cut return the non-dominated solutions whose images dominate the reference point, among the kernels' incumbents. It's the pre-image of self.pareto_front_cut.
Property termination_status return a dictionary of the current termination states of the kernels.
Method _indices_to_ask No summary
Method _UHVI_indicator return indicator function(!) for uncrowded hypervolume improvement for kernel.
Method _UHVI_indicator_archive return archive for uncrowded hypervolume improvement indicator for kernel.
Method _UHVIs return uncrowded HV contributions of all kernels,
Instance Variable _active_indices Undocumented
Instance Variable _last_stopped_kernel_id Undocumented
Instance Variable _ratio_nondom_offspring_incumbent Undocumented
Instance Variable _remaining_indices_to_ask Undocumented
Instance Variable _told_indices Undocumented
def __getitem__(self, i):

make self subscriptable.

def __init__(self, list_of_solvers_instances, reference_point=None, opts=None):

Initialization:

  • list_of_solvers_instances is a list of single-objective solvers' instances
  • The reference_point is set by the user during the initialization.
  • opts is a dictionary updating the values of 'indicator_front', 'archive', 'restart', 'update_order'; that respond respectfully to the changing fitness we will choose within an iteration of Sofomore, whether or not keeping an archive, how to do the restart in case of any restart, and the order of update of the kernels. It also has the keys 'verb_filename', 'verb_log' and 'verb_disp'; that respectfully indicate the name of the filename containing the Sofomore data, the logging of the Sofomore data every 'verb_log' iterations and the display of the data via self.disp() every 'verb_disp' iterations.
def __iter__(self):

make self iterable.

def __len__(self):

return length of the Sofomore instance by calling len(.).

The length is the number of (active and inactive) kernels and hence consistent with subscription like [moes[i] for i in range(len(moes)) if i in moes._active_indices].

def activate(self, kernel):

activate kernel when it was inactivated beforehand. Otherwise it remains quiet.

We expect the kernel's stop method in interest to look like: kernel.stop() = {'callback': ['kernel turned off']}

def add(self, kernels):

add kernels of type list to self.kernels. Generally, kernels are created from a factory function. If kernels is of length 1, the brackets can be omitted.

def ask(self, number_to_ask=1):

get the kernels' incumbents to be evaluated and sample new candidate solutions from number_to_ask kernels. The sampling is done by calling the ask method of the cma.CMAEvolutionStrategy class. The indices of the considered kernels' incumbents are given by the _told_indices attribute.

To get the number_to_ask kernels, we use the function self.key_sort_indices as a key to sort self._remaining_indices_to_ask (which is the list of kernels' indices wherein we choose the first number_to_ask elements. And if number_to_ask is larger than len(self._remaining_indices_to_ask), we select the list self._remaining_indices_to_ask extended with the first number_to_ask - len(self._remaining_indices_to_ask) elements of range(len(self)), sorted with self.key_sort_indices as key.

Arguments

number_to_ask is the number of kernels for which we sample solutions from, it is of TYPE int or str and is smaller or equal to len(self). The unique case where it is a str instance is when it is equal to "all", meaning that all the kernels are asked. That case is mainly used on parallel mode by distributing the evaluations of the kernels at the same time before calling the tell method. The opposite is when number_to_ask is equal to 1, which is the exact COMO-CMA-ES algorithm, where the evaluations are done sequentially by updating the kernels one by one.

Return

The list of the kernels' incumbents to be evaluated, extended with a list of N-dimensional (N is the dimension of the search space) candidate solutions generated from number_to_ask kernels to be evaluated.

See Also
the ask method from the class cma.CMAEvolutionStrategy, in evolution_strategy.py from the cma module.
def disp(self, modulo=None):

copy-pasted from cma.evolution_strategy. print current state variables in a single-line. copy-pasted from cma.evolution_strategy module

Prints only if iteration_counter % modulo == 0.

See Also
disp_annotation.
def disp_annotation(self):

copy-pasted from cma.evolution_strategy. print annotation line for disp ()

def inactivate(self, kernel):

inactivate kernel, assuming that it's an element of self.kernels, or an index in range(len(self)). When inactivated, kernel is no longer updated, it is ignored. However we do not remove it from self.kernels, meaning that kernel might still play a role, due to its eventual trace in self.pareto_front_cut.

def remove(self, kernels):

remove elements of the kernels (type list) that belong to self.kernels, and update the _active_indices attribute. If kernels is of length 1, the brackets can be omitted.

def sorted(self, key=None, reverse=True, **kwargs):

return a reversed sorted list of kernels.

By default kernels are reversed sorted by HV contribution or UHVI (which we aim to maximize) in the set of kernels. Exact copies have zero or negative UHVI value.

>>> import comocma, cma
>>> list_of_solvers_instances = comocma.get_cmas(13 * [5 * [1]], 0.7, {'verbose':-9})
>>> fitness = comocma.FitFun(cma.ff.sphere, lambda x: cma.ff.sphere(x-1))
>>> moes = comocma.Sofomore(list_of_solvers_instances, [11, 11])
>>> moes.optimize(fitness, iterations=31) # doctest:+ELLIPSIS
Iterat #Fevals   Hypervolume   axis ratios   sigmas   min&max stds***
>>> moes.sorted(key = lambda k: moes.archive.contributing_hypervolume(
...                          k.objective_values)) # doctest:+ELLIPSIS
[<comocma.como.CmaKernel object at***

sorts w.r.t. archive contribution (clones may get positive contribution).

def stop(self):

return a nonempty dictionary when all kernels stop, containing all the termination status. Therefore it's solely ... on the kernels' stop method, which also return dictionaries. Return ------ For example with 5 kernels, stop should return either None, or a dict of the form:

{0: dict0,
 1: dict1,
 2: dict2,
 3: dict2,
 4: dict4},

where each index i is a key which value is the dict instance self.kernels[i].stop()

def tell(self, solutions, objective_values):

pass objective function values to update the state variables of some kernels, self._told_indices and eventually self.archive. Arguments ---------

solutions
list or array of points (of type numpy.ndarray), most presumably before delivered by the ask() method.
objective_values
list of multiobjective function values (of type list) corresponding to the respective points in solutions.
constraints_values
list of list of constraint values: each element is a list containing the values of one constraint function, that are obtained by evaluation on solutions.

Details

To update a kernel, tell() applies the kernel's tell method to the kernel's corresponding candidate solutions (offspring) along with the "changing" fitness - self.indicator_front.hypervolume_improvement.

See Also
archive =

Undocumented

best_hypervolume_pareto_front =

Undocumented

count_kernel_updates: int =

Undocumented

countevals: int =

Undocumented

countiter: int =

Undocumented

dimension =

Undocumented

epsilon_hypervolume_pareto_front =

Undocumented

has_been_called: bool =

Undocumented

indicator_front =

Undocumented

isarchive =

Undocumented

kernels =

Undocumented

key_sort_indices =

Undocumented

logger =

Undocumented

NDA =

Undocumented

offspring: list =

Undocumented

opts =

Undocumented

reference_point =

Undocumented

restart =

Undocumented

@property
max_max_stds =
@property
median_stds =
@property
pareto_front_cut =

return the non-dominated solutions dominating the reference point, among the kernels' objective values. It's the image of self.pareto_set_cut.

@property
pareto_front_uncut =

provisorial, return _all_ non-dominated objective values irrespectively

of the current reference point. Only points whose contributing HV does not depend on the current reference point still have the same cHV in the resulting list. HV improvements in general may change.

@property
pareto_set_cut =

return the non-dominated solutions whose images dominate the reference point, among the kernels' incumbents. It's the pre-image of self.pareto_front_cut.

@property
termination_status =

return a dictionary of the current termination states of the kernels.

def _indices_to_ask(self, number_to_ask):
def _UHVI_indicator(self, kernel):

return indicator function(!) for uncrowded hypervolume improvement for kernel.

>>> import comocma, cma
>>> list_of_solvers_instances = comocma.get_cmas(13 * [5 * [1]], 0.7, {'verbose':-9})
>>> fitness = comocma.FitFun(cma.ff.sphere, lambda x: cma.ff.sphere(x-1))
>>> moes = comocma.Sofomore(list_of_solvers_instances, [11, 11])
>>> moes.optimize(fitness, iterations=37) # doctest:+ELLIPSIS
Iterat #Fevals   Hypervolume   axis ratios   sigmas   min&max stds***
>>> moes._UHVI_indicator(moes[1])(moes[2].objective_values) # doctest:+ELLIPSIS
***
>>> moes._UHVI_indicator(1)(moes[2].objective_values) # doctest:+ELLIPSIS
***

both return the UHVI indicator function for kernel 1 and evaluate kernel 2 on it:

>>> [[moes._UHVI_indicator(k)(k.objective_values)] for k in moes] # doctest:+ELLIPSIS
***

is the list of UHVI values for all kernels where kernels occupying the very same objective value have indicator value zero.

def _UHVI_indicator_archive(self, kernel):

return archive for uncrowded hypervolume improvement indicator for kernel.

kernel can also be the respective index in self.

def _UHVIs(self, kernel=None, none_value=-np.inf):

return uncrowded HV contributions of all kernels,

more numerically efficient than _UHVI_indicator does.

_active_indices =

Undocumented

_last_stopped_kernel_id =

Undocumented

_ratio_nondom_offspring_incumbent =

Undocumented

_remaining_indices_to_ask =

Undocumented

_told_indices: list =

Undocumented