# Finger 机械手指

Finger 是 DeepMind Control Suite 中的经典操控任务：一个由两段连杆组成的“手指”通过施加关节力矩与旋转拨片（spinner）交互。MotrixLab 当前提供了 3 个 Finger 相关环境：

```{video} /_static/videos/dm_finger_spin.mp4
:poster: _static/images/poster/dm_finger_spin.jpg
:nocontrols:
:autoplay:
:playsinline:
:muted:
:loop:
:width: 100%
```

```{video} /_static/videos/dm_finger_turn.mp4
:poster: _static/images/poster/dm_finger_turn.jpg
:nocontrols:
:autoplay:
:playsinline:
:muted:
:loop:
:width: 100%
```

-   `dm-finger-spin`：让 spinner 持续向指定方向旋转
-   `dm-finger-turn-easy`：将 spinner 的顶端（tip）对准目标点（较大目标半径）
-   `dm-finger-turn-hard`：同 Turn，但目标半径更小

---

## 任务描述

Finger 是一个二维平面（x-z）内的手指与旋转拨片交互任务：

-   手指有 2 个受控关节：`proximal`、`distal`
-   spinner 通过关节 `hinge` 转动，`tip` 表示 spinner 顶端位置
-   Turn 任务会在 spinner 周围随机采样一个目标点（target）

---

## 动作空间（Action Space）

| 项目     | 详细信息                        |
| -------- | ------------------------------- |
| **类型** | `Box(-1.0, 1.0, (2,), float32)` |
| **维度** | 2                               |

动作对应如下：

| 序号 | 动作含义（施加在关节的力矩） | 最小值 | 最大值 | 对应 XML 中名称 |
| ---: | ---------------------------- | :----: | :----: | :-------------: |
|    0 | 近端关节 `proximal` 驱动扭矩 |   -1   |   1    |   `proximal`    |
|    1 | 远端关节 `distal` 驱动扭矩   |   -1   |   1    |    `distal`     |

---

## 观察空间

Finger 环境的观测以 dm_control 的 observation dict 为参考，但在 MotrixLab 中被拼成一个向量。

### Spin 观察空间

| 项目     | 详细信息                        |
| -------- | ------------------------------- |
| **类型** | `Box(-inf, inf, (9,), float32)` |
| **维度** | 9                               |

组成如下（按顺序）：

| 部分         | 内容说明                                      | 维度 | 备注                          |
| ------------ | --------------------------------------------- | ---- | ----------------------------- |
| **position** | `qpos(proximal, distal)` + `tip_xz`           | 4    | tip 相对 spinner 的 x、z 位置 |
| **velocity** | `qvel(proximal, distal, hinge)`               | 3    | hinge 角速度用于 Spin 奖励    |
| **touch**    | `log(1 + touch_top)`、`log(1 + touch_bottom)` | 2    | 触觉传感器的对数压缩          |

| 序号 | 观察量                             | 最小值 | 最大值 | XML/Sensor 名称         | 类型 (单位)    |
| ---- | ---------------------------------- | ------ | ------ | ----------------------- | -------------- |
| 0    | `proximal` 关节角度                | -Inf   | Inf    | `proximal`              | 角度 (rad)     |
| 1    | `distal` 关节角度                  | -Inf   | Inf    | `distal`                | 角度 (rad)     |
| 2    | tip 相对 spinner 的 x 位移         | -Inf   | Inf    | `framepos(tip/spinner)` | 位置 (m)       |
| 3    | tip 相对 spinner 的 z 位移         | -Inf   | Inf    | `framepos(tip/spinner)` | 位置 (m)       |
| 4    | `proximal` 角速度                  | -Inf   | Inf    | `proximal_velocity`     | 角速度 (rad/s) |
| 5    | `distal` 角速度                    | -Inf   | Inf    | `distal_velocity`       | 角速度 (rad/s) |
| 6    | spinner 的 `hinge` 角速度          | -Inf   | Inf    | `hinge_velocity`        | 角速度 (rad/s) |
| 7    | 触觉（上侧）`log(1 + touchtop)`    | -Inf   | Inf    | `touchtop`              | 无量纲         |
| 8    | 触觉（下侧）`log(1 + touchbottom)` | -Inf   | Inf    | `touchbottom`           | 无量纲         |

