Skip to content

Environments

The environment classes own the Genesis scene, its entities and the managers, and run the step loop. This page is the class reference; the environments guide explains construction, the step-phase order and auto-reset.

from eden.envs.base import EnvBase, MimicRLEnvBase, RLEnvBase, env_class_for_config

Environment classes construct only: from_config(cfg) returns an unbuilt env, and you call build() and reset() yourself. Use env_class_for_config(cfg).from_config(cfg) when the class is not known statically. The training wrappers (below) build and reset inside their own from_config.

Base classes

EnvBase

EnvBase(env_options: EnvOptions | None = None, scene_options: SceneOptions | None = None, observation_options: ObservationManagerOptions | None = None, event_options: EventManagerOptions | None = None, action_options: ActionManagerOptions | None = None, metric_options: MetricManagerOptions | None = None, recorder_options: RecorderManagerOptions | None = None, cameras_options: CamerasOptions | None = None, sensors_options: SensorsOptions | None = None, renderer_options: RendererOptions | None = None, *, show_viewer: bool = False, eval_mode: bool = False, **kwargs)

Bases: DrawDebugMixin, RBC, Generic[TConfig]

Base class for all environments.

PARAMETER DESCRIPTION
env_options

Environment options.

TYPE: EnvOptions | None DEFAULT: None

scene_options

Scene options.

TYPE: SceneOptions | None DEFAULT: None

observation_options

Observation manager options.

TYPE: ObservationManagerOptions | None DEFAULT: None

event_options

Event manager options.

TYPE: EventManagerOptions | None DEFAULT: None

action_options

Action manager options.

TYPE: ActionManagerOptions | None DEFAULT: None

metric_options

Metric manager options.

TYPE: MetricManagerOptions | None DEFAULT: None

recorder_options

Recorder manager options.

TYPE: RecorderManagerOptions | None DEFAULT: None

cameras_options

Cameras options.

TYPE: CamerasOptions | None DEFAULT: None

sensors_options

Sensors options.

TYPE: SensorsOptions | None DEFAULT: None

renderer_options

Renderer options.

TYPE: RendererOptions | None DEFAULT: None

show_viewer

Whether to show the viewer.

TYPE: bool DEFAULT: False

eval_mode

Whether to run in eval mode.

TYPE: bool DEFAULT: False

**kwargs

Forwarded to gs.Scene, overriding the scene options built from env_options (and its extra fields).

DEFAULT: {}

Source code in eden/envs/base.py
def __init__(
    self,
    env_options: EnvOptions | None = None,
    scene_options: SceneOptions | None = None,
    observation_options: ObservationManagerOptions | None = None,
    event_options: EventManagerOptions | None = None,
    action_options: ActionManagerOptions | None = None,
    metric_options: MetricManagerOptions | None = None,
    recorder_options: RecorderManagerOptions | None = None,
    cameras_options: CamerasOptions | None = None,
    sensors_options: SensorsOptions | None = None,
    renderer_options: RendererOptions | None = None,
    *,
    show_viewer: bool = False,
    eval_mode: bool = False,
    **kwargs,
):
    # The config ``from_config`` built this env from, if any. Direct construction
    # leaves it None and :attr:`config` synthesizes a base config instead.
    self._source_config: EdenConfig | None = None

    # Handling of default arguments
    self.env_options = env_options or EnvOptions()
    self.scene_options = scene_options or SceneOptions()
    self.observation_options = observation_options or ObservationManagerOptions()
    self.event_options = event_options or EventManagerOptions()
    self.action_options = action_options or ActionManagerOptions()
    self.metric_options = metric_options or MetricManagerOptions()
    self.recorder_options = recorder_options or RecorderManagerOptions()
    self.cameras_options = cameras_options or CamerasOptions()
    self.sensors_options = sensors_options or SensorsOptions()
    self.renderer_options = renderer_options or Rasterizer()

    if self.env_options.cuda_graph_options is not None and not self._HAS_GRADED_PHASE:
        raise ValueError(
            f"EnvOptions(cuda_graph_options=...) replays the graded phase of RLEnvBase.step (metrics, "
            f"terminations, rewards), but {type(self).__name__} is not an RLEnvBase and has no such phase, "
            "so the option would do nothing. Set cuda_graph_options=None, or build an RL task (an "
            "EdenRLConfig) if graded steps were intended."
        )

    if isinstance(self.renderer_options, BatchRenderer):
        en.logger.info(f"You are using {type(self.renderer_options).__name__} as the renderer options.")
        en.logger.info("Consider adding lights to the scene by `env.scene.add_light()`.")

    self.headless = not show_viewer
    # `eval_mode` only flips training/randomization off; the env count is
    # always `num_envs` so an explicit override can't be silently shadowed.
    self.training = not eval_mode
    self.num_envs = self.env_options.num_envs

    self.sim_dt = self.env_options.sim_dt
    self.dt = self.env_options.decimation * self.env_options.sim_dt
    self._sim_step_counter = 0
    self.episode_length_buf = torch.zeros(self.num_envs, device=self.device, dtype=torch.long)

    self.obs_buf = None
    self.reset_info = None
    # Sanitized index of the envs the last ``_reset_idx`` call actually reset;
    # consumed (and cleared) by ``_dispatch_post_reset`` at the public entry points.
    self._last_reset_envs_idx: slice | torch.Tensor | None = None

    # hooks
    self._pre_build_hooks: list[Callable[[], None]] = []
    self._post_build_hooks: list[Callable[[], None]] = []
    # Called around every scene.step() of step(): before it, then after it (see register_substep_callback).
    self._pre_substep_callbacks: list[Callable[[], None]] = []
    self._substep_callbacks: list[Callable[[], None]] = []
    self._post_build_hooks_drained = False

    # create scene
    # Build default scene kwargs from env_options fields
    scene_kwargs = dict(
        sim_options=gs.options.SimOptions(
            dt=env_options.sim_dt,
            substeps=env_options.sim_substeps,
            requires_grad=env_options.requires_grad,
        ),
        viewer_options=gs.options.ViewerOptions(
            refresh_rate=int(1 / env_options.sim_dt),
            camera_pos=(2.0, 0.0, 2.5),
            camera_lookat=(0.0, 0.0, 0.5),
            camera_fov=40,
            enable_default_keybinds=env_options.enable_default_keybinds,
        ),
        vis_options=gs.options.VisOptions(
            # Genesis renamed ``env_separate_rigid`` to ``split_envs`` and warns on every build that
            # passes the old keyword, so Eden passes the new one.
            split_envs=env_options.env_separate_rigid,
            show_link_frame=env_options.show_link_frame,
            show_world_frame=env_options.show_world_frame,
            show_cameras=env_options.show_camera_frustums,
            segmentation_level=env_options.segmentation_level,
            background_color=env_options.background_color,
            shadow=env_options.rasterizer_shadows,
        ),
        rigid_options=gs.options.RigidOptions(
            # No ``dt=`` here: a solver dt is now an *authored* integration interval, and
            # authoring the step dt while also passing ``SimOptions.substeps > 1`` is a
            # conflict Genesis rejects. The rigid solver derives its rate from
            # ``SimOptions(dt, substeps)`` like every other solver.
            constraint_solver=_CONSTRAINT_SOLVERS[env_options.solver],
            constraint_timeconst=env_options.constraint_timeconst
            if env_options.constraint_timeconst is not None
            else max(0.01, 2 * env_options.sim_dt / env_options.sim_substeps),
            use_gjk_collision=env_options.use_gjk_collision,
            max_collision_pairs=env_options.max_collision_pairs,
            multiplier_collision_broad_phase=env_options.multiplier_collision_broad_phase,
            iterations=env_options.iterations,
            tolerance=env_options.tolerance,
            ls_iterations=env_options.ls_iterations,
            ls_tolerance=env_options.ls_tolerance,
            enable_collision=True,
            enable_self_collision=env_options.enable_self_collision,
            enable_neutral_collision=env_options.enable_neutral_collision,
            enable_joint_limit=True,
            use_hibernation=env_options.use_hibernation,
            batch_links_info=env_options.batch_links_info,
            batch_joints_info=env_options.batch_joints_info,
            batch_dofs_info=env_options.batch_dofs_info,
            enable_multi_contact=env_options.enable_multi_contact,
            enable_torsional_friction=env_options.enable_torsional_friction,
            enable_rolling_friction=env_options.enable_rolling_friction,
            friction_cone=_FRICTION_CONES[env_options.friction_cone],
            integrator=_INTEGRATORS[env_options.integrator],
            impratio=env_options.impratio,
            contact_resolution=_CONTACT_RESOLUTIONS[env_options.contact_resolution],
            noslip_iterations=env_options.noslip_iterations,
        ),
        coupler_options=env_options.coupler_options,
        fem_options=env_options.fem_options,
        mpm_options=env_options.mpm_options,
        sph_options=env_options.sph_options,
        pbd_options=env_options.pbd_options,
        profiling_options=gs.options.ProfilingOptions(
            show_FPS=env_options.show_FPS,
        ),
        renderer=renderer_options,
        show_viewer=show_viewer,
    )

    # Allow extra fields on EnvOptions to override auto-constructed defaults
    extras = env_options.__pydantic_extra__ or {}
    internal_keys = set(OPTION_METADATA_KEYS)
    for key, value in extras.items():
        if key not in internal_keys:
            scene_kwargs[key] = value

    # Explicit kwargs take highest priority
    scene_kwargs.update(kwargs)

    self.scene = gs.Scene(**scene_kwargs)
    # Genesis ships no MochiSolver: Eden hosts one, installed on the scene's first Mochi entity.
    self._mochi_solver: MochiSolver | None = None
    # The misconfiguration flags of the envs a reset or a setter cleared, per solver, which the next control step
    # raises (see `get_solver_error_envs_mask`), and whether any were kept since. Allocated on the first use.
    self._misconfigurations: list[tuple[Any, int, torch.Tensor]] | None = None
    self._has_kept_misconfigurations = False

    # gs-madrona bakes the light count at scene.build(), so BatchRenderer
    # lights must be registered up front. Only the eden BatchRendererOptions
    # subclass carries a ``lights`` field — the raw genesis BatchRenderer has
    # none, and lights declared elsewhere would silently never render.
    if isinstance(self.renderer_options, BatchRendererOptions):
        for light in self.renderer_options.lights:
            self.scene.add_light(**light)

    entity_data: dict[str, EntityOptionsLike] = {}
    composite_attachments: dict[str, tuple[str, str]] = {}
    for key in self.scene_options.keys():
        if key == "attachments_dict":
            continue
        value = getattr(self.scene_options, key)
        if isinstance(value, CompositeEntityOptions):
            # A composite (e.g. a Droid manipulator) is stored in the scene as one
            # unit and unfolds here into its sub-entities + welds. The scene stays the
            # source of truth (it round-trips as the composite); expansion is local to
            # the build. Name collisions / primary-key were checked in SceneOptions.
            sub_entities, sub_attachments = value.expand()
            entity_data.update(sub_entities)
            composite_attachments.update(sub_attachments)
        else:
            entity_data[key] = value

    # Composite welds join the scene's own attachments; both feed the topo sort below
    # and the attach loop that welds children onto parents.
    attachments_dict = {**(self.scene_options.attachments_dict or {}), **composite_attachments}
    if attachments_dict:
        # Order so each attachment parent is immediately followed by its attached
        # subtree. genesis' ``attach`` requires a welded child to be instantiated
        # *consecutively* with its parent (the merged pair is simulated as one
        # kinematic tree whose DOFs must form one contiguous range), so no other
        # DOF-carrying entity may be created between a parent and its child. A plain
        # "parents before children" topological order is not enough: free entities
        # (a table, a cube) declared between the arm and its welded gripper would sit
        # in the middle and genesis rejects the weld. Each entity has at most one
        # parent (``attachments_dict`` is keyed by child), so the graph is a forest —
        # emit each root in declaration order depth-first so a parent and its welded
        # descendants stay adjacent.
        children_of: dict[str, list[str]] = defaultdict(list)
        attached_children: set[str] = set()
        for child_entity_name, spec in attachments_dict.items():
            children_of[spec[0]].append(child_entity_name)
            attached_children.add(child_entity_name)

        ordered_keys: list[str] = []
        seen: set[str] = set()

        def _emit_subtree(name: str) -> None:
            if name in seen:  # defensive: a malformed cycle must not recurse forever
                return
            seen.add(name)
            ordered_keys.append(name)
            for child in children_of.get(name, []):
                _emit_subtree(child)

        for name in entity_data.keys():
            if name not in attached_children:  # roots: entities not welded onto anything
                _emit_subtree(name)

        # A key left unemitted means a child whose parent is missing, or a cycle among
        # attached entities (no root to reach them from).
        if len(ordered_keys) != len(entity_data):
            missing = {spec[0] for spec in attachments_dict.values() if spec[0] not in entity_data}
            if missing:
                raise ValueError(f"attachments_dict references nonexistent entities: {missing}")
            raise ValueError(f"Cycle detected in attachments_dict: {attachments_dict}")
    else:
        ordered_keys = list(entity_data.keys())

    self.entities: dict[str, Entity] = {}
    for entity_name in ordered_keys:
        entity_options: EntityOptionsLike = entity_data[entity_name]
        entity_cls = _entity_class_for_options(entity_options)
        entity = entity_cls(env=self, options=entity_options)
        entity._name = entity_name
        self.entities[entity_name] = entity

    for entity_name, spec in attachments_dict.items():
        attach_to_entity_name, attach_to_link_name = spec[0], spec[1]
        # Optional 4-tuple form carries a (pos, quat) mounting offset; the 2-tuple form
        # falls back to the child's default_root_pos/quat (see RigidEntity.attach_to).
        attach_offset, attach_quat = (spec[2], spec[3]) if len(spec) >= 4 else (None, None)
        self.entities[entity_name].attach_to(
            self.entities[attach_to_entity_name],
            attach_to_link_name,
            attach_offset=attach_offset,
            attach_quat=attach_quat,
        )

    self.cameras: dict[str, Camera] = {}
    self._setup_cameras()

    self.sensors: dict[str, Sensor] = {}
    self._setup_sensors()

