pyVolutionary 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. pyvolutionary/__init__.py +44 -0
  2. pyvolutionary/abstract.py +325 -0
  3. pyvolutionary/african_vulture/__init__.py +2 -0
  4. pyvolutionary/african_vulture/african_vulture_optimization.py +87 -0
  5. pyvolutionary/african_vulture/models.py +41 -0
  6. pyvolutionary/ant_colony/__init__.py +2 -0
  7. pyvolutionary/ant_colony/ant_colony_optimization.py +80 -0
  8. pyvolutionary/ant_colony/models.py +25 -0
  9. pyvolutionary/aquila/__init__.py +2 -0
  10. pyvolutionary/aquila/aquila_optimization.py +75 -0
  11. pyvolutionary/aquila/models.py +9 -0
  12. pyvolutionary/bacterial_foraging/__init__.py +2 -0
  13. pyvolutionary/bacterial_foraging/bacterial_foraging_optimization.py +152 -0
  14. pyvolutionary/bacterial_foraging/models.py +63 -0
  15. pyvolutionary/bat/__init__.py +2 -0
  16. pyvolutionary/bat/bat_optimization.py +66 -0
  17. pyvolutionary/bat/models.py +66 -0
  18. pyvolutionary/bee_colony/__init__.py +2 -0
  19. pyvolutionary/bee_colony/bee_colony_optimization.py +116 -0
  20. pyvolutionary/bee_colony/models.py +22 -0
  21. pyvolutionary/camel_caravan/__init__.py +2 -0
  22. pyvolutionary/camel_caravan/camel_caravan_optimization.py +96 -0
  23. pyvolutionary/camel_caravan/models.py +61 -0
  24. pyvolutionary/coral_reef/__init__.py +2 -0
  25. pyvolutionary/coral_reef/coral_reef_optimization.py +133 -0
  26. pyvolutionary/coral_reef/models.py +81 -0
  27. pyvolutionary/coyotes/__init__.py +2 -0
  28. pyvolutionary/coyotes/coyotes_optimization.py +96 -0
  29. pyvolutionary/coyotes/models.py +22 -0
  30. pyvolutionary/earthworms/__init__.py +2 -0
  31. pyvolutionary/earthworms/earthworms_optimization.py +124 -0
  32. pyvolutionary/earthworms/models.py +62 -0
  33. pyvolutionary/electromagnetic_field/__init__.py +2 -0
  34. pyvolutionary/electromagnetic_field/electromagnetic_field_optimization.py +58 -0
  35. pyvolutionary/electromagnetic_field/models.py +45 -0
  36. pyvolutionary/elephant_herd/__init__.py +2 -0
  37. pyvolutionary/elephant_herd/elephant_herd_optimization.py +56 -0
  38. pyvolutionary/elephant_herd/models.py +38 -0
  39. pyvolutionary/enums.py +39 -0
  40. pyvolutionary/firefly_swarm/__init__.py +2 -0
  41. pyvolutionary/firefly_swarm/firefly_swarm_optimization.py +76 -0
  42. pyvolutionary/firefly_swarm/models.py +37 -0
  43. pyvolutionary/fireworks/__init__.py +2 -0
  44. pyvolutionary/fireworks/fireworks_optimization.py +96 -0
  45. pyvolutionary/fireworks/models.py +41 -0
  46. pyvolutionary/fish_school_search/__init__.py +2 -0
  47. pyvolutionary/fish_school_search/fish_school_search_optimization.py +142 -0
  48. pyvolutionary/fish_school_search/models.py +27 -0
  49. pyvolutionary/flower_pollination_algorithm/__init__.py +2 -0
  50. pyvolutionary/flower_pollination_algorithm/flower_pollination_algorithm_optimization.py +46 -0
  51. pyvolutionary/flower_pollination_algorithm/models.py +29 -0
  52. pyvolutionary/forest_algorithm/__init__.py +2 -0
  53. pyvolutionary/forest_algorithm/forest_optimization_algorithm.py +121 -0
  54. pyvolutionary/forest_algorithm/models.py +53 -0
  55. pyvolutionary/fox/__init__.py +2 -0
  56. pyvolutionary/fox/fox_optimization.py +43 -0
  57. pyvolutionary/fox/models.py +29 -0
  58. pyvolutionary/genetic_algorithm/__init__.py +2 -0
  59. pyvolutionary/genetic_algorithm/genetic_algorithm_optimization.py +167 -0
  60. pyvolutionary/genetic_algorithm/models.py +21 -0
  61. pyvolutionary/grasshopper/__init__.py +2 -0
  62. pyvolutionary/grasshopper/grasshopper_optimization_algorithm.py +73 -0
  63. pyvolutionary/grasshopper/models.py +29 -0
  64. pyvolutionary/grey_wolf/__init__.py +2 -0
  65. pyvolutionary/grey_wolf/grey_wolf_optimization.py +62 -0
  66. pyvolutionary/grey_wolf/models.py +9 -0
  67. pyvolutionary/harmony_search/__init__.py +2 -0
  68. pyvolutionary/harmony_search/harmony_search_optimization.py +64 -0
  69. pyvolutionary/harmony_search/models.py +29 -0
  70. pyvolutionary/helpers.py +290 -0
  71. pyvolutionary/imperialist_competitive/__init__.py +2 -0
  72. pyvolutionary/imperialist_competitive/classes.py +119 -0
  73. pyvolutionary/imperialist_competitive/imperialist_competitive_optimization.py +198 -0
  74. pyvolutionary/imperialist_competitive/models.py +47 -0
  75. pyvolutionary/invasive_weed/__init__.py +2 -0
  76. pyvolutionary/invasive_weed/invasive_weed_optimization.py +52 -0
  77. pyvolutionary/invasive_weed/models.py +51 -0
  78. pyvolutionary/krill_herd/__init__.py +2 -0
  79. pyvolutionary/krill_herd/krill_herd_optimization.py +179 -0
  80. pyvolutionary/krill_herd/models.py +62 -0
  81. pyvolutionary/levi_jaya_swarm/__init__.py +2 -0
  82. pyvolutionary/levi_jaya_swarm/levy_flight_jaya_swarm_optimization.py +40 -0
  83. pyvolutionary/levi_jaya_swarm/models.py +9 -0
  84. pyvolutionary/models.py +130 -0
  85. pyvolutionary/monarch_butterfly/__init__.py +2 -0
  86. pyvolutionary/monarch_butterfly/models.py +25 -0
  87. pyvolutionary/monarch_butterfly/monarch_butterfly_optimization.py +104 -0
  88. pyvolutionary/mountain_gazelle/__init__.py +2 -0
  89. pyvolutionary/mountain_gazelle/models.py +9 -0
  90. pyvolutionary/mountain_gazelle/mountain_gazelle_optimization.py +118 -0
  91. pyvolutionary/osprey/__init__.py +2 -0
  92. pyvolutionary/osprey/models.py +9 -0
  93. pyvolutionary/osprey/osprey_optimization.py +54 -0
  94. pyvolutionary/particle_swarm/__init__.py +2 -0
  95. pyvolutionary/particle_swarm/models.py +40 -0
  96. pyvolutionary/particle_swarm/particle_swarm_optimization.py +78 -0
  97. pyvolutionary/pathfinder_algorithm/__init__.py +2 -0
  98. pyvolutionary/pathfinder_algorithm/models.py +12 -0
  99. pyvolutionary/pathfinder_algorithm/pathfinder_algorithm_optimization.py +56 -0
  100. pyvolutionary/pelican/__init__.py +2 -0
  101. pyvolutionary/pelican/models.py +9 -0
  102. pyvolutionary/pelican/pelican_optimization.py +46 -0
  103. pyvolutionary/seagull/__init__.py +2 -0
  104. pyvolutionary/seagull/models.py +21 -0
  105. pyvolutionary/seagull/seagull_optimization.py +43 -0
  106. pyvolutionary/siberian_tiger/__init__.py +2 -0
  107. pyvolutionary/siberian_tiger/models.py +9 -0
  108. pyvolutionary/siberian_tiger/siberian_tiger_optimization.py +53 -0
  109. pyvolutionary/tasmanian_devil/__init__.py +2 -0
  110. pyvolutionary/tasmanian_devil/models.py +9 -0
  111. pyvolutionary/tasmanian_devil/tasmanian_devil_optimization.py +69 -0
  112. pyvolutionary/tests/fixtures.py +21 -0
  113. pyvolutionary/tests/test_african_vulture_optimization.py +34 -0
  114. pyvolutionary/tests/test_ant_colony_optimization.py +34 -0
  115. pyvolutionary/tests/test_aquila_optimization.py +27 -0
  116. pyvolutionary/tests/test_bacterial_foraging_optimization.py +37 -0
  117. pyvolutionary/tests/test_bat_optimization.py +34 -0
  118. pyvolutionary/tests/test_bee_colony_optimization.py +32 -0
  119. pyvolutionary/tests/test_camel_caravan_optimization.py +37 -0
  120. pyvolutionary/tests/test_coral_reef_optimization.py +39 -0
  121. pyvolutionary/tests/test_coyotes_optimization.py +32 -0
  122. pyvolutionary/tests/test_earthworms_optimization.py +37 -0
  123. pyvolutionary/tests/test_electromagnetic_field_optimization.py +35 -0
  124. pyvolutionary/tests/test_elephant_herd_optimization.py +34 -0
  125. pyvolutionary/tests/test_firefly_swarm_optimization.py +34 -0
  126. pyvolutionary/tests/test_fireworks_algorithm_optimization.py +36 -0
  127. pyvolutionary/tests/test_fish_school_search_optimization.py +37 -0
  128. pyvolutionary/tests/test_flower_pollination_algorithm_optimization.py +33 -0
  129. pyvolutionary/tests/test_forest_algorithm_optimization.py +36 -0
  130. pyvolutionary/tests/test_foxes_optimization.py +33 -0
  131. pyvolutionary/tests/test_genetic_algorithm_optimization.py +32 -0
  132. pyvolutionary/tests/test_grasshopper_optimization_algorithm.py +33 -0
  133. pyvolutionary/tests/test_grey_wolf_optimization.py +27 -0
  134. pyvolutionary/tests/test_harmony_search_optimization.py +33 -0
  135. pyvolutionary/tests/test_imperialist_competitive_optimization.py +40 -0
  136. pyvolutionary/tests/test_invasive_weed_optimization.py +34 -0
  137. pyvolutionary/tests/test_krill_herd_optimization.py +40 -0
  138. pyvolutionary/tests/test_levy_flight_jaya_swarm_optimization.py +33 -0
  139. pyvolutionary/tests/test_monarch_butterfly_optimization.py +33 -0
  140. pyvolutionary/tests/test_mountain_gazelle_optimization.py +27 -0
  141. pyvolutionary/tests/test_osprey_optimization.py +26 -0
  142. pyvolutionary/tests/test_particle_swarm_optimization.py +34 -0
  143. pyvolutionary/tests/test_pathfinder_algorithm_optimization.py +27 -0
  144. pyvolutionary/tests/test_pelican_optimization.py +27 -0
  145. pyvolutionary/tests/test_seagull_optimization.py +32 -0
  146. pyvolutionary/tests/test_siberian_tiger_optimization.py +27 -0
  147. pyvolutionary/tests/test_task_bounds.py +9 -0
  148. pyvolutionary/tests/test_tasmanian_devil_optimization.py +27 -0
  149. pyvolutionary/tests/test_utils.py +43 -0
  150. pyvolutionary/tests/test_virus_colony_search_optimization.py +33 -0
  151. pyvolutionary/tests/test_walrus_optimization.py +27 -0
  152. pyvolutionary/tests/test_whales_optimization.py +27 -0
  153. pyvolutionary/tests/test_wildebeest_herd_optimization.py +41 -0
  154. pyvolutionary/tests/test_zebra_optimization.py +27 -0
  155. pyvolutionary/utils.py +94 -0
  156. pyvolutionary/virus_colony_search/__init__.py +2 -0
  157. pyvolutionary/virus_colony_search/models.py +29 -0
  158. pyvolutionary/virus_colony_search/virus_colony_search_optimization.py +99 -0
  159. pyvolutionary/walrus/__init__.py +2 -0
  160. pyvolutionary/walrus/models.py +9 -0
  161. pyvolutionary/walrus/walrus_optimization.py +43 -0
  162. pyvolutionary/whales/__init__.py +2 -0
  163. pyvolutionary/whales/models.py +12 -0
  164. pyvolutionary/whales/whales_optimization.py +56 -0
  165. pyvolutionary/wildebeest_herd/__init__.py +2 -0
  166. pyvolutionary/wildebeest_herd/models.py +94 -0
  167. pyvolutionary/wildebeest_herd/wildebeest_herd_optimization.py +143 -0
  168. pyvolutionary/zebra/__init__.py +2 -0
  169. pyvolutionary/zebra/models.py +9 -0
  170. pyvolutionary/zebra/zebra_optimization.py +53 -0
  171. pyvolutionary-1.0.1.dist-info/METADATA +350 -0
  172. pyvolutionary-1.0.1.dist-info/RECORD +173 -0
  173. pyvolutionary-1.0.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,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,2 @@
1
+ from .models import Grasshopper, GrasshopperOptimizationConfig
2
+ from .grasshopper_optimization_algorithm import GrasshopperOptimization
@@ -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,2 @@
1
+ from .models import GreyWolf, GreyWolfOptimizationConfig
2
+ from .grey_wolf_optimization import GreyWolfOptimization
@@ -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,9 @@
1
+ from ..models import Agent, BaseOptimizationConfig
2
+
3
+
4
+ class GreyWolf(Agent):
5
+ pass
6
+
7
+
8
+ class GreyWolfOptimizationConfig(BaseOptimizationConfig):
9
+ pass
@@ -0,0 +1,2 @@
1
+ from .models import Harmony, HarmonySearchOptimizationConfig
2
+ from .harmony_search_optimization import HarmonySearchOptimization
@@ -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
@@ -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
@@ -0,0 +1,2 @@
1
+ from .models import Empire, ImperialistCompetitiveOptimizationConfig
2
+ from .imperialist_competitive_optimization import ImperialistCompetitiveOptimization