pyVolutionary 1.0.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.
- pyvolutionary/__init__.py +44 -0
- pyvolutionary/abstract.py +325 -0
- pyvolutionary/african_vulture/__init__.py +2 -0
- pyvolutionary/african_vulture/african_vulture_optimization.py +87 -0
- pyvolutionary/african_vulture/models.py +41 -0
- pyvolutionary/ant_colony/__init__.py +2 -0
- pyvolutionary/ant_colony/ant_colony_optimization.py +80 -0
- pyvolutionary/ant_colony/models.py +25 -0
- pyvolutionary/aquila/__init__.py +2 -0
- pyvolutionary/aquila/aquila_optimization.py +75 -0
- pyvolutionary/aquila/models.py +9 -0
- pyvolutionary/bacterial_foraging/__init__.py +2 -0
- pyvolutionary/bacterial_foraging/bacterial_foraging_optimization.py +152 -0
- pyvolutionary/bacterial_foraging/models.py +63 -0
- pyvolutionary/bat/__init__.py +2 -0
- pyvolutionary/bat/bat_optimization.py +66 -0
- pyvolutionary/bat/models.py +66 -0
- pyvolutionary/bee_colony/__init__.py +2 -0
- pyvolutionary/bee_colony/bee_colony_optimization.py +116 -0
- pyvolutionary/bee_colony/models.py +22 -0
- pyvolutionary/camel_caravan/__init__.py +2 -0
- pyvolutionary/camel_caravan/camel_caravan_optimization.py +96 -0
- pyvolutionary/camel_caravan/models.py +61 -0
- pyvolutionary/coral_reef/__init__.py +2 -0
- pyvolutionary/coral_reef/coral_reef_optimization.py +133 -0
- pyvolutionary/coral_reef/models.py +81 -0
- pyvolutionary/coyotes/__init__.py +2 -0
- pyvolutionary/coyotes/coyotes_optimization.py +96 -0
- pyvolutionary/coyotes/models.py +22 -0
- pyvolutionary/earthworms/__init__.py +2 -0
- pyvolutionary/earthworms/earthworms_optimization.py +124 -0
- pyvolutionary/earthworms/models.py +62 -0
- pyvolutionary/electromagnetic_field/__init__.py +2 -0
- pyvolutionary/electromagnetic_field/electromagnetic_field_optimization.py +58 -0
- pyvolutionary/electromagnetic_field/models.py +45 -0
- pyvolutionary/elephant_herd/__init__.py +2 -0
- pyvolutionary/elephant_herd/elephant_herd_optimization.py +56 -0
- pyvolutionary/elephant_herd/models.py +38 -0
- pyvolutionary/enums.py +39 -0
- pyvolutionary/firefly_swarm/__init__.py +2 -0
- pyvolutionary/firefly_swarm/firefly_swarm_optimization.py +76 -0
- pyvolutionary/firefly_swarm/models.py +37 -0
- pyvolutionary/fireworks/__init__.py +2 -0
- pyvolutionary/fireworks/fireworks_optimization.py +96 -0
- pyvolutionary/fireworks/models.py +41 -0
- pyvolutionary/fish_school_search/__init__.py +2 -0
- pyvolutionary/fish_school_search/fish_school_search_optimization.py +142 -0
- pyvolutionary/fish_school_search/models.py +27 -0
- pyvolutionary/flower_pollination_algorithm/__init__.py +2 -0
- pyvolutionary/flower_pollination_algorithm/flower_pollination_algorithm_optimization.py +46 -0
- pyvolutionary/flower_pollination_algorithm/models.py +29 -0
- pyvolutionary/forest_algorithm/__init__.py +2 -0
- pyvolutionary/forest_algorithm/forest_optimization_algorithm.py +121 -0
- pyvolutionary/forest_algorithm/models.py +53 -0
- pyvolutionary/fox/__init__.py +2 -0
- pyvolutionary/fox/fox_optimization.py +43 -0
- pyvolutionary/fox/models.py +29 -0
- pyvolutionary/genetic_algorithm/__init__.py +2 -0
- pyvolutionary/genetic_algorithm/genetic_algorithm_optimization.py +167 -0
- pyvolutionary/genetic_algorithm/models.py +21 -0
- pyvolutionary/grasshopper/__init__.py +2 -0
- pyvolutionary/grasshopper/grasshopper_optimization_algorithm.py +73 -0
- pyvolutionary/grasshopper/models.py +29 -0
- pyvolutionary/grey_wolf/__init__.py +2 -0
- pyvolutionary/grey_wolf/grey_wolf_optimization.py +62 -0
- pyvolutionary/grey_wolf/models.py +9 -0
- pyvolutionary/harmony_search/__init__.py +2 -0
- pyvolutionary/harmony_search/harmony_search_optimization.py +64 -0
- pyvolutionary/harmony_search/models.py +29 -0
- pyvolutionary/helpers.py +290 -0
- pyvolutionary/imperialist_competitive/__init__.py +2 -0
- pyvolutionary/imperialist_competitive/classes.py +119 -0
- pyvolutionary/imperialist_competitive/imperialist_competitive_optimization.py +198 -0
- pyvolutionary/imperialist_competitive/models.py +47 -0
- pyvolutionary/invasive_weed/__init__.py +2 -0
- pyvolutionary/invasive_weed/invasive_weed_optimization.py +52 -0
- pyvolutionary/invasive_weed/models.py +51 -0
- pyvolutionary/krill_herd/__init__.py +2 -0
- pyvolutionary/krill_herd/krill_herd_optimization.py +179 -0
- pyvolutionary/krill_herd/models.py +62 -0
- pyvolutionary/levi_jaya_swarm/__init__.py +2 -0
- pyvolutionary/levi_jaya_swarm/levy_flight_jaya_swarm_optimization.py +40 -0
- pyvolutionary/levi_jaya_swarm/models.py +9 -0
- pyvolutionary/models.py +130 -0
- pyvolutionary/monarch_butterfly/__init__.py +2 -0
- pyvolutionary/monarch_butterfly/models.py +25 -0
- pyvolutionary/monarch_butterfly/monarch_butterfly_optimization.py +104 -0
- pyvolutionary/mountain_gazelle/__init__.py +2 -0
- pyvolutionary/mountain_gazelle/models.py +9 -0
- pyvolutionary/mountain_gazelle/mountain_gazelle_optimization.py +118 -0
- pyvolutionary/osprey/__init__.py +2 -0
- pyvolutionary/osprey/models.py +9 -0
- pyvolutionary/osprey/osprey_optimization.py +54 -0
- pyvolutionary/particle_swarm/__init__.py +2 -0
- pyvolutionary/particle_swarm/models.py +40 -0
- pyvolutionary/particle_swarm/particle_swarm_optimization.py +78 -0
- pyvolutionary/pathfinder_algorithm/__init__.py +2 -0
- pyvolutionary/pathfinder_algorithm/models.py +12 -0
- pyvolutionary/pathfinder_algorithm/pathfinder_algorithm_optimization.py +56 -0
- pyvolutionary/pelican/__init__.py +2 -0
- pyvolutionary/pelican/models.py +9 -0
- pyvolutionary/pelican/pelican_optimization.py +46 -0
- pyvolutionary/seagull/__init__.py +2 -0
- pyvolutionary/seagull/models.py +21 -0
- pyvolutionary/seagull/seagull_optimization.py +43 -0
- pyvolutionary/siberian_tiger/__init__.py +2 -0
- pyvolutionary/siberian_tiger/models.py +9 -0
- pyvolutionary/siberian_tiger/siberian_tiger_optimization.py +53 -0
- pyvolutionary/tasmanian_devil/__init__.py +2 -0
- pyvolutionary/tasmanian_devil/models.py +9 -0
- pyvolutionary/tasmanian_devil/tasmanian_devil_optimization.py +69 -0
- pyvolutionary/tests/fixtures.py +21 -0
- pyvolutionary/tests/test_african_vulture_optimization.py +34 -0
- pyvolutionary/tests/test_ant_colony_optimization.py +34 -0
- pyvolutionary/tests/test_aquila_optimization.py +27 -0
- pyvolutionary/tests/test_bacterial_foraging_optimization.py +37 -0
- pyvolutionary/tests/test_bat_optimization.py +34 -0
- pyvolutionary/tests/test_bee_colony_optimization.py +32 -0
- pyvolutionary/tests/test_camel_caravan_optimization.py +37 -0
- pyvolutionary/tests/test_coral_reef_optimization.py +39 -0
- pyvolutionary/tests/test_coyotes_optimization.py +32 -0
- pyvolutionary/tests/test_earthworms_optimization.py +37 -0
- pyvolutionary/tests/test_electromagnetic_field_optimization.py +35 -0
- pyvolutionary/tests/test_elephant_herd_optimization.py +34 -0
- pyvolutionary/tests/test_firefly_swarm_optimization.py +34 -0
- pyvolutionary/tests/test_fireworks_algorithm_optimization.py +36 -0
- pyvolutionary/tests/test_fish_school_search_optimization.py +37 -0
- pyvolutionary/tests/test_flower_pollination_algorithm_optimization.py +33 -0
- pyvolutionary/tests/test_forest_algorithm_optimization.py +36 -0
- pyvolutionary/tests/test_foxes_optimization.py +33 -0
- pyvolutionary/tests/test_genetic_algorithm_optimization.py +32 -0
- pyvolutionary/tests/test_grasshopper_optimization_algorithm.py +33 -0
- pyvolutionary/tests/test_grey_wolf_optimization.py +27 -0
- pyvolutionary/tests/test_harmony_search_optimization.py +33 -0
- pyvolutionary/tests/test_imperialist_competitive_optimization.py +40 -0
- pyvolutionary/tests/test_invasive_weed_optimization.py +34 -0
- pyvolutionary/tests/test_krill_herd_optimization.py +40 -0
- pyvolutionary/tests/test_levy_flight_jaya_swarm_optimization.py +33 -0
- pyvolutionary/tests/test_monarch_butterfly_optimization.py +33 -0
- pyvolutionary/tests/test_mountain_gazelle_optimization.py +27 -0
- pyvolutionary/tests/test_osprey_optimization.py +26 -0
- pyvolutionary/tests/test_particle_swarm_optimization.py +34 -0
- pyvolutionary/tests/test_pathfinder_algorithm_optimization.py +27 -0
- pyvolutionary/tests/test_pelican_optimization.py +27 -0
- pyvolutionary/tests/test_seagull_optimization.py +32 -0
- pyvolutionary/tests/test_siberian_tiger_optimization.py +27 -0
- pyvolutionary/tests/test_task_bounds.py +9 -0
- pyvolutionary/tests/test_tasmanian_devil_optimization.py +27 -0
- pyvolutionary/tests/test_utils.py +43 -0
- pyvolutionary/tests/test_virus_colony_search_optimization.py +33 -0
- pyvolutionary/tests/test_walrus_optimization.py +27 -0
- pyvolutionary/tests/test_whales_optimization.py +27 -0
- pyvolutionary/tests/test_wildebeest_herd_optimization.py +41 -0
- pyvolutionary/tests/test_zebra_optimization.py +27 -0
- pyvolutionary/utils.py +94 -0
- pyvolutionary/virus_colony_search/__init__.py +2 -0
- pyvolutionary/virus_colony_search/models.py +29 -0
- pyvolutionary/virus_colony_search/virus_colony_search_optimization.py +99 -0
- pyvolutionary/walrus/__init__.py +2 -0
- pyvolutionary/walrus/models.py +9 -0
- pyvolutionary/walrus/walrus_optimization.py +43 -0
- pyvolutionary/whales/__init__.py +2 -0
- pyvolutionary/whales/models.py +12 -0
- pyvolutionary/whales/whales_optimization.py +56 -0
- pyvolutionary/wildebeest_herd/__init__.py +2 -0
- pyvolutionary/wildebeest_herd/models.py +94 -0
- pyvolutionary/wildebeest_herd/wildebeest_herd_optimization.py +143 -0
- pyvolutionary/zebra/__init__.py +2 -0
- pyvolutionary/zebra/models.py +9 -0
- pyvolutionary/zebra/zebra_optimization.py +53 -0
- pyvolutionary-1.0.1.dist-info/METADATA +350 -0
- pyvolutionary-1.0.1.dist-info/RECORD +173 -0
- pyvolutionary-1.0.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..helpers import parse_obj_doc # type: ignore
|
|
4
|
+
from ..abstract import OptimizationAbstract
|
|
5
|
+
from .models import Fish, FishSchoolSearchOptimizationConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FishSchoolSearchOptimization(OptimizationAbstract):
|
|
9
|
+
"""
|
|
10
|
+
Implementation of the Fish School Search Optimization.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
config (FishSchoolSearchOptimizationConfig): an instance of FishSchoolSearchOptimizationConfig class.
|
|
14
|
+
{parse_obj_doc(FishSchoolSearchOptimizationConfig)}
|
|
15
|
+
|
|
16
|
+
Bibliography
|
|
17
|
+
----------
|
|
18
|
+
[1] Bastos Filho, Lima Neto, Lins, D. O. Nascimento and P. Lima, A novel search algorithm based on fish school
|
|
19
|
+
behavior, in 2008 IEEE International Conference on Systems, Man and Cybernetics, Oct 2008, pp. 2646–2651.
|
|
20
|
+
"""
|
|
21
|
+
def __init__(self, config: FishSchoolSearchOptimizationConfig, debug: bool | None = False):
|
|
22
|
+
super().__init__(config, debug)
|
|
23
|
+
self.__school_weight = self._config.population_size * self._config.w_scale / 2.0
|
|
24
|
+
self.__step_individual: np.ndarray | None = None
|
|
25
|
+
self.__step_volitive: np.ndarray | None = None
|
|
26
|
+
|
|
27
|
+
def before_initialization(self):
|
|
28
|
+
self.__step_individual = self._config.step_individual_init * self._bandwidth()
|
|
29
|
+
self.__step_volitive = self._config.step_volitive_init * self._bandwidth()
|
|
30
|
+
|
|
31
|
+
def _init_agent(self, position: list[float] | np.ndarray | None = None) -> Fish:
|
|
32
|
+
agent = super()._init_agent(position)
|
|
33
|
+
return Fish(**agent.model_dump(), weight=self._config.w_scale / 2.0)
|
|
34
|
+
|
|
35
|
+
def __update_steps__(self):
|
|
36
|
+
"""
|
|
37
|
+
Update the steps of the fishes. The steps are updated according to the current cycle. The steps are updated
|
|
38
|
+
according to the following equations: \n
|
|
39
|
+
step_individual = step_individual_init - (cycles + 1) * (step_individual_init - step_individual_final) / max_cycles \n
|
|
40
|
+
step_volitive = step_volitive_init - (cycles + 1) * (step_volitive_init - step_volitive_final) / max_cycles \n
|
|
41
|
+
where: \n
|
|
42
|
+
step_individual_init: length of initial individual step \n
|
|
43
|
+
step_individual_final: length of final individual step \n
|
|
44
|
+
step_volitive_init: length of initial volatile step \n
|
|
45
|
+
step_volitive_final: length of final volatile step \n
|
|
46
|
+
cycles: current cycle \n
|
|
47
|
+
max_cycles: maximum number of cycles.
|
|
48
|
+
"""
|
|
49
|
+
def update(init: float, final: float) -> np.ndarray:
|
|
50
|
+
return np.full(
|
|
51
|
+
self._task.space_dimension, init - (self._cycles + 1) * (init - final) / self._config.max_cycles
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
self.__step_individual = update(self._config.step_individual_init, self._config.step_individual_final)
|
|
55
|
+
self.__step_volitive = update(self._config.step_volitive_init, self._config.step_volitive_final)
|
|
56
|
+
|
|
57
|
+
def __feeding__(self):
|
|
58
|
+
"""
|
|
59
|
+
Feed all fishes. The feeding is done according to the following equation: \n
|
|
60
|
+
weight = weight + delta_cost / max_delta_cost \n
|
|
61
|
+
where: \n
|
|
62
|
+
weight: weight of the fish \n
|
|
63
|
+
delta_cost: difference between the current cost and the previous cost of the fish \n
|
|
64
|
+
max_delta_cost: maximum difference between the current cost and the previous cost of all fishes.
|
|
65
|
+
"""
|
|
66
|
+
def feeding(fish: Fish, mxd: float) -> Fish:
|
|
67
|
+
if mxd:
|
|
68
|
+
fish.weight += (fish.delta_cost / mxd)
|
|
69
|
+
fish.weight = np.clip(fish.weight, self._config.min_w, self._config.w_scale)
|
|
70
|
+
return fish
|
|
71
|
+
|
|
72
|
+
max_delta_cost = max([fish.delta_cost for fish in self._population])
|
|
73
|
+
self._population = [feeding(fish, max_delta_cost) for fish in self._population]
|
|
74
|
+
|
|
75
|
+
def __individual_movement__(self):
|
|
76
|
+
"""
|
|
77
|
+
Perform individual movement for each fish.
|
|
78
|
+
"""
|
|
79
|
+
def move(fish: Fish, sd: int, si: np.ndarray) -> Fish:
|
|
80
|
+
pos = np.array(fish.position)
|
|
81
|
+
new_fish = self._init_agent(pos + (si * self._uniform_position()))
|
|
82
|
+
if new_fish.cost < fish.cost:
|
|
83
|
+
delta_cost = abs(new_fish.cost - fish.cost)
|
|
84
|
+
delta_pos = (np.array(new_fish.position) - pos).tolist()
|
|
85
|
+
|
|
86
|
+
fish = new_fish
|
|
87
|
+
fish.delta_cost = delta_cost
|
|
88
|
+
fish.delta_pos = delta_pos
|
|
89
|
+
return fish
|
|
90
|
+
|
|
91
|
+
fish.delta_pos = np.zeros(sd).tolist()
|
|
92
|
+
fish.delta_cost = 0
|
|
93
|
+
return fish
|
|
94
|
+
|
|
95
|
+
self._population = [move(
|
|
96
|
+
fish, self._task.space_dimension, self.__step_individual
|
|
97
|
+
) for fish in self._population]
|
|
98
|
+
|
|
99
|
+
def __collective_instinctive_movement__(self):
|
|
100
|
+
"""
|
|
101
|
+
Perform collective instinctive movement.
|
|
102
|
+
"""
|
|
103
|
+
cost_eval_enhanced = sum(
|
|
104
|
+
[fish.delta_cost * np.array(fish.delta_pos) for fish in self._population],
|
|
105
|
+
start=np.zeros(self._task.space_dimension)
|
|
106
|
+
)
|
|
107
|
+
density = sum([f.delta_cost for f in self._population])
|
|
108
|
+
if density != 0:
|
|
109
|
+
cost_eval_enhanced /= density
|
|
110
|
+
self._population = [
|
|
111
|
+
self._init_agent((np.array(fish.position) + cost_eval_enhanced).tolist()) for fish in self._population
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
def __collective_volitive_movement__(self):
|
|
115
|
+
"""
|
|
116
|
+
Perform collective volitive movement.
|
|
117
|
+
"""
|
|
118
|
+
def move(fish: Fish, m: int, bc: np.ndarray, s: np.ndarray, d: int) -> Fish:
|
|
119
|
+
pos = np.array(fish.position)
|
|
120
|
+
step = (pos - bc) * s
|
|
121
|
+
new_pos = pos + (m * step * np.random.uniform(0, 1, d))
|
|
122
|
+
return self._init_agent(new_pos)
|
|
123
|
+
|
|
124
|
+
school_weight = sum([fish.weight for fish in self._population])
|
|
125
|
+
multiplier = -1 if school_weight > self.__school_weight else 1
|
|
126
|
+
|
|
127
|
+
barycenter = sum(
|
|
128
|
+
[np.array(fish.position) * fish.weight for fish in self._population],
|
|
129
|
+
start=np.zeros(self._task.space_dimension)
|
|
130
|
+
)
|
|
131
|
+
barycenter /= sum([fish.weight for fish in self._population])
|
|
132
|
+
|
|
133
|
+
self._population = [move(
|
|
134
|
+
fish, multiplier, barycenter, self.__step_volitive, self._task.space_dimension
|
|
135
|
+
) for fish in self._population]
|
|
136
|
+
|
|
137
|
+
def optimization_step(self):
|
|
138
|
+
self.__individual_movement__()
|
|
139
|
+
self.__feeding__()
|
|
140
|
+
self.__collective_instinctive_movement__()
|
|
141
|
+
self.__collective_volitive_movement__()
|
|
142
|
+
self.__update_steps__()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..models import Agent, BaseOptimizationConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Fish(Agent):
|
|
7
|
+
weight: float
|
|
8
|
+
delta_pos: list[float] | None = None
|
|
9
|
+
delta_cost: float | None = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FishSchoolSearchOptimizationConfig(BaseOptimizationConfig):
|
|
13
|
+
"""
|
|
14
|
+
Configuration class of the Fish School Search Optimization.\n
|
|
15
|
+
step_individual_init (float): length of initial individual step.\n
|
|
16
|
+
step_individual_final (float): length of final individual step.\n
|
|
17
|
+
step_volitive_init (float): length of initial volatile step.\n
|
|
18
|
+
step_volitive_final (float): length of final volatile step.\n
|
|
19
|
+
min_w (float): minimum weight of a fish.\n
|
|
20
|
+
w_scale (float): scale weight of a fish.
|
|
21
|
+
"""
|
|
22
|
+
step_individual_init: float
|
|
23
|
+
step_individual_final: float
|
|
24
|
+
step_volitive_init: float
|
|
25
|
+
step_volitive_final: float
|
|
26
|
+
min_w: float
|
|
27
|
+
w_scale: float
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..helpers import (
|
|
4
|
+
get_levy_flight_step,
|
|
5
|
+
parse_obj_doc, # type: ignore
|
|
6
|
+
)
|
|
7
|
+
from ..abstract import OptimizationAbstract
|
|
8
|
+
from .models import FlowerPollinationAlgorithmOptimizationConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FlowerPollinationAlgorithmOptimization(OptimizationAbstract):
|
|
12
|
+
"""
|
|
13
|
+
Implementation of the Flower Pollination Algorithm Optimization.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
config (FlowerPollinationAlgorithmOptimizationConfig): an instance of
|
|
17
|
+
FlowerPollinationAlgorithmOptimizationConfig class.
|
|
18
|
+
{parse_obj_doc(FlowerPollinationAlgorithmOptimizationConfig)}
|
|
19
|
+
|
|
20
|
+
Bibliography
|
|
21
|
+
----------
|
|
22
|
+
[1] Yang, X.S., 2012, September. Flower pollination algorithm for global optimization. In International
|
|
23
|
+
conference on unconventional computing and natural computation (pp. 240-249). Springer, Berlin, Heidelberg.
|
|
24
|
+
"""
|
|
25
|
+
def __init__(self, config: FlowerPollinationAlgorithmOptimizationConfig, debug: bool | None = False):
|
|
26
|
+
super().__init__(config, debug)
|
|
27
|
+
|
|
28
|
+
def optimization_step(self):
|
|
29
|
+
n_dims = self._task.space_dimension
|
|
30
|
+
pop_size = self._config.population_size
|
|
31
|
+
|
|
32
|
+
for idx, pollinator in enumerate(self._population):
|
|
33
|
+
position = np.array(pollinator.position)
|
|
34
|
+
best_position = np.array(self._best_agent.position)
|
|
35
|
+
|
|
36
|
+
if np.random.uniform() < self._config.p_s:
|
|
37
|
+
levy = get_levy_flight_step(multiplier=self._config.levy_multiplier, size=n_dims, case=-1)
|
|
38
|
+
pos_new = position + 1.0 / np.sqrt(self._cycles) * levy * (position - best_position)
|
|
39
|
+
else:
|
|
40
|
+
id1, id2 = np.random.choice(list(set(range(0, pop_size)) - {idx}), 2, replace=False)
|
|
41
|
+
pos_new = position + np.random.uniform() * (
|
|
42
|
+
np.array(self._population[id1].position) - np.array(self._population[id2].position)
|
|
43
|
+
)
|
|
44
|
+
self._population[idx] = self._greedy_select_agent(
|
|
45
|
+
self._init_agent(self._correct_position(pos_new)), pollinator
|
|
46
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from pydantic import field_validator
|
|
2
|
+
|
|
3
|
+
from ..models import Agent, BaseOptimizationConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Pollinator(Agent):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FlowerPollinationAlgorithmOptimizationConfig(BaseOptimizationConfig):
|
|
11
|
+
"""
|
|
12
|
+
Configuration class of the Flower Pollination Algorithm.
|
|
13
|
+
p_s (float): [0.5, 0.95], switch probability.\n
|
|
14
|
+
levy_multiplier (float): [0.0001, 1000], multiplier factor of Levy-flight trajectory, depends on the problem.
|
|
15
|
+
"""
|
|
16
|
+
p_s: float
|
|
17
|
+
levy_multiplier: float
|
|
18
|
+
|
|
19
|
+
@field_validator("p_s")
|
|
20
|
+
def correct_p_s(cls, v):
|
|
21
|
+
if not 0.5 <= v <= 0.95:
|
|
22
|
+
raise ValueError(f"\"p_s\" must be a float between 0.5 and 0.95. Got {v}")
|
|
23
|
+
return v
|
|
24
|
+
|
|
25
|
+
@field_validator("levy_multiplier")
|
|
26
|
+
def correct_levy_multiplier(cls, v):
|
|
27
|
+
if not 0.0001 <= v <= 1000:
|
|
28
|
+
raise ValueError(f"\"levy_multiplier\" must be a float between 0.0001 and 1000. Got {v}")
|
|
29
|
+
return v
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..helpers import (
|
|
4
|
+
sort_and_trim,
|
|
5
|
+
sort_by_cost,
|
|
6
|
+
parse_obj_doc, # type: ignore
|
|
7
|
+
)
|
|
8
|
+
from ..abstract import OptimizationAbstract
|
|
9
|
+
from .models import Tree, ForestOptimizationAlgorithmConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ForestOptimizationAlgorithm(OptimizationAbstract):
|
|
13
|
+
"""
|
|
14
|
+
Implementation of the Forest Optimization Algorithm.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
config (ForestOptimizationAlgorithmConfig): an instance of ForestOptimizationAlgorithmConfig class.
|
|
18
|
+
{parse_obj_doc(ForestOptimizationAlgorithmConfig)}
|
|
19
|
+
|
|
20
|
+
Bibliography
|
|
21
|
+
----------
|
|
22
|
+
[1] Manizheh Ghaemi, Mohammad-Reza Feizi-Derakhshi, Forest Optimization Algorithm, Expert Systems with Applications,
|
|
23
|
+
Volume 41, Issue 15, 2014, Pages 6676-6687, ISSN 0957-4174, https://doi.org/10.1016/j.eswa.2014.05.009.
|
|
24
|
+
"""
|
|
25
|
+
def __init__(self, config: ForestOptimizationAlgorithmConfig, debug: bool | None = False):
|
|
26
|
+
super().__init__(config, debug)
|
|
27
|
+
self.__dx: np.ndarray | None = None
|
|
28
|
+
|
|
29
|
+
def before_initialization(self):
|
|
30
|
+
_, ub = self._get_bounds()
|
|
31
|
+
self.__dx = np.absolute(ub) / 5.0
|
|
32
|
+
|
|
33
|
+
def _init_agent(self, position: list[float] | np.ndarray | None = None) -> Tree:
|
|
34
|
+
agent = super()._init_agent(position)
|
|
35
|
+
return Tree(**agent.model_dump())
|
|
36
|
+
|
|
37
|
+
def __local_seeding__(self) -> list[Tree]:
|
|
38
|
+
"""
|
|
39
|
+
Local optimum search stage that should prevent getting stuck in a local optimum. It is performed by randomly
|
|
40
|
+
changing the position of a tree, i.e. by randomly seeding a tree around it. The number of changes is defined by
|
|
41
|
+
the local_seeding_changes parameter. Finally, the age of each tree is increased by one and the seeded trees are
|
|
42
|
+
added to the population.
|
|
43
|
+
:return: list of seeded trees.
|
|
44
|
+
:rtype: list[Tree]
|
|
45
|
+
"""
|
|
46
|
+
def update_position(position: np.ndarray) -> np.ndarray:
|
|
47
|
+
indices = np.random.choice(self._task.space_dimension, self._config.local_seeding_changes, replace=False)
|
|
48
|
+
position[indices] += np.random.uniform(-self.__dx[indices], self.__dx[indices])
|
|
49
|
+
return position
|
|
50
|
+
|
|
51
|
+
# repeat local_seeding_changes times each tree that is going to be removed
|
|
52
|
+
candidates = [
|
|
53
|
+
self._init_agent(update_position(np.array(tree.position)))
|
|
54
|
+
for tree in self._population if tree.age == 0
|
|
55
|
+
for _ in range(self._config.local_seeding_changes)
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
# increase the age of each tree
|
|
59
|
+
for tree in self._population:
|
|
60
|
+
tree.age += 1
|
|
61
|
+
|
|
62
|
+
return candidates
|
|
63
|
+
|
|
64
|
+
def __global_seeding__(self, candidates: list[Tree], size: int) -> list[Tree]:
|
|
65
|
+
"""
|
|
66
|
+
Global optimum search stage that should prevent getting stuck in a local optimum. It is performed by randomly
|
|
67
|
+
changing the position of a tree, i.e. by randomly seeding a tree around it. The number of changes is defined by
|
|
68
|
+
the global_seeding_changes parameter. The difference between this method and the local_seeding method is that
|
|
69
|
+
this method is performed on trees that are dying, i.e. that are going to be removed.
|
|
70
|
+
:param candidates: list of trees that are dying, i.e. that are going to be removed.
|
|
71
|
+
:param size: number of seeds to be generated.
|
|
72
|
+
:return: list of seeded trees.
|
|
73
|
+
"""
|
|
74
|
+
def update_position(position: np.ndarray) -> np.ndarray:
|
|
75
|
+
indices = np.random.choice(self._task.space_dimension, self._config.global_seeding_changes, replace=False)
|
|
76
|
+
position[indices] = self._uniform_coordinates(indices)
|
|
77
|
+
return position
|
|
78
|
+
|
|
79
|
+
return [self._init_agent(
|
|
80
|
+
update_position(np.array(candidates[idx].position))
|
|
81
|
+
) for idx in np.random.choice(len(candidates), size, replace=False)]
|
|
82
|
+
|
|
83
|
+
def __remove_lifetime_exceeded__(self) -> list[Tree]:
|
|
84
|
+
"""
|
|
85
|
+
Remove trees that exceeded their lifetime. The lifetime of a tree is defined by the lifetime parameter.
|
|
86
|
+
:return: list of trees that exceeded their lifetime.
|
|
87
|
+
:rtype: list[Tree]
|
|
88
|
+
"""
|
|
89
|
+
candidates = [tree for tree in self._population if tree.age > self._config.lifetime]
|
|
90
|
+
# remove trees that exceeded their lifetime from the population
|
|
91
|
+
self._population = [tree for tree in self._population if tree.age <= self._config.lifetime]
|
|
92
|
+
return candidates
|
|
93
|
+
|
|
94
|
+
def __survival_of_the_fittest__(self, candidates: list[Tree]):
|
|
95
|
+
"""
|
|
96
|
+
Survival of the fittest stage. It is performed by removing the trees with the highest cost function value.
|
|
97
|
+
:param candidates: list of trees that are dying, i.e. that are going to be removed.
|
|
98
|
+
:return: candidates to be removed.
|
|
99
|
+
:rtype: list[Tree]
|
|
100
|
+
"""
|
|
101
|
+
sort_by_cost(self._population)
|
|
102
|
+
candidates += self._population[self._config.area_limit+1:]
|
|
103
|
+
self._population = self._population[:self._config.area_limit]
|
|
104
|
+
return candidates
|
|
105
|
+
|
|
106
|
+
def optimization_step(self):
|
|
107
|
+
# add seeded trees to the population
|
|
108
|
+
self._population += self.__local_seeding__()
|
|
109
|
+
|
|
110
|
+
# identify trees that exceeded their lifetime and area limit
|
|
111
|
+
dying_trees = self.__survival_of_the_fittest__(
|
|
112
|
+
self.__remove_lifetime_exceeded__()
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# global seeding
|
|
116
|
+
gsn = int(self._config.transfer_rate * len(dying_trees))
|
|
117
|
+
if gsn > 0:
|
|
118
|
+
self._population += self.__global_seeding__(dying_trees, gsn)
|
|
119
|
+
|
|
120
|
+
self._population = sort_and_trim(self._population, self._config.population_size)
|
|
121
|
+
self._population[0].age = 0
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from pydantic import model_validator, field_validator
|
|
2
|
+
|
|
3
|
+
from ..models import Agent, BaseOptimizationConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Tree(Agent):
|
|
7
|
+
age: int = 0
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ForestOptimizationAlgorithmConfig(BaseOptimizationConfig):
|
|
11
|
+
"""
|
|
12
|
+
Configuration class of the Forest Optimization Algorithm.
|
|
13
|
+
lifetime (int): the lifetime of a tree.\n
|
|
14
|
+
area_limit (int): the area limit of a tree.\n
|
|
15
|
+
local_seeding_changes (int): the number of local seeding changes.\n
|
|
16
|
+
global_seeding_changes (int): the number of global seeding changes.\n
|
|
17
|
+
transfer_rate (float): (0, 1), the transfer rate.
|
|
18
|
+
"""
|
|
19
|
+
lifetime: int = 10
|
|
20
|
+
area_limit: int = 10
|
|
21
|
+
local_seeding_changes: int = 3
|
|
22
|
+
global_seeding_changes: int = 3
|
|
23
|
+
transfer_rate: float = 0.5
|
|
24
|
+
|
|
25
|
+
@model_validator(mode="after")
|
|
26
|
+
def _check_area_limit(self) -> "ForestOptimizationAlgorithmConfig":
|
|
27
|
+
if not 0 < self.area_limit < self.population_size:
|
|
28
|
+
raise ValueError('area_limit must be a positive integer, lower than the population size')
|
|
29
|
+
return self
|
|
30
|
+
|
|
31
|
+
@field_validator('lifetime')
|
|
32
|
+
def _check_lifetime(cls, v):
|
|
33
|
+
if v < 0:
|
|
34
|
+
raise ValueError('lifetime must be a positive integer')
|
|
35
|
+
return v
|
|
36
|
+
|
|
37
|
+
@field_validator('local_seeding_changes')
|
|
38
|
+
def _check_local_seeding_changes(cls, v):
|
|
39
|
+
if v < 0:
|
|
40
|
+
raise ValueError('local_seeding_changes must be a positive integer')
|
|
41
|
+
return v
|
|
42
|
+
|
|
43
|
+
@field_validator('global_seeding_changes')
|
|
44
|
+
def _check_global_seeding_changes(cls, v):
|
|
45
|
+
if v < 0:
|
|
46
|
+
raise ValueError('global_seeding_changes must be a positive integer')
|
|
47
|
+
return v
|
|
48
|
+
|
|
49
|
+
@field_validator('transfer_rate')
|
|
50
|
+
def _check_transfer_rate(cls, v):
|
|
51
|
+
if not 0 < v < 1:
|
|
52
|
+
raise ValueError('transfer_rate must be a float between 0 and 1')
|
|
53
|
+
return v
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..helpers import parse_obj_doc # type: ignore
|
|
4
|
+
from ..abstract import OptimizationAbstract
|
|
5
|
+
from .models import FoxOptimizationConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FoxOptimization(OptimizationAbstract):
|
|
9
|
+
"""
|
|
10
|
+
Implementation of the Fox Optimization algorithm.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
config (FoxOptimizationConfig): an instance of FoxOptimizationConfig class.
|
|
14
|
+
{parse_obj_doc(FoxOptimizationConfig)}
|
|
15
|
+
|
|
16
|
+
Bibliography
|
|
17
|
+
----------
|
|
18
|
+
[1] Mohammed, H., & Rashid, T. (2023). FOX: a FOX-inspired optimization algorithm. Applied Intelligence, 53(1),
|
|
19
|
+
1030-1050.
|
|
20
|
+
"""
|
|
21
|
+
def __init__(self, config: FoxOptimizationConfig, debug: bool | None = False):
|
|
22
|
+
super().__init__(config, debug)
|
|
23
|
+
self.__mint = np.inf
|
|
24
|
+
|
|
25
|
+
def optimization_step(self):
|
|
26
|
+
a = 2 * (1 - (1.0 / self._cycles))
|
|
27
|
+
best_position = np.array(self._best_agent.position)
|
|
28
|
+
|
|
29
|
+
for idx, fox in enumerate(self._population):
|
|
30
|
+
if np.random.random() >= 0.5:
|
|
31
|
+
time1 = np.random.random(self._task.space_dimension)
|
|
32
|
+
sps = best_position / time1
|
|
33
|
+
travel_distance = 0.5 * sps * time1
|
|
34
|
+
tt = np.mean(time1)
|
|
35
|
+
jump = 0.5 * 9.81 * (tt / 2) ** 2
|
|
36
|
+
pos_new = travel_distance * jump * (self._config.c1 if np.random.random() > 0.18 else self._config.c2)
|
|
37
|
+
if self.__mint > tt:
|
|
38
|
+
self.__mint = tt
|
|
39
|
+
else:
|
|
40
|
+
pos_new = best_position + np.random.standard_normal(self._task.space_dimension) * (self.__mint * a)
|
|
41
|
+
agent = self._init_agent(self._correct_position(pos_new))
|
|
42
|
+
|
|
43
|
+
self._population[idx] = self._greedy_select_agent(fox, agent)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from pydantic import field_validator
|
|
2
|
+
|
|
3
|
+
from ..models import Agent, BaseOptimizationConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Fox(Agent):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FoxOptimizationConfig(BaseOptimizationConfig):
|
|
11
|
+
"""
|
|
12
|
+
Configuration class of the Fox Optimization algorithm.
|
|
13
|
+
c1 (float): [0, 0.18], the probability of jumping (c1 in the paper)
|
|
14
|
+
c2 (float): [0.19, 1], the probability of jumping (c2 in the paper)
|
|
15
|
+
"""
|
|
16
|
+
c1: float
|
|
17
|
+
c2: float
|
|
18
|
+
|
|
19
|
+
@field_validator("c1")
|
|
20
|
+
def correct_c1(cls, v):
|
|
21
|
+
if not 0 <= v <= 0.18:
|
|
22
|
+
raise ValueError("c1 must be in [0, 0.18]")
|
|
23
|
+
return v
|
|
24
|
+
|
|
25
|
+
@field_validator("c2")
|
|
26
|
+
def correct_c2(cls, v):
|
|
27
|
+
if not 0.19 <= v <= 1:
|
|
28
|
+
raise ValueError("c2 must be in [0.19, 1]")
|
|
29
|
+
return v
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..helpers import parse_obj_doc # type: ignore
|
|
4
|
+
from ..abstract import OptimizationAbstract
|
|
5
|
+
from .models import Gene, GeneticAlgorithmOptimizationConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GeneticAlgorithmOptimization(OptimizationAbstract):
|
|
9
|
+
"""
|
|
10
|
+
Implementation of the Genetic Algorithm Optimization algorithm.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
config (GeneticAlgorithmOptimizationConfig): an instance of GeneticAlgorithmOptimizationConfig class.
|
|
14
|
+
{parse_obj_doc(GeneticAlgorithmOptimizationConfig)}
|
|
15
|
+
|
|
16
|
+
Bibliography
|
|
17
|
+
----------
|
|
18
|
+
[1] Alander, Jarmo. (1999). An Indexed Bibliography of Genetic Algorithm Implementations.
|
|
19
|
+
[2] Goldberg, David E. (1989). Genetic Algorithms in Search, Optimization, and Machine Learning. Addison-Wesley.
|
|
20
|
+
ISBN 978-0201157673. OCLC 18319123.
|
|
21
|
+
[3] Holland, John H. (1975). Adaptation in Natural and Artificial Systems. University of Michigan Press.
|
|
22
|
+
ISBN 978-0262581110. OCLC 1635804.
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, config: GeneticAlgorithmOptimizationConfig, debug: bool | None = False):
|
|
25
|
+
super().__init__(config, debug)
|
|
26
|
+
self.__n_bits: int | None = None
|
|
27
|
+
self.__p_mutation: float | None = None
|
|
28
|
+
self.__bit_positions: list[list[int]] | None = None
|
|
29
|
+
|
|
30
|
+
def before_initialization(self):
|
|
31
|
+
self.__n_bits = 8 * self._task.space_dimension
|
|
32
|
+
self.__p_mutation = 1.0 / (float(self.__n_bits) * self._task.space_dimension)
|
|
33
|
+
|
|
34
|
+
def _init_population(self):
|
|
35
|
+
self.__bit_positions = [np.random.randint(
|
|
36
|
+
0, 2, self.__n_bits * self._task.space_dimension
|
|
37
|
+
).tolist() for _ in range(0, self._config.population_size)]
|
|
38
|
+
|
|
39
|
+
self._population = self.__from_bit_positions_to_population__()
|
|
40
|
+
|
|
41
|
+
def __decode__(self, bitstring: list[int]) -> list[float]:
|
|
42
|
+
"""
|
|
43
|
+
Decode a bitstring to a float list.
|
|
44
|
+
:param bitstring: the bitstring to decode
|
|
45
|
+
:return: the decoded float list
|
|
46
|
+
:rtype: list[float]
|
|
47
|
+
"""
|
|
48
|
+
decoded = []
|
|
49
|
+
largest = 2 ** self.__n_bits
|
|
50
|
+
|
|
51
|
+
lb, ub = self._get_bounds()
|
|
52
|
+
for i in range(0, self._task.space_dimension):
|
|
53
|
+
# extract the substring
|
|
54
|
+
start, end = i * self.__n_bits, (i * self.__n_bits) + self.__n_bits
|
|
55
|
+
substring = bitstring[start:end]
|
|
56
|
+
# convert bitstring to a string of chars
|
|
57
|
+
chars = ''.join([str(s) for s in substring])
|
|
58
|
+
# convert string to integer
|
|
59
|
+
integer = int(chars, 2)
|
|
60
|
+
# scale integer to desired range
|
|
61
|
+
value = lb[i] + (integer / largest) * (ub[i] - lb[i])
|
|
62
|
+
# store
|
|
63
|
+
decoded.append(value)
|
|
64
|
+
return decoded
|
|
65
|
+
|
|
66
|
+
def __encode__(self, position: list[float]) -> list[int]:
|
|
67
|
+
"""
|
|
68
|
+
Encode a float list to a bitstring.
|
|
69
|
+
:param position: the float list to encode
|
|
70
|
+
:return: the encoded bitstring
|
|
71
|
+
:rtype: list[int]
|
|
72
|
+
"""
|
|
73
|
+
encoded = []
|
|
74
|
+
|
|
75
|
+
lb, ub = self._get_bounds()
|
|
76
|
+
for i in range(0, self._task.space_dimension):
|
|
77
|
+
# scale integer to desired range
|
|
78
|
+
value = (position[i] - lb[i]) / (ub[i] - lb[i])
|
|
79
|
+
# convert to bitstring
|
|
80
|
+
value = bin(int(value * (2 ** self.__n_bits)))[2:].zfill(self.__n_bits)
|
|
81
|
+
binary_representation = [int(bit) for bit in value]
|
|
82
|
+
# append to the encoded list
|
|
83
|
+
encoded.extend(binary_representation)
|
|
84
|
+
return encoded
|
|
85
|
+
|
|
86
|
+
def __selection__(self, k: int | None = 3) -> list[int]:
|
|
87
|
+
"""
|
|
88
|
+
Select a parent from the population. The selection is based on the fitness of the genes. The lower the cost,
|
|
89
|
+
the higher the fitness. The selection is performed by tournament selection. The tournament size is k. The
|
|
90
|
+
selection is repeated until k parents are selected. The best parent is selected from the k parents. The best is
|
|
91
|
+
the one with the lowest cost. The selection is repeated until the population size is reached. The selection is
|
|
92
|
+
performed with replacement. The same parent can be selected multiple times. The selection is performed with a 3
|
|
93
|
+
tournament selection by default.
|
|
94
|
+
:param k: the tournament size
|
|
95
|
+
:return: the selected parent
|
|
96
|
+
"""
|
|
97
|
+
scores = [gene.cost for gene in self._population]
|
|
98
|
+
|
|
99
|
+
# first random selection
|
|
100
|
+
selection_ix = np.random.randint(len(self.__bit_positions))
|
|
101
|
+
for ix in np.random.randint(0, len(self.__bit_positions), k - 1):
|
|
102
|
+
# check if better (e.g. perform a tournament)
|
|
103
|
+
if scores[ix] < scores[selection_ix]:
|
|
104
|
+
selection_ix = ix
|
|
105
|
+
return self.__bit_positions[selection_ix]
|
|
106
|
+
|
|
107
|
+
def __crossover__(self, parent1: list[int], parent2: list[int]) -> tuple[list[int], list[int]]:
|
|
108
|
+
"""
|
|
109
|
+
Crossover two parents to create two children. The crossover is performed with a probability of px_over. The
|
|
110
|
+
crossover point is selected randomly. The crossover point is not on the end of the string. The crossover is
|
|
111
|
+
performed by default. The children are copies of the parents by default.
|
|
112
|
+
:param parent1: the first parent
|
|
113
|
+
:param parent2: the second parent
|
|
114
|
+
:return: the two children
|
|
115
|
+
:rtype: tuple[list[int], list[int]]
|
|
116
|
+
"""
|
|
117
|
+
# children are copies of parents by default
|
|
118
|
+
c1, c2 = parent1.copy(), parent2.copy()
|
|
119
|
+
# check for recombination
|
|
120
|
+
if np.random.rand() < self._config.px_over:
|
|
121
|
+
# select crossover point that is not on the end of the string
|
|
122
|
+
pt = np.random.randint(1, len(parent1) - 2)
|
|
123
|
+
# perform crossover
|
|
124
|
+
c1 = parent1[:pt] + parent2[pt:]
|
|
125
|
+
c2 = parent1[:pt] + parent2[pt:]
|
|
126
|
+
return c1, c2
|
|
127
|
+
|
|
128
|
+
def __mutation__(self, bitstring: list[int]) -> list[int]:
|
|
129
|
+
"""
|
|
130
|
+
Mutate a bitstring by flipping bits with a probability of pm_mutate. The mutation is performed by default.
|
|
131
|
+
:param bitstring: the bitstring to mutate
|
|
132
|
+
:return: the mutated bitstring
|
|
133
|
+
:rtype: list[int]
|
|
134
|
+
"""
|
|
135
|
+
return [1 - bit if np.random.rand() < self.__p_mutation else bit for bit in bitstring]
|
|
136
|
+
|
|
137
|
+
def __from_bit_positions_to_population__(self) -> list[Gene]:
|
|
138
|
+
"""
|
|
139
|
+
Convert the bit positions to a population. The cost of each gene is calculated. The fitness of each gene is
|
|
140
|
+
calculated.
|
|
141
|
+
:return: the population
|
|
142
|
+
:rtype: list[Gene]
|
|
143
|
+
"""
|
|
144
|
+
def to_gene(bitstring: list[int]) -> Gene:
|
|
145
|
+
position = self.__decode__(bitstring)
|
|
146
|
+
return Gene(**self._init_agent(position).model_dump())
|
|
147
|
+
|
|
148
|
+
return list(map(to_gene, self.__bit_positions))
|
|
149
|
+
|
|
150
|
+
def optimization_step(self):
|
|
151
|
+
# select parents
|
|
152
|
+
selected = [self.__selection__() for _ in range(0, self._config.population_size)]
|
|
153
|
+
|
|
154
|
+
# create the next generation
|
|
155
|
+
children = []
|
|
156
|
+
for i in range(0, self._config.population_size, 2):
|
|
157
|
+
# get selected parents in pairs
|
|
158
|
+
p1, p2 = selected[i], selected[i + 1]
|
|
159
|
+
# crossover
|
|
160
|
+
c1, c2 = self.__crossover__(p1, p2)
|
|
161
|
+
# mutations and store for next generation
|
|
162
|
+
children.append(self.__mutation__(c1))
|
|
163
|
+
children.append(self.__mutation__(c2))
|
|
164
|
+
|
|
165
|
+
# update population
|
|
166
|
+
self.__bit_positions = children
|
|
167
|
+
self._population = self.__from_bit_positions_to_population__()
|