Attributes

metric_manager class-attribute instance-attribute

metric_manager: MetricManager | None = None

env_options instance-attribute

env_options = env_options or EnvOptions()

scene_options instance-attribute

scene_options = scene_options or SceneOptions()

observation_options instance-attribute

observation_options = observation_options or ObservationManagerOptions()

event_options instance-attribute

event_options = event_options or EventManagerOptions()

action_options instance-attribute

action_options = action_options or ActionManagerOptions()

metric_options instance-attribute

metric_options = metric_options or MetricManagerOptions()

recorder_options instance-attribute

recorder_options = recorder_options or RecorderManagerOptions()

cameras_options instance-attribute

cameras_options = cameras_options or CamerasOptions()

sensors_options instance-attribute

sensors_options = sensors_options or SensorsOptions()

renderer_options instance-attribute

renderer_options = renderer_options or Rasterizer()

headless instance-attribute

headless = not show_viewer

training instance-attribute

training = not eval_mode

num_envs instance-attribute

num_envs = self.env_options.num_envs

sim_dt instance-attribute

sim_dt = self.env_options.sim_dt

dt instance-attribute

dt = self.env_options.decimation * self.env_options.sim_dt

episode_length_buf instance-attribute

episode_length_buf = torch.zeros(self.num_envs, device=self.device, dtype=torch.long)

obs_buf instance-attribute

obs_buf = None

reset_info instance-attribute

reset_info = None

scene instance-attribute

scene = gs.Scene(**scene_kwargs)

entities instance-attribute

entities: dict[str, Entity] = {}

cameras instance-attribute

cameras: dict[str, Camera] = {}

sensors instance-attribute

sensors: dict[str, Sensor] = {}

is_built property

is_built: bool

Whether the scene has been built.

coupler property

coupler: LegacyCoupler | SAPCoupler | IPCCoupler

rigid_solver property

rigid_solver: RigidSolver

mochi_solver property

mochi_solver: MochiSolver

The Mochi implicit multi-physics solver of this scene's Mochi entities.

See :mod:eden.engine.solvers.mochi.

RAISES DESCRIPTION
RuntimeError

If no entity of the scene takes a Mochi material, so no solver was installed.

segmentation_idx_dict property

segmentation_idx_dict: dict[int, int | tuple[int, ...]]

Genesis' segmentation id -> scene element map.

The element is a bare entity_idx at segmentation_level="entity" and an (entity_idx, link_idx, ...) tuple at "link" / "geom". To go the other way round use :attr:entity_segmentation_ids, not a manual inversion.

entity_segmentation_ids property

entity_segmentation_ids: dict[int, list[int]]

entity_idx -> [segmentation ids], the inverse of :attr:segmentation_idx_dict.

An entity owns one id per link/geom at segmentation_level="link"/"geom", so this is the accessor to mask an entity out of a segmentation image — indexing segmentation_idx_dict the other way round only ever finds one of them. Recomputed per access so it tracks a rebuilt scene rather than going stale.

device property

device: device

The torch device the environment's tensors live on.

config property

config: EdenConfig

The config describing this environment — a round trip of the config it was built from.

Built via :meth:from_config, this returns a new instance of the source config's own class (ReacherConfig, not EdenRLConfig), carrying every field the env never receives — runner_options, language_instructions, task-specific fields — verbatim, with every field :attr:_CONFIG_CLASS declares except :attr:_NON_RUNTIME_CONFIG_FIELDS read off the live env, so runtime mutations (and any None the env replaced with a default) are reflected. That is what makes env.config.save_as_json() write a file that reloads into the same task and rebuilds the same env — synthesizing a base config here instead would drop the runner and task fields with no error, since no env parameter carries them.

Constructed directly (EnvBase(env_options=..., ...)), there is no source config to round-trip: this synthesizes a plain :attr:_CONFIG_CLASS from the runtime options only, and the fields the env does not own take their class defaults.

The returned root is a fresh, unlocked object, but its option children are the live ones (not copies) — mutating them mutates the environment. Use env.config.clone() for an independent copy.

gravity property

gravity: Tensor

The gravity vector applied by the rigid solver.

fixed_shape_queries property

fixed_shape_queries: bool

Whether a query must take its fixed-shape, device-only form now: inside :meth:fixed_shape_query_scope.

A query with a data-dependent size (a contact query trimmed to the most contacts any env has) reads that size back to the host and is shaped by it. Its fixed-shape form (the padded contact query, see :meth:~eden.entities.base.Entity._query_contacts) does neither.

Methods:

from_config classmethod

from_config(cfg: TConfig, *, show_viewer: bool = False, eval_mode: bool = False) -> Self

Create an environment from a configuration object.

Inherited by every env class rather than reimplemented per leaf: a hand-copied list of option groups drops a forgotten group in silence, the config fully valid and the env simply never told. Reading the field list off :meth:_runtime_option_fields instead makes forwarding a property of the structure: that is the same list :attr:config reads back off the live env, so the two directions are symmetric by construction, and a group added to a config leaf later is forwarded without anyone remembering to. Task-specific fields are not swept in — the list comes from this env's leaf, not from type(cfg) — and :attr:config carries them across via the recorded source instead.

PARAMETER DESCRIPTION
cfg

The configuration object. Must be the leaf this env class reads — see :meth:_check_config_pairing — with every field a TaskMod binds filled in, see :meth:~eden.utils.configs.EdenConfig.check_bindings.

TYPE: TConfig

show_viewer

Whether to show the viewer.

TYPE: bool DEFAULT: False

eval_mode

Whether to run in eval mode.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
env

The constructed environment, unbuilt — call build() yourself. The training wrappers' from_config builds and resets; this one does not.

TYPE: Self

Source code in eden/envs/base.py
@classmethod
def from_config(cls, cfg: TConfig, *, show_viewer: bool = False, eval_mode: bool = False) -> Self:
    """
    Create an environment from a configuration object.

    Inherited by every env class rather than reimplemented per leaf: a hand-copied list of
    option groups drops a forgotten group in silence, the config fully valid and the env
    simply never told. Reading the field list off :meth:`_runtime_option_fields` instead makes
    forwarding a property of the *structure*: that is the same list :attr:`config` reads back
    off the live env, so the two directions are symmetric by construction, and a group added
    to a config leaf later is forwarded without anyone remembering to. Task-specific fields
    are not swept in — the list comes from this env's leaf, not from ``type(cfg)`` — and
    :attr:`config` carries them across via the recorded source instead.

    Parameters
    ----------
    cfg: TConfig
        The configuration object. Must be the leaf this env class reads — see
        :meth:`_check_config_pairing` — with every field a TaskMod binds filled in, see
        :meth:`~eden.utils.configs.EdenConfig.check_bindings`.
    show_viewer: bool
        Whether to show the viewer.
    eval_mode: bool
        Whether to run in eval mode.

    Returns
    -------
    env: Self
        The constructed environment, **unbuilt** — call ``build()`` yourself. The training
        wrappers' ``from_config`` builds and resets; this one does not.
    """
    cls._check_config_pairing(cfg)
    cfg.check_bindings()
    env = cls(
        **{name: getattr(cfg, name) for name in cls._runtime_option_fields()},
        show_viewer=show_viewer,
        eval_mode=eval_mode,
    )
    # Keeps `env.config` faithful to its source: the concrete config class plus the
    # fields no env parameter carries (runner_options, language_instructions, and any
    # task-specific ones). See :attr:`config`.
    env._source_config = cfg

    return env

register_pre_build_hook

register_pre_build_hook(hook: Callable[[], None]) -> None
Source code in eden/envs/base.py
@gs.assert_unbuilt
def register_pre_build_hook(self, hook: Callable[[], None]) -> None:
    self._pre_build_hooks.append(hook)

register_post_build_hook

register_post_build_hook(hook: Callable[[], None]) -> None

Queue hook to run once at the end of :meth:post_build_setup.

PARAMETER DESCRIPTION
hook

Zero-argument callable, invoked after every entity/camera post_build() and after the managers are loaded.

TYPE: Callable[[], None]

RAISES DESCRIPTION
RuntimeError

If :meth:post_build_setup already drained the queue, so hook would be registered too late to ever run.

Source code in eden/envs/base.py
def register_post_build_hook(self, hook: Callable[[], None]) -> None:
    """Queue ``hook`` to run once at the end of :meth:`post_build_setup`.

    Parameters
    ----------
    hook : Callable[[], None]
        Zero-argument callable, invoked after every entity/camera ``post_build()`` and
        after the managers are loaded.

    Raises
    ------
    RuntimeError
        If :meth:`post_build_setup` already drained the queue, so ``hook`` would be
        registered too late to ever run.
    """
    if self._post_build_hooks_drained:
        raise RuntimeError(
            f"register_post_build_hook({getattr(hook, '__qualname__', hook)}) was called after "
            "post_build_setup() drained the post-build hooks, so the hook would never run. "
            "Register it from __init__, from a pre-build hook, or from a manager's "
            "construction (which happens inside post_build_setup, before the drain)."
        )
    self._post_build_hooks.append(hook)

register_substep_callback

register_substep_callback(callback: Callable[[], None], *, before: bool = False) -> None

Run callback around every scene.step() of :meth:step (decimation times per control step).

For a term that has to act on the simulation state between sim steps — clamp what the solver just did, before the next step integrates it — which neither the action manager (it writes controls before each step) nor a per-control-step hook can do. A before callback runs right before scene.step(), after the action manager has applied its controls; the rest run right after it. Each runs in registration order, with no arguments, on every env; keep them cheap and sync-free, since they sit on the physics hot path.

One scene.step() advances EnvOptions.sim_dt; Genesis' own sim_substeps run inside it, so a callback never sees them.

A setter that overwrites an env's state clears the error flags its solver latched for it (the MochiSolver's and Genesis' particle solvers' do), so a callback calling one would hide the error of that sim step. While a callback is registered, the env keeps the flags of a misconfiguration after every sim step, which the next control step still raises; a corrupt state the setter overwrote is gone with its flag, as Genesis intends.

PARAMETER DESCRIPTION
callback

Zero-argument callable.

TYPE: Callable[[], None]

before

Run it before each scene.step() instead of after. Defaults to False.

TYPE: bool DEFAULT: False

Source code in eden/envs/base.py
def register_substep_callback(self, callback: Callable[[], None], *, before: bool = False) -> None:
    """Run ``callback`` around every ``scene.step()`` of :meth:`step` (``decimation`` times per control step).

    For a term that has to act on the simulation state *between* sim steps — clamp what the solver just did,
    before the next step integrates it — which neither the action manager (it writes controls *before* each
    step) nor a per-control-step hook can do. A ``before`` callback runs right before ``scene.step()``, after the
    action manager has applied its controls; the rest run right after it. Each runs in registration order, with
    no arguments, on every env; keep them cheap and sync-free, since they sit on the physics hot path.

    One ``scene.step()`` advances ``EnvOptions.sim_dt``; Genesis' own ``sim_substeps`` run inside it, so a
    callback never sees them.

    A setter that overwrites an env's state clears the error flags its solver latched for it (the MochiSolver's
    and Genesis' particle solvers' do), so a callback calling one would hide the error of that sim step. While a
    callback is registered, the env keeps the flags of a misconfiguration after every sim step, which the next
    control step still raises; a corrupt state the setter overwrote is gone with its flag, as Genesis intends.

    Parameters
    ----------
    callback : Callable[[], None]
        Zero-argument callable.
    before : bool, optional
        Run it before each ``scene.step()`` instead of after. Defaults to False.
    """
    (self._pre_substep_callbacks if before else self._substep_callbacks).append(callback)

build

build(env_spacing: Vec2FType | None = None, center_envs_at_origin: bool | None = None)

