Skip to content

Commit d8f768e

Browse files
committed
Merge branch 'review'
2 parents 675bcd3 + cefc791 commit d8f768e

44 files changed

Lines changed: 1280 additions & 152 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/_static/components.png

272 KB
Loading

docs/contributing.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ Contributing
33
===========================================
44

55
When contributing to this project, please first discuss the change you wish to make via GitHub discussions,
6-
email, or any other method with the owners of this repository before making a change.
6+
email, or any other method with the owners of this repository before making a change.
7+
The contribution can be the report of a bug, the request for a new feature or
8+
modifying the code of the framework to improve it.
79

810
Please note we have a code of conduct, please follow it in all your interactions with the project.
911

@@ -31,6 +33,13 @@ help us to understand your request. Share with us the following information:
3133
- the error raised by your code
3234
- all steps to reproduce the bug
3335

36+
37+
Support
38+
________________________________
39+
40+
If you have any questions about the project, don't hesitate to create a new discussion with GitHub Discussions (https://github.com/moead-framework/framework/discussions).
41+
It is the space for our community to have conversations, ask questions and post answers without opening issues.
42+
3443
Code of Conduct
3544
________________________________
3645

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ If you have any questions about the framework or the project, feel free to creat
2222
:maxdepth: 2
2323

2424
install
25+
user_guide
2526
tuto
2627
api
2728
examples

