123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- """Basic Algorithm class
- """
- # main imports
- import logging
- import sys, os
- from macop.utils.progress import macop_text, macop_line, macop_progress
- # Generic algorithm class
- class Algorithm():
- """Abstract Algorithm class used as basic algorithm implementation with some specific initialization
- This class enables to manage some common usages of operation research algorithms:
- - initialization function of solution
- - validator function to check if solution is valid or not (based on some criteria)
- - evaluation function to give fitness score to a solution
- - operators used in order to update solution during search process
- - policy process applied when choosing next operator to apply
- - callbacks function in order to do some relative stuff every number of evaluation or reload algorithm state
- - parent algorithm associated to this new algorithm instance (hierarchy management)
- Attributes:
- initialiser: {function} -- basic function strategy to initialise solution
- evaluator: {:class:`~macop.evaluators.base.Evaluator`} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
- operators: {[:class:`~macop.operators.base.Operator`]} -- list of operator to use when launching algorithm
- policy: {:class:`~macop.policies.base.Policy`} -- Policy implementation strategy to select operators
- validator: {function} -- basic function to check if solution is valid or not under some constraints
- maximise: {bool} -- specify kind of optimisation problem
- verbose: {bool} -- verbose or not information about the algorithm
- currentSolution: {:class:`~macop.solutions.base.Solution`} -- current solution managed for current evaluation comparison
- bestSolution: {:class:`~macop.solutions.base.Solution`} -- best solution found so far during running algorithm
- callbacks: {[:class:`~macop.callbacks.base.Callback`]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initialising algorithm
- parent: {:class:`~macop.algorithms.base.Algorithm`} -- parent algorithm reference in case of inner Algorithm instance (optional)
- """
- def __init__(self,
- initialiser,
- evaluator,
- operators,
- policy,
- validator,
- maximise=True,
- parent=None,
- verbose=True):
- """Basic Algorithm initialisation
- Args:
- initialiser: {function} -- basic function strategy to initialise solution
- evaluator: {:class:`~macop.evaluators.base.Evaluator`} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
- operators: {[:class:`~macop.operators.base.Operator`]} -- list of operator to use when launching algorithm
- policy: {:class:`~macop.policies.base.Policy`} -- Policy implementation strategy to select operators
- validator: {function} -- basic function to check if solution is valid or not under some constraints
- maximise: {bool} -- specify kind of optimisation problem
- parent: {:class:`~macop.algorithms.base.Algorithm`} -- parent algorithm reference in case of inner Algorithm instance (optional)
- verbose: {bool} -- verbose or not information about the algorithm
- """
- # public members intialization
- self.initialiser = initialiser
- self.evaluator = evaluator
- self.validator = validator
- self.policy = policy
- # protected members intialization
- self.operators = operators
- self._callbacks = []
- self._bestSolution = None
- self._currentSolution = None
- # by default
- self._numberOfEvaluations = 0
- self._maxEvaluations = 0
- # other parameters
- self._parent = parent # parent algorithm if it's sub algorithm
- #self.maxEvaluations = 0 # by default
- self._maximise = maximise
- self._verbose = verbose
- # track reference of algorihtm into operator (keep an eye into best solution)
- for operator in self.operators:
- if self._parent is not None:
- operator.setAlgo(self.getParent())
- else:
- operator.setAlgo(self)
- # also track reference for policy
- if self._parent is not None:
- self.policy.setAlgo(self.getParent())
- else:
- self.policy.setAlgo(self)
- def addCallback(self, callback):
- """Add new callback to algorithm specifying usefull parameters
- Args:
- callback: {:class:`~macop.callbacks.base.Callback`} -- specific Callback instance
- """
- # specify current main algorithm reference for callback
- if self._parent is not None:
- callback.setAlgo(self.getParent())
- else:
- callback.setAlgo(self)
- # set as new
- self._callbacks.append(callback)
- def resume(self):
- """Resume algorithm using Callback instances
- """
- # load every callback if many things are necessary to do before running algorithm
- for callback in self._callbacks:
- callback.load()
- def getParent(self):
- """Recursively find the main parent algorithm attached of the current algorithm
- Returns:
- {:class:`~macop.algorithms.base.Algorithm`}: main algorithm set for this algorithm
- """
- currentalgorithm = self
- parent_alrogithm = None
- # recursively find the main algorithm parent
- while currentalgorithm._parent is not None:
- parent_alrogithm = currentalgorithm._parent
- currentalgorithm = currentalgorithm._parent
- return parent_alrogithm
- def setParent(self, parent):
- """Set parent algorithm to current algorithm
- Args:
- parent: {:class:`~macop.algorithms.base.Algorithm`} -- main algorithm set for this algorithm
- """
- self._parent = parent
- @property
- def result(self):
- """Get the expected result of the current algorithm
- By default the best solution (but can be anything you want)
- Returns:
- {object}: expected result data of the current algorithm
- """
- return self._bestSolution
- @result.setter
- def result(self, result):
- """Set current default result of the algorithm
- Args:
- result: {object} -- expected result data of the current algorithm
- """
- self._bestSolution = result
- def initRun(self):
- """
- initialise the current solution and best solution using the `initialiser` function
- """
- self._currentSolution = self.initialiser()
- # evaluate current solution
- self._currentSolution.evaluate(self.evaluator)
- self.increaseEvaluation()
- # keep in memory best known solution (current solution)
- if self._bestSolution is None:
- self._bestSolution = self._currentSolution
- def increaseEvaluation(self):
- """
- Increase number of evaluation once a solution is evaluated for each dependant algorithm (parents hierarchy)
- """
- currentalgorithm = self
- while currentalgorithm is not None:
- currentalgorithm._numberOfEvaluations += 1
- currentalgorithm = currentalgorithm._parent
- def getGlobalEvaluation(self):
- """Get the global number of evaluation (if inner algorithm)
- Returns:
- {int}: current global number of evaluation
- """
- parentalgorithm = self.getParent()
- if parentalgorithm is not None:
- return parentalgorithm.getGlobalEvaluation()
- return self._numberOfEvaluations
- def getEvaluation(self):
- """Get the current number of evaluation
- Returns:
- {int}: current number of evaluation
- """
- return self._numberOfEvaluations
- def setEvaluation(self, evaluations):
- """Set the current number of evaluation
- Args:
- evaluations: {int} -- current expected number of evaluation
- """
- self._numberOfEvaluations = evaluations
- def getGlobalMaxEvaluation(self):
- """Get the global max number of evaluation (if inner algorithm)
- Returns:
- {int}: current global max number of evaluation
- """
- parentalgorithm = self.getParent()
- if parentalgorithm is not None:
- return parentalgorithm.getGlobalMaxEvaluation()
- return self._maxEvaluations
- def stop(self):
- """
- Global stopping criteria (check for parents algorithm hierarchy too)
- """
- parentalgorithm = self.getParent()
- # based on global stopping creteria or on its own stopping critera
- if parentalgorithm is not None:
- return parentalgorithm._numberOfEvaluations >= parentalgorithm._maxEvaluations or self._numberOfEvaluations >= self._maxEvaluations
- return self._numberOfEvaluations >= self._maxEvaluations
- def evaluate(self, solution):
- """
- Evaluate a solution using evaluator passed when intialize algorithm
- Args:
- solution: {:class:`~macop.solutions.base.Solution`} -- solution to evaluate
- Returns:
- {float}: fitness score of solution which is not already evaluated or changed
- Note:
- if multi-objective problem this method can be updated using array of `evaluator`
- """
- return solution.evaluate(self.evaluator)
- def update(self, solution):
- """
- Apply update function to solution using specific `policy`
- Check if solution is valid after modification and returns it
-
- Args:
- solution: {:class:`~macop.solutions.base.Solution`} -- solution to update using current policy
- Returns:
- {:class:`~macop.solutions.base.Solution`}: updated solution obtained by the selected operator
- """
- # two parameters are sent if specific crossover solution are wished
- sol = self.policy.apply(solution)
- # compute fitness of new solution if not already computed
- if sol._score is None:
- sol.evaluate(self.evaluator)
- if (sol.isValid(self.validator)):
- return sol
- else:
- logging.info("-- New solution is not valid %s" % sol)
- return solution
- def isBetter(self, solution):
- """
- Check if solution is better than best found
- - if the new solution is not valid then the fitness comparison is not done
- - fitness comparison is done using problem nature (maximising or minimising)
- Args:
- solution: {:class:`~macop.solutions.base.Solution`} -- solution to compare with best one
- Returns:
- {bool}:`True` if better
- """
- if not solution.isValid(self.validator):
- return False
- # depending of problem to solve (maximizing or minimizing)
- if self._maximise:
- if solution.fitness > self._bestSolution.fitness:
- return True
- else:
- if solution.fitness < self._bestSolution.fitness:
- return True
- # by default
- return False
- def run(self, evaluations):
- """
- Run the specific algorithm following number of evaluations to find optima
- """
- # append number of max evaluation if multiple run called
- self._maxEvaluations += evaluations
- # check if global evaluation is used or not
- if self.getParent() is not None and self.getGlobalEvaluation() != 0:
- # init number evaluations of inner algorithm depending of globalEvaluation
- # allows to restart from `checkpoint` last evaluation into inner algorithm
- rest = self.getGlobalEvaluation() % self._maxEvaluations
- self._numberOfEvaluations = rest
- else:
- self._numberOfEvaluations = 0
- logging.info(
- f"Run {self.__str__()} with {(self.__str__(), evaluations)} evaluations"
- )
- def progress(self):
- """
- Log progress and apply callbacks if necessary
- """
- if len(self._callbacks) > 0:
- for callback in self._callbacks:
- callback.run()
- if self._verbose:
- macop_progress(self, self.getGlobalEvaluation(),
- self.getGlobalMaxEvaluation())
- logging.info(
- f"-- {type(self).__name__} evaluation {self._numberOfEvaluations} of {self._maxEvaluations} - BEST SCORE {self._bestSolution._score}"
- )
- def end(self):
- """Display end message into `run` method
- """
- macop_text(
- self,
- f'({type(self).__name__}) Found after {self._numberOfEvaluations} evaluations \n - {self._bestSolution}'
- )
- macop_line(self)
- def information(self):
- logging.info(
- f"-- Best {self._bestSolution} - SCORE {self._bestSolution.fitness}"
- )
- def __str__(self):
- return f"{type(self).__name__} using {type(self._bestSolution).__name__}"
|