### Turn 观察空间

| 项目     | 详细信息                         |
| -------- | -------------------------------- |
| **类型** | `Box(-inf, inf, (12,), float32)` |
| **维度** | 12                               |

相较 Spin 额外增加：

| 部分                | 内容说明                         | 维度 | 备注                      |
| ------------------- | -------------------------------- | ---- | ------------------------- |
| **target_position** | target 相对 spinner 的 x、z 坐标 | 2    | target 在 reset 时采样    |
| **dist_to_target**  | tip 到 target 球面距离（带符号） | 1    | 负值表示 tip 落在目标球内 |

向量最后 3 个维度为：`target_x`, `target_z`, `dist_to_target`。

---

## 奖励函数设计

### Spin

在 dm_control 中，Spin 的稀疏奖励通常由 spinner 的角速度阈值触发。MotrixLab 默认使用更易训练的 dense/shaped 奖励，并同时在 info 中记录稀疏版本：

```python
# hinge_velocity 为 spinner 关节角速度
spin_sparse = 1 if hinge_velocity <= -15 else 0

# shaped: clip(-hinge_velocity / 15, 0..1)
spin = clip(-hinge_velocity / 15, 0, 1)
```

### Turn（Easy / Hard）

Turn 的核心是 tip 触达并对准目标点：目标点位于 spinner 周围一圈，目标球半径在 easy/hard 中不同。

-   `turn_sparse = 1` 当 tip 进入目标球内部（`dist_to_target <= 0`）
-   默认 shaped 奖励以 `dist_to_target` 的指数衰减为主，并额外加入“靠近 spinner、增加接触、抑制动作抖动”等项（最终裁剪到 `[0, 1]`）

---

## 初始状态

-   `proximal`、`distal` 关节角：在各自关节限制范围内均匀采样
-   spinner 的 `hinge` 角：`[-pi, pi]` 均匀采样
-   Turn 任务：每个 episode 在 spinner 周围采样 target（角度均匀采样，位置落在 x-z 平面）

## Episode 终止条件

-   若观测中出现 `NaN` 值则终止

---

## 使用指南

### 1. 环境预览（随机动作）

```bash
uv run scripts/view.py --env dm-finger-spin
```

```bash
uv run scripts/view.py --env dm-finger-turn-easy
```

```bash
uv run scripts/view.py --env dm-finger-turn-hard
```

### 2. 开始训练

建议显式指定训练后端（JAX / PyTorch 二选一）：

```bash
uv run scripts/train.py --env dm-finger-spin --train-backend torch
```

```bash
uv run scripts/train.py --env dm-finger-turn-easy --train-backend torch
```

```bash
uv run scripts/train.py --env dm-finger-turn-hard --train-backend torch
```

### 3. 查看训练进度

```bash
uv run tensorboard --logdir runs/dm-finger-spin
```

### 4. 测试训练结果

`scripts/play.py` 默认会自动在 `runs/{env-name}/` 下寻找最新的 `best_agent.*`，也可以用 `--policy` 显式指定：

```bash
uv run scripts/play.py --env dm-finger-turn-hard
```

---

## 预期训练结果

1. `dm-finger-spin`：spinner 能稳定持续向目标方向旋转（hinge_velocity 达到阈值附近）
2. `dm-finger-turn-easy`：手指能稳定接触并将 tip 对准目标点（成功率较高、抖动较小）
3. `dm-finger-turn-hard`：能对准更小目标，但更容易出现“接触不足/动作抖动”的训练难点