Build the underlying Genesis scene and finalize all entities.

Runs :meth:pre_build_setup, builds the vectorized scene, then runs :meth:post_build_setup. Must be called once before stepping the environment.

PARAMETER DESCRIPTION
env_spacing

Spacing between environments in the xy-plane. Falls back to env_options.env_spacing when None.

TYPE: tuple[float, float] DEFAULT: None

center_envs_at_origin

Whether to center the grid of environments at the world origin. Falls back to env_options.center_envs_at_origin when None.

TYPE: bool DEFAULT: None

Source code in eden/envs/base.py
@gs.assert_unbuilt
def build(
    self,
    env_spacing: Vec2FType | None = None,
    center_envs_at_origin: bool | None = None,
):
    """Build the underlying Genesis scene and finalize all entities.

    Runs :meth:`pre_build_setup`, builds the vectorized scene, then runs
    :meth:`post_build_setup`. Must be called once before stepping the environment.

    Parameters
    ----------
    env_spacing : tuple[float, float], optional
        Spacing between environments in the xy-plane. Falls back to
        ``env_options.env_spacing`` when None.
    center_envs_at_origin : bool, optional
        Whether to center the grid of environments at the world origin.
        Falls back to ``env_options.center_envs_at_origin`` when None.
    """
    env_spacing = env_spacing if env_spacing is not None else self.env_options.env_spacing
    center_envs_at_origin = (
        center_envs_at_origin if center_envs_at_origin is not None else self.env_options.center_envs_at_origin
    )
    self.pre_build_setup()
    self.scene.build(
        n_envs=self.num_envs,
        env_spacing=env_spacing,
        center_envs_at_origin=center_envs_at_origin,
    )
    self.post_build_setup()
    self._verify_build()

pre_build_setup

pre_build_setup()
Source code in eden/envs/base.py
@gs.assert_unbuilt
def pre_build_setup(self):
    for entity in self.entities.values():
        entity.pre_build()

    for camera in self.cameras.values():
        camera.pre_build()

    for sensor in self.sensors.values():
        sensor.pre_build()

    for hook in self._pre_build_hooks:
        hook()

post_build_setup

post_build_setup()
Source code in eden/envs/base.py
@gs.assert_built
def post_build_setup(self):
    for entity in self.entities.values():
        entity.post_build()

    for camera in self.cameras.values():
        camera.post_build()

    self._load_managers()
    self._validate_manager_order()

    # Managers register post-build hooks from their __init__ (MotionManager's motion-end
    # resync validator), so this must stay below _load_managers(). Iterating the live list lets
    # a hook queue another one; after this point registering is a loud error.
    for hook in self._post_build_hooks:
        hook()
    self._post_build_hooks_drained = True

    # NOTE: save the post setup state for reset
    self.reset_state = self.scene.get_state()

destroy

destroy()
Source code in eden/envs/base.py
def destroy(self):
    # Cameras first, so a recording still running is finalized while the scene it renders
    # is alive (see Camera.destroy). The scene is destroyed even when one of them fails:
    # a lost video is recoverable, whereas a scene left alive keeps the Quadrants SNode
    # trees every build spends, and the process runs out of them after ~100 builds.
    error = None
    for name, camera in self.cameras.items():
        try:
            camera.destroy()
        except Exception as e:  # noqa: BLE001 - teardown continues; the error is re-raised below
            en.logger.warning(f"Tearing down camera '{name}' failed: {e}")
            if error is None:
                error = e

    self.scene.destroy()

    if error is not None:
        raise error

summary

summary() -> str

Return a human-readable summary of the environment and its managers.

Source code in eden/envs/base.py
@gs.assert_built
def summary(self) -> str:
    """Return a human-readable summary of the environment and its managers."""
    msg = "Environment Summary:\n"
    msg += f"Number of environments: {self.num_envs}\n"
    msg += f"Step time step: {self.dt} s\n"
    msg += f"Simulation time step: {self.sim_dt} s\n"
    msg += f"Decimation: {self.env_options.decimation}\n"

    msg += "Managers:\n"
    msg += self.action_manager.summary()
    msg += self.event_manager.summary()
    msg += self.observation_manager.summary()
    if self.metric_manager is not None:
        msg += self.metric_manager.summary()
    msg += self.recorder_manager.summary()
    return msg

seed staticmethod

seed(seed: int) -> int

Seed all RNGs used by the environment.

Applies exactly the seed given: no DDP rank offset, and no write-back to any config. Seeding a run is :func:eden.utils.random.apply_run_seed's job — the training wrappers call it from from_config, before the env is built.

PARAMETER DESCRIPTION
seed

The seed to set. Must be non-negative; -1 is not a "draw one" sentinel (see :func:eden.utils.random.validate_seed).

TYPE: int

RETURNS DESCRIPTION
int

The seed that was actually applied.

RAISES DESCRIPTION
ValueError

If seed is negative.

Source code in eden/envs/base.py
@staticmethod
def seed(seed: int) -> int:
    """Seed all RNGs used by the environment.

    Applies exactly the seed given: no DDP rank offset, and no write-back to any
    config. Seeding a *run* is :func:`eden.utils.random.apply_run_seed`'s job — the
    training wrappers call it from ``from_config``, before the env is built.

    Parameters
    ----------
    seed : int
        The seed to set. Must be non-negative; ``-1`` is not a "draw one"
        sentinel (see :func:`eden.utils.random.validate_seed`).

    Returns
    -------
    int
        The seed that was actually applied.

    Raises
    ------
    ValueError
        If ``seed`` is negative.
    """
    seed = validate_seed(seed)
    en.logger.info(f"Setting seed: {seed}")
    set_random_seed(seed)
    return seed

get_solver_error_envs_mask

get_solver_error_envs_mask() -> Tensor

Envs a physics solver flagged since their last reset, (num_envs,) bool.

The rigid solver's errors (a NaN contact force) and, in a Mochi scene, the Mochi solver's (a diverged implicit solve). The termination manager truncates these envs, and the reward and metric managers drop what their terms read of them, so no value of the corrupt state reaches the learner; the reset clears the flag before the next error check would raise for the whole scene.

A misconfiguration of the scene (a contact capacity overflow, an unsupported collision pair, a NaN gradient under requires_grad; see _RECOVERABLE_SOLVER_ERRORS) is reset and dropped the same way, but the env keeps its flag across the reset, and the next control step raises it with the message that names the option to change (on CUDA, whose check does not wait for the device, the one after).

Source code in eden/envs/base.py
def get_solver_error_envs_mask(self) -> torch.Tensor:
    """Envs a physics solver flagged since their last reset, ``(num_envs,)`` bool.

    The rigid solver's errors (a NaN contact force) and, in a Mochi scene, the Mochi solver's (a diverged
    implicit solve). The termination manager truncates these envs, and the reward and metric managers drop what
    their terms read of them, so no value of the corrupt state reaches the learner; the reset clears the flag
    before the next error check would raise for the whole scene.

    A misconfiguration of the scene (a contact capacity overflow, an unsupported collision pair, a NaN gradient
    under ``requires_grad``; see ``_RECOVERABLE_SOLVER_ERRORS``) is reset and dropped the same way, but the env
    keeps its flag across the reset, and the next control step raises it with the message that names the option to
    change (on CUDA, whose check does not wait for the device, the one after).
    """
    mask = self.rigid_solver.get_error_envs_mask()
    if self._mochi_solver is not None:
        mask = mask | self._mochi_solver.get_error_envs_mask()
    return mask

set_global_sol_params

set_global_sol_params(sol_params) -> None

Set constraint solver parameters.

Reference: https://mujoco.readthedocs.io/en/latest/modeling.html#solver-parameters

PARAMETER DESCRIPTION
sol_params

array of length 7 in which each element corresponds to (timeconst, dampratio, dmin, dmax, width, mid, power)

Source code in eden/envs/base.py
def set_global_sol_params(self, sol_params) -> None:
    """
    Set constraint solver parameters.

    Reference: https://mujoco.readthedocs.io/en/latest/modeling.html#solver-parameters

    Parameters
    ----------
    sol_params: tuple[float] | list[float] | np.ndarray | torch.tensor
        array of length 7 in which each element corresponds to
        (timeconst, dampratio, dmin, dmax, width, mid, power)
    """
    self.rigid_solver.set_global_sol_params(sol_params)

set_sol_params

set_sol_params(sol_params, geoms_idx=None, envs_idx=None, *, joints_idx=None, eqs_idx=None) -> None

Set constraint solver parameters.

Reference: https://mujoco.readthedocs.io/en/latest/modeling.html#solver-parameters

PARAMETER DESCRIPTION
sol_params

array of length 7 in which each element corresponds to (timeconst, dampratio, dmin, dmax, width, mid, power)

geoms_idx

Scene-global indices of the geoms to set parameters for — geom.idx, not a position in entity.geoms (that would be geom.idx - entity.geom_start). If None, all geoms are used. Defaults to None.

DEFAULT: None

envs_idx

Indices of the environments to set parameters for. If None, all environments are used. Defaults to None.

DEFAULT: None

joints_idx

Indices of the joints to set parameters for. If None, no joints are targeted. Defaults to None.

DEFAULT: None

eqs_idx

Indices of the equality constraints to set parameters for. If None, none are targeted. Defaults to None.

DEFAULT: None

Source code in eden/envs/base.py
def set_sol_params(
    self,
    sol_params,
    geoms_idx=None,
    envs_idx=None,
    *,
    joints_idx=None,
    eqs_idx=None,
) -> None:
    """
    Set constraint solver parameters.

    Reference: https://mujoco.readthedocs.io/en/latest/modeling.html#solver-parameters

    Parameters
    ----------
    sol_params: tuple[float] | list[float] | np.ndarray | torch.tensor
        array of length 7 in which each element corresponds to
        (timeconst, dampratio, dmin, dmax, width, mid, power)
    geoms_idx: array_like, optional
        Scene-global indices of the geoms to set parameters for — ``geom.idx``, not a position in
        ``entity.geoms`` (that would be ``geom.idx - entity.geom_start``). If None, all geoms are used.
        Defaults to None.
    envs_idx: array_like, optional
        Indices of the environments to set parameters for. If None, all environments are used. Defaults to None.
    joints_idx: array_like, optional
        Indices of the joints to set parameters for. If None, no joints are targeted. Defaults to None.
    eqs_idx: array_like, optional
        Indices of the equality constraints to set parameters for. If None, none are targeted. Defaults to None.
    """
    self.rigid_solver.set_sol_params(
        sol_params,
        geoms_idx=geoms_idx,
        envs_idx=envs_idx,
        joints_idx=joints_idx,
        eqs_idx=eqs_idx,
    )

compute_once

compute_once(key: Hashable, producer: Callable[[], _R]) -> _R

producer(), evaluated at most once per pass for each key.

For terms that derive different values from one intermediate — a body-position and a body-rotation error from the same anchor-relative transform, say — so a pass computes it once rather than once per term. A pass (:meth:compute_pass) spans code that only reads the simulation, so the first result holds for the rest of it: each manager's term loop opens one, and RLEnvBase.step opens one across its whole graded phase (metrics, the observation tick, terminations, rewards), which grades one post-physics state. The memo is dropped when the pass ends, and the next pass starts empty. Outside a pass (a term's compute() called on its own) producer simply runs, so a result is never served stale. Every term sharing key receives the same object: treat it as read-only.

PARAMETER DESCRIPTION
key

Names the intermediate, including every argument it depends on (entity, link names, ...): two calls with one key, from any term of any manager, are assumed to want the same value.

TYPE: Hashable

producer

Computes the intermediate.

TYPE: Callable[[], T]

RETURNS DESCRIPTION
T

producer()'s result, from this pass's memo when an earlier term already produced it.

Source code in eden/envs/base.py
def compute_once(self, key: Hashable, producer: Callable[[], _R]) -> _R:
    """``producer()``, evaluated at most once per pass for each ``key``.

    For terms that derive different values from one intermediate — a body-position and a body-rotation
    error from the same anchor-relative transform, say — so a pass computes it once rather than once per
    term. A pass (:meth:`compute_pass`) spans code that only reads the simulation, so the first result holds
    for the rest of it: each manager's term loop opens one, and ``RLEnvBase.step`` opens one across its whole
    graded phase (metrics, the observation tick, terminations, rewards), which grades one post-physics
    state. The memo is dropped when the pass ends, and the next pass starts empty. Outside a pass (a term's
    ``compute()`` called on its own) ``producer`` simply runs, so a result is never served stale. Every term
    sharing ``key`` receives the same object: treat it as read-only.

    Parameters
    ----------
    key : Hashable
        Names the intermediate, including every argument it depends on (entity, link names, ...): two calls
        with one key, from any term of any manager, are assumed to want the same value.
    producer : Callable[[], T]
        Computes the intermediate.

    Returns
    -------
    T
        ``producer()``'s result, from this pass's memo when an earlier term already produced it.
    """
    cache = self._pass_cache
    if cache is None:
        return producer()
    if key not in cache:
        cache[key] = producer()
    return cache[key]

