pymoo 0.6.1.6__cp312-cp312-macosx_10_13_universal2.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 (337) hide show
  1. pymoo/__init__.py +3 -0
  2. pymoo/algorithms/__init__.py +0 -0
  3. pymoo/algorithms/base/__init__.py +0 -0
  4. pymoo/algorithms/base/bracket.py +38 -0
  5. pymoo/algorithms/base/genetic.py +110 -0
  6. pymoo/algorithms/base/line.py +62 -0
  7. pymoo/algorithms/base/local.py +39 -0
  8. pymoo/algorithms/base/meta.py +79 -0
  9. pymoo/algorithms/hyperparameters.py +91 -0
  10. pymoo/algorithms/moo/__init__.py +0 -0
  11. pymoo/algorithms/moo/age.py +310 -0
  12. pymoo/algorithms/moo/age2.py +194 -0
  13. pymoo/algorithms/moo/cmopso.py +239 -0
  14. pymoo/algorithms/moo/ctaea.py +305 -0
  15. pymoo/algorithms/moo/dnsga2.py +80 -0
  16. pymoo/algorithms/moo/kgb.py +450 -0
  17. pymoo/algorithms/moo/moead.py +183 -0
  18. pymoo/algorithms/moo/mopso_cd.py +309 -0
  19. pymoo/algorithms/moo/nsga2.py +113 -0
  20. pymoo/algorithms/moo/nsga3.py +361 -0
  21. pymoo/algorithms/moo/pinsga2.py +370 -0
  22. pymoo/algorithms/moo/rnsga2.py +188 -0
  23. pymoo/algorithms/moo/rnsga3.py +246 -0
  24. pymoo/algorithms/moo/rvea.py +214 -0
  25. pymoo/algorithms/moo/sms.py +196 -0
  26. pymoo/algorithms/moo/spea2.py +191 -0
  27. pymoo/algorithms/moo/unsga3.py +49 -0
  28. pymoo/algorithms/soo/__init__.py +0 -0
  29. pymoo/algorithms/soo/convex/__init__.py +0 -0
  30. pymoo/algorithms/soo/nonconvex/__init__.py +0 -0
  31. pymoo/algorithms/soo/nonconvex/brkga.py +162 -0
  32. pymoo/algorithms/soo/nonconvex/cmaes.py +556 -0
  33. pymoo/algorithms/soo/nonconvex/de.py +283 -0
  34. pymoo/algorithms/soo/nonconvex/direct.py +148 -0
  35. pymoo/algorithms/soo/nonconvex/es.py +213 -0
  36. pymoo/algorithms/soo/nonconvex/g3pcx.py +94 -0
  37. pymoo/algorithms/soo/nonconvex/ga.py +95 -0
  38. pymoo/algorithms/soo/nonconvex/ga_niching.py +223 -0
  39. pymoo/algorithms/soo/nonconvex/isres.py +74 -0
  40. pymoo/algorithms/soo/nonconvex/nelder.py +251 -0
  41. pymoo/algorithms/soo/nonconvex/nrbo.py +191 -0
  42. pymoo/algorithms/soo/nonconvex/optuna.py +80 -0
  43. pymoo/algorithms/soo/nonconvex/pattern.py +185 -0
  44. pymoo/algorithms/soo/nonconvex/pso.py +337 -0
  45. pymoo/algorithms/soo/nonconvex/pso_ep.py +307 -0
  46. pymoo/algorithms/soo/nonconvex/random_search.py +25 -0
  47. pymoo/algorithms/soo/nonconvex/sres.py +56 -0
  48. pymoo/algorithms/soo/univariate/__init__.py +0 -0
  49. pymoo/algorithms/soo/univariate/exp.py +46 -0
  50. pymoo/algorithms/soo/univariate/golden.py +65 -0
  51. pymoo/algorithms/soo/univariate/quadr_interp.py +81 -0
  52. pymoo/algorithms/soo/univariate/wolfe.py +163 -0
  53. pymoo/config.py +33 -0
  54. pymoo/constraints/__init__.py +3 -0
  55. pymoo/constraints/adaptive.py +66 -0
  56. pymoo/constraints/as_obj.py +56 -0
  57. pymoo/constraints/as_penalty.py +41 -0
  58. pymoo/constraints/eps.py +34 -0
  59. pymoo/constraints/from_bounds.py +36 -0
  60. pymoo/core/__init__.py +0 -0
  61. pymoo/core/algorithm.py +408 -0
  62. pymoo/core/callback.py +38 -0
  63. pymoo/core/crossover.py +79 -0
  64. pymoo/core/decision_making.py +102 -0
  65. pymoo/core/decomposition.py +76 -0
  66. pymoo/core/duplicate.py +163 -0
  67. pymoo/core/evaluator.py +116 -0
  68. pymoo/core/indicator.py +34 -0
  69. pymoo/core/individual.py +784 -0
  70. pymoo/core/infill.py +65 -0
  71. pymoo/core/initialization.py +44 -0
  72. pymoo/core/mating.py +39 -0
  73. pymoo/core/meta.py +21 -0
  74. pymoo/core/mixed.py +164 -0
  75. pymoo/core/mutation.py +44 -0
  76. pymoo/core/operator.py +46 -0
  77. pymoo/core/parameters.py +134 -0
  78. pymoo/core/plot.py +208 -0
  79. pymoo/core/population.py +180 -0
  80. pymoo/core/problem.py +373 -0
  81. pymoo/core/recorder.py +99 -0
  82. pymoo/core/repair.py +23 -0
  83. pymoo/core/replacement.py +96 -0
  84. pymoo/core/result.py +52 -0
  85. pymoo/core/sampling.py +45 -0
  86. pymoo/core/selection.py +61 -0
  87. pymoo/core/solution.py +10 -0
  88. pymoo/core/survival.py +107 -0
  89. pymoo/core/termination.py +70 -0
  90. pymoo/core/variable.py +415 -0
  91. pymoo/decomposition/__init__.py +0 -0
  92. pymoo/decomposition/aasf.py +24 -0
  93. pymoo/decomposition/asf.py +10 -0
  94. pymoo/decomposition/pbi.py +13 -0
  95. pymoo/decomposition/perp_dist.py +13 -0
  96. pymoo/decomposition/tchebicheff.py +11 -0
  97. pymoo/decomposition/util.py +13 -0
  98. pymoo/decomposition/weighted_sum.py +8 -0
  99. pymoo/docs.py +187 -0
  100. pymoo/experimental/__init__.py +0 -0
  101. pymoo/experimental/algorithms/__init__.py +0 -0
  102. pymoo/experimental/algorithms/gde3.py +57 -0
  103. pymoo/functions/__init__.py +135 -0
  104. pymoo/functions/compiled/__init__.py +0 -0
  105. pymoo/functions/compiled/calc_perpendicular_distance.cpp +27464 -0
  106. pymoo/functions/compiled/calc_perpendicular_distance.cpython-312-darwin.so +0 -0
  107. pymoo/functions/compiled/decomposition.cpp +28853 -0
  108. pymoo/functions/compiled/decomposition.cpython-312-darwin.so +0 -0
  109. pymoo/functions/compiled/info.cpp +7058 -0
  110. pymoo/functions/compiled/info.cpython-312-darwin.so +0 -0
  111. pymoo/functions/compiled/mnn.cpp +30095 -0
  112. pymoo/functions/compiled/mnn.cpython-312-darwin.so +0 -0
  113. pymoo/functions/compiled/non_dominated_sorting.cpp +35692 -0
  114. pymoo/functions/compiled/non_dominated_sorting.cpython-312-darwin.so +0 -0
  115. pymoo/functions/compiled/pruning_cd.cpp +29248 -0
  116. pymoo/functions/compiled/pruning_cd.cpython-312-darwin.so +0 -0
  117. pymoo/functions/compiled/stochastic_ranking.cpp +28042 -0
  118. pymoo/functions/compiled/stochastic_ranking.cpython-312-darwin.so +0 -0
  119. pymoo/functions/standard/__init__.py +1 -0
  120. pymoo/functions/standard/calc_perpendicular_distance.py +20 -0
  121. pymoo/functions/standard/decomposition.py +18 -0
  122. pymoo/functions/standard/hv.py +5 -0
  123. pymoo/functions/standard/mnn.py +78 -0
  124. pymoo/functions/standard/non_dominated_sorting.py +474 -0
  125. pymoo/functions/standard/pruning_cd.py +93 -0
  126. pymoo/functions/standard/stochastic_ranking.py +42 -0
  127. pymoo/gradient/__init__.py +24 -0
  128. pymoo/gradient/automatic.py +85 -0
  129. pymoo/gradient/grad_autograd.py +105 -0
  130. pymoo/gradient/grad_complex.py +35 -0
  131. pymoo/gradient/grad_jax.py +51 -0
  132. pymoo/gradient/numpy.py +22 -0
  133. pymoo/gradient/toolbox/__init__.py +19 -0
  134. pymoo/indicators/__init__.py +0 -0
  135. pymoo/indicators/distance_indicator.py +55 -0
  136. pymoo/indicators/gd.py +7 -0
  137. pymoo/indicators/gd_plus.py +7 -0
  138. pymoo/indicators/hv/__init__.py +59 -0
  139. pymoo/indicators/hv/approximate.py +105 -0
  140. pymoo/indicators/hv/exact.py +68 -0
  141. pymoo/indicators/hv/exact_2d.py +102 -0
  142. pymoo/indicators/igd.py +7 -0
  143. pymoo/indicators/igd_plus.py +7 -0
  144. pymoo/indicators/kktpm.py +151 -0
  145. pymoo/indicators/migd.py +55 -0
  146. pymoo/indicators/rmetric.py +203 -0
  147. pymoo/indicators/spacing.py +52 -0
  148. pymoo/mcdm/__init__.py +0 -0
  149. pymoo/mcdm/compromise_programming.py +19 -0
  150. pymoo/mcdm/high_tradeoff.py +40 -0
  151. pymoo/mcdm/pseudo_weights.py +32 -0
  152. pymoo/operators/__init__.py +0 -0
  153. pymoo/operators/control.py +190 -0
  154. pymoo/operators/crossover/__init__.py +0 -0
  155. pymoo/operators/crossover/binx.py +47 -0
  156. pymoo/operators/crossover/dex.py +125 -0
  157. pymoo/operators/crossover/erx.py +164 -0
  158. pymoo/operators/crossover/expx.py +53 -0
  159. pymoo/operators/crossover/hux.py +37 -0
  160. pymoo/operators/crossover/nox.py +25 -0
  161. pymoo/operators/crossover/ox.py +88 -0
  162. pymoo/operators/crossover/pcx.py +84 -0
  163. pymoo/operators/crossover/pntx.py +49 -0
  164. pymoo/operators/crossover/sbx.py +137 -0
  165. pymoo/operators/crossover/spx.py +5 -0
  166. pymoo/operators/crossover/ux.py +20 -0
  167. pymoo/operators/mutation/__init__.py +0 -0
  168. pymoo/operators/mutation/bitflip.py +17 -0
  169. pymoo/operators/mutation/gauss.py +60 -0
  170. pymoo/operators/mutation/inversion.py +42 -0
  171. pymoo/operators/mutation/nom.py +7 -0
  172. pymoo/operators/mutation/pm.py +96 -0
  173. pymoo/operators/mutation/rm.py +23 -0
  174. pymoo/operators/repair/__init__.py +0 -0
  175. pymoo/operators/repair/bounce_back.py +32 -0
  176. pymoo/operators/repair/bounds_repair.py +97 -0
  177. pymoo/operators/repair/inverse_penalty.py +91 -0
  178. pymoo/operators/repair/rounding.py +18 -0
  179. pymoo/operators/repair/to_bound.py +31 -0
  180. pymoo/operators/repair/vtype.py +11 -0
  181. pymoo/operators/sampling/__init__.py +0 -0
  182. pymoo/operators/sampling/lhs.py +76 -0
  183. pymoo/operators/sampling/rnd.py +52 -0
  184. pymoo/operators/selection/__init__.py +0 -0
  185. pymoo/operators/selection/rnd.py +75 -0
  186. pymoo/operators/selection/tournament.py +78 -0
  187. pymoo/operators/survival/__init__.py +0 -0
  188. pymoo/operators/survival/rank_and_crowding/__init__.py +1 -0
  189. pymoo/operators/survival/rank_and_crowding/classes.py +212 -0
  190. pymoo/operators/survival/rank_and_crowding/metrics.py +208 -0
  191. pymoo/optimize.py +72 -0
  192. pymoo/parallelization/__init__.py +15 -0
  193. pymoo/parallelization/dask.py +25 -0
  194. pymoo/parallelization/joblib.py +28 -0
  195. pymoo/parallelization/ray.py +31 -0
  196. pymoo/parallelization/starmap.py +24 -0
  197. pymoo/problems/__init__.py +157 -0
  198. pymoo/problems/dyn.py +47 -0
  199. pymoo/problems/dynamic/__init__.py +0 -0
  200. pymoo/problems/dynamic/cec2015.py +108 -0
  201. pymoo/problems/dynamic/df.py +451 -0
  202. pymoo/problems/dynamic/misc.py +167 -0
  203. pymoo/problems/functional.py +48 -0
  204. pymoo/problems/many/__init__.py +5 -0
  205. pymoo/problems/many/cdtlz.py +159 -0
  206. pymoo/problems/many/dcdtlz.py +88 -0
  207. pymoo/problems/many/dtlz.py +264 -0
  208. pymoo/problems/many/wfg.py +553 -0
  209. pymoo/problems/multi/__init__.py +14 -0
  210. pymoo/problems/multi/bnh.py +34 -0
  211. pymoo/problems/multi/carside.py +48 -0
  212. pymoo/problems/multi/clutch.py +104 -0
  213. pymoo/problems/multi/csi.py +55 -0
  214. pymoo/problems/multi/ctp.py +198 -0
  215. pymoo/problems/multi/dascmop.py +213 -0
  216. pymoo/problems/multi/kursawe.py +25 -0
  217. pymoo/problems/multi/modact.py +68 -0
  218. pymoo/problems/multi/mw.py +400 -0
  219. pymoo/problems/multi/omnitest.py +48 -0
  220. pymoo/problems/multi/osy.py +32 -0
  221. pymoo/problems/multi/srn.py +28 -0
  222. pymoo/problems/multi/sympart.py +94 -0
  223. pymoo/problems/multi/tnk.py +24 -0
  224. pymoo/problems/multi/truss2d.py +83 -0
  225. pymoo/problems/multi/welded_beam.py +41 -0
  226. pymoo/problems/multi/wrm.py +36 -0
  227. pymoo/problems/multi/zdt.py +151 -0
  228. pymoo/problems/multi_to_single.py +22 -0
  229. pymoo/problems/single/__init__.py +12 -0
  230. pymoo/problems/single/ackley.py +24 -0
  231. pymoo/problems/single/cantilevered_beam.py +34 -0
  232. pymoo/problems/single/flowshop_scheduling.py +113 -0
  233. pymoo/problems/single/g.py +874 -0
  234. pymoo/problems/single/griewank.py +18 -0
  235. pymoo/problems/single/himmelblau.py +15 -0
  236. pymoo/problems/single/knapsack.py +49 -0
  237. pymoo/problems/single/mopta08.py +26 -0
  238. pymoo/problems/single/multimodal.py +20 -0
  239. pymoo/problems/single/pressure_vessel.py +30 -0
  240. pymoo/problems/single/rastrigin.py +20 -0
  241. pymoo/problems/single/rosenbrock.py +22 -0
  242. pymoo/problems/single/schwefel.py +18 -0
  243. pymoo/problems/single/simple.py +13 -0
  244. pymoo/problems/single/sphere.py +19 -0
  245. pymoo/problems/single/traveling_salesman.py +79 -0
  246. pymoo/problems/single/zakharov.py +19 -0
  247. pymoo/problems/static.py +14 -0
  248. pymoo/problems/util.py +42 -0
  249. pymoo/problems/zero_to_one.py +27 -0
  250. pymoo/termination/__init__.py +23 -0
  251. pymoo/termination/collection.py +12 -0
  252. pymoo/termination/cv.py +48 -0
  253. pymoo/termination/default.py +45 -0
  254. pymoo/termination/delta.py +64 -0
  255. pymoo/termination/fmin.py +16 -0
  256. pymoo/termination/ftol.py +144 -0
  257. pymoo/termination/indicator.py +49 -0
  258. pymoo/termination/max_eval.py +14 -0
  259. pymoo/termination/max_gen.py +15 -0
  260. pymoo/termination/max_time.py +20 -0
  261. pymoo/termination/robust.py +34 -0
  262. pymoo/termination/xtol.py +33 -0
  263. pymoo/util/__init__.py +33 -0
  264. pymoo/util/archive.py +152 -0
  265. pymoo/util/cache.py +29 -0
  266. pymoo/util/clearing.py +82 -0
  267. pymoo/util/display/__init__.py +0 -0
  268. pymoo/util/display/column.py +52 -0
  269. pymoo/util/display/display.py +34 -0
  270. pymoo/util/display/multi.py +100 -0
  271. pymoo/util/display/output.py +53 -0
  272. pymoo/util/display/progress.py +54 -0
  273. pymoo/util/display/single.py +67 -0
  274. pymoo/util/dominator.py +67 -0
  275. pymoo/util/hv.py +21 -0
  276. pymoo/util/matlab_engine.py +39 -0
  277. pymoo/util/misc.py +447 -0
  278. pymoo/util/nds/__init__.py +0 -0
  279. pymoo/util/nds/dominance_degree_non_dominated_sort.py +159 -0
  280. pymoo/util/nds/efficient_non_dominated_sort.py +152 -0
  281. pymoo/util/nds/fast_non_dominated_sort.py +70 -0
  282. pymoo/util/nds/find_non_dominated.py +54 -0
  283. pymoo/util/nds/naive_non_dominated_sort.py +36 -0
  284. pymoo/util/nds/non_dominated_sorting.py +94 -0
  285. pymoo/util/nds/tree_based_non_dominated_sort.py +133 -0
  286. pymoo/util/normalization.py +312 -0
  287. pymoo/util/optimum.py +42 -0
  288. pymoo/util/randomized_argsort.py +63 -0
  289. pymoo/util/ref_dirs/__init__.py +24 -0
  290. pymoo/util/ref_dirs/construction.py +89 -0
  291. pymoo/util/ref_dirs/das_dennis.py +52 -0
  292. pymoo/util/ref_dirs/energy.py +317 -0
  293. pymoo/util/ref_dirs/energy_layer.py +119 -0
  294. pymoo/util/ref_dirs/genetic_algorithm.py +64 -0
  295. pymoo/util/ref_dirs/incremental.py +69 -0
  296. pymoo/util/ref_dirs/misc.py +128 -0
  297. pymoo/util/ref_dirs/optimizer.py +59 -0
  298. pymoo/util/ref_dirs/performance.py +162 -0
  299. pymoo/util/ref_dirs/reduction.py +85 -0
  300. pymoo/util/ref_dirs/sample_and_map.py +24 -0
  301. pymoo/util/reference_direction.py +258 -0
  302. pymoo/util/remote.py +55 -0
  303. pymoo/util/roulette.py +29 -0
  304. pymoo/util/running_metric.py +128 -0
  305. pymoo/util/sliding_window.py +25 -0
  306. pymoo/util/value_functions.py +720 -0
  307. pymoo/util/vectors.py +40 -0
  308. pymoo/util/vf_dominator.py +102 -0
  309. pymoo/vendor/__init__.py +0 -0
  310. pymoo/vendor/cec2018.py +398 -0
  311. pymoo/vendor/gta.py +617 -0
  312. pymoo/vendor/vendor_cmaes.py +421 -0
  313. pymoo/vendor/vendor_coco.py +81 -0
  314. pymoo/vendor/vendor_scipy.py +232 -0
  315. pymoo/version.py +1 -0
  316. pymoo/visualization/__init__.py +21 -0
  317. pymoo/visualization/app/__init__.py +0 -0
  318. pymoo/visualization/app/pso.py +61 -0
  319. pymoo/visualization/fitness_landscape.py +128 -0
  320. pymoo/visualization/heatmap.py +123 -0
  321. pymoo/visualization/matplotlib.py +61 -0
  322. pymoo/visualization/pcp.py +121 -0
  323. pymoo/visualization/petal.py +91 -0
  324. pymoo/visualization/radar.py +108 -0
  325. pymoo/visualization/radviz.py +68 -0
  326. pymoo/visualization/scatter.py +150 -0
  327. pymoo/visualization/star_coordinate.py +75 -0
  328. pymoo/visualization/util.py +296 -0
  329. pymoo/visualization/video/__init__.py +0 -0
  330. pymoo/visualization/video/callback_video.py +82 -0
  331. pymoo/visualization/video/one_var_one_obj.py +57 -0
  332. pymoo/visualization/video/two_var_one_obj.py +62 -0
  333. pymoo-0.6.1.6.dist-info/METADATA +209 -0
  334. pymoo-0.6.1.6.dist-info/RECORD +337 -0
  335. pymoo-0.6.1.6.dist-info/WHEEL +6 -0
  336. pymoo-0.6.1.6.dist-info/licenses/LICENSE +191 -0
  337. pymoo-0.6.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,239 @@
