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,75 @@
|
|
|
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 AquilaOptimizationConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AquilaOptimization(OptimizationAbstract):
|
|
12
|
+
"""
|
|
13
|
+
Implementation of the Aquila Optimization algorithm.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
config (AquilaOptimizationConfig): an instance of AquilaOptimizationConfig class.
|
|
17
|
+
{parse_obj_doc(AquilaOptimizationConfig)}
|
|
18
|
+
|
|
19
|
+
Bibliography
|
|
20
|
+
----------
|
|
21
|
+
[1] Abualigah, L., Yousri, D., Abd Elaziz, M., Ewees, A.A., Al-Qaness, M.A. and Gandomi, A.H., 2021.
|
|
22
|
+
Aquila optimizer: a novel meta-heuristic optimization algorithm. Computers & Industrial Engineering, 157,
|
|
23
|
+
p.107250.
|
|
24
|
+
"""
|
|
25
|
+
def __init__(self, config: AquilaOptimizationConfig, debug: bool | None = False):
|
|
26
|
+
super().__init__(config, debug)
|
|
27
|
+
|
|
28
|
+
def __move__(self, idx: int, pos: list[float]) -> np.ndarray:
|
|
29
|
+
"""
|
|
30
|
+
Move the agent to a new position. This method is called by the optimization step. It is not intended to be
|
|
31
|
+
called directly.
|
|
32
|
+
:param idx: the index of the agent in the population.
|
|
33
|
+
:param pos: the current position of the agent.
|
|
34
|
+
:return: the new position of the agent.
|
|
35
|
+
:rtype: np.ndarray
|
|
36
|
+
"""
|
|
37
|
+
alpha = delta = 0.1
|
|
38
|
+
g1 = 2 * np.random.random() - 1 # Eq. 16
|
|
39
|
+
g2 = 2 * (1 - self._cycles / self._config.max_cycles) # Eq. 17
|
|
40
|
+
dim_list = np.array(list(range(1, self._task.space_dimension + 1)))
|
|
41
|
+
miu = 0.00565
|
|
42
|
+
r0 = 10
|
|
43
|
+
r = r0 + miu * dim_list
|
|
44
|
+
w = 0.005
|
|
45
|
+
phi0 = 3 * np.pi / 2
|
|
46
|
+
phi = -w * dim_list + phi0
|
|
47
|
+
x = r * np.sin(phi) # Eq.(9)
|
|
48
|
+
y = r * np.cos(phi) # Eq.(10)
|
|
49
|
+
QF = self._cycles ** ((2 * np.random.random() - 1) / (1 - self._config.max_cycles) ** 2) # Eq.(15)
|
|
50
|
+
|
|
51
|
+
best_position = np.array(self._best_agent.position)
|
|
52
|
+
pop_size = self._config.population_size
|
|
53
|
+
|
|
54
|
+
pos = np.array(pos)
|
|
55
|
+
|
|
56
|
+
x_mean = np.mean(np.array([agent.cost for agent in self._population]), axis=0)
|
|
57
|
+
levy_step = get_levy_flight_step(beta=1.5, multiplier=1.0, case=-1)
|
|
58
|
+
if self._cycles <= (2 / 3) * self._config.max_cycles: # Eq. 3, 4
|
|
59
|
+
jdx = np.random.choice(list(set(range(0, pop_size)) - {idx}))
|
|
60
|
+
return best_position * (1 - self._cycles / self._config.max_cycles) + np.random.random() * (
|
|
61
|
+
x_mean - best_position
|
|
62
|
+
) if np.random.random() < 0.5 else best_position * levy_step + (
|
|
63
|
+
np.array(self._population[jdx].position) + np.random.random() * (y - x)
|
|
64
|
+
) # Eq. 5
|
|
65
|
+
|
|
66
|
+
return alpha * (best_position - x_mean) - (
|
|
67
|
+
np.random.random() * self._random_position() * delta
|
|
68
|
+
) if np.random.random() < 0.5 else QF * best_position - (
|
|
69
|
+
g2 * pos * np.random.random()
|
|
70
|
+
) - g2 * levy_step + np.random.random() * g1 # Eq. 13, 14
|
|
71
|
+
|
|
72
|
+
def optimization_step(self):
|
|
73
|
+
for idx, aquila in enumerate(self._population):
|
|
74
|
+
agent = self._init_agent(self._correct_position(self.__move__(idx, aquila.position)))
|
|
75
|
+
self._population[idx] = self._greedy_select_agent(agent, aquila)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..helpers import parse_obj_doc # type: ignore
|
|
4
|
+
from ..abstract import OptimizationAbstract
|
|
5
|
+
from .models import Cell, BacterialForagingOptimizationConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BacterialForagingOptimization(OptimizationAbstract):
|
|
9
|
+
"""
|
|
10
|
+
Implementation of the Bacterial Foraging Optimization algorithm, in its Adaptive version.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
config (BacterialForagingOptimizationConfig): an instance of BacterialForagingOptimization class.
|
|
14
|
+
{parse_obj_doc(AdaptiveBacterialForagingOptimizationConfig)}
|
|
15
|
+
|
|
16
|
+
Bibliography
|
|
17
|
+
----------
|
|
18
|
+
[1] Passino, K.M., 2002. Biomimicry of bacterial foraging for distributed optimization and control.
|
|
19
|
+
IEEE control systems magazine, 22(3), pp.52-67.
|
|
20
|
+
[2] Nguyen, T., Nguyen, B.M. and Nguyen, G., 2019, April. Building resource auto-scaler with functional-link
|
|
21
|
+
neural network and adaptive bacterial foraging optimization. In International Conference on
|
|
22
|
+
Theory and Applications of Models of Computation (pp. 501-517). Springer, Cham.
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, config: BacterialForagingOptimizationConfig, debug: bool | None = False):
|
|
25
|
+
super().__init__(config, debug)
|
|
26
|
+
self.__C_s: np.ndarray | None = None
|
|
27
|
+
self.__C_e: np.ndarray | None = None
|
|
28
|
+
|
|
29
|
+
def before_initialization(self):
|
|
30
|
+
self.__C_s = self._config.C_s * self._bandwidth()
|
|
31
|
+
self.__C_e = self._config.C_e * self._bandwidth()
|
|
32
|
+
|
|
33
|
+
def _init_agent(self, position: list[float] | np.ndarray | None = None) -> Cell:
|
|
34
|
+
agent = super()._init_agent(position)
|
|
35
|
+
return Cell(**agent.model_dump(), local_best=agent.position.copy(), local_cost=agent.cost)
|
|
36
|
+
|
|
37
|
+
def __update_step_size__(self, cell: Cell) -> float:
|
|
38
|
+
"""
|
|
39
|
+
Update the step size of the cell. The step size is computed as follows:\n
|
|
40
|
+
s / cell.nutrients if cell.nutrients > 0 else s, where:\n
|
|
41
|
+
s = C_s - (C_s - C_e) * cell.cost / total_costs\n
|
|
42
|
+
:param cell: the cell to update the step size.
|
|
43
|
+
:return: the step size.
|
|
44
|
+
:rtype: float
|
|
45
|
+
"""
|
|
46
|
+
total_costs = np.sum([agent.cost for agent in self._population])
|
|
47
|
+
step_size = self.__C_s - (self.__C_s - self.__C_e) * cell.cost / total_costs
|
|
48
|
+
return step_size / cell.nutrients if cell.nutrients > 0 else step_size
|
|
49
|
+
|
|
50
|
+
def __tumble_cell__(self, cell: Cell, step_size: float) -> Cell:
|
|
51
|
+
"""
|
|
52
|
+
Tumble the cell. The new position is computed as follows:\n
|
|
53
|
+
new_position = cell.position + step_size * delta_i / delta, where:\n
|
|
54
|
+
delta_i = (best_agent.position - cell.position) + (cell.local_best - cell.position)\n
|
|
55
|
+
delta = sqrt(delta_i * delta_i.T)\n
|
|
56
|
+
:param cell: the cell to tumble.
|
|
57
|
+
:param step_size: the step size.
|
|
58
|
+
:return: the new cell.
|
|
59
|
+
:rtype: Cell
|
|
60
|
+
"""
|
|
61
|
+
position = np.array(cell.position)
|
|
62
|
+
delta_i = (np.array(self._best_agent.position) - position) + (np.array(cell.local_best) - position)
|
|
63
|
+
delta = np.sqrt(np.dot(delta_i, delta_i.T))
|
|
64
|
+
if delta == 0:
|
|
65
|
+
delta_i = np.random.uniform(-1.0, 1.0, self._task.space_dimension)
|
|
66
|
+
delta = np.sqrt(np.dot(delta_i, delta_i.T))
|
|
67
|
+
|
|
68
|
+
return self._init_agent(self._correct_position(cell.position + step_size * delta_i / delta))
|
|
69
|
+
|
|
70
|
+
def __swim__(self, cell: Cell, step_size: float) -> Cell:
|
|
71
|
+
"""
|
|
72
|
+
Swim the cell. The cell will swim to a new cell or tumble some every time interval.
|
|
73
|
+
:param cell: the cell to swim.
|
|
74
|
+
:param step_size: the step size.
|
|
75
|
+
:return: the cell after swimming.
|
|
76
|
+
:rtype: Cell
|
|
77
|
+
"""
|
|
78
|
+
for m in range(self._config.Ns):
|
|
79
|
+
# move the bacterium to the location of the new cell and evaluate the moved position
|
|
80
|
+
new_cell = self.__tumble_cell__(cell, step_size)
|
|
81
|
+
if new_cell.cost >= cell.cost:
|
|
82
|
+
cell.nutrients -= 1
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
new_cell.nutrients += 1
|
|
86
|
+
cell = new_cell
|
|
87
|
+
# update personal best
|
|
88
|
+
if new_cell.cost < cell.local_cost:
|
|
89
|
+
cell.local_best = new_cell.position.copy()
|
|
90
|
+
cell.local_cost = new_cell.cost
|
|
91
|
+
|
|
92
|
+
return cell
|
|
93
|
+
|
|
94
|
+
def __clean_population__(self):
|
|
95
|
+
"""
|
|
96
|
+
Remove duplicates from the population. This method is called after each optimization step.
|
|
97
|
+
"""
|
|
98
|
+
new_set = set()
|
|
99
|
+
for idx, obj in enumerate(self._population):
|
|
100
|
+
pos = tuple(obj.position)
|
|
101
|
+
if pos in new_set:
|
|
102
|
+
self._population.pop(idx)
|
|
103
|
+
else:
|
|
104
|
+
new_set.add(pos)
|
|
105
|
+
|
|
106
|
+
def __balance_population__(self):
|
|
107
|
+
"""
|
|
108
|
+
Balance the population by adding more agents or remove some agents. This method is called after each
|
|
109
|
+
optimization step. The population size is fixed to the value of the configuration. If the population size
|
|
110
|
+
is greater than the value of the configuration, then some agents are removed. Otherwise, new agents are added.
|
|
111
|
+
"""
|
|
112
|
+
n_agents = len(self._population) - self._config.population_size
|
|
113
|
+
if n_agents == 0:
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
if n_agents < 0:
|
|
117
|
+
self._population += [self._init_agent() for _ in range(0, -n_agents)]
|
|
118
|
+
else:
|
|
119
|
+
list_idx_removed = np.random.choice(range(0, len(self._population)), n_agents, replace=False)
|
|
120
|
+
for idx in sorted(list_idx_removed, reverse=True):
|
|
121
|
+
self._population.pop(idx)
|
|
122
|
+
|
|
123
|
+
def optimization_step(self):
|
|
124
|
+
for idx, cell in enumerate(self._population):
|
|
125
|
+
step_size = self.__update_step_size__(cell)
|
|
126
|
+
cell = self.__swim__(cell, step_size)
|
|
127
|
+
|
|
128
|
+
m = max(self._config.N_split, self._config.N_split + (
|
|
129
|
+
len(self._population) - self._config.population_size
|
|
130
|
+
) / self._config.N_adapt)
|
|
131
|
+
|
|
132
|
+
pos = np.array(cell.position)
|
|
133
|
+
|
|
134
|
+
if cell.nutrients > m:
|
|
135
|
+
tt = np.random.normal(0, 1, self._task.space_dimension)
|
|
136
|
+
agent = self._init_agent(
|
|
137
|
+
self._correct_position(tt * pos + (1 - tt) * (np.array(self._best_agent.position) - pos))
|
|
138
|
+
)
|
|
139
|
+
self._population.append(agent)
|
|
140
|
+
nut_min = min(self._config.N_adapt, self._config.N_adapt + (
|
|
141
|
+
len(self._population) - self._config.population_size
|
|
142
|
+
) / self._config.N_adapt)
|
|
143
|
+
|
|
144
|
+
self._population[idx] = cell
|
|
145
|
+
if cell.nutrients < nut_min and np.random.random() < self._config.Ped:
|
|
146
|
+
self._population[idx] = self._init_agent()
|
|
147
|
+
|
|
148
|
+
# make sure the population does not have duplicates
|
|
149
|
+
self.__clean_population__()
|
|
150
|
+
|
|
151
|
+
# balance the population by adding more agents or remove some agents
|
|
152
|
+
self.__balance_population__()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from pydantic import field_validator
|
|
2
|
+
|
|
3
|
+
from ..models import Agent, BaseOptimizationConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Cell(Agent):
|
|
7
|
+
local_cost: float
|
|
8
|
+
local_best: list[float]
|
|
9
|
+
nutrients: float = 0.0
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BacterialForagingOptimizationConfig(BaseOptimizationConfig):
|
|
13
|
+
"""
|
|
14
|
+
Configuration class of the Adaptive Bacterial Foraging Optimization Algorithm.
|
|
15
|
+
C_s (float): (0, 2.0), step size start.\n
|
|
16
|
+
C_e (float): (0, 1.0), step size end.\n
|
|
17
|
+
Ped (float): (0, 1.0), probability eliminate.\n
|
|
18
|
+
Ns (int): [2, 100], swim_length.\n
|
|
19
|
+
N_adapt (int): [0, 4], dead threshold value.\n
|
|
20
|
+
N_split (int): [5, 50], split threshold value.
|
|
21
|
+
"""
|
|
22
|
+
C_s: float
|
|
23
|
+
C_e: float
|
|
24
|
+
Ped: float
|
|
25
|
+
Ns: int
|
|
26
|
+
N_adapt: int
|
|
27
|
+
N_split: int
|
|
28
|
+
|
|
29
|
+
@field_validator("C_s")
|
|
30
|
+
def check_C_s(cls, v):
|
|
31
|
+
if not (0 < v < 2.0):
|
|
32
|
+
raise ValueError("C_s must be in (0, 2.0)")
|
|
33
|
+
return v
|
|
34
|
+
|
|
35
|
+
@field_validator("C_e")
|
|
36
|
+
def check_C_e(cls, v):
|
|
37
|
+
if not (0 < v < 1.0):
|
|
38
|
+
raise ValueError("C_e must be in (0, 1.0)")
|
|
39
|
+
return v
|
|
40
|
+
|
|
41
|
+
@field_validator("Ped")
|
|
42
|
+
def check_Ped(cls, v):
|
|
43
|
+
if not (0 < v < 1.0):
|
|
44
|
+
raise ValueError("Ped must be in (0, 1.0)")
|
|
45
|
+
return v
|
|
46
|
+
|
|
47
|
+
@field_validator("Ns")
|
|
48
|
+
def check_Ns(cls, v):
|
|
49
|
+
if not (2 <= v <= 100):
|
|
50
|
+
raise ValueError("Ns must be in [2, 100]")
|
|
51
|
+
return v
|
|
52
|
+
|
|
53
|
+
@field_validator("N_adapt")
|
|
54
|
+
def check_N_adapt(cls, v):
|
|
55
|
+
if not (0 <= v <= 4):
|
|
56
|
+
raise ValueError("N_adapt must be in [0, 4]")
|
|
57
|
+
return v
|
|
58
|
+
|
|
59
|
+
@field_validator("N_split")
|
|
60
|
+
def check_N_split(cls, v):
|
|
61
|
+
if not (5 <= v <= 50):
|
|
62
|
+
raise ValueError("N_split must be in [5, 50]")
|
|
63
|
+
return v
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..helpers import parse_obj_doc # type: ignore
|
|
4
|
+
from ..abstract import OptimizationAbstract
|
|
5
|
+
from .models import Bat, BatOptimizationConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BatOptimization(OptimizationAbstract):
|
|
9
|
+
"""
|
|
10
|
+
Implementation of the Bat Optimization algorithm.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
config (BatOptimizationConfig): an instance of BatOptimizationConfig class.
|
|
14
|
+
{parse_obj_doc(BatsOptimizationConfig)}
|
|
15
|
+
|
|
16
|
+
Bibliography
|
|
17
|
+
----------
|
|
18
|
+
[1] Yang, Xin-She. "A new metaheuristic bat-inspired algorithm." Nature inspired cooperative strategies for
|
|
19
|
+
optimization" (NICSO 2010). Springer, Berlin, Heidelberg, 2010. 65-74.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: BatOptimizationConfig, debug: bool | None = False):
|
|
23
|
+
super().__init__(config, debug)
|
|
24
|
+
|
|
25
|
+
def _init_agent(
|
|
26
|
+
self,
|
|
27
|
+
position: list[float] | np.ndarray | None = None,
|
|
28
|
+
velocity: list[float] | np.ndarray | None = None,
|
|
29
|
+
loudness: float | None = None,
|
|
30
|
+
pulse_rate: float | None = None,
|
|
31
|
+
) -> Bat:
|
|
32
|
+
agent = super()._init_agent(position)
|
|
33
|
+
velocity = self._correct_position(velocity if velocity is not None else self._uniform_position().tolist())
|
|
34
|
+
|
|
35
|
+
return Bat(
|
|
36
|
+
**agent.model_dump(),
|
|
37
|
+
velocity=velocity,
|
|
38
|
+
loudness=loudness if loudness is not None else np.random.uniform(*self._config.loudness),
|
|
39
|
+
pulse_rate=pulse_rate if pulse_rate is not None else np.random.uniform(*self._config.pulse_rate),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def _greedy_select_agent(self, agent: Bat, new_agent: Bat) -> Bat:
|
|
43
|
+
"""
|
|
44
|
+
Perform the greedy selection between the current agent and the new one. The greedy selection is performed by
|
|
45
|
+
comparing the costs of each agent. The one with the lowest cost is kept.
|
|
46
|
+
:param agent: the current agent
|
|
47
|
+
:param new_agent: the new agent
|
|
48
|
+
:return: the best agent
|
|
49
|
+
:rtype: Bat
|
|
50
|
+
"""
|
|
51
|
+
return new_agent if new_agent.cost < agent.cost and np.random.random() < agent.loudness else agent
|
|
52
|
+
|
|
53
|
+
def optimization_step(self):
|
|
54
|
+
mean_a = np.mean([bat.loudness for bat in self._population])
|
|
55
|
+
|
|
56
|
+
pf_min, pf_max = self._config.pulse_frequency
|
|
57
|
+
best_position = np.array(self._best_agent.position)
|
|
58
|
+
for idx, bat in enumerate(self._population):
|
|
59
|
+
velocity = bat.velocity + np.random.uniform(pf_min, pf_max) * (np.array(bat.position) - best_position)
|
|
60
|
+
|
|
61
|
+
# Local Search around g_best position
|
|
62
|
+
position = best_position + mean_a * np.random.normal(-1, 1) \
|
|
63
|
+
if np.random.random() > bat.pulse_rate else bat.position + velocity
|
|
64
|
+
self._population[idx] = self._greedy_select_agent(
|
|
65
|
+
bat, self._init_agent(position, velocity, bat.loudness, bat.pulse_rate)
|
|
66
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from pydantic import field_validator, conlist
|
|
2
|
+
|
|
3
|
+
from ..models import Agent, BaseOptimizationConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Bat(Agent):
|
|
7
|
+
velocity: list[float]
|
|
8
|
+
loudness: float
|
|
9
|
+
pulse_rate: float
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BatOptimizationConfig(BaseOptimizationConfig):
|
|
13
|
+
"""
|
|
14
|
+
Configuration class for Bat Optimization algorithm.
|
|
15
|
+
loudness (list[float]): [0.5, 1.5], [1.5, 3.0], loudness range.\n
|
|
16
|
+
pulse_rate (list[float]): (0, 1.0), (0, 1.0), pulse rate range.\n
|
|
17
|
+
pulse_frequency (list[float]): [-10, 0], [0, 10], pulse frequency range (both cannot be 0).\n
|
|
18
|
+
alpha (float): [0, 1], loudness update parameter.\n
|
|
19
|
+
gamma (float): [0, 1], pulse rate update parameter.
|
|
20
|
+
"""
|
|
21
|
+
loudness: conlist(float, min_length=2, max_length=2)
|
|
22
|
+
pulse_rate: conlist(float, min_length=2, max_length=2)
|
|
23
|
+
pulse_frequency: conlist(float, min_length=2, max_length=2)
|
|
24
|
+
alpha: float = 0.9
|
|
25
|
+
gamma: float = 0.9
|
|
26
|
+
|
|
27
|
+
@field_validator("loudness")
|
|
28
|
+
def correct_loudness(cls, v):
|
|
29
|
+
loudness_min, loudness_max = v
|
|
30
|
+
if not 0.5 <= loudness_min <= 1.5:
|
|
31
|
+
raise ValueError(f"\"loudness[0]\" must be a float in [0.5, 1.5]. Got {loudness_min}")
|
|
32
|
+
if not 1.5 <= loudness_max <= 3.0:
|
|
33
|
+
raise ValueError(f"\"loudness[1]\" must be a float in [1.5, 3.0]. Got {loudness_max}")
|
|
34
|
+
return v
|
|
35
|
+
|
|
36
|
+
@field_validator("pulse_rate")
|
|
37
|
+
def correct_pulse_rate(cls, v):
|
|
38
|
+
pulse_rate_min, pulse_rate_max = v
|
|
39
|
+
if not 0 < pulse_rate_min < 1:
|
|
40
|
+
raise ValueError(f"\"pulse_rate[0]\" must be a float in (0, 1.0). Got {pulse_rate_min}")
|
|
41
|
+
if not 0 < pulse_rate_max < 1:
|
|
42
|
+
raise ValueError(f"\"pulse_rate[1]\" must be a float in (0, 1.0). Got {pulse_rate_max}")
|
|
43
|
+
return v
|
|
44
|
+
|
|
45
|
+
@field_validator("pulse_frequency")
|
|
46
|
+
def correct_pulse_frequency(cls, v):
|
|
47
|
+
pulse_frequency_min, pulse_frequency_max = v
|
|
48
|
+
if not -10 <= pulse_frequency_min <= 0:
|
|
49
|
+
raise ValueError(f"\"pulse_frequency[0]\" must be a float in [-10, 0]. Got {pulse_frequency_min}")
|
|
50
|
+
if not 0 <= pulse_frequency_max <= 10:
|
|
51
|
+
raise ValueError(f"\"pulse_frequency[1]\" must be a float in [0, 10]. Got {pulse_frequency_max}")
|
|
52
|
+
if pulse_frequency_max == 0 or pulse_frequency_max == 0:
|
|
53
|
+
raise ValueError(f"\"pulse_frequency\" cannot have both 0 values. Got {v}")
|
|
54
|
+
return v
|
|
55
|
+
|
|
56
|
+
@field_validator("alpha")
|
|
57
|
+
def correct_alpha(cls, v):
|
|
58
|
+
if not 0 <= v <= 1:
|
|
59
|
+
raise ValueError(f"\"alpha\" must be a float in [0, 1]. Got {v}")
|
|
60
|
+
return v
|
|
61
|
+
|
|
62
|
+
@field_validator("gamma")
|
|
63
|
+
def correct_gamma(cls, v):
|
|
64
|
+
if not 0 <= v <= 1:
|
|
65
|
+
raise ValueError(f"\"gamma\" must be a float in [0, 1]. Got {v}")
|
|
66
|
+
return v
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..helpers import (
|
|
4
|
+
get_partner_index,
|
|
5
|
+
roulette_wheel_index,
|
|
6
|
+
parse_obj_doc, # type: ignore
|
|
7
|
+
)
|
|
8
|
+
from ..abstract import OptimizationAbstract
|
|
9
|
+
from .models import Bee, BeeColonyOptimizationConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BeeColonyOptimization(OptimizationAbstract):
|
|
13
|
+
"""
|
|
14
|
+
Implementation of the Bee Colony Optimization algorithm.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
config (BeeColonyOptimizationConfig): an instance of BeeColonyOptimizationConfig class.
|
|
18
|
+
{parse_obj_doc(AntColonyOptimizationConfig)}
|
|
19
|
+
|
|
20
|
+
Bibliography
|
|
21
|
+
----------
|
|
22
|
+
[1] D. Karaboga, An idea based on honey bee swarm for numerical optimization, Technical Report TR06, Erciyes
|
|
23
|
+
University, Engineering Faculty, Computer Engineering Department, 2005.
|
|
24
|
+
[2] D. Karaboga, B. Basturk, On The Performance Of Artificial Bee Colony (ABC) Algorithm, Applied Soft Computing,
|
|
25
|
+
8(1), 687-697, 2008.
|
|
26
|
+
[3] D. Karaboga, B. Basturk, A Powerful And Efficient Algorithm For Numerical Function Optimization: Artificial Bee
|
|
27
|
+
Colony (ABC) Algorithm, Journal of Global Optimization, 39(3), 459-471, 2007.
|
|
28
|
+
"""
|
|
29
|
+
def __init__(self, config: BeeColonyOptimizationConfig, debug: bool | None = False):
|
|
30
|
+
super().__init__(config, debug)
|
|
31
|
+
self._config.population_size = int(self._config.population_size / 2)
|
|
32
|
+
|
|
33
|
+
def _init_agent(self, position: list[float] | np.ndarray | None = None) -> Bee:
|
|
34
|
+
agent = super()._init_agent(position)
|
|
35
|
+
return Bee(**agent.model_dump())
|
|
36
|
+
|
|
37
|
+
def _greedy_select_agent(self, agent: Bee, new_agent: Bee) -> Bee:
|
|
38
|
+
"""
|
|
39
|
+
Perform the greedy selection between the current agent and the new one. The greedy selection is performed by
|
|
40
|
+
comparing the costs of each agent. The one with the lowest cost is kept.
|
|
41
|
+
:param agent: the current agent
|
|
42
|
+
:param new_agent: the new agent
|
|
43
|
+
:return: the best agent
|
|
44
|
+
"""
|
|
45
|
+
# if the current agent can not be improved, increase its trial counter
|
|
46
|
+
return new_agent if new_agent.cost < agent.cost else agent.model_copy(update={"trials": agent.trials + 1})
|
|
47
|
+
|
|
48
|
+
def __send_employed_bees__(self) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Send employed bees to search for food sources. Each employed bee will dance on a food source.
|
|
51
|
+
:return:
|
|
52
|
+
"""
|
|
53
|
+
for i, bee in enumerate(self._population):
|
|
54
|
+
self.__food_source_dance__(i, bee)
|
|
55
|
+
|
|
56
|
+
def __food_source_dance__(self, index: int, bee: Bee):
|
|
57
|
+
"""
|
|
58
|
+
Perform a food source dance. The dance is performed by generating a mutant solution and evaluating it. If the
|
|
59
|
+
mutant solution is better than the current solution, the current solution is replaced with the mutant solution.
|
|
60
|
+
Otherwise, the trial counter of the current solution is increased by one.
|
|
61
|
+
:param index:
|
|
62
|
+
:param bee:
|
|
63
|
+
:return:
|
|
64
|
+
"""
|
|
65
|
+
# a randomly chosen solution is used in producing a mutant solution of the i-th solution
|
|
66
|
+
# randomly selected solution must be different from the i-th solution
|
|
67
|
+
partner_index = get_partner_index(index, self._config.population_size)
|
|
68
|
+
partner = self._population[partner_index]
|
|
69
|
+
|
|
70
|
+
# generate a mutant solution by perturbing the current solution "index" with a random number
|
|
71
|
+
phi = np.random.uniform(low=-1, high=1, size=self._task.space_dimension)
|
|
72
|
+
pos_new = np.array(bee.position) + phi * (np.array(bee.position) - np.array(partner.position))
|
|
73
|
+
self._population[index] = self._greedy_select_agent(bee, self._init_agent(pos_new))
|
|
74
|
+
|
|
75
|
+
def __send_onlooker_bees__(self):
|
|
76
|
+
"""
|
|
77
|
+
Send onlooker bees to search for food sources. Each onlooker bee will dance on a food source. The probability of
|
|
78
|
+
each onlooker bee to dance on a food source is proportional to the quality of the food source. The better the
|
|
79
|
+
food source, the higher the probability of being selected. The probability of each food source is calculated
|
|
80
|
+
using the following formula:
|
|
81
|
+
p_i = cost_i / sum(costs)
|
|
82
|
+
where p_i is the probability of the i-th food source, and cost_i is the cost of the i-th food source. The
|
|
83
|
+
probability of each food source is calculated using the costs of the employed bees. The onlooker bees will use a
|
|
84
|
+
roulette wheel selection to select a food source.
|
|
85
|
+
:return:
|
|
86
|
+
"""
|
|
87
|
+
# Calculate the probabilities of each employed bee
|
|
88
|
+
employed_costs = np.array([agent.cost for agent in self._population])
|
|
89
|
+
probabilities = employed_costs / np.sum(employed_costs)
|
|
90
|
+
for idx in range(0, self._config.population_size):
|
|
91
|
+
# Select an employed bee using roulette wheel selection
|
|
92
|
+
selected_bee = self._population[roulette_wheel_index(probabilities)]
|
|
93
|
+
self.__food_source_dance__(idx, selected_bee)
|
|
94
|
+
|
|
95
|
+
def __send_scout_bees__(self):
|
|
96
|
+
"""
|
|
97
|
+
Send scout bees to search for food sources. If the number of trials of a food source exceeds a predefined limit,
|
|
98
|
+
the food source is abandoned and a new food source is generated. The new food source is generated randomly.
|
|
99
|
+
"""
|
|
100
|
+
trials = np.array([food.trials for food in self._population])
|
|
101
|
+
|
|
102
|
+
# Check the number of trials for each employed bee and abandon the food sources if the limit is exceeded
|
|
103
|
+
abandoned = np.where(trials >= self._config.scouting_limit)[0]
|
|
104
|
+
for idx in abandoned:
|
|
105
|
+
self._population[idx] = self._init_agent() # replace food source with a brand new one
|
|
106
|
+
|
|
107
|
+
def optimization_step(self):
|
|
108
|
+
# generate and evaluate a neighbour point to every food source
|
|
109
|
+
self.__send_employed_bees__()
|
|
110
|
+
|
|
111
|
+
# based to probability, generate a neighbour point and evaluate again some food sources
|
|
112
|
+
# same food source can be evaluated multiple times
|
|
113
|
+
self.__send_onlooker_bees__()
|
|
114
|
+
|
|
115
|
+
# abandon the food sources which have not been improved after a predefined number of trials
|
|
116
|
+
self.__send_scout_bees__()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from pydantic import field_validator
|
|
2
|
+
|
|
3
|
+
from ..models import Agent, BaseOptimizationConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Bee(Agent):
|
|
7
|
+
trials: int = 0
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BeeColonyOptimizationConfig(BaseOptimizationConfig):
|
|
11
|
+
"""
|
|
12
|
+
Configuration class for Bee Colony Optimization algorithm.
|
|
13
|
+
scouting_limit (int): [1, +Inf), the number of times a bee can scout before it is considered exhausted and is
|
|
14
|
+
replaced.
|
|
15
|
+
"""
|
|
16
|
+
scouting_limit: int
|
|
17
|
+
|
|
18
|
+
@field_validator("scouting_limit")
|
|
19
|
+
def correct_number_of_scouts(cls, v):
|
|
20
|
+
if v < 1:
|
|
21
|
+
raise ValueError(f"\"scouting_limit\" must be an integer greater than 1. Got {v}")
|
|
22
|
+
return v
|