Author SHA1 Message Date
dodox be320b2d43 Merge branch 'master' into 81_fix_docs_mp 2023-11-23 20:52:07 +01:00
Hongyi Zhou 216a6f215d Merge pull request #83 from ALRhub/78_fix_render_mode
Fix render_mode not working for many fancy envs
2023-11-22 11:41:13 +01:00
Hongyi Zhou 052abcbf1c fix the render_mode in via_point_reacher and hole reacher 2023-11-22 11:24:23 +01:00
dodox cee8d59107 remove 'mode' from render() for all dummy envs (test suite) 2023-11-21 20:19:47 +01:00
dodox 01d2bf44ba remove 'mode' from render() for all classic_control envs 2023-11-21 20:17:19 +01:00
Hongyi Zhou 2f4b6fcea4 Merge pull request #79 from maxmunzel/fix_pip_metaworld 2023-11-21 16:18:23 +01:00
Onur ef104cfc2a Merge pull request #86 from Thifhi/air_hockey_env
Implement Air Hockey Envs in Fancy Gym
2023-11-20 16:07:54 +01:00
Mustafa Enes Batur 879b823d02 Stop mujoco viewer on close 2023-11-20 13:50:48 +01:00
Mustafa Enes Batur 8b585d7ea8 Correct fps 2023-11-20 13:47:23 +01:00
Mustafa Enes Batur b95cf85e3d Tournament random after reset 2023-11-20 13:47:15 +01:00
Mustafa Enes Batur bedb4297f2 Add close. Fix rendering to conform with gym 2023-11-20 12:39:14 +01:00
Mustafa Enes Batur 62fecda9d6 Fix max episode steps 2023-11-20 12:38:27 +01:00
Mustafa Enes Batur 079225cd1b Raise exception on invalid env_mode 2023-11-19 22:55:13 +01:00
Mustafa Enes Batur f102e49581 Empty mp types 2023-11-19 22:50:35 +01:00
Mustafa Enes Batur 7a05c063b7 Add license 2023-11-19 22:46:49 +01:00
Mustafa Enes Batur 7e537447e1 Register environments separately 2023-11-19 22:45:48 +01:00
Mustafa Enes Batur 9aa572271f Bugfixes and update for deprecated code 2023-11-19 22:45:32 +01:00
Mustafa Enes Batur d0cb6316a5 Numpy bugfix 2023-11-18 23:07:43 +01:00
Mustafa Enes Batur edd1e83813 Implement air hockey envs 2023-11-18 18:19:05 +01:00
dodox da042eebd9 Ensure envs are closed 2023-10-29 13:41:06 +01:00
dodox f1d3b5333f Docs: Show usage using new api functionality 2023-10-29 13:25:35 +01:00
dodox 668765b145 Merge branch '78_fix_render_mode' into 81_fix_docs_mp 2023-10-29 13:23:06 +01:00
dodox e5a9aaff0b Fix: Did not correlcty pass render_mode to gym.make (as per new spec) 2023-10-29 12:57:29 +01:00
dodox dc773c6c10 Fixed TableTennisWind and TableTennisGoalSwitching not correctly passing args to parent class 2023-10-29 12:53:06 +01:00
dodox d7ea1f80a0 gym.Env actually does not want to known about render_mode 2023-10-23 13:12:34 +02:00
dodox f7a493d8e5 Mujoco envs: Follow new spec for render_mode 2023-10-23 12:27:13 +02:00
dodox b681129a46 Classical Controll envs: Follow new spec for render_mode 2023-10-23 12:26:26 +02:00
dodox 5db73f90c4 Update BB wrapper to follow new spec for render_kwargs 2023-10-23 12:25:53 +02:00
Max Nagy 8ba3d4ec6b Fix `pip install ".[metaworld]" 2023-10-20 14:45:20 +02:00
Fabian c985f2c415 Create CONTRIBUTING.md 2023-10-11 15:37:18 +02:00
Fabian 8f34cfe5ed Merge pull request #77 from ALRhub/ottofabian-patch-2
Update issue templates
2023-10-11 15:31:42 +02:00
Fabian 5e60aabef5 Update bug_report.md 2023-10-11 15:30:57 +02:00
Fabian faa537596a Update issue templates 2023-10-11 15:28:19 +02:00
Fabian b1fd1e5b5c Create CODE_OF_CONDUCT.md 2023-10-11 15:26:13 +02:00
Fabian cfda88d98c Merge pull request #76 from ALRhub/ottofabian-patch-1
Create LICENSE
2023-10-11 14:17:59 +02:00
Fabian 7976abae5e Create LICENSE 2023-10-11 14:17:02 +02:00
Dominik Roth c420a96d4f Merge pull request #75 from D-o-d-o-x/great_refactor
Refactor and Upgrade to Gymnasium
2023-10-11 13:42:00 +02:00
dodox a04a4b6e67 Link legacy branch in README 2023-10-11 13:36:46 +02:00
dodox f8dcf5b2df Fix: Did not define MP_Wrapper for new RandomInit version of BoxPushing 2023-10-11 13:29:08 +02:00
dodox 614d2a42db READE: Minor cosmetic changes pt.3 2023-10-11 13:28:03 +02:00
dodox 4f534779ff READE: Minor cosmetic changes pt.2 2023-10-11 13:25:14 +02:00
dodox fc7382443d README: Minor cosmetic changes 2023-10-11 13:23:33 +02:00
dodox 600575cbac Dont run determinism tests on arbitrary external envs added by other
libs (like atari envs added by shimmy), only those included in gym and those added by us.
2023-10-11 13:08:06 +02:00
dodox 877a7ea6ba Fix: Wrong horizon for Metaworld tasks 2023-10-11 12:50:55 +02:00
dodox 980bb0de6b Add docstring to DefaultMPWrapper 2023-10-11 12:46:34 +02:00
dodox db5e032987 Development status alpha -> beta; version to 1.0 (for pub release) 2023-10-11 12:41:38 +02:00
dodox 3317ffb06d README: @ is not a legal symbol in BibTex attribs 2023-10-11 12:39:11 +02:00
dodox 757072c762 Documnenting merging behavior for mp_configs (and remove TODOs) 2023-10-11 12:35:23 +02:00
dodox bc150f5a4d Fix: Bibtex in README mixed between tabs & spaces 2023-10-11 11:59:02 +02:00
dodox 5f3c28e8f0 Added bibtex for citations to README 2023-10-11 11:56:51 +02:00
dodox fcc79cdde0 Added Hongyi & Hongyi as authors to setup.py 2023-10-11 11:38:42 +02:00
dodox 3180154fa6 Added docstring to deprecated 'fancy_gym.make' 2023-10-11 11:37:46 +02:00
dodox 9fee527dbe Fix: Typo in setup.py 2023-10-11 11:36:36 +02:00
dodox 0ae87088d9 README: Better Justifycation of text 2023-09-30 18:48:10 +02:00
dodox 1eeccff35f README: Justify text about MP envs (looks way better) 2023-09-30 18:46:50 +02:00
dodox 27f3a824c6 Tiny update to Metaworld README 2023-09-30 17:27:11 +02:00
dodox b3de71e0d6 Expanded README for Metaworld
(Thanks to ChatGPT for helping)
2023-09-30 16:55:50 +02:00
dodox e633b1eeeb Fixed open_ai README 2023-09-30 16:55:22 +02:00
dodox a803d92dd2 Slighly better docs for OpenAI envs 2023-09-30 16:31:23 +02:00
dodox e3055568e8 Improved README for classical controll envs 2023-09-29 20:39:42 +02:00
dodox e55905a824 Improved and Updated README for custom mujoco envs 2023-09-29 20:39:11 +02:00
dodox 8bba78b813 Updated dmc README 2023-09-29 20:36:36 +02:00
dodox 56c1c65d09 Better doumentation of fancy registry fucntions (register & upgrade) 2023-09-29 19:19:57 +02:00
dodox d6ecc0dc67 minor changes to README 2023-09-20 12:49:31 +02:00
dodox 6d0e96c6f2 typo 2023-09-19 18:04:02 +02:00
dodox 740d1d7896 README: Added link to changelog of refactor 2023-09-19 17:54:27 +02:00
dodox 310719e471 Fix: Typo in README 2023-09-19 17:32:29 +02:00
dodox 79c2eda4fe Fixed minor issues 2023-09-19 17:07:12 +02:00
dodox 49658515b0 Extended README 2023-09-19 17:05:41 +02:00
dodox d8315c8d97 Fixed typo in NS 2023-09-19 17:00:49 +02:00
dodox e077a8f13d Updated README 2023-09-19 16:51:30 +02:00
dodox 842ab3fbc9 Added mujoco-legacy (mujoco-py) as an optional dependency
required to pass all tests, since some OpenAI envs still use old mujoco
2023-09-19 13:42:03 +02:00
dodox 59ee559f8d README: reset will return (obs, info) 2023-09-18 19:52:08 +02:00
dodox 57c4a940b8 Another typo gone from the README 2023-09-18 19:50:23 +02:00
dodox c5b051da20 Fixed typo in README 2023-09-18 19:47:43 +02:00
dodox cf886b2b87 New optional dependency: jax
(Required, so that we can make all gymnasium envs during our tests)
2023-09-18 19:42:58 +02:00
dodox 659a717fbd Removed old files 2023-09-18 19:31:17 +02:00
dodox 725e6c8fe1 Fixed wrong env name (still contained mp info) 2023-09-18 19:18:26 +02:00
dodox 5488ca6dda Fixed merge error 2023-09-18 19:17:43 +02:00
dodox 60a820456c Merge remote-tracking branch 'original/master' into great_refactor 2023-09-18 19:09:03 +02:00
dodox 54e8ec4015 Print warning on seeded metaworld reset 2023-09-18 18:42:34 +02:00
dodox ddd11c1783 Skip Tests: Metaworld does currently not correclty implement seeding 2023-09-18 18:40:47 +02:00
dodox 9ce040d110 Porting Metaworld Bug Mitigations 2023-09-18 18:40:10 +02:00
dodox 20510d8f68 Ported MetaWorld wrapper to new mujoco binding 2023-09-18 18:38:32 +02:00
dodox 592f09789d Future proofing: Accesing attributes of other wrappers needs to be
explicit in future gymnasium versions
2023-09-18 18:37:46 +02:00
dodox 513bf2361e Fix: Deprecated beerpong env still referenced old mujoco binding 2023-09-18 18:37:19 +02:00
dodox 4c8dda3b60 Fix: README code imported gym instead of gymnasium 2023-09-18 17:43:15 +02:00
dodox c933a75880 More README improvements 2023-09-18 17:41:10 +02:00
dodox 38358c183e Upgrading metaworld 2023-09-17 19:05:42 +02:00
dodox 89bd6781c7 mp_config for vanilla gym Reacher 2023-09-17 19:05:25 +02:00
dodox a76967a498 Forgot to port gym_Reacher over 2023-09-17 19:05:08 +02:00
dodox 7f58093c5e Fixed all examples 2023-09-17 18:50:21 +02:00
dodox 8749fc52cb Better README 2023-09-17 18:37:40 +02:00
dodox da34db22c8 Fix: mp_config missing for BoxPushing ProDMP 2023-09-17 17:31:41 +02:00
dodox 0629d1260c Ensure mp_config defined for all envs, even if just using defaults. 2023-09-17 17:29:26 +02:00
dodox 7f95923cf4 Fixed typo in env registration 2023-09-17 17:17:20 +02:00
dodox 315e135ff0 Added message about discontinuation of fancy_gym.make 2023-09-10 09:12:15 +02:00
dodox 155807207f Fix: SimpleReacher and ViaPointReacher did not seed correctly 2023-08-28 18:38:33 +02:00
dodox 820e781a0c Fix: Some simple_reacher did not seed correctly 2023-08-28 18:32:11 +02:00
dodox 07aeb779a7 Fix: Some fancy envs failed determinism test because they sampled during reset before the random gen was seeded 2023-08-28 18:16:16 +02:00
dodox 5921e0008b Mitigation: Metaworld .reset ignores seeds; we must manually seed on reset. 2023-08-28 17:57:24 +02:00
dodox 22a72fed2f Fix: Seeding of act-space is not guaranteed, seed it manually since we
depend on it...
2023-08-28 17:37:34 +02:00
dodox 2c0c4e5508 Fix Typo: alpha_phase belongs to phase_gen not basis_gen 2023-08-28 17:37:03 +02:00
dodox 20b1b0ccac Quickfix for mp_config merging and allow defining different base_id for
upgrades
2023-08-28 17:36:17 +02:00
dodox 8b3d05aaaf Fix Typo: weights_scale should apply to traj_gen, not controller 2023-08-28 17:35:30 +02:00
dodox a4e28837bb Fix: Incorrect Controller Type in mp_config for dmc reach_site 2023-08-28 17:34:46 +02:00
dodox 15e1bdc218 Mitigation: Allow seeding Metaworld on reset 2023-08-28 16:18:18 +02:00
dodox 6d80201a03 Simplified metaworld adapter 2023-08-21 11:58:29 +02:00
dodox 94c4397fac Fixed incorrect ns seperator for metaworld tests 2023-08-21 11:57:58 +02:00
dodox 78823d95b6 Various fixed to tests 2023-08-14 16:47:05 +02:00
dodox 5b99227fac Moving the ugly_mitigation_for_metaworld_bug into the metaworld env wrapper 2023-08-14 16:45:33 +02:00
dodox f3ffa714cb Cleaning up test/test_dmc_envs.py 2023-08-14 16:09:28 +02:00
dodox 1fb5368cc2 Fix: Multiple issues in test/test_fancy_registry.py 2023-08-14 16:08:32 +02:00
dodox 14d545acee Fixed: Name generation wrong for mp envs 2023-08-14 16:08:13 +02:00
dodox eefcbcb0f0 tiny fix to env references in test 2023-08-14 11:57:06 +02:00
dodox fb1282fe1e Use new namespace convention for mp-versions of envs 2023-08-14 10:51:43 +02:00
dodox 14223c56ba Fix: Controllers not getting kwargs 2023-07-30 19:32:32 +02:00
dodox 221e3fee5a Fix: Some tests omitted new ns 'fancy' when trying to access our envs 2023-07-30 18:34:27 +02:00
dodox caf483a23b Fixed Typo dm_controll -> dm_control 2023-07-30 18:31:50 +02:00
dodox fb8f81afea Don't use defaultdicts for MOVEMENT_PRIMITIVE_ENVIRONMENTS_FOR_NS
(is ugly when exporting)
2023-07-30 18:29:47 +02:00
dodox 9020eb92ea Fix: test/test_replanning_sequencing.py still referenced old fancy_gym.make
(No longer exists as we now just use gym.make)
2023-07-30 18:16:47 +02:00
dodox 9c6d7956ce Extended Tests: Ensure no known namespace is empty 2023-07-30 18:16:19 +02:00
dodox e30b6c0e04 Removed old code samples from meta mp docs 2023-07-30 18:15:51 +02:00
dodox 5519fd5a34 Fix: Wrong name for ns: dmc -> dm_controll 2023-07-30 18:14:50 +02:00
dodox b4793c89bc Fix: ALL_FANCY_MOVEMENT_PRIMITIVE_ENVIRONMENTS no longer exists in fancy
registry
2023-07-30 17:59:30 +02:00
dodox 20d0be3c8d Replicate legacy behavior in exporting lists off all mp envs 2023-07-30 17:56:28 +02:00
dodox bc3f540daf weight_scale -> weights_scale 2023-07-30 17:51:37 +02:00
dodox e63a0a50df Ported metaworld to mp-config 2023-07-30 17:42:27 +02:00
dodox e743663018 Ported dmc envs to mp-config 2023-07-30 17:41:44 +02:00
dodox d48836368c Print warning when trying to register already registered step-based env 2023-07-29 11:37:03 +02:00
dodox 6b07705a88 Fix: fancy_gym.upgrade ignored mp_config_override-arg 2023-07-29 11:27:58 +02:00
dodox ae1033a18c Remember mp-envs for each ns seperately (replicate legacy functionality) 2023-07-29 11:26:48 +02:00
dodox 2fc44667c6 fancy_gym.upgrade allows adding mp-versions for already registered envs 2023-07-24 10:33:00 +02:00
dodox bb20bb53ec entry_point does not have to be given, when skipping registration of step-based 2023-07-24 10:30:34 +02:00
dodox edd8c75ed3 Consistent quotation marks 2023-07-23 15:26:11 +02:00
dodox 3a9143fd93 removed file deprecated 2023-07-23 15:14:14 +02:00
dodox 58bb8c0395 Fix: Env in new namespace 2023-07-23 13:24:16 +02:00
dodox 127d355ccc Move into our own namespace ('fancy') 2023-07-23 13:17:54 +02:00
dodox 95b2c255e9 Fixed typo 2023-07-23 13:13:02 +02:00
dodox fc4b98ea7d Allow envs to already be wrapped with a RawInterfaceWrapper 2023-07-23 13:12:07 +02:00
dodox e2ff915a71 Fix: Cannot use .get for object attributes 2023-07-23 13:11:31 +02:00
dodox 0fb0506e49 Fix: Many tests still tried to seed during make 2023-07-23 12:31:47 +02:00
dodox 6787b02bbd Fixed missing import in metaworld_adapter 2023-07-23 12:26:41 +02:00
dodox 99a02b8347 Started work on new adapter & mp_config port for metaworld 2023-07-23 12:21:34 +02:00
dodox 3e586a1407 Added tests for new fancy registry 2023-07-23 12:21:18 +02:00
dodox eb9b6e1e22 test_black_box.py should use vanilla env.make 2023-07-23 12:20:49 +02:00
dodox b0f7dc6c7c test/utils.py must use gym.make now (was fancy_gym.make) 2023-07-23 11:13:15 +02:00
dodox 21a9c0f8a3 Fixed Bug leading to infinite recusrion when making mp envs 2023-07-23 11:06:08 +02:00
dodox ce34df1814 No longer export old custom make 2023-07-23 11:05:45 +02:00
dodox a069aaac65 Removed old manual registration of mp-envs (port to fancy registry) 2023-07-23 10:25:28 +02:00
dodox 9ba3fa9dbc ported mp_config for mujoco/table_tennis 2023-07-23 10:25:01 +02:00
dodox 64e6ac5323 ported mp_config for mujoco/beerpong 2023-07-23 10:03:18 +02:00
dodox 83d5d39b42 removed deprecated envs 2023-07-20 11:48:19 +02:00
dodox dbf2be1006 refactoring env registration wip 2023-07-20 11:45:53 +02:00
dodox 1b061b2a37 ported mp_config for mujoco/box_pushing 2023-07-20 11:45:32 +02:00
dodox 8d26cccc35 ported mp_config for mujoco/box_pushing 2023-07-20 11:44:50 +02:00
dodox 17d370e2ba Allow overriding mp_config during register and make (also better errors
for DefaultMPWrapper)
2023-07-20 11:44:04 +02:00
dodox 9d03542282 Move mp_config out of metadata and onto MPWrappers 2023-07-20 10:56:30 +02:00
dodox f6e1718c1a metadata.mp_config now expected in MP_wrapper (implementing Fabian's feedback) 2023-07-20 10:34:38 +02:00
dodox 9fa932d2bb minor refactoring 2023-07-20 10:33:39 +02:00
dodox 30bafd7a4f Allow skipping merge with default bb config 2023-07-14 15:58:58 +02:00
dodox f375a6e4df Ported classic_control envs to fancy registry 2023-07-14 14:31:36 +02:00
dodox 6c90f8ade2 Getting rid of some old code 2023-07-14 14:29:08 +02:00
dodox ffbada2311 Started implementing new fancy registry 2023-07-14 14:28:31 +02:00
dodox 67a5d197d9 Weaker version requirement for dm-control 2023-07-07 18:16:13 +02:00
dodox 27f8335a0d Remove dependence of wrapper on old gym 2023-07-07 13:12:57 +02:00
dodox 14a95eb5ca Fixed Typo in README 2023-07-07 11:51:06 +02:00
dodox cdabd3f478 Downgrading mujoco 2.3.6 -> 2.3.3 2023-07-07 10:52:48 +02:00
dodox bf1eb496f6 Make Icon Attribution h2 2023-06-28 22:20:49 +02:00
dodox eb1d145dbd Fix Test: Delay was not supplied to mp-generator 2023-06-28 20:25:50 +02:00
dodox de118a3113 Down to 20 failing tests (more mitigations of metaworld bug) 2023-06-28 19:42:41 +02:00
dodox 1601a87cb1 Added attribution for the icon. 2023-06-27 21:43:22 +02:00
dodox 55f84da5e1 A fancy icon for fancy gym 2023-06-27 21:39:03 +02:00
dodox ad7ddd3b28 Fix: A missing ',' caused very weird bugs... 2023-06-24 12:24:52 +02:00
dodox e8119798c3 Updating some dependencies 2023-06-24 11:52:59 +02:00
dodox bc0dcb7642 Fixed: Tried importing old dependency 2023-06-24 11:43:40 +02:00
dodox 55c6cff690 Fix: PyPi had troubles finding a compatible version for dm-control 2023-06-24 11:03:40 +02:00
dodox 0fe56c4c0e Stricter dependency versions 2023-06-24 11:01:07 +02:00
dodox 9af42112b6 Version bump (made very breaking changes) 2023-06-24 10:37:35 +02:00
dodox 7354257f8e Bug mitigation for metaworld refactored and extended 2023-06-18 17:47:54 +02:00
dodox b6089c4b83 ugly_hack_to_mitigate_metaworld_bug 2023-06-18 15:52:17 +02:00
dodox f8ad65b790 Remove old file 2023-06-18 14:25:59 +02:00
dodox 9ade0dcdc4 Fix: Make wrappers work with BB and Dict-Space 2023-06-18 14:25:20 +02:00
dodox b032dec5fe Better handling of envs without defined max_steps 2023-06-18 14:23:59 +02:00
dodox 60a4cf11d6 Fix: Some envs used no longer existing binding to mujoco 2023-06-18 12:10:01 +02:00
dodox 49ac9c3785 Fix: Don't throw errors for envs, that do not expose a max_episode_steps. 2023-06-18 11:53:10 +02:00
dodox f44f01b478 Fix: Allow observation space dict in test_replanning 2023-06-18 11:52:35 +02:00
dodox fbba129034 Fix: Need to supply seed to reset in tests 2023-06-18 11:51:01 +02:00
dodox 9605f2e56c Fix: Test for dmc still referenced lib directly (not via shimmy) 2023-06-11 18:05:50 +02:00
dodox c9467c0e06 Upgrading metaworld (fixed bug that affected us) 2023-06-11 17:39:04 +02:00
dodox a23b44752e Implement support for Dict spaces for time_aware_observation-wrapper 2023-06-11 17:38:16 +02:00
dodox abeb963b4e Little hack to make envs work, that don't expose the max_episode_steps in their spec 2023-06-11 17:37:32 +02:00
dodox 2ad42f4132 Fix: Minor bugs in time aware obs wrapper 2023-06-11 13:47:38 +02:00
dodox e44b0ed9ed Fix: Version specification now requirted for dmc envs 2023-06-11 13:46:38 +02:00
dodox 4921cc4b0b Fix: Some envs had wrong obs space shapes and did not follow new gym spec 2023-06-11 13:45:44 +02:00
dodox 80de15fd14 Fix: Return for invalid trajectories did not follow new gym spec 2023-06-11 13:43:10 +02:00
dodox 6f1a36d18c Adding required render_fps metadata to custom envs 2023-06-11 12:30:56 +02:00
dodox ef64b0c21c Adding/fixing obs space definitions and metadata for various envs 2023-06-11 11:08:46 +02:00
dodox f07b8a26ac Made some assertions more verbose for easier debugging 2023-06-10 18:49:02 +02:00
dodox 40d2409c26 Fixed: ant_jump returned observations bigger then observation_space 2023-06-10 18:48:13 +02:00
dodox 42003a3f9a Allow custom XML-files for ant_env 2023-06-10 18:47:41 +02:00
dodox ddf6fd73b2 Fixed: hopper_jump returned observations bigger then observation_space 2023-06-10 18:40:03 +02:00
dodox 5e7f027ea0 Fixed Bug: walker_2d_jump was missing obsvation_space attribute 2023-06-10 18:34:49 +02:00
dodox ff382a2922 Converted mujoco env 'walker2d' to new coordinate convention
New versions of mujoco no longer allow global coordinates.
We therefore convert them to local ones.
The original files are kept as reference.
2023-06-10 13:40:33 +02:00
dodox c06fbee728 Converted mujoco env 'hopper_throw' to new coordinate convention
New versions of mujoco no longer allow global coordinates.
We therefore convert them to local ones.
The original files are kept as reference.
2023-06-10 13:37:40 +02:00
dodox 38cb5e1750 Converted mujoco env 'hopper_jump' to new coordinate convention
New versions of mujoco no longer allow global coordinates.
We therefore convert them to local ones.
The original files are kept as reference.
2023-06-10 13:35:48 +02:00
dodox 1fddeb838b Forgot to make it derive from Walker2dEnvCustomXML instead of original Walker2dEnv 2023-05-27 13:06:19 +02:00
dodox 110a8a9c0c Fix: MujocoEnv no longer supports manual assignment of mujoco_bindings 2023-05-27 12:55:46 +02:00
dodox dbd7c37da5 Also support old gym Box as observation_space (backwards compat) 2023-05-27 12:54:30 +02:00
dodox 29b458c7df Fix: hopper_jump_on_box still used unpatched HopperEnv 2023-05-27 12:53:57 +02:00
dodox a8a67601ca Fix: NameError: name 'd' is not defined 2023-05-27 12:48:45 +02:00
dodox 35680ab9a2 Merge branch 'master' into gym_upgrade 2023-05-27 11:45:51 +02:00
dodox d6df6779a1 Auto convert output spaces.Dict to Box for BB-Envs 2023-05-27 11:39:47 +02:00
dodox dabfc7cafe Adapted Mujoco Envs to new gymnasium spec
Gymnasium Mujoco Envs no longer allow overriding the used xml_file
We therefore implement intermediate classes, that reimplement this
feature.
2023-05-19 15:18:14 +02:00
dodox 1c002a235b Warning no longer applicable 2023-05-19 15:17:28 +02:00
dodox bc9d4cf19d More verbose errors for run_env_determinism 2023-05-19 14:54:13 +02:00
dodox 3bbf101e86 Fixed: test_black_box.py still used old gym specs in two places 2023-05-19 14:53:04 +02:00
dodox e70a56a7e3 Fixed: Typos 2023-05-19 14:04:19 +02:00
dodox e75ab89a37 Ported box_pushing to new mujoco bindings 2023-05-19 14:01:31 +02:00
dodox bf3ed8a06c Fix: TimeAwareObservation did not support float64 and tried to forbidden access private attribute. 2023-05-19 13:59:54 +02:00
dodox 07de655025 Allow dmc envs to be accessed via dmc:... (for backwards compatibility) 2023-05-18 19:50:19 +02:00
dodox 21d5ebb442 Fixed Bug: ToyEnv did not follow new gym spec 2023-05-18 19:13:35 +02:00
dodox 0dfd8e18e4 Fixed Bug: env_id was broken, when prefix is handled by other library
(e.g. 'dmc:...' by shimmy)
2023-05-18 19:08:11 +02:00
dodox d4a8306b9d Buf Fix: PyTests still used binding to old gym 2023-05-18 19:07:19 +02:00
dodox 1e62da0833 Use shimmy as a binding for dmc, also 'testing' seperate from 'all'
pip install .[all] will no longer install components only required for
testing
pip install .[testing] will also install all compionents required to run
all tests
2023-05-18 18:03:42 +02:00
dodox a559f92562 Adapted test to new gym interface
In prevous gym versions executing a step returned
obs, reward, done, info = env.step(...)

With the switch to gymnasium this has changed to
obs, reward, terminated, truncated, info = env.step(...)

We also made the code a bit more self explainatory.
2023-05-18 17:52:55 +02:00
dodox 228e343a1b Merge branch '47-update-to-new-gym-api' into gym_upgrade 2023-05-15 17:19:50 +02:00
dodox 826f22b18e Update URL for Metaworld (rlworkgroup -> Farama) 2023-05-15 17:11:57 +02:00
dodox 1c092d01bd Optional Dependency: PyTest 2023-05-15 17:11:14 +02:00
dodox 2cbfff7919 Added box2d as a dependency 2023-05-15 17:09:52 +02:00
dodox 9de1257e1c Updating gym(nasium) and metaworld; sticter versions 2023-05-15 16:55:53 +02:00
dodox b695239318 num_basis is not implemented; tests are expected to fail 2023-05-15 16:32:45 +02:00
Fabian c53924d9fc updated to new API, so tests still failing 2023-01-17 08:27:29 +01:00
Fabian ec2063aa0b updated tests for dm_control 2023-01-12 17:36:33 +01:00
Fabian 9ebc021ae0 updated dm_control envs to use shimmy 2023-01-12 17:23:56 +01:00
Fabian ed724046f3 updated custom tasks to new api 2023-01-12 17:22:45 +01:00
Fabian fbe3ef4a4b updated examples to new api, 2023-01-12 17:21:56 +01:00
Fabian 0c7ac838bf first updats to reacher and env creation 2022-10-20 10:10:44 +02:00
159 changed files with 6589 additions and 2827 deletions
+32
View File
@@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Select variables '....'
3. Execute '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Ubunutu]
- Python version [e.g. 3.8]
- Version [e.g. 1.0]
**Additional context**
Add any other context about the problem here.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+128
View File
@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
their respective email addresses.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
+28
View File
@@ -0,0 +1,28 @@
# Contribution Guidelines
We welcome and appreciate contributions to this repository from the community. To ensure a positive and productive collaboration, we have established the following guidelines for contributing to this project:
**Code of Conduct**: Please abide by our Code of Conduct, which sets the standards for respectful and inclusive behavior within the community.
**Issues and Feature Requests**: Feel free to open new issues to report bugs or suggest new features. Before submitting, please check if a similar issue or request already exists.
**Pull Requests**: We encourage pull requests for bug fixes, new features, and improvements. Please follow these steps when submitting a pull request:
- Fork this repository and create a new branch for your changes.
- Ensure your code aligns with our coding standards.
- Provide a clear and concise description of your changes.
- Test your changes thoroughly.
- Make sure your code is well-documented.
- Code Style: Follow our coding style guidelines, which include formatting, naming conventions, and other coding standards. Adhering to these guidelines will help streamline the review process.
**Collaboration**: Be prepared for constructive feedback during the review process. We aim to maintain the quality and consistency of the codebase.
**License**: Ensure your contributions comply with the project's existing open-source license. By contributing, you grant us the right to distribute your code under the project's license.
**Ownership**: When you submit a contribution, you confirm that you have the right to license your code to us and that your work does not violate any existing patents, trademarks, or intellectual property rights.
**Maintainers**: The repository maintainers have the final authority on merging or rejecting contributions. They will work collaboratively with contributors to ensure a smooth process.
**Community Support**: As part of our community, please consider helping others, answering questions, and providing support to fellow contributors.
We appreciate your interest in contributing to our project. Your involvement helps make this community vibrant and successful.
Thank you for being part of this project!
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Autonomous Learning Robots Lab @ KIT
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+159 -78
View File
@@ -1,27 +1,35 @@
# Fancy Gym <h1 align="center">
<br>
<img src='./icon.svg' width="250px">
<br><br>
<b>Fancy Gym</b>
<br><br>
</h1>
`fancy_gym` offers a large variety of reinforcement learning environments under the unifying interface | :exclamation: Fancy Gym has recently received a major refactor, which also updated many of the used dependencies to current versions. The update has brought some breaking changes. If you want to access the old version, check out the [legacy branch](https://github.com/ALRhub/fancy_gym/tree/legacy). Find out more about what changed [here](https://github.com/ALRhub/fancy_gym/pull/75). |
of [OpenAI gym](https://gymlibrary.dev/). We provide support (under the OpenAI gym interface) for the benchmark suites | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
[DeepMind Control](https://deepmind.com/research/publications/2020/dm-control-Software-and-Tasks-for-Continuous-Control)
(DMC) and [Metaworld](https://meta-world.github.io/). If those are not sufficient and you want to create your own custom Built upon the foundation of [Gymnasium](https://gymnasium.farama.org/) (a maintained fork of OpenAIs renowned Gym library) `fancy_gym` offers a comprehensive collection of reinforcement learning environments.
gym environments, use [this guide](https://www.gymlibrary.dev/content/environment_creation/). We highly appreciate it, if
you would then submit a PR for this environment to become part of `fancy_gym`. **Key Features**:
In comparison to existing libraries, we additionally support to control agents with movement primitives, such as Dynamic
Movement Primitives (DMPs) and Probabilistic Movement Primitives (ProMP). - **New Challenging Environments**: `fancy_gym` includes several new environments (Panda Box Pushing, Table Tennis, etc.) that present a higher degree of difficulty, pushing the boundaries of reinforcement learning research.
- **Support for Movement Primitives**: `fancy_gym` supports a range of movement primitives (MPs), including Dynamic Movement Primitives (DMPs), Probabilistic Movement Primitives (ProMP), and Probabilistic Dynamic Movement Primitives (ProDMP).
- **Upgrade to Movement Primitives**: With our framework, it's straightforward to transform standard Gymnasium environments into environments that support movement primitives.
- **Benchmark Suite Compatibility**: `fancy_gym` makes it easy to access renowned benchmark suites such as [DeepMind Control](https://deepmind.com/research/publications/2020/dm-control-Software-and-Tasks-for-Continuous-Control) and [Metaworld](https://meta-world.github.io/), whether you want to use them in the regular step-based setting or using MPs.
- **Contribute Your Own Environments**: If you're inspired to create custom gym environments, both step-based and with movement primitives, this [guide](https://gymnasium.farama.org/tutorials/gymnasium_basics/environment_creation/) will assist you. We encourage and highly appreciate submissions via PRs to integrate these environments into `fancy_gym`.
## Movement Primitive Environments (Episode-Based/Black-Box Environments) ## Movement Primitive Environments (Episode-Based/Black-Box Environments)
Unlike step-based environments, movement primitive (MP) environments are closer related to stochastic search, black-box <p align="justify">
optimization, and methods that are often used in traditional robotics and control. MP environments are typically Movement primitive (MP) environments differ from traditional step-based environments. They align more with concepts from stochastic search, black-box optimization, and methods commonly found in classical robotics and control. Instead of individual steps, MP environments operate on an episode basis, executing complete trajectories. These trajectories are produced by trajectory generators like Dynamic Movement Primitives (DMP), Probabilistic Movement Primitives (ProMP) or Probabilistic Dynamic Movement Primitives (ProDMP).
episode-based and execute a full trajectory, which is generated by a trajectory generator, such as a Dynamic Movement </p>
Primitive (DMP) or a Probabilistic Movement Primitive (ProMP). The generated trajectory is translated into individual <p align="justify">
step-wise actions by a trajectory tracking controller. The exact choice of controller is, however, dependent on the type Once generated, these trajectories are converted into step-by-step actions using a trajectory tracking controller. The specific controller chosen depends on the environment's requirements. Currently, we support position, velocity, and PD-Controllers tailored for position, velocity, and torque control. Additionally, we have a specialized controller designed for the MetaWorld control suite.
of environment. We currently support position, velocity, and PD-Controllers for position, velocity, and torque control, </p>
respectively as well as a special controller for the MetaWorld control suite. <p align="justify">
The goal of all MP environments is still to learn an optimal policy. Yet, an action represents the parametrization of While the overarching objective of MP environments remains the learning of an optimal policy, the actions here represent the parametrization of motion primitives to craft the right trajectory. Our framework further enhances this by accommodating a contextual setting. At the episode's onset, we present the context space—a subset of the observation space. This demands the prediction of a new action or MP parametrization for every unique context.
the motion primitives to generate a suitable trajectory. Additionally, in this framework we support all of this also for </p>
the contextual setting, i.e. we expose the context space - a subset of the observation space - in the beginning of the
episode. This requires to predict a new action/MP parametrization for each context.
## Installation ## Installation
@@ -43,59 +51,60 @@ cd fancy_gym
pip install -e . pip install -e .
``` ```
In case you want to use dm_control oder metaworld, you can install them by specifying extras We have a few optional dependencies. If you also want to install those use
```bash ```bash
pip install -e .[dmc,metaworld] pip install -e '.[all]' # to install all optional dependencies
pip install -e '.[dmc,metaworld,box2d,mujoco,mujoco-legacy,jax,testing]' # or choose only those you want
``` ```
> **Note:**
> While our library already fully supports the new mujoco bindings, metaworld still relies on
> [mujoco_py](https://github.com/openai/mujoco-py), hence make sure to have mujoco 2.1 installed beforehand.
## How to use Fancy Gym ## How to use Fancy Gym
We will only show the basics here and prepared [multiple examples](fancy_gym/examples/) for a more detailed look. We will only show the basics here and prepared [multiple examples](fancy_gym/examples/) for a more detailed look.
### Step-wise Environments ### Step-Based Environments
Regular step based environments added by Fancy Gym are added into the `fancy/` namespace.
| :exclamation: Legacy versions of Fancy Gym used `fancy_gym.make(...)`. This is no longer supported and will raise an Exception on new versions. |
| ----------------------------------------------------------------------------------------------------------------------------------------------- |
```python ```python
import gymnasium as gym
import fancy_gym import fancy_gym
env = fancy_gym.make('Reacher5d-v0', seed=1) env = gym.make('fancy/Reacher5d-v0')
obs = env.reset() # or env = gym.make('metaworld/reach-v2') # fancy_gym allows access to all metaworld ML1 tasks via the metaworld/ NS
# or env = gym.make('dm_control/ball_in_cup-catch-v0')
# or env = gym.make('Reacher-v2')
observation = env.reset(seed=1)
for i in range(1000): for i in range(1000):
action = env.action_space.sample() action = env.action_space.sample()
obs, reward, done, info = env.step(action) observation, reward, terminated, truncated, info = env.step(action)
if i % 5 == 0: if i % 5 == 0:
env.render() env.render()
if done: if terminated or truncated:
obs = env.reset() observation, info = env.reset()
``` ```
When using `dm_control` tasks we expect the `env_id` to be specified as `dmc:domain_name-task_name` or for manipulation
tasks as `dmc:manipulation-environment_name`. For `metaworld` tasks, we require the structure `metaworld:env_id-v2`, our
custom tasks and standard gym environments can be created without prefixes.
### Black-box Environments ### Black-box Environments
All environments provide by default the cumulative episode reward, this can however be changed if necessary. Optionally, All environments provide by default the cumulative episode reward, this can however be changed if necessary. Optionally, each environment returns all collected information from each step as part of the infos. This information is, however, mainly meant for debugging as well as logging and not for training.
each environment returns all collected information from each step as part of the infos. This information is, however,
mainly meant for debugging as well as logging and not for training.
|Key| Description|Type | Key | Description | Type |
|---|---|---| | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
`positions`| Generated trajectory from MP | Optional | `positions` | Generated trajectory from MP | Optional |
`velocities`| Generated trajectory from MP | Optional | `velocities` | Generated trajectory from MP | Optional |
`step_actions`| Step-wise executed action based on controller output | Optional | `step_actions` | Step-wise executed action based on controller output | Optional |
`step_observations`| Step-wise intermediate observations | Optional | `step_observations` | Step-wise intermediate observations | Optional |
`step_rewards`| Step-wise rewards | Optional | `step_rewards` | Step-wise rewards | Optional |
`trajectory_length`| Total number of environment interactions | Always | `trajectory_length` | Total number of environment interactions | Always |
`other`| All other information from the underlying environment are returned as a list with length `trajectory_length` maintaining the original key. In case some information are not provided every time step, the missing values are filled with `None`. | Always | `other` | All other information from the underlying environment are returned as a list with length `trajectory_length` maintaining the original key. In case some information are not provided every time step, the missing values are filled with `None`. | Always |
Existing MP tasks can be created the same way as above. Just keep in mind, calling `step()` executes a full trajectory. Existing MP tasks can be created the same way as above. The namespace of a MP-variant of an environment is given by `<original namespace>_<MP name>/`.
Just keep in mind, calling `step()` executes a full trajectory.
> **Note:** > **Note:**
> Currently, we are also in the process of enabling replanning as well as learning of sub-trajectories. > Currently, we are also in the process of enabling replanning as well as learning of sub-trajectories.
@@ -105,30 +114,38 @@ Existing MP tasks can be created the same way as above. Just keep in mind, calli
> Feel free to try it and open an issue with any problems that occur. > Feel free to try it and open an issue with any problems that occur.
```python ```python
import gymnasium as gym
import fancy_gym import fancy_gym
env = fancy_gym.make('Reacher5dProMP-v0', seed=1) env = gym.make('fancy_ProMP/Reacher5d-v0')
# or env = gym.make('metaworld_ProDMP/reach-v2')
# or env = gym.make('dm_control_DMP/ball_in_cup-catch-v0')
# or env = gym.make('gym_ProMP/Reacher-v2') # mp versions of envs added directly by gymnasium are in the gym_<MP-type> NS
# render() can be called once in the beginning with all necessary arguments. # render() can be called once in the beginning with all necessary arguments.
# To turn it of again just call render() without any arguments. # To turn it of again just call render() without any arguments.
env.render(mode='human') env.render(mode='human')
# This returns the context information, not the full state observation # This returns the context information, not the full state observation
obs = env.reset() observation, info = env.reset(seed=1)
for i in range(5): for i in range(5):
action = env.action_space.sample() action = env.action_space.sample()
obs, reward, done, info = env.step(action) observation, reward, terminated, truncated, info = env.step(action)
# Done is always True as we are working on the episode level, hence we always reset() # terminated or truncated is always True as we are working on the episode level, hence we always reset()
obs = env.reset() observation, info = env.reset()
``` ```
To show all available environments, we provide some additional convenience variables. All of them return a dictionary To show all available environments, we provide some additional convenience variables. All of them return a dictionary
with two keys `DMP` and `ProMP` that store a list of available environment ids. with the keys `DMP`, `ProMP`, `ProDMP` and `all` that store a list of available environment ids.
```python ```python
import fancy_gym import fancy_gym
print("All Black-box tasks:")
print(fancy_gym.ALL_MOVEMENT_PRIMITIVE_ENVIRONMENTS)
print("Fancy Black-box tasks:") print("Fancy Black-box tasks:")
print(fancy_gym.ALL_FANCY_MOVEMENT_PRIMITIVE_ENVIRONMENTS) print(fancy_gym.ALL_FANCY_MOVEMENT_PRIMITIVE_ENVIRONMENTS)
@@ -140,6 +157,9 @@ print(fancy_gym.ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS)
print("MetaWorld Black-box tasks:") print("MetaWorld Black-box tasks:")
print(fancy_gym.ALL_METAWORLD_MOVEMENT_PRIMITIVE_ENVIRONMENTS) print(fancy_gym.ALL_METAWORLD_MOVEMENT_PRIMITIVE_ENVIRONMENTS)
print("If you add custom envs, their mp versions will be found in:")
print(fancy_gym.MOVEMENT_PRIMITIVE_ENVIRONMENTS_FOR_NS['<my_custom_namespace>'])
``` ```
### How to create a new MP task ### How to create a new MP task
@@ -151,23 +171,27 @@ hand, the following [interface](fancy_gym/black_box/raw_interface_wrapper.py) ne
from abc import abstractmethod from abc import abstractmethod
from typing import Union, Tuple from typing import Union, Tuple
import gym import gymnasium as gym
import numpy as np import numpy as np
class RawInterfaceWrapper(gym.Wrapper): class RawInterfaceWrapper(gym.Wrapper):
mp_config = {
'ProMP': {},
'DMP': {},
'ProDMP': {},
}
@property @property
def context_mask(self) -> np.ndarray: def context_mask(self) -> np.ndarray:
""" """
Returns boolean mask of the same shape as the observation space. Returns boolean mask of the same shape as the observation space.
It determines whether the observation is returned for the contextual case or not. It determines whether the observation is returned for the contextual case or not.
This effectively allows to filter unwanted or unnecessary observations from the full step-based case. This effectively allows to filter unwanted or unnecessary observations from the full step-based case.
E.g. Velocities starting at 0 are only changing after the first action. Given we only receive the E.g. Velocities starting at 0 are only changing after the first action. Given we only receive the
context/part of the first observation, the velocities are not necessary in the observation for the task. context/part of the first observation, the velocities are not necessary in the observation for the task.
Returns: Returns:
bool array representing the indices of the observations bool array representing the indices of the observations
""" """
return np.ones(self.env.observation_space.shape[0], dtype=bool) return np.ones(self.env.observation_space.shape[0], dtype=bool)
@@ -197,34 +221,91 @@ class RawInterfaceWrapper(gym.Wrapper):
``` ```
Default configurations for MPs can be overitten by defining attributes in mp_config.
Available parameters are documented in the [MP_PyTorch Userguide](https://github.com/ALRhub/MP_PyTorch/blob/main/doc/README.md).
```python
class RawInterfaceWrapper(gym.Wrapper):
mp_config = {
'ProMP': {
'phase_generator_kwargs': {
'phase_generator_type': 'linear'
# When selecting another generator type, the default configuration will not be merged for the attribute.
},
'controller_kwargs': {
'p_gains': 0.5 * np.array([1.0, 4.0, 2.0, 4.0, 1.0, 4.0, 1.0]),
'd_gains': 0.5 * np.array([0.1, 0.4, 0.2, 0.4, 0.1, 0.4, 0.1]),
},
'basis_generator_kwargs': {
'num_basis': 3,
'num_basis_zero_start': 1,
'num_basis_zero_goal': 1,
},
},
'DMP': {},
'ProDMP': {}.
}
[...]
```
If you created a new task wrapper, feel free to open a PR, so we can integrate it for others to use as well. Without the If you created a new task wrapper, feel free to open a PR, so we can integrate it for others to use as well. Without the
integration the task can still be used. A rough outline can be shown here, for more details we recommend having a look integration the task can still be used. A rough outline can be shown here, for more details we recommend having a look
at the [examples](fancy_gym/examples/). at the [examples](fancy_gym/examples/).
If the step-based is already registered with gym, you can simply do the following:
```python ```python
import fancy_gym fancy_gym.upgrade(
id='custom/cool_new_env-v0',
mp_wrapper=my_custom_MPWrapper
)
```
# Base environment name, according to structure of above example If the step-based is not yet registered with gym we can add both the step-based and MP-versions via
base_env_id = "dmc:ball_in_cup-catch"
# Replace this wrapper with the custom wrapper for your environment by inheriting from the RawInferfaceWrapper. ```python
# You can also add other gym.Wrappers in case they are needed, fancy_gym.register(
# e.g. gym.wrappers.FlattenObservation for dict observations id='custom/cool_new_env-v0',
wrappers = [fancy_gym.dmc.suite.ball_in_cup.MPWrapper] entry_point=my_custom_env,
kwargs = {...} mp_wrapper=my_custom_MPWrapper
env = fancy_gym.make_bb(base_env_id, wrappers=wrappers, seed=0, **kwargs) )
```
From this point on, you can access MP-version of your environments via
```python
env = gym.make('custom_ProDMP/cool_new_env-v0')
rewards = 0 rewards = 0
obs = env.reset() observation, info = env.reset()
# number of samples/full trajectories (multiple environment steps) # number of samples/full trajectories (multiple environment steps)
for i in range(5): for i in range(5):
ac = env.action_space.sample() ac = env.action_space.sample()
obs, reward, done, info = env.step(ac) observation, reward, terminated, truncated, info = env.step(ac)
rewards += reward rewards += reward
if done: if terminated or truncated:
print(base_env_id, rewards) print(rewards)
rewards = 0 rewards = 0
obs = env.reset() observation, info = env.reset()
``` ```
## Citing the Project
To cite this repository in publications:
```bibtex
@software{fancy_gym,
title = {Fancy Gym},
author = {Otto, Fabian and Celik, Onur and Roth, Dominik and Zhou, Hongyi},
abstract = {Fancy Gym: Unifying interface for various RL benchmarks with support for Black Box approaches.},
url = {https://github.com/ALRhub/fancy_gym},
organization = {Autonomous Learning Robots Lab (ALR) at KIT},
}
```
## Icon Attribution
The icon is based on the [Gymnasium](https://github.com/Farama-Foundation/Gymnasium) icon as can be found [here](https://gymnasium.farama.org/_static/img/gymnasium_black.svg).
+15 -11
View File
@@ -1,13 +1,17 @@
from fancy_gym import dmc, meta, open_ai from fancy_gym import dmc, meta, open_ai
from fancy_gym.utils.make_env_helpers import make, make_bb, make_rank from fancy_gym import envs as fancy
from .dmc import ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS from fancy_gym.utils.make_env_helpers import make_bb
# Convenience function for all MP environments from .envs.registry import register, upgrade
from .envs import ALL_FANCY_MOVEMENT_PRIMITIVE_ENVIRONMENTS from .envs.registry import ALL_MOVEMENT_PRIMITIVE_ENVIRONMENTS, MOVEMENT_PRIMITIVE_ENVIRONMENTS_FOR_NS
from .meta import ALL_METAWORLD_MOVEMENT_PRIMITIVE_ENVIRONMENTS
from .open_ai import ALL_GYM_MOVEMENT_PRIMITIVE_ENVIRONMENTS
ALL_MOVEMENT_PRIMITIVE_ENVIRONMENTS = { ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS = MOVEMENT_PRIMITIVE_ENVIRONMENTS_FOR_NS['dm_control']
key: value + ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS[key] + ALL_FANCY_MOVEMENT_PRIMITIVE_ENVIRONMENTS = MOVEMENT_PRIMITIVE_ENVIRONMENTS_FOR_NS['fancy']
ALL_GYM_MOVEMENT_PRIMITIVE_ENVIRONMENTS[key] + ALL_METAWORLD_MOVEMENT_PRIMITIVE_ENVIRONMENTS = MOVEMENT_PRIMITIVE_ENVIRONMENTS_FOR_NS['metaworld']
ALL_METAWORLD_MOVEMENT_PRIMITIVE_ENVIRONMENTS[key] ALL_GYM_MOVEMENT_PRIMITIVE_ENVIRONMENTS = MOVEMENT_PRIMITIVE_ENVIRONMENTS_FOR_NS['gym']
for key, value in ALL_FANCY_MOVEMENT_PRIMITIVE_ENVIRONMENTS.items()}
def make(*args, **kwargs):
"""
As part of the refactor of Fancy Gym and upgrade to gymnasium the use of fancy_gym.make has been discontinued. Regular gym.make should be used instead. For more details check out the github README. If your codebase was build for older versions of Fancy Gym and relies on the old behavior and dependency versions, please check out the legacy branch.
"""
raise Exception('As part of the refactor of Fancy Gym and upgrade to gymnasium the use of fancy_gym.make has been discontinued. Regular gym.make should be used instead. For more details check out the github README. If your codebase was build for older versions of Fancy Gym and relies on the old behavior and dependency versions, please check out the legacy branch.')
+37 -31
View File
@@ -1,8 +1,9 @@
from typing import Tuple, Optional, Callable from typing import Tuple, Optional, Callable, Dict, Any
import gym import gymnasium as gym
import numpy as np import numpy as np
from gym import spaces from gymnasium import spaces
from gymnasium.core import ObsType
from mp_pytorch.mp.mp_interfaces import MPInterface from mp_pytorch.mp.mp_interfaces import MPInterface
from fancy_gym.black_box.controller.base_controller import BaseController from fancy_gym.black_box.controller.base_controller import BaseController
@@ -67,13 +68,14 @@ class BlackBoxWrapper(gym.ObservationWrapper):
self.reward_aggregation = reward_aggregation self.reward_aggregation = reward_aggregation
# spaces # spaces
self.return_context_observation = not (learn_sub_trajectories or self.do_replanning) self.return_context_observation = not (
learn_sub_trajectories or self.do_replanning)
self.traj_gen_action_space = self._get_traj_gen_action_space() self.traj_gen_action_space = self._get_traj_gen_action_space()
self.action_space = self._get_action_space() self.action_space = self._get_action_space()
self.observation_space = self._get_observation_space() self.observation_space = self._get_observation_space()
# rendering # rendering
self.render_kwargs = {} self.do_render = False
self.verbose = verbose self.verbose = verbose
# condition value # condition value
@@ -99,14 +101,17 @@ class BlackBoxWrapper(gym.ObservationWrapper):
# If we do not do this, the traj_gen assumes we are continuing the trajectory. # If we do not do this, the traj_gen assumes we are continuing the trajectory.
self.traj_gen.reset() self.traj_gen.reset()
clipped_params = np.clip(action, self.traj_gen_action_space.low, self.traj_gen_action_space.high) clipped_params = np.clip(
action, self.traj_gen_action_space.low, self.traj_gen_action_space.high)
self.traj_gen.set_params(clipped_params) self.traj_gen.set_params(clipped_params)
init_time = np.array(0 if not self.do_replanning else self.current_traj_steps * self.dt) init_time = np.array(
0 if not self.do_replanning else self.current_traj_steps * self.dt)
condition_pos = self.condition_pos if self.condition_pos is not None else self.current_pos condition_pos = self.condition_pos if self.condition_pos is not None else self.env.get_wrapper_attr('current_pos')
condition_vel = self.condition_vel if self.condition_vel is not None else self.current_vel condition_vel = self.condition_vel if self.condition_vel is not None else self.env.get_wrapper_attr('current_vel')
self.traj_gen.set_initial_conditions(init_time, condition_pos, condition_vel) self.traj_gen.set_initial_conditions(
init_time, condition_pos, condition_vel)
self.traj_gen.set_duration(duration, self.dt) self.traj_gen.set_duration(duration, self.dt)
position = get_numpy(self.traj_gen.get_traj_pos()) position = get_numpy(self.traj_gen.get_traj_pos())
@@ -153,24 +158,27 @@ class BlackBoxWrapper(gym.ObservationWrapper):
trajectory_length = len(position) trajectory_length = len(position)
rewards = np.zeros(shape=(trajectory_length,)) rewards = np.zeros(shape=(trajectory_length,))
if self.verbose >= 2: if self.verbose >= 2:
actions = np.zeros(shape=(trajectory_length,) + self.env.action_space.shape) actions = np.zeros(shape=(trajectory_length,) +
self.env.action_space.shape)
observations = np.zeros(shape=(trajectory_length,) + self.env.observation_space.shape, observations = np.zeros(shape=(trajectory_length,) + self.env.observation_space.shape,
dtype=self.env.observation_space.dtype) dtype=self.env.observation_space.dtype)
infos = dict() infos = dict()
done = False terminated, truncated = False, False
if not traj_is_valid: if not traj_is_valid:
obs, trajectory_return, done, infos = self.env.invalid_traj_callback(action, position, velocity, obs, trajectory_return, terminated, truncated, infos = self.env.invalid_traj_callback(action, position, velocity,
self.return_context_observation, self.return_context_observation, self.tau_bound, self.delay_bound)
self.tau_bound, self.delay_bound) return self.observation(obs), trajectory_return, terminated, truncated, infos
return self.observation(obs), trajectory_return, done, infos
self.plan_steps += 1 self.plan_steps += 1
for t, (pos, vel) in enumerate(zip(position, velocity)): for t, (pos, vel) in enumerate(zip(position, velocity)):
step_action = self.tracking_controller.get_action(pos, vel, self.current_pos, self.current_vel) step_action = self.tracking_controller.get_action(
c_action = np.clip(step_action, self.env.action_space.low, self.env.action_space.high) pos, vel, self.env.get_wrapper_attr('current_pos'), self.env.get_wrapper_attr('current_vel'))
obs, c_reward, done, info = self.env.step(c_action) c_action = np.clip(
step_action, self.env.action_space.low, self.env.action_space.high)
obs, c_reward, terminated, truncated, info = self.env.step(
c_action)
rewards[t] = c_reward rewards[t] = c_reward
if self.verbose >= 2: if self.verbose >= 2:
@@ -182,12 +190,11 @@ class BlackBoxWrapper(gym.ObservationWrapper):
elems[t] = v elems[t] = v
infos[k] = elems infos[k] = elems
if self.render_kwargs: if self.do_render:
self.env.render(**self.render_kwargs) self.env.render()
if done or (self.replanning_schedule(self.current_pos, self.current_vel, obs, c_action,
t + 1 + self.current_traj_steps) if terminated or truncated or (self.replanning_schedule(self.env.get_wrapper_attr('current_pos'), self.env.get_wrapper_attr('current_vel'), obs, c_action, t + 1 + self.current_traj_steps) and self.plan_steps < self.max_planning_times):
and self.plan_steps < self.max_planning_times):
if self.condition_on_desired: if self.condition_on_desired:
self.condition_pos = pos self.condition_pos = pos
@@ -207,17 +214,16 @@ class BlackBoxWrapper(gym.ObservationWrapper):
infos['trajectory_length'] = t + 1 infos['trajectory_length'] = t + 1
trajectory_return = self.reward_aggregation(rewards[:t + 1]) trajectory_return = self.reward_aggregation(rewards[:t + 1])
return self.observation(obs), trajectory_return, done, infos return self.observation(obs), trajectory_return, terminated, truncated, infos
def render(self, **kwargs): def render(self):
"""Only set render options here, such that they can be used during the rollout. self.do_render = True
This only needs to be called once"""
self.render_kwargs = kwargs
def reset(self, *, seed: Optional[int] = None, return_info: bool = False, options: Optional[dict] = None): def reset(self, *, seed: Optional[int] = None, options: Optional[Dict[str, Any]] = None) \
-> Tuple[ObsType, Dict[str, Any]]:
self.current_traj_steps = 0 self.current_traj_steps = 0
self.plan_steps = 0 self.plan_steps = 0
self.traj_gen.reset() self.traj_gen.reset()
self.condition_pos = None self.condition_pos = None
self.condition_vel = None self.condition_vel = None
return super(BlackBoxWrapper, self).reset() return super(BlackBoxWrapper, self).reset(seed=seed, options=options)
@@ -11,11 +11,11 @@ def get_controller(controller_type: str, **kwargs):
if controller_type == "motor": if controller_type == "motor":
return PDController(**kwargs) return PDController(**kwargs)
elif controller_type == "velocity": elif controller_type == "velocity":
return VelController() return VelController(**kwargs)
elif controller_type == "position": elif controller_type == "position":
return PosController() return PosController(**kwargs)
elif controller_type == "metaworld": elif controller_type == "metaworld":
return MetaWorldController() return MetaWorldController(**kwargs)
else: else:
raise ValueError(f"Specified controller type {controller_type} not supported, " raise ValueError(f"Specified controller type {controller_type} not supported, "
f"please choose one of {ALL_TYPES}.") f"please choose one of {ALL_TYPES}.")
+4 -3
View File
@@ -1,6 +1,6 @@
from typing import Union, Tuple from typing import Union, Tuple
import gym import gymnasium as gym
import numpy as np import numpy as np
from mp_pytorch.mp.mp_interfaces import MPInterface from mp_pytorch.mp.mp_interfaces import MPInterface
@@ -114,7 +114,8 @@ class RawInterfaceWrapper(gym.Wrapper):
Returns: Returns:
obs: artificial observation if the trajectory is invalid, by default a zero vector obs: artificial observation if the trajectory is invalid, by default a zero vector
reward: artificial reward if the trajectory is invalid, by default 0 reward: artificial reward if the trajectory is invalid, by default 0
done: artificial done if the trajectory is invalid, by default True terminated: artificial terminated if the trajectory is invalid, by default True
truncated: artificial truncated if the trajectory is invalid, by default False
info: artificial info if the trajectory is invalid, by default empty dict info: artificial info if the trajectory is invalid, by default empty dict
""" """
return np.zeros(1), 0, True, {} return np.zeros(1), 0, True, False, {}
+8 -8
View File
@@ -9,11 +9,11 @@ environments in order to use our Motion Primitive gym interface with them.
[//]: <> (These environments are wrapped-versions of their Deep Mind Control Suite &#40;DMC&#41; counterparts. Given most task can be) [//]: <> (These environments are wrapped-versions of their Deep Mind Control Suite &#40;DMC&#41; counterparts. Given most task can be)
[//]: <> (solved in shorter horizon lengths than the original 1000 steps, we often shorten the episodes for those task.) [//]: <> (solved in shorter horizon lengths than the original 1000 steps, we often shorten the episodes for those task.)
|Name| Description|Trajectory Horizon|Action Dimension|Context Dimension | Name | Description | Trajectory Horizon | Action Dimension | Context Dimension |
|---|---|---|---|---| | ---------------------------------------- | ------------------------------------------------------------------------------ | ------------------ | ---------------- | ----------------- |
|`dmc_ball_in_cup-catch_promp-v0`| A ProMP wrapped version of the "catch" task for the "ball_in_cup" environment. | 1000 | 10 | 2 | `dm_control_ProDMP/ball_in_cup-catch-v0` | A ProMP wrapped version of the "catch" task for the "ball_in_cup" environment. | 1000 | 10 | 2 |
|`dmc_ball_in_cup-catch_dmp-v0`| A DMP wrapped version of the "catch" task for the "ball_in_cup" environment. | 1000| 10 | 2 | `dm_control_DMP/ball_in_cup-catch-v0` | A DMP wrapped version of the "catch" task for the "ball_in_cup" environment. | 1000 | 10 | 2 |
|`dmc_reacher-easy_promp-v0`| A ProMP wrapped version of the "easy" task for the "reacher" environment. | 1000 | 10 | 4 | `dm_control_ProDMP/reacher-easy-v0` | A ProMP wrapped version of the "easy" task for the "reacher" environment. | 1000 | 10 | 4 |
|`dmc_reacher-easy_dmp-v0`| A DMP wrapped version of the "easy" task for the "reacher" environment. | 1000| 10 | 4 | `dm_control_DMP/reacher-easy-v0` | A DMP wrapped version of the "easy" task for the "reacher" environment. | 1000 | 10 | 4 |
|`dmc_reacher-hard_promp-v0`| A ProMP wrapped version of the "hard" task for the "reacher" environment.| 1000 | 10 | 4 | `dm_control_ProDMP/reacher-hard-v0` | A ProMP wrapped version of the "hard" task for the "reacher" environment. | 1000 | 10 | 4 |
|`dmc_reacher-hard_dmp-v0`| A DMP wrapped version of the "hard" task for the "reacher" environment. | 1000 | 10 | 4 | `dm_control_DMP/reacher-hard-v0` | A DMP wrapped version of the "hard" task for the "reacher" environment. | 1000 | 10 | 4 |
+33 -217
View File
@@ -1,245 +1,61 @@
from gym.envs.registration import register
from copy import deepcopy from copy import deepcopy
from gymnasium.wrappers import FlattenObservation
from gymnasium.envs.registration import register
from ..envs.registry import register
from . import manipulation, suite from . import manipulation, suite
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS = {"DMP": [], "ProMP": [], "ProDMP": []}
DEFAULT_BB_DICT_ProMP = {
"name": 'EnvName',
"wrappers": [],
"trajectory_generator_kwargs": {
'trajectory_generator_type': 'promp'
},
"phase_generator_kwargs": {
'phase_generator_type': 'linear'
},
"controller_kwargs": {
'controller_type': 'motor',
"p_gains": 50.,
"d_gains": 1.,
},
"basis_generator_kwargs": {
'basis_generator_type': 'zero_rbf',
'num_basis': 5,
'num_basis_zero_start': 1
}
}
DEFAULT_BB_DICT_DMP = {
"name": 'EnvName',
"wrappers": [],
"trajectory_generator_kwargs": {
'trajectory_generator_type': 'dmp'
},
"phase_generator_kwargs": {
'phase_generator_type': 'exp'
},
"controller_kwargs": {
'controller_type': 'motor',
"p_gains": 50.,
"d_gains": 1.,
},
"basis_generator_kwargs": {
'basis_generator_type': 'rbf',
'num_basis': 5
}
}
# DeepMind Control Suite (DMC) # DeepMind Control Suite (DMC)
kwargs_dict_bic_dmp = deepcopy(DEFAULT_BB_DICT_DMP)
kwargs_dict_bic_dmp['name'] = f"dmc:ball_in_cup-catch"
kwargs_dict_bic_dmp['wrappers'].append(suite.ball_in_cup.MPWrapper)
# bandwidth_factor=2
kwargs_dict_bic_dmp['phase_generator_kwargs']['alpha_phase'] = 2
kwargs_dict_bic_dmp['trajectory_generator_kwargs']['weight_scale'] = 10 # TODO: weight scale 1, but goal scale 0.1
register( register(
id=f'dmc_ball_in_cup-catch_dmp-v0', id=f"dm_control/ball_in_cup-catch-v0",
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper', register_step_based=False,
kwargs=kwargs_dict_bic_dmp mp_wrapper=suite.ball_in_cup.MPWrapper,
add_mp_types=['DMP', 'ProMP'],
) )
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["DMP"].append("dmc_ball_in_cup-catch_dmp-v0")
kwargs_dict_bic_promp = deepcopy(DEFAULT_BB_DICT_ProMP)
kwargs_dict_bic_promp['name'] = f"dmc:ball_in_cup-catch"
kwargs_dict_bic_promp['wrappers'].append(suite.ball_in_cup.MPWrapper)
register( register(
id=f'dmc_ball_in_cup-catch_promp-v0', id=f"dm_control/reacher-easy-v0",
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper', register_step_based=False,
kwargs=kwargs_dict_bic_promp mp_wrapper=suite.reacher.MPWrapper,
add_mp_types=['DMP', 'ProMP'],
) )
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["ProMP"].append("dmc_ball_in_cup-catch_promp-v0")
kwargs_dict_reacher_easy_dmp = deepcopy(DEFAULT_BB_DICT_DMP)
kwargs_dict_reacher_easy_dmp['name'] = f"dmc:reacher-easy"
kwargs_dict_reacher_easy_dmp['wrappers'].append(suite.reacher.MPWrapper)
# bandwidth_factor=2
kwargs_dict_reacher_easy_dmp['phase_generator_kwargs']['alpha_phase'] = 2
# TODO: weight scale 50, but goal scale 0.1
kwargs_dict_reacher_easy_dmp['trajectory_generator_kwargs']['weight_scale'] = 500
register( register(
id=f'dmc_reacher-easy_dmp-v0', id=f"dm_control/reacher-hard-v0",
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper', register_step_based=False,
kwargs=kwargs_dict_bic_dmp mp_wrapper=suite.reacher.MPWrapper,
add_mp_types=['DMP', 'ProMP'],
) )
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["DMP"].append("dmc_reacher-easy_dmp-v0")
kwargs_dict_reacher_easy_promp = deepcopy(DEFAULT_BB_DICT_ProMP)
kwargs_dict_reacher_easy_promp['name'] = f"dmc:reacher-easy"
kwargs_dict_reacher_easy_promp['wrappers'].append(suite.reacher.MPWrapper)
kwargs_dict_reacher_easy_promp['trajectory_generator_kwargs']['weight_scale'] = 0.2
register(
id=f'dmc_reacher-easy_promp-v0',
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper',
kwargs=kwargs_dict_reacher_easy_promp
)
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["ProMP"].append("dmc_reacher-easy_promp-v0")
kwargs_dict_reacher_hard_dmp = deepcopy(DEFAULT_BB_DICT_DMP)
kwargs_dict_reacher_hard_dmp['name'] = f"dmc:reacher-hard"
kwargs_dict_reacher_hard_dmp['wrappers'].append(suite.reacher.MPWrapper)
# bandwidth_factor = 2
kwargs_dict_reacher_hard_dmp['phase_generator_kwargs']['alpha_phase'] = 2
# TODO: weight scale 50, but goal scale 0.1
kwargs_dict_reacher_hard_dmp['trajectory_generator_kwargs']['weight_scale'] = 500
register(
id=f'dmc_reacher-hard_dmp-v0',
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper',
kwargs=kwargs_dict_reacher_hard_dmp
)
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["DMP"].append("dmc_reacher-hard_dmp-v0")
kwargs_dict_reacher_hard_promp = deepcopy(DEFAULT_BB_DICT_ProMP)
kwargs_dict_reacher_hard_promp['name'] = f"dmc:reacher-hard"
kwargs_dict_reacher_hard_promp['wrappers'].append(suite.reacher.MPWrapper)
kwargs_dict_reacher_hard_promp['trajectory_generator_kwargs']['weight_scale'] = 0.2
register(
id=f'dmc_reacher-hard_promp-v0',
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper',
kwargs=kwargs_dict_reacher_hard_promp
)
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["ProMP"].append("dmc_reacher-hard_promp-v0")
_dmc_cartpole_tasks = ["balance", "balance_sparse", "swingup", "swingup_sparse"] _dmc_cartpole_tasks = ["balance", "balance_sparse", "swingup", "swingup_sparse"]
for _task in _dmc_cartpole_tasks: for _task in _dmc_cartpole_tasks:
_env_id = f'dmc_cartpole-{_task}_dmp-v0'
kwargs_dict_cartpole_dmp = deepcopy(DEFAULT_BB_DICT_DMP)
kwargs_dict_cartpole_dmp['name'] = f"dmc:cartpole-{_task}"
kwargs_dict_cartpole_dmp['wrappers'].append(suite.cartpole.MPWrapper)
# bandwidth_factor = 2
kwargs_dict_cartpole_dmp['phase_generator_kwargs']['alpha_phase'] = 2
# TODO: weight scale 50, but goal scale 0.1
kwargs_dict_cartpole_dmp['trajectory_generator_kwargs']['weight_scale'] = 500
kwargs_dict_cartpole_dmp['controller_kwargs']['p_gains'] = 10
kwargs_dict_cartpole_dmp['controller_kwargs']['d_gains'] = 10
register( register(
id=_env_id, id=f'dm_control/cartpole-{_task}-v0',
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper', register_step_based=False,
kwargs=kwargs_dict_cartpole_dmp mp_wrapper=suite.cartpole.MPWrapper,
add_mp_types=['DMP', 'ProMP'],
) )
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["DMP"].append(_env_id)
_env_id = f'dmc_cartpole-{_task}_promp-v0'
kwargs_dict_cartpole_promp = deepcopy(DEFAULT_BB_DICT_ProMP)
kwargs_dict_cartpole_promp['name'] = f"dmc:cartpole-{_task}"
kwargs_dict_cartpole_promp['wrappers'].append(suite.cartpole.MPWrapper)
kwargs_dict_cartpole_promp['controller_kwargs']['p_gains'] = 10
kwargs_dict_cartpole_promp['controller_kwargs']['d_gains'] = 10
kwargs_dict_cartpole_promp['trajectory_generator_kwargs']['weight_scale'] = 0.2
register(
id=_env_id,
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper',
kwargs=kwargs_dict_cartpole_promp
)
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["ProMP"].append(_env_id)
kwargs_dict_cartpole2poles_dmp = deepcopy(DEFAULT_BB_DICT_DMP)
kwargs_dict_cartpole2poles_dmp['name'] = f"dmc:cartpole-two_poles"
kwargs_dict_cartpole2poles_dmp['wrappers'].append(suite.cartpole.TwoPolesMPWrapper)
# bandwidth_factor = 2
kwargs_dict_cartpole2poles_dmp['phase_generator_kwargs']['alpha_phase'] = 2
# TODO: weight scale 50, but goal scale 0.1
kwargs_dict_cartpole2poles_dmp['trajectory_generator_kwargs']['weight_scale'] = 500
kwargs_dict_cartpole2poles_dmp['controller_kwargs']['p_gains'] = 10
kwargs_dict_cartpole2poles_dmp['controller_kwargs']['d_gains'] = 10
_env_id = f'dmc_cartpole-two_poles_dmp-v0'
register( register(
id=_env_id, id=f"dm_control/cartpole-two_poles-v0",
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper', register_step_based=False,
kwargs=kwargs_dict_cartpole2poles_dmp mp_wrapper=suite.cartpole.TwoPolesMPWrapper,
add_mp_types=['DMP', 'ProMP'],
) )
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["DMP"].append(_env_id)
kwargs_dict_cartpole2poles_promp = deepcopy(DEFAULT_BB_DICT_ProMP)
kwargs_dict_cartpole2poles_promp['name'] = f"dmc:cartpole-two_poles"
kwargs_dict_cartpole2poles_promp['wrappers'].append(suite.cartpole.TwoPolesMPWrapper)
kwargs_dict_cartpole2poles_promp['controller_kwargs']['p_gains'] = 10
kwargs_dict_cartpole2poles_promp['controller_kwargs']['d_gains'] = 10
kwargs_dict_cartpole2poles_promp['trajectory_generator_kwargs']['weight_scale'] = 0.2
_env_id = f'dmc_cartpole-two_poles_promp-v0'
register( register(
id=_env_id, id=f"dm_control/cartpole-three_poles-v0",
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper', register_step_based=False,
kwargs=kwargs_dict_cartpole2poles_promp mp_wrapper=suite.cartpole.ThreePolesMPWrapper,
add_mp_types=['DMP', 'ProMP'],
) )
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["ProMP"].append(_env_id)
kwargs_dict_cartpole3poles_dmp = deepcopy(DEFAULT_BB_DICT_DMP)
kwargs_dict_cartpole3poles_dmp['name'] = f"dmc:cartpole-three_poles"
kwargs_dict_cartpole3poles_dmp['wrappers'].append(suite.cartpole.ThreePolesMPWrapper)
# bandwidth_factor = 2
kwargs_dict_cartpole3poles_dmp['phase_generator_kwargs']['alpha_phase'] = 2
# TODO: weight scale 50, but goal scale 0.1
kwargs_dict_cartpole3poles_dmp['trajectory_generator_kwargs']['weight_scale'] = 500
kwargs_dict_cartpole3poles_dmp['controller_kwargs']['p_gains'] = 10
kwargs_dict_cartpole3poles_dmp['controller_kwargs']['d_gains'] = 10
_env_id = f'dmc_cartpole-three_poles_dmp-v0'
register(
id=_env_id,
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper',
kwargs=kwargs_dict_cartpole3poles_dmp
)
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["DMP"].append(_env_id)
kwargs_dict_cartpole3poles_promp = deepcopy(DEFAULT_BB_DICT_ProMP)
kwargs_dict_cartpole3poles_promp['name'] = f"dmc:cartpole-three_poles"
kwargs_dict_cartpole3poles_promp['wrappers'].append(suite.cartpole.ThreePolesMPWrapper)
kwargs_dict_cartpole3poles_promp['controller_kwargs']['p_gains'] = 10
kwargs_dict_cartpole3poles_promp['controller_kwargs']['d_gains'] = 10
kwargs_dict_cartpole3poles_promp['trajectory_generator_kwargs']['weight_scale'] = 0.2
_env_id = f'dmc_cartpole-three_poles_promp-v0'
register(
id=_env_id,
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper',
kwargs=kwargs_dict_cartpole3poles_promp
)
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["ProMP"].append(_env_id)
# DeepMind Manipulation # DeepMind Manipulation
kwargs_dict_mani_reach_site_features_dmp = deepcopy(DEFAULT_BB_DICT_DMP)
kwargs_dict_mani_reach_site_features_dmp['name'] = f"dmc:manipulation-reach_site_features"
kwargs_dict_mani_reach_site_features_dmp['wrappers'].append(manipulation.reach_site.MPWrapper)
kwargs_dict_mani_reach_site_features_dmp['phase_generator_kwargs']['alpha_phase'] = 2
# TODO: weight scale 50, but goal scale 0.1
kwargs_dict_mani_reach_site_features_dmp['trajectory_generator_kwargs']['weight_scale'] = 500
kwargs_dict_mani_reach_site_features_dmp['controller_kwargs']['controller_type'] = 'velocity'
register( register(
id=f'dmc_manipulation-reach_site_dmp-v0', id=f"dm_control/reach_site_features-v0",
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper', register_step_based=False,
kwargs=kwargs_dict_mani_reach_site_features_dmp mp_wrapper=manipulation.reach_site.MPWrapper,
add_mp_types=['DMP', 'ProMP'],
) )
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["DMP"].append("dmc_manipulation-reach_site_dmp-v0")
kwargs_dict_mani_reach_site_features_promp = deepcopy(DEFAULT_BB_DICT_ProMP)
kwargs_dict_mani_reach_site_features_promp['name'] = f"dmc:manipulation-reach_site_features"
kwargs_dict_mani_reach_site_features_promp['wrappers'].append(manipulation.reach_site.MPWrapper)
kwargs_dict_mani_reach_site_features_promp['trajectory_generator_kwargs']['weight_scale'] = 0.2
kwargs_dict_mani_reach_site_features_promp['controller_kwargs']['controller_type'] = 'velocity'
register(
id=f'dmc_manipulation-reach_site_promp-v0',
entry_point='fancy_gym.utils.make_env_helpers:make_bb_env_helper',
kwargs=kwargs_dict_mani_reach_site_features_promp
)
ALL_DMC_MOVEMENT_PRIMITIVE_ENVIRONMENTS["ProMP"].append("dmc_manipulation-reach_site_promp-v0")
-186
View File
@@ -1,186 +0,0 @@
# Adopted from: https://github.com/denisyarats/dmc2gym/blob/master/dmc2gym/wrappers.py
# License: MIT
# Copyright (c) 2020 Denis Yarats
import collections
from collections.abc import MutableMapping
from typing import Any, Dict, Tuple, Optional, Union, Callable
import gym
import numpy as np
from dm_control import composer
from dm_control.rl import control
from dm_env import specs
from gym import spaces
from gym.core import ObsType
def _spec_to_box(spec):
def extract_min_max(s):
assert s.dtype == np.float64 or s.dtype == np.float32, \
f"Only float64 and float32 types are allowed, instead {s.dtype} was found"
dim = int(np.prod(s.shape))
if type(s) == specs.Array:
bound = np.inf * np.ones(dim, dtype=s.dtype)
return -bound, bound
elif type(s) == specs.BoundedArray:
zeros = np.zeros(dim, dtype=s.dtype)
return s.minimum + zeros, s.maximum + zeros
mins, maxs = [], []
for s in spec:
mn, mx = extract_min_max(s)
mins.append(mn)
maxs.append(mx)
low = np.concatenate(mins, axis=0)
high = np.concatenate(maxs, axis=0)
assert low.shape == high.shape
return spaces.Box(low, high, dtype=s.dtype)
def _flatten_obs(obs: MutableMapping):
"""
Flattens an observation of type MutableMapping, e.g. a dict to a 1D array.
Args:
obs: observation to flatten
Returns: 1D array of observation
"""
if not isinstance(obs, MutableMapping):
raise ValueError(f'Requires dict-like observations structure. {type(obs)} found.')
# Keep key order consistent for non OrderedDicts
keys = obs.keys() if isinstance(obs, collections.OrderedDict) else sorted(obs.keys())
obs_vals = [np.array([obs[key]]) if np.isscalar(obs[key]) else obs[key].ravel() for key in keys]
return np.concatenate(obs_vals)
class DMCWrapper(gym.Env):
def __init__(self,
env: Callable[[], Union[composer.Environment, control.Environment]],
):
# TODO: Currently this is required to be a function because dmc does not allow to copy composers environments
self._env = env()
# action and observation space
self._action_space = _spec_to_box([self._env.action_spec()])
self._observation_space = _spec_to_box(self._env.observation_spec().values())
self._window = None
self.id = 'dmc'
def __getattr__(self, item):
"""Propagate only non-existent properties to wrapped env."""
if item.startswith('_'):
raise AttributeError("attempted to get missing private attribute '{}'".format(item))
if item in self.__dict__:
return getattr(self, item)
return getattr(self._env, item)
def _get_obs(self, time_step):
obs = _flatten_obs(time_step.observation).astype(self.observation_space.dtype)
return obs
@property
def observation_space(self):
return self._observation_space
@property
def action_space(self):
return self._action_space
@property
def dt(self):
return self._env.control_timestep()
def seed(self, seed=None):
self._action_space.seed(seed)
self._observation_space.seed(seed)
def step(self, action) -> Tuple[np.ndarray, float, bool, Dict[str, Any]]:
assert self._action_space.contains(action)
extra = {'internal_state': self._env.physics.get_state().copy()}
time_step = self._env.step(action)
reward = time_step.reward or 0.
done = time_step.last()
obs = self._get_obs(time_step)
extra['discount'] = time_step.discount
return obs, reward, done, extra
def reset(self, *, seed: Optional[int] = None, return_info: bool = False,
options: Optional[dict] = None, ) -> Union[ObsType, Tuple[ObsType, dict]]:
time_step = self._env.reset()
obs = self._get_obs(time_step)
return obs
def render(self, mode='rgb_array', height=240, width=320, camera_id=-1, overlays=(), depth=False,
segmentation=False, scene_option=None, render_flag_overrides=None):
# assert mode == 'rgb_array', 'only support rgb_array mode, given %s' % mode
if mode == "rgb_array":
return self._env.physics.render(height=height, width=width, camera_id=camera_id, overlays=overlays,
depth=depth, segmentation=segmentation, scene_option=scene_option,
render_flag_overrides=render_flag_overrides)
# Render max available buffer size. Larger is only possible by altering the XML.
img = self._env.physics.render(height=self._env.physics.model.vis.global_.offheight,
width=self._env.physics.model.vis.global_.offwidth,
camera_id=camera_id, overlays=overlays, depth=depth, segmentation=segmentation,
scene_option=scene_option, render_flag_overrides=render_flag_overrides)
if depth:
img = np.dstack([img.astype(np.uint8)] * 3)
if mode == 'human':
try:
import cv2
if self._window is None:
self._window = cv2.namedWindow(self.id, cv2.WINDOW_AUTOSIZE)
cv2.imshow(self.id, img[..., ::-1]) # Image in BGR
cv2.waitKey(1)
except ImportError:
raise gym.error.DependencyNotInstalled("Rendering requires opencv. Run `pip install opencv-python`")
# PYGAME seems to destroy some global rendering configs from the physics render
# except ImportError:
# import pygame
# img_copy = img.copy().transpose((1, 0, 2))
# if self._window is None:
# pygame.init()
# pygame.display.init()
# self._window = pygame.display.set_mode(img_copy.shape[:2])
# self.clock = pygame.time.Clock()
#
# surf = pygame.surfarray.make_surface(img_copy)
# self._window.blit(surf, (0, 0))
# pygame.event.pump()
# self.clock.tick(30)
# pygame.display.flip()
def close(self):
super().close()
if self._window is not None:
try:
import cv2
cv2.destroyWindow(self.id)
except ImportError:
import pygame
pygame.display.quit()
pygame.quit()
@property
def reward_range(self) -> Tuple[float, float]:
reward_spec = self._env.reward_spec()
if isinstance(reward_spec, specs.BoundedArray):
return reward_spec.minimum, reward_spec.maximum
return -float('inf'), float('inf')
@property
def metadata(self):
return {'render.modes': ['human', 'rgb_array'],
'video.frames_per_second': round(1.0 / self._env.control_timestep())}
@@ -6,6 +6,28 @@ from fancy_gym.black_box.raw_interface_wrapper import RawInterfaceWrapper
class MPWrapper(RawInterfaceWrapper): class MPWrapper(RawInterfaceWrapper):
mp_config = {
'ProMP': {
'controller_kwargs': {
'p_gains': 50.0,
},
'trajectory_generator_kwargs': {
'weights_scale': 0.2,
},
},
'DMP': {
'controller_kwargs': {
'p_gains': 50.0,
},
'phase_generator': {
'alpha_phase': 2,
},
'trajectory_generator_kwargs': {
'weights_scale': 500,
},
},
'ProDMP': {},
}
@property @property
def context_mask(self) -> np.ndarray: def context_mask(self) -> np.ndarray:
@@ -35,4 +57,4 @@ class MPWrapper(RawInterfaceWrapper):
@property @property
def dt(self) -> Union[float, int]: def dt(self) -> Union[float, int]:
return self.env.dt return self.env.control_timestep()
+20 -1
View File
@@ -6,6 +6,25 @@ from fancy_gym.black_box.raw_interface_wrapper import RawInterfaceWrapper
class MPWrapper(RawInterfaceWrapper): class MPWrapper(RawInterfaceWrapper):
mp_config = {
'ProMP': {
'controller_kwargs': {
'p_gains': 50.0,
},
},
'DMP': {
'controller_kwargs': {
'p_gains': 50.0,
},
'phase_generator': {
'alpha_phase': 2,
},
'trajectory_generator_kwargs': {
'weights_scale': 10
},
},
'ProDMP': {},
}
@property @property
def context_mask(self) -> np.ndarray: def context_mask(self) -> np.ndarray:
@@ -31,4 +50,4 @@ class MPWrapper(RawInterfaceWrapper):
@property @property
def dt(self) -> Union[float, int]: def dt(self) -> Union[float, int]:
return self.env.dt return self.env.control_timestep()
+25 -1
View File
@@ -6,6 +6,30 @@ from fancy_gym.black_box.raw_interface_wrapper import RawInterfaceWrapper
class MPWrapper(RawInterfaceWrapper): class MPWrapper(RawInterfaceWrapper):
mp_config = {
'ProMP': {
'controller_kwargs': {
'p_gains': 10,
'd_gains': 10,
},
'trajectory_generator_kwargs': {
'weights_scale': 0.2,
},
},
'DMP': {
'controller_kwargs': {
'p_gains': 10,
'd_gains': 10,
},
'phase_generator': {
'alpha_phase': 2,
},
'trajectory_generator_kwargs': {
'weights_scale': 500,
},
},
'ProDMP': {},
}
def __init__(self, env, n_poles: int = 1): def __init__(self, env, n_poles: int = 1):
self.n_poles = n_poles self.n_poles = n_poles
@@ -35,7 +59,7 @@ class MPWrapper(RawInterfaceWrapper):
@property @property
def dt(self) -> Union[float, int]: def dt(self) -> Union[float, int]:
return self.env.dt return self.env.control_timestep()
class TwoPolesMPWrapper(MPWrapper): class TwoPolesMPWrapper(MPWrapper):
+25 -1
View File
@@ -6,6 +6,30 @@ from fancy_gym.black_box.raw_interface_wrapper import RawInterfaceWrapper
class MPWrapper(RawInterfaceWrapper): class MPWrapper(RawInterfaceWrapper):
mp_config = {
'ProMP': {
'controller_kwargs': {
'p_gains': 50.0,
'd_gains': 1.0,
},
'trajectory_generator_kwargs': {
'weights_scale': 0.2,
},
},
'DMP': {
'controller_kwargs': {
'p_gains': 50.0,
'd_gains': 1.0,
},
'phase_generator': {
'alpha_phase': 2,
},
'trajectory_generator_kwargs': {
'weights_scale': 500,
},
},
'ProDMP': {},
}
@property @property
def context_mask(self) -> np.ndarray: def context_mask(self) -> np.ndarray:
@@ -30,4 +54,4 @@ class MPWrapper(RawInterfaceWrapper):
@property @property
def dt(self) -> Union[float, int]: def dt(self) -> Union[float, int]:
return self.env.dt return self.env.control_timestep()
+139 -716
View File
File diff suppressed because it is too large Load Diff
+14 -12
View File
@@ -1,18 +1,20 @@
### Classic Control ### Classic Control
## Step-based Environments ## Step-based Environments
|Name| Description|Horizon|Action Dimension|Observation Dimension
|---|---|---|---|---| | Name | Description | Horizon | Action Dimension | Observation Dimension |
|`SimpleReacher-v0`| Simple reaching task (2 links) without any physics simulation. Provides no reward until 150 time steps. This allows the agent to explore the space, but requires precise actions towards the end of the trajectory.| 200 | 2 | 9 | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ---------------- | --------------------- |
|`LongSimpleReacher-v0`| Simple reaching task (5 links) without any physics simulation. Provides no reward until 150 time steps. This allows the agent to explore the space, but requires precise actions towards the end of the trajectory.| 200 | 5 | 18 | `fancy/SimpleReacher-v0` | Simple reaching task (2 links) without any physics simulation. Provides no reward until 150 time steps. This allows the agent to explore the space, but requires precise actions towards the end of the trajectory. | 200 | 2 | 9 |
|`ViaPointReacher-v0`| Simple reaching task leveraging a via point, which supports self collision detection. Provides a reward only at 100 and 199 for reaching the viapoint and goal point, respectively.| 200 | 5 | 18 | `fancy/LongSimpleReacher-v0` | Simple reaching task (5 links) without any physics simulation. Provides no reward until 150 time steps. This allows the agent to explore the space, but requires precise actions towards the end of the trajectory. | 200 | 5 | 18 |
|`HoleReacher-v0`| 5 link reaching task where the end-effector needs to reach into a narrow hole without collding with itself or walls | 200 | 5 | 18 | `fancy/ViaPointReacher-v0` | Simple reaching task leveraging a via point, which supports self collision detection. Provides a reward only at 100 and 199 for reaching the viapoint and goal point, respectively. | 200 | 5 | 18 |
| `fancy/HoleReacher-v0` | 5 link reaching task where the end-effector needs to reach into a narrow hole without collding with itself or walls | 200 | 5 | 18 |
## MP Environments ## MP Environments
|Name| Description|Horizon|Action Dimension|Context Dimension
|---|---|---|---|---|
|`ViaPointReacherDMP-v0`| A DMP provides a trajectory for the `ViaPointReacher-v0` task. | 200 | 25
|`HoleReacherFixedGoalDMP-v0`| A DMP provides a trajectory for the `HoleReacher-v0` task with a fixed goal attractor. | 200 | 25
|`HoleReacherDMP-v0`| A DMP provides a trajectory for the `HoleReacher-v0` task. The goal attractor needs to be learned. | 200 | 30
[//]: |`HoleReacherProMPP-v0`| | Name | Description | Horizon | Action Dimension | Context Dimension |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------- | ------- | ---------------- | ----------------- |
| `fancy_DMP/ViaPointReacher-v0` | A DMP provides a trajectory for the `fancy/ViaPointReacher-v0` task. | 200 | 25 |
| `fancy_DMP/HoleReacherFixedGoal-v0` | A DMP provides a trajectory for the `fancy/HoleReacher-v0` task with a fixed goal attractor. | 200 | 25 |
| `fancy_DMP/HoleReacher-v0` | A DMP provides a trajectory for the `fancy/HoleReacher-v0` task. The goal attractor needs to be learned. | 200 | 30 |
[//]: |`fancy/HoleReacherProMPP-v0`|
@@ -1,10 +1,10 @@
from typing import Union, Tuple, Optional from typing import Union, Tuple, Optional, Any, Dict
import gym import gymnasium as gym
import numpy as np import numpy as np
from gym import spaces from gymnasium import spaces
from gym.core import ObsType from gymnasium.core import ObsType
from gym.utils import seeding from gymnasium.utils import seeding
from fancy_gym.envs.classic_control.utils import intersect from fancy_gym.envs.classic_control.utils import intersect
@@ -14,12 +14,14 @@ class BaseReacherEnv(gym.Env):
Base class for all reaching environments. Base class for all reaching environments.
""" """
def __init__(self, n_links: int, random_start: bool = True, allow_self_collision: bool = False): def __init__(self, n_links: int, random_start: bool = True, allow_self_collision: bool = False, render_mode: str = None):
super().__init__() super().__init__()
self.link_lengths = np.ones(n_links) self.link_lengths = np.ones(n_links)
self.n_links = n_links self.n_links = n_links
self._dt = 0.01 self._dt = 0.01
self.render_mode = render_mode
self.random_start = random_start self.random_start = random_start
self.allow_self_collision = allow_self_collision self.allow_self_collision = allow_self_collision
@@ -55,7 +57,6 @@ class BaseReacherEnv(gym.Env):
self.fig = None self.fig = None
self._steps = 0 self._steps = 0
self.seed()
@property @property
def dt(self) -> Union[float, int]: def dt(self) -> Union[float, int]:
@@ -69,10 +70,15 @@ class BaseReacherEnv(gym.Env):
def current_vel(self): def current_vel(self):
return self._angle_velocity.copy() return self._angle_velocity.copy()
def reset(self, *, seed: Optional[int] = None, return_info: bool = False, def reset(self, *, seed: Optional[int] = None, options: Optional[Dict[str, Any]] = None) \
options: Optional[dict] = None, ) -> Union[ObsType, Tuple[ObsType, dict]]: -> Tuple[ObsType, Dict[str, Any]]:
# Sample only orientation of first link, i.e. the arm is always straight. # Sample only orientation of first link, i.e. the arm is always straight.
if self.random_start: super(BaseReacherEnv, self).reset(seed=seed, options=options)
try:
random_start = options.get('random_start', self.random_start)
except AttributeError:
random_start = self.random_start
if random_start:
first_joint = self.np_random.uniform(np.pi / 4, 3 * np.pi / 4) first_joint = self.np_random.uniform(np.pi / 4, 3 * np.pi / 4)
self._joint_angles = np.hstack([[first_joint], np.zeros(self.n_links - 1)]) self._joint_angles = np.hstack([[first_joint], np.zeros(self.n_links - 1)])
self._start_pos = self._joint_angles.copy() self._start_pos = self._joint_angles.copy()
@@ -84,7 +90,7 @@ class BaseReacherEnv(gym.Env):
self._update_joints() self._update_joints()
self._steps = 0 self._steps = 0
return self._get_obs().copy() return self._get_obs().copy(), {}
def _update_joints(self): def _update_joints(self):
""" """
@@ -124,10 +130,6 @@ class BaseReacherEnv(gym.Env):
def _terminate(self, info) -> bool: def _terminate(self, info) -> bool:
raise NotImplementedError raise NotImplementedError
def seed(self, seed=None):
self.np_random, seed = seeding.np_random(seed)
return [seed]
def close(self): def close(self):
super(BaseReacherEnv, self).close() super(BaseReacherEnv, self).close()
del self.fig del self.fig
@@ -1,5 +1,5 @@
import numpy as np import numpy as np
from gym import spaces from gymnasium import spaces
from fancy_gym.envs.classic_control.base_reacher.base_reacher import BaseReacherEnv from fancy_gym.envs.classic_control.base_reacher.base_reacher import BaseReacherEnv
@@ -10,8 +10,8 @@ class BaseReacherDirectEnv(BaseReacherEnv):
""" """
def __init__(self, n_links: int, random_start: bool = True, def __init__(self, n_links: int, random_start: bool = True,
allow_self_collision: bool = False): allow_self_collision: bool = False, **kwargs):
super().__init__(n_links, random_start, allow_self_collision) super().__init__(n_links, random_start, allow_self_collision, **kwargs)
self.max_vel = 2 * np.pi self.max_vel = 2 * np.pi
action_bound = np.ones((self.n_links,)) * self.max_vel action_bound = np.ones((self.n_links,)) * self.max_vel
@@ -32,6 +32,7 @@ class BaseReacherDirectEnv(BaseReacherEnv):
reward, info = self._get_reward(action) reward, info = self._get_reward(action)
self._steps += 1 self._steps += 1
done = self._terminate(info) terminated = self._terminate(info)
truncated = False
return self._get_obs().copy(), reward, done, info return self._get_obs().copy(), reward, terminated, truncated, info
@@ -1,5 +1,5 @@
import numpy as np import numpy as np
from gym import spaces from gymnasium import spaces
from fancy_gym.envs.classic_control.base_reacher.base_reacher import BaseReacherEnv from fancy_gym.envs.classic_control.base_reacher.base_reacher import BaseReacherEnv
@@ -10,8 +10,8 @@ class BaseReacherTorqueEnv(BaseReacherEnv):
""" """
def __init__(self, n_links: int, random_start: bool = True, def __init__(self, n_links: int, random_start: bool = True,
allow_self_collision: bool = False): allow_self_collision: bool = False, **kwargs):
super().__init__(n_links, random_start, allow_self_collision) super().__init__(n_links, random_start, allow_self_collision, **kwargs)
self.max_torque = 1000 self.max_torque = 1000
action_bound = np.ones((self.n_links,)) * self.max_torque action_bound = np.ones((self.n_links,)) * self.max_torque
@@ -31,6 +31,7 @@ class BaseReacherTorqueEnv(BaseReacherEnv):
reward, info = self._get_reward(action) reward, info = self._get_reward(action)
self._steps += 1 self._steps += 1
done = False terminated = False
truncated = False
return self._get_obs().copy(), reward, done, info return self._get_obs().copy(), reward, terminated, truncated, info
@@ -1,22 +1,25 @@
from typing import Union, Optional, Tuple from typing import Union, Optional, Tuple, Any, Dict
import gym import gymnasium as gym
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
from gym.core import ObsType from gymnasium import spaces
from gymnasium.core import ObsType
from matplotlib import patches from matplotlib import patches
from fancy_gym.envs.classic_control.base_reacher.base_reacher_direct import BaseReacherDirectEnv from fancy_gym.envs.classic_control.base_reacher.base_reacher_direct import BaseReacherDirectEnv
from . import MPWrapper
MAX_EPISODE_STEPS_HOLEREACHER = 200 MAX_EPISODE_STEPS_HOLEREACHER = 200
class HoleReacherEnv(BaseReacherDirectEnv): class HoleReacherEnv(BaseReacherDirectEnv):
def __init__(self, n_links: int, hole_x: Union[None, float] = None, hole_depth: Union[None, float] = None, def __init__(self, n_links: int, hole_x: Union[None, float] = None, hole_depth: Union[None, float] = None,
hole_width: float = 1., random_start: bool = False, allow_self_collision: bool = False, hole_width: float = 1., random_start: bool = False, allow_self_collision: bool = False,
allow_wall_collision: bool = False, collision_penalty: float = 1000, rew_fct: str = "simple"): allow_wall_collision: bool = False, collision_penalty: float = 1000, rew_fct: str = "simple", **kwargs):
super().__init__(n_links, random_start, allow_self_collision) super().__init__(n_links, random_start, allow_self_collision, **kwargs)
# provided initial parameters # provided initial parameters
self.initial_x = hole_x # x-position of center of hole self.initial_x = hole_x # x-position of center of hole
@@ -40,7 +43,7 @@ class HoleReacherEnv(BaseReacherDirectEnv):
[np.inf] # env steps, because reward start after n steps TODO: Maybe [np.inf] # env steps, because reward start after n steps TODO: Maybe
]) ])
# self.action_space = gym.spaces.Box(low=-action_bound, high=action_bound, shape=action_bound.shape) # self.action_space = gym.spaces.Box(low=-action_bound, high=action_bound, shape=action_bound.shape)
self.observation_space = gym.spaces.Box(low=-state_bound, high=state_bound, shape=state_bound.shape) self.observation_space = spaces.Box(low=-state_bound, high=state_bound, shape=state_bound.shape)
if rew_fct == "simple": if rew_fct == "simple":
from fancy_gym.envs.classic_control.hole_reacher.hr_simple_reward import HolereacherReward from fancy_gym.envs.classic_control.hole_reacher.hr_simple_reward import HolereacherReward
@@ -54,13 +57,18 @@ class HoleReacherEnv(BaseReacherDirectEnv):
else: else:
raise ValueError("Unknown reward function {}".format(rew_fct)) raise ValueError("Unknown reward function {}".format(rew_fct))
def reset(self, *, seed: Optional[int] = None, return_info: bool = False, def reset(self, *, seed: Optional[int] = None, options: Optional[Dict[str, Any]] = None) \
options: Optional[dict] = None, ) -> Union[ObsType, Tuple[ObsType, dict]]: -> Tuple[ObsType, Dict[str, Any]]:
# initialize seed here as the random goal needs to be generated before the super reset()
gym.Env.reset(self, seed=seed, options=options)
self._generate_hole() self._generate_hole()
self._set_patches() self._set_patches()
self.reward_function.reset() self.reward_function.reset()
return super().reset() # do not provide seed to avoid setting it twice
return super(HoleReacherEnv, self).reset(options=options)
def _get_reward(self, action: np.ndarray) -> (float, dict): def _get_reward(self, action: np.ndarray) -> (float, dict):
return self.reward_function.get_reward(self) return self.reward_function.get_reward(self)
@@ -160,7 +168,7 @@ class HoleReacherEnv(BaseReacherDirectEnv):
# all points that are above the hole # all points that are above the hole
r, c = np.where((line_points[:, :, 0] > (self._tmp_x - self._tmp_width / 2)) & ( r, c = np.where((line_points[:, :, 0] > (self._tmp_x - self._tmp_width / 2)) & (
line_points[:, :, 0] < (self._tmp_x + self._tmp_width / 2))) line_points[:, :, 0] < (self._tmp_x + self._tmp_width / 2)))
# check if any of those points are below surface # check if any of those points are below surface
nr_line_points_below_surface_in_hole = np.sum(line_points[r, c, 1] < -self._tmp_depth) nr_line_points_below_surface_in_hole = np.sum(line_points[r, c, 1] < -self._tmp_depth)
@@ -170,7 +178,7 @@ class HoleReacherEnv(BaseReacherDirectEnv):
return False return False
def render(self, mode='human'): def render(self):
if self.fig is None: if self.fig is None:
# Create base figure once on the beginning. Afterwards only update # Create base figure once on the beginning. Afterwards only update
plt.ion() plt.ion()
@@ -189,7 +197,7 @@ class HoleReacherEnv(BaseReacherDirectEnv):
self.fig.gca().set_title( self.fig.gca().set_title(
f"Iteration: {self._steps}, distance: {np.linalg.norm(self.end_effector - self._goal) ** 2}") f"Iteration: {self._steps}, distance: {np.linalg.norm(self.end_effector - self._goal) ** 2}")
if mode == "human": if self.render_mode == "human":
# arm # arm
self.line.set_data(self._joints[:, 0], self._joints[:, 1]) self.line.set_data(self._joints[:, 0], self._joints[:, 1])
@@ -197,7 +205,7 @@ class HoleReacherEnv(BaseReacherDirectEnv):
self.fig.canvas.draw() self.fig.canvas.draw()
self.fig.canvas.flush_events() self.fig.canvas.flush_events()
elif mode == "partial": elif self.render_mode == "partial":
if self._steps % 20 == 0 or self._steps in [1, 199] or self._is_collided: if self._steps % 20 == 0 or self._steps in [1, 199] or self._is_collided:
# Arm # Arm
plt.plot(self._joints[:, 0], self._joints[:, 1], 'ro-', markerfacecolor='k', plt.plot(self._joints[:, 0], self._joints[:, 1], 'ro-', markerfacecolor='k',
@@ -223,16 +231,3 @@ class HoleReacherEnv(BaseReacherDirectEnv):
self.fig.gca().add_patch(left_block) self.fig.gca().add_patch(left_block)
self.fig.gca().add_patch(right_block) self.fig.gca().add_patch(right_block)
self.fig.gca().add_patch(hole_floor) self.fig.gca().add_patch(hole_floor)
if __name__ == "__main__":
env = HoleReacherEnv(5)
env.reset()
for i in range(10000):
ac = env.action_space.sample()
obs, rew, done, info = env.step(ac)
env.render()
if done:
env.reset()
@@ -7,6 +7,30 @@ from fancy_gym.black_box.raw_interface_wrapper import RawInterfaceWrapper
class MPWrapper(RawInterfaceWrapper): class MPWrapper(RawInterfaceWrapper):
mp_config = {
'ProMP': {
'controller_kwargs': {
'controller_type': 'velocity',
},
'trajectory_generator_kwargs': {
'weights_scale': 2,
},
},
'DMP': {
'controller_kwargs': {
'controller_type': 'velocity',
},
'trajectory_generator_kwargs': {
# TODO: Before it was weight scale 50 and goal scale 0.1. We now only have weight scale and thus set it to 500. Check
'weights_scale': 500,
},
'phase_generator_kwargs': {
'alpha_phase': 2.5,
},
},
'ProDMP': {},
}
@property @property
def context_mask(self): def context_mask(self):
return np.hstack([ return np.hstack([
@@ -7,6 +7,28 @@ from fancy_gym.black_box.raw_interface_wrapper import RawInterfaceWrapper
class MPWrapper(RawInterfaceWrapper): class MPWrapper(RawInterfaceWrapper):
mp_config = {
'ProMP': {
'controller_kwargs': {
'p_gains': 0.6,
'd_gains': 0.075,
},
},
'DMP': {
'controller_kwargs': {
'p_gains': 0.6,
'd_gains': 0.075,
},
'trajectory_generator_kwargs': {
'weights_scale': 50,
},
'phase_generator_kwargs': {
'alpha_phase': 2,
},
},
'ProDMP': {},
}
@property @property
def context_mask(self): def context_mask(self):
return np.hstack([ return np.hstack([
@@ -1,11 +1,12 @@
from typing import Iterable, Union, Optional, Tuple from typing import Iterable, Union, Optional, Tuple, Any, Dict
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
from gym import spaces from gymnasium import spaces
from gym.core import ObsType from gymnasium.core import ObsType
from fancy_gym.envs.classic_control.base_reacher.base_reacher_torque import BaseReacherTorqueEnv from fancy_gym.envs.classic_control.base_reacher.base_reacher_torque import BaseReacherTorqueEnv
from . import MPWrapper
class SimpleReacherEnv(BaseReacherTorqueEnv): class SimpleReacherEnv(BaseReacherTorqueEnv):
@@ -16,8 +17,8 @@ class SimpleReacherEnv(BaseReacherTorqueEnv):
""" """
def __init__(self, n_links: int, target: Union[None, Iterable] = None, random_start: bool = True, def __init__(self, n_links: int, target: Union[None, Iterable] = None, random_start: bool = True,
allow_self_collision: bool = False, ): allow_self_collision: bool = False, **kwargs):
super().__init__(n_links, random_start, allow_self_collision) super().__init__(n_links, random_start, allow_self_collision, **kwargs)
# provided initial parameters # provided initial parameters
self.inital_target = target self.inital_target = target
@@ -42,11 +43,15 @@ class SimpleReacherEnv(BaseReacherTorqueEnv):
# def start_pos(self): # def start_pos(self):
# return self._start_pos # return self._start_pos
def reset(self, *, seed: Optional[int] = None, return_info: bool = False, def reset(self, *, seed: Optional[int] = None, options: Optional[Dict[str, Any]] = None) \
options: Optional[dict] = None, ) -> Union[ObsType, Tuple[ObsType, dict]]: -> Tuple[ObsType, Dict[str, Any]]:
# Reset twice to ensure we return obs after generating goal and generating goal after executing seeded reset.
# (Env will not behave deterministic otherwise)
# Yes, there is probably a more elegant solution to this problem...
self._generate_goal() self._generate_goal()
super().reset(seed=seed, options=options)
return super().reset() self._generate_goal()
return super().reset(seed=seed, options=options)
def _get_reward(self, action: np.ndarray): def _get_reward(self, action: np.ndarray):
diff = self.end_effector - self._goal diff = self.end_effector - self._goal
@@ -93,7 +98,7 @@ class SimpleReacherEnv(BaseReacherTorqueEnv):
def _check_collisions(self) -> bool: def _check_collisions(self) -> bool:
return self._check_self_collision() return self._check_self_collision()
def render(self, mode='human'): # pragma: no cover def render(self): # pragma: no cover
if self.fig is None: if self.fig is None:
# Create base figure once on the beginning. Afterwards only update # Create base figure once on the beginning. Afterwards only update
plt.ion() plt.ion()
@@ -127,15 +132,3 @@ class SimpleReacherEnv(BaseReacherTorqueEnv):
self.fig.canvas.draw() self.fig.canvas.draw()
self.fig.canvas.flush_events() self.fig.canvas.flush_events()
if __name__ == "__main__":
env = SimpleReacherEnv(5)
env.reset()
for i in range(200):
ac = env.action_space.sample()
obs, rew, done, info = env.step(ac)
env.render()
if done:
break
@@ -7,6 +7,26 @@ from fancy_gym.black_box.raw_interface_wrapper import RawInterfaceWrapper
class MPWrapper(RawInterfaceWrapper): class MPWrapper(RawInterfaceWrapper):
mp_config = {
'ProMP': {
'controller_kwargs': {
'controller_type': 'velocity',
},
},
'DMP': {
'controller_kwargs': {
'controller_type': 'velocity',
},
'trajectory_generator_kwargs': {
'weights_scale': 50,
},
'phase_generator_kwargs': {
'alpha_phase': 2,
},
},
'ProDMP': {},
}
@property @property
def context_mask(self): def context_mask(self):
return np.hstack([ return np.hstack([
@@ -1,19 +1,21 @@
from typing import Iterable, Union, Tuple, Optional from typing import Iterable, Union, Tuple, Optional, Any, Dict
import gym import gymnasium as gym
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
from gym.core import ObsType from gymnasium import spaces
from gymnasium.core import ObsType
from fancy_gym.envs.classic_control.base_reacher.base_reacher_direct import BaseReacherDirectEnv from fancy_gym.envs.classic_control.base_reacher.base_reacher_direct import BaseReacherDirectEnv
from . import MPWrapper
class ViaPointReacherEnv(BaseReacherDirectEnv): class ViaPointReacherEnv(BaseReacherDirectEnv):
def __init__(self, n_links, random_start: bool = False, via_target: Union[None, Iterable] = None, def __init__(self, n_links, random_start: bool = False, via_target: Union[None, Iterable] = None,
target: Union[None, Iterable] = None, allow_self_collision=False, collision_penalty=1000): target: Union[None, Iterable] = None, allow_self_collision=False, collision_penalty=1000, **kwargs):
super().__init__(n_links, random_start, allow_self_collision) super().__init__(n_links, random_start, allow_self_collision, **kwargs)
# provided initial parameters # provided initial parameters
self.intitial_target = target # provided target value self.intitial_target = target # provided target value
@@ -34,16 +36,21 @@ class ViaPointReacherEnv(BaseReacherDirectEnv):
[np.inf] * 2, # x-y coordinates of target distance [np.inf] * 2, # x-y coordinates of target distance
[np.inf] # env steps, because reward start after n steps [np.inf] # env steps, because reward start after n steps
]) ])
self.observation_space = gym.spaces.Box(low=-state_bound, high=state_bound, shape=state_bound.shape) self.observation_space = spaces.Box(low=-state_bound, high=state_bound, shape=state_bound.shape)
# @property # @property
# def start_pos(self): # def start_pos(self):
# return self._start_pos # return self._start_pos
def reset(self, *, seed: Optional[int] = None, return_info: bool = False, def reset(self, *, seed: Optional[int] = None, options: Optional[Dict[str, Any]] = None) \
options: Optional[dict] = None, ) -> Union[ObsType, Tuple[ObsType, dict]]: -> Tuple[ObsType, Dict[str, Any]]:
# Reset twice to ensure we return obs after generating goal and generating goal after executing seeded reset.
# (Env will not behave deterministic otherwise)
# Yes, there is probably a more elegant solution to this problem...
self._generate_goal() self._generate_goal()
return super().reset() super().reset(seed=seed, options=options)
self._generate_goal()
return super().reset(seed=seed, options=options)
def _generate_goal(self): def _generate_goal(self):
# TODO: Maybe improve this later, this can yield quite a lot of invalid settings # TODO: Maybe improve this later, this can yield quite a lot of invalid settings
@@ -116,7 +123,7 @@ class ViaPointReacherEnv(BaseReacherDirectEnv):
def _check_collisions(self) -> bool: def _check_collisions(self) -> bool:
return self._check_self_collision() return self._check_self_collision()
def render(self, mode='human'): def render(self):
goal_pos = self._goal.T goal_pos = self._goal.T
via_pos = self._via_point.T via_pos = self._via_point.T
@@ -139,7 +146,7 @@ class ViaPointReacherEnv(BaseReacherDirectEnv):
self.fig.gca().set_title(f"Iteration: {self._steps}, distance: {self.end_effector - self._goal}") self.fig.gca().set_title(f"Iteration: {self._steps}, distance: {self.end_effector - self._goal}")
if mode == "human": if self.render_mode == "human":
# goal # goal
if self._steps == 1: if self._steps == 1:
self.goal_point_plot.set_data(goal_pos[0], goal_pos[1]) self.goal_point_plot.set_data(goal_pos[0], goal_pos[1])
@@ -151,7 +158,7 @@ class ViaPointReacherEnv(BaseReacherDirectEnv):
self.fig.canvas.draw() self.fig.canvas.draw()
self.fig.canvas.flush_events() self.fig.canvas.flush_events()
elif mode == "partial": elif self.render_mode == "partial":
if self._steps == 1: if self._steps == 1:
# fig, ax = plt.subplots() # fig, ax = plt.subplots()
# Add the patch to the Axes # Add the patch to the Axes
@@ -171,7 +178,7 @@ class ViaPointReacherEnv(BaseReacherDirectEnv):
plt.ylim([-1.1, lim]) plt.ylim([-1.1, lim])
plt.pause(0.01) plt.pause(0.01)
elif mode == "final": elif self.render_mode == "final":
if self._steps == 199 or self._is_collided: if self._steps == 199 or self._is_collided:
# fig, ax = plt.subplots() # fig, ax = plt.subplots()
@@ -183,16 +190,3 @@ class ViaPointReacherEnv(BaseReacherDirectEnv):
plt.plot(self._joints[:, 0], self._joints[:, 1], 'ro-', markerfacecolor='k') plt.plot(self._joints[:, 0], self._joints[:, 1], 'ro-', markerfacecolor='k')
plt.pause(0.01) plt.pause(0.01)
if __name__ == "__main__":
env = ViaPointReacherEnv(5)
env.reset()
for i in range(10000):
ac = env.action_space.sample()
obs, rew, done, info = env.step(ac)
env.render()
if done:
env.reset()
+44 -11
View File
@@ -1,15 +1,48 @@
# Custom Mujoco tasks # Custom Mujoco tasks
## Step-based Environments ## Step-based Environments
|Name| Description|Horizon|Action Dimension|Observation Dimension
|---|---|---|---|---|
|`ALRReacher-v0`|Modified (5 links) Mujoco gym's `Reacher-v2` (2 links)| 200 | 5 | 21
|`ALRReacherSparse-v0`|Same as `ALRReacher-v0`, but the distance penalty is only provided in the last time step.| 200 | 5 | 21
|`ALRReacherSparseBalanced-v0`|Same as `ALRReacherSparse-v0`, but the end-effector has to remain upright.| 200 | 5 | 21
|`ALRLongReacher-v0`|Modified (7 links) Mujoco gym's `Reacher-v2` (2 links)| 200 | 7 | 27
|`ALRLongReacherSparse-v0`|Same as `ALRLongReacher-v0`, but the distance penalty is only provided in the last time step.| 200 | 7 | 27
|`ALRLongReacherSparseBalanced-v0`|Same as `ALRLongReacherSparse-v0`, but the end-effector has to remain upright.| 200 | 7 | 27
|`ALRBallInACupSimple-v0`| Ball-in-a-cup task where a robot needs to catch a ball attached to a cup at its end-effector. | 4000 | 3 | wip
|`ALRBallInACup-v0`| Ball-in-a-cup task where a robot needs to catch a ball attached to a cup at its end-effector | 4000 | 7 | wip
|`ALRBallInACupGoal-v0`| Similar to `ALRBallInACupSimple-v0` but the ball needs to be caught at a specified goal position | 4000 | 7 | wip
| Name | Description | Horizon | Action Dimension | Observation Dimension |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------- | ------- | ---------------- | --------------------- |
| `fancy/Reacher-v0` | Modified (5 links) gymnasiums's mujoco `Reacher-v2` (2 links) | 200 | 5 | 21 |
| `fancy/ReacherSparse-v0` | Same as `fancy/Reacher-v0`, but the distance penalty is only provided in the last time step. | 200 | 5 | 21 |
| `fancy/ReacherSparseBalanced-v0` | Same as `fancy/ReacherSparse-v0`, but the end-effector has to remain upright. | 200 | 5 | 21 |
| `fancy/LongReacher-v0` | Modified (7 links) gymnasiums's mujoco `Reacher-v2` (2 links) | 200 | 7 | 27 |
| `fancy/LongReacherSparse-v0` | Same as `fancy/LongReacher-v0`, but the distance penalty is only provided in the last time step. | 200 | 7 | 27 |
| `fancy/LongReacherSparseBalanced-v0` | Same as `fancy/LongReacherSparse-v0`, but the end-effector has to remain upright. | 200 | 7 | 27 |
| `fancy/Reacher5d-v0` | Reacher task with 5 links, based on Gymnasium's `gym.envs.mujoco.ReacherEnv` | 200 | 5 | 20 |
| `fancy/Reacher5dSparse-v0` | Sparse Reacher task with 5 links, based on Gymnasium's `gym.envs.mujoco.ReacherEnv` | 200 | 5 | 20 |
| `fancy/Reacher7d-v0` | Reacher task with 7 links, based on Gymnasium's `gym.envs.mujoco.ReacherEnv` | 200 | 7 | 22 |
| `fancy/Reacher7dSparse-v0` | Sparse Reacher task with 7 links, based on Gymnasium's `gym.envs.mujoco.ReacherEnv` | 200 | 7 | 22 |
| `fancy/HopperJumpSparse-v0` | Hopper Jump task with sparse rewards, based on Gymnasium's `gym.envs.mujoco.Hopper` | 250 | 3 | 15 / 16\* |
| `fancy/HopperJump-v0` | Hopper Jump task with continuous rewards, based on Gymnasium's `gym.envs.mujoco.Hopper` | 250 | 3 | 15 / 16\* |
| `fancy/AntJump-v0` | Ant Jump task, based on Gymnasium's `gym.envs.mujoco.Ant` | 200 | 8 | 119 |
| `fancy/HalfCheetahJump-v0` | HalfCheetah Jump task, based on Gymnasium's `gym.envs.mujoco.HalfCheetah` | 100 | 6 | 112 |
| `fancy/HopperJumpOnBox-v0` | Hopper Jump on Box task, based on Gymnasium's `gym.envs.mujoco.Hopper` | 250 | 4 | 16 / 100\* |
| `fancy/HopperThrow-v0` | Hopper Throw task, based on Gymnasium's `gym.envs.mujoco.Hopper` | 250 | 3 | 18 / 100\* |
| `fancy/HopperThrowInBasket-v0` | Hopper Throw in Basket task, based on Gymnasium's `gym.envs.mujoco.Hopper` | 250 | 3 | 18 / 100\* |
| `fancy/Walker2DJump-v0` | Walker 2D Jump task, based on Gymnasium's `gym.envs.mujoco.Walker2d` | 300 | 6 | 18 / 19\* |
| `fancy/BeerPong-v0` | Beer Pong task, based on a custom environment with multiple task variations | 300 | 3 | 29 |
| `fancy/BeerPongStepBased-v0` | Step-based Beer Pong task, based on a custom environment with episodic rewards | 300 | 3 | 29 |
| `fancy/BeerPongFixedRelease-v0` | Beer Pong with fixed release, based on a custom environment with episodic rewards | 300 | 3 | 29 |
| `fancy/BoxPushingDense-v0` | Custom Box-pushing task with dense rewards | 100 | 3 | 13 |
| `fancy/BoxPushingTemporalSparse-v0` | Custom Box-pushing task with temporally sparse rewards | 100 | 3 | 13 |
| `fancy/BoxPushingTemporalSpatialSparse-v0` | Custom Box-pushing task with temporally and spatially sparse rewards | 100 | 3 | 13 |
| `fancy/TableTennis2D-v0` | Table Tennis task with 2D context, based on a custom environment for table tennis | 350 | 7 | 19 |
| `fancy/TableTennis2DReplan-v0` | Table Tennis task with 2D context and replanning, based on a custom environment for table tennis | 350 | 7 | 19 |
| `fancy/TableTennis4D-v0` | Table Tennis task with 4D context, based on a custom environment for table tennis | 350 | 7 | 22 |
| `fancy/TableTennis4DReplan-v0` | Table Tennis task with 4D context and replanning, based on a custom environment for table tennis | 350 | 7 | 22 |
| `fancy/TableTennisWind-v0` | Table Tennis task with wind effects, based on a custom environment for table tennis | 350 | 7 | 19 |
| `fancy/TableTennisGoalSwitching-v0` | Table Tennis task with goal switching, based on a custom environment for table tennis | 350 | 7 | 19 |
| `fancy/TableTennisWindReplan-v0` | Table Tennis task with wind effects and replanning, based on a custom environment for table tennis | 350 | 7 | 19 |
\*Observation dimensions depend on configuration.
<!--
No longer used?
| Name | Description | Horizon | Action Dimension | Observation Dimension |
| --------------------------- | --------------------------------------------------------------------------------------------------- | ------- | ---------------- | --------------------- |
| `fancy/BallInACupSimple-v0` | Ball-in-a-cup task where a robot needs to catch a ball attached to a cup at its end-effector. | 4000 | 3 | wip |
| `fancy/BallInACup-v0` | Ball-in-a-cup task where a robot needs to catch a ball attached to a cup at its end-effector | 4000 | 7 | wip |
| `fancy/BallInACupGoal-v0` | Similar to `fancy/BallInACupSimple-v0` but the ball needs to be caught at a specified goal position | 4000 | 7 | wip |
-->
+5
View File
@@ -9,3 +9,8 @@ from .reacher.reacher import ReacherEnv
from .walker_2d_jump.walker_2d_jump import Walker2dJumpEnv from .walker_2d_jump.walker_2d_jump import Walker2dJumpEnv
from .box_pushing.box_pushing_env import BoxPushingDense, BoxPushingTemporalSparse, BoxPushingTemporalSpatialSparse from .box_pushing.box_pushing_env import BoxPushingDense, BoxPushingTemporalSparse, BoxPushingTemporalSpatialSparse
from .table_tennis.table_tennis_env import TableTennisEnv, TableTennisWind, TableTennisGoalSwitching from .table_tennis.table_tennis_env import TableTennisEnv, TableTennisWind, TableTennisGoalSwitching
try:
from .air_hockey.air_hockey_env_wrapper import AirHockeyEnv
except ModuleNotFoundError:
print("[FANCY GYM] Air Hockey not available (depends on mushroom-rl, dmc, mujoco)")
+26
View File
@@ -0,0 +1,26 @@
MIT License
Copyright (c) 2022 Puze Liu, Jonas Guenster, Davide Tateo.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
This project is a derivative of [AirHockeyChallenge](https://github.com/AirHockeyChallenge/air_hockey_challenge).
The changes are mostly focused on adapting the provided environments to fancy_gym.
@@ -0,0 +1,180 @@
from copy import deepcopy
import numpy as np
from gymnasium import spaces
import fancy_gym.envs.mujoco.air_hockey.constraints as constraints
from fancy_gym.envs.mujoco.air_hockey import position_control_wrapper as position
from fancy_gym.envs.mujoco.air_hockey.utils import robot_to_world
from mushroom_rl.core import Environment
class AirHockeyEnv(Environment):
metadata = {"render_modes": ["human"], "render_fps": 50}
def __init__(self, env_mode=None, interpolation_order=3, render_mode=None, **kwargs):
"""
Environment Constructor
Args:
env [string]:
The string to specify the running environments. Available environments: [3dof-hit, 3dof-defend, 7dof-hit, 7dof-defend, tournament].
interpolation_order (int, 3): Type of interpolation used, has to correspond to action shape. Order 1-5 are
polynomial interpolation of the degree. Order -1 is linear interpolation of position and velocity.
Set Order to None in order to turn off interpolation. In this case the action has to be a trajectory
of position, velocity and acceleration of the shape (20, 3, n_joints)
"""
env_dict = {
"tournament": position.IiwaPositionTournament,
"7dof-hit": position.IiwaPositionHit,
"7dof-defend": position.IiwaPositionDefend,
"3dof-hit": position.PlanarPositionHit,
"3dof-defend": position.PlanarPositionDefend
}
if env_mode not in env_dict:
raise Exception(f"Please specify one of the environments in {list(env_dict.keys())} for env_mode parameter!")
if env_mode == "tournament" and type(interpolation_order) != tuple:
interpolation_order = (interpolation_order, interpolation_order)
self.base_env = env_dict[env_mode](interpolation_order=interpolation_order, **kwargs)
self.env_name = env_mode
self.env_info = self.base_env.env_info
single_robot_obs_size = len(self.base_env.info.observation_space.low)
if env_mode == "tournament":
self.observation_space = spaces.Box(low=-np.inf, high=np.inf, shape=(2,single_robot_obs_size), dtype=np.float64)
else:
self.observation_space = spaces.Box(low=-np.inf, high=np.inf, shape=(single_robot_obs_size,), dtype=np.float64)
robot_info = self.env_info["robot"]
if env_mode != "tournament":
if interpolation_order in [1, 2]:
self.action_space = spaces.Box(low=robot_info["joint_pos_limit"][0], high=robot_info["joint_pos_limit"][1])
if interpolation_order in [3, 4, -1]:
self.action_space = spaces.Box(low=np.vstack([robot_info["joint_pos_limit"][0], robot_info["joint_vel_limit"][0]]),
high=np.vstack([robot_info["joint_pos_limit"][1], robot_info["joint_vel_limit"][1]]))
if interpolation_order in [5]:
self.action_space = spaces.Box(low=np.vstack([robot_info["joint_pos_limit"][0], robot_info["joint_vel_limit"][0], robot_info["joint_acc_limit"][0]]),
high=np.vstack([robot_info["joint_pos_limit"][1], robot_info["joint_vel_limit"][1], robot_info["joint_acc_limit"][1]]))
else:
acts = [None, None]
for i in range(2):
if interpolation_order[i] in [1, 2]:
acts[i] = spaces.Box(low=robot_info["joint_pos_limit"][0], high=robot_info["joint_pos_limit"][1])
if interpolation_order[i] in [3, 4, -1]:
acts[i] = spaces.Box(low=np.vstack([robot_info["joint_pos_limit"][0], robot_info["joint_vel_limit"][0]]),
high=np.vstack([robot_info["joint_pos_limit"][1], robot_info["joint_vel_limit"][1]]))
if interpolation_order[i] in [5]:
acts[i] = spaces.Box(low=np.vstack([robot_info["joint_pos_limit"][0], robot_info["joint_vel_limit"][0], robot_info["joint_acc_limit"][0]]),
high=np.vstack([robot_info["joint_pos_limit"][1], robot_info["joint_vel_limit"][1], robot_info["joint_acc_limit"][1]]))
self.action_space = spaces.Tuple((acts[0], acts[1]))
constraint_list = constraints.ConstraintList()
constraint_list.add(constraints.JointPositionConstraint(self.env_info))
constraint_list.add(constraints.JointVelocityConstraint(self.env_info))
constraint_list.add(constraints.EndEffectorConstraint(self.env_info))
if "7dof" in self.env_name or self.env_name == "tournament":
constraint_list.add(constraints.LinkConstraint(self.env_info))
self.env_info['constraints'] = constraint_list
self.env_info['env_name'] = self.env_name
self.render_mode = render_mode
self.render_human_active = False
super().__init__(self.base_env.info)
def step(self, action):
obs, reward, done, info = self.base_env.step(action)
if "tournament" in self.env_name:
info["constraints_value"] = list()
info["jerk"] = list()
for i in range(2):
obs_agent = obs[i * int(len(obs) / 2): (i + 1) * int(len(obs) / 2)]
info["constraints_value"].append(deepcopy(self.env_info['constraints'].fun(
obs_agent[self.env_info['joint_pos_ids']], obs_agent[self.env_info['joint_vel_ids']])))
info["jerk"].append(
self.base_env.jerk[i * self.env_info['robot']['n_joints']:(i + 1) * self.env_info['robot'][
'n_joints']])
info["score"] = self.base_env.score
info["faults"] = self.base_env.faults
else:
info["constraints_value"] = deepcopy(self.env_info['constraints'].fun(obs[self.env_info['joint_pos_ids']],
obs[self.env_info['joint_vel_ids']]))
info["jerk"] = self.base_env.jerk
info["success"] = self.check_success(obs)
if self.env_info['env_name'] == "tournament":
obs = np.array(np.split(obs, 2))
if self.render_human_active:
self.base_env.render()
return obs, reward, done, False, info
def render(self):
self.render_human_active = True
def reset(self, seed=None, options={}):
self.base_env.seed(seed)
obs = self.base_env.reset()
if self.env_info['env_name'] == "tournament":
obs = np.array(np.split(obs, 2))
return obs, {}
def check_success(self, obs):
puck_pos, puck_vel = self.base_env.get_puck(obs)
puck_pos, _ = robot_to_world(self.base_env.env_info["robot"]["base_frame"][0], translation=puck_pos)
success = 0
if "hit" in self.env_name:
if puck_pos[0] - self.base_env.env_info['table']['length'] / 2 > 0 and \
np.abs(puck_pos[1]) - self.base_env.env_info['table']['goal_width'] / 2 < 0:
success = 1
elif "defend" in self.env_name:
if -0.8 < puck_pos[0] <= -0.2 and puck_vel[0] < 0.1:
success = 1
elif "prepare" in self.env_name:
if -0.8 < puck_pos[0] <= -0.2 and np.abs(puck_pos[1]) < 0.39105 and puck_vel[0] < 0.1:
success = 1
return success
@property
def unwrapped(self):
return self
def close(self):
self.base_env.stop()
if __name__ == "__main__":
env = AirHockeyEnv(env_mode="7dof-hit")
env.reset()
R = 0.
J = 0.
gamma = 1.
steps = 0
while True:
action = np.random.uniform(-1, 1, (2, env.env_info['robot']['n_joints'])) * 3
observation, reward, done, info = env.step(action)
env.render()
gamma *= env.info.gamma
J += gamma * reward
R += reward
steps += 1
if done or steps > env.info.horizon:
print("J: ", J, " R: ", R)
R = 0.
J = 0.
gamma = 1.
steps = 0
env.reset()
@@ -0,0 +1 @@
from .constraints import *
@@ -0,0 +1,212 @@
import copy
import numpy as np
from fancy_gym.envs.mujoco.air_hockey.utils.kinematics import forward_kinematics, jacobian
class Constraint:
def __init__(self, env_info, output_dim, **kwargs):
"""
Constructor
Args
----
env_info: dict
A dictionary contains information about the environment;
output_dim: int
The output dimension of the constraints.
**kwargs: dict
A dictionary contains agent related information.
"""
self._env_info = env_info
self._name = None
self.output_dim = output_dim
self._fun_value = np.zeros(self.output_dim)
self._jac_value = np.zeros((self.output_dim, 2 * env_info["robot"]["n_joints"]))
self._q_prev = None
self._dq_prev = None
@property
def name(self):
"""
The name of the constraints
"""
return self._name
def fun(self, q, dq):
"""
The function of the constraint.
Args
----
q: numpy.ndarray, (num_joints,)
The joint position of the robot
dq: numpy.ndarray, (num_joints,)
The joint velocity of the robot
Returns
-------
numpy.ndarray, (out_dim,):
The value computed by the constraints function.
"""
if np.equal(q, self._q_prev).all() and np.equal(dq, self._dq_prev):
return self._fun_value
else:
self._jacobian(q, dq)
return self._fun(q, dq)
def jacobian(self, q, dq):
"""
Jacobian is the derivative of the constraint function w.r.t the robot joint position and velocity.
Args
----
q: ndarray, (num_joints,)
The joint position of the robot
dq: ndarray, (num_joints,)
The joint velocity of the robot
Returns
-------
numpy.ndarray, (dim_output, num_joints * 2):
The flattened jacobian of the constraint function J = [dc / dq, dc / dq_dot]
"""
if np.equal(q, self._q_prev).all() and np.equal(dq, self._dq_prev):
return self._fun_value
else:
self._fun(q, dq)
return self._jacobian(q, dq)
def _fun(self, q, dq):
raise NotImplementedError
def _jacobian(self, q, dq):
raise NotImplementedError
class ConstraintList:
def __init__(self):
self.constraints = dict()
def keys(self):
return self.constraints.keys()
def get(self, key):
return self.constraints.get(key)
def add(self, c):
self.constraints.update({c.name: c})
def delete(self, name):
del self.constraints[name]
def fun(self, q, dq):
return {key: self.constraints[key].fun(q, dq) for key in self.constraints}
def jacobian(self, q, dq):
return {key: self.constraints[key].jacobian(q, dq) for key in self.constraints}
class JointPositionConstraint(Constraint):
def __init__(self, env_info, **kwargs):
super().__init__(env_info, output_dim=2 * env_info["robot"]["n_joints"], **kwargs)
self.joint_limits = self._env_info['robot']['joint_pos_limit'] * 0.95
self._name = 'joint_pos_constr'
def _fun(self, q, dq):
self._fun_value[:int(self.output_dim / 2)] = q - self.joint_limits[1]
self._fun_value[int(self.output_dim / 2):] = self.joint_limits[0] - q
return self._fun_value
def _jacobian(self, q, dq):
self._jac_value[:int(self.output_dim / 2), :int(self.output_dim / 2)] = np.eye(
self._env_info['robot']['n_joints'])
self._jac_value[int(self.output_dim / 2):, :int(self.output_dim / 2)] = -np.eye(
self._env_info['robot']['n_joints'])
return self._jac_value
class JointVelocityConstraint(Constraint):
def __init__(self, env_info, **kwargs):
super().__init__(env_info, output_dim=2 * env_info["robot"]["n_joints"], **kwargs)
self.joint_limits = self._env_info['robot']['joint_vel_limit'] * 0.95
self._name = 'joint_vel_constr'
def _fun(self, q, dq):
self._fun_value[:int(self.output_dim / 2)] = dq - self.joint_limits[1]
self._fun_value[int(self.output_dim / 2):] = self.joint_limits[0] - dq
return self._fun_value
def _jacobian(self, q, dq):
self._jac_value[:int(self.output_dim / 2), int(self.output_dim / 2):] = np.eye(
self._env_info['robot']['n_joints'])
self._jac_value[int(self.output_dim / 2):, int(self.output_dim / 2):] = -np.eye(
self._env_info['robot']['n_joints'])
return self._jac_value
class EndEffectorConstraint(Constraint):
def __init__(self, env_info, **kwargs):
# 1 Dimension on x direction: x > x_lb
# 2 Dimension on y direction: y > y_lb, y < y_ub
# 2 Dimension on z direction: z > z_lb, z < z_ub
super().__init__(env_info, output_dim=5, **kwargs)
self._name = "ee_constr"
tolerance = 0.02
self.robot_model = copy.deepcopy(self._env_info['robot']['robot_model'])
self.robot_data = copy.deepcopy(self._env_info['robot']['robot_data'])
self.x_lb = - self._env_info['robot']['base_frame'][0][0, 3] - (
self._env_info['table']['length'] / 2 - self._env_info['mallet']['radius'])
self.y_lb = - (self._env_info['table']['width'] / 2 - self._env_info['mallet']['radius'])
self.y_ub = (self._env_info['table']['width'] / 2 - self._env_info['mallet']['radius'])
self.z_lb = self._env_info['robot']['ee_desired_height'] - tolerance
self.z_ub = self._env_info['robot']['ee_desired_height'] + tolerance
def _fun(self, q, dq):
ee_pos, _ = forward_kinematics(self.robot_model, self.robot_data, q)
self._fun_value = np.array([-ee_pos[0] + self.x_lb,
-ee_pos[1] + self.y_lb, ee_pos[1] - self.y_ub,
-ee_pos[2] + self.z_lb, ee_pos[2] - self.z_ub])
return self._fun_value
def _jacobian(self, q, dq):
jac = jacobian(self.robot_model, self.robot_data, q)
dc_dx = np.array([[-1, 0., 0.], [0., -1., 0.], [0., 1., 0.], [0., 0., -1.], [0., 0., 1.]])
self._jac_value[:, :self._env_info['robot']['n_joints']] = dc_dx @ jac[:3, :self._env_info['robot']['n_joints']]
return self._jac_value
class LinkConstraint(Constraint):
def __init__(self, env_info, **kwargs):
# 1 Dimension: wrist_z > minimum_height
# 2 Dimension: elbow_z > minimum_height
super().__init__(env_info, output_dim=2, **kwargs)
self._name = "link_constr"
self.robot_model = copy.deepcopy(self._env_info['robot']['robot_model'])
self.robot_data = copy.deepcopy(self._env_info['robot']['robot_data'])
self.z_lb = 0.25
def _fun(self, q, dq):
wrist_pos, _ = forward_kinematics(self.robot_model, self.robot_data, q, link="7")
elbow_pos, _ = forward_kinematics(self.robot_model, self.robot_data, q, link="4")
self._fun_value = np.array([-wrist_pos[2] + self.z_lb,
-elbow_pos[2] + self.z_lb])
return self._fun_value
def _jacobian(self, q, dq):
jac_wrist = jacobian(self.robot_model, self.robot_data, q, link="7")
jac_elbow = jacobian(self.robot_model, self.robot_data, q, link="4")
self._jac_value[:, :self._env_info['robot']['n_joints']] = np.vstack([
-jac_wrist[2, :self._env_info['robot']['n_joints']],
-jac_elbow[2, :self._env_info['robot']['n_joints']],
])
return self._jac_value
@@ -0,0 +1,58 @@
<mujoco model="AirHockeySingle">
<include file="iiwa1.xml"/>
<include file="iiwa2.xml"/>
<include file="../table.xml"/>
<contact>
<exclude body1="table_surface" body2="iiwa_1/base"/>
<exclude body1="table_surface" body2="iiwa_1/link_1"/>
<exclude body1="table_surface" body2="iiwa_1/link_2"/>
<exclude body1="table_surface" body2="iiwa_1/link_3"/>
<exclude body1="table_surface" body2="iiwa_1/link_4"/>
<exclude body1="table_surface" body2="iiwa_1/link_5"/>
<exclude body1="table_surface" body2="iiwa_1/link_6"/>
<exclude body1="table_surface" body2="iiwa_1/link_7"/>
<exclude body1="table_surface" body2="iiwa_1/striker_joint_link"/>
<exclude body1="table_surface" body2="iiwa_1/striker_mallet"/>
<exclude body1="rim" body2="iiwa_1/base"/>
<exclude body1="rim" body2="iiwa_1/link_1"/>
<exclude body1="rim" body2="iiwa_1/link_2"/>
<exclude body1="rim" body2="iiwa_1/link_3"/>
<exclude body1="rim" body2="iiwa_1/link_4"/>
<exclude body1="rim" body2="iiwa_1/link_5"/>
<exclude body1="rim" body2="iiwa_1/link_6"/>
<exclude body1="rim" body2="iiwa_1/link_7"/>
<exclude body1="rim" body2="iiwa_1/striker_joint_link"/>
<exclude body1="rim" body2="iiwa_1/striker_mallet"/>
<exclude body1="world" body2="iiwa_1/striker_mallet"/>
<exclude body1="table_surface" body2="iiwa_2/base"/>
<exclude body1="table_surface" body2="iiwa_2/link_1"/>
<exclude body1="table_surface" body2="iiwa_2/link_2"/>
<exclude body1="table_surface" body2="iiwa_2/link_3"/>
<exclude body1="table_surface" body2="iiwa_2/link_4"/>
<exclude body1="table_surface" body2="iiwa_2/link_5"/>
<exclude body1="table_surface" body2="iiwa_2/link_6"/>
<exclude body1="table_surface" body2="iiwa_2/link_7"/>
<exclude body1="table_surface" body2="iiwa_2/striker_joint_link"/>
<exclude body1="table_surface" body2="iiwa_2/striker_mallet"/>
<exclude body1="rim" body2="iiwa_2/base"/>
<exclude body1="rim" body2="iiwa_2/link_1"/>
<exclude body1="rim" body2="iiwa_2/link_2"/>
<exclude body1="rim" body2="iiwa_2/link_3"/>
<exclude body1="rim" body2="iiwa_2/link_4"/>
<exclude body1="rim" body2="iiwa_2/link_5"/>
<exclude body1="rim" body2="iiwa_2/link_6"/>
<exclude body1="rim" body2="iiwa_2/link_7"/>
<exclude body1="rim" body2="iiwa_2/striker_joint_link"/>
<exclude body1="rim" body2="iiwa_2/striker_mallet"/>
<exclude body1="world" body2="iiwa_2/striker_mallet"/>
</contact>
</mujoco>
@@ -0,0 +1,108 @@
<mujoco model="iiwa_1">
<compiler angle="radian" autolimits="true" meshdir="assets"/>
<default>
<default class="vis">
<geom contype="0" conaffinity="0"/>
</default>
<default class="robot">
<geom condim="4" solref="0.02 0.3" priority="2"/>
</default>
</default>
<asset>
<mesh name="link_0" file="link_0.stl"/>
<mesh name="link_1" file="link_1.stl"/>
<mesh name="link_2" file="link_2.stl"/>
<mesh name="link_3" file="link_3.stl"/>
<mesh name="link_4" file="link_4.stl"/>
<mesh name="link_5" file="link_5.stl"/>
<mesh name="link_6" file="link_6.stl"/>
<mesh name="link_7" file="link_7.stl"/>
<mesh name="EE_arm" file="EE_arm.stl"/>
<mesh name="EE_mallet_foam" file="EE_mallet_foam.stl"/>
</asset>
<worldbody>
<body name="iiwa_1/base" pos="-1.51 0 -0.1">
<geom type="mesh" rgba="0.4 0.4 0.4 1" mesh="link_0" class="vis"/>
<body name="iiwa_1/link_1" pos="0 0 0.1575">
<inertial pos="4.007709e-06 -0.033936 0.122467" mass="8.240527"
fullinertia="0.021981 0.022182 0.008234 -2.897243e-07 6.3165236e-07 0.003285"/>
<joint name="iiwa_1/joint_1" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706" damping="0.33032"
frictionloss="0.384477"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_1" class="vis"/>
<body name="iiwa_1/link_2" pos="0 0 0.2025" quat="0 0 0.707107 0.707107">
<inertial pos="0.003402 0.034792 0.046725" mass="6.357896"
fullinertia="0.015565 0.005180 0.015484 -4.147301e-06 1.192255e-05 0.002538"/>
<joint name="iiwa_1/joint_2" pos="0 0 0" axis="0 0 1" range="-2.0944 2.0944" damping="0.21216"
frictionloss="0.496333"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_2" class="vis"/>
<body name="iiwa_1/link_3" pos="0 0.2045 0" quat="0 0 0.707107 0.707107">
<inertial pos="-0.001452 0.031526 0.133584" mass="4.042756"
fullinertia="0.010914 0.010381 0.003139 -3.540575e-06 -9.059062e-06 -0.002128"/>
<joint name="iiwa_1/joint_3" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706" damping="0.1"
frictionloss="0.173951"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_3" class="vis"/>
<body name="iiwa_1/link_4" pos="0 0 0.2155" quat="0.707107 0.707107 0 0">
<inertial pos="-0.002527 0.053508 0.037205" mass="3.642249"
fullinertia="0.007536 0.002538 0.007206 -5.707028e-06 2.781894e-06 0.001256"/>
<joint name="iiwa_1/joint_4" pos="0 0 0" axis="0 0 1" range="-2.0944 2.0944"
damping="0.219041" frictionloss="0.3751"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_4" class="vis"/>
<body name="iiwa_1/link_5" pos="0 0.1845 0" quat="0 0 0.707107 0.707107">
<inertial pos="0.001855 0.024573 0.080131" mass="2.580896"
fullinertia="0.005201 0.004488 0.002242 1.089316e-07 9.035623e-07 -0.001613"/>
<joint name="iiwa_1/joint_5" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706"
damping="0.185923" frictionloss="0.481099"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_5" class="vis"/>
<body name="iiwa_1/link_6" pos="0 0 0.2155" quat="0.707107 0.707107 0 0">
<inertial pos="-0.001739 -0.001973 -0.002502" mass="2.760564"
fullinertia="0.002534 0.001821 0.002393 -1.311766e-06 9.508242e-07 0.000134"/>
<joint name="iiwa_1/joint_6" pos="0 0 0" axis="0 0 1" range="-2.0944 2.0944"
damping="0.1" frictionloss="0.196149"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_6" class="vis"/>
<body name="iiwa_1/link_7" pos="0 0.081 0" quat="0 0 0.707107 0.707107">
<inertial pos="0.000735 0.000387 0.026460" mass="1.285417"
fullinertia="0.000151 0.000150 0.000187 -7.223100e-08 2.038333e-06 -3.396830e-07"/>
<joint name="iiwa_1/joint_7" pos="0 0 0" axis="0 0 1" range="-3.05433 3.05433"
damping="0.1" frictionloss="0.299238" armature="0.01"/>
<geom type="mesh" rgba="0.4 0.4 0.4 1" mesh="link_7" class="vis"/>
<geom pos="0 0 0.07" type="mesh" rgba="0.3 0.3 0.3 1" mesh="EE_arm"
class="vis"/>
<body name="iiwa_1/striker_joint_link" pos="0 0 0.585">
<inertial pos="0 0 0" mass="0.1" diaginertia="0.001 0.001 0.001"/>
<body name="iiwa_1/striker_mallet" pos="0 0 0">
<inertial pos="0 0 0.0682827" mass="0.283"
diaginertia="0.005 0.005 0.005"/>
<joint name="iiwa_1/striker_joint_1" pos="0 0 0" axis="0 1 0"
range="-1.5708 1.5708" damping="0.0"/>
<joint name="iiwa_1/striker_joint_2" pos="0 0 0" axis="1 0 0"
range="-1.5708 1.5708" damping="0.0"/>
<geom type="mesh" rgba="0.3 0.3 0.3 1" mesh="EE_mallet_foam"
class="vis"/>
<geom name="iiwa_1/ee" type="cylinder" rgba="0.3 0.3 0.3 0.1"
size="0.04815 0.03" pos="0 0 0.0505" friction="0 0 0"/>
</body>
</body>
</body>
</body>
</body>
</body>
</body>
</body>
</body>
</body>
</worldbody>
<actuator>
<motor name="iiwa_1/joint_1" joint="iiwa_1/joint_1" ctrlrange="-320 320"/>
<motor name="iiwa_1/joint_2" joint="iiwa_1/joint_2" ctrlrange="-320 320"/>
<motor name="iiwa_1/joint_3" joint="iiwa_1/joint_3" ctrlrange="-176 176"/>
<motor name="iiwa_1/joint_4" joint="iiwa_1/joint_4" ctrlrange="-176 176"/>
<motor name="iiwa_1/joint_5" joint="iiwa_1/joint_5" ctrlrange="-110 110"/>
<motor name="iiwa_1/joint_6" joint="iiwa_1/joint_6" ctrlrange="-40 40"/>
<motor name="iiwa_1/joint_7" joint="iiwa_1/joint_7" ctrlrange="-40 40"/>
<motor name="iiwa_1/striker_joint_1" joint="iiwa_1/striker_joint_1" ctrlrange="-10 10"/>
<motor name="iiwa_1/striker_joint_2" joint="iiwa_1/striker_joint_2" ctrlrange="-10 10"/>
</actuator>
</mujoco>
@@ -0,0 +1,89 @@
<mujoco model="iiwa_2">
<compiler angle="radian" autolimits="true" meshdir="assets"/>
<worldbody>
<body name="iiwa_2/base" pos="1.51 0 -0.1" quat="0 0 0 1">
<geom type="mesh" rgba="0.4 0.4 0.4 1" mesh="link_0" class="vis"/>
<body name="iiwa_2/link_1" pos="0 0 0.1575">
<inertial pos="4.007709e-06 -0.033936 0.122467" mass="8.240527"
fullinertia="0.021981 0.022182 0.008234 -2.897243e-07 6.3165236e-07 0.003285"/>
<joint name="iiwa_2/joint_1" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706" damping="0.33032"
frictionloss="0.384477"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_1" class="vis"/>
<body name="iiwa_2/link_2" pos="0 0 0.2025" quat="0 0 0.707107 0.707107">
<inertial pos="0.003402 0.034792 0.046725" mass="6.357896"
fullinertia="0.015565 0.005180 0.015484 -4.147301e-06 1.192255e-05 0.002538"/>
<joint name="iiwa_2/joint_2" pos="0 0 0" axis="0 0 1" range="-2.0944 2.0944" damping="0.21216"
frictionloss="0.496333"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_2" class="vis"/>
<body name="iiwa_2/link_3" pos="0 0.2045 0" quat="0 0 0.707107 0.707107">
<inertial pos="-0.001452 0.031526 0.133584" mass="4.042756"
fullinertia="0.010914 0.010381 0.003139 -3.540575e-06 -9.059062e-06 -0.002128"/>
<joint name="iiwa_2/joint_3" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706" damping="0.1"
frictionloss="0.173951"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_3" class="vis"/>
<body name="iiwa_2/link_4" pos="0 0 0.2155" quat="0.707107 0.707107 0 0">
<inertial pos="-0.002527 0.053508 0.037205" mass="3.642249"
fullinertia="0.007536 0.002538 0.007206 -5.707028e-06 2.781894e-06 0.001256"/>
<joint name="iiwa_2/joint_4" pos="0 0 0" axis="0 0 1" range="-2.0944 2.0944"
damping="0.219041" frictionloss="0.3751"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_4" class="vis"/>
<body name="iiwa_2/link_5" pos="0 0.1845 0" quat="0 0 0.707107 0.707107">
<inertial pos="0.001855 0.024573 0.080131" mass="2.580896"
fullinertia="0.005201 0.004488 0.002242 1.089316e-07 9.035623e-07 -0.001613"/>
<joint name="iiwa_2/joint_5" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706"
damping="0.185923" frictionloss="0.481099"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_5" class="vis"/>
<body name="iiwa_2/link_6" pos="0 0 0.2155" quat="0.707107 0.707107 0 0">
<inertial pos="-0.001739 -0.001973 -0.002502" mass="2.760564"
fullinertia="0.002534 0.001821 0.002393 -1.311766e-06 9.508242e-07 0.000134"/>
<joint name="iiwa_2/joint_6" pos="0 0 0" axis="0 0 1" range="-2.0944 2.0944"
damping="0.1" frictionloss="0.196149"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_6" class="vis"/>
<body name="iiwa_2/link_7" pos="0 0.081 0" quat="0 0 0.707107 0.707107">
<inertial pos="0.000735 0.000387 0.026460" mass="1.285417"
fullinertia="0.000151 0.000150 0.000187 -7.223100e-08 2.038333e-06 -3.396830e-07"/>
<joint name="iiwa_2/joint_7" pos="0 0 0" axis="0 0 1" range="-3.05433 3.05433"
damping="0.1" frictionloss="0.299238" armature="0.01"/>
<geom type="mesh" rgba="0.4 0.4 0.4 1" mesh="link_7" class="vis"/>
<geom pos="0 0 0.07" type="mesh" rgba="0.3 0.3 0.3 1" mesh="EE_arm"
class="vis"/>
<body name="iiwa_2/striker_joint_link" pos="0 0 0.585">
<inertial pos="0 0 0" mass="0.1" diaginertia="0.001 0.001 0.001"/>
<body name="iiwa_2/striker_mallet" pos="0 0 0">
<inertial pos="0 0 0.0682827" mass="0.283"
diaginertia="0.005 0.005 0.005"/>
<joint name="iiwa_2/striker_joint_1" pos="0 0 0" axis="0 1 0"
range="-1.5708 1.5708" damping="0.0"/>
<joint name="iiwa_2/striker_joint_2" pos="0 0 0" axis="1 0 0"
range="-1.5708 1.5708" damping="0.0"/>
<geom type="mesh" rgba="0.3 0.3 0.3 1" mesh="EE_mallet_foam"
class="vis"/>
<geom name="iiwa_2/ee" type="cylinder" rgba="0.3 0.3 0.3 0.1"
size="0.04815 0.03" pos="0 0 0.0505" friction="0 0 0"/>
</body>
</body>
</body>
</body>
</body>
</body>
</body>
</body>
</body>
</body>
</worldbody>
<actuator>
<motor name="iiwa_2/joint_1" joint="iiwa_2/joint_1" ctrlrange="-320 320"/>
<motor name="iiwa_2/joint_2" joint="iiwa_2/joint_2" ctrlrange="-320 320"/>
<motor name="iiwa_2/joint_3" joint="iiwa_2/joint_3" ctrlrange="-176 176"/>
<motor name="iiwa_2/joint_4" joint="iiwa_2/joint_4" ctrlrange="-176 176"/>
<motor name="iiwa_2/joint_5" joint="iiwa_2/joint_5" ctrlrange="-110 110"/>
<motor name="iiwa_2/joint_6" joint="iiwa_2/joint_6" ctrlrange="-40 40"/>
<motor name="iiwa_2/joint_7" joint="iiwa_2/joint_7" ctrlrange="-40 40"/>
<motor name="iiwa_2/striker_joint_1" joint="iiwa_2/striker_joint_1" ctrlrange="-10 10"/>
<motor name="iiwa_2/striker_joint_2" joint="iiwa_2/striker_joint_2" ctrlrange="-10 10"/>
</actuator>
</mujoco>
@@ -0,0 +1,94 @@
<mujoco model="iiwa_1">
<compiler angle="radian" autolimits="true" meshdir="assets"/>
<default>
<default class="vis">
<geom contype="0" conaffinity="0"/>
</default>
<default class="robot">
<geom condim="4" solref="0.02 0.3" priority="2"/>
</default>
</default>
<asset>
<mesh name="link_0" file="link_0.stl"/>
<mesh name="link_1" file="link_1.stl"/>
<mesh name="link_2" file="link_2.stl"/>
<mesh name="link_3" file="link_3.stl"/>
<mesh name="link_4" file="link_4.stl"/>
<mesh name="link_5" file="link_5.stl"/>
<mesh name="link_6" file="link_6.stl"/>
<mesh name="link_7" file="link_7.stl"/>
<mesh name="EE_arm" file="EE_arm.stl"/>
<mesh name="EE_mallet_foam" file="EE_mallet_foam.stl"/>
</asset>
<worldbody>
<body name="iiwa_1/base" pos="0.0 0 0.0">
<geom type="mesh" rgba="0.4 0.4 0.4 1" mesh="link_0" class="vis"/>
<body name="iiwa_1/link_1" pos="0 0 0.1575">
<inertial pos="4.007709e-06 -0.033936 0.122467" mass="8.240527"
fullinertia="0.021981 0.022182 0.008234 -2.897243e-07 6.3165236e-07 0.003285"/>
<joint name="iiwa_1/joint_1" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706" damping="0.33032"
frictionloss="0.384477"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_1" class="vis"/>
<body name="iiwa_1/link_2" pos="0 0 0.2025" quat="0 0 0.707107 0.707107">
<inertial pos="0.003402 0.034792 0.046725" mass="6.357896"
fullinertia="0.015565 0.005180 0.015484 -4.147301e-06 1.192255e-05 0.002538"/>
<joint name="iiwa_1/joint_2" pos="0 0 0" axis="0 0 1" range="-2.0944 2.0944" damping="0.21216"
frictionloss="0.496333"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_2" class="vis"/>
<body name="iiwa_1/link_3" pos="0 0.2045 0" quat="0 0 0.707107 0.707107">
<inertial pos="-0.001452 0.031526 0.133584" mass="4.042756"
fullinertia="0.010914 0.010381 0.003139 -3.540575e-06 -9.059062e-06 -0.002128"/>
<joint name="iiwa_1/joint_3" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706" damping="0.1"
frictionloss="0.173951"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_3" class="vis"/>
<body name="iiwa_1/link_4" pos="0 0 0.2155" quat="0.707107 0.707107 0 0">
<inertial pos="-0.002527 0.053508 0.037205" mass="3.642249"
fullinertia="0.007536 0.002538 0.007206 -5.707028e-06 2.781894e-06 0.001256"/>
<joint name="iiwa_1/joint_4" pos="0 0 0" axis="0 0 1" range="-2.0944 2.0944"
damping="0.219041" frictionloss="0.3751"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_4" class="vis"/>
<body name="iiwa_1/link_5" pos="0 0.1845 0" quat="0 0 0.707107 0.707107">
<inertial pos="0.001855 0.024573 0.080131" mass="2.580896"
fullinertia="0.005201 0.004488 0.002242 1.089316e-07 9.035623e-07 -0.001613"/>
<joint name="iiwa_1/joint_5" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706"
damping="0.185923" frictionloss="0.481099"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_5" class="vis"/>
<body name="iiwa_1/link_6" pos="0 0 0.2155" quat="0.707107 0.707107 0 0">
<inertial pos="-0.001739 -0.001973 -0.002502" mass="2.760564"
fullinertia="0.002534 0.001821 0.002393 -1.311766e-06 9.508242e-07 0.000134"/>
<joint name="iiwa_1/joint_6" pos="0 0 0" axis="0 0 1" range="-2.0944 2.0944"
damping="0.1" frictionloss="0.196149"/>
<geom type="mesh" rgba="1 0.423529 0.0392157 1" mesh="link_6" class="vis"/>
<body name="iiwa_1/link_7" pos="0 0.081 0" quat="0 0 0.707107 0.707107">
<inertial pos="0.000735 0.000387 0.026460" mass="1.285417"
fullinertia="0.000151 0.000150 0.000187 -7.223100e-08 2.038333e-06 -3.396830e-07"/>
<joint name="iiwa_1/joint_7" pos="0 0 0" axis="0 0 1" range="-3.05433 3.05433"
damping="0.1" frictionloss="0.299238" armature="0.01"/>
<geom type="mesh" rgba="0.4 0.4 0.4 1" mesh="link_7" class="vis"/>
<geom pos="0 0 0.07" type="mesh" rgba="0.3 0.3 0.3 1" mesh="EE_arm"
class="vis"/>
<body name="iiwa_1/striker_joint_link" pos="0 0 0.585">
<inertial pos="0 0 0" mass="0.1" diaginertia="0.001 0.001 0.001"/>
</body>
</body>
</body>
</body>
</body>
</body>
</body>
</body>
</body>
</worldbody>
<actuator>
<motor name="iiwa_1/joint_1" joint="iiwa_1/joint_1" ctrlrange="-320 320"/>
<motor name="iiwa_1/joint_2" joint="iiwa_1/joint_2" ctrlrange="-320 320"/>
<motor name="iiwa_1/joint_3" joint="iiwa_1/joint_3" ctrlrange="-176 176"/>
<motor name="iiwa_1/joint_4" joint="iiwa_1/joint_4" ctrlrange="-176 176"/>
<motor name="iiwa_1/joint_5" joint="iiwa_1/joint_5" ctrlrange="-110 110"/>
<motor name="iiwa_1/joint_6" joint="iiwa_1/joint_6" ctrlrange="-40 40"/>
<motor name="iiwa_1/joint_7" joint="iiwa_1/joint_7" ctrlrange="-40 40"/>
</actuator>
</mujoco>
@@ -0,0 +1,32 @@
<mujoco model="AirHockeySingle">
<include file="iiwa1.xml"/>
<include file="../table.xml"/>
<contact>
<exclude body1="table_surface" body2="iiwa_1/base"/>
<exclude body1="table_surface" body2="iiwa_1/link_1"/>
<exclude body1="table_surface" body2="iiwa_1/link_2"/>
<exclude body1="table_surface" body2="iiwa_1/link_3"/>
<exclude body1="table_surface" body2="iiwa_1/link_4"/>
<exclude body1="table_surface" body2="iiwa_1/link_5"/>
<exclude body1="table_surface" body2="iiwa_1/link_6"/>
<exclude body1="table_surface" body2="iiwa_1/link_7"/>
<exclude body1="table_surface" body2="iiwa_1/striker_joint_link"/>
<exclude body1="table_surface" body2="iiwa_1/striker_mallet"/>
<exclude body1="rim" body2="iiwa_1/base"/>
<exclude body1="rim" body2="iiwa_1/link_1"/>
<exclude body1="rim" body2="iiwa_1/link_2"/>
<exclude body1="rim" body2="iiwa_1/link_3"/>
<exclude body1="rim" body2="iiwa_1/link_4"/>
<exclude body1="rim" body2="iiwa_1/link_5"/>
<exclude body1="rim" body2="iiwa_1/link_6"/>
<exclude body1="rim" body2="iiwa_1/link_7"/>
<exclude body1="rim" body2="iiwa_1/striker_joint_link"/>
<exclude body1="rim" body2="iiwa_1/striker_mallet"/>
<exclude body1="world" body2="iiwa_1/striker_mallet"/>
</contact>
</mujoco>
@@ -0,0 +1,37 @@
<mujoco model="AirHockeySingle">
<include file="planar_robot_1.xml"/>
<include file="planar_robot_2.xml"/>
<include file="../table.xml"/>
<contact>
<exclude body1="planar_robot_1/body_ee" body2="table_surface"/>
<exclude body1="planar_robot_1/body_hand" body2="table_surface"/>
<exclude body1="planar_robot_1/body_3" body2="table_surface"/>
<exclude body1="planar_robot_1/body_2" body2="table_surface"/>
<exclude body1="planar_robot_1/body_1" body2="table_surface"/>
<exclude body1="planar_robot_1/base" body2="table_surface"/>
<exclude body1="planar_robot_1/body_ee" body2="rim"/>
<exclude body1="planar_robot_1/body_hand" body2="rim"/>
<exclude body1="planar_robot_1/body_3" body2="rim"/>
<exclude body1="planar_robot_1/body_2" body2="rim"/>
<exclude body1="planar_robot_1/body_1" body2="rim"/>
<exclude body1="planar_robot_1/base" body2="rim"/>
<exclude body1="planar_robot_2/body_ee" body2="table_surface"/>
<exclude body1="planar_robot_2/body_hand" body2="table_surface"/>
<exclude body1="planar_robot_2/body_3" body2="table_surface"/>
<exclude body1="planar_robot_2/body_2" body2="table_surface"/>
<exclude body1="planar_robot_2/body_1" body2="table_surface"/>
<exclude body1="planar_robot_2/base" body2="table_surface"/>
<exclude body1="planar_robot_2/body_ee" body2="rim"/>
<exclude body1="planar_robot_2/body_hand" body2="rim"/>
<exclude body1="planar_robot_2/body_3" body2="rim"/>
<exclude body1="planar_robot_2/body_2" body2="rim"/>
<exclude body1="planar_robot_2/body_1" body2="rim"/>
<exclude body1="planar_robot_2/base" body2="rim"/>
</contact>
</mujoco>
@@ -0,0 +1,61 @@
<mujoco model="planar_robot_1">
<compiler autolimits="true" angle="radian"/>
<asset>
<material name="dark_red" rgba="0.58 0.03 0.25 1"/>
<material name="black" rgba="0.1 0.1 0.11 1"/>
</asset>
<default>
<default class="visual">
<geom contype="0" conaffinity="0"/>
</default>
<default class="robot">
<geom condim="4" solref="0.02 0.3" priority="2"/>
</default>
</default>
<worldbody>
<body name="planar_robot_1/base" pos="-1.51 0 -0.1">
<geom type="cylinder" material="black" size="0.075 0.2" pos="0 0 0.2" class="visual"/>
<inertial pos="0 0 0" mass="1" diaginertia="0.05 0.018 0.044"/>
<body name="planar_robot_1/body_1" pos="0 0 0.25">
<joint name="planar_robot_1/joint_1" axis="0 0 1" range="-2.9670597283903604 2.9670597283903604"/>
<inertial pos="0.25 0 0" mass="3" diaginertia="0.064375 0.064375 0.00375"/>
<geom class="robot" type="cylinder" material="dark_red" size="0.04 0.275" euler="0 1.57079632679 0"
pos="0.275 0 0"/>
<body name="planar_robot_1/body_2" pos="0.55 0 0">
<joint name="planar_robot_1/joint_2" axis="0 0 1" range="-1.8 1.8"/>
<inertial pos="0.2 0 0" mass="2" diaginertia="0.0335 0.0335 0.003"/>
<geom class="robot" type="cylinder" material="dark_red" size="0.04 0.22" euler="0 1.57079632679 0"
pos="0.22 0 0"/>
<geom class="robot" type="sphere" material="dark_red" size="0.05"/>
<body name="planar_robot_1/body_3" pos="0.44 0 0">
<joint name="planar_robot_1/joint_3" axis="0 0 1"
range="-2.0943951023931953 2.0943951023931953"/>
<inertial pos="0.2 0 0" mass="2" diaginertia="0.0335 0.0335 0.003"/>
<geom class="robot" type="cylinder" material="dark_red" size="0.04 0.22"
euler="0 1.57079632679 0" pos="0.22 0 0"/>
<geom class="robot" type="sphere" material="dark_red" size="0.05"/>
<body name="planar_robot_1/body_hand" pos="0.44 0 0">
<inertial pos="0 0 0" mass="0.1" diaginertia="0.0008 0.0023 0.0023"/>
<geom class="robot" type="sphere" material="dark_red" size="0.05"/>
<geom class="robot" type="cylinder" material="black" size="0.01 0.075" pos="0 0 -0.075"/>
<body name="planar_robot_1/body_ee" pos="0 0 -0.15">
<inertial pos="0 0 0" mass="0.1" diaginertia="0.0008 0.0023 0.0023"/>
<geom class="robot" name="planar_robot_1/ee" type="cylinder" material="black"
size="0.04815 0.01" pos="0 0 0.01" friction="0 0 0"/>
</body>
</body>
</body>
</body>
</body>
</body>
</worldbody>
<actuator>
<motor name="planar_robot_1/joint_1" joint="planar_robot_1/joint_1" ctrlrange="-100 100"/>
<motor name="planar_robot_1/joint_2" joint="planar_robot_1/joint_2" ctrlrange="-50 50"/>
<motor name="planar_robot_1/joint_3" joint="planar_robot_1/joint_3" ctrlrange="-30 30"/>
</actuator>
</mujoco>
@@ -0,0 +1,46 @@
<mujoco model="planar_robot_2">
<compiler autolimits="true" angle="radian"/>
<worldbody>
<body name="planar_robot_2/base" pos="1.51 0 -0.1" quat="0 0 0 1">
<geom type="cylinder" material="black" size="0.075 0.2" pos="0 0 0.2" class="visual"/>
<inertial pos="0 0 0" mass="1" diaginertia="0.05 0.018 0.044"/>
<body name="planar_robot_2/body_1" pos="0 0 0.25">
<joint name="planar_robot_2/joint_1" axis="0 0 1" range="-2.9670597283903604 2.9670597283903604"/>
<inertial pos="0.25 0 0" mass="3" diaginertia="0.064375 0.064375 0.00375"/>
<geom type="cylinder" material="dark_red" size="0.04 0.275" euler="0 1.57079632679 0" pos="0.275 0 0"/>
<body name="planar_robot_2/body_2" pos="0.55 0 0">
<joint name="planar_robot_2/joint_2" axis="0 0 1" range="-1.8 1.8"/>
<inertial pos="0.2 0 0" mass="2" diaginertia="0.0335 0.0335 0.003"/>
<geom type="cylinder" material="dark_red" size="0.04 0.22" euler="0 1.57079632679 0"
pos="0.22 0 0"/>
<geom type="sphere" material="dark_red" size="0.05"/>
<body name="planar_robot_2/body_3" pos="0.44 0 0">
<joint name="planar_robot_2/joint_3" axis="0 0 1"
range="-2.0943951023931953 2.0943951023931953"/>
<inertial pos="0.2 0 0" mass="2" diaginertia="0.0335 0.0335 0.003"/>
<geom type="cylinder" material="dark_red" size="0.04 0.22" euler="0 1.57079632679 0"
pos="0.22 0 0"/>
<geom type="sphere" material="dark_red" size="0.05"/>
<body name="planar_robot_2/body_hand" pos="0.44 0 0">
<inertial pos="0 0 0" mass="0.1" diaginertia="0.0008 0.0023 0.0023"/>
<geom type="sphere" material="dark_red" size="0.05"/>
<geom type="cylinder" material="black" size="0.01 0.075" pos="0 0 -0.075"/>
<body name="planar_robot_2/body_ee" pos="0 0 -0.15">
<inertial pos="0 0 0" mass="0.1" diaginertia="0.0008 0.0023 0.0023"/>
<geom name="planar_robot_2/ee" type="cylinder" material="black" size="0.04815 0.01"
pos="0 0 0.01" friction="0 0 0"/>
</body>
</body>
</body>
</body>
</body>
</body>
</worldbody>
<actuator>
<motor name="planar_robot_2/joint_1" joint="planar_robot_2/joint_1" ctrlrange="-100 100"/>
<motor name="planar_robot_2/joint_2" joint="planar_robot_2/joint_2" ctrlrange="-50 50"/>
<motor name="planar_robot_2/joint_3" joint="planar_robot_2/joint_3" ctrlrange="-30 30"/>
</actuator>
</mujoco>
@@ -0,0 +1,24 @@
<mujoco model="AirHockeySingle">
<include file="planar_robot_1.xml"/>
<include file="../table.xml"/>
<contact>
<exclude body1="planar_robot_1/body_ee" body2="table_surface"/>
<exclude body1="planar_robot_1/body_hand" body2="table_surface"/>
<exclude body1="planar_robot_1/body_3" body2="table_surface"/>
<exclude body1="planar_robot_1/body_2" body2="table_surface"/>
<exclude body1="planar_robot_1/body_1" body2="table_surface"/>
<exclude body1="planar_robot_1/base" body2="table_surface"/>
<exclude body1="planar_robot_1/body_ee" body2="rim"/>
<exclude body1="planar_robot_1/body_hand" body2="rim"/>
<exclude body1="planar_robot_1/body_3" body2="rim"/>
<exclude body1="planar_robot_1/body_2" body2="rim"/>
<exclude body1="planar_robot_1/body_1" body2="rim"/>
<exclude body1="planar_robot_1/base" body2="rim"/>
</contact>
</mujoco>
@@ -0,0 +1,89 @@
<mujoco model="table">
<option timestep="0.001" cone="elliptic" impratio="1"/>
<asset>
<material name="grey" specular="0.5" shininess="0.25" rgba="0.8 0.8 0.8 1"/>
<material name="white" specular="0.5" shininess="0.25" rgba="1.0 1.0 1.0 1"/>
<material name="red" specular="0.5" shininess="0.25" rgba="1.0 0.0 0.0 1"/>
<material name="blue" specular="0.5" shininess="0.25" rgba="0.0 0.0 1.0 1"/>
<material name="transparent" specular="0.5" shininess="0.25" rgba="0.0 0.0 1.0 0"/>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512"
height="3072"/>
<texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="0.2 0.3 0.4"
rgb2="0.1 0.2 0.3" markrgb="0.8 0.8 0.8" width="300" height="300"/>
<material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5"
reflectance="0.2"/>
</asset>
<default>
<geom condim="4" solref="0.02 0.3"/>
<default class="rim">
<geom type="box" material="grey" condim="6" friction="10000 0.0 0.0" priority="1"
solref="-2000000 -250" solimp="0.99 0.999 0.001 0.5 2"/>
</default>
</default>
<worldbody>
<body name="table">
<body name="table_surface">
<geom name="surface" type="box" material="white" size="1.064 0.609 0.0505" pos="0 0 -0.0505"
condim="4" friction="0.08 0.08 0.0" priority="1"
solref="0.0125 0.5" solimp="0.9 0.999 0.001 0.5 2"/>
</body>
<body name="rim">
<!-- <geom name="rim_home_l" class="rim" size="0.045 0.197 0.005" pos="-1.019 0.322 0.005"/>-->
<!-- <geom name="rim_home_r" class="rim" size="0.045 0.197 0.005" pos="-1.019 -0.322 0.005"/>-->
<geom name="rim_home_l" class="rim" size="0.045 0.1945 0.005" pos="-1.019 0.3245 0.005"/>
<geom name="rim_home_r" class="rim" size="0.045 0.1945 0.005" pos="-1.019 -0.3245 0.005"/>
<geom name="rim_home_bound_l" class="rim" type="cylinder" size="0.005 0.005" pos="-0.979 0.13 0.005"/>
<geom name="rim_home_bound_l_tail" class="rim" size="0.0425 0.005 0.005" pos="-1.0215 0.13 0.005"/>
<geom name="rim_home_bound_r" class="rim" type="cylinder" size="0.005 0.005" pos="-0.979 -0.13 0.005"/>
<geom name="rim_home_bound_r_tail" class="rim" size="0.0425 0.005 0.005" pos="-1.0215 -0.13 0.005"/>
<geom name="rim_home_top" class="rim" size="0.045 0.519 0.01" pos="-1.019 0 0.02"/>
<geom name="rim_left" class="rim" size="1.064 0.045 0.015" pos="0 0.564 0.015"/>
<geom name="rim_right" class="rim" size="1.064 0.045 0.015" pos="0 -0.564 0.015"/>
<!-- <geom name="rim_away_l" class="rim" size="0.045 0.197 0.005" pos="1.019 0.322 0.005"/>-->
<!-- <geom name="rim_away_r" class="rim" size="0.045 0.197 0.005" pos="1.019 -0.322 0.005"/>-->
<geom name="rim_away_l" class="rim" size="0.045 0.1945 0.005" pos="1.019 0.3245 0.005"/>
<geom name="rim_away_r" class="rim" size="0.045 0.1945 0.005" pos="1.019 -0.3245 0.005"/>
<geom name="rim_away_bound_l" class="rim" type="cylinder" size="0.005 0.005" pos="0.979 0.13 0.005"/>
<geom name="rim_away_bound_l_tail" class="rim" size="0.0425 0.005 0.005" pos="1.0215 0.13 0.005"/>
<geom name="rim_away_bound_r" class="rim" type="cylinder" size="0.005 0.005" pos="0.979 -0.13 0.005"/>
<geom name="rim_away_bound_r_tail" class="rim" size="0.0425 0.005 0.005" pos="1.0215 -0.13 0.005"/>
<geom name="rim_away_top" class="rim" size="0.045 0.519 0.01" pos="1.019 0 0.02"/>
</body>
</body>
<body name="base" pos="0 0 0">
<joint name="puck_x" type="slide" axis="1 0 0" damping="0.005" limited="false"/>
<joint name="puck_y" type="slide" axis="0 1 0" damping="0.005" limited="false"/>
<joint name="puck_yaw" type="hinge" axis="0 0 1" damping="2e-6" limited="false"/>
<body name="puck">
<geom pos="0 0 0" name="puck" type="cylinder" material="red" size="0.03165 0.003"
condim="4" priority="0"/>
<geom pos="0.02 0 0" type="cylinder" material="blue" size="0.01 0.0031"
condim="4" contype="0" conaffinity="0"/>
<inertial pos="0 0 0" mass="0.01" diaginertia="2.5e-6 2.5e-6 5e-6"/>
</body>
</body>
<site name="puck_vis" type="ellipsoid" size="0.03165 0.03165 0.004" pos="0 0 -0.05"/>
<site name="puck_vis_rot" type="cylinder" size="0.0045 0.006" rgba="1 0 0 1" pos="0 0 -0.05"/>
</worldbody>
<contact>
<exclude body1="puck" body2="table_surface"/>
</contact>
<worldbody>
<light pos="0 0 3" dir="0 0 -1" directional="true"/>
<geom pos="0 0 -0.1" name="floor" size="0 0 0.05" type="plane" material="groundplane"/>
</worldbody>
</mujoco>
@@ -0,0 +1,270 @@
from collections import deque
import mujoco
import numpy as np
import scipy
from fancy_gym.envs.mujoco.air_hockey import seven_dof
from fancy_gym.envs.mujoco.air_hockey import three_dof
class PositionControl:
def __init__(self, p_gain, d_gain, i_gain, interpolation_order=3, debug=False, *args, **kwargs):
"""
Mixin that adds position controller to mujoco environments.
Args:
p_gain (float): Proportional controller gain
d_gain (float): Differential controller gain
i_gain (float): Integral controller gain
interpolation_order (int, 3): Type of interpolation used, has to correspond to action shape. Order 1-5 are
polynomial interpolation of the degree. Order -1 is linear interpolation of position and velocity.
Set Order to None in order to turn off interpolation. In this case the action has to be a trajectory
of position, velocity and acceleration of the shape (20, 3, n_joints)
In the case of 2 agents it is a tuple, which describes the interpolation order for each agent
debug (bool, True): If true it logs the controller performance into controller_record queue. The order of the
entries is desired_pos, current_pos, desired_vel, current_vel, desired_acc, jerk.
"""
self.debug = debug
super(PositionControl, self).__init__(*args, **kwargs)
self.robot_model = self.env_info['robot']['robot_model']
self.robot_data = self.env_info['robot']['robot_data']
self.p_gain = np.array(p_gain * self.n_agents)
self.d_gain = np.array(d_gain * self.n_agents)
self.i_gain = np.array(i_gain * self.n_agents)
self.prev_pos = np.zeros(len(self.actuator_joint_ids))
self.prev_vel = np.zeros(len(self.actuator_joint_ids))
self.prev_acc = np.zeros(len(self.actuator_joint_ids))
self.i_error = np.zeros(len(self.actuator_joint_ids))
self.prev_controller_cmd_pos = np.zeros(len(self.actuator_joint_ids))
self.interp_order = interpolation_order if type(interpolation_order) is tuple else (interpolation_order,)
self._num_env_joints = len(self.actuator_joint_ids)
self.n_robot_joints = self.env_info['robot']["n_joints"]
self.action_shape = [None] * self.n_agents
for i in range(self.n_agents):
if self.interp_order[i] is None:
self.action_shape[i] = (int(self.dt / self._timestep), 3, self.n_robot_joints)
elif self.interp_order[i] in [1, 2]:
self.action_shape[i] = (self.n_robot_joints,)
elif self.interp_order[i] in [3, 4, -1]:
self.action_shape[i] = (2, self.n_robot_joints)
elif self.interp_order[i] == 5:
self.action_shape[i] = (3, self.n_robot_joints)
self.traj = None
self.jerk = np.zeros(self._num_env_joints)
if self.debug:
self.controller_record = deque(maxlen=self.info.horizon * self._n_intermediate_steps)
def _enforce_safety_limits(self, desired_pos, desired_vel):
# ROS safe controller
pos = self.prev_controller_cmd_pos
k = 20
joint_pos_lim = np.tile(self.env_info['robot']['joint_pos_limit'], (1, self.n_agents))
joint_vel_lim = np.tile(self.env_info['robot']['joint_vel_limit'], (1, self.n_agents))
min_vel = np.minimum(np.maximum(-k * (pos - joint_pos_lim[0]), joint_vel_lim[0]), joint_vel_lim[1])
max_vel = np.minimum(np.maximum(-k * (pos - joint_pos_lim[1]), joint_vel_lim[0]), joint_vel_lim[1])
clipped_vel = np.minimum(np.maximum(desired_vel, min_vel), max_vel)
min_pos = pos + min_vel * self._timestep
max_pos = pos + max_vel * self._timestep
clipped_pos = np.minimum(np.maximum(desired_pos, min_pos), max_pos)
self.prev_controller_cmd_pos = clipped_pos.copy()
return clipped_pos, clipped_vel
def _controller(self, desired_pos, desired_vel, desired_acc, current_pos, current_vel):
clipped_pos, clipped_vel = self._enforce_safety_limits(desired_pos, desired_vel)
error = (clipped_pos - current_pos)
self.i_error += self.i_gain * error * self._timestep
torque = self.p_gain * error + self.d_gain * (clipped_vel - current_vel) + self.i_error
# Acceleration FeedForward
tau_ff = np.zeros(self.robot_model.nv)
for i in range(self.n_agents):
robot_joint_ids = np.arange(self.n_robot_joints) + self.n_robot_joints * i
self.robot_data.qpos = current_pos[robot_joint_ids]
self.robot_data.qvel = current_vel[robot_joint_ids]
acc_ff = desired_acc[robot_joint_ids]
mujoco.mj_forward(self.robot_model, self.robot_data)
mujoco.mj_mulM(self.robot_model, self.robot_data, tau_ff, acc_ff)
torque[robot_joint_ids] += tau_ff
# Gravity Compensation and Coriolis and Centrifugal force
torque[robot_joint_ids] += self.robot_data.qfrc_bias
torque[robot_joint_ids] = np.minimum(np.maximum(torque[robot_joint_ids],
self.robot_model.actuator_ctrlrange[:, 0]),
self.robot_model.actuator_ctrlrange[:, 1])
if self.debug:
self.controller_record.append(
np.concatenate([desired_pos, current_pos, desired_vel, current_vel, desired_acc, self.jerk]))
return torque
def _interpolate_trajectory(self, interp_order, action, i=0):
tf = self.dt
prev_pos = self.prev_pos[i*self.n_robot_joints:(i+1)*self.n_robot_joints]
prev_vel = self.prev_vel[i*self.n_robot_joints:(i+1)*self.n_robot_joints]
prev_acc = self.prev_acc[i*self.n_robot_joints:(i+1)*self.n_robot_joints]
if interp_order == 1 and action.ndim == 1:
coef = np.array([[1, 0], [1, tf]])
results = np.vstack([prev_pos, action])
elif interp_order == 2 and action.ndim == 1:
coef = np.array([[1, 0, 0], [1, tf, tf ** 2], [0, 1, 0]])
if np.linalg.norm(action - prev_pos) < 1e-3:
prev_vel = np.zeros_like(prev_vel)
results = np.vstack([prev_pos, action, prev_vel])
elif interp_order == 3 and action.shape[0] == 2:
coef = np.array([[1, 0, 0, 0], [1, tf, tf ** 2, tf ** 3], [0, 1, 0, 0], [0, 1, 2 * tf, 3 * tf ** 2]])
results = np.vstack([prev_pos, action[0], prev_vel, action[1]])
elif interp_order == 4 and action.shape[0] == 2:
coef = np.array([[1, 0, 0, 0, 0], [1, tf, tf ** 2, tf ** 3, tf ** 4],
[0, 1, 0, 0, 0], [0, 1, 2 * tf, 3 * tf ** 2, 4 * tf ** 3],
[0, 0, 2, 0, 0]])
results = np.vstack([prev_pos, action[0], prev_vel, action[1], prev_acc])
elif interp_order == 5 and action.shape[0] == 3:
coef = np.array([[1, 0, 0, 0, 0, 0], [1, tf, tf ** 2, tf ** 3, tf ** 4, tf ** 5],
[0, 1, 0, 0, 0, 0], [0, 1, 2 * tf, 3 * tf ** 2, 4 * tf ** 3, 5 * tf ** 4],
[0, 0, 2, 0, 0, 0], [0, 0, 2, 6 * tf, 12 * tf ** 2, 20 * tf ** 3]])
results = np.vstack([prev_pos, action[0], prev_vel, action[1], prev_acc, action[2]])
elif interp_order == -1:
# Interpolate position and velocity linearly
pass
else:
raise ValueError("Undefined interpolator order or the action dimension does not match!")
if interp_order > 0:
A = scipy.linalg.block_diag(*[coef] * self.n_robot_joints)
y = results.reshape(-2, order='F')
weights = np.linalg.solve(A, y).reshape(self.n_robot_joints, interp_order + 1)
weights_d = np.polynomial.polynomial.polyder(weights, axis=1)
weights_dd = np.polynomial.polynomial.polyder(weights_d, axis=1)
elif interp_order == -1:
weights = np.vstack([prev_pos, (action[0] - prev_pos) / self.dt]).T
weights_d = np.vstack([prev_vel, (action[1] - prev_vel) / self.dt]).T
weights_dd = np.polynomial.polynomial.polyder(weights_d, axis=1)
if interp_order in [3, 4, 5]:
self.jerk[i*self.n_robot_joints:(i+1)*self.n_robot_joints] = np.abs(weights_dd[:, 1]) + np.abs(weights_dd[:, 0] - prev_acc) / self._timestep
else:
self.jerk[i*self.n_robot_joints:(i+1)*self.n_robot_joints] = np.ones_like(prev_acc) * np.inf
self.prev_pos[i*self.n_robot_joints:(i+1)*self.n_robot_joints] = np.polynomial.polynomial.polyval(tf, weights.T)
self.prev_vel[i*self.n_robot_joints:(i+1)*self.n_robot_joints] = np.polynomial.polynomial.polyval(tf, weights_d.T)
self.prev_acc[i*self.n_robot_joints:(i+1)*self.n_robot_joints] = np.polynomial.polynomial.polyval(tf, weights_dd.T)
for t in np.linspace(self._timestep, self.dt, self._n_intermediate_steps):
q = np.polynomial.polynomial.polyval(t, weights.T)
qd = np.polynomial.polynomial.polyval(t, weights_d.T)
qdd = np.polynomial.polynomial.polyval(t, weights_dd.T)
yield q, qd, qdd
def reset(self, obs=None):
obs = super(PositionControl, self).reset(obs)
self.prev_pos = self._data.qpos[self.actuator_joint_ids]
self.prev_vel = self._data.qvel[self.actuator_joint_ids]
self.prev_acc = np.zeros(len(self.actuator_joint_ids))
self.i_error = np.zeros(len(self.actuator_joint_ids))
self.prev_controller_cmd_pos = self._data.qpos[self.actuator_joint_ids]
if self.debug:
self.controller_record = deque(maxlen=self.info.horizon * self._n_intermediate_steps)
return obs
def _step_init(self, obs, action):
super(PositionControl, self)._step_init(obs, action)
if self.n_agents == 1:
self.traj = self._create_traj(self.interp_order[0], action)
else:
def _traj():
traj_1 = self._create_traj(self.interp_order[0], action[0], 0)
traj_2 = self._create_traj(self.interp_order[1], action[1], 1)
for a1, a2 in zip(traj_1, traj_2):
yield np.hstack([a1, a2])
self.traj = _traj()
def _create_traj(self, interp_order, action, i=0):
if interp_order is None:
return iter(action)
return self._interpolate_trajectory(interp_order, action, i)
def _compute_action(self, obs, action):
cur_pos, cur_vel = self.get_joints(obs)
desired_pos, desired_vel, desired_acc = next(self.traj)
return self._controller(desired_pos, desired_vel, desired_acc, cur_pos, cur_vel)
def _preprocess_action(self, action):
action = super(PositionControl, self)._preprocess_action(action)
if self.n_agents == 1:
assert action.shape == self.action_shape[0], f"Unexpected action shape. Expected {self.action_shape[0]} but got" \
f" {action.shape}"
else:
for i in range(self.n_agents):
assert action[i].shape == self.action_shape[i], f"Unexpected action shape. Expected {self.action_shape[i]} but got" \
f" {action[i].shape}"
return action
class PositionControlIIWA(PositionControl):
def __init__(self, *args, **kwargs):
p_gain = [1500., 1500., 1200., 1200., 1000., 1000., 500.]
d_gain = [60, 80, 60, 30, 10, 1, 0.5]
i_gain = [0, 0, 0, 0, 0, 0, 0]
super(PositionControlIIWA, self).__init__(p_gain=p_gain, d_gain=d_gain, i_gain=i_gain, *args, **kwargs)
class PositionControlPlanar(PositionControl):
def __init__(self, *args, **kwargs):
p_gain = [960, 480, 240]
d_gain = [60, 20, 4]
i_gain = [0, 0, 0]
super(PositionControlPlanar, self).__init__(p_gain=p_gain, d_gain=d_gain, i_gain=i_gain, *args, **kwargs)
class PlanarPositionHit(PositionControlPlanar, three_dof.AirHockeyHit):
pass
class PlanarPositionDefend(PositionControlPlanar, three_dof.AirHockeyDefend):
pass
class IiwaPositionHit(PositionControlIIWA, seven_dof.AirHockeyHit):
pass
class IiwaPositionDefend(PositionControlIIWA, seven_dof.AirHockeyDefend):
pass
class IiwaPositionTournament(PositionControlIIWA, seven_dof.AirHockeyTournament):
pass
@@ -0,0 +1,4 @@
from .env_base import AirHockeyBase
from .tournament import AirHockeyTournament
from .hit import AirHockeyHit
from .defend import AirHockeyDefend
@@ -0,0 +1,75 @@
import numpy as np
from fancy_gym.envs.mujoco.air_hockey.seven_dof.env_single import AirHockeySingle
class AirHockeyDefend(AirHockeySingle):
"""
Class for the air hockey defending task.
The agent should stop the puck at the line x=-0.6.
"""
def __init__(self, gamma=0.99, horizon=500, viewer_params={}):
self.init_velocity_range = (1, 3)
self.start_range = np.array([[0.29, 0.65], [-0.4, 0.4]]) # Table Frame
self.init_ee_range = np.array([[0.60, 1.25], [-0.4, 0.4]]) # Robot Frame
super().__init__(gamma=gamma, horizon=horizon, viewer_params=viewer_params)
def setup(self, obs):
puck_pos = np.random.rand(2) * (self.start_range[:, 1] - self.start_range[:, 0]) + self.start_range[:, 0]
lin_vel = np.random.uniform(self.init_velocity_range[0], self.init_velocity_range[1])
angle = np.random.uniform(-0.5, 0.5)
puck_vel = np.zeros(3)
puck_vel[0] = -np.cos(angle) * lin_vel
puck_vel[1] = np.sin(angle) * lin_vel
puck_vel[2] = np.random.uniform(-10, 10)
self._write_data("puck_x_pos", puck_pos[0])
self._write_data("puck_y_pos", puck_pos[1])
self._write_data("puck_x_vel", puck_vel[0])
self._write_data("puck_y_vel", puck_vel[1])
self._write_data("puck_yaw_vel", puck_vel[2])
super(AirHockeyDefend, self).setup(obs)
def reward(self, state, action, next_state, absorbing):
return 0
def is_absorbing(self, state):
puck_pos, puck_vel = self.get_puck(state)
# If puck is over the middle line and moving towards opponent
if puck_pos[0] > 0 and puck_vel[0] > 0:
return True
if np.linalg.norm(puck_vel[:2]) < 0.1:
return True
return super().is_absorbing(state)
if __name__ == '__main__':
env = AirHockeyDefend()
R = 0.
J = 0.
gamma = 1.
steps = 0
env.reset()
env.render()
while True:
# action = np.random.uniform(-1, 1, env.info.action_space.low.shape) * 8
action = np.zeros(7)
observation, reward, done, info = env.step(action)
env.render()
print(observation)
gamma *= env.info.gamma
J += gamma * reward
R += reward
steps += 1
if done or steps > env.info.horizon:
print("J: ", J, " R: ", R)
R = 0.
J = 0.
gamma = 1.
steps = 0
env.reset()
@@ -0,0 +1,258 @@
import os
import mujoco
import numpy as np
from scipy.spatial.transform import Rotation as R
from fancy_gym.envs.mujoco.air_hockey.data.iiwas import __file__ as env_path
from fancy_gym.envs.mujoco.air_hockey.utils.universal_joint_plugin import UniversalJointPlugin
from mushroom_rl.environments.mujoco import MuJoCo, ObservationType
from mushroom_rl.utils.spaces import Box
"""
Abstract class for all AirHockey Environments.
"""
class AirHockeyBase(MuJoCo):
def __init__(self, gamma=0.99, horizon=500, timestep=1 / 1000., n_intermediate_steps=20, n_substeps=1,
n_agents=1, viewer_params={}):
"""
Constructor.
Args:
n_agents (int, 1): number of agent to be used in the environment (one or two)
"""
self.n_agents = n_agents
action_spec = []
observation_spec = [("puck_x_pos", "puck_x", ObservationType.JOINT_POS),
("puck_y_pos", "puck_y", ObservationType.JOINT_POS),
("puck_yaw_pos", "puck_yaw", ObservationType.JOINT_POS),
("puck_x_vel", "puck_x", ObservationType.JOINT_VEL),
("puck_y_vel", "puck_y", ObservationType.JOINT_VEL),
("puck_yaw_vel", "puck_yaw", ObservationType.JOINT_VEL)]
additional_data = [("puck_x_pos", "puck_x", ObservationType.JOINT_POS),
("puck_y_pos", "puck_y", ObservationType.JOINT_POS),
("puck_yaw_pos", "puck_yaw", ObservationType.JOINT_POS),
("puck_x_vel", "puck_x", ObservationType.JOINT_VEL),
("puck_y_vel", "puck_y", ObservationType.JOINT_VEL),
("puck_yaw_vel", "puck_yaw", ObservationType.JOINT_VEL)]
collision_spec = [("puck", ["puck"]),
("rim", ["rim_home_l", "rim_home_r", "rim_away_l", "rim_away_r", "rim_left", "rim_right"]),
("rim_short_sides", ["rim_home_l", "rim_home_r", "rim_away_l", "rim_away_r"])]
if 1 <= self.n_agents <= 2:
scene = os.path.join(os.path.dirname(os.path.abspath(env_path)), "single.xml")
action_spec += ["iiwa_1/joint_1", "iiwa_1/joint_2", "iiwa_1/joint_3", "iiwa_1/joint_4", "iiwa_1/joint_5",
"iiwa_1/joint_6", "iiwa_1/joint_7"]
observation_spec += [("robot_1/joint_1_pos", "iiwa_1/joint_1", ObservationType.JOINT_POS),
("robot_1/joint_2_pos", "iiwa_1/joint_2", ObservationType.JOINT_POS),
("robot_1/joint_3_pos", "iiwa_1/joint_3", ObservationType.JOINT_POS),
("robot_1/joint_4_pos", "iiwa_1/joint_4", ObservationType.JOINT_POS),
("robot_1/joint_5_pos", "iiwa_1/joint_5", ObservationType.JOINT_POS),
("robot_1/joint_6_pos", "iiwa_1/joint_6", ObservationType.JOINT_POS),
("robot_1/joint_7_pos", "iiwa_1/joint_7", ObservationType.JOINT_POS),
("robot_1/joint_1_vel", "iiwa_1/joint_1", ObservationType.JOINT_VEL),
("robot_1/joint_2_vel", "iiwa_1/joint_2", ObservationType.JOINT_VEL),
("robot_1/joint_3_vel", "iiwa_1/joint_3", ObservationType.JOINT_VEL),
("robot_1/joint_4_vel", "iiwa_1/joint_4", ObservationType.JOINT_VEL),
("robot_1/joint_5_vel", "iiwa_1/joint_5", ObservationType.JOINT_VEL),
("robot_1/joint_6_vel", "iiwa_1/joint_6", ObservationType.JOINT_VEL),
("robot_1/joint_7_vel", "iiwa_1/joint_7", ObservationType.JOINT_VEL)]
additional_data += [("robot_1/joint_8_pos", "iiwa_1/striker_joint_1", ObservationType.JOINT_POS),
("robot_1/joint_9_pos", "iiwa_1/striker_joint_2", ObservationType.JOINT_POS),
("robot_1/joint_8_vel", "iiwa_1/striker_joint_1", ObservationType.JOINT_VEL),
("robot_1/joint_9_vel", "iiwa_1/striker_joint_2", ObservationType.JOINT_VEL),
("robot_1/ee_pos", "iiwa_1/striker_mallet", ObservationType.BODY_POS),
("robot_1/ee_vel", "iiwa_1/striker_mallet", ObservationType.BODY_VEL),
("robot_1/rod_rot", "iiwa_1/striker_joint_link", ObservationType.BODY_ROT)]
collision_spec += [("robot_1/ee", ["iiwa_1/ee"])]
if self.n_agents == 2:
scene = os.path.join(os.path.dirname(os.path.abspath(env_path)), "double.xml")
observation_spec += [("robot_1/opponent_ee_pos", "iiwa_2/striker_joint_link", ObservationType.BODY_POS)]
action_spec += ["iiwa_2/joint_1", "iiwa_2/joint_2", "iiwa_2/joint_3", "iiwa_2/joint_4",
"iiwa_2/joint_5",
"iiwa_2/joint_6", "iiwa_2/joint_7"]
observation_spec += [("robot_2/puck_x_pos", "puck_x", ObservationType.JOINT_POS),
("robot_2/puck_y_pos", "puck_y", ObservationType.JOINT_POS),
("robot_2/puck_yaw_pos", "puck_yaw", ObservationType.JOINT_POS),
("robot_2/puck_x_vel", "puck_x", ObservationType.JOINT_VEL),
("robot_2/puck_y_vel", "puck_y", ObservationType.JOINT_VEL),
("robot_2/puck_yaw_vel", "puck_yaw", ObservationType.JOINT_VEL),
("robot_2/joint_1_pos", "iiwa_2/joint_1", ObservationType.JOINT_POS),
("robot_2/joint_2_pos", "iiwa_2/joint_2", ObservationType.JOINT_POS),
("robot_2/joint_3_pos", "iiwa_2/joint_3", ObservationType.JOINT_POS),
("robot_2/joint_4_pos", "iiwa_2/joint_4", ObservationType.JOINT_POS),
("robot_2/joint_5_pos", "iiwa_2/joint_5", ObservationType.JOINT_POS),
("robot_2/joint_6_pos", "iiwa_2/joint_6", ObservationType.JOINT_POS),
("robot_2/joint_7_pos", "iiwa_2/joint_7", ObservationType.JOINT_POS),
("robot_2/joint_1_vel", "iiwa_2/joint_1", ObservationType.JOINT_VEL),
("robot_2/joint_2_vel", "iiwa_2/joint_2", ObservationType.JOINT_VEL),
("robot_2/joint_3_vel", "iiwa_2/joint_3", ObservationType.JOINT_VEL),
("robot_2/joint_4_vel", "iiwa_2/joint_4", ObservationType.JOINT_VEL),
("robot_2/joint_5_vel", "iiwa_2/joint_5", ObservationType.JOINT_VEL),
("robot_2/joint_6_vel", "iiwa_2/joint_6", ObservationType.JOINT_VEL),
("robot_2/joint_7_vel", "iiwa_2/joint_7", ObservationType.JOINT_VEL)]
observation_spec += [("robot_2/opponent_ee_pos", "iiwa_1/striker_joint_link", ObservationType.BODY_POS)]
additional_data += [("robot_2/joint_8_pos", "iiwa_2/striker_joint_1", ObservationType.JOINT_POS),
("robot_2/joint_9_pos", "iiwa_2/striker_joint_2", ObservationType.JOINT_POS),
("robot_2/joint_8_vel", "iiwa_2/striker_joint_1", ObservationType.JOINT_VEL),
("robot_2/joint_9_vel", "iiwa_2/striker_joint_2", ObservationType.JOINT_VEL),
("robot_2/ee_pos", "iiwa_2/striker_mallet", ObservationType.BODY_POS),
("robot_2/ee_vel", "iiwa_2/striker_mallet", ObservationType.BODY_VEL),
("robot_2/rod_rot", "iiwa_2/striker_joint_link", ObservationType.BODY_ROT)]
collision_spec += [("robot_2/ee", ["iiwa_2/ee"])]
else:
raise ValueError('n_agents should be 1 or 2')
self.env_info = dict()
self.env_info['table'] = {"length": 1.948, "width": 1.038, "goal_width": 0.25}
self.env_info['puck'] = {"radius": 0.03165}
self.env_info['mallet'] = {"radius": 0.04815}
self.env_info['n_agents'] = self.n_agents
self.env_info['robot'] = {
"n_joints": 7,
"ee_desired_height": 0.1645,
"joint_vel_limit": np.array([[-85, -85, -100, -75, -130, -135, -135],
[85, 85, 100, 75, 130, 135, 135]]) / 180. * np.pi,
"joint_acc_limit": np.array([[-85, -85, -100, -75, -130, -135, -135],
[85, 85, 100, 75, 130, 135, 135]]) / 180. * np.pi * 10,
"base_frame": [],
"universal_height": 0.0645,
"control_frequency": 50,
}
self.env_info['puck_pos_ids'] = [0, 1, 2]
self.env_info['puck_vel_ids'] = [3, 4, 5]
self.env_info['joint_pos_ids'] = [6, 7, 8, 9, 10, 11, 12]
self.env_info['joint_vel_ids'] = [13, 14, 15, 16, 17, 18, 19]
if self.n_agents == 2:
self.env_info['opponent_ee_ids'] = [20, 21, 22]
else:
self.env_info['opponent_ee_ids'] = []
max_joint_vel = ([np.inf] * 3 + list(self.env_info["robot"]["joint_vel_limit"][1, :7])) * self.n_agents
super().__init__(scene, action_spec, observation_spec, gamma, horizon, timestep, n_substeps,
n_intermediate_steps, additional_data, collision_spec, max_joint_vel, **viewer_params)
# Construct the mujoco model at origin
robot_model = mujoco.MjModel.from_xml_path(
os.path.join(os.path.dirname(os.path.abspath(env_path)), "iiwa_only.xml"))
robot_model.body('iiwa_1/base').pos = np.zeros(3)
robot_data = mujoco.MjData(robot_model)
# Add env_info that requires mujoco models
self.env_info['dt'] = self.dt
self.env_info["robot"]["joint_pos_limit"] = np.array(
[self._model.joint(f"iiwa_1/joint_{i + 1}").range for i in range(7)]).T
self.env_info["robot"]["robot_model"] = robot_model
self.env_info["robot"]["robot_data"] = robot_data
self.env_info["rl_info"] = self.info
frame_T = np.eye(4)
temp = np.zeros((9, 1))
mujoco.mju_quat2Mat(temp, self._model.body("iiwa_1/base").quat)
frame_T[:3, :3] = temp.reshape(3, 3)
frame_T[:3, 3] = self._model.body("iiwa_1/base").pos
self.env_info['robot']['base_frame'].append(frame_T.copy())
if self.n_agents == 2:
mujoco.mju_quat2Mat(temp, self._model.body("iiwa_2/base").quat)
frame_T[:3, :3] = temp.reshape(3, 3)
frame_T[:3, 3] = self._model.body("iiwa_2/base").pos
self.env_info['robot']['base_frame'].append(frame_T.copy())
# Ids of the joint, which are controller by the action space
self.actuator_joint_ids = [self._model.joint(name).id for name in action_spec]
self.universal_joint_plugin = UniversalJointPlugin(self._model, self._data, self.env_info)
def _modify_mdp_info(self, mdp_info):
obs_low = np.array([0, -1, -np.pi, -20., -20., -100,
*np.array([self._model.joint(f"iiwa_1/joint_{i + 1}").range[0]
for i in range(self.env_info['robot']['n_joints'])]),
*self.env_info['robot']['joint_vel_limit'][0]])
obs_high = np.array([3.02, 1, np.pi, 20., 20., 100,
*np.array([self._model.joint(f"iiwa_1/joint_{i + 1}").range[1]
for i in range(self.env_info['robot']['n_joints'])]),
*self.env_info['robot']['joint_vel_limit'][1]])
if self.n_agents == 2:
obs_low = np.concatenate([obs_low, [1.5, -1.5, -1.5]])
obs_high = np.concatenate([obs_high, [4.5, 1.5, 1.5]])
mdp_info.observation_space = Box(obs_low, obs_high)
return mdp_info
def _simulation_pre_step(self):
self.universal_joint_plugin.update()
def is_absorbing(self, obs):
boundary = np.array([self.env_info['table']['length'], self.env_info['table']['width']]) / 2
puck_pos, puck_vel = self.get_puck(obs)
if np.any(np.abs(puck_pos[:2]) > boundary) or np.linalg.norm(puck_vel) > 100:
return True
return False
@staticmethod
def _puck_2d_in_robot_frame(puck_in, robot_frame, type='pose'):
if type == 'pose':
puck_w = np.eye(4)
puck_w[:2, 3] = puck_in[:2]
puck_w[:3, :3] = R.from_euler("xyz", [0., 0., puck_in[2]]).as_matrix()
puck_r = np.linalg.inv(robot_frame) @ puck_w
puck_out = np.concatenate([puck_r[:2, 3],
R.from_matrix(puck_r[:3, :3]).as_euler('xyz')[2:3]])
if type == 'vel':
rot_mat = robot_frame[:3, :3]
vel_lin = np.array([*puck_in[:2], 0])
vel_ang = np.array([0., 0., puck_in[2]])
vel_lin_r = rot_mat.T @ vel_lin
vel_ang_r = rot_mat.T @ vel_ang
puck_out = np.concatenate([vel_lin_r[:2], vel_ang_r[2:3]])
return puck_out
def get_puck(self, obs):
"""
Getting the puck properties from the observations
Args:
obs: The current observation
Returns:
([pos_x, pos_y, yaw], [lin_vel_x, lin_vel_y, yaw_vel])
"""
puck_pos = np.concatenate([self.obs_helper.get_from_obs(obs, "puck_x_pos"),
self.obs_helper.get_from_obs(obs, "puck_y_pos"),
self.obs_helper.get_from_obs(obs, "puck_yaw_pos")])
puck_vel = np.concatenate([self.obs_helper.get_from_obs(obs, "puck_x_vel"),
self.obs_helper.get_from_obs(obs, "puck_y_vel"),
self.obs_helper.get_from_obs(obs, "puck_yaw_vel")])
return puck_pos, puck_vel
def get_ee(self):
raise NotImplementedError
def get_joints(self, obs):
raise NotImplementedError
@@ -0,0 +1,171 @@
import mujoco
import numpy as np
from scipy.spatial.transform import Rotation as R
from fancy_gym.envs.mujoco.air_hockey.seven_dof.env_base import AirHockeyBase
from fancy_gym.envs.mujoco.air_hockey.utils import inverse_kinematics
class AirHockeyDouble(AirHockeyBase):
"""
Base class for two agents air hockey tasks.
"""
def __init__(self, gamma=0.99, horizon=500, viewer_params={}):
super().__init__(gamma=gamma, horizon=horizon, n_agents=2, viewer_params=viewer_params)
self._compute_init_state()
self.filter_ratio = 0.274
self.q_pos_prev = np.zeros(self.env_info["robot"]["n_joints"] * self.env_info["n_agents"])
self.q_vel_prev = np.zeros(self.env_info["robot"]["n_joints"] * self.env_info["n_agents"])
def _compute_init_state(self):
init_state = np.array([0., -0.1961, 0., -1.8436, 0., 0.9704, 0.])
success, self.init_state = inverse_kinematics(self.env_info['robot']['robot_model'],
self.env_info['robot']['robot_data'],
np.array([0.65, 0., 0.1645]),
R.from_euler('xyz', [0, 5 / 6 * np.pi, 0]).as_matrix(),
initial_q=init_state)
assert success is True
def get_ee(self, robot=1):
"""
Getting the ee properties from the current internal state the selected robot. Can also be obtained via forward kinematics
on the current joint position, this function exists to avoid redundant computations.
Args:
robot: ID of robot, either 1 or 2
Returns: ([pos_x, pos_y, pos_z], [ang_vel_x, ang_vel_y, ang_vel_z, lin_vel_x, lin_vel_y, lin_vel_z])
"""
ee_pos = self._read_data("robot_" + str(robot) + "/ee_pos")
ee_vel = self._read_data("robot_" + str(robot) + "/ee_vel")
return ee_pos, ee_vel
def get_joints(self, obs, agent=None):
"""
Get joint position and velocity of the robots
Can choose the robot with agent = 1 / 2. If agent is None both are returned
"""
if agent:
q_pos = np.zeros(7)
q_vel = np.zeros(7)
for i in range(7):
q_pos[i] = self.obs_helper.get_from_obs(obs, "robot_" + str(agent) + "/joint_" + str(i + 1) + "_pos")[0]
q_vel[i] = self.obs_helper.get_from_obs(obs, "robot_" + str(agent) + "/joint_" + str(i + 1) + "_vel")[0]
else:
q_pos = np.zeros(14)
q_vel = np.zeros(14)
for i in range(7):
q_pos[i] = self.obs_helper.get_from_obs(obs, "robot_1/joint_" + str(i + 1) + "_pos")[0]
q_vel[i] = self.obs_helper.get_from_obs(obs, "robot_1/joint_" + str(i + 1) + "_vel")[0]
q_pos[i + 7] = self.obs_helper.get_from_obs(obs, "robot_2/joint_" + str(i + 1) + "_pos")[0]
q_vel[i + 7] = self.obs_helper.get_from_obs(obs, "robot_2/joint_" + str(i + 1) + "_vel")[0]
return q_pos, q_vel
def _create_observation(self, obs):
# Filter the joint velocity
q_pos, q_vel = self.get_joints(obs)
q_vel_filter = self.filter_ratio * q_vel + (1 - self.filter_ratio) * self.q_vel_prev
self.q_pos_prev = q_pos
self.q_vel_prev = q_vel_filter
for i in range(7):
self.obs_helper.get_from_obs(obs, "robot_1/joint_" + str(i + 1) + "_vel")[:] = q_vel_filter[i]
self.obs_helper.get_from_obs(obs, "robot_2/joint_" + str(i + 1) + "_vel")[:] = q_vel_filter[i + 7]
# Wrap puck's rotation angle to [-pi, pi)
yaw_angle = self.obs_helper.get_from_obs(obs, "puck_yaw_pos")
self.obs_helper.get_from_obs(obs, "puck_yaw_pos")[:] = (yaw_angle + np.pi) % (2 * np.pi) - np.pi
return obs
def _modify_observation(self, obs):
new_obs = obs.copy()
puck_pos, puck_vel = self.get_puck(new_obs)
puck_pos_1 = self._puck_2d_in_robot_frame(puck_pos, self.env_info['robot']['base_frame'][0])
puck_vel_1 = self._puck_2d_in_robot_frame(puck_vel, self.env_info['robot']['base_frame'][0], type='vel')
self.obs_helper.get_from_obs(new_obs, "puck_x_pos")[:] = puck_pos_1[0]
self.obs_helper.get_from_obs(new_obs, "puck_y_pos")[:] = puck_pos_1[1]
self.obs_helper.get_from_obs(new_obs, "puck_yaw_pos")[:] = puck_pos_1[2]
self.obs_helper.get_from_obs(new_obs, "puck_x_vel")[:] = puck_vel_1[0]
self.obs_helper.get_from_obs(new_obs, "puck_y_vel")[:] = puck_vel_1[1]
self.obs_helper.get_from_obs(new_obs, "puck_yaw_vel")[:] = puck_vel_1[2]
opponent_pos_1 = self.obs_helper.get_from_obs(new_obs, 'robot_1/opponent_ee_pos')
self.obs_helper.get_from_obs(new_obs, 'robot_1/opponent_ee_pos')[:] = \
(np.linalg.inv(self.env_info['robot']['base_frame'][0]) @ np.concatenate([opponent_pos_1, [1]]))[:3]
puck_pos_2 = self._puck_2d_in_robot_frame(puck_pos, self.env_info['robot']['base_frame'][1])
puck_vel_2 = self._puck_2d_in_robot_frame(puck_vel, self.env_info['robot']['base_frame'][1], type='vel')
self.obs_helper.get_from_obs(new_obs, "robot_2/puck_x_pos")[:] = puck_pos_2[0]
self.obs_helper.get_from_obs(new_obs, "robot_2/puck_y_pos")[:] = puck_pos_2[1]
self.obs_helper.get_from_obs(new_obs, "robot_2/puck_yaw_pos")[:] = puck_pos_2[2]
self.obs_helper.get_from_obs(new_obs, "robot_2/puck_x_vel")[:] = puck_vel_2[0]
self.obs_helper.get_from_obs(new_obs, "robot_2/puck_y_vel")[:] = puck_vel_2[1]
self.obs_helper.get_from_obs(new_obs, "robot_2/puck_yaw_vel")[:] = puck_vel_2[2]
opponent_pos_2 = self.obs_helper.get_from_obs(new_obs, 'robot_2/opponent_ee_pos')
self.obs_helper.get_from_obs(new_obs, 'robot_2/opponent_ee_pos')[:] = \
(np.linalg.inv(self.env_info['robot']['base_frame'][1]) @ np.concatenate([opponent_pos_2, [1]]))[:3]
return new_obs
def setup(self, obs):
for i in range(7):
self._data.joint("iiwa_1/joint_" + str(i + 1)).qpos = self.init_state[i]
self._data.joint("iiwa_2/joint_" + str(i + 1)).qpos = self.init_state[i]
self.q_pos_prev[i] = self.init_state[i]
self.q_pos_prev[i + 7] = self.init_state[i]
self.q_vel_prev[i] = self._data.joint("iiwa_1/joint_" + str(i + 1)).qvel[0]
self.q_vel_prev[i + 7] = self._data.joint("iiwa_2/joint_" + str(i + 1)).qvel[0]
self.universal_joint_plugin.reset()
super().setup(obs)
# Update body positions, needed for _compute_universal_joint
mujoco.mj_fwdPosition(self._model, self._data)
def reward(self, state, action, next_state, absorbing):
return 0
def main():
env = AirHockeyDouble(viewer_params={'start_paused': True})
env.reset()
env.render()
R = 0.
J = 0.
gamma = 1.
steps = 0
while True:
action = np.zeros(14)
observation, reward, done, info = env.step(action)
env.render()
gamma *= env.info.gamma
J += gamma * reward
R += reward
steps += 1
if done or steps > env.info.horizon:
print("J: ", J, " R: ", R)
R = 0.
J = 0.
gamma = 1.
steps = 0
env.reset()
if __name__ == '__main__':
main()
@@ -0,0 +1,104 @@
import mujoco
import numpy as np
from scipy.spatial.transform import Rotation as R
from fancy_gym.envs.mujoco.air_hockey.seven_dof.env_base import AirHockeyBase
from fancy_gym.envs.mujoco.air_hockey.utils import inverse_kinematics
class AirHockeySingle(AirHockeyBase):
"""
Base class for single agent air hockey tasks.
"""
def __init__(self, gamma=0.99, horizon=500, viewer_params={}):
super().__init__(gamma=gamma, horizon=horizon, n_agents=1, viewer_params=viewer_params)
self._compute_init_state()
self.filter_ratio = 0.274
self.q_pos_prev = np.zeros(self.env_info["robot"]["n_joints"])
self.q_vel_prev = np.zeros(self.env_info["robot"]["n_joints"])
def _compute_init_state(self):
init_state = np.array([0., -0.1961, 0., -1.8436, 0., 0.9704, 0.])
success, self.init_state = inverse_kinematics(self.env_info['robot']['robot_model'],
self.env_info['robot']['robot_data'],
np.array([0.65, 0., 0.1645]),
R.from_euler('xyz', [0, 5 / 6 * np.pi, 0]).as_matrix(),
initial_q=init_state)
assert success is True
def get_ee(self):
"""
Getting the ee properties from the current internal state. Can also be obtained via forward kinematics
on the current joint position, this function exists to avoid redundant computations.
Returns:
([pos_x, pos_y, pos_z], [ang_vel_x, ang_vel_y, ang_vel_z, lin_vel_x, lin_vel_y, lin_vel_z])
"""
ee_pos = self._read_data("robot_1/ee_pos")
ee_vel = self._read_data("robot_1/ee_vel")
return ee_pos, ee_vel
def get_joints(self, obs):
"""
Get joint position and velocity of the robot
"""
q_pos = np.zeros(7)
q_vel = np.zeros(7)
for i in range(7):
q_pos[i] = self.obs_helper.get_from_obs(obs, "robot_1/joint_" + str(i + 1) + "_pos")[0]
q_vel[i] = self.obs_helper.get_from_obs(obs, "robot_1/joint_" + str(i + 1) + "_vel")[0]
return q_pos, q_vel
def _create_observation(self, obs):
# Filter the joint velocity
q_pos, q_vel = self.get_joints(obs)
q_vel_filter = self.filter_ratio * q_vel + (1 - self.filter_ratio) * self.q_vel_prev
self.q_pos_prev = q_pos
self.q_vel_prev = q_vel_filter
for i in range(7):
self.obs_helper.get_from_obs(obs, "robot_1/joint_" + str(i + 1) + "_vel")[:] = q_vel_filter[i]
yaw_angle = self.obs_helper.get_from_obs(obs, "puck_yaw_pos")
self.obs_helper.get_from_obs(obs, "puck_yaw_pos")[:] = (yaw_angle + np.pi) % (2 * np.pi) - np.pi
return obs
def _modify_observation(self, obs):
new_obs = obs.copy()
puck_pos, puck_vel = self.get_puck(new_obs)
puck_pos = self._puck_2d_in_robot_frame(puck_pos, self.env_info['robot']['base_frame'][0])
puck_vel = self._puck_2d_in_robot_frame(puck_vel, self.env_info['robot']['base_frame'][0], type='vel')
self.obs_helper.get_from_obs(new_obs, "puck_x_pos")[:] = puck_pos[0]
self.obs_helper.get_from_obs(new_obs, "puck_y_pos")[:] = puck_pos[1]
self.obs_helper.get_from_obs(new_obs, "puck_yaw_pos")[:] = puck_pos[2]
self.obs_helper.get_from_obs(new_obs, "puck_x_vel")[:] = puck_vel[0]
self.obs_helper.get_from_obs(new_obs, "puck_y_vel")[:] = puck_vel[1]
self.obs_helper.get_from_obs(new_obs, "puck_yaw_vel")[:] = puck_vel[2]
return new_obs
def setup(self, obs):
for i in range(7):
self._data.joint("iiwa_1/joint_" + str(i + 1)).qpos = self.init_state[i]
self.q_pos_prev[i] = self.init_state[i]
self.q_vel_prev[i] = self._data.joint("iiwa_1/joint_" + str(i + 1)).qvel[0]
self.universal_joint_plugin.reset()
super().setup(obs)
# Update body positions, needed for _compute_universal_joint
mujoco.mj_fwdPosition(self._model, self._data)
@@ -0,0 +1,74 @@
import numpy as np
from fancy_gym.envs.mujoco.air_hockey.seven_dof.env_single import AirHockeySingle
class AirHockeyHit(AirHockeySingle):
"""
Class for the air hockey hitting task.
"""
def __init__(self, gamma=0.99, horizon=500, moving_init=True, viewer_params={}):
"""
Constructor
Args:
opponent_agent(Agent, None): Agent which controls the opponent
moving_init(bool, False): If true, initialize the puck with inital velocity.
"""
self.hit_range = np.array([[-0.65, -0.25], [-0.4, 0.4]]) # Table Frame
self.init_velocity_range = (0, 0.5) # Table Frame
super().__init__(gamma=gamma, horizon=horizon, viewer_params=viewer_params)
self.moving_init = moving_init
hit_width = self.env_info['table']['width'] / 2 - self.env_info['puck']['radius'] - \
self.env_info['mallet']['radius'] * 2
self.hit_range = np.array([[-0.7, -0.2], [-hit_width, hit_width]]) # Table Frame
self.init_velocity_range = (0, 0.5) # Table Frame
self.init_ee_range = np.array([[0.60, 1.25], [-0.4, 0.4]]) # Robot Frame
def setup(self, obs):
# Initial position of the puck
puck_pos = np.random.rand(2) * (self.hit_range[:, 1] - self.hit_range[:, 0]) + self.hit_range[:, 0]
self._write_data("puck_x_pos", puck_pos[0])
self._write_data("puck_y_pos", puck_pos[1])
if self.moving_init:
lin_vel = np.random.uniform(self.init_velocity_range[0], self.init_velocity_range[1])
angle = np.random.uniform(-np.pi / 2 - 0.1, np.pi / 2 + 0.1)
puck_vel = np.zeros(3)
puck_vel[0] = -np.cos(angle) * lin_vel
puck_vel[1] = np.sin(angle) * lin_vel
puck_vel[2] = np.random.uniform(-2, 2)
self._write_data("puck_x_vel", puck_vel[0])
self._write_data("puck_y_vel", puck_vel[1])
self._write_data("puck_yaw_vel", puck_vel[2])
super(AirHockeyHit, self).setup(obs)
def reward(self, state, action, next_state, absorbing):
return 0
def is_absorbing(self, obs):
puck_pos, puck_vel = self.get_puck(obs)
# Stop if the puck bounces back on the opponents wall
if puck_pos[0] > 0 and puck_vel[0] < 0:
return True
return super(AirHockeyHit, self).is_absorbing(obs)
if __name__ == '__main__':
env = AirHockeyHit(moving_init=True)
env.reset()
steps = 0
while True:
action = np.zeros(7)
observation, reward, done, info = env.step(action)
env.render()
if done or steps > env.info.horizon:
steps = 0
env.reset()
@@ -0,0 +1,111 @@
import mujoco
import numpy as np
from fancy_gym.envs.mujoco.air_hockey.seven_dof.env_double import AirHockeyDouble
class AirHockeyTournament(AirHockeyDouble):
"""
Class for the air hockey tournament. Consists of 2 robots which should play against each other.
When the puck is on one side for more than 15 seconds the puck is reset and the player gets a penalty.
If a player accumulates 3 penalties his score is reduced by 1.
"""
def __init__(self, gamma=0.99, horizon=15000, viewer_params={}, agent_name="Agent", opponent_name="Opponent"):
self.agent_name = agent_name
self.opponent_name = opponent_name
self.score = [0, 0]
self.faults = [0, 0]
self.start_side = None
self.timer = 0
def custom_render_callback(viewport, context):
names = f"Agents \nScores \nFaults "
data = f"{self.agent_name} - {self.opponent_name}\n "
data += f"{self.score[0]} - {self.score[1]}\n "
data += f"{self.faults[0]} - {self.faults[1]}"
mujoco.mjr_overlay(mujoco.mjtFont.mjFONT_BIG, mujoco.mjtGridPos.mjGRID_TOPLEFT, viewport, names, data, context)
viewer_params["custom_render_callback"] = custom_render_callback
super().__init__(gamma=gamma, horizon=horizon, viewer_params=viewer_params)
hit_width = self.env_info['table']['width'] / 2 - self.env_info['puck']['radius'] - \
self.env_info['mallet']['radius'] * 2
self.hit_range = np.array([[-0.7, -0.2], [-hit_width, hit_width]]) # Table Frame
def setup(self, obs):
if self.start_side == None:
self.start_side = np.random.choice([1, -1])
self.prev_side = self.start_side
# Initial position of the puck
puck_pos = np.random.rand(2) * (self.hit_range[:, 1] - self.hit_range[:, 0]) + self.hit_range[:, 0]
self._write_data("puck_x_pos", puck_pos[0] * self.start_side)
self._write_data("puck_y_pos", puck_pos[1])
self.prev_side = self.start_side
self.timer = 0
super(AirHockeyTournament, self).setup(obs)
def reward(self, state, action, next_state, absorbing):
return 0
def is_absorbing(self, obs):
puck_pos, puck_vel = self.get_puck(obs)
# Puck stuck on one side for more than 15s
if np.sign(puck_pos[0]) == self.prev_side:
self.timer += self.dt
else:
self.prev_side *= -1
self.timer = 0
if self.timer > 15.0 and np.abs(puck_pos[0]) >= 0.15:
if self.prev_side == -1:
self.faults[0] += 1
self.start_side = -1
if self.faults[0] % 3 == 0:
self.score[1] += 1
else:
self.faults[1] += 1
self.start_side = 1
if self.faults[1] % 3 == 0:
self.score[0] += 1
return True
# Puck in Goal
if (np.abs(puck_pos[1]) - self.env_info['table']['goal_width'] / 2) <= 0:
if puck_pos[0] > self.env_info['table']['length'] / 2:
self.score[0] += 1
self.start_side = -1
return True
if puck_pos[0] < -self.env_info['table']['length'] / 2:
self.score[1] += 1
self.start_side = 1
return True
# Puck stuck in the middle
if np.abs(puck_pos[0]) < 0.15 and np.linalg.norm(puck_vel[0]) < 0.025:
return True
return super(AirHockeyTournament, self).is_absorbing(obs)
if __name__ == '__main__':
env = AirHockeyTournament()
env.reset()
steps = 0
while True:
action = np.zeros(14)
steps += 1
observation, reward, done, info = env.step(action)
env.render()
if done or steps > env.info.horizon:
steps = 0
env.reset()
@@ -0,0 +1,3 @@
from .env_base import AirHockeyBase
from .defend import AirHockeyDefend
from .hit import AirHockeyHit
@@ -0,0 +1,76 @@
import numpy as np
from fancy_gym.envs.mujoco.air_hockey.three_dof.env_single import AirHockeySingle
class AirHockeyDefend(AirHockeySingle):
"""
Class for the air hockey defending task.
The agent should stop the puck at the line x=-0.6.
"""
def __init__(self, gamma=0.99, horizon=500, viewer_params={}):
self.init_velocity_range = (1, 3)
self.start_range = np.array([[0.29, 0.65], [-0.4, 0.4]]) # Table Frame
self.init_ee_range = np.array([[0.60, 1.25], [-0.4, 0.4]]) # Robot Frame
super().__init__(gamma=gamma, horizon=horizon, viewer_params=viewer_params)
def setup(self, state=None):
# Set initial puck parameters
puck_pos = np.random.rand(2) * (self.start_range[:, 1] - self.start_range[:, 0]) + self.start_range[:, 0]
lin_vel = np.random.uniform(self.init_velocity_range[0], self.init_velocity_range[1])
angle = np.random.uniform(-0.5, 0.5)
puck_vel = np.zeros(3)
puck_vel[0] = -np.cos(angle) * lin_vel
puck_vel[1] = np.sin(angle) * lin_vel
puck_vel[2] = np.random.uniform(-10, 10)
self._write_data("puck_x_pos", puck_pos[0])
self._write_data("puck_y_pos", puck_pos[1])
self._write_data("puck_x_vel", puck_vel[0])
self._write_data("puck_y_vel", puck_vel[1])
self._write_data("puck_yaw_vel", puck_vel[2])
super(AirHockeyDefend, self).setup(state)
def reward(self, state, action, next_state, absorbing):
return 0
def is_absorbing(self, state):
puck_pos, puck_vel = self.get_puck(state)
# If puck is over the middle line and moving towards opponent
if puck_pos[0] > 0 and puck_vel[0] > 0:
return True
if np.linalg.norm(puck_vel[:2]) < 0.1:
return True
return super().is_absorbing(state)
if __name__ == '__main__':
env = AirHockeyDefend()
R = 0.
J = 0.
gamma = 1.
steps = 0
env.reset()
while True:
action = np.zeros(3)
observation, reward, done, info = env.step(action)
env.render()
gamma *= env.info.gamma
J += gamma * reward
R += reward
steps += 1
if done or steps > env.info.horizon:
print("J: ", J, " R: ", R)
R = 0.
J = 0.
gamma = 1.
steps = 0
env.reset()
@@ -0,0 +1,224 @@
import os
import mujoco
import numpy as np
from scipy.spatial.transform import Rotation as R
from fancy_gym.envs.mujoco.air_hockey.data.planar import __file__ as env_path
from mushroom_rl.environments.mujoco import MuJoCo, ObservationType
from mushroom_rl.utils.spaces import Box
class AirHockeyBase(MuJoCo):
"""
Abstract class for all AirHockey Environments.
"""
def __init__(self, gamma=0.99, horizon=500, timestep=1 / 1000., n_intermediate_steps=20, n_substeps=1,
n_agents=1, viewer_params={}):
"""
Constructor.
Args:
n_agents (int, 1): number of agent to be used in the environment (one or two)
"""
self.n_agents = n_agents
action_spec = []
observation_spec = [("puck_x_pos", "puck_x", ObservationType.JOINT_POS),
("puck_y_pos", "puck_y", ObservationType.JOINT_POS),
("puck_yaw_pos", "puck_yaw", ObservationType.JOINT_POS),
("puck_x_vel", "puck_x", ObservationType.JOINT_VEL),
("puck_y_vel", "puck_y", ObservationType.JOINT_VEL),
("puck_yaw_vel", "puck_yaw", ObservationType.JOINT_VEL)]
additional_data = [("puck_x_pos", "puck_x", ObservationType.JOINT_POS),
("puck_y_pos", "puck_y", ObservationType.JOINT_POS),
("puck_yaw_pos", "puck_yaw", ObservationType.JOINT_POS),
("puck_x_vel", "puck_x", ObservationType.JOINT_VEL),
("puck_y_vel", "puck_y", ObservationType.JOINT_VEL),
("puck_yaw_vel", "puck_yaw", ObservationType.JOINT_VEL)]
collision_spec = [("puck", ["puck"]),
("rim", ["rim_home_l", "rim_home_r", "rim_away_l", "rim_away_r", "rim_left", "rim_right"]),
("rim_short_sides", ["rim_home_l", "rim_home_r", "rim_away_l", "rim_away_r"])]
if 1 <= self.n_agents <= 2:
scene = os.path.join(os.path.dirname(os.path.abspath(env_path)), "single.xml")
action_spec += ["planar_robot_1/joint_1", "planar_robot_1/joint_2", "planar_robot_1/joint_3"]
observation_spec += [("robot_1/joint_1_pos", "planar_robot_1/joint_1", ObservationType.JOINT_POS),
("robot_1/joint_2_pos", "planar_robot_1/joint_2", ObservationType.JOINT_POS),
("robot_1/joint_3_pos", "planar_robot_1/joint_3", ObservationType.JOINT_POS),
("robot_1/joint_1_vel", "planar_robot_1/joint_1", ObservationType.JOINT_VEL),
("robot_1/joint_2_vel", "planar_robot_1/joint_2", ObservationType.JOINT_VEL),
("robot_1/joint_3_vel", "planar_robot_1/joint_3", ObservationType.JOINT_VEL)]
additional_data += [("robot_1/ee_pos", "planar_robot_1/body_ee", ObservationType.BODY_POS),
("robot_1/ee_vel", "planar_robot_1/body_ee", ObservationType.BODY_VEL)]
collision_spec += [("robot_1/ee", ["planar_robot_1/ee"])]
if self.n_agents == 2:
scene = os.path.join(os.path.dirname(os.path.abspath(env_path)), "double.xml")
observation_spec += [("robot_1/opponent_ee_pos", "planar_robot_2/body_ee", ObservationType.BODY_POS)]
action_spec += ["planar_robot_2/joint_1", "planar_robot_2/joint_2", "planar_robot_2/joint_3"]
# Add puck pos/vel again to transform into second agents frame
observation_spec += [("robot_2/puck_x_pos", "puck_x", ObservationType.JOINT_POS),
("robot_2/puck_y_pos", "puck_y", ObservationType.JOINT_POS),
("robot_2/puck_yaw_pos", "puck_yaw", ObservationType.JOINT_POS),
("robot_2/puck_x_vel", "puck_x", ObservationType.JOINT_VEL),
("robot_2/puck_y_vel", "puck_y", ObservationType.JOINT_VEL),
("robot_2/puck_yaw_vel", "puck_yaw", ObservationType.JOINT_VEL),
("robot_2/joint_1_pos", "planar_robot_2/joint_1", ObservationType.JOINT_POS),
("robot_2/joint_2_pos", "planar_robot_2/joint_2", ObservationType.JOINT_POS),
("robot_2/joint_3_pos", "planar_robot_2/joint_3", ObservationType.JOINT_POS),
("robot_2/joint_1_vel", "planar_robot_2/joint_1", ObservationType.JOINT_VEL),
("robot_2/joint_2_vel", "planar_robot_2/joint_2", ObservationType.JOINT_VEL),
("robot_2/joint_3_vel", "planar_robot_2/joint_3", ObservationType.JOINT_VEL)]
observation_spec += [("robot_2/opponent_ee_pos", "planar_robot_1/body_ee", ObservationType.BODY_POS)]
additional_data += [("robot_2/ee_pos", "planar_robot_2/body_ee", ObservationType.BODY_POS),
("robot_2/ee_vel", "planar_robot_2/body_ee", ObservationType.BODY_VEL)]
collision_spec += [("robot_2/ee", ["planar_robot_2/ee"])]
else:
raise ValueError('n_agents should be 1 or 2')
self.env_info = dict()
self.env_info['table'] = {"length": 1.948, "width": 1.038, "goal_width": 0.25}
self.env_info['puck'] = {"radius": 0.03165}
self.env_info['mallet'] = {"radius": 0.04815}
self.env_info['n_agents'] = self.n_agents
self.env_info['robot'] = {
"n_joints": 3,
"ee_desired_height": 0.1,
"joint_vel_limit": np.array([[-np.pi / 2, -np.pi / 2, -np.pi * 2 / 3],
[np.pi / 2, np.pi / 2, np.pi * 2 / 3]]),
"joint_acc_limit": np.array([[-2 * np.pi, -2 * np.pi, -2 * 4 / 3 * np.pi],
[2 * np.pi, 2 * np.pi, 2 * 4 / 3 * np.pi]]),
"base_frame": [],
"control_frequency": 50,
}
self.env_info['puck_pos_ids'] = [0, 1, 2]
self.env_info['puck_vel_ids'] = [3, 4, 5]
self.env_info['joint_pos_ids'] = [6, 7, 8]
self.env_info['joint_vel_ids'] = [9, 10, 11]
if self.n_agents == 2:
self.env_info['opponent_ee_ids'] = [13, 14, 15]
else:
self.env_info['opponent_ee_ids'] = []
max_joint_vel = ([np.inf] * 3 + list(self.env_info["robot"]["joint_vel_limit"][1, :3])) * self.n_agents
super().__init__(scene, action_spec, observation_spec, gamma, horizon, timestep, n_substeps,
n_intermediate_steps, additional_data, collision_spec, max_joint_vel, **viewer_params)
# Construct the mujoco model at origin
robot_model = mujoco.MjModel.from_xml_path(
os.path.join(os.path.dirname(os.path.abspath(env_path)), "planar_robot_1.xml"))
robot_model.body('planar_robot_1/base').pos = np.zeros(3)
robot_data = mujoco.MjData(robot_model)
# Add env_info that requires mujoco models
self.env_info['dt'] = self.dt
self.env_info["robot"]["joint_pos_limit"] = np.array(
[self._model.joint(f"planar_robot_1/joint_{i + 1}").range for i in range(3)]).T
self.env_info["robot"]["robot_model"] = robot_model
self.env_info["robot"]["robot_data"] = robot_data
self.env_info["rl_info"] = self.info
frame_T = np.eye(4)
temp = np.zeros((9, 1))
mujoco.mju_quat2Mat(temp, self._model.body("planar_robot_1/base").quat)
frame_T[:3, :3] = temp.reshape(3, 3)
frame_T[:3, 3] = self._model.body("planar_robot_1/base").pos
self.env_info['robot']['base_frame'].append(frame_T.copy())
if self.n_agents == 2:
mujoco.mju_quat2Mat(temp, self._model.body("planar_robot_2/base").quat)
frame_T[:3, :3] = temp.reshape(3, 3)
frame_T[:3, 3] = self._model.body("planar_robot_2/base").pos
self.env_info['robot']['base_frame'].append(frame_T.copy())
# Ids of the joint, which are controller by the action space
self.actuator_joint_ids = [self._model.joint(name).id for name in action_spec]
def _modify_mdp_info(self, mdp_info):
obs_low = np.array([0, -1, -np.pi, -20., -20., -100,
*np.array([self._model.joint(f"planar_robot_1/joint_{i + 1}").range[0]
for i in range(self.env_info['robot']['n_joints'])]),
*self.env_info['robot']['joint_vel_limit'][0]])
obs_high = np.array([3.02, 1, np.pi, 20., 20., 100,
*np.array([self._model.joint(f"planar_robot_1/joint_{i + 1}").range[1]
for i in range(self.env_info['robot']['n_joints'])]),
*self.env_info['robot']['joint_vel_limit'][1]])
if self.n_agents == 2:
obs_low = np.concatenate([obs_low, [1.5, -1.5, -1.5]])
obs_high = np.concatenate([obs_high, [4.5, 1.5, 1.5]])
mdp_info.observation_space = Box(obs_low, obs_high)
return mdp_info
def is_absorbing(self, obs):
boundary = np.array([self.env_info['table']['length'], self.env_info['table']['width']]) / 2
puck_pos, puck_vel = self.get_puck(obs)
if np.any(np.abs(puck_pos[:2]) > boundary) or np.linalg.norm(puck_vel) > 100:
return True
return False
@staticmethod
def _puck_2d_in_robot_frame(puck_in, robot_frame, type='pose'):
if type == 'pose':
puck_w = np.eye(4)
puck_w[:2, 3] = puck_in[:2]
puck_w[:3, :3] = R.from_euler("xyz", [0., 0., puck_in[2]]).as_matrix()
puck_r = np.linalg.inv(robot_frame) @ puck_w
puck_out = np.concatenate([puck_r[:2, 3],
R.from_matrix(puck_r[:3, :3]).as_euler('xyz')[2:3]])
if type == 'vel':
rot_mat = robot_frame[:3, :3]
vel_lin = np.array([*puck_in[:2], 0])
vel_ang = np.array([0., 0., puck_in[2]])
vel_lin_r = rot_mat.T @ vel_lin
vel_ang_r = rot_mat.T @ vel_ang
puck_out = np.concatenate([vel_lin_r[:2], vel_ang_r[2:3]])
return puck_out
def get_puck(self, obs):
"""
Getting the puck properties from the observations
Args:
obs: The current observation
Returns:
([pos_x, pos_y, yaw], [lin_vel_x, lin_vel_y, yaw_vel])
"""
puck_pos = np.concatenate([self.obs_helper.get_from_obs(obs, "puck_x_pos"),
self.obs_helper.get_from_obs(obs, "puck_y_pos"),
self.obs_helper.get_from_obs(obs, "puck_yaw_pos")])
puck_vel = np.concatenate([self.obs_helper.get_from_obs(obs, "puck_x_vel"),
self.obs_helper.get_from_obs(obs, "puck_y_vel"),
self.obs_helper.get_from_obs(obs, "puck_yaw_vel")])
return puck_pos, puck_vel
def get_ee(self):
raise NotImplementedError
def get_joints(self, obs):
raise NotImplementedError
@@ -0,0 +1,91 @@
import mujoco
import numpy as np
from fancy_gym.envs.mujoco.air_hockey.three_dof.env_base import AirHockeyBase
class AirHockeySingle(AirHockeyBase):
"""
Base class for single agent air hockey tasks.
"""
def __init__(self, gamma=0.99, horizon=500, viewer_params={}):
"""
Constructor.
"""
self.init_state = np.array([-1.15570723, 1.30024401, 1.44280414])
super().__init__(gamma=gamma, horizon=horizon, n_agents=1, viewer_params=viewer_params)
self.filter_ratio = 0.274
self.q_pos_prev = np.zeros(self.env_info["robot"]["n_joints"])
self.q_vel_prev = np.zeros(self.env_info["robot"]["n_joints"])
def get_ee(self):
"""
Getting the ee properties from the current internal state. Can also be obtained via forward kinematics
on the current joint position, this function exists to avoid redundant computations.
Returns:
([pos_x, pos_y, pos_z], [ang_vel_x, ang_vel_y, ang_vel_z, lin_vel_x, lin_vel_y, lin_vel_z])
"""
ee_pos = self._read_data("robot_1/ee_pos")
ee_vel = self._read_data("robot_1/ee_vel")
return ee_pos, ee_vel
def get_joints(self, obs):
"""
Get joint position and velocity of the robot
"""
q_pos = np.zeros(3)
q_vel = np.zeros(3)
for i in range(3):
q_pos[i] = self.obs_helper.get_from_obs(obs, "robot_1/joint_" + str(i + 1) + "_pos")[0]
q_vel[i] = self.obs_helper.get_from_obs(obs, "robot_1/joint_" + str(i + 1) + "_vel")[0]
return q_pos, q_vel
def _modify_observation(self, obs):
new_obs = obs.copy()
puck_pos, puck_vel = self.get_puck(obs)
puck_pos = self._puck_2d_in_robot_frame(puck_pos, self.env_info['robot']['base_frame'][0])
puck_vel = self._puck_2d_in_robot_frame(puck_vel, self.env_info['robot']['base_frame'][0], type='vel')
self.obs_helper.get_from_obs(new_obs, "puck_x_pos")[:] = puck_pos[0]
self.obs_helper.get_from_obs(new_obs, "puck_y_pos")[:] = puck_pos[1]
self.obs_helper.get_from_obs(new_obs, "puck_yaw_pos")[:] = puck_pos[2]
self.obs_helper.get_from_obs(new_obs, "puck_x_vel")[:] = puck_vel[0]
self.obs_helper.get_from_obs(new_obs, "puck_y_vel")[:] = puck_vel[1]
self.obs_helper.get_from_obs(new_obs, "puck_yaw_vel")[:] = puck_vel[2]
return new_obs
def setup(self, state=None):
for i in range(3):
self._data.joint("planar_robot_1/joint_" + str(i + 1)).qpos = self.init_state[i]
self.q_pos_prev[i] = self.init_state[i]
self.q_vel_prev[i] = self._data.joint("planar_robot_1/joint_" + str(i + 1)).qvel[0]
mujoco.mj_fwdPosition(self._model, self._data)
super().setup(state)
def _create_observation(self, obs):
# Filter the joint velocity
q_pos, q_vel = self.get_joints(obs)
q_vel_filter = self.filter_ratio * q_vel + (1 - self.filter_ratio) * self.q_vel_prev
self.q_pos_prev = q_pos
self.q_vel_prev = q_vel_filter
for i in range(3):
self.obs_helper.get_from_obs(obs, "robot_1/joint_" + str(i + 1) + "_vel")[:] = q_vel_filter[i]
yaw_angle = self.obs_helper.get_from_obs(obs, "puck_yaw_pos")
self.obs_helper.get_from_obs(obs, "puck_yaw_pos")[:] = (yaw_angle + np.pi) % (2 * np.pi) - np.pi
return obs
@@ -0,0 +1,84 @@
import numpy as np
from fancy_gym.envs.mujoco.air_hockey.three_dof.env_single import AirHockeySingle
class AirHockeyHit(AirHockeySingle):
"""
Class for the air hockey hitting task.
"""
def __init__(self, gamma=0.99, horizon=500, moving_init=False, viewer_params={}):
"""
Constructor
Args:
moving_init(bool, False): If true, initialize the puck with inital velocity.
"""
super().__init__(gamma=gamma, horizon=horizon, viewer_params=viewer_params)
self.moving_init = moving_init
hit_width = self.env_info['table']['width'] / 2 - self.env_info['puck']['radius'] - \
self.env_info['mallet']['radius'] * 2
self.hit_range = np.array([[-0.7, -0.2], [-hit_width, hit_width]]) # Table Frame
self.init_velocity_range = (0, 0.5) # Table Frame
self.init_ee_range = np.array([[0.60, 1.25], [-0.4, 0.4]]) # Robot Frame
def setup(self, state=None):
# Initial position of the puck
puck_pos = np.random.rand(2) * (self.hit_range[:, 1] - self.hit_range[:, 0]) + self.hit_range[:, 0]
# self.init_state = np.array([-0.9273, 0.9273, np.pi / 2])
self._write_data("puck_x_pos", puck_pos[0])
self._write_data("puck_y_pos", puck_pos[1])
if self.moving_init:
lin_vel = np.random.uniform(self.init_velocity_range[0], self.init_velocity_range[1])
angle = np.random.uniform(-np.pi / 2 - 0.1, np.pi / 2 + 0.1)
puck_vel = np.zeros(3)
puck_vel[0] = -np.cos(angle) * lin_vel
puck_vel[1] = np.sin(angle) * lin_vel
puck_vel[2] = np.random.uniform(-2, 2, 1)
self._write_data("puck_x_vel", puck_vel[0])
self._write_data("puck_y_vel", puck_vel[1])
self._write_data("puck_yaw_vel", puck_vel[2])
super(AirHockeyHit, self).setup(state)
def reward(self, state, action, next_state, absorbing):
return 0
def is_absorbing(self, obs):
puck_pos, puck_vel = self.get_puck(obs)
# Stop if the puck bounces back on the opponents wall
if puck_pos[0] > 0 and puck_vel[0] < 0:
return True
return super(AirHockeyHit, self).is_absorbing(obs)
if __name__ == '__main__':
env = AirHockeyHit(moving_init=False)
env.reset()
env.render()
R = 0.
J = 0.
gamma = 1.
steps = 0
while True:
action = np.zeros(3)
observation, reward, done, info = env.step(action)
env.render()
gamma *= env.info.gamma
J += gamma * reward
R += reward
steps += 1
if done or steps > env.info.horizon:
print("J: ", J, " R: ", R)
R = 0.
J = 0.
gamma = 1.
steps = 0
env.reset()
@@ -0,0 +1,2 @@
from .kinematics import inverse_kinematics, forward_kinematics, jacobian
from .transformations import robot_to_world, world_to_robot
@@ -0,0 +1,248 @@
import mujoco
import numpy as np
def forward_kinematics(mj_model, mj_data, q, link="ee"):
"""
Compute the forward kinematics of the robots.
IMPORTANT:
For the iiwa we assume that the universal joint at the end of the end-effector always leaves the mallet
parallel to the table and facing down. This assumption only makes sense for a subset of robot configurations
where the mallet can be parallel to the table without colliding with the rod it is mounted on. If this is the
case this function will return the wrong values.
Coordinate System:
All translations and rotations are in the coordinate frame of the Robot. The zero point is in the center of the
base of the Robot. The x-axis points forward, the z-axis points up and the y-axis forms a right-handed
coordinate system
Args:
mj_model (mujoco.MjModel):
mujoco MjModel of the robot-only model
mj_data (mujoco.MjData):
mujoco MjData object generated from the model
q (np.array):
joint configuration for which the forward kinematics are computed
link (string, "ee"):
Link for which the forward kinematics is calculated. When using the iiwas the choices are
["1", "2", "3", "4", "5", "6", "7", "ee"]. When using planar the choices are ["1", "2", "3", "ee"]
Returns
-------
position: numpy.ndarray, (3,)
Position of the link in robot's base frame
orientation: numpy.ndarray, (3, 3)
Orientation of the link in robot's base frame
"""
return _mujoco_fk(q, link_to_xml_name(mj_model, link), mj_model, mj_data)
def inverse_kinematics(mj_model, mj_data, desired_position, desired_rotation=None, initial_q=None, link="ee"):
"""
Compute the inverse kinematics of the robots.
IMPORTANT:
For the iiwa we assume that the universal joint at the end of the end-effector always leaves the mallet
parallel to the table and facing down. This assumption only makes sense for a subset of robot configurations
where the mallet can be parallel to the table without colliding with the rod it is mounted on. If this is
the case this function will return the wrong values.
Coordinate System:
All translations and rotations are in the coordinate frame of the Robot. The zero point is in the center of the
base of the Robot. The x-axis points forward, the z-axis points up and the y-axis forms a right-handed
coordinate system
Args:
mj_model (mujoco.MjModel):
mujoco MjModel of the robot-only model
mj_data (mujoco.MjData):
mujoco MjData object generated from the model
desired_position (numpy.ndarray, (3,)):
The desired position of the selected link.
desired_rotation (optional, numpy.array, (3,3)):
The desired rotation of the selected link.
initial_q (numpy.ndarray, None):
The initial configuration of the algorithm, if set to None it will take the initial configuration of the
mj_data.
link (str, "ee"):
Link for which the inverse kinematics is calculated. When using the iiwas the choices are
["1", "2", "3", "4", "5", "6", "7", "ee"]. When using planar the choices are ["1", "2", "3", "ee"]
"""
q_init = np.zeros(mj_model.nq)
if initial_q is None:
q_init = mj_data.qpos
else:
q_init[:initial_q.size] = initial_q
q_l = mj_model.jnt_range[:, 0]
q_h = mj_model.jnt_range[:, 1]
lower_limit = (q_l + q_h) / 2 - 0.95 * (q_h - q_l) / 2
upper_limit = (q_l + q_h) / 2 + 0.95 * (q_h - q_l) / 2
desired_quat = None
if desired_rotation is not None:
desired_quat = np.zeros(4)
mujoco.mju_mat2Quat(desired_quat, desired_rotation.reshape(-1, 1))
return _mujoco_clik(desired_position, desired_quat, q_init, link_to_xml_name(mj_model, link), mj_model,
mj_data, lower_limit, upper_limit)
def jacobian(mj_model, mj_data, q, link="ee"):
"""
Compute the Jacobian of the robots.
IMPORTANT:
For the iiwa we assume that the universal joint at the end of the end-effector always leaves the mallet
parallel to the table and facing down. This assumption only makes sense for a subset of robot configurations
where the mallet can be parallel to the table without colliding with the rod it is mounted on. If this is the
case this function will return the wrong values.
Coordinate System:
All translations and rotations are in the coordinate frame of the Robot. The zero point is in the center of the
base of the Robot. The x-axis points forward, the z-axis points up and the y-axis forms a right-handed
coordinate system
Args:
mj_model (mujoco.MjModel):
mujoco MjModel of the robot-only model
mj_data (mujoco.MjData):
mujoco MjData object generated from the model
q (numpy.ndarray):
joint configuration for which the forward kinematics are computed
link (string, "ee"):
Link for which the forward kinematics is calculated. When using the iiwas the choices are
["1", "2", "3", "4", "5", "6", "7", "ee"]. When using planar the choices are ["1", "2", "3", "ee"]
Returns
-------
numpy.ndarray, (6, num_joints):
The Jacobian matrix for the robot kinematics.
"""
return _mujoco_jac(q, link_to_xml_name(mj_model, link), mj_model, mj_data)
def link_to_xml_name(mj_model, link):
try:
mj_model.body('iiwa_1/base')
link_to_frame_idx = {
"1": "iiwa_1/link_1",
"2": "iiwa_1/link_2",
"3": "iiwa_1/link_3",
"4": "iiwa_1/link_4",
"5": "iiwa_1/link_5",
"6": "iiwa_1/link_6",
"7": "iiwa_1/link_7",
"ee": "iiwa_1/striker_joint_link",
}
except:
link_to_frame_idx = {
"1": "planar_robot_1/body_1",
"2": "planar_robot_1/body_2",
"3": "planar_robot_1/body_3",
"ee": "planar_robot_1/body_ee",
}
return link_to_frame_idx[link]
def _mujoco_fk(q, name, model, data):
data.qpos[:len(q)] = q
mujoco.mj_fwdPosition(model, data)
return data.body(name).xpos.copy(), data.body(name).xmat.reshape(3, 3).copy()
def _mujoco_jac(q, name, model, data):
data.qpos[:len(q)] = q
dtype = data.qpos.dtype
jac = np.empty((6, model.nv), dtype=dtype)
jac_pos, jac_rot = jac[:3], jac[3:]
mujoco.mj_fwdPosition(model, data)
mujoco.mj_jacBody(model, data, jac_pos, jac_rot, model.body(name).id)
return jac
def _mujoco_clik(desired_pos, desired_quat, initial_q, name, model, data, lower_limit, upper_limit):
IT_MAX = 1000
eps = 1e-4
damp = 1e-3
progress_thresh = 20.0
max_update_norm = 0.1
rot_weight = 1
i = 0
dtype = data.qpos.dtype
data.qpos = initial_q
neg_x_quat = np.empty(4, dtype=dtype)
error_x_quat = np.empty(4, dtype=dtype)
if desired_pos is not None and desired_quat is not None:
jac = np.empty((6, model.nv), dtype=dtype)
err = np.empty(6, dtype=dtype)
jac_pos, jac_rot = jac[:3], jac[3:]
err_pos, err_rot = err[:3], err[3:]
else:
jac = np.empty((3, model.nv), dtype=dtype)
err = np.empty(3, dtype=dtype)
if desired_pos is not None:
jac_pos, jac_rot = jac, None
err_pos, err_rot = err, None
elif desired_quat is not None:
jac_pos, jac_rot = None, jac
err_pos, err_rot = None, err
else:
raise ValueError("Desired Position and desired rotation is None, cannot compute inverse kinematics")
while True:
# forward kinematics
mujoco.mj_fwdPosition(model, data)
x_pos = data.body(name).xpos
x_quat = data.body(name).xquat
error_norm = 0
if desired_pos is not None:
err_pos[:] = desired_pos - x_pos
error_norm += np.linalg.norm(err_pos)
if desired_quat is not None:
mujoco.mju_negQuat(neg_x_quat, x_quat)
mujoco.mju_mulQuat(error_x_quat, desired_quat, neg_x_quat)
mujoco.mju_quat2Vel(err_rot, error_x_quat, 1)
error_norm += np.linalg.norm(err_rot) * rot_weight
if error_norm < eps:
success = True
break
if i >= IT_MAX:
success = False
break
mujoco.mj_jacBody(model, data, jac_pos, jac_rot, model.body(name).id)
hess_approx = jac.T.dot(jac)
joint_delta = jac.T.dot(err)
hess_approx += np.eye(hess_approx.shape[0]) * damp
update_joints = np.linalg.solve(hess_approx, joint_delta)
update_norm = np.linalg.norm(update_joints)
# Check whether we are still making enough progress, and halt if not.
progress_criterion = error_norm / update_norm
if progress_criterion > progress_thresh:
success = False
break
if update_norm > max_update_norm:
update_joints *= max_update_norm / update_norm
mujoco.mj_integratePos(model, data.qpos, update_joints, 1)
data.qpos = np.clip(data.qpos, lower_limit, upper_limit)
i += 1
q_cur = data.qpos.copy()
return success, q_cur
@@ -0,0 +1,65 @@
import numpy as np
def robot_to_world(base_frame, translation, rotation=None):
"""
Transform position or rotation optional from the robot base frame to the world frame
Args
----
base_frame: numpy.ndarray, (4,4)
The transformation matrix from the world to robot base frame
translation: ndarray, (3,)
The 3D position to be transformed
rotation: optional, ndarray, (3, 3)
The rotation in the matrix form to be transformed
Returns
-------
position: ndarray (3,)
The transformed 3D position
rotation: ndarray (3, 3)
The transformed rotation in the matrix form
"""
target = np.eye(4)
target[:len(translation), 3] = translation
if rotation is not None:
target[:3, :3] = rotation
target_frame = base_frame @ target
return target_frame[:len(translation), 3], target_frame[:3, :3]
def world_to_robot(base_frame, translation, rotation=None):
"""
Transfrom position and rotation (optional) from the world frame to the robot's base frame
Args
----
base_frame: ndarray, (4,4)
The transformation matrix from the world to robot base frame
translation: ndarray, (3,)
The 3D position to be transformed
rotation: optional, ndarray, (3, 3)
The rotation in the matrix form to be tranformed
Returns
-------
position: ndarray, (3,)
The transformed 3D position
rotation: ndarray, (3, 3)
The transformed rotation in the matrix form
"""
target = np.eye(4)
target[:len(translation), 3] = translation
if rotation is not None:
target[:3, :3] = rotation
target_frame = np.linalg.inv(base_frame) @ target
return target_frame[:len(translation), 3], target_frame[:3, :3]
@@ -0,0 +1,120 @@
import numpy as np
from fancy_gym.envs.mujoco.air_hockey.utils.kinematics import forward_kinematics
class UniversalJointPlugin:
def __init__(self, env_model, env_data, env_info):
self.env_info = env_info
self.env_model = env_model
self.env_data = env_data
self.Kp = 20
self.Kd = 0.31
self.universal_joint_ids = []
self.universal_joint_ctrl_ids = []
self.universal_joint_ids += [env_model.joint("iiwa_1/striker_joint_1").id,
env_model.joint("iiwa_1/striker_joint_2").id]
self.universal_joint_ctrl_ids += [env_model.actuator("iiwa_1/striker_joint_1").id,
env_model.actuator("iiwa_1/striker_joint_2").id]
action_spec = ["iiwa_1/joint_1", "iiwa_1/joint_2", "iiwa_1/joint_3", "iiwa_1/joint_4", "iiwa_1/joint_5",
"iiwa_1/joint_6", "iiwa_1/joint_7"]
if self.env_info['n_agents'] >= 2:
self.universal_joint_ids += [env_model.joint("iiwa_2/striker_joint_1").id,
env_model.joint("iiwa_2/striker_joint_2").id]
self.universal_joint_ctrl_ids += [env_model.actuator("iiwa_2/striker_joint_1").id,
env_model.actuator("iiwa_2/striker_joint_2").id]
action_spec += ["iiwa_2/joint_1", "iiwa_2/joint_2", "iiwa_2/joint_3", "iiwa_2/joint_4", "iiwa_2/joint_5",
"iiwa_2/joint_6", "iiwa_2/joint_7"]
self.actuator_joint_ids = [self.env_model.joint(name).id for name in action_spec]
self.filter_ratio = 0.273
self.u_joint_pos_des = np.zeros(2 * self.env_info['n_agents'])
self.u_joint_pos_prev = None
self.u_joint_vel_prev = np.zeros(2 * self.env_info['n_agents'])
def reset(self):
self.u_joint_pos_prev = None
self._control_universal_joint()
for i in range(self.env_info['n_agents']):
self.u_joint_vel_prev = self.env_data.qvel[self.universal_joint_ids]
self.env_data.qpos[self.universal_joint_ctrl_ids] = self.u_joint_pos_des
def update(self):
self._control_universal_joint()
def _control_universal_joint(self):
self._compute_universal_joint()
self.u_joint_pos_prev = self.env_data.qpos[self.universal_joint_ids]
self.u_joint_vel_prev = self.filter_ratio * self.env_data.qvel[self.universal_joint_ids] + \
(1 - self.filter_ratio) * self.u_joint_vel_prev
Kp = 4
Kd = 0.31
torque = Kp * (self.u_joint_pos_des - self.u_joint_pos_prev) - Kd * self.u_joint_vel_prev
self.env_data.ctrl[self.universal_joint_ctrl_ids] = torque
def _compute_universal_joint(self):
for i in range(self.env_info['n_agents']):
q = self.env_data.qpos[self.actuator_joint_ids[i * 7: (i + 1) * 7]]
# Have to exclude the puck joints
pos, rot_mat = forward_kinematics(self.env_info['robot']['robot_model'],
self.env_info['robot']['robot_data'], q)
v_x = rot_mat[:, 0]
v_y = rot_mat[:, 1]
# The desired position of the x-axis is the cross product of the desired z (0, 0, 1).T
# and the current y-axis. (0, 0, 1).T x v_y
x_desired = np.array([-v_y[1], v_y[0], 0])
# Find the signed angle from the current to the desired x-axis around the y-axis
# https://stackoverflow.com/questions/5188561/signed-angle-between-two-3d-vectors-with-same-origin-within-the-same-plane
q1 = np.arctan2(self._cross_3d(v_x, x_desired) @ v_y, v_x @ x_desired)
if self.u_joint_pos_prev is not None:
if q1 - self.u_joint_pos_prev[0] > np.pi:
q1 -= np.pi * 2
elif q1 - self.u_joint_pos_prev[0] < -np.pi:
q1 += np.pi * 2
# Rotate the X axis by the calculated amount
w = np.array([[0, -v_y[2], v_y[1]],
[v_y[2], 0, -v_y[0]],
[-v_y[1], v_y[0], 0]])
r = np.eye(3) + w * np.sin(q1) + w ** 2 * (1 - np.cos(q1))
v_x_rotated = r @ v_x
# The desired position of the y-axis is the negative cross product of the desired z (0, 0, 1).T and the current
# x-axis, which is already rotated around y. The negative is there because the x-axis is flipped.
# -((0, 0, 1).T x v_x))
y_desired = np.array([v_x_rotated[1], - v_x_rotated[0], 0])
# Find the signed angle from the current to the desired y-axis around the new rotated x-axis
q2 = np.arctan2(self._cross_3d(v_y, y_desired) @ v_x_rotated, v_y @ y_desired)
if self.u_joint_pos_prev is not None:
if q2 - self.u_joint_pos_prev[1] > np.pi:
q2 -= np.pi * 2
elif q2 - self.u_joint_pos_prev[1] < -np.pi:
q2 += np.pi * 2
alpha_y = np.minimum(np.maximum(q1, -np.pi / 2 * 0.95), np.pi / 2 * 0.95)
alpha_x = np.minimum(np.maximum(q2, -np.pi / 2 * 0.95), np.pi / 2 * 0.95)
if self.u_joint_pos_prev is None:
self.u_joint_pos_des[i * 2: i * 2 + 2] = np.array([alpha_y, alpha_x])
else:
self.u_joint_pos_des[i * 2: i * 2 + 2] += np.minimum(np.maximum(
10 * (np.array([alpha_y, alpha_x]) - self.u_joint_pos_des[i * 2: i * 2 + 2]),
-np.pi * 0.01), np.pi * 0.01)
self.u_joint_pos_des[i * 2: i * 2 + 2] = np.array([alpha_y, alpha_x])
return self.u_joint_pos_des
def _cross_3d(self, a, b):
return np.array([a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]])
+84 -11
View File
@@ -1,8 +1,11 @@
from typing import Tuple, Union, Optional from typing import Tuple, Union, Optional, Any, Dict
import numpy as np import numpy as np
from gym.core import ObsType from gymnasium.core import ObsType
from gym.envs.mujoco.ant_v4 import AntEnv from gymnasium.envs.mujoco.ant_v4 import AntEnv, DEFAULT_CAMERA_CONFIG
from gymnasium import utils
from gymnasium.envs.mujoco import MujocoEnv
from gymnasium.spaces import Box
MAX_EPISODE_STEPS_ANTJUMP = 200 MAX_EPISODE_STEPS_ANTJUMP = 200
@@ -12,8 +15,74 @@ MAX_EPISODE_STEPS_ANTJUMP = 200
# to the same structure as the Hopper, where the angles are randomized (->contexts) and the agent should jump as heigh # to the same structure as the Hopper, where the angles are randomized (->contexts) and the agent should jump as heigh
# as possible, while landing at a specific target position # as possible, while landing at a specific target position
class AntEnvCustomXML(AntEnv):
def __init__(
self,
xml_file="ant.xml",
ctrl_cost_weight=0.5,
use_contact_forces=False,
contact_cost_weight=5e-4,
healthy_reward=1.0,
terminate_when_unhealthy=True,
healthy_z_range=(0.2, 1.0),
contact_force_range=(-1.0, 1.0),
reset_noise_scale=0.1,
exclude_current_positions_from_observation=True,
**kwargs,
):
utils.EzPickle.__init__(
self,
xml_file,
ctrl_cost_weight,
use_contact_forces,
contact_cost_weight,
healthy_reward,
terminate_when_unhealthy,
healthy_z_range,
contact_force_range,
reset_noise_scale,
exclude_current_positions_from_observation,
**kwargs,
)
class AntJumpEnv(AntEnv): self._ctrl_cost_weight = ctrl_cost_weight
self._contact_cost_weight = contact_cost_weight
self._healthy_reward = healthy_reward
self._terminate_when_unhealthy = terminate_when_unhealthy
self._healthy_z_range = healthy_z_range
self._contact_force_range = contact_force_range
self._reset_noise_scale = reset_noise_scale
self._use_contact_forces = use_contact_forces
self._exclude_current_positions_from_observation = (
exclude_current_positions_from_observation
)
obs_shape = 27 + 1
if not exclude_current_positions_from_observation:
obs_shape += 2
if use_contact_forces:
obs_shape += 84
observation_space = Box(
low=-np.inf, high=np.inf, shape=(obs_shape,), dtype=np.float64
)
MujocoEnv.__init__(
self,
xml_file,
5,
observation_space=observation_space,
default_camera_config=DEFAULT_CAMERA_CONFIG,
**kwargs,
)
class AntJumpEnv(AntEnvCustomXML):
""" """
Initialization changes to normal Ant: Initialization changes to normal Ant:
- healthy_reward: 1.0 -> 0.01 -> 0.0 no healthy reward needed - Paul and Marc - healthy_reward: 1.0 -> 0.01 -> 0.0 no healthy reward needed - Paul and Marc
@@ -32,6 +101,7 @@ class AntJumpEnv(AntEnv):
contact_force_range=(-1.0, 1.0), contact_force_range=(-1.0, 1.0),
reset_noise_scale=0.1, reset_noise_scale=0.1,
exclude_current_positions_from_observation=True, exclude_current_positions_from_observation=True,
**kwargs
): ):
self.current_step = 0 self.current_step = 0
self.max_height = 0 self.max_height = 0
@@ -44,7 +114,7 @@ class AntJumpEnv(AntEnv):
healthy_z_range=healthy_z_range, healthy_z_range=healthy_z_range,
contact_force_range=contact_force_range, contact_force_range=contact_force_range,
reset_noise_scale=reset_noise_scale, reset_noise_scale=reset_noise_scale,
exclude_current_positions_from_observation=exclude_current_positions_from_observation) exclude_current_positions_from_observation=exclude_current_positions_from_observation, **kwargs)
def step(self, action): def step(self, action):
self.current_step += 1 self.current_step += 1
@@ -61,9 +131,10 @@ class AntJumpEnv(AntEnv):
costs = ctrl_cost + contact_cost costs = ctrl_cost + contact_cost
done = bool(height < 0.3) # fall over -> is the 0.3 value from healthy_z_range? TODO change 0.3 to the value of healthy z angle terminated = bool(
height < 0.3) # fall over -> is the 0.3 value from healthy_z_range? TODO change 0.3 to the value of healthy z angle
if self.current_step == MAX_EPISODE_STEPS_ANTJUMP or done: if self.current_step == MAX_EPISODE_STEPS_ANTJUMP or terminated:
# -10 for scaling the value of the distance between the max_height and the goal height; only used when context is enabled # -10 for scaling the value of the distance between the max_height and the goal height; only used when context is enabled
# height_reward = -10 * (np.linalg.norm(self.max_height - self.goal)) # height_reward = -10 * (np.linalg.norm(self.max_height - self.goal))
height_reward = -10 * np.linalg.norm(self.max_height - self.goal) height_reward = -10 * np.linalg.norm(self.max_height - self.goal)
@@ -80,19 +151,21 @@ class AntJumpEnv(AntEnv):
'max_height': self.max_height, 'max_height': self.max_height,
'goal': self.goal 'goal': self.goal
} }
truncated = False
return obs, reward, done, info return obs, reward, terminated, truncated, info
def _get_obs(self): def _get_obs(self):
return np.append(super()._get_obs(), self.goal) return np.append(super()._get_obs(), self.goal)
def reset(self, *, seed: Optional[int] = None, return_info: bool = False, def reset(self, *, seed: Optional[int] = None, options: Optional[Dict[str, Any]] = None) \
options: Optional[dict] = None, ) -> Union[ObsType, Tuple[ObsType, dict]]: -> Tuple[ObsType, Dict[str, Any]]:
self.current_step = 0 self.current_step = 0
self.max_height = 0 self.max_height = 0
# goal heights from 1.0 to 2.5; can be increased, but didnt work well with CMORE # goal heights from 1.0 to 2.5; can be increased, but didnt work well with CMORE
ret = super().reset(seed=seed, options=options)
self.goal = self.np_random.uniform(1.0, 2.5, 1) self.goal = self.np_random.uniform(1.0, 2.5, 1)
return super().reset() return ret
# reset_model had to be implemented in every env to make it deterministic # reset_model had to be implemented in every env to make it deterministic
def reset_model(self): def reset_model(self):

Some files were not shown because too many files have changed in this diff Show More