diff --git a/UPDATE.md b/UPDATE.md index edc902d..cfc8b01 100644 --- a/UPDATE.md +++ b/UPDATE.md @@ -1,5 +1,7 @@ # UPDATE -TODO: 即使模型崩溃也要继续测完后续的goals +## 20251222 +### v0.1.13 +1. 即使模型崩溃也要继续测完后续的goals, 但是跳过当前的sub goals ## 20251221 ### v0.1.12 1. 在模型崩溃时也记录下最后的gauge信息, 修改single/multi pipeline逻辑 diff --git a/robogauge/tasks/__init__.py b/robogauge/tasks/__init__.py index 2074fa3..ad85e3f 100644 --- a/robogauge/tasks/__init__.py +++ b/robogauge/tasks/__init__.py @@ -10,4 +10,6 @@ task_register.register('base', BasePipeline, MujocoConfig, BaseGaugeConfig, Robo task_register.register('go2_flat', BasePipeline, MujocoConfig, Go2FlatGaugeConfig, Go2Config) task_register.register('go2_moe_flat', BasePipeline, MujocoConfig, Go2FlatGaugeConfig, Go2MoEConfig) task_register.register('go2_slope', BasePipeline, Go2SlopeMujocoConfig, Go2SlopeGaugeConfig, Go2Config) -task_register.register('go2_moe_slope', BasePipeline, Go2SlopeMujocoConfig, Go2SlopeGaugeConfig, Go2MoEConfig) \ No newline at end of file +task_register.register('go2_moe_slope', BasePipeline, Go2SlopeMujocoConfig, Go2SlopeGaugeConfig, Go2MoEConfig) +task_register.register('go2_wave', BasePipeline, MujocoConfig, Go2WaveGaugeConfig, Go2Config) +task_register.register('go2_moe_wave', BasePipeline, MujocoConfig, Go2WaveGaugeConfig, Go2MoEConfig) diff --git a/robogauge/tasks/custom/go2/__init__.py b/robogauge/tasks/custom/go2/__init__.py index 3d67a3f..1e11955 100644 --- a/robogauge/tasks/custom/go2/__init__.py +++ b/robogauge/tasks/custom/go2/__init__.py @@ -8,3 +8,4 @@ ''' from .go2_flat_task import Go2FlatGaugeConfig from .go2_slope_task import Go2SlopeGaugeConfig, Go2SlopeMujocoConfig +from .go2_wave_task import Go2WaveGaugeConfig diff --git a/robogauge/tasks/custom/go2/go2_wave_task.py b/robogauge/tasks/custom/go2/go2_wave_task.py new file mode 100644 index 0000000..c98237e --- /dev/null +++ b/robogauge/tasks/custom/go2/go2_wave_task.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +''' +@File : go2_wave_task.py +@Time : 2025/12/22 11:02:23 +@Author : wty-yy +@Version : 1.0 +@Blog : https://wty-yy.github.io/ +@Desc : Go2 Wave Task Configuration +''' +from robogauge.tasks.robots import Go2Config, Go2MoEConfig +from robogauge.tasks.gauge import WaveGaugeConfig +from robogauge.tasks.simulator.mujoco_config import MujocoConfig + +class Go2WaveGaugeConfig(WaveGaugeConfig): + class metrics(WaveGaugeConfig.metrics): + class dof_limits(WaveGaugeConfig.metrics.dof_limits): + enabled = True + soft_dof_limit_ratio = 0.7 + dof_names = ['hip', 'thigh'] # List of DOF names to monitor, None for all diff --git a/robogauge/tasks/gauge/__init__.py b/robogauge/tasks/gauge/__init__.py index 1ce2ec5..11f4776 100644 --- a/robogauge/tasks/gauge/__init__.py +++ b/robogauge/tasks/gauge/__init__.py @@ -2,3 +2,4 @@ from .base_gauge import BaseGauge from .base_gauge_config import BaseGaugeConfig from .gauge_configs.flat_gauge_config import FlatGaugeConfig from .gauge_configs.slope_gauge_config import SlopeGaugeConfig +from .gauge_configs.wave_gauge_config import WaveGaugeConfig diff --git a/robogauge/tasks/gauge/base_gauge.py b/robogauge/tasks/gauge/base_gauge.py index e7e02d2..344e390 100644 --- a/robogauge/tasks/gauge/base_gauge.py +++ b/robogauge/tasks/gauge/base_gauge.py @@ -42,7 +42,7 @@ class BaseGauge: self.info = {'goal': [], 'metric': []} self.results = {} # {'goal/sub_goal': {'metric': result}} - log_str = "Initialized Gauge with Goals and Metrics:\n" + log_str = "Initialized Gauge with Goals 🎯 and Metrics 📊:\n" for name, kwargs in self.goals_cfg.items(): if not kwargs['enabled']: continue if name == 'max_velocity': diff --git a/robogauge/tasks/gauge/gauge_configs/wave_gauge_config.py b/robogauge/tasks/gauge/gauge_configs/wave_gauge_config.py new file mode 100644 index 0000000..9046361 --- /dev/null +++ b/robogauge/tasks/gauge/gauge_configs/wave_gauge_config.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +''' +@File : wave_gauge_config.py +@Time : 2025/12/22 11:02:09 +@Author : wty-yy +@Version : 1.0 +@Blog : https://wty-yy.github.io/ +@Desc : Wave Gauge Configuration +''' +from robogauge.tasks.gauge.base_gauge_config import BaseGaugeConfig + +class WaveGaugeConfig(BaseGaugeConfig): + gauge_class = 'BaseGauge' + + class assets(BaseGaugeConfig.assets): + terrain_name = "wave_1" # {type}_{level} + terrain_xml = '{ROBOGAUGE_ROOT_DIR}/resources/terrains/wave/wave_1.xml' + terrain_spawn_pos = [1.5, 0, 1] # x y z [m], robot freejoint spawn position on the terrain + + class goals: + class target_pos_velocity: # goal to reach a target position by velocity command + enabled = True + target_pos = [4, 0, 1.0] # x y z [m], target position in the environment, used for target position goal + lin_vel_x = 1.0 # +/- m/s + lin_vel_y = 1.0 # +/- m/s + ang_vel_yaw = 1.5 # +/- rad/s + max_cmd_duration = 20.0 # [s] maximum duration to reach the target position + reach_threshold = 0.1 diff --git a/robogauge/tasks/pipeline/base_pipeline.py b/robogauge/tasks/pipeline/base_pipeline.py index 235cdd2..ad6fb7d 100644 --- a/robogauge/tasks/pipeline/base_pipeline.py +++ b/robogauge/tasks/pipeline/base_pipeline.py @@ -61,15 +61,16 @@ class BasePipeline: def run(self): logger.info(f"🚀 Starting single run: {self.run_name}") - try: - self.load() - sim_data = self.sim.step() - frame_skip = int(self.robot_cfg.control.control_dt / self.sim_cfg.physics.simulation_dt) - assert frame_skip * self.sim_cfg.physics.simulation_dt == self.robot_cfg.control.control_dt, \ - "Control dt must be multiple of simulation dt." - logger.info(f"Sim FPS: {1.0 / self.sim_cfg.physics.simulation_dt:.2f}, Control FPS: {1.0 / self.robot_cfg.control.control_dt:.2f}, Frame Skip: {frame_skip:d}") - logger.info("Running pipeline...") - while not self.gauge.is_done(): + self.load() + sim_data = self.sim.step() + frame_skip = int(self.robot_cfg.control.control_dt / self.sim_cfg.physics.simulation_dt) + assert frame_skip * self.sim_cfg.physics.simulation_dt == self.robot_cfg.control.control_dt, \ + "Control dt must be multiple of simulation dt." + logger.info(f"Sim FPS: {1.0 / self.sim_cfg.physics.simulation_dt:.2f}, Control FPS: {1.0 / self.robot_cfg.control.control_dt:.2f}, Frame Skip: {frame_skip:d}") + logger.info("Running pipeline...") + error = None + while not self.gauge.is_done(): + try: if self.first_reset: # wait for robot to be still goal_data = GoalData( goal_type=self.robot_cfg.control.support_goal, @@ -82,7 +83,7 @@ class BasePipeline: goal_data = self.gauge.get_goal(sim_data) if goal_data is None: # Change goal - sim_data = self.reset_sim(sim_data) + sim_data = self.reset_sim_and_robot(sim_data) continue if goal_data.visualization_pos is not None: @@ -103,25 +104,27 @@ class BasePipeline: sim_data = self.sim.step() self.gauge.update_metrics(sim_data, goal_data) if self.gauge.is_reset(sim_data): - sim_data = self.reset_sim(sim_data) - except Exception as e: - logger.error(f"❌ Pipeline execution failed with error: {e},\n{traceback.format_exc()}") - self.gauge.switch_to_next_goal() # save current goal metrics - self.gauge.save_results() - return logger.log_dir, e - finally: - self.sim.close_viewer() - self.sim.close_video_writer() - logger.info("✅ Pipeline execution finished.") - logger.info(f"📁 Logging saved at: {logger.log_dir}") + sim_data = self.reset_sim_and_robot(sim_data) + except Exception as e: + error = e + logger.error(f"❌ Goal '{self.gauge.goal_str}' failed with error: {e},\n{traceback.format_exc()}") + self.gauge.switch_to_next_goal() # save current goal metrics + sim_data = self.reset_sim_and_robot(sim_data) + logger.info("⏩ Pipeline recovered from error and continued next goal 🎯.") - return logger.log_dir, None + self.sim.close_viewer() + self.sim.close_video_writer() + logger.info("✅ Pipeline execution finished.") + logger.info(f"📁 Logging saved at: {logger.log_dir}") + + return logger.log_dir, error - def reset_sim(self, sim_data: SimData): + def reset_sim_and_robot(self, sim_data: SimData): self.sim.reset() self.last_reset_time = sim_data.sim_time self.first_reset = True sim_data = self.sim.step() + self.robot.reset() return sim_data def add_noise(self, sim_data: SimData): diff --git a/robogauge/tasks/robots/base_robot.py b/robogauge/tasks/robots/base_robot.py index 0cd1d8e..4545d7f 100644 --- a/robogauge/tasks/robots/base_robot.py +++ b/robogauge/tasks/robots/base_robot.py @@ -45,3 +45,6 @@ class BaseRobot: action = np.zeros(self.num_action, dtype=np.float32) return action, self.p_gains, self.d_gains, self.control_type + def reset(self): + """ Reset model state/history if needed """ + pass diff --git a/robogauge/tasks/robots/go2/go2.py b/robogauge/tasks/robots/go2/go2.py index 3b93b94..f799880 100644 --- a/robogauge/tasks/robots/go2/go2.py +++ b/robogauge/tasks/robots/go2/go2.py @@ -53,6 +53,10 @@ class Go2(BaseRobot): raise NotImplementedError(f"Goal type '{goal_data.goal_type}' not implemented in Go2 robot.") return obs + def reset(self): + self.last_action = np.zeros(self.num_action, dtype=np.float32) + self.model.reset() # reset history + def get_action(self, obs: np.ndarray): obs_tensor = torch.tensor(obs, dtype=torch.float32).unsqueeze(0).to(self.device) action = self.model(obs_tensor).detach().cpu().numpy().squeeze(0)[self.model2mj_idx] diff --git a/robogauge/tasks/robots/go2/go2_config.py b/robogauge/tasks/robots/go2/go2_config.py index f927675..48ccfa5 100644 --- a/robogauge/tasks/robots/go2/go2_config.py +++ b/robogauge/tasks/robots/go2/go2_config.py @@ -48,7 +48,7 @@ class Go2Config(RobotConfig): class commands(RobotConfig.commands): lin_vel_x = [-2.0, 2.0] # min max [m/s] - lin_vel_y = [-1.0, 1.0] # min max [m/s] + lin_vel_y = [-2.0, 2.0] # min max [m/s] lin_vel_z = None # min max [m/s] ang_vel_roll = None # min max [rad/s] ang_vel_pitch = None # min max [rad/s] diff --git a/robogauge/utils/helpers.py b/robogauge/utils/helpers.py index 98515ab..cf7e4fa 100644 --- a/robogauge/utils/helpers.py +++ b/robogauge/utils/helpers.py @@ -73,6 +73,8 @@ def parse_args(): {"name": "--seed", "type": int, "default": 42, "help": "Random seed."}, {"name": "--write-tensorboard", "action": "store_true", "default": False, "help": "Write tensorboard logs."}, {"name": "--plot-radar", "action": "store_true", "default": False, "help": "Plot radar charts for metrics."}, + {"name": "--base-mass", "type": float, "default": 0.0, "help": "Set the base mass of the robot."}, + {"name": "--friction", "type": float, "default": 1.0, "help": "Set the ground friction coefficient."}, # Multiprocessing parameters, with different seeds {"name": "--multi", "action": "store_true", "default": False, "help": "Enable multiprocessing."}, diff --git a/robogauge/utils/task_register.py b/robogauge/utils/task_register.py index 2cc5f40..d7040e3 100644 --- a/robogauge/utils/task_register.py +++ b/robogauge/utils/task_register.py @@ -65,9 +65,9 @@ class TaskRegister(): sim_cfg.render.save_video = args.save_video if args.write_tensorboard is not None: gauger_cfg.write_tensorboard = args.write_tensorboard - if hasattr(args, 'friction') and args.friction is not None: + if args.friction is not None: sim_cfg.domain_rand.friction = args.friction - if hasattr(args, 'base_mass') and args.base_mass is not None: + if args.base_mass is not None: sim_cfg.domain_rand.base_mass = args.base_mass task_register = TaskRegister()