Parcourir la source

Add of UBQP instance and unit test for new evaluators

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

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

@@ -144,12 +144,12 @@ If you are uncomfortable with some of the elements in the code that will follow,
     algo = ILS(init, evaluator, operators, policy, validator, localSearch=hcfi, maximise=False, verbose=True)
 
     # run the algorithm
-    bestSol = algo.run(100000, ls_evaluations=100)
+    bestSol = algo.run(10000, ls_evaluations=100)
 
-    print('Solution score is {}'.format(evaluator.compute(bestSol)))
+    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.
+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

+ 2 - 3
docs/source/examples/qap/instance.rst

@@ -1,14 +1,13 @@
 QAP Problem instance generation
 ===============================
 
-To define a quadratic problem, we will use the available mQAP_ multi-objective quadratic problem generator. 
+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
@@ -37,7 +36,7 @@ We are now going to load this instance via a Python code which will be useful to
 
 .. code:: Python
 
-    qap_instance_file = 'instances/qap/qap_instance.txt'
+    qap_instance_file = 'qap_instance.txt'
 
     n = 100 # the instance size
 

+ 32 - 39
docs/source/examples/ubqp/implementation.rst

@@ -28,17 +28,16 @@ The resulting solution obtained:
 
 
 UBQP 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:
+To do this, we need to create a new evaluator specific to our problem and the relative evaluation function we need to maximise:
 
-- :math:`min_{ϕ∈S_n}\sum_{i=1}^{n}{\sum_{j=1}^{n}{f_{ij}⋅d_{\phi(i)\phi(j)}}}`
+- :math:`f(x)=x′Qx=\sum_{i=1}^{n}{\sum_{j=1}^{n}{q_{ij}⋅x_i⋅x_j}}`
 
 So we are going to create a class that will inherit from the abstract class ``macop.evalutarors.base.Evaluator``:
 
-
 .. code:: python
 
     from macop.evaluators.base import Evaluator