compute_pass

compute_pass() -> Iterator[None]

Open a :meth:compute_once pass for the duration of the block, unless one is already open.

A pass opened inside another joins it and leaves it open on exit, so a manager's term loop run inside the graded phase shares the phase's memo.

Source code in eden/envs/base.py
@contextmanager
def compute_pass(self) -> Iterator[None]:
    """Open a :meth:`compute_once` pass for the duration of the block, unless one is already open.

    A pass opened inside another joins it and leaves it open on exit, so a manager's term loop run inside
    the graded phase shares the phase's memo.
    """
    if self._pass_cache is not None:
        yield
        return
    self._pass_cache = {}
    try:
        yield
    finally:
        self._pass_cache = None

fixed_shape_query_scope

fixed_shape_query_scope() -> Iterator[None]

Make every query in the block take its fixed-shape form (:attr:fixed_shape_queries).

The CUDA graph of the graded phase (EnvOptions.cuda_graph_options) runs each graded step in one, the warm-up, the eager validation runs and the capture alike: a capture cannot record a host read, and the runs it compares bit for bit must reduce over the same shapes. A scope opened inside another leaves it open on exit.

Source code in eden/envs/base.py
@contextmanager
def fixed_shape_query_scope(self) -> Iterator[None]:
    """Make every query in the block take its fixed-shape form (:attr:`fixed_shape_queries`).

    The CUDA graph of the graded phase (``EnvOptions.cuda_graph_options``) runs each graded step in one, the
    warm-up, the eager validation runs and the capture alike: a capture cannot record a host read, and the
    runs it compares bit for bit must reduce over the same shapes. A scope opened inside another leaves it
    open on exit.
    """
    previous = self._fixed_shape_queries
    self._fixed_shape_queries = True
    try:
        yield
    finally:
        self._fixed_shape_queries = previous

get_entity

get_entity(name: str) -> 'Entity'

Return the entity registered under name, or raise naming the ones that exist.

PARAMETER DESCRIPTION
name

The entity's name.

TYPE: str

RETURNS DESCRIPTION
Entity

The matching entity.

RAISES DESCRIPTION
KeyError

If no entity is registered under name.

Source code in eden/envs/base.py
def get_entity(self, name: str) -> "Entity":
    """Return the entity registered under ``name``, or raise naming the ones that exist.

    Parameters
    ----------
    name : str
        The entity's name.

    Returns
    -------
    Entity
        The matching entity.

    Raises
    ------
    KeyError
        If no entity is registered under ``name``.
    """
    return resolve_named_entity(self.entities, name)

get_entities

get_entities(names: str | list[str]) -> list['Entity']

Get entities by name(s) with regex pattern matching support.

This method uses re.match() which matches patterns from the beginning of the entity name. To match entities containing a pattern anywhere, prefix with .* (e.g., ".*arm" to match entities containing "arm"; add $ to anchor the end).

PARAMETER DESCRIPTION
names

A single regex pattern string or a list of regex pattern strings to match against entity names. Patterns are matched from the beginning of entity names.

TYPE: str | list[str]

RETURNS DESCRIPTION
list[Entity]

A list of entities whose names match any of the provided regex patterns.

Examples:

>>> # Match all entities starting with "robot"
>>> env.get_entities("robot.*")
>>> # Match entities containing "_<number>" (won't match "robot" without a suffix)
>>> env.get_entities(".*_[0-9]+")
>>> # Match multiple patterns
>>> env.get_entities(["robot_[0-9]+", "box.*"])
>>> # Exact match
>>> env.get_entities("^robot$")  # or simply "robot$"
Source code in eden/envs/base.py
def get_entities(self, names: str | list[str]) -> list["Entity"]:
    """
    Get entities by name(s) with regex pattern matching support.

    This method uses `re.match()` which matches patterns from the beginning
    of the entity name. To match entities containing a pattern anywhere,
    prefix with `.*` (e.g., `".*arm"` to match entities containing "arm"; add `$` to anchor the end).

    Parameters
    ----------
    names : str | list[str]
        A single regex pattern string or a list of regex pattern strings
        to match against entity names. Patterns are matched from the
        beginning of entity names.

    Returns
    -------
    list[Entity]
        A list of entities whose names match any of the provided regex patterns.

    Examples
    --------
    >>> # Match all entities starting with "robot"
    >>> env.get_entities("robot.*")
    >>> # Match entities containing "_<number>" (won't match "robot" without a suffix)
    >>> env.get_entities(".*_[0-9]+")
    >>> # Match multiple patterns
    >>> env.get_entities(["robot_[0-9]+", "box.*"])
    >>> # Exact match
    >>> env.get_entities("^robot$")  # or simply "robot$"
    """
    # Normalize input to a list
    patterns = [names] if isinstance(names, str) else names

    matched_entities = []
    for entity_name in self.entities.keys():
        for pattern in patterns:
            if re.match(pattern, entity_name):
                matched_entities.append(self.entities[entity_name])
                break  # Avoid adding the same entity multiple times

    return matched_entities

get_camera

get_camera(name: str) -> 'Camera'

Return the camera registered under name.

PARAMETER DESCRIPTION
name

The camera's name.

TYPE: str

RETURNS DESCRIPTION
Camera

The matching camera.

Source code in eden/envs/base.py
def get_camera(self, name: str) -> "Camera":
    """Return the camera registered under ``name``.

    Parameters
    ----------
    name : str
        The camera's name.

    Returns
    -------
    Camera
        The matching camera.
    """
    return self.cameras[name]

reset

reset(envs_idx: slice | Tensor | None = None, *, seed: int | None = None) -> VecEnvReset

Reset the given environments and return their fresh observations.

PARAMETER DESCRIPTION
envs_idx

The environments to reset. If None, all environments are reset.

TYPE: slice | Tensor DEFAULT: None

seed

If given, seed the RNGs before resetting.

TYPE: int DEFAULT: None

RETURNS DESCRIPTION
VecEnvReset

A (observations, extras) tuple, where extras carries reset logging info under the "log" key.

Source code in eden/envs/base.py
def reset(
    self,
    envs_idx: slice | torch.Tensor | None = None,
    *,
    seed: int | None = None,
) -> VecEnvReset:
    """Reset the given environments and return their fresh observations.

    Parameters
    ----------
    envs_idx : slice | torch.Tensor, optional
        The environments to reset. If None, all environments are reset.
    seed : int, optional
        If given, seed the RNGs before resetting.

    Returns
    -------
    VecEnvReset
        A ``(observations, extras)`` tuple, where ``extras`` carries reset
        logging info under the ``"log"`` key.
    """
    if envs_idx is None:
        envs_idx = slice(None)
    if seed is not None:
        self.seed(seed)

    self.recorder_manager._on_reset_started(envs_idx)
    with sharing_env_indices(envs_idx):
        self.reset_info = self._reset_idx(envs_idx)
        self._dispatch_post_reset()
    self.recorder_manager._on_reset_finished(envs_idx=envs_idx)
    self.clear_debug_objects()

    self.obs_buf = self.observation_manager.compute(update_history=True)

    return self.obs_buf, {"log": self.reset_info}

step

step(action: Tensor | dict[str, Tensor] | None = None) -> VecEnvStep

Advance the environment by one control step.

Applies action through the action manager, runs decimation physics sub-steps, and computes observations. EnvBase itself has no reward or termination; subclasses (:class:RLEnvBase) extend this.

PARAMETER DESCRIPTION
action

The action to apply. If None, no action is applied (the sim still steps).

TYPE: Tensor | dict[str, Tensor] DEFAULT: None

RETURNS DESCRIPTION
VecEnvStep

A (obs, reward, terminated, timeouts, info) tuple. EnvBase has no reward or termination, so reward, terminated, and timeouts are always None.

Source code in eden/envs/base.py
def step(self, action: torch.Tensor | dict[str, torch.Tensor] | None = None) -> VecEnvStep:
    """Advance the environment by one control step.

    Applies ``action`` through the action manager, runs ``decimation`` physics
    sub-steps, and computes observations. ``EnvBase`` itself has no reward or
    termination; subclasses (:class:`RLEnvBase`) extend this.

    Parameters
    ----------
    action : torch.Tensor | dict[str, torch.Tensor], optional
        The action to apply. If None, no action is applied (the sim still steps).

    Returns
    -------
    VecEnvStep
        A ``(obs, reward, terminated, timeouts, info)`` tuple. ``EnvBase``
        has no reward or termination, so ``reward``, ``terminated``, and
        ``timeouts`` are always ``None``.
    """
    if action is not None:
        self.action_manager.compute(action)

    self.recorder_manager._on_step_started()

    self._run_sim_steps(apply_actions=action is not None)

    self.episode_length_buf += 1

    # NOTE: cameras are deliberately NOT rendered here — the camera API is
    # pull-based, and `start_recording` captures one frame per `render()`
    # call. See `Camera.start_recording`.

    if self.metric_manager is not None:
        self.metric_manager.compute()

    # Phase 7: the once-per-step tick for stateful observation terms, on the post-physics
    # state and after the metrics so a term may integrate one. See
    # ObservationManager.post_physics_refresh for the contract.
    self.observation_manager.post_physics_refresh()

    # Close the recorded step here — below the phases that grade it, above the ones that
    # prepare the next — which is the position it holds in ``RLEnvBase.step`` (the two bodies
    # agree phase-for-phase, pinned by ``tests/envs/test_base.py``). Closing it after the
    # INTERVAL events would corrupt every recorder term that reads simulation state in
    # ``record_post_step()``: an INTERVAL-mode randomizer (``PushByVelocity`` writes the base
    # DOFs outright) perturbs the robot for the *next* step, so ``SysIDRecorder`` would log
    # "after action A the velocity was V" with a V no physics had produced.
    self.recorder_manager._on_step_finished()

    if _INTERVAL in self.event_manager.available_modes:
        self.event_manager.compute(mode=_INTERVAL, dt=self.dt)

    self.obs_buf = self.observation_manager.compute(update_history=True)

    # NOTE: to match the RLEnvBase.step() return type for consistency
    return (self.obs_buf, None, None, None, {"log": self.reset_info})

RLEnvBase

RLEnvBase(env_options: EnvOptions | None = None, scene_options: SceneOptions | None = None, observation_options: ObservationManagerOptions | None = None, reward_options: RewardManagerOptions | None = None, termination_options: TerminationManagerOptions | None = None, event_options: EventManagerOptions | None = None, curriculum_options: CurriculumManagerOptions | None = None, command_options: CommandManagerOptions | None = None, action_options: ActionManagerOptions | None = None, metric_options: MetricManagerOptions | None = None, recorder_options: RecorderManagerOptions | None = None, cameras_options: CamerasOptions | None = None, sensors_options: SensorsOptions | None = None, renderer_options: RendererOptions | None = None, *, show_viewer: bool = False, eval_mode: bool = False, **kwargs)

Bases: EnvBase[EdenRLConfig]

Base class for all RL environments.

PARAMETER DESCRIPTION
env_options

Environment options.

TYPE: EnvOptions | None DEFAULT: None

scene_options

Scene options.

TYPE: SceneOptions | None DEFAULT: None

observation_options

Observation manager options.

TYPE: ObservationManagerOptions | None DEFAULT: None

reward_options

Reward manager options.

TYPE: RewardManagerOptions | None DEFAULT: None

termination_options

Termination manager options.

TYPE: TerminationManagerOptions | None DEFAULT: None

event_options

Event manager options.

TYPE: EventManagerOptions | None DEFAULT: None

curriculum_options

Curriculum manager options.

TYPE: CurriculumManagerOptions | None DEFAULT: None

command_options

Command manager options.

TYPE: CommandManagerOptions | None DEFAULT: None

action_options

Action manager options.

TYPE: ActionManagerOptions | None DEFAULT: None

metric_options

Metric manager options.

TYPE: MetricManagerOptions | None DEFAULT: None

recorder_options

Recorder manager options.

TYPE: RecorderManagerOptions | None DEFAULT: None

cameras_options

Cameras options.

TYPE: CamerasOptions | None DEFAULT: None

sensors_options

Sensors options.

TYPE: SensorsOptions | None DEFAULT: None

renderer_options

Renderer options.

TYPE: RendererOptions | None DEFAULT: None

show_viewer

Whether to show the viewer.

TYPE: bool DEFAULT: False

eval_mode

Whether to run in eval mode.

TYPE: bool DEFAULT: False

**kwargs

Forwarded to gs.Scene (see :class:EnvBase).

DEFAULT: {}

