getGlobalMaxEvaluation() (macop.algorithms.base.Algorithm method)

initalizer (macop.algorithms.mono.HillClimberBestImprovment attribute)

evaluator (macop.algorithms.mono.HillClimberBestImprovment attribute)

operators (macop.algorithms.mono.HillClimberBestImprovment attribute)

policy (macop.algorithms.mono.HillClimberBestImprovment attribute)

validator (macop.algorithms.mono.HillClimberBestImprovment attribute)

maximise (macop.algorithms.mono.HillClimberBestImprovment attribute)

currentSolution (macop.algorithms.mono.HillClimberBestImprovment attribute)

bestSolution (macop.algorithms.mono.HillClimberBestImprovment attribute)

callbacks (macop.algorithms.mono.HillClimberBestImprovment attribute)

initalizer (macop.algorithms.mono.HillClimberFirstImprovment attribute)

evaluator (macop.algorithms.mono.HillClimberFirstImprovment attribute)

operators (macop.algorithms.mono.HillClimberFirstImprovment attribute)

policy (macop.algorithms.mono.HillClimberFirstImprovment attribute)

validator (macop.algorithms.mono.HillClimberFirstImprovment attribute)

maximise (macop.algorithms.mono.HillClimberFirstImprovment attribute)

currentSolution (macop.algorithms.mono.HillClimberFirstImprovment attribute)

bestSolution (macop.algorithms.mono.HillClimberFirstImprovment attribute)

callbacks (macop.algorithms.mono.HillClimberFirstImprovment attribute)

initalizer (macop.algorithms.mono.IteratedLocalSearch attribute)

currentSolution (macop.algorithms.mono.IteratedLocalSearch attribute)

bestSolution (macop.algorithms.mono.IteratedLocalSearch attribute)

compute() (macop.evaluators.knapsacks.KnapsackMultiEvaluator method)

RandomSplitCrossover (class in macop.operators.discrete.crossovers)

kind (macop.operators.discrete.crossovers.RandomSplitCrossover attribute)

apply() (macop.operators.discrete.crossovers.RandomSplitCrossover method)

kind (macop.operators.discrete.crossovers.SimpleCrossover attribute)

apply() (macop.operators.discrete.crossovers.SimpleCrossover method)

SimpleBinaryMutation (class in macop.operators.discrete.mutators)

kind (macop.operators.discrete.mutators.SimpleBinaryMutation attribute)

apply() (macop.operators.discrete.mutators.SimpleBinaryMutation method)

kind (macop.operators.discrete.mutators.SimpleMutation attribute)

apply() (macop.operators.discrete.mutators.SimpleMutation method)

data (macop.solutions.discrete.CombinatoryIntegerSolution attribute)

size (macop.solutions.discrete.CombinatoryIntegerSolution attribute)

score (macop.solutions.discrete.CombinatoryIntegerSolution attribute)

random() (macop.solutions.discrete.CombinatoryIntegerSolution method)

"""Basic Algorithm class
"""

