From f92f34ac14d5dce00c3c01792bce0d485b9fcf9a Mon Sep 17 00:00:00 2001 From: wty-yy <993660140@qq.com> Date: Sat, 27 Dec 2025 17:42:30 +0800 Subject: [PATCH] v0.1.17 --- CMD.md | 20 ++++++- README.md | 8 +++ UPDATE.md | 8 +++ resources/terrains/obstacle/obstacle_1.xml | 23 ++++++++ resources/terrains/obstacle/obstacle_10.xml | 23 ++++++++ resources/terrains/obstacle/obstacle_2.xml | 23 ++++++++ resources/terrains/obstacle/obstacle_3.xml | 23 ++++++++ resources/terrains/obstacle/obstacle_4.xml | 23 ++++++++ resources/terrains/obstacle/obstacle_5.xml | 23 ++++++++ resources/terrains/obstacle/obstacle_6.xml | 23 ++++++++ resources/terrains/obstacle/obstacle_7.xml | 23 ++++++++ resources/terrains/obstacle/obstacle_8.xml | 23 ++++++++ resources/terrains/obstacle/obstacle_9.xml | 23 ++++++++ robogauge/tasks/__init__.py | 2 + robogauge/tasks/custom/go2/__init__.py | 1 + .../tasks/custom/go2/go2_obstacle_task.py | 19 +++++++ robogauge/tasks/gauge/__init__.py | 1 + .../gauge_configs/obstacle_gauge_config.py | 32 +++++++++++ .../gauge_configs/terrain_levels_config.py | 3 + .../gauge/gauge_configs/wave_gauge_config.py | 2 +- robogauge/tasks/pipeline/level_pipeline.py | 14 ++++- robogauge/tasks/pipeline/multi_pipeline.py | 11 +++- robogauge/tasks/pipeline/stress_pipeline.py | 24 +++++++- robogauge/utils/file_utils.py | 57 +++++++++++++++++++ robogauge/utils/helpers.py | 3 + robogauge/utils/logger.py | 10 +++- robogauge/utils/task_register.py | 15 ++++- setup.py | 14 ++--- 28 files changed, 452 insertions(+), 22 deletions(-) create mode 100644 resources/terrains/obstacle/obstacle_1.xml create mode 100644 resources/terrains/obstacle/obstacle_10.xml create mode 100644 resources/terrains/obstacle/obstacle_2.xml create mode 100644 resources/terrains/obstacle/obstacle_3.xml create mode 100644 resources/terrains/obstacle/obstacle_4.xml create mode 100644 resources/terrains/obstacle/obstacle_5.xml create mode 100644 resources/terrains/obstacle/obstacle_6.xml create mode 100644 resources/terrains/obstacle/obstacle_7.xml create mode 100644 resources/terrains/obstacle/obstacle_8.xml create mode 100644 resources/terrains/obstacle/obstacle_9.xml create mode 100644 robogauge/tasks/custom/go2/go2_obstacle_task.py create mode 100644 robogauge/tasks/gauge/gauge_configs/obstacle_gauge_config.py create mode 100644 robogauge/utils/file_utils.py diff --git a/CMD.md b/CMD.md index d5b0732..89d62fe 100644 --- a/CMD.md +++ b/CMD.md @@ -15,6 +15,20 @@ python robogauge/scripts/run.py \ --num-processes 5 \ --seeds 0 1 2 3 4 \ --frictions 0.5 1.0 1.5 2.0 2.5 \ + --compress-logs \ + --headless + +# Terrain with Level, need specify goals (default is target_pos) +python robogauge/scripts/run.py \ + --task go2_moe.slope \ + --experiment-name debug \ + --multi \ + --num-processes 5 \ + --seeds 0 1 2 3 4 \ + --frictions 1.0 \ + --level 3 \ + --goals max_velocity diagonal_velocity\ + --compress-logs \ --headless ``` @@ -25,8 +39,9 @@ python robogauge/scripts/run.py \ --task go2_moe.slope \ --experiment-name debug \ --search-max-level \ - --seeds 0 1 2 \ + --seeds 0 1 2 3 4 \ --frictions 1.0 \ + --compress-logs \ --headless ``` @@ -39,8 +54,9 @@ python robogauge/scripts/run.py \ --stress-terrain-names flat slope stairs_up stairs_down wave \ --stress-num-processes 2 \ --num-processes 3 \ - --seeds 0 1 2 \ + --seeds 0 1 2 3 4 \ --frictions 0.5 1.0 1.5 2.0 2.5 \ + --compress-logs \ --headless ``` diff --git a/README.md b/README.md index 757b820..a491ef4 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,14 @@ | - | - | - | | Unitree go2 | 四足 | ✅ | +## 安装 +参考[PyTorch官网](https://pytorch.org/get-started/locally/)安装2.0以上任意版本, 安装本仓库 + +```bash +git clone https://github.com/wty-yy/RoboGauge.git +pip install -e . +``` + ## 目录结构 - `robogauge/scripts`: 启动指标测试程序 - `robogauge/tasks`: 定义测试任务 diff --git a/UPDATE.md b/UPDATE.md index 41c64e1..37fcc83 100644 --- a/UPDATE.md +++ b/UPDATE.md @@ -1,4 +1,12 @@ # UPDATE +## 20251227 +### v0.1.17 +1. args中添加`goals`配置, 在LevelPipeline和MultiPipeline中指定goals, 修正压力测试中评估的目标不对的问题 +2. 评估必须使用5个种子, 否则LevelPipeline中的80%通过率就没有意义了, 并且提高5个种子最终的评级影响可能非常大 (moe.stairs_up Friction 2.0从Lv 7->10) +3. 在StressPipeline测试中, 不再保存 +4. 添加obstacles地形 +5. 优化分数计算方法, 按照`(level-1)*0.1+metric_score`计算 +6. 加入日志文件夹递归保存, StressPipeline -> LevelPipeline -> MultiPipeline, 加入压缩参数`--compress-logs`, 将日志文件夹压缩为`subtasks.tar.xz`, 日志文件压缩`4M->100kb` ## 20251226 ### v0.1.16 1. 基本完成StressPipeline, 加入绘制进度条的线程, 其他进程通过Queue更新主进程的进度条 diff --git a/resources/terrains/obstacle/obstacle_1.xml b/resources/terrains/obstacle/obstacle_1.xml new file mode 100644 index 0000000..264e3e5 --- /dev/null +++ b/resources/terrains/obstacle/obstacle_1.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/terrains/obstacle/obstacle_10.xml b/resources/terrains/obstacle/obstacle_10.xml new file mode 100644 index 0000000..ac0bbd6 --- /dev/null +++ b/resources/terrains/obstacle/obstacle_10.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/terrains/obstacle/obstacle_2.xml b/resources/terrains/obstacle/obstacle_2.xml new file mode 100644 index 0000000..5f29b65 --- /dev/null +++ b/resources/terrains/obstacle/obstacle_2.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/terrains/obstacle/obstacle_3.xml b/resources/terrains/obstacle/obstacle_3.xml new file mode 100644 index 0000000..37e649b --- /dev/null +++ b/resources/terrains/obstacle/obstacle_3.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/terrains/obstacle/obstacle_4.xml b/resources/terrains/obstacle/obstacle_4.xml new file mode 100644 index 0000000..670fdfd --- /dev/null +++ b/resources/terrains/obstacle/obstacle_4.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/terrains/obstacle/obstacle_5.xml b/resources/terrains/obstacle/obstacle_5.xml new file mode 100644 index 0000000..e9a0977 --- /dev/null +++ b/resources/terrains/obstacle/obstacle_5.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/terrains/obstacle/obstacle_6.xml b/resources/terrains/obstacle/obstacle_6.xml new file mode 100644 index 0000000..17ab25f --- /dev/null +++ b/resources/terrains/obstacle/obstacle_6.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/terrains/obstacle/obstacle_7.xml b/resources/terrains/obstacle/obstacle_7.xml new file mode 100644 index 0000000..94fba34 --- /dev/null +++ b/resources/terrains/obstacle/obstacle_7.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/terrains/obstacle/obstacle_8.xml b/resources/terrains/obstacle/obstacle_8.xml new file mode 100644 index 0000000..c3df2ff --- /dev/null +++ b/resources/terrains/obstacle/obstacle_8.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/terrains/obstacle/obstacle_9.xml b/resources/terrains/obstacle/obstacle_9.xml new file mode 100644 index 0000000..d51cc22 --- /dev/null +++ b/resources/terrains/obstacle/obstacle_9.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/robogauge/tasks/__init__.py b/robogauge/tasks/__init__.py index 5a2c480..d2f7242 100644 --- a/robogauge/tasks/__init__.py +++ b/robogauge/tasks/__init__.py @@ -15,6 +15,7 @@ task_register.register('go2.slope', BasePipeline, MujocoConfig, Go2SlopeGaugeCon task_register.register('go2.wave', BasePipeline, MujocoConfig, Go2WaveGaugeConfig, Go2TerrainConfig) task_register.register('go2.stairs_up', BasePipeline, MujocoConfig, Go2StairsUpGaugeConfig, Go2TerrainConfig) task_register.register('go2.stairs_down', BasePipeline, MujocoConfig, Go2StairsDownGaugeConfig, Go2TerrainConfig) +task_register.register('go2.obstacle', BasePipeline, MujocoConfig, Go2ObstacleGaugeConfig, Go2TerrainConfig) # Go2 MoE task_register.register('go2_moe.flat', BasePipeline, MujocoConfig, Go2FlatGaugeConfig, Go2MoEConfig) @@ -22,3 +23,4 @@ task_register.register('go2_moe.slope', BasePipeline, MujocoConfig, Go2SlopeGaug task_register.register('go2_moe.wave', BasePipeline, MujocoConfig, Go2WaveGaugeConfig, Go2MoETerrainConfig) task_register.register('go2_moe.stairs_up', BasePipeline, MujocoConfig, Go2StairsUpGaugeConfig, Go2MoETerrainConfig) task_register.register('go2_moe.stairs_down', BasePipeline, MujocoConfig, Go2StairsDownGaugeConfig, Go2MoETerrainConfig) +task_register.register('go2_moe.obstacle', BasePipeline, MujocoConfig, Go2ObstacleGaugeConfig, Go2MoETerrainConfig) diff --git a/robogauge/tasks/custom/go2/__init__.py b/robogauge/tasks/custom/go2/__init__.py index b1b0b6b..8cd836d 100644 --- a/robogauge/tasks/custom/go2/__init__.py +++ b/robogauge/tasks/custom/go2/__init__.py @@ -11,3 +11,4 @@ from .go2_slope_task import Go2SlopeGaugeConfig from .go2_wave_task import Go2WaveGaugeConfig from .go2_stairs_up_task import Go2StairsUpGaugeConfig from .go2_stairs_down_task import Go2StairsDownGaugeConfig +from .go2_obstacle_task import Go2ObstacleGaugeConfig diff --git a/robogauge/tasks/custom/go2/go2_obstacle_task.py b/robogauge/tasks/custom/go2/go2_obstacle_task.py new file mode 100644 index 0000000..638a3a8 --- /dev/null +++ b/robogauge/tasks/custom/go2/go2_obstacle_task.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +''' +@File : go2_obstacle_task.py +@Time : 2025/12/27 14:32:22 +@Author : wty-yy +@Version : 1.0 +@Blog : https://wty-yy.github.io/ +@Desc : Go2 Obstacle Task Configuration +''' +from robogauge.tasks.robots import Go2Config, Go2MoEConfig +from robogauge.tasks.gauge import ObstacleGaugeConfig +from robogauge.tasks.simulator.mujoco_config import MujocoConfig + +class Go2ObstacleGaugeConfig(ObstacleGaugeConfig): + class metrics(ObstacleGaugeConfig.metrics): + class dof_limits(ObstacleGaugeConfig.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 e0fcddb..4fb00cd 100644 --- a/robogauge/tasks/gauge/__init__.py +++ b/robogauge/tasks/gauge/__init__.py @@ -5,3 +5,4 @@ from .gauge_configs.slope_gauge_config import SlopeGaugeConfig from .gauge_configs.wave_gauge_config import WaveGaugeConfig from .gauge_configs.stairs_up_gauge_config import StairsUpGaugeConfig from .gauge_configs.stairs_down_gauge_config import StairsDownGaugeConfig +from .gauge_configs.obstacle_gauge_config import ObstacleGaugeConfig diff --git a/robogauge/tasks/gauge/gauge_configs/obstacle_gauge_config.py b/robogauge/tasks/gauge/gauge_configs/obstacle_gauge_config.py new file mode 100644 index 0000000..1213d48 --- /dev/null +++ b/robogauge/tasks/gauge/gauge_configs/obstacle_gauge_config.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +''' +@File : obstacle_gauge_config.py +@Time : 2025/12/27 14:33:14 +@Author : wty-yy +@Version : 1.0 +@Blog : https://wty-yy.github.io/ +@Desc : Obstacle Gauge Configuration +''' +from robogauge.tasks.gauge.base_gauge_config import BaseGaugeConfig + +class ObstacleGaugeConfig(BaseGaugeConfig): + gauge_class = 'BaseGauge' + + class assets(BaseGaugeConfig.assets): + terrain_name = "obstacle" + terrain_level = 10 # 1-10 + terrain_xmls = [ + '{ROBOGAUGE_ROOT_DIR}/resources/terrains/obstacle/obstacle_10.xml', + '{ROBOGAUGE_ROOT_DIR}/resources/terrains/wall/10x10_wall.xml', + ] + terrain_spawn_pos = [0.5, 2.0, 0.0] # x y z [m], robot freejoint spawn position on the terrain + + class goals(BaseGaugeConfig.goals): + class target_pos_velocity: # goal to reach a target position by velocity command + enabled = True + target_pos = [6.5, -2.0, 0.3] # x y z [m], target position in the environment, used for target position goal + lin_vel_x = 0.8 # +/- m/s + lin_vel_y = 1.0 # +/- m/s + ang_vel_yaw = 1.5 # +/- rad/s + max_cmd_duration = 30.0 # [s] maximum duration to reach the target position + reach_threshold = 0.1 diff --git a/robogauge/tasks/gauge/gauge_configs/terrain_levels_config.py b/robogauge/tasks/gauge/gauge_configs/terrain_levels_config.py index bc4219f..82a10d8 100644 --- a/robogauge/tasks/gauge/gauge_configs/terrain_levels_config.py +++ b/robogauge/tasks/gauge/gauge_configs/terrain_levels_config.py @@ -87,6 +87,9 @@ class TerrainSearchLevelsConfig(Config): [1.1, 3.0, 7], ] + class obstacle: + levels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + class TerrainEvalLevelsConfig(Config): class slope: levels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] diff --git a/robogauge/tasks/gauge/gauge_configs/wave_gauge_config.py b/robogauge/tasks/gauge/gauge_configs/wave_gauge_config.py index 98854eb..2880ff7 100644 --- a/robogauge/tasks/gauge/gauge_configs/wave_gauge_config.py +++ b/robogauge/tasks/gauge/gauge_configs/wave_gauge_config.py @@ -21,7 +21,7 @@ class WaveGaugeConfig(BaseGaugeConfig): ] terrain_spawn_pos = [1.5, 1.25, 0.0] # x y z [m], robot freejoint spawn position on the terrain - class goals: + class goals(BaseGaugeConfig.goals): class target_pos_velocity: # goal to reach a target position by velocity command enabled = True target_pos = [6.5, -2.65, 0.8] # x y z [m], target position in the environment, used for target position goal diff --git a/robogauge/tasks/pipeline/level_pipeline.py b/robogauge/tasks/pipeline/level_pipeline.py index 874c6e6..e3f8867 100644 --- a/robogauge/tasks/pipeline/level_pipeline.py +++ b/robogauge/tasks/pipeline/level_pipeline.py @@ -12,6 +12,7 @@ import yaml from robogauge.tasks.pipeline.multi_pipeline import MultiPipeline from robogauge.utils.logger import Logger from robogauge.utils.progress_monitor import report_progress, ProgressTypes, ProgressData +from robogauge.utils.file_utils import compress_directory level_logger = Logger() # LevelPipeline logger @@ -21,7 +22,11 @@ class LevelPipeline: self.seeds = args.seeds self.console_output = console_output self.progress_data = progress_data - level_logger.create(args.experiment_name+'_level', args.run_name, console_output=console_output) + parent_log_dir = getattr(args, 'parent_log_dir', None) + level_logger.create(args.experiment_name+'_level', args.run_name, console_output=console_output, parent_log_dir=parent_log_dir) + self.args.parent_log_dir = str(level_logger.log_dir / "subtasks") + self.compress_logs = args.compress_logs + args.compress_logs = False # Disable child log compression def run(self): level_logger.info(f"🚀 Starting Level Searcher for '{self.args.experiment_name}'.") @@ -53,6 +58,9 @@ class LevelPipeline: level_logger.info(f"❌ No valid level found [1-10].") with open(level_logger.log_dir / "level_search_results.yaml", 'w') as f: yaml.dump(level_results, f, allow_unicode=True, sort_keys=False) + level_logger.logger.info(f"📂 Level search results saved to: {level_logger.log_dir / 'level_search_results.yaml'}") + if self.compress_logs: + compress_directory(level_logger.log_dir / "subtasks", delete_original=True, logger=level_logger) return level, level_results def test_level(self, level: int): @@ -63,7 +71,7 @@ class LevelPipeline: success_mean = float(aggregated_results['summary']['success']['mean'].split(' ')[0]) all_success = success_mean >= 0.8 if all_success: - level_logger.info(f"✅ Level {level} passed all tests.") + level_logger.info(f"✅ Level {level} passed all tests, success mean: {success_mean}.") else: - level_logger.info(f"❌ Level {level} failed some tests.") + level_logger.info(f"❌ Level {level} failed some tests, success mean: {success_mean}.") return all_success, aggregated_results diff --git a/robogauge/tasks/pipeline/multi_pipeline.py b/robogauge/tasks/pipeline/multi_pipeline.py index 93cad20..6247b4d 100644 --- a/robogauge/tasks/pipeline/multi_pipeline.py +++ b/robogauge/tasks/pipeline/multi_pipeline.py @@ -24,6 +24,7 @@ from robogauge.utils.task_register import task_register from robogauge.utils.logger import Logger from robogauge.utils.process_utils import NoDaemonPool from robogauge.utils.progress_monitor import report_progress, ProgressTypes, ProgressData +from robogauge.utils.file_utils import compress_directory multi_logger = Logger() # MultiPipeline logger @@ -38,7 +39,8 @@ def run_single_process(args, data): logger.create( experiment_name=local_args.experiment_name, run_name=run_name, - console_output=False + console_output=False, + parent_log_dir=args.parent_log_dir ) pipeline = task_register.make_pipeline(args=local_args, create_logger=False) results, warning, error = pipeline.run() @@ -73,7 +75,10 @@ class MultiPipeline: self.progress_data = progress_data self.num_processes = args.num_processes self.static_info = {} - multi_logger.create(args.experiment_name+'_multi', args.run_name+'_multi', console_output=console_output) + parent_log_dir = getattr(args, 'parent_log_dir', None) + multi_logger.create(args.experiment_name+'_multi', args.run_name+'_multi', console_output=console_output, parent_log_dir=parent_log_dir) + self.args.parent_log_dir = str(multi_logger.log_dir / "subtasks") + self.compress_logs = args.compress_logs def add_static_info(self, key: str, value): if key not in self.static_info: @@ -153,6 +158,8 @@ class MultiPipeline: multi_logger.info("✅ Aggregated execution finished.") multi_logger.info(f"📁 Aggregated results saved to: {save_path}") + if self.compress_logs: + compress_directory(multi_logger.log_dir / "subtasks", delete_original=True, logger=multi_logger) # multi_logger.info( # f"""\n{'='*20} Multi-Run Summary {'='*20}\n""" # f"""{yaml.dump(summary, allow_unicode=True)}""" diff --git a/robogauge/tasks/pipeline/stress_pipeline.py b/robogauge/tasks/pipeline/stress_pipeline.py index a52ee0c..c9b441d 100644 --- a/robogauge/tasks/pipeline/stress_pipeline.py +++ b/robogauge/tasks/pipeline/stress_pipeline.py @@ -7,6 +7,12 @@ @Blog : https://wty-yy.github.io/ @Desc : Stress Pipeline for Robogauge ''' +# MuJoCo/XLA warnings suppression +import os +os.environ['ABSL_LOG_LEVEL'] = 'error' +import absl.logging +absl.logging.set_verbosity(absl.logging.ERROR) + import yaml import traceback import functools @@ -22,9 +28,15 @@ from robogauge.utils.process_utils import NoDaemonPool from robogauge.utils.progress_monitor import report_progress, ProgressTypes, start_progress_monitor_thread, ProgressData from robogauge.tasks.pipeline import MultiPipeline, LevelPipeline from robogauge.tasks.gauge.gauge_configs.terrain_levels_config import TerrainSearchLevelsConfig +from robogauge.utils.file_utils import compress_directory stress_logger = Logger() # StressPipeline logger +GOALS = { + 'level_pipeline': ['target_pos_velocity'], + 'multi_pipeline': ['max_velocity', 'diagonal_velocity'] +} + def run_pipeline(args, progress_queue, data): args = deepcopy(args) task_id = data['task_id'] @@ -44,6 +56,7 @@ def run_pipeline(args, progress_queue, data): args.base_masses = [data['base_mass']] args.task_name = f"{data['task_robot_model']}.{data['terrain_name']}" args.experiment_name = f"{args.experiment_name}_{data['terrain_name']}_M{data['base_mass']}_F{data['friction']}" + args.goals = GOALS['level_pipeline'] level, level_results = LevelPipeline(args, console_output=False, progress_data=progress_data).run() if level == 0: # no valid level found @@ -63,6 +76,7 @@ def run_pipeline(args, progress_queue, data): args.experiment_name = f"{args.experiment_name}_{data['terrain_name']}" args.level = level + args.goals = GOALS['multi_pipeline'] results = { 'success': True, 'results': MultiPipeline(args, console_output=False, progress_data=progress_data).run(), @@ -81,6 +95,9 @@ class StressPipeline: args.experiment_name = self.task_robot_model + '_stress' + ('' if args.cli_experiment_name is None else '_' + args.cli_experiment_name) self.static_info = {} stress_logger.create(args.experiment_name, args.run_name) + self.args.parent_log_dir = str(stress_logger.log_dir / "subtasks") + self.compress_logs = args.compress_logs + args.compress_logs = False # Disable child log compression def add_static_info(self, key: str, value): if key not in self.static_info: @@ -183,7 +200,9 @@ class StressPipeline: for metric, means in result['results']['summary'].items(): for mean_name, mean_value in means.items(): - value_collections[metric][mean_name].append(float(mean_value.split(' ')[0])) + value = float(mean_value.split(' ')[0]) + value += (terrain_level - 1) * 0.1 if terrain_level is not None else 0.0 + value_collections[metric][mean_name].append(value) for metric, means in value_collections.items(): summary['summary'][metric] = {} @@ -196,4 +215,7 @@ class StressPipeline: yaml.dump(summary, file, allow_unicode=True, sort_keys=False) stress_logger.info(f"✅ Stress benchmark aggregated execution finished.") stress_logger.info(f"📁 Stress benchmark results saved to: {save_path}") + + if self.compress_logs: + compress_directory(stress_logger.log_dir / "subtasks", delete_original=True, logger=stress_logger) return summary diff --git a/robogauge/utils/file_utils.py b/robogauge/utils/file_utils.py new file mode 100644 index 0000000..be4fa18 --- /dev/null +++ b/robogauge/utils/file_utils.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +''' +@File : file_utils.py +@Time : 2025/12/27 17:14:29 +@Author : wty-yy +@Version : 1.0 +@Blog : https://wty-yy.github.io/ +@Desc : Common File Utilities +''' +import tarfile +import shutil +import os +from pathlib import Path +import logging + +def log_msg(logger: logging.Logger, msg: str, level: str): + if logger: + getattr(logger, level)(msg) + else: + print(msg) + +def compress_directory(source_dir, output_filename=None, delete_original=True, logger: logging.Logger=None): + """ + Compress a directory into a .tar.xz file using LZMA compression. + + Args: + source_dir (str | Path): Path to the directory to compress. + output_filename (str | Path, optional): Output path. Defaults to source_dir + ".tar.xz". + delete_original (bool): Whether to delete the source directory after successful compression. + """ + source_dir = Path(source_dir) + if not source_dir.exists(): + log_msg(logger, f"⚠️ Source directory for compression not found: {source_dir}", "warning") + return + + if output_filename is None: + output_filename = source_dir.with_suffix('.tar.xz') + else: + output_filename = Path(output_filename) + + log_msg(logger, f"📦 Compressing logs: {source_dir.name} -> {output_filename.name} ...", "info") + + try: + with tarfile.open(output_filename, "w:xz") as tar: + tar.add(source_dir, arcname=source_dir.name) + + log_msg(logger, f"✅ Compression finished: {output_filename}", "info") + + if delete_original: + shutil.rmtree(source_dir) + log_msg(logger, f"🗑️ Deleted original directory: {source_dir}", "info") + + except Exception as e: + log_msg(logger, f"❌ Failed to compress directory {source_dir}: {e}", "error") + # If compression failed, ensure we don't leave a half-baked file + if output_filename.exists(): + os.remove(output_filename) diff --git a/robogauge/utils/helpers.py b/robogauge/utils/helpers.py index 93fa544..bfb2ca8 100644 --- a/robogauge/utils/helpers.py +++ b/robogauge/utils/helpers.py @@ -76,6 +76,7 @@ def parse_args(): {"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."}, {"name": "--level", "type": int, "help": "Set the difficulty level of the environment, range 1-10 (flat is 0)."}, + {"name": "--goals", "type": str, "nargs": "+", "help": "List of goal names to evaluate."}, # Multiprocessing parameters, with different seeds {"name": "--multi", "action": "store_true", "default": False, "help": "Enable multiprocessing."}, @@ -91,6 +92,8 @@ def parse_args(): {"name": "--stress-benchmark", "action": "store_true", "default": False, "help": "Use stress pipeline to benchmark model robustness."}, {"name": "--stress-terrain-names", "type": str, "nargs": "+", "default": ["flat", "slope", "wave", "stairs_up", "stairs_down"], "help": "List of terrain names for stress benchmark."}, {"name": "--stress-num-processes", "type": int, "default": 2, "help": "Number of parallel processes for stress benchmark."}, + + {"name": "--compress-logs", "action": "store_true", "default": False, "help": "Compress and delete logs after run."}, ] for param in parameters: parser.add_argument(param['name'], **{k: v for k, v in param.items() if k != 'name'}) diff --git a/robogauge/utils/logger.py b/robogauge/utils/logger.py index 74f72bd..12d7682 100644 --- a/robogauge/utils/logger.py +++ b/robogauge/utils/logger.py @@ -58,7 +58,8 @@ class Logger: experiment_name, run_name, console_output=True, color_output=True, - log_level=logging.DEBUG, save_file_mode='a' + log_level=logging.DEBUG, save_file_mode='a', + parent_log_dir=None ): """ Create customed Logger @@ -69,6 +70,7 @@ class Logger: color_output (bool, optional): Whether use color output. Defaults to True. log_level (int, optional): Defaults to logging.DEBUG. save_file_mode (str, optional): The mode of saving to path_log_file + parent_log_dir (Path | str, optional): If specified, log_dir will be created under this directory. Returns: logging.Logger: logger @@ -102,7 +104,11 @@ class Logger: sh.setFormatter(console_formatter) self.logger.addHandler(sh) - self.log_dir = Path(ROBOGAUGE_LOGS_DIR) / experiment_name / self.tag + if parent_log_dir: + baes_dir = Path(parent_log_dir) + else: + baes_dir = Path(ROBOGAUGE_LOGS_DIR) + self.log_dir = baes_dir / experiment_name / self.tag self.log_dir.mkdir(parents=True, exist_ok=True) path_log_file = self.log_dir / "stdout.log" if path_log_file: diff --git a/robogauge/utils/task_register.py b/robogauge/utils/task_register.py index f898dd4..eb324cd 100644 --- a/robogauge/utils/task_register.py +++ b/robogauge/utils/task_register.py @@ -9,7 +9,7 @@ ''' from robogauge import ROBOGAUGE_ROOT_DIR from robogauge.utils.logger import logger -from robogauge.utils.helpers import parse_args, set_seed +from robogauge.utils.helpers import parse_args, set_seed, class_to_dict from robogauge.tasks.gauge.gauge_configs.terrain_levels_config import TerrainSearchLevelsConfig class TaskRegister(): @@ -73,14 +73,23 @@ class TaskRegister(): if args.level is not None: gauger_cfg.assets.terrain_level = args.level levels_cfg = TerrainSearchLevelsConfig() - cfg = getattr(levels_cfg, gauger_cfg.assets.terrain_name) + cfg = getattr(levels_cfg, gauger_cfg.assets.terrain_name, None) assert cfg is not None, f"Level {args.level} configuration not found in TerrainLevelsConfig." assert args.level in cfg.levels, f"Level must be in {cfg.levels}." - gauger_cfg.goals.target_pos_velocity.target_pos = cfg.targets[cfg.levels.index(args.level)] + if hasattr(cfg, 'targets'): + gauger_cfg.goals.target_pos_velocity.target_pos = cfg.targets[cfg.levels.index(args.level)] if hasattr(cfg, 'spawns'): gauger_cfg.assets.terrain_spawn_pos = cfg.spawns[cfg.levels.index(args.level)] xml = gauger_cfg.assets.terrain_xmls[0] xml = xml.rsplit('/', 1)[0] + f"/{gauger_cfg.assets.terrain_name}_{args.level}.xml" gauger_cfg.assets.terrain_xmls[0] = xml + if args.goals is not None: + keys = class_to_dict(gauger_cfg.goals).keys() + enable_count = 0 + for key in keys: + flag = key in args.goals + getattr(gauger_cfg.goals, key).enabled = flag + enable_count += int(flag) + assert enable_count > 0, f"At least one goal must be enabled from '{args.goals}', available goals are {list(keys)}." task_register = TaskRegister() diff --git a/setup.py b/setup.py index 4cf7fa8..c2e3170 100644 --- a/setup.py +++ b/setup.py @@ -10,14 +10,12 @@ setup( packages=find_packages(), install_requires=[ "numpy>=1.20.0", - "mujoco>=3.0.0", # 必须依赖 - "dm_control>=1.0.14", # 之前推荐用来组合地形的库 - "scipy", # 计算信号处理、统计指标常用 - "matplotlib", # 用于画出评估图表 - "pandas", # 用于生成评估报告表格 - "tqdm", # 显示评估进度条 - # "gymnasium", # 如果你的接口兼容 gym - # "torch", # 如果你需要加载 pytorch 模型 + "mujoco>=3.0.0", + "dm_control>=1.0.14", + "scipy", + "matplotlib", + "tqdm", + "imageio[ffmpeg]", ], python_requires=">=3.8",