Source code in eden/envs/base.py
def __init__(
    self,
    env_options: EnvOptions | None = None,
    scene_options: SceneOptions | None = None,
    observation_options: ObservationManagerOptions | None = None,
    reward_options: RewardManagerOptions | None = None,
    termination_options: TerminationManagerOptions | None = None,
    event_options: EventManagerOptions | None = None,
    curriculum_options: CurriculumManagerOptions | None = None,
    command_options: CommandManagerOptions | None = None,
    action_options: ActionManagerOptions | None = None,
    metric_options: MetricManagerOptions | None = None,
    recorder_options: RecorderManagerOptions | None = None,
    cameras_options: CamerasOptions | None = None,
    sensors_options: SensorsOptions | None = None,
    renderer_options: RendererOptions | None = None,
    *,
    show_viewer: bool = False,
    eval_mode: bool = False,
    **kwargs,
):
    super().__init__(
        env_options=env_options,
        scene_options=scene_options,
        observation_options=observation_options,
        event_options=event_options,
        action_options=action_options,
        metric_options=metric_options,
        recorder_options=recorder_options,
        cameras_options=cameras_options,
        sensors_options=sensors_options,
        renderer_options=renderer_options,
        show_viewer=show_viewer,
        eval_mode=eval_mode,
        **kwargs,
    )
    # Handling of default arguments
    self.reward_options = reward_options or RewardManagerOptions()
    self.termination_options = termination_options or TerminationManagerOptions()
    self.curriculum_options = curriculum_options or CurriculumManagerOptions()
    self.command_options = command_options or CommandManagerOptions()

    self.max_episode_length_s = self.env_options.episode_length_s
    if not np.isfinite(self.max_episode_length_s) or self.max_episode_length_s <= 0.0:
        # inf/NaN would die two lines below with an opaque OverflowError,
        # and <= 0 would truncate every env on step one;
        # `max_episode_length` feeds integer buffers and the rsl_rl runner.
        raise ValueError(
            f"env_options.episode_length_s must be a positive finite value, got "
            f"{self.max_episode_length_s}. For unbounded episodes, keep a finite horizon "
            "and omit the `time_out` termination term instead — truncation then never fires."
        )
    self.max_episode_length = int(np.ceil(self.max_episode_length_s / self.dt))
    self.common_step_counter = 0

    # Lazily allocated zero-initialized cache for ``extras['final_observations']``.
    # See ``EnvOptions.record_final_observations`` and ``RLEnvBase.step()``.
    self._final_obs_cache: dict[str, torch.Tensor | dict[str, torch.Tensor]] = {}

Attributes

command_manager class-attribute instance-attribute

command_manager: CommandManager | None = None

curriculum_manager class-attribute instance-attribute

curriculum_manager: CurriculumManager | None = None

reward_options instance-attribute

reward_options = reward_options or RewardManagerOptions()

termination_options instance-attribute

termination_options = termination_options or TerminationManagerOptions()

curriculum_options instance-attribute

curriculum_options = curriculum_options or CurriculumManagerOptions()

command_options instance-attribute

command_options = command_options or CommandManagerOptions()

max_episode_length_s instance-attribute

max_episode_length_s = self.env_options.episode_length_s

max_episode_length instance-attribute

max_episode_length = int(np.ceil(self.max_episode_length_s / self.dt))

common_step_counter instance-attribute property writable

common_step_counter: int = 0

Control steps taken since construction, one count shared by every env.

Reading it while the graded phase is captured (EnvOptions.cuda_graph_options) raises :class:~eden.envs.graded_graph.BakedHostReadError: the graph would replay the count it read on every later step.

unwrapped property

unwrapped: 'RLEnvBase'

The underlying environment (returns self; provided for wrapper compatibility).

Methods:

graded_capture_scope

graded_capture_scope() -> Iterator[None]

Refuse reads of :attr:common_step_counter in the block, which captures the graded phase.

:class:~eden.envs.graded_graph.GradedPhaseGraph captures in one: the graph would replay the count a term read there on every later step. Leaving it restores the guard as it found it, so a nested scope leaves the outer one open.

Source code in eden/envs/base.py
@contextmanager
def graded_capture_scope(self) -> Iterator[None]:
    """Refuse reads of :attr:`common_step_counter` in the block, which captures the graded phase.

    :class:`~eden.envs.graded_graph.GradedPhaseGraph` captures in one: the graph would replay the count a
    term read there on every later step. Leaving it restores the guard as it found it, so a nested scope
    leaves the outer one open.
    """
    previous = self._graded_capture_active
    self._graded_capture_active = True
    try:
        yield
    finally:
        self._graded_capture_active = previous

summary

summary() -> str

Return a human-readable summary, including RL managers (reward, termination, etc.).

Source code in eden/envs/base.py
@gs.assert_built
def summary(self) -> str:
    """Return a human-readable summary, including RL managers (reward, termination, etc.)."""
    msg = super().summary()
    if self.command_manager is not None:
        msg += self.command_manager.summary()
    if self.curriculum_manager is not None:
        msg += self.curriculum_manager.summary()
    msg += self.reward_manager.summary()
    msg += self.termination_manager.summary()
    return msg

step

step(action: Tensor | dict[str, Tensor] | None = None) -> VecEnvStep

Advance the RL environment by one control step.

Applies action, runs decimation physics sub-steps, then computes metrics, terminations, and rewards. Done environments are auto-reset after reward/termination but before observation, so the returned observations reflect the fresh post-reset state for those environments.

PARAMETER DESCRIPTION
action

The action to apply. If None, no action is applied (the sim still steps).

TYPE: Tensor | dict[str, Tensor] DEFAULT: None

RETURNS DESCRIPTION
VecEnvStep

A (obs, reward, terminated, timeouts, info) tuple.

Source code in eden/envs/base.py
def step(
    self,
    action: torch.Tensor | dict[str, torch.Tensor] | None = None,
) -> VecEnvStep:
    """Advance the RL environment by one control step.

    Applies ``action``, runs ``decimation`` physics sub-steps, then computes
    metrics, terminations, and rewards. Done environments are auto-reset
    **after** reward/termination but **before** observation, so the returned
    observations reflect the fresh post-reset state for those environments.

    Parameters
    ----------
    action : torch.Tensor | dict[str, torch.Tensor], optional
        The action to apply. If None, no action is applied (the sim still steps).

    Returns
    -------
    VecEnvStep
        A ``(obs, reward, terminated, timeouts, info)`` tuple.
    """
    if action is not None:
        self.action_manager.compute(action)

    self.recorder_manager._on_step_started()

    # Advance command references BEFORE physics (no-op unless a command term
    # overrides pre_physics_advance, e.g. MotionCommand). Advance-then-grade:
    # advancing the reference before the physics sub-steps makes the post-
    # physics robot get graded against the temporally-aligned reference frame
    # (matching MotionManager) instead of a one-control-step-stale one. Any
    # wrap-teleport therefore precedes physics, keeping forward kinematics fresh.
    if self.command_manager is not None:
        self.command_manager.pre_physics_advance()

    self._run_sim_steps(apply_actions=action is not None)

    # Update env counters.
    self.episode_length_buf += 1
    self.common_step_counter += 1

    # Refresh command terms' cached robot state on the just-computed post-physics
    # state, so metric/termination/reward grade the CURRENT step (S_N) rather than
    # the previous one. Matches IsaacLab/mjlab. See CommandManager.post_physics_refresh
    # for the ordering contract.
    if self.command_manager is not None:
        self.command_manager.post_physics_refresh()

    # NOTE: cameras are deliberately NOT rendered here — the camera API is
    # pull-based, and `start_recording` captures one frame per `render()`
    # call. See `Camera.start_recording`.

    # Phases 6-9: metrics, the observation tick, terminations and rewards, from a CUDA graph when
    # EnvOptions.cuda_graph_options asks for one.
    if self._graded_graph is not None:
        self.reset_buf, self.reward_buf = self._graded_graph()
    else:
        self.reset_buf, self.reward_buf = self._compute_graded()
    self.reset_terminated = self.termination_manager.terminated
    self.reset_timeouts = self.termination_manager.timeouts
    # Gate on is_recording so `.any()` doesn't force a GPU→CPU sync every step during training.
    if self.recorder_manager.is_recording:
        if self.metric_manager is not None and hasattr(self.metric_manager, "success_buf") and self.reset_buf.any():
            self.recorder_manager.set_episode_success(
                self.reset_buf, self.metric_manager.success_buf[self.reset_buf]
            )
    self.recorder_manager._on_step_finished(self.reset_buf)

    # Snapshot pre-reset observations for done envs (opt-in via
    # ``EnvOptions.record_final_observations``). Must run before
    # ``_reset_idx`` since ``scene.reset`` wipes the done envs' state.
    if self.env_options.record_final_observations:
        final_obs = self._record_final_observations()
    else:
        final_obs = None

    # Always call _reset_idx — bool-mask ops are no-ops for all-False. The mask's ids are taken once
    # here, for every consumer that gathers the resetting rows, and its count gates the reset: one
    # GPU->CPU sync for the whole reset rather than one per consumer.
    with sharing_env_indices(self.reset_buf):
        self.reset_info = self._reset_idx(self.reset_buf)
        # Terms rebuild per-step caches from the final post-reset state, before the
        # recorder snapshots new-episode data and observations read them below.
        self._dispatch_post_reset()

    # Record new-episode data after reset, such as initial state snapshots.
    self.recorder_manager._on_reset_finished(envs_idx=self.reset_buf)

    # Curricula are frozen at eval. The gate lives in CurriculumManager, not here, so that it
    # covers reset() as well — see CurriculumManager.compute for why difficulty must not move
    # during an eval rollout, and why the freeze has to be all-or-nothing.
    if self.curriculum_manager is not None:
        self.curriculum_manager.compute()

    if self.command_manager is not None:
        self.command_manager.compute(dt=self.dt)

    if _INTERVAL in self.event_manager.available_modes:
        self.event_manager.compute(mode=_INTERVAL, dt=self.dt)

    self.obs_buf = self.observation_manager.compute(update_history=True)

    if not self.training and self.command_manager is not None:
        self.command_manager.draw_vis()

    extras: dict[str, Any] = {"log": self.reset_info}
    if final_obs is not None:
        extras["final_observations"] = final_obs

    return (
        self.obs_buf,
        self.reward_buf,
        self.reset_terminated,
        self.reset_timeouts,
        extras,
    )

post_build_setup

post_build_setup()
Source code in eden/envs/base.py
@gs.assert_built
def post_build_setup(self):
    super().post_build_setup()
    if self.env_options.cuda_graph_options is not None:
        self._graded_graph = GradedPhaseGraph(self, self._compute_graded, self.env_options.cuda_graph_options)

MimicRLEnvBase

MimicRLEnvBase(env_options: EnvOptions | None = None, scene_options: SceneOptions | None = None, observation_options: ObservationManagerOptions | None = None, reward_options: RewardManagerOptions | None = None, termination_options: TerminationManagerOptions | None = None, event_options: EventManagerOptions | None = None, curriculum_options: CurriculumManagerOptions | None = None, command_options: CommandManagerOptions | None = None, action_options: ActionManagerOptions | None = None, metric_options: MetricManagerOptions | None = None, recorder_options: RecorderManagerOptions | None = None, motion_options: MotionManagerOptions | None = None, cameras_options: CamerasOptions | None = None, sensors_options: SensorsOptions | None = None, renderer_options: RendererOptions | None = None, *, show_viewer: bool = False, eval_mode: bool = False, **kwargs)

Bases: RLEnvBase, EnvBase[EdenMimicConfig]

Base class for all Mimic RL environments.

The extra EnvBase[EdenMimicConfig] base is how this leaf re-binds its config: RLEnvBase is itself instantiable so it binds EdenRLConfig outright rather than staying generic, which leaves it unsubscriptable — a plain MimicRLEnvBase(RLEnvBase) would inherit that binding.

Source code in eden/envs/base.py
def __init__(
    self,
    env_options: EnvOptions | None = None,
    scene_options: SceneOptions | None = None,
    observation_options: ObservationManagerOptions | None = None,
    reward_options: RewardManagerOptions | None = None,
    termination_options: TerminationManagerOptions | None = None,
    event_options: EventManagerOptions | None = None,
    curriculum_options: CurriculumManagerOptions | None = None,
    command_options: CommandManagerOptions | None = None,
    action_options: ActionManagerOptions | None = None,
    metric_options: MetricManagerOptions | None = None,
    recorder_options: RecorderManagerOptions | None = None,
    motion_options: MotionManagerOptions | None = None,
    cameras_options: CamerasOptions | None = None,
    sensors_options: SensorsOptions | None = None,
    renderer_options: RendererOptions | None = None,
    *,
    show_viewer: bool = False,
    eval_mode: bool = False,
    **kwargs,
):
    super().__init__(
        env_options=env_options,
        scene_options=scene_options,
        observation_options=observation_options,
        reward_options=reward_options,
        termination_options=termination_options,
        event_options=event_options,
        curriculum_options=curriculum_options,
        command_options=command_options,
        action_options=action_options,
        metric_options=metric_options,
        recorder_options=recorder_options,
        cameras_options=cameras_options,
        sensors_options=sensors_options,
        renderer_options=renderer_options,
        show_viewer=show_viewer,
        eval_mode=eval_mode,
        **kwargs,
    )

    self.motion_options = motion_options or MotionManagerOptions()

