Alopezcordero's picture
Upload 11 files
5cd3814 verified
Raw
History Blame Contribute Delete
3.44 kB
"""Shared, heading-invariant AMP feature construction.
WHY THIS FILE EXISTS (the main bug in the original code):
The discriminator was fed `concat[qpos[2:], qvel]`, which contains the root
quaternion in the WORLD frame and the root linear velocity in the WORLD frame.
Your task randomizes the walking direction every episode, but the mocap clips
walk in one fixed world direction. So the discriminator learns to separate
real/fake by *global heading*, not by gait quality -> the style reward
actively punishes the policy whenever it follows a target direction that
differs from the dataset's heading. Task reward and style reward fight each
other and neither wins.
Fix: express all root quantities in a local "heading frame" (yaw removed),
exactly like the AMP paper. Both the policy transitions (amp_env.py) and the
mocap transitions (motion_lib.py) MUST use this same function.
Feature layout (45 dims -> transition pair is 90 = discriminator input_dim):
root height 1
root orientation, yaw removed (quat, w>=0) 4
root linear velocity in heading frame 3
root angular velocity (MuJoCo body frame) 3
joint angles 17
joint velocities 17
"""
import numpy as np
AMP_OBS_DIM = 45
AMP_TRANSITION_DIM = 2 * AMP_OBS_DIM
def quat_mul(a, b):
"""Hamilton product, MuJoCo [w, x, y, z] convention."""
w1, x1, y1, z1 = a
w2, x2, y2, z2 = b
return np.array([
w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2,
w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2,
w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2,
w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2,
])
def yaw_from_quat(q):
"""ZYX-convention yaw (heading) of a [w, x, y, z] quaternion."""
w, x, y, z = q
return np.arctan2(2.0 * (w * z + x * y), 1.0 - 2.0 * (y * y + z * z))
def build_amp_obs(qpos, qvel):
"""Humanoid-v5 layout:
qpos = [x, y, z, qw, qx, qy, qz, 17 joint angles] (24,)
qvel = [vx, vy, vz (world), wx, wy, wz (body), 17 vels] (23,)
Returns float32 (45,) heading- and position-invariant features.
"""
quat = np.asarray(qpos[3:7], dtype=np.float64)
yaw = yaw_from_quat(quat)
c, s = np.cos(yaw), np.sin(yaw)
# Rotates a world-frame vector into the heading frame (inverse yaw).
R_inv = np.array([[c, s, 0.0],
[-s, c, 0.0],
[0.0, 0.0, 1.0]])
# Remove yaw from the root orientation: q_local = q_yaw^-1 (x) q
half = -0.5 * yaw
q_yaw_inv = np.array([np.cos(half), 0.0, 0.0, np.sin(half)])
quat_local = quat_mul(q_yaw_inv, quat)
if quat_local[0] < 0.0:
quat_local = -quat_local # canonical sign (quats double-cover)
lin_vel_local = R_inv @ np.asarray(qvel[0:3], dtype=np.float64)
# MuJoCo free-joint angular velocity (qvel[3:6]) is already expressed in
# the body-local frame, i.e. heading-invariant. Left untouched. Either
# way, expert and policy use the identical convention/transform.
ang_vel = qvel[3:6]
return np.concatenate([
qpos[2:3], # root height
quat_local, # roll/pitch information only
lin_vel_local, # forward/lateral/vertical speed relative to facing
ang_vel,
qpos[7:], # joint angles (already local)
qvel[6:], # joint velocities (already local)
]).astype(np.float32)