docs/user_guide.rst

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
2+
User guide
3+
===========================================
4+
5+
Overview
6+
--------------------------------------
7+
8+
moead-framework is a modular framework composed of 10 customizable components summarized in the following figure.
9+
In this user guide, we will present the role of each component in a general way.
10+
For more detailed information, you can visit the following documentation : https://moead-framework.github.io/framework/html/api.html
11+
12+
.. image:: _static/components.png
13+
:width: 600
14+
:alt: overview
15+
16+
Problem & Solution
17+
--------------------------------------
18+
19+
The problem is one of the main components of the framework.
20+
The solutions are the link between the algorithm and the problem. The solutions are generated during the algorithm
21+
in order to solve the problem.
22+
23+
The problem in the framework is a component that allows to randomly generate solutions and to evaluate them.
24+
The solutions have two important attributes:
25+
- ``decision_vector`` : list of all decision variables of the solution
26+
- ``F`` : all objectives values of the solution, for example, ``F[0]`` is the objective value for the function f0 and ``F[1]`` is the objective value for the function f1.
27+
28+
Example of the Rmnk problem :
29+
>>> from moead_framework.problem.combinatorial import Rmnk
30+
>>>
31+
>>> # The file is available here : https://github.com/moead-framework/data/blob/master/problem/RMNK/Instances/rmnk_0_2_100_1_0.dat
32+
>>> # Others instances are available here : https://github.com/moead-framework/data/tree/master/problem/RMNK/Instances
33+
>>> instance_file = "rmnk_0_2_100_1_0.dat"
34+
>>> problem = Rmnk(instance_file=instance_file)
35+
>>>
36+
>>> # Generate a new solution
37+
>>> solution = problem.generate_random_solution()
38+
>>>
39+
>>> # Print all decision variables of the solution
40+
>>> print(solution.decision_vector)
41+
>>>
42+
>>> # Print all objectives values of the solution
43+
>>> print(solution.F)
44+
45+
46+
Algorithm
47+
--------------------------------------
48+
49+
In the framework, the algorithm is the main component, composed of several sub-components to easily parameterize it.
50+
The algorithms are defined with default components which allows to execute them very easily with few parameters.
51+
The mandatory parameters are the problem, the maximum number of evaluations,
52+
the number of sub-problems in the neighborhood, the file that defines the weight vectors and the aggregation function.
53+
The complete and detailed settings of each algorithm are available here : https://moead-framework.github.io/framework/html/main_components.html#algorithms.
54+
55+
Example with the original MOEA/D algorithm :
56+
>>> from moead_framework.aggregation import Tchebycheff
57+
>>> from moead_framework.algorithm.combinatorial import Moead
58+
>>> from moead_framework.problem.combinatorial import Rmnk
59+
>>>
60+
>>> # The file is available here : https://github.com/moead-framework/data/blob/master/problem/RMNK/Instances/rmnk_0_2_100_1_0.dat
61+
>>> # Others instances are available here : https://github.com/moead-framework/data/tree/master/problem/RMNK/Instances
62+
>>> instance_file = "moead_framework/test/data/instances/rmnk_0_2_100_1_0.dat"
63+
>>> rmnk = Rmnk(instance_file=instance_file)
64+
>>>
65+
>>> number_of_weight = 10
66+
>>> # The file is available here : https://github.com/moead-framework/data/blob/master/weights/SOBOL-2objs-10wei.ws
67+
>>> # Others weights files are available here : https://github.com/moead-framework/data/tree/master/weights
68+
>>> weight_file = "moead_framework/test/data/weights/SOBOL-" + str(rmnk.number_of_objective) + "objs-" + str(number_of_weight) + "wei.ws"
69+
>>>
70+
>>> moead = Moead(problem=rmnk,
71+
>>> max_evaluation=1000,
72+
>>> number_of_weight_neighborhood=2,
73+
>>> weight_file=weight_file,
74+
>>> aggregation_function=Tchebycheff,
75+
>>> )
76+
>>>
77+
>>> population = moead.run()
78+
79+
80+
Sub-problem selection strategy
81+
--------------------------------------
82+
83+
This component, introduced in :cite:`moead_dra` and :cite:`gpruvost_evocop2020`, has the objective to select the sub-problems
84+
to be optimized during the next generation. By default in MOEA/D, all subproblems are selected.
85+
This component requires the attribute ``number_of_subproblem`` in the algorithm which defines the
86+
number of subproblems to select.
87+
88+
More information : https://moead-framework.github.io/framework/html/other_components.html#sub-problem-selection-strategy
89+
90+
91+
Aggregation functions
92+
--------------------------------------
93+
94+
This component defines the aggregation function used to decompose the multi-objective problem into several single-objective sub-problems.
95+
The function ``run(solution, number_of_objective, weights, sub_problem, z)`` allows to evaluate a solution for a
96+
given sub-problem and the function ``is_better(old_value, new_value)`` allows to compare two aggregation values.
97+
98+
More information : https://moead-framework.github.io/framework/html/main_components.html#aggregation-functions
99+
100+
101+
Mating Selector
102+
--------------------------------------
103+
104+
This component aims to select the solutions that can be chosen as parent solutions to generate an offspring.
105+
The method ``select(sub_problem)`` returns the indexes of the selected solutions.
106+
By default in MOEA/D, this component returns the index of the solutions in the neighborhood of the subproblem
107+
currently visited.
108+
109+
More information : https://moead-framework.github.io/framework/html/other_components.html#mating-selector
110+
111+
112+
Offspring generator
113+
--------------------------------------
114+
115+
This component is designed to generate offsprings from a set of solutions given in parameter.
116+
Its unique method ``run(population_indexes)`` returns a unique solution.
117+
By default, a generic component is used, it uses two subcomponents which allow to select
118+
parent solutions (Parent Selector) and then to execute a genetic operator to generate the new offspring.
119+
120+
More information : https://moead-framework.github.io/framework/html/other_components.html#offspring-generator
121+
122+
123+
Parent Selector
124+
~~~~~~~~~~~~~~~~~~~~~~
125+
126+
This component is used in the offspring generator component.
127+
It allows to choose the solutions which will be used to generate new solutions.
128+
The method ``select(indexes)`` takes as parameter the indexes of the solutions available to be
129+
selected (chosen by the Mating selector component) to return a list of solutions.
130+
131+
More information : https://moead-framework.github.io/framework/html/other_components.html#parent-selector
132+
133+
134+
Genetic operators
135+
~~~~~~~~~~~~~~~~~~~~~~
136+
137+
This component is used in the offspring generator component. This component is initialized by its constructor with
138+
the solutions chosen by the Parent Selector component. Other parameters can be added according to the operators
139+
like the number of crossover points or the mutation rate for example.
140+
The ``run()`` method returns after its execution a new offspring.
141+
142+
More information : https://moead-framework.github.io/framework/html/other_components.html#genetic-operators
143+
144+
145+
Termination criteria
146+
--------------------------------------
147+
148+
This component aims at defining the stopping criteria of the algorithm. The method ``test()`` of this component
149+
returns a boolean to define if the algorithm can continue to be executed.
150+
151+
More information : https://moead-framework.github.io/framework/html/other_components.html#termination-criteria

moead_framework/aggregation/functions.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import numpy as np
12
from abc import ABC, abstractmethod
23

4+
from moead_framework.solution.base import Solution
5+
36