Attributes

motion_options instance-attribute

motion_options = motion_options or MotionManagerOptions()

Methods:

summary

summary() -> str

Return a human-readable summary, including the motion manager.

Source code in eden/envs/base.py
@gs.assert_built
def summary(self) -> str:
    """Return a human-readable summary, including the motion manager."""
    msg = super().summary()
    msg += self.motion_manager.summary()
    return msg

step

step(action: Tensor | dict[str, Tensor] | None = None) -> VecEnvStep

Advance the motion-imitation environment by one control step.

Updates the motion reference for the current step, then delegates to :meth:RLEnvBase.step.

PARAMETER DESCRIPTION
action

The action to apply. If None, no action is applied (the sim still steps).

TYPE: Tensor | dict[str, Tensor] DEFAULT: None

RETURNS DESCRIPTION
VecEnvStep

A (obs, reward, terminated, timeouts, info) tuple.

Source code in eden/envs/base.py
def step(
    self,
    action: torch.Tensor | dict[str, torch.Tensor] | None = None,
) -> VecEnvStep:
    """Advance the motion-imitation environment by one control step.

    Updates the motion reference for the current step, then delegates to
    :meth:`RLEnvBase.step`.

    Parameters
    ----------
    action : torch.Tensor | dict[str, torch.Tensor], optional
        The action to apply. If None, no action is applied (the sim still steps).

    Returns
    -------
    VecEnvStep
        A ``(obs, reward, terminated, timeouts, info)`` tuple.
    """
    self.motion_manager.compute(dt=self.dt)
    return super().step(action)

env_class_for_config

env_class_for_config(cfg: EdenConfig) -> type[EnvBase]

Return the env class that builds cfg.

Maps each config leaf to its env class, mirroring the parallel hierarchies:

  • EdenMimicConfig → MimicRLEnvBase
  • EdenSplatRLConfig → SplatRLEnvBase
  • EdenSplatConfig → SplatEnvBase
  • EdenRLConfig → RLEnvBase
  • EdenNyxSplatConfig → NyxSplatEnvBase
  • EdenConfig → EnvBase

The single dispatch point for config→env-class, shared by the from_config wrappers, the CLI play path, and the smoke harness. The configs form an inheritance chain (EdenMimicConfig, EdenSplatRLConfig ⊂ EdenRLConfig ⊂ EdenConfig, and EdenSplatConfig / EdenNyxSplatConfig ⊂ EdenConfig), so the pairs are checked most-specific first — EdenSplatRLConfig before the generic EdenRLConfig branch that would otherwise shadow it, and EdenSplatConfig before the EdenConfig fallback.

The splat env classes are imported lazily: they pull optional renderer dependencies, and a config that is not a splat config must not require them. A splat config that is passed while those dependencies are missing gets the ImportError — that is the actionable failure, and far better than a silent downgrade to RLEnvBase, which would build an env with no splat renderer and no error.

The reverse direction is not stated here: each env declares its own leaf as :attr:EnvBase._CONFIG_CLASS, following the definition-site convention Eden uses for tasks rather than keeping a second central index. Deduplicating the two would mean deriving one by reflection over __subclasses__ — import-order dependent — so they are pinned to each other by tests/envs/test_base.py::test_every_leaf_accepts_its_own_config_and_only_the_two_keywords instead. A new config leaf must therefore update both, and that test says so if it doesn't.

Source code in eden/envs/base.py
def env_class_for_config(cfg: EdenConfig) -> type[EnvBase]:
    """Return the env class that builds ``cfg``.

    Maps each config leaf to its env class, mirroring the parallel hierarchies:

    * ``EdenMimicConfig`` → ``MimicRLEnvBase``
    * ``EdenSplatRLConfig`` → ``SplatRLEnvBase``
    * ``EdenSplatConfig``   → ``SplatEnvBase``
    * ``EdenRLConfig``      → ``RLEnvBase``
    * ``EdenNyxSplatConfig`` → ``NyxSplatEnvBase``
    * ``EdenConfig``        → ``EnvBase``

    The single *dispatch* point for config→env-class, shared by the ``from_config``
    wrappers, the CLI play path, and the smoke harness. The configs form an inheritance chain
    (``EdenMimicConfig``, ``EdenSplatRLConfig`` ⊂ ``EdenRLConfig`` ⊂ ``EdenConfig``, and
    ``EdenSplatConfig`` / ``EdenNyxSplatConfig`` ⊂ ``EdenConfig``), so the pairs are checked
    most-specific first — ``EdenSplatRLConfig`` before the generic ``EdenRLConfig`` branch that
    would otherwise shadow it, and ``EdenSplatConfig`` before the ``EdenConfig`` fallback.

    The splat env classes are imported lazily: they pull optional renderer dependencies, and a
    config that is not a splat config must not require them. A splat config that *is* passed
    while those dependencies are missing gets the ``ImportError`` — that is the actionable
    failure, and far better than a silent downgrade to ``RLEnvBase``, which would build an env
    with no splat renderer and no error.

    The reverse direction is not stated here: each env declares its own leaf as
    :attr:`EnvBase._CONFIG_CLASS`, following the definition-site convention Eden uses for
    tasks rather than keeping a second central index. Deduplicating the two would mean
    deriving one by reflection over ``__subclasses__`` — import-order dependent — so they are
    pinned to each other by
    ``tests/envs/test_base.py::test_every_leaf_accepts_its_own_config_and_only_the_two_keywords``
    instead. A new config leaf must therefore update both, and that test says so if it doesn't.
    """
    from eden.utils.configs import (
        EdenMimicConfig,
        EdenNyxSplatConfig,
        EdenRLConfig,
        EdenSplatConfig,
        EdenSplatRLConfig,
    )

    if isinstance(cfg, EdenNyxSplatConfig):
        from eden.envs.nyx_splat import NyxSplatEnvBase

        return NyxSplatEnvBase
    # Checked before EdenRLConfig below: EdenSplatRLConfig subclasses it, so the generic RL branch
    # would otherwise shadow it (and drop splat_options silently).
    if isinstance(cfg, EdenSplatRLConfig):
        from eden.envs.splat import SplatRLEnvBase

        return SplatRLEnvBase
    # Checked before the EdenConfig fallback below (it is an EdenConfig, not an EdenRLConfig).
    if isinstance(cfg, EdenSplatConfig):
        from eden.envs.splat import SplatEnvBase

        return SplatEnvBase

    for cfg_cls, env_cls in (
        (EdenMimicConfig, MimicRLEnvBase),
        (EdenRLConfig, RLEnvBase),
        (EdenConfig, EnvBase),
    ):
        if isinstance(cfg, cfg_cls):
            return env_cls
    raise TypeError(f"No env class for config of type {type(cfg).__name__}")

Gaussian-splat environments

Environments that composite a Gaussian-splat background behind the simulated scene, with the gsplat backend (SplatEnvBase, SplatRLEnvBase) or the Nyx path tracer (NyxSplatEnvBase). They pair with the splat configs on the configs page.

SplatEnvBase

SplatEnvBase(env_options: EnvOptions | None = None, scene_options: SceneOptions | None = None, splat_options: SplatOptions | None = None, observation_options: ObservationManagerOptions | None = None, event_options: EventManagerOptions | None = None, action_options: ActionManagerOptions | None = None, metric_options: MetricManagerOptions | None = None, recorder_options: RecorderManagerOptions | None = None, cameras_options: CamerasOptions | None = None, sensors_options: SensorsOptions | None = None, renderer_options: RendererOptions | None = None, *, show_viewer: bool = False, eval_mode: bool = False, **kwargs)

Bases: _SplatEnvMixin, EnvBase[EdenSplatConfig]

Non-RL environment with a gsplat splat background (viewer / teleop / inference / data collection).

A sibling of :class:SplatRLEnvBase with no reward/termination/command/curriculum surface, as EnvBase is of RLEnvBase. The EnvBase[EdenSplatConfig] base re-binds the config leaf.

Source code in eden/envs/splat.py
def __init__(
    self,
    env_options: EnvOptions | None = None,
    scene_options: SceneOptions | None = None,
    splat_options: SplatOptions | None = None,
    observation_options: ObservationManagerOptions | None = None,
    event_options: EventManagerOptions | None = None,
    action_options: ActionManagerOptions | None = None,
    metric_options: MetricManagerOptions | None = None,
    recorder_options: RecorderManagerOptions | None = None,
    cameras_options: CamerasOptions | None = None,
    sensors_options: SensorsOptions | None = None,
    renderer_options: RendererOptions | None = None,
    *,
    show_viewer: bool = False,
    eval_mode: bool = False,
    **kwargs,
):
    self._init_splat(splat_options)
    super().__init__(  # -> EnvBase (no RL managers)
        env_options=env_options,
        scene_options=scene_options,
        observation_options=observation_options,
        event_options=event_options,
        action_options=action_options,
        metric_options=metric_options,
        recorder_options=recorder_options,
        cameras_options=cameras_options,
        sensors_options=sensors_options,
        renderer_options=renderer_options,
        show_viewer=show_viewer,
        eval_mode=eval_mode,
        **kwargs,
    )

Methods:

SplatRLEnvBase

SplatRLEnvBase(env_options: EnvOptions | None = None, scene_options: SceneOptions | None = None, splat_options: SplatOptions | None = None, observation_options: ObservationManagerOptions | None = None, reward_options: RewardManagerOptions | None = None, termination_options: TerminationManagerOptions | None = None, event_options: EventManagerOptions | None = None, curriculum_options: CurriculumManagerOptions | None = None, command_options: CommandManagerOptions | None = None, action_options: ActionManagerOptions | None = None, metric_options: MetricManagerOptions | None = None, recorder_options: RecorderManagerOptions | None = None, cameras_options: CamerasOptions | None = None, sensors_options: SensorsOptions | None = None, renderer_options: RendererOptions | None = None, *, show_viewer: bool = False, eval_mode: bool = False, **kwargs)

Bases: _SplatEnvMixin, RLEnvBase, EnvBase[EdenSplatRLConfig]

RL environment with a gsplat splat background (splat scene + full RL surface).

The RL counterpart of :class:SplatEnvBase, sharing its splat plumbing via :class:_SplatEnvMixin; the extra EnvBase[EdenSplatRLConfig] base re-binds the config leaf, the same way :class:~eden.envs.base.MimicRLEnvBase does.

Source code in eden/envs/splat.py
def __init__(
    self,
    env_options: EnvOptions | None = None,
    scene_options: SceneOptions | None = None,
    splat_options: SplatOptions | None = None,
    observation_options: ObservationManagerOptions | None = None,
    reward_options: RewardManagerOptions | None = None,
    termination_options: TerminationManagerOptions | None = None,
    event_options: EventManagerOptions | None = None,
    curriculum_options: CurriculumManagerOptions | None = None,
    command_options: CommandManagerOptions | None = None,
    action_options: ActionManagerOptions | None = None,
    metric_options: MetricManagerOptions | None = None,
    recorder_options: RecorderManagerOptions | None = None,
    cameras_options: CamerasOptions | None = None,
    sensors_options: SensorsOptions | None = None,
    renderer_options: RendererOptions | None = None,
    *,
    show_viewer: bool = False,
    eval_mode: bool = False,
    **kwargs,
):
    self._init_splat(splat_options)
    super().__init__(  # -> RLEnvBase (full RL surface)
        env_options=env_options,
        scene_options=scene_options,
        observation_options=observation_options,
        reward_options=reward_options,
        termination_options=termination_options,
        event_options=event_options,
        curriculum_options=curriculum_options,
        command_options=command_options,
        action_options=action_options,
        metric_options=metric_options,
        recorder_options=recorder_options,
        cameras_options=cameras_options,
        sensors_options=sensors_options,
        renderer_options=renderer_options,
        show_viewer=show_viewer,
        eval_mode=eval_mode,
        **kwargs,
    )

Methods:

NyxSplatEnvBase

NyxSplatEnvBase(env_options: EnvOptions | None = None, scene_options: SceneOptions | None = None, splat_options: NyxSplatOptions | None = None, observation_options: ObservationManagerOptions | None = None, event_options: EventManagerOptions | None = None, action_options: ActionManagerOptions | None = None, metric_options: MetricManagerOptions | None = None, recorder_options: RecorderManagerOptions | None = None, cameras_options: CamerasOptions | None = None, sensors_options: SensorsOptions | None = None, renderer_options: RendererOptions | None = None, *, show_viewer: bool = False, eval_mode: bool = False, **kwargs)

Bases: EnvBase[EdenNyxSplatConfig]

Env (no RL managers) with the Nyx renderer for photorealistic background rendering using gaussian splats.

Drives the official gs_nyx_plugin (see gs_nyx_plugin.NyxCameraSensor) so gaussian splats render as native LightFieldAsset instances inside the path-traced scene, with proper lighting interaction between splat backgrounds and Genesis entities.

