From cdc8026dc77d9775671294f01a03acffd2ee6c5c Mon Sep 17 00:00:00 2001 From: NielsenErik Date: Mon, 20 Nov 2023 10:48:25 +0100 Subject: [PATCH] update code and readme --- README.md | 22 +- .../__pycache__/agents.cpython-310.pyc | Bin 0 -> 4122 bytes .../__pycache__/agents.cpython-311.pyc | Bin 5821 -> 6774 bytes .../differentObservations.cpython-310.pyc | Bin 0 -> 7716 bytes .../differentObservations.cpython-311.pyc | Bin 0 -> 20440 bytes .../__pycache__/evaluations.cpython-311.pyc | Bin 0 -> 11981 bytes .../get_interpretability.cpython-311.pyc | Bin 0 -> 19731 bytes .../processing_element.cpython-310.pyc | Bin 0 -> 3082 bytes .../team_evaluation.cpython-311.pyc | Bin 0 -> 11783 bytes .../test_environments.cpython-310.pyc | Bin 0 -> 623 bytes .../test_environments.cpython-311.pyc | Bin 0 -> 4597 bytes src/QD_MARL/__pycache__/utils.cpython-311.pyc | Bin 8384 -> 10591 bytes src/QD_MARL/agents.py | 47 +- .../Depracated/grammatical_evolution.py | 488 +++++++++++ .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 532 bytes .../__pycache__/common.cpython-310.pyc | Bin 0 -> 1635 bytes .../continuous_optimization.cpython-310.pyc | Bin 0 -> 5937 bytes .../genetic_algorithm.cpython-310.pyc | Bin 0 -> 7342 bytes .../genetic_programming.cpython-310.pyc | Bin 0 -> 6781 bytes .../genetic_programming.cpython-311.pyc | Bin 15328 -> 15322 bytes .../grammatical_evolution.cpython-310.pyc | Bin 0 -> 20403 bytes .../grammatical_evolution.cpython-311.pyc | Bin 33665 -> 38708 bytes .../__pycache__/individuals.cpython-310.pyc | Bin 0 -> 13091 bytes .../__pycache__/individuals.cpython-311.pyc | Bin 20903 -> 21863 bytes .../__pycache__/mapElitesCMA.cpython-310.pyc | Bin 0 -> 14594 bytes .../mapElitesCMA_pyRibs.cpython-310.pyc | Bin 0 -> 17298 bytes .../mapElitesCMA_pyRibs.cpython-311.pyc | Bin 37962 -> 38416 bytes .../mapElitesCMA_pyRibs_GE.cpython-310.pyc | Bin 0 -> 11080 bytes .../mapElitesCMA_pyRibs_GE.cpython-311.pyc | Bin 21271 -> 21355 bytes .../__pycache__/map_elites.cpython-310.pyc | Bin 0 -> 18685 bytes .../__pycache__/map_elites.cpython-311.pyc | Bin 36189 -> 36253 bytes .../map_elites_Pyribs.cpython-310.pyc | Bin 0 -> 12463 bytes .../map_elites_Pyribs.cpython-311.pyc | Bin 28349 -> 29879 bytes .../__pycache__/map_elites_ge.cpython-310.pyc | Bin 0 -> 11424 bytes src/QD_MARL/algorithms/genetic_programming.py | 2 +- .../algorithms/grammatical_evolution.py | 205 +++-- src/QD_MARL/algorithms/individuals.py | 18 +- src/QD_MARL/algorithms/mapElitesCMA_pyRibs.py | 49 +- .../algorithms/mapElitesCMA_pyRibs_GE.py | 2 + src/QD_MARL/algorithms/map_elites.py | 7 +- src/QD_MARL/algorithms/map_elites_Pyribs.py | 70 +- src/QD_MARL/configs/CartPole-ME_pyRibs.yaml | 80 ++ src/QD_MARL/configs/battlefield.json | 85 +- src/QD_MARL/configs/map_elite_config.json | 66 ++ src/QD_MARL/configs/tmp_map_elite_config.json | 102 +++ .../__pycache__/__init__.cpython-311.pyc | Bin 535 -> 0 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 681 bytes .../__pycache__/conditions.cpython-310.pyc | Bin 0 -> 18076 bytes .../__pycache__/conditions.cpython-311.pyc | Bin 27079 -> 27349 bytes .../__pycache__/factories.cpython-310.pyc | Bin 0 -> 3666 bytes .../__pycache__/leaves.cpython-310.pyc | Bin 0 -> 22280 bytes .../__pycache__/leaves.cpython-311.pyc | Bin 35690 -> 33885 bytes .../__pycache__/nodes.cpython-310.pyc | Bin 0 -> 1834 bytes .../__pycache__/trees.cpython-310.pyc | Bin 0 -> 10414 bytes .../__pycache__/trees.cpython-311.pyc | Bin 17381 -> 18514 bytes src/QD_MARL/decisiontrees/conditions.py | 7 + src/QD_MARL/decisiontrees/leaves.py | 155 ++-- src/QD_MARL/decisiontrees/trees.py | 34 +- .../decisiontreelibrary/README.md | 0 .../decisiontreelibrary/__init__.py | 2 +- .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 561 bytes .../__pycache__/conditions.cpython-311.pyc | Bin .../__pycache__/leaves.cpython-311.pyc | Bin .../__pycache__/nodes.cpython-311.pyc | Bin .../__pycache__/trees.cpython-311.pyc | Bin .../decisiontreelibrary/conditions.py | 0 .../decisiontreelibrary/.gitignore | 2 + .../decisiontreelibrary/.gitlab-ci.yml | 8 + .../decisiontreelibrary/README.md | 8 + .../decisiontreelibrary/__init__.py | 9 + .../decisiontreelibrary/__leaves.py | 620 ++++++++++++++ .../decisiontreelibrary/conditions.py | 255 ++++++ .../decisiontreelibrary/leaves.py | 706 ++++++++++++++++ .../decisiontreelibrary/nodes.py | 45 + .../decisiontreelibrary/tests/__init__.py | 0 .../tests/test_conditions.py | 0 .../decisiontreelibrary/tests/test_leaves.py | 260 ++++++ .../decisiontreelibrary/tests/test_trees.py | 0 .../decisiontreelibrary/trees.py | 234 +++++ .../decisiontreelibrary/leaves.py | 0 .../decisiontreelibrary/nodes.py | 0 .../decisiontreelibrary/tests/__init__.py | 0 .../tests/test_conditions.py | 147 ++++ .../decisiontreelibrary/tests/test_leaves.py | 0 .../decisiontreelibrary/tests/test_trees.py | 311 +++++++ .../decisiontreelibrary/trees.py | 1 - src/QD_MARL/deprecated/leaves.py | 800 ++++++++++++++++++ src/QD_MARL/{ => deprecated}/utils.py | 3 + src/QD_MARL/differentObservations.py | 6 +- src/QD_MARL/evaluations.py | 306 +++++++ src/QD_MARL/evolve_tree_me.py | 2 +- src/QD_MARL/get_interpretability.py | 26 +- src/QD_MARL/marl_qd_launcher.py | 389 +++------ src/QD_MARL/test_environments.py | 90 ++ src/QD_MARL/utils/__init__.py | 2 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 235 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 276 bytes .../__pycache__/print_outputs.cpython-310.pyc | Bin 0 -> 1846 bytes .../__pycache__/print_outputs.cpython-311.pyc | Bin 0 -> 4076 bytes .../utils/__pycache__/utils.cpython-310.pyc | Bin 0 -> 5967 bytes .../utils/__pycache__/utils.cpython-311.pyc | Bin 0 -> 10537 bytes src/QD_MARL/utils/print_outputs.py | 36 + src/QD_MARL/utils/utils.py | 181 ++++ src/{ => env}/melting_pot.py | 0 src/script.sh | 13 +- 105 files changed, 5391 insertions(+), 510 deletions(-) create mode 100644 src/QD_MARL/__pycache__/agents.cpython-310.pyc create mode 100644 src/QD_MARL/__pycache__/differentObservations.cpython-310.pyc create mode 100644 src/QD_MARL/__pycache__/differentObservations.cpython-311.pyc create mode 100644 src/QD_MARL/__pycache__/evaluations.cpython-311.pyc create mode 100644 src/QD_MARL/__pycache__/get_interpretability.cpython-311.pyc create mode 100644 src/QD_MARL/__pycache__/processing_element.cpython-310.pyc create mode 100644 src/QD_MARL/__pycache__/team_evaluation.cpython-311.pyc create mode 100644 src/QD_MARL/__pycache__/test_environments.cpython-310.pyc create mode 100644 src/QD_MARL/__pycache__/test_environments.cpython-311.pyc create mode 100644 src/QD_MARL/algorithms/Depracated/grammatical_evolution.py create mode 100644 src/QD_MARL/algorithms/__pycache__/__init__.cpython-310.pyc create mode 100644 src/QD_MARL/algorithms/__pycache__/common.cpython-310.pyc create mode 100644 src/QD_MARL/algorithms/__pycache__/continuous_optimization.cpython-310.pyc create mode 100644 src/QD_MARL/algorithms/__pycache__/genetic_algorithm.cpython-310.pyc create mode 100644 src/QD_MARL/algorithms/__pycache__/genetic_programming.cpython-310.pyc create mode 100644 src/QD_MARL/algorithms/__pycache__/grammatical_evolution.cpython-310.pyc create mode 100644 src/QD_MARL/algorithms/__pycache__/individuals.cpython-310.pyc create mode 100644 src/QD_MARL/algorithms/__pycache__/mapElitesCMA.cpython-310.pyc create mode 100644 src/QD_MARL/algorithms/__pycache__/mapElitesCMA_pyRibs.cpython-310.pyc create mode 100644 src/QD_MARL/algorithms/__pycache__/mapElitesCMA_pyRibs_GE.cpython-310.pyc create mode 100644 src/QD_MARL/algorithms/__pycache__/map_elites.cpython-310.pyc create mode 100644 src/QD_MARL/algorithms/__pycache__/map_elites_Pyribs.cpython-310.pyc create mode 100644 src/QD_MARL/algorithms/__pycache__/map_elites_ge.cpython-310.pyc create mode 100644 src/QD_MARL/configs/CartPole-ME_pyRibs.yaml create mode 100644 src/QD_MARL/configs/map_elite_config.json create mode 100644 src/QD_MARL/configs/tmp_map_elite_config.json delete mode 100644 src/QD_MARL/decisiontreelibrary/__pycache__/__init__.cpython-311.pyc create mode 100644 src/QD_MARL/decisiontrees/__pycache__/__init__.cpython-310.pyc create mode 100644 src/QD_MARL/decisiontrees/__pycache__/conditions.cpython-310.pyc create mode 100644 src/QD_MARL/decisiontrees/__pycache__/factories.cpython-310.pyc create mode 100644 src/QD_MARL/decisiontrees/__pycache__/leaves.cpython-310.pyc create mode 100644 src/QD_MARL/decisiontrees/__pycache__/nodes.cpython-310.pyc create mode 100644 src/QD_MARL/decisiontrees/__pycache__/trees.cpython-310.pyc rename src/QD_MARL/{ => deprecated}/decisiontreelibrary/README.md (100%) rename src/QD_MARL/{ => deprecated}/decisiontreelibrary/__init__.py (75%) create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/__pycache__/__init__.cpython-311.pyc rename src/QD_MARL/{ => deprecated}/decisiontreelibrary/__pycache__/conditions.cpython-311.pyc (100%) rename src/QD_MARL/{ => deprecated}/decisiontreelibrary/__pycache__/leaves.cpython-311.pyc (100%) rename src/QD_MARL/{ => deprecated}/decisiontreelibrary/__pycache__/nodes.cpython-311.pyc (100%) rename src/QD_MARL/{ => deprecated}/decisiontreelibrary/__pycache__/trees.cpython-311.pyc (100%) rename src/QD_MARL/{ => deprecated}/decisiontreelibrary/conditions.py (100%) create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/.gitignore create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/.gitlab-ci.yml create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/README.md create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/__init__.py create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/__leaves.py create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/conditions.py create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/leaves.py create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/nodes.py rename src/QD_MARL/{ => deprecated/decisiontreelibrary}/decisiontreelibrary/tests/__init__.py (100%) rename src/QD_MARL/{ => deprecated/decisiontreelibrary}/decisiontreelibrary/tests/test_conditions.py (100%) create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/tests/test_leaves.py rename src/QD_MARL/{ => deprecated/decisiontreelibrary}/decisiontreelibrary/tests/test_trees.py (100%) create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/trees.py rename src/QD_MARL/{ => deprecated}/decisiontreelibrary/leaves.py (100%) rename src/QD_MARL/{ => deprecated}/decisiontreelibrary/nodes.py (100%) create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/tests/__init__.py create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/tests/test_conditions.py rename src/QD_MARL/{ => deprecated}/decisiontreelibrary/tests/test_leaves.py (100%) create mode 100644 src/QD_MARL/deprecated/decisiontreelibrary/tests/test_trees.py rename src/QD_MARL/{ => deprecated}/decisiontreelibrary/trees.py (99%) create mode 100644 src/QD_MARL/deprecated/leaves.py rename src/QD_MARL/{ => deprecated}/utils.py (97%) create mode 100644 src/QD_MARL/evaluations.py create mode 100644 src/QD_MARL/test_environments.py create mode 100644 src/QD_MARL/utils/__init__.py create mode 100644 src/QD_MARL/utils/__pycache__/__init__.cpython-310.pyc create mode 100644 src/QD_MARL/utils/__pycache__/__init__.cpython-311.pyc create mode 100644 src/QD_MARL/utils/__pycache__/print_outputs.cpython-310.pyc create mode 100644 src/QD_MARL/utils/__pycache__/print_outputs.cpython-311.pyc create mode 100644 src/QD_MARL/utils/__pycache__/utils.cpython-310.pyc create mode 100644 src/QD_MARL/utils/__pycache__/utils.cpython-311.pyc create mode 100644 src/QD_MARL/utils/print_outputs.py create mode 100644 src/QD_MARL/utils/utils.py rename src/{ => env}/melting_pot.py (100%) diff --git a/README.md b/README.md index bd69c7bb6..4da63c6e3 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,26 @@ The project is supervised by Giovanni Iacca and Andrea Ferigo from University of In [references](/references) there is a comprehensive list of references of the studied papers to complete the project. ## Source codes -In [src](/src) are stored all the scripts developed during the project. The produced scripts work only if linked to the code developed in the following papers by Giovanni Iacca, Marco Crespi, Andrea Ferigo, Leonardo Lucio Custode: +In [src](/src) are stored all the scripts developed during the project. The produced scripts are based and continue the work developed in the following papers by Giovanni Iacca, Marco Crespi, Andrea Ferigo, Leonardo Lucio Custode: - [A Population-Based Approach for Multi-Agent Interpretable Reinforcement Learning](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4467882) - [Quality Diversity Evolutionary Learning of Decision Trees](https://arxiv.org/abs/2208.12758) -It is possible to run the aforementioned code by following the instructions in the README.md file in the [src/base](/src/base/) folder. \ No newline at end of file +It is possible to run the aforementioned code by following the instructions in the README.md file in the [src/base](/src/base/) folder. +Otherwise by changing the working direcotry `cd src` and running the following command: +`chmod +x script.sh` +`source script.sh` +On the terminal output will appear the following menu: +`Hello! Here you can set environment and run codes` +`Please enter an integer to select an option:` +`[1]. Activate environment` +`[2]. Deactivate environment` +`[3]. Run code dts4marl` +`[4]. Run code marldts` +`[5]. Run code qd_marl` +`[6]. Run code qd_marl in debug mode` +`[7]. Run test environment` +`[8]. Exit` +Press 1 to activate the python venv. +Then run `./script.sh` again and select one of the possible experiment. +If 3 or 4 is selected it will run the projects developed by Giovanni Iacca, Marco Crespi, Andrea Ferigo, Leonardo Lucio Custode. +if 5 or 6 (for debug and serialized mode) is selected it will run the project developed in this repository which apply a Quality Diversity approach to a Multi Agent Reinforcement Learning task. \ No newline at end of file diff --git a/src/QD_MARL/__pycache__/agents.cpython-310.pyc b/src/QD_MARL/__pycache__/agents.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69ef77c7c6a4acb3b5e49dc0d69d5c80e624678a GIT binary patch literal 4122 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 literal 5821 zcmcgwPizzE6`vW8?TPI$EKUeXfSOIh;!TN5S^5W1SlDP;RZGeex5!f8PlJ`r?rNfLxd z__<};?UB$EGK8fN`tbAmL|vW|(&3!nKu^)DY`hC%UND4dQ5U8qUHn9t4(V8DTGm6r zur325dKehhBS1xu0=skt7}L9eaXki1=y71To&avsyRqXoCw%gnQM7VVZ!>xj%f#Bo zr+|5(Ce{QElv`>NYCda1ErePiVom;OPfcD7#Pa9Ra|ypkK|>LuV5aOh)WWFwiWF)Q z)FOW0H5wW4rwdcULvb>#IAN`roiUtW-|LhUIkWhEdjXrSdKGTg)(=ZQ4w> zSjiT&a;cEd-EtC|RnkgjD?gL3^4Yl(GtyB(8@Jl!%SFgnAu$msAMifkJmCQV4OUZA5~ zgF;EeKJ$J(P6O$Rfq6AKSGARX)`#l!;;f_U)p5_?cE!S8G87XR*2SPi$r2 zId_I%=-fHVH!`ehcgwzp^%61I-1U~N?0U{!6I<-cdeB&X$=$KVP^sA@RdX#j)Re8H zI=2n3|7#f8e!z>ZhJaqTv|nxC2qZ@Gu`Ytm=fUlOu2riYP@T@)+zIgPU{xJjQHSd4 zNJAa5l@XWOz~5jTSt7T_k3h6D%LZVZ0mF-}voO{zHE{_e9tESO$esiL^>ydCRxdMt z79_P3qhl{sta8O-r0}%F?}8IAy>A)}*E?S-n#57cy%}r>bai{vM%=jrXf=-lppLlO zi0@t(LR}};l6{{ZT}>WXNgh}ptS65&l1J>sk-sJT8p-{u$wMp2Lyt%6$?-;V+)j-D z7nMz7;dYg6eZHHOB=V4OBT%R=Qsg7YoR%j4`EQhTh#$TaBL~Win^~r_ZjABO5OLeu zH1G=geGN3HrO;|p7c`(G=3=YLo)u-!!qvJm(oo1G)&^4KtL~k;tIp!nDK5F977L;9 z+3K?2vqArmsyXC&n9^mz`9;!)s5u@bhsNadd6rq^NWSc7^ zH<`q5;{ae@pb+<-dK*(Gg!f{%C4}6TjeZ@Pi_dLG*b~?&hNnZ0H1;j%mI9Zi=W~`5 z`mu4#B&HFBxTk#@J6_0}R<1Nt9gj`=F! zj$rp)a=*(zu;01pl3xrh#1;=fP=h`YT!x)F3SoJh7@Hi~+Or;Hbko0?J1n_;9igT zsxq*m3@jX~D|;Hs9^3zRib0o`i$O7=T?}2Qz+(MKPTs47tJw9d>E%=Ym2I|=jhd7k zfG6&gmWS=+n5~R;azVb5b8!X4JW}S?n|WW*k!v<~a=G=>$b~CuCa8_3)dc zSya=|hm6dN@m_Tdv&>@v=s_(n$PZ$+TE=(z{2wm5)RUe+_q)_zE?l%PU1?mJsb45I zE)?BHTPbsuO{P^!`zUYHG$*EMGbO!JAUdIGkfPxBFp8UuLI&GSKxV-+0k)5*Ap-KG z>^lTxzKr}fJ3&B)#y8{cgVCXIAk#N(z@6Z;E=9xr>p}oK#PGnn5I{r>4^q_wR53UJ zRQFNU1621@)q{u}KCvzYkdSCx0E!eoFH$`~MGWty=^kLU3{^cq^#hFG8eL&Y&K!P0 z@q^mN6*T<}HtD~1%?ejV#13UT#3>1s%&8ElFsEct5l)3cML87#rEn?=%6lc76+RND z zb5-59qV8K9t*c`Vwdn(2q)c?>eVHg+Y#|d3X_Kj$e|-;KTg$Y+uBIDm+E&sp{+!5z z`7)7ZZ6Om4seWVAIc-jV$UApPH@c#ZF27k<4>#1qwsP2&uI2VM{~wva^yjg{CCqz- zpIbKBAUWoT?(2dp78#3wzo@givqh?wTwol1CdH;!^Lu$keYvggrHK9kP|77cBx0jvXRUYjsDf@h( zTvt=idf6ts;vS9MGcq8&L~+mT2IS3|MjZc9;eR8|GO{ymX3)~kT~-vU@sw~@<{C>U z*z0Iwq+oW6fIRj8$O+D3h|}%4`v6=;)@7X9L$M7kUy2Je?n} zY|5gZD#zVSZJeFM5Ym}>1F#+vMR8pk6Dd2{8o%W2U$ypL5&J}N)Cu=`zogc$w&>z~ F{s%ldidp~w diff --git a/src/QD_MARL/__pycache__/differentObservations.cpython-310.pyc b/src/QD_MARL/__pycache__/differentObservations.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..204684da302ff65a3000344c35f2d4a77da3908b GIT binary patch literal 7716 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 literal 0 HcmV?d00001 diff --git a/src/QD_MARL/__pycache__/evaluations.cpython-311.pyc b/src/QD_MARL/__pycache__/evaluations.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28d0d8570531a9d5aa4e4979100212607aeeb0a1 GIT binary patch literal 11981 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 literal 0 HcmV?d00001 diff --git a/src/QD_MARL/__pycache__/get_interpretability.cpython-311.pyc b/src/QD_MARL/__pycache__/get_interpretability.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..602321b4d400bd12acf331f84e9cace5f6f52255 GIT binary patch literal 19731 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 literal 0 HcmV?d00001 diff --git a/src/QD_MARL/__pycache__/team_evaluation.cpython-311.pyc b/src/QD_MARL/__pycache__/team_evaluation.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c7ab0bb2b2de265169b1ffa0120fbd2f68e6d0b GIT binary patch literal 11783 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 literal 0 HcmV?d00001 diff --git a/src/QD_MARL/__pycache__/utils.cpython-311.pyc b/src/QD_MARL/__pycache__/utils.cpython-311.pyc index b2a8206a44a8c5f85af1c7fc9f2700574330e782..20b2c1fa4e24e3f083dce34dde1999c1c4809f67 100644 GIT binary patch delta 3786 zcma)9Yiv`=6`r}iwyz)8cI-H@osc*WA{>^400}e+d4Pd!9+hQd{+p$Wm0Ks#1R%wTQGos-C%t z^8j{X?)aNCGiT1sIdkUB_z#_bd)M@9p4THt1Ch<4ckI_pj>J6FZXf~qkbngyf_+#i z8J|iiSs$yEYM&ZPRYWtS@o6ySWh2@l&c_Yud^)ABj_8LBK7&GQfHwM!L%ffNx^@Ej zOoC0&O<_6I`5;sQ{K_*~i8)z4ySrb6N4y8ftokvoy*H})65}0YJ2#EDldriy>O45B z9udP)iCoduZ7zp7V#fiN#!=3SI6jB;>LYlO<0m6&t|Y^iq_~oMf6*Oh@Ex+n*i+gF zykqwLVKI0%5)gaBF{#e)4@bk2-`{#~%BaV9H@V4I;b!uCeiJ@O7WtquMr?+GS>Ept zMgp-|FcgXS{T?jtCGVRYxarn-UmhE zy14$0RSti<%I3VvdkqYd)>K0>Cs<_tP4o$K8>YLvx*4)zhIpfNORApaMV_Xq|&@K5UNH~SX5)ATQT>W$PvGw#$J1EJw{i4PY~JP-M2(NmHu{=iMc#vPnxqoH%s9agM? zs^|g8vIC)LNbDsS9S3lX{K?T&$w^`WVqSD0s~%876ZP6G8wvGGS+y7*I4w;(0=R*Q z&bRR;a^KnFp+Y48VUIPdqXd;0%d%l9BreA%EuD=9A5!MphFeIFYiF^zgEn?k(YgcY zf}s%_tvm(fqN|<}kCOYYZSJfg5R}daBL3*G5X$0^*hh!-0>r2(+~m2d)-Vc`pEmtU zMcyhIW%Y8J#&~Q9GVeP^cVh(m582TvI0|5B|eypX$H1_4?KsJY7FkzfipC z#@O`OOvSbM_4pLKXxo$&Zhb#(tIOExa!6aywCMKCHqGr$)wZPFtr>S~4r%O73jB;s zCUCN0p{D)Tz^pLW^<~3c*IZY+rae>Bp04T4)O02d3newFlDa3gJAPCDS$(>;B~#my z>|Q8s{N&J#ceZw}V77L)HeK47DQ%n^OqcGQ}XF-p_Z} zF^{%ks649E_mrt0?O`e1ih*g~sfL#MG8MGUSI}YeRg`nSS_Slxx-Hr3dz+&ONFJd)Ura?Spm<7{t`=+Nk36FF_p3 z=wjur->YWPZOzGa|>ucU@w*S6NXMy=TV1+KEnCopw)%m~Z) zsl7?{TeeyI6>ipg@IjAL?3^yQYh>sW6QrQL#Ka3L9#55sk&bfLJG*FaJq6_*@1+#I zyhM8ch%|C2?kQ4@UwEVcmunpw7S2XOZQ@U$mj;elfZQzKYSE1=-vZ*tm5+Q={#Ngn z6xxs%P)F)jImc?T8?Ko=6c%E04sC=wve*4y0O9Hss#>3O>|Wf7b7&oQFc8mkXcdao S7+ic6OloYQ)fYjDj{gU;wr;He delta 1910 zcmbVNTWlLe6rEY`dUw6{t{-vjIEfn?L*s@fX%i))FB;`pwNQzaN4H3p<8dPs+bQEs zL!u3hP()o6q?Gxf;)fui@Q8$p^3xB5`az`d6H;V}Vj)pf^@ERs5s#Ywlo`8e;!vcD z(e9bYy>suKvvcqM_}tYl^&^MF257tCP~v|(FRK1?AJW;o00A8!h`1!wVX5bI3+*jQ zKE>-iWHLS}q^!C%CF){nE+nOttjndbm5yz?Ev4uRor^O-w<9N#XQ1xjKtDjX8Gw|y zx?lldk-p`oYgE<*kev!0apxAH3#rtM8)+0hh#Lf9*XPBU1y+%l`H9^eHNg!19;9%d z9>9c{M{ovAacLn7PSc~zNK>52_E|{1uuAR^2BH$UR5H_NSPhe~aF5+(;PlR3y?vy3|<77G{chMstW0ppwr5ej8FoO6s_i-1GR!uO3aXnwEKq zS@dMptah5CcSJI+Jz#-uUL>s^zbK~nn0#4>?MR7+XTz{5Rz`;$8v&R?ks%iJ-*YF{OEhMYe;Uv_fS{Ha5Hfq#}(tyw%%nQ_7<=koa!~ zoLd<1+Bsm<+z*d-ri@&sLdLd*cp9S0LJXKT(_Z;GAeeo7GjCp+pAWHe&lfs3`{kn$ za`{f$)c_2ZKlfV*U|KLv0^}Y9h zAr!^z#*kA144cSmAPA3=+kswqhP2gmScPGh8RYevG!@>i8GzHIqjrDevtegmrb!qX zj;G_9JWrDmm-B$R!V-nNh04i$wY%XyvQpc|g`LD**P`UbveO7YPP*%MYxpGFaFjyE zNrBAO?NSUnIK@`Za%8z~JSYLpJE!N=`tQ`xw`yqqrH@~|rtZn9d#3wuh$=Z8obX-s zUDJ-_v?FWMksGQz`@(czfp-d3&5jm}f7Swf zbpb%3Y6Groo~)5I3KqIrEEaDm)t5r^*7?a?{oc!+x%&NAUc9FC=9JzwzBj{?IhmwgQ%<>2eY{i4(u|y^@nl^Ae&O}i>L1uRb zy4sjZ3xiViW7P>Wf-&>IrIg*34x~oWSTcSHPf{rhK2t~j+Sx?f8h1U@yap!5G# rVNby-!ukSu0978S6+k6CNj_~n(b5e2U;%8xAt*zc{x1FnC0hOgBh;n4 diff --git a/src/QD_MARL/agents.py b/src/QD_MARL/agents.py index dadb09df8..9fcb27b9a 100644 --- a/src/QD_MARL/agents.py +++ b/src/QD_MARL/agents.py @@ -1,3 +1,15 @@ +import os +import sys +from utils import print_debugging +sys.path.append(".") +import random +import time +from copy import deepcopy +from math import sqrt +import pettingzoo + +import numpy as np + class Agent: def __init__(self, name, squad, set_, tree, manual_policy, to_optimize): self._name = name @@ -32,29 +44,50 @@ def get_output(self, observation): def set_reward(self, reward): self._tree.set_reward(reward) self._score[-1] = reward + + def set_action(self, action): + if self._manual_policy is not None: + self._manual_policy.set_action(action) def get_score_statistics(self, params): - score_values = [score_dict[key] for score_dict in self._score for key in score_dict] - return getattr(np, f"{params['type']}")(a=score_values, **params['params'])#Can't compare dicts with > + + scores = np.array(self._score) + avg = np.mean(scores) + return avg + #return getattr(np, f"{params['type']}")(a=self._score, **params['params'])#Can't compare dicts with > def new_episode(self): self._score.append(0) def has_policy(self): return not self._manual_policy is None + + def observe(self, observation): + pass def __str__(self): return f"Name: {self._name}; Squad: {self._squad}; Set: {self._set}; Optimize: {str(self._to_optimize)}" class CoachAgent(Agent): - def __init__(self, name, squad, set_, tree, manual_policy, to_optimize): - super().__init__(name, squad, set_, tree, manual_policy, to_optimize) + def __init__(self, name, squad, tree, manual_policy = None): + super().__init__(name, squad, None, tree, manual_policy, None) + self._agents_features = {} + self._max_fit = 0 + self._get_team = False + self._fitnesses = {} + self._score = [] def get_output(self, observation): return super().get_output(observation) - def set_reward(self, reward): - return super().set_reward(reward) + def set_reward(self, agent_features): + if self._agents_features is not None: + if agent_features > 5: + self._tree.set_reward(1) + self._score.append(1) + else: + self._tree.set_reward(0) + self._score.append(0) def get_score_statistics(self, params): return super().get_score_statistics(params) @@ -68,5 +101,5 @@ def has_policy(self): def __str__(self): return super().__str__() - def select_team(self): + def select_team(self): pass \ No newline at end of file diff --git a/src/QD_MARL/algorithms/Depracated/grammatical_evolution.py b/src/QD_MARL/algorithms/Depracated/grammatical_evolution.py new file mode 100644 index 000000000..4d677669a --- /dev/null +++ b/src/QD_MARL/algorithms/Depracated/grammatical_evolution.py @@ -0,0 +1,488 @@ +#!/usr/bin/python3 +""" +Implementation of the grammatical evolution + +Author: Leonardo Lucio Custode +Creation Date: 04-04-2020 +Last modified: mer 6 mag 2020, 16:30:41 +""" +import re +import os +import string +import numpy as np +from typing import List +from abc import abstractmethod + + +TAB = " " * 4 + + +class GrammaticalEvolutionTranslator: + def __init__(self, grammar): + """ + Initializes a new instance of the Grammatical Evolution + :param n_inputs: the number of inputs of the program + :param leaf: the leaf that can be used - a constructor + :param constant_range: A list of constants that can be used - default is a list of integers between -10 and 10 + """ + self.operators = grammar + + def _find_candidates(self, string): + return re.findall("<[^> ]+>", string) + + def _find_replacement(self, candidate, gene): + key = candidate.replace("<", "").replace(">", "") + value = self.operators[key][gene % len(self.operators[key])] + return value + + def genotype_to_str(self, genotype): + """ This method translates a genotype into an executable program (python) """ + string = "" + candidates = [None] + ctr = 0 + _max_trials = 1 + genes_used = 0 + + # Generate phenotype starting from the genotype + # If the individual runs out of genes, it restarts from the beginning, as suggested by Ryan et al 1998 + while len(candidates) > 0 and ctr <= _max_trials: + if ctr == _max_trials: + return "", len(genotype) + for gene in genotype: + candidates = self._find_candidates(string) + if len(candidates) > 0: + value = self._find_replacement(candidates[0], gene) + string = string.replace(candidates[0], value, 1) + genes_used += 1 + else: + break + ctr += 1 + + string = self._fix_indentation(string) + return string, genes_used + + def _fix_indentation(self, string): + # If parenthesis are present in the outermost block, remove them + if string[0] == "{": + string = string[1:-1] + + # Split in lines + string = string.replace(";", "\n") + string = string.replace("{", "{\n") + string = string.replace("}", "}\n") + lines = string.split("\n") + + fixed_lines = [] + n_tabs = 0 + + # Fix lines + for line in lines: + if len(line) > 0: + fixed_lines.append(TAB * n_tabs + line.replace("{", "").replace("}", "")) + + if line[-1] == "{": + n_tabs += 1 + while len(line) > 0 and line[-1] == "}": + n_tabs -= 1 + line = line[:-1] + if n_tabs >= 100: + return "None" + + return "\n".join(fixed_lines) + + +class Individual: + """Represents an individual.""" + + def __init__(self, genes, fitness=None, parents=None): + """Initializes a new individual + + :genes: a list of genes + :fitness: the fitness for the individual. Default: None. + + """ + self._genes = np.array(genes) + self._fitness = fitness + self._parents = parents # A list containing the indices of the parents in the previous population + self._id = "".join(np.random.choice([*string.ascii_lowercase], 10)) + + def get_genes(self): + return self._genes + + def __repr__(self): + return repr(self._genes).replace("array(", "").replace(")", "").replace("\n", "") + "; Fitness: {}; Parents: {}".format(self._fitness, self._parents) + + def __str__(self): + return repr(self) + + def __eq__(self, other): + return sum(self._genes != other._genes) == 0 + + def copy(self): + return Individual(self._genes[:], self._fitness, self._parents[:] if self._parents is not None else None) + + def __hash__(self): + return hash(self._id) + + +class Mutator: + """Interface for the mutation operators""" + + @abstractmethod + def __call__(self, individual): + pass + + +class UniformMutator(Mutator): + """Uniform mutation""" + + def __init__(self, gene_probability, max_value): + """Initializes the mutator + + :gene_probability: The probability of mutation of a single gene + :max_value: The maximum value for a gene + + """ + Mutator.__init__(self) + + self._gene_probability = gene_probability + self._max_value = max_value + + def __call__(self, individual): + mutated_genes = np.random.uniform(0, 1, len(individual._genes)) < self._gene_probability + gene_values = np.random.randint(0, self._max_value, sum(mutated_genes)) + genes = individual._genes.copy() + genes[mutated_genes] = gene_values + new_individual = Individual(genes, parents=individual._parents) + return new_individual + + def __repr__(self): + return "UniformMutator({}, {})".format(self._gene_probability, self._max_value) + + +class Crossover: + """Interface for the crossover operators""" + + @abstractmethod + def __call__(self, individual1, individual2) -> List: + pass + + +class OnePointCrossover(Crossover): + """One point crossover""" + + def __call__(self, individual1, individual2): + point = np.random.randint(0, len(individual1._genes) - 2) + + new_individuals = [Individual([*individual1._genes[:point], *individual2._genes[point:]])] + new_individuals.append(Individual([*individual2._genes[:point], *individual1._genes[point:]])) + return new_individuals + + def __repr__(self): + return "OnePointCrossover" + + +class Selection: + """Abstract class for the selection operators""" + + def __init__(self, logfile=None): + self._logfile = logfile + + @abstractmethod + def __call__(self, fitnesses: List) -> List: + """ Returns a sorted list of indices, so that one can simply crossover the adjacent individuals """ + pass + + def _log(self, msg): + if self._logfile is not None: + with open(self._logfile, "a") as f: + f.write(msg + "\n") + + +class BestSelection(Selection): + """Interface for the selection operators""" + + @abstractmethod + def __call__(self, fitnesses: List) -> List: + """ Returns a sorted list of indices, so that one can simply crossover the adjacent individuals """ + order = sorted(range(len(fitnesses)), key=lambda x: fitnesses[x], reverse=True) + self._log("The individuals (Fitnesses {}) have been sorted as {}.".format(fitnesses, order)) + return order + + def __repr__(self): + return "BestSelection" + + +class TournamentSelection(Selection): + """Tournament selection""" + + def __init__(self, tournament_size, logfile=None): + """Initializes a new tournament selection + + :tournament_size: number of individual in the tournament + """ + Selection.__init__(self, logfile) + + self._tournament_size = tournament_size + + def __call__(self, fitnesses): + tournaments = np.random.randint(0, len(fitnesses), (len(fitnesses), self._tournament_size)) + selection = [] + + for i in tournaments: + selection.append(i[np.argmax([fitnesses[j] for j in i])]) + self._log("Individuals in the tournament: {} (fitnesses: {}), selected: {}".format(list(i), [fitnesses[j] for j in i], selection[-1])) + + return selection + + def __repr__(self): + return "TournamentSelection({})".format(self._tournament_size) + + +class Replacement: + """ Interface for the replacement operators """ + + @abstractmethod + def __call__(self, old_pop: List, new_pop: List) -> List: + pass + + +class NoReplacement(Replacement): + """Uses the new population.""" + + def __call__(self, old_pop, new_pop): + return new_pop + + +class ReplaceIfBetter(Replacement): + """Replaces the parents if the new individual is better""" + + def __init__(self, logfile=None): + self._logfile = logfile + + def _log(self, msg): + if self._logfile is not None: + with open(self._logfile, "a") as f: + f.write(msg + "\n") + + def __call__(self, old_pop, new_pop): + final_population = {old: old.copy() for old in old_pop} + assert len(final_population) == len(old_pop), f"Initially {len(final_population)} != {len(old_pop)}" + assert len(final_population) == len(new_pop), f"Initially {len(final_population)} != {len(new_pop)}" + + for ind in new_pop: + # Here I use final_population instead of directly the parents because + # They may have already been replaced by someone else + if ind._parents is None: + continue + parents_fitnesses = {p: final_population[p]._fitness for p in ind._parents} + + worst_fitness = float("inf") + worst_parent = None + + for parent, fitness in parents_fitnesses.items(): + if fitness < worst_fitness: + worst_fitness = fitness + worst_parent = parent + + if ind._fitness >= worst_fitness: + final_population[worst_parent] = ind + + final_population = list(final_population.values()) + assert len(final_population) == len(old_pop), f"{len(final_population)} != {len(old_pop)}" + assert len(final_population) == len(new_pop), f"{len(final_population)} != {len(new_pop)}" + return final_population + + def __repr__(self): + return "ReplaceIfBetter" + + +class Replacement: + """ Interface for the replacement operators """ + + @abstractmethod + def __call__(self, old_pop: List, new_pop: List) -> List: + pass + + +class ReplaceWithOldIfWorse(Replacement): + """Replaces the parents if the new individual is better""" + + def __init__(self, logfile=None): + self._logfile = logfile + + def _log(self, msg): + if self._logfile is not None: + with open(self._logfile, "a") as f: + f.write(msg + "\n") + + def __call__(self, old_pop, new_pop): + final_pop = [] + + for o in old_pop: + o._parents = None + + for i in np.arange(0, len(new_pop), step=2): + i1, i2 = new_pop[i:i + 2] + if i1._parents is None: + # First generation + assert i2._parents is None + final_pop.append(i1.copy()) + final_pop.append(i2.copy()) + continue + + if len(i1._parents) == 1: + for ind in [i1, i2]: + parent = ind._parents[0] + if ind._fitness > parent._fitness: + final_pop.append(ind.copy()) + else: + final_pop.append(parent.copy()) + else: + p1, p2 = [k for k in i1._parents] + fn1, fn2 = [k._fitness for k in [i1, i2]] + fo1, fo2 = [k._fitness for k in [p1, p2]] + + newbest = max(fn1, fn2) + newworst = min(fn1, fn2) + oldbest = max(fo1, fo2) + oldworst = min(fo1, fo2) + + if (newworst > oldbest) or (newbest > oldbest and newworst > oldworst): + final_pop.append(i1.copy()) + final_pop.append(i2.copy()) + else: + if newbest < oldworst: + final_pop.append(p1.copy()) + final_pop.append(p2.copy()) + else: + if fn1 > fn2: + final_pop.append(i1.copy()) + else: + final_pop.append(i2.copy()) + + if fo1 == oldbest: + final_pop.append(p1.copy()) + else: + final_pop.append(p2.copy()) + + assert len(final_pop) == len(old_pop) == len(new_pop) + return final_pop + + def __repr__(self): + return "ReplaceWithOldIfWorse" + + +class GrammaticalEvolution: + """A class that implements grammatical evolution (Ryan et al. 1995)""" + + def __init__(self, pop_size, mutation, crossover, selection, replacement, mut_prob, cx_prob, genotype_length, max_int=10000, logdir=None): + """TODO: to be defined. + + :pop_size: the size of the population + :mutation: the mutation operator + :crossover: the crossover operator + :selection: the selection operator + :replacement: the replacement operator + :mut_prob: the mutation probability + :cx_prob: the crossover probability + :genotype_length: the length of the genotype + :max_int: the biggest constant that can be contained in the genotype (so random number in the range [0, max_int] are generated) + + """ + self._pop_size = pop_size + self._mutation = mutation + self._crossover = crossover + self._selection = selection + self._replacement = replacement + self._mut_prob = mut_prob + self._cx_prob = cx_prob + self._genotype_length = genotype_length + self._individuals = [] + self._max_int = max_int + self._logfile = os.path.join(logdir, "grammatical_evolution.log") if logdir is not None else None + self._init_pop() + self._old_individuals = [] + self._updated = False # To detect the first generation + + def _init_pop(self): + """Initializes the population""" + for i in range(self._pop_size): + self._individuals.append(self._random_individual()) + self._log("INIT", "Individual {}:\n{}".format(i, self._individuals[-1])) + + def _log(self, tag, string): + if self._logfile is not None: + with open(self._logfile, "a") as f: + f.write("[{}] {}\n".format(tag, string)) + + def _random_individual(self): + """ Generates a random individual """ + return Individual(np.random.randint(0, self._max_int + 1, self._genotype_length)) + + def ask(self): + """ Returns the current population """ + self._old_individuals = self._individuals[:] + if self._updated: + self._individuals = [] + _sorted_pop = [self._old_individuals[j].copy() for j in self._selection([i._fitness for i in self._old_individuals])] + + for s in _sorted_pop: + s._parents = None + + for s in _sorted_pop: + s._parents = None + + self._log("POPULATION", "Sorted population:\n" + "\n".join("Individual {}:\n{}".format(srt_idx, _sorted_pop[srt_idx]) for srt_idx in range(len(_sorted_pop)))) + + cx_random_numbers = np.random.uniform(0, 1, len(_sorted_pop)//2) + m_random_numbers = np.random.uniform(0, 1, len(_sorted_pop)) + + # Crossover + for index, cxp in enumerate(cx_random_numbers): + ind1, ind2 = _sorted_pop[2 * index: 2 * index + 2] + if cxp < self._cx_prob: + self._log("CX", "cx happened between individual {} and {}".format(2 * index, 2 * index + 1)) + self._individuals.extend(self._crossover(ind1, ind2)) + self._individuals[-1]._parents = [ind1, ind2] + self._individuals[-2]._parents = [ind1, ind2] + self._log("CX", "Individual {} has parents [{}, {}] (Fitness [{}, {}])".format(2 * index, 2 * index, 2 * index + 1, ind1._fitness, ind2._fitness)) + self._log("CX", "Individual {} has parents [{}, {}] (Fitness [{}, {}])".format(2 * index + 1, 2 * index, 2 * index + 1, ind1._fitness, ind2._fitness)) + else: + self._log("CX", "cx did not happen between individual {} and {}".format(2 * index, 2 * index + 1)) + self._individuals.extend([Individual(ind1._genes), Individual(ind2._genes)]) + self._individuals[-1]._parents = [ind2] + self._individuals[-2]._parents = [ind1] + self._log("CX", "Individual {} has parents [{}] (Fitness {})".format(2 * index, 2 * index, ind1._fitness)) + self._log("CX", "Individual {} has parents [{}] (Fitness {})".format(2 * index + 1, 2 * index + 1, ind2._fitness)) + + if len(_sorted_pop) % 2 == 1: + self._individuals.append(_sorted_pop[-1]) + self._individuals[-1]._parents = [_sorted_pop[-1]] + + # Mutation + for i, mp in enumerate(m_random_numbers): + if mp < self._mut_prob: + self._log("MUT", "Mutation occurred for individual {}".format(i)) + self._individuals[i] = self._mutation(self._individuals[i]) + self._old_individuals = _sorted_pop + return self._individuals + + def tell(self, fitnesses): + """ + Assigns the fitness for each individual + + :fitnesses: a list of numbers (the higher the better) associated (by index) to the individuals + """ + for index, (i, f) in enumerate(zip(self._individuals, fitnesses)): + if i._parents is not None: + self._log("FITNESS", "Individual {} has fitness {}. Its parents ({}) have fitnesses {}".format(index, f, i._parents, [k._fitness for k in i._parents])) + else: + self._log("FITNESS", "Individual {} has fitness {}".format(index, f)) + i._fitness = f + self._update_population() + + def _update_population(self): + """ Creates the next population """ + self._updated = True + self._individuals = self._replacement(self._old_individuals, self._individuals) diff --git a/src/QD_MARL/algorithms/__pycache__/__init__.cpython-310.pyc b/src/QD_MARL/algorithms/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71dd414745d1c09341de3c1750e32580fd04e487 GIT binary patch literal 532 zcmZXQ%Sr<=6oxac*0C3)6fdt(L4zwdqL#L}u~MsU4RIz+PswFSGL^dWb$q34UHJ;G zOvk7LCggni|C|$&qdJ`?@?-XR!CoVT-q)f!YF=FWWl|qfltvuge=`cK1=a&M0yhI2 zfm?xb;C5g$u;p31L;d=Pe+TMEE;_vFw_PJqLMZ9>6D2LkLKTLn+=74?YQZ0LGbWjZ zgixL-4R$VE@5jou$}|-MWaf6uov%qKCv2&BQI@wrrm%#xpxn5opgCb2EHke2w@%U(MELyK1_yrYEO(q>@4~ zX$^jmU?rYVW0}TNtrje?Lse+Qys7!o?TAb@EU9JK=mdK?8O*NnZyO9I1pFO@9Oo++ Wp9z%~oSm<`YYu#fQ5^lB?dS`s=$|40 literal 0 HcmV?d00001 diff --git a/src/QD_MARL/algorithms/__pycache__/common.cpython-310.pyc b/src/QD_MARL/algorithms/__pycache__/common.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..511608091f8e919ce38fb3c769730f5c489c75ad GIT binary patch literal 1635 zcmah}!EPHj5G8jhTUO*$P23a#3Y0~Auz*yX9dhUPhwV%*iPo3e~mJOr`0ggCak~8z(8=1|`4uP@%+hg@7Bjj&% zt`68EPhh%jFc}$>pGZct=pD%-MNi{#1b&<`@LBNVtaU*8Ym2`+9EP)UAWc!2GPu8A zj!y6or`qwtWL2)XG^I~<>0p89dF6Gky;hDN8_Uhy>q0M-bH<=oQXv17fRXR1~yDtv!ai<5h49<&^Sk%S9bp#V(`Ui#T$qZn+OAwI7 zyFe@IY+re+)oT!opMsRIOb*jX4asHPo3IZU>2lHt2yYD=JP@KGKZJ|8;^uhu$Yp?` zRlc&N8}g^k^Os-xQJ}#hqi6I2V|*!q+n7!GSjQ9P?_eQVQK!A=T`M0x?)BF#!c%vI zXu3j(x+}!1D$VghU>u_aEbFfGsn-%%o|??w!hIz3iou{Ex0|qVh+dm8+K!^uO&az; zs&P0*+&wT?HU6F))1dSAb)CN_jEvWz;am0BBWPe(KNT8;(r*Wy*e$&E1)87>tiz(hf*k72^IE0Kb3kep^(#CQLStgzz`RsPH;ULT q8WP^?GyJE?jD*WbRHO4y%j&JL3i570YBc*JHf?Bg7WHmy-Tn`HdVlo* literal 0 HcmV?d00001 diff --git a/src/QD_MARL/algorithms/__pycache__/continuous_optimization.cpython-310.pyc b/src/QD_MARL/algorithms/__pycache__/continuous_optimization.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd87281171fd7ca5fd1ddd7cb7223faf7e21776d GIT binary patch literal 5937 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 G`TqhL7&zJh literal 0 HcmV?d00001 diff --git a/src/QD_MARL/algorithms/__pycache__/genetic_algorithm.cpython-310.pyc b/src/QD_MARL/algorithms/__pycache__/genetic_algorithm.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b065edf25d517652dd8633168138f05965f3303 GIT binary patch literal 7342 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 literal 0 HcmV?d00001 diff --git a/src/QD_MARL/algorithms/__pycache__/genetic_programming.cpython-310.pyc b/src/QD_MARL/algorithms/__pycache__/genetic_programming.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04f2dfb832dfab75e983aba2355e7be1fa7c94e0 GIT binary patch literal 6781 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 literal 0 HcmV?d00001 diff --git a/src/QD_MARL/algorithms/__pycache__/genetic_programming.cpython-311.pyc b/src/QD_MARL/algorithms/__pycache__/genetic_programming.cpython-311.pyc index 813f84f0ab6db85cb930bc8d0c4256222b6dd5be..bedb52c9d1b1b6c44f7b2b96d9af3dc43ac8333e 100644 GIT binary patch delta 62 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 Bfv^Ao literal 0 HcmV?d00001 diff --git a/src/QD_MARL/algorithms/__pycache__/grammatical_evolution.cpython-311.pyc b/src/QD_MARL/algorithms/__pycache__/grammatical_evolution.cpython-311.pyc index a213a4e7826ebb907955108a3c772b28a9903297..7296594a774aa095dcc55ce0e19f63294c82d46e 100644 GIT binary patch delta 9362 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 delta 5359 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 diff --git a/src/QD_MARL/algorithms/__pycache__/individuals.cpython-310.pyc b/src/QD_MARL/algorithms/__pycache__/individuals.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b13c9d8b90797035e5fff57005c7b64e38651c94 GIT binary patch literal 13091 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 ILMM;>4~7qB+yDRo diff --git a/src/QD_MARL/algorithms/__pycache__/mapElitesCMA.cpython-310.pyc b/src/QD_MARL/algorithms/__pycache__/mapElitesCMA.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7858d1137159c5fe3f978bb49425e329fb90989 GIT binary patch literal 14594 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 literal 0 HcmV?d00001 diff --git a/src/QD_MARL/algorithms/__pycache__/mapElitesCMA_pyRibs.cpython-311.pyc b/src/QD_MARL/algorithms/__pycache__/mapElitesCMA_pyRibs.cpython-311.pyc index 692a248e9f4c2721242420b20581687e2490fbfc..eb16c8e85516397a78af2dc13d45a04b9f704d77 100644 GIT binary patch delta 8116 zcmb7Jd3apab$@p@&Aw?gn|4dKJho*?wq<#j<$Xig@q!s+#8_`6jXg6Gcb>3O#~Im} zAUiI|z61j$MTTHZ?NDPSU)=@@v}sZx@JaqAMR|b&;Um;-NaR9EN+9hy_h~lc{?RMx zr@NecwtMfn%X|I2YWSkc`mDub6d--OW>5I_U8k&GvglM@Yifre3Sv>z7&8S;F>}xy zvjiWO)SUY<8ceKCK~ z&-0dOX{;<*#`9Llmj}yv-Uj)KV5))_?7*lpSjqE_XjQB_Sk3b!IxSWctcAQr)J3Or zWl3~Ka0VzwqIJPKm9SnAoyP>R_?$`*F2J9Xf-`xG3tCFTja;cu!vAd>Au zz{8@XQA1=hYxH?yB%?2>?o<&!E7Q&(rK790*9j?O%|+LZMbJwPf1h4qjruxvsmspp z)_WU8RZ`fWB$kg7P7AW)?3Q-3oXQm|8WpaYB-v#Pcv5iKbDQoIQJ&PIr)~}PPL<4)z z;7R3Co&%R(K)3S*QrwxmD8S$u+pnST2Xta2mY7pDP$c&D$Hhbyt%BNXXzMzP7$CA% zlJs zR`>QvvUy`%jNB6u`$N(7TV*W`C&H4f>*(x_CnVa4w9A>sO8GSFuYb*Sw3?E1DNA=|!8btBLCt@|l1L-Si(hD;|}eI5;Au>lTkY zm!zFbklBRZ%zkaUOxl>mQosH=(jFj+jo-*q46?KZV8F397LlYdU42X2+(e{17HV9E zWH?NquC4fFa!g-JkyrCN?xY*JEJrRSd0h5|q|Tm>L}Vb`ikxuja!zSm<(R$_SiM@} z^6}O7m*A5#IPZZ(NputYp`}qJs@Q{SKQmd6I-xZ?Wt0iSn6-wvQYCCu(!(?oK;* zvrYEfVHmz=-#E7vS@AgwqcIN(0TsoGBAepc+LSuxlC5l$W0tNB zsyemo{q3&N2OX0tvWBHxZqmvgaV;Pr_J(UZr=WiJxr;(|q@YpSN3 z?Q+kAnv~mP$JIg6rF0L1o1Jr4yZLOs1#hM;5YHo}+!YeE4cPKr zfn+;JFM9@w6ZH(}s2_Q7W6RhYU%h=LmbW9|Y=Z@J#OHM(E#(|B{85Bb_I=+>r&${& zlR_Srv5$Q_6bpT<&7UUGQFm#bdR~%<-dyNBE_9$so{kH7A)utRRTx=U}2= zfIlT2C1M5esLGUisXAM#gfv^8QT^tVZW;_(#4^eI6HbE za4#{J*Nn8M%jXYi(t1DpaMnWar5)+!_5UpXEIt;Fq{Gr!S^s!hf4Z!n)y-bc24;VU zFHRS$opW~CS>O}U=G2mlz##CI#CIrJyMF7g5PfbcSZE`0F?=W&CP;k~d4bKZUoyq; zX#ESVPXa?ag79_L{qaawFO4azi`H*l&9}0D0^%G`I=7kpke#2qp0tfl&aGFi#u*f_ z&=-(p&U^oe#h)U)0U(<@Q0?AWNBB@5Rb0zs3`T!yW_LA}K_EHYRA=J8d<$v^s?e9_ zyl#KX#+rTwQb*@$RkM*qHbxQ=*la`b&MfXB$yJB z?jDK$92yfEfGS8Z&89amC$F*2<}nblU?Ek_9>Lz|B#3u#e2~@Xb#&i=d1w4Uy!T+d z3hM(V`aBz7R5AYolIl8XI0XA8KR@sYn$wB<o-ATmzWpB73=ty*d(1O@^DjsfiD z6_xC(wbe|w`em@{C#xSNOFsuD5HxNkpF8-!QOFkve*`Gx|Jd5aMRw$WSzxcOTFWZd z^#R+5*7c~`{sVic5cs*!e4~H~Xp)V1oD(A$`ltYWXinH!wrFFeUon>U_DVEstgPzn zq+hYSH`c<~KC*E!sbFIpzXZ8&ZhB1BeS;k+hN_VD?=kl$1TN$cnB!(~E7eG%0f1Iugw z&OD_UjwZs&ks#}WuWN#{iK06Bnji~vN0eROHixWc?```%%&N*|J~sJ{eHEW!2P0ZF z6~bVViMe3Yw|z0$!TxG{BiQiE?T3hGHPWM%ly6*U4NQ$WZ2!)H?L8>SS~$+f5&;dY zt*3U*1pmLWvx(fszS{X0N(|Jgf$GxT>~mO4?0uBz{5aFxAZ>^akVx|Mwr+)F0d<~2^4Dc#F=Yq)xK3yE=U2DK_Hts**a^ zxVx53Zn0;IV_TKt_0daZrx$pgw4^{wy;Ol(vTMaYxRKa(aaU3paj?+B8i%2P z(7??pEnsO-Iy){GQ0f)K)wd=MthL{pv0IZgj1;rBem7XfE!V}{p~}hbUg;q&kT?y! zqfix8F@0z$al+vd?n(U)UEG^20!C9sV_cOq9EO*UkrH0@UdW+=f_tjqbl_e#m6G@% z;$?%kxtVvLCB>I053W9XJCIO#5)$YJ%>66E&DPNk5k0pN^3C%X*zLCnd?Df?AaAMh zZPI})K&H!{yT!GL6 zOUz<@XxE8d$9JFHeX?Un1^4hj7l-E^?>X6X+;-A7WSbaW(fNo9g455#>-4Ew=ol!@ zGxh&(;Ez#dew5)J%7u}fEf&QV4O|MFWW;%N1_+i_-oAOX4s&4NJUSC{8QU($17O=b z@dk2X)ZBYe4GJHldo=8i2Mtafr0SQ@wAPB_( z46&Nu64#-!&j2Jas49o`I}SGMu7l{rVs_?Wb=~}{-uai>#=Q&E-i4EDb(MQk$N+7_ z?ij)88uqJ$J(Y8y+jDB#jWyFzd)(#JG=l3FQR{j)l#_>49n;4i0T(bpzEf1{(oC8a*)JQO!ct+B?y6 znS}8k%b$vRL3luT!1jRsi~!e6*4E`>uOvNYe%m9*^iZc_A5?lZhxHIPKPYwkVeirx z-a^+1!({K-!v=Ppdf7;~#_Y?8fmcfQLW_Iu30u-2s&my)(&S1Vcp~J9OVzUR!`n^v zA={8JWbaaoIxG_#oBqwUZN}raq%qeMUK)8nsuMNXg4e&3u0k5H0IlUIX=E3^S-Cu) zgL=Ry2-&`^Qb~puYYS&4SfQo?R+OqKxxt>}HjrXwn^WByw3CH4_}N&>3rb&01t8*2 z?(nnvK}U&t0HS!Osv{ff;V|$t+dkOfGafbp^I1}LPP|x@H0^g_h|>`vX@ckqaTR9b z$)=7>wr>uC9PYThfbYh|^)1)RL)(&Acp=7flz% z`&Kt>ERTQd*@bwKP}#U2_Hp@tq3U_}KFgYz8)8Jg~dw zG3Xrm3@i+&WX(OXNE|VAh`*fCCs4+BS^vpD<)2Jf6XOdb~RtrY~Yweum0zYy7~4qpv))K8-oP)fq7-EkWcS z`+Lm2gzz^2vbrw{-!r-*-TgF7N3j~C8^v%zF(A{6to?Yo{t9FR4!Cb6jxXIN+u-$~ z1MYBBA4U?&PR_l)5-n;)Sc6cEunA!^Kms2`@5&PBfa$@G3iOg(( z(SN1(#{u{^?%$I3ZyB<^>u?S4c=+~5?>*lz?pTs`EE(N$qMB69ylS6$-gW-&OOA`u zr5#rmytsVazA9^N(eanmHFC=x%)E^gD-<$KKO{uqIFFQS*T!V z9}3$1+Tuwe3yYX>xM}+?vP4@kDf}OxNI>TB;mZ&UOQFtaD3J)vf-f2SI^`ni06b%b zDW8S17QQa@!8wRMI2@>w^}XtUkIenX{bWE7~onWt= zu3>MT*0B#y&#_M*6AFG=C3_m@(JyE1wMTL3sp5zO#lD@ delta 7592 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 Hg-`xJ4_>}O literal 0 HcmV?d00001 diff --git a/src/QD_MARL/algorithms/__pycache__/mapElitesCMA_pyRibs_GE.cpython-311.pyc b/src/QD_MARL/algorithms/__pycache__/mapElitesCMA_pyRibs_GE.cpython-311.pyc index 4b63d910ab4a05bbd032e87d3f0e676bbfa12caf..0abef9211afda13b75b4bb48f07e749c88d56e40 100644 GIT binary patch delta 359 zcmXwzPbh^%o<8-dr|0l&=kc6}uqz0aoSI-^SWaaH6Fk}WcZ)Vy zr*!(b;IF$>b3rIJzpZT1FESA(bh8myV5dctbZ8-G#f%mX%IFoq#l8$FxG5>bc?{7* zwG4P@ymmtaiCr~ZLS{RSMTBV1I1fzIanmQzOb4dTN^H@f^$Ez4*tW(aN_!nK4fe?_ zdU+gD#6Aq1vLnYg3Ut)t1J0NxYp}`SlXNU0ZEy3>Hq)$ delta 276 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 Uta5HPH#;|5DbLQ{y>s#Z0L*7*%K!iX literal 0 HcmV?d00001 diff --git a/src/QD_MARL/algorithms/__pycache__/map_elites.cpython-311.pyc b/src/QD_MARL/algorithms/__pycache__/map_elites.cpython-311.pyc index 93f809307157a2a5cd7f6f1d8f5ef829187d93d4..53499b9ef6ab9d91234943ca932a5792e29ab6a0 100644 GIT binary patch delta 7161 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 delta 6817 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 diff --git a/src/QD_MARL/algorithms/__pycache__/map_elites_Pyribs.cpython-310.pyc b/src/QD_MARL/algorithms/__pycache__/map_elites_Pyribs.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7cd6e2eeceeec9d8375b3d5bb30d02a248b62fef GIT binary patch literal 12463 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 DMUY70 diff --git a/src/QD_MARL/algorithms/__pycache__/map_elites_ge.cpython-310.pyc b/src/QD_MARL/algorithms/__pycache__/map_elites_ge.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..767c10facb1f7804396c71f4a04dc4e5bfb63acb GIT binary patch literal 11424 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 self._genotype_length): + individual_genes = individual_genes[:self._genotype_length] + + individue_to_inject = Individual(individual_genes, None, None) # Create the individual with the genes injeceted + for set_ in range(self._sets): # Inject the individual in each set with the same rate + indexes = np.random.choice(self._pop_size, int(self._pop_size * injection_rate), replace=False) # Extract random indexes + for index in indexes: + self._log(set_, "INJ", f"Individual {self._individuals[set_][index]} has been replaced with injected {individue_to_inject}") + self._individuals[set_][index] = individue_to_inject.copy() # Replace selected individual with injected individual + + + def _log(self, set_, tag, string): # CONTROLLARE CHE LA SCRITTURA AVVENGA CORRETTAMENTE + if self._logdir is not None: + with open(os.path.join(self._logdir, f"set_{set_}.log"), "a") as f: f.write("[{}] {}\n".format(tag, string)) def _random_individual(self): @@ -422,67 +448,92 @@ def _random_individual(self): def ask(self): """ Returns the current population """ - self._old_individuals = self._individuals[:] - if self._updated: - self._individuals = [] - _sorted_pop = [self._old_individuals[j].copy() for j in self._selection([i._fitness for i in self._old_individuals])] - - for s in _sorted_pop: - s._parents = None - - for s in _sorted_pop: - s._parents = None - - self._log("POPULATION", "Sorted population:\n" + "\n".join("Individual {}:\n{}".format(srt_idx, _sorted_pop[srt_idx]) for srt_idx in range(len(_sorted_pop)))) - - cx_random_numbers = np.random.uniform(0, 1, len(_sorted_pop)//2) - m_random_numbers = np.random.uniform(0, 1, len(_sorted_pop)) - - # Crossover - for index, cxp in enumerate(cx_random_numbers): - ind1, ind2 = _sorted_pop[2 * index: 2 * index + 2] - if cxp < self._cx_prob: - self._log("CX", "cx happened between individual {} and {}".format(2 * index, 2 * index + 1)) - self._individuals.extend(self._crossover(ind1, ind2)) - self._individuals[-1]._parents = [ind1, ind2] - self._individuals[-2]._parents = [ind1, ind2] - self._log("CX", "Individual {} has parents [{}, {}] (Fitness [{}, {}])".format(2 * index, 2 * index, 2 * index + 1, ind1._fitness, ind2._fitness)) - self._log("CX", "Individual {} has parents [{}, {}] (Fitness [{}, {}])".format(2 * index + 1, 2 * index, 2 * index + 1, ind1._fitness, ind2._fitness)) - else: - self._log("CX", "cx did not happen between individual {} and {}".format(2 * index, 2 * index + 1)) - self._individuals.extend([Individual(ind1._genes), Individual(ind2._genes)]) - self._individuals[-1]._parents = [ind2] - self._individuals[-2]._parents = [ind1] - self._log("CX", "Individual {} has parents [{}] (Fitness {})".format(2 * index, 2 * index, ind1._fitness)) - self._log("CX", "Individual {} has parents [{}] (Fitness {})".format(2 * index + 1, 2 * index + 1, ind2._fitness)) - - if len(_sorted_pop) % 2 == 1: - self._individuals.append(_sorted_pop[-1]) - self._individuals[-1]._parents = [_sorted_pop[-1]] - - # Mutation - for i, mp in enumerate(m_random_numbers): - if mp < self._mut_prob: - self._log("MUT", "Mutation occurred for individual {}".format(i)) - self._individuals[i] = self._mutation(self._individuals[i]) - self._old_individuals = _sorted_pop + for set_ in range(self._sets): + self._old_individuals[set_] = self._individuals[set_][:] + if self._updated[set_]: + self._individuals[set_] = [] + _sorted_pop = [self._old_individuals[set_][j].copy() for j in self._selection([i._fitness for i in self._old_individuals[set_]])] + + for s in _sorted_pop: + s._parents = None + + self._log(set_, "POPULATION", "Sorted population:\n" + "\n".join("Individual {}:\n{}".format(srt_idx, _sorted_pop[srt_idx]) for srt_idx in range(len(_sorted_pop)))) + + cx_random_numbers = np.random.uniform(0, 1, len(_sorted_pop)//2) + m_random_numbers = np.random.uniform(0, 1, len(_sorted_pop)) + + # Crossover + for index, cxp in enumerate(cx_random_numbers): + ind1, ind2 = _sorted_pop[2 * index: 2 * index + 2] + if cxp < self._cx_prob: + self._log(set_, "CX", "cx happened between individual {} and {}".format(2 * index, 2 * index + 1)) + self._individuals[set_].extend(self._crossover(ind1, ind2)) + self._individuals[set_][-1]._parents = [ind1, ind2] + self._individuals[set_][-2]._parents = [ind1, ind2] + self._log(set_, "CX", "Individual {} has parents [{}, {}] (Fitness [{}, {}])".format(2 * index, 2 * index, 2 * index + 1, ind1._fitness, ind2._fitness)) + self._log(set_, "CX", "Individual {} has parents [{}, {}] (Fitness [{}, {}])".format(2 * index + 1, 2 * index, 2 * index + 1, ind1._fitness, ind2._fitness)) + else: + self._log(set_, "CX", "cx did not happen between individual {} and {}".format(2 * index, 2 * index + 1)) + self._individuals[set_].extend([Individual(ind1._genes), Individual(ind2._genes)]) + self._individuals[set_][-1]._parents = [ind2] + self._individuals[set_][-2]._parents = [ind1] + self._log(set_, "CX", "Individual {} has parents [{}] (Fitness {})".format(2 * index, 2 * index, ind1._fitness)) + self._log(set_, "CX", "Individual {} has parents [{}] (Fitness {})".format(2 * index + 1, 2 * index + 1, ind2._fitness)) + + if len(_sorted_pop) % 2 == 1: + self._individuals[set_].append(_sorted_pop[-1]) + self._individuals[set_][-1]._parents = [_sorted_pop[-1]] + + # Mutation + for i, mp in enumerate(m_random_numbers): + if mp < self._mut_prob: + self._log(set_, "MUT", "Mutation occurred for individual {}".format(i)) + self._individuals[set_][i] = self._mutation(self._individuals[set_][i]) + self._old_individuals[set_] = _sorted_pop + + # Migration and adoption + if not self._no_mig and all(self._updated): + random_set = np.random.randint(self._sets) + best = None + best_fitness = -float("inf") + for i in self._old_individuals[random_set]: + if best_fitness < i._fitness: + best_fitness = i._fitness + best = i.copy() + best._fitness = None + best._parents = None + self._log(random_set, "MIG", "Individual {} migrate".format(best)) + + for set_ in range(self._sets): + if set_ != random_set: + random_index = np.random.randint(self._pop_size) + old_ind = self._individuals[set_][random_index] + new_ind = best.copy() + new_ind._parents = [p.copy() for p in old_ind._parents] + self._individuals[set_][random_index] = new_ind + self._log(set_, "MIG", "Individual {} replaced with {}".format(old_ind, self._individuals[set_][random_index])) + # Migration and adoption + return self._individuals def tell(self, fitnesses): """ Assigns the fitness for each individual - :fitnesses: a list of numbers (the higher the better) associated (by index) to the individuals + :squad_fitnesses: [agents x pop_size] list of numbers (the higher the better) associated (by index) to the individuals + Must be orginezed in [agents x pop_size] before the call of this function """ - for index, (i, f) in enumerate(zip(self._individuals, fitnesses)): - if i._parents is not None: - self._log("FITNESS", "Individual {} has fitness {}. Its parents ({}) have fitnesses {}".format(index, f, i._parents, [k._fitness for k in i._parents])) - else: - self._log("FITNESS", "Individual {} has fitness {}".format(index, f)) - i._fitness = f - self._update_population() - def _update_population(self): + for set_ in range(self._sets): + for index, (i, f) in enumerate(zip(self._individuals[set_], fitnesses[set_])): + if i._parents is not None: + self._log(set_, "FITNESS", "Individual {} has fitness {}. Its parents ({}) have fitnesses {}".format(index, f, i._parents, [k._fitness for k in i._parents])) + else: + self._log(set_, "FITNESS", "Individual {} has fitness {}".format(index, f)) + i._fitness = f + self._update_population(set_) + + def _update_population(self, set_): """ Creates the next population """ - self._updated = True - self._individuals = self._replacement(self._old_individuals, self._individuals) + self._updated[set_] = True + self._individuals[set_] = self._replacement(self._old_individuals[set_], self._individuals[set_]) diff --git a/src/QD_MARL/algorithms/individuals.py b/src/QD_MARL/algorithms/individuals.py index cc0d93e2a..744f82313 100644 --- a/src/QD_MARL/algorithms/individuals.py +++ b/src/QD_MARL/algorithms/individuals.py @@ -7,6 +7,7 @@ import re import os import string +from utils.print_outputs import * from copy import deepcopy from .common import OptMetaClass from decisiontrees import Leaf, Condition @@ -36,7 +37,11 @@ def __init__(self, index): self._index = index def get_output(self, input_): - return input_[self._index] + if type(input_) == dict: + output = list(input_.values())[self._index] + else: + output = input_[self._index] + return output def __repr__(self): return f"input_[{self._index}]" @@ -198,6 +203,9 @@ def get_output(self, input_): def empty_buffers(self): self._then.empty_buffers() self._else.empty_buffers() + + def type(self): + pass def copy(self): """ @@ -292,7 +300,9 @@ def __init__(self, genes, padding=0, fitness=None, parents=None, const=None, con def copy(self): - return IndividualGP(self._genes.copy(), self._padding, self._fitness, self._parents,np.copy(self._const),self._const_len) + return IndividualGP(self._genes, self._padding, self._fitness, self._parents,np.copy(self._const),self._const_len) + def deep_copy(self): + return IndividualGP(deepcopy(self._genes), self._padding, self._fitness, self._parents,deepcopy(self._const),self._const_len) def get_genes_const_nested(self,expr,const_temp): fringe = [expr] @@ -352,3 +362,7 @@ def genes_to_const(self): i = self.genes_to_const_nested(cond.get_right(),i) fringe.append(cur.get_then()) fringe.append(cur.get_else()) + + def get_output(self, _input): + return self._genes.get_output(_input) + diff --git a/src/QD_MARL/algorithms/mapElitesCMA_pyRibs.py b/src/QD_MARL/algorithms/mapElitesCMA_pyRibs.py index b04bdcc3b..43e9ea536 100644 --- a/src/QD_MARL/algorithms/mapElitesCMA_pyRibs.py +++ b/src/QD_MARL/algorithms/mapElitesCMA_pyRibs.py @@ -6,11 +6,11 @@ import numpy as np from copy import deepcopy from .common import OptMetaClass -from decisiontrees import Leaf, Condition +from decisiontrees import * from operator import gt, lt, add, sub, mul from processing_element import ProcessingElementFactory, PEFMetaClass from ribs.archives._cvt_archive import CVTArchive -from ribs.archives._grid_archive import GridArchive +from ribs.archives import GridArchive from ribs.archives._sliding_boundaries_archive import SlidingBoundariesArchive from ribs.archives._archive_data_frame import ArchiveDataFrame from ribs.visualize import cvt_archive_heatmap @@ -20,6 +20,7 @@ from ribs.emitters.opt import CMAEvolutionStrategy import matplotlib.pyplot as plt from .individuals import * + import os import time @@ -38,15 +39,16 @@ def __init__(self,archive,sigma0,padding,selection_rule="filter",restart_rule="n if restart_rule not in ["basic", "no_improvement"]: raise ValueError(f"Invalid restart_rule {restart_rule}") self._restart_rule = restart_rule - self._bounds = bounds + self._bounds = bounds self._solution_dim = padding def initialize(self): - self.x0 = self._archive.get_random_elite()[4] + self.x0 = self._archive.sample_elites(4)#TODO: no pop initialized in archive + print("dims, ",self._solution_dim, "dounds ",self._bounds) self.opt = CMAEvolutionStrategy(self._sigma0, self._batch_size, self._solution_dim, - self._weight_rule, self._opt_seed, - self._archive.dtype) + self._opt_seed, self._archive.dtype) + self.x0 = IndividualGP(self.x0,self._solution_dim) self.opt.reset(self.x0._const) self._num_parents = (self.opt.batch_size // 2 if self._selection_rule == "mu" else None) @@ -64,9 +66,9 @@ def batch_size(self): def ask(self): if self._restarts: self._restarts = False - self.x0 = self._archive.get_random_elite()[4] + self.x0 = self._archive.sample_elites(4) self.opt.reset(self.x0._const) - evolved = self.opt.ask(self._lower_bounds, self._upper_bounds) + evolved = self.opt.ask(self._lower_bounds, self._upper_bounds, self._batch_size) tree_out = [] for i in evolved: temp = self.x0.copy() @@ -150,16 +152,16 @@ def __init__(self, **kwargs): self._sigma0 = kwargs["sigma0"] self._padding = pow(2,self._max_depth)*self._cond_depth self._restart = [False for _ in range(self._num_emitters)] + self._solution_dim = kwargs["solution_dim"] if self._archive_type == "CVT": self._archive = CVTArchive(self._bins,self._map_bound) elif self._archive_type == "Grid": - self._archive = GridArchive(self._map_size,self._map_bound) + self._archive = GridArchive(solution_dim=self._solution_dim, dims=self._map_size, ranges=self._map_bound) elif self._archive_type == "SlidingBoundaries": self._archive = SlidingBoundariesArchive(self._bins_sliding,self._map_bound) else: raise Exception("archive not valid") - - self._archive.initialize(1) # one dimension (counter) + # self._archive.initialize(self._solution_dim) # pyribs v.0.6.3 don't have inizialization method anymore self._counter = 1 # number inserted in sol field of the archive self._gen_number = 1 self._max_fitness = -250 @@ -246,7 +248,7 @@ def _get_depth(self, node): if d > max_: max_ = d - + if not isinstance(n, Leaf): fringe.append((d + 1, n._then)) fringe.append((d + 1, n._else)) @@ -254,7 +256,6 @@ def _get_depth(self, node): def _reduce_expr_len(self, expr): fringe = [(0, expr)] - max_ = 0 while len(fringe) > 0: d, cur = fringe.pop(0) @@ -353,10 +354,8 @@ def _init_pop(self): for i in range(grow): root = self._get_random_leaf_or_condition() fringe = [root] - while len(fringe) > 0: node = fringe.pop(0) - if isinstance(node, Leaf): continue @@ -366,19 +365,19 @@ def _init_pop(self): else: left = self._random_leaf() right = self._random_leaf() - node.set_then(left) node.set_else(right) fringe.append(left) fringe.append(right) - - pop.append(IndividualGP(root,self._padding)) + # appemding directly the individual without calling the constructor + pop.append(root) + self._pop = pop return pop def _mutation(self, p): p1 = p.copy()._genes - #print(type(p1)) + print(type(p1)) cp1 = None p1nodes = [(None, None, p1)] @@ -496,7 +495,7 @@ def ask(self): #print("Crossover and mutation on emitter ",i) temp = list() pop_temp = [ - self._archive.get_random_elite()[4] #metadata + self._archive.sample_elites(4) #metadata for _ in range(self._batch_pop) ] for i in range(0, len(pop_temp), 2): @@ -525,11 +524,13 @@ def ask(self): end = time.time() return [p._genes for p in self._pop] + def tell(self,fitnesses, data=None): archive_flag = self._archive.empty sols, objs, behavs, meta = [], [], [], [] - for p in zip(self._pop,fitnesses): - desc = self._get_descriptor(p[0]._genes) + pop_individuals = [IndividualGP(p,self._padding) for p in self._pop] + for p in zip(self._pop,fitnesses, pop_individuals): + desc = self._get_descriptor(p[2]._genes) p[0]._fitness = p[1] thr = [abs((max(self._map_bound[i]) - min(self._map_bound[i])) / self._map_size[i]) for i in range(len(self._map_size))] @@ -540,9 +541,9 @@ def tell(self,fitnesses, data=None): elif desc[i] >= self._map_size[i]: desc[i] = self._map_size[i] - 1 desc = tuple(desc) - + if archive_flag: - self._archive.add(self._counter,p[1],desc,p[0]) + self._archive.add_single(desc, p[1], desc, self._counter) else: sols.append(self._counter,) objs.append(p[1]) diff --git a/src/QD_MARL/algorithms/mapElitesCMA_pyRibs_GE.py b/src/QD_MARL/algorithms/mapElitesCMA_pyRibs_GE.py index 1d8eca454..3b5c69d10 100644 --- a/src/QD_MARL/algorithms/mapElitesCMA_pyRibs_GE.py +++ b/src/QD_MARL/algorithms/mapElitesCMA_pyRibs_GE.py @@ -20,6 +20,8 @@ from ribs.emitters.opt import CMAEvolutionStrategy import matplotlib.pyplot as plt from .individuals import * +from .grammatical_evolution import * +from .genetic_programming import * import os import time diff --git a/src/QD_MARL/algorithms/map_elites.py b/src/QD_MARL/algorithms/map_elites.py index e2aca8abf..780c7cb8c 100644 --- a/src/QD_MARL/algorithms/map_elites.py +++ b/src/QD_MARL/algorithms/map_elites.py @@ -16,6 +16,7 @@ from decisiontrees import Leaf, Condition from operator import gt, lt, add, sub, mul from processing_element import ProcessingElementFactory, PEFMetaClass +from utils.print_outputs import * def safediv(a, b): @@ -582,10 +583,10 @@ def _add_to_map(self, ind, fitness, data=None): desc = self._get_descriptor(ind) thr = [abs(max(self._map_bound[i]) - min(self._map_bound[i])) / self._map_size[i] for i in range(len(self._map_size))] - print(desc) - print(thr) + print_info(desc) + print_info(thr) desc = [int(desc[i] - min(self._map_bound[i]) / thr[i]) for i in range(len(self._map_size))] - print(desc) + print_info(desc) print("-----------------") for i in range(len(self._map_size)): if desc[i] < 0: diff --git a/src/QD_MARL/algorithms/map_elites_Pyribs.py b/src/QD_MARL/algorithms/map_elites_Pyribs.py index b01680ece..f4e389fb4 100644 --- a/src/QD_MARL/algorithms/map_elites_Pyribs.py +++ b/src/QD_MARL/algorithms/map_elites_Pyribs.py @@ -13,6 +13,7 @@ from ribs.archives._cvt_archive import CVTArchive from ribs.archives._grid_archive import GridArchive from ribs.archives._sliding_boundaries_archive import SlidingBoundariesArchive +from ribs.archives import EliteBatch, Elite from ribs.archives._archive_data_frame import ArchiveDataFrame from ribs.visualize import cvt_archive_heatmap from ribs.visualize import grid_archive_heatmap @@ -62,17 +63,15 @@ def __init__(self, **kwargs): self._archive_type = kwargs["archive"] self._bins = kwargs["bins"] self._bins_sliding = kwargs["sliding_bins"] - + self._solution_dim = kwargs["solution_dim"] if self._archive_type == "CVT": self._archive = CVTArchive(self._bins,self._map_bound) elif self._archive_type == "Grid": - self._archive = GridArchive(self._map_size,self._map_bound) + self._archive = GridArchive(solution_dim=self._solution_dim, dims=self._map_size, ranges=self._map_bound) elif self._archive_type == "SlidingBoundaries": self._archive = SlidingBoundariesArchive(self._bins_sliding,self._map_bound) else: raise Exception("archive not valid") - - self._archive.initialize(1) # one dimension (counter) self._counter = 1 # number inserted in sol field of the archive self._gen_number = 1 self._max_fitness = -inf @@ -277,22 +276,22 @@ def _init_pop(self): fringe.append(left) fringe.append(right) - + pop.append(IndividualGP(root)) + self._pop = pop return pop def _mutation(self, p): - p1 = p.copy()._genes + p1 = p.deep_copy()._genes #print(type(p1)) cp1 = None p1nodes = [(None, None, p1)] - fringe = [p1] + fringe = [IndividualGP(p1)] while len(fringe) > 0: node = fringe.pop(0) - - if not isinstance(node, Leaf): + if not isinstance(node, Leaf) and not isinstance(node, IndividualGP): fringe.append(node.get_left()) fringe.append(node.get_right()) @@ -301,8 +300,8 @@ def _mutation(self, p): cp1 = np.random.randint(0, len(p1nodes)) - parent = p1nodes[cp1][0] - old_node = p1nodes[cp1][2] + parent = IndividualGP(p1nodes[cp1][0]) + old_node = IndividualGP(p1nodes[cp1][2]) if not isinstance(old_node, GPNodeCondition) or \ not isinstance(old_node, GPExpr): new_node = self._get_random_leaf_or_condition() @@ -310,8 +309,10 @@ def _mutation(self, p): new_node = self._random_expr() if not isinstance(new_node, Leaf) and \ - not isinstance(new_node, GPExpr): - if not isinstance(old_node, Leaf): + not isinstance(new_node, GPExpr) and \ + not isinstance(new_node, IndividualGP): + if not isinstance(old_node, Leaf) and \ + not isinstance(old_node, IndividualGP): new_node.set_then(old_node.get_left()) new_node.set_else(old_node.get_right()) else: @@ -341,7 +342,7 @@ def _crossover(self, par1, par2): while len(fringe) > 0: node = fringe.pop(0) - if not isinstance(node, Leaf): + if not isinstance(node, Leaf) and not isinstance(node, IndividualGP) and not isinstance(node, EliteBatch): fringe.append(node.get_left()) fringe.append(node.get_right()) @@ -356,10 +357,11 @@ def _crossover(self, par1, par2): fringe = [p2] while len(fringe) > 0: node = fringe.pop(0) - if not isinstance(node, Leaf) and \ not isinstance(node, GPVar) and \ - not isinstance(node, GPConst): + not isinstance(node, GPConst) and \ + not isinstance(node, IndividualGP) and \ + not isinstance(node, EliteBatch): fringe.append(node.get_left()) fringe.append(node.get_right()) @@ -387,25 +389,42 @@ def _crossover(self, par1, par2): p2nodes[cp2][0].set_else(st1) else: p2 = st1 + return IndividualGP(p1), IndividualGP(p2) - def ask(self): + + def ask(self, random = False, best = False): self._pop = [] if self._archive.empty: self._pop = self._init_pop() else: temp = list() - self._pop = [ - self._archive.get_random_elite()[4] #metadata - for _ in range(self._batch_pop) - ] + if random: + self._pop = [ + self._archive.sample_elites(1) #metadata + for _ in range(self._batch_pop) + ] + elif best: + self._pop = [ + IndividualGP(self._archive.best_elite) #metadata + for _ in range(self._batch_pop) + ] + else: + self._pop = [ + self._archive.sample_elites(1) #metadata + for _ in range(self._batch_pop) + ] for i in range(0, len(self._pop), 2): p1 = self._pop[i] if i + 1 < len(self._pop): p2 = self._pop[i + 1] else: p2 = None - o1, o2 = None, None + + p1 = IndividualGP(p1) + if p2 is not None: + p2 = IndividualGP(p2) + # Crossover if p2 is not None: if np.random.uniform() < self._cx_prob: @@ -419,7 +438,9 @@ def ask(self): temp.append(p1) self._pop = [self._mutation(p) for p in temp] return [p._genes for p in self._pop] + def tell(self,fitnesses,data=None): + for p in zip(self._pop,fitnesses): desc = self._get_descriptor(p[0]._genes) p[0]._fitness = p[1] @@ -432,14 +453,14 @@ def tell(self,fitnesses,data=None): elif desc[i] >= self._map_size[i]: desc[i] = self._map_size[i] - 1 desc = tuple(desc) - status, value = self._archive.add(self._counter,p[1],desc,p[0]) + status, value = self._archive.add_single(desc, p[1], desc, self._counter) #print(status, value) self._counter += 1 #Visualize archives if max(fitnesses) > self._max_fitness: self._max_fitness = max(fitnesses) - print("New best at generation: ",self._gen_number-1, " fitness: ",max(fitnesses)) + print_info("New best at generation: ",self._gen_number-1, " fitness: ",max(fitnesses)) if self._gen_number%50 == 0 : plt.figure(figsize=(8, 6)) if self._archive_type == "CVT": @@ -454,4 +475,5 @@ def tell(self,fitnesses,data=None): plt.xlabel("Depth") plt.show() self._gen_number += 1 + \ No newline at end of file diff --git a/src/QD_MARL/configs/CartPole-ME_pyRibs.yaml b/src/QD_MARL/configs/CartPole-ME_pyRibs.yaml new file mode 100644 index 000000000..5e335c92a --- /dev/null +++ b/src/QD_MARL/configs/CartPole-ME_pyRibs.yaml @@ -0,0 +1,80 @@ +Factories: + - class_name: DecisionTreeFactory + kwargs: + Optimizer: + class_name: MapElitesCMA_pyRibs + kwargs: + map_size: [ 7,7 ] + cx_prob: 0.3 + init_pop_size: 50 #50 + map_bounds: [ [ 0,7 ],[ 0,7 ] ] + batch_pop: 6 #20 + maximize: true + restart_rule: 3 + archive: Grid + bins: 50 + sliding_bins: [ 6,4 ] + emitters: 5 + sigma0: 1 + bounds: + float: + type: float + min: -1.5 + max: 1 + input_index: + type: int + min: 0 + max: 4 + action: + type: int + min: 0 + max: 1 + max_depth: 7 + cond_depth: 7 + DecisionTree: + gamma: 0 + ConditionFactory: + # Not used with GP + type: orthogonal + n_inputs: 2 + #LeafFactory: + # class_name: ConstantLeafFactory + # kwargs: + # n_actions: 2 + LeafFactory: + class_name: QLearningLeafFactory + kwargs: + leaf_params: { n_actions: 2, learning_rate: 0.1 } + decorators: [ + [ "RandomInit", { "low": -1, "high": 1 } ], + [ "EpsilonGreedy",{ "epsilon": 0.05, "decay": 0.999 } ], + [ "NoBuffers", { } ], + [ "QLambda", { "decay": 0.81 } ] + ] + - class_name: UtilFactory + kwargs: + module: OneHotEncoder + n_actions: 2 +Mapper: + class: MultiProcessMapper + kwargs: + n_jobs: 10 #10 + +PipelineFactory: + class: CartesianProductPipelineFactory + config: + aggregation_fcn: max + +EvolutionaryProcess: + generations: 5 #200 + +Fitness: + name: GymTask + episodes: 120 + seeding: False + seed: 0 + case: train + time_window: 30 + n_actions: 2 + kwargs: + env_name: CartPole-v1 diff --git a/src/QD_MARL/configs/battlefield.json b/src/QD_MARL/configs/battlefield.json index aea504a6d..f7f9814ff 100644 --- a/src/QD_MARL/configs/battlefield.json +++ b/src/QD_MARL/configs/battlefield.json @@ -45,6 +45,16 @@ ] ] }, + "_environment": { + "map_size": 46, + "minimap_mode": true, + "step_reward": -0.0777, + "dead_penalty": -10, + "attack_penalty": -1000.0, + "attack_opponent_reward": 10.0, + "max_cycles": 50000, + "extra_features": false + }, "environment": { "map_size": 80, "minimap_mode": true, @@ -52,10 +62,16 @@ "dead_penalty": -0.1, "attack_penalty": -0.1, "attack_opponent_reward": 0.9, - "max_cycles": 500, + "max_cycles": 5000, "extra_features": false }, "training": { + "gamma": 0.9, + "episodes": 800, + "jobs": 12, + "generations": 40 + }, + "coach_training": { "gamma": 0.9, "episodes": 400, "jobs": 12, @@ -107,5 +123,72 @@ "cx_prob": 0.4, "genotype_length": 500, "max_int": 10000 + }, + + "me_config":{ + "me":{ + "name": "map_elites.MapElites", + "__name_2__": "map_elites_Pyribs.MapElites_Pyribs", + "__name_3__": "mapElitesCMA_pyRibs.MapElitesCMA_pyRibs", + "kwargs": { + "seed": 5, + "map_size": [5, 5], + "cx_prob": 0.3, + "init_pop_size": 36, + "map_bounds": [[0, 5], [-5, 5]], + "batch_pop": 12, + "maximize": "True", + "restart_rule": 3, + "archive": "Grid", + "solution_dim": 2, + "bins": 50, + "sliding_bins": [6, 4], + "emitters": 5, + "sigma0": 1, + "bounds": { + "float": { + "type": "float", + "min": -5, + "max": 5 + }, + "input_index": { + "type": "int", + "min": 0, + "max": 4 + }, + "action": { + "type": "int", + "min": 0, + "max": 1 + } + }, + "max_depth": 2, + "cond_depth": 2, + "generations": 5, + "logdir": "logs/" + } + }, + "DecisionTree": { + "gamma": 0 + }, + "ConditionFactory": { + "type": "orthogonal", + "n_inputs": 5 + }, + "QLearningLeafFactory":{ + "kwargs": { + "leaf_params": { + "n_actions": 21, + "learning_rate": 0.5 + }, + "decorators": [ + ["RandomInit", {"low": -1, "high": 1}], + ["EpsilonGreedy", {"epsilon": 0.05, "decay": 0.999}], + ["NoBuffers", {}], + ["QLambda", {"decay": 0.81}] + ] + } + } } } + diff --git a/src/QD_MARL/configs/map_elite_config.json b/src/QD_MARL/configs/map_elite_config.json new file mode 100644 index 000000000..ebe1875d8 --- /dev/null +++ b/src/QD_MARL/configs/map_elite_config.json @@ -0,0 +1,66 @@ +{ + "me":{ + + "name": "map_elites.MapElites", + "__name_2__": "map_elites_Pyribs.MapElites_Pyribs", + "__name_3__": "mapElitesCMA_pyRibs.MapElitesCMA_pyRibs", + "kwargs": { + "seed": 5, + "map_size": [5, 5], + "cx_prob": 0.3, + "init_pop_size": 36, + "map_bounds": [[0, 5], [-5, 5]], + "batch_pop": 12, + "maximize": "True", + "restart_rule": 3, + "archive": "Grid", + "solution_dim": 2, + "bins": 50, + "sliding_bins": [6, 4], + "emitters": 5, + "sigma0": 1, + "bounds": { + "float": { + "type": "float", + "min": -5, + "max": 5 + }, + "input_index": { + "type": "int", + "min": 0, + "max": 4 + }, + "action": { + "type": "int", + "min": 0, + "max": 1 + } + }, + "max_depth": 2, + "cond_depth": 2, + "generations": 5, + "logdir": "logs/" + } + }, + "DecisionTree": { + "gamma": 0 + }, + "ConditionFactory": { + "type": "orthogonal", + "n_inputs": 5 + }, + "QLearningLeafFactory":{ + "kwargs": { + "leaf_params": { + "n_actions": 21, + "learning_rate": 0.5 + }, + "decorators": [ + ["RandomInit", {"low": -1, "high": 1}], + ["EpsilonGreedy", {"epsilon": 0.05, "decay": 0.999}], + ["NoBuffers", {}], + ["QLambda", {"decay": 0.81}] + ] + } + } +} diff --git a/src/QD_MARL/configs/tmp_map_elite_config.json b/src/QD_MARL/configs/tmp_map_elite_config.json new file mode 100644 index 000000000..44c5cb6b9 --- /dev/null +++ b/src/QD_MARL/configs/tmp_map_elite_config.json @@ -0,0 +1,102 @@ +{ + "Factories": [{ + "class_name": "DecisionTreeFactory", + "kwargs": { + "Optimizer": { + "me": "MapElitesCMA_pyRibs", + "kwargs": { + "seed": 5, + "map_size": [7, 7], + "cx_prob": 0.3, + "init_pop_size": 50, + "map_bounds": [[0, 7], [0, 7]], + "batch_pop": 6, + "maximize": "True", + "restart_rule": 3, + "archive": "Grid", + "bins": 50, + "sliding_bins": [6, 4], + "emitters": 5, + "sigma0": 1, + "bounds": { + "float": { + "type": "float", + "min": -1.5, + "max": 1 + }, + "input_index": { + "type": "int", + "min": 0, + "max": 4 + }, + "action": { + "type": "int", + "min": 0, + "max": 1 + } + }, + "max_depth": 7 + , "cond_depth": 7 + } + }, + "DecisionTree": { + "gamma": 0 + }, + "ConditionFactory": { + "type": "orthogonal" + , "n_inputs": 2 + }, + "LeafFactory": { + "class_name": "QLearningLeafFactory", + "kwargs": { + "leaf_params": { + "n_actions": 2, + "learning_rate": 0.1 + }, + "decorators": [ + ["RandomInit", {"low": -1, "high": 1}], + ["EpsilonGreedy", {"epsilon": 0.05, "decay": 0.999}], + ["NoBuffers", {}], + ["QLambda", {"decay": 0.81}] + ] + } + } + } + }, + { + "class_name": "UtilFactory", + "kwargs": { + "module": "OneHotEncoder", + "n_actions": 2 + } + } + ], + "Mapper": { + "class": "MultiProcessMapper", + "kwargs": { + "n_jobs": 10 + + } + }, + "PipelineFactory": { + "class": "CartesianProductPipelineFactory", + "config": { + "aggregation_fcn": "max" + } + }, + "EvolutionaryProcess": { + "generations": 5 + }, + "Fitness": { + "name": "PettingzooTask", + "episodes": 120, + "seeding": "False", + "seed": 0, + "case": "train", + "time_window": 30, + "n_actions": 2, + "kwargs": { + "env_name": "battlefield-v5" + } + } +} \ No newline at end of file diff --git a/src/QD_MARL/decisiontreelibrary/__pycache__/__init__.cpython-311.pyc b/src/QD_MARL/decisiontreelibrary/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index a5c5b7c763961a09df9068caf86e993909fbb911..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 535 zcmZ`#O-lnY5KVUbt@Q)@BV0j)CodvMi+X59YrX7cNq3{BY!aEImHrZcg7{B7_E`2L zc=9HA>&e;DLOnDLZ|1#B-VAxG*J}jNXMIC_VE)R<@_38l_Y8|yl8}U^MA48cFZ7fj z`ZU+Pw9w15pB7aqEX}n-T2_^?qN-sPdGU?}rEiKRfuA*Q+k%6Z`VsTP1&^J9akkeq z+B&Y?s#wTLKr?!Pu!9|mbkgX>o50I9TOyVgr5*^8ZFSqL{5q&*rBv{du;AltDCAH? z$cow|!e+I2X=$N6*B$y;sN{`Rgm?0bNM?>b870_&zwqM|)-v%>3GFPq*V1X$;noRY zH(&-Lc1<$`E7+xUSHnKuL3()Gif%xTxD(7m%#e1@yKR;%md}T#vJV`_EQ%z0M$ysp vcvd-8CK;yU40dse{QGQ#@Z(cTKS^`4KBlBGS;my?PyWZ0u*v#>ZJhB3lW3mY diff --git a/src/QD_MARL/decisiontrees/__pycache__/__init__.cpython-310.pyc b/src/QD_MARL/decisiontrees/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a7592f33e8e177045faad05660a329ae7371cf5 GIT binary patch literal 681 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 O>G68vGvmV(wf_YbvZ~Vn literal 0 HcmV?d00001 diff --git a/src/QD_MARL/decisiontrees/__pycache__/conditions.cpython-311.pyc b/src/QD_MARL/decisiontrees/__pycache__/conditions.cpython-311.pyc index e794e0488dfe3d07a89489da9fb2d9a6ed41e9be..ebe16ab8a2d535b8b7e332a5e6ef6e8e832dcf18 100644 GIT binary patch delta 6826 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 diff --git a/src/QD_MARL/decisiontrees/__pycache__/factories.cpython-310.pyc b/src/QD_MARL/decisiontrees/__pycache__/factories.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c281f7bda351b116d3bb882df7555f693c3a11eb GIT binary patch literal 3666 zcmb7HTW=f372eqkxuhu7mfgg1Q)f^hNf|_9;sOmoxJ?4dE{sZwZMiQ*+r?^UD6X{^ zdS)qC0@)Y&wMBn|1@tBVpuc5-KIJKYp-=tJEN_zK6dhuQXJ*cvIrlT0udX&Nv}b?* zN`Aj;S>MxSxz#ZF8eKoZAgrDxn6ML;+C7^Q>?BU=_T1F#d1me=wY1)=r+&{*8@)!_ z>@}J7J4<+?_SzCP=}J%3|K|2qgnwYQ8)yG+ax|@?t$r94xr(JWD}P;XO|$fT5Nn?1 zVw6Z8 z4T>)RWz^>PKfnJ4KRo5TGS5OKa=tr?;+$`fbdd|WWzKh#IFgx`UB37D`Da{9$#)-b zKioffXzogLC2=8`OK{5%P@2_dW$W8vv4_R&B-FZXmv^41Jd#?++0ny_^+!hf)3WvC z;iCnnTi)2tGZB+qGmP6$cV(zD9L0Zswt5SAx(Kskd2#L8?#@*9=MXz0ZkC&a$=B$* zi$PjF1_RrI^&DXf=e5;yg)2P#!+4^O|C;bc1OIigDpo}6HS77J2{W#hx0dgj@^to_ zh1nt!d|@d44h!+Ab;u_o2Bv0>+|Wb+J-YrEhQfNo#?}R6R$-5sU>laOH>@e-Y*^>^ zgx1{+YvPO@;bFgaY^#q7n$&dTF}s1%r0!efG%w8_~Iwf)y?n|sNHCWqKcBPNy&4AH`Bo)saXN&o`y?xcJaIo&CCQF-Q3VlVlJJ+?9-^BcLN^(y z=`!6J-X_mgG02ag`Q$AEgtQ6E^!rjF3B}=IBHw{@|0GmZew$IA{cm!!_bTuAwY+S| z1xkLH#4kq+;@zcS|KG;AEZWRHNK?x$GS%AKdg2G8;Sg%j{F+q5!%Xn&64NC`Z1S0- z(XGbWWFghU;bz`H6IoHrmo6E%^s^w&hND84D@$URO_N#z5<%4oyH?#%HnIS*gkaHS zV;)0gBMPRMl#OH#wwB$x^o(;V%0K(xp-3splcgWv{FJTDf?Qa`7~W{`%*G9U@R!d#O*eCD&e?CjpP?9b6JRTmP#MK=Q62LKjP zUoNZ(7#9qp+$?Niqjo@?_fa<>k_#LUbDMCG*PXNTxvy6*6^V=?xRS+4;h_@pSA!fc zmtj5}CG&@h&XbYx(W_Z&5r5q&1q$$Y99|&rpSf~y%G)({2V#^?sE^f$2GWHpoirS( zUtl^@ssn;oby~-n0P8!z)ATrOdOo`WuW;Gf&#qJI6#tS&8#qVCFdF>_fWkV5HqB_8 z(J>=wk-f1mtR3r5@Bh)Bc#|6Hr!)4bvX5;*HLC4q=9n3(Jk4I-oLT^GHNl%@QXku( zu|Dy~F7VhGb$jfX(eFFvd|lXIT9f+AUyWU3uah6O7iFloEOw=|%n2-P6efu&K6o)I zDV7X2$2EE72TCrKA(9w|F}7Mk1_^MSbYIrO;ZSBmeFU`1VvQ;c>TxFIU!*QQ6V_$D zkKBNlr1}_`B|9$JNy*}pT@4jQak-DKKfqA)YK(sCtjX4<#`W19%wXgXuQT!zbNi74 z60=6|Ja#CrRj~sL*cav#0lK!&4(H-8#Rh@`PvE2JD?Z9oWOwLnDv^l@8AbyRk>itC zjKXC0j#EG sB6F3iV!1q2HO-VWDxy{TB|ffWu=F2P^8S{**yIS;ZZ&Vff$n(!1!!r?_W%F@ literal 0 HcmV?d00001 diff --git a/src/QD_MARL/decisiontrees/__pycache__/leaves.cpython-310.pyc b/src/QD_MARL/decisiontrees/__pycache__/leaves.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac74facd0e5edb0dc3fee6fd3919d6c8189902a8 GIT binary patch literal 22280 zcmdU1dyHJyS)Y67&SPh1*X#8=iQ{W0X*`a-PSQ4Mwn=Mu?WQTtCQe*vm(c0#+`GGD z&(5sRow2jSx}~*C#2Z>Dh_+}Q`Ui%8pol)86pC6T5JkKy#04QLBt#_;ND)YlTEg%5 zo%@=ZJL?TXLd@u%Ip>~x&*MAa`+R5G6BDHjKF_`X?e4dp&t(3Qh2+OV;S??}V`MT; zW+CGkj@dL84EZ$|vQE~qn$~J=A!o2I*UYaL77CI#o5h9VlNl%P6mDdkf}6XZT^K{T z=!~H}=8mCULb>FOqde}GP##CQ>`b6M;f|wRMtRcNf$|P1PoO;I>_mB|lqXT1c6On> zOUgS?-tFu`dCzroVG57fot=OFti9A$7`Jk5$L-V_O>d_3Eh5AGx3Kt`is4)HXyumS7aiAK zW7l<7CqG#fPT}&FkhmG(NoK)x4B(0BWN%~^EXQ(k_|0joSjcN^SSV;LSSY$<7fTug zt{V&EdhHj=dd(Lm^ja@W>NQ^2;q1k#?DMU2Zf&XeR1AE;pU_^cd9Gb=);v$97B9eY z%y6mEB2-u0h64CIt^%k75Y;)~@WVW(HHXXFi=>m;G&V9f4I{IW*)(s0G8_ZLFnjmO z0Ei$@wHlp9t=Z_IhZ+}sNyDCX^OdY(dw#K6ZQ!|T)t_)z*E;Lf#qQFQt2_YM^4#Xq zt&AGSbFaE5SK6!Y$&0nCwTB)(d8S?O3O=2D4_?fa9?Tc=`GuQ4a&&5sE zJaPU^^;y-pQtP-Ufv_i0eERfr=T16qz2P<5Ey1;u8X;$ZMFsSA0GCIaFXhT+Z-QW* z2}fBsaX7agxpBp9VHt2w zV>Tf*E9L5}eY$1Wl&Y=U?Irz;z2w$9T@}!ZJgZ#XXnAaS~0UXR(MK>6dpwi+EiVJv&tdL&fq7SV)ZA^YgW} zHMiyXW^2vQ^<35VZkdW4*3av?$cpNu%-f}9w^P-yID)dkzFaJsy&VGkxOOD;Uzx&t zN$3+9Z<)P%G@s|(dRsX_L_kp{Ky+^lfq+qgyR0WbuAi8-FGSO|JGDz#8*mkZfVdpB zJYO;W9aa5GHE2ssVm#5Tb=}~sCzK2+cyk6Xyh2D$<$KdI?Y`>1nG@$Cyt@m94(D!M zH?Emu%mMQ>4r(a;$LH}#G~6eFWP0J1dll@FEH#`{z)%BJVy%Wjr`-W|1kY>-39LkzYp4a!dCVJ_Hu1?wPydp=k4>WwHJ?i z_g5;>Xy<`g*{0i46PN>-g?`Ty_=1IKtkO4qs|5@kgmTFPZs`XeK@(n{uqc=Yz^t(= zt{-A}T#h-uM25Ev$X0b~NFE$raO{a_uxH$+8{uGtHr2{bDrW=QO*pYxc6+ za#f>lU#T^_Ao0{e6I1G%eF?^Bs78&6IFDDj4^7&@FcrxC=mkL=VSb~qgy?WSi47Ve zDFE`v$R%X-^O&i}rLzjX$%uhS)ev|hDhl}r>4+4xl*QT_y&KVBLr}fZZh~ESvrz>X z@`U{^%|jyI9J9xvdd&^Ccp$*>s0VcqCX69yrUS_CkCP^YEal1z;TqO}#*mGa zqaErP(F3;(#aB~dZSkEI)C!RER&PvzEDGSDrqnkOppy(_zG7%fPeWf3fm(jHqCSj- z0MvE!l__dWt=g&!jj2?vY6Tnla=E@)AE=Tdez& zwMDO^upFyyXQl0^`&nIK@*tBpGkJ_j%#9>RsV$J&jf{5(l48a(idJ!~SS}WeQ&zEL z;CHN8Ea&C9Iv(l!kns5NUPjWVBHu$}>V^R&8S1ep$pxo~-~3CNCBqqWN;irN1<8*) zW#o&_gfofyF=vM}h2N6Y?R2J5H}33mcH>^z+2icR?}W3@*^l2z=YVques?%`I(OlB z$~oxVjo+QlA;-q=v~$=wg5O;)WvtA?Zm4kg`V;4|-Xh7k?!9F}8ZmOTZk@1kw6Y7z zfPK^COM#-*ffzWUk>6g0k_4$Jtw5_-b!&JE7#XudEm7y|A*cv0=6TKG@{S`(sNp1E zTw3-cZ*_8;dFlbk7c}dy^?;cXwkP%TPy@jFV z&@>lOgTdFXz+kE^!r~ibaztGC?C#&Ju1SX?ovusQmy}f;C9NRU6EG&nY1j}q>G?ra7ZyKM35`hwg#6?R= z=BF)?M|Qqv+DCguI~Z#vCo_O4pq3%T)fqJ6XT9#KI*VK?e+A0|FRE651P_S#wvFDN z!NJNCeY!9OSws<32W70e^q2!>*9Umk)Xl>)-idj}HA&ZHfyR~vjJhg=?4E(a^wF&@P$ zkL#xkysO;-7jx8$U;WEy=xH{up0Kv0q6hz<*t$hO=XKXyr3I^y>bn3njpgMk?d*Ve zYIGOTyAw~qsWDcqioAeqjzuX2^e1q6MBs99(%5ZG8hN9)zaRfc7{Vk~E}bmj0y4?v z!?KvyAQmpd8PUzCy$)fVptR88Nn`OU*g4MO{PMR$Zkb1CpEKq@WS zM>ID)V#Br+w^krB?Z%8dLwQ|mHrrQ4%tq6e7b#-V6y6Jcbg}Qng9{Kc-gcXwtGy0% z94~d7v?8Ns+!xoHZKw?7t5k2HxIk4&a(C^9+MY{IkGszobOgQCUFQhhLCGOSBhciL z)8rxAw~&X(J~9K?{6ZPiT*duN`pLjAOAi4P`6?dJoM9{06oi7LrdDnVbojjK3K*(@RQ0i@&s#B{Uhti0D8NJ zq25&6=mhL@r2ZxrtXBX2De5Rifaynu*0v8xwL`*z*(`HYOK32JWKNc55SrrV(@lR1 z4{P*F6&XSQgkbm~UQvh&ZhbE@AQ9kF*HDukdV@08gY=rwf@|cO(E-QY%-$phQn;F% z;FFuVo0-l0O}!;zZWcO4+#9=zcZGin_f&60 z&g2OuZ(~A(NWGg07g{TChnc&di2(llnTr?0Ws%TVvKX=~Yb+`x{zRb4)!k+@RObXd z!py09=*2}wI2L^*rCf1mFcEC*YO6MCZ<{t?+AhJQr1hT$KPO=0*)WHT845!*RT z|A=$Mxd*?yoqL`8@Vm!(gL6NA_c}+N3V!!F$D9Z7yWctPoWSn^XT~{+-#eTKormyy zr}MD$M*QC8yvcbpeh)g2IFI7@Zs#pv-jDhH#_AdFm(;LT(>o@7`}E+}rsxmQ8uU|| zdk^7W9v~D#w~^6|rekb^pf?On(wi2DI2-L}UWjDGpb04(t5J6e|uv= zGWPBr(P95q86J;5%!l(7#z=O)X@vXL>Imw?K~M~*&YBem(UM4&No(XVw%&>fhaqZM zI1EU*qlqC@Mh$@#_h1MfTPYQKM@B$`l75uWKN4|MXhRPG_z!#UCN?{uKRkfjAuJw5 zUL9v598D78XskFM4rIz2IS}&xa3CCW_+dVaOGuC{h69nEazj0hTj3x?bEe%qI0#le z91fyf>>b_$8hT%Ohz*2e(71SKqSwPZFA6$M*TK$N|2R5V$B-G>HRrFlH-_}85E0Zm zTYnFdaKa*p(&&yAXTu4X@;!Tt38&kn{he~NDC|@OUs|WEc$yRa05ZM1w&*h5;FKk% z9Um_7hIx}}kfGj(n_>4Na_O}ggflCi59d5N*1KoK0JdfOXjuP1qF>>*Y13xK`=e=( zVmaHhR0O*)(;C`DoPcI_qUSB*&ftt$;jm|6cB24J%({FVq|(e>cPC0r+C*8=WOq$u zdIv{o*ghiW2u?Cuh&mRNgzMQe$#qm#f#gTwBF$x$*6`yTtf3Gz$>kgH{GR9nhvdg z5Ci!^3`B$&HUdVe3q&tAGIAiQ$GIjZ?~e(yV*p-i&)4w$2MK}=WJa>CsEzHTnqL&; zy-Z$L-W9aju`(R#%+WeC5z> z4C({yNc86lbD}+p_I8*>QET4IoCtf7ydnfeG>Nm5bdr8d1 z^vYoSS^Lgw{k%ms(-Gh3zSu$dB{v;qr%4CX!Ew8SSZ&5Xx_S&JmSqrle8^qCE+f#p zJPJ_E)M-0}ALcDwbGSTOFM5Xsp#~jgadx%Q zszwuvyS>1DWXxl=yBY-R!C!s*J|2`l@S=Vx?njn)zLM9PQvs`q0HdHiKVOwmA-E_p z8gT)KdwXMWo>XhHCy-z0SRA~T67 zEW~`k3p{D&;U|J8_`k}z-pqC&O|2-foJs%Thj1tP3#{)(Cf33M2SSEBhPmt3HSEHK zT6tdGz>^8BJO&2g0C7)T@Uz%n8JrKr!XI-!)c8%S2w%2*zhmWlN4GN%s_}?Ukrj~z zxm6e~%0yy`B#MzX7Ac&>r$WpPeYxd*GPyeH`aVR;Q@Fe%Ncu!XaX7vWh%@Hf&j})s zC-$+oGoe4Tr+4d4lT&C9XP+LOz=w!1iL1o{(z1!5wxGaqyB z*%jD1`o%7eIjGyd8ckr7~WDJXF{AtbR8Lq5}B0cyB1&d%YCgHE%oz!>KB;AbRG$(zaxp$ zL0z6Gfo+O&9nmsT4$M9x? zkNzBspJ4K{OdKYnX8#m(19Kx^`%lho3YjD;ox&zU@4nm4i=31p%mV5BXeK(6e{%4D z%>j_z2>-OocgU26xF^AN3fC*tF8jFW$o($iq0{s2cj(CUm}~xnpz;=6vv#H3a1elt zGk@!hN4w z4SZHdO8p-k+6#35oMtJa{fPN~5D*p{zEX(el>97au1>Iu3{0Iw;*WP*Ew}D^9uRYI zt=Zl#tfEJ4wLCVJEgMA~TjF;Lu?f9{DXYJQ+L=Wwg4Jv;W}i6pmQXdb>X%TPV3DIZ zVTA)DBN9&dnw9PCOBs&vJoPhdGDc*{iMU{278|125hnwj3FW;qhB|Kbeehi76s}j8 zY>%h$h;>T=@JJM=4%MWv(F!G%9|CfFAN0>MwE62 zBTe!TsAvcl>|{4_v;Y(+CKAJ2aJLxUEe3bS#O^NzJg56V;5j54Zjnc?&O-C05zY$9 z26px=jSgvW z;}%gXvr6;Jvi3=i2v=s>Vjy@w!pz7X2W!I*M-WMmV<`0TS?$?L2^A&3ROj$G>|Ys} zuo8kz{W9++q88!s5%Kz-)MqXh9>dB$p1EAc-3;xd(E0&f?!r*Dx1chnK_|GF zgNlK}Ho}JqLFFYJ*@tjH(5Knf`%NVH)Ap z)8NYF$7O_A?=`2)6rju2j2spo+BArG#l2`yCMdBLJpA4vcm>b_N61G3eHgpo2^Tv) z)u87q3am^z4XNb~PJn1yGVU;X2tpaak!^-yuqnzB**<&=N5VGKc69tP{?$X3 zDf#^|qVGf)H1y-%8v_}W@~0BxRUdH zQ0Iq*SP-C=3^_gufu?WE>CWT!v17W!SW)a9d{0Xb*TFQw|8|I}kZ=a|9*AZX8|}7z zIy#dV^?*t6Ts=;z#PjmU>FW&!=1-nEJNL|Ur!PG7T=mRTa~FK;lDqB&K3^Q=NW4}` zyk+t9sc!@)TVfpM@_m#zOw}z9**FQmWD#HMwft+=YXhMN0jj0TNs?&9bGWzD!5TNn zd5q5(KS^B>>kW+90gM1npp@mgmJMUcz*AZM%;j-9Co9?BA$v~YKQw5g$s&m_BT&bA zMqyq^5>o9ndQ!j695$$k!8np2lzlqu!ja zlvA174Gc@WCPevunMFcKbGt7x_d86|*xRq6Vx5qo$A#?8%$aaiBdGB^#X@niTu^*s zh{fTJ%qd*ngGiDr?)Lw%gM}aW%X6^a5t`XK2UPCe8!$EEb!dl35zlKV3p!U8mBFRA}!@d$Bqba9M0@f1$FOs|xk~aT9Cy(9*$T%db8&GL6 zlqA1jA&f~pr993U>`6{PCNx4d{4gKloSb6{BEr>|QK5D+iPuv!U`n*l;x&E5U|(Ms zBhu0%<#c74sz!#9$XP*Y}hz#Q^gbLAhFQi28j+BYa+=freAH6unG8sKqdp~=V zj#8MVu&1wawJA}0$J4qS$rNv;`$D&7dtX66U*a4o5cJAJo8#S)tFC?@wOG}uGu_qI z^>bh!;gW{zE4ic&AL?6A5uad^`T&r#ig!l?`6g=NTq!*J>@x&$@6D(2H*%p= zNO)3EFQkL51t4MvaTWS3xwl5Y!H}#VDihcuuLfmBCkqQw{!8#DXkx>G8SFm8tr94! zaUjBN`InTK&2 z%bHSrlF!ec{=za7*Eztu(9EJz_>-f=jF7qL~!^C&!A7TKu?)*vHQ zf5hZnOz4pH^ZdU;EJhCJH}Ow)se=p97614~70|<#*^kp)Is8;_oT7<;9>#Cv7IZokKzrqgm5&-GYPTDl(jMul>{IBBi z-i~l~!5>U)X`74G{!{ky=S=>B$-9}PF_=F@#owc!*fuxu|2JXi7;8r}nopw^yZ)2G z;6h%0Bt<;qkv>jNXoMf;c?on@kO`qCS+8Xay z_G%B$+X;?8V?s|eh!KAl>d9YSYQIm8RYJMKQBSkjVRD1X$C$)c)UUB9wEh+5D7rMz z*<%_?`o+ifKSI5Y3+T~>YneD-2z#3r_f&T3{h1Fa9~JrXHL;J9TXGz~s)U IozsW@8*rmYlmGw# literal 0 HcmV?d00001 diff --git a/src/QD_MARL/decisiontrees/__pycache__/leaves.cpython-311.pyc b/src/QD_MARL/decisiontrees/__pycache__/leaves.cpython-311.pyc index 932384c4720a3d02fb5b072a4612eb92e415e120..a15093ac084ec5d1ecfd8d159753419ae0273966 100644 GIT binary patch delta 10129 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 C0Nnck literal 0 HcmV?d00001 diff --git a/src/QD_MARL/decisiontrees/__pycache__/trees.cpython-310.pyc b/src/QD_MARL/decisiontrees/__pycache__/trees.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e72bf72909f6c1958114cb19f800280a0e37a09 GIT binary patch literal 10414 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 delta 3515 zcmZ`*eN0=|6@S-XJU?uVjWKv6V89TZq>%7cLSiT+kkDp`=A)w?3pjqy;KX0O&!h=S z2C1TjwoIYBRf49fAJaseP3sz2(^hGkChd<&lQuz$*6c}ByGorjX`7s>owP}lcFuj~ z!;l*L$9?DAd(L_1aHLQAwedxAx=UD z*(jUh#)K(kA_A<XC$+8iY@Y6pb2iG}3SGq%2DjQA< z0VlJN-yATVRKn2!(af^)!n6Vl>(NwFj%8x0BwRR0Xey$pYAiW95LXgPGQ(0{Pu6en z8^~@pWO};H2LA35>VSVL0q{{LTQ>R28&H5*n-d;7$`)o;L@#x-PfV>o4+tq93mEt@ z&3q{wpH@`7Z66*#X!ch&!G&rEfaB#~n>Q^;^YBOA*@U^nVA%}{YG>~iH4q2;Ly@o8 zi2@(MOa=RCu!8JkPVo)mX77t{HlP*Ft|Zc#E92*5dZ`x+U8AgQ~wNlvjfs+B#5494S;4$r_!1fgS@Gk za5AD$GpcG)B~B`t@wgJrXjWX&*yL0uV4@Bt+v%s2etL#cj|QRB!m92jMbMq-R)6#pUs2N>(&6sl3Qnv=z6vv%i$qf>)!nj*Mm- zoy7#)wz_T^4i&o93^7?Vsu{}tKh~9nyP~b^U+w@{+*R%;l=YQYlh4^ic`f;ry;OeF zWVCmF$|O&Zw_I?z78_r_xNvc)@0HYiDpx6#dgkqr%`j*&`OtkPs-$UQ34Y zO3j{}umOJ7>)m7FnH1lLQkqE0e z@IIUxRJhpXs$KTiVW}Phm=nG%W$#pV!mWfoUiPR;ZMJ||vyI2YD)wOblA>zjI8|N@ zQ+el5jI!_T8iB*Mpx2s#gLj2%!UcA}&Bum=j!Qg83;kD|+t76r>xEO$mTob(?i~a|q|Lx~j#g;X(Wkqax=%|=? zKx-w;-t1~^y+QdVI>)Go10o&Ai!cI?E{~OunkMnt;>n@k0F^$loU8&yeXuNg?lco5 zGg?>-g+1^8J!B8r;i-lwd7{udRnQsw&UYuBg7rpwvX2^np0C>v_pl>Khm=P>2CLzK znPMgNSb6+B*Ss-zQk&W|1*RtK-t3o6e#0!^%E4QjE5um1kfa; zsPhykY>Fc3X~q4g`i7657(Y2Mct+;}&NJpM^|a_`>3Oty0zgGl!ea7j8U>nQ3;X_& zPYv6Dn><%YHkqk?Hr+2!Wn<#RaJvevMf{|gw| zD8lw+?Pq5XBqnhdhXZyxf?J+NIE8Q;ffE8h>%&W4MQ)ELKY=n8f%C&Q8aRszgxl~_ z-(%0TdIOSqcTU)VN`tv6Cv1RNWvsdOfm zh|MUPF*TjR*5(d*#C9fj9CRURZnPMzfia%xs0Oya&@sgsC}Lm-B{%zf$CDz}jk?*5 zE_ZFd-*xH%PL-9yTwi4qoj!OYxY5}F50G~|$4GBKoc|kk*o;Mqt4g;Nu;|Y|JiS?B zdIRIYr<(r6$}>YL>+hQKDhkH=;JAih~#Uq<*g3iNji z&M*ac?1M->?3Y1_1Xx*j*N|Ro@vCcn6;1LWeYo0I(n}~qu(Q{@$8{FJge;zQ9sYpq z!q*(SGwUFosb}v5tJp%7SlmruQyK0=t{v5%euho@Tu;8AT@QNoocLJcn4kSRXd(UV zx4}j}OUY(lGNWE~94SqbV`_vd8R&@}t-c5*eC$U(5*cJa>j{8~PkOxMkJ#GZY^!%`(Bv!?~hT@>rF@qucFZCk2Y3zwP$StF8CqS06+2JeiU<<-m?pqDAl zaz;t2DGEHc`5Y*MrcF<0RDGTY!B8P_2<&LzQOgh{OZ|{7^!>zn``RnlmX5DF{A&*X zis;Y3iG};`kq8gdx;;!D9H+E&n1&PTmbkY!pk~Q$X~p~F!C)`1lbAr4Aboqub8P9@ z{b9X_tY6(jJ~Xn3a0g)tVOt};iZXxWai`<&OtVimboNtL)rhbr 0: cur, par = fringe.pop(0) - string += f"{id(cur)} [{str(cur)}]\n" if par is not None: branch = "True" if par.get_left() is cur else "False" string += f"{id(par)} -->|{branch}| {id(cur)}\n" if not isinstance(cur, Leaf): - fringe.append((cur.get_left(), cur)) fringe.append((cur.get_right(), cur)) - return string def __str__(self): @@ -152,6 +151,7 @@ def __init__(self, root, gamma): :root: The root of the tree :gamma: The discount factor """ + DecisionTree.__init__(self, root) self._gamma = gamma @@ -169,28 +169,28 @@ def get_output(self, input_): :returns: A numpy array, with size equal to the dimensionality of the output space """ - #print(self) - #print(self._root) - decision, last_leaf = self._root.get_output(input_) - self._last_leaves.appendleft(last_leaf) - return decision - - """ + # decision = self._root + # self._last_leaves.appendleft(decision) + # decision = self._root.get_output(input_) + + decision = self._root - while isinstance(decision, Node): + while isinstance(decision, Node) or isinstance(decision, IndividualGP): if isinstance(decision, Leaf): self._last_leaves.appendleft(decision) decision = decision.get_output(input_) + elif isinstance(decision, IndividualGP): + decision = decision.get_output(input_) else: branch = decision.get_branch(input_) if branch == Condition.BRANCH_LEFT: decision = decision.get_left() else: decision = decision.get_right() - """ return decision + - def set_reward(self, reward): + def set_reward(self, reward): #TODO fix set rewards and why is called in DecisionTree """ Gives a reward to the tree. NOTE: this method stores the last reward and makes @@ -264,6 +264,10 @@ def new_episode(self): if len(self._last_leaves) > 0: self.set_reward_end_of_episode() self._init_buffers() + + def deep_copy(self): + dt = RLDecisionTree(self.get_root().deep_copy(), self._gamma) + return dt class FastDecisionTree(RLDecisionTree): diff --git a/src/QD_MARL/decisiontreelibrary/README.md b/src/QD_MARL/deprecated/decisiontreelibrary/README.md similarity index 100% rename from src/QD_MARL/decisiontreelibrary/README.md rename to src/QD_MARL/deprecated/decisiontreelibrary/README.md diff --git a/src/QD_MARL/decisiontreelibrary/__init__.py b/src/QD_MARL/deprecated/decisiontreelibrary/__init__.py similarity index 75% rename from src/QD_MARL/decisiontreelibrary/__init__.py rename to src/QD_MARL/deprecated/decisiontreelibrary/__init__.py index 147f16b00..f699c42b0 100644 --- a/src/QD_MARL/decisiontreelibrary/__init__.py +++ b/src/QD_MARL/deprecated/decisiontreelibrary/__init__.py @@ -1,5 +1,5 @@ #!/usr/bin/env python from .leaves import QLearningLeafFactory, ConstantLeafFactory, Leaf from .nodes import * -from .conditions import ConditionFactory +from .conditions import ConditionFactory, Condition from .trees import DecisionTree, RLDecisionTree diff --git a/src/QD_MARL/deprecated/decisiontreelibrary/__pycache__/__init__.cpython-311.pyc b/src/QD_MARL/deprecated/decisiontreelibrary/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a14aea3a498f7232acc6cd701d1396a11fecaaa8 GIT binary patch literal 561 zcmZ`#%}N6?5Keac?+>Cq`T{Fx5D#8N&=&R3ioch=Ea`64$R?3Vs`N>G1o3SOUdo;X zPu_%rhn}2>6%RHHGxL34GBcT{g@p#e^T}4l3&uAOrYHDG#`~B&l8j_DCyK^Y1#zIl zIHaBn^3sKmVP001xT30Y^_wr{HC2!6su4F3m(NL5d8b$?3XA4xM{v+mUt_#F;Hfh( zC{|iVTgSEgOO`x{XhGMapjg5inRL?V>6`j5gYtY^q|%~#0YVguXPv+JIGQUex#0K0 zLWpBP$-6STqO46OY|%)k)h$%LSo; action mapping (e.g. subset -> class in classification, subset -> value in regression, state -> action in RL) +- Conditions: These classes split their input space into two subsets. The type of conditions is not bound to any type (e.g. orthogonal, oblique, etc), in fact, a class extending Condition can implement its own splitting criterion. +- Trees: These classes are "wrappers" that contain a root (either a leaf or a condition) and implement methods to manage the tree. diff --git a/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/__init__.py b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/__init__.py new file mode 100644 index 000000000..7e716399b --- /dev/null +++ b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/__init__.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python +from .leaves import QLearningLeafFactory +from .nodes import * +from .conditions import ConditionFactory +from .trees import DecisionTree, RLDecisionTree + +# MIA AGGIUNTA +from .conditions import Condition +from .leaves import Leaf diff --git a/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/__leaves.py b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/__leaves.py new file mode 100644 index 000000000..1de22fe82 --- /dev/null +++ b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/__leaves.py @@ -0,0 +1,620 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + src.leaves + ~~~~~~~~~~ + + This module implements the leaves that can be used in the trees + and the factories to build them. + + :copyright: (c) 2021 by Leonardo Lucio Custode. + :license: MIT, see LICENSE for more details. +""" +import abc +import numpy as np +from copy import deepcopy +from .nodes import Node + +class Leaf(Node): + """ + This is the base class for the leaves, defines their interface. + """ + + def __init__(self): + """ + Initializes a leaf. + """ + # Call to the super + Node.__init__(self) + # Initialize the buffers + self.empty_buffers() + + def get_output(self, input_): + """ + Computes the output for the given input + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + self._inputs.append(input_) # Record the input + # Return an invalid action. The base class cannot compute decisions + return np.zeros(1) + + def record_action(self, action): + """ + Records an action into the history + + :action: The action taken by the leaf + """ + self._action_history.append(action) + + def set_reward(self, reward): + """ + Gives the reward to the leaf. + + :reward: The total reward given to the leaf (e.g. + for Q-learning it should be reward = r + gamma · Qmax(s')) + """ + n_in = len(self._inputs) + assert len(self._rewards) in [n_in - 2, n_in - 1], \ + f"The number of calls to get_output are different \ +from the number of calls to set_reward ({len(self._rewards)}, \ +{len(self._inputs)})." + self._rewards.append(reward) + + def empty_buffers(self): + """ + Deletes the buffers associated to the leaf. + """ + self._inputs = [] + self._action_history = [] + self._rewards = [] + + @abc.abstractmethod + def get_value(self): + """ + Returns a generic value associated to the leaf to compute the reward + that should be given to the other leaves + (e.g. for Q-learning it should be max(Q)) + """ + pass + + def get_buffers(self): + """ + Returns 3 lists: + - History of inputs + - History of actions + - History of rewards + """ + return self._inputs, self._action_history, self._rewards + + def set_buffers(self, inputs, actions, rewards): + """ + Sets the three buffers as the leaves' buffers + """ + self._inputs = inputs + self._action_history = actions + self._rewards = rewards + + def copy_structure(self): + """ + Returns a copy of the structure of itself + """ + return Leaf() + + def deep_copy(self): + """ + Returns a deep_copy of itself + """ + return deepcopy(self) + + def get_inputs(self): + return self._inputs + +# DA ELIMINARE + def get_name(self, i): + print("Leaf", i) + + +class QLearningLeaf(Leaf): + """ + This class implements a leaf that learns the state-action mapping + by means of Q-learning. + """ + + def __init__(self, n_actions, learning_rate=None): + """ + Initializes the leaf + + :n_actions: An integer that specifies the number of available actions + :learning_rate: A float (or None) that specifies the learning rate. + If it is None, a schedule of learning rate of 1/x, + where x is the number of visits to that action, + will be used + + """ + Leaf.__init__(self) + + self._learning_rate = learning_rate + self._q = self._init_q(n_actions) + self._visits = np.zeros(n_actions) + + def __str__(self): + #return '"{}"'.format(np.argmax(self._q)) # Without visits + return '"{} ({} visits)"'.format(np.argmax(self._q), np.sum(self._visits)) # Original + + def _init_q(self, n_actions): + """ + Initializes the Q function (to zero) + + :n_actions: The number of available actions + :returns: A list representing the multi-armed bandit version of the + Q-function + """ + return np.zeros(n_actions) + + def get_output(self, input_): + """ + Computes the output of the leaf + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + super().get_output(input_) + self._last_action = np.argmax(self._q) + self._visits[self._last_action] += 1 + super().record_action(self._last_action) + return self._last_action + + def set_reward(self, reward): + """ + Gives the reward to the leaf. + + :reward: The total reward given to the leaf (e.g. + for Q-learning it should be reward = r + gamma · Qmax(s')) + """ + super().set_reward(reward) + # Take the last action without reward + last_action = self._action_history[ + len(self._rewards)-len(self._action_history)-1 + ] + if self._learning_rate is None: + lr = 1 / self._visits[last_action] + else: + lr = self._learning_rate + + old_q = self._q[last_action] + self._q[last_action] = (1 - lr) * old_q + lr * reward + + def get_value(self): + """ + Returns a generic value associated to the leaf to compute the reward + that should be given to the other leaves + (e.g. for Q-learning it should be max(Q)) + """ + return np.max(self._q) + + def get_q(self): + """ + Returns the current Q function + """ + return self._q + + def force_action(self, input_, action): + """ + This method makes the leaf "return" a given action, i.e. it allows the + leaf to take a decision taken by someone else (e.g. useful for + exploration strategies) + + :input_: An array of input features + :action: The action "forced" by someone else + """ + super().get_output(input_) + self._last_action = action + self._visits[self._last_action] += 1 + super().record_action(self._last_action) + return self._last_action + + def get_n_actions(self): + """ + Returns the number of actions available to the leaf. + """ + return len(self._q) + + def set_q(self, q): + """ + Sets the Q function + + :q: A list of Q-values + """ + assert len(q) == len(self._q), \ + "The new Q has to be of the same size as the old Q" + self._q = q + + def get_lr(self): + return self._learning_rate + + def get_visits(self): + return self._visits + + def set_visits(self, value): + self._visits = value + + def copy_structure(self): + """ + Returns a copy of the structure of itself + """ + if self._learning_rate is not None: + lr = type(self._learning_rate)(self._learning_rate) + else: + lr = None + leaf = QLearningLeaf(self.get_n_actions(), lr) + #leaf.set_q(self.get_q()) # TENTATIVO MODIFICA COPIA + #leaf.set_visits(self.get_visits()) # TENTATIVO MODIFICA COPIA + return leaf + + def deep_copy(self): + """ + Returns a deep copy of itself + """ + return deepcopy(self) + +# DA ELIMINARE + def get_name(self, i): + print("QLearningLeaf", i) + i += 1 + super().get_name(i) + + +class QLearningLeafDecorator(QLearningLeaf): + """ + A base class for leaf decorators + """ + + def __init__(self, leaf): + """ + Initializes the base decorator + + :leaf: An instance of Leaf + """ + self._leaf = leaf + QLearningLeaf.__init__( + self, + leaf.get_n_actions(), + leaf.get_lr() + ) + + def __str__(self): + return str(self._leaf) + + def record_action(self, action): + """ + Records an action into the history + + :action: The action taken by the leaf + """ + self._leaf.record_action(action) + + def empty_buffers(self): + """ + Deletes the buffers associated to the leaf. + """ + self._leaf.empty_buffers() + + def get_buffers(self): + """ + Returns 3 lists: + - History of inputs + - History of actions + - History of rewards + """ + return self._leaf.get_buffers() + + def set_buffers(self, inputs, actions, rewards): + """ + Sets the three buffers as the leaves' buffers + """ + return self._leaf.set_buffers(inputs, actions, rewards) + + def get_output(self, input_): + """ + Computes the output of the leaf + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + return self._leaf.get_output(input_) + + def set_reward(self, reward): + """ + Gives the reward to the leaf. + + :reward: The total reward given to the leaf (e.g. + for Q-learning it should be reward = r + gamma · Qmax(s')) + """ + self._leaf.set_reward(reward) + + def get_value(self): + """ + Returns a generic value associated to the leaf to compute the reward + that should be given to the other leaves + (e.g. for Q-learning it should be max(Q)) + """ + return self._leaf.get_value() + + def get_q(self): + """ + Returns the current Q function + """ + return self._leaf.get_q() + + def force_action(self, input_, action): + """ + This method makes the leaf "return" a given action, i.e. it allows the + leaf to take a decision taken by someone else (e.g. useful for + exploration strategies) + + :input_: An array of input features + :action: The action "forced" by someone else + """ + return self._leaf.force_action(input_, action) + + def get_n_actions(self): + """ + Returns the number of actions available to the leaf. + """ + return self._leaf.get_n_actions() + + def set_q(self, q): + """ + Sets the Q function + + :q: A list of Q-values + """ + self._leaf.set_q(q) + + def get_lr(self): + return self._leaf.get_lr() + + def get_visits(self): + return self._leaf.get_visits() + + def copy_structure(self): + """ + Returns a copy of the structure of itself + """ + return QLearningLeafDecorator(self._leaf.copy_structure()) + + def deep_copy(self): + """ + Returns a deep copy of itself + """ + return deepcopy(self) + + def get_inputs(self): + return self._leaf.get_inputs() + +# DA ELIMINARE + def get_name(self, i): + print("QLearningLeafDecorator", i) + i += 1 + self.get_name(i) + + +class EpsilonGreedyQLearningLeafDecorator(QLearningLeafDecorator): + """ + QLearningLeafDecorator that allows a QLearningLeaf (or an extending class) + to have an epsilon-greedy exploration strategy + """ + + def __init__(self, leaf, epsilon, decay=1, min_epsilon=0): + """ + Initializes the decorator + + :leaf: An instance of QLearningLeaf + :epsilon: A float indicating the (initial) probability of exploration + :decay: Optional. A float indicating the decay factor for epsilon. + Default: 1 (No decay) + :min_epsilon: Optional. The minimum value of epsilon. + Default: 0 (No min value) + """ + QLearningLeafDecorator.__init__(self, leaf) + self._epsilon = epsilon + self._decay = decay + self._min_epsilon = min_epsilon + + def get_output(self, input_): + """ + Computes the output of the leaf + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + if np.random.uniform() < self._epsilon: + self._epsilon *= self._decay + self._epsilon = max(self._epsilon, self._min_epsilon) + return self._leaf.force_action( + input_, + np.random.randint(0, self._leaf.get_n_actions()) + ) + else: + self._epsilon *= self._decay + self._epsilon = max(self._epsilon, self._min_epsilon) + return self._leaf.get_output(input_) + + def copy_structure(self): + """ + Returns a copy of the structure of itself + """ + new = EpsilonGreedyQLearningLeafDecorator( + self._leaf.copy_structure(), + self._epsilon, + self._decay, + self._min_epsilon + ) + return new + + def deep_copy(self): + """ + Returns a copy of the structure of itself + """ + return deepcopy(self) + +# DA ELIMINARE + def get_name(self, i): + print("EpsilonGreedyQLearningLeafDecorator", i) + i += 1 + self._leaf.get_name(i) + + +class RandomInitQLearningLeafDecorator(QLearningLeafDecorator): + """ + A decorator that allows to randomly intialize the Q function + """ + + def __init__(self, leaf, low, high, distribution="uniform"): + """ + Initializes the decorator + + :leaf: An instance of QLearningLeaf + :low: The low bound for the initial Q function. + :high: The upper bound for the initial Q function + :distribution: The name of the distribution. + Can be either "normal" or "uniform". + In case the distribution is "normal", low and high will + be used to compute mean and std deviation of the normal. + + """ + QLearningLeafDecorator.__init__(self, leaf) + + n = self._leaf.get_n_actions() + self._distribution = distribution + self._low = low + self._high = high + if distribution == "normal": + mean = np.mean([low, high]) + std = np.std([low, high]) + self._leaf.set_q(np.random.normal(mean, std, n)) + else: + self._leaf.set_q(np.random.uniform(low, high, n)) + + def copy_structure(self): + """ + Returns a copy of the structure of itself + """ + new = RandomInitQLearningLeafDecorator( + self._leaf.copy_structure(), + self._low, + self._high, + self._distribution + ) + #new.set_q(self._leaf.get_q()) # TENTATIVO MODIFICA COPIA + return new + + def deep_copy(self): + """ + Returns a deep copy itself + """ + return deep_copy(self) + +# DA ELIMINARE + def get_name(self, i): + print("RandomInitQLearningLeafDecorator", i) + i += 1 + self._leaf.get_name(i) + + +class NoBuffersDecorator(QLearningLeafDecorator): + """ + A decorator that allows to avoid memory leaks due to the big number + of transitions recorded. Useful when a lot of trees are used with + algorithms that do not need their history (e.g. evolutionary algorithms) + """ + + def __init__(self, leaf): + """ + Initializes the decorator + + :leaf: An instance of QLearningLeaf + """ + QLearningLeafDecorator.__init__(self, leaf) + + def get_output(self, input_): + """ + Returns the output associated with the input + + :input_: The input vector (1D) + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + # Retrieve the output from the leaf + out = self._leaf.get_output(input_) + # Delete the unnecessary elements in the buffers + # I.e. the ones whose reward has already been set + inputs, actions, rewards = self._leaf.get_buffers() + unnecessary = len(rewards) - 1 + if unnecessary > 0: + del inputs[:unnecessary] + del rewards[:unnecessary] + del actions[:unnecessary] + self._leaf.set_buffers(inputs, actions, rewards) + # return the output of the leaf + return out + + def copy_structure(self): + """ + Returns a copy of the structure of itself + """ + new = NoBuffersDecorator( + self._leaf.copy_structure() + ) + return new + + def deep_copy(self): + """ + Returns a deep copy itself + """ + return deepcopy(self) + +# DA ELIMINARE + def get_name(self, i): + print("NoBuffersDecorator", i) + i += 1 + self._leaf.get_name(i) + + +class QLearningLeafFactory: + """ + A base class for the factories of leaves. + """ + + DECORATOR_DICT = { + "EpsilonGreedy": EpsilonGreedyQLearningLeafDecorator, + "RandomInit": RandomInitQLearningLeafDecorator, + "NoBuffers": NoBuffersDecorator, + } + + def __init__(self, leaf_params, decorators): + """ + Initializes the factory + + :leaf_params: A dictionary containing all the parameters of the leaf + :decorators: A list of (decorator_name, **params) + """ + self._leaf_params = leaf_params + self._decorators = decorators + for name, _ in self._decorators: + assert name in self.DECORATOR_DICT, \ + f"Unable to find the decorator {name}\n\ + Available decorators: {self.DECORATOR_DICT.keys()}" + + def create(self): + """ Creates a leaf and returns it """ + leaf = QLearningLeaf(**self._leaf_params) + for name, params in self._decorators: + leaf = self.DECORATOR_DICT[name](leaf, **params) + return leaf diff --git a/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/conditions.py b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/conditions.py new file mode 100644 index 000000000..f132c7979 --- /dev/null +++ b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/conditions.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + src.trees + ~~~~~~~~~ + + This module implements the conditions to be used in the decision trees + + :copyright: (c) 2021 by Leonardo Lucio Custode. + :license: MIT, see LICENSE for more details. +""" +import abc +from copy import deepcopy +from .nodes import Node + + +class Condition(Node): + """ + This is the base class for the conditions. + """ + BRANCH_LEFT = True + BRANCH_RIGHT = False + + def __init__(self, left, right): + """ + Initializes an internal node (that checks a condition) + + :left: The node in the left branch (i.e. the one taken when the + condition evaluates to True) + :right: The node in the right branch (i.e. the one taken when + the condition evaluates to False) + + """ + Node.__init__(self) + + self._left = left + self._right = right + + def get_left(self): + return self._left + + def get_right(self): + return self._right + + def set_left(self, value): + self._left = value + + def set_right(self, value): + self._right = value + + @abc.abstractstaticmethod + def get_trainable_parameters(self): + """ + Returns a list of parameters with their type + (input_index, int or float) as a string. + """ + pass + + @abc.abstractmethod + def set_params_from_list(self, params): + """ + Sets its parameters according to the parameters specified by + the input list. + + :params: A list of params (int or float) + """ + pass + + def get_output(self, input_): + """ + Computes the output associated to its inputs (i.e. computes + the path of the input vector (or vectors) in the tree and returns + the decision associated to it). + + :input_: A 1D numpy array + :returns: A 1D numpy array + """ + assert len(input_.shape) == 1, "Only 1D arrays are currently supported" + if self.get_branch(input_) == Condition.BRANCH_LEFT: + return self._left.get_output(input_) + else: + return self._right.get_output(input_) + + @abc.abstractmethod + def get_branch(self, inputs): + """ + Computes the branch taken given the inputs + + :inputs: 1D numpy array (1 sample) or 2D numpy array (N samples) + :returns: A numpy array where each element is either: + - True: Left branch has been taken + - False: Right branch has been taken + """ + pass + + def empty_buffers(self): + self._left.empty_buffers() + self._right.empty_buffers() + + def copy_structure(self): + """ + Returns a copy of the structure of itself + """ + new = Condition(self.get_left().copy_structure(), self.get_right().copy_structure()) + return new + + def deep_copy(self): + """ + Returns a deep copy of itself + """ + return deepcopy(self) + +class OrthogonalCondition(Condition): + """ + This class implements orthogonal conditions for the decision tree. + Orthogonal conditions are the ones that generate hyperplanes that are + orthogonal to the chosen axis (i.e. they test only one variable). + """ + + def __init__(self, feature_idx, split_value, left=None, right=None): + """ + Initializes the condition. + + :feature_idx: the index of the variable (that has to be tested) in + the input vector + :split_value: The value used for the test + :left: The left node. Default: None. + :right: The right node. Default: None. + """ + Condition.__init__(self, left, right) + + self._feature_idx = feature_idx + self._split_value = split_value + + def get_branch(self, inputs): + """ + Computes the branch taken given the inputs + + :inputs: 1D numpy array (1 sample) or 2D numpy array (N samples) + :returns: A numpy array where each element is either: + - True: Left branch has been taken + - False: Right branch has been taken + """ + if len(inputs.shape) == 1: + return inputs[self._feature_idx] < self._split_value + return inputs[:, self._feature_idx] < self._split_value + + @staticmethod + def get_trainable_parameters(): + """ + Returns a list of parameters with their type + (input_index, int or float) as a string. + """ + return ["input_index", "float"] + + @staticmethod + def check_params(params): + """ + Checks whether the parameters are good for this type of node. + If not, it raises an AssertionError + + :params: A list of params (int or float) + """ + assert len(params) >= 2, \ + "This type of condition requires 2 parameters." + + @staticmethod + def create_from_params(params): + """ + Creates a condition from its parameters + + :params: A list of params (int or float) + """ + OrthogonalCondition.check_params(params) + return OrthogonalCondition(int(params[0]), float(params[1])) + + def set_params_from_list(self, params): + """ + Sets its parameters according to the parameters specified by + the input list. + + :params: A list of params (int or float) + """ + OrthogonalCondition.check_params(params) + self._feature_idx = int(params[0]) + self._split_value = float(params[1]) + + def get_feature_idx(self): + return self._feature_idx + + def get_split_value(self): + return self._split_value + + def set_feature_idx(self, value): + self._feature_idx = value + + def set_split_value(self, value): + self._split_value = value + + def __str__(self): + return f"x_{self._feature_idx} < {round(self._split_value, 1)}" + #return f"x_{self._feature_idx} < {self._split_value}" # Original + + def copy_structure(self): + """ + Returns a copy of the structure of itself + """ + new = OrthogonalCondition( + self.get_feature_idx(), + self.get_split_value(), + self.get_left().copy_structure(), + self.get_right().copy_structure() + ) + return new + + def deep_copy(self): + """ + Returns a deep copy of itself + """ + return deepcopy(self) + + +class ConditionFactory: + """ + A factory for conditions + """ + ORTHOGONAL = "orthogonal" + + CONDITIONS = { + ORTHOGONAL: OrthogonalCondition, + } + + def __init__(self, condition_type="orthogonal"): + """ + Initializes the factory of conditions + + :condition_type: strings supported: + - orthogonal + """ + self._condition_type = condition_type + + def create(self, params): + """ + Creates a condition + :returns: A Condition + """ + return self.CONDITIONS[self._condition_type].create_from_params(params) + + def get_trainable_parameters(self): + """ + Returns a list of parameters with their type (int or float). + """ + return self.CONDITIONS[self._condition_type].get_trainable_parameters() + + diff --git a/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/leaves.py b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/leaves.py new file mode 100644 index 000000000..3648dc091 --- /dev/null +++ b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/leaves.py @@ -0,0 +1,706 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + src.leaves + ~~~~~~~~~~ + + This module implements the leaves that can be used in the trees + and the factories to build them. + + :copyright: (c) 2021 by Leonardo Lucio Custode. + :license: MIT, see LICENSE for more details. +""" +import abc +import numpy as np +from .nodes import Node +from copy import deepcopy + + +class Leaf(Node): + """ + This is the base class for the leaves, defines their interface. + """ + + def __init__(self): + """ + Initializes a leaf. + """ + # Call to the super + Node.__init__(self) + # Initialize the buffers + self.empty_buffers() + + def get_output(self, input_): + """ + Computes the output for the given input + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + self._inputs.append(input_) # Record the input + # Return an invalid action. The base class cannot compute decisions + return np.zeros(1) + + def record_action(self, action): + """ + Records an action into the history + + :action: The action taken by the leaf + """ + self._action_history.append(action) + + def set_reward(self, reward): + """ + Gives the reward to the leaf. + + :reward: The total reward given to the leaf (e.g. + for Q-learning it should be reward = r + gamma · Qmax(s')) + """ + n_in = len(self._inputs) + """ + assert len(self._rewards) in [n_in - 2, n_in - 1], \ + f"The number of calls to get_output are different \ +from the number of calls to set_reward ({len(self._rewards)}, \ +{len(self._inputs)})." + """ + self._rewards.append(reward) + + def empty_buffers(self): + """ + Deletes the buffers associated to the leaf. + """ + self._inputs = [] + self._action_history = [] + self._rewards = [] + + @abc.abstractmethod + def get_value(self): + """ + Returns a generic value associated to the leaf to compute the reward + that should be given to the other leaves + (e.g. for Q-learning it should be max(Q)) + """ + pass + + def get_buffers(self): + """ + Returns 3 lists: + - History of inputs + - History of actions + - History of rewards + """ + return self._inputs, self._action_history, self._rewards + + def set_buffers(self, inputs, actions, rewards): + """ + Sets the three buffers as the leaves' buffers + """ + self._inputs = inputs + self._action_history = actions + self._rewards = rewards + + def copy(self): + """ + Returns a copy of itself + """ + return Leaf() + + def get_inputs(self): + return self._inputs + + +class QLearningLeaf(Leaf): + """ + This class implements a leaf that learns the state-action mapping + by means of Q-learning. + """ + + def __init__(self, n_actions, learning_rate=None): + """ + Initializes the leaf + + :n_actions: An integer that specifies the number of available actions + :learning_rate: A float (or None) that specifies the learning rate. + If it is None, a schedule of learning rate of 1/x, + where x is the number of visits to that action, + will be used + + """ + Leaf.__init__(self) + + self._learning_rate = learning_rate + self._q = self._init_q(n_actions) + self._visits = np.zeros(n_actions) + + def __str__(self): + return f"{np.argmax(self._q)} ({np.sum(self._visits)} visits)" + + def _init_q(self, n_actions): + """ + Initializes the Q function (to zero) + + :n_actions: The number of available actions + :returns: A list representing the multi-armed bandit version of the + Q-function + """ + return np.zeros(n_actions) + + def get_output(self, input_): + """ + Computes the output of the leaf + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + super().get_output(input_) + self._last_action = np.argmax(self._q) + self._visits[self._last_action] += 1 + super().record_action(self._last_action) + return self._last_action + + def set_reward(self, reward): + """ + Gives the reward to the leaf. + + :reward: The total reward given to the leaf (e.g. + for Q-learning it should be reward = r + gamma · Qmax(s')) + """ + super().set_reward(reward) + # Take the last action without reward + last_action = self._last_action + if self._learning_rate is None: + lr = 1 / self._visits[last_action] + else: + lr = self._learning_rate + + old_q = self._q[last_action] + self._q[last_action] = (1 - lr) * old_q + lr * reward + + def get_value(self): + """ + Returns a generic value associated to the leaf to compute the reward + that should be given to the other leaves + (e.g. for Q-learning it should be max(Q)) + """ + return np.max(self._q) + + def get_q(self): + """ + Returns the current Q function + """ + return self._q + + def force_action(self, input_, action): + """ + This method makes the leaf "return" a given action, i.e. it allows the + leaf to take a decision taken by someone else (e.g. useful for + exploration strategies) + + :input_: An array of input features + :action: The action "forced" by someone else + """ + super().get_output(input_) + self._last_action = action + self._visits[self._last_action] += 1 + super().record_action(self._last_action) + return self._last_action + + def get_n_actions(self): + """ + Returns the number of actions available to the leaf. + """ + return len(self._q) + + def set_q(self, q): + """ + Sets the Q function + + :q: A list of Q-values + """ + assert len(q) == len(self._q), \ + "The new Q has to be of the same size as the old Q" + self._q = q + + def get_lr(self): + return self._learning_rate + + def get_visits(self): + return self._visits + + def set_visits(self, value): + self._visits = value + + def copy(self): + """ + Returns a copy of itself + """ + if self._learning_rate is not None: + lr = type(self._learning_rate)(self._learning_rate) + else: + lr = None + leaf = QLearningLeaf(self.get_n_actions(), lr) + leaf.set_q(self.get_q().copy()) + leaf.set_visits(deepcopy(self.get_visits())) + return leaf + + def set_then(self, value): + self._then = value + + def set_else(self, value): + self._else = value + + +class QLearningLeafDecorator(QLearningLeaf): + """ + A base class for leaf decorators + """ + + def __init__(self, leaf): + """ + Initializes the base decorator + + :leaf: An instance of Leaf + """ + self._leaf = leaf + QLearningLeaf.__init__( + self, + leaf.get_n_actions(), + leaf.get_lr() + ) + + def __str__(self): + return str(self._leaf) + + def record_action(self, action): + """ + Records an action into the history + + :action: The action taken by the leaf + """ + self._leaf.record_action(action) + + def empty_buffers(self): + """ + Deletes the buffers associated to the leaf. + """ + self._leaf.empty_buffers() + + def get_buffers(self): + """ + Returns 3 lists: + - History of inputs + - History of actions + - History of rewards + """ + return self._leaf.get_buffers() + + def set_buffers(self, inputs, actions, rewards): + """ + Sets the three buffers as the leaves' buffers + """ + return self._leaf.set_buffers(inputs, actions, rewards) + + def get_output(self, input_): + """ + Computes the output of the leaf + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + return self._leaf.get_output(input_) + + def set_reward(self, reward): + """ + Gives the reward to the leaf. + + :reward: The total reward given to the leaf (e.g. + for Q-learning it should be reward = r + gamma · Qmax(s')) + """ + self._leaf.set_reward(reward) + + def get_value(self): + """ + Returns a generic value associated to the leaf to compute the reward + that should be given to the other leaves + (e.g. for Q-learning it should be max(Q)) + """ + return self._leaf.get_value() + + def get_q(self): + """ + Returns the current Q function + """ + return self._leaf.get_q() + + def force_action(self, input_, action): + """ + This method makes the leaf "return" a given action, i.e. it allows the + leaf to take a decision taken by someone else (e.g. useful for + exploration strategies) + + :input_: An array of input features + :action: The action "forced" by someone else + """ + return self._leaf.force_action(input_, action) + + def get_n_actions(self): + """ + Returns the number of actions available to the leaf. + """ + return self._leaf.get_n_actions() + + def set_q(self, q): + """ + Sets the Q function + + :q: A list of Q-values + """ + self._leaf.set_q(q) + + def get_lr(self): + return self._leaf.get_lr() + + def get_visits(self): + return self._leaf.get_visits() + + def copy(self): + """ + Returns a copy of itself + """ + return QLearningLeafDecorator(self._leaf.copy()) + + def get_inputs(self): + return self._leaf.get_inputs() + + def set_left(self, value): + self._leaf.set_left(value) + + def set_right(self, value): + self._leaf.set_right(value) + + def get_left(self): + return self._leaf.get_left() + + def get_right(self): + return self._leaf.get_right() + + def get_then(self): + return self._leaf.get_then() + + def get_else(self): + return self._leaf.get_else() + + def _then(self, value): + self._leaf.set_then(value) + + def _else(self, value): + self._leaf.set_else(value) + + def set_then(self, value): + self._leaf.set_then(value) + + def set_else(self, value): + self._leaf.set_else(value) + + + + +class EpsilonGreedyQLearningLeafDecorator(QLearningLeafDecorator): + """ + QLearningLeafDecorator that allows a QLearningLeaf (or an extending class) + to have an epsilon-greedy exploration strategy + """ + + def __init__(self, leaf, epsilon=None, decay=1, min_epsilon=0): + """ + Initializes the decorator + + :leaf: An instance of QLearningLeaf + :epsilon: A float indicating the (initial) probability of exploration. Default: 1/k + :decay: Optional. A float indicating the decay factor for epsilon. + Default: 1 (No decay) + :min_epsilon: Optional. The minimum value of epsilon. + Default: 0 (No min value) + """ + QLearningLeafDecorator.__init__(self, leaf) + self._epsilon = epsilon + self._decay = decay + self._min_epsilon = min_epsilon + self._visits = 0 + + def get_output(self, input_): + """ + Computes the output of the leaf + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + self._visits += 1 + if self._epsilon is None: + eps = 1/self._visits + else: + self._epsilon *= self._decay + eps = max(self._epsilon, self._min_epsilon) + + if np.random.uniform() < eps: + return self._leaf.force_action( + input_, + np.random.randint(0, self._leaf.get_n_actions()) + ) + else: + return self._leaf.get_output(input_) + + def copy(self): + """ + Returns a copy of itself + """ + new = EpsilonGreedyQLearningLeafDecorator( + self._leaf.copy(), + self._epsilon, + self._decay, + self._min_epsilon + ) + return new + + +class RandomInitQLearningLeafDecorator(QLearningLeafDecorator): + """ + A decorator that allows to randomly intialize the Q function + """ + + def __init__(self, leaf, low, high, distribution="uniform"): + """ + Initializes the decorator + + :leaf: An instance of QLearningLeaf + :low: The low bound for the initial Q function. + :high: The upper bound for the initial Q function + :distribution: The name of the distribution. + Can be either "normal" or "uniform". + In case the distribution is "normal", low and high will + be used to compute mean and std deviation of the normal. + + """ + QLearningLeafDecorator.__init__(self, leaf) + + n = self._leaf.get_n_actions() + self._distribution = distribution + self._low = low + self._high = high + if distribution == "normal": + mean = np.mean([low, high]) + std = np.std([low, high]) + self._leaf.set_q(np.random.normal(mean, std, n)) + else: + self._leaf.set_q(np.random.uniform(low, high, n)) + + def copy(self): + """ + Returns a copy of itself + """ + new = RandomInitQLearningLeafDecorator( + self._leaf.copy(), + self._low, + self._high, + self._distribution + ) + new.set_q(self._leaf.get_q()) + return new + + +class NoBuffersDecorator(QLearningLeafDecorator): + """ + A decorator that allows to avoid memory leaks due to the big number + of transitions recorded. Useful when a lot of trees are used with + algorithms that do not need their history (e.g. evolutionary algorithms) + """ + + def __init__(self, leaf): + """ + Initializes the decorator + + :leaf: An instance of QLearningLeaf + """ + QLearningLeafDecorator.__init__(self, leaf) + + def get_output(self, input_): + """ + Returns the output associated with the input + + :input_: The input vector (1D) + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + # Retrieve the output from the leaf + out = self._leaf.get_output(input_) + # Delete the unnecessary elements in the buffers + # I.e. the ones whose reward has already been set + inputs, actions, rewards = self._leaf.get_buffers() + unnecessary = len(rewards) - 1 + if unnecessary > 0: + del inputs[:unnecessary] + del rewards[:unnecessary] + del actions[:unnecessary] + self._leaf.set_buffers(inputs, actions, rewards) + # return the output of the leaf + return out + + def copy(self): + """ + Returns a copy of itself + """ + new = NoBuffersDecorator( + self._leaf.copy() + ) + return new + + + +class QLambdaDecorator(QLearningLeafDecorator): + """ + A decorator that implements Naive Q(lambda). + """ + + def __init__(self, leaf, decay): + """ + Initializes the decorator + + :leaf: The leaf + :decay: The decay factor: lambda * gamma + """ + QLearningLeafDecorator.__init__(self, leaf) + self._decay = decay + + self._eligibility_traces = np.zeros(len(self._leaf.get_q())) + self._visits = np.zeros(len(self._eligibility_traces)) + self._last_action = None + + def copy(self): + """ + Returns a copy of itself + """ + return QLambdaDecorator(self._leaf.copy(), self._decay) + + def get_output(self, input_): + """ + Computes the output of the leaf + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + output = self._leaf.get_output(input_) + self._last_action = output + self._eligibility_traces[self._last_action] += 1 + return output + + def set_reward(self, reward): + """ + Gives the reward to the leaf. + + :reward: The total reward given to the leaf (e.g. + for Q-learning it should be reward = r + gamma · Qmax(s')) + """ + q = self._leaf.get_q() + + for i in range(len(self._eligibility_traces)): + _learning_rate = self._leaf.get_lr() + self._visits[i] += 1 + if _learning_rate is None: + lr = 1 / self._visits[i] + else: + lr = _learning_rate + + delta = reward - q[i] + q[i] += lr * self._eligibility_traces[i] * delta + + self._eligibility_traces[i] *= self._decay + + self._leaf.set_q(q) + + def empty_buffers(self): + self._leaf.empty_buffers() + self._eligibility_traces = np.zeros(len(self._leaf.get_q())) + +class QLearningLeafFactory: + """ + A base class for the factories of leaves. + """ + + DECORATOR_DICT = { + "EpsilonGreedy": EpsilonGreedyQLearningLeafDecorator, + "RandomInit": RandomInitQLearningLeafDecorator, + "NoBuffers": NoBuffersDecorator, + "QLambda": QLambdaDecorator, + } + + def __init__(self, leaf_params, decorators): + """ + Initializes the factory + + :leaf_params: A dictionary containing all the parameters of the leaf + :decorators: A list of (decorator_name, **params) + """ + self._leaf_params = leaf_params + self._decorators = decorators + for name, _ in self._decorators: + assert name in self.DECORATOR_DICT, \ + f"Unable to find the decorator {name}\n\ + Available decorators: {self.DECORATOR_DICT.keys()}" + + def create(self): + """ Creates a leaf and returns it """ + leaf = QLearningLeaf(**self._leaf_params) + for name, params in self._decorators: + leaf = self.DECORATOR_DICT[name](leaf, **params) + return leaf + + def get_trainable_parameters(self): + return [] + + +####################################################################### +# Constant leaves # +####################################################################### + + +class ConstantLeaf(Leaf): + """ + A leaf that does not learn + """ + + def __init__(self, action): + """ + Initializes the leaf + + :action: The action contained in the leaf + """ + Leaf.__init__(self) + + self._action = action + + def get_output(self, input_): + return self._action + + def set_reward(self, input_): + pass + + def get_value(self): + return 0 + + +class ConstantLeafFactory(): + """ + A Factory for constant leaves + """ + + def create(self, params): + action = params[0] if not isinstance(params, int) else params + return ConstantLeaf(action) + + def get_trainable_parameters(self): + return ["action"] diff --git a/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/nodes.py b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/nodes.py new file mode 100644 index 000000000..1d8883457 --- /dev/null +++ b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/nodes.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + src.nodes + ~~~~~~~~~ + + This module contains the implementations of the node base class + + :copyright: (c) 2021 by Leonardo Lucio Custode. + :license: MIT, see LICENSE for more details. +""" +import abc + +class Node: + """ + A basic node for a decision tree + """ + + @abc.abstractmethod + def get_output(self, input_features): + """ + Computes the output of the current node, + possibly calling the child leaves + + :input_features: An array containing all the input features + :returns: A numpy array whose last dimension has size + equal to the size of the output space + + """ + pass + + @abc.abstractmethod + def empty_buffers(self): + """ + Clears the buffers of the node and, recursively, + the buffers of all its subtree + """ + pass + + @abc.abstractmethod + def copy(self): + """ + Returns a copy of itself + """ + pass diff --git a/src/QD_MARL/decisiontreelibrary/tests/__init__.py b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/tests/__init__.py similarity index 100% rename from src/QD_MARL/decisiontreelibrary/tests/__init__.py rename to src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/tests/__init__.py diff --git a/src/QD_MARL/decisiontreelibrary/tests/test_conditions.py b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/tests/test_conditions.py similarity index 100% rename from src/QD_MARL/decisiontreelibrary/tests/test_conditions.py rename to src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/tests/test_conditions.py diff --git a/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/tests/test_leaves.py b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/tests/test_leaves.py new file mode 100644 index 000000000..c9066d0e4 --- /dev/null +++ b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/tests/test_leaves.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + src.test_leaves + ~~~~~~~~~ + + Tests for the leaves module + + :copyright: (c) 2021 by Leonardo Lucio Custode. + :license: MIT, see LICENSE for more details. +""" +import pytest +import numpy as np +from functools import partial +from ..leaves import Leaf, QLearningLeaf, RandomInitQLearningLeafDecorator, \ + EpsilonGreedyQLearningLeafDecorator, NoBuffersDecorator, \ + QLearningLeafFactory + + +##################### +# Leaf Base class # +##################### + +n_tests = 10 + +inputs = [ + np.random.uniform( + -100, 100, size=np.random.randint(1, 100) + ) for _ in range(n_tests) +] + +leaves = [ + Leaf, + partial(QLearningLeaf, n_actions=10, learning_rate=0.1), +] + +actions = np.random.randint(0, 10, n_tests) + +rewards = np.random.uniform(-10, 10, n_tests) + +lgo_inputs_to_test = [] +lra_inputs_to_test = [] +lsr_inputs_to_test = [] + +for inp in inputs: + for leaf in leaves: + lgo_inputs_to_test.append((inp, leaf)) + for a in actions: + lra_inputs_to_test.append((inp, a, leaf)) + for r in rewards: + lsr_inputs_to_test.append((inp, a, r, leaf)) + + +@pytest.mark.parametrize("input_, leaf_type", lgo_inputs_to_test) +def test_lgo(input_, leaf_type): + leaf = leaf_type() + leaf.get_output(input_) + assert len(leaf.get_inputs()) == 1 + assert np.linalg.norm(leaf.get_inputs()[0] - input_) == 0 + states, actions, rewards = leaf.get_buffers() + assert len(states) == 1 + assert np.linalg.norm(states[0] - input_) == 0 + if not isinstance(leaf, Leaf): + assert len(actions) == 1 + assert len(rewards) == 0 + return leaf + + +@pytest.mark.parametrize( + "input_, action, reward, leaf_type", + lsr_inputs_to_test +) +def leaf_lsrgo(input_, action, reward, leaf_type): + leaf = test_lgo(input_, leaf_type) + leaf.set_reward(reward) + assert len(leaf._rewards) == 1 + assert leaf._rewards[0] == reward + states, actions, rewards = leaf.get_buffers() + assert len(states) == 1 + if not isinstance(leaf, Leaf): + assert len(actions) == 1 + assert len(rewards) == 1 + assert rewards[0] == reward + return leaf + + +@pytest.mark.parametrize( + "input_, action, reward, leaf_type", + lsr_inputs_to_test +) +def test_lebgo(input_, action, reward, leaf_type): + leaf = leaf_lsrgo(input_, action, reward, leaf_type) + leaf.empty_buffers() + assert len(leaf.get_inputs()) == 0 + assert len(leaf._action_history) == 0 + assert len(leaf._rewards) == 0 + states, actions, rewards = leaf.get_buffers() + assert len(states) == 0 + assert len(actions) == 0 + assert len(rewards) == 0 + + +@pytest.mark.parametrize("leaf_type", leaves) +def test_lc(leaf_type): + leaf = leaf_type() + new_leaf = leaf.copy() + + assert id(leaf) != id(new_leaf) + for attribute in dir(leaf): + if attribute[:2] != "__": + if not isinstance(attribute, str) and \ + not isinstance(attribute, int): + assert id(getattr(leaf, attribute)) != \ + id(getattr(new_leaf, attribute)) + +################### +# QLearningLeaf # +################### + + +n_actions = 10 +bounds = [-10, 10] +eps = 0.05 + +decorators = [ + None, + partial(RandomInitQLearningLeafDecorator, low=bounds[0], high=bounds[1]), + partial(EpsilonGreedyQLearningLeafDecorator, epsilon=eps, decay=1, min_epsilon=eps), + partial(NoBuffersDecorator), +] + +actions = np.random.randint(0, n_actions, 20) + +qllgo_inputs = [] +qllfa_inputs = [] + +for d in decorators: + for i in inputs: + qllgo_inputs.append((i, d)) + for a in actions: + qllfa_inputs.append((i, a, d)) + + +def make_qlearningleaf(decorator): + leaf = QLearningLeaf(n_actions, None) + if decorator is not None: + leaf = decorator(leaf) + return leaf + + +@pytest.mark.parametrize("decorator", decorators) +def test_qlliq(decorator): + leaf = make_qlearningleaf(decorator) + leaf._init_q(n_actions) + assert len(leaf.get_q()) == n_actions + if isinstance(leaf, RandomInitQLearningLeafDecorator): + assert max(leaf.get_q()) <= bounds[1] + assert min(leaf.get_q()) >= bounds[0] + else: + assert max(leaf.get_q()) == 0 + assert min(leaf.get_q()) == 0 + return leaf + + +@pytest.mark.parametrize("input_, decorator", qllgo_inputs) +def test_qllgo(input_, decorator): + leaf = make_qlearningleaf(decorator) + outputs = np.array([leaf.get_output(input_) for i in range(101)]) + + if isinstance(leaf, RandomInitQLearningLeafDecorator): + # The action should always be the same + # For the moment let's ignore the case in which two elements + # have the same Q values + assert max(outputs) == min(outputs) + elif isinstance(leaf, EpsilonGreedyQLearningLeafDecorator): + # Since epsilon is low, this should be the most frequent choice + median = np.median(outputs) + # Give this some margin + assert np.sum(outputs == median) > 1 - 2 * eps + # Default Leaf Behavior + assert max(outputs) < n_actions + assert min(outputs) >= 0 + + +@pytest.mark.parametrize("input_, decorator", qllgo_inputs) +def test_qllsr(input_, decorator): + leaf = make_qlearningleaf(decorator) + for i in np.arange(0, 100): + action = leaf.get_output(input_) + rew = np.random.uniform(-1, 1) + cur_q = leaf.get_q()[action] + leaf.set_reward(rew) + if rew < cur_q: + assert leaf.get_q()[action] <= cur_q + else: + assert leaf.get_q()[action] >= cur_q + + +@pytest.mark.parametrize("input_, action, leaf_type", qllfa_inputs) +def test_qllfa(input_, action, leaf_type): + leaf = make_qlearningleaf(leaf_type) + leaf.force_action(input_, action) + assert len(leaf.get_inputs()) == 1 + assert np.linalg.norm(leaf.get_inputs()[0] - input_) == 0 + states, actions, rewards = leaf.get_buffers() + assert len(states) == 1 + assert np.linalg.norm(states[0] - input_) == 0 + assert len(actions) == 1 + assert actions[0] == action + assert len(rewards) == 0 + return leaf + + +################# +# LeafFactory # +################# + +decorators_list = [ + None, + [["RandomInit", {"low": -100, "high": 100}]], + [["EpsilonGreedy", {"epsilon": 0.05, "decay": 1}]], + [["NoBuffers", {}]], + [ + ["RandomInit", {"low": -100, "high": 100}], + ["EpsilonGreedy", {"epsilon": 0.05, "decay": 1}] + ], + [ + ["RandomInit", {"low": -100, "high": 100}], + ["EpsilonGreedy", {"epsilon": 0.05, "decay": 1}], + ["NoBuffers", {}] + ], +] + + +@pytest.mark.parametrize("decorator", decorators_list) +def test_qllf(decorator): + d = [] if decorator is None else decorator + lf = QLearningLeafFactory({"n_actions": 10}, d).create() + assert isinstance(lf, Leaf) + assert isinstance(lf, QLearningLeaf) + if decorator is not None: + assert isinstance(lf._leaf, QLearningLeaf) + assert lf.get_q() is lf._leaf.get_q() + return lf + + +@pytest.mark.parametrize("decorator", decorators_list) +def test_qllfc(decorator): + leaf = test_qllf(decorator) + new_leaf = leaf.copy() + + assert id(leaf) != id(new_leaf) + for attribute in dir(leaf): + if attribute[:2] != "__": + if not isinstance(attribute, str) and \ + not isinstance(attribute, int): + assert id(getattr(leaf, attribute)) != \ + id(getattr(new_leaf, attribute)) + + diff --git a/src/QD_MARL/decisiontreelibrary/tests/test_trees.py b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/tests/test_trees.py similarity index 100% rename from src/QD_MARL/decisiontreelibrary/tests/test_trees.py rename to src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/tests/test_trees.py diff --git a/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/trees.py b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/trees.py new file mode 100644 index 000000000..2d8b640df --- /dev/null +++ b/src/QD_MARL/deprecated/decisiontreelibrary/decisiontreelibrary/trees.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + dt.tree + ~~~~~~~ + + This module implements a decision tree + + :copyright: (c) 2021 by Leonardo Lucio Custode. + :license: MIT, see LICENSE for more details. +""" +from .nodes import Node +from .leaves import Leaf +from collections import deque +from copy import deepcopy +from .conditions import Condition + +class DecisionTree: + """ + This class implements a general decision tree. + It can be used for classification/regression tasks. + """ + + def __init__(self, root): + """ + Initializes the decision tree + + :root: The root of the tree, must be of type Node. + """ + self._root = root + + def get_root(self): + return self._root + + def set_root(self, value): + self._root = value + + def get_output(self, input_): + """ + Computes the output of the decision tree + + :input_: An input vector + :returns: A numpy array, with size equal to the + dimensionality of the output space + + """ + return self._root.get_output(input_) + + def empty_buffers(self): + """ + Resets the buffers of all the nodes in the tree + """ + self._root.empty_buffers() + + def get_leaves(self): + """ + Returns the leaves of the tree + :returns: A Leaf object + """ + fringe = [self._root] + leaves = [] + while len(fringe) > 0: + node = fringe.pop(0) + if isinstance(node, Leaf): + leaves.append(node) + else: + fringe.append(node.get_left()) + fringe.append(node.get_right()) + return leaves + + def replace(self, old_node, new_node): + """ + Replaces a node of the tree with another node. + If the tree does not contain the node, the tree remains unchanged. + + :old_node: The node to replace + :new_node: The node that replaces the old one + """ + fringe = [(self._root, None)] # (node, parent) + + while len(fringe) > 0: + node, parent = fringe.pop(0) + if node is old_node: + if parent is not None: + is_left = parent.get_left() == node + if is_left: + parent.set_left(new_node) + else: + parent.set_right(new_node) + else: + self._root = new_node + else: + if not isinstance(node, Leaf): + fringe.append((node.get_left(), node)) + fringe.append((node.get_right(), node)) + + def __repr__(self): + fringe = [(self._root, None)] + string = "" + + while len(fringe) > 0: + cur, par = fringe.pop(0) + + string += f"{id(cur)}[{str(cur)}]\n" + if par is not None: + branch = "True" if par._left is cur else "False" + string += f"{id(par)} -->|{branch}| {id(cur)}\n" + if not isinstance(cur, Leaf): + fringe.append((cur.get_left(), cur)) + fringe.append((cur.get_right(), cur)) + + return string + + def __str__(self): + return repr(self) + + def copy_structure(self): + """ + Returns a copy of the strcture + """ + dt = DecisionTree(self.get_root().copy_structure()) + return dt + + def deep_copy(self): + """ + Returns a deep copy + """ + return deepcopy(self) + + +class RLDecisionTree(DecisionTree): + """ + A Decision tree that can perform RL task + """ + + def __init__(self, root, gamma): + """ + Initializes the decision tree for RL tasks + + :root: The root of the tree + :gamma: The discount factor + """ + DecisionTree.__init__(self, root) + self._gamma = gamma + self._last_leaves = deque(maxlen=2) + self._rewards = deque(maxlen=2) + + def get_output(self, input_): + """ + Computes the output of the decision tree + + :input_: An input vector + :returns: A numpy array, with size equal to the + dimensionality of the output space + """ + decision = self._root + while isinstance(decision, Node): + if isinstance(decision, Leaf): + self._last_leaves.appendleft(decision) + decision = decision.get_output(input_) + else: + branch = decision.get_branch(input_) + if branch == Condition.BRANCH_LEFT: + decision = decision.get_left() + else: + decision = decision.get_right() + return decision + + def set_reward(self, reward): + """ + Gives a reward to the tree. + NOTE: this method stores the last reward and makes + the tree "learn" the penultimate reward. + To make the tree "learn" from the last reward, use + set_reward_end_of_episode(). + + :reward: The reward obtained by the environment + """ + self._rewards.appendleft(reward) + if len(self._last_leaves) == 2: + leaf = self._last_leaves.pop() + leaf.set_reward(self._rewards.pop() + + self._gamma * self._last_leaves[0].get_value()) + + def set_reward_end_of_episode(self): + """ + Sets the reward to the last leaf visited in the episode. + """ + assert len(self._last_leaves) == 1, \ + "This method has to be called at the end of an episode" + leaf = self._last_leaves.pop() + leaf.set_reward(self._rewards.pop()) + + def empty_buffers(self): + """ + Resets the buffers of all the nodes in the tree + """ + self._last_leaves = deque(maxlen=2) + self._rewards = deque(maxlen=2) + self._root.empty_buffers() + + def force_action(self, input_, action): + """ + Forces the tree to take an action + + :input_: the input of the tree + :action: the action to be forced + """ + decision = self._root + while isinstance(decision, Node): + if isinstance(decision, Leaf): + self._last_leaves.appendleft(decision) + decision.force_action(input_, action) + decision = None + else: + branch = decision.get_branch(input_) + if branch == Condition.BRANCH_LEFT: + decision = decision.get_left() + else: + decision = decision.get_right() + return action + + def copy_structure(self): + """ + Returns a copy of the structure + """ + dt = RLDecisionTree(self.get_root().copy_structure(), self._gamma) + return dt + + def deep_copy(self): + """ + Returns a deep copy + """ + return deepcopy(self) diff --git a/src/QD_MARL/decisiontreelibrary/leaves.py b/src/QD_MARL/deprecated/decisiontreelibrary/leaves.py similarity index 100% rename from src/QD_MARL/decisiontreelibrary/leaves.py rename to src/QD_MARL/deprecated/decisiontreelibrary/leaves.py diff --git a/src/QD_MARL/decisiontreelibrary/nodes.py b/src/QD_MARL/deprecated/decisiontreelibrary/nodes.py similarity index 100% rename from src/QD_MARL/decisiontreelibrary/nodes.py rename to src/QD_MARL/deprecated/decisiontreelibrary/nodes.py diff --git a/src/QD_MARL/deprecated/decisiontreelibrary/tests/__init__.py b/src/QD_MARL/deprecated/decisiontreelibrary/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/QD_MARL/deprecated/decisiontreelibrary/tests/test_conditions.py b/src/QD_MARL/deprecated/decisiontreelibrary/tests/test_conditions.py new file mode 100644 index 000000000..be3134249 --- /dev/null +++ b/src/QD_MARL/deprecated/decisiontreelibrary/tests/test_conditions.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + src.test_conditions + ~~~~~~~~~~~~~~~~~~~ + + Tests for the condition module + + :copyright: (c) 2021 by Leonardo Lucio Custode. + :license: MIT, see LICENSE for more details. +""" +import pytest +import numpy as np +from numpy.linalg import norm +from ..leaves import QLearningLeaf +from ..conditions import Condition, OrthogonalCondition, ConditionFactory + + +def test_c(): + l1 = QLearningLeaf(10) + l2 = QLearningLeaf(10) + c = Condition(l1, l2) + assert l1 is not l2 + assert c.get_left() is not c.get_right() + assert c.get_left() is l1 + assert c.get_right() is l2 + c.set_left(l2) + assert c.get_left() is l2 + assert c.get_left() is c.get_right() + c.set_right(l1) + assert c.get_right() is l1 + assert c.get_left() is not c.get_right() + return c + + +inputs = np.random.uniform(-100, 100, (10, 10, 3)) +lots_of_inputs = np.random.uniform(-100, 100, (10, 100, 3)) + + +@pytest.mark.parametrize("inputs", inputs) +def test_eb(inputs): + l1 = QLearningLeaf(10) + l2 = QLearningLeaf(10) + c = Condition(l1, l2) + for i in inputs: + l1.get_output(i) + l2.get_output(i) + s1, a1, r1 = c.get_left().get_buffers() + s2, a2, r2 = c.get_right().get_buffers() + assert len(s1) > 0 + assert len(a1) > 0 + assert len(s2) > 0 + assert len(a2) > 0 + c.empty_buffers() + s1, a1, r1 = c.get_left().get_buffers() + s2, a2, r2 = c.get_right().get_buffers() + assert len(s1) == 0 + assert len(a1) == 0 + assert len(s2) == 0 + assert len(a2) == 0 + + +def test_cc(): + c = test_c() + new = c.copy() + + assert c is not new + assert c.get_left() is not new.get_left() + assert c.get_right() is not new.get_right() + + +######################### +# OrthogonalCondition # +######################### + + +@pytest.mark.parametrize("inputs", inputs) +def test_ocgo1(inputs): + c = OrthogonalCondition(0, 0, QLearningLeaf(2), QLearningLeaf(3)) + + for i in inputs: + c.get_output(i) + if i[0] < 0: + assert norm(c.get_left().get_buffers()[0][-1] - i) == 0 + else: + assert norm(c.get_right().get_buffers()[0][-1] - i) == 0 + + +@pytest.mark.parametrize("inputs", lots_of_inputs) +def test_ocgo2(inputs): + l1 = QLearningLeaf(10) + l2 = QLearningLeaf(10) + l3 = QLearningLeaf(10) + l4 = QLearningLeaf(10) + + left = OrthogonalCondition(1, 0, l1, l2) + right = OrthogonalCondition(2, 0, l3, l4) + + root = OrthogonalCondition(0, 0, left, right) + + for i in inputs: + root.get_output(i) + if i[0] < 0: + if i[1] < 0: + assert norm(root.get_left().get_left().get_buffers()[0][-1] - i) == 0 + else: + assert norm(root.get_left().get_right().get_buffers()[0][-1] - i) == 0 + else: + if i[2] < 0: + assert norm(root.get_right().get_left().get_buffers()[0][-1] - i) == 0 + else: + assert norm(root.get_right().get_right().get_buffers()[0][-1] - i) == 0 + + +def test_ocgtp(): + c = OrthogonalCondition(0, 1, None, None) + assert c.get_trainable_parameters()[0] == "input_index" + assert c.get_trainable_parameters()[1] == "float" + + +def test_occ(): + c = OrthogonalCondition(0, 0, QLearningLeaf(10), QLearningLeaf(10)) + new = c.copy() + + assert c is not new + assert new.get_feature_idx() == c.get_feature_idx() + assert new.get_split_value() == c.get_split_value() + assert c.get_left() is not new.get_left() + assert c.get_right() is not new.get_right() + + new.set_params_from_list([1, 1]) + assert new.get_feature_idx() != c.get_feature_idx() + assert new.get_split_value() != c.get_split_value() + assert c.get_left() is not new.get_left() + assert c.get_right() is not new.get_right() + + +###################### +# ConditionFactory # +###################### + +def test_cf(): + cf = ConditionFactory("orthogonal") + c = cf.create([0, 1]) + assert isinstance(c, OrthogonalCondition) + assert c.get_feature_idx() == 0 + assert c.get_split_value() == 1.0 diff --git a/src/QD_MARL/decisiontreelibrary/tests/test_leaves.py b/src/QD_MARL/deprecated/decisiontreelibrary/tests/test_leaves.py similarity index 100% rename from src/QD_MARL/decisiontreelibrary/tests/test_leaves.py rename to src/QD_MARL/deprecated/decisiontreelibrary/tests/test_leaves.py diff --git a/src/QD_MARL/deprecated/decisiontreelibrary/tests/test_trees.py b/src/QD_MARL/deprecated/decisiontreelibrary/tests/test_trees.py new file mode 100644 index 000000000..505bae686 --- /dev/null +++ b/src/QD_MARL/deprecated/decisiontreelibrary/tests/test_trees.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + src.test_trees + ~~~~~~~~~~~~~~ + + Tests for the trees module + + :copyright: (c) 2021 by Leonardo Lucio Custode. + :license: MIT, see LICENSE for more details. +""" +import pytest +import numpy as np +from numpy.linalg import norm +from ..trees import DecisionTree, RLDecisionTree +from ..leaves import QLearningLeafFactory +from ..conditions import ConditionFactory + +################## +# DecisionTree # +################## + +inputs = np.random.uniform(-100, 100, (10, 100, 5)) +types = [ + DecisionTree, + RLDecisionTree +] + +full_inputs = [] + + +for t in types: + for i in inputs: + full_inputs.append((t, i)) + + +def test_dt(): + c = ConditionFactory().create([1, 1]) + d = ConditionFactory().create([0, 0]) + dt = DecisionTree(c) + + assert dt.get_root() is c + assert dt.get_root().get_feature_idx() == c.get_feature_idx() + assert dt.get_root().get_split_value() == c.get_split_value() + + dt.set_root(d) + assert dt.get_root() is d + assert dt.get_root().get_feature_idx() == d.get_feature_idx() + assert dt.get_root().get_split_value() == d.get_split_value() + + +@pytest.mark.parametrize("tp, inputs", full_inputs) +def test_dtfull(tp, inputs): + root = ConditionFactory().create([0, 0]) + left = ConditionFactory().create([1, 1]) + right = ConditionFactory().create([2, 1]) + qllf = QLearningLeafFactory({"n_actions": 10}, []) + l1 = qllf.create() + l2 = qllf.create() + l3 = qllf.create() + l4 = qllf.create() + + root.set_left(left) + root.set_right(right) + left.set_left(l1) + left.set_right(l2) + right.set_left(l3) + right.set_right(l4) + + if tp == RLDecisionTree: + dt = RLDecisionTree(root, 0) + else: + dt = DecisionTree(root) + + leaves = dt.get_leaves() + assert len(leaves) == 4 + assert l1 in leaves + assert l2 in leaves + assert l3 in leaves + assert l4 in leaves + + for i in inputs: + dt.get_output(i) + if i[0] < 0: + # Left + if i[1] < 1: + assert norm(l1.get_buffers()[0][-1] - i) == 0 + else: + assert norm(l2.get_buffers()[0][-1] - i) == 0 + else: + if i[2] < 1: + assert norm(l3.get_buffers()[0][-1] - i) == 0 + else: + assert norm(l4.get_buffers()[0][-1] - i) == 0 + + s1, a1, _ = l1.get_buffers() + s2, a2, _ = l2.get_buffers() + s3, a3, _ = l3.get_buffers() + s4, a4, _ = l4.get_buffers() + + assert len(s1) > 0 + assert len(s2) > 0 + assert len(s3) > 0 + assert len(s4) > 0 + assert len(a1) > 0 + assert len(a2) > 0 + assert len(a3) > 0 + assert len(a4) > 0 + + new_dt = dt.copy() + + new_leaves = new_dt.get_leaves() + + assert l1 not in new_leaves + assert l2 not in new_leaves + assert l3 not in new_leaves + assert l4 not in new_leaves + + l1, l2, l3, l4 = new_leaves + + assert len(s1) > 0 + assert len(s2) > 0 + assert len(s3) > 0 + assert len(s4) > 0 + assert len(a1) > 0 + assert len(a2) > 0 + assert len(a3) > 0 + assert len(a4) > 0 + + dt.empty_buffers() + + s1, a1, _ = l1.get_buffers() + s2, a2, _ = l2.get_buffers() + s3, a3, _ = l3.get_buffers() + s4, a4, _ = l4.get_buffers() + + assert len(s1) == 0 + assert len(s2) == 0 + assert len(s3) == 0 + assert len(s4) == 0 + assert len(a1) == 0 + assert len(a2) == 0 + assert len(a3) == 0 + assert len(a4) == 0 + + l1, l2, l3, l4 = new_leaves + + assert len(s1) == 0 + assert len(s2) == 0 + assert len(s3) == 0 + assert len(s4) == 0 + assert len(a1) == 0 + assert len(a2) == 0 + assert len(a3) == 0 + assert len(a4) == 0 + + +#################### +# RLDecisionTree # +#################### + + +@pytest.mark.parametrize("inputs", inputs) +def test_rldtsr(inputs): + root = ConditionFactory().create([0, 0]) + left = ConditionFactory().create([1, 1]) + right = ConditionFactory().create([2, 1]) + qllf = QLearningLeafFactory({"n_actions": 10, "learning_rate": 0.1}, []) + l1 = qllf.create() + l2 = qllf.create() + l3 = qllf.create() + l4 = qllf.create() + + root.set_left(left) + root.set_right(right) + left.set_left(l1) + left.set_right(l2) + right.set_left(l3) + right.set_right(l4) + + dt = RLDecisionTree(root, 0.9) + + for i in inputs: + if i[0] < 0: + if i[1] < 1: + tgt_leaf = l1 + else: + tgt_leaf = l2 + else: + if i[2] < 1: + tgt_leaf = l3 + else: + tgt_leaf = l4 + + dt.empty_buffers() + output = dt.get_output(i) + previous_q = tgt_leaf.get_q().copy() + rew = np.random.uniform(-1, 1) + dt.set_reward(rew) + dt.get_output(i) + dt.set_reward(rew) + cur_q = tgt_leaf.get_q() + + assert cur_q[output] == 0.9 * previous_q[output] + 0.1 * (rew + 0.9 * max(previous_q)) + for j in range(10): + if j == output: + continue + assert cur_q[j] == previous_q[j] + + +@pytest.mark.parametrize("inputs", inputs) +def test_rldtsrfa(inputs): + root = ConditionFactory().create([0, 0]) + left = ConditionFactory().create([1, 1]) + right = ConditionFactory().create([2, 1]) + qllf = QLearningLeafFactory({"n_actions": 10, "learning_rate": 0.1}, []) + l1 = qllf.create() + l2 = qllf.create() + l3 = qllf.create() + l4 = qllf.create() + + root.set_left(left) + root.set_right(right) + left.set_left(l1) + left.set_right(l2) + right.set_left(l3) + right.set_right(l4) + + dt = RLDecisionTree(root, 0.9) + + for i in inputs: + if i[0] < 0: + if i[1] < 1: + tgt_leaf = l1 + else: + tgt_leaf = l2 + else: + if i[2] < 1: + tgt_leaf = l3 + else: + tgt_leaf = l4 + + dt.empty_buffers() + output = dt.force_action(i, 0) + assert output == 0 + previous_q = tgt_leaf.get_q().copy() + rew = np.random.uniform(-1, 1) + dt.set_reward(rew) + dt.get_output(i) + dt.set_reward(rew) + cur_q = tgt_leaf.get_q() + + assert cur_q[output] == 0.9 * previous_q[output] + 0.1 * (rew + 0.9 * max(previous_q)) + for j in range(10): + if j == output: + continue + assert cur_q[j] == previous_q[j] + + +@pytest.mark.parametrize("inputs", inputs) +def test_rldtsree(inputs): + root = ConditionFactory().create([0, 0]) + left = ConditionFactory().create([1, 1]) + right = ConditionFactory().create([2, 1]) + qllf = QLearningLeafFactory({"n_actions": 10, "learning_rate": 0.1}, []) + l1 = qllf.create() + l2 = qllf.create() + l3 = qllf.create() + l4 = qllf.create() + + root.set_left(left) + root.set_right(right) + left.set_left(l1) + left.set_right(l2) + right.set_left(l3) + right.set_right(l4) + + dt = RLDecisionTree(root, 0.9) + + for i in inputs: + if i[0] < 0: + if i[1] < 1: + tgt_leaf = l1 + else: + tgt_leaf = l2 + else: + if i[2] < 1: + tgt_leaf = l3 + else: + tgt_leaf = l4 + + dt.empty_buffers() + output = dt.get_output(i) + print("Output:", output) + + s, a, r = tgt_leaf.get_buffers() + assert len(s) == 1 + assert len(a) == 1 + assert len(r) == 0 + + previous_q = tgt_leaf.get_q().copy() + rew = np.random.uniform(-1, 1) + dt.set_reward(rew) + dt.set_reward_end_of_episode() + cur_q = tgt_leaf.get_q() + + assert cur_q[output] == 0.9 * previous_q[output] + 0.1 * (rew) + for j in range(10): + if j == output: + continue + assert cur_q[j] == previous_q[j] diff --git a/src/QD_MARL/decisiontreelibrary/trees.py b/src/QD_MARL/deprecated/decisiontreelibrary/trees.py similarity index 99% rename from src/QD_MARL/decisiontreelibrary/trees.py rename to src/QD_MARL/deprecated/decisiontreelibrary/trees.py index b1384d8f3..c194a3d59 100644 --- a/src/QD_MARL/decisiontreelibrary/trees.py +++ b/src/QD_MARL/deprecated/decisiontreelibrary/trees.py @@ -100,7 +100,6 @@ def __repr__(self): while len(fringe) > 0: cur, par = fringe.pop(0) - string += f"{id(cur)} [{str(cur)}]\n" if par is not None: branch = "True" if par._left is cur else "False" diff --git a/src/QD_MARL/deprecated/leaves.py b/src/QD_MARL/deprecated/leaves.py new file mode 100644 index 000000000..d9e8d1040 --- /dev/null +++ b/src/QD_MARL/deprecated/leaves.py @@ -0,0 +1,800 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + src.leaves + ~~~~~~~~~~ + + This module implements the leaves that can be used in the trees + and the factories to build them. + + :copyright: (c) 2021 by Leonardo Lucio Custode. + :license: MIT, see LICENSE for more details. +""" +import abc +import torch +import numpy as np +from .nodes import Node +from copy import deepcopy + + +class Leaf(Node): + """ + This is the base class for the leaves, defines their interface. + """ + + def __init__(self): + """ + Initializes a leaf. + """ + # Call to the super + Node.__init__(self) + # Initialize the buffers + self.empty_buffers() + + def get_output(self, input_): + """ + Computes the output for the given input + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions and the reference to self + + """ + self._inputs.append(input_) # Record the input + # Return an invalid action. The base class cannot compute decisions + return np.zeros(1), self + + def record_action(self, action): + """ + Records an action into the history + + :action: The action taken by the leaf + """ + self._action_history.append(action) + + def set_reward(self, reward): + """ + Gives the reward to the leaf. + + :reward: The total reward given to the leaf (e.g. + for Q-learning it should be reward = r + gamma · Qmax(s')) + """ + n_in = len(self._inputs) + """ + assert len(self._rewards) in [n_in - 2, n_in - 1], \ + f"The number of calls to get_output are different \ +from the number of calls to set_reward ({len(self._rewards)}, \ +{len(self._inputs)})." + """ + self._rewards.append(reward) + + def empty_buffers(self): + """ + Deletes the buffers associated to the leaf. + """ + self._inputs = [] + self._action_history = [] + self._rewards = [] + + @abc.abstractmethod + def get_value(self): + """ + Returns a generic value associated to the leaf to compute the reward + that should be given to the other leaves + (e.g. for Q-learning it should be max(Q)) + """ + pass + + @abc.abstractmethod + def stop_learning(self): + pass + + def get_buffers(self): + """ + Returns 3 lists: + - History of inputs + - History of actions + - History of rewards + """ + return self._inputs, self._action_history, self._rewards + + def set_buffers(self, inputs, actions, rewards): + """ + Sets the three buffers as the leaves' buffers + """ + self._inputs = inputs + self._action_history = actions + self._rewards = rewards + + def copy(self): + """ + Returns a copy of itself + """ + return deepcopy(self) + + def get_inputs(self): + return self._inputs + + +class QLearningLeaf(Leaf): + """ + This class implements a leaf that learns the state-action mapping + by means of Q-learning. + """ + + def __init__(self, n_actions, learning_rate=None): + """ + Initializes the leaf + + :n_actions: An integer that specifies the number of available actions + :learning_rate: A float (or None) that specifies the learning rate. + If it is None, a schedule of learning rate of 1/x, + where x is the number of visits to that action, + will be used + + """ + Leaf.__init__(self) + + self._learning_rate = learning_rate + self._q = self._init_q(n_actions) + self._visits = np.zeros(n_actions) + + def __str__(self): + return f"{np.argmax(self._q)} ({np.sum(self._visits)} visits)" + + def stop_learning(self): + self._learning_rate = .0 + + def _init_q(self, n_actions): + """ + Initializes the Q function (to zero) + + :n_actions: The number of available actions + :returns: A list representing the multi-armed bandit version of the + Q-function + """ + return np.zeros(n_actions) + + def get_output(self, input_): + """ + Computes the output of the leaf + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + super().get_output(input_) + self._last_action = np.argmax(self._q) + self._visits[self._last_action] += 1 + super().record_action(self._last_action) + return self._last_action, self + + def set_reward(self, reward): + """ + Gives the reward to the leaf. + + :reward: The total reward given to the leaf (e.g. + for Q-learning it should be reward = r + gamma · Qmax(s')) + """ + super().set_reward(reward) + # Take the last action without reward + last_action = self._last_action + if self._learning_rate is None: + lr = 1 / self._visits[last_action] + else: + lr = self._learning_rate + + old_q = self._q[last_action] + self._q[last_action] = (1 - lr) * old_q + lr * reward + + def get_value(self): + """ + Returns a generic value associated to the leaf to compute the reward + that should be given to the other leaves + (e.g. for Q-learning it should be max(Q)) + """ + return np.max(self._q) + + def get_q(self): + """ + Returns the current Q function + """ + return self._q + + def force_action(self, input_, action): + """ + This method makes the leaf "return" a given action, i.e. it allows the + leaf to take a decision taken by someone else (e.g. useful for + exploration strategies) + + :input_: An array of input features + :action: The action "forced" by someone else + """ + super().get_output(input_) + self._last_action = action + self._visits[self._last_action] += 1 + super().record_action(self._last_action) + return self._last_action, self + + def get_n_actions(self): + """ + Returns the number of actions available to the leaf. + """ + return len(self._q) + + def set_q(self, q): + """ + Sets the Q function + + :q: A list of Q-values + """ + assert len(q) == len(self._q), \ + "The new Q has to be of the same size as the old Q" + self._q = q + + def get_lr(self): + return self._learning_rate + + def set_lr(self, lrn): + self._learning_rate = lrn + + def get_visits(self): + return self._visits + + def set_visits(self, value): + self._visits = value + + def copy(self): + """ + Returns a copy of itself + """ + if self._learning_rate is not None: + lr = type(self._learning_rate)(self._learning_rate) + else: + lr = None + leaf = QLearningLeaf(self.get_n_actions(), lr) + leaf.set_q(self.get_q().copy()) + leaf.set_visits(deepcopy(self.get_visits())) + return leaf + + +class QLearningLeafDecorator(QLearningLeaf): + """ + A base class for leaf decorators + """ + + def __init__(self, leaf): + """ + Initializes the base decorator + + :leaf: An instance of Leaf + """ + self._leaf = leaf + QLearningLeaf.__init__( + self, + leaf.get_n_actions(), + leaf.get_lr() + ) + + def __str__(self): + return str(self._leaf) + + def record_action(self, action): + """ + Records an action into the history + + :action: The action taken by the leaf + """ + self._leaf.record_action(action) + + def empty_buffers(self): + """ + Deletes the buffers associated to the leaf. + """ + self._leaf.empty_buffers() + + def get_buffers(self): + """ + Returns 3 lists: + - History of inputs + - History of actions + - History of rewards + """ + return self._leaf.get_buffers() + + def set_buffers(self, inputs, actions, rewards): + """ + Sets the three buffers as the leaves' buffers + """ + return self._leaf.set_buffers(inputs, actions, rewards) + + def get_output(self, input_): + """ + Computes the output of the leaf + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + return self._leaf.get_output(input_)[0], self + + def set_reward(self, reward): + """ + Gives the reward to the leaf. + + :reward: The total reward given to the leaf (e.g. + for Q-learning it should be reward = r + gamma · Qmax(s')) + """ + self._leaf.set_reward(reward) + + def get_value(self): + """ + Returns a generic value associated to the leaf to compute the reward + that should be given to the other leaves + (e.g. for Q-learning it should be max(Q)) + """ + return self._leaf.get_value() + + def get_q(self): + """ + Returns the current Q function + """ + return self._leaf.get_q() + + def force_action(self, input_, action): + """ + This method makes the leaf "return" a given action, i.e. it allows the + leaf to take a decision taken by someone else (e.g. useful for + exploration strategies) + + :input_: An array of input features + :action: The action "forced" by someone else + """ + return self._leaf.force_action(input_, action)[0], self + + def get_n_actions(self): + """ + Returns the number of actions available to the leaf. + """ + return self._leaf.get_n_actions() + + def set_q(self, q): + """ + Sets the Q function + + :q: A list of Q-values + """ + self._leaf.set_q(q) + + def get_lr(self): + #print("hhhh " + str(self._leaf.get_lr())) + return super().get_lr() + + def set_lr(self, lrn): + super().set_lr(lrn) + + def stop_learning(self): + self._leaf.stop_learning() + super().stop_learning() + + def get_visits(self): + return self._leaf.get_visits() + + def copy(self): + """ + Returns a copy of itself + """ + return QLearningLeafDecorator(self._leaf.copy()) + + def get_inputs(self): + return self._leaf.get_inputs() + + +class EpsilonGreedyQLearningLeafDecorator(QLearningLeafDecorator): + """ + QLearningLeafDecorator that allows a QLearningLeaf (or an extending class) + to have an epsilon-greedy exploration strategy + """ + + def __init__(self, leaf, epsilon=None, decay=1, min_epsilon=0): + """ + Initializes the decorator + + :leaf: An instance of QLearningLeaf + :epsilon: A float indicating the (initial) probability of exploration. Default: 1/k + :decay: Optional. A float indicating the decay factor for epsilon. + Default: 1 (No decay) + :min_epsilon: Optional. The minimum value of epsilon. + Default: 0 (No min value) + """ + QLearningLeafDecorator.__init__(self, leaf) + self._epsilon = epsilon + self._decay = decay + self._min_epsilon = min_epsilon + self._visits = 0 + + def get_output(self, input_): + """ + Computes the output of the leaf + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + self._visits += 1 + if self._epsilon is None: + eps = 1 / self._visits + else: + self._epsilon *= self._decay + eps = max(self._epsilon, self._min_epsilon) + + if np.random.uniform() < eps: + return self._leaf.force_action( + input_, + np.random.randint(0, self._leaf.get_n_actions()) + )[0], self + else: + return self._leaf.get_output(input_)[0], self + + def copy(self): + """ + Returns a copy of itself + """ + new = EpsilonGreedyQLearningLeafDecorator( + self._leaf.copy(), + self._epsilon, + self._decay, + self._min_epsilon + ) + return new + + def stop_learning(self): + self._epsilon = .0 + self._decay = 1. + self._leaf.stop_learning() + super().stop_learning() + + +class RandomInitQLearningLeafDecorator(QLearningLeafDecorator): + """ + A decorator that allows to randomly intialize the Q function + """ + + def __init__(self, leaf, low, high, distribution="uniform"): + """ + Initializes the decorator + + :leaf: An instance of QLearningLeaf + :low: The low bound for the initial Q function. + :high: The upper bound for the initial Q function + :distribution: The name of the distribution. + Can be either "normal" or "uniform". + In case the distribution is "normal", low and high will + be used to compute mean and std deviation of the normal. + + """ + QLearningLeafDecorator.__init__(self, leaf) + + n = self._leaf.get_n_actions() + self._distribution = distribution + self._low = low + self._high = high + if distribution == "normal": + mean = np.mean([low, high]) + std = np.std([low, high]) + self._leaf.set_q(np.random.normal(mean, std, n)) + else: + self._leaf.set_q(np.random.uniform(low, high, n)) + + def copy(self): + """ + Returns a copy of itself + """ + new = RandomInitQLearningLeafDecorator( + self._leaf.copy(), + self._low, + self._high, + self._distribution + ) + new.set_q(self._leaf.get_q()) + return new + + def stop_learning(self): + self._leaf.stop_learning() + super().stop_learning() + +class NoBuffersDecorator(QLearningLeafDecorator): + """ + A decorator that allows to avoid memory leaks due to the big number + of transitions recorded. Useful when a lot of trees are used with + algorithms that do not need their history (e.g. evolutionary algorithms) + """ + + def __init__(self, leaf): + """ + Initializes the decorator + + :leaf: An instance of QLearningLeaf + """ + QLearningLeafDecorator.__init__(self, leaf) + + def get_output(self, input_): + """ + Returns the output associated with the input + + :input_: The input vector (1D) + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + # Retrieve the output from the leaf + out = self._leaf.get_output(input_)[0] + # Delete the unnecessary elements in the buffers + # I.e. the ones whose reward has already been set + inputs, actions, rewards = self._leaf.get_buffers() + unnecessary = len(rewards) - 1 + if unnecessary > 0: + del inputs[:unnecessary] + del rewards[:unnecessary] + del actions[:unnecessary] + self._leaf.set_buffers(inputs, actions, rewards) + # return the output of the leaf + return out, self + + def copy(self): + """ + Returns a copy of itself + """ + new = NoBuffersDecorator( + self._leaf.copy() + ) + return new + + def stop_learning(self): + self._leaf.stop_learning() + super().stop_learning() + +class QLambdaDecorator(QLearningLeafDecorator): + """ + A decorator that implements Naive Q(lambda). + """ + + def __init__(self, leaf, decay): + """ + Initializes the decorator + + :leaf: The leaf + :decay: The decay factor: lambda * gamma + """ + QLearningLeafDecorator.__init__(self, leaf) + self._decay = decay + + self._eligibility_traces = np.zeros(len(self._leaf.get_q())) + self._visits = np.zeros(len(self._eligibility_traces)) + self._last_action = None + + def copy(self): + """ + Returns a copy of itself + """ + return QLambdaDecorator(self._leaf.copy(), self._decay) + + def get_output(self, input_): + """ + Computes the output of the leaf + + :input_: An array of input features + :returns: A numpy array whose last dimension has the size equal to + the dimensionality of the actions + """ + output = self._leaf.get_output(input_)[0] + self._last_action = output + self._eligibility_traces[self._last_action] += 1 + return output, self + + def set_reward(self, reward): + """ + Gives the reward to the leaf. + + :reward: The total reward given to the leaf (e.g. + for Q-learning it should be reward = r + gamma · Qmax(s')) + """ + q = self._leaf.get_q() + + for i in range(len(self._eligibility_traces)): + _learning_rate = self._leaf.get_lr() + self._visits[i] += 1 + if _learning_rate is None: + lr = 1 / self._visits[i] + else: + lr = _learning_rate + + delta = reward - q[i] + q[i] += lr * self._eligibility_traces[i] * delta + + self._eligibility_traces[i] *= self._decay + + self._leaf.set_q(q) + + def empty_buffers(self): + self._leaf.empty_buffers() + self._eligibility_traces = np.zeros(len(self._leaf.get_q())) + + def stop_learning(self): + self._leaf.stop_learning() + #print("jjjjjjj "+str(self._leaf.get_lr())) + super().stop_learning() + + def set_lr(self, new_lr): + super().set_lr(new_lr) + + def get_lr(self): + return super().get_lr() + + +class QLearningLeafFactory: + """ + A base class for the factories of leaves. + """ + + DECORATOR_DICT = { + "EpsilonGreedy": EpsilonGreedyQLearningLeafDecorator, + "RandomInit": RandomInitQLearningLeafDecorator, + "NoBuffers": NoBuffersDecorator, + "QLambda": QLambdaDecorator, + } + + def __init__(self, **kwargs): + """ + Initializes the factory + + :leaf_params: A dictionary containing all the parameters of the leaf + :decorators: A list of (decorator_name, **params) + """ + self._leaf_params = kwargs["leaf_params"] + self._decorators = kwargs["decorators"] + + for name, _ in self._decorators: + assert name in self.DECORATOR_DICT, \ + f"Unable to find the decorator {name}\n\ + Available decorators: {self.DECORATOR_DICT.keys()}" + + def create(self): + """ Creates a leaf and returns it """ + leaf = QLearningLeaf(**self._leaf_params) + for name, params in self._decorators: + leaf = self.DECORATOR_DICT[name](leaf, **params) + return leaf + + def get_trainable_parameters(self): + return [] + + +####################################################################### +# Constant leaves # +####################################################################### + + +class ConstantLeaf(Leaf): + """ + A leaf that does not learn + """ + + def __init__(self, action): + """ + Initializes the leaf + + :action: The action contained in the leaf + """ + Leaf.__init__(self) + + self._action = action + + def get_output(self, input_): + return self._action + + def get_code(self): + return f"out={self._action}" + + def set_reward(self, input_): + pass + + def get_value(self): + return 0 + + def __repr__(self): + return str(self._action) + + def get_trainable_parameters(self): + return ["action"] + + def set_params_from_list(self, params): + assert isinstance(params[0], int) + self._action = params[0] + + +class ConstantLeafFactory(): + """ + A Factory for constant leaves + """ + + def __init__(self, n_actions): + self._n_actions = n_actions + + def create(self, params=None): + #print(str(isinstance(params, int))+" sss "+str(type(params))) + if params is None: + action = np.random.randint(0, self._n_actions) + else: + action = params[0] if not isinstance(params, int) else params + return ConstantLeaf(action) + + def get_trainable_parameters(self): + return ["action"] + + +####################################################################### +# Dummy Leaves # +####################################################################### + + +class DummyLeafFactory(): + """ + A Factory for dummy leaves + """ + + def create(self): + return Leaf() + + def get_trainable_parameters(self): + return [] + + +####################################################################### +# Differentiable Leaves # +####################################################################### + + +class PPOLeaf(Leaf): + """A Leaf that implements PPO for discrete action spaces""" + + def __init__(self, n_actions): + """ + Initializes the leaf + + :n_actions: The number of actions that the leaf can perform + """ + Leaf.__init__(self) + + self._n_actions = n_actions + self.actions = torch.rand(n_actions, requires_grad=True) + self.sm = torch.nn.Softmax() + + def get_output(self, input_): + return self.sm(self.actions), self + + def get_params(self): + return self.actions + + def discretize(self): + return ConstantLeaf(torch.argmax(self.actions).detach().numpy()) + + def __repr__(self): + return str(self.sm(self.actions)) + + def __str__(self): + return repr(self) + + +class PPOLeafFactory(): + """ + A Factory for PPO leaves + """ + + def __init__(self, n_actions): + self._n = n_actions + + def create(self): + return PPOLeaf(self._n) + + def get_trainable_parameters(self): + return [] diff --git a/src/QD_MARL/utils.py b/src/QD_MARL/deprecated/utils.py similarity index 97% rename from src/QD_MARL/utils.py rename to src/QD_MARL/deprecated/utils.py index a2516325e..df83f9b91 100644 --- a/src/QD_MARL/utils.py +++ b/src/QD_MARL/deprecated/utils.py @@ -36,7 +36,10 @@ def get_map(n_jobs, debug=False): :returns: A function pointer """ + print(f"DEBUG: {debug}") + debug = True #TO FIX WHEN ENDED DEBUG if debug: + print(f"DEBUG: {debug}") def fcn(function, iterable, config): ret_vals = [] for i in iterable: diff --git a/src/QD_MARL/differentObservations.py b/src/QD_MARL/differentObservations.py index 003503360..8e3dea5f4 100644 --- a/src/QD_MARL/differentObservations.py +++ b/src/QD_MARL/differentObservations.py @@ -1,4 +1,5 @@ import numpy as np +from utils.print_outputs import * def compute_features_42(obs, n_allies, n_enemies): @@ -226,6 +227,7 @@ def compute_features_42_obs(obs, n_allies, n_enemies): def compute_features_34_old(obs, n_allies, n_enemies): + map_h = obs.shape[0] map_w = obs.shape[1] @@ -323,7 +325,7 @@ def compute_features_34_old(obs, n_allies, n_enemies): def compute_features_34_new(obs, n_allies, n_enemies): - map_h = obs.shape[0] + map_h = obs.shape[1] map_w = obs.shape[1] coordinates = tuple(obs[0, 0, 7:9]) @@ -425,7 +427,7 @@ def compute_features_34_new(obs, n_allies, n_enemies): enemy_presence.append(nondead[map_h_2 + y, map_w_2 + x]) new_features.extend(enemy_presence) - + return np.array(new_features) ''' diff --git a/src/QD_MARL/evaluations.py b/src/QD_MARL/evaluations.py new file mode 100644 index 000000000..9fab2ce81 --- /dev/null +++ b/src/QD_MARL/evaluations.py @@ -0,0 +1,306 @@ +import os +import sys + +sys.path.append(".") +import random +import time +from copy import deepcopy +from math import sqrt + +import differentObservations +import numpy as np +import pettingzoo +from agents import * +from algorithms import (grammatical_evolution, individuals, map_elites, + map_elites_Pyribs, mapElitesCMA_pyRibs) +from decisiontrees import (ConditionFactory, DecisionTree, + QLearningLeafFactory, RLDecisionTree) +from magent2.environments import battlefield_v5 +from pettingzoo.utils import aec_to_parallel, parallel_to_aec +from test_environments import * +from utils import * + + +def set_trees_to_train(trees, beginning_index,config): + trees_to_train = [] + for i in range(config['ge']['agents']): + if i + beginning_index >= config['ge']['pop_size']: + trees_to_train.append(trees[i]) + else: + trees_to_train.append(trees[beginning_index + i]) + return trees_to_train + +def set_set(config): + number_of_agents = config["ge"]["agents"] + number_of_sets = config["ge"]["sets"] + if config["sets"] == "random": + sets = [[] for _ in range(number_of_sets)] + set_full = [False for _ in range(number_of_sets)] + agents = [i for i in range(number_of_agents)] + + agents_per_set = number_of_agents // number_of_sets + surplus = number_of_agents // number_of_sets + + while not all(set_full): + set_index = random.randint(0, number_of_sets -1) + if not set_full[set_index]: + random_index = random.randint(0, len(agents) -1) + sets[set_index].append(agents[random_index]) + del agents[random_index] + if len(sets[set_index]) == agents_per_set: + set_full[set_index] = True + + if surplus > 0: + while len(agents) != 0: + set_index = random.randint(0, number_of_sets -1) + random_index = random.randint(0, len(agents) -1) + sets[set_index].append(agents[random_index]) + del agents[random_index] + + for set_ in sets: + set_.sort() + + config["sets"] = sets + return config["sets"] + +def evaluate(trees, config, replay_buffer, map_, init_eval=False, coach = None): + compute_features = getattr(differentObservations, f"compute_features_{config['observation']}") + policy = None + # Load the environment + config["sets"] = set_set(config) + # Setup the agents + agents = {} + + env = battlefield_v5.env(**config['environment']).unwrapped + env.reset()#This reset lead to the problem + + for agent_name in env.agents: + agent_squad = "_".join(agent_name.split("_")[:-1]) + if agent_squad == config["team_to_optimize"]: + agent_number = int("_".join(agent_name.split("_")[1])) + + # Search the index of the set in which the agent belongs + set_ = 0 + for set_index in range(len(config["sets"])): + if agent_number in config["sets"][set_index]: + set_ = set_index + + # Initialize trining agent + agents[agent_name] = Agent(agent_name, agent_squad, set_, trees[set_], None, True) + else: + # Initialize random or policy agent + agents[agent_name] = Agent(agent_name, agent_squad, None, None, policy, False) + + red_agents = 12 + blue_agents = 12 + + count_attack_penalty = 0 + count_moves_penalty = 0 + count_dead_penalty = 0 + count_good_penalty = 0 + + for i in range(config["training"]["episodes"]): + for agent in agents: + if agents[agent].to_optimize(): + agents[agent].new_episode() + # Seed the environment + env.reset(seed=i) + np.random.seed(i) + + # Set variabler for new episode + for agent_name in agents: + if agents[agent_name].to_optimize(): + agents[agent_name].new_episode() + + agents_features = {} + for agent_name in agents: + #print_debugging(f"Agent blue_0 action: {actions['blue_0']}") + actions = {agent: env.action_spaces[agent].sample() for agent in agents} + obs, rew, done, trunc, info = env.step(actions) + agent = agents[agent_name] + obs_ = obs[agent_name] + rew_ = rew[agent_name] + done_ = done[agent_name] + trunc_ = trunc[agent_name] + + if rew_ > -10: count_moves_penalty += 1 + elif rew_ in range(-50,-150): count_dead_penalty += 1 + elif rew_< -500: count_attack_penalty += 1 + elif rew_ > 0 and rew < 10: count_good_penalty += 1 + if done_ or trunc_: + print_info(f"Agent {agent_name} is dead at the of the episode {i}") + + if agent.to_optimize(): + # Register the reward + agent.set_reward(rew_) + # print_debugging(f"Agent {agent_name} reward: {rew_}") + + if not done_ and not trunc_: # if the agent is alive + if agent.to_optimize(): + # compute_features(observation, allies, enemies) + agents_features[agent_name] = compute_features(obs_, blue_agents, red_agents) + if agent.get_squad() == 'blue': + action = agent.get_output(agents_features[agent_name]) + else: + action = agent.get_output(agents_features[agent_name]) + # print_debugging(f"Agent {agent_name} action: {action}") + else: + if agent.has_policy(): + if agent.get_squad() == 'blue': + action = agent.get_output(compute_features(obs_, blue_agents, red_agents)) + else: + action = agent.get_output(compute_features(obs_, red_agents, blue_agents)) + else: + action = env.action_space(agent_name).sample() + #action = np.random.randint(21) + else: # update the number of active agents + if agent.get_squad() == 'red': + red_agents -= 1 + else: + blue_agents -= 1 + + actions[agent_name] = action + env.step(actions) + # print_debugging(f"End episode {i}") + # print_debugging(f"counts: attack {count_attack_penalty}, moves {count_moves_penalty}, dead {count_dead_penalty}, good {count_good_penalty}") + env.close() + + if init_eval: + round_agents = [] + for agent_name in agents: + agent = agents[agent_name] + if agent.to_optimize(): + round_agents.append(agent) + print_debugging(f"End evaluation team, red agents: {red_agents}, blue agents: {blue_agents}") + return round_agents, agents_features + else: + scores = [] + actual_trees = [] + for agent_name in agents: + if agents[agent_name].to_optimize(): + scores.append(agents[agent_name].get_score_statistics(config['statistics']['agent'])) + actual_trees.append(agents[agent_name].get_tree()) + return scores, actual_trees + +def init_eval(trees, config, replay_buffer, map_, coach = None): + + number_of_possible_teams = int(len(trees)/(config["ge"]["agents"])) + round_agents = [] + team_selection = {} + for index_ in range(number_of_possible_teams): + print_debugging(f"Evaluating team {index_}") + round_trees = set_trees_to_train(trees, index_ * config["ge"]["agents"], config) + round_agents_, agent_features = evaluate(round_trees, config, replay_buffer, map_, init_eval=True, coach=coach) + round_agents.append(round_agents_) + team_selection[index_] = agent_features + features = get_pop_features(team_selection) + # coach training + if coach is not None: + for index_ in range(len(features)): + coach.set_reward(features[index_]) + scores = [] + actual_trees = [] + round_agents = [item for sublist in round_agents for item in sublist] + for agent_name in round_agents: + scores.append(agent_name.get_score_statistics(config['statistics']['agent'])) + actual_trees.append(agent_name.get_tree()) + return scores, actual_trees + +def get_pop_features(features_dict): + features_sum = [] + for n in features_dict: + for agent_name in features_dict[n]: + features_sum.append(features_dict[n][agent_name]) + for i in range(len(features_sum)): + agent = np.array(features_sum[i]) + features_sum[i] = np.sum(agent, axis=0) + return features_sum + + +# def evaluate(trees, config, replay_buffer, map_): +# compute_features = getattr(differentObservations, f"compute_features_{config['observation']}") +# policy = None +# # Load the environment +# env = battlefield_v5.env(**config['environment']).unwrapped +# env.reset()#This reset lead to the problem +# config["sets"] = set_set(config) +# # Setup the agents +# agents = {} +# for agent_name in env.agents: +# agent_squad = "_".join(agent_name.split("_")[:-1]) +# if agent_squad == config["team_to_optimize"]: +# agent_number = int("_".join(agent_name.split("_")[1])) + +# # Search the index of the set in which the agent belongs +# set_ = 0 +# for set_index in range(len(config["sets"])): +# if agent_number in config["sets"][set_index]: +# set_ = set_index + +# # Initialize trining agent +# agents[agent_name] = Agent(agent_name, agent_squad, set_, trees[set_], None, True) +# else: +# # Initialize random or policy agent +# agents[agent_name] = Agent(agent_name, agent_squad, None, None, policy, False) +# for i in range(config["training"]["episodes"]): + +# # Seed the environment +# env.reset(seed=i) +# np.random.seed(i) +# env.reset() + +# # Set variabler for new episode +# for agent_name in agents: +# if agents[agent_name].to_optimize(): +# agents[agent_name].new_episode() +# red_agents = 12 +# blue_agents = 12 + +# for index, agent_name in enumerate(env.agents): +# actions = {agent: env.action_spaces[agent].sample() for agent in env.agents} +# obs, rew, done, trunc, info = env.step(actions) +# agent = agents[agent_name] +# #f = compute_features(obs, blue_agents, red_agents) + +# obs = obs[agent._name] +# rew = rew[agent._name] +# done = done[agent._name] +# trunc = trunc[agent._name] + +# if agent.to_optimize(): +# # Register the reward +# agent.set_reward(rew) + +# #action = env.action_space(agent_name).sample() + +# if not done and not trunc: # if the agent is alive +# if agent.to_optimize(): +# # compute_features(observation, allies, enemies) +# if agent.get_squad() == 'blue': +# action = agent.get_output(compute_features(obs, blue_agents, red_agents)) +# else: +# action = agent.get_output(compute_features(obs, red_agents, blue_agents)) +# else: +# if agent.has_policy(): +# if agent.get_squad() == 'blue': +# action = agent.get_output(compute_features(obs, blue_agents, red_agents)) +# else: +# action = agent.get_output(compute_features(obs, red_agents, blue_agents)) +# else: +# action = env.action_space(agent_name).sample() +# #action = np.random.randint(21) +# else: # update the number of active agents +# if agent.get_squad() == 'red': +# red_agents -= 1 +# else: +# blue_agents -= 1 +# actions[agent_name] = action +# env.step(actions) +# env.close() +# scores = [] +# actual_trees = [] +# for agent_name in agents: +# if agents[agent_name].to_optimize(): +# scores.append(agents[agent_name].get_score_statistics(config['statistics']['agent'])) +# actual_trees.append(agents[agent_name].get_tree()) +# return scores, actual_trees \ No newline at end of file diff --git a/src/QD_MARL/evolve_tree_me.py b/src/QD_MARL/evolve_tree_me.py index 7a0139702..4ebd3c382 100644 --- a/src/QD_MARL/evolve_tree_me.py +++ b/src/QD_MARL/evolve_tree_me.py @@ -21,7 +21,7 @@ from tqdm import tqdm from copy import deepcopy from algorithms import grammatical_evolution, map_elites -from decisiontreelibrary import QLearningLeafFactory, ConditionFactory, \ +from decisiontrees import QLearningLeafFactory, ConditionFactory, \ RLDecisionTree from joblib import Parallel, delayed diff --git a/src/QD_MARL/get_interpretability.py b/src/QD_MARL/get_interpretability.py index 90fe4841f..cd82a9c55 100644 --- a/src/QD_MARL/get_interpretability.py +++ b/src/QD_MARL/get_interpretability.py @@ -1,5 +1,5 @@ from importlib.resources import path -from experiment_launchers.pipeline import * +# from experiment_launchers.pipeline import * import pickle import re import os @@ -161,7 +161,7 @@ def calc_complexity_from_string(code, env, tree2Node=tree2Node): for episode in (range(5)): mean_reward.append(0) - e = gym.make(env) + e = env e.seed(episode) obs = e.reset() done = False @@ -319,6 +319,28 @@ def convert(string): root = nodes[id_] return root +def fit_tree(tree): + tree = str(tree) + print(tree) + tree = tree.replace(" [","[") + tree = tree.replace("[0]","0") + tree = tree.replace("[1]","1") + tree = tree.replace("[2]","2") + tree = tree.replace("[3]","3") + tree = tree.replace("[4]","4") + tree = tree.replace("[5]","5") + tree = tree.replace("input","_in") + tree = tree.replace("Node_m_to_p object at ","") + tree = re.sub("\(.*?\)","",tree) + #tree = re.sub("\<.*?\>","",tree) + tree = tree.replace("\nOneHotEncoder\n\n","") + tree = tree.replace("RLDecisionTree\n","") + tree = tree[:tree.rfind('\n')] + tree = tree[:tree.rfind('\n')] + tree = tree[:tree.rfind('\n')] + print(tree) + return tree + if __name__ == "__main__": #path_dir = "/home/matteo/marl_dts/src/logs/CartPole-ME_pyRibs_3/28-05-2022_18-38-45_xvkuotpf/" path_dir = "/home/matteo/marl_dts/src/logs/MountainCar-ME_pyRibs_7/29-05-2022_18-27-08_jlnjexmc" diff --git a/src/QD_MARL/marl_qd_launcher.py b/src/QD_MARL/marl_qd_launcher.py index caf3fc417..d1efe56c9 100644 --- a/src/QD_MARL/marl_qd_launcher.py +++ b/src/QD_MARL/marl_qd_launcher.py @@ -6,15 +6,21 @@ import time from copy import deepcopy from math import sqrt - +from test_environments import * import numpy as np import pettingzoo -import utils -from algorithms import grammatical_evolution, map_elites_Pyribs +from pettingzoo.utils import parallel_to_aec, aec_to_parallel +import differentObservations +from utils import * +from algorithms import grammatical_evolution, map_elites, mapElitesCMA_pyRibs, map_elites_Pyribs, individuals from agents import * -from decisiontreelibrary import (ConditionFactory, QLearningLeafFactory, - RLDecisionTree) +from decisiontrees import (ConditionFactory, QLearningLeafFactory, + RLDecisionTree, DecisionTree) from magent2.environments import battlefield_v5 +from evaluations import * +from decisiontrees.leaves import * +import get_interpretability + def get_map_elite(config): """ @@ -24,19 +30,21 @@ def get_map_elite(config): :log_path: a path to the log directory """ # Setup GE - me_config = config["me"] - + me_config = config["me"]["kwargs"] # Build classes of the operators from the config file me_config["c_factory"] = ConditionFactory() me_config["l_factory"] = QLearningLeafFactory( - config["leaves"]["params"], - config["leaves"]["decorators"] + config["QLearningLeafFactory"]["kwargs"]["leaf_params"], + config["QLearningLeafFactory"]["kwargs"]["decorators"] ) + + # me = map_elites.MapElites(**me_config) me = map_elites_Pyribs.MapElites_Pyribs(**me_config) + # me = mapElitesCMA_pyRibs.MapElitesCMA_pyRibs(**me_config) return me -def pretrain_tree(t, rb): +def pretrain_tree(tree, rb): """ Pretrains a tree @@ -44,277 +52,137 @@ def pretrain_tree(t, rb): :rb: The replay buffer :returns: The pretrained tree """ - if t is None: + if tree is None: return None for e in rb: - t.empty_buffers() + tree.empty_buffers() if len(e) > 0: for s, a, r, sp in e: - t.force_action(s, a) - t.set_reward(r) - t.set_reward_end_of_episode() - return t - -def evaluate(trees, config): - # Check whether the phenotype is valid - for tree in trees: - if tree is None: - return -10**3, None - - # Re-import the environments here to avoid problems with parallelization - import differentObservations - #from manual_policies import Policies - import numpy as np - - # Load the function used to computer the features from the observation - compute_features = getattr(differentObservations, f"compute_features_{config['observation']}") - - # Load manual policy if present - policy = None - #if config['manual_policy']: - # policy = Policies(config['manual_policy']) - - # Load the environment - env = battlefield_v5.env(**config['environment']).unwrapped - env.reset()#This reset lead to the problem - - # Set tree and policy to agents - agents = {} - for agent_name in env.agents: - agent_squad = "_".join(agent_name.split("_")[:-1]) - if agent_squad == config["team_to_optimize"]: - agent_number = int("_".join(agent_name.split("_")[1])) + tree.force_action(s, a) + tree.set_reward(r) + tree.set_reward_end_of_episode() + return tree - # Search the index of the set in which the agent belongs - set_ = 0 - for set_index in range(len(config["sets"])): - if agent_number in config["sets"][set_index]: - set_ = set_index - - # Initialize trining agent - agents[agent_name] = Agent(agent_name, agent_squad, set_, trees[set_], None, True) - else: - # Initialize random or policy agent - agents[agent_name] = Agent(agent_name, agent_squad, None, None, policy, False) - - # Start the training - for i in range(config["training"]["episodes"]): - - # Seed the environment - env.reset(seed=i) - np.random.seed(i) - env.reset() - - # Set variabler for new episode - for agent_name in agents: - if agents[agent_name].to_optimize(): - agents[agent_name].new_episode() - red_agents = 12 - blue_agents = 12 - - # tree.empty_buffers() # NO-BUFFER LEAFS - # Iterate over all the agents - for index, agent_name in enumerate(env.agents): - actions = {agent: env.action_space(agent).sample() for agent in env.agents} - obs, rew, done, trunc, _ = env.step(actions) - - agent = agents[agent_name] - - if agent.to_optimize(): - # Register the reward - agent.set_reward(rew) - - action = env.action_space(agent_name) - if not done and not trunc: # if the agent is alive - if agent.to_optimize(): - # compute_features(observation, allies, enemies) - if agent.get_squad() == 'blue': - action = agent.get_output(compute_features(obs, blue_agents, red_agents)) - else: - action = agent.get_output(compute_features(obs, red_agents, blue_agents)) - else: - if agent.has_policy(): - if agent.get_squad() == 'blue': - action = agent.get_output(compute_features(obs, blue_agents, red_agents)) - else: - action = agent.get_output(compute_features(obs, red_agents, blue_agents)) - else: - action = env.action_space(agent_name).sample() - #action = np.random.randint(21) - else: # update the number of active agents - if agent.get_squad() == 'red': - red_agents -= 1 - else: - blue_agents -= 1 - - env.step(action) - - env.close() - - scores = [] - actual_trees = [] - for agent_name in agents: - if agents[agent_name].to_optimize(): - scores.append(agents[agent_name].get_score_statistics(config['statistics']['agent'])) - actual_trees.append(agents[agent_name].get_tree()) - return scores, actual_trees - def produce_tree(config, log_path=None, extra_log=False, debug=False, manual_policy=False): - - number_of_agents = config["ge"]["agents"] - number_of_sets = config["ge"]["sets"] - + # Setup GE + ge_config = config["ge"] + me_config = config["me_config"] + gram_dir = None + for op in ["mutation", "crossover", "selection", "replacement"]: + ge_config[op] = getattr( + grammatical_evolution, ge_config[op]["type"] + )(**ge_config[op]["params"]) + # ge[i] -> set_i + ge = grammatical_evolution.GrammaticalEvolution(**ge_config, logdir=gram_dir) # setup log files evolution_dir = os.path.join(log_path, "Evolution_dir") os.makedirs(evolution_dir , exist_ok=False) trees_dir = os.path.join(log_path, "Trees_dir") os.makedirs(trees_dir , exist_ok=False) - me = get_map_elite(config) # TODO - print(me) + # Retrieve the map function from utils + map_ = utils.get_map(config["training"]["jobs"], debug) + + # setup map elite and trees + me = get_map_elite(me_config) + init_trees = me._init_pop() + # Transform the me-trees in RLDecisionTree + trees = map_(RLDecisionTree, init_trees, config["training"]["gamma"]) + + # init coach + # coach_tree = RLDecisionTree(init_trees_[-1], config["coach_training"]["gamma"]) + # coach = CoachAgent("coach", None, coach_tree) + coach = None + # Init replay buffer + replay_buffer = [] + # Pretrain the trees + trees = [pretrain_tree(t, replay_buffer) for t in trees] + # Initialize best individual + best, best_fit, new_best = None, -float("inf"), False - gram_dir = None - if extra_log: - gram_dir = os.path.join(log_path, "grammatical_evolution") - os.makedirs(gram_dir, exist_ok=False) - # Setup sets - if config["sets"] == "random": - sets = [[] for _ in range(number_of_sets)] - set_full = [False for _ in range(number_of_sets)] - agents = [i for i in range(number_of_agents)] - - agents_per_set = number_of_agents // number_of_sets - surplus = number_of_agents // number_of_sets - - while not all(set_full): - set_index = random.randint(0, number_of_sets -1) - if not set_full[set_index]: - random_index = random.randint(0, len(agents) -1) - sets[set_index].append(agents[random_index]) - del agents[random_index] - if len(sets[set_index]) == agents_per_set: - set_full[set_index] = True - - if surplus > 0: - while len(agents) != 0: - set_index = random.randint(0, number_of_sets -1) - random_index = random.randint(0, len(agents) -1) - sets[set_index].append(agents[random_index]) - del agents[random_index] - - for set_ in sets: - set_.sort() - - config["sets"] = sets - + + with open(os.path.join(log_path, "sets.log"), "w") as f: f.write(f"{config['sets']}\n") - # Setup GE - ge_config = config["ge"] + with open(os.path.join(log_path, "log.txt"), "a") as f: + f.write(f"Generation Min Mean Max Std\n") - # Build classes of the operators from the config file - for op in ["mutation", "crossover", "selection", "replacement"]: - ge_config[op] = getattr( - grammatical_evolution, ge_config[op]["type"] - )(**ge_config[op]["params"]) - - # ge[i] -> set_i - ge = grammatical_evolution.GrammaticalEvolution(**ge_config, logdir=gram_dir) - # Retrive the map function from utils - map_ = utils.get_map(config["training"]["jobs"], debug) - # Initialize best individual for each agent - best = [None for _ in range(number_of_agents)] - best_fit = [-float("inf") for _ in range(number_of_agents)] - new_best = [False for _ in range(number_of_agents)] - - print(f"{'Generation' : <10} {'Set': <10} {'Min': <10} {'Mean': <10} {'Max': <10} {'Std': <10}") + # Compute the fitnesses + # We need to return the trees in order to retrieve the + # correct values for the leaves when using the + # parallelization + print_debugging("Evaluating initial population") + fitnesses, trees = init_eval(trees, config, replay_buffer, map_, coach = None) + + print_debugging((fitnesses, len(fitnesses), len(trees))) + + amax = np.argmax(fitnesses) + max_ = fitnesses[amax] + me.tell(fitnesses, trees) + + # print_info(f"{'Generation' : <10} {'Min': <10} {'Mean': <10} \ + # {'Max': <10} {'Std': <10} {'Invalid': <10} {'Best': <10}") + + # Check whether the best has to be updated # Iterate over the generations - for gen in range(config["training"]["generations"]): + for i in range(config["training"]["generations"]): + # Retrieve the current population - # Retrive the current population - pop = ge.ask() + #team_selection(me, config) - # Convert the genotypes in phenotypes - trees = [map_(utils.genotype2phenotype, pop[i], config) for i in range(number_of_sets)] - # Form different groups of trees - squads = [[trees[j][i] for j in range(number_of_sets)] for i in range(config['ge']['pop_size'])] + trees = me.ask(random = False, best = True) + trees = map_(RLDecisionTree, trees, config["training"]["gamma"]) + print_debugging(len(trees)) # Compute the fitnesses - # We need to return the trees in order to retrive the - # correct values for the leaves when using the parallelization - return_values = map_(evaluate, squads, config) - agents_fitness = [ [] for _ in range(number_of_agents)] - agents_tree = [ [] for _ in range(number_of_agents)] - - # Store trees and fitnesses - for values in return_values: - for i in range(number_of_agents): - agents_fitness[i].append(values[0][i]) - agents_tree[i].append(values[1][i]) - - # Check whether the best, for each agent, has to be updated - amax = [np.argmax(agents_fitness[i]) for i in range(number_of_agents)] - max_ = [agents_fitness[i][amax[i]] for i in range(number_of_agents)] - - for i in range(number_of_agents): - if max_[i] > best_fit[i]: - best_fit[i] = max_[i] - best[i] = agents_tree[i][amax[i]] - new_best[i] = True - - tree_text = f"{best[i]}" - utils.save_tree(best[i], trees_dir, f"best_agent_{i}") - with open(os.path.join(trees_dir, f"best_agent_{i}.log"), "w") as f: - f.write(tree_text) - - # Calculate fitnesses for each set - sets_fitness = [ [] for _ in range(number_of_sets)] - for index, set_ in enumerate(config["sets"]): - - set_agents_fitnesses = [] - for agent in set_: - set_agents_fitnesses.append(agents_fitness[agent]) - - set_agents_fitnesses = np.array(set_agents_fitnesses) - - # Calculate fitness for each individual in the set - sets_fitness[index] = [getattr(np, config['statistics']['set']['type'])(a=set_agents_fitnesses[:, i], **config['statistics']['set']['params']) for i in range(set_agents_fitnesses.shape[1])] + # We need to return the trees in order to retrieve the + # correct values for the leaves when using the + # parallelization + fitnesses, trees= evaluate(trees, config, replay_buffer, map_) + print_debugging((fitnesses, len(fitnesses), len(trees))) + # Check whether the best has to be updated + amax = np.argmax(fitnesses) + max_ = fitnesses[amax] + + if max_ > best_fit: + best_fit = max_ + best = trees[amax] + new_best = True # Tell the fitnesses to the GE - ge.tell(sets_fitness) - - # Compute stats for each agent - agent_min = [np.min(agents_fitness[i]) for i in range(number_of_agents)] - agent_mean = [np.mean(agents_fitness[i]) for i in range(number_of_agents)] - agent_max = [np.max(agents_fitness[i]) for i in range(number_of_agents)] - agent_std = [np.std(agents_fitness[i]) for i in range(number_of_agents)] - - for i in range(number_of_agents): - #print(f"{gen: <10} agent_{i: <4} {agent_min[i]: <10.2f} {agent_mean[i]: <10.2f} {agent_max[i]: <10.2f} {agent_std[i]: <10.2f}") - with open(os.path.join(evolution_dir, f"agent_{i}.log"), "a") as f: - f.write(f"{gen: <10} {agent_min[i]: <10.2f} {agent_mean[i]: <10.2f} {agent_max[i]: <10.2f} {agent_std[i]: <10.2f}\n") - - new_best[i] = False - - # Compute states for each set - set_min = [np.min(sets_fitness[i]) for i in range(number_of_sets)] - set_mean = [np.mean(sets_fitness[i]) for i in range(number_of_sets)] - set_max = [np.max(sets_fitness[i]) for i in range(number_of_sets)] - set_std = [np.std(sets_fitness[i]) for i in range(number_of_sets)] - - for i in range(number_of_sets): - print(f"{gen: <10} set_{i: <4} {set_min[i]: <10.2f} {set_mean[i]: <10.2f} {set_max[i]: <10.2f} {set_std[i]: <10.2f}") - with open(os.path.join(evolution_dir, f"set_{i}.log"), "a") as f: - f.write(f"{gen: <10} {set_min[i]: <10.2f} {set_mean[i]: <10.2f} {set_max[i]: <10.2f} {set_std[i]: <10.2f}\n") + me.tell(fitnesses) + + # Compute stats + fitnesses = np.array(fitnesses) + valid = fitnesses != -100000 + min_ = np.min(fitnesses[valid]) + mean = np.mean(fitnesses[valid]) + max_ = np.max(fitnesses[valid]) + std = np.std(fitnesses[valid]) + invalid = np.sum(fitnesses == -100000) + print_info(f"{'Generation' : <10} {'Min': <10} {'Mean': <10} \ + {'Max': <10} {'Std': <10} {'Invalid': <10} {'Best': <10}") + print_info(f"{i: <10} {min_: <10.4f} {mean: <10.4f} \ + {max_: <10.4f} {std: <10.4f} {invalid: <10} {best_fit: <10.4f}") + + # Update the log file + with open(os.path.join(log_path, "log.txt"), "a") as f: + f.write(f"{i} {min_} {mean} {max_} {std} {invalid}\n") + if new_best: + f.write(f"New best: {best}; Fitness: {best_fit}\n") + with open(join("best_tree.mermaid"), "w") as f: + f.write(str(best)) + new_best = False + + return trees - return best if __name__ == "__main__": import argparse import json import shutil - + import yaml import utils parser = argparse.ArgumentParser() parser.add_argument("config", help="Path of the config file to use") @@ -322,9 +190,14 @@ def produce_tree(config, log_path=None, extra_log=False, debug=False, manual_pol parser.add_argument("--log", action="store_true", help="Log flag") parser.add_argument("seed", type=int, help="Random seed to use") args = parser.parse_args() - + print_info("Launching Quality Diversity MARL") + print_configs("Environment configurations file: ", args.config) + if args.debug: + print_configs("DEBUG MODE") + # Load the config file config = json.load(open(args.config)) + # Set the random seed random.seed(args.seed) np.random.seed(args.seed) @@ -339,15 +212,17 @@ def produce_tree(config, log_path=None, extra_log=False, debug=False, manual_pol with open(join("seed.log"), "w") as f: f.write(str(args.seed)) - best = produce_tree(config, log_path, args.log, args.debug) + squad = produce_tree(config, log_path, args.log, args.debug) import logging logging.basicConfig(filename=join("output.log"), level=logging.INFO, format='%(asctime)s %(message)s', filemode='w') logger=logging.getLogger() + index = 0 + + for player in squad: + print_info(player) + + # return interpretability(best, config, log_path, index, logger) - for index, tree in enumerate(best): - print(f"\nagent_{index}:\n{tree}") - logger.info(f"\nagent_{index}:\n{tree}") - \ No newline at end of file diff --git a/src/QD_MARL/test_environments.py b/src/QD_MARL/test_environments.py new file mode 100644 index 000000000..99182a28b --- /dev/null +++ b/src/QD_MARL/test_environments.py @@ -0,0 +1,90 @@ +from magent2.environments import battlefield_v5 +import pettingzoo +from pettingzoo.utils import BaseParallelWrapper +from pettingzoo.utils.conversions import parallel_to_aec, aec_to_parallel +import numpy as np +import random +import pandas as pd +import os +import time +from utils.print_outputs import * + +''' +['__annotations__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', +'__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', +'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', +'__str__', '__subclasshook__', '__weakref__', '_all_handles', '_calc_obs_shapes', '_calc_state_shape', '_compute_observations', +'_compute_rewards', '_compute_terminates', '_ezpickle_args', '_ezpickle_kwargs', '_minimap_features', '_renderer', +'_zero_obs', 'action_space', 'action_spaces', 'agents', 'base_state', 'close', 'env', 'extra_features', 'frames', +'generate_map', 'handles', 'leftID', 'map_size', 'max_cycles', 'max_num_agents', 'metadata', 'minimap_mode', 'num_agents', +'observation_space', 'observation_spaces', 'possible_agents', 'render', 'render_mode', 'reset', 'rightID', 'seed', 'state', +'state_space', 'step', 'team_sizes', 'unwrapped'] +''' + +class Quick_Test(): + def __init__(self, config) -> None: + self._env = battlefield_v5.env(**config['environment']).unwrapped + print_debugging(self._env.metadata) + self._episodes = 100000 + + def run_env_test(self): + + total_reward = 0 + completed_episodes = 0 + render = True + + while completed_episodes < self._episodes: + self._env.reset() + for agent in self._env.agents: + if render: + self._env.render() + actions = {agent: self._env.action_spaces[agent].sample() for agent in self._env.agents} + obs, reward, termination, truncation, _ = self._env.step(actions) + + obs_ = obs[agent] + rew = reward[agent] + term = termination[agent] + trunc = truncation[agent] + + print_debugging(rew) + + total_reward += rew + if term or trunc: + action = None + elif isinstance(obs, dict) and "action_mask" in obs: + action = random.choice(np.flatnonzero(obs["action_mask"]).tolist()) + else: + action = self._env.action_space(agent).sample() + actions[agent] = action + self._env.step(actions) + print_debugging(self._env.num_agents) + completed_episodes += 1 + + if render: + self._env.close() + + print("Average total reward", total_reward / self._episodes) + return 0 + +class Battlefield(): + def __init__(self, config) -> None: + self._env = battlefield_v5.env(**config['environment']).unwrapped + + +if __name__ == "__main__": + import argparse + import json + import shutil + import yaml + import utils + + # Load the config file + file = "src/QD_MARL/configs/battlefield.json" + config = json.load(open(file)) + + # Set the random seed + random.seed(1) + np.random.seed(1) + + quick_test = Quick_Test(config) + quick_test.run_env_test() \ No newline at end of file diff --git a/src/QD_MARL/utils/__init__.py b/src/QD_MARL/utils/__init__.py new file mode 100644 index 000000000..91d0ee3d5 --- /dev/null +++ b/src/QD_MARL/utils/__init__.py @@ -0,0 +1,2 @@ +from .utils import * +from .print_outputs import * \ No newline at end of file diff --git a/src/QD_MARL/utils/__pycache__/__init__.cpython-310.pyc b/src/QD_MARL/utils/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04b08facd90b19084c48a560fc097839c1db0c57 GIT binary patch literal 235 zcmYjL%L>9U5KO9qh`sp}6?Eyzi}*l~D)n_Wlr~_bZA!9H{3L&=S5N+eC)av$U}u=! zS$4JWcNk%Jn#z#$`8EIW$jqp0k0@m+&)DIooYdR1pz-1~3V6l)o4~7D4pn9=zci{q zp&gbE?Sq*Xg=%^WIO|-(CBDVs2$ni=xh&Aau~1mRI<_bcL`Gj^f(z}8mDDuZV7nBN pQP&tHSd#$R^?bL2`W^_OXkCb5d9T{zT&FIRlka1;Nz`Ut{sCLTJ?a1e literal 0 HcmV?d00001 diff --git a/src/QD_MARL/utils/__pycache__/__init__.cpython-311.pyc b/src/QD_MARL/utils/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d1ddd51f41bb47dcd15146825ee0c41319ff7aeb GIT binary patch literal 276 zcmZ3^%ge<81pA}ZQVoIhV-N=hn4pZ$VnD`ph7^Vr#vFza2+atjnSvQKnO`yjB{dmu zF>3i~GTmY=Ey>I&zQtQml$lo&pI=&1P+C&FlHoJRq+eC%v UZ!m~lfT0@uV~i zy>-w04Xwn>{-yiM6VH3=6X#r)kqMA8Y~A1QoNN2s-?``5PV4mwfy4jqtMHmh$nWSZ zA38YqU}Qc3BaAM{nDhwKnEr$?{Zt!MW-#*!8Eed9C19P|tPE_h3abK}tj6lV7HhCI z;1XMBSAcCs_etyO9}t&#Q^e(if^!c>b{8NdunMj~A-V>Ub%<&}L=&QE5Xl;sVN_UY zTxIsS#>(S5tBf103MO&rZzEgD*af72PiAvw}WRbn8~(UDD0?#MnIeSYct` zj=k2Jw1Xg-#9q~2Re|s=;kvosto-7UT_k7p& zV8U;EJ-Fbz`=h}(vh=X+dws7b%UySS)a#*X?6ms>X}DguBaQ9hpexHG526nGp8J&W ztFSQ&E|~K}8i&q&ay+^DxzkOjc^oDpb4E!d5@&CciI6)FcsdWKqLb!43t^HkKKi;F zJmAsK6A?NYpE}^~wf%vUi)fKKM?6YIkmlkj7uk)YU$_SAVK-#21K7l%|909`-S>}j z;)5N0cpX5HIXR=}C-&Cvovpn)Tl+0t+6*L#C=R8bq{q_E1V2Rg@01Jc z6^O-O1Gs{bQQtaM{tL_Fd|-M=&zK*n*g#c??P)HNYnNGp?{%$+q|GnNh__gG}Z1YEowhFrp+4& zYXH)ir%@vHcyc6l$nL2ojR~J+()#fjc_~rMQC5p$gq)}RRhW2&Iqg#ZF3^AG$~WLD zXyn&`c?|(8$2Sq)0BDu@n`q+>E~&)tfl|reLhEgWCc<@u=UvB@vU;HmR3}j~!xi}! z!g~O#DU+t^_|>%4F*fetD%#cxZ7lpgpUps>%3i5^e|2?6-`g3kCa(N4;wpu>C^<}3 z5tObER@YVP485IxZ+|su*#9f<+ht9sX>u6NvL-&N)P498>Wa_Wit4KWLmdyZd?B)T zo~ye77IZYe9z`>&w#GL^H6zEGbKGE#d2qaashL$YyiFRK-LUn) H&YJ!wFj7z- literal 0 HcmV?d00001 diff --git a/src/QD_MARL/utils/__pycache__/print_outputs.cpython-311.pyc b/src/QD_MARL/utils/__pycache__/print_outputs.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e910a3dd79a82eab5f4473ff45675ae3f4b2518f GIT binary patch literal 4076 zcmeHKO>omj6kge~V@nC4E&qQt(6j^!CQV8L(_xy#j_t%ai5(MgNEF+05(nD~E192^ zgAZJoX*)f%!<7T2FkCpm4Q4XEl*uJt#vU@7>8YpQ(n(J__3g?Qn1qab&r194+xPUI zr2XEmR=+hh2?)yJ{*$RaHiZ77ooeAL!fF_Vdq_qymq9UPK|Ul~?jYIv(1Oqts78r# zGJglfEV2#ID%$~hxdG57I{@vn6R<%R039+HL9OE7)G?H-__KzqbXL!Ua1W&rM5Qq~ zh-iU$R*1$!EE`0!KpcC_3006AVxsJbHOkIdlPtux$s)vW)a^+nqhNL3wtwvG*ICwd z7|j`@`HD5|?MAQCv}Nb{{bQ#Y8gfTA)Y_~&Bq zx}&+YmUCZ-t6B=Xhp;l6N@~4I9;+$0FRl6W39#YJ$zhK)gwxmKTFR~Bq#L>yy2C+t zUQ1_G_Z&{=G)c*8b9qfYHFpcsOkiLILG1@9n8$L{?n2)?ux(GlXLLd8TV~zu`wD?& zac9A^Ebgen_PvDwJ@C$I@R~%RccOJw2Dpbbz-pPT3g~_{T}%xX9zuO$YVMl}YN|kx zxrgBU#Po-Bv4rLwE&g*Y7tXarTCKW6hQZO&*_3Y0DK~V7s^KZx#XG>=c<5bJ+FON> zKu}u%3g}PqlahGgjp%wUx`=qBBp!Jqo_H;uc+^kC&XU+!-R8mXPaSi$%h zXegN~O(-CZcm8nnZp-&AKk;{u|8nu?F(SBr-SuEU5x#shN`#K*7oUOZ+l3uO@Ghu- z-2DA!c_>mI87*GALWagmL*v9ZQSwa?VWKD{iI6PvIeJcKD}vGGb6d>6&pbW=1mJBH z4pI1&f(xM4i4Rkm&9S%KH&Pwt9v`7_l)`5e=oxMP8+6 z23G{5%d1kzfQHmx=Fj)$+DZWKJd~Rz#q?<1BY@+LY zAk;TlAM0Y>tP2L~>D-ipPlMM%O8BPJ{16e`PwhlF^Zfd=?}*T|&_M*>E9;VD(NP{A z0m_S)$I0+SX?TM8rIKGFf>fM@oREvWLeCjk-kDIvt!cjTG;in~4D|(kk@_g7+o1X; zdS!7>D!Hljw2Iro(FJvNQ`R?L+>b6DTRc{dTr7{qienOqOqL>(q(5Hjj}swYOiU4B zs>olX=j>cXFuMG;E!IE4y77ZkC+kiBmvx*{^BGOWo#4ne039>}{g;4E!yYQL{Q}!L z(2XAZ_Onj`TOL@pFz5I7^J|W8j9rSXMlk|lGuN?*lm|?;j54`ZeAHeX!>ke-`^$n&B%rLa!_nnpGOTu)eJ-g@Z zxqbKFcfxA5tl_%xgIC1$!{3t($ z=P|A?YmMjnkINRWNV>DV*z+S*dv|!1RrTho7g?Q<_k3Y_ovtrBB8Vf4`g^e_qO8}v z8+JFP*Ite1ty6BpI(z2qOV-M!wJ5^Ckvy~(d#)E+3%w`~xtLWm&-WmKrQIxf6@n>xhP)n%5oa_`dMdA{V~1 zDL6XRZY*9Dt`~V>a8nBEHn9)}9GeY;%Z?j|ax<;JxhNbNctIP_)}Ym;C5D%dmjdnu z+|f5s#M%yfr0wXOJz`rL*LQNUaVNi1Xlrrtj`>SS`H_w|YSb@_Kiokdk^m#Jlh0MOs4?B2)Mc=`QjuTs)&vZI7o4D-xPG>%f>~qoy zc-XO`Sb~c4YLfn;X+AUEnVG(APk(!6`r6F&a;kk~V)H4p8zo6JrMV#7NKFiD5ffA6 zPUr=x9(2<}He*_FS3?LUEo4)t<4)vyp6!PlLb^^w3^mkHlLpx6^;nZI741K7&#i_X zF?Yw=a9(Zgbq=aH zhw8NpHx}oVN@nG3cQYMtix|6TgJm}Hkd}-{M78{Ltj6R~yk2&R#^II2%kYj)p^`9J ziD8A?3>KpE+ z=~~171M>*%JK;(+Z{37UgI;F^6ND{mBb0ZAjI21cqOP+MWU9c$O0P|d(DI#jwhs3q zXT=vV#GW5}T^YI}iXis`x^rQ)>~Nde^8NC~ORv3k#k%&!#Y--6WO(KALM&Xy9i2qc z*0yp9yQqD?yk#W09WBukBhj~^*-TqQ%d+;Ekrfu4ZdU}nkx$JbrK#zGFSJdlP#Cnl zc4|Va?RCeGQsyZY$%-RbD9yFp;LEfu>8HU=HpvXuual6$zu9ws=wjgcS#;aCWpcnY zWQxXKL=kHb^=pZBOqA>QO(J6(qQ0f+aMT8V+c^eGU(%xIH$V>kZsX=? zYvh((hPj3pX%Q{-Rov0%QTzy;08bbe!;}eosBP=t#o#rahRb8<(qNK?z&!NXGpQlq zDIe?d1e)Y?RFJ+(MSAI6TLj{rt~`GhZ4r&q@14;4UwRhtyPOz}!b&8>hLKJDJWWjN zq^$cGqRDhkk~r0R@(Xx7gIifknH^y-Jb?)FU3iYTk&`dd(7e(*)ivAc^l*4u9BhlG zi7F_Jk_mYkrOGEpPBzfs;@)=+bxc{E&=4K9Cd7j1sI0DO3@k2x-)!a|fYr?cgiuH; z3!Zd)z9SdCDDG$R=z`^=qU_+bwXrG!%L}-8M{q0b#gWGmmJp9t!d}25>y-Ltd5zQ7 zz@w5IRc1m0LFmG07)PYSf#q4#mM?-+k?>m$)oY&vAvGxCUcfyF0tt)Rwl1e)1b{@N z5Wsa0j^)?v$Lx^~ZDPw>_AMFJ8@T}~h_%*LI783zy*{j&JUkJX#Y6SA1!^)YvJiJ2 z1PC`iO&tTv!5=QSEv#^bTqt#LrGVSyma{vVB7^2yQi23m%Ls-+6gvUDxNQ5H_u5C;v|TkV>zV){azH&Z6Tv;s$w*a{dIH(EEq?3T<{c=TnR%UqgBk z)ltOSHsUFmsV6YfBibXdEwiFrnvZ&2A=5G2hT?%69otTGkWxdJq{V4o$rzemvTfIQ z5De{IIVmu)-4IC!tv_FB*J2VaV|%|jPU5b0|6 zv^rSV6}~@QTe0Zt)P+o3l4m2-puC%@8M%NQj`6}*+oXE3mg&Tw;(&82A zbUKdg|4K>t>Mj;@tidOMD2hm%1HD1Ga^xlj8h2n;O538-mNz@VV5A46I*4VNy^xj- zEk&%8v9*+%W_BgTTF;ZvH>lpw`kGi^q(O8oH4La1R4!7olBcm*CHO`@)4lTU%xQJVRa@BWpBTt%WGqXV|7BY1n^b&@ig_xO&Z*gU3#DIVgRkROb_o4qm4c);`1R5V?d~(d zaeS@fRwlR(rAsstz?KfiBYwUQuazK)ISzAtpTz(HTtBYy9M!h~bx}7^&mV`cQ*Q-) z(YK6;j2Dk|$cyF2>?YV)8>t8BiS%CmADHh;^z-DH$WhTU#0IvT~qjg?g^7?CvkRbxR9ELyK!a zVVg%YY|+1xQL@`h{%a2vujA3f6cbW z_Fh$c|I_Ze(I2q8&tZ3musbA&TQ#QjCvE}#@TslZyS3ZeE$wMJWZyQ}2KohK8_(?X ztZ@ftDk!AIfHQ9s(yI4=uM8Plx-KM~k7EH1h4E$=fgBOe>LS*u>Hs>!^LXnXhew7j z2Z*1;s&&fo+oAO0)lM{Z4^pQM-K^D@Hn-Ut`?+Mf%=0{l|J6WW+ zB`V$+wJ8@yZt*>8x`BcqM{E-rvAxuoOiT1aG=YHxh?H}a1Ue)EGI@>09HUNZlazJX ziunSaA&3kYN#d;}5TE9{VK*%aX$)obI8opbX$Yd1=AnA$tyI=Rhp&HqC6e=E5P}Oq?96ML0SyN z1~m{@omM)A383e;YGNW2rD7X#ES(&#wJSIq?NJwnX_~Bp*rKyCe#WQuJP#o>5=pF{=EZIf|z*?6aq-Gi3{_vbu zMv?+?zaWMXcZ`w@@z|Hnl7x|Vuv4ULot*!zJqa)6r$~8c4yL>+?KPJA({E(SL>E9h19tGf zaKrmuV81xq_1wG2?e&e7hMAcM?LE~)m}u9DS0$l~)Ch4d07sa0Hh8bo1$H%JoW!L? zoZbK{Nf-f8cWP3#Dv%y?mm!hQ5^Qas_!Z`A`|7 zfVGAWN6RL)tW(ja;(!vsHIRD};&=EMO`xD-jtYL|Cx$-psiDhv(b!;@8^<%acL>!q z4N zDk|@$%vfoqVyN2C9y%07%J-P(vi#^gnRrA|2RV&82gv*m`5sd*mnV)+)GJe_ZoX=c HRf_)uEu+@I literal 0 HcmV?d00001 diff --git a/src/QD_MARL/utils/__pycache__/utils.cpython-311.pyc b/src/QD_MARL/utils/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b39b9d1dce006382307530fda60094d91785b6f GIT binary patch literal 10537 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 literal 0 HcmV?d00001 diff --git a/src/QD_MARL/utils/print_outputs.py b/src/QD_MARL/utils/print_outputs.py new file mode 100644 index 000000000..ba418913e --- /dev/null +++ b/src/QD_MARL/utils/print_outputs.py @@ -0,0 +1,36 @@ +import datetime + +class colors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + ORANGE = '\033[33m' + RESULT = '\033[94m' + WARNING = '\033[93m' + DEBUGGING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +def now(): + return datetime.datetime.now().strftime("%H:%M:%S") + +def print_error(*args, **kwargs): + print(now()+" ["+colors.BOLD+colors.FAIL+"ERROR"+colors.ENDC+"] " +" ".join(map(str,args))+"\n", **kwargs) + +def print_warning(*args, **kwargs): + print(now()+" ["+colors.BOLD+colors.ORANGE+"WARNING"+colors.ENDC+"] " +" ".join(map(str,args))+ "\n", **kwargs) + +def print_debugging(*args, **kwargs): + print(now()+" ["+colors.BOLD+colors.DEBUGGING+"DEBUG"+colors.ENDC+"] " +" ".join(map(str,args))+"\n", **kwargs) + +def print_info(*args, **kwargs): + print(now()+" ["+colors.BOLD+colors.OKGREEN+"INFO"+colors.ENDC+"] "+" ".join(map(str,args))+"\n", **kwargs) + +def print_configs(*args, **kwargs): + print(now()+" ["+colors.BOLD+colors.OKBLUE+"CONFIGS"+colors.ENDC+"] "+" ".join(map(str,args))+"\n", **kwargs) + +def print_results(*args, **kwargs): + print(now()+" ["+colors.BOLD+colors.RESULT+"RESULTS"+colors.ENDC+"] "+" ".join(map(str,args))+"\n", **kwargs) \ No newline at end of file diff --git a/src/QD_MARL/utils/utils.py b/src/QD_MARL/utils/utils.py new file mode 100644 index 000000000..672ecdeb2 --- /dev/null +++ b/src/QD_MARL/utils/utils.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + src.utils + ~~~~~~~~~ + + This module implements utilities + + :copyright: (c) 2021 by Leonardo Lucio Custode. + :license: MIT, see LICENSE for more details. +""" +import string +import numpy as np +import os +import pickle +from datetime import datetime +from joblib import Parallel, delayed +from decisiontrees import RLDecisionTree +from decisiontrees import ConditionFactory, QLearningLeafFactory + +def get_logdir_name(): + """ + Returns a name for the dir + :returns: a name in the format dd-mm-yyyy_:mm:ss_ + """ + time = datetime.now().strftime("%d-%m-%Y_%H-%M-%S") + rand_str = "".join(np.random.choice([*string.ascii_lowercase], 8)) + return f"{time}_{rand_str}" + + +def get_map(n_jobs, debug=False): + """ + Returns a function pointer that implements a parallel map function + + :n_jobs: The number of workers to spawn + :debug: a flag that disables multiprocessing + :returns: A function pointer + + """ + if debug: + def fcn(function, iterable, config): + ret_vals = [] + for i in iterable: + ret_vals.append(function(i, config)) + return ret_vals + else: + def fcn(function, iterable, config): + with Parallel(n_jobs) as p: + return p(delayed(function)(elem, config) for elem in iterable) + return fcn + + +class CircularList(list): + + """ + A list that, when indexed outside its bounds (index i), returns the + element in position i % len(self) + """ + def __init__(self, iterable): + """ + Initializes the list. + If iterable is a dict, then an arange object is created as the list + """ + if isinstance(iterable, dict): + list.__init__(self, np.arange(**iterable)) + else: + list.__init__(self, iterable) + + def __getitem__(self, index): + return super().__getitem__(index % len(self)) + + +class Grammar(dict): + """ + Implements a Grammar, simply a dictionary of circular lists, i.e. + lists that return an element even if the index required is outside their + bounds + """ + + def __init__(self, dictionary): + """ + Initializes the grammar + + :dictionary: A dictionary containing the grammar + """ + circular_dict = {} + for k, v in dictionary.items(): + circular_dict[k] = CircularList(v) + dict.__init__(self, circular_dict) + +# PER RIPARAZIONE +from decisiontrees import Condition + +def genotype2phenotype(individual, config): + """ + Converts a genotype in a phenotype + + :individual: An Individual (algorithms.grammatical_evolution) + :config: A dictionary + :returns: An instance of RLDecisionTree + """ + genotype = individual.get_genes() + gene = iter(genotype) + grammar = Grammar(config["grammar"]) + cfactory = ConditionFactory(config["conditions"]["type"]) + lfactory = QLearningLeafFactory( + config["leaves"]["params"], + config["leaves"]["decorators"] + ) + + if grammar["root"][next(gene)] == "condition": + params = cfactory.get_trainable_parameters() + root = cfactory.create( + [grammar[p][next(gene)] for p in params] + ) + else: + root = lfactory.create() + return RLDecisionTree(root, config["training"]["gamma"]) + + fringe = [root] + + try: + while len(fringe) > 0: + node = fringe.pop(0) + for i, n in enumerate(["left", "right"]): + if grammar["root"][next(gene)] == "condition": + params = cfactory.get_trainable_parameters() + newnode = cfactory.create( + [grammar[p][next(gene)] for p in params] + ) + getattr(node, f"set_{n}")(newnode) + fringe.insert(i, newnode) + else: + leaf = lfactory.create() + getattr(node, f"set_{n}")(leaf) + except StopIteration: + # tree repair + try: + fringe = [root] + + while len(fringe) > 0: + node = fringe.pop(0) + if isinstance(node, Condition): + for i, n in enumerate(["left", "right"]): + actual_node = getattr(node, f"get_{n}")() + if actual_node is None: + #print("INVALIDO") + actual_node = lfactory.create() + getattr(node, f"set_{n}")(actual_node) + fringe.insert(i, actual_node) + except Exception as e: + return None + + finally: + return RLDecisionTree(root, config["training"]["gamma"]) + + +def genotype2str(genotype, config): + """ + Transforms a genotype in a string given the grammar in config + + :individual: An Individual algorithms.grammatical_evolution + :config: A dictionary with the "grammar" key + :returns: A string + + """ + pass + +def save_tree(tree, log_dir, name): + if log_dir is not None: + assert isinstance(tree, RLDecisionTree), "Object passed is not a RLDecisionTree" + log_file = os.path.join(log_dir, name + ".pickle") + with open(log_file, "wb") as f: + pickle.dump(tree, f) + +def get_tree(log_file): + tree = None + if log_file is not None: + with open(log_file, "rb") as f: + tree = pickle.load(f) + return tree diff --git a/src/melting_pot.py b/src/env/melting_pot.py similarity index 100% rename from src/melting_pot.py rename to src/env/melting_pot.py diff --git a/src/script.sh b/src/script.sh index 1dcd1378d..fbf36cd42 100755 --- a/src/script.sh +++ b/src/script.sh @@ -6,7 +6,9 @@ echo "[2]. Deactivate environment" echo "[3]. Run code dts4marl" echo "[4]. Run code marldts" echo "[5]. Run code qd_marl" -echo "[6]. Exit" +echo "[6]. Run code qd_marl in debug mode" +echo "[7]. Run test environment" +echo "[8]. Exit" read option if [ $option -eq 1 ] then @@ -29,8 +31,15 @@ elif [ $option -eq 5 ] then echo "Running code..." python3 QD_MARL/marl_qd_launcher.py QD_MARL/configs/battlefield.json 4 - elif [ $option -eq 6 ] +then + echo "Running code in DEBUG MODE..." + python3 QD_MARL/marl_qd_launcher.py QD_MARL/configs/battlefield.json 4 --debug +elif [ $option -eq 7 ] +then + echo "Running test environment..." + python3 QD_MARL/test_environments.py QD_MARL/configs/battlefield.json 4 +elif [ $option -eq 8 ] then echo "Exiting..." exit