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.
Files changed (173) hide show
  1. pyvolutionary/__init__.py +44 -0
  2. pyvolutionary/abstract.py +325 -0
  3. pyvolutionary/african_vulture/__init__.py +2 -0
  4. pyvolutionary/african_vulture/african_vulture_optimization.py +87 -0
  5. pyvolutionary/african_vulture/models.py +41 -0
  6. pyvolutionary/ant_colony/__init__.py +2 -0
  7. pyvolutionary/ant_colony/ant_colony_optimization.py +80 -0
  8. pyvolutionary/ant_colony/models.py +25 -0
  9. pyvolutionary/aquila/__init__.py +2 -0
  10. pyvolutionary/aquila/aquila_optimization.py +75 -0
  11. pyvolutionary/aquila/models.py +9 -0
  12. pyvolutionary/bacterial_foraging/__init__.py +2 -0
  13. pyvolutionary/bacterial_foraging/bacterial_foraging_optimization.py +152 -0
  14. pyvolutionary/bacterial_foraging/models.py +63 -0
  15. pyvolutionary/bat/__init__.py +2 -0
  16. pyvolutionary/bat/bat_optimization.py +66 -0
  17. pyvolutionary/bat/models.py +66 -0
  18. pyvolutionary/bee_colony/__init__.py +2 -0
  19. pyvolutionary/bee_colony/bee_colony_optimization.py +116 -0
  20. pyvolutionary/bee_colony/models.py +22 -0
  21. pyvolutionary/camel_caravan/__init__.py +2 -0
  22. pyvolutionary/camel_caravan/camel_caravan_optimization.py +96 -0
  23. pyvolutionary/camel_caravan/models.py +61 -0
  24. pyvolutionary/coral_reef/__init__.py +2 -0
  25. pyvolutionary/coral_reef/coral_reef_optimization.py +133 -0
  26. pyvolutionary/coral_reef/models.py +81 -0
  27. pyvolutionary/coyotes/__init__.py +2 -0
  28. pyvolutionary/coyotes/coyotes_optimization.py +96 -0
  29. pyvolutionary/coyotes/models.py +22 -0
  30. pyvolutionary/earthworms/__init__.py +2 -0
  31. pyvolutionary/earthworms/earthworms_optimization.py +124 -0
  32. pyvolutionary/earthworms/models.py +62 -0
  33. pyvolutionary/electromagnetic_field/__init__.py +2 -0
  34. pyvolutionary/electromagnetic_field/electromagnetic_field_optimization.py +58 -0
  35. pyvolutionary/electromagnetic_field/models.py +45 -0
  36. pyvolutionary/elephant_herd/__init__.py +2 -0
  37. pyvolutionary/elephant_herd/elephant_herd_optimization.py +56 -0
  38. pyvolutionary/elephant_herd/models.py +38 -0
  39. pyvolutionary/enums.py +39 -0
  40. pyvolutionary/firefly_swarm/__init__.py +2 -0
  41. pyvolutionary/firefly_swarm/firefly_swarm_optimization.py +76 -0
  42. pyvolutionary/firefly_swarm/models.py +37 -0
  43. pyvolutionary/fireworks/__init__.py +2 -0
  44. pyvolutionary/fireworks/fireworks_optimization.py +96 -0
  45. pyvolutionary/fireworks/models.py +41 -0
  46. pyvolutionary/fish_school_search/__init__.py +2 -0
  47. pyvolutionary/fish_school_search/fish_school_search_optimization.py +142 -0
  48. pyvolutionary/fish_school_search/models.py +27 -0
  49. pyvolutionary/flower_pollination_algorithm/__init__.py +2 -0
  50. pyvolutionary/flower_pollination_algorithm/flower_pollination_algorithm_optimization.py +46 -0
  51. pyvolutionary/flower_pollination_algorithm/models.py +29 -0
  52. pyvolutionary/forest_algorithm/__init__.py +2 -0
  53. pyvolutionary/forest_algorithm/forest_optimization_algorithm.py +121 -0
  54. pyvolutionary/forest_algorithm/models.py +53 -0
  55. pyvolutionary/fox/__init__.py +2 -0
  56. pyvolutionary/fox/fox_optimization.py +43 -0
  57. pyvolutionary/fox/models.py +29 -0
  58. pyvolutionary/genetic_algorithm/__init__.py +2 -0
  59. pyvolutionary/genetic_algorithm/genetic_algorithm_optimization.py +167 -0
  60. pyvolutionary/genetic_algorithm/models.py +21 -0
  61. pyvolutionary/grasshopper/__init__.py +2 -0
  62. pyvolutionary/grasshopper/grasshopper_optimization_algorithm.py +73 -0
  63. pyvolutionary/grasshopper/models.py +29 -0
  64. pyvolutionary/grey_wolf/__init__.py +2 -0
  65. pyvolutionary/grey_wolf/grey_wolf_optimization.py +62 -0
  66. pyvolutionary/grey_wolf/models.py +9 -0
  67. pyvolutionary/harmony_search/__init__.py +2 -0
  68. pyvolutionary/harmony_search/harmony_search_optimization.py +64 -0
  69. pyvolutionary/harmony_search/models.py +29 -0
  70. pyvolutionary/helpers.py +290 -0
  71. pyvolutionary/imperialist_competitive/__init__.py +2 -0
  72. pyvolutionary/imperialist_competitive/classes.py +119 -0
  73. pyvolutionary/imperialist_competitive/imperialist_competitive_optimization.py +198 -0
  74. pyvolutionary/imperialist_competitive/models.py +47 -0
  75. pyvolutionary/invasive_weed/__init__.py +2 -0
  76. pyvolutionary/invasive_weed/invasive_weed_optimization.py +52 -0
  77. pyvolutionary/invasive_weed/models.py +51 -0
  78. pyvolutionary/krill_herd/__init__.py +2 -0
  79. pyvolutionary/krill_herd/krill_herd_optimization.py +179 -0
  80. pyvolutionary/krill_herd/models.py +62 -0
  81. pyvolutionary/levi_jaya_swarm/__init__.py +2 -0
  82. pyvolutionary/levi_jaya_swarm/levy_flight_jaya_swarm_optimization.py +40 -0
  83. pyvolutionary/levi_jaya_swarm/models.py +9 -0
  84. pyvolutionary/models.py +130 -0
  85. pyvolutionary/monarch_butterfly/__init__.py +2 -0
  86. pyvolutionary/monarch_butterfly/models.py +25 -0
  87. pyvolutionary/monarch_butterfly/monarch_butterfly_optimization.py +104 -0
  88. pyvolutionary/mountain_gazelle/__init__.py +2 -0
  89. pyvolutionary/mountain_gazelle/models.py +9 -0
  90. pyvolutionary/mountain_gazelle/mountain_gazelle_optimization.py +118 -0
  91. pyvolutionary/osprey/__init__.py +2 -0
  92. pyvolutionary/osprey/models.py +9 -0
  93. pyvolutionary/osprey/osprey_optimization.py +54 -0
  94. pyvolutionary/particle_swarm/__init__.py +2 -0
  95. pyvolutionary/particle_swarm/models.py +40 -0
  96. pyvolutionary/particle_swarm/particle_swarm_optimization.py +78 -0
  97. pyvolutionary/pathfinder_algorithm/__init__.py +2 -0
  98. pyvolutionary/pathfinder_algorithm/models.py +12 -0
  99. pyvolutionary/pathfinder_algorithm/pathfinder_algorithm_optimization.py +56 -0
  100. pyvolutionary/pelican/__init__.py +2 -0
  101. pyvolutionary/pelican/models.py +9 -0
  102. pyvolutionary/pelican/pelican_optimization.py +46 -0
  103. pyvolutionary/seagull/__init__.py +2 -0
  104. pyvolutionary/seagull/models.py +21 -0
  105. pyvolutionary/seagull/seagull_optimization.py +43 -0
  106. pyvolutionary/siberian_tiger/__init__.py +2 -0
  107. pyvolutionary/siberian_tiger/models.py +9 -0
  108. pyvolutionary/siberian_tiger/siberian_tiger_optimization.py +53 -0
  109. pyvolutionary/tasmanian_devil/__init__.py +2 -0
  110. pyvolutionary/tasmanian_devil/models.py +9 -0
  111. pyvolutionary/tasmanian_devil/tasmanian_devil_optimization.py +69 -0
  112. pyvolutionary/tests/fixtures.py +21 -0
  113. pyvolutionary/tests/test_african_vulture_optimization.py +34 -0
  114. pyvolutionary/tests/test_ant_colony_optimization.py +34 -0
  115. pyvolutionary/tests/test_aquila_optimization.py +27 -0
  116. pyvolutionary/tests/test_bacterial_foraging_optimization.py +37 -0
  117. pyvolutionary/tests/test_bat_optimization.py +34 -0
  118. pyvolutionary/tests/test_bee_colony_optimization.py +32 -0
  119. pyvolutionary/tests/test_camel_caravan_optimization.py +37 -0
  120. pyvolutionary/tests/test_coral_reef_optimization.py +39 -0
  121. pyvolutionary/tests/test_coyotes_optimization.py +32 -0
  122. pyvolutionary/tests/test_earthworms_optimization.py +37 -0
  123. pyvolutionary/tests/test_electromagnetic_field_optimization.py +35 -0
  124. pyvolutionary/tests/test_elephant_herd_optimization.py +34 -0
  125. pyvolutionary/tests/test_firefly_swarm_optimization.py +34 -0
  126. pyvolutionary/tests/test_fireworks_algorithm_optimization.py +36 -0
  127. pyvolutionary/tests/test_fish_school_search_optimization.py +37 -0
  128. pyvolutionary/tests/test_flower_pollination_algorithm_optimization.py +33 -0
  129. pyvolutionary/tests/test_forest_algorithm_optimization.py +36 -0
  130. pyvolutionary/tests/test_foxes_optimization.py +33 -0
  131. pyvolutionary/tests/test_genetic_algorithm_optimization.py +32 -0
  132. pyvolutionary/tests/test_grasshopper_optimization_algorithm.py +33 -0
  133. pyvolutionary/tests/test_grey_wolf_optimization.py +27 -0
  134. pyvolutionary/tests/test_harmony_search_optimization.py +33 -0
  135. pyvolutionary/tests/test_imperialist_competitive_optimization.py +40 -0
  136. pyvolutionary/tests/test_invasive_weed_optimization.py +34 -0
  137. pyvolutionary/tests/test_krill_herd_optimization.py +40 -0
  138. pyvolutionary/tests/test_levy_flight_jaya_swarm_optimization.py +33 -0
  139. pyvolutionary/tests/test_monarch_butterfly_optimization.py +33 -0
  140. pyvolutionary/tests/test_mountain_gazelle_optimization.py +27 -0
  141. pyvolutionary/tests/test_osprey_optimization.py +26 -0
  142. pyvolutionary/tests/test_particle_swarm_optimization.py +34 -0
  143. pyvolutionary/tests/test_pathfinder_algorithm_optimization.py +27 -0
  144. pyvolutionary/tests/test_pelican_optimization.py +27 -0
  145. pyvolutionary/tests/test_seagull_optimization.py +32 -0
  146. pyvolutionary/tests/test_siberian_tiger_optimization.py +27 -0
  147. pyvolutionary/tests/test_task_bounds.py +9 -0
  148. pyvolutionary/tests/test_tasmanian_devil_optimization.py +27 -0
  149. pyvolutionary/tests/test_utils.py +43 -0
  150. pyvolutionary/tests/test_virus_colony_search_optimization.py +33 -0
  151. pyvolutionary/tests/test_walrus_optimization.py +27 -0
  152. pyvolutionary/tests/test_whales_optimization.py +27 -0
  153. pyvolutionary/tests/test_wildebeest_herd_optimization.py +41 -0
  154. pyvolutionary/tests/test_zebra_optimization.py +27 -0
  155. pyvolutionary/utils.py +94 -0
  156. pyvolutionary/virus_colony_search/__init__.py +2 -0
  157. pyvolutionary/virus_colony_search/models.py +29 -0
  158. pyvolutionary/virus_colony_search/virus_colony_search_optimization.py +99 -0
  159. pyvolutionary/walrus/__init__.py +2 -0
  160. pyvolutionary/walrus/models.py +9 -0
  161. pyvolutionary/walrus/walrus_optimization.py +43 -0
  162. pyvolutionary/whales/__init__.py +2 -0
  163. pyvolutionary/whales/models.py +12 -0
  164. pyvolutionary/whales/whales_optimization.py +56 -0
  165. pyvolutionary/wildebeest_herd/__init__.py +2 -0
  166. pyvolutionary/wildebeest_herd/models.py +94 -0
  167. pyvolutionary/wildebeest_herd/wildebeest_herd_optimization.py +143 -0
  168. pyvolutionary/zebra/__init__.py +2 -0
  169. pyvolutionary/zebra/models.py +9 -0
  170. pyvolutionary/zebra/zebra_optimization.py +53 -0
  171. pyvolutionary-1.0.1.dist-info/METADATA +350 -0
  172. pyvolutionary-1.0.1.dist-info/RECORD +173 -0
  173. pyvolutionary-1.0.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,44 @@
