Parcourir la source

Merge branch 'release/v1.0.11'

Jérôme BUISINE il y a 3 ans
Parent
commit
f6ec3d48f7
46 fichiers modifiés avec 2551 ajouts et 285 suppressions
  1. 0 1
      .github/workflows/docs.yml
  2. 1 1
      .github/workflows/python-app.yml
  3. 1 1
      .github/workflows/python-publish.yml
  4. 11 4
      CONTRIBUTING
  5. 22 7
      README.md
  6. 21 0
      docs/source/_static/css/custom.css
  7. 5 2
      docs/source/conf.py
  8. 13 0
      docs/source/description.rst
  9. 1353 0
      docs/source/documentations.rst
  10. 15 15
      docs/source/documentations/algorithms.rst
  11. 7 7
      docs/source/documentations/callbacks.rst
  12. 2 2
      docs/source/documentations/evaluators.rst
  13. 2 2
      docs/source/documentations/index.rst
  14. 5 5
      docs/source/documentations/operators.rst
  15. 1 1
      docs/source/documentations/policies.rst
  16. 3 3
      docs/source/documentations/solutions.rst
  17. 2 2
      docs/source/documentations/validator.rst
  18. 2 2
      docs/source/examples.rst
  19. 4 4
      docs/source/examples/qap/implementation.rst
  20. 3 3
      docs/source/examples/ubqp/implementation.rst
  21. 21 4
      docs/source/index.rst
  22. 308 0
      docs/source/qap_example.rst
  23. 230 0
      docs/source/ubqp_example.rst
  24. 1 1
      examples/knapsackExample.py
  25. 1 1
      examples/knapsackMultiExample.py
  26. 1 1
      examples/qapExample.py
  27. 65 17
      macop/algorithms/base.py
  28. 74 31
      macop/algorithms/mono.py
  29. 115 45
      macop/algorithms/multi.py
  30. 6 0
      macop/callbacks/base.py
  31. 10 11
      macop/callbacks/classicals.py
  32. 17 17
      macop/callbacks/multi.py
  33. 6 6
      macop/callbacks/policies.py
  34. 5 1
      macop/evaluators/base.py
  35. 28 8
      macop/evaluators/discrete/mono.py
  36. 7 1
      macop/evaluators/discrete/multi.py
  37. 23 0
      macop/operators/base.py
  38. 40 24
      macop/operators/discrete/crossovers.py
  39. 11 10
      macop/operators/discrete/mutators.py
  40. 6 1
      macop/policies/base.py
  41. 2 0
      macop/policies/classicals.py
  42. 42 19
      macop/policies/reinforcement.py
  43. 25 4
      macop/solutions/base.py
  44. 23 15
      macop/solutions/discrete.py
  45. 8 3
      paper.md
  46. 3 3
      setup.py

+ 0 - 1
.github/workflows/docs.yml

@@ -22,7 +22,6 @@ jobs:
     - name: Commit documentation changes
       run: |
         git clone https://github.com/jbuisine/macop.git --branch gh-pages --single-branch gh-pages
