comrad-bench 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. comrad/curriculum/__init__.py +30 -0
  2. comrad/curriculum/base.py +133 -0
  3. comrad/curriculum/learning_progress.py +136 -0
  4. comrad/curriculum/observer.py +100 -0
  5. comrad/curriculum/omni.py +74 -0
  6. comrad/curriculum/plr.py +208 -0
  7. comrad/curriculum/sequential.py +72 -0
  8. comrad/curriculum/uniform.py +20 -0
  9. comrad/enjoy.py +22 -0
  10. comrad/envs/__init__.py +0 -0
  11. comrad/envs/action_space.py +513 -0
  12. comrad/envs/doom_gym.py +766 -0
  13. comrad/envs/doom_params.py +252 -0
  14. comrad/envs/multi_wad_env.py +171 -0
  15. comrad/envs/multiagent/__init__.py +0 -0
  16. comrad/envs/multiagent/doom_multiagent.py +224 -0
  17. comrad/envs/multiagent/doom_multiagent_wrapper.py +592 -0
  18. comrad/envs/wad_catalog.py +46 -0
  19. comrad/examples/WandB_Scraper/plot_wandb.py +230 -0
  20. comrad/examples/agent_run_env.py +50 -0
  21. comrad/examples/inspect_obs.py +28 -0
  22. comrad/examples/single_player.py +21 -0
  23. comrad/examples/vizdoom.pyi +3233 -0
  24. comrad/examples/you_run_env.py +192 -0
  25. comrad/misc/resource_greed.py +90 -0
  26. comrad/models/__init__.py +0 -0
  27. comrad/models/doom_model.py +47 -0
  28. comrad/models/happo_model.py +376 -0
  29. comrad/models/mappo_model.py +85 -0
  30. comrad/models/qmix_model.py +415 -0
  31. comrad/models/qplex_mixer.py +443 -0
  32. comrad/record_topdown_heatmap.py +1299 -0
  33. comrad/record_video.py +262 -0
  34. comrad/scenarios/ammo_carrier.cfg +39 -0
  35. comrad/scenarios/ammo_carrier.wad +0 -0
  36. comrad/scenarios/armory_siege.cfg +41 -0
  37. comrad/scenarios/armory_siege.wad +0 -0
  38. comrad/scenarios/batch_armory/armory_0.wad +0 -0
  39. comrad/scenarios/batch_armory/armory_1.wad +0 -0
  40. comrad/scenarios/batch_armory/armory_10.wad +0 -0
  41. comrad/scenarios/batch_armory/armory_11.wad +0 -0
  42. comrad/scenarios/batch_armory/armory_12.wad +0 -0
  43. comrad/scenarios/batch_armory/armory_13.wad +0 -0
  44. comrad/scenarios/batch_armory/armory_14.wad +0 -0
  45. comrad/scenarios/batch_armory/armory_15.wad +0 -0
  46. comrad/scenarios/batch_armory/armory_16.wad +0 -0
  47. comrad/scenarios/batch_armory/armory_17.wad +0 -0
  48. comrad/scenarios/batch_armory/armory_18.wad +0 -0
  49. comrad/scenarios/batch_armory/armory_19.wad +0 -0
  50. comrad/scenarios/batch_armory/armory_2.wad +0 -0
  51. comrad/scenarios/batch_armory/armory_20.wad +0 -0
  52. comrad/scenarios/batch_armory/armory_21.wad +0 -0
  53. comrad/scenarios/batch_armory/armory_22.wad +0 -0
  54. comrad/scenarios/batch_armory/armory_23.wad +0 -0
  55. comrad/scenarios/batch_armory/armory_3.wad +0 -0
  56. comrad/scenarios/batch_armory/armory_4.wad +0 -0
  57. comrad/scenarios/batch_armory/armory_5.wad +0 -0
  58. comrad/scenarios/batch_armory/armory_6.wad +0 -0
  59. comrad/scenarios/batch_armory/armory_7.wad +0 -0
  60. comrad/scenarios/batch_armory/armory_8.wad +0 -0
  61. comrad/scenarios/batch_armory/armory_9.wad +0 -0
  62. comrad/scenarios/batch_lava_maze/lava_maze_0.wad +0 -0
  63. comrad/scenarios/batch_lava_maze/lava_maze_1.wad +0 -0
  64. comrad/scenarios/batch_lava_maze/lava_maze_10.wad +0 -0
  65. comrad/scenarios/batch_lava_maze/lava_maze_11.wad +0 -0
  66. comrad/scenarios/batch_lava_maze/lava_maze_12.wad +0 -0
  67. comrad/scenarios/batch_lava_maze/lava_maze_13.wad +0 -0
  68. comrad/scenarios/batch_lava_maze/lava_maze_14.wad +0 -0
  69. comrad/scenarios/batch_lava_maze/lava_maze_15.wad +0 -0
  70. comrad/scenarios/batch_lava_maze/lava_maze_16.wad +0 -0
  71. comrad/scenarios/batch_lava_maze/lava_maze_17.wad +0 -0
  72. comrad/scenarios/batch_lava_maze/lava_maze_18.wad +0 -0
  73. comrad/scenarios/batch_lava_maze/lava_maze_19.wad +0 -0
  74. comrad/scenarios/batch_lava_maze/lava_maze_2.wad +0 -0
  75. comrad/scenarios/batch_lava_maze/lava_maze_20.wad +0 -0
  76. comrad/scenarios/batch_lava_maze/lava_maze_21.wad +0 -0
  77. comrad/scenarios/batch_lava_maze/lava_maze_22.wad +0 -0
  78. comrad/scenarios/batch_lava_maze/lava_maze_23.wad +0 -0
  79. comrad/scenarios/batch_lava_maze/lava_maze_24.wad +0 -0
  80. comrad/scenarios/batch_lava_maze/lava_maze_25.wad +0 -0
  81. comrad/scenarios/batch_lava_maze/lava_maze_26.wad +0 -0
  82. comrad/scenarios/batch_lava_maze/lava_maze_27.wad +0 -0
  83. comrad/scenarios/batch_lava_maze/lava_maze_28.wad +0 -0
  84. comrad/scenarios/batch_lava_maze/lava_maze_29.wad +0 -0
  85. comrad/scenarios/batch_lava_maze/lava_maze_3.wad +0 -0
  86. comrad/scenarios/batch_lava_maze/lava_maze_30.wad +0 -0
  87. comrad/scenarios/batch_lava_maze/lava_maze_31.wad +0 -0
  88. comrad/scenarios/batch_lava_maze/lava_maze_32.wad +0 -0
  89. comrad/scenarios/batch_lava_maze/lava_maze_33.wad +0 -0
  90. comrad/scenarios/batch_lava_maze/lava_maze_34.wad +0 -0
  91. comrad/scenarios/batch_lava_maze/lava_maze_35.wad +0 -0
  92. comrad/scenarios/batch_lava_maze/lava_maze_36.wad +0 -0
  93. comrad/scenarios/batch_lava_maze/lava_maze_37.wad +0 -0
  94. comrad/scenarios/batch_lava_maze/lava_maze_38.wad +0 -0
  95. comrad/scenarios/batch_lava_maze/lava_maze_39.wad +0 -0
  96. comrad/scenarios/batch_lava_maze/lava_maze_4.wad +0 -0
  97. comrad/scenarios/batch_lava_maze/lava_maze_40.wad +0 -0
  98. comrad/scenarios/batch_lava_maze/lava_maze_41.wad +0 -0
  99. comrad/scenarios/batch_lava_maze/lava_maze_42.wad +0 -0
  100. comrad/scenarios/batch_lava_maze/lava_maze_43.wad +0 -0
  101. comrad/scenarios/batch_lava_maze/lava_maze_44.wad +0 -0
  102. comrad/scenarios/batch_lava_maze/lava_maze_45.wad +0 -0
  103. comrad/scenarios/batch_lava_maze/lava_maze_46.wad +0 -0
  104. comrad/scenarios/batch_lava_maze/lava_maze_47.wad +0 -0
  105. comrad/scenarios/batch_lava_maze/lava_maze_48.wad +0 -0
  106. comrad/scenarios/batch_lava_maze/lava_maze_49.wad +0 -0
  107. comrad/scenarios/batch_lava_maze/lava_maze_5.wad +0 -0
  108. comrad/scenarios/batch_lava_maze/lava_maze_50.wad +0 -0
  109. comrad/scenarios/batch_lava_maze/lava_maze_51.wad +0 -0
  110. comrad/scenarios/batch_lava_maze/lava_maze_52.wad +0 -0
  111. comrad/scenarios/batch_lava_maze/lava_maze_53.wad +0 -0
  112. comrad/scenarios/batch_lava_maze/lava_maze_54.wad +0 -0
  113. comrad/scenarios/batch_lava_maze/lava_maze_55.wad +0 -0
  114. comrad/scenarios/batch_lava_maze/lava_maze_56.wad +0 -0
  115. comrad/scenarios/batch_lava_maze/lava_maze_57.wad +0 -0
  116. comrad/scenarios/batch_lava_maze/lava_maze_58.wad +0 -0
  117. comrad/scenarios/batch_lava_maze/lava_maze_59.wad +0 -0
  118. comrad/scenarios/batch_lava_maze/lava_maze_6.wad +0 -0
  119. comrad/scenarios/batch_lava_maze/lava_maze_60.wad +0 -0
  120. comrad/scenarios/batch_lava_maze/lava_maze_61.wad +0 -0
  121. comrad/scenarios/batch_lava_maze/lava_maze_62.wad +0 -0
  122. comrad/scenarios/batch_lava_maze/lava_maze_63.wad +0 -0
  123. comrad/scenarios/batch_lava_maze/lava_maze_64.wad +0 -0
  124. comrad/scenarios/batch_lava_maze/lava_maze_65.wad +0 -0
  125. comrad/scenarios/batch_lava_maze/lava_maze_7.wad +0 -0
  126. comrad/scenarios/batch_lava_maze/lava_maze_8.wad +0 -0
  127. comrad/scenarios/batch_lava_maze/lava_maze_9.wad +0 -0
  128. comrad/scenarios/batch_platform_chain_curriculum/platform_chain_bridge_intro.wad +0 -0
  129. comrad/scenarios/batch_platform_chain_curriculum/platform_chain_bridge_mastery.wad +0 -0
  130. comrad/scenarios/batch_platform_chain_curriculum/platform_chain_bridge_progress.wad +0 -0
  131. comrad/scenarios/batch_platform_chain_curriculum/platform_chain_easy_intro.wad +0 -0
  132. comrad/scenarios/batch_platform_chain_curriculum/platform_chain_easy_mastery.wad +0 -0
  133. comrad/scenarios/batch_platform_chain_curriculum/platform_chain_easy_movers.wad +0 -0
  134. comrad/scenarios/batch_platform_chain_curriculum/platform_chain_easy_spacing.wad +0 -0
  135. comrad/scenarios/batch_platform_chain_curriculum/platform_chain_hard_default.wad +0 -0
  136. comrad/scenarios/batch_platform_chain_curriculum/platform_chain_hard_endgame.wad +0 -0
  137. comrad/scenarios/batch_platform_chain_curriculum/platform_chain_hard_extended.wad +0 -0
  138. comrad/scenarios/batch_platform_chain_curriculum/platform_chain_hard_intro.wad +0 -0
  139. comrad/scenarios/batch_platform_chain_curriculum/platform_chain_hard_tighter.wad +0 -0
  140. comrad/scenarios/coop_health_gathering.cfg +31 -0
  141. comrad/scenarios/coop_health_gathering.wad +0 -0
  142. comrad/scenarios/coop_puzzle.cfg +39 -0
  143. comrad/scenarios/coop_puzzle.wad +0 -0
  144. comrad/scenarios/dumb_enemies.cfg +35 -0
  145. comrad/scenarios/dumb_enemies.wad +0 -0
  146. comrad/scenarios/foraging_commons.cfg +36 -0
  147. comrad/scenarios/foraging_commons.wad +0 -0
  148. comrad/scenarios/lava_maze.cfg +41 -0
  149. comrad/scenarios/lava_maze.wad +0 -0
  150. comrad/scenarios/lava_maze_simple.cfg +32 -0
  151. comrad/scenarios/lava_maze_simple.wad +0 -0
  152. comrad/scenarios/lavapit.cfg +46 -0
  153. comrad/scenarios/lavapit.wad +0 -0
  154. comrad/scenarios/platform_chain.cfg +38 -0
  155. comrad/scenarios/platform_chain.wad +0 -0
  156. comrad/scenarios/platform_chain_easy.cfg +38 -0
  157. comrad/scenarios/platform_chain_easy.wad +0 -0
  158. comrad/scenarios/rhythm_sync.cfg +49 -0
  159. comrad/scenarios/rhythm_sync.wad +0 -0
  160. comrad/scenarios/smart_enemies.cfg +36 -0
  161. comrad/scenarios/smart_enemies.wad +0 -0
  162. comrad/scenarios/stag_hunt_arena.cfg +39 -0
  163. comrad/scenarios/stag_hunt_arena.wad +0 -0
  164. comrad/scenarios/stealth_labyrinth.cfg +45 -0
  165. comrad/scenarios/stealth_labyrinth.wad +0 -0
  166. comrad/templates/asdf.sh +24 -0
  167. comrad/templates/run.sh +364 -0
  168. comrad/templates/run_algo.sh +84 -0
  169. comrad/templates/run_bench.sh +77 -0
  170. comrad/tests/__init__.py +0 -0
  171. comrad/tests/test_doom_utils_additional_input.py +60 -0
  172. comrad/tests/test_lava_maze_action_mask.py +86 -0
  173. comrad/tests/test_record_topdown_heatmap.py +271 -0
  174. comrad/tests/test_train_all.py +118 -0
  175. comrad/tests/test_train_all_scaling.py +168 -0
  176. comrad/train.py +228 -0
  177. comrad/train_all.py +368 -0
  178. comrad/utils/__init__.py +0 -0
  179. comrad/utils/doom_render.py +35 -0
  180. comrad/utils/doom_utils.py +644 -0
  181. comrad/utils/recording_actions.py +29 -0
  182. comrad/utils/video_uploader.py +51 -0
  183. comrad/utils/wad_utils.py +55 -0
  184. comrad/wrappers/__init__.py +0 -0
  185. comrad/wrappers/action_mask.py +73 -0
  186. comrad/wrappers/additional_input.py +101 -0
  187. comrad/wrappers/agent_id_wrapper.py +42 -0
  188. comrad/wrappers/exploration.py +56 -0
  189. comrad/wrappers/multiplayer_stats.py +61 -0
  190. comrad/wrappers/observation_space.py +75 -0
  191. comrad/wrappers/reward_shaping.py +262 -0
  192. comrad/wrappers/scenario_wrappers/__init__.py +18 -0
  193. comrad/wrappers/scenario_wrappers/ammo_carrier.py +231 -0
  194. comrad/wrappers/scenario_wrappers/armory_siege.py +264 -0
  195. comrad/wrappers/scenario_wrappers/coop_health_gathering.py +124 -0
  196. comrad/wrappers/scenario_wrappers/coop_puzzle.py +131 -0
  197. comrad/wrappers/scenario_wrappers/dumb_enemies.py +135 -0
  198. comrad/wrappers/scenario_wrappers/foraging_commons.py +147 -0
  199. comrad/wrappers/scenario_wrappers/gathering_reward_shaping.py +44 -0
  200. comrad/wrappers/scenario_wrappers/lava_maze.py +303 -0
  201. comrad/wrappers/scenario_wrappers/lava_maze_simple.py +300 -0
  202. comrad/wrappers/scenario_wrappers/lavapit_reward_shaping.py +235 -0
  203. comrad/wrappers/scenario_wrappers/mwh_reward_shaping.py +79 -0
  204. comrad/wrappers/scenario_wrappers/pitfall_reward_shaping.py +93 -0
  205. comrad/wrappers/scenario_wrappers/platform_chain.py +115 -0
  206. comrad/wrappers/scenario_wrappers/rhythm_sync.py +111 -0
  207. comrad/wrappers/scenario_wrappers/rhythm_sync_dense.py +327 -0
  208. comrad/wrappers/scenario_wrappers/smart_enemies.py +132 -0
  209. comrad/wrappers/scenario_wrappers/stag_hunt_arena.py +128 -0
  210. comrad/wrappers/scenario_wrappers/stealth_labyrinth.py +157 -0
  211. comrad/wrappers/shared_reward.py +55 -0
  212. comrad/wrappers/step_human_input.py +37 -0
  213. comrad/wrappers/video_recorder.py +347 -0
  214. comrad_bench-1.0.0.dist-info/METADATA +229 -0
  215. comrad_bench-1.0.0.dist-info/RECORD +384 -0
  216. comrad_bench-1.0.0.dist-info/WHEEL +5 -0
  217. comrad_bench-1.0.0.dist-info/licenses/LICENSE +21 -0
  218. comrad_bench-1.0.0.dist-info/top_level.txt +2 -0
  219. sample_factory/__init__.py +9 -0
  220. sample_factory/algo/__init__.py +0 -0
  221. sample_factory/algo/evaluators/__init__.py +0 -0
  222. sample_factory/algo/evaluators/default_evaluator.py +0 -0
  223. sample_factory/algo/learning/__init__.py +0 -0
  224. sample_factory/algo/learning/batcher.py +271 -0
  225. sample_factory/algo/learning/learner.py +1322 -0
  226. sample_factory/algo/learning/learner_dqn.py +450 -0
  227. sample_factory/algo/learning/learner_happo.py +818 -0
  228. sample_factory/algo/learning/learner_qmix.py +1254 -0
  229. sample_factory/algo/learning/learner_worker.py +178 -0
  230. sample_factory/algo/learning/rnn_utils.py +158 -0
  231. sample_factory/algo/runners/__init__.py +0 -0
  232. sample_factory/algo/runners/runner.py +774 -0
  233. sample_factory/algo/runners/runner_parallel.py +65 -0
  234. sample_factory/algo/runners/runner_serial.py +27 -0
  235. sample_factory/algo/sampling/__init__.py +0 -0
  236. sample_factory/algo/sampling/batched_sampling.py +466 -0
  237. sample_factory/algo/sampling/evaluation_sampling_api.py +315 -0
  238. sample_factory/algo/sampling/inference_worker.py +524 -0
  239. sample_factory/algo/sampling/non_batched_sampling.py +709 -0
  240. sample_factory/algo/sampling/rollout_worker.py +308 -0
  241. sample_factory/algo/sampling/sampler.py +296 -0
  242. sample_factory/algo/sampling/sampling_utils.py +70 -0
  243. sample_factory/algo/sampling/stats.py +31 -0
  244. sample_factory/algo/sampling/sync_sampling_api.py +65 -0
  245. sample_factory/algo/utils/__init__.py +0 -0
  246. sample_factory/algo/utils/action_distributions.py +350 -0
  247. sample_factory/algo/utils/agent_policy_mapping.py +62 -0
  248. sample_factory/algo/utils/context.py +50 -0
  249. sample_factory/algo/utils/env_info.py +151 -0
  250. sample_factory/algo/utils/epsilon_schedule.py +50 -0
  251. sample_factory/algo/utils/gymnasium_utils.py +128 -0
  252. sample_factory/algo/utils/heartbeat.py +35 -0
  253. sample_factory/algo/utils/joint_replay_buffer.py +296 -0
  254. sample_factory/algo/utils/joint_sequence_replay_buffer.py +225 -0
  255. sample_factory/algo/utils/make_env.py +388 -0
  256. sample_factory/algo/utils/misc.py +44 -0
  257. sample_factory/algo/utils/model_sharing.py +170 -0
  258. sample_factory/algo/utils/multiprocessing_utils.py +41 -0
  259. sample_factory/algo/utils/optimizers.py +189 -0
  260. sample_factory/algo/utils/replay_buffer.py +390 -0
  261. sample_factory/algo/utils/rl_utils.py +117 -0
  262. sample_factory/algo/utils/running_mean_std.py +152 -0
  263. sample_factory/algo/utils/shared_buffers.py +248 -0
  264. sample_factory/algo/utils/spaces/__init__.py +0 -0
  265. sample_factory/algo/utils/spaces/discretized.py +14 -0
  266. sample_factory/algo/utils/tensor_dict.py +156 -0
  267. sample_factory/algo/utils/tensor_utils.py +47 -0
  268. sample_factory/algo/utils/torch_utils.py +66 -0
  269. sample_factory/cfg/__init__.py +0 -0
  270. sample_factory/cfg/arguments.py +617 -0
  271. sample_factory/cfg/cfg.py +1187 -0
  272. sample_factory/cfg/configurable.py +6 -0
  273. sample_factory/enjoy.py +292 -0
  274. sample_factory/envs/__init__.py +0 -0
  275. sample_factory/envs/create_env.py +46 -0
  276. sample_factory/envs/env_utils.py +142 -0
  277. sample_factory/envs/env_wrappers.py +457 -0
  278. sample_factory/envs/pettingzoo_envs.py +79 -0
  279. sample_factory/eval.py +119 -0
  280. sample_factory/export_onnx.py +202 -0
  281. sample_factory/huggingface/__init__.py +0 -0
  282. sample_factory/huggingface/huggingface_utils.py +154 -0
  283. sample_factory/huggingface/load_from_hub.py +27 -0
  284. sample_factory/huggingface/push_to_hub.py +61 -0
  285. sample_factory/launcher/__init__.py +0 -0
  286. sample_factory/launcher/launcher_utils.py +6 -0
  287. sample_factory/launcher/run.py +77 -0
  288. sample_factory/launcher/run_description.py +194 -0
  289. sample_factory/launcher/run_ngc.py +72 -0
  290. sample_factory/launcher/run_processes.py +142 -0
  291. sample_factory/launcher/run_slurm.py +173 -0
  292. sample_factory/model/__init__.py +0 -0
  293. sample_factory/model/action_parameterization.py +76 -0
  294. sample_factory/model/actor_critic.py +351 -0
  295. sample_factory/model/core.py +86 -0
  296. sample_factory/model/decoder.py +35 -0
  297. sample_factory/model/encoder.py +242 -0
  298. sample_factory/model/model_factory.py +60 -0
  299. sample_factory/model/model_utils.py +93 -0
  300. sample_factory/model/utils.py +17 -0
  301. sample_factory/pbt/__init__.py +0 -0
  302. sample_factory/pbt/population_based_training.py +415 -0
  303. sample_factory/tests/__init__.py +0 -0
  304. sample_factory/tests/algo/__init__.py +0 -0
  305. sample_factory/tests/algo/happo/__init__.py +0 -0
  306. sample_factory/tests/algo/happo/conftest.py +111 -0
  307. sample_factory/tests/algo/happo/test_critic_rnn.py +347 -0
  308. sample_factory/tests/algo/happo/test_critic_rnn_training.py +420 -0
  309. sample_factory/tests/algo/happo/test_helpers.py +112 -0
  310. sample_factory/tests/algo/happo/test_learner.py +328 -0
  311. sample_factory/tests/algo/happo/test_loss.py +384 -0
  312. sample_factory/tests/algo/happo/test_model.py +280 -0
  313. sample_factory/tests/algo/mappo/__init__.py +0 -0
  314. sample_factory/tests/algo/mappo/conftest.py +56 -0
  315. sample_factory/tests/algo/mappo/test_critic.py +176 -0
  316. sample_factory/tests/algo/mappo/test_model.py +138 -0
  317. sample_factory/tests/algo/mappo/test_rnn_edges.py +171 -0
  318. sample_factory/tests/algo/qmix/__init__.py +0 -0
  319. sample_factory/tests/algo/qmix/conftest.py +179 -0
  320. sample_factory/tests/algo/qmix/test_buffer.py +332 -0
  321. sample_factory/tests/algo/qmix/test_forward_equivalence.py +399 -0
  322. sample_factory/tests/algo/qmix/test_learner.py +458 -0
  323. sample_factory/tests/algo/qmix/test_qplex_compound_actions.py +512 -0
  324. sample_factory/tests/algo/qmix/test_rnn_buffer.py +189 -0
  325. sample_factory/tests/algo/qmix/test_rnn_core.py +390 -0
  326. sample_factory/tests/algo/qmix/test_rnn_loss.py +365 -0
  327. sample_factory/tests/algo/qmix/test_rnn_model.py +545 -0
  328. sample_factory/tests/algo/qmix/test_transitions.py +277 -0
  329. sample_factory/tests/algo/qplex/test_qatten_regress.py +31 -0
  330. sample_factory/tests/algo/qplex/test_qplex.py +893 -0
  331. sample_factory/tests/algo/qplex/test_qplex_cfg_validation.py +56 -0
  332. sample_factory/tests/algo/qplex/test_qplex_compound_convergence.py +487 -0
  333. sample_factory/tests/algo/test_action_distributions.py +259 -0
  334. sample_factory/tests/algo/test_joint_replay_buffer_rng.py +83 -0
  335. sample_factory/tests/algo/test_learner.py +172 -0
  336. sample_factory/tests/algo/test_model.py +64 -0
  337. sample_factory/tests/algo/test_model_builder.py +60 -0
  338. sample_factory/tests/algo/test_pbt.py +25 -0
  339. sample_factory/tests/algo/test_rms.py +68 -0
  340. sample_factory/tests/algo/test_rnn.py +64 -0
  341. sample_factory/tests/algo/test_spaces.py +21 -0
  342. sample_factory/tests/algo/test_tensordict.py +67 -0
  343. sample_factory/tests/envs/__init__.py +0 -0
  344. sample_factory/tests/envs/test_batched_sampling_true_objective.py +81 -0
  345. sample_factory/tests/envs/test_doom_gym_terminal_info.py +56 -0
  346. sample_factory/tests/envs/test_doom_multiagent_wrapper.py +259 -0
  347. sample_factory/tests/envs/tuple_action_envs/__init__.py +0 -0
  348. sample_factory/tests/envs/tuple_action_envs/test_mixed_dist_env_batched.py +137 -0
  349. sample_factory/tests/envs/tuple_action_envs/test_mixed_dist_env_non_batched.py +101 -0
  350. sample_factory/tests/envs/tuple_action_envs/test_two_discrete_action_dist_env_batched.py +144 -0
  351. sample_factory/tests/envs/tuple_action_envs/test_two_discrete_action_dist_env_non_batched.py +113 -0
  352. sample_factory/tests/envs/utils.py +63 -0
  353. sample_factory/tests/envs/vizdoom/__init__.py +0 -0
  354. sample_factory/tests/envs/vizdoom/test_vizdoom_basic.py +84 -0
  355. sample_factory/tests/envs/vizdoom/test_vizdoom_multiplayer.py +85 -0
  356. sample_factory/tests/examples/__init__.py +0 -0
  357. sample_factory/tests/examples/test_example.py +178 -0
  358. sample_factory/tests/examples/test_example_multi.py +64 -0
  359. sample_factory/tests/examples/test_example_sampler.py +31 -0
  360. sample_factory/tests/export_onnx_utils.py +68 -0
  361. sample_factory/tests/test_comrad_curriculum.py +595 -0
  362. sample_factory/tests/test_comrad_train_resume.py +155 -0
  363. sample_factory/tests/test_launcher.py +189 -0
  364. sample_factory/tests/test_precheck.py +26 -0
  365. sample_factory/tests/test_utils.py +60 -0
  366. sample_factory/tests/test_video_recorder.py +444 -0
  367. sample_factory/tests/test_video_uploader.py +107 -0
  368. sample_factory/tests/utils.py +14 -0
  369. sample_factory/train.py +41 -0
  370. sample_factory/utils/__init__.py +0 -0
  371. sample_factory/utils/algo_version.py +3 -0
  372. sample_factory/utils/attr_dict.py +8 -0
  373. sample_factory/utils/decay.py +47 -0
  374. sample_factory/utils/dicts.py +103 -0
  375. sample_factory/utils/get_available_gpus.py +42 -0
  376. sample_factory/utils/gifs.py +41 -0
  377. sample_factory/utils/gpu_utils.py +90 -0
  378. sample_factory/utils/network.py +15 -0
  379. sample_factory/utils/normalize.py +76 -0
  380. sample_factory/utils/tb.py +91 -0
  381. sample_factory/utils/timing.py +161 -0
  382. sample_factory/utils/typing.py +31 -0
  383. sample_factory/utils/utils.py +497 -0
  384. sample_factory/utils/wandb_utils.py +76 -0
