Parcourir la source

update some private attributes as public

Jérôme BUISINE il y a 3 ans
Parent
commit
afadd950ac

+ 10 - 0
README.md

@@ -39,6 +39,16 @@ Flexible discrete optimisation package allowing a quick implementation of your p
 - **Extensible:** the package is open to extension, i.e. it does not partition the user in these developer choices. It can just as well implement continuous optimization problems if needed while making use of the main interaction loop proposed by the package.
 - **Easy Setup:** as a pure Python package distributed is ``pip`` installable and easy to use.
 
+## Target Audience 
+
+This package would meet the expectations of people wishing to: 
+- Solve a complex problem oriented evolutionary algorithm but who do not wish to develop their own framework. They can rely on what the package already proposes but also on its generic and flexible contribution in order to adapt their own content;
+- Conduct research work leading to the rapid modification of meta-heuristics and the interaction of different algorithms. More precisely:
+  - test new combinations of algorithms. Changing algorithms during evaluations, e.g. different local searches;
+  - provide reinforcement learning during searches (e.g. adaptive operator choice strategy).
+  - test new multi-objective methods quickly thanks to the proposed algorithmic hierarchy allowing to easily decompose the multi-objective problem into single-objective sub-problems.
+- Take advantage of a system for launching calculations from a backup in order to avoid any loss in case of unwanted program interruption;
+- Quickly model a problem that is still unknown, i.e. the type of solution and the evaluation function, while taking advantage of the interaction loop proposed by the package.
 
 ## Content
 

+ 2 - 2
docs/source/conf.py

@@ -25,9 +25,9 @@ copyright = '2021, Jérôme BUISINE'
 author = 'Jérôme BUISINE'
 
 # The short X.Y version
-version = '1.0.14'
+version = '1.0.15'
 # The full version, including alpha/beta/rc tags
-release = 'v1.0.14'
+release = 'v1.0.15'
 
 
 # -- General configuration ---------------------------------------------------

+ 14 - 0
docs/source/description.rst

@@ -30,6 +30,20 @@ Flexible discrete optimisation package allowing a quick implementation of your p
 - **Easy Setup:** as a pure Python package distributed is ``pip`` installable and easy to use.
 
 
+Target Audience
+~~~~~~~~~~~~~~~
+
+This package would meet the expectations of people wishing to: 
+
+- Solve a complex problem oriented evolutionary algorithm but who do not wish to develop their own framework. They can rely on what the package already proposes but also on its generic and flexible contribution in order to adapt their own content;
+- Conduct research work leading to the rapid modification of meta-heuristics and the interaction of different algorithms. More precisely:
+
+  - test new combinations of algorithms. Changing algorithms during evaluations, e.g. different local searches;
+  - provide reinforcement learning during searches (e.g. adaptive operator choice strategy).
+  - test new multi-objective methods quickly thanks to the proposed algorithmic hierarchy allowing to easily decompose the multi-objective problem into single-objective sub-problems.
+
+- Take advantage of a system for launching calculations from a backup in order to avoid any loss in case of unwanted program interruption;
+- Quickly model a problem that is still unknown, i.e. the type of solution and the evaluation function, while taking advantage of the interaction loop proposed by the package.
 
 Installation
 ------------

+ 18 - 337
docs/source/documentations.rst

@@ -100,84 +100,6 @@ Inside macop.solutions.base_ module of `Macop`, the ``Solution`` class is availa
 
 Some specific methods are available:
 
-.. code-block:: python
-
-    class Solution():
-
-        def __init__(self, data, size):
-            """
-            Abstract solution class constructor
-            """
-            ...
-
-        def isValid(self, validator):
-            """
-            Use of custom function which checks if a solution is valid or not
-            """
-            ...
-
-        def evaluate(self, evaluator):
-            """
-            Evaluate solution using specific `evaluator`
-            """
-            ...
-
-        @property
-        def fitness(self):
-            """
-            Returns fitness score (by default `score` private attribute)
-            """
-            ...
-
-        @fitness.setter
-        def fitness(self, score):
-            """
-            Set solution score as wished (by default `score` private attribute)
-            """
-            ...
-
-        @property
-        def data(self):
-            """
-            Returns solution data (by default `data` private attribute)
-            """
-            ...
-
-        @data.setter
-        def data(self, data):
-            """
-            Set solution data (by default `data` private attribute)
-            """
-            ...
-
-
-        @property
-        def size(self):
-            """
-            Returns solution size (by default `size` private attribute)
-            """
-            ...
-
-        @size.setter
-        def size(self, size):
-            """
-            Set solution size (by default `size` private attribute)
-            """
-            ...
-
-        @staticmethod
-        def random(size, validator=None):
-            """
-            initialise solution using random data with validator or not
-            """
-            ...
-
-        def clone(self):
-            """
-            Clone the current solution and its data, but without keeping evaluated `_score`
-            """
-            ...
-
 .. caution::
     An important thing here are the ``fitness``, ``size`` and ``data`` functions brought as an editable attribute by the ``@property`` and ``@XXXXX.setter`` decorators. The idea is to allow the user to modify these functions in order to change the expected result of the algorithm regardless of the data to be returned/modified. 
 
@@ -342,31 +264,9 @@ As for the management of solutions, a generic evaluator class macop.evaluators.b
 
 Abstract Evaluator class is used for computing fitness score associated to a solution. To evaluate all the solutions, this class:
 
