Browse Source

First version of OR framework

Jérôme BUISINE 1 year ago
parent
commit
0a1b108095

+ 0 - 0
__init__.py


+ 106 - 0
algorithms/Algorithm.py

@@ -0,0 +1,106 @@
+# Generic algorithm class
+class Algorithm():
+
+    def __init__(self, _initalizer, _evaluator, _updators, _policy, _validator, _maximise=True):
+        """
+        Initialize all usefull parameters for problem to solve
+        """
+
+        self.initializer = _initalizer
+        self.evaluator = _evaluator
+        self.updators = _updators
+        self.validator = _validator
+        self.policy = _policy
+
+        self.currentSolution = self.initializer()
+        
+        # evaluate current solution
+        self.currentSolution.evaluate(self.evaluator)
+
+        # keep in memory best known solution (current solution)
+        self.bestSolution = self.currentSolution
+
+        # other parameters
+        self.maxEvalutations = 0 # by default
+        self.numberOfEvaluations = 0
+        self.maximise = _maximise
+
+
+    def reinit(self):
+        """
+        Reinit the whole variable
+        """
+        self.currentSolution = self.initializer
+        self.bestSolution = self.currentSolution
+
+        self.numberOfEvaluations = 0
+
+
+    def evaluate(self, solution):
+        """
+        Returns: 
+            fitness score of solution which is not already evaluated or changed
+
+        Note: 
+            if multi-objective problem this method can be updated using array of `evaluator`
+        """
+        return solution.evaluate(self.evaluator)
+
+
+    def update(self, solution):
+        """
+        Apply update function to solution using specific `policy`
+
+        Check if solution is valid after modification and returns it
+
+        Returns:
+            updated solution
+        """
+
+        sol = self.policy.apply(solution)
+
+        if(sol.isValid(self.validator)):
+            return sol
+        else:
+            print("New solution is not valid", sol)
+            return solution
+
+
+    def isBetter(self, solution):
+        """
+        Check if solution is better than best found
+
+        Returns:
+            `True` if better
+        """
+        # depending of problem to solve (maximizing or minimizing)
+        if self.maximise:
+            if self.evaluate(solution) > self.bestSolution.fitness():
+                return True
+        else:
+            if self.evaluate(solution) < self.bestSolution.fitness():
+                return True
+
+        # by default
+        return False
+
+
+    def run(self, _evaluations):
+        """
+        Run the specific algorithm following number of evaluations to find optima
+        """
+        self.maxEvalutations = _evaluations
+
+
+    def progress(self):
+        return "Evaluation n°%s/%s, %s%%" % (self.numberOfEvaluations, self.maxEvalutations, "{0:.2f}".format((self.numberOfEvaluations) / self.maxEvalutations * 100.))
+
+
+    def information(self):
+        return "%s found with score of %s" % (self.bestSolution, self.bestSolution.fitness())
+
+
+    def __str__(self):
+        return "%s using %s" % (type(self).__name__, type(self.bestSolution).__name__)
+
+

+ 33 - 0
algorithms/IteratedLocalSearch.py

@@ -0,0 +1,33 @@
+# module imports
+from .Algorithm import Algorithm
+from.LocalSearch import LocalSearch
+
+class IteratedLocalSearch(Algorithm):
+
+    def run(self, _evaluations):
+
+        # by default use of mother method to initialize variables
+        super().run(_evaluations)
+
+        ls = LocalSearch(self.initializer, self.evaluator, self.updators, self.policy, self.validator, self.maximise)
+
+        # local search algorithm implementation
+        while self.numberOfEvaluations < self.maxEvalutations:
+
+            # create and search solution from local search
+            newSolution = ls.run(100)
+
+            # if better solution than currently, replace it
+            if self.isBetter(newSolution):
+                self.bestSolution = newSolution
+
+            # increase number of evaluations
+            self.numberOfEvaluations += 100
+
+            print(self.progress())
+        print(self.information())
+            
+
+        print("End of local search algorithm..")
+
+        return self.bestSolution

+ 38 - 0
algorithms/LocalSearch.py

@@ -0,0 +1,38 @@
+# module imports
+from .Algorithm import Algorithm
+
+class LocalSearch(Algorithm):
+
+    def run(self, _evaluations):
+
+        # by default use of mother method to initialize variables
+        super().run(_evaluations)
+
+        solutionSize = self.bestSolution.size
+
+        # local search algorithm implementation
+        while self.numberOfEvaluations < self.maxEvalutations:
+
+            for _ in range(solutionSize):
+
+                # update solution using policy
+                newSolution = self.update(self.bestSolution)
+
+                # if better solution than currently, replace it
+                if self.isBetter(newSolution):
+                    self.bestSolution = newSolution
+
+                # increase number of evaluations
+                self.numberOfEvaluations += 1
+
+                print(self.progress())
+
+                # stop algorithm if necessary
+                if self.numberOfEvaluations >= self.maxEvalutations:
+                    break
+            
+            print(self.information())
+
+        print("End of local search algorithm..")
+
+        return self.bestSolution

+ 0 - 0
algorithms/__init__.py


+ 8 - 0
evaluators/EvaluatorExample.py

@@ -0,0 +1,8 @@
+# evaluator example
+def evaluatorExample(solution):
+
+    fitness = 0
+    for index, elem in enumerate(solution.data):
+        fitness = fitness + (elem * index)
+    
+    return fitness

