MASA-Safe-RL 0.3.2__tar.gz → 0.3.4__tar.gz

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 (216) hide show
  1. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4/MASA_Safe_RL.egg-info}/PKG-INFO +1 -1
  2. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/MASA_Safe_RL.egg-info/SOURCES.txt +1 -0
  3. {masa_safe_rl-0.3.2/MASA_Safe_RL.egg-info → masa_safe_rl-0.3.4}/PKG-INFO +1 -1
  4. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/on_policy/trpo.py +3 -3
  5. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/tabular/recreg.py +7 -2
  6. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/CPO.yaml +0 -5
  7. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/PPOLag.yaml +0 -6
  8. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/ProbShieldPPO.yaml +1 -6
  9. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/RECREG_EXACT.yaml +1 -1
  10. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/RECREG_MODEL_BASED.yaml +1 -1
  11. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/recreg.yaml +1 -1
  12. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/__main__.py +5 -15
  13. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/api.py +26 -3
  14. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/config.py +58 -7
  15. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/pipeline.py +7 -23
  16. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/plot.py +117 -2
  17. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/processing.py +139 -11
  18. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/render.py +0 -4
  19. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/run.py +5 -8
  20. masa_safe_rl-0.3.4/masa/plotting/source_registry.py +130 -0
  21. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/sources.py +42 -85
  22. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/prob_shield/parameterized_ppo.py +1 -1
  23. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/pyproject.toml +1 -1
  24. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/LICENSE +0 -0
  25. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/MANIFEST.in +0 -0
  26. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/MASA_Safe_RL.egg-info/dependency_links.txt +0 -0
  27. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/MASA_Safe_RL.egg-info/entry_points.txt +0 -0
  28. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/MASA_Safe_RL.egg-info/requires.txt +0 -0
  29. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/MASA_Safe_RL.egg-info/top_level.txt +0 -0
  30. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/README.md +0 -0
  31. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/__init__.py +0 -0
  32. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/__init__.py +0 -0
  33. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/on_policy/__init__.py +0 -0
  34. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/on_policy/a2c.py +0 -0
  35. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/on_policy/abstract_classes.py +0 -0
  36. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/on_policy/cpo.py +0 -0
  37. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/on_policy/ppo.py +0 -0
  38. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/on_policy/ppo_lag.py +0 -0
  39. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/on_policy/prob_shield_ppo.py +0 -0
  40. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/on_policy/trpo_lag.py +0 -0
  41. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/tabular/__init__.py +0 -0
  42. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/tabular/base.py +0 -0
  43. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/tabular/lcrl.py +0 -0
  44. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/tabular/q_learning.py +0 -0
  45. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/tabular/q_learning_lambda.py +0 -0
  46. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/tabular/recovery_rl.py +0 -0
  47. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/tabular/recreg_exact.py +0 -0
  48. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/tabular/recreg_model_based.py +0 -0
  49. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/algorithms/tabular/sem.py +0 -0
  50. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/cli/__init__.py +0 -0
  51. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/cli/cli_app.py +0 -0
  52. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/__init__.py +0 -0
  53. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/base_class.py +0 -0
  54. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/buffers.py +0 -0
  55. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/configs.py +0 -0
  56. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/constraints/__init__.py +0 -0
  57. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/constraints/base.py +0 -0
  58. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/constraints/cmdp.py +0 -0
  59. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/constraints/ltl_safety.py +0 -0
  60. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/constraints/multi_agent/__init__.py +0 -0
  61. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/constraints/multi_agent/cmg.py +0 -0
  62. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/constraints/pctl.py +0 -0
  63. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/constraints/prob.py +0 -0
  64. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/constraints/reach_avoid.py +0 -0
  65. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/dummy.py +0 -0
  66. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/label_fn.py +0 -0
  67. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/labelled_env.py +0 -0
  68. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/labelled_pz_env.py +0 -0
  69. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/layers.py +0 -0
  70. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/ltl.py +0 -0
  71. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/metrics.py +0 -0
  72. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/notebook_play.py +0 -0
  73. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/on_policy_algorithm.py +0 -0
  74. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/pctl.py +0 -0
  75. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/pettingzoo_record_video.py +0 -0
  76. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/policies.py +0 -0
  77. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/registry.py +0 -0
  78. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/running_mean_std.py +0 -0
  79. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/schedule.py +0 -0
  80. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/utils.py +0 -0
  81. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/common/wrappers.py +0 -0
  82. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/QL.yaml +0 -0
  83. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/QL_LAMBDA.yaml +0 -0
  84. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/TRPO.yaml +0 -0
  85. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/TRPOLag.yaml +0 -0
  86. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/a2c.yaml +0 -0
  87. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/lcrl.yaml +0 -0
  88. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/ppo.yaml +0 -0
  89. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/recovery_rl.yaml +0 -0
  90. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/algorithms/sem.yaml +0 -0
  91. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/defaults.yaml +0 -0
  92. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/BridgeCrossing.yaml +0 -0
  93. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/BridgeCrossingV2.yaml +0 -0
  94. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/ColourBombGridWorld.yaml +0 -0
  95. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/ColourBombGridWorldV2.yaml +0 -0
  96. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/ColourBombGridWorldV3.yaml +0 -0
  97. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/ColourGridWorld.yaml +0 -0
  98. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/ContinuousCartpole.yaml +0 -0
  99. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/ContinuousMountainCar.yaml +0 -0
  100. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/ConveyorBelt.yaml +0 -0
  101. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/DiscreteCartpole.yaml +0 -0
  102. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/DiscreteMountainCar.yaml +0 -0
  103. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/FrozenLake.yaml +0 -0
  104. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/IslandNavigation.yaml +0 -0
  105. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/MediaStreaming.yaml +0 -0
  106. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/MediaStreamingV2.yaml +0 -0
  107. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/MediaStreamingV3.yaml +0 -0
  108. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/MiniPacman.yaml +0 -0
  109. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/MiniPacmanWithCoins.yaml +0 -0
  110. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/Obstacle.yaml +0 -0
  111. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/ObstacleV2.yaml +0 -0
  112. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/ObstacleV3.yaml +0 -0
  113. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/ObstacleV4.yaml +0 -0
  114. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/PacmanWithCoins.yaml +0 -0
  115. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/Road1D.yaml +0 -0
  116. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/Road2D.yaml +0 -0
  117. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/pacman.yaml +0 -0
  118. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/configs/envs/sokoban.yaml +0 -0
  119. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/__init__.py +0 -0
  120. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/__init__.py +0 -0
  121. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/base.py +0 -0
  122. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/cartpole.py +0 -0
  123. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/mountain_car.py +0 -0
  124. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/obstacle.py +0 -0
  125. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/obstacle_v2.py +0 -0
  126. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/obstacle_v3.py +0 -0
  127. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/obstacle_v4.py +0 -0
  128. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/renderers/__init__.py +0 -0
  129. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/renderers/cartpole.py +0 -0
  130. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/renderers/mountain_car.py +0 -0
  131. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/renderers/obstacle.py +0 -0
  132. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/renderers/road_1d.py +0 -0
  133. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/renderers/road_2d.py +0 -0
  134. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/road_1d.py +0 -0
  135. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/continuous/road_2d.py +0 -0
  136. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/discrete/__init__.py +0 -0
  137. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/discrete/base.py +0 -0
  138. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/discrete/cartpole.py +0 -0
  139. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/discrete/conveyor_belt.py +0 -0
  140. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/discrete/island_navigation.py +0 -0
  141. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/discrete/mini_pacman_with_coins.py +0 -0
  142. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/discrete/mountain_car.py +0 -0
  143. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/discrete/pacman_with_coins.py +0 -0
  144. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/discrete/renderers/__init__.py +0 -0
  145. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/discrete/renderers/pacman.py +0 -0
  146. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/discrete/sokoban.py +0 -0
  147. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/multiagent/__init__.py +0 -0
  148. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/multiagent/matrix/__init__.py +0 -0
  149. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/multiagent/matrix/_label_utils.py +0 -0
  150. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/multiagent/matrix/bertrand.py +0 -0
  151. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/multiagent/matrix/chicken.py +0 -0
  152. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/multiagent/matrix/congestion.py +0 -0
  153. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/multiagent/matrix/dpgg.py +0 -0
  154. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/multiagent/matrix/inspection.py +0 -0
  155. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/__init__.py +0 -0
  156. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/base.py +0 -0
  157. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/bridge_crossing.py +0 -0
  158. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/bridge_crossing_v2.py +0 -0
  159. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/colour_bomb_grid_world.py +0 -0
  160. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/colour_bomb_grid_world_v2.py +0 -0
  161. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/colour_bomb_grid_world_v3.py +0 -0
  162. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/colour_grid_world.py +0 -0
  163. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/frozen_lake.py +0 -0
  164. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/media_streaming.py +0 -0
  165. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/media_streaming_v2.py +0 -0
  166. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/media_streaming_v3.py +0 -0
  167. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/mini_pacman.py +0 -0
  168. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/pacman.py +0 -0
  169. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/renderers/__init__.py +0 -0
  170. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/renderers/bridge_crossing.py +0 -0
  171. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/renderers/colour_bomb_grid_world.py +0 -0
  172. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/renderers/colour_grid_world.py +0 -0
  173. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/renderers/media_streaming.py +0 -0
  174. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/renderers/pacman.py +0 -0
  175. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/envs/tabular/utils.py +0 -0
  176. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/__init__.py +0 -0
  177. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/colour_bomb_grid_world/__init__.py +0 -0
  178. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/colour_bomb_grid_world/property_1.py +0 -0
  179. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/colour_bomb_grid_world/property_2.py +0 -0
  180. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/colour_bomb_grid_world/property_3.py +0 -0
  181. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/cpo_shield_example.py +0 -0
  182. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/norm_obs_example.py +0 -0
  183. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/prob_shield_cont_example.py +0 -0
  184. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/prob_shield_cont_ltl_example.py +0 -0
  185. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/prob_shield_example.py +0 -0
  186. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/prob_shield_frozen_lake_example.py +0 -0
  187. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/prob_shield_ltl_example.py +0 -0
  188. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/prob_shield_safety_abstraction_example.py +0 -0
  189. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/prob_shield_vec_ltl_example.py +0 -0
  190. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/record_video_example.py +0 -0
  191. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/reward_shaping_example.py +0 -0
  192. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/examples/wandb_example.py +0 -0
  193. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/__init__.py +0 -0
  194. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/io_utils.py +0 -0
  195. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/specs/__init__.py +0 -0
  196. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/specs/base.py +0 -0
  197. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/specs/margin.py +0 -0
  198. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/plotting/specs/performance.py +0 -0
  199. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/prob_shield/__init__.py +0 -0
  200. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/prob_shield/cpo_shield.py +0 -0
  201. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/prob_shield/eventual_discounted_vi.py +0 -0
  202. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/prob_shield/helpers.py +0 -0
  203. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/prob_shield/interval_bound_vi.py +0 -0
  204. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/prob_shield/parameterized_policy.py +0 -0
  205. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/prob_shield/parameterized_policy_v2.py +0 -0
  206. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/prob_shield/parameterized_ppo_v2.py +0 -0
  207. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/prob_shield/prob_shield_wrapper_v1.py +0 -0
  208. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/masa/prob_shield/prob_shield_wrapper_v2.py +0 -0
  209. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/setup.cfg +0 -0
  210. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/tests/test_cmg.py +0 -0
  211. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/tests/test_frozen_lake_shield.py +0 -0
  212. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/tests/test_notebook_play.py +0 -0
  213. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/tests/test_reach_avoid.py +0 -0
  214. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/tests/test_record_video.py +0 -0
  215. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/tests/test_sample.py +0 -0
  216. {masa_safe_rl-0.3.2 → masa_safe_rl-0.3.4}/tests/test_tutorial_notebooks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MASA-Safe-RL
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: A safe reinforcement library for providing a common interface for different constraints and environments
5
5
  Project-URL: Homepage, https://github.com/sacktock/MASA-Safe-RL