-- stores into its ``_data`` dictionary attritute required measures when computing a solution
+- stores into its ``data`` initialiser dictionary attritute required measures when computing a solution
 - has a ``compute`` abstract method enable to compute and associate a score to a given solution
-- stores into its ``_algo`` attritute the current algorithm to use (we will talk about algorithm later)
-
-.. code-block: python
-
-    class Evaluator():
-    """
-    Abstract Evaluator class which enables to compute solution using specific `_data` 
-    """
-    def __init__(self, data):
-        self._data = data
-
-    @abstractmethod
-    def compute(self, solution):
-        """
-        Apply the computation of fitness from solution
-        """
-        pass
-
-    def setAlgo(self, algo):
-        """
-        Keep into evaluator reference of the whole algorithm
-        """
-        self._algo = algo
+- stores into its ``algo`` attritute the current algorithm to use (we will talk about algorithm later)
 
 We must therefore now create our own evaluator based on the proposed structure.
 
@@ -376,7 +276,7 @@ Custom evaluator
 To create our own evaluator, we need both:
 
 - data useful for evaluating a solution
-- calculate the score (fitness) associated with the state of the solution from these data. Hence, implement specific ``compute`` method.
+- compute the fitness associated with the state of the solution from these data. Hence, implement specific ``compute`` method.
 
 We will define the ``KnapsackEvaluator`` class, which will therefore allow us to evaluate solutions to our current problem.
 
@@ -389,7 +289,7 @@ We will define the ``KnapsackEvaluator`` class, which will therefore allow us to
 
     class KnapsackEvaluator(Evaluator):
         
-        def compute(solution):
+        def compute(self, solution):
 
             # `_data` contains worths array values of objects
             fitness = 0
@@ -443,60 +343,10 @@ In the discrete optimisation literature, we can categorise operators into two se
 
 Inside **Macop**, operators are also decomposed into these two categories. Inside macop.operators.base_, generic class ``Operator`` enables to manage any kind of operator.
 
-.. code-block:: python
-
-    class Operator():
-        """
-        Abstract Operator class which enables to update solution applying operator (computation)
-        """
-        @abstractmethod
-        def __init__(self):
-            pass
-
-        @abstractmethod
-        def apply(self, solution):
-            """
-            Apply the current operator transformation
-            """
-            pass
-
-        def setAlgo(self, algo):
-            """
-            Keep into operator reference of the whole algorithm
-            """
-            self._algo = algo
-
 Like the evaluator, the operator keeps **track of the algorithm** (using ``setAlgo`` method) to which he will be linked. This will allow better management of the way in which the operator must take into account the state of current data relating to the evolution of research.
 
 ``Mutation`` and ``Crossover`` classes inherite from ``Operator``. An ``apply`` function is required for any new operator.
 
-.. code-block:: python
-
-    class Mutation(Operator):
-        """Abstract Mutation extend from Operator
-
-        Attributes:
-            kind: {:class:`~macop.operators.base.KindOperator`} -- specify the kind of operator
-        """
-        def __init__(self):
-            self._kind = KindOperator.MUTATOR
-
-        def apply(self, solution):
-            raise NotImplementedError
-
-
-    class Crossover(Operator):
-        """Abstract crossover extend from Operator
-
-        Attributes:
-            kind: {:class:`~macop.operators.base.KindOperator`} -- specify the kind of operator
-        """
-        def __init__(self):
-            self._kind = KindOperator.CROSSOVER
-
-        def apply(self, solution1, solution2):
-            raise NotImplementedError
-
 We will now detail these categories of operators and suggest some relative to our problem.
 
 Mutator operator
@@ -597,7 +447,7 @@ The first half of solution 1 will be saved and added to the second half of solut
             splitIndex = int(size / 2)
 
             # copy data of solution 1
-            firstData = solution1._data.copy()
+            firstData = solution1.data.copy()
 
             # copy of solution 2
             copy_solution = solution2.clone()
@@ -656,36 +506,10 @@ Custom policy
 
 In our case, we are not going to exploit a complex enough implementation of a ``policy``. Simply, we will use a random choice of operator.
 
-First, let's take a look of the ``policy`` abstract class available in macop.policies.base_:
-
-.. code-block:: python
-
-    class Policy():
-
-        def __init__(self, operators):
-            self._operators = operators
-
-        @abstractmethod
-        def select(self):
-            """
-            Select specific operator
-            """
-            pass
-
-        def apply(self, solution):
-            """
-            Apply specific operator to create new solution, compute its fitness and return it
-            """
-            ...
-
-        def setAlgo(self, algo):
-            """
-            Keep into policy reference of the whole algorithm
-            """
-            ...
+First, let's take a look of the ``Policy`` abstract class available in macop.policies.base_:
 
 
-``Policy`` instance will have of ``_operators`` attributs in order to keep track of possible operators when selecting one. 
+``Policy`` instance will have of ``operators`` attributes in order to keep track of possible operators when selecting one. 
 Here, in our implementation we only need to specify the ``select`` abstract method. The ``apply`` method will select the next operator and return the new solution.
 
 .. code-block:: python