+ 0 - 0
evaluators/__init__.py


+ 29 - 0
mainExample.py

@@ -0,0 +1,29 @@
+# module imports
+from algorithms.IteratedLocalSearch import IteratedLocalSearch as ILS
+from solutions.BinarySolution import BinarySolution
+from evaluators.EvaluatorExample import evaluatorExample
+
+from updators.mutators.SimpleMutation import SimpleMutation, SimpleBinaryMutation
+from updators.policies.RandomPolicy import RandomPolicy
+
+def init():
+    return BinarySolution([], 30).random()
+
+# default validator
+def validator(solution):
+    return True
+
+def main():
+
+    updators = [SimpleBinaryMutation, SimpleMutation]
+    policy = RandomPolicy(updators)
+
+    algo = ILS(init, evaluatorExample, updators, policy, validator, True)
+
+    bestSol = algo.run(100000)
+
+    print("Found ", bestSol)
+
+
+if __name__ == "__main__":
+    main()

+ 33 - 0
solutions/BinarySolution.py

@@ -0,0 +1,33 @@
+# main imports
+import numpy as np
+
+# modules imports
+from .Solution import Solution
+
+
+# Solution which stores solution data as binary array
+class BinarySolution(Solution):
+
+    def __init__(self, _data, _size):
+        """
+        Initialize data of solution using specific data
+
+        - `data` field is array of binary values
+        - `size` field is the size of array binary values
+        """
+
+        self.data = _data
+        self.size = _size
+
+
+    def random(self):
+        """
+        Intialize binary array using size solution data
+        """
+        self.data = np.random.randint(2, size=self.size)
+        return self
+
+
+    def __str__(self):
+        return "Binary solution %s of size %s" % (self.data, self.size)
+        

+ 52 - 0
solutions/Solution.py

@@ -0,0 +1,52 @@
+# Generic solution class 
+class Solution():
+
+    def __init__(self, _data, _size):
+        """
+        Initialize data of solution using specific data
+
+        Note : `data` field can be anything, such as array/list of integer
+        """
+        self.data = _data
+        self.size = _size
+        self.score = None
+    
+
+    def apply(self, _updator):
+        """
+        Apply custom modification of solution and return the transformed solution
+        """
+        return _updator(self)
+
+
+    def isValid(self, _validator):
+        """
+        Use of custom method which validates if solution is valid or not
+        """
+        return _validator(self)
+
+
+    def evaluate(self, _evaluator):
+        """
+        Evaluate function using specific `_evaluator`
+        """
+        self.score = _evaluator(self)
+        return self.score
+
+
+    def fitness(self):
+        """
+        Returns fitness score
+        """
+        return self.score
+
+
+    def random(self):
+        """
+        Initialize solution using random data
+        """
+        raise NotImplementedError
+
+
+    def __str__(self):
+        print("Generic solution with ", self.data)

+ 0 - 0
solutions/__init__.py


+ 0 - 0
updators/__init__.py


+ 0 - 0
updators/crossovers/__init__.py


+ 51 - 0
updators/mutators/SimpleMutation.py

@@ -0,0 +1,51 @@
+# main imports
+import random
+import sys
+
+# module imports
+sys.path.insert(0, '') # trick to enable import of main folder module
+
+from solutions.BinarySolution import BinarySolution
+from solutions.Solution import Solution
+
+def SimpleBinaryMutation(solution):
+    size = solution.size
+
+    cell = random.randint(0, size - 1)
+
+    # copy data of solution
+    currentData = solution.data.copy()
+
+    # swicth values
+    if currentData[cell]:
+        currentData[cell] = 0
+    else:
+        currentData[cell] = 1
+
+    # create solution of same kind with new data
+    return globals()[type(solution).__name__](currentData, size)
+
+
+def SimpleMutation(solution):
+
+    size = solution.size
+
+    firstCell = 0
+    secondCell = 0
+
+    # copy data of solution
+    currentData = solution.data.copy()
+
+    while firstCell == secondCell:
+        firstCell = random.randint(0, size - 1) 
+        secondCell = random.randint(0, size - 1)
+
+    temp = currentData[firstCell]
+
+    # swicth values
+    currentData[firstCell] = currentData[secondCell]
+    currentData[secondCell] = temp
+    
+    # create solution of same kind with new data
+    return globals()[type(solution).__name__](currentData, size)
+

+ 0 - 0
updators/mutators/__init__.py


+ 13 - 0
updators/policies/Policy.py

@@ -0,0 +1,13 @@
+# define policy to choose `updator` function at current iteration
+class Policy():
+
+    # here you can define your statistical variables for choosing next operator to apply
+
+    def __init__(self, _updators):
+        self.updators = _updators
+
+    def apply(self, solution):
+        """
+        Apply specific updator to solution and returns solution
+        """
+        raise NotImplementedError

+ 17 - 0
updators/policies/RandomPolicy.py

@@ -0,0 +1,17 @@
+# main imports
+import random
+
+# module imports
+from .Policy import Policy
+
+class RandomPolicy(Policy):
+
+    def apply(self, solution):  
+
+        # TODO : implement for mutator (need two parameters)
+
+        # choose updator randomly
+        index = random.randint(0, len(self.updators) - 1)
+        updator = self.updators[index]
+        
+        return solution.apply(updator)

+ 0 - 0
updators/policies/__init__.py