diff --git a/poetry.lock b/poetry.lock index cd87a2d..a5f46f5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -29,18 +29,19 @@ dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest [[package]] name = "backrefs" -version = "5.8" +version = "5.9" description = "A wrapper around re and regex that adds additional back references." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d"}, - {file = "backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b"}, - {file = "backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486"}, - {file = "backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585"}, - {file = "backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc"}, - {file = "backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd"}, + {file = "backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f"}, + {file = "backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf"}, + {file = "backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa"}, + {file = "backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b"}, + {file = "backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9"}, + {file = "backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60"}, + {file = "backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59"}, ] [package.extras] @@ -552,67 +553,67 @@ colorama = ">=0.4" [[package]] name = "grpcio" -version = "1.73.0" +version = "1.73.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "grpcio-1.73.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:d050197eeed50f858ef6c51ab09514856f957dba7b1f7812698260fc9cc417f6"}, - {file = "grpcio-1.73.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:ebb8d5f4b0200916fb292a964a4d41210de92aba9007e33d8551d85800ea16cb"}, - {file = "grpcio-1.73.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:c0811331b469e3f15dda5f90ab71bcd9681189a83944fd6dc908e2c9249041ef"}, - {file = "grpcio-1.73.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12787c791c3993d0ea1cc8bf90393647e9a586066b3b322949365d2772ba965b"}, - {file = "grpcio-1.73.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c17771e884fddf152f2a0df12478e8d02853e5b602a10a9a9f1f52fa02b1d32"}, - {file = "grpcio-1.73.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:275e23d4c428c26b51857bbd95fcb8e528783597207ec592571e4372b300a29f"}, - {file = "grpcio-1.73.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9ffc972b530bf73ef0f948f799482a1bf12d9b6f33406a8e6387c0ca2098a833"}, - {file = "grpcio-1.73.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d269df64aff092b2cec5e015d8ae09c7e90888b5c35c24fdca719a2c9f35"}, - {file = "grpcio-1.73.0-cp310-cp310-win32.whl", hash = "sha256:072d8154b8f74300ed362c01d54af8b93200c1a9077aeaea79828d48598514f1"}, - {file = "grpcio-1.73.0-cp310-cp310-win_amd64.whl", hash = "sha256:ce953d9d2100e1078a76a9dc2b7338d5415924dc59c69a15bf6e734db8a0f1ca"}, - {file = "grpcio-1.73.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:51036f641f171eebe5fa7aaca5abbd6150f0c338dab3a58f9111354240fe36ec"}, - {file = "grpcio-1.73.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:d12bbb88381ea00bdd92c55aff3da3391fd85bc902c41275c8447b86f036ce0f"}, - {file = "grpcio-1.73.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:483c507c2328ed0e01bc1adb13d1eada05cc737ec301d8e5a8f4a90f387f1790"}, - {file = "grpcio-1.73.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c201a34aa960c962d0ce23fe5f423f97e9d4b518ad605eae6d0a82171809caaa"}, - {file = "grpcio-1.73.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:859f70c8e435e8e1fa060e04297c6818ffc81ca9ebd4940e180490958229a45a"}, - {file = "grpcio-1.73.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e2459a27c6886e7e687e4e407778425f3c6a971fa17a16420227bda39574d64b"}, - {file = "grpcio-1.73.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e0084d4559ee3dbdcce9395e1bc90fdd0262529b32c417a39ecbc18da8074ac7"}, - {file = "grpcio-1.73.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef5fff73d5f724755693a464d444ee0a448c6cdfd3c1616a9223f736c622617d"}, - {file = "grpcio-1.73.0-cp311-cp311-win32.whl", hash = "sha256:965a16b71a8eeef91fc4df1dc40dc39c344887249174053814f8a8e18449c4c3"}, - {file = "grpcio-1.73.0-cp311-cp311-win_amd64.whl", hash = "sha256:b71a7b4483d1f753bbc11089ff0f6fa63b49c97a9cc20552cded3fcad466d23b"}, - {file = "grpcio-1.73.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:fb9d7c27089d9ba3746f18d2109eb530ef2a37452d2ff50f5a6696cd39167d3b"}, - {file = "grpcio-1.73.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:128ba2ebdac41e41554d492b82c34586a90ebd0766f8ebd72160c0e3a57b9155"}, - {file = "grpcio-1.73.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:068ecc415f79408d57a7f146f54cdf9f0acb4b301a52a9e563973dc981e82f3d"}, - {file = "grpcio-1.73.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ddc1cfb2240f84d35d559ade18f69dcd4257dbaa5ba0de1a565d903aaab2968"}, - {file = "grpcio-1.73.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53007f70d9783f53b41b4cf38ed39a8e348011437e4c287eee7dd1d39d54b2f"}, - {file = "grpcio-1.73.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4dd8d8d092efede7d6f48d695ba2592046acd04ccf421436dd7ed52677a9ad29"}, - {file = "grpcio-1.73.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:70176093d0a95b44d24baa9c034bb67bfe2b6b5f7ebc2836f4093c97010e17fd"}, - {file = "grpcio-1.73.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:085ebe876373ca095e24ced95c8f440495ed0b574c491f7f4f714ff794bbcd10"}, - {file = "grpcio-1.73.0-cp312-cp312-win32.whl", hash = "sha256:cfc556c1d6aef02c727ec7d0016827a73bfe67193e47c546f7cadd3ee6bf1a60"}, - {file = "grpcio-1.73.0-cp312-cp312-win_amd64.whl", hash = "sha256:bbf45d59d090bf69f1e4e1594832aaf40aa84b31659af3c5e2c3f6a35202791a"}, - {file = "grpcio-1.73.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:da1d677018ef423202aca6d73a8d3b2cb245699eb7f50eb5f74cae15a8e1f724"}, - {file = "grpcio-1.73.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:36bf93f6a657f37c131d9dd2c391b867abf1426a86727c3575393e9e11dadb0d"}, - {file = "grpcio-1.73.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:d84000367508ade791d90c2bafbd905574b5ced8056397027a77a215d601ba15"}, - {file = "grpcio-1.73.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c98ba1d928a178ce33f3425ff823318040a2b7ef875d30a0073565e5ceb058d9"}, - {file = "grpcio-1.73.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a73c72922dfd30b396a5f25bb3a4590195ee45ecde7ee068acb0892d2900cf07"}, - {file = "grpcio-1.73.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:10e8edc035724aba0346a432060fd192b42bd03675d083c01553cab071a28da5"}, - {file = "grpcio-1.73.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f5cdc332b503c33b1643b12ea933582c7b081957c8bc2ea4cc4bc58054a09288"}, - {file = "grpcio-1.73.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:07ad7c57233c2109e4ac999cb9c2710c3b8e3f491a73b058b0ce431f31ed8145"}, - {file = "grpcio-1.73.0-cp313-cp313-win32.whl", hash = "sha256:0eb5df4f41ea10bda99a802b2a292d85be28958ede2a50f2beb8c7fc9a738419"}, - {file = "grpcio-1.73.0-cp313-cp313-win_amd64.whl", hash = "sha256:38cf518cc54cd0c47c9539cefa8888549fcc067db0b0c66a46535ca8032020c4"}, - {file = "grpcio-1.73.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:1284850607901cfe1475852d808e5a102133461ec9380bc3fc9ebc0686ee8e32"}, - {file = "grpcio-1.73.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:0e092a4b28eefb63eec00d09ef33291cd4c3a0875cde29aec4d11d74434d222c"}, - {file = "grpcio-1.73.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:33577fe7febffe8ebad458744cfee8914e0c10b09f0ff073a6b149a84df8ab8f"}, - {file = "grpcio-1.73.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60813d8a16420d01fa0da1fc7ebfaaa49a7e5051b0337cd48f4f950eb249a08e"}, - {file = "grpcio-1.73.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9c957dc65e5d474378d7bcc557e9184576605d4b4539e8ead6e351d7ccce20"}, - {file = "grpcio-1.73.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3902b71407d021163ea93c70c8531551f71ae742db15b66826cf8825707d2908"}, - {file = "grpcio-1.73.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1dd7fa7276dcf061e2d5f9316604499eea06b1b23e34a9380572d74fe59915a8"}, - {file = "grpcio-1.73.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2d1510c4ea473110cb46a010555f2c1a279d1c256edb276e17fa571ba1e8927c"}, - {file = "grpcio-1.73.0-cp39-cp39-win32.whl", hash = "sha256:d0a1517b2005ba1235a1190b98509264bf72e231215dfeef8db9a5a92868789e"}, - {file = "grpcio-1.73.0-cp39-cp39-win_amd64.whl", hash = "sha256:6228f7eb6d9f785f38b589d49957fca5df3d5b5349e77d2d89b14e390165344c"}, - {file = "grpcio-1.73.0.tar.gz", hash = "sha256:3af4c30918a7f0d39de500d11255f8d9da4f30e94a2033e70fe2a720e184bd8e"}, + {file = "grpcio-1.73.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:2d70f4ddd0a823436c2624640570ed6097e40935c9194482475fe8e3d9754d55"}, + {file = "grpcio-1.73.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:3841a8a5a66830261ab6a3c2a3dc539ed84e4ab019165f77b3eeb9f0ba621f26"}, + {file = "grpcio-1.73.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:628c30f8e77e0258ab788750ec92059fc3d6628590fb4b7cea8c102503623ed7"}, + {file = "grpcio-1.73.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67a0468256c9db6d5ecb1fde4bf409d016f42cef649323f0a08a72f352d1358b"}, + {file = "grpcio-1.73.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b84d65bbdebd5926eb5c53b0b9ec3b3f83408a30e4c20c373c5337b4219ec5"}, + {file = "grpcio-1.73.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c54796ca22b8349cc594d18b01099e39f2b7ffb586ad83217655781a350ce4da"}, + {file = "grpcio-1.73.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:75fc8e543962ece2f7ecd32ada2d44c0c8570ae73ec92869f9af8b944863116d"}, + {file = "grpcio-1.73.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6a6037891cd2b1dd1406b388660522e1565ed340b1fea2955b0234bdd941a862"}, + {file = "grpcio-1.73.1-cp310-cp310-win32.whl", hash = "sha256:cce7265b9617168c2d08ae570fcc2af4eaf72e84f8c710ca657cc546115263af"}, + {file = "grpcio-1.73.1-cp310-cp310-win_amd64.whl", hash = "sha256:6a2b372e65fad38842050943f42ce8fee00c6f2e8ea4f7754ba7478d26a356ee"}, + {file = "grpcio-1.73.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:ba2cea9f7ae4bc21f42015f0ec98f69ae4179848ad744b210e7685112fa507a1"}, + {file = "grpcio-1.73.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:d74c3f4f37b79e746271aa6cdb3a1d7e4432aea38735542b23adcabaaee0c097"}, + {file = "grpcio-1.73.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:5b9b1805a7d61c9e90541cbe8dfe0a593dfc8c5c3a43fe623701b6a01b01d710"}, + {file = "grpcio-1.73.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3215f69a0670a8cfa2ab53236d9e8026bfb7ead5d4baabe7d7dc11d30fda967"}, + {file = "grpcio-1.73.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc5eccfd9577a5dc7d5612b2ba90cca4ad14c6d949216c68585fdec9848befb1"}, + {file = "grpcio-1.73.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dc7d7fd520614fce2e6455ba89791458020a39716951c7c07694f9dbae28e9c0"}, + {file = "grpcio-1.73.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:105492124828911f85127e4825d1c1234b032cb9d238567876b5515d01151379"}, + {file = "grpcio-1.73.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:610e19b04f452ba6f402ac9aa94eb3d21fbc94553368008af634812c4a85a99e"}, + {file = "grpcio-1.73.1-cp311-cp311-win32.whl", hash = "sha256:d60588ab6ba0ac753761ee0e5b30a29398306401bfbceffe7d68ebb21193f9d4"}, + {file = "grpcio-1.73.1-cp311-cp311-win_amd64.whl", hash = "sha256:6957025a4608bb0a5ff42abd75bfbb2ed99eda29d5992ef31d691ab54b753643"}, + {file = "grpcio-1.73.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:921b25618b084e75d424a9f8e6403bfeb7abef074bb6c3174701e0f2542debcf"}, + {file = "grpcio-1.73.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:277b426a0ed341e8447fbf6c1d6b68c952adddf585ea4685aa563de0f03df887"}, + {file = "grpcio-1.73.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:96c112333309493c10e118d92f04594f9055774757f5d101b39f8150f8c25582"}, + {file = "grpcio-1.73.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f48e862aed925ae987eb7084409a80985de75243389dc9d9c271dd711e589918"}, + {file = "grpcio-1.73.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83a6c2cce218e28f5040429835fa34a29319071079e3169f9543c3fbeff166d2"}, + {file = "grpcio-1.73.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:65b0458a10b100d815a8426b1442bd17001fdb77ea13665b2f7dc9e8587fdc6b"}, + {file = "grpcio-1.73.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0a9f3ea8dce9eae9d7cb36827200133a72b37a63896e0e61a9d5ec7d61a59ab1"}, + {file = "grpcio-1.73.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:de18769aea47f18e782bf6819a37c1c528914bfd5683b8782b9da356506190c8"}, + {file = "grpcio-1.73.1-cp312-cp312-win32.whl", hash = "sha256:24e06a5319e33041e322d32c62b1e728f18ab8c9dbc91729a3d9f9e3ed336642"}, + {file = "grpcio-1.73.1-cp312-cp312-win_amd64.whl", hash = "sha256:303c8135d8ab176f8038c14cc10d698ae1db9c480f2b2823f7a987aa2a4c5646"}, + {file = "grpcio-1.73.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b310824ab5092cf74750ebd8a8a8981c1810cb2b363210e70d06ef37ad80d4f9"}, + {file = "grpcio-1.73.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:8f5a6df3fba31a3485096ac85b2e34b9666ffb0590df0cd044f58694e6a1f6b5"}, + {file = "grpcio-1.73.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:052e28fe9c41357da42250a91926a3e2f74c046575c070b69659467ca5aa976b"}, + {file = "grpcio-1.73.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c0bf15f629b1497436596b1cbddddfa3234273490229ca29561209778ebe182"}, + {file = "grpcio-1.73.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab860d5bfa788c5a021fba264802e2593688cd965d1374d31d2b1a34cacd854"}, + {file = "grpcio-1.73.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ad1d958c31cc91ab050bd8a91355480b8e0683e21176522bacea225ce51163f2"}, + {file = "grpcio-1.73.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f43ffb3bd415c57224c7427bfb9e6c46a0b6e998754bfa0d00f408e1873dcbb5"}, + {file = "grpcio-1.73.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:686231cdd03a8a8055f798b2b54b19428cdf18fa1549bee92249b43607c42668"}, + {file = "grpcio-1.73.1-cp313-cp313-win32.whl", hash = "sha256:89018866a096e2ce21e05eabed1567479713ebe57b1db7cbb0f1e3b896793ba4"}, + {file = "grpcio-1.73.1-cp313-cp313-win_amd64.whl", hash = "sha256:4a68f8c9966b94dff693670a5cf2b54888a48a5011c5d9ce2295a1a1465ee84f"}, + {file = "grpcio-1.73.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:b4adc97d2d7f5c660a5498bda978ebb866066ad10097265a5da0511323ae9f50"}, + {file = "grpcio-1.73.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:c45a28a0cfb6ddcc7dc50a29de44ecac53d115c3388b2782404218db51cb2df3"}, + {file = "grpcio-1.73.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:10af9f2ab98a39f5b6c1896c6fc2036744b5b41d12739d48bed4c3e15b6cf900"}, + {file = "grpcio-1.73.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45cf17dcce5ebdb7b4fe9e86cb338fa99d7d1bb71defc78228e1ddf8d0de8cbb"}, + {file = "grpcio-1.73.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c502c2e950fc7e8bf05c047e8a14522ef7babac59abbfde6dbf46b7a0d9c71e"}, + {file = "grpcio-1.73.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6abfc0f9153dc4924536f40336f88bd4fe7bd7494f028675e2e04291b8c2c62a"}, + {file = "grpcio-1.73.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ed451a0e39c8e51eb1612b78686839efd1a920666d1666c1adfdb4fd51680c0f"}, + {file = "grpcio-1.73.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:07f08705a5505c9b5b0cbcbabafb96462b5a15b7236bbf6bbcc6b0b91e1cbd7e"}, + {file = "grpcio-1.73.1-cp39-cp39-win32.whl", hash = "sha256:ad5c958cc3d98bb9d71714dc69f1c13aaf2f4b53e29d4cc3f1501ef2e4d129b2"}, + {file = "grpcio-1.73.1-cp39-cp39-win_amd64.whl", hash = "sha256:42f0660bce31b745eb9d23f094a332d31f210dcadd0fc8e5be7e4c62a87ce86b"}, + {file = "grpcio-1.73.1.tar.gz", hash = "sha256:7fce2cd1c0c1116cf3850564ebfc3264fba75d3c74a7414373f1238ea365ef87"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.73.0)"] +protobuf = ["grpcio-tools (>=1.73.1)"] [[package]] name = "idna" @@ -696,14 +697,14 @@ files = [ [[package]] name = "ir-sim" -version = "2.5.4" +version = "2.5.5" description = "IR-SIM is an open-source, lightweight robot simulator based on Python, designed for robotics navigation, control, and learning. This simulator provides a simple and user-friendly framework for simulating robots, sensors, and environments, facilitating the development and testing of robotics algorithms with minimal hardware requirements." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "ir_sim-2.5.4-py3-none-any.whl", hash = "sha256:a899f6da22ee53fb0ac150c9d2054d3de86e101e029c775a9265d3b49e9c6885"}, - {file = "ir_sim-2.5.4.tar.gz", hash = "sha256:30c17a2b1708f9096e19d60968cf7eb77f563008f52ed4b406462c501f76683c"}, + {file = "ir_sim-2.5.5-py3-none-any.whl", hash = "sha256:2c865da7390594ff939caa561e749d13a6ef2bc29190b3cced46be9b7821ed7d"}, + {file = "ir_sim-2.5.5.tar.gz", hash = "sha256:fe45ac921ba0e939a19d5f72c250d40721e4391f0f5fb51ce9770e74bcdb0e5f"}, ] [package.dependencies] @@ -1078,14 +1079,14 @@ pyyaml = ">=5.1" [[package]] name = "mkdocs-material" -version = "9.6.14" +version = "9.6.15" description = "Documentation that simply works" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "mkdocs_material-9.6.14-py3-none-any.whl", hash = "sha256:3b9cee6d3688551bf7a8e8f41afda97a3c39a12f0325436d76c86706114b721b"}, - {file = "mkdocs_material-9.6.14.tar.gz", hash = "sha256:39d795e90dce6b531387c255bd07e866e027828b7346d3eba5ac3de265053754"}, + {file = "mkdocs_material-9.6.15-py3-none-any.whl", hash = "sha256:ac969c94d4fe5eb7c924b6d2f43d7db41159ea91553d18a9afc4780c34f2717a"}, + {file = "mkdocs_material-9.6.15.tar.gz", hash = "sha256:64adf8fa8dba1a17905b6aee1894a5aafd966d4aeb44a11088519b0f5ca4f1b5"}, ] [package.dependencies] @@ -1529,101 +1530,106 @@ files = [ [[package]] name = "pillow" -version = "11.2.1" +version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pillow-11.2.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:d57a75d53922fc20c165016a20d9c44f73305e67c351bbc60d1adaf662e74047"}, - {file = "pillow-11.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:127bf6ac4a5b58b3d32fc8289656f77f80567d65660bc46f72c0d77e6600cc95"}, - {file = "pillow-11.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4ba4be812c7a40280629e55ae0b14a0aafa150dd6451297562e1764808bbe61"}, - {file = "pillow-11.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8bd62331e5032bc396a93609982a9ab6b411c05078a52f5fe3cc59234a3abd1"}, - {file = "pillow-11.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:562d11134c97a62fe3af29581f083033179f7ff435f78392565a1ad2d1c2c45c"}, - {file = "pillow-11.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c97209e85b5be259994eb5b69ff50c5d20cca0f458ef9abd835e262d9d88b39d"}, - {file = "pillow-11.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0c3e6d0f59171dfa2e25d7116217543310908dfa2770aa64b8f87605f8cacc97"}, - {file = "pillow-11.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc1c3bc53befb6096b84165956e886b1729634a799e9d6329a0c512ab651e579"}, - {file = "pillow-11.2.1-cp310-cp310-win32.whl", hash = "sha256:312c77b7f07ab2139924d2639860e084ec2a13e72af54d4f08ac843a5fc9c79d"}, - {file = "pillow-11.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9bc7ae48b8057a611e5fe9f853baa88093b9a76303937449397899385da06fad"}, - {file = "pillow-11.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:2728567e249cdd939f6cc3d1f049595c66e4187f3c34078cbc0a7d21c47482d2"}, - {file = "pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70"}, - {file = "pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf"}, - {file = "pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7"}, - {file = "pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8"}, - {file = "pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600"}, - {file = "pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788"}, - {file = "pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e"}, - {file = "pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e"}, - {file = "pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6"}, - {file = "pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193"}, - {file = "pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7"}, - {file = "pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f"}, - {file = "pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b"}, - {file = "pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d"}, - {file = "pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4"}, - {file = "pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d"}, - {file = "pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4"}, - {file = "pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443"}, - {file = "pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c"}, - {file = "pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3"}, - {file = "pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941"}, - {file = "pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb"}, - {file = "pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28"}, - {file = "pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830"}, - {file = "pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0"}, - {file = "pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1"}, - {file = "pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f"}, - {file = "pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155"}, - {file = "pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14"}, - {file = "pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b"}, - {file = "pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2"}, - {file = "pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691"}, - {file = "pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c"}, - {file = "pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22"}, - {file = "pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7"}, - {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16"}, - {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b"}, - {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406"}, - {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91"}, - {file = "pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751"}, - {file = "pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9"}, - {file = "pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd"}, - {file = "pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e"}, - {file = "pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681"}, - {file = "pillow-11.2.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:7491cf8a79b8eb867d419648fff2f83cb0b3891c8b36da92cc7f1931d46108c8"}, - {file = "pillow-11.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b02d8f9cb83c52578a0b4beadba92e37d83a4ef11570a8688bbf43f4ca50909"}, - {file = "pillow-11.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:014ca0050c85003620526b0ac1ac53f56fc93af128f7546623cc8e31875ab928"}, - {file = "pillow-11.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3692b68c87096ac6308296d96354eddd25f98740c9d2ab54e1549d6c8aea9d79"}, - {file = "pillow-11.2.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:f781dcb0bc9929adc77bad571b8621ecb1e4cdef86e940fe2e5b5ee24fd33b35"}, - {file = "pillow-11.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2b490402c96f907a166615e9a5afacf2519e28295f157ec3a2bb9bd57de638cb"}, - {file = "pillow-11.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dd6b20b93b3ccc9c1b597999209e4bc5cf2853f9ee66e3fc9a400a78733ffc9a"}, - {file = "pillow-11.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4b835d89c08a6c2ee7781b8dd0a30209a8012b5f09c0a665b65b0eb3560b6f36"}, - {file = "pillow-11.2.1-cp39-cp39-win32.whl", hash = "sha256:b10428b3416d4f9c61f94b494681280be7686bda15898a3a9e08eb66a6d92d67"}, - {file = "pillow-11.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:6ebce70c3f486acf7591a3d73431fa504a4e18a9b97ff27f5f47b7368e4b9dd1"}, - {file = "pillow-11.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:c27476257b2fdcd7872d54cfd119b3a9ce4610fb85c8e32b70b42e3680a29a1e"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b7b0d4fd2635f54ad82785d56bc0d94f147096493a79985d0ab57aedd563156"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:aa442755e31c64037aa7c1cb186e0b369f8416c567381852c63444dd666fb772"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d3348c95b766f54b76116d53d4cb171b52992a1027e7ca50c81b43b9d9e363"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85d27ea4c889342f7e35f6d56e7e1cb345632ad592e8c51b693d7b7556043ce0"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bf2c33d6791c598142f00c9c4c7d47f6476731c31081331664eb26d6ab583e01"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e616e7154c37669fc1dfc14584f11e284e05d1c650e1c0f972f281c4ccc53193"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:39ad2e0f424394e3aebc40168845fee52df1394a4673a6ee512d840d14ab3013"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044"}, - {file = "pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6"}, + {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, + {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"}, + {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"}, + {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"}, + {file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"}, + {file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"}, + {file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"}, + {file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"}, + {file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"}, + {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"}, + {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"}, + {file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"}, + {file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"}, + {file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"}, + {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"}, + {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"}, + {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"}, + {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"}, + {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"}, + {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"}, + {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"}, + {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"}, + {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"}, + {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"}, + {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"}, + {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"}, + {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"}, + {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"}, + {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"}, + {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"}, + {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"}, + {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"}, + {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"}, + {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"}, + {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"}, + {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"}, + {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"}, + {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"}, + {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"}, + {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"}, + {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"}, + {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"}, + {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"}, + {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"}, + {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"}, + {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"}, + {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"}, + {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"}, + {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"}, + {file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"}, + {file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"}, + {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"}, + {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"}, + {file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"}, + {file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"}, + {file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"}, + {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] test-arrow = ["pyarrow"] -tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] typing = ["typing-extensions"] xmp = ["defusedxml"] @@ -1705,14 +1711,14 @@ test = ["pytest", "pytest-xdist", "setuptools"] [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, - {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] [package.extras] @@ -1720,14 +1726,14 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pymdown-extensions" -version = "10.15" +version = "10.16" description = "Extension pack for Python Markdown." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pymdown_extensions-10.15-py3-none-any.whl", hash = "sha256:46e99bb272612b0de3b7e7caf6da8dd5f4ca5212c0b273feb9304e236c484e5f"}, - {file = "pymdown_extensions-10.15.tar.gz", hash = "sha256:0e5994e32155f4b03504f939e501b981d306daf7ec2aa1cd2eb6bd300784f8f7"}, + {file = "pymdown_extensions-10.16-py3-none-any.whl", hash = "sha256:f5dd064a4db588cb2d95229fc4ee63a1b16cc8b4d0e6145c0899ed8723da1df2"}, + {file = "pymdown_extensions-10.16.tar.gz", hash = "sha256:71dac4fca63fabeffd3eb9038b756161a33ec6e8d230853d3cecf562155ab3de"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index 3052ad3..c66e62e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ dependencies = [ "mkdocs (>=1.6.1,<2.0.0)", "mkdocstrings[python] (>=0.29.1,<0.30.0)", "mkdocs-material (>=9.6.11,<10.0.0)", - "scipy (>=1.15.2,<2.0.0)" + "scipy (>=1.15.2,<2.0.0)", ] [tool.pytest.ini_options] diff --git a/robot_nav/SIM_ENV/__init__.py b/robot_nav/SIM_ENV/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/robot_nav/SIM_ENV/marl_sim.py b/robot_nav/SIM_ENV/marl_sim.py new file mode 100644 index 0000000..8fd10ba --- /dev/null +++ b/robot_nav/SIM_ENV/marl_sim.py @@ -0,0 +1,300 @@ +import irsim +import numpy as np +import random +import torch + +from robot_nav.SIM_ENV.sim_env import SIM_ENV + + +class MARL_SIM(SIM_ENV): + """ + A simulation environment interface for robot navigation using IRSim in MARL setting. + + This class wraps around the IRSim environment and provides methods for stepping, + resetting, and interacting with mobile robots, including reward computation. + + Attributes: + env (object): The simulation environment instance from IRSim. + robot_goal (np.ndarray): The goal position of the robot. + """ + + def __init__(self, world_file="multi_robot_world.yaml", disable_plotting=False): + """ + Initialize the simulation environment. + + Args: + world_file (str): Path to the world configuration YAML file. + disable_plotting (bool): If True, disables rendering and plotting. + """ + display = False if disable_plotting else True + self.env = irsim.make( + world_file, disable_all_plot=disable_plotting, display=display + ) + robot_info = self.env.get_robot_info(0) + self.robot_goal = robot_info.goal + self.num_robots = len(self.env.robot_list) + self.x_range = self.env._world.x_range + self.y_range = self.env._world.y_range + + def step(self, action, connection, combined_weights=None): + """ + Perform one step in the simulation using the given control commands. + + Args: + lin_velocity (float): Linear velocity to apply to the robot. + ang_velocity (float): Angular velocity to apply to the robot. + + Returns: + (tuple): Contains the latest LIDAR scan, distance to goal, cosine and sine of angle to goal, + collision flag, goal reached flag, applied action, and computed reward. + """ + self.env.step(action_id=[i for i in range(self.num_robots)], action=action) + self.env.render() + + poses = [] + distances = [] + coss = [] + sins = [] + collisions = [] + goals = [] + rewards = [] + positions = [] + goal_positions = [] + robot_states = [ + [self.env.robot_list[i].state[0], self.env.robot_list[i].state[1]] + for i in range(self.num_robots) + ] + for i in range(self.num_robots): + + robot_state = self.env.robot_list[i].state + closest_robots = [ + np.linalg.norm( + [ + robot_states[j][0] - robot_state[0], + robot_states[j][1] - robot_state[1], + ] + ) + for j in range(self.num_robots) + if j != i + ] + robot_goal = self.env.robot_list[i].goal + goal_vector = [ + robot_goal[0].item() - robot_state[0].item(), + robot_goal[1].item() - robot_state[1].item(), + ] + distance = np.linalg.norm(goal_vector) + goal = self.env.robot_list[i].arrive + pose_vector = [np.cos(robot_state[2]).item(), np.sin(robot_state[2]).item()] + cos, sin = self.cossin(pose_vector, goal_vector) + collision = self.env.robot_list[i].collision + action_i = action[i] + reward = self.get_reward( + goal, collision, action_i, closest_robots, distance + ) + + position = [robot_state[0].item(), robot_state[1].item()] + goal_position = [robot_goal[0].item(), robot_goal[1].item()] + + distances.append(distance) + coss.append(cos) + sins.append(sin) + collisions.append(collision) + goals.append(goal) + rewards.append(reward) + positions.append(position) + poses.append( + [robot_state[0].item(), robot_state[1].item(), robot_state[2].item()] + ) + goal_positions.append(goal_position) + + i_probs = torch.sigmoid( + connection[i] + ) # connection[i] is logits for "connect" per pair + + # Now we need to insert the self-connection (optional, typically skipped) + i_connections = i_probs.tolist() + i_connections.insert(i, 0) + if combined_weights is not None: + i_weights = combined_weights[i].tolist() + i_weights.insert(i, 0) + + for j in range(self.num_robots): + if i_connections[j] > 0.5: + if combined_weights is not None: + weight = i_weights[j] + else: + weight = 1 + other_robot_state = self.env.robot_list[j].state + other_pos = [ + other_robot_state[0].item(), + other_robot_state[1].item(), + ] + rx = [position[0], other_pos[0]] + ry = [position[1], other_pos[1]] + self.env.draw_trajectory( + np.array([rx, ry]), refresh=True, linewidth=weight + ) + + if goal: + self.env.robot_list[i].set_random_goal( + obstacle_list=self.env.obstacle_list, + init=True, + range_limits=[ + [self.x_range[0] + 1, self.y_range[0] + 1, -3.141592653589793], + [self.x_range[1] - 1, self.y_range[1] - 1, 3.141592653589793], + ], + ) + + return ( + poses, + distances, + coss, + sins, + collisions, + goals, + action, + rewards, + positions, + goal_positions, + ) + + def reset( + self, + robot_state=None, + robot_goal=None, + random_obstacles=False, + random_obstacle_ids=None, + ): + """ + Reset the simulation environment, optionally setting robot and obstacle states. + + Args: + robot_state (list or None): Initial state of the robot as a list of [x, y, theta, speed]. + robot_goal (list or None): Goal state for the robot. + random_obstacles (bool): Whether to randomly reposition obstacles. + random_obstacle_ids (list or None): Specific obstacle IDs to randomize. + + Returns: + (tuple): Initial observation after reset, including LIDAR scan, distance, cos/sin, + and reward-related flags and values. + """ + if robot_state is None: + robot_state = [[random.uniform(3, 9)], [random.uniform(3, 9)], [0]] + + init_states = [] + for robot in self.env.robot_list: + conflict = True + while conflict: + conflict = False + robot_state = [ + [random.uniform(3, 9)], + [random.uniform(3, 9)], + [random.uniform(-3.14, 3.14)], + ] + pos = [robot_state[0][0], robot_state[1][0]] + for loc in init_states: + vector = [ + pos[0] - loc[0], + pos[1] - loc[1], + ] + if np.linalg.norm(vector) < 0.6: + conflict = True + init_states.append(pos) + + robot.set_state( + state=np.array(robot_state), + init=True, + ) + + if random_obstacles: + if random_obstacle_ids is None: + random_obstacle_ids = [i + self.num_robots for i in range(7)] + self.env.random_obstacle_position( + range_low=[self.x_range[0], self.y_range[0], -3.14], + range_high=[self.x_range[1], self.y_range[1], 3.14], + ids=random_obstacle_ids, + non_overlapping=True, + ) + + for robot in self.env.robot_list: + if robot_goal is None: + robot.set_random_goal( + obstacle_list=self.env.obstacle_list, + init=True, + range_limits=[ + [self.x_range[0] + 1, self.y_range[0] + 1, -3.141592653589793], + [self.x_range[1] - 1, self.y_range[1] - 1, 3.141592653589793], + ], + ) + else: + self.env.robot.set_goal(np.array(robot_goal), init=True) + self.env.reset() + self.robot_goal = self.env.robot.goal + + action = [[0.0, 0.0] for _ in range(self.num_robots)] + con = torch.tensor( + [[0.0 for _ in range(self.num_robots - 1)] for _ in range(self.num_robots)] + ) + poses, distance, cos, sin, _, _, action, reward, positions, goal_positions = ( + self.step(action, con) + ) + return ( + poses, + distance, + cos, + sin, + [False] * self.num_robots, + [False] * self.num_robots, + action, + reward, + positions, + goal_positions, + ) + + @staticmethod + def get_reward(goal, collision, action, closest_robots, distance, phase=1): + """ + Calculate the reward for the current step. + + Args: + goal (bool): Whether the goal has been reached. + collision (bool): Whether a collision occurred. + action (list): The action taken [linear velocity, angular velocity]. + closest_robots (list): Distances to the closest robots. + distance (float): Distance to goal. + phase (int, optional): Reward function phase. Defaults to 1. + + Returns: + (float): Computed reward for the current state. + """ + + match phase: + case 1: + if goal: + return 100.0 + elif collision: + return -100.0 * 3 * action[0] + else: + r_dist = 1.5 / distance + cl_pen = 0 + for rob in closest_robots: + add = 1.5 - rob if rob < 1.5 else 0 + cl_pen += add + + return action[0] - 0.5 * abs(action[1]) - cl_pen + r_dist + + case 2: + if goal: + return 70.0 + elif collision: + return -100.0 * 3 * action[0] + else: + cl_pen = 0 + for rob in closest_robots: + add = (3 - rob) ** 2 if rob < 3 else 0 + cl_pen += add + + return -0.5 * abs(action[1]) - cl_pen + + case _: + raise ValueError("Unknown reward phase") diff --git a/robot_nav/sim.py b/robot_nav/SIM_ENV/sim.py similarity index 89% rename from robot_nav/sim.py rename to robot_nav/SIM_ENV/sim.py index adfe7f0..c341373 100644 --- a/robot_nav/sim.py +++ b/robot_nav/SIM_ENV/sim.py @@ -2,11 +2,10 @@ import numpy as np import random -import shapely -from irsim.lib.handler.geometry_handler import GeometryFactory +from robot_nav.SIM_ENV.sim_env import SIM_ENV -class SIM_ENV: +class SIM(SIM_ENV): """ A simulation environment interface for robot navigation using IRSim. @@ -121,24 +120,6 @@ def reset( ) return latest_scan, distance, cos, sin, False, False, action, reward - @staticmethod - def cossin(vec1, vec2): - """ - Compute the cosine and sine of the angle between two 2D vectors. - - Args: - vec1 (list): First 2D vector. - vec2 (list): Second 2D vector. - - Returns: - (tuple): (cosine, sine) of the angle between the vectors. - """ - vec1 = vec1 / np.linalg.norm(vec1) - vec2 = vec2 / np.linalg.norm(vec2) - cos = np.dot(vec1, vec2) - sin = vec1[0] * vec2[1] - vec1[1] * vec2[0] - return cos, sin - @staticmethod def get_reward(goal, collision, action, laser_scan): """ diff --git a/robot_nav/SIM_ENV/sim_env.py b/robot_nav/SIM_ENV/sim_env.py new file mode 100644 index 0000000..4cd6f77 --- /dev/null +++ b/robot_nav/SIM_ENV/sim_env.py @@ -0,0 +1,35 @@ +from abc import ABC, abstractmethod +import numpy as np + + +class SIM_ENV(ABC): + @abstractmethod + def step(self): + raise NotImplementedError("step method must be implemented by subclass.") + + @abstractmethod + def reset(self): + raise NotImplementedError("reset method must be implemented by subclass.") + + @staticmethod + def cossin(vec1, vec2): + """ + Compute the cosine and sine of the angle between two 2D vectors. + + Args: + vec1 (list): First 2D vector. + vec2 (list): Second 2D vector. + + Returns: + (tuple): (cosine, sine) of the angle between the vectors. + """ + vec1 = vec1 / np.linalg.norm(vec1) + vec2 = vec2 / np.linalg.norm(vec2) + cos = np.dot(vec1, vec2) + sin = vec1[0] * vec2[1] - vec1[1] * vec2[0] + return cos, sin + + @staticmethod + @abstractmethod + def get_reward(): + raise NotImplementedError("get_reward method must be implemented by subclass.") diff --git a/robot_nav/models/CNNTD3/CNNTD3.py b/robot_nav/models/CNNTD3/CNNTD3.py index 0be20da..af6cf45 100644 --- a/robot_nav/models/CNNTD3/CNNTD3.py +++ b/robot_nav/models/CNNTD3/CNNTD3.py @@ -325,8 +325,8 @@ def train( state = torch.Tensor(batch_states).to(self.device) next_state = torch.Tensor(batch_next_states).to(self.device) action = torch.Tensor(batch_actions).to(self.device) - reward = torch.Tensor(batch_rewards).to(self.device) - done = torch.Tensor(batch_dones).to(self.device) + reward = torch.Tensor(batch_rewards).to(self.device).reshape(-1, 1) + done = torch.Tensor(batch_dones).to(self.device).reshape(-1, 1) # Obtain the estimated action from the next state by using the actor-target next_action = self.actor_target(next_state) diff --git a/robot_nav/models/DDPG/DDPG.py b/robot_nav/models/DDPG/DDPG.py index 0f9b98b..2b3abfe 100644 --- a/robot_nav/models/DDPG/DDPG.py +++ b/robot_nav/models/DDPG/DDPG.py @@ -254,8 +254,8 @@ def train( state = torch.Tensor(batch_states).to(self.device) next_state = torch.Tensor(batch_next_states).to(self.device) action = torch.Tensor(batch_actions).to(self.device) - reward = torch.Tensor(batch_rewards).to(self.device) - done = torch.Tensor(batch_dones).to(self.device) + reward = torch.Tensor(batch_rewards).to(self.device).reshape(-1, 1) + done = torch.Tensor(batch_dones).to(self.device).reshape(-1, 1) # Obtain the estimated action from the next state by using the actor-target next_action = self.actor_target(next_state) diff --git a/robot_nav/models/MARL/hardsoftAttention.py b/robot_nav/models/MARL/hardsoftAttention.py new file mode 100644 index 0000000..4978a9f --- /dev/null +++ b/robot_nav/models/MARL/hardsoftAttention.py @@ -0,0 +1,222 @@ +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class Attention(nn.Module): + def __init__(self, embedding_dim): + super(Attention, self).__init__() + self.embedding_dim = embedding_dim + + self.embedding1 = nn.Linear(5, 128) + nn.init.kaiming_uniform_(self.embedding1.weight, nonlinearity="leaky_relu") + self.embedding2 = nn.Linear(128, embedding_dim) + nn.init.kaiming_uniform_(self.embedding2.weight, nonlinearity="leaky_relu") + + # Hard attention MLP with distance + self.hard_mlp = nn.Sequential( + nn.Linear(embedding_dim + 7, embedding_dim), + nn.ReLU(), + nn.Linear(embedding_dim, embedding_dim), + ) + self.hard_encoding = nn.Linear(embedding_dim, 2) + + # Soft attention projections + self.q = nn.Linear(embedding_dim, embedding_dim, bias=False) + self.k = nn.Linear(10, embedding_dim, bias=False) + self.v = nn.Linear(10, embedding_dim) + + # Soft attention score network (with polar other robot goal position) + self.attn_score_layer = nn.Sequential( + nn.Linear(embedding_dim * 2, embedding_dim), + nn.ReLU(), + nn.Linear(embedding_dim, 1), + ) + + # Decoder + self.decode_1 = nn.Linear(embedding_dim * 2, embedding_dim * 2) + nn.init.kaiming_uniform_(self.decode_1.weight, nonlinearity="leaky_relu") + self.decode_2 = nn.Linear(embedding_dim * 2, embedding_dim * 2) + nn.init.kaiming_uniform_(self.decode_2.weight, nonlinearity="leaky_relu") + + def encode_agent_features(self, embed): + embed = F.leaky_relu(self.embedding1(embed)) + embed = F.leaky_relu(self.embedding2(embed)) + return embed + + def forward(self, embedding): + if embedding.dim() == 2: + embedding = embedding.unsqueeze(0) + batch_size, n_agents, _ = embedding.shape + + embed = embedding[:, :, 4:9].reshape(batch_size * n_agents, -1) + position = embedding[:, :, :2].reshape(batch_size, n_agents, 2) + heading = embedding[:, :, 2:4].reshape( + batch_size, n_agents, 2 + ) # assume (cos(θ), sin(θ)) + action = embedding[:, :, 7:9].reshape(batch_size, n_agents, 2) + goal = embedding[:, :, -2:].reshape(batch_size, n_agents, 2) + goal_j = goal.unsqueeze(1).expand(-1, n_agents, -1, -1) + pos_i = position.unsqueeze(2) + goal_rel_vec = goal_j - pos_i + + agent_embed = self.encode_agent_features(embed) + agent_embed = agent_embed.view(batch_size, n_agents, self.embedding_dim) + + # For hard attention + h_i = agent_embed.unsqueeze(2) # (B, N, 1, D) + pos_i = position.unsqueeze(2) # (B, N, 1, 2) + pos_j = position.unsqueeze(1) # (B, 1, N, 2) + heading_i = heading.unsqueeze(2) # (B, N, 1, 2) + heading_j = heading.unsqueeze(1).expand(-1, n_agents, -1, -1) # (B, 1, N, 2) + + # Compute relative vectors and distance + rel_vec = pos_j - pos_i # (B, N, N, 2) + dx, dy = rel_vec[..., 0], rel_vec[..., 1] + rel_dist = ( + torch.linalg.vector_norm(rel_vec, dim=-1, keepdim=True) / 12 + ) # (B, N, N, 1) + + # Relative angle in agent i's frame + angle = torch.atan2(dy, dx) - torch.atan2(heading_i[..., 1], heading_i[..., 0]) + angle = (angle + np.pi) % (2 * np.pi) - np.pi + rel_angle_sin = torch.sin(angle) + rel_angle_cos = torch.cos(angle) + + # Other agent's heading + heading_j_cos = heading_j[..., 0] # (B, 1, N) + heading_j_sin = heading_j[..., 1] # (B, 1, N) + + # Stack edge features + edge_features = torch.cat( + [ + rel_dist, # (B, N, N, 1) + rel_angle_cos.unsqueeze(-1), # (B, N, N, 1) + rel_angle_sin.unsqueeze(-1), # (B, N, N, 1) + heading_j_cos.unsqueeze(-1), # (B, N, N, 1) + heading_j_sin.unsqueeze(-1), # (B, N, N, 1) + action.unsqueeze(1).expand(-1, n_agents, -1, -1), # (B, N, N, 2) + ], + dim=-1, + ) + + # Broadcast h_i along N (for each pair) + h_i_expanded = h_i.expand(-1, -1, n_agents, -1) + + # Remove self-pairs using mask + mask = ~torch.eye(n_agents, dtype=torch.bool, device=embedding.device) + h_i_flat = h_i_expanded[:, mask].reshape( + batch_size * n_agents, n_agents - 1, self.embedding_dim + ) + edge_flat = edge_features[:, mask].reshape( + batch_size * n_agents, n_agents - 1, -1 + ) + + # Concatenate agent embedding and edge features + hard_input = torch.cat([h_i_flat, edge_flat], dim=-1) + + # Hard attention forward + h_hard = self.hard_mlp(hard_input) + hard_logits = self.hard_encoding(h_hard) + hard_weights = F.gumbel_softmax(hard_logits, hard=False, tau=0.5, dim=-1)[ + ..., 1 + ].unsqueeze(2) + hard_weights = hard_weights.view(batch_size, n_agents, n_agents - 1) + + unnorm_rel_dist = torch.linalg.vector_norm(rel_vec, dim=-1, keepdim=True) + unnorm_rel_dist = unnorm_rel_dist[:, mask].reshape( + batch_size * n_agents, n_agents - 1, 1 + ) + + # Soft attention + q = self.q(agent_embed) + + attention_outputs = [] + entropy_list = [] + combined_w = [] + + goal_rel_dist = torch.linalg.vector_norm(goal_rel_vec, dim=-1, keepdim=True) + goal_angle_global = torch.atan2(goal_rel_vec[..., 1], goal_rel_vec[..., 0]) + heading_angle = torch.atan2(heading_i[..., 1], heading_i[..., 0]) + goal_rel_angle = goal_angle_global - heading_angle + goal_rel_angle = (goal_rel_angle + np.pi) % (2 * np.pi) - np.pi + goal_rel_angle_cos = torch.cos(goal_rel_angle).unsqueeze(-1) + goal_rel_angle_sin = torch.sin(goal_rel_angle).unsqueeze(-1) + goal_polar = torch.cat( + [goal_rel_dist, goal_rel_angle_cos, goal_rel_angle_sin], dim=-1 + ) + + soft_edge_features = torch.cat([edge_features, goal_polar], dim=-1) + for i in range(n_agents): + q_i = q[:, i : i + 1, :] + mask = torch.ones(n_agents, dtype=torch.bool, device=edge_features.device) + mask[i] = False + edge_i_wo_self = soft_edge_features[:, i, mask, :] + edge_i_wo_self = edge_i_wo_self.squeeze(1) + k = F.leaky_relu(self.k(edge_i_wo_self)) + + q_i_expanded = q_i.expand(-1, n_agents - 1, -1) + attention_input = torch.cat([q_i_expanded, k], dim=-1) + + # Score computation + scores = self.attn_score_layer(attention_input).transpose(1, 2) + + # Mask using hard weights + h_weights = hard_weights[:, i].unsqueeze(1) + mask = (h_weights > 0.5).float() + + # All-zero mask handling + epsilon = 1e-6 + mask_sum = mask.sum(dim=-1, keepdim=True) + all_zero_mask = mask_sum < epsilon + + # Apply mask to scores + masked_scores = scores.masked_fill(mask == 0, float("-inf")) + + # Compute softmax, safely handle all-zero cases + soft_weights = F.softmax(masked_scores, dim=-1) + soft_weights = torch.where( + all_zero_mask, torch.zeros_like(soft_weights), soft_weights + ) + + combined_weights = soft_weights * mask # (B, 1, N-1) + combined_w.append(combined_weights) + + # Normalize combined_weights for entropy calculation + combined_weights_norm = combined_weights / ( + combined_weights.sum(dim=-1, keepdim=True) + epsilon + ) + + # Calculate entropy from combined_weights + entropy = ( + -(combined_weights_norm * (combined_weights_norm + epsilon).log()) + .sum(dim=-1) + .mean() + ) + entropy_list.append(entropy) + + v_j = F.leaky_relu(self.v(edge_i_wo_self)) + attn_output = torch.bmm(combined_weights, v_j).squeeze(1) + attention_outputs.append(attn_output) + + comb_w = torch.stack(combined_w, dim=1).reshape(n_agents, -1) + attn_stack = torch.stack(attention_outputs, dim=1).reshape( + -1, self.embedding_dim + ) + self_embed = agent_embed.reshape(-1, self.embedding_dim) + concat_embed = torch.cat([self_embed, attn_stack], dim=-1) + + x = F.leaky_relu(self.decode_1(concat_embed)) + att_embedding = F.leaky_relu(self.decode_2(x)) + + mean_entropy = torch.stack(entropy_list).mean() + + return ( + att_embedding, + hard_logits[..., 1], + unnorm_rel_dist, + mean_entropy, + hard_weights, + comb_w, + ) diff --git a/robot_nav/models/MARL/marlTD3.py b/robot_nav/models/MARL/marlTD3.py new file mode 100644 index 0000000..9fd4508 --- /dev/null +++ b/robot_nav/models/MARL/marlTD3.py @@ -0,0 +1,506 @@ +from pathlib import Path + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from numpy import inf +from torch.utils.tensorboard import SummaryWriter + +from robot_nav.models.MARL.hardsoftAttention import Attention + + +class Actor(nn.Module): + def __init__(self, action_dim, embedding_dim): + super().__init__() + self.attention = Attention(embedding_dim) # ➊ edge classifier + + # ➋ policy head (everything _after_ attention) + self.policy_head = nn.Sequential( + nn.Linear(embedding_dim * 2, 400), + nn.LeakyReLU(), + nn.Linear(400, 300), + nn.LeakyReLU(), + nn.Linear(300, action_dim), + nn.Tanh(), + ) + + def forward(self, obs, detach_attn=False): + attn_out, hard_logits, pair_d, mean_entropy, hard_weights, combined_weights = ( + self.attention(obs) + ) + if detach_attn: # used in the policy phase + attn_out = attn_out.detach() + action = self.policy_head(attn_out) + return action, hard_logits, pair_d, mean_entropy, hard_weights, combined_weights + + +class Critic(nn.Module): + def __init__(self, action_dim, embedding_dim): + super(Critic, self).__init__() + self.embedding_dim = embedding_dim + self.attention = Attention(self.embedding_dim) + + self.layer_1 = nn.Linear(self.embedding_dim * 2, 400) + torch.nn.init.kaiming_uniform_(self.layer_1.weight, nonlinearity="leaky_relu") + + self.layer_2_s = nn.Linear(400, 300) + torch.nn.init.kaiming_uniform_(self.layer_2_s.weight, nonlinearity="leaky_relu") + + self.layer_2_a = nn.Linear(action_dim, 300) + torch.nn.init.kaiming_uniform_(self.layer_2_a.weight, nonlinearity="leaky_relu") + + self.layer_3 = nn.Linear(300, 1) + torch.nn.init.kaiming_uniform_(self.layer_3.weight, nonlinearity="leaky_relu") + + self.layer_4 = nn.Linear(self.embedding_dim * 2, 400) + torch.nn.init.kaiming_uniform_( + self.layer_4.weight, nonlinearity="leaky_relu" + ) # ✅ Fixed init bug + + self.layer_5_s = nn.Linear(400, 300) + torch.nn.init.kaiming_uniform_(self.layer_5_s.weight, nonlinearity="leaky_relu") + + self.layer_5_a = nn.Linear(action_dim, 300) + torch.nn.init.kaiming_uniform_(self.layer_5_a.weight, nonlinearity="leaky_relu") + + self.layer_6 = nn.Linear(300, 1) + torch.nn.init.kaiming_uniform_(self.layer_6.weight, nonlinearity="leaky_relu") + + def forward(self, embedding, action): + + ( + embedding_with_attention, + hard_logits, + unnorm_rel_dist, + mean_entropy, + hard_weights, + _, + ) = self.attention(embedding) + + # Q1 + s1 = F.leaky_relu(self.layer_1(embedding_with_attention)) + s1 = F.leaky_relu(self.layer_2_s(s1) + self.layer_2_a(action)) # ✅ No .data + q1 = self.layer_3(s1) + + # Q2 + s2 = F.leaky_relu(self.layer_4(embedding_with_attention)) + s2 = F.leaky_relu(self.layer_5_s(s2) + self.layer_5_a(action)) # ✅ No .data + q2 = self.layer_6(s2) + + return q1, q2, mean_entropy, hard_logits, unnorm_rel_dist, hard_weights + + +class TD3(object): + """ + CNNTD3 (Twin Delayed Deep Deterministic Policy Gradient with CNN-based inputs) agent for + continuous control tasks. + + This class encapsulates the full implementation of the TD3 algorithm using neural network + architectures for the actor and critic, with optional bounding for critic outputs to + regularize learning. The agent is designed to train in environments where sensor + observations (e.g., LiDAR) are used for navigation tasks. + + Args: + state_dim (int): Dimension of the input state. + action_dim (int): Dimension of the output action. + max_action (float): Maximum magnitude of the action. + device (torch.device): Torch device to use (CPU or GPU). + lr (float): Learning rate for both actor and critic optimizers. + save_every (int): Save model every N training iterations (0 to disable). + load_model (bool): Whether to load a pre-trained model at initialization. + save_directory (Path): Path to the directory for saving model checkpoints. + model_name (str): Base name for the saved model files. + load_directory (Path): Path to load model checkpoints from (if `load_model=True`). + use_max_bound (bool): Whether to apply maximum Q-value bounding during training. + bound_weight (float): Weight for the bounding loss term in total loss. + """ + + def __init__( + self, + state_dim, + action_dim, + max_action, + device, + num_robots, + lr_actor=1e-4, + lr_critic=3e-4, + save_every=0, + load_model=False, + save_directory=Path("robot_nav/models/MARL/checkpoint"), + model_name="marlTD3", + load_model_name=None, + load_directory=Path("robot_nav/models/MARL/checkpoint"), + ): + # Initialize the Actor network + self.num_robots = num_robots + self.device = device + self.actor = Actor(action_dim, embedding_dim=256).to( + self.device + ) # Using the updated Actor + self.actor_target = Actor(action_dim, embedding_dim=256).to(self.device) + self.actor_target.load_state_dict(self.actor.state_dict()) + + self.attn_params = list(self.actor.attention.parameters()) + self.policy_params = list(self.actor.policy_head.parameters()) + + self.actor_optimizer = torch.optim.Adam( + self.policy_params + self.attn_params, lr=lr_actor + ) # TD3 policy + + self.critic = Critic(action_dim, embedding_dim=256).to( + self.device + ) # Using the updated Critic + self.critic_target = Critic(action_dim, embedding_dim=256).to(self.device) + self.critic_target.load_state_dict(self.critic.state_dict()) + self.critic_optimizer = torch.optim.Adam( + params=self.critic.parameters(), lr=lr_critic + ) + self.action_dim = action_dim + self.max_action = max_action + self.state_dim = state_dim + self.writer = SummaryWriter(comment=model_name) + self.iter_count = 0 + if load_model_name is None: + load_model_name = model_name + if load_model: + self.load(filename=load_model_name, directory=load_directory) + self.save_every = save_every + self.model_name = model_name + self.save_directory = save_directory + + def get_action(self, obs, add_noise): + """ + Selects an action for a given observation. + + Args: + obs (np.ndarray): The current observation/state. + add_noise (bool): Whether to add exploration noise to the action. + + Returns: + (np.ndarray): The selected action. + """ + action, connection, combined_weights = self.act(obs) + if add_noise: + noise = np.random.normal(0, 0.5, size=action.shape) + noise = [n / 4 if i % 2 else n for i, n in enumerate(noise)] + action = (action + noise).clip(-self.max_action, self.max_action) + + return action.reshape(-1, 2), connection, combined_weights + + def act(self, state): + """ + Computes the deterministic action from the actor network for a given state. + + Args: + state (np.ndarray): Input state. + + Returns: + (np.ndarray): Action predicted by the actor network. + """ + # Function to get the action from the actor + state = torch.Tensor(state).to(self.device) + # res = self.attention(state) + action, connection, _, _, _, combined_weights = self.actor(state) + return action.cpu().data.numpy().flatten(), connection, combined_weights + + # training cycle + def train( + self, + replay_buffer, + iterations, + batch_size, + discount=0.99, + tau=0.005, + policy_noise=0.2, + noise_clip=0.5, + policy_freq=2, + bce_weight=0.1, + entropy_weight=1, + connection_proximity_threshold=4, + ): + av_Q = 0 + max_Q = -inf + av_loss = 0 + av_critic_loss = 0 + av_critic_entropy = [] + av_actor_entropy = [] + av_actor_loss = 0 + av_critic_bce_loss = [] + av_actor_bce_loss = [] + + for it in range(iterations): + # sample a batch + ( + batch_states, + batch_actions, + batch_rewards, + batch_dones, + batch_next_states, + ) = replay_buffer.sample_batch(batch_size) + + state = ( + torch.Tensor(batch_states) + .to(self.device) + .view(batch_size, self.num_robots, self.state_dim) + ) + next_state = ( + torch.Tensor(batch_next_states) + .to(self.device) + .view(batch_size, self.num_robots, self.state_dim) + ) + action = ( + torch.Tensor(batch_actions) + .to(self.device) + .view(batch_size * self.num_robots, self.action_dim) + ) + reward = ( + torch.Tensor(batch_rewards) + .to(self.device) + .view(batch_size * self.num_robots, 1) + ) + done = ( + torch.Tensor(batch_dones) + .to(self.device) + .view(batch_size * self.num_robots, 1) + ) + + with torch.no_grad(): + next_action, _, _, _, _, _ = self.actor_target( + next_state, detach_attn=True + ) + + # --- Target smoothing --- + noise = ( + torch.Tensor(batch_actions) + .data.normal_(0, policy_noise) + .to(self.device) + ).reshape(-1, 2) + noise = noise.clamp(-noise_clip, noise_clip) + next_action = (next_action + noise).clamp(-self.max_action, self.max_action) + + # --- Target Q values --- + target_Q1, target_Q2, _, _, _, _ = self.critic_target( + next_state, next_action + ) + target_Q = torch.min(target_Q1, target_Q2) + av_Q += target_Q.mean() + max_Q = max(max_Q, target_Q.max().item()) + target_Q = reward + ((1 - done) * discount * target_Q).detach() + + # --- Critic update --- + ( + current_Q1, + current_Q2, + mean_entropy, + hard_logits, + unnorm_rel_dist, + hard_weights, + ) = self.critic(state, action) + critic_loss = F.mse_loss(current_Q1, target_Q) + F.mse_loss( + current_Q2, target_Q + ) + + targets = ( + unnorm_rel_dist.flatten() < connection_proximity_threshold + ).float() + flat_logits = hard_logits.flatten() + bce_loss = F.binary_cross_entropy_with_logits(flat_logits, targets) + + av_critic_bce_loss.append(bce_loss) + + total_loss = ( + critic_loss - entropy_weight * mean_entropy + bce_weight * bce_loss + ) + av_critic_entropy.append(mean_entropy) + + self.critic_optimizer.zero_grad() + total_loss.backward() + torch.nn.utils.clip_grad_norm_(self.critic.parameters(), 10.0) + self.critic_optimizer.step() + + av_loss += total_loss.item() + av_critic_loss += critic_loss.item() + + # --- Actor update --- + if it % policy_freq == 0: + + action, hard_logits, unnorm_rel_dist, mean_entropy, hard_weights, _ = ( + self.actor(state, detach_attn=False) + ) + targets = ( + unnorm_rel_dist.flatten() < connection_proximity_threshold + ).float() + flat_logits = hard_logits.flatten() + bce_loss = F.binary_cross_entropy_with_logits(flat_logits, targets) + + av_actor_bce_loss.append(bce_loss) + + actor_Q, _, _, _, _, _ = self.critic(state, action) + actor_loss = -actor_Q.mean() + total_loss = ( + actor_loss - entropy_weight * mean_entropy + bce_weight * bce_loss + ) + av_actor_entropy.append(mean_entropy) + + self.actor_optimizer.zero_grad() + total_loss.backward() + torch.nn.utils.clip_grad_norm_(self.policy_params, 10.0) + self.actor_optimizer.step() + + av_actor_loss += total_loss.item() + + # Soft update target networks + for param, target_param in zip( + self.actor.parameters(), self.actor_target.parameters() + ): + target_param.data.copy_( + tau * param.data + (1 - tau) * target_param.data + ) + + for param, target_param in zip( + self.critic.parameters(), self.critic_target.parameters() + ): + target_param.data.copy_( + tau * param.data + (1 - tau) * target_param.data + ) + + self.iter_count += 1 + self.writer.add_scalar( + "train/loss_total", av_loss / iterations, self.iter_count + ) + self.writer.add_scalar( + "train/critic_loss", av_critic_loss / iterations, self.iter_count + ) + self.writer.add_scalar( + "train/av_critic_entropy", + sum(av_critic_entropy) / len(av_critic_entropy), + self.iter_count, + ) + self.writer.add_scalar( + "train/av_actor_entropy", + sum(av_actor_entropy) / len(av_actor_entropy), + self.iter_count, + ) + self.writer.add_scalar( + "train/av_critic_bce_loss", + sum(av_critic_bce_loss) / len(av_critic_bce_loss), + self.iter_count, + ) + self.writer.add_scalar( + "train/av_actor_bce_loss", + sum(av_actor_bce_loss) / len(av_actor_bce_loss), + self.iter_count, + ) + self.writer.add_scalar("train/avg_Q", av_Q / iterations, self.iter_count) + self.writer.add_scalar("train/max_Q", max_Q, self.iter_count) + + self.writer.add_scalar( + "train/actor_loss", + av_actor_loss / (iterations // policy_freq), + self.iter_count, + ) + + if self.save_every > 0 and self.iter_count % self.save_every == 0: + self.save(filename=self.model_name, directory=self.save_directory) + + def save(self, filename, directory): + """ + Saves the current model parameters to the specified directory. + + Args: + filename (str): Base filename for saved files. + directory (Path): Path to save the model files. + """ + Path(directory).mkdir(parents=True, exist_ok=True) + torch.save(self.actor.state_dict(), "%s/%s_actor.pth" % (directory, filename)) + torch.save( + self.actor_target.state_dict(), + "%s/%s_actor_target.pth" % (directory, filename), + ) + torch.save(self.critic.state_dict(), "%s/%s_critic.pth" % (directory, filename)) + torch.save( + self.critic_target.state_dict(), + "%s/%s_critic_target.pth" % (directory, filename), + ) + + def load(self, filename, directory): + """ + Loads model parameters from the specified directory. + + Args: + filename (str): Base filename for saved files. + directory (Path): Path to load the model files from. + """ + self.actor.load_state_dict( + torch.load("%s/%s_actor.pth" % (directory, filename)) + ) + self.actor_target.load_state_dict( + torch.load("%s/%s_actor_target.pth" % (directory, filename)) + ) + self.critic.load_state_dict( + torch.load("%s/%s_critic.pth" % (directory, filename)) + ) + self.critic_target.load_state_dict( + torch.load("%s/%s_critic_target.pth" % (directory, filename)) + ) + print(f"Loaded weights from: {directory}") + + def prepare_state( + self, poses, distance, cos, sin, collision, action, goal_positions + ): + """ + Prepares the environment's raw agent state for learning. + + Args: + poses (list): Each agent's global pose [x, y, theta]. + distance, cos, sin: Unused, can be removed or ignored. + collision (list): Collision flags per agent. + action (list): Last action taken [lin_vel, ang_vel]. + goal_positions (list): Each agent's goal [x, y]. + + Returns: + states (list): List of processed state vectors. + terminal (list): Flags (1 if collision or goal reached, else 0). + """ + states = [] + terminal = [] + + for i in range(self.num_robots): + pose = poses[i] # [x, y, theta] + goal_pos = goal_positions[i] # [goal_x, goal_y] + act = action[i] # [lin_vel, ang_vel] + + px, py, theta = pose + gx, gy = goal_pos + + # Heading as cos/sin + heading_cos = np.cos(theta) + heading_sin = np.sin(theta) + + # Last velocity + lin_vel = act[0] * 2 # Assuming original range [0, 0.5] + ang_vel = (act[1] + 1) / 2 # Assuming original range [-1, 1] + + # Final state vector + state = [ + px, + py, + heading_cos, + heading_sin, + distance[i] / 17, + cos[i], + sin[i], + lin_vel, + ang_vel, + gx, + gy, + ] + + assert ( + len(state) == self.state_dim + ), f"State length mismatch: expected {self.state_dim}, got {len(state)}" + states.append(state) + terminal.append(collision[i]) + + return states, terminal diff --git a/robot_nav/models/SAC/SAC.py b/robot_nav/models/SAC/SAC.py index 10639ae..157e1d3 100644 --- a/robot_nav/models/SAC/SAC.py +++ b/robot_nav/models/SAC/SAC.py @@ -346,8 +346,8 @@ def update(self, replay_buffer, step, batch_size): state = torch.Tensor(batch_states).to(self.device) next_state = torch.Tensor(batch_next_states).to(self.device) action = torch.Tensor(batch_actions).to(self.device) - reward = torch.Tensor(batch_rewards).to(self.device) - done = torch.Tensor(batch_dones).to(self.device) + reward = torch.Tensor(batch_rewards).to(self.device).reshape(-1, 1) + done = torch.Tensor(batch_dones).to(self.device).reshape(-1, 1) self.train_metrics_dict["train/batch_reward_av"].append( batch_rewards.mean().item() ) diff --git a/robot_nav/models/TD3/TD3.py b/robot_nav/models/TD3/TD3.py index c35943b..8c66666 100644 --- a/robot_nav/models/TD3/TD3.py +++ b/robot_nav/models/TD3/TD3.py @@ -268,8 +268,8 @@ def train( state = torch.Tensor(batch_states).to(self.device) next_state = torch.Tensor(batch_next_states).to(self.device) action = torch.Tensor(batch_actions).to(self.device) - reward = torch.Tensor(batch_rewards).to(self.device) - done = torch.Tensor(batch_dones).to(self.device) + reward = torch.Tensor(batch_rewards).to(self.device).reshape(-1, 1) + done = torch.Tensor(batch_dones).to(self.device).reshape(-1, 1) # Obtain the estimated action from the next state by using the actor-target next_action = self.actor_target(next_state) @@ -445,7 +445,7 @@ def prepare_state(self, latest_scan, distance, cos, sin, collision, goal, action ang_vel = (action[1] + 1) / 2 state = min_values + [distance, cos, sin] + [lin_vel, ang_vel] - assert len(state) == self.state_dim + assert len(state) == self.state_dim, f"{len(state), self.state_dim}" terminal = 1 if collision or goal else 0 return state, terminal diff --git a/robot_nav/multi_robot_world.yaml b/robot_nav/multi_robot_world.yaml new file mode 100644 index 0000000..66e31ec --- /dev/null +++ b/robot_nav/multi_robot_world.yaml @@ -0,0 +1,22 @@ +world: + height: 12 # the height of the world + width: 12 # the height of the world + step_time: 0.3 # Calculate each step + sample_time: 0.3 # For render and data extraction + collision_mode: 'reactive' + +robot: + - number: 5 + kinematics: {name: 'diff'} + distribution: {name: 'circle', radius: 4, center: [6, 6]} + shape: {name: 'circle', radius: 0.2} + vel_min: [ 0, -1.0 ] + vel_max: [ 1.0, 1.0 ] + state: [2, 2, 0, 0] + goal: [9, 9, 0] + color: ['royalblue', 'red', 'green', 'orange', 'purple', 'yellow', 'cyan', 'magenta', 'lime', 'pink', 'brown'] + arrive_mode: position + goal_threshold: 0.3 + + plot: + show_trajectory: False diff --git a/robot_nav/replay_buffer.py b/robot_nav/replay_buffer.py index 8c8eeea..f5902d7 100644 --- a/robot_nav/replay_buffer.py +++ b/robot_nav/replay_buffer.py @@ -71,8 +71,8 @@ def sample_batch(self, batch_size): s_batch = np.array([_[0] for _ in batch]) a_batch = np.array([_[1] for _ in batch]) - r_batch = np.array([_[2] for _ in batch]).reshape(-1, 1) - t_batch = np.array([_[3] for _ in batch]).reshape(-1, 1) + r_batch = np.array([_[2] for _ in batch]) + t_batch = np.array([_[3] for _ in batch]) s2_batch = np.array([_[4] for _ in batch]) return s_batch, a_batch, r_batch, t_batch, s2_batch diff --git a/robot_nav/test.py b/robot_nav/test.py index fe59051..087f570 100644 --- a/robot_nav/test.py +++ b/robot_nav/test.py @@ -1,13 +1,8 @@ from models.TD3.TD3 import TD3 -from models.DDPG.DDPG import DDPG -from models.SAC.SAC import SAC -from models.HCM.hardcoded_model import HCM -from models.PPO.PPO import PPO -from robot_nav.models.CNNTD3.CNNTD3 import CNNTD3 import torch import numpy as np -from sim import SIM_ENV +from robot_nav.SIM_ENV.sim import SIM import yaml @@ -31,7 +26,7 @@ def main(args=None): model_name="TD3", ) # instantiate a model - sim = SIM_ENV(world_file="eval_world.yaml") # instantiate environment + sim = SIM(world_file="eval_world.yaml") # instantiate environment with open("robot_nav/eval_points.yaml") as file: points = yaml.safe_load(file) robot_poses = points["robot"]["poses"] diff --git a/robot_nav/test_random.py b/robot_nav/test_random.py index 90446a2..63690ec 100644 --- a/robot_nav/test_random.py +++ b/robot_nav/test_random.py @@ -1,16 +1,11 @@ -from robot_nav.models.TD3.TD3 import TD3 -from robot_nav.models.DDPG.DDPG import DDPG from robot_nav.models.SAC.SAC import SAC -from robot_nav.models.HCM.hardcoded_model import HCM -from robot_nav.models.PPO.PPO import PPO -from robot_nav.models.CNNTD3.CNNTD3 import CNNTD3 import statistics import numpy as np import tqdm import matplotlib.pyplot as plt import torch -from sim import SIM_ENV +from robot_nav.SIM_ENV.sim import SIM def main(args=None): @@ -34,7 +29,7 @@ def main(args=None): model_name="SAC", ) # instantiate a model - sim = SIM_ENV( + sim = SIM( world_file="eval_world.yaml", disable_plotting=False ) # instantiate environment diff --git a/robot_nav/test_rnn.py b/robot_nav/test_rnn.py index 526ee34..9fca0b9 100644 --- a/robot_nav/test_rnn.py +++ b/robot_nav/test_rnn.py @@ -4,7 +4,7 @@ import torch import numpy as np -from sim import SIM_ENV +from robot_nav.SIM_ENV.sim import SIM import yaml @@ -27,7 +27,7 @@ def main(args=None): load_model=True, ) # instantiate a model - sim = SIM_ENV(world_file="eval_world.yaml") # instantiate environment + sim = SIM(world_file="eval_world.yaml") # instantiate environment with open("robot_nav/eval_points.yaml") as file: points = yaml.safe_load(file) robot_poses = points["robot"]["poses"] diff --git a/robot_nav/train.py b/robot_nav/train.py index 936ecc7..6d97362 100644 --- a/robot_nav/train.py +++ b/robot_nav/train.py @@ -1,13 +1,8 @@ -from robot_nav.models.TD3.TD3 import TD3 -from robot_nav.models.DDPG.DDPG import DDPG -from robot_nav.models.SAC.SAC import SAC -from robot_nav.models.HCM.hardcoded_model import HCM -from robot_nav.models.PPO.PPO import PPO from robot_nav.models.CNNTD3.CNNTD3 import CNNTD3 import torch import numpy as np -from sim import SIM_ENV +from robot_nav.SIM_ENV.sim import SIM from utils import get_buffer @@ -15,7 +10,7 @@ def main(args=None): """Main training function""" action_dim = 2 # number of actions produced by the model max_action = 1 # maximum absolute value of output actions - state_dim = 25 # number of input values in the neural network (vector length of state input) + state_dim = 95 # number of input values in the neural network (vector length of state input) device = torch.device( "cuda" if torch.cuda.is_available() else "cpu" ) # using cuda if it is available, cpu otherwise @@ -36,17 +31,19 @@ def main(args=None): ) save_every = 5 # save the model every n training cycles - model = TD3( + model = CNNTD3( state_dim=state_dim, action_dim=action_dim, max_action=max_action, device=device, save_every=save_every, load_model=False, - model_name="TD3", + model_name="CNNTD3", ) # instantiate a model - sim = SIM_ENV(disable_plotting=False) # instantiate environment + sim = SIM( + world_file="robot_world.yaml", disable_plotting=False + ) # instantiate environment replay_buffer = get_buffer( model, sim, diff --git a/robot_nav/train_marl.py b/robot_nav/train_marl.py new file mode 100644 index 0000000..52e4dfa --- /dev/null +++ b/robot_nav/train_marl.py @@ -0,0 +1,154 @@ +from robot_nav.models.MARL.marlTD3 import TD3 + +import torch +import numpy as np +from robot_nav.SIM_ENV.marl_sim import MARL_SIM +from utils import get_buffer + + +def outside_of_bounds(poses): + outside = False + for pose in poses: + norm_x = pose[0] - 6 + norm_y = pose[1] - 6 + if abs(norm_x) > 10.5 or abs(norm_y) > 10.5: + outside = True + break + return outside + + +def main(args=None): + """Main training function""" + action_dim = 2 # number of actions produced by the model + max_action = 1 # maximum absolute value of output actions + state_dim = 11 # number of input values in the neural network (vector length of state input) + device = torch.device( + "cuda" if torch.cuda.is_available() else "cpu" + ) # using cuda if it is available, cpu otherwise + max_epochs = 600 # max number of epochs + epoch = 1 # starting epoch number + episode = 0 # starting episode number + train_every_n = 10 # train and update network parameters every n episodes + training_iterations = 80 # how many batches to use for single training cycle + batch_size = 16 # batch size for each training iteration + max_steps = 300 # maximum number of steps in single episode + steps = 0 # starting step number + load_saved_buffer = False # whether to load experiences from assets/data.yml + pretrain = False # whether to use the loaded experiences to pre-train the model (load_saved_buffer must be True) + pretraining_iterations = ( + 10 # number of training iterations to run during pre-training + ) + save_every = 5 # save the model every n training cycles + + sim = MARL_SIM( + world_file="multi_robot_world.yaml", disable_plotting=False + ) # instantiate environment + + model = TD3( + state_dim=state_dim, + action_dim=action_dim, + max_action=max_action, + num_robots=sim.num_robots, + device=device, + save_every=save_every, + load_model=False, + model_name="phase1", + load_model_name="phase1", + ) # instantiate a model + + replay_buffer = get_buffer( + model, + sim, + load_saved_buffer, + pretrain, + pretraining_iterations, + training_iterations, + batch_size, + ) + connections = torch.tensor( + [[0.0 for _ in range(sim.num_robots - 1)] for _ in range(sim.num_robots)] + ) + + poses, distance, cos, sin, collision, goal, a, reward, positions, goal_positions = ( + sim.step([[0, 0] for _ in range(sim.num_robots)], connections) + ) # get the initial step state + running_goals = 0 + running_collisions = 0 + running_timesteps = 0 + while epoch < max_epochs: # train until max_epochs is reached + state, terminal = model.prepare_state( + poses, distance, cos, sin, collision, a, goal_positions + ) # get state a state representation from returned data from the environment + + action, connection, combined_weights = model.get_action( + np.array(state), True + ) # get an action from the model + + a_in = [ + [(a[0] + 1) / 4, a[1]] for a in action + ] # clip linear velocity to [0, 0.5] m/s range + + ( + poses, + distance, + cos, + sin, + collision, + goal, + a, + reward, + positions, + goal_positions, + ) = sim.step( + a_in, connection, combined_weights + ) # get data from the environment + running_goals += sum(goal) + running_collisions += sum(collision) + running_timesteps += 1 + next_state, terminal = model.prepare_state( + poses, distance, cos, sin, collision, a, goal_positions + ) # get a next state representation + replay_buffer.add( + state, action, reward, terminal, next_state + ) # add experience to the replay buffer + outside = outside_of_bounds(poses) + if ( + any(terminal) or steps == max_steps or outside + ): # reset environment of terminal state reached, or max_steps were taken + ( + poses, + distance, + cos, + sin, + collision, + goal, + a, + reward, + positions, + goal_positions, + ) = sim.reset() + episode += 1 + if episode % train_every_n == 0: + model.writer.add_scalar( + "run/avg_goal", running_goals / running_timesteps, epoch + ) + model.writer.add_scalar( + "run/avg_collision", running_collisions / running_timesteps, epoch + ) + running_goals = 0 + running_collisions = 0 + running_timesteps = 0 + epoch += 1 + model.train( + replay_buffer=replay_buffer, + iterations=training_iterations, + batch_size=batch_size, + ) # train the model and update its parameters + + steps = 0 + else: + steps += 1 + + +if __name__ == "__main__": + main() diff --git a/robot_nav/train_rnn.py b/robot_nav/train_rnn.py index aec1f70..5d1ad4e 100644 --- a/robot_nav/train_rnn.py +++ b/robot_nav/train_rnn.py @@ -5,7 +5,7 @@ import numpy as np -from sim import SIM_ENV +from robot_nav.SIM_ENV.sim import SIM from utils import get_buffer @@ -47,7 +47,7 @@ def main(args=None): rnn="gru", ) # instantiate a model - sim = SIM_ENV() # instantiate environment + sim = SIM() # instantiate environment replay_buffer = get_buffer( model, sim, diff --git a/tests/test_marl_world.yaml b/tests/test_marl_world.yaml new file mode 100644 index 0000000..6af7f68 --- /dev/null +++ b/tests/test_marl_world.yaml @@ -0,0 +1,22 @@ +world: + height: 12 # the height of the world + width: 12 # the height of the world + step_time: 1.0 + sample_time: 1.0 + collision_mode: 'reactive' + +robot: + - number: 3 + kinematics: {name: 'diff'} + distribution: {name: 'manual'} + shape: {name: 'circle', radius: 0.2} + vel_min: [ 0, -1.0 ] + vel_max: [ 1.0, 1.0 ] + state: [[3, 10, 0], [3, 6, 0], [3, 2, 0]] + goal: [[9, 9, 0], [8, 8, 0], [7, 7, 0]] + color: ['royalblue', 'red', 'green', 'orange', 'purple', 'yellow', 'cyan', 'magenta', 'lime', 'pink', 'brown'] + arrive_mode: position + goal_threshold: 0.3 + + plot: + show_trajectory: False diff --git a/tests/test_model.py b/tests/test_model.py index 13193ac..6ad13d7 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1,16 +1,21 @@ from pathlib import Path +import torch + +from robot_nav.SIM_ENV.marl_sim import MARL_SIM from robot_nav.models.RCPG.RCPG import RCPG from robot_nav.models.TD3.TD3 import TD3 from robot_nav.models.CNNTD3.CNNTD3 import CNNTD3 from robot_nav.models.SAC.SAC import SAC from robot_nav.models.DDPG.DDPG import DDPG from robot_nav.utils import get_buffer -from robot_nav.sim import SIM_ENV +from robot_nav.SIM_ENV.sim import SIM +from robot_nav.models.MARL.marlTD3 import TD3 as marlTD3 import pytest PROJECT_ROOT = Path(__file__).resolve().parents[1] + @pytest.mark.parametrize( "model, state_dim", [ @@ -31,7 +36,7 @@ def test_models(model, state_dim): load_model=False, ) # instantiate a model - sim = SIM_ENV + sim = SIM prefilled_buffer = get_buffer( model=test_model, @@ -71,7 +76,7 @@ def test_max_bound_models(model, state_dim): use_max_bound=True, ) # instantiate a model - sim = SIM_ENV + sim = SIM prefilled_buffer = get_buffer( model=test_model, @@ -90,3 +95,56 @@ def test_max_bound_models(model, state_dim): iterations=2, batch_size=8, ) + + +def test_marl_models(): + sim = MARL_SIM("/tests/test_marl_world.yaml", disable_plotting=True) + test_model = marlTD3( + state_dim=11, + action_dim=2, + max_action=1, + num_robots=sim.num_robots, + device="cpu", + save_every=0, + load_model=False, + ) # instantiate a model + + replay_buffer = get_buffer( + model=test_model, + sim=sim, + load_saved_buffer=False, + pretrain=False, + pretraining_iterations=0, + training_iterations=0, + batch_size=0, + buffer_size=100, + ) + + for _ in range(10): + connections = torch.tensor( + [[0.0 for _ in range(sim.num_robots - 1)] for _ in range(3)] + ) + ( + poses, + distance, + cos, + sin, + collision, + goal, + a, + reward, + positions, + goal_positions, + ) = sim.step([[0, 0] for _ in range(sim.num_robots)], connections) + state, terminal = test_model.prepare_state( + poses, distance, cos, sin, collision, a, goal_positions + ) + replay_buffer.add( + state, [[0, 0] for _ in range(sim.num_robots)], reward, terminal, state + ) + + test_model.train( + replay_buffer=replay_buffer, + iterations=2, + batch_size=8, + ) diff --git a/tests/test_sim.py b/tests/test_sim.py index adb9e01..cf2e616 100644 --- a/tests/test_sim.py +++ b/tests/test_sim.py @@ -1,18 +1,20 @@ import os import pytest +import torch -from robot_nav.sim import SIM_ENV +from robot_nav.SIM_ENV.marl_sim import MARL_SIM +from robot_nav.SIM_ENV.sim import SIM import numpy as np skip_on_ci = pytest.mark.skipif( - os.getenv("CI") == "true", - reason="Skipped on CI (GitHub Actions)" + os.getenv("CI") == "true", reason="Skipped on CI (GitHub Actions)" ) + @skip_on_ci def test_sim(): - sim = SIM_ENV("/tests/test_world.yaml") + sim = SIM("/tests/test_world.yaml", disable_plotting=True) robot_state = sim.env.get_robot_state() state = sim.step(1, 0) next_robot_state = sim.env.get_robot_state() @@ -27,9 +29,32 @@ def test_sim(): assert np.not_equal(robot_state[0], new_robot_state[0]) assert np.not_equal(robot_state[1], new_robot_state[1]) + +def test_marl_sim(): + sim = MARL_SIM("/tests/test_marl_world.yaml", disable_plotting=True) + robot_state = [sim.env.robot_list[i].state[:2] for i in range(3)] + connections = torch.tensor( + [[0.0 for _ in range(sim.num_robots - 1)] for _ in range(3)] + ) + + _ = sim.step([[1, 0], [1, 0], [1, 0]], connections) + next_robot_state = [sim.env.robot_list[i].state[:2] for i in range(3)] + for j in range(3): + assert np.isclose(robot_state[j][0], next_robot_state[j][0] - 1) + assert np.isclose(robot_state[j][1], robot_state[j][1]) + + assert len(sim.env.obstacle_list) == 0 + + sim.reset() + new_robot_state = [sim.env.robot_list[i].state[:2] for i in range(3)] + for j in range(3): + assert np.not_equal(robot_state[j][0], new_robot_state[j][0]) + assert np.not_equal(robot_state[j][1], new_robot_state[j][1]) + + @skip_on_ci def test_sincos(): - sim = SIM_ENV("/tests/test_world.yaml") + sim = SIM("/tests/test_world.yaml") cos, sin = sim.cossin([1, 0], [0, 1]) assert np.isclose(cos, 0) assert np.isclose(sin, 1) diff --git a/tests/test_utils.py b/tests/test_utils.py index 7aa18ef..2414152 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -4,10 +4,11 @@ from robot_nav.models.PPO.PPO import PPO, RolloutBuffer from robot_nav.models.RCPG.RCPG import RCPG from robot_nav.utils import get_buffer, RolloutReplayBuffer, ReplayBuffer -from robot_nav.sim import SIM_ENV +from robot_nav.SIM_ENV.sim import SIM PROJECT_ROOT = Path(__file__).resolve().parents[1] + def test_buffer(): model = SAC( state_dim=10, @@ -18,7 +19,7 @@ def test_buffer(): load_model=False, ) # instantiate a model - sim = SIM_ENV + sim = SIM buffer = get_buffer( model=model, sim=sim, @@ -57,7 +58,7 @@ def test_rollout_buffer(): load_model=False, ) # instantiate a model - sim = SIM_ENV + sim = SIM buffer = get_buffer( model=model, sim=sim, @@ -95,7 +96,7 @@ def test_ppo_buffer(): save_every=0, load_model=False, ) - sim = SIM_ENV + sim = SIM buffer = get_buffer( model=model, sim=sim,