@@ -702,8 +526,8 @@ Here, in our implementation we only need to specify the ``select`` abstract meth
             Select specific operator
             """
             # choose operator randomly
-            index = random.randint(0, len(self._operators) - 1)
-            return self._operators[index]
+            index = random.randint(0, len(self.operators) - 1)
+            return self.operators[index]
 
 
 We can now use this operator choice policy to update our current solution:
@@ -784,121 +608,6 @@ She is composed of few default attributes:
 - 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)
 
-.. code-block:: python
-
-    class Algorithm():
-
-        def __init__(self,
-                    initialiser,
-                    evaluator,
-                    operators,
-                    policy,
-                    validator,
-                    maximise=True,
-                    parent=None,
-                    verbose=True):
-            ...
-
-        def addCallback(self, callback):
-            """
-            Add new callback to algorithm specifying usefull parameters
-            """
-            ...
-
-        def resume(self):
-            """
-            Resume algorithm using Callback instances
-            """
-            ...
-
-        @property
-        def result(self):
-            """Get the expected result of the current algorithm
-
-            By default the best solution (but can be anything you want)
-            """
-            ...
-
-        @result.setter
-        def result(self, result):
-            """Set current default result of the algorithm
-            """
-            ...
-
-        def getParent(self):
-            """
-            Recursively find the main parent algorithm attached of the current algorithm
-            """
-            ...
-
-        def setParent(self, parent):
-            """
-            Set parent algorithm to current algorithm
-            """
-            ...
-
-        def initRun(self):
-            """
-            initialise the current solution and best solution using the `initialiser` function
-            """
-            ...
-
-        def increaseEvaluation(self):
-            """
-            Increase number of evaluation once a solution is evaluated for each dependant algorithm (parents hierarchy)
-            """
-            ...
-                
-        def getGlobalEvaluation(self):
-            """
-            Get the global number of evaluation (if inner algorithm)
-            """
-            ...
-
-        def getGlobalMaxEvaluation(self):
-            """
-            Get the global max number of evaluation (if inner algorithm)
-            """
-            ...
-
-        def stop(self):
-            """
-            Global stopping criteria (check for parents algorithm hierarchy too)
-            """
-            ...
-
-        def evaluate(self, solution):
-            """
-            Evaluate a solution using evaluator passed when intialize algorithm
-            """
-            ...
-
-        def update(self, solution):
-            """
-            Apply update function to solution using specific `policy`
-            Check if solution is valid after modification and returns it
-            """
-            ...
-
-        def isBetter(self, solution):
-            """
-            Check if solution is better than best found
-            """
-            ...
-
-        def run(self, evaluations):
-            """
-            Run the specific algorithm following number of evaluations to find optima
-            """
-            ...
-
-        def progress(self):
-            """
-            Log progress and apply callbacks if necessary
-            """
-            ...
-
-
 .. caution::
     An important thing here are the ``result`` functions brought as an editable attribute by the ``@property`` and ``@result.setter`` decorators. The idea is to allow the user to modify these functions in order to change the expected result of the algorithm regardless of the data to be returned/modified. 
 
@@ -1171,34 +880,6 @@ For this, the functionality relating to callbacks has been developed.
 Within **Macop**, a callback is a specific instance of macop.callbacks.base.Callback_ that allows you to perform an action of tracing / saving information **every** ``n`` **evaluations** but also reloading information if necessary when restarting an algorithm.
 
 
-.. code-block:: python
-
-    class Callback():
-
-        def __init__(self, every, filepath):
-            ...
-
-        @abstractmethod
-        def run(self):
-            """
-            Check if necessary to do backup based on `every` variable
-            """
-            pass
-
-        @abstractmethod
-        def load(self):
-            """
-            Load last backup line of solution and set algorithm state at this backup
-            """
-            pass
-
-        def setAlgo(self, algo):
-            """
-            Specify the main algorithm instance reference
-            """
-            ...
-
-
 - The ``run`` method will be called during run process of the algo and do backup at each specific number of evaluations. 
 - The ``load`` method will be used to reload the state of the algorithm from the last information saved. All saved data is saved in a file whose name will be specified by the user.
 
@@ -1222,9 +903,9 @@ We are going to create our own Callback instance called ``BasicCheckpoint`` whic
             Check if necessary to do backup based on `every` variable
             """
             # get current best solution
-            solution = self._algo._bestSolution
+            solution = self.algo._bestSolution
 
-            currentEvaluation = self._algo.getGlobalEvaluation()
+            currentEvaluation = self.algo.getGlobalEvaluation()
 
             # backup if necessary every number of evaluations
             if currentEvaluation % self._every == 0:
@@ -1267,21 +948,21 @@ We are going to create our own Callback instance called ``BasicCheckpoint`` whic
                     globalEvaluation = int(data[0])
 
                     # restore number of evaluations
-                    if self._algo.getParent() is not None:
-                        self._algo.getParent()._numberOfEvaluations = globalEvaluation
+                    if self.algo.getParent() is not None:
+                        self.algo.getParent()._numberOfEvaluations = globalEvaluation
                     else:
-                        self._algo._numberOfEvaluations = globalEvaluation
+                        self.algo._numberOfEvaluations = globalEvaluation
 
                     # get best solution data information
                     solution.data = list(map(int, data[1].split(' ')))
 
                     # avoid uninitialised solution
-                    if self._algo._bestSolution is None:
-                        self._algo._bestSolution = self._algo.initialiser()
+                    if self.algo._bestSolution is None:
+                        self.algo._bestSolution = self.algo.initialiser()
 
                     # set to algorithm the lastest obtained best solution
