Source code for zellij.strategies.dba

# @Author: Thomas Firmin <ThomasFirmin>
# @Date:   2022-05-03T15:41:48+02:00
# @Email:  thomas.firmin@univ-lille.fr
# @Project: Zellij
# @Last modified by:   tfirmin
# @Last modified time: 2022-10-03T22:38:12+02:00
# @License: CeCILL-C (http://www.cecill.info/index.fr.html)


from zellij.core.metaheuristic import Metaheuristic

import numpy as np
import copy

import logging

logger = logging.getLogger("zellij.DBA")


[docs]class DBA(Metaheuristic): """DBA Decomposition-Based-Algorithm (DBA) is made of 5 part: * **Geometry** : DBA uses hyper-spheres or hyper-cubes to decompose the search-space into smaller sub-spaces in a fractal way. * **Tree search**: Fractals are stored in a *k-ary rooted tree*. The tree search determines how to move inside this tree. * **Exploration** : To explore a fractal, DBA requires an exploration algorithm. * **Exploitation** : At the final fractal level (e.g. a leaf of the rooted tree) DBA performs an exploitation. * **Scoring method**: To score a fractal, DBA can use the best score found, the median, ... Attributes ---------- search_space : Fractal :ref:`sp` defined as a :ref:`frac`. Contains decision variables of the search space, converted to continuous and constrained to an Euclidean :ref:`frac`. f_calls : int Maximum number of :ref:`lf` calls exploration : {Metaheuristic, list[Metaheuristic]}, default=None Algorithm used to sample inside each subspaces. exploitation : Metaheuristic, default=None Intensification algorithm applied on a subspace at the last level of the partition tree. tree_search : Tree_search Tree search algorithm applied on the partition tree. verbose : boolean, default=True Algorithm verbosity Methods ------- evaluate(hypervolumes) Evaluate a list of fractals using exploration and/or exploitation run(n_process=1) Runs DBA See Also -------- Metaheuristic : Parent class defining what a Metaheuristic is LossFunc : Describes what a loss function is in Zellij Searchspace : Describes what a search space is in Zellij Tree_search : Tree search algorithm to explore and exploit the fractal tree. Fractal : Base class which defines what a fractal is. """ def __init__( self, search_space, f_calls, tree_search, exploration=None, exploitation=None, verbose=True, **kwargs, ): """__init__(search_space, f_calls, tree_search, exploration=None, exploitation=None, verbose=True, **kwargs) Initialize DBA class Parameters ---------- search_space : Fractal :ref:`sp` defined as a :ref:`frac`. Contains decision variables of the search space, converted to continuous and constrained to an EUclidean :ref:`frac`. f_calls : int Maximum number of :ref:`lf` calls exploration : {Metaheuristic, list[Metaheuristic]}, default=None Algorithm used to sample inside each subspaces. exploitation : Metaheuristic, default=None Intensification algorithm applied on a subspace at the last level of the partition tree. tree_search : Tree_search Tree search algorithm applied on the partition tree. verbose : boolean, default=True Algorithm verbosity """ ############## # PARAMETERS # ############## super(DBA, self).__init__(search_space, f_calls, verbose) # Exploration and exploitation function if exploration: if type(exploration) != list: self.exploration = [exploration] else: self.exploration = exploration else: self.exploration = False if exploitation: self.exploitation = exploitation self.exploi_calls = self.exploitation.f_calls else: self.exploitation = False ############# # VARIABLES # ############# # Save f_calls from metaheuristic, to adapt them during DBA. if self.exploration: self.explor_calls = [i.f_calls for i in self.exploration] else: self.explor_calls = None self.tree_search = tree_search # Number of explored hypersphere self.n_h = 0 self.executed = False # Evaluate a list of hypervolumes
[docs] def evaluate(self, hypervolumes, n_process): """evaluate(hypervolumes, n_process) Evaluate a list of fractals using exploration and/or exploitation. Parameters ---------- hypervolumes : list[Fractal] list of hypervolumes to evaluate with exploration and/or exploitation """ # While there are hypervolumes to evaluate do... i = 0 while ( i < len(hypervolumes) and self.search_space.loss.calls < self.f_calls ): # Select parent hypervolume H = hypervolumes[i] H.create_children() i += 1 j = 0 # While there are children do... while ( j < len(H.children) and self.search_space.loss.calls < self.f_calls ): # Select children of parent H child = H.children[j] j += 1 # Count the number of explored hypervolume self.n_h += 1 # Exploration if child.level != self.tree_search.max_depth: if self.exploration: # Compute the first index of the first solution # which will be computed during exploration start_idx = len(self.search_space.loss.all_solutions) opti_idx = ( np.min([child.level, len(self.exploration)]) - 1 ) calls_left = np.min( [ self.explor_calls[opti_idx], self.f_calls - self.search_space.loss.calls, ] ) # If there is budget if calls_left > 0: self.exploration[opti_idx].search_space = child self.exploration[opti_idx].f_calls = ( calls_left + self.search_space.loss.calls ) logger.info( f""" Exploration {child.__class__.__name__} {child.id} child of {child.father.id} at level {child.level}\n # of explored fractals : {self.n_h}""" ) # Progress bar prec_calls = self.search_space.loss.calls # Run exploration, scores self.exploration[opti_idx].run( H=child, n_process=n_process ) # Compute the last index of the last solution # computed during exploration last_idx = len(self.search_space.loss.all_solutions) # Save best found solution if self.search_space.loss.new_best: logger.info( f""" Best solution found : {self.search_space.loss.best_score}""" ) # score fractal child.compute_score(slice(start_idx, last_idx)) logger.debug( f""" Child {child.father.id}.{child.id}.{child.level} score: {child.score} """ ) # Progress bar self.pending_pb( self.search_space.loss.calls - prec_calls ) self.update_main_pb( self.search_space.loss.calls - prec_calls, explor=True, best=self.search_space.loss.new_best, ) self.meta_pb.update( self.search_space.loss.calls - prec_calls ) # Add child to tree search self.tree_search.add(child) # Exploitation elif self.exploitation: # Run exploitation, scores calls_left = np.min( [ self.exploi_calls, self.f_calls - self.search_space.loss.calls, ] ) if calls_left > 0: self.exploitation.f_calls = ( calls_left + self.search_space.loss.calls ) logger.info( f""" Exploration {child.__class__.__name__} {child.id} child of {child.father.id} at level {child.level}\n # of explored fractals : {self.n_h}""" ) # Progress bar prec_calls = self.search_space.loss.calls # Run exploitation self.exploitation.run(H=child, n_process=n_process) if self.search_space.loss.new_best: logger.info( f"""Best solution found : {self.search_space.loss.best_score}""" ) logger.debug( f"""Child {child.father.id}.{child.id}.{child.level} score: EXPLOITATION""" ) # Progress bar self.pending_pb( self.search_space.loss.calls - prec_calls ) self.update_main_pb( self.search_space.loss.calls - prec_calls, explor=False, best=self.search_space.loss.new_best, ) self.meta_pb.update( self.search_space.loss.calls - prec_calls )
[docs] def run(self, n_process=1): """run(n_process=1) Runs DBA. Parameters ---------- n_process : int, default=1 Determine the number of best solution found to return. Returns ------- best_sol : list[float] Returns a list of the :code:`n_process` best found points to the continuous format best_scores : list[float] Returns a list of the :code:`n_process` best found scores associated to best_sol """ self.build_bar(self.f_calls) if self.exploration: for exp in self.exploration: exp.manager = self.manager logger.info("Starting") if self.exploitation: self.exploitation.manager = self.manager self.n_h = 0 stop = True # Select initial hypervolume (root) from the search tree stop, hypervolumes = self.tree_search.get_next() while stop and self.search_space.loss.calls < self.f_calls: self.evaluate(hypervolumes, n_process) stop, hypervolumes = self.tree_search.get_next() self.executed = True logger.info(f"Loss function calls: {self.search_space.loss.calls}") logger.info( f"Explored {self.search_space.__class__.__name__}: {self.n_h}" ) logger.info(f"Best score: {self.search_space.loss.best_score}") logger.info(f"Best solution: {self.search_space.loss.best_point}") self.close_bar() logger.info("Ending") return self.search_space.loss.get_best(n_process)