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,21 @@
|
|
|
1
|
+
from pydantic import field_validator
|
|
2
|
+
|
|
3
|
+
from ..models import Agent, BaseOptimizationConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Gene(Agent):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GeneticAlgorithmOptimizationConfig(BaseOptimizationConfig):
|
|
11
|
+
"""
|
|
12
|
+
Configuration class of the Genetic Algorithm.
|
|
13
|
+
px_over (float): [0, 1), probability of crossover between two genes.
|
|
14
|
+
"""
|
|
15
|
+
px_over: float
|
|
16
|
+
|
|
17
|
+
@field_validator("px_over")
|
|
18
|
+
def correct_px_over(cls, v):
|
|
19
|
+
if not 0 <= v < 1:
|
|
20
|
+
raise ValueError(f"\"px_over\" must be a positive float lower than 1. Got {v}")
|
|
21
|
+
return v
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..helpers import (
|
|
4
|
+
distance,
|
|
5
|
+
verser,
|
|
6
|
+
parse_obj_doc, # type: ignore
|
|
7
|
+
)
|
|
8
|
+
from ..abstract import OptimizationAbstract
|
|
9
|
+
from .models import GrasshopperOptimizationConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GrasshopperOptimization(OptimizationAbstract):
|
|
13
|
+
"""
|
|
14
|
+
Implementation of the Grasshopper Optimization Algorithm.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
config (GrasshopperOptimizationConfig): an instance of GrasshopperOptimizationConfig class.
|
|
18
|
+
{parse_obj_doc(GrasshopperOptimizationConfig)}
|
|
19
|
+
|
|
20
|
+
Bibliography
|
|
21
|
+
----------
|
|
22
|
+
[1] Saremi, S., Mirjalili, S. and Lewis, A., 2017. Grasshopper optimisation algorithm: theory and application.
|
|
23
|
+
Advances in Engineering Software, 105, pp.30-47.
|
|
24
|
+
"""
|
|
25
|
+
def __init__(self, config: GrasshopperOptimizationConfig, debug: bool | None = False):
|
|
26
|
+
super().__init__(config, debug)
|
|
27
|
+
|
|
28
|
+
def __s_function__(self, position: list[float], ran: np.ndarray):
|
|
29
|
+
"""
|
|
30
|
+
Eq.(2.3) in the paper. This function is used to calculate the sum of the attraction forces between the current
|
|
31
|
+
grasshopper and the other grasshoppers in the population. The sum of the attraction forces is calculated by
|
|
32
|
+
multiplying the attraction force between the current grasshopper and each other grasshopper by the direction of
|
|
33
|
+
the attraction force. The direction of the attraction force is calculated by the verser of the vector between
|
|
34
|
+
the current grasshopper and each other grasshopper. The attraction force between the current grasshopper and
|
|
35
|
+
each other grasshopper is calculated by the Eq.(2.2) in the paper. The Eq.(2.2) is a function of the distance
|
|
36
|
+
between the current grasshopper and each other grasshopper. The distance between the current grasshopper and
|
|
37
|
+
each other grasshopper is calculated by the Eq.(2.1) in the paper. The Eq.(2.1) is a function of the position
|
|
38
|
+
of the current grasshopper and the position of each other grasshopper. The position of the current grasshopper
|
|
39
|
+
is the position of the current grasshopper in the current iteration. The position of each other grasshopper is
|
|
40
|
+
the position of each other grasshopper in the current iteration. The Eq.(2.3) is a function of the position of
|
|
41
|
+
the current grasshopper and the position of each other grasshopper. The position of the current grasshopper is
|
|
42
|
+
the position of the current grasshopper in the current iteration. The position of each other grasshopper is the
|
|
43
|
+
position of each other grasshopper in the current iteration.
|
|
44
|
+
:param position: the position of the current grasshopper in the current iteration.
|
|
45
|
+
:param ran: a random vector.
|
|
46
|
+
:return: the sum of the attraction forces between the current grasshopper and the other grasshoppers in the
|
|
47
|
+
population.
|
|
48
|
+
:rtype: np.ndarray
|
|
49
|
+
"""
|
|
50
|
+
def f(r_vector: np.ndarray):
|
|
51
|
+
return 0.5 * np.exp(-r_vector / 1.5) - np.exp(-r_vector)
|
|
52
|
+
|
|
53
|
+
return np.sum([
|
|
54
|
+
ran * f(2 + np.remainder(distance(position, g.position), 2)) * verser(position, g.position)
|
|
55
|
+
for jdx, g in enumerate(self._population)
|
|
56
|
+
], axis=0)
|
|
57
|
+
|
|
58
|
+
def optimization_step(self):
|
|
59
|
+
# Eq.(2.8)
|
|
60
|
+
c = self._config.c_max - self._cycles * ((self._config.c_max - self._config.c_min) / self._config.max_cycles)
|
|
61
|
+
ran = (c / 2) * self._bandwidth()
|
|
62
|
+
|
|
63
|
+
best_pos = np.array(self._best_agent.position)
|
|
64
|
+
for idx, grasshopper in enumerate(self._population):
|
|
65
|
+
sum_grass = self.__s_function__(grasshopper.position, ran)
|
|
66
|
+
|
|
67
|
+
# Eq. (2.7)
|
|
68
|
+
self._population[idx] = self._greedy_select_agent(
|
|
69
|
+
self._init_agent(self._correct_position(
|
|
70
|
+
c * np.random.normal(0, 1, self._task.space_dimension) * sum_grass + best_pos
|
|
71
|
+
)),
|
|
72
|
+
grasshopper
|
|
73
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from pydantic import field_validator
|
|
2
|
+
|
|
3
|
+
from ..models import Agent, BaseOptimizationConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Grasshopper(Agent):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GrasshopperOptimizationConfig(BaseOptimizationConfig):
|
|
11
|
+
"""
|
|
12
|
+
Configuration class of the Grasshopper Optimization Algorithm.
|
|
13
|
+
c_min (float): [0.00001, 0.2], coefficient c min, default = 0.00004.\n
|
|
14
|
+
c_max (float): [0.2, 5.0], coefficient c max, default = 2.0.
|
|
15
|
+
"""
|
|
16
|
+
c_min: float
|
|
17
|
+
c_max: float
|
|
18
|
+
|
|
19
|
+
@field_validator("c_min")
|
|
20
|
+
def validate_c_min(cls, v):
|
|
21
|
+
if not 0.00001 <= v <= 0.2:
|
|
22
|
+
raise ValueError("c_min must be in range [0.00001, 0.2].")
|
|
23
|
+
return v
|
|
24
|
+
|
|
25
|
+
@field_validator("c_max")
|
|
26
|
+
def validate_c_max(cls, v):
|
|
27
|
+
if not 0.2 <= v <= 5.0:
|
|
28
|
+
raise ValueError("c_max must be in range [0.2, 5.0].")
|
|
29
|
+
return v
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..helpers import (
|
|
4
|
+
best_agents,
|
|
5
|
+
parse_obj_doc, # type: ignore
|
|
6
|
+
)
|
|
7
|
+
from ..abstract import OptimizationAbstract
|
|
8
|
+
from .models import GreyWolf, GreyWolfOptimizationConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GreyWolfOptimization(OptimizationAbstract):
|
|
12
|
+
"""
|
|
13
|
+
Implementation of the Grey Wolf Optimization algorithm.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
config (GreyWolfOptimizationConfig): an instance of GreyWolfOptimizationConfig class.
|
|
17
|
+
{parse_obj_doc(GreyWolfOptimizationConfig)}
|
|
18
|
+
|
|
19
|
+
Bibliography
|
|
20
|
+
----------
|
|
21
|
+
[1] Mirjalili, S., Mirjalili, S. M., & Lewis, A. (2014). Grey wolf optimizer. Advances in engineering software, 69,
|
|
22
|
+
46-61. Chicago
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, config: GreyWolfOptimizationConfig, debug: bool | None = False):
|
|
25
|
+
super().__init__(config, debug)
|
|
26
|
+
self.__alpha_wolf: GreyWolf | None = None
|
|
27
|
+
self.__beta_wolf: GreyWolf | None = None
|
|
28
|
+
self.__gamma_wolf: GreyWolf | None = None
|
|
29
|
+
|
|
30
|
+
def _init_population(self):
|
|
31
|
+
super()._init_population()
|
|
32
|
+
self.__alpha_wolf, self.__beta_wolf, self.__gamma_wolf = best_agents(self._population, 3)
|
|
33
|
+
|
|
34
|
+
def optimization_step(self):
|
|
35
|
+
"""
|
|
36
|
+
Implementation of the Grey Wolf Optimization algorithm. [1]
|
|
37
|
+
"""
|
|
38
|
+
# linearly decreased from 2 to 0
|
|
39
|
+
a = 2 * (1 - self._cycles / self._config.max_cycles)
|
|
40
|
+
|
|
41
|
+
# updating each population member with the help of best three members
|
|
42
|
+
for idx in range(0, self._config.population_size):
|
|
43
|
+
position = np.array(self._population[idx].position)
|
|
44
|
+
a1, a2, a3 = (a * (2 * np.random.random() - 1),
|
|
45
|
+
a * (2 * np.random.random() - 1),
|
|
46
|
+
a * (2 * np.random.random() - 1))
|
|
47
|
+
c1, c2, c3 = 2 * np.random.random(), 2 * np.random.random(), 2 * np.random.random()
|
|
48
|
+
|
|
49
|
+
x1 = np.array(self.__alpha_wolf.position) - a1 * np.abs(
|
|
50
|
+
c1 * np.array(self.__alpha_wolf.position) - position
|
|
51
|
+
)
|
|
52
|
+
x2 = np.array(self.__beta_wolf.position) - a2 * np.abs(c2 * np.array(self.__beta_wolf.position) - position)
|
|
53
|
+
x3 = np.array(self.__gamma_wolf.position) - a3 * np.abs(
|
|
54
|
+
c3 * np.array(self.__gamma_wolf.position) - position
|
|
55
|
+
)
|
|
56
|
+
# greedy selection
|
|
57
|
+
self._population[idx] = self._greedy_select_agent(
|
|
58
|
+
self._population[idx], self._init_agent((x1 + x2 + x3) / 3)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# best 3 solutions will be called as alpha, beta and gaama
|
|
62
|
+
self.__alpha_wolf, self.__beta_wolf, self.__gamma_wolf = best_agents(self._population, 3)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..helpers import parse_obj_doc # type: ignore
|
|
4
|
+
from ..abstract import OptimizationAbstract
|
|
5
|
+
from .models import Harmony, HarmonySearchOptimizationConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HarmonySearchOptimization(OptimizationAbstract):
|
|
9
|
+
"""
|
|
10
|
+
Implementation of the Harmony Search Optimization algorithm.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
config (HarmonySearchOptimizationConfig): an instance of HarmonySearchOptimizationConfig class.
|
|
14
|
+
{parse_obj_doc(HarmonySearchOptimizationConfig)}
|
|
15
|
+
|
|
16
|
+
Bibliography
|
|
17
|
+
----------
|
|
18
|
+
[1] https://doi.org/10.1177/003754970107600201
|
|
19
|
+
"""
|
|
20
|
+
def __init__(self, config: HarmonySearchOptimizationConfig, debug: bool | None = False):
|
|
21
|
+
super().__init__(config, debug)
|
|
22
|
+
self.__fw: np.ndarray | None = None
|
|
23
|
+
self.__dyn_fw: np.ndarray | None = None
|
|
24
|
+
self.__fw_damp = 0.9995 # Damp Ratio
|
|
25
|
+
|
|
26
|
+
def before_initialization(self):
|
|
27
|
+
self.__fw = 0.0001 * self._bandwidth() # Bandwidth
|
|
28
|
+
self.__dyn_fw = self.__fw
|
|
29
|
+
|
|
30
|
+
def __create_new_harmony__(self) -> Harmony:
|
|
31
|
+
"""
|
|
32
|
+
Create a new harmony. This method is used in the optimization step. It creates a new harmony based on the best
|
|
33
|
+
solution and the harmony memory. The new harmony is created using the following steps: (1) create a new harmony
|
|
34
|
+
position, (2) use the harmony memory, (3) pitch adjustment. The pitch adjustment is a random process that can be
|
|
35
|
+
used to improve the new harmony position.
|
|
36
|
+
:return: a new harmony.
|
|
37
|
+
:rtype: Harmony
|
|
38
|
+
"""
|
|
39
|
+
# Create New Harmony Position
|
|
40
|
+
pos_new = self._uniform_position()
|
|
41
|
+
delta = self.__dyn_fw * self._uniform_position()
|
|
42
|
+
|
|
43
|
+
# Use Harmony Memory
|
|
44
|
+
pos_new = np.where(
|
|
45
|
+
np.random.random(self._task.space_dimension) < self._config.consideration_rate,
|
|
46
|
+
self._best_agent.position,
|
|
47
|
+
pos_new
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Pitch Adjustment
|
|
51
|
+
pos_new = np.where(
|
|
52
|
+
np.random.random(self._task.space_dimension) < self._config.pitch_adjusting_rate, pos_new + delta, pos_new
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Create New Harmony
|
|
56
|
+
return Harmony(**self._init_agent(pos_new).model_dump())
|
|
57
|
+
|
|
58
|
+
def optimization_step(self):
|
|
59
|
+
pop_new = []
|
|
60
|
+
for index in range(0, self._config.population_size):
|
|
61
|
+
pop_new.append(self.__create_new_harmony__())
|
|
62
|
+
|
|
63
|
+
self.__dyn_fw *= self.__fw_damp
|
|
64
|
+
self._extend_and_trim_population(pop_new)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from pydantic import field_validator
|
|
2
|
+
|
|
3
|
+
from ..models import Agent, BaseOptimizationConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Harmony(Agent):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HarmonySearchOptimizationConfig(BaseOptimizationConfig):
|
|
11
|
+
"""
|
|
12
|
+
Configuration class for Harmony Search Optimization algorithm.
|
|
13
|
+
consideration_rate (float): [0, 1), the rate of using harmony memory.\n
|
|
14
|
+
pitch_adjusting_rate (float): [0, 1), the rate of pitch adjusting.
|
|
15
|
+
"""
|
|
16
|
+
consideration_rate: float
|
|
17
|
+
pitch_adjusting_rate: float
|
|
18
|
+
|
|
19
|
+
@field_validator("consideration_rate")
|
|
20
|
+
def correct_consideration_rate(cls, v):
|
|
21
|
+
if not 0 <= v < 1:
|
|
22
|
+
raise ValueError(f"\"consideration_rate\" must be a positive float lower than 1. Got {v}")
|
|
23
|
+
return v
|
|
24
|
+
|
|
25
|
+
@field_validator("pitch_adjusting_rate")
|
|
26
|
+
def correct_pitch_adjusting_rate(cls, v):
|
|
27
|
+
if not 0 <= v < 1:
|
|
28
|
+
raise ValueError(f"\"pitch_adjusting_rate\" must be a positive float lower than 1. Got {v}")
|
|
29
|
+
return v
|
pyvolutionary/helpers.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import random
|
|
3
|
+
import numpy as np
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from .models import T, TaskType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def parse_obj_doc(model: BaseModel):
|
|
10
|
+
"""
|
|
11
|
+
Parse the docstring of a model and return a string containing the name of each field and its type. The docstring
|
|
12
|
+
must be formatted as follows: "field1 (type1)\nfield2 (type2)\n...". The docstring is used to print the details of
|
|
13
|
+
the model. The details are printed as follows: "field1 (type1)\nfield2 (type2)\n...".
|
|
14
|
+
:param model: the model
|
|
15
|
+
:return: the docstring
|
|
16
|
+
"""
|
|
17
|
+
details = model.__annotations__
|
|
18
|
+
doc = [f"{field} ({field_type.__name__})" for field, field_type in details.items()]
|
|
19
|
+
return "\n".join(doc)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def squared_norm(point1: list[float], point2: list[float]):
|
|
23
|
+
"""
|
|
24
|
+
Calculate the squared distance between two points in the space. This function is used to avoid the square root
|
|
25
|
+
operation when calculating the distance between two points. The squared distance is calculated as follows:
|
|
26
|
+
squared_distance = sum((point1 - point2)^2)
|
|
27
|
+
:param point1: the first point
|
|
28
|
+
:param point2: the second point
|
|
29
|
+
:return: the squared distance between the two points
|
|
30
|
+
:rtype: float
|
|
31
|
+
"""
|
|
32
|
+
return np.sum((np.array(point1) - np.array(point2))**2)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def distance(point1: list[float], point2: list[float]):
|
|
36
|
+
"""
|
|
37
|
+
Calculate the distance between two points in the space using the Euclidean distance.
|
|
38
|
+
:param point1: the first point
|
|
39
|
+
:param point2: the second point
|
|
40
|
+
:return: the distance between the two points
|
|
41
|
+
:rtype: float
|
|
42
|
+
"""
|
|
43
|
+
return np.sqrt(squared_norm(point1, point2))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def distances(elements: list | np.ndarray) -> np.ndarray:
|
|
47
|
+
"""
|
|
48
|
+
Calculate the distance between each element of the provided list
|
|
49
|
+
:param elements: the list of elements
|
|
50
|
+
:return: the distance matrix
|
|
51
|
+
"""
|
|
52
|
+
elements = np.array(elements)
|
|
53
|
+
a = elements[:, :-1]
|
|
54
|
+
b = a.reshape(np.prod(a.shape[:-1]), 1, a.shape[-1])
|
|
55
|
+
return np.sqrt(np.einsum('ijk,ijk->ij', b - a, b - a)).squeeze()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def verser(point1: list[float], point2: list[float]) -> np.ndarray:
|
|
59
|
+
"""
|
|
60
|
+
Calculate the unit vector between two points in the space. The unit vector is calculated as follows:
|
|
61
|
+
unit_vector = (point1 - point2) / (distance + eps)
|
|
62
|
+
:param point1: the first point
|
|
63
|
+
:param point2: the second point
|
|
64
|
+
:return: the unit vector between the two points
|
|
65
|
+
:rtype: np.ndarray
|
|
66
|
+
"""
|
|
67
|
+
dist = distance(point1, point2)
|
|
68
|
+
return (np.array(point1) - np.array(point2)) / (dist + np.finfo(float).eps)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def calculate_fitness(value: float, task_type: TaskType) -> float:
|
|
72
|
+
"""
|
|
73
|
+
Calculate the fitness of the agent based on its cost value. The fitness is calculated as follows:
|
|
74
|
+
fitness = 1 / (1 + cost) if cost >= 0 else 1 + abs(cost) (the higher the fitness, the best the agent).
|
|
75
|
+
:param value: the cost value
|
|
76
|
+
:param task_type: the type of task (minimization or maximization)
|
|
77
|
+
:return: the fitness value
|
|
78
|
+
:rtype: float
|
|
79
|
+
"""
|
|
80
|
+
value = value if task_type == TaskType.MIN else -value
|
|
81
|
+
return (1 / (value + 1)) if value >= 0 else (1 + abs(value))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def sort_by_cost(population: list[T], reverse: bool = False):
|
|
85
|
+
"""
|
|
86
|
+
Sort the population by the cost value of each agent (the lower the cost, the best the agent). The sorting is done
|
|
87
|
+
in-place. If reverse is True, the sorting is done in descending order (the higher the cost, the worst the agent).
|
|
88
|
+
:param population: the population
|
|
89
|
+
:param reverse: whether to sort in ascending or descending order
|
|
90
|
+
"""
|
|
91
|
+
population.sort(key=lambda x: x.cost, reverse=reverse)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def sort_and_trim(population: list[T], population_size: int) -> list[T]:
|
|
95
|
+
"""
|
|
96
|
+
Sort the population according to the cost in ascending order and trim the population to the population size if
|
|
97
|
+
the population size is exceeded.
|
|
98
|
+
:param population: the current population
|
|
99
|
+
:param population_size: the population size
|
|
100
|
+
:return: the sorted and trimmed population
|
|
101
|
+
:rtype: list[Agent]
|
|
102
|
+
"""
|
|
103
|
+
sort_by_cost(population)
|
|
104
|
+
population = population[:population_size]
|
|
105
|
+
return population
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def best_agent(population: list[T], task_type: TaskType | None = TaskType.MIN) -> T:
|
|
109
|
+
"""
|
|
110
|
+
Get the best agent of the population based on its cost value (the lower the cost, the best the agent).
|
|
111
|
+
:param population: the population
|
|
112
|
+
:param task_type: the type of task (minimization or maximization)
|
|
113
|
+
:return: the best agent
|
|
114
|
+
:rtype: Agent
|
|
115
|
+
"""
|
|
116
|
+
(b_agent, ) = best_agents(population, 1, task_type)
|
|
117
|
+
return b_agent
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def worst_agent(population: list[T], task_type: TaskType | None = TaskType.MIN) -> T:
|
|
121
|
+
"""
|
|
122
|
+
Get the worst agent of the population based on its cost value (the higher the cost, the worst the agent).
|
|
123
|
+
:param population: the population
|
|
124
|
+
:param task_type: the type of task (minimization or maximization)
|
|
125
|
+
:return: the worst agent
|
|
126
|
+
:rtype: Agent
|
|
127
|
+
"""
|
|
128
|
+
(w_agent, ) = worst_agents(population, 1, task_type)
|
|
129
|
+
return w_agent
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def best_agents(population: list[T], n_best: int | None = 3, task_type: TaskType | None = TaskType.MIN) -> list[T]:
|
|
133
|
+
"""
|
|
134
|
+
Get the best agents of the population based on their cost value (the lower the cost, the best the agent).
|
|
135
|
+
:param population: the population
|
|
136
|
+
:param n_best: the number of best agents to return
|
|
137
|
+
:param task_type: the type of task (minimization or maximization)
|
|
138
|
+
:return: the best agents
|
|
139
|
+
:rtype: list[T]
|
|
140
|
+
"""
|
|
141
|
+
pop = population.copy()
|
|
142
|
+
sort_by_cost(pop, reverse=(task_type == TaskType.MAX))
|
|
143
|
+
return pop[:n_best].copy()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def worst_agents(population: list[T], n_worst: int | None = 3, task_type: TaskType | None = TaskType.MIN) -> list[T]:
|
|
147
|
+
"""
|
|
148
|
+
Get the worst agents of the population based on their cost value (the higher the cost, the worst the agent).
|
|
149
|
+
:param population: the population
|
|
150
|
+
:param n_worst: the number of worst agents to return
|
|
151
|
+
:param task_type: the type of task (minimization or maximization)
|
|
152
|
+
:return: the worst agents
|
|
153
|
+
:rtype: list[T]
|
|
154
|
+
"""
|
|
155
|
+
pop = population.copy()
|
|
156
|
+
sort_by_cost(pop, reverse=(task_type == TaskType.MIN))
|
|
157
|
+
return pop[:n_worst]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def special_agents(
|
|
161
|
+
population: list[T],
|
|
162
|
+
n_best: int | None = None,
|
|
163
|
+
n_worst: int | None = None,
|
|
164
|
+
task_type: TaskType | None = TaskType.MIN
|
|
165
|
+
) -> tuple[list[T], list[T]]:
|
|
166
|
+
"""
|
|
167
|
+
Get the best and worst agents of the population based on their cost value (the lower the cost, the best the agent).
|
|
168
|
+
Either n_best or n_worst must be provided. The best and worst agents are returned as a tuple.
|
|
169
|
+
:param population: the population
|
|
170
|
+
:param n_best: the number of best agents to return
|
|
171
|
+
:param n_worst: the number of worst agents to return
|
|
172
|
+
:param task_type: the type of task (minimization or maximization)
|
|
173
|
+
:return: a tuple containing the best and worst agents
|
|
174
|
+
:rtype: tuple[list[T], list[T]]
|
|
175
|
+
"""
|
|
176
|
+
if n_best is None and n_worst is None:
|
|
177
|
+
raise ValueError("Either n_best or n_worst must be provided")
|
|
178
|
+
|
|
179
|
+
best = []
|
|
180
|
+
if n_best is not None:
|
|
181
|
+
best = best_agents(population, n_best, task_type)
|
|
182
|
+
|
|
183
|
+
worst = []
|
|
184
|
+
if n_worst is not None:
|
|
185
|
+
worst = worst_agents(population, n_worst, task_type)
|
|
186
|
+
|
|
187
|
+
return best, worst
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def average_fitness(population: list[T]) -> float:
|
|
191
|
+
"""
|
|
192
|
+
Calculate the average fitness of the population based on the fitness of each agent of the population itself.
|
|
193
|
+
:param population:
|
|
194
|
+
:return: the average fitness
|
|
195
|
+
:rtype: float
|
|
196
|
+
"""
|
|
197
|
+
return np.average([agent.fitness for agent in population])
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def get_partner_index(index: int, num_elements: int) -> int:
|
|
201
|
+
while True:
|
|
202
|
+
partner_index = random.randint(0, num_elements - 1)
|
|
203
|
+
if partner_index != index:
|
|
204
|
+
break
|
|
205
|
+
return partner_index
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def roulette_wheel_index(probabilities: np.ndarray) -> int:
|
|
209
|
+
"""
|
|
210
|
+
Select an index using roulette wheel selection with probabilities as weights of the selection process and return the
|
|
211
|
+
selected index of the population array of the algorithm instance.
|
|
212
|
+
:param probabilities: the probabilities of each element of the population
|
|
213
|
+
:return: the index of the selected element
|
|
214
|
+
:rtype: int
|
|
215
|
+
"""
|
|
216
|
+
final_probabilities = np.max(probabilities) - probabilities
|
|
217
|
+
l = len(probabilities)
|
|
218
|
+
if all(final_probabilities == 0):
|
|
219
|
+
return int(np.random.choice(range(0, l)))
|
|
220
|
+
prob = final_probabilities / np.sum(final_probabilities)
|
|
221
|
+
return int(np.random.choice(range(0, len(probabilities)), p=prob))
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def random_selection(p: list | np.ndarray) -> int:
|
|
225
|
+
"""
|
|
226
|
+
Randomly select an index from the provided probabilities array.
|
|
227
|
+
:param p: the probabilities array
|
|
228
|
+
:return: the selected index
|
|
229
|
+
:rtype: int
|
|
230
|
+
"""
|
|
231
|
+
r = np.random.random()
|
|
232
|
+
c = np.cumsum(p)
|
|
233
|
+
index = [i for i, x in enumerate(c) if r <= x]
|
|
234
|
+
return index[0]
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def get_levy_flight_step(
|
|
238
|
+
beta: float = 1.0,
|
|
239
|
+
multiplier: float = 0.001,
|
|
240
|
+
size: int | list | tuple | np.ndarray = None,
|
|
241
|
+
case: int = 0
|
|
242
|
+
) -> float | list | np.ndarray:
|
|
243
|
+
"""
|
|
244
|
+
Get the Levy-flight step size
|
|
245
|
+
:param beta: Should be in range [0, 2]. 0-1: small range --> exploit. 1-2: large range --> explore
|
|
246
|
+
:param multiplier: default = 0.001
|
|
247
|
+
:param size: size of levy-flight steps, for example: (3, 2), 5, (4, )
|
|
248
|
+
:param case: Should be one of these value [0, 1, -1]. 0: return multiplier * s * self.generator.uniform().
|
|
249
|
+
1: return multiplier * s * self.generator.normal(0, 1). -1: return multiplier * s
|
|
250
|
+
:return: the step size of Levy-flight trajectory
|
|
251
|
+
:rtype: float, list, np.ndarray
|
|
252
|
+
"""
|
|
253
|
+
# u and v are two random variables which follow self.generator.normal distribution
|
|
254
|
+
# sigma_u : standard deviation of u
|
|
255
|
+
sigma_u = np.power(math.gamma(1. + beta) * np.sin(np.pi * beta / 2) / (
|
|
256
|
+
math.gamma((1 + beta) / 2.) * beta * np.power(2., (beta - 1) / 2)
|
|
257
|
+
), 1. / beta)
|
|
258
|
+
size = 1 if size is None else size
|
|
259
|
+
u = np.random.normal(0, sigma_u ** 2, size)
|
|
260
|
+
v = np.random.normal(0, 1, size)
|
|
261
|
+
s = u / np.power(np.abs(v), 1 / beta)
|
|
262
|
+
|
|
263
|
+
step = multiplier * s
|
|
264
|
+
if case == 0:
|
|
265
|
+
step = multiplier * s * np.random.uniform()
|
|
266
|
+
elif case == 1:
|
|
267
|
+
step = multiplier * s * np.random.normal(0, 1)
|
|
268
|
+
return step[0] if size == 1 else step
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def generate_group_population(population: list[T], n_groups: int, n_agents: int) -> list[list[T]]:
|
|
272
|
+
"""
|
|
273
|
+
Generate a list of group population from pop
|
|
274
|
+
:param population: The current population
|
|
275
|
+
:param n_groups: The number of groups
|
|
276
|
+
:param n_agents: The number of agents in each group
|
|
277
|
+
:return: a list of group population
|
|
278
|
+
:rtype: list[list[Agent]]
|
|
279
|
+
"""
|
|
280
|
+
# calculate the groups composed by n_agents
|
|
281
|
+
groups = []
|
|
282
|
+
for idx in range(0, n_groups):
|
|
283
|
+
group = population[idx * n_agents:(idx + 1) * n_agents]
|
|
284
|
+
groups.append([agent.model_copy() for agent in group])
|
|
285
|
+
|
|
286
|
+
# calculate the group composed by the residual agents
|
|
287
|
+
residual = len(population) % n_groups
|
|
288
|
+
if residual != 0:
|
|
289
|
+
groups.append([agent.model_copy() for agent in population[-residual:]])
|
|
290
|
+
return groups
|