Source code in eden/envs/nyx_splat.py
def __init__(
    self,
    env_options: EnvOptions | None = None,
    scene_options: SceneOptions | None = None,
    splat_options: NyxSplatOptions | None = None,
    observation_options: ObservationManagerOptions | None = None,
    event_options: EventManagerOptions | None = None,
    action_options: ActionManagerOptions | None = None,
    metric_options: MetricManagerOptions | None = None,
    recorder_options: RecorderManagerOptions | None = None,
    cameras_options: CamerasOptions | None = None,
    sensors_options: SensorsOptions | None = None,
    renderer_options: RendererOptions | None = None,
    *,
    show_viewer: bool = False,
    eval_mode: bool = False,
    **kwargs,
):
    # Checked here rather than in `from_config` so direct construction is covered too, and
    # so `from_config` stays the one generic body every env inherits. `EdenNyxSplatConfig`
    # validates the *type* when the field is set at construction, but not on later
    # assignment (`cfg.splat_options = SplatOptions()` is accepted).
    if splat_options is None:
        raise ValueError(
            "`NyxSplatEnvBase` requires `splat_options` (it owns a Nyx splat renderer). Set "
            "`splat_options=NyxSplatOptions(...)` on the config, or build a task without "
            "splats through `env_class_for_config(cfg).from_config(cfg)`."
        )
    if not isinstance(splat_options, NyxSplatOptions):
        raise TypeError(
            f"`NyxSplatEnvBase` renders through the Nyx path tracer, so `splat_options` must be "
            f"a `NyxSplatOptions`, not a `{type(splat_options).__name__}`. For "
            f"`SplatOptions`, build the config through `SplatEnvBase`."
        )

    # Held publicly so `env.config` can report it live (a declared EdenNyxSplatConfig
    # field); the private alias stays for the render path and entities/base.py's probe.
    self.splat_options = splat_options
    self._nyx_splat_options = splat_options
    self._nyx_light_fields = self._build_light_fields(splat_options.pcds)
    self._nyx_env_maps = self._build_env_maps(splat_options.env_map_paths)

    super().__init__(
        env_options=env_options,
        scene_options=scene_options,
        observation_options=observation_options,
        event_options=event_options,
        action_options=action_options,
        metric_options=metric_options,
        recorder_options=recorder_options,
        cameras_options=cameras_options,
        sensors_options=sensors_options,
        renderer_options=renderer_options,
        show_viewer=show_viewer,
        eval_mode=eval_mode,
        **kwargs,
    )

Attributes

splat_options instance-attribute

splat_options = splat_options

Methods:

Debug drawing

DrawDebugMixin

Mixin providing debug drawing pass-throughs to the Genesis scene.

Methods:

draw_debug_line

draw_debug_line(start, end, radius=0.002, color=(1.0, 0.0, 0.0, 0.5))

Draws a line in the scene for visualization.

PARAMETER DESCRIPTION
start

The starting point of the line.

TYPE: (array_like, shape(3))

end

The ending point of the line.

TYPE: (array_like, shape(3))

radius

The radius of the line (represented as a cylinder)

TYPE: float DEFAULT: 0.002

color

The color of the line in RGBA format.

TYPE: (array_like, shape(4)) DEFAULT: (1.0, 0.0, 0.0, 0.5)

RETURNS DESCRIPTION
node

The created debug object.

TYPE: Mesh

Source code in eden/envs/draw_debug_mixin.py
@gs.assert_built
def draw_debug_line(self, start, end, radius=0.002, color=(1.0, 0.0, 0.0, 0.5)):
    """
    Draws a line in the scene for visualization.

    Parameters
    ----------
    start : array_like, shape (3,)
        The starting point of the line.
    end : array_like, shape (3,)
        The ending point of the line.
    radius : float, optional
        The radius of the line (represented as a cylinder)
    color : array_like, shape (4,), optional
        The color of the line in RGBA format.

    Returns
    -------
    node : pyrender.mesh.Mesh
        The created debug object.
    """
    return self.scene.draw_debug_line(start, end, radius, color)

draw_debug_arrow

draw_debug_arrow(pos, vec=(0, 0, 1), radius=0.01, color=(1.0, 0.0, 0.0, 0.5))

Draws an arrow in the scene for visualization.

PARAMETER DESCRIPTION
pos

The starting position of the arrow.

TYPE: (array_like, shape(3))

vec

The vector of the arrow.

TYPE: (array_like, shape(3)) DEFAULT: (0, 0, 1)

radius

The radius of the arrow body (represented as a cylinder).

TYPE: float DEFAULT: 0.01

color

The color of the arrow in RGBA format.

TYPE: (array_like, shape(4)) DEFAULT: (1.0, 0.0, 0.0, 0.5)

RETURNS DESCRIPTION
node

The created debug object.

TYPE: Mesh

Source code in eden/envs/draw_debug_mixin.py
@gs.assert_built
def draw_debug_arrow(self, pos, vec=(0, 0, 1), radius=0.01, color=(1.0, 0.0, 0.0, 0.5)):
    """
    Draws an arrow in the scene for visualization.

    Parameters
    ----------
    pos : array_like, shape (3,)
        The starting position of the arrow.
    vec : array_like, shape (3,), optional
        The vector of the arrow.
    radius : float, optional
        The radius of the arrow body (represented as a cylinder).
    color : array_like, shape (4,), optional
        The color of the arrow in RGBA format.

    Returns
    -------
    node : pyrender.mesh.Mesh
        The created debug object.
    """
    return self.scene.draw_debug_arrow(pos, vec, radius, color)

draw_debug_frame

draw_debug_frame(T, axis_length=1.0, origin_size=0.015, axis_radius=0.01)

Draws a 3-axis coordinate frame in the scene for visualization.

PARAMETER DESCRIPTION
T

The transformation matrix of the frame.

TYPE: (array_like, shape(4, 4))

axis_length

The length of the axes.

TYPE: float DEFAULT: 1.0

origin_size

The size of the origin point (represented as a sphere).

TYPE: float DEFAULT: 0.015

axis_radius

The radius of the axes (represented as cylinders).

TYPE: float DEFAULT: 0.01

RETURNS DESCRIPTION
node

The created debug object.

TYPE: Mesh

Source code in eden/envs/draw_debug_mixin.py
@gs.assert_built
def draw_debug_frame(self, T, axis_length=1.0, origin_size=0.015, axis_radius=0.01):
    """
    Draws a 3-axis coordinate frame in the scene for visualization.

    Parameters
    ----------
    T : array_like, shape (4, 4)
        The transformation matrix of the frame.
    axis_length : float, optional
        The length of the axes.
    origin_size : float, optional
        The size of the origin point (represented as a sphere).
    axis_radius : float, optional
        The radius of the axes (represented as cylinders).

    Returns
    -------
    node : pyrender.mesh.Mesh
        The created debug object.
    """
    return self.scene.draw_debug_frame(T, axis_length, origin_size, axis_radius)

draw_debug_frames

draw_debug_frames(Ts, axis_length=1.0, origin_size=0.015, axis_radius=0.01)

Draws 3-axis coordinate frames in the scene for visualization.

PARAMETER DESCRIPTION
Ts

The transformation matrices of frames.

TYPE: (array_like, shape(n, 4, 4))

axis_length

The length of the axes.

TYPE: float DEFAULT: 1.0

origin_size

The size of the origin point (represented as a sphere).

TYPE: float DEFAULT: 0.015

axis_radius

The radius of the axes (represented as cylinders).

TYPE: float DEFAULT: 0.01

RETURNS DESCRIPTION
node

The created debug object.

TYPE: Mesh

Source code in eden/envs/draw_debug_mixin.py
@gs.assert_built
def draw_debug_frames(self, Ts, axis_length=1.0, origin_size=0.015, axis_radius=0.01):
    """
    Draws 3-axis coordinate frames in the scene for visualization.

    Parameters
    ----------
    Ts : array_like, shape (n, 4, 4)
        The transformation matrices of frames.
    axis_length : float, optional
        The length of the axes.
    origin_size : float, optional
        The size of the origin point (represented as a sphere).
    axis_radius : float, optional
        The radius of the axes (represented as cylinders).

    Returns
    -------
    node : pyrender.mesh.Mesh
        The created debug object.
    """
    return self.scene.draw_debug_frames(Ts, axis_length, origin_size, axis_radius)

draw_debug_mesh

draw_debug_mesh(mesh, pos=zeros(3), T=None)

Draws a mesh in the scene for visualization.

PARAMETER DESCRIPTION
mesh

The mesh to be drawn.

TYPE: Trimesh

pos

The position of the mesh in the scene.

TYPE: (array_like, shape(3)) DEFAULT: zeros(3)

T

The transformation matrix of the mesh. If None, the mesh will be drawn at the position specified by pos. Otherwise, T has a higher priority than pos.

TYPE: (array_like, shape(4, 4) | None) DEFAULT: None

RETURNS DESCRIPTION
node

The created debug object.

TYPE: Mesh

Source code in eden/envs/draw_debug_mixin.py
@gs.assert_built
def draw_debug_mesh(self, mesh, pos=np.zeros(3), T=None):
    """
    Draws a mesh in the scene for visualization.

    Parameters
    ----------
    mesh : trimesh.Trimesh
        The mesh to be drawn.
    pos : array_like, shape (3,), optional
        The position of the mesh in the scene.
    T : array_like, shape (4, 4) | None, optional
        The transformation matrix of the mesh. If None, the mesh will be drawn at the position specified by
        `pos`. Otherwise, `T` has a higher priority than `pos`.

    Returns
    -------
    node : pyrender.mesh.Mesh
        The created debug object.
    """
    return self.scene.draw_debug_mesh(mesh, pos, T)

draw_debug_sphere

draw_debug_sphere(pos, radius=0.01, color=(1.0, 0.0, 0.0, 0.5))

Draws a sphere in the scene for visualization.

PARAMETER DESCRIPTION
pos

The center position of the sphere.

TYPE: (array_like, shape(3))

radius

radius of the sphere.

TYPE: float DEFAULT: 0.01

color

The color of the sphere in RGBA format.

TYPE: (array_like, shape(4)) DEFAULT: (1.0, 0.0, 0.0, 0.5)

RETURNS DESCRIPTION
node

The created debug object.

TYPE: Mesh

Source code in eden/envs/draw_debug_mixin.py
@gs.assert_built
def draw_debug_sphere(self, pos, radius=0.01, color=(1.0, 0.0, 0.0, 0.5)):
    """
    Draws a sphere in the scene for visualization.

    Parameters
    ----------
    pos : array_like, shape (3,)
        The center position of the sphere.
    radius : float, optional
        radius of the sphere.
    color : array_like, shape (4,), optional
        The color of the sphere in RGBA format.

    Returns
    -------
    node : pyrender.mesh.Mesh
        The created debug object.
    """
    return self.scene.draw_debug_sphere(pos, radius, color)

draw_debug_spheres

draw_debug_spheres(poss, radius=0.01, color=(1.0, 0.0, 0.0, 0.5))

Draws multiple spheres in the scene for visualization.

PARAMETER DESCRIPTION
poss

The positions of the spheres.

TYPE: (array_like, shape(N, 3))

radius

The radius of the spheres.

TYPE: float DEFAULT: 0.01

color

The color of the spheres in RGBA format.

TYPE: (array_like, shape(4)) DEFAULT: (1.0, 0.0, 0.0, 0.5)

RETURNS DESCRIPTION
node

The created debug object.

TYPE: Mesh

Source code in eden/envs/draw_debug_mixin.py
@gs.assert_built
def draw_debug_spheres(self, poss, radius=0.01, color=(1.0, 0.0, 0.0, 0.5)):
    """
    Draws multiple spheres in the scene for visualization.

    Parameters
    ----------
    poss : array_like, shape (N, 3)
        The positions of the spheres.
    radius : float, optional
        The radius of the spheres.
    color : array_like, shape (4,), optional
        The color of the spheres in RGBA format.

    Returns
    -------
    node : pyrender.mesh.Mesh
        The created debug object.
    """
    return self.scene.draw_debug_spheres(poss, radius, color)

draw_debug_box

draw_debug_box(bounds, color=(1.0, 0.0, 0.0, 1.0), wireframe=True, wireframe_radius=0.0015)

Draws a box in the scene for visualization.

PARAMETER DESCRIPTION
bounds

The bounds of the box, specified as [[min_x, min_y, min_z], [max_x, max_y, max_z]].

TYPE: (array_like, shape(2, 3))

color

The color of the box in RGBA format.

TYPE: (array_like, shape(4)) DEFAULT: (1.0, 0.0, 0.0, 1.0)

wireframe

Whether to draw the box as a wireframe.

TYPE: bool DEFAULT: True

wireframe_radius

The radius of the wireframe lines.

TYPE: float DEFAULT: 0.0015

RETURNS DESCRIPTION
node

