Skip to content

Termination Terms

Termination terms return a boolean per env; any true term ends the episode. A term configured with time_out=True marks a truncation rather than a failure. The rewards and terminations guide explains both; the term catalog lists every parameter.

from eden import terminations
from eden.terms import DoneTerm

DoneTerm.configure(func=terminations.time_out, time_out=True)

The base classes TerminationTerm and TerminationTermFuncWrapper are on the managers page.

Common

Time-out, metric success and failure, illegal contact and limit violations.

Common termination terms (timeout, success/failure, illegal contact, limits).

Attributes

Classes

OnMetricSuccess

OnMetricSuccess(env: EnvBase, options: _TermOptionsT)

Bases: TerminationTerm

Terminate the envs in which the named metric term reports success this step.

:meth:build runs the manager's own current-step success check once, so a missing metric manager, an unset or unknown metric_name (the error lists the active metrics), a non-INTERVAL metric and a contributes_to_success=False metric all raise at env construction. To end the episode on a metric that defines no success, use :class:MetricAbove.

Source code in eden/managers/base.py
def __init__(self, env: EnvBase, options: _TermOptionsT):
    ManagerTermBase.__init__(self, env=env)
    ConfigurableMixin.__init__(self, options=options)

Attributes

metric_name class-attribute instance-attribute
metric_name: str = ''

Methods:

build
build() -> None
Source code in eden/managers/terms/terminations/common.py
def build(self) -> None:
    metric_manager = resolve_metric_manager(self._env, term="on_metric_success", metric_name=self.metric_name)
    metric_manager.is_current_step_success(self.metric_name)
compute
compute() -> Tensor
Source code in eden/managers/terms/terminations/common.py
def compute(self) -> torch.Tensor:
    return self._env.metric_manager.is_current_step_success(self.metric_name)

MetricAbove

MetricAbove(env: EnvBase, options: _TermOptionsT)

Bases: TerminationTerm

Terminate the envs in which the named metric's cached value is strictly above threshold.

Reads metric_manager.get_value(metric_name): the current-step value, or — for a metric declared is_cumulative=True — the running episode sum (reset per-env on episode reset), so on a cumulative metric threshold bounds the episode total, not one step's value.

Unlike on_metric_success this never consults the metric's success check, so it accepts a metric with contributes_to_success=False — e.g. an arm–table collision metric that must end the episode without being part of the task's success accounting.

A missing metric manager or an unset or unknown metric_name (the error lists the active metrics) raises in :meth:build, i.e. at env construction.

Source code in eden/managers/base.py
def __init__(self, env: EnvBase, options: _TermOptionsT):
    ManagerTermBase.__init__(self, env=env)
    ConfigurableMixin.__init__(self, options=options)

Attributes

metric_name class-attribute instance-attribute
metric_name: str = ''
threshold class-attribute instance-attribute
threshold: float = 0.0

Methods:

build
build() -> None
Source code in eden/managers/terms/terminations/common.py
def build(self) -> None:
    resolve_metric_manager(self._env, term="metric_above", metric_name=self.metric_name)
compute
compute() -> Tensor
Source code in eden/managers/terms/terminations/common.py
def compute(self) -> torch.Tensor:
    return self._env.metric_manager.get_value(self.metric_name) > self.threshold

IllegalLinkContact

IllegalLinkContact(env: EnvBase, options: _TermOptionsT)

Bases: TerminationTerm

Terminate when any of the named links carries a net contact force above force_threshold.

The link-level sibling of :func:illegal_contact, and the reason both exist: that term asks which entities touched, this one asks which links of one entity took load, and how hard. The knee-and-torso fall detector every locomotion task wants is this one — the entity-level term cannot express it, because a walking robot's feet are in contact with exactly the same entity (the ground or the terrain) as its knees. It mirrors Isaac Lab / mjlab's mdp.illegal_contact(body_names, threshold), minus their force history: Eden reads the net force from the most recent scene.step(), so a contact shorter than one control step can slip between two reads.

Same reading as :class:~eden.managers.terms.rewards.common.UndesiredContacts — that term counts the offending links so a weight can price them, this one ends the episode on the first — and the two resolve links_name identically, so a config can penalise and terminate on one link set without the two drifting apart.

PARAMETER DESCRIPTION
entity_name

Entity whose links are checked. Must be a rigid entity (get_links_net_contact_force).

links_name

:func:~eden.utils.string.resolve_matching_names-compatible patterns selecting the links that may not take load — e.g. [".*knee.*", "torso.*"]. Required: an empty list would make the term a constant False, so it raises in :meth:build listing the entity's links.

force_threshold

