Update MJX rne_postconstraint with contributions from equality connect and equality weld constraints.
PiperOrigin-RevId: 777491801 Change-Id: Ib32dda4178def7a96d31e91787f3d0866a56cd76
This commit is contained in:
committed by
Copybara-Service
parent
57b3d6d0f4
commit
2ce5caa239
@@ -248,23 +248,6 @@ def _put_model_jax(
|
|||||||
if t == mujoco.mjtGeom.mjGEOM_MESH:
|
if t == mujoco.mjtGeom.mjGEOM_MESH:
|
||||||
mesh_geomid.add(g)
|
mesh_geomid.add(g)
|
||||||
|
|
||||||
# check for unsupported sensor and equality constraint combinations
|
|
||||||
sensor_rne_postconstraint = (
|
|
||||||
np.any(m.sensor_type == types.SensorType.ACCELEROMETER)
|
|
||||||
| np.any(m.sensor_type == types.SensorType.FORCE)
|
|
||||||
| np.any(m.sensor_type == types.SensorType.TORQUE)
|
|
||||||
| np.any(m.sensor_type == types.SensorType.FRAMELINACC)
|
|
||||||
| np.any(m.sensor_type == types.SensorType.FRAMEANGACC)
|
|
||||||
)
|
|
||||||
eq_connect_weld = np.any(m.eq_type == types.EqType.CONNECT) | np.any(
|
|
||||||
m.eq_type == types.EqType.WELD
|
|
||||||
)
|
|
||||||
if sensor_rne_postconstraint and eq_connect_weld:
|
|
||||||
raise NotImplementedError(
|
|
||||||
'rne_postconstraint not implemented with equality constraints:'
|
|
||||||
' connect, weld.'
|
|
||||||
)
|
|
||||||
|
|
||||||
for enum_field, enum_type, mj_type in (
|
for enum_field, enum_type, mj_type in (
|
||||||
(m.actuator_biastype, types.BiasType, mujoco.mjtBias),
|
(m.actuator_biastype, types.BiasType, mujoco.mjtBias),
|
||||||
(m.actuator_dyntype, types.DynType, mujoco.mjtDyn),
|
(m.actuator_dyntype, types.DynType, mujoco.mjtDyn),
|
||||||
|
|||||||
@@ -625,38 +625,6 @@ class DataIOTest(parameterized.TestCase):
|
|||||||
with self.assertRaises(NotImplementedError):
|
with self.assertRaises(NotImplementedError):
|
||||||
mjx.make_data(m)
|
mjx.make_data(m)
|
||||||
|
|
||||||
@parameterized.product(
|
|
||||||
sensor=['accelerometer', 'force', 'torque'], equality=['connect', 'weld']
|
|
||||||
)
|
|
||||||
def test_sensor_constraint_compatibility(self, sensor, equality):
|
|
||||||
"""Test unsupported sensor and equality constraint combinations."""
|
|
||||||
equality_constraint = f'{equality} body1="body1" body2="body2"'
|
|
||||||
if equality == 'connect':
|
|
||||||
equality_constraint += ' anchor="0 0 0"'
|
|
||||||
m = mujoco.MjModel.from_xml_string(f"""
|
|
||||||
<mujoco>
|
|
||||||
<worldbody>
|
|
||||||
<body name="body1">
|
|
||||||
<freejoint/>
|
|
||||||
<geom size="0.1"/>
|
|
||||||
<site name="site1"/>
|
|
||||||
</body>
|
|
||||||
<body name="body2">
|
|
||||||
<freejoint/>
|
|
||||||
<geom size="0.1"/>
|
|
||||||
</body>
|
|
||||||
</worldbody>
|
|
||||||
<equality>
|
|
||||||
<{equality_constraint}/>
|
|
||||||
</equality>
|
|
||||||
<sensor>
|
|
||||||
<{sensor} site="site1"/>
|
|
||||||
</sensor>
|
|
||||||
</mujoco>
|
|
||||||
""")
|
|
||||||
with self.assertRaises(NotImplementedError):
|
|
||||||
mjx.put_model(m, impl='jax')
|
|
||||||
|
|
||||||
@parameterized.parameters(JacobianType.DENSE, JacobianType.SPARSE)
|
@parameterized.parameters(JacobianType.DENSE, JacobianType.SPARSE)
|
||||||
def test_qm_mapm2m(self, jacobian):
|
def test_qm_mapm2m(self, jacobian):
|
||||||
"""Test that qM is mapped to M."""
|
"""Test that qM is mapped to M."""
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from mujoco.mjx._src.types import EqType
|
|||||||
from mujoco.mjx._src.types import JointType
|
from mujoco.mjx._src.types import JointType
|
||||||
from mujoco.mjx._src.types import Model
|
from mujoco.mjx._src.types import Model
|
||||||
from mujoco.mjx._src.types import ModelJAX
|
from mujoco.mjx._src.types import ModelJAX
|
||||||
|
from mujoco.mjx._src.types import ObjType
|
||||||
from mujoco.mjx._src.types import TrnType
|
from mujoco.mjx._src.types import TrnType
|
||||||
from mujoco.mjx._src.types import WrapType
|
from mujoco.mjx._src.types import WrapType
|
||||||
# pylint: enable=g-importing-member
|
# pylint: enable=g-importing-member
|
||||||
@@ -659,11 +660,126 @@ def rne_postconstraint(m: Model, d: Data) -> Data:
|
|||||||
cfrc_contact.reshape((-1, 6))
|
cfrc_contact.reshape((-1, 6))
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO(taylorhowell): connect and weld constraints
|
# cfrc_ext += connect, weld
|
||||||
if np.any(m.eq_type == EqType.CONNECT):
|
cfrc_ext_equality = []
|
||||||
raise NotImplementedError('Connect constraints are not implemented.')
|
cfrc_ext_equality_adr = []
|
||||||
if np.any(m.eq_type == EqType.WELD):
|
|
||||||
raise NotImplementedError('Weld constraints are not implemented.')
|
connect_id = m.eq_type == EqType.CONNECT
|
||||||
|
nconnect = connect_id.sum()
|
||||||
|
|
||||||
|
if nconnect:
|
||||||
|
cfrc_connect_force = d._impl.efc_force[: 3 * nconnect].reshape(
|
||||||
|
(nconnect, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
is_site = m.eq_objtype == ObjType.SITE
|
||||||
|
body1id = np.copy(m.eq_obj1id)
|
||||||
|
body2id = np.copy(m.eq_obj2id)
|
||||||
|
pos1 = m.eq_data[:, :3]
|
||||||
|
pos2 = m.eq_data[:, 3:6]
|
||||||
|
|
||||||
|
if m.nsite:
|
||||||
|
body1id[is_site] = m.site_bodyid[m.eq_obj1id[is_site]]
|
||||||
|
body2id[is_site] = m.site_bodyid[m.eq_obj2id[is_site]]
|
||||||
|
pos1 = jp.where(is_site[:, None], m.site_pos[m.eq_obj1id], pos1)
|
||||||
|
pos2 = jp.where(is_site[:, None], m.site_pos[m.eq_obj2id], pos2)
|
||||||
|
|
||||||
|
# body 1
|
||||||
|
k1_connect = body1id[connect_id]
|
||||||
|
k1_connect_mask = k1_connect != 0
|
||||||
|
offset1_connect = pos1[connect_id]
|
||||||
|
|
||||||
|
pos1_connect = jax.vmap(lambda pnt, mat, vec: mat @ pnt + vec)(
|
||||||
|
offset1_connect, d.xmat[k1_connect], d.xpos[k1_connect]
|
||||||
|
)
|
||||||
|
subtree_com1_connect = d.subtree_com[jp.array(m.body_rootid)[k1_connect]]
|
||||||
|
cfrc_com1_connect = jax.vmap(
|
||||||
|
lambda dif, frc, mask: mask * jp.concatenate([-jp.cross(dif, frc), frc])
|
||||||
|
)(subtree_com1_connect - pos1_connect, cfrc_connect_force, k1_connect_mask)
|
||||||
|
|
||||||
|
# body 2
|
||||||
|
k2_connect = body2id[connect_id]
|
||||||
|
k2_connect_mask = -1 * (k2_connect != 0)
|
||||||
|
offset2_connect = pos2[connect_id]
|
||||||
|
|
||||||
|
pos2_connect = jax.vmap(lambda pnt, mat, vec: mat @ pnt + vec)(
|
||||||
|
offset2_connect, d.xmat[k2_connect], d.xpos[k2_connect]
|
||||||
|
)
|
||||||
|
subtree_com2_connect = d.subtree_com[jp.array(m.body_rootid)[k2_connect]]
|
||||||
|
cfrc_com2_connect = jax.vmap(
|
||||||
|
lambda dif, frc, mask: mask * jp.concatenate([-jp.cross(dif, frc), frc])
|
||||||
|
)(subtree_com2_connect - pos2_connect, cfrc_connect_force, k2_connect_mask)
|
||||||
|
|
||||||
|
cfrc_ext_equality.append(jp.vstack([cfrc_com1_connect, cfrc_com2_connect]))
|
||||||
|
cfrc_ext_equality_adr.append(jp.concatenate([k1_connect, k2_connect]))
|
||||||
|
|
||||||
|
weld_id = m.eq_type == EqType.WELD
|
||||||
|
nweld = weld_id.sum()
|
||||||
|
|
||||||
|
if nweld:
|
||||||
|
cfrc_weld = d._impl.efc_force[
|
||||||
|
3 * nconnect : 3 * nconnect + 6 * nweld
|
||||||
|
].reshape((nweld, 6))
|
||||||
|
cfrc_weld_force = cfrc_weld[:, :3]
|
||||||
|
cfrc_weld_torque = cfrc_weld[:, 3:]
|
||||||
|
|
||||||
|
is_site = m.eq_objtype == ObjType.SITE
|
||||||
|
body1id = np.copy(m.eq_obj1id)
|
||||||
|
body2id = np.copy(m.eq_obj2id)
|
||||||
|
pos1 = m.eq_data[:, 3:6]
|
||||||
|
pos2 = m.eq_data[:, :3]
|
||||||
|
|
||||||
|
if m.nsite:
|
||||||
|
body1id[is_site] = m.site_bodyid[m.eq_obj1id[is_site]]
|
||||||
|
body2id[is_site] = m.site_bodyid[m.eq_obj2id[is_site]]
|
||||||
|
pos1 = jp.where(is_site[:, None], m.site_pos[m.eq_obj1id], pos1)
|
||||||
|
pos2 = jp.where(is_site[:, None], m.site_pos[m.eq_obj2id], pos2)
|
||||||
|
|
||||||
|
# body 1
|
||||||
|
k1_weld = body1id[weld_id]
|
||||||
|
k1_weld_mask = k1_weld != 0
|
||||||
|
offset1_weld = pos1[weld_id]
|
||||||
|
|
||||||
|
pos1_weld = jax.vmap(lambda pnt, mat, vec: mat @ pnt + vec)(
|
||||||
|
offset1_weld, d.xmat[k1_weld], d.xpos[k1_weld]
|
||||||
|
)
|
||||||
|
subtree_com1_weld = d.subtree_com[jp.array(m.body_rootid)[k1_weld]]
|
||||||
|
cfrc_com1_weld = jax.vmap(
|
||||||
|
lambda dif, frc, trq, mask: mask
|
||||||
|
* jp.concatenate([trq - jp.cross(dif, frc), frc])
|
||||||
|
)(
|
||||||
|
subtree_com1_weld - pos1_weld,
|
||||||
|
cfrc_weld_force,
|
||||||
|
cfrc_weld_torque,
|
||||||
|
k1_weld_mask,
|
||||||
|
)
|
||||||
|
|
||||||
|
# body 2
|
||||||
|
k2_weld = body2id[weld_id]
|
||||||
|
k2_weld_mask = -1 * (k2_weld != 0)
|
||||||
|
offset2_weld = pos2[weld_id]
|
||||||
|
|
||||||
|
pos2_weld = jax.vmap(lambda pnt, mat, vec: mat @ pnt + vec)(
|
||||||
|
offset2_weld, d.xmat[k2_weld], d.xpos[k2_weld]
|
||||||
|
)
|
||||||
|
subtree_com2_weld = d.subtree_com[jp.array(m.body_rootid)[k2_weld]]
|
||||||
|
cfrc_com2_weld = jax.vmap(
|
||||||
|
lambda dif, frc, trq, mask: mask
|
||||||
|
* jp.concatenate([trq - jp.cross(dif, frc), frc])
|
||||||
|
)(
|
||||||
|
subtree_com2_weld - pos2_weld,
|
||||||
|
cfrc_weld_force,
|
||||||
|
cfrc_weld_torque,
|
||||||
|
k2_weld_mask,
|
||||||
|
)
|
||||||
|
|
||||||
|
cfrc_ext_equality.append(jp.vstack([cfrc_com1_weld, cfrc_com2_weld]))
|
||||||
|
cfrc_ext_equality_adr.append(jp.concatenate([k1_weld, k2_weld]))
|
||||||
|
|
||||||
|
if nconnect or nweld:
|
||||||
|
cfrc_ext = cfrc_ext.at[jp.concatenate(cfrc_ext_equality_adr)].add(
|
||||||
|
jp.vstack(cfrc_ext_equality)
|
||||||
|
)
|
||||||
|
|
||||||
# forward pass over bodies: compute cacc, cfrc_int
|
# forward pass over bodies: compute cacc, cfrc_int
|
||||||
def _forward(carry, cfrc_ext, cinert, cvel, body_dofadr, body_dofnum):
|
def _forward(carry, cfrc_ext, cinert, cvel, body_dofadr, body_dofnum):
|
||||||
|
|||||||
@@ -230,21 +230,98 @@ class SmoothTest(absltest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class RnePostConstraintTest(parameterized.TestCase):
|
class RnePostConstraintTest(parameterized.TestCase):
|
||||||
|
_CONNECT_SITE = """
|
||||||
|
<equality>
|
||||||
|
<connect site1="site1" site2="site2"/>
|
||||||
|
</equality>
|
||||||
|
"""
|
||||||
|
_CONNECT_BODY = """
|
||||||
|
<equality>
|
||||||
|
<connect body1="body1" body2="body2" anchor="1 2 3"/>
|
||||||
|
</equality>
|
||||||
|
"""
|
||||||
|
_WELD_SITE = """
|
||||||
|
<equality>
|
||||||
|
<weld site1="site1" site2="site2"/>
|
||||||
|
</equality>
|
||||||
|
"""
|
||||||
|
_WELD_BODY = """
|
||||||
|
<equality>
|
||||||
|
<weld body1="body1" body2="body2"/>
|
||||||
|
</equality>
|
||||||
|
"""
|
||||||
|
_CONNECT_SITE_WELD_SITE = """
|
||||||
|
<equality>
|
||||||
|
<connect site1="site1" site2="site2"/>
|
||||||
|
<weld site1="site1" site2="site2"/>
|
||||||
|
</equality>
|
||||||
|
"""
|
||||||
|
_WELD_SITE_CONNECT_SITE = """
|
||||||
|
<equality>
|
||||||
|
<weld site1="site1" site2="site2"/>
|
||||||
|
<connect site1="site1" site2="site2"/>
|
||||||
|
</equality>
|
||||||
|
"""
|
||||||
|
_WELD_SITE_CONNECT_SITE_WELD_BODY = """
|
||||||
|
<equality>
|
||||||
|
<weld site1="site1" site2="site2"/>
|
||||||
|
<connect site1="site1" site2="site2"/>
|
||||||
|
<weld body1="body1" body2="body2"/>
|
||||||
|
</equality>
|
||||||
|
"""
|
||||||
|
_CONNECT_SITE_WELD_SITE_WELD_BODY = """
|
||||||
|
<equality>
|
||||||
|
<connect site1="site1" site2="site2"/>
|
||||||
|
<weld site1="site1" site2="site2"/>
|
||||||
|
<weld body1="body1" body2="body2"/>
|
||||||
|
</equality>
|
||||||
|
"""
|
||||||
|
_CONNECT_SITE_CONNECT_BODY_CONNECT_WELD = """
|
||||||
|
<equality>
|
||||||
|
<connect site1="site1" site2="site2"/>
|
||||||
|
<connect body1="body1" body2="body2" anchor="1 2 3"/>
|
||||||
|
<weld body1="body1" body2="body2"/>
|
||||||
|
</equality>
|
||||||
|
"""
|
||||||
|
|
||||||
@parameterized.parameters(ConeType)
|
@parameterized.parameters(
|
||||||
def test_rnepostconstraint(self, cone_type):
|
('', ConeType.PYRAMIDAL, None),
|
||||||
|
('', ConeType.ELLIPTIC, None),
|
||||||
|
(_CONNECT_SITE, ConeType.PYRAMIDAL, None),
|
||||||
|
(_CONNECT_BODY, ConeType.PYRAMIDAL, None),
|
||||||
|
(_WELD_SITE, ConeType.PYRAMIDAL, None),
|
||||||
|
(_WELD_BODY, ConeType.PYRAMIDAL, None),
|
||||||
|
(_CONNECT_SITE_WELD_SITE, ConeType.PYRAMIDAL, None),
|
||||||
|
(
|
||||||
|
_WELD_SITE_CONNECT_SITE,
|
||||||
|
ConeType.PYRAMIDAL,
|
||||||
|
np.array([6, 7, 8, 0, 1, 2, 3, 4, 5]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
_WELD_SITE_CONNECT_SITE_WELD_BODY,
|
||||||
|
ConeType.PYRAMIDAL,
|
||||||
|
np.array([6, 7, 8, 0, 1, 2, 3, 4, 5]),
|
||||||
|
),
|
||||||
|
(_CONNECT_SITE_WELD_SITE_WELD_BODY, ConeType.PYRAMIDAL, None),
|
||||||
|
(_CONNECT_SITE_CONNECT_BODY_CONNECT_WELD, ConeType.PYRAMIDAL, None),
|
||||||
|
)
|
||||||
|
def test_rnepostconstraint(self, equality, cone_type, efc_map):
|
||||||
"""Tests MJX rne_postconstraint function to match MuJoCo mj_rnePostConstraint."""
|
"""Tests MJX rne_postconstraint function to match MuJoCo mj_rnePostConstraint."""
|
||||||
|
|
||||||
m = mujoco.MjModel.from_xml_string("""
|
m = mujoco.MjModel.from_xml_string(f"""
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
<geom name="floor" size="10 10 .05" type="plane"/>
|
<geom name="floor" size="10 10 .05" type="plane"/>
|
||||||
<body pos="0 0 1">
|
<site name="site1"/>
|
||||||
|
<body name="body1">
|
||||||
|
</body>
|
||||||
|
<body pos="0 0 1" name="body2">
|
||||||
<joint type="ball" damping="1"/>
|
<joint type="ball" damping="1"/>
|
||||||
<geom type="capsule" size="0.1 0.5" fromto="0 0 0 0.5 0 0" condim="1"/>
|
<geom type="capsule" size="0.1 0.5" fromto="0 0 0 0.5 0 0" condim="1"/>
|
||||||
<body pos="0.5 0 0">
|
<body pos="0.5 0 0">
|
||||||
<joint type="ball" damping="1"/>
|
<joint type="ball" damping="1"/>
|
||||||
<geom type="capsule" size="0.1 0.5" fromto="0 0 0 0.5 0 0" condim="3"/>
|
<geom type="capsule" size="0.1 0.5" fromto="0 0 0 0.5 0 0" condim="3"/>
|
||||||
|
<site name="site2"/>
|
||||||
</body>
|
</body>
|
||||||
</body>
|
</body>
|
||||||
<body pos="0 1 1">
|
<body pos="0 1 1">
|
||||||
@@ -256,6 +333,7 @@ class RnePostConstraintTest(parameterized.TestCase):
|
|||||||
</body>
|
</body>
|
||||||
</body>
|
</body>
|
||||||
</worldbody>
|
</worldbody>
|
||||||
|
{equality}
|
||||||
<keyframe>
|
<keyframe>
|
||||||
<key qpos='0.424577 0.450592 0.451703 -0.642391 0.729379 0.545151 0.407756 0.0674697 0.424577 1.450592 0.451703 -0.642391 0.729379 0.545151 0.407756 0.0674697'/>
|
<key qpos='0.424577 0.450592 0.451703 -0.642391 0.729379 0.545151 0.407756 0.0674697 0.424577 1.450592 0.451703 -0.642391 0.729379 0.545151 0.407756 0.0674697'/>
|
||||||
</keyframe>
|
</keyframe>
|
||||||
@@ -273,6 +351,11 @@ class RnePostConstraintTest(parameterized.TestCase):
|
|||||||
mx = mjx.put_model(m)
|
mx = mjx.put_model(m)
|
||||||
dx = mjx.put_data(m, d)
|
dx = mjx.put_data(m, d)
|
||||||
|
|
||||||
|
if efc_map is not None:
|
||||||
|
efc_force = d.efc_force.copy()
|
||||||
|
efc_force[: len(efc_map)] = d.efc_force[efc_map]
|
||||||
|
dx = dx.tree_replace({'_impl.efc_force': jp.array(efc_force)})
|
||||||
|
|
||||||
# rne postconstraint
|
# rne postconstraint
|
||||||
mujoco.mj_rnePostConstraint(m, d)
|
mujoco.mj_rnePostConstraint(m, d)
|
||||||
dx = jax.jit(mjx.rne_postconstraint)(mx, dx)
|
dx = jax.jit(mjx.rne_postconstraint)(mx, dx)
|
||||||
|
|||||||
Reference in New Issue
Block a user