@@ -55,7 +54,7 @@ So we are going to create a class that will inherit from the abstract class ``ma
         """Apply the computation of fitness from solution
 
         Args:
-            solution: {Solution} -- QAP solution instance
+            solution: {Solution} -- UBQP solution instance
     
         Returns:
             {float} -- fitness score of solution
@@ -63,19 +62,19 @@ So we are going to create a class that will inherit from the abstract class ``ma
         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]
+                fitness += self._data['Q'][index_i, index_j] * val_i * val_j
 
         return fitness
 
-The cost function for the quadratic problem is now well defined.
+The cost function for the Unconstrained binary quadratic problem is now well defined.
 
 .. warning::
-    The class proposed here, is available in the Macop package ``macop.evaluators.discrete.mono.QAPEvaluator``.
+    The class proposed here, is available in the Macop package ``macop.evaluators.discrete.mono.UBQPEvaluator``.
 
 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.
+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 UBQP instance.
 
 Here we will use local search algorithms already implemented in **Macop**.
 
@@ -87,10 +86,10 @@ If you are uncomfortable with some of the elements in the code that will follow,
     import numpy as np
 
     # module imports
-    from macop.solutions.discrete import CombinatoryIntegerSolution
-    from macop.evaluators.discrete.mono import QAPEvaluator
+    from macop.solutions.discrete import BinarySolution
+    from macop.evaluators.discrete.mono import UBQPEvaluator
 
-    from macop.operators.discrete.mutators import SimpleMutation
+    from macop.operators.discrete.mutators import SimpleMutation, SimpleBinaryMutation
 
     from macop.policies.classicals import RandomPolicy
 
@@ -101,53 +100,47 @@ If you are uncomfortable with some of the elements in the code that will follow,
     n = 100
     qap_instance_file = 'qap_instance.txt'
 
-    # default validator (check the consistency of our data, i.e. only unique element)
+    # default validator
     def validator(solution):
-        if len(list(solution._data)) > len(set(list(solution._data))):
-            print("not valid")
-            return False
         return True
 
     # define init random solution
     def init():
-        return CombinatoryIntegerSolution.random(n, validator)
+        return BinarySolution.random(n, validator)
+
+    # load UBQP instance
+    with open(ubqp_instance_file, 'r') as f:
 
-    # load qap instance
-    with open(qap_instance_file, 'r') as f:
-        file_data = f.readlines()
-        print(f'Instance information {file_data[0]}')
+        lines = f.readlines()
 
-        D_lines = file_data[1:n + 1]
-        D_data = ''.join(D_lines).replace('\n', '')
+        # get all string floating point values of matrix
+        Q_data = ''.join([ line.replace('\n', '') for line in lines[8:] ])
 
-        F_lines = file_data[n:2 * n + 1]
-        F_data = ''.join(F_lines).replace('\n', '')
+        # load the concatenate obtained string
+        Q_matrix = np.fromstring(Q_data, dtype=float, sep=' ').reshape(n, 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}')
+    print(f'Q_matrix shape: {Q_matrix.shape}')
 
     # only one operator here
-    operators = [SimpleMutation()]
+    operators = [SimpleMutation(), SimpleBinaryMutation()]
 
-    # random policy even if list of solution has only one element
+    # random policy
     policy = RandomPolicy(operators)
 
-    # use of loaded data from QAP instance
-    evaluator = QAPEvaluator(data={'F': F_matrix, 'D': D_matrix})
+    # use of loaded data from UBQP instance
+    evaluator = UBQPEvaluator(data={'Q': Q_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)
+    hcfi = HillClimberFirstImprovment(init, evaluator, operators, policy, validator, maximise=True, verbose=True)
+    algo = ILS(init, evaluator, operators, policy, validator, localSearch=hcfi, maximise=True, verbose=True)
 
     # run the algorithm
-    bestSol = algo.run(100000, ls_evaluations=100)
+    bestSol = algo.run(10000, ls_evaluations=100)
 
-    print('Solution score is {}'.format(evaluator.compute(bestSol)))
+    print('Solution for UBQP 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.
+UBQP problem solving is now possible with **Macop**. As a reminder, the complete code is available in the ubqpExample.py_ file.
 
-.. _qapExample.py: https://github.com/jbuisine/macop/blob/master/examples/qapExample.py
+.. _ubqpExample.py: https://github.com/jbuisine/macop/blob/master/examples/ubqpExample.py
 .. _documentation: https://jbuisine.github.io/macop/_build/html/documentations

+ 3 - 1
docs/source/examples/ubqp/index.rst

@@ -1,7 +1,9 @@
 Unconstrained Binary Quadratic Programming
 ==========================================
 
-This example will be available soon.
+Given a collection of :math:`n` items such that each pair of items is associated with a profit value that can be positive, negative or zero, unconstrained binary quadratic programming (UBQP) seeks a subset of items that maximizes the sum of their paired values. The value of a pair is accumulated in the sum only if the two corresponding items are selected.
+
+The UBQP problem will be tackle in this example.
 
 .. toctree::
    :maxdepth: 1

+ 47 - 5
docs/source/examples/ubqp/instance.rst

@@ -1,10 +1,52 @@
 UBQP Problem instance generation
 ================================
 
+To define our quadratic assignment problem instance, we will use the available mUBQP_ multi-objective quadratic problem generator. 
 
-The four main parameters of the family of mUBQP are:
+Genration of the instance
+~~~~~~~~~~~~~~~~~~~~~~~~~
 
-- ρ: the objective correlation coefficient
-- M: the number of objective functions
-- N: the length of bit strings
-- d: the matrix density (frequency of non-zero numbers)
+We will limit ourselves here to a single objective for the purposes of this example. The available file **mubqpGenerator.R**, will be used to generate the instance (using R language).
+
+.. code:: bash
+
+    Rscript mubqpGenerator.R 0.8 1 100 5 42 ubqp_instance.txt
+
+The main parameters used for generating our UBQP instance are:
+
+- **ρ:** the objective correlation coefficient
+- **M:** the number of objective functions
+- **N:** the length of bit strings
+- **d:** the matrix density (frequency of non-zero numbers)
+- **s:** seed to use
+
+.. _mUBQP: http://mocobench.sourceforge.net/index.php?n=Problem.MUBQP
+
+.. _ubqp_instance.txt: https://github.com/jbuisine/macop/blob/master/examples/instances/ubqp/ubqp_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 = 'ubqp_instance.txt'
+
+    n = 100 # the instance size
+
+    # load UBQP instance
+    with open(ubqp_instance_file, 'r') as f:
+
+        lines = f.readlines()
+
+        # get all string floating point values of matrix
+        Q_data = ''.join([ line.replace('\n', '') for line in lines[8:] ])
+
+        # load the concatenate obtained string
+        Q_matrix = np.fromstring(Q_data, dtype=float, sep=' ').reshape(n, n)
+
+    print(f'Q_matrix {Q_matrix.shape}')
+
+.. note::
+    As we know the size of our instance and the structure of the document (header size), it is quite quick to look for the lines related to the :math:`Q` matrix.

+ 0 - 1
docs/source/examples/ubqp/problem.rst

@@ -1,7 +1,6 @@
 UBQP problem definition
 =======================
 
-
 Understand the UBQP Problem
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

Fichier diff supprimé car celui-ci est trop grand
+ 10008 - 0
examples/instances/ubqp/ubqp_instance.txt


+ 2 - 2
examples/qapExample.py

@@ -72,9 +72,9 @@ def main():
     # add callback into callback list
     algo.addCallback(callback)
 
-    bestSol = algo.run(100000, ls_evaluations=100)
+    bestSol = algo.run(10000, ls_evaluations=100)
 
-    print('Solution score is {}'.format(evaluator.compute(bestSol)))
+    print('Solution for QAP instance score is {}'.format(evaluator.compute(bestSol)))
 
 if __name__ == "__main__":
     main()

+ 28 - 27
examples/ubqpExample.py

@@ -2,18 +2,16 @@
 import logging
 import os
 import random
+import numpy as np
 
 # module imports
 from macop.solutions.discrete import BinarySolution
-from macop.evaluators.discrete.mono import KnapsackEvaluator
+from macop.evaluators.discrete.mono import UBQPEvaluator
 
 from macop.operators.discrete.mutators import SimpleMutation
 from macop.operators.discrete.mutators import SimpleBinaryMutation
-from macop.operators.discrete.crossovers import SimpleCrossover
-from macop.operators.discrete.crossovers import RandomSplitCrossover
 
 from macop.policies.classicals import RandomPolicy
-from macop.policies.reinforcement import UCBPolicy
 
 from macop.algorithms.mono import IteratedLocalSearch as ILS
 from macop.algorithms.mono import HillClimberFirstImprovment
@@ -27,50 +25,53 @@ logging.basicConfig(format='%(asctime)s %(message)s', filename='data/example.log
 
 random.seed(42)
 
-elements_score = [ random.randint(1, 20) for _ in range(30) ]
-elements_weight = [ random.randint(2, 5) for _ in range(30) ]
+# usefull instance data
+n = 100
+ubqp_instance_file = 'instances/ubqp/ubqp_instance.txt'
+filepath = "data/checkpoints_ubqp.csv"
 
-def knapsackWeight(solution):
-
-    weight_sum = 0
-    for index, elem in enumerate(solution._data):
-        weight_sum += elements_weight[index] * elem
-
-    return weight_sum
 
 # default validator
 def validator(solution):
-
-    if knapsackWeight(solution) <= 80:
-        return True
-    else:
-        False
+    return True
 
 # define init random solution
 def init():
-    return BinarySolution.random(30, validator)
+    return BinarySolution.random(n, validator)
 
 
 filepath = "data/checkpoints.csv"
 
 def main():
 
-    operators = [SimpleBinaryMutation(), SimpleMutation(), SimpleCrossover(), RandomSplitCrossover()]
-    policy = UCBPolicy(operators)
+    # load UBQP instance
+    with open(ubqp_instance_file, 'r') as f:
+
+        lines = f.readlines()
+
+        # get all string floating point values of matrix
+        Q_data = ''.join([ line.replace('\n', '') for line in lines[8:] ])
+
+        # load the concatenate obtained string
+        Q_matrix = np.fromstring(Q_data, dtype=float, sep=' ').reshape(n, n)
+
+    print(f'Q_matrix shape: {Q_matrix.shape}')
+
+    operators = [SimpleBinaryMutation(), SimpleMutation()]
+    policy = RandomPolicy(operators)
     callback = BasicCheckpoint(every=5, filepath=filepath)
-    evaluator = KnapsackEvaluator(data={'worths': elements_score})
+    evaluator = UBQPEvaluator(data={'Q': Q_matrix})
 
     # passing global evaluation param from ILS
-    hcfi = HillClimberFirstImprovment(init, evaluator, operators, policy, validator, maximise=True, verbose=False)
-    algo = ILS(init, evaluator, operators, policy, validator, localSearch=hcfi, maximise=True, verbose=False)
+    hcfi = HillClimberFirstImprovment(init, evaluator, operators, policy, validator, maximise=True, verbose=True)
+    algo = ILS(init, evaluator, operators, policy, validator, localSearch=hcfi, maximise=True, verbose=True)
     
     # add callback into callback list
     algo.addCallback(callback)
 
-    bestSol = algo.run(1000)
+    bestSol = algo.run(10000, ls_evaluations=100)
 
-    print('Solution score is {}'.format(evaluator.compute(bestSol)))
-    print('Solution weigth is {}'.format(knapsackWeight(bestSol)))
+    print('Solution for UBQP instance score is {}'.format(evaluator.compute(bestSol)))
 
 if __name__ == "__main__":
     main()

+ 58 - 6
macop/evaluators/discrete/mono.py

@@ -43,9 +43,8 @@ class KnapsackEvaluator(Evaluator):
 
         return fitness
 
-
 class QAPEvaluator(Evaluator):
-    """QAP evaluator class which enables to compute qap solution using specific `_data`
+    """Quadratic Assignment Problem (QAP) evaluator class which enables to compute qap solution using specific `_data`
 
     Solutions use for this evaluator are with type of `macop.solutions.discrete.CombinatoryIntegerSolution`
 
@@ -57,14 +56,14 @@ class QAPEvaluator(Evaluator):
     Example:
 
     >>> import random
+    >>> import numpy as np
     >>> # combinatory solution import
     >>> from macop.solutions.discrete import CombinatoryIntegerSolution
     >>> # evaluator import
-    >>> from macop.evaluators.discrete.QAPEvaluator import QAPEvaluator
+    >>> from macop.evaluators.discrete.mono import QAPEvaluator
     >>> # define problem data using QAP example instance
-    >>> qap_instance_file = '../../../examples/instances/qap/qap_instance.txt'
+    >>> qap_instance_file = 'examples/instances/qap/qap_instance.txt'
     >>> n = 100 # problem size
-    >>> size = len(solution_data)
     >>> # loading data
     >>> f = open(qap_instance_file, 'r')
     >>> file_data = f.readlines()
@@ -81,7 +80,7 @@ class QAPEvaluator(Evaluator):
     >>> solution = CombinatoryIntegerSolution.random(n)
     >>> # compute solution score
     >>> evaluator.compute(solution)
-    40
+    6397983.0
     """
     def compute(self, solution):
         """Apply the computation of fitness from solution
@@ -98,3 +97,56 @@ class QAPEvaluator(Evaluator):
                 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`
+
+    - stores into its `_data` dictionary attritute required measures when computing a UBQP solution
+    - `_data['Q']` matrix of size n x n with real values data (stored as numpy array)
+    - `compute` method enables to compute and associate a score to a given UBQP solution
+
+    Example:
+
+    >>> 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
+
+        Args:
+            solution: {Solution} -- UBQP solution instance
+    
+        Returns:
+            {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['Q'][index_i, index_j] * val_i * val_j
+
+        return fitness

+ 1 - 1
macop/utils/progress.py

@@ -79,4 +79,4 @@ def macop_progress(algo, evaluations, max):
     # go to line
     if progress >= 1.:
         print()
-        print(macop_line(algo))
+        macop_line(algo)