From 1ae8d6505990eda7a1a9fe55ca92c27a6a830367 Mon Sep 17 00:00:00 2001 From: Stone Tao Date: Mon, 29 Apr 2024 15:49:50 -0700 Subject: [PATCH] Cartpole (#296) * work * add cartpole robot urdf * work * Update cartpole.py * align reward functions, change camera pose * align to dm control time horizons, example ppo solving scripts * fix bug with record episode and reward computations * work * remove old assets * Update index.md --------- Co-authored-by: chenbao --- docs/source/tasks/index.md | 40 ++++-- examples/baselines/ppo/README.md | 10 +- figures/environment_demos/MS-CartPole-v1.mp4 | Bin 44940 -> 0 bytes .../MS-CartpoleBalance-v1_rt.mp4 | Bin 0 -> 27453 bytes mani_skill/envs/scene.py | 22 ++-- mani_skill/envs/tasks/control/__init__.py | 2 +- mani_skill/envs/tasks/control/cartpole.py | 122 +++++++++++++----- mani_skill/envs/utils/rewards/common.py | 59 ++++++++- mani_skill/trajectory/replay_trajectory.py | 4 + mani_skill/utils/wrappers/record.py | 4 +- 10 files changed, 202 insertions(+), 61 deletions(-) delete mode 100644 figures/environment_demos/MS-CartPole-v1.mp4 create mode 100644 figures/environment_demos/MS-CartpoleBalance-v1_rt.mp4 diff --git a/docs/source/tasks/index.md b/docs/source/tasks/index.md index 87ce7b707..ca52cbb1b 100644 --- a/docs/source/tasks/index.md +++ b/docs/source/tasks/index.md @@ -280,28 +280,42 @@ Using the TriFingerPro robot, rotate a cube ## Control Tasks -### MS-CartPole-v1 - +### MS-CartpoleBalance-v1 :::{dropdown} Task Card :icon: note :color: primary **Task Description:** -Keep the CartPole stable and up right by sliding it left and right +Use the Cartpole robot to balance a pole on a cart. -**Supported Robots: None** -**Randomizations:** -- TODO +**Supported Robots: Cartpole** -**Success Conditions:** -- the cart is within 0.25m of the center of the rail (which is at 0) -- the cosine of the hinge angle attaching the pole is between 0.995 and 1 +**Randomizations:** +- Pole direction is randomized around the vertical axis. the range is [-0.05, 0.05] radians. -**Goal Specification:** -- None +**Fail Conditions:** +- Pole is lower than the horizontal plane \ No newline at end of file + + + +### MS-CartpoleSwingup-v1 + +:::{dropdown} Task Card +:icon: note +:color: primary + +**Task Description:** +Use the Cartpole robot to swing up a pole on a cart. + + +**Supported Robots: Cartpole** + +**Randomizations:** +- Pole direction is randomized around the whole circle. the range is [-pi, pi] radians. + +**Success Conditions:** +- No specific success conditions. The task is considered successful if the pole is upright for the whole episode. We can threshold the episode accumulated reward to determine success. diff --git a/examples/baselines/ppo/README.md b/examples/baselines/ppo/README.md index fbbd69482..438b87941 100644 --- a/examples/baselines/ppo/README.md +++ b/examples/baselines/ppo/README.md @@ -61,9 +61,15 @@ python ppo.py --env_id="RotateCubeLevel4-v1" \ --num_envs=1024 --update_epochs=8 --num_minibatches=32 \ --total_timesteps=500_000_000 --num-steps=250 --num-eval-steps=250 -python ppo.py --env_id="MS-CartPole-v1" \ +python ppo.py --env_id="MS-CartpoleBalance-v1" \ --num_envs=1024 --update_epochs=8 --num_minibatches=32 \ - --total_timesteps=10_000_000 --num-steps=500 --num-eval-steps=500 \ + --total_timesteps=4_000_000 --num-steps=250 --num-eval-steps=1000 \ + --gamma=0.99 --gae_lambda=0.95 \ + --eval_freq=5 + +python ppo.py --env_id="MS-CartpoleSwingUp-v1" \ + --num_envs=1024 --update_epochs=8 --num_minibatches=32 \ + --total_timesteps=10_000_000 --num-steps=250 --num-eval-steps=1000 \ --gamma=0.99 --gae_lambda=0.95 \ --eval_freq=5 diff --git a/figures/environment_demos/MS-CartPole-v1.mp4 b/figures/environment_demos/MS-CartPole-v1.mp4 deleted file mode 100644 index fe7260a51680aa491e2e47253b875b5ab5c76c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44940 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 aGsAo|*`UGdd~Ng6dlxOaZ_(oJ?!N(O17EQK diff --git a/figures/environment_demos/MS-CartpoleBalance-v1_rt.mp4 b/figures/environment_demos/MS-CartpoleBalance-v1_rt.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..8ae31e61f8ffee311c3585aaad9b900123043506 GIT binary patch literal 27453 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%0DTsc{^+M%%-!7@5zT^k^6^R29Yw@mgyh(MnNU% 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 VK&FV5dyJ2&x}1`RoT7rl{{iL~jA8%) literal 0 HcmV?d00001 diff --git a/mani_skill/envs/scene.py b/mani_skill/envs/scene.py index 22748c0df..ed0d835d2 100644 --- a/mani_skill/envs/scene.py +++ b/mani_skill/envs/scene.py @@ -485,17 +485,23 @@ def get_sim_state(self) -> torch.Tensor: state_dict["articulations"][ articulation.name ] = articulation.get_state().clone() + if len(state_dict["actors"]) == 0: + del state_dict["actors"] + if len(state_dict["articulations"]) == 0: + del state_dict["articulations"] return state_dict def set_sim_state(self, state: Dict): - for actor_id, actor_state in state["actors"].items(): - if len(actor_state.shape) == 1: - actor_state = actor_state[None, :] - self.actors[actor_id].set_state(actor_state) - for art_id, art_state in state["articulations"].items(): - if len(art_state.shape) == 1: - art_state = art_state[None, :] - self.articulations[art_id].set_state(art_state) + if "actors" in state: + for actor_id, actor_state in state["actors"].items(): + if len(actor_state.shape) == 1: + actor_state = actor_state[None, :] + self.actors[actor_id].set_state(actor_state) + if "articulations" in state: + for art_id, art_state in state["articulations"].items(): + if len(art_state.shape) == 1: + art_state = art_state[None, :] + self.articulations[art_id].set_state(art_state) # ---------------------------------------------------------------------------- # # GPU Simulation Management diff --git a/mani_skill/envs/tasks/control/__init__.py b/mani_skill/envs/tasks/control/__init__.py index 6c9c00c7b..920f2d3f6 100644 --- a/mani_skill/envs/tasks/control/__init__.py +++ b/mani_skill/envs/tasks/control/__init__.py @@ -1 +1 @@ -from .cartpole import CartPoleEnv +from .cartpole import CartpoleBalanceEnv, CartpoleSwingUpEnv diff --git a/mani_skill/envs/tasks/control/cartpole.py b/mani_skill/envs/tasks/control/cartpole.py index a5caef2fa..f6812c0b3 100644 --- a/mani_skill/envs/tasks/control/cartpole.py +++ b/mani_skill/envs/tasks/control/cartpole.py @@ -8,17 +8,23 @@ from mani_skill.agents.base_agent import BaseAgent from mani_skill.agents.controllers import * from mani_skill.envs.sapien_env import BaseEnv -from mani_skill.envs.utils import randomization +from mani_skill.envs.utils import randomization, rewards from mani_skill.sensors.camera import CameraConfig from mani_skill.utils import common, sapien_utils from mani_skill.utils.registration import register_env -from mani_skill.utils.structs.types import SceneConfig, SimConfig +from mani_skill.utils.structs.pose import Pose +from mani_skill.utils.structs.types import ( + Array, + GPUMemoryConfig, + SceneConfig, + SimConfig, +) MJCF_FILE = f"{os.path.join(os.path.dirname(__file__), 'assets/cartpole.xml')}" class CartPoleRobot(BaseAgent): - uid = "cartpole" + uid = "cart_pole" mjcf_path = MJCF_FILE @property @@ -57,15 +63,23 @@ def _load_articulation(self): self.robot_link_ids = [link.name for link in self.robot.get_links()] -@register_env("MS-CartPole-v1", max_episode_steps=500) -class CartPoleEnv(BaseEnv): - SUPPORTED_REWARD_MODES = ["sparse", "none"] +# @register_env("MS-CartPole-v1", max_episode_steps=500) +# class CartPoleEnv(BaseEnv): +# SUPPORTED_REWARD_MODES = ["sparse", "none"] - SUPPORTED_ROBOTS = [CartPoleRobot] - agent: Union[CartPoleRobot] +# SUPPORTED_ROBOTS = [CartPoleRobot] +# agent: Union[CartPoleRobot] + +# CART_RANGE = [-0.25, 0.25] +# ANGLE_COSINE_RANGE = [0.995, 1] + +# def __init__(self, *args, robot_uids=CartPoleRobot, **kwargs): +# super().__init__(*args, robot_uids=robot_uids, **kwargs) - CART_RANGE = [-0.25, 0.25] - ANGLE_COSINE_RANGE = [0.995, 1] + +class CartpoleEnv(BaseEnv): + + agent: Union[CartPoleRobot] def __init__(self, *args, robot_uids=CartPoleRobot, **kwargs): super().__init__(*args, robot_uids=robot_uids, **kwargs) @@ -94,6 +108,60 @@ def _load_scene(self, options: dict): for a in actor_builders: a.build(a.name) + def evaluate(self): + return dict() + + def _get_obs_extra(self, info: Dict): + obs = dict( + velocity=self.agent.robot.links_map["pole_1"].linear_velocity, + angular_velocity=self.agent.robot.links_map["pole_1"].angular_velocity, + ) + return obs + + @property + def pole_angle_cosine(self): + return torch.cos(self.agent.robot.joints_map["hinge_1"].qpos) + + def compute_dense_reward(self, obs: Any, action: Array, info: Dict): + cart_pos = self.agent.robot.links_map["cart"].pose.p[ + :, 0 + ] # (B, ), we only care about x position + centered = rewards.tolerance(cart_pos, margin=2) + centered = (1 + centered) / 2 # (B, ) + + small_control = rewards.tolerance( + action, margin=1, value_at_margin=0, sigmoid="quadratic" + )[:, 0] + small_control = (4 + small_control) / 5 + + angular_vel = self.agent.robot.get_qvel()[:, 1] + small_velocity = rewards.tolerance(angular_vel, margin=5) + small_velocity = (1 + small_velocity) / 2 # (B, ) + + upright = (self.pole_angle_cosine + 1) / 2 # (B, ) + + # upright is 1 when the pole is upright, 0 when the pole is upside down + # small_control is 1 when the action is small, 0.8 when the action is large + # small_velocity is 1 when the angular velocity is small, 0.5 when the angular velocity is large + # centered is 1 when the cart is centered, 0 when the cart is at the edge of the screen + + reward = upright * centered * small_control * small_velocity + return reward + + def compute_normalized_dense_reward(self, obs: Any, action: Array, info: Dict): + # this should be equal to compute_dense_reward / max possible reward + max_reward = 1.0 + return self.compute_dense_reward(obs=obs, action=action, info=info) / max_reward + + +@register_env("MS-CartpoleBalance-v1", max_episode_steps=1000) +class CartpoleBalanceEnv(CartpoleEnv): + def __init__(self, *args, **kwargs): + super().__init__( + *args, + **kwargs, + ) + def _initialize_episode(self, env_idx: torch.Tensor, options: dict): with torch.device(self.device): b = len(env_idx) @@ -104,38 +172,24 @@ def _initialize_episode(self, env_idx: torch.Tensor, options: dict): self.agent.robot.set_qpos(qpos) self.agent.robot.set_qvel(qvel) - @property - def pole_angle_cosine(self): - return torch.cos(self.agent.robot.joints_map["hinge_1"].qpos) - def evaluate(self): - cart_pos = self.agent.robot.joints_map["slider"].qpos - pole_angle_cosine = self.pole_angle_cosine - cart_in_bounds = cart_pos < self.CART_RANGE[1] - cart_in_bounds = cart_in_bounds & (cart_pos > self.CART_RANGE[0]) - angle_in_bounds = pole_angle_cosine < self.ANGLE_COSINE_RANGE[1] - angle_in_bounds = angle_in_bounds & ( - pole_angle_cosine > self.ANGLE_COSINE_RANGE[0] - ) - return {"cart_in_bounds": cart_in_bounds, "angle_in_bounds": angle_in_bounds} + return dict(fail=self.pole_angle_cosine < 0) - def _get_obs_extra(self, info: Dict): - return dict() - - def compute_sparse_reward(self, obs: Any, action: torch.Tensor, info: Dict): - return info["cart_in_bounds"] * info["angle_in_bounds"] +@register_env("MS-CartpoleSwingUp-v1", max_episode_steps=1000) +class CartpoleSwingUpEnv(CartpoleEnv): + def __init__(self, *args, **kwargs): + super().__init__( + *args, + **kwargs, + ) -@register_env("CartPoleSwingUp-v1", max_episode_steps=500, override=True) -class CartPoleSwingUpEnv(CartPoleEnv): def _initialize_episode(self, env_idx: torch.Tensor, options: dict): with torch.device(self.device): b = len(env_idx) qpos = torch.zeros((b, 2)) - qpos[:, 0] = 0.01 * torch.randn(size=(b,)) - qpos[:, 1] = torch.pi + 0.01 * torch.randn(size=(b,)) + qpos[:, 0] = torch.randn((b,)) * 0.01 + qpos[:, 1] = torch.randn((b,)) * 0.01 + torch.pi qvel = torch.randn(size=(b, 2)) * 0.01 self.agent.robot.set_qpos(qpos) self.agent.robot.set_qvel(qvel) - # Note DM-Control sets some randomness to other qpos values but am not sure what they are - # as cartpole.xml seems to only load two joints diff --git a/mani_skill/envs/utils/rewards/common.py b/mani_skill/envs/utils/rewards/common.py index 707258f26..23f9ee127 100644 --- a/mani_skill/envs/utils/rewards/common.py +++ b/mani_skill/envs/utils/rewards/common.py @@ -1 +1,58 @@ -"""Useful utilities for reward functions""" +import torch + + +def tolerance( + x, lower=0.0, upper=0.0, margin=0.0, sigmoid="gaussian", value_at_margin=0.1 +): + # modified from https://github.com/google-deepmind/dm_control/blob/554ad2753df914372597575505249f22c255979d/dm_control/utils/rewards.py#L93 + """Returns 1 when `x` falls inside the bounds, between 0 and 1 otherwise. + + Args: + x: A torch array. (B, 3) + lower, upper: specifying inclusive `(lower, upper)` bounds for + the target interval. These can be infinite if the interval is unbounded + at one or both ends, or they can be equal to one another if the target + value is exact. + margin: Float. Parameter that controls how steeply the output decreases as + `x` moves out-of-bounds. + * If `margin == 0` then the output will be 0 for all values of `x` + outside of `bounds`. + * If `margin > 0` then the output will decrease sigmoidally with + increasing distance from the nearest bound. + sigmoid: String, choice of sigmoid type. Valid values are: 'gaussian', + 'linear', 'hyperbolic', 'long_tail', 'cosine', 'tanh_squared'. + value_at_margin: A float between 0 and 1 specifying the output value when + the distance from `x` to the nearest bound is equal to `margin`. Ignored + if `margin == 0`. todo: not implemented yet + + Returns: + A torch array with values between 0.0 and 1.0. + + Raises: + ValueError: If `bounds[0] > bounds[1]`. + ValueError: If `margin` is negative. + """ + if lower > upper: + raise ValueError("Lower bound must be <= upper bound.") + + if margin < 0: + raise ValueError("`margin` must be non-negative.") + + in_bounds = torch.logical_and(lower <= x, x <= upper) + + if margin == 0: + value = torch.where(in_bounds, torch.tensor(1.0), torch.tensor(0.0)) + else: + d = torch.where(x < lower, lower - x, x - upper) / margin + if sigmoid == "gaussian": + value = torch.where( + in_bounds, torch.tensor(1.0), torch.exp(-0.5 * (d**2)) + ) + elif sigmoid == "hyperbolic": + value = torch.where(in_bounds, torch.tensor(1.0), 1 / (1 + torch.exp(d))) + elif sigmoid == "quadratic": + value = torch.where(in_bounds, torch.tensor(1.0), 1 - d**2) + else: + raise ValueError(f"Unknown sigmoid type {sigmoid!r}.") + + return value diff --git a/mani_skill/trajectory/replay_trajectory.py b/mani_skill/trajectory/replay_trajectory.py index 3f4a778d4..f04179e80 100644 --- a/mani_skill/trajectory/replay_trajectory.py +++ b/mani_skill/trajectory/replay_trajectory.py @@ -343,6 +343,9 @@ def parse_args(args=None): type=str, help="Change shader used for rendering. Default is 'default' which is very fast. Can also be 'rt' for ray tracing and generating photo-realistic renders. Can also be 'rt-fast' for a faster but lower quality ray-traced renderer", ) + parser.add_argument( + "--video-fps", default=30, type=int, help="The FPS of saved videos" + ) return parser.parse_args(args) @@ -418,6 +421,7 @@ def _main(args, proc_id: int = 0, num_procs=1, pbar=None): save_trajectory=args.save_traj, trajectory_name=new_traj_name, save_video=args.save_video, + video_fps=args.video_fps, record_reward=args.record_rewards, ) diff --git a/mani_skill/utils/wrappers/record.py b/mani_skill/utils/wrappers/record.py index 11af5383d..faa0cbab1 100644 --- a/mani_skill/utils/wrappers/record.py +++ b/mani_skill/utils/wrappers/record.py @@ -216,7 +216,7 @@ def __init__( max_steps_per_video=None, clean_on_close=True, record_reward=True, - video_fps=20, + video_fps=30, source_type=None, source_desc=None, ): @@ -597,7 +597,7 @@ def recursive_add_to_h5py(group: h5py.Group, data: dict, key): dtype=bool, ) episode_info.update( - fail=self._trajectory_buffer.success[end_ptr - 1, env_idx] + fail=self._trajectory_buffer.fail[end_ptr - 1, env_idx] ) recursive_add_to_h5py(group, self._trajectory_buffer.state, "env_states") if self.record_reward: