Math and Geometry Utilities¶
Batched math helpers: quaternion, rotation and pose-transform operations, bilateral symmetry for augmentation, sampling and seeding, and torch configuration. Quaternions are wxyz throughout.
Geometry¶
eden.utils.geom re-exports, as the same objects, every genesis.utils.geom quaternion and pose-transform helper
that has a batched torch path (quat_to_R, transform_by_quat, inv_transform_by_trans_quat, slerp, ...), so
either import works, and adds the helpers Genesis does not have (quat_mul, subtract_frame_transforms,
rot6d_to_quat, align_up_and_front, ...). Genesis' Euler converters are deliberately not re-exported: their
convention differs from this module's quat_from_euler_xyz. Use these helpers rather than re-implementing quaternion
math at a call site; if an operation is missing, add it here with a test in tests/utils/test_geom.py.
Geometry helpers: axis alignment, point transforms, rotation conversions.
Functions:¶
align_up_to_z
¶
Aligns the given 'up' vector to the Z-up direction (0, 0, 1).
| PARAMETER | DESCRIPTION |
|---|---|
up
|
A 3D vector to align to Z-up. If list/tuple, it's converted to a tensor. |
as_torch
|
If True, the returned rotation matrix is a torch.Tensor. Otherwise, it's a numpy.ndarray.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
R
|
A 3x3 rotation matrix that aligns 'up' to the Z-axis.
TYPE:
|
Source code in eden/utils/geom.py
align_up_and_front
¶
Return a rotation matrix R that realigns an asset's canonical axes to world axes.
The asset's canonical up and front axes are given by up and front (in the
asset's own mesh frame) so that after applying R::
R @ up = (0, 0, 1) (asset's up aligned to world Z)
R @ front = (1, 0, 0) (asset's front aligned to world X)
| PARAMETER | DESCRIPTION |
|---|---|
up
|
The asset's "up" direction in its own canonical/mesh frame. |
front
|
The asset's "front" direction in its own canonical/mesh frame. |
as_torch
|
If True, the returned rotation matrix is a torch.Tensor. Otherwise, it's a numpy.ndarray.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
R
|
A 3x3 rotation matrix.
TYPE:
|
Source code in eden/utils/geom.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
transform_by_R
¶
Transform 3D points by a 3x3 rotation matrix or a batch of matrices.
| PARAMETER | DESCRIPTION |
|---|---|
pos
|
A torch tensor of 3D points. Can be a single point (3,), a batch of points (B, 3), or a batched batch of points (B, N, 3).
TYPE:
|
R
|
The 3x3 rotation matrix or a batch of B rotation matrices of shape (B, 3, 3).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
The transformed points in a shape corresponding to the input dimensions. |
Source code in eden/utils/geom.py
transform_by_T
¶
Transform 3D points by a 4x4 transformation matrix or a batch of matrices.
| PARAMETER | DESCRIPTION |
|---|---|
pos
|
A torch tensor of 3D points. Can be a single point (3,), a batch of points (B, 3), or a batched batch of points (B, N, 3).
TYPE:
|
T
|
The 4x4 transformation matrix or a batch of B transformation matrices of shape (B, 4, 4).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
The transformed points in a shape corresponding to the input dimensions. |
Source code in eden/utils/geom.py
rot6d_to_quat
¶
quat_to_rot6d
¶
Zhou et al. 6D rotation rep via genesis R_to_rot6d: the first two rows of R.
Layout is [R00, R01, R02, R10, R11, R12]. Two other 6D layouts exist in
this repo and are not interchangeable with it at a trained policy's
input — they are different scalars, not a reordering:
- first two columns, row-major (
quat_to_R(q)[..., :2].flatten(-2)=[R00, R01, R10, R11, R20, R21]) — what IsaacLab'smatrix_from_quat(q)[..., :2].reshape(-1, 6)produces, used by the SONIC port and bymotion_future_links_ori_b(representation="rot6d"). - :func:
quat_to_tan_norm([R·x̂, R·ẑ]) — the ProtoMotions rep.
Source code in eden/utils/geom.py
R_to_rot6d_cols
¶
6D rotation rep as the first two columns of R, flattened row-major.
Layout [R00, R01, R10, R11, R20, R21] — what IsaacLab's
matrix_from_quat(q)[..., :2].reshape(-1, 6) produces, and what the SONIC
deploy C++ writes as {R[0][0], R[0][1], R[1][0], R[1][1], R[2][0], R[2][1]}.
A column-stacked [R00, R10, R20, R01, R11, R21] would also round-trip
through a Gram-Schmidt decoder but feeds a trained head different scalars,
so the flatten order is part of the contract, not a detail. This is also NOT
:func:quat_to_rot6d (genesis: the first two rows) — see that docstring.
Source code in eden/utils/geom.py
quat_to_rot6d_cols
¶
R_to_tan_norm
¶
ProtoMotions quat_to_tan_norm 6D rotation rep from a rotation matrix (..., 3, 3).
Returns [R·x̂, R·ẑ] concatenated — columns 0 and 2 of R — as (..., 6).
Takes R (not a quat) for the callers that already built the matrix; see
:func:quat_to_tan_norm for the quaternion entry point.
This is a different 6D layout from :func:quat_to_rot6d — see its
docstring for the three variants in play.
Source code in eden/utils/geom.py
quat_to_tan_norm
¶
ProtoMotions quat_to_tan_norm 6D rotation rep from a wxyz quaternion (..., 4).
Returns [R(q)·x̂, R(q)·ẑ] as (..., 6). Both halves are of the form
R(q) @ v for a fixed canonical vector, so the output scalars are the
same whether the source stack encodes q wxyz or xyzw — only the input
ordering matters, and Eden is wxyz throughout.
Source code in eden/utils/geom.py
transform_by_quat_yaw_approx
¶
Rotate v by an approximate yaw-only quaternion (cheap path).
The heading is approximated by zeroing qx/qy and renormalising,
which is fast but only equals the true yaw when roll/pitch are ~0. For the
exact atan2 heading use :func:quat_apply_yaw (note the swapped argument
order). The two are deliberately NOT aliases — see
:func:yaw_quat and test_yaw_quat_differs_from_zeroing_qx_qy_shortcut.
Source code in eden/utils/geom.py
xyzw_to_wxyz
¶
Reorder the last axis of a quaternion from (x, y, z, w) to (w, x, y, z).
Accepts either numpy.ndarray or torch.Tensor; fancy indexing on the
last axis is backend-agnostic.
wxyz_to_xyzw
¶
Reorder the last axis of a quaternion from (w, x, y, z) to (x, y, z, w).
Accepts either numpy.ndarray or torch.Tensor.
segment_closest_points
¶
Closest points between segment batches [p0, p1] and [q0, q1], all (..., 3) numpy arrays.
Returns (on_p, on_q), each (..., 3). A zero-length segment is a point, in either slot; parallel
segments return one of their equally close pairs.
Source code in eden/utils/geom.py
planar_rigid_transform
¶
Least-squares planar rigid motion b ~ a @ R.T + t of (n, 2) point sets: (R (2, 2), t (2,)).
Translation only when the points are too close together to show a rotation (rms distance from their centroid
under min_spread): for coincident points the fit is atan2(0, 0), an arbitrary angle (three probes sharing
one source point would otherwise read a planted foot as turned 180 deg).
Source code in eden/utils/geom.py
skew
¶
(..., 3) vectors to (..., 3, 3) cross-product matrices: skew(a) @ b == cross(a, b).
Accepts either numpy.ndarray or torch.Tensor.
Source code in eden/utils/geom.py
quat_mul
¶
Hamilton product q1 ∘ q2 (wxyz), matching isaac_math's argument order.
genesis.transform_quat_by_quat(v, u) applies u on the left, so the
arguments are swapped here to reproduce q1 ∘ q2. Kept as a named wrapper
(rather than calling genesis at the use sites) precisely because both
operands are quaternions, so the order swap is an invisible footgun. genesis
renormalises the product (drift-resistant): identical to
isaac_math.quat_mul for unit inputs; for non-unit inputs it returns a
unit quaternion rather than the scaled product.
Source code in eden/utils/geom.py
quat_nlerp
¶
Shortest-path normalized linear interpolation (NLERP) of two quaternions.
Flips q1 into q0's hemisphere, blends linearly by t, and
renormalises. Batched over any leading dims; t is a scalar, one weight
per quaternion (shape q0.shape[:-1]), or already shaped (..., 1).
Accepts numpy.ndarray or torch.Tensor (both operands the same type).
Usable before gs.init() (its epsilon is not gs.EPS, which is None
until then), like the other helpers here that deployment hot loops call.
NLERP is a component-wise blend, so it is convention-agnostic — valid for
both wxyz and scalar-last (xyzw) quaternions. It is cheaper than a true
spherical interpolation and matches the reference SONIC pose resampler's
interpolant; for exact SLERP use genesis.utils.geom.slerp instead. There
is no NLERP in genesis.utils.geom, which is why this lives here.
Source code in eden/utils/geom.py
quat_swing_twist
¶
Swing-twist decomposition of wxyz quaternions about axis: quat = twist ⊗ swing.
twist is the rotation about axis (the quaternion's vector part projected onto
it, renormalised); swing is the remainder, inv(twist) ⊗ quat. A 180-degree
rotation about an axis perpendicular to axis has no twist component at all (the
projection is zero); the twist is then the identity and the swing carries the whole
rotation, rather than the NaN a plain renormalisation gives.
Batched over leading dims; axis is a unit 3-vector. Accepts numpy.ndarray or
torch.Tensor. The product and inverse are genesis.utils.geom's, whose numpy
path needs gs.init().
Source code in eden/utils/geom.py
quat_from_euler_xyz
¶
Quaternion (..., 4) wxyz from extrinsic XYZ Euler angles (radians): R = Rz(yaw) Ry(pitch) Rx(roll).
The inverse of :func:euler_xyz_from_quat.
Ported verbatim (torch) from isaac_math.quat_from_euler_xyz. genesis
euler_to_quat is numpy-only and expects degrees, so it is not a drop-in
for the vectorised torch term code.
Source code in eden/utils/geom.py
euler_xyz_from_quat
¶
Roll/pitch/yaw (radians, extrinsic XYZ) from a wxyz quaternion (..., 4).
Ported verbatim (torch) from isaac_math.euler_xyz_from_quat. genesis
quat_to_xyz is numpy-only and returns degrees.
Source code in eden/utils/geom.py
intrinsic_xyz_from_quat
¶
Intrinsic X-Y-Z Euler angles (..., 3) (radians) of a wxyz quaternion (..., 4).
The angles (a, b, c) with R(q) = Rx(a) @ Ry(b) @ Rz(c) — the coordinates of three stacked hinge
joints about the body's x, y, then z axes (the MJCF/Genesis way of writing a 3-DOF ball, e.g. PHC's SMPL
humanoid). b is in [-pi/2, pi/2]; a and c in (-pi, pi].
Not :func:euler_xyz_from_quat, which is the extrinsic XYZ (roll/pitch/yaw) decomposition. It is the
torch form of genesis.utils.geom.quat_to_xyz(q, rpy=False), which is numba/numpy-only.
Source code in eden/utils/geom.py
intrinsic_xyz_nearest
¶
intrinsic_xyz_nearest(abc: Tensor, near: Tensor, lock_band: float = 0.0) -> Tensor
The intrinsic X-Y-Z angles (..., 3) of the rotation abc names that lie nearest near.
A rotation has two intrinsic X-Y-Z decompositions, (a, b, c) and (a + pi, pi - b, c + pi), and each
angle is defined only up to whole turns. :func:intrinsic_xyz_from_quat returns the one with b in
[-pi/2, pi/2], so a body turning past b = +-pi/2 — a knee bending past 90 deg about y — jumps by
pi in both a and c there. This returns, of both decompositions with every angle shifted by whole
turns towards near, the one at the smaller squared distance from it: the target three stacked hinges
reach without swinging through unrelated rotations. abc and near broadcast against each other.
Near b = +-pi/2 (gimbal lock) the x and z hinges turn about nearly one axis, so v = a - sin(b) c is
ill-conditioned: a rotation delta off that axis needs v to move by about delta / cos(b), up to
pi from a twitch of the input, and at the lock v is not defined at all. With lock_band > 0, where
|cos b| < lock_band the result moves along (1, 0, -sin b) — v alone, to first order — by the
fraction (1 - (cos(b) / lock_band)^2)^2 of the way to near's v: fully at the lock, where that
names the same rotation, and not at all at the band's edge or outside it. The price is a rotation error
within the band, from any near, of just under lock_band / 2 (0.14 rad at 0.3); 0 keeps the exact
angles.
Source code in eden/utils/geom.py
intrinsic_xyz_rates
¶
intrinsic_xyz_rates(abc: Tensor, ang_vel: Tensor, lock_band: float = 0.0) -> Tensor
Rates (..., 3) of the intrinsic X-Y-Z angles abc that turn the body at ang_vel.
ang_vel (..., 3) is the body-frame angular velocity of R = Rx(a) @ Ry(b) @ Rz(c) — w with
R^T dR/dt = [w]x — so for three stacked hinges it is the child's angular velocity relative to the
parent, in the child's axes; the result is what to write to the hinges' velocities. It inverts
w = (cos b cos c, -cos b sin c, sin b) da + (sin c, cos c, 0) db + (0, 0, 1) dc, whose determinant is
cos b: da = p / cos b with p = cos c w_x - sin c w_y, so near gimbal lock a slow turn off the
x/z axis spins the x and z hinges at about |w| / cos b, without bound.
With lock_band > 0, where |cos b| < lock_band the 1 / cos b becomes (1 - weight) / cos b, with
weight the fraction :func:intrinsic_xyz_nearest damps v by: at most 1.09 / lock_band, and 0 at
the lock. dc still matches w_z, so only p, the part of w the hinges barely reach there, is
given up. 0 keeps the exact rates (infinite at the lock).
Source code in eden/utils/geom.py
quat_unique
¶
The wxyz quaternion with a non-negative real part, of the two (q, -q) naming one rotation.
Ported verbatim from isaac_math.quat_unique. Shape (..., 4) in and out. Accepts
numpy.ndarray or torch.Tensor.
Source code in eden/utils/geom.py
quat_slerp
¶
Shortest-arc spherical interpolation from wxyz q0 to q1 at t, PHC's torch_utils.slerp.
q0 / q1 are (..., 4); t broadcasts against them with a trailing axis of 1 ((..., 1)).
Ported verbatim, quirks included, because PHC-trained policies are graded against references
interpolated this way: when the two rotations are within ~0.1 deg (sin(half angle) < 1e-3) the result
is their plain average whatever t is, and the output is not renormalized.
Source code in eden/utils/geom.py
axis_angle_from_quat
¶
axis_angle_from_quat(quat: Tensor, eps: float = 1e-06) -> Tensor
Axis-angle vector (..., 3) from a wxyz quaternion (..., 4).
The vector magnitude is the rotation angle (radians), canonicalised to [0, pi]:
the input is first flipped to the w >= 0 hemisphere, so a quaternion and its
negation — the same rotation — map to the same vector. Ported (torch) from
isaac_math.axis_angle_from_quat, including the small-angle Taylor branch. genesis
quat_to_rotvec is numba/numpy-only and lacks the Taylor expansion near zero, so it
is not a torch drop-in; it also reads gs.EPS, so it raises before gs.init().
Accepts numpy.ndarray or torch.Tensor. Composed with R_to_quat it is the
rotation-matrix log map (R -> rotation vector).
Source code in eden/utils/geom.py
quat_from_axis_angle
¶
quat_from_axis_angle(axis_angle: Tensor, eps: float = 1e-06) -> Tensor
Quaternion (wxyz) (..., 4) from an axis-angle vector (..., 3).
Inverse of :func:axis_angle_from_quat (magnitude = angle in radians), with the same
small-angle Taylor branch so a zero vector maps to identity instead of NaN.
The round trip preserves the rotation for any input, and the vector itself only for
||v|| <= pi: beyond that, axis_angle_from_quat canonicalises to the w >= 0
hemisphere and returns the equal-but-shorter (2*pi - ||v||) turn about the flipped axis.
SMPL/AMASS consume the rotation, so the distinction does not matter there — but do not rely
on the vector surviving a round trip unchanged.
genesis axis_angle_to_quat takes the angle and a separate unit axis, so it is not a
drop-in for the packed-vector form SMPL/AMASS pose data uses; genesis rotvec_to_R and
rotvec_to_quat have no small-angle branch (NaN for a zero vector before gs.init()).
Accepts numpy.ndarray or torch.Tensor. Composed with quat_to_R it is the
rotation-vector exp map (rotation vector -> R).
Source code in eden/utils/geom.py
yaw_quat
¶
Heading-only (yaw) component of a wxyz quaternion, same shape in/out.
Extracts the true yaw via atan2 and rebuilds a pure-z quaternion. NOTE:
this is NOT the same as zeroing qx/qy and renormalising
(:func:transform_by_quat_yaw_approx), which only agrees when roll/pitch are zero.
Ported from isaac_math.yaw_quat so the heading-invariant motion warp
keeps identical behaviour.
Source code in eden/utils/geom.py
yaw_from_quat
¶
Heading angle in radians about world Z from a wxyz quaternion. (..., 4) -> (...).
atan2(2(wz + xy), 1 - 2(yy + zz)) — the same angle :func:yaw_quat builds
its pure-z quaternion from, and identical to euler_xyz_from_quat(q)[2]
without computing and discarding roll and pitch.
Accepts numpy.ndarray or torch.Tensor. Deliberately hand-written
rather than delegating to genesis.utils.geom.quat_to_xyz(q, rpy=True)[..., 2]:
that path is a numba kernel that reads gs.EPS, so it raises a
TypingError until gs.init() has run, and it decomposes all three
Euler angles to return one. Callers here are offline tools and deployment
hot loops that must not depend on simulator init order.
NOTE: this is the heading. genesis's quat_to_xyz defaults to
rpy=False, an intrinsic Rx·Ry·Rz decomposition whose third angle is a
body-z angle, not the heading — the two agree only when roll/pitch are ~0.
Source code in eden/utils/geom.py
heading_from_quat
¶
Heading angle in radians about world Z of a body-frame forward axis. (..., 4) -> (...).
Rotates forward ((..., 3), broadcast against quat's batch shape; body +x when
None) into the world by the wxyz quat and returns atan2(y, x) of the result.
Accepts numpy.ndarray or torch.Tensor.
With the default +x this is the angle :func:yaw_from_quat computes, but not the same
floats: transform_by_quat divides by |q|² where yaw_from_quat assumes a unit
quaternion, so the two drift apart by O(|q|² - 1) (most near pitch ±90°, where the rotated
axis is almost vertical). This is the spelling a ported policy that took its heading from the
rotated forward axis needs, to stay bit-exact.
Source code in eden/utils/geom.py
quat_apply_yaw
¶
Rotate vec (N, 3) by only the yaw component of the wxyz quat.
Composition transform_by_quat(vec, yaw_quat(quat)) — mirrors
isaac_math.quat_apply_yaw.
Source code in eden/utils/geom.py
quat_box_minus
¶
Box-minus (axis-angle difference) of two wxyz quaternions: log(q1 ∘ q2⁻¹).
Composition mirroring isaac_math.quat_box_minus (which uses the
conjugate; for the unit quaternions used here conjugate == inverse).
Returns an axis-angle vector (N, 3).
Source code in eden/utils/geom.py
quat_error_magnitude
¶
Geodesic angle (radians) between two unit wxyz quaternions. Shape (...).
The magnitude of the relative rotation q1 ∘ q2⁻¹, computed as
2·atan2(‖vec‖, |w|). Equivalent to isaac_math's
‖axis_angle(q1 ∘ q2⁻¹)‖ (verified in tests) but uses atan2 directly,
so it is robust at the identity without a Taylor-series branch.
Source code in eden/utils/geom.py
random_quat_uniform
¶
Sample n quaternions uniformly on SO(3). Returns (n, 4) wxyz.
Shoemake's method: three uniform variates map to a rotation drawn from the Haar measure, so every orientation is equally likely (unlike normalising a Gaussian 4-vector by hand, which is also uniform but easy to get subtly wrong, or perturbing Euler angles, which is not).
genesis.utils.geom.random_quaternion implements the same method but is
numpy-and-CPU-only, so it is not a drop-in for a per-env sampler on device.
| PARAMETER | DESCRIPTION |
|---|---|
n
|
Number of quaternions to sample.
TYPE:
|
device
|
Device to allocate on. Defaults to None (torch's current default).
TYPE:
|
dtype
|
Floating dtype of the result. Defaults to
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Unit quaternions |
Source code in eden/utils/geom.py
wrap_to_pi
¶
Wrap angles (radians) to :math:[-\pi, \pi]. Same shape in/out.
Ported verbatim from isaac_math.wrap_to_pi; genesis has no equivalent. Accepts a
torch.Tensor, a numpy.ndarray or a Python float (returned as a float).
Source code in eden/utils/geom.py
combine_frame_transforms
¶
combine_frame_transforms(t01: Tensor, q01: Tensor, t12: Tensor | None = None, q12: Tensor | None = None) -> tuple[Tensor, Tensor]
Pose of frame 2 in frame 0: T_02 = T_01 · T_12 (wxyz), the inverse of :func:subtract_frame_transforms.
Mirrors isaac_math.combine_frame_transforms. With both operands given it is genesis
transform_pos_quat_by_trans_quat(t12, q12, t01, q01); kept as a named wrapper for isaac's
frame-order signature and the optional operands: t12 / q12 default to zero translation /
identity rotation. Returns (t02, q02) with shapes (N, 3) and (N, 4).
Source code in eden/utils/geom.py
subtract_frame_transforms
¶
subtract_frame_transforms(t01: Tensor, q01: Tensor, t02: Tensor | None = None, q02: Tensor | None = None) -> tuple[Tensor, Tensor]
Pose of frame 2 in frame 1: T_12 = T_01⁻¹ · T_02 (wxyz).
Pure composition of the quaternion ops above — mirrors
isaac_math.subtract_frame_transforms exactly. t02 / q02 default
to zero translation / identity rotation. Returns (t12, q12) with shapes
(N, 3) and (N, 4).
Source code in eden/utils/geom.py
transform_points
¶
Transform points into a target frame: p' = R · p + t (wxyz quat).
Ported verbatim (torch) from isaac_math.transform_points. points is
(N, P, 3) or (P, 3); pos is (N, 3)/(3,) (default: no
translation); quat is wxyz (N, 4)/(4,) (default: identity). genesis
has no batched point-cloud transform with this broadcasting contract.
Source code in eden/utils/geom.py
Symmetry¶
Mirroring across the robot's XZ plane, and SymmetryAugmentation for flat observation and action tensors.
Bilateral (XZ-plane) symmetry primitives and a flat-tensor augmentation helper.
Eden has two consumers of XZ-plane symmetry:
-
SymmetricAugmentationMixin(eden/utils/motion_reference/augmentation_mixin.py) mirrors per-env fields ofMotionReferenceData/MotionReferenceState— used byMotionManagerso the policy sees both an original and a mirrored reference trajectory during motion-imitation training. -
SymmetryAugmentation(this file) mirrors flat concatenated policy observations and actions — intended for RL training loops that double the batch by appending the mirrored copy (PPO/SAC symmetry augmentation).
The two operate on different data structures and have different lifecycle semantics, so they remain distinct classes. The underlying tensor math — joint-vector mirror, angular-velocity XZ negation, quaternion XZ negation, y-axis negation — lives here as module-level free functions that both consumers call. This keeps the math single-sourced.
Conventions: - Quaternions are wxyz (Eden convention; see AGENTS.md). - "XZ-plane mirror" = reflection across the body's sagittal plane. - Angular velocity under XZ mirror: negate x and z components. - Quaternion under XZ mirror (wxyz): negate the x and z components (indices 1 and 3). - Linear velocity / position under XZ mirror: negate the y component.
Usage::
from eden.utils.symmetry import SymmetryAugmentation
sym = SymmetryAugmentation(
dofs_name=robot_options.dofs_name,
symmetry_joint_names={"left_hip_pitch_joint": "right_hip_pitch_joint", ...},
flip_sign_joint_names=["left_hip_roll_joint", "right_hip_roll_joint", ...],
obs_components={
"base_lin_vel": 3,
"base_ang_vel": 3,
"projected_gravity": 3,
"dofs_pos": 29,
"dofs_vel": 29,
"last_action": 29,
"sin_phase": 2,
"cos_phase": 2,
"command_lin_vel": 2,
"command_ang_vel": 1,
},
)
aug_obs = sym.augment_observations(obs_batch) # (2*B, obs_dim)
aug_act = sym.augment_actions(action_batch) # (2*B, act_dim)
Classes¶
SymmetryAugmentation
¶
SymmetryAugmentation(dofs_name: Sequence[str], symmetry_joint_names: dict[str, str], flip_sign_joint_names: Sequence[str], obs_components: dict[str, int], device: device | str = 'cpu')
XZ-plane symmetry augmentation for bilateral robots.
Operates on flat concatenated policy tensors. For motion-reference-data
mirroring, see SymmetricAugmentationMixin in
eden/utils/motion_reference/augmentation_mixin.py.
| PARAMETER | DESCRIPTION |
|---|---|
dofs_name
|
Ordered DOF names matching the action/observation DOF ordering. |
symmetry_joint_names
|
Mapping from each joint name to its bilateral counterpart. Both directions should be specified (left->right AND right->left). |
flip_sign_joint_names
|
Joint names whose values get negated under the mirror transform (e.g., roll and yaw joints). |
obs_components
|
Ordered mapping of observation component name to its dimension. The concatenation order must match the actual observation tensor. |
device
|
Device of the index map and sign mask; must match the tensors being mirrored.
TYPE:
|
Source code in eden/utils/symmetry.py
Attributes¶
joint_index_map
instance-attribute
¶
sign_flip_mask
instance-attribute
¶
sign_flip_mask = torch.tensor([-1.0 if i in flip_indices else 1.0 for i in range(self.n_dofs)], device=device, dtype=torch.float32)
Methods:¶
mirror_actions
¶
mirror_observations
¶
Mirror a concatenated observation tensor. Shape (B, obs_dim).
Each named component is dispatched to the appropriate mirror function.
Source code in eden/utils/symmetry.py
augment_observations
¶
Concatenate original and mirrored observations. Returns (2*B, obs_dim).
augment_actions
¶
Concatenate original and mirrored actions. Returns (2*B, n_dofs).
Functions:¶
mirror_xz_joint
¶
Mirror a joint-indexed tensor along the XZ plane (reorder + sign flip on last dim).
Returns a new tensor; does not modify the input. sign_mask is reshaped to
broadcast over arbitrary leading dims.
| PARAMETER | DESCRIPTION |
|---|---|
joint_buf
|
Tensor with last dimension equal to
TYPE:
|
joint_mapping
|
Index map that swaps each joint with its bilateral counterpart, shape
TYPE:
|
sign_mask
|
Per-joint sign multipliers (
TYPE:
|
Source code in eden/utils/symmetry.py
mirror_xz_ang_vel_
¶
In-place XZ-plane mirror of an angular-velocity tensor.
Last dim is (wx, wy, wz). XZ-plane reflection negates rotations about
x and z; rotation about y is preserved. Returns the same tensor.
Source code in eden/utils/symmetry.py
mirror_xz_quat_
¶
In-place XZ-plane mirror of a quaternion tensor (wxyz convention).
Negates components at indices 1 and 3 (i.e. quat.x and quat.z). Returns the same tensor.
Source code in eden/utils/symmetry.py
mirror_xz_y_negate_
¶
mirror_xz_y_negate_(buf: Tensor, axis: int = -1) -> Tensor
In-place XZ-plane mirror of a position / linear-velocity tensor.
Negates the y component (index 1) along axis. Returns the same tensor.
Source code in eden/utils/symmetry.py
Sampling and seeding¶
Sampling helpers (e.g. per-env probability gating).
Functions:¶
sample_uniform
¶
sample_uniform(lower: Tensor | float, upper: Tensor | float, size: int | tuple[int, ...], device: str) -> Tensor
Sample uniformly within a range.
| PARAMETER | DESCRIPTION |
|---|---|
lower
|
Lower bound of the uniform range.
TYPE:
|
upper
|
Upper bound of the uniform range.
TYPE:
|
size
|
Desired tensor shape. |
device
|
Device to create the tensor on.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Tensor of uniformly sampled values with shape |
Source code in eden/utils/sample.py
sample_uniform_ranges
¶
sample_uniform_ranges(ranges: Sequence[Sequence[float]], num_samples: int, device: str | device) -> Tensor
Sample (num_samples, len(ranges)) values, column i uniform in ranges[i] = (low, high).
The same draw as sample_uniform(torch.tensor(lows), torch.tensor(highs), (num_samples, len(ranges))),
but the bound tensors are built once per distinct ranges and reused: building them from host
floats on every call is a blocking host->device copy, and a reset event or a command resample makes
that call every step the auto-reset fires. Ranges a curriculum rewrites at runtime just key
a new entry (:func:eden.utils.torch.device_constant).
| PARAMETER | DESCRIPTION |
|---|---|
ranges
|
One |
num_samples
|
Number of rows to draw (usually
TYPE:
|
device
|
Device to create the samples on.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Samples of shape |
Source code in eden/utils/sample.py
apply_probability
¶
apply_probability(mask: Tensor, probability: float) -> Tensor
Optionally gate a per-env boolean mask by a global Bernoulli draw.
A single sample is drawn per call (not per env). probability=1.0 is a no-op,
probability=0.0 zeroes the mask. Used by probabilistic-soft-limit termination
terms — gating is coarse on purpose, so they behave as a stochastic safety net
rather than a hard cap. Mirrors the convention of holosoma's locomotion termination terms.
The intermediate path uses a 0-dim bool tensor and a tensor-level &
instead of a Python if on sample.item(), so the function stays
async on GPU (no device→host sync per call).
Raises ValueError if probability is NaN or outside [0, 1] — silently
zeroing every termination on a misconfiguration would mask the bug.
Source code in eden/utils/sample.py
Random-seed utilities.
Attributes¶
Functions:¶
set_random_seed
¶
Seed Python's random, numpy and torch (every device), and set PYTHONHASHSEED.
PYTHONHASHSEED only reaches subprocesses started afterwards; this interpreter's hash seed is fixed at
startup. torch_deterministic turns on torch.use_deterministic_algorithms(True, warn_only=True).
Source code in eden/utils/random.py
validate_seed
¶
Reject a negative seed, including a -1 meant as "draw a random seed".
Eden has no such sentinel, and it could not honour one: :func:eden.init seeds numpy
through gs.init(seed=LOCAL_RANK) before any config is read, so a draw would come off a
stream just seeded to a constant and return the same value on every run. Pass an
explicit seed (--seed $RANDOM in a sweep script) so the value is recorded rather than
invented.
| PARAMETER | DESCRIPTION |
|---|---|
seed
|
The configured seed.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
int
|
The same seed, as an |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Source code in eden/utils/random.py
apply_run_seed
¶
Apply a run's seed to every RNG — the single owner of run seeding.
Call it once, before the environment is constructed, so the build, its first reset and
every domain-randomization draw come from the seeded RNGs. The training wrappers'
from_config do exactly that, and rsl-rl-lib never applies runner_options.seed
itself.
Under DDP each rank offsets the configured base by its global rank, so ranks sample distinct env randomization and action noise instead of duplicating rank 0's rollout.
seed is the run's base, and stays the base: the applied value is returned and
logged, never written back into the config. A config field that is both the input and
the output of this function would compound the offset every time it ran — rank r
getting base + 2r on the second call, invisibly, since rank 0 is unaffected. Keeping
it read-only also keeps one meaning for seed everywhere: config.json and
run.json record the base, and run.json's world_size pins the rest
(rank r used seed + r), so the base plus the topology reproduces every rank.
| PARAMETER | DESCRIPTION |
|---|---|
seed
|
The run's configured base seed. Must be non-negative.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
int
|
The seed applied on this rank ( |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Source code in eden/utils/random.py
Torch helpers¶
Backend configuration (TF32, determinism), model compilation, checkpoint inspection, device resolution and numpy/torch conversion.
Torch helpers: backend configuration, model compilation, checkpoint inspection, config values, numpy<->torch.
Attributes¶
DeviceStr
module-attribute
¶
Functions:¶
resolve_device
¶
Resolve the sentinel "auto" to the active Genesis device.
Allows runner config defaults (e.g. RslRlBaseRunnerOptions.device) to
avoid hardcoding "cuda:0" — which crashes on CPU-only Macs and
Windows boxes without CUDA. Any non-"auto" string is returned
unchanged so explicit user choices still win.
Source code in eden/utils/torch.py
configure_torch_backends
¶
Configure PyTorch CUDA and cuDNN backends for performance/reproducibility.
| PARAMETER | DESCRIPTION |
|---|---|
allow_tf32
|
If True, use TF32 precision for faster computation on Ampere+ GPUs. If False, use standard IEEE FP32 precision.
TYPE:
|
deterministic
|
Currently ignored here: :func:
TYPE:
|
Notes
TF32 uses reduced precision (10-bit mantissa vs 23-bit for FP32) for internal matrix multiplications providing a speedup with minimal impact on accuracy.
See https://pytorch.org/docs/stable/notes/cuda.html#tf32-on-ampere for details.
Source code in eden/utils/torch.py
compile_model
¶
Safely compile a model or function if torch.compile is available.
Worth it only for a function whose eager cost exceeds what a compiled call costs by itself: ~25 us of
Dynamo guard evaluation and CUDA-graph replay per call (L40S / RTX 3080, measured at 4096 envs).
A one- to five-op reward kernel runs in 6-22 us eager and in ~25 us compiled, which is why the generic
reward helpers in eden.managers.terms.rewards are not compiled; a fused helper with a dozen ops (the
getup / handstand terms, 42 -> 31 us) is.
| PARAMETER | DESCRIPTION |
|---|---|
model
|
The
TYPE:
|
mode
|
The compilation mode.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
T
|
The compiled model/function, or the original if compilation fails/is unavailable. A
|
Source code in eden/utils/torch.py
numpy_to_tensor
¶
numpy_to_tensor(array: Any, *, dtype: dtype | None = None, device: device | str | None = None) -> Tensor
A tensor of a numpy array (or a tensor), sharing its memory where it can.
A tensor passes through. An array goes through np.ascontiguousarray first, because
torch.from_numpy refuses negative strides (a flipped image, x[::-1]). Then .to(dtype,
device) when either is given, which copies only when it converts: a contiguous array already in
dtype on the CPU stays shared with the tensor.
Source code in eden/utils/torch.py
to_numpy
¶
A numpy array of value: a tensor is detached and moved to the host, anything else goes via np.asarray.
dtype converts (np.asarray(..., dtype=dtype)), copying only when the dtype differs — so a CPU
tensor already in dtype comes back as a view of its storage. Copy the result if it must outlive
writes to the tensor.
Source code in eden/utils/torch.py
device_constant
¶
device_constant(values, device: device | str, dtype: dtype = float32) -> Tensor
torch.tensor(values, dtype=dtype, device=device), built once per distinct values and reused.
For constants a function term builds from its (host) parameters on every call: each build is a
blocking host->device copy, a GPU->CPU sync on the step path. values is a number or a
(nested) sequence of numbers — whatever torch.tensor takes. The tensor is shared between
callers, so treat it as read-only: derive from it (.unsqueeze, arithmetic), never write into it.
Source code in eden/utils/torch.py
to_numpy_unbatched
¶
Move a per-env tensor to CPU numpy and drop the leading num_envs axis.
A (1, ...) input becomes (...); a 1-D input is returned unchanged. A leading
axis larger than one raises, since silently taking row 0 would hide a batching error
(single-env consumers such as remote policy servers).
Source code in eden/utils/torch.py
resolve_scalar_or_vector
¶
resolve_scalar_or_vector(value: float | list[float] | tuple[float, ...], expected_dim: int, device: device | str, *, what: str, dtype: dtype = float32) -> float | Tensor
Resolve a scalar or fixed-length vector into its runtime form at build() time.
A scalar stays a Python float. A sequence is length-checked against expected_dim and becomes
a 1-D tensor on device.
| PARAMETER | DESCRIPTION |
|---|---|
value
|
The configured value. |
expected_dim
|
Required vector length.
TYPE:
|
device
|
Device for the resolved tensor.
TYPE:
|
what
|
Subject of the error message, naming the offending term (e.g.
TYPE:
|
dtype
|
Dtype of the resolved tensor; defaults to
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
float or Tensor
|
The scalar itself, or a 1-D tensor of length |
| RAISES | DESCRIPTION |
|---|---|
TypeError
|
If |
ValueError
|
If |
Source code in eden/utils/torch.py
read_checkpoint_index
¶
The checkpoint's top-level mapping, with tensor data left on disk.
For callers that read key names only — the rsl_rl legacy-format probe and key
audit in :mod:eden.envs.wrappers.rsl_rl_env, the CLI's checkpoint framework
detection — a full torch.load deserializes every weight just to throw it
away. torch.load(mmap=True) maps the storages lazily instead, so an
inspection costs a directory read rather than a copy of the weights, and the
result can be dropped without ever having held them in RAM. The mapping's
tensor values are still usable (paged in on first touch) — but a caller that
needs values eagerly, e.g. to rewrite them into a new file, should do its own
full torch.load so its output never depends on a live mapping of the input.
| PARAMETER | DESCRIPTION |
|---|---|
path
|
Path to the checkpoint file.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
dict | None
|
The mapping, or |
Source code in eden/utils/torch.py
See also¶
- Entities guide — pose frames
- Robotics utilities