1
+ from .african_vulture import *
2
+ from .ant_colony import *
3
+ from .aquila import *
4
+ from .bacterial_foraging import *
5
+ from .bat import *
6
+ from .bee_colony import *
7
+ from .camel_caravan import *
8
+ from .coral_reef import *
9
+ from .coyotes import *
10
+ from .earthworms import *
11
+ from .electromagnetic_field import *
12
+ from .elephant_herd import *
13
+ from .firefly_swarm import *
14
+ from .fireworks import *
15
+ from .fish_school_search import *
16
+ from .flower_pollination_algorithm import *
17
+ from .forest_algorithm import *
18
+ from .fox import *
19
+ from .genetic_algorithm import *
20
+ from .grasshopper import *
21
+ from .grey_wolf import *
22
+ from .harmony_search import *
23
+ from .imperialist_competitive import *
24
+ from .invasive_weed import *
25
+ from .krill_herd import *
26
+ from .levi_jaya_swarm import *
27
+ from .monarch_butterfly import *
28
+ from .mountain_gazelle import *
29
+ from .osprey import *
30
+ from .particle_swarm import *
31
+ from .pathfinder_algorithm import *
32
+ from .pelican import *
33
+ from .seagull import *
34
+ from .siberian_tiger import *
35
+ from .tasmanian_devil import *
36
+ from .virus_colony_search import *
37
+ from .walrus import *
38
+ from .whales import *
39
+ from .wildebeest_herd import *
40
+ from .zebra import *
41
+ from .enums import TaskType
42
+ from .models import OptimizationResult, Population, Agent, Task, ContinuousVariable, DiscreteVariable
43
+ from .helpers import best_agent, worst_agent, best_agents, worst_agents, special_agents
44
+ from .utils import plot, animate, agent_trend, best_agent_trend
@@ -0,0 +1,325 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Generic, Any
3
+ import numpy as np
4
+ import concurrent.futures as parallel
5
+
6
+ from .enums import ModeSolver
7
+ from .helpers import (
8
+ average_fitness,
9
+ calculate_fitness,
10
+ sort_and_trim,
11
+ sort_by_cost,
12
+ special_agents,
13
+ )
14
+ from .models import OptimizationResult, Population, T, BaseOptimizationConfig, Task, TaskType, Agent, ContinuousVariable
15
+
16
+
17
+ class OptimizationAbstract(ABC, Generic[T]):
18
+ """
19
+ This class is an abstract for optimization algorithms.
20
+ """
21
+
22
+ def __init__(self, config: BaseOptimizationConfig, debug: bool | None = False):
23
+ """
24
+ The constructor of the class.
25
+ :param config: The configuration of the optimization algorithm.
26
+ :param debug: Whether to print debug messages or not.
27
+ """
28
+ self._config = config
29
+ self._debug = debug
30
+ self._mode: ModeSolver = ModeSolver.SERIAL
31
+ self._workers: int = 4
32
+ self._task: Task | None = None
33
+ self._population: list[T] = []
34
+ self._best_agent: T | None = None
35
+ self._worst_agent: T | None = None
36
+ self._cycles = 1
37
+
38
+ @abstractmethod
39
+ def optimization_step(self):
40
+ pass
41
+
42
+ def before_initialization(self):
43
+ pass
44
+
45
+ def after_initialization(self):
46
+ pass
47
+
48
+ def _get_bounds(self) -> tuple[np.ndarray, np.ndarray]:
49
+ """
50
+ Get the lower and upper bounds of the search space.
51
+ :return: the lower and upper bounds
52
+ :rtype: tuple[np.ndarray, np.ndarray]
53
+ """
54
+ lb, ub = zip(*[
55
+ (v.lower_bound, v.upper_bound) if isinstance(v, ContinuousVariable) else (0, 0)
56
+ for v in self._task.variables
57
+ ])
58
+ return np.array(lb), np.array(ub)
59
+
60
+ def _init_position(self, position: list[float] | np.ndarray | None = None) -> list[float]:
61
+ """
62
+ This method initializes the position of the agent of the optimization algorithm. The position is randomly
63
+ generated if it is not provided.
64
+ :param position: the position to initialize
65
+ :return: the initialized position
66
+ :rtype: list[float]
67
+ """
68
+ position = position.tolist() if isinstance(position, np.ndarray) else position
69
+ position = self._correct_position(position if position is not None else self._uniform_position())
70
+
71
+ return position
72
+
73
+ def _uniform_position(self) -> np.ndarray:
74
+ """
75
+ This method generates a uniform random position in the search space.
76
+ :return: the random position
77
+ :rtype: np.ndarray
78
+ """
79
+ return np.array([v.randomize() for v in self._task.variables])
80
+
81
+ def _random_position(self) -> np.ndarray:
82
+ """
83
+ This method generates a random position in the search space.
84
+ :return: the random position
85
+ :rtype: np.ndarray
86
+ """
87
+ lb, _ = self._get_bounds()
88
+ return np.where(
89
+ isinstance(self._task.variables, ContinuousVariable),
90
+ np.random.random() * self._bandwidth() + lb,
91
+ self._uniform_position()
92
+ )
93
+
94
+ def _increase_position(self, position: list[float], scale_factor: float | None = None) -> np.ndarray:
95
+ """
96
+ This method increases the position in the search space.
97
+ :param position: the position to increase
98
+ :param scale_factor: the scale factor
99
+ :return: the increased position
100
+ :rtype: np.ndarray
101
+ """
102
+ scale_factor = scale_factor if scale_factor is not None else 1.0
103
+ return np.where(
104
+ isinstance(self._task.variables, ContinuousVariable),
105
+ np.array(position) + self._random_position() / scale_factor,
106
+ self._uniform_position()
107
+ )
108
+
109
+ def _uniform_coordinates(self, dimensions: int | list[int]) -> np.ndarray:
110
+ """
111
+ This method generates uniform random coordinates in the search space.
112
+ :param dimensions: the dimensions to generate
113
+ :return: the random coordinates
114
+ :rtype: np.ndarray
115
+ """
116
+ return self._uniform_position()[dimensions]
117
+
118
+ def _bandwidth(self) -> np.ndarray:
119
+ """
120
+ This method calculates the bandwidth in the search space for each dimension.
121
+ :return: the bandwidth
122
+ :rtype: np.ndarray
123
+ """
124
+ lb, ub = self._get_bounds()
125
+ return ub - lb
126
+
127
+ def _sum_bounds(self) -> np.ndarray:
128
+ """
129
+ This method calculates the sum of the lower and upper bounds in the search space for each dimension.
130
+ :return: the sum of the lower and upper bounds
131
+ :rtype: np.ndarray
132
+ """
133
+ lb, ub = self._get_bounds()
134
+ return lb + ub
135
+
136
+ def _fcn(self, x: list[float] | np.ndarray) -> float:
137
+ """
138
+ This method evaluates the objective function.
139
+ :param x: the position to evaluate
140
+ """
141
+ return self._task.objective_function(x) \
142
+ if self._task.minmax == TaskType.MIN else -self._task.objective_function(x)
143
+
144
+ def _init_agent(self, position: list[float] | np.ndarray | None = None) -> Agent:
145
+ """
146
+ This method initializes the agent of the optimization algorithm. The position is randomly generated if it is
147
+ not provided. The other properties of the agent.
148
+ """
149
+ position = self._init_position(position)
150
+ cost = self._fcn(position)
151
+ return Agent(position=position, cost=cost, fitness=calculate_fitness(cost, self._task.minmax))
152
+
153
+ def _init_population(self):
154
+ """
155
+ This method initializes the population of the optimization algorithm.
156
+ """
157
+ # Serial mode
158
+ if self._mode == ModeSolver.SERIAL:
159
+ self._population = [self._init_agent() for _ in range(0, self._config.population_size)]
160
+ return
161
+
162
+ # Parallel mode
163
+ pop = []
164
+ pool = parallel.ThreadPoolExecutor if self._mode == ModeSolver.THREAD else parallel.ProcessPoolExecutor
165
+ with pool(self._workers) as executor:
166
+ executors = [executor.submit(self._init_agent) for _ in range(0, self._config.population_size)]
167
+ for i in parallel.as_completed(executors):
168
+ pop.append(i.result())
169
+ self._population = pop
170
+
171
+ def _is_valid_position(self, position: list[float] | np.ndarray) -> bool:
172
+ """
173
+ Check whether the position is valid or not.
174
+ :param position: the position to check
175
+ :return: whether the position is valid or not
176
+ """
177
+ lb, ub = self._get_bounds()
178
+ return np.all(np.less_equal(lb, position)) and np.all(np.less_equal(position, ub))
179
+
180
+ def _greedy_select_population(self, new_population: list[T]):
181
+ """
182
+ Perform the greedy selection between the current population and the new one. Both are sorted by cost in
183
+ ascending order. The greedy selection is performed by comparing the costs of each agent in the current
184
+ population with the corresponding agent in the new population. The one with the lowest cost is kept.
185
+ The new population is so created.
186
+ :param new_population: the new population
187
+ """
188
+ sort_by_cost(self._population)
189
+ sort_by_cost(new_population)
190
+
191
+ # Serial mode
192
+ if self._mode == ModeSolver.SERIAL:
193
+ self._population = [
194
+ self._greedy_select_agent(agent, new_population[idx]) for idx, agent in enumerate(self._population)
195
+ ]
196
+ return
197
+
198
+ # Parallel mode
199
+ pop = []
200
+ pool = parallel.ThreadPoolExecutor if self._mode == ModeSolver.THREAD else parallel.ProcessPoolExecutor
201
+ with pool(self._workers) as executor:
202
+ executors = [executor.submit(
203
+ self._greedy_select_agent, agent, new_population[idx]
204
+ ) for idx, agent in enumerate(self._population)]
205
+ for i in parallel.as_completed(executors):
206
+ pop.append(i.result())
207
+ self._population = pop
208
+
209
+ def _greedy_select_agent(self, agent: T, new_agent: T) -> T:
210
+ """
211
+ Perform the greedy selection between the current agent and the new one. The greedy selection is performed by
212
+ comparing the costs of each agent. The one with the lowest cost is kept.
213
+ :param agent: the current agent
214
+ :param new_agent: the new agent
215
+ :return: the best agent
216
+ """
217
+ agent_copy = agent.model_copy()
218
+ return new_agent if new_agent.cost < agent_copy.cost else agent_copy
219
+
220
+ def _correct_position(self, position: list[float] | np.ndarray) -> list[Any]:
221
+ """
222
+ Correct the solution if it is outside the bounds by setting the solution to the closest bound. This function is
223
+ used to correct the solution after the position update.
224
+ :param position: the position
225
+ :return: the corrected position
226
+ :rtype: list[Any]
227
+ """
228
+ variables = self._task.variables
229
+ return [
230
+ np.clip(p, variables[idx].lower_bound, variables[idx].upper_bound)
231
+ if isinstance(variables[idx], ContinuousVariable) else p
232
+ for idx, p in enumerate(position)
233
+ ]
234
+
235
+ def _extend_and_trim_population(self, new_population: list[T]):
236
+ """
237
+ Extend the population with the new population and trim the population to the population size if the population
238
+ size is exceeded. The population is sorted by cost in ascending order.
239
+ :param new_population: the new population
240
+ """
241
+ self._population.extend(new_population)
242
+ self._population = sort_and_trim(self._population, self._config.population_size)
243
+
244
+ def optimize(self, task: Task, mode: str | None = None, workers: int | None = None) -> OptimizationResult:
245
+ """
246
+ This method optimizes the given objective function. At the beginning, a random population is generated and then
247
+ the optimization algorithm is executed. The returned result contains the evolution of the population and the
248
+ best solution found. A list of errors per cycle is also returned in the result itself.
249
+ :param task: the task to optimize
250
+ :param mode, the mode of the solver; possible values are "serial", "thread" and "process"
251
+ :param workers: the number of workers to use, in case of parallel or thread execution
252
+ :return: the result of the optimization
253
+ :rtype: OptimizationResult
254
+ """
255
+ np.random.seed(task.seed)
256
+
257
+ evolution: list[Population] = []
258
+
259
+ self._task = task
260
+ if workers is not None:
261
+ if workers <= 0:
262
+ raise ValueError("Invalid number of workers. It must be greater than 0")
263
+ self._workers = workers
264
+
265
+ if mode is not None:
266
+ try:
267
+ self._mode = ModeSolver(mode)
268
+ except ValueError:
269
+ raise ValueError("Invalid mode. Possible values are 'serial', 'thread' and 'process'")
270
+
271
+ self.before_initialization()
272
+ self._init_population()
273
+ self.after_initialization()
274
+
275
+ evolution.append(Population(agents=self._population, task_type=task.minmax))
276
+ (self._best_agent, ), (self._worst_agent, ) = special_agents(self._population, n_best=1, n_worst=1)
277
+
278
+ errors: list[float] = []
279
+ while True:
280
+ self.optimization_step()
281
+ # append the current population to the evolution, being sure that costs and fitness are updated
282
+ evolution.append(Population(agents=self._population, task_type=task.minmax))
283
+
284
+ (self._best_agent, ), (self._worst_agent, ) = special_agents(self._population, n_best=1, n_worst=1)
285
+
286
+ # stop when the error is below the error criteria or when the maximum number of cycles is reached
287
+ error, fitness, has_to_stop = self.__should_stop__()
288
+ errors.append(error)
289
+ if self._debug:
290
+ print(f"Cycle {self._cycles} - Best position {self._best_agent.position}, "
291
+ f"cost {self._best_agent.cost if task.minmax == TaskType.MIN else -self._best_agent.cost} - "
292
+ f"Average fitness {fitness}, fitness error {error}")
293
+ if has_to_stop:
294
+ break
295
+
296
+ self._cycles += 1
297
+
298
+ if has_to_stop and self._debug:
299
+ print("Maximum number of cycles reached" if self._cycles >= self._config.max_cycles else
300
+ f"Error criteria reached - Fitness error: {error}")
301
+
302
+ return OptimizationResult(
303
+ evolution=evolution, rates=errors, best_solution=self._best_agent, task_type=task.minmax
304
+ )
305
+
306
+ def __should_stop__(self) -> tuple[float, float, bool]:
307
+ """
308
+ Check whether the optimization algorithm has to stop or not based on the error criteria and the current cycle.
309
+ :return: a tuple containing the current error, the average fitness and a boolean indicating whether the
310
+ algorithm has to stop or not
311
+ :rtype: tuple[float, float, bool]
312
+ """
313
+ # Get the optimal population
314
+ fitness_error = self._config.fitness_error
315
+ avg_fit = average_fitness(self._population)
316
+ cycle = self._cycles
317
+ max_cycles = self._config.max_cycles
318
+
319
+ current_error = abs(1 - avg_fit)
320
+
321
+ # Stop when the error is below the error criteria or when the maximum number of cycles is reached
322
+ if fitness_error is None:
323
+ return current_error, avg_fit, cycle >= max_cycles
324
+
325
+ return current_error, avg_fit, current_error <= fitness_error or cycle >= max_cycles
@@ -0,0 +1,2 @@
1
+ from .models import AfricanVulture, AfricanVultureOptimizationConfig
2
+ from .african_vulture_optimization import AfricanVultureOptimization
@@ -0,0 +1,87 @@
1
+ import numpy as np
2
+
3
+ from ..helpers import (
4
+ best_agents,
5
+ get_levy_flight_step,
6
+ parse_obj_doc, # type: ignore
7
+ )
8
+ from ..abstract import OptimizationAbstract
9
+ from .models import AfricanVulture, AfricanVultureOptimizationConfig
10
+
11
+
12
+ class AfricanVultureOptimization(OptimizationAbstract):
13
+ """
14
+ Implementation of the African Vulture Optimization algorithm.
15
+
16
+ Args:
17
+ config (AfricanVultureOptimizationConfig): an instance of AfricanVultureOptimizationConfig class.
18
+ {parse_obj_doc(AfricanVultureOptimizationConfig)}
19
+
20
+ Bibliography
21
+ ----------
22
+ [1] Abdollahzadeh, B., Gharehchopogh, F. S., & Mirjalili, S. (2021). African vultures optimization algorithm: A new
23
+ nature-inspired metaheuristic algorithm for global optimization problems. Computers & Industrial Engineering,
24
+ 158, 107408.
25
+ """
26
+ def __init__(self, config: AfricanVultureOptimizationConfig, debug: bool | None = False):
27
+ super().__init__(config, debug)
28
+
29
+ def __exploration_position__(self, rand_pos: np.ndarray, position: np.ndarray, F: float) -> np.ndarray:
30
+ p1, _, _ = self._config.p
31
+ if np.random.random() < p1:
32
+ return rand_pos - (np.abs((2 * np.random.random()) * rand_pos - position)) * F
33
+
34
+ return rand_pos - F + np.random.random() * self._uniform_position()
35
+
36
+ def __exploration_position_phase1__(
37
+ self, best_list: list[AfricanVulture], rand_pos: np.ndarray, position: np.ndarray, F: float
38
+ ) -> np.ndarray:
39
+ _, p2, _ = self._config.p
40
+
41
+ best_x1 = np.array(best_list[0].position)
42
+ best_x2 = np.array(best_list[1].position)
43
+ if np.random.random() < p2:
44
+ A = best_x1 - ((best_x1 * position) / (best_x1 - position ** 2)) * F
45
+ B = best_x2 - ((best_x2 * position) / (best_x2 - position ** 2)) * F
46
+ return (A + B) / 2
47
+
48
+ return rand_pos - np.abs(rand_pos - position) * F * get_levy_flight_step(
49
+ beta=1.5, multiplier=1., size=self._task.space_dimension, case=-1
50
+ )
51
+
52
+ def __exploration_position_phase2__(self, rand_pos: np.ndarray, position: np.ndarray, F: float) -> np.ndarray:
53
+ _, _, p3 = self._config.p
54
+
55
+ if np.random.random() < p3:
56
+ return (
57
+ np.abs(2 * np.random.random() * rand_pos - position)
58
+ ) * (F + np.random.random()) - (rand_pos - position)
59
+
60
+ s1 = np.random.random() * position * np.cos(position)
61
+ s2 = np.random.random() * position * np.sin(position)
62
+ return rand_pos * (1 - ((s1 + s2) / (2 * np.pi)))
63
+
64
+ def optimization_step(self):
65
+ cycle_ratio = self._cycles / self._config.max_cycles
66
+ alpha = self._config.alpha
67
+
68
+ a = np.random.uniform(-2, 2) * (
69
+ (np.sin((np.pi / 2) * cycle_ratio) ** self._config.gamma) + np.cos((np.pi / 2) * cycle_ratio) - 1
70
+ )
71
+ ppp = (2 * np.random.random() + 1) * (1 - cycle_ratio) + a
72
+ best_list = best_agents(self._population, n_best=2)
73
+
74
+ for idx in range(0, self._config.population_size):
75
+ position = np.array(self._population[idx].position)
76
+
77
+ F = ppp * (2 * np.random.random() - 1)
78
+ rand_pos = np.array(best_list[np.random.choice([0, 1], p=[alpha, 1 - alpha])].position)
79
+
80
+ if np.abs(F) >= 1: # Exploration
81
+ pos_new = self.__exploration_position__(rand_pos, position, F)
82
+ elif np.abs(F) < 0.5: # Exploitation Phase 1
83
+ pos_new = self.__exploration_position_phase1__(best_list, rand_pos, position, F)
84
+ else: # Exploitation Phase 2
85
+ pos_new = self.__exploration_position_phase2__(rand_pos, position, F)
86
+
87
+ self._population[idx] = self._greedy_select_agent(self._population[idx], self._init_agent(pos_new))
@@ -0,0 +1,41 @@
1
+ from pydantic import field_validator
2
+
3
+ from ..models import Agent, BaseOptimizationConfig
4
+
5
+
6
+ class AfricanVulture(Agent):
7
+ pass
8
+
9
+
10
+ class AfricanVultureOptimizationConfig(BaseOptimizationConfig):
11
+ """
12
+ Configuration class of the African Vulture Optimization algorithm.
13
+ p (list[float]): (0, 1), (0, 1), (0, 1), list of 3 floats in [0, 1] that represent the probabilities of each of
14
+ the 3 status transition (see paper).\n
15
+ alpha (float): (0, 1), the alpha parameter of the algorithm, i.e. probability of 1st best (see paper).\n
16
+ gamma (float): (0, 5), the gamma parameter of the algorithm (see paper).
17
+ """
18
+ p: list[float]
19
+ alpha: float
20
+ gamma: float
21
+
22
+ @field_validator("p")
23
+ def correct_p(cls, v):
24
+ if len(v) != 3:
25
+ raise ValueError(f"\"p\" must be a list of 3 floats. Got {v}")
26
+ # check if any of v is not in [0, 1]
27
+ if any([not (0 < x < 1) for x in v]):
28
+ raise ValueError(f"\"p\" must be a list of 3 floats in [0, 1]. Got {v}")
29
+ return v
30
+
31
+ @field_validator("alpha")
32
+ def correct_alpha(cls, v):
33
+ if not (0 < v < 1):
34
+ raise ValueError(f"\"alpha\" must be a float in (0, 1). Got {v}")
35
+ return v
36
+
37
+ @field_validator("gamma")
38
+ def correct_gamma(cls, v):
39
+ if not (0 < v < 5):
40
+ raise ValueError(f"\"gamma\" must be a float in (0, 5). Got {v}")
41
+ return v
@@ -0,0 +1,2 @@
1
+ from .models import Ant, AntColonyOptimizationConfig
2
+ from .ant_colony_optimization import AntColonyOptimization
@@ -0,0 +1,80 @@
1
+ import numpy as np
2
+
3
+ from ..helpers import (
4
+ roulette_wheel_index,
5
+ parse_obj_doc, # type: ignore
6
+ )
7
+ from ..abstract import OptimizationAbstract
8
+ from .models import Ant, AntColonyOptimizationConfig
9
+
10
+
11
+ class AntColonyOptimization(OptimizationAbstract):
12
+ """
13
+ Implementation of the Ant Colony Optimization algorithm.
14
+
15
+ Args:
16
+ config (AntColonyOptimizationConfig): an instance of AntColonyOptimizationConfig class.
17
+ {parse_obj_doc(AntColonyOptimizationConfig)}
18
+
19
+ Bibliography
20
+ ----------
21
+ [1] A. Colorni, M. Dorigo et V. Maniezzo, Distributed Optimization by Ant Colonies, actes de la première conférence
22
+ européenne sur la vie artificielle, Paris, France, Elsevier Publishing, 134-142, 1991.
23
+ [2] M. Dorigo, Optimization, Learning and Natural Algorithms, PhD thesis, Politecnico di Milano, Italy, 1992.
24
+ """
25
+ def __init__(self, config: AntColonyOptimizationConfig, debug: bool | None = False):
26
+ super().__init__(config, debug)
27
+
28
+ def __selection_probability__(self) -> np.ndarray:
29
+ """
30
+ Compute the selection probability of each ant in the population.
31
+ :return: a numpy array of the selection probability of each ant in the population.
32
+ :rtype: np.ndarray
33
+ """
34
+ ranks = np.array([idx for idx in range(1, self._config.population_size + 1)])
35
+ Q = self._config.intent_factor * self._config.population_size
36
+ weights = 1 / (np.sqrt(2 * np.pi) * Q) * np.exp(-0.5 * ((ranks - 1) / Q) ** 2)
37
+ return weights / np.sum(weights) # Normalize to find the probability.
38
+
39
+ def __compute_sigmas__(self) -> np.ndarray:
40
+ """
41
+ Compute the sigmas of each ant in the population. Each sigma is computed as follows:
42
+ sigma_i = zeta * sum(|x_j - x_i|) / (N - 1)
43
+ where:
44
+ x_i: the position of the i-th ant.\n
45
+ x_j: the position of the j-th ant.\n
46
+ zeta: the zeta parameter of the colony.\n
47
+ N: the population size.
48
+ :return: a numpy array of the sigmas of each ant in the population.
49
+ :rtype: np.ndarray
50
+ """
51
+ positions = np.array([ant.position for ant in self._population])
52
+ pop_size = self._config.population_size
53
+
54
+ sigmas = []
55
+ for idx in range(0, self._config.population_size):
56
+ M = np.repeat(np.array(self._population[idx].position).reshape((1, -1)), pop_size, axis=0)
57
+ sigmas.append(
58
+ self._config.zeta * np.sum(np.abs(positions - M), axis=0) / (pop_size - 1)
59
+ )
60
+ return np.array(sigmas)
61
+
62
+ def __generate_new_ants__(self, weights: np.ndarray, sigmas: np.ndarray) -> list[Ant]:
63
+ def generate_coordinate(j: int) -> float:
64
+ rdx = roulette_wheel_index(weights)
65
+ return float(self._population[rdx].position[j] + np.random.normal() * sigmas[rdx, j])
66
+
67
+ # Generate Samples
68
+ return [Ant(**self._init_agent(
69
+ list(map(generate_coordinate, range(0, self._task.space_dimension)))
70
+ ).model_dump()) for _ in range(0, self._config.archive_size)]
71
+
72
+ def optimization_step(self):
73
+ weights = self.__selection_probability__()
74
+ sigmas = self.__compute_sigmas__()
75
+
76
+ # Generate new ants
77
+ new_ants = self.__generate_new_ants__(weights, sigmas)
78
+
79
+ self._extend_and_trim_population(new_ants)
80
+
@@ -0,0 +1,25 @@
1
+ from pydantic import field_validator
2
+
3
+ from ..models import Agent, BaseOptimizationConfig
4
+
5
+
6
+ class Ant(Agent):
7
+ pass
8
+
9
+
10
+ class AntColonyOptimizationConfig(BaseOptimizationConfig):
11
+ """
12
+ Configuration class of the Ant Colony Optimization algorithm.
13
+ archive_size (int): the size of the archive of the colony.\n
14
+ intent_factor (float): [0, 1), the intent factor of the colony.\n
15
+ zeta (float): the zeta parameter of the colony.
16
+ """
17
+ archive_size: int
18
+ intent_factor: float
19
+ zeta: float
20
+
21
+ @field_validator("intent_factor")
22
+ def correct_intent_factor(cls, v):
23
+ if not 0 <= v < 1:
24
+ raise ValueError(f"\"intent_factor\" must be a positive float lower than 1. Got {v}")
25
+ return v
@@ -0,0 +1,2 @@
1
+ from .models import Aquila, AquilaOptimizationConfig
2
+ from .aquila_optimization import AquilaOptimization