diff --git a/moead_framework/algorithm/abstract_moead.py b/moead_framework/algorithm/abstract_moead.py index 6b64e9a..7574126 100644 --- a/moead_framework/algorithm/abstract_moead.py +++ b/moead_framework/algorithm/abstract_moead.py @@ -3,6 +3,7 @@ from moead_framework.core.offspring_generator.offspring_generator import OffspringGeneratorGeneric from moead_framework.core.selector.closest_neighbors_selector import ClosestNeighborsSelector +from moead_framework.core.sps_strategy.sps_all import SpsAllSubproblems from moead_framework.core.termination_criteria.max_evaluation import MaxEvaluation from moead_framework.tool.mop import is_duplicated, get_non_dominated, generate_weight_vectors @@ -15,9 +16,11 @@ def __init__(self, problem, max_evaluation, number_of_objective, number_of_weigh genetic_operator=None, parent_selector=None, mating_pool_selector=None, + sps_strategy=None, weight_file=None): self.problem = problem self.aggregation_function = aggregation_function() + if termination_criteria is None: self.termination_criteria = MaxEvaluation(algorithm_instance=self) else: @@ -35,6 +38,11 @@ def __init__(self, problem, max_evaluation, number_of_objective, number_of_weigh self.b = self.generate_closest_weight_vectors() self.current_sub_problem = -1 + if sps_strategy is None: + self.sps_strategy = SpsAllSubproblems(algorithm_instance=self) + else: + self.sps_strategy = sps_strategy(algorithm_instance=self) + if mating_pool_selector is None: self.mating_pool_selector = ClosestNeighborsSelector(algorithm_instance=self) else: @@ -44,7 +52,6 @@ def __init__(self, problem, max_evaluation, number_of_objective, number_of_weigh self.parent_selector = parent_selector self.offspring_generator = OffspringGeneratorGeneric(algorithm_instance=self) - @abstractmethod def run(self, checkpoint=None): pass @@ -53,14 +60,14 @@ def run(self, checkpoint=None): def update_solutions(self, solution, scal_function, sub_problem): pass - def sps_strategy(self): - return range(self.number_of_weight) + def get_sub_problems_to_visit(self): + return self.sps_strategy.get_sub_problems() def mating_pool_selection(self, sub_problem): return self.mating_pool_selector.select(sub_problem) def generate_offspring(self, population): - return self.offspring_generator.run(population_indexes=population) # useless d'utiliser une class si c'est générique + return self.offspring_generator.run(population_indexes=population) def repair(self, solution): return solution diff --git a/moead_framework/algorithm/combinatorial/moead.py b/moead_framework/algorithm/combinatorial/moead.py index 80429f0..52390ce 100644 --- a/moead_framework/algorithm/combinatorial/moead.py +++ b/moead_framework/algorithm/combinatorial/moead.py @@ -17,6 +17,7 @@ def __init__(self, problem, mating_pool_selector=None, genetic_operator=None, parent_selector=None, + sps_strategy=None, weight_file=None): self.current_eval = 1 @@ -32,6 +33,7 @@ def __init__(self, problem, genetic_operator=genetic_operator, mating_pool_selector=mating_pool_selector, parent_selector=parent_selector, + sps_strategy=sps_strategy, weight_file=weight_file) self.number_of_crossover_points = number_of_crossover_points @@ -50,7 +52,7 @@ def run(self, checkpoint=None): while self.termination_criteria.test(): # For each sub-problem i - for i in self.sps_strategy(): + for i in self.get_sub_problems_to_visit(): if checkpoint is not None: checkpoint() diff --git a/moead_framework/algorithm/combinatorial/moead_delta_nr.py b/moead_framework/algorithm/combinatorial/moead_delta_nr.py index 34811af..7b07a8b 100644 --- a/moead_framework/algorithm/combinatorial/moead_delta_nr.py +++ b/moead_framework/algorithm/combinatorial/moead_delta_nr.py @@ -15,6 +15,7 @@ def __init__(self, problem, delta, number_of_replacement, aggregation_function, + sps_strategy=None, number_of_crossover_points=2, parent_selector=None, weight_file=None): @@ -33,6 +34,7 @@ def __init__(self, problem, mating_pool_selector=mating_pool_selector, parent_selector=parent_selector, number_of_crossover_points=number_of_crossover_points, + sps_strategy=sps_strategy, weight_file=weight_file) def update_solutions(self, solution, aggregation_function, sub_problem): diff --git a/moead_framework/algorithm/combinatorial/moead_dra.py b/moead_framework/algorithm/combinatorial/moead_dra.py index 67c4082..56a96e4 100644 --- a/moead_framework/algorithm/combinatorial/moead_dra.py +++ b/moead_framework/algorithm/combinatorial/moead_dra.py @@ -1,6 +1,7 @@ import random import numpy as np from moead_framework.algorithm.combinatorial.moead_delta_nr import MoeadDeltaNr +from moead_framework.core.sps_strategy.sps_dra import SpsDra class MoeadDRA(MoeadDeltaNr): @@ -27,6 +28,7 @@ def __init__(self, problem, number_of_replacement=number_of_replacement, aggregation_function=aggregation_function, number_of_crossover_points=number_of_crossover_points, + sps_strategy=SpsDra, weight_file=weight_file) self.pi = np.ones(self.number_of_weight) @@ -43,7 +45,7 @@ def run(self, checkpoint=None): while self.current_eval < self.max_evaluation: - for i in self.sps_strategy(): + for i in self.get_sub_problems_to_visit(): if checkpoint is not None: checkpoint(self.current_eval) @@ -75,54 +77,6 @@ def run(self, checkpoint=None): return self.ep - def get_xtrem_index(self): - xtrem_index = [] - for i in range(self.number_of_weight): - weight = self.weights[i] - for j in range(self.number_of_objective): - if weight[j] == 1: - xtrem_index.append(i) - break - - return xtrem_index - - def sps_strategy(self): - """ - Select at first the indexes of the sub problems whose objectives are MOP - individual objectives fi ([1, 0] and [0, 1] for example) - and add sub problems by a 10-tournament - :return: - """ - selection = [] - - for w in range(self.number_of_weight): - count_zero = 0 - for o in self.weights[w]: - if o == 0: - count_zero += 1 - - if count_zero == self.number_of_objective - 1: - selection.append(w) - break - - xtrem_index = self.get_xtrem_index() - - # 10-tournament - for i in range(int((self.number_of_weight / 5) - self.number_of_objective)): - range_list = list(range(self.number_of_weight)) - random_indexes = random.sample(list(set(range_list) - set(xtrem_index)), 10) - - best_index = random_indexes[0] - best_pi = self.pi[random_indexes[0]] - for index in random_indexes: - if self.pi[index] > best_pi: - best_index = index - best_pi = self.pi[index] - - selection.append(best_index) - - return selection - def update_scores(self, sub_problem, score): """ self.scores[sub_problem][0] = old score diff --git a/moead_framework/algorithm/combinatorial/moead_sps_random.py b/moead_framework/algorithm/combinatorial/moead_sps_random.py index 5ca309e..c851921 100644 --- a/moead_framework/algorithm/combinatorial/moead_sps_random.py +++ b/moead_framework/algorithm/combinatorial/moead_sps_random.py @@ -1,5 +1,6 @@ import random from moead_framework.algorithm.combinatorial.moead import Moead +from moead_framework.core.sps_strategy.sps_random_and_boundaries import SpsRandomAndBoundaries class MoeadSPSRandom(Moead): @@ -9,7 +10,7 @@ def __init__(self, problem, number_of_objective, number_of_weight, number_of_weight_neighborhood, - number_of_subproblem, + number_of_subproblem_to_visit, aggregation_function, number_of_crossover_points=2, mating_pool_selector=None, @@ -28,32 +29,8 @@ def __init__(self, problem, genetic_operator=genetic_operator, mating_pool_selector=mating_pool_selector, parent_selector=parent_selector, + sps_strategy=SpsRandomAndBoundaries, weight_file=weight_file) self.current_eval = 1 - self.number_of_subproblem = number_of_subproblem - - def sps_strategy(self): - """ - Select one random sub problems in each cluster - :return: an array of sub problems - """ - range_list = list(range(self.number_of_weight)) - xtrem_index = self.get_xtrem_index() - random_indexes = random.sample(list(set(range_list)-set(xtrem_index)), self.number_of_subproblem) - - random_indexes = random_indexes + xtrem_index - random.shuffle(random_indexes) - - return random_indexes - - def get_xtrem_index(self): - xtrem_index = [] - for i in range(self.number_of_weight): - weight = self.weights[i] - for j in range(self.number_of_objective): - if weight[j] == 1: - xtrem_index.append(i) - break - - return xtrem_index + self.number_of_subproblem = number_of_subproblem_to_visit diff --git a/moead_framework/algorithm/numerical/moead.py b/moead_framework/algorithm/numerical/moead.py index ccb07c2..0ec2177 100644 --- a/moead_framework/algorithm/numerical/moead.py +++ b/moead_framework/algorithm/numerical/moead.py @@ -17,6 +17,7 @@ def __init__(self, problem, mating_pool_selector=None, genetic_operator=None, parent_selector=None, + sps_strategy=None, weight_file=None): self.current_eval = 1 @@ -32,6 +33,7 @@ def __init__(self, problem, genetic_operator=genetic_operator, mating_pool_selector=mating_pool_selector, parent_selector=parent_selector, + sps_strategy=sps_strategy, weight_file=weight_file) if genetic_operator is None: @@ -49,7 +51,7 @@ def run(self, checkpoint=None): while self.termination_criteria.test(): # For each sub-problem i - for i in self.sps_strategy(): + for i in self.get_sub_problems_to_visit(): if checkpoint is not None: checkpoint() diff --git a/moead_framework/core/sps_strategy/__init__.py b/moead_framework/core/sps_strategy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/moead_framework/core/sps_strategy/abstract_sps.py b/moead_framework/core/sps_strategy/abstract_sps.py new file mode 100644 index 0000000..f870aeb --- /dev/null +++ b/moead_framework/core/sps_strategy/abstract_sps.py @@ -0,0 +1,11 @@ +from abc import ABC, abstractmethod + + +class SpsStrategy(ABC): + + def __init__(self, algorithm_instance): + self.algorithm = algorithm_instance + + @abstractmethod + def get_sub_problems(self): + pass diff --git a/moead_framework/core/sps_strategy/sps_all.py b/moead_framework/core/sps_strategy/sps_all.py new file mode 100644 index 0000000..39df2e1 --- /dev/null +++ b/moead_framework/core/sps_strategy/sps_all.py @@ -0,0 +1,9 @@ +from moead_framework.core.sps_strategy.abstract_sps import SpsStrategy + + +class SpsAllSubproblems(SpsStrategy): + """ + It is the classic SPS Strategy of MOEA/D, we visit all sub-problems at each generation + """ + def get_sub_problems(self): + return range(self.algorithm.number_of_weight) diff --git a/moead_framework/core/sps_strategy/sps_dra.py b/moead_framework/core/sps_strategy/sps_dra.py new file mode 100644 index 0000000..03c5271 --- /dev/null +++ b/moead_framework/core/sps_strategy/sps_dra.py @@ -0,0 +1,60 @@ +import random + +from moead_framework.core.sps_strategy.abstract_sps import SpsStrategy + + +class SpsDra(SpsStrategy): + """ + Q. Zhang, W. Liu and H. Li, + "The performance of a new version of MOEA/D on CEC09 unconstrained MOP test instances" + 2009 IEEE Congress on Evolutionary Computation + Trondheim, 2009, pp. 203-208 + doi: 10.1109/CEC.2009.4982949. + """ + def get_sub_problems(self): + """ + Select at first the indexes of the sub problems whose objectives are MOP + individual objectives fi ([1, 0] and [0, 1] for example) + and add sub problems by a 10-tournament + :return: + """ + selection = [] + + for w in range(self.algorithm.number_of_weight): + count_zero = 0 + for o in self.algorithm.weights[w]: + if o == 0: + count_zero += 1 + + if count_zero == self.algorithm.number_of_objective - 1: + selection.append(w) + break + + xtrem_index = self.get_xtrem_index() + + # 10-tournament + for i in range(int((self.algorithm.number_of_weight / 5) - self.algorithm.number_of_objective)): + range_list = list(range(self.algorithm.number_of_weight)) + random_indexes = random.sample(list(set(range_list) - set(xtrem_index)), 10) + + best_index = random_indexes[0] + best_pi = self.algorithm.pi[random_indexes[0]] + for index in random_indexes: + if self.algorithm.pi[index] > best_pi: + best_index = index + best_pi = self.algorithm.pi[index] + + selection.append(best_index) + + return selection + + def get_xtrem_index(self): + xtrem_index = [] + for i in range(self.algorithm.number_of_weight): + weight = self.algorithm.weights[i] + for j in range(self.algorithm.number_of_objective): + if weight[j] == 1: + xtrem_index.append(i) + break + + return xtrem_index diff --git a/moead_framework/core/sps_strategy/sps_random_and_boundaries.py b/moead_framework/core/sps_strategy/sps_random_and_boundaries.py new file mode 100644 index 0000000..e2a49f6 --- /dev/null +++ b/moead_framework/core/sps_strategy/sps_random_and_boundaries.py @@ -0,0 +1,36 @@ +import random + +from moead_framework.core.sps_strategy.abstract_sps import SpsStrategy + + +class SpsRandomAndBoundaries(SpsStrategy): + """ + Pruvost, Geoffrey, et al. + "On the Combined Impact of Population Size and Sub-problem Selection in MOEA/D." + European Conference on Evolutionary Computation in Combinatorial Optimization (Part of EvoStar). + Springer, Cham, 2020 + """ + def get_sub_problems(self): + """ + Select one random sub problems in each cluster + :return: an array of sub problems + """ + range_list = list(range(self.algorithm.number_of_weight)) + xtrem_index = self.get_xtrem_index() + random_indexes = random.sample(list(set(range_list) - set(xtrem_index)), self.algorithm.number_of_subproblem) + + random_indexes = random_indexes + xtrem_index + random.shuffle(random_indexes) + + return random_indexes + + def get_xtrem_index(self): + xtrem_index = [] + for i in range(self.algorithm.number_of_weight): + weight = self.algorithm.weights[i] + for j in range(self.algorithm.number_of_objective): + if weight[j] == 1: + xtrem_index.append(i) + break + + return xtrem_index diff --git a/moead_framework/test/test_combinatorial_algorithm.py b/moead_framework/test/test_combinatorial_algorithm.py index ffdc733..e10ccb4 100644 --- a/moead_framework/test/test_combinatorial_algorithm.py +++ b/moead_framework/test/test_combinatorial_algorithm.py @@ -81,7 +81,7 @@ def test_moead_sps_random(self): aggregation_function=Tchebycheff, number_of_weight_neighborhood=self.number_of_weight_neighborhood, number_of_crossover_points=self.number_of_crossover_points, - number_of_subproblem=number_of_subproblem, + number_of_subproblem_to_visit=number_of_subproblem, weight_file=self.weight_file, ) diff --git a/setup.py b/setup.py index a5c7eb9..c8192ee 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="moead-framework", - version="0.4.0", + version="0.5.0", author="Geoffrey Pruvost", author_email="geoffrey@pruvost.xyz", description="MOEA/D Framework in Python 3",