-                    self._algo._bestsolution.data = np.array(solution.data)
-                    self._algo._bestSolution._score = float(data[2])
+                    self.algo._bestsolution.data = np.array(solution.data)
+                    self.algo._bestSolution._score = float(data[2])
 
 
 In this way, it is possible to specify the use of a callback to our algorithm instance:

+ 9 - 9
docs/source/documentations/callbacks.rst

@@ -92,9 +92,9 @@ We are going to create our own Callback instance called ``BasicCheckpoint`` whic
             Check if necessary to do backup based on `every` variable
             """
             # get current best solution
-            solution = self._algo._bestSolution
+            solution = self.algo._bestSolution
 
-            currentEvaluation = self._algo.getGlobalEvaluation()
+            currentEvaluation = self.algo.getGlobalEvaluation()
 
             # backup if necessary every number of evaluations
             if currentEvaluation % self._every == 0:
@@ -137,21 +137,21 @@ We are going to create our own Callback instance called ``BasicCheckpoint`` whic
                     globalEvaluation = int(data[0])
 
                     # restore number of evaluations
-                    if self._algo.getParent() is not None:
-                        self._algo.getParent()._numberOfEvaluations = globalEvaluation
+                    if self.algo.getParent() is not None:
+                        self.algo.getParent()._numberOfEvaluations = globalEvaluation
                     else:
-                        self._algo._numberOfEvaluations = globalEvaluation
+                        self.algo._numberOfEvaluations = globalEvaluation
 
                     # get best solution data information
                     solution.data = list(map(int, data[1].split(' ')))
 
                     # avoid uninitialised solution
-                    if self._algo._bestSolution is None:
-                        self._algo._bestSolution = self._algo.initialiser()
+                    if self.algo._bestSolution is None:
+                        self.algo._bestSolution = self.algo.initialiser()
 
                     # set to algorithm the lastest obtained best solution
-                    self._algo._bestsolution.getdata = ) = np.array(solution.data)
-                    self._algo._bestSolution._score = float(data[2])
+                    self.algo._bestsolution.getdata = ) = np.array(solution.data)
+                    self.algo._bestSolution._score = float(data[2])
 
 
 In this way, it is possible to specify the use of a callback to our algorithm instance:

+ 2 - 2
docs/source/documentations/evaluators.rst

@@ -12,7 +12,7 @@ Abstract Evaluator class is used for computing fitness score associated to a sol
 
 - stores into its ``_data`` dictionary attritute required measures when computing a solution
 - has a ``compute`` abstract method enable to compute and associate a score to a given solution
-- stores into its ``_algo`` attritute the current algorithm to use (we will talk about algorithm later)
+- stores into its ``algo`` attritute the current algorithm to use (we will talk about algorithm later)
 
 .. code-block: python
 
@@ -34,7 +34,7 @@ Abstract Evaluator class is used for computing fitness score associated to a sol
         """
         Keep into evaluator reference of the whole algorithm
         """
-        self._algo = algo
+        self.algo = algo
 
 We must therefore now create our own evaluator based on the proposed structure.
 

+ 1 - 1
docs/source/documentations/operators.rst

@@ -34,7 +34,7 @@ Inside **Macop**, operators are also decomposed into these two categories. Insid
             """
             Keep into operator reference of the whole algorithm
             """
-            self._algo = algo
+            self.algo = algo
 
 Like the evaluator, the operator keeps **track of the algorithm** (using ``setAlgo`` method) to which he will be linked. This will allow better management of the way in which the operator must take into account the state of current data relating to the evolution of research.
 

+ 4 - 4
docs/source/documentations/policies.rst

@@ -32,7 +32,7 @@ First, let's take a look of the ``policy`` abstract class available in ``macop.p
     class Policy():
 
         def __init__(self, operators):
-            self._operators = operators
+            self.operators = operators
 
         @abstractmethod
         def select(self):
@@ -54,7 +54,7 @@ First, let's take a look of the ``policy`` abstract class available in ``macop.p
             ...
 
 
-``Policy`` instance will have of ``_operators`` attributs in order to keep track of possible operators when selecting one. 
+``Policy`` instance will have of ``operators`` attributs in order to keep track of possible operators when selecting one. 
 Here, in our implementation we only need to specify the ``select`` abstract method. The ``apply`` method will select the next operator and return the new solution.
 
 .. code-block:: python
@@ -71,8 +71,8 @@ Here, in our implementation we only need to specify the ``select`` abstract meth
             Select specific operator
             """
             # choose operator randomly
-            index = random.randint(0, len(self._operators) - 1)
-            return self._operators[index]
+            index = random.randint(0, len(self.operators) - 1)
+            return self.operators[index]
 
 
 We can now use this operator choice policy to update our current solution:

+ 19 - 19
macop/algorithms/base.py

@@ -63,7 +63,7 @@ class Algorithm():
         self.policy = policy
 
         # protected members intialization
-        self._operators = operators
+        self.operators = operators
         self._callbacks = []
         self._bestSolution = None
         self._currentSolution = None
@@ -81,7 +81,7 @@ class Algorithm():
         self._verbose = verbose
 
         # track reference of algorihtm into operator (keep an eye into best solution)
-        for operator in self._operators:
+        for operator in self.operators:
             if self._parent is not None:
                 operator.setAlgo(self.getParent())
             else:
