holosoma 0.0.1__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.
- holosoma/__init__.py +0 -0
- holosoma/agents/base_algo/base_algo.py +155 -0
- holosoma/agents/callbacks/base_callback.py +21 -0
- holosoma/agents/callbacks/payload.py +172 -0
- holosoma/agents/callbacks/push.py +263 -0
- holosoma/agents/callbacks/recording.py +192 -0
- holosoma/agents/fast_sac/fast_sac.py +474 -0
- holosoma/agents/fast_sac/fast_sac_agent.py +1056 -0
- holosoma/agents/fast_sac/fast_sac_utils.py +385 -0
- holosoma/agents/modules/augmentation_utils.py +547 -0
- holosoma/agents/modules/data_utils.py +134 -0
- holosoma/agents/modules/logging_utils.py +446 -0
- holosoma/agents/modules/module_utils.py +52 -0
- holosoma/agents/modules/modules.py +407 -0
- holosoma/agents/modules/ppo_modules.py +186 -0
- holosoma/agents/modules/tests/test_augmentation_utils.py +366 -0
- holosoma/agents/modules/tests/test_logging_utils.py +187 -0
- holosoma/agents/modules/tests/test_module_dimensions.py +263 -0
- holosoma/agents/ppo/ppo.py +918 -0
- holosoma/bridge/__init__.py +45 -0
- holosoma/bridge/base/__init__.py +9 -0
- holosoma/bridge/base/basic_sdk2py_bridge.py +368 -0
- holosoma/bridge/booster/__init__.py +7 -0
- holosoma/bridge/booster/booster_sdk2py_bridge.py +124 -0
- holosoma/bridge/ros2/__init__.py +5 -0
- holosoma/bridge/ros2/ros2_bridge.py +135 -0
- holosoma/bridge/unitree/__init__.py +13 -0
- holosoma/bridge/unitree/tests/__init__.py +0 -0
- holosoma/bridge/unitree/tests/_fake_unitree_interface.py +146 -0
- holosoma/bridge/unitree/tests/test_unitree_mp_bridge.py +207 -0
- holosoma/bridge/unitree/unitree_sdk2py_bridge.py +129 -0
- holosoma/bridge/unitree/unitree_sdk2py_bridge_mp.py +320 -0
- holosoma/config_types/action.py +33 -0
- holosoma/config_types/algo.py +345 -0
- holosoma/config_types/command.py +131 -0
- holosoma/config_types/curriculum.py +36 -0
- holosoma/config_types/distribution.py +193 -0
- holosoma/config_types/env.py +80 -0
- holosoma/config_types/eval_callback.py +129 -0
- holosoma/config_types/experiment.py +227 -0
- holosoma/config_types/frequency.py +97 -0
- holosoma/config_types/full_sim.py +38 -0
- holosoma/config_types/logger.py +89 -0
- holosoma/config_types/observation.py +57 -0
- holosoma/config_types/plugin.py +385 -0
- holosoma/config_types/randomization.py +147 -0
- holosoma/config_types/reward.py +37 -0
- holosoma/config_types/robot.py +169 -0
- holosoma/config_types/run_sim.py +105 -0
- holosoma/config_types/scene.py +425 -0
- holosoma/config_types/sensor.py +292 -0
- holosoma/config_types/simulator.py +343 -0
- holosoma/config_types/termination.py +30 -0
- holosoma/config_types/terrain.py +216 -0
- holosoma/config_types/tests/test_frequency.py +143 -0
- holosoma/config_types/tests/test_link_physics.py +215 -0
- holosoma/config_types/tests/test_plugin_egress_config.py +79 -0
- holosoma/config_types/tests/test_sensor_config.py +63 -0
- holosoma/config_types/tests/test_tyro_cli.py +8 -0
- holosoma/config_types/video.py +156 -0
- holosoma/config_types/viewer.py +53 -0
- holosoma/config_values/__init__.py +1 -0
- holosoma/config_values/action.py +18 -0
- holosoma/config_values/algo.py +117 -0
- holosoma/config_values/command.py +24 -0
- holosoma/config_values/curriculum.py +22 -0
- holosoma/config_values/experiment.py +43 -0
- holosoma/config_values/loco/__init__.py +7 -0
- holosoma/config_values/loco/g1/__init__.py +7 -0
- holosoma/config_values/loco/g1/action.py +16 -0
- holosoma/config_values/loco/g1/command.py +40 -0
- holosoma/config_values/loco/g1/curriculum.py +59 -0
- holosoma/config_values/loco/g1/experiment.py +58 -0
- holosoma/config_values/loco/g1/observation.py +119 -0
- holosoma/config_values/loco/g1/randomization.py +106 -0
- holosoma/config_values/loco/g1/reward.py +193 -0
- holosoma/config_values/loco/g1/termination.py +21 -0
- holosoma/config_values/loco/t1/__init__.py +7 -0
- holosoma/config_values/loco/t1/action.py +16 -0
- holosoma/config_values/loco/t1/command.py +40 -0
- holosoma/config_values/loco/t1/curriculum.py +53 -0
- holosoma/config_values/loco/t1/experiment.py +60 -0
- holosoma/config_values/loco/t1/observation.py +119 -0
- holosoma/config_values/loco/t1/randomization.py +108 -0
- holosoma/config_values/loco/t1/reward.py +193 -0
- holosoma/config_values/loco/t1/termination.py +21 -0
- holosoma/config_values/logger.py +14 -0
- holosoma/config_values/observation.py +21 -0
- holosoma/config_values/plugin.py +150 -0
- holosoma/config_values/randomization.py +21 -0
- holosoma/config_values/reward.py +28 -0
- holosoma/config_values/robot.py +1128 -0
- holosoma/config_values/run_sim.py +95 -0
- holosoma/config_values/scene.py +70 -0
- holosoma/config_values/sensor.py +57 -0
- holosoma/config_values/simulator.py +117 -0
- holosoma/config_values/termination.py +20 -0
- holosoma/config_values/terrain.py +78 -0
- holosoma/config_values/tests/test_plugin_registry.py +150 -0
- holosoma/config_values/tests/test_registry.py +389 -0
- holosoma/config_values/wbt/__init__.py +1 -0
- holosoma/config_values/wbt/g1/__init__.py +7 -0
- holosoma/config_values/wbt/g1/command.py +82 -0
- holosoma/config_values/wbt/g1/curriculum.py +19 -0
- holosoma/config_values/wbt/g1/experiment.py +205 -0
- holosoma/config_values/wbt/g1/observation.py +141 -0
- holosoma/config_values/wbt/g1/randomization.py +154 -0
- holosoma/config_values/wbt/g1/reward.py +113 -0
- holosoma/config_values/wbt/g1/scene.py +16 -0
- holosoma/config_values/wbt/g1/sensor.py +110 -0
- holosoma/config_values/wbt/g1/termination.py +50 -0
- holosoma/data/__init__.py +1 -0
- holosoma/data/motions/g1_29dof/whole_body_tracking/motion_crawl_slope.npz +0 -0
- holosoma/data/motions/g1_29dof/whole_body_tracking/sub3_largebox_003_mj.npz +0 -0
- holosoma/data/motions/g1_29dof/whole_body_tracking/sub3_largebox_003_mj_w_obj.npz +0 -0
- holosoma/data/motions/g1_29dof/whole_body_tracking/terrain_slope.obj +61 -0
- holosoma/data/robots/g1/g1_29dof.urdf +1133 -0
- holosoma/data/robots/g1/g1_29dof.xml +486 -0
- holosoma/data/robots/g1/main_mesh_collision_halfspherehand.urdf +1128 -0
- holosoma/data/robots/g1/meshes/half_sphere.obj +114743 -0
- holosoma/data/robots/g1/meshes/head_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_G1_EE_pad.STL +0 -0
- holosoma/data/robots/g1/meshes/left_ankle_pitch_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_ankle_roll_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_elbow_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_elbow_link_merge.STL +0 -0
- holosoma/data/robots/g1/meshes/left_hand_index_0_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_hand_index_1_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_hand_middle_0_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_hand_middle_1_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_hand_palm_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_hand_thumb_0_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_hand_thumb_1_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_hand_thumb_2_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_hip_pitch_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_hip_roll_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_hip_yaw_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_knee_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_rubber_hand.STL +0 -0
- holosoma/data/robots/g1/meshes/left_shoulder_pitch_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_shoulder_roll_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_shoulder_yaw_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_wrist_pitch_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_wrist_roll_link.STL +0 -0
- holosoma/data/robots/g1/meshes/left_wrist_roll_rubber_hand.STL +0 -0
- holosoma/data/robots/g1/meshes/left_wrist_yaw_link.STL +0 -0
- holosoma/data/robots/g1/meshes/logo_link.STL +0 -0
- holosoma/data/robots/g1/meshes/pelvis.STL +0 -0
- holosoma/data/robots/g1/meshes/pelvis_contour_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_G1_EE_pad.STL +0 -0
- holosoma/data/robots/g1/meshes/right_ankle_pitch_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_ankle_roll_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_elbow_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_elbow_link_merge.STL +0 -0
- holosoma/data/robots/g1/meshes/right_hand_index_0_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_hand_index_1_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_hand_middle_0_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_hand_middle_1_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_hand_palm_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_hand_thumb_0_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_hand_thumb_1_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_hand_thumb_2_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_hip_pitch_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_hip_roll_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_hip_yaw_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_knee_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_rubber_hand.STL +0 -0
- holosoma/data/robots/g1/meshes/right_shoulder_pitch_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_shoulder_roll_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_shoulder_yaw_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_wrist_pitch_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_wrist_roll_link.STL +0 -0
- holosoma/data/robots/g1/meshes/right_wrist_roll_rubber_hand.STL +0 -0
- holosoma/data/robots/g1/meshes/right_wrist_yaw_link.STL +0 -0
- holosoma/data/robots/g1/meshes/torso_constraint_L_link.STL +0 -0
- holosoma/data/robots/g1/meshes/torso_constraint_L_rod_link.STL +0 -0
- holosoma/data/robots/g1/meshes/torso_constraint_R_link.STL +0 -0
- holosoma/data/robots/g1/meshes/torso_constraint_R_rod_link.STL +0 -0
- holosoma/data/robots/g1/meshes/torso_link.STL +0 -0
- holosoma/data/robots/g1/meshes/torso_link_23dof_rev_1_0.STL +0 -0
- holosoma/data/robots/g1/meshes/torso_link_rev_1_0.STL +0 -0
- holosoma/data/robots/g1/meshes/tote_no_bar.STL +0 -0
- holosoma/data/robots/g1/meshes/waist_constraint_L.STL +0 -0
- holosoma/data/robots/g1/meshes/waist_constraint_R.STL +0 -0
- holosoma/data/robots/g1/meshes/waist_roll_link.STL +0 -0
- holosoma/data/robots/g1/meshes/waist_roll_link_rev_1_0.STL +0 -0
- holosoma/data/robots/g1/meshes/waist_support_link.STL +0 -0
- holosoma/data/robots/g1/meshes/waist_yaw_link.STL +0 -0
- holosoma/data/robots/g1/meshes/waist_yaw_link_rev_1_0.STL +0 -0
- holosoma/data/robots/g1/scenes/scene_g1_29dof_wbt_plane.xml +26 -0
- holosoma/data/robots/onelink_box/onelink_box.urdf +45 -0
- holosoma/data/robots/onelink_box/onelink_box.xml +10 -0
- holosoma/data/robots/t1/meshes/AL1.STL +0 -0
- holosoma/data/robots/t1/meshes/AL2.STL +0 -0
- holosoma/data/robots/t1/meshes/AL3.STL +0 -0
- holosoma/data/robots/t1/meshes/AL4.STL +0 -0
- holosoma/data/robots/t1/meshes/AL5.STL +0 -0
- holosoma/data/robots/t1/meshes/AL6.STL +0 -0
- holosoma/data/robots/t1/meshes/AL7.STL +0 -0
- holosoma/data/robots/t1/meshes/AR1.STL +0 -0
- holosoma/data/robots/t1/meshes/AR2.STL +0 -0
- holosoma/data/robots/t1/meshes/AR3.STL +0 -0
- holosoma/data/robots/t1/meshes/AR4.STL +0 -0
- holosoma/data/robots/t1/meshes/AR5.STL +0 -0
- holosoma/data/robots/t1/meshes/AR6.STL +0 -0
- holosoma/data/robots/t1/meshes/AR7.STL +0 -0
- holosoma/data/robots/t1/meshes/Ankle_Cross_Left.STL +0 -0
- holosoma/data/robots/t1/meshes/Ankle_Cross_Right.STL +0 -0
- holosoma/data/robots/t1/meshes/Crank_Down_Left.STL +0 -0
- holosoma/data/robots/t1/meshes/Crank_Down_Right.STL +0 -0
- holosoma/data/robots/t1/meshes/Crank_Up_Left.STL +0 -0
- holosoma/data/robots/t1/meshes/Crank_Up_Right.STL +0 -0
- holosoma/data/robots/t1/meshes/Down_Left_XX_Ball.STL +0 -0
- holosoma/data/robots/t1/meshes/Down_Left_X_Ball.STL +0 -0
- holosoma/data/robots/t1/meshes/Down_Left_Y_Ball.STL +0 -0
- holosoma/data/robots/t1/meshes/Down_Right_XX_Ball.STL +0 -0
- holosoma/data/robots/t1/meshes/Down_Right_X_Ball.STL +0 -0
- holosoma/data/robots/t1/meshes/Down_Right_Y_Ball.STL +0 -0
- holosoma/data/robots/t1/meshes/H1.STL +0 -0
- holosoma/data/robots/t1/meshes/H2.STL +0 -0
- holosoma/data/robots/t1/meshes/Hip_Pitch_Left.STL +0 -0
- holosoma/data/robots/t1/meshes/Hip_Pitch_Right.STL +0 -0
- holosoma/data/robots/t1/meshes/Hip_Roll_Left.STL +0 -0
- holosoma/data/robots/t1/meshes/Hip_Roll_Right.STL +0 -0
- holosoma/data/robots/t1/meshes/Hip_Yaw_Left.STL +0 -0
- holosoma/data/robots/t1/meshes/Hip_Yaw_Right.STL +0 -0
- holosoma/data/robots/t1/meshes/Link_Long_Left.STL +0 -0
- holosoma/data/robots/t1/meshes/Link_Long_Right.STL +0 -0
- holosoma/data/robots/t1/meshes/Link_Short_Left.STL +0 -0
- holosoma/data/robots/t1/meshes/Link_Short_Right.STL +0 -0
- holosoma/data/robots/t1/meshes/Shank_Left.STL +0 -0
- holosoma/data/robots/t1/meshes/Shank_Right.STL +0 -0
- holosoma/data/robots/t1/meshes/Trunk.STL +0 -0
- holosoma/data/robots/t1/meshes/Up_Left_XX_Ball.STL +0 -0
- holosoma/data/robots/t1/meshes/Up_Left_X_Ball.STL +0 -0
- holosoma/data/robots/t1/meshes/Up_Left_Y_Ball.STL +0 -0
- holosoma/data/robots/t1/meshes/Up_Right_XX_Ball.STL +0 -0
- holosoma/data/robots/t1/meshes/Up_Right_X_Ball.STL +0 -0
- holosoma/data/robots/t1/meshes/Up_Right_Y_Ball.STL +0 -0
- holosoma/data/robots/t1/meshes/Waist.STL +0 -0
- holosoma/data/robots/t1/meshes/left_Link1.STL +0 -0
- holosoma/data/robots/t1/meshes/left_Link11.STL +0 -0
- holosoma/data/robots/t1/meshes/left_Link2.STL +0 -0
- holosoma/data/robots/t1/meshes/left_Link22.STL +0 -0
- holosoma/data/robots/t1/meshes/left_base_link.STL +0 -0
- holosoma/data/robots/t1/meshes/left_foot_link.STL +0 -0
- holosoma/data/robots/t1/meshes/left_hand_link.STL +0 -0
- holosoma/data/robots/t1/meshes/left_rubber_hand.STL +0 -0
- holosoma/data/robots/t1/meshes/right_Link1.STL +0 -0
- holosoma/data/robots/t1/meshes/right_Link11.STL +0 -0
- holosoma/data/robots/t1/meshes/right_Link2.STL +0 -0
- holosoma/data/robots/t1/meshes/right_Link22.STL +0 -0
- holosoma/data/robots/t1/meshes/right_base_link.STL +0 -0
- holosoma/data/robots/t1/meshes/right_foot_link.STL +0 -0
- holosoma/data/robots/t1/meshes/right_hand_link.STL +0 -0
- holosoma/data/robots/t1/meshes/right_rubber_hand.STL +0 -0
- holosoma/data/robots/t1/t1_29dof.urdf +1629 -0
- holosoma/data/robots/t1/t1_29dof.xml +300 -0
- holosoma/data/scene_objects/boxes/large_box.urdf +32 -0
- holosoma/data/scene_objects/boxes/largebox.obj +54685 -0
- holosoma/data/scene_objects/boxes/small_box.urdf +19 -0
- holosoma/data/scene_objects/boxes/small_box.usda +24 -0
- holosoma/data/scene_objects/boxes/small_box.xml +9 -0
- holosoma/data/scene_objects/multibody/multibody.urdf +47 -0
- holosoma/data/scene_objects/multibody/multibody.usda +48 -0
- holosoma/data/scene_objects/multibody/multibody.xml +15 -0
- holosoma/data/scene_objects/multibody/unauthored_table.usda +45 -0
- holosoma/data/scene_objects/panels/red_panel.urdf +23 -0
- holosoma/data/scene_objects/panels/red_panel.usda +27 -0
- holosoma/data/scene_objects/panels/red_panel.xml +12 -0
- holosoma/data/scene_objects/rubber_duck/LICENSE +1 -0
- holosoma/data/scene_objects/rubber_duck/RubberDuck.mtl +7 -0
- holosoma/data/scene_objects/rubber_duck/RubberDuck.obj +1773 -0
- holosoma/data/scene_objects/rubber_duck/RubberDuck_BaseColor.png +0 -0
- holosoma/data/scene_objects/rubber_duck/huge_rubber_duck.urdf +33 -0
- holosoma/data/scene_objects/rubber_duck/huge_rubber_duck.xml +17 -0
- holosoma/data/scene_objects/rubber_duck/large_rubber_duck.urdf +33 -0
- holosoma/data/scene_objects/rubber_duck/large_rubber_duck.xml +16 -0
- holosoma/data/scene_objects/rubber_duck/rubber_duck.urdf +32 -0
- holosoma/data/scene_objects/rubber_duck/rubber_duck.xml +19 -0
- holosoma/envs/__init__.py +1 -0
- holosoma/envs/base_task/__init__.py +5 -0
- holosoma/envs/base_task/base_task.py +616 -0
- holosoma/envs/locomotion/__init__.py +9 -0
- holosoma/envs/locomotion/locomotion_manager.py +336 -0
- holosoma/envs/tests/test_e2e.py +49 -0
- holosoma/envs/tests/test_obs_dict_group.py +99 -0
- holosoma/envs/tests/test_push_randomization.py +178 -0
- holosoma/envs/wbt/wbt_manager.py +245 -0
- holosoma/eval_agent.py +110 -0
- holosoma/examples/__init__.py +1 -0
- holosoma/examples/object_managers_demo.py +380 -0
- holosoma/managers/__init__.py +10 -0
- holosoma/managers/action/__init__.py +10 -0
- holosoma/managers/action/base.py +145 -0
- holosoma/managers/action/manager.py +242 -0
- holosoma/managers/action/terms/__init__.py +9 -0
- holosoma/managers/action/terms/joint_control.py +337 -0
- holosoma/managers/command/__init__.py +6 -0
- holosoma/managers/command/base.py +41 -0
- holosoma/managers/command/manager.py +227 -0
- holosoma/managers/command/terms/__init__.py +5 -0
- holosoma/managers/command/terms/locomotion.py +224 -0
- holosoma/managers/command/terms/wbt.py +1495 -0
- holosoma/managers/curriculum/__init__.py +5 -0
- holosoma/managers/curriculum/base.py +68 -0
- holosoma/managers/curriculum/manager.py +173 -0
- holosoma/managers/curriculum/terms/__init__.py +5 -0
- holosoma/managers/curriculum/terms/locomotion.py +374 -0
- holosoma/managers/observation/__init__.py +19 -0
- holosoma/managers/observation/base.py +53 -0
- holosoma/managers/observation/manager.py +353 -0
- holosoma/managers/observation/terms/__init__.py +5 -0
- holosoma/managers/observation/terms/cameras.py +54 -0
- holosoma/managers/observation/terms/locomotion.py +179 -0
- holosoma/managers/observation/terms/objects.py +103 -0
- holosoma/managers/observation/terms/wbt.py +221 -0
- holosoma/managers/observation/tests/test_camera_obs_terms.py +140 -0
- holosoma/managers/randomization/__init__.py +6 -0
- holosoma/managers/randomization/base.py +43 -0
- holosoma/managers/randomization/exceptions.py +16 -0
- holosoma/managers/randomization/manager.py +274 -0
- holosoma/managers/randomization/terms/__init__.py +1 -0
- holosoma/managers/randomization/terms/_shared.py +20 -0
- holosoma/managers/randomization/terms/locomotion.py +1098 -0
- holosoma/managers/randomization/terms/objects.py +938 -0
- holosoma/managers/reset_events/__init__.py +10 -0
- holosoma/managers/reset_events/base.py +33 -0
- holosoma/managers/reset_events/manager.py +57 -0
- holosoma/managers/reward/__init__.py +6 -0
- holosoma/managers/reward/base.py +53 -0
- holosoma/managers/reward/manager.py +333 -0
- holosoma/managers/reward/terms/__init__.py +5 -0
- holosoma/managers/reward/terms/locomotion.py +392 -0
- holosoma/managers/reward/terms/wbt.py +175 -0
- holosoma/managers/termination/__init__.py +5 -0
- holosoma/managers/termination/base.py +27 -0
- holosoma/managers/termination/manager.py +129 -0
- holosoma/managers/termination/terms/__init__.py +1 -0
- holosoma/managers/termination/terms/common.py +10 -0
- holosoma/managers/termination/terms/locomotion.py +81 -0
- holosoma/managers/termination/terms/wbt.py +146 -0
- holosoma/managers/terrain/__init__.py +5 -0
- holosoma/managers/terrain/base.py +104 -0
- holosoma/managers/terrain/manager.py +60 -0
- holosoma/managers/terrain/terms/__init__.py +1 -0
- holosoma/managers/terrain/terms/locomotion.py +278 -0
- holosoma/managers/utils.py +56 -0
- holosoma/py.typed +0 -0
- holosoma/replay.py +44 -0
- holosoma/run_sim.py +106 -0
- holosoma/simulator/__init__.py +11 -0
- holosoma/simulator/base_simulator/base_simulator.py +1315 -0
- holosoma/simulator/base_simulator/hooks.py +346 -0
- holosoma/simulator/base_simulator/tests/test_hooks.py +274 -0
- holosoma/simulator/base_simulator/tests/test_plugins.py +119 -0
- holosoma/simulator/isaacgym/isaacgym.py +1555 -0
- holosoma/simulator/isaacgym/physics.py +164 -0
- holosoma/simulator/isaacgym/urdf_scene_loader.py +770 -0
- holosoma/simulator/isaacgym/video_recorder.py +176 -0
- holosoma/simulator/isaacsim/__init__.py +1 -0
- holosoma/simulator/isaacsim/converters.py +127 -0
- holosoma/simulator/isaacsim/event_cfg.py +33 -0
- holosoma/simulator/isaacsim/events.py +352 -0
- holosoma/simulator/isaacsim/isaaclab_viewpoint_camera_controller.py +250 -0
- holosoma/simulator/isaacsim/isaacsim.py +1482 -0
- holosoma/simulator/isaacsim/isaacsim_articulation_cfg.py +96 -0
- holosoma/simulator/isaacsim/object_spawner.py +565 -0
- holosoma/simulator/isaacsim/prim_naming.py +76 -0
- holosoma/simulator/isaacsim/prim_utils.py +358 -0
- holosoma/simulator/isaacsim/proxy_utils.py +81 -0
- holosoma/simulator/isaacsim/spawners/__init__.py +4 -0
- holosoma/simulator/isaacsim/spawners/from_files.py +463 -0
- holosoma/simulator/isaacsim/spawners/from_files_cfg.py +58 -0
- holosoma/simulator/isaacsim/spawners/schema_utils.py +146 -0
- holosoma/simulator/isaacsim/state_adapter.py +186 -0
- holosoma/simulator/isaacsim/state_utils.py +53 -0
- holosoma/simulator/isaacsim/video_recorder.py +292 -0
- holosoma/simulator/mujoco/__init__.py +5 -0
- holosoma/simulator/mujoco/backends/__init__.py +38 -0
- holosoma/simulator/mujoco/backends/base.py +428 -0
- holosoma/simulator/mujoco/backends/classic_backend.py +466 -0
- holosoma/simulator/mujoco/backends/randomization.py +438 -0
- holosoma/simulator/mujoco/backends/warp_backend.py +911 -0
- holosoma/simulator/mujoco/backends/warp_bridge.py +241 -0
- holosoma/simulator/mujoco/command_registry.py +98 -0
- holosoma/simulator/mujoco/fields.py +133 -0
- holosoma/simulator/mujoco/mjw_views.py +629 -0
- holosoma/simulator/mujoco/mujoco.py +1967 -0
- holosoma/simulator/mujoco/scene_manager.py +896 -0
- holosoma/simulator/mujoco/tensor_views.py +568 -0
- holosoma/simulator/mujoco/video_recorder.py +172 -0
- holosoma/simulator/plugins/__init__.py +23 -0
- holosoma/simulator/plugins/camera_consumer.py +230 -0
- holosoma/simulator/plugins/depth_color.py +67 -0
- holosoma/simulator/plugins/ros2/__init__.py +2 -0
- holosoma/simulator/plugins/ros2/camera_info.py +50 -0
- holosoma/simulator/plugins/ros2/encode.py +120 -0
- holosoma/simulator/plugins/ros2/ros2_image_plugin.py +228 -0
- holosoma/simulator/plugins/ros2/worker.py +87 -0
- holosoma/simulator/plugins/tests/__init__.py +0 -0
- holosoma/simulator/plugins/tests/test_camera_consumer.py +226 -0
- holosoma/simulator/plugins/tests/test_image_grid.py +150 -0
- holosoma/simulator/plugins/tests/test_presets.py +193 -0
- holosoma/simulator/plugins/tests/test_ros2_helpers.py +208 -0
- holosoma/simulator/plugins/tests/test_ros2_image_plugin.py +384 -0
- holosoma/simulator/plugins/viz/__init__.py +5 -0
- holosoma/simulator/plugins/viz/image_grid.py +155 -0
- holosoma/simulator/plugins/viz/viz_plugin.py +176 -0
- holosoma/simulator/shared/__init__.py +1 -0
- holosoma/simulator/shared/asset_format.py +69 -0
- holosoma/simulator/shared/builtin_plugins.py +23 -0
- holosoma/simulator/shared/camera_controller.py +406 -0
- holosoma/simulator/shared/camera_sensor.py +154 -0
- holosoma/simulator/shared/field_decorators.py +59 -0
- holosoma/simulator/shared/image_transform.py +73 -0
- holosoma/simulator/shared/object_registry.py +561 -0
- holosoma/simulator/shared/root_states_view.py +114 -0
- holosoma/simulator/shared/ros2_plugins.py +335 -0
- holosoma/simulator/shared/scene_types.py +33 -0
- holosoma/simulator/shared/simulator_bridge.py +197 -0
- holosoma/simulator/shared/terrain.py +581 -0
- holosoma/simulator/shared/terrain_types.py +34 -0
- holosoma/simulator/shared/tests/test_camera_runtime_cache.py +95 -0
- holosoma/simulator/shared/tests/test_get_camera_data.py +95 -0
- holosoma/simulator/shared/tests/test_image_transform.py +126 -0
- holosoma/simulator/shared/tests/test_optical_frame.py +59 -0
- holosoma/simulator/shared/tests/test_ros2_plugins.py +257 -0
- holosoma/simulator/shared/tests/test_sensor_manager.py +109 -0
- holosoma/simulator/shared/video_recorder.py +568 -0
- holosoma/simulator/shared/virtual_gantry.py +583 -0
- holosoma/simulator/types.py +84 -0
- holosoma/train_agent.py +340 -0
- holosoma/utils/__init__.py +0 -0
- holosoma/utils/adapters/draw_utils.py +58 -0
- holosoma/utils/adapters/isaacgym_draw_adapter.py +128 -0
- holosoma/utils/adapters/isaacsim_draw_adapter.py +62 -0
- holosoma/utils/adapters/mujoco_draw_adapter.py +295 -0
- holosoma/utils/average_meters.py +82 -0
- holosoma/utils/clock.py +152 -0
- holosoma/utils/common.py +160 -0
- holosoma/utils/config_registry.py +285 -0
- holosoma/utils/config_utils.py +1 -0
- holosoma/utils/draw.py +71 -0
- holosoma/utils/eval_utils.py +298 -0
- holosoma/utils/experiment_paths.py +130 -0
- holosoma/utils/file_cache.py +418 -0
- holosoma/utils/helpers.py +121 -0
- holosoma/utils/inference_helpers.py +304 -0
- holosoma/utils/logging.py +54 -0
- holosoma/utils/module_utils.py +8 -0
- holosoma/utils/path.py +82 -0
- holosoma/utils/pycompat.py +22 -0
- holosoma/utils/rate.py +113 -0
- holosoma/utils/rotations.py +743 -0
- holosoma/utils/safe_torch_import.py +14 -0
- holosoma/utils/sampler.py +452 -0
- holosoma/utils/sim_utils.py +607 -0
- holosoma/utils/simulator_config.py +102 -0
- holosoma/utils/terrain_utils.py +607 -0
- holosoma/utils/tests/test_distributions.py +272 -0
- holosoma/utils/tests/test_eval_utils.py +380 -0
- holosoma/utils/tests/test_keyed_distributions.py +335 -0
- holosoma/utils/tests/test_rotations.py +173 -0
- holosoma/utils/tests/test_torch_utils.py +111 -0
- holosoma/utils/tests/test_tyro_utils.py +186 -0
- holosoma/utils/torch_jit.py +276 -0
- holosoma/utils/torch_utils.py +181 -0
- holosoma/utils/tyro_utils.py +136 -0
- holosoma/utils/video_utils.py +226 -0
- holosoma/utils/wandb.py +113 -0
- holosoma/utils/warp_utils.py +169 -0
- holosoma-0.0.1.dist-info/METADATA +349 -0
- holosoma-0.0.1.dist-info/RECORD +476 -0
- holosoma-0.0.1.dist-info/WHEEL +4 -0
- holosoma-0.0.1.dist-info/entry_points.txt +5 -0
holosoma/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
4
|
+
|
|
5
|
+
from holosoma.config_types.algo import AlgoInitConfig
|
|
6
|
+
from holosoma.envs.base_task.base_task import BaseTask
|
|
7
|
+
from holosoma.utils.safe_torch_import import torch
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from holosoma.config_types.experiment import ExperimentConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseAlgo:
|
|
14
|
+
def __init__(self, env: BaseTask, config: AlgoInitConfig, device, multi_gpu_cfg=None):
|
|
15
|
+
self.env = env
|
|
16
|
+
self.config = config
|
|
17
|
+
self.device = device
|
|
18
|
+
|
|
19
|
+
self.is_multi_gpu = multi_gpu_cfg is not None
|
|
20
|
+
if multi_gpu_cfg is not None:
|
|
21
|
+
self.gpu_global_rank = multi_gpu_cfg["global_rank"]
|
|
22
|
+
self.gpu_local_rank = multi_gpu_cfg["local_rank"]
|
|
23
|
+
self.gpu_world_size = multi_gpu_cfg["world_size"]
|
|
24
|
+
else:
|
|
25
|
+
self.gpu_global_rank = 0
|
|
26
|
+
self.gpu_local_rank = 0
|
|
27
|
+
self.gpu_world_size = 1
|
|
28
|
+
self.is_main_process = self.gpu_global_rank == 0
|
|
29
|
+
self._experiment_config: ExperimentConfig | None = None
|
|
30
|
+
self._wandb_run_path: str | None = None
|
|
31
|
+
|
|
32
|
+
def setup(self):
|
|
33
|
+
return NotImplementedError
|
|
34
|
+
|
|
35
|
+
def learn(self):
|
|
36
|
+
return NotImplementedError
|
|
37
|
+
|
|
38
|
+
def load(self, path):
|
|
39
|
+
return NotImplementedError
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def inference_model(self):
|
|
43
|
+
return NotImplementedError
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def actor_onnx_wrapper(self):
|
|
47
|
+
return NotImplementedError
|
|
48
|
+
|
|
49
|
+
def env_step(self, actions, extra_info=None):
|
|
50
|
+
obs_dict, rewards, dones, extras = self.env.step(actions, extra_info)
|
|
51
|
+
return obs_dict, rewards, dones, extras
|
|
52
|
+
|
|
53
|
+
def attach_checkpoint_metadata(
|
|
54
|
+
self,
|
|
55
|
+
experiment_config: ExperimentConfig,
|
|
56
|
+
wandb_run_path: str | None = None,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Attach metadata that should be saved with checkpoints."""
|
|
59
|
+
|
|
60
|
+
self._experiment_config = experiment_config
|
|
61
|
+
self._wandb_run_path = wandb_run_path
|
|
62
|
+
|
|
63
|
+
def _checkpoint_metadata(self, iteration: int | None = None) -> dict[str, Any]:
|
|
64
|
+
if self._experiment_config is None:
|
|
65
|
+
raise RuntimeError("Experiment config metadata missing. Call attach_checkpoint_metadata() before saving.")
|
|
66
|
+
|
|
67
|
+
metadata: dict[str, Any] = {"experiment_config": self._experiment_config.to_serializable_dict()}
|
|
68
|
+
if self._wandb_run_path:
|
|
69
|
+
metadata["wandb_run_path"] = self._wandb_run_path
|
|
70
|
+
if iteration is not None:
|
|
71
|
+
metadata["iteration"] = int(iteration)
|
|
72
|
+
return metadata
|
|
73
|
+
|
|
74
|
+
def has_curricula_enabled(self) -> bool:
|
|
75
|
+
"""Check if any curricula are enabled in the environment.
|
|
76
|
+
|
|
77
|
+
This helper method checks for the presence of various curriculum flags
|
|
78
|
+
to determine if any curriculum learning is active. This is commonly used
|
|
79
|
+
for multi-GPU synchronization and logging purposes.
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
bool
|
|
84
|
+
True if any curriculum is enabled, False otherwise.
|
|
85
|
+
"""
|
|
86
|
+
return getattr(self.env, "use_reward_penalty_curriculum", False) or getattr(
|
|
87
|
+
self.env, "use_domain_rand_scale_curriculum", False
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def _synchronize_curriculum_metrics(self):
|
|
91
|
+
"""Synchronize curriculum-related metrics across all GPUs."""
|
|
92
|
+
# Check if any curricula are enabled before synchronizing
|
|
93
|
+
if not self.has_curricula_enabled():
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
env = self._unwrap_env()
|
|
97
|
+
env.synchronize_curriculum_state(device=self.device, world_size=self.gpu_world_size)
|
|
98
|
+
|
|
99
|
+
def get_inference_policy(self, device: str | None = None) -> Callable[[dict[str, torch.Tensor]], torch.Tensor]:
|
|
100
|
+
"""Get a callable policy function for inference.
|
|
101
|
+
|
|
102
|
+
This method returns a function that takes observations as input and returns
|
|
103
|
+
actions. The policy function is configured to run on the specified device.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
device : str | None, optional
|
|
108
|
+
The device to run the policy on (e.g., 'cuda', 'cpu').
|
|
109
|
+
If None, uses the default device.
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
Callable[[torch.Tensor], torch.Tensor]
|
|
114
|
+
A function that takes observations as input and returns actions.
|
|
115
|
+
The function expects input observations as a torch.Tensor and
|
|
116
|
+
returns actions as a torch.Tensor. Both input and output tensors
|
|
117
|
+
should be on the specified device.
|
|
118
|
+
|
|
119
|
+
Notes
|
|
120
|
+
-----
|
|
121
|
+
This is an abstract method that should be implemented by subclasses.
|
|
122
|
+
The returned policy function should:
|
|
123
|
+
- Run the policy network on the specified device
|
|
124
|
+
- Return actions in the expected format
|
|
125
|
+
"""
|
|
126
|
+
raise NotImplementedError
|
|
127
|
+
|
|
128
|
+
@torch.no_grad()
|
|
129
|
+
def evaluate_policy(self, max_eval_steps: int | None = None):
|
|
130
|
+
raise NotImplementedError
|
|
131
|
+
|
|
132
|
+
def save(self, path=None, name="last.ckpt"):
|
|
133
|
+
raise NotImplementedError
|
|
134
|
+
|
|
135
|
+
def _unwrap_env(self) -> BaseTask | Any:
|
|
136
|
+
"""Return the underlying environment.
|
|
137
|
+
|
|
138
|
+
Algorithms that wrap the task (e.g. ``FastSACEnv``) keep a reference to the
|
|
139
|
+
original environment as ``unwrapped_env`` during construction, so we simply
|
|
140
|
+
return that when it is present.
|
|
141
|
+
"""
|
|
142
|
+
return getattr(self, "unwrapped_env", self.env)
|
|
143
|
+
|
|
144
|
+
def _collect_env_state(self) -> dict[str, torch.Tensor | float]:
|
|
145
|
+
"""Collect environment state for checkpointing via the environment interface."""
|
|
146
|
+
env = self._unwrap_env()
|
|
147
|
+
state = env.get_checkpoint_state()
|
|
148
|
+
return state or {}
|
|
149
|
+
|
|
150
|
+
def _restore_env_state(self, env_state: dict[str, torch.Tensor | float] | None) -> None:
|
|
151
|
+
"""Restore environment state from checkpoint via the environment interface."""
|
|
152
|
+
if not env_state:
|
|
153
|
+
return
|
|
154
|
+
env = self._unwrap_env()
|
|
155
|
+
env.load_checkpoint_state(env_state)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from torch.nn import Module
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RLEvalCallback(Module):
|
|
5
|
+
def __init__(self, config, training_loop):
|
|
6
|
+
super().__init__()
|
|
7
|
+
self.config = config
|
|
8
|
+
self.training_loop = training_loop
|
|
9
|
+
self.device = self.training_loop.device
|
|
10
|
+
|
|
11
|
+
def on_pre_evaluate_policy(self):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
def on_pre_eval_env_step(self, actor_state):
|
|
15
|
+
return actor_state
|
|
16
|
+
|
|
17
|
+
def on_post_eval_env_step(self, actor_state):
|
|
18
|
+
return actor_state
|
|
19
|
+
|
|
20
|
+
def on_post_evaluate_policy(self):
|
|
21
|
+
pass
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Eval callback that applies constant downward forces on wrists to simulate a held payload.
|
|
2
|
+
|
|
3
|
+
Applies F = mass * g / num_bodies on each resolved wrist/elbow link
|
|
4
|
+
as a sustained downward (-Z) force throughout the evaluation episode. Uses the same
|
|
5
|
+
IsaacLab set_external_force_and_torque API as the push callback, with substep-level
|
|
6
|
+
force application via monkey-patching.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from loguru import logger
|
|
15
|
+
|
|
16
|
+
from holosoma.agents.callbacks.base_callback import RLEvalCallback
|
|
17
|
+
from holosoma.config_types.eval_callback import PayloadConfig
|
|
18
|
+
from holosoma.utils.safe_torch_import import torch
|
|
19
|
+
|
|
20
|
+
GRAVITY = 9.81
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class EvalPayloadCallback(RLEvalCallback):
|
|
24
|
+
"""Applies constant downward forces on wrist links to simulate holding a payload.
|
|
25
|
+
|
|
26
|
+
The total payload weight (mass * g) is split evenly across all resolved
|
|
27
|
+
wrist bodies. Forces are applied at every physics substep via monkey-patch.
|
|
28
|
+
|
|
29
|
+
Recorded arrays (per env step):
|
|
30
|
+
payload_force_per_body [T] force magnitude per body in N
|
|
31
|
+
payload_body_pos_w [T, N, 3] world positions of payload bodies
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
config: PayloadConfig,
|
|
37
|
+
training_loop: Any = None,
|
|
38
|
+
):
|
|
39
|
+
super().__init__(config, training_loop)
|
|
40
|
+
self.env_id = config.env_id
|
|
41
|
+
self.candidate_body_names = [s.strip() for s in config.body_names.split(",")]
|
|
42
|
+
self._record_buffers: dict[str, list[np.ndarray]] | None = None
|
|
43
|
+
self._record_metadata: dict[str, Any] | None = None
|
|
44
|
+
|
|
45
|
+
# Resolved at on_pre_evaluate_policy
|
|
46
|
+
self._resolved_bodies: list[tuple[str, int, int]] = []
|
|
47
|
+
self._force_per_body: float = 0.0
|
|
48
|
+
self._sim: Any = None
|
|
49
|
+
self._original_apply_force: Any = None
|
|
50
|
+
self._step_count: int = 0
|
|
51
|
+
|
|
52
|
+
def _get_env(self):
|
|
53
|
+
return self.training_loop._unwrap_env()
|
|
54
|
+
|
|
55
|
+
def _find_recording_callback(self):
|
|
56
|
+
from holosoma.agents.callbacks.recording import EvalRecordingCallback
|
|
57
|
+
|
|
58
|
+
for cb in self.training_loop.eval_callbacks:
|
|
59
|
+
if isinstance(cb, EvalRecordingCallback):
|
|
60
|
+
return cb
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
def on_pre_evaluate_policy(self) -> None:
|
|
64
|
+
# Discover recording callback for shared buffer/metadata access
|
|
65
|
+
recording_cb = self._find_recording_callback()
|
|
66
|
+
if recording_cb is not None:
|
|
67
|
+
self._record_buffers = recording_cb._buffers
|
|
68
|
+
self._record_metadata = recording_cb._metadata
|
|
69
|
+
|
|
70
|
+
env = self._get_env()
|
|
71
|
+
self._sim = env.simulator
|
|
72
|
+
if self._sim.simulator_config.name != "isaacsim":
|
|
73
|
+
raise RuntimeError(f"EvalPayloadCallback requires IsaacSim, got '{self._sim.simulator_config.name}'")
|
|
74
|
+
|
|
75
|
+
# Resolve body names
|
|
76
|
+
self._resolved_bodies = []
|
|
77
|
+
available = list(getattr(self._sim, "body_names", []))
|
|
78
|
+
for name in self.candidate_body_names:
|
|
79
|
+
body_names_idx = self._sim.find_rigid_body_indice(name)
|
|
80
|
+
if isinstance(body_names_idx, int) and body_names_idx >= 0:
|
|
81
|
+
isaac_body_id = self._sim.body_ids[body_names_idx]
|
|
82
|
+
self._resolved_bodies.append((name, body_names_idx, isaac_body_id))
|
|
83
|
+
else:
|
|
84
|
+
raise ValueError(f"EvalPayloadCallback: body '{name}' not found. Available: {available}")
|
|
85
|
+
|
|
86
|
+
total_force = self.config.mass_kg * GRAVITY
|
|
87
|
+
self._force_per_body = total_force / len(self._resolved_bodies)
|
|
88
|
+
|
|
89
|
+
resolved_names = [name for name, _, _ in self._resolved_bodies]
|
|
90
|
+
logger.info(
|
|
91
|
+
f"EvalPayloadCallback: {len(self._resolved_bodies)} payload bodies: {resolved_names}, "
|
|
92
|
+
f"mass={self.config.mass_kg}kg, total_force={total_force:.1f}N, "
|
|
93
|
+
f"force_per_body={self._force_per_body:.1f}N"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Store config in recording metadata
|
|
97
|
+
if self._record_metadata is not None:
|
|
98
|
+
self._record_metadata["payload_config"] = {
|
|
99
|
+
"body_names": resolved_names,
|
|
100
|
+
"mass_kg": self.config.mass_kg,
|
|
101
|
+
"total_force_n": total_force,
|
|
102
|
+
"force_per_body_n": self._force_per_body,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Initialize recording buffers
|
|
106
|
+
buffers = self._get_buffers()
|
|
107
|
+
if buffers is not None:
|
|
108
|
+
buffers["payload_force_per_body"] = []
|
|
109
|
+
buffers["payload_body_pos_w"] = []
|
|
110
|
+
|
|
111
|
+
# Monkey-patch substep force application
|
|
112
|
+
self._original_apply_force = env._apply_force_in_physics_step
|
|
113
|
+
|
|
114
|
+
def _patched_apply_force():
|
|
115
|
+
self._original_apply_force()
|
|
116
|
+
self._apply_payload_forces()
|
|
117
|
+
|
|
118
|
+
env._apply_force_in_physics_step = _patched_apply_force
|
|
119
|
+
|
|
120
|
+
def on_post_eval_env_step(self, actor_state: dict) -> dict:
|
|
121
|
+
buffers = self._get_buffers()
|
|
122
|
+
if buffers is not None:
|
|
123
|
+
eid = self.env_id
|
|
124
|
+
body_positions = []
|
|
125
|
+
for _name, _body_names_idx, isaac_body_id in self._resolved_bodies:
|
|
126
|
+
pos = self._sim._robot.data.body_pos_w[eid, isaac_body_id].detach().cpu().numpy().copy()
|
|
127
|
+
body_positions.append(pos)
|
|
128
|
+
|
|
129
|
+
buffers["payload_force_per_body"].append(np.array(self._force_per_body, dtype=np.float32))
|
|
130
|
+
buffers["payload_body_pos_w"].append(np.stack(body_positions, axis=0).astype(np.float32))
|
|
131
|
+
|
|
132
|
+
self._step_count += 1
|
|
133
|
+
return actor_state
|
|
134
|
+
|
|
135
|
+
def on_post_evaluate_policy(self) -> None:
|
|
136
|
+
self._get_env()._apply_force_in_physics_step = self._original_apply_force
|
|
137
|
+
logger.info(
|
|
138
|
+
f"EvalPayloadCallback: completed {self._step_count} steps, "
|
|
139
|
+
f"payload={self.config.mass_kg}kg on {len(self._resolved_bodies)} bodies"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def _apply_payload_forces(self) -> None:
|
|
143
|
+
"""Apply downward force on all resolved wrist bodies (called each substep)."""
|
|
144
|
+
from isaaclab.utils.math import quat_apply_inverse
|
|
145
|
+
|
|
146
|
+
eid = self.env_id
|
|
147
|
+
device = self._sim.sim_device
|
|
148
|
+
|
|
149
|
+
# Force in world frame: straight down (-Z)
|
|
150
|
+
force_world = torch.tensor(
|
|
151
|
+
[0.0, 0.0, -self._force_per_body],
|
|
152
|
+
dtype=torch.float32,
|
|
153
|
+
device=device,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
for _name, _body_names_idx, isaac_body_id in self._resolved_bodies:
|
|
157
|
+
# Transform world-frame force to body-local frame
|
|
158
|
+
body_quat_w = self._sim._robot.data.body_quat_w[eid, isaac_body_id]
|
|
159
|
+
force_body = quat_apply_inverse(body_quat_w, force_world)
|
|
160
|
+
|
|
161
|
+
forces = force_body.unsqueeze(0).unsqueeze(0) # [1, 1, 3]
|
|
162
|
+
torques = torch.zeros_like(forces)
|
|
163
|
+
|
|
164
|
+
self._sim._robot.set_external_force_and_torque(
|
|
165
|
+
forces=forces,
|
|
166
|
+
torques=torques,
|
|
167
|
+
env_ids=torch.tensor([eid], device=device),
|
|
168
|
+
body_ids=torch.tensor([isaac_body_id], device=device),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def _get_buffers(self) -> dict[str, list[np.ndarray]] | None:
|
|
172
|
+
return self._record_buffers
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""Eval callback that applies random force pulses to test policy robustness.
|
|
2
|
+
|
|
3
|
+
Periodically pushes the robot at random body locations with configurable
|
|
4
|
+
force magnitude, duration, and interval. Uses IsaacLab's
|
|
5
|
+
set_external_force_and_torque API to apply forces in the physics simulation.
|
|
6
|
+
|
|
7
|
+
Push events are optionally recorded for visualization in viser by sharing
|
|
8
|
+
buffer/metadata dicts with EvalRecordingCallback.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import math
|
|
14
|
+
import random
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
from loguru import logger
|
|
19
|
+
|
|
20
|
+
from holosoma.agents.callbacks.base_callback import RLEvalCallback
|
|
21
|
+
from holosoma.config_types.eval_callback import PushConfig
|
|
22
|
+
from holosoma.utils.safe_torch_import import torch
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class EvalPushCallback(RLEvalCallback):
|
|
26
|
+
"""Applies random force pulses during evaluation and optionally records them.
|
|
27
|
+
|
|
28
|
+
Forces are applied at the physics substep level by patching the env's
|
|
29
|
+
_apply_force_in_physics_step method, ensuring the force is active for the
|
|
30
|
+
full duration across all substeps (control decimation).
|
|
31
|
+
|
|
32
|
+
Recorded arrays (per env step):
|
|
33
|
+
push_active [T] 1.0 when pushing, 0.0 otherwise
|
|
34
|
+
push_force_w [T, 3] force vector in world frame (zeros when inactive)
|
|
35
|
+
push_body_pos_w[T, 3] world position of pushed body (zeros when inactive)
|
|
36
|
+
push_body_idx [T] index into body_names (-1 when inactive)
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
config: PushConfig,
|
|
42
|
+
training_loop: Any = None,
|
|
43
|
+
):
|
|
44
|
+
super().__init__(config, training_loop)
|
|
45
|
+
self.env_id = config.env_id
|
|
46
|
+
self.candidate_body_names = [s.strip() for s in config.body_names.split(",")]
|
|
47
|
+
self._record_buffers: dict[str, list[np.ndarray]] | None = None
|
|
48
|
+
self._record_metadata: dict[str, Any] | None = None
|
|
49
|
+
|
|
50
|
+
# Resolved at on_pre_evaluate_policy
|
|
51
|
+
self._resolved_bodies: list[tuple[str, int, int]] = []
|
|
52
|
+
self._dt: float = 0.0
|
|
53
|
+
self._sim: Any = None
|
|
54
|
+
self._original_apply_force: Any = None
|
|
55
|
+
|
|
56
|
+
# Push state machine
|
|
57
|
+
self._push_active: bool = False
|
|
58
|
+
self._push_steps_remaining: int = 0
|
|
59
|
+
self._idle_steps_remaining: int = 0
|
|
60
|
+
self._current_force_w: torch.Tensor | None = None
|
|
61
|
+
self._current_body_names_idx: int = -1
|
|
62
|
+
self._current_isaac_body_id: int = -1
|
|
63
|
+
|
|
64
|
+
self._step_count: int = 0
|
|
65
|
+
|
|
66
|
+
def _get_env(self):
|
|
67
|
+
return self.training_loop._unwrap_env()
|
|
68
|
+
|
|
69
|
+
def _find_recording_callback(self):
|
|
70
|
+
from holosoma.agents.callbacks.recording import EvalRecordingCallback
|
|
71
|
+
|
|
72
|
+
for cb in self.training_loop.eval_callbacks:
|
|
73
|
+
if isinstance(cb, EvalRecordingCallback):
|
|
74
|
+
return cb
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def on_pre_evaluate_policy(self) -> None:
|
|
78
|
+
# Discover recording callback for shared buffer/metadata access
|
|
79
|
+
recording_cb = self._find_recording_callback()
|
|
80
|
+
if recording_cb is not None:
|
|
81
|
+
self._record_buffers = recording_cb._buffers
|
|
82
|
+
self._record_metadata = recording_cb._metadata
|
|
83
|
+
|
|
84
|
+
env = self._get_env()
|
|
85
|
+
self._sim = env.simulator
|
|
86
|
+
if self._sim.simulator_config.name != "isaacsim":
|
|
87
|
+
raise RuntimeError(f"EvalPushCallback requires IsaacSim, got '{self._sim.simulator_config.name}'")
|
|
88
|
+
self._dt = float(env.dt)
|
|
89
|
+
|
|
90
|
+
# Resolve body names to simulator indices
|
|
91
|
+
self._resolved_bodies = []
|
|
92
|
+
available = list(getattr(self._sim, "body_names", []))
|
|
93
|
+
for name in self.candidate_body_names:
|
|
94
|
+
body_names_idx = self._sim.find_rigid_body_indice(name)
|
|
95
|
+
if isinstance(body_names_idx, int) and body_names_idx >= 0:
|
|
96
|
+
isaac_body_id = self._sim.body_ids[body_names_idx]
|
|
97
|
+
self._resolved_bodies.append((name, body_names_idx, isaac_body_id))
|
|
98
|
+
else:
|
|
99
|
+
raise ValueError(f"EvalPushCallback: body '{name}' not found. Available: {available}")
|
|
100
|
+
|
|
101
|
+
resolved_names = [name for name, _, _ in self._resolved_bodies]
|
|
102
|
+
logger.info(
|
|
103
|
+
f"EvalPushCallback: {len(self._resolved_bodies)} push bodies: {resolved_names}, "
|
|
104
|
+
f"force={self.config.force_range}N, duration={self.config.duration_s}s, interval={self.config.interval_s}s"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Store push config in recording metadata
|
|
108
|
+
if self._record_metadata is not None:
|
|
109
|
+
self._record_metadata["push_config"] = {
|
|
110
|
+
"body_names": resolved_names,
|
|
111
|
+
"force_range": list(self.config.force_range),
|
|
112
|
+
"duration_s": list(self.config.duration_s),
|
|
113
|
+
"interval_s": list(self.config.interval_s),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Initialize recording buffers
|
|
117
|
+
buffers = self._get_buffers()
|
|
118
|
+
if buffers is not None:
|
|
119
|
+
for key in ("push_active", "push_force_w", "push_body_pos_w", "push_body_idx"):
|
|
120
|
+
buffers[key] = []
|
|
121
|
+
|
|
122
|
+
# Start with a random idle period
|
|
123
|
+
self._idle_steps_remaining = self._sample_interval_steps()
|
|
124
|
+
self._push_active = False
|
|
125
|
+
|
|
126
|
+
# Patch _apply_force_in_physics_step to inject push forces at every substep
|
|
127
|
+
self._original_apply_force = env._apply_force_in_physics_step
|
|
128
|
+
|
|
129
|
+
def _patched_apply_force():
|
|
130
|
+
self._original_apply_force()
|
|
131
|
+
if self._push_active:
|
|
132
|
+
self._apply_push_force()
|
|
133
|
+
|
|
134
|
+
env._apply_force_in_physics_step = _patched_apply_force
|
|
135
|
+
|
|
136
|
+
def on_post_eval_env_step(self, actor_state: dict) -> dict:
|
|
137
|
+
# Manage push schedule
|
|
138
|
+
if self._push_active:
|
|
139
|
+
self._push_steps_remaining -= 1
|
|
140
|
+
if self._push_steps_remaining <= 0:
|
|
141
|
+
self._deactivate_push()
|
|
142
|
+
self._idle_steps_remaining = self._sample_interval_steps()
|
|
143
|
+
else:
|
|
144
|
+
self._idle_steps_remaining -= 1
|
|
145
|
+
if self._idle_steps_remaining <= 0:
|
|
146
|
+
self._activate_push()
|
|
147
|
+
|
|
148
|
+
buffers = self._get_buffers()
|
|
149
|
+
eid = self.env_id
|
|
150
|
+
|
|
151
|
+
if self._push_active:
|
|
152
|
+
assert self._current_force_w is not None
|
|
153
|
+
force_w = self._current_force_w.detach().cpu().numpy().copy()
|
|
154
|
+
body_pos = self._sim._robot.data.body_pos_w[eid, self._current_isaac_body_id].detach().cpu().numpy().copy()
|
|
155
|
+
body_idx = self._current_body_names_idx
|
|
156
|
+
active = 1.0
|
|
157
|
+
else:
|
|
158
|
+
force_w = np.zeros(3, dtype=np.float32)
|
|
159
|
+
body_pos = np.zeros(3, dtype=np.float32)
|
|
160
|
+
body_idx = -1
|
|
161
|
+
active = 0.0
|
|
162
|
+
|
|
163
|
+
if buffers is not None:
|
|
164
|
+
buffers["push_active"].append(np.array(active, dtype=np.float32))
|
|
165
|
+
buffers["push_force_w"].append(force_w.astype(np.float32))
|
|
166
|
+
buffers["push_body_pos_w"].append(body_pos.astype(np.float32))
|
|
167
|
+
buffers["push_body_idx"].append(np.array(body_idx, dtype=np.int32))
|
|
168
|
+
|
|
169
|
+
self._step_count += 1
|
|
170
|
+
return actor_state
|
|
171
|
+
|
|
172
|
+
def on_post_evaluate_policy(self) -> None:
|
|
173
|
+
if self._push_active:
|
|
174
|
+
self._deactivate_push()
|
|
175
|
+
self._get_env()._apply_force_in_physics_step = self._original_apply_force
|
|
176
|
+
logger.info(f"EvalPushCallback: completed {self._step_count} steps")
|
|
177
|
+
|
|
178
|
+
# ------------------------------------------------------------------
|
|
179
|
+
# Push lifecycle
|
|
180
|
+
# ------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
def _activate_push(self) -> None:
|
|
183
|
+
name, body_names_idx, isaac_body_id = random.choice(self._resolved_bodies)
|
|
184
|
+
self._current_body_names_idx = body_names_idx
|
|
185
|
+
self._current_isaac_body_id = isaac_body_id
|
|
186
|
+
|
|
187
|
+
# Random force magnitude
|
|
188
|
+
mag = random.uniform(*self.config.force_range)
|
|
189
|
+
|
|
190
|
+
# Random horizontal direction (XY plane)
|
|
191
|
+
angle = random.uniform(0, 2 * math.pi)
|
|
192
|
+
direction = torch.tensor(
|
|
193
|
+
[math.cos(angle), math.sin(angle), 0.0],
|
|
194
|
+
dtype=torch.float32,
|
|
195
|
+
device=self._sim.sim_device,
|
|
196
|
+
)
|
|
197
|
+
self._current_force_w = direction * mag
|
|
198
|
+
|
|
199
|
+
# Duration in env steps
|
|
200
|
+
dur_s = random.uniform(*self.config.duration_s)
|
|
201
|
+
self._push_steps_remaining = max(1, round(dur_s / self._dt))
|
|
202
|
+
|
|
203
|
+
self._push_active = True
|
|
204
|
+
logger.info(
|
|
205
|
+
f"PUSH START body='{name}' force={mag:.0f}N "
|
|
206
|
+
f"dir=[{direction[0].item():.2f},{direction[1].item():.2f}] "
|
|
207
|
+
f"duration={self._push_steps_remaining} steps ({dur_s:.2f}s)"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def _deactivate_push(self) -> None:
|
|
211
|
+
self._clear_push_force()
|
|
212
|
+
self._push_active = False
|
|
213
|
+
self._current_force_w = None
|
|
214
|
+
self._current_body_names_idx = -1
|
|
215
|
+
self._current_isaac_body_id = -1
|
|
216
|
+
logger.info("PUSH END")
|
|
217
|
+
|
|
218
|
+
# ------------------------------------------------------------------
|
|
219
|
+
# Force application (called each physics substep via monkey-patch)
|
|
220
|
+
# ------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
def _apply_push_force(self) -> None:
|
|
223
|
+
from isaaclab.utils.math import quat_apply_inverse
|
|
224
|
+
|
|
225
|
+
eid = self.env_id
|
|
226
|
+
isaac_body_id = self._current_isaac_body_id
|
|
227
|
+
|
|
228
|
+
# Transform world-frame force to body-local frame
|
|
229
|
+
# (IsaacLab 2.1 hardcodes is_global=False)
|
|
230
|
+
body_quat_w = self._sim._robot.data.body_quat_w[eid, isaac_body_id]
|
|
231
|
+
force_body = quat_apply_inverse(body_quat_w, self._current_force_w)
|
|
232
|
+
|
|
233
|
+
forces = force_body.unsqueeze(0).unsqueeze(0) # [1, 1, 3]
|
|
234
|
+
torques = torch.zeros_like(forces) # [1, 1, 3]
|
|
235
|
+
|
|
236
|
+
self._sim._robot.set_external_force_and_torque(
|
|
237
|
+
forces=forces,
|
|
238
|
+
torques=torques,
|
|
239
|
+
env_ids=torch.tensor([eid], device=self._sim.sim_device),
|
|
240
|
+
body_ids=torch.tensor([isaac_body_id], device=self._sim.sim_device),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def _clear_push_force(self) -> None:
|
|
244
|
+
if self._current_isaac_body_id < 0:
|
|
245
|
+
return
|
|
246
|
+
zero = torch.zeros(1, 1, 3, device=self._sim.sim_device)
|
|
247
|
+
self._sim._robot.set_external_force_and_torque(
|
|
248
|
+
forces=zero,
|
|
249
|
+
torques=zero,
|
|
250
|
+
env_ids=torch.tensor([self.env_id], device=self._sim.sim_device),
|
|
251
|
+
body_ids=torch.tensor([self._current_isaac_body_id], device=self._sim.sim_device),
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# ------------------------------------------------------------------
|
|
255
|
+
# Helpers
|
|
256
|
+
# ------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
def _sample_interval_steps(self) -> int:
|
|
259
|
+
s = random.uniform(*self.config.interval_s)
|
|
260
|
+
return max(1, round(s / self._dt))
|
|
261
|
+
|
|
262
|
+
def _get_buffers(self) -> dict[str, list[np.ndarray]] | None:
|
|
263
|
+
return self._record_buffers
|