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,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,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,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
|