diff --git a/notebooks/getting_started.ipynb b/notebooks/getting_started.ipynb index 46139eef4a..52042db59b 100644 --- a/notebooks/getting_started.ipynb +++ b/notebooks/getting_started.ipynb @@ -58,7 +58,7 @@ "source": [ "## Discovering Available Environments\n", "\n", - "RoboDisco provides 15 PyBullet-based robotic manipulation environments.\n", + "RoboDisco provides 22 PyBullet-based robotic manipulation environments.\n", "Let's register them all and see what's available." ] }, diff --git a/predicators/envs/README.md b/predicators/envs/README.md index d461aec15f..c9ded6402e 100644 --- a/predicators/envs/README.md +++ b/predicators/envs/README.md @@ -1,15 +1,13 @@ # PyBullet Environments -**RoboDisco** (Robot Model Discovery Benchmark) — a robotic -world-model learning and causal-discovery suite. The envs ship as part -of the [predicators](../../README.md) repository and are exposed -through a standard [Gymnasium](https://gymnasium.farama.org/) API. +**RoboDisco** (Robot Model Discovery Benchmark) is a suite of 22 environments for robotic world-model learning and causal discovery. +The envs ship as part of the [predicators](../../README.md) repository and are exposed through a standard [Gymnasium](https://gymnasium.farama.org/) API. 🌐 **Project page:** -Each environment features a Fetch or Panda robot interacting with objects -on a tabletop. The same envs are used by predicators' planning research -code and can be consumed independently of the planner. +Each environment features a Fetch or Panda robot interacting with objects on a tabletop. +Five of them (Balloons, Boil, Bridge, Domino and Fan) are the benchmark domains of the EMPIRIC experiments; their benchmark settings live in [`scripts/configs/empiric/envs.yaml`](../../scripts/configs/empiric/envs.yaml). +The same envs are used by predicators' planning research code and can be consumed independently of the planner. ## Installation @@ -19,9 +17,8 @@ From the repo root: pip install -e . ``` -This installs the agent solvers and the RoboDisco envs together. The -package is slightly heavy because it bundles both — a lighter -envs-only install is future work. +This installs the agent solvers and the RoboDisco envs together. +The package is slightly heavy because it bundles both; a lighter envs-only install is future work. ## Quick Start (Gymnasium API) @@ -49,64 +46,83 @@ env.close() The Gymnasium wrapper exposes: -- `obs`: a 1-D `float32` numpy array of object features (PyBullet body ids - and other `sim_features` are excluded). -- `action_space`: the underlying robot's joint action space, as a - `gymnasium.spaces.Box`. +- `obs`: a 1-D `float32` numpy array of object features (PyBullet body ids and other `sim_features` are excluded). + Its layout comes from the objects of the first training task, so objects that only appear in other tasks (the BusyBoard and Laser test tasks add some) are missing from `obs`; read them from `info["state"]`. +- `action_space`: the underlying robot's joint action space, as a `gymnasium.spaces.Box`. - `reward`: `1.0` when all goal predicates are satisfied, `0.0` otherwise. - `terminated`: `True` when the goal is reached. - `truncated`: `True` when the episode hits the 500-step limit. -- `info["state"]`: the full object-centric `predicators.structs.State` - for the current step (predicates, types, sim state). +- `info["state"]`: the full object-centric `predicators.structs.State` for the current step (predicates, types, sim state). - `info["goal_reached"]`: shortcut for `env.goal_reached()`. +- `env.reset(options={"train_or_test": "test", "task_idx": 0})` selects the task to play (training task 0 by default), and `env.reset(seed=s)` reseeds the environment. ## Walkthroughs -- **Notebook:** [`notebooks/getting_started.ipynb`](../../notebooks/getting_started.ipynb) - — interactive walkthrough with rendering. -- **Smoke script:** [`scripts/robodisco_getting_started.py`](../../scripts/robodisco_getting_started.py) - — non-interactive smoke test that mirrors the notebook and resets every - env to verify installation health. +- **Notebook:** [`notebooks/getting_started.ipynb`](../../notebooks/getting_started.ipynb), an interactive walkthrough with rendering. +- **Smoke script:** [`scripts/robodisco_getting_started.py`](../../scripts/robodisco_getting_started.py), a non-interactive smoke test that mirrors the notebook and resets every env to verify installation health. ## Environments Status legend: -- **Tasks** — the env's task generator produces multiple init states and goals (✅) versus only a single fixed configuration (❌). -- **Skills** — `predicators/ground_truth_models//options.py` exposes a non-empty set of primitive options (✅) versus an empty set or no factory (❌). -- **Demos** — `python predicators/main.py --env --approach oracle --seed 0 --num_train_tasks 1 --num_test_tasks 1 --timeout 60` solves the test task end-to-end (✅) versus failing during planning, execution, or sampler grounding (❌). +- **Preview** - one task being solved, by planning with ground-truth models or by a scripted skill sequence, from the [RoboDisco site](https://yichao-liang.github.io/robodisco-site/); click it for the environment's page. +- **Tasks** - the task generator varies the initial state across tasks (✅) or starts every task from one layout (❌). +- **Skills** - `predicators/ground_truth_models//options.py` builds a non-empty skill library (✅) or none (❌); see [Skill libraries](#skill-libraries). +- **Oracle** - the approach that solves the test task with `python predicators/main.py --env --approach --seed 0 --num_train_tasks 1 --num_test_tasks 1 --timeout 60` in the default configuration: `oracle` (bilevel planning with ground-truth operators and samplers) or `oracle_process_planning` (planning with ground-truth process models); ❌ when neither does. +- Names in **bold** are the five benchmark domains. -| Environment | Preview | Description | Tasks | Skills | Demos | +| Environment | Preview | Description | Tasks | Skills | Oracle | |---|---|---|:---:|:---:|:---:| -| `robodisco/Ants-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_ants.gif) | Place food items near ants on a tabletop | ❌ | ✅ | ❌ | -| `robodisco/Balance-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_balance.gif) | Balance blocks on a beam by pressing buttons | ✅ | ✅ | ❌ | -| `robodisco/Barrier-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_barrier.gif) | Move blocks past barriers to target locations | ❌ | ❌ | ❌ | -| `robodisco/Blocks-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_blocks.gif) | Stack and arrange blocks on a table | ✅ | ✅ | ✅ | -| `robodisco/Boil-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_boil.gif) | Fill and boil water using a jug, faucet, and burner | ✅ | ✅ | ✅ | -| `robodisco/Circuit-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_circuit.gif) | Assemble circuit components (batteries, wires, switch) | ❌ | ✅ | ✅ | -| `robodisco/Coffee-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_coffee.gif) | Operate a coffee machine: plug in, brew, pour, serve | ✅ | ✅ | ❌ | -| `robodisco/Cover-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_cover.gif) | Place blocks to cover target regions | ✅ | ✅ | ✅ | -| `robodisco/Domino-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_domino.gif) | Set up domino chains with fans, balls, and ramps | ✅ | ✅ | ✅ | -| `robodisco/Fan-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_fan.gif) | Use fans to blow lightweight objects to goals | ✅ | ✅ | ✅ | -| `robodisco/Float-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_float.gif) | Float light blocks by filling a container with water | ❌ | ✅ | ✅ | -| `robodisco/Grow-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_grow.gif) | Grow plants by watering them | ✅ | ✅ | ✅ | -| `robodisco/Laser-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_laser.gif) | Align lasers and mirrors to hit targets | ❌ | ✅ | ❌ | -| `robodisco/MagicBin-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_magic_bin.gif) | Sort objects into magic bins that transform them | ❌ | ❌ | ❌ | -| `robodisco/Switch-v0` | ![](../../docs/envs/assets/random_action_gifs/pybullet_switch.gif) | Toggle switches to open doors and move objects | ❌ | ❌ | ❌ | - -The Demos column was verified by running the oracle command above on -every env. Failing envs typically need additional `CFG` overrides or -hit known issues (missing NSRTs, sampler-grounding errors, or -execution drift); they may still be useful as targets for skill or -NSRT learning research. A handful of envs (`Circuit`, `Cover`, `Laser`, -`Switch`) also currently fail to instantiate through the Gymnasium -wrapper with parser defaults — pass `cfg_overrides={...}` to -`robodisco.make(...)` or call `utils.update_config({...})` before -`make()` to supply the missing fields. +| `robodisco/Ants-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/ants.gif)](https://yichao-liang.github.io/robodisco-site/envs/ants.html) | Sort food by whether it attracts ants, stacking same-coloured items | ✅ | ✅ | `oracle` | +| `robodisco/Balance-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/balance.gif)](https://yichao-liang.github.io/robodisco-site/envs/balance.html) | Equalise two plates on a beam, then press the button | ✅ | ✅ | `oracle` | +| **`robodisco/Balloons-v0`** | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/balloons.gif)](https://yichao-liang.github.io/robodisco-site/envs/balloons.html) | Free clipped balloons so they lift a box to hang inside a target band | ✅ | ✅ | ❌ | +| `robodisco/Barrier-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/barrier.gif)](https://yichao-liang.github.io/robodisco-site/envs/barrier.html) | Raise and lower barriers with the switches that control them | ✅ | ❌ | ❌ | +| `robodisco/Blocks-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/blocks.gif)](https://yichao-liang.github.io/robodisco-site/envs/blocks.html) | Rearrange blocks into the requested towers | ✅ | ✅ | `oracle` | +| **`robodisco/Boil-v0`** | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/boil.gif)](https://yichao-liang.github.io/robodisco-site/envs/boil.html) | Fill jugs at a faucet and boil them on a burner without spilling | ✅ | ✅ | `oracle_process_planning`\* | +| **`robodisco/Bridge-v0`** | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/bridge.gif)](https://yichao-liang.github.io/robodisco-site/envs/bridge.html) | Glue identical blocks into an n-shaped bridge whose joints cure over time | ✅ | ✅ | `oracle_process_planning`\* | +| `robodisco/BusyBoard-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/busyboard.gif)](https://yichao-liang.github.io/robodisco-site/envs/busyboard.html) | Light the right lamps on a board of switches with hidden wiring | ✅ | ✅ | ❌ | +| `robodisco/Circuit-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/circuit.gif)](https://yichao-liang.github.io/robodisco-site/envs/circuit.html) | Wire a battery to a bulb and switch it on | ✅ | ✅ | `oracle` | +| `robodisco/Coffee-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/coffee.gif)](https://yichao-liang.github.io/robodisco-site/envs/coffee.html) | Brew a jug of coffee and pour it into every cup | ✅ | ✅ | ❌ | +| `robodisco/Cover-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/cover.gif)](https://yichao-liang.github.io/robodisco-site/envs/cover.html) | Place blocks so they cover target regions | ✅ | ✅ | `oracle` | +| `robodisco/Crane-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/crane.gif)](https://yichao-liang.github.io/robodisco-site/envs/crane.html) | Swing a hinged ram to knock a crate onto a landing pad | ✅ | ✅ | `oracle` | +| **`robodisco/Domino-v0`** | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/domino.gif)](https://yichao-liang.github.io/robodisco-site/envs/domino.html) | Place as few dominoes as possible so a pushed chain topples the target | ✅ | ✅ | ❌ | +| **`robodisco/Fan-v0`** | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/fan.gif)](https://yichao-liang.github.io/robodisco-site/envs/fan.html) | Switch banks of fans to blow a ball through a walled grid to a target | ✅ | ✅ | ❌ | +| `robodisco/Float-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/float.gif)](https://yichao-liang.github.io/robodisco-site/envs/float.html) | Raise the water level to lift a floating block within reach | ✅ | ✅ | ❌ | +| `robodisco/Grow-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/grow.gif)](https://yichao-liang.github.io/robodisco-site/envs/grow.html) | Grow plants by pouring from the jug of matching colour | ✅ | ✅ | `oracle_process_planning` | +| `robodisco/IceRink-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/icerink.gif)](https://yichao-liang.github.io/robodisco-site/envs/icerink.html) | Push sliding tiles onto matching targets across a low-friction rink | ✅ | ✅ | ❌ | +| `robodisco/Laser-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/laser.gif)](https://yichao-liang.github.io/robodisco-site/envs/laser.html) | Place mirrors to route a laser beam onto the targets | ✅ | ✅ | ❌ | +| `robodisco/Launcher-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/launcher.gif)](https://yichao-liang.github.io/robodisco-site/envs/launcher.html) | Cock a spring launcher to knock only the top block off a tower | ✅ | ✅ | `oracle` | +| `robodisco/MagicBin-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/magic-bin.gif)](https://yichao-liang.github.io/robodisco-site/envs/magic-bin.html) | Make blocks vanish by dropping them in a bin that a switch activates | ❌ | ❌ | ❌ | +| `robodisco/Magnets-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/magnets.gif)](https://yichao-liang.github.io/robodisco-site/envs/magnets.html) | Steer coloured pieces into slots with a magnetic wand | ✅ | ✅ | `oracle` | +| `robodisco/Switch-v0` | [![](https://yichao-liang.github.io/robodisco-site/assets/gifs/switch.gif)](https://yichao-liang.github.io/robodisco-site/envs/switch.html) | Set a light's colour with a power switch and a colour switch | ✅ | ❌ | ❌ | + +All columns were checked on October 1, 2026. +Every environment builds, resets, steps and renders through the Gymnasium wrapper with the Quick Start configuration. +Circuit, Laser and Switch fail with a PyBullet joint error when another environment was built earlier in the same process, so build them in a fresh process. + +\* Boil and Bridge solve with `oracle_process_planning` under the settings of their oracle tests ([`test_oracle_process_planning_boil.py`](../../tests/approaches/test_oracle_process_planning_boil.py), [`test_oracle_process_planning_bridge.py`](../../tests/approaches/test_oracle_process_planning_bridge.py)); they do not solve in the default configuration. + +Laser and BusyBoard start every training task from one layout and use a different one for testing; MagicBin keeps one layout and varies only the goal. + +The other ❌ entries in the Oracle column, in the default configuration: +Barrier, MagicBin and Switch have no ground-truth operators or processes; +Domino and Fan fail an assertion while building their ground-truth processes; +Coffee fails to read a cup's pose; +Balloons, BusyBoard, Float, IceRink and Laser run but do not solve the test task. +The clips on the project page come from the benchmark settings or from scripted skill sequences, as each environment's page says. + +## Skill libraries + +`CFG.skill_library` selects the skills that `get_gt_options` builds: + +- `"composite"` (the default) is each environment's own skills, such as `PickJug`, `SwitchFaucetOn` or `Push`, with task knowledge built in. + These are the skills of the Skills column. +- `"primitive"` is one domain-general library, identical in every environment that supports it: `MoveTo[x, y, z, yaw, tilt]`, `MoveLinear[dx, dy, dz, step]`, `MoveUntilContact[dx, dy, dz, step]`, `Gripper[width, force]` and `Wait[steps]` (see [`skill_factories/primitives.py`](../ground_truth_models/skill_factories/primitives.py)). + The agent supplies the grasp points, push strokes and release moments itself. + Balloons, Boil, Bridge, Domino and Fan support it. ## Per-environment configuration -The RoboDisco envs read from predicators' global `CFG` object, which -normally gets populated by `predicators/main.py`'s command-line parser. +The RoboDisco envs read from predicators' global `CFG` object, which normally gets populated by `predicators/main.py`'s command-line parser. For library use, set it explicitly: ```python @@ -138,23 +154,21 @@ Each env can be used directly via predicators' `BaseEnv` interface: ```python from predicators import utils from predicators.envs.pybullet_blocks import PyBulletBlocksEnv +from predicators.structs import Action utils.reset_config({"num_train_tasks": 5, "num_test_tasks": 5}) env = PyBulletBlocksEnv(use_gui=False) state = env.reset("train", 0) for _ in range(50): - action = env.action_space.sample() + action = Action(env.action_space.sample()) state = env.step(action) ``` -This gives you direct access to `env.predicates`, `env.types`, -`env.goal_predicates`, `env.get_train_tasks()`, etc., without flattening -the state into a `Box` observation. +This gives you direct access to `env.predicates`, `env.types`, `env.goal_predicates`, `env.get_train_tasks()`, etc., without flattening the state into a `Box` observation. ## Developing new envs -For a guide on writing new PyBullet environments, see -[`docs/envs/pybullet-guide.md`](../../docs/envs/pybullet-guide.md). +For a guide on writing new PyBullet environments, see [`docs/envs/pybullet-guide.md`](../../docs/envs/pybullet-guide.md). ## Predicators planning framework diff --git a/predicators/envs/gymnasium_wrapper.py b/predicators/envs/gymnasium_wrapper.py index 98a65cb797..6f57f5edf4 100644 --- a/predicators/envs/gymnasium_wrapper.py +++ b/predicators/envs/gymnasium_wrapper.py @@ -1,6 +1,6 @@ """RoboDisco: a Gymnasium-API wrapper for predicators' PyBullet envs. -RoboDisco (Robot Model Discovery Benchmark) exposes the 15 native +RoboDisco (Robot Model Discovery Benchmark) exposes the 22 native ``predicators.envs.pybullet_*`` environments through a standard ``gymnasium.Env`` interface so the suite can be used as a robot model-discovery benchmark independent of the predicators planning @@ -200,24 +200,37 @@ def close(self) -> None: ("robodisco/Ants-v0", "predicators.envs.pybullet_ants:PyBulletAntsEnv"), ("robodisco/Balance-v0", "predicators.envs.pybullet_balance:PyBulletBalanceEnv"), + ("robodisco/Balloons-v0", + "predicators.envs.pybullet_balloons:PyBulletBalloonsEnv"), ("robodisco/Barrier-v0", "predicators.envs.pybullet_barrier:PyBulletBarrierEnv"), ("robodisco/Blocks-v0", "predicators.envs.pybullet_blocks:PyBulletBlocksEnv"), ("robodisco/Boil-v0", "predicators.envs.pybullet_boil:PyBulletBoilEnv"), + ("robodisco/Bridge-v0", + "predicators.envs.pybullet_bridge:PyBulletBridgeEnv"), + ("robodisco/BusyBoard-v0", + "predicators.envs.pybullet_busyboard:PyBulletBusyBoardEnv"), ("robodisco/Circuit-v0", "predicators.envs.pybullet_circuit:PyBulletCircuitEnv"), ("robodisco/Coffee-v0", "predicators.envs.pybullet_coffee:PyBulletCoffeeEnv"), ("robodisco/Cover-v0", "predicators.envs.pybullet_cover:PyBulletCoverEnv"), + ("robodisco/Crane-v0", "predicators.envs.pybullet_crane:PyBulletCraneEnv"), ("robodisco/Domino-v0", "predicators.envs.pybullet_domino.env:PyBulletDominoEnv"), ("robodisco/Fan-v0", "predicators.envs.pybullet_fan:PyBulletFanEnv"), ("robodisco/Float-v0", "predicators.envs.pybullet_float:PyBulletFloatEnv"), ("robodisco/Grow-v0", "predicators.envs.pybullet_grow:PyBulletGrowEnv"), + ("robodisco/IceRink-v0", + "predicators.envs.pybullet_icerink:PyBulletIceRinkEnv"), ("robodisco/Laser-v0", "predicators.envs.pybullet_laser:PyBulletLaserEnv"), + ("robodisco/Launcher-v0", + "predicators.envs.pybullet_launcher:PyBulletLauncherEnv"), ("robodisco/MagicBin-v0", "predicators.envs.pybullet_magic_bin:PyBulletMagicBinEnv"), + ("robodisco/Magnets-v0", + "predicators.envs.pybullet_magnets:PyBulletMagnetsEnv"), ("robodisco/Switch-v0", "predicators.envs.pybullet_switch:PyBulletSwitchEnv"), ] diff --git a/scripts/robodisco_getting_started.py b/scripts/robodisco_getting_started.py index 353415e617..35469780a8 100644 --- a/scripts/robodisco_getting_started.py +++ b/scripts/robodisco_getting_started.py @@ -27,7 +27,7 @@ robodisco.register_all_environments() env_ids = sorted(robodisco.get_all_env_ids()) print(f"[1/6] Found {len(env_ids)} environments") -assert len(env_ids) == 15, f"Expected 15 environments, got {len(env_ids)}" +assert len(env_ids) == 22, f"Expected 22 environments, got {len(env_ids)}" for eid in env_ids: print(f" {eid}") @@ -132,7 +132,9 @@ print(f"\nSummary: {len(ok)}/{len(env_ids)} envs reset cleanly.") if fail: - print("Failing envs (require additional CFG to instantiate):") + print("Failing envs (Circuit, Laser and Switch fail when another env " + "was built earlier in the same process; see " + "predicators/envs/README.md):") for eid, kind, msg in fail: print(f" {eid}: {kind}: {msg}") diff --git a/tests/envs/test_robodisco.py b/tests/envs/test_robodisco.py index 5bc7a4bbb7..f08e66220c 100644 --- a/tests/envs/test_robodisco.py +++ b/tests/envs/test_robodisco.py @@ -35,18 +35,18 @@ def rgb_env(): def test_register_all_environments_count(): - """register_all_environments() registers all 15 envs.""" + """register_all_environments() registers all 22 envs.""" register_all_environments() rd_ids = { eid for eid in gymnasium.registry if eid.startswith("robodisco/") } - assert len(rd_ids) == 15 + assert len(rd_ids) == 22 -def test_get_all_env_ids_returns_15(): - """get_all_env_ids() returns exactly 15 ids.""" - assert len(get_all_env_ids()) == 15 +def test_get_all_env_ids_returns_22(): + """get_all_env_ids() returns exactly 22 ids.""" + assert len(get_all_env_ids()) == 22 def test_get_all_env_ids_prefix(): @@ -60,7 +60,7 @@ def test_register_is_idempotent(): """register_all_environments() is safe to call multiple times.""" register_all_environments() register_all_environments() - assert len(get_all_env_ids()) == 15 + assert len(get_all_env_ids()) == 22 # ---------------------------------------------------------------------------