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,142 @@
1
+ import numpy as np
2
+
3
+ from ..helpers import parse_obj_doc # type: ignore
4
+ from ..abstract import OptimizationAbstract
5
+ from .models import Fish, FishSchoolSearchOptimizationConfig
6
+
7
+
8
+ class FishSchoolSearchOptimization(OptimizationAbstract):
9
+ """
10
+ Implementation of the Fish School Search Optimization.
11
+
12
+ Args:
13
+ config (FishSchoolSearchOptimizationConfig): an instance of FishSchoolSearchOptimizationConfig class.
14
+ {parse_obj_doc(FishSchoolSearchOptimizationConfig)}
15
+
16
+ Bibliography
17
+ ----------
18
+ [1] Bastos Filho, Lima Neto, Lins, D. O. Nascimento and P. Lima, A novel search algorithm based on fish school
19
+ behavior, in 2008 IEEE International Conference on Systems, Man and Cybernetics, Oct 2008, pp. 2646–2651.
20
+ """
21
+ def __init__(self, config: FishSchoolSearchOptimizationConfig, debug: bool | None = False):
22
+ super().__init__(config, debug)
23
+ self.__school_weight = self._config.population_size * self._config.w_scale / 2.0
24
+ self.__step_individual: np.ndarray | None = None
25
+ self.__step_volitive: np.ndarray | None = None
26
+
27
+ def before_initialization(self):
28
+ self.__step_individual = self._config.step_individual_init * self._bandwidth()
29
+ self.__step_volitive = self._config.step_volitive_init * self._bandwidth()
30
+
31
+ def _init_agent(self, position: list[float] | np.ndarray | None = None) -> Fish:
32
+ agent = super()._init_agent(position)
33
+ return Fish(**agent.model_dump(), weight=self._config.w_scale / 2.0)
34
+
35
+ def __update_steps__(self):
36
+ """
37
+ Update the steps of the fishes. The steps are updated according to the current cycle. The steps are updated
38
+ according to the following equations: \n
39
+ step_individual = step_individual_init - (cycles + 1) * (step_individual_init - step_individual_final) / max_cycles \n
40
+ step_volitive = step_volitive_init - (cycles + 1) * (step_volitive_init - step_volitive_final) / max_cycles \n
41
+ where: \n
42
+ step_individual_init: length of initial individual step \n
43
+ step_individual_final: length of final individual step \n
44
+ step_volitive_init: length of initial volatile step \n
45
+ step_volitive_final: length of final volatile step \n
46
+ cycles: current cycle \n
47
+ max_cycles: maximum number of cycles.
48
+ """
49
+ def update(init: float, final: float) -> np.ndarray:
50
+ return np.full(
51
+ self._task.space_dimension, init - (self._cycles + 1) * (init - final) / self._config.max_cycles
52
+ )
53
+
54
+ self.__step_individual = update(self._config.step_individual_init, self._config.step_individual_final)
55
+ self.__step_volitive = update(self._config.step_volitive_init, self._config.step_volitive_final)
56
+
57
+ def __feeding__(self):
58
+ """
59
+ Feed all fishes. The feeding is done according to the following equation: \n
60
+ weight = weight + delta_cost / max_delta_cost \n
61
+ where: \n
62
+ weight: weight of the fish \n
63
+ delta_cost: difference between the current cost and the previous cost of the fish \n
64
+ max_delta_cost: maximum difference between the current cost and the previous cost of all fishes.
65
+ """
66
+ def feeding(fish: Fish, mxd: float) -> Fish:
67
+ if mxd:
68
+ fish.weight += (fish.delta_cost / mxd)
69
+ fish.weight = np.clip(fish.weight, self._config.min_w, self._config.w_scale)
70
+ return fish
71
+
72
+ max_delta_cost = max([fish.delta_cost for fish in self._population])
73
+ self._population = [feeding(fish, max_delta_cost) for fish in self._population]
74
+
75
+ def __individual_movement__(self):
76
+ """
77
+ Perform individual movement for each fish.
78
+ """
79
+ def move(fish: Fish, sd: int, si: np.ndarray) -> Fish:
80
+ pos = np.array(fish.position)
81
+ new_fish = self._init_agent(pos + (si * self._uniform_position()))
82
+ if new_fish.cost < fish.cost:
83
+ delta_cost = abs(new_fish.cost - fish.cost)
84
+ delta_pos = (np.array(new_fish.position) - pos).tolist()
85
+
86
+ fish = new_fish
87
+ fish.delta_cost = delta_cost
88
+ fish.delta_pos = delta_pos
89
+ return fish
90
+
91
+ fish.delta_pos = np.zeros(sd).tolist()
92
+ fish.delta_cost = 0
93
+ return fish
94
+
95
+ self._population = [move(
96
+ fish, self._task.space_dimension, self.__step_individual
97
+ ) for fish in self._population]
98
+
99
+ def __collective_instinctive_movement__(self):
100
+ """
101
+ Perform collective instinctive movement.
102
+ """
103
+ cost_eval_enhanced = sum(
104
+ [fish.delta_cost * np.array(fish.delta_pos) for fish in self._population],
105
+ start=np.zeros(self._task.space_dimension)
106
+ )
107
+ density = sum([f.delta_cost for f in self._population])
108
+ if density != 0:
109
+ cost_eval_enhanced /= density
110
+ self._population = [
111
+ self._init_agent((np.array(fish.position) + cost_eval_enhanced).tolist()) for fish in self._population
112
+ ]
113
+
114
+ def __collective_volitive_movement__(self):
115
+ """
116
+ Perform collective volitive movement.
117
+ """
118
+ def move(fish: Fish, m: int, bc: np.ndarray, s: np.ndarray, d: int) -> Fish:
119
+ pos = np.array(fish.position)
120
+ step = (pos - bc) * s
121
+ new_pos = pos + (m * step * np.random.uniform(0, 1, d))
122
+ return self._init_agent(new_pos)
123
+
124
+ school_weight = sum([fish.weight for fish in self._population])
125
+ multiplier = -1 if school_weight > self.__school_weight else 1
126
+
127
+ barycenter = sum(
128
+ [np.array(fish.position) * fish.weight for fish in self._population],
129
+ start=np.zeros(self._task.space_dimension)
130
+ )
131
+ barycenter /= sum([fish.weight for fish in self._population])
132
+
133
+ self._population = [move(
134
+ fish, multiplier, barycenter, self.__step_volitive, self._task.space_dimension
135
+ ) for fish in self._population]
136
+
137
+ def optimization_step(self):
138
+ self.__individual_movement__()
139
+ self.__feeding__()
140
+ self.__collective_instinctive_movement__()
141
+ self.__collective_volitive_movement__()
142
+ self.__update_steps__()
@@ -0,0 +1,27 @@
1
+ import numpy as np
2
+
3
+ from ..models import Agent, BaseOptimizationConfig
4
+
5
+
6
+ class Fish(Agent):
7
+ weight: float
8
+ delta_pos: list[float] | None = None
9
+ delta_cost: float | None = None
10
+
11
+
12
+ class FishSchoolSearchOptimizationConfig(BaseOptimizationConfig):
13
+ """
14
+ Configuration class of the Fish School Search Optimization.\n
15
+ step_individual_init (float): length of initial individual step.\n
16
+ step_individual_final (float): length of final individual step.\n
17
+ step_volitive_init (float): length of initial volatile step.\n
18
+ step_volitive_final (float): length of final volatile step.\n
19
+ min_w (float): minimum weight of a fish.\n
20
+ w_scale (float): scale weight of a fish.
21
+ """
22
+ step_individual_init: float
23
+ step_individual_final: float
24
+ step_volitive_init: float
25
+ step_volitive_final: float
26
+ min_w: float
27
+ w_scale: float
@@ -0,0 +1,2 @@
1
+ from .models import Pollinator, FlowerPollinationAlgorithmOptimizationConfig
2
+ from .flower_pollination_algorithm_optimization import FlowerPollinationAlgorithmOptimization
@@ -0,0 +1,46 @@
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 FlowerPollinationAlgorithmOptimizationConfig
9
+
10
+
11
+ class FlowerPollinationAlgorithmOptimization(OptimizationAbstract):
12
+ """
13
+ Implementation of the Flower Pollination Algorithm Optimization.
14
+
15
+ Args:
16
+ config (FlowerPollinationAlgorithmOptimizationConfig): an instance of
17
+ FlowerPollinationAlgorithmOptimizationConfig class.
18
+ {parse_obj_doc(FlowerPollinationAlgorithmOptimizationConfig)}
19
+
20
+ Bibliography
21
+ ----------
22
+ [1] Yang, X.S., 2012, September. Flower pollination algorithm for global optimization. In International
23
+ conference on unconventional computing and natural computation (pp. 240-249). Springer, Berlin, Heidelberg.
24
+ """
25
+ def __init__(self, config: FlowerPollinationAlgorithmOptimizationConfig, debug: bool | None = False):
26
+ super().__init__(config, debug)
27
+
28
+ def optimization_step(self):
29
+ n_dims = self._task.space_dimension
30
+ pop_size = self._config.population_size
31
+
32
+ for idx, pollinator in enumerate(self._population):
33
+ position = np.array(pollinator.position)
34
+ best_position = np.array(self._best_agent.position)
35
+
36
+ if np.random.uniform() < self._config.p_s:
37
+ levy = get_levy_flight_step(multiplier=self._config.levy_multiplier, size=n_dims, case=-1)
38
+ pos_new = position + 1.0 / np.sqrt(self._cycles) * levy * (position - best_position)
39
+ else:
40
+ id1, id2 = np.random.choice(list(set(range(0, pop_size)) - {idx}), 2, replace=False)
41
+ pos_new = position + np.random.uniform() * (
42
+ np.array(self._population[id1].position) - np.array(self._population[id2].position)
43
+ )
44
+ self._population[idx] = self._greedy_select_agent(
45
+ self._init_agent(self._correct_position(pos_new)), pollinator
46
+ )
@@ -0,0 +1,29 @@
1
+ from pydantic import field_validator
2
+
3
+ from ..models import Agent, BaseOptimizationConfig
4
+
5
+
6
+ class Pollinator(Agent):
7
+ pass
8
+
9
+
10
+ class FlowerPollinationAlgorithmOptimizationConfig(BaseOptimizationConfig):
11
+ """
12
+ Configuration class of the Flower Pollination Algorithm.
13
+ p_s (float): [0.5, 0.95], switch probability.\n
14
+ levy_multiplier (float): [0.0001, 1000], multiplier factor of Levy-flight trajectory, depends on the problem.
15
+ """
16
+ p_s: float
17
+ levy_multiplier: float
18
+
19
+ @field_validator("p_s")
20
+ def correct_p_s(cls, v):
21
+ if not 0.5 <= v <= 0.95:
22
+ raise ValueError(f"\"p_s\" must be a float between 0.5 and 0.95. Got {v}")
23
+ return v
24
+
25
+ @field_validator("levy_multiplier")
26
+ def correct_levy_multiplier(cls, v):
27
+ if not 0.0001 <= v <= 1000:
28
+ raise ValueError(f"\"levy_multiplier\" must be a float between 0.0001 and 1000. Got {v}")
29
+ return v
@@ -0,0 +1,2 @@
1
+ from .models import Tree, ForestOptimizationAlgorithmConfig
2
+ from .forest_optimization_algorithm import ForestOptimizationAlgorithm
@@ -0,0 +1,121 @@
1
+ import numpy as np
2
+
3
+ from ..helpers import (
4
+ sort_and_trim,
5
+ sort_by_cost,
6
+ parse_obj_doc, # type: ignore
7
+ )
8
+ from ..abstract import OptimizationAbstract
9
+ from .models import Tree, ForestOptimizationAlgorithmConfig
10
+
11
+
12
+ class ForestOptimizationAlgorithm(OptimizationAbstract):
13
+ """
14
+ Implementation of the Forest Optimization Algorithm.
15
+
16
+ Args:
17
+ config (ForestOptimizationAlgorithmConfig): an instance of ForestOptimizationAlgorithmConfig class.
18
+ {parse_obj_doc(ForestOptimizationAlgorithmConfig)}
19
+
20
+ Bibliography
21
+ ----------
22
+ [1] Manizheh Ghaemi, Mohammad-Reza Feizi-Derakhshi, Forest Optimization Algorithm, Expert Systems with Applications,
23
+ Volume 41, Issue 15, 2014, Pages 6676-6687, ISSN 0957-4174, https://doi.org/10.1016/j.eswa.2014.05.009.
24
+ """
25
+ def __init__(self, config: ForestOptimizationAlgorithmConfig, debug: bool | None = False):
26
+ super().__init__(config, debug)
27
+ self.__dx: np.ndarray | None = None
28
+
29
+ def before_initialization(self):
30
+ _, ub = self._get_bounds()
31
+ self.__dx = np.absolute(ub) / 5.0
32
+
33
+ def _init_agent(self, position: list[float] | np.ndarray | None = None) -> Tree:
34
+ agent = super()._init_agent(position)
35
+ return Tree(**agent.model_dump())
36
+
37
+ def __local_seeding__(self) -> list[Tree]:
38
+ """
39
+ Local optimum search stage that should prevent getting stuck in a local optimum. It is performed by randomly
40
+ changing the position of a tree, i.e. by randomly seeding a tree around it. The number of changes is defined by
41
+ the local_seeding_changes parameter. Finally, the age of each tree is increased by one and the seeded trees are
42
+ added to the population.
43
+ :return: list of seeded trees.
44
+ :rtype: list[Tree]
45
+ """
46
+ def update_position(position: np.ndarray) -> np.ndarray:
47
+ indices = np.random.choice(self._task.space_dimension, self._config.local_seeding_changes, replace=False)
48
+ position[indices] += np.random.uniform(-self.__dx[indices], self.__dx[indices])
49
+ return position
50
+
51
+ # repeat local_seeding_changes times each tree that is going to be removed
52
+ candidates = [
53
+ self._init_agent(update_position(np.array(tree.position)))
54
+ for tree in self._population if tree.age == 0
55
+ for _ in range(self._config.local_seeding_changes)
56
+ ]
57
+
58
+ # increase the age of each tree
59
+ for tree in self._population:
60
+ tree.age += 1
61
+
62
+ return candidates
63
+
64
+ def __global_seeding__(self, candidates: list[Tree], size: int) -> list[Tree]:
65
+ """
66
+ Global optimum search stage that should prevent getting stuck in a local optimum. It is performed by randomly
67
+ changing the position of a tree, i.e. by randomly seeding a tree around it. The number of changes is defined by
68
+ the global_seeding_changes parameter. The difference between this method and the local_seeding method is that
69
+ this method is performed on trees that are dying, i.e. that are going to be removed.
70
+ :param candidates: list of trees that are dying, i.e. that are going to be removed.
71
+ :param size: number of seeds to be generated.
72
+ :return: list of seeded trees.
73
+ """
74
+ def update_position(position: np.ndarray) -> np.ndarray:
75
+ indices = np.random.choice(self._task.space_dimension, self._config.global_seeding_changes, replace=False)
76
+ position[indices] = self._uniform_coordinates(indices)
77
+ return position
78
+
79
+ return [self._init_agent(
80
+ update_position(np.array(candidates[idx].position))
81
+ ) for idx in np.random.choice(len(candidates), size, replace=False)]
82
+
83
+ def __remove_lifetime_exceeded__(self) -> list[Tree]:
84
+ """
85
+ Remove trees that exceeded their lifetime. The lifetime of a tree is defined by the lifetime parameter.
86
+ :return: list of trees that exceeded their lifetime.
87
+ :rtype: list[Tree]
88
+ """
89
+ candidates = [tree for tree in self._population if tree.age > self._config.lifetime]
90
+ # remove trees that exceeded their lifetime from the population
91
+ self._population = [tree for tree in self._population if tree.age <= self._config.lifetime]
92
+ return candidates
93
+
94
+ def __survival_of_the_fittest__(self, candidates: list[Tree]):
95
+ """
96
+ Survival of the fittest stage. It is performed by removing the trees with the highest cost function value.
97
+ :param candidates: list of trees that are dying, i.e. that are going to be removed.
98
+ :return: candidates to be removed.
99
+ :rtype: list[Tree]
100
+ """
101
+ sort_by_cost(self._population)
102
+ candidates += self._population[self._config.area_limit+1:]
103
+ self._population = self._population[:self._config.area_limit]
104
+ return candidates
105
+
106
+ def optimization_step(self):
107
+ # add seeded trees to the population
108
+ self._population += self.__local_seeding__()
109
+
110
+ # identify trees that exceeded their lifetime and area limit
111
+ dying_trees = self.__survival_of_the_fittest__(
112
+ self.__remove_lifetime_exceeded__()
113
+ )
114
+
115
+ # global seeding
116
+ gsn = int(self._config.transfer_rate * len(dying_trees))
117
+ if gsn > 0:
118
+ self._population += self.__global_seeding__(dying_trees, gsn)
119
+
120
+ self._population = sort_and_trim(self._population, self._config.population_size)
121
+ self._population[0].age = 0
@@ -0,0 +1,53 @@
1
+ from pydantic import model_validator, field_validator
2
+
3
+ from ..models import Agent, BaseOptimizationConfig
4
+
5
+
6
+ class Tree(Agent):
7
+ age: int = 0
8
+
9
+
10
+ class ForestOptimizationAlgorithmConfig(BaseOptimizationConfig):
11
+ """
12
+ Configuration class of the Forest Optimization Algorithm.
13
+ lifetime (int): the lifetime of a tree.\n
14
+ area_limit (int): the area limit of a tree.\n
15
+ local_seeding_changes (int): the number of local seeding changes.\n
16
+ global_seeding_changes (int): the number of global seeding changes.\n
17
+ transfer_rate (float): (0, 1), the transfer rate.
18
+ """
19
+ lifetime: int = 10
20
+ area_limit: int = 10
21
+ local_seeding_changes: int = 3
22
+ global_seeding_changes: int = 3
23
+ transfer_rate: float = 0.5
24
+
25
+ @model_validator(mode="after")
26
+ def _check_area_limit(self) -> "ForestOptimizationAlgorithmConfig":
27
+ if not 0 < self.area_limit < self.population_size:
28
+ raise ValueError('area_limit must be a positive integer, lower than the population size')
29
+ return self
30
+
31
+ @field_validator('lifetime')
32
+ def _check_lifetime(cls, v):
33
+ if v < 0:
34
+ raise ValueError('lifetime must be a positive integer')
35
+ return v
36
+
37
+ @field_validator('local_seeding_changes')
38
+ def _check_local_seeding_changes(cls, v):
39
+ if v < 0:
40
+ raise ValueError('local_seeding_changes must be a positive integer')
41
+ return v
42
+
43
+ @field_validator('global_seeding_changes')
44
+ def _check_global_seeding_changes(cls, v):
45
+ if v < 0:
46
+ raise ValueError('global_seeding_changes must be a positive integer')
47
+ return v
48
+
49
+ @field_validator('transfer_rate')
50
+ def _check_transfer_rate(cls, v):
51
+ if not 0 < v < 1:
52
+ raise ValueError('transfer_rate must be a float between 0 and 1')
53
+ return v
@@ -0,0 +1,2 @@
1
+ from .models import Fox, FoxOptimizationConfig
2
+ from .fox_optimization import FoxOptimization
@@ -0,0 +1,43 @@
1
+ import numpy as np
2
+
3
+ from ..helpers import parse_obj_doc # type: ignore
4
+ from ..abstract import OptimizationAbstract
5
+ from .models import FoxOptimizationConfig
6
+
7
+
8
+ class FoxOptimization(OptimizationAbstract):
9
+ """
10
+ Implementation of the Fox Optimization algorithm.
11
+
12
+ Args:
13
+ config (FoxOptimizationConfig): an instance of FoxOptimizationConfig class.
14
+ {parse_obj_doc(FoxOptimizationConfig)}
15
+
16
+ Bibliography
17
+ ----------
18
+ [1] Mohammed, H., & Rashid, T. (2023). FOX: a FOX-inspired optimization algorithm. Applied Intelligence, 53(1),
19
+ 1030-1050.
20
+ """
21
+ def __init__(self, config: FoxOptimizationConfig, debug: bool | None = False):
22
+ super().__init__(config, debug)
23
+ self.__mint = np.inf
24
+
25
+ def optimization_step(self):
26
+ a = 2 * (1 - (1.0 / self._cycles))
27
+ best_position = np.array(self._best_agent.position)
28
+
29
+ for idx, fox in enumerate(self._population):
30
+ if np.random.random() >= 0.5:
31
+ time1 = np.random.random(self._task.space_dimension)
32
+ sps = best_position / time1
33
+ travel_distance = 0.5 * sps * time1
34
+ tt = np.mean(time1)
35
+ jump = 0.5 * 9.81 * (tt / 2) ** 2
36
+ pos_new = travel_distance * jump * (self._config.c1 if np.random.random() > 0.18 else self._config.c2)
37
+ if self.__mint > tt:
38
+ self.__mint = tt
39
+ else:
40
+ pos_new = best_position + np.random.standard_normal(self._task.space_dimension) * (self.__mint * a)
41
+ agent = self._init_agent(self._correct_position(pos_new))
42
+
43
+ self._population[idx] = self._greedy_select_agent(fox, agent)
@@ -0,0 +1,29 @@
1
+ from pydantic import field_validator
2
+
3
+ from ..models import Agent, BaseOptimizationConfig
4
+
5
+
6
+ class Fox(Agent):
7
+ pass
8
+
9
+
10
+ class FoxOptimizationConfig(BaseOptimizationConfig):
11
+ """
12
+ Configuration class of the Fox Optimization algorithm.
13
+ c1 (float): [0, 0.18], the probability of jumping (c1 in the paper)
14
+ c2 (float): [0.19, 1], the probability of jumping (c2 in the paper)
15
+ """
16
+ c1: float
17
+ c2: float
18
+
19
+ @field_validator("c1")
20
+ def correct_c1(cls, v):
21
+ if not 0 <= v <= 0.18:
22
+ raise ValueError("c1 must be in [0, 0.18]")
23
+ return v
24
+
25
+ @field_validator("c2")
26
+ def correct_c2(cls, v):
27
+ if not 0.19 <= v <= 1:
28
+ raise ValueError("c2 must be in [0.19, 1]")
29
+ return v
@@ -0,0 +1,2 @@
1
+ from .models import Gene, GeneticAlgorithmOptimizationConfig
2
+ from .genetic_algorithm_optimization import GeneticAlgorithmOptimization
@@ -0,0 +1,167 @@
1
+ import numpy as np
2
+
3
+ from ..helpers import parse_obj_doc # type: ignore
4
+ from ..abstract import OptimizationAbstract
5
+ from .models import Gene, GeneticAlgorithmOptimizationConfig
6
+
7
+
8
+ class GeneticAlgorithmOptimization(OptimizationAbstract):
9
+ """
10
+ Implementation of the Genetic Algorithm Optimization algorithm.
11
+
12
+ Args:
13
+ config (GeneticAlgorithmOptimizationConfig): an instance of GeneticAlgorithmOptimizationConfig class.
14
+ {parse_obj_doc(GeneticAlgorithmOptimizationConfig)}
15
+
16
+ Bibliography
17
+ ----------
18
+ [1] Alander, Jarmo. (1999). An Indexed Bibliography of Genetic Algorithm Implementations.
19
+ [2] Goldberg, David E. (1989). Genetic Algorithms in Search, Optimization, and Machine Learning. Addison-Wesley.
20
+ ISBN 978-0201157673. OCLC 18319123.
21
+ [3] Holland, John H. (1975). Adaptation in Natural and Artificial Systems. University of Michigan Press.
22
+ ISBN 978-0262581110. OCLC 1635804.
23
+ """
24
+ def __init__(self, config: GeneticAlgorithmOptimizationConfig, debug: bool | None = False):
25
+ super().__init__(config, debug)
26
+ self.__n_bits: int | None = None
27
+ self.__p_mutation: float | None = None
28
+ self.__bit_positions: list[list[int]] | None = None
29
+
30
+ def before_initialization(self):
31
+ self.__n_bits = 8 * self._task.space_dimension
32
+ self.__p_mutation = 1.0 / (float(self.__n_bits) * self._task.space_dimension)
33
+
34
+ def _init_population(self):
35
+ self.__bit_positions = [np.random.randint(
36
+ 0, 2, self.__n_bits * self._task.space_dimension
37
+ ).tolist() for _ in range(0, self._config.population_size)]
38
+
39
+ self._population = self.__from_bit_positions_to_population__()
40
+
41
+ def __decode__(self, bitstring: list[int]) -> list[float]:
42
+ """
43
+ Decode a bitstring to a float list.
44
+ :param bitstring: the bitstring to decode
45
+ :return: the decoded float list
46
+ :rtype: list[float]
47
+ """
48
+ decoded = []
49
+ largest = 2 ** self.__n_bits
50
+
51
+ lb, ub = self._get_bounds()
52
+ for i in range(0, self._task.space_dimension):
53
+ # extract the substring
54
+ start, end = i * self.__n_bits, (i * self.__n_bits) + self.__n_bits
55
+ substring = bitstring[start:end]
56
+ # convert bitstring to a string of chars
57
+ chars = ''.join([str(s) for s in substring])
58
+ # convert string to integer
59
+ integer = int(chars, 2)
60
+ # scale integer to desired range
61
+ value = lb[i] + (integer / largest) * (ub[i] - lb[i])
62
+ # store
63
+ decoded.append(value)
64
+ return decoded
65
+
66
+ def __encode__(self, position: list[float]) -> list[int]:
67
+ """
68
+ Encode a float list to a bitstring.
69
+ :param position: the float list to encode
70
+ :return: the encoded bitstring
71
+ :rtype: list[int]
72
+ """
73
+ encoded = []
74
+
75
+ lb, ub = self._get_bounds()
76
+ for i in range(0, self._task.space_dimension):
77
+ # scale integer to desired range
78
+ value = (position[i] - lb[i]) / (ub[i] - lb[i])
79
+ # convert to bitstring
80
+ value = bin(int(value * (2 ** self.__n_bits)))[2:].zfill(self.__n_bits)
81
+ binary_representation = [int(bit) for bit in value]
82
+ # append to the encoded list
83
+ encoded.extend(binary_representation)
84
+ return encoded
85
+
86
+ def __selection__(self, k: int | None = 3) -> list[int]:
87
+ """
88
+ Select a parent from the population. The selection is based on the fitness of the genes. The lower the cost,
89
+ the higher the fitness. The selection is performed by tournament selection. The tournament size is k. The
90
+ selection is repeated until k parents are selected. The best parent is selected from the k parents. The best is
91
+ the one with the lowest cost. The selection is repeated until the population size is reached. The selection is
92
+ performed with replacement. The same parent can be selected multiple times. The selection is performed with a 3
93
+ tournament selection by default.
94
+ :param k: the tournament size
95
+ :return: the selected parent
96
+ """
97
+ scores = [gene.cost for gene in self._population]
98
+
99
+ # first random selection
100
+ selection_ix = np.random.randint(len(self.__bit_positions))
101
+ for ix in np.random.randint(0, len(self.__bit_positions), k - 1):
102
+ # check if better (e.g. perform a tournament)
103
+ if scores[ix] < scores[selection_ix]:
104
+ selection_ix = ix
105
+ return self.__bit_positions[selection_ix]
106
+
107
+ def __crossover__(self, parent1: list[int], parent2: list[int]) -> tuple[list[int], list[int]]:
108
+ """
109
+ Crossover two parents to create two children. The crossover is performed with a probability of px_over. The
110
+ crossover point is selected randomly. The crossover point is not on the end of the string. The crossover is
111
+ performed by default. The children are copies of the parents by default.
112
+ :param parent1: the first parent
113
+ :param parent2: the second parent
114
+ :return: the two children
115
+ :rtype: tuple[list[int], list[int]]
116
+ """
117
+ # children are copies of parents by default
118
+ c1, c2 = parent1.copy(), parent2.copy()
119
+ # check for recombination
120
+ if np.random.rand() < self._config.px_over:
121
+ # select crossover point that is not on the end of the string
122
+ pt = np.random.randint(1, len(parent1) - 2)
123
+ # perform crossover
124
+ c1 = parent1[:pt] + parent2[pt:]
125
+ c2 = parent1[:pt] + parent2[pt:]
126
+ return c1, c2
127
+
128
+ def __mutation__(self, bitstring: list[int]) -> list[int]:
129
+ """
130
+ Mutate a bitstring by flipping bits with a probability of pm_mutate. The mutation is performed by default.
131
+ :param bitstring: the bitstring to mutate
132
+ :return: the mutated bitstring
133
+ :rtype: list[int]
134
+ """
135
+ return [1 - bit if np.random.rand() < self.__p_mutation else bit for bit in bitstring]
136
+
137
+ def __from_bit_positions_to_population__(self) -> list[Gene]:
138
+ """
139
+ Convert the bit positions to a population. The cost of each gene is calculated. The fitness of each gene is
140
+ calculated.
141
+ :return: the population
142
+ :rtype: list[Gene]
143
+ """
144
+ def to_gene(bitstring: list[int]) -> Gene:
145
+ position = self.__decode__(bitstring)
146
+ return Gene(**self._init_agent(position).model_dump())
147
+
148
+ return list(map(to_gene, self.__bit_positions))
149
+
150
+ def optimization_step(self):
151
+ # select parents
152
+ selected = [self.__selection__() for _ in range(0, self._config.population_size)]
153
+
154
+ # create the next generation
155
+ children = []
156
+ for i in range(0, self._config.population_size, 2):
157
+ # get selected parents in pairs
158
+ p1, p2 = selected[i], selected[i + 1]
159
+ # crossover
160
+ c1, c2 = self.__crossover__(p1, p2)
161
+ # mutations and store for next generation
162
+ children.append(self.__mutation__(c1))
163
+ children.append(self.__mutation__(c2))
164
+
165
+ # update population
166
+ self.__bit_positions = children
167
+ self._population = self.__from_bit_positions_to_population__()