Parcourir la source

add of algorithms documentation

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

+ 1 - 1
README.md

@@ -3,7 +3,7 @@
 ![](https://img.shields.io/github/workflow/status/jbuisine/macop/build?style=flat-square) ![](https://img.shields.io/pypi/v/macop?style=flat-square) ![](https://img.shields.io/pypi/dm/macop?style=flat-square)
 
 <p align="center">
-    <img src="https://github.com/jbuisine/macop/blob/master/logo_macop.png" alt="" width="50%">
+    <img src="https://github.com/jbuisine/macop/blob/master/docs/source/_static/logo_macop.png" alt="" width="50%">
 </p>
 
 

BIN
docs/figures/search_space.kra


BIN
docs/figures/search_space.kra~


BIN
docs/figures/search_space.png~


BIN
docs/figures/search_space_simple.kra


BIN
docs/figures/search_space_simple.kra~


BIN
docs/source/_static/documentation/search_space.png


BIN
docs/source/_static/documentation/search_space_simple.png


BIN
docs/source/_static/logo_macop.png


logo_macop.png → docs/source/_static/logo_macop.png~


+ 364 - 2
docs/source/documentations/algorithms.rst

@@ -1,2 +1,364 @@
-8. Optimisation process
-=======================
+Optimisation process
+=======================
+
+Let us now tackle the interesting part concerning the search for optimum solutions in our research space.
+
+Find local and global optima
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Overall, in an optimization process, we will seek to find the best, or the best solutions that minimize or maximize our objective function (fitness score obtained) in order to respond to our problem.
+
+.. image:: ../_static/documentation/search_space.png
+   :width:  800 px
+   :align: center
+
+Sometimes, the search space can be very simple. A local search can provide access to the global optimum as shown in figure (a) above. 
+In other cases, the search space is more complex. It may be necessary to explore more rather than exploit in order to get out of a convex zone and not find the global optimum but only a local opmatime solution. 
+This problem is illustrated in figure (b).
+
+Abstract algorithm class
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+An abstract class is proposed within Macop to generalize the management of an algorithm and therefore of a heuristic. 
+It is located in the ``macop.algorithms.base`` module. 
+
+We will pay attention to the different methods of which she is composed. This class enables to manage some common usages of operation research algorithms:
+
+- initialization function of solution
+- validator function to check if solution is valid or not (based on some criteria)
+- evaluation function to give fitness score to a solution
+- operators used in order to update solution during search process
+- policy process applied when choosing next operator to apply
+- callbacks function in order to do some relative stuff every number of evaluation or reload algorithm state
+- parent algorithm associated to this new algorithm instance (hierarchy management)
+
+She is composed of few default attributes:
+
+- initializer: {function} -- basic function strategy to initialize 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
+- validator: {function} -- basic function to check if solution is valid or not under some constraints
+- maximise: {bool} -- specify kind of optimisation problem 
+- verbose: {bool} -- verbose or not information about the algorithm
+- currentSolution: {Solution} -- current solution managed for current evaluation comparison
+- bestSolution: {Solution} -- best solution found so far during running algorithm
+- callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initializing algorithm
+- parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
+
+.. code-block:: python
+
+    class Algorithm():
+
+        def __init__(self,
+                    initializer,
+                    evaluator,
+                    operators,
+                    policy,
+                    validator,
+                    maximise=True,
+                    parent=None,
+                    verbose=True):
+            ...
+
+        def addCallback(self, callback):
+            """
+            Add new callback to algorithm specifying usefull parameters
+            """
+            ...
+
+        def resume(self):
+            """
+            Resume algorithm using Callback instances
+            """
+            ...
+
+        def getParent(self):
+            """
+            Recursively find the main parent algorithm attached of the current algorithm
+            """
+            ...
+
+        def setParent(self, parent):
+            """
+            Set parent algorithm to current algorithm
+            """
+            ...
+
+
+        def initRun(self):
+            """
+            Initialize the current solution and best solution using the `initialiser` function
+            """
+            ...
+
+        def increaseEvaluation(self):
+            """
+            Increase number of evaluation once a solution is evaluated for each dependant algorithm (parents hierarchy)
+            """
+            ...
+                
+        def getGlobalEvaluation(self):
+            """
+            Get the global number of evaluation (if inner algorithm)
+            """
+            ...
+
+        def getGlobalMaxEvaluation(self):
+            """
+            Get the global max number of evaluation (if inner algorithm)
+            """
+            ...
+
+        def stop(self):
+            """
+            Global stopping criteria (check for parents algorithm hierarchy too)
+            """
+            ...
+
+        def evaluate(self, solution):
+            """
+            Evaluate a solution using evaluator passed when intialize algorithm
+            """
+            ...
+
+        def update(self, solution):
+            """
+            Apply update function to solution using specific `policy`
+            Check if solution is valid after modification and returns it
+            """
+            ...
+
+        def isBetter(self, solution):
+            """
+            Check if solution is better than best found
+            """
+            ...
+
+        def run(self, evaluations):
+            """
+            Run the specific algorithm following number of evaluations to find optima
+            """
+            ...
+
+        def progress(self):
+            """
+            Log progress and apply callbacks if necessary
+            """
+            ...
+
+
+The notion of hierarchy between algorithms is introduced here. We can indeed have certain dependencies between algorithms. 
+The methods ``increaseEvaluation``, ``getGlobalEvaluation`` and ``getGlobalMaxEvaluation`` ensure that the expected global number of evaluations is correctly managed, just like the ``stop`` method for the search stop criterion.
+
+The ``evaluate``, ``update`` and ``isBetter`` will be used a lot when looking for a solution in the search space. 
+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.
+
+.. 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.
+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::
+    The other methods such as ``addCallback``, ``resume`` and ``progress`` will be detailed in the next part focusing on the notion of callback.
+
+Local search algorithm
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We are going to carry out our first local search algorithm within our search space. A `local search` consists of starting from a solution, then applying a mutation or crossover operation to it, in order to obtain a new one. 
+This new solution is evaluated and retained if it is better. We will speak here of the notion of **neighborhood exploration**. The process is then completed in the same way. 
+The local search ends after a certain number of evaluations and the best evaluated solution obtained is returned.
+
+Let's implement an algorithm well known under the name of hill climber best improvment inheriting from the mother algorithm class and name it ``HillClimberBestImprovment``.
+
+
+.. code-block:: python
+
+    class HillClimberBestImprovment(Algorithm):
+
+        def run(self, evaluations):
+            """
+            Run a local search algorithm
+            """
+
+            # by default use of mother method to initialize variables
+            super().run(evaluations)
+
+            # initialize current solution and best solution
+            self.initRun()
+
+            solutionSize = self._currentSolution._size
+
+            # local search algorithm implementation
+            while not self.stop():
+
+                for _ in range(solutionSize):
+
+                    # update current solution using policy
+                    newSolution = self.update(self._currentSolution)
+
+                    # if better solution than currently, replace it
+                    if self.isBetter(newSolution):
+                        self._bestSolution = newSolution
+
+                    # increase number of evaluations
+                    self.increaseEvaluation()
+
+                    # stop algorithm if necessary
+                    if self.stop():
+                        break
+
+                # set new current solution using best solution found in this neighbor search
+                self._currentSolution = self._bestSolution
+            
+            return self._bestSolution
+
+Our algorithm is now ready to work. As previously, let us define two operators as well as a random choice strategy. 
+We will also need to define a **solution initialisation function** so that the algorithm can generate new solutions.
+
+
+.. code-block:: python
+
+    """
+    Problem instance definition
+    """
+    elements_score = [ 4, 2, 10, 1, 2 ] # worth of each object
+    elements_weight = [ 12, 1, 4, 1, 2 ] # weight of each object
+
+    # evaluator instance
+    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
+    
+    # initialiser instance using lambda with default param value
+    initialiser = lambda x=5: BinarySolution.random(x, validator)
+    
+    # operators list with crossover and mutation
+    operators = [SimpleCrossover(), SimpleMutation()]
+    
+    # policy random instance
+    policy = RandomPolicy(operators)
+    
+    # maximizing algorithm (relative to knapsack problem)
+    algo = HillClimberBestImprovment(initialiser, evaluator, operators, policy, validator, maximise=True, verbose=False)
+
+    # run the algorithm and get solution found
+    solution = algo.run(100)
+    print(solution.fitness())
+
+
+.. note::
+    The ``verbose`` algorithm parameter will log into console the advancement process of the algorithm is set to ``True`` (the default value).
+
+Exploratory algorithm
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As explained in **figure (b)** of **section 8.1**, sometimes the search space is more complicated due to convex parts and need heuristic with other strategy rather than a simple local search.
+
+The way to counter this problem is to allow the algorithm to exit the exploitation phase offered by local search. But rather to seek to explore other parts of the research space. This is possible by simply carrying out several local searches with our budget (number of evaluations).
+
+The idea is to make a leap in the search space in order to find a new local optimum which can be the global optimum. The explained process is illustrated below:
+
+.. image:: ../_static/documentation/search_space_simple.png
+   :width:  400 px
+   :align: center
+
+
+We are going to implement a more specific algorithm allowing to take a new parameter as input. This is a local search, the one previously developed. For that, we will have to modify the constructor a little.
+Let's called this new algorithm ``IteratedLocalSearch``:
+
+.. code-block:: python
+
+    class IteratedLocalSearch(Algorithm):
+        
+        def __init__(self,
+                    initializer,
+                    evaluator,
+                    operators,
+                    policy,
+                    validator,
+                    localSearch,
+                    maximise=True,
+                    parent=None,
+                    verbose=True):
+            
+            super().__init__(initializer, evaluator, operators, policy, validator, maximise, parent, verbose)
+
+            # specific local search associated with current algorithm
+            self._localSearch = localSearch
+
+            # need to attach current algorithm as parent
+            self._localSearch.setParent(self)
+
+
+        def run(self, evaluations, ls_evaluations=100):
+            """
+            Run the iterated local search algorithm using local search
+            """
+
+            # by default use of mother method to initialize variables
+            super().run(evaluations)
+
+            # initialize current solution
+            self.initRun()
+
+            # local search algorithm implementation
+            while not self.stop():
+
+                # create and search solution from local search (stop method can be called inside local search)
+                newSolution = self._localSearch.run(ls_evaluations)
+
+                # if better solution than currently, replace it
+                if self.isBetter(newSolution):
+                    self._bestSolution = newSolution
+
+                self.information()
+
+            return self._bestSolution
+
+In the initialization phase we have attached our local search passed as a parameter with the current algorithm as parent. 
+The goal is to touch keep track of the overall search evaluation number (relative to the parent algorithm).
+
+Then, we use this local search in our ``run`` method to allow a better search for solutions.
+
+.. code-block:: python
+
+    """
+    Problem instance definition
+    """
+    elements_score = [ 4, 2, 10, 1, 2 ] # worth of each object
+    elements_weight = [ 12, 1, 4, 1, 2 ] # weight of each object
+
+    # evaluator instance
+    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
+    
+    # initialiser instance using lambda with default param value
+    initialiser = lambda x=5: BinarySolution.random(x, validator)
+    
+    # operators list with crossover and mutation
+    operators = [SimpleCrossover(), SimpleMutation()]
+    
+    # policy random instance
+    policy = RandomPolicy(operators)
+    
+    # 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)
+
+    # run the algorithm using local search and get solution found 
+    solution = algo.run(evaluations=100, ls_evaluations=10)
+    print(solution.fitness())
+
+
+.. note:: 
+    These two last algorithms developed are available in the library within the module ``maocp.algorithms.mono``.
+
+We have one final feature to explore in the next part. This is the notion of ``callback``.

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

@@ -1,2 +1,2 @@
-9. Keep track
+Keep track
 ==============

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

@@ -1,9 +1,9 @@
-5. Use of evaluators
+Use of evaluators
 ====================
 
 Now that it is possible to generate a solution randomly or not. It is important to know the value associated with this solution. We will then speak of evaluation of the solution. With the score associated with it, the `fitness`.
 
-5.1. Generic evaluator
+Generic evaluator
 ~~~~~~~~~~~~~~~~~~~~~~
 
 As for the management of solutions, a generic evaluator class ``macop.evaluators.base.Evaluator`` is developed within **Macop**:
@@ -38,7 +38,7 @@ Abstract Evaluator class is used for computing fitness score associated to a sol
 
 We must therefore now create our own evaluator based on the proposed structure.
 
-5.2. Custom evaluator
+Custom evaluator
 ~~~~~~~~~~~~~~~~~~~~~
 
 To create our own evaluator, we need both:

+ 1 - 0
docs/source/documentations/index.rst

@@ -11,6 +11,7 @@ It will gradually take up the major ideas developed within **Macop** to allow fo
 
 .. toctree::
    :maxdepth: 1
+   :numbered:
    :caption: Contents:
 
    introduction

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


+ 5 - 6
docs/source/documentations/operators.rst

@@ -1,9 +1,9 @@
-6. Apply operators to solution
+Apply operators to solution
 ==============================
 
 Applying an operator to a solution consists of modifying the current state of the solution in order to obtain a new one. The goal is to find a better solution in the search space.
 
-6.1. Operators definition
+Operators definition
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
 In the discrete optimisation literature, we can categorise operators into two sections:
@@ -69,7 +69,7 @@ Like the evaluator, the operator keeps **track of the algorithm** (using ``setAl
 
 We will now detail these categories of operators and suggest some relative to our problem.
 
-6.2. Mutator operator
+Mutator operator
 ~~~~~~~~~~~~~~~~~~~~~
 
 As detailed, the mutation operator consists in having a minimum impact on the current state of our solution. Here is an example of a modification that could be done for our problem.
@@ -134,7 +134,7 @@ We can now instanciate our new operator in order to obtain a new solution:
     The developed ``SimpleBinaryMutation`` is available into ``macop.operators.discrete.mutators.SimpleBinaryMutation`` in **Macop**.
 
 
-6.3. Crossover operator
+Crossover operator
 ~~~~~~~~~~~~~~~~~~~~~~~
 
 
@@ -195,7 +195,6 @@ We can now use the crossover operator created to generate new solutions. Here is
 
 .. warning::
     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. In addition, the second solution can be omitted, 
-    by default the operator will crossover between ``solution1`` and the best current solution of the algorithm.
+    However, the choice of halves of the merged data is made randomly.
 
 Next part introduce the ``policy`` feature of **Macop** which enables to choose the next operator to apply during the search process based on specific criterion.

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

@@ -1,2 +1,2 @@
-10. Others features
+Others features
 ===================

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

@@ -1,9 +1,9 @@
-7. Operator choices
+Operator choices
 ===================
 
 The ``policy`` feature of **Macop** enables to choose the next operator to apply during the search process of the algorithm based on specific criterion.
 
-7.1. Why using policy ?
+Why using policy ?
 ~~~~~~~~~~~~~~~~~~~~~~~
 
 Sometimes the nature of the problem and its instance can strongly influence the search results when using mutation operators or crossovers. 
@@ -20,7 +20,7 @@ The operator choice problem can be seen as the desire to find the best solution
     However, it will not be detailed here. You can refer to the API documentation for more details.
 
 
-7.2. Custom policy
+Custom policy
 ~~~~~~~~~~~~~~~~~~
 
 In our case, we are not going to exploit a complex enough implementation of a ``policy``. Simply, we will use a random choice of operator.

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

@@ -1,9 +1,9 @@
-2. Problem instance
+Problem instance
 ===================
 
 In this tutorial, we introduce the way of using **Macop** and running your algorithm quickly using the well known `knapsack` problem.
 
-2.1 Problem definition
+Problem definition
 ~~~~~~~~~~~~~~~~~~~~~~
 
 The **knapsack problem** is a problem in combinatorial optimisation: Given a set of items, each with a weight and a value, determine the number of each item to include in a collection so that the total weight is less than or equal to a given limit and the total value is as large as possible.
@@ -22,7 +22,7 @@ In this problem, we try to optimise the value associated with the objects we wis
     It is a combinatorial and therefore discrete problem. **Macop** decomposes its package into two parts, which is related to discrete optimisation on the one hand, and continuous optimisation on the other hand. This will be detailed later.
 
 
-2.2 Problem implementation
+Problem implementation
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 During the whole tutorial, the example used is based on the previous illustration with:

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

@@ -1,11 +1,11 @@
-3. Solutions
+Solutions
 =============
 
 Representing a solution to a specific problem is very important in an optimisation process. In this example, we will always use the **knapsack problem** as a basis.
 
 In a first step, the management of the solutions by the macop package will be presented. Then a specific implementation for the current problem will be detailed.
 
-3.1. Generic Solution
+Generic Solution
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Inside ``macop.solutions.base`` module of `Macop`, the ``Solution`` class is available. It's an abstract solution class structure which:
@@ -67,13 +67,13 @@ Allowing to initialize it randomly or not (using constructor or ``random`` metho
 
 We will now see how to define a type of solution specific to our problem.
 
-3.2. Solution representation for knapsack
+Solution representation for knapsack
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 We will now use the abstract ``Solution`` type available in the ``macop.solutions.base`` module in order to define our own solution.
 First of all, let's look at the representation of our knapsack problem. **How to represent the solution?**
 
-3.2.1. Knapsack solution
+Knapsack solution
 ************************
 
 A valid solution can be shown below where the sum of the object weights is 15 and the sum of the selected objects values is 8 (its fitness):
@@ -90,7 +90,7 @@ Its representation can be translate as a **binary array** with value:
 
 where selected objects have **1** as value otherwise **0**.
 
-3.2.2. Binary Solution
+Binary Solution
 **********************
 
 We will now define our own type of solution by inheriting from ``macop.solutions.base.Solution``, which we will call ``BinarySolution``.

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

@@ -1,10 +1,10 @@
-4. Validate a solution
+Validate a solution
 ======================
 
 When an optimisation problem requires respecting certain constraints, Macop allows you to quickly verify that a solution is valid. 
 It is based on a defined function taking a solution as input and returning the validity criterion (true or false).
 
-4.1. Validator definition
+Validator definition
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
 An invalid solution can be shown below where the sum of the object weights is greater than 15:
@@ -41,7 +41,7 @@ To avoid taking into account invalid solutions, we can define our function which
         return weight_sum <= 15
 
 
-4.2. Use of validator
+Use of validator
 ~~~~~~~~~~~~~~~~~~~~~
 
 We can now generate solutions randomly by passing our validation function as a parameter:

+ 2 - 0
docs/source/index.rst

@@ -15,7 +15,9 @@ What's **Macop** ?
    :caption: Contents:
 
    description
+
    documentations/index
+
    api
    contributing
 

+ 2 - 17
macop/algorithms/base.py

@@ -23,9 +23,9 @@ class Algorithm():
 
     Attributes:
         initializer: {function} -- basic function strategy to initialize solution
-        evaluator: {function} -- basic function in order to obtained fitness (mono or multiple objectives)
+        evaluator: {Evaluator} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
         operators: {[Operator]} -- list of operator to use when launching algorithm
-        policy: {Policy} -- Policy class implementation strategy to select operators
+        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 
         verbose: {bool} -- verbose or not information about the algorithm
@@ -307,20 +307,5 @@ class Algorithm():
     def information(self):
         logging.info(f"-- Best {self._bestSolution} - SCORE {self._bestSolution.fitness()}")
 
-    # def __blockPrint(self):
-    #     """Private method which disables console prints when running algorithm if specified when instancing algorithm
-    #     """
-    #     sys.stdout = open(os.devnull, 'w')
-
-    # def __enablePrint(self):
-    #     """Private which enables console prints when running algorithm
-    #     """
-    #     sys.stdout = sys.__stdout__
-
-    # def __del__(self):
-    #     # enable prints when object is deleted
-    #     if not self._verbose:
-    #         self.__enablePrint()
-
     def __str__(self):
         return f"{type(self).__name__} using {type(self._bestSolution).__name__}"

+ 2 - 2
macop/algorithms/mono.py

@@ -17,7 +17,7 @@ class HillClimberFirstImprovment(Algorithm):
 
     Attributes:
         initalizer: {function} -- basic function strategy to initialize solution
-        evaluator: {function} -- basic function in order to obtained fitness (mono or multiple objectives)
+        evaluator: {Evaluator} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy class implementation strategy to select operators
         validator: {function} -- basic function to check if solution is valid or not under some constraints
@@ -116,7 +116,7 @@ class HillClimberBestImprovment(Algorithm):
 
     Attributes:
         initalizer: {function} -- basic function strategy to initialize solution
-        evaluator: {function} -- basic function in order to obtained fitness (mono or multiple objectives)
+        evaluator: {Evaluator} -- evaluator instance in order to obtained fitness (mono or multiple objectives)
         operators: {[Operator]} -- list of operator to use when launching algorithm
         policy: {Policy} -- Policy class implementation strategy to select operators
         validator: {function} -- basic function to check if solution is valid or not under some constraints

+ 4 - 10
macop/operators/discrete/crossovers.py

@@ -68,11 +68,8 @@ class SimpleCrossover(Crossover):
         # copy data of solution
         firstData = solution1._data.copy()
 
-        # get best solution from current algorithm
-        if solution2 is None:
-            copy_solution = self._algo._bestSolution.clone()
-        else:
-            copy_solution = solution2.clone()
+        # copy of solution2 as output solution
+        copy_solution = solution2.clone()
 
         splitIndex = int(size / 2)
 
@@ -143,11 +140,8 @@ class RandomSplitCrossover(Crossover):
         # copy data of solution
         firstData = solution1._data.copy()
 
-        # get best solution from current algorithm
-        if solution2 is None:
-            copy_solution = self._algo._bestSolution.clone()
-        else:
-            copy_solution = solution2.clone()
+        # copy of solution2 as output solution
+        copy_solution = solution2.clone()
 
         splitIndex = random.randint(0, size)
 

+ 23 - 8
macop/policies/reinforcement.py

@@ -8,6 +8,7 @@ import numpy as np
 
 # module imports
 from .base import Policy
+from ..operators.base import KindOperator
 
 
 class UCBPolicy(Policy):
@@ -101,7 +102,7 @@ class UCBPolicy(Policy):
         else:
             return self._operators[random.choice(indices)]
 
-    def apply(self, solution):
+    def apply(self, solution1, solution2=None):
         """
         Apply specific operator chosen to create new solution, computes its fitness and returns solution
 
@@ -109,7 +110,8 @@ class UCBPolicy(Policy):
         - selected operator occurence is also increased
 
         Args:
-            solution: {Solution} -- the solution to use for generating new solution
+            solution1: {Solution} -- the first solution to use for generating new solution
+            solution2: {Solution} -- the second solution to use for generating new solution (in case of specific crossover, default is best solution from algorithm)
 
         Returns:
             {Solution} -- new generated solution
@@ -118,10 +120,23 @@ class UCBPolicy(Policy):
         operator = self.select()
 
         logging.info("---- Applying %s on %s" %
-                     (type(operator).__name__, solution))
+                     (type(operator).__name__, solution1))
+
+        # default value of solution2 is current best solution
+        if solution2 is None and self._algo is not None:
+            solution2 = self._algo._bestSolution
+
+        # avoid use of crossover if only one solution is passed
+        if solution2 is None and operator._kind == KindOperator.CROSSOVER:
+
+            while operator._kind == KindOperator.CROSSOVER:            
+                operator = self.select()
 
         # apply operator on solution
-        newSolution = operator.apply(solution)
+        if operator._kind == KindOperator.CROSSOVER:
+            newSolution = operator.apply(solution1, solution2)
+        else:
+            newSolution = operator.apply(solution1)
 
         # compute fitness of new solution
         newSolution.evaluate(self._algo._evaluator)
@@ -129,10 +144,10 @@ class UCBPolicy(Policy):
         # compute fitness improvment rate
         if self._algo._maximise:
             fir = (newSolution.fitness() -
-                   solution.fitness()) / solution.fitness()
+                   solution1.fitness()) / solution1.fitness()
         else:
-            fir = (solution.fitness() -
-                   newSolution.fitness()) / solution.fitness()
+            fir = (solution1.fitness() -
+                   newSolution.fitness()) / solution1.fitness()
 
         operator_index = self._operators.index(operator)
 
@@ -141,6 +156,6 @@ class UCBPolicy(Policy):
 
         self._occurences[operator_index] += 1
 
-        logging.info("---- Obtaining %s" % (solution))
+        logging.info("---- Obtaining %s" % (newSolution))
 
         return newSolution