Explorar el Código

Update comments and enable use of getter and setter

Jérôme BUISINE hace 3 años
padre
commit
b0b1450ba9

+ 11 - 4
CONTRIBUTING

@@ -15,9 +15,11 @@ Thank you for taking the time to read this guide for the package's contribution.
 
 
 1. [Submission processes](#submission-process)
 1. [Submission processes](#submission-process)
 
 
-    1.2. [Submit an issue](#submit-an-issue)
+    1.1. [Submit an issue](#submit-an-issue)
 
 
-    1.1. [Pull request](#pull-request)
+    1.2. [Pull request](#pull-request)
+
+    1.3. [Seek support](#seek-support)
 
 
 2. [Coding conventions](#coding-conventions)
 2. [Coding conventions](#coding-conventions)
 
 
@@ -62,6 +64,11 @@ To enhance the package, do not hesitate to fix bug or missing feature. To do tha
 
 
 Whatever the problem reported, I will thank you for your contribution to this project. So do not hesitate.
 Whatever the problem reported, I will thank you for your contribution to this project. So do not hesitate.
 
 
+## Seek support
+
+If you have any problem with the use of the package, issue or pull request submission, do not hesitate to let a message to [https://github.com/jbuisine/macop/discussions](https://github.com/jbuisine/macop/discussions). Especially in the question and answer section. 
+
+You can also contact me at the following email address: `contact@jeromebuisine.fr`.
 
 
 # Coding conventions
 # Coding conventions
 
 
@@ -126,11 +133,11 @@ This project uses the [doctest](https://docs.python.org/3/library/doctest.html)
     >>> data = [0, 1, 0, 1, 1]
     >>> data = [0, 1, 0, 1, 1]
     >>> solution = BinarySolution(data, len(data))
     >>> solution = BinarySolution(data, len(data))
     >>> # check data content
     >>> # check data content
-    >>> sum(solution._data) == 3
+    >>> sum(solution.getData()) == 3
     True
     True
     >>> # clone solution
     >>> # clone solution
     >>> solution_copy = solution.clone()
     >>> solution_copy = solution.clone()
-    >>> all(solution_copy._data == solution._data)
+    >>> all(solution_copy._data == solution.getData())
 """
 """
 ```
 ```
 
 

+ 19 - 4
README.md

@@ -24,9 +24,24 @@
     <img src="https://github.com/jbuisine/macop/blob/master/docs/source/_static/documentation/macop_behaviour.png" alt="" width="50%">
     <img src="https://github.com/jbuisine/macop/blob/master/docs/source/_static/documentation/macop_behaviour.png" alt="" width="50%">
 </p>
 </p>
 
 
+
+## Motivation
+
+Flexible discrete optimisation package allowing a quick implementation of your problems. In particular it meets the following needs:
+
+- **Common basis:** the interaction loop during the solution finding process proposed within the package is common to all heuristics. This allows the user to modify only a part of this interaction loop if necessary without rendering the process non-functional.
+- **Hierarchy:** a hierarchical algorithm management system is available, especially when an algorithm needs to manage local searches. This hierarchy remains transparent to the user. The main algorithm will be able to manage and control the process of searching for solutions.
+- **Flexibility:** although the algorithms are dependent on each other, it is possible that their internal management is different. This means that the ways in which solutions are evaluated and updated, for example, may be different.
+- **Abstraction:** thanks to the modular separability of the package, it is quickly possible to implement new problems, solutions representation, way to evaluate, update solutions within the package.
+- **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.
+
+
+## Content
+
 The primary advantage of using Python is that it allows you to dynamically add new members within the new implemented solution or algorithm classes. This of course does not close the possibilities of extension and storage of information within solutions and algorithms. It all depends on the need in question.
 The primary advantage of using Python is that it allows you to dynamically add new members within the new implemented solution or algorithm classes. This of course does not close the possibilities of extension and storage of information within solutions and algorithms. It all depends on the need in question.
 
 
-## In `macop.algorihtms` module:
+### In `macop.algorihtms` module:
 
 
 Both single and multi-objective algorithms have been implemented for demonstration purposes. 
 Both single and multi-objective algorithms have been implemented for demonstration purposes. 
 
 
@@ -36,15 +51,15 @@ The mono-objective Iterated Local Search algorithm which aims to perform local s
 
 
 The main purpose of these developed algorithms is to show the possibilities of operational search algorithm implementations based on the minimalist structure of the library.
 The main purpose of these developed algorithms is to show the possibilities of operational search algorithm implementations based on the minimalist structure of the library.
 
 
-## In `macop.solutions` module:
+### In `macop.solutions` module:
 
 
 Currently, only combinatorial solutions (discrete problem modelisation) are offered, with the well-known problem of the knapsack as an example. Of course, it's easy to add your own representations of solutions. Solutions modeling continuous problems can also be created by the anyone who wants to model his own problem.
 Currently, only combinatorial solutions (discrete problem modelisation) are offered, with the well-known problem of the knapsack as an example. Of course, it's easy to add your own representations of solutions. Solutions modeling continuous problems can also be created by the anyone who wants to model his own problem.
 
 
-## In `macop.operators` and `macop.policies` modules:
+### In `macop.operators` and `macop.policies` modules:
 
 
 A few mutation and crossover operators have been implemented, however, it remains quite simple. What is interesting here is that it is possible to develop one's own strategy for choosing operators for the next evaluation. The available UCBPolicy class proposes this functionality as an example, since it will seek to propose the best operator to apply based on a method known as the Adaptive Operator Selection (AOS) via the use of the Upper Confidence Bound (UCB) algorithm. 
 A few mutation and crossover operators have been implemented, however, it remains quite simple. What is interesting here is that it is possible to develop one's own strategy for choosing operators for the next evaluation. The available UCBPolicy class proposes this functionality as an example, since it will seek to propose the best operator to apply based on a method known as the Adaptive Operator Selection (AOS) via the use of the Upper Confidence Bound (UCB) algorithm. 
 
 
-## In `macop.callbacks` module:
+### In `macop.callbacks` module:
 
 
 The use of callback instance, allows both to do an action every $k$ evaluations of information, but also to reload them once the run of the algorithm is cut. Simply inherit the abstract Callback class and implement the `apply` method to backup and `load` to restore. It is possible to add as many callbacks as required. Such as an example, implemented UCBPolicy has its own callback allowing the instance to reload previously collected statistics and restart using them.
 The use of callback instance, allows both to do an action every $k$ evaluations of information, but also to reload them once the run of the algorithm is cut. Simply inherit the abstract Callback class and implement the `apply` method to backup and `load` to restore. It is possible to add as many callbacks as required. Such as an example, implemented UCBPolicy has its own callback allowing the instance to reload previously collected statistics and restart using them.
 
 

+ 2 - 2
docs/source/conf.py

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

+ 13 - 0
docs/source/description.rst

@@ -17,6 +17,19 @@ Based on its generic behaviour, each **Macop** algorithm runs can be represented
 
 
 The package is strongly oriented on combinatorial optimisation (hence discrete optimisation) but it remains possible to extend for continuous optimisation.
 The package is strongly oriented on combinatorial optimisation (hence discrete optimisation) but it remains possible to extend for continuous optimisation.
 
 
+Motivation
+~~~~~~~~~~
+
+Flexible discrete optimisation package allowing a quick implementation of your problems. In particular it meets the following needs:
+
+- **Common basis:** the interaction loop during the solution finding process proposed within the package is common to all heuristics. This allows the user to modify only a part of this interaction loop if necessary without rendering the process non-functional.
+- **Hierarchy:** a hierarchical algorithm management system is available, especially when an algorithm needs to manage local searches. This hierarchy remains transparent to the user. The main algorithm will be able to manage and control the process of searching for solutions.
+- **Flexibility:** although the algorithms are dependent on each other, it is possible that their internal management is different. This means that the ways in which solutions are evaluated and updated, for example, may be different.
+- **Abstraction:** thanks to the modular separability of the package, it is quickly possible to implement new problems, solutions representation, way to evaluate, update solutions within the package.
+- **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.
+
+
 
 
 Installation
 Installation
 ------------
 ------------

+ 15 - 15
docs/source/documentations/algorithms.rst

@@ -34,7 +34,7 @@ We will pay attention to the different methods of which she is composed. This cl
 
 
 She is composed of few default attributes:
 She is composed of few default attributes:
 
 
-- initializer: {function} -- basic function strategy to initialize solution
+- initialiser: {function} -- basic function strategy to initialise solution
 - evaluator: {Evaluator} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
 - evaluator: {Evaluator} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
 - operators: {[Operator]} -- list of operator to use when launching algorithm
 - operators: {[Operator]} -- list of operator to use when launching algorithm
 - policy: {Policy} -- Policy instance strategy to select operators
 - policy: {Policy} -- Policy instance strategy to select operators
@@ -43,7 +43,7 @@ She is composed of few default attributes:
 - verbose: {bool} -- verbose or not information about the algorithm
 - verbose: {bool} -- verbose or not information about the algorithm
 - currentSolution: {Solution} -- current solution managed for current evaluation comparison
 - currentSolution: {Solution} -- current solution managed for current evaluation comparison
 - bestSolution: {Solution} -- best solution found so far during running algorithm
 - 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
+- callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initialising algorithm
 - parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
 - parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
 
 
 .. code-block:: python
 .. code-block:: python
@@ -51,7 +51,7 @@ She is composed of few default attributes:
     class Algorithm():
     class Algorithm():
 
 
         def __init__(self,
         def __init__(self,
-                    initializer,
+                    initialiser,
                     evaluator,
                     evaluator,
                     operators,
                     operators,
                     policy,
                     policy,
@@ -88,7 +88,7 @@ She is composed of few default attributes:
 
 
         def initRun(self):
         def initRun(self):
             """
             """
-            Initialize the current solution and best solution using the `initialiser` function
+            initialise the current solution and best solution using the `initialiser` function
             """
             """
             ...
             ...
 
 
@@ -155,12 +155,12 @@ The ``evaluate``, ``update`` and ``isBetter`` will be used a lot when looking fo
 In particular the ``update`` function, which will call the ``policy`` instance to generate a new valid solution.
 In particular the ``update`` function, which will call the ``policy`` instance to generate a new valid solution.
 ``isBetter`` method is also overloadable especially if the algorithm does not take any more into account than a single solution to be verified (verification via a population for example).
 ``isBetter`` method is also overloadable especially if the algorithm does not take any more into account than a single solution to be verified (verification via a population for example).
 
 
-The ``initRun`` method specify the way you intialise your algorithm (``bestSolution`` and ``currentSolution`` as example) if algorithm not already initialized.
+The ``initRun`` method specify the way you intialise your algorithm (``bestSolution`` and ``currentSolution`` as example) if algorithm not already initialised.
 
 
 .. note:: 
 .. note:: 
     The ``initRun`` method can also be used for intialise population of solutions instead of only one best solution, if you want to manage a genetic algorithm.
     The ``initRun`` method can also be used for intialise population of solutions instead of only one best solution, if you want to manage a genetic algorithm.
 
 
-Most important part is the ``run`` method. Into abstract, the ``run`` method only initialized the current number of evaluation for the algorithm based on the parent algorithm if we are into inner algorithm.
+Most important part is the ``run`` method. Into abstract, the ``run`` method only initialised the current number of evaluation for the algorithm based on the parent algorithm if we are into inner algorithm.
 It is always **mandatory** to call the parent class ``run`` method using ``super().run(evaluations)``. Then, using ``evaluations`` parameter which is the number of evaluations budget to run, we can process or continue to find solutions into search space.
 It is always **mandatory** to call the parent class ``run`` method using ``super().run(evaluations)``. Then, using ``evaluations`` parameter which is the number of evaluations budget to run, we can process or continue to find solutions into search space.
 
 
 .. warning::
 .. warning::
@@ -190,10 +190,10 @@ Let's implement an algorithm well known under the name of hill climber best impr
             Run a local search algorithm
             Run a local search algorithm
             """
             """
 
 
-            # by default use of mother method to initialize variables
+            # by default use of mother method to initialise variables
             super().run(evaluations)
             super().run(evaluations)
 
 
-            # initialize current solution and best solution
+            # initialise current solution and best solution
             self.initRun()
             self.initRun()
 
 
             solutionSize = self._currentSolution._size
             solutionSize = self._currentSolution._size
@@ -238,7 +238,7 @@ We will also need to define a **solution initialisation function** so that the a
     evaluator = KnapsackEvaluator(data={'worths': elements_score})
     evaluator = KnapsackEvaluator(data={'worths': elements_score})
 
 
     # valid instance using lambda
     # valid instance using lambda
-    validator = lambda solution: sum([ elements_weight[i] * solution._data[i] for i in range(len(solution._data))]) <= 15
+    validator = lambda solution: sum([ elements_weight[i] * solution.getData()[i] for i in range(len(solution.getData()))]) <= 15
     
     
     # initialiser instance using lambda with default param value
     # initialiser instance using lambda with default param value
     initialiser = lambda x=5: BinarySolution.random(x, validator)
     initialiser = lambda x=5: BinarySolution.random(x, validator)
@@ -287,7 +287,7 @@ Let's called this new algorithm ``IteratedLocalSearch``:
     class IteratedLocalSearch(Algorithm):
     class IteratedLocalSearch(Algorithm):
         
         
         def __init__(self,
         def __init__(self,
-                    initializer,
+                    initialiser,
                     evaluator,
                     evaluator,
                     operators,
                     operators,
                     policy,
                     policy,
@@ -297,7 +297,7 @@ Let's called this new algorithm ``IteratedLocalSearch``:
                     parent=None,
                     parent=None,
                     verbose=True):
                     verbose=True):
             
             
-            super().__init__(initializer, evaluator, operators, policy, validator, maximise, parent, verbose)
+            super().__init__(initialiser, evaluator, operators, policy, validator, maximise, parent, verbose)
 
 
             # specific local search associated with current algorithm
             # specific local search associated with current algorithm
             self._localSearch = localSearch
             self._localSearch = localSearch
@@ -311,10 +311,10 @@ Let's called this new algorithm ``IteratedLocalSearch``:
             Run the iterated local search algorithm using local search
             Run the iterated local search algorithm using local search
             """
             """
 
 
-            # by default use of mother method to initialize variables
+            # by default use of mother method to initialise variables
             super().run(evaluations)
             super().run(evaluations)
 
 
-            # initialize current solution
+            # initialise current solution
             self.initRun()
             self.initRun()
 
 
             # local search algorithm implementation
             # local search algorithm implementation
@@ -348,7 +348,7 @@ Then, we use this local search in our ``run`` method to allow a better search fo
     evaluator = KnapsackEvaluator(data={'worths': elements_score})
     evaluator = KnapsackEvaluator(data={'worths': elements_score})
 
 
     # valid instance using lambda
     # valid instance using lambda
-    validator = lambda solution: sum([ elements_weight[i] * solution._data[i] for i in range(len(solution._data))]) <= 15
+    validator = lambda solution: sum([ elements_weight[i] * solution.getData()[i] for i in range(len(solution.getData()))]) <= 15
     
     
     # initialiser instance using lambda with default param value
     # initialiser instance using lambda with default param value
     initialiser = lambda x=5: BinarySolution.random(x, validator)
     initialiser = lambda x=5: BinarySolution.random(x, validator)
@@ -361,7 +361,7 @@ Then, we use this local search in our ``run`` method to allow a better search fo
     
     
     # maximizing algorithm (relative to knapsack problem)
     # maximizing algorithm (relative to knapsack problem)
     localSearch = HillClimberBestImprovment(initialiser, evaluator, operators, policy, validator, maximise=True, verbose=False)
     localSearch = HillClimberBestImprovment(initialiser, evaluator, operators, policy, validator, maximise=True, verbose=False)
-    algo = IteratedLocalSearch(initializer, evaluator, operators, policy, validator, localSearch=local_search, maximise=True, verbose=False)
+    algo = IteratedLocalSearch(initialiser, evaluator, operators, policy, validator, localSearch=local_search, maximise=True, verbose=False)
 
 
     # run the algorithm using local search and get solution found 
     # run the algorithm using local search and get solution found 
     solution = algo.run(evaluations=100, ls_evaluations=10)
     solution = algo.run(evaluations=100, ls_evaluations=10)

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

@@ -101,9 +101,9 @@ We are going to create our own Callback instance called ``BasicCheckpoint`` whic
 
 
                 # create specific line with solution data
                 # create specific line with solution data
                 solutionData = ""
                 solutionData = ""
-                solutionSize = len(solution._data)
+                solutionSize = len(solution.getData())
 
 
-                for index, val in enumerate(solution._data):
+                for index, val in enumerate(solution.getData()):
                     solutionData += str(val)
                     solutionData += str(val)
 
 
                     if index < solutionSize - 1:
                     if index < solutionSize - 1:
@@ -145,12 +145,12 @@ We are going to create our own Callback instance called ``BasicCheckpoint`` whic
                     # get best solution data information
                     # get best solution data information
                     solutionData = list(map(int, data[1].split(' ')))
                     solutionData = list(map(int, data[1].split(' ')))
 
 
-                    # avoid uninitialized solution
+                    # avoid uninitialised solution
                     if self._algo._bestSolution is None:
                     if self._algo._bestSolution is None:
-                        self._algo._bestSolution = self._algo._initializer()
+                        self._algo._bestSolution = self._algo.initialiser()
 
 
                     # set to algorithm the lastest obtained best solution
                     # set to algorithm the lastest obtained best solution
-                    self._algo._bestSolution._data = np.array(solutionData)
+                    self._algo._bestsolution.getData() = np.array(solutionData)
                     self._algo._bestSolution._score = float(data[2])
                     self._algo._bestSolution._score = float(data[2])
 
 
 
 
@@ -203,10 +203,10 @@ If we want to exploit this functionality, then we will need to exploit them with
             Run the iterated local search algorithm using local search
             Run the iterated local search algorithm using local search
             """
             """
 
 
-            # by default use of mother method to initialize variables
+            # by default use of mother method to initialise variables
             super().run(evaluations)
             super().run(evaluations)
 
 
-            # initialize current solution
+            # initialise current solution
             self.initRun()
             self.initRun()
 
 
             # restart using callbacks backup list
             # restart using callbacks backup list

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

@@ -61,13 +61,13 @@ We will define the ``KnapsackEvaluator`` class, which will therefore allow us to
 
 
             # `_data` contains worths array values of objects
             # `_data` contains worths array values of objects
             fitness = 0
             fitness = 0
-            for index, elem in enumerate(solution._data):
+            for index, elem in enumerate(solution.getData()):
                 fitness += self._data['worths'][index] * elem
                 fitness += self._data['worths'][index] * elem
 
 
             return fitness
             return fitness
 
 
 
 
-It is now possible to initialize our new evaluator with specific data of our problem instance:
+It is now possible to initialise our new evaluator with specific data of our problem instance:
 
 
 .. code-block:: python
 .. code-block:: python
 
 

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

@@ -105,10 +105,10 @@ The modification applied here is just a bit swapped. Let's define the ``SimpleBi
             copy_solution = solution.clone()
             copy_solution = solution.clone()
 
 
             # swicth values
             # swicth values
-            if copy_solution._data[cell]:
-                copy_solution._data[cell] = 0
+            if copy_solution.getData()[cell]:
+                copy_solution.getData()[cell] = 0
             else:
             else:
-                copy_solution._data[cell] = 1
+                copy_solution.getData()[cell] = 1
 
 
             # return the new obtained solution
             # return the new obtained solution
             return copy_solution
             return copy_solution
@@ -172,7 +172,7 @@ The first half of solution 1 will be saved and added to the second half of solut
             # copy of solution 2
             # copy of solution 2
             copy_solution = solution2.clone()
             copy_solution = solution2.clone()
 
 
-            copy_solution._data[splitIndex:] = firstData[splitIndex:]
+            copy_solution.getData()[splitIndex:] = firstData[splitIndex:]
 
 
             return copy_solution
             return copy_solution
 
 

+ 3 - 3
docs/source/documentations/solutions.rst

@@ -47,7 +47,7 @@ Some specific methods are available:
         @staticmethod
         @staticmethod
         def random(size, validator=None):
         def random(size, validator=None):
             """
             """
-            Initialize solution using random data with validator or not
+            initialise solution using random data with validator or not
             """
             """
             ...
             ...
 
 
@@ -60,7 +60,7 @@ Some specific methods are available:
 
 
 From these basic methods, it is possible to manage a representation of a solution to our problem. 
 From these basic methods, it is possible to manage a representation of a solution to our problem. 
 
 
-Allowing to initialize it randomly or not (using constructor or ``random`` method), to evaluate it (``evaluate`` method) and to check some constraints of validation of the solution (``isValid`` method).
+Allowing to initialise it randomly or not (using constructor or ``random`` method), to evaluate it (``evaluate`` method) and to check some constraints of validation of the solution (``isValid`` method).
 
 
 .. note::
 .. note::
     Only one of these methods needs specification if we create our own type of solution. This is the ``random`` method, which depends on the need of the problem.
     Only one of these methods needs specification if we create our own type of solution. This is the ``random`` method, which depends on the need of the problem.
@@ -113,7 +113,7 @@ We will also have to implement the ``random`` method to create a new random solu
 
 
             # create binary array of specific size using numpy random module
             # create binary array of specific size using numpy random module
             data = np.random.randint(2, size=size)
             data = np.random.randint(2, size=size)
-            # initialize new solution using constructor
+            # initialise new solution using constructor
             solution = BinarySolution(data, size)
             solution = BinarySolution(data, size)
 
 
             # check if validator is set
             # check if validator is set

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

@@ -35,7 +35,7 @@ To avoid taking into account invalid solutions, we can define our function which
 
 
         for i, w in enumerate(elements_weight):
         for i, w in enumerate(elements_weight):
             # add weight if current object is set to 1
             # add weight if current object is set to 1
-            weight_sum += w * solution._data[i]
+            weight_sum += w * solution.getData()[i]
         
         
         # validation condition
         # validation condition
         return weight_sum <= 15
         return weight_sum <= 15

+ 3 - 3
docs/source/examples/qap/implementation.rst

@@ -63,8 +63,8 @@ So we are going to create a class that will inherit from the abstract class ``ma
             {float} -- fitness score of solution
             {float} -- fitness score of solution
         """
         """
         fitness = 0
         fitness = 0
-        for index_i, val_i in enumerate(solution._data):
-            for index_j, val_j in enumerate(solution._data):
+        for index_i, val_i in enumerate(solution.getData()):
+            for index_j, val_j in enumerate(solution.getData()):
                 fitness += self._data['F'][index_i, index_j] * self._data['D'][val_i, val_j]
                 fitness += self._data['F'][index_i, index_j] * self._data['D'][val_i, val_j]
 
 
         return fitness
         return fitness
@@ -105,7 +105,7 @@ If you are uncomfortable with some of the elements in the code that will follow,
 
 
     # default validator (check the consistency of our data, i.e. only unique element)
     # default validator (check the consistency of our data, i.e. only unique element)
     def validator(solution):
     def validator(solution):
-        if len(list(solution._data)) > len(set(list(solution._data))):
+        if len(list(solution.getData())) > len(set(list(solution.getData()))):
             print("not valid")
             print("not valid")
             return False
             return False
         return True
         return True

+ 2 - 2
docs/source/examples/ubqp/implementation.rst

@@ -60,8 +60,8 @@ So we are going to create a class that will inherit from the abstract class ``ma
             {float} -- fitness score of solution
             {float} -- fitness score of solution
         """
         """
         fitness = 0
         fitness = 0
-        for index_i, val_i in enumerate(solution._data):
-            for index_j, val_j in enumerate(solution._data):
+        for index_i, val_i in enumerate(solution.getData()):
+            for index_j, val_j in enumerate(solution.getData()):
                 fitness += self._data['Q'][index_i, index_j] * val_i * val_j
                 fitness += self._data['Q'][index_i, index_j] * val_i * val_j
 
 
         return fitness
         return fitness

+ 19 - 2
docs/source/index.rst

@@ -6,13 +6,16 @@ Minimalist And Customisable Optimisation Package
    :align: center
    :align: center
 
 
 What's **Macop** ?
 What's **Macop** ?
-------------------
+~~~~~~~~~~~~~~~~~~
 
 
 **Macop** is a discrete optimisation Python package which not which doesn't implement every algorithm in the literature but provides the ability to quickly develop and test your own algorithm and strategies. The main objective of this package is to provide maximum flexibility, which allows for easy experimentation in implementation.
 **Macop** is a discrete optimisation Python package which not which doesn't implement every algorithm in the literature but provides the ability to quickly develop and test your own algorithm and strategies. The main objective of this package is to provide maximum flexibility, which allows for easy experimentation in implementation.
 
 
+
+Contents
+~~~~~~~~
+
 .. toctree::
 .. toctree::
    :maxdepth: 1
    :maxdepth: 1
-   :caption: Contents:
 
 
    description
    description
 
 
@@ -24,6 +27,20 @@ What's **Macop** ?
 
 
    contributing
    contributing
 
 
+
+Motivation
+~~~~~~~~~~
+
+Flexible discrete optimisation package allowing a quick implementation of your problems. In particular it meets the following needs:
+
+- **Common basis:** the interaction loop during the solution finding process proposed within the package is common to all heuristics. This allows the user to modify only a part of this interaction loop if necessary without rendering the process non-functional.
+- **Hierarchy:** a hierarchical algorithm management system is available, especially when an algorithm needs to manage local searches. This hierarchy remains transparent to the user. The main algorithm will be able to manage and control the process of searching for solutions.
+- **Flexibility:** although the algorithms are dependent on each other, it is possible that their internal management is different. This means that the ways in which solutions are evaluated and updated, for example, may be different.
+- **Abstraction:** thanks to the modular separability of the package, it is quickly possible to implement new problems, solutions representation, way to evaluate, update solutions within the package.
+- **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.
+
+
 Indices and tables
 Indices and tables
 ------------------
 ------------------
 
 

+ 1 - 1
examples/knapsackExample.py

@@ -33,7 +33,7 @@ elements_weight = [ random.randint(2, 5) for _ in range(30) ]
 def knapsackWeight(solution):
 def knapsackWeight(solution):
 
 
     weight_sum = 0
     weight_sum = 0
-    for index, elem in enumerate(solution._data):
+    for index, elem in enumerate(solution.getData()):
         weight_sum += elements_weight[index] * elem
         weight_sum += elements_weight[index] * elem
 
 
     return weight_sum
     return weight_sum

+ 1 - 1
examples/knapsackMultiExample.py

@@ -35,7 +35,7 @@ elements_weight = [ random.randint(90, 100) for _ in range(500) ]
 def knapsackWeight(solution):
 def knapsackWeight(solution):
 
 
     weight_sum = 0
     weight_sum = 0
-    for index, elem in enumerate(solution._data):
+    for index, elem in enumerate(solution.getData()):
         weight_sum += elements_weight[index] * elem
         weight_sum += elements_weight[index] * elem
 
 
     return weight_sum
     return weight_sum

+ 1 - 1
examples/qapExample.py

@@ -33,7 +33,7 @@ filepath = "data/checkpoints_qap.csv"
 
 
 # default validator
 # default validator
 def validator(solution):
 def validator(solution):
-    if len(list(solution._data)) > len(set(list(solution._data))):
+    if len(list(solution.getData())) > len(set(list(solution.getData()))):
         print("not valid")
         print("not valid")
         return False
         return False
     return True
     return True

+ 65 - 17
macop/algorithms/base.py

@@ -22,7 +22,7 @@ class Algorithm():
 
 
 
 
     Attributes:
     Attributes:
-        initializer: {function} -- basic function strategy to initialize solution
+        initialiser: {function} -- basic function strategy to initialise solution
         evaluator: {Evaluator} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
         evaluator: {Evaluator} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy implementation strategy to select operators
         policy: {Policy} -- Policy implementation strategy to select operators
@@ -31,11 +31,11 @@ class Algorithm():
         verbose: {bool} -- verbose or not information about the algorithm
         verbose: {bool} -- verbose or not information about the algorithm
         currentSolution: {Solution} -- current solution managed for current evaluation comparison
         currentSolution: {Solution} -- current solution managed for current evaluation comparison
         bestSolution: {Solution} -- best solution found so far during running algorithm
         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
+        callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initialising algorithm
         parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
         parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
     """
     """
     def __init__(self,
     def __init__(self,
-                 initializer,
+                 initialiser,
                  evaluator,
                  evaluator,
                  operators,
                  operators,
                  policy,
                  policy,
@@ -43,13 +43,27 @@ class Algorithm():
                  maximise=True,
                  maximise=True,
                  parent=None,
                  parent=None,
                  verbose=True):
                  verbose=True):
+        """Basic Algorithm initialisation
+
+        Args:
+            initialiser: {function} -- basic function strategy to initialise solution
+            evaluator: {Evaluator} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
+            operators: {[Operator]} -- list of operator to use when launching algorithm
+            policy: {Policy} -- Policy implementation strategy to select operators
+            validator: {function} -- basic function to check if solution is valid or not under some constraints
+            maximise: {bool} -- specify kind of optimisation problem 
+            parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
+            verbose: {bool} -- verbose or not information about the algorithm
+        """
+
+        # public members intialization
+        self.initialiser = initialiser
+        self.evaluator = evaluator
+        self.validator = validator
+        self.policy = policy
 
 
         # protected members intialization
         # protected members intialization
-        self._initializer = initializer
-        self._evaluator = evaluator
         self._operators = operators
         self._operators = operators
-        self._policy = policy
-        self._validator = validator
         self._callbacks = []
         self._callbacks = []
         self._bestSolution = None
         self._bestSolution = None
         self._currentSolution = None
         self._currentSolution = None
@@ -75,9 +89,9 @@ class Algorithm():
 
 
         # also track reference for policy
         # also track reference for policy
         if self._parent is not None:
         if self._parent is not None:
-            self._policy.setAlgo(self.getParent())
+            self.policy.setAlgo(self.getParent())
         else:
         else:
-            self._policy.setAlgo(self)
+            self.policy.setAlgo(self)
 
 
     def addCallback(self, callback):
     def addCallback(self, callback):
         """Add new callback to algorithm specifying usefull parameters
         """Add new callback to algorithm specifying usefull parameters
@@ -127,15 +141,33 @@ class Algorithm():
         """
         """
         self._parent = parent
         self._parent = parent
 
 
+    def getResult(self):
+        """Get the expected result of the current algorithm
+
+        By default the best solution (but can be anything you want)
+
+        Returns:
+            {object} -- expected result data of the current algorithm
+        """
+        return self._bestSolution
+
+    def setDefaultResult(self, result):
+        """Set current default result of the algorithm
+
+        Args:
+            result: {object} -- expected result data of the current algorithm
+        """
+        self._bestSolution = result
+
     def initRun(self):
     def initRun(self):
         """
         """
-        Initialize the current solution and best solution using the `initialiser` function
+        initialise the current solution and best solution using the `initialiser` function
         """
         """
 
 
-        self._currentSolution = self._initializer()
+        self._currentSolution = self.initialiser()
 
 
         # evaluate current solution
         # evaluate current solution
-        self._currentSolution.evaluate(self._evaluator)
+        self._currentSolution.evaluate(self.evaluator)
         self.increaseEvaluation()
         self.increaseEvaluation()
 
 
         # keep in memory best known solution (current solution)
         # keep in memory best known solution (current solution)
@@ -167,6 +199,22 @@ class Algorithm():
 
 
         return self._numberOfEvaluations
         return self._numberOfEvaluations
 
 
+    def getEvaluation(self):
+        """Get the current number of evaluation
+
+        Returns:
+            {int} -- current number of evaluation
+        """
+        return self._numberOfEvaluations
+
+    def setEvaluation(self, evaluations):
+        """Set the current number of evaluation
+
+        Args:
+            evaluations: {int} -- current expected number of evaluation
+        """
+        self._numberOfEvaluations = evaluations
+
     def getGlobalMaxEvaluation(self):
     def getGlobalMaxEvaluation(self):
         """Get the global max number of evaluation (if inner algorithm)
         """Get the global max number of evaluation (if inner algorithm)
 
 
@@ -206,7 +254,7 @@ class Algorithm():
         Note: 
         Note: 
             if multi-objective problem this method can be updated using array of `evaluator`
             if multi-objective problem this method can be updated using array of `evaluator`
         """
         """
-        return solution.evaluate(self._evaluator)
+        return solution.evaluate(self.evaluator)
 
 
     def update(self, solution):
     def update(self, solution):
         """
         """
@@ -221,13 +269,13 @@ class Algorithm():
         """
         """
 
 
         # two parameters are sent if specific crossover solution are wished
         # two parameters are sent if specific crossover solution are wished
-        sol = self._policy.apply(solution)
+        sol = self.policy.apply(solution)
 
 
         # compute fitness of new solution if not already computed
         # compute fitness of new solution if not already computed
         if sol._score is None:
         if sol._score is None:
-            sol.evaluate(self._evaluator)
+            sol.evaluate(self.evaluator)
 
 
-        if (sol.isValid(self._validator)):
+        if (sol.isValid(self.validator)):
             return sol
             return sol
         else:
         else:
             logging.info("-- New solution is not valid %s" % sol)
             logging.info("-- New solution is not valid %s" % sol)
@@ -246,7 +294,7 @@ class Algorithm():
         Returns:
         Returns:
             {bool} -- `True` if better
             {bool} -- `True` if better
         """
         """
-        if not solution.isValid(self._validator):
+        if not solution.isValid(self.validator):
             return False
             return False
 
 
         # depending of problem to solve (maximizing or minimizing)
         # depending of problem to solve (maximizing or minimizing)

+ 40 - 24
macop/algorithms/mono.py

@@ -16,7 +16,7 @@ class HillClimberFirstImprovment(Algorithm):
     - And do these steps until a number of evaluation (stopping criterion) is reached.
     - And do these steps until a number of evaluation (stopping criterion) is reached.
 
 
     Attributes:
     Attributes:
-        initalizer: {function} -- basic function strategy to initialize solution
+        initalizer: {function} -- basic function strategy to initialise solution
         evaluator: {Evaluator} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
         evaluator: {Evaluator} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy class implementation strategy to select operators
         policy: {Policy} -- Policy class implementation strategy to select operators
@@ -24,7 +24,8 @@ class HillClimberFirstImprovment(Algorithm):
         maximise: {bool} -- specify kind of optimisation problem 
         maximise: {bool} -- specify kind of optimisation problem 
         currentSolution: {Solution} -- current solution managed for current evaluation
         currentSolution: {Solution} -- current solution managed for current evaluation
         bestSolution: {Solution} -- best solution found so far during running algorithm
         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
+        callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initialising algorithm
+        parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
     
     
     Example:
     Example:
 
 
@@ -46,12 +47,12 @@ class HillClimberFirstImprovment(Algorithm):
     >>> # validator specification (based on weights of each objects)
     >>> # validator specification (based on weights of each objects)
     >>> weights = [ random.randint(5, 30) for i in range(problem_size) ]
     >>> weights = [ random.randint(5, 30) for i in range(problem_size) ]
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=20: BinarySolution.random(x, validator)
+    >>> # initialiser function with lambda function
+    >>> initialiser = lambda x=20: BinarySolution.random(x, validator)
     >>> # operators list with crossover and mutation
     >>> # operators list with crossover and mutation
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> policy = RandomPolicy(operators)
     >>> policy = RandomPolicy(operators)
-    >>> algo = HillClimberFirstImprovment(initializer, evaluator, operators, policy, validator, maximise=True, verbose=False)
+    >>> algo = HillClimberFirstImprovment(initialiser, evaluator, operators, policy, validator, maximise=True, verbose=False)
     >>> # run the algorithm
     >>> # run the algorithm
     >>> solution = algo.run(100)
     >>> solution = algo.run(100)
     >>> solution._score
     >>> solution._score
@@ -68,10 +69,10 @@ class HillClimberFirstImprovment(Algorithm):
             {Solution} -- best solution found
             {Solution} -- best solution found
         """
         """
 
 
-        # by default use of mother method to initialize variables
+        # by default use of mother method to initialise variables
         super().run(evaluations)
         super().run(evaluations)
 
 
-        # initialize current solution and best solution
+        # initialise current solution and best solution
         self.initRun()
         self.initRun()
 
 
         solutionSize = self._currentSolution._size
         solutionSize = self._currentSolution._size
@@ -119,7 +120,7 @@ class HillClimberBestImprovment(Algorithm):
     - And do these steps until a number of evaluation (stopping criterion) is reached.
     - And do these steps until a number of evaluation (stopping criterion) is reached.
 
 
     Attributes:
     Attributes:
-        initalizer: {function} -- basic function strategy to initialize solution
+        initalizer: {function} -- basic function strategy to initialise solution
         evaluator: {Evaluator} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
         evaluator: {Evaluator} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy class implementation strategy to select operators
         policy: {Policy} -- Policy class implementation strategy to select operators
@@ -127,7 +128,8 @@ class HillClimberBestImprovment(Algorithm):
         maximise: {bool} -- specify kind of optimisation problem 
         maximise: {bool} -- specify kind of optimisation problem 
         currentSolution: {Solution} -- current solution managed for current evaluation
         currentSolution: {Solution} -- current solution managed for current evaluation
         bestSolution: {Solution} -- best solution found so far during running algorithm
         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
+        callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initialising algorithm
+        parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
     
     
     Example:
     Example:
 
 
@@ -149,12 +151,12 @@ class HillClimberBestImprovment(Algorithm):
     >>> # validator specification (based on weights of each objects)
     >>> # validator specification (based on weights of each objects)
     >>> weights = [ random.randint(5, 30) for i in range(problem_size) ]
     >>> weights = [ random.randint(5, 30) for i in range(problem_size) ]
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=20: BinarySolution.random(x, validator)
+    >>> # initialiser function with lambda function
+    >>> initialiser = lambda x=20: BinarySolution.random(x, validator)
     >>> # operators list with crossover and mutation
     >>> # operators list with crossover and mutation
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> policy = RandomPolicy(operators)
     >>> policy = RandomPolicy(operators)
-    >>> algo = HillClimberBestImprovment(initializer, evaluator, operators, policy, validator, maximise=True, verbose=False)
+    >>> algo = HillClimberBestImprovment(initialiser, evaluator, operators, policy, validator, maximise=True, verbose=False)
     >>> # run the algorithm
     >>> # run the algorithm
     >>> solution = algo.run(100)
     >>> solution = algo.run(100)
     >>> solution._score
     >>> solution._score
@@ -171,10 +173,10 @@ class HillClimberBestImprovment(Algorithm):
             {Solution} -- best solution found
             {Solution} -- best solution found
         """
         """
 
 
-        # by default use of mother method to initialize variables
+        # by default use of mother method to initialise variables
         super().run(evaluations)
         super().run(evaluations)
 
 
-        # initialize current solution and best solution
+        # initialise current solution and best solution
         self.initRun()
         self.initRun()
 
 
         solutionSize = self._currentSolution._size
         solutionSize = self._currentSolution._size
@@ -222,7 +224,7 @@ class IteratedLocalSearch(Algorithm):
     - Restart this process until stopping critirion (number of expected evaluations).
     - Restart this process until stopping critirion (number of expected evaluations).
 
 
     Attributes:
     Attributes:
-        initalizer: {function} -- basic function strategy to initialize solution
+        initalizer: {function} -- basic function strategy to initialise solution
         evaluator: {function} -- basic function in order to obtained fitness (mono or multiple objectives)
         evaluator: {function} -- basic function in order to obtained fitness (mono or multiple objectives)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy class implementation strategy to select operators
         policy: {Policy} -- Policy class implementation strategy to select operators
@@ -231,7 +233,8 @@ class IteratedLocalSearch(Algorithm):
         currentSolution: {Solution} -- current solution managed for current evaluation
         currentSolution: {Solution} -- current solution managed for current evaluation
         bestSolution: {Solution} -- best solution found so far during running algorithm
         bestSolution: {Solution} -- best solution found so far during running algorithm
         localSearch: {Algorithm} -- current local search into ILS
         localSearch: {Algorithm} -- current local search into ILS
-        callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initializing algorithm
+        callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initialising algorithm
+        parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
     
     
     Example:
     Example:
 
 
@@ -254,20 +257,20 @@ class IteratedLocalSearch(Algorithm):
     >>> # validator specification (based on weights of each objects)
     >>> # validator specification (based on weights of each objects)
     >>> weights = [ random.randint(5, 30) for i in range(problem_size) ]
     >>> weights = [ random.randint(5, 30) for i in range(problem_size) ]
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=20: BinarySolution.random(x, validator)
+    >>> # initialiser function with lambda function
+    >>> initialiser = lambda x=20: BinarySolution.random(x, validator)
     >>> # operators list with crossover and mutation
     >>> # operators list with crossover and mutation
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> policy = RandomPolicy(operators)
     >>> policy = RandomPolicy(operators)
-    >>> local_search = HillClimberFirstImprovment(initializer, evaluator, operators, policy, validator, maximise=True, verbose=False)
-    >>> algo = IteratedLocalSearch(initializer, evaluator, operators, policy, validator, localSearch=local_search, maximise=True, verbose=False)
+    >>> local_search = HillClimberFirstImprovment(initialiser, evaluator, operators, policy, validator, maximise=True, verbose=False)
+    >>> algo = IteratedLocalSearch(initialiser, evaluator, operators, policy, validator, localSearch=local_search, maximise=True, verbose=False)
     >>> # run the algorithm
     >>> # run the algorithm
     >>> solution = algo.run(100, ls_evaluations=10)
     >>> solution = algo.run(100, ls_evaluations=10)
     >>> solution._score
     >>> solution._score
     137
     137
     """
     """
     def __init__(self,
     def __init__(self,
-                 initializer,
+                 initialiser,
                  evaluator,
                  evaluator,
                  operators,
                  operators,
                  policy,
                  policy,
@@ -276,8 +279,21 @@ class IteratedLocalSearch(Algorithm):
                  maximise=True,
                  maximise=True,
                  parent=None,
                  parent=None,
                  verbose=True):
                  verbose=True):
+        """Iterated Local Search Algorithm initialisation with use of specific LocalSearch {Algorithm} instance
 
 
-        super().__init__(initializer, evaluator, operators, policy, validator,
+        Args:
+            initialiser: {function} -- basic function strategy to initialise solution
+            evaluator: {Evaluator} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
+            operators: {[Operator]} -- list of operator to use when launching algorithm
+            policy: {Policy} -- Policy implementation strategy to select operators
+            validator: {function} -- basic function to check if solution is valid or not under some constraints
+            localSearch: {Algorithm} -- current local search into ILS
+            maximise: {bool} -- specify kind of optimisation problem 
+            parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
+            verbose: {bool} -- verbose or not information about the algorithm
+        """
+
+        super().__init__(initialiser, evaluator, operators, policy, validator,
                          maximise, parent, verbose)
                          maximise, parent, verbose)
 
 
         # specific local search associated with current algorithm
         # specific local search associated with current algorithm
@@ -297,13 +313,13 @@ class IteratedLocalSearch(Algorithm):
             {Solution} -- best solution found
             {Solution} -- best solution found
         """
         """
 
 
-        # by default use of mother method to initialize variables
+        # by default use of mother method to initialise variables
         super().run(evaluations)
         super().run(evaluations)
 
 
         # enable resuming for ILS
         # enable resuming for ILS
         self.resume()
         self.resume()
 
 
-        # initialize current solution
+        # initialise current solution
         self.initRun()
         self.initRun()
 
 
         # add same callbacks
         # add same callbacks

+ 89 - 41
macop/algorithms/multi.py

@@ -20,7 +20,7 @@ class MOEAD(Algorithm):
         mu: {int} -- number of sub problems
         mu: {int} -- number of sub problems
         T: {[float]} -- number of neightbors for each sub problem
         T: {[float]} -- number of neightbors for each sub problem
         nObjectives: {int} -- number of objectives (based of number evaluator)
         nObjectives: {int} -- number of objectives (based of number evaluator)
-        initializer: {function} -- basic function strategy to initialize solution
+        initialiser: {function} -- basic function strategy to initialise solution
         evaluator: {[function]} -- list of basic function in order to obtained fitness (multiple objectives)
         evaluator: {[function]} -- list of basic function in order to obtained fitness (multiple objectives)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy class implementation strategy to select operators
         policy: {Policy} -- Policy class implementation strategy to select operators
@@ -30,7 +30,8 @@ class MOEAD(Algorithm):
         population: [{Solution}] -- population of solution, one for each sub problem
         population: [{Solution}] -- population of solution, one for each sub problem
         pfPop: [{Solution}] -- pareto front population
         pfPop: [{Solution}] -- pareto front population
         weights: [[{float}]] -- random weights used for custom mu sub problems
         weights: [[{float}]] -- random weights used for custom mu sub problems
-        callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initializing algorithm
+        callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initialising algorithm
+        parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
 
 
     >>> import random
     >>> import random
     >>> # operators import
     >>> # operators import
@@ -52,13 +53,13 @@ class MOEAD(Algorithm):
     >>> # validator specification (based on weights of each objects)
     >>> # validator specification (based on weights of each objects)
     >>> weights = [ random.randint(5, 30) for i in range(problem_size) ]
     >>> weights = [ random.randint(5, 30) for i in range(problem_size) ]
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=20: BinarySolution.random(x, validator)
+    >>> # initialiser function with lambda function
+    >>> initialiser = lambda x=20: BinarySolution.random(x, validator)
     >>> # operators list with crossover and mutation
     >>> # operators list with crossover and mutation
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> policy = RandomPolicy(operators)
     >>> policy = RandomPolicy(operators)
     >>> # MOEAD use multi-objective, hence list of evaluators with mu=100 and T=10
     >>> # MOEAD use multi-objective, hence list of evaluators with mu=100 and T=10
-    >>> algo = MOEAD(20, 5, initializer, [evaluator1, evaluator2], operators, policy, validator, maximise=True, verbose=False)
+    >>> algo = MOEAD(20, 5, initialiser, [evaluator1, evaluator2], operators, policy, validator, maximise=True, verbose=False)
     >>> # run the algorithm and get the pareto front obtained
     >>> # run the algorithm and get the pareto front obtained
     >>> pf_solutions = algo.run(100)
     >>> pf_solutions = algo.run(100)
     >>> # check size of expected pareto
     >>> # check size of expected pareto
@@ -68,7 +69,7 @@ class MOEAD(Algorithm):
     def __init__(self,
     def __init__(self,
                  mu,
                  mu,
                  T,
                  T,
-                 initializer,
+                 initialiser,
                  evaluator,
                  evaluator,
                  operators,
                  operators,
                  policy,
                  policy,
@@ -76,13 +77,28 @@ class MOEAD(Algorithm):
                  maximise=True,
                  maximise=True,
                  parent=None,
                  parent=None,
                  verbose=True):
                  verbose=True):
+        """Multi-Ojective Evolutionary Algorithm with Scalar Decomposition initialisation
+
+        Args:
+            mu: {int} -- number of sub problems
+            T: {[float]} -- number of neightbors for each sub problem
+            initialiser: {function} -- basic function strategy to initialise solution
+            evaluator: {[function]} -- list of basic function in order to obtained fitness (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
+            parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
+            verbose: {bool} -- verbose or not information about the algorithm
+        """
 
 
         # redefinition of constructor to well use `initRun` method
         # redefinition of constructor to well use `initRun` method
-        self._initializer = initializer
-        self._evaluator = evaluator
+        self.initialiser = initialiser
+        self.evaluator = evaluator
+        self.validator = validator
+
         self._operators = operators
         self._operators = operators
-        self._policy = policy
-        self._validator = validator
+        self.policy = policy
         self._callbacks = []
         self._callbacks = []
 
 
         # by default
         # by default
@@ -103,7 +119,7 @@ class MOEAD(Algorithm):
             operator.setAlgo(self)
             operator.setAlgo(self)
 
 
         # by default track reference for policy
         # by default track reference for policy
-        self._policy.setAlgo(self)
+        self.policy.setAlgo(self)
 
 
         if mu < T:
         if mu < T:
             raise ValueError('`mu` cannot be less than `T`')
             raise ValueError('`mu` cannot be less than `T`')
@@ -114,7 +130,7 @@ class MOEAD(Algorithm):
         self._mu = mu
         self._mu = mu
         self._T = T
         self._T = T
 
 
-        # initialize neighbors for each sub problem
+        # initialise neighbors for each sub problem
         self.setNeighbors()
         self.setNeighbors()
 
 
         weights = []
         weights = []
@@ -150,7 +166,7 @@ class MOEAD(Algorithm):
             # use copy of list to keep track for each sub problem
             # use copy of list to keep track for each sub problem
             subProblem = MOSubProblem(i,
             subProblem = MOSubProblem(i,
                                       weights[i],
                                       weights[i],
-                                      initializer,
+                                      initialiser,
                                       sub_evaluator,
                                       sub_evaluator,
                                       operators.copy(),
                                       operators.copy(),
                                       policy,
                                       policy,
@@ -161,7 +177,8 @@ class MOEAD(Algorithm):
 
 
             self._subProblems.append(subProblem)
             self._subProblems.append(subProblem)
 
 
-        self._population = [None for n in range(self._mu)]
+        self.population = [None for n in range(self._mu)]
+
         self._pfPop = []
         self._pfPop = []
 
 
         # ref point based on number of evaluators
         # ref point based on number of evaluators
@@ -174,7 +191,7 @@ class MOEAD(Algorithm):
 
 
     def initRun(self):
     def initRun(self):
         """
         """
-        Method which initialiazes or re-initializes the whole algorithm context specifically for MOEAD
+        Method which initialiazes or re-initialises the whole algorithm context specifically for MOEAD
         """
         """
         # initialization is done during run method
         # initialization is done during run method
         pass
         pass
@@ -190,18 +207,18 @@ class MOEAD(Algorithm):
             {Solution} -- best solution found
             {Solution} -- best solution found
         """
         """
 
 
-        # by default use of mother method to initialize variables
+        # by default use of mother method to initialise variables
         super().run(evaluations)
         super().run(evaluations)
 
 
         # enable callback resume for MOEAD
         # enable callback resume for MOEAD
         self.resume()
         self.resume()
 
 
-        # initialize each sub problem if no backup
+        # initialise each sub problem if no backup
         for i in range(self._mu):
         for i in range(self._mu):
 
 
             if self._subProblems[i]._bestSolution is None:
             if self._subProblems[i]._bestSolution is None:
                 self._subProblems[i].run(1)
                 self._subProblems[i].run(1)
-                self._population[i] = self._subProblems[i]._bestSolution
+                self.population[i] = self._subProblems[i]._bestSolution
 
 
         # if no backup for pf population
         # if no backup for pf population
         if len(self._pfPop) == 0:
         if len(self._pfPop) == 0:
@@ -233,7 +250,7 @@ class MOEAD(Algorithm):
                         self._subProblems[j]._bestSolution = newSolution
                         self._subProblems[j]._bestSolution = newSolution
 
 
                         # update population solution for this sub problem
                         # update population solution for this sub problem
-                        self._population[j] = newSolution
+                        self.population[j] = newSolution
 
 
                         improvment = True
                         improvment = True
 
 
@@ -252,7 +269,7 @@ class MOEAD(Algorithm):
                     break
                     break
 
 
         logging.info(
         logging.info(
-            f"End of {type(self).__name__}, best solution found {self._population}"
+            f"End of {type(self).__name__}, best solution found {self.population}"
         )
         )
 
 
         self.end()
         self.end()
@@ -301,19 +318,19 @@ class MOEAD(Algorithm):
     def updateRefPoint(self, solution):
     def updateRefPoint(self, solution):
 
 
         if self._maximise:
         if self._maximise:
-            for i in range(len(self._evaluator)):
-                if solution._scores[i] > self._refPoint[i]:
-                    self._refPoint[i] = solution._scores[i]
+            for i in range(len(self.evaluator)):
+                if solution.scores[i] > self._refPoint[i]:
+                    self._refPoint[i] = solution.scores[i]
         else:
         else:
-            for i in range(len(self._evaluator)):
+            for i in range(len(self.evaluator)):
                 if solution.scores[i] < self._refPoint[i]:
                 if solution.scores[i] < self._refPoint[i]:
-                    self._refPoint[i] = solution._scores[i]
+                    self._refPoint[i] = solution.scores[i]
 
 
     def paretoFront(self, population):
     def paretoFront(self, population):
 
 
         paFront = []
         paFront = []
         indexes = []
         indexes = []
-        nObjectives = len(self._evaluator)
+        nObjectives = len(self.evaluator)
         nSolutions = len(population)
         nSolutions = len(population)
 
 
         # find dominated solution
         # find dominated solution
@@ -333,12 +350,12 @@ class MOEAD(Algorithm):
                 nDominated = 0
                 nDominated = 0
 
 
                 # check number of dominated objectives of current solution by the others solution
                 # check number of dominated objectives of current solution by the others solution
-                for k in range(len(self._evaluator)):
+                for k in range(len(self.evaluator)):
                     if self._maximise:
                     if self._maximise:
-                        if population[i]._scores[k] < population[j]._scores[k]:
+                        if population[i].scores[k] < population[j].scores[k]:
                             nDominated += 1
                             nDominated += 1
                     else:
                     else:
-                        if population[i]._scores[k] > population[j]._scores[k]:
+                        if population[i].scores[k] > population[j].scores[k]:
                             nDominated += 1
                             nDominated += 1
 
 
                 if nDominated == nObjectives:
                 if nDominated == nObjectives:
@@ -352,6 +369,22 @@ class MOEAD(Algorithm):
 
 
         return paFront
         return paFront
 
 
+    def getResult(self):
+        """Get the expected result of the current algorithm
+
+        Returns:
+            [{Solution}] -- pareto front population
+        """
+        return self._pfPop
+
+    def setDefaultResult(self, result):
+        """Set current default result of the algorithm
+
+        Args:
+            result: {object} -- expected result data of the current algorithm
+        """
+        self._pfPop = result
+
     def end(self):
     def end(self):
         """Display end message into `run` method
         """Display end message into `run` method
         """
         """
@@ -362,7 +395,7 @@ class MOEAD(Algorithm):
         )
         )
 
 
         for i, solution in enumerate(self._pfPop):
         for i, solution in enumerate(self._pfPop):
-            macop_text(self, f'  - [{i}] {solution._scores} : {solution}')
+            macop_text(self, f'  - [{i}] {solution.scores} : {solution}')
 
 
         macop_line(self)
         macop_line(self)
 
 
@@ -371,10 +404,10 @@ class MOEAD(Algorithm):
         logging.info("-- Pareto front :")
         logging.info("-- Pareto front :")
 
 
         for i, solution in enumerate(self._pfPop):
         for i, solution in enumerate(self._pfPop):
-            logging.info(f"-- {i}] SCORE {solution._scores} - {solution}")
+            logging.info(f"-- {i}] SCORE {solution.scores} - {solution}")
 
 
     def __str__(self):
     def __str__(self):
-        return f"{type(self).__name__} using {type(self._population).__name__}"
+        return f"{type(self).__name__} using {type(self.population).__name__}"
 
 
 
 
 class MOSubProblem(Algorithm):
 class MOSubProblem(Algorithm):
@@ -383,7 +416,7 @@ class MOSubProblem(Algorithm):
     Attributes:
     Attributes:
         index: {int} -- sub problem index
         index: {int} -- sub problem index
         weights: {[float]} -- sub problems objectives weights
         weights: {[float]} -- sub problems objectives weights
-        initalizer: {function} -- basic function strategy to initialize solution
+        initalizer: {function} -- basic function strategy to initialise solution
         evaluator: {function} -- basic function in order to obtained fitness (mono or multiple objectives)
         evaluator: {function} -- basic function in order to obtained fitness (mono or multiple objectives)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy class implementation strategy to select operators
         policy: {Policy} -- Policy class implementation strategy to select operators
@@ -392,7 +425,8 @@ class MOSubProblem(Algorithm):
         verbose: {bool} -- verbose or not information about the algorithm
         verbose: {bool} -- verbose or not information about the algorithm
         currentSolution: {Solution} -- current solution managed for current evaluation
         currentSolution: {Solution} -- current solution managed for current evaluation
         bestSolution: {Solution} -- best solution found so far during running algorithm
         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
+        callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initialising algorithm
+        parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
     
     
     Example:
     Example:
 
 
@@ -416,17 +450,17 @@ class MOSubProblem(Algorithm):
     >>> # validator specification (based on weights of each objects)
     >>> # validator specification (based on weights of each objects)
     >>> weights = [ random.randint(5, 30) for i in range(problem_size) ]
     >>> weights = [ random.randint(5, 30) for i in range(problem_size) ]
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=20: BinarySolution.random(x, validator)
+    >>> # initialiser function with lambda function
+    >>> initialiser = lambda x=20: BinarySolution.random(x, validator)
     >>> # operators list with crossover and mutation
     >>> # operators list with crossover and mutation
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> policy = RandomPolicy(operators)
     >>> policy = RandomPolicy(operators)
-    >>> algo = MOEAD(20, 5, initializer, [evaluator1, evaluator2], operators, policy, validator, maximise=True, verbose=False)
+    >>> algo = MOEAD(20, 5, initialiser, [evaluator1, evaluator2], operators, policy, validator, maximise=True, verbose=False)
     >>> # weights of the sub problem
     >>> # weights of the sub problem
     >>> sub_problem_weights = [0.4, 0.6]
     >>> sub_problem_weights = [0.4, 0.6]
     >>> sub_evaluator = WeightedSum(data={'evaluators': [evaluator1, evaluator2], 'weights': sub_problem_weights})
     >>> sub_evaluator = WeightedSum(data={'evaluators': [evaluator1, evaluator2], 'weights': sub_problem_weights})
     >>> # first parameter is the index of the MOSubProblem
     >>> # first parameter is the index of the MOSubProblem
-    >>> subProblem = MOSubProblem(0, sub_problem_weights, initializer, sub_evaluator, operators, policy, validator, maximise=True, parent=algo, verbose=False)
+    >>> subProblem = MOSubProblem(0, sub_problem_weights, initialiser, sub_evaluator, operators, policy, validator, maximise=True, parent=algo, verbose=False)
     >>> # run the algorithm
     >>> # run the algorithm
     >>> solution = subProblem.run(100)
     >>> solution = subProblem.run(100)
     >>> solution._score
     >>> solution._score
@@ -443,6 +477,20 @@ class MOSubProblem(Algorithm):
                  maximise=True,
                  maximise=True,
                  parent=None,
                  parent=None,
                  verbose=True):
                  verbose=True):
+        """Specific multi-objective sub problem used into MOEAD
+
+        Args:
+            index: {int} -- sub problem index
+            weights: {[float]} -- sub problems objectives weights
+            initalizer: {function} -- basic function strategy to initialise 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 
+            parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
+            verbose: {bool} -- verbose or not information about the algorithm
+        """
 
 
         super().__init__(initalizer, evaluator, operators, policy, validator,
         super().__init__(initalizer, evaluator, operators, policy, validator,
                          maximise, parent)
                          maximise, parent)
@@ -463,10 +511,10 @@ class MOSubProblem(Algorithm):
             {Solution} -- best solution found
             {Solution} -- best solution found
         """
         """
 
 
-        # by default use of mother method to initialize variables
+        # by default use of mother method to initialise variables
         super().run(evaluations)
         super().run(evaluations)
 
 
-        # initialize solution if necessary
+        # initialise solution if necessary
         if self._bestSolution is None:
         if self._bestSolution is None:
             self.initRun()
             self.initRun()
 
 
@@ -477,7 +525,7 @@ class MOSubProblem(Algorithm):
         for _ in range(evaluations):
         for _ in range(evaluations):
 
 
             # keep reference of sub problem used
             # keep reference of sub problem used
-            self._policy.setAlgo(self)
+            self.policy.setAlgo(self)
 
 
             # update solution using policy
             # update solution using policy
             newSolution = self.update(self._bestSolution)
             newSolution = self.update(self._bestSolution)

+ 6 - 0
macop/callbacks/base.py

@@ -16,6 +16,12 @@ class Callback():
         filepath: {str} -- file path where checkpoints will be saved
         filepath: {str} -- file path where checkpoints will be saved
     """
     """
     def __init__(self, every, filepath):
     def __init__(self, every, filepath):
+        """Callback abstract initialiser
+
+        Args:
+            every: {int} -- checkpoint frequency used (based on number of evaluations)
+            filepath: {str} -- file path where checkpoints will be saved
+        """
 
 
         self._algo = None
         self._algo = None
         self._every = every
         self._every = every

+ 8 - 9
macop/callbacks/classicals.py

@@ -25,7 +25,7 @@ class BasicCheckpoint(Callback):
         Check if necessary to do backup based on `every` variable
         Check if necessary to do backup based on `every` variable
         """
         """
         # get current best solution
         # get current best solution
-        solution = self._algo._bestSolution
+        solution = self._algo.getResult()
 
 
         currentEvaluation = self._algo.getGlobalEvaluation()
         currentEvaluation = self._algo.getGlobalEvaluation()
 
 
@@ -71,26 +71,25 @@ class BasicCheckpoint(Callback):
                 globalEvaluation = int(data[0])
                 globalEvaluation = int(data[0])
 
 
                 if self._algo.getParent() is not None:
                 if self._algo.getParent() is not None:
-                    self._algo.getParent(
-                    )._numberOfEvaluations = globalEvaluation
+                    self._algo.getParent().setEvaluation(globalEvaluation)
                 else:
                 else:
-                    self._algo._numberOfEvaluations = globalEvaluation
+                    self._algo.setEvaluation(globalEvaluation)
 
 
                 # get best solution data information
                 # get best solution data information
                 solutionData = list(map(int, data[1].split(' ')))
                 solutionData = list(map(int, data[1].split(' ')))
 
 
-                if self._algo._bestSolution is None:
-                    self._algo._bestSolution = self._algo._initializer()
+                if self._algo.getResult() is None:
+                    self._algo.setDefaultResult(self._algo.initialiser())
 
 
-                self._algo._bestsolution.setData(np.array(solutionData))
-                self._algo._bestSolution._score = float(data[2])
+                self._algo.getResult().setData(np.array(solutionData))
+                self._algo.getResult().setScore(float(data[2]))
 
 
             macop_line(self._algo)
             macop_line(self._algo)
             macop_text(self._algo,
             macop_text(self._algo,
                        f'Checkpoint found from `{self._filepath}` file.')
                        f'Checkpoint found from `{self._filepath}` file.')
             macop_text(
             macop_text(
                 self._algo,
                 self._algo,
-                f'Restart algorithm from evaluation {self._algo._numberOfEvaluations}.'
+                f'Restart algorithm from evaluation {self._algo.getEvaluation()}.'
             )
             )
         else:
         else:
             macop_text(
             macop_text(

+ 13 - 13
macop/callbacks/multi.py

@@ -25,7 +25,7 @@ class MultiCheckpoint(Callback):
         Check if necessary to do backup based on `every` variable
         Check if necessary to do backup based on `every` variable
         """
         """
         # get current population
         # get current population
-        population = self._algo._population
+        population = self._algo.population
 
 
         currentEvaluation = self._algo.getGlobalEvaluation()
         currentEvaluation = self._algo.getGlobalEvaluation()
 
 
@@ -48,8 +48,8 @@ class MultiCheckpoint(Callback):
 
 
                     line = str(currentEvaluation) + ';'
                     line = str(currentEvaluation) + ';'
 
 
-                    for i in range(len(self._algo._evaluator)):
-                        line += str(solution._scores[i]) + ';'
+                    for i in range(len(self._algo.evaluator)):
+                        line += str(solution.scores[i]) + ';'
 
 
                     line += solutionData + ';\n'
                     line += solutionData + ';\n'
 
 
@@ -80,18 +80,18 @@ class MultiCheckpoint(Callback):
                         else:
                         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]]
                     scores = [float(s) for s in data[1:nObjectives + 1]]
 
 
                     # get best solution data information
                     # get best solution data information
                     solutionData = list(map(int, data[-1].split(' ')))
                     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
+                    # initialise and fill with data
+                    self._algo.population[i] = self._algo.initialiser()
+                    self._algo.population[i].setData(np.array(solutionData))
+                    self._algo.population[i].scores = scores
 
 
-                    self._algo._pfPop.append(self._algo._population[i])
+                    self._algo._pfPop.append(self._algo.population[i])
 
 
             macop_line(self._algo)
             macop_line(self._algo)
             macop_text(
             macop_text(
@@ -149,8 +149,8 @@ class ParetoCheckpoint(Callback):
 
 
                     line = ''
                     line = ''
 
 
-                    for i in range(len(self._algo._evaluator)):
-                        line += str(solution._scores[i]) + ';'
+                    for i in range(len(self._algo.evaluator)):
+                        line += str(solution.scores[i]) + ';'
 
 
                     line += solutionData + ';\n'
                     line += solutionData + ';\n'
 
 
@@ -170,14 +170,14 @@ class ParetoCheckpoint(Callback):
 
 
                     data = line.replace(';\n', '').split(';')
                     data = line.replace(';\n', '').split(';')
 
 
-                    nObjectives = len(self._algo._evaluator)
+                    nObjectives = len(self._algo.evaluator)
                     scores = [float(s) for s in data[0:nObjectives]]
                     scores = [float(s) for s in data[0:nObjectives]]
 
 
                     # get best solution data information
                     # get best solution data information
                     solutionData = list(map(int, data[-1].split(' ')))
                     solutionData = list(map(int, data[-1].split(' ')))
 
 
                     self._algo._pfPop[i]._data = solutionData
                     self._algo._pfPop[i]._data = solutionData
-                    self._algo._pfPop[i]._scores = scores
+                    self._algo._pfPop[i].scores = scores
 
 
             macop_text(
             macop_text(
                 self._algo,
                 self._algo,

+ 6 - 6
macop/callbacks/policies.py

@@ -37,20 +37,20 @@ class UCBCheckpoint(Callback):
 
 
                 rewardsLine = ''
                 rewardsLine = ''
 
 
-                for i, r in enumerate(self._algo._policy._rewards):
+                for i, r in enumerate(self._algo.policy.rewards):
                     rewardsLine += str(r)
                     rewardsLine += str(r)
 
 
-                    if i != len(self._algo._policy._rewards) - 1:
+                    if i != len(self._algo.policy.rewards) - 1:
                         rewardsLine += ';'
                         rewardsLine += ';'
 
 
                 f.write(rewardsLine + '\n')
                 f.write(rewardsLine + '\n')
 
 
                 occurrencesLine = ''
                 occurrencesLine = ''
 
 
-                for i, o in enumerate(self._algo._policy._occurences):
+                for i, o in enumerate(self._algo.policy.occurences):
                     occurrencesLine += str(o)
                     occurrencesLine += str(o)
 
 
-                    if i != len(self._algo._policy._occurences) - 1:
+                    if i != len(self._algo.policy.occurences) - 1:
                         occurrencesLine += ';'
                         occurrencesLine += ';'
 
 
                 f.write(occurrencesLine + '\n')
                 f.write(occurrencesLine + '\n')
@@ -69,10 +69,10 @@ class UCBCheckpoint(Callback):
                 rewardsLine = lines[0].replace('\n', '')
                 rewardsLine = lines[0].replace('\n', '')
                 occurrencesLine = lines[1].replace('\n', '')
                 occurrencesLine = lines[1].replace('\n', '')
 
 
-                self._algo._policy._rewards = [
+                self._algo.policy.rewards = [
                     float(f) for f in rewardsLine.split(';')
                     float(f) for f in rewardsLine.split(';')
                 ]
                 ]
-                self._algo._policy._occurences = [
+                self._algo.policy.occurences = [
                     float(f) for f in occurrencesLine.split(';')
                     float(f) for f in occurrencesLine.split(';')
                 ]
                 ]
 
 

+ 2 - 8
macop/evaluators/base.py

@@ -11,15 +11,9 @@ class Evaluator():
     """Abstract Evaluator class which enables to compute solution using specific `_data` 
     """Abstract Evaluator class which enables to compute solution using specific `_data` 
     """
     """
     def __init__(self, data: dict):
     def __init__(self, data: dict):
-                """Apply the computation of fitness from solution
-
-        Fitness is a float value for mono-objective or set of float values if multi-objective evaluation
-
+        """Initialise Evaluator instance which stores into its `_data` dictionary attritute required measures when computing a solution
         Args:
         Args:
-            solution: {Solution} -- Solution instance
-
-        Return:
-            {float} -- computed solution score (float or set of float if multi-objective evaluation)
+            data: {dict} -- specific data dictionnary
         """
         """
         self._data = data
         self._data = data
 
 

+ 4 - 3
macop/evaluators/discrete/mono.py

@@ -43,6 +43,7 @@ class KnapsackEvaluator(Evaluator):
 
 
         return fitness
         return fitness
 
 
+
 class QAPEvaluator(Evaluator):
 class QAPEvaluator(Evaluator):
     """Quadratic Assignment Problem (QAP) evaluator class which enables to compute qap solution using specific `_data`
     """Quadratic Assignment Problem (QAP) evaluator class which enables to compute qap solution using specific `_data`
 
 
@@ -94,12 +95,13 @@ class QAPEvaluator(Evaluator):
         fitness = 0
         fitness = 0
         for index_i, val_i in enumerate(solution.getData()):
         for index_i, val_i in enumerate(solution.getData()):
             for index_j, val_j in enumerate(solution.getData()):
             for index_j, val_j in enumerate(solution.getData()):
-                fitness += self._data['F'][index_i, index_j] * self._data['D'][val_i, val_j]
+                fitness += self._data['F'][index_i,
+                                           index_j] * self._data['D'][val_i,
+                                                                      val_j]
 
 
         return fitness
         return fitness
 
 
 
 
-
 class UBQPEvaluator(Evaluator):
 class UBQPEvaluator(Evaluator):
     """Unconstrained Binary Quadratic Programming (UBQP) evaluator class which enables to compute UBQP solution using specific `_data`
     """Unconstrained Binary Quadratic Programming (UBQP) evaluator class which enables to compute UBQP solution using specific `_data`
 
 
@@ -134,7 +136,6 @@ class UBQPEvaluator(Evaluator):
     >>> evaluator.compute(solution)
     >>> evaluator.compute(solution)
     477.0
     477.0
     """
     """
-
     def compute(self, solution):
     def compute(self, solution):
         """Apply the computation of fitness from solution
         """Apply the computation of fitness from solution
 
 

+ 1 - 1
macop/evaluators/discrete/multi.py

@@ -53,7 +53,7 @@ class WeightedSum(Evaluator):
         ]
         ]
 
 
         # associate objectives scores to solution
         # associate objectives scores to solution
-        solution._scores = scores
+        solution.scores = scores
 
 
         return sum(
         return sum(
             [scores[i] * w for i, w in enumerate(self._data['weights'])])
             [scores[i] * w for i, w in enumerate(self._data['weights'])])

+ 23 - 0
macop/operators/base.py

@@ -17,6 +17,8 @@ class Operator():
     """
     """
     @abstractmethod
     @abstractmethod
     def __init__(self):
     def __init__(self):
+        """Abstract Operator initialiser
+        """
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
@@ -45,9 +47,19 @@ class Mutation(Operator):
         kind: {KindOperator} -- specify the kind of operator
         kind: {KindOperator} -- specify the kind of operator
     """
     """
     def __init__(self):
     def __init__(self):
+        """Mutation initialiser in order to specify kind of Operator
+        """
         self._kind = KindOperator.MUTATOR
         self._kind = KindOperator.MUTATOR
 
 
     def apply(self, solution):
     def apply(self, solution):
+        """Apply mutation over solution in order to obtained new solution
+
+        Args:
+            solution: {Solution} -- solution to use in order to create new solution
+
+        Return:
+            {Solution} -- new generated solution
+        """
         raise NotImplementedError
         raise NotImplementedError
 
 
 
 
@@ -58,7 +70,18 @@ class Crossover(Operator):
         kind: {KindOperator} -- specify the kind of operator
         kind: {KindOperator} -- specify the kind of operator
     """
     """
     def __init__(self):
     def __init__(self):
+        """Crossover initialiser in order to specify kind of Operator
+        """
         self._kind = KindOperator.CROSSOVER
         self._kind = KindOperator.CROSSOVER
 
 
     def apply(self, solution1, solution2=None):
     def apply(self, solution1, solution2=None):
+        """Apply crossover using two solutions in order to obtained new solution
+
+        Args:
+            solution1: {Solution} -- the first solution to use for generating new solution
+            solution2: {Solution} -- the second solution to use for generating new solution
+
+        Return:
+            {Solution} -- new generated solution
+        """
         raise NotImplementedError
         raise NotImplementedError

+ 12 - 12
macop/operators/discrete/crossovers.py

@@ -33,21 +33,21 @@ class SimpleCrossover(Crossover):
     >>> # validator specification (based on weights of each objects)
     >>> # validator specification (based on weights of each objects)
     >>> weights = [ random.randint(20, 30) for i in range(10) ]
     >>> weights = [ random.randint(20, 30) for i in range(10) ]
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=10: BinarySolution.random(x, validator)
+    >>> # initialiser function with lambda function
+    >>> initialiser = lambda x=10: BinarySolution.random(x, validator)
     >>> # operators list with crossover and mutation
     >>> # operators list with crossover and mutation
     >>> simple_crossover = SimpleCrossover()
     >>> simple_crossover = SimpleCrossover()
     >>> simple_mutation = SimpleMutation()
     >>> simple_mutation = SimpleMutation()
     >>> operators = [simple_crossover, simple_mutation]
     >>> operators = [simple_crossover, simple_mutation]
     >>> policy = UCBPolicy(operators)
     >>> policy = UCBPolicy(operators)
-    >>> local_search = HillClimberFirstImprovment(initializer, evaluator, operators, policy, validator, maximise=True, verbose=False)
-    >>> algo = IteratedLocalSearch(initializer, evaluator, operators, policy, validator, localSearch=local_search, maximise=True, verbose=False)
+    >>> local_search = HillClimberFirstImprovment(initialiser, evaluator, operators, policy, validator, maximise=True, verbose=False)
+    >>> algo = IteratedLocalSearch(initialiser, evaluator, operators, policy, validator, localSearch=local_search, maximise=True, verbose=False)
     >>> # using best solution, simple crossover is applied
     >>> # using best solution, simple crossover is applied
     >>> best_solution = algo.run(100)
     >>> best_solution = algo.run(100)
     >>> list(best_solution.getData())
     >>> list(best_solution.getData())
     [1, 1, 0, 1, 0, 1, 1, 1, 0, 1]
     [1, 1, 0, 1, 0, 1, 1, 1, 0, 1]
-    >>> new_solution_1 = initializer()
-    >>> new_solution_2 = initializer()
+    >>> new_solution_1 = initialiser()
+    >>> new_solution_2 = initialiser()
     >>> offspring_solution = simple_crossover.apply(new_solution_1, new_solution_2)
     >>> offspring_solution = simple_crossover.apply(new_solution_1, new_solution_2)
     >>> list(offspring_solution.getData())
     >>> list(offspring_solution.getData())
     [0, 1, 1, 0, 1, 0, 1, 1, 0, 1]
     [0, 1, 1, 0, 1, 0, 1, 1, 0, 1]
@@ -106,21 +106,21 @@ class RandomSplitCrossover(Crossover):
     >>> # validator specification (based on weights of each objects)
     >>> # validator specification (based on weights of each objects)
     >>> weights = [ random.randint(20, 30) for i in range(10) ]
     >>> weights = [ random.randint(20, 30) for i in range(10) ]
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=10: BinarySolution.random(x, validator)
+    >>> # initialiser function with lambda function
+    >>> initialiser = lambda x=10: BinarySolution.random(x, validator)
     >>> # operators list with crossover and mutation
     >>> # operators list with crossover and mutation
     >>> random_split_crossover = RandomSplitCrossover()
     >>> random_split_crossover = RandomSplitCrossover()
     >>> simple_mutation = SimpleMutation()
     >>> simple_mutation = SimpleMutation()
     >>> operators = [random_split_crossover, simple_mutation]
     >>> operators = [random_split_crossover, simple_mutation]
     >>> policy = UCBPolicy(operators)
     >>> policy = UCBPolicy(operators)
-    >>> local_search = HillClimberFirstImprovment(initializer, evaluator, operators, policy, validator, maximise=True, verbose=False)
-    >>> algo = IteratedLocalSearch(initializer, evaluator, operators, policy, validator, localSearch=local_search, maximise=True, verbose=False)
+    >>> local_search = HillClimberFirstImprovment(initialiser, evaluator, operators, policy, validator, maximise=True, verbose=False)
+    >>> algo = IteratedLocalSearch(initialiser, evaluator, operators, policy, validator, localSearch=local_search, maximise=True, verbose=False)
     >>> # using best solution, simple crossover is applied
     >>> # using best solution, simple crossover is applied
     >>> best_solution = algo.run(100)
     >>> best_solution = algo.run(100)
     >>> list(best_solution.getData())
     >>> list(best_solution.getData())
     [1, 1, 1, 0, 1, 0, 1, 1, 1, 0]
     [1, 1, 1, 0, 1, 0, 1, 1, 1, 0]
-    >>> new_solution_1 = initializer()
-    >>> new_solution_2 = initializer()
+    >>> new_solution_1 = initialiser()
+    >>> new_solution_2 = initialiser()
     >>> offspring_solution = random_split_crossover.apply(new_solution_1, new_solution_2)
     >>> offspring_solution = random_split_crossover.apply(new_solution_1, new_solution_2)
     >>> list(offspring_solution.getData())
     >>> list(offspring_solution.getData())
     [0, 0, 0, 1, 1, 0, 0, 1, 0, 0]
     [0, 0, 0, 1, 1, 0, 0, 1, 0, 0]

+ 2 - 1
macop/operators/discrete/mutators.py

@@ -52,7 +52,8 @@ class SimpleMutation(Mutation):
         temp = copy_solution.getData()[firstCell]
         temp = copy_solution.getData()[firstCell]
 
 
         # swicth values
         # swicth values
-        copy_solution.getData()[firstCell] = copy_solution.getData()[secondCell]
+        copy_solution.getData()[firstCell] = copy_solution.getData(
+        )[secondCell]
         copy_solution.getData()[secondCell] = temp
         copy_solution.getData()[secondCell] = temp
 
 
         return copy_solution
         return copy_solution

+ 6 - 1
macop/policies/base.py

@@ -14,6 +14,11 @@ class Policy():
         operators: {[Operator]} -- list of selected operators for the algorithm
         operators: {[Operator]} -- list of selected operators for the algorithm
     """
     """
     def __init__(self, operators):
     def __init__(self, operators):
+        """Initialise new Policy instance using specific list of operators
+
+        Args:
+            operators: [{}] -- list of operators to use
+        """
         self._operators = operators
         self._operators = operators
 
 
     @abstractmethod
     @abstractmethod
@@ -45,7 +50,7 @@ class Policy():
 
 
         # default value of solution2 is current best solution
         # default value of solution2 is current best solution
         if solution2 is None and self._algo is not None:
         if solution2 is None and self._algo is not None:
-            solution2 = self._algo._bestSolution
+            solution2 = self._algo.getResult()
 
 
         # avoid use of crossover if only one solution is passed
         # avoid use of crossover if only one solution is passed
         if solution2 is None and operator._kind == KindOperator.CROSSOVER:
         if solution2 is None and operator._kind == KindOperator.CROSSOVER:

+ 27 - 16
macop/policies/reinforcement.py

@@ -47,28 +47,39 @@ class UCBPolicy(Policy):
     >>> # validator specification (based on weights of each objects)
     >>> # validator specification (based on weights of each objects)
     >>> weights = [ random.randint(5, 30) for i in range(20) ]
     >>> weights = [ random.randint(5, 30) for i in range(20) ]
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
     >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=20: BinarySolution.random(x, validator)
+    >>> # initialiser function with lambda function
+    >>> initialiser = lambda x=20: BinarySolution.random(x, validator)
     >>> # operators list with crossover and mutation
     >>> # operators list with crossover and mutation
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> policy = UCBPolicy(operators)
     >>> policy = UCBPolicy(operators)
-    >>> local_search = HillClimberFirstImprovment(initializer, evaluator, operators, policy, validator, maximise=True, verbose=False)
-    >>> algo = IteratedLocalSearch(initializer, evaluator, operators, policy, validator, localSearch=local_search, maximise=True, verbose=False)
-    >>> policy._occurences
+    >>> local_search = HillClimberFirstImprovment(initialiser, evaluator, operators, policy, validator, maximise=True, verbose=False)
+    >>> algo = IteratedLocalSearch(initialiser, evaluator, operators, policy, validator, localSearch=local_search, maximise=True, verbose=False)
+    >>> policy.occurences
     [0, 0]
     [0, 0]
     >>> solution = algo.run(100)
     >>> solution = algo.run(100)
     >>> type(solution).__name__
     >>> type(solution).__name__
     'BinarySolution'
     'BinarySolution'
-    >>> policy._occurences # one more due to first evaluation
+    >>> policy.occurences # one more due to first evaluation
     [51, 53]
     [51, 53]
     """
     """
     def __init__(self, operators, C=100., exp_rate=0.5):
     def __init__(self, operators, C=100., exp_rate=0.5):
+        """UCB Policy initialiser
+
+        Args:
+            operators: {[Operator]} -- list of selected operators for the algorithm
+            C: {float} -- The second half of the UCB equation adds exploration, with the degree of exploration being controlled by the hyper-parameter `C`.
+            exp_rate: {float} -- exploration rate (probability to choose randomly next operator)
+        """
+
+        # private members
         self._operators = operators
         self._operators = operators
-        self._rewards = [0. for o in self._operators]
-        self._occurences = [0 for o in self._operators]
         self._C = C
         self._C = C
         self._exp_rate = exp_rate
         self._exp_rate = exp_rate
 
 
+        # public members
+        self.rewards = [0. for o in self._operators]
+        self.occurences = [0 for o in self._operators]
+
     def select(self):
     def select(self):
         """Select using Upper Confidence Bound the next operator to use (using acquired rewards)
         """Select using Upper Confidence Bound the next operator to use (using acquired rewards)
 
 
@@ -76,7 +87,7 @@ class UCBPolicy(Policy):
             {Operator}: the selected operator
             {Operator}: the selected operator
         """
         """
 
 
-        indices = [i for i, o in enumerate(self._occurences) if o == 0]
+        indices = [i for i, o in enumerate(self.occurences) if o == 0]
 
 
         # random choice following exploration rate
         # random choice following exploration rate
         if np.random.uniform(0, 1) <= self._exp_rate:
         if np.random.uniform(0, 1) <= self._exp_rate:
@@ -88,12 +99,12 @@ class UCBPolicy(Policy):
 
 
             # if operator have at least be used one time
             # if operator have at least be used one time
             ucbValues = []
             ucbValues = []
-            nVisits = sum(self._occurences)
+            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))
+                ucbValue = self.rewards[i] + self._C * math.sqrt(
+                    math.log(nVisits) / (self.occurences[i] + 0.1))
                 ucbValues.append(ucbValue)
                 ucbValues.append(ucbValue)
 
 
             return self._operators[ucbValues.index(max(ucbValues))]
             return self._operators[ucbValues.index(max(ucbValues))]
@@ -123,7 +134,7 @@ class UCBPolicy(Policy):
 
 
         # default value of solution2 is current best solution
         # default value of solution2 is current best solution
         if solution2 is None and self._algo is not None:
         if solution2 is None and self._algo is not None:
-            solution2 = self._algo._bestSolution
+            solution2 = self._algo.getResult()
 
 
         # avoid use of crossover if only one solution is passed
         # avoid use of crossover if only one solution is passed
         if solution2 is None and operator._kind == KindOperator.CROSSOVER:
         if solution2 is None and operator._kind == KindOperator.CROSSOVER:
@@ -138,7 +149,7 @@ class UCBPolicy(Policy):
             newSolution = operator.apply(solution1)
             newSolution = operator.apply(solution1)
 
 
         # compute fitness of new solution
         # compute fitness of new solution
-        newSolution.evaluate(self._algo._evaluator)
+        newSolution.evaluate(self._algo.evaluator)
 
 
         # compute fitness improvment rate
         # compute fitness improvment rate
         if self._algo._maximise:
         if self._algo._maximise:
@@ -151,9 +162,9 @@ class UCBPolicy(Policy):
         operator_index = self._operators.index(operator)
         operator_index = self._operators.index(operator)
 
 
         if fir > 0:
         if fir > 0:
-            self._rewards[operator_index] += fir
+            self.rewards[operator_index] += fir
 
 
-        self._occurences[operator_index] += 1
+        self.occurences[operator_index] += 1
 
 
         logging.info("---- Obtaining %s" % (newSolution))
         logging.info("---- Obtaining %s" % (newSolution))
 
 

+ 7 - 1
macop/solutions/base.py

@@ -74,10 +74,16 @@ class Solution():
         """
         """
         self._data = data
         self._data = data
 
 
+    def setScore(self, score):
+        """
+        Set solution score as wished
+        """
+        self._score = score
+
     @staticmethod
     @staticmethod
     def random(size, validator=None):
     def random(size, validator=None):
         """
         """
-        Initialize solution using random data with validator or not
+        initialise solution using random data with validator or not
 
 
         Args:
         Args:
             size: {int} -- expected solution size to generate
             size: {int} -- expected solution size to generate

+ 3 - 3
macop/solutions/discrete.py

@@ -21,7 +21,7 @@ class BinarySolution(Solution):
     """
     """
     def __init__(self, data, size):
     def __init__(self, data, size):
         """
         """
-        Initialize binary solution using specific data
+        initialise binary solution using specific data
 
 
         Args:
         Args:
             data: {ndarray} --  array of binary values
             data: {ndarray} --  array of binary values
@@ -95,7 +95,7 @@ class CombinatoryIntegerSolution(Solution):
     """
     """
     def __init__(self, data, size):
     def __init__(self, data, size):
         """
         """
-        Initialize integer solution using specific data
+        initialise integer solution using specific data
 
 
         Args:
         Args:
             data: {ndarray} --  array of integer values
             data: {ndarray} --  array of integer values
@@ -163,7 +163,7 @@ class IntegerSolution(Solution):
     """
     """
     def __init__(self, data, size):
     def __init__(self, data, size):
         """
         """
-        Initialize integer solution using specific data
+        initialise integer solution using specific data
 
 
         Args:
         Args:
             data: {ndarray} --  array of binary values
             data: {ndarray} --  array of binary values

+ 3 - 3
setup.py

@@ -73,14 +73,14 @@ class TestCommand(distutils.command.check.check):
 
 
 setup(
 setup(
     name='macop',
     name='macop',
-    version='1.0.9',
+    version='1.0.10',
     description='Minimalist And Customisable Optimisation Package',
     description='Minimalist And Customisable Optimisation Package',
     long_description=open('README.md').read(),
     long_description=open('README.md').read(),
     long_description_content_type='text/markdown',
     long_description_content_type='text/markdown',
     classifiers=[
     classifiers=[
-        'Development Status :: 4 - Beta',
+        'Development Status :: 5 - Production/Stable',
         'License :: OSI Approved :: MIT License',
         'License :: OSI Approved :: MIT License',
-        'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
         'Topic :: Scientific/Engineering',
         'Topic :: Scientific/Engineering',
         'Topic :: Utilities'
         'Topic :: Utilities'
     ],
     ],