-        ls -l docs/_build
         cp -r docs/* gh-pages/docs
         cd gh-pages
         touch .nojekyll

+ 1 - 1
.github/workflows/python-app.yml

@@ -15,7 +15,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [3.6, 3.7, 3.8]
+        python-version: [3.8, 3.9]
 
     steps:
     - uses: actions/checkout@v2

+ 1 - 1
.github/workflows/python-publish.yml

@@ -18,7 +18,7 @@ jobs:
     - name: Set up Python
       uses: actions/setup-python@v2
       with:
-        python-version: '3.x'
+        python-version: '3.8'
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip

+ 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.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)
 
@@ -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.
 
+## 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
 
@@ -126,11 +133,11 @@ This project uses the [doctest](https://docs.python.org/3/library/doctest.html)
     >>> data = [0, 1, 0, 1, 1]
     >>> solution = BinarySolution(data, len(data))
     >>> # check data content
-    >>> sum(solution._data) == 3
+    >>> sum(solution.getData()) == 3
     True
     >>> # clone solution
     >>> solution_copy = solution.clone()
-    >>> all(solution_copy._data == solution._data)
+    >>> all(solution_copy._data == solution.getData())
 """
 ```
 

+ 22 - 7
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%">
 </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.
 
-## In `macop.algorihtms` module:
+### In `macop.algorihtms` module:
 
 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.
 
-## 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.
 
-## 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. 
 
-## 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.
 
@@ -58,8 +73,8 @@ Main idea about this Python package is that it does not which doesn't implement
 Fully documentation of package with examples is [available](https://jbuisine.github.io/macop). 
 
 You can also see examples of use:
--  in the [knapsackExample.py](https://github.com/jbuisine/macop/blob/master/examples/knapsackExample.py) for mono-objective instance.
--  in the [knapsackMultiExample.py](https://github.com/jbuisine/macop/blob/master/examples/knapsackMultiExample.py) for multi-objective instance.
+-  in the [knapsackExample.py](https://github.com/jbuisine/macop/blob/master/examples/knapsackExample.py) for mono-objective knapsack instance.
+-  in the [knapsackMultiExample.py](https://github.com/jbuisine/macop/blob/master/examples/knapsackMultiExample.py) for multi-objective knapsack instance.
 -  in the [qapExample.py](https://github.com/jbuisine/macop/blob/master/examples/qapExample.py) for mono-objective QAP instance.
 -  in the [ubqpExample.py](https://github.com/jbuisine/macop/blob/master/examples/ubqpExample.py) for mono-objective UBQP problem instance.
 
@@ -69,7 +84,7 @@ You can also see examples of use:
 git submodule add https://github.com/jbuisine/macop.git
 ```
 
-## Current projects which use `Macop`
+## Current projects which use `Macop`:
 
 - [@prise3d/noise-detection-attributes-optimization](https://github.com/prise-3d/noise-detection-attributes-optimization): use of a parent algorithm with the real (but very expensive) evaluation function, and then inner local searches which use a substitution model (a model that has learned to approximate the real cost function with a quick-to-evaluate function). Hence, two evaluation functions have been used in order to accelerate the search in the set of solutions.
 

+ 21 - 0
docs/source/_static/css/custom.css

@@ -6,6 +6,15 @@
     background-image: url("../logo_macop.png") !important;
 }
 
+@media only screen and (max-width: 900px) {
+
+    .header-logo {
+        background-size: 93px 30px !important;
+        height: 33px !important;
+        margin-top: 1px !important;
+    }
+}
+
 .pytorch-left-menu-search input[type="text"] {
     border-color: #009900 !important;
 }
@@ -35,6 +44,14 @@ div[id=api] colgroup :first-child {
     width: 60% !important;
 }
 
+table.docutils colgroup :first-child {
+    width: 60% !important;
+}
+
+table.colwidths-given colgroup :first-child {
+    width: 30% !important;
+}
+
 .rst-content a:link, .rst-content a:visited, .rst-content a:hover {
     color: #009900 !important;
 }
@@ -67,4 +84,8 @@ div[id=api] colgroup :first-child {
     display: block;
     margin-left: auto;
     margin-right: auto;
+}
+
+.tutorials-header .main-menu-open-button {
+    display: none !important;
 }

+ 5 - 2
docs/source/conf.py

@@ -25,9 +25,9 @@ copyright = '2021, Jérôme BUISINE'
 author = 'Jérôme BUISINE'
 
 # The short X.Y version
-version = '1.0.9'
+version = '1.0.11'
 # The full version, including alpha/beta/rc tags
-release = 'v1.0.9'
+release = 'v1.0.11'
 
 
 # -- General configuration ---------------------------------------------------
@@ -154,6 +154,9 @@ html_static_path = ['_static']
 # 'searchbox.html']``.
 #
 # html_sidebars = {}
+# html_sidebars = { '**': ['globaltoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html'] }
+
+html_sidebars = {'**': ['fulltoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html']}
 
 # -- Options for HTMLHelp output ---------------------------------------------
 

+ 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.
 
+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
 ------------

Fichier diff supprimé car celui-ci est trop grand
+ 1353 - 0
docs/source/documentations.rst


+ 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:
 
-- 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)
 - operators: {[Operator]} -- list of operator to use when launching algorithm
 - 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
 - currentSolution: {Solution} -- current solution managed for current evaluation comparison
 - bestSolution: {Solution} -- best solution found so far during running algorithm
-- callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initializing algorithm
+- 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)
 
 .. code-block:: python
@@ -51,7 +51,7 @@ She is composed of few default attributes:
     class Algorithm():
 
         def __init__(self,
-                    initializer,
+                    initialiser,
                     evaluator,
                     operators,
                     policy,
@@ -88,7 +88,7 @@ She is composed of few default attributes:
 
         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.
 ``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:: 
     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.
 
 .. 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
             """
 
-            # by default use of mother method to initialize variables
+            # by default use of mother method to initialise variables
             super().run(evaluations)
 
-            # initialize current solution and best solution
+            # initialise current solution and best solution
             self.initRun()
 
             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})
 
     # 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 = lambda x=5: BinarySolution.random(x, validator)
@@ -287,7 +287,7 @@ Let's called this new algorithm ``IteratedLocalSearch``:
     class IteratedLocalSearch(Algorithm):
         
         def __init__(self,
-                    initializer,
+                    initialiser,
                     evaluator,
                     operators,
                     policy,
@@ -297,7 +297,7 @@ Let's called this new algorithm ``IteratedLocalSearch``:
                     parent=None,
                     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
             self._localSearch = localSearch
@@ -311,10 +311,10 @@ Let's called this new algorithm ``IteratedLocalSearch``:
             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)
 
-            # initialize current solution
+            # initialise current solution
             self.initRun()
 
             # 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})
 
     # 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 = 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)
     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 
     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
                 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)
 
                     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
                     solutionData = list(map(int, data[1].split(' ')))
 
-                    # avoid uninitialized solution
+                    # avoid uninitialised solution
                     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
-                    self._algo._bestSolution._data = np.array(solutionData)
+                    self._algo._bestsolution.getData() = np.array(solutionData)
                     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
             """
 
-            # by default use of mother method to initialize variables
+            # by default use of mother method to initialise variables
             super().run(evaluations)
 
-            # initialize current solution
+            # initialise current solution
             self.initRun()
 
             # 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
             fitness = 0
-            for index, elem in enumerate(solution._data):
+            for index, elem in enumerate(solution.getData()):
                 fitness += self._data['worths'][index] * elem
 
             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
 

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

@@ -1,5 +1,5 @@
-Documentation
-==============
+A tour of Macop
+===================
 
 .. image:: ../_static/logo_macop.png
    :width: 300 px

+ 5 - 5
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()
 
             # swicth values
-            if copy_solution._data[cell]:
-                copy_solution._data[cell] = 0
+            if copy_solution.getData()[cell]:
+                copy_solution.getData()[cell] = 0
             else:
-                copy_solution._data[cell] = 1
+                copy_solution.getData()[cell] = 1
 
             # return the new obtained 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_solution = solution2.clone()
 
-            copy_solution._data[splitIndex:] = firstData[splitIndex:]
+            copy_solution.getData()[splitIndex:] = firstData[splitIndex:]
 
             return copy_solution
 
@@ -193,7 +193,7 @@ We can now use the crossover operator created to generate new solutions. Here is
     # obtaining new solution using crossover
     offspring = crossover.apply(solution1, solution2)
 
-.. warning::
+.. tip::
     The developed ``SimpleCrossover`` is available into ``macop.operators.discrete.crossovers.SimpleCrossover`` in **Macop**.
     However, the choice of halves of the merged data is made randomly.
 

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

@@ -100,7 +100,7 @@ We can now use this operator choice policy to update our current solution:
     # pass two solutions in parameters in case of selected crossover operator
     new_solution = policy.apply(solution1, solution2)
 
-.. warning::
+.. caution::
     By default if ``solution2`` parameter is not provided into ``policy.apply`` method for crossover, the best solution known is used from the algorithm linked to the ``policy``.
 
 Updating solutions is therefore now possible with our policy. It is high time to dive into the process of optimizing solutions and digging into our research space.

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

@@ -47,7 +47,7 @@ Some specific methods are available:
         @staticmethod
         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. 
 
-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::
     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
             data = np.random.randint(2, size=size)
-            # initialize new solution using constructor
+            # initialise new solution using constructor
             solution = BinarySolution(data, size)
 
             # check if validator is set

+ 2 - 2
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):
             # add weight if current object is set to 1
-            weight_sum += w * solution._data[i]
+            weight_sum += w * solution.getData()[i]
         
         # validation condition
         return weight_sum <= 15
@@ -62,7 +62,7 @@ We can now generate solutions randomly by passing our validation function as a p
     solution = BinarySolution.random(5, validator)
 
 
-.. warning::
+.. caution::
     If the search space for valid solutions is very small compared to the overall search space, this can involve a considerable time for validating the solution and therefore obtaining a solution.
 
 The validation of a solution is therefore now possible. In the next part we will focus on the evaluation of a solution.

+ 2 - 2
docs/source/examples.rst

@@ -9,8 +9,8 @@ Implemented problem examples
 .. toctree::
    :maxdepth: 1
 
-   examples/qap/index
-   examples/ubqp/index
+   qap_example
+   ubqp_example
 
 
 Available code examples

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

@@ -63,15 +63,15 @@ So we are going to create a class that will inherit from the abstract class ``ma
             {float} -- fitness score of solution
         """
         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]
 
         return fitness
 
 The cost function for the quadratic problem is now well defined.
 
-.. warning::
+.. tip::
     The class proposed here, is available in the Macop package ``macop.evaluators.discrete.mono.QAPEvaluator``.
 
 Running algorithm
@@ -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)
     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")
             return False
         return True

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

@@ -60,15 +60,15 @@ So we are going to create a class that will inherit from the abstract class ``ma
             {float} -- fitness score of solution
         """
         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
 
         return fitness
 
 The cost function for the Unconstrained binary quadratic problem is now well defined.
 
-.. warning::
+.. tip::
     The class proposed here, is available in the Macop package ``macop.evaluators.discrete.mono.UBQPEvaluator``.
 
 Running algorithm

+ 21 - 4
docs/source/index.rst

@@ -6,17 +6,20 @@ Minimalist And Customisable Optimisation Package
    :align: center
 
 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.
 
+
+Contents
+~~~~~~~~
+
 .. toctree::
    :maxdepth: 1
-   :caption: Contents:
 
    description
 
-   documentations/index
+   documentations
 
    api
 
@@ -24,8 +27,22 @@ What's **Macop** ?
 
    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
-------------------
+~~~~~~~~~~~~~~~~~~
 
 * :ref:`genindex`
 * :ref:`modindex`

+ 308 - 0
docs/source/qap_example.rst

@@ -0,0 +1,308 @@
+===============================
+Quadratric Assignment Problem
+===============================
+
+This example will deal with the use of the **Macop** package in relation to a quadratic assignment problem (QAP). We will use a known example of this problem to associate a set of facilities (:math:`F`) to a set of locations (:math:`L`).
+
+.. image:: _static//examples/qap/factories_qap.png
+   :width: 50 %
+   :align: center
+   :alt: Example of QAP facilities to locations problem
+
+
+.. note:: 
+   The full code for what will be proposed in this example is available: qapExample.py_.
+
+.. _qapExample.py: https://github.com/jbuisine/macop/blob/master/examples/qapExample.py
+
+
+QAP problem definition
+======================
+
+The quadratic assignment problem (QAP) was introduced by Koopmans and Beckman in 1957 in the context of locating "indivisible economic activities". The objective of the problem is to assign a set of facilities to a set of locations in such a way as to minimize the total assignment cost. The assignment cost for a pair of facilities is a function of the flow between the facilities and the distance between the locations of the facilities.
+
+Location assignment example
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Consider a **facility location problem** with **four** facilities (and **four** locations). One possible assignment is shown in the figure below: facility 4 is assigned to location 1, facility 1 
+is assigned to location 2, facility 3 is assigned to location 3, and facility 2 is assigned to location 3. This assignment can be written as the permutation :math:`p=\{4,1,3,2\}`, 
+which means that facility 4 is assigned to location 1, facility 1 is assigned to location 2, facility 3 is assigned to location 3, and facility 2 is assigned to location 3. 
+In the figure, the line between a pair of facilities indicates that there is required flow between the facilities, and the thickness of the line increases with the value of the flow. 
+
+.. image:: _static//examples/qap/factories_qap.png
+   :width: 50 %
+   :align: center
+   :alt: Example of QAP facilities to locations problem
+
+
+To calculate the assignment cost of the permutation, the required flows between facilities and the distances between locations are needed.
+
+
+.. tabularcolumns:: |p{1cm}|p{1cm}|p{1cm}|p{1cm}|
+
+.. csv-table:: flow of the current facilities
+   :header: facility `i`, facility `j`, flow( `i`\, `j` )
+   :widths: 2, 2, 3
+
+   1, 4, 4
+   3, 4, 10  
+   3, 1, 8
+   2, 1, 6  
+
+
+.. csv-table:: distances of between locations
+   :header: location `i`, location `j`, distances( `i`\, `j` )
+   :widths: 2, 2, 3
+
+   1, 2, 42
+   1, 3, 30  
+   2, 3, 41
+   3, 4, 23  
+
+
+Then, the assignment cost of the permutation can be computed as:
+
+:math:`f(1,4)⋅d(1,2)+f(3,4)⋅d(1,3)+f(1,3)⋅d(2,3)+f(3,2)⋅d(3,4)` 
+with result :math:`4⋅42+10⋅30+8⋅41+6⋅23=934`.
+
+Note that this permutation is not the optimal solution.
+
+Mathematical definition
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Sets**
+
+- :math:`N=\{1,2,⋯,n\}`
+- :math:`S_n=\phi:N→N` is the set of all permutations
+
+**Parameters**
+
+- :math:`F=(f_{ij})` is an :math:`n×n` matrix where :math:`f_{ij}` is the required flow between facilities :math:`i` and :math:`j`
+- :math:`D=(d_{ij})` is an :math:`n×n` matrix where :math:`d_{ij}` is the distance between locations :math:`i` and :math:`j`.
+
+**Optimization Problem**
+
+- :math:`min_{ϕ∈S_n}\sum_{i=1}^{n}{\sum_{j=1}^{n}{f_{ij}⋅d_{\phi(i)\phi(j)}}}`
+
+The assignment of facilities to locations is represented by a permutation :math:`\phi`, where :math:`\phi(i)` is the location to which facility :math:`i` is assigned. Each individual product :math:`f_{ij}⋅d_{\phi(i)\phi(j)}` is the cost of assigning facility :math:`i` to location :math:`\phi(i)` and facility :math:`j` to location :math:`\phi(j)`.
+
+QAP Problem instance generation
+===============================
+
+To define our quadratic assignment problem instance, we will use the available mQAP_ multi-objective quadratic problem generator. 
+
+Genration of the instance
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We will limit ourselves here to a single objective for the purposes of this example. The file **makeQAPuni.cc**, will be used to generate the instance.
+
+.. code:: bash
+
+    g++ makeQAPuni.cc -o mQAPGenerator
+    ./mQAPGenerator -n 100 -k 1 -f 30 -d 80 -s 42 > qap_instance.txt
+
+with the following parameters:
+
+- **-n** positive integer: number of facilities/locations;
+- **-k** positive integer: number of objectives;
+- **-f** positive integer: maximum flow between facilities;
+- **-d** positive integer: maximum distance between locations;
+- **-s** positive long: random seed.
+
+The generated qap_instance.txt_ file contains the two matrices :math:`F` and :math:`D` and define our instance problem.
+
+.. _mQAP: https://www.cs.bham.ac.uk/~jdk/mQAP/
+
+.. _qap_instance.txt: https://github.com/jbuisine/macop/blob/master/examples/instances/qap/qap_instance.txt
+
+
+Load data instance
+~~~~~~~~~~~~~~~~~~
+
+
+We are now going to load this instance via a Python code which will be useful to us later on:
+
+.. code:: Python
+
+    qap_instance_file = 'qap_instance.txt'
+
+    n = 100 # the instance size
+
+    with open(qap_instance_file, 'r') as f:
+        file_data = f.readlines()
+        print(f'Instance information {file_data[0]}')
+
+        D_lines = file_data[1:n + 1]
+        D_data = ''.join(D_lines).replace('\n', '')
+
+        F_lines = file_data[n:2 * n + 1]
+        F_data = ''.join(F_lines).replace('\n', '')
+
+    D_matrix = np.fromstring(D_data, dtype=float, sep=' ').reshape(n, n)
+    print(f'D matrix shape: {D_matrix.shape}')
+    F_matrix = np.fromstring(F_data, dtype=float, sep=' ').reshape(n, n)
+    print(f'F matrix shape: {F_matrix.shape}')
+
+.. note::
+    As we know the size of our instance and the structure of the document, it is quite quick to look for the lines related to the :math:`F` and :math:`D` matrices.
+
+Macop QAP implementation
+========================
+
+Let's see how it is possible with the use of the **Macop** package to implement and deal with this QAP instance problem.
+
+Solution structure definition
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Firstly, we are going to use a type of solution that will allow us to define the structure of our solutions.
+
+The available macop.solutions.discrete.CombinatoryIntegerSolution_ type of solution within the Macop package represents exactly what one would wish for. 
+I.e. a solution that stores a sequence of integers relative to the size of the problem, the order of which is not sorted.
+
+Let's see an example of its use:
+
+.. code:: python
+
+    from macop.solutions.discrete import CombinatoryIntegerSolution
+    
+    solution = CombinatoryIntegerSolution.random(10)
+    print(solution)
+
+
+The resulting solution obtained:
+
+.. code:: bash
+
+    Combinatory integer solution [2 9 8 1 7 6 0 4 3 5]
+
+
+QAP Evaluator
+~~~~~~~~~~~~~
+
+Now that we have the structure of our solutions, and the means to generate them, we will seek to evaluate them.
+
+To do this, we need to create a new evaluator specific to our problem and the relative evaluation function:
+
+- :math:`min_{ϕ∈S_n}\sum_{i=1}^{n}{\sum_{j=1}^{n}{f_{ij}⋅d_{\phi(i)\phi(j)}}}`
+
+So we are going to create a class that will inherit from the abstract class macop.evaluators.base.Evaluator_:
+
+
+.. code:: python
+
+    from macop.evaluators.base import Evaluator
+
+    class QAPEvaluator(Evaluator):
+    """QAP evaluator class which enables to compute QAP solution using specific `_data`
+
+    - stores into its `_data` dictionary attritute required measures when computing a QAP solution
+    - `_data['F']` matrix of size n x n with flows data between facilities (stored as numpy array)
+    - `_data['D']` matrix of size n x n with distances data between locations (stored as numpy array)
+    - `compute` method enables to compute and associate a score to a given QAP solution
+    """
+
+    def compute(self, solution):
+        """Apply the computation of fitness from solution
+
+        Args:
+            solution: {Solution} -- QAP solution instance
+    
+        Returns:
+            {float} -- fitness score of solution
+        """
+        fitness = 0
+        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]
+
+        return fitness
+
+The cost function for the quadratic problem is now well defined.
+
+.. tip::
+    The class proposed here, is available in the Macop package macop.evaluators.discrete.mono.QAPEvaluator_.
+
+Running algorithm
+~~~~~~~~~~~~~~~~~
+
+Now that the necessary tools are available, we will be able to deal with our problem and look for solutions in the search space of our QAP instance.
+
+Here we will use local search algorithms already implemented in **Macop**.
+
+If you are uncomfortable with some of the elements in the code that will follow, you can refer to the more complete **Macop** documentation_ that focuses more on the concepts and tools of the package.
+
+.. code:: python
+
+    # main imports
+    import numpy as np
+
+    # module imports
+    from macop.solutions.discrete import CombinatoryIntegerSolution
+    from macop.evaluators.discrete.mono import QAPEvaluator
+
+    from macop.operators.discrete.mutators import SimpleMutation
+
+    from macop.policies.classicals import RandomPolicy
+
+    from macop.algorithms.mono import IteratedLocalSearch as ILS
+    from macop.algorithms.mono import HillClimberFirstImprovment
+
+    # usefull instance data
+    n = 100
+    qap_instance_file = 'qap_instance.txt'
+
+    # default validator (check the consistency of our data, i.e. only unique element)
+    def validator(solution):
+        if len(list(solution.getData())) > len(set(list(solution.getData()))):
+            print("not valid")
+            return False
+        return True
+
+    # define init random solution
+    def init():
+        return CombinatoryIntegerSolution.random(n, validator)
+
+    # load qap instance
+    with open(qap_instance_file, 'r') as f:
+        file_data = f.readlines()
+        print(f'Instance information {file_data[0]}')
+
+        D_lines = file_data[1:n + 1]
+        D_data = ''.join(D_lines).replace('\n', '')
+
+        F_lines = file_data[n:2 * n + 1]
+        F_data = ''.join(F_lines).replace('\n', '')
+
+    D_matrix = np.fromstring(D_data, dtype=float, sep=' ').reshape(n, n)
+    print(f'D matrix shape: {D_matrix.shape}')
+    F_matrix = np.fromstring(F_data, dtype=float, sep=' ').reshape(n, n)
+    print(f'F matrix shape: {F_matrix.shape}')
+
+    # only one operator here
+    operators = [SimpleMutation()]
+
+    # random policy even if list of solution has only one element
+    policy = RandomPolicy(operators)
+
+    # use of loaded data from QAP instance
+    evaluator = QAPEvaluator(data={'F': F_matrix, 'D': D_matrix})
+
+    # passing global evaluation param from ILS
+    hcfi = HillClimberFirstImprovment(init, evaluator, operators, policy, validator, maximise=False, verbose=True)
+    algo = ILS(init, evaluator, operators, policy, validator, localSearch=hcfi, maximise=False, verbose=True)
+
+    # run the algorithm
+    bestSol = algo.run(10000, ls_evaluations=100)
+
+    print('Solution for QAP instance score is {}'.format(evaluator.compute(bestSol)))
+
+
+QAP problem solving is now possible with **Macop**. As a reminder, the complete code is available in the qapExample.py_ file.
+
+.. _qapExample.py: https://github.com/jbuisine/macop/blob/master/examples/qapExample.py
+.. _documentation: https://jbuisine.github.io/macop/_build/html/documentations
+
+
+.. _macop.solutions.discrete.CombinatoryIntegerSolution: macop/macop.solutions.discrete.html#macop.solutions.discrete.CombinatoryIntegerSolution
+.. _macop.evaluators.base.Evaluator: macop/macop.evaluators.base.html#macop.evaluators.base.Evaluator
+.. _macop.evaluators.discrete.mono.QAPEvaluator: macop/macop.evaluators.discrete.mono.html#macop.evaluators.discrete.mono.QAPEvaluator

Fichier diff supprimé car celui-ci est trop grand
+ 230 - 0
docs/source/ubqp_example.rst


+ 1 - 1
examples/knapsackExample.py

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

+ 1 - 1
examples/qapExample.py

@@ -33,7 +33,7 @@ filepath = "data/checkpoints_qap.csv"
 
 # default validator
 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")
         return False
     return True

+ 65 - 17
macop/algorithms/base.py

@@ -22,7 +22,7 @@ class Algorithm():
 
 
     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)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy implementation strategy to select operators
@@ -31,11 +31,11 @@ class Algorithm():
         verbose: {bool} -- verbose or not information about the algorithm
         currentSolution: {Solution} -- current solution managed for current evaluation comparison
         bestSolution: {Solution} -- best solution found so far during running algorithm
-        callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initializing algorithm
+        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)
     """
     def __init__(self,
-                 initializer,
+                 initialiser,
                  evaluator,
                  operators,
                  policy,
@@ -43,13 +43,27 @@ class Algorithm():
                  maximise=True,
                  parent=None,
                  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
-        self._initializer = initializer
-        self._evaluator = evaluator
         self._operators = operators
-        self._policy = policy
-        self._validator = validator
         self._callbacks = []
         self._bestSolution = None
         self._currentSolution = None
@@ -75,9 +89,9 @@ class Algorithm():
 
         # also track reference for policy
         if self._parent is not None:
-            self._policy.setAlgo(self.getParent())
+            self.policy.setAlgo(self.getParent())
         else:
-            self._policy.setAlgo(self)
+            self.policy.setAlgo(self)
 
     def addCallback(self, callback):
         """Add new callback to algorithm specifying usefull parameters
@@ -127,15 +141,33 @@ class Algorithm():
         """
         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):
         """
-        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
-        self._currentSolution.evaluate(self._evaluator)
+        self._currentSolution.evaluate(self.evaluator)
         self.increaseEvaluation()
 
         # keep in memory best known solution (current solution)
@@ -167,6 +199,22 @@ class Algorithm():
 
         return self._numberOfEvaluations
 
+    def getEvaluation(self):
+        """Get the current number of evaluation
+
+        Returns:
+            {int} -- current number of evaluation
+        """
+        return self._numberOfEvaluations
+
+    def setEvaluation(self, evaluations):
+        """Set the current number of evaluation
+
+        Args:
+            evaluations: {int} -- current expected number of evaluation
+        """
+        self._numberOfEvaluations = evaluations
+
     def getGlobalMaxEvaluation(self):
         """Get the global max number of evaluation (if inner algorithm)
 
@@ -206,7 +254,7 @@ class Algorithm():
         Note: 
             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):
         """
@@ -221,13 +269,13 @@ class Algorithm():
         """
 
         # 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
         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
         else:
             logging.info("-- New solution is not valid %s" % sol)
@@ -246,7 +294,7 @@ class Algorithm():
         Returns:
             {bool} -- `True` if better
         """
-        if not solution.isValid(self._validator):
+        if not solution.isValid(self.validator):
             return False
 
         # depending of problem to solve (maximizing or minimizing)

+ 74 - 31
macop/algorithms/mono.py

@@ -16,7 +16,7 @@ class HillClimberFirstImprovment(Algorithm):
     - And do these steps until a number of evaluation (stopping criterion) is reached.
 
     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)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy class implementation strategy to select operators
@@ -24,34 +24,44 @@ class HillClimberFirstImprovment(Algorithm):
         maximise: {bool} -- specify kind of optimisation problem 
         currentSolution: {Solution} -- current solution managed for current evaluation
         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:
 
     >>> import random
+    >>>
     >>> # operators import
     >>> from macop.operators.discrete.crossovers import SimpleCrossover
     >>> from macop.operators.discrete.mutators import SimpleMutation
+    >>>
     >>> # policy import
     >>> from macop.policies.classicals import RandomPolicy
-    >>> # solution and algorithm
+    >>>
+    >>> # solution and algorithm imports
     >>> from macop.solutions.discrete import BinarySolution
     >>> from macop.algorithms.mono import HillClimberFirstImprovment
+    >>>
     >>> # evaluator import
     >>> from macop.evaluators.discrete.mono import KnapsackEvaluator
+    >>>
     >>> # evaluator initialization (worths objects passed into data)
     >>> problem_size = 20
     >>> worths = [ random.randint(0, 20) for i in range(problem_size) ]
     >>> evaluator = KnapsackEvaluator(data={'worths': worths})
+    >>>
     >>> # validator specification (based on weights of each objects)
     >>> 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._data) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=20: BinarySolution.random(x, validator)
+    >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
+    >>>
+    >>> # initialiser function for binary solution using specific solution size
+    >>> initialiser = lambda x=20: BinarySolution.random(x, validator)
+    >>>
     >>> # operators list with crossover and mutation
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> 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
     >>> solution = algo.run(100)
     >>> solution._score
@@ -68,10 +78,10 @@ class HillClimberFirstImprovment(Algorithm):
             {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)
 
-        # initialize current solution and best solution
+        # initialise current solution and best solution
         self.initRun()
 
         solutionSize = self._currentSolution._size
@@ -119,7 +129,7 @@ class HillClimberBestImprovment(Algorithm):
     - And do these steps until a number of evaluation (stopping criterion) is reached.
 
     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)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy class implementation strategy to select operators
@@ -127,34 +137,44 @@ class HillClimberBestImprovment(Algorithm):
         maximise: {bool} -- specify kind of optimisation problem 
         currentSolution: {Solution} -- current solution managed for current evaluation
         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:
 
     >>> import random
+    >>>
     >>> # operators import
     >>> from macop.operators.discrete.crossovers import SimpleCrossover
     >>> from macop.operators.discrete.mutators import SimpleMutation
+    >>>
     >>> # policy import
     >>> from macop.policies.classicals import RandomPolicy
-    >>> # solution and algorithm
+    >>>
+    >>> # solution and algorithm imports
     >>> from macop.solutions.discrete import BinarySolution
     >>> from macop.algorithms.mono import HillClimberBestImprovment
+    >>>
     >>> # evaluator import
     >>> from macop.evaluators.discrete.mono import KnapsackEvaluator
+    >>>
     >>> # evaluator initialization (worths objects passed into data)
     >>> problem_size = 20
     >>> worths = [ random.randint(0, 20) for i in range(problem_size) ]
     >>> evaluator = KnapsackEvaluator(data={'worths': worths})
+    >>>
     >>> # validator specification (based on weights of each objects)
     >>> 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._data) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=20: BinarySolution.random(x, validator)
+    >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
+    >>>
+    >>> # initialiser function for binary solution using specific solution size
+    >>> initialiser = lambda x=20: BinarySolution.random(x, validator)
+    >>>
     >>> # operators list with crossover and mutation
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> 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
     >>> solution = algo.run(100)
     >>> solution._score
@@ -171,10 +191,10 @@ class HillClimberBestImprovment(Algorithm):
             {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)
 
-        # initialize current solution and best solution
+        # initialise current solution and best solution
         self.initRun()
 
         solutionSize = self._currentSolution._size
@@ -222,7 +242,7 @@ class IteratedLocalSearch(Algorithm):
     - Restart this process until stopping critirion (number of expected evaluations).
 
     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)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy class implementation strategy to select operators
@@ -231,43 +251,53 @@ class IteratedLocalSearch(Algorithm):
         currentSolution: {Solution} -- current solution managed for current evaluation
         bestSolution: {Solution} -- best solution found so far during running algorithm
         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:
 
     >>> import random
+    >>>
     >>> # operators import
     >>> from macop.operators.discrete.crossovers import SimpleCrossover
     >>> from macop.operators.discrete.mutators import SimpleMutation
+    >>>
     >>> # policy import
     >>> from macop.policies.classicals import RandomPolicy
-    >>> # solution and algorithm
+    >>>
+    >>> # import for solution and algorithm
     >>> from macop.solutions.discrete import BinarySolution
     >>> from macop.algorithms.mono import IteratedLocalSearch
     >>> from macop.algorithms.mono import HillClimberFirstImprovment
+    >>>
     >>> # evaluator import
     >>> from macop.evaluators.discrete.mono import KnapsackEvaluator
+    >>>
     >>> # evaluator initialization (worths objects passed into data)
     >>> problem_size = 20
     >>> worths = [ random.randint(0, 20) for i in range(problem_size) ]
     >>> evaluator = KnapsackEvaluator(data={'worths': worths})
+    >>>
     >>> # validator specification (based on weights of each objects)
     >>> 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._data) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=20: BinarySolution.random(x, validator)
+    >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
+    >>>
+    >>> # initialiser function with lambda function
+    >>> initialiser = lambda x=20: BinarySolution.random(x, validator)
+    >>>
     >>> # operators list with crossover and mutation
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> 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)
-    >>> # run the algorithm
+    >>> 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 using specific number of evaluations for local search
     >>> solution = algo.run(100, ls_evaluations=10)
     >>> solution._score
     137
     """
     def __init__(self,
-                 initializer,
+                 initialiser,
                  evaluator,
                  operators,
                  policy,
@@ -276,8 +306,21 @@ class IteratedLocalSearch(Algorithm):
                  maximise=True,
                  parent=None,
                  verbose=True):
+        """Iterated Local Search Algorithm initialisation with use of specific LocalSearch {Algorithm} instance
+
+        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__(initializer, evaluator, operators, policy, validator,
+        super().__init__(initialiser, evaluator, operators, policy, validator,
                          maximise, parent, verbose)
 
         # specific local search associated with current algorithm
@@ -297,13 +340,13 @@ class IteratedLocalSearch(Algorithm):
             {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)
 
         # enable resuming for ILS
         self.resume()
 
-        # initialize current solution
+        # initialise current solution
         self.initRun()
 
         # add same callbacks

+ 115 - 45
macop/algorithms/multi.py

@@ -20,7 +20,7 @@ class MOEAD(Algorithm):
         mu: {int} -- number of sub problems
         T: {[float]} -- number of neightbors for each sub problem
         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)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy class implementation strategy to select operators
@@ -30,37 +30,49 @@ class MOEAD(Algorithm):
         population: [{Solution}] -- population of solution, one for each sub problem
         pfPop: [{Solution}] -- pareto front population
         weights: [[{float}]] -- random weights used for custom mu sub problems
-        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
+    >>>
     >>> # operators import
     >>> from macop.operators.discrete.crossovers import SimpleCrossover
     >>> from macop.operators.discrete.mutators import SimpleMutation
+    >>>
     >>> # policy import
     >>> from macop.policies.classicals import RandomPolicy
-    >>> # solution and algorithm
+    >>>
+    >>> # solution and algorithm imports
     >>> from macop.solutions.discrete import BinarySolution
     >>> from macop.algorithms.multi import MOEAD
+    >>>
     >>> # evaluator import
     >>> from macop.evaluators.discrete.mono import KnapsackEvaluator
+    >>>
     >>> # evaluator initialization (worths objects passed into data)
     >>> problem_size = 20
     >>> worths1 = [ random.randint(0, 20) for i in range(problem_size) ]
     >>> evaluator1 = KnapsackEvaluator(data={'worths': worths1})
     >>> worths2 = [ random.randint(10, 15) for i in range(problem_size) ]
     >>> evaluator2 = KnapsackEvaluator(data={'worths': worths2})
+    >>>
     >>> # validator specification (based on weights of each objects)
     >>> 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._data) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=20: BinarySolution.random(x, validator)
+    >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
+    >>>
+    >>> # initialiser function for binary solution using specific solution size
+    >>> initialiser = lambda x=20: BinarySolution.random(x, validator)
+    >>>
     >>> # operators list with crossover and mutation
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> policy = RandomPolicy(operators)
+    >>>
     >>> # 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
     >>> pf_solutions = algo.run(100)
+    >>>
     >>> # check size of expected pareto
     >>> len(pf_solutions)
     33
@@ -68,7 +80,7 @@ class MOEAD(Algorithm):
     def __init__(self,
                  mu,
                  T,
-                 initializer,
+                 initialiser,
                  evaluator,
                  operators,
                  policy,
@@ -76,13 +88,28 @@ class MOEAD(Algorithm):
                  maximise=True,
                  parent=None,
                  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
-        self._initializer = initializer
-        self._evaluator = evaluator
+        self.initialiser = initialiser
+        self.evaluator = evaluator
+        self.validator = validator
+
         self._operators = operators
-        self._policy = policy
-        self._validator = validator
+        self.policy = policy
         self._callbacks = []
 
         # by default
@@ -103,7 +130,7 @@ class MOEAD(Algorithm):
             operator.setAlgo(self)
 
         # by default track reference for policy
-        self._policy.setAlgo(self)
+        self.policy.setAlgo(self)
 
         if mu < T:
             raise ValueError('`mu` cannot be less than `T`')
@@ -114,7 +141,7 @@ class MOEAD(Algorithm):
         self._mu = mu
         self._T = T
 
-        # initialize neighbors for each sub problem
+        # initialise neighbors for each sub problem
         self.setNeighbors()
 
         weights = []
@@ -150,7 +177,7 @@ class MOEAD(Algorithm):
             # use copy of list to keep track for each sub problem
             subProblem = MOSubProblem(i,
                                       weights[i],
-                                      initializer,
+                                      initialiser,
                                       sub_evaluator,
                                       operators.copy(),
                                       policy,
@@ -161,7 +188,8 @@ class MOEAD(Algorithm):
 
             self._subProblems.append(subProblem)
 
-        self._population = [None for n in range(self._mu)]
+        self.population = [None for n in range(self._mu)]
+
         self._pfPop = []
 
         # ref point based on number of evaluators
@@ -174,7 +202,7 @@ class MOEAD(Algorithm):
 
     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
         pass
@@ -190,18 +218,18 @@ class MOEAD(Algorithm):
             {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)
 
         # enable callback resume for MOEAD
         self.resume()
 
-        # initialize each sub problem if no backup
+        # initialise each sub problem if no backup
         for i in range(self._mu):
 
             if self._subProblems[i]._bestSolution is None:
                 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 len(self._pfPop) == 0:
@@ -233,7 +261,7 @@ class MOEAD(Algorithm):
                         self._subProblems[j]._bestSolution = newSolution
 
                         # update population solution for this sub problem
-                        self._population[j] = newSolution
+                        self.population[j] = newSolution
 
                         improvment = True
 
@@ -252,7 +280,7 @@ class MOEAD(Algorithm):
                     break
 
         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()
@@ -301,19 +329,19 @@ class MOEAD(Algorithm):
     def updateRefPoint(self, solution):
 
         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:
-            for i in range(len(self._evaluator)):
+            for i in range(len(self.evaluator)):
                 if solution.scores[i] < self._refPoint[i]:
-                    self._refPoint[i] = solution._scores[i]
+                    self._refPoint[i] = solution.scores[i]
 
     def paretoFront(self, population):
 
         paFront = []
         indexes = []
-        nObjectives = len(self._evaluator)
+        nObjectives = len(self.evaluator)
         nSolutions = len(population)
 
         # find dominated solution
@@ -333,12 +361,12 @@ class MOEAD(Algorithm):
                 nDominated = 0
 
                 # 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 population[i]._scores[k] < population[j]._scores[k]:
+                        if population[i].scores[k] < population[j].scores[k]:
                             nDominated += 1
                     else:
-                        if population[i]._scores[k] > population[j]._scores[k]:
+                        if population[i].scores[k] > population[j].scores[k]:
                             nDominated += 1
 
                 if nDominated == nObjectives:
@@ -352,6 +380,22 @@ class MOEAD(Algorithm):
 
         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):
         """Display end message into `run` method
         """
@@ -362,7 +406,7 @@ class MOEAD(Algorithm):
         )
 
         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)
 
@@ -371,10 +415,10 @@ class MOEAD(Algorithm):
         logging.info("-- Pareto front :")
 
         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):
-        return f"{type(self).__name__} using {type(self._population).__name__}"
+        return f"{type(self).__name__} using {type(self.population).__name__}"
 
 
 class MOSubProblem(Algorithm):
@@ -383,7 +427,7 @@ class MOSubProblem(Algorithm):
     Attributes:
         index: {int} -- sub problem index
         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)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy class implementation strategy to select operators
@@ -392,41 +436,53 @@ class MOSubProblem(Algorithm):
         verbose: {bool} -- verbose or not information about the algorithm
         currentSolution: {Solution} -- current solution managed for current evaluation
         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:
 
     >>> import random
+    >>>
     >>> # operators import
     >>> from macop.operators.discrete.crossovers import SimpleCrossover
     >>> from macop.operators.discrete.mutators import SimpleMutation
+    >>>
     >>> # policy import
     >>> from macop.policies.classicals import RandomPolicy
-    >>> # solution and algorithm
+    >>>
+    >>> # solution and algorithm imports
     >>> from macop.solutions.discrete import BinarySolution
     >>> from macop.algorithms.multi import MOEAD, MOSubProblem
+    >>>
     >>> # evaluator import
     >>> from macop.evaluators.discrete.mono import KnapsackEvaluator
+    >>>
     >>> # evaluator initialization (worths objects passed into data)
     >>> problem_size = 20
     >>> worths1 = [ random.randint(0, 20) for i in range(problem_size) ]
     >>> evaluator1 = KnapsackEvaluator(data={'worths': worths1})
     >>> worths2 = [ random.randint(10, 15) for i in range(problem_size) ]
     >>> evaluator2 = KnapsackEvaluator(data={'worths': worths2})
+    >>>
     >>> # validator specification (based on weights of each objects)
     >>> 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._data) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=20: BinarySolution.random(x, validator)
+    >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
+    >>>
+    >>> # initialiser function for binary solution using specific solution size
+    >>> initialiser = lambda x=20: BinarySolution.random(x, validator)
+    >>>
     >>> # operators list with crossover and mutation
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> 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
     >>> sub_problem_weights = [0.4, 0.6]
     >>> sub_evaluator = WeightedSum(data={'evaluators': [evaluator1, evaluator2], 'weights': sub_problem_weights})
+    >>>
     >>> # 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
     >>> solution = subProblem.run(100)
     >>> solution._score
@@ -443,6 +499,20 @@ class MOSubProblem(Algorithm):
                  maximise=True,
                  parent=None,
                  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,
                          maximise, parent)
@@ -463,10 +533,10 @@ class MOSubProblem(Algorithm):
             {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)
 
-        # initialize solution if necessary
+        # initialise solution if necessary
         if self._bestSolution is None:
             self.initRun()
 
@@ -477,7 +547,7 @@ class MOSubProblem(Algorithm):
         for _ in range(evaluations):
 
             # keep reference of sub problem used
-            self._policy.setAlgo(self)
+            self.policy.setAlgo(self)
 
             # update solution using policy
             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
     """
     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._every = every

+ 10 - 11
macop/callbacks/classicals.py

@@ -25,7 +25,7 @@ class BasicCheckpoint(Callback):
         Check if necessary to do backup based on `every` variable
         """
         # get current best solution
-        solution = self._algo._bestSolution
+        solution = self._algo.getResult()
 
         currentEvaluation = self._algo.getGlobalEvaluation()
 
@@ -35,9 +35,9 @@ class BasicCheckpoint(Callback):
             logging.info("Checkpoint is done into " + self._filepath)
 
             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)
 
                 if index < solutionSize - 1:
@@ -71,26 +71,25 @@ class BasicCheckpoint(Callback):
                 globalEvaluation = int(data[0])
 
                 if self._algo.getParent() is not None:
-                    self._algo.getParent(
-                    )._numberOfEvaluations = globalEvaluation
+                    self._algo.getParent().setEvaluation(globalEvaluation)
                 else:
-                    self._algo._numberOfEvaluations = globalEvaluation
+                    self._algo.setEvaluation(globalEvaluation)
 
                 # get best solution data information
                 solutionData = list(map(int, data[1].split(' ')))
 
-                if self._algo._bestSolution is None:
-                    self._algo._bestSolution = self._algo._initializer()
+                if self._algo.getResult() is None:
+                    self._algo.setDefaultResult(self._algo.initialiser())
 
-                self._algo._bestSolution._data = 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_text(self._algo,
                        f'Checkpoint found from `{self._filepath}` file.')
             macop_text(
                 self._algo,
-                f'Restart algorithm from evaluation {self._algo._numberOfEvaluations}.'
+                f'Restart algorithm from evaluation {self._algo.getEvaluation()}.'
             )
         else:
             macop_text(

+ 17 - 17
macop/callbacks/multi.py

@@ -25,7 +25,7 @@ class MultiCheckpoint(Callback):
         Check if necessary to do backup based on `every` variable
         """
         # get current population
-        population = self._algo._population
+        population = self._algo.population
 
         currentEvaluation = self._algo.getGlobalEvaluation()
 
@@ -38,9 +38,9 @@ class MultiCheckpoint(Callback):
 
                 for solution in population:
                     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)
 
                         if index < solutionSize - 1:
@@ -48,8 +48,8 @@ class MultiCheckpoint(Callback):
 
                     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'
 
@@ -80,18 +80,18 @@ class MultiCheckpoint(Callback):
                         else:
                             self._algo._numberOfEvaluations = globalEvaluation
 
-                    nObjectives = len(self._algo._evaluator)
+                    nObjectives = len(self._algo.evaluator)
                     scores = [float(s) for s in data[1:nObjectives + 1]]
 
                     # get best solution data information
                     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_text(
@@ -139,9 +139,9 @@ class ParetoCheckpoint(Callback):
 
                 for solution in pfPop:
                     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)
 
                         if index < solutionSize - 1:
@@ -149,8 +149,8 @@ class ParetoCheckpoint(Callback):
 
                     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'
 
@@ -170,14 +170,14 @@ class ParetoCheckpoint(Callback):
 
                     data = line.replace(';\n', '').split(';')
 
-                    nObjectives = len(self._algo._evaluator)
+                    nObjectives = len(self._algo.evaluator)
                     scores = [float(s) for s in data[0:nObjectives]]
 
                     # get best solution data information
                     solutionData = list(map(int, data[-1].split(' ')))
 
                     self._algo._pfPop[i]._data = solutionData
-                    self._algo._pfPop[i]._scores = scores
+                    self._algo._pfPop[i].scores = scores
 
             macop_text(
                 self._algo,

+ 6 - 6
macop/callbacks/policies.py

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

+ 5 - 1
macop/evaluators/base.py

@@ -10,7 +10,11 @@ from abc import abstractmethod
 class Evaluator():
     """Abstract Evaluator class which enables to compute solution using specific `_data` 
     """
-    def __init__(self, data):
+    def __init__(self, data: dict):
+        """Initialise Evaluator instance which stores into its `_data` dictionary attritute required measures when computing a solution
+        Args:
+            data: {dict} -- specific data dictionnary
+        """
         self._data = data
 
     @abstractmethod

+ 28 - 8
macop/evaluators/discrete/mono.py

@@ -14,16 +14,20 @@ class KnapsackEvaluator(Evaluator):
     Example:
 
     >>> import random
+    >>>
     >>> # binary solution import
     >>> from macop.solutions.discrete import BinarySolution
+    >>>
     >>> # evaluator import
     >>> from macop.evaluators.discrete.mono import KnapsackEvaluator
     >>> solution_data = [1, 0, 0, 1, 1, 0, 1, 0]
     >>> size = len(solution_data)
     >>> solution = BinarySolution(solution_data, size)
+    >>>
     >>> # evaluator initialization (worths objects passed into data)
     >>> worths = [ random.randint(5, 20) for i in range(size) ]
     >>> evaluator = KnapsackEvaluator(data={'worths': worths})
+    >>>
     >>> # compute solution score
     >>> evaluator.compute(solution)
     40
@@ -38,11 +42,12 @@ class KnapsackEvaluator(Evaluator):
             {float} -- fitness score of solution
         """
         fitness = 0
-        for index, elem in enumerate(solution._data):
+        for index, elem in enumerate(solution.getData()):
             fitness += self._data['worths'][index] * elem
 
         return fitness
 
+
 class QAPEvaluator(Evaluator):
     """Quadratic Assignment Problem (QAP) evaluator class which enables to compute qap solution using specific `_data`
 
@@ -57,13 +62,17 @@ class QAPEvaluator(Evaluator):
 
     >>> import random
     >>> import numpy as np
+    >>>
     >>> # combinatory solution import
     >>> from macop.solutions.discrete import CombinatoryIntegerSolution
+    >>>
     >>> # evaluator import
     >>> from macop.evaluators.discrete.mono import QAPEvaluator
+    >>>
     >>> # define problem data using QAP example instance
     >>> qap_instance_file = 'examples/instances/qap/qap_instance.txt'
     >>> n = 100 # problem size
+    >>>
     >>> # loading data
     >>> f = open(qap_instance_file, 'r')
     >>> file_data = f.readlines()
@@ -74,10 +83,13 @@ class QAPEvaluator(Evaluator):
     >>> D_matrix = np.fromstring(D_data, dtype=float, sep=' ').reshape(n, n)
     >>> F_matrix = np.fromstring(F_data, dtype=float, sep=' ').reshape(n, n)
     >>> f.close()    
+    >>>
     >>> # create evaluator instance using loading data
     >>> evaluator = QAPEvaluator(data={'F': F_matrix, 'D': D_matrix})
+    >>>
     >>> # create new random combinatory solution using n, the instance QAP size
     >>> solution = CombinatoryIntegerSolution.random(n)
+    >>>
     >>> # compute solution score
     >>> evaluator.compute(solution)
     6397983.0
@@ -92,14 +104,15 @@ class QAPEvaluator(Evaluator):
             {float} -- fitness score of solution
         """
         fitness = 0
-        for index_i, val_i in enumerate(solution._data):
-            for index_j, val_j in enumerate(solution._data):
-                fitness += self._data['F'][index_i, index_j] * self._data['D'][val_i, val_j]
+        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]
 
         return fitness
 
 
-
 class UBQPEvaluator(Evaluator):
     """Unconstrained Binary Quadratic Programming (UBQP) evaluator class which enables to compute UBQP solution using specific `_data`
 
@@ -111,30 +124,37 @@ class UBQPEvaluator(Evaluator):
 
     >>> import random
     >>> import numpy as np
+    >>>
     >>> # binary solution import
     >>> from macop.solutions.discrete import BinarySolution
+    >>>
     >>> # evaluator import
     >>> from macop.evaluators.discrete.mono import UBQPEvaluator
+    >>>
     >>> # define problem data using UBQP example instance
     >>> ubqp_instance_file = 'examples/instances/ubqp/ubqp_instance.txt'
     >>> n = 100 # problem size
+    >>>
     >>> # loading data
     >>> f = open(ubqp_instance_file, 'r')
     >>> file_data = f.readlines()
+    >>>
     >>> # get all string floating point values of matrix
     >>> Q_data = ''.join([ line.replace('\\n', '') for line in file_data[8:] ])
     >>> # load the concatenate obtained string
     >>> Q_matrix = np.fromstring(Q_data, dtype=float, sep=' ').reshape(n, n)
     >>> f.close()    
+    >>>
     >>> # create evaluator instance using loading data
     >>> evaluator = UBQPEvaluator(data={'Q': Q_matrix})
+    >>>
     >>> # create new random combinatory solution using n, the instance QAP size
     >>> solution = BinarySolution.random(n)
+    >>>
     >>> # compute solution score
     >>> evaluator.compute(solution)
     477.0
     """
-
     def compute(self, solution):
         """Apply the computation of fitness from solution
 
@@ -145,8 +165,8 @@ class UBQPEvaluator(Evaluator):
             {float} -- fitness score of solution
         """
         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
 
         return fitness

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

@@ -13,21 +13,27 @@ class WeightedSum(Evaluator):
     - `compute` method enables to compute and associate a tuples of scores to a given solution
     
     >>> import random
+    >>>
     >>> # binary solution import
     >>> from macop.solutions.discrete import BinarySolution
+    >>>
     >>> # evaluators imports
     >>> from macop.evaluators.discrete.mono import KnapsackEvaluator
     >>> from macop.evaluators.discrete.multi import WeightedSum
     >>> solution_data = [1, 0, 0, 1, 1, 0, 1, 0]
     >>> size = len(solution_data)
     >>> solution = BinarySolution(solution_data, size)
+    >>>
     >>> # evaluator 1 initialization (worths objects passed into data)
     >>> worths1 = [ random.randint(5, 20) for i in range(size) ]
     >>> evaluator1 = KnapsackEvaluator(data={'worths': worths1})
+    >>>
     >>> # evaluator 2 initialization (worths objects passed into data)
     >>> worths2 = [ random.randint(10, 15) for i in range(size) ]
     >>> evaluator2 = KnapsackEvaluator(data={'worths': worths2})
     >>> weighted_evaluator = WeightedSum(data={'evaluators': [evaluator1, evaluator2], 'weights': [0.3, 0.7]})
+    >>>
+    >>> # compute score and check with expected one
     >>> weighted_score = weighted_evaluator.compute(solution)
     >>> expected_score = evaluator1.compute(solution) * 0.3 + evaluator2.compute(solution) * 0.7
     >>> weighted_score == expected_score
@@ -53,7 +59,7 @@ class WeightedSum(Evaluator):
         ]
 
         # associate objectives scores to solution
-        solution._scores = scores
+        solution.scores = scores
 
         return sum(
             [scores[i] * w for i, w in enumerate(self._data['weights'])])

+ 23 - 0
macop/operators/base.py

@@ -17,6 +17,8 @@ class Operator():
     """
     @abstractmethod
     def __init__(self):
+        """Abstract Operator initialiser
+        """
         pass
 
     @abstractmethod
@@ -45,9 +47,19 @@ class Mutation(Operator):
         kind: {KindOperator} -- specify the kind of operator
     """
     def __init__(self):
+        """Mutation initialiser in order to specify kind of Operator
+        """
         self._kind = KindOperator.MUTATOR
 
     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
 
 
@@ -58,7 +70,18 @@ class Crossover(Operator):
         kind: {KindOperator} -- specify the kind of operator
     """
     def __init__(self):
+        """Crossover initialiser in order to specify kind of Operator
+        """
         self._kind = KindOperator.CROSSOVER
 
     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

+ 40 - 24
macop/operators/discrete/crossovers.py

@@ -19,37 +19,45 @@ class SimpleCrossover(Crossover):
     >>> # operators import
     >>> from macop.operators.discrete.crossovers import SimpleCrossover
     >>> from macop.operators.discrete.mutators import SimpleMutation
+    >>>
     >>> # policy import
     >>> from macop.policies.reinforcement import UCBPolicy
-    >>> # solution and algorithm
+    >>>
+    >>> # solution and algorithm imports
     >>> from macop.solutions.discrete import BinarySolution
     >>> from macop.algorithms.mono import IteratedLocalSearch
     >>> from macop.algorithms.mono import HillClimberFirstImprovment
+    >>>
     >>> # evaluator import
     >>> from macop.evaluators.discrete.mono import KnapsackEvaluator
+    >>>
     >>> # evaluator initialization (worths objects passed into data)
     >>> worths = [ random.randint(0, 20) for i in range(10) ]
     >>> evaluator = KnapsackEvaluator(data={'worths': worths})
+    >>>
     >>> # validator specification (based on weights of each objects)
     >>> weights = [ random.randint(20, 30) for i in range(10) ]
-    >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution._data) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=10: BinarySolution.random(x, validator)
+    >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
+    >>>
+    >>> # initialiser function for binary solution using specific solution size
+    >>> initialiser = lambda x=10: BinarySolution.random(x, validator)
+    >>>
     >>> # operators list with crossover and mutation
     >>> simple_crossover = SimpleCrossover()
     >>> simple_mutation = SimpleMutation()
     >>> operators = [simple_crossover, simple_mutation]
     >>> 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
     >>> best_solution = algo.run(100)
-    >>> list(best_solution._data)
+    >>> list(best_solution.getData())
     [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)
-    >>> list(offspring_solution._data)
+    >>> list(offspring_solution.getData())
     [0, 1, 1, 0, 1, 0, 1, 1, 0, 1]
     """
     def apply(self, solution1, solution2=None):
@@ -74,9 +82,9 @@ class SimpleCrossover(Crossover):
         splitIndex = int(size / 2)
 
         if random.uniform(0, 1) > 0.5:
-            copy_solution._data[splitIndex:] = firstData[splitIndex:]
+            copy_solution.getData()[splitIndex:] = firstData[splitIndex:]
         else:
-            copy_solution._data[:splitIndex] = firstData[:splitIndex]
+            copy_solution.getData()[:splitIndex] = firstData[:splitIndex]
 
         return copy_solution
 
@@ -92,37 +100,45 @@ class RandomSplitCrossover(Crossover):
     >>> # operators import
     >>> from macop.operators.discrete.crossovers import RandomSplitCrossover
     >>> from macop.operators.discrete.mutators import SimpleMutation
+    >>>
     >>> # policy import
     >>> from macop.policies.reinforcement import UCBPolicy
-    >>> # solution and algorithm
+    >>>
+    >>> # solution and algorithm imports
     >>> from macop.solutions.discrete import BinarySolution
     >>> from macop.algorithms.mono import IteratedLocalSearch
     >>> from macop.algorithms.mono import HillClimberFirstImprovment
+    >>>
     >>> # evaluator import
     >>> from macop.evaluators.discrete.mono import KnapsackEvaluator
+    >>>
     >>> # evaluator initialization (worths objects passed into data)
     >>> worths = [ random.randint(0, 20) for i in range(10) ]
     >>> evaluator = KnapsackEvaluator(data={'worths': worths})
+    >>>
     >>> # validator specification (based on weights of each objects)
     >>> weights = [ random.randint(20, 30) for i in range(10) ]
-    >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution._data) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=10: BinarySolution.random(x, validator)
+    >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
+    >>>
+    >>> # initialiser function for binary solution using specific solution size
+    >>> initialiser = lambda x=10: BinarySolution.random(x, validator)
+    >>>
     >>> # operators list with crossover and mutation
     >>> random_split_crossover = RandomSplitCrossover()
     >>> simple_mutation = SimpleMutation()
     >>> operators = [random_split_crossover, simple_mutation]
     >>> 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
     >>> best_solution = algo.run(100)
-    >>> list(best_solution._data)
+    >>> list(best_solution.getData())
     [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)
-    >>> list(offspring_solution._data)
+    >>> list(offspring_solution.getData())
     [0, 0, 0, 1, 1, 0, 0, 1, 0, 0]
     """
     def apply(self, solution1, solution2=None):
@@ -146,8 +162,8 @@ class RandomSplitCrossover(Crossover):
         splitIndex = random.randint(0, size)
 
         if random.uniform(0, 1) > 0.5:
-            copy_solution._data[splitIndex:] = firstData[splitIndex:]
+            copy_solution.getData()[splitIndex:] = firstData[splitIndex:]
         else:
-            copy_solution._data[:splitIndex] = firstData[:splitIndex]
+            copy_solution.getData()[:splitIndex] = firstData[:splitIndex]
 
         return copy_solution

+ 11 - 10
macop/operators/discrete/mutators.py

@@ -20,11 +20,11 @@ class SimpleMutation(Mutation):
     >>> from macop.solutions.discrete import BinarySolution
     >>> from macop.operators.discrete.mutators import SimpleMutation
     >>> solution = BinarySolution.random(5)
-    >>> list(solution._data)
+    >>> list(solution.getData())
     [1, 0, 0, 0, 1]
     >>> mutator = SimpleMutation()
     >>> mutation_solution = mutator.apply(solution)
-    >>> list(mutation_solution._data)
+    >>> list(mutation_solution.getData())
     [0, 0, 1, 0, 1]
     """
     def apply(self, solution):
@@ -49,11 +49,12 @@ class SimpleMutation(Mutation):
             firstCell = random.randint(0, size - 1)
             secondCell = random.randint(0, size - 1)
 
-        temp = copy_solution._data[firstCell]
+        temp = copy_solution.getData()[firstCell]
 
         # swicth values
-        copy_solution._data[firstCell] = copy_solution._data[secondCell]
-        copy_solution._data[secondCell] = temp
+        copy_solution.getData()[firstCell] = copy_solution.getData(
+        )[secondCell]
+        copy_solution.getData()[secondCell] = temp
 
         return copy_solution
 
@@ -70,11 +71,11 @@ class SimpleBinaryMutation(Mutation):
     >>> from macop.solutions.discrete import BinarySolution
     >>> from macop.operators.discrete.mutators import SimpleBinaryMutation
     >>> solution = BinarySolution.random(5)
-    >>> list(solution._data)
+    >>> list(solution.getData())
     [0, 1, 0, 0, 0]
     >>> mutator = SimpleBinaryMutation()
     >>> mutation_solution = mutator.apply(solution)
-    >>> list(mutation_solution._data)
+    >>> list(mutation_solution.getData())
     [1, 1, 0, 0, 0]
     """
     def apply(self, solution):
@@ -94,9 +95,9 @@ class SimpleBinaryMutation(Mutation):
         copy_solution = solution.clone()
 
         # swicth values
-        if copy_solution._data[cell]:
-            copy_solution._data[cell] = 0
+        if copy_solution.getData()[cell]:
+            copy_solution.getData()[cell] = 0
         else:
-            copy_solution._data[cell] = 1
+            copy_solution.getData()[cell] = 1
 
         return copy_solution

+ 6 - 1
macop/policies/base.py

@@ -14,6 +14,11 @@ class Policy():
         operators: {[Operator]} -- list of selected operators for the algorithm
     """
     def __init__(self, operators):
+        """Initialise new Policy instance using specific list of operators
+
+        Args:
+            operators: [{}] -- list of operators to use
+        """
         self._operators = operators
 
     @abstractmethod
@@ -45,7 +50,7 @@ class Policy():
 
         # default value of solution2 is current best solution
         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
         if solution2 is None and operator._kind == KindOperator.CROSSOVER:

+ 2 - 0
macop/policies/classicals.py

@@ -20,6 +20,8 @@ class RandomPolicy(Policy):
     >>> from macop.operators.discrete.crossovers import SimpleCrossover
     >>> from macop.operators.discrete.mutators import SimpleMutation
     >>> from macop.policies.classicals import RandomPolicy
+    >>>
+    >>> # create policy instance and select next operator to apply using policy
     >>> policy = RandomPolicy([SimpleCrossover(), SimpleMutation()])
     >>> operator = policy.select()
     >>> type(operator).__name__

+ 42 - 19
macop/policies/reinforcement.py

@@ -23,52 +23,75 @@ class UCBPolicy(Policy):
 
     Attributes:
         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`.
+        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)
         rewards: {[float]} -- list of summed rewards obtained for each operator
         occurrences: {[int]} -- number of use (selected) of each operator
 
+    The value of attribute ``C`` will allow us to specify whether we wish to exploit or explore further in relation to our earned rewards. 
+    A low value of ``C`` (e.g. 2) will allow more exploitation, while a high value of ``C`` (e.g. 1000) will allow exploration.
+
+    The ``exp_rate`` variable avoids using an operator too much and allows to explore from time to time (especially if the variable ``C`` has a small value). Typical value for ``exp_rate`` can be 0.9.
+
     Example:
 
     >>> # operators import
     >>> from macop.operators.discrete.crossovers import SimpleCrossover
     >>> from macop.operators.discrete.mutators import SimpleMutation
+    >>>
     >>> # policy import
     >>> from macop.policies.reinforcement import UCBPolicy
+    >>>
     >>> # solution and algorithm
     >>> from macop.solutions.discrete import BinarySolution
     >>> from macop.algorithms.mono import IteratedLocalSearch
     >>> from macop.algorithms.mono import HillClimberFirstImprovment
+    >>>
     >>> # evaluator import
     >>> from macop.evaluators.discrete.mono import KnapsackEvaluator
     >>> # evaluator initialization (worths objects passed into data)
+    >>>
     >>> worths = [ random.randint(0, 20) for i in range(20) ]
     >>> evaluator = KnapsackEvaluator(data={'worths': worths})
+    >>>
     >>> # validator specification (based on weights of each objects)
     >>> weights = [ random.randint(5, 30) for i in range(20) ]
-    >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution._data) if value == 1]) < 200 else False
-    >>> # initializer function with lambda function
-    >>> initializer = lambda x=20: BinarySolution.random(x, validator)
+    >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.getData()) if value == 1]) < 200 else False
+    >>>
+    >>> # initialiser function with lambda function
+    >>> initialiser = lambda x=20: BinarySolution.random(x, validator)
+    >>>
     >>> # operators list with crossover and mutation
     >>> operators = [SimpleCrossover(), SimpleMutation()]
     >>> 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]
     >>> solution = algo.run(100)
     >>> type(solution).__name__
     'BinarySolution'
-    >>> policy._occurences # one more due to first evaluation
+    >>> policy.occurences # one more due to first evaluation
     [51, 53]
     """
-    def __init__(self, operators, C=100., exp_rate=0.5):
+    def __init__(self, operators, C=100., exp_rate=0.9):
+        """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._rewards = [0. for o in self._operators]
-        self._occurences = [0 for o in self._operators]
         self._C = C
         self._exp_rate = exp_rate
 
+        # public members
+        self.rewards = [0. for o in self._operators]
+        self.occurences = [0 for o in self._operators]
+
     def select(self):
         """Select using Upper Confidence Bound the next operator to use (using acquired rewards)
 
@@ -76,7 +99,7 @@ class UCBPolicy(Policy):
             {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
         if np.random.uniform(0, 1) <= self._exp_rate:
@@ -88,12 +111,12 @@ class UCBPolicy(Policy):
 
             # if operator have at least be used one time
             ucbValues = []
-            nVisits = sum(self._occurences)
+            nVisits = sum(self.occurences)
 
             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)
 
             return self._operators[ucbValues.index(max(ucbValues))]
@@ -123,7 +146,7 @@ class UCBPolicy(Policy):
 
         # default value of solution2 is current best solution
         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
         if solution2 is None and operator._kind == KindOperator.CROSSOVER:
@@ -138,7 +161,7 @@ class UCBPolicy(Policy):
             newSolution = operator.apply(solution1)
 
         # compute fitness of new solution
-        newSolution.evaluate(self._algo._evaluator)
+        newSolution.evaluate(self._algo.evaluator)
 
         # compute fitness improvment rate
         if self._algo._maximise:
@@ -151,9 +174,9 @@ class UCBPolicy(Policy):
         operator_index = self._operators.index(operator)
 
         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))
 

+ 25 - 4
macop/solutions/base.py

@@ -8,7 +8,7 @@ from copy import deepcopy
 class Solution():
     """Base abstract solution class structure
     
-    - stores solution data representation into `data` attribute
+    - stores solution data representation into ndarray `data` attribute
     - get size (shape) of specific data representation
     - stores the score of the solution
     """
@@ -17,8 +17,8 @@ class Solution():
         Abstract solution class constructor
 
         Attributes:
-            data: {ndarray} --  array of binary values
-            size: {int} -- size of binary array values
+            data: {ndarray} --  ndarray of values
+            size: {int} -- size of ndarray values
             score: {float} -- fitness score value
         """
         self._data = data
@@ -59,10 +59,31 @@ class Solution():
         """
         return self._score
 
+    def getData(self):
+        """
+        Returns solution data
+
+        Returns:
+            {ndarray} -- data values
+        """
+        return self._data
+
+    def setData(self, data):
+        """
+        Set solution data
+        """
+        self._data = data
+
+    def setScore(self, score):
+        """
+        Set solution score as wished
+        """
+        self._score = score
+
     @staticmethod
     def random(size, validator=None):
         """
-        Initialize solution using random data with validator or not
+        initialise solution using random data with validator or not
 
         Args:
             size: {int} -- expected solution size to generate

+ 23 - 15
macop/solutions/discrete.py

@@ -21,7 +21,7 @@ class BinarySolution(Solution):
     """
     def __init__(self, data, size):
         """
-        Initialize binary solution using specific data
+        initialise binary solution using specific data
 
         Args:
             data: {ndarray} --  array of binary values
@@ -30,15 +30,17 @@ class BinarySolution(Solution):
         Example:
 
         >>> from macop.solutions.discrete import BinarySolution
+        >>>
         >>> # build of a solution using specific data and size
         >>> data = [0, 1, 0, 1, 1]
         >>> solution = BinarySolution(data, len(data))
+        >>>
         >>> # check data content
-        >>> sum(solution._data) == 3
+        >>> sum(solution.getData()) == 3
         True
         >>> # clone solution
         >>> solution_copy = solution.clone()
-        >>> all(solution_copy._data == solution._data)
+        >>> all(solution_copy._data == solution.getData())
         True
         """
         super().__init__(np.array(data), size)
@@ -58,9 +60,11 @@ class BinarySolution(Solution):
         Example:
 
         >>> from macop.solutions.discrete import BinarySolution
-        >>> validator = lambda solution: True if sum(solution._data) > 5 else False
+        >>>
+        >>> # generate random solution using specific validator
+        >>> validator = lambda solution: True if sum(solution.getData()) > 5 else False
         >>> solution = BinarySolution.random(10, validator)
-        >>> sum(solution._data) > 5
+        >>> sum(solution.getData()) > 5
         True
         """
 
@@ -95,7 +99,7 @@ class CombinatoryIntegerSolution(Solution):
     """
     def __init__(self, data, size):
         """
-        Initialize integer solution using specific data
+        initialise integer solution using specific data
 
         Args:
             data: {ndarray} --  array of integer values
@@ -105,10 +109,10 @@ class CombinatoryIntegerSolution(Solution):
         >>> import numpy as np
         >>> data = np.arange(5)
         >>> solution = CombinatoryIntegerSolution(data, 5)
-        >>> sum(solution._data) == 10
+        >>> sum(solution.getData()) == 10
         True
         >>> solution_copy = solution.clone()
-        >>> all(solution_copy._data == solution._data)
+        >>> all(solution_copy._data == solution.getData())
         True
         """
         super().__init__(data, size)
@@ -128,9 +132,11 @@ class CombinatoryIntegerSolution(Solution):
         Example:
 
         >>> from macop.solutions.discrete import CombinatoryIntegerSolution
-        >>> validator = lambda solution: True if sum(solution._data) > 5 else False
+        >>>
+        >>> # generate random solution using specific validator
+        >>> validator = lambda solution: True if sum(solution.getData()) > 5 else False
         >>> solution = CombinatoryIntegerSolution.random(5, validator)
-        >>> sum(solution._data) > 5
+        >>> sum(solution.getData()) > 5
         True
         """
 
@@ -163,7 +169,7 @@ class IntegerSolution(Solution):
     """
     def __init__(self, data, size):
         """
-        Initialize integer solution using specific data
+        initialise integer solution using specific data
 
         Args:
             data: {ndarray} --  array of binary values
@@ -176,10 +182,10 @@ class IntegerSolution(Solution):
         >>> np.random.seed(42)
         >>> data = np.random.randint(5, size=10)
         >>> solution = IntegerSolution(data, 10)
-        >>> sum(solution._data)
+        >>> sum(solution.getData())
         28
         >>> solution_copy = solution.clone()
-        >>> all(solution_copy._data == solution._data)
+        >>> all(solution_copy._data == solution.getData())
         True
         """
         super().__init__(data, size)
@@ -201,9 +207,11 @@ class IntegerSolution(Solution):
         >>> from macop.solutions.discrete import IntegerSolution
         >>> import numpy as np
         >>> np.random.seed(42)
-        >>> validator = lambda solution: True if sum(solution._data) > 5 else False
+        >>>
+        >>> # generate random solution using specific validator
+        >>> validator = lambda solution: True if sum(solution.getData()) > 5 else False
         >>> solution = IntegerSolution.random(5, validator)
-        >>> sum(solution._data) > 10
+        >>> sum(solution.getData()) > 10
         True
         """
 

Fichier diff supprimé car celui-ci est trop grand
+ 8 - 3
paper.md


+ 3 - 3
setup.py

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