6
6
  Project-URL: Repository, https://github.com/sacktock/MASA-Safe-RL
@@ -188,6 +188,7 @@ masa/plotting/plot.py
188
188
  masa/plotting/processing.py
189
189
  masa/plotting/render.py
190
190
  masa/plotting/run.py
191
+ masa/plotting/source_registry.py
191
192
  masa/plotting/sources.py
192
193
  masa/plotting/specs/__init__.py
193
194
  masa/plotting/specs/base.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MASA-Safe-RL
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: A safe reinforcement library for providing a common interface for different constraints and environments
5
5
  Project-URL: Homepage, https://github.com/sacktock/MASA-Safe-RL
6
6
  Project-URL: Repository, https://github.com/sacktock/MASA-Safe-RL
@@ -30,10 +30,10 @@ class TRPO(OnPolicyAlgorithm):
30
30
  vf_coef: float = 1.0,
31
31
  max_grad_norm: float = 0.5,
32
32
  target_kl: float = 0.01,
33
- cg_iters: int = 10,
33
+ cg_iters: int = 15,
34
34
  cg_damping: float = 0.1,
35
- fvp_sample_freq: int = 4,
36
- line_search_steps: int = 10,
35
+ fvp_sample_freq: int = 1,
36
+ line_search_steps: int = 15,
37
37
  line_search_decay: float = 0.8,