47
class AggregationFunction(ABC):
58
"""
@@ -18,6 +21,29 @@ def run(self, solution, number_of_objective, weights, sub_problem, z):
1821
:param z: {list<float>} coordinates of the reference point Z*
1922
:return: {float} the aggregation value of the solution for the weight vector: weights[sub-problem]
2023
"""
24+
if not isinstance(solution, Solution):
25+
raise TypeError(
26+
f"The parameter 'solution' of {self.__class__.__name__}.run() must be a child of the Solution class. Instead, we have {type(solution)}")
27+
28+
if not isinstance(number_of_objective, int):
29+
raise TypeError(
30+
f"The parameter 'number_of_objective' of {self.__class__.__name__}.run() must be an integer. Instead, we have {type(number_of_objective)}")
31+
32+
if not isinstance(weights, list):
33+
raise TypeError(
34+
f"The parameter 'weights' of {self.__class__.__name__}.run() must be a list. Instead, we have {type(weights)}")
35+
36+
if not isinstance(sub_problem, int):
37+
raise TypeError(
38+
f"The parameter 'sub_problem' of {self.__class__.__name__}.run() must be an integer. Instead, we have {type(sub_problem)}")
39+
40+
if (not isinstance(z, list)) & (not isinstance(z, np.ndarray)):
41+
raise TypeError(
42+
f"The parameter 'z' of {self.__class__.__name__}.run() must be a list. Instead, we have {type(z)}")
43+
else:
44+
if len(z) != number_of_objective:
45+
raise TypeError(
46+
f"The number of items in the parameter 'z' of {self.__class__.__name__}.run() must be equals at {number_of_objective}. Instead, we have {len(z)}")
2147

2248
@abstractmethod
2349
def is_better(self, old_value, new_value):
@@ -29,3 +55,6 @@ def is_better(self, old_value, new_value):
2955
:return: {boolean} True if new_value is better than old_value.
3056
The test depends of the aggregation function and of the context (minimization or maximization).
3157
"""
58+
# if not isinstance(old_value, (np.floating, float)) & (not isinstance(new_value, (np.floating, float))):
59+
if ((not np.issubdtype(type(old_value), (np.floating))) & (not isinstance(old_value, int))) | (not np.issubdtype(type(new_value), (np.floating))) & (not isinstance(new_value, int)):
60+
raise TypeError(f"Parameters of {self.__class__.__name__}.is_better() must be float or int. Instead, we have {type(old_value)} and {type(new_value)}")

moead_framework/aggregation/tchebycheff.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def is_better(self, old_value, new_value):
1616
:param new_value: {float} new aggregation value
1717
:return: {boolean} True if new_value is better than old_value.
1818
"""
19+
super().is_better(old_value, new_value)
1920
return new_value < old_value
2021

2122
def run(self, solution, number_of_objective, weights, sub_problem, z):
@@ -29,6 +30,7 @@ def run(self, solution, number_of_objective, weights, sub_problem, z):
2930
:param z: {list<float>} coordinates of the reference point Z*
3031
:return: {float} the aggregation value of the solution for the weight vector: weights[sub-problem]
3132
"""
33+
super().run(solution,number_of_objective,weights, sub_problem, z)
3234
max_distance = 0
3335

3436
for i in range(number_of_objective):

moead_framework/aggregation/weighted_sum.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def is_better(self, old_value, new_value):
1313
:param new_value: {float} new aggregation value
1414
:return: {boolean} True if new_value is better than old_value.
1515
"""
16+
super().is_better(old_value, new_value)
1617
return new_value < old_value
1718

1819
def run(self, solution, number_of_objective, weights, sub_problem, z):
@@ -26,6 +27,8 @@ def run(self, solution, number_of_objective, weights, sub_problem, z):
2627
:param z: {list<float>} coordinates of the reference point Z*
2728
:return: {float} the aggregation value of the solution for the weight vector: weights[sub-problem]
2829
"""
30+
super().run(solution, number_of_objective, weights, sub_problem, z)
31+
2932
res = 0
3033
for i in range(number_of_objective):
3134
res += solution.F[i] * weights[sub_problem][i]

moead_framework/algorithm/abstract_moead.py

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,18 @@
22

33
import numpy as np
44

5+
from moead_framework.aggregation.functions import AggregationFunction
6+
from moead_framework.core.genetic_operator import GeneticOperator
7+
from moead_framework.core.offspring_generator.abstract_mating import OffspringGenerator
58
from moead_framework.core.offspring_generator.offspring_generator import OffspringGeneratorGeneric
9+
from moead_framework.core.parent_selector.abstract_parent_selector import ParentSelector
10+
from moead_framework.core.selector.abstract_selector import MatingPoolSelector
611
from moead_framework.core.selector.closest_neighbors_selector import ClosestNeighborsSelector
12+
from moead_framework.core.sps_strategy.abstract_sps import SpsStrategy
713
from moead_framework.core.sps_strategy.sps_all import SpsAllSubproblems
14+
from moead_framework.core.termination_criteria.abstract_termination_criteria import TerminationCriteria
815
from moead_framework.core.termination_criteria.max_evaluation import MaxEvaluation
16+
from moead_framework.problem import Problem
917
from moead_framework.tool.mop import is_duplicated, get_non_dominated, generate_weight_vectors
1018

1119

@@ -44,8 +52,18 @@ def __init__(self, problem, max_evaluation, number_of_weight_neighborhood,
4452
:param number_of_weight: Deprecated -- {integer} number of weight vector used to decompose the problem. Deprecated, remove in the next major release.
4553
:param number_of_objective: Deprecated -- {integer} number of objective in the problem. Deprecated, remove in the next major release.
4654
"""
47-
self.problem = problem
48-
self.aggregation_function = aggregation_function()
55+
if isinstance(problem, Problem):
56+
self.problem = problem
57+
else:
58+
raise TypeError("The expected type of `problem` is `Problem`")
59+
60+
if not isinstance(weight_file, str):
61+
raise TypeError("The expected type of `weight_file` is `Str`")
62+
63+
if issubclass(aggregation_function, AggregationFunction):
64+
self.aggregation_function = aggregation_function()
65+
else:
66+
raise TypeError("The expected type of `aggregation_function` is `AggregationFunction`")
4967

