@@ -424,6 +424,21 @@ def _compute_jacobian_root_frame(robot, ee_jacobi_idx, arm_joint_ids):
424424 return jacobian
425425
426426
427+ def _compute_ee_vel_root (jacobian_b , joint_vel ):
428+ """Return the EE 6D velocity in the root frame as ``J · q_dot``.
429+
430+ Required to make OSC's ``kd * ee_vel_b`` damping term meaningful.
431+ Passing zero EE velocity (the convenient hack) leaves the impedance
432+ undamped and the EE oscillates around the target. We use ``J · q_dot``
433+ rather than reading ``data.body_vel_w`` because Newton's lazy
434+ velocity buffers can return stale/zero values until forced
435+ materialization, while ``joint_vel`` and ``J`` are already pulled
436+ by the loop. ``J`` correctness is pinned independently by
437+ ``test_get_jacobians_link_origin_contract``.
438+ """
439+ return torch .bmm (jacobian_b , joint_vel .unsqueeze (- 1 )).squeeze (- 1 )
440+
441+
427442def _build_relative_pose_target (robot , ee_frame_idx , delta_xyz , device ):
428443 """Build a target pose = (current EE pose) + ``delta_xyz``, preserving orientation."""
429444 initial_ee_pos_b , initial_ee_quat_b , _ = _compute_ee_pose_root (robot , ee_frame_idx )
@@ -3049,15 +3064,17 @@ def test_franka_ik_tracking_accuracy(sim, device, articulation_type, gravity_ena
30493064 # Print metrics every run for stress-test capture.
30503065 print (f"IK_METRIC pos_min={ pos_min :.5f} pos_mean={ pos_mean :.5f} rot_min={ rot_min :.5f} rot_mean={ rot_mean :.5f} " )
30513066
3052- # Regression sentinel: 5 mm best-of-tail. With the configured home
3053- # pose and scene gravity off, Newton converges to machine precision
3054- # (sub-mm) on this 5 cm Cartesian step. The 5 mm bound absorbs any
3055- # CUDA-kernel-ordering noise while remaining well below the "totally
3056- # broken" regime: a bridge regression (wrong-frame Jacobian, missing
3057- # COM->origin shift, DoF mis-ordering) would push the steady-state
3058- # error well past this bound.
3059- assert pos_min < 5e-3 , f"IK pos_min { pos_min :.5f} > 5 mm — bridge regression?"
3060- assert rot_min < 5e-2 , f"IK rot_min { rot_min :.5f} > 0.05 rad — bridge regression?"
3067+ # Regression sentinel: assert on tail mean rather than min. Tail
3068+ # min is the bottom of any oscillation envelope and can be tiny
3069+ # while the actual tracking error is much larger. With the
3070+ # configured home pose and scene gravity off, Newton converges to
3071+ # machine precision (sub-mm). The 5 mm bound absorbs any CUDA-
3072+ # kernel-ordering noise while remaining well below the "totally
3073+ # broken" regime: a bridge regression (wrong-frame Jacobian,
3074+ # missing COM->origin shift, DoF mis-ordering) would push the
3075+ # steady-state error well past this bound.
3076+ assert pos_mean < 5e-3 , f"IK pos_mean { pos_mean :.5f} > 5 mm — bridge regression?"
3077+ assert rot_mean < 5e-2 , f"IK rot_mean { rot_mean :.5f} > 0.05 rad — bridge regression?"
30613078
30623079
30633080@pytest .mark .parametrize ("device" , ["cuda:0" ])
@@ -3104,12 +3121,13 @@ def test_franka_osc_tracking_accuracy(sim, device, articulation_type, gravity_en
31043121
31053122 pos_history : list [float ] = []
31063123 rot_history : list [float ] = []
3107- ee_vel_b = torch .zeros (1 , 6 , device = device ) # OSC needs vel; use zero (works at low speed).
31083124 for _ in range (800 ):
31093125 jacobian_b = _compute_jacobian_root_frame (robot , ee_jacobi_idx , arm_joint_ids )
31103126 mass_matrix = wp .to_torch (robot .get_mass_matrix ())[:, arm_joint_ids , :][:, :, arm_joint_ids ]
31113127 ee_pos_b , ee_quat_b , _ = _compute_ee_pose_root (robot , ee_frame_idx )
31123128 ee_pose_b = torch .cat ([ee_pos_b , ee_quat_b ], dim = - 1 )
3129+ joint_vel = robot .data .joint_vel .torch [:, arm_joint_ids ]
3130+ ee_vel_b = _compute_ee_vel_root (jacobian_b , joint_vel )
31133131
31143132 osc .set_command (target_pose_b , current_ee_pose_b = ee_pose_b )
31153133 joint_efforts = osc .compute (
@@ -3134,18 +3152,16 @@ def test_franka_osc_tracking_accuracy(sim, device, articulation_type, gravity_en
31343152
31353153 print (f"OSC_METRIC pos_min={ pos_min :.5f} pos_mean={ pos_mean :.5f} rot_min={ rot_min :.5f} rot_mean={ rot_mean :.5f} " )
31363154
3137- # Regression sentinel: 2 cm best-of-tail (matches the PhysX-side
3138- # test). With the home pose start, zero actuator PD, and no
3139- # gravity, both backends settle into a sustained sub-cm oscillation
3140- # around the target driven by the critically damped impedance
3141- # against ``current_ee_vel_b = 0``. Tighter bounds would require
3142- # non-zero ee-velocity feedback or a larger ``motion_stiffness_task``,
3143- # neither of which is part of the bridge-pinning contract. A bridge
3144- # regression (wrong J, wrong mass matrix, DoF mis-ordering) pushes
3145- # the error well past 2 cm because OSC consumes both
3146- # ``get_jacobians`` and ``get_mass_matrix`` per step.
3147- assert pos_min < 2e-2 , f"OSC pos_min { pos_min :.5f} > 2 cm — bridge regression?"
3148- assert rot_min < 2e-1 , f"OSC rot_min { rot_min :.5f} > 0.2 rad — bridge regression?"
3155+ # Regression sentinel: assert on tail mean rather than min. With
3156+ # ``current_ee_vel_b = J · q_dot`` providing OSC's damping term and
3157+ # the actuator PD zeroed, the impedance settles to machine
3158+ # precision -- same ballpark as the IK test. The 5 mm bound is a
3159+ # bridge regression sentinel: a wrong J, wrong mass matrix, or
3160+ # DoF mis-ordering pushes the steady-state error well past it
3161+ # because OSC consumes both ``get_jacobians`` and
3162+ # ``get_mass_matrix`` per step.
3163+ assert pos_mean < 5e-3 , f"OSC pos_mean { pos_mean :.5f} > 5 mm — bridge regression?"
3164+ assert rot_mean < 5e-2 , f"OSC rot_mean { rot_mean :.5f} > 0.05 rad — bridge regression?"
31493165
31503166
31513167if __name__ == "__main__" :
0 commit comments