# main imports
import logging
import sys, os
from ..utils.color 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:
        initalizer: {function} -- basic function strategy to initialize solution
        evaluator: {function} -- basic function in order to obtained fitness (mono or multiple objectives)
        operators: {[Operator]} -- list of operator to use when launching algorithm
        policy: {Policy} -- Policy class 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: {Solution} -- current solution managed for current evaluation comparison
        bestSolution: {Solution} -- best solution found so far during running algorithm
        callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initializing algorithm
        parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
    """
    def __init__(self,
                 initalizer,
                 evaluator,
                 operators,
                 policy,
                 validator,
                 maximise=True,
                 parent=None,
                 verbose=True):

        # protected members intialization
        self._initializer = initalizer
        self._evaluator = evaluator
        self._operators = operators
        self._policy = policy
        self._validator = validator
        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: {Callback} -- specific Callback instance
        """
        # specify current main algorithm reference for callback
            _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:
            {Algorithm} -- main algorithm set for this algorithm
        """

        current_algorithm = self
        parent_alrogithm = None

        # recursively find the main algorithm parent
        while current_algorithm._parent is not None:
            parent_alrogithm = current_algorithm._parent
            current_algorithm = current_algorithm._parent

        return parent_alrogithm

    def initRun(self):
        """
        Initialize the current solution and best solution using the `initialiser` function
        """

        self._currentSolution = self._initializer()

        # evaluate current solution
        self._currentSolution.evaluate(self._evaluator)

        # 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)
        """

        while current_algorithm is not None:

            current_algorithm._numberOfEvaluations += 1
            
    def getGlobalEvaluation(self):
        """Get the global number of evaluation (if inner algorithm)

        Returns:
            {int} -- current global number of evaluation
        """
        parent_algorithm = self.getParent()

        if parent_algorithm is not None:
            return parent_algorithm.getGlobalEvaluation()

        return self._numberOfEvaluations

    def getGlobalMaxEvaluation(self):
        """Get the global max number of evaluation (if inner algorithm)

        Returns:
            {int} -- current global max number of evaluation
        """

            return parent_algorithm.getGlobalMaxEvaluation()

        return self._maxEvaluations

    def stop(self):
        """
        Global stopping criteria (check for parents algorithm hierarchy too)
        """

        # based on global stopping creteria or on its own stopping critera
            return parent_algorithm._numberOfEvaluations >= parent_algorithm._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: {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: {Solution} -- solution to update using current policy

        Returns:
            {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

        Args:
            solution: {Solution} -- solution to compare with best one

        Returns:
            {bool} -- `True` if better
        """
        # 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 False

    def run(self, evaluations):
        """
        Run the specific algorithm following number of evaluations to find optima
        """

        # enable or not block print element
        if not self._verbose:
            self.__blockPrint()
        else:
            self.__enablePrint()

        # 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("Run %s with %s evaluations" %
                     (self.__str__(), evaluations))

    def progress(self):
        """
        Log progress and apply callbacks if necessary
        """
        if len(self._callbacks) > 0:
            for callback in self._callbacks:
                callback.run()

        macop_progress(self.getGlobalEvaluation(),
                       self.getGlobalMaxEvaluation())

        logging.info(f"-- {type(self).__name__} evaluation {self._numberOfEvaluations} of {self._maxEvaluations} ({((self._numberOfEvaluations / self._maxEvaluations) * 100):.2f}%) - BEST SCORE {self._bestSolution.fitness()}")

    def end(self, verbose=True):
        """Display end message into `run` method
        """
        print(macop_text(f'({type(self).__name__}) Found after {self._numberOfEvaluations} evaluations \n   - {self._bestSolution}'))
        print(macop_line())

    def information(self):
        logging.info(f"-- Best {self._bestSolution} - SCORE {self._bestSolution.fitness()}")

    def __blockPrint(self):
        """Private method which disables console prints when running algorithm if specified when instancing algorithm
        """
        sys.stdout = open(os.devnull, 'w')

    def __enablePrint(self):
        """Private which enables console prints when running algorithm
        """
        sys.stdout = sys.__stdout__

    def __del__(self):
        # enable prints when object is deleted

    def __str__(self):
        return f"{type(self).__name__} using {type(self._bestSolution).__name__}"

"""Mono-objective available algorithms
"""

# module imports
from .base import Algorithm

class HillClimberFirstImprovment(Algorithm):
    """Hill Climber First Improvment used as quick exploration optimisation algorithm

    - First, this algorithm do a neighborhood exploration of a new generated solution (by doing operation on the current solution obtained) in order to find a better solution from the neighborhood space.
    - Then replace the current solution by the first one from the neighbordhood space which is better than the current solution.
    - And do these steps until a number of evaluation (stopping criterion) is reached.

        currentSolution: {Solution} -- current solution managed for current evaluation
    """
        """
        Run the local search algorithm

        Args:
            evaluations: {int} -- number of Local search evaluations
            
        Returns:
            {Solution} -- best solution found
        """

        # by default use of mother method to initialize variables
        super().run(evaluations)

        # initialize current solution and best solution
        self.initRun()

        solutionSize = self._currentSolution._size

        # local search algorithm implementation
        while not self.stop():

            for _ in range(solutionSize):

                # update current solution using policy
                newSolution = self.update(self._currentSolution)

                # if better solution than currently, replace it and stop current exploration (first improvment)
                if self.isBetter(newSolution):
                    self._bestSolution = newSolution
                    break

                # increase number of evaluations
                self.increaseEvaluation()

                self.progress()
                logging.info(f"---- Current {newSolution} - SCORE {newSolution.fitness()}")

                # stop algorithm if necessary
                if self.stop():
                    break

            # set new current solution using best solution found in this neighbor search
            self._currentSolution = self._bestSolution

        logging.info(f"End of {type(self).__name__}, best solution found {self._bestSolution}")

        return self._bestSolution

class HillClimberBestImprovment(Algorithm):
    """Hill Climber Best Improvment used as exploitation optimisation algorithm

    - First, this algorithm do a neighborhood exploration of a new generated solution (by doing operation on the current solution obtained) in order to find the best solution from the neighborhood space.
    - Then replace the best solution found from the neighbordhood space as current solution to use.

    """
        """

        Args:
            
        Returns:
        """

                # if better solution than currently, replace it

                    break

class IteratedLocalSearch(Algorithm):
    """Iterated Local Search used to avoid local optima and increave EvE (Exploration vs Exploitation) compromise

    """
    def run(self, evaluations, ls_evaluations=100):
        """
        Run the iterated local search algorithm using local search (EvE compromise)

        Args:
            evaluations: {int} -- number of global evaluations for ILS
            ls_evaluations: {int} -- number of Local search evaluations (default: 100)

        Returns:
        """

        # enable resuming for ILS
        self.resume()

        # initialize current solution

        # passing global evaluation param from ILS
        ls = HillClimberFirstImprovment(self._initializer,
                         self._evaluator,
                         self._operators,
                         self._policy,
                         self._validator,
                         self._maximise,
                         parent=self)

        # add same callbacks
            ls.addCallback(callback)

            # create and search solution from local search
            newSolution = ls.run(ls_evaluations)

            # if better solution than currently, replace it
            if self.isBetter(newSolution):
                self._bestSolution = newSolution

            # number of evaluatins increased from LocalSearch
            # increase number of evaluations and progress are then not necessary there
            #self.increaseEvaluation()
            #self.progress()

            self.information()

"""Multi-objetive classes algorithm
"""

import math
import numpy as np
import sys

def moEvaluator(solution, evaluator, weights):

    scores = [eval(solution) for eval in evaluator]

    # associate objectives scores to solution
    solution._scores = scores

    return sum([scores[i] for i, w in enumerate(weights)])

class MOEAD(Algorithm):
    """Multi-Ojective Evolutionary Algorithm with Scalar Decomposition

        mu: {int} -- number of sub problems
        T: {[float]} -- number of neightbors for each sub problem
        nObjectives: {int} -- number of objectives (based of number evaluator)
        evaluator: {[function]} -- list of basic function in order to obtained fitness (multiple objectives)
        maximise: {bool} -- specify kind of optimisation problem
        population: [{Solution}] -- population of solution, one for each sub problem
        pfPop: [{Solution}] -- pareto front population
        weights: [[{float}]] -- random weights used for custom mu sub problems
    """
                 mu,
                 T,
                 policy,

        # redefinition of constructor to well use `initRun` method

        self._nObjectives = len(evaluator)

        # track reference of algo into operator (keep an eye into best solution)
            operator.setAlgo(self)

        # by default track reference for policy
        self._policy.setAlgo(self)

        if mu < T:
            raise ValueError('`mu` cannot be less than `T`')

        self._mu = mu
        self._T = T

        # initialize neighbors for each sub problem
        self.setNeighbors()

        weights = []

        if self._nObjectives == 2:

            for i in range(self._mu):
                angle = math.pi / 2 * i / (self._mu - 1)
                weights.append([math.cos(angle), math.sin(angle)])

        elif self._nObjectives >= 3:

            # random weights using uniform
                w_i = np.random.uniform(0, 1, self._nObjectives)
                weights.append(w_i / sum(w_i))
        else:
            raise ValueError('Unvalid number of objectives')

        self._weights = weights

        self._subProblems = []

        for i in range(self._mu):

            # compute weight sum from solution
            sub_evaluator = lambda solution: moEvaluator(
                solution, evaluator, weights[i])

            # intialize each sub problem
            # use copy of list to keep track for each sub problem
            subProblem = MOSubProblem(i, weights[i],
                                      initalizer, sub_evaluator,
                                      operators.copy(), policy, validator,
                                      maximise, self)

            self._subProblems.append(subProblem)

        self._population = [None for n in range(self._mu)]
        self._pfPop = []

        # ref point based on number of evaluators
            self._refPoint = [0 for _ in range(self._nObjectives)]
        else:
            self._refPoint = [
                sys.float_info.max for _ in range(self._nObjectives)
            ]

        """
        Method which initialiazes or re-initializes the whole algorithm context specifically for MOEAD
        """
        # initialization is done during run method
        pass

        """

        Args:
            
        Returns:
        """

        # enable callback resume for MOEAD

        # initialize each sub problem if no backup

            if self._subProblems[i]._bestSolution is None:
                self._subProblems[i].run(1)
                self._population[i] = self._subProblems[i]._bestSolution

        # if no backup for pf population
        if len(self._pfPop) == 0:
                self._pfPop.append(self._subProblems[i]._bestSolution)

        # MOEAD algorithm implementation

                # run 1 iteration into sub problem `i`
                spBestSolution = self._subProblems[i]._bestSolution

                self.updateRefPoint(spBestSolution)

                # for each neighbor of current sub problem update solution if better
                improvment = False
                for j in self._neighbors[i]:
                    if spBestSolution.fitness(
                    ) > self._subProblems[j]._bestSolution.fitness():

                        # create new solution based on current new if better, computes fitness associated to new solution for sub problem
                        class_name = type(spBestSolution).__name__

                        # dynamically load solution class if unknown
                        if class_name not in sys.modules:
                            load_class(class_name, globals())

                        newSolution = getattr(
                            globals()['macop.solutions.' + class_name],
                            class_name)(spBestSolution._data,
                                        len(spBestSolution._data))

                        # evaluate solution for new sub problem and update as best solution
                        self._subProblems[j].evaluate(newSolution)
                        self._subProblems[j]._bestSolution = newSolution

                        # update population solution for this sub problem
                        self._population[j] = newSolution

                        improvment = True

                # add new solution if improvment is idenfity
                if improvment:
                    self._pfPop.append(spBestSolution)

                # update pareto front
                self._pfPop = self.paretoFront(self._pfPop)

                # add progress here

                    break

        logging.info(f"End of {type(self).__name__}, best solution found {self._population}")

        self.end()

        return self._pfPop

        """
        """

        macop_progress(self.getGlobalEvaluation(), self.getGlobalMaxEvaluation())

        logging.info(f"-- {type(self).__name__} evaluation {self._numberOfEvaluations} of {self._maxEvaluations} ({((self._numberOfEvaluations) / self._maxEvaluations * 100.):.2f}%)")

    def setNeighbors(self):

        dmin = dmax = 0

        if self._T % 2 == 1:
            dmin = -int(self._T / 2)
            dmax = int(self._T / 2) + 1
        else:
            dmin = -int(self._T / 2) + 1
            dmax = +self._T / 2

        # init neighbord list
        self._neighbors = [[] for n in range(self._mu)]

        for direction in range(0, -dmin):
            for i in range(self._T):
                self._neighbors[direction].append(i)

        for direction in range(-dmin, self._mu - dmax):
            for i in range(direction + dmin, direction + dmax):

        for direction in range(self._mu - dmax, self._mu):
            for i in range(self._mu - self._T, self._mu):

    def updateRefPoint(self, solution):

            for i in range(len(self._evaluator)):
                if solution._scores[i] > self._refPoint[i]:
                    self._refPoint[i] = solution._scores[i]
        else:
                if solution.scores[i] < self._refPoint[i]:

    def paretoFront(self, population):

        paFront = []
        indexes = []
        nObjectives = len(self._evaluator)
        nSolutions = len(population)

        # find dominated solution
        for i in range(nSolutions):
            for j in range(nSolutions):

                if j in indexes:
                    continue

                # check if solutions are the same
                if all([
                        population[i]._data[k] == population[j]._data[k]
                        for k in range(len(population[i]._data))
                ]):
                    continue

                nDominated = 0

                # check number of dominated objectives of current solution by the others solution
                for k in range(len(self._evaluator)):
                    if self._maximise:
                        if population[i]._scores[k] < population[j]._scores[k]:
                            nDominated += 1
                    else:
                        if population[i]._scores[k] > population[j]._scores[k]:

                if nDominated == nObjectives:
                    indexes.append(i)
                    break

        # store the non dominated solution into pareto front
            if i not in indexes:
                paFront.append(population[i])

        return paFront

    def end(self):
        """

        print(macop_text(f'({type(self).__name__}) Found after {self._numberOfEvaluations} evaluations'))

        for i, solution in enumerate(self._pfPop):
            print(f'  - [{i}] {solution._scores} : {solution}')

        logging.info("-- Pareto front :")

            logging.info(f"-- {i}] SCORE {solution._scores} - {solution}")

        return f"{type(self).__name__} using {type(self._population).__name__}"

class MOSubProblem(Algorithm):
    """Specific MO sub problem used into MOEAD

        index: {int} -- sub problem index
        weights: {[float]} -- sub problems objectives weights
    """
                 index,
                 weights,
                 policy,

        super().__init__(initalizer, evaluator, operators, policy,
                         validator, maximise, parent)

        self._index = index

        """

        Args:
            evaluations: {int} -- number of evaluations
            
        Returns:
        """

        # initialize solution if necessary
            self.initRun()

        # new operators list keep track of current sub problem
        for op in self._operators:
            op.setAlgo(self)

        for _ in range(evaluations):

            # keep reference of sub problem used

            # update solution using policy
            newSolution = self.update(self._bestSolution)

            # increase number of evaluations
            self.increaseEvaluation()

            self.progress()

            # stop algorithm if necessary
            if self.stop():
                break

            logging.info(f"---- Current {newSolution} - SCORE {newSolution.fitness()}")

            logging.info(f"End of {type(self).__name__}, best solution found {self._bestSolution}")

"""Abstract Checkpoint classes for callback process
"""
import os
from abc import abstractmethod

class Callback():
    """
    Callback abstract class in order to compute some instruction every evaluation

        algo: {Algorithm} -- main algorithm instance reference
        every: {int} -- checkpoint frequency used (based on number of evaluations)
        filepath: {str} -- file path where checkpoints will be saved
    """
    def __init__(self, every, filepath):

        self._algo = None
        self._every = every
        self._filepath = filepath

        # build path if not already exists
        head, _ = os.path.split(self._filepath)

        if not os.path.exists(head):
            os.makedirs(head)

    def setAlgo(self, algo):
        """Specify the main algorithm instance reference

        Args:
            algo: {Algorithm} -- main algorithm instance reference
        """
        self._algo = algo

    @abstractmethod
    def run(self):
        """
        Check if necessary to do backup based on `every` variable
        """
        pass

    def load(self):
        """
        Load last backup line of solution and set algorithm state at this backup
        """
        pass

"""Classical Checkpoints classes implementations
"""

import os

from .base import Callback
from ..utils.color import macop_text, macop_line

class BasicCheckpoint(Callback):
    """
    BasicCheckpoint is used for loading previous computations and start again after loading checkpoint

    """
        """
        """
        # get current best solution
        solution = self._algo._bestSolution

        currentEvaluation = self._algo.getGlobalEvaluation()

        # backup if necessary
        if currentEvaluation % self._every == 0:

            logging.info("Checkpoint is done into " + self._filepath)

            solutionData = ""
            solutionSize = len(solution._data)

            for index, val in enumerate(solution._data):
                solutionData += str(val)

                if index < solutionSize - 1:
                    solutionData += ' '

            line = str(currentEvaluation) + ';' + solutionData + ';' + str(
                solution.fitness()) + ';\n'

            # check if file exists
            if not os.path.exists(self._filepath):
                with open(self._filepath, 'w') as f:
                    f.write(line)
            else:
                with open(self._filepath, 'a') as f:

        """
        Load last backup line of solution and set algorithm state (best solution and evaluations) at this backup
        """
        if os.path.exists(self._filepath):

            logging.info('Load best solution from last checkpoint')
            with open(self._filepath) as f:

                # get last line and read data
                lastline = f.readlines()[-1]
                data = lastline.split(';')

                # get evaluation  information
                globalEvaluation = int(data[0])

                if self._algo.getParent() is not None:
                    self._algo.getParent()._numberOfEvaluations = globalEvaluation
                else:
                    self._algo._numberOfEvaluations = globalEvaluation

                # get best solution data information
                solutionData = list(map(int, data[1].split(' ')))

                if self._algo._bestSolution is None:
                    self._algo._bestSolution = self._algo._initializer()

                self._algo._bestSolution._data = np.array(solutionData)
                self._algo._bestSolution._score = float(data[2])

            print(macop_line())
            print(macop_text(f'Checkpoint found from `{self._filepath}` file.'))

            print(macop_text(f'Restart algorithm from evaluation {self._algo._numberOfEvaluations}.'))

        else:
            print(macop_text('No backup found... Start running algorithm from evaluation 0.'))
            logging.info("Can't load backup... Backup filepath not valid in Checkpoint")

"""Multi-objective Checkpoints classes implementations
"""

import os

class MultiCheckpoint(Callback):
    """
    MultiCheckpoint is used for loading previous computations and start again after loading checkpoint

    """
        """
        """
        # get current population
        population = self._algo._population

            with open(self._filepath, 'w') as f:

                for solution in population:
                    solutionData = ""
                    solutionSize = len(solution._data)

                    for index, val in enumerate(solution._data):
                        solutionData += str(val)

                        if index < solutionSize - 1:
                            solutionData += ' '

                    line = str(currentEvaluation) + ';'

                    for i in range(len(self._algo._evaluator)):
                        line += str(solution._scores[i]) + ';'

                    line += solutionData + ';\n'

        """
        Load backup lines as population and set algorithm state (population and pareto front) at this backup
        """

                # read data for each line
                for i, line in enumerate(f.readlines()):

                    data = line.replace(';\n', '').split(';')

                    # only the first time
                    if i == 0:
                        # get evaluation  information
                        globalEvaluation = int(data[0])

                        if self._algo.getParent() is not None:
                            self._algo.getParen()._numberOfEvaluations = globalEvaluation
                        else:
                            self._algo._numberOfEvaluations = globalEvaluation

                    nObjectives = len(self._algo._evaluator)
                    scores = [float(s) for s in data[1:nObjectives + 1]]

                    # get best solution data information
                    solutionData = list(map(int, data[-1].split(' ')))

                    # initialize and fill with data
                    self._algo._population[i] = self._algo._initializer()
                    self._algo._population[i]._data = np.array(solutionData)
                    self._algo._population[i]._scores = scores

                    self._algo._pfPop.append(self._algo._population[i])

            print(macop_text(f'Load of available population from `{self._filepath}`'))

        else:

class ParetoCheckpoint(Callback):
    """
    Pareto checkpoint is used for loading previous computations and start again after loading checkpoint

    """
        """
        """
        pfPop = self._algo._pfPop

                for solution in pfPop:

                    line = ''

        """
        """

                # reinit pf population
                self._algo._pfPop = []

                # retrieve class name from algo
                class_name = type(self._algo._population[0]).__name__

                # dynamically load solution class if unknown
                if class_name not in sys.modules:
                    load_class(class_name, globals())

                for line in f.readlines():

                    scores = [float(s) for s in data[0:nObjectives]]

                    newSolution = getattr(
                        globals()['macop.solutions.' + class_name],
                        class_name)(solutionData, len(solutionData))
                    newSolution._scores = scores

                    self._algo._pfPop.append(newSolution)

            print(
                macop_text(f'Load of available pareto front backup from `{ self._filepath}`'))

        else:
            print(macop_text('No pareto front found... Start running algorithm with new pareto front population.'))
            logging.info("No pareto front backup used...")

"""UCB policy Checkpoint class implementation
"""

import os

class UCBCheckpoint(Callback):
    """
    UCB checkpoint is used for loading previous Upper Confidence Bound data and start again after loading checkpoint
    Need to be the same operators used during previous run (see `macop.policies.reinforcement.UCBPolicy` for more details)

    """
        """
        """

            logging.info("UCB Checkpoint is done into " + self._filepath)

                rewardsLine = ''

                for i, r in enumerate(self._algo._policy._rewards):
                    rewardsLine += str(r)

                    if i != len(self._algo._policy._rewards) - 1:
                        rewardsLine += ';'

                f.write(rewardsLine + '\n')

                occurrencesLine = ''

                for i, o in enumerate(self._algo._policy._occurrences):
                    occurrencesLine += str(o)

                    if i != len(self._algo._policy._occurrences) - 1:
                        occurrencesLine += ';'

                f.write(occurrencesLine + '\n')

        """
        Load backup lines as rewards and occurrences for UCB
        """

            logging.info('Load UCB data')

                lines = f.readlines()
                rewardsLine = lines[0].replace('\n', '')
                occurrencesLine = lines[1].replace('\n', '')

                self._algo._policy._rewards = [
                    float(f) for f in rewardsLine.split(';')
                ]
                self._algo._policy._occurrences = [
                    float(f) for f in occurrencesLine.split(';')
                ]

            print(
                macop_text(f'Load of available UCB policy data from `{self._filepath}`'))

        else:
            print(macop_text('No UCB data found, use default UCB policy'))
            logging.info("No UCB data found...")

"""Knapsack evaluators classes
"""
from .base import Evaluator

class KnapsackEvaluator(Evaluator):
    """Knapsack evaluator class which enables to compute solution using specific `_data`

    - stores into its `_data` dictionary attritute required measures when computing a knapsack solution
    - `_data['worths']` stores knapsack objects worths information
    - `_data['weights']` stores knapsack objects weights information
    - `compute` method enables to compute and associate a score to a given knapsack solution
    """

    def compute(self, solution):
        """Apply the computation of fitness from solution

        Args:
            solution: {Solution} -- Solution instance
    
        Returns:
            {float} -- fitness score of solution
        """
        fitness = 0
        for index, elem in enumerate(solution._data):
            fitness += self._data['worths'][index] * elem

        return fitness

class KnapsackMultiEvaluator(Evaluator):
    """Knapsack multi-objective evaluator class which enables to compute solution using specific `_data` 

    - `_data['worths1']` stores knapsack objects worths information
    - `_data['worths2']` stores knapsack objects worths information
    """

        Args:
    
        Returns:
        """
        fitness = 0
            fitness += self._data['worths1'][index] * elem

"""Abstract Operator classes
"""
from enum import Enum

class KindOperator(Enum):
    """Enum in order to recognize kind of operators
    """
    MUTATOR = 1
    CROSSOVER = 2

class Operator():
    """Abstract Operator class which enables to update solution applying operator (computation)
    """
    def __init__(self):
        pass

    def apply(self, solution):
        """Apply the current operator transformation

        Args:
        """
        pass

        """Keep into operator reference of the whole algorithm
           The reason is to better manage operator instance

        Args:
            algo: {Algorithm} -- the algorithm reference runned
        """

class Mutation(Operator):
    """Abstract Mutation extend from Operator

        kind: {KindOperator} -- specify the kind of operator
    """
        self._kind = KindOperator.MUTATOR

        raise NotImplementedError

class Crossover(Operator):
    """Abstract crossover extend from Operator

    """
        self._kind = KindOperator.CROSSOVER

"""Crossover implementations for discrete solutions kind
"""
import random
import sys

from ..base import Crossover

class SimpleCrossover(Crossover):
    """Crossover implementation which generated new solution by splitting at mean size best solution and current solution

        kind: {Algorithm} -- specify the kind of operator
    """
        """Create new solution based on best solution found and solution passed as parameter

        Args:
            solution: {Solution} -- the solution to use for generating new solution

        Returns:
            {Solution} -- new generated solution
        """

        size = solution._size

        # copy data of solution
        firstData = solution._data.copy()

        # get best solution from current algorithm
        copy_solution = self._algo._bestSolution.clone()

        splitIndex = int(size / 2)

        if random.uniform(0, 1) > 0.5:
            copy_solution._data[splitIndex:] = firstData[splitIndex:]
        else:
            copy_solution._data[:splitIndex] = firstData[:splitIndex]

        return copy_solution

class RandomSplitCrossover(Crossover):
    """Crossover implementation which generated new solution by randomly splitting best solution and current solution

    """

        Args:

        Returns:
        """

        splitIndex = random.randint(0, size)

        else:

"""Mutation implementations for discrete solution
"""
import sys

from ..base import Mutation

class SimpleMutation(Mutation):
    """Mutation implementation for binary solution, swap two bits randomly from solution

    """
        """Create new solution based on solution passed as parameter

        Args:

        Returns:
        """

        firstCell = 0
        secondCell = 0

        # copy of solution
        copy_solution = solution.clone()

        while firstCell == secondCell:
            firstCell = random.randint(0, size - 1)
            secondCell = random.randint(0, size - 1)

        temp = copy_solution._data[firstCell]

        # swicth values
        copy_solution._data[firstCell] = copy_solution._data[secondCell]
        copy_solution._data[secondCell] = temp

class SimpleBinaryMutation(Mutation):
    """Mutation implementation for binary solution, swap bit randomly from solution

    """

        Args:

        Returns:
        """

        cell = random.randint(0, size - 1)

        if copy_solution._data[cell]:
            copy_solution._data[cell] = 0
        else:
            copy_solution._data[cell] = 1

"""Discrete solution classes implementations
"""

# modules imports
from .base import Solution

class BinarySolution(Solution):
    """
    Binary integer solution class

        data: {ndarray} --  array of binary values
        size: {int} -- size of binary array values
        score: {float} -- fitness score value
    """
    def __init__(self, data, size):
        """
        Initialize binary solution using specific data

        Args:
            data: {ndarray} --  array of binary values
            size: {int} -- size of binary array values
        """
        super().__init__(data, size)

    def random(self, validator):
        """
        Intialize binary array with use of validator to generate valid random solution

        Args:
            validator: {function} -- specific function which validates or not a solution

        Returns:
            {BinarySolution} -- new generated binary solution
        """

        self._data = np.random.randint(2, size=self._size)

        while not self.isValid(validator):
            self._data = np.random.randint(2, size=self._size)

        return self

        return "Binary solution %s" % (self._data)

class CombinatoryIntegerSolution(Solution):
    """
    Combinatory integer solution class

    """
        """

        Args:
        """

        """
        Intialize combinatory integer array with use of validator to generate valid random solution

        Args:

        Returns:
            {CombinatoryIntegerSolution} -- new generated combinatory integer solution
        """

        self._data = np.random.shuffle(np.arange(self._size))

            self._data = np.random.shuffle(np.arange(self._size))

        return "Combinatory integer solution %s" % (self._data)

class IntegerSolution(Solution):
    """
    Integer solution class

    """
        """
        Initialize integer solution using specific data

        Args:
        """

        """
        Intialize integer array with use of validator to generate valid random solution

        Args:

        Returns:
            {IntegerSolution} -- new generated integer solution
        """

        self._data = np.random.randint(self._size, size=self._size)

            self._data = np.random.randint(self._size, size=self._size)

        return "Integer solution %s" % (self._data)