react-manip 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- assets/materials/realistic/common/ceramic.png +0 -0
- assets/materials/realistic/common/cream-plaster.png +0 -0
- assets/materials/realistic/common/gray-felt.png +0 -0
- assets/materials/realistic/common/gray-plaster.png +0 -0
- assets/materials/realistic/common/gray-woodgrain.png +0 -0
- assets/materials/realistic/common/light-gray-floor-tile.png +0 -0
- assets/materials/realistic/common/light-gray-plaster.png +0 -0
- assets/materials/realistic/common/metal.png +0 -0
- assets/materials/realistic/common/oak-wood.png +0 -0
- assets/materials/realistic/common/steel-brushed.png +0 -0
- assets/materials/realistic/common/steel-scratched.png +0 -0
- assets/materials/realistic/common/varnished-wood-panels.png +0 -0
- assets/materials/realistic/common/walnut-wood.png +0 -0
- assets/materials/realistic/common/white-bricks.png +0 -0
- assets/materials/realistic/common/wood-tiles.png +0 -0
- assets/materials/realistic/tiles074/Tiles074_2K_Color.png +0 -0
- assets/materials/realistic/woodfloor049/WoodFloor049_2K_Color.png +0 -0
- configs/box_insertion/profile_default.json +204 -0
- react_manip/__init__.py +12 -0
- react_manip/asset_cli.py +18 -0
- react_manip/builtin.py +168 -0
- react_manip/cameras.py +301 -0
- react_manip/category_objects.py +248 -0
- react_manip/core/__init__.py +60 -0
- react_manip/core/aggregate.py +59 -0
- react_manip/core/compatibility.py +64 -0
- react_manip/core/contracts.py +42 -0
- react_manip/core/geometry.py +269 -0
- react_manip/core/language.py +116 -0
- react_manip/core/metrics.py +196 -0
- react_manip/core/observation.py +59 -0
- react_manip/core/protocol.py +91 -0
- react_manip/core/registry.py +94 -0
- react_manip/core/runner.py +498 -0
- react_manip/core/scenarios.py +110 -0
- react_manip/core/specs.py +203 -0
- react_manip/core/timeouts.py +44 -0
- react_manip/core/visual_randomization.py +261 -0
- react_manip/evaluation/__init__.py +4 -0
- react_manip/evaluation/metrics.py +17 -0
- react_manip/evaluation/protocols.py +326 -0
- react_manip/evaluation/viewer.py +240 -0
- react_manip/instances/__init__.py +6 -0
- react_manip/instances/candidate_bank.py +109 -0
- react_manip/instances/public.py +95 -0
- react_manip/instances/records.py +345 -0
- react_manip/instances/runtime.py +306 -0
- react_manip/instances/suite.py +231 -0
- react_manip/instances/trajectory.py +59 -0
- react_manip/instances/variation.py +118 -0
- react_manip/legacy.py +318 -0
- react_manip/policies/__init__.py +5 -0
- react_manip/policies/base.py +79 -0
- react_manip/rendering.py +58 -0
- react_manip/robot_registry.py +31 -0
- react_manip/robots/__init__.py +33 -0
- react_manip/robots/adapters.py +135 -0
- react_manip/robots/agilex_adapter.py +35 -0
- react_manip/robots/agilex_description.py +74 -0
- react_manip/robots/agilex_robosuite.py +246 -0
- react_manip/robots/arm_actions.py +17 -0
- react_manip/robots/base.py +119 -0
- react_manip/robots/catalog.py +18 -0
- react_manip/robots/legacy_agilex_robosuite.py +169 -0
- react_manip/robots/mobile_base.py +33 -0
- react_manip/robots/mounts.py +80 -0
- react_manip/robots/resolution.py +103 -0
- react_manip/robots/sawyer_robosuite.py +147 -0
- react_manip/robots/sensors.py +137 -0
- react_manip/utils/__init__.py +1 -0
- react_manip/visual_task.py +50 -0
- react_manip-0.1.0.dist-info/METADATA +446 -0
- react_manip-0.1.0.dist-info/RECORD +99 -0
- react_manip-0.1.0.dist-info/WHEEL +5 -0
- react_manip-0.1.0.dist-info/entry_points.txt +2 -0
- react_manip-0.1.0.dist-info/licenses/LICENSE +131 -0
- react_manip-0.1.0.dist-info/top_level.txt +5 -0
- scripts/add_phone_lean_to_bank.py +242 -0
- scripts/agilex_wheeled_builder.py +133 -0
- scripts/audit_task_solvability.py +150 -0
- scripts/build_agilex_assets.py +317 -0
- scripts/build_legacy_agilex_assets.py +244 -0
- scripts/evaluate_all_tasks.py +309 -0
- scripts/evaluate_policy.py +281 -0
- scripts/piperx_assets.lock.json +181 -0
- scripts/pygame_demo.py +360 -0
- scripts/pygame_task_debug.py +242 -0
- scripts/regenerate_instance_bank.py +192 -0
- scripts/remove_task_from_bank.py +103 -0
- scripts/select_task_instances.py +64 -0
- scripts/verify_curling_reachability.py +206 -0
- scripts/verify_piperx_tasks.py +81 -0
- tasks/box_insertion/__init__.py +3 -0
- tasks/box_insertion/assets/scene.xml +50 -0
- tasks/box_insertion/env.py +642 -0
- tasks/box_insertion/evaluation.json +16 -0
- tasks/box_insertion/instance_contract.json +321 -0
- tasks/box_insertion/scoring.py +23 -0
- tasks/box_insertion/task_spec.json +333 -0
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
"""Physics-based robot box insertion task.
|
|
2
|
+
|
|
3
|
+
The block is a passive free rigid body. Its position changes only when contact
|
|
4
|
+
forces from the robot end-effector are integrated by MuJoCo; the environment
|
|
5
|
+
never writes the block joint ``qpos`` / ``qvel`` during ``step``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import json
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import mujoco
|
|
15
|
+
import numpy as np
|
|
16
|
+
from robosuite.controllers import load_composite_controller_config
|
|
17
|
+
from robosuite.environments.manipulation.manipulation_env import ManipulationEnv
|
|
18
|
+
from robosuite.models.arenas import Arena
|
|
19
|
+
from react_manip.visual_task import ManipulationTask
|
|
20
|
+
|
|
21
|
+
from react_manip.core import SuccessRule, TaskSpec
|
|
22
|
+
from react_manip.core.scenarios import sample_scenario
|
|
23
|
+
from react_manip.core.visual_randomization import apply_visual_scenario, resolve_scenario
|
|
24
|
+
|
|
25
|
+
ROOT = Path(__file__).resolve().parent
|
|
26
|
+
SCENE_XML = ROOT / "assets" / "scene.xml"
|
|
27
|
+
BLOCK_FRICTION_PATCHES = ("left", "right")
|
|
28
|
+
MIN_SLOT_CLEARANCE_M = 0.05 # total opening margin; 2.5 cm nominal per side
|
|
29
|
+
MIN_INITIAL_GRIPPER_CLEARANCE_M = 0.05
|
|
30
|
+
|
|
31
|
+
SPEC = TaskSpec(
|
|
32
|
+
task_id="box_insertion", task_version="1.7", task_type="one_shot",
|
|
33
|
+
required_arms=1, required_gripper="parallel", required_eef_dof=6,
|
|
34
|
+
supported_robots=("dual_piperx", "panda", "sawyer", "piperx", "mobile_dual_piperx"),
|
|
35
|
+
horizon_sec=60.0, control_hz=20.0, done_behavior="stop",
|
|
36
|
+
success=SuccessRule(min_occupancy=0.0, min_events=1, max_failures=0,
|
|
37
|
+
require_terminal_success=True),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BoxInsertionArena(Arena):
|
|
42
|
+
def __init__(self, scenario=None):
|
|
43
|
+
super().__init__(str(SCENE_XML))
|
|
44
|
+
self.set_origin([0, 0, 0])
|
|
45
|
+
resolved = resolve_scenario(scenario)
|
|
46
|
+
parameters = resolved.parameters if resolved is not None else {}
|
|
47
|
+
sampled = parameters.get("block_friction", {})
|
|
48
|
+
geoms = {geom.get("name"): geom for geom in self.worldbody.iter("geom")}
|
|
49
|
+
physics = parameters.get("physics", {})
|
|
50
|
+
# Priority 1 makes the wall coefficient the actual contact coefficient,
|
|
51
|
+
# independent of either block patch's table-contact friction.
|
|
52
|
+
for name, key, fallback in (
|
|
53
|
+
("table", "table_friction", 0.005),
|
|
54
|
+
("box_left", "slot_left_wall_friction", physics.get("slot_side_wall_friction", 3.0)),
|
|
55
|
+
("box_right", "slot_right_wall_friction", physics.get("slot_side_wall_friction", 3.0)),
|
|
56
|
+
("box_back", "slot_back_wall_friction", 0.02),
|
|
57
|
+
):
|
|
58
|
+
coefficient = float(physics.get(key, fallback))
|
|
59
|
+
if not np.isfinite(coefficient) or coefficient <= 0.0:
|
|
60
|
+
raise ValueError(f"invalid {key}: {coefficient}")
|
|
61
|
+
friction = geoms[name].get("friction").split()
|
|
62
|
+
friction[0] = f"{coefficient:.12g}"
|
|
63
|
+
geoms[name].set("friction", " ".join(friction))
|
|
64
|
+
density = parameters.get("block_density")
|
|
65
|
+
patch_densities = dict.fromkeys(BLOCK_FRICTION_PATCHES, 650.0)
|
|
66
|
+
if density is not None:
|
|
67
|
+
light = float(density["light_kg_m3"])
|
|
68
|
+
heavy = float(density["heavy_kg_m3"])
|
|
69
|
+
heavy_side = density["heavy_side"]
|
|
70
|
+
if (not np.isfinite([light, heavy]).all() or not 0 < light < heavy
|
|
71
|
+
or heavy_side not in BLOCK_FRICTION_PATCHES):
|
|
72
|
+
raise ValueError(f"invalid block_density: {density}")
|
|
73
|
+
patch_densities = {
|
|
74
|
+
patch: heavy if patch == heavy_side else light
|
|
75
|
+
for patch in BLOCK_FRICTION_PATCHES
|
|
76
|
+
}
|
|
77
|
+
variation = resolved.metadata.get('episode_variation', {}) if resolved else {}
|
|
78
|
+
mass_scale = float(variation.get('mass_scale', 1.))
|
|
79
|
+
if not np.isfinite(mass_scale) or not .7 <= mass_scale <= 1.3:
|
|
80
|
+
raise ValueError('invalid Episode block mass scale')
|
|
81
|
+
# Uniform density scaling changes mass and inertia together, preserving COM.
|
|
82
|
+
patch_densities = {patch: value * mass_scale for patch, value in patch_densities.items()}
|
|
83
|
+
self.block_patch_density = np.array(list(patch_densities.values()))
|
|
84
|
+
block_size = np.asarray(geoms["block_visual"].get("size").split(), dtype=float)
|
|
85
|
+
block_params = parameters.get("wood_block", {})
|
|
86
|
+
width = float(block_params.get("width_m", 2.0 * block_size[1]))
|
|
87
|
+
if not np.isfinite(width) or not 0.60 <= width <= 1.00:
|
|
88
|
+
raise ValueError(f"wood block width must be in [0.60, 1.00] m, got {width}")
|
|
89
|
+
block_size[1] = width / 2.0
|
|
90
|
+
self.block_width = width
|
|
91
|
+
|
|
92
|
+
# The slot is derived from the sampled block width. Clearance is a
|
|
93
|
+
# total lateral margin (not per-side). Enforce the
|
|
94
|
+
# contract at construction time as well as in the Profile, so older
|
|
95
|
+
# saved Scenarios with the former 0.0002 m value cannot create a slot
|
|
96
|
+
# that is narrower than the required 5 cm margin.
|
|
97
|
+
box_params = parameters.get("box", {})
|
|
98
|
+
clearance = float(box_params.get("clearance_m", MIN_SLOT_CLEARANCE_M))
|
|
99
|
+
if not np.isfinite(clearance) or clearance <= 0.0:
|
|
100
|
+
raise ValueError(f"slot clearance must be positive, got {clearance}")
|
|
101
|
+
clearance = max(clearance, MIN_SLOT_CLEARANCE_M)
|
|
102
|
+
wall = float(box_params.get("wall", 0.02))
|
|
103
|
+
if not np.isfinite(wall) or wall <= 0.0:
|
|
104
|
+
raise ValueError(f"slot wall thickness must be positive, got {wall}")
|
|
105
|
+
opening = width + clearance
|
|
106
|
+
outer_width = opening + 2.0 * wall
|
|
107
|
+
self.slot_opening_width = opening
|
|
108
|
+
self.slot_outer_width = outer_width
|
|
109
|
+
self.slot_clearance = clearance
|
|
110
|
+
side_half_thickness = wall / 2.0
|
|
111
|
+
side_center = opening / 2.0 + side_half_thickness
|
|
112
|
+
geoms["box_left"].set("pos", f"0 {-side_center:.9g} 0.035")
|
|
113
|
+
geoms["box_right"].set("pos", f"0 {side_center:.9g} 0.035")
|
|
114
|
+
geoms["box_left"].set("size", f"0.16 {side_half_thickness:.9g} 0.035")
|
|
115
|
+
geoms["box_right"].set("size", f"0.16 {side_half_thickness:.9g} 0.035")
|
|
116
|
+
back = geoms["box_back"]
|
|
117
|
+
back_size = np.asarray(back.get("size").split(), dtype=float)
|
|
118
|
+
back_size[1] = outer_width / 2.0
|
|
119
|
+
back.set("size", " ".join(f"{value:.9g}" for value in back_size))
|
|
120
|
+
for site in self.worldbody.iter("site"):
|
|
121
|
+
if site.get("name") == "box_entry":
|
|
122
|
+
site_size = np.asarray(site.get("size").split(), dtype=float)
|
|
123
|
+
site_size[1] = opening / 2.0
|
|
124
|
+
site.set("size", " ".join(f"{value:.9g}" for value in site_size))
|
|
125
|
+
|
|
126
|
+
half_width = float(block_size[1])
|
|
127
|
+
full_width = 2.0 * half_width
|
|
128
|
+
split_from_left = sampled.get("split_from_left_m")
|
|
129
|
+
if split_from_left is None:
|
|
130
|
+
# New profiles sample a width-independent ratio. This keeps the
|
|
131
|
+
# historical 20--80% split rule valid for both 0.60 m and 1.00 m
|
|
132
|
+
# blocks. Scenarios saved before this change may still provide a
|
|
133
|
+
# ratio or an absolute split distance.
|
|
134
|
+
split_from_left = full_width * float(sampled.get("split_ratio", 0.5))
|
|
135
|
+
split_from_left = float(split_from_left)
|
|
136
|
+
if (not np.isfinite(split_from_left)
|
|
137
|
+
or not 0.0 < split_from_left < full_width):
|
|
138
|
+
raise ValueError(
|
|
139
|
+
"invalid block friction split distance from left edge: "
|
|
140
|
+
f"{split_from_left} m for width {full_width} m"
|
|
141
|
+
)
|
|
142
|
+
split_y = -half_width + split_from_left
|
|
143
|
+
bounds = ((-half_width, split_y), (split_y, half_width))
|
|
144
|
+
for patch, (lower, upper) in zip(BLOCK_FRICTION_PATCHES, bounds):
|
|
145
|
+
coefficient = float(sampled.get(patch, 0.10))
|
|
146
|
+
if not np.isfinite(coefficient) or coefficient <= 0.0:
|
|
147
|
+
raise ValueError(f"invalid block friction for {patch}: {coefficient}")
|
|
148
|
+
geom = geoms[f"block_contact_{patch}"]
|
|
149
|
+
geom.set("pos", f"0 {(lower + upper) / 2.0:.9g} 0")
|
|
150
|
+
geom.set(
|
|
151
|
+
"size",
|
|
152
|
+
f"{block_size[0]:.9g} {(upper - lower) / 2.0:.9g} {block_size[2]:.9g}",
|
|
153
|
+
)
|
|
154
|
+
geom.set("friction", f"{coefficient:.9g} 0.01 0.001")
|
|
155
|
+
# MuJoCo derives one body's mass, offset COM and inertia from the
|
|
156
|
+
# two disjoint solid regions; the visual geom has zero mass.
|
|
157
|
+
geom.set("density", f"{patch_densities[patch]:.12g}")
|
|
158
|
+
geoms["block_visual"].set("size", " ".join(f"{value:.9g}" for value in block_size))
|
|
159
|
+
apply_visual_scenario(self, scenario)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _default_controller():
|
|
163
|
+
config = load_composite_controller_config(controller="BASIC")
|
|
164
|
+
config["body_parts"]["right"] = {
|
|
165
|
+
"type": "OSC_POSE", "input_max": 1, "input_min": -1,
|
|
166
|
+
"output_max": [0.015, 0.015, 0.015, 0.25, 0.25, 0.25],
|
|
167
|
+
"output_min": [-0.015, -0.015, -0.015, -0.25, -0.25, -0.25],
|
|
168
|
+
"kp": 190, "damping_ratio": 1.5, "impedance_mode": "fixed",
|
|
169
|
+
"kp_limits": [0, 300], "damping_ratio_limits": [0, 10],
|
|
170
|
+
"position_limits": None, "orientation_limits": None,
|
|
171
|
+
"uncouple_pos_ori": True, "input_type": "delta",
|
|
172
|
+
"input_ref_frame": "world", "interpolation": None,
|
|
173
|
+
"ramp_ratio": 0.1, "gripper": {"type": "GRIP"},
|
|
174
|
+
}
|
|
175
|
+
return config
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class BoxInsertion(ManipulationEnv):
|
|
179
|
+
privileged_info_keys = (
|
|
180
|
+
'task_seed', 'block_patch_friction', 'block_patch_density_kg_m3',
|
|
181
|
+
'block_mass_kg', 'block_com_local_m', 'slot_wall_friction',
|
|
182
|
+
'block_patch_width_m', 'block_width_m', 'slot_opening_width_m',
|
|
183
|
+
'slot_clearance_m', 'block_friction_split_y_m', 'block_friction_split_from_left_m',
|
|
184
|
+
)
|
|
185
|
+
privileged_observation_keys = privileged_info_keys + (
|
|
186
|
+
'block_pose', 'block_velocity', 'block_angular_velocity', 'eef_to_push_face',
|
|
187
|
+
)
|
|
188
|
+
"""Single-arm EEF controller that physically pushes a block into a box."""
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def action_dim(self):
|
|
192
|
+
"""Expose robosuite's compiled layout (7 single-arm / 14 bimanual)."""
|
|
193
|
+
return int(getattr(self, "_action_dim", 7))
|
|
194
|
+
# Pygame must not inject an additional _pre_action call: robosuite's base
|
|
195
|
+
# step loop already applies this action at every physics substep.
|
|
196
|
+
step_handles_robot_action = True
|
|
197
|
+
task_space_active_kp = 180.0
|
|
198
|
+
task_space_passive_kp = 20.0
|
|
199
|
+
task_space_rotation_kp = 8.0
|
|
200
|
+
_TARGET_DEPTH = 0.36
|
|
201
|
+
_BOTTOM_TOLERANCE = 0.005
|
|
202
|
+
# MuJoCo's resting contact permits sub-millimetre penetration into the
|
|
203
|
+
# tabletop. Keep this separate from the much stricter bottom-depth rule.
|
|
204
|
+
_CONTAINMENT_TOLERANCE = 0.0005
|
|
205
|
+
_STABLE_SPEED = 0.025
|
|
206
|
+
_STABLE_ANGULAR_SPEED = 0.08
|
|
207
|
+
_STABLE_TICKS = 10
|
|
208
|
+
# Default geometry (1.00 m block + 0.05 m total clearance). Each Scenario
|
|
209
|
+
# replaces this with its sampled width-derived bounds at model load.
|
|
210
|
+
_SLOT_BOUNDS = (0.10, 0.40, -0.5001, 0.5001)
|
|
211
|
+
_SLOT_Z_BOUNDS = (0.78, 0.85)
|
|
212
|
+
|
|
213
|
+
def reward(self, action=None):
|
|
214
|
+
"""Robosuite hook; task scoring is computed in :meth:`step`.
|
|
215
|
+
|
|
216
|
+
Returning a finite scalar keeps the base environment's stepping loop
|
|
217
|
+
intact while avoiding any hidden state writes or legacy object-state
|
|
218
|
+
reward assumptions.
|
|
219
|
+
"""
|
|
220
|
+
del action
|
|
221
|
+
return 0.0
|
|
222
|
+
|
|
223
|
+
def _task_instruction(self):
|
|
224
|
+
return (
|
|
225
|
+
"Push the free wooden block into the open box slot. "
|
|
226
|
+
"Block contact friction and overall mass vary between Episodes but remain fixed within each Episode. "
|
|
227
|
+
"The block and slot dimensions and the center-of-mass bias pattern stay the same within a Task Instance. "
|
|
228
|
+
"Use observed motion to adjust pushes and correct rotation. "
|
|
229
|
+
"The block must remain inside the slot, reach the required insertion "
|
|
230
|
+
"depth near the back wall, and settle without crossing the side walls. "
|
|
231
|
+
f"Keep it stable for {self._STABLE_TICKS / self.control_freq:g} seconds. "
|
|
232
|
+
"Final-position points are at most 50; success earns up to 50 time points: "
|
|
233
|
+
"full through 10 seconds, linearly decreasing to zero at 40 seconds. "
|
|
234
|
+
f"The deadline is {self.horizon / self.control_freq:g} seconds. "
|
|
235
|
+
"Position points use the final pose, not the maximum earlier insertion. "
|
|
236
|
+
"Invalid side, back or vertical containment gives zero position points; partial entry is allowed for partial credit. "
|
|
237
|
+
"Falling below the table ends the episode with zero. Success ends immediately; timeout retains final-position points."
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def get_episode_language_prompt(self):
|
|
241
|
+
from react_manip.core.language import task_language_prompt
|
|
242
|
+
return task_language_prompt(
|
|
243
|
+
self, task_id='box_insertion',
|
|
244
|
+
task_instruction=self._task_instruction(),
|
|
245
|
+
success_condition=(f"All eight corners contained, insertion depth at least {self._TARGET_DEPTH - self._BOTTOM_TOLERANCE:g} m, "
|
|
246
|
+
f"linear speed <={self._STABLE_SPEED:g} m/s and angular speed <={self._STABLE_ANGULAR_SPEED:g} rad/s "
|
|
247
|
+
f"for {self._STABLE_TICKS} ticks"),
|
|
248
|
+
constraints=(),
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
def __init__(self, robots="dual_piperx", env_configuration="default",
|
|
252
|
+
controller_configs=None, gripper_types="default",
|
|
253
|
+
base_types="default", initialization_noise=None,
|
|
254
|
+
use_camera_obs=False, use_object_obs=False,
|
|
255
|
+
reward_scale=1.0, reward_shaping=False, has_renderer=False,
|
|
256
|
+
has_offscreen_renderer=False, render_camera="overview",
|
|
257
|
+
render_collision_mesh=False, render_visual_mesh=True,
|
|
258
|
+
render_gpu_device_id=-1, control_freq=20, lite_physics=False,
|
|
259
|
+
horizon=1200, ignore_done=False, hard_reset=True,
|
|
260
|
+
camera_names="overview", camera_heights=256,
|
|
261
|
+
camera_widths=256, camera_depths=False,
|
|
262
|
+
camera_segmentations=None, renderer="mjviewer",
|
|
263
|
+
renderer_config=None, seed=None, scenario=None,
|
|
264
|
+
**kwargs: Any):
|
|
265
|
+
del kwargs
|
|
266
|
+
if robots in ('dual_piperx', 'piperx', 'mobile_dual_piperx', 'panda', 'sawyer', 'legacy_piperx', 'aloha', 'aloha_mobile'):
|
|
267
|
+
from react_manip.robots.resolution import resolve_robosuite_robot
|
|
268
|
+
resolved = resolve_robosuite_robot(robots)
|
|
269
|
+
robots = resolved.robot_name
|
|
270
|
+
if controller_configs is None:
|
|
271
|
+
controller_configs = resolved.controller_configs
|
|
272
|
+
if scenario is None:
|
|
273
|
+
profile = json.loads((ROOT.parents[1] / "configs/box_insertion/profile_default.json").read_text())
|
|
274
|
+
scenario = sample_scenario(profile, seed=0 if seed is None else seed)
|
|
275
|
+
self.scenario = resolve_scenario(scenario)
|
|
276
|
+
self.seed = seed
|
|
277
|
+
self.use_object_obs = use_object_obs
|
|
278
|
+
self.reward_scale = float(reward_scale)
|
|
279
|
+
self.reward_shaping = bool(reward_shaping)
|
|
280
|
+
self._stable_ticks = 0
|
|
281
|
+
self._max_depth_seen = 0.0
|
|
282
|
+
self._events: list[str] = []
|
|
283
|
+
self._failure_reason: str | None = None
|
|
284
|
+
if controller_configs is None:
|
|
285
|
+
controller_configs = _default_controller()
|
|
286
|
+
super().__init__(
|
|
287
|
+
robots=robots, env_configuration=env_configuration,
|
|
288
|
+
controller_configs=controller_configs, base_types=base_types,
|
|
289
|
+
gripper_types=gripper_types, initialization_noise=initialization_noise,
|
|
290
|
+
use_camera_obs=use_camera_obs,
|
|
291
|
+
has_renderer=has_renderer, has_offscreen_renderer=has_offscreen_renderer,
|
|
292
|
+
render_camera=render_camera, render_collision_mesh=render_collision_mesh,
|
|
293
|
+
render_visual_mesh=render_visual_mesh,
|
|
294
|
+
render_gpu_device_id=render_gpu_device_id, control_freq=control_freq,
|
|
295
|
+
lite_physics=lite_physics, horizon=horizon, ignore_done=ignore_done,
|
|
296
|
+
hard_reset=hard_reset, camera_names=camera_names,
|
|
297
|
+
camera_heights=camera_heights, camera_widths=camera_widths,
|
|
298
|
+
camera_depths=camera_depths, camera_segmentations=camera_segmentations,
|
|
299
|
+
renderer=renderer, renderer_config=renderer_config, seed=seed,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
def _load_model(self):
|
|
303
|
+
super()._load_model()
|
|
304
|
+
robot_model = self.robots[0].robot_model
|
|
305
|
+
if robot_model.__class__.__name__ in ("MobileDualPiperX", "AgileXMobileAloha"):
|
|
306
|
+
robot_model.set_base_xpos([-0.72, 0.0, 0.0])
|
|
307
|
+
elif robot_model.__class__.__name__ == "Panda":
|
|
308
|
+
robot_model.set_base_xpos([-0.5, 0.0, -0.1])
|
|
309
|
+
elif robot_model.__class__.__name__ in ("PiperX", "DualPiperX", "AgileXPiper", "AgileXAloha"):
|
|
310
|
+
from react_manip.robots.mounts import set_fixed_mount_pose
|
|
311
|
+
piper = robot_model.__class__.__name__ in ("PiperX", "AgileXPiper")
|
|
312
|
+
height = .62 if piper else .55
|
|
313
|
+
# Preserve calibrated arm world poses without sinking the stand
|
|
314
|
+
# root below the floor to lower the mounting plane.
|
|
315
|
+
set_fixed_mount_pose(robot_model, position=[-.38 if piper else -.62, 0., height],
|
|
316
|
+
yaw=0., height=height)
|
|
317
|
+
else:
|
|
318
|
+
robot_model.set_base_xpos([-0.62, 0.0, -0.1])
|
|
319
|
+
if robot_model.__class__.__name__ == "PiperX":
|
|
320
|
+
# The shared start pose places the fingertips inside this task's
|
|
321
|
+
# wide block. Raise/retract the shoulder before controller reset;
|
|
322
|
+
# leave the block, fixture, and robot mounting pose unchanged.
|
|
323
|
+
self.robots[0].init_qpos = np.array([0., .8, -1.1, 1.2, 0., 0.])
|
|
324
|
+
arena = BoxInsertionArena(self.scenario)
|
|
325
|
+
self._block_patch_density = arena.block_patch_density.copy()
|
|
326
|
+
self._block_width_m = arena.block_width
|
|
327
|
+
self._slot_opening_width_m = arena.slot_opening_width
|
|
328
|
+
self._slot_clearance_m = arena.slot_clearance
|
|
329
|
+
self._slot_bounds = (0.10, 0.40, -arena.slot_opening_width / 2.0,
|
|
330
|
+
arena.slot_opening_width / 2.0)
|
|
331
|
+
self.model = ManipulationTask(
|
|
332
|
+
mujoco_arena=arena,
|
|
333
|
+
mujoco_robots=[r.robot_model for r in self.robots],
|
|
334
|
+
mujoco_objects=[],
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def _setup_references(self):
|
|
338
|
+
super()._setup_references()
|
|
339
|
+
model = self.sim.model
|
|
340
|
+
self.block_body_id = model.body_name2id("wood_block")
|
|
341
|
+
self._block_mass_kg = float(model.body_mass[self.block_body_id])
|
|
342
|
+
self._block_com_local_m = np.asarray(model.body_ipos[self.block_body_id]).copy()
|
|
343
|
+
self._slot_wall_friction = np.array([
|
|
344
|
+
model.geom_friction[model.geom_name2id(name), 0]
|
|
345
|
+
for name in ("box_left", "box_right", "box_back")
|
|
346
|
+
])
|
|
347
|
+
self._block_geom_ids = {
|
|
348
|
+
model.geom_name2id(f"block_contact_{patch}")
|
|
349
|
+
for patch in BLOCK_FRICTION_PATCHES
|
|
350
|
+
}
|
|
351
|
+
# Kept for compatibility with external diagnostics that previously
|
|
352
|
+
# expected one representative collision geom.
|
|
353
|
+
self.block_geom_id = min(self._block_geom_ids)
|
|
354
|
+
visual_geom_id = model.geom_name2id("block_visual")
|
|
355
|
+
self.block_joint_id = model.joint_name2id("block_free")
|
|
356
|
+
self.block_qpos_addr = int(model.jnt_qposadr[self.block_joint_id])
|
|
357
|
+
self.block_qvel_addr = int(model.jnt_dofadr[self.block_joint_id])
|
|
358
|
+
self._block_start_pos = np.asarray(model.body_pos[self.block_body_id], dtype=float).copy()
|
|
359
|
+
self._block_half_length = float(model.geom_size[visual_geom_id, 0])
|
|
360
|
+
self._block_half_width = float(model.geom_size[visual_geom_id, 1])
|
|
361
|
+
self._block_half_height = float(model.geom_size[visual_geom_id, 2])
|
|
362
|
+
self._block_patch_friction = np.asarray([
|
|
363
|
+
model.geom_friction[model.geom_name2id(f"block_contact_{patch}"), 0]
|
|
364
|
+
for patch in BLOCK_FRICTION_PATCHES
|
|
365
|
+
], dtype=float)
|
|
366
|
+
patch_geom_ids = [
|
|
367
|
+
model.geom_name2id(f"block_contact_{patch}")
|
|
368
|
+
for patch in BLOCK_FRICTION_PATCHES
|
|
369
|
+
]
|
|
370
|
+
self._block_patch_width_m = np.asarray(
|
|
371
|
+
[2.0 * model.geom_size[geom_id, 1] for geom_id in patch_geom_ids],
|
|
372
|
+
dtype=float,
|
|
373
|
+
)
|
|
374
|
+
left_geom_id = patch_geom_ids[0]
|
|
375
|
+
self._block_friction_split_y_m = float(
|
|
376
|
+
model.geom_pos[left_geom_id, 1] + model.geom_size[left_geom_id, 1]
|
|
377
|
+
)
|
|
378
|
+
self._block_friction_split_from_left_m = float(
|
|
379
|
+
self._block_friction_split_y_m + self._block_half_width
|
|
380
|
+
)
|
|
381
|
+
self._gripper_geom_ids = set()
|
|
382
|
+
for gripper in self.robots[0].gripper.values():
|
|
383
|
+
for name in gripper.contact_geoms:
|
|
384
|
+
try:
|
|
385
|
+
self._gripper_geom_ids.add(model.geom_name2id(name))
|
|
386
|
+
except (ValueError, KeyError):
|
|
387
|
+
pass
|
|
388
|
+
sites = self.robots[0].eef_site_id
|
|
389
|
+
self.eef_site_id = sites["right"] if isinstance(sites, dict) else int(sites)
|
|
390
|
+
gripper_indexes = self.robots[0]._ref_gripper_joint_pos_indexes
|
|
391
|
+
if isinstance(gripper_indexes, dict):
|
|
392
|
+
gripper_indexes = next(iter(gripper_indexes.values()), [])
|
|
393
|
+
self._gripper_qpos = np.asarray(gripper_indexes, dtype=int)
|
|
394
|
+
|
|
395
|
+
def _block_depth(self):
|
|
396
|
+
displacement = self._block_pose()[0] - self._block_start_pos[0]
|
|
397
|
+
return float(np.clip(displacement, 0.0, self._TARGET_DEPTH))
|
|
398
|
+
|
|
399
|
+
def _block_pose(self):
|
|
400
|
+
return np.asarray(self.sim.data.xpos[self.block_body_id], dtype=float).copy()
|
|
401
|
+
|
|
402
|
+
def _block_velocity(self):
|
|
403
|
+
# cvel is angular then linear in the body frame; this is sufficient
|
|
404
|
+
# for the low-speed stability criterion after converting to world.
|
|
405
|
+
return np.asarray(self.sim.data.cvel[self.block_body_id, 3:6], dtype=float).copy()
|
|
406
|
+
|
|
407
|
+
def _block_angular_velocity(self):
|
|
408
|
+
return np.asarray(self.sim.data.cvel[self.block_body_id, :3], dtype=float).copy()
|
|
409
|
+
|
|
410
|
+
def _block_yaw(self):
|
|
411
|
+
rotation = np.asarray(self.sim.data.xmat[self.block_body_id], dtype=float).reshape(3, 3)
|
|
412
|
+
return float(np.arctan2(rotation[1, 0], rotation[0, 0]))
|
|
413
|
+
|
|
414
|
+
def _block_corners(self):
|
|
415
|
+
"""World corners of the rotated visual bounding box."""
|
|
416
|
+
center = self._block_pose()
|
|
417
|
+
rotation = np.asarray(self.sim.data.xmat[self.block_body_id], dtype=float).reshape(3, 3)
|
|
418
|
+
local = np.array([
|
|
419
|
+
[sx * self._block_half_length, sy * self._block_half_width, sz * self._block_half_height]
|
|
420
|
+
for sx in (-1.0, 1.0) for sy in (-1.0, 1.0) for sz in (-1.0, 1.0)
|
|
421
|
+
])
|
|
422
|
+
return center + local @ rotation.T
|
|
423
|
+
|
|
424
|
+
def _block_contained(self):
|
|
425
|
+
"""Whether every rotated 3D corner of the block is inside the slot."""
|
|
426
|
+
corners = self._block_corners()
|
|
427
|
+
xmin, xmax, ymin, ymax = self._slot_bounds
|
|
428
|
+
zmin, zmax = self._SLOT_Z_BOUNDS
|
|
429
|
+
tolerance = self._CONTAINMENT_TOLERANCE
|
|
430
|
+
return bool(
|
|
431
|
+
np.all(corners[:, 0] >= xmin - tolerance)
|
|
432
|
+
and np.all(corners[:, 0] <= xmax + tolerance)
|
|
433
|
+
and np.all(corners[:, 1] >= ymin - tolerance)
|
|
434
|
+
and np.all(corners[:, 1] <= ymax + tolerance)
|
|
435
|
+
and np.all(corners[:, 2] >= zmin - tolerance)
|
|
436
|
+
and np.all(corners[:, 2] <= zmax + tolerance)
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
def _bottom_reached(self):
|
|
440
|
+
"""Whether the block center is within tolerance of the rear wall."""
|
|
441
|
+
return bool(
|
|
442
|
+
self._block_depth() >= self._TARGET_DEPTH - self._BOTTOM_TOLERANCE
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
def _success_blockers(self, *, speed=None, angular_speed=None):
|
|
446
|
+
"""Return explicit reasons why the current state is not terminal success.
|
|
447
|
+
|
|
448
|
+
A block can touch the rear wall while still being yawed or pressed
|
|
449
|
+
against a side wall. Reporting each independent condition makes that
|
|
450
|
+
physically meaningful distinction visible to interactive users and
|
|
451
|
+
benchmark logs instead of reducing it to a mysterious false result.
|
|
452
|
+
"""
|
|
453
|
+
if speed is None:
|
|
454
|
+
speed = float(np.linalg.norm(self._block_velocity()))
|
|
455
|
+
if angular_speed is None:
|
|
456
|
+
angular_speed = float(np.linalg.norm(self._block_angular_velocity()))
|
|
457
|
+
blockers = []
|
|
458
|
+
if not self._block_contained():
|
|
459
|
+
blockers.append("block_not_contained")
|
|
460
|
+
if not self._bottom_reached():
|
|
461
|
+
blockers.append("slot_bottom_not_reached")
|
|
462
|
+
if speed > self._STABLE_SPEED:
|
|
463
|
+
blockers.append("linear_motion_not_stable")
|
|
464
|
+
if angular_speed > self._STABLE_ANGULAR_SPEED:
|
|
465
|
+
blockers.append("angular_motion_not_stable")
|
|
466
|
+
return blockers
|
|
467
|
+
|
|
468
|
+
def _contact_force(self):
|
|
469
|
+
peak = 0.0
|
|
470
|
+
for index in range(self.sim.data.ncon):
|
|
471
|
+
contact = self.sim.data.contact[index]
|
|
472
|
+
geom1, geom2 = int(contact.geom1), int(contact.geom2)
|
|
473
|
+
if geom1 not in self._block_geom_ids and geom2 not in self._block_geom_ids:
|
|
474
|
+
continue
|
|
475
|
+
other = geom2 if geom1 in self._block_geom_ids else geom1
|
|
476
|
+
if other not in self._gripper_geom_ids:
|
|
477
|
+
continue
|
|
478
|
+
force = np.zeros(6, dtype=float)
|
|
479
|
+
mujoco.mj_contactForce(self.sim.model._model, self.sim.data._data, index, force)
|
|
480
|
+
peak = max(peak, float(np.linalg.norm(force[:3])))
|
|
481
|
+
return peak
|
|
482
|
+
|
|
483
|
+
def _observation(self):
|
|
484
|
+
data = self.sim.data
|
|
485
|
+
eef_pos = np.asarray(data.site_xpos[self.eef_site_id], dtype=np.float32).copy()
|
|
486
|
+
eef_mat = np.asarray(data.site_xmat[self.eef_site_id], dtype=np.float32).copy()
|
|
487
|
+
block_pose = self._block_pose().astype(np.float32)
|
|
488
|
+
return {
|
|
489
|
+
"robot_qpos": data.qpos[self.robots[0]._ref_joint_pos_indexes].astype(np.float32).copy(),
|
|
490
|
+
"robot_qvel": data.qvel[self.robots[0]._ref_joint_vel_indexes].astype(np.float32).copy(),
|
|
491
|
+
"eef_pos": eef_pos, "eef_mat": eef_mat,
|
|
492
|
+
"gripper_qpos": data.qpos[self._gripper_qpos].astype(np.float32).copy(),
|
|
493
|
+
"block_pose": block_pose,
|
|
494
|
+
"block_velocity": self._block_velocity().astype(np.float32),
|
|
495
|
+
"block_angular_velocity": self._block_angular_velocity().astype(np.float32),
|
|
496
|
+
"block_patch_friction": self._block_patch_friction.astype(np.float32).copy(),
|
|
497
|
+
"block_patch_density_kg_m3": self._block_patch_density.astype(np.float32).copy(),
|
|
498
|
+
"block_mass_kg": np.array([self._block_mass_kg], dtype=np.float32),
|
|
499
|
+
"block_com_local_m": self._block_com_local_m.astype(np.float32).copy(),
|
|
500
|
+
"slot_wall_friction": self._slot_wall_friction.astype(np.float32).copy(),
|
|
501
|
+
"block_patch_width_m": self._block_patch_width_m.astype(np.float32).copy(),
|
|
502
|
+
"block_width_m": np.array([self._block_width_m], dtype=np.float32),
|
|
503
|
+
"slot_opening_width_m": np.array([self._slot_opening_width_m], dtype=np.float32),
|
|
504
|
+
"slot_clearance_m": np.array([self._slot_clearance_m], dtype=np.float32),
|
|
505
|
+
"block_friction_split_y_m": np.array(
|
|
506
|
+
[self._block_friction_split_y_m], dtype=np.float32
|
|
507
|
+
),
|
|
508
|
+
"block_friction_split_from_left_m": np.array(
|
|
509
|
+
[self._block_friction_split_from_left_m], dtype=np.float32
|
|
510
|
+
),
|
|
511
|
+
"block_lateral_m": np.array([block_pose[1] - self._block_start_pos[1]], dtype=np.float32),
|
|
512
|
+
"block_yaw_rad": np.array([self._block_yaw()], dtype=np.float32),
|
|
513
|
+
"block_contained": np.array([self._block_contained()], dtype=np.bool_),
|
|
514
|
+
"bottom_reached": np.array([self._bottom_reached()], dtype=np.bool_),
|
|
515
|
+
"insertion_depth_m": np.array([self._block_depth()], dtype=np.float32),
|
|
516
|
+
"insertion_fraction": np.array([self._block_depth() / self._TARGET_DEPTH], dtype=np.float32),
|
|
517
|
+
"eef_to_push_face": block_pose - eef_pos + np.array([-self._block_half_length, 0, 0], dtype=np.float32),
|
|
518
|
+
"contact_force_N": np.array([self._contact_force()], dtype=np.float32),
|
|
519
|
+
"time_remaining_sec": np.array([max(0.0, SPEC.horizon_sec - self.timestep / self.control_freq)], dtype=np.float32),
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
def _score_info(self):
|
|
523
|
+
from .scoring import score_state
|
|
524
|
+
depth = self._block_depth()
|
|
525
|
+
score = score_state(center=self._block_pose(), corners=self._block_corners(),
|
|
526
|
+
bounds=self._slot_bounds, z_bounds=self._SLOT_Z_BOUNDS,
|
|
527
|
+
half_length=self._block_half_length, elapsed=self.timestep/self.control_freq,
|
|
528
|
+
success=bool(self._events), failed=self._failure_reason is not None)
|
|
529
|
+
return {
|
|
530
|
+
**score,
|
|
531
|
+
"elapsed_sec": float(self.timestep / self.control_freq),
|
|
532
|
+
"insertion_depth_m": depth,
|
|
533
|
+
"max_insertion_depth_m": self._max_depth_seen,
|
|
534
|
+
"insertion_fraction": float(depth / self._TARGET_DEPTH),
|
|
535
|
+
"contact_peak_force_N": self._contact_force(),
|
|
536
|
+
"stable_ticks": float(self._stable_ticks),
|
|
537
|
+
"block_contained": self._block_contained(),
|
|
538
|
+
"bottom_reached": self._bottom_reached(),
|
|
539
|
+
"remaining_insertion_distance_m": float(
|
|
540
|
+
max(0.0, self._TARGET_DEPTH - depth)
|
|
541
|
+
),
|
|
542
|
+
"block_lateral_m": float(self._block_pose()[1] - self._block_start_pos[1]),
|
|
543
|
+
"block_yaw_rad": self._block_yaw(),
|
|
544
|
+
"block_patch_friction": self._block_patch_friction.tolist(),
|
|
545
|
+
"task_seed": self.scenario.seed,
|
|
546
|
+
"block_patch_density_kg_m3": self._block_patch_density.tolist(),
|
|
547
|
+
"block_mass_kg": self._block_mass_kg,
|
|
548
|
+
"block_com_local_m": self._block_com_local_m.tolist(),
|
|
549
|
+
"slot_wall_friction": self._slot_wall_friction.tolist(),
|
|
550
|
+
"block_patch_width_m": self._block_patch_width_m.tolist(),
|
|
551
|
+
"block_width_m": self._block_width_m,
|
|
552
|
+
"slot_opening_width_m": self._slot_opening_width_m,
|
|
553
|
+
"slot_clearance_m": self._slot_clearance_m,
|
|
554
|
+
"block_friction_split_y_m": self._block_friction_split_y_m,
|
|
555
|
+
"block_friction_split_from_left_m": self._block_friction_split_from_left_m,
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
def _reset_internal(self):
|
|
559
|
+
super()._reset_internal()
|
|
560
|
+
self._stable_ticks = 0
|
|
561
|
+
self._max_depth_seen = self._block_depth()
|
|
562
|
+
self._events = []
|
|
563
|
+
self._failure_reason = None
|
|
564
|
+
|
|
565
|
+
def _initial_gripper_clearance(self):
|
|
566
|
+
"""Minimum collision-surface gap to the block, slot, and tabletop."""
|
|
567
|
+
model, data = self.sim.model._model, self.sim.data._data
|
|
568
|
+
obstacles = [self.sim.model.geom_name2id(name) for name in (
|
|
569
|
+
"block_contact_left", "block_contact_right", "box_left",
|
|
570
|
+
"box_right", "box_back", "table",
|
|
571
|
+
)]
|
|
572
|
+
gripper_geoms = [self.sim.model.geom_name2id(name)
|
|
573
|
+
for gripper in self.robots[0].gripper.values()
|
|
574
|
+
for name in gripper.contact_geoms]
|
|
575
|
+
return min(mujoco.mj_geomDistance(model, data, hand, obstacle, 1., None)
|
|
576
|
+
for hand in gripper_geoms for obstacle in obstacles)
|
|
577
|
+
|
|
578
|
+
def reset(self, *, seed=None, **kwargs):
|
|
579
|
+
del kwargs
|
|
580
|
+
if seed is not None:
|
|
581
|
+
self.seed = seed
|
|
582
|
+
super().reset()
|
|
583
|
+
if self.robots[0].robot_model.__class__.__name__ == "PiperX":
|
|
584
|
+
clearance = self._initial_gripper_clearance()
|
|
585
|
+
if clearance < MIN_INITIAL_GRIPPER_CLEARANCE_M:
|
|
586
|
+
raise ValueError(f"Initial gripper clearance {clearance:.6f} m is below "
|
|
587
|
+
f"{MIN_INITIAL_GRIPPER_CLEARANCE_M:.3f} m")
|
|
588
|
+
return self._observation(), {
|
|
589
|
+
"success_state": False,
|
|
590
|
+
"success_blockers": self._success_blockers(),
|
|
591
|
+
"events": [],
|
|
592
|
+
**self._score_info(),
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
def step(self, action):
|
|
596
|
+
action = np.asarray(action, dtype=float).reshape(-1)
|
|
597
|
+
if action.shape != (self.action_dim,) or not np.isfinite(action).all():
|
|
598
|
+
raise ValueError(f"expected finite action shape {(self.action_dim,)}")
|
|
599
|
+
if self._failure_reason is not None or self._events:
|
|
600
|
+
raise RuntimeError("episode finished; call reset")
|
|
601
|
+
previous = self._score_info()
|
|
602
|
+
super().step(np.clip(action, -1.0, 1.0))
|
|
603
|
+
depth = self._block_depth()
|
|
604
|
+
self._max_depth_seen = max(self._max_depth_seen, depth)
|
|
605
|
+
speed = float(np.linalg.norm(self._block_velocity()))
|
|
606
|
+
angular_speed = float(np.linalg.norm(self._block_angular_velocity()))
|
|
607
|
+
completed = (
|
|
608
|
+
self._block_contained()
|
|
609
|
+
and self._bottom_reached()
|
|
610
|
+
and speed <= self._STABLE_SPEED
|
|
611
|
+
and angular_speed <= self._STABLE_ANGULAR_SPEED
|
|
612
|
+
)
|
|
613
|
+
self._stable_ticks = self._stable_ticks + 1 if completed else 0
|
|
614
|
+
tick_events: list[str] = []
|
|
615
|
+
if self._stable_ticks >= self._STABLE_TICKS:
|
|
616
|
+
self._events.append("block_inserted")
|
|
617
|
+
tick_events.append("block_inserted")
|
|
618
|
+
finite = bool(np.isfinite(self.sim.data.qpos).all() and np.isfinite(self.sim.data.qvel).all())
|
|
619
|
+
if not finite:
|
|
620
|
+
self._failure_reason = "non_finite_state"
|
|
621
|
+
elif self._block_pose()[2] < 0.65:
|
|
622
|
+
self._failure_reason = "block_fell_from_table"
|
|
623
|
+
if self._failure_reason is not None:
|
|
624
|
+
self._events.clear()
|
|
625
|
+
tick_events.clear()
|
|
626
|
+
current = self._score_info()
|
|
627
|
+
current_total = current["time_score"] + current["distance_score"]
|
|
628
|
+
previous_total = previous["time_score"] + previous["distance_score"]
|
|
629
|
+
current["total_score"] = float(current_total)
|
|
630
|
+
info = {
|
|
631
|
+
"success_state": bool(tick_events), "terminal_success": bool(tick_events),
|
|
632
|
+
"events": tick_events,
|
|
633
|
+
"failure": {"recoverable": False, "reason": self._failure_reason} if self._failure_reason else None,
|
|
634
|
+
"success_blockers": self._success_blockers(speed=speed, angular_speed=angular_speed),
|
|
635
|
+
"score_delta": float(current_total - previous_total), **current,
|
|
636
|
+
}
|
|
637
|
+
terminated = bool(tick_events or self._failure_reason)
|
|
638
|
+
truncated = bool(self.timestep >= self.horizon and not terminated)
|
|
639
|
+
return self._observation(), float(info["score_delta"]), terminated, truncated, info
|
|
640
|
+
|
|
641
|
+
def close(self):
|
|
642
|
+
super().close()
|