From 2461bc48f643346d51fe32219f2a6f6a7ff2e51a Mon Sep 17 00:00:00 2001 From: ajnebro Date: Sat, 22 Jun 2024 10:46:00 +0200 Subject: [PATCH] Add new documentation --- docs/.buildinfo | 2 +- docs/_images/abstract.png | Bin 18489 -> 0 bytes docs/_images/bad_access.png | Bin 5388 -> 0 bytes docs/_images/class_header.png | Bin 34297 -> 0 bytes docs/_images/generic_class1.png | Bin 29000 -> 0 bytes docs/_images/generic_class2.png | Bin 13510 -> 0 bytes docs/_images/generic_types.png | Bin 26333 -> 0 bytes docs/_images/generic_types_fixed.png | Bin 7923 -> 0 bytes docs/_images/good_access.png | Bin 5576 -> 0 bytes .../inheritance_generic_to_generic.png | Bin 8441 -> 0 bytes .../inheritance_non_generic_to_generic.png | Bin 8070 -> 0 bytes .../instance_with_generic_class_wearning.png | Bin 4796 -> 0 bytes .../instance_with_generic_types1_wearning.png | Bin 3306 -> 0 bytes .../instance_with_generic_types2_wearning.png | Bin 3554 -> 0 bytes docs/_images/method_way_sphinx.png | Bin 26143 -> 0 bytes docs/_images/property_annotation.png | Bin 33398 -> 0 bytes docs/_images/property_functional.png | Bin 34977 -> 0 bytes .../_images/python_functional_programming.png | Bin 15864 -> 0 bytes .../_images/python_imperative_programming.png | Bin 7771 -> 0 bytes docs/_images/python_poo_programming.png | Bin 20820 -> 0 bytes docs/_images/types_in_methods.png | Bin 18914 -> 0 bytes docs/_images/with_getter_setter.png | Bin 25386 -> 0 bytes docs/_images/without_getter_setter.png | Bin 8150 -> 0 bytes docs/_modules/index.html | 36 +- .../jmetal/algorithm/multiobjective/gde3.html | 184 +- .../jmetal/algorithm/multiobjective/hype.html | 123 +- .../jmetal/algorithm/multiobjective/ibea.html | 125 +- .../algorithm/multiobjective/mocell.html | 136 +- .../algorithm/multiobjective/moead.html | 256 +- .../algorithm/multiobjective/nsgaii.html | 284 +- .../algorithm/multiobjective/nsgaiii.html | 205 +- .../algorithm/multiobjective/omopso.html | 204 +- .../algorithm/multiobjective/smpso.html | 348 +- .../algorithm/multiobjective/spea2.html | 104 +- .../singleobjective/evolution_strategy.html | 121 +- .../singleobjective/genetic_algorithm.html | 144 +- .../singleobjective/local_search.html | 117 +- .../singleobjective/simulated_annealing.html | 134 +- docs/_modules/jmetal/core/observer.html | 80 +- docs/_modules/jmetal/core/problem.html | 295 +- docs/_modules/jmetal/lab/experiment.html | 794 +- .../lab/statistical_test/apv_procedures.html | 213 +- .../jmetal/lab/statistical_test/bayesian.html | 84 +- .../statistical_test/critical_distance.html | 182 +- .../lab/statistical_test/functions.html | 271 +- .../jmetal/lab/visualization/chord_plot.html | 380 +- .../jmetal/lab/visualization/interactive.html | 238 +- .../jmetal/lab/visualization/plotting.html | 150 +- .../jmetal/lab/visualization/posterior.html | 132 +- .../jmetal/lab/visualization/streaming.html | 112 +- docs/_modules/jmetal/operator/crossover.html | 428 +- docs/_modules/jmetal/operator/mutation.html | 306 +- docs/_modules/jmetal/operator/selection.html | 266 +- .../problem/multiobjective/constrained.html | 211 +- .../jmetal/problem/multiobjective/dtlz.html | 335 +- .../jmetal/problem/multiobjective/fda.html | 197 +- .../problem/multiobjective/lircmop.html | 571 +- .../jmetal/problem/multiobjective/lz09.html | 373 +- .../problem/multiobjective/unconstrained.html | 382 +- .../jmetal/problem/multiobjective/zdt.html | 353 +- .../problem/singleobjective/knapsack.html | 109 +- .../jmetal/problem/singleobjective/tsp.html | 107 +- .../singleobjective/unconstrained.html | 212 +- docs/_modules/jmetal/util/evaluator.html | 115 +- docs/_modules/jmetal/util/observer.html | 221 +- docs/_sources/about.rst.txt | 2 +- .../algorithm/multiobjective/eas/gde3.rst.txt | 95 - .../multiobjective/eas/gde3_dynamic.ipynb.txt | 2 +- .../eas/gde3_preference.ipynb.txt | 2 +- .../algorithm/multiobjective/eas/hype.rst.txt | 41 - .../algorithm/multiobjective/eas/ibea.rst.txt | 37 - .../multiobjective/eas/mocell.rst.txt | 41 - .../multiobjective/eas/moead.rst.txt | 79 - .../multiobjective/eas/nsgaii.rst.txt | 144 - .../eas/nsgaii_distributed.ipynb.txt | 2 +- .../eas/nsgaii_dynamic.ipynb.txt | 2 +- .../eas/nsgaii_preference.ipynb.txt | 2 +- .../multiobjective/eas/nsgaiii.ipynb.txt | 2 +- .../multiobjective/eas/nsgaiii.rst.txt | 11 - .../multiobjective/eas/nsgaiij.ipynb.txt | 112 - .../multiobjective/eas/oldnsgaii.rst.txt | 144 - .../multiobjective/eas/oldspea2.rst.txt | 73 - .../multiobjective/eas/spea2.rst.txt | 73 - .../multiobjective/psos/omopso.rst.txt | 45 - .../multiobjective/psos/smpso.rst.txt | 114 - .../problem/algorithm/multiobjective/ea.rst | 15 - .../problem/algorithm/multiobjective/pso.rst | 9 - .../singleobjective/evolution.strategy.rst | 8 - .../singleobjective/genetic.algorithm.rst | 8 - .../singleobjective/local.search.rst | 8 - .../singleobjective/simulated.annealing.rst | 8 - .../problem/jmetal.lab.statistical_test.rst | 46 - .../api/problem/operator/crossover.rst | 7 - .../api/problem/operator/mutation.rst | 7 - .../api/problem/operator/selection.rst | 7 - .../api/problem/problem/multiobjective.rst | 58 - .../api/problem/problem/singleobjective.rst | 26 - docs/_sources/index.rst.txt | 2 +- docs/_static/basic.css | 325 +- docs/_static/css/bootstrap-theme.min.css | 7 - docs/_static/doctools.js | 376 +- docs/_static/documentation_options.js | 12 +- docs/_static/jquery-3.4.1.js | 10598 ---------------- docs/_static/language_data.js | 106 +- docs/_static/nbsphinx-broken-thumbnail.svg | 9 + docs/_static/nbsphinx-code-cells.css | 259 + docs/_static/nbsphinx-gallery.css | 31 + docs/_static/nbsphinx-no-thumbnail.svg | 9 + docs/_static/pygments.css | 8 +- docs/_static/searchtools.js | 863 +- docs/_static/sphinx_highlight.js | 154 + docs/_static/underscore-1.3.1.js | 999 -- docs/_static/underscore.js | 31 - docs/about.html | 202 + docs/api/algorithm/multiobjective/ea.html | 47 +- .../algorithm/multiobjective/eas/gde3.html | 339 +- .../algorithm/multiobjective/eas/gde3.ipynb | 0 .../multiobjective/eas/gde3_dynamic.html | 293 +- .../multiobjective/eas/gde3_dynamic.ipynb | 4 +- .../multiobjective/eas/gde3_preference.html | 262 +- .../multiobjective/eas/gde3_preference.ipynb | 4 +- .../algorithm/multiobjective/eas/hype.html | 311 +- .../algorithm/multiobjective/eas/hype.ipynb | 0 .../algorithm/multiobjective/eas/ibea.html | 318 +- .../algorithm/multiobjective/eas/ibea.ipynb | 0 .../algorithm/multiobjective/eas/mocell.html | 332 +- .../algorithm/multiobjective/eas/mocell.ipynb | 0 .../algorithm/multiobjective/eas/moead.html | 346 +- .../algorithm/multiobjective/eas/moead.ipynb | 0 .../algorithm/multiobjective/eas/nsgaii.html | 308 +- .../algorithm/multiobjective/eas/nsgaii.ipynb | 0 .../eas/nsgaii_distributed.html | 360 +- .../eas/nsgaii_distributed.ipynb | 4 +- .../multiobjective/eas/nsgaii_dynamic.html | 293 +- .../multiobjective/eas/nsgaii_dynamic.ipynb | 4 +- .../multiobjective/eas/nsgaii_preference.html | 296 +- .../eas/nsgaii_preference.ipynb | 4 +- .../algorithm/multiobjective/eas/nsgaiii.html | 90 +- .../multiobjective/eas/nsgaiii.ipynb | 4 +- .../algorithm/multiobjective/eas/nsgaiij.html | 425 - .../multiobjective/eas/oldnsgaii.html | 421 - .../multiobjective/eas/oldspea2.html | 251 - .../algorithm/multiobjective/eas/spea2.html | 308 +- .../algorithm/multiobjective/eas/spea2.ipynb | 0 docs/api/algorithm/multiobjective/pso.html | 47 +- .../algorithm/multiobjective/psos/omopso.html | 395 +- .../multiobjective/psos/omopso.ipynb | 0 .../algorithm/multiobjective/psos/smpso.html | 395 +- .../algorithm/multiobjective/psos/smpso.ipynb | 0 .../multiobjective/psos/smpso_dynamic.html | 293 +- .../multiobjective/psos/smpso_dynamic.ipynb | 0 .../multiobjective/psos/smpso_preference.html | 342 +- .../psos/smpso_preference.ipynb | 0 .../singleobjective/evolution.strategy.html | 127 +- .../singleobjective/genetic.algorithm.html | 127 +- .../singleobjective/local.search.html | 138 +- .../singleobjective/simulated.annealing.html | 145 +- docs/api/jmetal.lab.statistical_test.html | 333 +- docs/api/operator/crossover.html | 356 +- docs/api/operator/mutation.html | 309 +- docs/api/operator/selection.html | 312 +- docs/api/problem/multiobjective.html | 2131 ++-- docs/api/problem/singleobjective.html | 394 +- docs/contributing.html | 379 + docs/genindex.html | 2014 +++ docs/index.html | 254 + docs/multiobjective.algorithms.html | 192 + docs/objects.inv | Bin 14161 -> 16451 bytes docs/operators.html | 212 + docs/problems.html | 190 + docs/py-modindex.html | 332 + docs/search.html | 162 + docs/searchindex.js | 2 +- docs/singleobjective.algorithms.html | 192 + docs/tutorials.html | 191 + docs/tutorials/evaluator.html | 165 +- docs/tutorials/experiment.html | 163 +- docs/tutorials/observer.html | 230 +- docs/tutorials/problem.html | 357 +- docs/tutorials/statistics.html | 43 +- docs/tutorials/visualization.html | 300 +- 181 files changed, 16665 insertions(+), 24442 deletions(-) delete mode 100644 docs/_images/abstract.png delete mode 100644 docs/_images/bad_access.png delete mode 100644 docs/_images/class_header.png delete mode 100644 docs/_images/generic_class1.png delete mode 100644 docs/_images/generic_class2.png delete mode 100644 docs/_images/generic_types.png delete mode 100644 docs/_images/generic_types_fixed.png delete mode 100644 docs/_images/good_access.png delete mode 100644 docs/_images/inheritance_generic_to_generic.png delete mode 100644 docs/_images/inheritance_non_generic_to_generic.png delete mode 100644 docs/_images/instance_with_generic_class_wearning.png delete mode 100644 docs/_images/instance_with_generic_types1_wearning.png delete mode 100644 docs/_images/instance_with_generic_types2_wearning.png delete mode 100644 docs/_images/method_way_sphinx.png delete mode 100644 docs/_images/property_annotation.png delete mode 100644 docs/_images/property_functional.png delete mode 100644 docs/_images/python_functional_programming.png delete mode 100644 docs/_images/python_imperative_programming.png delete mode 100644 docs/_images/python_poo_programming.png delete mode 100644 docs/_images/types_in_methods.png delete mode 100644 docs/_images/with_getter_setter.png delete mode 100644 docs/_images/without_getter_setter.png delete mode 100644 docs/_sources/api/algorithm/multiobjective/eas/gde3.rst.txt delete mode 100644 docs/_sources/api/algorithm/multiobjective/eas/hype.rst.txt delete mode 100644 docs/_sources/api/algorithm/multiobjective/eas/ibea.rst.txt delete mode 100644 docs/_sources/api/algorithm/multiobjective/eas/mocell.rst.txt delete mode 100644 docs/_sources/api/algorithm/multiobjective/eas/moead.rst.txt delete mode 100644 docs/_sources/api/algorithm/multiobjective/eas/nsgaii.rst.txt delete mode 100644 docs/_sources/api/algorithm/multiobjective/eas/nsgaiii.rst.txt delete mode 100644 docs/_sources/api/algorithm/multiobjective/eas/nsgaiij.ipynb.txt delete mode 100644 docs/_sources/api/algorithm/multiobjective/eas/oldnsgaii.rst.txt delete mode 100644 docs/_sources/api/algorithm/multiobjective/eas/oldspea2.rst.txt delete mode 100644 docs/_sources/api/algorithm/multiobjective/eas/spea2.rst.txt delete mode 100644 docs/_sources/api/algorithm/multiobjective/psos/omopso.rst.txt delete mode 100644 docs/_sources/api/algorithm/multiobjective/psos/smpso.rst.txt delete mode 100644 docs/_sources/api/problem/algorithm/multiobjective/ea.rst delete mode 100644 docs/_sources/api/problem/algorithm/multiobjective/pso.rst delete mode 100644 docs/_sources/api/problem/algorithm/singleobjective/evolution.strategy.rst delete mode 100644 docs/_sources/api/problem/algorithm/singleobjective/genetic.algorithm.rst delete mode 100644 docs/_sources/api/problem/algorithm/singleobjective/local.search.rst delete mode 100644 docs/_sources/api/problem/algorithm/singleobjective/simulated.annealing.rst delete mode 100644 docs/_sources/api/problem/jmetal.lab.statistical_test.rst delete mode 100644 docs/_sources/api/problem/operator/crossover.rst delete mode 100644 docs/_sources/api/problem/operator/mutation.rst delete mode 100644 docs/_sources/api/problem/operator/selection.rst delete mode 100644 docs/_sources/api/problem/problem/multiobjective.rst delete mode 100644 docs/_sources/api/problem/problem/singleobjective.rst delete mode 100644 docs/_static/css/bootstrap-theme.min.css delete mode 100644 docs/_static/jquery-3.4.1.js create mode 100644 docs/_static/nbsphinx-broken-thumbnail.svg create mode 100644 docs/_static/nbsphinx-code-cells.css create mode 100644 docs/_static/nbsphinx-gallery.css create mode 100644 docs/_static/nbsphinx-no-thumbnail.svg create mode 100644 docs/_static/sphinx_highlight.js delete mode 100644 docs/_static/underscore-1.3.1.js delete mode 100644 docs/_static/underscore.js create mode 100644 docs/about.html rename docs/{_sources/api/problem => api}/algorithm/multiobjective/eas/gde3.ipynb (100%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/eas/gde3_dynamic.ipynb (99%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/eas/gde3_preference.ipynb (99%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/eas/hype.ipynb (100%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/eas/ibea.ipynb (100%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/eas/mocell.ipynb (100%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/eas/moead.ipynb (100%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/eas/nsgaii.ipynb (100%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/eas/nsgaii_distributed.ipynb (99%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/eas/nsgaii_dynamic.ipynb (99%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/eas/nsgaii_preference.ipynb (99%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/eas/nsgaiii.ipynb (98%) delete mode 100644 docs/api/algorithm/multiobjective/eas/nsgaiij.html delete mode 100644 docs/api/algorithm/multiobjective/eas/oldnsgaii.html delete mode 100644 docs/api/algorithm/multiobjective/eas/oldspea2.html rename docs/{_sources/api/problem => api}/algorithm/multiobjective/eas/spea2.ipynb (100%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/psos/omopso.ipynb (100%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/psos/smpso.ipynb (100%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/psos/smpso_dynamic.ipynb (100%) rename docs/{_sources/api/problem => api}/algorithm/multiobjective/psos/smpso_preference.ipynb (100%) create mode 100644 docs/contributing.html create mode 100644 docs/genindex.html create mode 100644 docs/index.html create mode 100644 docs/multiobjective.algorithms.html create mode 100644 docs/operators.html create mode 100644 docs/problems.html create mode 100644 docs/py-modindex.html create mode 100644 docs/search.html create mode 100644 docs/singleobjective.algorithms.html create mode 100644 docs/tutorials.html diff --git a/docs/.buildinfo b/docs/.buildinfo index 93767c9e..da73e819 100644 --- a/docs/.buildinfo +++ b/docs/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: d5baa1b64aedad170a2f4617683d66c9 +config: 4b780489226086b8db31ab45dede2a32 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_images/abstract.png b/docs/_images/abstract.png deleted file mode 100644 index 6a530e6a4c3cf3cbef77727c72c8682aad50b3cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18489 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 C?Px~; diff --git a/docs/_images/class_header.png b/docs/_images/class_header.png deleted file mode 100644 index ea734158797b1b920941a47caa8a8f7bbf37737b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34297 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 E0b3a6f&c&j diff --git a/docs/_images/generic_class1.png b/docs/_images/generic_class1.png deleted file mode 100644 index 5679412f9d13584fe2543b860fbaaabc2571f3f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29000 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%0AFH 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 LFIp*V_~pL&TUvcJ90f5zSW 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∋Y@#&yj^x-AkEy(e#2%9;?m^ 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 NQ9&tz3O*g*{{y>I2dn@9 diff --git a/docs/_images/generic_types.png b/docs/_images/generic_types.png deleted file mode 100644 index fa8cb9e1c97cf3c7ce44e86c007030cabbf45fc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26333 zcmbrl1#nwWm-ZQR%#1O_%yw*nVFfHIp&xdWQ^^YnPj%t`M=-H&d$8M zHC0EN-VpFh8|9d#kVVB=LfEc!9?(XnOXeFfp=`Vrb^%kyV%ei+b^_A4IR z`Rk@`#?t-4!<2Ba_H}KD&FDzl)wVzExWvKX@_hR9v~mXGQbzyLyY`g0@Gw)}7Ds55 zc3-Zd8D}LEdc7IN0JhGb`iu01DHY4DgwRbIOR=j7MSj1}w>yjc(=!`zWEVx^A@xZb zjUnmHcX?C+iN8_T8t3=#pF15GdXkS0#mcVP7p!Wv0w9yA$%4*wL9rwnR~Mq z=bl*ISz@2+grx7|>5ohxn0F4@-^hA`zFb-6!C}C$a8gAExqoY(_uIX1>p-|uJCKIQ zHD+)csAk!W@L(I{Qkqr2!9dal#D7V>oWxT1io%iQ)&Ifk8{v?dwcuGWgRa3SvMR_! z35KY!YB>vr!%Bq-m!w6{mEH0aE`0+u2a?UxYE{9?&XQE73}68v_{H;;)N;NxXDABV zJ9P-UB2Y4-X66-dwgKrp8u3AJ-$L#8cLQ_l9K<1^mm_Z9UcU64YeSFFoRy7mu;ont zF3#YR;2Q1thOFhk7nV&QK0`f#(_p}EZkINbTs9eS1bA$6hCTf>7sFh&`qd8UsDJW8 zt)6j*BBNx!?ZsOB)7-9{gAlp|e`%I$OtKZZ)~80az3O*s!b0O}YBA#rgdQ&JX$AY* zmqV<0A8I()Bd)_ak~R}sLEF-iqr#*1gc6H;tj9)cTpl2xNln=Lm3xuv9_h?Ou%P_UUTFpmRq6B z4K8O*xuKyg-23Qwuw3^-qy6x{MA4`u$*Ie|WtebjNy>qi;CuNp#Y1pUVMn-HPqkY-7l$F=l0#BBAZq}EMC{qAU?;c{+ZDM~yIT7&c zLvLoiA6+hG-n<`Cr&DYwYH~NHD>NH%KyS<~Lmo4~i$9%Ww$nzEGiAXa*{Q%S6nY5E zG;^jDuBFY=J-ngwo$ZYUUij@PD5>2~CiE{ZjCI}3O7!5M_ea0m?ub|+b-3-DOo>5V z=hl{ddk(S^snr|ZmjI+0t$ed459?lI_T0uWi-p@CuFzk>zbkXxTu)X$ArktrkKf5? zhIofp@bJu`Vyy&Ts#N{>kjWI~JA{6~RSPhlf;5}P@_-^) z-m2)t+hVf^+*H2LY(9hWo`*H36K8GhpAD`!p*Z&vNFpys>Ws$OHF$)2S+u))PLn{N zN9=FO>zqP#)YW?zuUD(+=csqC&cB6`lCjzcA3xOEs5KjSvOZ7j(U@oS%%U)2qFgfj zO3RzR={vrZPHFjdU`$Yc(L&2p7ihMNDq|0W)h2n&D0F z`}66y*nps~y?u|#xL%WkUoI&sMThssaq+6M|AGLXcf}{n9!%-=Hy^PM*y3zZ{y?;= zHRkk?Z8%%z1@-LFf=2X_=cfPDm~YzMqNAC=W9Yu|n$e>rYNs`#iNX(vh*K{Y%6F5i zgx>Z%*doD3wkRSEXrkw?4QWk)64T15!vdcHFP!&Sx5@>`pSNPEpuvw-eTRPG3MvUn z#Rex02)KnCl=uDqi;R|ouRuJ1MzLJ>+lOUQ>1JY)yVc*RFf(e$T)XQ?cK9g3%5y^{ zA>^w930|J2Wmg|MZf2VITe5mKS;}ljCbHEfYgMpJ9}M3V)r{hLb&+B)c`>!sN`vK~ z_9{@yo<3agE3qjP?)!FIJxeX?wP5M@xyh58{t{j%Q%q6*yhoUOBTrM>_a1-cvO7ZNl3?%q+Bjh458W^7Jd6i+kuSpjL6UPQ!VoQo+)(({)?j!qHVFAwDgT zlQl3YO8SLm+u2XigisnQ2x=KSiYL$IQ9+KC@2+zDa!8P&s5`)ISyQFY_i}u+thXO& zWbLe0sB+41gGdQWS_=)=je(=WenY$ccUe4qr3VuD>Z0)YQSZ$gVTQ1Pddn@sn6Cuw za!w9m_k-UF;d+b3QCkO3E92HaS&c3`m{%Q{GyP_Sv7>mCogc=H-E4) zm{Iu~B%-w?<06Ac=y&d^H~$-m&e{3!WeKW#pmV78W>iX_4~O7;CCRg*?Y&obni_9R zV&{FG>8y-j!=rMkBtZsSjYvQvToc%BE^qQ%tRhx&C4b-(Kc_2ow%QQEMZ0I+;Q7It zsX8nnXVSJQz5*^o{lkE&r@fRkH0&sr00bLEIJMfX9oGVq9Y?#$ZnWhFRjqf_;$D6h z-i6hItJZ`*CBu$`knCjs=E{cmXQRU3VQ(s+slzDPq6{$ZI=lo4*3l4E9s>DWT&>YK zzAAj4EY|m@)-K`puId$rIEGrU>w{->%|d(W+6ecJ3N2;e+n$wNiI&8A?{_Fe@}q@8 z_%Jwvqoej9Sp8SoqQ20{xSvP#y^Y&aOTaSUK(@(bo9t)O5vi79LnhND1@({l>rtUT z`Hly#n3+<~#CC8ocoL~f@yjKAU{wAv)v$!aP0YGno#FxvQJNW>^o(lPUVJS1b5h_Y z$Dd2wG$s8g8v0eh&drq!t^#71^jcc?Z^+U1?tQCQOJIMHGk@)FZ6q}g0<{R?+H{Tt zKuwv8qM7z>6I520;Ne3wl6alpZ3``D%OMloC-pS+Y7^~1S(E_a%Kf-jaK?AT@MLK= zD>CIGIUK`RCqN=>Pny!zAT30E&SoP5n6nCnWp&?a%WC&Uqo@dy`T+l-ysL5{%+5%g z&(VDblfSQHcy&?&D00FGGo(&_q_!ATpsuuk3EVA>Tdn2YJKqe=ri>mt%Xc3g&1!_6 zIcj`FV5kKX_1t{7r76FND$W>JRZ@-VQs|^N#%gV6<&b!cVU@6GPcb5rJy^OE(ynKm ze|ibK7s>ztN1&$j@Pa+|e4<42k&E2hWhXwSaQP_*~r$NdndQvICZ4i&IO`R+!*eQaOO%+5P|$fGnTFZl{CYzj z?T3sNtgh%(lhvWsrucCFlxNXIEF$Eu_%9n?bL7t|l}!74u#-K1iDY=U{Pq3O8K+4~KK*I&^Mbwr z@v_<#2G3>NM|RTUGSH}sx)vSnQo)n0 z_QZg;B+^&Eyy4GX#ek;@aBv$E0zKWEhynZGMYQ(tCzc_Ca(}g~f^cNCIYE(M0kiqs zoEM6bdh)E5VIhN!*b| zs~3-%xI!K^Pq-YDme=)raJhyqE60*E3C}0fdUEIUNUW~oqP%Ej>isk60jwMN#Id&d zNmap2F6VGxAnWZ7~hO+vA)Ao9A?KMp$eH=F7zDbWPCK znp`GPjnmt?lS!g2|1t~T0^SWSW2$e~vJ#@sV-a}wvmGTb|MXTcJzYAk=raWF3*#e6 z(D6zk)n;P&!LDXQ6({J?n{~1UNz@qx4%n_N1>|j+VC7D&x3F%bE92?%5q$}ACC8Fr2%;8pw%Iy(ba)5(A)J^Yhuk-5KaD4hf>Px8X~8d_QbwaUZ)t zG&5b}uTS4?<)Va&x9v5W((`OB)-zw|iY#I)8=bY++y4`c*F}(5^3xws=~Ajw`v#fn z@q|oX9iAGlo>Y`n3o1_~cLseQcp*&n*X{2#XXLn9Fk@86bDVQxV)>3xwa(+26l|mw zv0hkL1%&**p0hDHy0id^hRN{IIT|$fe6H@~3a-cf3so4ip(&bb#oSziPRAZ8HG6uW5~ixvtlJl{$KsaXP^Cx z5nFoQov(aoiup7~9*~6;2%qBOv$4=!NdsN1wV&)}^BrvZy}kVk1I^F3JhW$jWj34F zBqrDxN^!cMvM|nA$x^*qo28D8iw%#8PMooa+K%ihKC=~UEYlt?Zz4C-$0sU3ee>>9 ztJ(Z!q+Y;OUCL-GaDZKhKOsmEaeN9zH3OwKaHsm^AW0;D4=eugE`sO~+(xWX& z=ksdivO`{bEwNaVea@kew=6Q%NPm7aVWCmSL-r1{D`sh@m1YC*X)x}azVix$O-?Ze z{TsS&=*VNwLGH13@r#e+1xUGr2cMTY9_TVzPR&ZQl?3Qrh%1#nwUx z(zi_LN%h;3(rtUsWss^Z1D}E9F7|+6&E|IlnUD#a=lPFBNd@ppS*_(2b+5@ldVH*f zKgev4AY79>g91Wav2t6f!`t-ZBM{`jfhC2Gj2>@px6WX1*7Di_G+T4;ro~Ln&itaf zn3~@41UE*Zw{W%WymiMwyVg@_{MKYSdhg!&vXMwvyO?$PELVZUFz9aBx3yLHYz{80 z6}3GRAj?wq2<6;Q>AaU{6~)P)dy&E>oQrIF)J>alj&1g>Y1{27th%(sSxyX5zOG~W z+m!3dujzc(Z!cSGQ8<@efg@};39V3>;?~eMukpuw*^(OEaoZ3u3O(Cq8T(~OnvK{x z;V{=&GjsBE<_i;vCKOM(19^z){_ifAo?6k~sdivMoua=P8)tvZWd6&YJRTpb_3X>u z`n2AsfX!I1m^GmDDH;3R`1}|x9U#w?|Ig3STy}?^xf35=>QA;$hzvTac}fAgtzJq%g?-gOOxs7}yVXKt2t=X8;b9faffxMI>}Qlw#kQvm6uwUJ zcJunB5qzeJP9<*_X7!8LWeA zjkp{9Tlc?U;#2SG3Wl%~EnjN=tJSi{1|-wcZT;7f#U;R%8XL}f-vVr&-M|>OKsMNZv*=Krh7kZ#IWLzDggThFSAruFFKkn^rqxOAu*zO_LKXwG$I9TNMS| zTE)5_7U&Qovi|z%xyI%hZo}Aotv0XMS%nrqg2`C~Q>#eyvhS84$={yxm2D8+|6UvR z!z3@>^!hfyJ(a5M(RGsY=of=Obvo1xW7X)0?_WlrBGn2V2qHSFSyL~0kHdOwcE) z$a2^F)eHIdc1=s`A)5_f*b8yr<=~G~oxa*bSZaYEWAENh`#UvSp>WDF6siaf-{BOX zMNysdQmOY5bat^#=>op2q}IqrqQT;GCCiAZ>`y>vbF22x%Z2xte6Aj&a6Zh<{Bu;l zuiFT_;t91=+LO)+FUX*|Gwn##!@<~#5l|=Ps*~wV(;$BXb~s1Nu~wrM}XT%O!I&o2rVJXw0`?AMog%@%@-} zb#Eve9dQ07{jqSHtf~Lj=udULcwn9~BYwGs+SGFhmMqTrR{5fQh(&w1vNg6;qfn@* z$)pXp{Gy(c8DC`Gb?NZyBQ8ahSErS9q}H(2s-b_5dj$CH3RQvGh$*4zdJJXFb74#Z z;q3^lpXQojuh@6g#vvv{vUn3K)>~m1YgVUPcl{1RSHqq*ysxZCr@Nq3yZc{4gN zvRn)VNJVNoaG^H9S|r%=#{xl^G%vJCe^hhFUr?E$rNgjw}LV!PeX6ZDH|+LBVB@rfAAS3#Cy3yRzCJBka~^8#nktX`e!WX>yA(} zuR))FitSVflqlq>tw{8`?iv9S?KH_t^Qe1YFF*!J_FL1HO@3&;BF{x})yr;u##*D^C zqmbn-_CrhA{#Zn>^_3FU{;93wP(moz0ro`M%X29eHfXnOCE7g4=Gty$;E$x9^gnX1 z3RD1P9?~o6!c~-;gLPxRr_+;W6@PjE9aecH|Jy;y_+nQ?Y(;S=TE2wNY737;+-6&5 zBD)o7@vXY-!u>DXp#a)^wcC|p8-koKDuHi_5{Ysnhow7-fdgD+?l->Y5Y?ONhmml5 z3${d`2G>>wjvpj`GyX`yh^LA-;Oua9cs(MdVa2Mb6e}^Q7PJ<|xKq<~Oy8#e4&E!> zXPv8TeH`_aaB{yoRkArPoWCb?Lz29VK%>t7xI)+0QHEnWq%%~yk#@Y$+{eQSz|PZ5 zn9)B4bp~WS(zj<(w9Ip1ln;ZwN!QwXLoNKAVEgXact*Q`*EDO6%X|mPB&NW{F*bG<&On~IMB+%whhlFA$NtjZVc z#nu9ToshEBOnO}WykHsY!2KnwN$AO_{KyOKW;uB~hu1U1ZZud<(Dg?-#E#YsZN3(Q zx0JGgMlW^=-hW?|AJZzg58-q^q@8&0VRVEbHLoM-reWNXAmaQK3TQhJ|N9(qh1Z*~ zP?-0M{w#K@dRwQ8EPG=#HZnEQpf2ALKJVy+m#VbWSJ-Gs7dAbstAD^cnVeU&GaoqVh4#Lf z>33n-T&0y0sa<7Xl$(L5$2ud@_u%t=F$IY4u8*o3tc2B+mttE^f2MhgfiS;H?p`^2 zZcw(dI8px=h~7j4jThluj`TycLP+v(Id4??J0?O}m(NHU?2t1lmq&)o#&0rgk5V7^ zs#2CPH%5CU8&o-#jt9mWlRZe3!?`_Rl)2Bo^^T_JfzmEB3v`?5qXG3RwU41Dt z1xoNvu{TaByFZygFVKVrC1R`Uc_wdSe#CDiE>8a6rIQBquzz?T5&B$eL$*HKS&HMT zweY=~`AQyCxKeE^1_m%#+t+gOo`b)A*xMQd~NWxGe+R;^^Tl*8CRw9OCc2`>BJl}&70Kh~&z%65TU^-?$APngHC z8#0l`AL%{p=jO9oJ#4zISj#Zq#y^wc)rZA1Ci@8Kbb? z9XkJD=faxHxgRW)F>Qb{)#TWTSR@|Ff+9v^0TGwCmG#xi%)w#m!sjX#Oo=q@Mvdw@Y7eKU4UkB=oL4aXV^#_@-au>m0VOtW zcZX1xpJ}Zc4el()7OK4^CE#}tZ_%m1ug8_ME10|zS*dBbtGB3VlT z5TU-~$uBsVLld)m^_`trp@}c8&P1T3?9mrU^!JTh=z3%C%M)Bu0}3}wWWMyug87s$ zFZxfiA2c3UJQLA&k}(ZEr=4&4{6rHEbGpLW+Tyq8ebt^-gg3Xt>*_1mq?sfs^A+^0 zSY{Tv&<}H=bFmhs!iS59%-@RHobM+Bx~UVJ;lP4lZ{B$9UiKbg>;#x;^AlvrUBky4 z?U!!?d1K_qI$nE_3NH>D#36;YdwTH!e_Xq&ay_0~CwfpqnDgYjmPkyTcS1$G?Poz@Oq?c~Z zNo}{gj}hgmPu$1-HH4v5vf9p@kAto2bdh=aVO+WLRe1 z9f#{|g>sO+sBiD?3!6Rrs2POg7xM;ciZlvV-G=WPXyRrJdr}qY3lQ)c-ZXKclTs#M zW=?5t^V1LBAGRy`+YRds0XhfADVV3dMv$toR)|%yw@r#Kp3Jb7lM*kRi}+nuT^nGn z_l_i_r&L$SPsQJRP5VdVl;F!rkZT z`$q2BY&gAEhDkgc|CIzeK{p+pp6JmafAcAQ+_qNx$uNR-Nv#(^b&%RsSRd5cS zotSW&HyhrregaznrBnsoYK(%`O7^$>CNh5|9m0aq}TNLqCM1sSkGUJ~!-;Nn|#v=VR z_nK}>oE`~ed z-Uvv0yAQ>~Rg{ZF>Ys zOZhzG1A4p(9q3G3tLmt1&NM&A0REBGn6-@bzPES#J{!xPJUjaw5ckuI-fO+%%S73b z@=}B#|@-=d|GD%bjBtziiFkX3&mym2*eoN10I6i2=J z26}tM5-{65Q`iak@<+8%Kb@X{Ji(AT@i=d+8OQF+1I5GQyu*;9DRBUGV7geb#q&Q> zsn;4&v(<9RhEiVd=cZ*#9k-ynx{lEtJ2wlQaTqt}@3tC^zV)6kPYqJgtWB(|0|FWs&l+z@RNxqAq{AIDgB-=7eVnwjl zY4PgLe!MNna|Ib#4R|HuD^tiNZVe?;2F+cB?8|T&PWTcuq@g2y^XbFqYfoxq2)tSq zjKQh6blWoMI&*DVr{ z&r0=%q<4R&yEHqYV>$%ZrCiMyIxJ*by&^l!uB46{R@T%@w=b(jwpe{0$YKG-*;v_f zk#d!S; z?@vXqY~xYJ#)t7q{M^;#Oa0zI(-M?@5@)gLO1O18IrgJz81IdEKedhJQV-qqiJCH%PGgldCTMJSuz;mQbWX^5Y# z_k9sUyq;|-qGulm6nbGka;W6t`kv#gC?3w!8W)ojVUxO7HaVgAaupeJ-x7l6_)3+p zY_+Z1{e{4_Q^V=2^(Vj`#yFe6iJvN*ruS-HN2~E-J#c}eE#-ui-N7L#SwA&2-tJ?7 zdbtiodqOou!Hzy=l70l=5|ZPwBshOPEMpns*XOiluB9Jij@6s2DRWLaaeY<)mqsUd z=41YNANo7r>97ML=Doi88oHYybpWojnymTP67o)0+`8^_R}tPiU@?CgJl~@hS!I=P zQKqS9nE7qCn!0u%mdOnyRFo>CCV!FDtBMW-zvXNGtR(J8adp(~85eISWLes6-=Id}-tM?mgS$2eW7dj%!hHI$pE|{v(R%*!>X2lsMWPA+Zp2C=K;oHkV2j zf~%CN(y-7#Nj0NYjz;X6AyDHX`w%oRBwJy>=-3X96dOuKkWbEH$5xHB(wOT<9OW!I zrl2oXWoqz>E&{GhW;Vx>zB%y@2V-Kg*A{JYw)vhZf*13h4j-1d%TXm)V%v!x5y(NU zTtAMsYCAIC!W8~sRqs2iA?=%b7agYej(2QvVW`5JjJ%LDH6Iybal9!^bNAlQPLm`Z z3DdPtqN>_2{qp-mX9-6u*~+l9gph!k?#_N(stHq^gPn12C{R2^$C3S67T>9t!I?Rp zT_%9As$68F?)RUQ;In?GO&rHGB!3$tU0wEt@@E_c$A{OEgh|ll?y3H`(}Ee50`)l& z^m2-|ynL*lPp>PoopzIpjxM3Eq0KZ*tVn^=RsXEZk6$XZ9JGBtiPqt8>k+_0M}`<< zF*@oL?J!MnNwJ(aN6=+Z=wTWQ&&ztGjes`=!{fQ@m@*s(k0n65XKU!7@gfS0n;kAc z9wJM>D>m9++|g@`oNaPrxPW(<;_;!NZ`C*rRTG~L^!BQK6&(oFE8(bvm zP~2GZ+4uvfh?JG4bFcb*38Q85C0e;0%Z^ZEYF+uw#cDsAnKT@MTfZX7RWLfFOm=() z$GZJqnVofFbuvqvPMcGoA*P{=c4h-oR`Npk)^}6xyphThn9nZ(6^V&navA? ztqE?4F5s``=txa8<#apN?3q;iN$Cy>W3vgNS~a>%l+yWDn~}*`7P=#Qo$$+_Uq$O7tWK^cJZfhqt@O> zJtsb|`C3`I9&4#vfTPrb0J7WA#7b^-T8(0|;rB}0fGzRisEC~PJN~3&?n*}025)b7 zTh!pa^V zz}8L`ka1iv)0F))#$TPxEUJf#t$e$GH?6A0>WyL39&Wo4&mtSb;8s14pfKZ{-?og` zbNNrEv{C6N6hHx@s&!N07M48tnJ3MNrd*L2fmttKu$zW5r_94vruQayIqXs0XSlf9vEXJjpBUjDO&Rmlqwt7RY+3Um* zW(6|$vGk3{>26rw;2*9|*?93$=7hPq`}w<6FUA_X(4lfRgkpLds?1_}<0lXk9(b~q zh-=C^mm{yZMi^wNJ$ORJ+XSO=HI6WaOH}t0e|kv8T&kVtH=n?f)P|b<%-jv^1}P3> z!%piKoImH#4ej06pbEF$k=xZM?PmgGYi<%^F$@!A*}u8Y4-(WFlczKvl|0;oNFlYy z113NZ%1(HEKuT`3W&rL1y#GgAA<|!%OCoL8IDieRx}KIM_={0<&$dPnJHK`27tt2d(C8R?FG z6FN_eX+JS#$)9}KGBs;c`<>LW&XAKovB{L1`Ba*l%`(s-TG0eJJ^iDVJn^MYz_fY= zD8u{($p=$_c*i$b^W14QSrf`j2xbjSplGBv$|PZ=8o;T$rc$?S@alCurWk(o~b`$xUIrFY`0{ z5<1=TZgYJmj$n}VCI5%4m)Cx5WB6}m9a9i0+16hJ7#hV9M~vvx+Eh5l@b@Z1Eu}%F z+48$bWj*~71%Emx-m6c+bp1H?u`j}t0{7s9(Sxs|tEM%a1;yV;hbj&LP`q2sZgbo- zFuwddSStRm$-$pM8)EpM9b1t?%q1u@BKZ4DEKMo0`rE%s)UXkQ_9fq zBGJ58gM$x zKhBq@vyVlQF`xRBD=k5>VimB_8ylZ)tf_3D?h06~P!~kKZ>|QV?iKkV_rm|TwGR}p zKl)$({~|e!4iIz}Fbs0^T|YBYZ7z31Fg(PG$q{g#S|6v5UD9cswMveSa+y%7#4L*UY+mUu-lWc08Kv+31Fd z?;6;ad^}a=I&1Q_uq^dbquHg@K?edYT5|Z%&blMk?o}W?PN@x^P<~)2P!nc_%`;oo z`I&~R4I3*`&EC^5M0|YHz2#!VqAwr2CYCAjE!*R0sSWXSH52qI>MZe=v0qh*?ok)-qO5b23x_82hLNHbed8Ek==QS&A@ ztuHdc?9Yf7P*Q8q zb^Yip|Is<{qca@8XDH}v2>sfdu*Ls6aI4Dg!W1F6PGWt>E&FFAfQpZi_~Y1miq+uA zHzPt^xzowd%tSWnb0lv1(KsNOT_^AjMp+0i;+-9~4kZdab8n4baQ&gSegi1*6N_w9 z{IFsEa|6Q7;|4$k9ztvU$g(v?@q4mG=%Kt6E%&81WX*>4*;99fpvSKlO_R=vX zv$Xoky6i{wJ*n0$DoFlUG{srq_3l;^m%c~sddaN&$Q>!=F|pQPL9QuIi!Z;YH9v%~ zKC>66WAVojzk02+JvBWs}Bn@R+9+{rih4Q9#U1Y0+Y4^bGcQuM^ zpB^o`+tt+s2QKbZdt9bc2;{e|3}Nz1_cI(P_CI9K%imM+SN8Yz2X z3Ats^h&tjn`xfh$p^PpxbH!d$P|ezI!vCYnn^L+Qb9BH@S|;pS@#2wy@X4A*T1>6< zXqbxbGcg!_FQabJNKqV^%AtPjCPc-2T~UQVtA%8jSW|y0O^=&=N$7tTerI_C&v=iX zcw=4b{+-D4a|?H_<0Vpw+p3{oEP#KpfhNFuE!FuO!CzB+I%RX%8OQ88WC@pglYRun zZRZ3e`xr2iTKma;fMFWXY4eBWWr+rG_UY@A0ZJB6TJQkh4Zi8eIOA2t(Bx|QUQPYE zb}IrY50$TZ<60#21|O-!iW=h84AjoyMrdnF7Ud+gNtU5ip;l5AAQ~N(`%txptLM!Y z*ZmvB-1~MA+#RlywFRvyk>O*=4W5@LO^%b3y8u;e zgal)|8@0&0ATeX`Z#f1NKcQVTc~ay^^-64}#ay&}uc!QC*T%%rGq%_Ac?f;EzFC}v zc&|P=t|?0h(6Y#wgP6?LISW)iHntX;jaRL3LjW8NqwG7w5mR}Nt`q@JD$-{2hkG)s zX)%+VnQvPy#wN8Bfq4lK+Du1|q4<;kop%{={aZvwF{5g5S_7d2 zd2HGz9R@=LRsmrC&mOe4r_EmbR|gvJO#ku4g-9};m-NxgGOuDIRMBcKYE_IOPqaf9#vvOCid3t-yJUL{g-@rBNneTiRXR(C+Pf=HpOH; z_FQ#OFp%?}vorT+D)^lvigQ_}-A&Ex-d$QN zS2}Dp@vM=nR$D4-YQNt^H(E63rqhcyz@1rpH6&^r!$4}6tY7N6?xZHZ1+rp0AG{k) zmbB}BcZB1MBL~g$8Fdcwv3Y)6ITQ3xq<1G!nZma20Imdd6=jkQ_9N=qGBSs`^=c8qlaR|MYa4-WmU z14vm@Fq&cbI;s*C_GDnizreBsrZtnevYH~+Qc0@kB7jx{Nj}jJx35t?txU%D&90%i zTXWZOau|UDW*4R~IPHYS{SgtF>T?%aw>==LKqVqKW0!gEKJmTd5bd1jTsRfDpPbfeLzCa!pJrq5tt zHiPRt$SX6_965Wr8;G}KckZ(IafY#^pOv~@_RsnRmm$!u(Do&Wh(v&^W5cNM_@t?v zX@JYOy7>U!;qeYp3oH;hJC>=JYr+TrLkl2J{rj?LlJ3zDJ9oOrnVIANh2?>lXVnOa zdwHE3^sD=$TYl8a$sD}KFKpVbf0$Fd>8V^xZeMO%L}~ok=*gH7(T-WRJopkUK4=FT zS@&;W_)qZHoypf?27n=*ugep&E)}sn2vAGDKQkRkL7ke5_P-ohyal(l4sjOEXd^NN zT}(Auuf}1Gj;HxE1*DGcZNq5w*x`#m44eyy&7PaUdKW1~OZ zN=ZUH>1Dk?}u-`7f1?P;|%4{3*+B?B&@HdLJI%*E%mQsVZKtb}uGNPVMlh zi)PV;z>6e4^Uk4y33?%8Lk)e01IMbsnbi^5i-`E)5s!v}0k$`is>eV=?2?Sd73}hr zi5CcZ4#JAFVf!M4_R^I@IURJK0|XpQOqfrlr%zW!l51KImQDU%h`+Ouo7Y;;5dzmr zHk>cRBocYtbv#1m%W%V*_Ool{SN4Jecnh<8J?a^s!K^)S9J9A%Eu*Yzs4~%-FUPSj ziJ+r$CsRz_)w|ouzaMHybvv(CP;?jO77lmyK!zdzev~~AZljshE1@yLXoMlVsqlwj zVF(JB>_P9su;LGe!D_hr;~EjRc*_fqi-q^SkSlJ69SxVmA1;HS^?x~V|LjgWUD z2f=ozrMa3tHVhcIyB6)h&baE@Lb=t*n!lHBU-grZ`S?b1a_sBIL?#YK%hXDCjG;?r ziU&s+3XjtlKL8dAL4Nyd_WGZJQ+4*jT=7RrEi0IZy0UB-VQb*Fjj87|FR+uKbs$;x z>+V)gzcI}4&K7@C2|UBhNi!#cBjX-ki|ki)vx$PqQQRh+mCwt4^50BU@zw7%)^m1p zw|l&MXDwLv$HV2D3#Pdyid10{-qne;)OK92*CQ*B-j%`7^`nY1gM_$Bz-={HbA>0x z0UP@CoTr2QF=E9EANU26#&6_c!sL@Q*Ynr=+`kD2tYP_gXS#O}*3ygns@A!XA54Wu zifdY{k{=-FBWM>{M6h7Kd;6qnQSv1ImD?wom8V=%At!@Yj}Ncz;yRElNplH&UN8Ro*|u`hT;~jXjw9s(pd z2~N=9+5`_4EVygq?j8v4?u`X^x5nMw-CY~EzMbzod+&Sp+2@XN|MZU@y{gu#HM&MU z@0|0U?~hOK4xc-NCQzX8mB%QbK7)88?f8T7r>S7S^93R213Qw0S3()H`izBvuN*ZD z8sL7M5Yu6h^aQ_!zZq$xDIN}Fh_=v%9h^%xeuPL}VQ}$)Bg#H#PqF*7L~d8$)d#pu z2xY&>i5^63HM%^(KJjoLUvc`V9`IVd)<&Y%Pj)E=F zqkeyEHjJuF#c~N@A{uYy|2u`_-%LpUexZL|@=uGAe`49YvBH)ilJ{7@gY~u+;Hp}z zhQ8~KPo}@i4gRdqfcP0Ivto|jUSxBpSV(`R8jw`J2izE#C-eL$Ew|$n1IpuUIv^fVtQY@zbWkR4d44k=hN1eci{q?u|mzz=$)S-gQ*?g0`H|u^d z_XWrc?DO3fJ`EJlRBJ^p4)W1FRgY`x^Dmg3@%DOA=d`CI(!isi&KH$VkaQ)}eV@;r zZCA}Gc*Mm1qT2yId_IoZdV!4BT3z9o4!8O@pWa=@DGxqYK`HBHgai`^^Io(?c1(!t z+Aqb1YH`cKEo8R_i(V+$QNO?sv+O+E?J!nXrs2cDg)mlLR1@*xH(f9Zf&ma@8e|ed z5A&S!qJ>&(j}K*N8ed1MLb$x1$d%FE+w0$>>wqCdZe`YLJ48FX_L?~}iZ3bxS* z8Oh<^a_k8~@(3Z}7p`AlW$$W2B%~eaY;LutF}r-AD0oOeUiWry1i8GlVR; zyY&P9UmUaK@;^*9Q4M-L1+1ViSg+Ts-E=7N^v4$AIC#R)#)`ts@B9o(RX-lT$ewA8vK z%j&vHWQ4$?BFq-r$ZFnM`cilhPWYB2iPj*QcTCkpPf8@MlVbae&OlKr_j}eZ`brG_ zy0j*9Ks5_FZS&r9ESvgn&Ts=YK;pqdt^$f_!5<06&!jvY2 z2NYl6jT}_aHe8{h&)B`FRL0|74W6wf{=@^7(c`xiL)#qY6H4iUZonlvJz^(#dip5X z?UY|NVure+S`r1V)JBAYox?qUgiPzN7-=TA+iPA#oSg*vVTVHfs)y3t_izTIj#63` zjSE2>2ETKoch|!5u9l0Mk4ucN9iEd18+KuV1HP(8H=S%ItE8J8)2=CPSn9{>;;SZm zz3u&MatiY`-<|E$-6^f@y{i+tI}573TC|OeUXI18->zDP(h1&D*TkcZhupHd9RlAA zJB*JlG%u{;YEKMHB+uDt^;b=a352z$%jozb?XulZko4FEBjhrj(2wtVqnXR1i{y`11Vd#q*6uOVrJO6cD;|V+7%<+L1wTkqxLxZy7!~TR@8?K2Uys=P zN&a|=v}hNa!Pg1CS=I3!#n$uUZTC}s5?8t4*47yR`=}Dce4V~SIOcr)7NEwN*r0!^ zk<)+gz|T|pQOA_Ar*$b>8F2iS40*1BGfIPiij=g|rExRt^uZoQUp6V1kWCoO=YM@7 zA)9|@qJeTad8p#~Db)$+*r{C7RnY&DvQ}Jwd4}Bn9-Ez>*XJ~rCiIPsa6x&t(qi7e z)~V5pnnHoRa}Olj@?0ZfndC)EJ7#G_C5CDup6HV>&5EkZ9LqXgdk2ZNZ&@=2nz4Bt zl{95&R=ktAhy*~jX3pST4t-%=_x>v|pY_13o}t-I-M%PoPSjDzc^B1oruFsqIl6;f zLgkP|(yvO#%+0=;di9@;o@RoUwZNdOrFfxD29U;zLliia2=&Ut2Tu&r4`@j*LR-Mp z*@)IEIMkYHT$(mAJXD`T!}}uJ8EzF3iLmv2j-?)}>5?0?Gomxl3|~Y!x;5y(lg3J| z!E-)6!*$tr8>Gxr(>(~wvg8llOnyG;^ph|Q%gleY^Kdj}lksyt7pZF$zFV}tlK;$T z(a@0!5iugF)er<_+T9~7;jf(kY-HNYWy(-;4($}bB z2OB!*Ckv$%byCrhG%Jnfpc0r)m)Su56$@?bz<5$P?8fG(##f?DJ2uQl>@k3quJLM8 zCe!{Q`z?yJaRW_zubjy2^Pa063Yr{WDoe+lS;%#pxP@db(26I6X zN>7V>nbsc2{(w9SxnbF_xt4047DM26f7~WbdsU5`abAuPT%TOn?H%39u)R8u=FxgzO;1Nio1(B!!hB>T_Yf>kwL9CeNZ^ScRs9$ zug(WZMVvc&Km0hcfN;3P2{Pl&ylmujTaQfmDvFu$+T%O^EXr{Ht~3LPI3I&DCKshU zN5#O(FbgzKi$pb`9j_@rfZtUc6F&(N1K%(u zzs$l#iMce#n0g5rf8_SdhZb)7CM&)#POA*0E+X-Hcyh52B z`6z-%r!o}_42~zGqLTyEEoPbNE!}VKLtA&DcvsxvRE8D%dhBj?vDIe|JKN#JuL^LC z7^dUf7<_F~TYQArIley@AM;s{5q)ZCdx1|o28>F~&hW3|laK&W);Q+-HAyl8)R_W$ zw|mr+Y>SOcOYr4MI0cATDj;O}obXbVp5}qQa=UWhOv>Mf z4%yt}4VrZiK3{)|{#>k*>o-}pBLNO+Iy>c5O6Bz7Oen*;^-sJjFq@qs%P*Z{1a#o4 zhtlK20zIMBfKUXRW37_P{DOWSt5s4hpuL{rMAyr7UNE`h<4?ns8OZVWBl0nspMpMj zJ@GmuJ#BBgf?l&qU&VSQfeLOq{>DutmSwz-1AignCE3#abC1+aA*>$0Vp((B0q+6t zW*a!f3ZZ;0bk6{AcEW*%$WY}mB}nq<-`>~5OABcPr@7=*@pOl?qiGB-iy;9O?k=;! z7p|IbYt=1sSslq$O1{eZ8_qdj>V)2W@|0>EXJTht93SKZ-4UQiWipbUkJZtQq8fem z_T8^7olFt<4&7XKs312B!>z$f<*oz`o2Z5SCF8nuT1*9FWIdkOUm}u7^MnBT>v*c1 zwc3YP#=^8(W>p^A*#Y7AVf4Ffzu1)&hX&@^w!+xPg}c7z1lf5UX)aFkbH@`nG1C^@ z9JwpC{5I4|pyVv{HTmL+I2)Slq|nqumPF)|CO*tPSt;!Wdf?|sc1g6^4E9jSijQc& z-vmwB9}jI!^_uM1Lo#$KK^(mZ|Ds|Nt;riY0~O;p;Rjy@SHBsoJ<{-==&Ud+e#%bd z`52O|Y$-OpyE>L#aG zu`;GgulJ2sx-!ZgdQR+7vlpwoI=OzWMJ=ACRCwd1?E9cxx>}$MElD;QSz`75!mBd2 zvwcnfn!Ur}3=35ir&qCg`TyH zS113*(vzVrY+8%Lt&tJN6&_fVKA)e(JJ5=D?DFr7b03N*fS`PP{5U~5dNJcHa_(ra z#qRbk!O$MK4KED=u$8h}?D|IL1Wl?;?+;5R_S#Ap%(hBODB|hWgxW> z9P#&^6p)bCIuVjl7IIZQW&Q}N0gZzD zuQVeRQifP0W26)uofs;Z>tC*PX_2;LbB7=O_o|FiL(}hbM_X zbT3P*eY`bkv3tRm1yJ9@65?5EguzA~nB>o(fnSW2{tUZ+6l5hb)Llhx;hxMg*?T_U zPa30oKRujR&Zx`ZL^<25f2KD=PC~IiBzWIQE9Gfq;^ zUYv5Sy8T_ywtIoV13{2b6NyMzVVup%!b_4_^|FkL-o@eM)L3)-9^R4I_?*^acmkBF zPW9xMfORQStDX;X2CP{ua6Al^8ZSRPVZ;Yu=!*pYbek2`wde_BjShmoG}`z81Kt{? z9OjY3O7X^Agejdqio0ZDF%q%R9fN&k?{O7D@1{{;yiow+AamQpt?Rjn1=${oDsjqB zw|Xi*R%F@F4en=VqNm^2SHwtull{*tTvwbt5|s+!*Dp#UOs!qvP&#Y&(s%{ z$f?s<%P!y6HI?`@4wI^04ZNCJfz)NmWOQrNCf)kMUKB=lQEDiJ!tk=TM10ZzA+UJ% z#p%f*lCu|a8C&HSGRf9=rz=>_GuM)8uCF%t9|+AR&~pEpYWQVq0qR+9ov@i2n>Fd& z&WQ0JaWo(s3v~17l0O)AD9(J2a^4p`rM&Uebq;S$WV)}5v8$7^piCIS?G|qhTEOx~ zkshn{t>VumY*OM-F)sK&kDjF#Nh1cUc9{;B;zEX1z!-@CWw!eNS>5u#NF$0KN&Iw_ z-*CG7Y7mS(Ruj{@vhw&Tq;M2-ZKs0(hfG-SpQJRnl!qf5K|j$t&Pvww)v}Qph3VHFnmxY78DWGR`J`{t)~pk!;w~jxY0kjGVG;RVC6OQ zB@5l_2q5g!a&hk_$%7zMa01z#i0GV;r&n0#icJERRk7x>;G-NG9$Q;)n=9r` z4V804&TDK5^P@u9R?XFJGx!gYEG}Pm(=j^OuU;*3XuZ~-R?B_MEa;Zu=J}}?a2L)H zF869$wP!W}`4m}Y9`X81!Ck0v2;dt&#BYBo|CP9RKdDuTXU(TMqmnG-t* zjo}~=IXfsRXiD`6Eu2lt*B7BYw3YO{{}Mibi+N&fIK%w{o5Ebz^rp7H;L-ZbHTXk3 zh@!08SF4>SYq2K7vS^-gaIU+&jmiDs(qN%o@7P2k=2{Ekh|jKhX&GeA;Fl!EGM`|W zK?e`;D?wI|LssWe$3{j#Zc>L@_>w|Dq7Y1PSJi`4ESf{OuJ9|5R6cDTZ?o`oP=+}N z+epLX?h|wziS*L2lHu;Xn>k-SX}MIO+1tnIS<0c3q*u1z{wRhlbZ`J zznI{^&AI#el-U6fG4X8r1K3GGOO=s4L-EaV0cWlhk5J`A$cc<w~`-Y>_wA&cQ#DJ0NN`{X+b&|XgGyGL;8uF?v#u1aUm^R!M9%uh<9$5@b%S7 zD~9-2F64$FOP?uM|fMQUIm>h69xN6YCXeW<{w-*|0l<)yzIet!$DjJrHBGEkfNRQ7{IfuDvk zk0bdqL6aWPP;%L_bMGLwElSwpj=LbX5qrz5qO#^;(OzGC8+miK;!%+EbmYjtOf5(u zMnK9url+w)e2=(|GDlujpL&4tCi-Z~6oh=f5n|~ObWyi`kn=-p!Pbv!H{`RWzQRmK zf0c}^5YkMG$Nqs=@-kHNi%U!hJHWr>!@}9nkf(D&$v7)scN=^EV=Y_z<+q6%ENzQ4 zp@`6Q=Nbid@Dk6L+fL5XxXpWgmjsi|m7j_+zzaj6I6@-7SJlChDQa)#mS`cj~>PsWkc)f&!b|boiD|6kSnlY!p5;Z)ouB0yKso4F*x5_Z)9sO@_27r}P3Z`cb?)q$^jH8*H!FGJ(1DF5h z_7jQcy2cLD_vg8gt$F<=DW|H-0p_^Zu;O(^?Q&Ze{c`xFMFDh@Gm6m6i^ z$(}%@np`;6E81cl*9*%!2f$tT0=_2YzH-P5v^^;4QzLOr9@co5(4|^YJ1ik>7epRjcJeza zD>vIsnMe(D6$9$NthSkeu@}JN@fa|dzqCoWP{-4t?x8@W6MDc+j_fZ`bz0GUiV15K zLq;i{VEHv%K-DR6L+p?L1%4;e3gpz&b(Y@Ul=Ihwev(cZIl<;xV6~=qYNbk*-kuEM zHl4lMShTE3nwX2yseu!5d2f0s&39@(a@6F5&X>fKU=6-v%%x* znC{?yEbZBiFiY+^SOuk#J6#B961?WWB6G-A9?M%#>%}q`>R~Zz!vR3* z)@c&1%_Z5rHK~1P-KDLMP(p2Lv+|1gS+bp?RR<8n{YqFI^Yn3-b18^NKD~-q@?Q-_ z5Eff?Dn2*t2aZs^~Hwl(Y|BG&I6$119PuwcaSnX z!;42(-nCM^vL6X@-Wo-FU$^0umzZG-6k4eys6Cxv(^}Pf#GDUx*v8L5l|m_-;kzARa?t#y}4{2YA3Xs&1eja8tKS`F}oXmTH2^#Cl< z6pTC&>Ulzr?3;2u#rz#?v~*2+39CZ+9ZdSW*Ti-VlV6`$pfDl{FOTiX4KL4S1rVGe z%6~4}W$yFibkc6fX2?a%Aje>lQ6Okr!e!E1MmL3|gTyWo!29)Y~C-k;m7&5o_MzfzQt6zoJe30>ZGM zv?&84Pk61ESnlZl*HtE-P|C`OqU4%w-@9epBg8e~^gS(MkNJ?kk7glWr`L+3AqjG* zdtGNbd>`VifYSx?%+84e6WWD;5t^O8{luY4sRD6egRKsY!tF(eLk7 zAC(YetlXJ^Pxk%A+6BPJ@Mdx?zJZk+^)(#!z?MR7RoAthS4RH`kl3F6!Cd^hT|}1C z>^J@qhkaA3Xcdd%&7v|B-wFwaow%T5T)@9B`|v_0-4y< zD_gE~C0B{Uf&Nd0_35x~>!;H$~E=hwIk zO|$};9eFnNM{NH|-xozE-pGvE9g{#|3L~tCL~puPKFH8scc;uh5fu|}o#N=136%UI z<)Lj0%&aabRad>w8v_2A#M7umBE48mXE{x}-wS~y8V@431+ps#$jpVAKPVbzj{%Nc zm1@|l9NT)Yj1E|7-N4Jm{o?(iz@bg4A#+L(a8S01h|W}W(te-hwsVoMs)dCnJU_`a z-{}3^xUsX9>7|Oeh7rqA!f{9LsGuMP2noqpF``4lX4ZQq_2{_{6iyBLp`9tKgMfSG zii+FM_=#h6xm6hu7h};{jHYps^17Hd6Qo!;Y(Gz~ISFUVv=!|x9g!VDCMvmlptHeJ zwSbQ>(8_=BtEpq<--xDvUaO)}yD;wLAT(vW_crcpF5zJQ<aH#CpR1zv!@Yg^dpgXx;Yh$f(JLX0Sn0MBW`El+FtL%ttJez_?oEghva4Ijb&(zp{>2N#7Hy#&UO&<`EOlO{+Y7y4~MRQ`{IA5 mmczC@m>0&sedIjz8`952BRUId!@ADA5EuC_Tq>yJ^M3%Q6KT}| diff --git a/docs/_images/generic_types_fixed.png b/docs/_images/generic_types_fixed.png deleted file mode 100644 index 91d2ebe09f5175d8432b0a784d19414ff8423c41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7923 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 Fe*q%wj^qFU diff --git a/docs/_images/good_access.png b/docs/_images/good_access.png deleted file mode 100644 index c3d4ab85ea6493e9408a1d69fc501f4c95b0f601..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5576 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 diff --git a/docs/_images/inheritance_non_generic_to_generic.png b/docs/_images/inheritance_non_generic_to_generic.png deleted file mode 100644 index c6c7878724ab0f6a0c33e0eea49956a05baf4611..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8070 zcma)hbx<79vn~=OcpxM|STwjpa0~9bxD#M;7I%UK*Whkh+;vT{&+PtXS#Z7rmDZwr%#{ni%?aT!NwrLKtMpimXnoKM?gSSe6hXJQD4Ss0UQ07 z2kI9E8A*iae^*{dapH^TwWF-A3jzWr!M__3AtQ_E#fj!BrzC~8fr^63gk3rCn&QPp z>?)<>Dgm~)w{UPpkZ`szb+s_3@UV8ZqL7hOQq>N`d5?fVb}A<+rs27Gvf|~dIQKjV zpEIXTPe0C-Je{}9c|EG!y{&jrPP*&eRj%vKy)2hM!pP$2l)-raGf<5Ig^v5NI!0(U zse^xwk#a|(olPj0Nug7c#l0%R6PGo?pbY-c2KsA7%HZ$aZ^i$U0kgE+*qk;QvK1M7 zaMBxH)o1fZ(NT}(I=+t~ofRD*w1U|#v=4!M8dNL{87`%z2E!i@chbvFr6HLukZ8 zH%?cAbv0(;$B*XN+bVVIvc~a)*_j)Z zlhiR?)CMa%x6w;E_f_6N&FgCc;jY@~5zTbfgeiBx2LJKTxZs$hc5>fK3(hJt$kWoJFYjI0FAm{Kh=*`V3 z!xvaf@{YKF;CEGFu<{M=4i#^$pnwmT;(Px{;MJ3cOAD05ZePWJP`k0+s#0yd(;T_; zd52`zN6&XI^~xj<8l**Lk#=@MzF)UnJT`xYTb+!$|sP48+pg!d&i$J*FgJgERnSk`Br!% z=QaEUbG%zyWqvIJ_qdb)0KNC!t8=LWu~=71BSn*p89H!L}@cR2zzz}vbH{a!B&+$DT5`|WrIS?^F1>t&dAyASWm1e;i7!>@m`py}YOCF_15xnv7>^|ETY=*I0AY3?oHH5$-i|dFcDr=x zI&GD>PUKy^WB$a5Tu#HQ8&O{JND|<)^3!)NmK`DWBcZJWia`+0xxG~juHhd9y64xZ ztADl3KOoWXA4&E!q9IR#ey>ZPDSe4Pi=h@5!c|$dRg43js6e~RF@AP67B}gG%3*eD z+wEB$Xe-e6F`rQk7%@ma($H)rFfmIoBeFD>n~8KPTtyoQHA6-Uo3|`#VF;@uO^;Y? zN-jw-EHNzMtLApz$V`@zJD$i`@`B8z*SM%!9>r!tnw%-hr~s%ly_yziEHdNE#jI_; z;E4;C?$MYsYHwhVE=uF--bsLN@Nw%;!JS7nkwfa;WejoZqj3)ficUQgjZ=kU0pJbC zr$~>X627{>c%56L;|)O{+ygv1sys!1TINVYdTe|hgQr&Sm9*mgfU(Y$Mmm^N1zXv;4Nf`tSms04~!Bzp%IB4_9yp71b#SJ;T1J_7d zR}*s9y|f}P@2dwo(%6YiwbZ(w7$sI=^SR)F{wnpeSC0NjV~^`n{yx9h-_-jqb*tQu z_ASGKj#5M)E&saVB*6}_BV@K>eQs!ixliGJ+La!L7lL-R3(ezcbMB72v39;>B@#33 zq@u_0mc&=Hp$tA4_92&_1RiGBg^WwFhTMe1$qoN5!4>(*Xm|=qRpn}bWFqKSEpr%A zglJy|Izg&sQ;)<}9C(SPpn&Yl3|#O-jEoq0gY`t;NWZSK!^?$Jp)&RYex>ulr1j@#iQrm7?lB*^b2An^)m`uTcBI3g0 zgQFtia12rA$!?``KwrLa{bk%_kg3OGGc*ynoh&V9`k1x;jfWDJ(Hz6UlEQwkqb0D& zuUe5nfY<-Paj$zT@ycg3vzzr?(9dw_ZN76rMp_Ng>GEAk6v$|gTQQqZY(D+dWDHL? zPGbkvUY!ioQVt7~vIl~!PeAp1TqYsjDNX7!`dHT`WhuUtMKurs_;C>UC)TbkvMJ9Y zUR3w_1W|nibrU)Y(zoMYt=LE*Okl`L>xgDL08Pd*Q*1|zUXl=wa(=l8tWtYeIhFOqh~eQm^%yc%4%Yg6q7Y^O{fwo@3cpTi4BGTUA^BTXyDajPY^qc zn{*>B{pWO-1?qFo5J)+dD(}(fF(R^$x!_Ts|Tu#^Nw(>|mmvx6$Tt zt?TI-@NailOI}-hTWHJuwA8hDuAfgCndoEYboBSna`Wn4_RoeC7ln3lbs+H8Vhfe_ z>m3KLgF&%+AtT#Ic5-PN@R&;iT+oGS#%!3|v4TGmE=E)69O5Z%+C^?M5X}(muofu| zv4F=h?Jrh_x&98p+cc{0%o=G=`+hb%}BAbub=V@QjVc)&TaaB&PmV7a!HTvcIgV0c%aio(#R} zNI-PBI8luBKs1T6s;V~D<;c7jJ-p7d*6x7ukk`|Te9!NoZM*0k4ZWfv@k3+z6#J!0 zQ%N)hJxW$-Q%{^2@u~(YN$jy=ov;LAZDE zJD9N!qOqp3#Y-yzSBZ>>$T!T~&Fb{UNEOT3_b7z$S#J4vRqn)iUm73Uk8#2CL|w^- ziVhz4Hxgm-IkM-%aQ!gw^OJ_wEzA>i>;{JVJCbN?7wQQGGhayGb>+z zQ(~zm50rr%SuL2kl=>41#+V=W={EG=lHD`YpKCRpfG zXz8WP&)+y2Mr57{cbVSNrO#(rvf|;zXTJx{h=dA6-B|#RfQ^{1J|ymf70eG$G9=s% z36@vXW3P%%)sZ4V>_$jx1z((Y zzHw0qmnovd4f^`}#w%@QdZ=+WuXM#UXig@=it;h#OpoVZx6k>TvG zJlxV9-h;C0#~W^@9k(gc|Go{0G5U@7q&6K_&9jL}_6-V8CUyuFm|NZGPKFx1ruBBN zkk=o-(T_Xe>Kt;Z91>diw-%Paiq_6_WoN-JiFhsqZ|wNu0O)!+ADby6$9H}8i*&x;BGlsUP@j+1 z>UJ%*(My1tTN~y%kS1%l@-hLXE zBB$&#dntT{&pv+5N$zi;@e93^FM#`5<3nV8#iQO;8Z8L?JF{#vvarOcQHyTQ_ft2= zFri;X!W{Slkkq>r3P87u50>L$URP9c|4P_c{~p0$y@)Fdt3-!t^D?$VNJr31a*rFh zo1BFn+}zx>PhL_4G6escXs(6axId8^h@6RQP9idU^iFyjgq_Z5Jl)Sg*&eUqvj1?5 z%MiTof<3#ljy?+4R#$Woq|kRKn9x#x4C!{yJ`i1h^i?BwCNIL<%)dHaP=ZzgICyF| zZzccO#o69Vq66mg)9VY@IynFcg-E?Wv7%+%e7SvaLNEx2JT6%S86?TJ4-bb5qu!V3 zw~;_(u(x38Q)BI;CreG79;0(sZEYsTM%W(Ko;0iuR0+{fU?(1OSw#XfGpJ$)bR4*J#h(sI4c~&8IVU ze?Id`MZWdgIno9hyx%0`G~ag&^1ki|)x_&*5AqIo{&@Pp{Tk1lIG56SN&j%|X!TO_pFQLyMjfHYxD;4t#9 z6_?SZB1wNmaW%CofG*@l*^k70N*qYB+-E#j?Opq|2+#kICUidP}IAAaf)XUV`&D`bmgDcz=~T{Q3^JZ>?)P+k(}o z{qqvfNCXRE7?2#-PI?J>gh9VGd0S=6^M2&B)@j zo035%{<-{U-oL39xmxMDjV8H$gR_!7&g0g3FAG_amX^i1JF!k;ej)(~x@`=vTQ}oe zN^34sE{z;O6My1cQ0;WRX3lC!BNP61W@sq?0vT>Jv2ak!&w3}oVT_-}NOtBJp$*Ee zg7l>JN{{bCcvC4?EMTQ3o!q%qpVDlEh4Evu<$Y{N$&AfwMe{RkN+W(Q(AN;7>a8%E zwGDx3Oba;dUBQTngf0vb%7MZzy|0$+-VO4S)nV+$w;!k*>khXtJGamgi8P>j$(BzR zhl)sfrKn;)oHrD?>N=^I#i9ad$`G&iAE=yGQw>v5^Ke2m2CL&l>DbeqC zTDbS`)D6)3%qx1#%tu}Myf@&peCy5DzD|fu_HNyE(N4eS_YT~V!5G?-hF$`;!}XPL zsc-k^Mn9J%*0u0^-%WPtCBs^;suUhOnfNR{hwSSIYAc?E|EzR~k|FMK=25@lma^46n(ZmaI2c;#-7Hk!FW_JQ zH7&j^xYUIe_SG8*;={gYjN+c039mNYj=Ss`f24hu`ZDeD$>pqQ?1~`0YY^sBp~-iv z1<@LC;u*+1f0TQg)o0OZH%Gavv?l7|BwHA2_?X7=kTV*-VMf34hwb2|9qB((OU`Hd zFI}nwe2Ol%xCdOg*)}WOzeMCPj?F$3`Ghb(mF>n<1tYu3Yxi;jh;RoFXiIU zaGEU=HD<@@PxmTp#npqEP?AvXp@PDD+ZloQ zjju*4t6$e`Bu?^ET#u0j!vsh0hj-MLmTm)UYzUT0{b+65DUw$s54barM!jml&@YWL z^@ld2$@cB;Nd%#hSXgDD)euybXB$_t3!ZA*oQ-Z*RmeoiM9E1lkiG`E_VmbsyxNI? z?Ry#gn0@m({=YdY%C)S0`=ut!#cACF7Ue!tYi}#73}q5WB^Qfd;~NRlV{Gh75B#RY zkUe8cnmj78-f}ewz+h>bfq4Iqt!m`bx2HOBwdZ=1(9j&n&kyM>a|j1>ugUNROwEWx zSq{4k%dSmNLvXnnhrIwluL;cM%av11g@F_U}WTw6(?E&3Dyej2iw&A<4oj5M3e>#kq23cqr>KQjQHBbqsuya;gJ|?B-b!LfAaro&g>&PA3GV4FH8{o7B*g~&!%29XsT3WE_iP*z;rjR5sC%KsqTdA<>JCFtY)8F zG26@V9`w-$lu2=Aj$yG3oTR+u(?#{gS-K z@7-xujVvfh%iqBv$VZXO77H3`2Er(apt3y0Bl@>tl=U68ZLcJQRsB_)b}K|EiMUQ=}XhI^;$S2S!#d0 zmP@`+JptZ=QY1Hc`7zJ)%|rGdcR$UR=~@<+obO_orI6d8p&Vf8+SSL>0FITQky7+; zmfD3`XN*jtRRu1kr49R!Xmin@l`UFgP2ZWpCS1(-N1aY?v^AF4!$m~F#7%C>!n+)BOHYkJXL@xtK^>8hdh<_L8BMveuEpH{hUdkkGw_hlJ&a!6HJxB^-r8{nRG1|mp=aH1Uc1vt8ZZQS7SAAMiw60F z6I&9@RW=e%Rc<7|0ayuFL1!7p&1t@&^|niR!hU#WxCLF^5=$AEfhEcH4`idGqs+#& z8O|a*a{5`pEHg-2A6jP^8f@=*synpw7!O)di#k%o$+~Bb3|d@7=4+Nu>lu?uGz0o2 zGY%ZQe1CxzbqT^+#QciD=wCKcpR7)k7#S8T0iuJxJx`Do;tBQM#+f!9r%5dS6k*yB zIs~K$kj3_jd&QWwhFOIn8ULoKY9u*(fOyreT?&R-m<*V!5imZI8Gt8jM z_*UibYAX=d+jklDTga_?QCR^#AnXbCMWq{~{TIHXq8+M{l+X@ymr5WK%8l0}tq`~s zvXm6*Ac3KmnSvXJl{n;e8hEg+P+V2@-7yGvF__(UlV_kor{~yA_rPlJbFIAc=eOsB z$Cf*|Z;tuFNNq4DP%5WX327cJEpw<~sn{}Tn7RF=jS`y9+Z;K6C_X1gs^)9Lvw=WJJTm`VBMkHbREOui%JczarWJSJB86A6@ za$E&27S|Y^f+vbI7#@Q?ISr#Z=?po395#pTS0*MIi9N(;C#1BykSLLBCcO%xD1U>% zM=?RpuX6eN*Ew$)^fd6{Eoihj8tKbX2gs3|Z{cWBmccF*3yF0i(EIKG4_Xk~#zI+` z5j6VSF1IP2L#(TYLQx@Gt@1U7>aIb`5fSLE@hndFOd)fKt>U_DHk_GU^UC2~;Yj70 z6SXNzY1{oHsUcxtt1epEGMPio`43Z7sY!9(bM4s{2d;rS!A(B21jRUjq+x2+dcC;* zz8Wn8?%F6b>v(3wO5|qV*X;PCV)g6wK@E4Nh(t?d0YzciDvw^F6KCrUtSXHdaT=s_ z_b1vTq$AZ|)i?mHfiGfmaFKJJ%gzXJhQ@%@htDJVZOzue?a2TS3BRu>kB8GegOx~# zVuF8j*>g+X0Dn+-6K5B=MtkBb83WKXLyL4&!<*AcZqCiyj` zq!F&-U}$5B>jC`{)d|B)B~7lIb75X74P>d>Z1hKRgTbM649KD6cZNE>Z3?7uil?3m zdAxNkd*@ezL^__hP>;eh^X_=cCn`i!~d<*o2Pjn`ANb^Apz ze&G>z${zq{Bzm`z>Gg@4L@=cor8S;xfjvyzc LELkOP67+unvX0F5 diff --git a/docs/_images/instance_with_generic_class_wearning.png b/docs/_images/instance_with_generic_class_wearning.png deleted file mode 100644 index 0b02e6dc7ce97375ad4745af06062c77a943a4e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4796 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 Pybx)t>#0>kUk3dLR8mj0 diff --git a/docs/_images/instance_with_generic_types2_wearning.png b/docs/_images/instance_with_generic_types2_wearning.png deleted file mode 100644 index a392af70105a07ac7a0251bc89756b51eaddb7dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3554 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 G+`j-9!06Nf diff --git a/docs/_images/method_way_sphinx.png b/docs/_images/method_way_sphinx.png deleted file mode 100644 index 6d14a595a30cfe437bda060de6c175b2a22a7da2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26143 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 BjMo4F diff --git a/docs/_images/property_annotation.png b/docs/_images/property_annotation.png deleted file mode 100644 index f821a255605f7d6f8c5b187ed7971be066c519cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33398 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 diff --git a/docs/_images/property_functional.png b/docs/_images/property_functional.png deleted file mode 100644 index 248f0e065ec20667bf0951762b6d21c97fc062a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34977 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%0DH`=8!3t^f9(P5v+MS&1A`>%Y;; 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 diff --git a/docs/_images/python_functional_programming.png b/docs/_images/python_functional_programming.png deleted file mode 100644 index 10fddcfda6ad8b71376630ffdf606a1c2d454825..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15864 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 diff --git a/docs/_images/python_imperative_programming.png b/docs/_images/python_imperative_programming.png deleted file mode 100644 index 40a59b445758d6189343e7772d7950e4408ac952..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7771 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
=qJ}M_dQXd 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 Z6g}GFLg)V6XU`WxR#H)-TFfZ;{{h`C1TX*q diff --git a/docs/_images/python_poo_programming.png b/docs/_images/python_poo_programming.png deleted file mode 100644 index 5d8aefe25c1bd0c011345859a0fd3174b95a216d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20820 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 DRH27Q diff --git a/docs/_images/types_in_methods.png b/docs/_images/types_in_methods.png deleted file mode 100644 index 5ea9076b370abfb7342dee81b03ceafb6651edc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18914 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 Rh_?U$002ovPDHLkV1jCG8nXZZ diff --git a/docs/_images/with_getter_setter.png b/docs/_images/with_getter_setter.png deleted file mode 100644 index e96691d7a2f9859efe50f75d2d6c1dde33695a96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25386 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 TDh8s2#VcuX`L7kCh5`Qv0U#2% diff --git a/docs/_images/without_getter_setter.png b/docs/_images/without_getter_setter.png deleted file mode 100644 index 36f68675c97f5803f159c433bbab76a62417338c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8150 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 diff --git a/docs/_modules/index.html b/docs/_modules/index.html index 97c8bd93..895b27d7 100644 --- a/docs/_modules/index.html +++ b/docs/_modules/index.html @@ -1,9 +1,9 @@ - - + + @@ -12,18 +12,15 @@ - Overview: module code — jMetalPy 1.5.3 documentation - - - - - - - - - - - + Overview: module code — jMetalPy 1.7.0 documentation + + + + + + + + @@ -39,7 +36,8 @@

Navigation

- + +
@@ -50,7 +48,7 @@

Navigation

- - - - - - - + jmetal.algorithm.multiobjective.gde3 — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -109,39 +106,43 @@

    Table Of Contents

    Source code for jmetal.algorithm.multiobjective.gde3

    -from typing import TypeVar, List
    +from typing import List, TypeVar
     
     from jmetal.config import store
    -from jmetal.core.algorithm import EvolutionaryAlgorithm, DynamicAlgorithm
    -from jmetal.core.problem import Problem, DynamicProblem
    +from jmetal.core.algorithm import DynamicAlgorithm, EvolutionaryAlgorithm
    +from jmetal.core.problem import DynamicProblem, Problem
     from jmetal.core.solution import FloatSolution
    -from jmetal.operator import DifferentialEvolutionCrossover, RankingAndCrowdingDistanceSelection
    +from jmetal.operator.crossover import DifferentialEvolutionCrossover
    +from jmetal.operator.selection import DifferentialEvolutionSelection
    +
     from jmetal.operator.selection import DifferentialEvolutionSelection
     from jmetal.util.comparator import Comparator, DominanceComparator
     from jmetal.util.evaluator import Evaluator
     from jmetal.util.generator import Generator
     from jmetal.util.termination_criterion import TerminationCriterion
     
    -S = TypeVar('S')
    +S = TypeVar("S")
     R = List[S]
     
     
    -
    [docs]class GDE3(EvolutionaryAlgorithm[FloatSolution, FloatSolution]): - - def __init__(self, - problem: Problem, - population_size: int, - cr: float, - f: float, - termination_criterion: TerminationCriterion = store.default_termination_criteria, - k: float = 0.5, - population_generator: Generator = store.default_generator, - population_evaluator: Evaluator = store.default_evaluator, - dominance_comparator: Comparator = store.default_comparator): +
    +[docs] +class GDE3(EvolutionaryAlgorithm[FloatSolution, FloatSolution]): + def __init__( + self, + problem: Problem, + population_size: int, + cr: float, + f: float, + termination_criterion: TerminationCriterion = store.default_termination_criteria, + k: float = 0.5, + population_generator: Generator = store.default_generator, + population_evaluator: Evaluator = store.default_evaluator, + dominance_comparator: Comparator = store.default_comparator, + ): super(GDE3, self).__init__( - problem=problem, - population_size=population_size, - offspring_population_size=population_size) + problem=problem, population_size=population_size, offspring_population_size=population_size + ) self.dominance_comparator = dominance_comparator self.selection_operator = DifferentialEvolutionSelection() self.crossover_operator = DifferentialEvolutionCrossover(cr, f, k) @@ -152,7 +153,9 @@

    Source code for jmetal.algorithm.multiobjective.gde3

    self.termination_criterion = termination_criterion self.observable.register(termination_criterion) -
    [docs] def selection(self, population: List[FloatSolution]) -> List[FloatSolution]: +
    +[docs] + def selection(self, population: List[FloatSolution]) -> List[FloatSolution]: mating_pool = [] for i in range(self.population_size): @@ -162,20 +165,26 @@

    Source code for jmetal.algorithm.multiobjective.gde3

    return mating_pool
    -
    [docs] def reproduction(self, mating_pool: List[S]) -> List[S]: + +
    +[docs] + def reproduction(self, mating_pool: List[S]) -> List[S]: offspring_population = [] first_parent_index = 0 for solution in self.solutions: self.crossover_operator.current_individual = solution - parents = mating_pool[first_parent_index:first_parent_index + 3] + parents = mating_pool[first_parent_index : first_parent_index + 3] first_parent_index += 3 offspring_population.append(self.crossover_operator.execute(parents)[0]) return offspring_population
    -
    [docs] def replacement(self, population: List[S], offspring_population: List[FloatSolution]) -> List[List[FloatSolution]]: + +
    +[docs] + def replacement(self, population: List[S], offspring_population: List[FloatSolution]) -> List[List[FloatSolution]]: tmp_list = [] for solution1, solution2 in zip(self.solutions, offspring_population): @@ -194,62 +203,100 @@

    Source code for jmetal.algorithm.multiobjective.gde3

    self.population_size, dominance_comparator=self.dominance_comparator ).execute(join_population)
    -
    [docs] def create_initial_solutions(self) -> List[FloatSolution]: + +
    +[docs] + def create_initial_solutions(self) -> List[FloatSolution]: return [self.population_generator.new(self.problem) for _ in range(self.population_size)]
    -
    [docs] def evaluate(self, solution_list: List[FloatSolution]) -> List[FloatSolution]: + +
    +[docs] + def evaluate(self, solution_list: List[FloatSolution]) -> List[FloatSolution]: return self.population_evaluator.evaluate(solution_list, self.problem)
    -
    [docs] def stopping_condition_is_met(self) -> bool: + +
    +[docs] + def stopping_condition_is_met(self) -> bool: return self.termination_criterion.is_met
    -
    [docs] def get_result(self) -> List[FloatSolution]: + +
    +[docs] + def result(self) -> List[FloatSolution]: return self.solutions
    -
    [docs] def get_name(self) -> str: - return 'GDE3'
    + +
    +[docs] + def get_name(self) -> str: + return "GDE3"
    +
    -
    [docs]class DynamicGDE3(GDE3, DynamicAlgorithm): - def __init__(self, - problem: DynamicProblem, - population_size: int, - cr: float, - f: float, - termination_criterion: TerminationCriterion, - k: float = 0.5, - population_generator: Generator = store.default_generator, - population_evaluator: Evaluator = store.default_evaluator, - dominance_comparator: Comparator = DominanceComparator()): +
    +[docs] +class DynamicGDE3(GDE3, DynamicAlgorithm): + def __init__( + self, + problem: DynamicProblem, + population_size: int, + cr: float, + f: float, + termination_criterion: TerminationCriterion, + k: float = 0.5, + population_generator: Generator = store.default_generator, + population_evaluator: Evaluator = store.default_evaluator, + dominance_comparator: Comparator = DominanceComparator(), + ): super(DynamicGDE3, self).__init__( - problem, population_size, cr, f, termination_criterion, k, - population_generator, population_evaluator, dominance_comparator) + problem, + population_size, + cr, + f, + termination_criterion, + k, + population_generator, + population_evaluator, + dominance_comparator, + ) self.completed_iterations = 0 -
    [docs] def restart(self) -> None: +
    +[docs] + def restart(self) -> None: pass
    -
    [docs] def update_progress(self): + +
    +[docs] + def update_progress(self): if self.problem.the_problem_has_changed(): self.restart() self.problem.clear_changed() - observable_data = self.get_observable_data() + observable_data = self.observable_data() self.observable.notify_all(**observable_data) self.evaluations += self.offspring_population_size
    -
    [docs] def stopping_condition_is_met(self): + +
    +[docs] + def stopping_condition_is_met(self): if self.termination_criterion.is_met: - observable_data = self.get_observable_data() + observable_data = self.observable_data() self.observable.notify_all(**observable_data) self.restart() self.init_progress() - self.completed_iterations += 1
    + self.completed_iterations += 1
    +
    +
    @@ -266,8 +313,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.algorithm.multiobjective.hype — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -109,39 +106,41 @@

    Table Of Contents

    Source code for jmetal.algorithm.multiobjective.hype

    -from typing import TypeVar, List
    +from typing import List, TypeVar
     
     from jmetal.algorithm.singleobjective.genetic_algorithm import GeneticAlgorithm
     from jmetal.config import store
    -from jmetal.core.operator import Mutation, Crossover
    +from jmetal.core.operator import Crossover, Mutation
     from jmetal.core.problem import Problem
     from jmetal.core.solution import Solution
    -from jmetal.operator import BinaryTournamentSelection
    +from jmetal.operator.selection import BinaryTournamentSelection
     from jmetal.operator.selection import RankingAndFitnessSelection
    -from jmetal.util.comparator import Comparator
    -from jmetal.util.comparator import SolutionAttributeComparator
    +from jmetal.util.comparator import Comparator, SolutionAttributeComparator
     from jmetal.util.evaluator import Evaluator
     from jmetal.util.generator import Generator
     from jmetal.util.termination_criterion import TerminationCriterion
     
    -S = TypeVar('S')
    -R = TypeVar('R')
    -
    -
    -
    [docs]class HYPE(GeneticAlgorithm[S, R]): - - def __init__(self, - problem: Problem, - reference_point: Solution, - population_size: int, - offspring_population_size: int, - mutation: Mutation, - crossover: Crossover, - termination_criterion: TerminationCriterion = store.default_termination_criteria, - population_generator: Generator = store.default_generator, - population_evaluator: Evaluator = store.default_evaluator, - dominance_comparator: Comparator = store.default_comparator): - """ This is an implementation of the Hypervolume Estimation Algorithm for Multi-objective Optimization +S = TypeVar("S") +R = TypeVar("R") + + +
    +[docs] +class HYPE(GeneticAlgorithm[S, R]): + def __init__( + self, + problem: Problem, + reference_point: Solution, + population_size: int, + offspring_population_size: int, + mutation: Mutation, + crossover: Crossover, + termination_criterion: TerminationCriterion = store.default_termination_criteria, + population_generator: Generator = store.default_generator, + population_evaluator: Evaluator = store.default_evaluator, + dominance_comparator: Comparator = store.default_comparator, + ): + """This is an implementation of the Hypervolume Estimation Algorithm for Multi-objective Optimization proposed in: * J. Bader and E. Zitzler. HypE: An Algorithm for Fast Hypervolume-Based Many-Objective @@ -160,10 +159,11 @@

    Source code for jmetal.algorithm.multiobjective.hype

    """ selection = BinaryTournamentSelection( - comparator=SolutionAttributeComparator(key='fitness', lowest_is_best=False)) - self.ranking_fitness = RankingAndFitnessSelection(population_size, - dominance_comparator=dominance_comparator, - reference_point=reference_point) + comparator=SolutionAttributeComparator(key="fitness", lowest_is_best=False) + ) + self.ranking_fitness = RankingAndFitnessSelection( + population_size, dominance_comparator=dominance_comparator, reference_point=reference_point + ) self.reference_point = reference_point self.dominance_comparator = dominance_comparator @@ -176,24 +176,38 @@

    Source code for jmetal.algorithm.multiobjective.hype

    selection=selection, termination_criterion=termination_criterion, population_evaluator=population_evaluator, - population_generator=population_generator + population_generator=population_generator, ) -
    [docs] def evaluate(self, population: List[S]): +
    +[docs] + def evaluate(self, population: List[S]): population = self.population_evaluator.evaluate(population, self.problem) - population = self.ranking_fitness.compute_hypervol_fitness_values(population, self.reference_point, - len(population)) + population = self.ranking_fitness.compute_hypervol_fitness_values( + population, self.reference_point, len(population) + ) return population
    -
    [docs] def replacement(self, population: List[S], offspring_population: List[S]) -> List[List[S]]: + +
    +[docs] + def replacement(self, population: List[S], offspring_population: List[S]) -> List[List[S]]: join_population = population + offspring_population return self.ranking_fitness.execute(join_population)
    -
    [docs] def get_result(self) -> R: + +
    +[docs] + def result(self) -> R: return self.solutions
    -
    [docs] def get_name(self) -> str: - return 'HYPE'
    + +
    +[docs] + def get_name(self) -> str: + return "HYPE"
    +
    +
    @@ -210,8 +224,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.algorithm.multiobjective.ibea — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -109,38 +106,41 @@

    Table Of Contents

    Source code for jmetal.algorithm.multiobjective.ibea

    -from typing import TypeVar, List
    +from typing import List, TypeVar
     
     import numpy as np
     
     from jmetal.algorithm.singleobjective.genetic_algorithm import GeneticAlgorithm
     from jmetal.config import store
    -from jmetal.core.operator import Mutation, Crossover
    +from jmetal.core.operator import Crossover, Mutation
     from jmetal.core.problem import Problem
     from jmetal.core.quality_indicator import EpsilonIndicator
    -from jmetal.operator import BinaryTournamentSelection
    +from jmetal.operator.selection import BinaryTournamentSelection
     from jmetal.util.comparator import SolutionAttributeComparator
     from jmetal.util.evaluator import Evaluator
     from jmetal.util.generator import Generator
     from jmetal.util.termination_criterion import TerminationCriterion
     
    -S = TypeVar('S')
    -R = TypeVar('R')
    -
    +S = TypeVar("S")
    +R = TypeVar("R")
     
    -
    [docs]class IBEA(GeneticAlgorithm[S, R]): - def __init__(self, - problem: Problem, - population_size: int, - offspring_population_size: int, - mutation: Mutation, - crossover: Crossover, - kappa: float, - termination_criterion: TerminationCriterion = store.default_termination_criteria, - population_generator: Generator = store.default_generator, - population_evaluator: Evaluator = store.default_evaluator): - """ Epsilon IBEA implementation as described in +
    +[docs] +class IBEA(GeneticAlgorithm[S, R]): + def __init__( + self, + problem: Problem, + population_size: int, + offspring_population_size: int, + mutation: Mutation, + crossover: Crossover, + kappa: float, + termination_criterion: TerminationCriterion = store.default_termination_criteria, + population_generator: Generator = store.default_generator, + population_evaluator: Evaluator = store.default_evaluator, + ): + """Epsilon IBEA implementation as described in * Zitzler, Eckart, and Simon Künzli. "Indicator-based selection in multiobjective search." In International Conference on Parallel Problem Solving from Nature, pp. 832-842. Springer, @@ -161,7 +161,8 @@

    Source code for jmetal.algorithm.multiobjective.ibea

    """ selection = BinaryTournamentSelection( - comparator=SolutionAttributeComparator(key='fitness', lowest_is_best=False)) + comparator=SolutionAttributeComparator(key="fitness", lowest_is_best=False) + ) self.kappa = kappa super(IBEA, self).__init__( @@ -173,48 +174,69 @@

    Source code for jmetal.algorithm.multiobjective.ibea

    selection=selection, termination_criterion=termination_criterion, population_evaluator=population_evaluator, - population_generator=population_generator + population_generator=population_generator, ) -
    [docs] def compute_fitness_values(self, population: List[S], kappa: float) -> List[S]: +
    +[docs] + def compute_fitness_values(self, population: List[S], kappa: float) -> List[S]: for i in range(len(population)): - population[i].attributes['fitness'] = 0 + population[i].attributes["fitness"] = 0 for j in range(len(population)): if j != i: - population[i].attributes['fitness'] += -np.exp( - -EpsilonIndicator([population[i]]).compute([population[j]]) / self.kappa) + population[i].attributes["fitness"] += -np.exp( + -EpsilonIndicator([population[i].objectives]).compute([population[j].objectives]) / self.kappa + ) return population
    -
    [docs] def create_initial_solutions(self) -> List[S]: + +
    +[docs] + def create_initial_solutions(self) -> List[S]: population = [self.population_generator.new(self.problem) for _ in range(self.population_size)] population = self.compute_fitness_values(population, self.kappa) return population
    -
    [docs] def replacement(self, population: List[S], offspring_population: List[S]) -> List[List[S]]: + +
    +[docs] + def replacement(self, population: List[S], offspring_population: List[S]) -> List[List[S]]: join_population = population + offspring_population join_population_size = len(join_population) join_population = self.compute_fitness_values(join_population, self.kappa) while join_population_size > self.population_size: - current_fitnesses = [individual.attributes['fitness'] for individual in join_population] + current_fitnesses = [individual.attributes["fitness"] for individual in join_population] index_worst = current_fitnesses.index(min(current_fitnesses)) for i in range(join_population_size): - join_population[i].attributes['fitness'] += np.exp( - - EpsilonIndicator([join_population[i]]).compute([join_population[index_worst]]) / self.kappa) + join_population[i].attributes["fitness"] += np.exp( + -EpsilonIndicator([join_population[i].objectives]).compute( + [join_population[index_worst].objectives] + ) + / self.kappa + ) join_population.pop(index_worst) join_population_size = join_population_size - 1 return join_population
    -
    [docs] def get_result(self) -> R: + +
    +[docs] + def result(self) -> R: return self.solutions
    -
    [docs] def get_name(self) -> str: - return 'Epsilon-IBEA'
    + +
    +[docs] + def get_name(self) -> str: + return "Epsilon-IBEA"
    +
    +
    @@ -231,8 +253,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.algorithm.multiobjective.mocell — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -111,24 +108,24 @@

    Table Of Contents

    Source code for jmetal.algorithm.multiobjective.mocell

     import copy
     from functools import cmp_to_key
    -from typing import TypeVar, List
    +from typing import List, TypeVar
     
     from jmetal.algorithm.singleobjective.genetic_algorithm import GeneticAlgorithm
     from jmetal.config import store
    -from jmetal.core.operator import Mutation, Crossover, Selection
    +from jmetal.core.operator import Crossover, Mutation, Selection
     from jmetal.core.problem import Problem
    -from jmetal.operator import BinaryTournamentSelection
    +from jmetal.operator.selection import BinaryTournamentSelection
     from jmetal.util.archive import BoundedArchive
    +from jmetal.util.comparator import Comparator, MultiComparator
     from jmetal.util.density_estimator import CrowdingDistance, DensityEstimator
     from jmetal.util.evaluator import Evaluator
     from jmetal.util.generator import Generator
     from jmetal.util.neighborhood import Neighborhood
     from jmetal.util.ranking import FastNonDominatedRanking, Ranking
    -from jmetal.util.comparator import Comparator, MultiComparator
     from jmetal.util.termination_criterion import TerminationCriterion
     
    -S = TypeVar('S')
    -R = TypeVar('R')
    +S = TypeVar("S")
    +R = TypeVar("R")
     
     """
     .. module:: MOCell
    @@ -138,23 +135,26 @@ 

    Source code for jmetal.algorithm.multiobjective.mocell

    """ -
    [docs]class MOCell(GeneticAlgorithm[S, R]): - - def __init__(self, - problem: Problem, - population_size: int, - neighborhood: Neighborhood, - archive: BoundedArchive, - mutation: Mutation, - crossover: Crossover, - selection: Selection = BinaryTournamentSelection( - MultiComparator([FastNonDominatedRanking.get_comparator(), - CrowdingDistance.get_comparator()])), - termination_criterion: TerminationCriterion = store.default_termination_criteria, - population_generator: Generator = store.default_generator, - population_evaluator: Evaluator = store.default_evaluator, - dominance_comparator: Comparator = store.default_comparator): - """ +
    +[docs] +class MOCell(GeneticAlgorithm[S, R]): + def __init__( + self, + problem: Problem, + population_size: int, + neighborhood: Neighborhood, + archive: BoundedArchive, + mutation: Mutation, + crossover: Crossover, + selection: Selection = BinaryTournamentSelection( + MultiComparator([FastNonDominatedRanking.get_comparator(), CrowdingDistance.get_comparator()]) + ), + termination_criterion: TerminationCriterion = store.default_termination_criteria, + population_generator: Generator = store.default_generator, + population_evaluator: Evaluator = store.default_evaluator, + dominance_comparator: Comparator = store.default_comparator, + ): + """ MOCEll implementation as described in: :param problem: The problem to solve. @@ -172,7 +172,7 @@

    Source code for jmetal.algorithm.multiobjective.mocell

    selection=selection, termination_criterion=termination_criterion, population_evaluator=population_evaluator, - population_generator=population_generator + population_generator=population_generator, ) self.dominance_comparator = dominance_comparator self.neighborhood = neighborhood @@ -180,19 +180,26 @@

    Source code for jmetal.algorithm.multiobjective.mocell

    self.current_individual = 0 self.current_neighbors = [] - self.comparator = MultiComparator([FastNonDominatedRanking.get_comparator(), - CrowdingDistance.get_comparator()]) + self.comparator = MultiComparator([FastNonDominatedRanking.get_comparator(), CrowdingDistance.get_comparator()]) -
    [docs] def init_progress(self) -> None: +
    +[docs] + def init_progress(self) -> None: super().init_progress() for solution in self.solutions: self.archive.add(copy.copy(solution))
    -
    [docs] def update_progress(self) -> None: + +
    +[docs] + def update_progress(self) -> None: super().update_progress() self.current_individual = (self.current_individual + 1) % self.population_size
    -
    [docs] def selection(self, population: List[S]): + +
    +[docs] + def selection(self, population: List[S]): parents = [] self.current_neighbors = self.neighborhood.get_neighbors(self.current_individual, population) @@ -206,23 +213,29 @@

    Source code for jmetal.algorithm.multiobjective.mocell

    return parents
    -
    [docs] def reproduction(self, mating_population: List[S]) -> List[S]: + +
    +[docs] + def reproduction(self, mating_population: List[S]) -> List[S]: number_of_parents_to_combine = self.crossover_operator.get_number_of_parents() if len(mating_population) % number_of_parents_to_combine != 0: - raise Exception('Wrong number of parents') + raise Exception("Wrong number of parents") offspring_population = self.crossover_operator.execute(mating_population) - self.mutation_operator.execute(offspring_population[0]) + offspring_population[0] = self.mutation_operator.execute(offspring_population[0]) return [offspring_population[0]]
    -
    [docs] def replacement(self, population: List[S], offspring_population: List[S]) -> List[List[S]]: + +
    +[docs] + def replacement(self, population: List[S], offspring_population: List[S]) -> List[List[S]]: result = self.dominance_comparator.compare(population[self.current_individual], offspring_population[0]) if result == 1: # the offspring individual dominates the current one population[self.current_individual] = offspring_population[0] - self.archive.add(offspring_population[0]) + self.archive.add(copy.deepcopy(offspring_population[0])) elif result == 0: # the offspring and current individuals are non-dominated new_individual = offspring_population[0] @@ -238,17 +251,25 @@

    Source code for jmetal.algorithm.multiobjective.mocell

    self.current_neighbors.sort(key=cmp_to_key(self.comparator.compare)) worst_solution = self.current_neighbors[-1] - self.archive.add(new_individual) + self.archive.add(copy.deepcopy(new_individual)) if worst_solution != new_individual: population[self.current_individual] = new_individual return population
    -
    [docs] def get_result(self) -> R: + +
    +[docs] + def result(self) -> R: return self.archive.solution_list
    -
    [docs] def get_name(self) -> str: - return 'MOCell'
    + +
    +[docs] + def get_name(self) -> str: + return "MOCell"
    +
    +
    @@ -265,8 +286,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.algorithm.multiobjective.moead — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -112,7 +109,7 @@

    Source code for jmetal.algorithm.multiobjective.moead

    import copy import random from math import ceil -from typing import TypeVar, List, Generator +from typing import Generator, List, TypeVar import numpy as np @@ -120,36 +117,46 @@

    Source code for jmetal.algorithm.multiobjective.moead

    from jmetal.config import store from jmetal.core.operator import Mutation from jmetal.core.problem import Problem -from jmetal.operator import DifferentialEvolutionCrossover, NaryRandomSolutionSelection -from jmetal.util.aggregative_function import AggregativeFunction -from jmetal.util.constraint_handling import feasibility_ratio, \ - overall_constraint_violation_degree, is_feasible +from jmetal.operator.crossover import DifferentialEvolutionCrossover +from jmetal.operator.selection import NaryRandomSolutionSelection +from jmetal.util.aggregation_function import AggregationFunction +from jmetal.util.constraint_handling import ( + feasibility_ratio, + is_feasible, + overall_constraint_violation_degree, +) from jmetal.util.density_estimator import CrowdingDistance from jmetal.util.evaluator import Evaluator from jmetal.util.neighborhood import WeightVectorNeighborhood from jmetal.util.ranking import FastNonDominatedRanking -from jmetal.util.termination_criterion import TerminationCriterion, StoppingByEvaluations +from jmetal.util.termination_criterion import ( + StoppingByEvaluations, + TerminationCriterion, +) -S = TypeVar('S') +S = TypeVar("S") R = List[S] -
    [docs]class MOEAD(GeneticAlgorithm): - - def __init__(self, - problem: Problem, - population_size: int, - mutation: Mutation, - crossover: DifferentialEvolutionCrossover, - aggregative_function: AggregativeFunction, - neighbourhood_selection_probability: float, - max_number_of_replaced_solutions: int, - neighbor_size: int, - weight_files_path: str, - termination_criterion: TerminationCriterion = store.default_termination_criteria, - population_generator: Generator = store.default_generator, - population_evaluator: Evaluator = store.default_evaluator): - """ +
    +[docs] +class MOEAD(GeneticAlgorithm): + def __init__( + self, + problem: Problem, + population_size: int, + mutation: Mutation, + crossover: DifferentialEvolutionCrossover, + aggregation_function: AggregationFunction, + neighbourhood_selection_probability: float, + max_number_of_replaced_solutions: int, + neighbor_size: int, + weight_files_path: str, + termination_criterion: TerminationCriterion = store.default_termination_criteria, + population_generator: Generator = store.default_generator, + population_evaluator: Evaluator = store.default_evaluator, + ): + """ :param max_number_of_replaced_solutions: (eta in Zhang & Li paper). :param neighbourhood_selection_probability: Probability of mating with a solution in the neighborhood rather than the entire population (Delta in Zhang & Li paper). @@ -163,36 +170,41 @@

    Source code for jmetal.algorithm.multiobjective.moead

    selection=NaryRandomSolutionSelection(2), population_evaluator=population_evaluator, population_generator=population_generator, - termination_criterion=termination_criterion + termination_criterion=termination_criterion, ) self.max_number_of_replaced_solutions = max_number_of_replaced_solutions - self.fitness_function = aggregative_function + self.fitness_function = aggregation_function self.neighbourhood = WeightVectorNeighborhood( number_of_weight_vectors=population_size, neighborhood_size=neighbor_size, - weight_vector_size=problem.number_of_objectives, - weights_path=weight_files_path + weight_vector_size=problem.number_of_objectives(), + weights_path=weight_files_path, ) self.neighbourhood_selection_probability = neighbourhood_selection_probability self.permutation = None self.current_subproblem = 0 self.neighbor_type = None -
    [docs] def init_progress(self) -> None: +
    +[docs] + def init_progress(self) -> None: self.evaluations = self.population_size for solution in self.solutions: self.fitness_function.update(solution.objectives) self.permutation = Permutation(self.population_size) - observable_data = self.get_observable_data() + observable_data = self.observable_data() self.observable.notify_all(**observable_data)
    -
    [docs] def selection(self, population: List[S]): + +
    +[docs] + def selection(self, population: List[S]): self.current_subproblem = self.permutation.get_next_value() self.neighbor_type = self.choose_neighbor_type() - if self.neighbor_type == 'NEIGHBOR': + if self.neighbor_type == "NEIGHBOR": neighbors = self.neighbourhood.get_neighbors(self.current_subproblem, population) mating_population = self.selection_operator.execute(neighbors) else: @@ -202,7 +214,10 @@

    Source code for jmetal.algorithm.multiobjective.moead

    return mating_population
    -
    [docs] def reproduction(self, mating_population: List[S]) -> List[S]: + +
    +[docs] + def reproduction(self, mating_population: List[S]) -> List[S]: self.crossover_operator.current_individual = self.solutions[self.current_subproblem] offspring_population = self.crossover_operator.execute(mating_population) @@ -210,7 +225,10 @@

    Source code for jmetal.algorithm.multiobjective.moead

    return offspring_population
    -
    [docs] def replacement(self, population: List[S], offspring_population: List[S]) -> List[S]: + +
    +[docs] + def replacement(self, population: List[S], offspring_population: List[S]) -> List[S]: new_solution = offspring_population[0] self.fitness_function.update(new_solution.objectives) @@ -219,7 +237,10 @@

    Source code for jmetal.algorithm.multiobjective.moead

    return new_population
    -
    [docs] def update_current_subproblem_neighborhood(self, new_solution, population): + +
    +[docs] + def update_current_subproblem_neighborhood(self, new_solution, population): permuted_neighbors_indexes = self.generate_permutation_of_neighbors(self.current_subproblem) replacements = 0 @@ -238,8 +259,11 @@

    Source code for jmetal.algorithm.multiobjective.moead

    return population
    -
    [docs] def generate_permutation_of_neighbors(self, subproblem_id): - if self.neighbor_type == 'NEIGHBOR': + +
    +[docs] + def generate_permutation_of_neighbors(self, subproblem_id): + if self.neighbor_type == "NEIGHBOR": neighbors = self.neighbourhood.get_neighborhood()[subproblem_id] permuted_array = copy.deepcopy(neighbors.tolist()) else: @@ -247,34 +271,64 @@

    Source code for jmetal.algorithm.multiobjective.moead

    return permuted_array
    -
    [docs] def choose_neighbor_type(self): + +
    +[docs] + def choose_neighbor_type(self): rnd = random.random() if rnd < self.neighbourhood_selection_probability: - neighbor_type = 'NEIGHBOR' + neighbor_type = "NEIGHBOR" else: - neighbor_type = 'POPULATION' + neighbor_type = "POPULATION" return neighbor_type
    -
    [docs] def get_name(self): - return 'MOEAD'
    -
    [docs] def get_result(self): - return self.solutions
    +
    +[docs] + def get_name(self): + return "MOEAD"
    + + +
    +[docs] + def result(self): + return self.solutions
    +
    + class MOEAD_DRA(MOEAD): - def __init__(self, problem, population_size, mutation, crossover, aggregative_function, - neighbourhood_selection_probability, max_number_of_replaced_solutions, neighbor_size, - weight_files_path, termination_criterion=store.default_termination_criteria, - population_generator=store.default_generator, population_evaluator=store.default_evaluator): - super(MOEAD_DRA, self).__init__(problem, population_size, mutation, crossover, aggregative_function, - neighbourhood_selection_probability, max_number_of_replaced_solutions, - neighbor_size, weight_files_path, - termination_criterion=termination_criterion, - population_generator=population_generator, - population_evaluator=population_evaluator) + def __init__( + self, + problem, + population_size, + mutation, + crossover, + aggregation_function, + neighbourhood_selection_probability, + max_number_of_replaced_solutions, + neighbor_size, + weight_files_path, + termination_criterion=store.default_termination_criteria, + population_generator=store.default_generator, + population_evaluator=store.default_evaluator, + ): + super(MOEAD_DRA, self).__init__( + problem, + population_size, + mutation, + crossover, + aggregation_function, + neighbourhood_selection_probability, + max_number_of_replaced_solutions, + neighbor_size, + weight_files_path, + termination_criterion=termination_criterion, + population_generator=population_generator, + population_evaluator=population_evaluator, + ) self.saved_values = [] self.utility = [1.0 for _ in range(population_size)] @@ -294,7 +348,7 @@

    Source code for jmetal.algorithm.multiobjective.moead

    self.order = self.__tour_selection(10) self.current_order_index = 0 - observable_data = self.get_observable_data() + observable_data = self.observable_data() self.observable.notify_all(**observable_data) def update_progress(self): @@ -316,7 +370,7 @@

    Source code for jmetal.algorithm.multiobjective.moead

    self.neighbor_type = self.choose_neighbor_type() - if self.neighbor_type == 'NEIGHBOR': + if self.neighbor_type == "NEIGHBOR": neighbors = self.neighbourhood.get_neighbors(self.current_subproblem, population) mating_population = self.selection_operator.execute(neighbors) else: @@ -327,7 +381,7 @@

    Source code for jmetal.algorithm.multiobjective.moead

    return mating_population def get_name(self): - return 'MOEAD-DRA' + return "MOEAD-DRA" def __utility_function(self): for i in range(len(self.solutions)): @@ -343,8 +397,8 @@

    Source code for jmetal.algorithm.multiobjective.moead

    self.saved_values[i] = copy.copy(self.solutions[i]) def __tour_selection(self, depth): - selected = [i for i in range(self.problem.number_of_objectives)] - candidate = [i for i in range(self.problem.number_of_objectives, self.population_size)] + selected = [i for i in range(self.problem.number_of_objectives())] + candidate = [i for i in range(self.problem.number_of_objectives(), self.population_size)] while len(selected) < int(self.population_size / 5.0): best_idd = int(random.random() * len(candidate)) @@ -362,20 +416,22 @@

    Source code for jmetal.algorithm.multiobjective.moead

    class MOEADIEpsilon(MOEAD): - def __init__(self, - problem: Problem, - population_size: int, - mutation: Mutation, - crossover: DifferentialEvolutionCrossover, - aggregative_function: AggregativeFunction, - neighbourhood_selection_probability: float, - max_number_of_replaced_solutions: int, - neighbor_size: int, - weight_files_path: str, - termination_criterion: TerminationCriterion = StoppingByEvaluations(300000), - population_generator: Generator = store.default_generator, - population_evaluator: Evaluator = store.default_evaluator): - """ + def __init__( + self, + problem: Problem, + population_size: int, + mutation: Mutation, + crossover: DifferentialEvolutionCrossover, + aggregation_function: AggregationFunction, + neighbourhood_selection_probability: float, + max_number_of_replaced_solutions: int, + neighbor_size: int, + weight_files_path: str, + termination_criterion: TerminationCriterion = StoppingByEvaluations(300000), + population_generator: Generator = store.default_generator, + population_evaluator: Evaluator = store.default_evaluator, + ): + """ :param max_number_of_replaced_solutions: (eta in Zhang & Li paper). :param neighbourhood_selection_probability: Probability of mating with a solution in the neighborhood rather than the entire population (Delta in Zhang & Li paper). @@ -385,14 +441,14 @@

    Source code for jmetal.algorithm.multiobjective.moead

    population_size=population_size, mutation=mutation, crossover=crossover, - aggregative_function=aggregative_function, + aggregation_function=aggregation_function, neighbourhood_selection_probability=neighbourhood_selection_probability, max_number_of_replaced_solutions=max_number_of_replaced_solutions, neighbor_size=neighbor_size, weight_files_path=weight_files_path, population_evaluator=population_evaluator, population_generator=population_generator, - termination_criterion=termination_criterion + termination_criterion=termination_criterion, ) self.constraints = [] self.epsilon_k = 0 @@ -409,8 +465,9 @@

    Source code for jmetal.algorithm.multiobjective.moead

    # for i in range(self.population_size): # self.constraints[i] = get_overall_constraint_violation_degree(self.permutation[i]) - self.constraints = [overall_constraint_violation_degree(self.solutions[i]) - for i in range(0, self.population_size)] + self.constraints = [ + overall_constraint_violation_degree(self.solutions[i]) for i in range(0, self.population_size) + ] sorted(self.constraints) self.epsilon_zero = abs(self.constraints[int(ceil(0.05 * self.population_size))]) @@ -489,20 +546,20 @@

    Source code for jmetal.algorithm.multiobjective.moead

    crowding_distance = CrowdingDistance() while len(first_rank_solutions) > self.population_size: crowding_distance.compute_density_estimator(first_rank_solutions) - first_rank_solutions = sorted(first_rank_solutions, key=lambda x: x.attributes['crowding_distance'], - reverse=True) + first_rank_solutions = sorted( + first_rank_solutions, key=lambda x: x.attributes["crowding_distance"], reverse=True + ) first_rank_solutions.pop() self.archive = [] for solution in first_rank_solutions: self.archive.append(copy.deepcopy(solution)) - def get_result(self): + def result(self): return self.archive class Permutation: - def __init__(self, length: int): self.counter = 0 self.length = length @@ -536,8 +593,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.algorithm.multiobjective.nsgaii — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -110,29 +107,32 @@

    Table Of Contents

    Source code for jmetal.algorithm.multiobjective.nsgaii

     import time
    -from typing import TypeVar, List, Generator
    +from typing import Generator, List, TypeVar
     
     try:
         import dask
    -    from distributed import as_completed, Client
    +    from distributed import Client, as_completed
     except ImportError:
         pass
     
     from jmetal.algorithm.singleobjective.genetic_algorithm import GeneticAlgorithm
     from jmetal.config import store
    -from jmetal.core.algorithm import DynamicAlgorithm, Algorithm
    -from jmetal.core.operator import Mutation, Crossover, Selection
    -from jmetal.core.problem import Problem, DynamicProblem
    -from jmetal.operator import BinaryTournamentSelection
    +from jmetal.core.algorithm import Algorithm, DynamicAlgorithm
    +from jmetal.core.operator import Crossover, Mutation, Selection
    +from jmetal.core.problem import DynamicProblem, Problem
    +from jmetal.operator.selection import BinaryTournamentSelection
    +from jmetal.util.comparator import Comparator, DominanceComparator, MultiComparator
     from jmetal.util.density_estimator import CrowdingDistance
     from jmetal.util.evaluator import Evaluator
     from jmetal.util.ranking import FastNonDominatedRanking
    -from jmetal.util.replacement import RankingAndDensityEstimatorReplacement, RemovalPolicyType
    -from jmetal.util.comparator import DominanceComparator, Comparator, MultiComparator
    +from jmetal.util.replacement import (
    +    RankingAndDensityEstimatorReplacement,
    +    RemovalPolicyType,
    +)
     from jmetal.util.termination_criterion import TerminationCriterion
     
    -S = TypeVar('S')
    -R = TypeVar('R')
    +S = TypeVar("S")
    +R = TypeVar("R")
     
     """
     .. module:: NSGA-II
    @@ -143,22 +143,25 @@ 

    Source code for jmetal.algorithm.multiobjective.nsgaii

    """ -
    [docs]class NSGAII(GeneticAlgorithm[S, R]): - - def __init__(self, - problem: Problem, - population_size: int, - offspring_population_size: int, - mutation: Mutation, - crossover: Crossover, - selection: Selection = BinaryTournamentSelection( - MultiComparator([FastNonDominatedRanking.get_comparator(), - CrowdingDistance.get_comparator()])), - termination_criterion: TerminationCriterion = store.default_termination_criteria, - population_generator: Generator = store.default_generator, - population_evaluator: Evaluator = store.default_evaluator, - dominance_comparator: Comparator = store.default_comparator): - """ +
    +[docs] +class NSGAII(GeneticAlgorithm[S, R]): + def __init__( + self, + problem: Problem, + population_size: int, + offspring_population_size: int, + mutation: Mutation, + crossover: Crossover, + selection: Selection = BinaryTournamentSelection( + MultiComparator([FastNonDominatedRanking.get_comparator(), CrowdingDistance.get_comparator()]) + ), + termination_criterion: TerminationCriterion = store.default_termination_criteria, + population_generator: Generator = store.default_generator, + population_evaluator: Evaluator = store.default_evaluator, + dominance_comparator: Comparator = store.default_comparator, + ): + """ NSGA-II implementation as described in * K. Deb, A. Pratap, S. Agarwal and T. Meyarivan, "A fast and elitist @@ -175,7 +178,6 @@

    Source code for jmetal.algorithm.multiobjective.nsgaii

    :param population_size: Size of the population. :param mutation: Mutation operator (see :py:mod:`jmetal.operator.mutation`). :param crossover: Crossover operator (see :py:mod:`jmetal.operator.crossover`). - :param selection: Selection operator (see :py:mod:`jmetal.operator.selection`). """ super(NSGAII, self).__init__( problem=problem, @@ -186,12 +188,14 @@

    Source code for jmetal.algorithm.multiobjective.nsgaii

    selection=selection, termination_criterion=termination_criterion, population_evaluator=population_evaluator, - population_generator=population_generator + population_generator=population_generator, ) self.dominance_comparator = dominance_comparator -
    [docs] def replacement(self, population: List[S], offspring_population: List[S]) -> List[List[S]]: - """ This method joins the current and offspring populations to produce the population of the next generation +
    +[docs] + def replacement(self, population: List[S], offspring_population: List[S]) -> List[List[S]]: + """This method joins the current and offspring populations to produce the population of the next generation by applying the ranking and crowding distance selection. :param population: Parent population. @@ -206,28 +210,39 @@

    Source code for jmetal.algorithm.multiobjective.nsgaii

    return solutions
    -
    [docs] def get_result(self) -> R: + +
    +[docs] + def result(self) -> R: return self.solutions
    -
    [docs] def get_name(self) -> str: - return 'NSGAII'
    - - -
    [docs]class DynamicNSGAII(NSGAII[S, R], DynamicAlgorithm): - - def __init__(self, - problem: DynamicProblem[S], - population_size: int, - offspring_population_size: int, - mutation: Mutation, - crossover: Crossover, - selection: Selection = BinaryTournamentSelection( - MultiComparator([FastNonDominatedRanking.get_comparator(), - CrowdingDistance.get_comparator()])), - termination_criterion: TerminationCriterion = store.default_termination_criteria, - population_generator: Generator = store.default_generator, - population_evaluator: Evaluator = store.default_evaluator, - dominance_comparator: DominanceComparator = DominanceComparator()): + +
    +[docs] + def get_name(self) -> str: + return "NSGAII"
    +
    + + + +
    +[docs] +class DynamicNSGAII(NSGAII[S, R], DynamicAlgorithm): + def __init__( + self, + problem: DynamicProblem[S], + population_size: int, + offspring_population_size: int, + mutation: Mutation, + crossover: Crossover, + selection: Selection = BinaryTournamentSelection( + MultiComparator([FastNonDominatedRanking.get_comparator(), CrowdingDistance.get_comparator()]) + ), + termination_criterion: TerminationCriterion = store.default_termination_criteria, + population_generator: Generator = store.default_generator, + population_evaluator: Evaluator = store.default_evaluator, + dominance_comparator: DominanceComparator = DominanceComparator(), + ): super(DynamicNSGAII, self).__init__( problem=problem, population_size=population_size, @@ -238,50 +253,64 @@

    Source code for jmetal.algorithm.multiobjective.nsgaii

    population_evaluator=population_evaluator, population_generator=population_generator, termination_criterion=termination_criterion, - dominance_comparator=dominance_comparator) + dominance_comparator=dominance_comparator, + ) self.completed_iterations = 0 self.start_computing_time = 0 self.total_computing_time = 0 -
    [docs] def restart(self): +
    +[docs] + def restart(self): self.solutions = self.evaluate(self.solutions)
    -
    [docs] def update_progress(self): + +
    +[docs] + def update_progress(self): if self.problem.the_problem_has_changed(): self.restart() self.problem.clear_changed() - observable_data = self.get_observable_data() + observable_data = self.observable_data() self.observable.notify_all(**observable_data) self.evaluations += self.offspring_population_size
    -
    [docs] def stopping_condition_is_met(self): + +
    +[docs] + def stopping_condition_is_met(self): if self.termination_criterion.is_met: - observable_data = self.get_observable_data() - observable_data['TERMINATION_CRITERIA_IS_MET'] = True + observable_data = self.observable_data() + observable_data["TERMINATION_CRITERIA_IS_MET"] = True self.observable.notify_all(**observable_data) self.restart() self.init_progress() - self.completed_iterations += 1
    + self.completed_iterations += 1
    +
    -
    [docs]class DistributedNSGAII(Algorithm[S, R]): - def __init__(self, - problem: Problem, - population_size: int, - mutation: Mutation, - crossover: Crossover, - number_of_cores: int, - client, - selection: Selection = BinaryTournamentSelection( - MultiComparator([FastNonDominatedRanking.get_comparator(), - CrowdingDistance.get_comparator()])), - termination_criterion: TerminationCriterion = store.default_termination_criteria, - dominance_comparator: DominanceComparator = DominanceComparator()): +
    +[docs] +class DistributedNSGAII(Algorithm[S, R]): + def __init__( + self, + problem: Problem, + population_size: int, + mutation: Mutation, + crossover: Crossover, + number_of_cores: int, + client, + selection: Selection = BinaryTournamentSelection( + MultiComparator([FastNonDominatedRanking.get_comparator(), CrowdingDistance.get_comparator()]) + ), + termination_criterion: TerminationCriterion = store.default_termination_criteria, + dominance_comparator: DominanceComparator = DominanceComparator(), + ): super(DistributedNSGAII, self).__init__() self.problem = problem self.population_size = population_size @@ -296,38 +325,63 @@

    Source code for jmetal.algorithm.multiobjective.nsgaii

    self.number_of_cores = number_of_cores self.client = client -
    [docs] def create_initial_solutions(self) -> List[S]: +
    +[docs] + def create_initial_solutions(self) -> List[S]: return [self.problem.create_solution() for _ in range(self.number_of_cores)]
    -
    [docs] def evaluate(self, solutions: List[S]) -> List[S]: + +
    +[docs] + def evaluate(self, solutions: List[S]) -> List[S]: return self.client.map(self.problem.evaluate, solutions)
    -
    [docs] def stopping_condition_is_met(self) -> bool: + +
    +[docs] + def stopping_condition_is_met(self) -> bool: return self.termination_criterion.is_met
    -
    [docs] def get_observable_data(self) -> dict: + +
    +[docs] + def observable_data(self) -> dict: ctime = time.time() - self.start_computing_time - return {'PROBLEM': self.problem, - 'EVALUATIONS': self.evaluations, - 'SOLUTIONS': self.get_result(), - 'COMPUTING_TIME': ctime}
    + return { + "PROBLEM": self.problem, + "EVALUATIONS": self.evaluations, + "SOLUTIONS": self.result(), + "COMPUTING_TIME": ctime, + }
    -
    [docs] def init_progress(self) -> None: + +
    +[docs] + def init_progress(self) -> None: self.evaluations = self.number_of_cores - observable_data = self.get_observable_data() + observable_data = self.observable_data() self.observable.notify_all(**observable_data)
    -
    [docs] def step(self) -> None: + +
    +[docs] + def step(self) -> None: pass
    -
    [docs] def update_progress(self): - observable_data = self.get_observable_data() + +
    +[docs] + def update_progress(self): + observable_data = self.observable_data() self.observable.notify_all(**observable_data)
    -
    [docs] def run(self): - """ Execute the algorithm. """ + +
    +[docs] + def run(self): + """Execute the algorithm.""" self.start_computing_time = time.time() create_solution = dask.delayed(self.problem.create_solution) @@ -384,8 +438,9 @@

    Source code for jmetal.algorithm.multiobjective.nsgaii

    mating_population.append(solution) # Reproduction and evaluation - new_task = self.client.submit(reproduction, mating_population, self.problem, - self.crossover_operator, self.mutation_operator) + new_task = self.client.submit( + reproduction, mating_population, self.problem, self.crossover_operator, self.mutation_operator + ) task_pool.add(new_task) # update progress @@ -403,11 +458,19 @@

    Source code for jmetal.algorithm.multiobjective.nsgaii

    for future, _ in task_pool: future.cancel()
    -
    [docs] def get_result(self) -> R: + +
    +[docs] + def result(self) -> R: return self.solutions
    -
    [docs] def get_name(self) -> str: - return 'dNSGA-II'
    + +
    +[docs] + def get_name(self) -> str: + return "dNSGA-II"
    +
    + def reproduction(mating_population: List[S], problem, crossover_operator, mutation_operator) -> S: @@ -438,8 +501,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - - + jmetal.algorithm.multiobjective.nsgaiii — jMetalPy 1.7.0 documentation + + + + + + + @@ -40,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -52,7 +48,7 @@

    Navigation

    @@ -74,8 +70,6 @@

    Table Of Contents

  • Single-objective algorithms
  • Operators
  • Problems
  • -
  • Contributing
  • -
  • About
  • @@ -112,27 +106,27 @@

    Table Of Contents

    Source code for jmetal.algorithm.multiobjective.nsgaiii

    -from abc import abstractmethod, ABC
    -from typing import TypeVar, List
    +from abc import ABC, abstractmethod
    +from typing import List, TypeVar
     
     import numpy as np
    -from numpy.linalg import LinAlgError
    -from scipy import special
    -
    -from jmetal.algorithm.multiobjective.nsgaii import NSGAII
    -from jmetal.config import store
    -from jmetal.core.operator import Mutation, Crossover, Selection
    -from jmetal.core.problem import Problem
    -from jmetal.operator import BinaryTournamentSelection
    -from jmetal.util.comparator import Comparator, MultiComparator
    -from jmetal.util.density_estimator import CrowdingDistance
    -from jmetal.util.evaluator import Evaluator
    -from jmetal.util.generator import Generator
    -from jmetal.util.ranking import FastNonDominatedRanking
    -from jmetal.util.termination_criterion import TerminationCriterion
    -
    -S = TypeVar('S')
    -R = TypeVar('R')
    +from numpy.linalg import LinAlgError
    +from scipy import special
    +
    +from jmetal.algorithm.multiobjective.nsgaii import NSGAII
    +from jmetal.config import store
    +from jmetal.core.operator import Crossover, Mutation, Selection
    +from jmetal.core.problem import Problem
    +from jmetal.operator.selection import BinaryTournamentSelection
    +from jmetal.util.comparator import Comparator, MultiComparator
    +from jmetal.util.density_estimator import CrowdingDistance
    +from jmetal.util.evaluator import Evaluator
    +from jmetal.util.generator import Generator
    +from jmetal.util.ranking import FastNonDominatedRanking
    +from jmetal.util.termination_criterion import TerminationCriterion
    +
    +S = TypeVar("S")
    +R = TypeVar("R")
     
     """
     .. module:: NSGA-III
    @@ -144,8 +138,7 @@ 

    Source code for jmetal.algorithm.multiobjective.nsgaiii

    class ReferenceDirectionFactory(ABC): - - def __init__(self, n_dim: int, scaling=None) -> None: + def __init__(self, n_dim: int, scaling=None) -> None: self.n_dim = n_dim self.scaling = scaling @@ -164,8 +157,7 @@

    Source code for jmetal.algorithm.multiobjective.nsgaiii

    class UniformReferenceDirectionFactory(ReferenceDirectionFactory): - - def __init__(self, n_dim: int, scaling=None, n_points: int = None, n_partitions: int = None) -> None: + def __init__(self, n_dim: int, scaling=None, n_points: int = None, n_partitions: int = None) -> None: super().__init__(n_dim, scaling) if n_points is not None: self.n_partitions = self.get_partition_closest_to_points(n_points, n_dim) @@ -191,8 +183,7 @@

    Source code for jmetal.algorithm.multiobjective.nsgaiii

    else: for i in range(beta + 1): ref_dir[depth] = 1.0 * i / (1.0 * n_partitions) - self.__uniform_reference_directions(ref_dirs, np.copy(ref_dir), n_partitions, beta - i, - depth + 1) + self.__uniform_reference_directions(ref_dirs, np.copy(ref_dir), n_partitions, beta - i, depth + 1) @staticmethod def get_partition_closest_to_points(n_points, n_dim): @@ -214,7 +205,7 @@

    Source code for jmetal.algorithm.multiobjective.nsgaiii

    def get_extreme_points(F, n_objs, ideal_point, extreme_points=None): - """ Calculate the Achievement Scalarization Function which is used for the extreme point decomposition. """ + """Calculate the Achievement Scalarization Function which is used for the extreme point decomposition.""" asf = np.eye(n_objs) asf[asf == 0] = 1e6 @@ -236,7 +227,7 @@

    Source code for jmetal.algorithm.multiobjective.nsgaiii

    def get_nadir_point(extreme_points, ideal_point, worst_point, worst_of_front, worst_of_population): - """ Calculate the axis intersects for a set of individuals and its extremes (construct hyperplane). """ + """Calculate the axis intersects for a set of individuals and its extremes (construct hyperplane).""" try: # find the intercepts using gaussian elimination M = extreme_points - ideal_point @@ -295,7 +286,7 @@

    Source code for jmetal.algorithm.multiobjective.nsgaiii

    # add the selected individual to the survivors mask[next_ind] = False - pop[next_ind].attributes['is_closest'] = is_closest + pop[next_ind].attributes["is_closest"] = is_closest survivors.append(int(next_ind)) # increase the corresponding niche count @@ -305,7 +296,7 @@

    Source code for jmetal.algorithm.multiobjective.nsgaiii

    def associate_to_niches(F, niches, ideal_point, nadir_point, utopian_epsilon: float = 0.0): - """ Associate each solution to a reference point. """ + """Associate each solution to a reference point.""" utopian_point = ideal_point - utopian_epsilon denom = nadir_point - utopian_point @@ -343,27 +334,30 @@

    Source code for jmetal.algorithm.multiobjective.nsgaiii

    return niche_count -
    [docs]class NSGAIII(NSGAII): - - def __init__(self, - reference_directions, - problem: Problem, - mutation: Mutation, - crossover: Crossover, - population_size: int = None, - selection: Selection = BinaryTournamentSelection( - MultiComparator([FastNonDominatedRanking.get_comparator(), - CrowdingDistance.get_comparator()])), - termination_criterion: TerminationCriterion = store.default_termination_criteria, - population_generator: Generator = store.default_generator, - population_evaluator: Evaluator = store.default_evaluator, - dominance_comparator: Comparator = store.default_comparator): +
    +[docs] +class NSGAIII(NSGAII): + def __init__( + self, + reference_directions, + problem: Problem, + mutation: Mutation, + crossover: Crossover, + population_size: int = None, + selection: Selection = BinaryTournamentSelection( + MultiComparator([FastNonDominatedRanking.get_comparator(), CrowdingDistance.get_comparator()]) + ), + termination_criterion: TerminationCriterion = store.default_termination_criteria, + population_generator: Generator = store.default_generator, + population_evaluator: Evaluator = store.default_evaluator, + dominance_comparator: Comparator = store.default_comparator, + ): self.reference_directions = reference_directions.compute() if not population_size: population_size = len(self.reference_directions) - if self.reference_directions.shape[1] != problem.number_of_objectives: - raise Exception('Dimensionality of reference points must be equal to the number of objectives') + if self.reference_directions.shape[1] != problem.number_of_objectives(): + raise Exception("Dimensionality of reference points must be equal to the number of objectives") super(NSGAIII, self).__init__( problem=problem, @@ -375,15 +369,17 @@

    Source code for jmetal.algorithm.multiobjective.nsgaiii

    termination_criterion=termination_criterion, population_evaluator=population_evaluator, population_generator=population_generator, - dominance_comparator=dominance_comparator + dominance_comparator=dominance_comparator, ) self.extreme_points = None - self.ideal_point = np.full(self.problem.number_of_objectives, np.inf) - self.worst_point = np.full(self.problem.number_of_objectives, -np.inf) + self.ideal_point = np.full(self.problem.number_of_objectives(), np.inf) + self.worst_point = np.full(self.problem.number_of_objectives(), -np.inf) -
    [docs] def replacement(self, population: List[S], offspring_population: List[S]) -> List[S]: - """ Implements NSGA-III environmental selection based on reference points as described in: +
    +[docs] + def replacement(self, population: List[S], offspring_population: List[S]) -> List[S]: + """Implements NSGA-III environmental selection based on reference points as described in: * Deb, K., & Jain, H. (2014). An Evolutionary Many-Objective Optimization Algorithm Using Reference-Point-Based Nondominated Sorting Approach, @@ -404,20 +400,24 @@

    Source code for jmetal.algorithm.multiobjective.nsgaiii

    fronts, non_dominated = ranking.ranked_sublists, ranking.get_subfront(0) # find the extreme points for normalization - self.extreme_points = get_extreme_points(F=np.array([s.objectives for s in non_dominated]), - n_objs=self.problem.number_of_objectives, - ideal_point=self.ideal_point, - extreme_points=self.extreme_points) + self.extreme_points = get_extreme_points( + F=np.array([s.objectives for s in non_dominated]), + n_objs=self.problem.number_of_objectives(), + ideal_point=self.ideal_point, + extreme_points=self.extreme_points, + ) # find the intercepts for normalization and do backup if gaussian elimination fails worst_of_population = np.max(F, axis=0) worst_of_front = np.max(np.array([s.objectives for s in non_dominated]), axis=0) - nadir_point = get_nadir_point(extreme_points=self.extreme_points, - ideal_point=self.ideal_point, - worst_point=self.worst_point, - worst_of_population=worst_of_population, - worst_of_front=worst_of_front) + nadir_point = get_nadir_point( + extreme_points=self.extreme_points, + ideal_point=self.ideal_point, + worst_point=self.worst_point, + worst_of_population=worst_of_population, + worst_of_front=worst_of_front, + ) # consider only the population until we come to the splitting front pop = np.concatenate(ranking.ranked_sublists) @@ -432,10 +432,9 @@

    Source code for jmetal.algorithm.multiobjective.nsgaiii

    last_front = np.array(fronts[-1]) # associate individuals to niches - niche_of_individuals, dist_to_niche = associate_to_niches(F=F, - niches=self.reference_directions, - ideal_point=self.ideal_point, - nadir_point=nadir_point) + niche_of_individuals, dist_to_niche = associate_to_niches( + F=F, niches=self.reference_directions, ideal_point=self.ideal_point, nadir_point=nadir_point + ) # if we need to select individuals to survive if len(pop) > self.population_size: @@ -447,30 +446,41 @@

    Source code for jmetal.algorithm.multiobjective.nsgaiii

    # if some individuals already survived else: until_last_front = np.concatenate(fronts[:-1]) - niche_count = compute_niche_count(len(self.reference_directions), - niche_of_individuals[until_last_front]) + niche_count = compute_niche_count( + len(self.reference_directions), niche_of_individuals[until_last_front] + ) n_remaining = self.population_size - len(until_last_front) - S_idx = niching(pop=pop[last_front], - n_remaining=n_remaining, - niche_count=niche_count, - niche_of_individuals=niche_of_individuals[last_front], - dist_to_niche=dist_to_niche[last_front]) + S_idx = niching( + pop=pop[last_front], + n_remaining=n_remaining, + niche_count=niche_count, + niche_of_individuals=niche_of_individuals[last_front], + dist_to_niche=dist_to_niche[last_front], + ) survivors_idx = np.concatenate((until_last_front, last_front[S_idx].tolist())) pop = pop[survivors_idx] return list(pop)
    -
    [docs] def get_result(self): - """ Return only non dominated solutions.""" + +
    +[docs] + def result(self): + """Return only non dominated solutions.""" ranking = FastNonDominatedRanking(self.dominance_comparator) ranking.compute_ranking(self.solutions, k=self.population_size) return ranking.get_subfront(0)
    -
    [docs] def get_name(self) -> str: - return 'NSGAIII'
    + +
    +[docs] + def get_name(self) -> str: + return "NSGAIII"
    +
    +
    @@ -487,8 +497,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.algorithm.multiobjective.omopso — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -111,8 +108,7 @@

    Table Of Contents

    Source code for jmetal.algorithm.multiobjective.omopso

     import random
     from copy import copy
    -from math import sqrt
    -from typing import TypeVar, List, Optional
    +from typing import List, Optional, TypeVar
     
     import numpy
     
    @@ -120,7 +116,7 @@ 

    Source code for jmetal.algorithm.multiobjective.omopso

    from jmetal.core.algorithm import ParticleSwarmOptimization from jmetal.core.problem import FloatProblem from jmetal.core.solution import FloatSolution -from jmetal.operator import UniformMutation +from jmetal.operator.mutation import UniformMutation from jmetal.operator.mutation import NonUniformMutation from jmetal.util.archive import BoundedArchive, NonDominatedSolutionsArchive from jmetal.util.comparator import DominanceComparator, EpsilonDominanceComparator @@ -128,7 +124,7 @@

    Source code for jmetal.algorithm.multiobjective.omopso

    from jmetal.util.generator import Generator from jmetal.util.termination_criterion import TerminationCriterion -R = TypeVar('R') +R = TypeVar("R") """ .. module:: OMOPSO @@ -139,19 +135,22 @@

    Source code for jmetal.algorithm.multiobjective.omopso

    """ -
    [docs]class OMOPSO(ParticleSwarmOptimization): - - def __init__(self, - problem: FloatProblem, - swarm_size: int, - uniform_mutation: UniformMutation, - non_uniform_mutation: NonUniformMutation, - leaders: Optional[BoundedArchive], - epsilon: float, - termination_criterion: TerminationCriterion, - swarm_generator: Generator = store.default_generator, - swarm_evaluator: Evaluator = store.default_evaluator): - """ This class implements the OMOPSO algorithm as described in +
    +[docs] +class OMOPSO(ParticleSwarmOptimization): + def __init__( + self, + problem: FloatProblem, + swarm_size: int, + uniform_mutation: UniformMutation, + non_uniform_mutation: NonUniformMutation, + leaders: Optional[BoundedArchive], + epsilon: float, + termination_criterion: TerminationCriterion, + swarm_generator: Generator = store.default_generator, + swarm_evaluator: Evaluator = store.default_evaluator, + ): + """This class implements the OMOPSO algorithm as described in todo Update this reference * SMPSO: A new PSO-based metaheuristic for multi-objective optimization @@ -163,9 +162,7 @@

    Source code for jmetal.algorithm.multiobjective.omopso

    :param swarm_size: Size of the swarm. :param leaders: Archive for leaders. """ - super(OMOPSO, self).__init__( - problem=problem, - swarm_size=swarm_size) + super(OMOPSO, self).__init__(problem=problem, swarm_size=swarm_size) self.swarm_generator = swarm_generator self.swarm_evaluator = swarm_evaluator @@ -195,34 +192,54 @@

    Source code for jmetal.algorithm.multiobjective.omopso

    self.dominance_comparator = DominanceComparator() - self.speed = numpy.zeros((self.swarm_size, self.problem.number_of_variables), dtype=float) + self.speed = numpy.zeros((self.swarm_size, self.problem.number_of_variables()), dtype=float) -
    [docs] def create_initial_solutions(self) -> List[FloatSolution]: +
    +[docs] + def create_initial_solutions(self) -> List[FloatSolution]: return [self.swarm_generator.new(self.problem) for _ in range(self.swarm_size)]
    -
    [docs] def evaluate(self, solution_list: List[FloatSolution]): + +
    +[docs] + def evaluate(self, solution_list: List[FloatSolution]): return self.swarm_evaluator.evaluate(solution_list, self.problem)
    -
    [docs] def stopping_condition_is_met(self) -> bool: + +
    +[docs] + def stopping_condition_is_met(self) -> bool: return self.termination_criterion.is_met
    -
    [docs] def initialize_global_best(self, swarm: List[FloatSolution]) -> None: + +
    +[docs] + def initialize_global_best(self, swarm: List[FloatSolution]) -> None: for particle in swarm: if self.leaders.add(particle): self.epsilon_archive.add(copy(particle))
    -
    [docs] def initialize_particle_best(self, swarm: List[FloatSolution]) -> None: + +
    +[docs] + def initialize_particle_best(self, swarm: List[FloatSolution]) -> None: for particle in swarm: - particle.attributes['local_best'] = copy(particle)
    + particle.attributes["local_best"] = copy(particle)
    -
    [docs] def initialize_velocity(self, swarm: List[FloatSolution]) -> None: + +
    +[docs] + def initialize_velocity(self, swarm: List[FloatSolution]) -> None: for i in range(self.swarm_size): - for j in range(self.problem.number_of_variables): + for j in range(self.problem.number_of_variables()): self.speed[i][j] = 0.0
    -
    [docs] def update_velocity(self, swarm: List[FloatSolution]) -> None: + +
    +[docs] + def update_velocity(self, swarm: List[FloatSolution]) -> None: for i in range(self.swarm_size): - best_particle = copy(swarm[i].attributes['local_best']) + best_particle = copy(swarm[i].attributes["local_best"]) best_global = self.select_global_best() r1 = round(random.uniform(self.r1_min, self.r1_max), 1) @@ -232,11 +249,16 @@

    Source code for jmetal.algorithm.multiobjective.omopso

    w = round(random.uniform(self.weight_min, self.weight_max), 1) for var in range(swarm[i].number_of_variables): - self.speed[i][var] = w * self.speed[i][var] \ - + (c1 * r1 * (best_particle.variables[var] - swarm[i].variables[var])) \ - + (c2 * r2 * (best_global.variables[var] - swarm[i].variables[var]))
    + self.speed[i][var] = ( + w * self.speed[i][var] + + (c1 * r1 * (best_particle.variables[var] - swarm[i].variables[var])) + + (c2 * r2 * (best_global.variables[var] - swarm[i].variables[var])) + )
    + -
    [docs] def update_position(self, swarm: List[FloatSolution]) -> None: +
    +[docs] + def update_position(self, swarm: List[FloatSolution]) -> None: for i in range(self.swarm_size): particle = swarm[i] @@ -251,20 +273,27 @@

    Source code for jmetal.algorithm.multiobjective.omopso

    particle.variables[j] = self.problem.upper_bound[j] self.speed[i][j] *= self.change_velocity2
    -
    [docs] def update_global_best(self, swarm: List[FloatSolution]) -> None: + +
    +[docs] + def update_global_best(self, swarm: List[FloatSolution]) -> None: for particle in swarm: if self.leaders.add(copy(particle)): self.epsilon_archive.add(copy(particle))
    -
    [docs] def update_particle_best(self, swarm: List[FloatSolution]) -> None: + +
    +[docs] + def update_particle_best(self, swarm: List[FloatSolution]) -> None: for i in range(self.swarm_size): - flag = self.dominance_comparator.compare( - swarm[i], - swarm[i].attributes['local_best']) + flag = self.dominance_comparator.compare(swarm[i], swarm[i].attributes["local_best"]) if flag != 1: - swarm[i].attributes['local_best'] = copy(swarm[i])
    + swarm[i].attributes["local_best"] = copy(swarm[i])
    + -
    [docs] def perturbation(self, swarm: List[FloatSolution]) -> None: +
    +[docs] + def perturbation(self, swarm: List[FloatSolution]) -> None: self.non_uniform_mutation.set_current_iteration(self.evaluations / self.swarm_size) for i in range(self.swarm_size): if (i % 3) == 0: @@ -272,7 +301,10 @@

    Source code for jmetal.algorithm.multiobjective.omopso

    else: self.uniform_mutation.execute(swarm[i])
    -
    [docs] def select_global_best(self) -> FloatSolution: + +
    +[docs] + def select_global_best(self) -> FloatSolution: leaders = self.leaders.solution_list if len(leaders) > 2: @@ -287,28 +319,10 @@

    Source code for jmetal.algorithm.multiobjective.omopso

    return best_global
    - def __velocity_constriction(self, value: float, delta_max: [], delta_min: [], variable_index: int) -> float: - result = value - if value > delta_max[variable_index]: - result = delta_max[variable_index] - if value < delta_min[variable_index]: - result = delta_min[variable_index] - - return result - - def __inertia_weight(self, wmax: float): - return wmax - - def __constriction_coefficient(self, c1: float, c2: float) -> float: - rho = c1 + c2 - if rho <= 4: - result = 1.0 - else: - result = 2.0 / (2.0 - rho - sqrt(pow(rho, 2.0) - 4.0 * rho)) - - return result -
    [docs] def init_progress(self) -> None: +
    +[docs] + def init_progress(self) -> None: self.evaluations = self.swarm_size self.leaders.compute_density_estimator() @@ -316,19 +330,30 @@

    Source code for jmetal.algorithm.multiobjective.omopso

    self.initialize_particle_best(self.solutions) self.initialize_global_best(self.solutions)
    -
    [docs] def update_progress(self) -> None: + +
    +[docs] + def update_progress(self) -> None: self.evaluations += self.swarm_size self.leaders.compute_density_estimator() - observable_data = self.get_observable_data() - observable_data['SOLUTIONS'] = self.epsilon_archive.solution_list + observable_data = self.observable_data() + observable_data["SOLUTIONS"] = self.epsilon_archive.solution_list self.observable.notify_all(**observable_data)
    -
    [docs] def get_result(self) -> List[FloatSolution]: + +
    +[docs] + def result(self) -> List[FloatSolution]: return self.epsilon_archive.solution_list
    -
    [docs] def get_name(self) -> str: - return 'OMOPSO'
    + +
    +[docs] + def get_name(self) -> str: + return "OMOPSO"
    +
    +
    @@ -345,8 +370,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.algorithm.multiobjective.smpso — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -113,22 +110,22 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    import threading from copy import copy from math import sqrt -from typing import TypeVar, List, Optional +from typing import List, Optional, TypeVar import numpy from jmetal.config import store -from jmetal.core.algorithm import ParticleSwarmOptimization, DynamicAlgorithm +from jmetal.core.algorithm import DynamicAlgorithm, ParticleSwarmOptimization from jmetal.core.operator import Mutation -from jmetal.core.problem import FloatProblem, DynamicProblem +from jmetal.core.problem import DynamicProblem, FloatProblem from jmetal.core.solution import FloatSolution -from jmetal.util.archive import BoundedArchive, ArchiveWithReferencePoint -from jmetal.util.comparator import DominanceComparator +from jmetal.util.archive import ArchiveWithReferencePoint, BoundedArchive +from jmetal.util.comparator import DominanceComparator, Comparator from jmetal.util.evaluator import Evaluator from jmetal.util.generator import Generator from jmetal.util.termination_criterion import TerminationCriterion -R = TypeVar('R') +R = TypeVar("R") """ .. module:: SMPSO @@ -139,17 +136,21 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    """ -
    [docs]class SMPSO(ParticleSwarmOptimization): - - def __init__(self, - problem: FloatProblem, - swarm_size: int, - mutation: Mutation, - leaders: Optional[BoundedArchive], - termination_criterion: TerminationCriterion = store.default_termination_criteria, - swarm_generator: Generator = store.default_generator, - swarm_evaluator: Evaluator = store.default_evaluator): - """ This class implements the SMPSO algorithm as described in +
    +[docs] +class SMPSO(ParticleSwarmOptimization): + def __init__( + self, + problem: FloatProblem, + swarm_size: int, + mutation: Mutation, + leaders: Optional[BoundedArchive], + dominance_comparator: Comparator = DominanceComparator(), + termination_criterion: TerminationCriterion = store.default_termination_criteria, + swarm_generator: Generator = store.default_generator, + swarm_evaluator: Evaluator = store.default_evaluator, + ): + """This class implements the SMPSO algorithm as described in * SMPSO: A new PSO-based metaheuristic for multi-objective optimization * MCDM 2009. DOI: `<http://dx.doi.org/10.1109/MCDM.2009.4938830/>`_. @@ -163,9 +164,7 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    :param mutation: Mutation operator (see :py:mod:`jmetal.operator.mutation`). :param leaders: Archive for leaders. """ - super(SMPSO, self).__init__( - problem=problem, - swarm_size=swarm_size) + super(SMPSO, self).__init__(problem=problem, swarm_size=swarm_size) self.swarm_generator = swarm_generator self.swarm_evaluator = swarm_evaluator self.termination_criterion = termination_criterion @@ -186,38 +185,60 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    self.change_velocity1 = -1 self.change_velocity2 = -1 - self.dominance_comparator = DominanceComparator() + self.dominance_comparator = dominance_comparator - self.speed = numpy.zeros((self.swarm_size, self.problem.number_of_variables), dtype=float) - self.delta_max, self.delta_min = numpy.empty(problem.number_of_variables), \ - numpy.empty(problem.number_of_variables) + self.speed = numpy.zeros((self.swarm_size, self.problem.number_of_variables()), dtype=float) + self.delta_max, self.delta_min = ( + numpy.empty(problem.number_of_variables()), + numpy.empty(problem.number_of_variables()), + ) -
    [docs] def create_initial_solutions(self) -> List[FloatSolution]: +
    +[docs] + def create_initial_solutions(self) -> List[FloatSolution]: return [self.swarm_generator.new(self.problem) for _ in range(self.swarm_size)]
    -
    [docs] def evaluate(self, solution_list: List[FloatSolution]): + +
    +[docs] + def evaluate(self, solution_list: List[FloatSolution]): return self.swarm_evaluator.evaluate(solution_list, self.problem)
    -
    [docs] def stopping_condition_is_met(self) -> bool: + +
    +[docs] + def stopping_condition_is_met(self) -> bool: return self.termination_criterion.is_met
    -
    [docs] def initialize_global_best(self, swarm: List[FloatSolution]) -> None: + +
    +[docs] + def initialize_global_best(self, swarm: List[FloatSolution]) -> None: for particle in swarm: self.leaders.add(copy(particle))
    -
    [docs] def initialize_particle_best(self, swarm: List[FloatSolution]) -> None: + +
    +[docs] + def initialize_particle_best(self, swarm: List[FloatSolution]) -> None: for particle in swarm: - particle.attributes['local_best'] = copy(particle)
    + particle.attributes["local_best"] = copy(particle)
    -
    [docs] def initialize_velocity(self, swarm: List[FloatSolution]) -> None: - for i in range(self.problem.number_of_variables): + +
    +[docs] + def initialize_velocity(self, swarm: List[FloatSolution]) -> None: + for i in range(self.problem.number_of_variables()): self.delta_max[i] = (self.problem.upper_bound[i] - self.problem.lower_bound[i]) / 2.0 self.delta_min = -1.0 * self.delta_max
    -
    [docs] def update_velocity(self, swarm: List[FloatSolution]) -> None: + +
    +[docs] + def update_velocity(self, swarm: List[FloatSolution]) -> None: for i in range(self.swarm_size): - best_particle = copy(swarm[i].attributes['local_best']) + best_particle = copy(swarm[i].attributes["local_best"]) best_global = self.select_global_best() r1 = round(random.uniform(self.r1_min, self.r1_max), 1) @@ -227,22 +248,27 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    wmax = self.max_weight wmin = self.min_weight - for var in range(swarm[i].number_of_variables): - self.speed[i][var] = \ - self.__velocity_constriction( - self.__constriction_coefficient(c1, c2) * - ((self.__inertia_weight(wmax) - * self.speed[i][var]) - + (c1 * r1 * (best_particle.variables[var] - swarm[i].variables[var])) - + (c2 * r2 * (best_global.variables[var] - swarm[i].variables[var])) - ), - self.delta_max, self.delta_min, var)
    - -
    [docs] def update_position(self, swarm: List[FloatSolution]) -> None: + for var in range(len(swarm[i].variables)): + self.speed[i][var] = self.__velocity_constriction( + self.__constriction_coefficient(c1, c2) + * ( + (self.__inertia_weight(wmax) * self.speed[i][var]) + + (c1 * r1 * (best_particle.variables[var] - swarm[i].variables[var])) + + (c2 * r2 * (best_global.variables[var] - swarm[i].variables[var])) + ), + self.delta_max, + self.delta_min, + var, + )
    + + +
    +[docs] + def update_position(self, swarm: List[FloatSolution]) -> None: for i in range(self.swarm_size): particle = swarm[i] - for j in range(particle.number_of_variables): + for j in range(len(particle.variables)): particle.variables[j] += self.speed[i][j] if particle.variables[j] < self.problem.lower_bound[j]: @@ -253,24 +279,34 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    particle.variables[j] = self.problem.upper_bound[j] self.speed[i][j] *= self.change_velocity2
    -
    [docs] def update_global_best(self, swarm: List[FloatSolution]) -> None: + +
    +[docs] + def update_global_best(self, swarm: List[FloatSolution]) -> None: for particle in swarm: self.leaders.add(copy(particle))
    -
    [docs] def update_particle_best(self, swarm: List[FloatSolution]) -> None: + +
    +[docs] + def update_particle_best(self, swarm: List[FloatSolution]) -> None: for i in range(self.swarm_size): - flag = self.dominance_comparator.compare( - swarm[i], - swarm[i].attributes['local_best']) + flag = self.dominance_comparator.compare(swarm[i], swarm[i].attributes["local_best"]) if flag != 1: - swarm[i].attributes['local_best'] = copy(swarm[i])
    + swarm[i].attributes["local_best"] = copy(swarm[i])
    -
    [docs] def perturbation(self, swarm: List[FloatSolution]) -> None: + +
    +[docs] + def perturbation(self, swarm: List[FloatSolution]) -> None: for i in range(self.swarm_size): if (i % 6) == 0: self.mutation_operator.execute(swarm[i])
    -
    [docs] def select_global_best(self) -> FloatSolution: + +
    +[docs] + def select_global_best(self) -> FloatSolution: leaders = self.leaders.solution_list if len(leaders) > 2: @@ -285,6 +321,7 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    return best_global
    + def __velocity_constriction(self, value: float, delta_max: [], delta_min: [], variable_index: int) -> float: result = value if value > delta_max[variable_index]: @@ -306,7 +343,9 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    return result -
    [docs] def init_progress(self) -> None: +
    +[docs] + def init_progress(self) -> None: self.evaluations = self.swarm_size self.leaders.compute_density_estimator() @@ -314,31 +353,45 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    self.initialize_particle_best(self.solutions) self.initialize_global_best(self.solutions)
    -
    [docs] def update_progress(self) -> None: + +
    +[docs] + def update_progress(self) -> None: self.evaluations += self.swarm_size self.leaders.compute_density_estimator() - observable_data = self.get_observable_data() - observable_data['SOLUTIONS'] = self.leaders.solution_list + observable_data = self.observable_data() + observable_data["SOLUTIONS"] = self.leaders.solution_list self.observable.notify_all(**observable_data)
    -
    [docs] def get_result(self) -> List[FloatSolution]: + +
    +[docs] + def result(self) -> List[FloatSolution]: return self.leaders.solution_list
    -
    [docs] def get_name(self) -> str: - return 'SMPSO'
    +
    +[docs] + def get_name(self) -> str: + return "SMPSO"
    +
    -
    [docs]class DynamicSMPSO(SMPSO, DynamicAlgorithm): - def __init__(self, - problem: DynamicProblem[FloatSolution], - swarm_size: int, - mutation: Mutation, - leaders: BoundedArchive, - termination_criterion: TerminationCriterion = store.default_termination_criteria, - swarm_generator: Generator = store.default_generator, - swarm_evaluator: Evaluator = store.default_evaluator): + +
    +[docs] +class DynamicSMPSO(SMPSO, DynamicAlgorithm): + def __init__( + self, + problem: DynamicProblem[FloatSolution], + swarm_size: int, + mutation: Mutation, + leaders: BoundedArchive, + termination_criterion: TerminationCriterion = store.default_termination_criteria, + swarm_generator: Generator = store.default_generator, + swarm_evaluator: Evaluator = store.default_evaluator, + ): super(DynamicSMPSO, self).__init__( problem=problem, swarm_size=swarm_size, @@ -346,10 +399,13 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    leaders=leaders, termination_criterion=termination_criterion, swarm_generator=swarm_generator, - swarm_evaluator=swarm_evaluator) + swarm_evaluator=swarm_evaluator, + ) self.completed_iterations = 0 -
    [docs] def restart(self) -> None: +
    +[docs] + def restart(self) -> None: self.solutions = self.create_initial_solutions() self.solutions = self.evaluate(self.solutions) @@ -361,40 +417,51 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    self.init_progress()
    -
    [docs] def update_progress(self): + +
    +[docs] + def update_progress(self): if self.problem.the_problem_has_changed(): self.restart() self.problem.clear_changed() - observable_data = self.get_observable_data() + observable_data = self.observable_data() self.observable.notify_all(**observable_data) self.evaluations += self.swarm_size self.leaders.compute_density_estimator()
    -
    [docs] def stopping_condition_is_met(self): + +
    +[docs] + def stopping_condition_is_met(self): if self.termination_criterion.is_met: - observable_data = self.get_observable_data() - observable_data['termination_criterion_is_met'] = True + observable_data = self.observable_data() + observable_data["termination_criterion_is_met"] = True self.observable.notify_all(**observable_data) self.restart() self.init_progress() - self.completed_iterations += 1
    + self.completed_iterations += 1
    +
    -
    [docs]class SMPSORP(SMPSO): - def __init__(self, - problem: FloatProblem, - swarm_size: int, - mutation: Mutation, - reference_points: List[List[float]], - leaders: List[ArchiveWithReferencePoint], - termination_criterion: TerminationCriterion, - swarm_generator: Generator = store.default_generator, - swarm_evaluator: Evaluator = store.default_evaluator): - """ This class implements the SMPSORP algorithm. +
    +[docs] +class SMPSORP(SMPSO): + def __init__( + self, + problem: FloatProblem, + swarm_size: int, + mutation: Mutation, + reference_points: List[List[float]], + leaders: List[ArchiveWithReferencePoint], + termination_criterion: TerminationCriterion, + swarm_generator: Generator = store.default_generator, + swarm_evaluator: Evaluator = store.default_evaluator, + ): + """This class implements the SMPSORP algorithm. :param problem: The problem to solve. :param swarm_size: @@ -409,7 +476,8 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    leaders=None, swarm_generator=swarm_generator, swarm_evaluator=swarm_evaluator, - termination_criterion=termination_criterion) + termination_criterion=termination_criterion, + ) self.leaders = leaders self.reference_points = reference_points self.lock = threading.Lock() @@ -417,17 +485,25 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    thread = threading.Thread(target=_change_reference_point, args=(self,)) thread.start() -
    [docs] def initialize_global_best(self, swarm: List[FloatSolution]) -> None: +
    +[docs] + def initialize_global_best(self, swarm: List[FloatSolution]) -> None: for particle in swarm: for leader in self.leaders: leader.add(copy(particle))
    -
    [docs] def update_global_best(self, swarm: List[FloatSolution]) -> None: + +
    +[docs] + def update_global_best(self, swarm: List[FloatSolution]) -> None: for particle in swarm: for leader in self.leaders: leader.add(copy(particle))
    -
    [docs] def select_global_best(self) -> FloatSolution: + +
    +[docs] + def select_global_best(self) -> FloatSolution: selected = False selected_swarm_index = 0 @@ -450,7 +526,10 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    return best_global
    -
    [docs] def init_progress(self) -> None: + +
    +[docs] + def init_progress(self) -> None: self.evaluations = self.swarm_size for leader in self.leaders: @@ -460,29 +539,41 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    self.initialize_particle_best(self.solutions) self.initialize_global_best(self.solutions)
    -
    [docs] def update_progress(self) -> None: + +
    +[docs] + def update_progress(self) -> None: self.evaluations += self.swarm_size for leader in self.leaders: leader.filter() leader.compute_density_estimator() - observable_data = self.get_observable_data() - observable_data['REFERENCE_POINT'] = self.get_reference_point() + observable_data = self.observable_data() + observable_data["REFERENCE_POINT"] = self.get_reference_point() self.observable.notify_all(**observable_data)
    -
    [docs] def update_reference_point(self, new_reference_points: list): + +
    +[docs] + def update_reference_point(self, new_reference_points: list): with self.lock: self.reference_points = new_reference_points for index, archive in enumerate(self.leaders): archive.update_reference_point(new_reference_points[index])
    -
    [docs] def get_reference_point(self): + +
    +[docs] + def get_reference_point(self): with self.lock: return self.reference_points
    -
    [docs] def get_result(self) -> List[FloatSolution]: + +
    +[docs] + def result(self) -> List[FloatSolution]: result = [] for leader in self.leaders: @@ -491,24 +582,28 @@

    Source code for jmetal.algorithm.multiobjective.smpso

    return result
    -
    [docs] def get_name(self) -> str: - return 'SMPSO/RP'
    + +
    +[docs] + def get_name(self) -> str: + return "SMPSO/RP"
    +
    + def _change_reference_point(algorithm: SMPSORP): - """ Auxiliar function to read new reference points from the keyboard for the SMPSO/RP algorithm - """ + """Auxiliar function to read new reference points from the keyboard for the SMPSO/RP algorithm""" number_of_reference_points = len(algorithm.reference_points) number_of_objectives = algorithm.problem.number_of_objectives while True: - print(f'Enter {number_of_reference_points}-points of dimension {number_of_objectives}: ') + print(f"Enter {number_of_reference_points}-points of dimension {number_of_objectives}: ") read = [float(x) for x in input().split()] # Update reference points reference_points = [] for i in range(0, len(read), number_of_objectives): - reference_points.append(read[i:i + number_of_objectives]) + reference_points.append(read[i : i + number_of_objectives]) algorithm.update_reference_point(reference_points)
    @@ -527,8 +622,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.algorithm.multiobjective.spea2 — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -109,23 +106,23 @@

    Table Of Contents

    Source code for jmetal.algorithm.multiobjective.spea2

    -from typing import TypeVar, List
    +from typing import List, TypeVar
     
     from jmetal.algorithm.singleobjective.genetic_algorithm import GeneticAlgorithm
     from jmetal.config import store
    -from jmetal.core.operator import Mutation, Crossover
    +from jmetal.core.operator import Crossover, Mutation
     from jmetal.core.problem import Problem
    -from jmetal.operator import BinaryTournamentSelection
    +from jmetal.operator.selection import BinaryTournamentSelection
    +from jmetal.util.comparator import Comparator, MultiComparator
     from jmetal.util.density_estimator import KNearestNeighborDensityEstimator
     from jmetal.util.evaluator import Evaluator
     from jmetal.util.generator import Generator
     from jmetal.util.ranking import StrengthRanking
     from jmetal.util.replacement import RankingAndDensityEstimatorReplacement, RemovalPolicyType
    -from jmetal.util.comparator import Comparator, MultiComparator
     from jmetal.util.termination_criterion import TerminationCriterion
     
    -S = TypeVar('S')
    -R = TypeVar('R')
    +S = TypeVar("S")
    +R = TypeVar("R")
     
     """
     .. module:: SPEA2
    @@ -143,26 +140,30 @@ 

    Source code for jmetal.algorithm.multiobjective.spea2

    """ -
    [docs]class SPEA2(GeneticAlgorithm[S, R]): - - def __init__(self, - problem: Problem, - population_size: int, - offspring_population_size: int, - mutation: Mutation, - crossover: Crossover, - termination_criterion: TerminationCriterion = store.default_termination_criteria, - population_generator: Generator = store.default_generator, - population_evaluator: Evaluator = store.default_evaluator, - dominance_comparator: Comparator = store.default_comparator): - """ +
    +[docs] +class SPEA2(GeneticAlgorithm[S, R]): + def __init__( + self, + problem: Problem, + population_size: int, + offspring_population_size: int, + mutation: Mutation, + crossover: Crossover, + termination_criterion: TerminationCriterion = store.default_termination_criteria, + population_generator: Generator = store.default_generator, + population_evaluator: Evaluator = store.default_evaluator, + dominance_comparator: Comparator = store.default_comparator, + ): + """ :param problem: The problem to solve. :param population_size: Size of the population. :param mutation: Mutation operator (see :py:mod:`jmetal.operator.mutation`). :param crossover: Crossover operator (see :py:mod:`jmetal.operator.crossover`). """ - multi_comparator = MultiComparator([StrengthRanking.get_comparator(), - KNearestNeighborDensityEstimator.get_comparator()]) + multi_comparator = MultiComparator( + [StrengthRanking.get_comparator(), KNearestNeighborDensityEstimator.get_comparator()] + ) selection = BinaryTournamentSelection(comparator=multi_comparator) super(SPEA2, self).__init__( @@ -174,12 +175,14 @@

    Source code for jmetal.algorithm.multiobjective.spea2

    selection=selection, termination_criterion=termination_criterion, population_evaluator=population_evaluator, - population_generator=population_generator + population_generator=population_generator, ) self.dominance_comparator = dominance_comparator -
    [docs] def replacement(self, population: List[S], offspring_population: List[S]) -> List[List[S]]: - """ This method joins the current and offspring populations to produce the population of the next generation +
    +[docs] + def replacement(self, population: List[S], offspring_population: List[S]) -> List[List[S]]: + """This method joins the current and offspring populations to produce the population of the next generation by applying the ranking and crowding distance selection. :param population: Parent population. @@ -194,11 +197,19 @@

    Source code for jmetal.algorithm.multiobjective.spea2

    return solutions
    -
    [docs] def get_result(self) -> R: + +
    +[docs] + def result(self) -> R: return self.solutions
    -
    [docs] def get_name(self) -> str: - return 'SPEA2'
    + +
    +[docs] + def get_name(self) -> str: + return "SPEA2"
    +
    +
    @@ -215,8 +226,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.algorithm.singleobjective.evolution_strategy — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -110,17 +107,18 @@

    Table Of Contents

    Source code for jmetal.algorithm.singleobjective.evolution_strategy

     from copy import copy
    -from typing import TypeVar, List
    +from typing import List, TypeVar
     
     from jmetal.core.algorithm import EvolutionaryAlgorithm
     from jmetal.core.operator import Mutation
     from jmetal.core.problem import Problem
    +from jmetal.util.constraint_handling import overall_constraint_violation_degree
     from jmetal.util.evaluator import Evaluator, SequentialEvaluator
     from jmetal.util.generator import Generator, RandomGenerator
     from jmetal.util.termination_criterion import TerminationCriterion
     
    -S = TypeVar('S')
    -R = TypeVar('R')
    +S = TypeVar("S")
    +R = TypeVar("R")
     
     """
     .. module:: evolutionary_algorithm
    @@ -131,21 +129,21 @@ 

    Source code for jmetal.algorithm.singleobjective.evolution_strategy

    """ -
    [docs]class EvolutionStrategy(EvolutionaryAlgorithm[S, R]): - - def __init__(self, - problem: Problem, - mu: int, - lambda_: int, - elitist: bool, - mutation: Mutation, - termination_criterion: TerminationCriterion, - population_generator: Generator = RandomGenerator(), - population_evaluator: Evaluator = SequentialEvaluator()): - super(EvolutionStrategy, self).__init__( - problem=problem, - population_size=mu, - offspring_population_size=lambda_) +
    +[docs] +class EvolutionStrategy(EvolutionaryAlgorithm[S, R]): + def __init__( + self, + problem: Problem, + mu: int, + lambda_: int, + elitist: bool, + mutation: Mutation, + termination_criterion: TerminationCriterion, + population_generator: Generator = RandomGenerator(), + population_evaluator: Evaluator = SequentialEvaluator(), + ): + super(EvolutionStrategy, self).__init__(problem=problem, population_size=mu, offspring_population_size=lambda_) self.mu = mu self.lambda_ = lambda_ self.elitist = elitist @@ -158,20 +156,33 @@

    Source code for jmetal.algorithm.singleobjective.evolution_strategy

    self.termination_criterion = termination_criterion self.observable.register(termination_criterion) -
    [docs] def create_initial_solutions(self) -> List[S]: - return [self.population_generator.new(self.problem) - for _ in range(self.population_size)]
    +
    +[docs] + def create_initial_solutions(self) -> List[S]: + return [self.population_generator.new(self.problem) for _ in range(self.population_size)]
    + -
    [docs] def evaluate(self, solution_list: List[S]): +
    +[docs] + def evaluate(self, solution_list: List[S]): return self.population_evaluator.evaluate(solution_list, self.problem)
    -
    [docs] def stopping_condition_is_met(self) -> bool: + +
    +[docs] + def stopping_condition_is_met(self) -> bool: return self.termination_criterion.is_met
    -
    [docs] def selection(self, population: List[S]) -> List[S]: + +
    +[docs] + def selection(self, population: List[S]) -> List[S]: return population
    -
    [docs] def reproduction(self, population: List[S]) -> List[S]: + +
    +[docs] + def reproduction(self, population: List[S]) -> List[S]: offspring_population = [] for solution in population: for j in range(int(self.lambda_ / self.mu)): @@ -180,7 +191,10 @@

    Source code for jmetal.algorithm.singleobjective.evolution_strategy

    return offspring_population
    -
    [docs] def replacement(self, population: List[S], offspring_population: List[S]) -> List[S]: + +
    +[docs] + def replacement(self, population: List[S], offspring_population: List[S]) -> List[S]: population_pool = [] if self.elitist: @@ -189,7 +203,7 @@

    Source code for jmetal.algorithm.singleobjective.evolution_strategy

    else: population_pool.extend(offspring_population) - population_pool.sort(key=lambda s: s.objectives[0]) + population_pool.sort(key=lambda s: (overall_constraint_violation_degree(s), s.objectives[0])) new_population = [] for i in range(self.mu): @@ -197,11 +211,19 @@

    Source code for jmetal.algorithm.singleobjective.evolution_strategy

    return new_population
    -
    [docs] def get_result(self) -> R: + +
    +[docs] + def result(self) -> R: return self.solutions[0]
    -
    [docs] def get_name(self) -> str: - return 'Elitist evolution Strategy'
    + +
    +[docs] + def get_name(self) -> str: + return "Elitist evolution Strategy"
    +
    +
    @@ -218,8 +240,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.algorithm.singleobjective.genetic_algorithm — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -109,18 +106,21 @@

    Table Of Contents

    Source code for jmetal.algorithm.singleobjective.genetic_algorithm

    -from typing import TypeVar, List
    +from functools import cmp_to_key
    +from typing import List, TypeVar
     
     from jmetal.config import store
     from jmetal.core.algorithm import EvolutionaryAlgorithm
    -from jmetal.core.operator import Mutation, Crossover, Selection
    +from jmetal.core.operator import Crossover, Mutation, Selection
     from jmetal.core.problem import Problem
    +from jmetal.operator.selection import BinaryTournamentSelection
    +from jmetal.util.comparator import Comparator, ObjectiveComparator
     from jmetal.util.evaluator import Evaluator
     from jmetal.util.generator import Generator
     from jmetal.util.termination_criterion import TerminationCriterion
     
    -S = TypeVar('S')
    -R = TypeVar('R')
    +S = TypeVar("S")
    +R = TypeVar("R")
     
     """
     .. module:: genetic_algorithm
    @@ -130,24 +130,29 @@ 

    Source code for jmetal.algorithm.singleobjective.genetic_algorithm

    """ -
    [docs]class GeneticAlgorithm(EvolutionaryAlgorithm[S, R]): - - def __init__(self, - problem: Problem, - population_size: int, - offspring_population_size: int, - mutation: Mutation, - crossover: Crossover, - selection: Selection, - termination_criterion: TerminationCriterion = store.default_termination_criteria, - population_generator: Generator = store.default_generator, - population_evaluator: Evaluator = store.default_evaluator): +
    +[docs] +class GeneticAlgorithm(EvolutionaryAlgorithm[S, R]): + def __init__( + self, + problem: Problem, + population_size: int, + offspring_population_size: int, + mutation: Mutation, + crossover: Crossover, + selection: Selection = BinaryTournamentSelection(ObjectiveComparator(0)), + termination_criterion: TerminationCriterion = store.default_termination_criteria, + population_generator: Generator = store.default_generator, + population_evaluator: Evaluator = store.default_evaluator, + solution_comparator: Comparator = ObjectiveComparator(0) + ): super(GeneticAlgorithm, self).__init__( - problem=problem, - population_size=population_size, - offspring_population_size=offspring_population_size) + problem=problem, population_size=population_size, offspring_population_size=offspring_population_size + ) self.mutation_operator = mutation self.crossover_operator = crossover + self.solution_comparator = solution_comparator + self.selection_operator = selection self.population_generator = population_generator @@ -156,37 +161,52 @@

    Source code for jmetal.algorithm.singleobjective.genetic_algorithm

    self.termination_criterion = termination_criterion self.observable.register(termination_criterion) - self.mating_pool_size = \ - self.offspring_population_size * \ - self.crossover_operator.get_number_of_parents() // self.crossover_operator.get_number_of_children() + self.mating_pool_size = ( + self.offspring_population_size + * self.crossover_operator.get_number_of_parents() + // self.crossover_operator.get_number_of_children() + ) if self.mating_pool_size < self.crossover_operator.get_number_of_children(): self.mating_pool_size = self.crossover_operator.get_number_of_children() -
    [docs] def create_initial_solutions(self) -> List[S]: - return [self.population_generator.new(self.problem) - for _ in range(self.population_size)]
    +
    +[docs] + def create_initial_solutions(self) -> List[S]: + return [self.population_generator.new(self.problem) for _ in range(self.population_size)]
    -
    [docs] def evaluate(self, population: List[S]): + +
    +[docs] + def evaluate(self, population: List[S]): return self.population_evaluator.evaluate(population, self.problem)
    -
    [docs] def stopping_condition_is_met(self) -> bool: + +
    +[docs] + def stopping_condition_is_met(self) -> bool: return self.termination_criterion.is_met
    -
    [docs] def selection(self, population: List[S]): + +
    +[docs] + def selection(self, population: List[S]): mating_population = [] - for i in range(self.mating_pool_size): + for _ in range(self.mating_pool_size): solution = self.selection_operator.execute(population) mating_population.append(solution) return mating_population
    -
    [docs] def reproduction(self, mating_population: List[S]) -> List[S]: + +
    +[docs] + def reproduction(self, mating_population: List[S]) -> List[S]: number_of_parents_to_combine = self.crossover_operator.get_number_of_parents() if len(mating_population) % number_of_parents_to_combine != 0: - raise Exception('Wrong number of parents') + raise Exception("Wrong number of parents") offspring_population = [] for i in range(0, self.offspring_population_size, number_of_parents_to_combine): @@ -204,18 +224,29 @@

    Source code for jmetal.algorithm.singleobjective.genetic_algorithm

    return offspring_population
    -
    [docs] def replacement(self, population: List[S], offspring_population: List[S]) -> List[S]: + +
    +[docs] + def replacement(self, population: List[S], offspring_population: List[S]) -> List[S]: population.extend(offspring_population) - population.sort(key=lambda s: s.objectives[0]) + population.sort(key=cmp_to_key(self.solution_comparator.compare)) + + return population[: self.population_size]
    - return population[:self.population_size]
    -
    [docs] def get_result(self) -> R: +
    +[docs] + def result(self) -> R: return self.solutions[0]
    -
    [docs] def get_name(self) -> str: - return 'Genetic algorithm'
    + +
    +[docs] + def get_name(self) -> str: + return "Genetic algorithm"
    +
    +
    @@ -232,8 +263,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.algorithm.singleobjective.local_search — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -113,7 +110,7 @@

    Source code for jmetal.algorithm.singleobjective.local_search

    import random import threading import time -from typing import TypeVar, List +from typing import List, TypeVar from jmetal.config import store from jmetal.core.algorithm import Algorithm @@ -123,8 +120,8 @@

    Source code for jmetal.algorithm.singleobjective.local_search

    from jmetal.util.comparator import Comparator from jmetal.util.termination_criterion import TerminationCriterion -S = TypeVar('S') -R = TypeVar('R') +S = TypeVar("S") +R = TypeVar("R") """ .. module:: local_search @@ -135,13 +132,16 @@

    Source code for jmetal.algorithm.singleobjective.local_search

    """ -
    [docs]class LocalSearch(Algorithm[S, R], threading.Thread): - - def __init__(self, - problem: Problem[S], - mutation: Mutation, - termination_criterion: TerminationCriterion = store.default_termination_criteria, - comparator: Comparator = store.default_comparator): +
    +[docs] +class LocalSearch(Algorithm[S, R], threading.Thread): + def __init__( + self, + problem: Problem[S], + mutation: Mutation, + termination_criterion: TerminationCriterion = store.default_termination_criteria, + comparator: Comparator = store.default_comparator, + ): super(LocalSearch, self).__init__() self.comparator = comparator self.problem = problem @@ -149,20 +149,34 @@

    Source code for jmetal.algorithm.singleobjective.local_search

    self.termination_criterion = termination_criterion self.observable.register(termination_criterion) -
    [docs] def create_initial_solutions(self) -> List[S]: +
    +[docs] + def create_initial_solutions(self) -> List[S]: self.solutions.append(self.problem.create_solution()) return self.solutions
    -
    [docs] def evaluate(self, solutions: List[S]) -> List[S]: + +
    +[docs] + def evaluate(self, solutions: List[S]) -> List[S]: return [self.problem.evaluate(solutions[0])]
    -
    [docs] def stopping_condition_is_met(self) -> bool: + +
    +[docs] + def stopping_condition_is_met(self) -> bool: return self.termination_criterion.is_met
    -
    [docs] def init_progress(self) -> None: + +
    +[docs] + def init_progress(self) -> None: self.evaluations = 0
    -
    [docs] def step(self) -> None: + +
    +[docs] + def step(self) -> None: mutated_solution = copy.deepcopy(self.solutions[0]) mutated_solution: Solution = self.mutation.execute(mutated_solution) mutated_solution = self.evaluate([mutated_solution])[0] @@ -176,22 +190,40 @@

    Source code for jmetal.algorithm.singleobjective.local_search

    if random.random() < 0.5: self.solutions[0] = mutated_solution
    -
    [docs] def update_progress(self) -> None: + +
    +[docs] + def update_progress(self) -> None: self.evaluations += 1 - observable_data = self.get_observable_data() + observable_data = self.observable_data() self.observable.notify_all(**observable_data)
    -
    [docs] def get_observable_data(self) -> dict: + +
    +[docs] + def observable_data(self) -> dict: ctime = time.time() - self.start_computing_time - return {'PROBLEM': self.problem, 'EVALUATIONS': self.evaluations, 'SOLUTIONS': self.get_result(), - 'COMPUTING_TIME': ctime}
    + return { + "PROBLEM": self.problem, + "EVALUATIONS": self.evaluations, + "SOLUTIONS": self.result(), + "COMPUTING_TIME": ctime, + }
    + -
    [docs] def get_result(self) -> R: +
    +[docs] + def result(self) -> R: return self.solutions[0]
    -
    [docs] def get_name(self) -> str: - return 'LS'
    + +
    +[docs] + def get_name(self) -> str: + return "LS"
    +
    +
    @@ -208,8 +240,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.algorithm.singleobjective.simulated_annealing — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -113,73 +110,95 @@

    Source code for jmetal.algorithm.singleobjective.simulated_annealing

    import random import threading import time -from typing import TypeVar, List +from typing import List, TypeVar import numpy +from jmetal.config import store from jmetal.core.algorithm import Algorithm from jmetal.core.operator import Mutation from jmetal.core.problem import Problem from jmetal.core.solution import Solution +from jmetal.util.generator import Generator from jmetal.util.termination_criterion import TerminationCriterion -S = TypeVar('S') -R = TypeVar('R') +S = TypeVar("S") +R = TypeVar("R") """ .. module:: simulated_annealing :platform: Unix, Windows - :synopsis: Implementation of Local search. + :synopsis: Implementation of Simulated Annealing. .. moduleauthor:: Antonio J. Nebro <antonio@lcc.uma.es>, Antonio Benítez-Hidalgo <antonio.b@uma.es> """ -
    [docs]class SimulatedAnnealing(Algorithm[S, R], threading.Thread): - - def __init__(self, - problem: Problem[S], - mutation: Mutation, - termination_criterion: TerminationCriterion): +
    +[docs] +class SimulatedAnnealing(Algorithm[S, R], threading.Thread): + def __init__( + self, + problem: Problem[S], + mutation: Mutation, + termination_criterion: TerminationCriterion, + solution_generator: Generator = store.default_generator, + ): super(SimulatedAnnealing, self).__init__() self.problem = problem self.mutation = mutation self.termination_criterion = termination_criterion + self.solution_generator = solution_generator self.observable.register(termination_criterion) self.temperature = 1.0 self.minimum_temperature = 0.000001 self.alpha = 0.95 self.counter = 0 -
    [docs] def create_initial_solutions(self) -> List[S]: - self.solutions.append(self.problem.create_solution()) - return self.solutions
    +
    +[docs] + def create_initial_solutions(self) -> List[S]: + return [self.solution_generator.new(self.problem)]
    -
    [docs] def evaluate(self, solutions: List[S]) -> List[S]: + +
    +[docs] + def evaluate(self, solutions: List[S]) -> List[S]: return [self.problem.evaluate(solutions[0])]
    -
    [docs] def stopping_condition_is_met(self) -> bool: + +
    +[docs] + def stopping_condition_is_met(self) -> bool: return self.termination_criterion.is_met
    -
    [docs] def init_progress(self) -> None: + +
    +[docs] + def init_progress(self) -> None: self.evaluations = 0
    -
    [docs] def step(self) -> None: + +
    +[docs] + def step(self) -> None: mutated_solution = copy.deepcopy(self.solutions[0]) mutated_solution: Solution = self.mutation.execute(mutated_solution) mutated_solution = self.evaluate([mutated_solution])[0] acceptance_probability = self.compute_acceptance_probability( - self.solutions[0].objectives[0], - mutated_solution.objectives[0], - self.temperature) + self.solutions[0].objectives[0], mutated_solution.objectives[0], self.temperature + ) if acceptance_probability > random.random(): self.solutions[0] = mutated_solution self.temperature *= self.alpha
    -
    [docs] def compute_acceptance_probability(self, current: float, new: float, temperature: float) -> float: + +
    +[docs] + def compute_acceptance_probability(self, current: float, new: float, temperature: float) -> float: if new < current: return 1.0 else: @@ -187,22 +206,40 @@

    Source code for jmetal.algorithm.singleobjective.simulated_annealing

    value = (new - current) / t return numpy.exp(-1.0 * value)
    -
    [docs] def update_progress(self) -> None: + +
    +[docs] + def update_progress(self) -> None: self.evaluations += 1 - observable_data = self.get_observable_data() + observable_data = self.observable_data() self.observable.notify_all(**observable_data)
    -
    [docs] def get_observable_data(self) -> dict: + +
    +[docs] + def observable_data(self) -> dict: ctime = time.time() - self.start_computing_time - return {'PROBLEM': self.problem, 'EVALUATIONS': self.evaluations, 'SOLUTIONS': self.get_result(), - 'COMPUTING_TIME': ctime}
    + return { + "PROBLEM": self.problem, + "EVALUATIONS": self.evaluations, + "SOLUTIONS": self.result(), + "COMPUTING_TIME": ctime, + }
    + -
    [docs] def get_result(self) -> R: +
    +[docs] + def result(self) -> R: return self.solutions[0]
    -
    [docs] def get_name(self) -> str: - return 'Simulated Annealing'
    + +
    +[docs] + def get_name(self) -> str: + return "Simulated Annealing"
    +
    +
    @@ -219,8 +256,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.core.observer — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -109,7 +106,7 @@

    Table Of Contents

    Source code for jmetal.core.observer

    -from abc import abstractmethod, ABC
    +from abc import ABC, abstractmethod
     
     """
     .. module:: Observable
    @@ -120,33 +117,49 @@ 

    Source code for jmetal.core.observer

     """
     
     
    -
    [docs]class Observer(ABC): - -
    [docs] @abstractmethod +
    +[docs] +class Observer(ABC): +
    +[docs] + @abstractmethod def update(self, *args, **kwargs): - """ Update method. - """ - pass
    + """Update method.""" + pass
    +
    -
    [docs]class Observable(ABC): -
    [docs] @abstractmethod +
    +[docs] +class Observable(ABC): +
    +[docs] + @abstractmethod def register(self, observer): pass
    -
    [docs] @abstractmethod + +
    +[docs] + @abstractmethod def deregister(self, observer): pass
    -
    [docs] @abstractmethod + +
    +[docs] + @abstractmethod def deregister_all(self): pass
    -
    [docs] @abstractmethod - def notify_all(self, *args, **kwargs): - pass
    +
    +[docs] + @abstractmethod + def notify_all(self, *args, **kwargs): + pass
    +
    @@ -164,8 +177,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.core.problem — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -109,123 +106,209 @@

    Table Of Contents

    Source code for jmetal.core.problem

    -import logging
    -import random
    +import random
     from abc import ABC, abstractmethod
    -from typing import Generic, TypeVar, List
    +from typing import Generic, List, TypeVar
     
     from jmetal.core.observer import Observer
    -from jmetal.core.solution import BinarySolution, FloatSolution, IntegerSolution, PermutationSolution
    +from jmetal.core.solution import (
    +    BinarySolution,
    +    FloatSolution,
    +    IntegerSolution,
    +    PermutationSolution,
    +)
    +from jmetal.logger import get_logger
     
    -LOGGER = logging.getLogger('jmetal')
    +logger = get_logger(__name__)
     
    -S = TypeVar('S')
    +S = TypeVar("S")
     
     
    -
    [docs]class Problem(Generic[S], ABC): - """ Class representing problems. """ +
    +[docs] +class Problem(Generic[S], ABC): + """Class representing problems.""" MINIMIZE = -1 MAXIMIZE = 1 def __init__(self): - self.number_of_variables: int = 0 - self.number_of_objectives: int = 0 - self.number_of_constraints: int = 0 - - self.reference_front: List[S] = None - + self.reference_front: List[S] = [] self.directions: List[int] = [] self.labels: List[str] = [] -
    [docs] @abstractmethod +
    +[docs] + @abstractmethod + def number_of_variables(self) -> int: + pass
    + + +
    +[docs] + @abstractmethod + def number_of_objectives(self) -> int: + pass
    + + +
    +[docs] + @abstractmethod + def number_of_constraints(self) -> int: + pass
    + + +
    +[docs] + @abstractmethod def create_solution(self) -> S: - """ Creates a random_search solution to the problem. + """Creates a random_search solution to the problem. - :return: Solution. """ + :return: Solution.""" pass
    -
    [docs] @abstractmethod + +
    +[docs] + @abstractmethod def evaluate(self, solution: S) -> S: - """ Evaluate a solution. For any new problem inheriting from :class:`Problem`, this method should be - replaced. Note that this framework ASSUMES minimization, thus solutions must be evaluated in consequence. + """Evaluate a solution. For any new problem inheriting from :class:`Problem`, this method should be replaced. + Note that this framework ASSUMES minimization, thus solutions must be evaluated in consequence. - :return: Evaluated solution. """ + :return: Evaluated solution.""" pass
    -
    [docs] @abstractmethod - def get_name(self) -> str: - pass
    + +
    +[docs] + @abstractmethod + def name(self) -> str: + pass
    +
    -
    [docs]class DynamicProblem(Problem[S], Observer, ABC): -
    [docs] @abstractmethod +
    +[docs] +class DynamicProblem(Problem[S], Observer, ABC): +
    +[docs] + @abstractmethod def the_problem_has_changed(self) -> bool: pass
    -
    [docs] @abstractmethod + +
    +[docs] + @abstractmethod def clear_changed(self) -> None: - pass
    + pass
    +
    + -
    [docs]class BinaryProblem(Problem[BinarySolution], ABC): - """ Class representing binary problems. """ +
    +[docs] +class BinaryProblem(Problem[BinarySolution], ABC): + """Class representing binary problems.""" def __init__(self): - super(BinaryProblem, self).__init__()
    + super(BinaryProblem, self).__init__() + + self.number_of_bits_per_variable = [] +
    +[docs] + def number_of_bits_per_variable_list(self): + return self.number_of_bits_per_variable
    + + +
    +[docs] + def total_number_of_bits(self): + return sum(self.number_of_bits_per_variable)
    +
    -
    [docs]class FloatProblem(Problem[FloatSolution], ABC): - """ Class representing float problems. """ + + +
    +[docs] +class FloatProblem(Problem[FloatSolution], ABC): + """Class representing float problems.""" def __init__(self): super(FloatProblem, self).__init__() self.lower_bound = [] self.upper_bound = [] -
    [docs] def create_solution(self) -> FloatSolution: +
    +[docs] + def number_of_variables(self) -> int: + return len(self.lower_bound)
    + + +
    +[docs] + def create_solution(self) -> FloatSolution: new_solution = FloatSolution( - self.lower_bound, - self.upper_bound, - self.number_of_objectives, - self.number_of_constraints) - new_solution.variables = \ - [random.uniform(self.lower_bound[i]*1.0, self.upper_bound[i]*1.0) for i in range(self.number_of_variables)] + self.lower_bound, self.upper_bound, self.number_of_objectives(), self.number_of_constraints() + ) + new_solution.variables = [ + random.uniform(self.lower_bound[i] * 1.0, self.upper_bound[i] * 1.0) + for i in range(self.number_of_variables()) + ] + + return new_solution
    +
    - return new_solution
    -
    [docs]class IntegerProblem(Problem[IntegerSolution], ABC): - """ Class representing integer problems. """ +
    +[docs] +class IntegerProblem(Problem[IntegerSolution], ABC): + """Class representing integer problems.""" def __init__(self): super(IntegerProblem, self).__init__() - self.lower_bound = None - self.upper_bound = None + self.lower_bound = [] + self.upper_bound = [] + +
    +[docs] + def number_of_variables(self) -> int: + return len(self.lower_bound)
    + -
    [docs] def create_solution(self) -> IntegerSolution: +
    +[docs] + def create_solution(self) -> IntegerSolution: new_solution = IntegerSolution( - self.lower_bound, - self.upper_bound, - self.number_of_objectives, - self.number_of_constraints) - new_solution.variables = \ - [int(random.uniform(self.lower_bound[i]*1.0, self.upper_bound[i]*1.0)) - for i in range(self.number_of_variables)] + self.lower_bound, self.upper_bound, self.number_of_objectives(), self.number_of_constraints() + ) + new_solution.variables = [ + round(random.uniform(self.lower_bound[i] * 1.0, self.upper_bound[i] * 1.0)) + for i in range(self.number_of_variables()) + ] + + return new_solution
    +
    - return new_solution
    -
    [docs]class PermutationProblem(Problem[PermutationSolution], ABC): - """ Class representing permutation problems. """ +
    +[docs] +class PermutationProblem(Problem[PermutationSolution], ABC): + """Class representing permutation problems.""" def __init__(self): super(PermutationProblem, self).__init__()
    -
    [docs]class OnTheFlyFloatProblem(FloatProblem): - """ Class for defining float problems on the fly. + +
    +[docs] +class OnTheFlyFloatProblem(FloatProblem): + """ Class for defining float problems on the fly. Example: @@ -251,45 +334,74 @@

    Source code for jmetal.core.problem

                 .add_constraint(c1)\
                 .add_constraint(c2)
         """
    +
         def __init__(self):
             super(OnTheFlyFloatProblem, self).__init__()
             self.functions = []
             self.constraints = []
    -        self.name = None
    +        self.problem_name = None
     
    -
    [docs] def set_name(self, name): - self.name = name +
    +[docs] + def set_name(self, name) -> "OnTheFlyFloatProblem": + self.problem_name = name return self
    -
    [docs] def add_function(self, function): + +
    +[docs] + def add_function(self, function) -> "OnTheFlyFloatProblem": self.functions.append(function) - self.number_of_objectives += 1 return self
    -
    [docs] def add_constraint(self, constraint): + +
    +[docs] + def add_constraint(self, constraint) -> "OnTheFlyFloatProblem": self.constraints.append(constraint) - self.number_of_constraints += 1 return self
    -
    [docs] def add_variable(self, lower_bound, upper_bound): + +
    +[docs] + def add_variable(self, lower_bound, upper_bound) -> "OnTheFlyFloatProblem": self.lower_bound.append(lower_bound) self.upper_bound.append(upper_bound) - self.number_of_variables += 1 return self
    -
    [docs] def evaluate(self, solution: FloatSolution): - for i in range(self.number_of_objectives): + +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.functions)
    + + +
    +[docs] + def number_of_constraints(self) -> int: + return len(self.constraints)
    + + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> None: + for i in range(self.number_of_objectives()): solution.objectives[i] = self.functions[i](solution.variables) - for i in range(self.number_of_constraints): + for i in range(self.number_of_constraints()): solution.constraints[i] = self.constraints[i](solution.variables)
    -
    [docs] def get_name(self) -> str: - return self.name
    + +
    +[docs] + def name(self) -> str: + return self.problem_name
    +
    +
    @@ -306,8 +418,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.lab.experiment — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -110,7 +107,6 @@

    Table Of Contents

    Source code for jmetal.lab.experiment

     import io
    -import logging
     import os
     from concurrent.futures import ProcessPoolExecutor
     from pathlib import Path
    @@ -120,13 +116,18 @@ 

    Source code for jmetal.lab.experiment

     import matplotlib.pyplot as plt
     import numpy as np
     import pandas as pd
    -from scipy.stats import mannwhitneyu
    +from scipy.stats import mannwhitneyu, iqr, ks_2samp
     
     from jmetal.core.algorithm import Algorithm
     from jmetal.core.quality_indicator import QualityIndicator
    -from jmetal.util.solution import print_function_values_to_file, print_variables_to_file, read_solutions
    +from jmetal.logger import get_logger
    +from jmetal.util.solution import (
    +    print_function_values_to_file,
    +    print_variables_to_file,
    +    read_solutions,
    +)
     
    -LOGGER = logging.getLogger('jmetal')
    +logger = get_logger(__name__)
     
     """
     .. module:: laboratory
    @@ -137,33 +138,45 @@ 

    Source code for jmetal.lab.experiment

     """
     
     
    -
    [docs]class Job: - +
    +[docs] +class Job: def __init__(self, algorithm: Algorithm, algorithm_tag: str, problem_tag: str, run: int): self.algorithm = algorithm self.algorithm_tag = algorithm_tag self.problem_tag = problem_tag self.run_tag = run -
    [docs] def execute(self, output_path: str = ''): +
    +[docs] + def execute(self, output_path: str = ""): self.algorithm.run() if output_path: - file_name = os.path.join(output_path, 'FUN.{}.tsv'.format(self.run_tag)) - print_function_values_to_file(self.algorithm.get_result(), filename=file_name) + file_name = os.path.join(output_path, "FUN.{}.tsv".format(self.run_tag)) + print_function_values_to_file(self.algorithm.result(), filename=file_name) - file_name = os.path.join(output_path, 'VAR.{}.tsv'.format(self.run_tag)) - print_variables_to_file(self.algorithm.get_result(), filename=file_name) + file_name = os.path.join(output_path, "VAR.{}.tsv".format(self.run_tag)) + print_variables_to_file(self.algorithm.result(), filename=file_name) - file_name = os.path.join(output_path, 'TIME.{}'.format(self.run_tag)) - with open(file_name, 'w+') as of: - of.write(str(self.algorithm.total_computing_time))
    + file_name = os.path.join(output_path, "TIME.{}".format(self.run_tag)) + with open(file_name, "w+") as of: + of.write(str(self.algorithm.total_computing_time))
    -
    [docs]class Experiment: +
    +[docs] + def get_algorithm_data(self): + return self.algorithm.observable_data()
    +
    + + +
    +[docs] +class Experiment: def __init__(self, output_dir: str, jobs: List[Job], m_workers: int = 6): - """ Run an experiment to execute a list of jobs. + """Run an experiment to execute a list of jobs. :param output_dir: Base directory where each job will save its results. :param jobs: List of Jobs (from :py:mod:`jmetal.util.laboratory)`) to be executed. @@ -172,17 +185,26 @@

    Source code for jmetal.lab.experiment

             self.jobs = jobs
             self.m_workers = m_workers
             self.output_dir = output_dir
    +        self.job_data = []
     
    -
    [docs] def run(self) -> None: +
    +[docs] + def run(self) -> None: with ProcessPoolExecutor(max_workers=self.m_workers) as executor: for job in self.jobs: output_path = os.path.join(self.output_dir, job.algorithm_tag, job.problem_tag) - executor.submit(job.execute(output_path))
    + executor.submit(job.execute(output_path)) + self.job_data.append(job.get_algorithm_data())
    +
    + -
    [docs]def generate_summary_from_experiment(input_dir: str, quality_indicators: List[QualityIndicator], - reference_fronts: str = ''): - """ Compute a list of quality indicators. The input data directory *must* met the following structure (this is generated +
    +[docs] +def generate_summary_from_experiment( + input_dir: str, quality_indicators: List[QualityIndicator], reference_fronts: str = "" +): + """Compute a list of quality indicators. The input data directory *must* met the following structure (this is generated automatically by the Experiment class): * <base_dir> @@ -206,52 +228,57 @@

    Source code for jmetal.lab.experiment

         if not quality_indicators:
             quality_indicators = []
     
    -    with open('QualityIndicatorSummary.csv', 'w+') as of:
    -        of.write('Algorithm,Problem,ExecutionId,IndicatorName,IndicatorValue\n')
    +    with open("QualityIndicatorSummary.csv", "w+") as of:
    +        of.write("Algorithm,Problem,ExecutionId,IndicatorName,IndicatorValue\n")
     
         for dirname, _, filenames in os.walk(input_dir):
             for filename in filenames:
                 try:
                     # Linux filesystem
    -                algorithm, problem = dirname.split('/')[-2:]
    +                algorithm, problem = dirname.split("/")[-2:]
                 except ValueError:
                     # Windows filesystem
    -                algorithm, problem = dirname.split('\\')[-2:]
    +                algorithm, problem = dirname.split("\\")[-2:]
     
    -            if 'TIME' in filename:
    -                run_tag = [s for s in filename.split('.') if s.isdigit()].pop()
    +            if "TIME" in filename:
    +                run_tag = [s for s in filename.split(".") if s.isdigit()].pop()
     
    -                with open(os.path.join(dirname, filename), 'r') as content_file:
    +                with open(os.path.join(dirname, filename), "r") as content_file:
                         content = content_file.read()
     
    -                with open('QualityIndicatorSummary.csv', 'a+') as of:
    -                    of.write(','.join([algorithm, problem, run_tag, 'Time', str(content)]))
    -                    of.write('\n')
    +                with open("QualityIndicatorSummary.csv", "a+") as of:
    +                    of.write(",".join([algorithm, problem, run_tag, "Time", str(content)]))
    +                    of.write("\n")
     
    -            if 'FUN' in filename:
    +            if "FUN" in filename:
                     solutions = read_solutions(os.path.join(dirname, filename))
    -                run_tag = [s for s in filename.split('.') if s.isdigit()].pop()
    -
    +                run_tag = [s for s in filename.split(".") if s.isdigit()].pop()
                     for indicator in quality_indicators:
    -                    reference_front_file = os.path.join(reference_fronts, problem + '.pf')
    +                    reference_front_file = os.path.join(reference_fronts, problem + ".pf")
     
                         # Add reference front if any
    -                    if hasattr(indicator, 'reference_front'):
    +                    if hasattr(indicator, "reference_front"):
                             if Path(reference_front_file).is_file():
    -                            indicator.reference_front = read_solutions(reference_front_file)
    +                            reference_front = []
    +                            with open(reference_front_file) as file:
    +                                for line in file:
    +                                    reference_front.append([float(x) for x in line.split()])
    +
    +                            indicator.reference_front = reference_front
                             else:
    -                            LOGGER.warning('Reference front not found at', reference_front_file)
    +                            logger.warning("Reference front not found at", reference_front_file)
     
    -                    result = indicator.compute(solutions)
    +                    result = indicator.compute([solutions[i].objectives for i in range(len(solutions))])
     
                         # Save quality indicator value to file
    -                    with open('QualityIndicatorSummary.csv', 'a+') as of:
    -                        of.write(','.join([algorithm, problem, run_tag, indicator.get_name(), str(result)]))
    -                        of.write('\n')
    + with open("QualityIndicatorSummary.csv", "a+") as of: + of.write(",".join([algorithm, problem, run_tag, indicator.get_short_name(), str(result)])) + of.write("\n")
    + -def generate_boxplot(filename: str, output_dir: str = 'boxplot'): - """ Generate boxplot diagrams. +def generate_boxplot(filename: str, output_dir: str = "boxplot"): + """Generate boxplot diagrams. :param filename: Input filename (summary). :param output_dir: Output path. @@ -259,31 +286,32 @@

    Source code for jmetal.lab.experiment

         df = pd.read_csv(filename, skipinitialspace=True)
     
         if len(set(df.columns.tolist())) != 5:
    -        raise Exception('Wrong number of columns')
    +        raise Exception("Wrong number of columns")
     
         if Path(output_dir).is_dir():
    -        LOGGER.warning('Directory {} exists. Removing contents.'.format(output_dir))
    +        logger.warning("Directory {} exists. Removing contents.".format(output_dir))
             for file in os.listdir(output_dir):
    -            os.remove('{0}/{1}'.format(output_dir, file))
    +            os.remove("{0}/{1}".format(output_dir, file))
         else:
    -        LOGGER.warning('Directory {} does not exist. Creating it.'.format(output_dir))
    +        logger.warning("Directory {} does not exist. Creating it.".format(output_dir))
             Path(output_dir).mkdir(parents=True)
     
    -    algorithms = pd.unique(df['Algorithm'])
    -    problems = pd.unique(df['Problem'])
    -    indicators = pd.unique(df['IndicatorName'])
    +    algorithms = pd.unique(df["Algorithm"])
    +    problems = pd.unique(df["Problem"])
    +    indicators = pd.unique(df["IndicatorName"])
     
         # We consider the quality indicator indicator_name
     
         for indicator_name in indicators:
    -        data = df[df['IndicatorName'] == indicator_name]
    +        data = df[df["IndicatorName"] == indicator_name]
     
             for pr in problems:
                 data_to_plot = []
     
                 for alg in algorithms:
    -                data_to_plot.append(data['IndicatorValue'][np.logical_and(
    -                    data['Algorithm'] == alg, data['Problem'] == pr)])
    +                data_to_plot.append(
    +                    data["IndicatorValue"][np.logical_and(data["Algorithm"] == alg, data["Problem"] == pr)]
    +                )
     
                 # Create a figure instance
                 fig = plt.figure(1, figsize=(9, 6))
    @@ -295,13 +323,13 @@ 

    Source code for jmetal.lab.experiment

                 ax.set_xticklabels(algorithms)
                 ax.tick_params(labelsize=20)
     
    -            plt.savefig(os.path.join(output_dir, 'boxplot-{}-{}.png'.format(pr, indicator_name)), bbox_inches='tight')
    -            plt.savefig(os.path.join(output_dir, 'boxplot-{}-{}.eps'.format(pr, indicator_name)), bbox_inches='tight')
    +            plt.savefig(os.path.join(output_dir, "boxplot-{}-{}.png".format(pr, indicator_name)), bbox_inches="tight")
    +            plt.savefig(os.path.join(output_dir, "boxplot-{}-{}.eps".format(pr, indicator_name)), bbox_inches="tight")
                 plt.close(fig)
     
     
    -def generate_latex_tables(filename: str, output_dir: str = 'latex/statistical'):
    -    """ Computes a number of statistical values (mean, median, standard deviation, interquartile range).
    +def generate_latex_tables(filename: str, output_dir: str = "latex/statistical"):
    +    """Computes a number of statistical values (mean, median, standard deviation, interquartile range).
     
         :param filename: Input filename (summary).
         :param output_dir: Output path.
    @@ -309,24 +337,24 @@ 

    Source code for jmetal.lab.experiment

         df = pd.read_csv(filename, skipinitialspace=True)
     
         if len(set(df.columns.tolist())) != 5:
    -        raise Exception('Wrong number of columns')
    +        raise Exception("Wrong number of columns")
     
         if Path(output_dir).is_dir():
    -        LOGGER.warning('Directory {} exists. Removing contents.'.format(output_dir))
    +        logger.warning("Directory {} exists. Removing contents.".format(output_dir))
             for file in os.listdir(output_dir):
    -            os.remove('{0}/{1}'.format(output_dir, file))
    +            os.remove("{0}/{1}".format(output_dir, file))
         else:
    -        LOGGER.warning('Directory {} does not exist. Creating it.'.format(output_dir))
    +        logger.warning("Directory {} does not exist. Creating it.".format(output_dir))
             Path(output_dir).mkdir(parents=True)
     
         # Generate median & iqr tables
         median, iqr = pd.DataFrame(), pd.DataFrame()
         mean, std = pd.DataFrame(), pd.DataFrame()
     
    -    for algorithm_name, subset in df.groupby('Algorithm', sort=False):
    -        subset = subset.drop('Algorithm', axis=1)
    -        subset = subset.rename(columns={'IndicatorValue': algorithm_name})
    -        subset = subset.set_index(['Problem', 'IndicatorName', 'ExecutionId'])
    +    for algorithm_name, subset in df.groupby("Algorithm", sort=False):
    +        subset = subset.drop("Algorithm", axis=1)
    +        subset = subset.rename(columns={"IndicatorValue": algorithm_name})
    +        subset = subset.set_index(["Problem", "IndicatorName", "ExecutionId"])
     
             # Compute Median and Interquartile range
             median_ = subset.groupby(level=[0, 1]).median()
    @@ -343,83 +371,83 @@ 

    Source code for jmetal.lab.experiment

             std = pd.concat([std, std_], axis=1)
     
         # Generate mean & std tables
    -    for indicator_name, subset in std.groupby('IndicatorName', sort=False):
    -        subset = median.groupby('IndicatorName', sort=False).get_group(indicator_name)
    +    for indicator_name, subset in std.groupby("IndicatorName", sort=False):
    +        subset = median.groupby("IndicatorName", sort=False).get_group(indicator_name)
             subset.index = subset.index.droplevel(1)
    -        subset.to_csv(os.path.join(output_dir, 'Median-{}.csv'.format(indicator_name)), sep='\t', encoding='utf-8')
    +        subset.to_csv(os.path.join(output_dir, "Median-{}.csv".format(indicator_name)), sep="\t", encoding="utf-8")
     
    -        subset = iqr.groupby('IndicatorName', sort=False).get_group(indicator_name)
    +        subset = iqr.groupby("IndicatorName", sort=False).get_group(indicator_name)
             subset.index = subset.index.droplevel(1)
    -        subset.to_csv(os.path.join(output_dir, 'IQR-{}.csv'.format(indicator_name)), sep='\t', encoding='utf-8')
    +        subset.to_csv(os.path.join(output_dir, "IQR-{}.csv".format(indicator_name)), sep="\t", encoding="utf-8")
     
    -        subset = mean.groupby('IndicatorName', sort=False).get_group(indicator_name)
    +        subset = mean.groupby("IndicatorName", sort=False).get_group(indicator_name)
             subset.index = subset.index.droplevel(1)
    -        subset.to_csv(os.path.join(output_dir, 'Mean-{}.csv'.format(indicator_name)), sep='\t', encoding='utf-8')
    +        subset.to_csv(os.path.join(output_dir, "Mean-{}.csv".format(indicator_name)), sep="\t", encoding="utf-8")
     
    -        subset = std.groupby('IndicatorName', sort=False).get_group(indicator_name)
    +        subset = std.groupby("IndicatorName", sort=False).get_group(indicator_name)
             subset.index = subset.index.droplevel(1)
    -        subset.to_csv(os.path.join(output_dir, 'Std-{}.csv'.format(indicator_name)), sep='\t', encoding='utf-8')
    +        subset.to_csv(os.path.join(output_dir, "Std-{}.csv".format(indicator_name)), sep="\t", encoding="utf-8")
     
         # Generate LaTeX tables
    -    for indicator_name in df.groupby('IndicatorName', sort=False).groups.keys():
    +    for indicator_name in df.groupby("IndicatorName", sort=False).groups.keys():
             # Median & IQR
    -        md = median.groupby('IndicatorName', sort=False).get_group(indicator_name)
    +        md = median.groupby("IndicatorName", sort=False).get_group(indicator_name)
             md.index = md.index.droplevel(1)
     
    -        i = iqr.groupby('IndicatorName', sort=False).get_group(indicator_name)
    +        i = iqr.groupby("IndicatorName", sort=False).get_group(indicator_name)
             i.index = i.index.droplevel(1)
     
    -        with open(os.path.join(output_dir, 'MedianIQR-{}.tex'.format(indicator_name)), 'w') as latex:
    +        with open(os.path.join(output_dir, "MedianIQR-{}.tex".format(indicator_name)), "w") as latex:
                 latex.write(
                     __averages_to_latex(
                         md,
                         i,
    -                    caption='Median and Interquartile Range of the {} quality indicator.'.format(indicator_name),
    +                    caption="Median and Interquartile Range of the {} quality indicator.".format(indicator_name),
                         minimization=check_minimization(indicator_name),
    -                    label='table:{}'.format(indicator_name)
    +                    label="table:{}".format(indicator_name),
                     )
                 )
     
             # Mean & Std
    -        mn = mean.groupby('IndicatorName', sort=False).get_group(indicator_name)
    +        mn = mean.groupby("IndicatorName", sort=False).get_group(indicator_name)
             mn.index = mn.index.droplevel(1)
     
    -        s = std.groupby('IndicatorName', sort=False).get_group(indicator_name)
    +        s = std.groupby("IndicatorName", sort=False).get_group(indicator_name)
             s.index = s.index.droplevel(1)
     
    -        with open(os.path.join(output_dir, 'MeanStd-{}.tex'.format(indicator_name)), 'w') as latex:
    +        with open(os.path.join(output_dir, "MeanStd-{}.tex".format(indicator_name)), "w") as latex:
                 latex.write(
                     __averages_to_latex(
                         mn,
                         s,
    -                    caption='Mean and Standard Deviation of the {} quality indicator.'.format(indicator_name),
    +                    caption="Mean and Standard Deviation of the {} quality indicator.".format(indicator_name),
                         minimization=check_minimization(indicator_name),
    -                    label='table:{}'.format(indicator_name)
    +                    label="table:{}".format(indicator_name),
                     )
                 )
     
     
    -def compute_wilcoxon(filename: str, output_dir: str = 'latex/wilcoxon'):
    -    """
    +def compute_wilcoxon(filename: str, output_dir: str = "latex/wilcoxon"):
    +    """
         :param filename: Input filename (summary).
         :param output_dir: Output path.
         """
         df = pd.read_csv(filename, skipinitialspace=True)
     
         if len(set(df.columns.tolist())) != 5:
    -        raise Exception('Wrong number of columns')
    +        raise Exception("Wrong number of columns")
     
         if Path(output_dir).is_dir():
    -        LOGGER.warning('Directory {} exists. Removing contents.'.format(output_dir))
    +        logger.warning("Directory {} exists. Removing contents.".format(output_dir))
             for file in os.listdir(output_dir):
    -            os.remove('{0}/{1}'.format(output_dir, file))
    +            os.remove("{0}/{1}".format(output_dir, file))
         else:
    -        LOGGER.warning('Directory {} does not exist. Creating it.'.format(output_dir))
    +        logger.warning("Directory {} does not exist. Creating it.".format(output_dir))
             Path(output_dir).mkdir(parents=True)
     
    -    algorithms = pd.unique(df['Algorithm'])
    -    problems = pd.unique(df['Problem'])
    -    indicators = pd.unique(df['IndicatorName'])
    +    algorithms = pd.unique(df["Algorithm"])
    +    problems = pd.unique(df["Problem"])
    +    indicators = pd.unique(df["IndicatorName"])
     
         table = pd.DataFrame(index=algorithms[0:-1], columns=algorithms[1:])
     
    @@ -431,10 +459,16 @@ 

    Source code for jmetal.lab.experiment

     
                     if i <= j:
                         for problem in problems:
    -                        df1 = df[(df["Algorithm"] == row_algorithm) & (df["Problem"] == problem) & (
    -                                df["IndicatorName"] == indicator_name)]
    -                        df2 = df[(df["Algorithm"] == col_algorithm) & (df["Problem"] == problem) & (
    -                                df["IndicatorName"] == indicator_name)]
    +                        df1 = df[
    +                            (df["Algorithm"] == row_algorithm)
    +                            & (df["Problem"] == problem)
    +                            & (df["IndicatorName"] == indicator_name)
    +                            ]
    +                        df2 = df[
    +                            (df["Algorithm"] == col_algorithm)
    +                            & (df["Problem"] == problem)
    +                            & (df["IndicatorName"] == indicator_name)
    +                            ]
     
                             data1 = df1["IndicatorValue"]
                             data2 = df2["IndicatorValue"]
    @@ -447,49 +481,51 @@ 

    Source code for jmetal.lab.experiment

                             if p <= 0.05:
                                 if check_minimization(indicator_name):
                                     if median1 <= median2:
    -                                    line.append('+')
    +                                    line.append("+")
                                     else:
    -                                    line.append('o')
    +                                    line.append("o")
                                 else:
                                     if median1 >= median2:
    -                                    line.append('+')
    +                                    line.append("+")
                                     else:
    -                                    line.append('o')
    +                                    line.append("o")
                             else:
    -                            line.append('-')
    -                    wilcoxon.append(''.join(line))
    +                            line.append("-")
    +                    wilcoxon.append("".join(line))
     
    -            if len(wilcoxon) < len(algorithms): wilcoxon = [''] * (len(algorithms) - len(wilcoxon) - 1) + wilcoxon
    +            if len(wilcoxon) < len(algorithms):
    +                wilcoxon = [""] * (len(algorithms) - len(wilcoxon) - 1) + wilcoxon
                 table.loc[row_algorithm] = wilcoxon
     
    -        table.to_csv(os.path.join(output_dir, 'Wilcoxon-{}.csv'.format(indicator_name)), sep='\t', encoding='utf-8')
    +        table.to_csv(os.path.join(output_dir, "Wilcoxon-{}.csv".format(indicator_name)), sep="\t", encoding="utf-8")
     
    -        with open(os.path.join(output_dir, 'Wilcoxon-{}.tex'.format(indicator_name)), 'w') as latex:
    +        with open(os.path.join(output_dir, "Wilcoxon-{}.tex".format(indicator_name)), "w") as latex:
                 latex.write(
                     __wilcoxon_to_latex(
                         table,
    -                    caption='Wilcoxon values of the {} quality indicator ({}).'.format(indicator_name,
    -                                                                                       ', '.join(problems)),
    -                    label='table:{}'.format(indicator_name)
    +                    caption="Wilcoxon values of the {} quality indicator ({}).".format(
    +                        indicator_name, ", ".join(problems)
    +                    ),
    +                    label="table:{}".format(indicator_name),
                     )
                 )
     
     
     def compute_mean_indicator(filename: str, indicator_name: str):
    -    """ Compute the mean values of an indicator.
    +    """Compute the mean values of an indicator.
         :param filename:
         :param indicator_name: Quality indicator name.
         """
         df = pd.read_csv(filename, skipinitialspace=True)
     
         if len(set(df.columns.tolist())) != 5:
    -        raise Exception('Wrong number of columns')
    +        raise Exception("Wrong number of columns")
     
    -    algorithms = pd.unique(df['Algorithm'])
    -    problems = pd.unique(df['Problem'])
    +    algorithms = pd.unique(df["Algorithm"])
    +    problems = pd.unique(df["Problem"])
     
         # We consider the quality indicator indicator_name
    -    data = df[df['IndicatorName'] == indicator_name]
    +    data = df[df["IndicatorName"] == indicator_name]
     
         # Compute for each pair algorithm/problem the average of IndicatorValue
         average_values = np.zeros((problems.size, algorithms.size))
    @@ -497,8 +533,9 @@ 

    Source code for jmetal.lab.experiment

         for alg in algorithms:
             i = 0
             for pr in problems:
    -            average_values[i, j] = data['IndicatorValue'][np.logical_and(
    -                data['Algorithm'] == alg, data['Problem'] == pr)].mean()
    +            average_values[i, j] = data["IndicatorValue"][
    +                np.logical_and(data["Algorithm"] == alg, data["Problem"] == pr)
    +            ].mean()
                 i += 1
             j += 1
     
    @@ -509,9 +546,170 @@ 

    Source code for jmetal.lab.experiment

         return df
     
     
    -def __averages_to_latex(central_tendency: pd.DataFrame, dispersion: pd.DataFrame,
    -                        caption: str, label: str, minimization=True, alignment: str = 'c'):
    -    """ Convert a pandas DataFrame to a LaTeX tabular. Prints labels in bold and does use math mode.
    +
    +[docs] +def generate_median_and_wilcoxon_latex_tables(filename: str, output_dir: str = "latex/meansAndWilcoxon"): + """Generate Latex tables including medians and IQRs. Additionally, the last algorithm is considered as the reference + algorithm, and the cells include a symbol indicating whether the differences with the reference algorithm + are significant or not according to the Wilcoxon rank sum test. + + :param filename: Input filename (summary). + :param output_dir: Output path. + """ + data = pd.read_csv(filename, skipinitialspace=True) + + if len(set(data.columns.tolist())) != 5: + raise Exception("Wrong number of columns") + + if Path(output_dir).is_dir(): + logger.warning("Directory {} exists. Removing contents.".format(output_dir)) + for file in os.listdir(output_dir): + os.remove("{0}/{1}".format(output_dir, file)) + else: + logger.warning("Directory {} does not exist. Creating it.".format(output_dir)) + Path(output_dir).mkdir(parents=True) + + algorithms = pd.unique(data["Algorithm"]) + problems = pd.unique(data["Problem"]) + indicators = pd.unique(data["IndicatorName"]) + + control_algorithm = algorithms[-1] + + # Compute medians and IQRs + medians = data.groupby(["Algorithm", "Problem", "IndicatorName"])["IndicatorValue"].median() + iqrs = data.groupby(["Algorithm", "Problem", "IndicatorName"])["IndicatorValue"].apply(lambda x: iqr(x)) + + # Create data frame to store the Wilcoxon test results + wilcoxon_data = pd.DataFrame(columns=["Indicator", "Algorithm", "Problem", "PValue", "Median", "TestResult"]) + + for indicator in indicators: + for algorithm in algorithms: + for problem in problems: + algorithm_data = data[(data["Problem"] == problem) & (data["Algorithm"] == algorithm) & ( + data["IndicatorName"] == indicator)] + ref_data = data[(data["Problem"] == problem) & (data["Algorithm"] == control_algorithm) & ( + data["IndicatorName"] == indicator)] + stat, p_value = mannwhitneyu(algorithm_data["IndicatorValue"], ref_data["IndicatorValue"]) + + test_result = "" + if p_value <= 0.05: + if check_minimization(indicator): + if medians[algorithm][problem][indicator] <= medians[control_algorithm][problem][indicator]: + test_result = '+' + else: + test_result = '-' + else: + if medians[algorithm][problem][indicator] >= medians[control_algorithm][problem][indicator]: + test_result = '+' + else: + test_result = '-' + else: + test_result = '=' + + new_row = {'Indicator': indicator, 'Algorithm': algorithm, "Problem": problem, + "PValue": p_value, + "Median": medians[algorithm][problem][indicator], + "IQR": iqrs[algorithm][problem][indicator], + "TestResult": test_result + } + wilcoxon_data = wilcoxon_data._append(new_row, ignore_index=True) + + # Generate LaTeX tables + caption = "Median and interquartile range (IQR) of the results of the {} quality indicator. " + \ + "Cells with dark and light gray background highlights, respectively, the best and second best indicator values. " + \ + "The algorithm in the last column is the reference " + \ + "algorithm, and the symbols $+$, $-$ and $\\approx$ indicate that the differences with the reference " + \ + "algorithm are significantly better, worse, or there is no difference according to the Wilcoxon rank " + \ + "sum test (confidence level: 95\%)." + for indicator_name in indicators: + with open(os.path.join(output_dir, "MedianIQRWilcoxon-{}.tex".format(indicator_name)), "w") as latex: + latex.write( + __median_wilcoxon_to_latex( + indicator_name, + wilcoxon_data, + caption=caption.format(indicator_name), + label="table:{}".format(indicator_name), + ) + )
    + + + +
    +[docs] +def generate_kolmogorov_smirnov_latex_tables(filename: str, output_dir: str = "latex/KolmogorovSmirnov"): + """Generate Latex tables with the results of the Kolmogorov-Smirnov test. The last algorithm is considered as + the reference algorithm, and the cells include a symbol with the p-value < 0.05. + + :param filename: Input filename (summary). + :param output_dir: Output path. + """ + data = pd.read_csv(filename, skipinitialspace=True) + + if len(set(data.columns.tolist())) != 5: + raise Exception("Wrong number of columns") + + if Path(output_dir).is_dir(): + logger.warning("Directory {} exists. Removing contents.".format(output_dir)) + for file in os.listdir(output_dir): + os.remove("{0}/{1}".format(output_dir, file)) + else: + logger.warning("Directory {} does not exist. Creating it.".format(output_dir)) + Path(output_dir).mkdir(parents=True) + + algorithms = pd.unique(data["Algorithm"]) + problems = pd.unique(data["Problem"]) + indicators = pd.unique(data["IndicatorName"]) + + control_algorithm = algorithms[-1] + + # Create data frame to store the Kolmogorov Smirnov test results + test_data = pd.DataFrame(columns=["Indicator", "Algorithm", "Problem", "PValue", "TestResult"]) + + for indicator in indicators: + for algorithm in algorithms: + for problem in problems: + algorithm_data = data[(data["Problem"] == problem) & (data["Algorithm"] == algorithm) & ( + data["IndicatorName"] == indicator)] + ref_data = data[(data["Problem"] == problem) & (data["Algorithm"] == control_algorithm) & ( + data["IndicatorName"] == indicator)] + stat, p_value = ks_2samp(algorithm_data["IndicatorValue"], ref_data["IndicatorValue"]) + + test_result = stat + + new_row = {'Indicator': indicator, 'Algorithm': algorithm, "Problem": problem, + "PValue": p_value, + "TestResult": test_result + } + test_data = test_data._append(new_row, ignore_index=True) + + # Generate LaTeX tables + caption = "Kolmogorov-Smirnov Test of the {} quality indicator. " \ + "The algorithm in the last column is the reference " + \ + "algorithm and each cell contain the p-value obtained when applying the test with the reference " \ + "algorithm. Cells with gray background highlight p-values less than 0.05 (i.e., the null hypothesis" \ + " -- the two distributions are identical -- is rejected)." + for indicator_name in indicators: + with open(os.path.join(output_dir, "KolmogorovSmirnov-{}.tex".format(indicator_name)), "w") as latex: + latex.write( + __kolmogorov_smirnov_to_latex( + indicator_name, + test_data, + caption=caption.format(indicator_name), + label="table:{}".format(indicator_name), + ) + )
    + + + +def __averages_to_latex( + central_tendency: pd.DataFrame, + dispersion: pd.DataFrame, + caption: str, + label: str, + minimization=True, + alignment: str = "c", +): + """Convert a pandas DataFrame to a LaTeX tabular. Prints labels in bold and does use math mode. :param caption: LaTeX table caption. :param label: LaTeX table label. @@ -520,40 +718,40 @@

    Source code for jmetal.lab.experiment

         num_columns, num_rows = central_tendency.shape[1], central_tendency.shape[0]
         output = io.StringIO()
     
    -    col_format = '{}|{}'.format(alignment, alignment * num_columns)
    -    column_labels = ['\\textbf{{{0}}}'.format(label.replace('_', '\\_')) for label in central_tendency.columns]
    +    col_format = "{}|{}".format(alignment, alignment * num_columns)
    +    column_labels = ["\\textbf{{{0}}}".format(label.replace("_", "\\_")) for label in central_tendency.columns]
     
         # Write header
    -    output.write('\\documentclass{article}\n')
    +    output.write("\\documentclass{article}\n")
     
    -    output.write('\\usepackage[utf8]{inputenc}\n')
    -    output.write('\\usepackage{tabularx}\n')
    -    output.write('\\usepackage{colortbl}\n')
    -    output.write('\\usepackage[table*]{xcolor}\n')
    +    output.write("\\usepackage[utf8]{inputenc}\n")
    +    output.write("\\usepackage{tabularx}\n")
    +    output.write("\\usepackage{colortbl}\n")
    +    output.write("\\usepackage[table*]{xcolor}\n")
     
    -    output.write('\\xdefinecolor{gray95}{gray}{0.65}\n')
    -    output.write('\\xdefinecolor{gray25}{gray}{0.8}\n')
    +    output.write("\\xdefinecolor{gray95}{gray}{0.65}\n")
    +    output.write("\\xdefinecolor{gray25}{gray}{0.8}\n")
     
    -    output.write('\\title{Median and IQR}\n')
    -    output.write('\\author{}\n')
    +    output.write("\\title{Median and IQR}\n")
    +    output.write("\\author{}\n")
     
    -    output.write('\\begin{document}\n')
    -    output.write('\\maketitle\n')
    +    output.write("\\begin{document}\n")
    +    output.write("\\maketitle\n")
     
    -    output.write('\\section{Table}\n')
    +    output.write("\\section{Table}\n")
     
    -    output.write('\\begin{table}[!htp]\n')
    -    output.write('  \\caption{{{}}}\n'.format(caption))
    -    output.write('  \\label{{{}}}\n'.format(label))
    -    output.write('  \\centering\n')
    -    output.write('  \\begin{scriptsize}\n')
    -    output.write('  \\begin{tabular}{%s}\n' % col_format)
    -    output.write('      & {} \\\\\\hline\n'.format(' & '.join(column_labels)))
    +    output.write("\\begin{table}[!htp]\n")
    +    output.write("  \\caption{{{}}}\n".format(caption))
    +    output.write("  \\label{{{}}}\n".format(label))
    +    output.write("  \\centering\n")
    +    output.write("  \\begin{scriptsize}\n")
    +    output.write("  \\begin{tabular}{%s}\n" % col_format)
    +    output.write("      & {} \\\\\\hline\n".format(" & ".join(column_labels)))
     
         # Write data lines
         for i in range(num_rows):
    -        central_values = [v for v in central_tendency.ix[i]]
    -        dispersion_values = [v for v in dispersion.ix[i]]
    +        central_values = [v for v in central_tendency.iloc[i]]
    +        dispersion_values = [v for v in dispersion.iloc[i]]
     
             # Sort mean/median values (the lower the better if minimization)
             # Note that mean/median values could be the same: in that case, sort by Std/IQR (the lower the better)
    @@ -567,29 +765,32 @@ 

    Source code for jmetal.lab.experiment

                 second_best, best = sorted_values[-1][2], sorted_values[-2][2]
     
             # Compose cell
    -        values = ['{:.2e}_{{{:.2e}}}'.format(central_values[i], dispersion_values[i]) for i in
    -                  range(len(central_values))]
    +        values = [
    +            "{:.2e}_{{{:.2e}}}".format(central_values[i], dispersion_values[i]) for i in range(len(central_values))
    +        ]
     
             # Highlight values
    -        values[best] = '\\cellcolor{gray25} ' + values[best]
    -        values[second_best] = '\\cellcolor{gray95} ' + values[second_best]
    +        values[best] = "\\cellcolor{gray25} " + values[best]
    +        values[second_best] = "\\cellcolor{gray95} " + values[second_best]
     
    -        output.write('      \\textbf{{{0}}} & ${1}$ \\\\\n'.format(
    -            central_tendency.index[i], ' $ & $ '.join([str(val) for val in values]))
    +        output.write(
    +            "      \\textbf{{{0}}} & ${1}$ \\\\\n".format(
    +                central_tendency.index[i], " $ & $ ".join([str(val) for val in values])
    +            )
             )
     
         # Write footer
    -    output.write('  \\end{tabular}\n')
    -    output.write('  \\end{scriptsize}\n')
    -    output.write('\\end{table}\n')
    +    output.write("  \\end{tabular}\n")
    +    output.write("  \\end{scriptsize}\n")
    +    output.write("\\end{table}\n")
     
    -    output.write('\\end{document}')
    +    output.write("\\end{document}")
     
         return output.getvalue()
     
     
    -def __wilcoxon_to_latex(df: pd.DataFrame, caption: str, label: str, minimization=True, alignment: str = 'c'):
    -    """ Convert a pandas DataFrame to a LaTeX tabular. Prints labels in bold and does use math mode.
    +def __wilcoxon_to_latex(df: pd.DataFrame, caption: str, label: str, minimization=True, alignment: str = "c"):
    +    """Convert a pandas DataFrame to a LaTeX tabular. Prints labels in bold and does use math mode.
     
         :param df: Pandas dataframe.
         :param caption: LaTeX table caption.
    @@ -599,58 +800,242 @@ 

    Source code for jmetal.lab.experiment

         num_columns, num_rows = df.shape[1], df.shape[0]
         output = io.StringIO()
     
    -    col_format = '{}|{}'.format(alignment, alignment * num_columns)
    -    column_labels = ['\\textbf{{{0}}}'.format(label.replace('_', '\\_')) for label in df.columns]
    +    col_format = "{}|{}".format(alignment, alignment * num_columns)
    +    column_labels = ["\\textbf{{{0}}}".format(label.replace("_", "\\_")) for label in df.columns]
     
         # Write header
    -    output.write('\\documentclass{article}\n')
    +    output.write("\\documentclass{article}\n")
     
    -    output.write('\\usepackage[utf8]{inputenc}\n')
    -    output.write('\\usepackage{tabularx}\n')
    -    output.write('\\usepackage{amssymb}\n')
    -    output.write('\\usepackage{amsmath}\n')
    +    output.write("\\usepackage[utf8]{inputenc}\n")
    +    output.write("\\usepackage{tabularx}\n")
    +    output.write("\\usepackage{amssymb}\n")
    +    output.write("\\usepackage{amsmath}\n")
     
    -    output.write('\\title{Wilcoxon - Mann-Whitney rank sum test}\n')
    -    output.write('\\author{}\n')
    +    output.write("\\title{Wilcoxon - Mann-Whitney rank sum test}\n")
    +    output.write("\\author{}\n")
     
    -    output.write('\\begin{document}\n')
    -    output.write('\\maketitle\n')
    +    output.write("\\begin{document}\n")
    +    output.write("\\maketitle\n")
     
    -    output.write('\\section{Table}\n')
    +    output.write("\\section{Table}\n")
     
    -    output.write('\\begin{table}[!htp]\n')
    -    output.write('  \\caption{{{}}}\n'.format(caption))
    -    output.write('  \\label{{{}}}\n'.format(label))
    -    output.write('  \\centering\n')
    -    output.write('  \\begin{scriptsize}\n')
    -    output.write('  \\begin{tabular}{%s}\n' % col_format)
    -    output.write('      & {} \\\\\\hline\n'.format(' & '.join(column_labels)))
    +    output.write("\\begin{table}[!htp]\n")
    +    output.write("  \\caption{{{}}}\n".format(caption))
    +    output.write("  \\label{{{}}}\n".format(label))
    +    output.write("  \\centering\n")
    +    output.write("  \\begin{scriptsize}\n")
    +    output.write("  \\begin{tabular}{%s}\n" % col_format)
    +    output.write("      & {} \\\\\\hline\n".format(" & ".join(column_labels)))
     
    -    symbolo = '\\triangledown\ '
    -    symbolplus = '\\blacktriangle\ '
    +    symbolo = "\\triangledown\ "
    +    symbolplus = "\\blacktriangle\ "
     
         if not minimization:
             symbolo, symbolplus = symbolplus, symbolo
     
         # Write data lines
         for i in range(num_rows):
    -        values = [val.replace('-', '\\text{--}\ ').replace('o', symbolo).replace('+', symbolplus) for val in df.ix[i]]
    -        output.write('      \\textbf{{{0}}} & ${1}$ \\\\\n'.format(
    -            df.index[i], ' $ & $ '.join([str(val) for val in values]))
    +        values = [val.replace("-", "\\text{--}\ ").replace("o", symbolo).replace("+", symbolplus) for val in df.iloc[i]]
    +        output.write(
    +            "      \\textbf{{{0}}} & ${1}$ \\\\\n".format(df.index[i], " $ & $ ".join([str(val) for val in values]))
    +        )
    +
    +    # Write footer
    +    output.write("  \\end{tabular}\n")
    +    output.write("  \\end{scriptsize}\n")
    +    output.write("\\end{table}\n")
    +
    +    output.write("\\end{document}")
    +
    +    return output.getvalue()
    +
    +
    +def __median_wilcoxon_to_latex(
    +        indicator_name: str,
    +        wilcoxon_data: pd.DataFrame,
    +        caption: str,
    +        label):
    +    indicator_data = wilcoxon_data[wilcoxon_data["Indicator"] == indicator_name]
    +
    +    problems = pd.unique(indicator_data["Problem"])
    +    algorithms = pd.unique(indicator_data["Algorithm"])
    +
    +    num_columns = len(algorithms)
    +    columns = algorithms
    +
    +    alignment = "c"
    +    col_format = "{}|{}".format(alignment, alignment * num_columns)
    +    column_labels = ["\\textbf{{{0}}}".format(label.replace("_", "\\_")) for label in columns]
    +
    +    output = io.StringIO()
    +
    +    output.write("\\documentclass{article}\n")
    +
    +    output.write("\\usepackage[utf8]{inputenc}\n")
    +    output.write("\\usepackage{tabularx}\n")
    +    output.write("\\usepackage{colortbl}\n")
    +    output.write("\\usepackage[table*]{xcolor}\n")
    +
    +    output.write("\\xdefinecolor{gray95}{gray}{0.65}\n")
    +    output.write("\\xdefinecolor{gray25}{gray}{0.8}\n")
    +
    +    output.write("\\title{Median and Wilcoxon}\n")
    +    output.write("\\author{}\n")
    +
    +    output.write("\\begin{document}\n")
    +    output.write("\\maketitle\n")
    +
    +    output.write("\\section{Table}\n")
    +
    +    output.write("\\begin{table}[!htp]\n")
    +    output.write("  \\caption{{{}}}\n".format(caption))
    +    output.write("  \\label{{{}}}\n".format(label))
    +    output.write("  \\centering\n")
    +    output.write("  \\begin{tiny}\n")
    +    output.write("  \\begin{tabular}{%s}\n" % col_format)
    +    output.write("      & {} \\\\\\hline\n".format(" & ".join(column_labels)))
    +
    +    # Counts the number of times that an algorithm performs better, worse or equal than the reference algorithm
    +    counters = {}
    +    for algorithm in algorithms:
    +        counters[algorithm] = [0, 0, 0]  # best, equal, worse
    +
    +    for problem in problems:
    +        values = []
    +
    +        for algorithm in algorithms:
    +            row = indicator_data[(indicator_data["Problem"] == problem) & (indicator_data["Algorithm"] == algorithm)]
    +            value = "{:.2e}({:.2e})".format(row["Median"].tolist()[0], row["IQR"].tolist()[0])
    +
    +            # Include the symbol according to the Wilcoxon rank sum test with the reference algorithm
    +            if algorithm != algorithms[-1]:
    +                if row["TestResult"].tolist()[0] == "-":
    +                    value = "{{{}-}}".format(value)
    +                    counters[algorithm][2] = counters[algorithm][2] + 1
    +                elif row["TestResult"].tolist()[0] == "+":
    +                    value = "{{{}+}}".format(value)
    +                    counters[algorithm][0] = counters[algorithm][0] + 1
    +                else:
    +                    value = "{{{}\\approx}}".format(value)
    +                    counters[algorithm][1] = counters[algorithm][1] + 1
    +            values.append(value)
    +
    +        # Find the best and second best values
    +        medians = indicator_data[(indicator_data["Problem"] == problem)]["Median"]
    +        iqrs = indicator_data[(indicator_data["Problem"] == problem)]["IQR"]
    +        pairs = list(zip(medians, iqrs))
    +        indexes = sorted(range(len(pairs)), key=lambda x: pairs[x])
    +
    +        if check_minimization(indicator_name):
    +            best = indexes[0]
    +            second_best = indexes[1]
    +        else:
    +            best = indexes[-1]
    +            second_best = indexes[-2]
    +
    +        values[best] = "\\cellcolor{gray95} " + values[best]
    +        values[second_best] = "\\cellcolor{gray25} " + values[second_best]
    +
    +        output.write(
    +            "\\textbf{{{0}}} & ${1}$ \\\\\n".format(problem, " $ & $ ".join(
    +                [str(val).replace("e-", "e\makebox[0.1cm]{-}").replace("e+", "e\makebox[0.1cm]{+}") for val in values])
    +                                                    )
             )
     
    +    # Select all but the last counter
    +    counter_summary = []
    +    for algorithm in algorithms[:-1]:
    +        counter_summary.append(counters[algorithm])
    +
    +    output.write("  \\hline\n")
    +    output.write(
    +        "\\textbf{{{0}}} & ${1}$ \\\\\n".format("$+/\\approx/-$", " $ & $ ".join(
    +            [str(val[0]) + "/" + str(val[1]) + "/" + str(val[2]) for val in counter_summary])))
    +
    +    # Write footer
    +    output.write("  \\end{tabular}\n")
    +    output.write("  \\end{tiny}\n")
    +    output.write("\\end{table}\n")
    +
    +    output.write("\\end{document}")
    +
    +    return output.getvalue()
    +
    +
    +def __kolmogorov_smirnov_to_latex(indicator_name: str, test_data: pd.DataFrame, caption: str, label: str):
    +    indicator_data = test_data[test_data["Indicator"] == indicator_name]
    +
    +    problems = pd.unique(indicator_data["Problem"])
    +    algorithms = pd.unique(indicator_data["Algorithm"])
    +
    +    num_columns = len(algorithms)
    +    columns = algorithms
    +
    +    alignment = "c"
    +    col_format = "{}|{}".format(alignment, alignment * num_columns)
    +    column_labels = ["\\textbf{{{0}}}".format(label.replace("_", "\\_")) for label in columns]
    +
    +    output = io.StringIO()
    +
    +    output.write("\\documentclass{article}\n")
    +
    +    output.write("\\usepackage[utf8]{inputenc}\n")
    +    output.write("\\usepackage{tabularx}\n")
    +    output.write("\\usepackage{colortbl}\n")
    +    output.write("\\usepackage[table*]{xcolor}\n")
    +
    +    output.write("\\xdefinecolor{gray95}{gray}{0.65}\n")
    +    output.write("\\xdefinecolor{gray25}{gray}{0.8}\n")
    +
    +    output.write("\\title{Kolmogorov-Smirnov Test}\n")
    +    output.write("\\author{}\n")
    +
    +    output.write("\\begin{document}\n")
    +    output.write("\\maketitle\n")
    +
    +    output.write("\\section{Table}\n")
    +
    +    output.write("\\begin{table}[!htp]\n")
    +    output.write("  \\caption{{{}}}\n".format(caption))
    +    output.write("  \\label{{{}}}\n".format(label))
    +    output.write("  \\centering\n")
    +    output.write("  \\begin{tiny}\n")
    +    output.write("  \\begin{tabular}{%s}\n" % col_format)
    +    output.write("      & {} \\\\\\hline\n".format(" & ".join(column_labels)))
    +
    +    for problem in problems:
    +        values = []
    +
    +        for algorithm in algorithms[:-1]:
    +            row = indicator_data[(indicator_data["Problem"] == problem) & (indicator_data["Algorithm"] == algorithm)]
    +            value = "{:.2e}".format(row["PValue"].tolist()[0])
    +
    +            if (row["PValue"].tolist()[0] < 0.05):
    +                value = "\\cellcolor{gray25} " + value
    +
    +            values.append(value)
    +        values.append("-")
    +
    +        output.write(
    +            "\\textbf{{{0}}} & ${1}$ \\\\\n".format(problem, " $ & $ ".join(
    +                [str(val).replace("e-", "e\makebox[0.1cm]{-}").replace("e+", "e\makebox[0.1cm]{+}") for val in values])
    +                                                    )
    +        )
    +
    +    output.write("  \\hline\n")
    +
         # Write footer
    -    output.write('  \\end{tabular}\n')
    -    output.write('  \\end{scriptsize}\n')
    -    output.write('\\end{table}\n')
    +    output.write("  \\end{tabular}\n")
    +    output.write("  \\end{tiny}\n")
    +    output.write("\\end{table}\n")
     
    -    output.write('\\end{document}')
    +    output.write("\\end{document}")
     
         return output.getvalue()
     
     
     def check_minimization(indicator) -> bool:
    -    if indicator == 'HV':
    +    if indicator == "HV":
             return False
         else:
             return True
    @@ -670,8 +1055,9 @@ 

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.lab.statistical_test.apv_procedures — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -113,8 +110,10 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    import pandas as pd -
    [docs]def bonferroni_dunn(p_values, control): - """ +
    +[docs] +def bonferroni_dunn(p_values, control): + """ Bonferroni-Dunn's procedure for the adjusted p-value computation. Parameters: @@ -132,14 +131,12 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    algorithms = p_values.columns p_values = p_values.values elif type(p_values) == np.ndarray: - algorithms = np.array( - ['Alg%d' % alg for alg in range(p_values.shape[1])]) + algorithms = np.array(["Alg%d" % alg for alg in range(p_values.shape[1])]) if type(control) == str: control = int(np.where(algorithms == control)[0]) if control is None: - raise ValueError( - 'Initialization ERROR. Incorrect value for control.') + raise ValueError("Initialization ERROR. Incorrect value for control.") k = p_values.shape[1] @@ -149,14 +146,16 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    APVs = np.zeros((k - 1, 1)) comparison = [] for i in range(k - 1): - comparison.append(algorithms[control] + - ' vs ' + algorithms[argsorted_pvals[i]]) + comparison.append(algorithms[control] + " vs " + algorithms[argsorted_pvals[i]]) APVs[i, 0] = np.min([(k - 1) * p_values[0, argsorted_pvals[i]], 1]) - return pd.DataFrame(data=APVs, index=comparison, columns=['Bonferroni'])
    + return pd.DataFrame(data=APVs, index=comparison, columns=["Bonferroni"])
    + -
    [docs]def holland(p_values, control): - """ +
    +[docs] +def holland(p_values, control): + """ Holland's procedure for the adjusted p-value computation. Parameters: @@ -174,14 +173,12 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    algorithms = p_values.columns p_values = p_values.values elif type(p_values) == np.ndarray: - algorithms = np.array( - ['Alg%d' % alg for alg in range(p_values.shape[1])]) + algorithms = np.array(["Alg%d" % alg for alg in range(p_values.shape[1])]) if type(control) == str: control = int(np.where(algorithms == control)[0]) if control is None: - raise ValueError( - 'Initialization ERROR. Incorrect value for control.') + raise ValueError("Initialization ERROR. Incorrect value for control.") # -------------------------------------------------------------------------- # ------------------------------- Procedure -------------------------------- @@ -194,16 +191,18 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    APVs = np.zeros((k - 1, 1)) comparison = [] for i in range(k - 1): - comparison.append(algorithms[control] + - ' vs ' + algorithms[argsorted_pvals[i]]) + comparison.append(algorithms[control] + " vs " + algorithms[argsorted_pvals[i]]) aux = k - 1 - np.arange(i + 1) - v = np.max(1 - (1 - p_values[0, argsorted_pvals[:(i + 1)]]) ** aux) + v = np.max(1 - (1 - p_values[0, argsorted_pvals[: (i + 1)]]) ** aux) APVs[i, 0] = np.min([v, 1]) - return pd.DataFrame(data=APVs, index=comparison, columns=['Holland'])
    + return pd.DataFrame(data=APVs, index=comparison, columns=["Holland"])
    + -
    [docs]def finner(p_values, control): - """ +
    +[docs] +def finner(p_values, control): + """ Finner's procedure for the adjusted p-value computation. Parameters: @@ -221,14 +220,12 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    algorithms = p_values.columns p_values = p_values.values elif type(p_values) == np.ndarray: - algorithms = np.array( - ['Alg%d' % alg for alg in range(p_values.shape[1])]) + algorithms = np.array(["Alg%d" % alg for alg in range(p_values.shape[1])]) if type(control) == str: control = int(np.where(algorithms == control)[0]) if control is None: - raise ValueError( - 'Initialization ERROR. Incorrect value for control.') + raise ValueError("Initialization ERROR. Incorrect value for control.") k = p_values.shape[1] @@ -238,16 +235,18 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    APVs = np.zeros((k - 1, 1)) comparison = [] for i in range(k - 1): - comparison.append(algorithms[control] + - ' vs ' + algorithms[argsorted_pvals[i]]) + comparison.append(algorithms[control] + " vs " + algorithms[argsorted_pvals[i]]) aux = float(k - 1) / (np.arange(i + 1) + 1) - v = np.max(1 - (1 - p_values[0, argsorted_pvals[:(i + 1)]]) ** aux) + v = np.max(1 - (1 - p_values[0, argsorted_pvals[: (i + 1)]]) ** aux) APVs[i, 0] = np.min([v, 1]) - return pd.DataFrame(data=APVs, index=comparison, columns=['Finner'])
    + return pd.DataFrame(data=APVs, index=comparison, columns=["Finner"])
    -
    [docs]def hochberg(p_values, control): - """ + +
    +[docs] +def hochberg(p_values, control): + """ Hochberg's procedure for the adjusted p-value computation. Parameters: @@ -265,14 +264,12 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    algorithms = p_values.columns p_values = p_values.values elif type(p_values) == np.ndarray: - algorithms = np.array( - ['Alg%d' % alg for alg in range(p_values.shape[1])]) + algorithms = np.array(["Alg%d" % alg for alg in range(p_values.shape[1])]) if type(control) == str: control = int(np.where(algorithms == control)[0]) if control is None: - raise ValueError( - 'Initialization ERROR. Incorrect value for control.') + raise ValueError("Initialization ERROR. Incorrect value for control.") k = p_values.shape[1] @@ -282,16 +279,18 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    APVs = np.zeros((k - 1, 1)) comparison = [] for i in range(k - 1): - comparison.append(algorithms[control] + - ' vs ' + algorithms[argsorted_pvals[i]]) + comparison.append(algorithms[control] + " vs " + algorithms[argsorted_pvals[i]]) aux = np.arange(k, i, -1).astype(np.uint8) v = np.max(p_values[0, argsorted_pvals[aux - 1]] * (k - aux)) APVs[i, 0] = np.min([v, 1]) - return pd.DataFrame(data=APVs, index=comparison, columns=['Hochberg'])
    + return pd.DataFrame(data=APVs, index=comparison, columns=["Hochberg"])
    + -
    [docs]def li(p_values, control): - """ +
    +[docs] +def li(p_values, control): + """ Li's procedure for the adjusted p-value computation. Parameters: @@ -311,14 +310,12 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    algorithms = p_values.columns p_values = p_values.values elif type(p_values) == np.ndarray: - algorithms = np.array( - ['Alg%d' % alg for alg in range(p_values.shape[1])]) + algorithms = np.array(["Alg%d" % alg for alg in range(p_values.shape[1])]) if type(control) == str: control = int(np.where(algorithms == control)[0]) if control is None: - raise ValueError( - 'Initialization ERROR. Incorrect value for control.') + raise ValueError("Initialization ERROR. Incorrect value for control.") k = p_values.shape[1] @@ -328,15 +325,22 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    APVs = np.zeros((k - 1, 1)) comparison = [] for i in range(k - 1): - comparison.append(algorithms[control] + - ' vs ' + algorithms[argsorted_pvals[i]]) - APVs[i, 0] = np.min([p_values[0, argsorted_pvals[-2]], p_values[0, argsorted_pvals[i]] / ( - p_values[0, argsorted_pvals[i]] + 1 - p_values[0, argsorted_pvals[-2]])]) - return pd.DataFrame(data=APVs, index=comparison, columns=['Li'])
    - - -
    [docs]def holm(p_values, control=None): - """ + comparison.append(algorithms[control] + " vs " + algorithms[argsorted_pvals[i]]) + APVs[i, 0] = np.min( + [ + p_values[0, argsorted_pvals[-2]], + p_values[0, argsorted_pvals[i]] + / (p_values[0, argsorted_pvals[i]] + 1 - p_values[0, argsorted_pvals[-2]]), + ] + ) + return pd.DataFrame(data=APVs, index=comparison, columns=["Li"])
    + + + +
    +[docs] +def holm(p_values, control=None): + """ Holm's procedure for the adjusted p-value computation. Parameters: @@ -356,8 +360,7 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    algorithms = p_values.columns p_values = p_values.values elif type(p_values) == np.ndarray: - algorithms = np.array( - ['Alg%d' % alg for alg in range(p_values.shape[1])]) + algorithms = np.array(["Alg%d" % alg for alg in range(p_values.shape[1])]) if type(control) == str: control = int(np.where(algorithms == control)[0]) @@ -372,9 +375,8 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    comparison = [] for i in range(k - 1): aux = k - 1 - np.arange(i + 1) - comparison.append( - algorithms[control] + ' vs ' + algorithms[argsorted_pvals[i]]) - v = np.max(aux * p_values[0, argsorted_pvals[:(i + 1)]]) + comparison.append(algorithms[control] + " vs " + algorithms[argsorted_pvals[i]]) + v = np.max(aux * p_values[0, argsorted_pvals[: (i + 1)]]) APVs[i, 0] = np.min([v, 1]) elif control is None: @@ -392,14 +394,17 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    for i in range(m): row = pairs_index[0][pairs_sorted[i]] col = pairs_index[1][pairs_sorted[i]] - comparison.append(algorithms[row] + ' vs ' + algorithms[col]) - v = np.max(aux[:i + 1]) + comparison.append(algorithms[row] + " vs " + algorithms[col]) + v = np.max(aux[: i + 1]) APVs[i, 0] = np.min([v, 1]) - return pd.DataFrame(data=APVs, index=comparison, columns=['Holm'])
    + return pd.DataFrame(data=APVs, index=comparison, columns=["Holm"])
    + -
    [docs]def shaffer(p_values): - """ +
    +[docs] +def shaffer(p_values): + """ Shaffer's procedure for adjusted p_value ccmputation. Parameters: @@ -412,7 +417,7 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    """ def S(k): - """ + """ Computes the set of possible numbers of true hoypotheses. Parameters: @@ -431,8 +436,7 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    TrueHset = [0] if k > 1: for j in np.arange(k, 0, -1, dtype=int): - TrueHset = list(set(TrueHset) | set( - [binomial(j, 2) + x for x in S(k - j)])) + TrueHset = list(set(TrueHset) | set([binomial(j, 2) + x for x in S(k - j)])) return TrueHset # Initial Checking @@ -440,15 +444,12 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    algorithms = p_values.columns p_values = p_values.values elif type(p_values) == np.ndarray: - algorithms = np.array( - ['Alg%d' % alg for alg in range(p_values.shape[1])]) + algorithms = np.array(["Alg%d" % alg for alg in range(p_values.shape[1])]) if p_values.ndim != 2: - raise ValueError( - 'Initialization ERROR. Incorrect number of array dimensions.') + raise ValueError("Initialization ERROR. Incorrect number of array dimensions.") elif p_values.shape[0] != p_values.shape[1]: - raise ValueError( - 'Initialization ERROR. Incorrect number of array dimensions.') + raise ValueError("Initialization ERROR. Incorrect number of array dimensions.") # define parameters k = p_values.shape[0] @@ -467,19 +468,22 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    # Adjust p-values APVs = np.zeros((m, 1)) - aux = (pairs_pvals[pairs_sorted] * t) + aux = pairs_pvals[pairs_sorted] * t comparison = [] for i in range(m): row = pairs_index[0][pairs_sorted[i]] col = pairs_index[1][pairs_sorted[i]] - comparison.append(algorithms[row] + ' vs ' + algorithms[col]) - v = np.max(aux[:i + 1]) + comparison.append(algorithms[row] + " vs " + algorithms[col]) + v = np.max(aux[: i + 1]) APVs[i, 0] = np.min([v, 1]) - return pd.DataFrame(data=APVs, index=comparison, columns=['Shaffer'])
    + return pd.DataFrame(data=APVs, index=comparison, columns=["Shaffer"])
    -
    [docs]def nemenyi(p_values): - """ + +
    +[docs] +def nemenyi(p_values): + """ Nemenyi's procedure for adjusted p_value computation. Parameters: @@ -496,15 +500,12 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    algorithms = p_values.columns p_values = p_values.values elif type(p_values) == np.ndarray: - algorithms = np.array( - ['Alg%d' % alg for alg in range(p_values.shape[1])]) + algorithms = np.array(["Alg%d" % alg for alg in range(p_values.shape[1])]) if p_values.ndim != 2: - raise ValueError( - 'Initialization ERROR. Incorrect number of array dimensions.') + raise ValueError("Initialization ERROR. Incorrect number of array dimensions.") elif p_values.shape[0] != p_values.shape[1]: - raise ValueError( - 'Initialization ERROR. Incorrect number of array dimensions.') + raise ValueError("Initialization ERROR. Incorrect number of array dimensions.") # define parameters k = p_values.shape[0] @@ -521,9 +522,10 @@

    Source code for jmetal.lab.statistical_test.apv_procedures

    for i in range(m): row = pairs_index[0][pairs_sorted[i]] col = pairs_index[1][pairs_sorted[i]] - comparison.append(algorithms[row] + ' vs ' + algorithms[col]) + comparison.append(algorithms[row] + " vs " + algorithms[col]) APVs[i, 0] = np.min([pairs_pvals[pairs_sorted[i]] * m, 1]) - return pd.DataFrame(data=APVs, index=comparison, columns=['Nemenyi'])
    + return pd.DataFrame(data=APVs, index=comparison, columns=["Nemenyi"])
    +
    @@ -540,8 +542,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.lab.statistical_test.bayesian — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -113,9 +110,12 @@

    Source code for jmetal.lab.statistical_test.bayesian

    import pandas as pd -
    [docs]def bayesian_sign_test(data, rope_limits=[-0.01, 0.01], prior_strength=0.5, prior_place='rope', sample_size=50000, - return_sample=False): - """ Bayesian version of the sign test. +
    +[docs] +def bayesian_sign_test( + data, rope_limits=[-0.01, 0.01], prior_strength=0.5, prior_place="rope", sample_size=50000, return_sample=False +): + """Bayesian version of the sign test. :param data: An (n x 2) array or DataFrame contaning the results. In data, each column represents an algorithm and, and each row a problem. :param rope_limits: array_like. Default [-0.01, 0.01]. Limits of the practical equivalence. @@ -138,16 +138,13 @@

    Source code for jmetal.lab.statistical_test.bayesian

    sample1, sample2 = data[:, 0], data[:, 1] n = data.shape[0] else: - raise ValueError( - 'Initialization ERROR. Incorrect number of dimensions for axis 1') + raise ValueError("Initialization ERROR. Incorrect number of dimensions for axis 1") if prior_strength <= 0: - raise ValueError( - 'Initialization ERROR. prior_strength mustb be a positive float') + raise ValueError("Initialization ERROR. prior_strength mustb be a positive float") - if prior_place not in ['left', 'rope', 'right']: - raise ValueError( - 'Initialization ERROR. Incorrect value fro prior_place') + if prior_place not in ["left", "rope", "right"]: + raise ValueError("Initialization ERROR. Incorrect value fro prior_place") # Compute the differences Z = sample1 - sample2 @@ -164,7 +161,7 @@

    Source code for jmetal.lab.statistical_test.bayesian

    # Parameters of the Dirichlet distribution alpha = np.array([Nleft, Nequiv, Nright], dtype=float) + 1e-6 - alpha[['left', 'rope', 'right'].index(prior_place)] += prior_strength + alpha[["left", "rope", "right"].index(prior_place)] += prior_strength # Simulate dirichlet process Dprocess = np.random.dirichlet(alpha, sample_size) @@ -180,10 +177,13 @@

    Source code for jmetal.lab.statistical_test.bayesian

    return np.array([win_left, win_rope, win_rifht]) / float(sample_size)
    -
    [docs]def bayesian_signed_rank_test(data, rope_limits=[-0.01, 0.01], prior_strength=1.0, prior_place='rope', - sample_size=10000, - return_sample=False): - """ Bayesian version of the signed rank test. + +
    +[docs] +def bayesian_signed_rank_test( + data, rope_limits=[-0.01, 0.01], prior_strength=1.0, prior_place="rope", sample_size=10000, return_sample=False +): + """Bayesian version of the signed rank test. :param data: An (n x 2) array or DataFrame contaning the results. In data, each column represents an algorithm and, and each row a problem. :param rope_limits: array_like. Default [-0.01, 0.01]. Limits of the practical equivalence. @@ -209,21 +209,17 @@

    Source code for jmetal.lab.statistical_test.bayesian

    sample1, sample2 = data[:, 0], data[:, 1] n = data.shape[0] else: - raise ValueError( - 'Initialization ERROR. Incorrect number of dimensions for axis 1') + raise ValueError("Initialization ERROR. Incorrect number of dimensions for axis 1") if prior_strength <= 0: - raise ValueError( - 'Initialization ERROR. prior_strength must be a positive float') + raise ValueError("Initialization ERROR. prior_strength must be a positive float") - if prior_place not in ['left', 'rope', 'right']: - raise ValueError( - 'Initialization ERROR. Incorrect value for prior_place') + if prior_place not in ["left", "rope", "right"]: + raise ValueError("Initialization ERROR. Incorrect value for prior_place") # Compute the differences Z = sample1 - sample2 - Z0 = [-float('Inf'), 0.0, float('Inf')][['left', - 'rope', 'right'].index(prior_place)] + Z0 = [-float("Inf"), 0.0, float("Inf")][["left", "rope", "right"].index(prior_place)] Z = np.concatenate(([Z0], Z), axis=None) # compute the the probabilities that the mean difference of accuracy is in @@ -253,6 +249,7 @@

    Source code for jmetal.lab.statistical_test.bayesian

    return np.array([win_left, win_rope, win_rifht]) / float(sample_size), Dprocess else: return np.array([win_left, win_rope, win_rifht]) / float(sample_size)
    +
    @@ -269,8 +266,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.lab.statistical_test.critical_distance — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -117,8 +114,10 @@

    Source code for jmetal.lab.statistical_test.critical_distance

    from jmetal.lab.statistical_test.functions import ranks -
    [docs]def NemenyiCD(alpha: float, num_alg, num_dataset): - """ Computes Nemenyi's critical difference: +
    +[docs] +def NemenyiCD(alpha: float, num_alg, num_dataset): + """Computes Nemenyi's critical difference: * CD = q_alpha * sqrt(num_alg*(num_alg + 1)/(6*num_prob)) where q_alpha is the critical value, of the Studentized range statistic divided by sqrt(2). :param alpha: {0.1, 0.999}. Significance level. @@ -135,8 +134,17 @@

    Source code for jmetal.lab.statistical_test.critical_distance

    return cd
    -
    [docs]def CDplot(results, alpha: float = 0.05, higher_is_better: bool=False, alg_names: list = None, output_filename: str = 'cdplot.eps'): - """ CDgraph plots the critical difference graph show in Janez Demsar's 2006 work: + +
    +[docs] +def CDplot( + results, + alpha: float = 0.05, + higher_is_better: bool = False, + alg_names: list = None, + output_filename: str = "cdplot.eps", +): + """CDgraph plots the critical difference graph show in Janez Demsar's 2006 work: * Statistical Comparisons of Classifiers over Multiple Data Sets. :param results: A 2-D array containing results from each algorithm. Each row of 'results' represents an algorithm, and each column a dataset. :param alpha: {0.1, 0.999}. Significance level for the critical difference. @@ -144,15 +152,14 @@

    Source code for jmetal.lab.statistical_test.critical_distance

    """ def _join_alg(avranks, num_alg, cd): - """ + """ join_alg returns the set of non significant methods """ # get all pairs sets = (-1) * np.ones((num_alg, 2)) for i in range(num_alg): - elements = np.where(np.logical_and( - avranks - avranks[i] > 0, avranks - avranks[i] < cd))[0] + elements = np.where(np.logical_and(avranks - avranks[i] > 0, avranks - avranks[i] < cd))[0] if elements.size > 0: sets[i, :] = [avranks[i], avranks[elements[-1]]] sets = np.delete(sets, np.where(sets[:, 0] < 0)[0], axis=0) @@ -170,14 +177,12 @@

    Source code for jmetal.lab.statistical_test.critical_distance

    alg_names = results.index results = results.values elif type(results) == np.ndarray and alg_names is None: - alg_names = np.array( - ['Alg%d' % alg for alg in range(results.shape[1])]) + alg_names = np.array(["Alg%d" % alg for alg in range(results.shape[1])]) if results.ndim == 2: num_alg, num_dataset = results.shape else: - raise ValueError( - 'Initialization ERROR: In CDplot(...) results must be 2-D array') + raise ValueError("Initialization ERROR: In CDplot(...) results must be 2-D array") # Get the critical difference cd = NemenyiCD(alpha, num_alg, num_dataset) @@ -201,9 +206,9 @@

    Source code for jmetal.lab.statistical_test.critical_distance

    highest = np.ceil(np.max(avranks)).astype(np.uint8) # highest shown rank lowest = np.floor(np.min(avranks)).astype(np.uint8) # lowest shown rank width = 6 # default figure width (in inches) - height = (0.575 * (rows + 1)) # figure height + height = 0.575 * (rows + 1) # figure height - """ + """ FIGURE (1,0) +-----+---------------------------+-------+ @@ -229,60 +234,79 @@

    Source code for jmetal.lab.statistical_test.critical_distance

    lline = sright - sleft # Initialize figure - fig = plt.figure(figsize=(width, height), facecolor='white') + fig = plt.figure(figsize=(width, height), facecolor="white") ax = fig.add_axes([0, 0, 1, 1]) ax.set_xlim(0, 1) ax.set_ylim(0, 1) ax.set_axis_off() # Main horizontal axis - ax.hlines(stop, sleft, sright, color='black', linewidth=0.7) + ax.hlines(stop, sleft, sright, color="black", linewidth=0.7) for xi in range(highest - lowest + 1): # Plot mayor ticks - ax.vlines(x=sleft + (lline * xi) / (highest - lowest), - ymin=stop, ymax=stop + 0.05, color='black', linewidth=0.7) + ax.vlines( + x=sleft + (lline * xi) / (highest - lowest), ymin=stop, ymax=stop + 0.05, color="black", linewidth=0.7 + ) # Mayor ticks labels - ax.text(x=sleft + (lline * xi) / (highest - lowest), - y=stop + 0.06, - s=str(lowest + xi), ha='center', va='bottom') + ax.text( + x=sleft + (lline * xi) / (highest - lowest), y=stop + 0.06, s=str(lowest + xi), ha="center", va="bottom" + ) # Minor ticks if xi < highest - lowest: - ax.vlines(x=sleft + (lline * (xi + 0.5)) / (highest - lowest), - ymin=stop, ymax=stop + 0.025, color='black', linewidth=0.7) + ax.vlines( + x=sleft + (lline * (xi + 0.5)) / (highest - lowest), + ymin=stop, + ymax=stop + 0.025, + color="black", + linewidth=0.7, + ) # Plot lines/names for left models vspace = 0.5 * (stop - sbottom) / (spoint + 1) for i in range(spoint): - ax.vlines(x=sleft + (lline * (leftalg[i] - lowest)) / (highest - lowest), - ymin=sbottom + (spoint - 1 - i) * vspace, ymax=stop, color='black', linewidth=0.7) - ax.hlines(y=sbottom + (spoint - 1 - i) * vspace, xmin=sleft, - xmax=sleft + - (lline * (leftalg[i] - lowest)) / (highest - lowest), - color='black', linewidth=0.7) - ax.text(x=sleft - 0.01, y=sbottom + (spoint - 1 - i) * vspace, - s=alg_names[indices][i], ha='right', va='center') + ax.vlines( + x=sleft + (lline * (leftalg[i] - lowest)) / (highest - lowest), + ymin=sbottom + (spoint - 1 - i) * vspace, + ymax=stop, + color="black", + linewidth=0.7, + ) + ax.hlines( + y=sbottom + (spoint - 1 - i) * vspace, + xmin=sleft, + xmax=sleft + (lline * (leftalg[i] - lowest)) / (highest - lowest), + color="black", + linewidth=0.7, + ) + ax.text(x=sleft - 0.01, y=sbottom + (spoint - 1 - i) * vspace, s=alg_names[indices][i], ha="right", va="center") # Plot lines/names for right models vspace = 0.5 * (stop - sbottom) / (num_alg - spoint + 1) for i in range(num_alg - spoint): - ax.vlines(x=sleft + (lline * (rightalg[i] - lowest)) / (highest - lowest), - ymin=sbottom + i * vspace, ymax=stop, color='black', linewidth=0.7) - ax.hlines(y=sbottom + i * vspace, - xmin=sleft + - (lline * (rightalg[i] - lowest)) / (highest - lowest), - xmax=sright, color='black', linewidth=0.7) - ax.text(x=sright + 0.01, y=sbottom + i * vspace, - s=alg_names[indices][spoint + i], ha='left', va='center') + ax.vlines( + x=sleft + (lline * (rightalg[i] - lowest)) / (highest - lowest), + ymin=sbottom + i * vspace, + ymax=stop, + color="black", + linewidth=0.7, + ) + ax.hlines( + y=sbottom + i * vspace, + xmin=sleft + (lline * (rightalg[i] - lowest)) / (highest - lowest), + xmax=sright, + color="black", + linewidth=0.7, + ) + ax.text(x=sright + 0.01, y=sbottom + i * vspace, s=alg_names[indices][spoint + i], ha="left", va="center") # Plot critical difference rule if sleft + (cd * lline) / (highest - lowest) <= sright: - ax.hlines(y=stop + 0.2, xmin=sleft, xmax=sleft + - (cd * lline) / (highest - lowest), linewidth=1.5) - ax.text(x=sleft + 0.5 * (cd * lline) / - (highest - lowest), y=stop + 0.21, s='CD=%.3f' % cd, ha='center', va='bottom') + ax.hlines(y=stop + 0.2, xmin=sleft, xmax=sleft + (cd * lline) / (highest - lowest), linewidth=1.5) + ax.text( + x=sleft + 0.5 * (cd * lline) / (highest - lowest), y=stop + 0.21, s="CD=%.3f" % cd, ha="center", va="bottom" + ) else: - ax.text(x=(sleft + sright) / 2, y=stop + 0.2, s='CD=%.3f' % - cd, ha='center', va='bottom') + ax.text(x=(sleft + sright) / 2, y=stop + 0.2, s="CD=%.3f" % cd, ha="center", va="bottom") # Get pair of non-significant methods nonsig = _join_alg(avranks, num_alg, cd) @@ -291,32 +315,35 @@

    Source code for jmetal.lab.statistical_test.critical_distance

    left_lines = np.reshape(nonsig[0, :], (1, 2)) right_lines = np.reshape(nonsig[1, :], (1, 2)) else: - left_lines = nonsig[:np.round( - nonsig.shape[0] / 2.0).astype(np.uint8), :] - right_lines = nonsig[np.round( - nonsig.shape[0] / 2.0).astype(np.uint8):, :] + left_lines = nonsig[: np.round(nonsig.shape[0] / 2.0).astype(np.uint8), :] + right_lines = nonsig[np.round(nonsig.shape[0] / 2.0).astype(np.uint8) :, :] else: left_lines = np.reshape(nonsig, (1, nonsig.shape[0])) # plot from the left vspace = 0.5 * (stop - sbottom) / (left_lines.shape[0] + 1) for i in range(left_lines.shape[0]): - ax.hlines(y=stop - (i + 1) * vspace, - xmin=sleft + lline * (left_lines[i, 0] - - lowest - 0.025) / (highest - lowest), - xmax=sleft + lline * (left_lines[i, 1] - lowest + 0.025) / (highest - lowest), linewidth=2) + ax.hlines( + y=stop - (i + 1) * vspace, + xmin=sleft + lline * (left_lines[i, 0] - lowest - 0.025) / (highest - lowest), + xmax=sleft + lline * (left_lines[i, 1] - lowest + 0.025) / (highest - lowest), + linewidth=2, + ) # plot from the rigth if nonsig.ndim == 2: vspace = 0.5 * (stop - sbottom) / (left_lines.shape[0]) for i in range(right_lines.shape[0]): - ax.hlines(y=stop - (i + 1) * vspace, - xmin=sleft + lline * (right_lines[i, 0] - - lowest - 0.025) / (highest - lowest), - xmax=sleft + lline * (right_lines[i, 1] - lowest + 0.025) / (highest - lowest), linewidth=2) - - plt.savefig(output_filename, bbox_inches='tight') + ax.hlines( + y=stop - (i + 1) * vspace, + xmin=sleft + lline * (right_lines[i, 0] - lowest - 0.025) / (highest - lowest), + xmax=sleft + lline * (right_lines[i, 1] - lowest + 0.025) / (highest - lowest), + linewidth=2, + ) + + plt.savefig(output_filename, bbox_inches="tight") plt.show()
    +
    @@ -333,8 +360,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.lab.statistical_test.functions — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -109,13 +106,15 @@

    Table Of Contents

    Source code for jmetal.lab.statistical_test.functions

    -from scipy.stats import chi2, f, binom, norm
    +from scipy.stats import binom, chi2, f, norm
     
     from jmetal.lab.statistical_test.apv_procedures import *
     
     
    -
    [docs]def ranks(data: np.array, descending=False): - """ Computes the rank of the elements in data. +
    +[docs] +def ranks(data: np.array, descending=False): + """Computes the rank of the elements in data. :param data: 2-D matrix :param descending: boolean (default False). If true, rank is sorted in descending order. @@ -128,23 +127,30 @@

    Source code for jmetal.lab.statistical_test.functions

    ranks = np.ones(data.shape) for i in range(data.shape[0]): values, indices, rep = np.unique( - (-1) ** s * np.sort((-1) ** s * data[i, :]), return_index=True, return_counts=True, ) + (-1) ** s * np.sort((-1) ** s * data[i, :]), + return_index=True, + return_counts=True, + ) for j in range(data.shape[1]): - ranks[i, j] += indices[values == data[i, j]] + \ - 0.5 * (rep[values == data[i, j]] - 1) + ranks[i, j] += indices[values == data[i, j]] + 0.5 * (rep[values == data[i, j]] - 1) return ranks elif data.ndim == 1: ranks = np.ones((data.size,)) values, indices, rep = np.unique( - (-1) ** s * np.sort((-1) ** s * data), return_index=True, return_counts=True, ) + (-1) ** s * np.sort((-1) ** s * data), + return_index=True, + return_counts=True, + ) for i in range(data.size): - ranks[i] += indices[values == data[i]] + \ - 0.5 * (rep[values == data[i]] - 1) + ranks[i] += indices[values == data[i]] + 0.5 * (rep[values == data[i]] - 1) return ranks
    -
    [docs]def sign_test(data): - """ Given the results drawn from two algorithms/methods X and Y, the sign test analyses if + +
    +[docs] +def sign_test(data): + """Given the results drawn from two algorithms/methods X and Y, the sign test analyses if there is a difference between X and Y. .. note:: Null Hypothesis: Pr(X<Y)= 0.5 @@ -161,8 +167,7 @@

    Source code for jmetal.lab.statistical_test.functions

    X, Y = data[:, 0], data[:, 1] n_perf = data.shape[0] else: - raise ValueError( - 'Initialization ERROR. Incorrect number of dimensions for axis 1') + raise ValueError("Initialization ERROR. Incorrect number of dimensions for axis 1") # Compute the differences Z = X - Y @@ -178,12 +183,16 @@

    Source code for jmetal.lab.statistical_test.functions

    p_value = 2 * min([p_value_minus, p_value_plus]) - return pd.DataFrame(data=np.array([Wminus, Wplus, p_value]), index=['Num X<Y', 'Num X>Y', 'p-value'], - columns=['Results'])
    + return pd.DataFrame( + data=np.array([Wminus, Wplus, p_value]), index=["Num X<Y", "Num X>Y", "p-value"], columns=["Results"] + )
    + -
    [docs]def friedman_test(data): - """ Friedman ranking test. +
    +[docs] +def friedman_test(data): + """Friedman ranking test. ..note:: Null Hypothesis: In a set of k (>=2) treaments (or tested algorithms), all the treatments are equivalent, so their average ranks should be equal. @@ -199,11 +208,9 @@

    Source code for jmetal.lab.statistical_test.functions

    if data.ndim == 2: n_samples, k = data.shape else: - raise ValueError( - 'Initialization ERROR. Incorrect number of array dimensions') + raise ValueError("Initialization ERROR. Incorrect number of array dimensions") if k < 2: - raise ValueError( - 'Initialization Error. Incorrect number of dimensions for axis 1.') + raise ValueError("Initialization Error. Incorrect number of dimensions for axis 1.") # Compute ranks. datarank = ranks(data) @@ -212,18 +219,21 @@

    Source code for jmetal.lab.statistical_test.functions

    avranks = np.mean(datarank, axis=0) # Get Friedman statistics - friedman_stat = (12.0 * n_samples) / (k * (k + 1.0)) * \ - (np.sum(avranks ** 2) - (k * (k + 1) ** 2) / 4.0) + friedman_stat = (12.0 * n_samples) / (k * (k + 1.0)) * (np.sum(avranks**2) - (k * (k + 1) ** 2) / 4.0) # Compute p-value - p_value = (1.0 - chi2.cdf(friedman_stat, df=(k - 1))) + p_value = 1.0 - chi2.cdf(friedman_stat, df=(k - 1)) - return pd.DataFrame(data=np.array([friedman_stat, p_value]), index=['Friedman-statistic', 'p-value'], - columns=['Results'])
    + return pd.DataFrame( + data=np.array([friedman_stat, p_value]), index=["Friedman-statistic", "p-value"], columns=["Results"] + )
    -
    [docs]def friedman_aligned_rank_test(data): - """ Method of aligned ranks for the Friedman test. + +
    +[docs] +def friedman_aligned_rank_test(data): + """Method of aligned ranks for the Friedman test. ..note:: Null Hypothesis: In a set of k (>=2) treaments (or tested algorithms), all the treatments are equivalent, so their average ranks should be equal. @@ -239,11 +249,9 @@

    Source code for jmetal.lab.statistical_test.functions

    if data.ndim == 2: n_samples, k = data.shape else: - raise ValueError( - 'Initialization ERROR. Incorrect number of array dimensions') + raise ValueError("Initialization ERROR. Incorrect number of array dimensions") if k < 2: - raise ValueError( - 'Initialization Error. Incorrect number of dimensions for axis 1.') + raise ValueError("Initialization Error. Incorrect number of dimensions for axis 1.") # Compute the average value achieved by all algorithms in each problem control = np.mean(data, axis=1) @@ -251,27 +259,31 @@

    Source code for jmetal.lab.statistical_test.functions

    diff = [data[:, j] - control for j in range(data.shape[1])] # rank diff alignedRanks = ranks(np.ravel(diff)) - alignedRanks = np.reshape(alignedRanks, newshape=(n_samples, k), order='F') + alignedRanks = np.reshape(alignedRanks, newshape=(n_samples, k), order="F") # Compute statistic Rhat_i = np.sum(alignedRanks, axis=1) Rhat_j = np.sum(alignedRanks, axis=0) - si, sj = np.sum(Rhat_i ** 2), np.sum(Rhat_j ** 2) + si, sj = np.sum(Rhat_i**2), np.sum(Rhat_j**2) - A = sj - (k * n_samples ** 2 / 4.0) * (k * n_samples + 1) ** 2 - B1 = (k * n_samples * (k * n_samples + 1) * (2 * k * n_samples + 1) / 6.0) + A = sj - (k * n_samples**2 / 4.0) * (k * n_samples + 1) ** 2 + B1 = k * n_samples * (k * n_samples + 1) * (2 * k * n_samples + 1) / 6.0 B2 = si / float(k) alignedRanks_stat = ((k - 1) * A) / (B1 - B2) p_value = 1 - chi2.cdf(alignedRanks_stat, df=k - 1) - return pd.DataFrame(data=np.array([alignedRanks_stat, p_value]), index=['Aligned Rank stat', 'p-value'], - columns=['Results'])
    + return pd.DataFrame( + data=np.array([alignedRanks_stat, p_value]), index=["Aligned Rank stat", "p-value"], columns=["Results"] + )
    + -
    [docs]def quade_test(data): - """ Quade test. +
    +[docs] +def quade_test(data): + """Quade test. ..note:: Null Hypothesis: In a set of k (>=2) treaments (or tested algorithms), all the treatments are equivalent, so their average ranks should be equal. @@ -287,11 +299,9 @@

    Source code for jmetal.lab.statistical_test.functions

    if data.ndim == 2: n_samples, k = data.shape else: - raise ValueError( - 'Initialization ERROR. Incorrect number of array dimensions') + raise ValueError("Initialization ERROR. Incorrect number of array dimensions") if k < 2: - raise ValueError( - 'Initialization Error. Incorrect number of dimensions for axis 1.') + raise ValueError("Initialization Error. Incorrect number of dimensions for axis 1.") # Compute ranks. datarank = ranks(data) @@ -308,8 +318,8 @@

    Source code for jmetal.lab.statistical_test.functions

    Salg = np.sum(S_stat, axis=0) # Compute Fq (Quade Test statistic) and associated p_value - A = np.sum(S_stat ** 2) - B = np.sum(Salg ** 2) / float(n_samples) + A = np.sum(S_stat**2) + B = np.sum(Salg**2) / float(n_samples) if A == B: Fq = np.Inf @@ -318,11 +328,14 @@

    Source code for jmetal.lab.statistical_test.functions

    Fq = (n_samples - 1.0) * B / (A - B) p_value = 1 - f.cdf(Fq, k - 1, (k - 1) * (n_samples - 1)) - return pd.DataFrame(data=np.array([Fq, p_value]), index=['Quade Test statistic', 'p-value'], columns=['Results'])
    + return pd.DataFrame(data=np.array([Fq, p_value]), index=["Quade Test statistic", "p-value"], columns=["Results"])
    -
    [docs]def friedman_ph_test(data, control=None, apv_procedure=None): - """ Friedman post-hoc test. + +
    +[docs] +def friedman_ph_test(data, control=None, apv_procedure=None): + """Friedman post-hoc test. :param data: An (n x 2) array or DataFrame contaning the results. In data, each column represents an algorithm and, and each row a problem. :param control: optional int or string. Default None. Index or Name of the control algorithm. If control = None all FriedmanPosHocTest considers all possible comparisons among algorithms. @@ -344,7 +357,7 @@

    Source code for jmetal.lab.statistical_test.functions

    algorithms = data.columns data = data.values elif type(data) == np.ndarray: - algorithms = np.array(['Alg%d' % alg for alg in range(data.shape[1])]) + algorithms = np.array(["Alg%d" % alg for alg in range(data.shape[1])]) if control is None: index = algorithms @@ -356,24 +369,29 @@

    Source code for jmetal.lab.statistical_test.functions

    if data.ndim == 2: n_samples, k = data.shape else: - raise ValueError( - 'Initialization ERROR. Incorrect number of array dimensions.') + raise ValueError("Initialization ERROR. Incorrect number of array dimensions.") if k < 2: - raise ValueError( - 'Initialization Error. Incorrect number of dimensions for axis 1.') + raise ValueError("Initialization Error. Incorrect number of dimensions for axis 1.") if control is not None: if type(control) == int and control >= data.shape[1]: - raise ValueError('Initialization ERROR. control is out of bounds') + raise ValueError("Initialization ERROR. control is out of bounds") if type(control) == str and control not in algorithms: - raise ValueError( - 'Initialization ERROR. %s is not a column name of data' % control) + raise ValueError("Initialization ERROR. %s is not a column name of data" % control) if apv_procedure is not None: - if apv_procedure not in ['Bonferroni', 'Holm', 'Hochberg', 'Hommel', 'Holland', 'Finner', 'Li', 'Shaffer', - 'Nemenyi']: - raise ValueError( - 'Initialization ERROR. Incorrect value for APVprocedure.') + if apv_procedure not in [ + "Bonferroni", + "Holm", + "Hochberg", + "Hommel", + "Holland", + "Finner", + "Li", + "Shaffer", + "Nemenyi", + ]: + raise ValueError("Initialization ERROR. Incorrect value for APVprocedure.") # Compute ranks. datarank = ranks(data) @@ -405,28 +423,31 @@

    Source code for jmetal.lab.statistical_test.functions

    if apv_procedure is None: return zvalues_df, pvalues_df else: - if apv_procedure == 'Bonferroni': + if apv_procedure == "Bonferroni": ap_vs_df = bonferroni_dunn(pvalues_df, control=control) - elif apv_procedure == 'Holm': + elif apv_procedure == "Holm": ap_vs_df = holm(pvalues_df, control=control) - elif apv_procedure == 'Hochberg': + elif apv_procedure == "Hochberg": ap_vs_df = hochberg(pvalues_df, control=control) - elif apv_procedure == 'Holland': + elif apv_procedure == "Holland": ap_vs_df = holland(pvalues_df, control=control) - elif apv_procedure == 'Finner': + elif apv_procedure == "Finner": ap_vs_df = finner(pvalues_df, control=control) - elif apv_procedure == 'Li': + elif apv_procedure == "Li": ap_vs_df = li(pvalues_df, control=control) - elif apv_procedure == 'Shaffer': + elif apv_procedure == "Shaffer": ap_vs_df = shaffer(pvalues_df) - elif apv_procedure == 'Nemenyi': + elif apv_procedure == "Nemenyi": ap_vs_df = nemenyi(pvalues_df) return zvalues_df, pvalues_df, ap_vs_df
    -
    [docs]def friedman_aligned_ph_test(data, control=None, apv_procedure=None): - """ Friedman Aligned Ranks post-hoc test. + +
    +[docs] +def friedman_aligned_ph_test(data, control=None, apv_procedure=None): + """Friedman Aligned Ranks post-hoc test. :param data: An (n x 2) array or DataFrame contaning the results. In data, each column represents an algorithm and, and each row a problem. :param control: optional int or string. Default None. Index or Name of the control algorithm. If control = None all FriedmanPosHocTest considers all possible comparisons among algorithms. @@ -448,7 +469,7 @@

    Source code for jmetal.lab.statistical_test.functions

    algorithms = data.columns data = data.values elif type(data) == np.ndarray: - algorithms = np.array(['Alg%d' % alg for alg in range(data.shape[1])]) + algorithms = np.array(["Alg%d" % alg for alg in range(data.shape[1])]) if control is None: index = algorithms @@ -460,18 +481,15 @@

    Source code for jmetal.lab.statistical_test.functions

    if data.ndim == 2: n_samples, k = data.shape else: - raise ValueError( - 'Initialization ERROR. Incorrect number of array dimensions.') + raise ValueError("Initialization ERROR. Incorrect number of array dimensions.") if k < 2: - raise ValueError( - 'Initialization Error. Incorrect number of dimensions for axis 1.') + raise ValueError("Initialization Error. Incorrect number of dimensions for axis 1.") if control is not None: if type(control) == int and control >= data.shape[1]: - raise ValueError('Initialization ERROR. control is out of bounds') + raise ValueError("Initialization ERROR. control is out of bounds") if type(control) == str and control not in algorithms: - raise ValueError( - 'Initialization ERROR. %s is not a column name of data' % control) + raise ValueError("Initialization ERROR. %s is not a column name of data" % control) # Compute the average value achieved by all algorithms in each problem problemmean = np.mean(data, axis=1) @@ -510,28 +528,31 @@

    Source code for jmetal.lab.statistical_test.functions

    if apv_procedure is None: return zvalues_df, pvalues_df else: - if apv_procedure == 'Bonferroni': + if apv_procedure == "Bonferroni": ap_vs_df = bonferroni_dunn(pvalues_df, control=control) - elif apv_procedure == 'Holm': + elif apv_procedure == "Holm": ap_vs_df = holm(pvalues_df, control=control) - elif apv_procedure == 'Hochberg': + elif apv_procedure == "Hochberg": ap_vs_df = hochberg(pvalues_df, control=control) - elif apv_procedure == 'Holland': + elif apv_procedure == "Holland": ap_vs_df = holland(pvalues_df, control=control) - elif apv_procedure == 'Finner': + elif apv_procedure == "Finner": ap_vs_df = finner(pvalues_df, control=control) - elif apv_procedure == 'Li': + elif apv_procedure == "Li": ap_vs_df = li(pvalues_df, control=control) - elif apv_procedure == 'Shaffer': + elif apv_procedure == "Shaffer": ap_vs_df = shaffer(pvalues_df) - elif apv_procedure == 'Nemenyi': + elif apv_procedure == "Nemenyi": ap_vs_df = nemenyi(pvalues_df) return zvalues_df, pvalues_df, ap_vs_df
    -
    [docs]def quade_ph_test(data, control=None, apv_procedure=None): - """ Quade post-hoc test. + +
    +[docs] +def quade_ph_test(data, control=None, apv_procedure=None): + """Quade post-hoc test. :param data: An (n x 2) array or DataFrame contaning the results. In data, each column represents an algorithm and, and each row a problem. :param control: optional int or string. Default None. Index or Name of the control algorithm. If control = None all FriedmanPosHocTest considers all possible comparisons among algorithms. @@ -553,7 +574,7 @@

    Source code for jmetal.lab.statistical_test.functions

    algorithms = data.columns data = data.values elif type(data) == np.ndarray: - algorithms = np.array(['Alg%d' % alg for alg in range(data.shape[1])]) + algorithms = np.array(["Alg%d" % alg for alg in range(data.shape[1])]) if control is None: index = algorithms @@ -565,18 +586,15 @@

    Source code for jmetal.lab.statistical_test.functions

    if data.ndim == 2: n_samples, k = data.shape else: - raise ValueError( - 'Initialization ERROR. Incorrect number of array dimensions.') + raise ValueError("Initialization ERROR. Incorrect number of array dimensions.") if k < 2: - raise ValueError( - 'Initialization Error. Incorrect number of dimensions for axis 1.') + raise ValueError("Initialization Error. Incorrect number of dimensions for axis 1.") if control is not None: if type(control) == int and control >= data.shape[1]: - raise ValueError('Initialization ERROR. control is out of bounds') + raise ValueError("Initialization ERROR. control is out of bounds") if type(control) == str and control not in algorithms: - raise ValueError( - 'Initialization ERROR. %s is not a column name of data' % control) + raise ValueError("Initialization ERROR. %s is not a column name of data" % control) # Compute ranks. datarank = ranks(data) @@ -591,8 +609,7 @@

    Source code for jmetal.lab.statistical_test.functions

    W[i, :] = problemRank[i] * datarank[i, :] avranks = 2 * np.sum(W, axis=0) / (n_samples * (n_samples + 1)) # Compute test statistics - aux = 1.0 / np.sqrt(k * (k + 1) * (2 * n_samples + 1) * (k - 1) / - (18.0 * n_samples * (n_samples + 1))) + aux = 1.0 / np.sqrt(k * (k + 1) * (2 * n_samples + 1) * (k - 1) / (18.0 * n_samples * (n_samples + 1))) if control is None: z = np.zeros((k, k)) for i in range(k): @@ -615,24 +632,25 @@

    Source code for jmetal.lab.statistical_test.functions

    if apv_procedure is None: return zvalues_df, pvalues_df else: - if apv_procedure == 'Bonferroni': + if apv_procedure == "Bonferroni": ap_vs_df = bonferroni_dunn(pvalues_df, control=control) - elif apv_procedure == 'Holm': + elif apv_procedure == "Holm": ap_vs_df = holm(pvalues_df, control=control) - elif apv_procedure == 'Hochberg': + elif apv_procedure == "Hochberg": ap_vs_df = hochberg(pvalues_df, control=control) - elif apv_procedure == 'Holland': + elif apv_procedure == "Holland": ap_vs_df = holland(pvalues_df, control=control) - elif apv_procedure == 'Finner': + elif apv_procedure == "Finner": ap_vs_df = finner(pvalues_df, control=control) - elif apv_procedure == 'Li': + elif apv_procedure == "Li": ap_vs_df = li(pvalues_df, control=control) - elif apv_procedure == 'Shaffer': + elif apv_procedure == "Shaffer": ap_vs_df = shaffer(pvalues_df) - elif apv_procedure == 'Nemenyi': + elif apv_procedure == "Nemenyi": ap_vs_df = nemenyi(pvalues_df) return zvalues_df, pvalues_df, ap_vs_df
    +
    @@ -649,8 +667,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.lab.visualization.chord_plot — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -121,34 +118,50 @@

    Source code for jmetal.lab.visualization.chord_plot

    from jmetal.core.solution import FloatSolution -
    [docs]def polar_to_cartesian(r, theta): +
    +[docs] +def polar_to_cartesian(r, theta): return np.array([r * np.cos(theta), r * np.sin(theta)])
    -
    [docs]def draw_sector(start_angle=0, end_angle=60, radius=1.0, width=0.2, lw=2, ls='-', ax=None, fc=(1, 0, 0), ec=(0, 0, 0), - z_order=1): + +
    +[docs] +def draw_sector( + start_angle=0, end_angle=60, radius=1.0, width=0.2, lw=2, ls="-", ax=None, fc=(1, 0, 0), ec=(0, 0, 0), z_order=1 +): if start_angle > end_angle: start_angle, end_angle = end_angle, start_angle - start_angle *= np.pi / 180. - end_angle *= np.pi / 180. + start_angle *= np.pi / 180.0 + end_angle *= np.pi / 180.0 # https://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves - opt = 4. / 3. * np.tan((end_angle - start_angle) / 4.) * radius + opt = 4.0 / 3.0 * np.tan((end_angle - start_angle) / 4.0) * radius inner = radius * (1 - width) - vertsPath = [polar_to_cartesian(radius, start_angle), - polar_to_cartesian(radius, start_angle) + polar_to_cartesian(opt, start_angle + 0.5 * np.pi), - polar_to_cartesian(radius, end_angle) + polar_to_cartesian(opt, end_angle - 0.5 * np.pi), - polar_to_cartesian(radius, end_angle), - polar_to_cartesian(inner, end_angle), - polar_to_cartesian(inner, end_angle) + polar_to_cartesian(opt * (1 - width), end_angle - 0.5 * np.pi), - polar_to_cartesian(inner, start_angle) + polar_to_cartesian(opt * (1 - width), - start_angle + 0.5 * np.pi), - polar_to_cartesian(inner, start_angle), - polar_to_cartesian(radius, start_angle)] - - codesPaths = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.LINETO, Path.CURVE4, Path.CURVE4, - Path.CURVE4, Path.CLOSEPOLY] + vertsPath = [ + polar_to_cartesian(radius, start_angle), + polar_to_cartesian(radius, start_angle) + polar_to_cartesian(opt, start_angle + 0.5 * np.pi), + polar_to_cartesian(radius, end_angle) + polar_to_cartesian(opt, end_angle - 0.5 * np.pi), + polar_to_cartesian(radius, end_angle), + polar_to_cartesian(inner, end_angle), + polar_to_cartesian(inner, end_angle) + polar_to_cartesian(opt * (1 - width), end_angle - 0.5 * np.pi), + polar_to_cartesian(inner, start_angle) + polar_to_cartesian(opt * (1 - width), start_angle + 0.5 * np.pi), + polar_to_cartesian(inner, start_angle), + polar_to_cartesian(radius, start_angle), + ] + + codesPaths = [ + Path.MOVETO, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.LINETO, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CLOSEPOLY, + ] if ax is None: return vertsPath, codesPaths @@ -156,38 +169,67 @@

    Source code for jmetal.lab.visualization.chord_plot

    path = Path(vertsPath, codesPaths) patch = patches.PathPatch(path, facecolor=fc, edgecolor=ec, lw=lw, linestyle=ls, zorder=z_order) ax.add_patch(patch) - return (patch)
    - - -
    [docs]def draw_chord(start_angle1=0, end_angle1=60, start_angle2=180, end_angle2=240, radius=1.0, chord_width=0.7, ax=None, - color=(1, 0, 0), z_order=1): + return patch
    + + + +
    +[docs] +def draw_chord( + start_angle1=0, + end_angle1=60, + start_angle2=180, + end_angle2=240, + radius=1.0, + chord_width=0.7, + ax=None, + color=(1, 0, 0), + z_order=1, +): if start_angle1 > end_angle1: start_angle1, end_angle1 = end_angle1, start_angle1 if start_angle2 > end_angle2: start_angle2, end_angle2 = end_angle2, start_angle2 - start_angle1 *= np.pi / 180. - end_angle1 *= np.pi / 180. - start_angle2 *= np.pi / 180. - end_angle2 *= np.pi / 180. + start_angle1 *= np.pi / 180.0 + end_angle1 *= np.pi / 180.0 + start_angle2 *= np.pi / 180.0 + end_angle2 *= np.pi / 180.0 - optAngle1 = 4. / 3. * np.tan((end_angle1 - start_angle1) / 4.) * radius - optAngle2 = 4. / 3. * np.tan((end_angle2 - start_angle2) / 4.) * radius + optAngle1 = 4.0 / 3.0 * np.tan((end_angle1 - start_angle1) / 4.0) * radius + optAngle2 = 4.0 / 3.0 * np.tan((end_angle2 - start_angle2) / 4.0) * radius rchord = radius * (1 - chord_width) - vertsPath = [polar_to_cartesian(radius, start_angle1), - polar_to_cartesian(radius, start_angle1) + polar_to_cartesian(optAngle1, start_angle1 + 0.5 * np.pi), - polar_to_cartesian(radius, end_angle1) + polar_to_cartesian(optAngle1, end_angle1 - 0.5 * np.pi), - polar_to_cartesian(radius, end_angle1), - polar_to_cartesian(rchord, end_angle1), polar_to_cartesian(rchord, start_angle2), - polar_to_cartesian(radius, start_angle2), - polar_to_cartesian(radius, start_angle2) + polar_to_cartesian(optAngle2, start_angle2 + 0.5 * np.pi), - polar_to_cartesian(radius, end_angle2) + polar_to_cartesian(optAngle2, end_angle2 - 0.5 * np.pi), - polar_to_cartesian(radius, end_angle2), - polar_to_cartesian(rchord, end_angle2), polar_to_cartesian(rchord, start_angle1), - polar_to_cartesian(radius, start_angle1)] - - codesPath = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, - Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4] + vertsPath = [ + polar_to_cartesian(radius, start_angle1), + polar_to_cartesian(radius, start_angle1) + polar_to_cartesian(optAngle1, start_angle1 + 0.5 * np.pi), + polar_to_cartesian(radius, end_angle1) + polar_to_cartesian(optAngle1, end_angle1 - 0.5 * np.pi), + polar_to_cartesian(radius, end_angle1), + polar_to_cartesian(rchord, end_angle1), + polar_to_cartesian(rchord, start_angle2), + polar_to_cartesian(radius, start_angle2), + polar_to_cartesian(radius, start_angle2) + polar_to_cartesian(optAngle2, start_angle2 + 0.5 * np.pi), + polar_to_cartesian(radius, end_angle2) + polar_to_cartesian(optAngle2, end_angle2 - 0.5 * np.pi), + polar_to_cartesian(radius, end_angle2), + polar_to_cartesian(rchord, end_angle2), + polar_to_cartesian(rchord, start_angle1), + polar_to_cartesian(radius, start_angle1), + ] + + codesPath = [ + Path.MOVETO, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + ] if ax == None: return vertsPath, codesPath @@ -195,10 +237,13 @@

    Source code for jmetal.lab.visualization.chord_plot

    path = Path(vertsPath, codesPath) patch = patches.PathPatch(path, facecolor=color + (0.5,), edgecolor=color + (0.4,), lw=2, alpha=0.5) ax.add_patch(patch) - return (patch)
    + return patch
    + -
    [docs]def hover_over_bin(event, handle_tickers, handle_plots, colors, fig): +
    +[docs] +def hover_over_bin(event, handle_tickers, handle_plots, colors, fig): is_found = False for iobj in range(len(handle_tickers)): @@ -220,8 +265,17 @@

    Source code for jmetal.lab.visualization.chord_plot

    fig.canvas.draw_idle()
    -
    [docs]def chord_diagram(solutions: List[FloatSolution], nbins='auto', ax=None, obj_labels=None, - prop_labels=dict(fontsize=13, ha='center', va='center'), pad=6): + +
    +[docs] +def chord_diagram( + solutions: List[FloatSolution], + nbins="auto", + ax=None, + obj_labels=None, + prop_labels=dict(fontsize=13, ha="center", va="center"), + pad=6, +): points_matrix = np.array([s.objectives for s in solutions]) (NPOINTS, NOBJ) = np.shape(points_matrix) @@ -230,13 +284,13 @@

    Source code for jmetal.lab.visualization.chord_plot

    if ax is None: fig = plt.figure(figsize=(6, 6)) - ax = plt.axes([0, 0, 1, 1], aspect='equal') + ax = plt.axes([0, 0, 1, 1], aspect="equal") ax.set_xlim(-2.3, 2.3) ax.set_ylim(-2.3, 2.3) - ax.axis('off') + ax.axis("off") - y = np.array([1. / NOBJ] * NOBJ) * (360 - pad * NOBJ) + y = np.array([1.0 / NOBJ] * NOBJ) * (360 - pad * NOBJ) sector_angles = [] labels_pos_and_ros = [] @@ -256,9 +310,13 @@

    Source code for jmetal.lab.visualization.chord_plot

    angleText -= 270 labels_pos_and_ros.append( - tuple(polar_to_cartesian(1.0, 0.5 * (start_angle + end_angle) * np.pi / 180.)) + (angle_diff,) + - tuple(polar_to_cartesian(0.725, (start_angle - 2.5) * np.pi / 180.)) + (angleText,) + - tuple(polar_to_cartesian(0.85, (start_angle - 2.5) * np.pi / 180.)) + (angleText,)) + tuple(polar_to_cartesian(1.0, 0.5 * (start_angle + end_angle) * np.pi / 180.0)) + + (angle_diff,) + + tuple(polar_to_cartesian(0.725, (start_angle - 2.5) * np.pi / 180.0)) + + (angleText,) + + tuple(polar_to_cartesian(0.85, (start_angle - 2.5) * np.pi / 180.0)) + + (angleText,) + ) start_angle = end_angle + pad arc_points = [] @@ -275,15 +333,41 @@

    Source code for jmetal.lab.visualization.chord_plot

    handle_tickers = [] handle_plots = [] - for iobj in tqdm(range(NOBJ), ascii=True, desc='Chord diagram'): - draw_sector(start_angle=sector_angles[iobj][0], end_angle=sector_angles[iobj][1], radius=0.925, width=0.225, - ax=ax, - fc=(1, 1, 1, 0.0), ec=(0, 0, 0), lw=2, z_order=10) - draw_sector(start_angle=sector_angles[iobj][0], end_angle=sector_angles[iobj][1], radius=0.925, width=0.05, - ax=ax, - fc=colors[iobj], ec=(0, 0, 0), lw=2, z_order=10) - draw_sector(start_angle=sector_angles[iobj][0], end_angle=sector_angles[iobj][1], radius=0.7 + 0.15, width=0.0, - ax=ax, fc=colors[iobj], ec=colors[iobj], lw=2, ls=':', z_order=5) + for iobj in tqdm(range(NOBJ), ascii=True, desc="Chord diagram"): + draw_sector( + start_angle=sector_angles[iobj][0], + end_angle=sector_angles[iobj][1], + radius=0.925, + width=0.225, + ax=ax, + fc=(1, 1, 1, 0.0), + ec=(0, 0, 0), + lw=2, + z_order=10, + ) + draw_sector( + start_angle=sector_angles[iobj][0], + end_angle=sector_angles[iobj][1], + radius=0.925, + width=0.05, + ax=ax, + fc=colors[iobj], + ec=(0, 0, 0), + lw=2, + z_order=10, + ) + draw_sector( + start_angle=sector_angles[iobj][0], + end_angle=sector_angles[iobj][1], + radius=0.7 + 0.15, + width=0.0, + ax=ax, + fc=colors[iobj], + ec=colors[iobj], + lw=2, + ls=":", + z_order=5, + ) histValues, binsDim = np.histogram(points_matrix[:, iobj], bins=nbins) relativeHeightBinPre = 0.025 @@ -292,73 +376,126 @@

    Source code for jmetal.lab.visualization.chord_plot

    handle_plots.append([]) for indexBin in range(len(histValues)): - startAngleBin = sector_angles[iobj][0] + (sector_angles[iobj][1] - sector_angles[iobj][0]) * binsDim[ - indexBin] - endAngleBin = sector_angles[iobj][0] + (sector_angles[iobj][1] - sector_angles[iobj][0]) * binsDim[ - indexBin + 1] + startAngleBin = ( + sector_angles[iobj][0] + (sector_angles[iobj][1] - sector_angles[iobj][0]) * binsDim[indexBin] + ) + endAngleBin = ( + sector_angles[iobj][0] + (sector_angles[iobj][1] - sector_angles[iobj][0]) * binsDim[indexBin + 1] + ) relativeHeightBin = 0.15 * histValues[indexBin] / max(histValues) handle_tickers[-1].append( - draw_sector(start_angle=startAngleBin, end_angle=endAngleBin, radius=0.69, width=0.08, ax=ax, lw=1, - fc=(1, 1, 1), ec=(0, 0, 0))) + draw_sector( + start_angle=startAngleBin, + end_angle=endAngleBin, + radius=0.69, + width=0.08, + ax=ax, + lw=1, + fc=(1, 1, 1), + ec=(0, 0, 0), + ) + ) handle_plots[-1].append([]) if histValues[indexBin] > 0: - draw_sector(start_angle=startAngleBin, end_angle=endAngleBin, radius=0.7 + relativeHeightBin, width=0, - ax=ax, lw=1, fc=colors[iobj], ec=colors[iobj]) - plotPoint1 = polar_to_cartesian(0.7 + relativeHeightBinPre, startAngleBin * np.pi / 180.) - plotPoint2 = polar_to_cartesian(0.7 + relativeHeightBin, startAngleBin * np.pi / 180.) + draw_sector( + start_angle=startAngleBin, + end_angle=endAngleBin, + radius=0.7 + relativeHeightBin, + width=0, + ax=ax, + lw=1, + fc=colors[iobj], + ec=colors[iobj], + ) + plotPoint1 = polar_to_cartesian(0.7 + relativeHeightBinPre, startAngleBin * np.pi / 180.0) + plotPoint2 = polar_to_cartesian(0.7 + relativeHeightBin, startAngleBin * np.pi / 180.0) plt.plot([plotPoint1[0], plotPoint2[0]], [plotPoint1[1], plotPoint2[1]], c=colors[iobj], lw=1) relativeHeightBinPre = relativeHeightBin else: - plotPoint1 = polar_to_cartesian(0.7 + relativeHeightBinPre, startAngleBin * np.pi / 180.) - plotPoint2 = polar_to_cartesian(0.725 + relativeHeightBin, startAngleBin * np.pi / 180.) + plotPoint1 = polar_to_cartesian(0.7 + relativeHeightBinPre, startAngleBin * np.pi / 180.0) + plotPoint2 = polar_to_cartesian(0.725 + relativeHeightBin, startAngleBin * np.pi / 180.0) plt.plot([plotPoint1[0], plotPoint2[0]], [plotPoint1[1], plotPoint2[1]], c=colors[iobj], lw=1) relativeHeightBinPre = 0.025 if indexBin == len(histValues) - 1: - plotPoint1 = polar_to_cartesian(0.7 + relativeHeightBin, endAngleBin * np.pi / 180.) - plotPoint2 = polar_to_cartesian(0.725, endAngleBin * np.pi / 180.) + plotPoint1 = polar_to_cartesian(0.7 + relativeHeightBin, endAngleBin * np.pi / 180.0) + plotPoint2 = polar_to_cartesian(0.725, endAngleBin * np.pi / 180.0) plt.plot([plotPoint1[0], plotPoint2[0]], [plotPoint1[1], plotPoint2[1]], c=colors[iobj], lw=1) for ipoint in range(len(points_matrix)): - plotPoint1 = polar_to_cartesian(0.6, arc_points[ipoint][iobj][0] * np.pi / 180.) - plotPoint2 = polar_to_cartesian(0.6, arc_points[ipoint][iobj][0] * np.pi / 180.) - plt.plot([plotPoint1[0], plotPoint2[0]], [plotPoint1[1], plotPoint2[1]], marker='o', markersize=3, - c=colors[iobj], lw=2) - - if binsDim[indexBin] < points_matrix[ipoint, iobj] <= binsDim[ - indexBin + 1]: + plotPoint1 = polar_to_cartesian(0.6, arc_points[ipoint][iobj][0] * np.pi / 180.0) + plotPoint2 = polar_to_cartesian(0.6, arc_points[ipoint][iobj][0] * np.pi / 180.0) + plt.plot( + [plotPoint1[0], plotPoint2[0]], + [plotPoint1[1], plotPoint2[1]], + marker="o", + markersize=3, + c=colors[iobj], + lw=2, + ) + + if binsDim[indexBin] < points_matrix[ipoint, iobj] <= binsDim[indexBin + 1]: for jdim in range(NOBJ): if jdim >= 1: handle_plots[iobj][indexBin].append( - draw_chord(arc_points[ipoint][jdim - 1][0], arc_points[ipoint][jdim - 1][1], - arc_points[ipoint][jdim][0], arc_points[ipoint][jdim][1], radius=0.55, - color=colors[iobj], chord_width=1, ax=ax)) + draw_chord( + arc_points[ipoint][jdim - 1][0], + arc_points[ipoint][jdim - 1][1], + arc_points[ipoint][jdim][0], + arc_points[ipoint][jdim][1], + radius=0.55, + color=colors[iobj], + chord_width=1, + ax=ax, + ) + ) handle_plots[iobj][indexBin][-1].set_visible(False) handle_plots[iobj][indexBin].append( - draw_chord(arc_points[ipoint][-1][0], arc_points[ipoint][-1][1], arc_points[ipoint][0][0], - arc_points[ipoint][0][1], radius=0.55, color=colors[iobj], chord_width=1, ax=ax)) + draw_chord( + arc_points[ipoint][-1][0], + arc_points[ipoint][-1][1], + arc_points[ipoint][0][0], + arc_points[ipoint][0][1], + radius=0.55, + color=colors[iobj], + chord_width=1, + ax=ax, + ) + ) handle_plots[iobj][indexBin][-1].set_visible(False) if obj_labels is None: - obj_labels = ['$f_{' + str(i) + '}(\mathbf{x})$' for i in range(NOBJ)] + obj_labels = ["$f_{" + str(i) + "}(\mathbf{x})$" for i in range(NOBJ)] - prop_legend_bins = dict(fontsize=9, ha='center', va='center') + prop_legend_bins = dict(fontsize=9, ha="center", va="center") for i in range(NOBJ): - p0, p1 = polar_to_cartesian(0.975, sector_angles[i][0] * np.pi / 180.) - ax.text(p0, p1, '0', **prop_legend_bins) - p0, p1 = polar_to_cartesian(0.975, sector_angles[i][1] * np.pi / 180.) - ax.text(p0, p1, '1', **prop_legend_bins) - ax.text(labels_pos_and_ros[i][0], labels_pos_and_ros[i][1], obj_labels[i], rotation=labels_pos_and_ros[i][2], - **prop_labels) - ax.text(labels_pos_and_ros[i][3], labels_pos_and_ros[i][4], '0', **prop_legend_bins, color=colors[i]) - ax.text(labels_pos_and_ros[i][6], labels_pos_and_ros[i][7], str(max_hist_values[i]), **prop_legend_bins, - color=colors[i]) + p0, p1 = polar_to_cartesian(0.975, sector_angles[i][0] * np.pi / 180.0) + ax.text(p0, p1, "0", **prop_legend_bins) + p0, p1 = polar_to_cartesian(0.975, sector_angles[i][1] * np.pi / 180.0) + ax.text(p0, p1, "1", **prop_legend_bins) + ax.text( + labels_pos_and_ros[i][0], + labels_pos_and_ros[i][1], + obj_labels[i], + rotation=labels_pos_and_ros[i][2], + **prop_labels + ) + ax.text(labels_pos_and_ros[i][3], labels_pos_and_ros[i][4], "0", **prop_legend_bins, color=colors[i]) + ax.text( + labels_pos_and_ros[i][6], + labels_pos_and_ros[i][7], + str(max_hist_values[i]), + **prop_legend_bins, + color=colors[i] + ) plt.axis([-1.2, 1.2, -1.2, 1.2]) - fig.canvas.mpl_connect("motion_notify_event", - lambda event: hover_over_bin(event, handle_tickers, handle_plots, colors, fig)) + fig.canvas.mpl_connect( + "motion_notify_event", lambda event: hover_over_bin(event, handle_tickers, handle_plots, colors, fig) + ) plt.show()
    +
    @@ -375,8 +512,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.lab.visualization.interactive — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -110,7 +107,7 @@

    Table Of Contents

    Source code for jmetal.lab.visualization.interactive

     import logging
    -from typing import TypeVar, List
    +from typing import List, TypeVar
     
     import pandas as pd
     from plotly import graph_objs as go
    @@ -119,25 +116,30 @@ 

    Source code for jmetal.lab.visualization.interactive

    from jmetal.lab.visualization.plotting import Plot -LOGGER = logging.getLogger('jmetal') - -S = TypeVar('S') +logger = logging.getLogger(__name__) +S = TypeVar("S") -
    [docs]class InteractivePlot(Plot): - def __init__(self, - title: str = 'Pareto front approximation', - reference_front: List[S] = None, - reference_point: list = None, - axis_labels: list = None): +
    +[docs] +class InteractivePlot(Plot): + def __init__( + self, + title: str = "Pareto front approximation", + reference_front: List[S] = None, + reference_point: list = None, + axis_labels: list = None, + ): super(InteractivePlot, self).__init__(title, reference_front, reference_point, axis_labels) self.figure = None self.layout = None self.data = [] -
    [docs] def plot(self, front, label=None, normalize: bool = False, filename: str = None, format: str = 'HTML'): - """ Plot a front of solutions (2D, 3D or parallel coordinates). +
    +[docs] + def plot(self, front, label=None, normalize: bool = False, filename: str = None, format: str = "HTML"): + """Plot a front of solutions (2D, 3D or parallel coordinates). :param front: List of solutions. :param label: Front name. @@ -150,109 +152,127 @@

    Source code for jmetal.lab.visualization.interactive

    self.layout = go.Layout( margin=dict(l=80, r=80, b=80, t=150), height=800, - title='{}<br>{}'.format(self.plot_title, label[0]), + title="{}<br>{}".format(self.plot_title, label[0]), scene=dict( xaxis=dict(title=self.axis_labels[0:1][0] if self.axis_labels[0:1] else None), yaxis=dict(title=self.axis_labels[1:2][0] if self.axis_labels[1:2] else None), - zaxis=dict(title=self.axis_labels[2:3][0] if self.axis_labels[2:3] else None) + zaxis=dict(title=self.axis_labels[2:3][0] if self.axis_labels[2:3] else None), ), - hovermode='closest' + hovermode="closest", ) # If any reference front, plot if self.reference_front: points, _ = self.get_points(self.reference_front) - trace = self.__generate_trace(points=points, legend='Reference front', normalize=normalize, - color='black', size=2) + trace = self.__generate_trace( + points=points, legend="Reference front", normalize=normalize, color="black", size=2 + ) self.data.append(trace) # If any reference point, plot if self.reference_point: points = pd.DataFrame(self.reference_point) - trace = self.__generate_trace(points=points, legend='Reference point', color='red', size=8) + trace = self.__generate_trace(points=points, legend="Reference point", color="red", size=8) self.data.append(trace) # Get points and metadata points, _ = self.get_points(front) metadata = list(solution.__str__() for solution in front) - trace = self.__generate_trace(points=points, metadata=metadata, legend='Front approximation', - normalize=normalize) + trace = self.__generate_trace( + points=points, metadata=metadata, legend="Front approximation", normalize=normalize + ) self.data.append(trace) self.figure = go.Figure(data=self.data, layout=self.layout) # Plot the figure if filename: - if format == 'HTML': + if format == "HTML": self.export_to_html(filename) + logger.info("Figure {_filename} exported to HTML file") else: - pio.write_image(self.figure, filename + '.' + format)
    + _filename = filename + "." + format -
    [docs] def export_to_html(self, filename: str) -> str: - """ Export the graph to an interactive HTML (solutions can be selected to show some metadata). + pio.write_image(self.figure, _filename) + logger.info("Figure {_filename} saved to file")
    + + +
    +[docs] + def export_to_html(self, filename: str) -> str: + """Export the graph to an interactive HTML (solutions can be selected to show some metadata). :param filename: Output file name. - :return: Script as string. """ - html_string = ''' - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"/> - <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> - <script src="https://unpkg.com/sweetalert2@7.7.0/dist/sweetalert2.all.js"></script> - <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"> - </head> - <body> - ''' + self.export_to_div(filename=None, include_plotlyjs=False) + ''' - <script> - var myPlot = document.querySelectorAll('div')[0]; - myPlot.on('plotly_click', function(data){ - var pts = ''; - - for(var i=0; i < data.points.length; i++){ - pts = '(x, y) = ('+data.points[i].x +', '+ data.points[i].y.toPrecision(4)+')'; - cs = data.points[i].customdata - } - - if(typeof cs !== "undefined"){ - swal({ - title: 'Closest solution clicked:', - text: cs, - type: 'info', - position: 'bottom-end' - }) - } - }); - - window.onresize = function() { - Plotly.Plots.resize(myPlot); - }; - </script> - </body> - </html>''' - - with open(filename + '.html', 'w') as outf: + :return: Script as string.""" + html_string = ( + """ + <!DOCTYPE html> + <html> + <head> + <meta charset="utf-8"/> + <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> + <script src="https://unpkg.com/sweetalert2@7.7.0/dist/sweetalert2.all.js"></script> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"> + </head> + <body> + """ + + self.export_to_div(filename=None, include_plotlyjs=False) + + """ + <script> + var myPlot = document.querySelectorAll('div')[0]; + myPlot.on('plotly_click', function(data){ + var pts = ''; + + for(var i=0; i < data.points.length; i++){ + pts = '(x, y) = ('+data.points[i].x +', '+ data.points[i].y.toPrecision(4)+')'; + cs = data.points[i].customdata + } + + if(typeof cs !== "undefined"){ + swal({ + title: 'Closest solution clicked:', + text: cs, + type: 'info', + position: 'bottom-end' + }) + } + }); + + window.onresize = function() { + Plotly.Plots.resize(myPlot); + }; + </script> + </body> + </html>""" + ) + + with open(filename + ".html", "w") as outf: outf.write(html_string) return html_string
    -
    [docs] def export_to_div(self, filename=None, include_plotlyjs: bool = False) -> str: - """ Export as a `div` for embedding the graph in an HTML file. + +
    +[docs] + def export_to_div(self, filename=None, include_plotlyjs: bool = False) -> str: + """Export as a `div` for embedding the graph in an HTML file. :param filename: Output file name (if desired, default to None). :param include_plotlyjs: If True, include plot.ly JS script (default to False). :return: Script as string. """ - script = offline.plot(self.figure, output_type='div', include_plotlyjs=include_plotlyjs, show_link=False) + script = offline.plot(self.figure, output_type="div", include_plotlyjs=include_plotlyjs, show_link=False) if filename: - with open(filename + '.html', 'w') as outf: + with open(filename + ".html", "w") as outf: outf.write(script) return script
    - def __generate_trace(self, points: pd.DataFrame, legend: str, metadata: list = None, normalize: bool = False, - **kwargs): + + def __generate_trace( + self, points: pd.DataFrame, legend: str, metadata: list = None, normalize: bool = False, **kwargs + ): dimension = points.shape[1] # tweak points size for 3D plots @@ -265,54 +285,39 @@

    Source code for jmetal.lab.visualization.interactive

    points = (points - points.min()) / (points.max() - points.min()) marker = dict( - color='#236FA4', - size=marker_size, - symbol='circle', - line=dict( - color='#236FA4', - width=1 - ), - opacity=0.8 + color="#236FA4", size=marker_size, symbol="circle", line=dict(color="#236FA4", width=1), opacity=0.8 ) marker.update(**kwargs) if dimension == 2: trace = go.Scattergl( - x=points[0], - y=points[1], - mode='markers', - marker=marker, - name=legend, - customdata=metadata + x=points[0], y=points[1], mode="markers", marker=marker, name=legend, customdata=metadata ) elif dimension == 3: trace = go.Scatter3d( - x=points[0], - y=points[1], - z=points[2], - mode='markers', - marker=marker, - name=legend, - customdata=metadata + x=points[0], y=points[1], z=points[2], mode="markers", marker=marker, name=legend, customdata=metadata ) else: dimensions = list() for column in points: dimensions.append( - dict(range=[0, 1], - label=self.axis_labels[column:column + 1][0] if self.axis_labels[column:column + 1] else None, - values=points[column]) + dict( + range=[0, 1], + label=self.axis_labels[column : column + 1][0] + if self.axis_labels[column : column + 1] + else None, + values=points[column], + ) ) trace = go.Parcoords( - line=dict( - color='#236FA4' - ), + line=dict(color="#236FA4"), dimensions=dimensions, name=legend, ) return trace
    +
    @@ -329,8 +334,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.lab.visualization.plotting — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -110,26 +107,28 @@

    Table Of Contents

    Source code for jmetal.lab.visualization.plotting

     import logging
    -from typing import TypeVar, List, Tuple
    +from typing import List, Tuple, TypeVar
     
     import numpy as np
     import pandas as pd
     from matplotlib import pyplot as plt
    -from pandas import plotting
    -
    -LOGGER = logging.getLogger('jmetal')
     
    -S = TypeVar('S')
    +logger = logging.getLogger(__name__)
     
    +S = TypeVar("S")
     
    -
    [docs]class Plot: - def __init__(self, - title: str = 'Pareto front approximation', - reference_front: List[S] = None, - reference_point: list = None, - axis_labels: list = None): - """ +
    +[docs] +class Plot: + def __init__( + self, + title: str = "Pareto front approximation", + reference_front: List[S] = None, + reference_point: list = None, + axis_labels: list = None, + ): + """ :param title: Title of the graph. :param axis_labels: List of axis labels. :param reference_point: Reference point (e.g., [0.4, 1.2]). @@ -145,21 +144,26 @@

    Source code for jmetal.lab.visualization.plotting

    self.reference_front = reference_front self.dimension = None -
    [docs] @staticmethod +
    +[docs] + @staticmethod def get_points(solutions: List[S]) -> Tuple[pd.DataFrame, int]: - """ Get points for each solution of the front. + """Get points for each solution of the front. :param solutions: List of solutions. :return: Pandas dataframe with one column for each objective and one row for each solution. """ if solutions is None: - raise Exception('Front is none!') + raise Exception("Front is none!") points = pd.DataFrame(list(solution.objectives for solution in solutions)) return points, points.shape[1]
    -
    [docs] def plot(self, front, label='', normalize: bool = False, filename: str = None, format: str = 'eps'): - """ Plot any arbitrary number of fronts in 2D, 3D or p-coords. + +
    +[docs] + def plot(self, front, label="", normalize: bool = False, filename: str = None, format: str = "eps"): + """Plot any arbitrary number of fronts in 2D, 3D or p-coords. :param front: Pareto front or a list of them. :param label: Pareto front title or a list of them. @@ -174,9 +178,9 @@

    Source code for jmetal.lab.visualization.plotting

    label = [label] if len(front) != len(label): - raise Exception('Number of fronts and labels must be the same') + raise Exception("Number of fronts and labels must be the same") - dimension = front[0][0].number_of_objectives + dimension = len(front[0][0].objectives) if dimension == 2: self.two_dim(front, label, filename, format) @@ -185,8 +189,11 @@

    Source code for jmetal.lab.visualization.plotting

    else: self.pcoords(front, normalize, filename, format)
    -
    [docs] def two_dim(self, fronts: List[list], labels: List[str] = None, filename: str = None, format: str = 'eps'): - """ Plot any arbitrary number of fronts in 2D. + +
    +[docs] + def two_dim(self, fronts: List[list], labels: List[str] = None, filename: str = None, format: str = "eps"): + """Plot any arbitrary number of fronts in 2D. :param fronts: List of fronts (containing solutions). :param labels: List of fronts title (if any). @@ -204,33 +211,39 @@

    Source code for jmetal.lab.visualization.plotting

    points, _ = self.get_points(fronts[i]) ax = fig.add_subplot(n, n, i + 1) - points.plot(kind='scatter', x=0, y=1, ax=ax, s=10, color='#236FA4', alpha=1.0) + points.plot(kind="scatter", x=0, y=1, ax=ax, s=10, color="#236FA4", alpha=1.0) if labels: ax.set_title(labels[i]) if self.reference_front: - reference.plot(x=0, y=1, ax=ax, color='k', legend=False) + reference.plot(x=0, y=1, ax=ax, color="k", legend=False) if self.reference_point: for point in self.reference_point: - plt.plot([point[0]], [point[1]], marker='o', markersize=5, color='r') - plt.axvline(x=point[0], color='r', linestyle=':') - plt.axhline(y=point[1], color='r', linestyle=':') + plt.plot([point[0]], [point[1]], marker="o", markersize=5, color="r") + plt.axvline(x=point[0], color="r", linestyle=":") + plt.axhline(y=point[1], color="r", linestyle=":") if self.axis_labels: plt.xlabel(self.axis_labels[0]) plt.ylabel(self.axis_labels[1]) if filename: - plt.savefig(filename + '.' + format, format=format, dpi=200) + _filename = filename + "." + format + + plt.savefig(_filename, format=format, dpi=1000) + logger.info("Figure {_filename} saved to file") else: plt.show() plt.close(fig=fig)
    -
    [docs] def three_dim(self, fronts: List[list], labels: List[str] = None, filename: str = None, format: str = 'eps'): - """ Plot any arbitrary number of fronts in 3D. + +
    +[docs] + def three_dim(self, fronts: List[list], labels: List[str] = None, filename: str = None, format: str = "eps"): + """Plot any arbitrary number of fronts in 3D. :param fronts: List of fronts (containing solutions). :param labels: List of fronts title (if any). @@ -241,18 +254,22 @@

    Source code for jmetal.lab.visualization.plotting

    fig.suptitle(self.plot_title, fontsize=16) for i, _ in enumerate(fronts): - ax = fig.add_subplot(n, n, i + 1, projection='3d') - ax.scatter([s.objectives[0] for s in fronts[i]], - [s.objectives[1] for s in fronts[i]], - [s.objectives[2] for s in fronts[i]]) + ax = fig.add_subplot(n, n, i + 1, projection="3d") + ax.scatter( + [s.objectives[0] for s in fronts[i]], + [s.objectives[1] for s in fronts[i]], + [s.objectives[2] for s in fronts[i]], + ) if labels: ax.set_title(labels[i]) if self.reference_front: - ax.scatter([s.objectives[0] for s in self.reference_front], - [s.objectives[1] for s in self.reference_front], - [s.objectives[2] for s in self.reference_front]) + ax.scatter( + [s.objectives[0] for s in self.reference_front], + [s.objectives[1] for s in self.reference_front], + [s.objectives[2] for s in self.reference_front], + ) if self.reference_point: # todo @@ -264,14 +281,20 @@

    Source code for jmetal.lab.visualization.plotting

    ax.locator_params(nbins=4) if filename: - plt.savefig(filename + '.' + format, format=format, dpi=1000) + _filename = filename + "." + format + + plt.savefig(_filename, format=format, dpi=1000) + logger.info("Figure {_filename} saved to file") else: plt.show() plt.close(fig=fig)
    -
    [docs] def pcoords(self, fronts: List[list], normalize: bool = False, filename: str = None, format: str = 'eps'): - """ Plot any arbitrary number of fronts in parallel coordinates. + +
    +[docs] + def pcoords(self, fronts: List[list], normalize: bool = False, filename: str = None, format: str = "eps"): + """Plot any arbitrary number of fronts in parallel coordinates. :param fronts: List of fronts (containing solutions). :param filename: Output filename. @@ -289,8 +312,8 @@

    Source code for jmetal.lab.visualization.plotting

    ax = fig.add_subplot(n, n, i + 1) min_, max_ = points.values.min(), points.values.max() - points['scale'] = np.linspace(0, 1, len(points)) * (max_ - min_) + min_ - pd.plotting.parallel_coordinates(points, 'scale', ax=ax) + points["scale"] = np.linspace(0, 1, len(points)) * (max_ - min_) + min_ + pd.plotting.parallel_coordinates(points, "scale", ax=ax) ax.get_legend().remove() @@ -298,11 +321,13 @@

    Source code for jmetal.lab.visualization.plotting

    ax.set_xticklabels(self.axis_labels) if filename: - plt.savefig(filename + '.' + format, format=format, dpi=1000) + plt.savefig(filename + "." + format, format=format, dpi=1000) else: plt.show() - plt.close(fig=fig)
    + plt.close(fig=fig)
    +
    +
    @@ -319,8 +344,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.lab.visualization.posterior — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -109,14 +106,25 @@

    Table Of Contents

    Source code for jmetal.lab.visualization.posterior

    -import numpy as np
    +import logging
    +
    +import numpy as np
     import pandas as pd
     from matplotlib import pyplot as plt
     
    +logger = logging.getLogger(__name__)
     
    -
    [docs]def plot_posterior(sample, higher_is_better: bool = False, min_points_per_hexbin: int = 2, alg_names: list = None, - filename: str = 'posterior.eps'): - """ + +
    +[docs] +def plot_posterior( + sample, + higher_is_better: bool = False, + min_points_per_hexbin: int = 2, + alg_names: list = None, + filename: str = "posterior.eps", +): + """ Plots the sample from posterior distribution of a Bayesian statistical test. Parameters: ----------- @@ -135,20 +143,18 @@

    Source code for jmetal.lab.visualization.posterior

    if sample.ndim == 2: nrow, ncol = sample.shape if ncol != 3: - raise ValueError( - 'Initialization ERROR. Incorrect number of dimensions in axis 1.') + raise ValueError("Initialization ERROR. Incorrect number of dimensions in axis 1.") else: - raise ValueError( - 'Initialization ERROR. Incorrect number of dimensions for sample') + raise ValueError("Initialization ERROR. Incorrect number of dimensions for sample") def transform(p): lambda1, lambda2, lambda3 = p.T - x = (0.1 * lambda1 + 0.5 * lambda2 + 0.9 * lambda3) + x = 0.1 * lambda1 + 0.5 * lambda2 + 0.9 * lambda3 y = (0.2 * lambda1 + 1.4 * lambda2 + 0.2 * lambda3) / np.sqrt(3) return np.vstack((x, y)).T # Initialize figure - fig = plt.figure(figsize=(5, 5), facecolor='white') + fig = plt.figure(figsize=(5, 5), facecolor="white") ax = fig.add_axes([0, 0, 1, 1]) ax.set_xlim(0, 1) ax.set_ylim(0, 1) @@ -158,34 +164,22 @@

    Source code for jmetal.lab.visualization.posterior

    if not higher_is_better: if not alg_names: - ax.text(x=0.5, y=1.4 / np.sqrt(3) + 0.005, - s='P(rope)', ha='center', va='bottom') - ax.text(x=0.15, y=0.175 / np.sqrt(3) - 0.005, - s='P(alg1<alg2)', ha='right', va='top') - ax.text(x=0.85, y=0.175 / np.sqrt(3) - 0.005, - s='P(alg1>alg2)', ha='left', va='top') + ax.text(x=0.5, y=1.4 / np.sqrt(3) + 0.005, s="P(rope)", ha="center", va="bottom") + ax.text(x=0.15, y=0.175 / np.sqrt(3) - 0.005, s="P(alg1<alg2)", ha="right", va="top") + ax.text(x=0.85, y=0.175 / np.sqrt(3) - 0.005, s="P(alg1>alg2)", ha="left", va="top") else: - ax.text(x=0.5, y=1.4 / np.sqrt(3) + 0.005, - s='P(rope)', ha='center', va='bottom') - ax.text(x=0.15, y=0.175 / np.sqrt(3) - 0.005, - s='P(' + alg_names[0] + ')', ha='right', va='top') - ax.text(x=0.85, y=0.175 / np.sqrt(3) - 0.005, - s='P(' + alg_names[1] + ')', ha='left', va='top') + ax.text(x=0.5, y=1.4 / np.sqrt(3) + 0.005, s="P(rope)", ha="center", va="bottom") + ax.text(x=0.15, y=0.175 / np.sqrt(3) - 0.005, s="P(" + alg_names[0] + ")", ha="right", va="top") + ax.text(x=0.85, y=0.175 / np.sqrt(3) - 0.005, s="P(" + alg_names[1] + ")", ha="left", va="top") else: if not alg_names: - ax.text(x=0.5, y=1.4 / np.sqrt(3) + 0.005, - s='P(rope)', ha='center', va='bottom') - ax.text(x=0.15, y=0.175 / np.sqrt(3) - 0.005, - s='P(alg2<alg1)', ha='right', va='top') - ax.text(x=0.85, y=0.175 / np.sqrt(3) - 0.005, - s='P(alg2>alg1)', ha='left', va='top') + ax.text(x=0.5, y=1.4 / np.sqrt(3) + 0.005, s="P(rope)", ha="center", va="bottom") + ax.text(x=0.15, y=0.175 / np.sqrt(3) - 0.005, s="P(alg2<alg1)", ha="right", va="top") + ax.text(x=0.85, y=0.175 / np.sqrt(3) - 0.005, s="P(alg2>alg1)", ha="left", va="top") else: - ax.text(x=0.5, y=1.4 / np.sqrt(3) + 0.005, - s='P(rope)', ha='center', va='bottom') - ax.text(x=0.15, y=0.175 / np.sqrt(3) - 0.005, - s='P(' + alg_names[1] + ')', ha='right', va='top') - ax.text(x=0.85, y=0.175 / np.sqrt(3) - 0.005, - s='P(' + alg_names[0] + ')', ha='left', va='top') + ax.text(x=0.5, y=1.4 / np.sqrt(3) + 0.005, s="P(rope)", ha="center", va="bottom") + ax.text(x=0.15, y=0.175 / np.sqrt(3) - 0.005, s="P(" + alg_names[1] + ")", ha="right", va="top") + ax.text(x=0.85, y=0.175 / np.sqrt(3) - 0.005, s="P(" + alg_names[0] + ")", ha="left", va="top") # Conversion between barycentric and Cartesian coordinates sample2d = np.zeros((sample.shape[0], 2)) @@ -197,32 +191,25 @@

    Source code for jmetal.lab.visualization.posterior

    # Plot triangle - ax.plot([0.095, 0.505], [0.2 / np.sqrt(3), 1.4 / np.sqrt(3)], - linewidth=3.0, color='white') - ax.plot([0.505, 0.905], [1.4 / np.sqrt(3), 0.2 / np.sqrt(3)], - linewidth=3.0, color='white') - ax.plot([0.09, 0.905], [0.2 / np.sqrt(3), 0.2 / np.sqrt(3)], - linewidth=3.0, color='white') + ax.plot([0.095, 0.505], [0.2 / np.sqrt(3), 1.4 / np.sqrt(3)], linewidth=3.0, color="white") + ax.plot([0.505, 0.905], [1.4 / np.sqrt(3), 0.2 / np.sqrt(3)], linewidth=3.0, color="white") + ax.plot([0.09, 0.905], [0.2 / np.sqrt(3), 0.2 / np.sqrt(3)], linewidth=3.0, color="white") - ax.plot([0.1, 0.5], [0.2 / np.sqrt(3), 1.4 / np.sqrt(3)], - linewidth=3.0, color='gray') - ax.plot([0.5, 0.9], [1.4 / np.sqrt(3), 0.2 / np.sqrt(3)], - linewidth=3.0, color='gray') - ax.plot([0.1, 0.9], [0.2 / np.sqrt(3), 0.2 / np.sqrt(3)], - linewidth=3.0, color='gray') + ax.plot([0.1, 0.5], [0.2 / np.sqrt(3), 1.4 / np.sqrt(3)], linewidth=3.0, color="gray") + ax.plot([0.5, 0.9], [1.4 / np.sqrt(3), 0.2 / np.sqrt(3)], linewidth=3.0, color="gray") + ax.plot([0.1, 0.9], [0.2 / np.sqrt(3), 0.2 / np.sqrt(3)], linewidth=3.0, color="gray") # plot division lines - ax.plot([0.5, 0.5], [0.2 / np.sqrt(3), 0.6 / np.sqrt(3)], - linewidth=3.0, color='gray') - ax.plot([0.3, 0.5], [0.8 / np.sqrt(3), 0.6 / np.sqrt(3)], - linewidth=3.0, color='gray') - ax.plot([0.5, 0.7], [0.6 / np.sqrt(3), 0.8 / np.sqrt(3)], - linewidth=3.0, color='gray') + ax.plot([0.5, 0.5], [0.2 / np.sqrt(3), 0.6 / np.sqrt(3)], linewidth=3.0, color="gray") + ax.plot([0.3, 0.5], [0.8 / np.sqrt(3), 0.6 / np.sqrt(3)], linewidth=3.0, color="gray") + ax.plot([0.5, 0.7], [0.6 / np.sqrt(3), 0.8 / np.sqrt(3)], linewidth=3.0, color="gray") if filename: - plt.savefig(filename, bbox_inches='tight') + plt.savefig(filename, bbox_inches="tight") + logger.info("Figure {filename} saved to file") plt.show()
    +
    @@ -239,8 +226,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.lab.visualization.streaming — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -110,7 +107,7 @@

    Table Of Contents

    Source code for jmetal.lab.visualization.streaming

     import logging
    -from typing import TypeVar, List
    +from typing import List, TypeVar
     
     import matplotlib
     from matplotlib import pyplot as plt
    @@ -118,9 +115,9 @@ 

    Source code for jmetal.lab.visualization.streaming

    from jmetal.lab.visualization.plotting import Plot -LOGGER = logging.getLogger('jmetal') +logger = logging.getLogger(__name__) -S = TypeVar('S') +S = TypeVar("S") """ .. module:: streaming @@ -131,14 +128,17 @@

    Source code for jmetal.lab.visualization.streaming

    """ -
    [docs]class StreamingPlot: - - def __init__(self, - plot_title: str = 'Pareto front approximation', - reference_front: List[S] = None, - reference_point: list = None, - axis_labels: list = None): - """ +
    +[docs] +class StreamingPlot: + def __init__( + self, + plot_title: str = "Pareto front approximation", + reference_front: List[S] = None, + reference_point: list = None, + axis_labels: list = None, + ): + """ :param plot_title: Title of the graph. :param axis_labels: List of axis labels. :param reference_point: Reference point (e.g., [0.4, 1.2]). @@ -155,13 +155,16 @@

    Source code for jmetal.lab.visualization.streaming

    self.dimension = None import warnings + warnings.filterwarnings("ignore", ".*GUI is implemented.*") self.fig, self.ax = plt.subplots() self.sc = None self.axis = None -
    [docs] def plot(self, front): +
    +[docs] + def plot(self, front): # Get data points, dimension = Plot.get_points(front) @@ -171,24 +174,33 @@

    Source code for jmetal.lab.visualization.streaming

    # If any reference point, plot if self.reference_point: for point in self.reference_point: - self.scp, = self.ax.plot(*[[p] for p in point], c='r', ls='None', marker='*', markersize=3) + (self.scp,) = self.ax.plot(*[[p] for p in point], c="r", ls="None", marker="*", markersize=3) # If any reference front, plot if self.reference_front: rpoints, _ = Plot.get_points(self.reference_front) - self.scf, = self.ax.plot(*[rpoints[column].tolist() for column in rpoints.columns.values], - c='k', ls='None', marker='*', markersize=1) + (self.scf,) = self.ax.plot( + *[rpoints[column].tolist() for column in rpoints.columns.values], + c="k", + ls="None", + marker="*", + markersize=1 + ) # Plot data - self.sc, = self.ax.plot(*[points[column].tolist() for column in points.columns.values], - ls='None', marker='o', markersize=4) + (self.sc,) = self.ax.plot( + *[points[column].tolist() for column in points.columns.values], ls="None", marker="o", markersize=4 + ) # Show plot plt.show(block=False)
    -
    [docs] def update(self, front: List[S], reference_point: list = None) -> None: + +
    +[docs] + def update(self, front: List[S], reference_point: list = None) -> None: if self.sc is None: - raise Exception('Figure is none') + raise Exception("Figure is none") points, dimension = Plot.get_points(front) @@ -214,31 +226,38 @@

    Source code for jmetal.lab.visualization.streaming

    pause(0.01)
    -
    [docs] def create_layout(self, dimension: int) -> None: - self.fig.canvas.set_window_title(self.plot_title) + +
    +[docs] + def create_layout(self, dimension: int) -> None: + logger.info("Creating figure layout") + + self.fig.canvas.manager.set_window_title(self.plot_title) self.fig.suptitle(self.plot_title, fontsize=16) if dimension == 2: # Stylize axis - self.ax.spines['top'].set_visible(False) - self.ax.spines['right'].set_visible(False) + self.ax.spines["top"].set_visible(False) + self.ax.spines["right"].set_visible(False) self.ax.get_xaxis().tick_bottom() self.ax.get_yaxis().tick_left() elif dimension == 3: self.ax = Axes3D(self.fig) - self.ax.autoscale(enable=True, axis='both') + self.ax.autoscale(enable=True, axis="both") else: - raise Exception('Dimension must be either 2 or 3') + raise Exception("Dimension must be either 2 or 3") self.ax.set_autoscale_on(True) self.ax.autoscale_view(True, True, True) # Style options - self.ax.grid(color='#f0f0f5', linestyle='-', linewidth=0.5, alpha=0.5)
    + self.ax.grid(color="#f0f0f5", linestyle="-", linewidth=0.5, alpha=0.5)
    +
    + def pause(interval: float): - backend = plt.rcParams['backend'] + backend = plt.rcParams["backend"] if backend in matplotlib.rcsetup.interactive_bk: figManager = matplotlib._pylab_helpers.Gcf.get_active() @@ -264,8 +283,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.operator.crossover — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -114,7 +111,15 @@

    Source code for jmetal.operator.crossover

     from typing import List
     
     from jmetal.core.operator import Crossover
    -from jmetal.core.solution import Solution, FloatSolution, BinarySolution, PermutationSolution
    +from jmetal.core.solution import (
    +    BinarySolution,
    +    CompositeSolution,
    +    FloatSolution,
    +    IntegerSolution,
    +    PermutationSolution,
    +    Solution,
    +)
    +from jmetal.util.ckecking import Check
     
     """
     .. module:: crossover
    @@ -125,38 +130,55 @@ 

    Source code for jmetal.operator.crossover

     """
     
     
    -
    [docs]class NullCrossover(Crossover[Solution, Solution]): - +
    +[docs] +class NullCrossover(Crossover[Solution, Solution]): def __init__(self): super(NullCrossover, self).__init__(probability=0.0) -
    [docs] def execute(self, parents: List[Solution]) -> List[Solution]: +
    +[docs] + def execute(self, parents: List[Solution]) -> List[Solution]: if len(parents) != 2: - raise Exception('The number of parents is not two: {}'.format(len(parents))) + raise Exception("The number of parents is not two: {}".format(len(parents))) return parents
    -
    [docs] def get_number_of_parents(self) -> int: + +
    +[docs] + def get_number_of_parents(self) -> int: return 2
    -
    [docs] def get_number_of_children(self) -> int: + +
    +[docs] + def get_number_of_children(self) -> int: return 2
    -
    [docs] def get_name(self): - return 'Null crossover'
    + +
    +[docs] + def get_name(self): + return "Null crossover"
    +
    -
    [docs]class PMXCrossover(Crossover[PermutationSolution, PermutationSolution]): +
    +[docs] +class PMXCrossover(Crossover[PermutationSolution, PermutationSolution]): def __init__(self, probability: float): super(PMXCrossover, self).__init__(probability=probability) -
    [docs] def execute(self, parents: List[PermutationSolution]) -> List[PermutationSolution]: +
    +[docs] + def execute(self, parents: List[PermutationSolution]) -> List[PermutationSolution]: if len(parents) != 2: - raise Exception('The number of parents is not two: {}'.format(len(parents))) + raise Exception("The number of parents is not two: {}".format(len(parents))) - offspring = [copy.deepcopy(parents[0]), copy.deepcopy(parents[1])] - permutation_length = offspring[0].number_of_variables + offspring = copy.deepcopy(parents) + permutation_length = len(offspring[0].variables) rand = random.random() if rand <= self.probability: @@ -188,81 +210,112 @@

    Source code for jmetal.operator.crossover

                                     swapped[i_son][i_chromosome] = map_[1 - i_son][map_index]
                     return s1, s2
     
    -            swapped = _swap(parents[0].variables, parents[1].variables, cross_points)
    +            swapped = _swap(offspring[0].variables, offspring[1].variables, cross_points)
                 mapped = _map(swapped, cross_points)
     
                 offspring[0].variables, offspring[1].variables = mapped
     
             return offspring
    -
    [docs] def get_number_of_parents(self) -> int: + +
    +[docs] + def get_number_of_parents(self) -> int: return 2
    -
    [docs] def get_number_of_children(self) -> int: + +
    +[docs] + def get_number_of_children(self) -> int: return 2
    -
    [docs] def get_name(self): - return 'Partially Matched crossover'
    + +
    +[docs] + def get_name(self): + return "Partially Matched crossover"
    +
    -
    [docs]class CXCrossover(Crossover[PermutationSolution, PermutationSolution]): +
    +[docs] +class CXCrossover(Crossover[PermutationSolution, PermutationSolution]): def __init__(self, probability: float): super(CXCrossover, self).__init__(probability=probability) -
    [docs] def execute(self, parents: List[PermutationSolution]) -> List[PermutationSolution]: +
    +[docs] + def execute(self, parents: List[PermutationSolution]) -> List[PermutationSolution]: if len(parents) != 2: - raise Exception('The number of parents is not two: {}'.format(len(parents))) + raise Exception("The number of parents is not two: {}".format(len(parents))) - offspring = [copy.deepcopy(parents[1]), copy.deepcopy(parents[0])] + offspring = copy.deepcopy(parents[::-1]) rand = random.random() if rand <= self.probability: - for i in range(parents[0].number_of_variables): - idx = random.randint(0, len(parents[0].variables[i]) - 1) - curr_idx = idx - cycle = [] + idx = random.randint(0, len(parents[0].variables) - 1) + curr_idx = idx + cycle = [] - while True: - cycle.append(curr_idx) - curr_idx = parents[0].variables[i].index(parents[1].variables[i][curr_idx]) + while True: + cycle.append(curr_idx) + curr_idx = parents[0].variables.index(parents[1].variables[curr_idx]) - if curr_idx == idx: - break + if curr_idx == idx: + break - for j in range(len(parents[0].variables[i])): - if j in cycle: - offspring[0].variables[i][j] = parents[0].variables[i][j] - offspring[1].variables[i][j] = parents[0].variables[i][j] + for j in range(len(parents[0].variables)): + if j in cycle: + offspring[0].variables[j] = parents[0].variables[j] + offspring[1].variables[j] = parents[1].variables[j] return offspring
    -
    [docs] def get_number_of_parents(self) -> int: + +
    +[docs] + def get_number_of_parents(self) -> int: return 2
    -
    [docs] def get_number_of_children(self) -> int: + +
    +[docs] + def get_number_of_children(self) -> int: return 2
    -
    [docs] def get_name(self): - return 'Cycle crossover'
    + +
    +[docs] + def get_name(self): + return "Cycle crossover"
    +
    -
    [docs]class SBXCrossover(Crossover[FloatSolution, FloatSolution]): + +
    +[docs] +class SBXCrossover(Crossover[FloatSolution, FloatSolution]): __EPS = 1.0e-14 def __init__(self, probability: float, distribution_index: float = 20.0): super(SBXCrossover, self).__init__(probability=probability) self.distribution_index = distribution_index + if distribution_index < 0: + raise Exception("The distribution index is negative: " + str(distribution_index)) -
    [docs] def execute(self, parents: List[FloatSolution]) -> List[FloatSolution]: - if len(parents) != 2: - raise Exception('The number of parents is not two: {}'.format(len(parents))) +
    +[docs] + def execute(self, parents: List[FloatSolution]) -> List[FloatSolution]: + Check.that(issubclass(type(parents[0]), FloatSolution), "Solution type invalid: " + str(type(parents[0]))) + Check.that(issubclass(type(parents[1]), FloatSolution), "Solution type invalid") + Check.that(len(parents) == 2, "The number of parents is not two: {}".format(len(parents))) - offspring = [copy.deepcopy(parents[0]), copy.deepcopy(parents[1])] + offspring = copy.deepcopy(parents) rand = random.random() if rand <= self.probability: - for i in range(parents[0].number_of_variables): + for i in range(len(parents[0].variables)): value_x1, value_x2 = parents[0].variables[i], parents[1].variables[i] if random.random() <= 0.5: @@ -317,26 +370,137 @@

    Source code for jmetal.operator.crossover

                         offspring[1].variables[i] = value_x2
             return offspring
    -
    [docs] def get_number_of_parents(self) -> int: + +
    +[docs] + def get_number_of_parents(self) -> int: + return 2
    + + +
    +[docs] + def get_number_of_children(self) -> int: + return 2
    + + +
    +[docs] + def get_name(self) -> str: + return "SBX crossover"
    +
    + + + +
    +[docs] +class IntegerSBXCrossover(Crossover[IntegerSolution, IntegerSolution]): + __EPS = 1.0e-14 + + def __init__(self, probability: float, distribution_index: float = 20.0): + super(IntegerSBXCrossover, self).__init__(probability=probability) + self.distribution_index = distribution_index + +
    +[docs] + def execute(self, parents: List[IntegerSolution]) -> List[IntegerSolution]: + Check.that(issubclass(type(parents[0]), IntegerSolution), "Solution type invalid") + Check.that(issubclass(type(parents[1]), IntegerSolution), "Solution type invalid") + Check.that(len(parents) == 2, "The number of parents is not two: {}".format(len(parents))) + + offspring = copy.deepcopy(parents) + rand = random.random() + + if rand <= self.probability: + for i in range(len(parents[0].variables)): + value_x1, value_x2 = parents[0].variables[i], parents[1].variables[i] + + if random.random() <= 0.5: + if abs(value_x1 - value_x2) > self.__EPS: + if value_x1 < value_x2: + y1, y2 = value_x1, value_x2 + else: + y1, y2 = value_x2, value_x1 + + lower_bound, upper_bound = parents[0].lower_bound[i], parents[1].upper_bound[i] + + beta = 1.0 + (2.0 * (y1 - lower_bound) / (y2 - y1)) + alpha = 2.0 - pow(beta, -(self.distribution_index + 1.0)) + + rand = random.random() + if rand <= (1.0 / alpha): + betaq = pow(rand * alpha, (1.0 / (self.distribution_index + 1.0))) + else: + betaq = pow(1.0 / (2.0 - rand * alpha), 1.0 / (self.distribution_index + 1.0)) + + c1 = 0.5 * (y1 + y2 - betaq * (y2 - y1)) + beta = 1.0 + (2.0 * (upper_bound - y2) / (y2 - y1)) + alpha = 2.0 - pow(beta, -(self.distribution_index + 1.0)) + + if rand <= (1.0 / alpha): + betaq = pow((rand * alpha), (1.0 / (self.distribution_index + 1.0))) + else: + betaq = pow(1.0 / (2.0 - rand * alpha), 1.0 / (self.distribution_index + 1.0)) + + c2 = 0.5 * (y1 + y2 + betaq * (y2 - y1)) + + if c1 < lower_bound: + c1 = lower_bound + if c2 < lower_bound: + c2 = lower_bound + if c1 > upper_bound: + c1 = upper_bound + if c2 > upper_bound: + c2 = upper_bound + + if random.random() <= 0.5: + offspring[0].variables[i] = int(c2) + offspring[1].variables[i] = int(c1) + else: + offspring[0].variables[i] = int(c1) + offspring[1].variables[i] = int(c2) + else: + offspring[0].variables[i] = value_x1 + offspring[1].variables[i] = value_x2 + else: + offspring[0].variables[i] = value_x1 + offspring[1].variables[i] = value_x2 + return offspring
    + + +
    +[docs] + def get_number_of_parents(self) -> int: return 2
    -
    [docs] def get_number_of_children(self) -> int: + +
    +[docs] + def get_number_of_children(self) -> int: return 2
    -
    [docs] def get_name(self) -> str: - return 'SBX crossover'
    +
    +[docs] + def get_name(self) -> str: + return "Integer SBX crossover"
    +
    -
    [docs]class SPXCrossover(Crossover[BinarySolution, BinarySolution]): + +
    +[docs] +class SPXCrossover(Crossover[BinarySolution, BinarySolution]): def __init__(self, probability: float): super(SPXCrossover, self).__init__(probability=probability) -
    [docs] def execute(self, parents: List[BinarySolution]) -> List[BinarySolution]: - if len(parents) != 2: - raise Exception('The number of parents is not two: {}'.format(len(parents))) +
    +[docs] + def execute(self, parents: List[BinarySolution]) -> List[BinarySolution]: + Check.that(type(parents[0]) is BinarySolution, "Solution type invalid") + Check.that(type(parents[1]) is BinarySolution, "Solution type invalid") + Check.that(len(parents) == 2, "The number of parents is not two: {}".format(len(parents))) - offspring = [copy.deepcopy(parents[0]), copy.deepcopy(parents[1])] + offspring = copy.deepcopy(parents) rand = random.random() if rand <= self.probability: @@ -370,30 +534,43 @@

    Source code for jmetal.operator.crossover

                 offspring[1].variables[variable_to_cut] = bitset2
     
                 # 6. Apply the crossover to the other variables
    -            for i in range(variable_to_cut + 1, parents[0].number_of_variables):
    +            for i in range(variable_to_cut + 1, len(parents[0].variables)):
                     offspring[0].variables[i] = copy.deepcopy(parents[1].variables[i])
                     offspring[1].variables[i] = copy.deepcopy(parents[0].variables[i])
     
             return offspring
    -
    [docs] def get_number_of_parents(self) -> int: + +
    +[docs] + def get_number_of_parents(self) -> int: return 2
    -
    [docs] def get_number_of_children(self) -> int: + +
    +[docs] + def get_number_of_children(self) -> int: return 2
    -
    [docs] def get_name(self) -> str: - return 'Single point crossover'
    + +
    +[docs] + def get_name(self) -> str: + return "Single point crossover"
    +
    + -
    [docs]class DifferentialEvolutionCrossover(Crossover[FloatSolution, FloatSolution]): - """ This operator receives two parameters: the current individual and an array of three parent individuals. The +
    +[docs] +class DifferentialEvolutionCrossover(Crossover[FloatSolution, FloatSolution]): + """This operator receives two parameters: the current individual and an array of three parent individuals. The best and rand variants depends on the third parent, according whether it represents the current of the "best" individual or a random_search one. The implementation of both variants are the same, due to that the parent selection is external to the crossover operator. """ - def __init__(self, CR: float, F: float, K: float): + def __init__(self, CR: float, F: float, K: float = 0.5): super(DifferentialEvolutionCrossover, self).__init__(probability=1.0) self.CR = CR self.F = F @@ -401,15 +578,16 @@

    Source code for jmetal.operator.crossover

     
             self.current_individual: FloatSolution = None
     
    -
    [docs] def execute(self, parents: List[FloatSolution]) -> List[FloatSolution]: - """ Execute the differential evolution crossover ('best/1/bin' variant in jMetal). - """ +
    +[docs] + def execute(self, parents: List[FloatSolution]) -> List[FloatSolution]: + """Execute the differential evolution crossover ('best/1/bin' variant in jMetal).""" if len(parents) != self.get_number_of_parents(): - raise Exception('The number of parents is not {}: {}'.format(self.get_number_of_parents(), len(parents))) + raise Exception("The number of parents is not {}: {}".format(self.get_number_of_parents(), len(parents))) child = copy.deepcopy(self.current_individual) - number_of_variables = parents[0].number_of_variables + number_of_variables = len(parents[0].variables) rand = random.randint(0, number_of_variables - 1) for i in range(number_of_variables): @@ -427,14 +605,81 @@

    Source code for jmetal.operator.crossover

     
             return [child]
    -
    [docs] def get_number_of_parents(self) -> int: + +
    +[docs] + def get_number_of_parents(self) -> int: return 3
    -
    [docs] def get_number_of_children(self) -> int: + +
    +[docs] + def get_number_of_children(self) -> int: return 1
    -
    [docs] def get_name(self) -> str: - return 'Differential Evolution crossover'
    + +
    +[docs] + def get_name(self) -> str: + return "Differential Evolution crossover"
    +
    + + + +
    +[docs] +class CompositeCrossover(Crossover[CompositeSolution, CompositeSolution]): + __EPS = 1.0e-14 + + def __init__(self, crossover_operator_list: [Crossover]): + super(CompositeCrossover, self).__init__(probability=1.0) + + Check.is_not_none(crossover_operator_list) + Check.collection_is_not_empty(crossover_operator_list) + + self.crossover_operators_list = [] + for operator in crossover_operator_list: + Check.that(issubclass(operator.__class__, Crossover), "Object is not a subclass of Crossover") + self.crossover_operators_list.append(operator) + +
    +[docs] + def execute(self, solutions: List[CompositeSolution]) -> List[CompositeSolution]: + Check.is_not_none(solutions) + Check.that(len(solutions) == 2, "The number of parents is not two: " + str(len(solutions))) + + offspring1 = [] + offspring2 = [] + + number_of_solutions_in_composite_solution = len(solutions[0].variables) + + for i in range(number_of_solutions_in_composite_solution): + parents = [solutions[0].variables[i], solutions[1].variables[i]] + children = self.crossover_operators_list[i].execute(parents) + offspring1.append(children[0]) + offspring2.append(children[1]) + + return [CompositeSolution(offspring1), CompositeSolution(offspring2)]
    + + +
    +[docs] + def get_number_of_parents(self) -> int: + return 2
    + + +
    +[docs] + def get_number_of_children(self) -> int: + return 2
    + + +
    +[docs] + def get_name(self) -> str: + return "Composite crossover"
    +
    +
    @@ -451,8 +696,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.operator.mutation — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -112,7 +109,15 @@

    Source code for jmetal.operator.mutation

     import random
     
     from jmetal.core.operator import Mutation
    -from jmetal.core.solution import BinarySolution, Solution, FloatSolution, IntegerSolution, PermutationSolution
    +from jmetal.core.solution import (
    +    BinarySolution,
    +    CompositeSolution,
    +    FloatSolution,
    +    IntegerSolution,
    +    PermutationSolution,
    +    Solution,
    +)
    +from jmetal.util.ckecking import Check
     
     """
     .. module:: mutation
    @@ -123,25 +128,38 @@ 

    Source code for jmetal.operator.mutation

     """
     
     
    -
    [docs]class NullMutation(Mutation[Solution]): - +
    +[docs] +class NullMutation(Mutation[Solution]): def __init__(self): super(NullMutation, self).__init__(probability=0) -
    [docs] def execute(self, solution: Solution) -> Solution: +
    +[docs] + def execute(self, solution: Solution) -> Solution: return solution
    -
    [docs] def get_name(self): - return 'Null mutation'
    + +
    +[docs] + def get_name(self): + return "Null mutation"
    +
    -
    [docs]class BitFlipMutation(Mutation[BinarySolution]): +
    +[docs] +class BitFlipMutation(Mutation[BinarySolution]): def __init__(self, probability: float): super(BitFlipMutation, self).__init__(probability=probability) -
    [docs] def execute(self, solution: BinarySolution) -> BinarySolution: - for i in range(solution.number_of_variables): +
    +[docs] + def execute(self, solution: BinarySolution) -> BinarySolution: + Check.that(type(solution) is BinarySolution, "Solution type invalid") + + for i in range(len(solution.variables)): for j in range(len(solution.variables[i])): rand = random.random() if rand <= self.probability: @@ -149,18 +167,27 @@

    Source code for jmetal.operator.mutation

     
             return solution
    -
    [docs] def get_name(self): - return 'BitFlip mutation'
    +
    +[docs] + def get_name(self): + return "BitFlip mutation"
    +
    -
    [docs]class PolynomialMutation(Mutation[FloatSolution]): + +
    +[docs] +class PolynomialMutation(Mutation[FloatSolution]): def __init__(self, probability: float, distribution_index: float = 0.20): super(PolynomialMutation, self).__init__(probability=probability) self.distribution_index = distribution_index -
    [docs] def execute(self, solution: FloatSolution) -> FloatSolution: - for i in range(solution.number_of_variables): +
    +[docs] + def execute(self, solution: FloatSolution) -> FloatSolution: + Check.that(issubclass(type(solution), FloatSolution), "Solution type invalid") + for i in range(len(solution.variables)): rand = random.random() if rand <= self.probability: @@ -193,18 +220,28 @@

    Source code for jmetal.operator.mutation

     
             return solution
    -
    [docs] def get_name(self): - return 'Polynomial mutation'
    + +
    +[docs] + def get_name(self): + return "Polynomial mutation"
    +
    -
    [docs]class IntegerPolynomialMutation(Mutation[IntegerSolution]): +
    +[docs] +class IntegerPolynomialMutation(Mutation[IntegerSolution]): def __init__(self, probability: float, distribution_index: float = 0.20): super(IntegerPolynomialMutation, self).__init__(probability=probability) self.distribution_index = distribution_index -
    [docs] def execute(self, solution: IntegerSolution) -> IntegerSolution: - for i in range(solution.number_of_variables): +
    +[docs] + def execute(self, solution: IntegerSolution) -> IntegerSolution: + Check.that(issubclass(type(solution), IntegerSolution), "Solution type invalid") + + for i in range(len(solution.variables)): if random.random() <= self.probability: y = solution.variables[i] yl, yu = solution.lower_bound[i], solution.upper_bound[i] @@ -219,11 +256,11 @@

    Source code for jmetal.operator.mutation

                         if rnd <= 0.5:
                             xy = 1.0 - delta1
                             val = 2.0 * rnd + (1.0 - 2.0 * rnd) * (xy ** (self.distribution_index + 1.0))
    -                        deltaq = val ** mut_pow - 1.0
    +                        deltaq = val**mut_pow - 1.0
                         else:
                             xy = 1.0 - delta2
                             val = 2.0 * (1.0 - rnd) + 2.0 * (rnd - 0.5) * (xy ** (self.distribution_index + 1.0))
    -                        deltaq = 1.0 - val ** mut_pow
    +                        deltaq = 1.0 - val**mut_pow
     
                         y += deltaq * (yu - yl)
                         if y < solution.lower_bound[i]:
    @@ -234,35 +271,56 @@ 

    Source code for jmetal.operator.mutation

                     solution.variables[i] = int(round(y))
             return solution
    -
    [docs] def get_name(self): - return 'Polynomial mutation (Integer)'
    + +
    +[docs] + def get_name(self): + return "Polynomial mutation (Integer)"
    +
    -
    [docs]class SimpleRandomMutation(Mutation[FloatSolution]): +
    +[docs] +class SimpleRandomMutation(Mutation[FloatSolution]): def __init__(self, probability: float): super(SimpleRandomMutation, self).__init__(probability=probability) -
    [docs] def execute(self, solution: FloatSolution) -> FloatSolution: - for i in range(solution.number_of_variables): +
    +[docs] + def execute(self, solution: FloatSolution) -> FloatSolution: + Check.that(type(solution) is FloatSolution, "Solution type invalid") + + for i in range(len(solution.variables)): rand = random.random() if rand <= self.probability: - solution.variables[i] = solution.lower_bound[i] + \ - (solution.upper_bound[i] - solution.lower_bound[i]) * random.random() + solution.variables[i] = ( + solution.lower_bound[i] + (solution.upper_bound[i] - solution.lower_bound[i]) * random.random() + ) return solution
    -
    [docs] def get_name(self): - return 'Simple random_search mutation'
    + +
    +[docs] + def get_name(self): + return "Simple random_search mutation"
    +
    -
    [docs]class UniformMutation(Mutation[FloatSolution]): +
    +[docs] +class UniformMutation(Mutation[FloatSolution]): def __init__(self, probability: float, perturbation: float = 0.5): super(UniformMutation, self).__init__(probability=probability) self.perturbation = perturbation -
    [docs] def execute(self, solution: FloatSolution) -> FloatSolution: - for i in range(solution.number_of_variables): +
    +[docs] + def execute(self, solution: FloatSolution) -> FloatSolution: + Check.that(type(solution) is FloatSolution, "Solution type invalid") + + for i in range(len(solution.variables)): rand = random.random() if rand <= self.probability: @@ -278,20 +336,30 @@

    Source code for jmetal.operator.mutation

     
             return solution
    -
    [docs] def get_name(self): - return 'Uniform mutation'
    +
    +[docs] + def get_name(self): + return "Uniform mutation"
    +
    -
    [docs]class NonUniformMutation(Mutation[FloatSolution]): + +
    +[docs] +class NonUniformMutation(Mutation[FloatSolution]): def __init__(self, probability: float, perturbation: float = 0.5, max_iterations: int = 0.5): super(NonUniformMutation, self).__init__(probability=probability) self.perturbation = perturbation self.max_iterations = max_iterations self.current_iteration = 0 -
    [docs] def execute(self, solution: FloatSolution) -> FloatSolution: - for i in range(solution.number_of_variables): +
    +[docs] + def execute(self, solution: FloatSolution) -> FloatSolution: + Check.that(type(solution) is FloatSolution, "Solution type invalid") + + for i in range(len(solution.variables)): if random.random() <= self.probability: rand = random.random() @@ -311,58 +379,123 @@

    Source code for jmetal.operator.mutation

     
             return solution
    -
    [docs] def set_current_iteration(self, current_iteration: int): + +
    +[docs] + def set_current_iteration(self, current_iteration: int): self.current_iteration = current_iteration
    + def __delta(self, y: float, b_mutation_parameter: float): - return (y * (1.0 - pow(random.random(), - pow((1.0 - 1.0 * self.current_iteration / self.max_iterations), b_mutation_parameter)))) + return y * ( + 1.0 + - pow( + random.random(), pow((1.0 - 1.0 * self.current_iteration / self.max_iterations), b_mutation_parameter) + ) + ) + +
    +[docs] + def get_name(self): + return "Uniform mutation"
    +
    -
    [docs] def get_name(self): - return 'Uniform mutation'
    -
    [docs]class PermutationSwapMutation(Mutation[PermutationSolution]): +
    +[docs] +class PermutationSwapMutation(Mutation[PermutationSolution]): +
    +[docs] + def execute(self, solution: PermutationSolution) -> PermutationSolution: + Check.that(type(solution) is PermutationSolution, "Solution type invalid") -
    [docs] def execute(self, solution: PermutationSolution) -> PermutationSolution: rand = random.random() if rand <= self.probability: - pos_one, pos_two = random.sample(range(solution.number_of_variables - 1), 2) - solution.variables[pos_one], solution.variables[pos_two] = \ - solution.variables[pos_two], solution.variables[pos_one] + pos_one, pos_two = random.sample(range(len(solution.variables)), 2) + solution.variables[pos_one], solution.variables[pos_two] = ( + solution.variables[pos_two], + solution.variables[pos_one], + ) return solution
    -
    [docs] def get_name(self): - return 'Permutation Swap mutation'
    +
    +[docs] + def get_name(self): + return "Permutation Swap mutation"
    +
    -
    [docs]class ScrambleMutation(Mutation[PermutationSolution]): -
    [docs] def execute(self, solution: PermutationSolution) -> PermutationSolution: - for i in range(solution.number_of_variables): - rand = random.random() - if rand <= self.probability: - point1 = random.randint(0, len(solution.variables[i])) - point2 = random.randint(0, len(solution.variables[i]) - 1) +
    +[docs] +class CompositeMutation(Mutation[Solution]): + def __init__(self, mutation_operator_list: [Mutation]): + super(CompositeMutation, self).__init__(probability=1.0) - if point2 >= point1: - point2 += 1 - else: - point1, point2 = point2, point1 + Check.is_not_none(mutation_operator_list) + Check.collection_is_not_empty(mutation_operator_list) + + self.mutation_operators_list = [] + for operator in mutation_operator_list: + Check.that(issubclass(operator.__class__, Mutation), "Object is not a subclass of Mutation") + self.mutation_operators_list.append(operator) + +
    +[docs] + def execute(self, solution: CompositeSolution) -> CompositeSolution: + Check.is_not_none(solution) + + mutated_solution_components = [] + for i in range(len(solution.variables)): + mutated_solution_components.append(self.mutation_operators_list[i].execute(solution.variables[i])) + + return CompositeSolution(mutated_solution_components)
    + + +
    +[docs] + def get_name(self) -> str: + return "Composite mutation operator"
    +
    - if point2 - point1 >= 20: - point2 = point1 + 20 - values = solution.variables[i][point1:point2] - solution.variables[i][point1:point2] = random.sample(values, len(values)) + +
    +[docs] +class ScrambleMutation(Mutation[PermutationSolution]): +
    +[docs] + def execute(self, solution: PermutationSolution) -> PermutationSolution: + rand = random.random() + + if rand <= self.probability: + point1 = random.randint(0, len(solution.variables)) + point2 = random.randint(0, len(solution.variables) - 1) + + if point2 >= point1: + point2 += 1 + else: + point1, point2 = point2, point1 + + if point2 - point1 >= 20: + point2 = point1 + 20 + + values = solution.variables[point1:point2] + solution.variables[point1:point2] = random.sample(values, len(values)) return solution
    -
    [docs] def get_name(self): - return 'Scramble'
    + +
    +[docs] + def get_name(self): + return "Scramble"
    +
    +
    @@ -379,8 +512,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.operator.selection — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -115,11 +112,12 @@

    Source code for jmetal.operator.selection

     import numpy as np
     
     from jmetal.core.operator import Selection
    +from jmetal.core.solution import Solution
    +from jmetal.util.comparator import Comparator, DominanceComparator
     from jmetal.util.density_estimator import CrowdingDistance
     from jmetal.util.ranking import FastNonDominatedRanking
    -from jmetal.util.comparator import Comparator, DominanceComparator
     
    -S = TypeVar('S')
    +S = TypeVar("S", bound=Solution)
     
     """
     .. module:: selection
    @@ -130,18 +128,21 @@ 

    Source code for jmetal.operator.selection

     """
     
     
    -
    [docs]class RouletteWheelSelection(Selection[List[S], S]): - """Performs roulette wheel selection. - """ +
    +[docs] +class RouletteWheelSelection(Selection[List[S], S]): + """Performs roulette wheel selection.""" def __init__(self): super(RouletteWheelSelection).__init__() -
    [docs] def execute(self, front: List[S]) -> S: +
    +[docs] + def execute(self, front: List[S]) -> S: if front is None: - raise Exception('The front is null') + raise Exception("The front is null") elif len(front) == 0: - raise Exception('The front is empty') + raise Exception("The front is empty") maximum = sum([solution.objectives[0] for solution in front]) rand = random.uniform(0.0, maximum) @@ -155,21 +156,29 @@

    Source code for jmetal.operator.selection

     
             return None
    -
    [docs] def get_name(self) -> str: - return 'Roulette wheel selection'
    +
    +[docs] + def get_name(self) -> str: + return "Roulette wheel selection"
    +
    -
    [docs]class BinaryTournamentSelection(Selection[List[S], S]): + +
    +[docs] +class BinaryTournamentSelection(Selection[List[S], S]): def __init__(self, comparator: Comparator = DominanceComparator()): super(BinaryTournamentSelection, self).__init__() self.comparator = comparator -
    [docs] def execute(self, front: List[S]) -> S: +
    +[docs] + def execute(self, front: List[S]) -> S: if front is None: - raise Exception('The front is null') + raise Exception("The front is null") elif len(front) == 0: - raise Exception('The front is empty') + raise Exception("The front is empty") if len(front) == 1: result = front[0] @@ -190,20 +199,28 @@

    Source code for jmetal.operator.selection

     
             return result
    -
    [docs] def get_name(self) -> str: - return 'Binary tournament selection'
    + +
    +[docs] + def get_name(self) -> str: + return "Binary tournament selection"
    +
    -
    [docs]class BestSolutionSelection(Selection[List[S], S]): +
    +[docs] +class BestSolutionSelection(Selection[List[S], S]): def __init__(self): super(BestSolutionSelection, self).__init__() -
    [docs] def execute(self, front: List[S]) -> S: +
    +[docs] + def execute(self, front: List[S]) -> S: if front is None: - raise Exception('The front is null') + raise Exception("The front is null") elif len(front) == 0: - raise Exception('The front is empty') + raise Exception("The front is empty") result = front[0] @@ -213,47 +230,63 @@

    Source code for jmetal.operator.selection

     
             return result
    -
    [docs] def get_name(self) -> str: - return 'Best solution selection'
    + +
    +[docs] + def get_name(self) -> str: + return "Best solution selection"
    +
    -
    [docs]class NaryRandomSolutionSelection(Selection[List[S], S]): +
    +[docs] +class NaryRandomSolutionSelection(Selection[List[S], S]): def __init__(self, number_of_solutions_to_be_returned: int = 1): super(NaryRandomSolutionSelection, self).__init__() if number_of_solutions_to_be_returned < 0: - raise Exception('The number of solutions to be returned must be positive integer') + raise Exception("The number of solutions to be returned must be positive integer") self.number_of_solutions_to_be_returned = number_of_solutions_to_be_returned -
    [docs] def execute(self, front: List[S]) -> S: +
    +[docs] + def execute(self, front: List[S]) -> S: if front is None: - raise Exception('The front is null') + raise Exception("The front is null") if len(front) == 0: - raise Exception('The front is empty') + raise Exception("The front is empty") if len(front) < self.number_of_solutions_to_be_returned: - raise Exception('The front contains less elements than required') + raise Exception("The front contains less elements than required") # random_search sampling without replacement return random.sample(front, self.number_of_solutions_to_be_returned)
    -
    [docs] def get_name(self) -> str: - return 'Nary random_search solution selection'
    + +
    +[docs] + def get_name(self) -> str: + return "Nary random_search solution selection"
    +
    -
    [docs]class DifferentialEvolutionSelection(Selection[List[S], List[S]]): +
    +[docs] +class DifferentialEvolutionSelection(Selection[List[S], List[S]]): def __init__(self): super(DifferentialEvolutionSelection, self).__init__() self.index_to_exclude = None -
    [docs] def execute(self, front: List[S]) -> List[S]: +
    +[docs] + def execute(self, front: List[S]) -> List[S]: if front is None: - raise Exception('The front is null') + raise Exception("The front is null") elif len(front) == 0: - raise Exception('The front is empty') + raise Exception("The front is empty") elif len(front) < 4: - raise Exception('The front has less than four solutions: ' + str(len(front))) + raise Exception("The front has less than four solutions: " + str(len(front))) selected_indexes = random.sample(range(len(front)), 3) while self.index_to_exclude in selected_indexes: @@ -261,42 +294,61 @@

    Source code for jmetal.operator.selection

     
             return [front[i] for i in selected_indexes]
    -
    [docs] def set_index_to_exclude(self, index: int): + +
    +[docs] + def set_index_to_exclude(self, index: int): self.index_to_exclude = index
    -
    [docs] def get_name(self) -> str: - return "Differential evolution selection"
    +
    +[docs] + def get_name(self) -> str: + return "Differential evolution selection"
    +
    -
    [docs]class RandomSolutionSelection(Selection[List[S], S]): + +
    +[docs] +class RandomSolutionSelection(Selection[List[S], S]): def __init__(self): super(RandomSolutionSelection, self).__init__() -
    [docs] def execute(self, front: List[S]) -> S: +
    +[docs] + def execute(self, front: List[S]) -> S: if front is None: - raise Exception('The front is null') + raise Exception("The front is null") elif len(front) == 0: - raise Exception('The front is empty') + raise Exception("The front is empty") return random.choice(front)
    -
    [docs] def get_name(self) -> str: - return 'Random solution selection'
    + +
    +[docs] + def get_name(self) -> str: + return "Random solution selection"
    +
    -
    [docs]class RankingAndCrowdingDistanceSelection(Selection[List[S], List[S]]): +
    +[docs] +class RankingAndCrowdingDistanceSelection(Selection[List[S], List[S]]): def __init__(self, max_population_size: int, dominance_comparator: Comparator = DominanceComparator()): super(RankingAndCrowdingDistanceSelection, self).__init__() self.max_population_size = max_population_size self.dominance_comparator = dominance_comparator -
    [docs] def execute(self, front: List[S]) -> List[S]: +
    +[docs] + def execute(self, front: List[S]) -> List[S]: if front is None: - raise Exception('The front is null') + raise Exception("The front is null") elif len(front) == 0: - raise Exception('The front is empty') + raise Exception("The front is empty") ranking = FastNonDominatedRanking(self.dominance_comparator) crowding_distance = CrowdingDistance() @@ -312,27 +364,35 @@

    Source code for jmetal.operator.selection

                 else:
                     subfront = ranking.get_subfront(ranking_index)
                     crowding_distance.compute_density_estimator(subfront)
    -                sorted_subfront = sorted(subfront, key=lambda x: x.attributes['crowding_distance'], reverse=True)
    +                sorted_subfront = sorted(subfront, key=lambda x: x.attributes["crowding_distance"], reverse=True)
                     for i in range((self.max_population_size - len(new_solution_list))):
                         new_solution_list.append(sorted_subfront[i])
     
             return new_solution_list
    -
    [docs] def get_name(self) -> str: - return 'Ranking and crowding distance selection'
    + +
    +[docs] + def get_name(self) -> str: + return "Ranking and crowding distance selection"
    +
    -
    [docs]class RankingAndFitnessSelection(Selection[List[S], List[S]]): - def __init__(self, - max_population_size: int, reference_point: S, - dominance_comparator: Comparator = DominanceComparator()): +
    +[docs] +class RankingAndFitnessSelection(Selection[List[S], List[S]]): + def __init__( + self, max_population_size: int, reference_point: S, dominance_comparator: Comparator = DominanceComparator() + ): super(RankingAndFitnessSelection, self).__init__() self.max_population_size = max_population_size self.dominance_comparator = dominance_comparator self.reference_point = reference_point -
    [docs] def hypesub(self, l, A, actDim, bounds, pvec, alpha, k): +
    +[docs] + def hypesub(self, l, A, actDim, bounds, pvec, alpha, k): h = [0 for _ in range(l)] Adim = [a[actDim - 1] for a in A] indices_sort = sorted(range(len(Adim)), key=Adim.__getitem__) @@ -353,12 +413,17 @@

    Source code for jmetal.operator.selection

                             h[p] = h[p] + extrusion * alpha[i - 1]
                 else:
                     if extrusion > 0:
    -                    h = [h[j] + extrusion * self.hypesub(l, S[0:i], actDim - 1, bounds, pvec[0:i], alpha, k)[j] for j in
    -                         range(l)]
    +                    h = [
    +                        h[j] + extrusion * self.hypesub(l, S[0:i], actDim - 1, bounds, pvec[0:i], alpha, k)[j]
    +                        for j in range(l)
    +                    ]
     
             return h
    -
    [docs] def compute_hypervol_fitness_values(self, population: List[S], reference_point: S, k: int): + +
    +[docs] + def compute_hypervol_fitness_values(self, population: List[S], reference_point: S, k: int): points = [ind.objectives for ind in population] bounds = reference_point.objectives population_size = len(points) @@ -376,15 +441,18 @@

    Source code for jmetal.operator.selection

             f = self.hypesub(population_size, points, actDim, bounds, pvec, alpha, k)
     
             for i in range(len(population)):
    -            population[i].attributes['fitness'] = f[i]
    +            population[i].attributes["fitness"] = f[i]
     
             return population
    -
    [docs] def execute(self, front: List[S]) -> List[S]: + +
    +[docs] + def execute(self, front: List[S]) -> List[S]: if front is None: - raise Exception('The front is null') + raise Exception("The front is null") elif len(front) == 0: - raise Exception('The front is empty') + raise Exception("The front is empty") ranking = FastNonDominatedRanking(self.dominance_comparator) ranking.compute_ranking(front) @@ -402,29 +470,37 @@

    Source code for jmetal.operator.selection

                     parameter_K = len(subfront) - (self.max_population_size - len(new_solution_list))
                     while parameter_K > 0:
                         subfront = self.compute_hypervol_fitness_values(subfront, self.reference_point, parameter_K)
    -                    subfront = sorted(subfront, key=lambda x: x.attributes['fitness'], reverse=True)
    +                    subfront = sorted(subfront, key=lambda x: x.attributes["fitness"], reverse=True)
                         subfront = subfront[:-1]
                         parameter_K = parameter_K - 1
                     new_solution_list = new_solution_list + subfront
             return new_solution_list
    -
    [docs] def get_name(self) -> str: - return 'Ranking and fitness selection'
    + +
    +[docs] + def get_name(self) -> str: + return "Ranking and fitness selection"
    +
    -
    [docs]class BinaryTournament2Selection(Selection[List[S], S]): +
    +[docs] +class BinaryTournament2Selection(Selection[List[S], S]): def __init__(self, comparator_list: List[Comparator]): super(BinaryTournament2Selection, self).__init__() self.comparator_list = comparator_list -
    [docs] def execute(self, front: List[S]) -> S: +
    +[docs] + def execute(self, front: List[S]) -> S: if front is None: - raise Exception('The front is null') + raise Exception("The front is null") elif len(front) == 0: - raise Exception('The front is empty') + raise Exception("The front is empty") elif not self.comparator_list: - raise Exception('The comparators\' list is empty') + raise Exception("The comparators' list is empty") winner = None @@ -442,6 +518,7 @@

    Source code for jmetal.operator.selection

     
             return winner
    + def __winner(self, front: List[S], comparator: Comparator): # Sampling without replacement i, j = random.sample(range(0, len(front)), 2) @@ -460,8 +537,12 @@

    Source code for jmetal.operator.selection

     
             return result
     
    -
    [docs] def get_name(self) -> str: - return 'Binary tournament selection (experimental)'
    +
    +[docs] + def get_name(self) -> str: + return "Binary tournament selection (experimental)"
    +
    +
    @@ -478,8 +559,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.problem.multiobjective.constrained — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -109,7 +106,7 @@

    Table Of Contents

    Source code for jmetal.problem.multiobjective.constrained

    -from math import pi, cos, atan
    +from math import atan, cos, pi
     
     from jmetal.core.problem import FloatProblem
     from jmetal.core.solution import FloatSolution
    @@ -123,22 +120,36 @@ 

    Source code for jmetal.problem.multiobjective.constrained

    """ -
    [docs]class Srinivas(FloatProblem): - """ Class representing problem Srinivas. """ +
    +[docs] +class Srinivas(FloatProblem): + """Class representing problem Srinivas.""" def __init__(self): super(Srinivas, self).__init__() - self.number_of_variables = 2 - self.number_of_objectives = 2 - self.number_of_constraints = 2 + number_of_variables = 2 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)'] + self.obj_labels = ["f(x)", "f(y)"] + + self.lower_bound = [-20.0 for _ in range(number_of_variables)] + self.upper_bound = [20.0 for _ in range(number_of_variables)] + +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    + - self.lower_bound = [-20.0 for _ in range(self.number_of_variables)] - self.upper_bound = [20.0 for _ in range(self.number_of_variables)] +
    +[docs] + def number_of_constraints(self) -> int: + return 2
    -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: x1 = solution.variables[0] x2 = solution.variables[1] @@ -149,6 +160,7 @@

    Source code for jmetal.problem.multiobjective.constrained

    return solution
    + def __evaluate_constraints(self, solution: FloatSolution) -> None: x1 = solution.variables[0] x2 = solution.variables[1] @@ -156,27 +168,44 @@

    Source code for jmetal.problem.multiobjective.constrained

    solution.constraints[0] = 1.0 - (x1 * x1 + x2 * x2) / 225.0 solution.constraints[1] = (3.0 * x2 - x1) / 10.0 - 1.0 -
    [docs] def get_name(self): - return 'Srinivas'
    +
    +[docs] + def name(self): + return "Srinivas"
    +
    -
    [docs]class Tanaka(FloatProblem): - """ Class representing problem Tanaka. """ + +
    +[docs] +class Tanaka(FloatProblem): + """Class representing problem Tanaka.""" def __init__(self): super(Tanaka, self).__init__() - self.number_of_variables = 2 - self.number_of_objectives = 2 - self.number_of_constraints = 2 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)'] + self.obj_labels = ["f(x)", "f(y)"] + + number_of_variables = 2 + self.lower_bound = [10e-5 for _ in range(number_of_variables)] + self.upper_bound = [pi for _ in range(number_of_variables)] + +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    - self.lower_bound = [10e-5 for _ in range(self.number_of_variables)] - self.upper_bound = [pi for _ in range(self.number_of_variables)] +
    +[docs] + def number_of_constraints(self) -> int: + return 2
    -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: solution.objectives[0] = solution.variables[0] solution.objectives[1] = solution.variables[1] @@ -184,50 +213,61 @@

    Source code for jmetal.problem.multiobjective.constrained

    return solution
    + def __evaluate_constraints(self, solution: FloatSolution) -> None: - constraints = [0.0 for _ in range(self.number_of_constraints)] + constraints = [0.0 for _ in range(self.number_of_constraints())] x1 = solution.variables[0] x2 = solution.variables[1] - constraints[0] = (x1 * x1 + x2 * x2 - 1.0 - 0.1 * cos(16.0 * atan(x1 / x2))) + constraints[0] = x1 * x1 + x2 * x2 - 1.0 - 0.1 * cos(16.0 * atan(x1 / x2)) constraints[1] = -2.0 * ((x1 - 0.5) * (x1 - 0.5) + (x2 - 0.5) * (x2 - 0.5) - 0.5) solution.constraints = constraints - #set_overall_constraint_violation_degree(solution) + # set_overall_constraint_violation_degree(solution) +
    +[docs] + def name(self): + return "Tanaka"
    +
    -
    [docs] def get_name(self): - return 'Tanaka'
    -
    [docs]class Osyczka2(FloatProblem): - """ Class representing problem Osyczka2. """ +
    +[docs] +class Osyczka2(FloatProblem): + """Class representing problem Osyczka2.""" def __init__(self): super(Osyczka2, self).__init__() - self.number_of_variables = 6 - self.number_of_objectives = 2 - self.number_of_constraints = 6 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)'] + self.obj_labels = ["f(x)", "f(y)"] self.lower_bound = [0.0, 0.0, 1.0, 0.0, 1.0, 0.0] self.upper_bound = [10.0, 10.0, 5.0, 6.0, 5.0, 10.0] - FloatSolution.lower_bound = self.lower_bound - FloatSolution.upper_bound = self.upper_bound +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: + +
    +[docs] + def number_of_constraints(self) -> int: + return 6
    + + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: x = solution.variables - solution.objectives[0] = - (25.0 * - (x[0] - 2.0) ** 2 + - (x[1] - 2.0) ** 2 + - (x[2] - 1.0) ** 2 + - (x[3] - 4.0) ** 2 + - (x[4] - 1.0) ** 2) + solution.objectives[0] = -( + 25.0 * (x[0] - 2.0) ** 2 + (x[1] - 2.0) ** 2 + (x[2] - 1.0) ** 2 + (x[3] - 4.0) ** 2 + (x[4] - 1.0) ** 2 + ) solution.objectives[1] = sum([x[i] ** 2 for i in range(len(x))]) @@ -235,8 +275,9 @@

    Source code for jmetal.problem.multiobjective.constrained

    return solution
    + def __evaluate_constraints(self, solution: FloatSolution) -> None: - constraints = [0.0 for _ in range(self.number_of_constraints)] + constraints = [0.0 for _ in range(self.number_of_constraints())] x = solution.variables constraints[0] = (x[0] + x[1]) / 2.0 - 1.0 @@ -248,29 +289,43 @@

    Source code for jmetal.problem.multiobjective.constrained

    solution.constraints = constraints -
    [docs] def get_name(self): - return 'Osyczka2'
    +
    +[docs] + def name(self): + return "Osyczka2"
    +
    + -
    [docs]class Binh2(FloatProblem): - """ Class representing problem Binh2. """ +
    +[docs] +class Binh2(FloatProblem): + """Class representing problem Binh2.""" def __init__(self): super(Binh2, self).__init__() - self.number_of_variables = 2 - self.number_of_objectives = 2 - self.number_of_constraints = 2 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)'] + self.obj_labels = ["f(x)", "f(y)"] self.lower_bound = [0.0, 0.0] self.upper_bound = [5.0, 3.0] - FloatSolution.lower_bound = self.lower_bound - FloatSolution.upper_bound = self.upper_bound +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: + +
    +[docs] + def number_of_constraints(self) -> int: + return 2
    + + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: x = solution.variables solution.objectives[0] = 4.0 * x[0] * x[0] + 4 * x[1] * x[1] solution.objectives[1] = (x[0] - 5.0) * (x[0] - 5.0) + (x[1] - 5.0) * (x[1] - 5.0) @@ -279,15 +334,20 @@

    Source code for jmetal.problem.multiobjective.constrained

    return solution
    + def __evaluate_constraints(self, solution: FloatSolution) -> None: - constraints = [0.0 for _ in range(self.number_of_constraints)] + constraints = [0.0 for _ in range(self.number_of_constraints())] x = solution.variables constraints[0] = -1.0 * (x[0] - 5) * (x[0] - 5) - x[1] * x[1] + 25.0 constraints[1] = (x[0] - 8) * (x[0] - 8) + (x[1] + 3) * (x[1] + 3) - 7.7 -
    [docs] def get_name(self): - return 'Binh2'
    +
    +[docs] + def name(self): + return "Binh2"
    +
    +
    @@ -304,8 +364,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.problem.multiobjective.dtlz — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -109,7 +106,7 @@

    Table Of Contents

    Source code for jmetal.problem.multiobjective.dtlz

    -from math import pi, cos, sin
    +from math import cos, pi, sin
     
     from jmetal.core.problem import FloatProblem
     from jmetal.core.solution import FloatSolution
    @@ -123,254 +120,333 @@ 

    Source code for jmetal.problem.multiobjective.dtlz

    """ -
    [docs]class DTLZ1(FloatProblem): - """ Problem DTLZ1. Continuous problem having a flat Pareto front +
    +[docs] +class DTLZ1(FloatProblem): + """Problem DTLZ1. Continuous problem having a flat Pareto front .. note:: Unconstrained problem. The default number of variables and objectives are, respectively, 7 and 3. """ def __init__(self, number_of_variables: int = 7, number_of_objectives=3): - """ :param number_of_variables: number of decision variables of the problem. - """ + """:param number_of_variables: number of decision variables of the problem.""" super(DTLZ1, self).__init__() - self.number_of_variables = number_of_variables - self.number_of_objectives = number_of_objectives - self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE] * number_of_objectives - self.obj_labels = ['$ f_{} $'.format(i) for i in range(number_of_objectives)] + self.obj_labels = ["$ f_{} $".format(i) for i in range(number_of_objectives)] + + self.lower_bound = number_of_variables * [0.0] + self.upper_bound = number_of_variables * [1.0] + +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    + + +
    +[docs] + def number_of_variables(self) -> int: + return len(self.lower_bound)
    + + +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    - self.lower_bound = self.number_of_variables * [0.0] - self.upper_bound = self.number_of_variables * [1.0] -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: - k = self.number_of_variables - self.number_of_objectives + 1 +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: + k = self.number_of_variables() - self.number_of_objectives() + 1 - g = sum([(x - 0.5) * (x - 0.5) - cos(20.0 * pi * (x - 0.5)) - for x in solution.variables[self.number_of_variables - k:]]) + g = sum( + [ + (x - 0.5) * (x - 0.5) - cos(20.0 * pi * (x - 0.5)) + for x in solution.variables[self.number_of_variables() - k :] + ] + ) g = 100 * (k + g) - solution.objectives = [(1.0 + g) * 0.5] * self.number_of_objectives + solution.objectives = [(1.0 + g) * 0.5] * self.number_of_objectives() - for i in range(self.number_of_objectives): - for j in range(self.number_of_objectives - (i + 1)): + for i in range(self.number_of_objectives()): + for j in range(self.number_of_objectives() - (i + 1)): solution.objectives[i] *= solution.variables[j] if i != 0: - solution.objectives[i] *= 1 - solution.variables[self.number_of_objectives - (i + 1)] + solution.objectives[i] *= 1 - solution.variables[self.number_of_objectives() - (i + 1)] return solution
    -
    [docs] def get_name(self): - return 'DTLZ1'
    + +
    +[docs] + def name(self): + return "DTLZ1"
    +
    -
    [docs]class DTLZ2(DTLZ1): - """ Problem DTLZ2. Continuous problem having a convex Pareto front + +
    +[docs] +class DTLZ2(DTLZ1): + """Problem DTLZ2. Continuous problem having a convex Pareto front .. note:: Unconstrained problem. The default number of variables and objectives are, respectively, 12 and 3. """ def __init__(self, number_of_variables: int = 12, number_of_objectives=3): - """:param number_of_variables: number of decision variables of the problem - """ + """:param number_of_variables: number of decision variables of the problem""" super(DTLZ2, self).__init__(number_of_variables, number_of_objectives) -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: - k = self.number_of_variables - self.number_of_objectives + 1 +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: + k = self.number_of_variables() - self.number_of_objectives() + 1 - g = sum([(x - 0.5) * (x - 0.5) for x in solution.variables[self.number_of_variables - k:]]) + g = sum([(x - 0.5) * (x - 0.5) for x in solution.variables[self.number_of_variables() - k :]]) - solution.objectives = [1.0 + g] * self.number_of_objectives + solution.objectives = [1.0 + g] * self.number_of_objectives() - for i in range(self.number_of_objectives): - for j in range(self.number_of_objectives - (i + 1)): + for i in range(self.number_of_objectives()): + for j in range(self.number_of_objectives() - (i + 1)): solution.objectives[i] *= cos(solution.variables[j] * 0.5 * pi) if i != 0: - solution.objectives[i] *= sin(0.5 * pi * solution.variables[self.number_of_objectives - (i + 1)]) + solution.objectives[i] *= sin(0.5 * pi * solution.variables[self.number_of_objectives() - (i + 1)]) return solution
    -
    [docs] def get_name(self): - return 'DTLZ2'
    +
    +[docs] + def name(self): + return "DTLZ2"
    +
    -
    [docs]class DTLZ3(DTLZ1): - """ Problem DTLZ3. Continuous problem having a convex Pareto front + + +
    +[docs] +class DTLZ3(DTLZ1): + """Problem DTLZ3. Continuous problem having a convex Pareto front .. note:: Unconstrained problem. The default number of variables and objectives are, respectively, 12 and 3. """ def __init__(self, number_of_variables: int = 12, number_of_objectives=3): - """:param number_of_variables: number of decision variables of the problem - """ + """:param number_of_variables: number of decision variables of the problem""" super(DTLZ3, self).__init__(number_of_variables, number_of_objectives) -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: - k = self.number_of_variables - self.number_of_objectives + 1 +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: + k = self.number_of_variables() - self.number_of_objectives() + 1 - g = sum([(x - 0.5) ** 2 - cos(20.0 * pi * (x - 0.5)) for x in solution.variables[self.number_of_variables - k:]]) + g = sum( + [(x - 0.5) ** 2 - cos(20.0 * pi * (x - 0.5)) for x in solution.variables[self.number_of_variables() - k :]] + ) g = 100.0 * (k + g) - f = [1.0 + g for _ in range(self.number_of_objectives)] + f = [1.0 + g for _ in range(self.number_of_objectives())] - for i in range(self.number_of_objectives): - for j in range(self.number_of_objectives - (i + 1)): + for i in range(self.number_of_objectives()): + for j in range(self.number_of_objectives() - (i + 1)): f[i] *= cos(solution.variables[j] * 0.5 * pi) if i != 0: - aux = self.number_of_objectives - (i + 1) + aux = self.number_of_objectives() - (i + 1) f[i] *= sin(solution.variables[aux] * 0.5 * pi) - solution.objectives = [f[x] for x in range(self.number_of_objectives)] + solution.objectives = [f[x] for x in range(self.number_of_objectives())] return solution
    -
    [docs] def get_name(self): - return 'DTLZ3'
    + +
    +[docs] + def name(self): + return "DTLZ3"
    +
    + -
    [docs]class DTLZ4(DTLZ1): - """ Problem DTLZ4. Continuous problem having a convex Pareto front +
    +[docs] +class DTLZ4(DTLZ1): + """Problem DTLZ4. Continuous problem having a convex Pareto front .. note:: Unconstrained problem. The default number of variables and objectives are, respectively, 12 and 3. """ def __init__(self, number_of_variables: int = 12, number_of_objectives=3): - """:param number_of_variables: number of decision variables of the problem - """ + """:param number_of_variables: number of decision variables of the problem""" super(DTLZ4, self).__init__(number_of_variables, number_of_objectives) -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: alpha = 100.0 - k = self.number_of_variables - self.number_of_objectives + 1 + k = self.number_of_variables() - self.number_of_objectives() + 1 - g = sum([(x - 0.5) ** 2 for x in solution.variables[self.number_of_variables - k:]]) - f = [1.0 + g for _ in range(self.number_of_objectives)] + g = sum([(x - 0.5) ** 2 for x in solution.variables[self.number_of_variables() - k :]]) + f = [1.0 + g for _ in range(self.number_of_objectives())] - for i in range(self.number_of_objectives): - for j in range(self.number_of_objectives - (i + 1)): + for i in range(self.number_of_objectives()): + for j in range(self.number_of_objectives() - (i + 1)): f[i] *= cos(pow(solution.variables[j], alpha) * pi / 2.0) if i != 0: - aux = self.number_of_objectives - (i + 1) + aux = self.number_of_objectives() - (i + 1) f[i] *= sin(pow(solution.variables[aux], alpha) * pi / 2.0) - solution.objectives = [f[x] for x in range(self.number_of_objectives)] + solution.objectives = [f[x] for x in range(self.number_of_objectives())] return solution
    -
    [docs] def get_name(self): - return 'DTLZ4'
    + +
    +[docs] + def name(self): + return "DTLZ4"
    +
    -
    [docs]class DTLZ5(DTLZ1): - """ Problem DTLZ5. Continuous problem having a convex Pareto front + +
    +[docs] +class DTLZ5(DTLZ1): + """Problem DTLZ5. Continuous problem having a convex Pareto front .. note:: Unconstrained problem. The default number of variables and objectives are, respectively, 12 and 3. """ def __init__(self, number_of_variables: int = 12, number_of_objectives=3): - """:param number_of_variables: number of decision variables of the problem - """ + """:param number_of_variables: number of decision variables of the problem""" super(DTLZ5, self).__init__(number_of_variables, number_of_objectives) -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: - k = self.number_of_variables - self.number_of_objectives + 1 +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: + k = self.number_of_variables() - self.number_of_objectives() + 1 - g = sum([(x - 0.5) ** 2 for x in solution.variables[self.number_of_variables - k:]]) - t = pi/(4.0 * (1.0 + g)) + g = sum([(x - 0.5) ** 2 for x in solution.variables[self.number_of_variables() - k :]]) + t = pi / (4.0 * (1.0 + g)) - theta = [0.0]*(self.number_of_objectives - 1) - theta[0] = solution.variables[0]*pi/2.0 - theta[1:] = [t * (1.0 + 2.0 * g * solution.variables[i]) for i in range(1,self.number_of_objectives-1)] + theta = [0.0] * (self.number_of_objectives() - 1) + theta[0] = solution.variables[0] * pi / 2.0 + theta[1:] = [t * (1.0 + 2.0 * g * solution.variables[i]) for i in range(1, self.number_of_objectives() - 1)] - f = [1.0 + g for _ in range(self.number_of_objectives)] + f = [1.0 + g for _ in range(self.number_of_objectives())] - for i in range(self.number_of_objectives): - for j in range(self.number_of_objectives - (i + 1)): + for i in range(self.number_of_objectives()): + for j in range(self.number_of_objectives() - (i + 1)): f[i] *= cos(theta[j]) if i != 0: - aux = self.number_of_objectives - (i + 1) + aux = self.number_of_objectives() - (i + 1) f[i] *= sin(theta[aux]) - solution.objectives = [f[x] for x in range(self.number_of_objectives)] + solution.objectives = [f[x] for x in range(self.number_of_objectives())] return solution
    -
    [docs] def get_name(self): - return 'DTLZ5'
    + +
    +[docs] + def name(self): + return "DTLZ5"
    +
    + -
    [docs]class DTLZ6(DTLZ1): - """ Problem DTLZ6. Continuous problem having a convex Pareto front +
    +[docs] +class DTLZ6(DTLZ1): + """Problem DTLZ6. Continuous problem having a convex Pareto front .. note:: Unconstrained problem. The default number of variables and objectives are, respectively, 12 and 3. """ def __init__(self, number_of_variables: int = 12, number_of_objectives=3): - """:param number_of_variables: number of decision variables of the problem - """ + """:param number_of_variables: number of decision variables of the problem""" super(DTLZ6, self).__init__(number_of_variables, number_of_objectives) -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: - k = self.number_of_variables - self.number_of_objectives + 1 +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: + k = self.number_of_variables() - self.number_of_objectives() + 1 - g = sum([pow(x, 0.1) for x in solution.variables[self.number_of_variables - k:]]) - t = pi/(4.0 * (1.0 + g)) + g = sum([pow(x, 0.1) for x in solution.variables[self.number_of_variables() - k :]]) + t = pi / (4.0 * (1.0 + g)) - theta = [0.0]*(self.number_of_objectives - 1) - theta[0] = solution.variables[0]*pi/2.0 - theta[1:] = [t * (1.0 + 2.0 * g * solution.variables[i]) for i in range(1,self.number_of_objectives-1)] + theta = [0.0] * (self.number_of_objectives() - 1) + theta[0] = solution.variables[0] * pi / 2.0 + theta[1:] = [t * (1.0 + 2.0 * g * solution.variables[i]) for i in range(1, self.number_of_objectives() - 1)] - f = [1.0 + g for _ in range(self.number_of_objectives)] + f = [1.0 + g for _ in range(self.number_of_objectives())] - for i in range(self.number_of_objectives): - for j in range(self.number_of_objectives - (i + 1)): + for i in range(self.number_of_objectives()): + for j in range(self.number_of_objectives() - (i + 1)): f[i] *= cos(theta[j]) if i != 0: - aux = self.number_of_objectives - (i + 1) + aux = self.number_of_objectives() - (i + 1) f[i] *= sin(theta[aux]) - solution.objectives = [f[x] for x in range(self.number_of_objectives)] + solution.objectives = [f[x] for x in range(self.number_of_objectives())] return solution
    -
    [docs] def get_name(self): - return 'DTLZ6'
    + +
    +[docs] + def name(self): + return "DTLZ6"
    +
    + -
    [docs]class DTLZ7(DTLZ1): - """ Problem DTLZ6. Continuous problem having a disconnected Pareto front +
    +[docs] +class DTLZ7(DTLZ1): + """Problem DTLZ6. Continuous problem having a disconnected Pareto front .. note:: Unconstrained problem. The default number of variables and objectives are, respectively, 22 and 3. """ def __init__(self, number_of_variables: int = 22, number_of_objectives=3): - """:param number_of_variables: number of decision variables of the problem - """ + """:param number_of_variables: number of decision variables of the problem""" super(DTLZ7, self).__init__(number_of_variables, number_of_objectives) -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: - k = self.number_of_variables - self.number_of_objectives + 1 +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: + k = self.number_of_variables() - self.number_of_objectives() + 1 - g = sum([x for x in solution.variables[self.number_of_variables - k:]]) + g = sum([x for x in solution.variables[self.number_of_variables() - k :]]) g = 1.0 + (9.0 * g) / k - h = sum([(x / (1.0 + g)) * (1 + sin(3.0 * pi * x)) for x in solution.variables[:self.number_of_objectives-1]]) - h = self.number_of_objectives - h + h = sum( + [(x / (1.0 + g)) * (1 + sin(3.0 * pi * x)) for x in solution.variables[: self.number_of_objectives() - 1]] + ) + h = self.number_of_objectives() - h - solution.objectives[:self.number_of_objectives-1] = solution.variables[:self.number_of_objectives-1] + solution.objectives[: self.number_of_objectives() - 1] = solution.variables[: self.number_of_objectives() - 1] solution.objectives[-1] = (1.0 + g) * h return solution
    -
    [docs] def get_name(self): - return 'DTLZ7'
    + +
    +[docs] + def name(self): + return "DTLZ7"
    +
    +
    @@ -387,8 +463,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.problem.multiobjective.fda — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -110,11 +107,11 @@

    Table Of Contents

    Source code for jmetal.problem.multiobjective.fda

     from abc import ABC, abstractmethod
    -from math import sqrt, pow, sin, pi, floor, cos
    +from math import cos, floor, pi, pow, sin, sqrt
     
     import numpy
     
    -from jmetal.core.problem import FloatProblem, DynamicProblem
    +from jmetal.core.problem import DynamicProblem, FloatProblem
     from jmetal.core.solution import FloatSolution
     
     """
    @@ -126,7 +123,9 @@ 

    Source code for jmetal.problem.multiobjective.fda

    """ -
    [docs]class FDA(DynamicProblem, FloatProblem, ABC): +
    +[docs] +class FDA(DynamicProblem, FloatProblem, ABC): def __init__(self): super(FDA, self).__init__() self.tau_T = 5 @@ -134,45 +133,61 @@

    Source code for jmetal.problem.multiobjective.fda

    self.time = 1.0 self.problem_modified = False -
    [docs] def update(self, *args, **kwargs): +
    +[docs] + def update(self, *args, **kwargs): counter: int = kwargs["COUNTER"] self.time = (1.0 / self.nT) * floor(counter * 1.0 / self.tau_T) self.problem_modified = True
    -
    [docs] def the_problem_has_changed(self) -> bool: + +
    +[docs] + def the_problem_has_changed(self) -> bool: return self.problem_modified
    -
    [docs] def clear_changed(self) -> None: + +
    +[docs] + def clear_changed(self) -> None: self.problem_modified = False
    -
    [docs] @abstractmethod + +
    +[docs] + @abstractmethod def evaluate(self, solution: FloatSolution): - pass
    + pass
    +
    + -
    [docs]class FDA1(FDA): - """ Problem FDA1. +
    +[docs] +class FDA1(FDA): + """Problem FDA1. .. note:: Bi-objective dynamic unconstrained problem. The default number of variables is 100. """ def __init__(self, number_of_variables: int = 100): - """ :param number_of_variables: Number of decision variables of the problem. - """ + """:param number_of_variables: Number of decision variables of the problem.""" super(FDA1, self).__init__() self.number_of_variables = number_of_variables self.number_of_objectives = 2 self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)'] + self.obj_labels = ["f(x)", "f(y)"] self.lower_bound = self.number_of_variables * [-1.0] self.upper_bound = self.number_of_variables * [1.0] self.lower_bound[0] = 0.0 self.upper_bound[0] = 1.0 -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: g = self.__eval_g(solution) h = self.__eval_h(solution.variables[0], g) @@ -181,6 +196,7 @@

    Source code for jmetal.problem.multiobjective.fda

    return solution
    + def __eval_g(self, solution: FloatSolution): gT = sin(0.5 * pi * self.time) g = 1.0 + sum([pow(v - gT, 2) for v in solution.variables[1:]]) @@ -190,33 +206,40 @@

    Source code for jmetal.problem.multiobjective.fda

    def __eval_h(self, f: float, g: float) -> float: return 1.0 - sqrt(f / g) -
    [docs] def get_name(self): - return 'FDA1'
    +
    +[docs] + def get_name(self): + return "FDA1"
    +
    + -
    [docs]class FDA2(FDA): - """ Problem FDA2 +
    +[docs] +class FDA2(FDA): + """Problem FDA2 .. note:: Bi-objective dynamic unconstrained problem. The default number of variables is 31. """ def __init__(self, number_of_variables: int = 31): - """ :param number_of_variables: Number of decision variables of the problem. - """ + """:param number_of_variables: Number of decision variables of the problem.""" super(FDA2, self).__init__() self.number_of_variables = number_of_variables self.number_of_objectives = 2 self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)'] + self.obj_labels = ["f(x)", "f(y)"] self.lower_bound = self.number_of_variables * [-1.0] self.upper_bound = self.number_of_variables * [1.0] self.lower_bound[0] = 0.0 self.upper_bound[0] = 1.0 -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: g = self.__eval_g(solution, 1, len(solution.variables)) h = self.__eval_h(solution.variables[0], g) @@ -225,6 +248,7 @@

    Source code for jmetal.problem.multiobjective.fda

    return solution
    + def __eval_g(self, solution: FloatSolution, lower_limit: int, upper_limit: int): g = sum([pow(v, 2) for v in solution.variables[lower_limit:upper_limit]]) g += 1.0 + sum([pow(v + 1.0, 2.0) for v in solution.variables[upper_limit:]]) @@ -235,19 +259,24 @@

    Source code for jmetal.problem.multiobjective.fda

    ht = 0.2 + 4.8 * pow(self.time, 2.0) return 1.0 - pow(f / g, ht) -
    [docs] def get_name(self): - return 'FDA2'
    +
    +[docs] + def get_name(self): + return "FDA2"
    +
    + -
    [docs]class FDA3(FDA): - """ Problem FDA3 +
    +[docs] +class FDA3(FDA): + """Problem FDA3 .. note:: Bi-objective dynamic unconstrained problem. The default number of variables is 30. """ def __init__(self, number_of_variables: int = 30): - """ :param number_of_variables: Number of decision variables of the problem. - """ + """:param number_of_variables: Number of decision variables of the problem.""" super(FDA3, self).__init__() self.number_of_variables = number_of_variables self.number_of_objectives = 2 @@ -257,14 +286,16 @@

    Source code for jmetal.problem.multiobjective.fda

    self.limitInfII = 1 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)'] + self.obj_labels = ["f(x)", "f(y)"] self.lower_bound = self.number_of_variables * [-1.0] self.upper_bound = self.number_of_variables * [1.0] self.lower_bound[0] = 0.0 self.upper_bound[0] = 1.0 -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: g = self.__eval_g(solution, self.limitInfII) h = self.__eval_h(solution.variables[0], g) @@ -273,6 +304,7 @@

    Source code for jmetal.problem.multiobjective.fda

    return solution
    + def __eval_f(self, solution: FloatSolution, lower_limit: int, upper_limit: int): f = 0.0 aux = 2.0 * sin(0.5 * pi * self.time) @@ -292,32 +324,40 @@

    Source code for jmetal.problem.multiobjective.fda

    h = 1.0 - sqrt(f / g) return h -
    [docs] def get_name(self): - return 'FDA3'
    +
    +[docs] + def get_name(self): + return "FDA3"
    +
    + -
    [docs]class FDA4(FDA): - """ Problem FDA4 +
    +[docs] +class FDA4(FDA): + """Problem FDA4 .. note:: Three-objective dynamic unconstrained problem. The default number of variables is 12. """ + M = 3 def __init__(self, number_of_variables: int = 12): - """ :param number_of_variables: Number of decision variables of the problem. - """ + """:param number_of_variables: Number of decision variables of the problem.""" super(FDA4, self).__init__() self.number_of_variables = number_of_variables self.number_of_objectives = 3 self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)', 'f(z)'] + self.obj_labels = ["f(x)", "f(y)", "f(z)"] self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: g = self.__eval_g(solution, self.M - 1) solution.objectives[0] = self.__eval_f1(solution, g) @@ -326,6 +366,7 @@

    Source code for jmetal.problem.multiobjective.fda

    return solution
    + def __eval_g(self, solution: FloatSolution, lower_limit: int): gt = abs(sin(0.5 * pi * self.time)) g = sum([pow(v - gt, 2) for v in solution.variables[lower_limit:]]) @@ -334,14 +375,14 @@

    Source code for jmetal.problem.multiobjective.fda

    def __eval_f1(self, solution: FloatSolution, g: float) -> float: f = 1.0 + g - mult = numpy.prod([cos(v * pi / 2.0) for v in solution.variables[:self.M - 1]]) + mult = numpy.prod([cos(v * pi / 2.0) for v in solution.variables[: self.M - 1]]) return f * mult def __eval_fk(self, solution: FloatSolution, g: float, k: int) -> float: f = 1.0 + g aux = sin((solution.variables[self.M - k] * pi) / 2.0) - mult = numpy.prod([cos(v * pi / 2.0) for v in solution.variables[:self.M - k]]) + mult = numpy.prod([cos(v * pi / 2.0) for v in solution.variables[: self.M - k]]) return f * mult * aux @@ -351,32 +392,40 @@

    Source code for jmetal.problem.multiobjective.fda

    return fm -
    [docs] def get_name(self): - return 'FDA4'
    +
    +[docs] + def get_name(self): + return "FDA4"
    +
    -
    [docs]class FDA5(FDA): - """ Problem FDA5 + +
    +[docs] +class FDA5(FDA): + """Problem FDA5 .. note:: Three-objective dynamic unconstrained problem. The default number of variables is 12. """ + M = 3 def __init__(self, number_of_variables: int = 12): - """ :param number_of_variables: Number of decision variables of the problem. - """ + """:param number_of_variables: Number of decision variables of the problem.""" super(FDA5, self).__init__() self.number_of_variables = number_of_variables self.number_of_objectives = 3 self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)', 'f(z)'] + self.obj_labels = ["f(x)", "f(y)", "f(z)"] self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: g = self.__eval_g(solution, self.M - 1) ft = 1.0 + 100.0 * pow(sin(0.5 * pi * self.time), 4.0) @@ -386,6 +435,7 @@

    Source code for jmetal.problem.multiobjective.fda

    return solution
    + def __eval_g(self, solution: FloatSolution, lower_limit: int): gt = abs(sin(0.5 * pi * self.time)) g = sum([pow(v - gt, 2) for v in solution.variables[lower_limit:]]) @@ -394,14 +444,14 @@

    Source code for jmetal.problem.multiobjective.fda

    def __eval_f1(self, solution: FloatSolution, g: float, ft: float) -> float: f = 1.0 + g - mult = numpy.prod([cos(pow(v, ft) * pi / 2.0) for v in solution.variables[:self.M - 1]]) + mult = numpy.prod([cos(pow(v, ft) * pi / 2.0) for v in solution.variables[: self.M - 1]]) return f * mult def __eval_fk(self, solution: FloatSolution, g: float, k: int, ft: float) -> float: f = 1.0 + g - mult = numpy.prod([cos(pow(v, ft) * pi / 2.0) for v in solution.variables[:self.M - k]]) + mult = numpy.prod([cos(pow(v, ft) * pi / 2.0) for v in solution.variables[: self.M - k]]) yy = pow(solution.variables[self.M - k], ft) mult *= sin(yy * pi / 2.0) @@ -414,8 +464,12 @@

    Source code for jmetal.problem.multiobjective.fda

    return fm * mult -
    [docs] def get_name(self): - return 'FDA5'
    +
    +[docs] + def get_name(self): + return "FDA5"
    +
    +
    @@ -432,8 +486,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.problem.multiobjective.lircmop — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -109,14 +106,17 @@

    Table Of Contents

    Source code for jmetal.problem.multiobjective.lircmop

    -from math import sin, pi, cos, sqrt
    +from math import cos, pi, sin, sqrt
    +from typing import List
     
     from jmetal.core.problem import FloatProblem
     from jmetal.core.solution import FloatSolution
     
     
    -
    [docs]class LIRCMOP1(FloatProblem): - """ Class representing problem LIR-CMOP1, defined in: +
    +[docs] +class LIRCMOP1(FloatProblem): + """Class representing problem LIR-CMOP1, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -124,17 +124,28 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP1, self).__init__() - self.number_of_variables = number_of_variables - self.number_of_objectives = 2 - self.number_of_constraints = 2 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)'] + self.obj_labels = ["f(x)", "f(y)"] + + self.lower_bound = [0.0 for _ in range(number_of_variables)] + self.upper_bound = [1.0 for _ in range(number_of_variables)] + +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    + + +
    +[docs] + def number_of_constraints(self) -> int: + return 2
    - self.lower_bound = [0.0 for _ in range(self.number_of_variables)] - self.upper_bound = [1.0 for _ in range(self.number_of_variables)] -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: x = solution.variables solution.objectives[0] = x[0] + self.g1(x) @@ -144,9 +155,12 @@

    Source code for jmetal.problem.multiobjective.lircmop

    return solution
    -
    [docs] def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: + +
    +[docs] + def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: x = solution.variables - constraints = [0.0 for _ in range(self.number_of_constraints)] + constraints = [0.0 for _ in range(len(solution.constraints))] a = 0.51 b = 0.5 @@ -158,26 +172,39 @@

    Source code for jmetal.problem.multiobjective.lircmop

    return solution
    -
    [docs] def g1(self, x: [float]) -> float: + +
    +[docs] + def g1(self, x: List[float]) -> float: result = 0 - for i in range(2, self.number_of_variables, 2): + for i in range(2, self.number_of_variables(), 2): result += pow(x[i] - sin(0.5 * pi * x[0]), 2.0) return result
    -
    [docs] def g2(self, x: [float]) -> float: + +
    +[docs] + def g2(self, x: List[float]) -> float: result = 0 - for i in range(1, self.number_of_variables, 2): + for i in range(1, self.number_of_variables(), 2): result += pow(x[i] - cos(0.5 * pi * x[0]), 2.0) return result
    -
    [docs] def get_name(self): - return 'LIR-CMOP1'
    + +
    +[docs] + def name(self): + return "LIR-CMOP1"
    +
    + -
    [docs]class LIRCMOP2(LIRCMOP1): - """ Class representing problem LIR-CMOP1, defined in: +
    +[docs] +class LIRCMOP2(LIRCMOP1): + """Class representing problem LIR-CMOP1, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -186,7 +213,9 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP2, self).__init__(number_of_variables) -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: x = solution.variables solution.objectives[0] = x[0] + self.g1(x) @@ -196,12 +225,19 @@

    Source code for jmetal.problem.multiobjective.lircmop

    return solution
    -
    [docs] def get_name(self): - return 'LIR-CMOP2'
    + +
    +[docs] + def name(self): + return "LIR-CMOP2"
    +
    -
    [docs]class LIRCMOP3(LIRCMOP1): - """ Class representing problem LIR-CMOP3, defined in: + +
    +[docs] +class LIRCMOP3(LIRCMOP1): + """Class representing problem LIR-CMOP3, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -210,9 +246,17 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP3, self).__init__(number_of_variables) -
    [docs] def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def number_of_constraints(self) -> int: + return 3
    + + +
    +[docs] + def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: x = solution.variables - constraints = [0.0 for _ in range(self.number_of_constraints)] + constraints = [0.0 for _ in range(self.number_of_constraints())] a = 0.51 b = 0.5 @@ -226,12 +270,19 @@

    Source code for jmetal.problem.multiobjective.lircmop

    return solution
    -
    [docs] def get_name(self): - return 'LIR-CMOP3'
    + +
    +[docs] + def name(self): + return "LIR-CMOP3"
    +
    -
    [docs]class LIRCMOP4(LIRCMOP2): - """ Class representing problem LIR-CMOP4, defined in: + +
    +[docs] +class LIRCMOP4(LIRCMOP2): + """Class representing problem LIR-CMOP4, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -240,9 +291,17 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP4, self).__init__(number_of_variables) -
    [docs] def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def number_of_constraints(self) -> int: + return 3
    + + +
    +[docs] + def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: x = solution.variables - constraints = [0.0 for _ in range(self.number_of_constraints)] + constraints = [0.0 for _ in range(self.number_of_constraints())] a = 0.51 b = 0.5 @@ -256,12 +315,19 @@

    Source code for jmetal.problem.multiobjective.lircmop

    return solution
    -
    [docs] def get_name(self): - return 'LIR-CMOP4'
    + +
    +[docs] + def name(self): + return "LIR-CMOP4"
    +
    -
    [docs]class LIRCMOP5(FloatProblem): - """ Class representing problem LIR-CMOP5, defined in: + +
    +[docs] +class LIRCMOP5(FloatProblem): + """Class representing problem LIR-CMOP5, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -269,17 +335,28 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP5, self).__init__() - self.number_of_variables = number_of_variables - self.number_of_objectives = 2 - self.number_of_constraints = 2 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)'] + self.obj_labels = ["f(x)", "f(y)"] + + self.lower_bound = [0.0 for _ in range(number_of_variables)] + self.upper_bound = [1.0 for _ in range(number_of_variables)] - self.lower_bound = [0.0 for _ in range(self.number_of_variables)] - self.upper_bound = [1.0 for _ in range(self.number_of_variables)] +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: + +
    +[docs] + def number_of_constraints(self) -> int: + return 2
    + + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: x = solution.variables solution.objectives[0] = x[0] + 10 * self.g1(x) + 0.7057 @@ -289,8 +366,11 @@

    Source code for jmetal.problem.multiobjective.lircmop

    return solution
    -
    [docs] def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: - constraints = [0.0 for _ in range(self.number_of_constraints)] + +
    +[docs] + def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: + constraints = [0.0 for _ in range(self.number_of_constraints())] r = 0.1 theta = -0.25 * pi @@ -302,36 +382,49 @@

    Source code for jmetal.problem.multiobjective.lircmop

    f2 = solution.objectives[1] for i in range(len(x_offset)): - constraints[i] = pow( - ((f1 - x_offset[i]) * cos(theta) - (f2 - y_offset[i]) * sin(theta)) / a_array[i], 2) + \ - pow( - ((f1 - x_offset[i]) * sin(theta) + (f2 - y_offset[i]) * cos(theta)) / b_array[i], - 2) - r + constraints[i] = ( + pow(((f1 - x_offset[i]) * cos(theta) - (f2 - y_offset[i]) * sin(theta)) / a_array[i], 2) + + pow(((f1 - x_offset[i]) * sin(theta) + (f2 - y_offset[i]) * cos(theta)) / b_array[i], 2) + - r + ) solution.constraints = constraints return solution
    -
    [docs] def g1(self, x: [float]) -> float: + +
    +[docs] + def g1(self, x: [float]) -> float: result = 0 - for i in range(2, self.number_of_variables, 2): + for i in range(2, self.number_of_variables(), 2): result += pow(x[i] - sin(0.5 * i / len(x) * pi * x[0]), 2.0) return result
    -
    [docs] def g2(self, x: [float]) -> float: + +
    +[docs] + def g2(self, x: [float]) -> float: result = 0 - for i in range(1, self.number_of_variables, 2): + for i in range(1, self.number_of_variables(), 2): result += pow(x[i] - cos(0.5 * i / len(x) * pi * x[0]), 2.0) return result
    -
    [docs] def get_name(self): - return 'LIR-CMOP5'
    + +
    +[docs] + def name(self): + return "LIR-CMOP5"
    +
    -
    [docs]class LIRCMOP6(LIRCMOP5): - """ Class representing problem LIR-CMOP6, defined in: + +
    +[docs] +class LIRCMOP6(LIRCMOP5): + """Class representing problem LIR-CMOP6, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -340,7 +433,9 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP6, self).__init__(number_of_variables) -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: x = solution.variables solution.objectives[0] = x[0] + 10 * self.g1(x) + 0.7057 @@ -350,8 +445,11 @@

    Source code for jmetal.problem.multiobjective.lircmop

    return solution
    -
    [docs] def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: - constraints = [0.0 for _ in range(self.number_of_constraints)] + +
    +[docs] + def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: + constraints = [0.0 for _ in range(self.number_of_constraints())] r = 0.1 theta = -0.25 * pi @@ -363,22 +461,29 @@

    Source code for jmetal.problem.multiobjective.lircmop

    f2 = solution.objectives[1] for i in range(len(x_offset)): - constraints[i] = pow( - ((f1 - x_offset[i]) * cos(theta) - (f2 - y_offset[i]) * sin(theta)) / a_array[i], 2) + \ - pow( - ((f1 - x_offset[i]) * sin(theta) + (f2 - y_offset[i]) * cos(theta)) / b_array[i], - 2) - r + constraints[i] = ( + pow(((f1 - x_offset[i]) * cos(theta) - (f2 - y_offset[i]) * sin(theta)) / a_array[i], 2) + + pow(((f1 - x_offset[i]) * sin(theta) + (f2 - y_offset[i]) * cos(theta)) / b_array[i], 2) + - r + ) solution.constraints = constraints return solution
    -
    [docs] def get_name(self): - return 'LIR-CMOP6'
    + +
    +[docs] + def name(self): + return "LIR-CMOP6"
    +
    -
    [docs]class LIRCMOP7(LIRCMOP5): - """ Class representing problem LIR-CMOP7, defined in: + +
    +[docs] +class LIRCMOP7(LIRCMOP5): + """Class representing problem LIR-CMOP7, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -387,8 +492,10 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP7, self).__init__(number_of_variables) -
    [docs] def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: - constraints = [0.0 for _ in range(self.number_of_constraints)] +
    +[docs] + def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: + constraints = [0.0 for _ in range(self.number_of_constraints())] r = 0.1 theta = -0.25 * pi @@ -400,22 +507,29 @@

    Source code for jmetal.problem.multiobjective.lircmop

    f2 = solution.objectives[1] for i in range(len(x_offset)): - constraints[i] = pow( - ((f1 - x_offset[i]) * cos(theta) - (f2 - y_offset[i]) * sin(theta)) / a_array[i], 2) + \ - pow( - ((f1 - x_offset[i]) * sin(theta) + (f2 - y_offset[i]) * cos(theta)) / b_array[i], - 2) - r + constraints[i] = ( + pow(((f1 - x_offset[i]) * cos(theta) - (f2 - y_offset[i]) * sin(theta)) / a_array[i], 2) + + pow(((f1 - x_offset[i]) * sin(theta) + (f2 - y_offset[i]) * cos(theta)) / b_array[i], 2) + - r + ) solution.constraints = constraints return solution
    -
    [docs] def get_name(self): - return 'LIR-CMOP7'
    +
    +[docs] + def name(self): + return "LIR-CMOP7"
    +
    -
    [docs]class LIRCMOP8(LIRCMOP6): - """ Class representing problem LIR-CMOP8, defined in: + + +
    +[docs] +class LIRCMOP8(LIRCMOP6): + """Class representing problem LIR-CMOP8, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -424,8 +538,10 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP8, self).__init__(number_of_variables) -
    [docs] def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: - constraints = [0.0 for _ in range(self.number_of_constraints)] +
    +[docs] + def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: + constraints = [0.0 for _ in range(self.number_of_constraints())] r = 0.1 theta = -0.25 * pi @@ -437,22 +553,29 @@

    Source code for jmetal.problem.multiobjective.lircmop

    f2 = solution.objectives[1] for i in range(len(x_offset)): - constraints[i] = pow( - ((f1 - x_offset[i]) * cos(theta) - (f2 - y_offset[i]) * sin(theta)) / a_array[i], 2) + \ - pow( - ((f1 - x_offset[i]) * sin(theta) + (f2 - y_offset[i]) * cos(theta)) / b_array[i], - 2) - r + constraints[i] = ( + pow(((f1 - x_offset[i]) * cos(theta) - (f2 - y_offset[i]) * sin(theta)) / a_array[i], 2) + + pow(((f1 - x_offset[i]) * sin(theta) + (f2 - y_offset[i]) * cos(theta)) / b_array[i], 2) + - r + ) solution.constraints = constraints return solution
    -
    [docs] def get_name(self): - return 'LIR-CMOP8'
    + +
    +[docs] + def name(self): + return "LIR-CMOP8"
    +
    + -
    [docs]class LIRCMOP9(LIRCMOP8): - """ Class representing problem LIR-CMOP9, defined in: +
    +[docs] +class LIRCMOP9(LIRCMOP8): + """Class representing problem LIR-CMOP9, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -461,7 +584,9 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP9, self).__init__(number_of_variables) -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: x = solution.variables solution.objectives[0] = 1.7057 * x[0] * (10 * self.g1(x) + 1) @@ -471,36 +596,49 @@

    Source code for jmetal.problem.multiobjective.lircmop

    return solution
    -
    [docs] def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: + +
    +[docs] + def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: x = solution.variables - constraints = [0.0 for _ in range(self.number_of_constraints)] + constraints = [0.0 for _ in range(self.number_of_constraints())] theta = -0.25 * pi n = 4.0 f0 = solution.objectives[0] f1 = solution.objectives[1] - constraints[0] = f0 * sin(theta) + f1 * cos(theta) - sin(n * pi * (f0 * cos(theta) - f1 * sin(theta))) - 2; + constraints[0] = f0 * sin(theta) + f1 * cos(theta) - sin(n * pi * (f0 * cos(theta) - f1 * sin(theta))) - 2 x_offset = 1.40 y_offset = 1.40 a = 1.5 b = 6.0 - r = 0.1; + r = 0.1 - constraints[1] = pow(((f0 - x_offset) * cos(theta) - (f1 - y_offset) * sin(theta)) / a, 2) + pow( - ((f0 - x_offset) * sin(theta) + (f1 - y_offset) * cos(theta)) / b, 2) - r + constraints[1] = ( + pow(((f0 - x_offset) * cos(theta) - (f1 - y_offset) * sin(theta)) / a, 2) + + pow(((f0 - x_offset) * sin(theta) + (f1 - y_offset) * cos(theta)) / b, 2) + - r + ) solution.constraints = constraints return solution
    -
    [docs] def get_name(self): - return 'LIR-CMOP9'
    + +
    +[docs] + def name(self): + return "LIR-CMOP9"
    +
    + -
    [docs]class LIRCMOP10(LIRCMOP8): - """ Class representing problem LIR-CMOP10, defined in: +
    +[docs] +class LIRCMOP10(LIRCMOP8): + """Class representing problem LIR-CMOP10, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -509,7 +647,9 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP10, self).__init__(number_of_variables) -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: x = solution.variables solution.objectives[0] = 1.7057 * x[0] * (10 * self.g1(x) + 1) @@ -519,35 +659,48 @@

    Source code for jmetal.problem.multiobjective.lircmop

    return solution
    -
    [docs] def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: - constraints = [0.0 for _ in range(self.number_of_constraints)] + +
    +[docs] + def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: + constraints = [0.0 for _ in range(self.number_of_constraints())] theta = -0.25 * pi n = 4.0 f0 = solution.objectives[0] f1 = solution.objectives[1] - constraints[0] = f0 * sin(theta) + f1 * cos(theta) - sin(n * pi * (f0 * cos(theta) - f1 * sin(theta))) - 1; + constraints[0] = f0 * sin(theta) + f1 * cos(theta) - sin(n * pi * (f0 * cos(theta) - f1 * sin(theta))) - 1 x_offset = 1.1 y_offset = 1.2 a = 2.0 b = 4.0 - r = 0.1; + r = 0.1 - constraints[1] = pow(((f0 - x_offset) * cos(theta) - (f1 - y_offset) * sin(theta)) / a, 2) + pow( - ((f0 - x_offset) * sin(theta) + (f1 - y_offset) * cos(theta)) / b, 2) - r + constraints[1] = ( + pow(((f0 - x_offset) * cos(theta) - (f1 - y_offset) * sin(theta)) / a, 2) + + pow(((f0 - x_offset) * sin(theta) + (f1 - y_offset) * cos(theta)) / b, 2) + - r + ) solution.constraints = constraints return solution
    -
    [docs] def get_name(self): - return 'LIR-CMOP10'
    + +
    +[docs] + def name(self): + return "LIR-CMOP10"
    +
    + -
    [docs]class LIRCMOP11(LIRCMOP10): - """ Class representing problem LIR-CMOP11, defined in: +
    +[docs] +class LIRCMOP11(LIRCMOP10): + """Class representing problem LIR-CMOP11, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -556,35 +709,47 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP11, self).__init__(number_of_variables) -
    [docs] def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: - constraints = [0.0 for _ in range(self.number_of_constraints)] +
    +[docs] + def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: + constraints = [0.0 for _ in range(self.number_of_constraints())] theta = -0.25 * pi n = 4.0 f0 = solution.objectives[0] f1 = solution.objectives[1] - constraints[0] = f0 * sin(theta) + f1 * cos(theta) - sin(n * pi * (f0 * cos(theta) - f1 * sin(theta))) - 2.1; + constraints[0] = f0 * sin(theta) + f1 * cos(theta) - sin(n * pi * (f0 * cos(theta) - f1 * sin(theta))) - 2.1 x_offset = 1.2 y_offset = 1.2 a = 1.5 b = 5.0 - r = 0.1; + r = 0.1 - constraints[1] = pow(((f0 - x_offset) * cos(theta) - (f1 - y_offset) * sin(theta)) / a, 2) + pow( - ((f0 - x_offset) * sin(theta) + (f1 - y_offset) * cos(theta)) / b, 2) - r + constraints[1] = ( + pow(((f0 - x_offset) * cos(theta) - (f1 - y_offset) * sin(theta)) / a, 2) + + pow(((f0 - x_offset) * sin(theta) + (f1 - y_offset) * cos(theta)) / b, 2) + - r + ) solution.constraints = constraints return solution
    -
    [docs] def get_name(self): - return 'LIR-CMOP11'
    + +
    +[docs] + def name(self): + return "LIR-CMOP11"
    +
    + -
    [docs]class LIRCMOP12(LIRCMOP9): - """ Class representing problem LIR-CMOP9, defined in: +
    +[docs] +class LIRCMOP12(LIRCMOP9): + """Class representing problem LIR-CMOP9, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -593,35 +758,47 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP12, self).__init__(number_of_variables) -
    [docs] def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: - constraints = [0.0 for _ in range(self.number_of_constraints)] +
    +[docs] + def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: + constraints = [0.0 for _ in range(self.number_of_constraints())] theta = -0.25 * pi n = 4.0 f0 = solution.objectives[0] f1 = solution.objectives[1] - constraints[0] = f0 * sin(theta) + f1 * cos(theta) - sin(n * pi * (f0 * cos(theta) - f1 * sin(theta))) - 2.5; + constraints[0] = f0 * sin(theta) + f1 * cos(theta) - sin(n * pi * (f0 * cos(theta) - f1 * sin(theta))) - 2.5 x_offset = 1.6 y_offset = 1.6 a = 1.5 b = 6.0 - r = 0.1; + r = 0.1 - constraints[1] = pow(((f0 - x_offset) * cos(theta) - (f1 - y_offset) * sin(theta)) / a, 2) + pow( - ((f0 - x_offset) * sin(theta) + (f1 - y_offset) * cos(theta)) / b, 2) - r + constraints[1] = ( + pow(((f0 - x_offset) * cos(theta) - (f1 - y_offset) * sin(theta)) / a, 2) + + pow(((f0 - x_offset) * sin(theta) + (f1 - y_offset) * cos(theta)) / b, 2) + - r + ) solution.constraints = constraints return solution
    -
    [docs] def get_name(self): - return 'LIR-CMOP12'
    + +
    +[docs] + def name(self): + return "LIR-CMOP12"
    +
    -
    [docs]class LIRCMOP13(FloatProblem): - """ Class representing problem LIR-CMOP13, defined in: + +
    +[docs] +class LIRCMOP13(FloatProblem): + """Class representing problem LIR-CMOP13, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -629,17 +806,27 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP13, self).__init__() - self.number_of_variables = number_of_variables - self.number_of_objectives = 3 - self.number_of_constraints = 2 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)'] + self.obj_labels = ["f(x)", "f(y)"] + + self.lower_bound = [0.0 for _ in range(number_of_variables)] + self.upper_bound = [1.0 for _ in range(number_of_variables)] - self.lower_bound = [0.0 for _ in range(self.number_of_variables)] - self.upper_bound = [1.0 for _ in range(self.number_of_variables)] +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: + +
    +[docs] + def number_of_constraints(self) -> int: + return 2
    + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: x = solution.variables solution.objectives[0] = (1.7057 + self.g1(x)) * cos(0.5 * pi * x[0]) * cos(0.5 * pi + x[1]) @@ -650,10 +837,13 @@

    Source code for jmetal.problem.multiobjective.lircmop

    return solution
    -
    [docs] def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: - constraints = [0.0 for _ in range(self.number_of_constraints)] - f = sum([pow(solution.objectives[i], 2) for i in range(solution.number_of_objectives)]) +
    +[docs] + def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: + constraints = [0.0 for _ in range(self.number_of_constraints())] + + f = sum([pow(solution.objectives[i], 2) for i in range(len(solution.objectives))]) constraints[0] = (f - 9) * (f - 4) constraints[1] = (f - 1.9 * 1.9) * (f - 1.8 * 1.8) @@ -662,19 +852,29 @@

    Source code for jmetal.problem.multiobjective.lircmop

    return solution
    -
    [docs] def g1(self, x: [float]) -> float: + +
    +[docs] + def g1(self, x: [float]) -> float: result = 0 - for i in range(2, self.number_of_variables, 2): + for i in range(2, self.number_of_variables(), 2): result += 10 * pow(x[i] - 0.5, 2.0) return result
    -
    [docs] def get_name(self): - return 'LIR-CMOP13'
    +
    +[docs] + def name(self): + return "LIR-CMOP13"
    +
    -
    [docs]class LIRCMOP14(LIRCMOP13): - """ Class representing problem LIR-CMOP14, defined in: + + +
    +[docs] +class LIRCMOP14(LIRCMOP13): + """Class representing problem LIR-CMOP14, defined in: * An Improved epsilon-constrained Method in MOEA/D for CMOPs with Large Infeasible Regions. Fan, Z., Li, W., Cai, X. et al. Soft Comput (2019). https://doi.org/10.1007/s00500-019-03794-x @@ -682,12 +882,25 @@

    Source code for jmetal.problem.multiobjective.lircmop

    def __init__(self, number_of_variables: int = 30): super(LIRCMOP14, self).__init__(number_of_variables) - self.number_of_constraints = 3 -
    [docs] def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: - constraints = [0.0 for _ in range(self.number_of_constraints)] +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    + + +
    +[docs] + def number_of_constraints(self) -> int: + return 3
    + - f = sum([pow(solution.objectives[i], 2) for i in range(solution.number_of_objectives)]) +
    +[docs] + def evaluate_constraints(self, solution: FloatSolution) -> FloatSolution: + constraints = [0.0 for _ in range(self.number_of_constraints())] + + f = sum([pow(solution.objectives[i], 2) for i in range(len(solution.objectives))]) constraints[0] = (f - 9) * (f - 4) constraints[1] = (f - 1.9 * 1.9) * (f - 1.8 * 1.8) @@ -697,8 +910,13 @@

    Source code for jmetal.problem.multiobjective.lircmop

    return solution
    -
    [docs] def get_name(self): - return 'LIR-CMOP14'
    + +
    +[docs] + def name(self): + return "LIR-CMOP14"
    +
    +
    @@ -715,8 +933,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.problem.multiobjective.lz09 — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -124,48 +121,53 @@

    Source code for jmetal.problem.multiobjective.lz09

    """ -
    [docs]class LZ09(FloatProblem): +
    +[docs] +class LZ09(FloatProblem): __metaclass__ = ABCMeta - def __init__(self, - number_of_variables: int, - number_of_objectives: int, - number_of_constraints: int, - ptype: int, - dtype: int, - ltype: int): - """ LZ09 benchmark family as defined in: + def __init__( + self, + number_of_variables: int, + ptype: int, + dtype: int, + ltype: int, + ): + """LZ09 benchmark family as defined in: * H. Li and Q. Zhang. Multiobjective optimization problems with complicated pareto sets, MOEA/D and NSGA-II. IEEE Transactions on Evolutionary Computation, 12(2):284-302, April 2009. """ super(LZ09, self).__init__() - self.number_of_variables = number_of_variables - self.number_of_objectives = number_of_objectives - self.number_of_constraints = number_of_constraints - - self.obj_directions = [self.MINIMIZE, self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)', 'f(z)'] - self.lower_bound = self.number_of_variables * [0.0] - self.upper_bound = self.number_of_variables * [1.0] + self.lower_bound = number_of_variables * [0.0] + self.upper_bound = number_of_variables * [1.0] self.ptype = ptype self.dtype = dtype self.ltype = ltype -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    + + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: x = solution.variables y = self.objective(x) - for i in range(self.number_of_objectives): + for i in range(self.number_of_objectives()): solution.objectives[i] = y[i] return solution
    + def __ps_func2(self, x: float, t1: float, dim: int, type: int, css: int) -> float: - """ Control the PS shapes of 2-D instances. + """Control the PS shapes of 2-D instances. :param type: The type of the curve. :param css: The class of the index. @@ -175,13 +177,13 @@

    Source code for jmetal.problem.multiobjective.lz09

    if type == 21: xy = 2 * (x - 0.5) - beta = xy - math.pow(t1, 0.5 * (self.number_of_variables + 3 * dim - 8) / (self.number_of_variables - 2)) + beta = xy - math.pow(t1, 0.5 * (self.number_of_variables() + 3 * dim - 8) / (self.number_of_variables() - 2)) if type == 22: - theta = 6 * math.pi * t1 + dim * math.pi / self.number_of_variables + theta = 6 * math.pi * t1 + dim * math.pi / self.number_of_variables() xy = 2 * (x - 0.5) beta = xy - math.sin(theta) if type == 23: - theta = 6 * math.pi * t1 + dim * math.pi / self.number_of_variables + theta = 6 * math.pi * t1 + dim * math.pi / self.number_of_variables() ra = 0.8 * t1 xy = 2 * (x - 0.5) if css == 1: @@ -189,7 +191,7 @@

    Source code for jmetal.problem.multiobjective.lz09

    else: beta = xy - ra * math.sin(theta) if type == 24: - theta = 6 * math.pi * t1 + dim * math.pi / self.number_of_variables + theta = 6 * math.pi * t1 + dim * math.pi / self.number_of_variables() xy = 2 * (x - 0.5) ra = 0.8 * t1 if css == 1: @@ -199,7 +201,7 @@

    Source code for jmetal.problem.multiobjective.lz09

    if type == 25: rho = 0.8 phi = math.pi * t1 - theta = 6 * math.pi * t1 + dim * math.pi / self.number_of_variables + theta = 6 * math.pi * t1 + dim * math.pi / self.number_of_variables() xy = 2 * (x - 0.5) if css == 1: beta = xy - rho * math.sin(phi) * math.sin(theta) @@ -208,7 +210,7 @@

    Source code for jmetal.problem.multiobjective.lz09

    else: beta = xy - rho * math.cos(phi) if type == 26: - theta = 6 * math.pi * t1 + dim * math.pi / self.number_of_variables + theta = 6 * math.pi * t1 + dim * math.pi / self.number_of_variables() ra = 0.3 * t1 * (t1 * math.cos(4 * theta) + 2) xy = 2 * (x - 0.5) if css == 1: @@ -219,26 +221,25 @@

    Source code for jmetal.problem.multiobjective.lz09

    return beta def __ps_func3(self, x: float, t1: float, t2: float, dim: int, type: int): - """ Control the PS shapes of 3-D instances. + """Control the PS shapes of 3-D instances. :param type: The type of curve. """ beta = 0.0 dim += 1 if type == 31: - xy = 4 * (x - .5) - rate = 1.0 * dim / self.number_of_variables + xy = 4 * (x - 0.5) + rate = 1.0 * dim / self.number_of_variables() beta = xy - 4 * (t1 * t1 * rate + t2 * (1.0 - rate)) + 2 if type == 32: - theta = 2 * math.pi * t1 + dim * math.pi / self.number_of_variables - xy = 4 * (x - .5) + theta = 2 * math.pi * t1 + dim * math.pi / self.number_of_variables() + xy = 4 * (x - 0.5) beta = xy - 2 * t2 * math.sin(theta) return beta def __alpha_func(self, x: list, dim: int, type: int) -> list: - """ Control the PF shape. - """ + """Control the PF shape.""" alpha = [0.0] * dim if dim == 2: @@ -275,8 +276,7 @@

    Source code for jmetal.problem.multiobjective.lz09

    return alpha def __beta_func(self, x: list, type: int) -> float: - """ Control the distance. - """ + """Control the distance.""" beta = 0.0 dim = len(x) @@ -294,7 +294,7 @@

    Source code for jmetal.problem.multiobjective.lz09

    sum, xx = 0, 0 for i in range(dim): xx = 2 * x[i] - sum += (xx * xx - math.cos(4 * math.pi * xx) + 1) + sum += xx * xx - math.cos(4 * math.pi * xx) + 1 beta = 2.0 * sum / dim if type == 4: sum, prod, xx = 0, 1, 0 @@ -306,16 +306,18 @@

    Source code for jmetal.problem.multiobjective.lz09

    return beta -
    [docs] def objective(self, x_variables: list) -> list: +
    +[docs] + def objective(self, x_variables: list) -> list: aa = [] bb = [] cc = [] - y_objectives = [0.0] * self.number_of_objectives + y_objectives = [0.0] * self.number_of_objectives() - if self.number_of_objectives == 2: + if self.number_of_objectives() == 2: if self.ltype in [21, 22, 23, 24, 26]: - for n in range(1, self.number_of_variables): + for n in range(1, self.number_of_variables()): if n % 2 == 0: a = self.__ps_func2(x_variables[n], x_variables[0], n, self.ltype, 1) aa.append(a) @@ -331,7 +333,7 @@

    Source code for jmetal.problem.multiobjective.lz09

    y_objectives[0] = alpha[0] + h y_objectives[1] = alpha[1] + g if self.ltype == 25: - for n in range(1, self.number_of_variables): + for n in range(1, self.number_of_variables()): if n % 3 == 0: a = self.__ps_func2(x_variables[n], x_variables[0], n, self.ltype, 1) aa.append(a) @@ -353,10 +355,10 @@

    Source code for jmetal.problem.multiobjective.lz09

    y_objectives[0] = alpha[0] + h y_objectives[1] = alpha[1] + g - if self.number_of_objectives == 3: + if self.number_of_objectives() == 3: if self.ltype == 31 or self.ltype == 32: - for n in range(2, self.number_of_variables): + for n in range(2, self.number_of_variables()): a = self.__ps_func3(x_variables[n], x_variables[0], x_variables[1], n, self.ltype) if n % 3 == 0: @@ -378,98 +380,232 @@

    Source code for jmetal.problem.multiobjective.lz09

    return y_objectives
    -
    [docs] def get_name(self): - return 'LZ09'
    +
    +[docs] + def get_name(self): + return "LZ09"
    +
    + + + +
    +[docs] +class LZ09_F1(LZ09): + def __init__(self, number_of_variables=10): + super(LZ09_F1, self).__init__( + number_of_variables, dtype=1, ltype=21, ptype=21 + ) + self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ["f(x)", "f(y)"] + +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    + + +
    +[docs] + def name(self): + return "LZ09_F1"
    +
    + + + +
    +[docs] +class LZ09_F2(LZ09): + def __init__(self, number_of_variables=30): + super(LZ09_F2, self).__init__( + number_of_variables, dtype=1, ltype=22, ptype=21 + ) + + self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ["f(x)", "f(y)"] + +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    -
    [docs]class LZ09_F1(LZ09): - def __init__(self): - super(LZ09_F1, self).__init__(number_of_variables=10, number_of_objectives=2, number_of_constraints=0, - dtype=1, ltype=21, ptype=21) +
    +[docs] + def name(self): + return "LZ09_F2"
    +
    -
    [docs] def get_name(self): - return 'LZ09_F1'
    -
    [docs]class LZ09_F2(LZ09): +
    +[docs] +class LZ09_F3(LZ09): + def __init__(self, number_of_variables=30): + super(LZ09_F3, self).__init__( + number_of_variables, dtype=1, ltype=23, ptype=21 + ) + self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ["f(x)", "f(y)"] - def __init__(self): - super(LZ09_F2, self).__init__(number_of_variables=30, number_of_objectives=2, number_of_constraints=0, - dtype=1, ltype=22, ptype=21) +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    -
    [docs] def get_name(self): - return 'LZ09_F2'
    +
    +[docs] + def name(self): + return "LZ09_F3"
    +
    -
    [docs]class LZ09_F3(LZ09): - def __init__(self): - super(LZ09_F3, self).__init__(number_of_variables=30, number_of_objectives=2, number_of_constraints=0, - dtype=1, ltype=23, ptype=21) -
    [docs] def get_name(self): - return 'LZ09_F3'
    +
    +[docs] +class LZ09_F4(LZ09): + def __init__(self, number_of_variables=30): + super(LZ09_F4, self).__init__( + number_of_variables, dtype=1, ltype=24, ptype=21 + ) + self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ["f(x)", "f(y)"] +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    -
    [docs]class LZ09_F4(LZ09): - def __init__(self): - super(LZ09_F4, self).__init__(number_of_variables=30, number_of_objectives=2, number_of_constraints=0, - dtype=1, ltype=24, ptype=21) +
    +[docs] + def name(self): + return "LZ09_F4"
    +
    -
    [docs] def get_name(self): - return 'LZ09_F4'
    -
    [docs]class LZ09_F5(LZ09): +
    +[docs] +class LZ09_F5(LZ09): + def __init__(self, number_of_variables=30): + super(LZ09_F5, self).__init__( + number_of_variables, dtype=1, ltype=26, ptype=21 + ) + self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ["f(x)", "f(y)"] - def __init__(self): - super(LZ09_F5, self).__init__(number_of_variables=30, number_of_objectives=2, number_of_constraints=0, - dtype=1, ltype=26, ptype=21) +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    -
    [docs] def get_name(self): - return 'LZ09_F5'
    +
    +[docs] + def name(self): + return "LZ09_F5"
    +
    -
    [docs]class LZ09_F6(LZ09): - def __init__(self): - super(LZ09_F6, self).__init__(number_of_variables=10, number_of_objectives=3, number_of_constraints=0, - dtype=1, ltype=32, ptype=31) -
    [docs] def get_name(self): - return 'LZ09_F6'
    +
    +[docs] +class LZ09_F6(LZ09): + def __init__(self, number_of_variables=10): + super(LZ09_F6, self).__init__( + number_of_variables, dtype=1, ltype=32, ptype=31 + ) + self.obj_directions = [self.MINIMIZE, self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ["f(x)", "f(y)", "f(z)"] +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    -
    [docs]class LZ09_F7(LZ09): - def __init__(self): - super(LZ09_F7, self).__init__(number_of_variables=10, number_of_objectives=2, number_of_constraints=0, - dtype=3, ltype=21, ptype=21) +
    +[docs] + def name(self): + return "LZ09_F6"
    +
    -
    [docs] def get_name(self): - return 'LZ09_F7'
    -
    [docs]class LZ09_F8(LZ09): +
    +[docs] +class LZ09_F7(LZ09): + def __init__(self, number_of_variables=10): + super(LZ09_F7, self).__init__( + number_of_variables, dtype=3, ltype=21, ptype=21 + ) + + self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ["f(x)", "f(y)"] + +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    + + +
    +[docs] + def name(self): + return "LZ09_F7"
    +
    - def __init__(self): - super(LZ09_F8, self).__init__(number_of_variables=10, number_of_objectives=2, number_of_constraints=0, - dtype=4, ltype=21, ptype=21) -
    [docs] def get_name(self): - return 'LZ09_F8'
    +
    +[docs] +class LZ09_F8(LZ09): + def __init__(self, number_of_variables=10): + super(LZ09_F8, self).__init__( + number_of_variables, dtype=4, ltype=21, ptype=21 + ) + self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ["f(x)", "f(y)"] -
    [docs]class LZ09_F9(LZ09): +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    - def __init__(self): - super(LZ09_F9, self).__init__(number_of_variables=30, number_of_objectives=2, number_of_constraints=0, - dtype=1, ltype=22, ptype=22) -
    [docs] def get_name(self): - return 'LZ09_F9'
    +
    +[docs] + def name(self): + return "LZ09_F8"
    +
    + + + +
    +[docs] +class LZ09_F9(LZ09): + def __init__(self, number_of_variables=30): + super(LZ09_F9, self).__init__( + number_of_variables, dtype=1, ltype=22, ptype=22 + ) + + self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ["f(x)", "f(y)"] + +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    + + +
    +[docs] + def name(self): + return "LZ09_F9"
    +
    +
    @@ -486,8 +622,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.problem.multiobjective.unconstrained — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -110,10 +107,15 @@

    Table Of Contents

    Source code for jmetal.problem.multiobjective.unconstrained

     import random
    -from math import sqrt, exp, pow, sin
    +from math import exp, pow, sin, sqrt
     
    -from jmetal.core.problem import FloatProblem, BinaryProblem
    -from jmetal.core.solution import FloatSolution, BinarySolution
    +from jmetal.core.problem import BinaryProblem, FloatProblem, Problem
    +from jmetal.core.solution import (
    +    BinarySolution,
    +    CompositeSolution,
    +    FloatSolution,
    +    IntegerSolution,
    +)
     
     """
     .. module:: constrained
    @@ -124,31 +126,43 @@ 

    Source code for jmetal.problem.multiobjective.unconstrained

    """ -
    [docs]class Kursawe(FloatProblem): - """ Class representing problem Kursawe. """ +
    +[docs] +class Kursawe(FloatProblem): + """Class representing problem Kursawe.""" def __init__(self, number_of_variables: int = 3): super(Kursawe, self).__init__() - self.number_of_objectives = 2 - self.number_of_variables = number_of_variables - self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)'] + self.obj_labels = ["f(x)", "f(y)"] self.lower_bound = [-5.0 for _ in range(number_of_variables)] self.upper_bound = [5.0 for _ in range(number_of_variables)] - FloatSolution.lower_bound = self.lower_bound - FloatSolution.upper_bound = self.upper_bound +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    + + +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: - fx = [0.0 for _ in range(self.number_of_objectives)] - for i in range(self.number_of_variables - 1): + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: + fx = [0.0 for _ in range(self.number_of_objectives())] + for i in range(self.number_of_variables() - 1): xi = solution.variables[i] * solution.variables[i] xj = solution.variables[i + 1] * solution.variables[i + 1] aux = -0.2 * sqrt(xi + xj) fx[0] += -10 * exp(aux) + + for i in range(self.number_of_variables()): fx[1] += pow(abs(solution.variables[i]), 0.8) + 5.0 * sin(pow(solution.variables[i], 3.0)) solution.objectives[0] = fx[0] @@ -156,85 +170,129 @@

    Source code for jmetal.problem.multiobjective.unconstrained

    return solution
    -
    [docs] def get_name(self): - return 'Kursawe'
    + +
    +[docs] + def name(self): + return "Kursawe"
    +
    -
    [docs]class Fonseca(FloatProblem): +
    +[docs] +class Fonseca(FloatProblem): def __init__(self): super(Fonseca, self).__init__() - self.number_of_variables = 3 - self.number_of_objectives = 2 - self.number_of_constraints = 0 - self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)'] + self.obj_labels = ["f(x)", "f(y)"] + + number_of_variables = 3 + + self.lower_bound = number_of_variables * [-4] + self.upper_bound = number_of_variables * [4] - self.lower_bound = self.number_of_variables * [-4] - self.upper_bound = self.number_of_variables * [4] +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    - FloatSolution.lower_bound = self.lower_bound - FloatSolution.upper_bound = self.upper_bound -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: - n = self.number_of_variables - solution.objectives[0] = 1 - exp(-sum([(x - 1.0 / n ** 0.5) ** 2 for x in solution.variables])) - solution.objectives[1] = 1 - exp(-sum([(x + 1.0 / n ** 0.5) ** 2 for x in solution.variables])) +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    + + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: + n = self.number_of_variables() + solution.objectives[0] = 1 - exp(-sum([(x - 1.0 / n**0.5) ** 2 for x in solution.variables])) + solution.objectives[1] = 1 - exp(-sum([(x + 1.0 / n**0.5) ** 2 for x in solution.variables])) return solution
    -
    [docs] def get_name(self): - return 'Fonseca'
    + +
    +[docs] + def name(self): + return "Fonseca"
    +
    -
    [docs]class Schaffer(FloatProblem): +
    +[docs] +class Schaffer(FloatProblem): def __init__(self): super(Schaffer, self).__init__() - self.number_of_variables = 1 - self.number_of_objectives = 2 - self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)'] + self.obj_labels = ["f(x)", "f(y)"] + + self.lower_bound = [-1000] + self.upper_bound = [1000] + +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    - self.lower_bound = [-100000] - self.upper_bound = [100000] - FloatSolution.lower_bound = self.lower_bound - FloatSolution.upper_bound = self.upper_bound +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: value = solution.variables[0] - solution.objectives[0] = value ** 2 + solution.objectives[0] = value**2 solution.objectives[1] = (value - 2) ** 2 return solution
    -
    [docs] def get_name(self): - return 'Schaffer'
    + +
    +[docs] + def name(self): + return "Schaffer"
    +
    -
    [docs]class Viennet2(FloatProblem): +
    +[docs] +class Viennet2(FloatProblem): def __init__(self): super(Viennet2, self).__init__() - self.number_of_variables = 2 - self.number_of_objectives = 3 - self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['f(x)', 'f(y)', 'f(z)'] + self.obj_labels = ["f(x)", "f(y)", "f(z)"] + + number_of_variables = 2 + self.lower_bound = number_of_variables * [-4] + self.upper_bound = number_of_variables * [4] + +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    + - self.lower_bound = self.number_of_variables * [-4] - self.upper_bound = self.number_of_variables * [4] +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    - FloatSolution.lower_bound = self.lower_bound - FloatSolution.upper_bound = self.upper_bound -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: x0 = solution.variables[0] x1 = solution.variables[1] @@ -248,14 +306,20 @@

    Source code for jmetal.problem.multiobjective.unconstrained

    return solution
    -
    [docs] def get_name(self): - return 'Viennet2'
    + +
    +[docs] + def name(self): + return "Viennet2"
    +
    -
    [docs]class SubsetSum(BinaryProblem): +
    +[docs] +class SubsetSum(BinaryProblem): def __init__(self, C: int, W: list): - """ The goal is to find a subset S of W whose elements sum is closest to (without exceeding) C. + """The goal is to find a subset S of W whose elements sum is closest to (without exceeding) C. :param C: Large integer. :param W: Set of non-negative integers.""" @@ -269,9 +333,11 @@

    Source code for jmetal.problem.multiobjective.unconstrained

    self.number_of_constraints = 0 self.obj_directions = [self.MAXIMIZE, self.MINIMIZE] - self.obj_labels = ['Sum', 'No. of Objects'] + self.obj_labels = ["Sum", "No. of Objects"] -
    [docs] def evaluate(self, solution: BinarySolution) -> BinarySolution: +
    +[docs] + def evaluate(self, solution: BinarySolution) -> BinarySolution: total_sum = 0.0 number_of_objects = 0 @@ -291,31 +357,61 @@

    Source code for jmetal.problem.multiobjective.unconstrained

    return solution
    -
    [docs] def create_solution(self) -> BinarySolution: - new_solution = BinarySolution(number_of_variables=self.number_of_variables, - number_of_objectives=self.number_of_objectives) - new_solution.variables[0] = \ - [True if random.randint(0, 1) == 0 else False for _ in range(self.number_of_bits)] + +
    +[docs] + def create_solution(self) -> BinarySolution: + new_solution = BinarySolution( + number_of_variables=self.number_of_variables, number_of_objectives=self.number_of_objectives + ) + new_solution.variables[0] = [True if random.randint(0, 1) == 0 else False for _ in range(self.number_of_bits)] return new_solution
    -
    [docs] def get_name(self) -> str: - return 'Subset Sum'
    + +
    +[docs] + def name(self) -> str: + return "Subset Sum"
    +
    + -
    [docs]class OneZeroMax(BinaryProblem): +
    +[docs] +class OneZeroMax(BinaryProblem): + """ The implementation of the OneZeroMax problems defines a single binary variable. This variable + will contain the bit string representing the solutions. + """ def __init__(self, number_of_bits: int = 256): super(OneZeroMax, self).__init__() - self.number_of_bits = number_of_bits - self.number_of_objectives = 2 - self.number_of_variables = 1 - self.number_of_constraints = 0 + self.number_of_bits_per_variable = [number_of_bits] + + self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ["Zeroes", "Ones"] + +
    +[docs] + def number_of_variables(self) -> int: + return 1
    - self.obj_directions = [self.MINIMIZE] - self.obj_labels = ['Ones'] -
    [docs] def evaluate(self, solution: BinarySolution) -> BinarySolution: +
    +[docs] + def number_of_objectives(self) -> int: + return 2
    + + +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    + + +
    +[docs] + def evaluate(self, solution: BinarySolution) -> BinarySolution: counter_of_ones = 0 counter_of_zeroes = 0 for bits in solution.variables[0]: @@ -329,15 +425,96 @@

    Source code for jmetal.problem.multiobjective.unconstrained

    return solution
    -
    [docs] def create_solution(self) -> BinarySolution: - new_solution = BinarySolution(number_of_variables=self.number_of_variables, - number_of_objectives=self.number_of_objectives) - new_solution.variables[0] = \ - [True if random.randint(0, 1) == 0 else False for _ in range(self.number_of_bits)] + +
    +[docs] + def create_solution(self) -> BinarySolution: + new_solution = BinarySolution( + number_of_variables=self.number_of_variables(), number_of_objectives=self.number_of_objectives() + ) + new_solution.variables[0] = [True if random.randint(0, 1) == 0 else False for _ in range(self.number_of_bits_per_variable[0])] return new_solution
    -
    [docs] def get_name(self) -> str: - return 'OneZeroMax'
    + +
    +[docs] + def name(self) -> str: + return "OneZeroMax"
    +
    + + + +
    +[docs] +class MixedIntegerFloatProblem(Problem): + def __init__( + self, + number_of_integer_variables=10, + number_of_float_variables=10, + n=100, + m=-100, + lower_bound=-1000, + upper_bound=1000, + ): + super(MixedIntegerFloatProblem, self).__init__() + self.number_of_objectives = 2 + self.number_of_variables = 2 + self.number_of_constraints = 0 + + self.n = n + self.m = m + + self.float_lower_bound = [lower_bound for _ in range(number_of_float_variables)] + self.float_upper_bound = [upper_bound for _ in range(number_of_float_variables)] + self.int_lower_bound = [lower_bound for _ in range(number_of_integer_variables)] + self.int_upper_bound = [upper_bound for _ in range(number_of_integer_variables)] + + self.obj_directions = [self.MINIMIZE] + self.obj_labels = ["Ones"] + +
    +[docs] + def evaluate(self, solution: CompositeSolution) -> CompositeSolution: + distance_to_n = sum([abs(self.n - value) for value in solution.variables[0].variables]) + distance_to_m = sum([abs(self.m - value) for value in solution.variables[0].variables]) + + distance_to_n += sum([abs(self.n - value) for value in solution.variables[1].variables]) + distance_to_m += sum([abs(self.m - value) for value in solution.variables[1].variables]) + + solution.objectives[0] = distance_to_n + solution.objectives[1] = distance_to_m + + return solution
    + + +
    +[docs] + def create_solution(self) -> CompositeSolution: + integer_solution = IntegerSolution( + self.int_lower_bound, self.int_upper_bound, self.number_of_objectives, self.number_of_constraints + ) + float_solution = FloatSolution( + self.float_lower_bound, self.float_upper_bound, self.number_of_objectives, self.number_of_constraints + ) + + float_solution.variables = [ + random.uniform(self.float_lower_bound[i] * 1.0, self.float_upper_bound[i] * 1.0) + for i in range(len(self.float_lower_bound)) + ] + integer_solution.variables = [ + random.uniform(self.int_lower_bound[i], self.int_upper_bound[i]) + for i in range(len(self.int_lower_bound)) + ] + + return CompositeSolution([integer_solution, float_solution])
    + + +
    +[docs] + def name(self) -> str: + return "Mixed Integer Float Problem"
    +
    +
    @@ -354,8 +531,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.problem.multiobjective.zdt — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -109,10 +106,11 @@

    Table Of Contents

    Source code for jmetal.problem.multiobjective.zdt

    -from math import sqrt, pow, sin, pi, cos
    +import random
    +from math import cos, pi, pow, sin, sqrt, exp
     
    -from jmetal.core.problem import FloatProblem
    -from jmetal.core.solution import FloatSolution
    +from jmetal.core.problem import FloatProblem, BinaryProblem
    +from jmetal.core.solution import FloatSolution, BinarySolution
     
     """
     .. module:: ZDT
    @@ -123,28 +121,46 @@ 

    Source code for jmetal.problem.multiobjective.zdt

    """ -
    [docs]class ZDT1(FloatProblem): - """ Problem ZDT1. +
    +[docs] +class ZDT1(FloatProblem): + """Problem ZDT1. .. note:: Bi-objective unconstrained problem. The default number of variables is 30. .. note:: Continuous problem having a convex Pareto front """ - def __init__(self, number_of_variables: int=30): - """ :param number_of_variables: Number of decision variables of the problem. - """ + def __init__(self, number_of_variables: int = 30): + """:param number_of_variables: Number of decision variables of the problem.""" super(ZDT1, self).__init__() - self.number_of_variables = number_of_variables - self.number_of_objectives = 2 - self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] - self.obj_labels = ['x', 'y'] + self.obj_labels = ["x", "y"] + + self.lower_bound = number_of_variables * [0.0] + self.upper_bound = number_of_variables * [1.0] + +
    +[docs] + def number_of_objectives(self) -> int: + return len(self.obj_directions)
    + + +
    +[docs] + def number_of_variables(self) -> int: + return len(self.lower_bound)
    - self.lower_bound = self.number_of_variables * [0.0] - self.upper_bound = self.number_of_variables * [1.0] -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    + + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: g = self.eval_g(solution) h = self.eval_h(solution.variables[0], g) @@ -153,106 +169,296 @@

    Source code for jmetal.problem.multiobjective.zdt

    return solution
    -
    [docs] def eval_g(self, solution: FloatSolution): + +
    +[docs] + def eval_g(self, solution: FloatSolution): g = sum(solution.variables) - solution.variables[0] - constant = 9.0 / (solution.number_of_variables - 1) + constant = 9.0 / (len(solution.variables) - 1) return constant * g + 1.0
    -
    [docs] def eval_h(self, f: float, g: float) -> float: + +
    +[docs] + def eval_h(self, f: float, g: float) -> float: return 1.0 - sqrt(f / g)
    -
    [docs] def get_name(self): - return 'ZDT1'
    + +
    +[docs] + def name(self): + return "ZDT1"
    +
    + + + +class ZDT1Modified(ZDT1): + """Problem ZDT1Modified. + + .. note:: Version including a loop for increasing the computing time of the evaluation functions. + """ + + def __init__(self, number_of_variables=30): + super(ZDT1Modified, self).__init__(number_of_variables) + + def evaluate(self, solution: FloatSolution) -> FloatSolution: + s: float = 0.0 + for i in range(1000): + for j in range(10000): + s += i * 0.235 / 1.234 + 1.23525 * j + return super().evaluate(solution) -
    [docs]class ZDT2(ZDT1): - """ Problem ZDT2. +
    +[docs] +class ZDT1Modified(ZDT1): + """ Problem ZDT1Modified. + + .. note:: Version including a loop for increasing the computing time of the evaluation functions. + """ + def __init__(self, number_of_variables = 30): + super(ZDT1Modified, self).__init__(number_of_variables) + +
    +[docs] + def evaluate(self, solution:FloatSolution) -> FloatSolution: + s: float = 0.0 + for i in range(1000): + for j in range(10000): + s += i * 0.235 / 1.234 + 1.23525 * j + return super().evaluate(solution)
    +
    + + + +
    +[docs] +class ZDT2(ZDT1): + """Problem ZDT2. .. note:: Bi-objective unconstrained problem. The default number of variables is 30. .. note:: Continuous problem having a non-convex Pareto front """ -
    [docs] def eval_h(self, f: float, g: float) -> float: +
    +[docs] + def eval_h(self, f: float, g: float) -> float: return 1.0 - pow(f / g, 2.0)
    -
    [docs] def get_name(self): - return 'ZDT2'
    + +
    +[docs] + def name(self): + return "ZDT2"
    +
    + -
    [docs]class ZDT3(ZDT1): - """ Problem ZDT3. +
    +[docs] +class ZDT3(ZDT1): + """Problem ZDT3. .. note:: Bi-objective unconstrained problem. The default number of variables is 30. .. note:: Continuous problem having a partitioned Pareto front """ -
    [docs] def eval_h(self, f: float, g: float) -> float: + +
    +[docs] + def eval_h(self, f: float, g: float) -> float: return 1.0 - sqrt(f / g) - (f / g) * sin(10.0 * f * pi)
    -
    [docs] def get_name(self): - return 'ZDT3'
    + +
    +[docs] + def name(self): + return "ZDT3"
    +
    -
    [docs]class ZDT4(ZDT1): - """ Problem ZDT4. + +
    +[docs] +class ZDT4(ZDT1): + """Problem ZDT4. .. note:: Bi-objective unconstrained problem. The default number of variables is 10. .. note:: Continuous multi-modal problem having a convex Pareto front """ - def __init__(self, number_of_variables: int=10): - """ :param number_of_variables: Number of decision variables of the problem. - """ - super(ZDT4, self).__init__(number_of_variables=number_of_variables) - self.lower_bound = self.number_of_variables * [-5.0] - self.upper_bound = self.number_of_variables * [5.0] + def __init__(self, number_of_variables: int = 10): + """:param number_of_variables: Number of decision variables of the problem.""" + super(ZDT4, self).__init__() + self.lower_bound = number_of_variables * [-5.0] + self.upper_bound = number_of_variables * [5.0] self.lower_bound[0] = 0.0 self.upper_bound[0] = 1.0 -
    [docs] def eval_g(self, solution: FloatSolution): +
    +[docs] + def eval_g(self, solution: FloatSolution): g = 0.0 - for i in range(1, solution.number_of_variables): + for i in range(1, len(solution.variables)): g += pow(solution.variables[i], 2.0) - 10.0 * cos(4.0 * pi * solution.variables[i]) - g += 1.0 + 10.0 * (solution.number_of_variables - 1) + g += 1.0 + 10.0 * (len(solution.variables) - 1) return g
    -
    [docs] def eval_h(self, f: float, g: float) -> float: + +
    +[docs] + def eval_h(self, f: float, g: float) -> float: return 1.0 - sqrt(f / g)
    -
    [docs] def get_name(self): - return 'ZDT4'
    + +
    +[docs] + def name(self): + return "ZDT4"
    +
    + + + +
    +[docs] +class ZDT5(BinaryProblem): + """Problem ZDT5. + + .. note:: Bi-objective binary unconstrained problem. The default number of variables is 11. + """ + + def __init__(self, number_of_variables: int = 11): + """:param number_of_bits: Number of bits of each variable of the problem.""" + super(ZDT5, self).__init__() + + self.number_of_bits_per_variable = [5 for _ in range(0, number_of_variables)] + self.number_of_bits_per_variable[0] = 30 + + self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ["x", "y"] + +
    +[docs] + def number_of_variables(self) -> int: + return len(self.number_of_bits_per_variable)
    + + +
    +[docs] + def number_of_objectives(self) -> int: + return 2
    + + +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    + + +
    +[docs] + def evaluate(self, solution: BinarySolution) -> BinarySolution: + solution.objectives[0] = 1.0 + solution.cardinality(0) + + g = self.eval_g(solution) + h = 1.0 / solution.objectives[0] + + solution.objectives[1] = h * g + + return solution
    + + +
    +[docs] + def eval_g(self, solution: BinarySolution): + result = 0.0 + for i in range(1, len(solution.variables)): + result = result + self.eval_v(solution.cardinality(i)) + + return result
    -
    [docs]class ZDT6(ZDT1): - """ Problem ZDT6. +
    +[docs] + def eval_v(self, value): + if value < 5.0: + return 2.0 + value + else: + return 1.0
    + + +
    +[docs] + def create_solution(self) -> BinarySolution: + new_solution = BinarySolution(number_of_variables=self.number_of_variables(), number_of_objectives=2) + for i in range(self.number_of_variables()): + new_solution.variables[i] = [True if random.randint(0, 1) == 0 else False for _ in range(self.number_of_bits_per_variable[i])] + return new_solution
    + + +
    +[docs] + def name(self): + return "ZDT5"
    +
    + + + +
    +[docs] +class ZDT6(ZDT1): + """Problem ZDT6. .. note:: Bi-objective unconstrained problem. The default number of variables is 10. .. note:: Continuous problem having a non-convex Pareto front """ - def __init__(self, number_of_variables: int=10): - """ :param number_of_variables: Number of decision variables of the problem. - """ + def __init__(self, number_of_variables: int = 10): + """:param number_of_variables: Number of decision variables of the problem.""" super(ZDT6, self).__init__(number_of_variables=number_of_variables) -
    [docs] def eval_g(self, solution: FloatSolution): +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: + solution.objectives[0] = ( + 1.0 - exp(-4.0 * solution.variables[0]) * (sin(6.0 * pi * solution.variables[0])) ** 6.0 + ) + + g = self.eval_g(solution) + h = self.eval_h(solution.objectives[0], g) + solution.objectives[1] = h * g + + return solution
    + + +
    +[docs] + def eval_g(self, solution: FloatSolution): g = sum(solution.variables) - solution.variables[0] - g = g / (solution.number_of_variables - 1) + g = g / (len(solution.variables) - 1) g = pow(g, 0.25) g = 9.0 * g g = 1.0 + g return g
    -
    [docs] def eval_h(self, f: float, g: float) -> float: + +
    +[docs] + def eval_h(self, f: float, g: float) -> float: return 1.0 - pow(f / g, 2.0)
    -
    [docs] def get_name(self): - return 'ZDT6'
    + +
    +[docs] + def name(self): + return "ZDT6"
    +
    +
    @@ -269,8 +475,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.problem.singleobjective.knapsack — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -125,11 +122,20 @@

    Source code for jmetal.problem.singleobjective.knapsack

    """ -
    [docs]class Knapsack(BinaryProblem): - """ Class representing Knapsack Problem. """ - - def __init__(self, number_of_items: int = 50, capacity: float = 1000, weights: list = None, - profits: list = None, from_file: bool = False, filename: str = None): +
    +[docs] +class Knapsack(BinaryProblem): + """Class representing Knapsack Problem.""" + + def __init__( + self, + number_of_items: int = 50, + capacity: float = 1000, + weights: list = None, + profits: list = None, + from_file: bool = False, + filename: str = None, + ): super(Knapsack, self).__init__() if from_file: @@ -140,13 +146,28 @@

    Source code for jmetal.problem.singleobjective.knapsack

    self.profits = profits self.number_of_bits = number_of_items - self.number_of_variables = 1 self.obj_directions = [self.MAXIMIZE] - self.number_of_objectives = 1 - self.number_of_constraints = 1 + +
    +[docs] + def number_of_variables(self) -> int: + return 1
    + + +
    +[docs] + def number_of_objectives(self) -> int: + return 1
    + + +
    +[docs] + def number_of_constraints(self) -> int: + return 1
    + def __read_from_file(self, filename: str): - """ + """ This function reads a Knapsack Problem instance from a file. It expects the following format: @@ -159,7 +180,7 @@

    Source code for jmetal.problem.singleobjective.knapsack

    """ if filename is None: - raise FileNotFoundError('Filename can not be None') + raise FileNotFoundError("Filename can not be None") with open(filename) as file: lines = file.readlines() @@ -168,12 +189,14 @@

    Source code for jmetal.problem.singleobjective.knapsack

    self.number_of_bits = int(data[0][0]) self.capacity = float(data[1][0]) - weights_and_profits = np.asfarray(data[2:], dtype=np.float32) + weights_and_profits = np.asarray(data[2:], dtype=np.float32) self.weights = weights_and_profits[:, 0] self.profits = weights_and_profits[:, 1] -
    [docs] def evaluate(self, solution: BinarySolution) -> BinarySolution: +
    +[docs] + def evaluate(self, solution: BinarySolution) -> BinarySolution: total_profits = 0.0 total_weigths = 0.0 @@ -188,18 +211,25 @@

    Source code for jmetal.problem.singleobjective.knapsack

    solution.objectives[0] = -1.0 * total_profits return solution
    -
    [docs] def create_solution(self) -> BinarySolution: - new_solution = BinarySolution(number_of_variables=self.number_of_variables, - number_of_objectives=self.number_of_objectives) - new_solution.variables[0] = \ - [True if random.randint(0, 1) == 0 else False for _ in range( - self.number_of_bits)] +
    +[docs] + def create_solution(self) -> BinarySolution: + new_solution = BinarySolution( + number_of_variables=self.number_of_variables(), number_of_objectives=self.number_of_objectives() + ) + + new_solution.variables[0] = [True if random.randint(0, 1) == 0 else False for _ in range(self.number_of_bits)] return new_solution
    -
    [docs] def get_name(self): - return 'Knapsack'
    + +
    +[docs] + def name(self): + return "Knapsack"
    +
    +
    @@ -216,8 +246,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.problem.singleobjective.tsp — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -125,23 +122,37 @@

    Source code for jmetal.problem.singleobjective.tsp

    """ -
    [docs]class TSP(PermutationProblem): - """ Class representing TSP Problem. """ +
    +[docs] +class TSP(PermutationProblem): + """Class representing TSP Problem.""" def __init__(self, instance: str = None): super(TSP, self).__init__() - distance_matrix, number_of_cities = self.__read_from_file(instance) + self.distance_matrix, self.number_of_cities = self.__read_from_file(instance) + self.obj_directions = [self.MINIMIZE] - self.distance_matrix = distance_matrix +
    +[docs] + def number_of_variables(self) -> int: + return self.number_of_cities
    + + +
    +[docs] + def number_of_objectives(self) -> int: + return 1
    + + +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    - self.obj_directions = [self.MINIMIZE] - self.number_of_variables = number_of_cities - self.number_of_objectives = 1 - self.number_of_constraints = 0 def __read_from_file(self, filename: str): - """ + """ This function reads a TSP Problem instance from a file. :param filename: File which describes the instance. @@ -149,24 +160,24 @@

    Source code for jmetal.problem.singleobjective.tsp

    """ if filename is None: - raise FileNotFoundError('Filename can not be None') + raise FileNotFoundError("Filename can not be None") with open(filename) as file: lines = file.readlines() data = [line.lstrip() for line in lines if line != ""] - dimension = re.compile(r'[^\d]+') + dimension = re.compile(r"[^\d]+") for item in data: - if item.startswith('DIMENSION'): - dimension = int(dimension.sub('', item)) + if item.startswith("DIMENSION"): + dimension = int(dimension.sub("", item)) break c = [-1.0] * (2 * dimension) for item in data: if item[0].isdigit(): - j, city_a, city_b = [int(x.strip()) for x in item.split(' ')] + j, city_a, city_b = [int(x.strip()) for x in item.split(" ")] c[2 * (j - 1)] = city_a c[2 * (j - 1) + 1] = city_b @@ -183,10 +194,12 @@

    Source code for jmetal.problem.singleobjective.tsp

    return matrix, dimension -
    [docs] def evaluate(self, solution: PermutationSolution) -> PermutationSolution: +
    +[docs] + def evaluate(self, solution: PermutationSolution) -> PermutationSolution: fitness = 0 - for i in range(self.number_of_variables - 1): + for i in range(self.number_of_variables() - 1): x = solution.variables[i] y = solution.variables[i + 1] @@ -199,19 +212,24 @@

    Source code for jmetal.problem.singleobjective.tsp

    return solution
    -
    [docs] def create_solution(self) -> PermutationSolution: - new_solution = PermutationSolution(number_of_variables=self.number_of_variables, - number_of_objectives=self.number_of_objectives) - new_solution.variables = random.sample(range(self.number_of_variables), k=self.number_of_variables) + +
    +[docs] + def create_solution(self) -> PermutationSolution: + new_solution = PermutationSolution( + number_of_variables=self.number_of_variables(), number_of_objectives=self.number_of_objectives() + ) + new_solution.variables = random.sample(range(self.number_of_variables()), k=self.number_of_variables()) return new_solution
    - @property - def number_of_cities(self): - return self.number_of_variables -
    [docs] def get_name(self): - return 'Symmetric TSP'
    +
    +[docs] + def name(self): + return "Symmetric TSP"
    +
    +
    @@ -228,8 +246,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.problem.singleobjective.unconstrained — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -124,19 +121,41 @@

    Source code for jmetal.problem.singleobjective.unconstrained

    """ -
    [docs]class OneMax(BinaryProblem): +
    +[docs] +class OneMax(BinaryProblem): + """ The implementation of the OneMax problems defines a single binary variable. This variable + will contain the bit string representing the solutions. + """ def __init__(self, number_of_bits: int = 256): super(OneMax, self).__init__() - self.number_of_bits = number_of_bits - self.number_of_objectives = 1 - self.number_of_variables = 1 - self.number_of_constraints = 0 + self.number_of_bits_per_variable = [number_of_bits] self.obj_directions = [self.MINIMIZE] - self.obj_labels = ['Ones'] + self.obj_labels = ["Ones"] + +
    +[docs] + def number_of_variables(self) -> int: + return 1
    + + +
    +[docs] + def number_of_objectives(self) -> int: + return 1
    -
    [docs] def evaluate(self, solution: BinarySolution) -> BinarySolution: + +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    + + +
    +[docs] + def evaluate(self, solution: BinarySolution) -> BinarySolution: counter_of_ones = 0 for bits in solution.variables[0]: if bits: @@ -146,26 +165,31 @@

    Source code for jmetal.problem.singleobjective.unconstrained

    return solution
    -
    [docs] def create_solution(self) -> BinarySolution: + +
    +[docs] + def create_solution(self) -> BinarySolution: new_solution = BinarySolution(number_of_variables=1, number_of_objectives=1) - new_solution.variables[0] = \ - [True if random.randint(0, 1) == 0 else False for _ in range(self.number_of_bits)] + new_solution.variables[0] = [True if random.randint(0, 1) == 0 else False for _ in range(self.number_of_bits_per_variable[0])] return new_solution
    -
    [docs] def get_name(self) -> str: - return 'OneMax'
    + +
    +[docs] + def name(self) -> str: + return "OneMax"
    +
    -
    [docs]class Sphere(FloatProblem): +
    +[docs] +class Sphere(FloatProblem): def __init__(self, number_of_variables: int = 10): super(Sphere, self).__init__() - self.number_of_objectives = 1 - self.number_of_variables = number_of_variables - self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE] - self.obj_labels = ['f(x)'] + self.obj_labels = ["f(x)"] self.lower_bound = [-5.12 for _ in range(number_of_variables)] self.upper_bound = [5.12 for _ in range(number_of_variables)] @@ -173,7 +197,21 @@

    Source code for jmetal.problem.singleobjective.unconstrained

    FloatSolution.lower_bound = self.lower_bound FloatSolution.upper_bound = self.upper_bound -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def number_of_objectives(self) -> int: + return 1
    + + +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    + + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: total = 0.0 for x in solution.variables: total += x * x @@ -182,20 +220,23 @@

    Source code for jmetal.problem.singleobjective.unconstrained

    return solution
    -
    [docs] def get_name(self) -> str: - return 'Sphere'
    + +
    +[docs] + def name(self) -> str: + return "Sphere"
    +
    -
    [docs]class Rastrigin(FloatProblem): +
    +[docs] +class Rastrigin(FloatProblem): def __init__(self, number_of_variables: int = 10): super(Rastrigin, self).__init__() - self.number_of_objectives = 1 - self.number_of_variables = number_of_variables - self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE] - self.obj_labels = ['f(x)'] + self.obj_labels = ["f(x)"] self.lower_bound = [-5.12 for _ in range(number_of_variables)] self.upper_bound = [5.12 for _ in range(number_of_variables)] @@ -203,26 +244,46 @@

    Source code for jmetal.problem.singleobjective.unconstrained

    FloatSolution.lower_bound = self.lower_bound FloatSolution.upper_bound = self.upper_bound -
    [docs] def evaluate(self, solution: FloatSolution) -> FloatSolution: +
    +[docs] + def number_of_objectives(self) -> int: + return 1
    + + +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    + + +
    +[docs] + def evaluate(self, solution: FloatSolution) -> FloatSolution: a = 10.0 - result = a * solution.number_of_variables + result = a * len(solution.variables) x = solution.variables - for i in range(solution.number_of_variables): + for i in range(len(solution.variables)): result += x[i] * x[i] - a * math.cos(2 * math.pi * x[i]) solution.objectives[0] = result return solution
    -
    [docs] def get_name(self) -> str: - return 'Rastrigin'
    + +
    +[docs] + def name(self) -> str: + return "Rastrigin"
    +
    -
    [docs]class SubsetSum(BinaryProblem): +
    +[docs] +class SubsetSum(BinaryProblem): def __init__(self, C: int, W: list): - """ The goal is to find a subset S of W whose elements sum is closest to (without exceeding) C. + """The goal is to find a subset S of W whose elements sum is closest to (without exceeding) C. :param C: Large integer. :param W: Set of non-negative integers.""" @@ -231,14 +292,30 @@

    Source code for jmetal.problem.singleobjective.unconstrained

    self.W = W self.number_of_bits = len(self.W) - self.number_of_objectives = 1 - self.number_of_variables = 1 - self.number_of_constraints = 0 self.obj_directions = [self.MAXIMIZE] - self.obj_labels = ['Sum'] + self.obj_labels = ["Sum"] + +
    +[docs] + def number_of_variables(self) -> int: + return 1
    + +
    +[docs] + def number_of_objectives(self) -> int: + return 1
    -
    [docs] def evaluate(self, solution: BinarySolution) -> BinarySolution: + +
    +[docs] + def number_of_constraints(self) -> int: + return 0
    + + +
    +[docs] + def evaluate(self, solution: BinarySolution) -> BinarySolution: total_sum = 0.0 for index, bits in enumerate(solution.variables[0]): @@ -255,16 +332,24 @@

    Source code for jmetal.problem.singleobjective.unconstrained

    return solution
    -
    [docs] def create_solution(self) -> BinarySolution: - new_solution = BinarySolution(number_of_variables=self.number_of_variables, - number_of_objectives=self.number_of_objectives) - new_solution.variables[0] = \ - [True if random.randint(0, 1) == 0 else False for _ in range(self.number_of_bits)] + +
    +[docs] + def create_solution(self) -> BinarySolution: + new_solution = BinarySolution( + number_of_variables=self.number_of_variables(), number_of_objectives=self.number_of_objectives() + ) + new_solution.variables[0] = [True if random.randint(0, 1) == 0 else False for _ in range(self.number_of_bits)] return new_solution
    -
    [docs] def get_name(self) -> str: - return 'Subset Sum'
    + +
    +[docs] + def name(self) -> str: + return "Subset Sum"
    +
    +
    @@ -281,8 +366,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.util.evaluator — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -111,8 +108,8 @@

    Table Of Contents

    Source code for jmetal.util.evaluator

     import functools
     from abc import ABC, abstractmethod
    -from multiprocessing.pool import ThreadPool, Pool
    -from typing import TypeVar, List, Generic
    +from multiprocessing.pool import Pool, ThreadPool
    +from typing import Generic, List, TypeVar
     
     try:
         import dask
    @@ -126,11 +123,10 @@ 

    Source code for jmetal.util.evaluator

     
     from jmetal.core.problem import Problem
     
    -S = TypeVar('S')
    +S = TypeVar("S")
     
     
     class Evaluator(Generic[S], ABC):
    -
         @abstractmethod
         def evaluate(self, solution_list: List[S], problem: Problem) -> List[S]:
             pass
    @@ -140,49 +136,69 @@ 

    Source code for jmetal.util.evaluator

             problem.evaluate(solution)
     
     
    -
    [docs]class SequentialEvaluator(Evaluator[S]): - -
    [docs] def evaluate(self, solution_list: List[S], problem: Problem) -> List[S]: +
    +[docs] +class SequentialEvaluator(Evaluator[S]): +
    +[docs] + def evaluate(self, solution_list: List[S], problem: Problem) -> List[S]: for solution in solution_list: Evaluator.evaluate_solution(solution, problem) - return solution_list
    + return solution_list
    +
    -
    [docs]class MapEvaluator(Evaluator[S]): +
    +[docs] +class MapEvaluator(Evaluator[S]): def __init__(self, processes: int = None): self.pool = ThreadPool(processes) -
    [docs] def evaluate(self, solution_list: List[S], problem: Problem) -> List[S]: +
    +[docs] + def evaluate(self, solution_list: List[S], problem: Problem) -> List[S]: self.pool.map(lambda solution: Evaluator.evaluate_solution(solution, problem), solution_list) - return solution_list
    + return solution_list
    +
    + -
    [docs]class MultiprocessEvaluator(Evaluator[S]): +
    +[docs] +class MultiprocessEvaluator(Evaluator[S]): def __init__(self, processes: int = None): super().__init__() self.pool = Pool(processes) -
    [docs] def evaluate(self, solution_list: List[S], problem: Problem) -> List[S]: - return self.pool.map(functools.partial(evaluate_solution, problem=problem), solution_list)
    +
    +[docs] + def evaluate(self, solution_list: List[S], problem: Problem) -> List[S]: + return self.pool.map(functools.partial(evaluate_solution, problem=problem), solution_list)
    +
    + -
    [docs]class SparkEvaluator(Evaluator[S]): +
    +[docs] +class SparkEvaluator(Evaluator[S]): def __init__(self, processes: int = 8): - self.spark_conf = SparkConf().setAppName("jmetalpy").setMaster(f"local[{processes}]") + self.spark_conf = SparkConf().setAppName("jmetalpy").setMaster(f"local[{processes}]") self.spark_context = SparkContext(conf=self.spark_conf) logger = self.spark_context._jvm.org.apache.log4j logger.LogManager.getLogger("org").setLevel(logger.Level.WARN) -
    [docs] def evaluate(self, solution_list: List[S], problem: Problem) -> List[S]: +
    +[docs] + def evaluate(self, solution_list: List[S], problem: Problem) -> List[S]: solutions_to_evaluate = self.spark_context.parallelize(solution_list) - return solutions_to_evaluate \ - .map(lambda s: problem.evaluate(s)) \ - .collect()
    + return solutions_to_evaluate.map(lambda s: problem.evaluate(s)).collect()
    +
    + def evaluate_solution(solution, problem): @@ -190,15 +206,23 @@

    Source code for jmetal.util.evaluator

         return solution
     
     
    -
    [docs]class DaskEvaluator(Evaluator[S]): - def __init__(self, scheduler='processes'): +
    +[docs] +class DaskEvaluator(Evaluator[S]): + def __init__(self, scheduler="processes"): self.scheduler = scheduler -
    [docs] def evaluate(self, solution_list: List[S], problem: Problem) -> List[S]: +
    +[docs] + def evaluate(self, solution_list: List[S], problem: Problem) -> List[S]: with dask.config.set(scheduler=self.scheduler): - return list(dask.compute(*[ - dask.delayed(evaluate_solution)(solution=solution, problem=problem) for solution in solution_list - ]))
    + return list( + dask.compute( + *[dask.delayed(evaluate_solution)(solution=solution, problem=problem) for solution in solution_list] + ) + )
    +
    +
    @@ -215,8 +239,9 @@

    Navigation

  • modules |
  • - - + + +
    - - - - - - - + jmetal.util.observer — jMetalPy 1.7.0 documentation + + + + + + + @@ -39,8 +35,9 @@

    Navigation

  • modules |
  • - - + + +
    @@ -51,7 +48,7 @@

    Navigation

    @@ -114,17 +111,18 @@

    Source code for jmetal.util.observer

     from pathlib import Path
     from typing import List, TypeVar
     
    +import numpy as np
     from tqdm import tqdm
     
     from jmetal.core.observer import Observer
     from jmetal.core.problem import DynamicProblem
     from jmetal.core.quality_indicator import InvertedGenerationalDistance
    -from jmetal.lab.visualization import StreamingPlot, Plot
    +from jmetal.lab.visualization import Plot, StreamingPlot
     from jmetal.util.solution import print_function_values_to_file
     
    -S = TypeVar('S')
    +S = TypeVar("S")
     
    -LOGGER = logging.getLogger('jmetal')
    +LOGGER = logging.getLogger("jmetal")
     
     """
     .. module:: observer
    @@ -135,10 +133,11 @@ 

    Source code for jmetal.util.observer

     """
     
     
    -
    [docs]class ProgressBarObserver(Observer): - +
    +[docs] +class ProgressBarObserver(Observer): def __init__(self, max: int) -> None: - """ Show a smart progress meter with the number of evaluations and computing time. + """Show a smart progress meter with the number of evaluations and computing time. :param max: Number of expected iterations. """ @@ -146,31 +145,38 @@

    Source code for jmetal.util.observer

             self.progress = 0
             self._max = max
     
    -
    [docs] def update(self, *args, **kwargs): +
    +[docs] + def update(self, *args, **kwargs): if not self.progress_bar: - self.progress_bar = tqdm(total=self._max, ascii=True, desc='Progress') + self.progress_bar = tqdm(total=self._max, ascii=True, desc="Progress") - evaluations = kwargs['EVALUATIONS'] + evaluations = kwargs["EVALUATIONS"] self.progress_bar.update(evaluations - self.progress) self.progress = evaluations if self.progress >= self._max: - self.progress_bar.close()
    + self.progress_bar.close()
    +
    -
    [docs]class BasicObserver(Observer): - def __init__(self, frequency: float = 1.0) -> None: - """ Show the number of evaluations, best fitness and computing time. +
    +[docs] +class BasicObserver(Observer): + def __init__(self, frequency: int = 1) -> None: + """Show the number of evaluations, the best fitness and the computing time. + :param frequency: Display frequency.""" - :param frequency: Display frequency. """ self.display_frequency = frequency -
    [docs] def update(self, *args, **kwargs): - computing_time = kwargs['COMPUTING_TIME'] - evaluations = kwargs['EVALUATIONS'] - solutions = kwargs['SOLUTIONS'] +
    +[docs] + def update(self, *args, **kwargs): + computing_time = kwargs["COMPUTING_TIME"] + evaluations = kwargs["EVALUATIONS"] + solutions = kwargs["SOLUTIONS"] if (evaluations % self.display_frequency) == 0 and solutions: if type(solutions) == list: @@ -179,23 +185,26 @@

    Source code for jmetal.util.observer

                     fitness = solutions.objectives
     
                 LOGGER.info(
    -                'Evaluations: {} \n Best fitness: {} \n Computing time: {}'.format(
    -                    evaluations, fitness, computing_time
    -                )
    -            )
    + "Evaluations: {} \n Best fitness: {} \n Computing time: {}".format(evaluations, fitness, computing_time) + )
    +
    -
    [docs]class PrintObjectivesObserver(Observer): - def __init__(self, frequency: float = 1.0) -> None: - """ Show the number of evaluations, best fitness and computing time. +
    +[docs] +class PrintObjectivesObserver(Observer): + def __init__(self, frequency: int = 1) -> None: + """Show the number of evaluations, best fitness and computing time. - :param frequency: Display frequency. """ + :param frequency: Display frequency.""" self.display_frequency = frequency -
    [docs] def update(self, *args, **kwargs): - evaluations = kwargs['EVALUATIONS'] - solutions = kwargs['SOLUTIONS'] +
    +[docs] + def update(self, *args, **kwargs): + evaluations = kwargs["EVALUATIONS"] + solutions = kwargs["SOLUTIONS"] if (evaluations % self.display_frequency) == 0 and solutions: if type(solutions) == list: @@ -203,124 +212,137 @@

    Source code for jmetal.util.observer

                 else:
                     fitness = solutions.objectives
     
    -            LOGGER.info(
    -                'Evaluations: {}. fitness: {}'.format(
    -                    evaluations, fitness
    -                )
    -            )
    + LOGGER.info("Evaluations: {}. fitness: {}".format(evaluations, fitness))
    +
    -
    [docs]class WriteFrontToFileObserver(Observer): +
    +[docs] +class WriteFrontToFileObserver(Observer): def __init__(self, output_directory: str) -> None: - """ Write function values of the front into files. + """Write function values of the front into files. - :param output_directory: Output directory. Each front will be saved on a file `FUN.x`. """ + :param output_directory: Output directory. Each front will be saved on a file `FUN.x`.""" self.counter = 0 self.directory = output_directory if Path(self.directory).is_dir(): - LOGGER.warning('Directory {} exists. Removing contents.'.format(self.directory)) + LOGGER.warning("Directory {} exists. Removing contents.".format(self.directory)) for file in os.listdir(self.directory): - os.remove('{0}/{1}'.format(self.directory, file)) + os.remove("{0}/{1}".format(self.directory, file)) else: - LOGGER.warning('Directory {} does not exist. Creating it.'.format(self.directory)) + LOGGER.warning("Directory {} does not exist. Creating it.".format(self.directory)) Path(self.directory).mkdir(parents=True) -
    [docs] def update(self, *args, **kwargs): - problem = kwargs['PROBLEM'] - solutions = kwargs['SOLUTIONS'] +
    +[docs] + def update(self, *args, **kwargs): + problem = kwargs["PROBLEM"] + solutions = kwargs["SOLUTIONS"] if solutions: if isinstance(problem, DynamicProblem): - termination_criterion_is_met = kwargs.get('TERMINATION_CRITERIA_IS_MET', None) + termination_criterion_is_met = kwargs.get("TERMINATION_CRITERIA_IS_MET", None) if termination_criterion_is_met: - print_function_values_to_file(solutions, '{}/FUN.{}'.format(self.directory, self.counter)) + print_function_values_to_file(solutions, "{}/FUN.{}".format(self.directory, self.counter)) self.counter += 1 else: - print_function_values_to_file(solutions, '{}/FUN.{}'.format(self.directory, self.counter)) - self.counter += 1
    + print_function_values_to_file(solutions, "{}/FUN.{}".format(self.directory, self.counter)) + self.counter += 1
    +
    -
    [docs]class PlotFrontToFileObserver(Observer): +
    +[docs] +class PlotFrontToFileObserver(Observer): def __init__(self, output_directory: str, step: int = 100, **kwargs) -> None: - """ Plot and save Pareto front approximations into files. + """Plot and save Pareto front approximations into files. :param output_directory: Output directory. """ self.directory = output_directory - self.plot_front = Plot(title='Pareto front approximation', **kwargs) + self.plot_front = Plot(title="Pareto front approximation", **kwargs) self.last_front = [] self.fronts = [] self.counter = 0 self.step = step if Path(self.directory).is_dir(): - LOGGER.warning('Directory {} exists. Removing contents.'.format(self.directory)) + LOGGER.warning("Directory {} exists. Removing contents.".format(self.directory)) for file in os.listdir(self.directory): - os.remove('{0}/{1}'.format(self.directory, file)) + os.remove("{0}/{1}".format(self.directory, file)) else: - LOGGER.warning('Directory {} does not exist. Creating it.'.format(self.directory)) + LOGGER.warning("Directory {} does not exist. Creating it.".format(self.directory)) Path(self.directory).mkdir(parents=True) -
    [docs] def update(self, *args, **kwargs): - problem = kwargs['PROBLEM'] - solutions = kwargs['SOLUTIONS'] - evaluations = kwargs['EVALUATIONS'] +
    +[docs] + def update(self, *args, **kwargs): + problem = kwargs["PROBLEM"] + solutions = kwargs["SOLUTIONS"] + evaluations = kwargs["EVALUATIONS"] if solutions: if (evaluations % self.step) == 0: if isinstance(problem, DynamicProblem): - termination_criterion_is_met = kwargs.get('TERMINATION_CRITERIA_IS_MET', None) + termination_criterion_is_met = kwargs.get("TERMINATION_CRITERIA_IS_MET", None) if termination_criterion_is_met: if self.counter > 0: - igd = InvertedGenerationalDistance(self.last_front) - igd_value = igd.compute(solutions) + igd = InvertedGenerationalDistance(np.array([s.objectives for s in self.last_front])) + igd_value = igd.compute(np.array([s.objectives for s in solutions])) else: igd_value = 1 if igd_value > 0.005: self.fronts += solutions - self.plot_front.plot([self.fronts], - label=problem.get_name(), - filename=f'{self.directory}/front-{evaluations}') + self.plot_front.plot( + [self.fronts], + label=problem.get_name(), + filename=f"{self.directory}/front-{evaluations}", + ) self.counter += 1 self.last_front = solutions else: - self.plot_front.plot([solutions], - label=f'{evaluations} evaluations', - filename=f'{self.directory}/front-{evaluations}') - self.counter += 1
    + self.plot_front.plot( + [solutions], + label=f"{evaluations} evaluations", + filename=f"{self.directory}/front-{evaluations}", + ) + self.counter += 1
    +
    -
    [docs]class VisualizerObserver(Observer): - def __init__(self, - reference_front: List[S] = None, - reference_point: list = None, - display_frequency: float = 1.0) -> None: +
    +[docs] +class VisualizerObserver(Observer): + def __init__( + self, reference_front: List[S] = None, reference_point: list = None, display_frequency: int = 1 + ) -> None: self.figure = None self.display_frequency = display_frequency self.reference_point = reference_point self.reference_front = reference_front -
    [docs] def update(self, *args, **kwargs): - evaluations = kwargs['EVALUATIONS'] - solutions = kwargs['SOLUTIONS'] +
    +[docs] + def update(self, *args, **kwargs): + evaluations = kwargs["EVALUATIONS"] + solutions = kwargs["SOLUTIONS"] if solutions: if self.figure is None: - self.figure = StreamingPlot(reference_point=self.reference_point, - reference_front=self.reference_front) + self.figure = StreamingPlot(reference_point=self.reference_point, reference_front=self.reference_front) self.figure.plot(solutions) if (evaluations % self.display_frequency) == 0: # check if reference point has changed - reference_point = kwargs.get('REFERENCE_POINT', None) + reference_point = kwargs.get("REFERENCE_POINT", None) if reference_point: self.reference_point = reference_point @@ -328,7 +350,9 @@

    Source code for jmetal.util.observer

                     else:
                         self.figure.update(solutions)
     
    -                self.figure.ax.set_title('Eval: {}'.format(evaluations), fontsize=13)
    + self.figure.ax.set_title("Eval: {}".format(evaluations), fontsize=13)
    +
    +
    @@ -345,8 +369,9 @@

    Navigation

  • modules |
  • - - + + +
    + + + + + + + + + + + + +
    + +
    + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    + +
    +

    About

    +

    jMetalPy is being developed by Antonio J. Nebro, full professor at the University of Málaga.

    +
    +

    Cite us

    +
    @article{BENITEZHIDALGO2019100598,
    +   title = "jMetalPy: A Python framework for multi-objective optimization with metaheuristics",
    +   journal = "Swarm and Evolutionary Computation",
    +   pages = "100598",
    +   year = "2019",
    +   issn = "2210-6502",
    +   doi = "https://doi.org/10.1016/j.swevo.2019.100598",
    +   url = "http://www.sciencedirect.com/science/article/pii/S2210650219301397",
    +   author = "Antonio Benítez-Hidalgo and Antonio J. Nebro and José García-Nieto and Izaskun Oregi and Javier Del Ser",
    +   keywords = "Multi-objective optimization, Metaheuristics, Software framework, Python, Statistical analysis, Visualization",
    +}
    +
    +
    +
    +
    +

    References

    +
      +
    1. J.J. Durillo, A.J. Nebro jMetal: a Java Framework for Multi-Objective Optimization. Advances in Engineering Software 42 (2011) 760-771.

    2. +
    3. A.J. Nebro, J.J. Durillo, M. Vergne Redesigning the jMetal Multi-Objective Optimization Framework. GECCO (Companion) 2015, pp: 1093-1100. July 2015.

    4. +
    5. Nebro A.J. et al. (2018) Extending the Speed-Constrained Multi-objective PSO (SMPSO) with Reference Point Based Preference Articulation. In: Auger A., Fonseca C., Lourenço N., Machado P., Paquete L., Whitley D. (eds) Parallel Problem Solving from Nature – PPSN XV. PPSN 2018. Lecture Notes in Computer Science, vol 11101. Springer, Cham

    6. +
    +
    +
    + + +
    + + +
    + +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/docs/api/algorithm/multiobjective/ea.html b/docs/api/algorithm/multiobjective/ea.html index e43d4443..4dab0909 100644 --- a/docs/api/algorithm/multiobjective/ea.html +++ b/docs/api/algorithm/multiobjective/ea.html @@ -1,9 +1,10 @@ - - + + + @@ -12,18 +13,16 @@ - Evolutionary Algorithms — jMetalPy 1.5.3 documentation - - - - - - - - - - - + Evolutionary Algorithms — jMetalPy 1.7.0 documentation + + + + + + + + + @@ -47,8 +46,9 @@

    Navigation

  • previous |
  • - - + + +
    @@ -59,7 +59,7 @@

    Navigation

    @@ -120,10 +120,10 @@

    Table Of Contents

    -
    -

    Evolutionary Algorithms

    +
    +

    Evolutionary Algorithms

    -

    Multi-objective evolutionary algorithms:

    +

    Multi-objective evolutionary algorithms:

    -
    +
    @@ -170,8 +170,9 @@

    Navigation

  • previous |
  • - - + + +
    - - - - - - - + GDE3 — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -47,9 +47,10 @@

    Navigation

  • previous |
  • - + - + +
    @@ -60,7 +61,7 @@

    Navigation

    @@ -98,7 +99,20 @@

    Contents

    @@ -138,216 +152,15 @@

    Contents

    - - -
    -

    GDE3

    -
    -

    Example

    +
    +

    GDE3

    +
    +

    Example

    [1]:
     
    -
    -from jmetal.algorithm.multiobjective.gde3 import GDE3
    +
    from jmetal.algorithm.multiobjective.gde3 import GDE3
     from jmetal.problem import ZDT1
     from jmetal.util.termination_criterion import StoppingByEvaluations
     
    @@ -373,8 +186,7 @@ 

    Example
    [3]:
     

    -
    -
    -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.gde3.GDE3(problem: jmetal.core.problem.Problem, population_size: int, cr: float, f: float, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, k: float = 0.5, population_generator: jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    -

    Bases: jmetal.core.algorithm.EvolutionaryAlgorithm

    -
    -
    -create_initial_solutions() → List[jmetal.core.solution.FloatSolution][source]
    +
    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.gde3.GDE3(problem: ~jmetal.core.problem.Problem, population_size: int, cr: float, f: float, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, k: float = 0.5, population_generator: ~jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: ~jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    +

    Bases: EvolutionaryAlgorithm[FloatSolution, FloatSolution]

    +
    +
    +create_initial_solutions() List[FloatSolution][source]

    Creates the initial list of solutions of a metaheuristic.

    -
    -
    -evaluate(solution_list: List[jmetal.core.solution.FloatSolution]) → List[jmetal.core.solution.FloatSolution][source]
    +
    +
    +evaluate(solution_list: List[FloatSolution]) List[FloatSolution][source]

    Evaluates a solution list.

    -
    -
    -get_name() → str[source]
    -
    - -
    -
    -get_result() → List[jmetal.core.solution.FloatSolution][source]
    +
    +
    +get_name() str[source]
    -
    -
    -replacement(population: List[S], offspring_population: List[jmetal.core.solution.FloatSolution]) → List[List[jmetal.core.solution.FloatSolution]][source]
    +
    +
    +replacement(population: List[S], offspring_population: List[FloatSolution]) List[List[FloatSolution]][source]

    Replace least-fit population with new individuals.

    -
    -
    -reproduction(mating_pool: List[S]) → List[S][source]
    +
    +
    +reproduction(mating_pool: List[S]) List[S][source]

    Breed new individuals through crossover and mutation operations to give birth to offspring.

    -
    -
    -selection(population: List[jmetal.core.solution.FloatSolution]) → List[jmetal.core.solution.FloatSolution][source]
    +
    +
    +result() List[FloatSolution][source]
    +
    + +
    +
    +selection(population: List[FloatSolution]) List[FloatSolution][source]

    Select the best-fit individuals for reproduction (parents).

    -
    -
    -stopping_condition_is_met() → bool[source]
    +
    +
    +stopping_condition_is_met() bool[source]

    The stopping condition is met or not.

    -
    -
    + +
    @@ -482,9 +294,10 @@

    Navigation

  • previous |
  • - + - + +
    - - - - - - - + Dynamic GDE3 — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -39,7 +39,8 @@

    Navigation

  • modules |
  • - + +
    @@ -50,7 +51,7 @@

    Navigation

    @@ -84,7 +85,15 @@

    Contents

    @@ -120,216 +129,15 @@

    Contents

    - - -
    -

    Dynamic GDE3

    -
    -

    Example

    +
    +

    Dynamic GDE3

    +
    +

    Example

    [ ]:
     
    -
    -from jmetal.algorithm.multiobjective.gde3 import DynamicGDE3
    +
    from jmetal.algorithm.multiobjective.gde3 import DynamicGDE3
     from jmetal.problem.multiobjective.fda import FDA2
     from jmetal.util.observable import TimeCounter
     from jmetal.util.observer import PlotFrontToFileObserver, WriteFrontToFileObserver
    @@ -356,34 +164,34 @@ 

    Example -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.gde3.DynamicGDE3(problem: jmetal.core.problem.DynamicProblem, population_size: int, cr: float, f: float, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion, k: float = 0.5, population_generator: jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    -

    Bases: jmetal.algorithm.multiobjective.gde3.GDE3, jmetal.core.algorithm.DynamicAlgorithm

    -
    -
    -restart() → None[source]
    +

    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.gde3.DynamicGDE3(problem: ~jmetal.core.problem.DynamicProblem, population_size: int, cr: float, f: float, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion, k: float = 0.5, population_generator: ~jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: ~jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    +

    Bases: GDE3, DynamicAlgorithm

    +
    +
    +restart() None[source]
    -
    -
    -stopping_condition_is_met()[source]
    +
    +
    +stopping_condition_is_met()[source]

    The stopping condition is met or not.

    -
    -
    -update_progress()[source]
    +
    +
    +update_progress()[source]

    Update the progress after each iteration.

    -
    -
    + +
    @@ -400,7 +208,8 @@

    Navigation

  • modules |
  • - + +
    - - - - - - - + Preference point-based GDE3 — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -39,7 +39,8 @@

    Navigation

  • modules |
  • - + +
    @@ -50,7 +51,7 @@

    Navigation

    @@ -120,216 +121,15 @@

    Contents

    - - -
    -

    Preference point-based GDE3

    -
    -

    Example

    +
    +

    Preference point-based GDE3

    +
    +

    Example

    [1]:
     
    -
    -from jmetal.algorithm.multiobjective.gde3 import GDE3
    +
    +
    +

    API

    .. autoclass:: jmetal.algorithm.multiobjective.gde3.GDE3 :members: :undoc-members: - :show-inheritance:
    -
    + :show-inheritance: +
    @@ -400,7 +199,8 @@

    Navigation

  • modules |
  • - + +
    - - - - - - - + HYPE — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -47,9 +47,10 @@

    Navigation

  • previous |
  • - + - + +
    @@ -60,7 +61,7 @@

    Navigation

    @@ -98,7 +99,16 @@

    Contents

    @@ -138,216 +148,15 @@

    Contents

    - - -
    -

    HYPE

    -
    -

    Example

    +
    +

    HYPE

    +
    +

    Example

    [1]:
     
    -
    -from jmetal.algorithm.multiobjective.hype import HYPE
    +
    from jmetal.algorithm.multiobjective.hype import HYPE
     from jmetal.core.solution import FloatSolution
     from jmetal.operator import SBXCrossover, PolynomialMutation
     from jmetal.problem import ZDT1
    @@ -378,8 +187,7 @@ 

    Example
    [3]:
     

    -
    -
    -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.hype.HYPE(problem: jmetal.core.problem.Problem, reference_point: jmetal.core.solution.Solution, population_size: int, offspring_population_size: int, mutation: jmetal.core.operator.Mutation, crossover: jmetal.core.operator.Crossover, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    -

    Bases: jmetal.algorithm.singleobjective.genetic_algorithm.GeneticAlgorithm

    -
    -
    -evaluate(population: List[S])[source]
    +
    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.hype.HYPE(problem: ~jmetal.core.problem.Problem, reference_point: ~jmetal.core.solution.Solution, population_size: int, offspring_population_size: int, mutation: ~jmetal.core.operator.Mutation, crossover: ~jmetal.core.operator.Crossover, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: ~jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: ~jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    +

    Bases: GeneticAlgorithm[S, R]

    +
    +
    +evaluate(population: List[S])[source]

    Evaluates a solution list.

    -
    -
    -get_name() → str[source]
    -
    - -
    -
    -get_result() → R[source]
    +
    +
    +get_name() str[source]
    -
    -
    -replacement(population: List[S], offspring_population: List[S]) → List[List[S]][source]
    +
    +
    +replacement(population: List[S], offspring_population: List[S]) List[List[S]][source]

    Replace least-fit population with new individuals.

    +
    +
    +result() R[source]
    +
    +
    -
    -
    + +
    @@ -463,9 +271,10 @@

    Navigation

  • previous |
  • - + - + +
    - - - - - - - + IBEA — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -47,9 +47,10 @@

    Navigation

  • previous |
  • - + - + +
    @@ -60,7 +61,7 @@

    Navigation

    @@ -98,7 +99,17 @@

    Contents

    @@ -138,216 +149,15 @@

    Contents

    - - -
    -

    IBEA

    -
    -

    Example

    +
    +

    IBEA

    +
    +

    Example

    [1]:
     
    -
    -from jmetal.algorithm.multiobjective.ibea import IBEA
    +
    from jmetal.algorithm.multiobjective.ibea import IBEA
     from jmetal.operator import SBXCrossover, PolynomialMutation
     from jmetal.problem import ZDT1
     from jmetal.util.termination_criterion import StoppingByEvaluations
    @@ -376,8 +186,7 @@ 

    Example
    [3]:
     

    -
    -
    -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.ibea.IBEA(problem: jmetal.core.problem.Problem, population_size: int, offspring_population_size: int, mutation: jmetal.core.operator.Mutation, crossover: jmetal.core.operator.Crossover, kappa: float, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>)[source]
    -

    Bases: jmetal.algorithm.singleobjective.genetic_algorithm.GeneticAlgorithm

    -
    -
    -compute_fitness_values(population: List[S], kappa: float) → List[S][source]
    +
    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.ibea.IBEA(problem: ~jmetal.core.problem.Problem, population_size: int, offspring_population_size: int, mutation: ~jmetal.core.operator.Mutation, crossover: ~jmetal.core.operator.Crossover, kappa: float, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: ~jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>)[source]
    +

    Bases: GeneticAlgorithm[S, R]

    +
    +
    +compute_fitness_values(population: List[S], kappa: float) List[S][source]
    -
    -
    -create_initial_solutions() → List[S][source]
    +
    +
    +create_initial_solutions() List[S][source]

    Creates the initial list of solutions of a metaheuristic.

    -
    -
    -get_name() → str[source]
    -
    - -
    -
    -get_result() → R[source]
    +
    +
    +get_name() str[source]
    -
    -
    -replacement(population: List[S], offspring_population: List[S]) → List[List[S]][source]
    +
    +
    +replacement(population: List[S], offspring_population: List[S]) List[List[S]][source]

    Replace least-fit population with new individuals.

    +
    +
    +result() R[source]
    +
    +
    -
    -
    + +
    @@ -463,9 +272,10 @@

    Navigation

  • previous |
  • - + - + +
    - - - - - - - + MOCell — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -47,9 +47,10 @@

    Navigation

  • previous |
  • - + - + +
    @@ -60,7 +61,7 @@

    Navigation

    @@ -98,7 +99,19 @@

    Contents

    @@ -138,216 +151,15 @@

    Contents

    - - -
    -

    MOCell

    -
    -

    Example

    +
    +

    MOCell

    +
    +

    Example

    [1]:
     
    -
    -from jmetal.algorithm.multiobjective.mocell import MOCell
    +
    from jmetal.algorithm.multiobjective.mocell import MOCell
     from jmetal.operator import SBXCrossover, PolynomialMutation
     from jmetal.problem import ZDT4
     from jmetal.util.archive import CrowdingDistanceArchive
    @@ -378,8 +190,7 @@ 

    Example
    [3]:
     

    -
    -
    -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.mocell.MOCell(problem: jmetal.core.problem.Problem, population_size: int, neighborhood: jmetal.util.neighborhood.Neighborhood, archive: jmetal.util.archive.BoundedArchive, mutation: jmetal.core.operator.Mutation, crossover: jmetal.core.operator.Crossover, selection: jmetal.core.operator.Selection = <jmetal.operator.selection.BinaryTournamentSelection object>, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    -

    Bases: jmetal.algorithm.singleobjective.genetic_algorithm.GeneticAlgorithm

    -
    -
    -get_name() → str[source]
    -
    - -
    -
    -get_result() → R[source]
    +
    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.mocell.MOCell(problem: ~jmetal.core.problem.Problem, population_size: int, neighborhood: ~jmetal.util.neighborhood.Neighborhood, archive: ~jmetal.util.archive.BoundedArchive, mutation: ~jmetal.core.operator.Mutation, crossover: ~jmetal.core.operator.Crossover, selection: ~jmetal.core.operator.Selection = <jmetal.operator.selection.BinaryTournamentSelection object>, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: ~jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: ~jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    +

    Bases: GeneticAlgorithm[S, R]

    +
    +
    +get_name() str[source]
    -
    -
    -init_progress() → None[source]
    +
    +
    +init_progress() None[source]

    Initialize the algorithm.

    -
    -
    -replacement(population: List[S], offspring_population: List[S]) → List[List[S]][source]
    +
    +
    +replacement(population: List[S], offspring_population: List[S]) List[List[S]][source]

    Replace least-fit population with new individuals.

    -
    -
    -reproduction(mating_population: List[S]) → List[S][source]
    +
    +
    +reproduction(mating_population: List[S]) List[S][source]

    Breed new individuals through crossover and mutation operations to give birth to offspring.

    -
    -
    -selection(population: List[S])[source]
    +
    +
    +result() R[source]
    +
    + +
    +
    +selection(population: List[S])[source]

    Select the best-fit individuals for reproduction (parents).

    -
    -
    -update_progress() → None[source]
    +
    +
    +update_progress() None[source]

    Update the progress after each iteration.

    -
    -
    + +
    @@ -478,9 +289,10 @@

    Navigation

  • previous |
  • - + - + +
    - - - - - - - + MOEA/D — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -47,9 +47,10 @@

    Navigation

  • previous |
  • - + - + +
    @@ -60,7 +61,7 @@

    Navigation

    @@ -98,7 +99,21 @@

    Contents

    @@ -138,216 +153,15 @@

    Contents

    - - -
    -

    MOEA/D

    -
    -

    Example

    +
    +

    MOEA/D

    +
    +

    Example

    [1]:
     
    -
    -from jmetal.algorithm.multiobjective.moead import MOEAD
    +
    from jmetal.algorithm.multiobjective.moead import MOEAD
     from jmetal.operator import PolynomialMutation, DifferentialEvolutionCrossover
     from jmetal.problem import LZ09_F2
     from jmetal.util.aggregative_function import Tschebycheff
    @@ -380,8 +194,7 @@ 

    Example
    [3]:
     

    -
    -
    -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.moead.MOEAD(problem: jmetal.core.problem.Problem, population_size: int, mutation: jmetal.core.operator.Mutation, crossover: jmetal.operator.crossover.DifferentialEvolutionCrossover, aggregative_function: jmetal.util.aggregative_function.AggregativeFunction, neighbourhood_selection_probability: float, max_number_of_replaced_solutions: int, neighbor_size: int, weight_files_path: str, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>)[source]
    -

    Bases: jmetal.algorithm.singleobjective.genetic_algorithm.GeneticAlgorithm

    -
    -
    -choose_neighbor_type()[source]
    -
    - -
    -
    -generate_permutation_of_neighbors(subproblem_id)[source]
    +
    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.moead.MOEAD(problem: ~jmetal.core.problem.Problem, population_size: int, mutation: ~jmetal.core.operator.Mutation, crossover: ~jmetal.operator.crossover.DifferentialEvolutionCrossover, aggregation_function: ~jmetal.util.aggregation_function.AggregationFunction, neighbourhood_selection_probability: float, max_number_of_replaced_solutions: int, neighbor_size: int, weight_files_path: str, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: ~typing.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>)[source]
    +

    Bases: GeneticAlgorithm

    +
    +
    +choose_neighbor_type()[source]
    -
    -
    -get_name()[source]
    +
    +
    +generate_permutation_of_neighbors(subproblem_id)[source]
    -
    -
    -get_result()[source]
    +
    +
    +get_name()[source]
    -
    -
    -init_progress() → None[source]
    +
    +
    +init_progress() None[source]

    Initialize the algorithm.

    -
    -
    -replacement(population: List[S], offspring_population: List[S]) → List[S][source]
    +
    +
    +replacement(population: List[S], offspring_population: List[S]) List[S][source]

    Replace least-fit population with new individuals.

    -
    -
    -reproduction(mating_population: List[S]) → List[S][source]
    +
    +
    +reproduction(mating_population: List[S]) List[S][source]

    Breed new individuals through crossover and mutation operations to give birth to offspring.

    -
    -
    -selection(population: List[S])[source]
    +
    +
    +result()[source]
    +
    + +
    +
    +selection(population: List[S])[source]

    Select the best-fit individuals for reproduction (parents).

    -
    -
    -update_current_subproblem_neighborhood(new_solution, population)[source]
    +
    +
    +update_current_subproblem_neighborhood(new_solution, population)[source]
    -
    -
    + +
    @@ -492,9 +305,10 @@

    Navigation

  • previous |
  • - + - + +
    - - - - - - - + NSGA-II — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -47,9 +47,10 @@

    Navigation

  • previous |
  • - + - + +
    @@ -60,7 +61,7 @@

    Navigation

    @@ -98,7 +99,15 @@

    Contents

    @@ -138,216 +147,15 @@

    Contents

    - - -
    -

    NSGA-II

    -
    -

    Example

    +
    +

    NSGA-II

    +
    +

    Example

    [1]:
     
    -
    -from jmetal.algorithm.multiobjective.nsgaii import NSGAII
    +
    from jmetal.algorithm.multiobjective.nsgaii import NSGAII
     from jmetal.operator import SBXCrossover, PolynomialMutation
     from jmetal.problem import ZDT1
     from jmetal.util.termination_criterion import StoppingByEvaluations
    @@ -375,8 +183,7 @@ 

    Example
    [3]:
     

    -
    -
    -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.nsgaii.NSGAII(problem: jmetal.core.problem.Problem, population_size: int, offspring_population_size: int, mutation: jmetal.core.operator.Mutation, crossover: jmetal.core.operator.Crossover, selection: jmetal.core.operator.Selection = <jmetal.operator.selection.BinaryTournamentSelection object>, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    -

    Bases: jmetal.algorithm.singleobjective.genetic_algorithm.GeneticAlgorithm

    -
    -
    -get_name() → str[source]
    -
    - -
    -
    -get_result() → R[source]
    +
    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.nsgaii.NSGAII(problem: ~jmetal.core.problem.Problem, population_size: int, offspring_population_size: int, mutation: ~jmetal.core.operator.Mutation, crossover: ~jmetal.core.operator.Crossover, selection: ~jmetal.core.operator.Selection = <jmetal.operator.selection.BinaryTournamentSelection object>, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: ~typing.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: ~jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    +

    Bases: GeneticAlgorithm[S, R]

    +
    +
    +get_name() str[source]
    -
    -
    -replacement(population: List[S], offspring_population: List[S]) → List[List[S]][source]
    +
    +
    +replacement(population: List[S], offspring_population: List[S]) List[List[S]][source]

    This method joins the current and offspring populations to produce the population of the next generation by applying the ranking and crowding distance selection.

    -
    Parameters
    +
    Parameters:
    • population – Parent population.

    • offspring_population – Offspring population.

    -
    Returns
    +
    Returns:

    New population after ranking and crowding distance selection is applied.

    +
    +
    +result() R[source]
    +
    +
    -
    -
    + +
    @@ -466,9 +273,10 @@

    Navigation

  • previous |
  • - + - + +
    - - - - - - - + Distributed NSGA-II — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -39,7 +39,8 @@

    Navigation

  • modules |
  • - + +
    @@ -50,7 +51,7 @@

    Navigation

    @@ -84,7 +85,22 @@

    Contents

    @@ -120,216 +136,15 @@

    Contents

    - - -
    -

    Distributed NSGA-II

    -
    -

    Example

    +
    +

    Distributed NSGA-II

    +
    +

    Example

    [ ]:
     
    -
    -from math import sqrt
    +
    -
    -from dask.distributed import Client
    +
    -
    -from jmetal.lab.visualization.plotting import Plot
    +
    from jmetal.lab.visualization.plotting import Plot
     from jmetal.util.solution import get_non_dominated_solutions
     
     front = get_non_dominated_solutions(solutions)
    @@ -438,75 +251,75 @@ 

    Example -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.nsgaii.DistributedNSGAII(problem: jmetal.core.problem.Problem, population_size: int, mutation: jmetal.core.operator.Mutation, crossover: jmetal.core.operator.Crossover, number_of_cores: int, client, selection: jmetal.core.operator.Selection = <jmetal.operator.selection.BinaryTournamentSelection object>, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, dominance_comparator: jmetal.util.comparator.DominanceComparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    -

    Bases: jmetal.core.algorithm.Algorithm

    -
    -
    -create_initial_solutions() → List[S][source]
    +

    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.nsgaii.DistributedNSGAII(problem: ~jmetal.core.problem.Problem, population_size: int, mutation: ~jmetal.core.operator.Mutation, crossover: ~jmetal.core.operator.Crossover, number_of_cores: int, client, selection: ~jmetal.core.operator.Selection = <jmetal.operator.selection.BinaryTournamentSelection object>, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, dominance_comparator: ~jmetal.util.comparator.DominanceComparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    +

    Bases: Algorithm[S, R]

    +
    +
    +create_initial_solutions() List[S][source]

    Creates the initial list of solutions of a metaheuristic.

    -
    -
    -evaluate(solutions: List[S]) → List[S][source]
    +
    +
    +evaluate(solutions: List[S]) List[S][source]

    Evaluates a solution list.

    -
    -
    -get_name() → str[source]
    +
    +
    +get_name() str[source]
    -
    -
    -get_observable_data() → dict[source]
    -

    Get observable data, with the information that will be send to all observers each time.

    +
    +
    +init_progress() None[source]
    +

    Initialize the algorithm.

    -
    -
    -get_result() → R[source]
    -
    - -
    -
    -init_progress() → None[source]
    -

    Initialize the algorithm.

    +
    +
    +observable_data() dict[source]
    +

    Get observable data, with the information that will be seng to all observers each time.

    -
    -
    -run()[source]
    +
    +
    +result() R[source]
    +
    + +
    +
    +run()[source]

    Execute the algorithm.

    -
    -
    -step() → None[source]
    +
    +
    +step() None[source]

    Performs one iteration/step of the algorithm’s loop.

    -
    -
    -stopping_condition_is_met() → bool[source]
    +
    +
    +stopping_condition_is_met() bool[source]

    The stopping condition is met or not.

    -
    -
    -update_progress()[source]
    +
    +
    +update_progress()[source]

    Update the progress after each iteration.

    -
    -
    + +
    @@ -523,7 +336,8 @@

    Navigation

  • modules |
  • - + +
    - - - - - - - + Dynamic NSGA-II — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -39,7 +39,8 @@

    Navigation

  • modules |
  • - + +
    @@ -50,7 +51,7 @@

    Navigation

    @@ -84,7 +85,15 @@

    Contents

    @@ -120,216 +129,15 @@

    Contents

    - - -
    -

    Dynamic NSGA-II

    -
    -

    Example

    +
    +

    Dynamic NSGA-II

    +
    +

    Example

    [ ]:
     
    -
    -from jmetal.algorithm.multiobjective.nsgaii import DynamicNSGAII
    +
    from jmetal.algorithm.multiobjective.nsgaii import DynamicNSGAII
     from jmetal.operator import PolynomialMutation, SBXCrossover
     from jmetal.problem.multiobjective.fda import FDA2
     from jmetal.util.observable import TimeCounter
    @@ -359,34 +167,34 @@ 

    Example -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.nsgaii.DynamicNSGAII(problem: jmetal.core.problem.DynamicProblem[~S][S], population_size: int, offspring_population_size: int, mutation: jmetal.core.operator.Mutation, crossover: jmetal.core.operator.Crossover, selection: jmetal.core.operator.Selection = <jmetal.operator.selection.BinaryTournamentSelection object>, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: jmetal.util.comparator.DominanceComparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    -

    Bases: jmetal.algorithm.multiobjective.nsgaii.NSGAII, jmetal.core.algorithm.DynamicAlgorithm

    -
    -
    -restart()[source]
    +

    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.nsgaii.DynamicNSGAII(problem: ~jmetal.core.problem.DynamicProblem[~jmetal.algorithm.multiobjective.nsgaii.S], population_size: int, offspring_population_size: int, mutation: ~jmetal.core.operator.Mutation, crossover: ~jmetal.core.operator.Crossover, selection: ~jmetal.core.operator.Selection = <jmetal.operator.selection.BinaryTournamentSelection object>, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: ~typing.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: ~jmetal.util.comparator.DominanceComparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    +

    Bases: NSGAII[S, R], DynamicAlgorithm

    +
    +
    +restart()[source]
    -
    -
    -stopping_condition_is_met()[source]
    +
    +
    +stopping_condition_is_met()[source]

    The stopping condition is met or not.

    -
    -
    -update_progress()[source]
    +
    +
    +update_progress()[source]

    Update the progress after each iteration.

    -
    -
    + +
    @@ -403,7 +211,8 @@

    Navigation

  • modules |
  • - + +
    - - - - - - - + Preference point-based NSGA-II — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -39,7 +39,8 @@

    Navigation

  • modules |
  • - + +
    @@ -50,7 +51,7 @@

    Navigation

    @@ -84,7 +85,15 @@

    Contents

    @@ -120,216 +129,15 @@

    Contents

    - - -
    -

    Preference point-based NSGA-II

    -
    -

    Example

    +
    +

    Preference point-based NSGA-II

    +
    +

    Example

    [1]:
     
    -
    -from jmetal.algorithm.multiobjective.nsgaii import NSGAII
    +
    from jmetal.algorithm.multiobjective.nsgaii import NSGAII
     from jmetal.operator import SBXCrossover, PolynomialMutation
     from jmetal.problem import ZDT2
     from jmetal.util.comparator import GDominanceComparator
    @@ -360,8 +168,7 @@ 

    Example
    [3]:
     

    -
    -
    -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.nsgaii.DynamicNSGAII(problem: jmetal.core.problem.DynamicProblem[~S][S], population_size: int, offspring_population_size: int, mutation: jmetal.core.operator.Mutation, crossover: jmetal.core.operator.Crossover, selection: jmetal.core.operator.Selection = <jmetal.operator.selection.BinaryTournamentSelection object>, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: jmetal.util.comparator.DominanceComparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    -

    Bases: jmetal.algorithm.multiobjective.nsgaii.NSGAII, jmetal.core.algorithm.DynamicAlgorithm

    -
    -
    -restart()[source]
    +
    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.nsgaii.DynamicNSGAII(problem: ~jmetal.core.problem.DynamicProblem[~jmetal.algorithm.multiobjective.nsgaii.S], population_size: int, offspring_population_size: int, mutation: ~jmetal.core.operator.Mutation, crossover: ~jmetal.core.operator.Crossover, selection: ~jmetal.core.operator.Selection = <jmetal.operator.selection.BinaryTournamentSelection object>, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: ~typing.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: ~jmetal.util.comparator.DominanceComparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    +

    Bases: NSGAII[S, R], DynamicAlgorithm

    +
    +
    +restart()[source]
    -
    -
    -stopping_condition_is_met()[source]
    +
    +
    +stopping_condition_is_met()[source]

    The stopping condition is met or not.

    -
    -
    -update_progress()[source]
    +
    +
    +update_progress()[source]

    Update the progress after each iteration.

    -
    -
    + +
    @@ -422,7 +229,8 @@

    Navigation

  • modules |
  • - + +
    - - - - - - - + NSGA-III — jMetalPy 1.7.0 documentation + + + + + + + + + @@ -47,9 +46,10 @@

    Navigation

  • previous |
  • - + - + +
    @@ -60,7 +60,7 @@

    Navigation

    @@ -97,7 +97,15 @@

    Contents

    - - - - - - - - - - - - - - - - - -
    - -
    - -
    -
    -
    - - - - -
    -
    -
    - - - -
    - - - -
    -

    NSGA-II

    -
    -

    Examples

    -
    -
    [2]:
    -
    -
    -
    -from jmetal.algorithm.multiobjective.nsgaii import NSGAII
    -from jmetal.operator import SBXCrossover, PolynomialMutation
    -from jmetal.problem import ZDT1
    -from jmetal.util.termination_criterion import StoppingByEvaluations
    -
    -problem = ZDT1()
    -
    -max_evaluations = 2500
    -algorithm = NSGAII(
    -    problem=problem,
    -    population_size=100,
    -    offspring_population_size=100,
    -    mutation=PolynomialMutation(probability=1.0 / problem.number_of_variables, distribution_index=20),
    -    crossover=SBXCrossover(probability=1.0, distribution_index=20),
    -    termination_criterion=StoppingByEvaluations(max=max_evaluations)
    -)
    -
    -algorithm.run()
    -front = algorithm.get_result()
    -
    -
    -
    -

    We can now vis

    -
    -
    -

    API

    -.. automodule:: jmetal.algorithm.multiobjective.nsgaii - :members: - :undoc-members: - :show-inheritance:
    -
    - - -
    - - -
    - -
    -
    -
    - - - - - - \ No newline at end of file diff --git a/docs/api/algorithm/multiobjective/eas/oldnsgaii.html b/docs/api/algorithm/multiobjective/eas/oldnsgaii.html deleted file mode 100644 index cf1fa25a..00000000 --- a/docs/api/algorithm/multiobjective/eas/oldnsgaii.html +++ /dev/null @@ -1,421 +0,0 @@ - - - - - - - - - - - - - - - - NSGA-II — jMetalPy 1.5.3 documentation - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    -
    -
    - - - - -
    -
    -
    - -
    - -
    - -
    - -
    -

    NSGA-II

    -
    -

    Examples

    -
    -

    Standard

    -
    from jmetal.algorithm.multiobjective.nsgaii import NSGAII
    -from jmetal.operator import SBXCrossover, PolynomialMutation
    -from jmetal.problem import ZDT1
    -from jmetal.util.solution import read_solutions
    -from jmetal.util.termination_criterion import StoppingByEvaluations
    -
    -problem = ZDT1()
    -problem.reference_front = read_solutions(filename='resources/reference_front/ZDT1.pf')
    -
    -max_evaluations = 25000
    -algorithm = NSGAII(
    -    problem=problem,
    -    population_size=100,
    -    offspring_population_size=100,
    -    mutation=PolynomialMutation(probability=1.0 / problem.number_of_variables, distribution_index=20),
    -    crossover=SBXCrossover(probability=1.0, distribution_index=20),
    -    termination_criterion=StoppingByEvaluations(max=max_evaluations)
    -)
    -
    -algorithm.run()
    -front = algorithm.get_result()
    -
    -
    -
    -
    -

    Distributed

    -
    -

    Warning

    -

    This requires some extra dependencies

    -
    -
    from dask.distributed import Client
    -from distributed import LocalCluster
    -
    -from examples.multiobjective.parallel.zdt1_modified import ZDT1Modified
    -from jmetal.algorithm.multiobjective.nsgaii import DistributedNSGAII
    -from jmetal.operator import PolynomialMutation, SBXCrossover
    -from jmetal.util.termination_criterion import StoppingByEvaluations
    -
    -problem = ZDT1Modified()
    -
    -# setup Dask client
    -client = Client(LocalCluster(n_workers=24))
    -
    -ncores = sum(client.ncores().values())
    -print(f'{ncores} cores available')
    -
    -# creates the algorithm
    -max_evaluations = 25000
    -
    -algorithm = DistributedNSGAII(
    -    problem=problem,
    -    population_size=100,
    -    mutation=PolynomialMutation(probability=1.0 / problem.number_of_variables, distribution_index=20),
    -    crossover=SBXCrossover(probability=1.0, distribution_index=20),
    -    termination_criterion=StoppingByEvaluations(max=max_evaluations),
    -    number_of_cores=ncores,
    -    client=client
    -)
    -
    -algorithm.run()
    -front = algorithm.get_result()
    -
    -
    -
    -
    -

    Dynamic

    -
    from jmetal.algorithm.multiobjective.nsgaii import DynamicNSGAII
    -from jmetal.operator import PolynomialMutation, SBXCrossover
    -from jmetal.problem.multiobjective.fda import FDA2
    -from jmetal.util.observable import TimeCounter
    -from jmetal.util.observer import PlotFrontToFileObserver, WriteFrontToFileObserver
    -from jmetal.util.termination_criterion import StoppingByEvaluations
    -
    -problem = FDA2()
    -
    -time_counter = TimeCounter(delay=1)
    -time_counter.observable.register(problem)
    -time_counter.start()
    -
    -max_evaluations = 25000
    -algorithm = DynamicNSGAII(
    -    problem=problem,
    -    population_size=100,
    -    offspring_population_size=100,
    -    mutation=PolynomialMutation(probability=1.0 / problem.number_of_variables, distribution_index=20),
    -    crossover=SBXCrossover(probability=1.0, distribution_index=20),
    -    termination_criterion=StoppingByEvaluations(max=max_evaluations)
    -)
    -
    -algorithm.observable.register(observer=PlotFrontToFileObserver('front_vis'))
    -algorithm.observable.register(observer=WriteFrontToFileObserver('front_files'))
    -
    -algorithm.run()
    -
    -
    -
    -
    -

    Preference point-based (gNSGA-II)

    -
    from jmetal.algorithm.multiobjective.nsgaii import NSGAII
    -from jmetal.operator import SBXCrossover, PolynomialMutation
    -from jmetal.problem import ZDT2
    -from jmetal.util.solutions import read_solutions
    -from jmetal.util.solutions.comparator import GDominanceComparator
    -from jmetal.util.termination_criterion import StoppingByEvaluations
    -
    -problem = ZDT2()
    -problem.reference_front = read_solutions(filename='resources/reference_front/ZDT2.pf')
    -
    -reference_point = [0.2, 0.5]
    -
    -max_evaluations = 25000
    -algorithm = NSGAII(
    -    problem=problem,
    -    population_size=100,
    -    offspring_population_size=100,
    -    mutation=PolynomialMutation(probability=1.0 / problem.number_of_variables, distribution_index=20),
    -    crossover=SBXCrossover(probability=1.0, distribution_index=20),
    -    dominance_comparator=GDominanceComparator(reference_point),
    -    termination_criterion=StoppingByEvaluations(max=max_evaluations)
    -)
    -
    -algorithm.run()
    -front = algorithm.get_result()
    -
    -
    -
    -
    -
    -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.nsgaii.DistributedNSGAII(problem: jmetal.core.problem.Problem, population_size: int, mutation: jmetal.core.operator.Mutation, crossover: jmetal.core.operator.Crossover, number_of_cores: int, client: distributed.client.Client, selection: jmetal.core.operator.Selection = <jmetal.operator.selection.BinaryTournamentSelection object>, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, dominance_comparator: jmetal.util.comparator.DominanceComparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    -

    Bases: jmetal.core.algorithm.Algorithm

    -
    -
    -create_initial_solutions() → List[S][source]
    -

    Creates the initial list of solutions of a metaheuristic.

    -
    - -
    -
    -evaluate(solutions: List[S]) → List[S][source]
    -

    Evaluates a solution list.

    -
    - -
    -
    -get_name() → str[source]
    -
    - -
    -
    -get_observable_data() → dict[source]
    -

    Get observable data, with the information that will be send to all observers each time.

    -
    - -
    -
    -get_result() → R[source]
    -
    - -
    -
    -init_progress() → None[source]
    -

    Initialize the algorithm.

    -
    - -
    -
    -run()[source]
    -

    Execute the algorithm.

    -
    - -
    -
    -step() → None[source]
    -

    Performs one iteration/step of the algorithm’s loop.

    -
    - -
    -
    -stopping_condition_is_met() → bool[source]
    -

    The stopping condition is met or not.

    -
    - -
    -
    -update_progress()[source]
    -

    Update the progress after each iteration.

    -
    - -
    - -
    -
    -class jmetal.algorithm.multiobjective.nsgaii.DynamicNSGAII(problem: jmetal.core.problem.DynamicProblem[~S][S], population_size: int, offspring_population_size: int, mutation: jmetal.core.operator.Mutation, crossover: jmetal.core.operator.Crossover, selection: jmetal.core.operator.Selection = <jmetal.operator.selection.BinaryTournamentSelection object>, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: jmetal.util.comparator.DominanceComparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    -

    Bases: jmetal.algorithm.multiobjective.nsgaii.NSGAII, jmetal.core.algorithm.DynamicAlgorithm

    -
    -
    -restart()[source]
    -
    - -
    -
    -stopping_condition_is_met()[source]
    -

    The stopping condition is met or not.

    -
    - -
    -
    -update_progress()[source]
    -

    Update the progress after each iteration.

    -
    - -
    - -
    -
    -class jmetal.algorithm.multiobjective.nsgaii.NSGAII(problem: jmetal.core.problem.Problem, population_size: int, offspring_population_size: int, mutation: jmetal.core.operator.Mutation, crossover: jmetal.core.operator.Crossover, selection: jmetal.core.operator.Selection = <jmetal.operator.selection.BinaryTournamentSelection object>, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    -

    Bases: jmetal.algorithm.singleobjective.genetic_algorithm.GeneticAlgorithm

    -
    -
    -get_name() → str[source]
    -
    - -
    -
    -get_result() → R[source]
    -
    - -
    -
    -replacement(population: List[S], offspring_population: List[S]) → List[List[S]][source]
    -

    This method joins the current and offspring populations to produce the population of the next generation -by applying the ranking and crowding distance selection.

    -
    -
    Parameters
    -
      -
    • population – Parent population.

    • -
    • offspring_population – Offspring population.

    • -
    -
    -
    Returns
    -

    New population after ranking and crowding distance selection is applied.

    -
    -
    -
    - -
    - -
    -
    -jmetal.algorithm.multiobjective.nsgaii.R = ~R
    -
    - -
    -
    -jmetal.algorithm.multiobjective.nsgaii.reproduction(mating_population: List[S], problem, crossover_operator, mutation_operator) → S[source]
    -
    - -
    -
    - - -
    - -
    -
    -
    - - - - - - \ No newline at end of file diff --git a/docs/api/algorithm/multiobjective/eas/oldspea2.html b/docs/api/algorithm/multiobjective/eas/oldspea2.html deleted file mode 100644 index b3e789e0..00000000 --- a/docs/api/algorithm/multiobjective/eas/oldspea2.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - - - - - - - - - - SPEA2 — jMetalPy 1.5.3 documentation - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    -
    -
    - - - - -
    -
    -
    - -
    - -
    - -
    - -
    -

    SPEA2

    -
    -

    Examples

    -
    -

    Standard

    -
    from jmetal.algorithm.multiobjective.spea2 import SPEA2
    -from jmetal.operator import SBXCrossover, PolynomialMutation
    -from jmetal.problem import ZDT1
    -from jmetal.util.solutions import read_solutions
    -from jmetal.util.termination_criterion import StoppingByEvaluations
    -
    -problem = ZDT1()
    -problem.reference_front = read_solutions(filename='resources/reference_front/ZDT1.pf')
    -
    -max_evaluations = 20000
    -algorithm = SPEA2(
    -    problem=problem,
    -    population_size=40,
    -    offspring_population_size=40,
    -    mutation=PolynomialMutation(probability=1.0 / problem.number_of_variables, distribution_index=20),
    -    crossover=SBXCrossover(probability=1.0, distribution_index=20),
    -    termination_criterion=StoppingByEvaluations(max=max_evaluations)
    -)
    -
    -algorithm.run()
    -front = algorithm.get_result()
    -
    -
    -
    -
    -

    Preference point-based (gSPEA2)

    -
    from jmetal.algorithm.multiobjective.spea2 import SPEA2
    -from jmetal.lab.visualization import Plot, InteractivePlot
    -from jmetal.operator import SBXCrossover, PolynomialMutation
    -from jmetal.problem import ZDT1
    -from jmetal.util.solutions import read_solutions
    -from jmetal.util.solutions.comparator import GDominanceComparator
    -from jmetal.util.termination_criterion import StoppingByEvaluations
    -
    -problem = ZDT1()
    -problem.reference_front = read_solutions(filename='resources/reference_front/ZDT1.pf')
    -
    -reference_point = [0.4, 0.6]
    -
    -max_evaluations = 25000
    -algorithm = SPEA2(
    -    problem=problem,
    -    population_size=40,
    -    offspring_population_size=40,
    -    mutation=PolynomialMutation(probability=1.0 / problem.number_of_variables, distribution_index=20),
    -    crossover=SBXCrossover(probability=1.0, distribution_index=20),
    -    termination_criterion=StoppingByEvaluations(max=max_evaluations),
    -    dominance_comparator=GDominanceComparator(reference_point)
    -)
    -
    -algorithm.run()
    -front = algorithm.get_result()
    -
    -
    -
    -
    -
    -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.spea2.SPEA2(problem: jmetal.core.problem.Problem, population_size: int, offspring_population_size: int, mutation: jmetal.core.operator.Mutation, crossover: jmetal.core.operator.Crossover, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    -

    Bases: jmetal.algorithm.singleobjective.genetic_algorithm.GeneticAlgorithm

    -
    -
    -get_name() → str[source]
    -
    - -
    -
    -get_result() → R[source]
    -
    - -
    -
    -replacement(population: List[S], offspring_population: List[S]) → List[List[S]][source]
    -

    This method joins the current and offspring populations to produce the population of the next generation -by applying the ranking and crowding distance selection.

    -
    -
    Parameters
    -
      -
    • population – Parent population.

    • -
    • offspring_population – Offspring population.

    • -
    -
    -
    Returns
    -

    New population after ranking and crowding distance selection is applied.

    -
    -
    -
    - -
    - -
    -
    - - -
    - -
    -
    -
    - - - - - - \ No newline at end of file diff --git a/docs/api/algorithm/multiobjective/eas/spea2.html b/docs/api/algorithm/multiobjective/eas/spea2.html index 4a388cb7..995f9b60 100644 --- a/docs/api/algorithm/multiobjective/eas/spea2.html +++ b/docs/api/algorithm/multiobjective/eas/spea2.html @@ -1,9 +1,10 @@ - - + + + @@ -12,18 +13,17 @@ - SPEA2 — jMetalPy 1.5.3 documentation - - - - - - - - - - - + SPEA2 — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -47,9 +47,10 @@

    Navigation

  • previous |
  • - + - + +
    @@ -60,7 +61,7 @@

    Navigation

    @@ -98,7 +99,15 @@

    Contents

    @@ -138,216 +147,15 @@

    Contents

    - - -
    -

    SPEA2

    -
    -

    Example

    +
    +

    SPEA2

    +
    +

    Example

    [1]:
     
    -
    -from jmetal.algorithm.multiobjective.spea2 import SPEA2
    +
    from jmetal.algorithm.multiobjective.spea2 import SPEA2
     from jmetal.operator import SBXCrossover, PolynomialMutation
     from jmetal.problem import ZDT1
     from jmetal.util.termination_criterion import StoppingByEvaluations
    @@ -375,8 +183,7 @@ 

    Example
    [3]:
     

    -
    -
    -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.spea2.SPEA2(problem: jmetal.core.problem.Problem, population_size: int, offspring_population_size: int, mutation: jmetal.core.operator.Mutation, crossover: jmetal.core.operator.Crossover, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    -

    Bases: jmetal.algorithm.singleobjective.genetic_algorithm.GeneticAlgorithm

    -
    -
    -get_name() → str[source]
    -
    - -
    -
    -get_result() → R[source]
    +
    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.spea2.SPEA2(problem: ~jmetal.core.problem.Problem, population_size: int, offspring_population_size: int, mutation: ~jmetal.core.operator.Mutation, crossover: ~jmetal.core.operator.Crossover, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, population_generator: ~jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, population_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>, dominance_comparator: ~jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>)[source]
    +

    Bases: GeneticAlgorithm[S, R]

    +
    +
    +get_name() str[source]
    -
    -
    -replacement(population: List[S], offspring_population: List[S]) → List[List[S]][source]
    +
    +
    +replacement(population: List[S], offspring_population: List[S]) List[List[S]][source]

    This method joins the current and offspring populations to produce the population of the next generation by applying the ranking and crowding distance selection.

    -
    Parameters
    +
    Parameters:
    • population – Parent population.

    • offspring_population – Offspring population.

    -
    Returns
    +
    Returns:

    New population after ranking and crowding distance selection is applied.

    +
    +
    +result() R[source]
    +
    +
    -
    -
    + +
    @@ -466,9 +273,10 @@

    Navigation

  • previous |
  • - + - + +
    - - - - - - - + PSO Algorithms — jMetalPy 1.7.0 documentation + + + + + + + + + @@ -47,8 +46,9 @@

    Navigation

  • previous |
  • - - + + +
    @@ -59,7 +59,7 @@

    Navigation

    @@ -120,16 +120,16 @@

    Table Of Contents

    -
    -

    PSO Algorithms

    +
    +

    PSO Algorithms

    -

    Multi-objective particle swarm optimization algorithms:

    +

    Multi-objective particle swarm optimization algorithms:

    -
    +
    @@ -164,8 +164,9 @@

    Navigation

  • previous |
  • - - + + +
    - - - - - - - + OMOPSO — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -47,9 +47,10 @@

    Navigation

  • previous |
  • - + - + +
    @@ -60,7 +61,7 @@

    Navigation

    @@ -98,7 +99,28 @@

    Contents

    @@ -138,216 +160,15 @@

    Contents

    - - -
    -

    OMOPSO

    -
    -

    Example

    +
    +

    OMOPSO

    +
    +

    Example

    [1]:
     
    -
    -from jmetal.algorithm.multiobjective.omopso import OMOPSO
    +
    from jmetal.algorithm.multiobjective.omopso import OMOPSO
     from jmetal.operator import UniformMutation
     from jmetal.operator.mutation import NonUniformMutation
     from jmetal.problem import ZDT1
    @@ -381,8 +202,7 @@ 

    Example
    [3]:
     

    -
    -
    -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.omopso.OMOPSO(problem: jmetal.core.problem.FloatProblem, swarm_size: int, uniform_mutation: jmetal.operator.mutation.UniformMutation, non_uniform_mutation: jmetal.operator.mutation.NonUniformMutation, leaders: Optional[jmetal.util.archive.BoundedArchive], epsilon: float, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion, swarm_generator: jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, swarm_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>)[source]
    -

    Bases: jmetal.core.algorithm.ParticleSwarmOptimization

    -
    -
    -create_initial_solutions() → List[jmetal.core.solution.FloatSolution][source]
    +
    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.omopso.OMOPSO(problem: ~jmetal.core.problem.FloatProblem, swarm_size: int, uniform_mutation: ~jmetal.operator.mutation.UniformMutation, non_uniform_mutation: ~jmetal.operator.mutation.NonUniformMutation, leaders: ~jmetal.util.archive.BoundedArchive | None, epsilon: float, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion, swarm_generator: ~jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, swarm_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>)[source]
    +

    Bases: ParticleSwarmOptimization

    +
    +
    +create_initial_solutions() List[FloatSolution][source]

    Creates the initial list of solutions of a metaheuristic.

    -
    -
    -evaluate(solution_list: List[jmetal.core.solution.FloatSolution])[source]
    +
    +
    +evaluate(solution_list: List[FloatSolution])[source]

    Evaluates a solution list.

    -
    -
    -get_name() → str[source]
    -
    - -
    -
    -get_result() → List[jmetal.core.solution.FloatSolution][source]
    +
    +
    +get_name() str[source]
    -
    -
    -init_progress() → None[source]
    +
    +
    +init_progress() None[source]

    Initialize the algorithm.

    -
    -
    -initialize_global_best(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +initialize_global_best(swarm: List[FloatSolution]) None[source]
    -
    -
    -initialize_particle_best(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +initialize_particle_best(swarm: List[FloatSolution]) None[source]
    -
    -
    -initialize_velocity(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +initialize_velocity(swarm: List[FloatSolution]) None[source]
    -
    -
    -perturbation(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +perturbation(swarm: List[FloatSolution]) None[source]
    -
    -
    -select_global_best() → jmetal.core.solution.FloatSolution[source]
    +
    +
    +result() List[FloatSolution][source]
    -
    -
    -stopping_condition_is_met() → bool[source]
    +
    +
    +select_global_best() FloatSolution[source]
    +
    + +
    +
    +stopping_condition_is_met() bool[source]

    The stopping condition is met or not.

    -
    -
    -update_global_best(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +update_global_best(swarm: List[FloatSolution]) None[source]
    -
    -
    -update_particle_best(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +update_particle_best(swarm: List[FloatSolution]) None[source]
    -
    -
    -update_position(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +update_position(swarm: List[FloatSolution]) None[source]
    -
    -
    -update_progress() → None[source]
    +
    +
    +update_progress() None[source]

    Update the progress after each iteration.

    -
    -
    -update_velocity(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +update_velocity(swarm: List[FloatSolution]) None[source]
    -
    -
    + +
    @@ -529,9 +349,10 @@

    Navigation

  • previous |
  • - + - + +
    - - - - - - - + SMPSO — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -47,9 +47,10 @@

    Navigation

  • previous |
  • - + - + +
    @@ -60,7 +61,7 @@

    Navigation

    @@ -98,7 +99,28 @@

    Contents

    @@ -138,216 +160,15 @@

    Contents

    - - -
    -

    SMPSO

    -
    -

    Example

    +
    +

    SMPSO

    +
    +

    Example

    [1]:
     
    -
    -from jmetal.algorithm.multiobjective.smpso import SMPSO
    +
    from jmetal.algorithm.multiobjective.smpso import SMPSO
     from jmetal.operator import PolynomialMutation
     from jmetal.problem import ZDT4
     from jmetal.util.archive import CrowdingDistanceArchive
    @@ -374,8 +195,7 @@ 

    Example
    [3]:
     

    -
    -
    -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.smpso.SMPSO(problem: jmetal.core.problem.FloatProblem, swarm_size: int, mutation: jmetal.core.operator.Mutation, leaders: Optional[jmetal.util.archive.BoundedArchive], termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, swarm_generator: jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, swarm_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>)[source]
    -

    Bases: jmetal.core.algorithm.ParticleSwarmOptimization

    -
    -
    -create_initial_solutions() → List[jmetal.core.solution.FloatSolution][source]
    +
    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.smpso.SMPSO(problem: ~jmetal.core.problem.FloatProblem, swarm_size: int, mutation: ~jmetal.core.operator.Mutation, leaders: ~jmetal.util.archive.BoundedArchive | None, dominance_comparator: ~jmetal.util.comparator.Comparator = <jmetal.util.comparator.DominanceComparator object>, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, swarm_generator: ~jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, swarm_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>)[source]
    +

    Bases: ParticleSwarmOptimization

    +
    +
    +create_initial_solutions() List[FloatSolution][source]

    Creates the initial list of solutions of a metaheuristic.

    -
    -
    -evaluate(solution_list: List[jmetal.core.solution.FloatSolution])[source]
    +
    +
    +evaluate(solution_list: List[FloatSolution])[source]

    Evaluates a solution list.

    -
    -
    -get_name() → str[source]
    -
    - -
    -
    -get_result() → List[jmetal.core.solution.FloatSolution][source]
    +
    +
    +get_name() str[source]
    -
    -
    -init_progress() → None[source]
    +
    +
    +init_progress() None[source]

    Initialize the algorithm.

    -
    -
    -initialize_global_best(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +initialize_global_best(swarm: List[FloatSolution]) None[source]
    -
    -
    -initialize_particle_best(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +initialize_particle_best(swarm: List[FloatSolution]) None[source]
    -
    -
    -initialize_velocity(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +initialize_velocity(swarm: List[FloatSolution]) None[source]
    -
    -
    -perturbation(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +perturbation(swarm: List[FloatSolution]) None[source]
    -
    -
    -select_global_best() → jmetal.core.solution.FloatSolution[source]
    +
    +
    +result() List[FloatSolution][source]
    -
    -
    -stopping_condition_is_met() → bool[source]
    +
    +
    +select_global_best() FloatSolution[source]
    +
    + +
    +
    +stopping_condition_is_met() bool[source]

    The stopping condition is met or not.

    -
    -
    -update_global_best(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +update_global_best(swarm: List[FloatSolution]) None[source]
    -
    -
    -update_particle_best(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +update_particle_best(swarm: List[FloatSolution]) None[source]
    -
    -
    -update_position(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +update_position(swarm: List[FloatSolution]) None[source]
    -
    -
    -update_progress() → None[source]
    +
    +
    +update_progress() None[source]

    Update the progress after each iteration.

    -
    -
    -update_velocity(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +update_velocity(swarm: List[FloatSolution]) None[source]
    -
    -
    + +
    @@ -522,9 +342,10 @@

    Navigation

  • previous |
  • - + - + +
    - - - - - - - + Dynamic SMPSO — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -39,7 +39,8 @@

    Navigation

  • modules |
  • - + +
    @@ -50,7 +51,7 @@

    Navigation

    @@ -84,7 +85,15 @@

    Contents

    @@ -120,216 +129,15 @@

    Contents

    - - -
    -

    Dynamic SMPSO

    -
    -

    Example

    +
    +

    Dynamic SMPSO

    +
    +

    Example

    [ ]:
     
    -
    -from jmetal.algorithm.multiobjective.smpso import DynamicSMPSO
    +
    from jmetal.algorithm.multiobjective.smpso import DynamicSMPSO
     from jmetal.operator import PolynomialMutation
     from jmetal.problem.multiobjective.fda import FDA2
     from jmetal.util.archive import CrowdingDistanceArchive
    @@ -359,34 +167,34 @@ 

    Example -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.smpso.DynamicSMPSO(problem: jmetal.core.problem.DynamicProblem[jmetal.core.solution.FloatSolution][jmetal.core.solution.FloatSolution], swarm_size: int, mutation: jmetal.core.operator.Mutation, leaders: jmetal.util.archive.BoundedArchive, termination_criterion: jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, swarm_generator: jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, swarm_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>)[source]
    -

    Bases: jmetal.algorithm.multiobjective.smpso.SMPSO, jmetal.core.algorithm.DynamicAlgorithm

    -
    -
    -restart() → None[source]
    +

    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.smpso.DynamicSMPSO(problem: ~jmetal.core.problem.DynamicProblem[~jmetal.core.solution.FloatSolution], swarm_size: int, mutation: ~jmetal.core.operator.Mutation, leaders: ~jmetal.util.archive.BoundedArchive, termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion = <jmetal.util.termination_criterion.StoppingByEvaluations object>, swarm_generator: ~jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, swarm_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>)[source]
    +

    Bases: SMPSO, DynamicAlgorithm

    +
    +
    +restart() None[source]
    -
    -
    -stopping_condition_is_met()[source]
    +
    +
    +stopping_condition_is_met()[source]

    The stopping condition is met or not.

    -
    -
    -update_progress()[source]
    +
    +
    +update_progress()[source]

    Update the progress after each iteration.

    -
    -
    + +
    @@ -403,7 +211,8 @@

    Navigation

  • modules |
  • - + +
    - - - - - - - + SMPSO/RP — jMetalPy 1.7.0 documentation + + + + + + + + + + @@ -39,7 +39,8 @@

    Navigation

  • modules |
  • - + +
    @@ -50,7 +51,7 @@

    Navigation

    @@ -84,7 +85,21 @@

    Contents

    @@ -120,216 +135,15 @@

    Contents

    - - -
    -

    SMPSO/RP

    -
    -

    Example

    +
    +

    SMPSO/RP

    +
    +

    Example

    [1]:
     
    -
    -from jmetal.algorithm.multiobjective.smpso import SMPSORP
    +
    from jmetal.algorithm.multiobjective.smpso import SMPSORP
     from jmetal.operator import PolynomialMutation
     from jmetal.problem import ZDT4
     from jmetal.util.archive import CrowdingDistanceArchiveWithReferencePoint
    @@ -375,8 +189,7 @@ 

    Example
    [3]:
     

    -
    -
    -

    API

    -
    -
    -class jmetal.algorithm.multiobjective.smpso.SMPSORP(problem: jmetal.core.problem.FloatProblem, swarm_size: int, mutation: jmetal.core.operator.Mutation, reference_points: List[List[float]], leaders: List[jmetal.util.archive.ArchiveWithReferencePoint], termination_criterion: jmetal.util.termination_criterion.TerminationCriterion, swarm_generator: jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, swarm_evaluator: jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>)[source]
    -

    Bases: jmetal.algorithm.multiobjective.smpso.SMPSO

    -
    -
    -get_name() → str[source]
    -
    - -
    -
    -get_reference_point()[source]
    +
    +
    +

    API

    +
    +
    +class jmetal.algorithm.multiobjective.smpso.SMPSORP(problem: ~jmetal.core.problem.FloatProblem, swarm_size: int, mutation: ~jmetal.core.operator.Mutation, reference_points: ~typing.List[~typing.List[float]], leaders: ~typing.List[~jmetal.util.archive.ArchiveWithReferencePoint], termination_criterion: ~jmetal.util.termination_criterion.TerminationCriterion, swarm_generator: ~jmetal.util.generator.Generator = <jmetal.util.generator.RandomGenerator object>, swarm_evaluator: ~jmetal.util.evaluator.Evaluator = <jmetal.util.evaluator.SequentialEvaluator object>)[source]
    +

    Bases: SMPSO

    +
    +
    +get_name() str[source]
    -
    -
    -get_result() → List[jmetal.core.solution.FloatSolution][source]
    +
    +
    +get_reference_point()[source]
    -
    -
    -init_progress() → None[source]
    +
    +
    +init_progress() None[source]

    Initialize the algorithm.

    -
    -
    -initialize_global_best(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +initialize_global_best(swarm: List[FloatSolution]) None[source]
    -
    -
    -select_global_best() → jmetal.core.solution.FloatSolution[source]
    +
    +
    +result() List[FloatSolution][source]
    -
    -
    -update_global_best(swarm: List[jmetal.core.solution.FloatSolution]) → None[source]
    +
    +
    +select_global_best() FloatSolution[source]
    -
    -
    -update_progress() → None[source]
    +
    +
    +update_global_best(swarm: List[FloatSolution]) None[source]
    +
    + +
    +
    +update_progress() None[source]

    Update the progress after each iteration.

    -
    -
    -update_reference_point(new_reference_points: list)[source]
    +
    +
    +update_reference_point(new_reference_points: list)[source]
    -
    -
    + +
    @@ -467,7 +280,8 @@

    Navigation

  • modules |
  • - + +
    - - - - - - - + Evolution Strategy — jMetalPy 1.7.0 documentation + + + + + + + @@ -47,8 +44,9 @@

    Navigation

  • previous |
  • - - + + +
    @@ -59,7 +57,7 @@

    Navigation

    @@ -92,6 +90,30 @@

    Table Of Contents

    +
  • modules |