desdeo 1.2__py3-none-any.whl → 2.0.0__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.
- desdeo/__init__.py +8 -8
- desdeo/api/README.md +73 -0
- desdeo/api/__init__.py +15 -0
- desdeo/api/app.py +40 -0
- desdeo/api/config.py +69 -0
- desdeo/api/config.toml +53 -0
- desdeo/api/db.py +25 -0
- desdeo/api/db_init.py +79 -0
- desdeo/api/db_models.py +164 -0
- desdeo/api/malaga_db_init.py +27 -0
- desdeo/api/models/__init__.py +66 -0
- desdeo/api/models/archive.py +34 -0
- desdeo/api/models/preference.py +90 -0
- desdeo/api/models/problem.py +507 -0
- desdeo/api/models/reference_point_method.py +18 -0
- desdeo/api/models/session.py +46 -0
- desdeo/api/models/state.py +96 -0
- desdeo/api/models/user.py +51 -0
- desdeo/api/routers/_NAUTILUS.py +245 -0
- desdeo/api/routers/_NAUTILUS_navigator.py +233 -0
- desdeo/api/routers/_NIMBUS.py +762 -0
- desdeo/api/routers/__init__.py +5 -0
- desdeo/api/routers/problem.py +110 -0
- desdeo/api/routers/reference_point_method.py +117 -0
- desdeo/api/routers/session.py +76 -0
- desdeo/api/routers/test.py +16 -0
- desdeo/api/routers/user_authentication.py +366 -0
- desdeo/api/schema.py +94 -0
- desdeo/api/tests/__init__.py +0 -0
- desdeo/api/tests/conftest.py +59 -0
- desdeo/api/tests/test_models.py +701 -0
- desdeo/api/tests/test_routes.py +216 -0
- desdeo/api/utils/database.py +274 -0
- desdeo/api/utils/logger.py +29 -0
- desdeo/core.py +27 -0
- desdeo/emo/__init__.py +29 -0
- desdeo/emo/hooks/archivers.py +172 -0
- desdeo/emo/methods/EAs.py +418 -0
- desdeo/emo/methods/__init__.py +0 -0
- desdeo/emo/methods/bases.py +59 -0
- desdeo/emo/operators/__init__.py +1 -0
- desdeo/emo/operators/crossover.py +780 -0
- desdeo/emo/operators/evaluator.py +118 -0
- desdeo/emo/operators/generator.py +356 -0
- desdeo/emo/operators/mutation.py +1053 -0
- desdeo/emo/operators/selection.py +1036 -0
- desdeo/emo/operators/termination.py +178 -0
- desdeo/explanations/__init__.py +6 -0
- desdeo/explanations/explainer.py +100 -0
- desdeo/explanations/utils.py +90 -0
- desdeo/mcdm/__init__.py +19 -0
- desdeo/mcdm/nautili.py +345 -0
- desdeo/mcdm/nautilus.py +477 -0
- desdeo/mcdm/nautilus_navigator.py +655 -0
- desdeo/mcdm/nimbus.py +417 -0
- desdeo/mcdm/pareto_navigator.py +269 -0
- desdeo/mcdm/reference_point_method.py +116 -0
- desdeo/problem/__init__.py +79 -0
- desdeo/problem/evaluator.py +561 -0
- desdeo/problem/gurobipy_evaluator.py +562 -0
- desdeo/problem/infix_parser.py +341 -0
- desdeo/problem/json_parser.py +944 -0
- desdeo/problem/pyomo_evaluator.py +468 -0
- desdeo/problem/schema.py +1808 -0
- desdeo/problem/simulator_evaluator.py +298 -0
- desdeo/problem/sympy_evaluator.py +244 -0
- desdeo/problem/testproblems/__init__.py +73 -0
- desdeo/problem/testproblems/binh_and_korn_problem.py +88 -0
- desdeo/problem/testproblems/dtlz2_problem.py +102 -0
- desdeo/problem/testproblems/forest_problem.py +275 -0
- desdeo/problem/testproblems/knapsack_problem.py +163 -0
- desdeo/problem/testproblems/mcwb_problem.py +831 -0
- desdeo/problem/testproblems/mixed_variable_dimenrions_problem.py +83 -0
- desdeo/problem/testproblems/momip_problem.py +172 -0
- desdeo/problem/testproblems/nimbus_problem.py +143 -0
- desdeo/problem/testproblems/pareto_navigator_problem.py +89 -0
- desdeo/problem/testproblems/re_problem.py +492 -0
- desdeo/problem/testproblems/river_pollution_problem.py +434 -0
- desdeo/problem/testproblems/rocket_injector_design_problem.py +140 -0
- desdeo/problem/testproblems/simple_problem.py +351 -0
- desdeo/problem/testproblems/simulator_problem.py +92 -0
- desdeo/problem/testproblems/spanish_sustainability_problem.py +945 -0
- desdeo/problem/testproblems/zdt_problem.py +271 -0
- desdeo/problem/utils.py +245 -0
- desdeo/tools/GenerateReferencePoints.py +181 -0
- desdeo/tools/__init__.py +102 -0
- desdeo/tools/generics.py +145 -0
- desdeo/tools/gurobipy_solver_interfaces.py +258 -0
- desdeo/tools/indicators_binary.py +11 -0
- desdeo/tools/indicators_unary.py +375 -0
- desdeo/tools/interaction_schema.py +38 -0
- desdeo/tools/intersection.py +54 -0
- desdeo/tools/iterative_pareto_representer.py +99 -0
- desdeo/tools/message.py +234 -0
- desdeo/tools/ng_solver_interfaces.py +199 -0
- desdeo/tools/non_dominated_sorting.py +133 -0
- desdeo/tools/patterns.py +281 -0
- desdeo/tools/proximal_solver.py +99 -0
- desdeo/tools/pyomo_solver_interfaces.py +464 -0
- desdeo/tools/reference_vectors.py +462 -0
- desdeo/tools/scalarization.py +3138 -0
- desdeo/tools/scipy_solver_interfaces.py +454 -0
- desdeo/tools/score_bands.py +464 -0
- desdeo/tools/utils.py +320 -0
- desdeo/utopia_stuff/__init__.py +0 -0
- desdeo/utopia_stuff/data/1.json +15 -0
- desdeo/utopia_stuff/data/2.json +13 -0
- desdeo/utopia_stuff/data/3.json +15 -0
- desdeo/utopia_stuff/data/4.json +17 -0
- desdeo/utopia_stuff/data/5.json +15 -0
- desdeo/utopia_stuff/from_json.py +40 -0
- desdeo/utopia_stuff/reinit_user.py +38 -0
- desdeo/utopia_stuff/utopia_db_init.py +212 -0
- desdeo/utopia_stuff/utopia_problem.py +403 -0
- desdeo/utopia_stuff/utopia_problem_old.py +415 -0
- desdeo/utopia_stuff/utopia_reference_solutions.py +79 -0
- desdeo-2.0.0.dist-info/LICENSE +21 -0
- desdeo-2.0.0.dist-info/METADATA +168 -0
- desdeo-2.0.0.dist-info/RECORD +120 -0
- {desdeo-1.2.dist-info → desdeo-2.0.0.dist-info}/WHEEL +1 -1
- desdeo-1.2.dist-info/METADATA +0 -16
- desdeo-1.2.dist-info/RECORD +0 -4
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
"""Evolutionary operators for recombination.
|
|
2
|
+
|
|
3
|
+
Various evolutionary operators for recombination
|
|
4
|
+
in multiobjective optimization are defined here.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import copy
|
|
8
|
+
from abc import abstractmethod
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
from random import shuffle
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import polars as pl
|
|
14
|
+
|
|
15
|
+
from desdeo.problem import Problem, VariableDomainTypeEnum
|
|
16
|
+
from desdeo.tools.message import (
|
|
17
|
+
CrossoverMessageTopics,
|
|
18
|
+
FloatMessage,
|
|
19
|
+
Message,
|
|
20
|
+
PolarsDataFrameMessage,
|
|
21
|
+
)
|
|
22
|
+
from desdeo.tools.patterns import Subscriber
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BaseCrossover(Subscriber):
|
|
26
|
+
"""A base class for crossover operators."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, problem: Problem, **kwargs):
|
|
29
|
+
"""Initialize a crossover operator."""
|
|
30
|
+
super().__init__(**kwargs)
|
|
31
|
+
self.problem = problem
|
|
32
|
+
self.variable_symbols = [var.symbol for var in problem.get_flattened_variables()]
|
|
33
|
+
self.lower_bounds = [var.lowerbound for var in problem.get_flattened_variables()]
|
|
34
|
+
self.upper_bounds = [var.upperbound for var in problem.get_flattened_variables()]
|
|
35
|
+
|
|
36
|
+
self.variable_types = [var.variable_type for var in problem.get_flattened_variables()]
|
|
37
|
+
self.variable_combination: VariableDomainTypeEnum = problem.variable_domain
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def do(self, *, population: pl.DataFrame, to_mate: list[int] | None = None) -> pl.DataFrame:
|
|
41
|
+
"""Perform the crossover operation.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
population (pl.DataFrame): the population to perform the crossover with. The DataFrame
|
|
45
|
+
contains the decision vectors, the target vectors, and the constraint vectors.
|
|
46
|
+
to_mate (list[int] | None): the indices of the population members that should
|
|
47
|
+
participate in the crossover. If `None`, the whole population is subject
|
|
48
|
+
to the crossover.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
pl.DataFrame: the offspring resulting from the crossover.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class SimulatedBinaryCrossover(BaseCrossover):
|
|
56
|
+
"""A class for creating a simulated binary crossover operator.
|
|
57
|
+
|
|
58
|
+
Reference:
|
|
59
|
+
Kalyanmoy Deb and Ram Bhushan Agrawal. 1995. Simulated binary crossover for continuous search space.
|
|
60
|
+
Complex Systems 9, 2 (1995), 115-148.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def provided_topics(self) -> dict[str, Sequence[CrossoverMessageTopics]]:
|
|
65
|
+
"""The message topics provided by the crossover operator."""
|
|
66
|
+
return {
|
|
67
|
+
0: [],
|
|
68
|
+
1: [CrossoverMessageTopics.XOVER_PROBABILITY, CrossoverMessageTopics.XOVER_DISTRIBUTION],
|
|
69
|
+
2: [
|
|
70
|
+
CrossoverMessageTopics.XOVER_PROBABILITY,
|
|
71
|
+
CrossoverMessageTopics.XOVER_DISTRIBUTION,
|
|
72
|
+
CrossoverMessageTopics.PARENTS,
|
|
73
|
+
CrossoverMessageTopics.OFFSPRINGS,
|
|
74
|
+
],
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def interested_topics(self):
|
|
79
|
+
"""The message topics the crossover operator is interested in."""
|
|
80
|
+
return []
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self, *, problem: Problem, seed: int, xover_probability: float = 1.0, xover_distribution: float = 30, **kwargs
|
|
84
|
+
):
|
|
85
|
+
"""Initialize a simulated binary crossover operator.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
problem (Problem): the problem object.
|
|
89
|
+
seed (int): the seed for the random number generator.
|
|
90
|
+
xover_probability (float, optional): the crossover probability
|
|
91
|
+
parameter. Ranges between 0 and 1.0. Defaults to 1.0.
|
|
92
|
+
xover_distribution (float, optional): the crossover distribution
|
|
93
|
+
parameter. Must be positive. Defaults to 30.
|
|
94
|
+
kwargs: Additional keyword arguments. These are passed to the Subscriber class. At the very least, the
|
|
95
|
+
publisher must be passed. See the Subscriber class for more information.
|
|
96
|
+
"""
|
|
97
|
+
# Subscribes to no topics, so no need to stroe/pass the topics to the super class.
|
|
98
|
+
super().__init__(problem, **kwargs)
|
|
99
|
+
self.problem = problem
|
|
100
|
+
|
|
101
|
+
if not 0 <= xover_probability <= 1:
|
|
102
|
+
raise ValueError("Crossover probability must be between 0 and 1.")
|
|
103
|
+
if xover_distribution <= 0:
|
|
104
|
+
raise ValueError("Crossover distribution must be positive.")
|
|
105
|
+
self.xover_probability = xover_probability
|
|
106
|
+
self.xover_distribution = xover_distribution
|
|
107
|
+
self.parent_population: pl.DataFrame
|
|
108
|
+
self.offspring_population: pl.DataFrame
|
|
109
|
+
self.rng = np.random.default_rng(seed)
|
|
110
|
+
self.seed = seed
|
|
111
|
+
|
|
112
|
+
def do(
|
|
113
|
+
self,
|
|
114
|
+
*,
|
|
115
|
+
population: pl.DataFrame,
|
|
116
|
+
to_mate: list[int] | None = None,
|
|
117
|
+
) -> pl.DataFrame:
|
|
118
|
+
"""Perform the simulated binary crossover operation.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
population (pl.DataFrame): the population to perform the crossover with. The DataFrame
|
|
122
|
+
contains the decision vectors, the target vectors, and the constraint vectors.
|
|
123
|
+
to_mate (list[int] | None): the indices of the population members that should
|
|
124
|
+
participate in the crossover. If `None`, the whole population is subject
|
|
125
|
+
to the crossover.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
pl.DataFrame: the offspring resulting from the crossover.
|
|
129
|
+
"""
|
|
130
|
+
self.parent_population = population
|
|
131
|
+
pop_size = self.parent_population.shape[0]
|
|
132
|
+
num_var = len(self.variable_symbols)
|
|
133
|
+
|
|
134
|
+
parent_decvars = self.parent_population[self.variable_symbols].to_numpy()
|
|
135
|
+
|
|
136
|
+
if to_mate is None:
|
|
137
|
+
shuffled_ids = list(range(pop_size))
|
|
138
|
+
shuffle(shuffled_ids)
|
|
139
|
+
else:
|
|
140
|
+
shuffled_ids = to_mate
|
|
141
|
+
mating_pop = parent_decvars[shuffled_ids]
|
|
142
|
+
mate_size = len(shuffled_ids)
|
|
143
|
+
|
|
144
|
+
if len(shuffled_ids) % 2 == 1:
|
|
145
|
+
mating_pop = np.vstack((mating_pop, mating_pop[0]))
|
|
146
|
+
mate_size += 1
|
|
147
|
+
|
|
148
|
+
offspring = np.zeros_like(mating_pop)
|
|
149
|
+
|
|
150
|
+
HALF = 0.5 # NOQA: N806
|
|
151
|
+
# TODO(@light-weaver): Extract into a numba jitted function.
|
|
152
|
+
for i in range(0, mate_size, 2):
|
|
153
|
+
beta = np.zeros(num_var)
|
|
154
|
+
miu = self.rng.random(num_var)
|
|
155
|
+
beta[miu <= HALF] = (2 * miu[miu <= HALF]) ** (1 / (self.xover_distribution + 1))
|
|
156
|
+
beta[miu > HALF] = (2 - 2 * miu[miu > HALF]) ** (-1 / (self.xover_distribution + 1))
|
|
157
|
+
beta = beta * ((-1) ** self.rng.integers(low=0, high=2, size=num_var))
|
|
158
|
+
beta[self.rng.random(num_var) > self.xover_probability] = 1
|
|
159
|
+
avg = (mating_pop[i] + mating_pop[i + 1]) / 2
|
|
160
|
+
diff = (mating_pop[i] - mating_pop[i + 1]) / 2
|
|
161
|
+
offspring[i] = avg + beta * diff
|
|
162
|
+
offspring[i + 1] = avg - beta * diff
|
|
163
|
+
|
|
164
|
+
self.offspring_population = pl.from_numpy(offspring, schema=self.variable_symbols)
|
|
165
|
+
self.notify()
|
|
166
|
+
|
|
167
|
+
return self.offspring_population
|
|
168
|
+
|
|
169
|
+
def update(self, *_, **__):
|
|
170
|
+
"""Do nothing. This is just the basic SBX operator."""
|
|
171
|
+
|
|
172
|
+
def state(self) -> Sequence[Message]:
|
|
173
|
+
"""Return the state of the crossover operator."""
|
|
174
|
+
if self.parent_population is None or self.offspring_population is None:
|
|
175
|
+
return []
|
|
176
|
+
if self.verbosity == 0:
|
|
177
|
+
return []
|
|
178
|
+
if self.verbosity == 1:
|
|
179
|
+
return [
|
|
180
|
+
FloatMessage(
|
|
181
|
+
topic=CrossoverMessageTopics.XOVER_PROBABILITY,
|
|
182
|
+
source="SimulatedBinaryCrossover",
|
|
183
|
+
value=self.xover_probability,
|
|
184
|
+
),
|
|
185
|
+
FloatMessage(
|
|
186
|
+
topic=CrossoverMessageTopics.XOVER_DISTRIBUTION,
|
|
187
|
+
source="SimulatedBinaryCrossover",
|
|
188
|
+
value=self.xover_distribution,
|
|
189
|
+
),
|
|
190
|
+
]
|
|
191
|
+
# verbosity == 2 or higher
|
|
192
|
+
return [
|
|
193
|
+
FloatMessage(
|
|
194
|
+
topic=CrossoverMessageTopics.XOVER_PROBABILITY,
|
|
195
|
+
source="SimulatedBinaryCrossover",
|
|
196
|
+
value=self.xover_probability,
|
|
197
|
+
),
|
|
198
|
+
FloatMessage(
|
|
199
|
+
topic=CrossoverMessageTopics.XOVER_DISTRIBUTION,
|
|
200
|
+
source="SimulatedBinaryCrossover",
|
|
201
|
+
value=self.xover_distribution,
|
|
202
|
+
),
|
|
203
|
+
PolarsDataFrameMessage(
|
|
204
|
+
topic=CrossoverMessageTopics.PARENTS,
|
|
205
|
+
source="SimulatedBinaryCrossover",
|
|
206
|
+
value=self.parent_population,
|
|
207
|
+
),
|
|
208
|
+
PolarsDataFrameMessage(
|
|
209
|
+
topic=CrossoverMessageTopics.OFFSPRINGS,
|
|
210
|
+
source="SimulatedBinaryCrossover",
|
|
211
|
+
value=self.offspring_population,
|
|
212
|
+
),
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class SinglePointBinaryCrossover(BaseCrossover):
|
|
217
|
+
"""A class that defines the single point binary crossover operation."""
|
|
218
|
+
|
|
219
|
+
def __init__(self, *, problem: Problem, seed: int, **kwargs):
|
|
220
|
+
"""Initialize the single point binary crossover operator.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
problem (Problem): the problem object.
|
|
224
|
+
seed (int): the seed used in the random number generator for choosing the crossover point.
|
|
225
|
+
kwargs: Additional keyword arguments. These are passed to the Subscriber class. At the very least, the
|
|
226
|
+
publisher must be passed. See the Subscriber class for more information.
|
|
227
|
+
"""
|
|
228
|
+
super().__init__(problem, **kwargs)
|
|
229
|
+
self.seed = seed
|
|
230
|
+
|
|
231
|
+
self.parent_population: pl.DataFrame
|
|
232
|
+
self.offspring_population: pl.DataFrame
|
|
233
|
+
self.rng = np.random.default_rng(seed)
|
|
234
|
+
self.seed = seed
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def provided_topics(self) -> dict[str, Sequence[CrossoverMessageTopics]]:
|
|
238
|
+
"""The message topics provided by the single point binary crossover operator."""
|
|
239
|
+
return {
|
|
240
|
+
0: [],
|
|
241
|
+
1: [],
|
|
242
|
+
2: [
|
|
243
|
+
CrossoverMessageTopics.PARENTS,
|
|
244
|
+
CrossoverMessageTopics.OFFSPRINGS,
|
|
245
|
+
],
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def interested_topics(self):
|
|
250
|
+
"""The message topics the single point binary crossover operator is interested in."""
|
|
251
|
+
return []
|
|
252
|
+
|
|
253
|
+
def do(
|
|
254
|
+
self,
|
|
255
|
+
*,
|
|
256
|
+
population: pl.DataFrame,
|
|
257
|
+
to_mate: list[int] | None = None,
|
|
258
|
+
) -> pl.DataFrame:
|
|
259
|
+
"""Perform single point binary crossover.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
population (pl.DataFrame): the population to perform the crossover with.
|
|
263
|
+
to_mate (list[int] | None, optional): indices. Defaults to None.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
pl.DataFrame: the offspring from the crossover.
|
|
267
|
+
"""
|
|
268
|
+
self.parent_population = population
|
|
269
|
+
pop_size = self.parent_population.shape[0]
|
|
270
|
+
num_var = len(self.variable_symbols)
|
|
271
|
+
|
|
272
|
+
parent_decision_vars = self.parent_population[self.variable_symbols].to_numpy().astype(np.bool)
|
|
273
|
+
|
|
274
|
+
if to_mate is None:
|
|
275
|
+
shuffled_ids = list(range(pop_size))
|
|
276
|
+
shuffle(shuffled_ids)
|
|
277
|
+
else:
|
|
278
|
+
shuffled_ids = copy.copy(to_mate)
|
|
279
|
+
|
|
280
|
+
mating_pop = parent_decision_vars[shuffled_ids]
|
|
281
|
+
mating_pop_size = len(shuffled_ids)
|
|
282
|
+
original_mating_pop_size = mating_pop_size
|
|
283
|
+
|
|
284
|
+
if mating_pop_size % 2 != 0:
|
|
285
|
+
# if the number of member to mate is of uneven size, copy the first member to the tail
|
|
286
|
+
mating_pop = np.vstack((mating_pop, mating_pop[0]))
|
|
287
|
+
mating_pop_size += 1
|
|
288
|
+
shuffled_ids.append(shuffled_ids[0])
|
|
289
|
+
|
|
290
|
+
# split the population into parents, one with members with even numbered indices, the
|
|
291
|
+
# other with uneven numbered indices
|
|
292
|
+
parents1 = mating_pop[[shuffled_ids[i] for i in range(0, mating_pop_size, 2)]]
|
|
293
|
+
parents2 = mating_pop[[shuffled_ids[i] for i in range(1, mating_pop_size, 2)]]
|
|
294
|
+
|
|
295
|
+
cross_over_points = self.rng.integers(1, num_var - 1, mating_pop_size // 2)
|
|
296
|
+
|
|
297
|
+
# create a mask where, on each row, the element is 1 before the crossover point,
|
|
298
|
+
# and zero after it
|
|
299
|
+
cross_over_mask = np.zeros_like(parents1, dtype=np.bool)
|
|
300
|
+
cross_over_mask[np.arange(cross_over_mask.shape[1]) < cross_over_points[:, None]] = 1
|
|
301
|
+
|
|
302
|
+
# pick genes from the first parents before the crossover point
|
|
303
|
+
# pick genes from the second parents after, and including, the crossover point
|
|
304
|
+
offspring1_first = cross_over_mask & parents1
|
|
305
|
+
offspring1_second = (~cross_over_mask) & parents2
|
|
306
|
+
|
|
307
|
+
# combine into a first half of the whole offspring population
|
|
308
|
+
offspring1 = offspring1_first | offspring1_second
|
|
309
|
+
|
|
310
|
+
# pick genes from the first parents after, and including, the crossover point
|
|
311
|
+
# pick genes from the second parents before the crossover point
|
|
312
|
+
offspring2_first = (~cross_over_mask) & parents1
|
|
313
|
+
offspring2_second = cross_over_mask & parents2
|
|
314
|
+
|
|
315
|
+
# combine into the second half of the whole offspring population
|
|
316
|
+
offspring2 = offspring2_first | offspring2_second
|
|
317
|
+
|
|
318
|
+
# combine the two offspring populations into one, drop the last member if the number of
|
|
319
|
+
# indices (to_mate) is uneven
|
|
320
|
+
self.offspring_population = pl.from_numpy(
|
|
321
|
+
np.vstack((offspring1, offspring2))[
|
|
322
|
+
: (original_mating_pop_size if original_mating_pop_size % 2 == 0 else -1)
|
|
323
|
+
],
|
|
324
|
+
schema=self.variable_symbols,
|
|
325
|
+
).select(pl.all().cast(pl.Float64))
|
|
326
|
+
self.notify()
|
|
327
|
+
|
|
328
|
+
return self.offspring_population
|
|
329
|
+
|
|
330
|
+
def update(self, *_, **__):
|
|
331
|
+
"""Do nothing. This is just the basic single point binary crossover operator."""
|
|
332
|
+
|
|
333
|
+
def state(self) -> Sequence[Message]:
|
|
334
|
+
"""Return the state of the single ponit binary crossover operator."""
|
|
335
|
+
if self.parent_population is None or self.offspring_population is None:
|
|
336
|
+
return []
|
|
337
|
+
if self.verbosity == 0:
|
|
338
|
+
return []
|
|
339
|
+
if self.verbosity == 1:
|
|
340
|
+
return []
|
|
341
|
+
# verbosity == 2 or higher
|
|
342
|
+
return [
|
|
343
|
+
PolarsDataFrameMessage(
|
|
344
|
+
topic=CrossoverMessageTopics.PARENTS,
|
|
345
|
+
source="SimulatedBinaryCrossover",
|
|
346
|
+
value=self.parent_population,
|
|
347
|
+
),
|
|
348
|
+
PolarsDataFrameMessage(
|
|
349
|
+
topic=CrossoverMessageTopics.OFFSPRINGS,
|
|
350
|
+
source="SimulatedBinaryCrossover",
|
|
351
|
+
value=self.offspring_population,
|
|
352
|
+
),
|
|
353
|
+
]
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class UniformIntegerCrossover(BaseCrossover):
|
|
357
|
+
"""A class that defines the uniform integer crossover operation."""
|
|
358
|
+
|
|
359
|
+
def __init__(self, *, problem: Problem, seed: int, **kwargs):
|
|
360
|
+
"""Initialize the uniform integer crossover operator.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
problem (Problem): the problem object.
|
|
364
|
+
seed (int): the seed used in the random number generator for choosing the crossover point.
|
|
365
|
+
kwargs: Additional keyword arguments. These are passed to the Subscriber class. At the very least, the
|
|
366
|
+
publisher must be passed. See the Subscriber class for more information.
|
|
367
|
+
"""
|
|
368
|
+
super().__init__(problem, **kwargs)
|
|
369
|
+
self.seed = seed
|
|
370
|
+
|
|
371
|
+
self.parent_population: pl.DataFrame
|
|
372
|
+
self.offspring_population: pl.DataFrame
|
|
373
|
+
self.rng = np.random.default_rng(seed)
|
|
374
|
+
self.seed = seed
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def provided_topics(self) -> dict[str, Sequence[CrossoverMessageTopics]]:
|
|
378
|
+
"""The message topics provided by the single point binary crossover operator."""
|
|
379
|
+
return {
|
|
380
|
+
0: [],
|
|
381
|
+
1: [],
|
|
382
|
+
2: [
|
|
383
|
+
CrossoverMessageTopics.PARENTS,
|
|
384
|
+
CrossoverMessageTopics.OFFSPRINGS,
|
|
385
|
+
],
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
@property
|
|
389
|
+
def interested_topics(self):
|
|
390
|
+
"""The message topics the single point binary crossover operator is interested in."""
|
|
391
|
+
return []
|
|
392
|
+
|
|
393
|
+
def do(
|
|
394
|
+
self,
|
|
395
|
+
*,
|
|
396
|
+
population: pl.DataFrame,
|
|
397
|
+
to_mate: list[int] | None = None,
|
|
398
|
+
) -> pl.DataFrame:
|
|
399
|
+
"""Perform single point binary crossover.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
population (pl.DataFrame): the population to perform the crossover with.
|
|
403
|
+
to_mate (list[int] | None, optional): indices. Defaults to None.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
pl.DataFrame: the offspring from the crossover.
|
|
407
|
+
"""
|
|
408
|
+
self.parent_population = population
|
|
409
|
+
pop_size = self.parent_population.shape[0]
|
|
410
|
+
num_var = len(self.variable_symbols)
|
|
411
|
+
|
|
412
|
+
parent_decision_vars = self.parent_population[self.variable_symbols].to_numpy().astype(int)
|
|
413
|
+
|
|
414
|
+
if to_mate is None:
|
|
415
|
+
shuffled_ids = list(range(pop_size))
|
|
416
|
+
shuffle(shuffled_ids)
|
|
417
|
+
else:
|
|
418
|
+
shuffled_ids = copy.copy(to_mate)
|
|
419
|
+
|
|
420
|
+
mating_pop = parent_decision_vars[shuffled_ids]
|
|
421
|
+
mating_pop_size = len(shuffled_ids)
|
|
422
|
+
original_mating_pop_size = mating_pop_size
|
|
423
|
+
|
|
424
|
+
if mating_pop_size % 2 != 0:
|
|
425
|
+
# if the number of member to mate is of uneven size, copy the first member to the tail
|
|
426
|
+
mating_pop = np.vstack((mating_pop, mating_pop[0]))
|
|
427
|
+
mating_pop_size += 1
|
|
428
|
+
shuffled_ids.append(shuffled_ids[0])
|
|
429
|
+
|
|
430
|
+
# split the population into parents, one with members with even numbered indices, the
|
|
431
|
+
# other with uneven numbered indices
|
|
432
|
+
parents1 = mating_pop[[shuffled_ids[i] for i in range(0, mating_pop_size, 2)]]
|
|
433
|
+
parents2 = mating_pop[[shuffled_ids[i] for i in range(1, mating_pop_size, 2)]]
|
|
434
|
+
|
|
435
|
+
mask = self.rng.choice([True, False], size=num_var)
|
|
436
|
+
|
|
437
|
+
offspring1 = np.where(mask, parents1, parents2) # True, pick from parent1, False, pick from parent2
|
|
438
|
+
offspring2 = np.where(mask, parents2, parents1) # True, pick from parent2, False, pick from parent1
|
|
439
|
+
|
|
440
|
+
# combine the two offspring populations into one, drop the last member if the number of
|
|
441
|
+
# indices (to_mate) is uneven
|
|
442
|
+
self.offspring_population = pl.from_numpy(
|
|
443
|
+
np.vstack((offspring1, offspring2))[
|
|
444
|
+
: (original_mating_pop_size if original_mating_pop_size % 2 == 0 else -1)
|
|
445
|
+
],
|
|
446
|
+
schema=self.variable_symbols,
|
|
447
|
+
).select(pl.all().cast(pl.Float64))
|
|
448
|
+
|
|
449
|
+
self.notify()
|
|
450
|
+
|
|
451
|
+
return self.offspring_population
|
|
452
|
+
|
|
453
|
+
def update(self, *_, **__):
|
|
454
|
+
"""Do nothing. This is just the basic single point binary crossover operator."""
|
|
455
|
+
|
|
456
|
+
def state(self) -> Sequence[Message]:
|
|
457
|
+
"""Return the state of the single ponit binary crossover operator."""
|
|
458
|
+
if self.parent_population is None or self.offspring_population is None:
|
|
459
|
+
return []
|
|
460
|
+
if self.verbosity == 0:
|
|
461
|
+
return []
|
|
462
|
+
if self.verbosity == 1:
|
|
463
|
+
return []
|
|
464
|
+
# verbosity == 2 or higher
|
|
465
|
+
return [
|
|
466
|
+
PolarsDataFrameMessage(
|
|
467
|
+
topic=CrossoverMessageTopics.PARENTS,
|
|
468
|
+
source="SimulatedBinaryCrossover",
|
|
469
|
+
value=self.parent_population,
|
|
470
|
+
),
|
|
471
|
+
PolarsDataFrameMessage(
|
|
472
|
+
topic=CrossoverMessageTopics.OFFSPRINGS,
|
|
473
|
+
source="SimulatedBinaryCrossover",
|
|
474
|
+
value=self.offspring_population,
|
|
475
|
+
),
|
|
476
|
+
]
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
class UniformMixedIntegerCrossover(BaseCrossover):
|
|
480
|
+
"""A class that defines the uniform mixed-integer crossover operation.
|
|
481
|
+
|
|
482
|
+
TODO: This is virtually identical to `UniformIntegerCrossover`. The only
|
|
483
|
+
difference is that the `parent_decision_vars` in `do` are not casted to
|
|
484
|
+
`int`. This is not an ideal way to implement crossover for mixed-integer
|
|
485
|
+
stuff...
|
|
486
|
+
"""
|
|
487
|
+
|
|
488
|
+
def __init__(self, *, problem: Problem, seed: int, **kwargs):
|
|
489
|
+
"""Initialize the uniform integer crossover operator.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
problem (Problem): the problem object.
|
|
493
|
+
seed (int): the seed used in the random number generator for choosing the crossover point.
|
|
494
|
+
kwargs: Additional keyword arguments. These are passed to the Subscriber class. At the very least, the
|
|
495
|
+
publisher must be passed. See the Subscriber class for more information.
|
|
496
|
+
"""
|
|
497
|
+
super().__init__(problem, **kwargs)
|
|
498
|
+
self.seed = seed
|
|
499
|
+
|
|
500
|
+
self.parent_population: pl.DataFrame
|
|
501
|
+
self.offspring_population: pl.DataFrame
|
|
502
|
+
self.rng = np.random.default_rng(seed)
|
|
503
|
+
self.seed = seed
|
|
504
|
+
|
|
505
|
+
@property
|
|
506
|
+
def provided_topics(self) -> dict[str, Sequence[CrossoverMessageTopics]]:
|
|
507
|
+
"""The message topics provided by the single point binary crossover operator."""
|
|
508
|
+
return {
|
|
509
|
+
0: [],
|
|
510
|
+
1: [],
|
|
511
|
+
2: [
|
|
512
|
+
CrossoverMessageTopics.PARENTS,
|
|
513
|
+
CrossoverMessageTopics.OFFSPRINGS,
|
|
514
|
+
],
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
@property
|
|
518
|
+
def interested_topics(self):
|
|
519
|
+
"""The message topics the single point binary crossover operator is interested in."""
|
|
520
|
+
return []
|
|
521
|
+
|
|
522
|
+
def do(
|
|
523
|
+
self,
|
|
524
|
+
*,
|
|
525
|
+
population: pl.DataFrame,
|
|
526
|
+
to_mate: list[int] | None = None,
|
|
527
|
+
) -> pl.DataFrame:
|
|
528
|
+
"""Perform single point binary crossover.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
population (pl.DataFrame): the population to perform the crossover with.
|
|
532
|
+
to_mate (list[int] | None, optional): indices. Defaults to None.
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
pl.DataFrame: the offspring from the crossover.
|
|
536
|
+
"""
|
|
537
|
+
self.parent_population = population
|
|
538
|
+
pop_size = self.parent_population.shape[0]
|
|
539
|
+
num_var = len(self.variable_symbols)
|
|
540
|
+
|
|
541
|
+
parent_decision_vars = self.parent_population[self.variable_symbols].to_numpy().astype(float)
|
|
542
|
+
|
|
543
|
+
if to_mate is None:
|
|
544
|
+
shuffled_ids = list(range(pop_size))
|
|
545
|
+
shuffle(shuffled_ids)
|
|
546
|
+
else:
|
|
547
|
+
shuffled_ids = copy.copy(to_mate)
|
|
548
|
+
|
|
549
|
+
mating_pop = parent_decision_vars[shuffled_ids]
|
|
550
|
+
mating_pop_size = len(shuffled_ids)
|
|
551
|
+
original_mating_pop_size = mating_pop_size
|
|
552
|
+
|
|
553
|
+
if mating_pop_size % 2 != 0:
|
|
554
|
+
# if the number of member to mate is of uneven size, copy the first member to the tail
|
|
555
|
+
mating_pop = np.vstack((mating_pop, mating_pop[0]))
|
|
556
|
+
mating_pop_size += 1
|
|
557
|
+
shuffled_ids.append(shuffled_ids[0])
|
|
558
|
+
|
|
559
|
+
# split the population into parents, one with members with even numbered indices, the
|
|
560
|
+
# other with uneven numbered indices
|
|
561
|
+
parents1 = mating_pop[[shuffled_ids[i] for i in range(0, mating_pop_size, 2)]]
|
|
562
|
+
parents2 = mating_pop[[shuffled_ids[i] for i in range(1, mating_pop_size, 2)]]
|
|
563
|
+
|
|
564
|
+
mask = self.rng.choice([True, False], size=num_var)
|
|
565
|
+
|
|
566
|
+
offspring1 = np.where(mask, parents1, parents2) # True, pick from parent1, False, pick from parent2
|
|
567
|
+
offspring2 = np.where(mask, parents2, parents1) # True, pick from parent2, False, pick from parent1
|
|
568
|
+
|
|
569
|
+
# combine the two offspring populations into one, drop the last member if the number of
|
|
570
|
+
# indices (to_mate) is uneven
|
|
571
|
+
self.offspring_population = pl.from_numpy(
|
|
572
|
+
np.vstack((offspring1, offspring2))[
|
|
573
|
+
: (original_mating_pop_size if original_mating_pop_size % 2 == 0 else -1)
|
|
574
|
+
],
|
|
575
|
+
schema=self.variable_symbols,
|
|
576
|
+
).select(pl.all().cast(pl.Float64))
|
|
577
|
+
|
|
578
|
+
self.notify()
|
|
579
|
+
|
|
580
|
+
return self.offspring_population
|
|
581
|
+
|
|
582
|
+
def update(self, *_, **__):
|
|
583
|
+
"""Do nothing. This is just the basic single point binary crossover operator."""
|
|
584
|
+
|
|
585
|
+
def state(self) -> Sequence[Message]:
|
|
586
|
+
"""Return the state of the single point binary crossover operator."""
|
|
587
|
+
if self.parent_population is None or self.offspring_population is None:
|
|
588
|
+
return []
|
|
589
|
+
if self.verbosity == 0:
|
|
590
|
+
return []
|
|
591
|
+
if self.verbosity == 1:
|
|
592
|
+
return []
|
|
593
|
+
# verbosity == 2 or higher
|
|
594
|
+
return [
|
|
595
|
+
PolarsDataFrameMessage(
|
|
596
|
+
topic=CrossoverMessageTopics.PARENTS,
|
|
597
|
+
source="SimulatedBinaryCrossover",
|
|
598
|
+
value=self.parent_population,
|
|
599
|
+
),
|
|
600
|
+
PolarsDataFrameMessage(
|
|
601
|
+
topic=CrossoverMessageTopics.OFFSPRINGS,
|
|
602
|
+
source="SimulatedBinaryCrossover",
|
|
603
|
+
value=self.offspring_population,
|
|
604
|
+
),
|
|
605
|
+
]
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
class BlendAlphaCrossover(BaseCrossover):
|
|
609
|
+
"""Blend-alpha (BLX-alpha) crossover for continuous problems."""
|
|
610
|
+
|
|
611
|
+
@property
|
|
612
|
+
def provided_topics(self) -> dict[int, Sequence[CrossoverMessageTopics]]:
|
|
613
|
+
"""The message topics provided by the blend alpha crossover operator."""
|
|
614
|
+
return {
|
|
615
|
+
0: [],
|
|
616
|
+
1: [
|
|
617
|
+
CrossoverMessageTopics.XOVER_PROBABILITY,
|
|
618
|
+
CrossoverMessageTopics.ALPHA,
|
|
619
|
+
],
|
|
620
|
+
2: [
|
|
621
|
+
CrossoverMessageTopics.XOVER_PROBABILITY,
|
|
622
|
+
CrossoverMessageTopics.ALPHA,
|
|
623
|
+
CrossoverMessageTopics.PARENTS,
|
|
624
|
+
CrossoverMessageTopics.OFFSPRINGS,
|
|
625
|
+
],
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
@property
|
|
629
|
+
def interested_topics(self):
|
|
630
|
+
"""The message topics provided by the blend alpha crossover operator."""
|
|
631
|
+
return []
|
|
632
|
+
|
|
633
|
+
def __init__(
|
|
634
|
+
self,
|
|
635
|
+
*,
|
|
636
|
+
problem: Problem,
|
|
637
|
+
seed: int = 0,
|
|
638
|
+
alpha: float = 0.5,
|
|
639
|
+
xover_probability: float = 1.0,
|
|
640
|
+
**kwargs,
|
|
641
|
+
):
|
|
642
|
+
"""Initialize the blend alpha crossover operator.
|
|
643
|
+
|
|
644
|
+
Args:
|
|
645
|
+
problem (Problem): the problem object.
|
|
646
|
+
seed (int): the seed used in the random number generator for choosing the crossover point.
|
|
647
|
+
alpha (float, optional): non-negative blending factor 'alpha' that controls the extent to which
|
|
648
|
+
offspring may be sampled outside the interval defined by each pair of parent
|
|
649
|
+
genes. alpha = 0 restricts children strictly within the
|
|
650
|
+
parents range, larger alpha allows some outliers. Defaults to 0.5.
|
|
651
|
+
xover_probability (float, optional): the crossover probability parameter.
|
|
652
|
+
Ranges between 0 and 1.0. Defaults to 1.0.
|
|
653
|
+
kwargs: Additional keyword arguments. These are passed to the Subscriber class. At the very least, the
|
|
654
|
+
publisher must be passed. See the Subscriber class for more information.
|
|
655
|
+
"""
|
|
656
|
+
super().__init__(problem=problem, **kwargs)
|
|
657
|
+
|
|
658
|
+
if problem.variable_domain is not VariableDomainTypeEnum.continuous:
|
|
659
|
+
raise ValueError("BlendAlphaCrossover only works on continuous problems.")
|
|
660
|
+
|
|
661
|
+
if not 0 <= xover_probability <= 1:
|
|
662
|
+
raise ValueError("Crossover probability must be in [0,1].")
|
|
663
|
+
if alpha < 0:
|
|
664
|
+
raise ValueError("Alpha must be non-negative.")
|
|
665
|
+
|
|
666
|
+
self.alpha = alpha
|
|
667
|
+
self.xover_probability = xover_probability
|
|
668
|
+
self.seed = seed
|
|
669
|
+
|
|
670
|
+
self.parent_population: pl.DataFrame | None = None
|
|
671
|
+
self.offspring_population: pl.DataFrame | None = None
|
|
672
|
+
|
|
673
|
+
def do(
|
|
674
|
+
self,
|
|
675
|
+
*,
|
|
676
|
+
population: pl.DataFrame,
|
|
677
|
+
to_mate: list[int] | None = None,
|
|
678
|
+
) -> pl.DataFrame:
|
|
679
|
+
"""Perform BLX-alpha crossover.
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
population (pl.DataFrame): the population to perform the crossover with. The DataFrame
|
|
683
|
+
contains the decision vectors, the target vectors, and the constraint vectors.
|
|
684
|
+
to_mate (list[int] | None): the indices of the population members that should
|
|
685
|
+
participate in the crossover. If `None`, the whole population is subject
|
|
686
|
+
to the crossover.
|
|
687
|
+
|
|
688
|
+
Returns:
|
|
689
|
+
pl.DataFrame: the offspring resulting from the crossover.
|
|
690
|
+
"""
|
|
691
|
+
self.parent_population = population
|
|
692
|
+
pop_size = population.shape[0]
|
|
693
|
+
num_var = len(self.variable_symbols)
|
|
694
|
+
|
|
695
|
+
parent_decision_vars = population[self.variable_symbols].to_numpy()
|
|
696
|
+
if to_mate is None:
|
|
697
|
+
shuffled_ids = list(range(pop_size))
|
|
698
|
+
shuffle(shuffled_ids)
|
|
699
|
+
else:
|
|
700
|
+
shuffled_ids = copy.copy(to_mate)
|
|
701
|
+
|
|
702
|
+
mating_pop_size = len(shuffled_ids)
|
|
703
|
+
original_pop_size = mating_pop_size
|
|
704
|
+
if mating_pop_size % 2 == 1:
|
|
705
|
+
shuffled_ids.append(shuffled_ids[0])
|
|
706
|
+
mating_pop_size += 1
|
|
707
|
+
|
|
708
|
+
mating_pop = parent_decision_vars[shuffled_ids]
|
|
709
|
+
|
|
710
|
+
parents1 = mating_pop[0::2, :]
|
|
711
|
+
parents2 = mating_pop[1::2, :]
|
|
712
|
+
|
|
713
|
+
c_min = np.minimum(parents1, parents2)
|
|
714
|
+
c_max = np.maximum(parents1, parents2)
|
|
715
|
+
span = c_max - c_min
|
|
716
|
+
|
|
717
|
+
lower = c_min - self.alpha * span
|
|
718
|
+
upper = c_max + self.alpha * span
|
|
719
|
+
|
|
720
|
+
rng = np.random.default_rng(self.seed)
|
|
721
|
+
|
|
722
|
+
unoform_1 = rng.random((mating_pop_size // 2, num_var))
|
|
723
|
+
uniform_2 = rng.random((mating_pop_size // 2, num_var))
|
|
724
|
+
|
|
725
|
+
offspring1 = lower + unoform_1 * (upper - lower)
|
|
726
|
+
offspring2 = lower + uniform_2 * (upper - lower)
|
|
727
|
+
|
|
728
|
+
mask = rng.random(mating_pop_size // 2) > self.xover_probability
|
|
729
|
+
offspring1[mask, :] = parents1[mask, :]
|
|
730
|
+
offspring2[mask, :] = parents2[mask, :]
|
|
731
|
+
|
|
732
|
+
offspring = np.vstack((offspring1, offspring2))
|
|
733
|
+
if original_pop_size % 2 == 1:
|
|
734
|
+
offspring = offspring[:-1, :]
|
|
735
|
+
|
|
736
|
+
self.offspring_population = pl.from_numpy(offspring, schema=self.variable_symbols).select(
|
|
737
|
+
pl.all().cast(pl.Float64)
|
|
738
|
+
)
|
|
739
|
+
self.notify()
|
|
740
|
+
return self.offspring_population
|
|
741
|
+
|
|
742
|
+
def update(self, *_, **__):
|
|
743
|
+
"""Do nothing."""
|
|
744
|
+
|
|
745
|
+
def state(self) -> Sequence[Message]:
|
|
746
|
+
"""Return the state of the blend-alpha crossover operator."""
|
|
747
|
+
if self.parent_population is None:
|
|
748
|
+
return []
|
|
749
|
+
msgs: list[Message] = []
|
|
750
|
+
if self.verbosity >= 1:
|
|
751
|
+
msgs.append(
|
|
752
|
+
FloatMessage(
|
|
753
|
+
topic=CrossoverMessageTopics.XOVER_PROBABILITY,
|
|
754
|
+
source=self.__class__.__name__,
|
|
755
|
+
value=self.xover_probability,
|
|
756
|
+
)
|
|
757
|
+
)
|
|
758
|
+
msgs.append(
|
|
759
|
+
FloatMessage(
|
|
760
|
+
topic=CrossoverMessageTopics.ALPHA,
|
|
761
|
+
source=self.__class__.__name__,
|
|
762
|
+
value=self.alpha,
|
|
763
|
+
)
|
|
764
|
+
)
|
|
765
|
+
if self.verbosity >= 2: # noqa: PLR2004
|
|
766
|
+
msgs.extend(
|
|
767
|
+
[
|
|
768
|
+
PolarsDataFrameMessage(
|
|
769
|
+
topic=CrossoverMessageTopics.PARENTS,
|
|
770
|
+
source=self.__class__.__name__,
|
|
771
|
+
value=self.parent_population,
|
|
772
|
+
),
|
|
773
|
+
PolarsDataFrameMessage(
|
|
774
|
+
topic=CrossoverMessageTopics.OFFSPRINGS,
|
|
775
|
+
source=self.__class__.__name__,
|
|
776
|
+
value=self.offspring_population,
|
|
777
|
+
),
|
|
778
|
+
]
|
|
779
|
+
)
|
|
780
|
+
return msgs
|