38
38
  policy_class: type[BaseJaxPolicy] = PPOPolicy,
39
39
  policy_kwargs: Optional[dict[str, Any]] = None,
@@ -512,8 +512,13 @@ class RECREG(QL):
512
512
 
513
513
  if override:
514
514
  # Use boltzmann temperature=0.01 or epsilon=0.001 for backup policy
515
- safe_act = self._act(key3, obs, self.B, deterministic, 0.01, 0.001)
516
-
515
+ if self.exploration == "boltzmann":
516
+ safe_act = self._act(key3, obs, self.B, deterministic, 0.01, 0.001)
517
+ elif self.exploration == "epsilon_greedy":
518
+ safe_act = self._act(key3, obs, self.B, True, 0.01, 0.001)
519
+ else:
520
+ raise NotImplementedError(f"Unexpected exploration: {self.exploration}")
521
+
517
522
  return self.prepare_act(safe_act), self.prepare_act(act), override
518
523
 
519
524
  def act(self, key, obs, deterministic=False):
@@ -50,11 +50,6 @@ ColourBombGridWorldV2:
50
50
  cost_limit: 0.025
51
51
  cost_gamma: 0.95
52
52
 
53
- ColourBombGridWorldV2_Property1:
54
- gamma: 0.95
55
- cost_limit: 0.025
56
- cost_gamma: 0.95
57
-
58
53
  ColourBombGridWorldV2_Property2:
59
54
  gamma: 0.95
60
55
  cost_limit: 0.025
@@ -55,12 +55,6 @@ ColourBombGridWorldV2:
55
55
  cost_gamma: 0.95
56
56
  lagrangian_multiplier_init: 10.0
57
57
 
58
- ColourBombGridWorldV2_Property1:
59
- gamma: 0.95
60
- cost_limit: 0.025
61
- cost_gamma: 0.95
62
- lagrangian_multiplier_init: 10.0
63
-
64
58
  ColourBombGridWorldV2_Property2:
65
59
  gamma: 0.95
66
60
  cost_limit: 0.025
@@ -10,7 +10,7 @@ defaults:
10
10
  ent_coef: 0.0
11
11
  vf_coef: 1.0
12
12
  max_grad_norm: 0.5
13
- policy_kwargs: {net_arch: [64, 64], log_std_init: -1.0}
13
+ policy_kwargs: {net_arch: [64, 64], log_std_init: -1.0, conditional_beta_network: True}
14
14
  init_safety_bound: 0.01
15
15
  theta: 1e-15
16
16
  max_vi_steps: 10000
@@ -39,11 +39,6 @@ ColourBombGridWorldV2:
39
39
  gamma: 0.95
40
40
  init_safety_bound: 0.025
41
41
 
42
- ColourBombGridWorldV2_Property1:
43
- gamma: 0.95
44
- init_safety_bound: 0.025
45
- policy_kwargs: {log_std_init: -2.0}
46
-
47
42
  ColourBombGridWorldV2_Property2:
48
43
  gamma: 0.95
49
44
  init_safety_bound: 0.025
@@ -114,7 +114,7 @@ MiniPacman:
114
114
  safe_gamma: 0.95
115
115
  mode: exact
116
116
  model_checking: exact
117
- horizon: 9
117
+ horizon: 20
118
118
  safety_prob: 0.01
119
119
  cost_coef: 10.0
120
120
 
@@ -114,7 +114,7 @@ MiniPacman:
114
114
  safe_gamma: 0.95
115
115
  mode: model_based
116
116
  model_checking: exact
117
- horizon: 9
117
+ horizon: 20
118
118
  safety_prob: 0.01
119
119
  cost_coef: 10.0
120
120
 
@@ -114,7 +114,7 @@ MiniPacman:
114
114
  safe_gamma: 0.95
115
115
  model_free_alpha: 0.1
116
116
  mode: model_free
117
- horizon: 9
117
+ horizon: 20
118
118
  safety_prob: 0.01
119
119
  cost_coef: 10.0
120
120
 
@@ -1,17 +1,7 @@
1
- """CLI entry point.
1
+ """Benchmark pipeline CLI: load runs, aggregate quantiles, render PlotSpecs.
2
2
 