@@ -0,0 +1,30 @@
1
+ from comrad.curriculum.base import Curriculum
2
+ from comrad.curriculum.uniform import UniformCurriculum
3
+ from comrad.curriculum.sequential import SequentialCurriculum
4
+ from comrad.curriculum.learning_progress import LearningProgress
5
+ from comrad.curriculum.omni import OMNICurriculum
6
+ from comrad.curriculum.plr import PrioritizedLevelReplay
7
+
8
+ def make_curriculum(n_tasks: int, strategy: str = "uniform", **kwargs) -> Curriculum:
9
+ """Factory to spin up proper curriculum based on configuration."""
10
+ strategies = {
11
+ "uniform": UniformCurriculum,
12
+ "sequential": SequentialCurriculum,
13
+ "learning_progress": LearningProgress,
14
+ "omni": OMNICurriculum,
15
+ "plr": PrioritizedLevelReplay,
16
+ }
17
+
18
+ assert strategy in strategies, f"Unknown strategy '{strategy}'. Available: {list(strategies.keys())}"
19
+
20
+ return strategies[strategy](n_tasks=n_tasks, **kwargs)
21
+
22
+ __all__ = [
23
+ "Curriculum",
24
+ "UniformCurriculum",
25
+ "SequentialCurriculum",
26
+ "LearningProgress",
27
+ "OMNICurriculum",
28
+ "PrioritizedLevelReplay",
29
+ "make_curriculum"
30
+ ]
@@ -0,0 +1,133 @@
1
+ import multiprocessing
2
+ import os
3
+ from typing import Optional
4
+ import numpy as np
5
+
6
+
7
+ class Curriculum:
8
+ """
9
+ Abstract base curriculum class for a fixed discrete batch of tasks (WADs).
10
+ Requires multiprocessing arrays and locks for disjoint worker/learner access.
11
+ """
12
+
13
+ EMA_ALPHA = 0.1
14
+ SCORE_MEAN_VALUE_L1 = "mean_value_l1"
15
+ SCORE_MAX_VALUE_L1 = "max_value_l1"
16
+ SCORE_MEAN_ADVANTAGE = "mean_advantage"
17
+ SCORE_MAX_ADVANTAGE = "max_advantage"
18
+ SCORE_MEAN_ENTROPY = "mean_entropy"
19
+ SCORE_MAX_ENTROPY = "max_entropy"
20
+
21
+ def __init__(self, n_tasks: int, seed: Optional[int] = None, tasks: Optional[list] = None, **kwargs):
22
+ self.ctx = multiprocessing.get_context("spawn")
23
+ self._n = n_tasks
24
+ self._base_seed = int(seed) if seed is not None else int.from_bytes(os.urandom(8), "little")
25
+ self._rng_counter = self.ctx.Value("Q", 0)
26
+ self._task_ids = tuple(tasks) if tasks is not None else None
27
+
28
+ self._weights = self.ctx.Array('d', [1.0 / n_tasks] * n_tasks)
29
+ self._counts = self.ctx.Array('l', [0] * n_tasks)
30
+ self._lock = self.ctx.Lock()
31
+
32
+ def update(self, task_idx: int, episode_return: float) -> None:
33
+ """Called at end of each episode."""
34
+ with self._lock:
35
+ self._counts[task_idx] += 1
36
+ self._update_logic(task_idx, float(episode_return))
37
+ self._recompute_weights()
38
+
39
+ def update_task_score(self, task_idx: int, score: dict, num_steps: int = 1) -> None:
40
+ """Used by sample factory learners (e.g. PLR)"""
41
+ pass
42
+
43
+ def sample(self) -> int:
44
+ with self._lock:
45
+ task_idx = self._sample_logic()
46
+ return task_idx
47
+
48
+ def _sample_logic(self) -> int:
49
+ """Isolated locked core for actually rolling the random sample"""
50
+ weights = np.array(self._weights[:])
51
+ return self._choice(self._n, p=weights)
52
+
53
+ def _next_rng(self) -> np.random.Generator:
54
+ """
55
+ Generate randomness from a single shared draw counter so spawned workers
56
+ do not each replay an identical local RNG stream.
57
+ """
58
+ counter = self._rng_counter.value
59
+ self._rng_counter.value = counter + 1
60
+ seed = (self._base_seed + 0x9E3779B97F4A7C15 * counter) & ((1 << 64) - 1)
61
+ return np.random.default_rng(seed)
62
+
63
+ def _choice(self, n: int, p=None) -> int:
64
+ return int(self._next_rng().choice(n, p=p))
65
+
66
+ def _random(self) -> float:
67
+ return float(self._next_rng().random())
68
+
69
+ def _update_logic(self, task_idx: int, episode_return: float) -> None:
70
+ """Sub-classes override this to implement update behavior."""
71
+ pass
72
+
73
+ def _recompute_weights(self) -> None:
74
+ """Sub-classes override this to update probabilities inside self._weights."""
75
+ pass
76
+
77
+ def task_weights(self) -> np.ndarray:
78
+ with self._lock:
79
+ return np.array(self._weights[:])
80
+
81
+ def task_returns(self) -> np.ndarray:
82
+ """Return fast moving average or appropriate returns proxy."""
83
+ # By default just returns zeroes, overridden in LP/OMNI classes
84
+ return np.zeros(self._n)
85
+
86
+ def task_scores(self) -> np.ndarray:
87
+ """PLR scores or similar metric."""
88
+ return np.zeros(self._n)
89
+
90
+ def metrics(self) -> dict:
91
+ """General metrics reporting."""
92
+ return {}
93
+
94
+ def state_dict(self) -> dict:
95
+ with self._lock:
96
+ state = {
97
+ "version": 1,
98
+ "curriculum_type": type(self).__name__,
99
+ "n_tasks": self._n,
100
+ "base_seed": int(self._base_seed),
101
+ "rng_counter": int(self._rng_counter.value),
102
+ "weights": list(self._weights[:]),
103
+ "counts": list(self._counts[:]),
104
+ }
105
+ if self._task_ids is not None:
106
+ state["task_ids"] = list(self._task_ids)
107
+ state.update(self._state_dict_locked())
108
+ return state
109
+
110
+ def load_state_dict(self, state: dict) -> None:
111
+ expected_type = type(self).__name__
112
+ actual_type = state.get("curriculum_type", expected_type)
113
+ if actual_type != expected_type:
114
+ raise ValueError(f"Expected curriculum state for {expected_type}, got {actual_type}")
115
+ if int(state.get("n_tasks", self._n)) != self._n:
116
+ raise ValueError(f"Expected curriculum state for {self._n} tasks, got {state.get('n_tasks')}")
117
+ expected_task_ids = list(self._task_ids) if self._task_ids is not None else None
118
+ actual_task_ids = state.get("task_ids", None)
119
+ if expected_task_ids is not None and actual_task_ids != expected_task_ids:
120
+ raise ValueError(f"Expected curriculum task ids {expected_task_ids}, got {actual_task_ids}")
121
+
122
+ with self._lock:
123
+ self._base_seed = int(state.get("base_seed", self._base_seed))
124
+ self._rng_counter.value = int(state.get("rng_counter", 0))
125
+ self._weights[:] = [float(weight) for weight in state.get("weights", self._weights[:])]
126
+ self._counts[:] = [int(count) for count in state.get("counts", self._counts[:])]
127
+ self._load_state_dict_locked(state)
128
+
129
+ def _state_dict_locked(self) -> dict:
130
+ return {}
131
+
132
+ def _load_state_dict_locked(self, state: dict) -> None:
133
+ pass
@@ -0,0 +1,136 @@
1
+ import numpy as np
2
+ from comrad.curriculum.base import Curriculum
3
+
4
+ class LearningProgress(Curriculum):
5
+ """
6
+ Syllabus-inspired Learning Progress curriculum tracking fast and slow EMAs over
7
+ normalized per-task success metrics. The raw terminal `true_objective` signal is
8
+ tracked separately for diagnostics/checkpoint continuity.
9
+ """
10
+
11
+ def __init__(
12
+ self,
13
+ n_tasks: int,
14
+ p_theta: float = 0.1,
15
+ max_return: float = 100.0,
16
+ min_return: float = -100.0,
17
+ uniform_prob: float = 0.25,
18
+ **kwargs,
19
+ ):
20
+ super().__init__(n_tasks, **kwargs)
21
+ self.p_theta = p_theta
22
+ self.max_return = max_return
23
+ self.min_return = min_return
24
+ self.uniform_prob = uniform_prob
25
+ self._stale_dist = self.ctx.Value('b', True)
26
+ self._p_fast = self.ctx.Array('d', [0.0] * n_tasks)
27
+ self._p_slow = self.ctx.Array('d', [0.0] * n_tasks)
28
+ self._p_true = self.ctx.Array('d', [0.0] * n_tasks)
29
+ self._p_seen = self.ctx.Array('d', [0.0] * n_tasks) # 0.0 = unseen, 1.0 = seen
30
+
31
+ def _sample_logic(self) -> int:
32
+ if self._stale_dist.value:
33
+ self._recompute_weights()
34
+ self._stale_dist.value = False
35
+ weights = np.array(self._weights[:])
36
+ return self._choice(self._n, p=weights)
37
+
38
+ def update(self, task_idx: int, task_score: float) -> None:
39
+ with self._lock:
40
+ self._counts[task_idx] += 1
41
+ self._update_logic(task_idx, float(task_score))
42
+ self._stale_dist.value = True
43
+
44
+ def _normalize(self, task_score: float) -> float:
45
+ """Map raw `true_objective` success metrics to [0, 1]."""
46
+ r_min = self.min_return
47
+ r_max = self.max_return
48
+ if r_max <= r_min:
49
+ return 0.0
50
+ normalized = (task_score - r_min) / (r_max - r_min)
51
+ return float(np.clip(normalized, 0.0, 1.0))
52
+
53
+ def _update_logic(self, task_idx: int, task_score: float) -> None:
54
+ """Update normalized LP EMAs plus a raw `true_objective` EMA."""
55
+ p = self._normalize(task_score)
56
+ alpha = self.EMA_ALPHA
57
+
58
+ if self._p_seen[task_idx] == 0.0:
59
+ self._p_fast[task_idx] = p
60
+ self._p_slow[task_idx] = p
61
+ self._p_true[task_idx] = task_score
62
+ self._p_seen[task_idx] = 1.0
63
+ else:
64
+ old_fast = self._p_fast[task_idx]
65
+ old_true = self._p_true[task_idx]
66
+ self._p_fast[task_idx] = p * alpha + old_fast * (1.0 - alpha)
67
+ self._p_slow[task_idx] = self._p_fast[task_idx] * alpha + self._p_slow[task_idx] * (1.0 - alpha)
68
+ self._p_true[task_idx] = task_score * alpha + old_true * (1.0 - alpha)
69
+
70
+ def _reweight(self, p: np.ndarray) -> np.ndarray:
71
+ numerator = p * (1.0 - self.p_theta)
72
+ denominator = p + self.p_theta * (1.0 - 2.0 * p)
73
+ return numerator / denominator
74
+
75
+ def _recompute_weights(self) -> None:
76
+ seen = np.array(self._p_seen[:]) > 0.0
77
+ unseen = ~seen
78
+
79
+ # LP is undefined until every task has at least some signal.
80
+ # Keep the curriculum uniform during this cold-start phase instead of
81
+ # forcing unseen-only sweeps or collapsing onto the first updated task.
82
+ if unseen.any():
83
+ self._weights[:] = (np.ones(self._n) / self._n).tolist()
84
+ return
85
+
86
+ fast = np.array(self._p_fast[:])
87
+ slow = np.array(self._p_slow[:])
88
+ # Compute learning progress as absolute difference of reweighted EMAs
89
+ learning_progress = np.abs(self._reweight(fast) - self._reweight(slow))
90
+ # Keep tasks with any normalized success signal eligible when LP is flat.
91
+ # `_p_true` stores raw objectives for OMNI ordering, so using its sign here
92
+ # would incorrectly bias sampling toward tasks whose reward scale is positive.
93
+ posidxs = [i for i, lp in enumerate(learning_progress) if lp > 0.0 or fast[i] > 0.0]
94
+ any_progress = len(posidxs) > 0
95
+ subprobs = learning_progress[posidxs] if any_progress else learning_progress
96
+
97
+ std = np.std(subprobs)
98
+ # z-score
99
+ subprobs = (subprobs - np.mean(subprobs)) / (std if std else 1.0)
100
+ # sigmoid
101
+ subprobs = 1.0 / (1.0 + np.exp(-subprobs))
102
+ # normalize
103
+ subprobs = subprobs / (np.sum(subprobs))
104
+
105
+ if any_progress:
106
+ task_dist = np.zeros(self._n)
107
+ task_dist[posidxs] = subprobs
108
+ else:
109
+ task_dist = subprobs
110
+
111
+ if self.uniform_prob > 0.0:
112
+ uniform = np.ones(self._n) / self._n
113
+ task_dist = (1.0 - self.uniform_prob) * task_dist + self.uniform_prob * uniform
114
+
115
+ task_dist = task_dist / np.sum(task_dist)
116
+ self._weights[:] = task_dist.tolist()
117
+
118
+ def task_returns(self) -> np.ndarray:
119
+ with self._lock:
120
+ return np.array(self._p_fast[:])
121
+
122
+ def _state_dict_locked(self) -> dict:
123
+ return {
124
+ "stale_dist": bool(self._stale_dist.value),
125
+ "p_fast": list(self._p_fast[:]),
126
+ "p_slow": list(self._p_slow[:]),
127
+ "p_true": list(self._p_true[:]),
128
+ "p_seen": list(self._p_seen[:]),
129
+ }
130
+
131
+ def _load_state_dict_locked(self, state: dict) -> None:
132
+ self._stale_dist.value = bool(state.get("stale_dist", True))
133
+ self._p_fast[:] = [float(value) for value in state.get("p_fast", self._p_fast[:])]
134
+ self._p_slow[:] = [float(value) for value in state.get("p_slow", self._p_slow[:])]
135
+ self._p_true[:] = [float(value) for value in state.get("p_true", self._p_true[:])]
136
+ self._p_seen[:] = [float(value) for value in state.get("p_seen", self._p_seen[:])]
@@ -0,0 +1,100 @@
1
+ import json
2
+ import os
3
+ from os.path import join
4
+
5
+ import numpy as np
6
+ import torch
7
+
8
+ from sample_factory.algo.runners.runner import AlgoObserver
9
+ from sample_factory.algo.learning.learner import Learner
10
+ from sample_factory.utils.utils import experiment_dir, log
11
+
12
+
13
+ def curriculum_state_file(cfg) -> str:
14
+ return join(experiment_dir(cfg), "curriculum_state.json")
15
+
16
+
17
+ def save_curriculum_state(cfg, curriculum) -> None:
18
+ if curriculum is None:
19
+ return
20
+
21
+ state_path = curriculum_state_file(cfg)
22
+ tmp_path = f"{state_path}.tmp"
23
+ with open(tmp_path, "w") as state_file:
24
+ json.dump(curriculum.state_dict(), state_file, indent=2)
25
+ os.replace(tmp_path, state_path)
26
+
27
+
28
+ def load_curriculum_state(cfg, curriculum) -> bool:
29
+ if curriculum is None:
30
+ return False
31
+
32
+ state_path = curriculum_state_file(cfg)
33
+ if not os.path.isfile(state_path):
34
+ return False
35
+
36
+ with open(state_path, "r") as state_file:
37
+ state = json.load(state_file)
38
+
39
+ curriculum.load_state_dict(state)
40
+ return True
41
+
42
+
43
+ class CurriculumObserver(AlgoObserver):
44
+ def __init__(self, curriculum):
45
+ self.curriculum = curriculum
46
+
47
+ def on_init(self, runner) -> None:
48
+ return None
49
+
50
+ def on_training_step(self, runner, training_iteration_since_resume: int) -> None:
51
+ return None
52
+
53
+ def on_stop(self, runner) -> None:
54
+ return None
55
+
56
+ def extra_summaries(self, runner, policy_id, writer, env_steps) -> None:
57
+ if policy_id != 0 or self.curriculum is None:
58
+ return
59
+
60
+ weights = self.curriculum.task_weights()
61
+ if weights.size > 0:
62
+ nonzero_weights = weights[weights > 0]
63
+ if nonzero_weights.size > 0:
64
+ entropy = float(-(nonzero_weights * np.log(nonzero_weights)).sum())
65
+ writer.add_scalar("curriculum/entropy", entropy, env_steps)
66
+ writer.add_scalar("curriculum/nonzero_probability_tasks", int(np.count_nonzero(weights)), env_steps)
67
+
68
+ task_returns = self.curriculum.task_returns()
69
+ if task_returns.size > 0 and not np.allclose(task_returns, 0.0):
70
+ writer.add_scalar("curriculum/mean_task_return", float(np.mean(task_returns)), env_steps)
71
+
72
+ metrics = self.curriculum.metrics()
73
+ for key, value in metrics.items():
74
+ if np.isscalar(value):
75
+ writer.add_scalar(f"curriculum/{key}", float(value), env_steps)
76
+ elif isinstance(value, np.ndarray) and value.size > 0 and np.issubdtype(value.dtype, np.number):
77
+ writer.add_scalar(f"curriculum/mean_{key}", float(np.mean(value)), env_steps)
78
+
79
+
80
+ def restore_curriculum_state(cfg, curriculum) -> None:
81
+ if curriculum is None or cfg.restart_behavior != "resume":
82
+ return
83
+
84
+ checkpoint_dir = Learner.checkpoint_dir(cfg, 0)
85
+ load_kind = getattr(cfg, "load_checkpoint_kind", "latest")
86
+ pattern = f"{dict(latest='checkpoint', best='best')[load_kind]}_*"
87
+ checkpoints = Learner.get_checkpoints(checkpoint_dir, pattern=pattern)
88
+ if not checkpoints:
89
+ return
90
+
91
+ checkpoint_dict = Learner.load_checkpoint(checkpoints, torch.device("cpu"))
92
+ if checkpoint_dict is None:
93
+ return
94
+
95
+ state = checkpoint_dict.get("curriculum_state", None)
96
+ if state is None:
97
+ raise RuntimeError("Checkpoint is missing curriculum_state; cannot safely resume curriculum-enabled training")
98
+
99
+ curriculum.load_state_dict(state)
100
+ log.info("Restored curriculum state from checkpoint %s", checkpoints[-1])
@@ -0,0 +1,74 @@
1
+ import numpy as np
2
+ from typing import Optional
3
+ from comrad.curriculum.learning_progress import LearningProgress
4
+
5
+ class OMNICurriculum(LearningProgress):
6
+ """
7
+ Syllabus-inspired OMNI curriculum tracking fast and slow EMAs masked by tasks dependencies
8
+ suppressing mathematically boring tasks once prerequisites are met.
9
+ """
10
+
11
+ def __init__(self, n_tasks: int, tasks: list, interestingness: Optional[dict] = None, **kwargs):
12
+ super().__init__(n_tasks, tasks=tasks, **kwargs)
13
+ self.tasks = tasks or list(range(n_tasks))
14
+ if interestingness is not None:
15
+ bad = set(interestingness.keys()) - set(self.tasks)
16
+ if bad:
17
+ raise ValueError(
18
+ f"interestingness keys not in tasks: {bad}. "
19
+ f"Use task identifiers only."
20
+ )
21
+ self._interestingness = {
22
+ task: {other: bool(is_interesting) for other, is_interesting in mapping.items()}
23
+ for task, mapping in interestingness.items()
24
+ }
25
+ else:
26
+ # Default: mastery of a task makes itself boring, but other tasks remain interesting.
27
+ self._interestingness = {
28
+ t: {other: (other != t) for other in self.tasks}
29
+ for t in self.tasks
30
+ }
31
+
32
+ def _recompute_weights(self) -> None:
33
+ """Combine LP weight calculation with "boring" penalty mask."""
34
+ super()._recompute_weights() # Update weights purely by LP
35
+
36
+ lp_dist = np.array(self._weights[:])
37
+ mastery = np.array(self._p_fast[:])
38
+
39
+ # OMNI prerequisite masking must be ordered by a comparable mastery signal.
40
+ # Raw true_objective values can differ wildly across tasks, so use the
41
+ # normalized fast EMA from LearningProgress instead.
42
+ tasks_by_success = np.argsort(mastery)
43
+ interesting_tasks = set()
44
+ boring_tasks = set()
45
+
46
+ for task_idx in tasks_by_success[::-1]:
47
+ if task_idx in interesting_tasks or task_idx in boring_tasks:
48
+ continue
49
+ interesting_tasks.add(task_idx)
50
+
51
+ task = self.tasks[task_idx]
52
+ if task not in self._interestingness:
53
+ continue
54
+
55
+ for other_idx, other_task in enumerate(self.tasks):
56
+ if other_task not in self._interestingness[task]:
57
+ boring_tasks.add(other_idx)
58
+ continue
59
+ if (
60
+ other_idx not in interesting_tasks
61
+ and other_idx not in boring_tasks
62
+ and not self._interestingness[task][other_task]
63
+ ):
64
+ boring_tasks.add(other_idx)
65
+
66
+ # Build weight mask where boring items are heavily penalized
67
+ moi_weight = np.ones(self._n)
68
+ for i in boring_tasks:
69
+ moi_weight[i] = 0.001
70
+
71
+ dist = lp_dist * moi_weight
72
+ norm_dist = dist / dist.sum() if dist.sum() > 0 else np.ones(self._n) / self._n
73
+
74
+ self._weights[:] = norm_dist.tolist()
@@ -0,0 +1,208 @@
1
+ import numpy as np
2
+ from comrad.curriculum.base import Curriculum
3
+
4
+ class PrioritizedLevelReplay(Curriculum):
5
+ """
6
+ Syllabus-inspired Prioritized Level Replay (PLR) tracking PPO rollout metrics natively.
7
+ Replays tasks proportional to learning potential metrics and random unseen tasks.
8
+ """
9
+
10
+ def __init__(
11
+ self,
12
+ n_tasks: int,
13
+ staleness_coef: float = 0.1,
14
+ score_transform: str = "rank",
15
+ temperature: float = 0.1,
16
+ staleness_transform: str = "power",
17
+ staleness_temperature: float = 1.0,
18
+ plr_score_key: str = "mean_value_l1",
19
+ alpha: float = 1.0,
20
+ replay_schedule: str = "proportionate",
21
+ replay_prob: float = 0.5,
22
+ rho: float = 1.0,
23
+ max_score_coef: float = 0.0,
24
+ eps: float = 0.05,
25
+ **kwargs
26
+ ):
27
+ super().__init__(n_tasks, **kwargs)
28
+ self._staleness_coef = staleness_coef
29
+ self._score_transform = score_transform
30
+ self._temperature = temperature
31
+ self._staleness_transform = staleness_transform
32
+ self._staleness_temperature = staleness_temperature
33
+ self._plr_score_key = plr_score_key
34
+ self._alpha = alpha
35
+ self._replay_schedule = replay_schedule
36
+ self._replay_prob = replay_prob
37
+ self._rho = rho
38
+ self._max_score_coef = max_score_coef
39
+ self._eps = eps
40
+
41
+ self._task_scores = self.ctx.Array('d', [0.0] * n_tasks)
42
+ self._task_score_steps = self.ctx.Array('l', [0] * n_tasks)
43
+ self._task_staleness = self.ctx.Array('d', [0.0] * n_tasks)
44
+ self._unseen_task_weights = self.ctx.Array('d', [1.0] * n_tasks)
45
+
46
+ def _update_logic(self, task_idx: int, episode_return: float) -> None:
47
+ """Called conventionally by workers. PLR relies on central PPO learner metrics natively."""
48
+ pass
49
+
50
+ def update_task_score(self, task_idx: int, score: dict, num_steps: int = 1) -> None:
51
+ """Inject from sample factory using native PLR learner loops."""
52
+ raw = float(score.get(self._plr_score_key, 0.0))
53
+ raw_max = float(score.get(self._plr_score_key.replace("mean_", "max_"), raw))
54
+ total_score = self._max_score_coef * raw_max + (1.0 - self._max_score_coef) * raw
55
+ num_steps = max(int(num_steps), 1)
56
+
57
+ with self._lock:
58
+ old = self._task_scores[task_idx]
59
+ self._task_scores[task_idx] = (1.0 - self._alpha) * old + self._alpha * total_score
60
+ self._task_score_steps[task_idx] += num_steps
61
+ self._unseen_task_weights[task_idx] = 0.0
62
+ self._recompute_weights()
63
+
64
+ def _sample_logic(self) -> int:
65
+ """Override to implement complex replay schedule logic directly inside lock."""
66
+ proportion_seen = self._proportion_seen()
67
+
68
+ if proportion_seen < self._rho:
69
+ unseen = np.array(self._unseen_task_weights[:])
70
+ if (unseen > 0).any():
71
+ return self._sample_unseen_level(unseen)
72
+ return self._sample_replay_level() # fallback to replay if all seen but still under rho
73
+
74
+ do_replay = self._sample_replay_decision(proportion_seen)
75
+ if do_replay:
76
+ return self._sample_replay_level()
77
+ else:
78
+ unseen = np.array(self._unseen_task_weights[:])
79
+ if (unseen > 0).any():
80
+ return self._sample_unseen_level(unseen)
81
+ return self._sample_replay_level()
82
+
83
+ def _proportion_seen(self) -> float:
84
+ unseen = np.array(self._unseen_task_weights[:])
85
+ return (self._n - (unseen > 0).sum()) / self._n
86
+
87
+ def _sample_replay_decision(self, proportion_seen: float) -> bool:
88
+ if self._replay_schedule == "fixed":
89
+ if proportion_seen >= self._rho:
90
+ if self._random() < self._replay_prob or proportion_seen >= 1.0:
91
+ return True
92
+ return False
93
+ else: # proportionate
94
+ if proportion_seen >= self._rho and self._random() < min(proportion_seen, self._replay_prob):
95
+ return True
96
+ return False
97
+
98
+ def _sample_replay_level(self) -> int:
99
+ weights = self._calculate_score_weights()
100
+ task_idx = self._choice(self._n, p=weights)
101
+ self._update_staleness(task_idx)
102
+ return task_idx
103
+
104
+ def _sample_unseen_level(self, unseen: np.ndarray) -> int:
105
+ unseen_w = unseen / unseen.sum()
106
+ task_idx = self._choice(self._n, p=unseen_w)
107
+ self._update_staleness(task_idx)
108
+ return task_idx
109
+
110
+ def _update_staleness(self, selected_idx: int) -> None:
111
+ if self._staleness_coef > 0:
112
+ staleness = np.array(self._task_staleness[:])
113
+ staleness += 1.0
114
+ staleness[selected_idx] = 0.0
115
+ self._task_staleness[:] = staleness.tolist()
116
+ self._recompute_weights()
117
+
118
+ def _calculate_score_weights(self) -> np.ndarray:
119
+ scores = np.array(self._task_scores[:])
120
+ unseen = np.array(self._unseen_task_weights[:])
121
+ staleness = np.array(self._task_staleness[:])
122
+
123
+ weights = self._apply_score_transform(self._score_transform, self._temperature, scores)
124
+ weights *= (1.0 - unseen)
125
+ z = weights.sum()
126
+ if z > 0:
127
+ weights /= z
128
+ else:
129
+ seen = (unseen == 0.0)
130
+ weights = seen.astype(float)
131
+ if seen.any():
132
+ weights /= weights.sum()
133
+
134
+ if self._staleness_coef > 0:
135
+ staleness_w = self._apply_score_transform(
136
+ self._staleness_transform, self._staleness_temperature, staleness
137
+ )
138
+ staleness_w *= (1.0 - unseen)
139
+ z_s = staleness_w.sum()
140
+ if z_s > 0:
141
+ staleness_w /= z_s
142
+ else:
143
+ staleness_w = (1.0 / self._n) * (1.0 - unseen)
144
+ weights = (1.0 - self._staleness_coef) * weights + self._staleness_coef * staleness_w
145
+
146
+ total = weights.sum()
147
+ return weights / total if total > 0 else weights
148
+
149
+ def _recompute_weights(self) -> None:
150
+ self._weights[:] = self._calculate_score_weights().tolist()
151
+
152
+ def _apply_score_transform(self, transform: str, temperature: float, scores: np.ndarray) -> np.ndarray:
153
+ if transform == "rank":
154
+ temp = np.flip(scores.argsort())
155
+ ranks = np.empty_like(temp, dtype=float)
156
+ ranks[temp] = np.arange(len(temp)) + 1
157
+ return 1.0 / (ranks ** (1.0 / temperature))
158
+ elif transform == "power":
159
+ eps = 0.0 if self._staleness_coef > 0 else 1e-3
160
+ return (np.clip(scores, 0, None) + eps) ** (1.0 / temperature)
161
+ elif transform == "softmax":
162
+ return np.exp(scores / temperature)
163
+ elif transform == "max":
164
+ w = np.zeros_like(scores)
165
+ scores_ = scores.copy()
166
+ scores_[np.array(self._unseen_task_weights[:]) > 0] = -np.inf
167
+ argmax_candidates = np.flatnonzero(np.isclose(scores_, scores_.max()))
168
+ argmax = argmax_candidates[self._choice(len(argmax_candidates))]
169
+ w[argmax] = 1.0
170
+ return w
171
+ elif transform == "eps_greedy":
172
+ w = np.zeros_like(scores)
173
+ w[scores.argmax()] = 1.0 - self._eps
174
+ w += self._eps / self._n
175
+ return w
176
+ return np.ones_like(scores)
177
+
178
+ def task_scores(self) -> np.ndarray:
179
+ with self._lock:
180
+ return np.array(self._task_scores[:])
181
+
182
+ def metrics(self) -> dict:
183
+ with self._lock:
184
+ unseen = np.array(self._unseen_task_weights[:])
185
+ return {
186
+ "task_scores": np.array(self._task_scores[:]),
187
+ "task_score_steps": np.array(self._task_score_steps[:]),
188
+ "unseen_task_weights": unseen,
189
+ "task_staleness": np.array(self._task_staleness[:]),
190
+ "proportion_seen": self._proportion_seen(),
191
+ }
192
+
193
+ def _state_dict_locked(self) -> dict:
194
+ return {
195
+ "task_scores": list(self._task_scores[:]),
196
+ "task_score_steps": list(self._task_score_steps[:]),
197
+ "task_staleness": list(self._task_staleness[:]),
198
+ "unseen_task_weights": list(self._unseen_task_weights[:]),
199
+ }
200
+
201
+ def _load_state_dict_locked(self, state: dict) -> None:
202
+ self._task_scores[:] = [float(value) for value in state.get("task_scores", self._task_scores[:])]
203
+ self._task_score_steps[:] = [int(value) for value in state.get("task_score_steps", self._task_score_steps[:])]
204
+ self._task_staleness[:] = [float(value) for value in state.get("task_staleness", self._task_staleness[:])]
205
+ self._unseen_task_weights[:] = [
206
+ float(value) for value in state.get("unseen_task_weights", self._unseen_task_weights[:])
207
+ ]
208
+ self._recompute_weights()