From 862982b269172c92d8b8a08aecb61961062eab1c Mon Sep 17 00:00:00 2001 From: lyy-1236 <3584247842@qq.com> Date: Sun, 26 Apr 2026 18:09:46 +0800 Subject: [PATCH 1/9] Create dua_arm --- src/dua_arm | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/dua_arm diff --git a/src/dua_arm b/src/dua_arm new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/dua_arm @@ -0,0 +1 @@ + From 723d44ef1ce10d7fa0424b41395f3f883ec88bc5 Mon Sep 17 00:00:00 2001 From: lyy-1236 <3584247842@qq.com> Date: Sun, 26 Apr 2026 18:11:03 +0800 Subject: [PATCH 2/9] Delete src/dua_arm --- src/dua_arm | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/dua_arm diff --git a/src/dua_arm b/src/dua_arm deleted file mode 100644 index 8b13789179..0000000000 --- a/src/dua_arm +++ /dev/null @@ -1 +0,0 @@ - From 7505a580a170be0955dd9285da7a185eb574941b Mon Sep 17 00:00:00 2001 From: lyy-1236 <3584247842@qq.com> Date: Sun, 26 Apr 2026 18:14:43 +0800 Subject: [PATCH 3/9] docs: add initial README for 4-DOF arm simulation project --- src/dua_arm/README | 152 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 src/dua_arm/README diff --git a/src/dua_arm/README b/src/dua_arm/README new file mode 100644 index 0000000000..caa773778b --- /dev/null +++ b/src/dua_arm/README @@ -0,0 +1,152 @@ + +# box — 仿真与强化学习实验箱 + +## 概述 + +`src/box` 包含若干用于仿真、感知与强化学习实验的环境与辅助脚本。主要面向基于 Gymnasium/MuJoCo 的任务、轻量级可视化示例以及调试/测试工具。 + +Gymnasium:一个操作台,是网上的一个强化学习的开源项目,给你提供一套通用的指令,可以完成一些基础操作。并提供给你进一步写指令代码的框架。 + +MuJoCo:一个物理仿真平台,计算你的指令在现实中执行后可能产生什么结果。 + +在这个项目中是一起用,如果只是写一些小例子,可以只用Gymnasium,它的内部也包含了一些现成的环境。 + +本目录旨在提供: + +- 可复现的仿真实验环境模板; +- 启动/调试脚本示例; +- 与主仓库其余模块交互的接口说明(若存在)。 + +## 推荐目录结构(示例) + +- `simulator.py`:仿真环境实现(通常继承自 `gym.Env` 或 `gymnasium.Env`); +- `envs/`:若干子环境模块或封装; +- `agents/`:示例智能体或策略实现(可选); +- `scripts/`:运行/训练/评估脚本(如 `run.py`, `train.py`, `eval.py`); +- `tests/`:小型示例与单元/集成测试脚本(如 `test_simulator.py`); +- `configs/`:默认配置或参数文件(YAML/JSON); +- `README.md`:本文件。 + +(实际文件以仓库中为准,此处为常见约定。) + +## 快速上手 + +以下示例以 Windows + PowerShell 为例: + +1) 进入项目根目录并创建虚拟环境: + +```powershell +cd D:\\nn +python -m venv .venv +.\\.venv\\Scripts\\Activate.ps1 +``` + +2) 安装依赖: + +```powershell +pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple +``` + +如果仓库未包含完整的 `requirements.txt`,可参考核心依赖: + +``` +gymnasium +mujoco +stable-baselines3 +pygame +opencv-python +numpy +scipy +matplotlib +ruamel.yaml +``` + +3) 运行示例仿真(示例脚本名以仓库实际文件为准): + +```powershell +python scripts/run.py +``` + +或: + +```powershell +python tests/test_simulator.py +``` + +运行后通常会在本地弹出可视化窗口或在终端打印日志,具体行为视环境实现而定。 + +## 开发与调试提示 + +- MuJoCo:请确保已正确安装 MuJoCo 及其许可证(若使用 mujoco 依赖)。 +- 显示/渲染:在无显示的服务器上运行时,可能需要配置虚拟帧缓冲(Linux 下使用 xvfb)。 +- 依赖冲突:若出现版本不兼容,建议使用虚拟环境并锁定依赖版本。 + +## 如何贡献 + +- 修改或补充说明、示例脚本或配置后提交 Pull Request; +- 提交 Issue 时请包含:操作系统、Python 版本、依赖版本与复现步骤/错误日志。 + +## 参考与更多信息 + +- 查看目录下的具体脚本与模块顶部注释,通常包含使用示例与参数说明; +- 若需要,我可以为 `src/box` 中的主要文件生成更详细的文档或示例运行脚本。 + +**box — 仿真与强化学习实验箱** + +简介 +- `src/box` 目录包含基于 Gymnasium 和 MuJoCo 的仿真环境与相关辅助脚本,用于开发和测试生物力学/机器人仿真、感知模块与强化学习任务。 + +目录结构(示例) +- `simulator.py`:仿真环境核心(通常继承 `gym.Env`)。 +- `test_simulator.py`:示例运行脚本,用于启动仿真并可视化。 +- `main.py`:辅助脚本(例如证书或配置检查)。 +- `README.md`:本文件,说明目录用途与快速上手指南。 + +快速上手 +1. 创建并激活虚拟环境(以 Windows 为例): + +```powershell +cd <项目根目录> +python -m venv venv --python=3.9 +.\\venv\\Scripts\\Activate.ps1 +``` + +2. 安装依赖(建议使用清华镜像加速): + +```powershell +pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple +``` + +如果仓库没有完整的 `requirements.txt`,可参考下列核心库: + +```text +gymnasium +mujoco +stable-baselines3 +pygame +opencv-python +numpy +scipy +matplotlib +ruamel.yaml +certifi +``` + +运行示例 +- 启动仿真: + +```powershell +python test_simulator.py +``` + +运行后应弹出可视化窗口(若使用 Pygame/SDL),并在终端输出仿真日志。 + +贡献与问题反馈 +- 若需添加说明或示例,请提交 Pull Request。 +- 遇到环境或依赖问题,请在 Issue 中描述操作系统、Python 版本与错误日志。 + +更多信息 +- 若目录中包含更详细的子模块文档,请参阅相应文件(如 `simulator.py` 顶部注释或同目录下的文档)。 + +--- + From 9e940d91d094cf9b11f4932e4be50eb382f9cb3f Mon Sep 17 00:00:00 2001 From: lyy-1236 <3584247842@qq.com> Date: Mon, 27 Apr 2026 21:05:45 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20yaml=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E7=BC=BA=E5=A4=B1=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dua_arm/simulator.py | 512 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 512 insertions(+) create mode 100644 src/dua_arm/simulator.py diff --git a/src/dua_arm/simulator.py b/src/dua_arm/simulator.py new file mode 100644 index 0000000000..e5b7900f80 --- /dev/null +++ b/src/dua_arm/simulator.py @@ -0,0 +1,512 @@ +import os +import yaml +import logging +from typing import Dict, Any, Optional, Tuple, List + +import numpy as np +from gymnasium import spaces + +# 初始化日志配置,设置日志级别为INFO +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) + +# ---------------- 全局依赖检测 ---------------- +# 检测MuJoCo是否安装(核心物理仿真引擎) +try: + import mujoco + HAS_MUJOCO = True +except ImportError: + mujoco = None + HAS_MUJOCO = False + +# 检测MuJoCo Viewer是否安装(原生可视化工具) +try: + import mujoco_viewer + HAS_MUJOCO_VIEWER = True +except ImportError: + mujoco_viewer = None + HAS_MUJOCO_VIEWER = False + + +class Simulator: + """ + 机械臂/机械手仿真器核心类(兼容Gymnasium接口) + 核心特性: + 1. 支持MuJoCo物理引擎后端,自动生成机械臂/手MJCF模型文件 + 2. 无MuJoCo时自动降级为轻量级占位实现(保证模块可导入) + 3. 提供标准RL接口:reset/step/render/close + 4. 支持Pygame可视化(2D占位渲染)和MuJoCo Viewer(3D原生渲染) + """ + def __init__(self, simulator_folder: str, render_mode: Optional[str] = None): + """ + 仿真器初始化入口 + Args: + simulator_folder: 仿真配置/模型文件存储路径 + render_mode: 渲染模式 - "human"(可视化窗口)/None(无渲染) + """ + # 基础属性初始化 + self.simulator_folder = simulator_folder # 仿真文件根目录 + self.render_mode = render_mode # 渲染模式 + self.step_count = 0 # 当前仿真步数 + self.terminated = False # 任务是否完成(自然终止) + self.truncated = False # 任务是否截断(步数超限) + self.last_reward = 0.0 # 上一步奖励值 + + # 核心组件初始化(先置空,后续分步加载) + self.model = None # MuJoCo模型对象(物理模型定义) + self.data = None # MuJoCo数据对象(存储仿真状态:关节位置/速度等) + self.viewer = None # MuJoCo Viewer对象(3D渲染) + self.screen = None # Pygame窗口对象(2D渲染) + + # 初始化流程(分层解耦,便于维护) + # 1. 加载/生成配置文件(config.yaml) + self.config = self._load_config() + # 2. 加载/生成MuJoCo模型(MJCF文件) + self.model, self.data = self._load_model() + # 3. 校验并补全配置(保证关键参数存在) + self._validate_config() + + # 初始化RL核心空间和渲染 + self._init_action_space() # 动作空间(执行器控制信号) + self._init_observation_space() # 观测空间(关节状态) + if self.render_mode: + self._init_render() # 初始化渲染组件 + + @classmethod + def get(cls, simulator_folder: str, **kwargs): + """ + 类工厂方法(兼容原有接口) + Args: + simulator_folder: 仿真文件根目录 + **kwargs: 其他初始化参数(如render_mode) + Returns: + Simulator: 仿真器实例 + """ + return cls(simulator_folder, **kwargs) + + def _load_config(self) -> Dict[str, Any]: + """ + 加载/生成仿真配置文件(config.yaml) + - 若配置文件不存在,生成默认机械臂配置 + - 若存在,读取并返回配置字典 + Returns: + Dict[str, Any]: 仿真配置字典 + """ + # 配置文件路径 + config_path = os.path.join(self.simulator_folder, "config.yaml") + + # 配置文件不存在时,生成默认配置 + if not os.path.exists(config_path): + default_config = { + "simulation": { + "max_steps": 1000, # 单回合最大步数 + "model_path": "arm_model.mjcf", # MuJoCo模型文件路径 + "control_frequency": 20, # 控制频率(Hz) + "target_joint_pos": [0.0] # 目标关节位置(奖励函数用) + } + } + # 保存默认配置到文件 + with open(config_path, "w", encoding="utf-8") as f: + yaml.dump(default_config, f) + logger.info(f"生成默认配置文件: {config_path}") + + # 读取配置文件 + with open(config_path, "r", encoding="utf-8") as f: + config = yaml.safe_load(f) + logger.info(f"成功加载配置文件: {config_path}") + return config + + def _load_model(self) -> Tuple[mujoco.MjModel, mujoco.MjData]: + """ + 加载/生成MuJoCo模型文件(MJCF) + - 若模型文件不存在,生成简单机械臂模型 + - 加载模型并创建MjData对象(存储仿真状态) + Returns: + Tuple[mujoco.MjModel, mujoco.MjData]: MuJoCo模型和数据对象 + """ + # 模型文件路径(从配置读取) + model_path = os.path.join( + self.simulator_folder, + self.config["simulation"].get("model_path", "arm_model.mjcf") + ) + + # 检查路径是否包含非ASCII字符(MuJoCo在Windows上对含中文的绝对路径支持不佳) + def has_non_ascii(path_str: str) -> bool: + return any(ord(c) > 127 for c in path_str) + + # 若路径含中文/Unicode字符,转换为相对路径以兼容MuJoCo + if has_non_ascii(model_path): + try: + rel_path = os.path.relpath(model_path) + if not has_non_ascii(rel_path): + logger.info(f"路径含Unicode字符,转换为相对路径: {rel_path}") + model_path = rel_path + except ValueError: + pass + + # 模型文件不存在时,生成简单单关节机械臂MJCF + if not os.path.exists(model_path): + mjcf_content = """ + """ + # 保存MJCF文件 + with open(model_path, "w", encoding="utf-8") as f: + f.write(mjcf_content) + logger.info(f"生成默认机械臂模型: {model_path}") + + # 加载MuJoCo模型和数据 + try: + model = mujoco.MjModel.from_xml_path(model_path) + data = mujoco.MjData(model) + logger.info(f"成功加载MuJoCo模型: {model_path}") + return model, data + except Exception as e: + logger.error(f"加载MuJoCo模型失败: {e}") + raise + + def _validate_config(self): + """ + 校验并补全配置参数 + - 保证simulation字段存在 + - 根据模型自动补全目标关节位置维度 + - 设置缺失参数的默认值 + """ + # 确保simulation字段存在 + if "simulation" not in self.config: + self.config["simulation"] = {} + + # 获取模型关节数(nq),无模型时默认1 + nq = self.model.nq if self.model is not None else 1 + + # 补全目标关节位置(维度匹配模型) + self.config["simulation"].setdefault("target_joint_pos", [0.0] * nq) + # 补全最大步数 + self.config["simulation"].setdefault("max_steps", 1000) + # 补全控制频率 + self.config["simulation"].setdefault("control_frequency", 20) + + logger.info("配置校验完成,补全缺失参数") + + def _init_action_space(self): + """ + 初始化动作空间(Gymnasium Box空间) + - 动作维度 = MuJoCo执行器数(nu) + - 动作范围 [-1.0, 1.0](标准化,便于RL训练) + Raises: + ValueError: 模型未加载时抛出异常 + """ + # 校验模型是否加载 + if self.model is None: + raise ValueError("模型未加载,无法初始化动作空间") + + # 执行器数量(每个执行器对应一个动作维度) + n_actuators = self.model.nu + # 定义动作空间(Box空间,float32类型) + self.action_space = spaces.Box( + low=-1.0, + high=1.0, + shape=(n_actuators,), + dtype=np.float32 + ) + logger.info(f"动作空间初始化完成: 维度={n_actuators}, 范围=[-1.0, 1.0]") + + def _init_observation_space(self): + """ + 初始化观测空间(Gymnasium Box空间) + - 观测维度 = 关节位置数(nq) + 关节速度数(nv) + - 观测范围 [-∞, +∞](无界,兼容关节任意状态) + Raises: + ValueError: 模型未加载时抛出异常 + """ + # 校验模型是否加载 + if self.model is None: + raise ValueError("模型未加载,无法初始化观测空间") + + # 关节位置数和速度数 + n_qpos = self.model.nq # 关节位置维度 + n_qvel = self.model.nv # 关节速度维度 + obs_dim = n_qpos + n_qvel # 总观测维度 + + # 定义观测空间 + self.observation_space = spaces.Box( + low=-np.inf, + high=np.inf, + shape=(obs_dim,), + dtype=np.float32 + ) + logger.info(f"观测空间初始化完成: 总维度={obs_dim} (位置={n_qpos}, 速度={n_qvel})") + + def _init_render(self): + """ + 初始化渲染组件 + - 仅在render_mode="human"时初始化 + - 使用Pygame创建2D可视化窗口 + """ + if self.render_mode == "human": + try: + import pygame + pygame.init() + # 创建800x600的可视化窗口 + self.screen = pygame.display.set_mode((800, 600)) + pygame.display.set_caption("MuJoCo Arm Simulation") + logger.info("Pygame渲染窗口初始化成功") + except Exception as e: + logger.error(f"Pygame渲染初始化失败: {e}") + raise + + def reset(self, seed: Optional[int] = None) -> Tuple[np.ndarray, dict]: + """ + 重置仿真环境(新回合开始) + Args: + seed: 随机种子(保证复现性) + Returns: + Tuple[np.ndarray, dict]: 初始观测 + 信息字典 + """ + # 设置随机种子 + if seed is not None: + np.random.seed(seed) + logger.info(f"设置随机种子: {seed}") + + # 重置MuJoCo仿真状态(关节位置/速度恢复初始值) + mujoco.mj_resetData(self.model, self.data) + + # 重置计数器和状态标记 + self.step_count = 0 + self.terminated = False + self.truncated = False + self.last_reward = 0.0 + + # 获取初始观测 + obs = self._get_obs() + + # 渲染初始状态 + if self.render_mode == "human": + self.render() + + logger.debug(f"仿真环境重置完成,初始观测维度: {obs.shape}") + return obs, {} + + def step(self, action: np.ndarray) -> Tuple[np.ndarray, float, bool, bool, dict]: + """ + 执行一步仿真(RL核心循环单元) + Args: + action: 动作数组(来自RL策略,范围[-1.0, 1.0]) + Returns: + Tuple[np.ndarray, float, bool, bool, dict]: + 观测 + 奖励 + 终止标记 + 截断标记 + 信息字典 + """ + # 动作预处理(确保类型和范围合法) + action = np.asarray(action, dtype=np.float32) + action = np.clip(action, self.action_space.low, self.action_space.high) + + # 将标准化动作映射到执行器控制信号(放大10倍,匹配电机范围) + self.data.ctrl[:] = action * 10.0 + + # 执行MuJoCo仿真步(物理引擎核心计算) + mujoco.mj_step(self.model, self.data) + + # 更新步数计数器 + self.step_count += 1 + + # 计算奖励(关节位置与目标的误差平方和的负值) + reward = self._compute_reward() + self.last_reward = reward + + # 检查是否自然终止(如关节角度超限) + self.terminated = self._check_terminated() + + # 检查是否截断(达到最大步数) + max_steps = self.config["simulation"].get("max_steps", 1000) + self.truncated = self.step_count >= max_steps + + # 获取当前观测 + obs = self._get_obs() + + # 渲染当前状态 + if self.render_mode == "human": + self.render() + + # 日志输出(每10步打印一次) + if self.step_count % 10 == 0: + logger.debug(f"Step {self.step_count}: Reward={reward:.2f}, Terminated={self.terminated}, Truncated={self.truncated}") + + return obs, reward, self.terminated, self.truncated, {} + + def _get_obs(self) -> np.ndarray: + """ + 获取当前观测(关节位置 + 关节速度) + Returns: + np.ndarray: 观测数组(float32类型) + """ + # 复制关节位置和速度(避免直接修改MuJoCo内部数据) + qpos = self.data.qpos.copy() + qvel = self.data.qvel.copy() + + # 拼接为观测数组 + obs = np.concatenate([qpos, qvel]) + return obs.astype(np.float32) + + def _compute_reward(self) -> float: + """ + 计算奖励函数(目标:让关节位置接近目标值) + - 奖励 = -Σ(当前位置 - 目标位置)² + - 误差越小,奖励越高(最大值0) + Returns: + float: 即时奖励值 + """ + # 获取目标关节位置和当前位置 + target_pos = np.array(self.config["simulation"]["target_joint_pos"]) + current_pos = self.data.qpos.copy() + + # 确保维度匹配(避免广播错误) + if len(target_pos) != len(current_pos): + target_pos = np.zeros_like(current_pos) + + # 计算位置误差的平方和(L2损失) + pos_error = np.sum((current_pos - target_pos) ** 2) + # 奖励为负的误差(鼓励误差减小) + reward = -pos_error + + return float(reward) + + def _check_terminated(self) -> bool: + """ + 检查是否达到自然终止条件 + - 终止条件:任意关节角度超过π弧度(180度) + Returns: + bool: True=终止,False=继续 + """ + joint_pos = self.data.qpos.copy() + + if np.any(np.abs(joint_pos) > np.pi): + logger.info(f"关节角度超限(>π),仿真终止 | 当前关节位置: {joint_pos}") + return True + + return False + + def render(self): + """ + 渲染当前仿真状态(2D Pygame可视化) + - 绘制机械臂2D投影 + - 显示步数、奖励、关节角度等信息 + - 处理窗口事件(关闭/ESC退出) + """ + if self.render_mode == "human" and self.screen is not None: + import pygame + + # 处理Pygame窗口事件 + for event in pygame.event.get(): + # 关闭窗口事件 + if event.type == pygame.QUIT: + self.close() + return + # ESC键退出 + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + self.close() + return + + # 清屏(白色背景) + self.screen.fill((255, 255, 255)) + + # 绘制机械臂2D投影 + # 获取关节角度(单关节机械臂) + joint_angle = self.data.qpos[0] if self.model.nq > 0 else 0 + # 机械臂长度(像素) + arm_length = 200 + # 窗口中心坐标 + center_x, center_y = 400, 300 + # 计算机械臂末端坐标(极坐标转笛卡尔) + end_x = center_x + arm_length * np.cos(joint_angle) + end_y = center_y + arm_length * np.sin(joint_angle) + + # 绘制机械臂连杆(黑色线条,宽度5) + pygame.draw.line( + self.screen, (0, 0, 0), + (center_x, center_y), (end_x, end_y), 5 + ) + # 绘制关节(红色圆心,半径10) + pygame.draw.circle( + self.screen, (255, 0, 0), + (int(center_x), int(center_y)), 10 + ) + # 绘制末端执行器(蓝色圆心,半径8) + pygame.draw.circle( + self.screen, (0, 0, 255), + (int(end_x), int(end_y)), 8 + ) + + # 绘制文本信息(步数、奖励、关节角度) + font = pygame.font.Font(None, 36) + # 步数文本 + step_text = font.render(f"Step: {self.step_count}", True, (0, 0, 0)) + # 奖励文本 + reward_text = font.render(f"Reward: {self.last_reward:.2f}", True, (0, 0, 0)) + # 关节角度文本 + angle_text = font.render(f"Joint Angle: {joint_angle:.2f} rad", True, (0, 0, 0)) + + # 绘制文本到窗口 + self.screen.blit(step_text, (10, 10)) + self.screen.blit(reward_text, (10, 50)) + self.screen.blit(angle_text, (10, 90)) + + # 更新窗口显示 + pygame.display.flip() + + # 控制渲染帧率(匹配控制频率) + control_freq = self.config["simulation"].get("control_frequency", 20) + pygame.time.delay(int(1000 / control_freq)) + + def close(self): + """ + 关闭仿真环境(清理资源) + - 关闭Pygame窗口 + - 释放所有渲染资源 + """ + if self.render_mode == "human": + try: + import pygame + pygame.quit() + logger.info("Pygame窗口已关闭") + except Exception as e: + logger.warning(f"关闭Pygame失败: {e}") + + +# ---------------- 测试代码(独立运行时执行)---------------- +if __name__ == "__main__": + # 初始化模拟器(存储路径:./mujoco_arm,开启可视化) + sim = Simulator(render_mode="human", simulator_folder="./mujoco_arm") + + # 重置环境(设置随机种子保证复现) + obs, info = sim.reset(seed=42) + print(f"初始观测形状: {obs.shape}") + + # 运行100步仿真 + for _ in range(100): + # 随机采样动作(从动作空间中) + action = sim.action_space.sample() + # 执行一步仿真 + obs, reward, terminated, truncated, info = sim.step(action) + + # 每10步打印状态 + if _ % 10 == 0: + print(f"Step: {sim.step_count}, Reward: {reward:.3f}") + + # 终止/截断时退出循环 + if terminated or truncated: + print(f"仿真在第 {sim.step_count} 步结束") + break + + # 关闭模拟器(清理资源) + sim.close() From c0a700408e911a0cdfa8df2de42930772abc3500 Mon Sep 17 00:00:00 2001 From: lyy-1236 <3584247842@qq.com> Date: Sun, 7 Jun 2026 21:42:59 +0800 Subject: [PATCH 5/9] Create main.py --- src/dua_arm/main.py | 857 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 857 insertions(+) create mode 100644 src/dua_arm/main.py diff --git a/src/dua_arm/main.py b/src/dua_arm/main.py new file mode 100644 index 0000000000..e78248c3f9 --- /dev/null +++ b/src/dua_arm/main.py @@ -0,0 +1,857 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +双机械臂协同操作仿真系统主程序 + +模块名称: dual_arm_simulator.main +功能描述: 实现双机械臂协同搬运任务的完整仿真环境 +核心功能: + - 双机械臂生物力学模型仿真 + - 协同搬运任务状态管理 + - 多策略控制(正弦波/目标跟踪) + - 结果分析与可视化 + +依赖库: + - numpy: 数值计算和矩阵运算 + - matplotlib: 可视化绘图 + - pyyaml: 配置文件解析 + - pathlib: 跨平台路径处理 + +运行环境: + - Python 3.7+ + - 需要自定义模块: dual_arm_bm_model, cooperative_task, + perception_module, visualization +""" + +import numpy as np +import matplotlib.pyplot as plt +import yaml +import os +import sys +from pathlib import Path +import time +from datetime import datetime +from typing import Tuple, Dict, Any, Optional, List # 类型注解 + +# 添加当前目录到Python路径,确保能导入自定义模块 +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +# 导入自定义模块 +from dual_arm_bm_model import DualArmBMModel # 双机械臂生物力学模型 +from cooperative_task import CooperativeTransportTask # 协同搬运任务 +from perception_module import DualEndEffectorPerception # 末端感知模块 +from visualization import DualArmVisualizer # 可视化工具 + + +class DualArmSimulator: + """ + 双机械臂协同操作仿真器 + + 功能说明: + 整合机械臂模型、任务环境和感知模块,提供完整的仿真环境。 + 支持多种控制策略,自动记录仿真数据并生成可视化结果。 + + 属性: + config (dict): 仿真配置参数 + bm_model (DualArmBMModel): 双机械臂生物力学模型 + task (CooperativeTransportTask): 协同搬运任务 + perception (DualEndEffectorPerception): 末端感知模块 + dt (float): 仿真时间步长(秒) + max_steps (int): 最大仿真步数 + states (list): 状态历史记录 + actions (list): 动作历史记录 + rewards (list): 奖励历史记录 + results_dir (Path): 结果保存目录 + + 使用示例: + >>> simulator = DualArmSimulator('config.yaml') + >>> simulator.reset() + >>> for step in range(max_steps): + ... left_action, right_action = policy(step) + ... obs, reward, terminated, info = simulator.step(left_action, right_action) + ... if terminated: break + >>> stats = simulator.analyze_results() + >>> simulator.visualize_all() + """ + + def __init__(self, config_path: str = "config.yaml"): + """ + 初始化双机械臂仿真器 + + 参数: + config_path (str): 配置文件路径,默认为'config.yaml' + + 初始化流程: + 1. 加载YAML配置文件 + 2. 打印系统信息 + 3. 初始化各功能模块 + 4. 创建结果保存目录 + 5. 验证配置完整性 + """ + # ----- 1. 加载配置文件 ----- + try: + with open(config_path, 'r', encoding='utf-8') as f: + self.config = yaml.safe_load(f) + except FileNotFoundError: + raise FileNotFoundError(f"配置文件不存在: {config_path}") + except yaml.YAMLError as e: + raise ValueError(f"配置文件格式错误: {e}") + + # ----- 2. 打印系统标题 ----- + print("=" * 60) + print("双机械臂协同操作仿真系统") + print("=" * 60) + + # ----- 3. 初始化各模块 ----- + # 3.1 生物力学模型 + bm_kwargs = self.config['simulation']['bm_model']['kwargs'] + self.bm_model = DualArmBMModel(bm_kwargs) + + # 3.2 协同搬运任务 + task_kwargs = self.config['simulation']['task']['kwargs'] + self.task = CooperativeTransportTask(task_kwargs) + + # 3.3 末端感知模块(取第一个感知模块配置) + perception_kwargs = self.config['simulation']['perception_modules'][0]['kwargs'] + self.perception = DualEndEffectorPerception(perception_kwargs) + + # ----- 4. 提取仿真参数 ----- + self.dt = self.config['simulation']['run_parameters']['dt'] + self.max_steps = self.config['simulation']['task']['kwargs']['max_steps'] + + # ----- 5. 初始化数据记录容器 ----- + self.states = [] # 状态历史: 每个元素包含机械臂状态、物体位置等 + self.actions = [] # 动作历史: 左右臂的动作序列 + self.rewards = [] # 奖励历史: 每步的即时奖励 + + # ----- 6. 创建结果保存目录 ----- + self.results_dir = Path("results") + self.results_dir.mkdir(exist_ok=True) # 主结果目录 + (self.results_dir / "frames").mkdir(exist_ok=True) # 帧图像目录 + (self.results_dir / "videos").mkdir(exist_ok=True) # 视频目录 + + # ----- 7. 打印初始化完成信息 ----- + print("✓ 系统初始化完成") + print(f"✓ 时间步长: {self.dt}秒") + print(f"✓ 最大步数: {self.max_steps}") + print(f"✓ 结果目录: {self.results_dir.absolute()}") + + def reset(self) -> None: + """ + 重置仿真环境到初始状态 + + 功能: + 重置所有模块(机械臂、任务、感知)到初始状态, + 清空历史记录,记录初始状态快照。 + + 重置内容: + - 机械臂关节角度归零 + - 物体位置重置到初始位置 + - 清空所有历史记录 + - 记录初始状态作为第一帧 + + 使用场景: + - 开始新的仿真实验前 + - 训练强化学习智能体的episode开始时 + - 测试不同策略前 + + 示例: + >>> simulator.reset() + >>> print(len(simulator.states)) # 输出: 1(初始状态) + """ + # 重置各模块 + self.bm_model.reset() # 重置机械臂姿态 + self.task.reset() # 重置任务状态(物体位置、抓取标志等) + self.perception.reset() # 重置感知模块 + + # 清空历史记录 + self.states = [] + self.actions = [] + self.rewards = [] + + # 记录初始状态快照 + initial_state = { + 'left_arm': self.bm_model.left_arm, # 左臂状态 + 'right_arm': self.bm_model.right_arm, # 右臂状态 + 'object': self.task.state.object_position # 物体位置 + } + self.states.append(initial_state) + + print("✓ 仿真已重置") + + def step(self, left_action: np.ndarray, right_action: np.ndarray) -> Tuple[np.ndarray, float, bool, Dict]: + """ + 执行一步仿真推进 + + 功能: + 根据输入的动作指令,更新机械臂状态、任务状态, + 计算奖励并判断是否终止。 + + 参数: + left_action (np.ndarray): 左臂动作指令,形状(3,),范围[-1, 1] + right_action (np.ndarray): 右臂动作指令,形状(3,),范围[-1, 1] + + 返回: + Tuple包含4个元素: + observation (np.ndarray): 观测数据(末端位置、物体位置等) + reward (float): 即时奖励值 + terminated (bool): 是否终止(抓取成功或失败) + info (dict): 额外信息(如抓取状态、距离误差等) + + 执行流程: + 1. 动作限幅(确保在安全范围内) + 2. 更新生物力学模型 + 3. 获取当前末端位置 + 4. 更新任务状态 + 5. 获取感知观测 + 6. 记录数据 + 7. 返回结果 + + 示例: + >>> left_action = np.array([0.5, 0.3, 0.2]) + >>> right_action = np.array([-0.5, -0.3, -0.2]) + >>> obs, reward, terminated, info = simulator.step(left_action, right_action) + >>> print(f"Reward: {reward:.3f}, Terminated: {terminated}") + """ + # ----- 1. 动作限幅(防止控制量过大)----- + # 限制动作值在[-1.0, 1.0]范围内,对应关节角度/力度的限制 + left_action = np.clip(left_action, -1.0, 1.0) + right_action = np.clip(right_action, -1.0, 1.0) + + # ----- 2. 更新生物力学模型 ----- + # 根据动作指令和时间步长更新机械臂状态 + self.bm_model.update(left_action, right_action, self.dt) + + # ----- 3. 获取当前末端位置 ----- + # 末端位置用于任务更新和感知 + left_pos = self.bm_model.left_arm.end_effector_pos + right_pos = self.bm_model.right_arm.end_effector_pos + + # ----- 4. 更新任务状态 ----- + # 计算奖励、检查终止条件、获取任务信息 + reward, terminated, info = self.task.update( + left_pos, right_pos, left_action, right_action, self.dt + ) + + # ----- 5. 获取感知观测 ----- + # 综合末端位置和物体位置,生成观测向量 + observation = self.perception.get_observation( + left_pos, right_pos, self.task.state.object_position + ) + + # ----- 6. 记录数据 ----- + # 保存动作和奖励历史 + self.actions.append((left_action.copy(), right_action.copy())) + self.rewards.append(reward) + + # 保存当前状态快照 + current_state = { + 'left_arm': self.bm_model.left_arm, + 'right_arm': self.bm_model.right_arm, + 'object': self.task.state.object_position.copy(), + 'observation': observation, + 'info': info + } + self.states.append(current_state) + + return observation, reward, terminated, info + + def run_simulation(self, policy_type: str = "sinusoidal") -> Tuple[int, float]: + """ + 运行完整的仿真过程 + + 功能: + 根据指定的控制策略,运行从重置到终止的完整仿真。 + 支持多种内置策略,可用于演示和测试。 + + 参数: + policy_type (str): 控制策略类型 + - "sinusoidal": 正弦波控制(演示用) + - "tracking": 目标跟踪控制(PD控制器) + + 返回: + Tuple[int, float]: (总步数, 总奖励) + + 执行流程: + 1. 重置仿真环境 + 2. 选择控制策略 + 3. 循环执行step直到终止或达到最大步数 + 4. 打印仿真结果 + 5. 返回统计信息 + + 示例: + >>> steps, total_reward = simulator.run_simulation("tracking") + >>> print(f"完成 {steps} 步,总奖励 {total_reward:.3f}") + """ + print("\n" + "=" * 60) + print("开始双机械臂协同操作仿真") + print("=" * 60) + + # 重置环境到初始状态 + self.reset() + + # ----- 定义控制策略函数 ----- + + def sinusoidal_policy(step: int) -> Tuple[np.ndarray, np.ndarray]: + """ + 正弦波控制策略(演示用) + + 功能: + 生成正弦波形式的周期性动作,用于测试机械臂运动范围。 + 左右臂相位相反,模拟协同运动。 + + 参数: + step (int): 当前步数 + + 返回: + Tuple[np.ndarray, np.ndarray]: (左臂动作, 右臂动作) + + 数学公式: + 左臂: A * sin(2πft + φ) + 右臂: A * sin(2πft + π + φ) + """ + t = step * self.dt + freq = 1.0 # 振荡频率 (Hz) + + # 左臂动作:三个关节的正弦波,相位递增 + left_action = np.array([ + 0.5 * np.sin(2 * np.pi * freq * t), # 关节1 + 0.3 * np.sin(2 * np.pi * freq * t + np.pi/3), # 关节2(相位偏移60°) + 0.2 * np.sin(2 * np.pi * freq * t + 2*np.pi/3) # 关节3(相位偏移120°) + ]) + + # 右臂动作:整体相位偏移π(180°),与左臂形成对称运动 + right_action = np.array([ + 0.5 * np.sin(2 * np.pi * freq * t + np.pi), # 关节1 + 0.3 * np.sin(2 * np.pi * freq * t + np.pi + np.pi/3), # 关节2 + 0.2 * np.sin(2 * np.pi * freq * t + np.pi + 2*np.pi/3) # 关节3 + ]) + + return left_action, right_action + + def target_tracking_policy(step: int) -> Tuple[np.ndarray, np.ndarray]: + """ + 目标跟踪控制策略(PD控制器) + + 功能: + 根据目标位置与当前末端位置的误差,使用比例控制 + 生成动作指令,使机械臂跟踪移动目标。 + + 参数: + step (int): 当前步数 + + 返回: + Tuple[np.ndarray, np.ndarray]: (左臂动作, 右臂动作) + + 控制原理: + 动作 = Kp * (目标位置 - 当前位置) + 其中 Kp 为比例增益系数 + + 目标轨迹: + 目标位置在初始位置附近做正弦运动 + """ + t = step * self.dt + + # 生成左臂移动目标(围绕初始位置做正弦运动) + target_left = self.task.target_left + np.array([ + 0.1 * np.sin(2 * np.pi * 0.2 * t), # X轴方向移动 ±0.1m + 0.0, # Y轴方向不移动 + 0.05 * np.sin(2 * np.pi * 0.3 * t) # Z轴方向移动 ±0.05m + ]) + + # 生成右臂移动目标(与左臂对称运动) + target_right = self.task.target_right + np.array([ + -0.1 * np.sin(2 * np.pi * 0.2 * t), # X轴方向相反相位 + 0.0, + 0.05 * np.sin(2 * np.pi * 0.3 * t) # Z轴方向相同相位 + ]) + + # 获取当前末端位置 + current_left = self.bm_model.left_arm.end_effector_pos + current_right = self.bm_model.right_arm.end_effector_pos + + # 计算位置误差 + left_error = target_left - current_left + right_error = target_right - current_right + + # 比例控制器(P Controller) + kp = 2.0 # 比例增益,控制响应速度 + + # 只取前三个分量(位置控制),转换为动作指令 + left_action = kp * left_error[:3] + right_action = kp * right_error[:3] + + return left_action, right_action + + # ----- 选择策略 ----- + if policy_type == "sinusoidal": + policy = sinusoidal_policy + print(f"\n使用策略: 正弦波控制(演示模式)") + elif policy_type == "tracking": + policy = target_tracking_policy + print(f"\n使用策略: 目标跟踪控制(PD控制器)") + else: + raise ValueError(f"未知策略类型: {policy_type}. 支持的类型: 'sinusoidal', 'tracking'") + + # ----- 仿真主循环 ----- + terminated = False + step = 0 + total_reward = 0.0 + + # 打印表头 + print(f"{'Step':>6} {'Left Pos':>20} {'Right Pos':>20} {'Reward':>10} {'Terminated':>10}") + print("-" * 80) + + # 循环执行,直到终止或达到最大步数 + while not terminated and step < self.max_steps: + # 根据策略生成动作 + left_action, right_action = policy(step) + + # 执行一步仿真 + observation, reward, terminated, info = self.step(left_action, right_action) + + # 累加总奖励 + total_reward += reward + + # 每100步打印一次状态(避免输出过多) + if step % 100 == 0: + left_pos = self.bm_model.left_arm.end_effector_pos + right_pos = self.bm_model.right_arm.end_effector_pos + print(f"{step:6d} {str(left_pos.round(2)):>20} {str(right_pos.round(2)):>20} " + f"{reward:10.3f} {str(terminated):>10}") + + step += 1 + + # ----- 打印仿真结果 ----- + print("-" * 80) + print(f"仿真完成!") + print(f"总步数: {step}") + print(f"总奖励: {total_reward:.3f}") + print(f"是否抓取成功: {self.task.state.is_grasped}") + print(f"物体最终位置: {self.task.state.object_position.round(3)}") + + return step, total_reward + + def analyze_results(self) -> Dict[str, Any]: + """ + 分析仿真结果并计算统计指标 + + 功能: + 从记录的状态历史中提取数据,计算各种性能指标: + 路径长度、最大速度、协同度、奖励统计等。 + + 返回: + Dict[str, Any]: 包含所有统计指标的字典 + + 统计指标: + - left_path_length: 左臂运动路径总长度(米) + - right_path_length: 右臂运动路径总长度(米) + - left_max_speed: 左臂最大速度(米/秒) + - right_max_speed: 右臂最大速度(米/秒) + - coordination_index: 协同度指标 [0,1],越高表示协同越好 + - mean_reward: 平均奖励 + - total_reward: 总奖励 + - total_steps: 总步数 + - success: 任务是否成功 + - final_object_position: 物体最终位置 + - timestamp: 分析时间戳 + + 协同度指标算法: + coordination = 0.5 * (1/(1+距离标准差)) + 0.5 * (平均余弦相似度+1)/2 + 其中: + - 距离标准差: 双手间距的稳定性 + - 余弦相似度: 双手运动方向的一致性 + + 示例: + >>> stats = simulator.analyze_results() + >>> print(f"协同度: {stats['coordination_index']:.3f}") + >>> print(f"成功率: {'成功' if stats['success'] else '失败'}") + """ + print("\n" + "=" * 60) + print("仿真结果分析") + print("=" * 60) + + # ----- 提取轨迹数据 ----- + # 从状态历史中提取末端位置和物体位置 + left_positions = np.array([s['left_arm'].end_effector_pos for s in self.states]) + right_positions = np.array([s['right_arm'].end_effector_pos for s in self.states]) + object_positions = np.array([s['object'] for s in self.states]) + rewards = np.array(self.rewards) + + # ----- 计算路径长度 ----- + # 路径长度 = 各段位移的累加和 + left_path_length = np.sum(np.linalg.norm(np.diff(left_positions, axis=0), axis=1)) + right_path_length = np.sum(np.linalg.norm(np.diff(right_positions, axis=0), axis=1)) + + # ----- 计算最大速度 ----- + # 速度 = 位移 / 时间步长 + left_velocities = np.diff(left_positions, axis=0) / self.dt + right_velocities = np.diff(right_positions, axis=0) / self.dt + + left_max_speed = np.max(np.linalg.norm(left_velocities, axis=1)) + right_max_speed = np.max(np.linalg.norm(right_velocities, axis=1)) + + # ----- 计算协同度指标 ----- + coordination_index = self._calculate_coordination_index(left_positions, right_positions) + + # ----- 打印分析结果 ----- + print(f"左机械臂路径长度: {left_path_length:.3f} m") + print(f"右机械臂路径长度: {right_path_length:.3f} m") + print(f"左机械臂最大速度: {left_max_speed:.3f} m/s") + print(f"右机械臂最大速度: {right_max_speed:.3f} m/s") + print(f"协同度指标: {coordination_index:.3f}") + print(f"平均奖励: {np.mean(rewards):.3f}") + print(f"总奖励: {np.sum(rewards):.3f}") + + # ----- 保存统计数据到YAML文件 ----- + stats = { + 'left_path_length': float(left_path_length), + 'right_path_length': float(right_path_length), + 'left_max_speed': float(left_max_speed), + 'right_max_speed': float(right_max_speed), + 'coordination_index': float(coordination_index), + 'mean_reward': float(np.mean(rewards)), + 'total_reward': float(np.sum(rewards)), + 'total_steps': len(self.states), + 'success': bool(self.task.state.is_grasped), + 'final_object_position': self.task.state.object_position.tolist(), + 'timestamp': datetime.now().isoformat() + } + + # 保存为YAML格式,便于阅读和后续分析 + stats_path = self.results_dir / "simulation_stats.yaml" + with open(stats_path, 'w', encoding='utf-8') as f: + yaml.dump(stats, f, default_flow_style=False, allow_unicode=True) + + print(f"✓ 统计数据已保存至: {stats_path}") + + return stats + + def _calculate_coordination_index(self, left_pos: np.ndarray, right_pos: np.ndarray) -> float: + """ + 计算双机械臂协同度指标 + + 功能: + 评估左右机械臂运动的协同程度,综合考虑: + 1. 双手距离的稳定性(标准差) + 2. 运动方向的一致性(余弦相似度) + + 参数: + left_pos (np.ndarray): 左臂末端位置历史,形状(N, 3) + right_pos (np.ndarray): 右臂末端位置历史,形状(N, 3) + + 返回: + float: 协同度指标,范围[0, 1] + - 1.0: 完美协同(理想情况) + - 0.5: 中等协同 + - 0.0: 完全无协同 + + 算法细节: + 1. 距离稳定性 = 1 / (1 + 距离标准差) + 距离标准差越小,稳定性越高 + + 2. 方向一致性 = (平均余弦相似度 + 1) / 2 + 余弦相似度范围[-1,1],归一化到[0,1] + + 3. 最终协同度 = 0.5 * 距离稳定性 + 0.5 * 方向一致性 + + 注意: + 需要至少2个时间步才能计算速度方向, + 如果轨迹长度不足,返回默认值0.5 + """ + # 计算双手欧氏距离序列 + distances = np.linalg.norm(left_pos - right_pos, axis=1) + distance_std = np.std(distances) # 标准差越小,距离越稳定 + + # 计算运动速度方向 + left_vel = np.diff(left_pos, axis=0) # 左臂速度向量 + right_vel = np.diff(right_pos, axis=0) # 右臂速度向量 + + # 计算速度方向余弦相似度 + if len(left_vel) > 0: + cos_similarities = [] + for lv, rv in zip(left_vel, right_vel): + # 避免除以零 + if np.linalg.norm(lv) > 0.001 and np.linalg.norm(rv) > 0.001: + # 余弦相似度 = (A·B) / (|A|·|B|) + cos_sim = np.dot(lv, rv) / (np.linalg.norm(lv) * np.linalg.norm(rv)) + cos_similarities.append(cos_sim) + + if cos_similarities: + mean_cos_sim = np.mean(cos_similarities) + else: + mean_cos_sim = 0 + else: + mean_cos_sim = 0 + + # 归一化余弦相似度到[0,1]范围 + # 原始范围[-1,1] → 映射到[0,1] + normalized_cos_sim = (mean_cos_sim + 1) / 2 + + # 距离稳定性指标:标准差越小,稳定性越高 + # 使用 1/(1+σ) 确保结果在(0,1]范围内 + distance_stability = 1.0 / (1.0 + distance_std) + + # 综合协同度:距离稳定性 + 方向一致性(各占50%) + coordination = 0.5 * distance_stability + 0.5 * normalized_cos_sim + + return coordination + + def visualize_all(self) -> None: + """ + 生成所有可视化结果 + + 功能: + 调用各模块的可视化方法,生成: + 1. 轨迹图(机械臂运动轨迹) + 2. 任务可视化(物体位置、抓取状态) + 3. 动画视频(完整运动过程) + 4. 性能图表(奖励曲线、误差曲线等) + + 输出文件: + - results/trajectory_plot.png + - results/task_visualization.png + - results/videos/dual_arm_animation.mp4 + - results/performance_analysis.png + + 使用场景: + - 仿真完成后自动生成报告 + - 分析机械臂运动规律 + - 制作演示视频 + """ + print("\n" + "=" * 60) + print("生成可视化结果") + print("=" * 60) + + # 1. 生成机械臂轨迹图 + trajectory_plot_path = self.results_dir / "trajectory_plot.png" + self.bm_model.plot_trajectory(str(trajectory_plot_path)) + + # 2. 生成任务可视化(物体、抓取点等) + task_viz_path = self.results_dir / "task_visualization.png" + self.task.visualize(self.bm_model.trajectory, str(task_viz_path)) + + # 3. 创建动画视频(需要ffmpeg支持) + animation_path = self.results_dir / "videos" / "dual_arm_animation.mp4" + self.task.create_animation(self.bm_model.trajectory, str(animation_path)) + + # 4. 生成性能分析图表 + self._plot_performance() + + # 打印输出文件位置 + print(f"✓ 轨迹图: {trajectory_plot_path}") + print(f"✓ 任务可视化: {task_viz_path}") + print(f"✓ 动画视频: {animation_path}") + + def _plot_performance(self) -> None: + """ + 绘制性能分析图表(内部方法) + + 功能: + 创建4个子图,分别显示: + 1. 奖励曲线(即时奖励和累积奖励) + 2. 位置误差(左右手到物体的距离) + 3. 关节角度变化(6个关节的时间序列) + 4. 双手间距(协同度指标) + + 图表特点: + - 4个子图布局:2x2网格 + - 包含图例、网格、阈值线 + - 高分辨率输出(150 DPI) + + 输出文件: + results/performance_analysis.png + """ + # 创建2x2子图布局,总尺寸15x10英寸 + fig = plt.figure(figsize=(15, 10)) + + # ----- 子图1: 奖励曲线 ----- + ax1 = fig.add_subplot(221) + rewards = np.array(self.rewards) + cumulative_rewards = np.cumsum(rewards) # 累积奖励 + + ax1.plot(rewards, 'b-', alpha=0.7, linewidth=1, label='即时奖励') + ax1.plot(cumulative_rewards, 'r-', linewidth=2, label='累积奖励') + ax1.axhline(y=0, color='k', linestyle='-', alpha=0.3) # 零线 + ax1.set_xlabel('时间步') + ax1.set_ylabel('奖励值') + ax1.set_title('奖励随时间变化') + ax1.legend() + ax1.grid(True, alpha=0.3) + + # ----- 子图2: 位置误差曲线 ----- + ax2 = fig.add_subplot(222) + left_errors = self.task.history['left_errors'] # 左臂到物体的距离 + right_errors = self.task.history['right_errors'] # 右臂到物体的距离 + + ax2.plot(left_errors, 'r-', linewidth=1.5, label='左臂误差') + ax2.plot(right_errors, 'b-', linewidth=1.5, label='右臂误差') + ax2.axhline(y=self.task.grasp_distance, color='g', linestyle='--', + linewidth=1.5, label='抓取阈值') + ax2.set_xlabel('时间步') + ax2.set_ylabel('到物体的距离 (m)') + ax2.set_title('位置误差') + ax2.legend() + ax2.grid(True, alpha=0.3) + + # ----- 子图3: 关节角度曲线 ----- + ax3 = fig.add_subplot(223) + # 提取左右臂的所有关节角度 + left_joints = np.array([s['left_arm'].joint_positions for s in self.states]) + right_joints = np.array([s['right_arm'].joint_positions for s in self.states]) + + # 绘制左臂关节(实线)和右臂关节(虚线) + for i in range(3): # 假设每个臂有3个关节 + ax3.plot(left_joints[:, i], f'C{i}-', alpha=0.7, linewidth=1.5, + label=f'左臂关节{i+1}') + ax3.plot(right_joints[:, i], f'C{i}--', alpha=0.7, linewidth=1.5, + label=f'右臂关节{i+1}') + + ax3.set_xlabel('时间步') + ax3.set_ylabel('关节角度 (rad)') + ax3.set_title('关节角度变化') + ax3.legend(ncol=2, fontsize=9) + ax3.grid(True, alpha=0.3) + + # ----- 子图4: 双手间距(协同度指标)----- + ax4 = fig.add_subplot(224) + + # 计算双手之间的欧氏距离 + left_pos = np.array([s['left_arm'].end_effector_pos for s in self.states]) + right_pos = np.array([s['right_arm'].end_effector_pos for s in self.states]) + hand_distances = np.linalg.norm(left_pos - right_pos, axis=1) + + ax4.plot(hand_distances, 'purple', linewidth=2, label='双手距离') + ax4.axhline(y=0.2, color='g', linestyle='--', linewidth=1.5, + label='理想距离 (0.2m)') + ax4.set_xlabel('时间步') + ax4.set_ylabel('双手间距 (m)') + ax4.set_title('双手间距 - 协同度指标') + ax4.legend() + ax4.grid(True, alpha=0.3) + + # 添加总标题 + plt.suptitle('双机械臂协同搬运性能分析', fontsize=14, fontweight='bold') + + # 调整子图间距,避免重叠 + plt.tight_layout() + + # 保存图片 + performance_path = self.results_dir / "performance_analysis.png" + plt.savefig(str(performance_path), dpi=150, bbox_inches='tight') + plt.show() + + print(f"✓ 性能分析图: {performance_path}") + + +def main() -> int: + """ + 主函数:程序入口 + + 功能: + 1. 解析命令行参数(可选) + 2. 创建仿真器实例 + 3. 交互式选择控制策略 + 4. 运行仿真 + 5. 分析结果并生成可视化 + 6. 保存仿真数据 + + 返回: + int: 退出码 + - 0: 正常完成 + - 1: 运行出错 + + 使用示例: + $ python main.py + 请选择控制策略: + 1. 正弦波控制 (演示) + 2. 目标跟踪控制 + 请输入选择 (1 或 2): 2 + """ + print("\n" + "=" * 60) + print("双机械臂协同操作仿真系统") + print("=" * 60) + + try: + # ----- 1. 创建仿真器实例 ----- + # 默认读取当前目录下的 config.yaml 文件 + simulator = DualArmSimulator() + + # ----- 2. 选择控制策略 ----- + print("\n请选择控制策略:") + print("1. 正弦波控制 (演示模式)") + print("2. 目标跟踪控制 (PD控制器)") + + choice = input("请输入选择 (1 或 2): ").strip() + + if choice == "1": + policy_type = "sinusoidal" + print("\n已选择: 正弦波控制模式") + elif choice == "2": + policy_type = "tracking" + print("\n已选择: 目标跟踪控制模式") + else: + print("输入无效,使用默认策略: 正弦波控制") + policy_type = "sinusoidal" + + # ----- 3. 运行仿真 ----- + steps, total_reward = simulator.run_simulation(policy_type) + + # ----- 4. 分析结果 ----- + stats = simulator.analyze_results() + + # ----- 5. 生成可视化 ----- + simulator.visualize_all() + + # ----- 6. 保存仿真数据 ----- + # 使用压缩格式保存numpy数组,节省空间 + data_path = simulator.results_dir / "simulation_data.npz" + np.savez_compressed( + str(data_path), + states=simulator.states, + actions=simulator.actions, + rewards=simulator.rewards, + config=simulator.config + ) + print(f"\n✓ 仿真数据已保存至: {data_path}") + + # ----- 7. 打印最终总结 ----- + print("\n" + "=" * 60) + print("仿真完成!") + print(f"总步数: {steps}") + print(f"总奖励: {total_reward:.3f}") + print(f"协同度: {stats['coordination_index']:.3f}") + print(f"任务成功: {'✓ 是' if stats['success'] else '✗ 否'}") + print("=" * 60) + + # 列出所有生成的结果文件 + print("\n生成的结果文件:") + for file in sorted(simulator.results_dir.rglob("*")): + if file.is_file(): + size_mb = file.stat().st_size / (1024 * 1024) + print(f" - {file.relative_to(simulator.results_dir)} ({size_mb:.2f} MB)") + + except FileNotFoundError as e: + print(f"\n❌ 文件未找到: {e}") + print("请确保配置文件 config.yaml 存在于当前目录") + return 1 + except KeyError as e: + print(f"\n❌ 配置错误: 缺少必要的配置项 {e}") + print("请检查 config.yaml 文件的完整性") + return 1 + except Exception as e: + print(f"\n❌ 运行出错: {e}") + import traceback + traceback.print_exc() + return 1 + + return 0 + + +if __name__ == "__main__": + """ + 程序入口点 + + 当脚本被直接运行时(而不是作为模块导入),执行 main() 函数 + """ + sys.exit(main()) From d42f2513fa81869cfb3d31fdb689bcd997b5c9af Mon Sep 17 00:00:00 2001 From: lyy-1236 <3584247842@qq.com> Date: Thu, 11 Jun 2026 22:37:05 +0800 Subject: [PATCH 6/9] Create readme.md --- readme.md | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 readme.md diff --git a/readme.md b/readme.md new file mode 100644 index 0000000000..caa773778b --- /dev/null +++ b/readme.md @@ -0,0 +1,152 @@ + +# box — 仿真与强化学习实验箱 + +## 概述 + +`src/box` 包含若干用于仿真、感知与强化学习实验的环境与辅助脚本。主要面向基于 Gymnasium/MuJoCo 的任务、轻量级可视化示例以及调试/测试工具。 + +Gymnasium:一个操作台,是网上的一个强化学习的开源项目,给你提供一套通用的指令,可以完成一些基础操作。并提供给你进一步写指令代码的框架。 + +MuJoCo:一个物理仿真平台,计算你的指令在现实中执行后可能产生什么结果。 + +在这个项目中是一起用,如果只是写一些小例子,可以只用Gymnasium,它的内部也包含了一些现成的环境。 + +本目录旨在提供: + +- 可复现的仿真实验环境模板; +- 启动/调试脚本示例; +- 与主仓库其余模块交互的接口说明(若存在)。 + +## 推荐目录结构(示例) + +- `simulator.py`:仿真环境实现(通常继承自 `gym.Env` 或 `gymnasium.Env`); +- `envs/`:若干子环境模块或封装; +- `agents/`:示例智能体或策略实现(可选); +- `scripts/`:运行/训练/评估脚本(如 `run.py`, `train.py`, `eval.py`); +- `tests/`:小型示例与单元/集成测试脚本(如 `test_simulator.py`); +- `configs/`:默认配置或参数文件(YAML/JSON); +- `README.md`:本文件。 + +(实际文件以仓库中为准,此处为常见约定。) + +## 快速上手 + +以下示例以 Windows + PowerShell 为例: + +1) 进入项目根目录并创建虚拟环境: + +```powershell +cd D:\\nn +python -m venv .venv +.\\.venv\\Scripts\\Activate.ps1 +``` + +2) 安装依赖: + +```powershell +pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple +``` + +如果仓库未包含完整的 `requirements.txt`,可参考核心依赖: + +``` +gymnasium +mujoco +stable-baselines3 +pygame +opencv-python +numpy +scipy +matplotlib +ruamel.yaml +``` + +3) 运行示例仿真(示例脚本名以仓库实际文件为准): + +```powershell +python scripts/run.py +``` + +或: + +```powershell +python tests/test_simulator.py +``` + +运行后通常会在本地弹出可视化窗口或在终端打印日志,具体行为视环境实现而定。 + +## 开发与调试提示 + +- MuJoCo:请确保已正确安装 MuJoCo 及其许可证(若使用 mujoco 依赖)。 +- 显示/渲染:在无显示的服务器上运行时,可能需要配置虚拟帧缓冲(Linux 下使用 xvfb)。 +- 依赖冲突:若出现版本不兼容,建议使用虚拟环境并锁定依赖版本。 + +## 如何贡献 + +- 修改或补充说明、示例脚本或配置后提交 Pull Request; +- 提交 Issue 时请包含:操作系统、Python 版本、依赖版本与复现步骤/错误日志。 + +## 参考与更多信息 + +- 查看目录下的具体脚本与模块顶部注释,通常包含使用示例与参数说明; +- 若需要,我可以为 `src/box` 中的主要文件生成更详细的文档或示例运行脚本。 + +**box — 仿真与强化学习实验箱** + +简介 +- `src/box` 目录包含基于 Gymnasium 和 MuJoCo 的仿真环境与相关辅助脚本,用于开发和测试生物力学/机器人仿真、感知模块与强化学习任务。 + +目录结构(示例) +- `simulator.py`:仿真环境核心(通常继承 `gym.Env`)。 +- `test_simulator.py`:示例运行脚本,用于启动仿真并可视化。 +- `main.py`:辅助脚本(例如证书或配置检查)。 +- `README.md`:本文件,说明目录用途与快速上手指南。 + +快速上手 +1. 创建并激活虚拟环境(以 Windows 为例): + +```powershell +cd <项目根目录> +python -m venv venv --python=3.9 +.\\venv\\Scripts\\Activate.ps1 +``` + +2. 安装依赖(建议使用清华镜像加速): + +```powershell +pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple +``` + +如果仓库没有完整的 `requirements.txt`,可参考下列核心库: + +```text +gymnasium +mujoco +stable-baselines3 +pygame +opencv-python +numpy +scipy +matplotlib +ruamel.yaml +certifi +``` + +运行示例 +- 启动仿真: + +```powershell +python test_simulator.py +``` + +运行后应弹出可视化窗口(若使用 Pygame/SDL),并在终端输出仿真日志。 + +贡献与问题反馈 +- 若需添加说明或示例,请提交 Pull Request。 +- 遇到环境或依赖问题,请在 Issue 中描述操作系统、Python 版本与错误日志。 + +更多信息 +- 若目录中包含更详细的子模块文档,请参阅相应文件(如 `simulator.py` 顶部注释或同目录下的文档)。 + +--- + From 9c157b3752632e14943722737974e255b2684d18 Mon Sep 17 00:00:00 2001 From: lyy-1236 <3584247842@qq.com> Date: Sun, 21 Jun 2026 23:29:28 +0800 Subject: [PATCH 7/9] Update index.md --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 7d57f06acc..ef2c2beef4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -184,7 +184,7 @@ title: 主页 # 其他 - [__YOLO数据生成器__](./carla-yolo-dataset-generator/README.md) - + - [__双机械臂协同仿真系统__](./docs/index.md) - 基于Gymnasium/MuJoCo的仿真与强化学习实验环境 - [__驾驶事故视频识别__](./carla_temporal_collage/index.md) - 基于 Temporal Collage Prompting 的 CARLA 驾驶事故视频识别系统 - [__CARLA IMU 数据采集平台__](./carla_imu/carla_imu.md) — CARLA惯性测量单元数据采集与可视化驾驶平台开发汇报文档 From 70c696635d12d065b2348090049fe2514ed3f4b6 Mon Sep 17 00:00:00 2001 From: lyy-1236 <3584247842@qq.com> Date: Sun, 21 Jun 2026 23:33:32 +0800 Subject: [PATCH 8/9] Create index.md --- docs/dua_arm/index.md | 152 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 docs/dua_arm/index.md diff --git a/docs/dua_arm/index.md b/docs/dua_arm/index.md new file mode 100644 index 0000000000..caa773778b --- /dev/null +++ b/docs/dua_arm/index.md @@ -0,0 +1,152 @@ + +# box — 仿真与强化学习实验箱 + +## 概述 + +`src/box` 包含若干用于仿真、感知与强化学习实验的环境与辅助脚本。主要面向基于 Gymnasium/MuJoCo 的任务、轻量级可视化示例以及调试/测试工具。 + +Gymnasium:一个操作台,是网上的一个强化学习的开源项目,给你提供一套通用的指令,可以完成一些基础操作。并提供给你进一步写指令代码的框架。 + +MuJoCo:一个物理仿真平台,计算你的指令在现实中执行后可能产生什么结果。 + +在这个项目中是一起用,如果只是写一些小例子,可以只用Gymnasium,它的内部也包含了一些现成的环境。 + +本目录旨在提供: + +- 可复现的仿真实验环境模板; +- 启动/调试脚本示例; +- 与主仓库其余模块交互的接口说明(若存在)。 + +## 推荐目录结构(示例) + +- `simulator.py`:仿真环境实现(通常继承自 `gym.Env` 或 `gymnasium.Env`); +- `envs/`:若干子环境模块或封装; +- `agents/`:示例智能体或策略实现(可选); +- `scripts/`:运行/训练/评估脚本(如 `run.py`, `train.py`, `eval.py`); +- `tests/`:小型示例与单元/集成测试脚本(如 `test_simulator.py`); +- `configs/`:默认配置或参数文件(YAML/JSON); +- `README.md`:本文件。 + +(实际文件以仓库中为准,此处为常见约定。) + +## 快速上手 + +以下示例以 Windows + PowerShell 为例: + +1) 进入项目根目录并创建虚拟环境: + +```powershell +cd D:\\nn +python -m venv .venv +.\\.venv\\Scripts\\Activate.ps1 +``` + +2) 安装依赖: + +```powershell +pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple +``` + +如果仓库未包含完整的 `requirements.txt`,可参考核心依赖: + +``` +gymnasium +mujoco +stable-baselines3 +pygame +opencv-python +numpy +scipy +matplotlib +ruamel.yaml +``` + +3) 运行示例仿真(示例脚本名以仓库实际文件为准): + +```powershell +python scripts/run.py +``` + +或: + +```powershell +python tests/test_simulator.py +``` + +运行后通常会在本地弹出可视化窗口或在终端打印日志,具体行为视环境实现而定。 + +## 开发与调试提示 + +- MuJoCo:请确保已正确安装 MuJoCo 及其许可证(若使用 mujoco 依赖)。 +- 显示/渲染:在无显示的服务器上运行时,可能需要配置虚拟帧缓冲(Linux 下使用 xvfb)。 +- 依赖冲突:若出现版本不兼容,建议使用虚拟环境并锁定依赖版本。 + +## 如何贡献 + +- 修改或补充说明、示例脚本或配置后提交 Pull Request; +- 提交 Issue 时请包含:操作系统、Python 版本、依赖版本与复现步骤/错误日志。 + +## 参考与更多信息 + +- 查看目录下的具体脚本与模块顶部注释,通常包含使用示例与参数说明; +- 若需要,我可以为 `src/box` 中的主要文件生成更详细的文档或示例运行脚本。 + +**box — 仿真与强化学习实验箱** + +简介 +- `src/box` 目录包含基于 Gymnasium 和 MuJoCo 的仿真环境与相关辅助脚本,用于开发和测试生物力学/机器人仿真、感知模块与强化学习任务。 + +目录结构(示例) +- `simulator.py`:仿真环境核心(通常继承 `gym.Env`)。 +- `test_simulator.py`:示例运行脚本,用于启动仿真并可视化。 +- `main.py`:辅助脚本(例如证书或配置检查)。 +- `README.md`:本文件,说明目录用途与快速上手指南。 + +快速上手 +1. 创建并激活虚拟环境(以 Windows 为例): + +```powershell +cd <项目根目录> +python -m venv venv --python=3.9 +.\\venv\\Scripts\\Activate.ps1 +``` + +2. 安装依赖(建议使用清华镜像加速): + +```powershell +pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple +``` + +如果仓库没有完整的 `requirements.txt`,可参考下列核心库: + +```text +gymnasium +mujoco +stable-baselines3 +pygame +opencv-python +numpy +scipy +matplotlib +ruamel.yaml +certifi +``` + +运行示例 +- 启动仿真: + +```powershell +python test_simulator.py +``` + +运行后应弹出可视化窗口(若使用 Pygame/SDL),并在终端输出仿真日志。 + +贡献与问题反馈 +- 若需添加说明或示例,请提交 Pull Request。 +- 遇到环境或依赖问题,请在 Issue 中描述操作系统、Python 版本与错误日志。 + +更多信息 +- 若目录中包含更详细的子模块文档,请参阅相应文件(如 `simulator.py` 顶部注释或同目录下的文档)。 + +--- + From 410797486aa758f9052d7f2cf15114c477685693 Mon Sep 17 00:00:00 2001 From: lyy-1236 <3584247842@qq.com> Date: Sun, 21 Jun 2026 23:35:06 +0800 Subject: [PATCH 9/9] Update index.md --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index ef2c2beef4..1eb998f78f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -184,7 +184,7 @@ title: 主页 # 其他 - [__YOLO数据生成器__](./carla-yolo-dataset-generator/README.md) - - [__双机械臂协同仿真系统__](./docs/index.md) - 基于Gymnasium/MuJoCo的仿真与强化学习实验环境 + - [__双机械臂协同仿真系统__](./dua_arm/index.md) - 基于Gymnasium/MuJoCo的仿真与强化学习实验环境 - [__驾驶事故视频识别__](./carla_temporal_collage/index.md) - 基于 Temporal Collage Prompting 的 CARLA 驾驶事故视频识别系统 - [__CARLA IMU 数据采集平台__](./carla_imu/carla_imu.md) — CARLA惯性测量单元数据采集与可视化驾驶平台开发汇报文档