Net contact-force magnitude, in newtons, above which a link counts as illegally touching. The default of 1.0 N (Isaac Lab's) rejects numerical noise while catching any real load; raise it on a heavy robot whose links rest against each other.

Source code in eden/managers/base.py
def __init__(self, env: EnvBase, options: _TermOptionsT):
    ManagerTermBase.__init__(self, env=env)
    ConfigurableMixin.__init__(self, options=options)

Attributes

entity_name class-attribute instance-attribute
entity_name: str = 'robot'
links_name: list[str] = []
force_threshold class-attribute instance-attribute
force_threshold: float = 1.0

Methods:

build
build() -> None
Source code in eden/managers/terms/terminations/common.py
def build(self) -> None:
    # Touch the entity for fail-fast but cache only the derived indices, so a deployment
    # hot-swap of env.entities[name] is honoured (see resolve_entity). The indices are
    # name-resolved idx_local, matching the order get_links_net_contact_force returns.
    entity = self.resolve_entity(self.entity_name)
    self._link_idx = resolve_link_idx(entity, self.links_name, self.device, term=type(self).__name__)
compute
compute() -> Tensor
Source code in eden/managers/terms/terminations/common.py
def compute(self) -> torch.Tensor:
    forces = self.resolve_entity(self.entity_name).get_links_net_contact_force()  # (N, n_links, 3)
    magnitudes = torch.norm(forces[:, self._link_idx], dim=-1)  # (N, K)
    return (magnitudes > self.force_threshold).any(dim=-1)

Functions:

time_out

time_out(env: RLEnvBase) -> Tensor
Source code in eden/managers/terms/terminations/common.py
@TERMINATION_TERM_REGISTRY.register()
def time_out(env: RLEnvBase) -> torch.Tensor:
    return env.episode_length_buf >= env.max_episode_length

on_success

on_success(env: RLEnvBase) -> Tensor
Source code in eden/managers/terms/terminations/common.py
@TERMINATION_TERM_REGISTRY.register()
def on_success(env: RLEnvBase) -> torch.Tensor:
    return env.metric_manager.success_buf

on_failure

on_failure(env: RLEnvBase) -> Tensor
Source code in eden/managers/terms/terminations/common.py
@TERMINATION_TERM_REGISTRY.register()
def on_failure(env: RLEnvBase) -> torch.Tensor:
    return ~env.metric_manager.success_buf

illegal_contact

illegal_contact(env: RLEnvBase, *, entity_name: str, illegal_contact_entity_names: list[str]) -> Tensor
Source code in eden/managers/terms/terminations/common.py
@TERMINATION_TERM_REGISTRY.register()
def illegal_contact(env: RLEnvBase, *, entity_name: str, illegal_contact_entity_names: list[str]) -> torch.Tensor:
    entity: Entity = env.get_entity(entity_name)
    contacts = torch.zeros(env.num_envs, dtype=torch.bool, device=env.device)
    for illegal_contact_entity_name in illegal_contact_entity_names:
        illegal_contact_entity: Entity = env.get_entity(illegal_contact_entity_name)
        contacts |= entity.get_contacts(with_entity=illegal_contact_entity)["valid_mask"].any(dim=-1)
    return contacts

dofs_pos_limit_exceeded

dofs_pos_limit_exceeded(env: RLEnvBase, *, entity_name: str, probability: float = 1.0) -> Tensor

Terminate when any DOF position is outside the entity's soft position limits.

probability gates the termination globally per call (one Bernoulli draw), leaving probabilistic slack for policies that occasionally bump the limits. Set to 1.0 for hard termination.

Source code in eden/managers/terms/terminations/common.py
@TERMINATION_TERM_REGISTRY.register()
def dofs_pos_limit_exceeded(
    env: RLEnvBase,
    *,
    entity_name: str,
    probability: float = 1.0,
) -> torch.Tensor:
    """Terminate when any DOF position is outside the entity's soft position limits.

    ``probability`` gates the termination globally per call (one Bernoulli draw),
    leaving probabilistic slack for policies that occasionally bump the limits.
    Set to 1.0 for hard termination.
    """
    entity: Entity = env.get_entity(entity_name)
    soft = entity.soft_dofs_pos_limits
    if soft is None:
        # Non-articulated or unbuilt entity: nothing to violate.
        return torch.zeros(env.num_envs, dtype=torch.bool, device=env.device)
    dofs_pos = entity.get_dofs_pos()
    violation = torch.sum(soft_dof_pos_violation(dofs_pos, soft), dim=1) > 0.0
    return apply_probability(violation, probability)

dofs_vel_limit_exceeded

dofs_vel_limit_exceeded(env: RLEnvBase, *, entity_name: str, max_vel: float, probability: float = 1.0) -> Tensor

Terminate when any DOF velocity exceeds max_vel in magnitude.

probability gates the termination globally per call. Set to 1.0 for hard termination.

Source code in eden/managers/terms/terminations/common.py
@TERMINATION_TERM_REGISTRY.register()
def dofs_vel_limit_exceeded(
    env: RLEnvBase,
    *,
    entity_name: str,
    max_vel: float,
    probability: float = 1.0,
) -> torch.Tensor:
    """Terminate when any DOF velocity exceeds ``max_vel`` in magnitude.

    ``probability`` gates the termination globally per call. Set to 1.0 for hard
    termination.
    """
    entity: Entity = env.get_entity(entity_name)
    dofs_vel = entity.get_dofs_vel()
    violation = (dofs_vel.abs() > max_vel).any(dim=1)
    return apply_probability(violation, probability)

cumulative_reward_below_threshold

cumulative_reward_below_threshold(env: RLEnvBase, *, reward_term: str, per_step_threshold: float, grace_steps: int = 0, check_interval: int = 1) -> Tensor

Terminate envs whose running raw reward for reward_term falls below a threshold.

The threshold is per_step_threshold * env.dt * completed_steps.

Mirrors dexmachina's early-reset curriculum trigger (dexmachina/envs/base_env.py:712-750). Reads the weight-free, dt-scaled accumulation via :meth:RewardManager.get_episode_sum so the threshold stays stable when reward curricula mutate term.weight at runtime.

Mental model: average reward per completed step ≈ cum / (dt * (episode_length_buf - 1)). The term fires when that ratio drops below per_step_threshold, optionally rounded down to multiples of check_interval steps.

PARAMETER DESCRIPTION
env

The environment instance.

TYPE: RLEnvBase

reward_term

Name of a term registered with the reward manager. Raises KeyError (with the active term list) if missing.

TYPE: str

per_step_threshold

Floor on raw per-step reward (unweighted compute() output). Compared as cum < per_step_threshold * env.dt * completed_steps. Same units as dexmachina's --early_reset_threshold flag (paper default 0.5); no manual * dt rescale at config time.

TYPE: float

grace_steps

Suppress the gate while episode_length_buf <= grace_steps. Default 0 matches dexmachina (its interval=0 rung is implicitly graceful — the natural cum == 0 initial state satisfies cum < 0 vacuously).

TYPE: int DEFAULT: 0

check_interval

1 (default) → continuous per-completed-step check. > 1 → dexmachina interval-ladder: round elapsed-steps down to check_interval multiples before applying the threshold. Pass 5 for early_reset_interval=5, 20 for the auxiliary imitation/contact/BC triggers.

TYPE: int DEFAULT: 1

Source code in eden/managers/terms/terminations/common.py
@TERMINATION_TERM_REGISTRY.register()
def cumulative_reward_below_threshold(
    env: RLEnvBase,
    *,
    reward_term: str,
    per_step_threshold: float,
    grace_steps: int = 0,
    check_interval: int = 1,
) -> torch.Tensor:
    """Terminate envs whose running raw reward for ``reward_term`` falls below a threshold.

    The threshold is ``per_step_threshold * env.dt * completed_steps``.

    Mirrors dexmachina's early-reset curriculum trigger
    (``dexmachina/envs/base_env.py:712-750``). Reads the weight-free,
    dt-scaled accumulation via :meth:`RewardManager.get_episode_sum` so the
    threshold stays stable when reward curricula mutate ``term.weight`` at
    runtime.

    Mental model: average reward per completed step ≈
    ``cum / (dt * (episode_length_buf - 1))``. The term fires when that
    ratio drops below ``per_step_threshold``, optionally rounded down to
    multiples of ``check_interval`` steps.

    Parameters
    ----------
    env : RLEnvBase
        The environment instance.
    reward_term : str
        Name of a term registered with the reward manager. Raises
        ``KeyError`` (with the active term list) if missing.
    per_step_threshold : float
        Floor on raw per-step reward (unweighted ``compute()`` output).
        Compared as ``cum < per_step_threshold * env.dt * completed_steps``.
        Same units as dexmachina's ``--early_reset_threshold`` flag (paper
        default ``0.5``); no manual ``* dt`` rescale at config time.
    grace_steps : int
        Suppress the gate while ``episode_length_buf <= grace_steps``.
        Default ``0`` matches dexmachina (its ``interval=0`` rung is
        implicitly graceful — the natural ``cum == 0`` initial state
        satisfies ``cum < 0`` vacuously).
    check_interval : int
        ``1`` (default) → continuous per-completed-step check.
        ``> 1`` → dexmachina interval-ladder: round elapsed-steps down
        to ``check_interval`` multiples before applying the threshold.
        Pass ``5`` for ``early_reset_interval=5``, ``20`` for the
        auxiliary imitation/contact/BC triggers.
    """
    cum = env.reward_manager.get_episode_sum(reward_term, raw=True)
    # _episode_raw_sums covers steps 1..t-1 when termination evaluates at step t,
    # so the elapsed multiplier is (episode_length_buf - 1) clamped at zero.
    completed = (env.episode_length_buf - 1).clamp(min=0)
    if check_interval > 1:
        # Match dexmachina's strict `>` rung compare: largest rung strictly below N
        # equals ((N - 1) // ci) * ci. Naive (N // ci) * ci would round one rung
        # too high on exact multiples (step 20 vs rung 20 instead of 15).
        completed = (completed // check_interval) * check_interval
    floor = per_step_threshold * env.dt * completed.float()
    return (cum < floor) & (env.episode_length_buf > grace_steps)

dofs_torque_limit_exceeded

dofs_torque_limit_exceeded(env: RLEnvBase, *, entity_name: str, soft_ratio: float = 1.0, probability: float = 1.0) -> Tensor

Terminate when any applied DOF torque exceeds soft_ratio * default_dofs_force_limits.

Uses the entity's per-DOF effort limit (the configured default_dofs_force_limits, else actuator-spec EFFORT_LIMIT, else the asset's force range) and triggers when |tau| > soft_ratio * limit for any joint. probability gates the termination globally per call.

Source code in eden/managers/terms/terminations/common.py
@TERMINATION_TERM_REGISTRY.register()
def dofs_torque_limit_exceeded(
    env: RLEnvBase,
    *,
    entity_name: str,
    soft_ratio: float = 1.0,
    probability: float = 1.0,
) -> torch.Tensor:
    """Terminate when any applied DOF torque exceeds ``soft_ratio * default_dofs_force_limits``.

    Uses the entity's per-DOF effort limit (the configured ``default_dofs_force_limits``, else actuator-spec
    ``EFFORT_LIMIT``, else the asset's force range) and triggers when ``|tau| > soft_ratio * limit`` for any
    joint. ``probability`` gates the termination globally per call.
    """
    entity: Entity = env.get_entity(entity_name)
    torques = entity.get_dofs_force()
    limit = entity.default_dofs_force_limits
    if limit is None:
        return torch.zeros(env.num_envs, dtype=torch.bool, device=env.device)
    # default_dofs_force_limits is shaped (num_envs, num_dofs) when batched, else (1, num_dofs).
    threshold = soft_ratio * limit
    violation = (torques.abs() > threshold).any(dim=1)
    return apply_probability(violation, probability)

Locomotion

Locomotion termination terms (base orientation thresholds, base height floor).

Attributes

Classes

Functions:

base_orientation_roll_greater_than

base_orientation_roll_greater_than(env: EnvBase, *, entity_name: str, threshold: float) -> Tensor

Terminate when the base roll exceeds threshold degrees.

Source code in eden/managers/terms/terminations/locomotion.py
@TERMINATION_TERM_REGISTRY.register()
def base_orientation_roll_greater_than(env: EnvBase, *, entity_name: str, threshold: float) -> torch.Tensor:
    """Terminate when the base roll exceeds ``threshold`` degrees."""
    robot: Entity = env.get_entity(entity_name)
    rpy = quat_to_xyz(robot.get_quat(), rpy=True)  # radians
    threshold_rad = math.radians(threshold)
    return torch.abs(rpy[:, 0]) > threshold_rad

base_orientation_pitch_greater_than

base_orientation_pitch_greater_than(env: EnvBase, *, entity_name: str, threshold: float) -> Tensor

Terminate when the base pitch exceeds threshold degrees.

Source code in eden/managers/terms/terminations/locomotion.py
@TERMINATION_TERM_REGISTRY.register()
def base_orientation_pitch_greater_than(env: EnvBase, *, entity_name: str, threshold: float) -> torch.Tensor:
    """Terminate when the base pitch exceeds ``threshold`` degrees."""
    robot: Entity = env.get_entity(entity_name)
    rpy = quat_to_xyz(robot.get_quat(), rpy=True)
    threshold_rad = math.radians(threshold)
    return torch.abs(rpy[:, 1]) > threshold_rad

root_height_below_minimum

root_height_below_minimum(env: EnvBase, *, entity_name: str, minimum_height: float, terrain_entity_name: str | None = None, relative: bool = False) -> Tensor

Terminate when the entity's base sinks below minimum_height above the ground.

The height floor half of a fall detector, complementing :func:~eden.managers.terms.terminations.velocity_tracking.fell_over (which watches the lean angle): a robot that collapses onto its belly while staying upright trips this one and not that one. Shares :func:~eden.managers.terms.utils.base_height_above_ground with the :func:~eden.managers.terms.observations.locomotion.base_height observation, so a policy that sees its height and an episode that ends on it agree on the number.

PARAMETER DESCRIPTION
env

The environment instance.

TYPE: EnvBase

entity_name

Entity whose base height is checked.

TYPE: str

minimum_height

Floor, in metres. The term fires while height < minimum_height — strictly below, so a robot resting exactly at the floor survives.

TYPE: float

terrain_entity_name

Terrain entity to measure against. None (the default) compares the world-frame z, which matches Isaac Lab's root_height_below_minimum and is correct on flat ground only; naming a :class:~eden.entities.terrain.Terrain compares the height above the terrain under the base instead, which is what a rough-terrain task wants — a world-z floor on a heightfield fires in every valley and never on a hill.

TYPE: str | None DEFAULT: None

relative

Frame the base pose is read in — see :func:~eden.managers.terms.observations.proprio.base_pos. Defaults to False (the solver frame), matching the base-pose observations and :func:~eden.managers.terms.rewards.locomotion.base_height_l2. Pass True to threshold the authored base-link origin, the frame default_root_pos and Isaac Lab's root_pos_w are in; the two differ by the base link's centre-of-mass offset.

TYPE: bool DEFAULT: False

Source code in eden/managers/terms/terminations/locomotion.py
@TERMINATION_TERM_REGISTRY.register()
def root_height_below_minimum(
    env: EnvBase,
    *,
    entity_name: str,
    minimum_height: float,
    terrain_entity_name: str | None = None,
    relative: bool = False,
) -> torch.Tensor:
    """Terminate when the entity's base sinks below ``minimum_height`` above the ground.

    The height floor half of a fall detector, complementing
    :func:`~eden.managers.terms.terminations.velocity_tracking.fell_over` (which watches the
    lean angle): a robot that collapses onto its belly while staying upright trips this one and not
    that one. Shares :func:`~eden.managers.terms.utils.base_height_above_ground` with the
    :func:`~eden.managers.terms.observations.locomotion.base_height` observation, so a policy that
    sees its height and an episode that ends on it agree on the number.

    Parameters
    ----------
    env : EnvBase
        The environment instance.
    entity_name : str
        Entity whose base height is checked.
    minimum_height : float
        Floor, in metres. The term fires while ``height < minimum_height`` — strictly below, so a
        robot resting exactly at the floor survives.
    terrain_entity_name : str | None, optional
        Terrain entity to measure against. ``None`` (the default) compares the world-frame ``z``,
        which matches Isaac Lab's ``root_height_below_minimum`` and is correct on flat ground only;
        naming a :class:`~eden.entities.terrain.Terrain` compares the height above the terrain under
        the base instead, which is what a rough-terrain task wants — a world-``z`` floor on a
        heightfield fires in every valley and never on a hill.
    relative : bool, optional
        Frame the base pose is read in — see
        :func:`~eden.managers.terms.observations.proprio.base_pos`. Defaults to ``False`` (the solver
        frame), matching the base-pose observations and
        :func:`~eden.managers.terms.rewards.locomotion.base_height_l2`. Pass ``True`` to threshold
        the authored base-link origin, the frame ``default_root_pos`` and Isaac Lab's ``root_pos_w``
        are in; the two differ by the base link's centre-of-mass offset.
    """
    terrain = resolve_terrain(env.entities, terrain_entity_name)
    robot: Entity = resolve_named_entity(env.entities, entity_name)
    return base_height_above_ground(robot, terrain, relative=relative) < minimum_height

Velocity tracking

Termination terms for humanoid velocity tracking tasks.

Attributes

Classes

Functions:

fell_over

fell_over(env: EnvBase, *, entity_name: str, limit_angle: float) -> Tensor

Terminate when the robot leans too far from vertical.

Uses projected gravity to determine the lean angle. The robot is considered fallen when the angle between the gravity vector in body frame and the world z-axis exceeds limit_angle (in radians).

Source code in eden/managers/terms/terminations/velocity_tracking.py
@TERMINATION_TERM_REGISTRY.register()
def fell_over(env: EnvBase, *, entity_name: str, limit_angle: float) -> torch.Tensor:
    """Terminate when the robot leans too far from vertical.

    Uses projected gravity to determine the lean angle. The robot is considered
    fallen when the angle between the gravity vector in body frame and the
    world z-axis exceeds ``limit_angle`` (in radians).
    """
    robot: Entity = env.get_entity(entity_name)
    proj_grav = projected_gravity(robot.get_quat(), gravity_vec(env.num_envs, env.device))
    # The z-component of projected gravity is -cos(lean_angle) (it is -1.0 when upright).
    cos_angle = -proj_grav[:, 2]
    return cos_angle < math.cos(limit_angle)

Manipulation

Manipulation termination terms (object out of reach).

Attributes

Classes

Functions:

object_out_of_reach

object_out_of_reach(env: EnvBase, *, entity_name: str, object_entity_name: str, object_link_name: str | None = None, max_dist: float)

Terminate when the base is over max_dist from the object in xy.

Source code in eden/managers/terms/terminations/manipulation.py
@TERMINATION_TERM_REGISTRY.register()
def object_out_of_reach(
    env: EnvBase,
    *,
    entity_name: str,
    object_entity_name: str,
    object_link_name: str | None = None,
    max_dist: float,
):
    """Terminate when the base is over ``max_dist`` from the object in xy."""
    entity: Entity = env.get_entity(entity_name)  # robot
    object_entity: Entity = env.get_entity(object_entity_name)  # object
    if object_link_name is not None:
        object_link: RigidLink = object_entity.get_link(object_link_name)
        # relative=False: world frame, to match the world-frame entity.get_pos() below.
        # Genesis >=1.1.1 link getters default to the user frame (offset stripped).
        object_pos_w = object_link.get_pos(relative=False)
    else:
        object_pos_w = object_entity.get_pos()
    dist = torch.norm(object_pos_w[..., :2] - entity.get_pos()[..., :2], dim=-1)
    return dist > max_dist

Motion tracking

Termination terms that gate episodes on robot-vs-motion-ref tracking error.

Two families:

  • Direct world-frame gates (function-style, registry keys motion_{base,link,dofs}_*_tracking_error_above): hard > threshold tests on raw distances. Symmetric with :mod:eden.managers.terms.rewards.tracking. The dexmachina ManipTrans curriculum kills envs whose pos / rot / per-link error exceed an eps; these terms supply the gates (decaying the eps is a curriculum's job).
  • Anchor-yaw-warped gates (bad_anchor_*, BadMotionLinkPosZOnly): compare robot state against the yaw-warped motion target so the gate doesn't fire just because the robot drifted in heading.

Also exposes :func:motion_ends, which fires when the motion buffer for the entity runs out of frames.

Reduction conventions for multi-link / multi-DOF terms:

  • "max" (default) — strict per-element gate; any link/DOF beyond threshold triggers. Matches ManipTrans semantics.
  • "mean" — averaged-error gate; lenient.

Attributes

Classes

BadMotionLinkPosZOnly

BadMotionLinkPosZOnly(env: EnvBase, options: _TermOptionsT)

Bases: TerminationTerm

Terminate when any tracked link's |z| error vs the motion ref exceeds threshold.

threshold_adaptive=True switches to an adaptive schedule — the threshold grows linearly when the motion's anchor z drops below root_height_threshold (default 0.5 m). At the floor the threshold becomes threshold * (1 + down_threshold) so the gate is more lenient for crawl / kneel clips, while staying tight for upright walks. With threshold_adaptive=False (default) the static threshold is used.

Source code in eden/managers/base.py
def __init__(self, env: EnvBase, options: _TermOptionsT):
    ManagerTermBase.__init__(self, env=env)
    ConfigurableMixin.__init__(self, options=options)

Attributes

entity_name class-attribute instance-attribute
entity_name: str = 'robot'
anchor_link_name: str = 'torso_link'
links_name: list[str] = []
threshold class-attribute instance-attribute
threshold: float = 0.01
threshold_adaptive class-attribute instance-attribute
threshold_adaptive: bool = False
down_threshold class-attribute instance-attribute
down_threshold: float = 0.0
root_height_threshold class-attribute instance-attribute
root_height_threshold: float = 0.5

Methods:

build
build() -> None
Source code in eden/managers/terms/terminations/tracking.py
def build(self) -> None:
    assert_motion_field_present(self._env, self.entity_name, "links_pos_w")
    self._motion_idx = get_motion_link_idx(self._env, self.entity_name, self.links_name)
    self._robot_idx = get_robot_link_idx(self._env, self.entity_name, self.links_name)
    if self.threshold_adaptive:
        # Resolve once at build time so compute() doesn't pay the
        # name-lookup cost per step.
        self._motion_anchor_idx = get_motion_link_idx(self._env, self.entity_name, [self.anchor_link_name])
    else:
        self._motion_anchor_idx = None
compute
compute(envs_idx: Tensor | None = None) -> Tensor
Source code in eden/managers/terms/terminations/tracking.py
def compute(self, envs_idx: torch.Tensor | None = None) -> torch.Tensor:
    ref = self._env.motion_manager.data[self.entity_name]
    motion_links_pos_w = current_motion_frame(ref.links_pos_w, self._motion_idx)
    robot_links_pos_w = self.resolve_entity(self.entity_name).get_links_pos(ls_idx_local=self._robot_idx)
    error = torch.abs(motion_links_pos_w[:, :, -1] - robot_links_pos_w[:, :, -1])
    if self._motion_anchor_idx is None:
        return torch.any(error > self.threshold, dim=-1)
    motion_anchor_z = current_motion_frame(ref.links_pos_w, self._motion_anchor_idx)[:, 0, -1]
    eff_threshold = _adaptive_pos_threshold(
        motion_anchor_z,
        self.threshold,
        self.threshold_adaptive,
        self.down_threshold,
        self.root_height_threshold,
    )
    # eff_threshold is (num_envs,); broadcast against the (num_envs, K) error.
    return torch.any(error > eff_threshold[:, None], dim=-1)

MotionBasePoseTrackingBreak

MotionBasePoseTrackingBreak(env: EnvBase, options: _TermOptionsT)

Bases: TerminationTerm

Base-pose deviation gate vs the current motion frame.

Terminates when the entity's world-frame base position error exceeds pos_threshold (m) OR its orientation error exceeds rot_threshold (radians). ConTrack's object pose-break termination.

pos_threshold / rot_threshold are where the gate starts, and assigning either after build raises. The gate reads its thresholds from the device (:attr:thresholds), so a curriculum can tighten them online with :meth:set_thresholds, which writes them in place without a host round trip: pair with :class:~eden.managers.terms.curricula.motion.MotionBasePoseThresholdCurriculum to get ConTrack's success-driven threshold schedule (which is where that state belongs — it is then logged under Curriculum/* and frozen at eval). On its own this term is a static gate.

Source code in eden/managers/base.py
def __init__(self, env: EnvBase, options: _TermOptionsT):
    ManagerTermBase.__init__(self, env=env)
    ConfigurableMixin.__init__(self, options=options)

Attributes

entity_name class-attribute instance-attribute
entity_name: str = 'object'
pos_threshold class-attribute instance-attribute
pos_threshold: float = 0.1
rot_threshold class-attribute instance-attribute
rot_threshold: float = 1.0
thresholds property
thresholds: tuple[Tensor, Tensor]

The gate's current (pos_threshold, rot_threshold), as the 0-dim device tensors it compares against.

Methods:

build
build() -> None
Source code in eden/managers/terms/terminations/tracking.py
def build(self) -> None:
    assert_motion_field_present(self._env, self.entity_name, "base_pos_w")
    assert_motion_field_present(self._env, self.entity_name, "base_quat_w")
    # Allocated once and written in place (set_thresholds), so the gate reads one address all run: a CUDA
    # graph of the graded phase (EnvOptions.cuda_graph_options) replays reading them where they live.
    self._pos_threshold = torch.tensor(float(self.pos_threshold), device=self.device)
    self._rot_threshold = torch.tensor(float(self.rot_threshold), device=self.device)
set_thresholds
set_thresholds(pos_threshold: float | Tensor, rot_threshold: float | Tensor) -> None

Move the gate to (pos_threshold, rot_threshold), in place (a 0-dim device tensor never syncs).

Source code in eden/managers/terms/terminations/tracking.py
def set_thresholds(self, pos_threshold: float | torch.Tensor, rot_threshold: float | torch.Tensor) -> None:
    """Move the gate to ``(pos_threshold, rot_threshold)``, in place (a 0-dim device tensor never syncs)."""
    self._pos_threshold.fill_(pos_threshold)
    self._rot_threshold.fill_(rot_threshold)
compute
compute(envs_idx: Tensor | None = None) -> Tensor
Source code in eden/managers/terms/terminations/tracking.py
def compute(self, envs_idx: torch.Tensor | None = None) -> torch.Tensor:
    entity = self.resolve_entity(self.entity_name)
    ref = self._env.motion_manager.data[self.entity_name]
    pos_err = torch.norm(entity.get_pos() - current_motion_frame(ref.base_pos_w), dim=-1)
    rot_err = quat_error_magnitude(entity.get_quat(), current_motion_frame(ref.base_quat_w))
    return (pos_err > self._pos_threshold) | (rot_err > self._rot_threshold)

Functions:

motion_base_position_tracking_error_above

motion_base_position_tracking_error_above(env: MimicRLEnvBase, *, entity_name: str, threshold: float) -> Tensor

Terminate when world-frame base-position L2 error exceeds threshold.

Source code in eden/managers/terms/terminations/tracking.py
@TERMINATION_TERM_REGISTRY.register()
def motion_base_position_tracking_error_above(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    threshold: float,
) -> torch.Tensor:
    """Terminate when world-frame base-position L2 error exceeds ``threshold``."""
    assert_motion_field_present(env, entity_name, "base_pos_w")
    entity = env.get_entity(entity_name)
    ref = env.motion_manager.data[entity_name]
    return torch.norm(entity.get_pos() - current_motion_frame(ref.base_pos_w), dim=-1) > threshold

motion_base_orientation_tracking_error_above

motion_base_orientation_tracking_error_above(env: MimicRLEnvBase, *, entity_name: str, threshold: float) -> Tensor

Terminate when base quat-error magnitude (wxyz) exceeds threshold.

Source code in eden/managers/terms/terminations/tracking.py
@TERMINATION_TERM_REGISTRY.register()
def motion_base_orientation_tracking_error_above(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    threshold: float,
) -> torch.Tensor:
    """Terminate when base quat-error magnitude (wxyz) exceeds ``threshold``."""
    assert_motion_field_present(env, entity_name, "base_quat_w")
    entity = env.get_entity(entity_name)
    ref = env.motion_manager.data[entity_name]
    return quat_error_magnitude(entity.get_quat(), current_motion_frame(ref.base_quat_w)) > threshold
motion_link_position_tracking_error_above(env: MimicRLEnvBase, *, entity_name: str, links_name: list[str], threshold: float, reduction: Literal['mean', 'max'] = 'max') -> Tensor

Terminate when link-position L2 error reduction exceeds threshold.

Source code in eden/managers/terms/terminations/tracking.py
@TERMINATION_TERM_REGISTRY.register()
def motion_link_position_tracking_error_above(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    links_name: list[str],
    threshold: float,
    reduction: Literal["mean", "max"] = "max",
) -> torch.Tensor:
    """Terminate when link-position L2 error reduction exceeds ``threshold``."""
    assert_motion_field_present(env, entity_name, "links_pos_w")
    motion_idx = get_motion_link_idx(env, entity_name, links_name)
    robot_idx = get_robot_link_idx(env, entity_name, links_name)
    ref = env.motion_manager.data[entity_name]
    motion_pos = current_motion_frame(ref.links_pos_w, motion_idx)
    robot_pos = env.get_entity(entity_name).get_links_pos(ls_idx_local=robot_idx)
    dist = torch.norm(motion_pos - robot_pos, dim=-1)
    err = dist.max(-1).values if reduction == "max" else dist.mean(-1)
    return err > threshold
motion_link_orientation_tracking_error_above(env: MimicRLEnvBase, *, entity_name: str, links_name: list[str], threshold: float, reduction: Literal['mean', 'max'] = 'max') -> Tensor

Terminate when link quat-error reduction exceeds threshold.

Source code in eden/managers/terms/terminations/tracking.py
@TERMINATION_TERM_REGISTRY.register()
def motion_link_orientation_tracking_error_above(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    links_name: list[str],
    threshold: float,
    reduction: Literal["mean", "max"] = "max",
) -> torch.Tensor:
    """Terminate when link quat-error reduction exceeds ``threshold``."""
    assert_motion_field_present(env, entity_name, "links_quat_w")
    motion_idx = get_motion_link_idx(env, entity_name, links_name)
    robot_idx = get_robot_link_idx(env, entity_name, links_name)
    ref = env.motion_manager.data[entity_name]
    motion_quat = current_motion_frame(ref.links_quat_w, motion_idx)
    robot_quat = env.get_entity(entity_name).get_links_quat(ls_idx_local=robot_idx)
    err = quat_error_magnitude(motion_quat, robot_quat)
    err = err.max(-1).values if reduction == "max" else err.mean(-1)
    return err > threshold

motion_dofs_pos_tracking_error_above

motion_dofs_pos_tracking_error_above(env: MimicRLEnvBase, *, entity_name: str, threshold: float, dofs_name: list[str] | None = None, reduction: Literal['mean', 'max'] = 'max') -> Tensor

Terminate when per-DOF |q − q_ref| reduction exceeds threshold.

Source code in eden/managers/terms/terminations/tracking.py
@TERMINATION_TERM_REGISTRY.register()
def motion_dofs_pos_tracking_error_above(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    threshold: float,
    dofs_name: list[str] | None = None,
    reduction: Literal["mean", "max"] = "max",
) -> torch.Tensor:
    """Terminate when per-DOF |q − q_ref| reduction exceeds ``threshold``."""
    assert_motion_field_present(env, entity_name, "dofs_pos")
    entity = env.get_entity(entity_name)
    ref = env.motion_manager.data[entity_name]
    if dofs_name is None:
        dofs_name = env.motion_manager.get_target_dofs_name(entity_name)
        if dofs_name is None:
            raise ValueError(f"Entity '{entity_name}' is not tracked by any motion term.")
        if not dofs_name:
            raise ValueError(
                f"motion_dofs_pos_tracking_error_above: entity '{entity_name}' has no DOFs "
                f"in the motion reference. Use motion_link_position_tracking_error_above / "
                f"motion_base_position_tracking_error_above for rigid or link-only entities, "
                f"or pass an explicit dofs_name list."
            )
    motion_idx = get_motion_dofs_idx(env, entity_name, dofs_name)
    robot_idx = get_robot_dofs_idx(env, entity_name, dofs_name)
    motion_q = current_motion_frame(ref.dofs_pos, motion_idx)
    # `dofs_idx_local` to the getter rather than a gather off a full-width read — see the same
    # line in ``motion_dofs_pos_tracking_exp``: `get_dofs_pos()` is columned in dofs_name order,
    # which `robot_idx` (Genesis-local) does not index.
    robot_q = entity.get_dofs_pos(dofs_idx_local=robot_idx)
    err = (motion_q - robot_q).abs()
    err = err.max(-1).values if reduction == "max" else err.mean(-1)
    return err > threshold

bad_anchor_pos_z_only

bad_anchor_pos_z_only(env: MimicRLEnvBase, *, entity_name: str = 'robot', anchor_link_name: str = 'torso_link', threshold: float = 0.01, threshold_adaptive: bool = False, down_threshold: float = 0.0, root_height_threshold: float = 0.5) -> Tensor

Terminate when anchor link's |z| error vs motion ref exceeds threshold.

Asserts both links_pos_w and links_quat_w because :func:get_anchor_state reads both regardless of which we consume here.

threshold_adaptive=True switches to an adaptive schedule — the threshold grows linearly when the motion's anchor z drops below root_height_threshold (default 0.5 m). At the floor the threshold becomes threshold * (1 + down_threshold) so the gate is more lenient for crawl / kneel clips, while staying tight for upright walks. With threshold_adaptive=False (default) the static threshold is used.

Source code in eden/managers/terms/terminations/tracking.py
@TERMINATION_TERM_REGISTRY.register()
def bad_anchor_pos_z_only(
    env: MimicRLEnvBase,
    *,
    entity_name: str = "robot",
    anchor_link_name: str = "torso_link",
    threshold: float = 0.01,
    threshold_adaptive: bool = False,
    down_threshold: float = 0.0,
    root_height_threshold: float = 0.5,
) -> torch.Tensor:
    """Terminate when anchor link's |z| error vs motion ref exceeds ``threshold``.

    Asserts both ``links_pos_w`` and ``links_quat_w`` because
    :func:`get_anchor_state` reads both regardless of which we consume here.

    ``threshold_adaptive=True`` switches to an adaptive schedule —
    the threshold grows linearly when the motion's anchor z drops below
    ``root_height_threshold`` (default ``0.5`` m). At the floor the
    threshold becomes ``threshold * (1 + down_threshold)`` so the gate is
    more lenient for crawl / kneel clips, while staying tight for upright
    walks. With ``threshold_adaptive=False`` (default) the static
    ``threshold`` is used.
    """
    assert_motion_field_present(env, entity_name, "links_pos_w")
    assert_motion_field_present(env, entity_name, "links_quat_w")
    robot_pos, _, motion_pos, _ = anchor_state_once(env, entity_name, anchor_link_name)
    eff_threshold = _adaptive_pos_threshold(
        motion_pos[:, -1], threshold, threshold_adaptive, down_threshold, root_height_threshold
    )
    return torch.abs(motion_pos[:, -1] - robot_pos[:, -1]) > eff_threshold

bad_anchor_ori

bad_anchor_ori(env: MimicRLEnvBase, *, entity_name: str = 'robot', anchor_link_name: str = 'torso_link', threshold: float = 0.01) -> Tensor

Terminate when the anchor's projected-gravity z diverges from the motion ref's.

Fires when the difference exceeds threshold (a pitch/roll-only check on the unit gravity direction, so an Isaac Lab threshold ports unchanged).

Asserts both links_pos_w and links_quat_w because :func:get_anchor_state reads both regardless of which we consume here.

Source code in eden/managers/terms/terminations/tracking.py
@TERMINATION_TERM_REGISTRY.register()
def bad_anchor_ori(
    env: MimicRLEnvBase,
    *,
    entity_name: str = "robot",
    anchor_link_name: str = "torso_link",
    threshold: float = 0.01,
) -> torch.Tensor:
    """Terminate when the anchor's projected-gravity z diverges from the motion ref's.

    Fires when the difference exceeds ``threshold`` (a pitch/roll-only check on the unit gravity direction, so
    an Isaac Lab threshold ports unchanged).

    Asserts both ``links_pos_w`` and ``links_quat_w`` because
    :func:`get_anchor_state` reads both regardless of which we consume here.
    """
    assert_motion_field_present(env, entity_name, "links_pos_w")
    assert_motion_field_present(env, entity_name, "links_quat_w")
    _, robot_quat, _, motion_quat = anchor_state_once(env, entity_name, anchor_link_name)
    return _projected_gravity_z_error(env, robot_quat, motion_quat) > threshold

bad_base_ori

bad_base_ori(env: MimicRLEnvBase, *, entity_name: str, threshold: float) -> Tensor

Terminate when a free base's projected-gravity z diverges from the motion ref's by more than threshold.

The base-pose counterpart of :func:bad_anchor_ori — a tilt-only check that ignores yaw, for an entity with no anchor link of its own to read: a tracked object that has been knocked over, while spinning it about the vertical is left to the orientation reward. Reads get_quat() (build the object with align=False so that is the authored body frame the reference records).

Source code in eden/managers/terms/terminations/tracking.py
@TERMINATION_TERM_REGISTRY.register()
def bad_base_ori(env: MimicRLEnvBase, *, entity_name: str, threshold: float) -> torch.Tensor:
    """Terminate when a free base's projected-gravity z diverges from the motion ref's by more than ``threshold``.

    The base-pose counterpart of :func:`bad_anchor_ori` — a tilt-only check that ignores yaw, for an entity with no
    anchor link of its own to read: a tracked object that has been knocked over, while spinning it about the vertical
    is left to the orientation reward. Reads ``get_quat()`` (build the object with ``align=False`` so that is the
    authored body frame the reference records).
    """
    assert_motion_field_present(env, entity_name, "base_quat_w")
    motion_quat = current_motion_frame(env.motion_manager.data[entity_name].base_quat_w)
    return _projected_gravity_z_error(env, env.get_entity(entity_name).get_quat(), motion_quat) > threshold

motion_ends

motion_ends(env: MimicRLEnvBase, *, entity_name: str) -> Tensor

Terminate when the motion buffer's current lookahead frame is invalid.

Reads :attr:MotionReferenceData.validity at index [:, 0] for entity_name. Note: with the default MotionManagerOptions.data_start_from="one_frame_interval", frame 0 is the next frame to be played, not the current one — so this term fires one step before the policy actually sees the missing frame. With data_start_from="current_time" it fires on the current frame.

Source code in eden/managers/terms/terminations/tracking.py
@TERMINATION_TERM_REGISTRY.register()
def motion_ends(env: MimicRLEnvBase, *, entity_name: str) -> torch.Tensor:
    """Terminate when the motion buffer's current lookahead frame is invalid.

    Reads :attr:`MotionReferenceData.validity` at index ``[:, 0]`` for
    ``entity_name``. **Note**: with the default
    ``MotionManagerOptions.data_start_from="one_frame_interval"``, frame ``0``
    is the *next* frame to be played, not the current one — so this term
    fires one step *before* the policy actually sees the missing frame. With
    ``data_start_from="current_time"`` it fires on the current frame.
    """
    ref = env.motion_manager.data[entity_name]
    return ~current_motion_frame(ref.validity)

See also