desdeo 2.0.0__py3-none-any.whl → 2.1.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/adm/ADMAfsar.py +551 -0
- desdeo/adm/ADMChen.py +414 -0
- desdeo/adm/BaseADM.py +119 -0
- desdeo/adm/__init__.py +11 -0
- desdeo/api/__init__.py +6 -6
- desdeo/api/app.py +38 -28
- desdeo/api/config.py +65 -44
- desdeo/api/config.toml +23 -12
- desdeo/api/db.py +10 -8
- desdeo/api/db_init.py +12 -6
- desdeo/api/models/__init__.py +220 -20
- desdeo/api/models/archive.py +16 -27
- desdeo/api/models/emo.py +128 -0
- desdeo/api/models/enautilus.py +69 -0
- desdeo/api/models/gdm/gdm_aggregate.py +139 -0
- desdeo/api/models/gdm/gdm_base.py +69 -0
- desdeo/api/models/gdm/gdm_score_bands.py +114 -0
- desdeo/api/models/gdm/gnimbus.py +138 -0
- desdeo/api/models/generic.py +104 -0
- desdeo/api/models/generic_states.py +401 -0
- desdeo/api/models/nimbus.py +158 -0
- desdeo/api/models/preference.py +44 -6
- desdeo/api/models/problem.py +274 -64
- desdeo/api/models/session.py +4 -1
- desdeo/api/models/state.py +419 -52
- desdeo/api/models/user.py +7 -6
- desdeo/api/models/utopia.py +25 -0
- desdeo/api/routers/_EMO.backup +309 -0
- desdeo/api/routers/_NIMBUS.py +6 -3
- desdeo/api/routers/emo.py +497 -0
- desdeo/api/routers/enautilus.py +237 -0
- desdeo/api/routers/gdm/gdm_aggregate.py +234 -0
- desdeo/api/routers/gdm/gdm_base.py +420 -0
- desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_manager.py +398 -0
- desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_routers.py +377 -0
- desdeo/api/routers/gdm/gnimbus/gnimbus_manager.py +698 -0
- desdeo/api/routers/gdm/gnimbus/gnimbus_routers.py +591 -0
- desdeo/api/routers/generic.py +233 -0
- desdeo/api/routers/nimbus.py +705 -0
- desdeo/api/routers/problem.py +201 -4
- desdeo/api/routers/reference_point_method.py +20 -44
- desdeo/api/routers/session.py +50 -26
- desdeo/api/routers/user_authentication.py +180 -26
- desdeo/api/routers/utils.py +187 -0
- desdeo/api/routers/utopia.py +230 -0
- desdeo/api/schema.py +10 -4
- desdeo/api/tests/conftest.py +94 -2
- desdeo/api/tests/test_enautilus.py +330 -0
- desdeo/api/tests/test_models.py +550 -72
- desdeo/api/tests/test_routes.py +902 -43
- desdeo/api/utils/_database.py +263 -0
- desdeo/api/utils/database.py +28 -266
- desdeo/api/utils/emo_database.py +40 -0
- desdeo/core.py +7 -0
- desdeo/emo/__init__.py +154 -24
- desdeo/emo/hooks/archivers.py +18 -2
- desdeo/emo/methods/EAs.py +128 -5
- desdeo/emo/methods/bases.py +9 -56
- desdeo/emo/methods/templates.py +111 -0
- desdeo/emo/operators/crossover.py +544 -42
- desdeo/emo/operators/evaluator.py +10 -14
- desdeo/emo/operators/generator.py +127 -24
- desdeo/emo/operators/mutation.py +212 -41
- desdeo/emo/operators/scalar_selection.py +202 -0
- desdeo/emo/operators/selection.py +956 -214
- desdeo/emo/operators/termination.py +124 -16
- desdeo/emo/options/__init__.py +108 -0
- desdeo/emo/options/algorithms.py +435 -0
- desdeo/emo/options/crossover.py +164 -0
- desdeo/emo/options/generator.py +131 -0
- desdeo/emo/options/mutation.py +260 -0
- desdeo/emo/options/repair.py +61 -0
- desdeo/emo/options/scalar_selection.py +66 -0
- desdeo/emo/options/selection.py +127 -0
- desdeo/emo/options/templates.py +383 -0
- desdeo/emo/options/termination.py +143 -0
- desdeo/gdm/__init__.py +22 -0
- desdeo/gdm/gdmtools.py +45 -0
- desdeo/gdm/score_bands.py +114 -0
- desdeo/gdm/voting_rules.py +50 -0
- desdeo/mcdm/__init__.py +23 -1
- desdeo/mcdm/enautilus.py +338 -0
- desdeo/mcdm/gnimbus.py +484 -0
- desdeo/mcdm/nautilus_navigator.py +7 -6
- desdeo/mcdm/reference_point_method.py +70 -0
- desdeo/problem/__init__.py +5 -1
- desdeo/problem/external/__init__.py +18 -0
- desdeo/problem/external/core.py +356 -0
- desdeo/problem/external/pymoo_provider.py +266 -0
- desdeo/problem/external/runtime.py +44 -0
- desdeo/problem/infix_parser.py +2 -2
- desdeo/problem/pyomo_evaluator.py +25 -6
- desdeo/problem/schema.py +69 -48
- desdeo/problem/simulator_evaluator.py +65 -15
- desdeo/problem/testproblems/__init__.py +26 -11
- desdeo/problem/testproblems/benchmarks_server.py +120 -0
- desdeo/problem/testproblems/cake_problem.py +185 -0
- desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
- desdeo/problem/testproblems/forest_problem.py +77 -69
- desdeo/problem/testproblems/multi_valued_constraints.py +119 -0
- desdeo/problem/testproblems/{river_pollution_problem.py → river_pollution_problems.py} +28 -22
- desdeo/problem/testproblems/single_objective.py +289 -0
- desdeo/problem/testproblems/zdt_problem.py +4 -1
- desdeo/tools/__init__.py +39 -21
- desdeo/tools/desc_gen.py +22 -0
- desdeo/tools/generics.py +22 -2
- desdeo/tools/group_scalarization.py +3090 -0
- desdeo/tools/indicators_binary.py +107 -1
- desdeo/tools/indicators_unary.py +3 -16
- desdeo/tools/message.py +33 -2
- desdeo/tools/non_dominated_sorting.py +4 -3
- desdeo/tools/patterns.py +9 -7
- desdeo/tools/pyomo_solver_interfaces.py +48 -35
- desdeo/tools/reference_vectors.py +118 -351
- desdeo/tools/scalarization.py +340 -1413
- desdeo/tools/score_bands.py +491 -328
- desdeo/tools/utils.py +117 -49
- desdeo/tools/visualizations.py +67 -0
- desdeo/utopia_stuff/utopia_problem.py +1 -1
- desdeo/utopia_stuff/utopia_problem_old.py +1 -1
- {desdeo-2.0.0.dist-info → desdeo-2.1.0.dist-info}/METADATA +46 -28
- desdeo-2.1.0.dist-info/RECORD +180 -0
- {desdeo-2.0.0.dist-info → desdeo-2.1.0.dist-info}/WHEEL +1 -1
- desdeo-2.0.0.dist-info/RECORD +0 -120
- /desdeo/api/utils/{logger.py → _logger.py} +0 -0
- {desdeo-2.0.0.dist-info → desdeo-2.1.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -7,7 +7,6 @@ in multiobjective optimization are defined here.
|
|
|
7
7
|
import copy
|
|
8
8
|
from abc import abstractmethod
|
|
9
9
|
from collections.abc import Sequence
|
|
10
|
-
from random import shuffle
|
|
11
10
|
|
|
12
11
|
import numpy as np
|
|
13
12
|
import polars as pl
|
|
@@ -19,15 +18,15 @@ from desdeo.tools.message import (
|
|
|
19
18
|
Message,
|
|
20
19
|
PolarsDataFrameMessage,
|
|
21
20
|
)
|
|
22
|
-
from desdeo.tools.patterns import Subscriber
|
|
21
|
+
from desdeo.tools.patterns import Publisher, Subscriber
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
class BaseCrossover(Subscriber):
|
|
26
25
|
"""A base class for crossover operators."""
|
|
27
26
|
|
|
28
|
-
def __init__(self, problem: Problem,
|
|
27
|
+
def __init__(self, problem: Problem, verbosity: int, publisher: Publisher):
|
|
29
28
|
"""Initialize a crossover operator."""
|
|
30
|
-
super().__init__(
|
|
29
|
+
super().__init__(verbosity=verbosity, publisher=publisher)
|
|
31
30
|
self.problem = problem
|
|
32
31
|
self.variable_symbols = [var.symbol for var in problem.get_flattened_variables()]
|
|
33
32
|
self.lower_bounds = [var.lowerbound for var in problem.get_flattened_variables()]
|
|
@@ -61,7 +60,7 @@ class SimulatedBinaryCrossover(BaseCrossover):
|
|
|
61
60
|
"""
|
|
62
61
|
|
|
63
62
|
@property
|
|
64
|
-
def provided_topics(self) -> dict[
|
|
63
|
+
def provided_topics(self) -> dict[int, Sequence[CrossoverMessageTopics]]:
|
|
65
64
|
"""The message topics provided by the crossover operator."""
|
|
66
65
|
return {
|
|
67
66
|
0: [],
|
|
@@ -80,22 +79,30 @@ class SimulatedBinaryCrossover(BaseCrossover):
|
|
|
80
79
|
return []
|
|
81
80
|
|
|
82
81
|
def __init__(
|
|
83
|
-
self,
|
|
82
|
+
self,
|
|
83
|
+
*,
|
|
84
|
+
problem: Problem,
|
|
85
|
+
seed: int,
|
|
86
|
+
verbosity: int,
|
|
87
|
+
publisher: Publisher,
|
|
88
|
+
xover_probability: float = 1.0,
|
|
89
|
+
xover_distribution: float = 30,
|
|
84
90
|
):
|
|
85
91
|
"""Initialize a simulated binary crossover operator.
|
|
86
92
|
|
|
87
93
|
Args:
|
|
88
94
|
problem (Problem): the problem object.
|
|
89
95
|
seed (int): the seed for the random number generator.
|
|
96
|
+
verbosity (int): the verbosity level of the component. The keys in `provided_topics` tell what
|
|
97
|
+
topics are provided by the operator at each verbosity level. Recommended to be set to 1.
|
|
98
|
+
publisher (Publisher): the publisher to which the operator will publish messages.
|
|
90
99
|
xover_probability (float, optional): the crossover probability
|
|
91
100
|
parameter. Ranges between 0 and 1.0. Defaults to 1.0.
|
|
92
101
|
xover_distribution (float, optional): the crossover distribution
|
|
93
102
|
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
103
|
"""
|
|
97
104
|
# Subscribes to no topics, so no need to stroe/pass the topics to the super class.
|
|
98
|
-
super().__init__(problem,
|
|
105
|
+
super().__init__(problem, verbosity=verbosity, publisher=publisher)
|
|
99
106
|
self.problem = problem
|
|
100
107
|
|
|
101
108
|
if not 0 <= xover_probability <= 1:
|
|
@@ -135,7 +142,7 @@ class SimulatedBinaryCrossover(BaseCrossover):
|
|
|
135
142
|
|
|
136
143
|
if to_mate is None:
|
|
137
144
|
shuffled_ids = list(range(pop_size))
|
|
138
|
-
shuffle(shuffled_ids)
|
|
145
|
+
self.rng.shuffle(shuffled_ids)
|
|
139
146
|
else:
|
|
140
147
|
shuffled_ids = to_mate
|
|
141
148
|
mating_pop = parent_decvars[shuffled_ids]
|
|
@@ -216,16 +223,17 @@ class SimulatedBinaryCrossover(BaseCrossover):
|
|
|
216
223
|
class SinglePointBinaryCrossover(BaseCrossover):
|
|
217
224
|
"""A class that defines the single point binary crossover operation."""
|
|
218
225
|
|
|
219
|
-
def __init__(self, *, problem: Problem, seed: int,
|
|
226
|
+
def __init__(self, *, problem: Problem, seed: int, verbosity: int, publisher: Publisher):
|
|
220
227
|
"""Initialize the single point binary crossover operator.
|
|
221
228
|
|
|
222
229
|
Args:
|
|
223
230
|
problem (Problem): the problem object.
|
|
224
231
|
seed (int): the seed used in the random number generator for choosing the crossover point.
|
|
225
|
-
|
|
226
|
-
|
|
232
|
+
verbosity (int): the verbosity level of the component. The keys in `provided_topics` tell what
|
|
233
|
+
topics are provided by the operator at each verbosity level.
|
|
234
|
+
publisher (Publisher): the publisher to which the operator will publish messages.
|
|
227
235
|
"""
|
|
228
|
-
super().__init__(problem,
|
|
236
|
+
super().__init__(problem, verbosity=verbosity, publisher=publisher)
|
|
229
237
|
self.seed = seed
|
|
230
238
|
|
|
231
239
|
self.parent_population: pl.DataFrame
|
|
@@ -234,7 +242,7 @@ class SinglePointBinaryCrossover(BaseCrossover):
|
|
|
234
242
|
self.seed = seed
|
|
235
243
|
|
|
236
244
|
@property
|
|
237
|
-
def provided_topics(self) -> dict[
|
|
245
|
+
def provided_topics(self) -> dict[int, Sequence[CrossoverMessageTopics]]:
|
|
238
246
|
"""The message topics provided by the single point binary crossover operator."""
|
|
239
247
|
return {
|
|
240
248
|
0: [],
|
|
@@ -273,7 +281,7 @@ class SinglePointBinaryCrossover(BaseCrossover):
|
|
|
273
281
|
|
|
274
282
|
if to_mate is None:
|
|
275
283
|
shuffled_ids = list(range(pop_size))
|
|
276
|
-
shuffle(shuffled_ids)
|
|
284
|
+
self.rng.shuffle(shuffled_ids)
|
|
277
285
|
else:
|
|
278
286
|
shuffled_ids = copy.copy(to_mate)
|
|
279
287
|
|
|
@@ -356,16 +364,17 @@ class SinglePointBinaryCrossover(BaseCrossover):
|
|
|
356
364
|
class UniformIntegerCrossover(BaseCrossover):
|
|
357
365
|
"""A class that defines the uniform integer crossover operation."""
|
|
358
366
|
|
|
359
|
-
def __init__(self, *, problem: Problem, seed: int,
|
|
367
|
+
def __init__(self, *, problem: Problem, seed: int, verbosity: int, publisher: Publisher):
|
|
360
368
|
"""Initialize the uniform integer crossover operator.
|
|
361
369
|
|
|
362
370
|
Args:
|
|
363
371
|
problem (Problem): the problem object.
|
|
364
372
|
seed (int): the seed used in the random number generator for choosing the crossover point.
|
|
365
|
-
|
|
366
|
-
|
|
373
|
+
verbosity (int): the verbosity level of the component. The keys in `provided_topics` tell what
|
|
374
|
+
topics are provided by the operator at each verbosity level. Recommended to be set to 1.
|
|
375
|
+
publisher (Publisher): the publisher to which the operator will publish messages.
|
|
367
376
|
"""
|
|
368
|
-
super().__init__(problem,
|
|
377
|
+
super().__init__(problem, verbosity=verbosity, publisher=publisher)
|
|
369
378
|
self.seed = seed
|
|
370
379
|
|
|
371
380
|
self.parent_population: pl.DataFrame
|
|
@@ -374,7 +383,7 @@ class UniformIntegerCrossover(BaseCrossover):
|
|
|
374
383
|
self.seed = seed
|
|
375
384
|
|
|
376
385
|
@property
|
|
377
|
-
def provided_topics(self) -> dict[
|
|
386
|
+
def provided_topics(self) -> dict[int, Sequence[CrossoverMessageTopics]]:
|
|
378
387
|
"""The message topics provided by the single point binary crossover operator."""
|
|
379
388
|
return {
|
|
380
389
|
0: [],
|
|
@@ -413,7 +422,7 @@ class UniformIntegerCrossover(BaseCrossover):
|
|
|
413
422
|
|
|
414
423
|
if to_mate is None:
|
|
415
424
|
shuffled_ids = list(range(pop_size))
|
|
416
|
-
shuffle(shuffled_ids)
|
|
425
|
+
self.rng.shuffle(shuffled_ids)
|
|
417
426
|
else:
|
|
418
427
|
shuffled_ids = copy.copy(to_mate)
|
|
419
428
|
|
|
@@ -485,16 +494,17 @@ class UniformMixedIntegerCrossover(BaseCrossover):
|
|
|
485
494
|
stuff...
|
|
486
495
|
"""
|
|
487
496
|
|
|
488
|
-
def __init__(self, *, problem: Problem, seed: int,
|
|
497
|
+
def __init__(self, *, problem: Problem, seed: int, verbosity: int, publisher: Publisher):
|
|
489
498
|
"""Initialize the uniform integer crossover operator.
|
|
490
499
|
|
|
491
500
|
Args:
|
|
492
501
|
problem (Problem): the problem object.
|
|
493
502
|
seed (int): the seed used in the random number generator for choosing the crossover point.
|
|
494
|
-
|
|
495
|
-
|
|
503
|
+
verbosity (int): the verbosity level of the component. The keys in `provided_topics` tell what
|
|
504
|
+
topics are provided by the operator at each verbosity level. Recommended to be set to 1.
|
|
505
|
+
publisher (Publisher): the publisher to which the operator will publish messages.
|
|
496
506
|
"""
|
|
497
|
-
super().__init__(problem,
|
|
507
|
+
super().__init__(problem, verbosity=verbosity, publisher=publisher)
|
|
498
508
|
self.seed = seed
|
|
499
509
|
|
|
500
510
|
self.parent_population: pl.DataFrame
|
|
@@ -503,7 +513,7 @@ class UniformMixedIntegerCrossover(BaseCrossover):
|
|
|
503
513
|
self.seed = seed
|
|
504
514
|
|
|
505
515
|
@property
|
|
506
|
-
def provided_topics(self) -> dict[
|
|
516
|
+
def provided_topics(self) -> dict[int, Sequence[CrossoverMessageTopics]]:
|
|
507
517
|
"""The message topics provided by the single point binary crossover operator."""
|
|
508
518
|
return {
|
|
509
519
|
0: [],
|
|
@@ -542,7 +552,7 @@ class UniformMixedIntegerCrossover(BaseCrossover):
|
|
|
542
552
|
|
|
543
553
|
if to_mate is None:
|
|
544
554
|
shuffled_ids = list(range(pop_size))
|
|
545
|
-
shuffle(shuffled_ids)
|
|
555
|
+
self.rng.shuffle(shuffled_ids)
|
|
546
556
|
else:
|
|
547
557
|
shuffled_ids = copy.copy(to_mate)
|
|
548
558
|
|
|
@@ -634,15 +644,19 @@ class BlendAlphaCrossover(BaseCrossover):
|
|
|
634
644
|
self,
|
|
635
645
|
*,
|
|
636
646
|
problem: Problem,
|
|
637
|
-
|
|
647
|
+
verbosity: int,
|
|
648
|
+
publisher: Publisher,
|
|
649
|
+
seed: int,
|
|
638
650
|
alpha: float = 0.5,
|
|
639
651
|
xover_probability: float = 1.0,
|
|
640
|
-
**kwargs,
|
|
641
652
|
):
|
|
642
653
|
"""Initialize the blend alpha crossover operator.
|
|
643
654
|
|
|
644
655
|
Args:
|
|
645
656
|
problem (Problem): the problem object.
|
|
657
|
+
verbosity (int): the verbosity level of the component. The keys in `provided_topics` tell what
|
|
658
|
+
topics are provided by the operator at each verbosity level. Recommended to be set to 1.
|
|
659
|
+
publisher (Publisher): the publisher to which the operator will publish messages.
|
|
646
660
|
seed (int): the seed used in the random number generator for choosing the crossover point.
|
|
647
661
|
alpha (float, optional): non-negative blending factor 'alpha' that controls the extent to which
|
|
648
662
|
offspring may be sampled outside the interval defined by each pair of parent
|
|
@@ -650,10 +664,8 @@ class BlendAlphaCrossover(BaseCrossover):
|
|
|
650
664
|
parents range, larger alpha allows some outliers. Defaults to 0.5.
|
|
651
665
|
xover_probability (float, optional): the crossover probability parameter.
|
|
652
666
|
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
667
|
"""
|
|
656
|
-
super().__init__(problem=problem,
|
|
668
|
+
super().__init__(problem=problem, verbosity=verbosity, publisher=publisher)
|
|
657
669
|
|
|
658
670
|
if problem.variable_domain is not VariableDomainTypeEnum.continuous:
|
|
659
671
|
raise ValueError("BlendAlphaCrossover only works on continuous problems.")
|
|
@@ -666,6 +678,7 @@ class BlendAlphaCrossover(BaseCrossover):
|
|
|
666
678
|
self.alpha = alpha
|
|
667
679
|
self.xover_probability = xover_probability
|
|
668
680
|
self.seed = seed
|
|
681
|
+
self.rng = np.random.default_rng(self.seed)
|
|
669
682
|
|
|
670
683
|
self.parent_population: pl.DataFrame | None = None
|
|
671
684
|
self.offspring_population: pl.DataFrame | None = None
|
|
@@ -695,7 +708,7 @@ class BlendAlphaCrossover(BaseCrossover):
|
|
|
695
708
|
parent_decision_vars = population[self.variable_symbols].to_numpy()
|
|
696
709
|
if to_mate is None:
|
|
697
710
|
shuffled_ids = list(range(pop_size))
|
|
698
|
-
shuffle(shuffled_ids)
|
|
711
|
+
self.rng.shuffle(shuffled_ids)
|
|
699
712
|
else:
|
|
700
713
|
shuffled_ids = copy.copy(to_mate)
|
|
701
714
|
|
|
@@ -710,22 +723,20 @@ class BlendAlphaCrossover(BaseCrossover):
|
|
|
710
723
|
parents1 = mating_pop[0::2, :]
|
|
711
724
|
parents2 = mating_pop[1::2, :]
|
|
712
725
|
|
|
713
|
-
c_min = np.
|
|
714
|
-
c_max = np.
|
|
726
|
+
c_min = np.array(self.lower_bounds)
|
|
727
|
+
c_max = np.array(self.upper_bounds)
|
|
715
728
|
span = c_max - c_min
|
|
716
729
|
|
|
717
730
|
lower = c_min - self.alpha * span
|
|
718
731
|
upper = c_max + self.alpha * span
|
|
719
732
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
unoform_1 = rng.random((mating_pop_size // 2, num_var))
|
|
723
|
-
uniform_2 = rng.random((mating_pop_size // 2, num_var))
|
|
733
|
+
uniform_1 = self.rng.random((mating_pop_size // 2, num_var))
|
|
734
|
+
uniform_2 = self.rng.random((mating_pop_size // 2, num_var))
|
|
724
735
|
|
|
725
|
-
offspring1 = lower +
|
|
736
|
+
offspring1 = lower + uniform_1 * (upper - lower)
|
|
726
737
|
offspring2 = lower + uniform_2 * (upper - lower)
|
|
727
738
|
|
|
728
|
-
mask = rng.random(mating_pop_size // 2) > self.xover_probability
|
|
739
|
+
mask = self.rng.random(mating_pop_size // 2) > self.xover_probability
|
|
729
740
|
offspring1[mask, :] = parents1[mask, :]
|
|
730
741
|
offspring2[mask, :] = parents2[mask, :]
|
|
731
742
|
|
|
@@ -778,3 +789,494 @@ class BlendAlphaCrossover(BaseCrossover):
|
|
|
778
789
|
]
|
|
779
790
|
)
|
|
780
791
|
return msgs
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
class SingleArithmeticCrossover(BaseCrossover):
|
|
795
|
+
"""Single Arithmetic Crossover for continuous problems."""
|
|
796
|
+
|
|
797
|
+
@property
|
|
798
|
+
def provided_topics(self) -> dict[int, Sequence[CrossoverMessageTopics]]:
|
|
799
|
+
"""The message topics provided by the single arithmetic crossover operator."""
|
|
800
|
+
return {
|
|
801
|
+
0: [], # No topics for 0
|
|
802
|
+
1: [
|
|
803
|
+
CrossoverMessageTopics.XOVER_PROBABILITY, # Probability of crossover
|
|
804
|
+
],
|
|
805
|
+
2: [
|
|
806
|
+
CrossoverMessageTopics.XOVER_PROBABILITY, # Crossover probability
|
|
807
|
+
CrossoverMessageTopics.PARENTS, # Parents involved in crossover
|
|
808
|
+
CrossoverMessageTopics.OFFSPRINGS, # Offsprings created from crossover
|
|
809
|
+
],
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
@property
|
|
813
|
+
def interested_topics(self):
|
|
814
|
+
"""The message topics that the single arithmetic crossover operator is interested in."""
|
|
815
|
+
return []
|
|
816
|
+
|
|
817
|
+
def __init__(
|
|
818
|
+
self,
|
|
819
|
+
problem: Problem,
|
|
820
|
+
verbosity: int,
|
|
821
|
+
publisher: Publisher,
|
|
822
|
+
seed: int,
|
|
823
|
+
xover_probability: float = 1.0,
|
|
824
|
+
):
|
|
825
|
+
"""Initialize the single arithmetic crossover operator.
|
|
826
|
+
|
|
827
|
+
Args:
|
|
828
|
+
problem (Problem): the problem object.
|
|
829
|
+
verbosity (int): the verbosity level of the component. The keys in `provided_topics` tell what
|
|
830
|
+
topics are provided by the operator at each verbosity level. Recommended to be set to 1.
|
|
831
|
+
publisher (Publisher): the publisher to which the operator will publish messages.
|
|
832
|
+
xover_probability (float): probability of performing crossover.
|
|
833
|
+
seed (int): random seed for reproducibility.
|
|
834
|
+
"""
|
|
835
|
+
super().__init__(problem=problem, verbosity=verbosity, publisher=publisher)
|
|
836
|
+
|
|
837
|
+
if not 0 <= xover_probability <= 1:
|
|
838
|
+
raise ValueError("Crossover probability must be in [0, 1].")
|
|
839
|
+
|
|
840
|
+
self.xover_probability = xover_probability
|
|
841
|
+
self.seed = seed
|
|
842
|
+
self.parent_population: pl.DataFrame | None = None
|
|
843
|
+
self.offspring_population: pl.DataFrame | None = None
|
|
844
|
+
self.rng = np.random.default_rng(self.seed)
|
|
845
|
+
|
|
846
|
+
def do(self, *, population: pl.DataFrame, to_mate: list[int] | None = None) -> pl.DataFrame:
|
|
847
|
+
"""Perform Single Arithmetic Crossover.
|
|
848
|
+
|
|
849
|
+
Args:
|
|
850
|
+
population (pl.DataFrame): the population to perform the crossover with. The DataFrame
|
|
851
|
+
contains the decision vectors, the target vectors, and the constraint vectors.
|
|
852
|
+
to_mate (list[int] | None): the indices of the population members that should
|
|
853
|
+
participate in the crossover. If `None`, the whole population is subject
|
|
854
|
+
to the crossover.
|
|
855
|
+
|
|
856
|
+
Returns:
|
|
857
|
+
pl.DataFrame: the offspring resulting from the crossover.
|
|
858
|
+
"""
|
|
859
|
+
self.parent_population = population
|
|
860
|
+
pop_size = population.shape[0]
|
|
861
|
+
num_vars = len(self.variable_symbols)
|
|
862
|
+
|
|
863
|
+
parents = population[self.variable_symbols].to_numpy()
|
|
864
|
+
|
|
865
|
+
if to_mate is None:
|
|
866
|
+
mating_indices = list(range(pop_size))
|
|
867
|
+
self.rng.shuffle(mating_indices)
|
|
868
|
+
else:
|
|
869
|
+
mating_indices = copy.copy(to_mate)
|
|
870
|
+
|
|
871
|
+
mating_pop_size = len(mating_indices)
|
|
872
|
+
original_pop_size = mating_pop_size
|
|
873
|
+
|
|
874
|
+
if mating_pop_size % 2 == 1:
|
|
875
|
+
mating_indices.append(mating_indices[0])
|
|
876
|
+
mating_pop_size += 1
|
|
877
|
+
|
|
878
|
+
mating_pool = parents[mating_indices, :]
|
|
879
|
+
|
|
880
|
+
parents1 = mating_pool[0::2, :]
|
|
881
|
+
parents2 = mating_pool[1::2, :]
|
|
882
|
+
|
|
883
|
+
mask = self.rng.random(mating_pop_size // 2) <= self.xover_probability
|
|
884
|
+
gene_pos = self.rng.integers(0, num_vars, size=mating_pop_size // 2)
|
|
885
|
+
|
|
886
|
+
# Initialize offspring as exact copies
|
|
887
|
+
offspring1 = parents1.copy()
|
|
888
|
+
offspring2 = parents2.copy()
|
|
889
|
+
|
|
890
|
+
# Apply crossover only for selected pairs
|
|
891
|
+
row_idx = np.arange(len(mask))[mask]
|
|
892
|
+
col_idx = gene_pos[mask]
|
|
893
|
+
|
|
894
|
+
avg = 0.5 * (parents1[row_idx, col_idx] + parents2[row_idx, col_idx])
|
|
895
|
+
|
|
896
|
+
# Use advanced indexing to set arithmetic crossover gene
|
|
897
|
+
offspring1[row_idx, col_idx] = avg
|
|
898
|
+
offspring2[row_idx, col_idx] = avg
|
|
899
|
+
|
|
900
|
+
for i, k in zip(row_idx, col_idx, strict=True):
|
|
901
|
+
offspring1[i, k + 1 :] = parents2[i, k + 1 :]
|
|
902
|
+
offspring2[i, k + 1 :] = parents1[i, k + 1 :]
|
|
903
|
+
offspring1[i, :k] = parents1[i, :k]
|
|
904
|
+
offspring2[i, :k] = parents2[i, :k]
|
|
905
|
+
|
|
906
|
+
offspring = np.vstack((offspring1, offspring2))
|
|
907
|
+
if original_pop_size % 2 == 1:
|
|
908
|
+
offspring = offspring[:-1, :]
|
|
909
|
+
|
|
910
|
+
self.offspring_population = pl.from_numpy(offspring, schema=self.variable_symbols).select(
|
|
911
|
+
pl.all().cast(pl.Float64)
|
|
912
|
+
)
|
|
913
|
+
self.notify()
|
|
914
|
+
return self.offspring_population
|
|
915
|
+
|
|
916
|
+
def update(self, *_, **__):
|
|
917
|
+
"""Do nothing."""
|
|
918
|
+
|
|
919
|
+
def state(self) -> Sequence[Message]:
|
|
920
|
+
"""Return the state of the single arithmetic crossover operator."""
|
|
921
|
+
if self.parent_population is None:
|
|
922
|
+
return []
|
|
923
|
+
|
|
924
|
+
msgs: list[Message] = []
|
|
925
|
+
|
|
926
|
+
# Messages for crossover probability
|
|
927
|
+
if self.verbosity >= 1:
|
|
928
|
+
msgs.append(
|
|
929
|
+
FloatMessage(
|
|
930
|
+
topic=CrossoverMessageTopics.XOVER_PROBABILITY,
|
|
931
|
+
source=self.__class__.__name__,
|
|
932
|
+
value=self.xover_probability,
|
|
933
|
+
)
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
# Messages for parents and offspring
|
|
937
|
+
if self.verbosity >= 2: # More detailed info
|
|
938
|
+
msgs.extend(
|
|
939
|
+
[
|
|
940
|
+
PolarsDataFrameMessage(
|
|
941
|
+
topic=CrossoverMessageTopics.PARENTS,
|
|
942
|
+
source=self.__class__.__name__,
|
|
943
|
+
value=self.parent_population,
|
|
944
|
+
),
|
|
945
|
+
PolarsDataFrameMessage(
|
|
946
|
+
topic=CrossoverMessageTopics.OFFSPRINGS,
|
|
947
|
+
source=self.__class__.__name__,
|
|
948
|
+
value=self.offspring_population,
|
|
949
|
+
),
|
|
950
|
+
]
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
return msgs
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
class LocalCrossover(BaseCrossover):
|
|
957
|
+
"""Local Crossover for continuous problems."""
|
|
958
|
+
|
|
959
|
+
@property
|
|
960
|
+
def provided_topics(self) -> dict[int, Sequence[CrossoverMessageTopics]]:
|
|
961
|
+
"""The message topics provided by the local crossover operator."""
|
|
962
|
+
return {
|
|
963
|
+
0: [],
|
|
964
|
+
1: [
|
|
965
|
+
CrossoverMessageTopics.XOVER_PROBABILITY,
|
|
966
|
+
],
|
|
967
|
+
2: [
|
|
968
|
+
CrossoverMessageTopics.XOVER_PROBABILITY,
|
|
969
|
+
CrossoverMessageTopics.PARENTS,
|
|
970
|
+
CrossoverMessageTopics.OFFSPRINGS,
|
|
971
|
+
],
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
@property
|
|
975
|
+
def interested_topics(self):
|
|
976
|
+
"""The message topics that the local crossover operator is interested in."""
|
|
977
|
+
return []
|
|
978
|
+
|
|
979
|
+
def __init__(
|
|
980
|
+
self,
|
|
981
|
+
problem: Problem,
|
|
982
|
+
verbosity: int,
|
|
983
|
+
publisher: Publisher,
|
|
984
|
+
seed: int,
|
|
985
|
+
xover_probability: float = 1.0,
|
|
986
|
+
):
|
|
987
|
+
"""Initialize the local crossover operator.
|
|
988
|
+
|
|
989
|
+
Args:
|
|
990
|
+
problem (Problem): the problem object.
|
|
991
|
+
verbosity (int): the verbosity level of the component. The keys in `provided_topics` tell what
|
|
992
|
+
topics are provided by the operator at each verbosity level. Recommended to be set to 1.
|
|
993
|
+
publisher (Publisher): the publisher to which the operator will publish messages.
|
|
994
|
+
xover_probability (float): probability of performing crossover.
|
|
995
|
+
seed (int): random seed for reproducibility.
|
|
996
|
+
"""
|
|
997
|
+
super().__init__(problem=problem, verbosity=verbosity, publisher=publisher)
|
|
998
|
+
|
|
999
|
+
if not 0 <= xover_probability <= 1:
|
|
1000
|
+
raise ValueError("Crossover probability must be in [0, 1].")
|
|
1001
|
+
|
|
1002
|
+
self.xover_probability = xover_probability
|
|
1003
|
+
self.seed = seed
|
|
1004
|
+
self.rng = np.random.default_rng(self.seed)
|
|
1005
|
+
self.parent_population: pl.DataFrame | None = None
|
|
1006
|
+
self.offspring_population: pl.DataFrame | None = None
|
|
1007
|
+
|
|
1008
|
+
def do(self, *, population: pl.DataFrame, to_mate: list[int] | None = None) -> pl.DataFrame:
|
|
1009
|
+
"""Perform Local Crossover.
|
|
1010
|
+
|
|
1011
|
+
Args:
|
|
1012
|
+
population (pl.DataFrame): the population to perform the crossover with. The DataFrame
|
|
1013
|
+
contains the decision vectors, the target vectors, and the constraint vectors.
|
|
1014
|
+
to_mate (list[int] | None): the indices of the population members that should
|
|
1015
|
+
participate in the crossover. If `None`, the whole population is subject
|
|
1016
|
+
to the crossover.
|
|
1017
|
+
|
|
1018
|
+
Returns:
|
|
1019
|
+
pl.DataFrame: the offspring resulting from the crossover.
|
|
1020
|
+
"""
|
|
1021
|
+
self.parent_population = population
|
|
1022
|
+
pop_size = population.shape[0]
|
|
1023
|
+
num_var = len(self.variable_symbols)
|
|
1024
|
+
|
|
1025
|
+
parent_decision_vars = population[self.variable_symbols].to_numpy()
|
|
1026
|
+
|
|
1027
|
+
if to_mate is None:
|
|
1028
|
+
shuffled_ids = list(range(pop_size))
|
|
1029
|
+
self.rng.shuffle(shuffled_ids)
|
|
1030
|
+
else:
|
|
1031
|
+
shuffled_ids = to_mate.copy()
|
|
1032
|
+
|
|
1033
|
+
mating_pop_size = len(shuffled_ids)
|
|
1034
|
+
if mating_pop_size % 2 == 1:
|
|
1035
|
+
shuffled_ids.append(shuffled_ids[0])
|
|
1036
|
+
mating_pop_size += 1
|
|
1037
|
+
|
|
1038
|
+
mating_pop = parent_decision_vars[shuffled_ids]
|
|
1039
|
+
parents1 = mating_pop[0::2]
|
|
1040
|
+
parents2 = mating_pop[1::2]
|
|
1041
|
+
|
|
1042
|
+
offspring = np.empty((mating_pop_size, num_var))
|
|
1043
|
+
|
|
1044
|
+
for i in range(mating_pop_size // 2):
|
|
1045
|
+
if self.rng.random() < self.xover_probability:
|
|
1046
|
+
alpha = self.rng.random(num_var)
|
|
1047
|
+
|
|
1048
|
+
offspring[2 * i] = alpha * parents1[i] + (1 - alpha) * parents2[i]
|
|
1049
|
+
offspring[2 * i + 1] = (1 - alpha) * parents1[i] + alpha * parents2[i]
|
|
1050
|
+
else:
|
|
1051
|
+
offspring[2 * i] = parents1[i]
|
|
1052
|
+
offspring[2 * i + 1] = parents2[i]
|
|
1053
|
+
|
|
1054
|
+
self.offspring_population = pl.from_numpy(offspring, schema=self.variable_symbols).select(
|
|
1055
|
+
pl.all().cast(pl.Float64)
|
|
1056
|
+
)
|
|
1057
|
+
|
|
1058
|
+
self.notify()
|
|
1059
|
+
return self.offspring_population
|
|
1060
|
+
|
|
1061
|
+
def update(self, *_, **__):
|
|
1062
|
+
"""Do nothing."""
|
|
1063
|
+
|
|
1064
|
+
def state(self) -> Sequence[Message]:
|
|
1065
|
+
"""Return the state of the local crossover operator."""
|
|
1066
|
+
if self.parent_population is None:
|
|
1067
|
+
return []
|
|
1068
|
+
|
|
1069
|
+
msgs: list[Message] = []
|
|
1070
|
+
|
|
1071
|
+
if self.verbosity >= 1:
|
|
1072
|
+
msgs.append(
|
|
1073
|
+
FloatMessage(
|
|
1074
|
+
topic=CrossoverMessageTopics.XOVER_PROBABILITY,
|
|
1075
|
+
source=self.__class__.__name__,
|
|
1076
|
+
value=self.xover_probability,
|
|
1077
|
+
)
|
|
1078
|
+
)
|
|
1079
|
+
if self.verbosity >= 2:
|
|
1080
|
+
msgs.extend(
|
|
1081
|
+
[
|
|
1082
|
+
PolarsDataFrameMessage(
|
|
1083
|
+
topic=CrossoverMessageTopics.PARENTS,
|
|
1084
|
+
source=self.__class__.__name__,
|
|
1085
|
+
value=self.parent_population,
|
|
1086
|
+
),
|
|
1087
|
+
PolarsDataFrameMessage(
|
|
1088
|
+
topic=CrossoverMessageTopics.OFFSPRINGS,
|
|
1089
|
+
source=self.__class__.__name__,
|
|
1090
|
+
value=self.offspring_population,
|
|
1091
|
+
),
|
|
1092
|
+
]
|
|
1093
|
+
)
|
|
1094
|
+
return msgs
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
class BoundedExponentialCrossover(BaseCrossover):
|
|
1098
|
+
"""Bounded‐exponential (BEX) crossover for continuous problems."""
|
|
1099
|
+
|
|
1100
|
+
@property
|
|
1101
|
+
def provided_topics(self) -> dict[int, Sequence[CrossoverMessageTopics]]:
|
|
1102
|
+
"""The message topics provided by the bounded exponential crossover operator."""
|
|
1103
|
+
return {
|
|
1104
|
+
0: [],
|
|
1105
|
+
1: [
|
|
1106
|
+
CrossoverMessageTopics.XOVER_PROBABILITY,
|
|
1107
|
+
CrossoverMessageTopics.LAMBDA,
|
|
1108
|
+
],
|
|
1109
|
+
2: [
|
|
1110
|
+
CrossoverMessageTopics.XOVER_PROBABILITY,
|
|
1111
|
+
CrossoverMessageTopics.LAMBDA,
|
|
1112
|
+
CrossoverMessageTopics.PARENTS,
|
|
1113
|
+
CrossoverMessageTopics.OFFSPRINGS,
|
|
1114
|
+
],
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
@property
|
|
1118
|
+
def interested_topics(self):
|
|
1119
|
+
"""The message topics provided by the bounded exponential crossover operator."""
|
|
1120
|
+
return []
|
|
1121
|
+
|
|
1122
|
+
def __init__(
|
|
1123
|
+
self,
|
|
1124
|
+
*,
|
|
1125
|
+
problem: Problem,
|
|
1126
|
+
verbosity: int,
|
|
1127
|
+
publisher: Publisher,
|
|
1128
|
+
seed: int,
|
|
1129
|
+
lambda_: float = 1.0,
|
|
1130
|
+
xover_probability: float = 1.0,
|
|
1131
|
+
):
|
|
1132
|
+
"""Initialize the bounded‐exponential crossover operator.
|
|
1133
|
+
|
|
1134
|
+
Args:
|
|
1135
|
+
problem (Problem): the problem object.
|
|
1136
|
+
verbosity (int): the verbosity level of the component. The keys in `provided_topics` tell what
|
|
1137
|
+
topics are provided by the operator at each verbosity level. Recommended to be set to 1.
|
|
1138
|
+
publisher (Publisher): the publisher to which the operator will publish messages.
|
|
1139
|
+
seed (int): random seed for the internal generator.
|
|
1140
|
+
lambda_ (float, optional): positive scale λ for the exponential distribution.
|
|
1141
|
+
Defaults to 1.0.
|
|
1142
|
+
xover_probability (float, optional): probability of applying crossover
|
|
1143
|
+
to each pair. Defaults to 1.0.
|
|
1144
|
+
"""
|
|
1145
|
+
super().__init__(problem=problem, verbosity=verbosity, publisher=publisher)
|
|
1146
|
+
|
|
1147
|
+
if problem.variable_domain is not VariableDomainTypeEnum.continuous:
|
|
1148
|
+
raise ValueError("BoundedExponentialCrossover only works on continuous problems.")
|
|
1149
|
+
if lambda_ <= 0:
|
|
1150
|
+
raise ValueError("lambda_ must be positive.")
|
|
1151
|
+
if not 0 <= xover_probability <= 1:
|
|
1152
|
+
raise ValueError("xover_probability must be in [0,1].")
|
|
1153
|
+
|
|
1154
|
+
self.lambda_ = lambda_
|
|
1155
|
+
self.xover_probability = xover_probability
|
|
1156
|
+
self.seed = seed
|
|
1157
|
+
self.rng = np.random.default_rng(self.seed)
|
|
1158
|
+
|
|
1159
|
+
self.parent_population: pl.DataFrame | None = None
|
|
1160
|
+
self.offspring_population: pl.DataFrame | None = None
|
|
1161
|
+
|
|
1162
|
+
def do(
|
|
1163
|
+
self,
|
|
1164
|
+
*,
|
|
1165
|
+
population: pl.DataFrame,
|
|
1166
|
+
to_mate: list[int] | None = None,
|
|
1167
|
+
) -> pl.DataFrame:
|
|
1168
|
+
"""Perform bounded‐exponential crossover.
|
|
1169
|
+
|
|
1170
|
+
Args:
|
|
1171
|
+
population (pl.DataFrame): the population to perform the crossover with. The DataFrame
|
|
1172
|
+
contains the decision vectors, the target vectors, and the constraint vectors.
|
|
1173
|
+
to_mate (list[int] | None): the indices of the population members that should
|
|
1174
|
+
participate in the crossover. If `None`, the whole population is subject
|
|
1175
|
+
to the crossover.
|
|
1176
|
+
|
|
1177
|
+
Returns:
|
|
1178
|
+
pl.DataFrame: the offspring resulting from the crossover.
|
|
1179
|
+
"""
|
|
1180
|
+
self.parent_population = population
|
|
1181
|
+
pop_size = population.shape[0]
|
|
1182
|
+
num_var = len(self.variable_symbols)
|
|
1183
|
+
|
|
1184
|
+
parent_decision_vars = population[self.variable_symbols].to_numpy()
|
|
1185
|
+
if to_mate is None:
|
|
1186
|
+
shuffled_ids = list(range(pop_size))
|
|
1187
|
+
self.rng.shuffle(shuffled_ids)
|
|
1188
|
+
else:
|
|
1189
|
+
shuffled_ids = copy.copy(to_mate)
|
|
1190
|
+
|
|
1191
|
+
mating_pop_size = len(shuffled_ids)
|
|
1192
|
+
original_pop_size = mating_pop_size
|
|
1193
|
+
if mating_pop_size % 2 == 1:
|
|
1194
|
+
shuffled_ids.append(shuffled_ids[0])
|
|
1195
|
+
mating_pop_size += 1
|
|
1196
|
+
|
|
1197
|
+
mating_pop = parent_decision_vars[shuffled_ids]
|
|
1198
|
+
|
|
1199
|
+
parents1 = mating_pop[0::2, :]
|
|
1200
|
+
parents2 = mating_pop[1::2, :]
|
|
1201
|
+
|
|
1202
|
+
x_lower = np.array(self.lower_bounds)
|
|
1203
|
+
x_upper = np.array(self.upper_bounds)
|
|
1204
|
+
span = parents2 - parents1 # y_i - x_1
|
|
1205
|
+
|
|
1206
|
+
u_i = self.rng.random((mating_pop_size // 2, num_var)) # random integers
|
|
1207
|
+
r_i = self.rng.random((mating_pop_size // 2, num_var))
|
|
1208
|
+
|
|
1209
|
+
exp_lower_1 = np.exp((x_lower - parents1) / (self.lambda_ * span))
|
|
1210
|
+
exp_upper_1 = np.exp((parents1 - x_upper) / (self.lambda_ * span))
|
|
1211
|
+
|
|
1212
|
+
exp_lower_2 = np.exp((x_lower - parents2) / (self.lambda_ * span))
|
|
1213
|
+
exp_upper_2 = np.exp((parents2 - x_upper) / (self.lambda_ * span))
|
|
1214
|
+
|
|
1215
|
+
beta_1 = np.where(
|
|
1216
|
+
r_i <= 0.5,
|
|
1217
|
+
self.lambda_ * np.log(exp_lower_1 + u_i * (1 - exp_lower_1)),
|
|
1218
|
+
-self.lambda_ * np.log(1 - u_i * (1 - exp_upper_1)),
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1221
|
+
beta_2 = np.where(
|
|
1222
|
+
r_i <= 0.5,
|
|
1223
|
+
self.lambda_ * np.log(exp_lower_2 + u_i * (1 - exp_lower_2)),
|
|
1224
|
+
-self.lambda_ * np.log(1 - u_i * (1 - exp_upper_2)),
|
|
1225
|
+
)
|
|
1226
|
+
|
|
1227
|
+
offspring1 = parents1 + beta_1 * span
|
|
1228
|
+
offspring2 = parents2 + beta_2 * span
|
|
1229
|
+
|
|
1230
|
+
mask = self.rng.random(mating_pop_size // 2) > self.xover_probability
|
|
1231
|
+
offspring1[mask, :] = parents1[mask, :]
|
|
1232
|
+
offspring2[mask, :] = parents2[mask, :]
|
|
1233
|
+
|
|
1234
|
+
children = np.vstack((offspring1, offspring2))
|
|
1235
|
+
if original_pop_size % 2 == 1:
|
|
1236
|
+
children = children[:-1, :]
|
|
1237
|
+
|
|
1238
|
+
self.offspring_population = pl.from_numpy(children, schema=self.variable_symbols).select(
|
|
1239
|
+
pl.all().cast(pl.Float64)
|
|
1240
|
+
)
|
|
1241
|
+
self.notify()
|
|
1242
|
+
return self.offspring_population
|
|
1243
|
+
|
|
1244
|
+
def update(self, *_, **__):
|
|
1245
|
+
"""Do nothing."""
|
|
1246
|
+
|
|
1247
|
+
def state(self) -> Sequence[Message]:
|
|
1248
|
+
"""Return the state of the crossover operator."""
|
|
1249
|
+
if getattr(self, "parent_population", None) is None:
|
|
1250
|
+
return []
|
|
1251
|
+
msgs: list[Message] = []
|
|
1252
|
+
if self.verbosity >= 1:
|
|
1253
|
+
msgs.append(
|
|
1254
|
+
FloatMessage(
|
|
1255
|
+
topic=CrossoverMessageTopics.XOVER_PROBABILITY,
|
|
1256
|
+
source=self.__class__.__name__,
|
|
1257
|
+
value=self.xover_probability,
|
|
1258
|
+
)
|
|
1259
|
+
)
|
|
1260
|
+
msgs.append(
|
|
1261
|
+
FloatMessage(
|
|
1262
|
+
topic=CrossoverMessageTopics.LAMBDA,
|
|
1263
|
+
source=self.__class__.__name__,
|
|
1264
|
+
value=self.lambda_,
|
|
1265
|
+
)
|
|
1266
|
+
)
|
|
1267
|
+
if self.verbosity >= 2:
|
|
1268
|
+
msgs.extend(
|
|
1269
|
+
[
|
|
1270
|
+
PolarsDataFrameMessage(
|
|
1271
|
+
topic=CrossoverMessageTopics.PARENTS,
|
|
1272
|
+
source=self.__class__.__name__,
|
|
1273
|
+
value=self.parent_population,
|
|
1274
|
+
),
|
|
1275
|
+
PolarsDataFrameMessage(
|
|
1276
|
+
topic=CrossoverMessageTopics.OFFSPRINGS,
|
|
1277
|
+
source=self.__class__.__name__,
|
|
1278
|
+
value=self.offspring_population,
|
|
1279
|
+
),
|
|
1280
|
+
]
|
|
1281
|
+
)
|
|
1282
|
+
return msgs
|