Add tendon armature with sparse qM to MJX. Fixes #2681.

PiperOrigin-RevId: 772583693
Change-Id: I827d61aacada33f53500bf54613ccb8145bc854b
This commit is contained in:
Taylor Howell
2025-06-17 12:50:56 -07:00
committed by Copybara-Service
parent d2501eff68
commit 8084b06a35
2 changed files with 32 additions and 28 deletions
+23 -23
View File
@@ -1194,19 +1194,23 @@ def tendon_armature(m: Model, d: Data) -> Data:
if not m.ntendon: if not m.ntendon:
return d return d
if not support.is_sparse(m): # TODO(taylorhowell): if sparse, compute sparse JTAJ
return d.tree_replace({ JTAJ = d._impl.ten_J.T @ jax.vmap(jp.multiply)(
'_impl.qM': ( d._impl.ten_J, m.tendon_armature
d._impl.qM )
+ d._impl.ten_J.T
@ jax.vmap(jp.multiply)(d._impl.ten_J, m.tendon_armature) if support.is_sparse(m):
) ij = []
}) for i in range(m.nv):
else: j = i
# TODO(taylorhowell): implement tendon armature with sparse qM while j > -1:
raise NotImplementedError( ij.append((i, j))
'Tendon armature with sparse qM is not implemented.' j = m.dof_parentid[j]
)
i, j = (jp.array(x) for x in zip(*ij))
JTAJ = JTAJ[(i, j)]
return d.tree_replace({'_impl.qM': d._impl.qM + JTAJ})
def tendon_dot(m: Model, d: Data) -> jax.Array: def tendon_dot(m: Model, d: Data) -> jax.Array:
@@ -1329,13 +1333,9 @@ def tendon_bias(m: Model, d: Data) -> Data:
# add bias term: qfrc += ten_J * armature * ten_Jdot @ qvel # add bias term: qfrc += ten_J * armature * ten_Jdot @ qvel
coef = m.tendon_armature * jp.dot(ten_Jdot, d.qvel) coef = m.tendon_armature * jp.dot(ten_Jdot, d.qvel)
if not support.is_sparse(m): return d.tree_replace({
return d.tree_replace({ 'qfrc_bias': (
'qfrc_bias': ( d.qfrc_bias
d.qfrc_bias + jp.sum(jax.vmap(jp.multiply)(d._impl.ten_J, coef), axis=0)
+ jp.sum(jax.vmap(jp.multiply)(d._impl.ten_J, coef), axis=0) )
) })
})
else:
# TODO(taylorhowell): implement tendon bias with sparse qM
raise NotImplementedError('Tendon bias with sparse qM is not implemented.')
+9 -5
View File
@@ -22,6 +22,7 @@ import mujoco
from mujoco import mjx from mujoco import mjx
from mujoco.mjx._src import test_util from mujoco.mjx._src import test_util
from mujoco.mjx._src.types import ConeType # pylint: disable=g-importing-member from mujoco.mjx._src.types import ConeType # pylint: disable=g-importing-member
from mujoco.mjx._src.types import JacobianType # pylint: disable=g-importing-member
import numpy as np import numpy as np
# tolerance for difference between MuJoCo and MJX smooth calculations - mostly # tolerance for difference between MuJoCo and MJX smooth calculations - mostly
@@ -317,7 +318,8 @@ class TendonTest(parameterized.TestCase):
_assert_eq(d.wrap_obj, dx._impl.wrap_obj, 'wrap_obj') _assert_eq(d.wrap_obj, dx._impl.wrap_obj, 'wrap_obj')
_assert_eq(d.wrap_xpos, dx._impl.wrap_xpos, 'wrap_xpos') _assert_eq(d.wrap_xpos, dx._impl.wrap_xpos, 'wrap_xpos')
def test_tendon_armature(self): @parameterized.parameters(JacobianType.DENSE, JacobianType.SPARSE)
def test_tendon_armature(self, jacobian):
"""Tests MJX tendon armature matches MuJoCo.""" """Tests MJX tendon armature matches MuJoCo."""
m = mujoco.MjModel.from_xml_string(""" m = mujoco.MjModel.from_xml_string("""
<mujoco> <mujoco>
@@ -345,14 +347,11 @@ class TendonTest(parameterized.TestCase):
</keyframe> </keyframe>
</mujoco> </mujoco>
""") """)
m.opt.jacobian = jacobian
d = mujoco.MjData(m) d = mujoco.MjData(m)
mujoco.mj_resetDataKeyframe(m, d, 0) mujoco.mj_resetDataKeyframe(m, d, 0)
mujoco.mj_forward(m, d) mujoco.mj_forward(m, d)
qM = np.zeros((m.nv, m.nv)) # pylint: disable=invalid-name
mujoco.mj_fullM(m, qM, d.qM)
mx = mjx.put_model(m) mx = mjx.put_model(m)
dx = mjx.put_data(m, d) dx = mjx.put_data(m, d)
@@ -363,6 +362,11 @@ class TendonTest(parameterized.TestCase):
dx = mjx.crb(mx, dx) dx = mjx.crb(mx, dx)
dx = mjx.tendon_armature(mx, dx) dx = mjx.tendon_armature(mx, dx)
if jacobian == JacobianType.DENSE:
qM = np.zeros((m.nv, m.nv)) # pylint: disable=invalid-name
mujoco.mj_fullM(m, qM, d.qM)
else:
qM = d.qM # pylint: disable=invalid-name
_assert_eq(dx._impl.qM, qM, 'qM') _assert_eq(dx._impl.qM, qM, 'qM')
dx = mjx.rne(mx, dx) dx = mjx.rne(mx, dx)