stable-envs 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.
- stable_envs/__about__.py +9 -0
- stable_envs/__init__.py +91 -0
- stable_envs/__main__.py +3 -0
- stable_envs/_core/__init__.py +1 -0
- stable_envs/_core/cache.py +12 -0
- stable_envs/_core/config.py +84 -0
- stable_envs/_core/dynamics.py +17 -0
- stable_envs/_core/environ.py +166 -0
- stable_envs/_core/mujoco_gl.py +106 -0
- stable_envs/_core/physics.py +492 -0
- stable_envs/_core/plate_texels.py +89 -0
- stable_envs/_core/reset_tree.py +78 -0
- stable_envs/_core/space_nodes.py +10 -0
- stable_envs/_core/stepping.py +341 -0
- stable_envs/_core/style.py +169 -0
- stable_envs/_core/tensordicts.py +136 -0
- stable_envs/_core/warp_cuda.py +66 -0
- stable_envs/_execution/__init__.py +1 -0
- stable_envs/_execution/mjbatch.py +1000 -0
- stable_envs/_execution/mjwarp_fused.py +387 -0
- stable_envs/_execution/mjwarp_tree.py +522 -0
- stable_envs/_execution/policy.py +38 -0
- stable_envs/_execution/process.py +503 -0
- stable_envs/_execution/protocol.py +114 -0
- stable_envs/_execution/render_pool.py +355 -0
- stable_envs/_execution/requests.py +119 -0
- stable_envs/_execution/shared_memory.py +46 -0
- stable_envs/_execution/state_columns.py +52 -0
- stable_envs/_execution/workers.py +34 -0
- stable_envs/_tune.py +263 -0
- stable_envs/cli.py +34 -0
- stable_envs/doctor.py +651 -0
- stable_envs/env.py +1450 -0
- stable_envs/envs/__init__.py +5 -0
- stable_envs/envs/classic_control/__init__.py +9 -0
- stable_envs/envs/classic_control/envs.py +1101 -0
- stable_envs/envs/classic_control/semantics.py +215 -0
- stable_envs/envs/classic_control/simulation/__init__.py +0 -0
- stable_envs/envs/classic_control/simulation/batched.py +577 -0
- stable_envs/envs/classic_control/simulation/device.py +425 -0
- stable_envs/envs/classic_control/simulation/execution.py +31 -0
- stable_envs/envs/classic_control/simulation/local.py +424 -0
- stable_envs/envs/classic_control/simulation/process.py +50 -0
- stable_envs/envs/classic_control/simulation/triton_step.py +504 -0
- stable_envs/envs/craftax/__init__.py +5 -0
- stable_envs/envs/craftax/envs.py +198 -0
- stable_envs/envs/craftax/kernel.py +259 -0
- stable_envs/envs/dmcontrol/__init__.py +14 -0
- stable_envs/envs/dmcontrol/_aligned.py +20 -0
- stable_envs/envs/dmcontrol/_cells.py +95 -0
- stable_envs/envs/dmcontrol/_continuation.py +12 -0
- stable_envs/envs/dmcontrol/_delegated.py +199 -0
- stable_envs/envs/dmcontrol/_family.py +107 -0
- stable_envs/envs/dmcontrol/_first_family.py +7 -0
- stable_envs/envs/dmcontrol/_humanoid.py +7 -0
- stable_envs/envs/dmcontrol/_mass_rows.py +112 -0
- stable_envs/envs/dmcontrol/_model.py +235 -0
- stable_envs/envs/dmcontrol/_shading.py +154 -0
- stable_envs/envs/dmcontrol/_tendon.py +206 -0
- stable_envs/envs/dmcontrol/acrobot.py +15 -0
- stable_envs/envs/dmcontrol/ball_in_cup.py +14 -0
- stable_envs/envs/dmcontrol/base.py +454 -0
- stable_envs/envs/dmcontrol/cartpole.py +9 -0
- stable_envs/envs/dmcontrol/cheetah.py +29 -0
- stable_envs/envs/dmcontrol/finger.py +25 -0
- stable_envs/envs/dmcontrol/hopper.py +29 -0
- stable_envs/envs/dmcontrol/humanoid.py +42 -0
- stable_envs/envs/dmcontrol/manipulator.py +60 -0
- stable_envs/envs/dmcontrol/pendulum.py +6 -0
- stable_envs/envs/dmcontrol/point_mass.py +8 -0
- stable_envs/envs/dmcontrol/quadruped.py +97 -0
- stable_envs/envs/dmcontrol/reacher.py +62 -0
- stable_envs/envs/dmcontrol/simulation/__init__.py +0 -0
- stable_envs/envs/dmcontrol/simulation/aligned.py +460 -0
- stable_envs/envs/dmcontrol/simulation/batch.py +280 -0
- stable_envs/envs/dmcontrol/simulation/continuation.py +286 -0
- stable_envs/envs/dmcontrol/simulation/execution.py +100 -0
- stable_envs/envs/dmcontrol/simulation/factory.py +74 -0
- stable_envs/envs/dmcontrol/simulation/first_family.py +205 -0
- stable_envs/envs/dmcontrol/simulation/humanoid.py +105 -0
- stable_envs/envs/dmcontrol/simulation/lanes.py +170 -0
- stable_envs/envs/dmcontrol/simulation/mjwarp.py +623 -0
- stable_envs/envs/dmcontrol/simulation/mjwarp_reset.py +380 -0
- stable_envs/envs/dmcontrol/simulation/mjwarp_tasks.py +675 -0
- stable_envs/envs/dmcontrol/simulation/physics.py +292 -0
- stable_envs/envs/dmcontrol/simulation/reacher_mjx.py +229 -0
- stable_envs/envs/dmcontrol/simulation/registered.py +54 -0
- stable_envs/envs/dmcontrol/simulation/state.py +132 -0
- stable_envs/envs/dmcontrol/suite.py +945 -0
- stable_envs/envs/dmcontrol/swimmer.py +50 -0
- stable_envs/envs/dmcontrol/trackcom.py +570 -0
- stable_envs/envs/dmcontrol/trackcom_opengl.py +81 -0
- stable_envs/envs/dmcontrol/trackcom_reflection.py +724 -0
- stable_envs/envs/dmcontrol/trackcom_texture.py +46 -0
- stable_envs/envs/dmcontrol/walker.py +29 -0
- stable_envs/envs/dmcontrol/warp_render.py +1252 -0
- stable_envs/envs/fetch/__init__.py +8 -0
- stable_envs/envs/fetch/envs.py +986 -0
- stable_envs/envs/fetch/mujoco_raster.py +168 -0
- stable_envs/envs/fetch/opengl.py +259 -0
- stable_envs/envs/fetch/raster.py +1284 -0
- stable_envs/envs/fetch/semantics.py +367 -0
- stable_envs/envs/fetch/simulation/__init__.py +0 -0
- stable_envs/envs/fetch/simulation/adapter.py +403 -0
- stable_envs/envs/fetch/simulation/batch.py +541 -0
- stable_envs/envs/fetch/simulation/execution.py +61 -0
- stable_envs/envs/fetch/simulation/mjwarp.py +246 -0
- stable_envs/envs/fetch/simulation/observations.py +85 -0
- stable_envs/envs/fetch/simulation/process.py +88 -0
- stable_envs/envs/fetch/transparency.py +356 -0
- stable_envs/envs/miniworld/__init__.py +6 -0
- stable_envs/envs/miniworld/envs.py +204 -0
- stable_envs/envs/miniworld/simulation/__init__.py +0 -0
- stable_envs/envs/miniworld/simulation/adapter.py +532 -0
- stable_envs/envs/miniworld/simulation/batched.py +371 -0
- stable_envs/envs/miniworld/simulation/kernel_cpu.py +491 -0
- stable_envs/envs/miniworld/simulation/kernel_torch.py +499 -0
- stable_envs/envs/miniworld/simulation/kernel_triton.py +338 -0
- stable_envs/envs/miniworld/simulation/rng.py +78 -0
- stable_envs/envs/miniworld/simulation/world.py +116 -0
- stable_envs/envs/mshuman/__init__.py +5 -0
- stable_envs/envs/mshuman/assets.py +59 -0
- stable_envs/envs/mshuman/envs.py +127 -0
- stable_envs/envs/mshuman/simulation/__init__.py +0 -0
- stable_envs/envs/mshuman/simulation/adapter.py +115 -0
- stable_envs/envs/navigation/__init__.py +8 -0
- stable_envs/envs/navigation/envs.py +1825 -0
- stable_envs/envs/navigation/maze_goal.py +210 -0
- stable_envs/envs/navigation/navdot.py +213 -0
- stable_envs/envs/navigation/navdot_cpu.py +114 -0
- stable_envs/envs/navigation/navstep_cpu.py +200 -0
- stable_envs/envs/navigation/tworoom_jax.py +115 -0
- stable_envs/envs/ogbench/__init__.py +33 -0
- stable_envs/envs/ogbench/context_cache.py +199 -0
- stable_envs/envs/ogbench/litpass_cpu.py +422 -0
- stable_envs/envs/ogbench/manipulation.py +1125 -0
- stable_envs/envs/ogbench/manipulation_fidelity.py +118 -0
- stable_envs/envs/ogbench/manipulation_opengl.py +468 -0
- stable_envs/envs/ogbench/manipulation_opengl_pool.py +221 -0
- stable_envs/envs/ogbench/manipulation_raster.py +207 -0
- stable_envs/envs/ogbench/manipulation_renderer.py +328 -0
- stable_envs/envs/ogbench/manipulation_semantics.py +496 -0
- stable_envs/envs/ogbench/maze.py +1527 -0
- stable_envs/envs/ogbench/simulation/__init__.py +0 -0
- stable_envs/envs/ogbench/simulation/compatibility.py +677 -0
- stable_envs/envs/ogbench/simulation/execution.py +78 -0
- stable_envs/envs/ogbench/simulation/manipulation.py +545 -0
- stable_envs/envs/ogbench/simulation/manipulation_batch.py +481 -0
- stable_envs/envs/ogbench/simulation/manipulation_control.py +330 -0
- stable_envs/envs/ogbench/simulation/manipulation_process.py +71 -0
- stable_envs/envs/ogbench/simulation/manipulation_warp.py +712 -0
- stable_envs/envs/ogbench/simulation/maze.py +446 -0
- stable_envs/envs/ogbench/simulation/maze_batch.py +903 -0
- stable_envs/envs/ogbench/simulation/maze_process.py +32 -0
- stable_envs/envs/ogbench/simulation/pcg.py +46 -0
- stable_envs/envs/ogbench/simulation/render_state.py +295 -0
- stable_envs/envs/pusht/__init__.py +17 -0
- stable_envs/envs/pusht/draw.py +123 -0
- stable_envs/envs/pusht/legacy.py +522 -0
- stable_envs/envs/pusht/projection.py +124 -0
- stable_envs/envs/pusht/raster.py +1379 -0
- stable_envs/envs/pusht/raster_cpu.py +669 -0
- stable_envs/envs/pusht/raster_device.py +933 -0
- stable_envs/envs/pusht/render_pool.py +114 -0
- stable_envs/envs/pusht/semantics.py +505 -0
- stable_envs/envs/pusht/simulation/__init__.py +0 -0
- stable_envs/envs/pusht/simulation/batch.py +540 -0
- stable_envs/envs/pusht/simulation/contacts.py +32 -0
- stable_envs/envs/pusht/simulation/execution.py +70 -0
- stable_envs/envs/pusht/simulation/history.py +27 -0
- stable_envs/envs/pusht/simulation/jax_batch.py +56 -0
- stable_envs/envs/pusht/simulation/jax_physics.py +402 -0
- stable_envs/envs/pusht/simulation/numba_batch.py +199 -0
- stable_envs/envs/pusht/simulation/numba_broadphase.py +329 -0
- stable_envs/envs/pusht/simulation/numba_physics.py +662 -0
- stable_envs/envs/pusht/simulation/numeric.py +506 -0
- stable_envs/envs/pusht/simulation/process.py +149 -0
- stable_envs/envs/pusht/simulation/quantizer.py +64 -0
- stable_envs/envs/pusht/simulation/replay.py +412 -0
- stable_envs/envs/pusht/simulation/replay_batch.py +387 -0
- stable_envs/envs/pusht/simulation/reset_pool.py +183 -0
- stable_envs/envs/pusht/simulation/task.py +898 -0
- stable_envs/envs/pusht/simulation/triton_batch.py +117 -0
- stable_envs/envs/pusht/simulation/triton_broadphase.py +370 -0
- stable_envs/envs/pusht/simulation/triton_physics.py +1007 -0
- stable_envs/envs/roadster/__init__.py +8 -0
- stable_envs/envs/roadster/envs.py +273 -0
- stable_envs/envs/roadster/render.py +219 -0
- stable_envs/envs/roadster/simulation/__init__.py +0 -0
- stable_envs/envs/roadster/simulation/adapter.py +191 -0
- stable_envs/envs/roadster/simulation/base.py +408 -0
- stable_envs/envs/roadster/simulation/batch.py +544 -0
- stable_envs/envs/roadster/simulation/execution.py +36 -0
- stable_envs/envs/roadster/simulation/warp.py +651 -0
- stable_envs/envs/roadster/simulation/world.py +295 -0
- stable_envs/envs/roadster/source/__init__.py +18 -0
- stable_envs/envs/roadster/source/body_mesh.py +459 -0
- stable_envs/envs/roadster/source/cinematic.py +95 -0
- stable_envs/envs/roadster/source/control.py +137 -0
- stable_envs/envs/roadster/source/course.py +55 -0
- stable_envs/envs/roadster/source/decor.py +358 -0
- stable_envs/envs/roadster/source/expert.py +64 -0
- stable_envs/envs/roadster/source/fx.py +219 -0
- stable_envs/envs/roadster/source/model.py +449 -0
- stable_envs/envs/roadster/source/scenarios.py +245 -0
- stable_envs/envs/roadster/source/sim.py +362 -0
- stable_envs/envs/roadster/source/tasks.py +429 -0
- stable_envs/envs/roadster/source/textures.py +171 -0
- stable_envs/envs/roadster/source/util.py +45 -0
- stable_envs/envs/roadster/source/weather.py +170 -0
- stable_envs/envs/rocket/__init__.py +20 -0
- stable_envs/envs/rocket/legacy.py +785 -0
- stable_envs/envs/rocket/raster.py +184 -0
- stable_envs/envs/rocket/render.py +518 -0
- stable_envs/envs/rocket/semantics.py +551 -0
- stable_envs/envs/rocket/simulation/__init__.py +0 -0
- stable_envs/envs/rocket/simulation/actions.py +33 -0
- stable_envs/envs/rocket/simulation/adapter.py +298 -0
- stable_envs/envs/rocket/simulation/execution.py +91 -0
- stable_envs/envs/rocket/simulation/numba_batch.py +635 -0
- stable_envs/envs/rocket/simulation/numba_cuda_batch.py +375 -0
- stable_envs/envs/rocket/simulation/numba_cuda_physics.py +300 -0
- stable_envs/envs/rocket/simulation/numba_physics.py +1210 -0
- stable_envs/envs/rocket/simulation/numeric.py +97 -0
- stable_envs/envs/rocket/simulation/process.py +115 -0
- stable_envs/envs/rocket/simulation/replay.py +648 -0
- stable_envs/envs/rocket/simulation/task.py +518 -0
- stable_envs/envs/rocket/tiny_raster.py +1169 -0
- stable_envs/registry.py +282 -0
- stable_envs/render/__init__.py +0 -0
- stable_envs/render/_resources.py +272 -0
- stable_envs/render/cpu3d.py +1117 -0
- stable_envs/render/cpu_raster/__init__.py +43 -0
- stable_envs/render/cpu_raster/_common.py +215 -0
- stable_envs/render/cpu_raster/classic.py +153 -0
- stable_envs/render/cpu_raster/rays3d.py +470 -0
- stable_envs/render/cpu_raster/scene3d.py +866 -0
- stable_envs/render/cpu_raster/suite3d.py +388 -0
- stable_envs/render/gfxdraw/__init__.py +1410 -0
- stable_envs/render/gfxdraw/classic.py +415 -0
- stable_envs/render/gfxdraw/fused.py +452 -0
- stable_envs/render/mjwarp/__init__.py +483 -0
- stable_envs/render/mujoco_raster.py +1601 -0
- stable_envs/render/mujoco_scene.py +861 -0
- stable_envs/render/nvdiff.py +1120 -0
- stable_envs/render/primitives.py +158 -0
- stable_envs/render/raster2d.py +759 -0
- stable_envs/render/raycast3d.py +1324 -0
- stable_envs/render/scene.py +411 -0
- stable_envs/render/texture_mip.py +151 -0
- stable_envs/spaces.py +405 -0
- stable_envs/specs.py +336 -0
- stable_envs/testing/__init__.py +5 -0
- stable_envs/testing/_data.py +595 -0
- stable_envs/testing/_sampling.py +135 -0
- stable_envs/testing/conformance.py +1270 -0
- stable_envs/testing/corpus.py +127 -0
- stable_envs/testing/evidence.py +111 -0
- stable_envs/testing/metrics.py +93 -0
- stable_envs/testing/provenance.py +164 -0
- stable_envs/tuning.py +569 -0
- stable_envs-0.1.0.dist-info/METADATA +241 -0
- stable_envs-0.1.0.dist-info/RECORD +278 -0
- stable_envs-0.1.0.dist-info/WHEEL +5 -0
- stable_envs-0.1.0.dist-info/entry_points.txt +2 -0
- stable_envs-0.1.0.dist-info/licenses/LICENSE +21 -0
- stable_envs-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +235 -0
- stable_envs-0.1.0.dist-info/licenses/licenses/APACHE-2.0.txt +201 -0
- stable_envs-0.1.0.dist-info/licenses/licenses/Bullet-zlib.txt +14 -0
- stable_envs-0.1.0.dist-info/licenses/licenses/Gymnasium-MIT.txt +22 -0
- stable_envs-0.1.0.dist-info/licenses/licenses/Gymnasium-Robotics-MIT.txt +21 -0
- stable_envs-0.1.0.dist-info/licenses/licenses/LGPL-2.1.txt +501 -0
- stable_envs-0.1.0.dist-info/licenses/licenses/Munk2D-MIT.txt +20 -0
- stable_envs-0.1.0.dist-info/licenses/licenses/NumPy-BSD-3-Clause.txt +30 -0
- stable_envs-0.1.0.dist-info/licenses/licenses/NumPy-random-NCSA-BSD-3-Clause.txt +71 -0
- stable_envs-0.1.0.dist-info/licenses/licenses/OGBench-MIT.txt +21 -0
- stable_envs-0.1.0.dist-info/licenses/licenses/PyFlyt-MIT.txt +19 -0
- stable_envs-0.1.0.dist-info/top_level.txt +1 -0
stable_envs/__about__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
__title__ = "stable-envs"
|
|
4
|
+
__version__ = "0.1.0"
|
|
5
|
+
__summary__ = "A batched research-environment library with explicit state, dynamics, variation spaces, and fast state-driven CPU/GPU rendering."
|
|
6
|
+
__author__ = "Randall Balestriero, Sami BuGhanem"
|
|
7
|
+
__license__ = "MIT"
|
|
8
|
+
|
|
9
|
+
__all__ = ["__title__", "__version__", "__summary__", "__author__", "__license__"]
|
stable_envs/__init__.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Batched environments with explicit state, variation and state-driven rendering.
|
|
2
|
+
|
|
3
|
+
Environment classes are attributes named by their registry key, for example ``stable_envs.PushT``.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from . import registry as _registry
|
|
11
|
+
from . import tuning
|
|
12
|
+
from .__about__ import __version__ # noqa: F401
|
|
13
|
+
from ._core.config import get_config, set # noqa: A004,F401
|
|
14
|
+
from .env import Env, State, StepResult, VariationLike # noqa: F401
|
|
15
|
+
from .registry import import_errors, info, list_envs, list_families # noqa: F401
|
|
16
|
+
from .registry import make as _make
|
|
17
|
+
from .specs import CompositeSpec, Spec # noqa: F401
|
|
18
|
+
|
|
19
|
+
# Discover available families; report missing optional dependencies through import_errors().
|
|
20
|
+
_registry.discover()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def make(name: str, **kwargs) -> Env:
|
|
24
|
+
"""Construct a registered environment by class name.
|
|
25
|
+
|
|
26
|
+
``make("CartPole", num_envs=64)`` is the same as ``stable_envs.CartPole(num_envs=64)``.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
name: Class name from ``list_envs``, not a ``reference_id`` such as ``"CartPole-v1"``.
|
|
30
|
+
**kwargs: Forwarded to the constructor, such as ``num_envs``, ``size``, ``device`` and ``seed``.
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
KeyError: If ``name`` is not registered. ``import_errors`` shows families that didn't import.
|
|
34
|
+
"""
|
|
35
|
+
return _make(name, **kwargs)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def tune(env: Env, *, knobs=(), repeats: int = 5, write: bool = True, verbose: bool = True) -> dict[str, int]:
|
|
39
|
+
"""Time the tuning knobs ``env`` reads and cache the fastest values.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
env: Environment whose device, ``size`` and ``num_envs`` define the workload.
|
|
43
|
+
knobs: Knobs to sweep (all that ``env`` reads if empty).
|
|
44
|
+
repeats: Timed calls per value.
|
|
45
|
+
write: Save the results to the tuning cache.
|
|
46
|
+
verbose: Print the result table.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
A dict mapping each swept knob to its chosen value.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValueError: If ``knobs`` names an unknown knob.
|
|
53
|
+
"""
|
|
54
|
+
from ._tune import tune as _tune
|
|
55
|
+
|
|
56
|
+
return _tune(env, knobs=knobs, repeats=repeats, write=write, echo=print if verbose else None)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def __getattr__(name: str) -> Any:
|
|
60
|
+
"""Resolve environment classes by canonical registry name."""
|
|
61
|
+
if name in _registry.REGISTRY:
|
|
62
|
+
return _registry.REGISTRY[name]
|
|
63
|
+
raise AttributeError(
|
|
64
|
+
f"module 'stable_envs' has no attribute {name!r}. Environments are looked up by class "
|
|
65
|
+
f"name: snv.list_envs() shows the {len(_registry.REGISTRY)} registered and "
|
|
66
|
+
f"snv.import_errors() shows the families that did not import."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def __dir__() -> list[str]:
|
|
71
|
+
return sorted({*__all__, *_registry.REGISTRY})
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
__all__ = [
|
|
75
|
+
"Env",
|
|
76
|
+
"State",
|
|
77
|
+
"StepResult",
|
|
78
|
+
"VariationLike",
|
|
79
|
+
"Spec",
|
|
80
|
+
"CompositeSpec",
|
|
81
|
+
"make",
|
|
82
|
+
"info",
|
|
83
|
+
"list_envs",
|
|
84
|
+
"list_families",
|
|
85
|
+
"import_errors",
|
|
86
|
+
"set",
|
|
87
|
+
"get_config",
|
|
88
|
+
"tuning",
|
|
89
|
+
"tune",
|
|
90
|
+
"__version__",
|
|
91
|
+
]
|
stable_envs/__main__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Private shared helpers used across the stable_envs package."""
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""The stable-envs cache directory, shared by the tuning cache and model downloads."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def cache_dir() -> Path:
|
|
10
|
+
"""Return the ``stable-envs`` cache root: ``$XDG_CACHE_HOME/stable-envs``, or ``~/.cache/stable-envs`` when unset."""
|
|
11
|
+
base = os.environ.get("XDG_CACHE_HOME") or str(Path.home() / ".cache")
|
|
12
|
+
return Path(base) / "stable-envs"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Global verbosity and default-device settings, and the package logger."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from loguru import logger as _loguru_logger
|
|
8
|
+
|
|
9
|
+
from . import environ as _environ
|
|
10
|
+
|
|
11
|
+
# Records carry extra["package"] == "stable_envs" for application-side filtering.
|
|
12
|
+
logger = _loguru_logger.bind(package="stable_envs")
|
|
13
|
+
|
|
14
|
+
_LEVELS = {
|
|
15
|
+
"TRACE": 5,
|
|
16
|
+
"DEBUG": 10,
|
|
17
|
+
"INFO": 20,
|
|
18
|
+
"SUCCESS": 25,
|
|
19
|
+
"WARNING": 30,
|
|
20
|
+
"ERROR": 40,
|
|
21
|
+
"CRITICAL": 50,
|
|
22
|
+
"SILENT": 100,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_DEFAULTS: dict[str, Any] = {"verbose": "WARNING", "default_device": "auto"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _initial() -> dict[str, Any]:
|
|
29
|
+
return {**_DEFAULTS, "verbose": _environ.read("STABLE_ENVS_VERBOSE")}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_CONFIG: dict[str, Any] = _initial()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def set(**kwargs: Any) -> dict[str, Any]: # noqa: A001 - the public spelling is ``snv.set``
|
|
36
|
+
"""Update the log level or default device and return a copy of the settings.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
**kwargs: ``verbose``, the lowest level logged (``"TRACE"``, ``"DEBUG"``, ``"INFO"``,
|
|
40
|
+
``"SUCCESS"``, ``"WARNING"``, ``"ERROR"``, ``"CRITICAL"`` or ``"SILENT"``), and
|
|
41
|
+
``default_device``, used when a constructor gets ``device=None``.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
KeyError: If any other key is passed.
|
|
45
|
+
ValueError: If ``verbose`` is not a known level.
|
|
46
|
+
"""
|
|
47
|
+
unknown = [k for k in kwargs if k not in _DEFAULTS]
|
|
48
|
+
if unknown:
|
|
49
|
+
raise KeyError(
|
|
50
|
+
f"stable_envs.set: unknown config key(s) {unknown}; the keys are {sorted(_DEFAULTS)}. "
|
|
51
|
+
f"device, size, supersample and backend are environment constructor arguments."
|
|
52
|
+
)
|
|
53
|
+
if "verbose" in kwargs:
|
|
54
|
+
v = str(kwargs["verbose"]).upper()
|
|
55
|
+
if v not in _LEVELS:
|
|
56
|
+
raise ValueError(f"stable_envs.set: verbose must be one of {sorted(_LEVELS)}, got {kwargs['verbose']!r}")
|
|
57
|
+
kwargs["verbose"] = v
|
|
58
|
+
if "default_device" in kwargs and kwargs["default_device"] is not None:
|
|
59
|
+
kwargs["default_device"] = str(kwargs["default_device"])
|
|
60
|
+
_CONFIG.update(kwargs)
|
|
61
|
+
return dict(_CONFIG)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_config() -> dict[str, Any]:
|
|
65
|
+
"""Return a copy of the global settings, ``verbose`` and ``default_device``."""
|
|
66
|
+
return dict(_CONFIG)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def reset_config() -> dict[str, Any]:
|
|
70
|
+
"""Restore and return the default global settings, with verbosity from ``STABLE_ENVS_VERBOSE``."""
|
|
71
|
+
_CONFIG.clear()
|
|
72
|
+
_CONFIG.update(_initial())
|
|
73
|
+
return dict(_CONFIG)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def log(level: str, message: str) -> None:
|
|
77
|
+
"""Log ``message`` through the package logger when ``level`` meets the configured verbosity.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
level: A level name such as ``"DEBUG"`` or ``"WARNING"``.
|
|
81
|
+
message: The text to log.
|
|
82
|
+
"""
|
|
83
|
+
if _LEVELS[level.upper()] >= _LEVELS[_CONFIG["verbose"]]:
|
|
84
|
+
logger.opt(depth=1).log(level.upper(), message)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Detect whether an environment implements dynamics, and the error raised when it does not."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def has_dynamics(env):
|
|
5
|
+
"""Return whether ``env`` overrides the base ``_step``."""
|
|
6
|
+
base = getattr(env, "_se_dynamics_base", None)
|
|
7
|
+
if base is None:
|
|
8
|
+
return hasattr(env, "step")
|
|
9
|
+
return type(env)._step is not base._step
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def no_dynamics(env, what):
|
|
13
|
+
"""Raise NotImplementedError for ``what`` on a render-only environment."""
|
|
14
|
+
raise NotImplementedError(
|
|
15
|
+
f"{type(env).__name__}.{what}: full simulator semantics are not implemented for this environment; "
|
|
16
|
+
"it renders supplied states only. Compact renderer rows cannot substitute for State."
|
|
17
|
+
)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""The registry of every ``STABLE_ENVS_*`` environment variable and the one function that reads them."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
PREFIX = "STABLE_ENVS_"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class Variable:
|
|
17
|
+
"""One environment variable: its name, value type, default, documentation and parser."""
|
|
18
|
+
|
|
19
|
+
name: str
|
|
20
|
+
kind: str
|
|
21
|
+
default: Any
|
|
22
|
+
doc: str
|
|
23
|
+
parse: Callable[[str], Any]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
VARIABLES: dict[str, Variable] = {}
|
|
27
|
+
|
|
28
|
+
_SUFFIX = {"": 1, "K": 1 << 10, "M": 1 << 20, "G": 1 << 30, "T": 1 << 40}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def parse_int(text: str) -> int | None:
|
|
32
|
+
"""Parse an integer with an optional K, M, G or T suffix (powers of 1024); ``auto`` or empty is None."""
|
|
33
|
+
t = text.strip().replace("_", "")
|
|
34
|
+
if t.lower() in ("", "auto"):
|
|
35
|
+
return None
|
|
36
|
+
m = re.fullmatch(r"(\d+)\s*([KMGT]?)I?B?", t.upper())
|
|
37
|
+
if m is None:
|
|
38
|
+
raise ValueError("expected an integer, optionally with a K, M, G or T suffix, or 'auto'")
|
|
39
|
+
return int(m.group(1)) * _SUFFIX[m.group(2)]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def parse_flag(text: str) -> bool:
|
|
43
|
+
"""Parse ``1`` as True and ``0`` or empty as False."""
|
|
44
|
+
t = text.strip()
|
|
45
|
+
if t not in ("", "0", "1"):
|
|
46
|
+
raise ValueError("expected 1 or 0")
|
|
47
|
+
return t == "1"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def parse_path(text: str) -> str | None:
|
|
51
|
+
"""Return a non-empty path string, or None when empty."""
|
|
52
|
+
return text.strip() or None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def register(name: str, kind: str, default: Any, doc: str, parse: Callable[[str], Any]) -> Variable:
|
|
56
|
+
"""Add a variable to the registry; registering the same name twice keeps the first entry."""
|
|
57
|
+
if not name.startswith(PREFIX):
|
|
58
|
+
raise ValueError(f"{name} does not start with {PREFIX}")
|
|
59
|
+
return VARIABLES.setdefault(name, Variable(name, kind, default, doc, parse))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@functools.lru_cache(maxsize=512)
|
|
63
|
+
def _parsed(name: str, raw: str) -> Any:
|
|
64
|
+
try:
|
|
65
|
+
return VARIABLES[name].parse(raw)
|
|
66
|
+
except ValueError as e:
|
|
67
|
+
raise ValueError(f"{name}={raw!r}: {e}") from None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def read(name: str) -> Any:
|
|
71
|
+
"""Return the parsed value of variable ``name``, or its default when unset.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
KeyError: ``name`` is not registered.
|
|
75
|
+
ValueError: The value does not parse; the message names the variable.
|
|
76
|
+
"""
|
|
77
|
+
var = VARIABLES[name]
|
|
78
|
+
raw = os.environ.get(name)
|
|
79
|
+
return var.default if raw is None else _parsed(name, raw)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def variables() -> dict[str, Variable]:
|
|
83
|
+
"""Return every registered variable, including those of modules not imported yet."""
|
|
84
|
+
from .. import tuning # noqa: F401 (registers the knob variables)
|
|
85
|
+
|
|
86
|
+
return dict(sorted(VARIABLES.items()))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def set_variables(environ=None) -> tuple[dict[str, str], list[str]]:
|
|
90
|
+
"""Return the ``STABLE_ENVS_*`` variables set in ``environ`` and the names that are not registered."""
|
|
91
|
+
environ = os.environ if environ is None else environ
|
|
92
|
+
known = variables()
|
|
93
|
+
found = {k: v for k, v in sorted(environ.items()) if k.startswith(PREFIX)}
|
|
94
|
+
return found, [k for k in found if k not in known]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def parse_verbosity(text: str) -> str:
|
|
98
|
+
"""Parse a log level name such as ``INFO`` or ``SILENT``; an empty value is ``WARNING``."""
|
|
99
|
+
from .config import _LEVELS
|
|
100
|
+
|
|
101
|
+
t = text.strip().upper() or "WARNING"
|
|
102
|
+
if t not in _LEVELS:
|
|
103
|
+
raise ValueError(f"expected one of {sorted(_LEVELS)}")
|
|
104
|
+
return t
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def parse_dmc_render(text: str) -> str:
|
|
108
|
+
"""Parse ``auto`` (or empty) or ``mjwarp``."""
|
|
109
|
+
t = text.strip() or "auto"
|
|
110
|
+
if t not in ("auto", "mjwarp"):
|
|
111
|
+
raise ValueError("expected auto or mjwarp")
|
|
112
|
+
return t
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def parse_json_object(text: str) -> dict | None:
|
|
116
|
+
"""Parse a JSON object; an empty value is None."""
|
|
117
|
+
import json
|
|
118
|
+
|
|
119
|
+
t = text.strip()
|
|
120
|
+
if not t:
|
|
121
|
+
return None
|
|
122
|
+
try:
|
|
123
|
+
value = json.loads(t)
|
|
124
|
+
except json.JSONDecodeError as e:
|
|
125
|
+
raise ValueError(f"expected a JSON object: {e}") from None
|
|
126
|
+
if not isinstance(value, dict):
|
|
127
|
+
raise ValueError("expected a JSON object")
|
|
128
|
+
return value
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
register(
|
|
132
|
+
"STABLE_ENVS_DMC_CUDA_RENDER",
|
|
133
|
+
"choice",
|
|
134
|
+
"auto",
|
|
135
|
+
"mjwarp makes backend='auto' fixed-camera DM Control environments on CUDA draw with MuJoCo Warp's ray tracer.",
|
|
136
|
+
parse_dmc_render,
|
|
137
|
+
)
|
|
138
|
+
register(
|
|
139
|
+
"STABLE_ENVS_DMC_WARP_OPTIONS",
|
|
140
|
+
"json",
|
|
141
|
+
None,
|
|
142
|
+
"A JSON object of default MuJoCo Warp render options for DM Control; render_options= overrides it.",
|
|
143
|
+
parse_json_object,
|
|
144
|
+
)
|
|
145
|
+
register(
|
|
146
|
+
"STABLE_ENVS_VERBOSE",
|
|
147
|
+
"level",
|
|
148
|
+
"WARNING",
|
|
149
|
+
"Default log level of the library, with the same values as snv.set(verbose=...). snv.set overrides it.",
|
|
150
|
+
parse_verbosity,
|
|
151
|
+
)
|
|
152
|
+
register(
|
|
153
|
+
"STABLE_ENVS_OGB_UNMEASURED",
|
|
154
|
+
"flag",
|
|
155
|
+
False,
|
|
156
|
+
"1 lets OGBench Cube and Scene cells without measured visual fidelity register, with placeholder "
|
|
157
|
+
"values, while measuring them. Never set it for normal use.",
|
|
158
|
+
parse_flag,
|
|
159
|
+
)
|
|
160
|
+
register(
|
|
161
|
+
"STABLE_ENVS_MSHUMAN_HOME",
|
|
162
|
+
"path",
|
|
163
|
+
None,
|
|
164
|
+
"Path to an existing MS-Human-700 checkout; unset downloads the pinned commit into the cache.",
|
|
165
|
+
parse_path,
|
|
166
|
+
)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Configure GL before importing MuJoCo or dm_control.
|
|
2
|
+
|
|
3
|
+
On macOS, bind MuJoCo to its native backend before importing dm_control with
|
|
4
|
+
rendering disabled. This avoids GLFW initialization during discovery. Restore
|
|
5
|
+
``glfw`` afterward for Gymnasium, which rejects ``cgl``. On other platforms,
|
|
6
|
+
default to offscreen EGL and preserve explicit settings.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import importlib
|
|
12
|
+
import importlib.util
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
_DARWIN_MUJOCO = frozenset({"cgl", "glfw"})
|
|
17
|
+
_LINUX_MUJOCO = frozenset({"egl", "osmesa", "glfw"})
|
|
18
|
+
GL_VARS = ("MUJOCO_GL", "PYOPENGL_PLATFORM", "MUJOCO_EGL_DEVICE_ID")
|
|
19
|
+
#: The GL variables as the process received them, before configure_mujoco_gl fills defaults.
|
|
20
|
+
INITIAL_GL_ENV = {name: os.environ.get(name) for name in GL_VARS}
|
|
21
|
+
|
|
22
|
+
NO_RENDERER_FIX = (
|
|
23
|
+
"Headless Linux: install EGL (apt install libegl1; NVIDIA machines also need the driver's "
|
|
24
|
+
"libEGL_nvidia) and keep MUJOCO_GL=egl, or install OSMesa (apt install libosmesa6) and set "
|
|
25
|
+
"MUJOCO_GL=osmesa PYOPENGL_PLATFORM=osmesa. With a display, MUJOCO_GL=glfw. "
|
|
26
|
+
"`stable-envs doctor` tests each backend on this machine."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def selected_backend(environ=None, platform: str = sys.platform) -> str:
|
|
31
|
+
"""Return the MUJOCO_GL backend MuJoCo binds when stable_envs imports it.
|
|
32
|
+
|
|
33
|
+
Unset on Linux, it follows a PYOPENGL_PLATFORM that names a MuJoCo backend, else EGL.
|
|
34
|
+
"""
|
|
35
|
+
environ = os.environ if environ is None else environ
|
|
36
|
+
backend = environ.get("MUJOCO_GL")
|
|
37
|
+
if platform == "darwin":
|
|
38
|
+
return backend if backend in _DARWIN_MUJOCO else "cgl"
|
|
39
|
+
if backend is not None:
|
|
40
|
+
return backend
|
|
41
|
+
pyopengl = (environ.get("PYOPENGL_PLATFORM") or "").lower()
|
|
42
|
+
return pyopengl if pyopengl in _LINUX_MUJOCO else "egl"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def configure_mujoco_gl() -> str:
|
|
46
|
+
"""Configure import-time GL selection and return the resulting environment value."""
|
|
47
|
+
if sys.platform == "darwin":
|
|
48
|
+
if "mujoco" not in sys.modules:
|
|
49
|
+
os.environ["MUJOCO_GL"] = selected_backend()
|
|
50
|
+
try:
|
|
51
|
+
import mujoco # noqa: F401
|
|
52
|
+
except Exception: # noqa: BLE001 — optional extra; discovery records the miss
|
|
53
|
+
pass
|
|
54
|
+
have_dmc = importlib.util.find_spec("dm_control") is not None
|
|
55
|
+
if have_dmc and "dm_control._render" not in sys.modules:
|
|
56
|
+
os.environ["MUJOCO_GL"] = "disable"
|
|
57
|
+
try:
|
|
58
|
+
importlib.import_module("dm_control.mujoco")
|
|
59
|
+
except Exception: # noqa: BLE001 — a backend that is not installed
|
|
60
|
+
return os.environ["MUJOCO_GL"]
|
|
61
|
+
# Gymnasium's OffScreenViewer only accepts glfw/egl/osmesa. dm_control is already
|
|
62
|
+
# imported (or absent), so restoring glfw does not re-enter GLFW init in this process.
|
|
63
|
+
os.environ["MUJOCO_GL"] = "glfw"
|
|
64
|
+
return os.environ["MUJOCO_GL"]
|
|
65
|
+
os.environ.setdefault("MUJOCO_GL", selected_backend())
|
|
66
|
+
return os.environ["MUJOCO_GL"]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _renderer_import_error() -> Exception | None:
|
|
70
|
+
# mujoco swallows the ImportError that leaves it without Renderer; importing again recovers it.
|
|
71
|
+
for name in ("mujoco.rendering.classic.renderer", "mujoco.renderer"):
|
|
72
|
+
try:
|
|
73
|
+
importlib.import_module(name)
|
|
74
|
+
except ModuleNotFoundError as e:
|
|
75
|
+
if e.name != name:
|
|
76
|
+
return e
|
|
77
|
+
except Exception as e: # noqa: BLE001 — reported to the user
|
|
78
|
+
return e
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def mujoco_renderer(model, *, height: int, width: int):
|
|
83
|
+
"""Construct ``mujoco.Renderer``, failing with the GL setup to change when no OpenGL context can be made."""
|
|
84
|
+
import mujoco
|
|
85
|
+
|
|
86
|
+
backend = os.environ.get("MUJOCO_GL", "<unset>")
|
|
87
|
+
renderer = getattr(mujoco, "Renderer", None)
|
|
88
|
+
if renderer is None:
|
|
89
|
+
cause = _renderer_import_error()
|
|
90
|
+
detail = f"MuJoCo has no OpenGL renderer with MUJOCO_GL={backend}"
|
|
91
|
+
else:
|
|
92
|
+
try:
|
|
93
|
+
return renderer(model, height=height, width=width)
|
|
94
|
+
except ValueError:
|
|
95
|
+
raise
|
|
96
|
+
except Exception as e: # noqa: BLE001 — re-raised with the fix
|
|
97
|
+
cause = e
|
|
98
|
+
detail = f"MuJoCo could not create an OpenGL context with MUJOCO_GL={backend}"
|
|
99
|
+
pyopengl = os.environ.get("PYOPENGL_PLATFORM")
|
|
100
|
+
if cause is not None and "PYOPENGL_PLATFORM" in str(cause) and pyopengl:
|
|
101
|
+
raise RuntimeError(
|
|
102
|
+
f"PYOPENGL_PLATFORM={pyopengl} conflicts with MUJOCO_GL={backend}: unset PYOPENGL_PLATFORM "
|
|
103
|
+
f"or set both to the same backend. {NO_RENDERER_FIX}"
|
|
104
|
+
) from cause
|
|
105
|
+
reason = f" ({type(cause).__name__}: {cause})" if cause is not None else ""
|
|
106
|
+
raise RuntimeError(f"{detail}{reason}. {NO_RENDERER_FIX}") from cause
|