Skip to content

Commit d420294

Browse files
authored
fix(libero): per-env stove termination and corrected task registration ids (#786)
libero_kitchen_scene3_turn_on_the_stove._terminated did (stove_joint_state > threshold).all(dim=-1) on a (N,) tensor, collapsing the per-env result to a scalar (reducing over the batch). Drop .all to keep the (num_envs,) bool contract, matching the correct sibling scene9. Two pick tasks had registration-id typos: a misspelled alias ('pick_alphnabet_soup') and a primary id missing the 'pick_' prefix ('libero.orange_juice'). Correct both (no other file references the old ids; no collision). Adds general regression tests (per-env stove termination; registration ids).
1 parent 249103f commit d420294

4 files changed

Lines changed: 74 additions & 40 deletions

File tree

roboverse_pack/tasks/libero/libero_pick_alphabet_soup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from .libero_base import LiberoBaseTask
1010

1111

12-
@register_task("libero.pick_alphabet_soup", "pick_alphnabet_soup")
12+
@register_task("libero.pick_alphabet_soup", "pick_alphabet_soup")
1313
class LiberoPickAlphabetSoupTask(LiberoBaseTask):
1414
"""Configuration for the Libero pick alphabet soup task.
1515

roboverse_pack/tasks/libero/libero_pick_orange_juice.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from .libero_base import LiberoBaseTask
1010

1111

12-
@register_task("libero.orange_juice", "pick_orange_juice")
12+
@register_task("libero.pick_orange_juice", "pick_orange_juice")
1313
class LiberoPickOrangeJuiceCfg(LiberoBaseTask):
1414
"""Configuration for the Libero pick orange juice task.
1515

roboverse_pack/tasks/libero_90/libero_kitchen_scene3_turn_on_the_stove.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ def _terminated(self, states: TensorState) -> torch.Tensor:
8888
knob_index = 0 # 示例:第一个关节为旋钮
8989
threshold = 0.5 # 示例:大于 0.5 视为打开
9090
stove_joint_state = states.objects["flat_stove"].joint_pos[:, 0] # (N,)
91-
is_on = (stove_joint_state > threshold).all(dim=-1) # (N,)
91+
# joint_pos[:, 0] is already (N,); .all(dim=-1) collapsed it to a scalar
92+
# (reducing over the batch). Compare element-wise to keep per-env shape.
93+
is_on = stove_joint_state > threshold # (N,)
9294
return is_on
9395

9496
def reset(self, states=None, env_ids=None):

tests/test_libero_fixes.py

Lines changed: 69 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,83 @@
1-
"""Regression tests (backend-free) for the libero task fixes.
2-
3-
Covers two independent hardening fixes:
4-
5-
* ``Libero90BaseTask._get_initial_states`` must degrade to None (handler
6-
defaults) when the trajectory is missing/empty or the scenario is robotless,
7-
matching the already-hardened ``LiberoBaseTask``. Previously it indexed
8-
``scenario.robots[0]`` and called get_traj with no guard, so those cases
9-
crashed.
10-
* Every ``libero_pick_*`` task must declare ``robots=["franka"]``. 9 of 10
11-
omitted it, so they instantiated with an empty robot list — a robotless pick
12-
task can never succeed and eval scores a silent 0%.
1+
"""Regression tests for two LIBERO content bugs (backend-free).
2+
3+
- scene3 "turn on the stove" termination did ``(stove_joint > thr).all(dim=-1)``
4+
on a ``(N,)`` tensor, collapsing the per-env result to a scalar (reducing over
5+
the batch). The sibling scene9 does it correctly.
6+
- two pick tasks had registration-id typos (a misspelled alias and a primary id
7+
missing the ``pick_`` prefix that every sibling uses).
138
"""
149

1510
from __future__ import annotations
1611

1712
import pathlib
1813

1914
import pytest
15+
import torch
16+
17+
from metasim.types import ObjectState, TensorState
2018

2119
_LIBERO = pathlib.Path(__file__).resolve().parents[1] / "roboverse_pack" / "tasks" / "libero"
2220

2321

22+
@pytest.mark.general
23+
def test_stove_terminated_is_per_env():
24+
from roboverse_pack.tasks.libero_90.libero_kitchen_scene3_turn_on_the_stove import (
25+
LiberoKitchenScene3TurnOnStoveTask,
26+
)
27+
28+
states = TensorState(
29+
objects={
30+
"flat_stove": ObjectState(
31+
root_state=torch.zeros(2, 13),
32+
joint_pos=torch.tensor([[0.6], [0.1]]), # env0 on, env1 off
33+
joint_vel=torch.zeros(2, 1),
34+
)
35+
},
36+
robots={},
37+
cameras={},
38+
)
39+
task = LiberoKitchenScene3TurnOnStoveTask.__new__(LiberoKitchenScene3TurnOnStoveTask)
40+
out = task._terminated(states)
41+
assert out.shape == (2,), f"_terminated must be per-env (2,), got {tuple(out.shape)}"
42+
assert out.tolist() == [True, False]
43+
44+
45+
@pytest.mark.general
46+
def test_libero_pick_registration_ids_are_correct():
47+
soup = (_LIBERO / "libero_pick_alphabet_soup.py").read_text()
48+
assert "pick_alphnabet_soup" not in soup, "misspelled alias 'pick_alphnabet_soup'"
49+
assert '"libero.pick_alphabet_soup", "pick_alphabet_soup"' in soup
50+
51+
juice = (_LIBERO / "libero_pick_orange_juice.py").read_text()
52+
assert '"libero.orange_juice"' not in juice, "primary id is missing the 'pick_' prefix"
53+
assert '"libero.pick_orange_juice", "pick_orange_juice"' in juice
54+
55+
56+
@pytest.mark.general
57+
def test_all_libero_pick_tasks_have_a_robot():
58+
"""Every ``libero_pick_*`` task is a "pick up X and place in basket" task and
59+
therefore MUST declare a robot arm. Previously 9 of 10 omitted
60+
``robots=["franka"]`` (only butter had it), so the scenario instantiated with
61+
an empty robot list — a robotless pick task can never succeed and eval scores
62+
a silent 0%. This pins every sibling to declare a robot.
63+
"""
64+
import importlib
65+
66+
pick_files = sorted(_LIBERO.glob("libero_pick_*.py"))
67+
assert pick_files, "no libero_pick_* task files found"
68+
missing = []
69+
for f in pick_files:
70+
mod = importlib.import_module(f"roboverse_pack.tasks.libero.{f.stem}")
71+
# the task class defines a class-level ScenarioCfg
72+
for obj in vars(mod).values():
73+
scen = getattr(obj, "scenario", None)
74+
if scen is not None and hasattr(scen, "robots"):
75+
if not scen.robots:
76+
missing.append(f.stem)
77+
break
78+
assert not missing, f"libero_pick tasks with no robot (robotless pick can never succeed): {missing}"
79+
80+
2481
@pytest.mark.general
2582
def test_libero_90_get_initial_states_degrades_gracefully(monkeypatch):
2683
"""Libero90BaseTask._get_initial_states must degrade to None (handler
@@ -54,28 +111,3 @@ def _raise(*a, **k):
54111
# 3. get_traj returns empty → None (empty guard before len())
55112
monkeypatch.setattr(mod, "get_traj", lambda *a, **k: ([], None, None))
56113
assert t._get_initial_states() is None
57-
58-
59-
@pytest.mark.general
60-
def test_all_libero_pick_tasks_have_a_robot():
61-
"""Every ``libero_pick_*`` task is a "pick up X and place in basket" task and
62-
therefore MUST declare a robot arm. Previously 9 of 10 omitted
63-
``robots=["franka"]`` (only butter had it), so the scenario instantiated with
64-
an empty robot list — a robotless pick task can never succeed and eval scores
65-
a silent 0%. This pins every sibling to declare a robot.
66-
"""
67-
import importlib
68-
69-
pick_files = sorted(_LIBERO.glob("libero_pick_*.py"))
70-
assert pick_files, "no libero_pick_* task files found"
71-
missing = []
72-
for f in pick_files:
73-
mod = importlib.import_module(f"roboverse_pack.tasks.libero.{f.stem}")
74-
# the task class defines a class-level ScenarioCfg
75-
for obj in vars(mod).values():
76-
scen = getattr(obj, "scenario", None)
77-
if scen is not None and hasattr(scen, "robots"):
78-
if not scen.robots:
79-
missing.append(f.stem)
80-
break
81-
assert not missing, f"libero_pick tasks with no robot (robotless pick can never succeed): {missing}"

0 commit comments

Comments
 (0)