5068
if number_of_weight is not None:
5169
import warnings
@@ -58,11 +76,23 @@ def __init__(self, problem, max_evaluation, number_of_weight_neighborhood,
5876
if termination_criteria is None:
5977
self.termination_criteria = MaxEvaluation(algorithm_instance=self)
6078
else:
61-
self.termination_criteria = termination_criteria(algorithm_instance=self)
79+
if issubclass(termination_criteria, TerminationCriteria):
80+
self.termination_criteria = termination_criteria(algorithm_instance=self)
81+
else:
82+
raise TypeError("The expected type of `termination_criteria` is `TerminationCriteria`")
83+
84+
if isinstance(max_evaluation, int):
85+
self.max_evaluation = max_evaluation
86+
else:
87+
raise TypeError("The expected type of `max_evaluation` is `int`")
6288

63-
self.max_evaluation = max_evaluation
6489
self.number_of_objective = self.problem.number_of_objective
65-
self.t = number_of_weight_neighborhood
90+
91+
if isinstance(number_of_weight_neighborhood, int):
92+
self.t = number_of_weight_neighborhood
93+
else:
94+
raise TypeError("The expected type of `number_of_weight_neighborhood` is `int`")
95+
6696
self.ep = []
6797

6898
self.weights = generate_weight_vectors(weight_file, shuffle=False)
@@ -78,20 +108,36 @@ def __init__(self, problem, max_evaluation, number_of_weight_neighborhood,
78108
if sps_strategy is None:
79109
self.sps_strategy = SpsAllSubproblems(algorithm_instance=self)
80110
else:
81-
self.sps_strategy = sps_strategy(algorithm_instance=self)
111+
if issubclass(sps_strategy, SpsStrategy):
112+
self.sps_strategy = sps_strategy(algorithm_instance=self)
113+
else:
114+
raise TypeError("The expected type of `sps_strategy` is `SpsStrategy`")
82115

83116
if mating_pool_selector is None:
84117
self.mating_pool_selector = ClosestNeighborsSelector(algorithm_instance=self)
85118
else:
86-
self.mating_pool_selector = mating_pool_selector(algorithm_instance=self)
119+
if issubclass(mating_pool_selector, MatingPoolSelector):
120+
self.mating_pool_selector = mating_pool_selector(algorithm_instance=self)
121+
else:
122+
raise TypeError("The expected type of `mating_pool_selector` is `MatingPoolSelector`")
87123

88-
self.genetic_operator = genetic_operator
89-
self.parent_selector = parent_selector
124+
if issubclass(genetic_operator, GeneticOperator):
125+
self.genetic_operator = genetic_operator
126+
else:
127+
raise TypeError("The expected type of `genetic_operator` is `GeneticOperator`")
128+
129+
if issubclass(parent_selector, ParentSelector):
130+
self.parent_selector = parent_selector(algorithm=self)
131+
else:
132+
raise TypeError("The expected type of `parent_selector` is `ParentSelector`")
90133

91134
if offspring_generator is None:
92135
self.offspring_generator = OffspringGeneratorGeneric(algorithm_instance=self)
93136
else:
94-
self.offspring_generator = offspring_generator(algorithm_instance=self)
137+
if issubclass(offspring_generator, OffspringGenerator):
138+
self.offspring_generator = offspring_generator(algorithm_instance=self)
139+
else:
140+
raise TypeError("The expected type of `offspring_generator` is `OffspringGenerator`")
95141

96142
def run(self, checkpoint=None):
97143
"""

0 commit comments

Comments
 (0)