The created debug object.

TYPE: Mesh

Source code in eden/envs/draw_debug_mixin.py
@gs.assert_built
def draw_debug_box(
    self,
    bounds,
    color=(1.0, 0.0, 0.0, 1.0),
    wireframe=True,
    wireframe_radius=0.0015,
):
    """
    Draws a box in the scene for visualization.

    Parameters
    ----------
    bounds : array_like, shape (2, 3)
        The bounds of the box, specified as [[min_x, min_y, min_z], [max_x, max_y, max_z]].
    color : array_like, shape (4,), optional
        The color of the box in RGBA format.
    wireframe : bool, optional
        Whether to draw the box as a wireframe.
    wireframe_radius : float, optional
        The radius of the wireframe lines.

    Returns
    -------
    node : pyrender.mesh.Mesh
        The created debug object.
    """
    return self.scene.draw_debug_box(bounds, color, wireframe=wireframe, wireframe_radius=wireframe_radius)

draw_debug_points

draw_debug_points(poss, colors=(1.0, 0.0, 0.0, 0.5))

Draws points in the scene for visualization.

PARAMETER DESCRIPTION
poss

The positions of the points.

TYPE: (array_like, shape(N, 3))

colors

The color of the points in RGBA format.

TYPE: (array_like, shape(4)) DEFAULT: (1.0, 0.0, 0.0, 0.5)

RETURNS DESCRIPTION
node

The created debug object.

TYPE: Mesh

Source code in eden/envs/draw_debug_mixin.py
@gs.assert_built
def draw_debug_points(self, poss, colors=(1.0, 0.0, 0.0, 0.5)):
    """
    Draws points in the scene for visualization.

    Parameters
    ----------
    poss : array_like, shape (N, 3)
        The positions of the points.
    colors : array_like, shape (4,), optional
        The color of the points in RGBA format.

    Returns
    -------
    node : pyrender.mesh.Mesh
        The created debug object.
    """
    return self.scene.draw_debug_points(poss, colors)

draw_debug_path

draw_debug_path(qposs, entity, link_idx=-1, density=0.3, frame_scaling=1.0)

Draws a planned joint trajectory in the scene for visualization.

PARAMETER DESCRIPTION
qposs

The joint positions of the planned points. N is the number of configurations (i.e., trajectory points). M is the number of degrees of freedom for the entity (i.e., joint dimensions).

TYPE: (array_like, shape(N, M))

entity

The rigid entity whose forward kinematics are used to compute the trajectory path.

TYPE: RigidEntity

link_idx

The link id of the rigid entity to visualize. Default is -1.

TYPE: int DEFAULT: -1

density

Controls the sampling density of the trajectory points to visualize. Default is 0.3.

TYPE: float DEFAULT: 0.3

frame_scaling

Scaling factor for the visualization frames' size. Affects the length and thickness of the debug frames. Default is 1.0.

TYPE: float DEFAULT: 1.0

RETURNS DESCRIPTION
node

The created debug object representing the visualized trajectory.

TYPE: Mesh

Notes

The function uses forward kinematics (FK) to convert joint positions to Cartesian space and render debug frames. The density parameter reduces FK computational load by sampling fewer points, with 1.0 representing the whole trajectory.

Source code in eden/envs/draw_debug_mixin.py
@gs.assert_built
def draw_debug_path(self, qposs, entity, link_idx=-1, density=0.3, frame_scaling=1.0):
    """
    Draws a planned joint trajectory in the scene for visualization.

    Parameters
    ----------
    qposs : array_like, shape (N, M)
        The joint positions of the planned points.
        N is the number of configurations (i.e., trajectory points).
        M is the number of degrees of freedom for the entity (i.e., joint dimensions).
    entity : gs.engine.entities.RigidEntity
        The rigid entity whose forward kinematics are used to compute the trajectory path.
    link_idx : int, optional
        The link id of the rigid entity to visualize. Default is -1.
    density : float, optional
        Controls the sampling density of the trajectory points to visualize. Default is 0.3.
    frame_scaling : float, optional
        Scaling factor for the visualization frames' size. Affects the length and thickness of the debug frames.
        Default is 1.0.

    Returns
    -------
    node : pyrender.mesh.Mesh
        The created debug object representing the visualized trajectory.

    Notes
    -----
    The function uses forward kinematics (FK) to convert joint positions to Cartesian space and render debug frames.
    The density parameter reduces FK computational load by sampling fewer points, with 1.0 representing the whole
    trajectory.
    """
    return self.scene.draw_debug_frames(
        qposs=qposs,
        entity=entity,
        link_idx=link_idx,
        density=density,
        frame_scaling=frame_scaling,
    )

clear_debug_object

clear_debug_object(node)
Source code in eden/envs/draw_debug_mixin.py
def clear_debug_object(self, node):
    self.scene.clear_debug_object(node)

clear_debug_objects

clear_debug_objects()
Source code in eden/envs/draw_debug_mixin.py
def clear_debug_objects(self):
    self.scene.clear_debug_objects()

CUDA-graph replay of the graded phase

EnvOptions.cuda_graph_options replays the metric, observation-refresh, termination and reward phase of RLEnvBase.step from a CUDA graph. GradedPhaseGraph is the implementation; BakedHostReadError is what it raises when a term reads a value back to the host during capture.

CUDA-graph replay of the graded phase of RLEnvBase.step (EnvOptions.cuda_graph_options).

Between the physics step and the auto-reset, RLEnvBase.step grades the post-physics state: the metric manager, the observation terms' post_physics_refresh tick, the termination manager and the reward manager. That phase reads the simulation (through Genesis' zero-copy views, plain torch indexing) and writes only the managers' own buffers, but it does so in hundreds of small kernels, and on a host-bound step their launches are what it costs: ~3 ms of host time on BeyondMimic at 4096 envs, against ~1 ms of device time. Replayed from a CUDA graph the same kernels launch in ~5 µs, and the checks before each replay (below) take ~70 µs.

A graph replays kernels, not Python, so it is only right for code that keeps its state in tensors it writes in place, and reads no value back to the host — which is the term contract (docs/guides/terms.md). :class:GradedPhaseGraph does not take that on trust (its settings are :class:~eden.options.envs.CudaGraphOptions):

  • It captures after warmup_steps eager steps, once every term has built its lazy caches (building one inside a capture is a host-to-device copy, which a capture rejects).
  • The capture step and the next validation_steps replays are each also run eagerly and compared bit for bit with the graph: every tensor the phase leaves behind (each one bound to an object the walk follows that it writes or rebinds, and each one it writes that no such binding reaches) and the phase's outputs. A mismatch raises, naming the tensors that differ. The two runs go in a different order on each kind of step:

  • On the capture step the eager run goes first (a capture records kernels without running them). It is undone so the capture starts from the same state. Every tensor that existed before it is put back, reached by a binding or not, so the capture's replay does not advance it a second time. One it allocated is not: it may already sit in a cache that outlives the phase (Genesis' contact query), and rolling it back would hand the cache's next reader stale values.

  • On a validated replay the graph goes first and is undone (the same tensors), and the eager run's result is the one kept. Run the other way round, work a capture recorded without being able to replay (a kernel launched outside torch's stream runs once, at capture time) would be redone by the eager run just before the replay read it, and agree. Graph-first, the replay reads the stale value and disagrees.
  • Before every replay it checks what the graph baked in, and re-captures (and re-validates) when it changed: the graded terms' configured fields (a curriculum moving a termination threshold), the env's per-run constants a term may read as Python values (:data:_ENV_CONSTANTS), and every binding on the way to a tensor the capture read (a term or load_state_dict rebinding the tensor, or replacing the dict or helper that holds it, leaves the graph reading the old address). The fields a manager mirrors on the device (:attr:~eden.managers.base.ManagerBase.device_synced_fields, the reward weights) are the exception: the manager's sync_device_fields runs before every replay, so a weight curriculum needs no re-capture.
  • env.common_step_counter changes on every step, so it cannot be watched: reading it during a capture raises :class:BakedHostReadError, naming the term's line. Any other Python value a term reads from outside its own configured fields (another manager's term, a module global) is not watched either, and the graph replays what it read at capture. Keep such a value in a tensor written in place.
  • More than max_recaptures_per_window re-captures within recapture_window steps raise, naming what changed: re-capturing on nearly every step costs more than the eager phase it replaces. A one-off change, such as a curriculum switching stage, re-captures once.

The walk that finds those bindings follows every object reachable from the env through attributes and containers, however deep, except three kinds: the env itself (every term's back-reference to it), the standard library's objects, and the objects of :data:_UNWALKED_PACKAGES (the simulator and torch), which hold the simulation's own state and per-step caches rather than a term's.

Classes

GradedPhaseGraph

GradedPhaseGraph(env: RLEnvBase, compute: Callable[[], tuple[Tensor, Tensor]], options: CudaGraphOptions)

Run the graded phase from a CUDA graph, validated against eager runs of it.

PARAMETER DESCRIPTION
env

The built env whose graded phase this replays.

TYPE: RLEnvBase

compute

The eager graded phase, returning (reset_buf, reward_buf): RLEnvBase._compute_graded.

TYPE: Callable[[], tuple[Tensor, Tensor]]

options

How to capture, validate and re-capture (EnvOptions.cuda_graph_options).

TYPE: CudaGraphOptions

Source code in eden/envs/graded_graph.py
def __init__(
    self,
    env: RLEnvBase,
    compute: Callable[[], tuple[torch.Tensor, torch.Tensor]],
    options: CudaGraphOptions,
) -> None:
    if torch.device(env.device).type != "cuda":
        raise ValueError(
            f"EnvOptions.cuda_graph_options needs a CUDA device, but this env runs on '{env.device}'. Set "
            "cuda_graph_options=None, or run on an NVIDIA GPU with en.init(backend=gs.cuda); gs.gpu is not "
            "enough, as it picks Metal on a Mac."
        )
    self._env = env
    self._compute = compute
    self._options = options
    self._graph: torch.cuda.CUDAGraph | None = None
    self._outputs: tuple[torch.Tensor, torch.Tensor] | None = None
    self._fingerprint: tuple[tuple, tuple] | None = None
    self._steps = 0
    self._validations_left = 0
    # The graded steps at which the most recent re-captures ran (not the first capture).
    self._recaptures: list[int] = []
    # The managers the phase runs, and every term whose configured fields a capture bakes in, as
    # ``(label, term, the names of those fields)``: the per-step fingerprint reads them. Resolved once, as
    # the terms are: walking each term class's MRO for its field names on every step cost more host time
    # than the replay saves of it (~150 µs a step on BeyondMimic). A field its manager mirrors on the device
    # is left out.
    self._managers = _graded_managers(env)
    self._terms = self._resolve_terms()
    # The bindings on the way to each tensor the capture read (see :meth:`_watch`): one that now holds
    # another object leaves the graph reading the old address. Set by each capture.
    self._links: list[tuple[str, Any, Any, Any]] = []
    self._leaves: list[tuple[str, Any, Any, tuple]] = []
    # The paths of the tensors the phase leaves behind (each bound tensor it writes, each binding it
    # replaces): what a validated replay compares and undoes. Set by each capture.
    self._state_paths: frozenset[str] = frozenset()
    # The tensors the phase writes that no binding the walk follows reaches (a helper's buffer below the
    # walk, a closure's), held directly: what a validated replay compares and undoes beside the paths. Set by each
    # capture.
    self._unreached: list[torch.Tensor] = []

Methods:

BakedHostReadError

Bases: RuntimeError

A host value that changes on every step was read while the graded phase was being captured.

Raised by RLEnvBase.common_step_counter during a capture: the graph would replay the count it read on every later step, so a cadence gated on it (counter % n == 0) would stay on the branch the capture took.

RL-library wrappers

The wrappers adapt an RLEnvBase to an RL library's vectorized-env interface. They live in eden/envs/wrappers/, which has no __init__.py, so the API generator cannot render them here; read their docstrings in the source.

Module Classes and functions Library
eden/envs/wrappers/rsl_rl_env.py RslRlVecEnvWrapper, make_rsl_rl_runner, load_runner_checkpoint, DDPOnPolicyRunner, MimicOnPolicyRunner rsl-rl-lib
eden/envs/wrappers/rl_games_env.py RlGamesVecEnvWrapper rl_games
eden/envs/wrappers/gymnasium_env.py GymnasiumVecEnvWrapper, make_vec_env, register_gymnasium_envs Gymnasium (gym.make_vec("Eden/<task>"))
eden/envs/wrappers/sb3_env.py Sb3VecEnvWrapper Stable-Baselines3
eden/envs/wrappers/skrl_env.py SkrlVecEnvWrapper skrl
eden/envs/wrappers/vec_env_mixin.py VecEnvMixin, shared by the Gymnasium, SB3 and skrl wrappers —
eden/envs/wrappers/core.py EnvWrapperMixin, action_space, observation_space, time_outs —

See Training runs and Other RL libraries.

See also