@@ -123,13 +123,13 @@ class Algorithm():
             {:class:`~macop.algorithms.base.Algorithm`}: main algorithm set for this algorithm
         """
 
-        current_algorithm = self
+        currentalgorithm = 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
+        while currentalgorithm._parent is not None:
+            parent_alrogithm = currentalgorithm._parent
+            currentalgorithm = currentalgorithm._parent
 
         return parent_alrogithm
 
@@ -181,12 +181,12 @@ class Algorithm():
         Increase number of evaluation once a solution is evaluated for each dependant algorithm (parents hierarchy)
         """
 
-        current_algorithm = self
+        currentalgorithm = self
 
-        while current_algorithm is not None:
+        while currentalgorithm is not None:
 
-            current_algorithm._numberOfEvaluations += 1
-            current_algorithm = current_algorithm._parent
+            currentalgorithm._numberOfEvaluations += 1
+            currentalgorithm = currentalgorithm._parent
 
     def getGlobalEvaluation(self):
         """Get the global number of evaluation (if inner algorithm)
@@ -194,10 +194,10 @@ class Algorithm():
         Returns:
             {int}: current global number of evaluation
         """
-        parent_algorithm = self.getParent()
+        parentalgorithm = self.getParent()
 
-        if parent_algorithm is not None:
-            return parent_algorithm.getGlobalEvaluation()
+        if parentalgorithm is not None:
+            return parentalgorithm.getGlobalEvaluation()
 
         return self._numberOfEvaluations
 
@@ -224,10 +224,10 @@ class Algorithm():
             {int}: current global max number of evaluation
         """
 
-        parent_algorithm = self.getParent()
+        parentalgorithm = self.getParent()
 
-        if parent_algorithm is not None:
-            return parent_algorithm.getGlobalMaxEvaluation()
+        if parentalgorithm is not None:
+            return parentalgorithm.getGlobalMaxEvaluation()
 
         return self._maxEvaluations
 
@@ -235,11 +235,11 @@ class Algorithm():
         """
         Global stopping criteria (check for parents algorithm hierarchy too)
         """
-        parent_algorithm = self.getParent()
+        parentalgorithm = self.getParent()
 
         # based on global stopping creteria or on its own stopping critera
-        if parent_algorithm is not None:
-            return parent_algorithm._numberOfEvaluations >= parent_algorithm._maxEvaluations or self._numberOfEvaluations >= self._maxEvaluations
+        if parentalgorithm is not None:
+            return parentalgorithm._numberOfEvaluations >= parentalgorithm._maxEvaluations or self._numberOfEvaluations >= self._maxEvaluations
 
         return self._numberOfEvaluations >= self._maxEvaluations
 

+ 3 - 3
macop/algorithms/multi.py

@@ -108,7 +108,7 @@ class MOEAD(Algorithm):
         self.evaluator = evaluator
         self.validator = validator
 
-        self._operators = operators
+        self.operators = operators
         self.policy = policy
         self._callbacks = []
 
@@ -126,7 +126,7 @@ class MOEAD(Algorithm):
         self._verbose = verbose
 
         # track reference of algo into operator (keep an eye into best solution)
-        for operator in self._operators:
+        for operator in self.operators:
             operator.setAlgo(self)
 
         # by default track reference for policy
@@ -543,7 +543,7 @@ class MOSubProblem(Algorithm):
             self.initRun()
 
         # new operators list keep track of current sub problem
-        for op in self._operators:
+        for op in self.operators:
             op.setAlgo(self)
 
         for _ in range(evaluations):

+ 2 - 2
macop/callbacks/base.py

@@ -23,7 +23,7 @@ class Callback():
             filepath: {str} -- file path where checkpoints will be saved
         """
 
-        self._algo = None
+        self.algo = None
         self._every = every
         self._filepath = filepath
 
@@ -39,7 +39,7 @@ class Callback():
         Args:
             algo: {:class:`~macop.algorithms.base.Algorithm`} -- main algorithm instance reference
         """
-        self._algo = algo
+        self.algo = algo
 
     @abstractmethod
     def run(self):

+ 30 - 30
macop/callbacks/classicals.py

@@ -25,9 +25,9 @@ class BasicCheckpoint(Callback):
         Check if necessary to do backup based on `every` variable
         """
         # get current best solution
-        solution = self._algo.result
+        solution = self.algo.result
 
-        currentEvaluation = self._algo.getGlobalEvaluation()
+        currentEvaluation = self.algo.getGlobalEvaluation()
 
         # backup if necessary
         if currentEvaluation % self._every == 0:
@@ -70,36 +70,36 @@ class BasicCheckpoint(Callback):
                 # get evaluation  information
                 globalEvaluation = int(data[0])
 
-                if self._algo.getParent() is not None:
-                    self._algo.getParent().setEvaluation(globalEvaluation)
+                if self.algo.getParent() is not None:
+                    self.algo.getParent().setEvaluation(globalEvaluation)
                 else:
-                    self._algo.setEvaluation(globalEvaluation)
+                    self.algo.setEvaluation(globalEvaluation)
 
                 # get best solution data information
                 solution_data = list(map(int, data[1].split(' ')))
 
-                if self._algo.result is None:
-                    self._algo.result = self._algo.initialiser()
+                if self.algo.result is None:
+                    self.algo.result = self.algo.initialiser()
 
-                self._algo.result.data = np.array(solution_data)
-                self._algo.result.fitness = float(data[2])
+                self.algo.result.data = np.array(solution_data)
+                self.algo.result.fitness = float(data[2])
 