3
- Example (generic researcher, ships with the ``performance`` spec):
4
-
5
- python -m masa.plotting \\
6
- --config masa/plotting/configs/example.yaml \\
7
- --stages all
8
-
9
- Example (load extra specs from another module before resolving):
10
-
11
- python -m masa.plotting \\
12
- --config <your-yaml> \\
13
- --specs-from <your.spec.module> \\
14
- --stages process,render
3
+ python -m masa.plotting --config configs/example_wandb.yaml --stages all
4
+ python -m masa.plotting --config <yaml> --specs-from <module> --specs my_plot
15
5
  """
16
6
 
17
7
  from __future__ import annotations
@@ -55,10 +45,10 @@ def _preload_spec_modules(modules: Sequence[str]) -> None:
55
45
  def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
56
46
  p = argparse.ArgumentParser(
57
47
  prog="python -m masa.plotting",
58
- description="Download W&B runs, aggregate to quantiles, render figures from PlotSpecs.",
48
+ description="Load runs from W&B or TensorBoard, aggregate to quantiles, render figures from PlotSpecs.",
59
49
  )
60
50
  p.add_argument("--config", required=True, type=Path,
61
- help="Path to a YAML config (see configs/example.yaml).")
51
+ help="Path to a YAML config (see configs/example_wandb.yaml or example_tensorboard.yaml).")
62
52
  p.add_argument("--stages", default="all",
63
53
  help="Comma separated subset of {download,process,render}, or 'all'.")
64
54
  p.add_argument("--specs", default=None,
@@ -2,11 +2,13 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import os
5
6
  from typing import Dict, List, Optional, Tuple
6
7
 
7
8
  import matplotlib.pyplot as plt
8
9
 
9
- from masa.plotting.plot import plot_metrics
10
+ from masa.plotting.plot import plot_metrics, plot_quantile_metrics
11
+ from masa.plotting.processing import build_long_form_from_tb, build_quantile_frame
10
12
  from masa.plotting.sources import TensorBoardSource, WandBSource
11
13
 
12
14
 
@@ -17,7 +19,8 @@ def plot_run(
17
19
  wandb_project: Optional[str] = None,
18
20
  wandb_entity: Optional[str] = None,
19
21
  wandb_filters: Optional[dict] = None,
20
- run_filter: Optional[str] = None,
22
+ run_filter: Optional[str | List[str]] = None,
23
+ group_seeds: bool = False,
21
24
  # What to plot
22
25
  metrics: Optional[List[str]] = None,
23
26
  # Plotting options
@@ -66,9 +69,29 @@ def plot_run(
66
69
  if not tensorboard_logdir and not wandb_project:
67
70
  raise ValueError("Provide either tensorboard_logdir or wandb_project.")
68
71
 
72
+ if group_seeds and not tensorboard_logdir:
73
+ raise ValueError("group_seeds is only supported for TensorBoard sources.")
74
+
69
75
  if tensorboard_logdir:
70
76
  source = TensorBoardSource(tensorboard_logdir)
71
- df = source.load(metrics=metrics, run_filter=run_filter)
77
+ raw_df = source.load(metrics=metrics, run_filter=run_filter)
78
+ if group_seeds:
79
+ env = os.path.basename(tensorboard_logdir.rstrip("/\\"))
80
+ long_df = build_long_form_from_tb(raw_df, env=env)
81
+ q_df = build_quantile_frame(long_df)
82
+ return plot_quantile_metrics(
83
+ q_df,
84
+ metrics=metrics,
85
+ smooth_weight=smooth_weight,
86
+ title=title,
87
+ output_path=output_path,
88
+ figsize=figsize,
89
+ show_legend=show_legend,
90
+ show_axes=show_axes,
91
+ dpi=dpi,
92
+ palette=palette,
93
+ )
94
+ df = raw_df
72
95
  else:
73
96
  source = WandBSource(wandb_project, entity=wandb_entity)
74
97
  df = source.load(metrics=metrics, filters=wandb_filters, run_filter=run_filter)
@@ -7,6 +7,10 @@ from dataclasses import dataclass, field
7
7
  from pathlib import Path
8
8
  from typing import Optional
9
9
 
10
+ # Default run-directory schema for TensorBoard sources: the leaf run dir is
11
+ # named ``{variant}_{seed}`` (env comes from the parent directory).
12
+ _TB_RUN_SCHEMA = r"^(?P<variant>.+)_(?P<seed>\d+)$"
13
+
10
14
 
11
15
  @dataclass(frozen=True)
12
16
  class VariantStyle:
@@ -28,17 +32,20 @@ class RunSchema:
28
32
 
29
33
  @dataclass(frozen=True)
30
34
  class Config:
31
- wandb_entity: str
32
- wandb_project: str
33
35
  run_schema: RunSchema
34
36
  variants: list[VariantStyle]
35
37
  cache_dir: Path
36
38
  output_dir: Path
39
+ source_kind: str = "wandb" # "wandb" or "tensorboard"
40
+ wandb_entity: Optional[str] = None
41
+ wandb_project: Optional[str] = None
42
+ tensorboard_logdir: Optional[Path] = None
37
43
  metrics: Optional[list[str]] = None
38
44
  envs: Optional[list[str]] = None
39
45
  palette: str = "colorblind"
40
46
  force_download: bool = False
41
47
  seed_for_logged_quantiles: int = 1
48
+ max_step: Optional[int] = None
42
49
 
43
50
  def variant_by_name(self) -> dict[str, VariantStyle]:
44
51
  return {v.name: v for v in self.variants}
@@ -77,24 +84,68 @@ def load_config(path: str | Path) -> Config:
77
84
  ]
78
85
  variants = _fill_palette(variants, raw.get("palette", "colorblind"))
79
86
 
87
+ source_kind, wandb_entity, wandb_project, tb_logdir = _resolve_source(path, raw, _resolve)
88
+
80
89
  schema_raw = raw.get("run_schema") or {}
81
- schema = RunSchema(pattern=schema_raw.get("pattern", RunSchema.pattern))
90
+ if "pattern" in schema_raw:
91
+ schema = RunSchema(pattern=schema_raw["pattern"])
92
+ elif source_kind == "tensorboard":
93
+ schema = RunSchema(pattern=_TB_RUN_SCHEMA)
94
+ else:
95
+ schema = RunSchema()
96
+
97
+ output_dir = _resolve(raw["output_dir"])
98
+ cache_raw = raw.get("cache_dir")
99
+ cache_dir = _resolve(cache_raw) if cache_raw else output_dir / "_cache"
82
100
 
83
101
  return Config(
84
- wandb_entity=raw["wandb_entity"],
85
- wandb_project=raw["wandb_project"],
86
102
  run_schema=schema,
87
103
  variants=variants,
88
- cache_dir=_resolve(raw["cache_dir"]),
89
- output_dir=_resolve(raw["output_dir"]),
104
+ cache_dir=cache_dir,
105
+ output_dir=output_dir,
106
+ source_kind=source_kind,
107
+ wandb_entity=wandb_entity,
108
+ wandb_project=wandb_project,
109
+ tensorboard_logdir=tb_logdir,
90
110
  metrics=raw.get("metrics"),
91
111
  envs=raw.get("envs"),
92
112
  palette=raw.get("palette", "colorblind"),
93
113
  force_download=raw.get("force_download", False),
94
114
  seed_for_logged_quantiles=raw.get("seed_for_logged_quantiles", 1),
115
+ max_step=raw.get("max_step"),
95
116
  )
96
117
 
97
118
 
119
+ def _resolve_source(
120
+ path: Path, raw: dict, resolve
121
+ ) -> tuple[str, Optional[str], Optional[str], Optional[Path]]:
122
+ """Return ``(source_kind, wandb_entity, wandb_project, tensorboard_logdir)``.
123
+
124
+ Accepts an explicit ``source:`` block, or falls back to legacy top-level
125
+ ``wandb_entity`` / ``wandb_project`` keys (treated as a W&B source).
126
+ """
127
+ src = raw.get("source")
128
+ if src:
129
+ kind = src.get("kind", "wandb")
130
+ if kind == "wandb":
131
+ project = src.get("project")
132
+ if not project:
133
+ raise ValueError(f"{path}: source.kind=wandb requires source.project.")
134
+ return "wandb", src.get("entity"), project, None
135
+ if kind == "tensorboard":
136
+ logdir = src.get("logdir")
137
+ if not logdir:
138
+ raise ValueError(f"{path}: source.kind=tensorboard requires source.logdir.")
139
+ return "tensorboard", None, None, resolve(logdir)
140
+ raise ValueError(
141
+ f"{path}: unknown source.kind={kind!r}; expected 'wandb' or 'tensorboard'."
142
+ )
143
+ project = raw.get("wandb_project")
144
+ if not project:
145
+ raise ValueError(f"{path}: provide a 'source:' block, or top-level 'wandb_project'.")
146
+ return "wandb", raw.get("wandb_entity"), project, None
147
+
148
+
98
149
  def _fill_palette(variants: list[VariantStyle], palette_name: str) -> list[VariantStyle]:
99
150
  """Assign colours from a seaborn palette to any variant where colour is None."""
100
151
  missing_idx = [i for i, v in enumerate(variants) if v.colour is None]
@@ -9,8 +9,7 @@ import pandas as pd
9
9
 
10
10
  from .config import Config
11
11
  from .io_utils import get_logger
12
- from .processing import build_frames
13
- from .sources import CachedWandbSource
12
+ from .source_registry import get_source
14
13
  from .specs import PlotSpec, all_specs
15
14
  from .specs.base import RenderContext
16
15
 
@@ -21,19 +20,17 @@ class Pipeline:
21
20
  def __init__(self, config: Config):
22
21
  self.config = config
23
22
  self.log = get_logger()
24
-
25
- # -- individual stages ------------------------------------------------
23
+ self.source = get_source(config)
26
24
 
27
25
  def download(self) -> list[Path]:
28
26
  try:
29
- source = CachedWandbSource(self.config)
30
- return source.download()
27
+ return self.source.download()
31
28
  except Exception as e:
32
29
  self.log.error(f"download stage crashed: {e}")
33
30
  return []
34
31
 
35
32
  def process(self) -> tuple[pd.DataFrame, pd.DataFrame]:
36
- return build_frames(self.config)
33
+ return self.source.build_frames()
37
34
 
38
35
  def render(self,
39
36
  long_df: pd.DataFrame,
@@ -68,8 +65,6 @@ class Pipeline:
68
65
  outputs.append(path)
69
66
  return outputs
70
67
 
71
- # -- orchestration ----------------------------------------------------
72
-
73
68
  def run(self,
74
69
  specs: Optional[Sequence[PlotSpec]] = None,
75
70
  stages: Sequence[str] = VALID_STAGES,
@@ -104,8 +99,6 @@ class Pipeline:
104
99
 
105
100
  return []
106
101
 
107
- # -- helpers ----------------------------------------------------------
108
-
109
102
  def _select_envs(self, discovered: list[str]) -> list[str]:
110
103
  """Restrict to ``config.envs`` if set; otherwise return all discovered envs."""
111
104
  if not self.config.envs:
@@ -128,23 +121,14 @@ class Pipeline:
128
121
  def _dry_run(self, specs: Optional[Sequence[PlotSpec]]) -> None:
129
122
  cfg = self.config
130
123
  self.log.info("=== DRY RUN ===")
131
- self.log.info(f"W&B : {cfg.wandb_entity}/{cfg.wandb_project}")
132
- self.log.info(f"cache_dir : {cfg.cache_dir}")
124
+ self.log.info(f"source : {self.source.describe()}")
133
125
  self.log.info(f"output_dir : {cfg.output_dir}")
134
126
  self.log.info(f"variants : {[v.name for v in cfg.variants]}")
135
127
  self.log.info(f"palette : {cfg.palette}")
136
128
  chosen = specs if specs is not None else all_specs()
137
129
  self.log.info(f"specs : {[s.id for s in chosen]}")
138
- envs: list[str] = []
139
- if cfg.cache_dir.exists():
140
- pat = cfg.run_schema.compile()
141
- for p in sorted(cfg.cache_dir.glob("*.csv")):
142
- m = pat.match(p.stem)
143
- if m:
144
- envs.append(m.group("env"))
145
- envs = sorted(set(envs))
146
- envs = self._select_envs(envs)
147
- self.log.info(f"envs (cache) : {envs or '(none discovered yet)'}")
130
+ envs = self._select_envs(self.source.discover_envs())
131
+ self.log.info(f"envs : {envs or '(none discovered yet)'}")
148
132
  for spec in chosen:
149
133
  for env in envs or ["<env>"]:
150
134
  target = cfg.output_dir / spec.out_subdir / f"{spec.id}_{env}.png"
@@ -58,7 +58,6 @@ def _thousands_formatter() -> ticker.FuncFormatter:
58
58
  )
59
59
 
60
60
 
61
- # Core plotting
62
61
  def plot_metrics(
63
62
  df: pd.DataFrame,
64
63
  metrics: Optional[List[str]] = None,
@@ -102,6 +101,45 @@ def plot_metrics(
102
101
  )
103
102
 
104
103
 
104
+ def plot_quantile_metrics(
105
+ q_df: pd.DataFrame,
106
+ metrics: Optional[List[str]] = None,
107
+ smooth_weight: float = 0.6,
108
+ title: Optional[str] = None,
109
+ output_path: Optional[str] = None,
110
+ figsize: Tuple[int, int] = (7, 5),
111
+ show_legend: bool = True,
112
+ show_axes: bool = True,
113
+ dpi: int = 300,
114
+ palette: Optional[Dict[str, str]] = None,
115
+ ) -> plt.Figure:
116
+ """Plot q50 ± q25/q75 bands from a quantile DataFrame.
117
+
118
+ Args:
119
+ q_df: DataFrame with columns ``[variant, step, metric, q25, q50, q75]``
120
+ as produced by :func:`~masa.plotting.processing.build_quantile_frame`.
121
+ metrics: Metric keys to plot. ``None`` plots all found in *q_df*.
122
+ smooth_weight: EMA weight applied per ``(variant, metric)`` group.
123
+ title: Per-panel title override. ``None`` uses the metric name.
124
+ output_path: Save path. ``None`` skips saving.
125
+ figsize: ``(width, height)`` per subplot panel.
126
+ show_legend: Render a shared legend below the panels.
127
+ show_axes: Label axes.
128
+ dpi: Resolution for saved figures.
129
+ palette: Colour mapping ``{variant: colour}``. ``None`` uses colorblind.
130
+
131
+ Returns:
132
+ The matplotlib :class:`~matplotlib.figure.Figure`.
133
+ """
134
+ with plt.rc_context(_RC_PARAMS):
135
+ return _plot_quantile_inner(
136
+ q_df, metrics=metrics, smooth_weight=smooth_weight,
137
+ title=title, output_path=output_path, figsize=figsize,
138
+ show_legend=show_legend, show_axes=show_axes, dpi=dpi,
139
+ palette=palette,
140
+ )
141
+
142
+
105
143
  def _plot_metrics_inner(
106
144
  df: pd.DataFrame,
107
145
  *,
@@ -185,7 +223,84 @@ def _plot_metrics_inner(
185
223
  return fig
186
224
 
187
225
 
188
- # Pretty-printing helpers
226
+ def _plot_quantile_inner(
227
+ q_df: pd.DataFrame,
228
+ *,
229
+ metrics: Optional[List[str]],
230
+ smooth_weight: float,
231
+ title: Optional[str],
232
+ output_path: Optional[str],
233
+ figsize: Tuple[int, int],
234
+ show_legend: bool,
235
+ show_axes: bool,
236
+ dpi: int,
237
+ palette: Optional[Dict[str, str]],
238
+ ) -> plt.Figure:
239
+ if metrics is None:
240
+ metrics = sorted(q_df["metric"].unique().tolist())
241
+ if not metrics:
242
+ raise ValueError("No metrics to plot.")
243
+ if q_df.empty:
244
+ raise ValueError("The provided DataFrame is empty.")
245
+
246
+ variants = sorted(q_df["variant"].unique().tolist())
247
+ if palette is None:
248
+ colours: Dict[str, str] = dict(zip(variants, sns.color_palette("colorblind", len(variants))))
249
+ else:
250
+ colours = {v: palette.get(v, "#888888") for v in variants}
251
+
252
+ n = len(metrics)
253
+ fig, axes = plt.subplots(1, n, figsize=(figsize[0] * n, figsize[1]))
254
+ if n == 1:
255
+ axes = [axes]
256
+
257
+ for k, metric in enumerate(metrics):
258
+ ax = axes[k]
259
+ sub = q_df[q_df["metric"] == metric]
260
+ if sub.empty:
261
+ ax.set_title(metric)
262
+ continue
263
+ for variant in variants:
264
+ vsub = sub[sub["variant"] == variant].sort_values("step")
265
+ if vsub.empty:
266
+ continue
267
+ colour = colours.get(variant)
268
+ steps = vsub["step"].values
269
+ q50 = smooth(vsub["q50"].values, smooth_weight)
270
+ q25 = smooth(vsub["q25"].values, smooth_weight)
271
+ q75 = smooth(vsub["q75"].values, smooth_weight)
272
+ ax.plot(steps, q50, color=colour, linewidth=2.0, label=variant)
273
+ ax.fill_between(steps, q25, q75, color=colour, alpha=0.25, linewidth=0)
274
+
275
+ ax.set_title(_pretty_name(metric) if title is None else title)
276
+ if show_axes:
277
+ ax.set_ylabel(_pretty_name(metric))
278
+ ax.set_xlabel("Training steps (in thousands)")
279
+ ax.xaxis.set_major_formatter(_thousands_formatter())
280
+ else:
281
+ ax.set_ylabel("")
282
+ ax.set_xlabel("")
283
+ ax.tick_params(axis="both", which="both", top=True, right=True)
284
+
285
+ if show_legend:
286
+ handles, labels = axes[0].get_legend_handles_labels()
287
+ if handles:
288
+ fig.legend(
289
+ handles, labels, loc="upper center",
290
+ bbox_to_anchor=(0.5, 0.02), ncol=min(len(handles), 6),
291
+ frameon=True, edgecolor="white", fancybox=False,
292
+ columnspacing=1.5, handlelength=2.0,
293
+ )
294
+
295
+ fig.tight_layout(rect=[0, 0.08, 1, 1] if show_legend else [0, 0, 1, 1])
296
+
297
+ if output_path is not None:
298
+ os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)
299
+ fig.savefig(output_path, bbox_inches="tight", pad_inches=0.15, dpi=dpi)
300
+
301
+ return fig
302
+
303
+
189
304
  _PRETTY: Dict[str, str] = {
190
305
  "ep_reward": "Reward",
191
306
  "ep_length": "Episode Length",