desdeo 2.0.0__py3-none-any.whl → 2.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. desdeo/adm/ADMAfsar.py +551 -0
  2. desdeo/adm/ADMChen.py +414 -0
  3. desdeo/adm/BaseADM.py +119 -0
  4. desdeo/adm/__init__.py +11 -0
  5. desdeo/api/__init__.py +6 -6
  6. desdeo/api/app.py +38 -28
  7. desdeo/api/config.py +65 -44
  8. desdeo/api/config.toml +23 -12
  9. desdeo/api/db.py +10 -8
  10. desdeo/api/db_init.py +12 -6
  11. desdeo/api/models/__init__.py +220 -20
  12. desdeo/api/models/archive.py +16 -27
  13. desdeo/api/models/emo.py +128 -0
  14. desdeo/api/models/enautilus.py +69 -0
  15. desdeo/api/models/gdm/gdm_aggregate.py +139 -0
  16. desdeo/api/models/gdm/gdm_base.py +69 -0
  17. desdeo/api/models/gdm/gdm_score_bands.py +114 -0
  18. desdeo/api/models/gdm/gnimbus.py +138 -0
  19. desdeo/api/models/generic.py +104 -0
  20. desdeo/api/models/generic_states.py +401 -0
  21. desdeo/api/models/nimbus.py +158 -0
  22. desdeo/api/models/preference.py +44 -6
  23. desdeo/api/models/problem.py +274 -64
  24. desdeo/api/models/session.py +4 -1
  25. desdeo/api/models/state.py +419 -52
  26. desdeo/api/models/user.py +7 -6
  27. desdeo/api/models/utopia.py +25 -0
  28. desdeo/api/routers/_EMO.backup +309 -0
  29. desdeo/api/routers/_NIMBUS.py +6 -3
  30. desdeo/api/routers/emo.py +497 -0
  31. desdeo/api/routers/enautilus.py +237 -0
  32. desdeo/api/routers/gdm/gdm_aggregate.py +234 -0
  33. desdeo/api/routers/gdm/gdm_base.py +420 -0
  34. desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_manager.py +398 -0
  35. desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_routers.py +377 -0
  36. desdeo/api/routers/gdm/gnimbus/gnimbus_manager.py +698 -0
  37. desdeo/api/routers/gdm/gnimbus/gnimbus_routers.py +591 -0
  38. desdeo/api/routers/generic.py +233 -0
  39. desdeo/api/routers/nimbus.py +705 -0
  40. desdeo/api/routers/problem.py +201 -4
  41. desdeo/api/routers/reference_point_method.py +20 -44
  42. desdeo/api/routers/session.py +50 -26
  43. desdeo/api/routers/user_authentication.py +180 -26
  44. desdeo/api/routers/utils.py +187 -0
  45. desdeo/api/routers/utopia.py +230 -0
  46. desdeo/api/schema.py +10 -4
  47. desdeo/api/tests/conftest.py +94 -2
  48. desdeo/api/tests/test_enautilus.py +330 -0
  49. desdeo/api/tests/test_models.py +550 -72
  50. desdeo/api/tests/test_routes.py +902 -43
  51. desdeo/api/utils/_database.py +263 -0
  52. desdeo/api/utils/database.py +28 -266
  53. desdeo/api/utils/emo_database.py +40 -0
  54. desdeo/core.py +7 -0
  55. desdeo/emo/__init__.py +154 -24
  56. desdeo/emo/hooks/archivers.py +18 -2
  57. desdeo/emo/methods/EAs.py +128 -5
  58. desdeo/emo/methods/bases.py +9 -56
  59. desdeo/emo/methods/templates.py +111 -0
  60. desdeo/emo/operators/crossover.py +544 -42
  61. desdeo/emo/operators/evaluator.py +10 -14
  62. desdeo/emo/operators/generator.py +127 -24
  63. desdeo/emo/operators/mutation.py +212 -41
  64. desdeo/emo/operators/scalar_selection.py +202 -0
  65. desdeo/emo/operators/selection.py +956 -214
  66. desdeo/emo/operators/termination.py +124 -16
  67. desdeo/emo/options/__init__.py +108 -0
  68. desdeo/emo/options/algorithms.py +435 -0
  69. desdeo/emo/options/crossover.py +164 -0
  70. desdeo/emo/options/generator.py +131 -0
  71. desdeo/emo/options/mutation.py +260 -0
  72. desdeo/emo/options/repair.py +61 -0
  73. desdeo/emo/options/scalar_selection.py +66 -0
  74. desdeo/emo/options/selection.py +127 -0
  75. desdeo/emo/options/templates.py +383 -0
  76. desdeo/emo/options/termination.py +143 -0
  77. desdeo/gdm/__init__.py +22 -0
  78. desdeo/gdm/gdmtools.py +45 -0
  79. desdeo/gdm/score_bands.py +114 -0
  80. desdeo/gdm/voting_rules.py +50 -0
  81. desdeo/mcdm/__init__.py +23 -1
  82. desdeo/mcdm/enautilus.py +338 -0
  83. desdeo/mcdm/gnimbus.py +484 -0
  84. desdeo/mcdm/nautilus_navigator.py +7 -6
  85. desdeo/mcdm/reference_point_method.py +70 -0
  86. desdeo/problem/__init__.py +16 -11
  87. desdeo/problem/evaluator.py +4 -5
  88. desdeo/problem/external/__init__.py +18 -0
  89. desdeo/problem/external/core.py +356 -0
  90. desdeo/problem/external/pymoo_provider.py +266 -0
  91. desdeo/problem/external/runtime.py +44 -0
  92. desdeo/problem/gurobipy_evaluator.py +37 -12
  93. desdeo/problem/infix_parser.py +1 -16
  94. desdeo/problem/json_parser.py +7 -11
  95. desdeo/problem/pyomo_evaluator.py +25 -6
  96. desdeo/problem/schema.py +73 -55
  97. desdeo/problem/simulator_evaluator.py +65 -15
  98. desdeo/problem/testproblems/__init__.py +26 -11
  99. desdeo/problem/testproblems/benchmarks_server.py +120 -0
  100. desdeo/problem/testproblems/cake_problem.py +185 -0
  101. desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
  102. desdeo/problem/testproblems/forest_problem.py +77 -69
  103. desdeo/problem/testproblems/multi_valued_constraints.py +119 -0
  104. desdeo/problem/testproblems/{river_pollution_problem.py → river_pollution_problems.py} +28 -22
  105. desdeo/problem/testproblems/single_objective.py +289 -0
  106. desdeo/problem/testproblems/zdt_problem.py +4 -1
  107. desdeo/problem/utils.py +1 -1
  108. desdeo/tools/__init__.py +39 -21
  109. desdeo/tools/desc_gen.py +22 -0
  110. desdeo/tools/generics.py +22 -2
  111. desdeo/tools/group_scalarization.py +3090 -0
  112. desdeo/tools/indicators_binary.py +107 -1
  113. desdeo/tools/indicators_unary.py +3 -16
  114. desdeo/tools/message.py +33 -2
  115. desdeo/tools/non_dominated_sorting.py +4 -3
  116. desdeo/tools/patterns.py +9 -7
  117. desdeo/tools/pyomo_solver_interfaces.py +49 -36
  118. desdeo/tools/reference_vectors.py +118 -351
  119. desdeo/tools/scalarization.py +340 -1413
  120. desdeo/tools/score_bands.py +491 -328
  121. desdeo/tools/utils.py +117 -49
  122. desdeo/tools/visualizations.py +67 -0
  123. desdeo/utopia_stuff/utopia_problem.py +1 -1
  124. desdeo/utopia_stuff/utopia_problem_old.py +1 -1
  125. {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info}/METADATA +47 -30
  126. desdeo-2.1.1.dist-info/RECORD +180 -0
  127. {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info}/WHEEL +1 -1
  128. desdeo-2.0.0.dist-info/RECORD +0 -120
  129. /desdeo/api/utils/{logger.py → _logger.py} +0 -0
  130. {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,131 @@
1
+ """JSON Schema for generator options."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Literal
6
+
7
+ import polars as pl
8
+ from pydantic import BaseModel, Field
9
+
10
+ from desdeo.emo.operators.evaluator import EMOEvaluator
11
+ from desdeo.emo.operators.generator import (
12
+ ArchiveGenerator,
13
+ BaseGenerator,
14
+ LHSGenerator,
15
+ RandomBinaryGenerator,
16
+ RandomGenerator,
17
+ RandomIntegerGenerator,
18
+ RandomMixedIntegerGenerator,
19
+ )
20
+
21
+ if TYPE_CHECKING:
22
+ from desdeo.problem import Problem
23
+ from desdeo.tools.patterns import Publisher
24
+
25
+
26
+ class BaseGeneratorOptions(BaseModel):
27
+ """Options for all generators."""
28
+
29
+ n_points: int = Field(gt=0, description="The number of points to generate for the initial population.")
30
+ """The number of points to generate for the initial population."""
31
+
32
+
33
+ class LHSGeneratorOptions(BaseGeneratorOptions):
34
+ """Options for Latin Hypercube Sampling (LHS) generator."""
35
+
36
+ name: Literal["LHSGenerator"] = Field(default="LHSGenerator", frozen=True, description="The name of the generator.")
37
+ """The name of the generator."""
38
+
39
+
40
+ class RandomBinaryGeneratorOptions(BaseGeneratorOptions):
41
+ """Options for Random Binary generator."""
42
+
43
+ name: Literal["RandomBinaryGenerator"] = Field(
44
+ default="RandomBinaryGenerator", frozen=True, description="The name of the generator."
45
+ )
46
+ """The name of the generator."""
47
+
48
+
49
+ class RandomGeneratorOptions(BaseGeneratorOptions):
50
+ """Options for Random generator."""
51
+
52
+ name: Literal["RandomGenerator"] = Field(
53
+ default="RandomGenerator", frozen=True, description="The name of the generator."
54
+ )
55
+ """The name of the generator."""
56
+
57
+
58
+ class RandomIntegerGeneratorOptions(BaseGeneratorOptions):
59
+ """Options for Random Integer generator."""
60
+
61
+ name: Literal["RandomIntegerGenerator"] = Field(
62
+ default="RandomIntegerGenerator", frozen=True, description="The name of the generator."
63
+ )
64
+ """The name of the generator."""
65
+
66
+
67
+ class RandomMixedIntegerGeneratorOptions(BaseGeneratorOptions):
68
+ """Options for Random Mixed Integer generator."""
69
+
70
+ name: Literal["RandomMixedIntegerGenerator"] = Field(
71
+ default="RandomMixedIntegerGenerator", frozen=True, description="The name of the generator."
72
+ )
73
+ """The name of the generator."""
74
+
75
+
76
+ class ArchiveGeneratorOptions(BaseModel):
77
+ """Options for Archive generator."""
78
+
79
+ model_config = {"arbitrary_types_allowed": True, "use_attribute_docstrings": True}
80
+
81
+ name: Literal["ArchiveGenerator"] = "ArchiveGenerator"
82
+ """The name of the generator."""
83
+ solutions: pl.DataFrame
84
+ """The initial solutions to populate the archive with."""
85
+ outputs: pl.DataFrame
86
+ """The corresponding outputs of the initial solutions."""
87
+
88
+ GeneratorOptions = (
89
+ LHSGeneratorOptions
90
+ | RandomBinaryGeneratorOptions
91
+ | RandomGeneratorOptions
92
+ | RandomIntegerGeneratorOptions
93
+ | RandomMixedIntegerGeneratorOptions
94
+ | ArchiveGeneratorOptions
95
+ )
96
+
97
+
98
+ def generator_constructor(
99
+ problem: Problem,
100
+ options: GeneratorOptions,
101
+ publisher: Publisher,
102
+ verbosity: int,
103
+ seed: int,
104
+ evaluator: EMOEvaluator,
105
+ ) -> BaseGenerator:
106
+ """Construct a generator based on the provided options.
107
+
108
+ Args:
109
+ problem (Problem): The optimization problem to solve.
110
+ options (GeneratorOptions): The options for the generator.
111
+ publisher (Publisher): The publisher for the generator.
112
+ verbosity (int): The verbosity level for the generator.
113
+ seed (int): The random seed for the generator.
114
+ evaluator (EMOEvaluator): The evaluator to use for evaluating solutions.
115
+
116
+ Returns:
117
+ BaseGenerator: The constructed generator.
118
+ """
119
+ generator_types = {
120
+ "LHSGenerator": LHSGenerator,
121
+ "RandomBinaryGenerator": RandomBinaryGenerator,
122
+ "RandomGenerator": RandomGenerator,
123
+ "RandomIntegerGenerator": RandomIntegerGenerator,
124
+ "RandomMixedIntegerGenerator": RandomMixedIntegerGenerator,
125
+ "ArchiveGenerator": ArchiveGenerator,
126
+ }
127
+ options: dict = options.model_dump()
128
+ name = options.pop("name")
129
+ return generator_types[name](
130
+ problem, **options, publisher=publisher, verbosity=verbosity, seed=seed, evaluator=evaluator
131
+ )
@@ -0,0 +1,260 @@
1
+ """JSON Schema for mutation operator options."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from desdeo.emo.operators.mutation import (
10
+ BaseMutation,
11
+ BinaryFlipMutation,
12
+ BoundedPolynomialMutation,
13
+ IntegerRandomMutation,
14
+ MixedIntegerRandomMutation,
15
+ MPTMutation,
16
+ NonUniformMutation,
17
+ PowerMutation,
18
+ SelfAdaptiveGaussianMutation,
19
+ )
20
+
21
+ if TYPE_CHECKING:
22
+ from desdeo.problem import Problem
23
+ from desdeo.tools.patterns import Publisher
24
+
25
+
26
+ class BoundedPolynomialMutationOptions(BaseModel):
27
+ """Options for Bounded Polynomial Mutation."""
28
+
29
+ model_config = {"use_attribute_docstrings": True}
30
+
31
+ name: Literal["BoundedPolynomialMutation"] = Field(
32
+ default="BoundedPolynomialMutation",
33
+ frozen=True,
34
+ )
35
+ """The name of the mutation operator."""
36
+ mutation_probability: float | None = Field(default=None, ge=0.0, le=1.0)
37
+ """
38
+ The probability of mutation. Defaults to None, which sets the mutation probability to
39
+ 1/<number of decision variables>.
40
+ """
41
+ distribution_index: float = Field(default=20.0, gt=0.0)
42
+ """The distribution index."""
43
+
44
+
45
+ class BinaryFlipMutationOptions(BaseModel):
46
+ """Options for Binary Flip Mutation."""
47
+
48
+ name: Literal["BinaryFlipMutation"] = Field(
49
+ default="BinaryFlipMutation", frozen=True, description="The name of the mutation operator."
50
+ )
51
+ """The name of the mutation operator."""
52
+ mutation_probability: float | None = Field(
53
+ default=None,
54
+ ge=0.0,
55
+ le=1.0,
56
+ description=(
57
+ "The probability of mutation. Defaults to None, which sets the "
58
+ "mutation probability to 1/<number of decision variables>."
59
+ ),
60
+ )
61
+ """
62
+ The probability of mutation. Defaults to None, which sets the mutation probability to
63
+ 1/<number of decision variables>.
64
+ """
65
+
66
+
67
+ class IntegerRandomMutationOptions(BaseModel):
68
+ """Options for Integer Random Mutation."""
69
+
70
+ name: Literal["IntegerRandomMutation"] = Field(
71
+ default="IntegerRandomMutation", frozen=True, description="The name of the mutation operator."
72
+ )
73
+ """The name of the mutation operator."""
74
+ mutation_probability: float | None = Field(
75
+ default=None,
76
+ ge=0.0,
77
+ le=1.0,
78
+ description=(
79
+ "The probability of mutation. Defaults to None, which sets the "
80
+ "mutation probability to 1/<number of decision variables>."
81
+ ),
82
+ )
83
+ """
84
+ The probability of mutation. Defaults to None, which sets the mutation probability to
85
+ 1/<number of decision variables>.
86
+ """
87
+
88
+
89
+ class MixedIntegerRandomMutationOptions(BaseModel):
90
+ """Options for Mixed Integer Random Mutation."""
91
+
92
+ name: Literal["MixedIntegerRandomMutation"] = Field(
93
+ default="MixedIntegerRandomMutation", frozen=True, description="The name of the mutation operator."
94
+ )
95
+ """The name of the mutation operator."""
96
+ mutation_probability: float | None = Field(
97
+ default=None,
98
+ ge=0.0,
99
+ le=1.0,
100
+ description=(
101
+ "The probability of mutation. Defaults to None, which sets the "
102
+ "mutation probability to 1/<number of decision variables>."
103
+ ),
104
+ )
105
+ """
106
+ The probability of mutation. Defaults to None, which sets the mutation probability to
107
+ 1/<number of decision variables>.
108
+ """
109
+
110
+
111
+ class MPTMutationOptions(BaseModel):
112
+ """Options for MPT Mutation."""
113
+
114
+ name: Literal["MPTMutation"] = Field(
115
+ default="MPTMutation", frozen=True, description="The name of the mutation operator."
116
+ )
117
+ """The name of the mutation operator."""
118
+ mutation_probability: float | None = Field(
119
+ default=None,
120
+ ge=0.0,
121
+ le=1.0,
122
+ description=(
123
+ "The probability of mutation. Defaults to None, which sets the "
124
+ "mutation probability to 1/<number of decision variables>."
125
+ ),
126
+ )
127
+ """
128
+ The probability of mutation. Defaults to None, which sets the mutation probability to
129
+ 1/<number of decision variables>.
130
+ """
131
+ mutation_exponent: float = Field(
132
+ default=2.0, ge=0.0, description="Controls strength of small mutation (larger means smaller mutations)."
133
+ )
134
+ """Controls strength of small mutation (larger means smaller mutations)."""
135
+
136
+
137
+ class NonUniformMutationOptions(BaseModel):
138
+ """Options for Non-Uniform Mutation."""
139
+
140
+ name: Literal["NonUniformMutation"] = Field(
141
+ default="NonUniformMutation", frozen=True, description="The name of the mutation operator."
142
+ )
143
+ """The name of the mutation operator."""
144
+ mutation_probability: float | None = Field(
145
+ default=None,
146
+ ge=0.0,
147
+ le=1.0,
148
+ description=(
149
+ "The probability of mutation. Defaults to None, which sets the "
150
+ "mutation probability to 1/<number of decision variables>."
151
+ ),
152
+ )
153
+ """
154
+ The probability of mutation. Defaults to None, which sets the mutation probability to
155
+ 1/<number of decision variables>.
156
+ """
157
+ max_generations: int = Field(
158
+ gt=0, description="Maximum number of generations in the evolutionary run. Used to scale mutation decay."
159
+ )
160
+ b: float = Field(
161
+ default=5.0,
162
+ ge=0.0,
163
+ description=(
164
+ "Non-uniform mutation decay parameter. Higher values cause"
165
+ "faster reduction in mutation strength over generations."
166
+ ),
167
+ )
168
+ """Non-uniform mutation decay parameter. Higher values cause
169
+ faster reduction in mutation strength over generations."""
170
+
171
+
172
+ class SelfAdaptiveGaussianMutationOptions(BaseModel):
173
+ """Options for Self-Adaptive Gaussian Mutation."""
174
+
175
+ name: Literal["SelfAdaptiveGaussianMutation"] = Field(
176
+ default="SelfAdaptiveGaussianMutation", frozen=True, description="The name of the mutation operator."
177
+ )
178
+ """The name of the mutation operator."""
179
+ mutation_probability: float | None = Field(
180
+ default=None,
181
+ ge=0.0,
182
+ le=1.0,
183
+ description=(
184
+ "The probability of mutation. Defaults to None, which sets the "
185
+ "mutation probability to 1/<number of decision variables>."
186
+ ),
187
+ )
188
+ """
189
+ The probability of mutation. Defaults to None, which sets the mutation probability to
190
+ 1/<number of decision variables>.
191
+ """
192
+
193
+
194
+ class PowerMutationOptions(BaseModel):
195
+ """Options for Power Mutation."""
196
+
197
+ name: Literal["PowerMutation"] = Field(
198
+ default="PowerMutation", frozen=True, description="The name of the mutation operator."
199
+ )
200
+ """The name of the mutation operator."""
201
+ mutation_probability: float | None = Field(
202
+ default=None,
203
+ ge=0.0,
204
+ le=1.0,
205
+ description=(
206
+ "The probability of mutation. Defaults to None, which sets the "
207
+ "mutation probability to 1/<number of decision variables>."
208
+ ),
209
+ )
210
+ """
211
+ The probability of mutation. Defaults to None, which sets the mutation probability to
212
+ 1/<number of decision variables>.
213
+ """
214
+ p: float = Field(
215
+ default=1.5, ge=0.0, description="Power distribution parameter. Controls the perturbation magnitude."
216
+ )
217
+ """Power distribution parameter. Controls the perturbation magnitude."""
218
+
219
+
220
+ MutationOptions = (
221
+ BoundedPolynomialMutationOptions
222
+ | BinaryFlipMutationOptions
223
+ | IntegerRandomMutationOptions
224
+ | MixedIntegerRandomMutationOptions
225
+ | MPTMutationOptions
226
+ | NonUniformMutationOptions
227
+ | SelfAdaptiveGaussianMutationOptions
228
+ | PowerMutationOptions
229
+ )
230
+ """All possible mutation operator options."""
231
+
232
+
233
+ def mutation_constructor(
234
+ problem: Problem, publisher: Publisher, seed: int, verbosity: int, options: MutationOptions
235
+ ) -> BaseMutation:
236
+ """Construct a mutation operator.
237
+
238
+ Args:
239
+ problem (Problem): The optimization problem to solve.
240
+ publisher (Publisher): The publisher for communication.
241
+ seed (int): The random seed for reproducibility.
242
+ verbosity (int): The verbosity level of the output.
243
+ options (MutationOptions): The options for the mutation operator.
244
+
245
+ Returns:
246
+ BaseCrossover: The constructed crossover operator.
247
+ """
248
+ mutation_types = {
249
+ "BoundedPolynomialMutation": BoundedPolynomialMutation,
250
+ "BinaryFlipMutation": BinaryFlipMutation,
251
+ "IntegerRandomMutation": IntegerRandomMutation,
252
+ "MixedIntegerRandomMutation": MixedIntegerRandomMutation,
253
+ "MPTMutation": MPTMutation,
254
+ "NonUniformMutation": NonUniformMutation,
255
+ "SelfAdaptiveGaussianMutation": SelfAdaptiveGaussianMutation,
256
+ "PowerMutation": PowerMutation,
257
+ }
258
+ options: dict = options.model_dump()
259
+ name = options.pop("name")
260
+ return mutation_types[name](problem=problem, publisher=publisher, seed=seed, verbosity=verbosity, **options)
@@ -0,0 +1,61 @@
1
+ """JSON Schema for repair operator options."""
2
+
3
+ from collections.abc import Callable
4
+ from typing import Literal
5
+
6
+ import polars as pl
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+ from desdeo.problem import Problem
10
+ from desdeo.tools.utils import repair
11
+
12
+
13
+ class NoRepairOptions(BaseModel):
14
+ """Options for No Repair."""
15
+
16
+ model_config = ConfigDict(use_attribute_docstrings=True)
17
+
18
+ name: Literal["NoRepair"] = Field(default="NoRepair")
19
+ """Do not apply any repair to the solutions."""
20
+
21
+
22
+ class ClipRepairOptions(BaseModel):
23
+ """Options for Clip Repair."""
24
+
25
+ model_config = ConfigDict(use_attribute_docstrings=True)
26
+
27
+ name: Literal["ClipRepair"] = Field(default="ClipRepair")
28
+ """Clip the solutions to be within the variable bounds."""
29
+
30
+ lower_bounds: dict[str, float] | None = None
31
+ """Lower bounds for the decision variables. If none, the lower bounds from the problem will be used."""
32
+ upper_bounds: dict[str, float] | None = None
33
+ """Upper bounds for the decision variables. If none, the upper bounds from the problem will be used."""
34
+
35
+
36
+ RepairOptions = ClipRepairOptions | NoRepairOptions
37
+
38
+
39
+ def repair_constructor(options: RepairOptions, problem: Problem) -> Callable[[pl.DataFrame], pl.DataFrame]:
40
+ """Get the repair operator based on the provided options.
41
+
42
+ Args:
43
+ options (RepairOptions): The repair options.
44
+ problem (Problem): The optimization problem to solve.
45
+
46
+ Returns:
47
+ callable: The repair operator function.
48
+ """
49
+ if options.name == "NoRepair":
50
+ return lambda x: x # No repair, return input as is
51
+ if options.name == "ClipRepair":
52
+ if options.lower_bounds is None:
53
+ lower_bounds = {var.symbol: var.lowerbound for var in problem.get_flattened_variables()}
54
+ else:
55
+ lower_bounds = options.lower_bounds
56
+ if options.upper_bounds is None:
57
+ upper_bounds = {var.symbol: var.upperbound for var in problem.get_flattened_variables()}
58
+ else:
59
+ upper_bounds = options.upper_bounds
60
+ return repair(lower_bounds=lower_bounds, upper_bounds=upper_bounds)
61
+ raise ValueError(f"Unknown repair operator: {options.name}")
@@ -0,0 +1,66 @@
1
+ """JSON Schema for scalar selector operator options."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from desdeo.emo.operators.scalar_selection import BaseScalarSelector, TournamentSelection
10
+
11
+ if TYPE_CHECKING:
12
+ from desdeo.tools.patterns import Publisher
13
+
14
+
15
+ class TournamentSelectionOptions(BaseModel):
16
+ """Options for tournament selection operator."""
17
+
18
+ name: Literal["TournamentSelection"] = Field(
19
+ default="TournamentSelection", frozen=True, description="The name of the scalar selection operator."
20
+ )
21
+ """The name of the scalar selection operator."""
22
+ tournament_size: int = Field(
23
+ default=2,
24
+ description="The number of individuals participating in the tournament.",
25
+ )
26
+ """The number of individuals participating in the tournament."""
27
+ winner_size: int = Field(
28
+ gt=1,
29
+ description="The number of winners to select (equivalent to population size).",
30
+ )
31
+ """The number of winners to select (equivalent to population size)."""
32
+
33
+
34
+ class RouletteWheelSelectionOptions(TournamentSelectionOptions):
35
+ """Options for roulette wheel selection operator."""
36
+
37
+ name: Literal["RouletteWheelSelection"] = Field(
38
+ default="RouletteWheelSelection", frozen=True, description="The name of the scalar selection operator."
39
+ )
40
+ """The name of the scalar selection operator."""
41
+
42
+
43
+ ScalarSelectionOptions = TournamentSelectionOptions | RouletteWheelSelectionOptions
44
+
45
+
46
+ def scalar_selector_constructor(
47
+ options: ScalarSelectionOptions, seed: int, publisher: Publisher, verbosity: int
48
+ ) -> BaseScalarSelector:
49
+ """Construct a scalar selector operator based on the provided options."""
50
+ if options.name == "TournamentSelection":
51
+ return TournamentSelection(
52
+ tournament_size=options.tournament_size,
53
+ winner_size=options.winner_size,
54
+ publisher=publisher,
55
+ verbosity=verbosity,
56
+ )
57
+ if options.name == "RouletteWheelSelection":
58
+ return TournamentSelection( # It implements both (and more)
59
+ winner_size=options.winner_size,
60
+ seed=seed, # By providing seed tournament selection behaves like roulette wheel
61
+ publisher=publisher,
62
+ verbosity=verbosity,
63
+ tournament_size=options.tournament_size,
64
+ )
65
+ else:
66
+ raise ValueError(f"Unknown scalar selection operator: {options.name}")
@@ -0,0 +1,127 @@
1
+ """JSON Schema for selection operator options."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Literal
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+ from desdeo.emo.operators.selection import (
10
+ BaseSelector,
11
+ IBEASelector,
12
+ NSGA2Selector,
13
+ NSGA3Selector,
14
+ ParameterAdaptationStrategy,
15
+ ReferenceVectorOptions,
16
+ RVEASelector,
17
+ )
18
+ from desdeo.tools.indicators_binary import self_epsilon, self_hv
19
+
20
+ if TYPE_CHECKING:
21
+ from desdeo.problem import Problem
22
+ from desdeo.tools.patterns import Publisher
23
+
24
+
25
+ class RVEASelectorOptions(BaseModel):
26
+ """Options for RVEA Selection."""
27
+
28
+ model_config = ConfigDict(use_enum_values=True)
29
+
30
+ name: Literal["RVEASelector"] = Field(
31
+ default="RVEASelector", frozen=True, description="The name of the selection operator."
32
+ )
33
+ """The name of the selection operator."""
34
+ reference_vector_options: ReferenceVectorOptions = Field(
35
+ default=ReferenceVectorOptions(), description="Options for the reference vectors."
36
+ )
37
+ """Options for the reference vectors."""
38
+ parameter_adaptation_strategy: ParameterAdaptationStrategy = Field(
39
+ default=ParameterAdaptationStrategy.GENERATION_BASED, description="The parameter adaptation strategy to use."
40
+ )
41
+ """Whether the angle penalized distance is adapted per generation or per function evaluation."""
42
+ alpha: float = Field(default=2.0, gt=0.0, description="The alpha parameter in the angle penalized distance.")
43
+ """The alpha parameter in the angle penalized distance."""
44
+
45
+
46
+ class NSGA3SelectorOptions(BaseModel):
47
+ """Options for NSGA-III Selection."""
48
+
49
+ name: Literal["NSGA3Selector"] = Field(
50
+ default="NSGA3Selector", frozen=True, description="The name of the selection operator."
51
+ )
52
+ """The name of the selection operator."""
53
+ reference_vector_options: ReferenceVectorOptions = Field(
54
+ default=ReferenceVectorOptions(), description="Options for the reference vectors."
55
+ )
56
+ """Options for the reference vectors."""
57
+ invert_reference_vectors: bool = Field(
58
+ default=False, description="Whether to invert the reference vectors (inverted triangle)."
59
+ )
60
+ """Whether to invert the reference vectors (inverted triangle)."""
61
+
62
+
63
+ class NSGA2SelectorOptions(BaseModel):
64
+ """Options for NSGA-II Selection."""
65
+
66
+ name: Literal["NSGA2Selector"] = Field(
67
+ default="NSGA2Selector", frozen=True, description="The name of the selection operator."
68
+ )
69
+ """The name of the selection operator."""
70
+ population_size: int = Field(gt=0, description="The population size.")
71
+ """The population size."""
72
+
73
+
74
+ class IBEASelectorOptions(BaseModel):
75
+ """Options for IBEA Selection."""
76
+
77
+ name: Literal["IBEASelector"] = Field(
78
+ default="IBEASelector", frozen=True, description="The name of the selection operator."
79
+ )
80
+ """The name of the selection operator."""
81
+ population_size: int = Field(gt=0, description="The population size.")
82
+ """The population size."""
83
+ kappa: float = Field(default=0.05, description="The kappa parameter for IBEA.")
84
+ """The kappa parameter for IBEA."""
85
+ binary_indicator: Literal["eps", "hv"] = Field(default="eps", description="The binary indicator for IBEA.")
86
+ """The binary indicator for IBEA."""
87
+
88
+
89
+ SelectorOptions = RVEASelectorOptions | NSGA2SelectorOptions | NSGA3SelectorOptions | IBEASelectorOptions
90
+
91
+
92
+ def selection_constructor(
93
+ problem: Problem, options: SelectorOptions, publisher: Publisher, verbosity: int, seed: int
94
+ ) -> BaseSelector:
95
+ """Construct a selection operator from given options.
96
+
97
+ Args:
98
+ problem (Problem): The optimization problem.
99
+ options (SelectorOptions): The options for the selection operator.
100
+ publisher (Publisher): The publisher to use for the operator.
101
+ verbosity (int): The verbosity level.
102
+ seed (int): The random seed.
103
+
104
+ Returns:
105
+ BaseSelector: The constructed selection operator.
106
+
107
+ Raises:
108
+ ValueError: If an unknown selection operator name is provided.
109
+ """
110
+ selection_types = {
111
+ "RVEASelector": RVEASelector,
112
+ "NSGA2Selector": NSGA2Selector,
113
+ "NSGA3Selector": NSGA3Selector,
114
+ "IBEASelector": IBEASelector,
115
+ }
116
+ options: dict = options.model_dump()
117
+ name = options.pop("name")
118
+ if name == "IBEASelector":
119
+ indi = options.pop("binary_indicator")
120
+ match indi:
121
+ case "eps":
122
+ options["binary_indicator"] = self_epsilon
123
+ case "hv":
124
+ options["binary_indicator"] = self_hv
125
+ case _:
126
+ raise ValueError(f"Unknown binary indicator: {indi}")
127
+ return selection_types[name](problem=problem, publisher=publisher, seed=seed, verbosity=verbosity, **options)