-            macop_line(self._algo)
-            macop_text(self._algo,
+            macop_line(self.algo)
+            macop_text(self.algo,
                        f'Checkpoint found from `{self._filepath}` file.')
             macop_text(
-                self._algo,
-                f'Restart algorithm from evaluation {self._algo.getEvaluation()}.'
+                self.algo,
+                f'Restart algorithm from evaluation {self.algo.getEvaluation()}.'
             )
         else:
             macop_text(
-                self._algo,
+                self.algo,
                 'No backup found... Start running algorithm from evaluation 0.'
             )
             logging.info(
                 "Can't load backup... Backup filepath not valid in Checkpoint")
 
-        macop_line(self._algo)
+        macop_line(self.algo)
 
 
 class ContinuousCheckpoint(Callback):
@@ -117,9 +117,9 @@ class ContinuousCheckpoint(Callback):
         Check if necessary to do backup based on `every` variable
         """
         # get current best solution
-        solution = self._algo.result
+        solution = self.algo.result
 
-        currentEvaluation = self._algo.getGlobalEvaluation()
+        currentEvaluation = self.algo.getGlobalEvaluation()
 
         # backup if necessary
         if currentEvaluation % self._every == 0:
@@ -162,33 +162,33 @@ class ContinuousCheckpoint(Callback):
                 # get evaluation  information
                 globalEvaluation = int(data[0])
 
-                if self._algo.getParent() is not None:
-                    self._algo.getParent().setEvaluation(globalEvaluation)
+                if self.algo.getParent() is not None:
+                    self.algo.getParent().setEvaluation(globalEvaluation)
                 else:
-                    self._algo.setEvaluation(globalEvaluation)
+                    self.algo.setEvaluation(globalEvaluation)
 
                 # get best solution data information
                 solution_data = list(map(float, data[1].split(' ')))
 
-                if self._algo.result is None:
-                    self._algo.result = self._algo.initialiser()
+                if self.algo.result is None:
+                    self.algo.result = self.algo.initialiser()
 
-                self._algo.result.data = np.array(solution_data)
-                self._algo.result.fitness = float(data[2])
+                self.algo.result.data = np.array(solution_data)
+                self.algo.result.fitness = float(data[2])
 
-            macop_line(self._algo)
-            macop_text(self._algo,
+            macop_line(self.algo)
+            macop_text(self.algo,
                        f'Checkpoint found from `{self._filepath}` file.')
             macop_text(
-                self._algo,
-                f'Restart algorithm from evaluation {self._algo.getEvaluation()}.'
+                self.algo,
+                f'Restart algorithm from evaluation {self.algo.getEvaluation()}.'
             )
         else:
             macop_text(
-                self._algo,
+                self.algo,
                 'No backup found... Start running algorithm from evaluation 0.'
             )
             logging.info(
                 "Can't load backup... Backup filepath not valid in Checkpoint")
 
-        macop_line(self._algo)
+        macop_line(self.algo)

+ 26 - 26
macop/callbacks/multi.py

@@ -25,9 +25,9 @@ class MultiCheckpoint(Callback):
         Check if necessary to do backup based on `every` variable
         """
         # get current population
-        population = self._algo.population
+        population = self.algo.population
 
-        currentEvaluation = self._algo.getGlobalEvaluation()
+        currentEvaluation = self.algo.getGlobalEvaluation()
 
         # backup if necessary
         if currentEvaluation % self._every == 0:
@@ -48,7 +48,7 @@ class MultiCheckpoint(Callback):
 
                     line = str(currentEvaluation) + ';'
 
-                    for i in range(len(self._algo.evaluator)):
+                    for i in range(len(self.algo.evaluator)):
                         line += str(solution.scores[i]) + ';'
 
                     line += solution_data + ';\n'
@@ -74,42 +74,42 @@ class MultiCheckpoint(Callback):
                         # get evaluation  information
                         globalEvaluation = int(data[0])
 
-                        if self._algo.getParent() is not None:
-                            self._algo.getParent(
+                        if self.algo.getParent() is not None:
+                            self.algo.getParent(
                             )._numberOfEvaluations = globalEvaluation
                         else:
-                            self._algo._numberOfEvaluations = globalEvaluation
+                            self.algo._numberOfEvaluations = globalEvaluation
 
-                    nObjectives = len(self._algo.evaluator)
+                    nObjectives = len(self.algo.evaluator)
                     scores = [float(s) for s in data[1:nObjectives + 1]]
 
                     # get best solution data information
                     current_data = list(map(int, data[-1].split(' ')))
 
                     # initialise and fill with data
-                    self._algo.population[i] = self._algo.initialiser()
-                    self._algo.population[i].data = np.array(current_data)
-                    self._algo.population[i].scores = scores
+                    self.algo.population[i] = self.algo.initialiser()
+                    self.algo.population[i].data = np.array(current_data)
+                    self.algo.population[i].scores = scores
 
-                    self._algo.result.append(self._algo.population[i])
+                    self.algo.result.append(self.algo.population[i])
 
-            macop_line(self._algo)
+            macop_line(self.algo)
             macop_text(
-                self._algo,
+                self.algo,
                 f'Load of available population from `{self._filepath}`')
             macop_text(
-                self._algo,
-                f'Restart algorithm from evaluation {self._algo._numberOfEvaluations}.'
+                self.algo,
+                f'Restart algorithm from evaluation {self.algo._numberOfEvaluations}.'
             )
         else:
             macop_text(
-                self._algo,
+                self.algo,
                 'No backup found... Start running algorithm from evaluation 0.'
             )
             logging.info(
                 "Can't load backup... Backup filepath not valid in Checkpoint")
 
-        macop_line(self._algo)
+        macop_line(self.algo)
 
 
 class ParetoCheckpoint(Callback):
@@ -126,9 +126,9 @@ class ParetoCheckpoint(Callback):
         Check if necessary to do backup based on `every` variable
         """
         # get current population
-        pfPop = self._algo.result
+        pfPop = self.algo.result
 
-        currentEvaluation = self._algo.getGlobalEvaluation()
+        currentEvaluation = self.algo.getGlobalEvaluation()
 
         # backup if necessary
         if currentEvaluation % self._every == 0:
@@ -149,7 +149,7 @@ class ParetoCheckpoint(Callback):
 
                     line = ''
 
-                    for i in range(len(self._algo.evaluator)):
+                    for i in range(len(self.algo.evaluator)):
                         line += str(solution.scores[i]) + ';'
 
                     line += solution_data + ';\n'
@@ -170,24 +170,24 @@ class ParetoCheckpoint(Callback):
 
                     data = line.replace(';\n', '').split(';')
 
-                    nObjectives = len(self._algo.evaluator)
+                    nObjectives = len(self.algo.evaluator)
                     scores = [float(s) for s in data[0:nObjectives]]
 
                     # get best solution data information
                     current_data = list(map(int, data[-1].split(' ')))
 
-                    self._algo.result[i].data = np.array(current_data)
-                    self._algo.result[i].scores = scores
+                    self.algo.result[i].data = np.array(current_data)
+                    self.algo.result[i].scores = scores
 
             macop_text(
-                self._algo,
+                self.algo,
                 f'Load of available pareto front backup from `{ self._filepath}`'
             )
         else:
             macop_text(
-                self._algo,
+                self.algo,
                 'No pareto front found... Start running algorithm with new pareto front population.'
             )
             logging.info("No pareto front backup used...")
 
-        macop_line(self._algo)
+        macop_line(self.algo)

+ 10 - 10
macop/callbacks/policies.py

@@ -26,7 +26,7 @@ class UCBCheckpoint(Callback):
         Check if necessary to do backup based on `every` variable
         """
         # get current population
-        currentEvaluation = self._algo.getGlobalEvaluation()
+        currentEvaluation = self.algo.getGlobalEvaluation()
 
         # backup if necessary
         if currentEvaluation % self._every == 0:
@@ -37,20 +37,20 @@ class UCBCheckpoint(Callback):
 
                 rewardsLine = ''
 
-                for i, r in enumerate(self._algo.policy.rewards):
+                for i, r in enumerate(self.algo.policy.rewards):
                     rewardsLine += str(r)
 
-                    if i != len(self._algo.policy.rewards) - 1:
+                    if i != len(self.algo.policy.rewards) - 1:
                         rewardsLine += ';'
 
                 f.write(rewardsLine + '\n')
 
                 occurrencesLine = ''
 
-                for i, o in enumerate(self._algo.policy.occurences):
+                for i, o in enumerate(self.algo.policy.occurences):
                     occurrencesLine += str(o)
 
-                    if i != len(self._algo.policy.occurences) - 1:
+                    if i != len(self.algo.policy.occurences) - 1:
                         occurrencesLine += ';'
 
                 f.write(occurrencesLine + '\n')
@@ -69,18 +69,18 @@ class UCBCheckpoint(Callback):
                 rewardsLine = lines[0].replace('\n', '')
                 occurrencesLine = lines[1].replace('\n', '')
 
-                self._algo.policy.rewards = [
+                self.algo.policy.rewards = [
                     float(f) for f in rewardsLine.split(';')
                 ]
-                self._algo.policy.occurences = [
+                self.algo.policy.occurences = [
                     float(f) for f in occurrencesLine.split(';')
                 ]
 
             macop_text(
-                self._algo,
+                self.algo,
                 f'Load of available UCB policy data from `{self._filepath}`')
         else:
-            macop_text(self._algo, 'No UCB data found, use default UCB policy')
+            macop_text(self.algo, 'No UCB data found, use default UCB policy')
             logging.info("No UCB data found...")
 
-        macop_line(self._algo)
+        macop_line(self.algo)

+ 1 - 1
macop/evaluators/base.py

@@ -38,4 +38,4 @@ class Evaluator():
         Args:
             algo: {:class:`~macop.algorithms.base.Algorithm`} -- the algorithm reference runned
         """
-        self._algo = algo
+        self.algo = algo

+ 1 - 1
macop/operators/base.py

@@ -37,7 +37,7 @@ class Operator():
         Args:
             algo: {:class:`~macop.algorithms.base.Algorithm`} -- the algorithm reference runned
         """
-        self._algo = algo
+        self.algo = algo
 
 
 class Mutation(Operator):

+ 4 - 4
macop/policies/base.py

@@ -19,7 +19,7 @@ class Policy():
         Args:
             operators: [{}] -- list of operators to use
         """
-        self._operators = operators
+        self.operators = operators
 
     @abstractmethod
     def select(self):
@@ -49,8 +49,8 @@ class Policy():
                      (type(operator).__name__, solution1))
 
         # default value of solution2 is current best solution
-        if solution2 is None and self._algo is not None:
-            solution2 = self._algo.result
+        if solution2 is None and self.algo is not None:
+            solution2 = self.algo.result
 
         # avoid use of crossover if only one solution is passed
         if solution2 is None and operator._kind == KindOperator.CROSSOVER:
@@ -75,4 +75,4 @@ class Policy():
         Args:
             algo: {:class:`~macop.algorithms.base.Algorithm`} -- the algorithm reference runned
         """
-        self._algo = algo
+        self.algo = algo

+ 2 - 2
macop/policies/classicals.py

@@ -35,5 +35,5 @@ class RandomPolicy(Policy):
 
         """
         # choose operator randomly
-        index = random.randint(0, len(self._operators) - 1)
-        return self._operators[index]
+        index = random.randint(0, len(self.operators) - 1)
+        return self.operators[index]

+ 13 - 13
macop/policies/reinforcement.py

@@ -84,13 +84,13 @@ class UCBPolicy(Policy):
         """
 
         # private members
-        self._operators = operators
+        self.operators = operators
         self._C = C
         self._exp_rate = exp_rate
 
         # public members
-        self.rewards = [0. for o in self._operators]
-        self.occurences = [0 for o in self._operators]
+        self.rewards = [0. for o in self.operators]
+        self.occurences = [0 for o in self.operators]
 
     def select(self):
         """Select using Upper Confidence Bound the next operator to use (using acquired rewards)
@@ -104,8 +104,8 @@ class UCBPolicy(Policy):
         # random choice following exploration rate
         if np.random.uniform(0, 1) <= self._exp_rate:
 
-            index = random.choice(range(len(self._operators)))
-            return self._operators[index]
+            index = random.choice(range(len(self.operators)))
+            return self.operators[index]
 
         elif len(indices) == 0:
 
@@ -113,16 +113,16 @@ class UCBPolicy(Policy):
             ucbValues = []
             nVisits = sum(self.occurences)
 
-            for i in range(len(self._operators)):
+            for i in range(len(self.operators)):
 
                 ucbValue = self.rewards[i] + self._C * math.sqrt(
                     math.log(nVisits) / (self.occurences[i] + 0.1))
                 ucbValues.append(ucbValue)
 
-            return self._operators[ucbValues.index(max(ucbValues))]
+            return self.operators[ucbValues.index(max(ucbValues))]
 
         else:
-            return self._operators[random.choice(indices)]
+            return self.operators[random.choice(indices)]
 
     def apply(self, solution1, solution2=None):
         """
@@ -145,8 +145,8 @@ class UCBPolicy(Policy):
                      (type(operator).__name__, solution1))
 
         # default value of solution2 is current best solution
-        if solution2 is None and self._algo is not None:
-            solution2 = self._algo.result
+        if solution2 is None and self.algo is not None:
+            solution2 = self.algo.result
 
         # avoid use of crossover if only one solution is passed
         if solution2 is None and operator._kind == KindOperator.CROSSOVER:
@@ -161,15 +161,15 @@ class UCBPolicy(Policy):
             newSolution = operator.apply(solution1)
 
         # compute fitness of new solution
-        newSolution.evaluate(self._algo.evaluator)
+        newSolution.evaluate(self.algo.evaluator)
 
         # compute fitness improvment rate
-        if self._algo._maximise:
+        if self.algo._maximise:
             fir = (newSolution.fitness - solution1.fitness) / solution1.fitness
         else:
             fir = (solution1.fitness - newSolution.fitness) / solution1.fitness
 
-        operator_index = self._operators.index(operator)
+        operator_index = self.operators.index(operator)
 
         if fir > 0:
             self.rewards[operator_index] += fir

+ 11 - 0
paper.md

@@ -53,6 +53,17 @@ Hence, motivation behind **Macop** is a flexible discrete optimisation package a
 - **Extensible:** the package is open to extension, i.e. it does not partition the user in these developer choices. It can just as well implement continuous optimization problems if needed while making use of the main interaction loop proposed by the package.
 - **Easy Setup:** as a pure Python package distributed is `pip` installable and easy to use.
 
+# Target Audience 
+
+This package would meet the expectations of people wishing to: 
+- Solve a complex problem oriented evolutionary algorithm but who do not wish to develop their own framework. They can rely on what the package already proposes but also on its generic and flexible contribution in order to adapt their own content;
+- Conduct research work leading to the rapid modification of meta-heuristics and the interaction of different algorithms. More precisely:
+  - test new combinations of algorithms. Changing algorithms during evaluations, e.g. different local searches;
+  - provide reinforcement learning during searches (e.g. adaptive operator choice strategy).
+  - test new multi-objective methods quickly thanks to the proposed algorithmic hierarchy allowing to easily decompose the multi-objective problem into single-objective sub-problems.
+- Take advantage of a system for launching calculations from a backup in order to avoid any loss in case of unwanted program interruption;
+- Quickly model a problem that is still unknown, i.e. the type of solution and the evaluation function, while taking advantage of the interaction loop proposed by the package.
+
 # Description
 
 At the beginning of the development of this library, the idea of making it as modular as possible was topical. The library divide into sub-module forms considered to be the most important to build and solve an optimisation problem.

+ 1 - 1
setup.py

@@ -82,7 +82,7 @@ class TestCommand(distutils.command.check.check):
 
 setup(
     name='macop',
-    version='1.0.14',
+    version='1.0.15',
     description='Minimalist And Customisable Optimisation Package',
     long_description=open('README.md').read(),
     long_description_content_type='text/markdown',