1
+ import numpy as np
2
+
3
+ from pymoo.algorithms.moo.spea2 import SPEA2Survival
4
+ from pymoo.core.algorithm import Algorithm
5
+ from pymoo.core.initialization import Initialization
6
+ from pymoo.core.population import Population
7
+ from pymoo.core.survival import Survival
8
+ from pymoo.docs import parse_doc_string
9
+ from pymoo.operators.mutation.pm import PolynomialMutation
10
+ from pymoo.operators.repair.to_bound import (
11
+ ToBoundOutOfBoundsRepair,
12
+ set_to_bounds_if_outside,
13
+ )
14
+ from pymoo.operators.sampling.rnd import FloatRandomSampling
15
+ from pymoo.operators.survival.rank_and_crowding.metrics import get_crowding_function
16
+ from pymoo.util import default_random_state
17
+ from pymoo.util.archive import MultiObjectiveArchive, SurvivalTruncation
18
+ from pymoo.util.display.multi import MultiObjectiveOutput
19
+
20
+
21
+ class CrowdingDistanceTournamentSurvival(Survival):
22
+ def __init__(self, tournament_size=3):
23
+ """
24
+ Survival strategy that uses tournament selection based on crowding distance.
25
+
26
+ This class inherits from :class:`~pymoo.core.survival.Survival` and implements a
27
+ tournament selection mechanism where individuals with higher crowding distance
28
+ (indicating better diversity) are preferred.
29
+
30
+ Parameters
31
+ ----------
32
+ tournament_size : int, optional
33
+ The number of individuals participating in each tournament. Defaults to 3.
34
+ """
35
+ super().__init__(filter_infeasible=True)
36
+ self._tournament_size = tournament_size
37
+ self._cd = get_crowding_function("cd")
38
+
39
+ def _do(self, problem, pop, n_survive, random_state=None, **kwargs):
40
+ crowding = self._cd.do(pop.get("F"))
41
+
42
+ # Select solutions with better crowding distance (more diverse)
43
+ selected_indices = []
44
+ remaining_indices = list(range(len(pop)))
45
+
46
+ while len(selected_indices) < n_survive and remaining_indices:
47
+ # Tournament selection favoring higher crowding distance
48
+ tournament_size = min(self._tournament_size, len(remaining_indices))
49
+ tournament_indices = random_state.choice(
50
+ remaining_indices, size=tournament_size, replace=False
51
+ )
52
+
53
+ # Select the one with highest crowding distance in tournament
54
+ best_idx = tournament_indices[np.argmax(crowding[tournament_indices])]
55
+ selected_indices.append(best_idx)
56
+ remaining_indices.remove(best_idx)
57
+
58
+ return pop[selected_indices]
59
+
60
+
61
+ def angle_between(v1, v2):
62
+ v1 = v1 / (np.linalg.norm(v1) + 1e-6) # unit vector; +1-e6 to avoid zero-division
63
+ v2 = v2 / (np.linalg.norm(v2) + 1e-6) # unit vector
64
+ return np.arccos(np.clip(np.dot(v1, v2), -1.0, 1.0))
65
+
66
+
67
+ @default_random_state
68
+ def cmopso_equation(X, L, V, V_max, random_state=None):
69
+ """
70
+ Calculates the new positions and velocities of particles based on the CMOPSO
71
+ competition-based learning strategy.
72
+
73
+ This function implements the core update equations for the Competitive Mechanism
74
+ based Multi-objective Particle Swarm Optimizer (CMOPSO). Each particle's
75
+ velocity is updated by learning from a "winner" selected from the elite archive
76
+ (`L`) through a binary tournament. The winner is chosen based on the smallest
77
+ angle between the particle's current position and the elite's position, promoting
78
+ diversity.
79
+
80
+ Parameters
81
+ ----------
82
+ X : numpy.ndarray
83
+ Current positions of the particles (population). Shape `(n_particles, n_var)`.
84
+ L : numpy.ndarray
85
+ Positions of the elite particles from the archive. Shape `(n_elites, n_var)`.
86
+ V : numpy.ndarray
87
+ Current velocities of the particles. Shape `(n_particles, n_var)`.
88
+ V_max : numpy.ndarray
89
+ Maximum allowed velocity for each decision variable. Shape `(n_var,)`.
90
+ random_state : numpy.random.RandomState, optional
91
+ Random state for reproducibility. If None, a new default random number
92
+ generator is used.
93
+
94
+ Returns
95
+ -------
96
+ Xp : numpy.ndarray
97
+ New positions of the particles after the update. Shape `(n_particles, n_var)`.
98
+ Vp : numpy.ndarray
99
+ New velocities of the particles after the update. Shape `(n_particles, n_var)`.
100
+ """
101
+
102
+ W_X = []
103
+ for i in range(np.shape(X)[0]): # binary tournament selection on elites
104
+ aidx = random_state.choice(range(len(L)))
105
+ bidx = random_state.choice(range(len(L)))
106
+ a = L[aidx]
107
+ b = L[bidx]
108
+ pw = min(a, b, key=lambda x: angle_between(x, X[i]))
109
+ W_X.append(pw)
110
+ W_X = np.asarray(W_X)
111
+
112
+ r1 = random_state.random(X.shape)
113
+ r2 = random_state.random(X.shape)
114
+
115
+ # calculate the velocity vector
116
+ Vp = r1 * V + r2 * (W_X - X)
117
+ Vp = set_to_bounds_if_outside(Vp, -V_max, V_max)
118
+
119
+ Xp = X + Vp
120
+
121
+ return Xp, Vp
122
+
123
+
124
+ class CMOPSO(Algorithm):
125
+ def __init__(
126
+ self,
127
+ pop_size=100,
128
+ max_velocity_rate=0.2,
129
+ elite_size=10,
130
+ initial_velocity="random", # 'random' | 'zero'
131
+ mutation_rate=0.5,
132
+ sampling=FloatRandomSampling(),
133
+ repair=ToBoundOutOfBoundsRepair(),
134
+ output=MultiObjectiveOutput(),
135
+ **kwargs,
136
+ ):
137
+ """
138
+ Competitive mechanism based Multi-objective Particle Swarm Optimizer (CMOPSO).
139
+
140
+ Particle updates are based on learning from the "winner" of binary tournaments
141
+ of randomly selected elites. Replacement strategy is based on SPEA2.
142
+
143
+ Zhang, X., Zheng, X., Cheng, R., Qiu, J., & Jin, Y. (2018). A competitive mechanism
144
+ based multi-objective particle swarm optimizer with fast convergence.
145
+ Inf. Sci., 427, 63-76.
146
+
147
+ Parameters
148
+ ----------
149
+ pop_size : int, optional
150
+ The population size. Defaults to 100.
151
+ max_velocity_rate : float, optional
152
+ The maximum velocity rate. Defaults to 0.2.
153
+ max_elite_size : int, optional
154
+ The maximum size of the elite archive. Defaults to 10.
155
+ initial_velocity : str, optional
156
+ Defines how the initial velocity of particles is set. Can be "random" or "zero".
157
+ Defaults to "random".
158
+ mutate_rate : float, optional
159
+ Rate at which to apply polynomial mutation to the offspring. Defaults to 0.5.
160
+ sampling : :class:`~pymoo.core.sampling.Sampling`, optional
161
+ Sampling strategy used to generate the initial population. Defaults to
162
+ :class:`~pymoo.operators.sampling.rnd.FloatRandomSampling`.
163
+ repair : :class:`~pymoo.operators.repair.Repair`, optional
164
+ Repair method for out-of-bounds variables. Defaults to
165
+ :class:`~pymoo.operators.repair.to_bound.ToBoundOutOfBoundsRepair`.
166
+ output : :class:`~pymoo.util.display.output.Output`, optional
167
+ Output object to be used for logging. Defaults to
168
+ :class:`~pymoo.util.display.multi.MultiObjectiveOutput`.
169
+ **kwargs
170
+ Additional keyword arguments to be passed to the Algorithm superclass.
171
+ """
172
+ super().__init__(output=output, **kwargs)
173
+ self.pop_size = pop_size
174
+ self.max_velocity_rate = max_velocity_rate
175
+ self.elite_size = elite_size
176
+ self.initialization = Initialization(sampling)
177
+ self.initial_velocity = initial_velocity
178
+ self.repair = repair
179
+ self._cd = get_crowding_function("cd")
180
+ self.mutation = PolynomialMutation(prob=mutation_rate)
181
+ self.survival = SPEA2Survival()
182
+
183
+ def _setup(self, problem, **kwargs):
184
+ super()._setup(problem, **kwargs)
185
+ self.elites = MultiObjectiveArchive(
186
+ truncation=SurvivalTruncation(
187
+ CrowdingDistanceTournamentSurvival(), problem=problem
188
+ ),
189
+ max_size=self.pop_size,
190
+ truncate_size=self.elite_size,
191
+ )
192
+ self.V_max = self.max_velocity_rate * (problem.xu - problem.xl)
193
+
194
+ def _initialize_infill(self):
195
+ return self.initialization.do(
196
+ self.problem, self.pop_size, algorithm=self, random_state=self.random_state
197
+ )
198
+
199
+ def _initialize_advance(self, infills=None, **kwargs):
200
+ self.pop = infills
201
+
202
+ if self.initial_velocity == "random":
203
+ init_V = (
204
+ self.random_state.random((len(self.pop), self.problem.n_var))
205
+ * self.V_max[None, :]
206
+ )
207
+ elif self.initial_velocity == "zero":
208
+ init_V = np.zeros((len(self.pop), self.problem.n_var))
209
+ else:
210
+ raise Exception("Unknown velocity initialization.")
211
+
212
+ self.pop.set("V", init_V)
213
+ self.elites = self.elites.add(self.pop)
214
+
215
+ def _infill(self):
216
+ (X, V) = self.pop.get("X", "V")
217
+ L = self.elites.get("X")
218
+
219
+ Xp, Vp = cmopso_equation(X, L, V, self.V_max, random_state=self.random_state)
220
+
221
+ # create the offspring population
222
+ off = Population.new(X=Xp, V=Vp)
223
+ off = self.mutation(self.problem, off, random_state=self.random_state)
224
+ off = self.repair(self.problem, off)
225
+
226
+ return off
227
+
228
+ def _advance(self, infills=None, **kwargs):
229
+ assert infills is not None, (
230
+ "This algorithm uses the AskAndTell interface thus 'infills' must to be provided."
231
+ )
232
+
233
+ particles = Population.merge(self.pop, infills)
234
+ self.elites = self.elites.add(particles)
235
+ self.pop = self.survival.do(self.problem, particles, n_survive=self.pop_size)
236
+
237
+
238
+ parse_doc_string(CrowdingDistanceTournamentSurvival.__init__)
239
+ parse_doc_string(CMOPSO.__init__)
@@ -0,0 +1,305 @@
1
+ import math
2
+
3
+ import numpy as np
4
+ from scipy.spatial.distance import cdist, pdist, squareform
5
+
6
+ from pymoo.algorithms.base.genetic import GeneticAlgorithm
7
+ from pymoo.core.population import Population
8
+ from pymoo.decomposition.asf import ASF
9
+ from pymoo.docs import parse_doc_string
10
+ from pymoo.functions import load_function
11
+ from pymoo.operators.crossover.sbx import SBX
12
+ from pymoo.operators.mutation.pm import PM
13
+ from pymoo.operators.sampling.rnd import FloatRandomSampling
14
+ from pymoo.operators.selection.tournament import TournamentSelection
15
+ from pymoo.util import default_random_state
16
+ from pymoo.util.display.multi import MultiObjectiveOutput
17
+ from pymoo.util.dominator import Dominator
18
+ from pymoo.util.misc import has_feasible, random_permutations
19
+ from pymoo.util.nds.non_dominated_sorting import NonDominatedSorting
20
+
21
+
22
+ # =========================================================================================================
23
+ # Implementation
24
+ # Following original code by K. Li https://cola-laboratory.github.io/codes/CTAEA.zip
25
+ # =========================================================================================================
26
+
27
+
28
+ @default_random_state
29
+ def comp_by_cv_dom_then_random(pop, P, random_state=None, **kwargs):
30
+ S = np.full(P.shape[0], np.nan)
31
+
32
+ for i in range(P.shape[0]):
33
+ a, b = P[i, 0], P[i, 1]
34
+
35
+ if pop[a].CV <= 0.0 and pop[b].CV <= 0.0:
36
+ rel = Dominator.get_relation(pop[a].F, pop[b].F)
37
+ if rel == 1:
38
+ S[i] = a
39
+ elif rel == -1:
40
+ S[i] = b
41
+ else:
42
+ S[i] = random_state.choice([a, b])
43
+ elif pop[a].CV <= 0.0:
44
+ S[i] = a
45
+ elif pop[b].CV <= 0.0:
46
+ S[i] = b
47
+ else:
48
+ S[i] = random_state.choice([a, b])
49
+
50
+ return S[:, None].astype(int)
51
+
52
+
53
+ class RestrictedMating(TournamentSelection):
54
+ """Restricted mating approach to balance convergence and diversity archives"""
55
+
56
+ @default_random_state
57
+ def _do(self, problem, Hm, n_select, n_parents, random_state=None, **kwargs):
58
+ n_pop = len(Hm) // 2
59
+
60
+ _, rank = NonDominatedSorting().do(Hm.get('F'), return_rank=True)
61
+
62
+ Pc = (rank[:n_pop] == 0).sum() / len(Hm)
63
+ Pd = (rank[n_pop:] == 0).sum() / len(Hm)
64
+
65
+ # number of random individuals needed
66
+ n_random = n_select * n_parents * self.pressure
67
+ n_perms = math.ceil(n_random / n_pop)
68
+ # get random permutations and reshape them
69
+ P = random_permutations(n_perms, n_pop, random_state=random_state)[:n_random]
70
+ P = np.reshape(P, (n_select * n_parents, self.pressure))
71
+ if Pc <= Pd:
72
+ # Choose from DA
73
+ P[::n_parents, :] += n_pop
74
+ pf = random_state.random(n_select)
75
+ P[1::n_parents, :][pf >= Pc] += n_pop
76
+
77
+ # compare using tournament function
78
+ S = self.func_comp(Hm, P, random_state=random_state, **kwargs)
79
+
80
+ return np.reshape(S, (n_select, n_parents))
81
+
82
+
83
+ class CADASurvival:
84
+
85
+ def __init__(self, ref_dirs):
86
+ self.ref_dirs = ref_dirs
87
+ self.opt = None
88
+ self.ideal_point = np.full(ref_dirs.shape[1], np.inf)
89
+ self._decomposition = ASF()
90
+ self._calc_perpendicular_distance = load_function("calc_perpendicular_distance")
91
+
92
+ @default_random_state
93
+ def do(self, _, pop, da, n_survive=None, random_state=None, **kwargs):
94
+ # Store random_state for use in methods
95
+ self.random_state = random_state
96
+ # Offspring are last of merged population
97
+ off = pop[-n_survive:]
98
+ # Update ideal point
99
+ self.ideal_point = np.min(np.vstack((self.ideal_point, off.get("F"))), axis=0)
100
+ # Update CA
101
+ pop = self._updateCA(pop, n_survive, random_state=random_state)
102
+ # Update DA
103
+ Hd = Population.merge(da, off)
104
+ da = self._updateDA(pop, Hd, n_survive)
105
+ return pop, da
106
+
107
+ def _associate(self, pop):
108
+ """Associate each individual with a F vector and calculate decomposed fitness"""
109
+ F = pop.get("F")
110
+ dist_matrix = self._calc_perpendicular_distance(F - self.ideal_point, self.ref_dirs)
111
+ niche_of_individuals = np.argmin(dist_matrix, axis=1)
112
+ FV = self._decomposition.do(F, weights=self.ref_dirs[niche_of_individuals, :],
113
+ ideal_point=self.ideal_point, weight_0=1e-4)
114
+ pop.set("niche", niche_of_individuals)
115
+ pop.set("FV", FV)
116
+ return niche_of_individuals, FV
117
+
118
+ @default_random_state
119
+ def _updateCA(self, pop, n_survive, random_state=None):
120
+ """Update the Convergence archive (CA)"""
121
+ CV = pop.get("CV").flatten()
122
+
123
+ Sc = pop[CV == 0] # ConstraintsAsObjective population
124
+ if len(Sc) == n_survive: # Exactly n_survive feasible individuals
125
+ F = Sc.get("F")
126
+ fronts, rank = NonDominatedSorting().do(F, return_rank=True)
127
+ Sc.set('rank', rank)
128
+ self.opt = Sc[fronts[0]]
129
+ return Sc
130
+ elif len(Sc) < n_survive: # Not enough feasible individuals
131
+ remainder = n_survive - len(Sc)
132
+ # Solve sub-problem CV, tche
133
+ SI = pop[CV > 0]
134
+ f1 = SI.get("CV")
135
+ _, f2 = self._associate(SI)
136
+ sub_F = np.column_stack([f1, f2])
137
+ fronts = NonDominatedSorting().do(sub_F, n_stop_if_ranked=remainder)
138
+ I = []
139
+ for front in fronts:
140
+ if len(I) + len(front) <= remainder:
141
+ I.extend(front)
142
+ else:
143
+ n_missing = remainder - len(I)
144
+ last_front_CV = np.argsort(f1.flatten()[front])
145
+ I.extend(front[last_front_CV[:n_missing]])
146
+ SI = SI[I]
147
+ S = Population.merge(Sc, SI)
148
+ F = S.get("F")
149
+ fronts, rank = NonDominatedSorting().do(F, return_rank=True)
150
+ S.set('rank', rank)
151
+ self.opt = S[fronts[0]]
152
+ return S
153
+ else: # Too many feasible individuals
154
+ F = Sc.get("F")
155
+ # Filter by non-dominated sorting
156
+ fronts, rank = NonDominatedSorting().do(F, return_rank=True, n_stop_if_ranked=n_survive)
157
+ I = np.concatenate(fronts)
158
+ S, rank, F = Sc[I], rank[I], F[I]
159
+ if len(S) > n_survive:
160
+ # Remove individual in most crowded niche and with worst fitness
161
+ niche_of_individuals, FV = self._associate(S)
162
+ index, count = np.unique(niche_of_individuals, return_counts=True)
163
+ survivors = np.full(S.shape[0], True)
164
+ while survivors.sum() > n_survive:
165
+ crowdest_niches, = np.where(count == count.max())
166
+ worst_idx = None
167
+ worst_niche = None
168
+ worst_fit = -1
169
+ for crowdest_niche in crowdest_niches:
170
+ crowdest, = np.where((niche_of_individuals == index[crowdest_niche]) & survivors)
171
+ niche_worst = crowdest[FV[crowdest].argmax()]
172
+ dist_to_max_fit = cdist(F[[niche_worst], :], F).flatten()
173
+ dist_to_max_fit[niche_worst] = np.inf
174
+ dist_to_max_fit[~survivors] = np.inf
175
+ min_d_to_max_fit = dist_to_max_fit.min()
176
+
177
+ dist_in_niche = squareform(pdist(F[crowdest]))
178
+ np.fill_diagonal(dist_in_niche, np.inf)
179
+
180
+ delta_d = dist_in_niche - min_d_to_max_fit
181
+ min_d_i = np.unravel_index(np.argmin(delta_d, axis=None), dist_in_niche.shape)
182
+ if (delta_d[min_d_i] < 0) or (
183
+ delta_d[min_d_i] == 0 and (FV[crowdest[list(min_d_i)]] > niche_worst).any()):
184
+ min_d_i = list(min_d_i)
185
+ random_state.shuffle(min_d_i)
186
+ closest = crowdest[min_d_i]
187
+ niche_worst = closest[np.argmax(FV[closest])]
188
+ if (FV[niche_worst] > worst_fit).all():
189
+ worst_fit = FV[niche_worst]
190
+ worst_idx = niche_worst
191
+ worst_niche = crowdest_niche
192
+ survivors[worst_idx] = False
193
+ count[worst_niche] -= 1
194
+ S, rank = S[survivors], rank[survivors]
195
+ S.set('rank', rank)
196
+ self.opt = S[rank == 0]
197
+ return S
198
+
199
+ def _updateDA(self, pop, Hd, n_survive):
200
+ """Update the Diversity archive (DA)"""
201
+ niche_Hd, FV = self._associate(Hd)
202
+ niche_CA, _ = self._associate(pop)
203
+
204
+ itr = 1
205
+ S = []
206
+ while len(S) < n_survive:
207
+ for i in range(n_survive):
208
+ current_ca, = np.where(niche_CA == i)
209
+ if len(current_ca) < itr:
210
+ for _ in range(itr - len(current_ca)):
211
+ current_da = np.where(niche_Hd == i)[0]
212
+ if current_da.size > 0:
213
+ F = Hd[current_da].get('F')
214
+ nd = NonDominatedSorting().do(F, only_non_dominated_front=True, n_stop_if_ranked=0)
215
+ i_best = current_da[nd[np.argmin(FV[current_da[nd]])]]
216
+ niche_Hd[i_best] = -1
217
+ if len(S) < n_survive:
218
+ S.append(i_best)
219
+ else:
220
+ break
221
+ if len(S) == n_survive:
222
+ break
223
+ itr += 1
224
+ return Hd[S]
225
+
226
+
227
+ class CTAEA(GeneticAlgorithm):
228
+
229
+ def __init__(self,
230
+ ref_dirs,
231
+ sampling=FloatRandomSampling(),
232
+ selection=RestrictedMating(func_comp=comp_by_cv_dom_then_random),
233
+ crossover=SBX(n_offsprings=1, eta=30, prob=1.0),
234
+ mutation=PM(prob_var=None, eta=20),
235
+ eliminate_duplicates=True,
236
+ output=MultiObjectiveOutput(),
237
+ **kwargs):
238
+ """
239
+ CTAEA
240
+
241
+ Parameters
242
+ ----------
243
+ ref_dirs : {ref_dirs}
244
+ sampling : {sampling}
245
+ selection : {selection}
246
+ crossover : {crossover}
247
+ mutation : {mutation}
248
+ eliminate_duplicates : {eliminate_duplicates}
249
+ """
250
+
251
+ self.ref_dirs = ref_dirs
252
+ pop_size = len(ref_dirs)
253
+
254
+ if 'survival' in kwargs:
255
+ survival = kwargs['survival']
256
+ del kwargs['survival']
257
+ else:
258
+ survival = CADASurvival(ref_dirs)
259
+
260
+ # Initialize diversity archives
261
+ self.da = None
262
+
263
+ super().__init__(pop_size=pop_size,
264
+ sampling=sampling,
265
+ selection=selection,
266
+ crossover=crossover,
267
+ mutation=mutation,
268
+ survival=survival,
269
+ eliminate_duplicates=eliminate_duplicates,
270
+ n_offsprings=pop_size,
271
+ output=output,
272
+ **kwargs)
273
+
274
+ def _setup(self, problem, **kwargs):
275
+
276
+ if self.ref_dirs is not None and self.ref_dirs.shape[1] != problem.n_obj:
277
+ raise Exception(
278
+ "Dimensionality of reference points must be equal to the number of objectives: %s != %s" %
279
+ (self.ref_dirs.shape[1], problem.n_obj))
280
+
281
+ def _initialize_infill(self):
282
+ return self.initialization.do(self.problem, self.pop_size, algorithm=self, random_state=self.random_state)
283
+
284
+ def _initialize_advance(self, infills=None, **kwargs):
285
+ super()._initialize_advance(infills, **kwargs)
286
+ self.pop, self.da = self.survival.do(self.problem, self.pop, Population(), n_survive=len(self.pop),
287
+ algorithm=self, random_state=self.random_state)
288
+
289
+ def _infill(self):
290
+ Hm = Population.merge(self.pop, self.da)
291
+ return self.mating.do(self.problem, Hm, n_offsprings=self.n_offsprings, algorithm=self, random_state=self.random_state)
292
+
293
+ def _advance(self, infills=None, **kwargs):
294
+ assert infills is not None, "This algorithms uses the AskAndTell interface thus infills must to be provided."
295
+ pop = Population.merge(self.pop, infills)
296
+ self.pop, self.da = self.survival.do(self.problem, pop, self.da, self.pop_size, algorithm=self, random_state=self.random_state)
297
+
298
+ def _set_optimum(self, **kwargs):
299
+ if not has_feasible(self.pop):
300
+ self.opt = self.pop[[np.argmin(self.pop.get("CV"))]]
301
+ else:
302
+ self.opt = self.survival.opt
303
+
304
+
305
+ parse_doc_string(CTAEA.__init__)
@@ -0,0 +1,80 @@
1
+ import numpy as np
2
+
3
+ from pymoo.algorithms.moo.nsga2 import NSGA2
4
+ from pymoo.core.population import Population
5
+
6
+
7
+ class DNSGA2(NSGA2):
8
+
9
+ def __init__(self,
10
+ perc_detect_change=0.1,
11
+ perc_diversity=0.3,
12
+ eps=0.0,
13
+ version="A",
14
+ **kwargs):
15
+
16
+ super().__init__(**kwargs)
17
+ self.perc_detect_change = perc_detect_change
18
+ self.perc_diversity = perc_diversity
19
+ self.eps = eps
20
+ self.version = version
21
+
22
+ def setup(self, problem, **kwargs):
23
+ assert not problem.has_constraints(), "DNSGA2 only works for unconstrained problems."
24
+ return super().setup(problem, **kwargs)
25
+
26
+ def _infill(self):
27
+
28
+ return None
29
+
30
+ def _advance(self, **kwargs):
31
+
32
+ pop = self.pop
33
+ X, F = pop.get("X", "F")
34
+
35
+ # the number of solutions to sample from the population to detect the change
36
+ n_samples = int(np.ceil(len(pop) * self.perc_detect_change))
37
+
38
+ # choose randomly some individuals of the current population to test if there was a change
39
+ I = self.random_state.choice(np.arange(len(pop)), size=n_samples)
40
+ samples = self.evaluator.eval(self.problem, Population.new(X=X[I]))
41
+
42
+ # calculate the differences between the old and newly evaluated pop
43
+ delta = ((samples.get("F") - F[I]) ** 2).mean()
44
+
45
+ # if there is an average deviation bigger than eps -> we have a change detected
46
+ change_detected = delta > self.eps
47
+
48
+ if change_detected:
49
+
50
+ # recreate the current population without being evaluated
51
+ pop = Population.new(X=X)
52
+
53
+ # find indices to be replaced (introduce diversity)
54
+ I = np.where(self.random_state.random(len(pop)) < self.perc_diversity)[0]
55
+
56
+ # replace with randomly sampled individuals
57
+ if self.version == "A":
58
+ pop[I] = self.initialization.sampling(self.problem, len(I), random_state=self.random_state)
59
+
60
+ # replace by mutations of existing solutions (this occurs inplace)
61
+ elif self.version == "B":
62
+ self.mating.mutation(self.problem, pop[I])
63
+ else:
64
+ raise Exception(f"Unknown version of D-NSGA-II: {self.version}")
65
+
66
+ # reevaluate because we know there was a change
67
+ self.evaluator.eval(self.problem, pop)
68
+
69
+ # do a survival to recreate rank and crowding of all individuals
70
+ pop = self.survival.do(self.problem, pop, n_survive=len(pop), random_state=self.random_state)
71
+
72
+ # create the offsprings from the current population
73
+ off = self.mating.do(self.problem, pop, self.n_offsprings, algorithm=self, random_state=self.random_state)
74
+ self.evaluator.eval(self.problem, off)
75
+
76
+ # merge the parent population and offsprings
77
+ pop = Population.merge(pop, off)
78
+
79
+ # execute the survival to find the fittest solutions
80
+ self.pop = self.survival.do(self.problem, pop, n_survive=self.pop_size, algorithm=self, random_state=self.random_state)